diff --git a/src/Ai/Base/Actions/CheckMountStateAction.cpp b/src/Ai/Base/Actions/CheckMountStateAction.cpp index 5ab7cc0f9..0d7fe4321 100644 --- a/src/Ai/Base/Actions/CheckMountStateAction.cpp +++ b/src/Ai/Base/Actions/CheckMountStateAction.cpp @@ -55,63 +55,6 @@ MountData CollectMountData(const Player* bot) return data; } -bool CheckMountStateAction::isUseful() -{ - // Not useful when: - if (botAI->IsInVehicle() || bot->isDead() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || - !bot->IsOutdoors() || bot->InArena()) - return false; - - master = GetMaster(); - - // Get shapeshift states, only applicable when there's a master - if (master) - { - botInShapeshiftForm = bot->GetShapeshiftForm(); - masterInShapeshiftForm = master->GetShapeshiftForm(); - } - - // Not useful when in combat and not currently mounted / travel formed - if ((bot->IsInCombat() || botAI->GetState() == BOT_STATE_COMBAT) && - !bot->IsMounted() && botInShapeshiftForm != FORM_TRAVEL && botInShapeshiftForm != FORM_FLIGHT && botInShapeshiftForm != FORM_FLIGHT_EPIC) - return false; - - // In addition to checking IsOutdoors, also check whether bot is clipping below floor slightly because that will - // cause bot to falsly indicate they are outdoors. This fixes bug where bot tries to mount indoors (which seems - // to mostly be an issue in tunnels of WSG and AV) - float posZ = bot->GetPositionZ(); - float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ); - if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel) - return false; - - // Not useful when bot does not have mount strat and is not currently mounted - if (!GET_PLAYERBOT_AI(bot)->HasStrategy("mount", BOT_STATE_NON_COMBAT) && !bot->IsMounted()) - return false; - - // Not useful when level lower than minimum required - if (bot->GetLevel() < sPlayerbotAIConfig.useGroundMountAtMinLevel) - return false; - - // Allow mounting while transformed only if the form allows it - if (bot->HasAuraType(SPELL_AURA_TRANSFORM) && bot->IsInDisallowedMountForm()) - return false; - - // BG Logic - if (bot->InBattleground()) - { - // Do not use when carrying BG Flags - if (bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG) || bot->HasAura(BG_EY_NETHERSTORM_FLAG_SPELL)) - return false; - - // Only mount if BG starts in less than 30 sec - if (Battleground* bg = bot->GetBattleground()) - if (bg->GetStatus() == STATUS_WAIT_JOIN && bg->GetStartDelayTime() > BG_START_DELAY_30S) - return false; - } - - return true; -} - bool CheckMountStateAction::Execute(Event /*event*/) { // Determine if there are no attackers @@ -182,6 +125,63 @@ bool CheckMountStateAction::Execute(Event /*event*/) return false; } +bool CheckMountStateAction::isUseful() +{ + // Not useful when: + if (botAI->IsInVehicle() || bot->isDead() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || + !bot->IsOutdoors() || bot->InArena()) + return false; + + master = GetMaster(); + + // Get shapeshift states, only applicable when there's a master + if (master) + { + botInShapeshiftForm = bot->GetShapeshiftForm(); + masterInShapeshiftForm = master->GetShapeshiftForm(); + } + + // Not useful when in combat and not currently mounted / travel formed + if ((bot->IsInCombat() || botAI->GetState() == BOT_STATE_COMBAT) && + !bot->IsMounted() && botInShapeshiftForm != FORM_TRAVEL && botInShapeshiftForm != FORM_FLIGHT && botInShapeshiftForm != FORM_FLIGHT_EPIC) + return false; + + // In addition to checking IsOutdoors, also check whether bot is clipping below floor slightly because that will + // cause bot to falsly indicate they are outdoors. This fixes bug where bot tries to mount indoors (which seems + // to mostly be an issue in tunnels of WSG and AV) + float posZ = bot->GetPositionZ(); + float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ); + if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel) + return false; + + // Not useful when bot does not have mount strat and is not currently mounted + if (!GET_PLAYERBOT_AI(bot)->HasStrategy("mount", BOT_STATE_NON_COMBAT) && !bot->IsMounted()) + return false; + + // Not useful when level lower than minimum required + if (bot->GetLevel() < sPlayerbotAIConfig.useGroundMountAtMinLevel) + return false; + + // Allow mounting while transformed only if the form allows it + if (bot->HasAuraType(SPELL_AURA_TRANSFORM) && bot->IsInDisallowedMountForm()) + return false; + + // BG Logic + if (bot->InBattleground()) + { + // Do not use when carrying BG Flags + if (bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG) || bot->HasAura(BG_EY_NETHERSTORM_FLAG_SPELL)) + return false; + + // Only mount if BG starts in less than 30 sec + if (Battleground* bg = bot->GetBattleground()) + if (bg->GetStatus() == STATUS_WAIT_JOIN && bg->GetStartDelayTime() > BG_START_DELAY_30S) + return false; + } + + return true; +} + bool CheckMountStateAction::Mount() { // Remove current Shapeshift if need be diff --git a/src/Ai/Base/Actions/ChooseTargetActions.cpp b/src/Ai/Base/Actions/ChooseTargetActions.cpp index 200094c90..3446c9b52 100644 --- a/src/Ai/Base/Actions/ChooseTargetActions.cpp +++ b/src/Ai/Base/Actions/ChooseTargetActions.cpp @@ -30,37 +30,6 @@ bool AttackEnemyFlagCarrierAction::isUseful() PlayerHasFlag::IsCapturingFlag(bot); } -bool AttackAnythingAction::isUseful() -{ - if (!bot || !botAI) // Prevents invalid accesses - return false; - - if (!botAI->AllowActivity(GRIND_ACTIVITY)) // Bot cannot be active - return false; - - if (botAI->HasStrategy("stay", BOT_STATE_NON_COMBAT)) - return false; - - if (bot->IsInCombat()) - return false; - - Unit* target = GetTarget(); - if (!target || !target->IsInWorld()) // Checks if the target is valid and in the world - return false; - - std::string const name = std::string(target->GetName()); - if (!name.empty() && - (name.find("Dummy") != std::string::npos || - name.find("Charge Target") != std::string::npos || - name.find("Melee Target") != std::string::npos || - name.find("Ranged Target") != std::string::npos)) - { - return false; - } - - return true; -} - bool DropTargetAction::Execute(Event event) { Unit* target = context->GetValue("current target")->Get(); @@ -127,7 +96,38 @@ bool AttackAnythingAction::Execute(Event event) return result; } -bool AttackAnythingAction::isPossible() { return AttackAction::isPossible() && GetTarget(); } +bool AttackAnythingAction::isUseful() +{ + if (!bot || !botAI) // Prevents invalid accesses + return false; + + if (!botAI->AllowActivity(GRIND_ACTIVITY)) // Bot cannot be active + return false; + + if (botAI->HasStrategy("stay", BOT_STATE_NON_COMBAT)) + return false; + + if (bot->IsInCombat()) + return false; + + Unit* target = GetTarget(); + if (!target || !target->IsInWorld()) // Checks if the target is valid and in the world + return false; + + std::string const name = std::string(target->GetName()); + if (!name.empty() && + (name.find("Dummy") != std::string::npos || + name.find("Charge Target") != std::string::npos || + name.find("Melee Target") != std::string::npos || + name.find("Ranged Target") != std::string::npos)) + { + return false; + } + + return true; +} + +bool AttackAnythingAction::isPossible() { return GetTarget() && AttackAction::isPossible(); } bool DpsAssistAction::isUseful() { diff --git a/src/Ai/Base/Actions/FishingAction.h b/src/Ai/Base/Actions/FishingAction.h index 407825ed0..35a7ab6fe 100644 --- a/src/Ai/Base/Actions/FishingAction.h +++ b/src/Ai/Base/Actions/FishingAction.h @@ -7,22 +7,24 @@ #define _PLAYERBOT_FISHINGACTION_H #include "Action.h" -#include "MovementActions.h" #include "Event.h" +#include "MovementActions.h" #include "Playerbots.h" extern const uint32 FISHING_SPELL; extern const uint32 FISHING_POLE; extern const uint32 FISHING_BOBBER; -WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS=false, int numDirections = 16); +WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, + float maxDistance, float increment, bool checkLOS = false, int numDirections = 16); class PlayerbotAI; class FishingAction : public Action { public: - FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing"){} + FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing") {} + bool Execute(Event event) override; bool isUseful() override; }; @@ -31,8 +33,10 @@ class EquipFishingPoleAction : public Action { public: EquipFishingPoleAction(PlayerbotAI* botAI) : Action(botAI, "equip fishing pole") {} + bool Execute(Event event) override; bool isUseful() override; + private: Item* _pole = nullptr; }; @@ -40,7 +44,8 @@ private: class MoveNearWaterAction : public MovementAction { public: - MoveNearWaterAction(PlayerbotAI* botAI): MovementAction(botAI, "move near water") {} + MoveNearWaterAction(PlayerbotAI* botAI) : MovementAction(botAI, "move near water") {} + bool Execute(Event event) override; bool isUseful() override; bool isPossible() override; @@ -50,6 +55,7 @@ class UseBobberAction : public Action { public: UseBobberAction(PlayerbotAI* botAI) : Action(botAI, "use fishing bobber") {} + bool Execute(Event event) override; bool isUseful() override; }; @@ -58,6 +64,7 @@ class EndMasterFishingAction : public Action { public: EndMasterFishingAction(PlayerbotAI* botAI) : Action(botAI, "end master fishing") {} + bool Execute(Event event) override; bool isUseful() override; }; @@ -66,6 +73,8 @@ class RemoveBobberStrategyAction : public Action { public: RemoveBobberStrategyAction(PlayerbotAI* botAI) : Action(botAI, "remove bobber strategy") {} + bool Execute(Event event) override; }; + #endif diff --git a/src/Ai/Base/Actions/GenericSpellActions.cpp b/src/Ai/Base/Actions/GenericSpellActions.cpp index 819816f94..02d1decc6 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.cpp +++ b/src/Ai/Base/Actions/GenericSpellActions.cpp @@ -78,6 +78,35 @@ bool CastSpellAction::Execute(Event event) return botAI->CastSpell(spell, GetTarget()); } +bool CastSpellAction::isUseful() +{ + if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true)) + return false; + + if (spell == "mount" && !bot->IsMounted() && !bot->IsInCombat()) + return true; + + if (spell == "mount" && bot->IsInCombat()) + { + bot->Dismount(); + return false; + } + + Unit* spellTarget = GetTarget(); + if (!spellTarget) + return false; + + if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId()) + return false; + + // float combatReach = bot->GetCombatReach() + target->GetCombatReach(); + // if (!botAI->IsRanged(bot)) + // combatReach += 4.0f / 3.0f; + + return AI_VALUE2(bool, "spell cast useful", spell); + // && ServerFacade::instance().GetDistance2d(bot, target) <= (range + combatReach); +} + bool CastSpellAction::isPossible() { if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true)) @@ -106,36 +135,6 @@ bool CastSpellAction::isPossible() return botAI->CanCastSpell(spell, GetTarget()); } -bool CastSpellAction::isUseful() -{ - if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true)) - return false; - - if (spell == "mount" && !bot->IsMounted() && !bot->IsInCombat()) - return true; - - if (spell == "mount" && bot->IsInCombat()) - { - bot->Dismount(); - return false; - } - - Unit* spellTarget = GetTarget(); - if (!spellTarget) - return false; - - if (!spellTarget->IsInWorld() || spellTarget->GetMapId() != bot->GetMapId()) - return false; - - // float combatReach = bot->GetCombatReach() + spellTarget->GetCombatReach(); - // if (!botAI->IsRanged(bot)) - // combatReach += 4.0f / 3.0f; - - return spellTarget && - AI_VALUE2(bool, "spell cast useful", - spell); // && ServerFacade::instance().GetDistance2d(bot, spellTarget) <= (range + combatReach); -} - CastMeleeSpellAction::CastMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) { range = ATTACK_DISTANCE; diff --git a/src/Ai/Base/Actions/GenericSpellActions.h b/src/Ai/Base/Actions/GenericSpellActions.h index b148b93ff..9aa83f62d 100644 --- a/src/Ai/Base/Actions/GenericSpellActions.h +++ b/src/Ai/Base/Actions/GenericSpellActions.h @@ -23,8 +23,8 @@ public: std::string const GetTargetName() override { return "current target"; }; bool Execute(Event event) override; - bool isPossible() override; bool isUseful() override; + bool isPossible() override; ActionThreatType getThreatType() override { return ActionThreatType::Single; } std::vector getPrerequisites() override diff --git a/src/Ai/Base/Actions/NonCombatActions.cpp b/src/Ai/Base/Actions/NonCombatActions.cpp index 492da8ae6..9de1e0bae 100644 --- a/src/Ai/Base/Actions/NonCombatActions.cpp +++ b/src/Ai/Base/Actions/NonCombatActions.cpp @@ -49,18 +49,16 @@ bool DrinkAction::Execute(Event event) bool DrinkAction::isUseful() { - return UseItemAction::isUseful() && - AI_VALUE2(bool, "has mana", "self target") && - AI_VALUE2(uint8, "mana", "self target") < 100; + return UseItemAction::isUseful() && AI_VALUE2(bool, "has mana", "self target") && + AI_VALUE2(uint8, "mana", "self target") < 100; } bool DrinkAction::isPossible() { - return !bot->IsInCombat() && - !bot->IsMounted() && - !botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", - "aquatic form","flight form", "swift flight form", nullptr) && - (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible()); + return !bot->IsInCombat() && !bot->IsMounted() && + !botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "aquatic form", + "flight form", "swift flight form", nullptr) && + (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible()); } bool EatAction::Execute(Event event) @@ -102,17 +100,12 @@ bool EatAction::Execute(Event event) return UseItemAction::Execute(event); } -bool EatAction::isUseful() -{ - return UseItemAction::isUseful() && - AI_VALUE2(uint8, "health", "self target") < 100; -} +bool EatAction::isUseful() { return UseItemAction::isUseful() && AI_VALUE2(uint8, "health", "self target") < 100; } bool EatAction::isPossible() { - return !bot->IsInCombat() && - !bot->IsMounted() && - !botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", - "aquatic form","flight form", "swift flight form", nullptr) && - (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible()); + return !bot->IsInCombat() && !bot->IsMounted() && + !botAI->HasAnyAuraOf(GetTarget(), "dire bear form", "bear form", "cat form", "travel form", "aquatic form", + "flight form", "swift flight form", nullptr) && + (botAI->HasCheat(BotCheatMask::food) || UseItemAction::isPossible()); } diff --git a/src/Ai/Base/Actions/RpgAction.cpp b/src/Ai/Base/Actions/RpgAction.cpp index 919e25c58..1e461b068 100644 --- a/src/Ai/Base/Actions/RpgAction.cpp +++ b/src/Ai/Base/Actions/RpgAction.cpp @@ -85,7 +85,7 @@ bool RpgAction::SetNextRpgAction() isChecked = true; Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName()); - if (!dynamic_cast(action) || !action->isPossible() || !action->isUseful()) + if (!dynamic_cast(action) || !action->isUseful() || !action->isPossible()) continue; actions.push_back(action); diff --git a/src/Ai/Base/Actions/UseItemAction.cpp b/src/Ai/Base/Actions/UseItemAction.cpp index 473816e3e..77c865dba 100644 --- a/src/Ai/Base/Actions/UseItemAction.cpp +++ b/src/Ai/Base/Actions/UseItemAction.cpp @@ -7,9 +7,9 @@ #include "ChatHelper.h" #include "Event.h" +#include "ItemPackets.h" #include "ItemUsageValue.h" #include "Playerbots.h" -#include "ItemPackets.h" bool UseItemAction::Execute(Event event) { @@ -416,13 +416,6 @@ bool UseHearthStone::Execute(Event event) bool UseHearthStone::isUseful() { return !bot->InBattleground(); } -bool UseRandomRecipe::isUseful() -{ - return !bot->IsInCombat() && !botAI->HasActivePlayerMaster() && !bot->InBattleground(); -} - -bool UseRandomRecipe::isPossible() { return AI_VALUE2(uint32, "item count", "recipe") > 0; } - bool UseRandomRecipe::Execute(Event event) { std::vector recipes = AI_VALUE2(std::vector, "inventory items", "recipe"); @@ -445,12 +438,12 @@ bool UseRandomRecipe::Execute(Event event) return used; } -bool UseRandomQuestItem::isUseful() +bool UseRandomRecipe::isUseful() { - return !botAI->HasActivePlayerMaster() && !bot->InBattleground() && !bot->HasUnitState(UNIT_STATE_IN_FLIGHT); + return !bot->IsInCombat() && !botAI->HasActivePlayerMaster() && !bot->InBattleground(); } -bool UseRandomQuestItem::isPossible() { return AI_VALUE2(uint32, "item count", "quest") > 0; } +bool UseRandomRecipe::isPossible() { return AI_VALUE2(uint32, "item count", "recipe") > 0; } bool UseRandomQuestItem::Execute(Event event) { @@ -478,7 +471,6 @@ bool UseRandomQuestItem::Execute(Event event) break; } } - } if (!item) @@ -490,3 +482,10 @@ bool UseRandomQuestItem::Execute(Event event) return used; } + +bool UseRandomQuestItem::isUseful() +{ + return !botAI->HasActivePlayerMaster() && !bot->InBattleground() && !bot->HasUnitState(UNIT_STATE_IN_FLIGHT); +} + +bool UseRandomQuestItem::isPossible() { return AI_VALUE2(uint32, "item count", "quest") > 0; } diff --git a/src/Ai/Base/Actions/UseItemAction.h b/src/Ai/Base/Actions/UseItemAction.h index 2b0c7e191..263bc29dc 100644 --- a/src/Ai/Base/Actions/UseItemAction.h +++ b/src/Ai/Base/Actions/UseItemAction.h @@ -69,8 +69,8 @@ class UseHearthStone : public UseItemAction public: UseHearthStone(PlayerbotAI* botAI) : UseItemAction(botAI, "hearthstone", true) {} - bool isUseful() override; bool Execute(Event event) override; + bool isUseful() override; }; class UseRandomRecipe : public UseItemAction @@ -78,9 +78,9 @@ class UseRandomRecipe : public UseItemAction public: UseRandomRecipe(PlayerbotAI* botAI) : UseItemAction(botAI, "random recipe", true) {} + bool Execute(Event event) override; bool isUseful() override; bool isPossible() override; - bool Execute(Event event) override; }; class UseRandomQuestItem : public UseItemAction @@ -88,9 +88,9 @@ class UseRandomQuestItem : public UseItemAction public: UseRandomQuestItem(PlayerbotAI* botAI) : UseItemAction(botAI, "random quest item", true) {} + bool Execute(Event event) override; bool isUseful() override; bool isPossible() override; - bool Execute(Event event) override; }; #endif diff --git a/src/Ai/Class/Druid/Action/DruidShapeshiftActions.cpp b/src/Ai/Class/Druid/Action/DruidShapeshiftActions.cpp index 1f066dc34..42e639f93 100644 --- a/src/Ai/Class/Druid/Action/DruidShapeshiftActions.cpp +++ b/src/Ai/Class/Druid/Action/DruidShapeshiftActions.cpp @@ -7,20 +7,19 @@ #include "Playerbots.h" -bool CastBearFormAction::isPossible() -{ - return CastBuffSpellAction::isPossible() && !botAI->HasAura("dire bear form", GetTarget()); -} - bool CastBearFormAction::isUseful() { return CastBuffSpellAction::isUseful() && !botAI->HasAura("dire bear form", GetTarget()); } +bool CastBearFormAction::isPossible() +{ + return CastBuffSpellAction::isPossible() && !botAI->HasAura("dire bear form", GetTarget()); +} + std::vector CastDireBearFormAction::getAlternatives() { - return NextAction::merge({ NextAction("bear form") }, - CastSpellAction::getAlternatives()); + return NextAction::merge({NextAction("bear form")}, CastSpellAction::getAlternatives()); } bool CastTravelFormAction::isUseful() @@ -32,22 +31,17 @@ bool CastTravelFormAction::isUseful() !botAI->HasAura("dash", bot); } -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; } -bool CastCancelTreeFormAction::isUseful() +bool CastCasterFormAction::isUseful() { - return botAI->HasAura(33891, bot); + 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 CastCancelTreeFormAction::Execute(Event event) @@ -56,6 +50,8 @@ bool CastCancelTreeFormAction::Execute(Event event) return true; } +bool CastCancelTreeFormAction::isUseful() { return botAI->HasAura(33891, bot); } + bool CastTreeFormAction::isUseful() { return GetTarget() && CastSpellAction::isUseful() && !botAI->HasAura(33891, bot); diff --git a/src/Ai/Class/Druid/Action/DruidShapeshiftActions.h b/src/Ai/Class/Druid/Action/DruidShapeshiftActions.h index 26e14c42c..9d75f2682 100644 --- a/src/Ai/Class/Druid/Action/DruidShapeshiftActions.h +++ b/src/Ai/Class/Druid/Action/DruidShapeshiftActions.h @@ -15,8 +15,8 @@ class CastBearFormAction : public CastBuffSpellAction public: CastBearFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "bear form") {} - bool isPossible() override; bool isUseful() override; + bool isPossible() override; }; class CastDireBearFormAction : public CastBuffSpellAction @@ -37,6 +37,7 @@ class CastTreeFormAction : public CastBuffSpellAction { public: CastTreeFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "tree of life") {} + bool isUseful() override; }; @@ -65,9 +66,9 @@ class CastCasterFormAction : public CastBuffSpellAction public: CastCasterFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "caster form") {} + bool Execute(Event event) override; bool isUseful() override; bool isPossible() override { return true; } - bool Execute(Event event) override; }; class CastCancelTreeFormAction : public CastBuffSpellAction @@ -75,9 +76,9 @@ class CastCancelTreeFormAction : public CastBuffSpellAction public: CastCancelTreeFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "cancel tree form") {} + bool Execute(Event event) override; bool isUseful() override; bool isPossible() override { return true; } - bool Execute(Event event) override; }; #endif diff --git a/src/Ai/Class/Priest/Action/PriestActions.cpp b/src/Ai/Class/Priest/Action/PriestActions.cpp index ae55b104d..bdc33ac34 100644 --- a/src/Ai/Class/Priest/Action/PriestActions.cpp +++ b/src/Ai/Class/Priest/Action/PriestActions.cpp @@ -8,16 +8,14 @@ #include "Event.h" #include "Playerbots.h" -bool CastRemoveShadowformAction::isUseful() { return botAI->HasAura("shadowform", AI_VALUE(Unit*, "self target")); } - -bool CastRemoveShadowformAction::isPossible() { return true; } - bool CastRemoveShadowformAction::Execute(Event event) { botAI->RemoveAura("shadowform"); return true; } +bool CastRemoveShadowformAction::isUseful() { return botAI->HasAura("shadowform", AI_VALUE(Unit*, "self target")); } + Unit* CastPowerWordShieldOnAlmostFullHealthBelowAction::GetTarget() { Group* group = bot->GetGroup(); diff --git a/src/Ai/Class/Priest/Action/PriestActions.h b/src/Ai/Class/Priest/Action/PriestActions.h index 1b09414d4..4e94f27cc 100644 --- a/src/Ai/Class/Priest/Action/PriestActions.h +++ b/src/Ai/Class/Priest/Action/PriestActions.h @@ -56,7 +56,10 @@ HEAL_PARTY_ACTION(CastRenewOnPartyAction, "renew", 15.0f, HealingManaEfficiency: class CastPrayerOfMendingAction : public HealPartyMemberAction { public: - CastPrayerOfMendingAction(PlayerbotAI* botAI) : HealPartyMemberAction(botAI, "prayer of mending", 10.0f, HealingManaEfficiency::HIGH, false) {} + CastPrayerOfMendingAction(PlayerbotAI* botAI) + : HealPartyMemberAction(botAI, "prayer of mending", 10.0f, HealingManaEfficiency::HIGH, false) + { + } }; HEAL_PARTY_ACTION(CastBindingHealAction, "binding heal", 15.0f, HealingManaEfficiency::MEDIUM); @@ -65,7 +68,8 @@ HEAL_PARTY_ACTION(CastPrayerOfHealingAction, "prayer of healing", 15.0f, Healing class CastCircleOfHealingAction : public HealPartyMemberAction { public: - CastCircleOfHealingAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "circle of healing", 15.0f, HealingManaEfficiency::HIGH) + CastCircleOfHealingAction(PlayerbotAI* ai) + : HealPartyMemberAction(ai, "circle of healing", 15.0f, HealingManaEfficiency::HIGH) { } }; @@ -134,15 +138,15 @@ class CastRemoveShadowformAction : public Action public: CastRemoveShadowformAction(PlayerbotAI* botAI) : Action(botAI, "remove shadowform") {} - bool isUseful() override; - bool isPossible() override; bool Execute(Event event) override; + bool isUseful() override; }; class CastDispersionAction : public CastSpellAction { public: CastDispersionAction(PlayerbotAI* ai) : CastSpellAction(ai, "dispersion") {} + virtual std::string const GetTargetName() { return "self target"; } }; @@ -158,6 +162,7 @@ class CastHymnOfHopeAction : public CastSpellAction { public: CastHymnOfHopeAction(PlayerbotAI* ai) : CastSpellAction(ai, "hymn of hope") {} + virtual std::string const GetTargetName() { return "self target"; } }; @@ -165,6 +170,7 @@ class CastDivineHymnAction : public CastSpellAction { public: CastDivineHymnAction(PlayerbotAI* ai) : CastSpellAction(ai, "divine hymn") {} + virtual std::string const GetTargetName() { return "self target"; } }; @@ -172,6 +178,7 @@ class CastShadowfiendAction : public CastSpellAction { public: CastShadowfiendAction(PlayerbotAI* ai) : CastSpellAction(ai, "shadowfiend") {} + virtual std::string const GetTargetName() { return "current target"; } }; @@ -182,6 +189,7 @@ public: : HealPartyMemberAction(ai, "power word: shield", 15.0f, HealingManaEfficiency::HIGH) { } + bool isUseful() override; Unit* GetTarget() override; }; @@ -193,6 +201,7 @@ public: : HealPartyMemberAction(ai, "power word: shield", 5.0f, HealingManaEfficiency::HIGH) { } + bool isUseful() override; Unit* GetTarget() override; }; @@ -201,13 +210,17 @@ class CastMindSearAction : public CastSpellAction { public: CastMindSearAction(PlayerbotAI* ai) : CastSpellAction(ai, "mind sear") {} + ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } }; class CastGuardianSpiritOnPartyAction : public HealPartyMemberAction { public: - CastGuardianSpiritOnPartyAction(PlayerbotAI* ai) : HealPartyMemberAction(ai, "guardian spirit", 40.0f, HealingManaEfficiency::MEDIUM) {} + CastGuardianSpiritOnPartyAction(PlayerbotAI* ai) + : HealPartyMemberAction(ai, "guardian spirit", 40.0f, HealingManaEfficiency::MEDIUM) + { + } }; #endif diff --git a/src/Ai/Class/Rogue/Action/RogueActions.h b/src/Ai/Class/Rogue/Action/RogueActions.h index c31dfbd7e..dd0ad4735 100644 --- a/src/Ai/Class/Rogue/Action/RogueActions.h +++ b/src/Ai/Class/Rogue/Action/RogueActions.h @@ -27,6 +27,7 @@ class CastHungerForBloodAction : public CastBuffSpellAction { public: CastHungerForBloodAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "hunger for blood") {} + std::string const GetTargetName() override { return "current target"; } }; @@ -43,9 +44,9 @@ class CastStealthAction : public CastBuffSpellAction public: CastStealthAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "stealth") {} - std::string const GetTargetName() override { return "self target"; } bool isUseful() override; bool isPossible() override; + std::string const GetTargetName() override { return "self target"; } }; class UnstealthAction : public Action @@ -61,8 +62,8 @@ class CheckStealthAction : public Action public: CheckStealthAction(PlayerbotAI* botAI) : Action(botAI, "check stealth") {} - bool isPossible() override { return true; } bool Execute(Event event) override; + bool isPossible() override { return true; } }; class CastKickAction : public CastSpellAction @@ -131,6 +132,7 @@ class CastEnvenomAction : public CastMeleeSpellAction { public: CastEnvenomAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "envenom") {} + bool isUseful() override; bool isPossible() override; }; @@ -139,37 +141,42 @@ class CastTricksOfTheTradeOnMainTankAction : public BuffOnMainTankAction { public: CastTricksOfTheTradeOnMainTankAction(PlayerbotAI* ai) : BuffOnMainTankAction(ai, "tricks of the trade", true) {} - virtual bool isUseful() override; + + bool isUseful() override; }; class UseDeadlyPoisonAction : public UseItemAction { public: UseDeadlyPoisonAction(PlayerbotAI* ai) : UseItemAction(ai, "Deadly Poison") {} - virtual bool Execute(Event event) override; - virtual bool isPossible() override; + + bool Execute(Event event) override; + bool isPossible() override; }; class UseInstantPoisonAction : public UseItemAction { public: UseInstantPoisonAction(PlayerbotAI* ai) : UseItemAction(ai, "Instant Poison") {} - virtual bool Execute(Event event) override; - virtual bool isPossible() override; + + bool Execute(Event event) override; + bool isPossible() override; }; class UseInstantPoisonOffHandAction : public UseItemAction { public: UseInstantPoisonOffHandAction(PlayerbotAI* ai) : UseItemAction(ai, "Instant Poison Off Hand") {} - virtual bool Execute(Event event) override; - virtual bool isPossible() override; + + bool Execute(Event event) override; + bool isPossible() override; }; class FanOfKnivesAction : public CastMeleeSpellAction { public: FanOfKnivesAction(PlayerbotAI* ai) : CastMeleeSpellAction(ai, "fan of knives") {} + ActionThreatType getThreatType() override { return ActionThreatType::Aoe; } }; diff --git a/src/Ai/Class/Warrior/Action/WarriorActions.cpp b/src/Ai/Class/Warrior/Action/WarriorActions.cpp index 9733226a9..0bde24cd9 100644 --- a/src/Ai/Class/Warrior/Action/WarriorActions.cpp +++ b/src/Ai/Class/Warrior/Action/WarriorActions.cpp @@ -176,20 +176,19 @@ Unit* CastShatteringThrowAction::GetTarget() return nullptr; // No valid target } +bool CastShatteringThrowAction::Execute(Event event) +{ + Unit* target = GetTarget(); + if (!target) + return false; + + return botAI->CastSpell("shattering throw", target); +} + bool CastShatteringThrowAction::isUseful() { - - // Spell cooldown check - if (!bot->HasSpell(64382)) - { + if (!bot->HasSpell(64382) || bot->HasSpellCooldown(64382)) return false; - } - - // Spell cooldown check - if (bot->HasSpellCooldown(64382)) - { - return false; - } GuidVector enemies = AI_VALUE(GuidVector, "possible targets"); @@ -220,25 +219,12 @@ bool CastShatteringThrowAction::isPossible() // Range check: Shattering Throw is 30 yards if (!bot->IsWithinDistInMap(target, 30.0f)) - { return false; - } // Check line of sight if (!bot->IsWithinLOSInMap(target)) - { return false; - } // If the minimal checks above pass, simply return true. return true; } - -bool CastShatteringThrowAction::Execute(Event event) -{ - Unit* target = GetTarget(); - if (!target) - return false; - - return botAI->CastSpell("shattering throw", target); -} diff --git a/src/Ai/Class/Warrior/Action/WarriorActions.h b/src/Ai/Class/Warrior/Action/WarriorActions.h index ea72fb269..7910fc0d8 100644 --- a/src/Ai/Class/Warrior/Action/WarriorActions.h +++ b/src/Ai/Class/Warrior/Action/WarriorActions.h @@ -25,8 +25,7 @@ MELEE_ACTION_U(CastBattleShoutTauntAction, "battle shout", CastSpellAction::isUs class CastDemoralizingShoutAction : public CastMeleeDebuffSpellAction { public: - CastDemoralizingShoutAction(PlayerbotAI* botAI) - : CastMeleeDebuffSpellAction(botAI, "demoralizing shout") {} + CastDemoralizingShoutAction(PlayerbotAI* botAI) : CastMeleeDebuffSpellAction(botAI, "demoralizing shout") {} }; class CastDemoralizingShoutWithoutLifeTimeCheckAction : public CastMeleeDebuffSpellAction @@ -140,8 +139,8 @@ class CastVigilanceAction : public BuffOnPartyAction public: CastVigilanceAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "vigilance") {} - Unit* GetTarget() override; bool Execute(Event event) override; + Unit* GetTarget() override; }; class CastRetaliationAction : public CastBuffSpellAction @@ -157,10 +156,10 @@ class CastShatteringThrowAction : public CastSpellAction public: CastShatteringThrowAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shattering throw") {} - Unit* GetTarget() override; + bool Execute(Event event) override; bool isUseful() override; bool isPossible() override; - bool Execute(Event event) override; + Unit* GetTarget() override; }; #endif diff --git a/src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.h b/src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.h index c6fe064c0..0d5b6fcc3 100644 --- a/src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.h +++ b/src/Ai/Raid/EyeOfEternity/Action/RaidEoEActions.h @@ -1,9 +1,9 @@ #ifndef _PLAYERBOT_RAIDEOEACTIONS_H #define _PLAYERBOT_RAIDEOEACTIONS_H -#include "MovementActions.h" #include "AttackAction.h" #include "GenericSpellActions.h" +#include "MovementActions.h" #include "PlayerbotAI.h" #include "Playerbots.h" @@ -13,34 +13,38 @@ const std::pair MALYGOS_STACK_POSITION = {755.0f, 1301.0f}; class MalygosPositionAction : public MovementAction { public: - MalygosPositionAction(PlayerbotAI* botAI, std::string const name = "malygos position") - : MovementAction(botAI, name) {} + MalygosPositionAction(PlayerbotAI* botAI, std::string const name = "malygos position") : MovementAction(botAI, name) + { + } + bool Execute(Event event) override; }; class MalygosTargetAction : public AttackAction { public: - MalygosTargetAction(PlayerbotAI* botAI, std::string const name = "malygos target") - : AttackAction(botAI, name) {} + MalygosTargetAction(PlayerbotAI* botAI, std::string const name = "malygos target") : AttackAction(botAI, name) {} + bool Execute(Event event) override; }; -class PullPowerSparkAction : public CastSpellAction -{ -public: - PullPowerSparkAction(PlayerbotAI* botAI, std::string const name = "pull power spark") - : CastSpellAction(botAI, name) {} - bool Execute(Event event) override; - bool isPossible() override; - bool isUseful() override; -}; +//class PullPowerSparkAction : public CastSpellAction +//{ +//public: +// PullPowerSparkAction(PlayerbotAI* botAI, std::string const name = "pull power spark") : CastSpellAction(botAI, name) +// { +// } + +// bool Execute(Event event) override; +// bool isUseful() override; +// bool isPossible() override; +//}; class KillPowerSparkAction : public AttackAction { public: - KillPowerSparkAction(PlayerbotAI* botAI, std::string const name = "kill power spark") - : AttackAction(botAI, name) {} + KillPowerSparkAction(PlayerbotAI* botAI, std::string const name = "kill power spark") : AttackAction(botAI, name) {} + bool Execute(Event event) override; }; @@ -48,6 +52,7 @@ class EoEFlyDrakeAction : public MovementAction { public: EoEFlyDrakeAction(PlayerbotAI* ai) : MovementAction(ai, "eoe fly drake") {} + bool Execute(Event event) override; bool isPossible() override; }; @@ -56,6 +61,7 @@ class EoEDrakeAttackAction : public Action { public: EoEDrakeAttackAction(PlayerbotAI* botAI) : Action(botAI, "eoe drake attack") {} + bool Execute(Event event) override; bool isPossible() override; diff --git a/src/Bot/Engine/Action/Action.h b/src/Bot/Engine/Action/Action.h index 2395c5ea8..6c54d24b9 100644 --- a/src/Bot/Engine/Action/Action.h +++ b/src/Bot/Engine/Action/Action.h @@ -60,8 +60,27 @@ public: virtual ~Action(void) {} virtual bool Execute([[maybe_unused]] Event event) { return true; } - virtual bool isPossible() { return true; } + + /** + * @brief First validation check - determines if this action is contextually useful + * + * Performs lightweight checks to evaluate whether the action makes sense + * in the current situation. Called before isPossible() during action selection. + * + * @return true if the action is useful, false otherwise + */ virtual bool isUseful() { return true; } + + /** + * @brief Second validation check - determines if this action can be executed + * + * Performs hard pre-execution validation against the event and game state. + * Called after isUseful() passes, before Execute(). + * + * @return true if the action is possible, false otherwise + */ + virtual bool isPossible() { return true; } + virtual std::vector getPrerequisites() { return {}; } virtual std::vector getAlternatives() { return {}; } virtual std::vector getContinuers() { return {}; } diff --git a/src/Bot/Engine/Engine.cpp b/src/Bot/Engine/Engine.cpp index bc24baf56..bb4f2eb35 100644 --- a/src/Bot/Engine/Engine.cpp +++ b/src/Bot/Engine/Engine.cpp @@ -323,18 +323,18 @@ ActionResult Engine::ExecuteAction(std::string const name, Event event, std::str q->Qualify(qualifier); } - if (!action->isPossible()) - { - delete actionNode; - return ACTION_RESULT_IMPOSSIBLE; - } - if (!action->isUseful()) { delete actionNode; return ACTION_RESULT_USELESS; } + if (!action->isPossible()) + { + delete actionNode; + return ACTION_RESULT_IMPOSSIBLE; + } + action->MakeVerbose(); result = ListenAndExecute(action, event);