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

Test staging
This commit is contained in:
Keleborn 2026-04-10 15:17:50 -07:00 committed by GitHub
commit 9fa03dc83f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 1857 additions and 1420 deletions

View File

@ -35,4 +35,5 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit ${{ github.event.pull_request.number }} \
--repo ${{ github.repository }} \
--add-label "Added translation"

View File

@ -582,7 +582,7 @@ AiPlayerbot.AutoGearScoreLimit = 0
AiPlayerbot.BotCheats = "food,taxi,raid"
# List of attunement quests (comma-separated list of quest IDs) that are automatically completed for all bots.
# While mod-playerbots does not restore removed attunement requirements, although other mods, such as mod-individual-progression, may do so.
# While mod-playerbots does not restore removed attunement requirements, other mods, such as mod-individual-progression, may do so.
# This is meant to exclude bots from such requirements.
#
# Default:
@ -865,34 +865,65 @@ AiPlayerbot.ExcludedHunterPetFamilies = ""
#
#
####################################################################################################
####################################################################################################
# ACTIVITIES
# ACTIVITY
#
# BotActiveAlone
# - Controls how many bots are active when no real players are nearby.
# - Think of it as a rough percentage: 10 means approximately 10% of bots will be active.
# Not exact — the actual number may vary slightly per rotation cycle.
# - The active bots rotate: every <DurationSeconds> a different set of bots takes a turn.
# - The real number of active bots will always be higher than this value, because bots in
# combat, dungeons, battlegrounds, LFG queue, groups with real players, etc. are always
# forced active on top of this (see force rules below).
# - Set to 100 (with SmartScale off) = all bots always active. Maximum server load.
# - Set to 0 = only bots that match a force rule below will be active.
#
# Specify percent of active bots
# The default is 100% but will be automatically adjusted if botActiveAloneSmartScale
# is enabled. Regardless, this value is only applied to inactive areas where no real players
# are detected. When real players are nearby, the value is always enforced to 100%
AiPlayerbot.BotActiveAlone = 100
# BotActiveAloneDurationSeconds
# - How often the active roster rotates (in seconds). A different group of bots wakes up
# and the previous group may go idle.
# - This is a minimum, not exact. If a bot is in combat or meets any force rule when the
# rotation happens, it stays active until those conditions end — it won't be cut off
# mid-fight just because its turn expired.
#
AiPlayerbot.BotActiveAlone = 10
AiPlayerbot.BotActiveAloneDurationSeconds = 30
# Force botActiveAlone when bot is within the specified distance of a real player
#
# Force-active rules (1 = on, 0 = off)
# These override the percentage above. If any of these conditions is true, the bot stays active.
#
# InRadius - A real player is within this many yards (set to 0 to disable).
# InZone - A real player is in the same zone (e.g. Elwynn Forest).
# InMap - A real player is on the same continent (e.g. Eastern Kingdoms).
# IsFriend - A real player has this bot on their friends list.
# InGuild - This bot is in a guild that has a real player in it.
#
# Bots are also always forced active (not configurable) when:
# in combat, inside a dungeon/raid/BG, in a BG or LFG queue,
# grouped with a real player, or controlled by a real player.
#
AiPlayerbot.BotActiveAloneForceWhenInRadius = 150
AiPlayerbot.BotActiveAloneForceWhenInZone = 1
AiPlayerbot.BotActiveAloneForceWhenInMap = 0
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 1
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 0
AiPlayerbot.BotActiveAloneForceWhenInGuild = 1
# SmartScale (automatic scaling of percentage of active bots based on latency)
# The default is 1. When enabled (smart) scales the 'BotActiveAlone' value.
# (The scaling will be overruled by the BotActiveAloneForceWhen...rules)
# SmartScale — automatically reduces active bots when the server is struggling.
# Monitors the server's update time (how long each server tick takes in milliseconds).
# When the server slows down, fewer bots are kept active to reduce load.
#
# Limitfloor - when DIFF (latency) is above floor, activity scaling begins
# LimitCeiling - when DIFF (latency) is above ceiling, activity is 0%
# Floor (default 50ms) - Below this, no reduction. Server is running fine.
# Ceiling (default 200ms) - At or above this, all non-forced bots are paused.
# Between floor and ceiling, activity scales down gradually.
# Example: BotActiveAlone=10, floor=50, ceiling=200
# Server at 50ms → ~10% active (no reduction)
# Server at 125ms → ~5% active (half reduction)
# Server at 200ms → 0% active (only forced bots remain)
#
# MinLevel - only apply scaling when level is above or equal to min(bot)Level
# MaxLevel - only apply scaling when level is lower or equal of max(bot)Level
# MinLevel/MaxLevel — only bots within this level range are affected by SmartScale.
# Bots outside the range always use the full BotActiveAlone value.
# Force rules always win over SmartScale.
#
AiPlayerbot.botActiveAloneSmartScale = 1
AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor = 50
@ -1687,7 +1718,7 @@ AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530135201051
AiPlayerbot.PremadeSpecLink.9.1.70 = -003203301135112530135201051-55
AiPlayerbot.PremadeSpecLink.9.1.80 = -003203301135112530135221351-55000005
AiPlayerbot.PremadeSpecName.9.2 = destro pve
AiPlayerbot.PremadeSpecGlyph.9.2 = 45785,43390,42454,43394,43393,45785
AiPlayerbot.PremadeSpecGlyph.9.2 = 45785,43390,42454,43394,43393,42453
AiPlayerbot.PremadeSpecLink.9.2.60 = --05203215200231051305031151
AiPlayerbot.PremadeSpecLink.9.2.80 = 23-0302-05203215220331051335231351
AiPlayerbot.PremadeSpecName.9.3 = affli pvp

View File

@ -151,7 +151,9 @@ bool CastMeleeSpellAction::isUseful()
return CastSpellAction::isUseful();
}
CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner, float needLifeTime) : CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(
PlayerbotAI* botAI, std::string const spell, bool isOwner, float needLifeTime) :
CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
{
range = ATTACK_DISTANCE;
}
@ -203,6 +205,35 @@ bool CastEnchantItemAction::isPossible()
return spellId && AI_VALUE2(Item*, "item for spell", spellId);
}
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell)
: CastEnchantItemAction(botAI, spell) {}
bool CastEnchantItemMainHandAction::isPossible()
{
if (!CastEnchantItemAction::isPossible())
return false;
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
return item && !item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) &&
item->GetTemplate()->Class == ITEM_CLASS_WEAPON;
}
CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell)
: CastEnchantItemAction(botAI, spell) {}
bool CastEnchantItemOffHandAction::isPossible()
{
if (!CastEnchantItemAction::isPossible())
return false;
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (!item || item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
return false;
uint32 invType = item->GetTemplate()->InventoryType;
return invType == INVTYPE_WEAPON || invType == INVTYPE_WEAPONOFFHAND;
}
CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount,
HealingManaEfficiency manaEfficiency, bool isOwner)
: CastAuraSpellAction(botAI, spell, isOwner), estAmount(estAmount), manaEfficiency(manaEfficiency)

View File

@ -130,6 +130,20 @@ public:
std::string const GetTargetName() override { return "self target"; }
};
class CastEnchantItemMainHandAction : public CastEnchantItemAction
{
public:
CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell);
bool isPossible() override;
};
class CastEnchantItemOffHandAction : public CastEnchantItemAction
{
public:
CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell);
bool isPossible() override;
};
class CastHealingSpellAction : public CastAuraSpellAction
{
public:

View File

@ -74,8 +74,18 @@ bool MoveToTravelTargetAction::Execute(Event /*event*/)
float maxDistance = target->getDestination()->getRadiusMin();
// Evenly distribute around the target.
float angle = 2 * M_PI * urand(0, 100) / 100.0;
// Spread bots around the target but keep the offset stable per
// (bot, destination) pair. Previously the angle and radius were
// re-rolled every time the action re-entered (i.e. every tick the
// bot wasn't already moving), which made bots oscillate between
// two random points around the same quest POI instead of
// committing to one approach.
uint32 botLow = bot->GetGUID().GetCounter();
int32 destSeed = static_cast<int32>(location.GetPositionX()) * 73856093 ^
static_cast<int32>(location.GetPositionY()) * 19349663;
uint32 seed = botLow ^ static_cast<uint32>(destSeed);
float angle = 2.0f * static_cast<float>(M_PI) * static_cast<float>(seed % 1000) / 1000.0f;
float mod = 0.5f + static_cast<float>((seed / 1000) % 1000) / 2000.0f; // [0.5, 1.0]
if (target->getMaxTravelTime() > target->getTimeLeft()) // The bot is late. Speed it up.
{
@ -89,9 +99,6 @@ bool MoveToTravelTargetAction::Execute(Event /*event*/)
float z = location.GetPositionZ();
float mapId = location.GetMapId();
// Move between 0.5 and 1.0 times the maxDistance.
float mod = frand(50.f, 100.f) / 100.0f;
x += cos(angle) * maxDistance * mod;
y += sin(angle) * maxDistance * mod;

View File

@ -101,6 +101,11 @@ bool MovementAction::MoveNear(WorldObject* target, float distance, MovementPrior
float x = target->GetPositionX() + cos(angle) * distance;
float y = target->GetPositionY() + sin(angle) * distance;
float z = target->GetPositionZ();
// Clamp Z to the terrain under the offset point so we don't
// hand PointMovementGenerator a Z that matches the target's
// floor but not the sampled (x,y) — avoids straight-line
// fallbacks through geometry.
bot->UpdateAllowedPositionZ(x, y, z);
if (!bot->IsWithinLOS(x, y, z))
continue;
@ -250,7 +255,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop();
// botAI->InterruptSpell();
// }
DoMovePoint(bot, x, y, z, generatePath, backwards);
DoMovePoint(bot, x, y, modifiedZ, generatePath, backwards);
float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay)
{
@ -258,7 +263,8 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
}
delay = std::max(.0f, delay);
delay = std::min((float)sPlayerbotAIConfig.maxWaitForMove, delay);
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
AI_VALUE(LastMovement&, "last movement")
.Set(mapId, x, y, modifiedZ, bot->GetOrientation(), delay, priority);
return true;
}
}
@ -778,15 +784,17 @@ bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriorit
float dx = cos(angle) * needToGo + bx;
float dy = sin(angle) * needToGo + by;
float dz; // = std::max(bz, tz); // calc accurate z position to avoid stuck
// Start from a seed Z between bot and target, then clamp to the
// terrain under (dx,dy). Linear interpolation alone ignores hills
// between the two units and fed PointMovementGenerator a Z that
// could be well above/below ground, triggering straight-line
// fallbacks through walls.
float dz;
if (distanceToTarget > CONTACT_DISTANCE)
{
dz = bz + (tz - bz) * (needToGo / distanceToTarget);
}
else
{
dz = tz;
}
bot->UpdateAllowedPositionZ(dx, dy, dz);
return MoveTo(target->GetMapId(), dx, dy, dz, false, false, false, false, priority);
}

View File

@ -44,7 +44,12 @@ WorldPosition GetBestPoint(AiObjectContext* context, Player* bot, Unit* target,
float z = targetPosition.GetPositionZ() + 1.0f;
WorldPosition point(targetPosition.GetMapId(), x, y, z);
point.setZ(point.getHeight());
float groundZ = bot->GetMapHeight(x, y, z);
if (groundZ == INVALID_HEIGHT || groundZ == VMAP_INVALID_HEIGHT_VALUE)
continue;
point.setZ(groundZ);
// Check line of sight to target
if (!target->IsWithinLOS(point.GetPositionX(), point.GetPositionY(),
@ -88,8 +93,11 @@ bool WaitForAttackKeepSafeDistanceAction::Execute(Event /*event*/)
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target)
return false;
// If our target is moving towards a stationary unit, use that unit as anchor
if (target && !target->IsStopped())
if (!target->IsStopped())
{
ObjectGuid targetGuid = target->GetTarget();
if (targetGuid)
@ -100,7 +108,7 @@ bool WaitForAttackKeepSafeDistanceAction::Execute(Event /*event*/)
}
}
if (target && target->IsAlive())
if (target->IsAlive())
{
float safeDistance = std::max(
target->GetCombatReach() + ATTACK_DISTANCE,

View File

@ -481,6 +481,13 @@ bool FearCharmSleepTrigger::IsActive()
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP);
}
bool FearSleepSapTrigger::IsActive()
{
return bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SAPPED);
}
bool HasAuraStackTrigger::IsActive()
{
Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack);

View File

@ -762,6 +762,14 @@ public:
bool IsActive() override;
};
class FearSleepSapTrigger : public Trigger
{
public:
FearSleepSapTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fear sleep sap", 1) {}
bool IsActive() override;
};
class IsSwimmingTrigger : public Trigger
{
public:

View File

@ -16,5 +16,5 @@ bool NoRtiTrigger::IsActive()
return false;
Unit* target = AI_VALUE(Unit*, "rti target");
return target != nullptr;
return target == nullptr;
}

View File

@ -62,6 +62,7 @@ public:
creators["generic boost"] = &TriggerContext::generic_boost;
creators["loss of control"] = &TriggerContext::loss_of_control;
creators["fear charm sleep"] = &TriggerContext::fear_charm_sleep;
creators["fear sleep sap"] = &TriggerContext::fear_sleep_sap;
creators["protect party member"] = &TriggerContext::protect_party_member;
@ -369,6 +370,7 @@ private:
static Trigger* generic_boost(PlayerbotAI* botAI) { return new GenericBoostTrigger(botAI); }
static Trigger* loss_of_control(PlayerbotAI* botAI) { return new LossOfControlTrigger(botAI); }
static Trigger* fear_charm_sleep(PlayerbotAI* botAI) { return new FearCharmSleepTrigger(botAI); }
static Trigger* fear_sleep_sap(PlayerbotAI* botAI) { return new FearSleepSapTrigger(botAI); }
static Trigger* PartyMemberCriticalHealth(PlayerbotAI* botAI)
{
return new PartyMemberCriticalHealthTrigger(botAI);

View File

@ -0,0 +1,64 @@
/*
* 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 "PartyMemberSnaredTargetValue.h"
#include <limits>
#include "PlayerbotAIAware.h"
#include "Playerbots.h"
class PartyMemberSnaredTargetPredicate : public FindPlayerPredicate, public PlayerbotAIAware
{
public:
PartyMemberSnaredTargetPredicate(PlayerbotAI* botAI)
: PlayerbotAIAware(botAI)
{
}
bool Check(Unit* unit) override
{
if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit == botAI->GetBot())
return false;
if (unit->GetMapId() != botAI->GetBot()->GetMapId())
return false;
if (!botAI->GetBot()->IsWithinLOSInMap(unit))
return false;
return botAI->IsMovementImpaired(unit);
}
};
Unit* PartyMemberSnaredTargetValue::Calculate()
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
PartyMemberSnaredTargetPredicate predicate(botAI);
Player* bestTarget = nullptr;
float closestDistanceSq = std::numeric_limits<float>::max();
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member)
continue;
if (!predicate.Check(member))
continue;
float const distanceSq = bot->GetExactDist2dSq(member->GetPositionX(), member->GetPositionY());
if (distanceSq < closestDistanceSq)
{
closestDistanceSq = distanceSq;
bestTarget = member;
}
}
return bestTarget;
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PARTYMEMBERSNAREDTARGETVALUE_H
#define _PLAYERBOT_PARTYMEMBERSNAREDTARGETVALUE_H
#include "NamedObjectContext.h"
#include "PartyMemberValue.h"
class PartyMemberSnaredTargetValue : public PartyMemberValue
{
public:
PartyMemberSnaredTargetValue(PlayerbotAI* botAI, std::string const name = "party member snared target")
: PartyMemberValue(botAI, name)
{
}
protected:
Unit* Calculate() override;
};
#endif

View File

@ -66,6 +66,7 @@
#include "PartyMemberToDispel.h"
#include "PartyMemberToHeal.h"
#include "PartyMemberToResurrect.h"
#include "PartyMemberSnaredTargetValue.h"
#include "PartyMemberWithoutAuraValue.h"
#include "PartyMemberWithoutItemValue.h"
#include "PetTargetValue.h"
@ -152,6 +153,7 @@ public:
creators["duel target"] = &ValueContext::duel_target;
creators["party member to dispel"] = &ValueContext::party_member_to_dispel;
creators["party member to protect"] = &ValueContext::party_member_to_protect;
creators["party member snared target"] = &ValueContext::party_member_snared_target;
creators["health"] = &ValueContext::health;
creators["rage"] = &ValueContext::rage;
creators["energy"] = &ValueContext::energy;
@ -450,6 +452,7 @@ private:
static UntypedValue* party_member_to_resurrect(PlayerbotAI* botAI) { return new PartyMemberToResurrect(botAI); }
static UntypedValue* party_member_to_dispel(PlayerbotAI* botAI) { return new PartyMemberToDispel(botAI); }
static UntypedValue* party_member_to_protect(PlayerbotAI* botAI) { return new PartyMemberToProtect(botAI); }
static UntypedValue* party_member_snared_target(PlayerbotAI* botAI) { return new PartyMemberSnaredTargetValue(botAI); }
static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); }

View File

@ -48,3 +48,55 @@ bool CastRaiseDeadAction::Execute(Event event)
return true;
}
Unit* CastHysteriaAction::GetTarget()
{
Group* group = bot->GetGroup();
if (!group)
{
if (!bot->HasAura(49016))
return bot;
return nullptr;
}
Unit* rangedDps = nullptr;
Unit* tank = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (member->GetMap() != bot->GetMap() || bot->GetDistance(member) > sPlayerbotAIConfig.spellDistance)
continue;
// Skip if already has hysteria
if (member->HasAura(49016))
continue;
// Priority 1: Melee DPS
if (botAI->IsMelee(member) && botAI->IsDps(member))
return member;
// Priority 2: Ranged DPS (physical, not casters)
if (!rangedDps && botAI->IsRanged(member) && botAI->IsDps(member) && !botAI->IsCaster(member))
rangedDps = member;
// Priority 3: Tank
if (!tank && botAI->IsTank(member))
tank = member;
}
if (rangedDps)
return rangedDps;
if (tank)
return tank;
// Fallback to self if no hysteria
if (!bot->HasAura(49016))
return bot;
return nullptr;
}

View File

@ -340,4 +340,11 @@ public:
CastBloodTapAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "blood tap") {}
};
class CastHysteriaAction : public CastSpellAction
{
public:
CastHysteriaAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hysteria") {}
Unit* GetTarget() override;
};
#endif

View File

@ -100,6 +100,7 @@ public:
creators["dd cd and no desolation"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_no_desolation;
creators["death and decay cooldown"] = &DeathKnightTriggerFactoryInternal::death_and_decay_cooldown;
creators["army of the dead"] = &DeathKnightTriggerFactoryInternal::army_of_the_dead;
creators["hysteria no cd"] = &DeathKnightTriggerFactoryInternal::hysteria_no_cd;
}
private:
@ -152,6 +153,7 @@ private:
}
static Trigger* death_and_decay_cooldown(PlayerbotAI* botAI) { return new DeathAndDecayCooldownTrigger(botAI); }
static Trigger* army_of_the_dead(PlayerbotAI* botAI) { return new ArmyOfTheDeadTrigger(botAI); }
static Trigger* hysteria_no_cd(PlayerbotAI* botAI) { return new HysteriaNoCooldownTrigger(botAI); }
};
class DeathKnightAiObjectContextInternal : public NamedObjectContext<Action>
@ -209,7 +211,7 @@ public:
creators["vampiric blood"] = &DeathKnightAiObjectContextInternal::vampiric_blood;
creators["death pact"] = &DeathKnightAiObjectContextInternal::death_pact;
creators["death rune_mastery"] = &DeathKnightAiObjectContextInternal::death_rune_mastery;
// creators["hysteria"] = &DeathKnightAiObjectContextInternal::hysteria;
creators["hysteria"] = &DeathKnightAiObjectContextInternal::hysteria;
creators["dancing rune weapon"] = &DeathKnightAiObjectContextInternal::dancing_rune_weapon;
creators["dark command"] = &DeathKnightAiObjectContextInternal::dark_command;
}
@ -265,7 +267,7 @@ private:
static Action* vampiric_blood(PlayerbotAI* botAI) { return new CastVampiricBloodAction(botAI); }
static Action* death_pact(PlayerbotAI* botAI) { return new CastDeathPactAction(botAI); }
static Action* death_rune_mastery(PlayerbotAI* botAI) { return new CastDeathRuneMasteryAction(botAI); }
// static Action* hysteria(PlayerbotAI* botAI) { return new CastHysteriaAction(botAI); }
static Action* hysteria(PlayerbotAI* botAI) { return new CastHysteriaAction(botAI); }
static Action* dancing_rune_weapon(PlayerbotAI* botAI) { return new CastDancingRuneWeaponAction(botAI); }
static Action* dark_command(PlayerbotAI* botAI) { return new CastDarkCommandAction(botAI); }
static Action* mind_freeze_on_enemy_healer(PlayerbotAI* botAI)

View File

@ -50,7 +50,9 @@ private:
{
NextAction("frost presence")
},
/*A*/ {},
/*A*/ {
NextAction("blood strike")
},
/*C*/ {}
);
}
@ -89,13 +91,11 @@ BloodDKStrategy::BloodDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
std::vector<NextAction> BloodDKStrategy::getDefaultActions()
{
return {
NextAction("rune strike", ACTION_DEFAULT + 0.8f),
NextAction("icy touch", ACTION_DEFAULT + 0.7f),
NextAction("heart strike", ACTION_DEFAULT + 0.6f),
NextAction("blood strike", ACTION_DEFAULT + 0.5f),
NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
NextAction("death coil", ACTION_DEFAULT + 0.3f),
NextAction("plague strike", ACTION_DEFAULT + 0.2f),
NextAction("rune strike", ACTION_DEFAULT + 0.6f),
NextAction("icy touch", ACTION_DEFAULT + 0.5f),
NextAction("heart strike", ACTION_DEFAULT + 0.4f),
NextAction("dancing rune weapon", ACTION_DEFAULT + 0.3f),
NextAction("death coil", ACTION_DEFAULT + 0.2f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT)
};
@ -105,6 +105,14 @@ void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode(
"hysteria no cd",
{
NextAction("hysteria", ACTION_NORMAL + 4)
}
)
);
triggers.push_back(
new TriggerNode(
"rune strike",
@ -162,4 +170,12 @@ void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
)
);
triggers.push_back(
new TriggerNode(
"high unholy rune",
{
NextAction("death strike", ACTION_HIGH + 1)
}
)
);
}

View File

@ -91,7 +91,6 @@ std::vector<NextAction> FrostDKStrategy::getDefaultActions()
return {
NextAction("obliterate", ACTION_DEFAULT + 0.7f),
NextAction("frost strike", ACTION_DEFAULT + 0.4f),
NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT)
};

View File

@ -41,16 +41,10 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
{
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 1) }));
triggers.push_back(
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) }));
triggers.push_back(
new TriggerNode("bone shield", { NextAction("bone shield", 21.0f) }));
triggers.push_back(
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
}
void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@ -165,12 +165,6 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
MeleeCombatStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 5) }));
triggers.push_back(
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
triggers.push_back(
new TriggerNode("mind freeze", { NextAction("mind freeze", ACTION_HIGH + 1) }));
triggers.push_back(
@ -179,7 +173,8 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode(
"horn of winter", { NextAction("horn of winter", ACTION_NORMAL + 1) }));
triggers.push_back(new TriggerNode("critical health",
{ NextAction("death pact", ACTION_HIGH + 5) }));
{ NextAction("raise dead", ACTION_HIGH + 6),
NextAction("death pact", ACTION_HIGH + 5) }));
triggers.push_back(
new TriggerNode("low health", { NextAction("icebound fortitude", ACTION_HIGH + 5),
@ -190,4 +185,11 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction("blood boil", ACTION_NORMAL + 3) }));
triggers.push_back(
new TriggerNode("pestilence glyph", { NextAction("pestilence", ACTION_HIGH + 9) }));
triggers.push_back(
new TriggerNode("no rune",
{
NextAction("empower rune weapon", ACTION_HIGH + 1)
}
)
);
}

View File

