diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index 2c81cefe..d8b603ff 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -55,6 +55,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new WotlkDungeonANActionContext()); actionContexts.Add(new WotlkDungeonOKActionContext()); actionContexts.Add(new WotlkDungeonDTKActionContext()); + actionContexts.Add(new WotlkDungeonVHActionContext()); triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new ChatTriggerContext()); @@ -70,6 +71,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new WotlkDungeonANTriggerContext()); triggerContexts.Add(new WotlkDungeonOKTriggerContext()); triggerContexts.Add(new WotlkDungeonDTKTriggerContext()); + triggerContexts.Add(new WotlkDungeonVHTriggerContext()); valueContexts.Add(new ValueContext()); diff --git a/src/strategy/actions/ActionContext.h b/src/strategy/actions/ActionContext.h index e195e2f9..c13f7162 100644 --- a/src/strategy/actions/ActionContext.h +++ b/src/strategy/actions/ActionContext.h @@ -94,6 +94,7 @@ public: creators["avoid aoe"] = &ActionContext::avoid_aoe; creators["combat formation move"] = &ActionContext::combat_formation_move; creators["tank face"] = &ActionContext::tank_face; + creators["rear flank"] = &ActionContext::rear_flank; creators["disperse set"] = &ActionContext::disperse_set; creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru; creators["shoot"] = &ActionContext::shoot; @@ -278,6 +279,7 @@ private: static Action* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeAction(botAI); } static Action* combat_formation_move(PlayerbotAI* botAI) { return new CombatFormationMoveAction(botAI); } static Action* tank_face(PlayerbotAI* botAI) { return new TankFaceAction(botAI); } + static Action* rear_flank(PlayerbotAI* botAI) { return new RearFlankAction(botAI); } static Action* disperse_set(PlayerbotAI* botAI) { return new DisperseSetAction(botAI); } static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); } static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); } diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index 569d7fcc..791f0990 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -2372,6 +2372,46 @@ bool TankFaceAction::Execute(Event event) return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } +bool RearFlankAction::isUseful() +{ + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target) { return false; } + + // Need to double the front angle check to account for mirrored angle. + bool inFront = target->HasInArc(2.f * minAngle, bot); + // Rear check does not need to double this angle as the logic is inverted + // and we are subtracting from 2pi. + bool inRear = !target->HasInArc((2.f * M_PI) - maxAngle, bot); + + return inFront || inRear; +} + +bool RearFlankAction::Execute(Event event) +{ + Unit* target = AI_VALUE(Unit*, "current target"); + if (!target) { return false; } + + float angle = frand(minAngle, maxAngle); + float baseDistance = bot->GetMeleeRange(target) * 0.5f; + Position leftFlank = target->GetPosition(); + Position rightFlank = target->GetPosition(); + Position* destination = nullptr; + leftFlank.RelocatePolarOffset(angle, baseDistance + distance); + rightFlank.RelocatePolarOffset(-angle, baseDistance + distance); + + if (bot->GetExactDist2d(leftFlank) < bot->GetExactDist2d(rightFlank)) + { + destination = &leftFlank; + } + else + { + destination = &rightFlank; + } + + return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(), destination->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT); +} + bool DisperseSetAction::Execute(Event event) { std::string const text = event.getParam(); diff --git a/src/strategy/actions/MovementActions.h b/src/strategy/actions/MovementActions.h index 7b03f034..76929427 100644 --- a/src/strategy/actions/MovementActions.h +++ b/src/strategy/actions/MovementActions.h @@ -18,6 +18,9 @@ class Unit; class WorldObject; class Position; +#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) class MovementAction : public Action { @@ -144,6 +147,27 @@ public: bool Execute(Event event) override; }; +class RearFlankAction : public MovementAction +{ +// 90 degree minimum angle prevents any frontal cleaves/breaths and avoids parry-hasting the boss. +// 120 degree maximum angle leaves a 120 degree symmetrical cone at the tail end which is usually enough to avoid tail swipes. +// Some dragons or mobs may have different danger zone angles, override if needed. +public: + RearFlankAction(PlayerbotAI* botAI, float distance = 0.0f, float minAngle = ANGLE_90_DEG, float maxAngle = ANGLE_120_DEG) + : MovementAction(botAI, "rear flank") + { + this->distance = distance; + this->minAngle = minAngle; + this->maxAngle = maxAngle; + } + + bool Execute(Event event) override; + bool isUseful() override; + +protected: + float distance, minAngle, maxAngle; +}; + class DisperseSetAction : public Action { public: @@ -268,4 +292,5 @@ public: bool Execute(Event event) override; }; + #endif diff --git a/src/strategy/dungeons/DungeonStrategyContext.h b/src/strategy/dungeons/DungeonStrategyContext.h index faae1372..8062e09b 100644 --- a/src/strategy/dungeons/DungeonStrategyContext.h +++ b/src/strategy/dungeons/DungeonStrategyContext.h @@ -7,12 +7,11 @@ #include "wotlk/azjolnerub/AzjolNerubStrategy.h" #include "wotlk/oldkingdom/OldKingdomStrategy.h" #include "wotlk/draktharonkeep/DrakTharonKeepStrategy.h" +#include "wotlk/violethold/VioletHoldStrategy.h" /* Full list/TODO: -The Violet Hold - VH -Erekem, Moragg, Ichoron, Xevozz, Lavanthor, Zuramat the Obliterator, Cyanigosa Gundrak - GD Slad'ran, Drakkari Colossus, Moorabi, Gal'darah, Eck the Ferocious (Heroic only) Halls of Stone - HoS @@ -76,8 +75,8 @@ class DungeonStrategyContext : public NamedObjectContext static Strategy* wotlk_an(PlayerbotAI* botAI) { return new WotlkDungeonANStrategy(botAI); } static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonOKStrategy(botAI); } static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonDTKStrategy(botAI); } - - static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } + static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonVHStrategy(botAI); } + static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h index eff45c50..060a4e5c 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h @@ -6,7 +6,7 @@ #include "azjolnerub/AzjolNerubActionContext.h" #include "oldkingdom/OldKingdomActionContext.h" #include "draktharonkeep/DrakTharonKeepActionContext.h" -// #include "violethold/VioletHoldActionContext.h" +#include "violethold/VioletHoldActionContext.h" // #include "gundrak/GundrakActionContext.h" // #include "hallsofstone/HallsOfStoneActionContext.h" // #include "hallsoflightning/HallsOfLightningActionContext.h" diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h index 15f98758..11097b06 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h @@ -6,7 +6,7 @@ #include "azjolnerub/AzjolNerubTriggerContext.h" #include "oldkingdom/OldKingdomTriggerContext.h" #include "draktharonkeep/DrakTharonKeepTriggerContext.h" -// #include "violethold/VioletHoldTriggerContext.h" +#include "violethold/VioletHoldTriggerContext.h" // #include "gundrak/GundrakTriggerContext.h" // #include "hallsofstone/HallsOfStoneTriggerContext.h" // #include "hallsoflightning/HallsOfLightningTriggerContext.h" diff --git a/src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp b/src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp index e8cece04..6c287591 100644 --- a/src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp +++ b/src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubMultipliers.cpp @@ -39,7 +39,9 @@ float KrikthirMultiplier::GetValue(Action* action) { // Do not target swap // TODO: Need to suppress AoE actions but unsure how to identify them - if (dynamic_cast(action)) + // TODO: TEST AOE Avoid + if (dynamic_cast(action) + || dynamic_cast(action)) { return 0.0f; } diff --git a/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp b/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp index 7d71d26a..e8863a12 100644 --- a/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp +++ b/src/strategy/dungeons/wotlk/draktharonkeep/DrakTharonKeepActions.cpp @@ -66,6 +66,8 @@ bool NovosTargetPriorityAction::Execute(Event event) // Designate a dps char to handle the stairs adds. // This is probably better as a melee, so just pick the first // melee dps in the party. If none exist, pick the first ranged. + + // TODO: Switch to botAI->Index instead, cleaner Player* stairsDps = nullptr; GuidVector members = AI_VALUE(GuidVector, "group members"); for (auto& member : members) diff --git a/src/strategy/dungeons/wotlk/nexus/NexusActionContext.h b/src/strategy/dungeons/wotlk/nexus/NexusActionContext.h index 61dd91d8..c0242262 100644 --- a/src/strategy/dungeons/wotlk/nexus/NexusActionContext.h +++ b/src/strategy/dungeons/wotlk/nexus/NexusActionContext.h @@ -15,7 +15,6 @@ class WotlkDungeonNexActionContext : public NamedObjectContext 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); } @@ -24,7 +23,6 @@ class WotlkDungeonNexActionContext : public NamedObjectContext 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 index cbeaa6f4..1a918614 100644 --- a/src/strategy/dungeons/wotlk/nexus/NexusActions.cpp +++ b/src/strategy/dungeons/wotlk/nexus/NexusActions.cpp @@ -157,50 +157,3 @@ bool IntenseColdJumpAction::Execute(Event event) 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 subtracting 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. - // TODO: Investigate using bot->GetObjectSize() for sizing - 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 index a7d31677..145efba7 100644 --- a/src/strategy/dungeons/wotlk/nexus/NexusActions.h +++ b/src/strategy/dungeons/wotlk/nexus/NexusActions.h @@ -68,12 +68,4 @@ public: 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/NexusStrategy.cpp b/src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp index 44140b6e..b86c1aa4 100644 --- a/src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp +++ b/src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp @@ -38,9 +38,9 @@ void WotlkDungeonNexStrategy::InitTriggers(std::vector &triggers) // 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))); + // Flank dragon positioning + triggers.push_back(new TriggerNode("keristrasza positioning", + NextAction::array(0, new NextAction("rear flank", ACTION_MOVE + 4), nullptr))); // TODO: Add frost resist aura for paladins? } diff --git a/src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h b/src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h index 72815f91..ca696ece 100644 --- a/src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h +++ b/src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h @@ -17,7 +17,7 @@ class WotlkDungeonNexTriggerContext : public NamedObjectContext creators["ormorok spikes"] = &WotlkDungeonNexTriggerContext::ormorok_spikes; creators["ormorok stack"] = &WotlkDungeonNexTriggerContext::ormorok_stack; creators["intense cold"] = &WotlkDungeonNexTriggerContext::intense_cold; - creators["dragon positioning"] = &WotlkDungeonNexTriggerContext::dragon_positioning; + creators["keristrasza positioning"] = &WotlkDungeonNexTriggerContext::keristrasza_positioning; } private: static Trigger* faction_commander_whirlwind(PlayerbotAI* ai) { return new FactionCommanderWhirlwindTrigger(ai); } @@ -27,7 +27,7 @@ class WotlkDungeonNexTriggerContext : public NamedObjectContext 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); } + static Trigger* keristrasza_positioning(PlayerbotAI* ai) { return new KeristraszaPositioningTrigger(ai); } }; #endif diff --git a/src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp b/src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp index 22f1b29d..a39e497d 100644 --- a/src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp +++ b/src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp @@ -98,8 +98,10 @@ bool IntenseColdTrigger::IsActive() return boss && botAI->GetAura("intense cold", bot, false, false, stackThreshold); } -bool DragonPositioningTrigger::IsActive() +bool KeristraszaPositioningTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza"); - return boss && botAI->IsMelee(bot) && !botAI->IsTank(bot); + // Include healers here for now, otherwise they stand in things + return boss && !botAI->IsTank(bot) && !botAI->IsRangedDps(bot); + // 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 c9f14038..ac5d88a4 100644 --- a/src/strategy/dungeons/wotlk/nexus/NexusTriggers.h +++ b/src/strategy/dungeons/wotlk/nexus/NexusTriggers.h @@ -79,10 +79,10 @@ public: bool IsActive() override; }; -class DragonPositioningTrigger : public Trigger +class KeristraszaPositioningTrigger : public Trigger { public: - DragonPositioningTrigger(PlayerbotAI* ai) : Trigger(ai, "dragon positioning") {} + KeristraszaPositioningTrigger(PlayerbotAI* ai) : Trigger(ai, "keristrasza positioning") {} bool IsActive() override; }; diff --git a/src/strategy/dungeons/wotlk/violethold/TODO b/src/strategy/dungeons/wotlk/violethold/TODO deleted file mode 100644 index e69de29b..00000000 diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldActionContext.h b/src/strategy/dungeons/wotlk/violethold/VioletHoldActionContext.h new file mode 100644 index 00000000..902332f1 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldActionContext.h @@ -0,0 +1,24 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONVHACTIONCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONVHACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "VioletHoldActions.h" + +class WotlkDungeonVHActionContext : public NamedObjectContext +{ + public: + WotlkDungeonVHActionContext() { + creators["attack erekem"] = &WotlkDungeonVHActionContext::attack_erekem; + creators["attack ichor globule"] = &WotlkDungeonVHActionContext::attack_ichor_globule; + creators["attack void sentry"] = &WotlkDungeonVHActionContext::attack_void_sentry; + creators["stop attack"] = &WotlkDungeonVHActionContext::stop_attack; + } + private: + static Action* attack_erekem(PlayerbotAI* ai) { return new AttackErekemAction(ai); } + static Action* attack_ichor_globule(PlayerbotAI* ai) { return new AttackIchorGlobuleAction(ai); } + static Action* attack_void_sentry(PlayerbotAI* ai) { return new AttackVoidSentryAction(ai); } + static Action* stop_attack(PlayerbotAI* ai) { return new StopAttackAction(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldActions.cpp b/src/strategy/dungeons/wotlk/violethold/VioletHoldActions.cpp new file mode 100644 index 00000000..a108b323 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldActions.cpp @@ -0,0 +1,99 @@ +#include "Playerbots.h" +#include "VioletHoldActions.h" +#include "VioletHoldStrategy.h" + + +bool AttackErekemAction::Execute(Event event) +{ + // Focus boss first, adds after + Unit* boss = AI_VALUE2(Unit*, "find target", "erekem"); + if (AI_VALUE(Unit*, "current target") != boss) + { + return Attack(boss); + } + return false; +} + +bool AttackIchorGlobuleAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ichoron"); + if (!boss) { return false; } + + // Tank prioritise boss if it's up + if (botAI->IsTank(bot) && !boss->HasAura(SPELL_DRAINED)) + { + if (AI_VALUE(Unit*, "current target") != boss) + { + return Attack(boss); + } + return false; + } + + // 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"); + + for (auto i = targets.begin(); i != targets.end(); ++i) + { + Unit* unit = botAI->GetUnit(*i); + if (unit && unit->GetEntry() == NPC_ICHOR_GLOBULE) + { + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + // Check IDs here, NOT Unit* pointers: + // Don't keep swapping between sentries. + // If we're already attacking one, don't retarget another + if (currentTarget && currentTarget->GetEntry() == NPC_ICHOR_GLOBULE) + { + return false; + } + return Attack(unit); + } + } + // No ichor globules left alive, fall back to targeting boss + if (AI_VALUE(Unit*, "current target") != boss) + { + return Attack(boss); + } + + return false; +} + +bool AttackVoidSentryAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "zuramat the obliterator"); + if (!boss) { return false; } + + // 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"); + 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->GetEntry() == NPC_VOID_SENTRY) + { + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + // Check IDs here, NOT Unit* pointers: + // Don't keep swapping between sentries. + // If we're already attacking one, don't retarget another + if (currentTarget && currentTarget->GetEntry() == NPC_VOID_SENTRY) + { + return false; + } + return Attack(unit); + } + } + // No void sentries left alive, fall back to targeting boss + if (AI_VALUE(Unit*, "current target") != boss) + { + return Attack(boss); + } + + return false; +} + +bool StopAttackAction::Execute(Event event) +{ + return bot->AttackStop(); +} diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldActions.h b/src/strategy/dungeons/wotlk/violethold/VioletHoldActions.h new file mode 100644 index 00000000..2d2dada0 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldActions.h @@ -0,0 +1,48 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONVHACTIONS_H +#define _PLAYERBOT_WOTLKDUNGEONVHACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "GenericSpellActions.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "VioletHoldTriggers.h" + +// const Position NOVOS_PARTY_POSITION = Position(-378.852f, -760.349f, 28.587f); + +class AttackErekemAction : public AttackAction +{ +public: + AttackErekemAction(PlayerbotAI* ai) : AttackAction(ai, "attack erekem") {} + bool Execute(Event event) override; +}; + +class AttackIchoronElementalsAction : public AttackAction +{ +public: + AttackIchoronElementalsAction(PlayerbotAI* ai) : AttackAction(ai, "attack ichoron elementals") {} + bool Execute(Event event) override; +}; + +class AttackIchorGlobuleAction : public AttackAction +{ +public: + AttackIchorGlobuleAction(PlayerbotAI* ai) : AttackAction(ai, "attack ichor globule") {} + bool Execute(Event event) override; +}; + +class AttackVoidSentryAction : public AttackAction +{ +public: + AttackVoidSentryAction(PlayerbotAI* ai) : AttackAction(ai, "attack void sentry") {} + bool Execute(Event event) override; +}; + +class StopAttackAction : public Action +{ +public: + StopAttackAction(PlayerbotAI* ai) : Action(ai, "stop attack") {} + bool Execute(Event event) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldMultipliers.cpp b/src/strategy/dungeons/wotlk/violethold/VioletHoldMultipliers.cpp new file mode 100644 index 00000000..ff6cc6de --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldMultipliers.cpp @@ -0,0 +1,57 @@ +#include "VioletHoldMultipliers.h" +#include "VioletHoldActions.h" +#include "GenericSpellActions.h" +#include "ChooseTargetActions.h" +#include "MovementActions.h" +#include "VioletHoldTriggers.h" +#include "Action.h" + +float ErekemMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "erekem"); + if (!boss || !botAI->IsDps(bot)) { return 1.0f; } + + if (dynamic_cast(action)) + { + return 0.0f; + } + if (action->getThreatType() == Action::ActionThreatType::Aoe) + { + return 0.0f; + } + return 1.0f; +} + +float IchoronMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ichoron"); + if (!boss) { return 1.0f; } + + if (dynamic_cast(action) + || dynamic_cast(action) + || dynamic_cast(action)) + { + return 0.0f; + } + return 1.0f; +} + +float ZuramatMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "zuramat the obliterator"); + if (!boss) { return 1.0f; } + + if (bot->HasAura(SPELL_VOID_SHIFTED)) + { + if (dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + + if (boss->HasAura(SPELL_SHROUD_OF_DARKNESS) && dynamic_cast(action)) + { + return 0.0f; + } + return 1.0f; +} diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldMultipliers.h b/src/strategy/dungeons/wotlk/violethold/VioletHoldMultipliers.h new file mode 100644 index 00000000..67952a16 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldMultipliers.h @@ -0,0 +1,33 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONVHMULTIPLIERS_H +#define _PLAYERBOT_WOTLKDUNGEONVHMULTIPLIERS_H + +#include "Multiplier.h" + +class ErekemMultiplier : public Multiplier +{ + public: + ErekemMultiplier(PlayerbotAI* ai) : Multiplier(ai, "erekem") {} + + public: + virtual float GetValue(Action* action); +}; + +class IchoronMultiplier : public Multiplier +{ + public: + IchoronMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ichoron") {} + + public: + virtual float GetValue(Action* action); +}; + +class ZuramatMultiplier : public Multiplier +{ + public: + ZuramatMultiplier(PlayerbotAI* ai) : Multiplier(ai, "zuramat the obliterator") {} + + public: + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.cpp b/src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.cpp new file mode 100644 index 00000000..bcc27506 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.cpp @@ -0,0 +1,41 @@ +#include "VioletHoldStrategy.h" +#include "VioletHoldMultipliers.h" + + +void WotlkDungeonVHStrategy::InitTriggers(std::vector &triggers) +{ + // Erekem + // This boss has many purgable buffs, purging/dispels could be merged into generic strats though + triggers.push_back(new TriggerNode("erekem target", + NextAction::array(0, new NextAction("attack erekem", ACTION_RAID + 1), nullptr))); + + // Moragg + // TODO: This guy has Optic Link which may require moving, add if needed + + // Ichoron + triggers.push_back(new TriggerNode("ichoron target", + NextAction::array(0, new NextAction("attack ichor globule", ACTION_RAID + 1), nullptr))); + + // Xevozz + // TODO: Revisit in heroics, waypoints back and forth on stairs. Need to test with double beacon spawn + + // Lavanthor + // Tank & spank + + // Zuramat the Obliterator + triggers.push_back(new TriggerNode("shroud of darkness", + NextAction::array(0, new NextAction("stop attack", ACTION_HIGH + 5), nullptr))); + triggers.push_back(new TriggerNode("void shift", + NextAction::array(0, new NextAction("attack void sentry", ACTION_RAID + 1), nullptr))); + + // Cyanigosa + triggers.push_back(new TriggerNode("cyanigosa positioning", + NextAction::array(0, new NextAction("rear flank", ACTION_MOVE + 5), nullptr))); +} + +void WotlkDungeonVHStrategy::InitMultipliers(std::vector &multipliers) +{ + multipliers.push_back(new ErekemMultiplier(botAI)); + multipliers.push_back(new IchoronMultiplier(botAI)); + multipliers.push_back(new ZuramatMultiplier(botAI)); +} diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.h b/src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.h new file mode 100644 index 00000000..16708871 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.h @@ -0,0 +1,18 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONVHSTRATEGY_H +#define _PLAYERBOT_WOTLKDUNGEONVHSTRATEGY_H + +#include "Multiplier.h" +#include "AiObjectContext.h" +#include "Strategy.h" + + +class WotlkDungeonVHStrategy : public Strategy +{ +public: + WotlkDungeonVHStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "violet hold"; } + virtual void InitTriggers(std::vector &triggers) override; + virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggerContext.h b/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggerContext.h new file mode 100644 index 00000000..48a722f4 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggerContext.h @@ -0,0 +1,27 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONVHTRIGGERCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONVHTRIGGERCONTEXT_H + +#include "NamedObjectContext.h" +#include "AiObjectContext.h" +#include "VioletHoldTriggers.h" + +class WotlkDungeonVHTriggerContext : public NamedObjectContext +{ + public: + WotlkDungeonVHTriggerContext() + { + creators["erekem target"] = &WotlkDungeonVHTriggerContext::erekem_target; + creators["ichoron target"] = &WotlkDungeonVHTriggerContext::ichoron_target; + creators["void shift"] = &WotlkDungeonVHTriggerContext::void_shift; + creators["shroud of darkness"] = &WotlkDungeonVHTriggerContext::shroud_of_darkness; + creators["cyanigosa positioning"] = &WotlkDungeonVHTriggerContext::cyanigosa_positioning; + } + private: + static Trigger* erekem_target(PlayerbotAI* ai) { return new ErekemTargetTrigger(ai); } + static Trigger* ichoron_target(PlayerbotAI* ai) { return new IchoronTargetTrigger(ai); } + static Trigger* void_shift(PlayerbotAI* ai) { return new VoidShiftTrigger(ai); } + static Trigger* shroud_of_darkness(PlayerbotAI* ai) { return new ShroudOfDarknessTrigger(ai); } + static Trigger* cyanigosa_positioning(PlayerbotAI* ai) { return new CyanigosaPositioningTrigger(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.cpp b/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.cpp new file mode 100644 index 00000000..cf9e9ab1 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.cpp @@ -0,0 +1,35 @@ +#include "Playerbots.h" +#include "VioletHoldTriggers.h" +#include "AiObject.h" +#include "AiObjectContext.h" + + +bool ErekemTargetTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "erekem") && botAI->IsDps(bot); +} + +bool IchoronTargetTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "ichoron") && !botAI->IsHeal(bot); +} + +bool VoidShiftTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "zuramat the obliterator"); + return boss && bot->HasAura(SPELL_VOID_SHIFTED) && !botAI->IsHeal(bot); +} + +bool ShroudOfDarknessTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "zuramat the obliterator"); + return boss && boss->HasAura(SPELL_SHROUD_OF_DARKNESS); +} + +bool CyanigosaPositioningTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "cyanigosa"); + // Include healers here for now, otherwise they stand in things + return boss && !botAI->IsTank(bot) && !botAI->IsRangedDps(bot); + // return boss && botAI->IsMelee(bot) && !botAI->IsTank(bot); +} diff --git a/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.h b/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.h new file mode 100644 index 00000000..b5a306d4 --- /dev/null +++ b/src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.h @@ -0,0 +1,59 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONVHTRIGGERS_H +#define _PLAYERBOT_WOTLKDUNGEONVHTRIGGERS_H + +#include "Trigger.h" +#include "PlayerbotAIConfig.h" +#include "GenericTriggers.h" +#include "DungeonStrategyUtils.h" + +enum VioletHoldIDs +{ + // Ichoron + SPELL_DRAINED = 59820, + NPC_ICHOR_GLOBULE = 29321, + + // Zuramat the Obliterator + SPELL_VOID_SHIFTED = 54343, + SPELL_SHROUD_OF_DARKNESS_N = 54524, + SPELL_SHROUD_OF_DARKNESS_H = 59745, + NPC_VOID_SENTRY = 29364, +}; + +#define SPELL_SHROUD_OF_DARKNESS DUNGEON_MODE(bot, SPELL_SHROUD_OF_DARKNESS_N, SPELL_SHROUD_OF_DARKNESS_H) + +class ErekemTargetTrigger : public Trigger +{ +public: + ErekemTargetTrigger(PlayerbotAI* ai) : Trigger(ai, "erekem target") {} + bool IsActive() override; +}; + +class IchoronTargetTrigger : public Trigger +{ +public: + IchoronTargetTrigger(PlayerbotAI* ai) : Trigger(ai, "ichoron target") {} + bool IsActive() override; +}; + +class VoidShiftTrigger : public Trigger +{ +public: + VoidShiftTrigger(PlayerbotAI* ai) : Trigger(ai, "void shift") {} + bool IsActive() override; +}; + +class ShroudOfDarknessTrigger : public Trigger +{ +public: + ShroudOfDarknessTrigger(PlayerbotAI* ai) : Trigger(ai, "shroud of darkness") {} + bool IsActive() override; +}; + +class CyanigosaPositioningTrigger : public Trigger +{ +public: + CyanigosaPositioningTrigger(PlayerbotAI* ai) : Trigger(ai, "cyanigosa positioning") {} + bool IsActive() override; +}; + +#endif