diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index b8121320c..9b52369e4 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 5ed70f6cd..3bb39c187 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 b1301868a..81b47ae27 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 cb8194303..0bafe36a5 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 000000000..0e12c908c --- /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 000000000..dd857569a --- /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 000000000..989cb6ab0 --- /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 000000000..31ec9283f --- /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 000000000..587b0f704 --- /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 000000000..a78270d15 --- /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 000000000..9c687da9c --- /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 000000000..723a1904c --- /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 000000000..a2b7dc11c --- /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 000000000..28fafa368 --- /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 e69de29bb..000000000 diff --git a/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp b/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp index e8863a12d..0cfd6202b 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 ac469e955..6bb82079d 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 e69de29bb..000000000 diff --git a/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h b/src/strategy/dungeons/wotlk/utgardepinnacle/UtgardePinnacleActionContext.h new file mode 100644 index 000000000..5a9dff5b8 --- /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 000000000..f084ca15b --- /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 000000000..38e90dd15 --- /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 000000000..82e5fd53b --- /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 000000000..365766783 --- /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 000000000..6877ba2cf --- /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 000000000..e8f45363b --- /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 000000000..143f3df5a --- /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 000000000..168d16e69 --- /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 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 + 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 000000000..bd42c3d62 --- /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 cbbc8894b..1152842ac 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 3915dde3b..9e192c577 100644 --- a/src/strategy/values/NearestNpcsValue.h +++ b/src/strategy/values/NearestNpcsValue.h @@ -24,6 +24,19 @@ class NearestNpcsValue : public NearestUnitsValue 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 f8f4a8fed..f57c8083b 100644 --- a/src/strategy/values/ValueContext.h +++ b/src/strategy/values/ValueContext.h @@ -104,6 +104,7 @@ class ValueContext : public NamedObjectContext 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 @@ class ValueContext : public NamedObjectContext } 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); }