@ -87,6 +87,13 @@ void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 5) }));
triggers.push_back(
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
triggers.push_back(
new TriggerNode(
"death and decay cooldown",
@ -146,13 +153,6 @@ void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
)
);
triggers.push_back(
new TriggerNode("no rune",
{
NextAction("empower rune weapon", ACTION_HIGH + 1)
}
)
);
triggers.push_back(
new TriggerNode(
"army of the dead",

View File

@ -198,4 +198,10 @@ public:
ArmyOfTheDeadTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "army of the dead") {}
};
class HysteriaNoCooldownTrigger : public SpellNoCooldownTrigger
{
public:
HysteriaNoCooldownTrigger(PlayerbotAI* botAI) : SpellNoCooldownTrigger(botAI, "hysteria") {}
};
#endif

View File

@ -23,6 +23,12 @@ public:
CastGrowlAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "growl") {}
};
class CastChallengingRoarAction : public CastMeleeDebuffSpellAction
{
public:
CastChallengingRoarAction(PlayerbotAI* botAI) : CastMeleeDebuffSpellAction(botAI, "challenging roar") {}
};
class CastMaulAction : public CastMeleeSpellAction
{
public:

View File

@ -183,6 +183,7 @@ public:
creators["bash"] = &DruidAiObjectContextInternal::bash;
creators["swipe"] = &DruidAiObjectContextInternal::swipe;
creators["growl"] = &DruidAiObjectContextInternal::growl;
creators["challenging roar"] = &DruidAiObjectContextInternal::challenging_roar;
creators["demoralizing roar"] = &DruidAiObjectContextInternal::demoralizing_roar;
creators["hibernate"] = &DruidAiObjectContextInternal::hibernate;
creators["entangling roots"] = &DruidAiObjectContextInternal::entangling_roots;
@ -277,6 +278,7 @@ private:
static Action* bash(PlayerbotAI* botAI) { return new CastBashAction(botAI); }
static Action* swipe(PlayerbotAI* botAI) { return new CastSwipeAction(botAI); }
static Action* growl(PlayerbotAI* botAI) { return new CastGrowlAction(botAI); }
static Action* challenging_roar(PlayerbotAI* botAI) { return new CastChallengingRoarAction(botAI); }
static Action* demoralizing_roar(PlayerbotAI* botAI) { return new CastDemoralizingRoarAction(botAI); }
static Action* moonkin_form(PlayerbotAI* botAI) { return new CastMoonkinFormAction(botAI); }
static Action* hibernate(PlayerbotAI* botAI) { return new CastHibernateAction(botAI); }

View File

@ -212,6 +212,7 @@ void BearTankDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
)
);
triggers.push_back(new TriggerNode("high aoe", {NextAction("challenging roar", ACTION_HIGH + 8)}));
triggers.push_back(
new TriggerNode(
"lose aggro",

View File

@ -16,18 +16,15 @@ bool CastViperStingAction::isUseful()
AI_VALUE2(uint8, "mana", "current target") >= 30;
}
bool CastAspectOfTheCheetahAction::isUseful()
{
return !botAI->HasAnyAuraOf(GetTarget(), "aspect of the cheetah", "aspect of the pack", nullptr);
}
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;
}
@ -36,11 +33,14 @@ 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 (bot->HasSpell(53301) || bot->HasSpell(60051) ||
bot->HasSpell(60052) || bot->HasSpell(60053)) // Explosive Shot spell IDs
return false;
// Armor Penetration rating check - will not cast Arcane Shot above 435 ArP
int32 armorPenRating = bot->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1) + bot->GetUInt32Value(CR_ARMOR_PENETRATION);
int32 armorPenRating =
bot->GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1) + bot->GetUInt32Value(CR_ARMOR_PENETRATION);
if (armorPenRating > 435)
return false;
@ -52,18 +52,26 @@ 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
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;
}
Value<Unit*>* CastFreezingTrap::GetTargetValue() { return context->GetValue<Unit*>("cc target", "freezing trap"); }
Value<Unit*>* CastFreezingTrap::GetTargetValue()
{
return context->GetValue<Unit*>("cc target", "freezing trap");
}
bool FeedPetAction::Execute(Event /*event*/)
{
if (Pet* pet = bot->GetPet())
if (pet->getPetType() == HUNTER_PET && pet->GetHappinessState() != HAPPY)
pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS)));
if (Pet* pet = bot->GetPet(); pet && pet->getPetType() == HUNTER_PET &&
pet->GetHappinessState() != HAPPY)
{
pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS)));
}
return true;
}
@ -79,6 +87,7 @@ bool CastAutoShotAction::isUseful()
{
return false;
}
return AI_VALUE(uint32, "active spell") != AI_VALUE2(uint32, "spell id", getName());
}
@ -87,6 +96,7 @@ bool CastDisengageAction::Execute(Event event)
Unit* target = AI_VALUE(Unit*, "current target");
if (!target)
return false;
// can cast spell check passed in isUseful()
bot->SetOrientation(bot->GetAngle(target));
return CastSpellAction::Execute(event);
@ -97,11 +107,20 @@ bool CastDisengageAction::isUseful()
return !botAI->HasStrategy("trap weave", BOT_STATE_COMBAT);
}
Value<Unit*>* CastScareBeastCcAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", "scare beast"); }
Value<Unit*>* CastScareBeastCcAction::GetTargetValue()
{
return context->GetValue<Unit*>("cc target", "scare beast");
}
bool CastScareBeastCcAction::Execute(Event /*event*/) { return botAI->CastSpell("scare beast", GetTarget()); }
bool CastScareBeastCcAction::Execute(Event /*event*/)
{
return botAI->CastSpell("scare beast", GetTarget());
}
bool CastWingClipAction::isUseful() { return CastSpellAction::isUseful() && !botAI->HasAura(spell, GetTarget()); }
bool CastWingClipAction::isUseful()
{
return CastSpellAction::isUseful() && !botAI->HasAura(spell, GetTarget());
}
std::vector<NextAction> CastWingClipAction::getPrerequisites()
{

View File

@ -19,52 +19,58 @@ class Unit;
class CastTrueshotAuraAction : public CastBuffSpellAction
{
public:
CastTrueshotAuraAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "trueshot aura") {}
CastTrueshotAuraAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "trueshot aura") {}
};
class CastAspectOfTheDragonhawkAction : public CastBuffSpellAction
{
public:
CastAspectOfTheDragonhawkAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "aspect of the dragonhawk") {}
};
class CastAspectOfTheHawkAction : public CastBuffSpellAction
{
public:
CastAspectOfTheHawkAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "aspect of the hawk") {}
CastAspectOfTheHawkAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "aspect of the hawk") {}
bool isUseful() override;
};
class CastAspectOfTheMonkeyAction : public CastBuffSpellAction
{
public:
CastAspectOfTheMonkeyAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "aspect of the monkey") {}
};
class CastAspectOfTheDragonhawkAction : public CastBuffSpellAction
{
public:
CastAspectOfTheDragonhawkAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "aspect of the dragonhawk") {}
CastAspectOfTheMonkeyAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "aspect of the monkey") {}
};
class CastAspectOfTheWildAction : public CastBuffSpellAction
{
public:
CastAspectOfTheWildAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "aspect of the wild") {}
CastAspectOfTheWildAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "aspect of the wild") {}
};
class CastAspectOfTheCheetahAction : public CastBuffSpellAction
{
public:
CastAspectOfTheCheetahAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "aspect of the cheetah") {}
bool isUseful() override;
CastAspectOfTheCheetahAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "aspect of the cheetah") {}
};
class CastAspectOfThePackAction : public CastBuffSpellAction
{
public:
CastAspectOfThePackAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "aspect of the pack") {}
CastAspectOfThePackAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "aspect of the pack") {}
};
class CastAspectOfTheViperAction : public CastBuffSpellAction
{
public:
CastAspectOfTheViperAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "aspect of the viper") {}
CastAspectOfTheViperAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "aspect of the viper") {}
};
// Cooldown Spells
@ -72,26 +78,28 @@ public:
class CastRapidFireAction : public CastBuffSpellAction
{
public:
CastRapidFireAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "rapid fire") {}
CastRapidFireAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "rapid fire") {}
};
class CastDeterrenceAction : public CastBuffSpellAction
{
public:
CastDeterrenceAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "deterrence") {}
CastDeterrenceAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "deterrence") {}
};
class CastReadinessAction : public CastBuffSpellAction
{
public:
CastReadinessAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "readiness") {}
CastReadinessAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "readiness") {}
};
class CastDisengageAction : public CastSpellAction
{
public:
CastDisengageAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "disengage") {}
bool Execute(Event event) override;
bool isUseful() override;
};
@ -101,14 +109,15 @@ public:
class CastScareBeastAction : public CastSpellAction
{
public:
CastScareBeastAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "scare beast") {}
CastScareBeastAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "scare beast") {}
};
class CastScareBeastCcAction : public CastSpellAction
{
public:
CastScareBeastCcAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "scare beast on cc") {}
CastScareBeastCcAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "scare beast on cc") {}
Value<Unit*>* GetTargetValue() override;
bool Execute(Event event) override;
};
@ -116,34 +125,41 @@ public:
class CastFreezingTrap : public CastDebuffSpellAction
{
public:
CastFreezingTrap(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "freezing trap") {}
CastFreezingTrap(PlayerbotAI* botAI) :
CastDebuffSpellAction(botAI, "freezing trap") {}
Value<Unit*>* GetTargetValue() override;
};
class CastWyvernStingAction : public CastDebuffSpellAction
{
public:
CastWyvernStingAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "wyvern sting", true) {}
CastWyvernStingAction(PlayerbotAI* botAI) :
CastDebuffSpellAction(botAI, "wyvern sting", true) {}
};
class CastSilencingShotAction : public CastSpellAction
{
public:
CastSilencingShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "silencing shot") {}
CastSilencingShotAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "silencing shot") {}
};
class CastConcussiveShotAction : public CastSnareSpellAction
{
public:
CastConcussiveShotAction(PlayerbotAI* botAI) : CastSnareSpellAction(botAI, "concussive shot") {}
CastConcussiveShotAction(PlayerbotAI* botAI) :
CastSnareSpellAction(botAI, "concussive shot") {}
};
class CastIntimidationAction : public CastBuffSpellAction
{
public:
CastIntimidationAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "intimidation", false, 5000) {}
std::string const GetTargetName() override { return "pet target"; }
CastIntimidationAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "intimidation", false, 5000) {}
std::string const GetTargetName() override
{
return "pet target";
}
};
// Threat Spells
@ -151,19 +167,22 @@ public:
class CastDistractingShotAction : public CastSpellAction
{
public:
CastDistractingShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "distracting shot") {}
CastDistractingShotAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "distracting shot") {}
};
class CastMisdirectionOnMainTankAction : public BuffOnMainTankAction
{
public:
CastMisdirectionOnMainTankAction(PlayerbotAI* ai) : BuffOnMainTankAction(ai, "misdirection", true) {}
CastMisdirectionOnMainTankAction(PlayerbotAI* botAI) :
BuffOnMainTankAction(botAI, "misdirection", true) {}
};
class CastFeignDeathAction : public CastBuffSpellAction
{
public:
CastFeignDeathAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "feign death") {}
CastFeignDeathAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "feign death") {}
};
// Pet Spells
@ -172,7 +191,6 @@ class FeedPetAction : public Action
{
public:
FeedPetAction(PlayerbotAI* botAI) : Action(botAI, "feed pet") {}
bool Execute(Event event) override;
};
@ -186,27 +204,39 @@ class CastMendPetAction : public CastAuraSpellAction
{
public:
CastMendPetAction(PlayerbotAI* botAI) : CastAuraSpellAction(botAI, "mend pet") {}
std::string const GetTargetName() override { return "pet target"; }
std::string const GetTargetName() override
{
return "pet target";
}
};
class CastRevivePetAction : public CastBuffSpellAction
{
public:
CastRevivePetAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "revive pet") {}
CastRevivePetAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "revive pet") {}
};
class CastKillCommandAction : public CastBuffSpellAction
{
public:
CastKillCommandAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "kill command", false, 5000) {}
std::string const GetTargetName() override { return "pet target"; }
CastKillCommandAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "kill command", false, 5000) {}
std::string const GetTargetName() override
{
return "pet target";
}
};
class CastBestialWrathAction : public CastBuffSpellAction
{
public:
CastBestialWrathAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "bestial wrath", false, 5000) {}
std::string const GetTargetName() override { return "pet target"; }
CastBestialWrathAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "bestial wrath", false, 5000) {}
std::string const GetTargetName() override
{
return "pet target";
}
};
// Direct Damage Spells
@ -215,14 +245,18 @@ class CastAutoShotAction : public CastSpellAction
{
public:
CastAutoShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "auto shot") {}
ActionThreatType getThreatType() override { return ActionThreatType::None; }
ActionThreatType getThreatType() override
{
return ActionThreatType::None;
}
bool isUseful() override;
};
class CastArcaneShotAction : public CastSpellAction
{
public:
CastArcaneShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "arcane shot") {}
CastArcaneShotAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "arcane shot") {}
bool isUseful() override;
};
@ -235,7 +269,8 @@ public:
class CastChimeraShotAction : public CastSpellAction
{
public:
CastChimeraShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "chimera shot") {}
CastChimeraShotAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "chimera shot") {}
};
class CastSteadyShotAction : public CastSpellAction
@ -255,7 +290,8 @@ public:
class CastHuntersMarkAction : public CastDebuffSpellAction
{
public:
CastHuntersMarkAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "hunter's mark") {}
CastHuntersMarkAction(PlayerbotAI* botAI) :
CastDebuffSpellAction(botAI, "hunter's mark") {}
bool isUseful() override
{
// Bypass TTL check
@ -266,20 +302,23 @@ public:
class CastTranquilizingShotAction : public CastSpellAction
{
public:
CastTranquilizingShotAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "tranquilizing shot") {}
CastTranquilizingShotAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "tranquilizing shot") {}
};
class CastViperStingAction : public CastDebuffSpellAction
{
public:
CastViperStingAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "viper sting", true) {}
CastViperStingAction(PlayerbotAI* botAI) :
CastDebuffSpellAction(botAI, "viper sting", true) {}
bool isUseful() override;
};
class CastSerpentStingAction : public CastDebuffSpellAction
{
public:
CastSerpentStingAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "serpent sting", true) {}
CastSerpentStingAction(PlayerbotAI* botAI) :
CastDebuffSpellAction(botAI, "serpent sting", true) {}
bool isUseful() override
{
// Bypass TTL check
@ -290,7 +329,8 @@ public:
class CastScorpidStingAction : public CastDebuffSpellAction
{
public:
CastScorpidStingAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "scorpid sting", true) {}
CastScorpidStingAction(PlayerbotAI* botAI) :
CastDebuffSpellAction(botAI, "scorpid sting", true) {}
bool isUseful() override
{
// Bypass TTL check
@ -301,7 +341,8 @@ public:
class CastSerpentStingOnAttackerAction : public CastDebuffSpellOnAttackerAction
{
public:
CastSerpentStingOnAttackerAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "serpent sting", true) {}
CastSerpentStingOnAttackerAction(PlayerbotAI* botAI)
: CastDebuffSpellOnAttackerAction(botAI, "serpent sting", true) {}
bool isUseful() override
{
// Bypass TTL check
@ -312,20 +353,23 @@ public:
class CastImmolationTrapAction : public CastSpellAction
{
public:
CastImmolationTrapAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "immolation trap") {}
CastImmolationTrapAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "immolation trap") {}
bool isUseful() override;
};
class CastExplosiveTrapAction : public CastSpellAction
{
public:
CastExplosiveTrapAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "explosive trap") {}
CastExplosiveTrapAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "explosive trap") {}
};
class CastBlackArrow : public CastDebuffSpellAction
class CastBlackArrowAction : public CastDebuffSpellAction
{
public:
CastBlackArrow(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "black arrow", true) {}
CastBlackArrowAction(PlayerbotAI* botAI) :
CastDebuffSpellAction(botAI, "black arrow", true) {}
bool isUseful() override
{
if (botAI->HasStrategy("trap weave", BOT_STATE_COMBAT))
@ -335,77 +379,89 @@ public:
}
};
class CastExplosiveShotAction : public CastDebuffSpellAction
class CastExplosiveShotBaseAction : public CastDebuffSpellAction
{
public:
CastExplosiveShotAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "explosive shot", true, 0.0f) {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
CastExplosiveShotBaseAction(PlayerbotAI* botAI)
: CastDebuffSpellAction(botAI, "explosive shot", true, 0.0f) {}
};
// Rank 4
class CastExplosiveShotRank4Action : public CastDebuffSpellAction
class CastExplosiveShotRank4Action : public CastExplosiveShotBaseAction
{
public:
CastExplosiveShotRank4Action(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "explosive shot", true, 0.0f) {}
bool Execute(Event event) override { return botAI->CastSpell(60053, GetTarget()); }
CastExplosiveShotRank4Action(PlayerbotAI* botAI) :
CastExplosiveShotBaseAction(botAI) {}
bool Execute(Event event) override
{
return botAI->CastSpell(60053, GetTarget());
}
bool isUseful() override
{
Unit* target = GetTarget();
if (!target)
return false;
return !target->HasAura(60053);
}
};
// Rank 3
class CastExplosiveShotRank3Action : public CastDebuffSpellAction
class CastExplosiveShotRank3Action : public CastExplosiveShotBaseAction
{
public:
CastExplosiveShotRank3Action(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "explosive shot", true, 0.0f) {}
bool Execute(Event event) override { return botAI->CastSpell(60052, GetTarget()); }
CastExplosiveShotRank3Action(PlayerbotAI* botAI) :
CastExplosiveShotBaseAction(botAI) {}
bool Execute(Event event) override
{
return botAI->CastSpell(60052, GetTarget());
}
bool isUseful() override
{
Unit* target = GetTarget();
if (!target)
return false;
return !target->HasAura(60052);
}
};
// Rank 2
class CastExplosiveShotRank2Action : public CastDebuffSpellAction
class CastExplosiveShotRank2Action : public CastExplosiveShotBaseAction
{
public:
CastExplosiveShotRank2Action(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "explosive shot", true, 0.0f) {}
bool Execute(Event event) override { return botAI->CastSpell(60051, GetTarget()); }
CastExplosiveShotRank2Action(PlayerbotAI* botAI) :
CastExplosiveShotBaseAction(botAI) {}
bool Execute(Event event) override
{
return botAI->CastSpell(60051, GetTarget());
}
bool isUseful() override
{
Unit* target = GetTarget();
if (!target)
return false;
return !target->HasAura(60051);
}
};
// Rank 1
class CastExplosiveShotRank1Action : public CastDebuffSpellAction
class CastExplosiveShotRank1Action : public CastExplosiveShotBaseAction
{
public:
CastExplosiveShotRank1Action(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "explosive shot", true, 0.0f) {}
bool Execute(Event event) override { return botAI->CastSpell(53301, GetTarget()); }
CastExplosiveShotRank1Action(PlayerbotAI* botAI) :
CastExplosiveShotBaseAction(botAI) {}
bool Execute(Event event) override
{
return botAI->CastSpell(53301, GetTarget());
}
bool isUseful() override
{
Unit* target = GetTarget();
if (!target)
return false;
return !target->HasAura(53301);
}
};
@ -415,8 +471,8 @@ public:
class CastWingClipAction : public CastSpellAction
{
public:
CastWingClipAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "wing clip") {}
CastWingClipAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "wing clip") {}
bool isUseful() override;
std::vector<NextAction> getPrerequisites() override;
};
@ -424,13 +480,15 @@ public:
class CastRaptorStrikeAction : public CastSpellAction
{
public:
CastRaptorStrikeAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "raptor strike") {}
CastRaptorStrikeAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "raptor strike") {}
};
class CastMongooseBiteAction : public CastSpellAction
{
public:
CastMongooseBiteAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "mongoose bite") {}
CastMongooseBiteAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "mongoose bite") {}
};
// AoE Spells
@ -445,7 +503,10 @@ class CastVolleyAction : public CastSpellAction
{
public:
CastVolleyAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "volley") {}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
ActionThreatType getThreatType() override
{
return ActionThreatType::Aoe;
}
};
#endif

View File

@ -22,7 +22,6 @@ public:
HunterStrategyFactoryInternal()
{
creators["nc"] = &HunterStrategyFactoryInternal::nc;
creators["boost"] = &HunterStrategyFactoryInternal::boost;
creators["pet"] = &HunterStrategyFactoryInternal::pet;
creators["cc"] = &HunterStrategyFactoryInternal::cc;
creators["trap weave"] = &HunterStrategyFactoryInternal::trap_weave;
@ -34,7 +33,6 @@ public:
private:
static Strategy* nc(PlayerbotAI* botAI) { return new GenericHunterNonCombatStrategy(botAI); }
static Strategy* boost(PlayerbotAI* botAI) { return new HunterBoostStrategy(botAI); }
static Strategy* pet(PlayerbotAI* botAI) { return new HunterPetStrategy(botAI); }
static Strategy* cc(PlayerbotAI* botAI) { return new HunterCcStrategy(botAI); }
static Strategy* trap_weave(PlayerbotAI* botAI) { return new HunterTrapWeaveStrategy(botAI); }
@ -51,14 +49,12 @@ public:
{
creators["bspeed"] = &HunterBuffStrategyFactoryInternal::bspeed;
creators["bdps"] = &HunterBuffStrategyFactoryInternal::bdps;
creators["bmana"] = &HunterBuffStrategyFactoryInternal::bmana;
creators["rnature"] = &HunterBuffStrategyFactoryInternal::rnature;
}
private:
static Strategy* bspeed(PlayerbotAI* botAI) { return new HunterBuffSpeedStrategy(botAI); }
static Strategy* bdps(PlayerbotAI* botAI) { return new HunterBuffDpsStrategy(botAI); }
static Strategy* bmana(PlayerbotAI* botAI) { return new HunterBuffManaStrategy(botAI); }
static Strategy* rnature(PlayerbotAI* botAI) { return new HunterNatureResistanceStrategy(botAI); }
};
@ -67,7 +63,6 @@ class HunterTriggerFactoryInternal : public NamedObjectContext<Trigger>
public:
HunterTriggerFactoryInternal()
{
creators["aspect of the viper"] = &HunterTriggerFactoryInternal::aspect_of_the_viper;
creators["black arrow"] = &HunterTriggerFactoryInternal::black_arrow;
creators["no stings"] = &HunterTriggerFactoryInternal::NoStings;
creators["hunters pet dead"] = &HunterTriggerFactoryInternal::hunters_pet_dead;
@ -75,10 +70,9 @@ public:
creators["hunters pet medium health"] = &HunterTriggerFactoryInternal::hunters_pet_medium_health;
creators["hunter's mark"] = &HunterTriggerFactoryInternal::hunters_mark;
creators["freezing trap"] = &HunterTriggerFactoryInternal::freezing_trap;
creators["aspect of the pack"] = &HunterTriggerFactoryInternal::aspect_of_the_pack;
creators["rapid fire"] = &HunterTriggerFactoryInternal::rapid_fire;
creators["aspect of the hawk"] = &HunterTriggerFactoryInternal::aspect_of_the_hawk;
creators["aspect of the monkey"] = &HunterTriggerFactoryInternal::aspect_of_the_monkey;
creators["aspect of the pack"] = &HunterTriggerFactoryInternal::aspect_of_the_pack;
creators["aspect of the dragonhawk"] = &HunterTriggerFactoryInternal::aspect_of_the_dragonhawk;
creators["aspect of the wild"] = &HunterTriggerFactoryInternal::aspect_of_the_wild;
creators["aspect of the viper"] = &HunterTriggerFactoryInternal::aspect_of_the_viper;
creators["trueshot aura"] = &HunterTriggerFactoryInternal::trueshot_aura;
@ -107,10 +101,8 @@ public:
private:
static Trigger* auto_shot(PlayerbotAI* botAI) { return new AutoShotTrigger(botAI); }
static Trigger* scare_beast(PlayerbotAI* botAI) { return new ScareBeastTrigger(botAI); }
static Trigger* concussive_shot_on_snare_target(PlayerbotAI* botAI)
{
return new ConsussiveShotSnareTrigger(botAI);
}
static Trigger* concussive_shot_on_snare_target(PlayerbotAI* botAI) {
return new ConcussiveShotOnSnareTargetTrigger(botAI); }
static Trigger* pet_not_happy(PlayerbotAI* botAI) { return new HunterPetNotHappy(botAI); }
static Trigger* serpent_sting_on_attacker(PlayerbotAI* botAI) { return new SerpentStingOnAttackerTrigger(botAI); }
static Trigger* trueshot_aura(PlayerbotAI* botAI) { return new TrueshotAuraTrigger(botAI); }
@ -125,18 +117,17 @@ private:
static Trigger* freezing_trap(PlayerbotAI* botAI) { return new FreezingTrapTrigger(botAI); }
static Trigger* aspect_of_the_pack(PlayerbotAI* botAI) { return new HunterAspectOfThePackTrigger(botAI); }
static Trigger* rapid_fire(PlayerbotAI* botAI) { return new RapidFireTrigger(botAI); }
static Trigger* aspect_of_the_hawk(PlayerbotAI* botAI) { return new HunterAspectOfTheHawkTrigger(botAI); }
static Trigger* aspect_of_the_monkey(PlayerbotAI* botAI) { return new HunterAspectOfTheMonkeyTrigger(botAI); }
static Trigger* aspect_of_the_dragonhawk(PlayerbotAI* botAI) { return new HunterAspectOfTheDragonhawkTrigger(botAI); }
static Trigger* aspect_of_the_wild(PlayerbotAI* botAI) { return new HunterAspectOfTheWildTrigger(botAI); }
static Trigger* low_ammo(PlayerbotAI* botAI) { return new HunterLowAmmoTrigger(botAI); }
static Trigger* no_ammo(PlayerbotAI* botAI) { return new HunterNoAmmoTrigger(botAI); }
static Trigger* has_ammo(PlayerbotAI* botAI) { return new HunterHasAmmoTrigger(botAI); }
static Trigger* switch_to_melee(PlayerbotAI* botAI) { return new SwitchToMeleeTrigger(botAI); }
static Trigger* switch_to_ranged(PlayerbotAI* botAI) { return new SwitchToRangedTrigger(botAI); }
static Trigger* misdirection_on_main_tank(PlayerbotAI* ai) { return new MisdirectionOnMainTankTrigger(ai); }
static Trigger* remove_enrage(PlayerbotAI* ai) { return new TargetRemoveEnrageTrigger(ai); }
static Trigger* remove_magic(PlayerbotAI* ai) { return new TargetRemoveMagicTrigger(ai); }
static Trigger* immolation_trap_no_cd(PlayerbotAI* ai) { return new ImmolationTrapNoCdTrigger(ai); }
static Trigger* misdirection_on_main_tank(PlayerbotAI* botAI) { return new MisdirectionOnMainTankTrigger(botAI); }
static Trigger* remove_enrage(PlayerbotAI* botAI) { return new TargetRemoveEnrageTrigger(botAI); }
static Trigger* remove_magic(PlayerbotAI* botAI) { return new TargetRemoveMagicTrigger(botAI); }
static Trigger* immolation_trap_no_cd(PlayerbotAI* botAI) { return new ImmolationTrapNoCdTrigger(botAI); }
static Trigger* kill_command(PlayerbotAI* botAI) { return new KillCommandTrigger(botAI); }
static Trigger* explosive_shot(PlayerbotAI* botAI) { return new ExplosiveShotTrigger(botAI); }
static Trigger* lock_and_load(PlayerbotAI* botAI) { return new LockAndLoadTrigger(botAI); }
@ -153,7 +144,6 @@ public:
creators["auto shot"] = &HunterAiObjectContextInternal::auto_shot;
creators["aimed shot"] = &HunterAiObjectContextInternal::aimed_shot;
creators["chimera shot"] = &HunterAiObjectContextInternal::chimera_shot;
creators["explosive shot"] = &HunterAiObjectContextInternal::explosive_shot;
creators["arcane shot"] = &HunterAiObjectContextInternal::arcane_shot;
creators["concussive shot"] = &HunterAiObjectContextInternal::concussive_shot;
creators["distracting shot"] = &HunterAiObjectContextInternal::distracting_shot;
@ -176,6 +166,7 @@ public:
creators["deterrence"] = &HunterAiObjectContextInternal::deterrence;
creators["readiness"] = &HunterAiObjectContextInternal::readiness;
creators["aspect of the hawk"] = &HunterAiObjectContextInternal::aspect_of_the_hawk;
creators["aspect of the dragonhawk"] = &HunterAiObjectContextInternal::aspect_of_the_dragonhawk;
creators["aspect of the monkey"] = &HunterAiObjectContextInternal::aspect_of_the_monkey;
creators["aspect of the wild"] = &HunterAiObjectContextInternal::aspect_of_the_wild;
creators["aspect of the viper"] = &HunterAiObjectContextInternal::aspect_of_the_viper;
@ -191,7 +182,6 @@ public:
creators["bestial wrath"] = &HunterAiObjectContextInternal::bestial_wrath;
creators["scare beast"] = &HunterAiObjectContextInternal::scare_beast;
creators["scare beast on cc"] = &HunterAiObjectContextInternal::scare_beast_on_cc;
creators["aspect of the dragonhawk"] = &HunterAiObjectContextInternal::aspect_of_the_dragonhawk;
creators["tranquilizing shot"] = &HunterAiObjectContextInternal::tranquilizing_shot;
creators["steady shot"] = &HunterAiObjectContextInternal::steady_shot;
creators["kill shot"] = &HunterAiObjectContextInternal::kill_shot;
@ -200,6 +190,7 @@ public:
creators["disengage"] = &HunterAiObjectContextInternal::disengage;
creators["immolation trap"] = &HunterAiObjectContextInternal::immolation_trap;
creators["explosive trap"] = &HunterAiObjectContextInternal::explosive_trap;
creators["explosive shot base"] = &HunterAiObjectContextInternal::explosive_shot_base;
creators["explosive shot rank 4"] = &HunterAiObjectContextInternal::explosive_shot_rank_4;
creators["explosive shot rank 3"] = &HunterAiObjectContextInternal::explosive_shot_rank_3;
creators["explosive shot rank 2"] = &HunterAiObjectContextInternal::explosive_shot_rank_2;
@ -218,7 +209,6 @@ private:
static Action* auto_shot(PlayerbotAI* botAI) { return new CastAutoShotAction(botAI); }
static Action* aimed_shot(PlayerbotAI* botAI) { return new CastAimedShotAction(botAI); }
static Action* chimera_shot(PlayerbotAI* botAI) { return new CastChimeraShotAction(botAI); }
static Action* explosive_shot(PlayerbotAI* botAI) { return new CastExplosiveShotAction(botAI); }
static Action* arcane_shot(PlayerbotAI* botAI) { return new CastArcaneShotAction(botAI); }
static Action* concussive_shot(PlayerbotAI* botAI) { return new CastConcussiveShotAction(botAI); }
static Action* distracting_shot(PlayerbotAI* botAI) { return new CastDistractingShotAction(botAI); }
@ -234,12 +224,13 @@ private:
static Action* kill_command(PlayerbotAI* botAI) { return new CastKillCommandAction(botAI); }
static Action* revive_pet(PlayerbotAI* botAI) { return new CastRevivePetAction(botAI); }
static Action* call_pet(PlayerbotAI* botAI) { return new CastCallPetAction(botAI); }
static Action* black_arrow(PlayerbotAI* botAI) { return new CastBlackArrow(botAI); }
static Action* black_arrow(PlayerbotAI* botAI) { return new CastBlackArrowAction(botAI); }
static Action* freezing_trap(PlayerbotAI* botAI) { return new CastFreezingTrap(botAI); }
static Action* rapid_fire(PlayerbotAI* botAI) { return new CastRapidFireAction(botAI); }
static Action* deterrence(PlayerbotAI* botAI) { return new CastDeterrenceAction(botAI); }
static Action* readiness(PlayerbotAI* botAI) { return new CastReadinessAction(botAI); }
static Action* aspect_of_the_hawk(PlayerbotAI* botAI) { return new CastAspectOfTheHawkAction(botAI); }
static Action* aspect_of_the_dragonhawk(PlayerbotAI* botAI) { return new CastAspectOfTheDragonhawkAction(botAI); }
static Action* aspect_of_the_monkey(PlayerbotAI* botAI) { return new CastAspectOfTheMonkeyAction(botAI); }
static Action* aspect_of_the_wild(PlayerbotAI* botAI) { return new CastAspectOfTheWildAction(botAI); }
static Action* aspect_of_the_viper(PlayerbotAI* botAI) { return new CastAspectOfTheViperAction(botAI); }
@ -248,20 +239,20 @@ private:
static Action* wing_clip(PlayerbotAI* botAI) { return new CastWingClipAction(botAI); }
static Action* raptor_strike(PlayerbotAI* botAI) { return new CastRaptorStrikeAction(botAI); }
static Action* mongoose_bite(PlayerbotAI* botAI) { return new CastMongooseBiteAction(botAI); }
static Action* aspect_of_the_dragonhawk(PlayerbotAI* ai) { return new CastAspectOfTheDragonhawkAction(ai); }
static Action* tranquilizing_shot(PlayerbotAI* ai) { return new CastTranquilizingShotAction(ai); }
static Action* steady_shot(PlayerbotAI* ai) { return new CastSteadyShotAction(ai); }
static Action* kill_shot(PlayerbotAI* ai) { return new CastKillShotAction(ai); }
static Action* misdirection_on_main_tank(PlayerbotAI* ai) { return new CastMisdirectionOnMainTankAction(ai); }
static Action* silencing_shot(PlayerbotAI* ai) { return new CastSilencingShotAction(ai); }
static Action* disengage(PlayerbotAI* ai) { return new CastDisengageAction(ai); }
static Action* immolation_trap(PlayerbotAI* ai) { return new CastImmolationTrapAction(ai); }
static Action* explosive_trap(PlayerbotAI* ai) { return new CastExplosiveTrapAction(ai); }
static Action* explosive_shot_rank_4(PlayerbotAI* ai) { return new CastExplosiveShotRank4Action(ai); }
static Action* explosive_shot_rank_3(PlayerbotAI* ai) { return new CastExplosiveShotRank3Action(ai); }
static Action* explosive_shot_rank_2(PlayerbotAI* ai) { return new CastExplosiveShotRank2Action(ai); }
static Action* explosive_shot_rank_1(PlayerbotAI* ai) { return new CastExplosiveShotRank1Action(ai); }
static Action* intimidation(PlayerbotAI* ai) { return new CastIntimidationAction(ai); }
static Action* tranquilizing_shot(PlayerbotAI* botAI) { return new CastTranquilizingShotAction(botAI); }
static Action* steady_shot(PlayerbotAI* botAI) { return new CastSteadyShotAction(botAI); }
static Action* kill_shot(PlayerbotAI* botAI) { return new CastKillShotAction(botAI); }
static Action* misdirection_on_main_tank(PlayerbotAI* botAI) { return new CastMisdirectionOnMainTankAction(botAI); }
static Action* silencing_shot(PlayerbotAI* botAI) { return new CastSilencingShotAction(botAI); }
static Action* disengage(PlayerbotAI* botAI) { return new CastDisengageAction(botAI); }
static Action* immolation_trap(PlayerbotAI* botAI) { return new CastImmolationTrapAction(botAI); }
static Action* explosive_trap(PlayerbotAI* botAI) { return new CastExplosiveTrapAction(botAI); }
static Action* explosive_shot_base(PlayerbotAI* botAI) { return new CastExplosiveShotBaseAction(botAI); }
static Action* explosive_shot_rank_4(PlayerbotAI* botAI) { return new CastExplosiveShotRank4Action(botAI); }
static Action* explosive_shot_rank_3(PlayerbotAI* botAI) { return new CastExplosiveShotRank3Action(botAI); }
static Action* explosive_shot_rank_2(PlayerbotAI* botAI) { return new CastExplosiveShotRank2Action(botAI); }
static Action* explosive_shot_rank_1(PlayerbotAI* botAI) { return new CastExplosiveShotRank1Action(botAI); }
static Action* intimidation(PlayerbotAI* botAI) { return new CastIntimidationAction(botAI); }
};
SharedNamedObjectContextList<Strategy> HunterAiObjectContext::sharedStrategyContexts;

View File

