Azjol-Nerub implementation

This commit is contained in:
Bobblybook
2024-10-07 01:32:00 +11:00
parent 4021387387
commit b895ac17e5
15 changed files with 448 additions and 5 deletions

View File

@@ -52,6 +52,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext());
actionContexts.Add(new WotlkDungeonNexActionContext());
actionContexts.Add(new WotlkDungeonANActionContext());
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
@@ -64,6 +65,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext());
triggerContexts.Add(new WotlkDungeonNexTriggerContext());
triggerContexts.Add(new WotlkDungeonANTriggerContext());
valueContexts.Add(new ValueContext());

View File

@@ -4,12 +4,12 @@
#include "Strategy.h"
#include "wotlk/utgardekeep/UtgardeKeepStrategy.h"
#include "wotlk/nexus/NexusStrategy.h"
#include "wotlk/azjolnerub/AzjolNerubStrategy.h"
/*
Full list/TODO:
Azjol-Nerub: Azjol-Nerub - AN
Krik'thir the Gatewatcher, Hadronox, Anub'arak
Ahn'kahet: The Old Kingdom - OK
Elder Nadox, Prince Taldaram, Jedoga Shadowseeker, Herald Volazj, Amanitar (Heroic Only)
Drak'Tharon Keep - DTK
@@ -76,7 +76,8 @@ class DungeonStrategyContext : public NamedObjectContext<Strategy>
private:
static Strategy* wotlk_uk(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_an(PlayerbotAI* botAI) { return new WotlkDungeonANStrategy(botAI); }
static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }

View File

@@ -3,7 +3,7 @@
#include "utgardekeep/UtgardeKeepActionContext.h"
#include "nexus/NexusActionContext.h"
// #include "azjolnerub/AzjolNerubActionContext.h"
#include "azjolnerub/AzjolNerubActionContext.h"
// #include "oldkingdom/OldKingdomActionContext.h"
// #include "draktharonkeep/DraktharonKeepActionContext.h"
// #include "violethold/VioletHoldActionContext.h"

View File

@@ -3,7 +3,7 @@
#include "utgardekeep/UtgardeKeepTriggerContext.h"
#include "nexus/NexusTriggerContext.h"
// #include "azjolnerub/AzjolNerubTriggerContext.h"
#include "azjolnerub/AzjolNerubTriggerContext.h"
// #include "oldkingdom/OldKingdomTriggerContext.h"
// #include "draktharonkeep/DraktharonKeepTriggerContext.h"
// #include "violethold/VioletHoldTriggerContext.h"

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONANACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "AzjolNerubActions.h"
class WotlkDungeonANActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonANActionContext() {
creators["attack web wrap"] = &WotlkDungeonANActionContext::attack_web_wrap;
creators["krik'thir priority"] = &WotlkDungeonANActionContext::krikthir_priority;
creators["dodge pound"] = &WotlkDungeonANActionContext::dodge_pound;
}
private:
static Action* attack_web_wrap(PlayerbotAI* ai) { return new AttackWebWrapAction(ai); }
static Action* krikthir_priority(PlayerbotAI* ai) { return new WatchersTargetAction(ai); }
static Action* dodge_pound(PlayerbotAI* ai) { return new AnubarakDodgePoundAction(ai); }
};
#endif

View File

@@ -0,0 +1,111 @@
#include "Playerbots.h"
#include "AzjolNerubActions.h"
#include "AzjolNerubStrategy.h"
bool AttackWebWrapAction::isUseful() { return !botAI->IsHeal(bot); }
bool AttackWebWrapAction::Execute(Event event)
{
Unit* webWrap = 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->GetEntry() == NPC_WEB_WRAP)
{
webWrap = unit;
break;
}
}
if (!webWrap || AI_VALUE(Unit*, "current target") == webWrap)
{
return false;
}
bot->Yell("ATTACKING WRAP", LANG_UNIVERSAL);
return Attack(webWrap);
}
bool WatchersTargetAction::isUseful() { return !botAI->IsHeal(bot); }
bool WatchersTargetAction::Execute(Event event)
{
// Always prioritise web wraps
Unit* currTarget = AI_VALUE(Unit*, "current target");
if (currTarget && currTarget->GetEntry() == NPC_WEB_WRAP) { return false; }
// Do not search all units in range!
// There are many adds we don't want to aggro in close proximity,
// only check in-combat adds now.
GuidVector attackers = AI_VALUE(GuidVector, "attackers");
Unit* priorityTargets[4] = {nullptr, nullptr, nullptr, nullptr};
for (auto& attacker : attackers)
{
Unit* npc = botAI->GetUnit(attacker);
if (!npc)
{
continue;
}
switch (npc->GetEntry())
{
// Focus skirmishers first
case NPC_WATCHER_SKIRMISHER:
priorityTargets[0] = npc;
break;
// Then shadowcaster. This doesn't work so well for the shadowcaster
// + skirmisher pack - ideally we would kill the watcher second.
// But don't want to make this unnecessarily complex and rigid...
// Will revisit if this causes problems in heroic.
case NPC_WATCHER_SHADOWCASTER:
priorityTargets[1] = npc;
break;
// Named watcher next
case NPC_WATCHER_SILTHIK:
case NPC_WATCHER_GASHRA:
case NPC_WATCHER_NARJIL:
priorityTargets[2] = npc;
break;
// Warrior last
case NPC_WATCHER_WARRIOR:
priorityTargets[3] = npc;
break;
}
}
for (Unit* target : priorityTargets)
{
// Attack the first valid split target in the priority list
if (target)
{
if (currTarget != target)
{
// bot->Yell("ATTACKING "+target->GetName(), LANG_UNIVERSAL);
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 AnubarakDodgePoundAction::isUseful() { return !AI_VALUE2(bool, "behind", "current target"); }
bool AnubarakDodgePoundAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'arak");
if (!boss) { return false; }
float distance = bot->GetExactDist2d(boss->GetPosition());
// Extra units to move into the boss, instead of being just 1 pixel past his midpoint.
// Can be adjusted - this value tends to mirror how a human would play,
// and visibly ensures you won't get hit while not creating excessive movements.
float distanceExtra = 2.0f;
bot->Yell("MOVING", LANG_UNIVERSAL);
return Move(bot->GetAngle(boss), distance + distanceExtra);
}

View File

@@ -0,0 +1,34 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONANACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "AzjolNerubTriggers.h"
class AttackWebWrapAction : public AttackAction
{
public:
AttackWebWrapAction(PlayerbotAI* ai) : AttackAction(ai, "attack web wrap") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class WatchersTargetAction : public AttackAction
{
public:
WatchersTargetAction(PlayerbotAI* ai) : AttackAction(ai, "krik'thir priority") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class AnubarakDodgePoundAction : public AttackAction
{
public:
AnubarakDodgePoundAction(PlayerbotAI* ai) : AttackAction(ai, "anub'arak dodge pound") {}
bool Execute(Event event) override;
bool isUseful() override;
};
#endif

View File

@@ -0,0 +1,54 @@
#include "AzjolNerubMultipliers.h"
#include "AzjolNerubActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "AzjolNerubTriggers.h"
#include "Action.h"
float KrikthirMultiplier::GetValue(Action* action)
{
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
Unit* boss = nullptr;
Unit* watcher = nullptr;
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) { continue; }
switch (unit->GetEntry())
{
case NPC_KRIKTHIR:
boss = unit;
continue;
case NPC_WATCHER_SILTHIK:
case NPC_WATCHER_GASHRA:
case NPC_WATCHER_NARJIL:
case NPC_WATCHER_SKIRMISHER:
case NPC_WATCHER_SHADOWCASTER:
case NPC_WATCHER_WARRIOR:
watcher = unit;
continue;
}
}
if (boss && watcher)
{
// Do not target swap
// TODO: Need to suppress AoE actions but unsure how to identify them
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
// Doesn't seem to work
// if (action->getThreatType() == Action::ActionThreatType::Aoe)
// {
// bot->Yell("Suppressed AoE", LANG_UNIVERSAL);
// return 0.0f;
// }
}
return 1.0f;
}

View File

@@ -0,0 +1,15 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONANMULTIPLIERS_H
#include "Multiplier.h"
class KrikthirMultiplier : public Multiplier
{
public:
KrikthirMultiplier(PlayerbotAI* ai) : Multiplier(ai, "krik'thir the gatewatcher") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,31 @@
#include "AzjolNerubStrategy.h"
#include "AzjolNerubMultipliers.h"
void WotlkDungeonANStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Krik'thir the Gatewatcher
// TODO: Add CC trigger while web wraps are casting?
// TODO: Bring healer closer than ranged dps to avoid fixates?
triggers.push_back(new TriggerNode("krik'thir web wrap",
NextAction::array(0, new NextAction("attack web wrap", ACTION_RAID + 5), nullptr)));
triggers.push_back(new TriggerNode("krik'thir watchers",
NextAction::array(0, new NextAction("krik'thir priority", ACTION_RAID + 4), nullptr)));
// Hadronox
// The core AC triggers are very buggy with this boss, but default strat seems to play correctly
//Anub'arak
// TODO: No clear way to track these spikes. They don't seem to appear as gameobjects or triggers,
// and cast time is instant so no way to check currently casting location.
// May need to hook boss AI.. might be able to just heal through it for now.
// triggers.push_back(new TriggerNode("anub'arak impale",
// NextAction::array(0, new NextAction("TODO", ACTION_MOVE + 5), nullptr)));
triggers.push_back(new TriggerNode("anub'arak pound",
NextAction::array(0, new NextAction("dodge pound", ACTION_MOVE + 5), nullptr)));
}
void WotlkDungeonANStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new KrikthirMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANSTRATEGY_H
#define _PLAYERBOT_WOTLKDUNGEONANSTRATEGY_H
#include "Multiplier.h"
#include "AiObjectContext.h"
#include "Strategy.h"
class WotlkDungeonANStrategy : public Strategy
{
public:
WotlkDungeonANStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "azjol-nerub"; }
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -0,0 +1,25 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONANTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "AzjolNerubTriggers.h"
class WotlkDungeonANTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonANTriggerContext()
{
creators["krik'thir web wrap"] = &WotlkDungeonANTriggerContext::krikthir_web_wrap;
creators["krik'thir watchers"] = &WotlkDungeonANTriggerContext::krikthir_watchers;
// creators["anub'arak impale"] = &WotlkDungeonANTriggerContext::anubarak_impale;
creators["anub'arak pound"] = &WotlkDungeonANTriggerContext::anubarak_pound;
}
private:
static Trigger* krikthir_web_wrap(PlayerbotAI* ai) { return new KrikthirWebWrapTrigger(ai); }
static Trigger* krikthir_watchers(PlayerbotAI* ai) { return new KrikthirWatchersTrigger(ai); }
// static Trigger* anubarak_impale(PlayerbotAI* ai) { return new AnubarakImpaleTrigger(ai); }
static Trigger* anubarak_pound(PlayerbotAI* ai) { return new AnubarakPoundTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,69 @@
#include "Playerbots.h"
#include "AzjolNerubTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool KrikthirWebWrapTrigger::IsActive()
{
if (!botAI->IsDps(bot)) { 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");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_WEB_WRAP)
{
return true;
}
}
return false;
}
bool KrikthirWatchersTrigger::IsActive()
{
if (!botAI->IsDps(bot)) { 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");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_KRIKTHIR)
{
return true;
}
}
return false;
}
// bool AnubarakImpaleTrigger::IsActive()
// {
// Unit* boss = AI_VALUE2(Unit*, "find target", "anub'arak");
// if (!boss) { return false; }
// GuidVector triggers = AI_VALUE(GuidVector, "possible triggers");
// for (auto i = triggers.begin(); i != triggers.end(); i++)
// {
// Unit* unit = botAI->GetUnit(*i);
// if (unit)
// {
// bot->Yell("TRIGGER="+unit->GetName(), LANG_UNIVERSAL);
// }
// }
// return false;
// }
bool AnubarakPoundTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'arak");
if (!boss) { return false; }
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_POUND);
}

View File

@@ -0,0 +1,61 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONANTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
enum AzjolNerubIDs
{
// Krik'thir the Gatewatcher
NPC_KRIKTHIR = 28684,
NPC_WATCHER_SILTHIK = 28731,
NPC_WATCHER_GASHRA = 28730,
NPC_WATCHER_NARJIL = 28729,
NPC_WATCHER_SKIRMISHER = 28734,
NPC_WATCHER_SHADOWCASTER = 28733,
NPC_WATCHER_WARRIOR = 28732,
DEBUFF_WEB_WRAP = 52086,
NPC_WEB_WRAP = 28619,
// Anub'arak
// Not sure how to track this - first one is cast as a buff on himself,
// which triggers periodic casts of the spikes spell.
SPELL_IMPALE_PERIODIC = 53456,
SPELL_IMPALE_SPIKES = 53457,
SPELL_POUND_N = 53472,
SPELL_POUND_H = 59433,
};
#define SPELL_POUND DUNGEON_MODE(bot, SPELL_POUND_N, SPELL_POUND_H)
class KrikthirWebWrapTrigger : public Trigger
{
public:
KrikthirWebWrapTrigger(PlayerbotAI* ai) : Trigger(ai, "krik'thir web wrap") {}
bool IsActive() override;
};
class KrikthirWatchersTrigger : public Trigger
{
public:
KrikthirWatchersTrigger(PlayerbotAI* ai) : Trigger(ai, "krik'thir watchers") {}
bool IsActive() override;
};
// class AnubarakImpaleTrigger : public Trigger
// {
// public:
// AnubarakImpaleTrigger(PlayerbotAI* ai) : Trigger(ai, "anub'arak impale") {}
// bool IsActive() override;
// };
class AnubarakPoundTrigger : public Trigger
{
public:
AnubarakPoundTrigger(PlayerbotAI* ai) : Trigger(ai, "anub'arak pound") {}
bool IsActive() override;
};
#endif