Nexus implementation & dragon flank code

This commit is contained in:
Bobblybook
2024-10-06 00:24:45 +10:00
parent 23ff699724
commit 45303d55c1
14 changed files with 720 additions and 49 deletions

View File

@@ -51,6 +51,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext());
actionContexts.Add(new WotlkDungeonNexActionContext());
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
@@ -62,6 +63,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext());
triggerContexts.Add(new WotlkDungeonNexTriggerContext());
valueContexts.Add(new ValueContext());

View File

@@ -3,12 +3,11 @@
#include "Strategy.h"
#include "wotlk/utgardekeep/UtgardeKeepStrategy.h"
#include "wotlk/nexus/NexusStrategy.h"
/*
Full list/TODO:
The Nexus - Nex
Grand Magus Telestra, Anomalus, Ormorok the Tree-Shaper, Keristrasza, Commander Stoutbeard (Horde Heroic Only)/Commander Kolurg (Alliance Heroic Only)
Azjol-Nerub: Azjol-Nerub - AN
Krik'thir the Gatewatcher, Hadronox, Anub'arak
Ahn'kahet: The Old Kingdom - OK
@@ -76,7 +75,7 @@ class DungeonStrategyContext : public NamedObjectContext<Strategy>
}
private:
static Strategy* wotlk_uk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonNexStrategy(botAI); }
static Strategy* wotlk_an(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }

View File

@@ -2,7 +2,7 @@
#define _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H
#include "utgardekeep/UtgardeKeepActionContext.h"
// #include "nexus/NexusActionContext.h"
#include "nexus/NexusActionContext.h"
// #include "azjolnerub/AzjolNerubActionContext.h"
// #include "oldkingdom/OldKingdomActionContext.h"
// #include "draktharonkeep/DraktharonKeepActionContext.h"

View File

@@ -2,7 +2,7 @@
#define _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H
#include "utgardekeep/UtgardeKeepTriggerContext.h"
// #include "nexus/NexusTriggerContext.h"
#include "nexus/NexusTriggerContext.h"
// #include "azjolnerub/AzjolNerubTriggerContext.h"
// #include "oldkingdom/OldKingdomTriggerContext.h"
// #include "draktharonkeep/DraktharonKeepTriggerContext.h"

View File

@@ -0,0 +1,30 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONNEXACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "NexusActions.h"
class WotlkDungeonNexActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonNexActionContext() {
creators["move from whirlwind"] = &WotlkDungeonNexActionContext::move_from_whirlwind;
creators["firebomb spread"] = &WotlkDungeonNexActionContext::firebomb_spread;
creators["telestra split target"] = &WotlkDungeonNexActionContext::telestra_split_target;
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); }
static Action* firebomb_spread(PlayerbotAI* ai) { return new FirebombSpreadAction(ai); }
static Action* telestra_split_target(PlayerbotAI* ai) { return new TelestraSplitTargetAction(ai); }
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

@@ -0,0 +1,206 @@
#include "Playerbots.h"
#include "NexusActions.h"
#include "NexusStrategy.h"
bool MoveFromWhirlwindAction::Execute(Event event)
{
Unit* boss = nullptr;
uint8 faction = bot->GetTeamId();
float targetDist = 10.0f; // Whirlwind has range of 8, add a couple for safety buffer
switch (bot->GetMap()->GetDifficulty())
{
case DUNGEON_DIFFICULTY_NORMAL:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "horde commander");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
}
break;
case DUNGEON_DIFFICULTY_HEROIC:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
}
break;
default:
break;
}
if (!boss || bot->GetExactDist2d(boss->GetPosition()) > targetDist)
{
return false;
}
return MoveAway(boss, targetDist - bot->GetExactDist2d(boss->GetPosition()));
}
bool FirebombSpreadAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
float radius = 5.0f;
float targetDist = radius + 1.0f;
if (!boss) { return false; }
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
if (bot->GetGUID() == member)
{
continue;
}
if (bot->GetExactDist2d(botAI->GetUnit(member)) < targetDist)
{
return MoveAway(botAI->GetUnit(member), targetDist);
}
}
return false;
}
bool TelestraSplitTargetAction::isUseful() { return !botAI->IsHeal(bot); }
bool TelestraSplitTargetAction::Execute(Event event)
{
GuidVector attackers = AI_VALUE(GuidVector, "attackers");
Unit* splitTargets[3] = {nullptr, nullptr, nullptr};
for (auto& attacker : attackers)
{
Unit* npc = botAI->GetUnit(attacker);
if (!npc)
{
continue;
}
switch (npc->GetEntry())
{
// Focus arcane clone first
case NPC_ARCANE_MAGUS:
splitTargets[0] = npc;
break;
// Then the frost clone
case NPC_FROST_MAGUS:
splitTargets[1] = npc;
break;
// Fire clone last
case NPC_FIRE_MAGUS:
splitTargets[2] = npc;
break;
}
}
for (Unit* target : splitTargets)
{
// Attack the first valid split target in the priority list
if (target)
{
if (AI_VALUE(Unit*, "current target") != target)
{
return Attack(target);
}
// Don't continue loop here, the target exists so we don't
// want to move down the prio list. We just don't need to send attack
// command again, just return false and exit the loop that way
return false;
}
}
return false;
}
bool ChaoticRiftTargetAction::isUseful() { return !botAI->IsHeal(bot); }
bool ChaoticRiftTargetAction::Execute(Event event)
{
Unit* chaoticRift = nullptr;
// 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");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetName() == "Chaotic Rift")
{
chaoticRift = unit;
break;
}
}
if (!chaoticRift || AI_VALUE(Unit*, "current target") == chaoticRift)
{
return false;
}
return Attack(chaoticRift);
}
bool DodgeSpikesAction::isUseful()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
return bot->GetExactDist2d(boss) > 0.5f;
}
bool DodgeSpikesAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
return Move(bot->GetAngle(boss), bot->GetExactDist2d(boss) - 0.3f);
}
bool IntenseColdJumpAction::Execute(Event event)
{
// This needs improving but maybe it should be done in the playerbot core.
// Jump doesn't seem to support zero offset (eg. jump on the spot) so need to add a tiny delta.
// This does a tiny bunnyhop that takes a couple of ms, it doesn't do a natural jump.
// Adding extra Z offset causes floating, and appears to scale the jump speed based on Z difference.
// Probably best to revisit once bot movement is improved
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 subtracing 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.
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

@@ -0,0 +1,79 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONNEXACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "NexusTriggers.h"
#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)
// Slice of the circle that we want melee dps to attack from.
// Measured from boss orientation, on one side.
// Even though the breath cone is not the full 180 degrees,
// avoid melee dps from the front due to parry potential.
// Bots should learn good dps etiquette :)
#define DRAGON_MELEE_MIN_ANGLE ANGLE_90_DEG
// This leaves a danger zone of 60 degrees at the tail end on both sides.
// This is a total of 120 degrees tail arc that bots will avoid -
// number just happens to be the same in this case, but this is always measured from the front.
#define DRAGON_MELEE_MAX_ANGLE ANGLE_120_DEG
class MoveFromWhirlwindAction : public MovementAction
{
public:
MoveFromWhirlwindAction(PlayerbotAI* ai) : MovementAction(ai, "move from whirlwind") {}
bool Execute(Event event) override;
};
class FirebombSpreadAction : public MovementAction
{
public:
FirebombSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "firebomb spread") {}
bool Execute(Event event) override;
};
class TelestraSplitTargetAction : public AttackAction
{
public:
TelestraSplitTargetAction(PlayerbotAI* ai) : AttackAction(ai, "telestra split target") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class ChaoticRiftTargetAction : public AttackAction
{
public:
ChaoticRiftTargetAction(PlayerbotAI* ai) : AttackAction(ai, "chaotic rift target") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class DodgeSpikesAction : public MovementAction
{
public:
DodgeSpikesAction(PlayerbotAI* ai) : MovementAction(ai, "dodge spikes") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class IntenseColdJumpAction : public MovementAction
{
public:
IntenseColdJumpAction(PlayerbotAI* ai) : MovementAction(ai, "intense cold jump") {}
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

@@ -0,0 +1,99 @@
#include "NexusMultipliers.h"
#include "NexusActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "NexusTriggers.h"
float FactionCommanderMultiplier::GetValue(Action* action)
{
Unit* boss = nullptr;
uint8 faction = bot->GetTeamId();
switch (bot->GetMap()->GetDifficulty())
{
case DUNGEON_DIFFICULTY_NORMAL:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "horde commander");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
}
break;
case DUNGEON_DIFFICULTY_HEROIC:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
}
break;
default:
break;
}
if (boss && boss->HasUnitState(UNIT_STATE_CASTING) &&
boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND))
{
// Prevent movement actions other than flee during a whirlwind, to prevent running back in early.
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<MoveFromWhirlwindAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float TelestraMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
if (boss && boss->GetEntry() != NPC_TELESTRA)
{
// boss is split into clones, do not auto acquire target
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float AnomalusMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anomalus");
if (boss && boss->HasAura(BUFF_RIFT_SHIELD))
{
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float OrmorokMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
if (!boss)
{
return 1.0f;
}
// These are used for auto ranged repositioning, need to suppress so ranged dps don't ping-pong
if (dynamic_cast<FleeAction*>(action))
{
return 0.0f;
}
// This boss is annoying and shuffles around a lot. Don't let tank move once fight has started.
// Extra checks are to allow the tank to close distance and engage the boss initially
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<DodgeSpikesAction*>(action)
&& botAI->IsTank(bot) && bot->IsWithinMeleeRange(boss)
&& AI_VALUE2(bool, "facing", "current target"))
{
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,51 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONNEXMULTIPLIERS_H
#include "Multiplier.h"
class FactionCommanderMultiplier : public Multiplier
{
public:
FactionCommanderMultiplier(PlayerbotAI* ai) : Multiplier(ai, "faction commander") {}
public:
virtual float GetValue(Action* action);
};
class TelestraMultiplier : public Multiplier
{
public:
TelestraMultiplier(PlayerbotAI* ai) : Multiplier(ai, "grand magus telestra") {}
public:
virtual float GetValue(Action* action);
};
class AnomalusMultiplier : public Multiplier
{
public:
AnomalusMultiplier(PlayerbotAI* ai) : Multiplier(ai, "anomalus") {}
public:
virtual float GetValue(Action* action);
};
class OrmorokMultiplier : public Multiplier
{
public:
OrmorokMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ormorok the tree-shaper") {}
public:
virtual float GetValue(Action* action);
};
class KeristraszaMultiplier : public Multiplier
{
public:
KeristraszaMultiplier(PlayerbotAI* ai) : Multiplier(ai, "keristrasza") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,54 @@
#include "NexusStrategy.h"
#include "NexusMultipliers.h"
void WotlkDungeonNexStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Horde Commander (Alliance N)/Commander Kolurg (Alliance H)
// or
// Alliance Commander (Horde N)/Commander Stoutbeard (Horde H)
triggers.push_back(new TriggerNode("faction commander whirlwind",
NextAction::array(0, new NextAction("move from whirlwind", ACTION_MOVE + 5), nullptr)));
// TODO: Handle fear? (tremor totems, fear ward etc.)
// Grand Magus Telestra
triggers.push_back(new TriggerNode("telestra firebomb",
NextAction::array(0, new NextAction("firebomb spread", ACTION_MOVE + 5), nullptr)));
triggers.push_back(new TriggerNode("telestra split phase",
NextAction::array(0, new NextAction("telestra split target", ACTION_RAID + 1), nullptr)));
// TODO: Add priority interrupt on the frost split's Blizzard casts
// Anomalus
triggers.push_back(new TriggerNode("chaotic rift",
NextAction::array(0, new NextAction("chaotic rift target", ACTION_RAID + 1), nullptr)));
// Ormorok the Tree-Shaper
// Tank trigger to stack inside boss. Can also add return action to prevent boss repositioning
// if it becomes too much of a problem. He usually dies before he's up against a wall though
triggers.push_back(new TriggerNode("ormorok spikes",
NextAction::array(0, new NextAction("dodge spikes", ACTION_MOVE + 5), nullptr)));
// Non-tank trigger to stack. Avoiding the spikes at range is.. harder than it seems.
// TODO: This turns hunters into melee marshmallows, have not come up with a better solution yet
triggers.push_back(new TriggerNode("ormorok stack",
NextAction::array(0, new NextAction("dodge spikes", ACTION_MOVE + 5), nullptr)));
// TODO: Add handling for spell reflect... best to spam low level/weak spells but don't want
// to hardcode spells per class, might be difficult to dynamically generate this.
// Will revisit if I find my altbots killing themselves in heroic, just heal through it for now
// 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)));
// TODO: Add frost resist aura for paladins?
}
void WotlkDungeonNexStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new FactionCommanderMultiplier(botAI));
multipliers.push_back(new TelestraMultiplier(botAI));
multipliers.push_back(new AnomalusMultiplier(botAI));
multipliers.push_back(new OrmorokMultiplier(botAI));
// multipliers.push_back(new KeristraszaMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXSTRATEGY_H
#define _PLAYERBOT_WOTLKDUNGEONNEXSTRATEGY_H
#include "Multiplier.h"
#include "AiObjectContext.h"
#include "Strategy.h"
class WotlkDungeonNexStrategy : public Strategy
{
public:
WotlkDungeonNexStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "nexus"; }
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_WOTLKDUNGEONNEXTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "NexusTriggers.h"
class WotlkDungeonNexTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonNexTriggerContext()
{
creators["faction commander whirlwind"] = &WotlkDungeonNexTriggerContext::faction_commander_whirlwind;
creators["telestra firebomb"] = &WotlkDungeonNexTriggerContext::telestra_firebomb;
creators["telestra split phase"] = &WotlkDungeonNexTriggerContext::telestra_split_phase;
creators["chaotic rift"] = &WotlkDungeonNexTriggerContext::chaotic_rift;
creators["ormorok spikes"] = &WotlkDungeonNexTriggerContext::ormorok_spikes;
creators["ormorok stack"] = &WotlkDungeonNexTriggerContext::ormorok_stack;
creators["intense cold"] = &WotlkDungeonNexTriggerContext::intense_cold;
creators["dragon positioning"] = &WotlkDungeonNexTriggerContext::dragon_positioning;
}
private:
static Trigger* faction_commander_whirlwind(PlayerbotAI* ai) { return new FactionCommanderWhirlwindTrigger(ai); }
static Trigger* telestra_firebomb(PlayerbotAI* ai) { return new TelestraFirebombTrigger(ai); }
static Trigger* telestra_split_phase(PlayerbotAI* ai) { return new TelestraSplitPhaseTrigger(ai); }
static Trigger* chaotic_rift(PlayerbotAI* ai) { return new ChaoticRiftTrigger(ai); }
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); }
};
#endif

View File

@@ -0,0 +1,105 @@
#include "Playerbots.h"
#include "NexusTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool FactionCommanderWhirlwindTrigger::IsActive()
{
Unit* boss = nullptr;
uint8 faction = bot->GetTeamId();
switch (bot->GetMap()->GetDifficulty())
{
case DUNGEON_DIFFICULTY_NORMAL:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "horde commander");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
}
break;
case DUNGEON_DIFFICULTY_HEROIC:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
}
break;
default:
break;
}
if (boss && boss->HasUnitState(UNIT_STATE_CASTING))
{
if (boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND))
{
return true;
}
}
return false;
}
bool TelestraFirebombTrigger::IsActive()
{
if (botAI->IsMelee(bot)) { return false; }
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
// Avoid split phase with the fake Telestra units, only match the true boss id
return boss && boss->GetEntry() == NPC_TELESTRA;
}
bool TelestraSplitPhaseTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
// Only match split phase with the fake Telestra units
return boss && boss->GetEntry() != NPC_TELESTRA;
}
bool ChaoticRiftTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anomalus");
return boss && boss->HasAura(BUFF_RIFT_SHIELD);
}
bool OrmorokSpikesTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
if (!boss || !botAI->IsTank(bot)) { return false; }
GuidVector objects = AI_VALUE(GuidVector, "closest game objects");
for (auto i = objects.begin(); i != objects.end(); ++i)
{
GameObject* go = botAI->GetGameObject(*i);
if (go && go->GetEntry() == GO_CRYSTAL_SPIKE)
{
return true;
}
}
return false;
}
bool OrmorokStackTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
return (boss && !botAI->IsTank(bot));
}
bool IntenseColdTrigger::IsActive()
{
// Adjust as needed - too much interrupting loses dps time,
// but too many stacks is deadly. Assuming 3-5 is a good number to clear
int stackThreshold = 5;
Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza");
return boss && botAI->GetAura("intense cold", bot, false, false, stackThreshold);
}
bool DragonPositioningTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza");
return boss && botAI->IsMelee(bot) && !botAI->IsTank(bot);
}

View File

@@ -1,93 +1,88 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERS_H
#include "EventMap.h"
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
// Taken from:
// src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp
enum Spells
enum NexusIDs
{
SPELL_SUMMON_VALKYR = 42912,
SPELL_RESURRECTION_BEAM = 42857,
SPELL_RESURRECTION_BALL = 42862,
SPELL_RESURRECTION_HEAL = 42704,
SPELL_INGVAR_TRANSFORM = 42796,
// Faction Commander
NPC_ALLIANCE_COMMANDER = 27949,
NPC_HORDE_COMMANDER = 27947,
NPC_COMMANDER_STOUTBEARD = 26796,
NPC_COMMANDER_KOLURG = 26798,
// SPELL_FRIGHTENING_SHOUT = 19134,
SPELL_WHIRLWIND = 38618,
SPELL_STAGGERING_ROAR_N = 42708,
SPELL_STAGGERING_ROAR_H = 59708,
SPELL_CLEAVE = 42724,
SPELL_SMASH_N = 42669,
SPELL_SMASH_H = 59706,
SPELL_ENRAGE_N = 42705,
SPELL_ENRAGE_H = 59707,
// Grand Magus Telestra
NPC_TELESTRA = 26731,
NPC_FIRE_MAGUS = 26928,
NPC_FROST_MAGUS = 26930,
NPC_ARCANE_MAGUS = 26929,
SPELL_DREADFUL_ROAR_N = 42729,
SPELL_DREADFUL_ROAR_H = 59734,
SPELL_WOE_STRIKE_N = 42730,
SPELL_WOE_STRIKE_H = 59735,
SPELL_DARK_SMASH = 42723,
SPELL_SHADOW_AXE = 42749,
// Anomalus
BUFF_RIFT_SHIELD = 47748,
// Added
DEBUFF_FROST_TOMB = 48400,
// Ormorok the Tree Shaper
// NPC_CRYSTAL_SPIKE = 27099,
GO_CRYSTAL_SPIKE = 188537,
};
#define SPELL_STAGGERING_ROAR DUNGEON_MODE(bot, SPELL_STAGGERING_ROAR_N, SPELL_STAGGERING_ROAR_H)
#define SPELL_DREADFUL_ROAR DUNGEON_MODE(bot, SPELL_DREADFUL_ROAR_N, SPELL_DREADFUL_ROAR_H)
#define SPELL_WOE_STRIKE DUNGEON_MODE(bot, SPELL_WOE_STRIKE_N, SPELL_WOE_STRIKE_H)
#define SPELL_SMASH DUNGEON_MODE(bot, SPELL_SMASH_N, SPELL_SMASH_H)
#define SPELL_ENRAGE DUNGEON_MODE(bot, SPELL_ENRAGE_N, SPELL_ENRAGE_H)
class KelesethFrostTombTrigger : public Trigger
class FactionCommanderWhirlwindTrigger : public Trigger
{
public:
KelesethFrostTombTrigger(PlayerbotAI* ai) : Trigger(ai, "keleseth frost tomb") {}
FactionCommanderWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "faction commander whirlwind") {}
bool IsActive() override;
};
class DalronnNontankTrigger : public Trigger
class TelestraFirebombTrigger : public Trigger
{
public:
DalronnNontankTrigger(PlayerbotAI* ai) : Trigger(ai, "dalronn non-tank") {}
TelestraFirebombTrigger(PlayerbotAI* ai) : Trigger(ai, "telestra firebomb spread") {}
bool IsActive() override;
};
class IngvarStaggeringRoarTrigger : public Trigger
class TelestraSplitPhaseTrigger : public Trigger
{
public:
IngvarStaggeringRoarTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar staggering roar") {}
TelestraSplitPhaseTrigger(PlayerbotAI* ai) : Trigger(ai, "telestra split phase") {}
bool IsActive() override;
};
class IngvarDreadfulRoarTrigger : public Trigger
class ChaoticRiftTrigger : public Trigger
{
public:
IngvarDreadfulRoarTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar dreadful roar") {}
ChaoticRiftTrigger(PlayerbotAI* ai) : Trigger(ai, "chaotic rift") {}
bool IsActive() override;
};
class IngvarSmashTankTrigger : public Trigger
class OrmorokSpikesTrigger : public Trigger
{
public:
IngvarSmashTankTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar smash tank") {}
OrmorokSpikesTrigger(PlayerbotAI* ai) : Trigger(ai, "ormorok spikes") {}
bool IsActive() override;
};
class IngvarSmashTankReturnTrigger : public Trigger
class OrmorokStackTrigger : public Trigger
{
public:
IngvarSmashTankReturnTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar smash tank return") {}
OrmorokStackTrigger(PlayerbotAI* ai) : Trigger(ai, "ormorok stack") {}
bool IsActive() override;
};
class NotBehindIngvarTrigger : public Trigger
class IntenseColdTrigger : public Trigger
{
public:
NotBehindIngvarTrigger(PlayerbotAI* ai) : Trigger(ai, "not behind ingvar") {}
IntenseColdTrigger(PlayerbotAI* ai) : Trigger(ai, "intense cold") {}
bool IsActive() override;
};
class DragonPositioningTrigger : public Trigger
{
public:
DragonPositioningTrigger(PlayerbotAI* ai) : Trigger(ai, "dragon positioning") {}
bool IsActive() override;
};