diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index c1d740774..6ea92b2d6 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1786,18 +1786,18 @@ AiPlayerbot.PremadeSpecLink.9.5.80 = -2032003311302-05230015220331351005031051 AiPlayerbot.PremadeSpecName.11.0 = balance pve AiPlayerbot.PremadeSpecGlyph.11.0 = 40916,43331,40921,43335,44922,40919 -AiPlayerbot.PremadeSpecLink.11.0.60 = 5022203105331003213005301231 -AiPlayerbot.PremadeSpecLink.11.0.80 = 5032203105331303213305301231--205003012 +AiPlayerbot.PremadeSpecLink.11.0.60 = 5032003125031003213304301231 +AiPlayerbot.PremadeSpecLink.11.0.80 = 5032003125331303213305301231--205003012 AiPlayerbot.PremadeSpecName.11.1 = bear pve AiPlayerbot.PremadeSpecGlyph.11.1 = 40897,43331,46372,43335,43332,40899 -AiPlayerbot.PremadeSpecLink.11.1.60 = -500232130322110353100301310501 +AiPlayerbot.PremadeSpecLink.11.1.60 = -503232132322010303120300013501 AiPlayerbot.PremadeSpecLink.11.1.80 = -503232132322010353120303013511-20350001 AiPlayerbot.PremadeSpecName.11.2 = resto pve -AiPlayerbot.PremadeSpecGlyph.11.2 = 40913,43331,40906,43335,43674,45602 -AiPlayerbot.PremadeSpecLink.11.2.60 = --230033312031500531050113051 -AiPlayerbot.PremadeSpecLink.11.2.80 = 05320131003--230023312131500531050313051 +AiPlayerbot.PremadeSpecGlyph.11.2 = 40906,43331,45602,43335,43674,45603 +AiPlayerbot.PremadeSpecLink.11.2.60 = --230033312031502331050313031 +AiPlayerbot.PremadeSpecLink.11.2.80 = 05320031--230033312031502431053313051 AiPlayerbot.PremadeSpecName.11.3 = cat pve -AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43335,43674,45604 +AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43674,43335,45604 AiPlayerbot.PremadeSpecLink.11.3.60 = -552202032322010053100030310501 AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053120030310511-203503012 AiPlayerbot.PremadeSpecName.11.4 = balance pvp diff --git a/src/Ai/Base/Actions/GenericActions.cpp b/src/Ai/Base/Actions/GenericActions.cpp index a4873d86a..354cb456e 100644 --- a/src/Ai/Base/Actions/GenericActions.cpp +++ b/src/Ai/Base/Actions/GenericActions.cpp @@ -50,7 +50,10 @@ bool MeleeAction::isUseful() if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true)) return false; - return true; + // Do not start autoattack while prowled — let opener spells break stealth intentionally. + // Future rogue stealth implementation should use this instead: + // return !(botAI->HasAura("stealth", bot) || botAI->HasAura("prowl", bot)); + return !botAI->HasAura("prowl", bot); } bool TogglePetSpellAutoCastAction::Execute(Event /*event*/) diff --git a/src/Ai/Base/Trigger/GenericTriggers.cpp b/src/Ai/Base/Trigger/GenericTriggers.cpp index 27dd5999c..2035e1c29 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.cpp +++ b/src/Ai/Base/Trigger/GenericTriggers.cpp @@ -34,6 +34,11 @@ bool MediumManaTrigger::IsActive() AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.mediumMana; } +bool LowEnergyTrigger::IsActive() +{ + return AI_VALUE2(uint8, "energy", "self target") < threshold; +} + bool NoPetTrigger::IsActive() { return (bot->GetMinionGUID().IsEmpty()) && (!AI_VALUE(Unit*, "pet target")) && (!bot->GetGuardianPet()) && diff --git a/src/Ai/Base/Trigger/GenericTriggers.h b/src/Ai/Base/Trigger/GenericTriggers.h index 7a44112f3..a3931c246 100644 --- a/src/Ai/Base/Trigger/GenericTriggers.h +++ b/src/Ai/Base/Trigger/GenericTriggers.h @@ -550,6 +550,17 @@ public: bool IsActive() override; }; +class LowEnergyTrigger : public Trigger +{ +public: + LowEnergyTrigger(PlayerbotAI* botAI, uint8 threshold = 30) : Trigger(botAI, "low energy"), threshold(threshold) {} + + bool IsActive() override; + +private: + uint8 threshold; +}; + BEGIN_TRIGGER(PanicTrigger, Trigger) // cppcheck-suppress unknownMacro std::string const getName() override { return "panic"; } END_TRIGGER() diff --git a/src/Ai/Base/Trigger/HealthTriggers.cpp b/src/Ai/Base/Trigger/HealthTriggers.cpp index a2b36962d..746198acf 100644 --- a/src/Ai/Base/Trigger/HealthTriggers.cpp +++ b/src/Ai/Base/Trigger/HealthTriggers.cpp @@ -22,6 +22,15 @@ bool DeadTrigger::IsActive() { return AI_VALUE2(bool, "dead", GetTargetName()); bool AoeHealTrigger::IsActive() { return AI_VALUE2(uint8, "aoe heal", type) >= count; } +bool HealerLowManaTrigger::IsActive() +{ + Unit* target = GetTarget(); + if (!target) + return false; + + return target->GetPowerPct(POWER_MANA) < sPlayerbotAIConfig.lowMana; +} + bool AoeInGroupTrigger::IsActive() { int32 member = botAI->GetNearGroupMemberCount(); diff --git a/src/Ai/Base/Trigger/HealthTriggers.h b/src/Ai/Base/Trigger/HealthTriggers.h index 0fbd403d7..14e68c7e3 100644 --- a/src/Ai/Base/Trigger/HealthTriggers.h +++ b/src/Ai/Base/Trigger/HealthTriggers.h @@ -143,6 +143,15 @@ public: TargetCriticalHealthTrigger(PlayerbotAI* botAI) : TargetLowHealthTrigger(botAI, 20) {} }; +class HealerLowManaTrigger : public Trigger +{ +public: + HealerLowManaTrigger(PlayerbotAI* botAI) : Trigger(botAI, "healer low mana") {} + + std::string const GetTargetName() override { return "healer low mana"; } + bool IsActive() override; +}; + class PartyMemberDeadTrigger : public Trigger { public: diff --git a/src/Ai/Base/Trigger/RtiTriggers.cpp b/src/Ai/Base/Trigger/RtiTriggers.cpp index c7dc737cf..ba934c5d7 100644 --- a/src/Ai/Base/Trigger/RtiTriggers.cpp +++ b/src/Ai/Base/Trigger/RtiTriggers.cpp @@ -18,3 +18,19 @@ bool NoRtiTrigger::IsActive() Unit* target = AI_VALUE(Unit*, "rti target"); return target == nullptr; } + +// Fires when the RTI CC target should be crowd controlled by this spell. +// Standard path: the target is already in the attackers list and "cc target" matches the RTI +// mark — delegates to HasCcTargetTrigger to confirm no one else is already CCing it. +bool RtiCcTrigger::IsActive() +{ + Unit* rtiCcTarget = AI_VALUE(Unit*, "rti cc target"); + if (!rtiCcTarget) + return false; + + Unit* ccTarget = AI_VALUE2(Unit*, "cc target", getName()); + if (ccTarget && ccTarget == rtiCcTarget) + return HasCcTargetTrigger::IsActive(); + + return botAI->CanCastSpell(getName(), rtiCcTarget); +} diff --git a/src/Ai/Base/Trigger/RtiTriggers.h b/src/Ai/Base/Trigger/RtiTriggers.h index a8ecab4d9..9ff128ec2 100644 --- a/src/Ai/Base/Trigger/RtiTriggers.h +++ b/src/Ai/Base/Trigger/RtiTriggers.h @@ -6,6 +6,7 @@ #ifndef _PLAYERBOT_RTITRIGGERS_H #define _PLAYERBOT_RTITRIGGERS_H +#include "GenericTriggers.h" #include "Trigger.h" class PlayerbotAI; @@ -18,4 +19,12 @@ public: bool IsActive() override; }; +class RtiCcTrigger : public HasCcTargetTrigger +{ +public: + RtiCcTrigger(PlayerbotAI* botAI, std::string const name) : HasCcTargetTrigger(botAI, name) {} + + bool IsActive() override; +}; + #endif diff --git a/src/Ai/Base/TriggerContext.h b/src/Ai/Base/TriggerContext.h index 54edbb017..d440e5b5f 100644 --- a/src/Ai/Base/TriggerContext.h +++ b/src/Ai/Base/TriggerContext.h @@ -51,6 +51,7 @@ public: creators["low mana"] = &TriggerContext::LowMana; creators["medium mana"] = &TriggerContext::MediumMana; + creators["low energy"] = &TriggerContext::LowEnergy; creators["high mana"] = &TriggerContext::HighMana; creators["almost full mana"] = &TriggerContext::AlmostFullMana; creators["enough mana"] = &TriggerContext::EnoughMana; @@ -59,6 +60,7 @@ public: creators["party member low health"] = &TriggerContext::PartyMemberLowHealth; creators["party member medium health"] = &TriggerContext::PartyMemberMediumHealth; creators["party member almost full health"] = &TriggerContext::PartyMemberAlmostFullHealth; + creators["healer low mana"] = &TriggerContext::HealerLowMana; creators["generic boost"] = &TriggerContext::generic_boost; creators["loss of control"] = &TriggerContext::loss_of_control; @@ -312,6 +314,7 @@ private: static Trigger* TargetCriticalHealth(PlayerbotAI* botAI) { return new TargetCriticalHealthTrigger(botAI); } static Trigger* LowMana(PlayerbotAI* botAI) { return new LowManaTrigger(botAI); } static Trigger* MediumMana(PlayerbotAI* botAI) { return new MediumManaTrigger(botAI); } + static Trigger* LowEnergy(PlayerbotAI* botAI) { return new LowEnergyTrigger(botAI); } static Trigger* HighMana(PlayerbotAI* botAI) { return new HighManaTrigger(botAI); } static Trigger* AlmostFullMana(PlayerbotAI* botAI) { return new AlmostFullManaTrigger(botAI); } static Trigger* EnoughMana(PlayerbotAI* botAI) { return new EnoughManaTrigger(botAI); } @@ -383,6 +386,7 @@ private: { return new PartyMemberCriticalHealthTrigger(botAI); } + static Trigger* HealerLowMana(PlayerbotAI* botAI) { return new HealerLowManaTrigger(botAI); } static Trigger* protect_party_member(PlayerbotAI* botAI) { return new ProtectPartyMemberTrigger(botAI); } static Trigger* no_pet(PlayerbotAI* botAI) { return new NoPetTrigger(botAI); } static Trigger* has_pet(PlayerbotAI* botAI) { return new HasPetTrigger(botAI); } diff --git a/src/Ai/Base/Value/PartyMemberToHeal.cpp b/src/Ai/Base/Value/PartyMemberToHeal.cpp index 9845bc119..80621775a 100644 --- a/src/Ai/Base/Value/PartyMemberToHeal.cpp +++ b/src/Ai/Base/Value/PartyMemberToHeal.cpp @@ -135,6 +135,32 @@ bool PartyMemberToHeal::Check(Unit* player) bot->GetDistance2d(player) < sPlayerbotAIConfig.healDistance * 2 && bot->IsWithinLOSInMap(player); } +Unit* HealerLowMana::Calculate() +{ + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + MinValueCalculator calc(100); + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->GetSource(); + if (!player || player == bot) + continue; + if (player->IsGameMaster() || !player->IsAlive()) + continue; + if (!botAI->IsHeal(player)) + continue; + + float mana = player->GetPowerPct(POWER_MANA); + if (mana < calc.minValue) + calc.probe(mana, player); + } + + return (Unit*)calc.param; +} + Unit* PartyMemberToProtect::Calculate() { return nullptr; diff --git a/src/Ai/Base/Value/PartyMemberToHeal.h b/src/Ai/Base/Value/PartyMemberToHeal.h index f86035c09..6a255b724 100644 --- a/src/Ai/Base/Value/PartyMemberToHeal.h +++ b/src/Ai/Base/Value/PartyMemberToHeal.h @@ -37,4 +37,13 @@ protected: Unit* Calculate() override; }; +class HealerLowMana : public PartyMemberValue +{ +public: + HealerLowMana(PlayerbotAI* botAI) : PartyMemberValue(botAI, "healer low mana") {} + +protected: + Unit* Calculate() override; +}; + #endif diff --git a/src/Ai/Base/ValueContext.h b/src/Ai/Base/ValueContext.h index bac5fd835..4070da80b 100644 --- a/src/Ai/Base/ValueContext.h +++ b/src/Ai/Base/ValueContext.h @@ -132,6 +132,7 @@ public: creators["attacker without aura"] = &ValueContext::attacker_without_aura; creators["melee attacker without aura"] = &ValueContext::melee_attacker_without_aura; creators["party member to heal"] = &ValueContext::party_member_to_heal; + creators["healer low mana"] = &ValueContext::healer_low_mana; creators["party member to resurrect"] = &ValueContext::party_member_to_resurrect; creators["current target"] = &ValueContext::current_target; creators["self target"] = &ValueContext::self_target; @@ -451,6 +452,7 @@ private: return new MeleeAttackerWithoutAuraTargetValue(botAI); } static UntypedValue* party_member_to_heal(PlayerbotAI* botAI) { return new PartyMemberToHeal(botAI); } + static UntypedValue* healer_low_mana(PlayerbotAI* botAI) { return new HealerLowMana(botAI); } 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); } diff --git a/src/Ai/Class/Druid/Action/DruidActions.cpp b/src/Ai/Class/Druid/Action/DruidActions.cpp index c01cc4342..e32a5ea04 100644 --- a/src/Ai/Class/Druid/Action/DruidActions.cpp +++ b/src/Ai/Class/Druid/Action/DruidActions.cpp @@ -11,6 +11,9 @@ #include "AoeValues.h" #include "TargetValue.h" +constexpr uint32 SPELL_ECLIPSE_SOLAR = 48517; +constexpr uint32 SPELL_ECLIPSE_LUNAR = 48518; + namespace { bool PrepareThornsTarget(PlayerbotAI* botAI, Unit* target) @@ -64,16 +67,89 @@ bool CastThornsOnMainTankAction::Execute(Event event) return PrepareThornsTarget(botAI, GetTarget()) && BuffOnMainTankAction::Execute(event); } -Value* CastEntanglingRootsCcAction::GetTargetValue() +bool CastWrathAction::isUseful() { - return context->GetValue("cc target", "entangling roots"); + time_t now = time(nullptr); + time_t solarTime = context->GetValue("eclipse solar proc time")->Get(); + time_t lunarTime = context->GetValue("eclipse lunar proc time")->Get(); + + // --- Update Solar Eclipse tracking --- + // Wrath is selected during Solar Eclipse (eclipse trigger at 20.0f), so we reliably see it here. + if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && !solarTime) + context->GetValue("eclipse solar proc time")->Set(now); + // Lunar procced — Solar fishing window is over, new cycle begins + if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && solarTime) + context->GetValue("eclipse solar proc time")->Set(0); + // 30 s cooldown window expired + if (solarTime && (now - solarTime) >= 30) + context->GetValue("eclipse solar proc time")->Set(0); + + // --- Update Lunar Eclipse tracking (belt-and-suspenders in case Starfire isn't evaluated) --- + if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && !lunarTime) + context->GetValue("eclipse lunar proc time")->Set(now); + // Solar procced — Lunar fishing window is over + if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && lunarTime) + context->GetValue("eclipse lunar proc time")->Set(0); + if (lunarTime && (now - lunarTime) >= 30) + context->GetValue("eclipse lunar proc time")->Set(0); + + // Block Wrath while in Lunar Eclipse / post-Lunar fishing window + if (context->GetValue("eclipse lunar proc time")->Get()) + return false; + + return CastSpellAction::isUseful(); } -bool CastEntanglingRootsCcAction::Execute(Event /*event*/) { return botAI->CastSpell("entangling roots", GetTarget()); } +bool CastStarfireAction::isUseful() +{ + time_t now = time(nullptr); + time_t solarTime = context->GetValue("eclipse solar proc time")->Get(); + time_t lunarTime = context->GetValue("eclipse lunar proc time")->Get(); -Value* CastHibernateCcAction::GetTargetValue() { return context->GetValue("cc target", "hibernate"); } + // --- Update Lunar Eclipse tracking --- + // Starfire is selected during Lunar Eclipse (eclipse trigger at 20.0f), so we reliably see it here. + if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && !lunarTime) + context->GetValue("eclipse lunar proc time")->Set(now); + // Solar procced — Lunar fishing window is over, new cycle begins + if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && lunarTime) + context->GetValue("eclipse lunar proc time")->Set(0); + // 30 s cooldown window expired + if (lunarTime && (now - lunarTime) >= 30) + context->GetValue("eclipse lunar proc time")->Set(0); + + // --- Update Solar Eclipse tracking (belt-and-suspenders in case Wrath isn't evaluated) --- + if (bot->HasAura(SPELL_ECLIPSE_SOLAR) && !solarTime) + context->GetValue("eclipse solar proc time")->Set(now); + // Lunar procced — Solar fishing window is over + if (bot->HasAura(SPELL_ECLIPSE_LUNAR) && solarTime) + context->GetValue("eclipse solar proc time")->Set(0); + if (solarTime && (now - solarTime) >= 30) + context->GetValue("eclipse solar proc time")->Set(0); + + // Block Starfire while in Solar Eclipse / post-Solar fishing window + if (context->GetValue("eclipse solar proc time")->Get()) + return false; + + return CastSpellAction::isUseful(); +} + +Value* CastEntanglingRootsCcAction::GetTargetValue() +{ + return context->GetValue("rti cc target"); +} + +Value* CastHibernateCcAction::GetTargetValue() { return context->GetValue("rti cc target"); } + +Value* CastCycloneCcAction::GetTargetValue() { return context->GetValue("rti cc target"); } + +bool CastTyphoonAction::isUseful() +{ + bool facingTarget = AI_VALUE2(bool, "facing", "current target"); + bool targetClose = ServerFacade::instance().IsDistanceLessOrEqualThan( + AI_VALUE2(float, "distance", GetTargetName()), 15.f); + return facingTarget && targetClose; +} -bool CastHibernateCcAction::Execute(Event /*event*/) { return botAI->CastSpell("hibernate", GetTarget()); } bool CastStarfallAction::isUseful() { if (!CastSpellAction::isUseful()) @@ -89,12 +165,26 @@ bool CastStarfallAction::isUseful() return false; } - // Avoid single-target usage on initial pull - uint8 aoeCount = *context->GetValue("aoe count"); - if (aoeCount < 2) + // Suppress if any unengaged hostile unit is within 40 yards — Starfall's 36-yard radius would pull them. + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + GuidVector const& nearbyNpcs = AI_VALUE(GuidVector, "possible targets"); + for (ObjectGuid const& guid : nearbyNpcs) { - Unit* target = context->GetValue("current target")->Get(); - if (!target || (!botAI->HasAura("moonfire", target) && !botAI->HasAura("insect swarm", target))) + Unit* unit = botAI->GetUnit(guid); + // Standard null/world-state guard before touching the unit. + if (!unit || !unit->IsAlive() || !unit->IsInWorld()) + continue; + // Already our target — its in-combat flag covers it. + if (unit == currentTarget) + continue; + // Safety net for any hostile-faction trigger creature that carries NON_ATTACKABLE flags. + if (!bot->IsValidAttackTarget(unit)) + continue; + // Outside Starfall's actual radius; no pull risk. + if (ServerFacade::instance().GetDistance2d(bot, unit) > 40.0f) + continue; + // Unengaged mob within range — casting would pull it. + if (!unit->IsInCombat()) return false; } @@ -119,6 +209,24 @@ bool CastRebirthAction::isUseful() AI_VALUE2(float, "distance", GetTargetName()) <= sPlayerbotAIConfig.spellDistance; } +bool CastInnervateOnHealerAction::isPossible() +{ + Unit* target = GetTarget(); + if (!target || !target->IsInWorld()) + return false; + + if (botAI->HasAura("innervate", target)) + return false; + + uint32 spellId = AI_VALUE2(uint32, "spell id", "innervate"); + return spellId && !bot->HasSpellCooldown(spellId); +} + +std::vector CastInnervateOnHealerAction::getPrerequisites() +{ + return { NextAction("caster form") }; +} + Unit* CastRejuvenationOnNotFullAction::GetTarget() { Group* group = bot->GetGroup(); @@ -149,3 +257,63 @@ bool CastRejuvenationOnNotFullAction::isUseful() { return GetTarget(); } + +// --- Blanket HoT actions --- + +Unit* CastBlanketHotAction::GetBlanketTarget(std::string const& auraName) +{ + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + auto eligible = [&](Player* member) -> bool + { + return member && member->IsAlive() && + !member->IsGameMaster() && + bot->GetDistance2d(member) <= sPlayerbotAIConfig.spellDistance && + !botAI->HasAura(auraName, member, false, true); + }; + + Player* firstMelee = nullptr; + Player* firstRanged = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!eligible(member)) + continue; + + if (PlayerbotAI::IsTank(member)) + return member; + else if (!firstMelee && PlayerbotAI::IsMelee(member) && !PlayerbotAI::IsTank(member)) + firstMelee = member; + else if (!firstRanged && PlayerbotAI::IsRanged(member)) + firstRanged = member; + + if (firstMelee && firstRanged) + break; + } + + if (firstMelee) return firstMelee; + return firstRanged; +} + +Unit* CastRejuvenationBlanketAction::GetTarget() +{ + return GetBlanketTarget("rejuvenation"); +} + +bool CastRejuvenationBlanketAction::isUseful() +{ + return GetTarget() != nullptr; +} + +Unit* CastWildGrowthBlanketAction::GetTarget() +{ + return GetBlanketTarget("wild growth"); +} + +bool CastWildGrowthBlanketAction::isUseful() +{ + return GetTarget() != nullptr; +} diff --git a/src/Ai/Class/Druid/Action/DruidActions.h b/src/Ai/Class/Druid/Action/DruidActions.h index 7e02a985f..e386ff05d 100644 --- a/src/Ai/Class/Druid/Action/DruidActions.h +++ b/src/Ai/Class/Druid/Action/DruidActions.h @@ -8,6 +8,7 @@ #include "GenericSpellActions.h" #include "SharedDefines.h" +#include "Value.h" class PlayerbotAI; class Unit; @@ -64,7 +65,7 @@ class CastHealingTouchOnPartyAction : public HealPartyMemberAction { public: CastHealingTouchOnPartyAction(PlayerbotAI* botAI) - : HealPartyMemberAction(botAI, "healing touch", 50.0f, HealingManaEfficiency::LOW) + : HealPartyMemberAction(botAI, "healing touch", 50.0f, HealingManaEfficiency::MEDIUM) { } }; @@ -142,16 +143,11 @@ public: bool isUseful() override; }; -class CastOmenOfClarityAction : public CastBuffSpellAction -{ -public: - CastOmenOfClarityAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "omen of clarity") {} -}; - class CastWrathAction : public CastSpellAction { public: CastWrathAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "wrath") {} + bool isUseful() override; }; class CastStarfallAction : public CastSpellAction @@ -169,6 +165,14 @@ public: ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } }; +class CastTyphoonAction : public CastSpellAction +{ +public: + CastTyphoonAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "typhoon") {} + ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } + bool isUseful() override; +}; + class CastMoonfireAction : public CastDebuffSpellAction { public: @@ -185,6 +189,7 @@ class CastStarfireAction : public CastSpellAction { public: CastStarfireAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "starfire") {} + bool isUseful() override; }; class CastEntanglingRootsAction : public CastSpellAction @@ -193,12 +198,11 @@ public: CastEntanglingRootsAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "entangling roots") {} }; -class CastEntanglingRootsCcAction : public CastSpellAction +class CastEntanglingRootsCcAction : public CastCrowdControlSpellAction { public: - CastEntanglingRootsCcAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "entangling roots on cc") {} + CastEntanglingRootsCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "entangling roots") {} Value* GetTargetValue() override; - bool Execute(Event event) override; }; class CastHibernateAction : public CastSpellAction @@ -207,12 +211,18 @@ public: CastHibernateAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hibernate") {} }; -class CastHibernateCcAction : public CastSpellAction +class CastHibernateCcAction : public CastCrowdControlSpellAction { public: - CastHibernateCcAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hibernate on cc") {} + CastHibernateCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "hibernate") {} + Value* GetTargetValue() override; +}; + +class CastCycloneCcAction : public CastCrowdControlSpellAction +{ +public: + CastCycloneCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "cyclone") {} Value* GetTargetValue() override; - bool Execute(Event event) override; }; class CastNaturesGraspAction : public CastBuffSpellAction @@ -264,6 +274,16 @@ public: std::string const GetTargetName() override { return "self target"; } }; +class CastInnervateOnHealerAction : public CastSpellAction +{ +public: + CastInnervateOnHealerAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "innervate") {} + + std::string const GetTargetName() override { return "healer low mana"; } + bool isPossible() override; + std::vector getPrerequisites() override; +}; + class CastTranquilityAction : public CastAoeHealSpellAction { public: @@ -312,13 +332,15 @@ public: class CastInsectSwarmOnAttackerAction : public CastDebuffSpellOnAttackerAction { public: - CastInsectSwarmOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "insect swarm") {} + CastInsectSwarmOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "insect swarm", true, 0.0f) {} + bool isUseful() override { return CastAuraSpellAction::isUseful(); } }; class CastMoonfireOnAttackerAction : public CastDebuffSpellOnAttackerAction { public: - CastMoonfireOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "moonfire") {} + CastMoonfireOnAttackerAction(PlayerbotAI* ai) : CastDebuffSpellOnAttackerAction(ai, "moonfire", true, 0.0f) {} + bool isUseful() override { return CastAuraSpellAction::isUseful(); } }; class CastEnrageAction : public CastBuffSpellAction @@ -344,4 +366,48 @@ public: CastForceOfNatureAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "force of nature") {} }; +// Base for blanket HoT actions. Provides GetBlanketTarget() as a member so +// subclasses can use AI_VALUE and the standard context machinery. +class CastBlanketHotAction : public CastSpellAction +{ +public: + CastBlanketHotAction(PlayerbotAI* ai, std::string const& spell) : CastSpellAction(ai, spell) + { + range = botAI->GetRange("heal"); + } + +protected: + Unit* GetBlanketTarget(std::string const& auraName); +}; + +class CastRejuvenationBlanketAction : public CastBlanketHotAction +{ +public: + CastRejuvenationBlanketAction(PlayerbotAI* ai) : CastBlanketHotAction(ai, "rejuvenation") {} + bool isUseful() override; + Unit* GetTarget() override; + std::string const getName() override { return "rejuvenation blanket"; } +}; + +class CastWildGrowthBlanketAction : public CastBlanketHotAction +{ +public: + CastWildGrowthBlanketAction(PlayerbotAI* ai) : CastBlanketHotAction(ai, "wild growth") {} + bool isUseful() override; + Unit* GetTarget() override; + std::string const getName() override { return "wild growth blanket"; } +}; + +class EclipseSolarProcTimeValue : public ManualSetValue +{ +public: + EclipseSolarProcTimeValue(PlayerbotAI* botAI) : ManualSetValue(botAI, 0) {} +}; + +class EclipseLunarProcTimeValue : public ManualSetValue +{ +public: + EclipseLunarProcTimeValue(PlayerbotAI* botAI) : ManualSetValue(botAI, 0) {} +}; + #endif diff --git a/src/Ai/Class/Druid/Action/DruidCatActions.h b/src/Ai/Class/Druid/Action/DruidCatActions.h index f6d8f2320..1fdd9aba7 100644 --- a/src/Ai/Class/Druid/Action/DruidCatActions.h +++ b/src/Ai/Class/Druid/Action/DruidCatActions.h @@ -9,12 +9,23 @@ #include "GenericSpellActions.h" #include "ReachTargetActions.h" +constexpr uint32 SPELL_POUNCE_RANK_1 = 9005; +constexpr uint32 SPELL_RAVAGE_RANK_1 = 6785; + class PlayerbotAI; class CastFeralChargeCatAction : public CastReachTargetSpellAction { public: CastFeralChargeCatAction(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, "feral charge - cat", 1.5f) {} + + bool isUseful() override + { + if (botAI->HasAura("prowl", bot)) + return false; + + return CastReachTargetSpellAction::isUseful(); + } }; class CastCowerAction : public CastBuffSpellAction @@ -48,28 +59,47 @@ public: CastRakeAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "rake", true, 6.0f) {} }; -class CastRakeOnMeleeAttackersAction : public CastDebuffSpellOnMeleeAttackerAction -{ -public: - CastRakeOnMeleeAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnMeleeAttackerAction(botAI, "rake", true, 6.0f) {} -}; - class CastClawAction : public CastMeleeSpellAction { public: CastClawAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "claw") {} + + bool isUseful() override + { + // Block Claw once Pounce is learned; Claw remains available as the stealth opener before then. + if (botAI->HasAura("prowl", bot) && bot->HasSpell(SPELL_POUNCE_RANK_1)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastMangleCatAction : public CastMeleeSpellAction { public: CastMangleCatAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "mangle (cat)") {} + + bool isUseful() override + { + if (botAI->HasAura("prowl", bot)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastSwipeCatAction : public CastMeleeSpellAction { public: CastSwipeCatAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "swipe (cat)") {} + + bool isUseful() override + { + if (botAI->HasAura("prowl", bot)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastFerociousBiteAction : public CastMeleeSpellAction @@ -78,6 +108,21 @@ public: CastFerociousBiteAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ferocious bite") {} }; +class CastMaimAction : public CastMeleeSpellAction +{ +public: + CastMaimAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "maim") {} + + bool isUseful() override + { + Unit* target = GetTarget(); + if (!target || !target->ToPlayer()) + return false; + + return CastMeleeSpellAction::isUseful(); + } +}; + class CastRipAction : public CastMeleeDebuffSpellAction { public: @@ -88,6 +133,14 @@ class CastShredAction : public CastMeleeSpellAction { public: CastShredAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "shred") {} + + bool isUseful() override + { + if (botAI->HasAura("prowl", bot) && bot->HasSpell(SPELL_RAVAGE_RANK_1)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastProwlAction : public CastBuffSpellAction @@ -106,12 +159,28 @@ class CastRavageAction : public CastMeleeSpellAction { public: CastRavageAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "ravage") {} + + bool isUseful() override + { + if (!botAI->HasAura("prowl", bot)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; class CastPounceAction : public CastMeleeSpellAction { public: CastPounceAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "pounce") {} + + bool isUseful() override + { + if (!botAI->HasAura("prowl", bot)) + return false; + + return CastMeleeSpellAction::isUseful(); + } }; #endif diff --git a/src/Ai/Class/Druid/DruidAiObjectContext.cpp b/src/Ai/Class/Druid/DruidAiObjectContext.cpp index 4d74d1db3..9a4f243c0 100644 --- a/src/Ai/Class/Druid/DruidAiObjectContext.cpp +++ b/src/Ai/Class/Druid/DruidAiObjectContext.cpp @@ -5,9 +5,9 @@ #include "DruidAiObjectContext.h" -#include "BearTankDruidStrategy.h" -#include "CasterDruidStrategy.h" -#include "CatDpsDruidStrategy.h" +#include "BalanceDruidStrategy.h" +#include "BearDruidStrategy.h" +#include "CatDruidStrategy.h" #include "DruidActions.h" #include "DruidBearActions.h" #include "DruidCatActions.h" @@ -15,9 +15,7 @@ #include "DruidTriggers.h" #include "GenericDruidNonCombatStrategy.h" #include "GenericDruidStrategy.h" -#include "HealDruidStrategy.h" -#include "MeleeDruidStrategy.h" -#include "OffhealDruidCatStrategy.h" +#include "RestoDruidStrategy.h" #include "Playerbots.h" #include "DruidPullStrategy.h" @@ -28,30 +26,31 @@ public: { creators["nc"] = &DruidStrategyFactoryInternal::nc; creators["pull"] = &DruidStrategyFactoryInternal::pull; - creators["cat aoe"] = &DruidStrategyFactoryInternal::cat_aoe; - creators["caster aoe"] = &DruidStrategyFactoryInternal::caster_aoe; - creators["caster debuff"] = &DruidStrategyFactoryInternal::caster_debuff; - creators["dps debuff"] = &DruidStrategyFactoryInternal::caster_debuff; + creators["aoe"] = &DruidStrategyFactoryInternal::aoe; creators["cure"] = &DruidStrategyFactoryInternal::cure; - creators["melee"] = &DruidStrategyFactoryInternal::melee; creators["buff"] = &DruidStrategyFactoryInternal::buff; creators["boost"] = &DruidStrategyFactoryInternal::boost; creators["cc"] = &DruidStrategyFactoryInternal::cc; creators["healer dps"] = &DruidStrategyFactoryInternal::healer_dps; + creators["offheal"] = &DruidStrategyFactoryInternal::offheal; + creators["blanketing"] = &DruidStrategyFactoryInternal::blanketing; + creators["tranquility"] = &DruidStrategyFactoryInternal::tranquility; + creators["feral charge"] = &DruidStrategyFactoryInternal::feral_charge; } private: static Strategy* nc(PlayerbotAI* botAI) { return new GenericDruidNonCombatStrategy(botAI); } static Strategy* pull(PlayerbotAI* botAI) { return new DruidPullStrategy(botAI); } - static Strategy* cat_aoe(PlayerbotAI* botAI) { return new CatAoeDruidStrategy(botAI); } - static Strategy* caster_aoe(PlayerbotAI* botAI) { return new CasterDruidAoeStrategy(botAI); } - static Strategy* caster_debuff(PlayerbotAI* botAI) { return new CasterDruidDebuffStrategy(botAI); } + static Strategy* aoe(PlayerbotAI* botAI) { return new DruidAoeStrategy(botAI); } static Strategy* cure(PlayerbotAI* botAI) { return new DruidCureStrategy(botAI); } - static Strategy* melee(PlayerbotAI* botAI) { return new MeleeDruidStrategy(botAI); } static Strategy* buff(PlayerbotAI* botAI) { return new GenericDruidBuffStrategy(botAI); } static Strategy* boost(PlayerbotAI* botAI) { return new DruidBoostStrategy(botAI); } static Strategy* cc(PlayerbotAI* botAI) { return new DruidCcStrategy(botAI); } static Strategy* healer_dps(PlayerbotAI* botAI) { return new DruidHealerDpsStrategy(botAI); } + static Strategy* offheal(PlayerbotAI* botAI) { return new CatOffhealStrategy(botAI); } + static Strategy* blanketing(PlayerbotAI* botAI) { return new DruidBlanketStrategy(botAI); } + static Strategy* tranquility(PlayerbotAI* botAI) { return new DruidTranquilityStrategy(botAI); } + static Strategy* feral_charge(PlayerbotAI* botAI) { return new FeralChargeDruidStrategy(botAI); } }; class DruidDruidStrategyFactoryInternal : public NamedObjectContext @@ -62,18 +61,16 @@ public: creators["bear"] = &DruidDruidStrategyFactoryInternal::bear; creators["tank"] = &DruidDruidStrategyFactoryInternal::bear; creators["cat"] = &DruidDruidStrategyFactoryInternal::cat; - creators["caster"] = &DruidDruidStrategyFactoryInternal::caster; + creators["balance"] = &DruidDruidStrategyFactoryInternal::balance; creators["dps"] = &DruidDruidStrategyFactoryInternal::cat; - creators["heal"] = &DruidDruidStrategyFactoryInternal::heal; - creators["offheal"] = &DruidDruidStrategyFactoryInternal::offheal; + creators["resto"] = &DruidDruidStrategyFactoryInternal::heal; } private: - static Strategy* bear(PlayerbotAI* botAI) { return new BearTankDruidStrategy(botAI); } - static Strategy* cat(PlayerbotAI* botAI) { return new CatDpsDruidStrategy(botAI); } - static Strategy* caster(PlayerbotAI* botAI) { return new CasterDruidStrategy(botAI); } - static Strategy* heal(PlayerbotAI* botAI) { return new HealDruidStrategy(botAI); } - static Strategy* offheal(PlayerbotAI* botAI) { return new OffhealDruidCatStrategy(botAI); } + static Strategy* bear(PlayerbotAI* botAI) { return new BearDruidStrategy(botAI); } + static Strategy* cat(PlayerbotAI* botAI) { return new CatDruidStrategy(botAI); } + static Strategy* balance(PlayerbotAI* botAI) { return new BalanceDruidStrategy(botAI); } + static Strategy* heal(PlayerbotAI* botAI) { return new RestoDruidStrategy(botAI); } }; class DruidTriggerFactoryInternal : public NamedObjectContext @@ -81,7 +78,6 @@ class DruidTriggerFactoryInternal : public NamedObjectContext public: DruidTriggerFactoryInternal() { - creators["omen of clarity"] = &DruidTriggerFactoryInternal::omen_of_clarity; creators["clearcasting"] = &DruidTriggerFactoryInternal::clearcasting; creators["thorns"] = &DruidTriggerFactoryInternal::thorns; creators["thorns on party"] = &DruidTriggerFactoryInternal::thorns_on_party; @@ -90,10 +86,12 @@ public: creators["faerie fire (feral)"] = &DruidTriggerFactoryInternal::faerie_fire_feral; creators["faerie fire"] = &DruidTriggerFactoryInternal::faerie_fire; creators["insect swarm"] = &DruidTriggerFactoryInternal::insect_swarm; + creators["insect swarm on attacker"] = &DruidTriggerFactoryInternal::insect_swarm_on_attacker; creators["moonfire"] = &DruidTriggerFactoryInternal::moonfire; + creators["moonfire on attacker"] = &DruidTriggerFactoryInternal::moonfire_on_attacker; creators["nature's grasp"] = &DruidTriggerFactoryInternal::natures_grasp; - creators["tiger's fury"] = &DruidTriggerFactoryInternal::tigers_fury; creators["berserk"] = &DruidTriggerFactoryInternal::berserk; + creators["berserk active"] = &DruidTriggerFactoryInternal::berserk_active; creators["savage roar"] = &DruidTriggerFactoryInternal::savage_roar; creators["rake"] = &DruidTriggerFactoryInternal::rake; creators["mark of the wild"] = &DruidTriggerFactoryInternal::mark_of_the_wild; @@ -110,17 +108,34 @@ public: creators["eclipse (lunar)"] = &DruidTriggerFactoryInternal::eclipse_lunar; creators["bash on enemy healer"] = &DruidTriggerFactoryInternal::bash_on_enemy_healer; creators["nature's swiftness"] = &DruidTriggerFactoryInternal::natures_swiftness; + creators["nature's swiftness active"] = &DruidTriggerFactoryInternal::natures_swiftness_active; creators["party member remove curse"] = &DruidTriggerFactoryInternal::party_member_remove_curse; - creators["eclipse (solar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_solar_cooldown; - creators["eclipse (lunar) cooldown"] = &DruidTriggerFactoryInternal::eclipse_lunar_cooldown; + creators["mangle (bear)"] = &DruidTriggerFactoryInternal::mangle_bear_trigger; + creators["lacerate"] = &DruidTriggerFactoryInternal::lacerate_trigger; + creators["demoralizing roar"] = &DruidTriggerFactoryInternal::demoralize_roar; creators["mangle (cat)"] = &DruidTriggerFactoryInternal::mangle_cat; creators["ferocious bite time"] = &DruidTriggerFactoryInternal::ferocious_bite_time; + creators["ferocious bite execute"] = &DruidTriggerFactoryInternal::ferocious_bite_execute; creators["hurricane channel check"] = &DruidTriggerFactoryInternal::hurricane_channel_check; creators["no healer dps strategy"] = &DruidTriggerFactoryInternal::no_healer_dps_strategy; + creators["starfall"] = &DruidTriggerFactoryInternal::starfall; + creators["force of nature"] = &DruidTriggerFactoryInternal::force_of_nature; + creators["cyclone"] = &DruidTriggerFactoryInternal::cyclone; + creators["predator's swiftness"] = &DruidTriggerFactoryInternal::predators_swiftness; + creators["predator's swiftness and cyclone"] = &DruidTriggerFactoryInternal::predators_swiftness_and_cyclone; + creators["predator's swiftness and hibernate"] = &DruidTriggerFactoryInternal::predators_swiftness_and_hibernate; + creators["predator's swiftness and entangling roots"] = &DruidTriggerFactoryInternal::predators_swiftness_and_entangling_roots; + creators["predator's swiftness and combat party member dead"] = &DruidTriggerFactoryInternal::predators_swiftness_and_combat_party_member_dead; + creators["clearcasting and medium aoe"] = &DruidTriggerFactoryInternal::clearcasting_and_medium_aoe; + creators["prowl"] = &DruidTriggerFactoryInternal::prowl_trigger; + creators["rejuvenation blanket"] = &DruidTriggerFactoryInternal::rejuvenation_blanket; + creators["wild growth blanket"] = &DruidTriggerFactoryInternal::wild_growth_blanket; + creators["aquatic form"] = &DruidTriggerFactoryInternal::aquatic_form; } private: static Trigger* natures_swiftness(PlayerbotAI* botAI) { return new NaturesSwiftnessTrigger(botAI); } + static Trigger* natures_swiftness_active(PlayerbotAI* botAI) { return new NaturesSwiftnessActiveTrigger(botAI); } static Trigger* clearcasting(PlayerbotAI* botAI) { return new ClearcastingTrigger(botAI); } static Trigger* eclipse_solar(PlayerbotAI* botAI) { return new EclipseSolarTrigger(botAI); } static Trigger* eclipse_lunar(PlayerbotAI* botAI) { return new EclipseLunarTrigger(botAI); } @@ -130,11 +145,13 @@ private: static Trigger* bash(PlayerbotAI* botAI) { return new BashInterruptSpellTrigger(botAI); } static Trigger* faerie_fire_feral(PlayerbotAI* botAI) { return new FaerieFireFeralTrigger(botAI); } static Trigger* insect_swarm(PlayerbotAI* botAI) { return new InsectSwarmTrigger(botAI); } + static Trigger* insect_swarm_on_attacker(PlayerbotAI* botAI) { return new InsectSwarmOnAttackerTrigger(botAI); } static Trigger* moonfire(PlayerbotAI* botAI) { return new MoonfireTrigger(botAI); } + static Trigger* moonfire_on_attacker(PlayerbotAI* botAI) { return new MoonfireOnAttackerTrigger(botAI); } static Trigger* faerie_fire(PlayerbotAI* botAI) { return new FaerieFireTrigger(botAI); } static Trigger* natures_grasp(PlayerbotAI* botAI) { return new NaturesGraspTrigger(botAI); } - static Trigger* tigers_fury(PlayerbotAI* botAI) { return new TigersFuryTrigger(botAI); } static Trigger* berserk(PlayerbotAI* botAI) { return new BerserkTrigger(botAI); } + static Trigger* berserk_active(PlayerbotAI* botAI) { return new BerserkActiveTrigger(botAI); } static Trigger* savage_roar(PlayerbotAI* botAI) { return new SavageRoarTrigger(botAI); } static Trigger* rake(PlayerbotAI* botAI) { return new RakeTrigger(botAI); } static Trigger* mark_of_the_wild(PlayerbotAI* botAI) { return new MarkOfTheWildTrigger(botAI); } @@ -148,14 +165,28 @@ private: static Trigger* cat_form(PlayerbotAI* botAI) { return new CatFormTrigger(botAI); } static Trigger* tree_form(PlayerbotAI* botAI) { return new TreeFormTrigger(botAI); } static Trigger* bash_on_enemy_healer(PlayerbotAI* botAI) { return new BashInterruptEnemyHealerSpellTrigger(botAI); } - static Trigger* omen_of_clarity(PlayerbotAI* botAI) { return new OmenOfClarityTrigger(botAI); } static Trigger* party_member_remove_curse(PlayerbotAI* ai) { return new DruidPartyMemberRemoveCurseTrigger(ai); } - static Trigger* eclipse_solar_cooldown(PlayerbotAI* ai) { return new EclipseSolarCooldownTrigger(ai); } - static Trigger* eclipse_lunar_cooldown(PlayerbotAI* ai) { return new EclipseLunarCooldownTrigger(ai); } + static Trigger* mangle_bear_trigger(PlayerbotAI* botAI) { return new MangleBearTrigger(botAI); } + static Trigger* lacerate_trigger(PlayerbotAI* botAI) { return new LacerateTrigger(botAI); } + static Trigger* demoralize_roar(PlayerbotAI* botAI) { return new DemoralizeRoarTrigger(botAI); } static Trigger* mangle_cat(PlayerbotAI* ai) { return new MangleCatTrigger(ai); } static Trigger* ferocious_bite_time(PlayerbotAI* ai) { return new FerociousBiteTimeTrigger(ai); } + static Trigger* ferocious_bite_execute(PlayerbotAI* ai) { return new FerociousBiteExecuteTrigger(ai); } static Trigger* hurricane_channel_check(PlayerbotAI* ai) { return new HurricaneChannelCheckTrigger(ai); } static Trigger* no_healer_dps_strategy(PlayerbotAI* ai) { return new NoHealerDpsStrategyTrigger(ai); } + static Trigger* starfall(PlayerbotAI* ai) { return new StarfallTrigger(ai); } + static Trigger* force_of_nature(PlayerbotAI* ai) { return new ForceOfNatureTrigger(ai); } + static Trigger* cyclone(PlayerbotAI* ai) { return new CycloneTrigger(ai); } + static Trigger* predators_swiftness(PlayerbotAI* ai) { return new PredatorsSwiftnessTrigger(ai); } + static Trigger* predators_swiftness_and_cyclone(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "cyclone"); } + static Trigger* predators_swiftness_and_hibernate(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "hibernate"); } + static Trigger* predators_swiftness_and_entangling_roots(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "entangling roots"); } + static Trigger* predators_swiftness_and_combat_party_member_dead(PlayerbotAI* ai) { return new TwoTriggers(ai, "predator's swiftness", "combat party member dead"); } + static Trigger* clearcasting_and_medium_aoe(PlayerbotAI* ai) { return new TwoTriggers(ai, "clearcasting", "medium aoe"); } + static Trigger* prowl_trigger(PlayerbotAI* ai) { return new ProwlTrigger(ai); } + static Trigger* rejuvenation_blanket(PlayerbotAI* ai) { return new BuffOnPartyTrigger(ai, "rejuvenation"); } + static Trigger* wild_growth_blanket(PlayerbotAI* ai) { return new BuffOnPartyTrigger(ai, "wild growth"); } + static Trigger* aquatic_form(PlayerbotAI* ai) { return new AquaticFormTrigger(ai); } }; class DruidAiObjectContextInternal : public NamedObjectContext @@ -193,8 +224,8 @@ public: creators["hibernate"] = &DruidAiObjectContextInternal::hibernate; creators["entangling roots"] = &DruidAiObjectContextInternal::entangling_roots; creators["entangling roots on cc"] = &DruidAiObjectContextInternal::entangling_roots_on_cc; - creators["hibernate"] = &DruidAiObjectContextInternal::hibernate; creators["hibernate on cc"] = &DruidAiObjectContextInternal::hibernate_on_cc; + creators["cyclone on cc"] = &DruidAiObjectContextInternal::cyclone_on_cc; creators["wrath"] = &DruidAiObjectContextInternal::wrath; creators["starfall"] = &DruidAiObjectContextInternal::starfall; creators["insect swarm"] = &DruidAiObjectContextInternal::insect_swarm; @@ -205,9 +236,9 @@ public: creators["mangle (cat)"] = &DruidAiObjectContextInternal::mangle_cat; creators["swipe (cat)"] = &DruidAiObjectContextInternal::swipe_cat; creators["rake"] = &DruidAiObjectContextInternal::rake; - creators["rake on attacker"] = &DruidAiObjectContextInternal::rake_on_attacker; creators["ferocious bite"] = &DruidAiObjectContextInternal::ferocious_bite; creators["rip"] = &DruidAiObjectContextInternal::rip; + creators["maim"] = &DruidAiObjectContextInternal::maim; creators["cower"] = &DruidAiObjectContextInternal::cower; creators["survival instincts"] = &DruidAiObjectContextInternal::survival_instincts; creators["frenzied regeneration"] = &DruidAiObjectContextInternal::frenzied_regeneration; @@ -237,9 +268,9 @@ public: creators["lacerate"] = &DruidAiObjectContextInternal::lacerate; creators["hurricane"] = &DruidAiObjectContextInternal::hurricane; creators["innervate"] = &DruidAiObjectContextInternal::innervate; + creators["innervate on healer"] = &DruidAiObjectContextInternal::innervate_on_healer; creators["tranquility"] = &DruidAiObjectContextInternal::tranquility; creators["bash on enemy healer"] = &DruidAiObjectContextInternal::bash_on_enemy_healer; - creators["omen of clarity"] = &DruidAiObjectContextInternal::omen_of_clarity; creators["nature's swiftness"] = &DruidAiObjectContextInternal::natures_swiftness; creators["prowl"] = &DruidAiObjectContextInternal::prowl; creators["dash"] = &DruidAiObjectContextInternal::dash; @@ -254,11 +285,13 @@ public: creators["moonfire on attacker"] = &DruidAiObjectContextInternal::moonfire_on_attacker; creators["enrage"] = &DruidAiObjectContextInternal::enrage; creators["force of nature"] = &DruidAiObjectContextInternal::force_of_nature; + creators["typhoon"] = &DruidAiObjectContextInternal::typhoon; + creators["rejuvenation blanket"] = &DruidAiObjectContextInternal::rejuvenation_blanket; + creators["wild growth blanket"] = &DruidAiObjectContextInternal::wild_growth_blanket; } private: static Action* natures_swiftness(PlayerbotAI* botAI) { return new CastNaturesSwiftnessAction(botAI); } - static Action* omen_of_clarity(PlayerbotAI* botAI) { return new CastOmenOfClarityAction(botAI); } static Action* tranquility(PlayerbotAI* botAI) { return new CastTranquilityAction(botAI); } static Action* feral_charge_bear(PlayerbotAI* botAI) { return new CastFeralChargeBearAction(botAI); } static Action* feral_charge_cat(PlayerbotAI* botAI) { return new CastFeralChargeCatAction(botAI); } @@ -291,6 +324,7 @@ private: static Action* entangling_roots(PlayerbotAI* botAI) { return new CastEntanglingRootsAction(botAI); } static Action* hibernate_on_cc(PlayerbotAI* botAI) { return new CastHibernateCcAction(botAI); } static Action* entangling_roots_on_cc(PlayerbotAI* botAI) { return new CastEntanglingRootsCcAction(botAI); } + static Action* cyclone_on_cc(PlayerbotAI* botAI) { return new CastCycloneCcAction(botAI); } static Action* wrath(PlayerbotAI* botAI) { return new CastWrathAction(botAI); } static Action* starfall(PlayerbotAI* botAI) { return new CastStarfallAction(botAI); } static Action* insect_swarm(PlayerbotAI* botAI) { return new CastInsectSwarmAction(botAI); } @@ -301,9 +335,9 @@ private: static Action* mangle_cat(PlayerbotAI* botAI) { return new CastMangleCatAction(botAI); } static Action* swipe_cat(PlayerbotAI* botAI) { return new CastSwipeCatAction(botAI); } static Action* rake(PlayerbotAI* botAI) { return new CastRakeAction(botAI); } - static Action* rake_on_attacker(PlayerbotAI* botAI) { return new CastRakeOnMeleeAttackersAction(botAI); } static Action* ferocious_bite(PlayerbotAI* botAI) { return new CastFerociousBiteAction(botAI); } static Action* rip(PlayerbotAI* botAI) { return new CastRipAction(botAI); } + static Action* maim(PlayerbotAI* botAI) { return new CastMaimAction(botAI); } static Action* cower(PlayerbotAI* botAI) { return new CastCowerAction(botAI); } static Action* survival_instincts(PlayerbotAI* botAI) { return new CastSurvivalInstinctsAction(botAI); } static Action* frenzied_regeneration(PlayerbotAI* botAI) { return new CastFrenziedRegenerationAction(botAI); } @@ -333,6 +367,7 @@ private: static Action* lacerate(PlayerbotAI* botAI) { return new CastLacerateAction(botAI); } static Action* hurricane(PlayerbotAI* botAI) { return new CastHurricaneAction(botAI); } static Action* innervate(PlayerbotAI* botAI) { return new CastInnervateAction(botAI); } + static Action* innervate_on_healer(PlayerbotAI* botAI) { return new CastInnervateOnHealerAction(botAI); } static Action* bash_on_enemy_healer(PlayerbotAI* botAI) { return new CastBashOnEnemyHealerAction(botAI); } static Action* ravage(PlayerbotAI* botAI) { return new CastRavageAction(botAI); } static Action* pounce(PlayerbotAI* botAI) { return new CastPounceAction(botAI); } @@ -347,6 +382,9 @@ private: static Action* moonfire_on_attacker(PlayerbotAI* ai) { return new CastMoonfireOnAttackerAction(ai); } static Action* enrage(PlayerbotAI* ai) { return new CastEnrageAction(ai); } static Action* force_of_nature(PlayerbotAI* ai) { return new CastForceOfNatureAction(ai); } + static Action* typhoon(PlayerbotAI* ai) { return new CastTyphoonAction(ai); } + static Action* rejuvenation_blanket(PlayerbotAI* ai) { return new CastRejuvenationBlanketAction(ai); } + static Action* wild_growth_blanket(PlayerbotAI* ai) { return new CastWildGrowthBlanketAction(ai); } }; SharedNamedObjectContextList DruidAiObjectContext::sharedStrategyContexts; @@ -386,7 +424,22 @@ void DruidAiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextLi triggerContexts.Add(new DruidTriggerFactoryInternal()); } +class DruidValueContextInternal : public NamedObjectContext +{ +public: + DruidValueContextInternal() + { + creators["eclipse solar proc time"] = &DruidValueContextInternal::eclipse_solar_proc_time; + creators["eclipse lunar proc time"] = &DruidValueContextInternal::eclipse_lunar_proc_time; + } + +private: + static UntypedValue* eclipse_solar_proc_time(PlayerbotAI* botAI) { return new EclipseSolarProcTimeValue(botAI); } + static UntypedValue* eclipse_lunar_proc_time(PlayerbotAI* botAI) { return new EclipseLunarProcTimeValue(botAI); } +}; + void DruidAiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList& valueContexts) { AiObjectContext::BuildSharedValueContexts(valueContexts); + valueContexts.Add(new DruidValueContextInternal()); } diff --git a/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.cpp similarity index 52% rename from src/Ai/Class/Druid/Strategy/CasterDruidStrategy.cpp rename to src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.cpp index a91f3b540..f3fbc2218 100644 --- a/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.cpp @@ -3,15 +3,15 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "CasterDruidStrategy.h" +#include "BalanceDruidStrategy.h" #include "AiObjectContext.h" #include "FeralDruidStrategy.h" -class CasterDruidStrategyActionNodeFactory : public NamedObjectFactory +class BalanceDruidStrategyActionNodeFactory : public NamedObjectFactory { public: - CasterDruidStrategyActionNodeFactory() + BalanceDruidStrategyActionNodeFactory() { creators["faerie fire"] = &faerie_fire; creators["hibernate"] = &hibernate; @@ -23,6 +23,10 @@ public: creators["moonfire"] = &moonfire; creators["starfire"] = &starfire; creators["moonkin form"] = &moonkin_form; + creators["typhoon"] = &typhoon; + creators["hurricane"] = &hurricane; + creators["force of nature"] = &force_of_nature; + creators["cyclone on cc"] = &cyclone_on_cc; } private: @@ -125,130 +129,76 @@ private: /*C*/ {} ); } + + static ActionNode* typhoon([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "typhoon", + /*P*/ { NextAction("moonkin form") }, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* hurricane([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "hurricane", + /*P*/ { NextAction("moonkin form") }, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* force_of_nature([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "force of nature", + /*P*/ { NextAction("moonkin form") }, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* cyclone_on_cc([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "cyclone on cc", + /*P*/ { NextAction("moonkin form") }, + /*A*/ {}, + /*C*/ {} + ); + } }; -CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI) +BalanceDruidStrategy::BalanceDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI) { - actionNodeFactories.Add(new CasterDruidStrategyActionNodeFactory()); + actionNodeFactories.Add(new BalanceDruidStrategyActionNodeFactory()); actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory()); } -std::vector CasterDruidStrategy::getDefaultActions() +std::vector BalanceDruidStrategy::getDefaultActions() { return { - NextAction("starfall", ACTION_HIGH + 1.0f), - NextAction("force of nature", ACTION_DEFAULT + 1.0f), - NextAction("wrath", ACTION_DEFAULT + 0.1f), + NextAction("starfire", 5.4f), + NextAction("wrath", 5.3f), }; } -void CasterDruidStrategy::InitTriggers(std::vector& triggers) +void BalanceDruidStrategy::InitTriggers(std::vector& triggers) { GenericDruidStrategy::InitTriggers(triggers); - triggers.push_back( - new TriggerNode( - "eclipse (lunar) cooldown", - { - NextAction("starfire", ACTION_DEFAULT + 0.2f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "eclipse (solar) cooldown", - { - NextAction("wrath", ACTION_DEFAULT + 0.2f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "insect swarm", - { - NextAction("insect swarm", ACTION_NORMAL + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "moonfire", - { - NextAction("moonfire", ACTION_NORMAL + 4) - } - ) - ); - triggers.push_back( - new TriggerNode( - "eclipse (solar)", - { - NextAction("wrath", ACTION_NORMAL + 6) - } - ) - ); - triggers.push_back( - new TriggerNode( - "eclipse (lunar)", - { - NextAction("starfire", ACTION_NORMAL + 6) - } - ) - ); - triggers.push_back( - new TriggerNode( - "medium mana", - { - NextAction("innervate", ACTION_HIGH + 9) - } - ) - ); - triggers.push_back( - new TriggerNode( - "enemy too close for spell", - { - NextAction("flee", ACTION_MOVE + 9) - } - ) - ); -} + // Debuffs and DoTs + triggers.push_back(new TriggerNode("faerie fire", { NextAction("faerie fire", 29.5f) })); + triggers.push_back(new TriggerNode("insect swarm", { NextAction("insect swarm", 18.0f) })); + triggers.push_back(new TriggerNode("moonfire", { NextAction("moonfire", 17.5f) })); -void CasterDruidAoeStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode( - "hurricane channel check", - { - NextAction("cancel channel", ACTION_HIGH + 2) - } - ) - ); - triggers.push_back( - new TriggerNode( - "medium aoe", - { - NextAction("hurricane", ACTION_HIGH + 1) - } - ) - ); - triggers.push_back( - new TriggerNode( - "light aoe", - { - NextAction("insect swarm on attacker", ACTION_NORMAL + 3), - NextAction("moonfire on attacker", ACTION_NORMAL + 3) - } - ) - ); -} + // Eclipse procs + triggers.push_back(new TriggerNode("eclipse (solar)", { NextAction("wrath", 20.0f) })); + triggers.push_back(new TriggerNode("eclipse (lunar)", { NextAction("starfire", 20.0f) })); -void CasterDruidDebuffStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode( - "faerie fire", - { - NextAction("faerie fire", ACTION_HIGH) - } - ) - ); + // Utility/Defensive + triggers.push_back(new TriggerNode("medium mana", { NextAction("innervate", 29.0f) })); + triggers.push_back(new TriggerNode("enemy too close for spell", { NextAction("flee", 39.0f) })); } diff --git a/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h b/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h new file mode 100644 index 000000000..d01db4901 --- /dev/null +++ b/src/Ai/Class/Druid/Strategy/BalanceDruidStrategy.h @@ -0,0 +1,25 @@ +/* + * 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_BALANCEDRUIDSTRATEGY_H +#define _PLAYERBOT_BALANCEDRUIDSTRATEGY_H + +#include "GenericDruidStrategy.h" + +class PlayerbotAI; + +class BalanceDruidStrategy : public GenericDruidStrategy +{ +public: + BalanceDruidStrategy(PlayerbotAI* botAI); + +public: + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "balance"; } + std::vector getDefaultActions() override; + uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; } +}; + +#endif diff --git a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/BearDruidStrategy.cpp similarity index 59% rename from src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp rename to src/Ai/Class/Druid/Strategy/BearDruidStrategy.cpp index 13af635c3..418f057cd 100644 --- a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/BearDruidStrategy.cpp @@ -3,19 +3,17 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#include "BearTankDruidStrategy.h" +#include "BearDruidStrategy.h" #include "Playerbots.h" -class BearTankDruidStrategyActionNodeFactory : public NamedObjectFactory +class BearDruidStrategyActionNodeFactory : public NamedObjectFactory { public: - BearTankDruidStrategyActionNodeFactory() + BearDruidStrategyActionNodeFactory() { - creators["melee"] = &melee; creators["feral charge - bear"] = &feral_charge_bear; creators["swipe (bear)"] = &swipe_bear; - creators["faerie fire (feral)"] = &faerie_fire_feral; creators["bear form"] = &bear_form; creators["dire bear form"] = &dire_bear_form; creators["mangle (bear)"] = &mangle_bear; @@ -28,16 +26,6 @@ public: } private: - static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "melee", - /*P*/ { NextAction("feral charge - bear") }, - /*A*/ {}, - /*C*/ {} - ); - } - static ActionNode* feral_charge_bear([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode( @@ -58,16 +46,6 @@ private: ); } - static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "faerie fire (feral)", - /*P*/ { NextAction("feral charge - bear") }, - /*A*/ {}, - /*C*/ {} - ); - } - static ActionNode* bear_form([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode( @@ -159,99 +137,81 @@ private: } }; -BearTankDruidStrategy::BearTankDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI) +BearDruidStrategy::BearDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI) { - actionNodeFactories.Add(new BearTankDruidStrategyActionNodeFactory()); + actionNodeFactories.Add(new BearDruidStrategyActionNodeFactory()); } -std::vector BearTankDruidStrategy::getDefaultActions() +std::vector BearDruidStrategy::getDefaultActions() { return { - NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f), - NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f), - NextAction("lacerate", ACTION_DEFAULT + 0.3f), - NextAction("maul", ACTION_DEFAULT + 0.2f), - NextAction("enrage", ACTION_DEFAULT + 0.1f), - NextAction("melee", ACTION_DEFAULT) + NextAction("maul", 5.2f), + NextAction("enrage", 5.1f), + NextAction("melee", 5.0f) }; } -void BearTankDruidStrategy::InitTriggers(std::vector& triggers) +void BearDruidStrategy::InitTriggers(std::vector& triggers) { FeralDruidStrategy::InitTriggers(triggers); - triggers.push_back( - new TriggerNode( - "enemy out of melee", - { - NextAction("feral charge - bear", ACTION_NORMAL + 8) - } - ) - ); triggers.push_back( new TriggerNode( "bear form", - { - NextAction("dire bear form", ACTION_HIGH + 8) - } + { NextAction("dire bear form", 28.0f) } ) ); triggers.push_back( new TriggerNode( - "low health", - { - NextAction("frenzied regeneration", ACTION_HIGH + 7) - } - ) - ); - triggers.push_back( - new TriggerNode( - "faerie fire (feral)", - { - NextAction("faerie fire (feral)", ACTION_HIGH + 7) - } - ) - ); - triggers.push_back(new TriggerNode("high aoe", {NextAction("challenging roar", ACTION_HIGH + 8)})); - triggers.push_back( - new TriggerNode( - "lose aggro", - { - NextAction("growl", ACTION_HIGH + 8) - } + "medium health", + { NextAction("frenzied regeneration", 27.0f) } ) ); + triggers.push_back(new TriggerNode( + "mangle (bear)", { NextAction("mangle (bear)", 17.5f) } + )); + triggers.push_back(new TriggerNode( + "faerie fire (feral)", { NextAction("faerie fire (feral)", 17.0f) } + )); + triggers.push_back(new TriggerNode( + "lacerate", { NextAction("lacerate", 16.0f) } + )); + triggers.push_back(new TriggerNode( + "demoralizing roar", { NextAction("demoralizing roar", 15.5f) } + )); + triggers.push_back(new TriggerNode("high aoe", { NextAction("challenging roar", 26.5f) })); + triggers.push_back(new TriggerNode("lose aggro", + { + NextAction("growl", 26.0f), + NextAction("faerie fire (feral)", 25.5f) + } + )); + triggers.push_back(new TriggerNode("berserk active", { NextAction("mangle (bear)", 25.0f) })); triggers.push_back( new TriggerNode( "medium aoe", { - NextAction("demoralizing roar", ACTION_HIGH + 6), - NextAction("swipe (bear)", ACTION_HIGH + 6) + NextAction("demoralizing roar", 24.5f), + NextAction("swipe (bear)", 24.0f) } ) ); triggers.push_back( new TriggerNode( "light aoe", - { - NextAction("swipe (bear)", ACTION_HIGH + 5) - } + { NextAction("swipe (bear)", 24.0f) } ) ); triggers.push_back( new TriggerNode( "bash", - { - NextAction("bash", ACTION_INTERRUPT + 2) - } + { NextAction("bash", 42.0f) } ) ); triggers.push_back( new TriggerNode( "bash on enemy healer", - { - NextAction("bash on enemy healer", ACTION_INTERRUPT + 1) - } + { NextAction("bash on enemy healer", 41.0f) } ) ); } diff --git a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.h b/src/Ai/Class/Druid/Strategy/BearDruidStrategy.h similarity index 75% rename from src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.h rename to src/Ai/Class/Druid/Strategy/BearDruidStrategy.h index 2019bd0eb..1e47fd08a 100644 --- a/src/Ai/Class/Druid/Strategy/BearTankDruidStrategy.h +++ b/src/Ai/Class/Druid/Strategy/BearDruidStrategy.h @@ -3,17 +3,17 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_BEARTANKDRUIDSTRATEGY_H -#define _PLAYERBOT_BEARTANKDRUIDSTRATEGY_H +#ifndef _PLAYERBOT_BEARDRUIDSTRATEGY_H +#define _PLAYERBOT_BEARDRUIDSTRATEGY_H #include "FeralDruidStrategy.h" class PlayerbotAI; -class BearTankDruidStrategy : public FeralDruidStrategy +class BearDruidStrategy : public FeralDruidStrategy { public: - BearTankDruidStrategy(PlayerbotAI* botAI); + BearDruidStrategy(PlayerbotAI* botAI); void InitTriggers(std::vector& triggers) override; std::string const getName() override { return "bear"; } diff --git a/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.h b/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.h deleted file mode 100644 index 45bc78dba..000000000 --- a/src/Ai/Class/Druid/Strategy/CasterDruidStrategy.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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_CASTERDRUIDSTRATEGY_H -#define _PLAYERBOT_CASTERDRUIDSTRATEGY_H - -#include "GenericDruidStrategy.h" - -class PlayerbotAI; - -class CasterDruidStrategy : public GenericDruidStrategy -{ -public: - CasterDruidStrategy(PlayerbotAI* botAI); - -public: - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "caster"; } - std::vector getDefaultActions() override; - uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; } -}; - -class CasterDruidAoeStrategy : public CombatStrategy -{ -public: - CasterDruidAoeStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} - -public: - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "caster aoe"; } -}; - -class CasterDruidDebuffStrategy : public CombatStrategy -{ -public: - CasterDruidDebuffStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} - -public: - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "caster debuff"; } -}; - -#endif diff --git a/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.cpp deleted file mode 100644 index f7a76b0fc..000000000 --- a/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/* - * 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 "CatDpsDruidStrategy.h" - -#include "AiObjectContext.h" - -class CatDpsDruidStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - CatDpsDruidStrategyActionNodeFactory() - { - creators["faerie fire (feral)"] = &faerie_fire_feral; - creators["melee"] = &melee; - creators["feral charge - cat"] = &feral_charge_cat; - creators["cat form"] = &cat_form; - creators["claw"] = &claw; - creators["mangle (cat)"] = &mangle_cat; - creators["rake"] = &rake; - creators["ferocious bite"] = &ferocious_bite; - creators["rip"] = &rip; - creators["pounce"] = &pounce; - creators["ravage"] = &ravage; - } - -private: - static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "faerie fire (feral)", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "melee", - /*P*/ { NextAction("feral charge - cat") }, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "feral charge - cat", - /*P*/ {}, - /*A*/ { NextAction("reach melee") }, - /*C*/ {} - ); - } - - static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "cat form", - /*P*/ { NextAction("caster form") }, - /*A*/ { NextAction("bear form") }, - /*C*/ {} - ); - } - - static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "claw", - /*P*/ {}, - /*A*/ { NextAction("melee") }, - /*C*/ {} - ); - } - - static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "mangle (cat)", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rake", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "ferocious bite", - /*P*/ {}, - /*A*/ { NextAction("rip") }, - /*C*/ {} - ); - } - - static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rip", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "pounce", - /*P*/ {}, - /*A*/ { NextAction("ravage") }, - /*C*/ {} - ); - } - - static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "ravage", - /*P*/ {}, - /*A*/ { NextAction("shred") }, - /*C*/ {} - ); - } -}; - -CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI) -{ - actionNodeFactories.Add(new CatDpsDruidStrategyActionNodeFactory()); -} - -std::vector CatDpsDruidStrategy::getDefaultActions() -{ - return { - NextAction("tiger's fury", ACTION_DEFAULT + 0.1f) - }; -} - -void CatDpsDruidStrategy::InitTriggers(std::vector& triggers) -{ - FeralDruidStrategy::InitTriggers(triggers); - - // Default priority - triggers.push_back( - new TriggerNode( - "almost full energy available", - { - NextAction("shred", ACTION_DEFAULT + 0.4f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points not full", - { - NextAction("shred", ACTION_DEFAULT + 0.4f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "almost full energy available", - { - NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points not full and high energy", - { - NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "almost full energy available", - { - NextAction("claw", ACTION_DEFAULT + 0.2f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points not full and high energy", - { - NextAction("claw", ACTION_DEFAULT + 0.2f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "faerie fire (feral)", - { - NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f) - } - ) - ); - - // Main spell - triggers.push_back( - new TriggerNode( - "cat form", { - NextAction("cat form", ACTION_HIGH + 8) - } - ) - ); - triggers.push_back( - new TriggerNode( - "savage roar", { - NextAction("savage roar", ACTION_HIGH + 7) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points 5 available", - { - NextAction("rip", ACTION_HIGH + 6) - } - ) - ); - triggers.push_back( - new TriggerNode( - "ferocious bite time", - { - NextAction("ferocious bite", ACTION_HIGH + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "target with combo points almost dead", - { - NextAction("ferocious bite", ACTION_HIGH + 4) - } - ) - ); - triggers.push_back( - new TriggerNode( - "mangle (cat)", - { - NextAction("mangle (cat)", ACTION_HIGH + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "rake", - { - NextAction("rake", ACTION_HIGH + 2) - } - ) - ); - triggers.push_back( - new TriggerNode( - "medium threat", - { - NextAction("cower", ACTION_HIGH + 1) - } - ) - ); - - // AOE - triggers.push_back( - new TriggerNode( - "medium aoe", - { - NextAction("swipe (cat)", ACTION_HIGH + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "light aoe", - { - NextAction("rake on attacker", ACTION_HIGH + 2) - } - ) - ); - // Reach target - triggers.push_back( - new TriggerNode( - "enemy out of melee", - { - NextAction("feral charge - cat", ACTION_HIGH + 9) - } - ) - ); - triggers.push_back( - new TriggerNode( - "enemy out of melee", - { - NextAction("dash", ACTION_HIGH + 8) - } - ) - ); -} - -void CatAoeDruidStrategy::InitTriggers(std::vector& /*triggers*/) {} diff --git a/src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp new file mode 100644 index 000000000..df6ba4a92 --- /dev/null +++ b/src/Ai/Class/Druid/Strategy/CatDruidStrategy.cpp @@ -0,0 +1,446 @@ +/* + * 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 "CatDruidStrategy.h" + +#include "AiObjectContext.h" + +class CatDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + CatDruidStrategyActionNodeFactory() + { + creators["faerie fire (feral)"] = &faerie_fire_feral; + creators["melee"] = &melee; + creators["feral charge - cat"] = &feral_charge_cat; + creators["cat form"] = &cat_form; + creators["claw"] = &claw; + creators["mangle (cat)"] = &mangle_cat; + creators["rake"] = &rake; + creators["ferocious bite"] = &ferocious_bite; + creators["rip"] = &rip; + creators["pounce"] = &pounce; + creators["ravage"] = &ravage; + creators["prowl"] = &prowl; + } + +private: + static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "faerie fire (feral)", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "melee", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "feral charge - cat", + /*P*/ {}, + /*A*/ { NextAction("reach melee") }, + /*C*/ {} + ); + } + + static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "cat form", + /*P*/ { NextAction("caster form") }, + /*A*/ { NextAction("bear form") }, + /*C*/ {} + ); + } + + static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "claw", + /*P*/ {}, + /*A*/ { NextAction("melee") }, + /*C*/ {} + ); + } + + static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "mangle (cat)", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "rake", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "ferocious bite", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "rip", + /*P*/ {}, + /*A*/ {}, + /*C*/ {} + ); + } + + static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "ravage", + /*P*/ {}, + /*A*/ { NextAction("pounce") }, + /*C*/ {} + ); + } + + static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "pounce", + /*P*/ {}, + /*A*/ { NextAction("shred") }, + /*C*/ {} + ); + } + + static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "prowl", + /*P*/ { NextAction("cat form") }, + /*A*/ {}, + /*C*/ {} + ); + } + +}; + +CatDruidStrategy::CatDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI) +{ + actionNodeFactories.Add(new CatDruidStrategyActionNodeFactory()); +} + +std::vector CatDruidStrategy::getDefaultActions() +{ + return { + NextAction("melee", ACTION_DEFAULT) + }; +} + +void CatDruidStrategy::InitTriggers(std::vector& triggers) +{ + FeralDruidStrategy::InitTriggers(triggers); + + triggers.push_back( + new TriggerNode( + "healer low mana", { + NextAction("innervate on healer", 35.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "prowl", { + NextAction("prowl", 29.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "enemy out of melee", { + NextAction("dash", 28.0f) + } + ) + ); + + triggers.push_back( + new TriggerNode( + "cat form", { + NextAction("cat form", 28.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "low energy", { + NextAction("tiger's fury", 27.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "savage roar", { + NextAction("savage roar", 26.0f) + } + ) + ); + + triggers.push_back( + new TriggerNode( + "combo points 5 available", { + NextAction("rip", 23.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points 5 available", { + NextAction("maim", 23.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "ferocious bite execute", { + NextAction("ferocious bite", 24.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "clearcasting", { + NextAction("shred", 24.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "ferocious bite time", { + NextAction("ferocious bite", 22.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "mangle (cat)", { + NextAction("mangle (cat)", 22.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "rake", { + NextAction("rake", 21.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "medium threat", { + NextAction("cower", 21.0f) + } + ) + ); + + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("ravage", 5.6f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full", { + NextAction("ravage", 5.6f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("pounce", 5.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full", { + NextAction("pounce", 5.5f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("shred", 5.4f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full", { + NextAction("shred", 5.4f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("mangle (cat)", 5.3f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full and high energy", { + NextAction("mangle (cat)", 5.3f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "almost full energy available", { + NextAction("claw", 5.2f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "combo points not full and high energy", { + NextAction("claw", 5.2f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "faerie fire (feral)", { + NextAction("faerie fire (feral)", 5.0f) + } + ) + ); +} + +// ============================================================ +// CatOffhealStrategy +// Additive overlay — only the healing triggers. Designed to be +// stacked on top of "cat" so the bot stays in cat form for DPS +// but shifts out to heal when the party needs it. +// ============================================================ + +class CatOffhealStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + CatOffhealStrategyActionNodeFactory() + { + creators["healing touch on party"] = &healing_touch_on_party; + creators["regrowth on party"] = ®rowth_on_party; + creators["rejuvenation on party"] = &rejuvenation_on_party; + } + +private: + // P: shift to caster form before casting C: shift back to cat form afterwards + static ActionNode* healing_touch_on_party([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "healing touch on party", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ { NextAction("cat form") } + ); + } + + static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "regrowth on party", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ { NextAction("cat form") } + ); + } + + static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode( + "rejuvenation on party", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ { NextAction("cat form") } + ); + } +}; + +CatOffhealStrategy::CatOffhealStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) +{ + actionNodeFactories.Add(new CatOffhealStrategyActionNodeFactory()); +} + +void CatOffhealStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back( + new TriggerNode( + "party member critical health", + { + NextAction("regrowth on party", 36.0f), + NextAction("healing touch on party", 35.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "party member low health", + { + NextAction("healing touch on party", 25.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "party member medium health", + { + NextAction("rejuvenation on party", 18.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "party member to heal out of spell range", + { + NextAction("reach party member to heal", 93.0f) + } + ) + ); + triggers.push_back( + new TriggerNode( + "low mana", + { + NextAction("innervate", 24.0f) + } + ) + ); +} diff --git a/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.h b/src/Ai/Class/Druid/Strategy/CatDruidStrategy.h similarity index 59% rename from src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.h rename to src/Ai/Class/Druid/Strategy/CatDruidStrategy.h index 312e94d0f..51d80cd77 100644 --- a/src/Ai/Class/Druid/Strategy/CatDpsDruidStrategy.h +++ b/src/Ai/Class/Druid/Strategy/CatDruidStrategy.h @@ -3,17 +3,17 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_CATDPSDRUIDSTRATEGY_H -#define _PLAYERBOT_CATDPSDRUIDSTRATEGY_H +#ifndef _PLAYERBOT_CATDRUIDSTRATEGY_H +#define _PLAYERBOT_CATDRUIDSTRATEGY_H #include "FeralDruidStrategy.h" class PlayerbotAI; -class CatDpsDruidStrategy : public FeralDruidStrategy +class CatDruidStrategy : public FeralDruidStrategy { public: - CatDpsDruidStrategy(PlayerbotAI* botAI); + CatDruidStrategy(PlayerbotAI* botAI); public: void InitTriggers(std::vector& triggers) override; @@ -22,14 +22,16 @@ public: uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; } }; -class CatAoeDruidStrategy : public CombatStrategy +// Optional additive strategy. Layers emergency heals on top of the "cat" strategy. +// Enable : co +offheal +// Disable: co -offheal +class CatOffhealStrategy : public CombatStrategy { public: - CatAoeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} + CatOffhealStrategy(PlayerbotAI* botAI); -public: void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "cat aoe"; } + std::string const getName() override { return "offheal"; } }; #endif diff --git a/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.cpp index 894c05bff..aec438890 100644 --- a/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.cpp @@ -14,12 +14,10 @@ public: { creators["survival instincts"] = &survival_instincts; creators["thorns"] = þs; - creators["omen of clarity"] = &omen_of_clarity; creators["cure poison"] = &cure_poison; creators["cure poison on party"] = &cure_poison_on_party; creators["abolish poison"] = &abolish_poison; creators["abolish poison on party"] = &abolish_poison_on_party; - creators["prowl"] = &prowl; } private: @@ -39,14 +37,6 @@ private: /*C*/ {}); } - static ActionNode* omen_of_clarity([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode("omen of clarity", - /*P*/ { NextAction("caster form") }, - /*A*/ {}, - /*C*/ {}); - } - static ActionNode* cure_poison([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("cure poison", @@ -79,13 +69,6 @@ private: /*C*/ {}); } - static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode("prowl", - /*P*/ { NextAction("cat form") }, - /*A*/ {}, - /*C*/ {}); - } }; FeralDruidStrategy::FeralDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI) @@ -99,15 +82,23 @@ void FeralDruidStrategy::InitTriggers(std::vector& triggers) GenericDruidStrategy::InitTriggers(triggers); triggers.push_back(new TriggerNode( - "enemy out of melee", { NextAction("reach melee", ACTION_HIGH + 1) })); + "enemy out of melee", { NextAction("reach melee", 21.0f) })); triggers.push_back(new TriggerNode( - "critical health", { NextAction("survival instincts", ACTION_EMERGENCY + 1) })); - triggers.push_back(new TriggerNode( - "omen of clarity", { NextAction("omen of clarity", ACTION_HIGH + 9) })); + "low health", { NextAction("survival instincts", 91.0f) })); triggers.push_back(new TriggerNode("player has flag", - { NextAction("dash", ACTION_EMERGENCY + 2) })); + { NextAction("dash", 92.0f) })); triggers.push_back(new TriggerNode("enemy flagcarrier near", - { NextAction("dash", ACTION_EMERGENCY + 2) })); - triggers.push_back( - new TriggerNode("berserk", { NextAction("berserk", ACTION_HIGH + 6) })); + { NextAction("dash", 92.0f) })); +} + +void FeralChargeDruidStrategy::InitTriggers(std::vector& triggers) +{ + Player* bot = botAI->GetBot(); + + if (bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE)) + triggers.push_back(new TriggerNode( + "enemy out of melee", { NextAction("feral charge - cat", 29.0f) })); + else + triggers.push_back(new TriggerNode( + "enemy out of melee", { NextAction("feral charge - bear", 18.0f) })); } diff --git a/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.h b/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.h index abf01c694..ebff73de3 100644 --- a/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.h +++ b/src/Ai/Class/Druid/Strategy/FeralDruidStrategy.h @@ -3,11 +3,14 @@ * and/or modify it under version 3 of the License, or (at your option), any later version. */ -#ifndef _PLAYERBOT_FERALRUIDSTRATEGY_H -#define _PLAYERBOT_FERALRUIDSTRATEGY_H +#ifndef _PLAYERBOT_FERALDRUIDSTRATEGY_H +#define _PLAYERBOT_FERALDRUIDSTRATEGY_H #include "GenericDruidStrategy.h" +constexpr uint32 SPELL_CAT_FORM = 768; +constexpr uint32 AURA_THICK_HIDE = 16931; + class PlayerbotAI; class ShapeshiftDruidStrategyActionNodeFactory : public NamedObjectFactory @@ -83,4 +86,18 @@ public: uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; } }; +// Optional strategy — enabled by default for cat and bear. +// Registers the "enemy out of melee" → Feral Charge trigger, spec-gated at +// init time so cats get Feral Charge (Cat) and bears get Feral Charge (Bear). +// Disable with: co -feral charge +// Re-enable with: co +feral charge +class FeralChargeDruidStrategy : public CombatStrategy +{ +public: + FeralChargeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "feral charge"; } +}; + #endif diff --git a/src/Ai/Class/Druid/Strategy/GenericDruidNonCombatStrategy.cpp b/src/Ai/Class/Druid/Strategy/GenericDruidNonCombatStrategy.cpp index 7d1a5f7ca..0977540f6 100644 --- a/src/Ai/Class/Druid/Strategy/GenericDruidNonCombatStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/GenericDruidNonCombatStrategy.cpp @@ -17,12 +17,13 @@ public: creators["thorns on party"] = þs_on_party; creators["mark of the wild"] = &mark_of_the_wild; creators["mark of the wild on party"] = &mark_of_the_wild_on_party; - // creators["innervate"] = &innervate; creators["regrowth_on_party"] = ®rowth_on_party; creators["rejuvenation on party"] = &rejuvenation_on_party; creators["remove curse on party"] = &remove_curse_on_party; creators["abolish poison on party"] = &abolish_poison_on_party; creators["revive"] = &revive; + creators["prowl"] = &prowl; + creators["aquatic form"] = &aquatic_form; } private: @@ -92,6 +93,23 @@ private: /*A*/ {}, /*C*/ {}); } + + static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("prowl", + /*P*/ { NextAction("cat form") }, + /*A*/ {}, + /*C*/ {}); + } + + static ActionNode* aquatic_form([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("aquatic form", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ {}); + } + }; GenericDruidNonCombatStrategy::GenericDruidNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) @@ -165,11 +183,16 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector& trig NextAction("remove curse on party", ACTION_DISPEL + 7), })); + triggers.push_back(new TriggerNode("aquatic form", { NextAction("aquatic form", 10.0f) })); + int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot()); - if (specTab == 0 || specTab == 2) // Balance or Restoration + if (specTab == DRUID_TAB_BALANCE || specTab == DRUID_TAB_RESTORATION) triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) })); - if (specTab == 1) // Feral + if (specTab == DRUID_TAB_FERAL) + { triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) })); + triggers.push_back(new TriggerNode("prowl", { NextAction("prowl", ACTION_INTERRUPT) })); + } } diff --git a/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.cpp index 36b90a146..d5e7b9f40 100644 --- a/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.cpp +++ b/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.cpp @@ -5,6 +5,8 @@ #include "GenericDruidStrategy.h" +#include "AiFactory.h" +#include "FeralDruidStrategy.h" #include "Playerbots.h" class GenericDruidStrategyActionNodeFactory : public NamedObjectFactory @@ -20,6 +22,8 @@ public: creators["abolish poison on party"] = &abolish_poison_on_party; creators["rebirth"] = &rebirth; creators["entangling roots on cc"] = &entangling_roots_on_cc; + creators["cyclone on cc"] = &cyclone_on_cc; + creators["hibernate on cc"] = &hibernate_on_cc; creators["innervate"] = &innervate; } @@ -88,6 +92,22 @@ private: /*C*/ {}); } + static ActionNode* cyclone_on_cc([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("cyclone on cc", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ {}); + } + + static ActionNode* hibernate_on_cc([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("hibernate on cc", + /*P*/ { NextAction("caster form") }, + /*A*/ {}, + /*C*/ {}); + } + static ActionNode* innervate([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("innervate", @@ -107,41 +127,95 @@ void GenericDruidStrategy::InitTriggers(std::vector& triggers) CombatStrategy::InitTriggers(triggers); triggers.push_back( - new TriggerNode("low health", { NextAction("barkskin", ACTION_HIGH + 7) })); + new TriggerNode("almost full health", { NextAction("barkskin", 40.0f) })); + + Player* bot = botAI->GetBot(); + int tab = AiFactory::GetPlayerSpecTab(bot); + + if (tab == DRUID_TAB_FERAL) + { + if (!bot->HasAura(16931) /*thick hide — bear spec*/) + { + triggers.push_back(new TriggerNode("predator's swiftness and combat party member dead", + { NextAction("rebirth", 29.0f) })); + triggers.push_back(new TriggerNode("combat party member dead", + { NextAction("rebirth", 28.5f) })); + } + } + else + { + triggers.push_back(new TriggerNode("combat party member dead", + { NextAction("rebirth", 29.0f) })); + } - triggers.push_back(new TriggerNode("combat party member dead", - { NextAction("rebirth", ACTION_HIGH + 9) })); triggers.push_back(new TriggerNode("being attacked", - { NextAction("nature's grasp", ACTION_HIGH + 1) })); - triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) })); + { NextAction("nature's grasp", 39.0f) })); } void DruidCureStrategy::InitTriggers(std::vector& triggers) { triggers.push_back( new TriggerNode("party member cure poison", - { NextAction("abolish poison on party", ACTION_DISPEL + 1) })); + { NextAction("abolish poison on party", 51.0f) })); triggers.push_back( new TriggerNode("party member remove curse", - { NextAction("remove curse on party", ACTION_DISPEL + 7) })); + { NextAction("remove curse on party", 57.0f) })); } void DruidBoostStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode( - "nature's swiftness", { NextAction("nature's swiftness", ACTION_HIGH + 9) })); + Player* bot = botAI->GetBot(); + int tab = AiFactory::GetPlayerSpecTab(bot); + + if (tab == DRUID_TAB_BALANCE) + { + triggers.push_back(new TriggerNode("force of nature", { NextAction("force of nature", 29.0f) })); + triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) })); + } + + if (tab == DRUID_TAB_FERAL) + { + triggers.push_back(new TriggerNode("berserk", { NextAction("berserk", 27.5f) })); + } } void DruidCcStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode( - "entangling roots", { NextAction("entangling roots on cc", ACTION_HIGH + 2) })); - triggers.push_back(new TriggerNode( - "entangling roots kite", { NextAction("entangling roots", ACTION_HIGH + 2) })); - triggers.push_back(new TriggerNode( - "hibernate", { NextAction("hibernate on cc", ACTION_HIGH + 3) })); + Player* bot = botAI->GetBot(); + int tab = AiFactory::GetPlayerSpecTab(bot); + + if (tab == DRUID_TAB_BALANCE || tab == DRUID_TAB_RESTORATION) + { + triggers.push_back(new TriggerNode( + "cyclone", { NextAction("cyclone on cc", 42.0f) })); + triggers.push_back(new TriggerNode( + "hibernate", { NextAction("hibernate on cc", 41.0f) })); + triggers.push_back(new TriggerNode( + "entangling roots", { NextAction("entangling roots on cc", 40.0f) })); + } + if (tab == DRUID_TAB_FERAL) + { + if (bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE)) + { + triggers.push_back(new TriggerNode( + "predator's swiftness and cyclone", { NextAction("cyclone on cc", 42.0f) })); + triggers.push_back(new TriggerNode( + "predator's swiftness and hibernate", { NextAction("hibernate on cc", 41.0f) })); + triggers.push_back(new TriggerNode( + "predator's swiftness and entangling roots", { NextAction("entangling roots on cc", 40.0f) })); + } + else + { + triggers.push_back(new TriggerNode( + "cyclone", { NextAction("cyclone on cc", 42.0f) })); + triggers.push_back(new TriggerNode( + "hibernate", { NextAction("hibernate on cc", 41.0f) })); + triggers.push_back(new TriggerNode( + "entangling roots", { NextAction("entangling roots on cc", 40.0f) })); + } + } } void DruidHealerDpsStrategy::InitTriggers(std::vector& triggers) @@ -149,10 +223,39 @@ void DruidHealerDpsStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("healer should attack", { - NextAction("cancel tree form", ACTION_DEFAULT + 0.4f), - NextAction("moonfire", ACTION_DEFAULT + 0.3f), - NextAction("wrath", ACTION_DEFAULT + 0.2f), - NextAction("starfire", ACTION_DEFAULT + 0.1f), -})); - + NextAction("cancel tree form", 5.4f), + NextAction("moonfire", 5.3f), + NextAction("wrath", 5.2f), + NextAction("starfire", 5.1f), + })); +} + +void DruidAoeStrategy::InitTriggers(std::vector& triggers) +{ + Player* bot = botAI->GetBot(); + int tab = AiFactory::GetPlayerSpecTab(bot); + + if (tab == DRUID_TAB_BALANCE) + { + triggers.push_back(new TriggerNode("hurricane channel check", { NextAction("cancel channel", 22.0f) })); + triggers.push_back(new TriggerNode("starfall", { NextAction("starfall", 28.5f) })); + triggers.push_back(new TriggerNode("medium aoe", { NextAction("hurricane", 23.0f) })); + triggers.push_back(new TriggerNode("enemy within melee", { NextAction("typhoon", 40.0f) })); + triggers.push_back(new TriggerNode("insect swarm on attacker", { NextAction("insect swarm on attacker", 5.2f) })); + triggers.push_back(new TriggerNode("moonfire on attacker", { NextAction("moonfire on attacker", 5.1f) })); + } + + if (tab == DRUID_TAB_RESTORATION) + { + triggers.push_back(new TriggerNode("hurricane channel check", { NextAction("cancel channel", 22.0f) })); + triggers.push_back(new TriggerNode("medium aoe", { NextAction("hurricane", 23.0f) })); + triggers.push_back(new TriggerNode("insect swarm on attacker", { NextAction("insect swarm on attacker", 5.2f) })); + triggers.push_back(new TriggerNode("moonfire on attacker", { NextAction("moonfire on attacker", 5.1f) })); + } + + if (tab == DRUID_TAB_FERAL && bot->HasSpell(SPELL_CAT_FORM) && !bot->HasAura(AURA_THICK_HIDE)) + { + triggers.push_back(new TriggerNode("clearcasting and medium aoe", { NextAction("swipe (cat)", 25.5f) })); + triggers.push_back(new TriggerNode("medium aoe", { NextAction("swipe (cat)", 25.0f) })); + } } diff --git a/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.h b/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.h index 2f1c2e787..1cb88f82a 100644 --- a/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.h +++ b/src/Ai/Class/Druid/Strategy/GenericDruidStrategy.h @@ -55,4 +55,13 @@ public: std::string const getName() override { return "healer dps"; } }; +class DruidAoeStrategy : public Strategy +{ +public: + DruidAoeStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "aoe"; } +}; + #endif diff --git a/src/Ai/Class/Druid/Strategy/HealDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/HealDruidStrategy.cpp deleted file mode 100644 index 0710e0fdc..000000000 --- a/src/Ai/Class/Druid/Strategy/HealDruidStrategy.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 "HealDruidStrategy.h" - -#include "Playerbots.h" - -class HealDruidStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - HealDruidStrategyActionNodeFactory() { - creators["nourish on party"] = &nourtish_on_party; - } - -private: - static ActionNode* nourtish_on_party([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode("nourish on party", - /*P*/ {}, - /*A*/ { NextAction("healing touch on party") }, - /*C*/ {}); - } -}; - -HealDruidStrategy::HealDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI) -{ - actionNodeFactories.Add(new HealDruidStrategyActionNodeFactory()); -} - -void HealDruidStrategy::InitTriggers(std::vector& triggers) -{ - GenericDruidStrategy::InitTriggers(triggers); - - // no healer dps strategy - triggers.push_back(new TriggerNode("no healer dps strategy", - { NextAction("tree form", ACTION_DEFAULT) })); - - triggers.push_back(new TriggerNode( - "party member to heal out of spell range", - { NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 9) })); - - // CRITICAL - triggers.push_back( - new TriggerNode("party member critical health", - { - NextAction("tree form", ACTION_CRITICAL_HEAL + 4.1f), - NextAction("swiftmend on party", ACTION_CRITICAL_HEAL + 4), - NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 3), - NextAction("wild growth on party", ACTION_CRITICAL_HEAL + 2), - NextAction("nourish on party", ACTION_CRITICAL_HEAL + 1), - })); - - triggers.push_back( - new TriggerNode("party member critical health", - { NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4) })); - - triggers.push_back(new TriggerNode("clearcasting", - { NextAction("lifebloom on main tank", ACTION_CRITICAL_HEAL - 1) })); - - triggers.push_back(new TriggerNode( - "group heal setting", - { - NextAction("tree form", ACTION_MEDIUM_HEAL + 2.3f), - NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 2.2f), - NextAction("rejuvenation on not full", ACTION_MEDIUM_HEAL + 2.1f), - })); - - triggers.push_back( - new TriggerNode("medium group heal setting", - { - NextAction("tree form", ACTION_CRITICAL_HEAL + 0.6f), - NextAction("tranquility", ACTION_CRITICAL_HEAL + 0.5f) })); - - // LOW - triggers.push_back( - new TriggerNode("party member low health", - { NextAction("tree form", ACTION_MEDIUM_HEAL + 1.5f), - NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 1.4f), - NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1.3f), - NextAction("swiftmend on party", ACTION_MEDIUM_HEAL + 1.2), - NextAction("nourish on party", ACTION_MEDIUM_HEAL + 1.1f), - })); - - // MEDIUM - triggers.push_back( - new TriggerNode("party member medium health", - { - NextAction("tree form", ACTION_MEDIUM_HEAL + 0.5f), - NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 0.4f), - NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 0.3f), - NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 0.2f), - NextAction("nourish on party", ACTION_MEDIUM_HEAL + 0.1f) })); - - // almost full - triggers.push_back( - new TriggerNode("party member almost full health", - { NextAction("wild growth on party", ACTION_LIGHT_HEAL + 0.3f), - NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 0.2f), - NextAction("regrowth on party", ACTION_LIGHT_HEAL + 0.1f) })); - - triggers.push_back( - new TriggerNode("medium mana", { NextAction("innervate", ACTION_HIGH + 5) })); - - triggers.push_back(new TriggerNode("enemy too close for spell", - { NextAction("flee", ACTION_MOVE + 9) })); -} diff --git a/src/Ai/Class/Druid/Strategy/HealDruidStrategy.h b/src/Ai/Class/Druid/Strategy/HealDruidStrategy.h deleted file mode 100644 index 36ce40271..000000000 --- a/src/Ai/Class/Druid/Strategy/HealDruidStrategy.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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_HEALDRUIDSTRATEGY_H -#define _PLAYERBOT_HEALDRUIDSTRATEGY_H - -#include "GenericDruidStrategy.h" -#include "Strategy.h" - -class PlayerbotAI; - -class HealDruidStrategy : public GenericDruidStrategy -{ -public: - HealDruidStrategy(PlayerbotAI* botAI); - - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "heal"; } - uint32 GetType() const override { return STRATEGY_TYPE_RANGED | STRATEGY_TYPE_HEAL; } -}; - -#endif diff --git a/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.cpp deleted file mode 100644 index 5dc0f85d9..000000000 --- a/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 "MeleeDruidStrategy.h" - -#include "Playerbots.h" - -MeleeDruidStrategy::MeleeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {} - -std::vector MeleeDruidStrategy::getDefaultActions() -{ - return { - NextAction("faerie fire", ACTION_DEFAULT + 0.1f), - NextAction("melee", ACTION_DEFAULT) - }; -} - -void MeleeDruidStrategy::InitTriggers(std::vector& triggers) -{ - triggers.push_back( - new TriggerNode( - "omen of clarity", - { - NextAction("omen of clarity", ACTION_HIGH + 9) - } - ) - ); - - CombatStrategy::InitTriggers(triggers); -} diff --git a/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.h b/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.h deleted file mode 100644 index 67bbbbfed..000000000 --- a/src/Ai/Class/Druid/Strategy/MeleeDruidStrategy.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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_MELEEDRUIDSTRATEGY_H -#define _PLAYERBOT_MELEEDRUIDSTRATEGY_H - -#include "CombatStrategy.h" - -class MeleeDruidStrategy : public CombatStrategy -{ -public: - MeleeDruidStrategy(PlayerbotAI* botAI); - - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "melee"; } - std::vector getDefaultActions() override; -}; - -#endif diff --git a/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.cpp b/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.cpp deleted file mode 100644 index fb7893651..000000000 --- a/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/* - * 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 "OffhealDruidCatStrategy.h" - - #include "Playerbots.h" - #include "Strategy.h" - - class OffhealDruidCatStrategyActionNodeFactory : public NamedObjectFactory -{ -public: - OffhealDruidCatStrategyActionNodeFactory() - { - creators["cat form"] = &cat_form; - creators["mangle (cat)"] = &mangle_cat; - creators["shred"] = &shred; - creators["rake"] = &rake; - creators["rip"] = &rip; - creators["ferocious bite"] = &ferocious_bite; - creators["savage roar"] = &savage_roar; - creators["faerie fire (feral)"] = &faerie_fire_feral; - creators["healing touch on party"] = &healing_touch_on_party; - creators["regrowth on party"] = ®rowth_on_party; - creators["rejuvenation on party"] = &rejuvenation_on_party; - } - -private: - static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "cat form", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "mangle (cat)", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* shred([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "shred", - /*P*/ {}, - /*A*/ { NextAction("claw") }, - /*C*/ {} - ); - } - - static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rake", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rip", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "ferocious bite", - /*P*/ {}, - /*A*/ { NextAction("rip") }, - /*C*/ {} - ); - } - - static ActionNode* savage_roar([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "savage roar", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "faerie fire (feral)", - /*P*/ {}, - /*A*/ {}, - /*C*/ {} - ); - } - - static ActionNode* healing_touch_on_party([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "healing touch on party", - /*P*/ { NextAction("caster form") }, - /*A*/ {}, - /*C*/ { NextAction("cat form") } - ); - } - - static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "regrowth on party", - /*P*/ { NextAction("caster form") }, - /*A*/ {}, - /*C*/ { NextAction("cat form") } - ); - } - - static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI) - { - return new ActionNode( - "rejuvenation on party", - /*P*/ { NextAction("caster form") }, - /*A*/ {}, - /*C*/ { NextAction("cat form") } - ); - } -}; - -OffhealDruidCatStrategy::OffhealDruidCatStrategy(PlayerbotAI* botAI) : FeralDruidStrategy(botAI) -{ - actionNodeFactories.Add(new OffhealDruidCatStrategyActionNodeFactory()); -} - -std::vector OffhealDruidCatStrategy::getDefaultActions() -{ - return { - NextAction("mangle (cat)", ACTION_DEFAULT + 0.5f), - NextAction("shred", ACTION_DEFAULT + 0.4f), - NextAction("rake", ACTION_DEFAULT + 0.3f), - NextAction("melee", ACTION_DEFAULT), - NextAction("cat form", ACTION_DEFAULT - 0.1f) - }; -} - -void OffhealDruidCatStrategy::InitTriggers(std::vector& triggers) -{ - FeralDruidStrategy::InitTriggers(triggers); - - triggers.push_back( - new TriggerNode( - "cat form", - { - NextAction("cat form", ACTION_HIGH + 8) - } - ) - ); - triggers.push_back( - new TriggerNode( - "savage roar", - { - NextAction("savage roar", ACTION_HIGH + 7) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points 5 available", - { - NextAction("rip", ACTION_HIGH + 6) - } - ) - ); - triggers.push_back( - new TriggerNode( - "ferocious bite time", - { - NextAction("ferocious bite", ACTION_HIGH + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "target with combo points almost dead", - { - NextAction("ferocious bite", ACTION_HIGH + 4) - } - ) - ); - triggers.push_back( - new TriggerNode( - "mangle (cat)", - { - NextAction("mangle (cat)", ACTION_HIGH + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "rake", - { - NextAction("rake", ACTION_HIGH + 2) - } - ) - ); - triggers.push_back( - new TriggerNode( - "almost full energy available", - { - NextAction("shred", ACTION_DEFAULT + 0.4f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "combo points not full", - { - NextAction("shred", ACTION_DEFAULT + 0.4f) - } - ) - ); - triggers.push_back( - new TriggerNode( - "faerie fire (feral)", - { - NextAction("faerie fire (feral)", ACTION_NORMAL) - } - ) - ); - triggers.push_back( - new TriggerNode( - "enemy out of melee", - { - NextAction("feral charge - cat", ACTION_HIGH + 9), - NextAction("dash", ACTION_HIGH + 8) - } - ) - ); - triggers.push_back( - new TriggerNode( - "medium aoe", - { - NextAction("swipe (cat)", ACTION_HIGH + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "tiger's fury", - { - NextAction("tiger's fury", ACTION_NORMAL + 1) - } - ) - ); - triggers.push_back( - new TriggerNode( - "party member critical health", - { - NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 6), - NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "party member low health", - { - NextAction("healing touch on party", ACTION_MEDIUM_HEAL + 5) - } - ) - ); - triggers.push_back( - new TriggerNode( - "party member medium health", - { - NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 8) - } - ) - ); - triggers.push_back( - new TriggerNode( - "party member to heal out of spell range", - { - NextAction("reach party member to heal", ACTION_EMERGENCY + 3) - } - ) - ); - triggers.push_back( - new TriggerNode( - "low mana", - { - NextAction("innervate", ACTION_HIGH + 4) - } - ) - ); -} diff --git a/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.h b/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.h deleted file mode 100644 index 83775ef98..000000000 --- a/src/Ai/Class/Druid/Strategy/OffhealDruidCatStrategy.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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_OFFHEALDRUIDCATSTRATEGY_H - #define _PLAYERBOT_OFFHEALDRUIDCATSTRATEGY_H - - #include "FeralDruidStrategy.h" - - class PlayerbotAI; - - class OffhealDruidCatStrategy : public FeralDruidStrategy - { - public: - OffhealDruidCatStrategy(PlayerbotAI* botAI); - - void InitTriggers(std::vector& triggers) override; - std::string const getName() override { return "offheal"; } - std::vector getDefaultActions() override; - uint32 GetType() const override - { - return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_HEAL | STRATEGY_TYPE_MELEE; - } - }; - - #endif diff --git a/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.cpp b/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.cpp new file mode 100644 index 000000000..5f086ee50 --- /dev/null +++ b/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.cpp @@ -0,0 +1,120 @@ +/* + * 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 "RestoDruidStrategy.h" + +#include "Playerbots.h" + +class RestoDruidStrategyActionNodeFactory : public NamedObjectFactory +{ +public: + RestoDruidStrategyActionNodeFactory() { + creators["nourish on party"] = &nourish_on_party; + } + +private: + static ActionNode* nourish_on_party([[maybe_unused]] PlayerbotAI* botAI) + { + return new ActionNode("nourish on party", + /*P*/ {}, + /*A*/ {}, + /*C*/ {}); + } +}; + +RestoDruidStrategy::RestoDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI) +{ + actionNodeFactories.Add(new RestoDruidStrategyActionNodeFactory()); +} + +void RestoDruidStrategy::InitTriggers(std::vector& triggers) +{ + GenericDruidStrategy::InitTriggers(triggers); + + triggers.push_back(new TriggerNode("no healer dps strategy", + { NextAction("tree form", 5.0f) })); + + triggers.push_back(new TriggerNode( + "party member to heal out of spell range", + { NextAction("reach party member to heal", 39.0f) })); + + triggers.push_back( + new TriggerNode("party member critical health", + { + NextAction("tree form", 34.1f), + NextAction("swiftmend on party", 34.0f), + NextAction("wild growth on party", 33.0f), + NextAction("nourish on party", 32.0f), + NextAction("regrowth on party", 31.0f), + NextAction("healing touch on party", 30.0f), + })); + + triggers.push_back( + new TriggerNode("party member critical health", + { NextAction("nature's swiftness", 58.0f) })); + + triggers.push_back(new TriggerNode( + "nature's swiftness active", + { NextAction("healing touch on party", 55.0f) })); + + triggers.push_back(new TriggerNode("clearcasting", + { NextAction("lifebloom on main tank", 13.0f) })); + + // LOW + triggers.push_back( + new TriggerNode("party member low health", + { + NextAction("tree form", 21.5f), + NextAction("swiftmend on party", 21.4f), + NextAction("wild growth on party", 21.3f), + NextAction("nourish on party", 21.2f), + NextAction("regrowth on party", 21.1f), + NextAction("healing touch on party", 21.0f), + })); + + // MEDIUM + triggers.push_back( + new TriggerNode("party member medium health", + { + NextAction("tree form", 20.5f), + NextAction("swiftmend on party", 20.4f), + NextAction("wild growth on party", 20.3f), + NextAction("nourish on party", 20.2f), + NextAction("regrowth on party", 20.1f), + NextAction("healing touch on party", 20.0f), + })); + + // ALMOST FULL + triggers.push_back( + new TriggerNode("party member almost full health", + { + NextAction("wild growth on party", 10.3f), + NextAction("rejuvenation on party", 10.2f), + NextAction("regrowth on party", 10.1f), + })); + + triggers.push_back( + new TriggerNode("medium mana", { NextAction("innervate", 25.0f) })); + + triggers.push_back(new TriggerNode("enemy too close for spell", + { NextAction("flee", 39.0f) })); +} + +void DruidTranquilityStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode("medium group heal setting", + { NextAction("tree form", 30.6f), NextAction("tranquility", 30.5f) })); +} + +void DruidBlanketStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back(new TriggerNode( + "wild growth blanket", + { NextAction("tree form", 8.1f), NextAction("wild growth blanket", 8.0f) })); + + triggers.push_back(new TriggerNode( + "rejuvenation blanket", + { NextAction("tree form", 6.1f), NextAction("rejuvenation blanket", 6.0f) })); +} diff --git a/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.h b/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.h new file mode 100644 index 000000000..afc3a93bc --- /dev/null +++ b/src/Ai/Class/Druid/Strategy/RestoDruidStrategy.h @@ -0,0 +1,42 @@ +/* + * 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_RESTODRUIDSTRATEGY_H +#define _PLAYERBOT_RESTODRUIDSTRATEGY_H + +#include "GenericDruidStrategy.h" +#include "Strategy.h" + +class PlayerbotAI; + +class RestoDruidStrategy : public GenericDruidStrategy +{ +public: + RestoDruidStrategy(PlayerbotAI* botAI); + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "resto"; } + uint32 GetType() const override { return STRATEGY_TYPE_RANGED | STRATEGY_TYPE_HEAL; } +}; + +class DruidBlanketStrategy : public Strategy +{ +public: + DruidBlanketStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "blanketing"; } +}; + +class DruidTranquilityStrategy : public Strategy +{ +public: + DruidTranquilityStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + void InitTriggers(std::vector& triggers) override; + std::string const getName() override { return "tranquility"; } +}; + +#endif diff --git a/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp b/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp index 6cf8552a3..5a8dbdfa4 100644 --- a/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp +++ b/src/Ai/Class/Druid/Trigger/DruidTriggers.cpp @@ -4,8 +4,10 @@ */ #include "DruidTriggers.h" +#include "DynamicObject.h" #include "Player.h" #include "Playerbots.h" +#include "ServerFacade.h" bool MarkOfTheWildOnPartyTrigger::IsActive() { @@ -35,6 +37,45 @@ bool TreeFormTrigger::IsActive() { return !botAI->HasAura(33891, bot); } bool CatFormTrigger::IsActive() { return !botAI->HasAura("cat form", bot); } +bool AquaticFormTrigger::IsActive() +{ + return !bot->IsInCombat() && !botAI->HasAura("aquatic form", bot) && + bot->GetLiquidData().Status == LIQUID_MAP_UNDER_WATER; +} + +bool ProwlTrigger::IsActive() +{ + if (botAI->HasAura("prowl", bot) || bot->IsInCombat()) + return false; + + uint32 prowlId = botAI->GetAiObjectContext()->GetValue("spell id", "prowl")->Get(); + if (!prowlId || !bot->HasSpell(prowlId) || bot->HasSpellCooldown(prowlId)) + return false; + + float distance = 30.f; + + Unit* target = AI_VALUE(Unit*, "enemy player target"); + if (target && !target->IsInWorld()) + return false; + if (!target) + target = AI_VALUE(Unit*, "grind target"); + if (!target) + target = AI_VALUE(Unit*, "dps target"); + if (!target) + return false; + + if (target && target->GetVictim()) + distance -= 10; + if (target->isMoving() && target->GetVictim()) + distance -= 10; + if (bot->InBattleground()) + distance += 15; + if (bot->InArena()) + distance += 15; + + return target && ServerFacade::instance().GetDistance2d(bot, target) < distance; +} + const std::set HurricaneChannelCheckTrigger::HURRICANE_SPELL_IDS = { 16914, // Hurricane Rank 1 17401, // Hurricane Rank 2 @@ -45,19 +86,38 @@ const std::set HurricaneChannelCheckTrigger::HURRICANE_SPELL_IDS = { bool HurricaneChannelCheckTrigger::IsActive() { - Player* bot = botAI->GetBot(); - - // Check if the bot is channeling a spell if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) { - // Only trigger if the spell being channeled is Hurricane - if (HURRICANE_SPELL_IDS.count(spell->m_spellInfo->Id)) + if (!HURRICANE_SPELL_IDS.count(spell->m_spellInfo->Id)) + return false; + + // Find this bot's own Hurricane DynamicObject + DynamicObject* dynObj = nullptr; + for (uint32 spellId : HURRICANE_SPELL_IDS) { - uint8 attackerCount = AI_VALUE(uint8, "attacker count"); - return attackerCount < minEnemies; + dynObj = bot->GetDynObject(spellId); + if (dynObj) + break; } + + if (!dynObj) + return false; + + // Count attackers actually inside the Hurricane AoE + float radius = dynObj->GetRadius(); + GuidVector attackers = AI_VALUE(GuidVector, "attackers"); + uint32 count = 0; + for (ObjectGuid const& guid : attackers) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive()) + continue; + if (unit->GetDistance(dynObj->GetPosition()) <= radius) + count++; + } + + return count < minEnemies; } - // Not channeling Hurricane return false; } diff --git a/src/Ai/Class/Druid/Trigger/DruidTriggers.h b/src/Ai/Class/Druid/Trigger/DruidTriggers.h index 01daaeba6..1f389c947 100644 --- a/src/Ai/Class/Druid/Trigger/DruidTriggers.h +++ b/src/Ai/Class/Druid/Trigger/DruidTriggers.h @@ -8,6 +8,7 @@ #include "CureTriggers.h" #include "GenericTriggers.h" +#include "RtiTriggers.h" #include "Player.h" #include "PlayerbotAI.h" #include "Playerbots.h" @@ -15,6 +16,8 @@ #include "Trigger.h" #include +constexpr uint32 AURA_OMEN_OF_CLARITY = 16864; + class PlayerbotAI; class MarkOfTheWildOnPartyTrigger : public BuffOnPartyTrigger @@ -55,22 +58,30 @@ public: bool IsActive() override; }; -class OmenOfClarityTrigger : public BuffTrigger -{ -public: - OmenOfClarityTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "omen of clarity") {} -}; - class ClearcastingTrigger : public HasAuraTrigger { public: ClearcastingTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "clearcasting") {} }; +class PredatorsSwiftnessTrigger : public HasAuraTrigger +{ +public: + PredatorsSwiftnessTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "predator's swiftness") {} +}; + +class NaturesSwiftnessActiveTrigger : public HasAuraTrigger +{ +public: + NaturesSwiftnessActiveTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "nature's swiftness") {} + bool IsActive() override { return botAI->HasAura("nature's swiftness", bot); } +}; + class RakeTrigger : public DebuffTrigger { public: RakeTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "rake", 1, true) {} + bool IsActive() override { return !botAI->HasAura("prowl", bot) && DebuffTrigger::IsActive(); } }; class InsectSwarmTrigger : public DebuffTrigger @@ -79,12 +90,26 @@ public: InsectSwarmTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "insect swarm", 1, true) {} }; +class InsectSwarmOnAttackerTrigger : public DebuffOnAttackerTrigger +{ +public: + InsectSwarmOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "insect swarm", true) {} + bool IsActive() override { return BuffTrigger::IsActive(); } +}; + class MoonfireTrigger : public DebuffTrigger { public: MoonfireTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "moonfire", 1, true) {} }; +class MoonfireOnAttackerTrigger : public DebuffOnAttackerTrigger +{ +public: + MoonfireOnAttackerTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "moonfire", true) {} + bool IsActive() override { return BuffTrigger::IsActive(); } +}; + class FaerieFireTrigger : public DebuffTrigger { public: @@ -95,6 +120,35 @@ class FaerieFireFeralTrigger : public DebuffTrigger { public: FaerieFireFeralTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "faerie fire (feral)") {} + + bool IsActive() override + { + if (!bot->IsInCombat()) + return false; + + // Bear: every cast generates immediate threat/damage for free — spam it + if (botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr)) + { + Unit* target = GetTarget(); + return target && target->IsAlive() && target->IsInWorld(); + } + + if (!botAI->HasAura("cat form", bot)) + return false; + + if (botAI->HasAura("prowl", bot)) + return false; + + // Cat with Omen of Clarity: spam to fish for Clearcasting procs + if (bot->HasAura(AURA_OMEN_OF_CLARITY)) + { + Unit* target = GetTarget(); + return target && target->IsAlive() && target->IsInWorld(); + } + + // Cat without Omen of Clarity: apply as a normal debuff, don't reapply + return DebuffTrigger::IsActive(); + } }; class BashInterruptSpellTrigger : public InterruptSpellTrigger @@ -103,18 +157,18 @@ public: BashInterruptSpellTrigger(PlayerbotAI* botAI) : InterruptSpellTrigger(botAI, "bash") {} }; -class TigersFuryTrigger : public BuffTrigger -{ -public: - TigersFuryTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "tiger's fury") {} -}; - class BerserkTrigger : public BoostTrigger { public: BerserkTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "berserk") {} }; +class BerserkActiveTrigger : public HasAuraTrigger +{ +public: + BerserkActiveTrigger(PlayerbotAI* botAI) : HasAuraTrigger(botAI, "berserk") {} +}; + class SavageRoarTrigger : public BuffTrigger { public: @@ -127,10 +181,10 @@ public: NaturesGraspTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "nature's grasp") {} }; -class EntanglingRootsTrigger : public HasCcTargetTrigger +class EntanglingRootsTrigger : public RtiCcTrigger { public: - EntanglingRootsTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "entangling roots") {} + EntanglingRootsTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "entangling roots") {} }; class EntanglingRootsKiteTrigger : public DebuffTrigger @@ -141,10 +195,16 @@ public: bool IsActive() override; }; -class HibernateTrigger : public HasCcTargetTrigger +class HibernateTrigger : public RtiCcTrigger { public: - HibernateTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "hibernate") {} + HibernateTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "hibernate") {} +}; + +class CycloneTrigger : public RtiCcTrigger +{ +public: + CycloneTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "cyclone") {} }; class CurePoisonTrigger : public NeedCureTrigger @@ -185,6 +245,14 @@ public: bool IsActive() override; }; +class AquaticFormTrigger : public Trigger +{ +public: + AquaticFormTrigger(PlayerbotAI* botAI) : Trigger(botAI, "aquatic form") {} + + bool IsActive() override; +}; + class EclipseSolarTrigger : public HasAuraTrigger { public: @@ -218,18 +286,69 @@ public: } }; -class EclipseSolarCooldownTrigger : public SpellCooldownTrigger +class StarfallTrigger : public SpellNoCooldownTrigger { public: - EclipseSolarCooldownTrigger(PlayerbotAI* ai) : SpellCooldownTrigger(ai, "eclipse (solar)") {} - bool IsActive() override { return bot->HasSpellCooldown(48517); } + StarfallTrigger(PlayerbotAI* botAI) : SpellNoCooldownTrigger(botAI, "starfall") {} }; -class EclipseLunarCooldownTrigger : public SpellCooldownTrigger +class ForceOfNatureTrigger : public BoostTrigger { public: - EclipseLunarCooldownTrigger(PlayerbotAI* ai) : SpellCooldownTrigger(ai, "eclipse (lunar)") {} - bool IsActive() override { return bot->HasSpellCooldown(48518); } + ForceOfNatureTrigger(PlayerbotAI* botAI) : BoostTrigger(botAI, "force of nature") {} +}; + +class MangleBearTrigger : public DebuffTrigger +{ +public: + MangleBearTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "mangle (bear)") {} + + bool IsActive() override + { + if (!bot->IsInCombat() || !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr)) + return false; + Unit* target = GetTarget(); + return target && target->IsAlive() && target->IsInWorld(); + } +}; + +class LacerateTrigger : public DebuffTrigger +{ +public: + LacerateTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "lacerate") {} + + bool IsActive() override + { + if (!bot->IsInCombat() || !botAI->HasAnyAuraOf(bot, "bear form", "dire bear form", nullptr)) + return false; + + Unit* target = GetTarget(); + if (!target || !target->IsAlive() || !target->IsInWorld()) + return false; + + Aura* lacerate = botAI->GetAura("lacerate", target, false, false); + if (!lacerate) + return true; + + if (lacerate->GetStackAmount() < 5) + return true; + + return lacerate->GetDuration() <= 6000; + } +}; + +class DemoralizeRoarTrigger : public DebuffTrigger +{ +public: + DemoralizeRoarTrigger(PlayerbotAI* botAI) : DebuffTrigger(botAI, "demoralizing roar") {} + + bool IsActive() override + { + return DebuffTrigger::IsActive() + && !botAI->HasAura("curse of weakness", GetTarget(), false, false) + && !botAI->HasAura("demoralizing shout", GetTarget(), false, false) + && !botAI->HasAura("vindication", GetTarget(), false, false); + } }; class MangleCatTrigger : public DebuffTrigger @@ -238,6 +357,8 @@ public: MangleCatTrigger(PlayerbotAI* ai) : DebuffTrigger(ai, "mangle (cat)", 1, false, 0.0f) {} bool IsActive() override { + if (botAI->HasAura("prowl", bot)) + return false; return DebuffTrigger::IsActive() && !botAI->HasAura("mangle (bear)", GetTarget(), false, false, -1, true) && !botAI->HasAura("trauma", GetTarget(), false, false, -1, true); } @@ -271,10 +392,36 @@ public: } }; +class FerociousBiteExecuteTrigger : public Trigger +{ +public: + FerociousBiteExecuteTrigger(PlayerbotAI* ai) : Trigger(ai, "ferocious bite execute") {} + bool IsActive() override + { + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target || !target->IsAlive()) + return false; + + if (!AI_VALUE2(uint32, "spell id", "ferocious bite")) + return false; + + if (AI_VALUE2(uint8, "combo", "current target") < 1) + return false; + + if (target->GetHealthPct() >= 25.0f) + return false; + + if (target->GetHealth() >= 20000) + return false; + + return true; + } +}; + class HurricaneChannelCheckTrigger : public Trigger { public: - HurricaneChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 2) + HurricaneChannelCheckTrigger(PlayerbotAI* botAI, uint32 minEnemies = 3) : Trigger(botAI, "hurricane channel check"), minEnemies(minEnemies) { } @@ -297,4 +444,12 @@ public: } }; +class ProwlTrigger : public Trigger +{ +public: + ProwlTrigger(PlayerbotAI* botAI) : Trigger(botAI, "prowl") {} + + bool IsActive() override; +}; + #endif diff --git a/src/Ai/Class/Warlock/Action/WarlockActions.cpp b/src/Ai/Class/Warlock/Action/WarlockActions.cpp index 93798b702..5e23f1af5 100644 --- a/src/Ai/Class/Warlock/Action/WarlockActions.cpp +++ b/src/Ai/Class/Warlock/Action/WarlockActions.cpp @@ -27,6 +27,9 @@ bool CastDrainSoulAction::isUseful() { return AI_VALUE2(uint32, "item count", "s // Checks if the bot's health is above a certain threshold, and if so, allows casting Life Tap bool CastLifeTapAction::isUseful() { return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.lowHealth; } +Value* CastBanishOnCcAction::GetTargetValue() { return context->GetValue("rti cc target"); } +Value* CastFearOnCcAction::GetTargetValue() { return context->GetValue("rti cc target"); } + // Checks if the target marked with the moon icon can be banished bool CastBanishOnCcAction::isPossible() { diff --git a/src/Ai/Class/Warlock/Action/WarlockActions.h b/src/Ai/Class/Warlock/Action/WarlockActions.h index 09f346370..8fca0fdc4 100644 --- a/src/Ai/Class/Warlock/Action/WarlockActions.h +++ b/src/Ai/Class/Warlock/Action/WarlockActions.h @@ -170,6 +170,7 @@ class CastBanishOnCcAction : public CastCrowdControlSpellAction { public: CastBanishOnCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "banish") {} + Value* GetTargetValue() override; bool isPossible() override; }; @@ -177,6 +178,7 @@ class CastFearOnCcAction : public CastCrowdControlSpellAction { public: CastFearOnCcAction(PlayerbotAI* botAI) : CastCrowdControlSpellAction(botAI, "fear") {} + Value* GetTargetValue() override; bool isPossible() override; }; diff --git a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp b/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp index 1df531e7f..14e7a52e5 100644 --- a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp +++ b/src/Ai/Class/Warlock/Trigger/WarlockTriggers.cpp @@ -50,22 +50,6 @@ bool TooManySoulShardsTrigger::IsActive() { return GetSoulShardCount(botAI->GetB bool OutOfSoulstoneTrigger::IsActive() { return GetSoulstoneCount(botAI->GetBot()) == 0; } -// Checks if the target marked with the moon icon can be banished -bool BanishTrigger::IsActive() -{ - Unit* ccTarget = context->GetValue("cc target", "banish")->Get(); - Unit* moonTarget = context->GetValue("rti cc target")->Get(); - return ccTarget && moonTarget && ccTarget == moonTarget && HasCcTargetTrigger::IsActive(); -} - -// Checks if the target marked with the moon icon can be feared -bool FearTrigger::IsActive() -{ - Unit* ccTarget = context->GetValue("cc target", "fear")->Get(); - Unit* moonTarget = context->GetValue("rti cc target")->Get(); - return ccTarget && moonTarget && ccTarget == moonTarget && HasCcTargetTrigger::IsActive(); -} - bool DemonArmorTrigger::IsActive() { Unit* target = GetTarget(); diff --git a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h b/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h index d66fe537e..15991e270 100644 --- a/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h +++ b/src/Ai/Class/Warlock/Trigger/WarlockTriggers.h @@ -8,6 +8,7 @@ #include "GenericTriggers.h" #include "PlayerbotAI.h" +#include "RtiTriggers.h" #include "Playerbots.h" #include "CureTriggers.h" #include "Trigger.h" @@ -137,18 +138,16 @@ public: // CC and Pet Triggers -class BanishTrigger : public HasCcTargetTrigger +class BanishTrigger : public RtiCcTrigger { public: - BanishTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "banish") {} - bool IsActive() override; + BanishTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "banish") {} }; -class FearTrigger : public HasCcTargetTrigger +class FearTrigger : public RtiCcTrigger { public: - FearTrigger(PlayerbotAI* botAI) : HasCcTargetTrigger(botAI, "fear") {} - bool IsActive() override; + FearTrigger(PlayerbotAI* botAI) : RtiCcTrigger(botAI, "fear") {} }; class SpellLockInterruptSpellTrigger : public InterruptSpellTrigger diff --git a/src/Bot/Factory/AiFactory.cpp b/src/Bot/Factory/AiFactory.cpp index c95180538..f1fd2491f 100644 --- a/src/Bot/Factory/AiFactory.cpp +++ b/src/Bot/Factory/AiFactory.cpp @@ -338,15 +338,17 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa break; case CLASS_DRUID: if (tab == DRUID_TAB_BALANCE) - engine->addStrategiesNoInit("caster", "cure", "caster aoe", "caster debuff", "dps assist", nullptr); + { + engine->addStrategiesNoInit("balance", "cure", "aoe", "cc", "dps assist", nullptr); + } else if (tab == DRUID_TAB_RESTORATION) - engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr); - else // if (tab == DRUID_TAB_FERAL) + engine->addStrategiesNoInit("resto", "cure", "dps assist", "blanketing", "tranquility", nullptr); + else { if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/) - engine->addStrategiesNoInit("cat", "dps assist", nullptr); + engine->addStrategiesNoInit("cat", "aoe", "cc", "dps assist", "feral charge", nullptr); else - engine->addStrategiesNoInit("bear", "tank assist", "pull", "pull back", nullptr); + engine->addStrategiesNoInit("bear", "tank assist", "pull", "pull back", "feral charge", nullptr); } break; case CLASS_HUNTER: @@ -420,8 +422,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa { if (tab == DRUID_TAB_RESTORATION) { - engine->addStrategiesNoInit("caster", "caster aoe", nullptr); - engine->addStrategy("caster debuff", false); + engine->addStrategiesNoInit("aoe", nullptr); } break; }