UP and CoS dungeons

- Utgarde Pinnacle implementation
- Culling of Stratholme implementation
- Added additional value ("nearest hostile npcs") needed to expose some hidden trigger-type npc units (eg. frost breath on Skadi fight in UP)
This commit is contained in:
Bobblybook
2024-10-21 22:29:03 +11:00
parent edcf90f4e8
commit c788e96828
31 changed files with 665 additions and 18 deletions

View File

@@ -59,6 +59,8 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new WotlkDungeonGDActionContext());
actionContexts.Add(new WotlkDungeonHoSActionContext());
actionContexts.Add(new WotlkDungeonHoLActionContext());
actionContexts.Add(new WotlkDungeonUPActionContext());
actionContexts.Add(new WotlkDungeonCoSActionContext());
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
@@ -78,6 +80,8 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new WotlkDungeonGDTriggerContext());
triggerContexts.Add(new WotlkDungeonHoSTriggerContext());
triggerContexts.Add(new WotlkDungeonHoLTriggerContext());
triggerContexts.Add(new WotlkDungeonUPTriggerContext());
triggerContexts.Add(new WotlkDungeonCoSTriggerContext());
valueContexts.Add(new ValueContext());

View File

@@ -11,16 +11,14 @@
#include "wotlk/gundrak/GundrakStrategy.h"
#include "wotlk/hallsofstone/HallsOfStoneStrategy.h"
#include "wotlk/hallsoflightning/HallsOfLightningStrategy.h"
#include "wotlk/utgardepinnacle/UtgardePinnacleStrategy.h"
#include "wotlk/cullingofstratholme/CullingOfStratholmeStrategy.h"
/*
Full list/TODO:
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
@@ -76,11 +74,12 @@ class DungeonStrategyContext : public NamedObjectContext<Strategy>
static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonGDStrategy(botAI); }
static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonHoSStrategy(botAI); }
static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonHoLStrategy(botAI); }
// static Strategy* wotlk_occ(PlayerbotAI* botAI) { return new WotlkDungeonOccStrategy(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_up(PlayerbotAI* botAI) { return new WotlkDungeonUPStrategy(botAI); }
static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonCoSStrategy(botAI); }
static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } // NYI from here down
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); }

View File

@@ -11,8 +11,8 @@
#include "hallsofstone/HallsOfStoneActionContext.h"
#include "hallsoflightning/HallsOfLightningActionContext.h"
// #include "oculus/OculusActionContext.h"
// #include "utgardepinnacle/UtgardePinnacleActionContext.h"
// #include "cullingofstratholme/CullingOfStratholmeActionContext.h"
#include "utgardepinnacle/UtgardePinnacleActionContext.h"
#include "cullingofstratholme/CullingOfStratholmeActionContext.h"
// #include "trialofthechampion/TrialOfTheChampionActionContext.h"
// #include "hallsofreflection/HallsOfReflectionActionContext.h"
// #include "pitofsaron/PitOfSaronActionContext.h"

View File

@@ -11,8 +11,8 @@
#include "hallsofstone/HallsOfStoneTriggerContext.h"
#include "hallsoflightning/HallsOfLightningTriggerContext.h"
// #include "oculus/OculusTriggerContext.h"
// #include "utgardepinnacle/UtgardePinnacleTriggerContext.h"
// #include "cullingofstratholme/CullingOfStratholmeTriggerContext.h"
#include "utgardepinnacle/UtgardePinnacleTriggerContext.h"
#include "cullingofstratholme/CullingOfStratholmeTriggerContext.h"
// #include "trialofthechampion/TrialOfTheChampionTriggerContext.h"
// #include "hallsofreflection/HallsOfReflectionTriggerContext.h"
// #include "pitofsaron/PitOfSaronTriggerContext.h"

View File

@@ -0,0 +1,20 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONCOSACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONCOSACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "CullingOfStratholmeActions.h"
class WotlkDungeonCoSActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonCoSActionContext() {
creators["explode ghoul spread"] = &WotlkDungeonCoSActionContext::explode_ghoul_spread;
creators["epoch stack"] = &WotlkDungeonCoSActionContext::epoch_stack;
}
private:
static Action* explode_ghoul_spread(PlayerbotAI* ai) { return new ExplodeGhoulSpreadAction(ai); }
static Action* epoch_stack(PlayerbotAI* ai) { return new EpochStackAction(ai); }
};
#endif

View File

@@ -0,0 +1,54 @@
#include "Playerbots.h"
#include "CullingOfStratholmeActions.h"
#include "CullingOfStratholmeStrategy.h"
bool ExplodeGhoulSpreadAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "salramm the fleshcrafter");
if (!boss) { return false; }
float distance = 10.0f;
float distanceExtra = 2.0f;
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
for (auto i = corpses.begin(); i != corpses.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_GHOUL_MINION)
{
float currentDistance = bot->GetExactDist2d(unit);
if (currentDistance < distance + distanceExtra)
{
return MoveAway(unit, distance + distanceExtra - currentDistance);
}
}
}
return false;
}
bool EpochStackAction::isUseful()
{
// Minimum hunter range is 5, but values too close to this seem to cause issues..
// Hunter bots will try and melee in between ranged attacks, or just melee entirely at 5 as they are in range.
// 7.5 or 8.0 solves this for this boss.
// Unfortunately at this range the boss will charge. So I guess just don't stack as a hunter..
// if(bot->getClass() == CLASS_HUNTER)
// {
// return AI_VALUE2(float, "distance", "current target") > 7.5f;
// }
// else
return !(bot->getClass() == CLASS_HUNTER) && AI_VALUE2(float, "distance", "current target") > 5.0f;
}
bool EpochStackAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "chrono-lord epoch");
if (!boss) { return false; }
float maxMovement = 10.0f;
// if(bot->getClass() == CLASS_HUNTER)
// {
// return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, maxMovement));
// }
// else
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), maxMovement));
}

View File

@@ -0,0 +1,26 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONCOSACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONCOSACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "GenericSpellActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "CullingOfStratholmeTriggers.h"
class ExplodeGhoulSpreadAction : public MovementAction
{
public:
ExplodeGhoulSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "explode ghoul spread") {}
bool Execute(Event event) override;
};
class EpochStackAction : public MovementAction
{
public:
EpochStackAction(PlayerbotAI* ai) : MovementAction(ai, "epoch stack") {}
bool Execute(Event event) override;
bool isUseful() override;
};
#endif

View File

@@ -0,0 +1,19 @@
#include "CullingOfStratholmeMultipliers.h"
#include "CullingOfStratholmeActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "CullingOfStratholmeTriggers.h"
#include "Action.h"
float EpochMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "chrono-lord epoch");
if (!boss) { return 1.0f; }
if (bot->getClass() == CLASS_HUNTER) { return 1.0f; }
if (dynamic_cast<FleeAction*>(action)) { return 0.0f; }
return 1.0f;
}

View File

@@ -0,0 +1,15 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONCOSMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONCOSMULTIPLIERS_H
#include "Multiplier.h"
class EpochMultiplier : public Multiplier
{
public:
EpochMultiplier(PlayerbotAI* ai) : Multiplier(ai, "chrono-lord epoch") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,27 @@
#include "CullingOfStratholmeStrategy.h"
#include "CullingOfStratholmeMultipliers.h"
void WotlkDungeonCoSStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Meathook
// Can tank this in a fixed position to allow healer to LoS the stun, probably not necessary
// Salramm the Fleshcrafter
triggers.push_back(new TriggerNode("explode ghoul",
NextAction::array(0, new NextAction("explode ghoul spread", ACTION_MOVE + 5), nullptr)));
// Chrono-Lord Epoch
// Not sure if this actually works, I think I've seen him charge melee characters..?
triggers.push_back(new TriggerNode("epoch ranged",
NextAction::array(0, new NextAction("epoch stack", ACTION_MOVE + 5), nullptr)));
// Mal'Ganis
// Infinite Corruptor (Heroic only)
}
void WotlkDungeonCoSStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new EpochMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONCOSTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONCOSTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "CullingOfStratholmeTriggers.h"
class WotlkDungeonCoSTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonCoSTriggerContext()
{
creators["explode ghoul"] = &WotlkDungeonCoSTriggerContext::explode_ghoul;
creators["epoch ranged"] = &WotlkDungeonCoSTriggerContext::epoch_ranged;
}
private:
static Trigger* explode_ghoul(PlayerbotAI* ai) { return new ExplodeGhoulTrigger(ai); }
static Trigger* epoch_ranged(PlayerbotAI* ai) { return new EpochRangedTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,32 @@
#include "Playerbots.h"
#include "CullingOfStratholmeTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool ExplodeGhoulTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "salramm the fleshcrafter");
if (!boss) { return false; }
float distance = 10.0f;
float distanceExtra = 2.0f;
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
for (auto i = corpses.begin(); i != corpses.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_RISEN_GHOUL)
{
if (bot->GetExactDist2d(unit) < distance + distanceExtra)
{
return true;
}
}
}
return false;
}
bool EpochRangedTrigger::IsActive()
{
return !botAI->IsMelee(bot) && AI_VALUE2(Unit*, "find target", "chrono-lord epoch");
}

View File

@@ -0,0 +1,29 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONCOSTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONCOSTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
enum CullingOfStratholmeIDs
{
// Salramm the Fleshcrafter
NPC_GHOUL_MINION = 27733,
};
class ExplodeGhoulTrigger : public Trigger
{
public:
ExplodeGhoulTrigger(PlayerbotAI* ai) : Trigger(ai, "explode ghoul") {}
bool IsActive() override;
};
class EpochRangedTrigger : public Trigger
{
public:
EpochRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "chrono-lord epoch ranged") {}
bool IsActive() override;
};
#endif

View File

@@ -8,16 +8,18 @@ bool CorpseExplodeSpreadAction::Execute(Event event)
Unit* boss = AI_VALUE2(Unit*, "find target", "trollgore");
if (!boss) { return false; }
float distance = 6.0f; // 5 unit radius, 1 unit added as buffer
float distance = 5.0f;
float distanceExtra = 2.0f;
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
for (auto i = corpses.begin(); i != corpses.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_DRAKKARI_INVADER)
{
if (bot->GetExactDist2d(unit) < distance)
float currentDistance = bot->GetExactDist2d(unit);
if (currentDistance < distance + distanceExtra)
{
return MoveAway(unit, distance - bot->GetExactDist2d(unit));
return MoveAway(unit, distance + distanceExtra - currentDistance);
}
}
}

View File

@@ -125,7 +125,7 @@ bool LokenStackAction::isUseful()
{
// Minimum hunter range is 5, but values too close to this seem to cause issues..
// Hunter bots will try and melee in between ranged attacks, or just melee entirely at 5 as they are in range.
// 6.5 or 7.0 solves this.
// 6.5 or 7.0 solves this for this boss.
if(bot->getClass() == CLASS_HUNTER)
{
return AI_VALUE2(float, "distance", "current target") > 6.5f;
@@ -138,14 +138,15 @@ bool LokenStackAction::Execute(Event event)
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
if (!boss) { return false; }
float maxMovement = 10.0f;
if (!boss->HasUnitState(UNIT_STATE_CASTING))
{
if(bot->getClass() == CLASS_HUNTER)
{
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, 10.0f));
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, maxMovement));
}
// else
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), 10.0f));
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), maxMovement));
}
return false;

View File

@@ -0,0 +1,20 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUPACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONUPACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "UtgardePinnacleActions.h"
class WotlkDungeonUPActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonUPActionContext() {
creators["avoid freezing cloud"] = &WotlkDungeonUPActionContext::avoid_freezing_cloud;
creators["avoid skadi whirlwind"] = &WotlkDungeonUPActionContext::avoid_whirlwind;
}
private:
static Action* avoid_freezing_cloud(PlayerbotAI* ai) { return new AvoidFreezingCloudAction(ai); }
static Action* avoid_whirlwind(PlayerbotAI* ai) { return new AvoidSkadiWhirlwindAction(ai); }
};
#endif

View File

@@ -0,0 +1,61 @@
#include "Playerbots.h"
#include "UtgardePinnacleActions.h"
#include "UtgardePinnacleStrategy.h"
bool AvoidFreezingCloudAction::Execute(Event event)
{
Unit* closestTrigger = nullptr;
GuidVector objects = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto i = objects.begin(); i != objects.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_BREATH_TRIGGER)
{
if (!closestTrigger || bot->GetExactDist2d(unit) < bot->GetExactDist2d(closestTrigger))
{
closestTrigger = unit;
}
}
}
if (!closestTrigger) { return false; }
float distance = bot->GetExactDist2d(closestTrigger->GetPosition());
float radius = 3.0f;
// Large buffer for this - the radius of the breath is a lot smaller than the graphic, but it looks dumb
// if the bot stands just outside the hitbox but still visibly in the cloud patches.
float distanceExtra = 3.0f;
if (distance < radius + distanceExtra - 1.0f)
{
// bot->Yell("MOVING", LANG_UNIVERSAL);
return MoveAway(closestTrigger, radius + distanceExtra - distance);
}
return false;
}
bool AvoidSkadiWhirlwindAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "skadi the ruthless");
if (!boss) { return false; }
float distance = bot->GetExactDist2d(boss->GetPosition());
float radius = 5.0f;
float distanceExtra = 2.0f;
if (distance < radius + distanceExtra)
{
if (botAI->IsTank(bot))
{
// The boss chases tank during this, leads to jittery stutter-stepping
// by the tank if we don't pre-move additional range. 2*radius seems ok
return MoveAway(boss, (2.0f * radius) + distanceExtra - distance);
}
// else
return MoveAway(boss, radius + distanceExtra - distance);
}
return false;
}

View File

@@ -0,0 +1,24 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUPACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONUPACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "UtgardePinnacleTriggers.h"
class AvoidFreezingCloudAction : public MovementAction
{
public:
AvoidFreezingCloudAction(PlayerbotAI* ai) : MovementAction(ai, "avoid freezing cloud") {}
bool Execute(Event event) override;
};
class AvoidSkadiWhirlwindAction : public MovementAction
{
public:
AvoidSkadiWhirlwindAction(PlayerbotAI* ai) : MovementAction(ai, "avoid skadi whirlwind") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,84 @@
#include "UtgardePinnacleMultipliers.h"
#include "UtgardePinnacleActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "UtgardePinnacleTriggers.h"
#include "Action.h"
float SkadiMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "skadi the ruthless");
if (!boss) { return 1.0f; }
Unit* bossMount = AI_VALUE2(Unit*, "find target", "grauf");
if (!bossMount)
// Actual bossfight (dismounted)
{
if (boss->HasAura(SPELL_SKADI_WHIRLWIND))
{
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<AvoidSkadiWhirlwindAction*>(action))
{
return 0.0f;
}
}
}
else
{
// Bots tend to get stuck trying to attack the boss in the sky, not the adds on the ground
if (dynamic_cast<AttackAction*>(action)
&& (action->GetTarget() == boss || action->GetTarget() == bossMount))
{
return 0.0f;
}
// TODO: BELOW IS EXPERIMENTAL
// Meant to stop the jittery movement when dodging the breath.
// Currently causes issues with making the bots unresponsive and often getting the healer killed.
// Semi-glitchy movement is better than semi-afk bots, so this is commented out until it gets improved
// bool cloudActive = false;
// // Need to check two conditions here - the persistent ground effect doesn't
// // seem to be detectable until 3-5 secs in, despite it dealing damage.
// // The initial breath triggers straight away but once it's over, the bots will run back on
// // to the frezzing cloud and take damage.
// // Therefore check both conditions and trigger on either.
// // Check this one early, if true then we don't need to iterate over any objects
// if (bossMount->HasAura(SPELL_FREEZING_CLOUD_BREATH))
// {
// cloudActive = true;
// }
// // Otherwise, check for persistent ground objects emitting the freezing cloud
// GuidVector objects = AI_VALUE(GuidVector, "nearest hostile npcs");
// for (auto i = objects.begin(); i != objects.end(); ++i)
// {
// Unit* unit = botAI->GetUnit(*i);
// if (unit && unit->GetEntry() == NPC_BREATH_TRIGGER)
// {
// Unit::AuraApplicationMap const& Auras = unit->GetAppliedAuras();
// for (Unit::AuraApplicationMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr)
// {
// Aura* aura = itr->second->GetBase();
// if (aura && aura->GetId() == SPELL_FREEZING_CLOUD)
// {
// cloudActive = true;
// break;
// }
// }
// }
// }
// if (cloudActive)
// {
// if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<AvoidFreezingCloudAction*>(action))
// {
// return 0.0f;
// }
// }
}
return 1.0f;
}

View File

@@ -0,0 +1,15 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUPMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONUPMULTIPLIERS_H
#include "Multiplier.h"
class SkadiMultiplier : public Multiplier
{
public:
SkadiMultiplier(PlayerbotAI* ai) : Multiplier(ai, "skadi the ruthless") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,25 @@
#include "UtgardePinnacleStrategy.h"
#include "UtgardePinnacleMultipliers.h"
void WotlkDungeonUPStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Svala Sorrowgrave
// Gortok Palehoof
// Skadi the Ruthless
// TODO: Harpoons launchable via GameObject. For now players should do them
triggers.push_back(new TriggerNode("freezing cloud",
NextAction::array(0, new NextAction("avoid freezing cloud", ACTION_RAID + 5), nullptr)));
triggers.push_back(new TriggerNode("skadi whirlwind",
NextAction::array(0, new NextAction("avoid skadi whirlwind", ACTION_RAID + 4), nullptr)));
// King Ymiron
// May need to avoid orb.. unclear if the generic avoid AoE does this well
}
void WotlkDungeonUPStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new SkadiMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,21 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUPTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONUPTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "UtgardePinnacleTriggers.h"
class WotlkDungeonUPTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonUPTriggerContext()
{
creators["freezing cloud"] = &WotlkDungeonUPTriggerContext::freezing_cloud;
creators["skadi whirlwind"] = &WotlkDungeonUPTriggerContext::whirlwind;
}
private:
static Trigger* freezing_cloud(PlayerbotAI* ai) { return new SkadiFreezingCloudTrigger(ai); }
static Trigger* whirlwind(PlayerbotAI* ai) { return new SkadiWhirlwindTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,48 @@
#include "Playerbots.h"
#include "UtgardePinnacleTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool SkadiFreezingCloudTrigger::IsActive()
{
Unit* bossMount = AI_VALUE2(Unit*, "find target", "grauf");
if (!bossMount) { return false; }
// Need to check two conditions here - the persistent ground effect doesn't
// seem to be detectable until 3-5 secs in, despite it dealing damage.
// The initial breath triggers straight away but once it's over, the bots will run back on
// to the frezzing cloud and take damage.
// Therefore check both conditions and trigger on either.
// Check this one first, if true then we don't need to iterate over any objects
if (bossMount->HasAura(SPELL_FREEZING_CLOUD_BREATH))
{
return true;
}
// Otherwise, check for persistent ground objects emitting the freezing cloud
GuidVector objects = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto i = objects.begin(); i != objects.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_BREATH_TRIGGER)
{
Unit::AuraApplicationMap const& Auras = unit->GetAppliedAuras();
for (Unit::AuraApplicationMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr)
{
Aura* aura = itr->second->GetBase();
if (aura && aura->GetId() == SPELL_FREEZING_CLOUD)
{
return true;
}
}
}
}
return false;
}
bool SkadiWhirlwindTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "skadi the ruthless");
return boss && boss->HasAura(SPELL_SKADI_WHIRLWIND);
}

View File

@@ -0,0 +1,39 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUPTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONUPTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
enum UtgardePinnacleIDs
{
// Skadi the Ruthless
SPELL_FREEZING_CLOUD_N = 47579,
SPELL_FREEZING_CLOUD_H = 60020,
SPELL_FREEZING_CLOUD_BREATH = 47592,
NPC_BREATH_TRIGGER = 28351,
SPELL_SKADI_WHIRLWIND_N = 50228,
SPELL_SKADI_WHIRLWIND_H = 59322,
};
#define SPELL_FREEZING_CLOUD DUNGEON_MODE(bot, SPELL_FREEZING_CLOUD_N, SPELL_FREEZING_CLOUD_H)
#define SPELL_SKADI_WHIRLWIND DUNGEON_MODE(bot, SPELL_SKADI_WHIRLWIND_N, SPELL_SKADI_WHIRLWIND_H)
// const float SKADI_BREATH_CENTRELINE = -512.46875f;
class SkadiFreezingCloudTrigger : public Trigger
{
public:
SkadiFreezingCloudTrigger(PlayerbotAI* ai) : Trigger(ai, "skadi freezing cloud") {}
bool IsActive() override;
};
class SkadiWhirlwindTrigger : public Trigger
{
public:
SkadiWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "skadi whirlwind") {}
bool IsActive() override;
};
#endif

View File

@@ -20,6 +20,15 @@ void NearestNpcsValue::FindUnits(std::list<Unit*>& targets)
bool NearestNpcsValue::AcceptUnit(Unit* unit) { return !unit->IsHostileTo(bot) && !unit->IsPlayer(); }
void NearestHostileNpcsValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);
Acore::UnitListSearcher<Acore::AnyUnitInObjectRangeCheck> searcher(bot, targets, u_check);
Cell::VisitAllObjects(bot, searcher, range);
}
bool NearestHostileNpcsValue::AcceptUnit(Unit* unit) { return unit->IsHostileTo(bot) && !unit->IsPlayer(); }
void NearestVehiclesValue::FindUnits(std::list<Unit*>& targets)
{
Acore::AnyUnitInObjectRangeCheck u_check(bot, range);

View File

@@ -24,6 +24,19 @@ protected:
bool AcceptUnit(Unit* unit) override;
};
class NearestHostileNpcsValue : public NearestUnitsValue
{
public:
NearestHostileNpcsValue(PlayerbotAI* botAI, float range = sPlayerbotAIConfig->sightDistance)
: NearestUnitsValue(botAI, "nearest hostile npcs", range)
{
}
protected:
void FindUnits(std::list<Unit*>& targets) override;
bool AcceptUnit(Unit* unit) override;
};
class NearestVehiclesValue : public NearestUnitsValue
{
public:

View File

@@ -104,6 +104,7 @@ public:
creators["nearest game objects no los"] = &ValueContext::nearest_game_objects_no_los;
creators["closest game objects"] = &ValueContext::closest_game_objects;
creators["nearest npcs"] = &ValueContext::nearest_npcs;
creators["nearest hostile npcs"] = &ValueContext::nearest_hostile_npcs;
creators["nearest totems"] = &ValueContext::nearest_totems;
creators["nearest vehicles"] = &ValueContext::nearest_vehicles;
creators["nearest vehicles far"] = &ValueContext::nearest_vehicles_far;
@@ -393,6 +394,7 @@ private:
}
static UntypedValue* log_level(PlayerbotAI* botAI) { return new LogLevelValue(botAI); }
static UntypedValue* nearest_npcs(PlayerbotAI* botAI) { return new NearestNpcsValue(botAI); }
static UntypedValue* nearest_hostile_npcs(PlayerbotAI* botAI) { return new NearestHostileNpcsValue(botAI); }
static UntypedValue* nearest_totems(PlayerbotAI* botAI) { return new NearestTotemsValue(botAI); }
static UntypedValue* nearest_vehicles(PlayerbotAI* botAI) { return new NearestVehiclesValue(botAI); }
static UntypedValue* nearest_vehicles_far(PlayerbotAI* botAI) { return new NearestVehiclesValue(botAI, 200.0f); }