mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Merge pull request #596 from Bobblybook/master
Dragon flanking & Violet Hold implementation
This commit is contained in:
@@ -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());
|
||||
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
99
src/strategy/dungeons/wotlk/violethold/VioletHoldActions.cpp
Normal file
99
src/strategy/dungeons/wotlk/violethold/VioletHoldActions.cpp
Normal 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();
|
||||
}
|
||||
48
src/strategy/dungeons/wotlk/violethold/VioletHoldActions.h
Normal file
48
src/strategy/dungeons/wotlk/violethold/VioletHoldActions.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
18
src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.h
Normal file
18
src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
59
src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.h
Normal file
59
src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.h
Normal 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
|
||||
Reference in New Issue
Block a user