Merge pull request #642 from Bobblybook/master

Oculus implementation
This commit is contained in:
Revision
2024-10-27 02:19:11 +02:00
committed by GitHub
17 changed files with 949 additions and 26 deletions

View File

@@ -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());

View File

@@ -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<Strategy>
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); }

View File

@@ -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"

View File

@@ -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"

View File

@@ -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)
{

View File

@@ -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<Action>
{
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

View File

@@ -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<int32, int32> drakeAssignments;
// Composition can be adjusted - both 3/1/1 and 2/2/1 are good default comps
// {Amber, Emerald, Ruby}
std::vector<uint8> composition = {2, 2, 1};
// std::vector<uint8> 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;
}

View File

@@ -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

View File

@@ -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<MountDrakeAction*>(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<MovementAction*>(action) && !dynamic_cast<FlyDrakeAction*>(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<MovementAction*>(action) && !dynamic_cast<AvoidArcaneExplosionAction*>(action))
{
return 0.0f;
}
}
// Don't bother avoiding Frostbomb for melee
if (botAI->IsMelee(bot))
{
if (dynamic_cast<AvoidAoeAction*>(action))
{
return 0.0f;
}
}
if (bot->HasAura(SPELL_TIME_BOMB))
{
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<TimeBombSpreadAction*>(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<DrakeAttackAction*>(action)))
{
return 0.0f;
}
return 1.0f;
}

View File

@@ -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

View File

@@ -0,0 +1,42 @@
#include "OculusStrategy.h"
#include "OculusMultipliers.h"
void WotlkDungeonOccStrategy::InitTriggers(std::vector<TriggerNode*> &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<Multiplier*> &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));
}

View File

@@ -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<TriggerNode*> &triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -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<Trigger>
{
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

View File

@@ -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);
}

View File

@@ -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<uint32> DRAKE_ITEMS = {ITEM_AMBER_ESSENCE, ITEM_EMERALD_ESSENCE, ITEM_RUBY_ESSENCE};
const std::vector<uint32> 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

View File

@@ -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)
{