@ -6,41 +6,9 @@
#include "BeastMasteryHunterStrategy.h"
#include "Playerbots.h"
// ===== Action Node Factory =====
class BeastMasteryHunterStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
BeastMasteryHunterStrategyActionNodeFactory()
{
creators["auto shot"] = &auto_shot;
creators["kill command"] = &kill_command;
creators["kill shot"] = &kill_shot;
creators["viper sting"] = &viper_sting;
creators["serpent sting"] = serpent_sting;
creators["aimed shot"] = &aimed_shot;
creators["arcane shot"] = &arcane_shot;
creators["steady shot"] = &steady_shot;
creators["multi-shot"] = &multi_shot;
creators["volley"] = &volley;
}
private:
static ActionNode* auto_shot(PlayerbotAI*) { return new ActionNode("auto shot", {}, {}, {}); }
static ActionNode* kill_command(PlayerbotAI*) { return new ActionNode("kill command", {}, {}, {}); }
static ActionNode* kill_shot(PlayerbotAI*) { return new ActionNode("kill shot", {}, {}, {}); }
static ActionNode* viper_sting(PlayerbotAI*) { return new ActionNode("viper sting", {}, {}, {}); }
static ActionNode* serpent_sting(PlayerbotAI*) { return new ActionNode("serpent sting", {}, {}, {}); }
static ActionNode* aimed_shot(PlayerbotAI*) { return new ActionNode("aimed shot", {}, {}, {}); }
static ActionNode* arcane_shot(PlayerbotAI*) { return new ActionNode("arcane shot", {}, {}, {}); }
static ActionNode* steady_shot(PlayerbotAI*) { return new ActionNode("steady shot", {}, {}, {}); }
static ActionNode* multi_shot(PlayerbotAI*) { return new ActionNode("multi shot", {}, {}, {}); }
static ActionNode* volley(PlayerbotAI*) { return new ActionNode("volley", {}, {}, {}); }
};
// ===== Single Target Strategy =====
BeastMasteryHunterStrategy::BeastMasteryHunterStrategy(PlayerbotAI* botAI) : GenericHunterStrategy(botAI)
{
actionNodeFactories.Add(new BeastMasteryHunterStrategyActionNodeFactory());
// No custom ActionNodeFactory needed
}
// ===== Default Actions =====

View File

@ -4,62 +4,31 @@
*/
#include "GenericHunterNonCombatStrategy.h"
#include "Playerbots.h"
class GenericHunterNonCombatStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
GenericHunterNonCombatStrategyActionNodeFactory()
{
creators["rapid fire"] = &rapid_fire;
creators["boost"] = &rapid_fire;
creators["aspect of the pack"] = &aspect_of_the_pack;
}
private:
static ActionNode* rapid_fire([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rapid fire",
/*P*/ {},
/*A*/ { NextAction("readiness")},
/*C*/ {});
}
static ActionNode* aspect_of_the_pack([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("aspect of the pack",
/*P*/ {},
/*A*/ { NextAction("aspect of the cheetah")},
/*C*/ {});
}
};
GenericHunterNonCombatStrategy::GenericHunterNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
{
actionNodeFactories.Add(new GenericHunterNonCombatStrategyActionNodeFactory());
// No custom ActionNodeFactory needed
}
void GenericHunterNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("trueshot aura", { NextAction("trueshot aura", 2.0f)}));
triggers.push_back(new TriggerNode("often", {
NextAction("apply stone", 1.0f),
NextAction("apply oil", 1.0f),
}));
triggers.push_back(new TriggerNode("low ammo", { NextAction("say::low ammo", ACTION_NORMAL)}));
triggers.push_back(new TriggerNode("no track", { NextAction("track humanoids", ACTION_NORMAL)}));
triggers.push_back(new TriggerNode("no ammo", { NextAction("equip upgrades packet action", ACTION_HIGH + 1)}));
triggers.push_back(new TriggerNode("trueshot aura", { NextAction("trueshot aura", 2.0f) }));
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f),
NextAction("apply oil", 1.0f) }));
triggers.push_back(new TriggerNode("low ammo", { NextAction("say::low ammo", ACTION_NORMAL) }));
triggers.push_back(new TriggerNode("no track", { NextAction("track humanoids", ACTION_NORMAL) }));
triggers.push_back(new TriggerNode("no ammo", { NextAction("equip upgrades packet action", ACTION_HIGH + 1) }));
}
void HunterPetStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("no pet", { NextAction("call pet", 60.0f)}));
triggers.push_back(new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f)}));
triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f)}));
triggers.push_back(new TriggerNode("pet not happy", { NextAction("feed pet", 60.0f)}));
triggers.push_back(new TriggerNode("hunters pet medium health", { NextAction("mend pet", 60.0f)}));
triggers.push_back(new TriggerNode("hunters pet dead", { NextAction("revive pet", 60.0f)}));
triggers.push_back(new TriggerNode("no pet", { NextAction("call pet", 60.0f) }));
triggers.push_back(new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
triggers.push_back(new TriggerNode("pet not happy", { NextAction("feed pet", 60.0f) }));
triggers.push_back(new TriggerNode("hunters pet medium health", { NextAction("mend pet", 60.0f) }));
triggers.push_back(new TriggerNode("hunters pet dead", { NextAction("revive pet", 60.0f) }));
}

View File

@ -11,11 +11,6 @@ public:
GenericHunterStrategyActionNodeFactory()
{
creators["rapid fire"] = &rapid_fire;
creators["boost"] = &rapid_fire;
creators["aspect of the pack"] = &aspect_of_the_pack;
creators["aspect of the dragonhawk"] = &aspect_of_the_dragonhawk;
creators["feign death"] = &feign_death;
creators["wing clip"] = &wing_clip;
creators["mongoose bite"] = &mongoose_bite;
creators["raptor strike"] = &raptor_strike;
creators["explosive trap"] = &explosive_trap;
@ -29,40 +24,6 @@ private:
/*A*/ { NextAction("readiness") },
/*C*/ {});
}
static ActionNode* aspect_of_the_pack([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("aspect of the pack",
/*P*/ {},
/*A*/ { NextAction("aspect of the cheetah") },
/*C*/ {});
}
static ActionNode* aspect_of_the_dragonhawk([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("aspect of the dragonhawk",
/*P*/ {},
/*A*/ { NextAction("aspect of the hawk") },
/*C*/ {});
}
static ActionNode* feign_death([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("feign death",
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* wing_clip([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("wing clip",
/*P*/ {},
// /*A*/ { NextAction("mongoose bite") },
{},
/*C*/ {});
}
static ActionNode* mongoose_bite([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("mongoose bite",
@ -70,7 +31,6 @@ private:
/*A*/ { NextAction("raptor strike") },
/*C*/ {});
}
static ActionNode* raptor_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("raptor strike",
@ -78,7 +38,6 @@ private:
/*A*/ {},
/*C*/ {});
}
static ActionNode* explosive_trap([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("explosive trap",
@ -102,7 +61,6 @@ void GenericHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("hunter's mark", { NextAction("hunter's mark", 29.5f) }));
triggers.push_back(new TriggerNode("rapid fire", { NextAction("rapid fire", 29.0f) }));
triggers.push_back(new TriggerNode("aspect of the viper", { NextAction("aspect of the viper", 28.0f) }));
triggers.push_back(new TriggerNode("aspect of the hawk", { NextAction("aspect of the dragonhawk", 27.5f) }));
// Aggro/Threat/Defensive Triggers
triggers.push_back(new TriggerNode("has aggro", { NextAction("concussive shot", 20.0f) }));
@ -118,14 +76,12 @@ void GenericHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("tranquilizing shot magic", { NextAction("tranquilizing shot", 61.0f) }));
// Ranged-based Triggers
triggers.push_back(new TriggerNode("enemy within melee", {
NextAction("explosive trap", 37.0f),
NextAction("mongoose bite", 22.0f),
NextAction("wing clip", 21.0f) }));
triggers.push_back(new TriggerNode("enemy within melee", { NextAction("explosive trap", 37.0f),
NextAction("mongoose bite", 22.0f),
NextAction("wing clip", 21.0f) }));
triggers.push_back(new TriggerNode("enemy too close for auto shot", {
NextAction("disengage", 35.0f),
NextAction("flee", 34.0f) }));
triggers.push_back(new TriggerNode("enemy too close for auto shot", { NextAction("disengage", 35.0f),
NextAction("flee", 34.0f) }));
}
// ===== AoE Strategy, 2/3+ enemies =====
@ -138,10 +94,6 @@ void AoEHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("light aoe", { NextAction("multi-shot", 21.0f) }));
}
void HunterBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
}
void HunterCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("scare beast", { NextAction("scare beast on cc", 23.0f) }));

View File

@ -30,15 +30,6 @@ public:
std::string const getName() override { return "aoe"; }
};
class HunterBoostStrategy : public Strategy
{
public:
HunterBoostStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "boost"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class HunterCcStrategy : public Strategy
{
public:

View File

@ -10,9 +10,21 @@
class BuffHunterStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
BuffHunterStrategyActionNodeFactory() { creators["aspect of the hawk"] = &aspect_of_the_hawk; }
BuffHunterStrategyActionNodeFactory()
{
creators["aspect of the dragonhawk"] = &aspect_of_the_dragonhawk;
creators["aspect of the hawk"] = &aspect_of_the_hawk;
creators["aspect of the pack"] = &aspect_of_the_pack;
}
private:
static ActionNode* aspect_of_the_dragonhawk([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("aspect of the dragonhawk",
/*P*/ {},
/*A*/ { NextAction("aspect of the hawk") },
/*C*/ {});
}
static ActionNode* aspect_of_the_hawk([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("aspect of the hawk",
@ -20,9 +32,16 @@ private:
/*A*/ { NextAction("aspect of the monkey") },
/*C*/ {});
}
static ActionNode* aspect_of_the_pack([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("aspect of the pack",
/*P*/ {},
/*A*/ { NextAction("aspect of the cheetah") },
/*C*/ {});
}
};
HunterBuffDpsStrategy::HunterBuffDpsStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
HunterBuffDpsStrategy::HunterBuffDpsStrategy(PlayerbotAI* botAI) : Strategy(botAI)
{
actionNodeFactories.Add(new BuffHunterStrategyActionNodeFactory());
}
@ -30,24 +49,35 @@ HunterBuffDpsStrategy::HunterBuffDpsStrategy(PlayerbotAI* botAI) : NonCombatStra
void HunterBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("aspect of the hawk", { NextAction("aspect of the dragonhawk", 20.1f),
NextAction("aspect of the hawk", 20.0f) }));
new TriggerNode(
"aspect of the dragonhawk",
{
NextAction("aspect of the dragonhawk", ACTION_HIGH)
}
)
);
}
void HunterNatureResistanceStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("aspect of the wild",
{ NextAction("aspect of the wild", 20.0f) }));
triggers.push_back(
new TriggerNode(
"aspect of the wild",
{
NextAction("aspect of the wild", ACTION_HIGH)
}
)
);
}
void HunterBuffSpeedStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("aspect of the pack",
{ NextAction("aspect of the pack", 20.0f) }));
}
void HunterBuffManaStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("aspect of the viper",
{ NextAction("aspect of the viper", 20.0f) }));
triggers.push_back(
new TriggerNode(
"aspect of the pack",
{
NextAction("aspect of the pack", ACTION_HIGH)
}
)
);
}

View File

@ -6,44 +6,35 @@
#ifndef _PLAYERBOT_HUNTERBUFFSTRATEGIES_H
#define _PLAYERBOT_HUNTERBUFFSTRATEGIES_H
#include "NonCombatStrategy.h"
#include "Strategy.h"
class PlayerbotAI;
class HunterBuffSpeedStrategy : public NonCombatStrategy
class HunterBuffSpeedStrategy : public Strategy
{
public:
HunterBuffSpeedStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {}
HunterBuffSpeedStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "bspeed"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class HunterBuffManaStrategy : public NonCombatStrategy
{
public:
HunterBuffManaStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {}
std::string const getName() override { return "bmana"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
};
class HunterBuffDpsStrategy : public NonCombatStrategy
class HunterBuffDpsStrategy : public Strategy
{
public:
HunterBuffDpsStrategy(PlayerbotAI* botAI);
std::string const getName() override { return "bdps"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "bdps"; }
};
class HunterNatureResistanceStrategy : public NonCombatStrategy
class HunterNatureResistanceStrategy : public Strategy
{
public:
HunterNatureResistanceStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {}
HunterNatureResistanceStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "rnature"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "rnature"; }
};
#endif

View File

@ -6,45 +6,9 @@
#include "MarksmanshipHunterStrategy.h"
#include "Playerbots.h"
// ===== Action Node Factory =====
class MarksmanshipHunterStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
MarksmanshipHunterStrategyActionNodeFactory()
{
creators["auto shot"] = &auto_shot;
creators["silencing shot"] = &silencing_shot;
creators["kill command"] = &kill_command;
creators["kill shot"] = &kill_shot;
creators["viper sting"] = &viper_sting;
creators["serpent sting"] = serpent_sting;
creators["chimera shot"] = &chimera_shot;
creators["aimed shot"] = &aimed_shot;
creators["arcane shot"] = &arcane_shot;
creators["steady shot"] = &steady_shot;
creators["multi-shot"] = &multi_shot;
creators["volley"] = &volley;
}
private:
static ActionNode* auto_shot(PlayerbotAI*) { return new ActionNode("auto shot", {}, {}, {}); }
static ActionNode* silencing_shot(PlayerbotAI*) { return new ActionNode("silencing shot", {}, {}, {}); }
static ActionNode* kill_command(PlayerbotAI*) { return new ActionNode("kill command", {}, {}, {}); }
static ActionNode* kill_shot(PlayerbotAI*) { return new ActionNode("kill shot", {}, {}, {}); }
static ActionNode* viper_sting(PlayerbotAI*) { return new ActionNode("viper sting", {}, {}, {}); }
static ActionNode* serpent_sting(PlayerbotAI*) { return new ActionNode("serpent sting", {}, {}, {}); }
static ActionNode* chimera_shot(PlayerbotAI*) { return new ActionNode("chimera shot", {}, {}, {}); }
static ActionNode* aimed_shot(PlayerbotAI*) { return new ActionNode("aimed shot", {}, {}, {}); }
static ActionNode* arcane_shot(PlayerbotAI*) { return new ActionNode("arcane shot", {}, {}, {}); }
static ActionNode* steady_shot(PlayerbotAI*) { return new ActionNode("steady shot", {}, {}, {}); }
static ActionNode* multi_shot(PlayerbotAI*) { return new ActionNode("multi shot", {}, {}, {}); }
static ActionNode* volley(PlayerbotAI*) { return new ActionNode("volley", {}, {}, {}); }
};
// ===== Single Target Strategy =====
MarksmanshipHunterStrategy::MarksmanshipHunterStrategy(PlayerbotAI* botAI) : GenericHunterStrategy(botAI)
{
actionNodeFactories.Add(new MarksmanshipHunterStrategyActionNodeFactory());
// No custom ActionNodeFactory needed
}
// ===== Default Actions =====

View File

@ -12,36 +12,35 @@ class SurvivalHunterStrategyActionNodeFactory : public NamedObjectFactory<Action
public:
SurvivalHunterStrategyActionNodeFactory()
{
creators["auto shot"] = &auto_shot;
creators["kill command"] = &kill_command;
creators["kill shot"] = &kill_shot;
creators["explosive shot"] = &explosive_shot;
creators["black arrow"] = &black_arrow;
creators["viper sting"] = &viper_sting;
creators["serpent sting"] = serpent_sting;
creators["aimed shot"] = &aimed_shot;
creators["arcane shot"] = &arcane_shot;
creators["steady shot"] = &steady_shot;
creators["multi-shot"] = &multi_shot;
creators["volley"] = &volley;
creators["explosive shot rank 4"] = &explosive_shot_rank_4;
creators["explosive shot rank 3"] = &explosive_shot_rank_3;
creators["explosive shot rank 2"] = &explosive_shot_rank_2;
}
private:
static ActionNode* auto_shot(PlayerbotAI*) { return new ActionNode("auto shot", {}, {}, {}); }
static ActionNode* kill_command(PlayerbotAI*) { return new ActionNode("kill command", {}, {}, {}); }
static ActionNode* kill_shot(PlayerbotAI*) { return new ActionNode("kill shot", {}, {}, {}); }
static ActionNode* explosive_shot(PlayerbotAI*) { return new ActionNode("explosive shot", {}, {}, {}); }
static ActionNode* black_arrow(PlayerbotAI*) { return new ActionNode("black arrow", {}, {}, {}); }
static ActionNode* viper_sting(PlayerbotAI*) { return new ActionNode("viper sting", {}, {}, {}); }
static ActionNode* serpent_sting(PlayerbotAI*) { return new ActionNode("serpent sting", {}, {}, {}); }
static ActionNode* aimed_shot(PlayerbotAI*) { return new ActionNode("aimed shot", {}, {}, {}); }
static ActionNode* arcane_shot(PlayerbotAI*) { return new ActionNode("arcane shot", {}, {}, {}); }
static ActionNode* steady_shot(PlayerbotAI*) { return new ActionNode("steady shot", {}, {}, {}); }
static ActionNode* multi_shot(PlayerbotAI*) { return new ActionNode("multi shot", {}, {}, {}); }
static ActionNode* volley(PlayerbotAI*) { return new ActionNode("volley", {}, {}, {}); }
static ActionNode* explosive_shot_rank_4([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("explosive shot rank 4",
/*P*/ {},
/*A*/ { NextAction("explosive shot rank 3") },
/*C*/ {});
}
static ActionNode* explosive_shot_rank_3([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("explosive shot rank 3",
/*P*/ {},
/*A*/ { NextAction("explosive shot rank 2") },
/*C*/ {});
}
static ActionNode* explosive_shot_rank_2([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("explosive shot rank 2",
/*P*/ {},
/*A*/ { NextAction("explosive shot rank 1") },
/*C*/ {});
}
};
// ===== Single Target Strategy =====
SurvivalHunterStrategy::SurvivalHunterStrategy(PlayerbotAI* botAI) : GenericHunterStrategy(botAI)
{
actionNodeFactories.Add(new SurvivalHunterStrategyActionNodeFactory());
@ -76,30 +75,6 @@ void SurvivalHunterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
}
)
);
triggers.push_back(
new TriggerNode(
"lock and load",
{
NextAction("explosive shot rank 3", 27.5f)
}
)
);
triggers.push_back(
new TriggerNode(
"lock and load",
{
NextAction("explosive shot rank 2", 27.0f)
}
)
);
triggers.push_back(
new TriggerNode(
"lock and load",
{
NextAction("explosive shot rank 1", 26.5f)
}
)
);
triggers.push_back(
new TriggerNode(
"kill command",

View File

@ -16,8 +16,7 @@
bool KillCommandTrigger::IsActive()
{
Unit* target = GetTarget();
return !botAI->HasAura("kill command", target);
return !botAI->HasAura("kill command", GetTarget());
}
bool BlackArrowTrigger::IsActive()
@ -26,36 +25,46 @@ bool BlackArrowTrigger::IsActive()
return false;
return DebuffTrigger::IsActive();
return BuffTrigger::IsActive();
}
bool HunterAspectOfTheHawkTrigger::IsActive()
bool HunterAspectOfTheDragonhawkTrigger::IsActive()
{
Unit* target = GetTarget();
return SpellTrigger::IsActive() && !botAI->HasAura("aspect of the hawk", target) &&
!botAI->HasAura("aspect of the dragonhawk", target) &&
(!AI_VALUE2(bool, "has mana", "self target") || AI_VALUE2(uint8, "mana", "self target") > 70);
if (!target)
return false;
if (!SpellTrigger::IsActive())
return false;
if (botAI->HasAura("aspect of the hawk", target) ||
botAI->HasAura("aspect of the dragonhawk", target))
return false;
if (botAI->HasAura("aspect of the viper", target))
return AI_VALUE2(uint8, "mana", "self target") >= 60;
return true;
}
bool HunterNoStingsActiveTrigger::IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target");
return DebuffTrigger::IsActive() && target && !botAI->HasAura("serpent sting", target, false, true) &&
!botAI->HasAura("scorpid sting", target, false, true) && !botAI->HasAura("viper sting", target, false, true);
return BuffTrigger::IsActive();
return DebuffTrigger::IsActive() && target &&
!botAI->HasAura("serpent sting", target, false, true) &&
!botAI->HasAura("scorpid sting", target, false, true) &&
!botAI->HasAura("viper sting", target, false, true);
}
bool HuntersPetDeadTrigger::IsActive()
{
// Unit* pet = AI_VALUE(Unit*, "pet target");
// return pet && AI_VALUE2(bool, "dead", "pet target") && !AI_VALUE2(bool, "mounted", "self target");
return AI_VALUE(bool, "pet dead") && !AI_VALUE2(bool, "mounted", "self target");
}
bool HuntersPetLowHealthTrigger::IsActive()
{
Unit* pet = AI_VALUE(Unit*, "pet target");
return pet && AI_VALUE2(uint8, "health", "pet target") < 40 && !AI_VALUE2(bool, "dead", "pet target") &&
return pet && AI_VALUE2(uint8, "health", "pet target") < 40 &&
!AI_VALUE2(bool, "dead", "pet target") &&
!AI_VALUE2(bool, "mounted", "self target");
}
@ -73,9 +82,14 @@ bool HunterPetNotHappy::IsActive()
bool HunterAspectOfTheViperTrigger::IsActive()
{
return SpellTrigger::IsActive() && !botAI->HasAura(spell, GetTarget()) &&
if (botAI->HasStrategy("rnature", BotState::BOT_STATE_COMBAT) ||
botAI->HasStrategy("rnature", BotState::BOT_STATE_NON_COMBAT) ||
botAI->HasStrategy("bspeed", BotState::BOT_STATE_COMBAT) ||
botAI->HasStrategy("bspeed", BotState::BOT_STATE_NON_COMBAT))
return false;
return BuffTrigger::IsActive() &&
AI_VALUE2(uint8, "mana", "self target") < (sPlayerbotAIConfig.lowMana / 2);
;
}
bool HunterAspectOfThePackTrigger::IsActive()
@ -85,11 +99,14 @@ bool HunterAspectOfThePackTrigger::IsActive()
bool HunterLowAmmoTrigger::IsActive()
{
return bot->GetGroup() && (AI_VALUE2(uint32, "item count", "ammo") < 100) &&
(AI_VALUE2(uint32, "item count", "ammo") > 0);
uint32 ammoCount = AI_VALUE2(uint32, "item count", "ammo");
return bot->GetGroup() && ammoCount > 0 && ammoCount < 100;
}
bool HunterHasAmmoTrigger::IsActive() { return !AmmoCountTrigger::IsActive(); }
bool HunterHasAmmoTrigger::IsActive()
{
return !AmmoCountTrigger::IsActive();
}
bool SwitchToRangedTrigger::IsActive()
{
@ -131,6 +148,7 @@ bool NoTrackTrigger::IsActive()
if (botAI->HasAura(track, bot))
return false;
}
return true;
}
@ -138,17 +156,17 @@ bool SerpentStingOnAttackerTrigger::IsActive()
{
if (!DebuffOnAttackerTrigger::IsActive())
return false;
Unit* target = GetTarget();
if (!target)
{
return false;
}
return !botAI->HasAura("scorpid sting", target, false, true) &&
!botAI->HasAura("viper sting", target, false, true);
return BuffTrigger::IsActive();
}
const std::set<uint32> VolleyChannelCheckTrigger::VOLLEY_SPELL_IDS = {
const std::set<uint32> VolleyChannelCheckTrigger::VOLLEY_SPELL_IDS =
{
1510, // Volley Rank 1
14294, // Volley Rank 2
14295, // Volley Rank 3
@ -159,19 +177,12 @@ const std::set<uint32> VolleyChannelCheckTrigger::VOLLEY_SPELL_IDS = {
bool VolleyChannelCheckTrigger::IsActive()
{
Player* bot = botAI->GetBot();
// Check if the bot is channeling a spell
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
spell && VOLLEY_SPELL_IDS.count(spell->m_spellInfo->Id))
{
// Only trigger if the spell being channeled is Volley
if (VOLLEY_SPELL_IDS.count(spell->m_spellInfo->Id))
{
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
return attackerCount < minEnemies;
}
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
return attackerCount < minEnemies;
}
// Not channeling Volley
return false;
}

View File

@ -16,16 +16,10 @@ class PlayerbotAI;
// Buff and Out of Combat Triggers
class HunterAspectOfTheMonkeyTrigger : public BuffTrigger
class HunterAspectOfTheDragonhawkTrigger : public BuffTrigger
{
public:
HunterAspectOfTheMonkeyTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "aspect of the monkey") {}
};
class HunterAspectOfTheHawkTrigger : public BuffTrigger
{
public:
HunterAspectOfTheHawkTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "aspect of the hawk") {}
HunterAspectOfTheDragonhawkTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "aspect of the dragonhawk") {}
bool IsActive() override;
};
@ -130,10 +124,10 @@ public:
FreezingTrapTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "freezing trap") {}
};
class ConsussiveShotSnareTrigger : public SnareTargetTrigger
class ConcussiveShotOnSnareTargetTrigger : public SnareTargetTrigger
{
public:
ConsussiveShotSnareTrigger(PlayerbotAI* botAI) : SnareTargetTrigger(botAI, "concussive shot") {}
ConcussiveShotOnSnareTargetTrigger(PlayerbotAI* botAI) : SnareTargetTrigger(botAI, "concussive shot") {}
};
class ScareBeastTrigger : public HasCcTargetTrigger
@ -212,25 +206,25 @@ public:
class MisdirectionOnMainTankTrigger : public BuffOnMainTankTrigger
{
public:
MisdirectionOnMainTankTrigger(PlayerbotAI* ai) : BuffOnMainTankTrigger(ai, "misdirection", true) {}
MisdirectionOnMainTankTrigger(PlayerbotAI* botAI) : BuffOnMainTankTrigger(botAI, "misdirection", true) {}
};
class TargetRemoveEnrageTrigger : public TargetAuraDispelTrigger
{
public:
TargetRemoveEnrageTrigger(PlayerbotAI* ai) : TargetAuraDispelTrigger(ai, "tranquilizing shot", DISPEL_ENRAGE) {}
TargetRemoveEnrageTrigger(PlayerbotAI* botAI) : TargetAuraDispelTrigger(botAI, "tranquilizing shot", DISPEL_ENRAGE) {}
};
class TargetRemoveMagicTrigger : public TargetAuraDispelTrigger
{
public:
TargetRemoveMagicTrigger(PlayerbotAI* ai) : TargetAuraDispelTrigger(ai, "tranquilizing shot", DISPEL_MAGIC) {}
TargetRemoveMagicTrigger(PlayerbotAI* botAI) : TargetAuraDispelTrigger(botAI, "tranquilizing shot", DISPEL_MAGIC) {}
};
class ImmolationTrapNoCdTrigger : public SpellNoCooldownTrigger
{
public:
ImmolationTrapNoCdTrigger(PlayerbotAI* ai) : SpellNoCooldownTrigger(ai, "immolation trap") {}
ImmolationTrapNoCdTrigger(PlayerbotAI* botAI) : SpellNoCooldownTrigger(botAI, "immolation trap") {}
};
BEGIN_TRIGGER(HuntersPetDeadTrigger, Trigger)

View File

@ -35,7 +35,7 @@ private:
static ActionNode* ice_lance(PlayerbotAI*) { return new ActionNode("ice lance", {}, {}, {}); }
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); }
static ActionNode* fireball(PlayerbotAI*) { return new ActionNode("fireball", {}, {}, {}); }
static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", {}, {}, {}); }
static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", {}, { NextAction("fireball") }, {}); }
};
// ===== Single Target Strategy =====

View File

