diff --git a/src/AiFactory.cpp b/src/AiFactory.cpp index 063f3411..09401a82 100644 --- a/src/AiFactory.cpp +++ b/src/AiFactory.cpp @@ -274,6 +274,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa { engine->addStrategy("avoid aoe"); } + engine->addStrategy("combat formation"); switch (player->getClass()) { case CLASS_PRIEST: diff --git a/src/strategy/StrategyContext.h b/src/strategy/StrategyContext.h index 55bdc4bc..82090440 100644 --- a/src/strategy/StrategyContext.h +++ b/src/strategy/StrategyContext.h @@ -112,6 +112,7 @@ class StrategyContext : public NamedObjectContext creators["grind"] = &StrategyContext::grind; creators["avoid aoe"] = &StrategyContext::avoid_aoe; creators["move random"] = &StrategyContext::move_random; + creators["combat formation"] = &StrategyContext::combat_formation; } private: @@ -174,6 +175,7 @@ class StrategyContext : public NamedObjectContext static Strategy* grind(PlayerbotAI* botAI) { return new GrindingStrategy(botAI); } static Strategy* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeStrategy(botAI); } static Strategy* move_random(PlayerbotAI* ai) { return new MoveRandomStrategy(ai); } + static Strategy* combat_formation(PlayerbotAI* ai) { return new CombatFormationStrategy(ai); } }; class MovementStrategyContext : public NamedObjectContext diff --git a/src/strategy/Value.h b/src/strategy/Value.h index 9cba6efe..e27600ad 100644 --- a/src/strategy/Value.h +++ b/src/strategy/Value.h @@ -326,4 +326,10 @@ class UnitManualSetValue : public ManualSetValue Unit* Get() override; }; +class DisperseDistanceValue : public ManualSetValue +{ + public: + DisperseDistanceValue(PlayerbotAI* botAI, float defaultValue = -1.0f, std::string const name = "disperse value") : + ManualSetValue(botAI, defaultValue, name) { } +}; #endif diff --git a/src/strategy/actions/ActionContext.h b/src/strategy/actions/ActionContext.h index 6cdc68b1..f2c1062c 100644 --- a/src/strategy/actions/ActionContext.h +++ b/src/strategy/actions/ActionContext.h @@ -89,6 +89,8 @@ class ActionContext : public NamedObjectContext creators["flee"] = &ActionContext::flee; creators["flee with pet"] = &ActionContext::flee_with_pet; creators["avoid aoe"] = &ActionContext::avoid_aoe; + creators["combat formation move"] = &ActionContext::combat_formation_move; + creators["disperse set"] = &ActionContext::disperse_set; creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru; creators["shoot"] = &ActionContext::shoot; creators["lifeblood"] = &ActionContext::lifeblood; @@ -265,6 +267,8 @@ class ActionContext : public NamedObjectContext static Action* flee(PlayerbotAI* botAI) { return new FleeAction(botAI); } static Action* flee_with_pet(PlayerbotAI* botAI) { return new FleeWithPetAction(botAI); } static Action* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeAction(botAI); } + static Action* combat_formation_move(PlayerbotAI* botAI) { return new CombatFormationMoveAction(botAI); } + static Action* disperse_set(PlayerbotAI* botAI) { return new DisperseSetAction(botAI); } static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); } static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); } static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); } diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index f2652168..0a47c8ea 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -12,6 +12,7 @@ #include "ObjectGuid.h" #include "PathGenerator.h" #include "PlayerbotAIConfig.h" +#include "Position.h" #include "Random.h" #include "SharedDefines.h" #include "SpellAuraEffects.h" @@ -25,10 +26,13 @@ #include "LootObjectStack.h" #include "Playerbots.h" #include "ServerFacade.h" +#include "Timer.h" #include "Transport.h" #include "Unit.h" #include "Vehicle.h" #include "WaypointMovementGenerator.h" +#include +#include MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) { @@ -1495,6 +1499,9 @@ bool FleeWithPetAction::Execute(Event event) bool AvoidAoeAction::isUseful() { + if (getMSTime() - moveInterval < lastMoveTimer) { + return false; + } GuidVector traps = AI_VALUE(GuidVector, "nearest trap with damage"); GuidVector triggers = AI_VALUE(GuidVector, "possible triggers"); return AI_VALUE(Aura*, "area debuff") || !traps.empty() || !triggers.empty(); @@ -1504,14 +1511,17 @@ bool AvoidAoeAction::Execute(Event event) { // Case #1: Aura with dynamic object (e.g. rain of fire) if (AvoidAuraWithDynamicObj()) { + lastMoveTimer = getMSTime(); return true; } // Case #2: Trap game object with spell (e.g. lava bomb) if (AvoidGameObjectWithDamage()) { + lastMoveTimer = getMSTime(); return true; } // Case #3: Trigger npc (e.g. Lesser shadow fissure) if (AvoidUnitWithDamageAura()) { + lastMoveTimer = getMSTime(); return true; } return false; @@ -1541,7 +1551,13 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj() } std::ostringstream name; name << spellInfo->SpellName[0]; // << "] (aura)"; - if (FleePosition(dynOwner->GetPosition(), radius, name.str())) { + if (FleePosition(dynOwner->GetPosition(), radius)) { + if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) { + lastTellTimer = time(NULL); + std::ostringstream out; + out << "I'm avoiding " << name.str() << "..."; + bot->Say(out.str(), LANG_UNIVERSAL); + } return true; } return false; @@ -1592,7 +1608,13 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage() } std::ostringstream name; name << spellInfo->SpellName[0]; // << "] (object)"; - if (FleePosition(go->GetPosition(), radius, name.str())) { + if (FleePosition(go->GetPosition(), radius)) { + if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) { + lastTellTimer = time(NULL); + std::ostringstream out; + out << "I'm avoiding " << name.str() << "..."; + bot->Say(out.str(), LANG_UNIVERSAL); + } return true; } @@ -1634,8 +1656,13 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura() } std::ostringstream name; name << triggerSpellInfo->SpellName[0]; //<< "] (unit)"; - if (FleePosition(unit->GetPosition(), radius, name.str())) { - return true; + if (FleePosition(unit->GetPosition(), radius)) { + if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) { + lastTellTimer = time(NULL); + std::ostringstream out; + out << "I'm avoiding " << name.str() << "..."; + bot->Say(out.str(), LANG_UNIVERSAL); + } } } } @@ -1645,7 +1672,7 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura() return false; } -Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius) +Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius) { Unit* currentTarget = AI_VALUE(Unit*, "current target"); std::vector possibleAngles; @@ -1671,7 +1698,7 @@ Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius) for (CheckAngle &checkAngle : possibleAngles) { float angle = checkAngle.angle; bool strict = checkAngle.strict; - float fleeDis = sPlayerbotAIConfig->fleeDistance; + float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance); Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis, bot->GetPositionY() + sin(angle) * fleeDis, bot->GetPositionZ()}; @@ -1690,7 +1717,7 @@ Position AvoidAoeAction::BestPositionForMelee(Position pos, float radius) return Position(); } -Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius) +Position MovementAction::BestPositionForRangedToFlee(Position pos, float radius) { Unit* currentTarget = AI_VALUE(Unit*, "current target"); std::vector possibleAngles; @@ -1714,7 +1741,7 @@ Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius) for (CheckAngle &checkAngle : possibleAngles) { float angle = checkAngle.angle; bool strict = checkAngle.strict; - float fleeDis = sPlayerbotAIConfig->fleeDistance; + float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance); Position fleePos{bot->GetPositionX() + cos(angle) * fleeDis, bot->GetPositionY() + sin(angle) * fleeDis, bot->GetPositionZ()}; @@ -1737,28 +1764,162 @@ Position AvoidAoeAction::BestPositionForRanged(Position pos, float radius) return Position(); } -bool AvoidAoeAction::FleePosition(Position pos, float radius, std::string name) +bool MovementAction::FleePosition(Position pos, float radius) { Position bestPos; if (botAI->IsMelee(bot)) { - bestPos = BestPositionForMelee(pos, radius); + bestPos = BestPositionForMeleeToFlee(pos, radius); } else { - bestPos = BestPositionForRanged(pos, radius); + bestPos = BestPositionForRangedToFlee(pos, radius); } if (bestPos != Position()) { if (MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, true)) { - if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10) { - lastTellTimer = time(NULL); - std::ostringstream out; - out << "I'm avoiding " << name << "..."; - bot->Say(out.str(), LANG_UNIVERSAL); - } return true; } } return false; } +bool CombatFormationMoveAction::isUseful() +{ + if (getMSTime() - moveInterval < lastMoveTimer) { + return false; + } + if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr) { + return false; + } + float dis = AI_VALUE(float, "disperse distance"); + return dis > 0.0f; +} + +bool CombatFormationMoveAction::Execute(Event event) +{ + float dis = AI_VALUE(float, "disperse distance"); + Player* playerToLeave = NearestGroupMember(dis); + if (playerToLeave && bot->GetExactDist(playerToLeave) < dis) { + if (FleePosition(playerToLeave->GetPosition(), dis)) { + lastMoveTimer = getMSTime(); + } + } + return false; +} + +Position CombatFormationMoveAction::AverageGroupPos(float dis) +{ + float averageX = 0, averageY = 0, averageZ = 0; + int cnt = 0; + Group* group = bot->GetGroup(); + if (!group) { + return Position(); + } + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *member = ObjectAccessor::FindPlayer(itr->guid); + if (!member || !member->IsAlive() || member->GetMapId() != bot->GetMapId() || member->IsCharmed() || sServerFacade->GetDistance2d(bot, member) > dis) + continue; + cnt++; + averageX += member->GetPositionX(); + averageY += member->GetPositionY(); + averageZ += member->GetPositionZ(); + } + averageX /= cnt; + averageY /= cnt; + averageZ /= cnt; + return Position(averageX, averageY, averageZ); +} + +Player* CombatFormationMoveAction::NearestGroupMember(float dis) +{ + float nearestDis = 10000.0f; + Player* result = nullptr; + Group* group = bot->GetGroup(); + if (!group) { + return result; + } + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player *member = ObjectAccessor::FindPlayer(itr->guid); + if (!member || !member->IsAlive() || member == bot || member->GetMapId() != bot->GetMapId() || member->IsCharmed() || sServerFacade->GetDistance2d(bot, member) > dis) + continue; + if (nearestDis > bot->GetExactDist(member)) { + result = member; + nearestDis = bot->GetExactDist(member); + } + } + return result; +} + +bool DisperseSetAction::Execute(Event event) +{ + std::string const text = event.getParam(); + if (text == "disable") { + RESET_AI_VALUE(float, "disperse distance"); + botAI->TellMasterNoFacing("Disable disperse"); + return true; + } + if (text == "enable" || text == "reset") { + if (botAI->IsMelee(bot)) { + SET_AI_VALUE(float, "disperse distance", DEFAULT_DISPERSE_DISTANCE_MELEE); + } else { + SET_AI_VALUE(float, "disperse distance", DEFAULT_DISPERSE_DISTANCE_RANGED); + } + float dis = AI_VALUE(float, "disperse distance"); + std::ostringstream out; + out << "Enable disperse distance " << std::setprecision(2) << dis; + botAI->TellMasterNoFacing(out.str()); + return true; + } + if (text == "increase") { + float dis = AI_VALUE(float, "disperse distance"); + std::ostringstream out; + if (dis <= 0.0f) { + out << "Enable disperse first"; + botAI->TellMasterNoFacing(out.str()); + return true; + } + dis += 1.0f; + SET_AI_VALUE(float, "disperse distance", dis); + out << "Increase disperse distance to " << std::setprecision(2) << dis; + botAI->TellMasterNoFacing(out.str()); + return true; + } + if (text == "decrease") { + float dis = AI_VALUE(float, "disperse distance"); + dis -= 1.0f; + if (dis <= 0.0f) { + dis += 1.0f; + } + SET_AI_VALUE(float, "disperse distance", dis); + std::ostringstream out; + out << "Increase disperse distance to " << std::setprecision(2) << dis; + botAI->TellMasterNoFacing(out.str()); + return true; + } + if (text.starts_with("set")) { + float dis = -1.0f;; + sscanf(text.c_str(), "set %f", &dis); + std::ostringstream out; + if (dis < 0 || dis > 100.0f) { + out << "Invalid disperse distance " << std::setprecision(2) << dis; + } else { + SET_AI_VALUE(float, "disperse distance", dis); + out << "Set disperse distance to " << std::setprecision(2) << dis; + } + botAI->TellMasterNoFacing(out.str()); + return true; + } + std::ostringstream out; + out << "Usage: disperse [enable|disable|increase|decrease|set {distance}]" << "\n"; + float dis = AI_VALUE(float, "disperse distance"); + if (dis > 0.0f) { + out << "Current disperse distance: " << std::setprecision(2) << dis; + } + botAI->TellMasterNoFacing(out.str()); + return true; +} + bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "master target")); diff --git a/src/strategy/actions/MovementActions.h b/src/strategy/actions/MovementActions.h index f26991a4..ed9527af 100644 --- a/src/strategy/actions/MovementActions.h +++ b/src/strategy/actions/MovementActions.h @@ -41,6 +41,14 @@ class MovementAction : public Action bool MoveAway(Unit* target); bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance); void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false); + Position BestPositionForMeleeToFlee(Position pos, float radius); + Position BestPositionForRangedToFlee(Position pos, float radius); + bool FleePosition(Position pos, float radius); + protected: + struct CheckAngle { + float angle; + bool strict; + }; private: // float SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range = 20.0f, bool normal_only = false, float step = 8.0f); const Movement::PointsArray SearchForBestPath(float x, float y, float z, float &modified_z, int maxSearchCount = 5, bool normal_only = false, float step = 8.0f); @@ -69,7 +77,8 @@ class FleeWithPetAction : public MovementAction class AvoidAoeAction : public MovementAction { public: - AvoidAoeAction(PlayerbotAI* botAI) : MovementAction(botAI, "avoid aoe") { } + AvoidAoeAction(PlayerbotAI* botAI, int moveInterval = 1000) : MovementAction(botAI, "avoid aoe"), + moveInterval(moveInterval) { } bool isUseful() override; bool Execute(Event event) override; @@ -78,14 +87,36 @@ class AvoidAoeAction : public MovementAction bool AvoidAuraWithDynamicObj(); bool AvoidGameObjectWithDamage(); bool AvoidUnitWithDamageAura(); - Position BestPositionForMelee(Position pos, float radius); - Position BestPositionForRanged(Position pos, float radius); - bool FleePosition(Position pos, float radius, std::string name); time_t lastTellTimer = 0; - struct CheckAngle { - float angle; - bool strict; - }; + int lastMoveTimer = 0; + int moveInterval; + +}; + +class CombatFormationMoveAction : public MovementAction +{ + public: + CombatFormationMoveAction(PlayerbotAI* botAI, int moveInterval = 1000) : MovementAction(botAI, "combat formation move"), + moveInterval(moveInterval) { } + + bool isUseful() override; + bool Execute(Event event) override; + + protected: + Position AverageGroupPos(float dis = sPlayerbotAIConfig->sightDistance); + Player* NearestGroupMember(float dis = sPlayerbotAIConfig->sightDistance); + int lastMoveTimer = 0; + int moveInterval; +}; + +class DisperseSetAction : public Action +{ + public: + DisperseSetAction(PlayerbotAI* botAI, std::string const name = "disperse set") : Action(botAI, name) { } + + bool Execute(Event event) override; + float DEFAULT_DISPERSE_DISTANCE_RANGED = 5.0f; + float DEFAULT_DISPERSE_DISTANCE_MELEE = 2.0f; }; class RunAwayAction : public MovementAction diff --git a/src/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/strategy/generic/ChatCommandHandlerStrategy.cpp index 4da74e5b..0f149960 100644 --- a/src/strategy/generic/ChatCommandHandlerStrategy.cpp +++ b/src/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -61,6 +61,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector& trigger triggers.push_back(new TriggerNode("naxx", NextAction::array(0, new NextAction("naxx chat shortcut", relevance), NULL))); triggers.push_back(new TriggerNode("bwl", NextAction::array(0, new NextAction("bwl chat shortcut", relevance), NULL))); triggers.push_back(new TriggerNode("dps", NextAction::array(0, new NextAction("tell expected dps", relevance), NULL))); + triggers.push_back(new TriggerNode("disperse", NextAction::array(0, new NextAction("disperse set", relevance), NULL))); } ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) diff --git a/src/strategy/generic/CombatStrategy.cpp b/src/strategy/generic/CombatStrategy.cpp index 3b72af7f..c9e3a95c 100644 --- a/src/strategy/generic/CombatStrategy.cpp +++ b/src/strategy/generic/CombatStrategy.cpp @@ -81,4 +81,11 @@ void AvoidAoeStrategy::InitTriggers(std::vector& triggers) void AvoidAoeStrategy::InitMultipliers(std::vector& multipliers) { // multipliers.push_back(new AvoidAoeStrategyMultiplier(botAI)); -} \ No newline at end of file +} + +NextAction** CombatFormationStrategy::getDefaultActions() +{ + return NextAction::array(0, + new NextAction("combat formation move", ACTION_EMERGENCY), + nullptr); +} diff --git a/src/strategy/generic/CombatStrategy.h b/src/strategy/generic/CombatStrategy.h index 33813615..7f618064 100644 --- a/src/strategy/generic/CombatStrategy.h +++ b/src/strategy/generic/CombatStrategy.h @@ -28,4 +28,12 @@ public: void InitTriggers(std::vector& triggers) override; }; +class CombatFormationStrategy : public Strategy +{ +public: + CombatFormationStrategy(PlayerbotAI* ai): Strategy(ai) {} + const std::string getName() override { return "combat formation"; } + NextAction** getDefaultActions() override; +}; + #endif diff --git a/src/strategy/triggers/ChatTriggerContext.h b/src/strategy/triggers/ChatTriggerContext.h index af52cd7e..598154ef 100644 --- a/src/strategy/triggers/ChatTriggerContext.h +++ b/src/strategy/triggers/ChatTriggerContext.h @@ -119,6 +119,7 @@ class ChatTriggerContext : public NamedObjectContext creators["naxx"] = &ChatTriggerContext::naxx; creators["bwl"] = &ChatTriggerContext::bwl; creators["dps"] = &ChatTriggerContext::dps; + creators["disperse"] = &ChatTriggerContext::disperse; } private: @@ -218,6 +219,7 @@ class ChatTriggerContext : public NamedObjectContext static Trigger* naxx(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "naxx"); } static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); } static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); } + static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); } }; #endif diff --git a/src/strategy/values/ValueContext.h b/src/strategy/values/ValueContext.h index 4298b44e..fe08de8b 100644 --- a/src/strategy/values/ValueContext.h +++ b/src/strategy/values/ValueContext.h @@ -301,6 +301,7 @@ class ValueContext : public NamedObjectContext creators["expected group dps"] = &ValueContext::expected_group_dps; creators["area debuff"] = &ValueContext::area_debuff; creators["nearest trap with damage"] = &ValueContext::nearest_trap_with_damange; + creators["disperse distance"] = &ValueContext::disperse_distance; } private: @@ -505,6 +506,7 @@ class ValueContext : public NamedObjectContext static UntypedValue* expected_group_dps(PlayerbotAI* ai) { return new ExpectedGroupDpsValue(ai); } static UntypedValue* area_debuff(PlayerbotAI* ai) { return new AreaDebuffValue(ai); } static UntypedValue* nearest_trap_with_damange(PlayerbotAI* ai) { return new NearestTrapWithDamageValue(ai); } + static UntypedValue* disperse_distance(PlayerbotAI* ai) { return new DisperseDistanceValue(ai); } }; #endif