Merge pull request #560 from Bobblybook/master

Wotlk dungeon structure & Utgarde Keep
This commit is contained in:
Yunfan Li
2024-10-01 01:02:51 +08:00
committed by GitHub
31 changed files with 802 additions and 0 deletions

View File

@@ -1529,12 +1529,60 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 533:
strategyName = "naxx";
break;
case 574:
strategyName = "wotlk-uk"; // Utgarde Keep
break;
case 575:
strategyName = "wotlk-up"; // Utgarde Pinnacle
break;
case 576:
strategyName = "wotlk-nex"; // The Nexus
break;
case 578:
strategyName = "wotlk-occ"; // The Oculus
break;
case 595:
strategyName = "wotlk-cos"; // The Culling of Stratholme
break;
case 599:
strategyName = "wotlk-hos"; // Halls of Stone
break;
case 600:
strategyName = "wotlk-dtk"; // Drak'Tharon Keep
break;
case 601:
strategyName = "wotlk-an"; // Azjol-Nerub
break;
case 602:
strategyName = "wotlk-hol"; // Halls of Lightning
break;
case 603:
strategyName = "uld";
break;
case 604:
strategyName = "wotlk-gd"; // Gundrak
break;
case 608:
strategyName = "wotlk-vh"; // Violet Hold
break;
case 619:
strategyName = "wotlk-ok"; // Ahn'kahet: The Old Kingdom
break;
case 631:
strategyName = "icc";
break;
case 632:
strategyName = "wotlk-fos"; // The Forge of Souls
break;
case 650:
strategyName = "wotlk-toc"; // Trial of the Champion
break;
case 658:
strategyName = "wotlk-pos"; // Pit of Saron
break;
case 668:
strategyName = "wotlk-hor"; // Halls of Reflection
break;
default:
break;
}

View File

@@ -28,6 +28,9 @@
#include "raids/moltencore/RaidMcTriggerContext.h"
#include "raids/aq20/RaidAq20ActionContext.h"
#include "raids/aq20/RaidAq20TriggerContext.h"
#include "dungeons/DungeonStrategyContext.h"
#include "dungeons/wotlk/WotlkDungeonActionContext.h"
#include "dungeons/wotlk/WotlkDungeonTriggerContext.h"
AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
{
@@ -36,6 +39,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
strategyContexts.Add(new AssistStrategyContext());
strategyContexts.Add(new QuestStrategyContext());
strategyContexts.Add(new RaidStrategyContext());
strategyContexts.Add(new DungeonStrategyContext());
actionContexts.Add(new ActionContext());
actionContexts.Add(new ChatActionContext());
@@ -46,6 +50,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext());
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
@@ -56,6 +61,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext());
valueContexts.Add(new ValueContext());

View File