@ -237,6 +237,14 @@ void MageBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
void MageCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("polymorph", { NextAction("polymorph", 30.0f) }));
Player* bot = botAI->GetBot();
int tab = AiFactory::GetPlayerSpecTab(bot);
if (tab == MAGE_TAB_FIRE)
{
triggers.push_back(new TriggerNode("enemy too close for spell", {NextAction("dragon's breath", ACTION_INTERRUPT + 1)}));
triggers.push_back(new TriggerNode("enemy is close", {NextAction("blast wave", ACTION_INTERRUPT)}));
}
}
void MageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@ -7,6 +7,7 @@
#include "AiFactory.h"
#include "Event.h"
#include "PaladinHelper.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "SharedDefines.h"
@ -468,6 +469,39 @@ bool CastSealSpellAction::isUseful() { return AI_VALUE2(bool, "combat", "self ta
Value<Unit*>* CastTurnUndeadAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", getName()); }
Unit* CastHandOfFreedomOnPartyAction::GetTarget()
{
bool const selfImpaired = botAI->IsMovementImpaired(bot);
bool const hasSelfHand = selfImpaired && ai::paladin::HasAnyPaladinHandFromCaster(bot, bot);
if (!bot->GetGroup())
{
if (selfImpaired && !hasSelfHand)
return bot;
return nullptr;
}
if (selfImpaired && !hasSelfHand)
return bot;
return CastBuffSpellAction::GetTarget();
}
Value<Unit*>* CastHandOfFreedomOnPartyAction::GetTargetValue()
{
return context->GetValue<Unit*>("party member snared target");
}
bool CastHandOfFreedomOnPartyAction::isUseful()
{
Unit* target = GetTarget();
if (!target)
return false;
return CastBuffSpellAction::isUseful() && !ai::paladin::HasAnyPaladinHandFromCaster(target, bot);
}
Unit* CastRighteousDefenseAction::GetTarget()
{
Unit* current_target = AI_VALUE(Unit*, "current target");

View File

@ -371,6 +371,19 @@ public:
Value<Unit*>* GetTargetValue() override;
};
class CastHandOfFreedomOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport
{
public:
CastHandOfFreedomOnPartyAction(PlayerbotAI* botAI)
: CastBuffSpellAction(botAI, "hand of freedom"), PartyMemberActionNameSupport("hand of freedom") {}
Unit* GetTarget() override;
Value<Unit*>* GetTargetValue() override;
std::string const GetTargetName() override { return "party member snared target"; }
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
bool isUseful() override;
};
PROTECT_ACTION(CastBlessingOfProtectionProtectAction, "blessing of protection");
class CastDivinePleaAction : public CastBuffSpellAction

View File

@ -134,6 +134,7 @@ public:
&PaladinTriggerFactoryInternal::hammer_of_justice_on_snare_target;
creators["not sensing undead"] = &PaladinTriggerFactoryInternal::not_sensing_undead;
creators["divine favor"] = &PaladinTriggerFactoryInternal::divine_favor;
creators["divine shield low health"] = &PaladinTriggerFactoryInternal::divine_shield_low_health;
creators["turn undead"] = &PaladinTriggerFactoryInternal::turn_undead;
creators["avenger's shield"] = &PaladinTriggerFactoryInternal::avenger_shield;
creators["consecration"] = &PaladinTriggerFactoryInternal::consecration;
@ -142,6 +143,7 @@ public:
creators["repentance interrupt"] = &PaladinTriggerFactoryInternal::repentance_interrupt;
creators["beacon of light on main tank"] = &PaladinTriggerFactoryInternal::beacon_of_light_on_main_tank;
creators["sacred shield on main tank"] = &PaladinTriggerFactoryInternal::sacred_shield_on_main_tank;
creators["hand of freedom on party"] = &PaladinTriggerFactoryInternal::hand_of_freedom_on_party;
creators["blessing of kings on party"] = &PaladinTriggerFactoryInternal::blessing_of_kings_on_party;
creators["blessing of wisdom on party"] = &PaladinTriggerFactoryInternal::blessing_of_wisdom_on_party;
@ -155,6 +157,7 @@ private:
static Trigger* not_sensing_undead(PlayerbotAI* botAI) { return new NotSensingUndeadTrigger(botAI); }
static Trigger* turn_undead(PlayerbotAI* botAI) { return new TurnUndeadTrigger(botAI); }
static Trigger* divine_favor(PlayerbotAI* botAI) { return new DivineFavorTrigger(botAI); }
static Trigger* divine_shield_low_health(PlayerbotAI* botAI) { return new DivineShieldLowHealthTrigger(botAI); }
static Trigger* holy_shield(PlayerbotAI* botAI) { return new HolyShieldTrigger(botAI); }
static Trigger* righteous_fury(PlayerbotAI* botAI) { return new RighteousFuryTrigger(botAI); }
static Trigger* judgement(PlayerbotAI* botAI) { return new JudgementTrigger(botAI); }
@ -207,6 +210,7 @@ private:
static Trigger* repentance_interrupt(PlayerbotAI* botAI) { return new RepentanceInterruptTrigger(botAI); }
static Trigger* beacon_of_light_on_main_tank(PlayerbotAI* ai) { return new BeaconOfLightOnMainTankTrigger(ai); }
static Trigger* sacred_shield_on_main_tank(PlayerbotAI* ai) { return new SacredShieldOnMainTankTrigger(ai); }
static Trigger* hand_of_freedom_on_party(PlayerbotAI* botAI) { return new HandOfFreedomOnPartyTrigger(botAI); }
static Trigger* blessing_of_kings_on_party(PlayerbotAI* botAI) { return new BlessingOfKingsOnPartyTrigger(botAI); }
static Trigger* blessing_of_wisdom_on_party(PlayerbotAI* botAI)
@ -308,6 +312,7 @@ public:
creators["divine illumination"] = &PaladinAiObjectContextInternal::divine_illumination;
creators["divine sacrifice"] = &PaladinAiObjectContextInternal::divine_sacrifice;
creators["cancel divine sacrifice"] = &PaladinAiObjectContextInternal::cancel_divine_sacrifice;
creators["hand of freedom on party"] = &PaladinAiObjectContextInternal::hand_of_freedom_on_party;
}
private:
@ -414,6 +419,7 @@ private:
static Action* divine_illumination(PlayerbotAI* ai) { return new CastDivineIlluminationAction(ai); }
static Action* divine_sacrifice(PlayerbotAI* ai) { return new CastDivineSacrificeAction(ai); }
static Action* cancel_divine_sacrifice(PlayerbotAI* ai) { return new CastCancelDivineSacrificeAction(ai); }
static Action* hand_of_freedom_on_party(PlayerbotAI* ai) { return new CastHandOfFreedomOnPartyAction(ai); }
};
SharedNamedObjectContextList<Strategy> PaladinAiObjectContext::sharedStrategyContexts;

View File

@ -16,27 +16,23 @@ void GenericPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
CombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("critical health", { NextAction("divine shield",
ACTION_HIGH + 5) }));
triggers.push_back(
new TriggerNode("hammer of justice interrupt",
{ NextAction("hammer of justice", ACTION_INTERRUPT) }));
triggers.push_back(new TriggerNode(
"hammer of justice on enemy healer",
triggers.push_back(new TriggerNode("hammer of justice interrupt",
{ NextAction("hammer of justice", ACTION_INTERRUPT) }));
triggers.push_back(new TriggerNode("hammer of justice on enemy healer",
{ NextAction("hammer of justice on enemy healer", ACTION_INTERRUPT) }));
triggers.push_back(new TriggerNode(
"hammer of justice on snare target",
triggers.push_back(new TriggerNode("hammer of justice on snare target",
{ NextAction("hammer of justice on snare target", ACTION_INTERRUPT) }));
triggers.push_back(new TriggerNode(
"critical health", { NextAction("lay on hands", ACTION_EMERGENCY) }));
triggers.push_back(
new TriggerNode("party member critical health",
{ NextAction("lay on hands on party", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode(
"protect party member",
{ NextAction("blessing of protection on party", ACTION_EMERGENCY + 2) }));
triggers.push_back(
new TriggerNode("high mana", { NextAction("divine plea", ACTION_HIGH) }));
triggers.push_back(new TriggerNode("critical health", { NextAction("divine shield", ACTION_EMERGENCY) }));
triggers.push_back(new TriggerNode("critical health", { NextAction("lay on hands", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("party member critical health",
{ NextAction("lay on hands on party", ACTION_EMERGENCY + 2) }));
triggers.push_back(new TriggerNode("divine shield low health",
{ NextAction("flash of light", ACTION_EMERGENCY + 3), NextAction("holy light", ACTION_EMERGENCY + 2)}));
triggers.push_back(new TriggerNode("protect party member",
{ NextAction("blessing of protection on party", ACTION_EMERGENCY + 3) }));
triggers.push_back(new TriggerNode("high mana", { NextAction("divine plea", ACTION_HIGH) }));
triggers.push_back(new TriggerNode("hand of freedom on party",
{ NextAction("hand of freedom on party", ACTION_HIGH + 4) }));
}
void PaladinCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)

View File

@ -8,6 +8,7 @@
#include "PaladinActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "PaladinHelper.h"
bool SealTrigger::IsActive()
{
@ -31,6 +32,45 @@ bool BlessingTrigger::IsActive()
"blessing of kings", "blessing of sanctuary", nullptr);
}
bool DivineShieldLowHealthTrigger::IsActive()
{
return botAI->HasAura("divine shield", bot) && AI_VALUE2(uint8, "health", "self target") < 80;
}
Unit* HandOfFreedomOnPartyTrigger::GetTarget()
{
bool const selfImpaired = botAI->IsMovementImpaired(bot);
bool const hasSelfHand = selfImpaired && ai::paladin::HasAnyPaladinHandFromCaster(bot, bot);
if (!bot->GetGroup())
{
if (selfImpaired && !hasSelfHand)
return bot;
return nullptr;
}
if (selfImpaired && !hasSelfHand)
return bot;
return Trigger::GetTarget();
}
bool HandOfFreedomOnPartyTrigger::IsActive()
{
Unit* target = GetTarget();
if (!target)
return false;
if (target != bot && bot->GetExactDist2dSq(target->GetPositionX(), target->GetPositionY()) > 30.0f * 30.0f)
return false;
if (!botAI->CanCastSpell("hand of freedom", target))
return false;
return !ai::paladin::HasAnyPaladinHandFromCaster(target, bot) && botAI->IsMovementImpaired(target);
}
bool NotSensingUndeadTrigger::IsActive()
{
return !botAI->HasAura("sense undead", bot);

View File

@ -185,6 +185,14 @@ public:
DivineFavorTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "divine favor") {}
};
class DivineShieldLowHealthTrigger : public Trigger
{
public:
DivineShieldLowHealthTrigger(PlayerbotAI* botAI) : Trigger(botAI, "divine shield low health") {}
bool IsActive() override;
};
class NotSensingUndeadTrigger : public BuffTrigger
{
public:
@ -242,6 +250,16 @@ public:
: BuffOnPartyTrigger(botAI, "blessing of sanctuary", 2 * 2000) {}
};
class HandOfFreedomOnPartyTrigger : public Trigger
{
public:
HandOfFreedomOnPartyTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hand of freedom on party", 1) {}
Unit* GetTarget() override;
std::string const GetTargetName() override { return "party member snared target"; }
bool IsActive() override;
};
class AvengingWrathTrigger : public BoostTrigger
{
public:

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PALADINHELPER_H
#define _PLAYERBOT_PALADINHELPER_H
#include <initializer_list>
#include "Unit.h"
class Player;
namespace ai::paladin
{
static constexpr uint32 SPELL_HAND_OF_PROTECTION = 1022;
static constexpr uint32 SPELL_HAND_OF_SALVATION = 1038;
static constexpr uint32 SPELL_HAND_OF_FREEDOM = 1044;
static constexpr uint32 SPELL_HAND_OF_SACRIFICE = 6940;
inline bool HasHandFromCaster(Unit* target, Player* caster, std::initializer_list<uint32> spellIds)
{
if (!target || !caster)
return false;
for (uint32 spellId : spellIds)
{
if (target->HasAura(spellId, caster->GetGUID()))
return true;
}
return false;
}
inline bool HasAnyPaladinHandFromCaster(Unit* target, Player* caster)
{
return HasHandFromCaster(target, caster,
{ SPELL_HAND_OF_PROTECTION, SPELL_HAND_OF_SALVATION, SPELL_HAND_OF_FREEDOM, SPELL_HAND_OF_SACRIFICE });
}
}
#endif

View File

@ -57,7 +57,8 @@ bool CastLavaBurstAction::isUseful()
if (!target)
return false;
static const uint32 FLAME_SHOCK_SPELL_IDS[] = {8050, 8052, 8053, 10447, 10448, 29228, 25457, 49232, 49233};
static const uint32 FLAME_SHOCK_SPELL_IDS[] =
{8050, 8052, 8053, 10447, 10448, 29228, 25457, 49232, 49233};
ObjectGuid botGuid = bot->GetGUID();
for (uint32 spellId : FLAME_SHOCK_SPELL_IDS)
@ -65,6 +66,7 @@ bool CastLavaBurstAction::isUseful()
if (target->HasAura(spellId, botGuid))
return true;
}
return false;
}
@ -77,15 +79,13 @@ bool CastSpiritWalkAction::Execute(Event /*event*/)
for (Unit* unit : bot->m_Controlled)
{
if (unit->GetEntry() == SPIRIT_WOLF)
if (unit->GetEntry() == SPIRIT_WOLF && unit->HasSpell(SPIRIT_WALK_SPELL))
{
if (unit->HasSpell(SPIRIT_WALK_SPELL))
{
unit->CastSpell(unit, SPIRIT_WALK_SPELL, false);
return true;
}
unit->CastSpell(unit, SPIRIT_WALK_SPELL, false);
return true;
}
}
return false;
}
@ -105,18 +105,15 @@ bool SetTotemAction::Execute(Event /*event*/)
}
if (!totemSpell)
return false;
if (const ActionButton* button = bot->GetActionButton(actionButtonId);
button && button->GetType() == ACTION_BUTTON_SPELL &&
button->GetAction() == totemSpell)
{
return false;
}
if (const ActionButton* button = bot->GetActionButton(actionButtonId))
{
if (button->GetType() == ACTION_BUTTON_SPELL && button->GetAction() == totemSpell)
{
return false;
}
}
bot->addActionButton(actionButtonId, totemSpell, ACTION_BUTTON_SPELL);
return true;
}

View File

@ -18,73 +18,92 @@ class PlayerbotAI;
class CastWaterShieldAction : public CastBuffSpellAction
{
public:
CastWaterShieldAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "water shield") {}
CastWaterShieldAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "water shield") {}
};
class CastLightningShieldAction : public CastBuffSpellAction
{
public:
CastLightningShieldAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "lightning shield") {}
CastLightningShieldAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "lightning shield") {}
};
class CastEarthlivingWeaponAction : public CastEnchantItemAction
class CastRockbiterWeaponMainHandAction : public CastEnchantItemMainHandAction
{
public:
CastEarthlivingWeaponAction(PlayerbotAI* botAI) : CastEnchantItemAction(botAI, "earthliving weapon") {}
CastRockbiterWeaponMainHandAction(PlayerbotAI* botAI) :
CastEnchantItemMainHandAction(botAI, "rockbiter weapon") {}
};
class CastRockbiterWeaponAction : public CastEnchantItemAction
class CastFlametongueWeaponMainHandAction : public CastEnchantItemMainHandAction
{
public:
CastRockbiterWeaponAction(PlayerbotAI* botAI) : CastEnchantItemAction(botAI, "rockbiter weapon") {}
CastFlametongueWeaponMainHandAction(PlayerbotAI* botAI) :
CastEnchantItemMainHandAction(botAI, "flametongue weapon") {}
};
class CastFlametongueWeaponAction : public CastEnchantItemAction
class CastFlametongueWeaponOffHandAction : public CastEnchantItemOffHandAction
{
public:
CastFlametongueWeaponAction(PlayerbotAI* botAI) : CastEnchantItemAction(botAI, "flametongue weapon") {}
CastFlametongueWeaponOffHandAction(PlayerbotAI* botAI) :
CastEnchantItemOffHandAction(botAI, "flametongue weapon") {}
};
class CastFrostbrandWeaponAction : public CastEnchantItemAction
/* class CastFrostbrandWeaponOffHandAction : public CastEnchantItemOffHandAction
{
public:
CastFrostbrandWeaponAction(PlayerbotAI* botAI) : CastEnchantItemAction(botAI, "frostbrand weapon") {}
CastFrostbrandWeaponOffHandAction(PlayerbotAI* botAI) :
CastEnchantItemOffHandAction(botAI, "frostbrand weapon") {}
}; */
class CastEarthlivingWeaponMainHandAction : public CastEnchantItemMainHandAction
{
public:
CastEarthlivingWeaponMainHandAction(PlayerbotAI* botAI) :
CastEnchantItemMainHandAction(botAI, "earthliving weapon") {}
};
class CastWindfuryWeaponAction : public CastEnchantItemAction
class CastWindfuryWeaponMainHandAction : public CastEnchantItemMainHandAction
{
public:
CastWindfuryWeaponAction(PlayerbotAI* botAI) : CastEnchantItemAction(botAI, "windfury weapon") {}
CastWindfuryWeaponMainHandAction(PlayerbotAI* botAI) :
CastEnchantItemMainHandAction(botAI, "windfury weapon") {}
};
class CastAncestralSpiritAction : public ResurrectPartyMemberAction
{
public:
CastAncestralSpiritAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "ancestral spirit") {}
CastAncestralSpiritAction(PlayerbotAI* botAI) :
ResurrectPartyMemberAction(botAI, "ancestral spirit") {}
};
class CastWaterBreathingAction : public CastBuffSpellAction
{
public:
CastWaterBreathingAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "water breathing") {}
CastWaterBreathingAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "water breathing") {}
};
class CastWaterWalkingAction : public CastBuffSpellAction
{
public:
CastWaterWalkingAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "water walking") {}
CastWaterWalkingAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "water walking") {}
};
class CastWaterBreathingOnPartyAction : public BuffOnPartyAction
{
public:
CastWaterBreathingOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "water breathing") {}
CastWaterBreathingOnPartyAction(PlayerbotAI* botAI) :
BuffOnPartyAction(botAI, "water breathing") {}
};
class CastWaterWalkingOnPartyAction : public BuffOnPartyAction
{
public:
CastWaterWalkingOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "water walking") {}
CastWaterWalkingOnPartyAction(PlayerbotAI* botAI) :
BuffOnPartyAction(botAI, "water walking") {}
};
// Boost Actions
@ -92,31 +111,36 @@ public:
class CastHeroismAction : public CastBuffSpellAction
{
public:
CastHeroismAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "heroism") {}
CastHeroismAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "heroism") {}
};
class CastBloodlustAction : public CastBuffSpellAction
{
public:
CastBloodlustAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "bloodlust") {}
CastBloodlustAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "bloodlust") {}
};
class CastElementalMasteryAction : public CastBuffSpellAction
{
public:
CastElementalMasteryAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "elemental mastery") {}
CastElementalMasteryAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "elemental mastery") {}
};
class CastShamanisticRageAction : public CastBuffSpellAction
{
public:
CastShamanisticRageAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "shamanistic rage") {}
CastShamanisticRageAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "shamanistic rage") {}
};
class CastFeralSpiritAction : public CastSpellAction
{
public:
CastFeralSpiritAction(PlayerbotAI* ai) : CastSpellAction(ai, "feral spirit") {}
CastFeralSpiritAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "feral spirit") {}
};
class CastSpiritWalkAction : public Action
@ -138,7 +162,8 @@ public:
class CastWindShearOnEnemyHealerAction : public CastSpellOnEnemyHealerAction
{
public:
CastWindShearOnEnemyHealerAction(PlayerbotAI* botAI) : CastSpellOnEnemyHealerAction(botAI, "wind shear") {}
CastWindShearOnEnemyHealerAction(PlayerbotAI* botAI) :
CastSpellOnEnemyHealerAction(botAI, "wind shear") {}
};
class CastPurgeAction : public CastSpellAction
@ -150,16 +175,15 @@ public:
class CastCleanseSpiritAction : public CastCureSpellAction
{
public:
CastCleanseSpiritAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "cleanse spirit") {}
CastCleanseSpiritAction(PlayerbotAI* botAI) :
CastCureSpellAction(botAI, "cleanse spirit") {}
};
class CastCleanseSpiritPoisonOnPartyAction : public CurePartyMemberAction
{
public:
CastCleanseSpiritPoisonOnPartyAction(PlayerbotAI* botAI)
: CurePartyMemberAction(botAI, "cleanse spirit", DISPEL_POISON)
{
}
CastCleanseSpiritPoisonOnPartyAction(PlayerbotAI* botAI) :
CurePartyMemberAction(botAI, "cleanse spirit", DISPEL_POISON) {}
std::string const getName() override { return "cleanse spirit poison on party"; }
};
@ -167,10 +191,8 @@ public:
class CastCleanseSpiritCurseOnPartyAction : public CurePartyMemberAction
{
public:
CastCleanseSpiritCurseOnPartyAction(PlayerbotAI* botAI)
: CurePartyMemberAction(botAI, "cleanse spirit", DISPEL_CURSE)
{
}
CastCleanseSpiritCurseOnPartyAction(PlayerbotAI* botAI) :
CurePartyMemberAction(botAI, "cleanse spirit", DISPEL_CURSE) {}
std::string const getName() override { return "cleanse spirit curse on party"; }
};
@ -178,42 +200,35 @@ public:
class CastCleanseSpiritDiseaseOnPartyAction : public CurePartyMemberAction
{
public:
CastCleanseSpiritDiseaseOnPartyAction(PlayerbotAI* botAI)
: CurePartyMemberAction(botAI, "cleanse spirit", DISPEL_DISEASE)
{
}
CastCleanseSpiritDiseaseOnPartyAction(PlayerbotAI* botAI) :
CurePartyMemberAction(botAI, "cleanse spirit", DISPEL_DISEASE) {}
std::string const getName() override { return "cleanse spirit disease on party"; }
};
class CastCurePoisonActionSham : public CastCureSpellAction
class CastCureToxinsActionSham : public CastCureSpellAction
{
public:
CastCurePoisonActionSham(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "cure poison") {}
CastCureToxinsActionSham(PlayerbotAI* botAI) :
CastCureSpellAction(botAI, "cure toxins") {}
};
class CastCurePoisonOnPartyActionSham : public CurePartyMemberAction
class CastCureToxinsPoisonOnPartyActionSham : public CurePartyMemberAction
{
public:
CastCurePoisonOnPartyActionSham(PlayerbotAI* botAI) : CurePartyMemberAction(botAI, "cure poison", DISPEL_POISON) {}
CastCureToxinsPoisonOnPartyActionSham(PlayerbotAI* botAI) :
CurePartyMemberAction(botAI, "cure toxins", DISPEL_POISON) {}
std::string const getName() override { return "cure poison on party"; }
std::string const getName() override { return "cure toxins poison on party"; }
};
class CastCureDiseaseActionSham : public CastCureSpellAction
class CastCureToxinsDiseaseOnPartyActionSham : public CurePartyMemberAction
{
public:
CastCureDiseaseActionSham(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "cure disease") {}
};
CastCureToxinsDiseaseOnPartyActionSham(PlayerbotAI* botAI) :
CurePartyMemberAction(botAI, "cure toxins", DISPEL_DISEASE) {}
class CastCureDiseaseOnPartyActionSham : public CurePartyMemberAction
{
public:
CastCureDiseaseOnPartyActionSham(PlayerbotAI* botAI) : CurePartyMemberAction(botAI, "cure disease", DISPEL_DISEASE)
{
}
std::string const getName() override { return "cure disease on party"; }
std::string const getName() override { return "cure toxins disease on party"; }
};
// Damage and Debuff Actions
@ -221,68 +236,77 @@ public:
class CastFireNovaAction : public CastMeleeSpellAction
{
public:
CastFireNovaAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "fire nova") {}
CastFireNovaAction(PlayerbotAI* botAI) :
CastMeleeSpellAction(botAI, "fire nova") {}
bool isUseful() override;
};
class CastStormstrikeAction : public CastMeleeSpellAction
{
public:
CastStormstrikeAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "stormstrike") {}
CastStormstrikeAction(PlayerbotAI* botAI) :
CastMeleeSpellAction(botAI, "stormstrike") {}
};
class CastLavaLashAction : public CastMeleeSpellAction
{
public:
CastLavaLashAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "lava lash") {}
CastLavaLashAction(PlayerbotAI* botAI) :
CastMeleeSpellAction(botAI, "lava lash") {}
};
class CastFlameShockAction : public CastDebuffSpellAction
{
public:
CastFlameShockAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "flame shock", true, 6.0f) {}
bool isUseful() override
{
// Bypass TTL check
return CastAuraSpellAction::isUseful();
}
CastFlameShockAction(PlayerbotAI* botAI) :
CastDebuffSpellAction(botAI, "flame shock", true, 6.0f) {}
bool isUseful() override { return CastAuraSpellAction::isUseful(); }
};
class CastEarthShockAction : public CastSpellAction
{
public:
CastEarthShockAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "earth shock") {}
CastEarthShockAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "earth shock") {}
};
class CastFrostShockAction : public CastSnareSpellAction
{
public:
CastFrostShockAction(PlayerbotAI* botAI) : CastSnareSpellAction(botAI, "frost shock") {}
CastFrostShockAction(PlayerbotAI* botAI) :
CastSnareSpellAction(botAI, "frost shock") {}
};
class CastChainLightningAction : public CastSpellAction
{
public:
CastChainLightningAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "chain lightning") {}
CastChainLightningAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "chain lightning") {}
ActionThreatType getThreatType() override { return ActionThreatType::Aoe; }
};
class CastLightningBoltAction : public CastSpellAction
{
public:
CastLightningBoltAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "lightning bolt") {}
CastLightningBoltAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "lightning bolt") {}
};
class CastThunderstormAction : public CastSpellAction
{
public:
CastThunderstormAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "thunderstorm") {}
CastThunderstormAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "thunderstorm") {}
};
class CastLavaBurstAction : public CastSpellAction
{
public:
CastLavaBurstAction(PlayerbotAI* ai) : CastSpellAction(ai, "lava burst") {}
CastLavaBurstAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "lava burst") {}
bool isUseful() override;
};
@ -291,73 +315,71 @@ public:
class CastLesserHealingWaveAction : public CastHealingSpellAction
{
public:
CastLesserHealingWaveAction(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, "lesser healing wave") {}
CastLesserHealingWaveAction(PlayerbotAI* botAI) :
CastHealingSpellAction(botAI, "lesser healing wave") {}
};
class CastLesserHealingWaveOnPartyAction : public HealPartyMemberAction
{
public:
CastLesserHealingWaveOnPartyAction(PlayerbotAI* botAI)
: HealPartyMemberAction(botAI, "lesser healing wave", 25.0f, HealingManaEfficiency::LOW)
{
}
CastLesserHealingWaveOnPartyAction(PlayerbotAI* botAI) :
HealPartyMemberAction(botAI, "lesser healing wave", 25.0f, HealingManaEfficiency::LOW) {}
};
class CastHealingWaveAction : public CastHealingSpellAction
{
public:
CastHealingWaveAction(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, "healing wave") {}
CastHealingWaveAction(PlayerbotAI* botAI) :
CastHealingSpellAction(botAI, "healing wave") {}
};
class CastHealingWaveOnPartyAction : public HealPartyMemberAction
{
public:
CastHealingWaveOnPartyAction(PlayerbotAI* botAI)
: HealPartyMemberAction(botAI, "healing wave", 50.0f, HealingManaEfficiency::MEDIUM)
{
}
CastHealingWaveOnPartyAction(PlayerbotAI* botAI) :
HealPartyMemberAction(botAI, "healing wave", 50.0f, HealingManaEfficiency::MEDIUM) {}
};
class CastChainHealAction : public HealPartyMemberAction
{
public:
CastChainHealAction(PlayerbotAI* botAI)
: HealPartyMemberAction(botAI, "chain heal", 15.0f, HealingManaEfficiency::HIGH)
{
}
CastChainHealAction(PlayerbotAI* botAI) :
HealPartyMemberAction(botAI, "chain heal", 15.0f, HealingManaEfficiency::HIGH) {}
};
class CastRiptideAction : public CastHealingSpellAction
{
public:
CastRiptideAction(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, "riptide") {}
CastRiptideAction(PlayerbotAI* botAI) :
CastHealingSpellAction(botAI, "riptide") {}
};
class CastRiptideOnPartyAction : public HealPartyMemberAction
{
public:
CastRiptideOnPartyAction(PlayerbotAI* botAI)
: HealPartyMemberAction(botAI, "riptide", 15.0f, HealingManaEfficiency::VERY_HIGH)
{
}
CastRiptideOnPartyAction(PlayerbotAI* botAI) :
HealPartyMemberAction(botAI, "riptide", 15.0f, HealingManaEfficiency::VERY_HIGH) {}
};
class CastEarthShieldAction : public CastBuffSpellAction
{
public:
CastEarthShieldAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "earth shield") {}
CastEarthShieldAction(PlayerbotAI* botAI) :
CastBuffSpellAction(botAI, "earth shield") {}
};
class CastEarthShieldOnPartyAction : public BuffOnPartyAction
{
public:
CastEarthShieldOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "earth shield") {}
CastEarthShieldOnPartyAction(PlayerbotAI* botAI) :
BuffOnPartyAction(botAI, "earth shield") {}
};
class CastEarthShieldOnMainTankAction : public BuffOnMainTankAction
{
public:
CastEarthShieldOnMainTankAction(PlayerbotAI* ai) : BuffOnMainTankAction(ai, "earth shield", false) {}
CastEarthShieldOnMainTankAction(PlayerbotAI* botAI) :
BuffOnMainTankAction(botAI, "earth shield", false) {}
};
// Totem Spells
@ -365,8 +387,9 @@ public:
class CastTotemAction : public CastBuffSpellAction
{
public:
CastTotemAction(PlayerbotAI* botAI, std::string const spell, std::string const buffName = "")
: CastBuffSpellAction(botAI, spell)
CastTotemAction(
PlayerbotAI* botAI, std::string const spell,
std::string const buffName = "") : CastBuffSpellAction(botAI, spell)
{
buff = (buffName == "") ? spell : buffName;
}
@ -380,56 +403,66 @@ protected:
class CastCallOfTheElementsAction : public CastSpellAction
{
public:
CastCallOfTheElementsAction(PlayerbotAI* ai) : CastSpellAction(ai, "call of the elements") {}
CastCallOfTheElementsAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "call of the elements") {}
};
class CastTotemicRecallAction : public CastSpellAction
{
public:
CastTotemicRecallAction(PlayerbotAI* ai) : CastSpellAction(ai, "totemic recall") {}
CastTotemicRecallAction(PlayerbotAI* botAI) :
CastSpellAction(botAI, "totemic recall") {}
};
class CastStrengthOfEarthTotemAction : public CastTotemAction
{
public:
CastStrengthOfEarthTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "strength of earth totem", "strength of earth") {}
CastStrengthOfEarthTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "strength of earth totem", "strength of earth") {}
};
class CastStoneskinTotemAction : public CastTotemAction
{
public:
CastStoneskinTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "stoneskin totem", "stoneskin") {}
CastStoneskinTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "stoneskin totem", "stoneskin") {}
};
class CastTremorTotemAction : public CastTotemAction
{
public:
CastTremorTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "tremor totem", "") {}
CastTremorTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "tremor totem", "") {}
};
class CastEarthbindTotemAction : public CastTotemAction
{
public:
CastEarthbindTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "earthbind totem", "") {}
CastEarthbindTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "earthbind totem", "") {}
};
class CastStoneclawTotemAction : public CastTotemAction
{
public:
CastStoneclawTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "stoneclaw totem", "") {}
CastStoneclawTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "stoneclaw totem", "") {}
bool isUseful() override;
};
class CastSearingTotemAction : public CastTotemAction
{
public:
CastSearingTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "searing totem", "") {}
CastSearingTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "searing totem", "") {}
};
class CastMagmaTotemAction : public CastTotemAction
{
public:
CastMagmaTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "magma totem", "") {}
CastMagmaTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "magma totem", "") {}
std::string const GetTargetName() override { return "self target"; }
bool isUseful() override;
};
@ -437,26 +470,30 @@ public:
class CastFlametongueTotemAction : public CastTotemAction
{
public:
CastFlametongueTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "flametongue totem", "flametongue totem") {}
CastFlametongueTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "flametongue totem", "flametongue totem") {}
};
class CastTotemOfWrathAction : public CastTotemAction
{
public:
CastTotemOfWrathAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "totem of wrath", "totem of wrath") {}
CastTotemOfWrathAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "totem of wrath", "totem of wrath") {}
};
class CastFrostResistanceTotemAction : public CastTotemAction
{
public:
CastFrostResistanceTotemAction(PlayerbotAI* botAI)
: CastTotemAction(botAI, "frost resistance totem", "frost resistance") {}
CastFrostResistanceTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "frost resistance totem", "frost resistance") {}
};
class CastFireElementalTotemAction : public CastTotemAction
{
public:
CastFireElementalTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "fire elemental totem", "") {}
CastFireElementalTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "fire elemental totem", "") {}
virtual std::string const GetTargetName() override { return "self target"; }
virtual bool isUseful() override { return CastTotemAction::isUseful(); }
};
@ -464,7 +501,9 @@ public:
class CastFireElementalTotemMeleeAction : public CastTotemAction
{
public:
CastFireElementalTotemMeleeAction(PlayerbotAI* ai) : CastTotemAction(ai, "fire elemental totem", "") {}
CastFireElementalTotemMeleeAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "fire elemental totem", "") {}
virtual std::string const GetTargetName() override { return "self target"; }
virtual bool isUseful() override
{
@ -478,51 +517,60 @@ public:
class CastHealingStreamTotemAction : public CastTotemAction
{
public:
CastHealingStreamTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "healing stream totem", "") {}
CastHealingStreamTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "healing stream totem", "") {}
};
class CastManaSpringTotemAction : public CastTotemAction
{
public:
CastManaSpringTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "mana spring totem", "mana spring") {}
CastManaSpringTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "mana spring totem", "mana spring") {}
};
class CastCleansingTotemAction : public CastTotemAction
{
public:
CastCleansingTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "cleansing totem", "") {}
CastCleansingTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "cleansing totem", "") {}
virtual bool isUseful();
};
class CastManaTideTotemAction : public CastTotemAction
{
public:
CastManaTideTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "mana tide totem", "") {}
CastManaTideTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "mana tide totem", "") {}
std::string const GetTargetName() override { return "self target"; }
};
class CastFireResistanceTotemAction : public CastTotemAction
{
public:
CastFireResistanceTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "fire resistance totem", "fire resistance") {}
CastFireResistanceTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "fire resistance totem", "fire resistance") {}
};
class CastWrathOfAirTotemAction : public CastTotemAction
{
public:
CastWrathOfAirTotemAction(PlayerbotAI* ai) : CastTotemAction(ai, "wrath of air totem", "wrath of air totem") {}
CastWrathOfAirTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "wrath of air totem", "wrath of air totem") {}
};
class CastWindfuryTotemAction : public CastTotemAction
{
public:
CastWindfuryTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "windfury totem", "windfury totem") {}
CastWindfuryTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "windfury totem", "windfury totem") {}
};
class CastNatureResistanceTotemAction : public CastTotemAction
{
public:
CastNatureResistanceTotemAction(PlayerbotAI* botAI) : CastTotemAction(botAI, "nature resistance totem", "nature resistance") {}
CastNatureResistanceTotemAction(PlayerbotAI* botAI) :
CastTotemAction(botAI, "nature resistance totem", "nature resistance") {}
};
// Set Strategy Assigned Totems
@ -532,12 +580,8 @@ class SetTotemAction : public Action
public:
// Template constructor: infers N (size of the id array) at compile time
template <size_t N>
SetTotemAction(PlayerbotAI* botAI, std::string const& totemName, const uint32 (&ids)[N], int actionButtonId)
: Action(botAI, "set " + totemName)
, totemSpellIds(ids)
, totemSpellIdsCount(N)
, actionButtonId(actionButtonId)
{}
SetTotemAction(PlayerbotAI* botAI, std::string const& totemName, const uint32 (&ids)[N], int actionButtonId) :
Action(botAI, "set " + totemName), totemSpellIds(ids), totemSpellIdsCount(N), actionButtonId(actionButtonId) {}
bool Execute(Event event) override;
uint32 const* totemSpellIds;
@ -548,120 +592,120 @@ public:
class SetStrengthOfEarthTotemAction : public SetTotemAction
{
public:
SetStrengthOfEarthTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "strength of earth totem", STRENGTH_OF_EARTH_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
SetStrengthOfEarthTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "strength of earth totem", STRENGTH_OF_EARTH_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
};
class SetStoneskinTotemAction : public SetTotemAction
{
public:
SetStoneskinTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "stoneskin totem", STONESKIN_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
SetStoneskinTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "stoneskin totem", STONESKIN_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
};
class SetTremorTotemAction : public SetTotemAction
{
public:
SetTremorTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "tremor totem", TREMOR_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
SetTremorTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "tremor totem", TREMOR_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
};
class SetEarthbindTotemAction : public SetTotemAction
{
public:
SetEarthbindTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "earthbind totem", EARTHBIND_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
SetEarthbindTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "earthbind totem", EARTHBIND_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
};
class SetSearingTotemAction : public SetTotemAction
{
public:
SetSearingTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "searing totem", SEARING_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
SetSearingTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "searing totem", SEARING_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
};
class SetMagmaTotemAction : public SetTotemAction
{
public:
SetMagmaTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "magma totem", MAGMA_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
SetMagmaTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "magma totem", MAGMA_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
};
class SetFlametongueTotemAction : public SetTotemAction
{
public:
SetFlametongueTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "flametongue totem", FLAMETONGUE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
SetFlametongueTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "flametongue totem", FLAMETONGUE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
};
class SetTotemOfWrathAction : public SetTotemAction
{
public:
SetTotemOfWrathAction(PlayerbotAI* ai)
: SetTotemAction(ai, "totem of wrath", TOTEM_OF_WRATH, TOTEM_BAR_SLOT_FIRE) {}
SetTotemOfWrathAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "totem of wrath", TOTEM_OF_WRATH, TOTEM_BAR_SLOT_FIRE) {}
};
class SetFrostResistanceTotemAction : public SetTotemAction
{
public:
SetFrostResistanceTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "frost resistance totem", FROST_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
SetFrostResistanceTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "frost resistance totem", FROST_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
};
class SetHealingStreamTotemAction : public SetTotemAction
{
public:
SetHealingStreamTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "healing stream totem", HEALING_STREAM_TOTEM, TOTEM_BAR_SLOT_WATER) {}
SetHealingStreamTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "healing stream totem", HEALING_STREAM_TOTEM, TOTEM_BAR_SLOT_WATER) {}
};
class SetManaSpringTotemAction : public SetTotemAction
{
public:
SetManaSpringTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "mana spring totem", MANA_SPRING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
SetManaSpringTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "mana spring totem", MANA_SPRING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
};
class SetCleansingTotemAction : public SetTotemAction
{
public:
SetCleansingTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "cleansing totem", CLEANSING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
SetCleansingTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "cleansing totem", CLEANSING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
};
class SetFireResistanceTotemAction : public SetTotemAction
{
public:
SetFireResistanceTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "fire resistance totem", FIRE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_WATER) {}
SetFireResistanceTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "fire resistance totem", FIRE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_WATER) {}
};
class SetWrathOfAirTotemAction : public SetTotemAction
{
public:
SetWrathOfAirTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "wrath of air totem", WRATH_OF_AIR_TOTEM, TOTEM_BAR_SLOT_AIR) {}
SetWrathOfAirTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "wrath of air totem", WRATH_OF_AIR_TOTEM, TOTEM_BAR_SLOT_AIR) {}
};
class SetWindfuryTotemAction : public SetTotemAction
{
public:
SetWindfuryTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "windfury totem", WINDFURY_TOTEM, TOTEM_BAR_SLOT_AIR) {}
SetWindfuryTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "windfury totem", WINDFURY_TOTEM, TOTEM_BAR_SLOT_AIR) {}
};
class SetNatureResistanceTotemAction : public SetTotemAction
{
public:
SetNatureResistanceTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "nature resistance totem", NATURE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_AIR) {}
SetNatureResistanceTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "nature resistance totem", NATURE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_AIR) {}
};
class SetGroundingTotemAction : public SetTotemAction
{
public:
SetGroundingTotemAction(PlayerbotAI* ai)
: SetTotemAction(ai, "grounding totem", GROUNDING_TOTEM, TOTEM_BAR_SLOT_AIR) {}
SetGroundingTotemAction(PlayerbotAI* botAI) :
SetTotemAction(botAI, "grounding totem", GROUNDING_TOTEM, TOTEM_BAR_SLOT_AIR) {}
};
#endif

