From de9f8fbbeab01c2d0cfc8b55a1075106866e5d8b Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Sun, 27 Jul 2025 01:10:31 -0700 Subject: [PATCH] Druid Flight Form Bug Fix Hello everyone, This is a fix for issue #1233 I have added a clause in the CanCastSpell check that checks if the bot is in flight form/swift flight form & not in combat. It will no longer attempt to shift into a caster form and repetitively try and heal/buff party members while flying. Tested on my PC with the changes and druid now successfully flies as fast as the player with no stopping. This was causing the issue: static ActionNode* thorns([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("thorns", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ nullptr, /*C*/ nullptr); } static ActionNode* thorns_on_party([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("thorns on party", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ nullptr, /*C*/ nullptr); } static ActionNode* mark_of_the_wild([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("mark of the wild", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ nullptr, /*C*/ nullptr); } static ActionNode* mark_of_the_wild_on_party([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("mark of the wild on party", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ nullptr, /*C*/ nullptr); } static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("regrowth on party", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ NULL, /*C*/ NULL); } static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("rejuvenation on party", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ NULL, /*C*/ NULL); } static ActionNode* remove_curse_on_party([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("remove curse on party", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ NULL, /*C*/ NULL); } static ActionNode* abolish_poison_on_party([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("abolish poison on party", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ NULL, /*C*/ NULL); } static ActionNode* revive([[maybe_unused]] PlayerbotAI* botAI) { return new ActionNode("revive", /*P*/ NextAction::array(0, new NextAction("caster form"), nullptr), /*A*/ NULL, /*C*/ NULL); } The *P* stands for prior, aka before this action is taken, do this other action "caster form". This is the Caster Form action: class CastCasterFormAction : public CastBuffSpellAction { public: CastCasterFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "caster form") {} bool isUseful() override; bool isPossible() override { return true; } bool Execute(Event event) override; }; And this: bool CastCasterFormAction::isUseful() { return botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "aquatic form", "flight form", "swift flight form", "moonkin form", nullptr) && AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig->mediumHealth; } bool CastCasterFormAction::Execute(Event event) { botAI->RemoveShapeshift(); return true; } So basically, the druid was triggering to heal/buff the party, and before it could do that, it had to switch into "caster form" which executed RemoveShapeshift(). After it did that, mounting back up into flight form/swift flight form had the highest priority, so it would loop the triggers/actions. --- src/PlayerbotAI.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 1d406345..b9847530 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -2935,6 +2935,18 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell, return false; } + if ((bot->GetShapeshiftForm() == FORM_FLIGHT || bot->GetShapeshiftForm() == FORM_FLIGHT_EPIC) && !bot->IsInCombat()) + { + if (!sPlayerbotAIConfig->logInGroupOnly || (bot->GetGroup() && HasRealPlayerMaster())) + { + LOG_DEBUG( + "playerbots", + "Can cast spell failed. In flight form (not in combat). - target name: {}, spellid: {}, bot name: {}", + target->GetName(), spellid, bot->GetName()); + } + return false; + } + uint32 CastingTime = !spellInfo->IsChanneled() ? spellInfo->CalcCastTime(bot) : spellInfo->GetDuration(); // bool interruptOnMove = spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT; if ((CastingTime || spellInfo->IsAutoRepeatRangedSpell()) && bot->isMoving())