@@ -0,0 +1,96 @@
#ifndef _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H_
#define _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H_
#include "Strategy.h"
#include "wotlk/utgardekeep/UtgardeKeepStrategy.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
Elder Nadox, Prince Taldaram, Jedoga Shadowseeker, Herald Volazj, Amanitar (Heroic Only)
Drak'Tharon Keep - DTK
Trollgore, Novos the Summoner, King Dred, The Prophet Tharon'ja
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
Maiden of Grief, Krystallus, Tribunal of Ages, Sjonnir The Ironshaper
Halls of Lightning - HoL
General Bjarngrim, Volkhan, Ionar, Loken
The Oculus - Occ
Drakos the Interrogator, Varos Cloudstrider, Mage-Lord Urom, Ley-Guardian Eregos
Utgarde Pinnacle - UP
Svala Sorrowgrave, Gortok Palehoof, Skadi the Ruthless, King Ymiron
The Culling of Stratholme - CoS
Meathook, Salramm the Fleshcrafter, Chrono-Lord Epoch, Mal'Ganis, Infinite Corruptor (Heroic only)
Trial of the Champion - ToC
Alliance Champions: Deathstalker Visceri, Eressea Dawnsinger, Mokra the Skullcrusher, Runok Wildmane, Zul'tore
Horde Champions: Ambrose Boltspark, Colosos, Jacob Alerius, Jaelyne Evensong, Lana Stouthammer
Argent Champion: Argent Confessor Paletress/Eadric the Pure
The Black Knight
Halls of Reflection - HoR
Falric, Marwyn, The Lich King
Pit of Saron - PoS
Forgemaster Garfrost, Krick & Ick, Scourgelord Tyrannus
The Forge of Souls - FoS
Bronjahm, Devourer of Souls
*/
class DungeonStrategyContext : public NamedObjectContext<Strategy>
{
public:
DungeonStrategyContext() : NamedObjectContext<Strategy>(false, true)
{
// Vanilla
// ...
// Burning Crusade
// ...
// Wrath of the Lich King
creators["wotlk-uk"] = &DungeonStrategyContext::wotlk_uk; // Utgarde Keep
creators["wotlk-nex"] = &DungeonStrategyContext::wotlk_nex; // The Nexus
creators["wotlk-an"] = &DungeonStrategyContext::wotlk_an; // Azjol-Nerub
creators["wotlk-ok"] = &DungeonStrategyContext::wotlk_ok; // Ahn'kahet: The Old Kingdom
creators["wotlk-dtk"] = &DungeonStrategyContext::wotlk_dtk; // Drak'Tharon Keep
creators["wotlk-vh"] = &DungeonStrategyContext::wotlk_vh; // The Violet Hold
creators["wotlk-gd"] = &DungeonStrategyContext::wotlk_gd; // Gundrak
creators["wotlk-hos"] = &DungeonStrategyContext::wotlk_hos; // Halls of Stone
creators["wotlk-hol"] = &DungeonStrategyContext::wotlk_hol; // Halls of Lightning
creators["wotlk-occ"] = &DungeonStrategyContext::wotlk_occ; // The Oculus
creators["wotlk-up"] = &DungeonStrategyContext::wotlk_up; // Utgarde Pinnacle
creators["wotlk-cos"] = &DungeonStrategyContext::wotlk_cos; // The Culling of Stratholme
creators["wotlk-toc"] = &DungeonStrategyContext::wotlk_toc; // Trial of the Champion
creators["wotlk-hor"] = &DungeonStrategyContext::wotlk_hor; // Halls of Reflection
creators["wotlk-pos"] = &DungeonStrategyContext::wotlk_pos; // Pit of Saron
creators["wotlk-fos"] = &DungeonStrategyContext::wotlk_fos; // The Forge of Souls
}
private:
static Strategy* wotlk_uk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(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); }
static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(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); }
static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_up(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_hor(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_pos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_fos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
};
#endif

View File

@@ -0,0 +1,21 @@
#ifndef _PLAYERBOT_DUNGEONUTILS_H_
#define _PLAYERBOT_DUNGEONUTILS_H_
template<class T> inline
const T& DUNGEON_MODE(Player* bot, const T& normal5, const T& heroic10)
{
switch (bot->GetMap()->GetDifficulty())
{
case DUNGEON_DIFFICULTY_NORMAL:
return normal5;
case DUNGEON_DIFFICULTY_HEROIC:
return heroic10;
default:
break;
}
return heroic10;
}
#endif

View File

@@ -0,0 +1,21 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H_
#define _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H_
#include "utgardekeep/UtgardeKeepActionContext.h"
// #include "nexus/NexusActionContext.h"
// #include "azjolnerub/AzjolNerubActionContext.h"
// #include "oldkingdom/OldKingdomActionContext.h"
// #include "draktharonkeep/DraktharonKeepActionContext.h"
// #include "violethold/VioletHoldActionContext.h"
// #include "gundrak/GundrakActionContext.h"
// #include "hallsofstone/HallsOfStoneActionContext.h"
// #include "hallsoflightning/HallsOfLightningActionContext.h"
// #include "oculus/OculusActionContext.h"
// #include "utgardepinnacle/UtgardePinnacleActionContext.h"
// #include "cullingofstratholme/CullingOfStratholmeActionContext.h"
// #include "trialofthechampion/TrialOfTheChampionActionContext.h"
// #include "hallsofreflection/HallsOfReflectionActionContext.h"
// #include "pitofsaron/PitOfSaronActionContext.h"
// #include "forgeofsouls/ForgeOfSoulsActionContext.h"
#endif

View File

@@ -0,0 +1,21 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H_
#define _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H_
#include "utgardekeep/UtgardeKeepTriggerContext.h"
// #include "nexus/NexusTriggerContext.h"
// #include "azjolnerub/AzjolNerubTriggerContext.h"
// #include "oldkingdom/OldKingdomTriggerContext.h"
// #include "draktharonkeep/DraktharonKeepTriggerContext.h"
// #include "violethold/VioletHoldTriggerContext.h"
// #include "gundrak/GundrakTriggerContext.h"
// #include "hallsofstone/HallsOfStoneTriggerContext.h"
// #include "hallsoflightning/HallsOfLightningTriggerContext.h"
// #include "oculus/OculusTriggerContext.h"
// #include "utgardepinnacle/UtgardePinnacleTriggerContext.h"
// #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h"
// #include "trialofthechampion/TrialOfTheChampionTriggerContext.h"
// #include "hallsofreflection/HallsOfReflectionTriggerContext.h"
// #include "pitofsaron/PitOfSaronTriggerContext.h"
// #include "forgeofsouls/ForgeOfSoulsTriggerContext.h"
#endif

View File

View File

View File

View File

@@ -0,0 +1,30 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUKACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONUKACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "UtgardeKeepActions.h"
class WotlkDungeonUKActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonUKActionContext() {
creators["attack frost tomb"] = &WotlkDungeonUKActionContext::attack_frost_tomb;
creators["attack dalronn"] = &WotlkDungeonUKActionContext::attack_dalronn;
creators["ingvar stop casting"] = &WotlkDungeonUKActionContext::ingvar_stop_casting;
creators["ingvar get behind"] = &WotlkDungeonUKActionContext::ingvar_get_behind;
// creators["ingvar hide los"] = &WotlkDungeonUKActionContext::ingvar_hide_los;
creators["ingvar dodge smash"] = &WotlkDungeonUKActionContext::ingvar_dodge_smash;
creators["ingvar smash return"] = &WotlkDungeonUKActionContext::ingvar_smash_return;
}
private:
static Action* attack_frost_tomb(PlayerbotAI* ai) { return new AttackFrostTombAction(ai); }
static Action* attack_dalronn(PlayerbotAI* ai) { return new AttackDalronnAction(ai); }
static Action* ingvar_stop_casting(PlayerbotAI* ai) { return new IngvarStopCastingAction(ai); }
static Action* ingvar_get_behind(PlayerbotAI* ai) { return new SetBehindTargetAction(ai); }
// static Action* ingvar_hide_los(PlayerbotAI* ai) { return new TellLosAction(ai); }
static Action* ingvar_dodge_smash(PlayerbotAI* ai) { return new IngvarDodgeSmashAction(ai); }
static Action* ingvar_smash_return(PlayerbotAI* ai) { return new IngvarSmashReturnAction(ai); }
};
#endif