View File

@ -158,10 +158,6 @@ public:
creators["bloodlust"] = &ShamanATriggerFactoryInternal::bloodlust;
creators["elemental mastery"] = &ShamanATriggerFactoryInternal::elemental_mastery;
creators["wind shear on enemy healer"] = &ShamanATriggerFactoryInternal::wind_shear_on_enemy_healer;
creators["cure poison"] = &ShamanATriggerFactoryInternal::cure_poison;
creators["party member cure poison"] = &ShamanATriggerFactoryInternal::party_member_cure_poison;
creators["cure disease"] = &ShamanATriggerFactoryInternal::cure_disease;
creators["party member cure disease"] = &ShamanATriggerFactoryInternal::party_member_cure_disease;
creators["earth shield on main tank"] = &ShamanATriggerFactoryInternal::earth_shield_on_main_tank;
creators["maelstrom weapon 3"] = &ShamanATriggerFactoryInternal::maelstrom_weapon_3;
creators["maelstrom weapon 4"] = &ShamanATriggerFactoryInternal::maelstrom_weapon_4;
@ -225,42 +221,38 @@ private:
static Trigger* shock(PlayerbotAI* botAI) { return new ShockTrigger(botAI); }
static Trigger* frost_shock_snare(PlayerbotAI* botAI) { return new FrostShockSnareTrigger(botAI); }
static Trigger* wind_shear_on_enemy_healer(PlayerbotAI* botAI) { return new WindShearInterruptEnemyHealerSpellTrigger(botAI); }
static Trigger* cure_poison(PlayerbotAI* botAI) { return new CurePoisonTrigger(botAI); }
static Trigger* party_member_cure_poison(PlayerbotAI* botAI) { return new PartyMemberCurePoisonTrigger(botAI); }
static Trigger* cure_disease(PlayerbotAI* botAI) { return new CureDiseaseTrigger(botAI); }
static Trigger* party_member_cure_disease(PlayerbotAI* botAI) { return new PartyMemberCureDiseaseTrigger(botAI); }
static Trigger* earth_shield_on_main_tank(PlayerbotAI* ai) { return new EarthShieldOnMainTankTrigger(ai); }
static Trigger* flame_shock(PlayerbotAI* ai) { return new FlameShockTrigger(ai); }
static Trigger* earth_shield_on_main_tank(PlayerbotAI* botAI) { return new EarthShieldOnMainTankTrigger(botAI); }
static Trigger* flame_shock(PlayerbotAI* botAI) { return new FlameShockTrigger(botAI); }
static Trigger* fire_elemental_totem(PlayerbotAI* botAI) { return new FireElementalTotemTrigger(botAI); }
static Trigger* earth_shock_execute(PlayerbotAI* botAI) { return new EarthShockExecuteTrigger(botAI); }
static Trigger* spirit_walk_ready(PlayerbotAI* ai) { return new SpiritWalkTrigger(ai); }
static Trigger* chain_lightning_no_cd(PlayerbotAI* ai) { return new ChainLightningNoCdTrigger(ai); }
static Trigger* call_of_the_elements_and_enemy_within_melee(PlayerbotAI* ai) { return new CallOfTheElementsAndEnemyWithinMeleeTrigger(ai); }
static Trigger* maelstrom_weapon_5_and_medium_aoe(PlayerbotAI* ai) { return new MaelstromWeapon5AndMediumAoeTrigger(ai); }
static Trigger* maelstrom_weapon_4_and_medium_aoe(PlayerbotAI* ai) { return new MaelstromWeapon4AndMediumAoeTrigger(ai); }
static Trigger* call_of_the_elements(PlayerbotAI* ai) { return new CallOfTheElementsTrigger(ai); }
static Trigger* totemic_recall(PlayerbotAI* ai) { return new TotemicRecallTrigger(ai); }
static Trigger* no_earth_totem(PlayerbotAI* ai) { return new NoEarthTotemTrigger(ai); }
static Trigger* no_fire_totem(PlayerbotAI* ai) { return new NoFireTotemTrigger(ai); }
static Trigger* no_water_totem(PlayerbotAI* ai) { return new NoWaterTotemTrigger(ai); }
static Trigger* no_air_totem(PlayerbotAI* ai) { return new NoAirTotemTrigger(ai); }
static Trigger* set_strength_of_earth_totem(PlayerbotAI* ai) { return new SetStrengthOfEarthTotemTrigger(ai); }
static Trigger* set_stoneskin_totem(PlayerbotAI* ai) { return new SetStoneskinTotemTrigger(ai); }
static Trigger* set_tremor_totem(PlayerbotAI* ai) { return new SetTremorTotemTrigger(ai); }
static Trigger* set_earthbind_totem(PlayerbotAI* ai) { return new SetEarthbindTotemTrigger(ai); }
static Trigger* set_searing_totem(PlayerbotAI* ai) { return new SetSearingTotemTrigger(ai); }
static Trigger* set_magma_totem(PlayerbotAI* ai) { return new SetMagmaTotemTrigger(ai); }
static Trigger* set_flametongue_totem(PlayerbotAI* ai) { return new SetFlametongueTotemTrigger(ai); }
static Trigger* set_totem_of_wrath(PlayerbotAI* ai) { return new SetTotemOfWrathTrigger(ai); }
static Trigger* set_frost_resistance_totem(PlayerbotAI* ai) { return new SetFrostResistanceTotemTrigger(ai); }
static Trigger* set_healing_stream_totem(PlayerbotAI* ai) { return new SetHealingStreamTotemTrigger(ai); }
static Trigger* set_mana_spring_totem(PlayerbotAI* ai) { return new SetManaSpringTotemTrigger(ai); }
static Trigger* set_cleansing_totem(PlayerbotAI* ai) { return new SetCleansingTotemTrigger(ai); }
static Trigger* set_fire_resistance_totem(PlayerbotAI* ai) { return new SetFireResistanceTotemTrigger(ai); }
static Trigger* set_wrath_of_air_totem(PlayerbotAI* ai) { return new SetWrathOfAirTotemTrigger(ai); }
static Trigger* set_windfury_totem(PlayerbotAI* ai) { return new SetWindfuryTotemTrigger(ai); }
static Trigger* set_nature_resistance_totem(PlayerbotAI* ai) { return new SetNatureResistanceTotemTrigger(ai); }
static Trigger* set_grounding_totem(PlayerbotAI* ai) { return new SetGroundingTotemTrigger(ai); }
static Trigger* spirit_walk_ready(PlayerbotAI* botAI) { return new SpiritWalkTrigger(botAI); }
static Trigger* chain_lightning_no_cd(PlayerbotAI* botAI) { return new ChainLightningNoCdTrigger(botAI); }
static Trigger* call_of_the_elements_and_enemy_within_melee(PlayerbotAI* botAI) { return new CallOfTheElementsAndEnemyWithinMeleeTrigger(botAI); }
static Trigger* maelstrom_weapon_5_and_medium_aoe(PlayerbotAI* botAI) { return new MaelstromWeapon5AndMediumAoeTrigger(botAI); }
static Trigger* maelstrom_weapon_4_and_medium_aoe(PlayerbotAI* botAI) { return new MaelstromWeapon4AndMediumAoeTrigger(botAI); }
static Trigger* call_of_the_elements(PlayerbotAI* botAI) { return new CallOfTheElementsTrigger(botAI); }
static Trigger* totemic_recall(PlayerbotAI* botAI) { return new TotemicRecallTrigger(botAI); }
static Trigger* no_earth_totem(PlayerbotAI* botAI) { return new NoEarthTotemTrigger(botAI); }
static Trigger* no_fire_totem(PlayerbotAI* botAI) { return new NoFireTotemTrigger(botAI); }
static Trigger* no_water_totem(PlayerbotAI* botAI) { return new NoWaterTotemTrigger(botAI); }
static Trigger* no_air_totem(PlayerbotAI* botAI) { return new NoAirTotemTrigger(botAI); }
static Trigger* set_strength_of_earth_totem(PlayerbotAI* botAI) { return new SetStrengthOfEarthTotemTrigger(botAI); }
static Trigger* set_stoneskin_totem(PlayerbotAI* botAI) { return new SetStoneskinTotemTrigger(botAI); }
static Trigger* set_tremor_totem(PlayerbotAI* botAI) { return new SetTremorTotemTrigger(botAI); }
static Trigger* set_earthbind_totem(PlayerbotAI* botAI) { return new SetEarthbindTotemTrigger(botAI); }
static Trigger* set_searing_totem(PlayerbotAI* botAI) { return new SetSearingTotemTrigger(botAI); }
static Trigger* set_magma_totem(PlayerbotAI* botAI) { return new SetMagmaTotemTrigger(botAI); }
static Trigger* set_flametongue_totem(PlayerbotAI* botAI) { return new SetFlametongueTotemTrigger(botAI); }
static Trigger* set_totem_of_wrath(PlayerbotAI* botAI) { return new SetTotemOfWrathTrigger(botAI); }
static Trigger* set_frost_resistance_totem(PlayerbotAI* botAI) { return new SetFrostResistanceTotemTrigger(botAI); }
static Trigger* set_healing_stream_totem(PlayerbotAI* botAI) { return new SetHealingStreamTotemTrigger(botAI); }
static Trigger* set_mana_spring_totem(PlayerbotAI* botAI) { return new SetManaSpringTotemTrigger(botAI); }
static Trigger* set_cleansing_totem(PlayerbotAI* botAI) { return new SetCleansingTotemTrigger(botAI); }
static Trigger* set_fire_resistance_totem(PlayerbotAI* botAI) { return new SetFireResistanceTotemTrigger(botAI); }
static Trigger* set_wrath_of_air_totem(PlayerbotAI* botAI) { return new SetWrathOfAirTotemTrigger(botAI); }
static Trigger* set_windfury_totem(PlayerbotAI* botAI) { return new SetWindfuryTotemTrigger(botAI); }
static Trigger* set_nature_resistance_totem(PlayerbotAI* botAI) { return new SetNatureResistanceTotemTrigger(botAI); }
static Trigger* set_grounding_totem(PlayerbotAI* botAI) { return new SetGroundingTotemTrigger(botAI); }
};
class ShamanAiObjectContextInternal : public NamedObjectContext<Action>
@ -272,11 +264,12 @@ public:
creators["lightning shield"] = &ShamanAiObjectContextInternal::lightning_shield;
creators["wind shear"] = &ShamanAiObjectContextInternal::wind_shear;
creators["wind shear on enemy healer"] = &ShamanAiObjectContextInternal::wind_shear_on_enemy_healer;
creators["rockbiter weapon"] = &ShamanAiObjectContextInternal::rockbiter_weapon;
creators["flametongue weapon"] = &ShamanAiObjectContextInternal::flametongue_weapon;
creators["frostbrand weapon"] = &ShamanAiObjectContextInternal::frostbrand_weapon;
creators["windfury weapon"] = &ShamanAiObjectContextInternal::windfury_weapon;
creators["earthliving weapon"] = &ShamanAiObjectContextInternal::earthliving_weapon;
creators["rockbiter weapon main hand"] = &ShamanAiObjectContextInternal::rockbiter_weapon_main_hand;
creators["flametongue weapon main hand"] = &ShamanAiObjectContextInternal::flametongue_weapon_main_hand;
creators["flametongue weapon off hand"] = &ShamanAiObjectContextInternal::flametongue_weapon_off_hand;
// creators["frostbrand weapon off hand"] = &ShamanAiObjectContextInternal::frostbrand_weapon_off_hand;
creators["windfury weapon main hand"] = &ShamanAiObjectContextInternal::windfury_weapon_main_hand;
creators["earthliving weapon main hand"] = &ShamanAiObjectContextInternal::earthliving_weapon_main_hand;
creators["purge"] = &ShamanAiObjectContextInternal::purge;
creators["healing wave"] = &ShamanAiObjectContextInternal::healing_wave;
creators["lesser healing wave"] = &ShamanAiObjectContextInternal::lesser_healing_wave;
@ -308,10 +301,9 @@ public:
creators["heroism"] = &ShamanAiObjectContextInternal::heroism;
creators["bloodlust"] = &ShamanAiObjectContextInternal::bloodlust;
creators["elemental mastery"] = &ShamanAiObjectContextInternal::elemental_mastery;
creators["cure disease"] = &ShamanAiObjectContextInternal::cure_disease;
creators["cure disease on party"] = &ShamanAiObjectContextInternal::cure_disease_on_party;
creators["cure poison"] = &ShamanAiObjectContextInternal::cure_poison;
creators["cure poison on party"] = &ShamanAiObjectContextInternal::cure_poison_on_party;
creators["cure toxins"] = &ShamanAiObjectContextInternal::cure_toxins;
creators["cure toxins poison on party"] = &ShamanAiObjectContextInternal::cure_toxins_poison_on_party;
creators["cure toxins disease on party"] = &ShamanAiObjectContextInternal::cure_toxins_disease_on_party;
creators["lava burst"] = &ShamanAiObjectContextInternal::lava_burst;
creators["earth shield on main tank"] = &ShamanAiObjectContextInternal::earth_shield_on_main_tank;
creators["shamanistic rage"] = &ShamanAiObjectContextInternal::shamanistic_rage;
@ -368,10 +360,10 @@ private:
static Action* frost_shock(PlayerbotAI* botAI) { return new CastFrostShockAction(botAI); }
static Action* earth_shock(PlayerbotAI* botAI) { return new CastEarthShockAction(botAI); }
static Action* flame_shock(PlayerbotAI* botAI) { return new CastFlameShockAction(botAI); }
static Action* cleanse_spirit(PlayerbotAI* botAI) { return new CastCleanseSpiritAction(botAI); }
static Action* cleanse_spirit_poison_on_party(PlayerbotAI* botAI) { return new CastCleanseSpiritPoisonOnPartyAction(botAI); }
static Action* cleanse_spirit_disease_on_party(PlayerbotAI* botAI) { return new CastCleanseSpiritDiseaseOnPartyAction(botAI); }
static Action* cleanse_spirit_curse_on_party(PlayerbotAI* botAI) { return new CastCleanseSpiritCurseOnPartyAction(botAI); }
static Action* cleanse_spirit(PlayerbotAI* botAI) { return new CastCleanseSpiritAction(botAI); }
static Action* water_walking(PlayerbotAI* botAI) { return new CastWaterWalkingAction(botAI); }
static Action* water_breathing(PlayerbotAI* botAI) { return new CastWaterBreathingAction(botAI); }
static Action* water_walking_on_party(PlayerbotAI* botAI) { return new CastWaterWalkingOnPartyAction(botAI); }
@ -380,11 +372,12 @@ private:
static Action* lightning_shield(PlayerbotAI* botAI) { return new CastLightningShieldAction(botAI); }
static Action* fire_nova(PlayerbotAI* botAI) { return new CastFireNovaAction(botAI); }
static Action* wind_shear(PlayerbotAI* botAI) { return new CastWindShearAction(botAI); }
static Action* rockbiter_weapon(PlayerbotAI* botAI) { return new CastRockbiterWeaponAction(botAI); }
static Action* flametongue_weapon(PlayerbotAI* botAI) { return new CastFlametongueWeaponAction(botAI); }
static Action* frostbrand_weapon(PlayerbotAI* botAI) { return new CastFrostbrandWeaponAction(botAI); }
static Action* windfury_weapon(PlayerbotAI* botAI) { return new CastWindfuryWeaponAction(botAI); }
static Action* earthliving_weapon(PlayerbotAI* botAI) { return new CastEarthlivingWeaponAction(botAI); }
static Action* rockbiter_weapon_main_hand(PlayerbotAI* botAI) { return new CastRockbiterWeaponMainHandAction(botAI); }
static Action* flametongue_weapon_main_hand(PlayerbotAI* botAI) { return new CastFlametongueWeaponMainHandAction(botAI); }
static Action* flametongue_weapon_off_hand(PlayerbotAI* botAI) { return new CastFlametongueWeaponOffHandAction(botAI); }
// static Action* frostbrand_weapon_off_hand(PlayerbotAI* botAI) { return new CastFrostbrandWeaponOffHandAction(botAI); }
static Action* earthliving_weapon_main_hand(PlayerbotAI* botAI) { return new CastEarthlivingWeaponMainHandAction(botAI); }
static Action* windfury_weapon_main_hand(PlayerbotAI* botAI) { return new CastWindfuryWeaponMainHandAction(botAI); }
static Action* purge(PlayerbotAI* botAI) { return new CastPurgeAction(botAI); }
static Action* healing_wave(PlayerbotAI* botAI) { return new CastHealingWaveAction(botAI); }
static Action* lesser_healing_wave(PlayerbotAI* botAI) { return new CastLesserHealingWaveAction(botAI); }
@ -399,54 +392,53 @@ private:
static Action* lava_lash(PlayerbotAI* botAI) { return new CastLavaLashAction(botAI); }
static Action* ancestral_spirit(PlayerbotAI* botAI) { return new CastAncestralSpiritAction(botAI); }
static Action* wind_shear_on_enemy_healer(PlayerbotAI* botAI) { return new CastWindShearOnEnemyHealerAction(botAI); }
static Action* cure_poison(PlayerbotAI* botAI) { return new CastCurePoisonActionSham(botAI); }
static Action* cure_poison_on_party(PlayerbotAI* botAI) { return new CastCurePoisonOnPartyActionSham(botAI); }
static Action* cure_disease(PlayerbotAI* botAI) { return new CastCureDiseaseActionSham(botAI); }
static Action* cure_disease_on_party(PlayerbotAI* botAI) { return new CastCureDiseaseOnPartyActionSham(botAI); }
static Action* lava_burst(PlayerbotAI* ai) { return new CastLavaBurstAction(ai); }
static Action* earth_shield_on_main_tank(PlayerbotAI* ai) { return new CastEarthShieldOnMainTankAction(ai); }
static Action* shamanistic_rage(PlayerbotAI* ai) { return new CastShamanisticRageAction(ai); }
static Action* feral_spirit(PlayerbotAI* ai) { return new CastFeralSpiritAction(ai); }
static Action* spirit_walk(PlayerbotAI* ai) { return new CastSpiritWalkAction(ai); }
static Action* call_of_the_elements(PlayerbotAI* ai) { return new CastCallOfTheElementsAction(ai); }
static Action* totemic_recall(PlayerbotAI* ai) { return new CastTotemicRecallAction(ai); }
static Action* strength_of_earth_totem(PlayerbotAI* ai) { return new CastStrengthOfEarthTotemAction(ai); }
static Action* stoneskin_totem(PlayerbotAI* ai) { return new CastStoneskinTotemAction(ai); }
static Action* tremor_totem(PlayerbotAI* ai) { return new CastTremorTotemAction(ai); }
static Action* earthbind_totem(PlayerbotAI* ai) { return new CastEarthbindTotemAction(ai); }
static Action* stoneclaw_totem(PlayerbotAI* ai) { return new CastStoneclawTotemAction(ai); }
static Action* searing_totem(PlayerbotAI* ai) { return new CastSearingTotemAction(ai); }
static Action* magma_totem(PlayerbotAI* ai) { return new CastMagmaTotemAction(ai); }
static Action* flametongue_totem(PlayerbotAI* ai) { return new CastFlametongueTotemAction(ai); }
static Action* totem_of_wrath(PlayerbotAI* ai) { return new CastTotemOfWrathAction(ai); }
static Action* frost_resistance_totem(PlayerbotAI* ai) { return new CastFrostResistanceTotemAction(ai); }
static Action* fire_elemental_totem(PlayerbotAI* ai) { return new CastFireElementalTotemAction(ai); }
static Action* fire_elemental_totem_melee(PlayerbotAI* ai) { return new CastFireElementalTotemMeleeAction(ai); }
static Action* healing_stream_totem(PlayerbotAI* ai) { return new CastHealingStreamTotemAction(ai); }
static Action* mana_spring_totem(PlayerbotAI* ai) { return new CastManaSpringTotemAction(ai); }
static Action* cleansing_totem(PlayerbotAI* ai) { return new CastCleansingTotemAction(ai); }
static Action* mana_tide_totem(PlayerbotAI* ai) { return new CastManaTideTotemAction(ai); }
static Action* fire_resistance_totem(PlayerbotAI* ai) { return new CastFireResistanceTotemAction(ai); }
static Action* wrath_of_air_totem(PlayerbotAI* ai) { return new CastWrathOfAirTotemAction(ai); }
static Action* windfury_totem(PlayerbotAI* ai) { return new CastWindfuryTotemAction(ai); }
static Action* nature_resistance_totem(PlayerbotAI* ai) { return new CastNatureResistanceTotemAction(ai); }
static Action* set_strength_of_earth_totem(PlayerbotAI* ai) { return new SetStrengthOfEarthTotemAction(ai); }
static Action* set_stoneskin_totem(PlayerbotAI* ai) { return new SetStoneskinTotemAction(ai); }
static Action* set_tremor_totem(PlayerbotAI* ai) { return new SetTremorTotemAction(ai); }
static Action* set_earthbind_totem(PlayerbotAI* ai) { return new SetEarthbindTotemAction(ai); }
static Action* set_searing_totem(PlayerbotAI* ai) { return new SetSearingTotemAction(ai); }
static Action* set_magma_totem(PlayerbotAI* ai) { return new SetMagmaTotemAction(ai); }
static Action* set_flametongue_totem(PlayerbotAI* ai) { return new SetFlametongueTotemAction(ai); }
static Action* set_totem_of_wrath(PlayerbotAI* ai) { return new SetTotemOfWrathAction(ai); }
static Action* set_frost_resistance_totem(PlayerbotAI* ai) { return new SetFrostResistanceTotemAction(ai); }
static Action* set_healing_stream_totem(PlayerbotAI* ai) { return new SetHealingStreamTotemAction(ai); }
static Action* set_mana_spring_totem(PlayerbotAI* ai) { return new SetManaSpringTotemAction(ai); }
static Action* set_cleansing_totem(PlayerbotAI* ai) { return new SetCleansingTotemAction(ai); }
static Action* set_fire_resistance_totem(PlayerbotAI* ai) { return new SetFireResistanceTotemAction(ai); }
static Action* set_wrath_of_air_totem(PlayerbotAI* ai) { return new SetWrathOfAirTotemAction(ai); }
static Action* set_windfury_totem(PlayerbotAI* ai) { return new SetWindfuryTotemAction(ai); }
static Action* set_nature_resistance_totem(PlayerbotAI* ai) { return new SetNatureResistanceTotemAction(ai); }
static Action* set_grounding_totem(PlayerbotAI* ai) { return new SetGroundingTotemAction(ai); }
static Action* cure_toxins(PlayerbotAI* botAI) { return new CastCureToxinsActionSham(botAI); }
static Action* cure_toxins_poison_on_party(PlayerbotAI* botAI) { return new CastCureToxinsPoisonOnPartyActionSham(botAI); }
static Action* cure_toxins_disease_on_party(PlayerbotAI* botAI) { return new CastCureToxinsDiseaseOnPartyActionSham(botAI); }
static Action* lava_burst(PlayerbotAI* botAI) { return new CastLavaBurstAction(botAI); }
static Action* earth_shield_on_main_tank(PlayerbotAI* botAI) { return new CastEarthShieldOnMainTankAction(botAI); }
static Action* shamanistic_rage(PlayerbotAI* botAI) { return new CastShamanisticRageAction(botAI); }
static Action* feral_spirit(PlayerbotAI* botAI) { return new CastFeralSpiritAction(botAI); }
static Action* spirit_walk(PlayerbotAI* botAI) { return new CastSpiritWalkAction(botAI); }
static Action* call_of_the_elements(PlayerbotAI* botAI) { return new CastCallOfTheElementsAction(botAI); }
static Action* totemic_recall(PlayerbotAI* botAI) { return new CastTotemicRecallAction(botAI); }
static Action* strength_of_earth_totem(PlayerbotAI* botAI) { return new CastStrengthOfEarthTotemAction(botAI); }
static Action* stoneskin_totem(PlayerbotAI* botAI) { return new CastStoneskinTotemAction(botAI); }
static Action* tremor_totem(PlayerbotAI* botAI) { return new CastTremorTotemAction(botAI); }
static Action* earthbind_totem(PlayerbotAI* botAI) { return new CastEarthbindTotemAction(botAI); }
static Action* stoneclaw_totem(PlayerbotAI* botAI) { return new CastStoneclawTotemAction(botAI); }
static Action* searing_totem(PlayerbotAI* botAI) { return new CastSearingTotemAction(botAI); }
static Action* magma_totem(PlayerbotAI* botAI) { return new CastMagmaTotemAction(botAI); }
static Action* flametongue_totem(PlayerbotAI* botAI) { return new CastFlametongueTotemAction(botAI); }
static Action* totem_of_wrath(PlayerbotAI* botAI) { return new CastTotemOfWrathAction(botAI); }
static Action* frost_resistance_totem(PlayerbotAI* botAI) { return new CastFrostResistanceTotemAction(botAI); }
static Action* fire_elemental_totem(PlayerbotAI* botAI) { return new CastFireElementalTotemAction(botAI); }
static Action* fire_elemental_totem_melee(PlayerbotAI* botAI) { return new CastFireElementalTotemMeleeAction(botAI); }
static Action* healing_stream_totem(PlayerbotAI* botAI) { return new CastHealingStreamTotemAction(botAI); }
static Action* mana_spring_totem(PlayerbotAI* botAI) { return new CastManaSpringTotemAction(botAI); }
static Action* cleansing_totem(PlayerbotAI* botAI) { return new CastCleansingTotemAction(botAI); }
static Action* mana_tide_totem(PlayerbotAI* botAI) { return new CastManaTideTotemAction(botAI); }
static Action* fire_resistance_totem(PlayerbotAI* botAI) { return new CastFireResistanceTotemAction(botAI); }
static Action* wrath_of_air_totem(PlayerbotAI* botAI) { return new CastWrathOfAirTotemAction(botAI); }
static Action* windfury_totem(PlayerbotAI* botAI) { return new CastWindfuryTotemAction(botAI); }
static Action* nature_resistance_totem(PlayerbotAI* botAI) { return new CastNatureResistanceTotemAction(botAI); }
static Action* set_strength_of_earth_totem(PlayerbotAI* botAI) { return new SetStrengthOfEarthTotemAction(botAI); }
static Action* set_stoneskin_totem(PlayerbotAI* botAI) { return new SetStoneskinTotemAction(botAI); }
static Action* set_tremor_totem(PlayerbotAI* botAI) { return new SetTremorTotemAction(botAI); }
static Action* set_earthbind_totem(PlayerbotAI* botAI) { return new SetEarthbindTotemAction(botAI); }
static Action* set_searing_totem(PlayerbotAI* botAI) { return new SetSearingTotemAction(botAI); }
static Action* set_magma_totem(PlayerbotAI* botAI) { return new SetMagmaTotemAction(botAI); }
static Action* set_flametongue_totem(PlayerbotAI* botAI) { return new SetFlametongueTotemAction(botAI); }
static Action* set_totem_of_wrath(PlayerbotAI* botAI) { return new SetTotemOfWrathAction(botAI); }
static Action* set_frost_resistance_totem(PlayerbotAI* botAI) { return new SetFrostResistanceTotemAction(botAI); }
static Action* set_healing_stream_totem(PlayerbotAI* botAI) { return new SetHealingStreamTotemAction(botAI); }
static Action* set_mana_spring_totem(PlayerbotAI* botAI) { return new SetManaSpringTotemAction(botAI); }
static Action* set_cleansing_totem(PlayerbotAI* botAI) { return new SetCleansingTotemAction(botAI); }
static Action* set_fire_resistance_totem(PlayerbotAI* botAI) { return new SetFireResistanceTotemAction(botAI); }
static Action* set_wrath_of_air_totem(PlayerbotAI* botAI) { return new SetWrathOfAirTotemAction(botAI); }
static Action* set_windfury_totem(PlayerbotAI* botAI) { return new SetWindfuryTotemAction(botAI); }
static Action* set_nature_resistance_totem(PlayerbotAI* botAI) { return new SetNatureResistanceTotemAction(botAI); }
static Action* set_grounding_totem(PlayerbotAI* botAI) { return new SetGroundingTotemAction(botAI); }
};
SharedNamedObjectContextList<Strategy> ShamanAiObjectContext::sharedStrategyContexts;

