From 6c1ddd3b77bf1b2a5a71d570956d6e17575910fc Mon Sep 17 00:00:00 2001 From: avirar Date: Mon, 21 Oct 2024 14:42:32 +1100 Subject: [PATCH 01/19] Update OldKingdomActions.cpp Null checks added --- .../wotlk/oldkingdom/OldKingdomActions.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp b/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp index 41b410bf..d13fae54 100644 --- a/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp +++ b/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp @@ -10,7 +10,6 @@ bool AttackNadoxGuardianAction::Execute(Event event) { return false; } - return Attack(target); } @@ -24,6 +23,10 @@ bool AttackJedogaVolunteerAction::Execute(Event event) for (auto i = targets.begin(); i != targets.end(); ++i) { Unit* unit = botAI->GetUnit(*i); + if (!unit) // Null check for safety + { + continue; // Skip null or invalid units + } if (unit && unit->GetEntry() == NPC_TWILIGHT_VOLUNTEER) { target = unit; @@ -55,6 +58,10 @@ bool AvoidShadowCrashAction::Execute(Event event) // This doesn't seem to avoid casts very well, perhaps because this isn't checked while allies are casting. // TODO: Revisit if this is an issue in heroics, otherwise ignore shadow crashes for the most part. victim = botAI->GetUnit(unit->GetTarget()); + if (!victim) + { + return false; // Exit early if no victim is found + } if (victim && bot->GetExactDist2d(victim) < radius) { return MoveAway(victim, targetDist - bot->GetExactDist2d(victim->GetPosition())); @@ -65,12 +72,21 @@ bool AvoidShadowCrashAction::Execute(Event event) if (botAI->IsMelee(bot)) { return false; } GuidVector members = AI_VALUE(GuidVector, "group members"); + if (members.empty()) + { + return false; // Exit early if no group members are found + } for (auto& member : members) { if (bot->GetGUID() == member) { continue; } + Unit* memberUnit = botAI->GetUnit(member); + if (!memberUnit) + { + continue; // Skip if the memberUnit is null + } float currentDist = bot->GetExactDist2d(botAI->GetUnit(member)); if (currentDist < radius) { From c788e96828ed34cbc4109140f9ce43ce3c176c62 Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Mon, 21 Oct 2024 22:29:03 +1100 Subject: [PATCH 02/19] UP and CoS dungeons - Utgarde Pinnacle implementation - Culling of Stratholme implementation - Added additional value ("nearest hostile npcs") needed to expose some hidden trigger-type npc units (eg. frost breath on Skadi fight in UP) --- src/strategy/AiObjectContext.cpp | 4 + .../dungeons/DungeonStrategyContext.h | 15 ++-- .../wotlk/WotlkDungeonActionContext.h | 4 +- .../wotlk/WotlkDungeonTriggerContext.h | 4 +- .../CullingOfStratholmeActionContext.h | 20 +++++ .../CullingOfStratholmeActions.cpp | 54 ++++++++++++ .../CullingOfStratholmeActions.h | 26 ++++++ .../CullingOfStratholmeMultipliers.cpp | 19 +++++ .../CullingOfStratholmeMultipliers.h | 15 ++++ .../CullingOfStratholmeStrategy.cpp | 27 ++++++ .../CullingOfStratholmeStrategy.h | 18 ++++ .../CullingOfStratholmeTriggerContext.h | 22 +++++ .../CullingOfStratholmeTriggers.cpp | 32 +++++++ .../CullingOfStratholmeTriggers.h | 29 +++++++ .../dungeons/wotlk/cullingofstratholme/TODO | 0 .../draktharonkeep/DrakTharonKeepActions.cpp | 8 +- .../HallsOfLightningActions.cpp | 7 +- .../dungeons/wotlk/utgardepinnacle/TODO | 0 .../UtgardePinnacleActionContext.h | 20 +++++ .../UtgardePinnacleActions.cpp | 61 ++++++++++++++ .../utgardepinnacle/UtgardePinnacleActions.h | 24 ++++++ .../UtgardePinnacleMultipliers.cpp | 84 +++++++++++++++++++ .../UtgardePinnacleMultipliers.h | 15 ++++ .../UtgardePinnacleStrategy.cpp | 25 ++++++ .../utgardepinnacle/UtgardePinnacleStrategy.h | 18 ++++ .../UtgardePinnacleTriggerContext.h | 21 +++++ .../UtgardePinnacleTriggers.cpp | 48 +++++++++++ .../utgardepinnacle/UtgardePinnacleTriggers.h | 39 +++++++++ src/strategy/values/NearestNpcsValue.cpp | 9 ++ src/strategy/values/NearestNpcsValue.h | 13 +++ src/strategy/values/ValueContext.h | 2 + 31 files changed, 665 insertions(+), 18 deletions(-) create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActionContext.h create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActions.cpp create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActions.h create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeMultipliers.cpp create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeMultipliers.h create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeStrategy.cpp create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggerContext.h create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggers.cpp create mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggers.h delete mode 100644 src/strategy/dungeons/wotlk/cullingofstratholme/TODO delete mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/TODO create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActions.cpp create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActions.h create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.cpp create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.h create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.cpp create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.h create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggerContext.h create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp create mode 100644 src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.h diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index b8121320..9b52369e 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -59,6 +59,8 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new WotlkDungeonGDActionContext()); actionContexts.Add(new WotlkDungeonHoSActionContext()); actionContexts.Add(new WotlkDungeonHoLActionContext()); + actionContexts.Add(new WotlkDungeonUPActionContext()); + actionContexts.Add(new WotlkDungeonCoSActionContext()); triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new ChatTriggerContext()); @@ -78,6 +80,8 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new WotlkDungeonGDTriggerContext()); triggerContexts.Add(new WotlkDungeonHoSTriggerContext()); triggerContexts.Add(new WotlkDungeonHoLTriggerContext()); + triggerContexts.Add(new WotlkDungeonUPTriggerContext()); + triggerContexts.Add(new WotlkDungeonCoSTriggerContext()); valueContexts.Add(new ValueContext()); diff --git a/src/strategy/dungeons/DungeonStrategyContext.h b/src/strategy/dungeons/DungeonStrategyContext.h index 5ed70f6c..3bb39c18 100644 --- a/src/strategy/dungeons/DungeonStrategyContext.h +++ b/src/strategy/dungeons/DungeonStrategyContext.h @@ -11,16 +11,14 @@ #include "wotlk/gundrak/GundrakStrategy.h" #include "wotlk/hallsofstone/HallsOfStoneStrategy.h" #include "wotlk/hallsoflightning/HallsOfLightningStrategy.h" +#include "wotlk/utgardepinnacle/UtgardePinnacleStrategy.h" +#include "wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h" /* Full list/TODO: The Oculus - Occ Drakos the Interrogator, Varos Cloudstrider, Mage-Lord Urom, Ley-Guardian Eregos -Utgarde Pinnacle - UP -Svala Sorrowgrave, Gortok Palehoof, Skadi the Ruthless, King Ymiron -The Culling of Stratholme - CoS -Meathook, Salramm the Fleshcrafter, Chrono-Lord Epoch, Mal'Ganis, Infinite Corruptor (Heroic only) Trial of the Champion - ToC Alliance Champions: Deathstalker Visceri, Eressea Dawnsinger, Mokra the Skullcrusher, Runok Wildmane, Zul'tore Horde Champions: Ambrose Boltspark, Colosos, Jacob Alerius, Jaelyne Evensong, Lana Stouthammer @@ -76,11 +74,12 @@ class DungeonStrategyContext : public NamedObjectContext static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonGDStrategy(botAI); } static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonHoSStrategy(botAI); } static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonHoLStrategy(botAI); } - + // static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonOccStrategy(botAI); } static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } - static Strategy* wotlk_up(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } - static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } - static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } + static Strategy* wotlk_up(PlayerbotAI* botAI) { return new WotlkDungeonUPStrategy(botAI); } + static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonCoSStrategy(botAI); } + + static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } // NYI from here down static Strategy* wotlk_hor(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_pos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_fos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h index b1301868..81b47ae2 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h @@ -11,8 +11,8 @@ #include "hallsofstone/HallsOfStoneActionContext.h" #include "hallsoflightning/HallsOfLightningActionContext.h" // #include "oculus/OculusActionContext.h" -// #include "utgardepinnacle/UtgardePinnacleActionContext.h" -// #include "cullingofstratholme/CullingOfStratholmeActionContext.h" +#include "utgardepinnacle/UtgardePinnacleActionContext.h" +#include "cullingofstratholme/CullingOfStratholmeActionContext.h" // #include "trialofthechampion/TrialOfTheChampionActionContext.h" // #include "hallsofreflection/HallsOfReflectionActionContext.h" // #include "pitofsaron/PitOfSaronActionContext.h" diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h index cb819430..0bafe36a 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h @@ -11,8 +11,8 @@ #include "hallsofstone/HallsOfStoneTriggerContext.h" #include "hallsoflightning/HallsOfLightningTriggerContext.h" // #include "oculus/OculusTriggerContext.h" -// #include "utgardepinnacle/UtgardePinnacleTriggerContext.h" -// #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h" +#include "utgardepinnacle/UtgardePinnacleTriggerContext.h" +#include "cullingofstratholme/CullingOfStratholmeTriggerContext.h" // #include "trialofthechampion/TrialOfTheChampionTriggerContext.h" // #include "hallsofreflection/HallsOfReflectionTriggerContext.h" // #include "pitofsaron/PitOfSaronTriggerContext.h" diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActionContext.h b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActionContext.h new file mode 100644 index 00000000..0e12c908 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActionContext.h @@ -0,0 +1,20 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONCOSACTIONCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONCOSACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "CullingOfStratholmeActions.h" + +class WotlkDungeonCoSActionContext : public NamedObjectContext +{ + public: + WotlkDungeonCoSActionContext() { + creators["explode ghoul spread"] = &WotlkDungeonCoSActionContext::explode_ghoul_spread; + creators["epoch stack"] = &WotlkDungeonCoSActionContext::epoch_stack; + } + private: + static Action* explode_ghoul_spread(PlayerbotAI* ai) { return new ExplodeGhoulSpreadAction(ai); } + static Action* epoch_stack(PlayerbotAI* ai) { return new EpochStackAction(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActions.cpp b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActions.cpp new file mode 100644 index 00000000..dd857569 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActions.cpp @@ -0,0 +1,54 @@ +#include "Playerbots.h" +#include "CullingOfStratholmeActions.h" +#include "CullingOfStratholmeStrategy.h" + + +bool ExplodeGhoulSpreadAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "salramm the fleshcrafter"); + if (!boss) { return false; } + + float distance = 10.0f; + float distanceExtra = 2.0f; + GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses"); + for (auto i = corpses.begin(); i != corpses.end(); ++i) + { + Unit* unit = botAI->GetUnit(*i); + if (unit && unit->GetEntry() == NPC_GHOUL_MINION) + { + float currentDistance = bot->GetExactDist2d(unit); + if (currentDistance < distance + distanceExtra) + { + return MoveAway(unit, distance + distanceExtra - currentDistance); + } + } + } + return false; +} + +bool EpochStackAction::isUseful() +{ + // Minimum hunter range is 5, but values too close to this seem to cause issues.. + // Hunter bots will try and melee in between ranged attacks, or just melee entirely at 5 as they are in range. + // 7.5 or 8.0 solves this for this boss. + // Unfortunately at this range the boss will charge. So I guess just don't stack as a hunter.. + // if(bot->getClass() == CLASS_HUNTER) + // { + // return AI_VALUE2(float, "distance", "current target") > 7.5f; + // } + // else + return !(bot->getClass() == CLASS_HUNTER) && AI_VALUE2(float, "distance", "current target") > 5.0f; +} +bool EpochStackAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "chrono-lord epoch"); + if (!boss) { return false; } + + float maxMovement = 10.0f; + // if(bot->getClass() == CLASS_HUNTER) + // { + // return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, maxMovement)); + // } + // else + return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), maxMovement)); +} diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActions.h b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActions.h new file mode 100644 index 00000000..989cb6ab --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeActions.h @@ -0,0 +1,26 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONCOSACTIONS_H +#define _PLAYERBOT_WOTLKDUNGEONCOSACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "GenericSpellActions.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "CullingOfStratholmeTriggers.h" + +class ExplodeGhoulSpreadAction : public MovementAction +{ +public: + ExplodeGhoulSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "explode ghoul spread") {} + bool Execute(Event event) override; +}; + +class EpochStackAction : public MovementAction +{ +public: + EpochStackAction(PlayerbotAI* ai) : MovementAction(ai, "epoch stack") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeMultipliers.cpp b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeMultipliers.cpp new file mode 100644 index 00000000..31ec9283 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeMultipliers.cpp @@ -0,0 +1,19 @@ +#include "CullingOfStratholmeMultipliers.h" +#include "CullingOfStratholmeActions.h" +#include "GenericSpellActions.h" +#include "ChooseTargetActions.h" +#include "MovementActions.h" +#include "CullingOfStratholmeTriggers.h" +#include "Action.h" + +float EpochMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "chrono-lord epoch"); + if (!boss) { return 1.0f; } + + if (bot->getClass() == CLASS_HUNTER) { return 1.0f; } + + if (dynamic_cast(action)) { return 0.0f; } + + return 1.0f; +} diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeMultipliers.h b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeMultipliers.h new file mode 100644 index 00000000..587b0f70 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeMultipliers.h @@ -0,0 +1,15 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONCOSMULTIPLIERS_H +#define _PLAYERBOT_WOTLKDUNGEONCOSMULTIPLIERS_H + +#include "Multiplier.h" + +class EpochMultiplier : public Multiplier +{ + public: + EpochMultiplier(PlayerbotAI* ai) : Multiplier(ai, "chrono-lord epoch") {} + + public: + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeStrategy.cpp b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeStrategy.cpp new file mode 100644 index 00000000..a78270d1 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeStrategy.cpp @@ -0,0 +1,27 @@ +#include "CullingOfStratholmeStrategy.h" +#include "CullingOfStratholmeMultipliers.h" + + +void WotlkDungeonCoSStrategy::InitTriggers(std::vector &triggers) +{ + // Meathook + // Can tank this in a fixed position to allow healer to LoS the stun, probably not necessary + + // Salramm the Fleshcrafter + triggers.push_back(new TriggerNode("explode ghoul", + NextAction::array(0, new NextAction("explode ghoul spread", ACTION_MOVE + 5), nullptr))); + + // Chrono-Lord Epoch + // Not sure if this actually works, I think I've seen him charge melee characters..? + triggers.push_back(new TriggerNode("epoch ranged", + NextAction::array(0, new NextAction("epoch stack", ACTION_MOVE + 5), nullptr))); + + // Mal'Ganis + + // Infinite Corruptor (Heroic only) +} + +void WotlkDungeonCoSStrategy::InitMultipliers(std::vector &multipliers) +{ + multipliers.push_back(new EpochMultiplier(botAI)); +} diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h new file mode 100644 index 00000000..9c687da9 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h @@ -0,0 +1,18 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONCOSSTRATEGY_H +#define _PLAYERBOT_WOTLKDUNGEONCOSSTRATEGY_H + +#include "Multiplier.h" +#include "AiObjectContext.h" +#include "Strategy.h" + + +class WotlkDungeonCoSStrategy : public Strategy +{ +public: + WotlkDungeonCoSStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "culling of stratholme"; } + virtual void InitTriggers(std::vector &triggers) override; + virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggerContext.h b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggerContext.h new file mode 100644 index 00000000..723a1904 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggerContext.h @@ -0,0 +1,22 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONCOSTRIGGERCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONCOSTRIGGERCONTEXT_H + +#include "NamedObjectContext.h" +#include "AiObjectContext.h" +#include "CullingOfStratholmeTriggers.h" + +class WotlkDungeonCoSTriggerContext : public NamedObjectContext +{ + public: + WotlkDungeonCoSTriggerContext() + { + creators["explode ghoul"] = &WotlkDungeonCoSTriggerContext::explode_ghoul; + creators["epoch ranged"] = &WotlkDungeonCoSTriggerContext::epoch_ranged; + + } + private: + static Trigger* explode_ghoul(PlayerbotAI* ai) { return new ExplodeGhoulTrigger(ai); } + static Trigger* epoch_ranged(PlayerbotAI* ai) { return new EpochRangedTrigger(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggers.cpp b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggers.cpp new file mode 100644 index 00000000..a2b7dc11 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggers.cpp @@ -0,0 +1,32 @@ +#include "Playerbots.h" +#include "CullingOfStratholmeTriggers.h" +#include "AiObject.h" +#include "AiObjectContext.h" + + +bool ExplodeGhoulTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "salramm the fleshcrafter"); + if (!boss) { return false; } + + float distance = 10.0f; + float distanceExtra = 2.0f; + GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses"); + for (auto i = corpses.begin(); i != corpses.end(); ++i) + { + Unit* unit = botAI->GetUnit(*i); + if (unit && unit->GetEntry() == NPC_RISEN_GHOUL) + { + if (bot->GetExactDist2d(unit) < distance + distanceExtra) + { + return true; + } + } + } + return false; +} + +bool EpochRangedTrigger::IsActive() +{ + return !botAI->IsMelee(bot) && AI_VALUE2(Unit*, "find target", "chrono-lord epoch"); +} diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggers.h b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggers.h new file mode 100644 index 00000000..28fafa36 --- /dev/null +++ b/src/strategy/dungeons/wotlk/cullingofstratholme/CullingOfStratholmeTriggers.h @@ -0,0 +1,29 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONCOSTRIGGERS_H +#define _PLAYERBOT_WOTLKDUNGEONCOSTRIGGERS_H + +#include "Trigger.h" +#include "PlayerbotAIConfig.h" +#include "GenericTriggers.h" +#include "DungeonStrategyUtils.h" + +enum CullingOfStratholmeIDs +{ + // Salramm the Fleshcrafter + NPC_GHOUL_MINION = 27733, +}; + +class ExplodeGhoulTrigger : public Trigger +{ +public: + ExplodeGhoulTrigger(PlayerbotAI* ai) : Trigger(ai, "explode ghoul") {} + bool IsActive() override; +}; + +class EpochRangedTrigger : public Trigger +{ +public: + EpochRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "chrono-lord epoch ranged") {} + bool IsActive() override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/cullingofstratholme/TODO b/src/strategy/dungeons/wotlk/cullingofstratholme/TODO deleted file mode 100644 index e69de29b..00000000 diff --git a/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp b/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp index e8863a12..0cfd6202 100644 --- a/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp +++ b/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp @@ -8,16 +8,18 @@ bool CorpseExplodeSpreadAction::Execute(Event event) Unit* boss = AI_VALUE2(Unit*, "find target", "trollgore"); if (!boss) { return false; } - float distance = 6.0f; // 5 unit radius, 1 unit added as buffer + float distance = 5.0f; + float distanceExtra = 2.0f; GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses"); for (auto i = corpses.begin(); i != corpses.end(); ++i) { Unit* unit = botAI->GetUnit(*i); if (unit && unit->GetEntry() == NPC_DRAKKARI_INVADER) { - if (bot->GetExactDist2d(unit) < distance) + float currentDistance = bot->GetExactDist2d(unit); + if (currentDistance < distance + distanceExtra) { - return MoveAway(unit, distance - bot->GetExactDist2d(unit)); + return MoveAway(unit, distance + distanceExtra - currentDistance); } } } diff --git a/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningActions.cpp b/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningActions.cpp index ac469e95..6bb82079 100644 --- a/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningActions.cpp +++ b/src/strategy/dungeons/wotlk/hallsoflightning/HallsOfLightningActions.cpp @@ -125,7 +125,7 @@ bool LokenStackAction::isUseful() { // Minimum hunter range is 5, but values too close to this seem to cause issues.. // Hunter bots will try and melee in between ranged attacks, or just melee entirely at 5 as they are in range. - // 6.5 or 7.0 solves this. + // 6.5 or 7.0 solves this for this boss. if(bot->getClass() == CLASS_HUNTER) { return AI_VALUE2(float, "distance", "current target") > 6.5f; @@ -138,14 +138,15 @@ bool LokenStackAction::Execute(Event event) Unit* boss = AI_VALUE2(Unit*, "find target", "loken"); if (!boss) { return false; } + float maxMovement = 10.0f; if (!boss->HasUnitState(UNIT_STATE_CASTING)) { if(bot->getClass() == CLASS_HUNTER) { - return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, 10.0f)); + return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, maxMovement)); } // else - return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), 10.0f)); + return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), maxMovement)); } return false; diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/TODO b/src/strategy/dungeons/wotlk/utgardepinnacle/TODO deleted file mode 100644 index e69de29b..00000000 diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h new file mode 100644 index 00000000..5a9dff5b --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h @@ -0,0 +1,20 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONUPACTIONCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONUPACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "UtgardePinnacleActions.h" + +class WotlkDungeonUPActionContext : public NamedObjectContext +{ + public: + WotlkDungeonUPActionContext() { + creators["avoid freezing cloud"] = &WotlkDungeonUPActionContext::avoid_freezing_cloud; + creators["avoid skadi whirlwind"] = &WotlkDungeonUPActionContext::avoid_whirlwind; + } + private: + static Action* avoid_freezing_cloud(PlayerbotAI* ai) { return new AvoidFreezingCloudAction(ai); } + static Action* avoid_whirlwind(PlayerbotAI* ai) { return new AvoidSkadiWhirlwindAction(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActions.cpp b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActions.cpp new file mode 100644 index 00000000..f084ca15 --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActions.cpp @@ -0,0 +1,61 @@ +#include "Playerbots.h" +#include "UtgardePinnacleActions.h" +#include "UtgardePinnacleStrategy.h" + +bool AvoidFreezingCloudAction::Execute(Event event) +{ + Unit* closestTrigger = nullptr; + GuidVector objects = AI_VALUE(GuidVector, "nearest hostile npcs"); + + for (auto i = objects.begin(); i != objects.end(); ++i) + { + Unit* unit = botAI->GetUnit(*i); + if (unit && unit->GetEntry() == NPC_BREATH_TRIGGER) + { + if (!closestTrigger || bot->GetExactDist2d(unit) < bot->GetExactDist2d(closestTrigger)) + { + closestTrigger = unit; + } + } + } + + if (!closestTrigger) { return false; } + + float distance = bot->GetExactDist2d(closestTrigger->GetPosition()); + float radius = 3.0f; + // Large buffer for this - the radius of the breath is a lot smaller than the graphic, but it looks dumb + // if the bot stands just outside the hitbox but still visibly in the cloud patches. + float distanceExtra = 3.0f; + + if (distance < radius + distanceExtra - 1.0f) + { + // bot->Yell("MOVING", LANG_UNIVERSAL); + return MoveAway(closestTrigger, radius + distanceExtra - distance); + } + + return false; +} + +bool AvoidSkadiWhirlwindAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "skadi the ruthless"); + if (!boss) { return false; } + + float distance = bot->GetExactDist2d(boss->GetPosition()); + float radius = 5.0f; + float distanceExtra = 2.0f; + + if (distance < radius + distanceExtra) + { + if (botAI->IsTank(bot)) + { + // The boss chases tank during this, leads to jittery stutter-stepping + // by the tank if we don't pre-move additional range. 2*radius seems ok + return MoveAway(boss, (2.0f * radius) + distanceExtra - distance); + } + // else + return MoveAway(boss, radius + distanceExtra - distance); + } + + return false; +} diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActions.h b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActions.h new file mode 100644 index 00000000..38e90dd1 --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActions.h @@ -0,0 +1,24 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONUPACTIONS_H +#define _PLAYERBOT_WOTLKDUNGEONUPACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "UtgardePinnacleTriggers.h" + +class AvoidFreezingCloudAction : public MovementAction +{ +public: + AvoidFreezingCloudAction(PlayerbotAI* ai) : MovementAction(ai, "avoid freezing cloud") {} + bool Execute(Event event) override; +}; + +class AvoidSkadiWhirlwindAction : public MovementAction +{ +public: + AvoidSkadiWhirlwindAction(PlayerbotAI* ai) : MovementAction(ai, "avoid skadi whirlwind") {} + bool Execute(Event event) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.cpp b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.cpp new file mode 100644 index 00000000..82e5fd53 --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.cpp @@ -0,0 +1,84 @@ +#include "UtgardePinnacleMultipliers.h" +#include "UtgardePinnacleActions.h" +#include "GenericSpellActions.h" +#include "ChooseTargetActions.h" +#include "MovementActions.h" +#include "UtgardePinnacleTriggers.h" +#include "Action.h" + +float SkadiMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "skadi the ruthless"); + if (!boss) { return 1.0f; } + + Unit* bossMount = AI_VALUE2(Unit*, "find target", "grauf"); + + if (!bossMount) + // Actual bossfight (dismounted) + { + if (boss->HasAura(SPELL_SKADI_WHIRLWIND)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + } + } + else + { + // Bots tend to get stuck trying to attack the boss in the sky, not the adds on the ground + if (dynamic_cast(action) + && (action->GetTarget() == boss || action->GetTarget() == bossMount)) + { + return 0.0f; + } + + // TODO: BELOW IS EXPERIMENTAL + // Meant to stop the jittery movement when dodging the breath. + // Currently causes issues with making the bots unresponsive and often getting the healer killed. + // Semi-glitchy movement is better than semi-afk bots, so this is commented out until it gets improved + + // bool cloudActive = false; + // // Need to check two conditions here - the persistent ground effect doesn't + // // seem to be detectable until 3-5 secs in, despite it dealing damage. + // // The initial breath triggers straight away but once it's over, the bots will run back on + // // to the frezzing cloud and take damage. + // // Therefore check both conditions and trigger on either. + + // // Check this one early, if true then we don't need to iterate over any objects + // if (bossMount->HasAura(SPELL_FREEZING_CLOUD_BREATH)) + // { + // cloudActive = true; + // } + + // // Otherwise, check for persistent ground objects emitting the freezing cloud + // GuidVector objects = AI_VALUE(GuidVector, "nearest hostile npcs"); + // for (auto i = objects.begin(); i != objects.end(); ++i) + // { + // Unit* unit = botAI->GetUnit(*i); + // if (unit && unit->GetEntry() == NPC_BREATH_TRIGGER) + // { + // Unit::AuraApplicationMap const& Auras = unit->GetAppliedAuras(); + // for (Unit::AuraApplicationMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) + // { + // Aura* aura = itr->second->GetBase(); + // if (aura && aura->GetId() == SPELL_FREEZING_CLOUD) + // { + // cloudActive = true; + // break; + // } + // } + // } + // } + + // if (cloudActive) + // { + // if (dynamic_cast(action) && !dynamic_cast(action)) + // { + // return 0.0f; + // } + // } + } + + return 1.0f; +} diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.h b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.h new file mode 100644 index 00000000..36576678 --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleMultipliers.h @@ -0,0 +1,15 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONUPMULTIPLIERS_H +#define _PLAYERBOT_WOTLKDUNGEONUPMULTIPLIERS_H + +#include "Multiplier.h" + +class SkadiMultiplier : public Multiplier +{ + public: + SkadiMultiplier(PlayerbotAI* ai) : Multiplier(ai, "skadi the ruthless") {} + + public: + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.cpp b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.cpp new file mode 100644 index 00000000..6877ba2c --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.cpp @@ -0,0 +1,25 @@ +#include "UtgardePinnacleStrategy.h" +#include "UtgardePinnacleMultipliers.h" + + +void WotlkDungeonUPStrategy::InitTriggers(std::vector &triggers) +{ + // Svala Sorrowgrave + + // Gortok Palehoof + + // Skadi the Ruthless + // TODO: Harpoons launchable via GameObject. For now players should do them + triggers.push_back(new TriggerNode("freezing cloud", + NextAction::array(0, new NextAction("avoid freezing cloud", ACTION_RAID + 5), nullptr))); + triggers.push_back(new TriggerNode("skadi whirlwind", + NextAction::array(0, new NextAction("avoid skadi whirlwind", ACTION_RAID + 4), nullptr))); + + // King Ymiron + // May need to avoid orb.. unclear if the generic avoid AoE does this well +} + +void WotlkDungeonUPStrategy::InitMultipliers(std::vector &multipliers) +{ + multipliers.push_back(new SkadiMultiplier(botAI)); +} diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.h b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.h new file mode 100644 index 00000000..e8f45363 --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleStrategy.h @@ -0,0 +1,18 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONUPSTRATEGY_H +#define _PLAYERBOT_WOTLKDUNGEONUPSTRATEGY_H + +#include "Multiplier.h" +#include "AiObjectContext.h" +#include "Strategy.h" + + +class WotlkDungeonUPStrategy : public Strategy +{ +public: + WotlkDungeonUPStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "utgarde pinnacle"; } + virtual void InitTriggers(std::vector &triggers) override; + virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggerContext.h b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggerContext.h new file mode 100644 index 00000000..143f3df5 --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggerContext.h @@ -0,0 +1,21 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONUPTRIGGERCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONUPTRIGGERCONTEXT_H + +#include "NamedObjectContext.h" +#include "AiObjectContext.h" +#include "UtgardePinnacleTriggers.h" + +class WotlkDungeonUPTriggerContext : public NamedObjectContext +{ + public: + WotlkDungeonUPTriggerContext() + { + creators["freezing cloud"] = &WotlkDungeonUPTriggerContext::freezing_cloud; + creators["skadi whirlwind"] = &WotlkDungeonUPTriggerContext::whirlwind; + } + private: + static Trigger* freezing_cloud(PlayerbotAI* ai) { return new SkadiFreezingCloudTrigger(ai); } + static Trigger* whirlwind(PlayerbotAI* ai) { return new SkadiWhirlwindTrigger(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp new file mode 100644 index 00000000..587f4e06 --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp @@ -0,0 +1,48 @@ +#include "Playerbots.h" +#include "UtgardePinnacleTriggers.h" +#include "AiObject.h" +#include "AiObjectContext.h" + +bool SkadiFreezingCloudTrigger::IsActive() +{ + Unit* bossMount = AI_VALUE2(Unit*, "find target", "grauf"); + if (!bossMount) { return false; } + + // Need to check two conditions here - the persistent ground effect doesn't + // seem to be detectable until 3-5 secs in, despite it dealing damage. + // The initial breath triggers straight away but once it's over, the bots will run back on + // to the frezzing cloud and take damage. + // Therefore check both conditions and trigger on either. + + // Check this one first, if true then we don't need to iterate over any objects + if (bossMount->HasAura(SPELL_FREEZING_CLOUD_BREATH)) + { + return true; + } + + // Otherwise, check for persistent ground objects emitting the freezing cloud + GuidVector objects = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto i = objects.begin(); i != objects.end(); ++i) + { + Unit* unit = botAI->GetUnit(*i); + if (unit && unit->GetEntry() == NPC_BREATH_TRIGGER) + { + Unit::AuraApplicationMap const& Auras = unit->GetAppliedAuras(); + for (Unit::AuraApplicationMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr) + { + Aura* aura = itr->second->GetBase(); + if (aura && aura->GetId() == SPELL_FREEZING_CLOUD) + { + return true; + } + } + } + } + return false; +} + +bool SkadiWhirlwindTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "skadi the ruthless"); + return boss && boss->HasAura(SPELL_SKADI_WHIRLWIND); +} diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.h b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.h new file mode 100644 index 00000000..bd42c3d6 --- /dev/null +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.h @@ -0,0 +1,39 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONUPTRIGGERS_H +#define _PLAYERBOT_WOTLKDUNGEONUPTRIGGERS_H + +#include "Trigger.h" +#include "PlayerbotAIConfig.h" +#include "GenericTriggers.h" +#include "DungeonStrategyUtils.h" + +enum UtgardePinnacleIDs +{ + // Skadi the Ruthless + SPELL_FREEZING_CLOUD_N = 47579, + SPELL_FREEZING_CLOUD_H = 60020, + SPELL_FREEZING_CLOUD_BREATH = 47592, + NPC_BREATH_TRIGGER = 28351, + SPELL_SKADI_WHIRLWIND_N = 50228, + SPELL_SKADI_WHIRLWIND_H = 59322, +}; + +#define SPELL_FREEZING_CLOUD DUNGEON_MODE(bot, SPELL_FREEZING_CLOUD_N, SPELL_FREEZING_CLOUD_H) +#define SPELL_SKADI_WHIRLWIND DUNGEON_MODE(bot, SPELL_SKADI_WHIRLWIND_N, SPELL_SKADI_WHIRLWIND_H) + +// const float SKADI_BREATH_CENTRELINE = -512.46875f; + +class SkadiFreezingCloudTrigger : public Trigger +{ +public: + SkadiFreezingCloudTrigger(PlayerbotAI* ai) : Trigger(ai, "skadi freezing cloud") {} + bool IsActive() override; +}; + +class SkadiWhirlwindTrigger : public Trigger +{ +public: + SkadiWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "skadi whirlwind") {} + bool IsActive() override; +}; + +#endif diff --git a/src/strategy/values/NearestNpcsValue.cpp b/src/strategy/values/NearestNpcsValue.cpp index cbbc8894..1152842a 100644 --- a/src/strategy/values/NearestNpcsValue.cpp +++ b/src/strategy/values/NearestNpcsValue.cpp @@ -20,6 +20,15 @@ void NearestNpcsValue::FindUnits(std::list& targets) bool NearestNpcsValue::AcceptUnit(Unit* unit) { return !unit->IsHostileTo(bot) && !unit->IsPlayer(); } +void NearestHostileNpcsValue::FindUnits(std::list& targets) +{ + Acore::AnyUnitInObjectRangeCheck u_check(bot, range); + Acore::UnitListSearcher searcher(bot, targets, u_check); + Cell::VisitAllObjects(bot, searcher, range); +} + +bool NearestHostileNpcsValue::AcceptUnit(Unit* unit) { return unit->IsHostileTo(bot) && !unit->IsPlayer(); } + void NearestVehiclesValue::FindUnits(std::list& targets) { Acore::AnyUnitInObjectRangeCheck u_check(bot, range); diff --git a/src/strategy/values/NearestNpcsValue.h b/src/strategy/values/NearestNpcsValue.h index 3915dde3..9e192c57 100644 --- a/src/strategy/values/NearestNpcsValue.h +++ b/src/strategy/values/NearestNpcsValue.h @@ -24,6 +24,19 @@ protected: bool AcceptUnit(Unit* unit) override; }; +class NearestHostileNpcsValue : public NearestUnitsValue +{ +public: + NearestHostileNpcsValue(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->sightDistance) + : NearestUnitsValue(botAI, "nearest hostile npcs", range) + { + } + +protected: + void FindUnits(std::list& targets) override; + bool AcceptUnit(Unit* unit) override; +}; + class NearestVehiclesValue : public NearestUnitsValue { public: diff --git a/src/strategy/values/ValueContext.h b/src/strategy/values/ValueContext.h index f8f4a8fe..f57c8083 100644 --- a/src/strategy/values/ValueContext.h +++ b/src/strategy/values/ValueContext.h @@ -104,6 +104,7 @@ public: creators["nearest game objects no los"] = &ValueContext::nearest_game_objects_no_los; creators["closest game objects"] = &ValueContext::closest_game_objects; creators["nearest npcs"] = &ValueContext::nearest_npcs; + creators["nearest hostile npcs"] = &ValueContext::nearest_hostile_npcs; creators["nearest totems"] = &ValueContext::nearest_totems; creators["nearest vehicles"] = &ValueContext::nearest_vehicles; creators["nearest vehicles far"] = &ValueContext::nearest_vehicles_far; @@ -393,6 +394,7 @@ private: } static UntypedValue* log_level(PlayerbotAI* botAI) { return new LogLevelValue(botAI); } static UntypedValue* nearest_npcs(PlayerbotAI* botAI) { return new NearestNpcsValue(botAI); } + static UntypedValue* nearest_hostile_npcs(PlayerbotAI* botAI) { return new NearestHostileNpcsValue(botAI); } static UntypedValue* nearest_totems(PlayerbotAI* botAI) { return new NearestTotemsValue(botAI); } static UntypedValue* nearest_vehicles(PlayerbotAI* botAI) { return new NearestVehiclesValue(botAI); } static UntypedValue* nearest_vehicles_far(PlayerbotAI* botAI) { return new NearestVehiclesValue(botAI, 200.0f); } From f09531775a3229dbed3da8ae6edc9b29e5ae803b Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Mon, 21 Oct 2024 22:33:24 +1100 Subject: [PATCH 03/19] Typo --- .../dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp index 587f4e06..168d16e6 100644 --- a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp +++ b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleTriggers.cpp @@ -11,7 +11,7 @@ bool SkadiFreezingCloudTrigger::IsActive() // Need to check two conditions here - the persistent ground effect doesn't // seem to be detectable until 3-5 secs in, despite it dealing damage. // The initial breath triggers straight away but once it's over, the bots will run back on - // to the frezzing cloud and take damage. + // to the freezing cloud and take damage. // Therefore check both conditions and trigger on either. // Check this one first, if true then we don't need to iterate over any objects From a2f78542a4b858cd9297d59b76315771e4c8d9bd Mon Sep 17 00:00:00 2001 From: Revision Date: Wed, 23 Oct 2024 00:18:44 +0200 Subject: [PATCH 04/19] Fix the level when a quest becomes trivial (grey/gray) --- src/strategy/actions/DropQuestAction.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/strategy/actions/DropQuestAction.cpp b/src/strategy/actions/DropQuestAction.cpp index 910e5498..2a23c019 100644 --- a/src/strategy/actions/DropQuestAction.cpp +++ b/src/strategy/actions/DropQuestAction.cpp @@ -101,8 +101,24 @@ bool CleanQuestLogAction::Execute(Event event) questLevel = botLevel; } + // Set the level difference for when a quest becomes trivial + // This was determined by using the Lua code the client uses + int32 trivialLevel = 5; + if (botLevel >= 40) + { + trivialLevel = 8; + } + else if (botLevel >= 30) + { + trivialLevel = 7; + } + else if (botLevel >= 20) + { + trivialLevel = 6; + } + // Check if the quest is trivial (grey) for the bot - if ((botLevel - questLevel) >= 5) + if ((botLevel - questLevel) >= trivialLevel) { // Output only if "debug rpg" strategy is enabled if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) From 9f273791a7ef14bbfc46ca321551e4edd716c167 Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Sun, 27 Oct 2024 02:20:04 +1100 Subject: [PATCH 05/19] Oculus implementation --- src/strategy/AiObjectContext.cpp | 2 + .../dungeons/DungeonStrategyContext.h | 10 +- .../wotlk/WotlkDungeonActionContext.h | 2 +- .../wotlk/WotlkDungeonTriggerContext.h | 2 +- .../hallsofstone/HallsOfStoneActions.cpp | 3 +- .../wotlk/oculus/OculusActionContext.h | 30 ++ .../dungeons/wotlk/oculus/OculusActions.cpp | 355 ++++++++++++++++++ .../dungeons/wotlk/oculus/OculusActions.h | 77 ++++ .../wotlk/oculus/OculusMultipliers.cpp | 109 ++++++ .../dungeons/wotlk/oculus/OculusMultipliers.h | 53 +++ .../dungeons/wotlk/oculus/OculusStrategy.cpp | 42 +++ .../dungeons/wotlk/oculus/OculusStrategy.h | 18 + .../wotlk/oculus/OculusTriggerContext.h | 33 ++ .../dungeons/wotlk/oculus/OculusTriggers.cpp | 85 +++++ .../dungeons/wotlk/oculus/OculusTriggers.h | 129 +++++++ src/strategy/dungeons/wotlk/oculus/TODO | 0 .../wotlk/oldkingdom/OldKingdomActions.cpp | 25 +- 17 files changed, 949 insertions(+), 26 deletions(-) create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusActionContext.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusActions.cpp create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusActions.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusMultipliers.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusStrategy.cpp create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusStrategy.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusTriggerContext.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusTriggers.h delete mode 100644 src/strategy/dungeons/wotlk/oculus/TODO diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index 9b52369e..b2ede465 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -59,6 +59,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new WotlkDungeonGDActionContext()); actionContexts.Add(new WotlkDungeonHoSActionContext()); actionContexts.Add(new WotlkDungeonHoLActionContext()); + actionContexts.Add(new WotlkDungeonOccActionContext()); actionContexts.Add(new WotlkDungeonUPActionContext()); actionContexts.Add(new WotlkDungeonCoSActionContext()); @@ -80,6 +81,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new WotlkDungeonGDTriggerContext()); triggerContexts.Add(new WotlkDungeonHoSTriggerContext()); triggerContexts.Add(new WotlkDungeonHoLTriggerContext()); + triggerContexts.Add(new WotlkDungeonOccTriggerContext()); triggerContexts.Add(new WotlkDungeonUPTriggerContext()); triggerContexts.Add(new WotlkDungeonCoSTriggerContext()); diff --git a/src/strategy/dungeons/DungeonStrategyContext.h b/src/strategy/dungeons/DungeonStrategyContext.h index 3bb39c18..5b1914a2 100644 --- a/src/strategy/dungeons/DungeonStrategyContext.h +++ b/src/strategy/dungeons/DungeonStrategyContext.h @@ -11,14 +11,13 @@ #include "wotlk/gundrak/GundrakStrategy.h" #include "wotlk/hallsofstone/HallsOfStoneStrategy.h" #include "wotlk/hallsoflightning/HallsOfLightningStrategy.h" +#include "wotlk/oculus/OculusStrategy.h" #include "wotlk/utgardepinnacle/UtgardePinnacleStrategy.h" #include "wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h" /* Full list/TODO: -The Oculus - Occ -Drakos the Interrogator, Varos Cloudstrider, Mage-Lord Urom, Ley-Guardian Eregos Trial of the Champion - ToC Alliance Champions: Deathstalker Visceri, Eressea Dawnsinger, Mokra the Skullcrusher, Runok Wildmane, Zul'tore Horde Champions: Ambrose Boltspark, Colosos, Jacob Alerius, Jaelyne Evensong, Lana Stouthammer @@ -74,12 +73,11 @@ class DungeonStrategyContext : public NamedObjectContext static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonGDStrategy(botAI); } static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonHoSStrategy(botAI); } static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonHoLStrategy(botAI); } - // static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonOccStrategy(botAI); } - static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } + static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonOccStrategy(botAI); } static Strategy* wotlk_up(PlayerbotAI* botAI) { return new WotlkDungeonUPStrategy(botAI); } static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonCoSStrategy(botAI); } - - static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } // NYI from here down + // NYI from here down + static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_hor(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_pos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_fos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h index 81b47ae2..e6f2e00a 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h @@ -10,7 +10,7 @@ #include "gundrak/GundrakActionContext.h" #include "hallsofstone/HallsOfStoneActionContext.h" #include "hallsoflightning/HallsOfLightningActionContext.h" -// #include "oculus/OculusActionContext.h" +#include "oculus/OculusActionContext.h" #include "utgardepinnacle/UtgardePinnacleActionContext.h" #include "cullingofstratholme/CullingOfStratholmeActionContext.h" // #include "trialofthechampion/TrialOfTheChampionActionContext.h" diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h index 0bafe36a..455239ab 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h @@ -10,7 +10,7 @@ #include "gundrak/GundrakTriggerContext.h" #include "hallsofstone/HallsOfStoneTriggerContext.h" #include "hallsoflightning/HallsOfLightningTriggerContext.h" -// #include "oculus/OculusTriggerContext.h" +#include "oculus/OculusTriggerContext.h" #include "utgardepinnacle/UtgardePinnacleTriggerContext.h" #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h" // #include "trialofthechampion/TrialOfTheChampionTriggerContext.h" diff --git a/src/strategy/dungeons/wotlk/hallsofstone/HallsOfStoneActions.cpp b/src/strategy/dungeons/wotlk/hallsofstone/HallsOfStoneActions.cpp index 29f9b3ca..54901249 100644 --- a/src/strategy/dungeons/wotlk/hallsofstone/HallsOfStoneActions.cpp +++ b/src/strategy/dungeons/wotlk/hallsofstone/HallsOfStoneActions.cpp @@ -5,10 +5,11 @@ bool ShatterSpreadAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "krystallus"); - float radius = 40.0f; if (!boss) { return false; } + float radius = 40.0f; Unit* closestMember = nullptr; + GuidVector members = AI_VALUE(GuidVector, "group members"); for (auto& member : members) { diff --git a/src/strategy/dungeons/wotlk/oculus/OculusActionContext.h b/src/strategy/dungeons/wotlk/oculus/OculusActionContext.h new file mode 100644 index 00000000..38cd640b --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusActionContext.h @@ -0,0 +1,30 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCACTIONCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONOCCACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "OculusActions.h" + +class WotlkDungeonOccActionContext : public NamedObjectContext +{ + public: + WotlkDungeonOccActionContext() { + creators["avoid unstable sphere"] = &WotlkDungeonOccActionContext::avoid_unstable_sphere; + creators["mount drake"] = &WotlkDungeonOccActionContext::mount_drake; + creators["dismount drake"] = &WotlkDungeonOccActionContext::dismount_drake; + creators["fly drake"] = &WotlkDungeonOccActionContext::fly_drake; + creators["drake attack"] = &WotlkDungeonOccActionContext::drake_attack; + creators["avoid arcane explosion"] = &WotlkDungeonOccActionContext::avoid_arcane_explosion; + creators["time bomb spread"] = &WotlkDungeonOccActionContext::time_bomb_spread; + } + private: + static Action* avoid_unstable_sphere(PlayerbotAI* ai) { return new AvoidUnstableSphereAction(ai); } + static Action* mount_drake(PlayerbotAI* ai) { return new MountDrakeAction(ai); } + static Action* dismount_drake(PlayerbotAI* ai) { return new DismountDrakeAction(ai); } + static Action* fly_drake(PlayerbotAI* ai) { return new FlyDrakeAction(ai); } + static Action* drake_attack(PlayerbotAI* ai) { return new DrakeAttackAction(ai); } + static Action* avoid_arcane_explosion(PlayerbotAI* ai) { return new AvoidArcaneExplosionAction(ai); } + static Action* time_bomb_spread(PlayerbotAI* ai) { return new TimeBombSpreadAction(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp b/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp new file mode 100644 index 00000000..e50ae07d --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp @@ -0,0 +1,355 @@ +#include "Playerbots.h" +#include "OculusActions.h" +#include "OculusStrategy.h" +#include "LastSpellCastValue.h" + +bool AvoidUnstableSphereAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "drakos the interrogator"); + if (!boss) { return false; } + + float radius = 12.0f; + float extraDistance = 1.0f; + Unit* closestSphere = nullptr; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit && unit->GetEntry() == NPC_UNSTABLE_SPHERE && !unit->isMoving()) + { + if (!closestSphere || bot->GetExactDist2d(unit) < bot->GetExactDist2d(closestSphere)) + { + closestSphere = unit; + } + } + } + + if (closestSphere && bot->GetExactDist2d(closestSphere) < radius + extraDistance) + { + return MoveAway(closestSphere, fmin(3.0f, bot->GetExactDist2d(closestSphere) - radius + extraDistance)); + } + + return false; +} + +bool MountDrakeAction::isPossible() { return bot->GetMapId() == OCULUS_MAP_ID; } +bool MountDrakeAction::Execute(Event event) +{ + std::map drakeAssignments; + // Composition can be adjusted - both 3/1/1 and 2/2/1 are good default comps + // {Amber, Emerald, Ruby} + std::vector composition = {2, 2, 1}; + // std::vector composition = {3, 1, 1}; + int32 myIndex = botAI->GetGroupSlotIndex(bot); + + Player* master = botAI->GetMaster(); + if (!master) { return false; } + Unit* vehicle = master->GetVehicleBase(); + if (!vehicle) { return false; } + + // Subtract the player's chosen mount type from the composition so player can play whichever they prefer + switch (vehicle->GetEntry()) + { + case NPC_AMBER_DRAKE: + composition[0]--; + break; + case NPC_EMERALD_DRAKE: + composition[1]--; + break; + case NPC_RUBY_DRAKE: + composition[2]--; + break; + } + + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto& member : members) + { + Player* player = botAI->GetPlayer(member); + if (!player) { continue; } + + for (int i = 0; i < composition.size(); i++) + { + if (composition[i] > 0) + { + drakeAssignments[botAI->GetGroupSlotIndex(player)] = DRAKE_ITEMS[i]; + composition[i]--; + break; + } + } + } + + // Correct/update the drake items in inventories incase assignments have changed + for (uint32 itemId : DRAKE_ITEMS) + { + Item* item = bot->GetItemByEntry(itemId); + if (!item) { continue; } + + if (itemId == drakeAssignments[myIndex]) + { + // Use our assigned drake + return UseItemAuto(item); + } + // Else assigned drake is different, destroy old drake + uint32 count = 1; + bot->DestroyItemCount(item, count, true); + break; + } + + // Bot does not have the correct drake item + bot->AddItem(drakeAssignments[myIndex], 1); + return false; +} + +bool DismountDrakeAction::Execute(Event event) +{ + if (bot->GetVehicle()) + { + bot->ExitVehicle(); + return true; + } + return false; +} + +bool FlyDrakeAction::Execute(Event event) +{ + Player* master = botAI->GetMaster(); + if (!master) { return false; } + Unit* masterVehicle = master->GetVehicleBase(); + Unit* vehicleBase = bot->GetVehicleBase(); + if (!vehicleBase || !masterVehicle) { return false; } + + MotionMaster* mm = vehicleBase->GetMotionMaster(); + Unit* boss = AI_VALUE2(Unit*, "find target", "ley-guardian eregos"); + if (boss && !boss->HasAura(SPELL_PLANAR_SHIFT)) + { + // Handle as boss encounter instead of formation flight + mm->Clear(false); + float distance = vehicleBase->GetExactDist(boss); + float range = 55.0f; // Drake range is 60yd + if (distance > range) + { + mm->MoveForwards(boss, range - distance); + vehicleBase->SendMovementFlagUpdate(); + return true; + } + + vehicleBase->SetFacingToObject(boss); + mm->MoveIdle(); + vehicleBase->SendMovementFlagUpdate(); + return false; + } + + if (vehicleBase->GetExactDist(masterVehicle) > 20.0f) + { + // 3/4 of a circle, with frontal cone 90 deg unobstructed + float angle = botAI->GetGroupSlotIndex(bot) * (2*M_PI - M_PI_2)/5 + M_PI_2; + vehicleBase->SetCanFly(true); + mm->MoveFollow(masterVehicle, 15.0f, angle); + vehicleBase->SendMovementFlagUpdate(); + return true; + } + return false; +} + +bool DrakeAttackAction::Execute(Event event) +{ + vehicleBase = bot->GetVehicleBase(); + if (!vehicleBase) { return false; } + + Unit* target = AI_VALUE(Unit*, "current target"); + + if (!target) + { + GuidVector attackers = AI_VALUE(GuidVector, "attackers"); + for (auto& attacker : attackers) + { + Unit* unit = botAI->GetUnit(attacker); + if (!unit) { continue; } + + SET_AI_VALUE(Unit*, "current target", unit); + target = unit; + break; + } + } + + if (!target) { return false; } + + switch (vehicleBase->GetEntry()) + { + case NPC_AMBER_DRAKE: + return AmberDrakeAction(target); + case NPC_EMERALD_DRAKE: + return EmeraldDrakeAction(target); + case NPC_RUBY_DRAKE: + return RubyDrakeAction(target); + default: + break; + } + return false; +} + +bool DrakeAttackAction::CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown) +{ + if (botAI->CanCastVehicleSpell(spellId, target)) + if (botAI->CastVehicleSpell(spellId, target)) + { + vehicleBase->AddSpellCooldown(spellId, 0, cooldown); + return true; + } + return false; +} + +bool DrakeAttackAction::AmberDrakeAction(Unit* target) +{ + Aura* shockCharges = target->GetAura(SPELL_SHOCK_CHARGE, vehicleBase->GetGUID()); + if (shockCharges && shockCharges->GetStackAmount() > 8) + { + // At 9 charges, better to detonate and re-channel rather than stacking the last charge due to gcd + // If stacking Amber drakes, may need to drop this even lower as the charges stack so fast + return CastDrakeSpellAction(target, SPELL_SHOCK_LANCE, 0); + } + + // Deal with enrage after shock charges, as Stop Time adds 5 charges and they may get wasted + if (target->HasAura(SPELL_ENRAGED_ASSAULT) && + !target->HasAura(SPELL_STOP_TIME) && + !vehicleBase->HasSpellCooldown(SPELL_STOP_TIME)) + { + return CastDrakeSpellAction(target, SPELL_STOP_TIME, 60000); + } + + if (!vehicleBase->FindCurrentSpellBySpellId(SPELL_TEMPORAL_RIFT)) + { + return CastDrakeSpellAction(target, SPELL_TEMPORAL_RIFT, 0); + } + + return false; +} + +bool DrakeAttackAction::EmeraldDrakeAction(Unit* target) +{ + Aura* poisonStacks = target->GetAura(SPELL_LEECHING_POISON, vehicleBase->GetGUID()); + if (!poisonStacks || (poisonStacks->GetStackAmount() < 3 || + poisonStacks->GetDuration() < 4000)) + { + return CastDrakeSpellAction(target, SPELL_LEECHING_POISON, 0); + } + + if (!vehicleBase->HasSpellCooldown(SPELL_TOUCH_THE_NIGHTMARE) && + (!target->HasAura(SPELL_TOUCH_THE_NIGHTMARE) || vehicleBase->HealthAbovePct(90))) + { + return CastDrakeSpellAction(target, SPELL_TOUCH_THE_NIGHTMARE, 10000); + } + + Unit* healingTarget = nullptr; + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto& member : members) + { + Unit* unit = botAI->GetUnit(member); + if (!unit || bot->GetGUID() == member) + { + continue; + } + + Unit* drake = unit->GetVehicleBase(); + if (!drake || drake->IsFullHealth()) { continue; } + + if (!healingTarget || drake->GetHealthPct() < healingTarget->GetHealthPct() - 15.0f) + { + healingTarget = drake; + } + } + + Spell* currentSpell = vehicleBase->FindCurrentSpellBySpellId(SPELL_DREAM_FUNNEL); + if (healingTarget) + { + if (!currentSpell || currentSpell->m_targets.GetUnitTarget() != healingTarget) + { + float distance = vehicleBase->GetExactDist(healingTarget); + float range = 55.0f; + if (distance > range) + { + MotionMaster* mm = vehicleBase->GetMotionMaster(); + mm->Clear(false); + mm->MoveForwards(healingTarget, distance - range - 10.0f); + vehicleBase->SendMovementFlagUpdate(); + return false; + } + return CastDrakeSpellAction(healingTarget, SPELL_DREAM_FUNNEL, 0); + } + } + // Fill GCDs with Leeching Poison to refresh timer, rather than idling + if (!currentSpell) + { + return CastDrakeSpellAction(target, SPELL_LEECHING_POISON, 0); + } + + return false; +} + +bool DrakeAttackAction::RubyDrakeAction(Unit* target) +{ + Aura* evasiveCharges = vehicleBase->GetAura(SPELL_EVASIVE_CHARGES); + Aura* evasiveManeuvers = vehicleBase->GetAura(SPELL_EVASIVE_MANEUVERS); + + if (evasiveCharges) + { + if (evasiveManeuvers && + !vehicleBase->HasSpellCooldown(SPELL_MARTYR) && + evasiveManeuvers->GetDuration() > 10000 && + evasiveCharges->GetStackAmount() >= 5) + { + return CastDrakeSpellAction(vehicleBase, SPELL_MARTYR, 10000); + } + + if (!vehicleBase->HasSpellCooldown(SPELL_EVASIVE_MANEUVERS) && + evasiveCharges->GetStackAmount() >= 10) + { + return CastDrakeSpellAction(vehicleBase, SPELL_EVASIVE_MANEUVERS, 5000); + } + } + + return CastDrakeSpellAction(target, SPELL_SEARING_WRATH, 0); +} + +bool AvoidArcaneExplosionAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom"); + if (!boss) { return false; } + + const Position* closestPos = nullptr; + + for (auto& position : uromSafePositions) + { + if (!closestPos || bot->GetExactDist(position) < bot->GetExactDist(closestPos)) + { + closestPos = &position; + } + } + + if (!closestPos) { return false; } + + return MoveNear(bot->GetMapId(), closestPos->GetPositionX(), closestPos->GetPositionY(), closestPos->GetPositionZ(), 2.0f, MovementPriority::MOVEMENT_COMBAT); +} + +bool TimeBombSpreadAction::Execute(Event event) +{ + float radius = 10.0f; + float distanceExtra = 2.0f; + + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto& member : members) + { + if (bot->GetGUID() == member) + { + continue; + } + + Unit* unit = botAI->GetUnit(member); + if (unit && bot->GetExactDist2d(unit) < radius) + { + return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit)); + } + } + return false; +} diff --git a/src/strategy/dungeons/wotlk/oculus/OculusActions.h b/src/strategy/dungeons/wotlk/oculus/OculusActions.h new file mode 100644 index 00000000..2111461a --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusActions.h @@ -0,0 +1,77 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCACTIONS_H +#define _PLAYERBOT_WOTLKDUNGEONOCCACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "OculusTriggers.h" +#include "UseItemAction.h" +#include "GenericSpellActions.h" + +const Position uromSafePositions[3] = +{ + Position(1138.88f, 1052.22f, 508.36f), + Position(1084.62f, 1079.71f, 508.36f), + Position(1087.42f, 1020.132f, 508.36f) +}; + +class AvoidUnstableSphereAction : public MovementAction +{ +public: + AvoidUnstableSphereAction(PlayerbotAI* ai) : MovementAction(ai, "avoid unstable sphere") {} + bool Execute(Event event) override; +}; + +class MountDrakeAction : public UseItemAction +{ +public: + MountDrakeAction(PlayerbotAI* ai) : UseItemAction(ai, "mount drake") {} + bool Execute(Event event) override; + bool isPossible() override; +}; + +class DismountDrakeAction : public Action +{ +public: + DismountDrakeAction(PlayerbotAI* ai) : Action(ai, "dismount drake") {} + bool Execute(Event event) override; +}; + +class FlyDrakeAction : public MovementAction +{ +public: + FlyDrakeAction(PlayerbotAI* ai) : MovementAction(ai, "fly drake") {} + bool Execute(Event event) override; +}; + +class DrakeAttackAction : public Action +{ +public: + DrakeAttackAction(PlayerbotAI* botAI) : Action(botAI, "drake attack") {} + bool Execute(Event event) override; + +protected: + Unit* vehicleBase; + bool CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown); + bool AmberDrakeAction(Unit* target); + bool EmeraldDrakeAction(Unit* target); + bool RubyDrakeAction(Unit* target); + +}; + +class AvoidArcaneExplosionAction : public MovementAction +{ +public: + AvoidArcaneExplosionAction(PlayerbotAI* ai) : MovementAction(ai, "avoid arcane explosion") {} + bool Execute(Event event) override; +}; + +class TimeBombSpreadAction : public MovementAction +{ +public: + TimeBombSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "time bomb spread") {} + bool Execute(Event event) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp new file mode 100644 index 00000000..13490f93 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp @@ -0,0 +1,109 @@ +#include "OculusMultipliers.h" +#include "OculusActions.h" +#include "GenericSpellActions.h" +#include "ChooseTargetActions.h" +#include "MovementActions.h" +#include "OculusTriggers.h" +#include "FollowActions.h" +#include "ReachTargetActions.h" + +float MountingDrakeMultiplier::GetValue(Action* action) +{ + // P.I.T.A bug where the bots will somehow interrupt their item spell use, + // even though the 0.5 sec cast goes off, it puts the drake essence on 15 sec cd + // and no drake comes down. + // It seems like this is due to moving/other actions being processed during the 0.5 secs. + // If we suppress everything, they seem to mount properly. A bit of a ham-fisted solution but it works + Player* master = botAI->GetMaster(); + if (bot->GetMapId() != OCULUS_MAP_ID || !master->GetVehicleBase() || bot->GetVehicleBase()) { return 1.0f; } + + if (!dynamic_cast(action)) + { + return 0.0f; + } + return 1.0f; +} + +float FlyingMultiplier::GetValue(Action* action) +{ + if (bot->GetMapId() != OCULUS_MAP_ID || !bot->GetVehicleBase()) { return 1.0f; } + + // Suppresses FollowAction as well as some attack-based movements + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + return 1.0f; +} + +float UromMultiplier::GetValue(Action* action) +{ + if(GetPhaseByCurrentPosition(bot) < 3) + { + Unit* target = action->GetTarget(); + if (target && target->GetEntry() == NPC_MAGE_LORD_UROM) + { + return 0.0f; + } + } + + Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom"); + if (!boss) { return 1.0f; } + + // REAL BOSS FIGHT + if (boss->HasUnitState(UNIT_STATE_CASTING) && + boss->FindCurrentSpellBySpellId(SPELL_EMPOWERED_ARCANE_EXPLOSION)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + } + + // Don't bother avoiding Frostbomb for melee + if (botAI->IsMelee(bot)) + { + if (dynamic_cast(action)) + { + return 0.0f; + } + } + + if (bot->HasAura(SPELL_TIME_BOMB)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + } + + return 1.0f; +} + +uint8 UromMultiplier::GetPhaseByCurrentPosition(Unit* unit) +{ + // Distance to return a positive match for spawn platforms, tweak slightly if needed/ + // Make sure this doesn't get too large and reach the central ring as well + float distance = 60.0f; + + for (uint8 i = 0; i < 3; ++i) + { + if (unit->GetDistance(uromCoords[i][0], uromCoords[i][1], uromCoords[i][2]) < distance) + { + return i; + } + } + return 3; +} + +float EregosMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ley-guardian eregos"); + if (!boss) { return 1.0f; } + + if (boss->HasAura(SPELL_PLANAR_SHIFT && dynamic_cast(action))) + { + return 0.0f; + } + return 1.0f; +} diff --git a/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.h b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.h new file mode 100644 index 00000000..f2f86f79 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.h @@ -0,0 +1,53 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCMULTIPLIERS_H +#define _PLAYERBOT_WOTLKDUNGEONOCCMULTIPLIERS_H + +#include "Multiplier.h" +#include "Unit.h" + +const float uromCoords[4][4] = +{ // Platform coordinates + {1177.47f, 937.722f, 527.405f, 2.21657f}, + {968.66f, 1042.53f, 527.32f, 0.077f}, + {1164.02f, 1170.85f, 527.321f, 3.66f}, + {1118.31f, 1080.377f, 508.361f, 4.25f} // Inner ring, actual boss fight +}; + +class MountingDrakeMultiplier : public Multiplier +{ + public: + MountingDrakeMultiplier(PlayerbotAI* ai) : Multiplier(ai, "mounting drake") {} + + public: + virtual float GetValue(Action* action); +}; + +class FlyingMultiplier : public Multiplier +{ + public: + FlyingMultiplier(PlayerbotAI* ai) : Multiplier(ai, "flying drake") {} + + public: + virtual float GetValue(Action* action); +}; + +class UromMultiplier : public Multiplier +{ + public: + UromMultiplier(PlayerbotAI* ai) : Multiplier(ai, "mage-lord urom") {} + + public: + virtual float GetValue(Action* action); + protected: + uint8 GetPhaseByCurrentPosition(Unit* boss); +}; + +class EregosMultiplier : public Multiplier +{ + public: + EregosMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ley-guardian eregos") {} + + public: + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusStrategy.cpp b/src/strategy/dungeons/wotlk/oculus/OculusStrategy.cpp new file mode 100644 index 00000000..2b98e7da --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusStrategy.cpp @@ -0,0 +1,42 @@ +#include "OculusStrategy.h" +#include "OculusMultipliers.h" + + +void WotlkDungeonOccStrategy::InitTriggers(std::vector &triggers) +{ + // Drakos the Interrogator + // TODO: May need work, TBA. + triggers.push_back(new TriggerNode("unstable sphere", + NextAction::array(0, new NextAction("avoid unstable sphere", ACTION_MOVE + 5), nullptr))); + + // DRAKES + triggers.push_back(new TriggerNode("drake mount", + NextAction::array(0, new NextAction("mount drake", ACTION_RAID + 5), nullptr))); + triggers.push_back(new TriggerNode("drake dismount", + NextAction::array(0, new NextAction("dismount drake", ACTION_RAID + 5), nullptr))); + triggers.push_back(new TriggerNode("group flying", + NextAction::array(0, new NextAction("fly drake", ACTION_NORMAL + 1), nullptr))); + triggers.push_back(new TriggerNode("drake combat", + NextAction::array(0, new NextAction("drake attack", ACTION_NORMAL + 5), nullptr))); + + // Varos Cloudstrider + // Seems to be no way to identify the marked cores, may need to hook boss AI.. + // triggers.push_back(new TriggerNode("varos cloudstrider", + // NextAction::array(0, new NextAction("avoid energize cores", ACTION_RAID + 5), nullptr))); + + // Mage-Lord Urom + triggers.push_back(new TriggerNode("arcane explosion", + NextAction::array(0, new NextAction("avoid arcane explosion", ACTION_MOVE + 5), nullptr))); + triggers.push_back(new TriggerNode("time bomb", + NextAction::array(0, new NextAction("time bomb spread", ACTION_MOVE + 4), nullptr))); + + // Ley-Guardian Eregos +} + +void WotlkDungeonOccStrategy::InitMultipliers(std::vector &multipliers) +{ + multipliers.push_back(new MountingDrakeMultiplier(botAI)); + multipliers.push_back(new FlyingMultiplier(botAI)); + multipliers.push_back(new UromMultiplier(botAI)); + multipliers.push_back(new EregosMultiplier(botAI)); +} diff --git a/src/strategy/dungeons/wotlk/oculus/OculusStrategy.h b/src/strategy/dungeons/wotlk/oculus/OculusStrategy.h new file mode 100644 index 00000000..a734f2f4 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusStrategy.h @@ -0,0 +1,18 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCSTRATEGY_H +#define _PLAYERBOT_WOTLKDUNGEONOCCSTRATEGY_H + +#include "Multiplier.h" +#include "AiObjectContext.h" +#include "Strategy.h" + + +class WotlkDungeonOccStrategy : public Strategy +{ +public: + WotlkDungeonOccStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "oculus"; } + virtual void InitTriggers(std::vector &triggers) override; + virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusTriggerContext.h b/src/strategy/dungeons/wotlk/oculus/OculusTriggerContext.h new file mode 100644 index 00000000..8cbf88db --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusTriggerContext.h @@ -0,0 +1,33 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERCONTEXT_H + +#include "NamedObjectContext.h" +#include "AiObjectContext.h" +#include "OculusTriggers.h" + +class WotlkDungeonOccTriggerContext : public NamedObjectContext +{ + public: + WotlkDungeonOccTriggerContext() + { + creators["unstable sphere"] = &WotlkDungeonOccTriggerContext::unstable_sphere; + creators["drake mount"] = &WotlkDungeonOccTriggerContext::drake_mount; + creators["drake dismount"] = &WotlkDungeonOccTriggerContext::drake_dismount; + creators["group flying"] = &WotlkDungeonOccTriggerContext::group_flying; + creators["drake combat"] = &WotlkDungeonOccTriggerContext::drake_combat; + creators["varos cloudstrider"] = &WotlkDungeonOccTriggerContext::varos_cloudstrider; + creators["arcane explosion"] = &WotlkDungeonOccTriggerContext::arcane_explosion; + creators["time bomb"] = &WotlkDungeonOccTriggerContext::time_bomb; + } + private: + static Trigger* unstable_sphere(PlayerbotAI* ai) { return new DrakosUnstableSphereTrigger(ai); } + static Trigger* drake_mount(PlayerbotAI* ai) { return new DrakeMountTrigger(ai); } + static Trigger* drake_dismount(PlayerbotAI* ai) { return new DrakeDismountTrigger(ai); } + static Trigger* group_flying(PlayerbotAI* ai) { return new GroupFlyingTrigger(ai); } + static Trigger* drake_combat(PlayerbotAI* ai) { return new DrakeCombatTrigger(ai); } + static Trigger* varos_cloudstrider(PlayerbotAI* ai) { return new VarosCloudstriderTrigger(ai); } + static Trigger* arcane_explosion(PlayerbotAI* ai) { return new UromArcaneExplosionTrigger(ai); } + static Trigger* time_bomb(PlayerbotAI* ai) { return new UromTimeBombTrigger(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp b/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp new file mode 100644 index 00000000..38c99280 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp @@ -0,0 +1,85 @@ +#include "Playerbots.h" +#include "OculusTriggers.h" +#include "AiObject.h" +#include "AiObjectContext.h" +#include "Unit.h" + +bool DrakosUnstableSphereTrigger::IsActive() +{ + // Doesn't seem to be much point trying to get melee to dodge this, + // they get hit anyway and it just causes a lot of running around and chaos + // if (botAI->IsMelee(bot)) { return false; } + if (botAI->IsTank(bot)) { return false; } + + GuidVector targets = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->GetEntry() == NPC_UNSTABLE_SPHERE) + { + return true; + } + } + return false; +} + +bool DrakeMountTrigger::IsActive() +{ + Player* master = botAI->GetMaster(); + if (!master) { return false; } + + return master->GetVehicleBase() && !bot->GetVehicleBase(); +} + +bool DrakeDismountTrigger::IsActive() +{ + Player* master = botAI->GetMaster(); + if (!master) { return false; } + + return !master->GetVehicleBase() && bot->GetVehicleBase(); +} + +bool GroupFlyingTrigger::IsActive() +{ + Player* master = botAI->GetMaster(); + if (!master) { return false; } + + return master->GetVehicleBase() && bot->GetVehicleBase(); +} + +bool DrakeCombatTrigger::IsActive() +{ + Unit* vehicleBase = bot->GetVehicleBase(); + if (!vehicleBase) { return false; } + + GuidVector attackers = AI_VALUE(GuidVector, "attackers"); + for (auto& attacker : attackers) + { + Unit* target = botAI->GetUnit(attacker); + if (!target) { continue; } + + return true; + } + return false; +} + +bool VarosCloudstriderTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "varos cloudstrider"); + if (!boss) { return false; } + + return true; +} + +bool UromArcaneExplosionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom"); + if (!boss) { return false; } + + return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_EMPOWERED_ARCANE_EXPLOSION); +} + +bool UromTimeBombTrigger::IsActive() +{ + return bot->HasAura(SPELL_TIME_BOMB); +} diff --git a/src/strategy/dungeons/wotlk/oculus/OculusTriggers.h b/src/strategy/dungeons/wotlk/oculus/OculusTriggers.h new file mode 100644 index 00000000..8d2d55d1 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusTriggers.h @@ -0,0 +1,129 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERS_H +#define _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERS_H + +#include "Trigger.h" +#include "PlayerbotAIConfig.h" +#include "GenericTriggers.h" +#include "DungeonStrategyUtils.h" + +enum OculusIDs +{ + // Drakos the Interrogator + NPC_UNSTABLE_SPHERE = 28166, + SPELL_UNSTABLE_SPHERE_PASSIVE = 50756, + SPELL_UNSTABLE_SPHERE_PULSE = 50757, + SPELL_UNSTABLE_SPHERE_TIMER = 50758, + + // Drakes + NPC_AMBER_DRAKE = 27755, + NPC_EMERALD_DRAKE = 27692, + NPC_RUBY_DRAKE = 27756, + ITEM_AMBER_ESSENCE = 37859, + ITEM_EMERALD_ESSENCE = 37815, + ITEM_RUBY_ESSENCE = 37860, + SPELL_AMBER_ESSENCE = 49461, + SPELL_EMERALD_ESSENCE = 49345, + SPELL_RUBY_ESSENCE = 49462, + // Abilities: + // Amber + SPELL_SHOCK_LANCE = 49840, + SPELL_SHOCK_CHARGE = 49836, + SPELL_STOP_TIME = 49838, + SPELL_TEMPORAL_RIFT = 49592, + // Emerald + SPELL_LEECHING_POISON = 50328, + SPELL_TOUCH_THE_NIGHTMARE = 50341, + SPELL_DREAM_FUNNEL = 50344, + // Ruby + SPELL_SEARING_WRATH = 50232, + SPELL_EVASIVE_MANEUVERS = 50240, + SPELL_EVASIVE_CHARGES = 50241, + SPELL_MARTYR = 50253, + + // Varos Cloudstrider + NPC_CENTRIFUGE_CORE = 28183, + + // Mage-Lord Urom + NPC_MAGE_LORD_UROM = 27655, + SPELL_TIME_BOMB_N = 51121, + SPELL_TIME_BOMB_H = 59376, + SPELL_EMPOWERED_ARCANE_EXPLOSION_N = 51110, + SPELL_EMPOWERED_ARCANE_EXPLOSION_H = 59377, + + // Ley-Guardian Eregos + SPELL_ENRAGED_ASSAULT = 51170, + SPELL_PLANAR_SHIFT = 51162, +}; + +#define SPELL_EMPOWERED_ARCANE_EXPLOSION DUNGEON_MODE(bot, SPELL_EMPOWERED_ARCANE_EXPLOSION_N, SPELL_EMPOWERED_ARCANE_EXPLOSION_H) +#define SPELL_TIME_BOMB DUNGEON_MODE(bot, SPELL_TIME_BOMB_N, SPELL_TIME_BOMB_H) + +const std::vector DRAKE_ITEMS = {ITEM_AMBER_ESSENCE, ITEM_EMERALD_ESSENCE, ITEM_RUBY_ESSENCE}; +const std::vector DRAKE_SPELLS = {SPELL_AMBER_ESSENCE, SPELL_EMERALD_ESSENCE, SPELL_RUBY_ESSENCE}; +const uint32 OCULUS_MAP_ID = 578; + +// const float uromCoords[4][4] = +// { +// {1177.47f, 937.722f, 527.405f, 2.21657f}, +// {968.66f, 1042.53f, 527.32f, 0.077f}, +// {1164.02f, 1170.85f, 527.321f, 3.66f}, +// {1118.31f, 1080.377f, 508.361f, 4.25f} // Inner ring, actual boss fight +// }; + +class DrakosUnstableSphereTrigger : public Trigger +{ +public: + DrakosUnstableSphereTrigger(PlayerbotAI* ai) : Trigger(ai, "drakos unstable sphere") {} + bool IsActive() override; +}; + +class DrakeMountTrigger : public Trigger +{ +public: + DrakeMountTrigger(PlayerbotAI* ai) : Trigger(ai, "drake mount") {} + bool IsActive() override; +}; + +class DrakeDismountTrigger : public Trigger +{ +public: + DrakeDismountTrigger(PlayerbotAI* ai) : Trigger(ai, "drake dismount") {} + bool IsActive() override; +}; + +class GroupFlyingTrigger : public Trigger +{ +public: + GroupFlyingTrigger(PlayerbotAI* ai) : Trigger(ai, "drake fly") {} + bool IsActive() override; +}; + +class DrakeCombatTrigger : public Trigger +{ +public: + DrakeCombatTrigger(PlayerbotAI* ai) : Trigger(ai, "drake combat") {} + bool IsActive() override; +}; + +class VarosCloudstriderTrigger : public Trigger +{ +public: + VarosCloudstriderTrigger(PlayerbotAI* ai) : Trigger(ai, "varos cloudstrider") {} + bool IsActive() override; +}; + +class UromArcaneExplosionTrigger : public Trigger +{ +public: + UromArcaneExplosionTrigger(PlayerbotAI* ai) : Trigger(ai, "urom arcane explosion") {} + bool IsActive() override; +}; + +class UromTimeBombTrigger : public Trigger +{ +public: + UromTimeBombTrigger(PlayerbotAI* ai) : Trigger(ai, "urom time bomb") {} + bool IsActive() override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/TODO b/src/strategy/dungeons/wotlk/oculus/TODO deleted file mode 100644 index e69de29b..00000000 diff --git a/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp b/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp index d13fae54..703c7454 100644 --- a/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp +++ b/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp @@ -46,10 +46,11 @@ bool AvoidShadowCrashAction::Execute(Event event) // Could check all enemy units in range as it's possible to pull multiple of these mobs. // They should really be killed 1 by 1, multipulls are messy so we just handle singles for now Unit* unit = AI_VALUE2(Unit*, "find target", "forgotten one"); + if (!unit) { return false; } + Unit* victim = nullptr; float radius = 10.0f; float targetDist = radius + 2.0f; - if (!unit) { return false; } // Actively move if targeted by a shadow crash. // Spell check not needed, they don't have any other non-instant casts @@ -58,13 +59,11 @@ bool AvoidShadowCrashAction::Execute(Event event) // This doesn't seem to avoid casts very well, perhaps because this isn't checked while allies are casting. // TODO: Revisit if this is an issue in heroics, otherwise ignore shadow crashes for the most part. victim = botAI->GetUnit(unit->GetTarget()); - if (!victim) - { - return false; // Exit early if no victim is found - } - if (victim && bot->GetExactDist2d(victim) < radius) + float distance = bot->GetExactDist2d(victim->GetPosition()); + + if (victim && distance < radius) { - return MoveAway(victim, targetDist - bot->GetExactDist2d(victim->GetPosition())); + return MoveAway(victim, targetDist - distance); } } @@ -72,21 +71,13 @@ bool AvoidShadowCrashAction::Execute(Event event) if (botAI->IsMelee(bot)) { return false; } GuidVector members = AI_VALUE(GuidVector, "group members"); - if (members.empty()) - { - return false; // Exit early if no group members are found - } for (auto& member : members) { - if (bot->GetGUID() == member) + Unit* unit = botAI->GetUnit(member); + if (!unit || bot->GetGUID() == member) { continue; } - Unit* memberUnit = botAI->GetUnit(member); - if (!memberUnit) - { - continue; // Skip if the memberUnit is null - } float currentDist = bot->GetExactDist2d(botAI->GetUnit(member)); if (currentDist < radius) { From b061f0016ceae51b869f2b76491d9194ff5571ff Mon Sep 17 00:00:00 2001 From: Revision Date: Tue, 29 Oct 2024 01:02:30 +0100 Subject: [PATCH 06/19] Fix new compiler errors (#645) * Add missing include * Move include --- src/TravelMgr.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TravelMgr.h b/src/TravelMgr.h index b054f436..af7d0717 100644 --- a/src/TravelMgr.h +++ b/src/TravelMgr.h @@ -10,6 +10,7 @@ #include #include "AiObject.h" +#include "Corpse.h" #include "CreatureData.h" #include "GameObject.h" #include "GridDefines.h" From 3cd68c6dec9bb45eb89102767d2ac1586afee2dd Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Tue, 29 Oct 2024 01:15:26 +0100 Subject: [PATCH 07/19] [performance] new baseline (#644) --- conf/playerbots.conf.dist | 10 +- src/PlayerbotAI.cpp | 207 +++++++++--------- src/PlayerbotAI.h | 9 +- src/PlayerbotAIConfig.cpp | 6 +- src/PlayerbotAIConfig.h | 2 - src/RandomPlayerbotMgr.cpp | 27 +-- src/RandomPlayerbotMgr.h | 11 +- .../actions/BattleGroundJoinAction.cpp | 15 -- 8 files changed, 115 insertions(+), 172 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index b644c6c9..8d7bf12e 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -728,7 +728,7 @@ AiPlayerbot.FastReactInBG = 1 # # All In seconds -AiPlayerbot.RandomBotUpdateInterval = 20 +AiPlayerbot.RandomBotUpdateInterval = 1 AiPlayerbot.RandomBotCountChangeMinInterval = 1800 AiPlayerbot.RandomBotCountChangeMaxInterval = 7200 AiPlayerbot.MinRandomBotInWorldTime = 3600 @@ -1459,17 +1459,11 @@ AiPlayerbot.BotActiveAlone = 100 # Specify smart scaling is enabled or not. # The default is 1. When enabled (smart) scales the 'BotActiveAlone' value. -AiPlayerbot.botActiveAloneSmartScale = 1 - # Only when botLevel is between WhenMinLevel and WhenMaxLevel. +AiPlayerbot.botActiveAloneSmartScale = 1 AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1 AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80 -# The server will tune bot activity to reach the desired server tick speed (in ms) -# bots will only join battleground when there is no lag based on latency diffs below -AiPlayerbot.botActiveAloneSmartScaleDiffWithPlayer = 100 -AiPlayerbot.botActiveAloneSmartScaleDiffEmpty = 200 - # Premade spell to avoid (undetected spells) # spellid-radius, ... AiPlayerbot.PremadeAvoidAoe = 62234-4 diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 023ec7b6..af993980 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -31,6 +31,7 @@ #include "ObjectGuid.h" #include "PerformanceMonitor.h" #include "Player.h" +#include "GameTime.h" #include "PlayerbotAIConfig.h" #include "PlayerbotDbStore.h" #include "PlayerbotMgr.h" @@ -4095,7 +4096,7 @@ ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - if (!member || !member->IsInWorld() && member->GetMapId() != bot->GetMapId()) + if (!member || (!member->IsInWorld() && member->GetMapId() != bot->GetMapId())) continue; if (member == bot) @@ -4106,11 +4107,11 @@ ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) if (!memberBotAI || memberBotAI->HasRealPlayerMaster()) return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER; - //IN_GROUP_WITH_REAL_PLAYER + //ALLOWED_PARTY_ACTIVITY if (group->IsLeader(member->GetGUID())) { if (!memberBotAI->AllowActivity(PARTY_ACTIVITY)) - return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER; + return ActivePiorityType::ALLOWED_PARTY_ACTIVITY; } } } @@ -4180,145 +4181,103 @@ ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) if (IsInRealGuild()) return ActivePiorityType::PLAYER_GUILD; - //IN_INACTIVE_MAP - if (bot->IsBeingTeleported() || !bot->IsInWorld() || !HasRealPlayers(bot->GetMap())) - return ActivePiorityType::IN_INACTIVE_MAP; + + //IN_NOT_ACTIVE_MAP + if (Map* map = bot->GetMap()) + { + if (map->GetEntry()->IsWorldMap()) + { + if (!HasRealPlayers(map)) + return ActivePiorityType::IN_NOT_ACTIVE_MAP; + + if (!map->IsGridLoaded(bot->GetPositionX(), bot->GetPositionY())) + return ActivePiorityType::IN_NOT_ACTIVE_MAP; + } + } //IN_ACTIVE_MAP - if (!bot->IsBeingTeleported() && bot->IsInWorld() && HasRealPlayers(bot->GetMap())) - return ActivePiorityType::IN_ACTIVE_MAP; + if (Map* map = bot->GetMap()) + { + if (map->GetEntry()->IsWorldMap()) + { + if (HasRealPlayers(map)) + return ActivePiorityType::IN_ACTIVE_MAP; + } + } // IN_ACTIVE_AREA if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY) { if (HasManyPlayersNearby(20, sPlayerbotAIConfig->sightDistance)) - return ActivePiorityType::IN_ACTIVE_AREA; + return ActivePiorityType::IN_VERY_ACTIVE_AREA; } - return ActivePiorityType::IN_ACTIVE_AREA; + return ActivePiorityType::DEFAULT; } -// Returns the lower and upper bracket for bots to be active. -// Ie. { 10, 20 } means all bots in this bracket will be inactive below 10% activityMod, -// and will be active above 20% activityMod and scale between those values. -std::pair PlayerbotAI::GetPriorityBracket(ActivePiorityType type) +bool PlayerbotAI::AllowActive(ActivityType activityType) { + // no activity allowed during bot initialization during first + // few minutes after starting the server based on maxRandomBots. + if (!sRandomPlayerbotMgr->isBotInitCompleted() && + GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.15) + return false; + + // General exceptions + if (activityType == PACKET_ACTIVITY) + return true; + + ActivePiorityType type = GetPriorityType(activityType); switch (type) { case ActivePiorityType::HAS_REAL_PLAYER_MASTER: case ActivePiorityType::IS_REAL_PLAYER: case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER: case ActivePiorityType::IN_INSTANCE: - case ActivePiorityType::VISIBLE_FOR_PLAYER: - return {0, 0}; - case ActivePiorityType::IS_ALWAYS_ACTIVE: case ActivePiorityType::IN_COMBAT: - return {0, 10}; - case ActivePiorityType::IN_BG_QUEUE: - return {0, 20}; - case ActivePiorityType::IN_LFG: - return {0, 30}; case ActivePiorityType::NEARBY_PLAYER: - return {0, 40}; + case ActivePiorityType::VISIBLE_FOR_PLAYER: + case ActivePiorityType::IS_ALWAYS_ACTIVE: + return true; + break; + case ActivePiorityType::ALLOWED_PARTY_ACTIVITY: + return false; + break; + case ActivePiorityType::IN_BG_QUEUE: + case ActivePiorityType::IN_LFG: case ActivePiorityType::PLAYER_FRIEND: case ActivePiorityType::PLAYER_GUILD: - return {0, 50}; - case ActivePiorityType::IN_ACTIVE_AREA: - case ActivePiorityType::IN_EMPTY_SERVER: - return {50, 100}; case ActivePiorityType::IN_ACTIVE_MAP: - return {70, 100}; - case ActivePiorityType::IN_INACTIVE_MAP: - return {80, 100}; + case ActivePiorityType::IN_NOT_ACTIVE_MAP: + case ActivePiorityType::IN_EMPTY_SERVER: default: - return {90, 100}; + break; } - return {90, 100}; -} + // Bots do not need to move using PathGenerator. + if (activityType == DETAILED_MOVE_ACTIVITY) return false; -bool PlayerbotAI::AllowActive(ActivityType activityType) -{ - // General exceptions - if (activityType == PACKET_ACTIVITY) - return true; + // All exceptions are now done, below is the code to have a specified % of bots + // active at all times. The default is 10%. With 0.1% of all bots going active + // or inactive each minute. + if (sPlayerbotAIConfig->botActiveAlone <= 0) return false; - ActivePiorityType type = GetPriorityType(activityType); - if (activityType == DETAILED_MOVE_ACTIVITY) - { - switch (type) - { - case ActivePiorityType::HAS_REAL_PLAYER_MASTER: - case ActivePiorityType::IS_REAL_PLAYER: - case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER: - case ActivePiorityType::IN_INSTANCE: - case ActivePiorityType::VISIBLE_FOR_PLAYER: - case ActivePiorityType::IN_COMBAT: - case ActivePiorityType::NEARBY_PLAYER: - return true; - break; - case ActivePiorityType::IS_ALWAYS_ACTIVE: - case ActivePiorityType::IN_BG_QUEUE: - case ActivePiorityType::IN_LFG: - case ActivePiorityType::PLAYER_FRIEND: - case ActivePiorityType::PLAYER_GUILD: - case ActivePiorityType::IN_ACTIVE_AREA: - case ActivePiorityType::IN_EMPTY_SERVER: - case ActivePiorityType::IN_ACTIVE_MAP: - case ActivePiorityType::IN_INACTIVE_MAP: - default: - break; - } - } - else if (activityType == REACT_ACTIVITY) - { - switch (type) - { - case ActivePiorityType::HAS_REAL_PLAYER_MASTER: - case ActivePiorityType::IS_REAL_PLAYER: - case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER: - case ActivePiorityType::IN_INSTANCE: - case ActivePiorityType::VISIBLE_FOR_PLAYER: - case ActivePiorityType::IS_ALWAYS_ACTIVE: - case ActivePiorityType::IN_COMBAT: - return true; - break; - case ActivePiorityType::NEARBY_PLAYER: - case ActivePiorityType::IN_BG_QUEUE: - case ActivePiorityType::IN_LFG: - case ActivePiorityType::PLAYER_FRIEND: - case ActivePiorityType::PLAYER_GUILD: - case ActivePiorityType::IN_ACTIVE_AREA: - case ActivePiorityType::IN_EMPTY_SERVER: - case ActivePiorityType::IN_ACTIVE_MAP: - case ActivePiorityType::IN_INACTIVE_MAP: - default: - return false; - break; - } - } + // Normalize the configured botActiveAlone value, and set as default mod. + uint32 botActiveAlonePerc = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone; + uint32 mod = botActiveAlonePerc; - // GetPriorityBracket acitivity - float normalizedBotActiveAlone = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone; - float activePerc = normalizedBotActiveAlone; if (sPlayerbotAIConfig->botActiveAloneSmartScale && bot->GetLevel() >= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMinLevel && bot->GetLevel() <= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMaxLevel) { - std::pair priorityBracket = GetPriorityBracket(type); - if (!priorityBracket.second) return true; - float activityPercentage = sRandomPlayerbotMgr->getActivityPercentage(); - if (priorityBracket.first >= activityPercentage) return false; - if (priorityBracket.second <= activityPercentage && priorityBracket.second < 100) return true; - activePerc = (activityPercentage - priorityBracket.first) / (priorityBracket.second - priorityBracket.first); - activePerc *= (priorityBracket.second == 100) ? normalizedBotActiveAlone : 100; + mod = SmartScaleActivity(type, botActiveAlonePerc); } - // The last number if the amount it cycles per min. Currently set to 1% of the active bots. - uint32 ActivityNumber = GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, activePerc * 0.01f); + uint32 ActivityNumber = GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, + botActiveAlonePerc * static_cast(mod) / 100 * 0.01f); - // The given percentage of bots should be active and rotate 1% of those active bots each minute. - return ActivityNumber <= (activePerc); + // The given percentage of bots should be active and rotate 1% of those active bots each minute. + return ActivityNumber <= (botActiveAlonePerc * mod) / 100; } bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) @@ -4335,6 +4294,42 @@ bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) return allowed; } +uint32 PlayerbotAI::SmartScaleActivity(ActivePiorityType type, uint32 botActiveAlonePerc) +{ + uint32 maxDiff = sWorldUpdateTime.GetMaxUpdateTime(); + if (maxDiff > 1000) return false; + switch (type) + { + case ActivePiorityType::IN_BG_QUEUE: + case ActivePiorityType::IN_LFG: + if (maxDiff > 100) return 80; + if (maxDiff > 50) return 90; + break; + case ActivePiorityType::PLAYER_FRIEND: + case ActivePiorityType::PLAYER_GUILD: + case ActivePiorityType::IN_ACTIVE_MAP: + if (maxDiff > 200) return 10; + if (maxDiff > 150) return 25; + if (maxDiff > 100) return 50; + if (maxDiff > 50) return 80; + break; + case ActivePiorityType::IN_VERY_ACTIVE_AREA: // Many bots nearby. Do not do heavy area checks. + case ActivePiorityType::IN_NOT_ACTIVE_MAP: + if (maxDiff > 100) return 10; + if (maxDiff > 50) return 25; + else return 30; + case ActivePiorityType::IN_EMPTY_SERVER: + return 10; + default: + if (maxDiff > 200) return 10; + if (maxDiff > 150) return 25; + if (maxDiff > 100) return 50; + break; + } + + return botActiveAlonePerc; +} + bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); } bool PlayerbotAI::IsOpposing(uint8 race1, uint8 race2) diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index d1a2f292..03fbe7fe 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -255,11 +255,12 @@ enum class ActivePiorityType : uint8 NEARBY_PLAYER = 9, PLAYER_FRIEND = 10, PLAYER_GUILD = 11, - IN_ACTIVE_AREA = 12, + IN_VERY_ACTIVE_AREA = 12, IN_ACTIVE_MAP = 13, - IN_INACTIVE_MAP = 14, + IN_NOT_ACTIVE_MAP = 14, IN_EMPTY_SERVER = 15, - MAX_TYPE + ALLOWED_PARTY_ACTIVITY = 16, + DEFAULT }; enum ActivityType @@ -547,9 +548,9 @@ public: bool HasPlayerNearby(float range = sPlayerbotAIConfig->reactDistance); bool HasManyPlayersNearby(uint32 trigerrValue = 20, float range = sPlayerbotAIConfig->sightDistance); ActivePiorityType GetPriorityType(ActivityType activityType); - std::pair GetPriorityBracket(ActivePiorityType type); bool AllowActive(ActivityType activityType); bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false); + uint32 SmartScaleActivity(ActivePiorityType type, uint32 botActiveAlonePerc); // Check if player is safe to use. bool IsSafe(Player* player); diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 816a345f..7bf16079 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -156,7 +156,7 @@ bool PlayerbotAIConfig::Initialize() randomBotAutologin = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutologin", true); minRandomBots = sConfigMgr->GetOption("AiPlayerbot.MinRandomBots", 50); maxRandomBots = sConfigMgr->GetOption("AiPlayerbot.MaxRandomBots", 200); - randomBotUpdateInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotUpdateInterval", 20); + randomBotUpdateInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotUpdateInterval", 1); randomBotCountChangeMinInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE); randomBotCountChangeMaxInterval = @@ -470,10 +470,6 @@ bool PlayerbotAIConfig::Initialize() sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1); botActiveAloneSmartScaleWhenMaxLevel = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel", 80); - botActiveAloneSmartScaleDiffWithPlayer = - sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleDiffWithPlayer", 100); - botActiveAloneSmartScaleDiffEmpty = - sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleDiffEmpty", 200); randombotsWalkingRPG = sConfigMgr->GetOption("AiPlayerbot.RandombotsWalkingRPG", false); randombotsWalkingRPGInDoors = sConfigMgr->GetOption("AiPlayerbot.RandombotsWalkingRPG.InDoors", false); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 5ac3fa4a..0f34b41e 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -266,8 +266,6 @@ public: bool botActiveAloneSmartScale; uint32 botActiveAloneSmartScaleWhenMinLevel; uint32 botActiveAloneSmartScaleWhenMaxLevel; - uint32 botActiveAloneSmartScaleDiffWithPlayer; - uint32 botActiveAloneSmartScaleDiffEmpty; bool freeMethodLoot; int32 lootRollLevel; diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 37acca8d..9020f8ce 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -292,11 +292,6 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) if (!sPlayerbotAIConfig->randomBotAutologin || !sPlayerbotAIConfig->enabled) return; - if (sPlayerbotAIConfig->botActiveAloneSmartScale) - { - ScaleBotActivity(); - } - uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); if (!maxAllowedBotCount || (maxAllowedBotCount < sPlayerbotAIConfig->minRandomBots || maxAllowedBotCount > sPlayerbotAIConfig->maxRandomBots)) @@ -318,6 +313,7 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) // when server is balancing bots then boost (decrease value of) the nextCheckDelay till // onlineBotCount reached the AllowedBotCount. + setIsBotInitCompleted(onlineBotCount < maxAllowedBotCount); uint32 updateIntervalTurboBoost = onlineBotCount < maxAllowedBotCount ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; SetNextCheckDelay(updateIntervalTurboBoost * (onlineBotFocus + 25) * 10); @@ -402,27 +398,6 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) } } -void RandomPlayerbotMgr::ScaleBotActivity() -{ - float activityPercentage = getActivityPercentage(); - - // if (activityPercentage >= 100.0f || activityPercentage <= 0.0f) pid.reset(); //Stop integer buildup during - // max/min activity - - // % increase/decrease wanted diff , avg diff - float activityPercentageMod = pid.calculate(sRandomPlayerbotMgr->GetPlayers().empty() ? - sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty : - sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer, - sWorldUpdateTime.GetAverageUpdateTime()); - - activityPercentage = activityPercentageMod + 50; - - // Cap the percentage between 0 and 100. - activityPercentage = std::max(0.0f, std::min(100.0f, activityPercentage)); - - setActivityPercentage(activityPercentage); -} - uint32 RandomPlayerbotMgr::AddRandomBots() { uint32 maxAllowedBotCount = GetEventValue(0, "bot_count"); diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index 5c35aefa..b5208656 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -103,8 +103,7 @@ public: void LogPlayerLocation(); void UpdateAIInternal(uint32 elapsed, bool minimal = false) override; -private: - void ScaleBotActivity(); +//private: public: uint32 activeBots = 0; @@ -164,11 +163,11 @@ public: return BattleMastersCache; } - float getActivityMod() { return activityMod; } - float getActivityPercentage() { return activityMod * 100.0f; } - void setActivityPercentage(float percentage) { activityMod = percentage / 100.0f; } static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; } + bool isBotInitCompleted() const { return _isBotInitCompleted; } + void setIsBotInitCompleted(bool completed) { _isBotInitCompleted = completed; } + void PrepareAddclassCache(); std::map> addclassCache; protected: @@ -177,7 +176,7 @@ protected: private: // pid values are set in constructor botPID pid = botPID(1, 50, -50, 0, 0, 0); - float activityMod = 0.25; + bool _isBotInitCompleted = false; uint32 GetEventValue(uint32 bot, std::string const event); std::string const GetEventData(uint32 bot, std::string const event); uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn, diff --git a/src/strategy/actions/BattleGroundJoinAction.cpp b/src/strategy/actions/BattleGroundJoinAction.cpp index 349ae410..541ce2a4 100644 --- a/src/strategy/actions/BattleGroundJoinAction.cpp +++ b/src/strategy/actions/BattleGroundJoinAction.cpp @@ -234,17 +234,9 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun return false; TeamId teamId = bot->GetTeamId(); - bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() ? - sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty : - sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer) * 1.1; - uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2; uint32 TeamSize = bg->GetMaxPlayersPerTeam(); - // If performance diff is enabled, only queue if there is no lag - if (sPlayerbotAIConfig->botActiveAloneSmartScale && !noLag) - return false; - // If the bot is in a group, only the leader can queue if (bot->GetGroup() && !bot->GetGroup()->IsLeader(bot->GetGUID())) return false; @@ -577,17 +569,10 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg return false; TeamId teamId = bot->GetTeamId(); - bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() ? - sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty : - sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer) * 1.1; uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2; uint32 TeamSize = bg->GetMaxPlayersPerTeam(); - // If performance diff is enabled, only queue if there is no lag - if (sPlayerbotAIConfig->botActiveAloneSmartScale && !noLag) - return false; - // If the bot is in a group, only the leader can queue if (bot->GetGroup() && !bot->GetGroup()->IsLeader(bot->GetGUID())) return false; From 756dcbaa84f526a1c2bec8e8cbf544659fbe06bd Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Tue, 29 Oct 2024 20:41:22 +1100 Subject: [PATCH 08/19] Limit JC jeweler's gems to 3 If bot has jewelcrafting, enforce limit of 3 of any type of jeweler's gems when autogearing/gemming --- src/factory/PlayerbotFactory.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 67344556..076500f6 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -3853,6 +3853,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) int32 bestGemEnchantId[4] = {-1, -1, -1, -1}; // 1, 2, 4, 8 color float bestGemScore[4] = {0, 0, 0, 0}; std::vector curCount = GetCurrentGemsCount(); + uint8 jewelersCount = 0; int requiredActive = bot->GetLevel() <= 70 ? 2 : 1; std::vector availableGems; for (const uint32& enchantGem : enchantGemIdCache) @@ -3982,6 +3983,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) } int32 enchantIdChosen = -1; int32 colorChosen; + bool jewelersGemChosen; float bestGemScore = -1; for (uint32& enchantGem : availableGems) { @@ -3989,6 +3991,11 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) if (!gemTemplate) continue; + // Limit jewelers (JC) epic gems to 3 + bool isJewelersGem = gemTemplate->ItemLimitCategory == 2; + if (isJewelersGem && jewelersCount >= 3) + continue; + const GemPropertiesEntry* gemProperties = sGemPropertiesStore.LookupEntry(gemTemplate->GemProperties); if (!gemProperties) continue; @@ -4022,6 +4029,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) enchantIdChosen = enchant_id; colorChosen = gemProperties->color; bestGemScore = score; + jewelersGemChosen = isJewelersGem; } } if (enchantIdChosen == -1) @@ -4030,6 +4038,8 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) item->SetEnchantment(EnchantmentSlot(enchant_slot), enchantIdChosen, 0, 0, bot->GetGUID()); bot->ApplyEnchantment(item, EnchantmentSlot(enchant_slot), true); curCount = GetCurrentGemsCount(); + if (jewelersGemChosen) + ++jewelersCount; } } } From 8a9c25c72ca46c4ea8f491cbf8c6d2a5fe8aa289 Mon Sep 17 00:00:00 2001 From: bash Date: Tue, 29 Oct 2024 10:57:26 +0000 Subject: [PATCH 09/19] [performance] initialize server and bots fase --- conf/playerbots.conf.dist | 2 +- src/PlayerbotAI.cpp | 3 +- src/PlayerbotAIConfig.cpp | 2 +- src/RandomPlayerbotMgr.cpp | 59 +++++++++++++++++++++----------------- src/RandomPlayerbotMgr.h | 6 ++-- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 8d7bf12e..a8273767 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -728,7 +728,7 @@ AiPlayerbot.FastReactInBG = 1 # # All In seconds -AiPlayerbot.RandomBotUpdateInterval = 1 +AiPlayerbot.RandomBotUpdateInterval = 5 AiPlayerbot.RandomBotCountChangeMinInterval = 1800 AiPlayerbot.RandomBotCountChangeMaxInterval = 7200 AiPlayerbot.MinRandomBotInWorldTime = 3600 diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index af993980..6a36ed24 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -4219,8 +4219,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) { // no activity allowed during bot initialization during first // few minutes after starting the server based on maxRandomBots. - if (!sRandomPlayerbotMgr->isBotInitCompleted() && - GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.15) + if (sRandomPlayerbotMgr->isBotInitializing()) return false; // General exceptions diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 7bf16079..9d9bdd44 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -156,7 +156,7 @@ bool PlayerbotAIConfig::Initialize() randomBotAutologin = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutologin", true); minRandomBots = sConfigMgr->GetOption("AiPlayerbot.MinRandomBots", 50); maxRandomBots = sConfigMgr->GetOption("AiPlayerbot.MaxRandomBots", 200); - randomBotUpdateInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotUpdateInterval", 1); + randomBotUpdateInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotUpdateInterval", 5); randomBotCountChangeMinInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE); randomBotCountChangeMaxInterval = diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 9020f8ce..669d9cea 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -40,6 +40,7 @@ #include "Unit.h" #include "UpdateTime.h" #include "World.h" +#include "GameTime.h" void PrintStatsThread() { sRandomPlayerbotMgr->PrintStats(); } @@ -309,12 +310,17 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) uint32 onlineBotFocus = 75; if (onlineBotCount < (uint32)(sPlayerbotAIConfig->minRandomBots * 90 / 100)) + { onlineBotFocus = 25; + } + + setBotInitializing( + onlineBotCount < maxAllowedBotCount && + GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.15); // when server is balancing bots then boost (decrease value of) the nextCheckDelay till // onlineBotCount reached the AllowedBotCount. - setIsBotInitCompleted(onlineBotCount < maxAllowedBotCount); - uint32 updateIntervalTurboBoost = onlineBotCount < maxAllowedBotCount ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; + uint32 updateIntervalTurboBoost = isBotInitializing() ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; SetNextCheckDelay(updateIntervalTurboBoost * (onlineBotFocus + 25) * 10); PerformanceMonitorOperation* pmo = sPerformanceMonitor->start( @@ -987,35 +993,34 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) if (isLogginIn) return false; + uint32 randomTime; - if (!player) - { - AddPlayerBot(botGUID, 0); - randomTime = urand(1, 2); - SetEventValue(bot, "login", 1, randomTime); + if (!player) + { + AddPlayerBot(botGUID, 0); + randomTime = urand(1, 2); + SetEventValue(bot, "login", 1, randomTime); - randomTime = urand( - std::max(5, static_cast(sPlayerbotAIConfig->randomBotUpdateInterval * 0.5)), - std::max(12, static_cast(sPlayerbotAIConfig->randomBotUpdateInterval * 2))); - SetEventValue(bot, "update", 1, randomTime); + uint32 updateIntervalTurboBoost = isBotInitializing() ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; + randomTime = urand(std::max(5, static_cast(updateIntervalTurboBoost * 0.5)), + std::max(10, static_cast(updateIntervalTurboBoost * 2))); + SetEventValue(bot, "update", 1, randomTime); - // do not randomize or teleport immediately after server start (prevent lagging) - if (!GetEventValue(bot, "randomize")) - { - randomTime = urand( - 3,std::max(4, static_cast(sPlayerbotAIConfig->randomBotUpdateInterval * 0.4))); - ScheduleRandomize(bot, randomTime); - } - if (!GetEventValue(bot, "teleport")) - { - randomTime = urand( - std::max(7, static_cast(sPlayerbotAIConfig->randomBotUpdateInterval * 0.7)), - std::max(14, static_cast(sPlayerbotAIConfig->randomBotUpdateInterval * 1.4))); - ScheduleTeleport(bot, randomTime); - } + // do not randomize or teleport immediately after server start (prevent lagging) + if (!GetEventValue(bot, "randomize")) + { + randomTime = urand(3, std::max(4, static_cast(updateIntervalTurboBoost * 0.4))); + ScheduleRandomize(bot, randomTime); + } + if (!GetEventValue(bot, "teleport")) + { + randomTime = urand(std::max(7, static_cast(updateIntervalTurboBoost * 0.7)), + std::max(14, static_cast(updateIntervalTurboBoost * 1.4))); + ScheduleTeleport(bot, randomTime); + } - return true; - } + return true; + } SetEventValue(bot, "login", 0, 0); diff --git a/src/RandomPlayerbotMgr.h b/src/RandomPlayerbotMgr.h index b5208656..10405413 100644 --- a/src/RandomPlayerbotMgr.h +++ b/src/RandomPlayerbotMgr.h @@ -165,8 +165,8 @@ public: static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; } - bool isBotInitCompleted() const { return _isBotInitCompleted; } - void setIsBotInitCompleted(bool completed) { _isBotInitCompleted = completed; } + bool isBotInitializing() const { return _isBotInitializing; } + void setBotInitializing(bool completed) { _isBotInitializing = completed; } void PrepareAddclassCache(); std::map> addclassCache; @@ -176,7 +176,7 @@ protected: private: // pid values are set in constructor botPID pid = botPID(1, 50, -50, 0, 0, 0); - bool _isBotInitCompleted = false; + bool _isBotInitializing = true; uint32 GetEventValue(uint32 bot, std::string const event); std::string const GetEventData(uint32 bot, std::string const event); uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn, From 76994ae3c01c6ec9435d0aa7ee26ba941ed31678 Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:08:46 +0100 Subject: [PATCH 10/19] [performance] server_and_bot_init_phase_patch1 (#652) --- conf/playerbots.conf.dist | 2 +- src/PlayerbotAIConfig.cpp | 2 +- src/RandomPlayerbotMgr.cpp | 46 +++++++++++++++++++------------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index a8273767..8d7bf12e 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -728,7 +728,7 @@ AiPlayerbot.FastReactInBG = 1 # # All In seconds -AiPlayerbot.RandomBotUpdateInterval = 5 +AiPlayerbot.RandomBotUpdateInterval = 1 AiPlayerbot.RandomBotCountChangeMinInterval = 1800 AiPlayerbot.RandomBotCountChangeMaxInterval = 7200 AiPlayerbot.MinRandomBotInWorldTime = 3600 diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 9d9bdd44..7bf16079 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -156,7 +156,7 @@ bool PlayerbotAIConfig::Initialize() randomBotAutologin = sConfigMgr->GetOption("AiPlayerbot.RandomBotAutologin", true); minRandomBots = sConfigMgr->GetOption("AiPlayerbot.MinRandomBots", 50); maxRandomBots = sConfigMgr->GetOption("AiPlayerbot.MaxRandomBots", 200); - randomBotUpdateInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotUpdateInterval", 5); + randomBotUpdateInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotUpdateInterval", 1); randomBotCountChangeMinInterval = sConfigMgr->GetOption("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE); randomBotCountChangeMaxInterval = diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 669d9cea..4fa098be 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -995,32 +995,32 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot) uint32 randomTime; - if (!player) - { - AddPlayerBot(botGUID, 0); - randomTime = urand(1, 2); - SetEventValue(bot, "login", 1, randomTime); + if (!player) + { + AddPlayerBot(botGUID, 0); + randomTime = urand(1, 2); + SetEventValue(bot, "login", 1, randomTime); - uint32 updateIntervalTurboBoost = isBotInitializing() ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; - randomTime = urand(std::max(5, static_cast(updateIntervalTurboBoost * 0.5)), - std::max(10, static_cast(updateIntervalTurboBoost * 2))); - SetEventValue(bot, "update", 1, randomTime); + uint32 updateIntervalTurboBoost = isBotInitializing() ? 1 : sPlayerbotAIConfig->randomBotUpdateInterval; + randomTime = urand(std::max(5, static_cast(updateIntervalTurboBoost * 0.5)), + std::max(12, static_cast(updateIntervalTurboBoost * 2))); + SetEventValue(bot, "update", 1, randomTime); - // do not randomize or teleport immediately after server start (prevent lagging) - if (!GetEventValue(bot, "randomize")) - { - randomTime = urand(3, std::max(4, static_cast(updateIntervalTurboBoost * 0.4))); - ScheduleRandomize(bot, randomTime); - } - if (!GetEventValue(bot, "teleport")) - { - randomTime = urand(std::max(7, static_cast(updateIntervalTurboBoost * 0.7)), - std::max(14, static_cast(updateIntervalTurboBoost * 1.4))); - ScheduleTeleport(bot, randomTime); - } + // do not randomize or teleport immediately after server start (prevent lagging) + if (!GetEventValue(bot, "randomize")) + { + randomTime = urand(3, std::max(4, static_cast(updateIntervalTurboBoost * 0.4))); + ScheduleRandomize(bot, randomTime); + } + if (!GetEventValue(bot, "teleport")) + { + randomTime = urand(std::max(7, static_cast(updateIntervalTurboBoost * 0.7)), + std::max(14, static_cast(updateIntervalTurboBoost * 1.4))); + ScheduleTeleport(bot, randomTime); + } - return true; - } + return true; + } SetEventValue(bot, "login", 0, 0); From b83cdf2057dc13a43a12d40112adbe9c3e9564e3 Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:26:18 +0100 Subject: [PATCH 11/19] [performance] server_and_bot_init_phase_patch2 (#653) --- src/PlayerbotAI.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 6a36ed24..cc84034c 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -4254,7 +4254,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) } // Bots do not need to move using PathGenerator. - if (activityType == DETAILED_MOVE_ACTIVITY) return false; + //if (activityType == DETAILED_MOVE_ACTIVITY) return false; // All exceptions are now done, below is the code to have a specified % of bots // active at all times. The default is 10%. With 0.1% of all bots going active @@ -4307,21 +4307,18 @@ uint32 PlayerbotAI::SmartScaleActivity(ActivePiorityType type, uint32 botActiveA case ActivePiorityType::PLAYER_FRIEND: case ActivePiorityType::PLAYER_GUILD: case ActivePiorityType::IN_ACTIVE_MAP: - if (maxDiff > 200) return 10; - if (maxDiff > 150) return 25; + if (maxDiff > 200) return 25; if (maxDiff > 100) return 50; if (maxDiff > 50) return 80; break; case ActivePiorityType::IN_VERY_ACTIVE_AREA: // Many bots nearby. Do not do heavy area checks. case ActivePiorityType::IN_NOT_ACTIVE_MAP: - if (maxDiff > 100) return 10; - if (maxDiff > 50) return 25; - else return 30; + if (maxDiff > 100) return 25; + if (maxDiff > 50) return 50; case ActivePiorityType::IN_EMPTY_SERVER: - return 10; + return 25; default: - if (maxDiff > 200) return 10; - if (maxDiff > 150) return 25; + if (maxDiff > 200) return 25; if (maxDiff > 100) return 50; break; } From e66475c3e0d9494e41330493712cad2a42bda204 Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:32:16 +0100 Subject: [PATCH 12/19] [performance] Added additional circle of life, 600 yards (#655) --- src/PlayerbotAI.cpp | 49 +++++++++++++++++++++++++-------------------- src/PlayerbotAI.h | 28 ++++++++++++++------------ 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index cc84034c..3c515ebb 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -4124,10 +4124,6 @@ ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) if (!WorldPosition(bot).isOverworld()) return ActivePiorityType::IN_INSTANCE; - //VISIBLE_FOR_PLAYER - if (HasPlayerNearby(sPlayerbotAIConfig->reactDistance)) - return ActivePiorityType::VISIBLE_FOR_PLAYER; - //IN_COMBAT if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) { @@ -4136,9 +4132,15 @@ ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) return ActivePiorityType::IN_COMBAT; } - //NEARBY_PLAYER + // IN_REACT_DISTANCE + if (HasPlayerNearby(sPlayerbotAIConfig->reactDistance)) + return ActivePiorityType::IN_REACT_DISTANCE; + + // NEARBY_PLAYER acitivity based on yards. if (HasPlayerNearby(300.f)) - return ActivePiorityType::NEARBY_PLAYER; + return ActivePiorityType::NEARBY_PLAYER_300; + if (HasPlayerNearby(600.f)) + return ActivePiorityType::NEARBY_PLAYER_600; //if (sPlayerbotAIConfig->IsFreeAltBot(bot) || HasStrategy("travel once", BotState::BOT_STATE_NON_COMBAT)) // return ActivePiorityType::IS_ALWAYS_ACTIVE; @@ -4205,7 +4207,7 @@ ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType) } } - // IN_ACTIVE_AREA + // IN_VERY_ACTIVE_AREA if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY) { if (HasManyPlayersNearby(20, sPlayerbotAIConfig->sightDistance)) @@ -4226,7 +4228,10 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) if (activityType == PACKET_ACTIVITY) return true; + uint32 botActiveAlonePerc = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone; + uint32 mod = botActiveAlonePerc; ActivePiorityType type = GetPriorityType(activityType); + switch (type) { case ActivePiorityType::HAS_REAL_PLAYER_MASTER: @@ -4234,8 +4239,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER: case ActivePiorityType::IN_INSTANCE: case ActivePiorityType::IN_COMBAT: - case ActivePiorityType::NEARBY_PLAYER: - case ActivePiorityType::VISIBLE_FOR_PLAYER: + case ActivePiorityType::IN_REACT_DISTANCE: + case ActivePiorityType::NEARBY_PLAYER_300: case ActivePiorityType::IS_ALWAYS_ACTIVE: return true; break; @@ -4260,11 +4265,6 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) // active at all times. The default is 10%. With 0.1% of all bots going active // or inactive each minute. if (sPlayerbotAIConfig->botActiveAlone <= 0) return false; - - // Normalize the configured botActiveAlone value, and set as default mod. - uint32 botActiveAlonePerc = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone; - uint32 mod = botActiveAlonePerc; - if (sPlayerbotAIConfig->botActiveAloneSmartScale && bot->GetLevel() >= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMinLevel && bot->GetLevel() <= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMaxLevel) @@ -4302,23 +4302,28 @@ uint32 PlayerbotAI::SmartScaleActivity(ActivePiorityType type, uint32 botActiveA case ActivePiorityType::IN_BG_QUEUE: case ActivePiorityType::IN_LFG: if (maxDiff > 100) return 80; - if (maxDiff > 50) return 90; + if (maxDiff > 100) return 90; + break; + case ActivePiorityType::NEARBY_PLAYER_600: + if (maxDiff > 100) return 50; + if (maxDiff > 50) return 75; break; case ActivePiorityType::PLAYER_FRIEND: case ActivePiorityType::PLAYER_GUILD: case ActivePiorityType::IN_ACTIVE_MAP: - if (maxDiff > 200) return 25; + if (maxDiff > 200) return 30; if (maxDiff > 100) return 50; - if (maxDiff > 50) return 80; - break; - case ActivePiorityType::IN_VERY_ACTIVE_AREA: // Many bots nearby. Do not do heavy area checks. + if (maxDiff > 50) return 75; + break; + case ActivePiorityType::IN_VERY_ACTIVE_AREA: case ActivePiorityType::IN_NOT_ACTIVE_MAP: - if (maxDiff > 100) return 25; + if (maxDiff > 100) return 30; if (maxDiff > 50) return 50; + break; case ActivePiorityType::IN_EMPTY_SERVER: - return 25; + return 30; default: - if (maxDiff > 200) return 25; + if (maxDiff > 200) return 30; if (maxDiff > 100) return 50; break; } diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 03fbe7fe..f7cf77ca 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -247,19 +247,21 @@ enum class ActivePiorityType : uint8 HAS_REAL_PLAYER_MASTER = 1, IN_GROUP_WITH_REAL_PLAYER = 2, IN_INSTANCE = 3, - VISIBLE_FOR_PLAYER = 4, - IS_ALWAYS_ACTIVE = 5, - IN_COMBAT = 6, - IN_BG_QUEUE = 7, - IN_LFG = 8, - NEARBY_PLAYER = 9, - PLAYER_FRIEND = 10, - PLAYER_GUILD = 11, - IN_VERY_ACTIVE_AREA = 12, - IN_ACTIVE_MAP = 13, - IN_NOT_ACTIVE_MAP = 14, - IN_EMPTY_SERVER = 15, - ALLOWED_PARTY_ACTIVITY = 16, + IS_ALWAYS_ACTIVE = 4, + IN_COMBAT = 5, + IN_BG_QUEUE = 6, + IN_LFG = 7, + IN_REACT_DISTANCE = 8, + NEARBY_PLAYER_300 = 9, + NEARBY_PLAYER_600 = 10, + NEARBY_PLAYER_900 = 11, + PLAYER_FRIEND = 12, + PLAYER_GUILD = 13, + IN_VERY_ACTIVE_AREA = 14, + IN_ACTIVE_MAP = 15, + IN_NOT_ACTIVE_MAP = 16, + IN_EMPTY_SERVER = 17, + ALLOWED_PARTY_ACTIVITY = 18, DEFAULT }; From 645bc4948e78f20a02cdbb2638ca0c67cbae75b1 Mon Sep 17 00:00:00 2001 From: bash Date: Wed, 30 Oct 2024 01:49:42 +0000 Subject: [PATCH 13/19] [performance] side-effect of other bug causes to stay inactive when botAmount min/max not equal --- src/RandomPlayerbotMgr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 4fa098be..ca9049ec 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -315,8 +315,8 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) } setBotInitializing( - onlineBotCount < maxAllowedBotCount && - GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.15); + //onlineBotCount < maxAllowedBotCount && <-- these fields are incorrect when using bot amount min/max are not equal. + GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.12); // when server is balancing bots then boost (decrease value of) the nextCheckDelay till // onlineBotCount reached the AllowedBotCount. From 2cedf3f05b40efb4340a32d561140f3cd840ec2a Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Wed, 30 Oct 2024 21:10:43 +0800 Subject: [PATCH 14/19] [Sync] Core sync 20241030 --- src/TravelMgr.cpp | 1 + src/strategy/actions/MovementActions.cpp | 1 + src/strategy/actions/ReleaseSpiritAction.cpp | 1 + src/strategy/actions/ReviveFromCorpseAction.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/src/TravelMgr.cpp b/src/TravelMgr.cpp index af8bb12f..301802b6 100644 --- a/src/TravelMgr.cpp +++ b/src/TravelMgr.cpp @@ -18,6 +18,7 @@ #include "TransportMgr.h" #include "VMapFactory.h" #include "VMapMgr2.h" +#include "Corpse.h" WorldPosition::WorldPosition(std::string const str) { diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index 791f0990..899b91b5 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -41,6 +41,7 @@ #include "Unit.h" #include "Vehicle.h" #include "WaypointMovementGenerator.h" +#include "Corpse.h" MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) { diff --git a/src/strategy/actions/ReleaseSpiritAction.cpp b/src/strategy/actions/ReleaseSpiritAction.cpp index 382464e4..d15c7487 100644 --- a/src/strategy/actions/ReleaseSpiritAction.cpp +++ b/src/strategy/actions/ReleaseSpiritAction.cpp @@ -11,6 +11,7 @@ #include "ObjectGuid.h" #include "Playerbots.h" #include "ServerFacade.h" +#include "Corpse.h" bool ReleaseSpiritAction::Execute(Event event) { diff --git a/src/strategy/actions/ReviveFromCorpseAction.cpp b/src/strategy/actions/ReviveFromCorpseAction.cpp index 4f522104..4f19a639 100644 --- a/src/strategy/actions/ReviveFromCorpseAction.cpp +++ b/src/strategy/actions/ReviveFromCorpseAction.cpp @@ -13,6 +13,7 @@ #include "Playerbots.h" #include "RandomPlayerbotMgr.h" #include "ServerFacade.h" +#include "Corpse.h" bool ReviveFromCorpseAction::Execute(Event event) { From 9ca93855fe9bb561a2e203417e7a7218c257c6c0 Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Thu, 31 Oct 2024 22:13:05 +1100 Subject: [PATCH 15/19] Fix healer and tank multiplier on Krikthir --- .../dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp b/src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp index 38cb9e9a..036d79c3 100644 --- a/src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp +++ b/src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp @@ -8,15 +8,17 @@ float KrikthirMultiplier::GetValue(Action* action) { + if (!botAI->IsDps(bot)) { return 1.0f; } + // Target is not findable from threat table using AI_VALUE2(), // therefore need to search manually for the unit name Unit* boss = nullptr; Unit* watcher = nullptr; - GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); - for (auto i = targets.begin(); i != targets.end(); ++i) + GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + for (auto& target : targets) { - Unit* unit = botAI->GetUnit(*i); + Unit* unit = botAI->GetUnit(target); if (!unit) { continue; } switch (unit->GetEntry()) From fb2391d4cef33c87dd930b7b498241920ae907eb Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Fri, 1 Nov 2024 17:47:51 +1100 Subject: [PATCH 16/19] Oculus crash fix Master nullptr check --- src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp index 13490f93..023d5256 100644 --- a/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp +++ b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp @@ -15,6 +15,8 @@ float MountingDrakeMultiplier::GetValue(Action* action) // It seems like this is due to moving/other actions being processed during the 0.5 secs. // If we suppress everything, they seem to mount properly. A bit of a ham-fisted solution but it works Player* master = botAI->GetMaster(); + if (!master) { return 1.0f; } + if (bot->GetMapId() != OCULUS_MAP_ID || !master->GetVehicleBase() || bot->GetVehicleBase()) { return 1.0f; } if (!dynamic_cast(action)) From dc55bdd8dc3764826ac89db5fa3c58dbe6dd800f Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:03:04 +0100 Subject: [PATCH 17/19] disabled smartscale by default for now (#663) * disabled smartscale by default for now * disabled smartscale by default for now --- conf/playerbots.conf.dist | 2 +- src/PlayerbotAIConfig.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 8d7bf12e..884ab9f1 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -1460,7 +1460,7 @@ AiPlayerbot.BotActiveAlone = 100 # Specify smart scaling is enabled or not. # The default is 1. When enabled (smart) scales the 'BotActiveAlone' value. # Only when botLevel is between WhenMinLevel and WhenMaxLevel. -AiPlayerbot.botActiveAloneSmartScale = 1 +AiPlayerbot.botActiveAloneSmartScale = 0 AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1 AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80 diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 7bf16079..6c721024 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -465,7 +465,7 @@ bool PlayerbotAIConfig::Initialize() playerbotsXPrate = sConfigMgr->GetOption("AiPlayerbot.KillXPRate", 1); disableDeathKnightLogin = sConfigMgr->GetOption("AiPlayerbot.DisableDeathKnightLogin", 0); botActiveAlone = sConfigMgr->GetOption("AiPlayerbot.BotActiveAlone", 100); - botActiveAloneSmartScale = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScale", 1); + botActiveAloneSmartScale = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScale", 0); botActiveAloneSmartScaleWhenMinLevel = sConfigMgr->GetOption("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1); botActiveAloneSmartScaleWhenMaxLevel = From 5c16e14d576e5f85a82f1d2eb82768e0456c14cb Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Sun, 3 Nov 2024 12:15:03 +0000 Subject: [PATCH 18/19] Fix Crash SayToChannel (#666) Additional checks for null pointers to ensure that the Channel and ChannelMgr objects are correctly initialized before accessing them. Validation of empty strings to avoid problems when checking the channel name (channel->GetName()). Implemented mutex (std::lock_guard) around access to SocialMgr to avoid race conditions, improving security in multi-threaded operations. --- src/PlayerbotAI.cpp | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 3c515ebb..b9c7ff60 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "AiFactory.h" #include "BudgetValues.h" @@ -2452,8 +2453,12 @@ bool PlayerbotAI::SayToWorld(const std::string& msg) bool PlayerbotAI::SayToChannel(const std::string& msg, const ChatChannelId& chanId) { + // Checks whether the message or ChannelMgr is valid + if (msg.empty()) + return false; + ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId()); - if (!cMgr || msg.empty()) + if (!cMgr) return false; AreaTableEntry const* current_zone = GetCurrentZone(); @@ -2461,11 +2466,24 @@ bool PlayerbotAI::SayToChannel(const std::string& msg, const ChatChannelId& chan return false; const auto current_str_zone = GetLocalizedAreaName(current_zone); + + std::mutex socialMutex; + std::lock_guard lock(socialMutex); // Blocking for thread safety when accessing SocialMgr + for (auto const& [key, channel] : cMgr->GetChannels()) { - // check for current zone - if (channel && channel->GetChannelId() == chanId) + // Checks if the channel pointer is valid + if (!channel) + continue; + + // Checks if the channel matches the specified ChatChannelId + if (channel->GetChannelId() == chanId) { + // If the channel name is empty, skip it to avoid access problems + if (channel->GetName().empty()) + continue; + + // Checks if the channel name contains the current zone const auto does_contains = channel->GetName().find(current_str_zone) != std::string::npos; if (chanId != ChatChannelId::LOOKING_FOR_GROUP && chanId != ChatChannelId::WORLD_DEFENSE && !does_contains) { @@ -2473,11 +2491,15 @@ bool PlayerbotAI::SayToChannel(const std::string& msg, const ChatChannelId& chan } else if (chanId == ChatChannelId::LOOKING_FOR_GROUP || chanId == ChatChannelId::WORLD_DEFENSE) { - // check if capitals then return false if not + // Here you can add the capital check if necessary } - channel->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL); - return true; + // Final check to ensure the channel is correct before trying to say something + if (channel) + { + channel->Say(bot->GetGUID(), msg.c_str(), LANG_UNIVERSAL); + return true; + } } } From 7e74fbe1811340c2679eea2577ef17a90c927042 Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Sun, 3 Nov 2024 12:15:16 +0000 Subject: [PATCH 19/19] Fix Crash QuestAction (#665) Added checks to ensure bot and botAI are valid at function start. Added extra pointer checks when accessing unit and gameobj in loops that iterate over nearest NPCs and game objects. This adjustment ensures that objects are initialized correctly before being used in the ProcessQuests function. --- src/strategy/actions/QuestAction.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/strategy/actions/QuestAction.cpp b/src/strategy/actions/QuestAction.cpp index 139033f1..13ed7bba 100644 --- a/src/strategy/actions/QuestAction.cpp +++ b/src/strategy/actions/QuestAction.cpp @@ -15,9 +15,13 @@ bool QuestAction::Execute(Event event) { ObjectGuid guid = event.getObject(); - Player* master = GetMaster(); + // Checks if the bot and botAI are valid + if (!bot || !botAI) + return false; + + // Sets guid based on bot or master target if (!guid) { if (!master) @@ -36,19 +40,27 @@ bool QuestAction::Execute(Event event) } bool result = false; + + // Check the nearest NPCs GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); - for (const auto npc : npcs) + for (const auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); if (unit && bot->GetDistance(unit) <= INTERACTION_DISTANCE) + { result |= ProcessQuests(unit); + } } + + // Checks the nearest game objects std::list gos = AI_VALUE(std::list, "nearest game objects"); - for (const auto go : gos) + for (const auto& go : gos) { GameObject* gameobj = botAI->GetGameObject(go); if (gameobj && bot->GetDistance(gameobj) <= INTERACTION_DISTANCE) + { result |= ProcessQuests(gameobj); + } } return result;