From 45303d55c1d40b959f3b5696e46722e765c96e79 Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Sun, 6 Oct 2024 00:24:45 +1000 Subject: [PATCH] Nexus implementation & dragon flank code --- src/strategy/AiObjectContext.cpp | 2 + .../dungeons/DungeonStrategyContext.h | 5 +- .../wotlk/WotlkDungeonActionContext.h | 2 +- .../wotlk/WotlkDungeonTriggerContext.h | 2 +- .../dungeons/wotlk/nexus/NexusActionContext.h | 30 +++ .../dungeons/wotlk/nexus/NexusActions.cpp | 206 ++++++++++++++++++ .../dungeons/wotlk/nexus/NexusActions.h | 79 +++++++ .../dungeons/wotlk/nexus/NexusMultipliers.cpp | 99 +++++++++ .../dungeons/wotlk/nexus/NexusMultipliers.h | 51 +++++ .../dungeons/wotlk/nexus/NexusStrategy.cpp | 54 +++++ .../dungeons/wotlk/nexus/NexusStrategy.h | 18 ++ .../wotlk/nexus/NexusTriggerContext.h | 33 +++ .../dungeons/wotlk/nexus/NexusTriggers.cpp | 105 +++++++++ .../dungeons/wotlk/nexus/NexusTriggers.h | 83 ++++--- 14 files changed, 720 insertions(+), 49 deletions(-) create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusActionContext.h create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusActions.cpp create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusActions.h create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusMultipliers.cpp create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusMultipliers.h create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusStrategy.h create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h create mode 100644 src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index c6fcbc47..0742d363 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -51,6 +51,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new RaidUlduarActionContext()); actionContexts.Add(new RaidIccActionContext()); actionContexts.Add(new WotlkDungeonUKActionContext()); + actionContexts.Add(new WotlkDungeonNexActionContext()); triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new ChatTriggerContext()); @@ -62,6 +63,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new RaidUlduarTriggerContext()); triggerContexts.Add(new RaidIccTriggerContext()); triggerContexts.Add(new WotlkDungeonUKTriggerContext()); + triggerContexts.Add(new WotlkDungeonNexTriggerContext()); valueContexts.Add(new ValueContext()); diff --git a/src/strategy/dungeons/DungeonStrategyContext.h b/src/strategy/dungeons/DungeonStrategyContext.h index 4715de4e..afa2616f 100644 --- a/src/strategy/dungeons/DungeonStrategyContext.h +++ b/src/strategy/dungeons/DungeonStrategyContext.h @@ -3,12 +3,11 @@ #include "Strategy.h" #include "wotlk/utgardekeep/UtgardeKeepStrategy.h" +#include "wotlk/nexus/NexusStrategy.h" /* Full list/TODO: -The Nexus - Nex -Grand Magus Telestra, Anomalus, Ormorok the Tree-Shaper, Keristrasza, Commander Stoutbeard (Horde Heroic Only)/Commander Kolurg (Alliance Heroic Only) Azjol-Nerub: Azjol-Nerub - AN Krik'thir the Gatewatcher, Hadronox, Anub'arak Ahn'kahet: The Old Kingdom - OK @@ -76,7 +75,7 @@ class DungeonStrategyContext : public NamedObjectContext } private: static Strategy* wotlk_uk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } - static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } + static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonNexStrategy(botAI); } static Strategy* wotlk_an(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h index 419229dd..d9a18ae8 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H #include "utgardekeep/UtgardeKeepActionContext.h" -// #include "nexus/NexusActionContext.h" +#include "nexus/NexusActionContext.h" // #include "azjolnerub/AzjolNerubActionContext.h" // #include "oldkingdom/OldKingdomActionContext.h" // #include "draktharonkeep/DraktharonKeepActionContext.h" diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h index 28a42d87..4cbb41e2 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h @@ -2,7 +2,7 @@ #define _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H #include "utgardekeep/UtgardeKeepTriggerContext.h" -// #include "nexus/NexusTriggerContext.h" +#include "nexus/NexusTriggerContext.h" // #include "azjolnerub/AzjolNerubTriggerContext.h" // #include "oldkingdom/OldKingdomTriggerContext.h" // #include "draktharonkeep/DraktharonKeepTriggerContext.h" diff --git a/src/strategy/dungeons/wotlk/nexus/NexusActionContext.h b/src/strategy/dungeons/wotlk/nexus/NexusActionContext.h new file mode 100644 index 00000000..61dd91d8 --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusActionContext.h @@ -0,0 +1,30 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONNEXACTIONCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONNEXACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "NexusActions.h" + +class WotlkDungeonNexActionContext : public NamedObjectContext +{ + public: + WotlkDungeonNexActionContext() { + creators["move from whirlwind"] = &WotlkDungeonNexActionContext::move_from_whirlwind; + creators["firebomb spread"] = &WotlkDungeonNexActionContext::firebomb_spread; + creators["telestra split target"] = &WotlkDungeonNexActionContext::telestra_split_target; + creators["chaotic rift target"] = &WotlkDungeonNexActionContext::chaotic_rift_target; + creators["dodge spikes"] = &WotlkDungeonNexActionContext::dodge_spikes; + creators["intense cold jump"] = &WotlkDungeonNexActionContext::intense_cold_jump; + creators["rear flank position"] = &WotlkDungeonNexActionContext::rear_flank_position; + } + private: + static Action* move_from_whirlwind(PlayerbotAI* ai) { return new MoveFromWhirlwindAction(ai); } + static Action* firebomb_spread(PlayerbotAI* ai) { return new FirebombSpreadAction(ai); } + static Action* telestra_split_target(PlayerbotAI* ai) { return new TelestraSplitTargetAction(ai); } + static Action* chaotic_rift_target(PlayerbotAI* ai) { return new ChaoticRiftTargetAction(ai); } + static Action* dodge_spikes(PlayerbotAI* ai) { return new DodgeSpikesAction(ai); } + static Action* intense_cold_jump(PlayerbotAI* ai) { return new IntenseColdJumpAction(ai); } + static Action* rear_flank_position(PlayerbotAI* ai) { return new RearFlankPositionAction(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/nexus/NexusActions.cpp b/src/strategy/dungeons/wotlk/nexus/NexusActions.cpp new file mode 100644 index 00000000..543fadba --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusActions.cpp @@ -0,0 +1,206 @@ +#include "Playerbots.h" +#include "NexusActions.h" +#include "NexusStrategy.h" + +bool MoveFromWhirlwindAction::Execute(Event event) +{ + Unit* boss = nullptr; + uint8 faction = bot->GetTeamId(); + float targetDist = 10.0f; // Whirlwind has range of 8, add a couple for safety buffer + + switch (bot->GetMap()->GetDifficulty()) + { + case DUNGEON_DIFFICULTY_NORMAL: + if (faction == TEAM_ALLIANCE) + { + boss = AI_VALUE2(Unit*, "find target", "horde commander"); + } + else //if (faction == TEAM_HORDE) + { + boss = AI_VALUE2(Unit*, "find target", "alliance commander"); + } + break; + case DUNGEON_DIFFICULTY_HEROIC: + if (faction == TEAM_ALLIANCE) + { + boss = AI_VALUE2(Unit*, "find target", "commander kolurg"); + } + else //if (faction == TEAM_HORDE) + { + boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard"); + } + break; + default: + break; + } + if (!boss || bot->GetExactDist2d(boss->GetPosition()) > targetDist) + { + return false; + } + return MoveAway(boss, targetDist - bot->GetExactDist2d(boss->GetPosition())); +} + +bool FirebombSpreadAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra"); + float radius = 5.0f; + float targetDist = radius + 1.0f; + if (!boss) { return false; } + + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto& member : members) + { + if (bot->GetGUID() == member) + { + continue; + } + if (bot->GetExactDist2d(botAI->GetUnit(member)) < targetDist) + { + return MoveAway(botAI->GetUnit(member), targetDist); + } + } + return false; +} + +bool TelestraSplitTargetAction::isUseful() { return !botAI->IsHeal(bot); } +bool TelestraSplitTargetAction::Execute(Event event) +{ + GuidVector attackers = AI_VALUE(GuidVector, "attackers"); + Unit* splitTargets[3] = {nullptr, nullptr, nullptr}; + + for (auto& attacker : attackers) + { + Unit* npc = botAI->GetUnit(attacker); + if (!npc) + { + continue; + } + switch (npc->GetEntry()) + { + // Focus arcane clone first + case NPC_ARCANE_MAGUS: + splitTargets[0] = npc; + break; + // Then the frost clone + case NPC_FROST_MAGUS: + splitTargets[1] = npc; + break; + // Fire clone last + case NPC_FIRE_MAGUS: + splitTargets[2] = npc; + break; + } + } + + for (Unit* target : splitTargets) + { + // Attack the first valid split target in the priority list + if (target) + { + if (AI_VALUE(Unit*, "current target") != target) + { + return Attack(target); + } + // Don't continue loop here, the target exists so we don't + // want to move down the prio list. We just don't need to send attack + // command again, just return false and exit the loop that way + return false; + } + } + + return false; +} + +bool ChaoticRiftTargetAction::isUseful() { return !botAI->IsHeal(bot); } +bool ChaoticRiftTargetAction::Execute(Event event) +{ + Unit* chaoticRift = nullptr; + + // Target is not findable from threat table using AI_VALUE2(), + // therefore need to search manually for the unit name + GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + + for (auto i = targets.begin(); i != targets.end(); ++i) + { + Unit* unit = botAI->GetUnit(*i); + if (unit && unit->GetName() == "Chaotic Rift") + { + chaoticRift = unit; + break; + } + } + if (!chaoticRift || AI_VALUE(Unit*, "current target") == chaoticRift) + { + return false; + } + return Attack(chaoticRift); +} + +bool DodgeSpikesAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper"); + return bot->GetExactDist2d(boss) > 0.5f; +} +bool DodgeSpikesAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper"); + return Move(bot->GetAngle(boss), bot->GetExactDist2d(boss) - 0.3f); +} + +bool IntenseColdJumpAction::Execute(Event event) +{ + // This needs improving but maybe it should be done in the playerbot core. + // Jump doesn't seem to support zero offset (eg. jump on the spot) so need to add a tiny delta. + // This does a tiny bunnyhop that takes a couple of ms, it doesn't do a natural jump. + // Adding extra Z offset causes floating, and appears to scale the jump speed based on Z difference. + // Probably best to revisit once bot movement is improved + return JumpTo(bot->GetMap()->GetId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ() + 0.01f); + // bot->GetMotionMaster()->MoveFall(); +} + +bool RearFlankPositionAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza"); + if (!boss) { return false; } + + // Need to double the front angle check to account for mirrored angle. + // Total 180 degrees (whole front half) + bool inFront = boss->HasInArc(2.f * DRAGON_MELEE_MIN_ANGLE, bot); + // Rear check does not need to double this angle as the logic is inverted + // and we are subtracing from 2pi. + bool inBack = !boss->HasInArc((2.f * M_PI) - DRAGON_MELEE_MAX_ANGLE, bot); + + return inFront || inBack; +} +bool RearFlankPositionAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza"); + if (!boss) { return false; } + + // float angleToMove = minAngle + rand_norm() * (maxAngle - minAngle); + float angle = frand(DRAGON_MELEE_MIN_ANGLE, DRAGON_MELEE_MAX_ANGLE); + // Need to reduce this value very slightly, or the bots get the jitters - + // may be due to rounding errors. Need to bring them just inside their attack range. + // This boss has a big hitbox so we can reduce by 50% and it's still fine and looks better. + float distance = bot->GetMeleeRange(boss) * 0.5f; + // Alternatively, summing both unit's melee ranges seems to give a fairly natural range. + // Use whichever gives the best results.. + // float distanceOffset = bot->GetMeleeReach() + boss->GetMeleeReach(); + + Position leftFlank = boss->GetPosition(); + Position rightFlank = boss->GetPosition(); + Position* destination = nullptr; + leftFlank.RelocatePolarOffset(angle, distance); + rightFlank.RelocatePolarOffset(-angle, distance); + + if (bot->GetExactDist2d(leftFlank) < bot->GetExactDist2d(rightFlank)) + { + destination = &leftFlank; + } + else + { + destination = &rightFlank; + } + + return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(), destination->GetPositionZ()); +} diff --git a/src/strategy/dungeons/wotlk/nexus/NexusActions.h b/src/strategy/dungeons/wotlk/nexus/NexusActions.h new file mode 100644 index 00000000..a7d31677 --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusActions.h @@ -0,0 +1,79 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONNEXACTIONS_H +#define _PLAYERBOT_WOTLKDUNGEONNEXACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "NexusTriggers.h" + +#define ANGLE_45_DEG (static_cast(M_PI) / 4.f) +#define ANGLE_90_DEG M_PI_2 +#define ANGLE_120_DEG (2.f * static_cast(M_PI) / 3.f) + +// Slice of the circle that we want melee dps to attack from. +// Measured from boss orientation, on one side. + +// Even though the breath cone is not the full 180 degrees, +// avoid melee dps from the front due to parry potential. +// Bots should learn good dps etiquette :) +#define DRAGON_MELEE_MIN_ANGLE ANGLE_90_DEG +// This leaves a danger zone of 60 degrees at the tail end on both sides. +// This is a total of 120 degrees tail arc that bots will avoid - +// number just happens to be the same in this case, but this is always measured from the front. +#define DRAGON_MELEE_MAX_ANGLE ANGLE_120_DEG + +class MoveFromWhirlwindAction : public MovementAction +{ +public: + MoveFromWhirlwindAction(PlayerbotAI* ai) : MovementAction(ai, "move from whirlwind") {} + bool Execute(Event event) override; +}; + +class FirebombSpreadAction : public MovementAction +{ +public: + FirebombSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "firebomb spread") {} + bool Execute(Event event) override; +}; + +class TelestraSplitTargetAction : public AttackAction +{ +public: + TelestraSplitTargetAction(PlayerbotAI* ai) : AttackAction(ai, "telestra split target") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class ChaoticRiftTargetAction : public AttackAction +{ +public: + ChaoticRiftTargetAction(PlayerbotAI* ai) : AttackAction(ai, "chaotic rift target") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class DodgeSpikesAction : public MovementAction +{ +public: + DodgeSpikesAction(PlayerbotAI* ai) : MovementAction(ai, "dodge spikes") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class IntenseColdJumpAction : public MovementAction +{ +public: + IntenseColdJumpAction(PlayerbotAI* ai) : MovementAction(ai, "intense cold jump") {} + bool Execute(Event event) override; +}; + +class RearFlankPositionAction : public MovementAction +{ +public: + RearFlankPositionAction(PlayerbotAI* ai) : MovementAction(ai, "rear flank position") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/nexus/NexusMultipliers.cpp b/src/strategy/dungeons/wotlk/nexus/NexusMultipliers.cpp new file mode 100644 index 00000000..4b8ab97a --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusMultipliers.cpp @@ -0,0 +1,99 @@ +#include "NexusMultipliers.h" +#include "NexusActions.h" +#include "GenericSpellActions.h" +#include "ChooseTargetActions.h" +#include "MovementActions.h" +#include "NexusTriggers.h" + +float FactionCommanderMultiplier::GetValue(Action* action) +{ + Unit* boss = nullptr; + uint8 faction = bot->GetTeamId(); + + switch (bot->GetMap()->GetDifficulty()) + { + case DUNGEON_DIFFICULTY_NORMAL: + if (faction == TEAM_ALLIANCE) + { + boss = AI_VALUE2(Unit*, "find target", "horde commander"); + } + else //if (faction == TEAM_HORDE) + { + boss = AI_VALUE2(Unit*, "find target", "alliance commander"); + } + break; + case DUNGEON_DIFFICULTY_HEROIC: + if (faction == TEAM_ALLIANCE) + { + boss = AI_VALUE2(Unit*, "find target", "commander kolurg"); + } + else //if (faction == TEAM_HORDE) + { + boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard"); + } + break; + default: + break; + } + if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && + boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND)) + { + // Prevent movement actions other than flee during a whirlwind, to prevent running back in early. + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + } + return 1.0f; +} + +float TelestraMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra"); + if (boss && boss->GetEntry() != NPC_TELESTRA) + { + // boss is split into clones, do not auto acquire target + if (dynamic_cast(action)) + { + return 0.0f; + } + } + return 1.0f; +} + +float AnomalusMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "anomalus"); + if (boss && boss->HasAura(BUFF_RIFT_SHIELD)) + { + if (dynamic_cast(action)) + { + return 0.0f; + } + } + return 1.0f; +} + +float OrmorokMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper"); + if (!boss) + { + return 1.0f; + } + // These are used for auto ranged repositioning, need to suppress so ranged dps don't ping-pong + if (dynamic_cast(action)) + { + return 0.0f; + } + // This boss is annoying and shuffles around a lot. Don't let tank move once fight has started. + // Extra checks are to allow the tank to close distance and engage the boss initially + if (dynamic_cast(action) && !dynamic_cast(action) + && botAI->IsTank(bot) && bot->IsWithinMeleeRange(boss) + && AI_VALUE2(bool, "facing", "current target")) + { + return 0.0f; + } + return 1.0f; +} + diff --git a/src/strategy/dungeons/wotlk/nexus/NexusMultipliers.h b/src/strategy/dungeons/wotlk/nexus/NexusMultipliers.h new file mode 100644 index 00000000..c7321a40 --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusMultipliers.h @@ -0,0 +1,51 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONNEXMULTIPLIERS_H +#define _PLAYERBOT_WOTLKDUNGEONNEXMULTIPLIERS_H + +#include "Multiplier.h" + +class FactionCommanderMultiplier : public Multiplier +{ + public: + FactionCommanderMultiplier(PlayerbotAI* ai) : Multiplier(ai, "faction commander") {} + + public: + virtual float GetValue(Action* action); +}; + +class TelestraMultiplier : public Multiplier +{ + public: + TelestraMultiplier(PlayerbotAI* ai) : Multiplier(ai, "grand magus telestra") {} + + public: + virtual float GetValue(Action* action); +}; + +class AnomalusMultiplier : public Multiplier +{ + public: + AnomalusMultiplier(PlayerbotAI* ai) : Multiplier(ai, "anomalus") {} + + public: + virtual float GetValue(Action* action); +}; + +class OrmorokMultiplier : public Multiplier +{ + public: + OrmorokMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ormorok the tree-shaper") {} + + public: + virtual float GetValue(Action* action); +}; + +class KeristraszaMultiplier : public Multiplier +{ + public: + KeristraszaMultiplier(PlayerbotAI* ai) : Multiplier(ai, "keristrasza") {} + + public: + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp b/src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp new file mode 100644 index 00000000..44140b6e --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp @@ -0,0 +1,54 @@ +#include "NexusStrategy.h" +#include "NexusMultipliers.h" + + +void WotlkDungeonNexStrategy::InitTriggers(std::vector &triggers) +{ + // Horde Commander (Alliance N)/Commander Kolurg (Alliance H) + // or + // Alliance Commander (Horde N)/Commander Stoutbeard (Horde H) + triggers.push_back(new TriggerNode("faction commander whirlwind", + NextAction::array(0, new NextAction("move from whirlwind", ACTION_MOVE + 5), nullptr))); + // TODO: Handle fear? (tremor totems, fear ward etc.) + + // Grand Magus Telestra + triggers.push_back(new TriggerNode("telestra firebomb", + NextAction::array(0, new NextAction("firebomb spread", ACTION_MOVE + 5), nullptr))); + triggers.push_back(new TriggerNode("telestra split phase", + NextAction::array(0, new NextAction("telestra split target", ACTION_RAID + 1), nullptr))); + // TODO: Add priority interrupt on the frost split's Blizzard casts + + // Anomalus + triggers.push_back(new TriggerNode("chaotic rift", + NextAction::array(0, new NextAction("chaotic rift target", ACTION_RAID + 1), nullptr))); + + // Ormorok the Tree-Shaper + // Tank trigger to stack inside boss. Can also add return action to prevent boss repositioning + // if it becomes too much of a problem. He usually dies before he's up against a wall though + triggers.push_back(new TriggerNode("ormorok spikes", + NextAction::array(0, new NextAction("dodge spikes", ACTION_MOVE + 5), nullptr))); + // Non-tank trigger to stack. Avoiding the spikes at range is.. harder than it seems. + // TODO: This turns hunters into melee marshmallows, have not come up with a better solution yet + triggers.push_back(new TriggerNode("ormorok stack", + NextAction::array(0, new NextAction("dodge spikes", ACTION_MOVE + 5), nullptr))); + // TODO: Add handling for spell reflect... best to spam low level/weak spells but don't want + // to hardcode spells per class, might be difficult to dynamically generate this. + // Will revisit if I find my altbots killing themselves in heroic, just heal through it for now + + // Keristrasza + triggers.push_back(new TriggerNode("intense cold", + NextAction::array(0, new NextAction("intense cold jump", ACTION_MOVE + 5), nullptr))); + // Flank dragon positioning for non-tank melee + triggers.push_back(new TriggerNode("dragon positioning", + NextAction::array(0, new NextAction("rear flank position", ACTION_MOVE + 4), nullptr))); + // TODO: Add frost resist aura for paladins? +} + +void WotlkDungeonNexStrategy::InitMultipliers(std::vector &multipliers) +{ + multipliers.push_back(new FactionCommanderMultiplier(botAI)); + multipliers.push_back(new TelestraMultiplier(botAI)); + multipliers.push_back(new AnomalusMultiplier(botAI)); + multipliers.push_back(new OrmorokMultiplier(botAI)); + // multipliers.push_back(new KeristraszaMultiplier(botAI)); +} diff --git a/src/strategy/dungeons/wotlk/nexus/NexusStrategy.h b/src/strategy/dungeons/wotlk/nexus/NexusStrategy.h new file mode 100644 index 00000000..f9ecf426 --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusStrategy.h @@ -0,0 +1,18 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONNEXSTRATEGY_H +#define _PLAYERBOT_WOTLKDUNGEONNEXSTRATEGY_H + +#include "Multiplier.h" +#include "AiObjectContext.h" +#include "Strategy.h" + + +class WotlkDungeonNexStrategy : public Strategy +{ +public: + WotlkDungeonNexStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "nexus"; } + virtual void InitTriggers(std::vector &triggers) override; + virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h b/src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h new file mode 100644 index 00000000..72815f91 --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h @@ -0,0 +1,33 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERCONTEXT_H + +#include "NamedObjectContext.h" +#include "AiObjectContext.h" +#include "NexusTriggers.h" + +class WotlkDungeonNexTriggerContext : public NamedObjectContext +{ + public: + WotlkDungeonNexTriggerContext() + { + creators["faction commander whirlwind"] = &WotlkDungeonNexTriggerContext::faction_commander_whirlwind; + creators["telestra firebomb"] = &WotlkDungeonNexTriggerContext::telestra_firebomb; + creators["telestra split phase"] = &WotlkDungeonNexTriggerContext::telestra_split_phase; + creators["chaotic rift"] = &WotlkDungeonNexTriggerContext::chaotic_rift; + creators["ormorok spikes"] = &WotlkDungeonNexTriggerContext::ormorok_spikes; + creators["ormorok stack"] = &WotlkDungeonNexTriggerContext::ormorok_stack; + creators["intense cold"] = &WotlkDungeonNexTriggerContext::intense_cold; + creators["dragon positioning"] = &WotlkDungeonNexTriggerContext::dragon_positioning; + } + private: + static Trigger* faction_commander_whirlwind(PlayerbotAI* ai) { return new FactionCommanderWhirlwindTrigger(ai); } + static Trigger* telestra_firebomb(PlayerbotAI* ai) { return new TelestraFirebombTrigger(ai); } + static Trigger* telestra_split_phase(PlayerbotAI* ai) { return new TelestraSplitPhaseTrigger(ai); } + static Trigger* chaotic_rift(PlayerbotAI* ai) { return new ChaoticRiftTrigger(ai); } + static Trigger* ormorok_spikes(PlayerbotAI* ai) { return new OrmorokSpikesTrigger(ai); } + static Trigger* ormorok_stack(PlayerbotAI* ai) { return new OrmorokStackTrigger(ai); } + static Trigger* intense_cold(PlayerbotAI* ai) { return new IntenseColdTrigger(ai); } + static Trigger* dragon_positioning(PlayerbotAI* ai) { return new DragonPositioningTrigger(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp b/src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp new file mode 100644 index 00000000..22f1b29d --- /dev/null +++ b/src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp @@ -0,0 +1,105 @@ +#include "Playerbots.h" +#include "NexusTriggers.h" +#include "AiObject.h" +#include "AiObjectContext.h" + +bool FactionCommanderWhirlwindTrigger::IsActive() +{ + Unit* boss = nullptr; + uint8 faction = bot->GetTeamId(); + + switch (bot->GetMap()->GetDifficulty()) + { + case DUNGEON_DIFFICULTY_NORMAL: + if (faction == TEAM_ALLIANCE) + { + boss = AI_VALUE2(Unit*, "find target", "horde commander"); + } + else //if (faction == TEAM_HORDE) + { + boss = AI_VALUE2(Unit*, "find target", "alliance commander"); + } + break; + case DUNGEON_DIFFICULTY_HEROIC: + if (faction == TEAM_ALLIANCE) + { + boss = AI_VALUE2(Unit*, "find target", "commander kolurg"); + } + else //if (faction == TEAM_HORDE) + { + boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard"); + } + break; + default: + break; + } + + if (boss && boss->HasUnitState(UNIT_STATE_CASTING)) + { + if (boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND)) + { + return true; + } + } + return false; +} + +bool TelestraFirebombTrigger::IsActive() +{ + if (botAI->IsMelee(bot)) { return false; } + + Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra"); + // Avoid split phase with the fake Telestra units, only match the true boss id + return boss && boss->GetEntry() == NPC_TELESTRA; +} + +bool TelestraSplitPhaseTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra"); + // Only match split phase with the fake Telestra units + return boss && boss->GetEntry() != NPC_TELESTRA; +} + +bool ChaoticRiftTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "anomalus"); + return boss && boss->HasAura(BUFF_RIFT_SHIELD); +} + +bool OrmorokSpikesTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper"); + if (!boss || !botAI->IsTank(bot)) { return false; } + + GuidVector objects = AI_VALUE(GuidVector, "closest game objects"); + for (auto i = objects.begin(); i != objects.end(); ++i) + { + GameObject* go = botAI->GetGameObject(*i); + if (go && go->GetEntry() == GO_CRYSTAL_SPIKE) + { + return true; + } + } + return false; +} + +bool OrmorokStackTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper"); + return (boss && !botAI->IsTank(bot)); +} + +bool IntenseColdTrigger::IsActive() +{ + // Adjust as needed - too much interrupting loses dps time, + // but too many stacks is deadly. Assuming 3-5 is a good number to clear + int stackThreshold = 5; + Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza"); + return boss && botAI->GetAura("intense cold", bot, false, false, stackThreshold); +} + +bool DragonPositioningTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza"); + return boss && botAI->IsMelee(bot) && !botAI->IsTank(bot); +} diff --git a/src/strategy/dungeons/wotlk/nexus/NexusTriggers.h b/src/strategy/dungeons/wotlk/nexus/NexusTriggers.h index 894b86f7..c9f14038 100644 --- a/src/strategy/dungeons/wotlk/nexus/NexusTriggers.h +++ b/src/strategy/dungeons/wotlk/nexus/NexusTriggers.h @@ -1,93 +1,88 @@ #ifndef _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERS_H #define _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERS_H -#include "EventMap.h" #include "Trigger.h" #include "PlayerbotAIConfig.h" #include "GenericTriggers.h" #include "DungeonStrategyUtils.h" -// Taken from: -// src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp -enum Spells +enum NexusIDs { - SPELL_SUMMON_VALKYR = 42912, - SPELL_RESURRECTION_BEAM = 42857, - SPELL_RESURRECTION_BALL = 42862, - SPELL_RESURRECTION_HEAL = 42704, - SPELL_INGVAR_TRANSFORM = 42796, + // Faction Commander + NPC_ALLIANCE_COMMANDER = 27949, + NPC_HORDE_COMMANDER = 27947, + NPC_COMMANDER_STOUTBEARD = 26796, + NPC_COMMANDER_KOLURG = 26798, + // SPELL_FRIGHTENING_SHOUT = 19134, + SPELL_WHIRLWIND = 38618, - SPELL_STAGGERING_ROAR_N = 42708, - SPELL_STAGGERING_ROAR_H = 59708, - SPELL_CLEAVE = 42724, - SPELL_SMASH_N = 42669, - SPELL_SMASH_H = 59706, - SPELL_ENRAGE_N = 42705, - SPELL_ENRAGE_H = 59707, + // Grand Magus Telestra + NPC_TELESTRA = 26731, + NPC_FIRE_MAGUS = 26928, + NPC_FROST_MAGUS = 26930, + NPC_ARCANE_MAGUS = 26929, - SPELL_DREADFUL_ROAR_N = 42729, - SPELL_DREADFUL_ROAR_H = 59734, - SPELL_WOE_STRIKE_N = 42730, - SPELL_WOE_STRIKE_H = 59735, - SPELL_DARK_SMASH = 42723, - SPELL_SHADOW_AXE = 42749, + // Anomalus + BUFF_RIFT_SHIELD = 47748, - // Added - DEBUFF_FROST_TOMB = 48400, + // Ormorok the Tree Shaper + // NPC_CRYSTAL_SPIKE = 27099, + GO_CRYSTAL_SPIKE = 188537, }; -#define SPELL_STAGGERING_ROAR DUNGEON_MODE(bot, SPELL_STAGGERING_ROAR_N, SPELL_STAGGERING_ROAR_H) -#define SPELL_DREADFUL_ROAR DUNGEON_MODE(bot, SPELL_DREADFUL_ROAR_N, SPELL_DREADFUL_ROAR_H) -#define SPELL_WOE_STRIKE DUNGEON_MODE(bot, SPELL_WOE_STRIKE_N, SPELL_WOE_STRIKE_H) -#define SPELL_SMASH DUNGEON_MODE(bot, SPELL_SMASH_N, SPELL_SMASH_H) -#define SPELL_ENRAGE DUNGEON_MODE(bot, SPELL_ENRAGE_N, SPELL_ENRAGE_H) - -class KelesethFrostTombTrigger : public Trigger +class FactionCommanderWhirlwindTrigger : public Trigger { public: - KelesethFrostTombTrigger(PlayerbotAI* ai) : Trigger(ai, "keleseth frost tomb") {} + FactionCommanderWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "faction commander whirlwind") {} bool IsActive() override; }; -class DalronnNontankTrigger : public Trigger +class TelestraFirebombTrigger : public Trigger { public: - DalronnNontankTrigger(PlayerbotAI* ai) : Trigger(ai, "dalronn non-tank") {} + TelestraFirebombTrigger(PlayerbotAI* ai) : Trigger(ai, "telestra firebomb spread") {} bool IsActive() override; }; -class IngvarStaggeringRoarTrigger : public Trigger +class TelestraSplitPhaseTrigger : public Trigger { public: - IngvarStaggeringRoarTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar staggering roar") {} + TelestraSplitPhaseTrigger(PlayerbotAI* ai) : Trigger(ai, "telestra split phase") {} bool IsActive() override; }; -class IngvarDreadfulRoarTrigger : public Trigger +class ChaoticRiftTrigger : public Trigger { public: - IngvarDreadfulRoarTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar dreadful roar") {} + ChaoticRiftTrigger(PlayerbotAI* ai) : Trigger(ai, "chaotic rift") {} bool IsActive() override; }; -class IngvarSmashTankTrigger : public Trigger +class OrmorokSpikesTrigger : public Trigger { public: - IngvarSmashTankTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar smash tank") {} + OrmorokSpikesTrigger(PlayerbotAI* ai) : Trigger(ai, "ormorok spikes") {} bool IsActive() override; }; -class IngvarSmashTankReturnTrigger : public Trigger +class OrmorokStackTrigger : public Trigger { public: - IngvarSmashTankReturnTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar smash tank return") {} + OrmorokStackTrigger(PlayerbotAI* ai) : Trigger(ai, "ormorok stack") {} bool IsActive() override; }; -class NotBehindIngvarTrigger : public Trigger +class IntenseColdTrigger : public Trigger { public: - NotBehindIngvarTrigger(PlayerbotAI* ai) : Trigger(ai, "not behind ingvar") {} + IntenseColdTrigger(PlayerbotAI* ai) : Trigger(ai, "intense cold") {} + bool IsActive() override; +}; + +class DragonPositioningTrigger : public Trigger +{ +public: + DragonPositioningTrigger(PlayerbotAI* ai) : Trigger(ai, "dragon positioning") {} bool IsActive() override; };