View File

@ -4,42 +4,11 @@
*/
#include "ElementalShamanStrategy.h"
#include "Playerbots.h"
// ===== Action Node Factory =====
class ElementalShamanStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
ElementalShamanStrategyActionNodeFactory()
{
creators["flame shock"] = &flame_shock;
creators["earth shock"] = &earth_shock;
creators["lava burst"] = &lava_burst;
creators["lightning bolt"] = &lightning_bolt;
creators["call of the elements"] = &call_of_the_elements;
creators["elemental mastery"] = &elemental_mastery;
creators["stoneclaw totem"] = &stoneclaw_totem;
creators["water shield"] = &water_shield;
creators["thunderstorm"] = &thunderstorm;
}
private:
static ActionNode* flame_shock(PlayerbotAI*) { return new ActionNode("flame shock", {}, {}, {}); }
static ActionNode* earth_shock(PlayerbotAI*) { return new ActionNode("earth shock", {}, {}, {}); }
static ActionNode* lava_burst(PlayerbotAI*) { return new ActionNode("lava burst", {}, {}, {}); }
static ActionNode* lightning_bolt(PlayerbotAI*) { return new ActionNode("lightning bolt", {}, {}, {}); }
static ActionNode* call_of_the_elements(PlayerbotAI*) { return new ActionNode("call of the elements", {}, {}, {}); }
static ActionNode* elemental_mastery(PlayerbotAI*) { return new ActionNode("elemental mastery", {}, {}, {}); }
static ActionNode* stoneclaw_totem(PlayerbotAI*) { return new ActionNode("stoneclaw totem", {}, {}, {}); }
static ActionNode* water_shield(PlayerbotAI*) { return new ActionNode("water shield", {}, {}, {}); }
static ActionNode* thunderstorm(PlayerbotAI*) { return new ActionNode("thunderstorm", {}, {}, {}); }
};
// ===== Single Target Strategy =====
ElementalShamanStrategy::ElementalShamanStrategy(PlayerbotAI* botAI) : GenericShamanStrategy(botAI)
{
actionNodeFactories.Add(new ElementalShamanStrategyActionNodeFactory());
// No custom ActionNodeFactory needed
}
// ===== Default Actions =====

View File

@ -4,7 +4,6 @@
*/
#include "EnhancementShamanStrategy.h"
#include "Playerbots.h"
// ===== Action Node Factory =====
@ -13,19 +12,10 @@ class EnhancementShamanStrategyActionNodeFactory : public NamedObjectFactory<Act
public:
EnhancementShamanStrategyActionNodeFactory()
{
creators["stormstrike"] = &stormstrike;
creators["lava lash"] = &lava_lash;
creators["feral spirit"] = &feral_spirit;
creators["lightning bolt"] = &lightning_bolt;
creators["earth shock"] = &earth_shock;
creators["flame shock"] = &flame_shock;
creators["shamanistic rage"] = &shamanistic_rage;
creators["call of the elements"] = &call_of_the_elements;
creators["lightning shield"] = &lightning_shield;
}
private:
static ActionNode* stormstrike(PlayerbotAI*) { return new ActionNode("stormstrike", {}, {}, {}); }
static ActionNode* lava_lash([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode(
@ -35,13 +25,6 @@ private:
/*C*/ {}
);
}
static ActionNode* feral_spirit(PlayerbotAI*) { return new ActionNode("feral spirit", {}, {}, {}); }
static ActionNode* lightning_bolt(PlayerbotAI*) { return new ActionNode("lightning bolt", {}, {}, {}); }
static ActionNode* earth_shock(PlayerbotAI*) { return new ActionNode("earth shock", {}, {}, {}); }
static ActionNode* flame_shock(PlayerbotAI*) { return new ActionNode("flame shock", {}, {}, {}); }
static ActionNode* shamanistic_rage(PlayerbotAI*) { return new ActionNode("shamanistic rage", {}, {}, {}); }
static ActionNode* call_of_the_elements(PlayerbotAI*) { return new ActionNode("call of the elements", {}, {}, {}); }
static ActionNode* lightning_shield(PlayerbotAI*) { return new ActionNode("lightning shield", {}, {}, {}); }
};
// ===== Single Target Strategy =====

View File

@ -16,17 +16,13 @@ public:
creators["totem of wrath"] = &totem_of_wrath;
creators["flametongue totem"] = &flametongue_totem;
creators["magma totem"] = &magma_totem;
creators["searing totem"] = &searing_totem;
creators["strength of earth totem"] = &strength_of_earth_totem;
creators["stoneskin totem"] = &stoneskin_totem;
creators["cleansing totem"] = &cleansing_totem;
creators["mana spring totem"] = &mana_spring_totem;
creators["healing stream totem"] = &healing_stream_totem;
creators["wrath of air totem"] = &wrath_of_air_totem;
creators["windfury totem"] = &windfury_totem;
creators["grounding totem"] = &grounding_totem;
creators["wind shear"] = &wind_shear;
creators["purge"] = &purge;
creators["cleanse spirit"] = &cleanse_spirit;
creators["cleanse spirit poison on party"] = &cleanse_spirit_poison_on_party;
creators["cleanse spirit disease on party"] = &cleanse_spirit_disease_on_party;
}
private:
@ -58,7 +54,6 @@ private:
/*A*/ { NextAction("searing totem") },
/*C*/ {});
}
static ActionNode* searing_totem(PlayerbotAI*) { return new ActionNode("searing totem", {}, {}, {}); }
static ActionNode* strength_of_earth_totem([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("strength of earth totem",
@ -66,7 +61,6 @@ private:
/*A*/ { NextAction("stoneskin totem") },
/*C*/ {});
}
static ActionNode* stoneskin_totem(PlayerbotAI*) { return new ActionNode("stoneskin totem", {}, {}, {}); }
static ActionNode* cleansing_totem([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cleansing totem",
@ -74,8 +68,6 @@ private:
/*A*/ { NextAction("mana spring totem") },
/*C*/ {});
}
static ActionNode* mana_spring_totem(PlayerbotAI*) { return new ActionNode("mana spring totem", {}, {}, {}); }
static ActionNode* healing_stream_totem(PlayerbotAI*) { return new ActionNode("healing stream totem", {}, {}, {}); }
static ActionNode* wrath_of_air_totem([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("wrath of air totem",
@ -90,9 +82,27 @@ private:
/*A*/ { NextAction("grounding totem") },
/*C*/ {});
}
static ActionNode* grounding_totem(PlayerbotAI*) { return new ActionNode("grounding totem", {}, {}, {}); }
static ActionNode* wind_shear(PlayerbotAI*) { return new ActionNode("wind shear", {}, {}, {}); }
static ActionNode* purge(PlayerbotAI*) { return new ActionNode("purge", {}, {}, {}); }
static ActionNode* cleanse_spirit([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cleanse spirit",
/*P*/ {},
/*A*/ { NextAction("cure toxins") },
/*C*/ {});
}
static ActionNode* cleanse_spirit_poison_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cleanse spirit poison on party",
/*P*/ {},
/*A*/ { NextAction("cure toxins poison on party") },
/*C*/ {});
}
static ActionNode* cleanse_spirit_disease_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cleanse spirit disease on party",
/*P*/ {},
/*A*/ { NextAction("cure toxins disease on party") },
/*C*/ {});
}
};
GenericShamanStrategy::GenericShamanStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI)
@ -107,18 +117,13 @@ void GenericShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("wind shear", { NextAction("wind shear", 23.0f), }));
triggers.push_back(new TriggerNode("wind shear on enemy healer", { NextAction("wind shear on enemy healer", 23.0f), }));
triggers.push_back(new TriggerNode("purge", { NextAction("purge", ACTION_DISPEL), }));
triggers.push_back(new TriggerNode("medium mana", { NextAction("mana potion", ACTION_DISPEL), }));
triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 65.0f), }));
}
void ShamanCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("cure poison", { NextAction("cure poison", 21.0f), }));
triggers.push_back(new TriggerNode("party member cure poison", { NextAction("cure poison on party", 21.0f), }));
triggers.push_back(new TriggerNode("cleanse spirit poison", { NextAction("cleanse spirit", 24.0f), }));
triggers.push_back(new TriggerNode("party member cleanse spirit poison", { NextAction("cleanse spirit poison on party", 23.0f), }));
triggers.push_back(new TriggerNode("cure disease", { NextAction("cure disease", 31.0f), }));
triggers.push_back(new TriggerNode("party member cure disease", { NextAction("cure disease on party", 30.0f), }));
triggers.push_back(new TriggerNode("cleanse spirit disease", { NextAction("cleanse spirit", 24.0f), }));
triggers.push_back(new TriggerNode("party member cleanse spirit disease", { NextAction("cleanse spirit disease on party", 23.0f), }));
triggers.push_back(new TriggerNode("cleanse spirit curse", { NextAction("cleanse spirit", 24.0f), }));
@ -133,11 +138,11 @@ void ShamanBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
Player* bot = botAI->GetBot();
int tab = AiFactory::GetPlayerSpecTab(bot);
if (tab == 0) // Elemental
if (tab == SHAMAN_TAB_ELEMENTAL)
{
triggers.push_back(new TriggerNode("fire elemental totem", { NextAction("fire elemental totem", 23.0f), }));
}
else if (tab == 1) // Enhancement
else if (tab == SHAMAN_TAB_ENHANCEMENT)
{
triggers.push_back(new TriggerNode("fire elemental totem", { NextAction("fire elemental totem melee", 24.0f), }));
}
@ -149,23 +154,19 @@ void ShamanAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
Player* bot = botAI->GetBot();
int tab = AiFactory::GetPlayerSpecTab(bot);
if (tab == 0) // Elemental
if (tab == SHAMAN_TAB_ELEMENTAL)
{
triggers.push_back(new TriggerNode("medium aoe",{ NextAction("fire nova", 23.0f), }));
triggers.push_back(new TriggerNode("chain lightning no cd", { NextAction("chain lightning", 5.6f), }));
}
else if (tab == 1) // Enhancement
else if (tab == SHAMAN_TAB_ENHANCEMENT)
{
triggers.push_back(new TriggerNode("medium aoe",{
NextAction("magma totem", 24.0f),
NextAction("fire nova", 23.0f), }));
triggers.push_back(new TriggerNode("medium aoe",{ NextAction("magma totem", 24.0f),
NextAction("fire nova", 23.0f), }));
triggers.push_back(new TriggerNode("maelstrom weapon 5 and medium aoe", { NextAction("chain lightning", 22.0f), }));
triggers.push_back(new TriggerNode("maelstrom weapon 4 and medium aoe", { NextAction("chain lightning", 21.0f), }));
triggers.push_back(new TriggerNode("enemy within melee", { NextAction("fire nova", 5.1f), }));
}
else if (tab == 2) // Restoration
{
// Handled by "Healer DPS" Strategy
}
// Resto AoE handled by "Healer DPS" Strategy
}

View File

@ -4,64 +4,11 @@
*/
#include "RestoShamanStrategy.h"
#include "Playerbots.h"
// ===== Action Node Factory =====
class RestoShamanStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
RestoShamanStrategyActionNodeFactory()
{
creators["mana tide totem"] = &mana_tide_totem;
creators["call of the elements"] = &call_of_the_elements;
creators["stoneclaw totem"] = &stoneclaw_totem;
creators["riptide on party"] = &riptide_on_party;
creators["chain heal on party"] = &chain_heal_on_party;
creators["healing wave on party"] = &healing_wave_on_party;
creators["lesser healing wave on party"] = &lesser_healing_wave_on_party;
creators["earth shield on main tank"] = &earth_shield_on_main_tank;
creators["cleanse spirit poison on party"] = &cleanse_spirit_poison_on_party;
creators["cleanse spirit disease on party"] = &cleanse_spirit_disease_on_party;
creators["cleanse spirit curse on party"] = &cleanse_spirit_curse_on_party;
creators["cleansing totem"] = &cleansing_totem;
creators["water shield"] = &water_shield;
creators["flame shock"] = &flame_shock;
creators["lava burst"] = &lava_burst;
creators["lightning bolt"] = &lightning_bolt;
creators["chain lightning"] = &chain_lightning;
}
private:
static ActionNode* mana_tide_totem([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("mana tide totem",
/*P*/ {},
/*A*/ { NextAction("mana potion") },
/*C*/ {});
}
static ActionNode* call_of_the_elements(PlayerbotAI*) { return new ActionNode("call of the elements", {}, {}, {}); }
static ActionNode* stoneclaw_totem(PlayerbotAI*) { return new ActionNode("stoneclaw totem", {}, {}, {}); }
static ActionNode* riptide_on_party(PlayerbotAI*) { return new ActionNode("riptide on party", {}, {}, {}); }
static ActionNode* chain_heal_on_party(PlayerbotAI*) { return new ActionNode("chain heal on party", {}, {}, {}); }
static ActionNode* healing_wave_on_party(PlayerbotAI*) { return new ActionNode("healing wave on party", {}, {}, {}); }
static ActionNode* lesser_healing_wave_on_party(PlayerbotAI*) { return new ActionNode("lesser healing wave on party", {}, {}, {}); }
static ActionNode* earth_shield_on_main_tank(PlayerbotAI*) { return new ActionNode("earth shield on main tank", {}, {}, {}); }
static ActionNode* cleanse_spirit_poison_on_party(PlayerbotAI*) { return new ActionNode("cleanse spirit poison on party", {}, {}, {}); }
static ActionNode* cleanse_spirit_disease_on_party(PlayerbotAI*) { return new ActionNode("cleanse spirit disease on party", {}, {}, {}); }
static ActionNode* cleanse_spirit_curse_on_party(PlayerbotAI*) { return new ActionNode("cleanse spirit curse on party", {}, {}, {}); }
static ActionNode* cleansing_totem(PlayerbotAI*) { return new ActionNode("cleansing totem", {}, {}, {}); }
static ActionNode* water_shield(PlayerbotAI*) { return new ActionNode("water shield", {}, {}, {}); }
static ActionNode* flame_shock(PlayerbotAI*) { return new ActionNode("flame shock", {}, {}, {}); }
static ActionNode* lava_burst(PlayerbotAI*) { return new ActionNode("lava burst", {}, {}, {}); }
static ActionNode* lightning_bolt(PlayerbotAI*) { return new ActionNode("lightning bolt", {}, {}, {}); }
static ActionNode* chain_lightning(PlayerbotAI*) { return new ActionNode("chain lightning", {}, {}, {}); }
};
// ===== Single Target Strategy =====
RestoShamanStrategy::RestoShamanStrategy(PlayerbotAI* botAI) : GenericShamanStrategy(botAI)
{
actionNodeFactories.Add(new RestoShamanStrategyActionNodeFactory());
// No custom ActionNodeFactory needed
}
// ===== Trigger Initialization ===
@ -75,28 +22,23 @@ void RestoShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("medium mana", { NextAction("mana tide totem", ACTION_HIGH + 5) }));
// Healing Triggers
triggers.push_back(new TriggerNode("group heal setting", {
NextAction("riptide on party", 27.0f),
NextAction("chain heal on party", 26.0f) }));
triggers.push_back(new TriggerNode("group heal setting", { NextAction("riptide on party", 27.0f),
NextAction("chain heal on party", 26.0f) }));
triggers.push_back(new TriggerNode("party member critical health", {
NextAction("riptide on party", 25.0f),
NextAction("healing wave on party", 24.0f),
NextAction("lesser healing wave on party", 23.0f) }));
triggers.push_back(new TriggerNode("party member critical health", { NextAction("riptide on party", 25.0f),
NextAction("healing wave on party", 24.0f),
NextAction("lesser healing wave on party", 23.0f) }));
triggers.push_back(new TriggerNode("party member low health", {
NextAction("riptide on party", 19.0f),
NextAction("healing wave on party", 18.0f),
NextAction("lesser healing wave on party", 17.0f) }));
triggers.push_back(new TriggerNode("party member low health", { NextAction("riptide on party", 19.0f),
NextAction("healing wave on party", 18.0f),
NextAction("lesser healing wave on party", 17.0f) }));
triggers.push_back(new TriggerNode("party member medium health", {
NextAction("riptide on party", 16.0f),
NextAction("healing wave on party", 15.0f),
NextAction("lesser healing wave on party", 14.0f) }));
triggers.push_back(new TriggerNode("party member medium health", { NextAction("riptide on party", 16.0f),
NextAction("healing wave on party", 15.0f),
NextAction("lesser healing wave on party", 14.0f) }));
triggers.push_back(new TriggerNode("party member almost full health", {
NextAction("riptide on party", 12.0f),
NextAction("lesser healing wave on party", 11.0f) }));
triggers.push_back(new TriggerNode("party member almost full health", { NextAction("riptide on party", 12.0f),
NextAction("lesser healing wave on party", 11.0f) }));
triggers.push_back(new TriggerNode("earth shield on main tank", { NextAction("earth shield on main tank", ACTION_HIGH + 7) }));
@ -113,12 +55,9 @@ void RestoShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
void ShamanHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("healer should attack",
{ NextAction("flame shock", ACTION_DEFAULT + 0.2f),
NextAction("lava burst", ACTION_DEFAULT + 0.1f),
NextAction("lightning bolt", ACTION_DEFAULT) }));
triggers.push_back(new TriggerNode("healer should attack", { NextAction("flame shock", ACTION_DEFAULT + 0.2f),
NextAction("lava burst", ACTION_DEFAULT + 0.1f),
NextAction("lightning bolt", ACTION_DEFAULT) }));
triggers.push_back(
new TriggerNode("medium aoe and healer should attack",
{ NextAction("chain lightning", ACTION_DEFAULT + 0.3f) }));
triggers.push_back( new TriggerNode("medium aoe and healer should attack", { NextAction("chain lightning", ACTION_DEFAULT + 0.3f) }));
}

View File

