Merge pull request #592 from Bobblybook/master

DTK Implementation
This commit is contained in:
Yunfan Li
2024-10-11 22:43:33 +08:00
committed by GitHub
18 changed files with 570 additions and 8 deletions

View File

@@ -54,6 +54,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new WotlkDungeonNexActionContext()); actionContexts.Add(new WotlkDungeonNexActionContext());
actionContexts.Add(new WotlkDungeonANActionContext()); actionContexts.Add(new WotlkDungeonANActionContext());
actionContexts.Add(new WotlkDungeonOKActionContext()); actionContexts.Add(new WotlkDungeonOKActionContext());
actionContexts.Add(new WotlkDungeonDTKActionContext());
triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext()); triggerContexts.Add(new ChatTriggerContext());
@@ -68,6 +69,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new WotlkDungeonNexTriggerContext()); triggerContexts.Add(new WotlkDungeonNexTriggerContext());
triggerContexts.Add(new WotlkDungeonANTriggerContext()); triggerContexts.Add(new WotlkDungeonANTriggerContext());
triggerContexts.Add(new WotlkDungeonOKTriggerContext()); triggerContexts.Add(new WotlkDungeonOKTriggerContext());
triggerContexts.Add(new WotlkDungeonDTKTriggerContext());
valueContexts.Add(new ValueContext()); valueContexts.Add(new ValueContext());

View File