View File

@@ -0,0 +1,97 @@
#include "Playerbots.h"
#include "UtgardeKeepActions.h"
#include "UtgardeKeepStrategy.h"
bool AttackFrostTombAction::Execute(Event event)
{
Unit* frostTomb = 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() == "Frost Tomb")
{
frostTomb = unit;
break;
}
}
if (!frostTomb || AI_VALUE(Unit*, "current target") == frostTomb)
{
return false;
}
return Attack(frostTomb);
}
// TODO: Possibly add player stacking behaviour close to tank, to prevent Skarvald charging ranged
bool AttackDalronnAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "dalronn the controller");
if (!boss || AI_VALUE(Unit*, "current target") == boss)
{
return false;
}
return Attack(boss);
}
bool IngvarStopCastingAction::Execute(Event event)
{
// Doesn't work, this action gets queued behind the current spell instead of interrupting it
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
if (!boss)
{
return false;
}
int32 my_spell_id = AI_VALUE(uint32, "active spell");
if (!my_spell_id || my_spell_id == 0)
{
return false;
}
Spell* spell = bot->FindCurrentSpellBySpellId(my_spell_id);
if (!spell)
{
return false;
}
// bot->Yell("cancelling spell="+std::to_string(my_spell_id), LANG_UNIVERSAL);
bot->InterruptSpell(spell->GetCurrentContainer(), false, true, true);
// Can slightly optimise by allowing bot to keep casting if they will finish the cast
// before boss spell goes off, however need to hook boss AI for cast remaining.
return true;
}
bool IngvarDodgeSmashAction::isUseful() { return !AI_VALUE2(bool, "behind", "current target"); }
bool IngvarDodgeSmashAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
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;
return Move(bot->GetAngle(boss), distance + distanceExtra);
}
bool IngvarSmashReturnAction::isUseful() { return AI_VALUE2(bool, "behind", "current target"); }
bool IngvarSmashReturnAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
if (!boss)
{
return false;
}
float distance = bot->GetExactDist2d(boss->GetPosition());
return Move(bot->GetAngle(boss), distance + bot->GetMeleeReach());
}

