diff --git a/.github/workflows/label_translation-pr.yml b/.github/workflows/label_translation-pr.yml index 5dbe92fc5..cadb0a46e 100644 --- a/.github/workflows/label_translation-pr.yml +++ b/.github/workflows/label_translation-pr.yml @@ -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" diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index c1c06b8b7..ccb392e1a 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -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 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 diff --git a/src/Ai/Base/Actions/GenericSpellActions.cpp b/src/Ai/Base/Actions/GenericSpellActions.cpp index 82bdb74d2..41911f62d 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.cpp +++ b/src/Ai/Base/Actions/GenericSpellActions.cpp @@ -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) diff --git a/src/Ai/Base/Actions/GenericSpellActions.h b/src/Ai/Base/Actions/GenericSpellActions.h index b15c4894a..dc0785713 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.h +++ b/src/Ai/Base/Actions/GenericSpellActions.h @@ -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: diff --git a/src/Ai/Base/Actions/MoveToTravelTargetAction.cpp b/src/Ai/Base/Actions/MoveToTravelTargetAction.cpp index f238135d9..37cf064d7 100644 --- a/src/Ai/Base/Actions/MoveToTravelTargetAction.cpp +++ b/src/Ai/Base/Actions/MoveToTravelTargetAction.cpp @@ -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(location.GetPositionX()) * 73856093 ^ + static_cast(location.GetPositionY()) * 19349663; + uint32 seed = botLow ^ static_cast(destSeed); + float angle = 2.0f * static_cast(M_PI) * static_cast(seed % 1000) / 1000.0f; + float mod = 0.5f + static_cast((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; diff --git a/src/Ai/Base/Actions/MovementActions.cpp b/src/Ai/Base/Actions/MovementActions.cpp index 4f4a110a3..85855f60d 100644 --- a/src/Ai/Base/Actions/MovementActions.cpp +++ b/src/Ai/Base/Actions/MovementActions.cpp @@ -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); } diff --git a/src/Ai/Base/Actions/WaitForAttackAction.cpp b/src/Ai/Base/Actions/WaitForAttackAction.cpp index 4fb8918c6..737e891da 100644 --- a/src/Ai/Base/Actions/WaitForAttackAction.cpp +++ b/src/Ai/Base/Actions/WaitForAttackAction.cpp @@ -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, diff --git a/src/Ai/Base/Trigger/GenericTriggers.cpp b/src/Ai/Base/Trigger/GenericTriggers.cpp index 68f5a5239..27dd5999c 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.cpp +++ b/src/Ai/Base/Trigger/GenericTriggers.cpp @@ -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); diff --git a/src/Ai/Base/Trigger/GenericTriggers.h b/src/Ai/Base/Trigger/GenericTriggers.h index f78728cd5..7a44112f3 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.h +++ b/src/Ai/Base/Trigger/GenericTriggers.h @@ -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: diff --git a/src/Ai/Base/Trigger/RtiTriggers.cpp b/src/Ai/Base/Trigger/RtiTriggers.cpp index 6158bc4d6..c7dc737cf 100644 --- a/src/Ai/Base/Trigger/RtiTriggers.cpp +++ b/src/Ai/Base/Trigger/RtiTriggers.cpp @@ -16,5 +16,5 @@ bool NoRtiTrigger::IsActive() return false; Unit* target = AI_VALUE(Unit*, "rti target"); - return target != nullptr; + return target == nullptr; } diff --git a/src/Ai/Base/TriggerContext.h b/src/Ai/Base/TriggerContext.h index 63f9be404..c77df3a31 100644 --- a/src/Ai/Base/TriggerContext.h +++ b/src/Ai/Base/TriggerContext.h @@ -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); diff --git a/src/Ai/Base/Value/PartyMemberSnaredTargetValue.cpp b/src/Ai/Base/Value/PartyMemberSnaredTargetValue.cpp new file mode 100644 index 000000000..02f726b0f --- /dev/null +++ b/src/Ai/Base/Value/PartyMemberSnaredTargetValue.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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 + +#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::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; +} diff --git a/src/Ai/Base/Value/PartyMemberSnaredTargetValue.h b/src/Ai/Base/Value/PartyMemberSnaredTargetValue.h new file mode 100644 index 000000000..d7b517e38 --- /dev/null +++ b/src/Ai/Base/Value/PartyMemberSnaredTargetValue.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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 diff --git a/src/Ai/Base/ValueContext.h b/src/Ai/Base/ValueContext.h index d1b07b88c..14cbd185c 100644 --- a/src/Ai/Base/ValueContext.h +++ b/src/Ai/Base/ValueContext.h @@ -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); } diff --git a/src/Ai/Class/Dk/Action/DKActions.cpp b/src/Ai/Class/Dk/Action/DKActions.cpp index 7788e4819..5cd475e81 100644 --- a/src/Ai/Class/Dk/Action/DKActions.cpp +++ b/src/Ai/Class/Dk/Action/DKActions.cpp @@ -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; +} diff --git a/src/Ai/Class/Dk/Action/DKActions.h b/src/Ai/Class/Dk/Action/DKActions.h index 74e066cd5..5e8bf968f 100644 --- a/src/Ai/Class/Dk/Action/DKActions.h +++ b/src/Ai/Class/Dk/Action/DKActions.h @@ -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 diff --git a/src/Ai/Class/Dk/DKAiObjectContext.cpp b/src/Ai/Class/Dk/DKAiObjectContext.cpp index ac80c7dc4..9a8271aa7 100644 --- a/src/Ai/Class/Dk/DKAiObjectContext.cpp +++ b/src/Ai/Class/Dk/DKAiObjectContext.cpp @@ -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 @@ -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) diff --git a/src/Ai/Class/Dk/Strategy/BloodDKStrategy.cpp b/src/Ai/Class/Dk/Strategy/BloodDKStrategy.cpp index 344b3dc09..9ced88ac7 100644 --- a/src/Ai/Class/Dk/Strategy/BloodDKStrategy.cpp +++ b/src/Ai/Class/Dk/Strategy/BloodDKStrategy.cpp @@ -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 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& 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& triggers) } ) ); + triggers.push_back( + new TriggerNode( + "high unholy rune", + { + NextAction("death strike", ACTION_HIGH + 1) + } + ) + ); } diff --git a/src/Ai/Class/Dk/Strategy/FrostDKStrategy.cpp b/src/Ai/Class/Dk/Strategy/FrostDKStrategy.cpp index d0b0ee203..48a61a5ff 100644 --- a/src/Ai/Class/Dk/Strategy/FrostDKStrategy.cpp +++ b/src/Ai/Class/Dk/Strategy/FrostDKStrategy.cpp @@ -91,7 +91,6 @@ std::vector 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) }; diff --git a/src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp b/src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp index d358d4370..0d3a43b79 100644 --- a/src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp +++ b/src/Ai/Class/Dk/Strategy/GenericDKNonCombatStrategy.cpp @@ -41,16 +41,10 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector& 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& triggers) diff --git a/src/Ai/Class/Dk/Strategy/GenericDKStrategy.cpp b/src/Ai/Class/Dk/Strategy/GenericDKStrategy.cpp index 61ff8c748..109341612 100644 --- a/src/Ai/Class/Dk/Strategy/GenericDKStrategy.cpp +++ b/src/Ai/Class/Dk/Strategy/GenericDKStrategy.cpp @@ -165,12 +165,6 @@ void GenericDKStrategy::InitTriggers(std::vector& 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& 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& 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) + } + ) + ); } diff --git a/src/Ai/Class/Dk/Strategy/UnholyDKStrategy.cpp b/src/Ai/Class/Dk/Strategy/UnholyDKStrategy.cpp index d94a94ec3..40a0ac041 100644 --- a/src/Ai/Class/Dk/Strategy/UnholyDKStrategy.cpp +++ b/src/Ai/Class/Dk/Strategy/UnholyDKStrategy.cpp @@ -87,6 +87,13 @@ void UnholyDKStrategy::InitTriggers(std::vector& 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& triggers) } ) ); - triggers.push_back( - new TriggerNode("no rune", - { - NextAction("empower rune weapon", ACTION_HIGH + 1) - } - ) - ); triggers.push_back( new TriggerNode( "army of the dead", diff --git a/src/Ai/Class/Dk/Trigger/DKTriggers.h b/src/Ai/Class/Dk/Trigger/DKTriggers.h index c46fbfe37..98070d85f 100644 --- a/src/Ai/Class/Dk/Trigger/DKTriggers.h +++ b/src/Ai/Class/Dk/Trigger/DKTriggers.h @@ -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 diff --git a/src/Ai/Class/Druid/Action/DruidBearActions.h b/src/Ai/Class/Druid/Action/DruidBearActions.h index caf369be3..d5354b7e6 100644 --- a/src/Ai/Class/Druid/Action/DruidBearActions.h +++ b/src/Ai/Class/Druid/Action/DruidBearActions.h @@ -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: diff --git a/src/Ai/Class/Druid/DruidAiObjectContext.cpp b/src/Ai/Class/Druid/DruidAiObjectContext.cpp index 3a638eedc..17b561d19 100644 --- a/src/Ai/Class/Druid/DruidAiObjectContext.cpp +++ b/src/Ai/Class/Druid/DruidAiObjectContext.cpp @@ -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); } diff --git a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp index 4a60fbd3e..13af635c3 100644 --- a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp @@ -212,6 +212,7 @@ void BearTankDruidStrategy::InitTriggers(std::vector& triggers) } ) ); + triggers.push_back(new TriggerNode("high aoe", {NextAction("challenging roar", ACTION_HIGH + 8)})); triggers.push_back( new TriggerNode( "lose aggro", diff --git a/src/Ai/Class/Hunter/Action/HunterActions.cpp b/src/Ai/Class/Hunter/Action/HunterActions.cpp index 18e5d5905..a49f9a10e 100644 --- a/src/Ai/Class/Hunter/Action/HunterActions.cpp +++ b/src/Ai/Class/Hunter/Action/HunterActions.cpp @@ -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* CastFreezingTrap::GetTargetValue() { return context->GetValue("cc target", "freezing trap"); } +Value* CastFreezingTrap::GetTargetValue() +{ + return context->GetValue("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* CastScareBeastCcAction::GetTargetValue() { return context->GetValue("cc target", "scare beast"); } +Value* CastScareBeastCcAction::GetTargetValue() +{ + return context->GetValue("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 CastWingClipAction::getPrerequisites() { diff --git a/src/Ai/Class/Hunter/Action/HunterActions.h b/src/Ai/Class/Hunter/Action/HunterActions.h index 8e2113525..a67f17780 100644 --- a/src/Ai/Class/Hunter/Action/HunterActions.h +++ b/src/Ai/Class/Hunter/Action/HunterActions.h @@ -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* 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* 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 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 diff --git a/src/Ai/Class/Hunter/HunterAiObjectContext.cpp b/src/Ai/Class/Hunter/HunterAiObjectContext.cpp index def1e4693..2a836ff5e 100644 --- a/src/Ai/Class/Hunter/HunterAiObjectContext.cpp +++ b/src/Ai/Class/Hunter/HunterAiObjectContext.cpp @@ -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 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 HunterAiObjectContext::sharedStrategyContexts; diff --git a/src/Ai/Class/Hunter/Strategy/BeastMasteryHunterStrategy.cpp b/src/Ai/Class/Hunter/Strategy/BeastMasteryHunterStrategy.cpp index a2c302d37..124da0b13 100644 --- a/src/Ai/Class/Hunter/Strategy/BeastMasteryHunterStrategy.cpp +++ b/src/Ai/Class/Hunter/Strategy/BeastMasteryHunterStrategy.cpp @@ -6,41 +6,9 @@ #include "BeastMasteryHunterStrategy.h" #include "Playerbots.h" -// ===== Action Node Factory ===== -class BeastMasteryHunterStrategyActionNodeFactory : public NamedObjectFactory -{ -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 ===== diff --git a/src/Ai/Class/Hunter/Strategy/GenericHunterNonCombatStrategy.cpp b/src/Ai/Class/Hunter/Strategy/GenericHunterNonCombatStrategy.cpp index 00c35dc2a..0093a490b 100644 --- a/src/Ai/Class/Hunter/Strategy/GenericHunterNonCombatStrategy.cpp +++ b/src/Ai/Class/Hunter/Strategy/GenericHunterNonCombatStrategy.cpp @@ -4,62 +4,31 @@ */ #include "GenericHunterNonCombatStrategy.h" - #include "Playerbots.h" -class GenericHunterNonCombatStrategyActionNodeFactory : public NamedObjectFactory -{ -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& 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& 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) })); } diff --git a/src/Ai/Class/Hunter/Strategy/GenericHunterStrategy.cpp b/src/Ai/Class/Hunter/Strategy/GenericHunterStrategy.cpp index 0a9e88a54..eb3907dd7 100644 --- a/src/Ai/Class/Hunter/Strategy/GenericHunterStrategy.cpp +++ b/src/Ai/Class/Hunter/Strategy/GenericHunterStrategy.cpp @@ -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& 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& 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& triggers) triggers.push_back(new TriggerNode("light aoe", { NextAction("multi-shot", 21.0f) })); } -void HunterBoostStrategy::InitTriggers(std::vector& triggers) -{ -} - void HunterCcStrategy::InitTriggers(std::vector& triggers) { triggers.push_back(new TriggerNode("scare beast", { NextAction("scare beast on cc", 23.0f) })); diff --git a/src/Ai/Class/Hunter/Strategy/GenericHunterStrategy.h b/src/Ai/Class/Hunter/Strategy/GenericHunterStrategy.h index 01aef4cec..b3a2248c3 100644 --- a/src/Ai/Class/Hunter/Strategy/GenericHunterStrategy.h +++ b/src/Ai/Class/Hunter/Strategy/GenericHunterStrategy.h @@ -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& triggers) override; -}; - class HunterCcStrategy : public Strategy { public: diff --git a/src/Ai/Class/Hunter/Strategy/HunterBuffStrategies.cpp b/src/Ai/Class/Hunter/Strategy/HunterBuffStrategies.cpp index 3601d7711..6333f184c 100644 --- a/src/Ai/Class/Hunter/Strategy/HunterBuffStrategies.cpp +++ b/src/Ai/Class/Hunter/Strategy/HunterBuffStrategies.cpp @@ -10,9 +10,21 @@ class BuffHunterStrategyActionNodeFactory : public NamedObjectFactory { 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& 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& 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& triggers) { - triggers.push_back(new TriggerNode("aspect of the pack", - { NextAction("aspect of the pack", 20.0f) })); -} - -void HunterBuffManaStrategy::InitTriggers(std::vector& 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) + } + ) + ); } diff --git a/src/Ai/Class/Hunter/Strategy/HunterBuffStrategies.h b/src/Ai/Class/Hunter/Strategy/HunterBuffStrategies.h index 7df8bc1bc..93155caeb 100644 --- a/src/Ai/Class/Hunter/Strategy/HunterBuffStrategies.h +++ b/src/Ai/Class/Hunter/Strategy/HunterBuffStrategies.h @@ -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& triggers) override; std::string const getName() override { return "bspeed"; } - void InitTriggers(std::vector& triggers) override; }; -class HunterBuffManaStrategy : public NonCombatStrategy -{ -public: - HunterBuffManaStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) {} - - std::string const getName() override { return "bmana"; } - void InitTriggers(std::vector& 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& 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& triggers) override; + std::string const getName() override { return "rnature"; } }; #endif diff --git a/src/Ai/Class/Hunter/Strategy/MarksmanshipHunterStrategy.cpp b/src/Ai/Class/Hunter/Strategy/MarksmanshipHunterStrategy.cpp index e4e8a9a4f..8289b90bf 100644 --- a/src/Ai/Class/Hunter/Strategy/MarksmanshipHunterStrategy.cpp +++ b/src/Ai/Class/Hunter/Strategy/MarksmanshipHunterStrategy.cpp @@ -6,45 +6,9 @@ #include "MarksmanshipHunterStrategy.h" #include "Playerbots.h" -// ===== Action Node Factory ===== -class MarksmanshipHunterStrategyActionNodeFactory : public NamedObjectFactory -{ -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 ===== diff --git a/src/Ai/Class/Hunter/Strategy/SurvivalHunterStrategy.cpp b/src/Ai/Class/Hunter/Strategy/SurvivalHunterStrategy.cpp index 796891a9b..cc8f2a64c 100644 --- a/src/Ai/Class/Hunter/Strategy/SurvivalHunterStrategy.cpp +++ b/src/Ai/Class/Hunter/Strategy/SurvivalHunterStrategy.cpp @@ -12,36 +12,35 @@ class SurvivalHunterStrategyActionNodeFactory : public NamedObjectFactory& 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", diff --git a/src/Ai/Class/Hunter/Trigger/HunterTriggers.cpp b/src/Ai/Class/Hunter/Trigger/HunterTriggers.cpp index 972332d1a..733960bca 100644 --- a/src/Ai/Class/Hunter/Trigger/HunterTriggers.cpp +++ b/src/Ai/Class/Hunter/Trigger/HunterTriggers.cpp @@ -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 VolleyChannelCheckTrigger::VOLLEY_SPELL_IDS = { +const std::set VolleyChannelCheckTrigger::VOLLEY_SPELL_IDS = +{ 1510, // Volley Rank 1 14294, // Volley Rank 2 14295, // Volley Rank 3 @@ -159,19 +177,12 @@ const std::set 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; } diff --git a/src/Ai/Class/Hunter/Trigger/HunterTriggers.h b/src/Ai/Class/Hunter/Trigger/HunterTriggers.h index 6bc5f3c44..7459f94bb 100644 --- a/src/Ai/Class/Hunter/Trigger/HunterTriggers.h +++ b/src/Ai/Class/Hunter/Trigger/HunterTriggers.h @@ -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) diff --git a/src/Ai/Class/Mage/Strategy/FrostMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/FrostMageStrategy.cpp index 34ed81dba..fe703f354 100644 --- a/src/Ai/Class/Mage/Strategy/FrostMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/FrostMageStrategy.cpp @@ -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 ===== diff --git a/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp b/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp index 75a63efa4..0e26c692d 100644 --- a/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp +++ b/src/Ai/Class/Mage/Strategy/GenericMageStrategy.cpp @@ -237,6 +237,14 @@ void MageBoostStrategy::InitTriggers(std::vector& triggers) void MageCcStrategy::InitTriggers(std::vector& 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& triggers) diff --git a/src/Ai/Class/Paladin/Action/PaladinActions.cpp b/src/Ai/Class/Paladin/Action/PaladinActions.cpp index 944b6e686..38e17af1e 100644 --- a/src/Ai/Class/Paladin/Action/PaladinActions.cpp +++ b/src/Ai/Class/Paladin/Action/PaladinActions.cpp @@ -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* CastTurnUndeadAction::GetTargetValue() { return context->GetValue("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* CastHandOfFreedomOnPartyAction::GetTargetValue() +{ + return context->GetValue("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"); diff --git a/src/Ai/Class/Paladin/Action/PaladinActions.h b/src/Ai/Class/Paladin/Action/PaladinActions.h index c58c3209d..75b0637a4 100644 --- a/src/Ai/Class/Paladin/Action/PaladinActions.h +++ b/src/Ai/Class/Paladin/Action/PaladinActions.h @@ -371,6 +371,19 @@ public: Value* 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* 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 diff --git a/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp b/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp index ed8f4931b..7edbf5c8f 100644 --- a/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp +++ b/src/Ai/Class/Paladin/PaladinAiObjectContext.cpp @@ -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 PaladinAiObjectContext::sharedStrategyContexts; diff --git a/src/Ai/Class/Paladin/Strategy/GenericPaladinStrategy.cpp b/src/Ai/Class/Paladin/Strategy/GenericPaladinStrategy.cpp index 49143f9ae..c4edc28fd 100644 --- a/src/Ai/Class/Paladin/Strategy/GenericPaladinStrategy.cpp +++ b/src/Ai/Class/Paladin/Strategy/GenericPaladinStrategy.cpp @@ -16,27 +16,23 @@ void GenericPaladinStrategy::InitTriggers(std::vector& 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& triggers) diff --git a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp index 71328c4dc..46a2d8a94 100644 --- a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp +++ b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.cpp @@ -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); diff --git a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h index f33b66890..d11c8024f 100644 --- a/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h +++ b/src/Ai/Class/Paladin/Trigger/PaladinTriggers.h @@ -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: diff --git a/src/Ai/Class/Paladin/Util/PaladinHelper.h b/src/Ai/Class/Paladin/Util/PaladinHelper.h new file mode 100644 index 000000000..64b88b731 --- /dev/null +++ b/src/Ai/Class/Paladin/Util/PaladinHelper.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016+ AzerothCore , 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 + +#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 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 diff --git a/src/Ai/Class/Shaman/Action/ShamanActions.cpp b/src/Ai/Class/Shaman/Action/ShamanActions.cpp index 3bb0ae86f..47634c4e0 100644 --- a/src/Ai/Class/Shaman/Action/ShamanActions.cpp +++ b/src/Ai/Class/Shaman/Action/ShamanActions.cpp @@ -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; } diff --git a/src/Ai/Class/Shaman/Action/ShamanActions.h b/src/Ai/Class/Shaman/Action/ShamanActions.h index 69f29f049..cdd661561 100644 --- a/src/Ai/Class/Shaman/Action/ShamanActions.h +++ b/src/Ai/Class/Shaman/Action/ShamanActions.h @@ -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 - 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 diff --git a/src/Ai/Class/Shaman/ShamanAiObjectContext.cpp b/src/Ai/Class/Shaman/ShamanAiObjectContext.cpp index 08ee3638d..73c41c4b6 100644 --- a/src/Ai/Class/Shaman/ShamanAiObjectContext.cpp +++ b/src/Ai/Class/Shaman/ShamanAiObjectContext.cpp @@ -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 @@ -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 ShamanAiObjectContext::sharedStrategyContexts; diff --git a/src/Ai/Class/Shaman/Strategy/ElementalShamanStrategy.cpp b/src/Ai/Class/Shaman/Strategy/ElementalShamanStrategy.cpp index c1295687c..5ef0d3f55 100644 --- a/src/Ai/Class/Shaman/Strategy/ElementalShamanStrategy.cpp +++ b/src/Ai/Class/Shaman/Strategy/ElementalShamanStrategy.cpp @@ -4,42 +4,11 @@ */ #include "ElementalShamanStrategy.h" - #include "Playerbots.h" -// ===== Action Node Factory ===== -class ElementalShamanStrategyActionNodeFactory : public NamedObjectFactory -{ -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 ===== diff --git a/src/Ai/Class/Shaman/Strategy/EnhancementShamanStrategy.cpp b/src/Ai/Class/Shaman/Strategy/EnhancementShamanStrategy.cpp index 4b7fb7159..0eb548993 100644 --- a/src/Ai/Class/Shaman/Strategy/EnhancementShamanStrategy.cpp +++ b/src/Ai/Class/Shaman/Strategy/EnhancementShamanStrategy.cpp @@ -4,7 +4,6 @@ */ #include "EnhancementShamanStrategy.h" - #include "Playerbots.h" // ===== Action Node Factory ===== @@ -13,19 +12,10 @@ class EnhancementShamanStrategyActionNodeFactory : public NamedObjectFactory& 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& 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& 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& 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 } diff --git a/src/Ai/Class/Shaman/Strategy/RestoShamanStrategy.cpp b/src/Ai/Class/Shaman/Strategy/RestoShamanStrategy.cpp index 698531a85..37f55c6c6 100644 --- a/src/Ai/Class/Shaman/Strategy/RestoShamanStrategy.cpp +++ b/src/Ai/Class/Shaman/Strategy/RestoShamanStrategy.cpp @@ -4,64 +4,11 @@ */ #include "RestoShamanStrategy.h" - #include "Playerbots.h" -// ===== Action Node Factory ===== -class RestoShamanStrategyActionNodeFactory : public NamedObjectFactory -{ -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& 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& triggers) void ShamanHealerDpsStrategy::InitTriggers(std::vector& 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) })); } diff --git a/src/Ai/Class/Shaman/Strategy/ShamanNonCombatStrategy.cpp b/src/Ai/Class/Shaman/Strategy/ShamanNonCombatStrategy.cpp index c72000539..1e10b46c9 100644 --- a/src/Ai/Class/Shaman/Strategy/ShamanNonCombatStrategy.cpp +++ b/src/Ai/Class/Shaman/Strategy/ShamanNonCombatStrategy.cpp @@ -13,45 +13,65 @@ class ShamanNonCombatStrategyActionNodeFactory : public NamedObjectFactory& 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), })); } diff --git a/src/Ai/Class/Shaman/Strategy/TotemsShamanStrategy.cpp b/src/Ai/Class/Shaman/Strategy/TotemsShamanStrategy.cpp index d00cc5e6c..c5ae222c1 100644 --- a/src/Ai/Class/Shaman/Strategy/TotemsShamanStrategy.cpp +++ b/src/Ai/Class/Shaman/Strategy/TotemsShamanStrategy.cpp @@ -75,13 +75,9 @@ void TotemOfWrathStrategy::InitTriggers(std::vector& 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& 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& 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& 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) })); } diff --git a/src/Ai/Class/Shaman/Trigger/ShamanTriggers.cpp b/src/Ai/Class/Shaman/Trigger/ShamanTriggers.cpp index c8b36135a..e7bcbfe4c 100644 --- a/src/Ai/Class/Shaman/Trigger/ShamanTriggers.cpp +++ b/src/Ai/Class/Shaman/Trigger/ShamanTriggers.cpp @@ -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) diff --git a/src/Ai/Class/Shaman/Trigger/ShamanTriggers.h b/src/Ai/Class/Shaman/Trigger/ShamanTriggers.h index 9e1a86aac..800dc8342 100644 --- a/src/Ai/Class/Shaman/Trigger/ShamanTriggers.h +++ b/src/Ai/Class/Shaman/Trigger/ShamanTriggers.h @@ -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 - 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 diff --git a/src/Ai/Class/Warrior/Action/WarriorActions.cpp b/src/Ai/Class/Warrior/Action/WarriorActions.cpp index 9f15cf767..20a42c219 100644 --- a/src/Ai/Class/Warrior/Action/WarriorActions.cpp +++ b/src/Ai/Class/Warrior/Action/WarriorActions.cpp @@ -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); diff --git a/src/Ai/Class/Warrior/Action/WarriorActions.h b/src/Ai/Class/Warrior/Action/WarriorActions.h index 7910fc0d8..da004fc70 100644 --- a/src/Ai/Class/Warrior/Action/WarriorActions.h +++ b/src/Ai/Class/Warrior/Action/WarriorActions.h @@ -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"); diff --git a/src/Ai/Class/Warrior/Strategy/GenericWarriorNonCombatStrategy.cpp b/src/Ai/Class/Warrior/Strategy/GenericWarriorNonCombatStrategy.cpp index 091aed2b8..05a6c2c63 100644 --- a/src/Ai/Class/Warrior/Strategy/GenericWarriorNonCombatStrategy.cpp +++ b/src/Ai/Class/Warrior/Strategy/GenericWarriorNonCombatStrategy.cpp @@ -7,9 +7,33 @@ #include "Playerbots.h" +class GenericWarriorNonCombatStrategyActionNodeFactory : public NamedObjectFactory +{ +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& 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) })); } diff --git a/src/Ai/Class/Warrior/Strategy/GenericWarriorNonCombatStrategy.h b/src/Ai/Class/Warrior/Strategy/GenericWarriorNonCombatStrategy.h index 276432583..160d8df8f 100644 --- a/src/Ai/Class/Warrior/Strategy/GenericWarriorNonCombatStrategy.h +++ b/src/Ai/Class/Warrior/Strategy/GenericWarriorNonCombatStrategy.h @@ -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& triggers) override; diff --git a/src/Ai/Class/Warrior/Strategy/GenericWarriorStrategy.cpp b/src/Ai/Class/Warrior/Strategy/GenericWarriorStrategy.cpp index 178984de9..18781ef31 100644 --- a/src/Ai/Class/Warrior/Strategy/GenericWarriorStrategy.cpp +++ b/src/Ai/Class/Warrior/Strategy/GenericWarriorStrategy.cpp @@ -7,9 +7,26 @@ #include "Playerbots.h" +class GenericWarriorStrategyActionNodeFactory : public NamedObjectFactory +{ +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& triggers) @@ -17,6 +34,8 @@ void GenericWarriorStrategy::InitTriggers(std::vector& 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 diff --git a/src/Ai/Class/Warrior/Trigger/WarriorTriggers.cpp b/src/Ai/Class/Warrior/Trigger/WarriorTriggers.cpp index 5aa419c92..f6e5bd234 100644 --- a/src/Ai/Class/Warrior/Trigger/WarriorTriggers.cpp +++ b/src/Ai/Class/Warrior/Trigger/WarriorTriggers.cpp @@ -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; } diff --git a/src/Ai/Class/Warrior/Trigger/WarriorTriggers.h b/src/Ai/Class/Warrior/Trigger/WarriorTriggers.h index 563d70769..8a9ed9248 100644 --- a/src/Ai/Class/Warrior/Trigger/WarriorTriggers.h +++ b/src/Ai/Class/Warrior/Trigger/WarriorTriggers.h @@ -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 diff --git a/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.cpp b/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.cpp index c7f25e331..710c15e0a 100644 --- a/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.cpp +++ b/src/Ai/Raid/Icecrown/Multiplier/RaidIccMultipliers.cpp @@ -647,7 +647,7 @@ float IccSindragosaMultiplier::GetValue(Action* action) dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) + dynamic_cast(action)) return 0.0f; } @@ -774,7 +774,7 @@ float IccLichKingAddsMultiplier::GetValue(Action* action) dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) + dynamic_cast(action) || dynamic_cast(action)) return 0.0f; } diff --git a/src/Ai/World/Rpg/Action/NewRpgAction.cpp b/src/Ai/World/Rpg/Action/NewRpgAction.cpp index 58846b949..ddd1240da 100644 --- a/src/Ai/World/Rpg/Action/NewRpgAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgAction.cpp @@ -151,7 +151,14 @@ bool NewRpgGoGrindAction::Execute(Event /*event*/) if (SearchQuestGiverAndAcceptOrReward()) return true; if (auto* data = std::get_if(&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(&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 diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp index b5156d6c1..986b2f0f5 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.cpp @@ -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(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(M_PI); float dx = x + distance * cos(angle); float dy = y + distance * sin(angle); diff --git a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h index 9cd939eb7..f17891ffc 100644 --- a/src/Ai/World/Rpg/Action/NewRpgBaseAction.h +++ b/src/Ai/World/Rpg/Action/NewRpgBaseAction.h @@ -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 diff --git a/src/Bot/Engine/PlayerbotAIBase.cpp b/src/Bot/Engine/PlayerbotAIBase.cpp index cf4ad172c..46e8c0bf5 100644 --- a/src/Bot/Engine/PlayerbotAIBase.cpp +++ b/src/Bot/Engine/PlayerbotAIBase.cpp @@ -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 (0–200 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; } diff --git a/src/Bot/Engine/PlayerbotAIBase.h b/src/Bot/Engine/PlayerbotAIBase.h index d0e0b775b..2d6ab31ce 100644 --- a/src/Bot/Engine/PlayerbotAIBase.h +++ b/src/Bot/Engine/PlayerbotAIBase.h @@ -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(); diff --git a/src/Bot/Factory/AiFactory.cpp b/src/Bot/Factory/AiFactory.cpp index 84b3a7dc5..a821886f9 100644 --- a/src/Bot/Factory/AiFactory.cpp +++ b/src/Bot/Factory/AiFactory.cpp @@ -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) diff --git a/src/Bot/Factory/PlayerbotFactory.cpp b/src/Bot/Factory/PlayerbotFactory.cpp index 506ad9154..11f301feb 100644 --- a/src/Bot/Factory/PlayerbotFactory.cpp +++ b/src/Bot/Factory/PlayerbotFactory.cpp @@ -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) diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 4a746009a..1a74b8b2f 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -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(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(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(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 + 0–499ms per-bot offset = 4500–4999ms, 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; } diff --git a/src/Bot/PlayerbotAI.h b/src/Bot/PlayerbotAI.h index 5d64bf159..cfa27ed4e 100644 --- a/src/Bot/PlayerbotAI.h +++ b/src/Bot/PlayerbotAI.h @@ -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 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(); diff --git a/src/Bot/RandomPlayerbotMgr.cpp b/src/Bot/RandomPlayerbotMgr.cpp index 3fea369a5..a772136e7 100644 --- a/src/Bot/RandomPlayerbotMgr.cpp +++ b/src/Bot/RandomPlayerbotMgr.cpp @@ -1768,13 +1768,16 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot) if (bot->InBattleground()) return; - std::vector locs = sTravelMgr.GetCityLocations(bot); - if (!locs.empty()) + if (bot->GetLevel() >= 10 && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100) { - RandomTeleport(bot, locs, true); - return; + std::vector locs = sTravelMgr.GetCityLocations(bot); + if (!locs.empty()) + { + RandomTeleport(bot, locs, true); + return; + } } - locs = sTravelMgr.GetTeleportLocations(bot); + std::vector locs = sTravelMgr.GetTeleportLocations(bot); if (!locs.empty()) { RandomTeleport(bot, locs, false); diff --git a/src/Mgr/Travel/TravelMgr.cpp b/src/Mgr/Travel/TravelMgr.cpp index 7a5ac4f2e..1868bc2e3 100644 --- a/src/Mgr/Travel/TravelMgr.cpp +++ b/src/Mgr/Travel/TravelMgr.cpp @@ -4419,6 +4419,7 @@ std::vector> TravelMgr::GetOptimalFlightDestinations(Player* bot->GetTeamId()); if (!fromNode) return validDestinations; + std::vector 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 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) { diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 6a8d60129..f150f7af1 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -595,11 +595,12 @@ bool PlayerbotAIConfig::Initialize() randomBotHordeRatio = sConfigMgr->GetOption("AiPlayerbot.RandomBotHordeRatio", 50); disableDeathKnightLogin = sConfigMgr->GetOption("AiPlayerbot.DisableDeathKnightLogin", 0); limitTalentsExpansion = sConfigMgr->GetOption("AiPlayerbot.LimitTalentsExpansion", 0); - botActiveAlone = sConfigMgr->GetOption("AiPlayerbot.BotActiveAlone", 100); + botActiveAlone = sConfigMgr->GetOption("AiPlayerbot.BotActiveAlone", 10); + BotActiveAloneDurationSeconds = sConfigMgr->GetOption("AiPlayerbot.BotActiveAloneDurationSeconds", 30); BotActiveAloneForceWhenInRadius = sConfigMgr->GetOption("AiPlayerbot.BotActiveAloneForceWhenInRadius", 150); BotActiveAloneForceWhenInZone = sConfigMgr->GetOption("AiPlayerbot.BotActiveAloneForceWhenInZone", 1); BotActiveAloneForceWhenInMap = sConfigMgr->GetOption("AiPlayerbot.BotActiveAloneForceWhenInMap", 0); - BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 1); + BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 0); BotActiveAloneForceWhenInGuild = sConfigMgr->GetOption("AiPlayerbot.BotActiveAloneForceWhenInGuild", 1); botActiveAloneSmartScale = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScale", 1); botActiveAloneSmartScaleDiffLimitfloor = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor", 50); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 7b9b2fe81..7b6c1eb6f 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -334,6 +334,7 @@ public: bool disableDeathKnightLogin; bool limitTalentsExpansion; uint32 botActiveAlone; + uint32 BotActiveAloneDurationSeconds; uint32 BotActiveAloneForceWhenInRadius; bool BotActiveAloneForceWhenInZone; bool BotActiveAloneForceWhenInMap;