@@ -6,11 +6,11 @@
#include "wotlk/nexus/NexusStrategy.h" #include "wotlk/nexus/NexusStrategy.h"
#include "wotlk/azjolnerub/AzjolNerubStrategy.h" #include "wotlk/azjolnerub/AzjolNerubStrategy.h"
#include "wotlk/oldkingdom/OldKingdomStrategy.h" #include "wotlk/oldkingdom/OldKingdomStrategy.h"
#include "wotlk/draktharonkeep/DrakTharonKeepStrategy.h"
/* /*
Full list/TODO: Full list/TODO:
Drak'Tharon Keep - DTK
Trollgore, Novos the Summoner, King Dred, The Prophet Tharon'ja
The Violet Hold - VH The Violet Hold - VH
Erekem, Moragg, Ichoron, Xevozz, Lavanthor, Zuramat the Obliterator, Cyanigosa Erekem, Moragg, Ichoron, Xevozz, Lavanthor, Zuramat the Obliterator, Cyanigosa
Gundrak - GD Gundrak - GD
@@ -75,8 +75,8 @@ class DungeonStrategyContext : public NamedObjectContext<Strategy>
static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonNexStrategy(botAI); } static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonNexStrategy(botAI); }
static Strategy* wotlk_an(PlayerbotAI* botAI) { return new WotlkDungeonANStrategy(botAI); } static Strategy* wotlk_an(PlayerbotAI* botAI) { return new WotlkDungeonANStrategy(botAI); }
static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonOKStrategy(botAI); } static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonOKStrategy(botAI); }
static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonDTKStrategy(botAI); }
static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }

View File

@@ -5,7 +5,7 @@
#include "nexus/NexusActionContext.h" #include "nexus/NexusActionContext.h"
#include "azjolnerub/AzjolNerubActionContext.h" #include "azjolnerub/AzjolNerubActionContext.h"
#include "oldkingdom/OldKingdomActionContext.h" #include "oldkingdom/OldKingdomActionContext.h"
// #include "draktharonkeep/DraktharonKeepActionContext.h" #include "draktharonkeep/DrakTharonKeepActionContext.h"
// #include "violethold/VioletHoldActionContext.h" // #include "violethold/VioletHoldActionContext.h"
// #include "gundrak/GundrakActionContext.h" // #include "gundrak/GundrakActionContext.h"
// #include "hallsofstone/HallsOfStoneActionContext.h" // #include "hallsofstone/HallsOfStoneActionContext.h"

View File

@@ -5,7 +5,7 @@
#include "nexus/NexusTriggerContext.h" #include "nexus/NexusTriggerContext.h"
#include "azjolnerub/AzjolNerubTriggerContext.h" #include "azjolnerub/AzjolNerubTriggerContext.h"
#include "oldkingdom/OldKingdomTriggerContext.h" #include "oldkingdom/OldKingdomTriggerContext.h"
// #include "draktharonkeep/DraktharonKeepTriggerContext.h" #include "draktharonkeep/DrakTharonKeepTriggerContext.h"
// #include "violethold/VioletHoldTriggerContext.h" // #include "violethold/VioletHoldTriggerContext.h"
// #include "gundrak/GundrakTriggerContext.h" // #include "gundrak/GundrakTriggerContext.h"
// #include "hallsofstone/HallsOfStoneTriggerContext.h" // #include "hallsofstone/HallsOfStoneTriggerContext.h"

View File

@@ -10,7 +10,7 @@ class WotlkDungeonANStrategy : public Strategy
{ {
public: public:
WotlkDungeonANStrategy(PlayerbotAI* ai) : Strategy(ai) {} WotlkDungeonANStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "azjol-nerub"; } virtual std::string const getName() override { return "azjol'nerub"; }
virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override; virtual void InitTriggers(std::vector<TriggerNode*> &triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override; virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
}; };

View 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

View File

@@ -0,0 +1,172 @@
#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 = 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 MoveAway(unit, distance - bot->GetExactDist2d(unit));
}
}
}
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.
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;
}

View 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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -0,0 +1,41 @@
#include "DrakTharonKeepStrategy.h"
#include "DrakTharonKeepMultipliers.h"
void WotlkDungeonDTKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Trollgore
triggers.push_back(new TriggerNode("corpse explode",
NextAction::array(0, new NextAction("corpse explode spread", ACTION_MOVE + 5), nullptr)));
// 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::array(0, new NextAction("avoid arcane field", ACTION_MOVE + 5), nullptr)));
triggers.push_back(new TriggerNode("arcane field",
NextAction::array(0, new NextAction("novos positioning", ACTION_MOVE + 4), nullptr)));
triggers.push_back(new TriggerNode("arcane field",
NextAction::array(0, new NextAction("novos target priority", ACTION_NORMAL + 1), nullptr)));
// 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::array(0, new NextAction("touch of life", ACTION_NORMAL + 5), nullptr)));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("bone armor", ACTION_NORMAL + 4), nullptr)));
// 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::array(0, new NextAction("reach melee", ACTION_NORMAL + 3), nullptr)));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("taunt", ACTION_NORMAL + 2), nullptr)));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("slaying strike", ACTION_NORMAL + 2), nullptr)));
}
void WotlkDungeonDTKStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new NovosMultiplier(botAI));
multipliers.push_back(new TharonjaMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#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

View 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

View File

@@ -0,0 +1,61 @@
#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);
}

View File

@@ -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

View File

@@ -182,11 +182,11 @@ bool RearFlankPositionAction::Execute(Event event)
// Need to reduce this value very slightly, or the bots get the jitters - // Need to reduce this value very slightly, or the bots get the jitters -
// may be due to rounding errors. Need to bring them just inside their attack range. // may be due to rounding errors. Need to bring them just inside their attack range.
// This boss has a big hitbox so we can reduce by 50% and it's still fine and looks better. // This boss has a big hitbox so we can reduce by 50% and it's still fine and looks better.
// TODO: Investigate using bot->GetObjectSize() for sizing
float distance = bot->GetMeleeRange(boss) * 0.5f; float distance = bot->GetMeleeRange(boss) * 0.5f;
// Alternatively, summing both unit's melee ranges seems to give a fairly natural range. // Alternatively, summing both unit's melee ranges seems to give a fairly natural range.
// Use whichever gives the best results.. // Use whichever gives the best results..
// float distanceOffset = bot->GetMeleeReach() + boss->GetMeleeReach(); // float distanceOffset = bot->GetMeleeReach() + boss->GetMeleeReach();
Position leftFlank = boss->GetPosition(); Position leftFlank = boss->GetPosition();
Position rightFlank = boss->GetPosition(); Position rightFlank = boss->GetPosition();
Position* destination = nullptr; Position* destination = nullptr;

View File

@@ -17,6 +17,7 @@ float PrinceKelesethMultiplier::GetValue(Action* action)
} }
return 1.0f; return 1.0f;
} }
float SkarvaldAndDalronnMultiplier::GetValue(Action* action) float SkarvaldAndDalronnMultiplier::GetValue(Action* action)
{ {
// Unit* skarvald = AI_VALUE2(Unit*, "find target", "skarvald the constructor"); // Unit* skarvald = AI_VALUE2(Unit*, "find target", "skarvald the constructor");