View File

@@ -0,0 +1,47 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUKACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONUKACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "UtgardeKeepTriggers.h"
class AttackFrostTombAction : public AttackAction
{
public:
AttackFrostTombAction(PlayerbotAI* ai) : AttackAction(ai, "attack frost tomb") {}
bool Execute(Event event) override;
};
class AttackDalronnAction : public AttackAction
{
public:
AttackDalronnAction(PlayerbotAI* ai) : AttackAction(ai, "attack dalronn") {}
bool Execute(Event event) override;
};
class IngvarStopCastingAction : public Action
{
public:
IngvarStopCastingAction(PlayerbotAI* ai) : Action(ai, "ingvar stop casting") {}
bool Execute(Event event) override;
};
class IngvarDodgeSmashAction : public MovementAction
{
public:
IngvarDodgeSmashAction(PlayerbotAI* ai) : MovementAction(ai, "ingvar dodge smash") {}
bool isUseful() override;
bool Execute(Event event) override;
};
class IngvarSmashReturnAction : public MovementAction
{
public:
IngvarSmashReturnAction(PlayerbotAI* ai) : MovementAction(ai, "ingvar smash return") {}
bool isUseful() override;
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,92 @@
#include "UtgardeKeepMultipliers.h"
#include "UtgardeKeepActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "UtgardeKeepTriggers.h"
float PrinceKelesethMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth");
if (!boss)
{
return 1.0f;
}
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
return 1.0f;
}
float SkarvaldAndDalronnMultiplier::GetValue(Action* action)
{
// Unit* skarvald = AI_VALUE2(Unit*, "find target", "skarvald the constructor");
Unit* dalronn = AI_VALUE2(Unit*, "find target", "dalronn the controller");
if (!dalronn)
{
return 1.0f;
}
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
return 1.0f;
}
float IngvarThePlundererMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
bool isTank = botAI->IsTank(bot);
if (!boss)
{
return 1.0f;
}
// Prevent movement actions overriding current movement, we're probably dodging a slam
if (isTank && bot->isMoving() && dynamic_cast<MovementAction*>(action))
{
return 0.0f;
}
// If boss is casting a roar, do not allow beginning a spell cast that is non-instant
if (boss->HasUnitState(UNIT_STATE_CASTING))
{
if (boss->FindCurrentSpellBySpellId(SPELL_STAGGERING_ROAR) ||
boss->FindCurrentSpellBySpellId(SPELL_DREADFUL_ROAR))
{
if (dynamic_cast<CastSpellAction*>(action))
{
uint32 spellId = AI_VALUE2(uint32, "spell id", action->getName());
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
{
return 1.0f;
}
uint32 castTime = spellInfo->CalcCastTime(bot);
if (castTime != 0)
{
return 0.0f;
}
}
}
// Done with non-tank logic
if (!isTank)
{
return 1.0f;
}
// TANK ONLY
if (boss->FindCurrentSpellBySpellId(SPELL_SMASH) ||
boss->FindCurrentSpellBySpellId(SPELL_DARK_SMASH))
{
// Prevent movement actions during smash which can mess up boss position.
// Allow through IngvarDodgeSmashAction only, as well as any non-movement actions.
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<IngvarDodgeSmashAction*>(action))
{
return 0.0f;
}
}
}
return 1.0f;
}

View File