@ -13,45 +13,65 @@ class ShamanNonCombatStrategyActionNodeFactory : public NamedObjectFactory<Actio
public:
ShamanNonCombatStrategyActionNodeFactory()
{
creators["flametongue weapon"] = &flametongue_weapon;
creators["frostbrand weapon"] = &frostbrand_weapon;
creators["windfury weapon"] = &windfury_weapon;
creators["earthliving weapon"] = &earthliving_weapon;
creators["wind shear"] = &wind_shear;
creators["purge"] = &purge;
creators["flametongue weapon main hand"] = &flametongue_weapon_main_hand;
// creators["frostbrand weapon off hand"] = &frostbrand_weapon_off_hand;
creators["windfury weapon main hand"] = &windfury_weapon_main_hand;
creators["earthliving weapon main hand"] = &earthliving_weapon_main_hand;
creators["cleanse spirit"] = &cleanse_spirit;
creators["cleanse spirit poison on party"] = &cleanse_spirit_poison_on_party;
creators["cleanse spirit disease on party"] = &cleanse_spirit_disease_on_party;
}
private:
static ActionNode* flametongue_weapon([[maybe_unused]] PlayerbotAI* botAI)
static ActionNode* flametongue_weapon_main_hand([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("flametongue weapon",
return new ActionNode("flametongue weapon main hand",
/*P*/ {},
/*A*/ { NextAction("rockbiter weapon") },
/*A*/ { NextAction("rockbiter weapon main hand") },
/*C*/ {});
}
static ActionNode* frostbrand_weapon([[maybe_unused]] PlayerbotAI* botAI)
// static ActionNode* frostbrand_weapon_off_hand([[maybe_unused]] PlayerbotAI* botAI)
// {
// return new ActionNode("frostbrand weapon off hand",
// /*P*/ {},
// /*A*/ { NextAction("flametongue weapon off hand") },
// /*C*/ {});
// }
static ActionNode* earthliving_weapon_main_hand([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("frostbrand weapon",
return new ActionNode("earthliving weapon main hand",
/*P*/ {},
/*A*/ { NextAction("flametongue weapon") },
/*A*/ { NextAction("flametongue weapon main hand") },
/*C*/ {});
}
static ActionNode* windfury_weapon([[maybe_unused]] PlayerbotAI* botAI)
static ActionNode* windfury_weapon_main_hand([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("windfury weapon",
return new ActionNode("windfury weapon main hand",
/*P*/ {},
/*A*/ { NextAction("flametongue weapon") },
/*A*/ { NextAction("flametongue weapon main hand") },
/*C*/ {});
}
static ActionNode* earthliving_weapon([[maybe_unused]] PlayerbotAI* botAI)
static ActionNode* cleanse_spirit([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("earthliving weapon",
return new ActionNode("cleanse spirit",
/*P*/ {},
/*A*/ { NextAction("flametongue weapon") },
/*A*/ { NextAction("cure toxins") },
/*C*/ {});
}
static ActionNode* cleanse_spirit_poison_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cleanse spirit poison on party",
/*P*/ {},
/*A*/ { NextAction("cure toxins poison on party") },
/*C*/ {});
}
static ActionNode* cleanse_spirit_disease_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cleanse spirit disease on party",
/*P*/ {},
/*A*/ { NextAction("cure toxins disease on party") },
/*C*/ {});
}
static ActionNode* wind_shear(PlayerbotAI*) { return new ActionNode("wind shear", {}, {}, {}); }
static ActionNode* purge(PlayerbotAI*) { return new ActionNode("purge", {}, {}, {}); }
};
ShamanNonCombatStrategy::ShamanNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
@ -64,48 +84,46 @@ void ShamanNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NonCombatStrategy::InitTriggers(triggers);
// Totemic Recall
triggers.push_back(new TriggerNode("totemic recall", { NextAction("totemic recall", 60.0f), }));
triggers.push_back(new TriggerNode("totemic recall", { NextAction("totemic recall", 60.0f) }));
// Healing/Resurrect Triggers
triggers.push_back(new TriggerNode("party member dead", { NextAction("ancestral spirit", ACTION_CRITICAL_HEAL + 10), }));
triggers.push_back(new TriggerNode("party member critical health", {
NextAction("riptide on party", 31.0f),
NextAction("healing wave on party", 30.0f) }));
triggers.push_back(new TriggerNode("party member low health",{
NextAction("riptide on party", 29.0f),
NextAction("healing wave on party", 28.0f) }));
triggers.push_back(new TriggerNode("party member medium health",{
NextAction("riptide on party", 27.0f),
NextAction("healing wave on party", 26.0f) }));
triggers.push_back(new TriggerNode("party member almost full health",{
NextAction("riptide on party", 25.0f),
NextAction("lesser healing wave on party", 24.0f) }));
triggers.push_back(new TriggerNode("group heal setting",{ NextAction("chain heal on party", 27.0f) }));
triggers.push_back(new TriggerNode("party member dead", { NextAction("ancestral spirit", ACTION_CRITICAL_HEAL + 10) }));
triggers.push_back(new TriggerNode("party member critical health", { NextAction("riptide on party", 31.0f),
NextAction("healing wave on party", 30.0f) }));
triggers.push_back(new TriggerNode("party member low health", { NextAction("riptide on party", 29.0f),
NextAction("healing wave on party", 28.0f) }));
triggers.push_back(new TriggerNode("party member medium health", { NextAction("riptide on party", 27.0f),
NextAction("healing wave on party", 26.0f) }));
triggers.push_back(new TriggerNode("party member almost full health", { NextAction("riptide on party", 25.0f),
NextAction("lesser healing wave on party", 24.0f) }));
triggers.push_back(new TriggerNode("group heal setting", { NextAction("chain heal on party", 27.0f) }));
// Cure Triggers
triggers.push_back(new TriggerNode("cure poison", { NextAction("cure poison", 21.0f), }));
triggers.push_back(new TriggerNode("party member cure poison", { NextAction("cure poison on party", 21.0f), }));
triggers.push_back(new TriggerNode("cure disease", { NextAction("cure disease", 31.0f), }));
triggers.push_back(new TriggerNode("party member cure disease", { NextAction("cure disease on party", 30.0f), }));
triggers.push_back(new TriggerNode("cleanse spirit poison", { NextAction("cleanse spirit", 24.0f) }));
triggers.push_back(new TriggerNode("party member cleanse spirit poison", { NextAction("cleanse spirit poison on party", 23.0f) }));
triggers.push_back(new TriggerNode("cleanse spirit disease", { NextAction("cleanse spirit", 24.0f) }));
triggers.push_back(new TriggerNode("party member cleanse spirit disease", { NextAction("cleanse spirit disease on party", 23.0f) }));
triggers.push_back(new TriggerNode("cleanse spirit curse", { NextAction("cleanse spirit", 24.0f) }));
triggers.push_back(new TriggerNode("party member cleanse spirit curse", { NextAction("cleanse spirit curse on party", 23.0f) }));
// Out of Combat Buff Triggers
Player* bot = botAI->GetBot();
int tab = AiFactory::GetPlayerSpecTab(bot);
if (tab == 0) // Elemental
if (tab == SHAMAN_TAB_ELEMENTAL)
{
triggers.push_back(new TriggerNode("main hand weapon no imbue", { NextAction("flametongue weapon", 22.0f), }));
triggers.push_back(new TriggerNode("main hand weapon no imbue", { NextAction("flametongue weapon main hand", 22.0f), }));
triggers.push_back(new TriggerNode("water shield", { NextAction("water shield", 21.0f), }));
}
else if (tab == 1) // Enhancement
else if (tab == SHAMAN_TAB_ENHANCEMENT)
{
triggers.push_back(new TriggerNode("main hand weapon no imbue", { NextAction("windfury weapon", 22.0f), }));
triggers.push_back(new TriggerNode("off hand weapon no imbue", { NextAction("flametongue weapon", 21.0f), }));
triggers.push_back(new TriggerNode("main hand weapon no imbue", { NextAction("windfury weapon main hand", 22.0f), }));
triggers.push_back(new TriggerNode("off hand weapon no imbue", { NextAction("flametongue weapon off hand", 21.0f), }));
triggers.push_back(new TriggerNode("lightning shield", { NextAction("lightning shield", 20.0f), }));
}
else if (tab == 2) // Restoration
else if (tab == SHAMAN_TAB_RESTORATION)
{
triggers.push_back(new TriggerNode("main hand weapon no imbue",{ NextAction("earthliving weapon", 22.0f), }));
triggers.push_back(new TriggerNode("main hand weapon no imbue", { NextAction("earthliving weapon main hand", 22.0f), }));
triggers.push_back(new TriggerNode("water shield", { NextAction("water shield", 20.0f), }));
}

View File

@ -75,13 +75,9 @@ void TotemOfWrathStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// If the bot hasn't learned Totem of Wrath yet, set Flametongue Totem instead.
Player* bot = botAI->GetBot();
if (bot->HasSpell(30706))
{
triggers.push_back(new TriggerNode("set totem of wrath", { NextAction("set totem of wrath", 60.0f) }));
}
else if (bot->HasSpell(8227))
{
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) }));
}
@ -117,13 +113,9 @@ void CleansingTotemStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// If the bot hasn't learned Cleansing Totem yet, set Mana Spring Totem instead.
Player* bot = botAI->GetBot();
if (bot->HasSpell(8170))
{
triggers.push_back(new TriggerNode("set cleansing totem", { NextAction("set cleansing totem", 60.0f) }));
}
else if (bot->HasSpell(5675))
{
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) }));
}
@ -143,15 +135,10 @@ void WrathOfAirTotemStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// If the bot hasn't learned Wrath of Air Totem yet, set Grounding Totem instead.
Player* bot = botAI->GetBot();
if (bot->HasSpell(3738))
{
triggers.push_back(new TriggerNode("set wrath of air totem", { NextAction("set wrath of air totem", 60.0f) }));
}
else if (bot->HasSpell(8177))
{
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) }));
triggers.push_back( new TriggerNode("no air totem", { NextAction("wrath of air totem", 55.0f) }));
}
WindfuryTotemStrategy::WindfuryTotemStrategy(PlayerbotAI* botAI) : GenericShamanStrategy(botAI) {}
@ -161,13 +148,9 @@ void WindfuryTotemStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// If the bot hasn't learned Windfury Totem yet, set Grounding Totem instead.
Player* bot = botAI->GetBot();
if (bot->HasSpell(8512))
{
triggers.push_back(new TriggerNode("set windfury totem", { NextAction("set windfury totem", 60.0f) }));
}
else if (bot->HasSpell(8177))
{
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

@ -261,13 +261,13 @@ bool TotemicRecallTrigger::IsActive()
}
// Find the active totem strategy for this slot, and return the highest-rank spellId the bot knows for it
static uint32 GetRequiredTotemSpellId(PlayerbotAI* ai, const char* strategies[],
static uint32 GetRequiredTotemSpellId(PlayerbotAI* botAI, const char* strategies[],
const uint32* spellList[], const size_t spellCounts[], size_t numStrategies)
{
Player* bot = ai->GetBot();
Player* bot = botAI->GetBot();
for (size_t i = 0; i < numStrategies; ++i)
{
if (ai->HasStrategy(strategies[i], BOT_STATE_COMBAT))
if (botAI->HasStrategy(strategies[i], BOT_STATE_COMBAT))
{
// Find the highest-rank spell the bot knows
for (size_t j = 0; j < spellCounts[i]; ++j)

View File

@ -41,14 +41,14 @@ const uint32 SPELL_CALL_OF_THE_ELEMENTS = 66842;
class MainHandWeaponNoImbueTrigger : public BuffTrigger
{
public:
MainHandWeaponNoImbueTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "main hand", 1) {}
MainHandWeaponNoImbueTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "main hand", 1) {}
virtual bool IsActive();
};
class OffHandWeaponNoImbueTrigger : public BuffTrigger
{
public:
OffHandWeaponNoImbueTrigger(PlayerbotAI* ai) : BuffTrigger(ai, "off hand", 1) {}
OffHandWeaponNoImbueTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "off hand", 1) {}
virtual bool IsActive();
};
@ -121,7 +121,7 @@ public:
class SpiritWalkTrigger : public Trigger
{
public:
SpiritWalkTrigger(PlayerbotAI* ai) : Trigger(ai, "spirit walk ready") {}
SpiritWalkTrigger(PlayerbotAI* botAI) : Trigger(botAI, "spirit walk ready") {}
bool IsActive() override;
@ -165,9 +165,7 @@ class PartyMemberCleanseSpiritPoisonTrigger : public PartyMemberNeedCureTrigger
{
public:
PartyMemberCleanseSpiritPoisonTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cleanse spirit", DISPEL_POISON)
{
}
: PartyMemberNeedCureTrigger(botAI, "cleanse spirit", DISPEL_POISON) {}
};
class CleanseSpiritCurseTrigger : public NeedCureTrigger
@ -180,9 +178,7 @@ class PartyMemberCleanseSpiritCurseTrigger : public PartyMemberNeedCureTrigger
{
public:
PartyMemberCleanseSpiritCurseTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cleanse spirit", DISPEL_CURSE)
{
}
: PartyMemberNeedCureTrigger(botAI, "cleanse spirit", DISPEL_CURSE) {}
};
class CleanseSpiritDiseaseTrigger : public NeedCureTrigger
@ -195,34 +191,7 @@ class PartyMemberCleanseSpiritDiseaseTrigger : public PartyMemberNeedCureTrigger
{
public:
PartyMemberCleanseSpiritDiseaseTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cleanse spirit", DISPEL_DISEASE)
{
}
};
class CurePoisonTrigger : public NeedCureTrigger
{
public:
CurePoisonTrigger(PlayerbotAI* botAI) : NeedCureTrigger(botAI, "cure poison", DISPEL_POISON) {}
};
class PartyMemberCurePoisonTrigger : public PartyMemberNeedCureTrigger
{
public:
PartyMemberCurePoisonTrigger(PlayerbotAI* botAI) : PartyMemberNeedCureTrigger(botAI, "cure poison", DISPEL_POISON) {}
};
class CureDiseaseTrigger : public NeedCureTrigger
{
public:
CureDiseaseTrigger(PlayerbotAI* botAI) : NeedCureTrigger(botAI, "cure disease", DISPEL_DISEASE) {}
};
class PartyMemberCureDiseaseTrigger : public PartyMemberNeedCureTrigger
{
public:
PartyMemberCureDiseaseTrigger(PlayerbotAI* botAI)
: PartyMemberNeedCureTrigger(botAI, "cure disease", DISPEL_DISEASE) {}
: PartyMemberNeedCureTrigger(botAI, "cleanse spirit", DISPEL_DISEASE) {}
};
// Damage and Debuff Triggers
@ -250,7 +219,7 @@ public:
class FlameShockTrigger : public DebuffTrigger
{
public:
FlameShockTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "flame shock", 1, true, 6.0f) {}
FlameShockTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "flame shock", 1, true, 6.0f) {}
bool IsActive() override { return BuffTrigger::IsActive(); }
};
@ -265,19 +234,19 @@ public:
class MaelstromWeapon5AndMediumAoeTrigger : public TwoTriggers
{
public:
MaelstromWeapon5AndMediumAoeTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "maelstrom weapon 5", "medium aoe") {}
MaelstromWeapon5AndMediumAoeTrigger(PlayerbotAI* botAI) : TwoTriggers(botAI, "maelstrom weapon 5", "medium aoe") {}
};
class MaelstromWeapon4AndMediumAoeTrigger : public TwoTriggers
{
public:
MaelstromWeapon4AndMediumAoeTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "maelstrom weapon 4", "medium aoe") {}
MaelstromWeapon4AndMediumAoeTrigger(PlayerbotAI* botAI) : TwoTriggers(botAI, "maelstrom weapon 4", "medium aoe") {}
};
class ChainLightningNoCdTrigger : public SpellNoCooldownTrigger
{
public:
ChainLightningNoCdTrigger(PlayerbotAI* ai) : SpellNoCooldownTrigger(ai, "chain lightning") {}
ChainLightningNoCdTrigger(PlayerbotAI* botAI) : SpellNoCooldownTrigger(botAI, "chain lightning") {}
};
// Healing Triggers
@ -307,49 +276,49 @@ protected:
class CallOfTheElementsTrigger : public Trigger
{
public:
CallOfTheElementsTrigger(PlayerbotAI* ai) : Trigger(ai, "call of the elements") {}
CallOfTheElementsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "call of the elements") {}
bool IsActive() override;
};
class TotemicRecallTrigger : public Trigger
{
public:
TotemicRecallTrigger(PlayerbotAI* ai) : Trigger(ai, "totemic recall") {}
TotemicRecallTrigger(PlayerbotAI* botAI) : Trigger(botAI, "totemic recall") {}
bool IsActive() override;
};
class NoEarthTotemTrigger : public Trigger
{
public:
NoEarthTotemTrigger(PlayerbotAI* ai) : Trigger(ai, "no earth totem") {}
NoEarthTotemTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no earth totem") {}
bool IsActive() override;
};
class NoFireTotemTrigger : public Trigger
{
public:
NoFireTotemTrigger(PlayerbotAI* ai) : Trigger(ai, "no fire totem") {}
NoFireTotemTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no fire totem") {}
bool IsActive() override;
};
class NoWaterTotemTrigger : public Trigger
{
public:
NoWaterTotemTrigger(PlayerbotAI* ai) : Trigger(ai, "no water totem") {}
NoWaterTotemTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no water totem") {}
bool IsActive() override;
};
class NoAirTotemTrigger : public Trigger
{
public:
NoAirTotemTrigger(PlayerbotAI* ai) : Trigger(ai, "no air totem") {}
NoAirTotemTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no air totem") {}
bool IsActive() override;
};
class CallOfTheElementsAndEnemyWithinMeleeTrigger : public TwoTriggers
{
public:
CallOfTheElementsAndEnemyWithinMeleeTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "call of the elements", "enemy within melee") {}
CallOfTheElementsAndEnemyWithinMeleeTrigger(PlayerbotAI* botAI) : TwoTriggers(botAI, "call of the elements", "enemy within melee") {}
};
// Set Strategy Assigned Totems
@ -359,8 +328,8 @@ class SetTotemTrigger : public Trigger
public:
// Template constructor: infers N (size of the id array) at compile time
template <size_t N>
SetTotemTrigger(PlayerbotAI* ai, std::string const& spellName, const uint32 (&ids)[N], int actionButtonId)
: Trigger(ai, "set " + spellName)
SetTotemTrigger(PlayerbotAI* botAI, std::string const& spellName, const uint32 (&ids)[N], int actionButtonId)
: Trigger(botAI, "set " + spellName)
, totemSpellIds(ids)
, totemSpellIdsCount(N)
, actionButtonId(actionButtonId)
@ -376,120 +345,120 @@ private:
class SetStrengthOfEarthTotemTrigger : public SetTotemTrigger
{
public:
SetStrengthOfEarthTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "strength of earth totem", STRENGTH_OF_EARTH_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
SetStrengthOfEarthTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "strength of earth totem", STRENGTH_OF_EARTH_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
};
class SetStoneskinTotemTrigger : public SetTotemTrigger
{
public:
SetStoneskinTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "stoneskin totem", STONESKIN_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
SetStoneskinTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "stoneskin totem", STONESKIN_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
};
class SetTremorTotemTrigger : public SetTotemTrigger
{
public:
SetTremorTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "tremor totem", TREMOR_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
SetTremorTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "tremor totem", TREMOR_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
};
class SetEarthbindTotemTrigger : public SetTotemTrigger
{
public:
SetEarthbindTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "earthbind totem", EARTHBIND_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
SetEarthbindTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "earthbind totem", EARTHBIND_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
};
class SetSearingTotemTrigger : public SetTotemTrigger
{
public:
SetSearingTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "searing totem", SEARING_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
SetSearingTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "searing totem", SEARING_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
};
class SetMagmaTotemTrigger : public SetTotemTrigger
{
public:
SetMagmaTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "magma totem", MAGMA_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
SetMagmaTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "magma totem", MAGMA_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
};
class SetFlametongueTotemTrigger : public SetTotemTrigger
{
public:
SetFlametongueTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "flametongue totem", FLAMETONGUE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
SetFlametongueTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "flametongue totem", FLAMETONGUE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
};
class SetTotemOfWrathTrigger : public SetTotemTrigger
{
public:
SetTotemOfWrathTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "totem of wrath", TOTEM_OF_WRATH, TOTEM_BAR_SLOT_FIRE) {}
SetTotemOfWrathTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "totem of wrath", TOTEM_OF_WRATH, TOTEM_BAR_SLOT_FIRE) {}
};
class SetFrostResistanceTotemTrigger : public SetTotemTrigger
{
public:
SetFrostResistanceTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "frost resistance totem", FROST_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
SetFrostResistanceTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "frost resistance totem", FROST_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
};
class SetHealingStreamTotemTrigger : public SetTotemTrigger
{
public:
SetHealingStreamTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "healing stream totem", HEALING_STREAM_TOTEM, TOTEM_BAR_SLOT_WATER) {}
SetHealingStreamTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "healing stream totem", HEALING_STREAM_TOTEM, TOTEM_BAR_SLOT_WATER) {}
};
class SetManaSpringTotemTrigger : public SetTotemTrigger
{
public:
SetManaSpringTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "mana spring totem", MANA_SPRING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
SetManaSpringTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "mana spring totem", MANA_SPRING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
};
class SetCleansingTotemTrigger : public SetTotemTrigger
{
public:
SetCleansingTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "cleansing totem", CLEANSING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
SetCleansingTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "cleansing totem", CLEANSING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
};
class SetFireResistanceTotemTrigger : public SetTotemTrigger
{
public:
SetFireResistanceTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "fire resistance totem", FIRE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_WATER) {}
SetFireResistanceTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "fire resistance totem", FIRE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_WATER) {}
};
class SetWrathOfAirTotemTrigger : public SetTotemTrigger
{
public:
SetWrathOfAirTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "wrath of air totem", WRATH_OF_AIR_TOTEM, TOTEM_BAR_SLOT_AIR) {}
SetWrathOfAirTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "wrath of air totem", WRATH_OF_AIR_TOTEM, TOTEM_BAR_SLOT_AIR) {}
};
class SetWindfuryTotemTrigger : public SetTotemTrigger
{
public:
SetWindfuryTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "windfury totem", WINDFURY_TOTEM, TOTEM_BAR_SLOT_AIR) {}
SetWindfuryTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "windfury totem", WINDFURY_TOTEM, TOTEM_BAR_SLOT_AIR) {}
};
class SetNatureResistanceTotemTrigger : public SetTotemTrigger
{
public:
SetNatureResistanceTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "nature resistance totem", NATURE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_AIR) {}
SetNatureResistanceTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "nature resistance totem", NATURE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_AIR) {}
};
class SetGroundingTotemTrigger : public SetTotemTrigger
{
public:
SetGroundingTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "grounding totem", GROUNDING_TOTEM, TOTEM_BAR_SLOT_AIR) {}
SetGroundingTotemTrigger(PlayerbotAI* botAI)
: SetTotemTrigger(botAI, "grounding totem", GROUNDING_TOTEM, TOTEM_BAR_SLOT_AIR) {}
};
#endif

View File

@ -7,6 +7,33 @@
#include "Playerbots.h"
bool CastBerserkerRageAction::isPossible()
{
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
return false;
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
if (!spellId)
return false;
if (!bot->HasSpell(spellId))
return false;
if (bot->HasSpellCooldown(spellId))
return false;
return true;
}
bool CastBerserkerRageAction::isUseful()
{
return (bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SAPPED))
&& !botAI->HasAura("berserker rage", bot)
&& CastSpellAction::isUseful();
}
bool CastSunderArmorAction::isUseful()
{
Aura* aura = botAI->GetAura("sunder armor", GetTarget(), false, true);

View File

@ -78,7 +78,15 @@ REACH_ACTION(CastInterceptAction, "intercept", 8.0f);
ENEMY_HEALER_ACTION(CastInterceptOnEnemyHealerAction, "intercept");
SNARE_ACTION(CastInterceptOnSnareTargetAction, "intercept");
MELEE_ACTION(CastSlamAction, "slam");
BUFF_ACTION(CastBerserkerRageAction, "berserker rage");
class CastBerserkerRageAction : public CastSpellAction
{
public:
CastBerserkerRageAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "berserker rage") {}
std::string const GetTargetName() override { return "self target"; }
bool isPossible() override;
bool isUseful() override;
};
MELEE_ACTION(CastWhirlwindAction, "whirlwind");
MELEE_ACTION(CastPummelAction, "pummel");
ENEMY_HEALER_ACTION(CastPummelOnEnemyHealerAction, "pummel");

View File

@ -7,9 +7,33 @@
#include "Playerbots.h"
class GenericWarriorNonCombatStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
GenericWarriorNonCombatStrategyActionNodeFactory() { creators["berserker rage"] = &berserker_rage; }
private:
static ActionNode* berserker_rage([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode(
"berserker rage",
/*P*/ { NextAction("berserker stance") },
/*A*/ {},
/*C*/ {}
);
}
};
GenericWarriorNonCombatStrategy::GenericWarriorNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
{
actionNodeFactories.Add(new GenericWarriorNonCombatStrategyActionNodeFactory());
}
void GenericWarriorNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) }));
triggers.push_back(new TriggerNode(
"fear sleep sap", { NextAction("berserker rage", ACTION_EMERGENCY + 1) }));
}

View File

@ -13,7 +13,7 @@ class PlayerbotAI;
class GenericWarriorNonCombatStrategy : public NonCombatStrategy
{
public:
GenericWarriorNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {}
GenericWarriorNonCombatStrategy(PlayerbotAI* botAI);
std::string const getName() override { return "nc"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;

View File

@ -7,9 +7,26 @@
#include "Playerbots.h"
class GenericWarriorStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
GenericWarriorStrategyActionNodeFactory() { creators["berserker rage"] = &berserker_rage; }
private:
static ActionNode* berserker_rage([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode(
"berserker rage",
/*P*/ { NextAction("berserker stance") },
/*A*/ {},
/*C*/ {}
);
}
};
GenericWarriorStrategy::GenericWarriorStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI)
{
actionNodeFactories.Add(new GenericWarriorStrategyActionNodeFactory());
}
void GenericWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@ -17,6 +34,8 @@ void GenericWarriorStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
CombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"enemy out of melee", { NextAction("reach melee", ACTION_HIGH + 1) }));
triggers.push_back(new TriggerNode(
"fear sleep sap", { NextAction("berserker rage", ACTION_EMERGENCY + 1) }));
}
class WarrirorAoeStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>

View File

@ -4,7 +4,6 @@
*/
#include "WarriorTriggers.h"
#include "Playerbots.h"
bool BloodrageBuffTrigger::IsActive()
@ -16,15 +15,11 @@ bool BloodrageBuffTrigger::IsActive()
bool VigilanceTrigger::IsActive()
{
if (!bot->HasSpell(50720))
{
return false;
}
Group* group = bot->GetGroup();
if (!group)
{
return false;
}
Player* currentVigilanceTarget = nullptr;
Player* mainTank = nullptr;
@ -33,37 +28,23 @@ bool VigilanceTrigger::IsActive()
Player* highestGearScorePlayer = nullptr;
uint32 highestGearScore = 0;
// Iterate once through the group to gather all necessary information
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || member == bot || !member->IsAlive())
continue;
// Check if member has Vigilance applied by the bot
if (!currentVigilanceTarget && botAI->HasAura("vigilance", member, false, true))
{
currentVigilanceTarget = member;
}
// Identify Main Tank
if (!mainTank && botAI->IsMainTank(member))
{
mainTank = member;
}
// Identify Assist Tanks
if (assistTank1 == nullptr && botAI->IsAssistTankOfIndex(member, 0))
{
else if (!assistTank1 && botAI->IsAssistTankOfIndex(member, 0))
assistTank1 = member;
}
else if (assistTank2 == nullptr && botAI->IsAssistTankOfIndex(member, 1))
{
else if (!assistTank2 && botAI->IsAssistTankOfIndex(member, 1))
assistTank2 = member;
}
// Determine Highest Gear Score
uint32 gearScore = botAI->GetEquipGearScore(member/*, false, false*/);
uint32 gearScore = botAI->GetEquipGearScore(member);
if (gearScore > highestGearScore)
{
highestGearScore = gearScore;
@ -71,33 +52,20 @@ bool VigilanceTrigger::IsActive()
}
}
// Determine the highest-priority target
Player* highestPriorityTarget = mainTank ? mainTank :
(assistTank1 ? assistTank1 :
(assistTank2 ? assistTank2 : highestGearScorePlayer));
// Trigger if no Vigilance is active or the current target is not the highest-priority target
if (!currentVigilanceTarget || currentVigilanceTarget != highestPriorityTarget)
{
return true;
}
return false; // No need to reassign Vigilance
return false;
}
bool ShatteringThrowTrigger::IsActive()
{
// Spell cooldown check
if (!bot->HasSpell(64382))
{
if (!bot->HasSpell(64382) || bot->HasSpellCooldown(64382))
return false;
}
// Spell cooldown check
if (bot->HasSpellCooldown(64382))
{
return false;
}
GuidVector enemies = AI_VALUE(GuidVector, "possible targets");
@ -107,7 +75,6 @@ bool ShatteringThrowTrigger::IsActive()
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
@ -117,5 +84,74 @@ bool ShatteringThrowTrigger::IsActive()
}
}
return false; // No valid targets within range
return false;
}
bool BattleShoutTrigger::IsActive()
{
if (!BuffTrigger::IsActive())
return false;
uint32 battleShoutSpellId = AI_VALUE2(uint32, "spell id", "battle shout");
if (!battleShoutSpellId)
return false;
SpellInfo const* bsInfo = sSpellMgr->GetSpellInfo(battleShoutSpellId);
if (!bsInfo)
return false;
int32 bsApValue = 0;
for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff)
{
if (bsInfo->Effects[eff].ApplyAuraName == SPELL_AURA_MOD_ATTACK_POWER)
{
bsApValue = bsInfo->Effects[eff].BasePoints + 1;
break;
}
}
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]))
{
cpBonus = commandingPresenceBonus[rank];
break;
}
}
int32 effectiveBsAp = int32(bsApValue * (1.0f + cpBonus));
static const char* blessingNames[] = {
"blessing of might", "greater blessing of might", nullptr
};
for (int i = 0; blessingNames[i] != nullptr; ++i)
{
Aura* bom = botAI->GetAura(blessingNames[i], bot);
if (!bom)
continue;
SpellInfo const* bomInfo = bom->GetSpellInfo();
if (!bomInfo)
continue;
for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff)
{
if (bomInfo->Effects[eff].ApplyAuraName == SPELL_AURA_MOD_ATTACK_POWER)
{
int32 bomApValue = bomInfo->Effects[eff].BasePoints + 1;
if (bomApValue >= effectiveBsAp)
return false;
break;
}
}
}
return true;
}

View File

@ -9,7 +9,13 @@
#include "GenericTriggers.h"
#include "PlayerbotAI.h"
BUFF_TRIGGER(BattleShoutTrigger, "battle shout");
class BattleShoutTrigger : public BuffTrigger
{
public:
BattleShoutTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "battle shout") {}
bool IsActive() override;
};
BUFF_TRIGGER(BattleStanceTrigger, "battle stance");
BUFF_TRIGGER(DefensiveStanceTrigger, "defensive stance");
BUFF_TRIGGER(BerserkerStanceTrigger, "berserker stance");
@ -85,4 +91,5 @@ public:
// public:
// SlamTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "slam!") {}
// };
#endif

View File

@ -647,7 +647,7 @@ float IccSindragosaMultiplier::GetValue(Action* action)
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastConsecrationAction*>(action) ||
dynamic_cast<CastFlamestrikeAction*>(action) || dynamic_cast<CastExplosiveTrapAction*>(action) ||
dynamic_cast<CastExplosiveShotAction*>(action))
dynamic_cast<CastExplosiveShotBaseAction*>(action))
return 0.0f;
}
@ -774,7 +774,7 @@ float IccLichKingAddsMultiplier::GetValue(Action* action)
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastFlamestrikeAction*>(action) ||
dynamic_cast<CastExplosiveTrapAction*>(action) || dynamic_cast<CastExplosiveShotAction*>(action))
dynamic_cast<CastExplosiveTrapAction*>(action) || dynamic_cast<CastExplosiveShotBaseAction*>(action))
return 0.0f;
}

View File

@ -151,7 +151,14 @@ bool NewRpgGoGrindAction::Execute(Event /*event*/)
if (SearchQuestGiverAndAcceptOrReward())
return true;
if (auto* data = std::get_if<NewRpgInfo::GoGrind>(&botAI->rpgInfo.data))
return MoveFarTo(data->pos);
{
if (MoveFarTo(data->pos))
return true;
// Small nudge so the next tick's MoveFarTo starts from a
// slightly different position. Kept small so it doesn't look
// like the bot is abandoning its destination.
return MoveRandomNear(10.0f);
}
return false;
}
@ -162,7 +169,11 @@ bool NewRpgGoCampAction::Execute(Event /*event*/)
return true;
if (auto* data = std::get_if<NewRpgInfo::GoCamp>(&botAI->rpgInfo.data))
return MoveFarTo(data->pos);
{
if (MoveFarTo(data->pos))
return true;
return MoveRandomNear(10.0f);
}
return false;
}
@ -215,7 +226,14 @@ bool NewRpgWanderNpcAction::Execute(Event /*event*/)
data.lastReach = 0;
}
else
return MoveWorldObjectTo(data.npcOrGo);
{
if (MoveWorldObjectTo(data.npcOrGo))
return true;
// NPC pathing failed (random offset in a wall, mmap hiccup, etc).
// Take a small random step so the next tick retries from a
// different spot instead of staring at the NPC from afar.
return MoveRandomNear(15.0f);
}
return true;
}
@ -305,7 +323,12 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
{
return MoveFarTo(data.pos);
if (MoveFarTo(data.pos))
return true;
// Long-range sampler couldn't land a candidate — nudge the
// bot a short distance so the next tick retries from a
// different position instead of sitting idle.
return MoveRandomNear(10.0f);
}
// Now we are near the quest objective
// kill mobs and looting quest should be done automatically by grind strategy
@ -352,7 +375,11 @@ bool NewRpgDoQuestAction::DoIncompleteQuest(NewRpgInfo::DoQuest& data)
return true;
}
return MoveRandomNear(20.0f);
// At the POI: keep the bot actively placed but avoid large
// random 20yd hops that look like pacing back and forth. A small
// ~8yd wander reads as the bot looking around while grind/loot
// strategies do their work.
return MoveRandomNear(8.0f);
}
bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
@ -392,7 +419,11 @@ bool NewRpgDoQuestAction::DoCompletedQuest(NewRpgInfo::DoQuest& data)
return false;
if (bot->GetDistance(data.pos) > 10.0f && !data.lastReachPOI)
return MoveFarTo(data.pos);
{
if (MoveFarTo(data.pos))
return true;
return MoveRandomNear(10.0f);
}
// Now we are near the qoi of reward
// the quest should be rewarded by SearchQuestGiverAndAcceptOrReward

View File

