From 9f273791a7ef14bbfc46ca321551e4edd716c167 Mon Sep 17 00:00:00 2001 From: Bobblybook Date: Sun, 27 Oct 2024 02:20:04 +1100 Subject: [PATCH] Oculus implementation --- src/strategy/AiObjectContext.cpp | 2 + .../dungeons/DungeonStrategyContext.h | 10 +- .../wotlk/WotlkDungeonActionContext.h | 2 +- .../wotlk/WotlkDungeonTriggerContext.h | 2 +- .../hallsofstone/HallsOfStoneActions.cpp | 3 +- .../wotlk/oculus/OculusActionContext.h | 30 ++ .../dungeons/wotlk/oculus/OculusActions.cpp | 355 ++++++++++++++++++ .../dungeons/wotlk/oculus/OculusActions.h | 77 ++++ .../wotlk/oculus/OculusMultipliers.cpp | 109 ++++++ .../dungeons/wotlk/oculus/OculusMultipliers.h | 53 +++ .../dungeons/wotlk/oculus/OculusStrategy.cpp | 42 +++ .../dungeons/wotlk/oculus/OculusStrategy.h | 18 + .../wotlk/oculus/OculusTriggerContext.h | 33 ++ .../dungeons/wotlk/oculus/OculusTriggers.cpp | 85 +++++ .../dungeons/wotlk/oculus/OculusTriggers.h | 129 +++++++ src/strategy/dungeons/wotlk/oculus/TODO | 0 .../wotlk/oldkingdom/OldKingdomActions.cpp | 25 +- 17 files changed, 949 insertions(+), 26 deletions(-) create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusActionContext.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusActions.cpp create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusActions.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusMultipliers.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusStrategy.cpp create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusStrategy.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusTriggerContext.h create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp create mode 100644 src/strategy/dungeons/wotlk/oculus/OculusTriggers.h delete mode 100644 src/strategy/dungeons/wotlk/oculus/TODO diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index 9b52369e4..b2ede465d 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -59,6 +59,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new WotlkDungeonGDActionContext()); actionContexts.Add(new WotlkDungeonHoSActionContext()); actionContexts.Add(new WotlkDungeonHoLActionContext()); + actionContexts.Add(new WotlkDungeonOccActionContext()); actionContexts.Add(new WotlkDungeonUPActionContext()); actionContexts.Add(new WotlkDungeonCoSActionContext()); @@ -80,6 +81,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new WotlkDungeonGDTriggerContext()); triggerContexts.Add(new WotlkDungeonHoSTriggerContext()); triggerContexts.Add(new WotlkDungeonHoLTriggerContext()); + triggerContexts.Add(new WotlkDungeonOccTriggerContext()); triggerContexts.Add(new WotlkDungeonUPTriggerContext()); triggerContexts.Add(new WotlkDungeonCoSTriggerContext()); diff --git a/src/strategy/dungeons/DungeonStrategyContext.h b/src/strategy/dungeons/DungeonStrategyContext.h index 3bb39c187..5b1914a27 100644 --- a/src/strategy/dungeons/DungeonStrategyContext.h +++ b/src/strategy/dungeons/DungeonStrategyContext.h @@ -11,14 +11,13 @@ #include "wotlk/gundrak/GundrakStrategy.h" #include "wotlk/hallsofstone/HallsOfStoneStrategy.h" #include "wotlk/hallsoflightning/HallsOfLightningStrategy.h" +#include "wotlk/oculus/OculusStrategy.h" #include "wotlk/utgardepinnacle/UtgardePinnacleStrategy.h" #include "wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h" /* Full list/TODO: -The Oculus - Occ -Drakos the Interrogator, Varos Cloudstrider, Mage-Lord Urom, Ley-Guardian Eregos Trial of the Champion - ToC Alliance Champions: Deathstalker Visceri, Eressea Dawnsinger, Mokra the Skullcrusher, Runok Wildmane, Zul'tore Horde Champions: Ambrose Boltspark, Colosos, Jacob Alerius, Jaelyne Evensong, Lana Stouthammer @@ -74,12 +73,11 @@ class DungeonStrategyContext : public NamedObjectContext static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonGDStrategy(botAI); } static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonHoSStrategy(botAI); } static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonHoLStrategy(botAI); } - // static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonOccStrategy(botAI); } - static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } + static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonOccStrategy(botAI); } static Strategy* wotlk_up(PlayerbotAI* botAI) { return new WotlkDungeonUPStrategy(botAI); } static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonCoSStrategy(botAI); } - - static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } // NYI from here down + // NYI from here down + static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_hor(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_pos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_fos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h index 81b47ae27..e6f2e00ac 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonActionContext.h @@ -10,7 +10,7 @@ #include "gundrak/GundrakActionContext.h" #include "hallsofstone/HallsOfStoneActionContext.h" #include "hallsoflightning/HallsOfLightningActionContext.h" -// #include "oculus/OculusActionContext.h" +#include "oculus/OculusActionContext.h" #include "utgardepinnacle/UtgardePinnacleActionContext.h" #include "cullingofstratholme/CullingOfStratholmeActionContext.h" // #include "trialofthechampion/TrialOfTheChampionActionContext.h" diff --git a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h index 0bafe36a5..455239abf 100644 --- a/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h +++ b/src/strategy/dungeons/wotlk/WotlkDungeonTriggerContext.h @@ -10,7 +10,7 @@ #include "gundrak/GundrakTriggerContext.h" #include "hallsofstone/HallsOfStoneTriggerContext.h" #include "hallsoflightning/HallsOfLightningTriggerContext.h" -// #include "oculus/OculusTriggerContext.h" +#include "oculus/OculusTriggerContext.h" #include "utgardepinnacle/UtgardePinnacleTriggerContext.h" #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h" // #include "trialofthechampion/TrialOfTheChampionTriggerContext.h" diff --git a/src/strategy/dungeons/wotlk/hallsofstone/HallsOfStoneActions.cpp b/src/strategy/dungeons/wotlk/hallsofstone/HallsOfStoneActions.cpp index 29f9b3ca3..549012496 100644 --- a/src/strategy/dungeons/wotlk/hallsofstone/HallsOfStoneActions.cpp +++ b/src/strategy/dungeons/wotlk/hallsofstone/HallsOfStoneActions.cpp @@ -5,10 +5,11 @@ bool ShatterSpreadAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "krystallus"); - float radius = 40.0f; if (!boss) { return false; } + float radius = 40.0f; Unit* closestMember = nullptr; + GuidVector members = AI_VALUE(GuidVector, "group members"); for (auto& member : members) { diff --git a/src/strategy/dungeons/wotlk/oculus/OculusActionContext.h b/src/strategy/dungeons/wotlk/oculus/OculusActionContext.h new file mode 100644 index 000000000..38cd640bf --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusActionContext.h @@ -0,0 +1,30 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCACTIONCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONOCCACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "OculusActions.h" + +class WotlkDungeonOccActionContext : public NamedObjectContext +{ + public: + WotlkDungeonOccActionContext() { + creators["avoid unstable sphere"] = &WotlkDungeonOccActionContext::avoid_unstable_sphere; + creators["mount drake"] = &WotlkDungeonOccActionContext::mount_drake; + creators["dismount drake"] = &WotlkDungeonOccActionContext::dismount_drake; + creators["fly drake"] = &WotlkDungeonOccActionContext::fly_drake; + creators["drake attack"] = &WotlkDungeonOccActionContext::drake_attack; + creators["avoid arcane explosion"] = &WotlkDungeonOccActionContext::avoid_arcane_explosion; + creators["time bomb spread"] = &WotlkDungeonOccActionContext::time_bomb_spread; + } + private: + static Action* avoid_unstable_sphere(PlayerbotAI* ai) { return new AvoidUnstableSphereAction(ai); } + static Action* mount_drake(PlayerbotAI* ai) { return new MountDrakeAction(ai); } + static Action* dismount_drake(PlayerbotAI* ai) { return new DismountDrakeAction(ai); } + static Action* fly_drake(PlayerbotAI* ai) { return new FlyDrakeAction(ai); } + static Action* drake_attack(PlayerbotAI* ai) { return new DrakeAttackAction(ai); } + static Action* avoid_arcane_explosion(PlayerbotAI* ai) { return new AvoidArcaneExplosionAction(ai); } + static Action* time_bomb_spread(PlayerbotAI* ai) { return new TimeBombSpreadAction(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp b/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp new file mode 100644 index 000000000..e50ae07df --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusActions.cpp @@ -0,0 +1,355 @@ +#include "Playerbots.h" +#include "OculusActions.h" +#include "OculusStrategy.h" +#include "LastSpellCastValue.h" + +bool AvoidUnstableSphereAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "drakos the interrogator"); + if (!boss) { return false; } + + float radius = 12.0f; + float extraDistance = 1.0f; + Unit* closestSphere = nullptr; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit && unit->GetEntry() == NPC_UNSTABLE_SPHERE && !unit->isMoving()) + { + if (!closestSphere || bot->GetExactDist2d(unit) < bot->GetExactDist2d(closestSphere)) + { + closestSphere = unit; + } + } + } + + if (closestSphere && bot->GetExactDist2d(closestSphere) < radius + extraDistance) + { + return MoveAway(closestSphere, fmin(3.0f, bot->GetExactDist2d(closestSphere) - radius + extraDistance)); + } + + return false; +} + +bool MountDrakeAction::isPossible() { return bot->GetMapId() == OCULUS_MAP_ID; } +bool MountDrakeAction::Execute(Event event) +{ + std::map drakeAssignments; + // Composition can be adjusted - both 3/1/1 and 2/2/1 are good default comps + // {Amber, Emerald, Ruby} + std::vector composition = {2, 2, 1}; + // std::vector composition = {3, 1, 1}; + int32 myIndex = botAI->GetGroupSlotIndex(bot); + + Player* master = botAI->GetMaster(); + if (!master) { return false; } + Unit* vehicle = master->GetVehicleBase(); + if (!vehicle) { return false; } + + // Subtract the player's chosen mount type from the composition so player can play whichever they prefer + switch (vehicle->GetEntry()) + { + case NPC_AMBER_DRAKE: + composition[0]--; + break; + case NPC_EMERALD_DRAKE: + composition[1]--; + break; + case NPC_RUBY_DRAKE: + composition[2]--; + break; + } + + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto& member : members) + { + Player* player = botAI->GetPlayer(member); + if (!player) { continue; } + + for (int i = 0; i < composition.size(); i++) + { + if (composition[i] > 0) + { + drakeAssignments[botAI->GetGroupSlotIndex(player)] = DRAKE_ITEMS[i]; + composition[i]--; + break; + } + } + } + + // Correct/update the drake items in inventories incase assignments have changed + for (uint32 itemId : DRAKE_ITEMS) + { + Item* item = bot->GetItemByEntry(itemId); + if (!item) { continue; } + + if (itemId == drakeAssignments[myIndex]) + { + // Use our assigned drake + return UseItemAuto(item); + } + // Else assigned drake is different, destroy old drake + uint32 count = 1; + bot->DestroyItemCount(item, count, true); + break; + } + + // Bot does not have the correct drake item + bot->AddItem(drakeAssignments[myIndex], 1); + return false; +} + +bool DismountDrakeAction::Execute(Event event) +{ + if (bot->GetVehicle()) + { + bot->ExitVehicle(); + return true; + } + return false; +} + +bool FlyDrakeAction::Execute(Event event) +{ + Player* master = botAI->GetMaster(); + if (!master) { return false; } + Unit* masterVehicle = master->GetVehicleBase(); + Unit* vehicleBase = bot->GetVehicleBase(); + if (!vehicleBase || !masterVehicle) { return false; } + + MotionMaster* mm = vehicleBase->GetMotionMaster(); + Unit* boss = AI_VALUE2(Unit*, "find target", "ley-guardian eregos"); + if (boss && !boss->HasAura(SPELL_PLANAR_SHIFT)) + { + // Handle as boss encounter instead of formation flight + mm->Clear(false); + float distance = vehicleBase->GetExactDist(boss); + float range = 55.0f; // Drake range is 60yd + if (distance > range) + { + mm->MoveForwards(boss, range - distance); + vehicleBase->SendMovementFlagUpdate(); + return true; + } + + vehicleBase->SetFacingToObject(boss); + mm->MoveIdle(); + vehicleBase->SendMovementFlagUpdate(); + return false; + } + + if (vehicleBase->GetExactDist(masterVehicle) > 20.0f) + { + // 3/4 of a circle, with frontal cone 90 deg unobstructed + float angle = botAI->GetGroupSlotIndex(bot) * (2*M_PI - M_PI_2)/5 + M_PI_2; + vehicleBase->SetCanFly(true); + mm->MoveFollow(masterVehicle, 15.0f, angle); + vehicleBase->SendMovementFlagUpdate(); + return true; + } + return false; +} + +bool DrakeAttackAction::Execute(Event event) +{ + vehicleBase = bot->GetVehicleBase(); + if (!vehicleBase) { return false; } + + Unit* target = AI_VALUE(Unit*, "current target"); + + if (!target) + { + GuidVector attackers = AI_VALUE(GuidVector, "attackers"); + for (auto& attacker : attackers) + { + Unit* unit = botAI->GetUnit(attacker); + if (!unit) { continue; } + + SET_AI_VALUE(Unit*, "current target", unit); + target = unit; + break; + } + } + + if (!target) { return false; } + + switch (vehicleBase->GetEntry()) + { + case NPC_AMBER_DRAKE: + return AmberDrakeAction(target); + case NPC_EMERALD_DRAKE: + return EmeraldDrakeAction(target); + case NPC_RUBY_DRAKE: + return RubyDrakeAction(target); + default: + break; + } + return false; +} + +bool DrakeAttackAction::CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown) +{ + if (botAI->CanCastVehicleSpell(spellId, target)) + if (botAI->CastVehicleSpell(spellId, target)) + { + vehicleBase->AddSpellCooldown(spellId, 0, cooldown); + return true; + } + return false; +} + +bool DrakeAttackAction::AmberDrakeAction(Unit* target) +{ + Aura* shockCharges = target->GetAura(SPELL_SHOCK_CHARGE, vehicleBase->GetGUID()); + if (shockCharges && shockCharges->GetStackAmount() > 8) + { + // At 9 charges, better to detonate and re-channel rather than stacking the last charge due to gcd + // If stacking Amber drakes, may need to drop this even lower as the charges stack so fast + return CastDrakeSpellAction(target, SPELL_SHOCK_LANCE, 0); + } + + // Deal with enrage after shock charges, as Stop Time adds 5 charges and they may get wasted + if (target->HasAura(SPELL_ENRAGED_ASSAULT) && + !target->HasAura(SPELL_STOP_TIME) && + !vehicleBase->HasSpellCooldown(SPELL_STOP_TIME)) + { + return CastDrakeSpellAction(target, SPELL_STOP_TIME, 60000); + } + + if (!vehicleBase->FindCurrentSpellBySpellId(SPELL_TEMPORAL_RIFT)) + { + return CastDrakeSpellAction(target, SPELL_TEMPORAL_RIFT, 0); + } + + return false; +} + +bool DrakeAttackAction::EmeraldDrakeAction(Unit* target) +{ + Aura* poisonStacks = target->GetAura(SPELL_LEECHING_POISON, vehicleBase->GetGUID()); + if (!poisonStacks || (poisonStacks->GetStackAmount() < 3 || + poisonStacks->GetDuration() < 4000)) + { + return CastDrakeSpellAction(target, SPELL_LEECHING_POISON, 0); + } + + if (!vehicleBase->HasSpellCooldown(SPELL_TOUCH_THE_NIGHTMARE) && + (!target->HasAura(SPELL_TOUCH_THE_NIGHTMARE) || vehicleBase->HealthAbovePct(90))) + { + return CastDrakeSpellAction(target, SPELL_TOUCH_THE_NIGHTMARE, 10000); + } + + Unit* healingTarget = nullptr; + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto& member : members) + { + Unit* unit = botAI->GetUnit(member); + if (!unit || bot->GetGUID() == member) + { + continue; + } + + Unit* drake = unit->GetVehicleBase(); + if (!drake || drake->IsFullHealth()) { continue; } + + if (!healingTarget || drake->GetHealthPct() < healingTarget->GetHealthPct() - 15.0f) + { + healingTarget = drake; + } + } + + Spell* currentSpell = vehicleBase->FindCurrentSpellBySpellId(SPELL_DREAM_FUNNEL); + if (healingTarget) + { + if (!currentSpell || currentSpell->m_targets.GetUnitTarget() != healingTarget) + { + float distance = vehicleBase->GetExactDist(healingTarget); + float range = 55.0f; + if (distance > range) + { + MotionMaster* mm = vehicleBase->GetMotionMaster(); + mm->Clear(false); + mm->MoveForwards(healingTarget, distance - range - 10.0f); + vehicleBase->SendMovementFlagUpdate(); + return false; + } + return CastDrakeSpellAction(healingTarget, SPELL_DREAM_FUNNEL, 0); + } + } + // Fill GCDs with Leeching Poison to refresh timer, rather than idling + if (!currentSpell) + { + return CastDrakeSpellAction(target, SPELL_LEECHING_POISON, 0); + } + + return false; +} + +bool DrakeAttackAction::RubyDrakeAction(Unit* target) +{ + Aura* evasiveCharges = vehicleBase->GetAura(SPELL_EVASIVE_CHARGES); + Aura* evasiveManeuvers = vehicleBase->GetAura(SPELL_EVASIVE_MANEUVERS); + + if (evasiveCharges) + { + if (evasiveManeuvers && + !vehicleBase->HasSpellCooldown(SPELL_MARTYR) && + evasiveManeuvers->GetDuration() > 10000 && + evasiveCharges->GetStackAmount() >= 5) + { + return CastDrakeSpellAction(vehicleBase, SPELL_MARTYR, 10000); + } + + if (!vehicleBase->HasSpellCooldown(SPELL_EVASIVE_MANEUVERS) && + evasiveCharges->GetStackAmount() >= 10) + { + return CastDrakeSpellAction(vehicleBase, SPELL_EVASIVE_MANEUVERS, 5000); + } + } + + return CastDrakeSpellAction(target, SPELL_SEARING_WRATH, 0); +} + +bool AvoidArcaneExplosionAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom"); + if (!boss) { return false; } + + const Position* closestPos = nullptr; + + for (auto& position : uromSafePositions) + { + if (!closestPos || bot->GetExactDist(position) < bot->GetExactDist(closestPos)) + { + closestPos = &position; + } + } + + if (!closestPos) { return false; } + + return MoveNear(bot->GetMapId(), closestPos->GetPositionX(), closestPos->GetPositionY(), closestPos->GetPositionZ(), 2.0f, MovementPriority::MOVEMENT_COMBAT); +} + +bool TimeBombSpreadAction::Execute(Event event) +{ + float radius = 10.0f; + float distanceExtra = 2.0f; + + GuidVector members = AI_VALUE(GuidVector, "group members"); + for (auto& member : members) + { + if (bot->GetGUID() == member) + { + continue; + } + + Unit* unit = botAI->GetUnit(member); + if (unit && bot->GetExactDist2d(unit) < radius) + { + return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit)); + } + } + return false; +} diff --git a/src/strategy/dungeons/wotlk/oculus/OculusActions.h b/src/strategy/dungeons/wotlk/oculus/OculusActions.h new file mode 100644 index 000000000..2111461aa --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusActions.h @@ -0,0 +1,77 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCACTIONS_H +#define _PLAYERBOT_WOTLKDUNGEONOCCACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "OculusTriggers.h" +#include "UseItemAction.h" +#include "GenericSpellActions.h" + +const Position uromSafePositions[3] = +{ + Position(1138.88f, 1052.22f, 508.36f), + Position(1084.62f, 1079.71f, 508.36f), + Position(1087.42f, 1020.132f, 508.36f) +}; + +class AvoidUnstableSphereAction : public MovementAction +{ +public: + AvoidUnstableSphereAction(PlayerbotAI* ai) : MovementAction(ai, "avoid unstable sphere") {} + bool Execute(Event event) override; +}; + +class MountDrakeAction : public UseItemAction +{ +public: + MountDrakeAction(PlayerbotAI* ai) : UseItemAction(ai, "mount drake") {} + bool Execute(Event event) override; + bool isPossible() override; +}; + +class DismountDrakeAction : public Action +{ +public: + DismountDrakeAction(PlayerbotAI* ai) : Action(ai, "dismount drake") {} + bool Execute(Event event) override; +}; + +class FlyDrakeAction : public MovementAction +{ +public: + FlyDrakeAction(PlayerbotAI* ai) : MovementAction(ai, "fly drake") {} + bool Execute(Event event) override; +}; + +class DrakeAttackAction : public Action +{ +public: + DrakeAttackAction(PlayerbotAI* botAI) : Action(botAI, "drake attack") {} + bool Execute(Event event) override; + +protected: + Unit* vehicleBase; + bool CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown); + bool AmberDrakeAction(Unit* target); + bool EmeraldDrakeAction(Unit* target); + bool RubyDrakeAction(Unit* target); + +}; + +class AvoidArcaneExplosionAction : public MovementAction +{ +public: + AvoidArcaneExplosionAction(PlayerbotAI* ai) : MovementAction(ai, "avoid arcane explosion") {} + bool Execute(Event event) override; +}; + +class TimeBombSpreadAction : public MovementAction +{ +public: + TimeBombSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "time bomb spread") {} + bool Execute(Event event) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp new file mode 100644 index 000000000..13490f935 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.cpp @@ -0,0 +1,109 @@ +#include "OculusMultipliers.h" +#include "OculusActions.h" +#include "GenericSpellActions.h" +#include "ChooseTargetActions.h" +#include "MovementActions.h" +#include "OculusTriggers.h" +#include "FollowActions.h" +#include "ReachTargetActions.h" + +float MountingDrakeMultiplier::GetValue(Action* action) +{ + // P.I.T.A bug where the bots will somehow interrupt their item spell use, + // even though the 0.5 sec cast goes off, it puts the drake essence on 15 sec cd + // and no drake comes down. + // It seems like this is due to moving/other actions being processed during the 0.5 secs. + // If we suppress everything, they seem to mount properly. A bit of a ham-fisted solution but it works + Player* master = botAI->GetMaster(); + if (bot->GetMapId() != OCULUS_MAP_ID || !master->GetVehicleBase() || bot->GetVehicleBase()) { return 1.0f; } + + if (!dynamic_cast(action)) + { + return 0.0f; + } + return 1.0f; +} + +float FlyingMultiplier::GetValue(Action* action) +{ + if (bot->GetMapId() != OCULUS_MAP_ID || !bot->GetVehicleBase()) { return 1.0f; } + + // Suppresses FollowAction as well as some attack-based movements + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + return 1.0f; +} + +float UromMultiplier::GetValue(Action* action) +{ + if(GetPhaseByCurrentPosition(bot) < 3) + { + Unit* target = action->GetTarget(); + if (target && target->GetEntry() == NPC_MAGE_LORD_UROM) + { + return 0.0f; + } + } + + Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom"); + if (!boss) { return 1.0f; } + + // REAL BOSS FIGHT + if (boss->HasUnitState(UNIT_STATE_CASTING) && + boss->FindCurrentSpellBySpellId(SPELL_EMPOWERED_ARCANE_EXPLOSION)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + } + + // Don't bother avoiding Frostbomb for melee + if (botAI->IsMelee(bot)) + { + if (dynamic_cast(action)) + { + return 0.0f; + } + } + + if (bot->HasAura(SPELL_TIME_BOMB)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + } + + return 1.0f; +} + +uint8 UromMultiplier::GetPhaseByCurrentPosition(Unit* unit) +{ + // Distance to return a positive match for spawn platforms, tweak slightly if needed/ + // Make sure this doesn't get too large and reach the central ring as well + float distance = 60.0f; + + for (uint8 i = 0; i < 3; ++i) + { + if (unit->GetDistance(uromCoords[i][0], uromCoords[i][1], uromCoords[i][2]) < distance) + { + return i; + } + } + return 3; +} + +float EregosMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "ley-guardian eregos"); + if (!boss) { return 1.0f; } + + if (boss->HasAura(SPELL_PLANAR_SHIFT && dynamic_cast(action))) + { + return 0.0f; + } + return 1.0f; +} diff --git a/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.h b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.h new file mode 100644 index 000000000..f2f86f794 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusMultipliers.h @@ -0,0 +1,53 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCMULTIPLIERS_H +#define _PLAYERBOT_WOTLKDUNGEONOCCMULTIPLIERS_H + +#include "Multiplier.h" +#include "Unit.h" + +const float uromCoords[4][4] = +{ // Platform coordinates + {1177.47f, 937.722f, 527.405f, 2.21657f}, + {968.66f, 1042.53f, 527.32f, 0.077f}, + {1164.02f, 1170.85f, 527.321f, 3.66f}, + {1118.31f, 1080.377f, 508.361f, 4.25f} // Inner ring, actual boss fight +}; + +class MountingDrakeMultiplier : public Multiplier +{ + public: + MountingDrakeMultiplier(PlayerbotAI* ai) : Multiplier(ai, "mounting drake") {} + + public: + virtual float GetValue(Action* action); +}; + +class FlyingMultiplier : public Multiplier +{ + public: + FlyingMultiplier(PlayerbotAI* ai) : Multiplier(ai, "flying drake") {} + + public: + virtual float GetValue(Action* action); +}; + +class UromMultiplier : public Multiplier +{ + public: + UromMultiplier(PlayerbotAI* ai) : Multiplier(ai, "mage-lord urom") {} + + public: + virtual float GetValue(Action* action); + protected: + uint8 GetPhaseByCurrentPosition(Unit* boss); +}; + +class EregosMultiplier : public Multiplier +{ + public: + EregosMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ley-guardian eregos") {} + + public: + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusStrategy.cpp b/src/strategy/dungeons/wotlk/oculus/OculusStrategy.cpp new file mode 100644 index 000000000..2b98e7dab --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusStrategy.cpp @@ -0,0 +1,42 @@ +#include "OculusStrategy.h" +#include "OculusMultipliers.h" + + +void WotlkDungeonOccStrategy::InitTriggers(std::vector &triggers) +{ + // Drakos the Interrogator + // TODO: May need work, TBA. + triggers.push_back(new TriggerNode("unstable sphere", + NextAction::array(0, new NextAction("avoid unstable sphere", ACTION_MOVE + 5), nullptr))); + + // DRAKES + triggers.push_back(new TriggerNode("drake mount", + NextAction::array(0, new NextAction("mount drake", ACTION_RAID + 5), nullptr))); + triggers.push_back(new TriggerNode("drake dismount", + NextAction::array(0, new NextAction("dismount drake", ACTION_RAID + 5), nullptr))); + triggers.push_back(new TriggerNode("group flying", + NextAction::array(0, new NextAction("fly drake", ACTION_NORMAL + 1), nullptr))); + triggers.push_back(new TriggerNode("drake combat", + NextAction::array(0, new NextAction("drake attack", ACTION_NORMAL + 5), nullptr))); + + // Varos Cloudstrider + // Seems to be no way to identify the marked cores, may need to hook boss AI.. + // triggers.push_back(new TriggerNode("varos cloudstrider", + // NextAction::array(0, new NextAction("avoid energize cores", ACTION_RAID + 5), nullptr))); + + // Mage-Lord Urom + triggers.push_back(new TriggerNode("arcane explosion", + NextAction::array(0, new NextAction("avoid arcane explosion", ACTION_MOVE + 5), nullptr))); + triggers.push_back(new TriggerNode("time bomb", + NextAction::array(0, new NextAction("time bomb spread", ACTION_MOVE + 4), nullptr))); + + // Ley-Guardian Eregos +} + +void WotlkDungeonOccStrategy::InitMultipliers(std::vector &multipliers) +{ + multipliers.push_back(new MountingDrakeMultiplier(botAI)); + multipliers.push_back(new FlyingMultiplier(botAI)); + multipliers.push_back(new UromMultiplier(botAI)); + multipliers.push_back(new EregosMultiplier(botAI)); +} diff --git a/src/strategy/dungeons/wotlk/oculus/OculusStrategy.h b/src/strategy/dungeons/wotlk/oculus/OculusStrategy.h new file mode 100644 index 000000000..a734f2f45 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusStrategy.h @@ -0,0 +1,18 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCSTRATEGY_H +#define _PLAYERBOT_WOTLKDUNGEONOCCSTRATEGY_H + +#include "Multiplier.h" +#include "AiObjectContext.h" +#include "Strategy.h" + + +class WotlkDungeonOccStrategy : public Strategy +{ +public: + WotlkDungeonOccStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "oculus"; } + virtual void InitTriggers(std::vector &triggers) override; + virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusTriggerContext.h b/src/strategy/dungeons/wotlk/oculus/OculusTriggerContext.h new file mode 100644 index 000000000..8cbf88db6 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusTriggerContext.h @@ -0,0 +1,33 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERCONTEXT_H +#define _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERCONTEXT_H + +#include "NamedObjectContext.h" +#include "AiObjectContext.h" +#include "OculusTriggers.h" + +class WotlkDungeonOccTriggerContext : public NamedObjectContext +{ + public: + WotlkDungeonOccTriggerContext() + { + creators["unstable sphere"] = &WotlkDungeonOccTriggerContext::unstable_sphere; + creators["drake mount"] = &WotlkDungeonOccTriggerContext::drake_mount; + creators["drake dismount"] = &WotlkDungeonOccTriggerContext::drake_dismount; + creators["group flying"] = &WotlkDungeonOccTriggerContext::group_flying; + creators["drake combat"] = &WotlkDungeonOccTriggerContext::drake_combat; + creators["varos cloudstrider"] = &WotlkDungeonOccTriggerContext::varos_cloudstrider; + creators["arcane explosion"] = &WotlkDungeonOccTriggerContext::arcane_explosion; + creators["time bomb"] = &WotlkDungeonOccTriggerContext::time_bomb; + } + private: + static Trigger* unstable_sphere(PlayerbotAI* ai) { return new DrakosUnstableSphereTrigger(ai); } + static Trigger* drake_mount(PlayerbotAI* ai) { return new DrakeMountTrigger(ai); } + static Trigger* drake_dismount(PlayerbotAI* ai) { return new DrakeDismountTrigger(ai); } + static Trigger* group_flying(PlayerbotAI* ai) { return new GroupFlyingTrigger(ai); } + static Trigger* drake_combat(PlayerbotAI* ai) { return new DrakeCombatTrigger(ai); } + static Trigger* varos_cloudstrider(PlayerbotAI* ai) { return new VarosCloudstriderTrigger(ai); } + static Trigger* arcane_explosion(PlayerbotAI* ai) { return new UromArcaneExplosionTrigger(ai); } + static Trigger* time_bomb(PlayerbotAI* ai) { return new UromTimeBombTrigger(ai); } +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp b/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp new file mode 100644 index 000000000..38c992803 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusTriggers.cpp @@ -0,0 +1,85 @@ +#include "Playerbots.h" +#include "OculusTriggers.h" +#include "AiObject.h" +#include "AiObjectContext.h" +#include "Unit.h" + +bool DrakosUnstableSphereTrigger::IsActive() +{ + // Doesn't seem to be much point trying to get melee to dodge this, + // they get hit anyway and it just causes a lot of running around and chaos + // if (botAI->IsMelee(bot)) { return false; } + if (botAI->IsTank(bot)) { return false; } + + GuidVector targets = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (unit && unit->GetEntry() == NPC_UNSTABLE_SPHERE) + { + return true; + } + } + return false; +} + +bool DrakeMountTrigger::IsActive() +{ + Player* master = botAI->GetMaster(); + if (!master) { return false; } + + return master->GetVehicleBase() && !bot->GetVehicleBase(); +} + +bool DrakeDismountTrigger::IsActive() +{ + Player* master = botAI->GetMaster(); + if (!master) { return false; } + + return !master->GetVehicleBase() && bot->GetVehicleBase(); +} + +bool GroupFlyingTrigger::IsActive() +{ + Player* master = botAI->GetMaster(); + if (!master) { return false; } + + return master->GetVehicleBase() && bot->GetVehicleBase(); +} + +bool DrakeCombatTrigger::IsActive() +{ + Unit* vehicleBase = bot->GetVehicleBase(); + if (!vehicleBase) { return false; } + + GuidVector attackers = AI_VALUE(GuidVector, "attackers"); + for (auto& attacker : attackers) + { + Unit* target = botAI->GetUnit(attacker); + if (!target) { continue; } + + return true; + } + return false; +} + +bool VarosCloudstriderTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "varos cloudstrider"); + if (!boss) { return false; } + + return true; +} + +bool UromArcaneExplosionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom"); + if (!boss) { return false; } + + return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_EMPOWERED_ARCANE_EXPLOSION); +} + +bool UromTimeBombTrigger::IsActive() +{ + return bot->HasAura(SPELL_TIME_BOMB); +} diff --git a/src/strategy/dungeons/wotlk/oculus/OculusTriggers.h b/src/strategy/dungeons/wotlk/oculus/OculusTriggers.h new file mode 100644 index 000000000..8d2d55d16 --- /dev/null +++ b/src/strategy/dungeons/wotlk/oculus/OculusTriggers.h @@ -0,0 +1,129 @@ +#ifndef _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERS_H +#define _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERS_H + +#include "Trigger.h" +#include "PlayerbotAIConfig.h" +#include "GenericTriggers.h" +#include "DungeonStrategyUtils.h" + +enum OculusIDs +{ + // Drakos the Interrogator + NPC_UNSTABLE_SPHERE = 28166, + SPELL_UNSTABLE_SPHERE_PASSIVE = 50756, + SPELL_UNSTABLE_SPHERE_PULSE = 50757, + SPELL_UNSTABLE_SPHERE_TIMER = 50758, + + // Drakes + NPC_AMBER_DRAKE = 27755, + NPC_EMERALD_DRAKE = 27692, + NPC_RUBY_DRAKE = 27756, + ITEM_AMBER_ESSENCE = 37859, + ITEM_EMERALD_ESSENCE = 37815, + ITEM_RUBY_ESSENCE = 37860, + SPELL_AMBER_ESSENCE = 49461, + SPELL_EMERALD_ESSENCE = 49345, + SPELL_RUBY_ESSENCE = 49462, + // Abilities: + // Amber + SPELL_SHOCK_LANCE = 49840, + SPELL_SHOCK_CHARGE = 49836, + SPELL_STOP_TIME = 49838, + SPELL_TEMPORAL_RIFT = 49592, + // Emerald + SPELL_LEECHING_POISON = 50328, + SPELL_TOUCH_THE_NIGHTMARE = 50341, + SPELL_DREAM_FUNNEL = 50344, + // Ruby + SPELL_SEARING_WRATH = 50232, + SPELL_EVASIVE_MANEUVERS = 50240, + SPELL_EVASIVE_CHARGES = 50241, + SPELL_MARTYR = 50253, + + // Varos Cloudstrider + NPC_CENTRIFUGE_CORE = 28183, + + // Mage-Lord Urom + NPC_MAGE_LORD_UROM = 27655, + SPELL_TIME_BOMB_N = 51121, + SPELL_TIME_BOMB_H = 59376, + SPELL_EMPOWERED_ARCANE_EXPLOSION_N = 51110, + SPELL_EMPOWERED_ARCANE_EXPLOSION_H = 59377, + + // Ley-Guardian Eregos + SPELL_ENRAGED_ASSAULT = 51170, + SPELL_PLANAR_SHIFT = 51162, +}; + +#define SPELL_EMPOWERED_ARCANE_EXPLOSION DUNGEON_MODE(bot, SPELL_EMPOWERED_ARCANE_EXPLOSION_N, SPELL_EMPOWERED_ARCANE_EXPLOSION_H) +#define SPELL_TIME_BOMB DUNGEON_MODE(bot, SPELL_TIME_BOMB_N, SPELL_TIME_BOMB_H) + +const std::vector DRAKE_ITEMS = {ITEM_AMBER_ESSENCE, ITEM_EMERALD_ESSENCE, ITEM_RUBY_ESSENCE}; +const std::vector DRAKE_SPELLS = {SPELL_AMBER_ESSENCE, SPELL_EMERALD_ESSENCE, SPELL_RUBY_ESSENCE}; +const uint32 OCULUS_MAP_ID = 578; + +// const float uromCoords[4][4] = +// { +// {1177.47f, 937.722f, 527.405f, 2.21657f}, +// {968.66f, 1042.53f, 527.32f, 0.077f}, +// {1164.02f, 1170.85f, 527.321f, 3.66f}, +// {1118.31f, 1080.377f, 508.361f, 4.25f} // Inner ring, actual boss fight +// }; + +class DrakosUnstableSphereTrigger : public Trigger +{ +public: + DrakosUnstableSphereTrigger(PlayerbotAI* ai) : Trigger(ai, "drakos unstable sphere") {} + bool IsActive() override; +}; + +class DrakeMountTrigger : public Trigger +{ +public: + DrakeMountTrigger(PlayerbotAI* ai) : Trigger(ai, "drake mount") {} + bool IsActive() override; +}; + +class DrakeDismountTrigger : public Trigger +{ +public: + DrakeDismountTrigger(PlayerbotAI* ai) : Trigger(ai, "drake dismount") {} + bool IsActive() override; +}; + +class GroupFlyingTrigger : public Trigger +{ +public: + GroupFlyingTrigger(PlayerbotAI* ai) : Trigger(ai, "drake fly") {} + bool IsActive() override; +}; + +class DrakeCombatTrigger : public Trigger +{ +public: + DrakeCombatTrigger(PlayerbotAI* ai) : Trigger(ai, "drake combat") {} + bool IsActive() override; +}; + +class VarosCloudstriderTrigger : public Trigger +{ +public: + VarosCloudstriderTrigger(PlayerbotAI* ai) : Trigger(ai, "varos cloudstrider") {} + bool IsActive() override; +}; + +class UromArcaneExplosionTrigger : public Trigger +{ +public: + UromArcaneExplosionTrigger(PlayerbotAI* ai) : Trigger(ai, "urom arcane explosion") {} + bool IsActive() override; +}; + +class UromTimeBombTrigger : public Trigger +{ +public: + UromTimeBombTrigger(PlayerbotAI* ai) : Trigger(ai, "urom time bomb") {} + bool IsActive() override; +}; + +#endif diff --git a/src/strategy/dungeons/wotlk/oculus/TODO b/src/strategy/dungeons/wotlk/oculus/TODO deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp b/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp index d13fae541..703c7454a 100644 --- a/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp +++ b/src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp @@ -46,10 +46,11 @@ bool AvoidShadowCrashAction::Execute(Event event) // Could check all enemy units in range as it's possible to pull multiple of these mobs. // They should really be killed 1 by 1, multipulls are messy so we just handle singles for now Unit* unit = AI_VALUE2(Unit*, "find target", "forgotten one"); + if (!unit) { return false; } + Unit* victim = nullptr; float radius = 10.0f; float targetDist = radius + 2.0f; - if (!unit) { return false; } // Actively move if targeted by a shadow crash. // Spell check not needed, they don't have any other non-instant casts @@ -58,13 +59,11 @@ bool AvoidShadowCrashAction::Execute(Event event) // This doesn't seem to avoid casts very well, perhaps because this isn't checked while allies are casting. // TODO: Revisit if this is an issue in heroics, otherwise ignore shadow crashes for the most part. victim = botAI->GetUnit(unit->GetTarget()); - if (!victim) - { - return false; // Exit early if no victim is found - } - if (victim && bot->GetExactDist2d(victim) < radius) + float distance = bot->GetExactDist2d(victim->GetPosition()); + + if (victim && distance < radius) { - return MoveAway(victim, targetDist - bot->GetExactDist2d(victim->GetPosition())); + return MoveAway(victim, targetDist - distance); } } @@ -72,21 +71,13 @@ bool AvoidShadowCrashAction::Execute(Event event) if (botAI->IsMelee(bot)) { return false; } GuidVector members = AI_VALUE(GuidVector, "group members"); - if (members.empty()) - { - return false; // Exit early if no group members are found - } for (auto& member : members) { - if (bot->GetGUID() == member) + Unit* unit = botAI->GetUnit(member); + if (!unit || bot->GetGUID() == member) { continue; } - Unit* memberUnit = botAI->GetUnit(member); - if (!memberUnit) - { - continue; // Skip if the memberUnit is null - } float currentDist = bot->GetExactDist2d(botAI->GetUnit(member)); if (currentDist < radius) {