mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-02-17 09:04:36 +00:00
[HOT FIX] MS build issues regarding folder / command lenght usage or rc.exe (#2038)
This commit is contained in:
109
src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.cpp
Normal file
109
src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
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;
|
||||
return Move(bot->GetAngle(boss), distance + distanceExtra);
|
||||
}
|
||||
34
src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.h
Normal file
34
src/Ai/Dungeon/AzjolNerub/Action/AzjolNerubActions.h
Normal 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
|
||||
22
src/Ai/Dungeon/AzjolNerub/AzjolNerubActionContext.h
Normal file
22
src/Ai/Dungeon/AzjolNerub/AzjolNerubActionContext.h
Normal 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
|
||||
25
src/Ai/Dungeon/AzjolNerub/AzjolNerubTriggerContext.h
Normal file
25
src/Ai/Dungeon/AzjolNerub/AzjolNerubTriggerContext.h
Normal 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
|
||||
@@ -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)
|
||||
{
|
||||
if (!botAI->IsDps(bot)) { return 1.0f; }
|
||||
|
||||
// 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& target : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
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
|
||||
if (dynamic_cast<DpsAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (action->getThreatType() == Action::ActionThreatType::Aoe)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
15
src/Ai/Dungeon/AzjolNerub/Multiplier/AzjolNerubMultipliers.h
Normal file
15
src/Ai/Dungeon/AzjolNerub/Multiplier/AzjolNerubMultipliers.h
Normal 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
|
||||
30
src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.cpp
Normal file
30
src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#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("attack web wrap", ACTION_RAID + 5) }));
|
||||
triggers.push_back(new TriggerNode("krik'thir watchers",
|
||||
{ NextAction("krik'thir priority", ACTION_RAID + 4) }));
|
||||
|
||||
// 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("TODO", ACTION_MOVE + 5) }));
|
||||
triggers.push_back(new TriggerNode("anub'arak pound",
|
||||
{ NextAction("dodge pound", ACTION_MOVE + 5) }));
|
||||
}
|
||||
|
||||
void WotlkDungeonANStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new KrikthirMultiplier(botAI));
|
||||
}
|
||||
17
src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.h
Normal file
17
src/Ai/Dungeon/AzjolNerub/Strategy/AzjolNerubStrategy.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#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
|
||||
68
src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.cpp
Normal file
68
src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#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);
|
||||
}
|
||||
61
src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.h
Normal file
61
src/Ai/Dungeon/AzjolNerub/Trigger/AzjolNerubTriggers.h
Normal 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
|
||||
@@ -0,0 +1,53 @@
|
||||
#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,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,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,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,26 @@
|
||||
#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("explode ghoul spread", ACTION_MOVE + 5) }));
|
||||
|
||||
// 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("epoch stack", ACTION_MOVE + 5) }));
|
||||
|
||||
// Mal'Ganis
|
||||
|
||||
// Infinite Corruptor (Heroic only)
|
||||
}
|
||||
|
||||
void WotlkDungeonCoSStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new EpochMultiplier(botAI));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#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,31 @@
|
||||
#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
|
||||
175
src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.cpp
Normal file
175
src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "Playerbots.h"
|
||||
#include "DrakTharonKeepActions.h"
|
||||
#include "DrakTharonKeepStrategy.h"
|
||||
|
||||
bool CorpseExplodeSpreadAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "trollgore");
|
||||
if (!boss) { return false; }
|
||||
|
||||
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)
|
||||
{
|
||||
float currentDistance = bot->GetExactDist2d(unit);
|
||||
if (currentDistance < distance + distanceExtra)
|
||||
{
|
||||
return MoveAway(unit, distance + distanceExtra - currentDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AvoidArcaneFieldAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
|
||||
if (!boss) { return false; }
|
||||
|
||||
float distance = 12.0f; // 11 unit radius, 1 unit added as buffer
|
||||
if (bot->GetExactDist2d(boss) < distance)
|
||||
{
|
||||
return MoveAway(boss, distance - bot->GetExactDist2d(boss));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NovosDefaultPositionAction::isUseful()
|
||||
{
|
||||
// Distance to tether to centre of room
|
||||
float threshold = 15.0f;
|
||||
return bot->GetDistance(NOVOS_PARTY_POSITION) > threshold;
|
||||
}
|
||||
bool NovosDefaultPositionAction::Execute(Event event)
|
||||
{
|
||||
float clusterDistance = 4.0f;
|
||||
// Only reposition if we're not killing anything
|
||||
if (!bot->GetTarget())
|
||||
{
|
||||
return MoveNear(bot->GetMap()->GetId(),
|
||||
NOVOS_PARTY_POSITION.GetPositionX(),
|
||||
NOVOS_PARTY_POSITION.GetPositionY(),
|
||||
NOVOS_PARTY_POSITION.GetPositionZ(),
|
||||
clusterDistance, MovementPriority::MOVEMENT_NORMAL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NovosTargetPriorityAction::Execute(Event event)
|
||||
{
|
||||
// TODO: This can be improved, some parts are still buggy.
|
||||
// But it works for now and this fight is very easy
|
||||
|
||||
// Designate a dps char to handle the stairs adds.
|
||||
// This is probably better as a melee, so just pick the first
|
||||
// melee dps in the party. If none exist, pick the first ranged.
|
||||
|
||||
// TODO: Switch to botAI->Index instead, cleaner
|
||||
Player* stairsDps = nullptr;
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
Player* groupMember = botAI->GetPlayer(member);
|
||||
if (!groupMember) { continue; }
|
||||
|
||||
if (botAI->IsDps(groupMember))
|
||||
{
|
||||
if (botAI->IsMelee(groupMember))
|
||||
{
|
||||
// Found our first melee dps, grab handle and break
|
||||
stairsDps = groupMember;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ranged dps, only set if none already assigned.
|
||||
// Don't break, we want to keep searching for a melee instead.
|
||||
if (!stairsDps)
|
||||
{
|
||||
stairsDps = groupMember;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Unit* selectedTargets[2] = {nullptr, 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) { continue; }
|
||||
uint32 creatureId = unit->GetEntry();
|
||||
|
||||
// Tank priority:
|
||||
// Hulking Corpse -> Crystal Handler
|
||||
if (botAI->IsTank(bot))
|
||||
{
|
||||
if (creatureId == NPC_HULKING_CORPSE)
|
||||
{
|
||||
selectedTargets[0] = unit;
|
||||
}
|
||||
else if (creatureId == NPC_CRYSTAL_HANDLER)
|
||||
{
|
||||
selectedTargets[1] = unit;
|
||||
}
|
||||
}
|
||||
// Dedicated stairs dps is assigned.
|
||||
// Priority: Risen Shadowcaster -> Fetid Troll Corpse
|
||||
else if (stairsDps && bot == stairsDps)
|
||||
{
|
||||
if (creatureId == NPC_RISEN_SHADOWCASTER)
|
||||
{
|
||||
if (!selectedTargets[0] || bot->GetDistance(unit) < bot->GetDistance(selectedTargets[0]) - 5.0f)
|
||||
{
|
||||
selectedTargets[0] = unit;
|
||||
}
|
||||
|
||||
}
|
||||
else if (creatureId == NPC_FETID_TROLL_CORPSE)
|
||||
{
|
||||
if (!selectedTargets[1] || bot->GetDistance(unit) < bot->GetDistance(selectedTargets[1]) - 5.0f)
|
||||
{
|
||||
selectedTargets[1] = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
// All other dps priority:
|
||||
// Crystal Handler -> Hulking Corpse
|
||||
else if (botAI->IsDps(bot))
|
||||
{
|
||||
if (creatureId == NPC_CRYSTAL_HANDLER)
|
||||
{
|
||||
selectedTargets[0] = unit;
|
||||
}
|
||||
else if (creatureId == NPC_HULKING_CORPSE)
|
||||
{
|
||||
selectedTargets[1] = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Unit* primaryTarget : selectedTargets)
|
||||
{
|
||||
// Attack the first valid target in the priority list
|
||||
if (primaryTarget)
|
||||
{
|
||||
if (AI_VALUE(Unit*, "current target") != primaryTarget)
|
||||
{
|
||||
// bot->Yell(primaryTarget->GetName(), LANG_UNIVERSAL);
|
||||
return Attack(primaryTarget);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
67
src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.h
Normal file
67
src/Ai/Dungeon/DraktharonKeep/Action/DrakTharonKeepActions.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKACTIONS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONDTKACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "DrakTharonKeepTriggers.h"
|
||||
|
||||
const Position NOVOS_PARTY_POSITION = Position(-378.852f, -760.349f, 28.587f);
|
||||
|
||||
class CorpseExplodeSpreadAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
CorpseExplodeSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "corpse explode spread") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AvoidArcaneFieldAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidArcaneFieldAction(PlayerbotAI* ai) : MovementAction(ai, "avoid arcane field") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class NovosDefaultPositionAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
NovosDefaultPositionAction(PlayerbotAI* ai) : MovementAction(ai, "novos default position") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class NovosTargetPriorityAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
NovosTargetPriorityAction(PlayerbotAI* ai) : AttackAction(ai, "novos target priority") {}
|
||||
bool Execute(Event event) override;
|
||||
// bool isUseful() override;
|
||||
};
|
||||
|
||||
class CastSlayingStrikeAction : public CastMeleeSpellAction
|
||||
{
|
||||
public:
|
||||
CastSlayingStrikeAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "slaying strike") {}
|
||||
};
|
||||
|
||||
class CastTauntAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastTauntAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "taunt") {}
|
||||
};
|
||||
|
||||
class CastBoneArmorAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastBoneArmorAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "bone armor") {}
|
||||
};
|
||||
|
||||
class CastTouchOfLifeAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastTouchOfLifeAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "touch of life") {}
|
||||
};
|
||||
|
||||
#endif
|
||||
32
src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepActionContext.h
Normal file
32
src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepActionContext.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONDTKACTIONCONTEXT_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "DrakTharonKeepActions.h"
|
||||
|
||||
class WotlkDungeonDTKActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonDTKActionContext() {
|
||||
creators["corpse explode spread"] = &WotlkDungeonDTKActionContext::corpse_explode_spread;
|
||||
creators["avoid arcane field"] = &WotlkDungeonDTKActionContext::avoid_arcane_field;
|
||||
creators["novos positioning"] = &WotlkDungeonDTKActionContext::novos_positioning;
|
||||
creators["novos target priority"] = &WotlkDungeonDTKActionContext::novos_target_priority;
|
||||
creators["slaying strike"] = &WotlkDungeonDTKActionContext::slaying_strike;
|
||||
creators["tharonja taunt"] = &WotlkDungeonDTKActionContext::taunt;
|
||||
creators["bone armor"] = &WotlkDungeonDTKActionContext::bone_armor;
|
||||
creators["touch of life"] = &WotlkDungeonDTKActionContext::touch_of_life;
|
||||
}
|
||||
private:
|
||||
static Action* corpse_explode_spread(PlayerbotAI* ai) { return new CorpseExplodeSpreadAction(ai); }
|
||||
static Action* avoid_arcane_field(PlayerbotAI* ai) { return new AvoidArcaneFieldAction(ai); }
|
||||
static Action* novos_positioning(PlayerbotAI* ai) { return new NovosDefaultPositionAction(ai); }
|
||||
static Action* novos_target_priority(PlayerbotAI* ai) { return new NovosTargetPriorityAction(ai); }
|
||||
static Action* slaying_strike(PlayerbotAI* ai) { return new CastSlayingStrikeAction(ai); }
|
||||
static Action* taunt(PlayerbotAI* ai) { return new CastTauntAction(ai); }
|
||||
static Action* bone_armor(PlayerbotAI* ai) { return new CastBoneArmorAction(ai); }
|
||||
static Action* touch_of_life(PlayerbotAI* ai) { return new CastTouchOfLifeAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
28
src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepTriggerContext.h
Normal file
28
src/Ai/Dungeon/DraktharonKeep/DrakTharonKeepTriggerContext.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKTRIGGERCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONDTKTRIGGERCONTEXT_H
|
||||
|
||||
#include "NamedObjectContext.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "DrakTharonKeepTriggers.h"
|
||||
|
||||
class WotlkDungeonDTKTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonDTKTriggerContext()
|
||||
{
|
||||
creators["corpse explode"] = &WotlkDungeonDTKTriggerContext::corpse_explode;
|
||||
creators["arcane field"] = &WotlkDungeonDTKTriggerContext::arcane_field;
|
||||
// creators["crystal handler"] = &WotlkDungeonDTKTriggerContext::crystal_handler;
|
||||
creators["gift of tharon'ja"] = &WotlkDungeonDTKTriggerContext::gift_of_tharonja;
|
||||
creators["tharon'ja out of melee"] = &WotlkDungeonDTKTriggerContext::tharonja_out_of_melee;
|
||||
|
||||
}
|
||||
private:
|
||||
static Trigger* corpse_explode(PlayerbotAI* ai) { return new CorpseExplodeTrigger(ai); }
|
||||
static Trigger* arcane_field(PlayerbotAI* ai) { return new ArcaneFieldTrigger(ai); }
|
||||
// static Trigger* crystal_handler(PlayerbotAI* ai) { return new CrystalHandlerTrigger(ai); }
|
||||
static Trigger* gift_of_tharonja(PlayerbotAI* ai) { return new GiftOfTharonjaTrigger(ai); }
|
||||
static Trigger* tharonja_out_of_melee(PlayerbotAI* ai) { return new TwoTriggers(ai, "gift of tharon'ja", "enemy out of melee"); }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "DrakTharonKeepMultipliers.h"
|
||||
#include "DrakTharonKeepActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "MovementActions.h"
|
||||
#include "DrakTharonKeepTriggers.h"
|
||||
#include "Action.h"
|
||||
|
||||
float NovosMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
if (boss->FindCurrentSpellBySpellId(SPELL_ARCANE_FIELD) && bot->GetTarget())
|
||||
{
|
||||
if (dynamic_cast<DpsAssistAction*>(action)
|
||||
|| dynamic_cast<TankAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float TharonjaMultiplier::GetValue(Action* action)
|
||||
{
|
||||
if (!bot->HasAura(SPELL_GIFT_OF_THARONJA)) { return 1.0f; }
|
||||
|
||||
// Suppress all skills that are not enabled in skeleton form.
|
||||
// Still allow non-ability actions such as movement
|
||||
if (dynamic_cast<CastSpellAction*>(action)
|
||||
&& !dynamic_cast<CastSlayingStrikeAction*>(action)
|
||||
&& !dynamic_cast<CastTauntAction*>(action)
|
||||
&& !dynamic_cast<CastBoneArmorAction*>(action)
|
||||
&& !dynamic_cast<CastTouchOfLifeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
// Also suppress FleeAction to prevent ranged characters from avoiding melee range
|
||||
if (dynamic_cast<FleeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Tanks should only taunt, no slaying strike
|
||||
if (botAI->IsTank(bot))
|
||||
{
|
||||
if (dynamic_cast<CastSlayingStrikeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
// Dps & healer should not taunt
|
||||
else
|
||||
{
|
||||
if (dynamic_cast<CastTauntAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKMULTIPLIERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONDTKMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class NovosMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
NovosMultiplier(PlayerbotAI* ai) : Multiplier(ai, "novos the summoner") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class TharonjaMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
TharonjaMultiplier(PlayerbotAI* ai) : Multiplier(ai, "the prophet tharon'ja") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "DrakTharonKeepStrategy.h"
|
||||
#include "DrakTharonKeepMultipliers.h"
|
||||
|
||||
void WotlkDungeonDTKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
{
|
||||
// Trollgore
|
||||
triggers.push_back(new TriggerNode("corpse explode",
|
||||
{ NextAction("corpse explode spread", ACTION_MOVE + 5) }));
|
||||
|
||||
// Novos the Summoner
|
||||
// TODO: Can be improved - it's a pretty easy fight but complex to program, revisit if needed
|
||||
triggers.push_back(new TriggerNode("arcane field",
|
||||
{ NextAction("avoid arcane field", ACTION_MOVE + 5) }));
|
||||
triggers.push_back(new TriggerNode("arcane field",
|
||||
{ NextAction("novos positioning", ACTION_MOVE + 4) }));
|
||||
triggers.push_back(new TriggerNode("arcane field",
|
||||
{ NextAction("novos target priority", ACTION_NORMAL + 1) }));
|
||||
|
||||
// King Dred
|
||||
// TODO: Fear ward / tremor totem, or general anti-fear strat development
|
||||
|
||||
//The Prophet Tharon'ja
|
||||
triggers.push_back(new TriggerNode("gift of tharon'ja",
|
||||
{ NextAction("touch of life", ACTION_NORMAL + 5) }));
|
||||
triggers.push_back(new TriggerNode("gift of tharon'ja",
|
||||
{ NextAction("bone armor", ACTION_NORMAL + 4) }));
|
||||
// Run ranged chars (who would normally stand at range) into melee, to dps in skeleton form
|
||||
triggers.push_back(new TriggerNode("tharon'ja out of melee",
|
||||
{ NextAction("reach melee", ACTION_NORMAL + 3) }));
|
||||
triggers.push_back(new TriggerNode("gift of tharon'ja",
|
||||
{ NextAction("taunt", ACTION_NORMAL + 2) }));
|
||||
triggers.push_back(new TriggerNode("gift of tharon'ja",
|
||||
{ NextAction("slaying strike", ACTION_NORMAL + 2) }));
|
||||
}
|
||||
|
||||
void WotlkDungeonDTKStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new NovosMultiplier(botAI));
|
||||
multipliers.push_back(new TharonjaMultiplier(botAI));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKSTRATEGY_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONDTKSTRATEGY_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
class WotlkDungeonDTKStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
WotlkDungeonDTKStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
virtual std::string const getName() override { return "drak'tharon keep"; }
|
||||
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
|
||||
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "Playerbots.h"
|
||||
#include "DrakTharonKeepTriggers.h"
|
||||
#include "AiObject.h"
|
||||
#include "AiObjectContext.h"
|
||||
|
||||
bool CorpseExplodeTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "trollgore");
|
||||
if (!boss) { return false; }
|
||||
|
||||
float distance = 6.0f; // 5 unit radius, 1 unit added as buffer
|
||||
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)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArcaneFieldTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
|
||||
if (boss)
|
||||
{
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_FIELD);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// bool CrystalHandlerTrigger::IsActive()
|
||||
// {
|
||||
// Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
|
||||
// if (!boss) { 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_CRYSTAL_HANDLER)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
bool GiftOfTharonjaTrigger::IsActive()
|
||||
{
|
||||
return bot->HasAura(SPELL_GIFT_OF_THARONJA);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKTRIGGERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONDTKTRIGGERS_H
|
||||
|
||||
#include "Trigger.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "GenericTriggers.h"
|
||||
#include "DungeonStrategyUtils.h"
|
||||
|
||||
enum DrakTharonIDs
|
||||
{
|
||||
// Trollgore
|
||||
NPC_DRAKKARI_INVADER = 27709,
|
||||
|
||||
// Novos the Summoner
|
||||
NPC_NOVOS = 26631,
|
||||
SPELL_ARCANE_FIELD = 47346,
|
||||
NPC_CRYSTAL_HANDLER = 26627,
|
||||
NPC_HULKING_CORPSE = 27597,
|
||||
NPC_RISEN_SHADOWCASTER = 27600,
|
||||
NPC_FETID_TROLL_CORPSE = 27598,
|
||||
|
||||
// The Prophet Tharon'ja
|
||||
SPELL_GIFT_OF_THARONJA = 52509,
|
||||
};
|
||||
|
||||
class CorpseExplodeTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
CorpseExplodeTrigger(PlayerbotAI* ai) : Trigger(ai, "corpse explode") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ArcaneFieldTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ArcaneFieldTrigger(PlayerbotAI* ai) : Trigger(ai, "arcane field") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
// class CrystalHandlerTrigger : public Trigger
|
||||
// {
|
||||
// public:
|
||||
// CrystalHandlerTrigger(PlayerbotAI* ai) : Trigger(ai, "crystal handler") {}
|
||||
// bool IsActive() override;
|
||||
// };
|
||||
|
||||
class GiftOfTharonjaTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
GiftOfTharonjaTrigger(PlayerbotAI* ai) : Trigger(ai, "gift of tharon'ja") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
87
src/Ai/Dungeon/DungeonStrategyContext.h
Normal file
87
src/Ai/Dungeon/DungeonStrategyContext.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#ifndef _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H
|
||||
#define _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H
|
||||
|
||||
#include "Strategy.h"
|
||||
#include "UtgardeKeep/Strategy/UtgardeKeepStrategy.h"
|
||||
#include "Nexus/Strategy/NexusStrategy.h"
|
||||
#include "AzjolNerub/Strategy/AzjolNerubStrategy.h"
|
||||
#include "OldKingdom/Strategy/OldKingdomStrategy.h"
|
||||
#include "DraktharonKeep/Strategy/DrakTharonKeepStrategy.h"
|
||||
#include "VioletHold/Strategy/VioletHoldStrategy.h"
|
||||
#include "Gundrak/Strategy/GundrakStrategy.h"
|
||||
#include "HallsOfStone/Strategy/HallsOfStoneStrategy.h"
|
||||
#include "HallsOfLightning/Strategy/HallsOfLightningStrategy.h"
|
||||
#include "Oculus/Strategy/OculusStrategy.h"
|
||||
#include "UtgardePinnacle/Strategy/UtgardePinnacleStrategy.h"
|
||||
#include "CullingOfStratholme/Strategy/CullingOfStratholmeStrategy.h"
|
||||
#include "ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.h"
|
||||
#include "PitOfSaron/Strategy/PitOfSaronStrategy.h"
|
||||
#include "TrialOfTheChampion/Strategy/TrialOfTheChampionStrategy.h"
|
||||
|
||||
/*
|
||||
Full list/TODO:
|
||||
|
||||
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 WotlkDungeonNexStrategy(botAI); }
|
||||
static Strategy* wotlk_an(PlayerbotAI* botAI) { return new WotlkDungeonANStrategy(botAI); }
|
||||
static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonOKStrategy(botAI); }
|
||||
static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonDTKStrategy(botAI); }
|
||||
static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonVHStrategy(botAI); }
|
||||
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_up(PlayerbotAI* botAI) { return new WotlkDungeonUPStrategy(botAI); }
|
||||
static Strategy* wotlk_cos(PlayerbotAI* botAI) { return new WotlkDungeonCoSStrategy(botAI); }
|
||||
static Strategy* wotlk_fos(PlayerbotAI* botAI) { return new WotlkDungeonFoSStrategy(botAI); }
|
||||
static Strategy* wotlk_pos(PlayerbotAI* botAI) { return new WotlkDungeonPoSStrategy(botAI); }
|
||||
static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonToCStrategy(botAI); }
|
||||
// NYI from here down
|
||||
static Strategy* wotlk_hor(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
||||
};
|
||||
|
||||
#endif
|
||||
20
src/Ai/Dungeon/DungeonStrategyUtils.h
Normal file
20
src/Ai/Dungeon/DungeonStrategyUtils.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#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
|
||||
165
src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.cpp
Normal file
165
src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "Playerbots.h"
|
||||
#include "ForgeOfSoulsActions.h"
|
||||
#include "ForgeOfSoulsStrategy.h"
|
||||
#include "SharedDefines.h"
|
||||
|
||||
bool MoveFromBronjahmAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm");
|
||||
if (!boss)
|
||||
return false;
|
||||
|
||||
if (bot->GetExactDist2d(boss) < 10.0f)
|
||||
return FleePosition(boss->GetPosition(), 15.0f, 2000U);
|
||||
else
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AttackCorruptedSoulFragmentAction::Execute(Event event)
|
||||
{
|
||||
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||||
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
|
||||
|
||||
// If no valid skull target, search for corrupted soul fragment
|
||||
Unit* empoweredPrince = nullptr;
|
||||
for (auto i = targets.begin(); i != targets.end(); ++i)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(*i);
|
||||
if (!unit || !unit->IsAlive())
|
||||
continue;
|
||||
|
||||
if (unit->GetEntry() == NPC_CORRUPTED_SOUL_FRAGMENT)
|
||||
{
|
||||
empoweredPrince = unit;
|
||||
|
||||
// Mark corrupted soul fragment with skull if in group and not already marked
|
||||
if (Group* group = bot->GetGroup())
|
||||
{
|
||||
ObjectGuid currentSkullGuid = group->GetTargetIcon(7);
|
||||
if (currentSkullGuid.IsEmpty() || currentSkullGuid != unit->GetGUID())
|
||||
{
|
||||
group->SetTargetIcon(7, bot->GetGUID(), unit->GetGUID()); // 7 = skull
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BronjahmGroupPositionAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm");
|
||||
if (!boss)
|
||||
return false;
|
||||
|
||||
Aura* aura = botAI->GetAura("soulstorm", boss);
|
||||
bool hasAura = aura;
|
||||
|
||||
Unit* corruptedSoul = bot->FindNearestCreature(NPC_CORRUPTED_SOUL_FRAGMENT, 50.0f);
|
||||
bool activeSoulExists = corruptedSoul && corruptedSoul->IsAlive();
|
||||
|
||||
if (botAI->IsTank(bot) && botAI->HasAggro(boss))
|
||||
{
|
||||
// If any corrupted soul exists, handle positioning carefully
|
||||
if (activeSoulExists)
|
||||
{
|
||||
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||
for (auto& npc : npcs)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npc);
|
||||
if (unit && unit->GetEntry() == NPC_CORRUPTED_SOUL_FRAGMENT)
|
||||
{
|
||||
// Soul exists - check positions
|
||||
float soulToBossDist = unit->GetExactDist2d(boss);
|
||||
float tankToBossDist = bot->GetExactDist2d(boss);
|
||||
float soulToTankDist = unit->GetExactDist2d(bot);
|
||||
|
||||
// Handle edge case: if soul is between tank and boss
|
||||
// This happens when: (tankToBossDist > soulToBossDist) AND (soulToTankDist < tankToBossDist)
|
||||
if (tankToBossDist > soulToBossDist && soulToTankDist < tankToBossDist)
|
||||
{
|
||||
// Calculate position 5 yards behind boss from the soul's perspective
|
||||
float angle = boss->GetAngle(unit); // Angle from boss to soul
|
||||
float oppositeAngle = Position::NormalizeOrientation(angle + M_PI); // Opposite direction
|
||||
|
||||
// Calculate position 5 yards behind boss
|
||||
float x = boss->GetPositionX() + 5.0f * cos(oppositeAngle);
|
||||
float y = boss->GetPositionY() + 5.0f * sin(oppositeAngle);
|
||||
float z = boss->GetPositionZ();
|
||||
|
||||
return MoveTo(bot->GetMapId(), x, y, z, false, false, false, true,
|
||||
MovementPriority::MOVEMENT_NORMAL);
|
||||
}
|
||||
|
||||
// If soul is near boss, flee from boss
|
||||
if (soulToBossDist < 10.0f)
|
||||
return FleePosition(unit->GetPosition(), 13.0f, 1000U);
|
||||
|
||||
// If soul exists but none of the above conditions, don't move to tank position yet
|
||||
bot->SetFacingToObject(boss);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No souls exist, safe to move to tank position
|
||||
if (bot->GetExactDist2d(BRONJAHM_TANK_POSITION) > 5.0f)
|
||||
{
|
||||
return MoveTo(bot->GetMapId(), BRONJAHM_TANK_POSITION.GetPositionX(),
|
||||
BRONJAHM_TANK_POSITION.GetPositionY(), BRONJAHM_TANK_POSITION.GetPositionZ(), false,
|
||||
false, false, true, MovementPriority::MOVEMENT_NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float maxMovement = 10.0f;
|
||||
|
||||
if (bot->GetExactDist2d(boss) > maxMovement && !activeSoulExists && (hasAura || boss->FindCurrentSpellBySpellId(SPELL_SOULSTORM_VISUAL) || boss->FindCurrentSpellBySpellId(SPELL_SOULSTORM_VISUAL2)))
|
||||
{
|
||||
if (botAI->IsRanged(bot))
|
||||
{
|
||||
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 6.5f, maxMovement));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss) - 2.0f, maxMovement));
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BronjahmGroupPositionAction::isUseful() { return true; }
|
||||
|
||||
bool DevourerOfSoulsAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "devourer of souls");
|
||||
if (!boss)
|
||||
return false;
|
||||
|
||||
Aura* aura = botAI->GetAura("mirrored soul", boss);
|
||||
bool hasAura = aura;
|
||||
|
||||
if (!botAI->IsTank(bot) && !botAI->IsHeal(bot) && hasAura)
|
||||
{
|
||||
// Calculate the opposite direction
|
||||
float angle = bot->GetAngle(boss);
|
||||
float newAngle = Position::NormalizeOrientation(angle + M_PI); // Add 180 degrees (PI radians)
|
||||
|
||||
// Set the bot's orientation to face away from DoS = no attacks
|
||||
bot->SetFacingTo(newAngle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
51
src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.h
Normal file
51
src/Ai/Dungeon/ForgeOfSouls/Action/ForgeOfSoulsActions.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONFOSACTIONS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONFOSACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ForgeOfSoulsTriggers.h"
|
||||
|
||||
const Position BRONJAHM_TANK_POSITION = Position(5297.920f, 2506.698f, 686.068f);
|
||||
|
||||
class MoveFromBronjahmAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveFromBronjahmAction(PlayerbotAI* ai) : MovementAction(ai, "move from bronjahm") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AttackCorruptedSoulFragmentAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackCorruptedSoulFragmentAction(PlayerbotAI* ai) : AttackAction(ai, "attack corrupted soul fragment") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class BronjahmGroupPositionAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
BronjahmGroupPositionAction(PlayerbotAI* ai) : AttackAction(ai, "bronjahm group position") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class DevourerOfSoulsAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
DevourerOfSoulsAction(PlayerbotAI * ai, float distance = 10.0f, float delta_angle = M_PI / 8)
|
||||
: AttackAction(ai, "devourer of souls")
|
||||
{
|
||||
this->distance = distance;
|
||||
this->delta_angle = delta_angle;
|
||||
}
|
||||
virtual bool Execute(Event event);
|
||||
|
||||
protected:
|
||||
float distance, delta_angle;
|
||||
};
|
||||
|
||||
#endif
|
||||
25
src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsActionContext.h
Normal file
25
src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsActionContext.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONFOSACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONFOSACTIONCONTEXT_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "ForgeOfSoulsActions.h"
|
||||
|
||||
class WotlkDungeonFoSActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonFoSActionContext()
|
||||
{
|
||||
creators["move from bronjahm"] = &WotlkDungeonFoSActionContext::move_from_bronjahm;
|
||||
creators["attack corrupted soul fragment"] = &WotlkDungeonFoSActionContext::attack_corrupted_soul_fragment;
|
||||
creators["bronjahm group position"] = &WotlkDungeonFoSActionContext::bronjahm_group_position;
|
||||
creators["devourer of souls"] = &WotlkDungeonFoSActionContext::devourer_of_souls;
|
||||
}
|
||||
private:
|
||||
static Action* move_from_bronjahm(PlayerbotAI* ai) { return new MoveFromBronjahmAction(ai); }
|
||||
static Action* attack_corrupted_soul_fragment(PlayerbotAI* ai) { return new AttackCorruptedSoulFragmentAction(ai); }
|
||||
static Action* bronjahm_group_position(PlayerbotAI* ai) { return new BronjahmGroupPositionAction(ai); }
|
||||
static Action* devourer_of_souls(PlayerbotAI* ai) { return new DevourerOfSoulsAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
26
src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsTriggerContext.h
Normal file
26
src/Ai/Dungeon/ForgeOfSouls/ForgeOfSoulsTriggerContext.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONFOSTRIGGERCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONFOSTRIGGERCONTEXT_H
|
||||
|
||||
#include "NamedObjectContext.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "ForgeOfSoulsTriggers.h"
|
||||
|
||||
class WotlkDungeonFoSTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonFoSTriggerContext()
|
||||
{
|
||||
creators["bronjahm position"] = &WotlkDungeonFoSTriggerContext::bronjahm_position;
|
||||
creators["move from bronjahm"] = &WotlkDungeonFoSTriggerContext::move_from_bronjahm;
|
||||
creators["switch to soul fragment"] = &WotlkDungeonFoSTriggerContext::switch_to_soul_fragment;
|
||||
creators["devourer of souls"] = &WotlkDungeonFoSTriggerContext::devourer_of_souls;
|
||||
}
|
||||
|
||||
private:
|
||||
static Trigger* move_from_bronjahm(PlayerbotAI* ai) { return new MoveFromBronjahmTrigger(ai); }
|
||||
static Trigger* switch_to_soul_fragment(PlayerbotAI* ai) { return new SwitchToSoulFragment(ai); }
|
||||
static Trigger* bronjahm_position(PlayerbotAI* ai) { return new BronjahmPositionTrigger(ai); }
|
||||
static Trigger* devourer_of_souls(PlayerbotAI* ai) { return new DevourerOfSoulsTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif // !_PLAYERBOT_WOTLKDUNGEONFOSTRIGGERCONTEXT_H
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "ForgeOfSoulsMultipliers.h"
|
||||
#include "ForgeOfSoulsActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "MovementActions.h"
|
||||
#include "ForgeOfSoulsTriggers.h"
|
||||
#include "ForgeOfSoulsActions.h"
|
||||
|
||||
float BronjahmMultiplier::GetValue(Action* action) {
|
||||
Unit* boss = AI_VALUE2(Unit *, "find target", "bronjahm");
|
||||
if (!boss)
|
||||
return 1.0f;
|
||||
|
||||
if (dynamic_cast<TankAssistAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
if (bot->HasAura(SPELL_CORRUPT_SOUL))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<MoveFromBronjahmAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float AttackFragmentMultiplier::GetValue(Action* action)
|
||||
{
|
||||
|
||||
Unit* fragment = nullptr;
|
||||
|
||||
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||
|
||||
for (auto& target : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && unit->GetEntry() == NPC_CORRUPTED_SOUL_FRAGMENT)
|
||||
{
|
||||
fragment = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fragment && botAI->IsDps(bot) && dynamic_cast<BronjahmGroupPositionAction*>(action))
|
||||
return 0.0f;
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONFOSMULTIPLIERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONFOSMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class BronjahmMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
BronjahmMultiplier(PlayerbotAI* ai) : Multiplier(ai, "bronjahm") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class AttackFragmentMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
AttackFragmentMultiplier(PlayerbotAI* ai) : Multiplier(ai, "attack fragment") { }
|
||||
|
||||
float GetValue(Action* action) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "ForgeOfSoulsStrategy.h"
|
||||
#include "ForgeOfSoulsMultipliers.h"
|
||||
|
||||
void WotlkDungeonFoSStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
triggers.push_back(new TriggerNode("move from bronjahm",
|
||||
{ NextAction("move from bronjahm", ACTION_MOVE + 5) }));
|
||||
triggers.push_back(new TriggerNode("switch to soul fragment",
|
||||
{ NextAction("attack corrupted soul fragment", ACTION_RAID + 2) }));
|
||||
triggers.push_back(new TriggerNode("bronjahm position",
|
||||
{ NextAction("bronjahm group position", ACTION_RAID + 1) }));
|
||||
triggers.push_back(new TriggerNode("devourer of souls",
|
||||
{ NextAction("devourer of souls", ACTION_RAID + 1) }));
|
||||
}
|
||||
|
||||
void WotlkDungeonFoSStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
||||
{
|
||||
multipliers.push_back(new BronjahmMultiplier(botAI));
|
||||
}
|
||||
16
src/Ai/Dungeon/ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.h
Normal file
16
src/Ai/Dungeon/ForgeOfSouls/Strategy/ForgeOfSoulsStrategy.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONFOSSTRATEGY_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONFOSSTRATEGY_H
|
||||
#include "Multiplier.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
class WotlkDungeonFoSStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
WotlkDungeonFoSStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
std::string const getName() override { return "forge of souls"; }
|
||||
void InitTriggers(std::vector<TriggerNode*> &triggers) override;
|
||||
void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // !_PLAYERBOT_WOTLKDUNGEONFOSSTRATEGY_H
|
||||
55
src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.cpp
Normal file
55
src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "Playerbots.h"
|
||||
#include "ForgeOfSoulsTriggers.h"
|
||||
#include "AiObject.h"
|
||||
#include "AiObjectContext.h"
|
||||
|
||||
bool MoveFromBronjahmTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm");
|
||||
if (!boss)
|
||||
return false;
|
||||
|
||||
if (!boss->FindCurrentSpellBySpellId(SPELL_CORRUPT_SOUL))
|
||||
return false;
|
||||
|
||||
if (!bot->HasAura(SPELL_CORRUPT_SOUL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SwitchToSoulFragment::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm");
|
||||
if (!boss)
|
||||
return false;
|
||||
|
||||
Unit* corruptedSoul = bot->FindNearestCreature(NPC_CORRUPTED_SOUL_FRAGMENT, 50.0f);
|
||||
bool activeSoulExists = corruptedSoul && corruptedSoul->IsAlive();
|
||||
|
||||
if (!activeSoulExists)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BronjahmPositionTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm");
|
||||
if (!boss)
|
||||
return false;
|
||||
|
||||
if (bot->HasAura(SPELL_CORRUPT_SOUL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DevourerOfSoulsTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "devourer of souls");
|
||||
if (!boss)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
53
src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.h
Normal file
53
src/Ai/Dungeon/ForgeOfSouls/Trigger/ForgeOfSoulsTriggers.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONFOSTRIGGERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONFOSTRIGGERS_H
|
||||
|
||||
#include "Trigger.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "GenericTriggers.h"
|
||||
#include "DungeonStrategyUtils.h"
|
||||
|
||||
enum ForgeOfSoulsBronjahmIDs
|
||||
{
|
||||
// Bronjahm
|
||||
NPC_CORRUPTED_SOUL_FRAGMENT = 36535,
|
||||
|
||||
SPELL_CORRUPT_SOUL = 68839,
|
||||
SPELL_SOULSTORM_VISUAL = 68870,
|
||||
SPELL_SOULSTORM_VISUAL2 = 68904,
|
||||
SPELL_SOULSTORM = 68872,
|
||||
|
||||
// Devourer of Souls
|
||||
SPELL_WAILING_SOULS = 68899,
|
||||
};
|
||||
|
||||
class MoveFromBronjahmTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
MoveFromBronjahmTrigger(PlayerbotAI* ai) : Trigger(ai, "move from bronjahm") {}
|
||||
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class SwitchToSoulFragment : public Trigger
|
||||
{
|
||||
public:
|
||||
SwitchToSoulFragment(PlayerbotAI* ai) : Trigger(ai, "switch to soul fragment") {}
|
||||
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class BronjahmPositionTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
BronjahmPositionTrigger(PlayerbotAI* ai) : Trigger(ai, "bronjahm position") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class DevourerOfSoulsTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
DevourerOfSoulsTrigger(PlayerbotAI* ai) : Trigger(ai, "devourer of souls") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
70
src/Ai/Dungeon/Gundrak/Action/GundrakActions.cpp
Normal file
70
src/Ai/Dungeon/Gundrak/Action/GundrakActions.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "Playerbots.h"
|
||||
#include "GundrakActions.h"
|
||||
#include "GundrakStrategy.h"
|
||||
|
||||
bool AvoidPoisonNovaAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran");
|
||||
if (!boss) { return false; }
|
||||
|
||||
float distance = bot->GetExactDist2d(boss->GetPosition());
|
||||
float radius = 15.0f;
|
||||
float distanceExtra = 2.0f;
|
||||
|
||||
if (distance < radius + distanceExtra)
|
||||
{
|
||||
return MoveAway(boss, radius + distanceExtra - distance);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AttackSnakeWrapAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran");
|
||||
if (!boss) { return false; }
|
||||
|
||||
Unit* snakeWrap = 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& target : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && unit->GetEntry() == NPC_SNAKE_WRAP)
|
||||
{
|
||||
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||||
if (!currentTarget || currentTarget->GetEntry() != NPC_SNAKE_WRAP)
|
||||
{
|
||||
return Attack(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AvoidWhirlingSlashAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "gal'darah");
|
||||
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;
|
||||
}
|
||||
31
src/Ai/Dungeon/Gundrak/Action/GundrakActions.h
Normal file
31
src/Ai/Dungeon/Gundrak/Action/GundrakActions.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONGDACTIONS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONGDACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "GundrakTriggers.h"
|
||||
|
||||
class AvoidPoisonNovaAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidPoisonNovaAction(PlayerbotAI* ai) : MovementAction(ai, "avoid poison nova") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AttackSnakeWrapAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackSnakeWrapAction(PlayerbotAI* ai) : AttackAction(ai, "attack snake wrap") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AvoidWhirlingSlashAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidWhirlingSlashAction(PlayerbotAI* ai) : MovementAction(ai, "avoid whirling slash") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
22
src/Ai/Dungeon/Gundrak/GundrakActionContext.h
Normal file
22
src/Ai/Dungeon/Gundrak/GundrakActionContext.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONGDACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONGDACTIONCONTEXT_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "GundrakActions.h"
|
||||
|
||||
class WotlkDungeonGDActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonGDActionContext() {
|
||||
creators["avoid poison nova"] = &WotlkDungeonGDActionContext::avoid_poison_nova;
|
||||
creators["attack snake wrap"] = &WotlkDungeonGDActionContext::attack_snake_wrap;
|
||||
creators["avoid whirling slash"] = &WotlkDungeonGDActionContext::avoid_whirling_slash;
|
||||
}
|
||||
private:
|
||||
static Action* avoid_poison_nova(PlayerbotAI* ai) { return new AvoidPoisonNovaAction(ai); }
|
||||
static Action* attack_snake_wrap(PlayerbotAI* ai) { return new AttackSnakeWrapAction(ai); }
|
||||
static Action* avoid_whirling_slash(PlayerbotAI* ai) { return new AvoidWhirlingSlashAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
23
src/Ai/Dungeon/Gundrak/GundrakTriggerContext.h
Normal file
23
src/Ai/Dungeon/Gundrak/GundrakTriggerContext.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONGDTRIGGERCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONGDTRIGGERCONTEXT_H
|
||||
|
||||
#include "NamedObjectContext.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "GundrakTriggers.h"
|
||||
|
||||
class WotlkDungeonGDTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonGDTriggerContext()
|
||||
{
|
||||
creators["poison nova"] = &WotlkDungeonGDTriggerContext::poison_nova;
|
||||
creators["snake wrap"] = &WotlkDungeonGDTriggerContext::snake_wrap;
|
||||
creators["whirling slash"] = &WotlkDungeonGDTriggerContext::whirling_slash;
|
||||
}
|
||||
private:
|
||||
static Trigger* poison_nova(PlayerbotAI* ai) { return new SladranPoisonNovaTrigger(ai); }
|
||||
static Trigger* snake_wrap(PlayerbotAI* ai) { return new SladranSnakeWrapTrigger(ai); }
|
||||
static Trigger* whirling_slash(PlayerbotAI* ai) { return new GaldarahWhirlingSlashTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
62
src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.cpp
Normal file
62
src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "GundrakMultipliers.h"
|
||||
#include "GundrakActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "MovementActions.h"
|
||||
#include "GundrakTriggers.h"
|
||||
#include "Action.h"
|
||||
|
||||
float SladranMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
if (boss->FindCurrentSpellBySpellId(SPELL_POISON_NOVA))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<AvoidPoisonNovaAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (!botAI->IsDps(bot)) { return 1.0f; }
|
||||
|
||||
if (action->getThreatType() == Action::ActionThreatType::Aoe)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
Unit* snakeWrap = nullptr;
|
||||
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||
for (auto& target : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && unit->GetEntry() == NPC_SNAKE_WRAP)
|
||||
{
|
||||
snakeWrap = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Prevent auto-target acquisition during snake wraps
|
||||
if (snakeWrap && dynamic_cast<DpsAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float GaldarahMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "gal'darah");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
if (boss->HasAura(SPELL_WHIRLING_SLASH))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<AvoidWhirlingSlashAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
24
src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.h
Normal file
24
src/Ai/Dungeon/Gundrak/Multiplier/GundrakMultipliers.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONGDMULTIPLIERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONGDMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class SladranMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
SladranMultiplier(PlayerbotAI* ai) : Multiplier(ai, "slad'ran") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class GaldarahMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
GaldarahMultiplier(PlayerbotAI* ai) : Multiplier(ai, "gal'darah") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
#endif
|
||||
29
src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.cpp
Normal file
29
src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "GundrakStrategy.h"
|
||||
#include "GundrakMultipliers.h"
|
||||
|
||||
void WotlkDungeonGDStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
{
|
||||
// Moorabi
|
||||
|
||||
// Drakkari Colossus
|
||||
|
||||
// Slad'ran
|
||||
// TODO: Might need to add target priority for heroic on the snakes or to burn down boss.
|
||||
// Will re-test in heroic, decent dps groups should be able to blast him down with no funky strats.
|
||||
triggers.push_back(new TriggerNode("poison nova",
|
||||
{ NextAction("avoid poison nova", ACTION_RAID + 5) }));
|
||||
triggers.push_back(new TriggerNode("snake wrap",
|
||||
{ NextAction("attack snake wrap", ACTION_RAID + 4) }));
|
||||
|
||||
// Gal'darah
|
||||
triggers.push_back(new TriggerNode("whirling slash",
|
||||
{ NextAction("avoid whirling slash", ACTION_RAID + 5) }));
|
||||
|
||||
// Eck the Ferocious (Heroic only)
|
||||
}
|
||||
|
||||
void WotlkDungeonGDStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new SladranMultiplier(botAI));
|
||||
multipliers.push_back(new GaldarahMultiplier(botAI));
|
||||
}
|
||||
17
src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.h
Normal file
17
src/Ai/Dungeon/Gundrak/Strategy/GundrakStrategy.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONGDSTRATEGY_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONGDSTRATEGY_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
class WotlkDungeonGDStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
WotlkDungeonGDStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
virtual std::string const getName() override { return "gundrak"; }
|
||||
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
|
||||
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
37
src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.cpp
Normal file
37
src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "Playerbots.h"
|
||||
#include "GundrakTriggers.h"
|
||||
#include "AiObject.h"
|
||||
#include "AiObjectContext.h"
|
||||
|
||||
bool SladranPoisonNovaTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return bool(boss->FindCurrentSpellBySpellId(SPELL_POISON_NOVA));
|
||||
}
|
||||
|
||||
bool SladranSnakeWrapTrigger::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");
|
||||
|
||||
for (auto& target : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && unit->GetEntry() == NPC_SNAKE_WRAP)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GaldarahWhirlingSlashTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "gal'darah");
|
||||
return boss && boss->HasAura(SPELL_WHIRLING_SLASH);
|
||||
}
|
||||
45
src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.h
Normal file
45
src/Ai/Dungeon/Gundrak/Trigger/GundrakTriggers.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONGDTRIGGERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONGDTRIGGERS_H
|
||||
|
||||
#include "Trigger.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "GenericTriggers.h"
|
||||
#include "DungeonStrategyUtils.h"
|
||||
|
||||
enum GundrakIDs
|
||||
{
|
||||
// Slad'ran
|
||||
SPELL_POISON_NOVA_N = 55081,
|
||||
SPELL_POISON_NOVA_H = 59842,
|
||||
NPC_SNAKE_WRAP = 29742,
|
||||
|
||||
// Gal'darah
|
||||
SPELL_WHIRLING_SLASH_N = 55250,
|
||||
SPELL_WHIRLING_SLASH_H = 59824,
|
||||
};
|
||||
|
||||
#define SPELL_POISON_NOVA DUNGEON_MODE(bot, SPELL_POISON_NOVA_N, SPELL_POISON_NOVA_H)
|
||||
#define SPELL_WHIRLING_SLASH DUNGEON_MODE(bot, SPELL_WHIRLING_SLASH_N, SPELL_WHIRLING_SLASH_H)
|
||||
|
||||
class SladranPoisonNovaTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
SladranPoisonNovaTrigger(PlayerbotAI* ai) : Trigger(ai, "slad'ran poison nova") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class SladranSnakeWrapTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
SladranSnakeWrapTrigger(PlayerbotAI* ai) : Trigger(ai, "slad'ran snake wrap") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class GaldarahWhirlingSlashTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
GaldarahWhirlingSlashTrigger(PlayerbotAI* ai) : Trigger(ai, "gal'darah whirling slash") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "Playerbots.h"
|
||||
#include "HallsOfLightningActions.h"
|
||||
#include "HallsOfLightningStrategy.h"
|
||||
|
||||
bool BjarngrimTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = nullptr;
|
||||
|
||||
// Target is not findable from threat table using AI_VALUE2(),
|
||||
// therefore need to search manually for the unit name
|
||||
GuidVector npcs = AI_VALUE(GuidVector, "possible targets");
|
||||
|
||||
for (auto& npc : npcs)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npc);
|
||||
if (unit && unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT)
|
||||
{
|
||||
target = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||||
// There are two, we don't want to ping-pong between them if we're attacking one already
|
||||
if (target && currentTarget && currentTarget->GetEntry() == NPC_STORMFORGED_LIEUTENANT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AI_VALUE(Unit*, "current target") == target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Attack(target);
|
||||
}
|
||||
|
||||
bool AvoidWhirlwindAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "general bjarngrim");
|
||||
if (!boss) { return false; }
|
||||
|
||||
float distance = bot->GetExactDist2d(boss->GetPosition());
|
||||
float radius = 8.0f;
|
||||
float distanceExtra = 2.0f;
|
||||
|
||||
if (distance < radius + distanceExtra)
|
||||
{
|
||||
return MoveAway(boss, radius + distanceExtra - distance);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VolkhanTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "volkhan");
|
||||
if (!boss || AI_VALUE(Unit*, "current target") == boss)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Attack(boss);
|
||||
}
|
||||
|
||||
bool StaticOverloadSpreadAction::Execute(Event event)
|
||||
{
|
||||
float radius = 8.0f;
|
||||
float distanceExtra = 2.0f;
|
||||
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
if (bot->GetGUID() == member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Unit* unit = botAI->GetUnit(member);
|
||||
if (unit && unit->HasAura(SPELL_STATIC_OVERLOAD)
|
||||
&& bot->GetExactDist2d(unit) < radius)
|
||||
{
|
||||
return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BallLightningSpreadAction::Execute(Event event)
|
||||
{
|
||||
float radius = 6.0f;
|
||||
float distanceExtra = 1.0f;
|
||||
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
if (bot->GetGUID() == member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Unit* unit = botAI->GetUnit(member);
|
||||
if (unit && bot->GetExactDist2d(unit) < radius + distanceExtra)
|
||||
{
|
||||
return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IonarTankPositionAction::isUseful() { return bot->GetExactDist2d(IONAR_TANK_POSITION) > 10.0f; }
|
||||
bool IonarTankPositionAction::Execute(Event event)
|
||||
{
|
||||
return MoveTo(bot->GetMapId(), IONAR_TANK_POSITION.GetPositionX(), IONAR_TANK_POSITION.GetPositionY(), IONAR_TANK_POSITION.GetPositionZ(),
|
||||
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
|
||||
bool DispersePositionAction::isUseful() { return bot->GetExactDist2d(DISPERSE_POSITION) > 8.0f; }
|
||||
bool DispersePositionAction::Execute(Event event)
|
||||
{
|
||||
return MoveTo(bot->GetMapId(), DISPERSE_POSITION.GetPositionX(), DISPERSE_POSITION.GetPositionY(), DISPERSE_POSITION.GetPositionZ(),
|
||||
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
|
||||
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 for this boss.
|
||||
if (bot->getClass() == CLASS_HUNTER)
|
||||
{
|
||||
return AI_VALUE2(float, "distance", "current target") > 6.5f;
|
||||
}
|
||||
// else
|
||||
return AI_VALUE2(float, "distance", "current target") > 2.0f;
|
||||
}
|
||||
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, maxMovement));
|
||||
}
|
||||
// else
|
||||
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), maxMovement));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AvoidLightningNovaAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
|
||||
if (!boss) { return false; }
|
||||
|
||||
float distance = bot->GetExactDist2d(boss->GetPosition());
|
||||
float radius = 20.0f;
|
||||
float distanceExtra = 2.0f;
|
||||
|
||||
if (distance < radius + distanceExtra)
|
||||
{
|
||||
return MoveAway(boss, radius + distanceExtra - distance);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLACTIONS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOLACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "HallsOfLightningTriggers.h"
|
||||
|
||||
const Position IONAR_TANK_POSITION = Position(1078.860f, -261.928f, 61.226f);
|
||||
const Position DISPERSE_POSITION = Position(1161.152f, -261.584f, 53.223f);
|
||||
|
||||
class BjarngrimTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
BjarngrimTargetAction(PlayerbotAI* ai) : AttackAction(ai, "bjarngrim target") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AvoidWhirlwindAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidWhirlwindAction(PlayerbotAI* ai) : MovementAction(ai, "avoid whirlwind") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class VolkhanTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
VolkhanTargetAction(PlayerbotAI* ai) : AttackAction(ai, "volkhan target") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class StaticOverloadSpreadAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
StaticOverloadSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "static overload spread") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class BallLightningSpreadAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
BallLightningSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "ball lightning spread") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class IonarTankPositionAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
IonarTankPositionAction(PlayerbotAI* ai) : MovementAction(ai, "ionar tank position") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class DispersePositionAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
DispersePositionAction(PlayerbotAI* ai) : MovementAction(ai, "disperse position") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class LokenStackAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
LokenStackAction(PlayerbotAI* ai) : MovementAction(ai, "loken stack") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class AvoidLightningNovaAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidLightningNovaAction(PlayerbotAI* ai) : MovementAction(ai, "avoid lightning nova") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOLACTIONCONTEXT_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "HallsOfLightningActions.h"
|
||||
|
||||
class WotlkDungeonHoLActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonHoLActionContext() {
|
||||
creators["bjarngrim target"] = &WotlkDungeonHoLActionContext::bjarngrim_target;
|
||||
creators["avoid whirlwind"] = &WotlkDungeonHoLActionContext::avoid_whirlwind;
|
||||
creators["volkhan target"] = &WotlkDungeonHoLActionContext::volkhan_target;
|
||||
creators["static overload spread"] = &WotlkDungeonHoLActionContext::static_overload_spread;
|
||||
creators["ball lightning spread"] = &WotlkDungeonHoLActionContext::ball_lightning_spread;
|
||||
creators["ionar tank position"] = &WotlkDungeonHoLActionContext::ionar_tank_position;
|
||||
creators["disperse position"] = &WotlkDungeonHoLActionContext::disperse_position;
|
||||
creators["loken stack"] = &WotlkDungeonHoLActionContext::loken_stack;
|
||||
creators["avoid lightning nova"] = &WotlkDungeonHoLActionContext::avoid_lightning_nova;
|
||||
}
|
||||
private:
|
||||
static Action* bjarngrim_target(PlayerbotAI* ai) { return new BjarngrimTargetAction(ai); }
|
||||
static Action* avoid_whirlwind(PlayerbotAI* ai) { return new AvoidWhirlwindAction(ai); }
|
||||
static Action* volkhan_target(PlayerbotAI* ai) { return new VolkhanTargetAction(ai); }
|
||||
static Action* static_overload_spread(PlayerbotAI* ai) { return new StaticOverloadSpreadAction(ai); }
|
||||
static Action* ball_lightning_spread(PlayerbotAI* ai) { return new BallLightningSpreadAction(ai); }
|
||||
static Action* ionar_tank_position(PlayerbotAI* ai) { return new IonarTankPositionAction(ai); }
|
||||
static Action* disperse_position(PlayerbotAI* ai) { return new DispersePositionAction(ai); }
|
||||
static Action* loken_stack(PlayerbotAI* ai) { return new LokenStackAction(ai); }
|
||||
static Action* avoid_lightning_nova(PlayerbotAI* ai) { return new AvoidLightningNovaAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLTRIGGERCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOLTRIGGERCONTEXT_H
|
||||
|
||||
#include "NamedObjectContext.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "HallsOfLightningTriggers.h"
|
||||
|
||||
class WotlkDungeonHoLTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonHoLTriggerContext()
|
||||
{
|
||||
creators["stormforged lieutenant"] = &WotlkDungeonHoLTriggerContext::stormforged_lieutenant;
|
||||
creators["whirlwind"] = &WotlkDungeonHoLTriggerContext::bjarngrim_whirlwind;
|
||||
creators["volkhan"] = &WotlkDungeonHoLTriggerContext::volkhan;
|
||||
creators["static overload"] = &WotlkDungeonHoLTriggerContext::static_overload;
|
||||
creators["ball lightning"] = &WotlkDungeonHoLTriggerContext::ball_lightning;
|
||||
creators["ionar tank aggro"] = &WotlkDungeonHoLTriggerContext::ionar_tank_aggro;
|
||||
creators["ionar disperse"] = &WotlkDungeonHoLTriggerContext::ionar_disperse;
|
||||
creators["loken ranged"] = &WotlkDungeonHoLTriggerContext::loken_ranged;
|
||||
creators["lightning nova"] = &WotlkDungeonHoLTriggerContext::lightning_nova;
|
||||
}
|
||||
private:
|
||||
static Trigger* stormforged_lieutenant(PlayerbotAI* ai) { return new StormforgedLieutenantTrigger(ai); }
|
||||
static Trigger* bjarngrim_whirlwind(PlayerbotAI* ai) { return new BjarngrimWhirlwindTrigger(ai); }
|
||||
static Trigger* volkhan(PlayerbotAI* ai) { return new VolkhanTrigger(ai); }
|
||||
static Trigger* static_overload(PlayerbotAI* ai) { return new IonarStaticOverloadTrigger(ai); }
|
||||
static Trigger* ball_lightning(PlayerbotAI* ai) { return new IonarBallLightningTrigger(ai); }
|
||||
static Trigger* ionar_tank_aggro(PlayerbotAI* ai) { return new IonarTankAggroTrigger(ai); }
|
||||
static Trigger* ionar_disperse(PlayerbotAI* ai) { return new IonarDisperseTrigger(ai); }
|
||||
static Trigger* loken_ranged(PlayerbotAI* ai) { return new LokenRangedTrigger(ai); }
|
||||
static Trigger* lightning_nova(PlayerbotAI* ai) { return new LokenLightningNovaTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "HallsOfLightningMultipliers.h"
|
||||
#include "HallsOfLightningActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "MovementActions.h"
|
||||
#include "HallsOfLightningTriggers.h"
|
||||
#include "Action.h"
|
||||
|
||||
#include "WarriorActions.h"
|
||||
|
||||
float BjarngrimMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "general bjarngrim");
|
||||
if (!boss || botAI->IsHeal(bot)) { return 1.0f; }
|
||||
|
||||
if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND_BJARNGRIM))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<AvoidWhirlwindAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect boss adds this way as sometimes they don't get added to threat table on dps bots,
|
||||
// and some dps just stand at range and don't engage the boss at all as they can't find the adds
|
||||
// Unit* boss_add = AI_VALUE2(Unit*, "find target", "stormforged lieutenant");
|
||||
Unit* boss_add = 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 && unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT)
|
||||
{
|
||||
boss_add = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!boss_add || botAI->IsTank(bot)) { return 1.0f; }
|
||||
|
||||
if (dynamic_cast<DpsAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (action->getThreatType() == Action::ActionThreatType::Aoe)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float VolkhanMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "volkhan");
|
||||
if (!boss || botAI->IsTank(bot) || botAI->IsHeal(bot)) { return 1.0f; }
|
||||
|
||||
if (dynamic_cast<DpsAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (action->getThreatType() == Action::ActionThreatType::Aoe)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float IonarMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
// Check if the boss has dispersed into Sparks (not visible).
|
||||
if (!bot->CanSeeOrDetect(boss))
|
||||
{
|
||||
// Block MovementActions except for specific exceptions.
|
||||
if (dynamic_cast<MovementAction*>(action)
|
||||
&& !dynamic_cast<DispersePositionAction*>(action)
|
||||
&& !dynamic_cast<StaticOverloadSpreadAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (boss->FindCurrentSpellBySpellId(SPELL_DISPERSE))
|
||||
{
|
||||
// Explicitly block the CastChargeAction during dispersal.
|
||||
if (dynamic_cast<CastChargeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
float LokenMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
// Prevent FleeAction from being executed.
|
||||
if (dynamic_cast<FleeAction*>(action)) { return 0.0f; }
|
||||
|
||||
// Prevent MovementActions during Lightning Nova unless it's AvoidLightningNovaAction.
|
||||
if (boss->FindCurrentSpellBySpellId(SPELL_LIGHTNING_NOVA))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action)
|
||||
&& !dynamic_cast<AvoidLightningNovaAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Specifically prevent Charge during Lightning Nova.
|
||||
if (dynamic_cast<CastChargeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f; // Default multiplier value for other cases.
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLMULTIPLIERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOLMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class BjarngrimMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
BjarngrimMultiplier(PlayerbotAI* ai) : Multiplier(ai, "general bjarngrim") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class VolkhanMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
VolkhanMultiplier(PlayerbotAI* ai) : Multiplier(ai, "volkhan") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class IonarMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
IonarMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ionar") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class LokenMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
LokenMultiplier(PlayerbotAI* ai) : Multiplier(ai, "loken") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "HallsOfLightningStrategy.h"
|
||||
#include "HallsOfLightningMultipliers.h"
|
||||
|
||||
void WotlkDungeonHoLStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
{
|
||||
// General Bjarngrim
|
||||
triggers.push_back(new TriggerNode("stormforged lieutenant",
|
||||
{ NextAction("bjarngrim target", ACTION_RAID + 5) }));
|
||||
triggers.push_back(new TriggerNode("whirlwind",
|
||||
{ NextAction("avoid whirlwind", ACTION_RAID + 4) }));
|
||||
|
||||
// Volkhan
|
||||
triggers.push_back(new TriggerNode("volkhan",
|
||||
{ NextAction("volkhan target", ACTION_RAID + 5) }));
|
||||
|
||||
// Ionar
|
||||
triggers.push_back(new TriggerNode("ionar disperse",
|
||||
{ NextAction("disperse position", ACTION_MOVE + 5) }));
|
||||
triggers.push_back(new TriggerNode("ionar tank aggro",
|
||||
{ NextAction("ionar tank position", ACTION_MOVE + 4) }));
|
||||
triggers.push_back(new TriggerNode("static overload",
|
||||
{ NextAction("static overload spread", ACTION_MOVE + 3) }));
|
||||
// TODO: Targeted player can dodge the ball, but a single player soaking it isn't too bad to heal
|
||||
triggers.push_back(new TriggerNode("ball lightning",
|
||||
{ NextAction("ball lightning spread", ACTION_MOVE + 2) }));
|
||||
|
||||
// Loken
|
||||
triggers.push_back(new TriggerNode("lightning nova",
|
||||
{ NextAction("avoid lightning nova", ACTION_MOVE + 5) }));
|
||||
triggers.push_back(new TriggerNode("loken ranged",
|
||||
{ NextAction("loken stack", ACTION_MOVE + 4) }));
|
||||
}
|
||||
|
||||
void WotlkDungeonHoLStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new BjarngrimMultiplier(botAI));
|
||||
multipliers.push_back(new VolkhanMultiplier(botAI));
|
||||
multipliers.push_back(new IonarMultiplier(botAI));
|
||||
multipliers.push_back(new LokenMultiplier(botAI));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLSTRATEGY_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOLSTRATEGY_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
class WotlkDungeonHoLStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
WotlkDungeonHoLStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
virtual std::string const getName() override { return "halls of lightning"; }
|
||||
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
|
||||
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "Playerbots.h"
|
||||
#include "HallsOfLightningTriggers.h"
|
||||
#include "AiObject.h"
|
||||
#include "AiObjectContext.h"
|
||||
|
||||
bool StormforgedLieutenantTrigger::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");
|
||||
|
||||
for (auto& target : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && unit->IsInCombat() &&
|
||||
unit->GetEntry() == NPC_STORMFORGED_LIEUTENANT)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BjarngrimWhirlwindTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "general bjarngrim");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND_BJARNGRIM);
|
||||
}
|
||||
|
||||
bool VolkhanTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "volkhan");
|
||||
return boss && !botAI->IsTank(bot) && !botAI->IsHeal(bot);
|
||||
}
|
||||
|
||||
bool IonarStaticOverloadTrigger::IsActive()
|
||||
{
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(member);
|
||||
if (unit && unit->HasAura(SPELL_STATIC_OVERLOAD))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IonarBallLightningTrigger::IsActive()
|
||||
{
|
||||
if (botAI->IsMelee(bot)) { return false; }
|
||||
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_BALL_LIGHTNING);
|
||||
}
|
||||
|
||||
bool IonarTankAggroTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsTank(bot)) { return false; }
|
||||
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return AI_VALUE2(bool, "has aggro", "current target");
|
||||
}
|
||||
|
||||
bool IonarDisperseTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return !bot->CanSeeOrDetect(boss) || boss->FindCurrentSpellBySpellId(SPELL_DISPERSE);
|
||||
}
|
||||
|
||||
bool LokenRangedTrigger::IsActive()
|
||||
{
|
||||
return !botAI->IsMelee(bot) && AI_VALUE2(Unit*, "find target", "loken");
|
||||
}
|
||||
|
||||
bool LokenLightningNovaTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_LIGHTNING_NOVA);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOLTRIGGERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOLTRIGGERS_H
|
||||
|
||||
#include "Trigger.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "GenericTriggers.h"
|
||||
#include "DungeonStrategyUtils.h"
|
||||
|
||||
enum HallsOfLightningIDs
|
||||
{
|
||||
// General Bjarngrim
|
||||
NPC_STORMFORGED_LIEUTENANT = 29240,
|
||||
SPELL_WHIRLWIND_BJARNGRIM = 52027,
|
||||
|
||||
// Ionar
|
||||
SPELL_STATIC_OVERLOAD_N = 52658,
|
||||
SPELL_STATIC_OVERLOAD_H = 59795,
|
||||
SPELL_BALL_LIGHTNING_N = 52780,
|
||||
SPELL_BALL_LIGHTNING_H = 59800,
|
||||
SPELL_DISPERSE = 52770,
|
||||
NPC_SPARK_OF_IONAR = 28926,
|
||||
|
||||
// Loken
|
||||
SPELL_LIGHTNING_NOVA_N = 52960,
|
||||
SPELL_LIGHTNING_NOVA_H = 59835,
|
||||
};
|
||||
|
||||
#define SPELL_STATIC_OVERLOAD DUNGEON_MODE(bot, SPELL_STATIC_OVERLOAD_N, SPELL_STATIC_OVERLOAD_H)
|
||||
#define SPELL_BALL_LIGHTNING DUNGEON_MODE(bot, SPELL_BALL_LIGHTNING_N, SPELL_BALL_LIGHTNING_H)
|
||||
#define SPELL_LIGHTNING_NOVA DUNGEON_MODE(bot, SPELL_LIGHTNING_NOVA_N, SPELL_LIGHTNING_NOVA_H)
|
||||
|
||||
class StormforgedLieutenantTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
StormforgedLieutenantTrigger(PlayerbotAI* ai) : Trigger(ai, "stormforged lieutenant") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class BjarngrimWhirlwindTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
BjarngrimWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "bjarngrim whirlwind") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class VolkhanTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
VolkhanTrigger(PlayerbotAI* ai) : Trigger(ai, "volkhan") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class IonarStaticOverloadTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
IonarStaticOverloadTrigger(PlayerbotAI* ai) : Trigger(ai, "ionar static overload") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class IonarBallLightningTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
IonarBallLightningTrigger(PlayerbotAI* ai) : Trigger(ai, "ionar ball lightning spread") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class IonarTankAggroTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
IonarTankAggroTrigger(PlayerbotAI* ai) : Trigger(ai, "ionar tank aggro") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class IonarDisperseTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
IonarDisperseTrigger(PlayerbotAI* ai) : Trigger(ai, "ionar disperse") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class LokenRangedTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
LokenRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "loken ranged") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class LokenLightningNovaTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
LokenLightningNovaTrigger(PlayerbotAI* ai) : Trigger(ai, "lightning nova") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
0
src/Ai/Dungeon/HallsOfReflection/TODO
Normal file
0
src/Ai/Dungeon/HallsOfReflection/TODO
Normal file
53
src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.cpp
Normal file
53
src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "Playerbots.h"
|
||||
#include "HallsOfStoneActions.h"
|
||||
#include "HallsOfStoneStrategy.h"
|
||||
|
||||
bool ShatterSpreadAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "krystallus");
|
||||
if (!boss) { return false; }
|
||||
|
||||
float radius = 40.0f;
|
||||
Unit* closestMember = nullptr;
|
||||
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(member);
|
||||
if (!unit || bot->GetGUID() == member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!closestMember || bot->GetExactDist2d(unit) < bot->GetExactDist2d(closestMember))
|
||||
{
|
||||
closestMember = unit;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestMember && bot->GetExactDist2d(closestMember) < radius)
|
||||
{
|
||||
// Move in small increments so course can be corrected, otherwise two bots may
|
||||
// run in the same direction and not adjust until they reach the destination
|
||||
// return MoveAway(closestMember, radius - bot->GetExactDist2d(closestMember));
|
||||
return MoveAway(closestMember, 5.0f);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AvoidLightningRingAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "sjonnir the ironshaper");
|
||||
if (!boss) { return false; }
|
||||
|
||||
float distance = bot->GetExactDist2d(boss->GetPosition());
|
||||
float radius = 10.0f;
|
||||
float distanceExtra = 2.0f;
|
||||
|
||||
if (distance < radius + distanceExtra)
|
||||
{
|
||||
return MoveAway(boss, radius + distanceExtra - distance);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
24
src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.h
Normal file
24
src/Ai/Dungeon/HallsOfStone/Action/HallsOfStoneActions.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOSACTIONS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOSACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "HallsOfStoneTriggers.h"
|
||||
|
||||
class ShatterSpreadAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
ShatterSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "shatter spread") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AvoidLightningRingAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidLightningRingAction(PlayerbotAI* ai) : MovementAction(ai, "avoid lightning ring") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
20
src/Ai/Dungeon/HallsOfStone/HallsOfStoneActionContext.h
Normal file
20
src/Ai/Dungeon/HallsOfStone/HallsOfStoneActionContext.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOSACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOSACTIONCONTEXT_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "HallsOfStoneActions.h"
|
||||
|
||||
class WotlkDungeonHoSActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonHoSActionContext() {
|
||||
creators["shatter spread"] = &WotlkDungeonHoSActionContext::shatter_spread;
|
||||
creators["avoid lightning ring"] = &WotlkDungeonHoSActionContext::avoid_lightning_ring;
|
||||
}
|
||||
private:
|
||||
static Action* shatter_spread(PlayerbotAI* ai) { return new ShatterSpreadAction(ai); }
|
||||
static Action* avoid_lightning_ring(PlayerbotAI* ai) { return new AvoidLightningRingAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
21
src/Ai/Dungeon/HallsOfStone/HallsOfStoneTriggerContext.h
Normal file
21
src/Ai/Dungeon/HallsOfStone/HallsOfStoneTriggerContext.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOSTRIGGERCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOSTRIGGERCONTEXT_H
|
||||
|
||||
#include "NamedObjectContext.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "HallsOfStoneTriggers.h"
|
||||
|
||||
class WotlkDungeonHoSTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonHoSTriggerContext()
|
||||
{
|
||||
creators["ground slam"] = &WotlkDungeonHoSTriggerContext::ground_slam;
|
||||
creators["lightning ring"] = &WotlkDungeonHoSTriggerContext::lightning_ring;
|
||||
}
|
||||
private:
|
||||
static Trigger* ground_slam(PlayerbotAI* ai) { return new KrystallusGroundSlamTrigger(ai); }
|
||||
static Trigger* lightning_ring(PlayerbotAI* ai) { return new SjonnirLightningRingTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "HallsOfStoneMultipliers.h"
|
||||
#include "HallsOfStoneActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "MovementActions.h"
|
||||
#include "HallsOfStoneTriggers.h"
|
||||
#include "Action.h"
|
||||
|
||||
float KrystallusMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "krystallus");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
// Check both of these... the spell is applied first, debuff later.
|
||||
// Neither is active for the full duration so we need to trigger off both
|
||||
if (bot->HasAura(SPELL_GROUND_SLAM) || bot->HasAura(DEBUFF_GROUND_SLAM))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<ShatterSpreadAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float SjonnirMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "sjonnir the ironshaper");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_LIGHTNING_RING))
|
||||
{
|
||||
// Problematic since there's a lot of movement on this boss, will prevent players from positioning
|
||||
// well to deal with adds etc. during the channel period. Takes a bit of work to improve this though
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<AvoidLightningRingAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOSMULTIPLIERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOSMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class KrystallusMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
KrystallusMultiplier(PlayerbotAI* ai) : Multiplier(ai, "krystallus") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class SjonnirMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
SjonnirMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sjonnir the ironshaper") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "HallsOfStoneStrategy.h"
|
||||
#include "HallsOfStoneMultipliers.h"
|
||||
|
||||
void WotlkDungeonHoSStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
{
|
||||
// Maiden of Grief
|
||||
// TODO: Jump into damage during shock of sorrow?
|
||||
|
||||
// Krystallus
|
||||
// TODO: I think bots need to dismiss pets on this, or they nuke players they are standing close to
|
||||
triggers.push_back(new TriggerNode("ground slam",
|
||||
{ NextAction("shatter spread", ACTION_RAID + 5) }));
|
||||
|
||||
// Tribunal of Ages
|
||||
// Seems fine, maybe add focus targeting strat if needed on heroic.
|
||||
// Main issue is dps will immediately rambo in and sometimes die before tank gets aggro,
|
||||
// this is mostly an issue with the bot AI as they do it on every fight
|
||||
|
||||
// Sjonnir The Ironshaper
|
||||
// Possibly tank in place in the middle of the room, assign a dps to adds?
|
||||
triggers.push_back(new TriggerNode("lightning ring",
|
||||
{ NextAction("avoid lightning ring", ACTION_RAID + 5) }));
|
||||
}
|
||||
|
||||
void WotlkDungeonHoSStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new KrystallusMultiplier(botAI));
|
||||
multipliers.push_back(new SjonnirMultiplier(botAI));
|
||||
}
|
||||
17
src/Ai/Dungeon/HallsOfStone/Strategy/HallsOfStoneStrategy.h
Normal file
17
src/Ai/Dungeon/HallsOfStone/Strategy/HallsOfStoneStrategy.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOSSTRATEGY_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOSSTRATEGY_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
class WotlkDungeonHoSStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
WotlkDungeonHoSStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
virtual std::string const getName() override { return "halls of stone"; }
|
||||
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
|
||||
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
22
src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.cpp
Normal file
22
src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "Playerbots.h"
|
||||
#include "HallsOfStoneTriggers.h"
|
||||
#include "AiObject.h"
|
||||
#include "AiObjectContext.h"
|
||||
|
||||
bool KrystallusGroundSlamTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "krystallus");
|
||||
if (!boss) { return false; }
|
||||
|
||||
// Check both of these... the spell is applied first, debuff later.
|
||||
// Neither is active for the full duration so we need to trigger off both
|
||||
return bot->HasAura(SPELL_GROUND_SLAM) || bot->HasAura(DEBUFF_GROUND_SLAM);
|
||||
}
|
||||
|
||||
bool SjonnirLightningRingTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "sjonnir the ironshaper");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_LIGHTNING_RING);
|
||||
}
|
||||
36
src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.h
Normal file
36
src/Ai/Dungeon/HallsOfStone/Trigger/HallsOfStoneTriggers.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONHOSTRIGGERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONHOSTRIGGERS_H
|
||||
|
||||
#include "Trigger.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "GenericTriggers.h"
|
||||
#include "DungeonStrategyUtils.h"
|
||||
|
||||
enum HallsOfStoneIDs
|
||||
{
|
||||
// Krystallus
|
||||
SPELL_GROUND_SLAM = 50827,
|
||||
DEBUFF_GROUND_SLAM = 50833,
|
||||
|
||||
// Sjonnir The Ironshaper
|
||||
SPELL_LIGHTNING_RING_N = 50840,
|
||||
SPELL_LIGHTNING_RING_H = 59848,
|
||||
};
|
||||
|
||||
#define SPELL_LIGHTNING_RING DUNGEON_MODE(bot, SPELL_LIGHTNING_RING_N, SPELL_LIGHTNING_RING_H)
|
||||
|
||||
class KrystallusGroundSlamTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KrystallusGroundSlamTrigger(PlayerbotAI* ai) : Trigger(ai, "krystallus ground slam") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class SjonnirLightningRingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
SjonnirLightningRingTrigger(PlayerbotAI* ai) : Trigger(ai, "sjonnir lightning ring") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
172
src/Ai/Dungeon/Nexus/Action/NexusActions.cpp
Normal file
172
src/Ai/Dungeon/Nexus/Action/NexusActions.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "Playerbots.h"
|
||||
#include "NexusActions.h"
|
||||
#include "NexusStrategy.h"
|
||||
|
||||
bool MoveFromWhirlwindAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = nullptr;
|
||||
uint8 faction = bot->GetTeamId();
|
||||
float targetDist = 10.0f; // Whirlwind has a range of 8, adding a safety buffer
|
||||
|
||||
switch (bot->GetMap()->GetDifficulty())
|
||||
{
|
||||
case DUNGEON_DIFFICULTY_NORMAL:
|
||||
if (faction == TEAM_ALLIANCE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "horde commander");
|
||||
}
|
||||
else // TEAM_HORDE
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
|
||||
}
|
||||
break;
|
||||
case DUNGEON_DIFFICULTY_HEROIC:
|
||||
if (faction == TEAM_ALLIANCE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
|
||||
}
|
||||
else // TEAM_HORDE
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure boss is valid before accessing its methods
|
||||
if (!boss)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float bossDistance = bot->GetExactDist2d(boss->GetPosition());
|
||||
|
||||
// Check if the bot is already at a safe distance
|
||||
if (bossDistance > targetDist)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move away from the boss to avoid Whirlwind
|
||||
return MoveAway(boss, targetDist - bossDistance);
|
||||
}
|
||||
|
||||
bool FirebombSpreadAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
|
||||
float radius = 5.0f;
|
||||
float targetDist = radius + 1.0f;
|
||||
if (!boss) { return false; }
|
||||
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(member);
|
||||
if (!unit || bot->GetGUID() == member) { continue; }
|
||||
|
||||
if (bot->GetExactDist2d(unit) < targetDist)
|
||||
{
|
||||
return MoveAway(unit, targetDist);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TelestraSplitTargetAction::isUseful() { return !botAI->IsHeal(bot); }
|
||||
bool TelestraSplitTargetAction::Execute(Event event)
|
||||
{
|
||||
GuidVector attackers = AI_VALUE(GuidVector, "attackers");
|
||||
Unit* splitTargets[3] = {nullptr, nullptr, nullptr};
|
||||
|
||||
for (auto& attacker : attackers)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(attacker);
|
||||
if (!unit) { continue; }
|
||||
|
||||
switch (unit->GetEntry())
|
||||
{
|
||||
// Focus arcane clone first
|
||||
case NPC_ARCANE_MAGUS:
|
||||
splitTargets[0] = unit;
|
||||
break;
|
||||
// Then the frost clone
|
||||
case NPC_FROST_MAGUS:
|
||||
splitTargets[1] = unit;
|
||||
break;
|
||||
// Fire clone last
|
||||
case NPC_FIRE_MAGUS:
|
||||
splitTargets[2] = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (Unit* target : splitTargets)
|
||||
{
|
||||
// Attack the first valid split target in the priority list
|
||||
if (target)
|
||||
{
|
||||
if (AI_VALUE(Unit*, "current target") != target)
|
||||
{
|
||||
return Attack(target);
|
||||
}
|
||||
// Don't continue loop here, the target exists so we don't
|
||||
// want to move down the prio list. We just don't need to send attack
|
||||
// command again, just return false and exit the loop that way
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChaoticRiftTargetAction::isUseful() { return !botAI->IsHeal(bot); }
|
||||
bool ChaoticRiftTargetAction::Execute(Event event)
|
||||
{
|
||||
Unit* chaoticRift = nullptr;
|
||||
|
||||
// Target is not findable from threat table using AI_VALUE2(),
|
||||
// therefore need to search manually for the unit name
|
||||
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||
|
||||
for (auto i = targets.begin(); i != targets.end(); ++i)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(*i);
|
||||
if (unit && unit->GetName() == "Chaotic Rift")
|
||||
{
|
||||
chaoticRift = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!chaoticRift || AI_VALUE(Unit*, "current target") == chaoticRift)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Attack(chaoticRift);
|
||||
}
|
||||
|
||||
bool DodgeSpikesAction::isUseful()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return bot->GetExactDist2d(boss) > 0.5f;
|
||||
}
|
||||
bool DodgeSpikesAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return Move(bot->GetAngle(boss), bot->GetExactDist2d(boss) - 0.3f);
|
||||
}
|
||||
|
||||
bool IntenseColdJumpAction::Execute(Event event)
|
||||
{
|
||||
// This needs improving but maybe it should be done in the playerbot core.
|
||||
// Jump doesn't seem to support zero offset (eg. jump on the spot) so need to add a tiny delta.
|
||||
// This does a tiny bunnyhop that takes a couple of ms, it doesn't do a natural jump.
|
||||
// Adding extra Z offset causes floating, and appears to scale the jump speed based on Z difference.
|
||||
// Probably best to revisit once bot movement is improved
|
||||
return JumpTo(bot->GetMap()->GetId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ() + 0.01f);
|
||||
// bot->GetMotionMaster()->MoveFall();
|
||||
}
|
||||
55
src/Ai/Dungeon/Nexus/Action/NexusActions.h
Normal file
55
src/Ai/Dungeon/Nexus/Action/NexusActions.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXACTIONS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONNEXACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "NexusTriggers.h"
|
||||
|
||||
class MoveFromWhirlwindAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
MoveFromWhirlwindAction(PlayerbotAI* ai) : MovementAction(ai, "move from whirlwind") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class FirebombSpreadAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
FirebombSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "firebomb spread") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TelestraSplitTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
TelestraSplitTargetAction(PlayerbotAI* ai) : AttackAction(ai, "telestra split target") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class ChaoticRiftTargetAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
ChaoticRiftTargetAction(PlayerbotAI* ai) : AttackAction(ai, "chaotic rift target") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class DodgeSpikesAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
DodgeSpikesAction(PlayerbotAI* ai) : MovementAction(ai, "dodge spikes") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class IntenseColdJumpAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
IntenseColdJumpAction(PlayerbotAI* ai) : MovementAction(ai, "intense cold jump") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
96
src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.cpp
Normal file
96
src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "NexusMultipliers.h"
|
||||
#include "NexusActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "MovementActions.h"
|
||||
#include "NexusTriggers.h"
|
||||
|
||||
float FactionCommanderMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = nullptr;
|
||||
uint8 faction = bot->GetTeamId();
|
||||
|
||||
switch (bot->GetMap()->GetDifficulty())
|
||||
{
|
||||
case DUNGEON_DIFFICULTY_NORMAL:
|
||||
if (faction == TEAM_ALLIANCE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "horde commander");
|
||||
}
|
||||
else //if (faction == TEAM_HORDE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
|
||||
}
|
||||
break;
|
||||
case DUNGEON_DIFFICULTY_HEROIC:
|
||||
if (faction == TEAM_ALLIANCE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
|
||||
}
|
||||
else //if (faction == TEAM_HORDE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (boss && boss->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND))
|
||||
{
|
||||
// Prevent movement actions other than flee during a whirlwind, to prevent running back in early.
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<MoveFromWhirlwindAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float TelestraMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
|
||||
if (boss && boss->GetEntry() != NPC_TELESTRA)
|
||||
{
|
||||
// boss is split into clones, do not auto acquire target
|
||||
if (dynamic_cast<DpsAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float AnomalusMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "anomalus");
|
||||
if (boss && boss->HasAura(BUFF_RIFT_SHIELD))
|
||||
{
|
||||
if (dynamic_cast<DpsAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float OrmorokMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
// These are used for auto ranged repositioning, need to suppress so ranged dps don't ping-pong
|
||||
if (dynamic_cast<FleeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
// This boss is annoying and shuffles around a lot. Don't let tank move once fight has started.
|
||||
// Extra checks are to allow the tank to close distance and engage the boss initially
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<DodgeSpikesAction*>(action)
|
||||
&& botAI->IsTank(bot) && bot->IsWithinMeleeRange(boss)
|
||||
&& AI_VALUE2(bool, "facing", "current target"))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
42
src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.h
Normal file
42
src/Ai/Dungeon/Nexus/Multiplier/NexusMultipliers.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXMULTIPLIERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONNEXMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class FactionCommanderMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
FactionCommanderMultiplier(PlayerbotAI* ai) : Multiplier(ai, "faction commander") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class TelestraMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
TelestraMultiplier(PlayerbotAI* ai) : Multiplier(ai, "grand magus telestra") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class AnomalusMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
AnomalusMultiplier(PlayerbotAI* ai) : Multiplier(ai, "anomalus") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class OrmorokMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
OrmorokMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ormorok the tree-shaper") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
#endif
|
||||
28
src/Ai/Dungeon/Nexus/NexusActionContext.h
Normal file
28
src/Ai/Dungeon/Nexus/NexusActionContext.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONNEXACTIONCONTEXT_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "NexusActions.h"
|
||||
|
||||
class WotlkDungeonNexActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonNexActionContext() {
|
||||
creators["move from whirlwind"] = &WotlkDungeonNexActionContext::move_from_whirlwind;
|
||||
creators["firebomb spread"] = &WotlkDungeonNexActionContext::firebomb_spread;
|
||||
creators["telestra split target"] = &WotlkDungeonNexActionContext::telestra_split_target;
|
||||
creators["chaotic rift target"] = &WotlkDungeonNexActionContext::chaotic_rift_target;
|
||||
creators["dodge spikes"] = &WotlkDungeonNexActionContext::dodge_spikes;
|
||||
creators["intense cold jump"] = &WotlkDungeonNexActionContext::intense_cold_jump;
|
||||
}
|
||||
private:
|
||||
static Action* move_from_whirlwind(PlayerbotAI* ai) { return new MoveFromWhirlwindAction(ai); }
|
||||
static Action* firebomb_spread(PlayerbotAI* ai) { return new FirebombSpreadAction(ai); }
|
||||
static Action* telestra_split_target(PlayerbotAI* ai) { return new TelestraSplitTargetAction(ai); }
|
||||
static Action* chaotic_rift_target(PlayerbotAI* ai) { return new ChaoticRiftTargetAction(ai); }
|
||||
static Action* dodge_spikes(PlayerbotAI* ai) { return new DodgeSpikesAction(ai); }
|
||||
static Action* intense_cold_jump(PlayerbotAI* ai) { return new IntenseColdJumpAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
33
src/Ai/Dungeon/Nexus/NexusTriggerContext.h
Normal file
33
src/Ai/Dungeon/Nexus/NexusTriggerContext.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERCONTEXT_H
|
||||
|
||||
#include "NamedObjectContext.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "NexusTriggers.h"
|
||||
|
||||
class WotlkDungeonNexTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonNexTriggerContext()
|
||||
{
|
||||
creators["faction commander whirlwind"] = &WotlkDungeonNexTriggerContext::faction_commander_whirlwind;
|
||||
creators["telestra firebomb"] = &WotlkDungeonNexTriggerContext::telestra_firebomb;
|
||||
creators["telestra split phase"] = &WotlkDungeonNexTriggerContext::telestra_split_phase;
|
||||
creators["chaotic rift"] = &WotlkDungeonNexTriggerContext::chaotic_rift;
|
||||
creators["ormorok spikes"] = &WotlkDungeonNexTriggerContext::ormorok_spikes;
|
||||
creators["ormorok stack"] = &WotlkDungeonNexTriggerContext::ormorok_stack;
|
||||
creators["intense cold"] = &WotlkDungeonNexTriggerContext::intense_cold;
|
||||
creators["keristrasza positioning"] = &WotlkDungeonNexTriggerContext::keristrasza_positioning;
|
||||
}
|
||||
private:
|
||||
static Trigger* faction_commander_whirlwind(PlayerbotAI* ai) { return new FactionCommanderWhirlwindTrigger(ai); }
|
||||
static Trigger* telestra_firebomb(PlayerbotAI* ai) { return new TelestraFirebombTrigger(ai); }
|
||||
static Trigger* telestra_split_phase(PlayerbotAI* ai) { return new TelestraSplitPhaseTrigger(ai); }
|
||||
static Trigger* chaotic_rift(PlayerbotAI* ai) { return new ChaoticRiftTrigger(ai); }
|
||||
static Trigger* ormorok_spikes(PlayerbotAI* ai) { return new OrmorokSpikesTrigger(ai); }
|
||||
static Trigger* ormorok_stack(PlayerbotAI* ai) { return new OrmorokStackTrigger(ai); }
|
||||
static Trigger* intense_cold(PlayerbotAI* ai) { return new IntenseColdTrigger(ai); }
|
||||
static Trigger* keristrasza_positioning(PlayerbotAI* ai) { return new KeristraszaPositioningTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
52
src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.cpp
Normal file
52
src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "NexusStrategy.h"
|
||||
#include "NexusMultipliers.h"
|
||||
|
||||
void WotlkDungeonNexStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
{
|
||||
// Horde Commander (Alliance N)/Commander Kolurg (Alliance H)
|
||||
// or
|
||||
// Alliance Commander (Horde N)/Commander Stoutbeard (Horde H)
|
||||
triggers.push_back(new TriggerNode("faction commander whirlwind",
|
||||
{ NextAction("move from whirlwind", ACTION_MOVE + 5) }));
|
||||
// TODO: Handle fear? (tremor totems, fear ward etc.)
|
||||
|
||||
// Grand Magus Telestra
|
||||
triggers.push_back(new TriggerNode("telestra firebomb",
|
||||
{ NextAction("firebomb spread", ACTION_MOVE + 5) }));
|
||||
triggers.push_back(new TriggerNode("telestra split phase",
|
||||
{ NextAction("telestra split target", ACTION_RAID + 1) }));
|
||||
// TODO: Add priority interrupt on the frost split's Blizzard casts
|
||||
|
||||
// Anomalus
|
||||
triggers.push_back(new TriggerNode("chaotic rift",
|
||||
{ NextAction("chaotic rift target", ACTION_RAID + 1) }));
|
||||
|
||||
// Ormorok the Tree-Shaper
|
||||
// Tank trigger to stack inside boss. Can also add return action to prevent boss repositioning
|
||||
// if it becomes too much of a problem. He usually dies before he's up against a wall though
|
||||
triggers.push_back(new TriggerNode("ormorok spikes",
|
||||
{ NextAction("dodge spikes", ACTION_MOVE + 5) }));
|
||||
// Non-tank trigger to stack. Avoiding the spikes at range is.. harder than it seems.
|
||||
// TODO: This turns hunters into melee marshmallows, have not come up with a better solution yet
|
||||
triggers.push_back(new TriggerNode("ormorok stack",
|
||||
{ NextAction("dodge spikes", ACTION_MOVE + 5) }));
|
||||
// TODO: Add handling for spell reflect... best to spam low level/weak spells but don't want
|
||||
// to hardcode spells per class, might be difficult to dynamically generate this.
|
||||
// Will revisit if I find my altbots killing themselves in heroic, just heal through it for now
|
||||
|
||||
// Keristrasza
|
||||
triggers.push_back(new TriggerNode("intense cold",
|
||||
{ NextAction("intense cold jump", ACTION_MOVE + 5) }));
|
||||
// Flank dragon positioning
|
||||
triggers.push_back(new TriggerNode("keristrasza positioning",
|
||||
{ NextAction("rear flank", ACTION_MOVE + 4) }));
|
||||
// TODO: Add frost resist aura for paladins?
|
||||
}
|
||||
|
||||
void WotlkDungeonNexStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new FactionCommanderMultiplier(botAI));
|
||||
multipliers.push_back(new TelestraMultiplier(botAI));
|
||||
multipliers.push_back(new AnomalusMultiplier(botAI));
|
||||
multipliers.push_back(new OrmorokMultiplier(botAI));
|
||||
}
|
||||
17
src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.h
Normal file
17
src/Ai/Dungeon/Nexus/Strategy/NexusStrategy.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXSTRATEGY_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONNEXSTRATEGY_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
class WotlkDungeonNexStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
WotlkDungeonNexStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
virtual std::string const getName() override { return "nexus"; }
|
||||
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
|
||||
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
107
src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.cpp
Normal file
107
src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "Playerbots.h"
|
||||
#include "NexusTriggers.h"
|
||||
#include "AiObject.h"
|
||||
#include "AiObjectContext.h"
|
||||
|
||||
bool FactionCommanderWhirlwindTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = nullptr;
|
||||
uint8 faction = bot->GetTeamId();
|
||||
|
||||
switch (bot->GetMap()->GetDifficulty())
|
||||
{
|
||||
case DUNGEON_DIFFICULTY_NORMAL:
|
||||
if (faction == TEAM_ALLIANCE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "horde commander");
|
||||
}
|
||||
else //if (faction == TEAM_HORDE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
|
||||
}
|
||||
break;
|
||||
case DUNGEON_DIFFICULTY_HEROIC:
|
||||
if (faction == TEAM_ALLIANCE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
|
||||
}
|
||||
else //if (faction == TEAM_HORDE)
|
||||
{
|
||||
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (boss && boss->HasUnitState(UNIT_STATE_CASTING))
|
||||
{
|
||||
if (boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TelestraFirebombTrigger::IsActive()
|
||||
{
|
||||
if (botAI->IsMelee(bot)) { return false; }
|
||||
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
|
||||
// Avoid split phase with the fake Telestra units, only match the true boss id
|
||||
return boss && boss->GetEntry() == NPC_TELESTRA;
|
||||
}
|
||||
|
||||
bool TelestraSplitPhaseTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
|
||||
// Only match split phase with the fake Telestra units
|
||||
return boss && boss->GetEntry() != NPC_TELESTRA;
|
||||
}
|
||||
|
||||
bool ChaoticRiftTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "anomalus");
|
||||
return boss && boss->HasAura(BUFF_RIFT_SHIELD);
|
||||
}
|
||||
|
||||
bool OrmorokSpikesTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
|
||||
if (!boss || !botAI->IsTank(bot)) { return false; }
|
||||
|
||||
GuidVector objects = AI_VALUE(GuidVector, "closest game objects");
|
||||
for (auto i = objects.begin(); i != objects.end(); ++i)
|
||||
{
|
||||
GameObject* go = botAI->GetGameObject(*i);
|
||||
if (go && go->GetEntry() == GO_CRYSTAL_SPIKE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OrmorokStackTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
|
||||
return (boss && !botAI->IsTank(bot));
|
||||
}
|
||||
|
||||
bool IntenseColdTrigger::IsActive()
|
||||
{
|
||||
// Adjust as needed - too much interrupting loses dps time,
|
||||
// but too many stacks is deadly. Assuming 3-5 is a good number to clear
|
||||
int stackThreshold = 5;
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza");
|
||||
return boss && botAI->GetAura("intense cold", bot, false, false, stackThreshold);
|
||||
}
|
||||
|
||||
bool KeristraszaPositioningTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza");
|
||||
// Include healers here for now, otherwise they stand in things
|
||||
return boss && !botAI->IsTank(bot) && !botAI->IsRangedDps(bot);
|
||||
// return boss && botAI->IsMelee(bot) && !botAI->IsTank(bot);
|
||||
}
|
||||
89
src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.h
Normal file
89
src/Ai/Dungeon/Nexus/Trigger/NexusTriggers.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERS_H
|
||||
|
||||
#include "Trigger.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "GenericTriggers.h"
|
||||
#include "DungeonStrategyUtils.h"
|
||||
|
||||
enum NexusIDs
|
||||
{
|
||||
// Faction Commander
|
||||
NPC_ALLIANCE_COMMANDER = 27949,
|
||||
NPC_HORDE_COMMANDER = 27947,
|
||||
NPC_COMMANDER_STOUTBEARD = 26796,
|
||||
NPC_COMMANDER_KOLURG = 26798,
|
||||
// SPELL_FRIGHTENING_SHOUT = 19134,
|
||||
SPELL_WHIRLWIND = 38618,
|
||||
|
||||
// Grand Magus Telestra
|
||||
NPC_TELESTRA = 26731,
|
||||
NPC_FIRE_MAGUS = 26928,
|
||||
NPC_FROST_MAGUS = 26930,
|
||||
NPC_ARCANE_MAGUS = 26929,
|
||||
|
||||
// Anomalus
|
||||
BUFF_RIFT_SHIELD = 47748,
|
||||
|
||||
// Ormorok the Tree Shaper
|
||||
// NPC_CRYSTAL_SPIKE = 27099,
|
||||
GO_CRYSTAL_SPIKE = 188537,
|
||||
};
|
||||
|
||||
class FactionCommanderWhirlwindTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
FactionCommanderWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "faction commander whirlwind") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TelestraFirebombTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TelestraFirebombTrigger(PlayerbotAI* ai) : Trigger(ai, "telestra firebomb spread") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class TelestraSplitPhaseTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
TelestraSplitPhaseTrigger(PlayerbotAI* ai) : Trigger(ai, "telestra split phase") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class ChaoticRiftTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
ChaoticRiftTrigger(PlayerbotAI* ai) : Trigger(ai, "chaotic rift") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class OrmorokSpikesTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
OrmorokSpikesTrigger(PlayerbotAI* ai) : Trigger(ai, "ormorok spikes") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class OrmorokStackTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
OrmorokStackTrigger(PlayerbotAI* ai) : Trigger(ai, "ormorok stack") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class IntenseColdTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
IntenseColdTrigger(PlayerbotAI* ai) : Trigger(ai, "intense cold") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class KeristraszaPositioningTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
KeristraszaPositioningTrigger(PlayerbotAI* ai) : Trigger(ai, "keristrasza positioning") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
354
src/Ai/Dungeon/Oculus/Action/OculusActions.cpp
Normal file
354
src/Ai/Dungeon/Oculus/Action/OculusActions.cpp
Normal file
@@ -0,0 +1,354 @@
|
||||
#include "Playerbots.h"
|
||||
#include "OculusActions.h"
|
||||
#include "OculusStrategy.h"
|
||||
#include "LastSpellCastValue.h"
|
||||
|
||||
bool AvoidUnstableSphereAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "drakos the interrogator");
|
||||
if (!boss) { return false; }
|
||||
|
||||
float radius = 12.0f;
|
||||
float extraDistance = 1.0f;
|
||||
Unit* closestSphere = nullptr;
|
||||
|
||||
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||
for (auto& npc : npcs)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npc);
|
||||
if (unit && unit->GetEntry() == NPC_UNSTABLE_SPHERE && !unit->isMoving())
|
||||
{
|
||||
if (!closestSphere || bot->GetExactDist2d(unit) < bot->GetExactDist2d(closestSphere))
|
||||
{
|
||||
closestSphere = unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closestSphere && bot->GetExactDist2d(closestSphere) < radius + extraDistance)
|
||||
{
|
||||
return MoveAway(closestSphere, fmin(3.0f, bot->GetExactDist2d(closestSphere) - radius + extraDistance));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MountDrakeAction::isPossible() { return bot->GetMapId() == OCULUS_MAP_ID; }
|
||||
bool MountDrakeAction::Execute(Event event)
|
||||
{
|
||||
std::map<int32, int32> drakeAssignments;
|
||||
// Composition can be adjusted - both 3/1/1 and 2/2/1 are good default comps
|
||||
// {Amber, Emerald, Ruby}
|
||||
std::vector<uint8> composition = {2, 2, 1};
|
||||
// std::vector<uint8> composition = {3, 1, 1};
|
||||
int32 myIndex = botAI->GetGroupSlotIndex(bot);
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master) { return false; }
|
||||
Unit* vehicle = master->GetVehicleBase();
|
||||
if (!vehicle) { return false; }
|
||||
|
||||
// Subtract the player's chosen mount type from the composition so player can play whichever they prefer
|
||||
switch (vehicle->GetEntry())
|
||||
{
|
||||
case NPC_AMBER_DRAKE:
|
||||
composition[0]--;
|
||||
break;
|
||||
case NPC_EMERALD_DRAKE:
|
||||
composition[1]--;
|
||||
break;
|
||||
case NPC_RUBY_DRAKE:
|
||||
composition[2]--;
|
||||
break;
|
||||
}
|
||||
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
Player* player = botAI->GetPlayer(member);
|
||||
if (!player->GetSession()->IsBot()) { continue; }
|
||||
|
||||
for (int i = 0; i < composition.size(); i++)
|
||||
{
|
||||
if (composition[i] > 0)
|
||||
{
|
||||
drakeAssignments[botAI->GetGroupSlotIndex(player)] = DRAKE_ITEMS[i];
|
||||
composition[i]--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Correct/update the drake items in inventories incase assignments have changed
|
||||
for (uint32 itemId : DRAKE_ITEMS)
|
||||
{
|
||||
Item* item = bot->GetItemByEntry(itemId);
|
||||
if (!item) { continue; }
|
||||
|
||||
if (itemId == drakeAssignments[myIndex])
|
||||
{
|
||||
// Use our assigned drake
|
||||
return UseItemAuto(item);
|
||||
}
|
||||
// Else assigned drake is different, destroy old drake
|
||||
uint32 count = 1;
|
||||
bot->DestroyItemCount(item, count, true);
|
||||
break;
|
||||
}
|
||||
|
||||
// Bot does not have the correct drake item
|
||||
bot->AddItem(drakeAssignments[myIndex], 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DismountDrakeAction::Execute(Event event)
|
||||
{
|
||||
if (bot->GetVehicle())
|
||||
{
|
||||
bot->ExitVehicle();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OccFlyDrakeAction::Execute(Event event)
|
||||
{
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master) { return false; }
|
||||
Unit* masterVehicle = master->GetVehicleBase();
|
||||
Unit* vehicleBase = bot->GetVehicleBase();
|
||||
if (!vehicleBase || !masterVehicle) { return false; }
|
||||
|
||||
MotionMaster* mm = vehicleBase->GetMotionMaster();
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ley-guardian eregos");
|
||||
if (boss && !boss->HasAura(SPELL_PLANAR_SHIFT))
|
||||
{
|
||||
// Handle as boss encounter instead of formation flight
|
||||
mm->Clear(false);
|
||||
float distance = vehicleBase->GetExactDist(boss);
|
||||
float range = 55.0f; // Drake range is 60yd
|
||||
if (distance > range)
|
||||
{
|
||||
mm->MoveForwards(boss, range - distance);
|
||||
vehicleBase->SendMovementFlagUpdate();
|
||||
return true;
|
||||
}
|
||||
|
||||
vehicleBase->SetFacingToObject(boss);
|
||||
mm->MoveIdle();
|
||||
vehicleBase->SendMovementFlagUpdate();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vehicleBase->GetExactDist(masterVehicle) > 20.0f)
|
||||
{
|
||||
// 3/4 of a circle, with frontal cone 90 deg unobstructed
|
||||
float angle = botAI->GetGroupSlotIndex(bot) * (2*M_PI - M_PI_2)/5 + M_PI_2;
|
||||
vehicleBase->SetCanFly(true);
|
||||
mm->MoveFollow(masterVehicle, 15.0f, angle);
|
||||
vehicleBase->SendMovementFlagUpdate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OccDrakeAttackAction::Execute(Event event)
|
||||
{
|
||||
vehicleBase = bot->GetVehicleBase();
|
||||
if (!vehicleBase) { return false; }
|
||||
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
|
||||
if (!target)
|
||||
{
|
||||
GuidVector npcs = AI_VALUE(GuidVector, "possible targets");
|
||||
for (auto& npc : npcs)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(npc);
|
||||
if (!unit || !unit->IsInCombat()) { continue; }
|
||||
|
||||
target = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check this again to see if a target was assigned
|
||||
if (!target) { return false; }
|
||||
|
||||
switch (vehicleBase->GetEntry())
|
||||
{
|
||||
case NPC_AMBER_DRAKE:
|
||||
return AmberDrakeAction(target);
|
||||
case NPC_EMERALD_DRAKE:
|
||||
return EmeraldDrakeAction(target);
|
||||
case NPC_RUBY_DRAKE:
|
||||
return RubyDrakeAction(target);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OccDrakeAttackAction::CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown)
|
||||
{
|
||||
if (botAI->CanCastVehicleSpell(spellId, target))
|
||||
if (botAI->CastVehicleSpell(spellId, target))
|
||||
{
|
||||
vehicleBase->AddSpellCooldown(spellId, 0, cooldown);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OccDrakeAttackAction::AmberDrakeAction(Unit* target)
|
||||
{
|
||||
Aura* shockCharges = target->GetAura(SPELL_SHOCK_CHARGE, vehicleBase->GetGUID());
|
||||
if (shockCharges && shockCharges->GetStackAmount() > 8)
|
||||
{
|
||||
// At 9 charges, better to detonate and re-channel rather than stacking the last charge due to gcd
|
||||
// If stacking Amber drakes, may need to drop this even lower as the charges stack so fast
|
||||
return CastDrakeSpellAction(target, SPELL_SHOCK_LANCE, 0);
|
||||
}
|
||||
|
||||
// Deal with enrage after shock charges, as Stop Time adds 5 charges and they may get wasted
|
||||
if (target->HasAura(SPELL_ENRAGED_ASSAULT) &&
|
||||
!target->HasAura(SPELL_STOP_TIME) &&
|
||||
!vehicleBase->HasSpellCooldown(SPELL_STOP_TIME))
|
||||
{
|
||||
return CastDrakeSpellAction(target, SPELL_STOP_TIME, 60000);
|
||||
}
|
||||
|
||||
if (!vehicleBase->FindCurrentSpellBySpellId(SPELL_TEMPORAL_RIFT))
|
||||
{
|
||||
return CastDrakeSpellAction(target, SPELL_TEMPORAL_RIFT, 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OccDrakeAttackAction::EmeraldDrakeAction(Unit* target)
|
||||
{
|
||||
Aura* poisonStacks = target->GetAura(SPELL_LEECHING_POISON, vehicleBase->GetGUID());
|
||||
if (!poisonStacks || (poisonStacks->GetStackAmount() < 3 ||
|
||||
poisonStacks->GetDuration() < 4000))
|
||||
{
|
||||
return CastDrakeSpellAction(target, SPELL_LEECHING_POISON, 0);
|
||||
}
|
||||
|
||||
if (!vehicleBase->HasSpellCooldown(SPELL_TOUCH_THE_NIGHTMARE) &&
|
||||
(!target->HasAura(SPELL_TOUCH_THE_NIGHTMARE) || vehicleBase->HealthAbovePct(90)))
|
||||
{
|
||||
return CastDrakeSpellAction(target, SPELL_TOUCH_THE_NIGHTMARE, 10000);
|
||||
}
|
||||
|
||||
Unit* healingTarget = nullptr;
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(member);
|
||||
if (!unit || bot->GetGUID() == member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Unit* drake = unit->GetVehicleBase();
|
||||
if (!drake || drake->IsFullHealth()) { continue; }
|
||||
|
||||
if (!healingTarget || drake->GetHealthPct() < healingTarget->GetHealthPct() - 15.0f)
|
||||
{
|
||||
healingTarget = drake;
|
||||
}
|
||||
}
|
||||
|
||||
Spell* currentSpell = vehicleBase->FindCurrentSpellBySpellId(SPELL_DREAM_FUNNEL);
|
||||
if (healingTarget)
|
||||
{
|
||||
if (!currentSpell || currentSpell->m_targets.GetUnitTarget() != healingTarget)
|
||||
{
|
||||
float distance = vehicleBase->GetExactDist(healingTarget);
|
||||
float range = 55.0f;
|
||||
if (distance > range)
|
||||
{
|
||||
MotionMaster* mm = vehicleBase->GetMotionMaster();
|
||||
mm->Clear(false);
|
||||
mm->MoveForwards(healingTarget, distance - range - 10.0f);
|
||||
vehicleBase->SendMovementFlagUpdate();
|
||||
return false;
|
||||
}
|
||||
return CastDrakeSpellAction(healingTarget, SPELL_DREAM_FUNNEL, 0);
|
||||
}
|
||||
}
|
||||
// Fill GCDs with Leeching Poison to refresh timer, rather than idling
|
||||
if (!currentSpell)
|
||||
{
|
||||
return CastDrakeSpellAction(target, SPELL_LEECHING_POISON, 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OccDrakeAttackAction::RubyDrakeAction(Unit* target)
|
||||
{
|
||||
Aura* evasiveCharges = vehicleBase->GetAura(SPELL_EVASIVE_CHARGES);
|
||||
Aura* evasiveManeuvers = vehicleBase->GetAura(SPELL_EVASIVE_MANEUVERS);
|
||||
|
||||
if (evasiveCharges)
|
||||
{
|
||||
if (evasiveManeuvers &&
|
||||
!vehicleBase->HasSpellCooldown(SPELL_MARTYR) &&
|
||||
evasiveManeuvers->GetDuration() > 10000 &&
|
||||
evasiveCharges->GetStackAmount() >= 5)
|
||||
{
|
||||
return CastDrakeSpellAction(vehicleBase, SPELL_MARTYR, 10000);
|
||||
}
|
||||
|
||||
if (!vehicleBase->HasSpellCooldown(SPELL_EVASIVE_MANEUVERS) &&
|
||||
evasiveCharges->GetStackAmount() >= 10)
|
||||
{
|
||||
return CastDrakeSpellAction(vehicleBase, SPELL_EVASIVE_MANEUVERS, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
return CastDrakeSpellAction(target, SPELL_SEARING_WRATH, 0);
|
||||
}
|
||||
|
||||
bool AvoidArcaneExplosionAction::Execute(Event event)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom");
|
||||
if (!boss) { return false; }
|
||||
|
||||
const Position* closestPos = nullptr;
|
||||
|
||||
for (auto& position : uromSafePositions)
|
||||
{
|
||||
if (!closestPos || bot->GetExactDist(position) < bot->GetExactDist(closestPos))
|
||||
{
|
||||
closestPos = &position;
|
||||
}
|
||||
}
|
||||
|
||||
if (!closestPos) { return false; }
|
||||
|
||||
return MoveNear(bot->GetMapId(), closestPos->GetPositionX(), closestPos->GetPositionY(), closestPos->GetPositionZ(), 2.0f, MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
|
||||
bool TimeBombSpreadAction::Execute(Event event)
|
||||
{
|
||||
float radius = 10.0f;
|
||||
float distanceExtra = 2.0f;
|
||||
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
if (bot->GetGUID() == member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Unit* unit = botAI->GetUnit(member);
|
||||
if (unit && bot->GetExactDist2d(unit) < radius)
|
||||
{
|
||||
return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
76
src/Ai/Dungeon/Oculus/Action/OculusActions.h
Normal file
76
src/Ai/Dungeon/Oculus/Action/OculusActions.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOCCACTIONS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOCCACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "OculusTriggers.h"
|
||||
#include "UseItemAction.h"
|
||||
#include "GenericSpellActions.h"
|
||||
|
||||
const Position uromSafePositions[3] =
|
||||
{
|
||||
Position(1138.88f, 1052.22f, 508.36f),
|
||||
Position(1084.62f, 1079.71f, 508.36f),
|
||||
Position(1087.42f, 1020.132f, 508.36f)
|
||||
};
|
||||
|
||||
class AvoidUnstableSphereAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidUnstableSphereAction(PlayerbotAI* ai) : MovementAction(ai, "avoid unstable sphere") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class MountDrakeAction : public UseItemAction
|
||||
{
|
||||
public:
|
||||
MountDrakeAction(PlayerbotAI* ai) : UseItemAction(ai, "mount drake") {}
|
||||
bool Execute(Event event) override;
|
||||
bool isPossible() override;
|
||||
};
|
||||
|
||||
class DismountDrakeAction : public Action
|
||||
{
|
||||
public:
|
||||
DismountDrakeAction(PlayerbotAI* ai) : Action(ai, "dismount drake") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class OccFlyDrakeAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
OccFlyDrakeAction(PlayerbotAI* ai) : MovementAction(ai, "occ fly drake") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class OccDrakeAttackAction : public Action
|
||||
{
|
||||
public:
|
||||
OccDrakeAttackAction(PlayerbotAI* botAI) : Action(botAI, "occ drake attack") {}
|
||||
bool Execute(Event event) override;
|
||||
|
||||
protected:
|
||||
Unit* vehicleBase;
|
||||
bool CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown);
|
||||
bool AmberDrakeAction(Unit* target);
|
||||
bool EmeraldDrakeAction(Unit* target);
|
||||
bool RubyDrakeAction(Unit* target);
|
||||
};
|
||||
|
||||
class AvoidArcaneExplosionAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidArcaneExplosionAction(PlayerbotAI* ai) : MovementAction(ai, "avoid arcane explosion") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class TimeBombSpreadAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
TimeBombSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "time bomb spread") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
111
src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.cpp
Normal file
111
src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "OculusMultipliers.h"
|
||||
#include "OculusActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "MovementActions.h"
|
||||
#include "OculusTriggers.h"
|
||||
#include "FollowActions.h"
|
||||
#include "ReachTargetActions.h"
|
||||
|
||||
float MountingDrakeMultiplier::GetValue(Action* action)
|
||||
{
|
||||
// P.I.T.A bug where the bots will somehow interrupt their item spell use,
|
||||
// even though the 0.5 sec cast goes off, it puts the drake essence on 15 sec cd
|
||||
// and no drake comes down.
|
||||
// It seems like this is due to moving/other actions being processed during the 0.5 secs.
|
||||
// If we suppress everything, they seem to mount properly. A bit of a ham-fisted solution but it works
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master) { return 1.0f; }
|
||||
|
||||
if (bot->GetMapId() != OCULUS_MAP_ID || !master->GetVehicleBase() || bot->GetVehicleBase()) { return 1.0f; }
|
||||
|
||||
if (!dynamic_cast<MountDrakeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float OccFlyingMultiplier::GetValue(Action* action)
|
||||
{
|
||||
if (bot->GetMapId() != OCULUS_MAP_ID || !bot->GetVehicleBase()) { return 1.0f; }
|
||||
|
||||
// Suppresses FollowAction as well as some attack-based movements
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<OccFlyDrakeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float UromMultiplier::GetValue(Action* action)
|
||||
{
|
||||
if (GetPhaseByCurrentPosition(bot) < 3)
|
||||
{
|
||||
Unit* target = action->GetTarget();
|
||||
if (target && target->GetEntry() == NPC_MAGE_LORD_UROM)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
// REAL BOSS FIGHT
|
||||
if (boss->HasUnitState(UNIT_STATE_CASTING) &&
|
||||
boss->FindCurrentSpellBySpellId(SPELL_EMPOWERED_ARCANE_EXPLOSION))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<AvoidArcaneExplosionAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't bother avoiding Frostbomb for melee
|
||||
if (botAI->IsMelee(bot))
|
||||
{
|
||||
if (dynamic_cast<AvoidAoeAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (bot->HasAura(SPELL_TIME_BOMB))
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<TimeBombSpreadAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
uint8 UromMultiplier::GetPhaseByCurrentPosition(Unit* unit)
|
||||
{
|
||||
// Distance to return a positive match for spawn platforms, tweak slightly if needed/
|
||||
// Make sure this doesn't get too large and reach the central ring as well
|
||||
float distance = 60.0f;
|
||||
|
||||
for (uint8 i = 0; i < 3; ++i)
|
||||
{
|
||||
if (unit->GetDistance(uromCoords[i][0], uromCoords[i][1], uromCoords[i][2]) < distance)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
float EregosMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ley-guardian eregos");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
if (boss->HasAura(SPELL_PLANAR_SHIFT && dynamic_cast<OccDrakeAttackAction*>(action)))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
53
src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.h
Normal file
53
src/Ai/Dungeon/Oculus/Multiplier/OculusMultipliers.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOCCMULTIPLIERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOCCMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
#include "Unit.h"
|
||||
|
||||
const float uromCoords[4][4] =
|
||||
{ // Platform coordinates
|
||||
{1177.47f, 937.722f, 527.405f, 2.21657f},
|
||||
{968.66f, 1042.53f, 527.32f, 0.077f},
|
||||
{1164.02f, 1170.85f, 527.321f, 3.66f},
|
||||
{1118.31f, 1080.377f, 508.361f, 4.25f} // Inner ring, actual boss fight
|
||||
};
|
||||
|
||||
class MountingDrakeMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
MountingDrakeMultiplier(PlayerbotAI* ai) : Multiplier(ai, "mounting drake") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class OccFlyingMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
OccFlyingMultiplier(PlayerbotAI* ai) : Multiplier(ai, "occ flying drake") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class UromMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
UromMultiplier(PlayerbotAI* ai) : Multiplier(ai, "mage-lord urom") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
protected:
|
||||
uint8 GetPhaseByCurrentPosition(Unit* boss);
|
||||
};
|
||||
|
||||
class EregosMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
EregosMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ley-guardian eregos") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
#endif
|
||||
30
src/Ai/Dungeon/Oculus/OculusActionContext.h
Normal file
30
src/Ai/Dungeon/Oculus/OculusActionContext.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOCCACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOCCACTIONCONTEXT_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "OculusActions.h"
|
||||
|
||||
class WotlkDungeonOccActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonOccActionContext() {
|
||||
creators["avoid unstable sphere"] = &WotlkDungeonOccActionContext::avoid_unstable_sphere;
|
||||
creators["mount drake"] = &WotlkDungeonOccActionContext::mount_drake;
|
||||
creators["dismount drake"] = &WotlkDungeonOccActionContext::dismount_drake;
|
||||
creators["occ fly drake"] = &WotlkDungeonOccActionContext::occ_fly_drake;
|
||||
creators["occ drake attack"] = &WotlkDungeonOccActionContext::occ_drake_attack;
|
||||
creators["avoid arcane explosion"] = &WotlkDungeonOccActionContext::avoid_arcane_explosion;
|
||||
creators["time bomb spread"] = &WotlkDungeonOccActionContext::time_bomb_spread;
|
||||
}
|
||||
private:
|
||||
static Action* avoid_unstable_sphere(PlayerbotAI* ai) { return new AvoidUnstableSphereAction(ai); }
|
||||
static Action* mount_drake(PlayerbotAI* ai) { return new MountDrakeAction(ai); }
|
||||
static Action* dismount_drake(PlayerbotAI* ai) { return new DismountDrakeAction(ai); }
|
||||
static Action* occ_fly_drake(PlayerbotAI* ai) { return new OccFlyDrakeAction(ai); }
|
||||
static Action* occ_drake_attack(PlayerbotAI* ai) { return new OccDrakeAttackAction(ai); }
|
||||
static Action* avoid_arcane_explosion(PlayerbotAI* ai) { return new AvoidArcaneExplosionAction(ai); }
|
||||
static Action* time_bomb_spread(PlayerbotAI* ai) { return new TimeBombSpreadAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
33
src/Ai/Dungeon/Oculus/OculusTriggerContext.h
Normal file
33
src/Ai/Dungeon/Oculus/OculusTriggerContext.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERCONTEXT_H
|
||||
|
||||
#include "NamedObjectContext.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "OculusTriggers.h"
|
||||
|
||||
class WotlkDungeonOccTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonOccTriggerContext()
|
||||
{
|
||||
creators["unstable sphere"] = &WotlkDungeonOccTriggerContext::unstable_sphere;
|
||||
creators["drake mount"] = &WotlkDungeonOccTriggerContext::drake_mount;
|
||||
creators["drake dismount"] = &WotlkDungeonOccTriggerContext::drake_dismount;
|
||||
creators["group flying"] = &WotlkDungeonOccTriggerContext::group_flying;
|
||||
creators["drake combat"] = &WotlkDungeonOccTriggerContext::drake_combat;
|
||||
creators["varos cloudstrider"] = &WotlkDungeonOccTriggerContext::varos_cloudstrider;
|
||||
creators["arcane explosion"] = &WotlkDungeonOccTriggerContext::arcane_explosion;
|
||||
creators["time bomb"] = &WotlkDungeonOccTriggerContext::time_bomb;
|
||||
}
|
||||
private:
|
||||
static Trigger* unstable_sphere(PlayerbotAI* ai) { return new DrakosUnstableSphereTrigger(ai); }
|
||||
static Trigger* drake_mount(PlayerbotAI* ai) { return new DrakeMountTrigger(ai); }
|
||||
static Trigger* drake_dismount(PlayerbotAI* ai) { return new DrakeDismountTrigger(ai); }
|
||||
static Trigger* group_flying(PlayerbotAI* ai) { return new GroupFlyingTrigger(ai); }
|
||||
static Trigger* drake_combat(PlayerbotAI* ai) { return new DrakeCombatTrigger(ai); }
|
||||
static Trigger* varos_cloudstrider(PlayerbotAI* ai) { return new VarosCloudstriderTrigger(ai); }
|
||||
static Trigger* arcane_explosion(PlayerbotAI* ai) { return new UromArcaneExplosionTrigger(ai); }
|
||||
static Trigger* time_bomb(PlayerbotAI* ai) { return new UromTimeBombTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
41
src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.cpp
Normal file
41
src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "OculusStrategy.h"
|
||||
#include "OculusMultipliers.h"
|
||||
|
||||
void WotlkDungeonOccStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
{
|
||||
// Drakos the Interrogator
|
||||
// TODO: May need work, TBA.
|
||||
triggers.push_back(new TriggerNode("unstable sphere",
|
||||
{ NextAction("avoid unstable sphere", ACTION_MOVE + 5) }));
|
||||
|
||||
// DRAKES
|
||||
triggers.push_back(new TriggerNode("drake mount",
|
||||
{ NextAction("mount drake", ACTION_RAID + 5) }));
|
||||
triggers.push_back(new TriggerNode("drake dismount",
|
||||
{ NextAction("dismount drake", ACTION_RAID + 5) }));
|
||||
triggers.push_back(new TriggerNode("group flying",
|
||||
{ NextAction("occ fly drake", ACTION_NORMAL + 1) }));
|
||||
triggers.push_back(new TriggerNode("drake combat",
|
||||
{ NextAction("occ drake attack", ACTION_NORMAL + 5) }));
|
||||
|
||||
// Varos Cloudstrider
|
||||
// Seems to be no way to identify the marked cores, may need to hook boss AI..
|
||||
// triggers.push_back(new TriggerNode("varos cloudstrider",
|
||||
// { NextAction("avoid energize cores", ACTION_RAID + 5) }));
|
||||
|
||||
// Mage-Lord Urom
|
||||
triggers.push_back(new TriggerNode("arcane explosion",
|
||||
{ NextAction("avoid arcane explosion", ACTION_MOVE + 5) }));
|
||||
triggers.push_back(new TriggerNode("time bomb",
|
||||
{ NextAction("time bomb spread", ACTION_MOVE + 4) }));
|
||||
|
||||
// Ley-Guardian Eregos
|
||||
}
|
||||
|
||||
void WotlkDungeonOccStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new MountingDrakeMultiplier(botAI));
|
||||
multipliers.push_back(new OccFlyingMultiplier(botAI));
|
||||
multipliers.push_back(new UromMultiplier(botAI));
|
||||
multipliers.push_back(new EregosMultiplier(botAI));
|
||||
}
|
||||
17
src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.h
Normal file
17
src/Ai/Dungeon/Oculus/Strategy/OculusStrategy.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOCCSTRATEGY_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOCCSTRATEGY_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Strategy.h"
|
||||
|
||||
class WotlkDungeonOccStrategy : public Strategy
|
||||
{
|
||||
public:
|
||||
WotlkDungeonOccStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||
virtual std::string const getName() override { return "oculus"; }
|
||||
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
|
||||
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
75
src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.cpp
Normal file
75
src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "Playerbots.h"
|
||||
#include "OculusTriggers.h"
|
||||
#include "AiObject.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Unit.h"
|
||||
|
||||
bool DrakosUnstableSphereTrigger::IsActive()
|
||||
{
|
||||
// Doesn't seem to be much point trying to get melee to dodge this,
|
||||
// they get hit anyway and it just causes a lot of running around and chaos
|
||||
// if (botAI->IsMelee(bot)) { return false; }
|
||||
if (botAI->IsTank(bot)) { return false; }
|
||||
|
||||
GuidVector targets = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||
for (auto& target : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(target);
|
||||
if (unit && unit->GetEntry() == NPC_UNSTABLE_SPHERE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DrakeMountTrigger::IsActive()
|
||||
{
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master) { return false; }
|
||||
|
||||
return master->GetVehicleBase() && !bot->GetVehicleBase();
|
||||
}
|
||||
|
||||
bool DrakeDismountTrigger::IsActive()
|
||||
{
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master) { return false; }
|
||||
|
||||
return !master->GetVehicleBase() && bot->GetVehicleBase();
|
||||
}
|
||||
|
||||
bool GroupFlyingTrigger::IsActive()
|
||||
{
|
||||
Player* master = botAI->GetMaster();
|
||||
if (!master) { return false; }
|
||||
|
||||
return master->GetVehicleBase() && bot->GetVehicleBase();
|
||||
}
|
||||
|
||||
bool DrakeCombatTrigger::IsActive()
|
||||
{
|
||||
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
|
||||
return !targets.empty();
|
||||
}
|
||||
|
||||
bool VarosCloudstriderTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "varos cloudstrider");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UromArcaneExplosionTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom");
|
||||
if (!boss) { return false; }
|
||||
|
||||
return bool(boss->FindCurrentSpellBySpellId(SPELL_EMPOWERED_ARCANE_EXPLOSION));
|
||||
}
|
||||
|
||||
bool UromTimeBombTrigger::IsActive()
|
||||
{
|
||||
return bot->HasAura(SPELL_TIME_BOMB);
|
||||
}
|
||||
129
src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.h
Normal file
129
src/Ai/Dungeon/Oculus/Trigger/OculusTriggers.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOCCTRIGGERS_H
|
||||
|
||||
#include "Trigger.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "GenericTriggers.h"
|
||||
#include "DungeonStrategyUtils.h"
|
||||
|
||||
enum OculusIDs
|
||||
{
|
||||
// Drakos the Interrogator
|
||||
NPC_UNSTABLE_SPHERE = 28166,
|
||||
SPELL_UNSTABLE_SPHERE_PASSIVE = 50756,
|
||||
SPELL_UNSTABLE_SPHERE_PULSE = 50757,
|
||||
SPELL_UNSTABLE_SPHERE_TIMER = 50758,
|
||||
|
||||
// Drakes
|
||||
NPC_AMBER_DRAKE = 27755,
|
||||
NPC_EMERALD_DRAKE = 27692,
|
||||
NPC_RUBY_DRAKE = 27756,
|
||||
ITEM_AMBER_ESSENCE = 37859,
|
||||
ITEM_EMERALD_ESSENCE = 37815,
|
||||
ITEM_RUBY_ESSENCE = 37860,
|
||||
SPELL_AMBER_ESSENCE = 49461,
|
||||
SPELL_EMERALD_ESSENCE = 49345,
|
||||
SPELL_RUBY_ESSENCE = 49462,
|
||||
// Abilities:
|
||||
// Amber
|
||||
SPELL_SHOCK_LANCE = 49840,
|
||||
SPELL_SHOCK_CHARGE = 49836,
|
||||
SPELL_STOP_TIME = 49838,
|
||||
SPELL_TEMPORAL_RIFT = 49592,
|
||||
// Emerald
|
||||
SPELL_LEECHING_POISON = 50328,
|
||||
SPELL_TOUCH_THE_NIGHTMARE = 50341,
|
||||
SPELL_DREAM_FUNNEL = 50344,
|
||||
// Ruby
|
||||
SPELL_SEARING_WRATH = 50232,
|
||||
SPELL_EVASIVE_MANEUVERS = 50240,
|
||||
SPELL_EVASIVE_CHARGES = 50241,
|
||||
SPELL_MARTYR = 50253,
|
||||
|
||||
// Varos Cloudstrider
|
||||
NPC_CENTRIFUGE_CORE = 28183,
|
||||
|
||||
// Mage-Lord Urom
|
||||
NPC_MAGE_LORD_UROM = 27655,
|
||||
SPELL_TIME_BOMB_N = 51121,
|
||||
SPELL_TIME_BOMB_H = 59376,
|
||||
SPELL_EMPOWERED_ARCANE_EXPLOSION_N = 51110,
|
||||
SPELL_EMPOWERED_ARCANE_EXPLOSION_H = 59377,
|
||||
|
||||
// Ley-Guardian Eregos
|
||||
SPELL_ENRAGED_ASSAULT = 51170,
|
||||
SPELL_PLANAR_SHIFT = 51162,
|
||||
};
|
||||
|
||||
#define SPELL_EMPOWERED_ARCANE_EXPLOSION DUNGEON_MODE(bot, SPELL_EMPOWERED_ARCANE_EXPLOSION_N, SPELL_EMPOWERED_ARCANE_EXPLOSION_H)
|
||||
#define SPELL_TIME_BOMB DUNGEON_MODE(bot, SPELL_TIME_BOMB_N, SPELL_TIME_BOMB_H)
|
||||
|
||||
const std::vector<uint32> DRAKE_ITEMS = {ITEM_AMBER_ESSENCE, ITEM_EMERALD_ESSENCE, ITEM_RUBY_ESSENCE};
|
||||
const std::vector<uint32> DRAKE_SPELLS = {SPELL_AMBER_ESSENCE, SPELL_EMERALD_ESSENCE, SPELL_RUBY_ESSENCE};
|
||||
const uint32 OCULUS_MAP_ID = 578;
|
||||
|
||||
// const float uromCoords[4][4] =
|
||||
// {
|
||||
// {1177.47f, 937.722f, 527.405f, 2.21657f},
|
||||
// {968.66f, 1042.53f, 527.32f, 0.077f},
|
||||
// {1164.02f, 1170.85f, 527.321f, 3.66f},
|
||||
// {1118.31f, 1080.377f, 508.361f, 4.25f} // Inner ring, actual boss fight
|
||||
// };
|
||||
|
||||
class DrakosUnstableSphereTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
DrakosUnstableSphereTrigger(PlayerbotAI* ai) : Trigger(ai, "drakos unstable sphere") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class DrakeMountTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
DrakeMountTrigger(PlayerbotAI* ai) : Trigger(ai, "drake mount") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class DrakeDismountTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
DrakeDismountTrigger(PlayerbotAI* ai) : Trigger(ai, "drake dismount") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class GroupFlyingTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
GroupFlyingTrigger(PlayerbotAI* ai) : Trigger(ai, "drake fly") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class DrakeCombatTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
DrakeCombatTrigger(PlayerbotAI* ai) : Trigger(ai, "drake combat") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class VarosCloudstriderTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
VarosCloudstriderTrigger(PlayerbotAI* ai) : Trigger(ai, "varos cloudstrider") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class UromArcaneExplosionTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
UromArcaneExplosionTrigger(PlayerbotAI* ai) : Trigger(ai, "urom arcane explosion") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
class UromTimeBombTrigger : public Trigger
|
||||
{
|
||||
public:
|
||||
UromTimeBombTrigger(PlayerbotAI* ai) : Trigger(ai, "urom time bomb") {}
|
||||
bool IsActive() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
87
src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.cpp
Normal file
87
src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "Playerbots.h"
|
||||
#include "OldKingdomActions.h"
|
||||
#include "OldKingdomStrategy.h"
|
||||
|
||||
bool AttackNadoxGuardianAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = AI_VALUE2(Unit*, "find target", "ahn'kahar guardian");
|
||||
if (!target || AI_VALUE(Unit*, "current target") == target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Attack(target);
|
||||
}
|
||||
|
||||
bool AttackJedogaVolunteerAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = 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) // Null check for safety
|
||||
{
|
||||
continue; // Skip null or invalid units
|
||||
}
|
||||
if (unit && unit->GetEntry() == NPC_TWILIGHT_VOLUNTEER)
|
||||
{
|
||||
target = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target || AI_VALUE(Unit*, "current target") == target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Attack(target);
|
||||
}
|
||||
|
||||
bool AvoidShadowCrashAction::Execute(Event event)
|
||||
{
|
||||
// Could check all enemy units in range as it's possible to pull multiple of these mobs.
|
||||
// They should really be killed 1 by 1, multipulls are messy so we just handle singles for now
|
||||
Unit* unit = AI_VALUE2(Unit*, "find target", "forgotten one");
|
||||
if (!unit) { return false; }
|
||||
|
||||
Unit* victim = nullptr;
|
||||
float radius = 10.0f;
|
||||
float targetDist = radius + 2.0f;
|
||||
|
||||
// Actively move if targeted by a shadow crash.
|
||||
// Spell check not needed, they don't have any other non-instant casts
|
||||
if (unit->HasUnitState(UNIT_STATE_CASTING)) // && unit->FindCurrentSpellBySpellId(SPELL_SHADOW_CRASH))
|
||||
{
|
||||
// This doesn't seem to avoid casts very well, perhaps because this isn't checked while allies are casting.
|
||||
// TODO: Revisit if this is an issue in heroics, otherwise ignore shadow crashes for the most part.
|
||||
victim = botAI->GetUnit(unit->GetTarget());
|
||||
float distance = bot->GetExactDist2d(victim->GetPosition());
|
||||
|
||||
if (victim && distance < radius)
|
||||
{
|
||||
return MoveAway(victim, targetDist - distance);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise ranged members passively spread, to avoid AoE overlap
|
||||
if (botAI->IsMelee(bot)) { return false; }
|
||||
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(member);
|
||||
if (!unit || bot->GetGUID() == member)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float currentDist = bot->GetExactDist2d(botAI->GetUnit(member));
|
||||
if (currentDist < radius)
|
||||
{
|
||||
return MoveAway(botAI->GetUnit(member), targetDist - currentDist);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
31
src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.h
Normal file
31
src/Ai/Dungeon/OldKingdom/Action/OldKingdomActions.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOKACTIONS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOKACTIONS_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "AttackAction.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "Playerbots.h"
|
||||
#include "OldKingdomTriggers.h"
|
||||
|
||||
class AttackNadoxGuardianAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackNadoxGuardianAction(PlayerbotAI* ai) : AttackAction(ai, "attack nadox guardian") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AttackJedogaVolunteerAction : public AttackAction
|
||||
{
|
||||
public:
|
||||
AttackJedogaVolunteerAction(PlayerbotAI* ai) : AttackAction(ai, "attack jedoga volunteer") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
class AvoidShadowCrashAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
AvoidShadowCrashAction(PlayerbotAI* ai) : MovementAction(ai, "avoid shadow crash") {}
|
||||
bool Execute(Event event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "OldKingdomMultipliers.h"
|
||||
#include "OldKingdomActions.h"
|
||||
#include "GenericSpellActions.h"
|
||||
#include "ChooseTargetActions.h"
|
||||
#include "MovementActions.h"
|
||||
#include "OldKingdomTriggers.h"
|
||||
#include "Action.h"
|
||||
|
||||
float ElderNadoxMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "elder nadox");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
Unit* guardian = AI_VALUE2(Unit*, "find target", "ahn'kahar guardian");
|
||||
if (guardian)
|
||||
{
|
||||
if (dynamic_cast<DpsAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float JedogaShadowseekerMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "jedoga shadowseeker");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
Unit* volunteer = 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_TWILIGHT_VOLUNTEER)
|
||||
{
|
||||
volunteer = unit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (volunteer)
|
||||
{
|
||||
if (dynamic_cast<DpsAssistAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float ForgottenOneMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* unit = AI_VALUE2(Unit*, "find target", "forgotten one");
|
||||
if (!unit) { return 1.0f; }
|
||||
|
||||
if (bot->isMoving())
|
||||
{
|
||||
if (dynamic_cast<MovementAction*>(action))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
33
src/Ai/Dungeon/OldKingdom/Multiplier/OldKingdomMultipliers.h
Normal file
33
src/Ai/Dungeon/OldKingdom/Multiplier/OldKingdomMultipliers.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOKMULTIPLIERS_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOKMULTIPLIERS_H
|
||||
|
||||
#include "Multiplier.h"
|
||||
|
||||
class ElderNadoxMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
ElderNadoxMultiplier(PlayerbotAI* ai) : Multiplier(ai, "elder nadox") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class JedogaShadowseekerMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
JedogaShadowseekerMultiplier(PlayerbotAI* ai) : Multiplier(ai, "jedoga shadowseeker") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
class ForgottenOneMultiplier : public Multiplier
|
||||
{
|
||||
public:
|
||||
ForgottenOneMultiplier(PlayerbotAI* ai) : Multiplier(ai, "forgotten one") {}
|
||||
|
||||
public:
|
||||
virtual float GetValue(Action* action);
|
||||
};
|
||||
|
||||
#endif
|
||||
22
src/Ai/Dungeon/OldKingdom/OldKingdomActionContext.h
Normal file
22
src/Ai/Dungeon/OldKingdom/OldKingdomActionContext.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOKACTIONCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOKACTIONCONTEXT_H
|
||||
|
||||
#include "Action.h"
|
||||
#include "NamedObjectContext.h"
|
||||
#include "OldKingdomActions.h"
|
||||
|
||||
class WotlkDungeonOKActionContext : public NamedObjectContext<Action>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonOKActionContext() {
|
||||
creators["attack nadox guardian"] = &WotlkDungeonOKActionContext::attack_nadox_guardian;
|
||||
creators["attack jedoga volunteer"] = &WotlkDungeonOKActionContext::attack_jedoga_volunteer;
|
||||
creators["avoid shadow crash"] = &WotlkDungeonOKActionContext::avoid_shadow_crash;
|
||||
}
|
||||
private:
|
||||
static Action* attack_nadox_guardian(PlayerbotAI* ai) { return new AttackNadoxGuardianAction(ai); }
|
||||
static Action* attack_jedoga_volunteer(PlayerbotAI* ai) { return new AttackJedogaVolunteerAction(ai); }
|
||||
static Action* avoid_shadow_crash(PlayerbotAI* ai) { return new AvoidShadowCrashAction(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
23
src/Ai/Dungeon/OldKingdom/OldKingdomTriggerContext.h
Normal file
23
src/Ai/Dungeon/OldKingdom/OldKingdomTriggerContext.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef _PLAYERBOT_WOTLKDUNGEONOKTRIGGERCONTEXT_H
|
||||
#define _PLAYERBOT_WOTLKDUNGEONOKTRIGGERCONTEXT_H
|
||||
|
||||
#include "NamedObjectContext.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "OldKingdomTriggers.h"
|
||||
|
||||
class WotlkDungeonOKTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonOKTriggerContext()
|
||||
{
|
||||
creators["nadox guardian"] = &WotlkDungeonOKTriggerContext::nadox_guardian;
|
||||
creators["jedoga volunteer"] = &WotlkDungeonOKTriggerContext::jedoga_volunteer;
|
||||
creators["shadow crash"] = &WotlkDungeonOKTriggerContext::shadow_crash;
|
||||
}
|
||||
private:
|
||||
static Trigger* nadox_guardian(PlayerbotAI* ai) { return new NadoxGuardianTrigger(ai); }
|
||||
static Trigger* jedoga_volunteer(PlayerbotAI* ai) { return new JedogaVolunteerTrigger(ai); }
|
||||
static Trigger* shadow_crash(PlayerbotAI* ai) { return new ShadowCrashTrigger(ai); }
|
||||
};
|
||||
|
||||
#endif
|
||||
35
src/Ai/Dungeon/OldKingdom/Strategy/OldKingdomStrategy.cpp
Normal file
35
src/Ai/Dungeon/OldKingdom/Strategy/OldKingdomStrategy.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "OldKingdomStrategy.h"
|
||||
#include "OldKingdomMultipliers.h"
|
||||
|
||||
void WotlkDungeonOKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
{
|
||||
// Elder Nadox
|
||||
triggers.push_back(new TriggerNode("nadox guardian",
|
||||
{ NextAction("attack nadox guardian", ACTION_RAID + 5) }));
|
||||
|
||||
// Prince Taldaram
|
||||
// Flame Orb spawns in melee, doesn't have a clear direction until it starts moving.
|
||||
// Maybe not worth trying to avoid and just heal through. Only consideration is not to have ranged
|
||||
// players anywhere near melee when it spawns
|
||||
|
||||
// Jedoga Shadowseeker
|
||||
triggers.push_back(new TriggerNode("jedoga volunteer",
|
||||
{ NextAction("attack jedoga volunteer", ACTION_RAID + 5) }));
|
||||
|
||||
// Herald Volazj
|
||||
// Trash mobs before him have a big telegraphed shadow crash spell,
|
||||
// this can be avoided and is intended to be dodged
|
||||
triggers.push_back(new TriggerNode("shadow crash",
|
||||
{ NextAction("avoid shadow crash", ACTION_MOVE + 5) }));
|
||||
// Volazj is not implemented properly in AC, insanity phase does nothing.
|
||||
|
||||
// Amanitar (Heroic Only)
|
||||
// TODO: once I get to heroics
|
||||
}
|
||||
|
||||
void WotlkDungeonOKStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
|
||||
{
|
||||
multipliers.push_back(new ElderNadoxMultiplier(botAI));
|
||||
multipliers.push_back(new JedogaShadowseekerMultiplier(botAI));
|
||||
multipliers.push_back(new ForgottenOneMultiplier(botAI));
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user