@ -46,17 +46,51 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
return false;
}
// Let previously committed movement finish before recomputing.
//
// MoveTo internally caps its stored delay at maxWaitForMove
// (default 5s), but a long path (200+ yd routed around a
// mountain) takes 30+ seconds to walk. After 5s
// IsWaitingForLastMove returns false and MoveFarTo re-enters.
// Without this gate, DoMovePoint would call mm->Clear() and
// reissue MovePoint from the new bot position — and from a new
// position mmap's partial-path endpoint often differs, so the
// bot gets clobbered mid-walk and ends up oscillating (e.g.
// cave entrance -> inside cave -> cave entrance -> mountain
// base -> cave entrance...) around an unreachable destination.
//
// If the bot is still actively walking toward its last
// committed point on the same map, just let the current spline
// finish. The stuck counter below continues to track real
// progress toward dest and triggers teleport recovery if the
// committed paths genuinely aren't closing the gap.
{
LastMovement& lastMove = AI_VALUE(LastMovement&, "last movement");
if (bot->isMoving() && lastMove.lastMoveToMapId == bot->GetMapId())
{
float remaining = bot->GetExactDist(lastMove.lastMoveToX, lastMove.lastMoveToY, lastMove.lastMoveToZ);
if (remaining > 10.0f)
return true;
}
}
// stuck check
float disToDest = bot->GetDistance(dest);
if (disToDest + 1.0f < botAI->rpgInfo.nearestMoveFarDis)
// Require a meaningful improvement (5yd) to reset the stuck counter.
// The old 1yd threshold was small enough that bots oscillating back
// and forth around an obstacle would keep "making progress" forever
// and never trigger the teleport recovery below.
if (disToDest + 5.0f < botAI->rpgInfo.nearestMoveFarDis)
{
botAI->rpgInfo.nearestMoveFarDis = disToDest;
botAI->rpgInfo.stuckTs = getMSTime();
botAI->rpgInfo.stuckAttempts = 0;
}
else if (++botAI->rpgInfo.stuckAttempts >= 10 && GetMSTimeDiffToNow(botAI->rpgInfo.stuckTs) >= stuckTime)
else if (++botAI->rpgInfo.stuckAttempts >= 5 && GetMSTimeDiffToNow(botAI->rpgInfo.stuckTs) >= stuckTime)
{
// Unfortunately we've been stuck here for over 5 mins, fallback to teleporting directly to the destination
// No meaningful progress toward dest for `stuckTime`: fall
// back to teleporting directly so the bot can get on with
// its RPG objective instead of oscillating indefinitely.
botAI->rpgInfo.stuckTs = getMSTime();
botAI->rpgInfo.stuckAttempts = 0;
const AreaTableEntry* entry = sAreaTableStore.LookupEntry(bot->GetZoneId());
@ -78,26 +112,62 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest)
false, true);
}
const uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
// Primary strategy: ask mmap for a route to the TRUE destination.
// If mmap can reach it directly (PATHFIND_NORMAL) or partially
// (PATHFIND_INCOMPLETE — destinations beyond the smooth-path cap
// of ~296 yards, or where local geometry blocks the final step),
// walk to the furthest reachable waypoint mmap computed. This
// lets bots follow the real route around obstacles (mountains,
// cave walls, cliffs) instead of trying to cut straight through.
// The spline system walks the whole returned path smoothly, so
// subsequent ticks early-out via IsWaitingForLastMove and no
// further PathGenerator calls fire until the bot arrives.
{
PathGenerator path(bot);
path.CalculatePath(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
PathType type = path.GetPathType();
bool canReach = !(type & (~typeOk));
if (canReach)
{
const G3D::Vector3& endPos = path.GetActualEndPosition();
// Only commit if the mmap endpoint actually makes progress
// toward the destination. For pathological INCOMPLETE
// results (e.g. disconnected polys that still report
// INCOMPLETE) the endpoint can land right under the bot;
// fall through to cone sampling in that case.
float endDistToDest = dest.GetExactDist(endPos.x, endPos.y, endPos.z);
if (endDistToDest + 5.0f < disToDest)
{
return MoveTo(bot->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, true);
}
}
}
// Fallback: mmap couldn't route to the destination. Sample the
// forward cone for a reachable stepping stone so the bot keeps
// moving and can try again from a new vantage point. Cap at 2
// samples — we already spent one PathGenerator call above and at
// 3000 bots every extra CalculatePath matters.
float minDelta = M_PI;
const float x = bot->GetPositionX();
const float y = bot->GetPositionY();
const float z = bot->GetPositionZ();
const float baseAngle = bot->GetAngle(&dest);
float rx, ry, rz;
bool found = false;
int attempt = 3;
while (attempt--)
for (int attempt = 0; attempt < 2; ++attempt)
{
float angle = bot->GetAngle(&dest);
float delta = urand(1, 100) <= 75 ? (rand_norm() - 0.5) * M_PI * 0.5 : (rand_norm() - 0.5) * M_PI * 2;
angle += delta;
float dis = rand_norm() * pathFinderDis;
float dx = x + cos(angle) * dis;
float dy = y + sin(angle) * dis;
float delta = (rand_norm() - 0.5f) * static_cast<float>(M_PI); // ±π/2, forward cone
float sampleDis = (0.5f + rand_norm() * 0.5f) * pathFinderDis;
float angle = baseAngle + delta;
float dx = x + cos(angle) * sampleDis;
float dy = y + sin(angle) * sampleDis;
float dz = z + 0.5f;
PathGenerator path(bot);
path.CalculatePath(dx, dy, dz);
PathType type = path.GetPathType();
uint32 typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
bool canReach = !(type & (~typeOk));
if (canReach && fabs(delta) <= minDelta)
@ -159,14 +229,18 @@ bool NewRpgBaseAction::MoveRandomNear(float moveStep, MovementPriority priority)
return false;
}
float distance = rand_norm() * moveStep;
Map* map = bot->GetMap();
const float x = bot->GetPositionX();
const float y = bot->GetPositionY();
const float z = bot->GetPositionZ();
int attempts = 1;
while (attempts--)
// Previously: attempts = 1. A single random sample often landed in
// water / blocked geometry / unreachable poly, the function returned
// false, and the caller had no fallback — bot stood still. Retry a
// handful of times with a fresh distance each loop so a bad roll
// doesn't lock the bot in place.
for (int attempt = 0; attempt < 8; ++attempt)
{
float distance = (0.4f + rand_norm() * 0.6f) * moveStep;
float angle = (float)rand_norm() * 2 * static_cast<float>(M_PI);
float dx = x + distance * cos(angle);
float dy = y + distance * sin(angle);

View File

@ -61,7 +61,14 @@ protected:
protected:
/* FOR MOVE FAR */
const float pathFinderDis = 70.0f;
const uint32 stuckTime = 5 * 60 * 1000;
// Time without real progress toward dest before MoveFarTo
// falls back to teleport recovery. Kept short enough that a
// bot truly oscillating around an unreachable destination
// (mmap returning non-progressing partial paths, or NOPATH +
// cone fallback wandering) doesn't spin for 5 minutes before
// the teleport fires, but long enough that a genuine long
// walk that is slowly making progress never triggers it.
const uint32 stuckTime = 90 * 1000;
};
#endif

View File

@ -25,7 +25,7 @@ void PlayerbotAIBase::UpdateAI(uint32 elapsed, bool minimal)
return;
UpdateAIInternal(elapsed, minimal);
YieldThread();
YieldThread(nullptr);
}
void PlayerbotAIBase::SetNextCheckDelay(uint32 const delay)
@ -49,10 +49,14 @@ void PlayerbotAIBase::IncreaseNextCheckDelay(uint32 delay)
bool PlayerbotAIBase::CanUpdateAI() { return nextAICheckDelay == 0; }
void PlayerbotAIBase::YieldThread(uint32 delay)
void PlayerbotAIBase::YieldThread(Player* bot, uint32 delay)
{
if (nextAICheckDelay < delay)
nextAICheckDelay = delay;
{
// Adding a deterministic per-bot slight offset (0200 ms) to stagger updates and prevent cpu spikes.
uint32 offset = bot ? (bot->GetGUID().GetCounter() % 201) : 0;
nextAICheckDelay = delay + offset;
}
}
bool PlayerbotAIBase::IsActive() { return nextAICheckDelay < sPlayerbotAIConfig.maxWaitForMove; }

View File

@ -8,6 +8,7 @@
#include "Define.h"
#include "PlayerbotAIConfig.h"
#include "Player.h"
class PlayerbotAIBase
{
@ -17,7 +18,7 @@ public:
bool CanUpdateAI();
void SetNextCheckDelay(uint32 const delay);
void IncreaseNextCheckDelay(uint32 delay);
void YieldThread(uint32 delay = sPlayerbotAIConfig.reactDelay);
void YieldThread(Player* bot, uint32 delay = sPlayerbotAIConfig.reactDelay);
virtual void UpdateAI(uint32 elapsed, bool minimal = false);
virtual void UpdateAIInternal(uint32 elapsed, bool minimal = false) = 0;
bool IsActive();

View File

@ -311,7 +311,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
else
engine->addStrategiesNoInit("frost", nullptr);
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
engine->addStrategiesNoInit("dps", "dps assist", "cure", "cc", "aoe", nullptr);
break;
case CLASS_WARRIOR:
if (tab == WARRIOR_TAB_PROTECTION)
@ -363,7 +363,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
else
engine->addStrategiesNoInit("surv", nullptr);
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
engine->addStrategiesNoInit("cc", "dps assist", "aoe", "bdps", nullptr);
break;
case CLASS_ROGUE:
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)

View File

@ -3032,6 +3032,17 @@ void PlayerbotFactory::InitMounts()
slow = {33660, 35020, 35022, 35018};
fast = {35025, 35025, 35027};
break;
default:
if (bot->GetTeamId() == TEAM_HORDE)
{ // Orc mounts
slow = {470, 6648, 458, 472};
fast = {23228, 23227, 23229};
}
else // Human mounts
{
slow = {6654, 6653, 580};
fast = {23250, 23252, 23251};
}
}
switch (bot->GetTeamId())
@ -3307,7 +3318,7 @@ void PlayerbotFactory::InitReagents()
break;
case CLASS_PALADIN:
if (level >= 52)
items.push_back({21177, 80}); // Symbol of Kings
items.push_back({21177, 100}); // Symbol of Kings
break;
case CLASS_PRIEST:
if (level >= 48 && level < 56)

View File

@ -119,7 +119,7 @@ PlayerbotAI::PlayerbotAI()
for (uint8 i = 0; i < MAX_ACTIVITY_TYPE; i++)
{
allowActiveCheckTimer[i] = time(nullptr);
allowActiveCheckTimer[i] = 0;
allowActive[i] = false;
}
}
@ -137,19 +137,20 @@ PlayerbotAI::PlayerbotAI(Player* bot)
for (uint8 i = 0; i < MAX_ACTIVITY_TYPE; i++)
{
allowActiveCheckTimer[i] = time(nullptr);
allowActiveCheckTimer[i] = 0;
allowActive[i] = false;
}
accountId = bot->GetSession()->GetAccountId();
aiObjectContext = AiFactory::createAiObjectContext(bot, this);
engines[BOT_STATE_COMBAT] = AiFactory::createCombatEngine(bot, this, aiObjectContext);
engines[BOT_STATE_NON_COMBAT] = AiFactory::createNonCombatEngine(bot, this, aiObjectContext);
engines[BOT_STATE_DEAD] = AiFactory::createDeadEngine(bot, this, aiObjectContext);
if (sPlayerbotAIConfig.applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId());
currentEngine = engines[BOT_STATE_NON_COMBAT];
currentState = BOT_STATE_NON_COMBAT;
@ -279,7 +280,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
if (spellTarget && !spellTarget->IsAlive() && !spellInfo->IsAllowingDeadTarget())
{
InterruptSpell();
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
return;
}
@ -288,7 +289,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
if (goSpellTarget && !goSpellTarget->isSpawned())
{
InterruptSpell();
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
return;
}
@ -320,7 +321,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
if (isHeal && isSingleTarget && spellTarget && spellTarget->IsFullHealth())
{
InterruptSpell();
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
return;
}
@ -332,7 +333,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
}
// Wait for spell cast
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
return;
}
}
@ -368,7 +369,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
// Update internal AI
UpdateAIInternal(elapsed, minimal);
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
}
// Helper function for UpdateAI to check group membership and handle removal if necessary
@ -445,9 +446,11 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
if (!bot->GetMap())
return; // instances are created and destroyed on demand
// kinda expensive call to make on every single updateAI, do we really need this information?
std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I";
PerfMonitorOperation* pmo =
sPerfMonitor.start(PERF_MON_TOTAL, "PlayerbotAI::UpdateAIInternal " + mapString);
ExternalEventHelper helper(aiObjectContext);
// chat replies
@ -1202,23 +1205,18 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
if (HasRealPlayerMaster() && guid1 != GetMaster()->GetGUID())
return;
auto itemIds = GetChatHelper()->ExtractAllItemIds(message);
if (message.starts_with(sPlayerbotAIConfig.toxicLinksPrefix) &&
(GetChatHelper()->ExtractAllItemIds(message).size() > 0 ||
GetChatHelper()->ExtractAllQuestIds(message).size() > 0) &&
(itemIds.size() > 0 || GetChatHelper()->ExtractAllQuestIds(message).size() > 0) &&
sPlayerbotAIConfig.toxicLinksRepliesChance)
{
if (urand(0, 50) > 0 || urand(1, 100) > sPlayerbotAIConfig.toxicLinksRepliesChance)
{
return;
}
}
else if ((GetChatHelper()->ExtractAllItemIds(message).count(19019) &&
sPlayerbotAIConfig.thunderfuryRepliesChance))
else if (itemIds.count(19019) && sPlayerbotAIConfig.thunderfuryRepliesChance)
{
if (urand(0, 60) > 0 || urand(1, 100) > sPlayerbotAIConfig.thunderfuryRepliesChance)
{
return;
}
}
else
{
@ -1962,6 +1960,11 @@ bool PlayerbotAI::HasAggro(Unit* unit)
return false;
}
bool PlayerbotAI::IsMovementImpaired(Unit* unit)
{
return unit && (unit->HasAuraType(SPELL_AURA_MOD_ROOT) || unit->IsRooted() || unit->GetSpeedRate(MOVE_RUN) < 1.0f);
}
int32 PlayerbotAI::GetAssistTankIndex(Player* player)
{
Group* group = player->GetGroup();
@ -4372,21 +4375,27 @@ Player* PlayerbotAI::GetGroupLeader()
return master;
}
uint32 PlayerbotAI::GetFixedBotNumer(uint32 maxNum, float cyclePerMin)
uint32 PlayerbotAI::GetFixedBotNumber(uint32 maxNum)
{
uint32 randseed = rand32(); // Seed random number
uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot.
if (maxNum == 0)
return 0;
if (cyclePerMin > 0)
{
uint32 cycle = floor(getMSTime() / (1000)); // Semi-random number adds 1 each second.
cycle = cycle * cyclePerMin / 60; // Cycles cyclePerMin per minute.
randnum += cycle; // Make the random number cylce.
}
// Deterministic pseudo-random hash based on the bot GUID evenly distributed across active slots
uint32 id = bot->GetGUID().GetCounter();
uint32 h = id;
h ^= h >> 16;
h *= 0x7feb352d;
h ^= h >> 15;
h *= 0x846ca68b;
h ^= h >> 16;
randnum =
(randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99.
return randnum; // Now we have a number unique for each bot between 0 and maxNum that increases by cyclePerMin.
// Current time slot
uint32 timeSlot = (getMSTime() / 1000) / sPlayerbotAIConfig.BotActiveAloneDurationSeconds;
// Mix timeSlot into the hash to reshuffle every rotation window
uint32 mixed = h ^ (timeSlot * 0x9e3779b9); // with multiplicative constant
return mixed % maxNum;
}
/*
@ -4403,7 +4412,7 @@ enum GrouperType
GrouperType PlayerbotAI::GetGrouperType()
{
uint32 grouperNumber = GetFixedBotNumer(100, 0);
uint32 grouperNumber = GetFixedBotNumber(100);
if (grouperNumber < 20 && !HasRealPlayerMaster())
return GrouperType::SOLO;
@ -4425,7 +4434,7 @@ GrouperType PlayerbotAI::GetGrouperType()
GuilderType PlayerbotAI::GetGuilderType()
{
uint32 grouperNumber = GetFixedBotNumer(100, 0);
uint32 grouperNumber = GetFixedBotNumber(100);
if (grouperNumber < 20 && !HasRealPlayerMaster())
return GuilderType::SOLO;
@ -4448,7 +4457,6 @@ GuilderType PlayerbotAI::GetGuilderType()
bool PlayerbotAI::HasPlayerNearby(WorldPosition* pos, float range)
{
float sqRange = range * range;
bool nearPlayer = false;
for (auto& player : sRandomPlayerbotMgr.GetPlayers())
{
if (!player->IsGameMaster() || player->isGMVisible())
@ -4457,19 +4465,18 @@ bool PlayerbotAI::HasPlayerNearby(WorldPosition* pos, float range)
continue;
if (pos->sqDistance(WorldPosition(player)) < sqRange)
nearPlayer = true;
return true;
// if player is far check farsight/cinematic camera
WorldObject* viewObj = player->GetViewpoint();
if (viewObj && viewObj != player)
{
if (pos->sqDistance(WorldPosition(viewObj)) < sqRange)
nearPlayer = true;
return true;
}
}
}
return nearPlayer;
return false;
}
bool PlayerbotAI::HasPlayerNearby(float range)
@ -4478,173 +4485,97 @@ bool PlayerbotAI::HasPlayerNearby(float range)
return HasPlayerNearby(&botPos, range);
};
bool PlayerbotAI::HasManyPlayersNearby(uint32 trigerrValue, float range)
{
float sqRange = range * range;
uint32 found = 0;
for (auto& player : sRandomPlayerbotMgr.GetPlayers())
{
if ((!player->IsGameMaster() || player->isGMVisible()) && ServerFacade::instance().GetDistance2d(player, bot) < sqRange)
{
found++;
if (found >= trigerrValue)
return true;
}
}
return false;
}
inline bool HasRealPlayers(Map* map)
{
Map::PlayerList const& players = map->GetPlayers();
if (players.IsEmpty())
{
return false;
}
for (auto const& itr : players)
{
Player* player = itr.GetSource();
if (!player || !player->IsVisible())
{
continue;
}
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || botAI->IsRealPlayer() || botAI->HasRealPlayerMaster())
{
return true;
}
}
return false;
}
inline bool ZoneHasRealPlayers(Player* bot)
{
Map* map = bot->GetMap();
if (!bot || !map)
{
return false;
}
for (Player* player : sRandomPlayerbotMgr.GetPlayers())
{
if (player->GetMapId() != bot->GetMapId())
continue;
if (player->IsGameMaster() && !player->IsVisible())
{
continue;
}
if (player->GetZoneId() == bot->GetZoneId())
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || botAI->IsRealPlayer() || botAI->HasRealPlayerMaster())
{
return true;
}
}
}
return false;
}
bool PlayerbotAI::AllowActive(ActivityType activityType)
{
// Early return if bot is in invalid state
// bot is in an invalid state, not safe to process
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
return false;
// when botActiveAlone is 100% and smartScale disabled
if (sPlayerbotAIConfig.botActiveAlone >= 100 && !sPlayerbotAIConfig.botActiveAloneSmartScale)
{
// always allow packet handling (e.g. group invites, trade, loot, friend requests etc)
if (activityType == PACKET_ACTIVITY)
return true;
}
// Is in combat. Always defend yourself.
// all bots forced active, no rotation or scaling needed
if (sPlayerbotAIConfig.botActiveAlone >= 100 && !sPlayerbotAIConfig.botActiveAloneSmartScale)
return true;
// bot is in combat, always defend yourself
if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY)
{
if (bot->IsInCombat())
{
return true;
}
}
// only keep updating till initializing time has completed,
// which prevents unneeded expensive GameTime calls.
if (_isBotInitializing)
{
_isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig.maxRandomBots * 0.11;
// no activity allowed during bot initialization
if (_isBotInitializing)
{
return false;
}
}
// General exceptions
if (activityType == PACKET_ACTIVITY)
{
return true;
}
// bg, raid, dungeon
// bot is inside a BG, dungeon, or raid — always active
if (!WorldPosition(bot).isOverworld())
{
return true;
}
// bot map has active players.
if (sPlayerbotAIConfig.BotActiveAloneForceWhenInMap)
{
if (HasRealPlayers(bot->GetMap()))
{
return true;
}
}
// bot is waiting in a BG queue — stay active to speed up join
if (bot->InBattlegroundQueue())
return true;
// bot zone has active players.
if (sPlayerbotAIConfig.BotActiveAloneForceWhenInZone)
{
if (ZoneHasRealPlayers(bot))
{
return true;
}
}
// when in real guild
// bot is in a guild that contains a real player
if (sPlayerbotAIConfig.BotActiveAloneForceWhenInGuild)
{
if (IsInRealGuild())
{
if (IsInRealGuild()) // checks cache list
return true;
}
// a real player is in the same zone (e.g. Elwynn Forest), same continent or within configured yard radius
// combined into a single loop to multiple iterations since this function is called so often
bool checkMap = sPlayerbotAIConfig.BotActiveAloneForceWhenInMap;
bool checkZone = sPlayerbotAIConfig.BotActiveAloneForceWhenInZone;
bool checkRadius = sPlayerbotAIConfig.BotActiveAloneForceWhenInRadius > 0;
if (checkMap || checkZone || checkRadius)
{
uint32 botMapId = bot->GetMapId();
uint32 botZoneId = checkZone ? bot->GetZoneId() : 0;
float sqRange = 0.0f;
WorldPosition botPos(bot);
if (checkRadius)
{
float range = static_cast<float>(sPlayerbotAIConfig.BotActiveAloneForceWhenInRadius);
sqRange = range * range;
}
for (auto& player : sRandomPlayerbotMgr.GetPlayers())
{
if (!player || player->GetMapId() != botMapId)
continue;
bool isGM = player->IsGameMaster();
// map check
if (checkMap && !(isGM && !player->IsVisible()))
return true;
// zone check
if (checkZone && !(isGM && !player->IsVisible()) && player->GetZoneId() == botZoneId)
return true;
// radius check
if (checkRadius && (!isGM || player->isGMVisible()))
{
if (botPos.sqDistance(WorldPosition(player)) < sqRange)
return true;
WorldObject* viewObj = player->GetViewpoint();
if (viewObj && viewObj != player && botPos.sqDistance(WorldPosition(viewObj)) < sqRange)
return true;
}
}
}
// Player is near. Always active.
if (HasPlayerNearby(sPlayerbotAIConfig.BotActiveAloneForceWhenInRadius))
{
return true;
}
// Has player master. Always active.
// bot has a real player master (not another bot)
if (GetMaster())
{
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(GetMaster());
if (!masterBotAI || masterBotAI->IsRealPlayer())
{
return true;
}
}
// if grouped up
// bot is grouped with a real player (or a bot owned by one)
Group* group = bot->GetGroup();
if (group)
{
@ -4655,52 +4586,37 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
continue;
if (member == bot)
{
continue;
}
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
{
if (!memberBotAI || memberBotAI->HasRealPlayerMaster())
{
return true;
}
}
// group member is a real player or owned by one — stay active
if (!memberBotAI || memberBotAI->HasRealPlayerMaster())
return true;
// if group leader (bot) is inactive, follow suit
if (group->IsLeader(member->GetGUID()))
{
if (!memberBotAI->AllowActivity(PARTY_ACTIVITY))
{
return false;
}
}
}
}
// In bg queue. Speed up bg queue/join.
if (bot->InBattlegroundQueue())
{
return true;
}
// bot is in LFG queue — stay active
bool isLFG = false;
if (group)
{
if (sLFGMgr->GetState(group->GetGUID()) != lfg::LFG_STATE_NONE)
{
isLFG = true;
}
}
if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
{
isLFG = true;
}
if (isLFG)
{
return true;
}
// HasFriend
if (isLFG)
return true;
// a real player has this bot on their friends list
if (sPlayerbotAIConfig.BotActiveAloneForceWhenIsFriend)
{
// shouldnt be needed analyse in future
@ -4717,41 +4633,27 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
if (!playerAI || !playerAI->IsRealPlayer())
continue;
// if a real player has the bot as a friend
PlayerSocial* social = player->GetSocial();
if (social && social->HasFriend(bot->GetGUID()))
return true;
}
}
// Force the bots to spread
if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY)
{
if (HasManyPlayersNearby(10, 40))
{
return true;
}
}
// Bots don't need react to PathGenerator activities
// pathfinding only runs for bots forced active by the rules above —
// skip it for bots that would only be active via random rotation
if (activityType == DETAILED_MOVE_ACTIVITY)
{
return false;
}
// #######################################################################################
// Acitivity throttling logic
// #######################################################################################
if (sPlayerbotAIConfig.botActiveAlone <= 0)
{
return false;
}
// #######################################################################################
// All mandatory conditations are checked to be active or not, from here the remaining
// situations are usable for scaling when enabled.
// #######################################################################################
// Below is code to have a specified % of bots active at all times.
// The default is 100%. With 1% of all bots going active or inactive each minute.
// base threshold capped at 100
uint32 mod = sPlayerbotAIConfig.botActiveAlone > 100 ? 100 : sPlayerbotAIConfig.botActiveAlone;
// reduce threshold based on server tick time when SmartScale is enabled
if (sPlayerbotAIConfig.botActiveAloneSmartScale &&
bot->GetLevel() >= sPlayerbotAIConfig.botActiveAloneSmartScaleWhenMinLevel &&
bot->GetLevel() <= sPlayerbotAIConfig.botActiveAloneSmartScaleWhenMaxLevel)
@ -4759,34 +4661,27 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
mod = AutoScaleActivity(mod);
}
uint32 ActivityNumber =
GetFixedBotNumer(100, sPlayerbotAIConfig.botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
return ActivityNumber <=
(sPlayerbotAIConfig.botActiveAlone * mod) /
100; // The given percentage of bots should be active and rotate 1% of those active bots each minute.
// deterministic rotation — bot is active if its hash falls below the threshold
uint32 ActivityNumber = GetFixedBotNumber(100);
return ActivityNumber < mod;
}
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
{
const int activityIndex = static_cast<int>(activityType);
// Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place.
if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE)
{
LOG_ERROR("playerbots", "AllowActivity received invalid activity type value: {}", activityIndex);
return true;
}
if (!allowActiveCheckTimer[activityIndex])
allowActiveCheckTimer[activityIndex] = time(nullptr);
allowActiveCheckTimer[activityIndex] = getMSTime();
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5))
// 4500ms base + 0499ms per-bot offset = 45004999ms, capping at just under 5 seconds
uint32 offset = bot->GetGUID().GetCounter() % 500;
if (!checkNow && getMSTime() < (allowActiveCheckTimer[activityIndex] + 4500 + offset))
return allowActive[activityIndex];
const bool allowed = AllowActive(activityType);
allowActive[activityIndex] = allowed;
allowActiveCheckTimer[activityIndex] = time(nullptr);
allowActiveCheckTimer[activityIndex] = getMSTime();
return allowed;
}

View File

@ -430,6 +430,7 @@ public:
static bool IsAssistHealOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers = false);
static bool IsAssistRangedDpsOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers = false);
bool HasAggro(Unit* unit);
bool IsMovementImpaired(Unit* unit);
static int32 GetAssistTankIndex(Player* player);
int32 GetGroupSlotIndex(Player* player);
int32 GetRangedIndex(Player* player);
@ -540,13 +541,11 @@ public:
// Checks if the bot is summoned as alt of a player
bool IsAlt();
Player* GetGroupLeader();
// Returns a semi-random (cycling) number that is fixed for each bot.
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
uint32 GetFixedBotNumber(uint32 maxNum = 100);
GrouperType GetGrouperType();
GuilderType GetGuilderType();
bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig.reactDistance);
bool HasPlayerNearby(float range = sPlayerbotAIConfig.reactDistance);
bool HasManyPlayersNearby(uint32 trigerrValue = 20, float range = sPlayerbotAIConfig.sightDistance);
bool AllowActive(ActivityType activityType);
bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false);
uint32 AutoScaleActivity(uint32 mod);
@ -614,7 +613,6 @@ private:
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = false;
inline bool IsValidUnit(const Unit* unit) const
{
return unit && unit->IsInWorld() && !unit->IsDuringRemoveFromWorld();

View File

@ -1768,13 +1768,16 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
if (bot->InBattleground())
return;
std::vector<WorldLocation> locs = sTravelMgr.GetCityLocations(bot);
if (!locs.empty())
if (bot->GetLevel() >= 10 && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100)
{
RandomTeleport(bot, locs, true);
return;
std::vector<WorldLocation> locs = sTravelMgr.GetCityLocations(bot);
if (!locs.empty())
{
RandomTeleport(bot, locs, true);
return;
}
}
locs = sTravelMgr.GetTeleportLocations(bot);
std::vector<WorldLocation> locs = sTravelMgr.GetTeleportLocations(bot);
if (!locs.empty())
{
RandomTeleport(bot, locs, false);

View File

@ -4419,6 +4419,7 @@ std::vector<std::vector<uint32>> TravelMgr::GetOptimalFlightDestinations(Player*
bot->GetTeamId());
if (!fromNode)
return validDestinations;
std::vector<WorldLocation> candidateLocations;
if (bot->GetLevel() >= 10 && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100)
candidateLocations = GetCityLocations(bot);
@ -4673,6 +4674,31 @@ void TravelMgr::PrepareDestinationCache()
if (forAlliance)
allianceFlightMasterCache[guid] = pos;
flightMastersCount++;
// Zones that have flight masters but no innkeepers — use flight master as hub
static const std::set<uint32> zonesWithoutInnkeeper = {
4, // Blasted Lands (52-57)
16, // Azshara (45-52)
28, // Western Plaguelands (50-60)
46, // Burning Steppes (51-60)
51, // Searing Gorge (45-51)
361, // Felwood (47-57)
490, // Un'Goro Crater (49-56)
2817, // Crystalsong Forest (77-80)
4197 // Wintergrasp (79-80)
};
if (zonesWithoutInnkeeper.count(areaId))
{
LevelBracket bracket = zone2LevelBracket[areaId];
WorldPosition loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
for (int i = bracket.low; i <= bracket.high; i++)
{
if (forHorde)
hordeHubsPerLevelCache[i].push_back(loc);
if (forAlliance)
allianceHubsPerLevelCache[i].push_back(loc);
}
}
}
else if (creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER)
{

View File

@ -595,11 +595,12 @@ bool PlayerbotAIConfig::Initialize()
randomBotHordeRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotHordeRatio", 50);
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
limitTalentsExpansion = sConfigMgr->GetOption<bool>("AiPlayerbot.LimitTalentsExpansion", 0);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 10);
BotActiveAloneDurationSeconds = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAloneDurationSeconds", 30);
BotActiveAloneForceWhenInRadius = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotActiveAloneForceWhenInRadius", 150);
BotActiveAloneForceWhenInZone = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInZone", 1);
BotActiveAloneForceWhenInMap = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInMap", 0);
BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 1);
BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 0);
BotActiveAloneForceWhenInGuild = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInGuild", 1);
botActiveAloneSmartScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneSmartScale", 1);
botActiveAloneSmartScaleDiffLimitfloor = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor", 50);

View File

@ -334,6 +334,7 @@ public:
bool disableDeathKnightLogin;
bool limitTalentsExpansion;
uint32 botActiveAlone;
uint32 BotActiveAloneDurationSeconds;
uint32 BotActiveAloneForceWhenInRadius;
bool BotActiveAloneForceWhenInZone;
bool BotActiveAloneForceWhenInMap;