Merge pull request #596 from Bobblybook/master

Dragon flanking & Violet Hold implementation
This commit is contained in:
Yunfan Li
2024-10-12 23:44:49 +08:00
committed by GitHub
27 changed files with 531 additions and 73 deletions

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,9 @@ class Unit;
class WorldObject;
class Position;
#define ANGLE_45_DEG (static_cast<float>(M_PI) / 4.f)
#define ANGLE_90_DEG M_PI_2
#define ANGLE_120_DEG (2.f * static_cast<float>(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

View File

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

View File

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

View File

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

View File

@@ -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<DpsAssistAction*>(action))
// TODO: TEST AOE Avoid
if (dynamic_cast<DpsAssistAction*>(action)
|| dynamic_cast<DpsAoeAction*>(action))
{
return 0.0f;
}

View File

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

View File

@@ -15,7 +15,6 @@ class WotlkDungeonNexActionContext : public NamedObjectContext<Action>
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<Action>
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

View File

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

View File

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

View File

@@ -38,9 +38,9 @@ void WotlkDungeonNexStrategy::InitTriggers(std::vector<TriggerNode*> &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?
}

View File

@@ -17,7 +17,7 @@ class WotlkDungeonNexTriggerContext : public NamedObjectContext<Trigger>
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<Trigger>
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<DpsAssistAction*>(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<DpsAssistAction*>(action)
|| dynamic_cast<TankAssistAction*>(action)
|| dynamic_cast<DropTargetAction*>(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<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action))
{
return 0.0f;
}
}
if (boss->HasAura(SPELL_SHROUD_OF_DARKNESS) && dynamic_cast<AttackAction*>(action))
{
return 0.0f;
}
return 1.0f;
}

View File

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

View File

@@ -0,0 +1,41 @@
#include "VioletHoldStrategy.h"
#include "VioletHoldMultipliers.h"
void WotlkDungeonVHStrategy::InitTriggers(std::vector<TriggerNode*> &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<Multiplier*> &multipliers)
{
multipliers.push_back(new ErekemMultiplier(botAI));
multipliers.push_back(new IchoronMultiplier(botAI));
multipliers.push_back(new ZuramatMultiplier(botAI));
}

View File

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

View File

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

View File

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

View File

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