@@ -0,0 +1,33 @@
#ifndef _PLAYERRBOT_WOTLKDUNGEONUKMULTIPLIERS_H_
#define _PLAYERRBOT_WOTLKDUNGEONUKMULTIPLIERS_H_
#include "Multiplier.h"
class PrinceKelesethMultiplier : public Multiplier
{
public:
PrinceKelesethMultiplier(PlayerbotAI* ai) : Multiplier(ai, "prince keleseth") {}
public:
virtual float GetValue(Action* action);
};
class SkarvaldAndDalronnMultiplier : public Multiplier
{
public:
SkarvaldAndDalronnMultiplier(PlayerbotAI* ai) : Multiplier(ai, "skarvald and dalronn") {}
public:
virtual float GetValue(Action* action);
};
class IngvarThePlundererMultiplier : public Multiplier
{
public:
IngvarThePlundererMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ingvar the plunderer") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,43 @@
#include "UtgardeKeepStrategy.h"
#include "UtgardeKeepMultipliers.h"
void WotlkDungeonUKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Prince Keleseth
triggers.push_back(new TriggerNode("keleseth frost tomb",
NextAction::array(0, new NextAction("attack frost tomb", ACTION_RAID + 1), nullptr)));
// Skarvald the Constructor & Dalronn the Controller
triggers.push_back(new TriggerNode("dalronn priority",
NextAction::array(0, new NextAction("attack dalronn", ACTION_RAID + 1), nullptr)));
// Ingvar the Plunderer
// Doesn't work yet, this action doesn't get processed until the existing cast finishes
// triggers.push_back(new TriggerNode("ingvar staggering roar",
// NextAction::array(0, new NextAction("ingvar stop casting", ACTION_RAID + 1), nullptr)));
// No easy way to check LoS here, the pillars do not seem to count as gameobjects.
// Not implemented for now, unsure if this is needed as a good group can probably burst through the boss
// and just eat the debuff.
// triggers.push_back(new TriggerNode("ingvar dreadful roar",
// NextAction::array(0, new NextAction("ingvar hide los", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("ingvar smash tank",
NextAction::array(0, new NextAction("ingvar dodge smash", ACTION_MOVE + 5), nullptr)));
triggers.push_back(new TriggerNode("ingvar smash tank return",
NextAction::array(0, new NextAction("ingvar smash return", ACTION_MOVE + 5), nullptr)));
// Buggy... if not behind target, ai can get stuck running towards and away from target.
// I think for ranged chars, a custom action should be added that doesn't attempt to run into melee.
// This is a bandaid for now, needs to be improved.
triggers.push_back(new TriggerNode("not behind ingvar",
NextAction::array(0, new NextAction("set behind", ACTION_MOVE + 1), nullptr)));
}
void WotlkDungeonUKStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new PrinceKelesethMultiplier(botAI));
multipliers.push_back(new SkarvaldAndDalronnMultiplier(botAI));
multipliers.push_back(new IngvarThePlundererMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,31 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUKTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONUKTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "UtgardeKeepTriggers.h"
class WotlkDungeonUKTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonUKTriggerContext()
{
creators["keleseth frost tomb"] = &WotlkDungeonUKTriggerContext::keleseth_frost_tomb;
creators["dalronn priority"] = &WotlkDungeonUKTriggerContext::dalronn_priority_target;
creators["ingvar staggering roar"] = &WotlkDungeonUKTriggerContext::ingvar_staggering_roar;
creators["ingvar dreadful roar"] = &WotlkDungeonUKTriggerContext::ingvar_dreadful_roar;
creators["ingvar smash tank"] = &WotlkDungeonUKTriggerContext::ingvar_smash_tank;
creators["ingvar smash tank return"] = &WotlkDungeonUKTriggerContext::ingvar_smash_tank_return;
creators["not behind ingvar"] = &WotlkDungeonUKTriggerContext::not_behind_ingvar;
}
private:
static Trigger* keleseth_frost_tomb(PlayerbotAI* ai) { return new KelesethFrostTombTrigger(ai); }
static Trigger* dalronn_priority_target(PlayerbotAI* ai) { return new DalronnNontankTrigger(ai); }
static Trigger* ingvar_staggering_roar(PlayerbotAI* ai) { return new IngvarStaggeringRoarTrigger(ai); }
static Trigger* ingvar_dreadful_roar(PlayerbotAI* ai) { return new IngvarDreadfulRoarTrigger(ai); }
static Trigger* ingvar_smash_tank(PlayerbotAI* ai) { return new IngvarSmashTankTrigger(ai); }
static Trigger* ingvar_smash_tank_return(PlayerbotAI* ai) { return new IngvarSmashTankReturnTrigger(ai); }
static Trigger* not_behind_ingvar(PlayerbotAI* ai) { return new NotBehindIngvarTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,104 @@
#include "Playerbots.h"
#include "UtgardeKeepTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool KelesethFrostTombTrigger::IsActive()
{
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
if (botAI->GetUnit(member)->HasAura(DEBUFF_FROST_TOMB))
{
return true;
}
}
return false;
}
bool DalronnNontankTrigger::IsActive()
{
Unit* dalronn = AI_VALUE2(Unit*, "find target", "dalronn the controller");
if (!dalronn)
{
return false;
}
return !botAI->IsTank(bot);
}
bool IngvarStaggeringRoarTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
if (!boss)
{
return false;
}
if (boss->HasUnitState(UNIT_STATE_CASTING))
{
if (boss->FindCurrentSpellBySpellId(SPELL_STAGGERING_ROAR))
{
return true;
}
}
return false;
}
bool IngvarDreadfulRoarTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
if (!boss)
{
return false;
}
if (boss->HasUnitState(UNIT_STATE_CASTING))
{
if (boss->FindCurrentSpellBySpellId(SPELL_DREADFUL_ROAR))
{
return true;
}
}
return false;
}
bool IngvarSmashTankTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
if (!boss || !botAI->IsTank(bot))
{
return false;
}
if (boss->HasUnitState(UNIT_STATE_CASTING))
{
if (boss->FindCurrentSpellBySpellId(SPELL_SMASH) ||
boss->FindCurrentSpellBySpellId(SPELL_DARK_SMASH))
{
return true;
}
}
return false;
}
bool IngvarSmashTankReturnTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
// if (!boss || !botAI->IsTank(bot) || boss->HasUnitState(UNIT_STATE_CASTING))
// Ignore casting state as Ingvar will sometimes chain-cast a roar after a smash..
// We don't want this to prevent our tank from repositioning properly.
if (!boss || !botAI->IsTank(bot))
{
return false;
}
return true;
}
bool NotBehindIngvarTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ingvar the plunderer");
if (!boss || botAI->IsTank(bot))
{
return false;
}
return AI_VALUE2(bool, "behind", "current target");
}

