mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
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:
@@ -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());
|
||||
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user