View File

@@ -0,0 +1,94 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUKTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONUKTRIGGERS_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 eSpells
{
SPELL_SUMMON_VALKYR = 42912,
SPELL_RESURRECTION_BEAM = 42857,
SPELL_RESURRECTION_BALL = 42862,
SPELL_RESURRECTION_HEAL = 42704,
SPELL_INGVAR_TRANSFORM = 42796,
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,
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,
// Added
DEBUFF_FROST_TOMB = 48400,
};
#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
{
public:
KelesethFrostTombTrigger(PlayerbotAI* ai) : Trigger(ai, "keleseth frost tomb") {}
bool IsActive() override;
};
class DalronnNontankTrigger : public Trigger
{
public:
DalronnNontankTrigger(PlayerbotAI* ai) : Trigger(ai, "dalronn non-tank") {}
bool IsActive() override;
};
class IngvarStaggeringRoarTrigger : public Trigger
{
public:
IngvarStaggeringRoarTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar staggering roar") {}
bool IsActive() override;
};
class IngvarDreadfulRoarTrigger : public Trigger
{
public:
IngvarDreadfulRoarTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar dreadful roar") {}
bool IsActive() override;
};
class IngvarSmashTankTrigger : public Trigger
{
public:
IngvarSmashTankTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar smash tank") {}
bool IsActive() override;
};
class IngvarSmashTankReturnTrigger : public Trigger
{
public:
IngvarSmashTankReturnTrigger(PlayerbotAI* ai) : Trigger(ai, "ingvar smash tank return") {}
bool IsActive() override;
};
class NotBehindIngvarTrigger : public Trigger
{
public:
NotBehindIngvarTrigger(PlayerbotAI* ai) : Trigger(ai, "not behind ingvar") {}
bool IsActive() override;
};
#endif