[HOT FIX] MS build issues regarding folder / command lenght usage or rc.exe (#2038)

This commit is contained in:
bashermens
2026-01-19 22:45:28 +01:00
committed by GitHub
parent fd07e02a8a
commit 41c53365ae
1119 changed files with 27 additions and 27 deletions

View File

@@ -0,0 +1,48 @@
#include "RaidAq20Actions.h"
#include "Playerbots.h"
#include "RaidAq20Utils.h"
bool Aq20UseCrystalAction::Execute(Event event)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "ossirian the unscarred"))
{
if (GameObject* crystal = RaidAq20Utils::GetNearestCrystal(boss))
{
float botDist = bot->GetDistance(crystal);
if (botDist > INTERACTION_DISTANCE)
return MoveTo(bot->GetMapId(),
crystal->GetPositionX() + frand(-3.5f, 3.5f),
crystal->GetPositionY() + frand(-3.5f, 3.5f),
crystal->GetPositionZ());
// if we're already in range just wait here until it's time to activate crystal
SetNextMovementDelay(500);
// don't activate crystal if boss too far or its already been activated
if (boss->GetDistance(crystal) > 25.0f ||
crystal->HasGameObjectFlag(GO_FLAG_IN_USE))
return false;
// don't activate crystal if boss doesn't have buff yet AND isn't going to have it soon
// (though ideally bot should activate it ~5 seconds early due to time it takes for
// crystal to activate and remove buff)
if (!RaidAq20Utils::IsOssirianBuffActive(boss) &&
RaidAq20Utils::GetOssirianDebuffTimeRemaining(boss) > 5000)
return false;
// this makes crystal do its animation (then disappear after)
WorldPacket data1(CMSG_GAMEOBJ_USE);
data1 << crystal->GetGUID();
bot->GetSession()->HandleGameObjectUseOpcode(data1);
// this makes crystal actually remove the buff and put on debuff (took a while to figure that out)
WorldPacket data2(CMSG_GAMEOBJ_USE);
data2 << crystal->GetGUID();
bot->GetSession()->HandleGameobjectReportUse(data2);
return true;
}
}
return false;
}

View File

@@ -0,0 +1,14 @@
#ifndef _PLAYERBOT_RAIDAQ20ACTIONS_H
#define _PLAYERBOT_RAIDAQ20ACTIONS_H
#include "MovementActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
class Aq20UseCrystalAction : public MovementAction
{
public:
Aq20UseCrystalAction(PlayerbotAI* botAI, std::string const name = "aq20 use crystal") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,20 @@
#ifndef _PLAYERBOT_RAIDAQ20ACTIONCONTEXT_H
#define _PLAYERBOT_RAIDAQ20ACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidAq20Actions.h"
class RaidAq20ActionContext : public NamedObjectContext<Action>
{
public:
RaidAq20ActionContext()
{
creators["aq20 use crystal"] = &RaidAq20ActionContext::use_crystal;
}
private:
static Action* use_crystal(PlayerbotAI* ai) { return new Aq20UseCrystalAction(ai); }
};
#endif

View File

@@ -0,0 +1,20 @@
#ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidAq20Triggers.h"
class RaidAq20TriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidAq20TriggerContext()
{
creators["aq20 move to crystal"] = &RaidAq20TriggerContext::move_to_crystal;
}
private:
static Trigger* move_to_crystal(PlayerbotAI* ai) { return new Aq20MoveToCrystalTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,11 @@
#include "RaidAq20Strategy.h"
#include "Strategy.h"
void RaidAq20Strategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("aq20 move to crystal",
{ NextAction("aq20 use crystal", ACTION_RAID) }));
}

View File

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

View File

@@ -0,0 +1,36 @@
#include "RaidAq20Triggers.h"
#include "SharedDefines.h"
#include "RaidAq20Utils.h"
bool Aq20MoveToCrystalTrigger::IsActive()
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "ossirian the unscarred"))
{
if (boss->IsInCombat())
{
// if buff is active move to crystal
if (RaidAq20Utils::IsOssirianBuffActive(boss))
return true;
// if buff is not active a debuff will be, buff becomes active once debuff expires
// so move to crystal when debuff almost done, or based debuff time left and
// distance bot is from crystal (ie: start moving early enough to make it)
int32 debuffTimeRemaining = RaidAq20Utils::GetOssirianDebuffTimeRemaining(boss);
if (debuffTimeRemaining < 5000)
return true;
if (debuffTimeRemaining < 30000)
{
if (GameObject* crystal = RaidAq20Utils::GetNearestCrystal(boss))
{
float botDist = bot->GetDistance(crystal);
float timeToReach = botDist / bot->GetSpeed(MOVE_RUN);
// bot should ideally activate crystal a ~5 seconds early (due to time it takes for crystal
// to activate) so aim to get there in time to do so
return debuffTimeRemaining - 5000 < timeToReach * 1000.0f;
}
}
}
}
return false;
}

View File

@@ -0,0 +1,14 @@
#ifndef _PLAYERBOT_RAIDAQ20TRIGGERS_H
#define _PLAYERBOT_RAIDAQ20TRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
class Aq20MoveToCrystalTrigger : public Trigger
{
public:
Aq20MoveToCrystalTrigger(PlayerbotAI* botAI) : Trigger(botAI, "aq20 move to crystal") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,38 @@
#include "RaidAq20Utils.h"
#include "SpellAuras.h"
uint32 const OSSIRIAN_BUFF = 25176;
uint32 const OSSIRIAN_DEBUFFS[] = {25177, 25178, 25180, 25181, 25183};
uint32 const OSSIRIAN_CRYSTAL_GO_ENTRY = 180619;
bool RaidAq20Utils::IsOssirianBuffActive(Unit* ossirian)
{
return ossirian && ossirian->HasAura(OSSIRIAN_BUFF);
}
int32 RaidAq20Utils::GetOssirianDebuffTimeRemaining(Unit* ossirian)
{
int32 retVal = 0xffffff;
if (ossirian)
{
for (uint32 debuff : OSSIRIAN_DEBUFFS)
{
if (AuraApplication* auraApplication = ossirian->GetAuraApplication(debuff))
{
if (Aura* aura = auraApplication->GetBase())
{
int32 duration = aura->GetDuration();
if (retVal > duration)
retVal = duration;
}
}
}
}
return retVal;
}
GameObject* RaidAq20Utils::GetNearestCrystal(Unit* ossirian)
{
return ossirian ? ossirian->FindNearestGameObject(OSSIRIAN_CRYSTAL_GO_ENTRY, 200.0f) : nullptr;
}

View File

@@ -0,0 +1,15 @@
#ifndef _PLAYERBOT_RAIDAQ20UTILS_H
#define _PLAYERBOT_RAIDAQ20UTILS_H
#include "GameObject.h"
#include "Unit.h"
class RaidAq20Utils
{
public:
static bool IsOssirianBuffActive(Unit* ossirian);
static int32 GetOssirianDebuffTimeRemaining(Unit* ossirian);
static GameObject* GetNearestCrystal(Unit* ossirian);
};
#endif

View File

@@ -0,0 +1,32 @@
#include "RaidBwlActions.h"
#include "Playerbots.h"
bool BwlOnyxiaScaleCloakAuraCheckAction::Execute(Event event)
{
bot->AddAura(22683, bot);
return true;
}
bool BwlOnyxiaScaleCloakAuraCheckAction::isUseful() { return !bot->HasAura(22683); }
bool BwlTurnOffSuppressionDeviceAction::Execute(Event event)
{
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects");
for (GuidVector::iterator i = gos.begin(); i != gos.end(); i++)
{
GameObject* go = botAI->GetGameObject(*i);
if (!go)
{
continue;
}
if (go->GetEntry() != 179784 || go->GetDistance(bot) >= 15.0f || go->GetGoState() != GO_STATE_READY)
{
continue;
}
go->SetGoState(GO_STATE_ACTIVE);
}
return true;
}
bool BwlUseHourglassSandAction::Execute(Event event) { return botAI->CastSpell(23645, bot); }

View File

@@ -0,0 +1,33 @@
#ifndef _PLAYERBOT_RAIDBWLACTIONS_H
#define _PLAYERBOT_RAIDBWLACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "GenericActions.h"
#include "MovementActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
class BwlOnyxiaScaleCloakAuraCheckAction : public Action
{
public:
BwlOnyxiaScaleCloakAuraCheckAction(PlayerbotAI* botAI) : Action(botAI, "bwl onyxia scale cloak aura check") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class BwlTurnOffSuppressionDeviceAction : public Action
{
public:
BwlTurnOffSuppressionDeviceAction(PlayerbotAI* botAI) : Action(botAI, "bwl turn off suppression device") {}
bool Execute(Event event) override;
};
class BwlUseHourglassSandAction : public Action
{
public:
BwlUseHourglassSandAction(PlayerbotAI* botAI) : Action(botAI, "bwl use hourglass sand") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,24 @@
#ifndef _PLAYERBOT_RAIDBWLACTIONCONTEXT_H
#define _PLAYERBOT_RAIDBWLACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidBwlActions.h"
class RaidBwlActionContext : public NamedObjectContext<Action>
{
public:
RaidBwlActionContext()
{
creators["bwl check onyxia scale cloak"] = &RaidBwlActionContext::bwl_check_onyxia_scale_cloak;
creators["bwl turn off suppression device"] = &RaidBwlActionContext::bwl_turn_off_suppression_device;
creators["bwl use hourglass sand"] = &RaidBwlActionContext::bwl_use_hourglass_sand;
}
private:
static Action* bwl_check_onyxia_scale_cloak(PlayerbotAI* botAI) { return new BwlOnyxiaScaleCloakAuraCheckAction(botAI); }
static Action* bwl_turn_off_suppression_device(PlayerbotAI* botAI) { return new BwlTurnOffSuppressionDeviceAction(botAI); }
static Action* bwl_use_hourglass_sand(PlayerbotAI* botAI) { return new BwlUseHourglassSandAction(botAI); }
};
#endif

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidBwlTriggers.h"
class RaidBwlTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidBwlTriggerContext()
{
creators["bwl suppression device"] = &RaidBwlTriggerContext::bwl_suppression_device;
creators["bwl affliction bronze"] = &RaidBwlTriggerContext::bwl_affliction_bronze;
}
private:
static Trigger* bwl_suppression_device(PlayerbotAI* ai) { return new BwlSuppressionDeviceTrigger(ai); }
static Trigger* bwl_affliction_bronze(PlayerbotAI* ai) { return new BwlAfflictionBronzeTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,15 @@
#include "RaidBwlStrategy.h"
#include "Strategy.h"
void RaidBwlStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("often",
{ NextAction("bwl check onyxia scale cloak", ACTION_RAID) }));
triggers.push_back(new TriggerNode("bwl suppression device",
{ NextAction("bwl turn off suppression device", ACTION_RAID) }));
triggers.push_back(new TriggerNode("bwl affliction bronze",
{ NextAction("bwl use hourglass sand", ACTION_RAID) }));
}

View File

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

View File

@@ -0,0 +1,24 @@
#include "RaidBwlTriggers.h"
#include "SharedDefines.h"
bool BwlSuppressionDeviceTrigger::IsActive()
{
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects");
for (GuidVector::iterator i = gos.begin(); i != gos.end(); i++)
{
GameObject* go = botAI->GetGameObject(*i);
if (!go)
{
continue;
}
if (go->GetEntry() != 179784 || go->GetDistance(bot) >= 15.0f || go->GetGoState() != GO_STATE_READY)
{
continue;
}
return true;
}
return false;
}
bool BwlAfflictionBronzeTrigger::IsActive() { return bot->HasAura(23170); }

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_RAIDBWLTRIGGERS_H
#define _PLAYERBOT_RAIDBWLTRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
class BwlSuppressionDeviceTrigger : public Trigger
{
public:
BwlSuppressionDeviceTrigger(PlayerbotAI* botAI) : Trigger(botAI, "bwl suppression device") {}
bool IsActive() override;
};
class BwlAfflictionBronzeTrigger : public Trigger
{
public:
BwlAfflictionBronzeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "bwl affliction bronze") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,404 @@
#include "Playerbots.h"
#include "RaidEoEActions.h"
#include "RaidEoETriggers.h"
bool MalygosPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
if (!boss) { return false; }
uint8 phase = MalygosTrigger::getPhase(bot, boss);
float distance = 5.0f;
if (phase == 1)
{
Unit* spark = nullptr;
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto& target : targets)
{
Unit* unit = botAI->GetUnit(target);
if (unit && unit->GetEntry() == NPC_POWER_SPARK)
{
spark = unit;
break;
}
}
// Position tank
if (botAI->IsMainTank(bot))
{
if (bot->GetDistance2d(MALYGOS_MAINTANK_POSITION.first, MALYGOS_MAINTANK_POSITION.second) > distance)
{
return MoveTo(EOE_MAP_ID, MALYGOS_MAINTANK_POSITION.first, MALYGOS_MAINTANK_POSITION.second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
// Position DK for spark pull
// else if (spark && bot->IsClass(CLASS_DEATH_KNIGHT))
// {
// if (bot->GetDistance2d(MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second) > distance)
// {
// bot->Yell("SPARK SPAWNED, MOVING TO STACK", LANG_UNIVERSAL);
// return MoveTo(EOE_MAP_ID, MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second, bot->GetPositionZ(),
// false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
// }
// return false;
// }
else if (spark)
{
return false;
}
else if (!bot->IsClass(CLASS_HUNTER))
{
if (bot->GetDistance2d(MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second) > (distance * 3.0f))
{
return MoveTo(EOE_MAP_ID, MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
}
return false;
}
bool MalygosTargetAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
if (!boss) { return false; }
uint8 phase = MalygosTrigger::getPhase(bot, boss);
if (phase == 1)
{
if (botAI->IsHeal(bot)) { return false; }
// Init this as boss by default, if no better target is found just fall back to Malygos
Unit* newTarget = boss;
// Unit* spark = nullptr;
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto& target : targets)
// {
// Unit* unit = botAI->GetUnit(target);
// if (unit && unit->GetEntry() == NPC_POWER_SPARK)
// {
// spark = unit;
// break;
// }
// }
// if (spark && botAI->IsRangedDps(bot))
// {
// newTarget = spark;
// }
Unit* currentTarget = AI_VALUE(Unit*, "current target");
if (!currentTarget || currentTarget->GetEntry() != newTarget->GetEntry())
{
return Attack(newTarget);
}
}
else if (phase == 2)
{
if (botAI->IsHeal(bot)) { return false; }
Unit* newTarget = nullptr;
Unit* nexusLord = nullptr;
Unit* scionOfEternity = nullptr;
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto& target : targets)
{
Unit* unit = botAI->GetUnit(target);
if (!unit) { continue; }
if (unit->GetEntry() == NPC_NEXUS_LORD)
{
nexusLord = unit;
}
else if (unit->GetEntry() == NPC_SCION_OF_ETERNITY)
{
scionOfEternity = unit;
}
}
if (botAI->IsRangedDps(bot) && scionOfEternity)
{
newTarget = scionOfEternity;
}
else
{
newTarget = nexusLord;
}
if (!newTarget) { return false; }
Unit* currentTarget = AI_VALUE(Unit*, "current target");
if (!currentTarget || currentTarget->GetEntry() != newTarget->GetEntry())
{
return Attack(newTarget);
}
}
// else if (phase == 3)
// {}
return false;
}
// bool PullPowerSparkAction::Execute(Event event)
// {
// Unit* spark = nullptr;
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto& target : targets)
// {
// Unit* unit = botAI->GetUnit(target);
// if (unit && unit->GetEntry() == NPC_POWER_SPARK)
// {
// spark = unit;
// break;
// }
// }
// if (!spark) { return false; }
// if (spark->GetDistance2d(MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second) > 3.0f)
// {
// bot->Yell("GRIPPING SPARK", LANG_UNIVERSAL);
// return botAI->CastSpell("death grip", spark);
// }
// return false;
// }
// bool PullPowerSparkAction::isPossible()
// {
// Unit* spark = nullptr;
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto& target : targets)
// {
// Unit* unit = botAI->GetUnit(target);
// if (unit && unit->GetEntry() == NPC_POWER_SPARK)
// {
// spark = unit;
// break;
// }
// }
// return botAI->CanCastSpell(spell, spark);
// }
// bool PullPowerSparkAction::isUseful()
// {
// Unit* spark = nullptr;
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto& target : targets)
// {
// Unit* unit = botAI->GetUnit(target);
// if (unit && unit->GetEntry() == NPC_POWER_SPARK)
// {
// spark = unit;
// break;
// }
// }
// if (!spark)
// return false;
// if (!spark->IsInWorld() || spark->GetMapId() != bot->GetMapId())
// return false;
// return bot->GetDistance2d(MALYGOS_STACK_POSITION.first, MALYGOS_STACK_POSITION.second) < 3.0f;
// }
// bool KillPowerSparkAction::Execute(Event event)
// {
// return false;
// }
bool EoEFlyDrakeAction::isPossible()
{
Unit* vehicleBase = bot->GetVehicleBase();
return (vehicleBase && vehicleBase->GetEntry() == NPC_WYRMREST_SKYTALON);
}
bool EoEFlyDrakeAction::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", "malygos");
if (boss && false)
{
// 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) > 5.0f)
{
uint8 numPlayers;
bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL ? numPlayers = 25 : numPlayers = 10;
// 3/4 of a circle, with frontal cone 90 deg unobstructed
float angle = botAI->GetGroupSlotIndex(bot) * (2*M_PI - M_PI_2)/numPlayers + M_PI_2;
// float angle = M_PI;
vehicleBase->SetCanFly(true);
mm->MoveFollow(masterVehicle, 3.0f, angle);
vehicleBase->SendMovementFlagUpdate();
return true;
}
return false;
}
bool EoEDrakeAttackAction::isPossible()
{
Unit* vehicleBase = bot->GetVehicleBase();
return (vehicleBase && vehicleBase->GetEntry() == NPC_WYRMREST_SKYTALON);
}
bool EoEDrakeAttackAction::Execute(Event event)
{
vehicleBase = bot->GetVehicleBase();
if (!vehicleBase)
{
return false;
}
// Unit* target = AI_VALUE(Unit*, "current target");
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
// if (!boss) { return false; }
if (!boss)
{
GuidVector npcs = AI_VALUE(GuidVector, "possible targets");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (!unit || unit->GetEntry() != NPC_MALYGOS)
{
continue;
}
boss = unit;
break;
}
}
// Check this again to see if a target was assigned
if (!boss)
{
return false;
}
uint8 numHealers;
bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL ? numHealers = 10 : numHealers = 4;
Group* group = bot->GetGroup();
if (!group)
return false;
std::vector<std::pair<ObjectGuid, Player*>> sortedMembers;
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
sortedMembers.push_back(std::make_pair(member->GetGUID(), member));
}
std::sort(sortedMembers.begin(), sortedMembers.end());
int botIndex = -1;
for (size_t i = 0; i < sortedMembers.size(); ++i)
{
if (sortedMembers[i].first == bot->GetGUID())
{
botIndex = i;
break;
}
}
if (botIndex == -1)
return false;
if (botIndex > numHealers)
{
return DrakeDpsAction(boss);
}
else
{
return DrakeHealAction();
}
return false;
}
bool EoEDrakeAttackAction::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 EoEDrakeAttackAction::DrakeDpsAction(Unit* target)
{
Unit* vehicleBase = bot->GetVehicleBase();
if (!vehicleBase) { return false; }
Vehicle* veh = bot->GetVehicle();
uint8 comboPoints = vehicleBase->GetComboPoints(target);
if (comboPoints >= 2)
{
return CastDrakeSpellAction(target, SPELL_ENGULF_IN_FLAMES, 0);
}
else
{
return CastDrakeSpellAction(target, SPELL_FLAME_SPIKE, 0);
}
}
bool EoEDrakeAttackAction::DrakeHealAction()
{
Unit* vehicleBase = bot->GetVehicleBase();
if (!vehicleBase)
{
return false;
}
uint8 comboPoints = vehicleBase->GetComboPoints(vehicleBase);
if (comboPoints >= 5)
{
return CastDrakeSpellAction(vehicleBase, SPELL_LIFE_BURST, 0);
}
else
{
// "Revivify" may be bugged server-side:
// "botAI->CanCastVehicleSpell()" returns SPELL_FAILED_BAD_TARGETS when targeting drakes.
// Forcing the cast attempt seems to succeed, not sure what's going on here.
// return CastDrakeSpellAction(target, SPELL_REVIVIFY, 0);
return botAI->CastVehicleSpell(SPELL_REVIVIFY, vehicleBase);
}
}

View File

@@ -0,0 +1,69 @@
#ifndef _PLAYERBOT_RAIDEOEACTIONS_H
#define _PLAYERBOT_RAIDEOEACTIONS_H
#include "MovementActions.h"
#include "AttackAction.h"
#include "GenericSpellActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
const std::pair<float, float> MALYGOS_MAINTANK_POSITION = {757.0f, 1337.0f};
const std::pair<float, float> MALYGOS_STACK_POSITION = {755.0f, 1301.0f};
class MalygosPositionAction : public MovementAction
{
public:
MalygosPositionAction(PlayerbotAI* botAI, std::string const name = "malygos position")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class MalygosTargetAction : public AttackAction
{
public:
MalygosTargetAction(PlayerbotAI* botAI, std::string const name = "malygos target")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class PullPowerSparkAction : public CastSpellAction
{
public:
PullPowerSparkAction(PlayerbotAI* botAI, std::string const name = "pull power spark")
: CastSpellAction(botAI, name) {}
bool Execute(Event event) override;
bool isPossible() override;
bool isUseful() override;
};
class KillPowerSparkAction : public AttackAction
{
public:
KillPowerSparkAction(PlayerbotAI* botAI, std::string const name = "kill power spark")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class EoEFlyDrakeAction : public MovementAction
{
public:
EoEFlyDrakeAction(PlayerbotAI* ai) : MovementAction(ai, "eoe fly drake") {}
bool Execute(Event event) override;
bool isPossible() override;
};
class EoEDrakeAttackAction : public Action
{
public:
EoEDrakeAttackAction(PlayerbotAI* botAI) : Action(botAI, "eoe drake attack") {}
bool Execute(Event event) override;
bool isPossible() override;
protected:
Unit* vehicleBase;
bool CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown);
bool DrakeDpsAction(Unit* target);
bool DrakeHealAction();
};
#endif

View File

@@ -0,0 +1,81 @@
#include "RaidEoEMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "RaidEoEActions.h"
#include "RaidEoETriggers.h"
#include "ReachTargetActions.h"
#include "ScriptedCreature.h"
#include "WarriorActions.h"
float MalygosMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
uint8 phase = MalygosTrigger::getPhase(bot, boss);
if (phase == 0) { return 1.0f; }
if (phase == 1)
{
if (dynamic_cast<FollowAction*>(action))
{
return 0.0f;
}
if (botAI->IsDps(bot) && dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
if (botAI->IsRangedDps(bot) && dynamic_cast<DropTargetAction*>(action))
{
return 0.0f;
}
if (!botAI->IsMainTank(bot) && dynamic_cast<TankAssistAction*>(action))
{
return 0.0f;
}
// if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<MalygosPositionAction*>(action))
// {
// return 0.0f;
// }
}
else if (phase == 2)
{
if (botAI->IsDps(bot) && dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
if (dynamic_cast<FleeAction*>(action))
{
return 0.0f;
}
if (dynamic_cast<TankAssistAction*>(action))
{
Unit* target = action->GetTarget();
if (target && target->GetEntry() == NPC_SCION_OF_ETERNITY)
return 0.0f;
}
}
else if (phase == 3)
{
// Suppresses FollowAction as well as some attack-based movements
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<EoEFlyDrakeAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}

View File

@@ -0,0 +1,16 @@
#ifndef _PLAYERRBOT_RAIDEOEMULTIPLIERS_H
#define _PLAYERRBOT_RAIDEOEMULTIPLIERS_H
#include "Multiplier.h"
class MalygosMultiplier : public Multiplier
{
public:
MalygosMultiplier(PlayerbotAI* ai) : Multiplier(ai, "malygos") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,30 @@
#ifndef _PLAYERBOT_RAIDEOEACTIONCONTEXT_H
#define _PLAYERBOT_RAIDEOEACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidEoEActions.h"
class RaidEoEActionContext : public NamedObjectContext<Action>
{
public:
RaidEoEActionContext()
{
creators["malygos position"] = &RaidEoEActionContext::position;
creators["malygos target"] = &RaidEoEActionContext::target;
// creators["pull power spark"] = &RaidEoEActionContext::pull_power_spark;
// creators["kill power spark"] = &RaidEoEActionContext::kill_power_spark;
creators["eoe fly drake"] = &RaidEoEActionContext::eoe_fly_drake;
creators["eoe drake attack"] = &RaidEoEActionContext::eoe_drake_attack;
}
private:
static Action* position(PlayerbotAI* ai) { return new MalygosPositionAction(ai); }
static Action* target(PlayerbotAI* ai) { return new MalygosTargetAction(ai); }
// static Action* pull_power_spark(PlayerbotAI* ai) { return new PullPowerSparkAction(ai); }
// static Action* kill_power_spark(PlayerbotAI* ai) { return new KillPowerSparkAction(ai); }
static Action* eoe_fly_drake(PlayerbotAI* ai) { return new EoEFlyDrakeAction(ai); }
static Action* eoe_drake_attack(PlayerbotAI* ai) { return new EoEDrakeAttackAction(ai); }
};
#endif

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidEoETriggers.h"
class RaidEoETriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidEoETriggerContext()
{
creators["malygos"] = &RaidEoETriggerContext::malygos;
creators["power spark"] = &RaidEoETriggerContext::power_spark;
}
private:
static Trigger* power_spark(PlayerbotAI* ai) { return new PowerSparkTrigger(ai); }
static Trigger* malygos(PlayerbotAI* ai) { return new MalygosTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,21 @@
#include "RaidEoEStrategy.h"
#include "RaidEoEMultipliers.h"
#include "Strategy.h"
void RaidEoEStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("malygos",
{ NextAction("malygos position", ACTION_MOVE) }));
triggers.push_back(new TriggerNode("malygos",
{ NextAction("malygos target", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("group flying",
{ NextAction("eoe fly drake", ACTION_NORMAL + 1) }));
triggers.push_back(new TriggerNode("drake combat",
{ NextAction("eoe drake attack", ACTION_NORMAL + 5) }));
}
void RaidEoEStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new MalygosMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,53 @@
#include "RaidEoETriggers.h"
#include "SharedDefines.h"
uint8 MalygosTrigger::getPhase(Player* bot, Unit* boss)
{
uint8 phase = 0;
Unit* vehicle = bot->GetVehicleBase();
if (bot->GetMapId() != EOE_MAP_ID) { return phase; }
if (vehicle && vehicle->GetEntry() == NPC_WYRMREST_SKYTALON)
{
phase = 3;
}
else if (boss && boss->HealthAbovePct(50))
{
phase = 1;
}
else if (boss)
{
phase = 2;
}
return phase;
}
bool MalygosTrigger::IsActive()
{
return bool(AI_VALUE2(Unit*, "find target", "malygos"));
}
bool PowerSparkTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
if (!boss) { return false; }
if (bot->getClass() != CLASS_DEATH_KNIGHT)
{
return false;
}
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto& target : targets)
{
Unit* unit = botAI->GetUnit(target);
if (unit && unit->GetEntry() == NPC_POWER_SPARK)
{
return true;
}
}
return false;
}

View File

@@ -0,0 +1,58 @@
#ifndef _PLAYERBOT_RAIDEOETRIGGERS_H
#define _PLAYERBOT_RAIDEOETRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
enum EyeOfEternityIDs
{
NPC_MALYGOS = 28859,
NPC_POWER_SPARK = 30084,
NPC_NEXUS_LORD = 30245,
NPC_SCION_OF_ETERNITY = 30249,
NPC_WYRMREST_SKYTALON = 30161,
SPELL_POWER_SPARK_VISUAL = 55845,
SPELL_POWER_SPARK_GROUND_BUFF = 55852,
SPELL_POWER_SPARK_MALYGOS_BUFF = 56152,
SPELL_TELEPORT_VISUAL = 52096,
SPELL_SCION_ARCANE_BARRAGE = 56397,
SPELL_ARCANE_SHOCK_N = 57058,
SPELL_ARCANE_SHOCK_H = 60073,
SPELL_HASTE = 57060,
SPELL_ALEXSTRASZA_GIFT = 61028,
// Drake Abilities:
// DPS
SPELL_FLAME_SPIKE = 56091,
SPELL_ENGULF_IN_FLAMES = 56092,
// Healing
SPELL_REVIVIFY = 57090,
SPELL_LIFE_BURST = 57143,
// Utility
SPELL_FLAME_SHIELD = 57108,
SPELL_BLAZING_SPEED = 57092,
};
const uint32 EOE_MAP_ID = 616;
class MalygosTrigger : public Trigger
{
public:
MalygosTrigger(PlayerbotAI* botAI) : Trigger(botAI, "malygos") {}
bool IsActive() override;
uint8 static getPhase(Player* bot, Unit* boss);
};
class PowerSparkTrigger : public Trigger
{
public:
PowerSparkTrigger(PlayerbotAI* botAI) : Trigger(botAI, "power spark") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,696 @@
#include "RaidGruulsLairActions.h"
#include "RaidGruulsLairHelpers.h"
#include "CreatureAI.h"
#include "Playerbots.h"
#include "Unit.h"
using namespace GruulsLairHelpers;
// High King Maulgar Actions
// Main tank on Maulgar
bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
MarkTargetWithSquare(bot, maulgar);
SetRtiTarget(botAI, "square", maulgar);
if (bot->GetVictim() != maulgar)
return Attack(maulgar);
if (maulgar->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::MaulgarTankPosition;
const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
if (distanceToTankPosition > maxDistance)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(maulgar->GetPositionY() - bot->GetPositionY(),
maulgar->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(maulgar))
{
return MoveTo(maulgar->GetMapId(), maulgar->GetPositionX(), maulgar->GetPositionY(),
maulgar->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
// First offtank on Olm
bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
{
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
MarkTargetWithCircle(bot, olm);
SetRtiTarget(botAI, "circle", olm);
if (bot->GetVictim() != olm)
return Attack(olm);
if (olm->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::OlmTankPosition;
const float maxDistance = 3.0f;
const float olmTankLeeway = 30.0f;
float distanceOlmToTankPosition = olm->GetExactDist2d(tankPosition.x, tankPosition.y);
if (distanceOlmToTankPosition > olmTankLeeway)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
else if (!bot->IsWithinMeleeRange(olm))
{
return MoveTo(olm->GetMapId(), olm->GetPositionX(), olm->GetPositionY(),
olm->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
// Second offtank on Blindeye
bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
{
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
MarkTargetWithStar(bot, blindeye);
SetRtiTarget(botAI, "star", blindeye);
if (bot->GetVictim() != blindeye)
return Attack(blindeye);
if (blindeye->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::BlindeyeTankPosition;
const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
if (distanceToTankPosition > maxDistance)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(blindeye->GetPositionY() - bot->GetPositionY(),
blindeye->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(blindeye))
{
return MoveTo(blindeye->GetMapId(), blindeye->GetPositionX(), blindeye->GetPositionY(),
blindeye->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
// Mage with highest max HP on Krosh
bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
MarkTargetWithTriangle(bot, krosh);
SetRtiTarget(botAI, "triangle", krosh);
if (krosh->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("spellsteal", krosh))
return botAI->CastSpell("spellsteal", krosh);
if (!bot->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("fire ward", bot))
return botAI->CastSpell("fire ward", bot);
if (bot->GetTarget() != krosh->GetGUID())
{
bot->SetSelection(krosh->GetGUID());
return true;
}
if (krosh->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::KroshTankPosition;
float distanceToKrosh = krosh->GetExactDist2d(tankPosition.x, tankPosition.y);
const float minDistance = 16.0f;
const float maxDistance = 29.0f;
const float tankPositionLeeway = 1.0f;
if (distanceToKrosh > minDistance && distanceToKrosh < maxDistance)
{
if (!bot->IsWithinDist2d(tankPosition.x, tankPosition.y, tankPositionLeeway))
{
return MoveTo(bot->GetMapId(), tankPosition.x, tankPosition.y, tankPosition.z, false,
false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(krosh->GetPositionY() - bot->GetPositionY(),
krosh->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
}
return false;
}
// Moonkin with highest max HP on Kiggler
bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event event)
{
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
MarkTargetWithDiamond(bot, kiggler);
SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID())
{
bot->SetSelection(kiggler->GetGUID());
return true;
}
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event)
{
// Target priority 1: Blindeye
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (blindeye && blindeye->IsAlive())
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(blindeye->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "star", blindeye);
if (bot->GetTarget() != blindeye->GetGUID())
{
bot->SetSelection(blindeye->GetGUID());
return Attack(blindeye);
}
return false;
}
// Target priority 2: Olm
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
if (olm && olm->IsAlive())
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(olm->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "circle", olm);
if (bot->GetTarget() != olm->GetGUID())
{
bot->SetSelection(olm->GetGUID());
return Attack(olm);
}
return false;
}
// Target priority 3a: Krosh (ranged only)
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (krosh && krosh->IsAlive() && botAI->IsRanged(bot))
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "triangle", krosh);
if (bot->GetTarget() != krosh->GetGUID())
{
bot->SetSelection(krosh->GetGUID());
return Attack(krosh);
}
return false;
}
// Target priority 3b: Kiggler
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
if (kiggler && kiggler->IsAlive())
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID())
{
bot->SetSelection(kiggler->GetGUID());
return Attack(kiggler);
}
return false;
}
// Target priority 4: Maulgar
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (maulgar && maulgar->IsAlive())
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(maulgar->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "square", maulgar);
if (bot->GetTarget() != maulgar->GetGUID())
{
bot->SetSelection(maulgar->GetGUID());
return Attack(maulgar);
}
}
return false;
}
// Avoid Whirlwind and Blast Wave and generally try to stay near the center of the room
bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
{
const Location& fightCenter = GruulsLairLocations::MaulgarRoomCenter;
const float maxDistanceFromFight = 50.0f;
float distToFight = bot->GetExactDist2d(fightCenter.x, fightCenter.y);
if (distToFight > maxDistanceFromFight)
{
float angle = atan2(bot->GetPositionY() - fightCenter.y, bot->GetPositionX() - fightCenter.x);
float destX = fightCenter.x + 40.0f * cos(angle);
float destY = fightCenter.y + 40.0f * sin(angle);
float destZ = fightCenter.z;
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ))
return false;
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
// Run away from Maulgar during Whirlwind (logic for after all other ogres are dead)
bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
const float safeDistance = 10.0f;
float distance = bot->GetExactDist2d(maulgar);
if (distance < safeDistance)
{
float angle = atan2(bot->GetPositionY() - maulgar->GetPositionY(),
bot->GetPositionX() - maulgar->GetPositionX());
float destX = maulgar->GetPositionX() + safeDistance * cos(angle);
float destY = maulgar->GetPositionY() + safeDistance * sin(angle);
float destZ = bot->GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ))
return false;
float destDist = maulgar->GetExactDist2d(destX, destY);
if (destDist >= safeDistance - 0.1f)
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(maulgar->GetMapId(), destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
return false;
}
bool HighKingMaulgarBanishFelstalkerAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
std::vector<Unit*> felStalkers;
for (auto const& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == NPC_WILD_FEL_STALKER && unit->IsAlive())
felStalkers.push_back(unit);
}
std::vector<Player*> warlocks;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_WARLOCK && GET_PLAYERBOT_AI(member))
warlocks.push_back(member);
}
int warlockIndex = -1;
for (size_t i = 0; i < warlocks.size(); ++i)
{
if (warlocks[i] == bot)
{
warlockIndex = static_cast<int>(i);
break;
}
}
if (warlockIndex >= 0 && warlockIndex < felStalkers.size())
{
Unit* assignedFelStalker = felStalkers[warlockIndex];
if (!assignedFelStalker->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedFelStalker, true))
return botAI->CastSpell("banish", assignedFelStalker);
}
return false;
}
// Hunter 1: Misdirect Olm to first offtank and have pet attack Blindeye
// Hunter 2: Misdirect Blindeye to second offtank
bool HighKingMaulgarMisdirectOlmAndBlindeyeAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
std::vector<Player*> hunters;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member))
hunters.push_back(member);
}
int hunterIndex = -1;
for (size_t i = 0; i < hunters.size(); ++i)
{
if (hunters[i] == bot)
{
hunterIndex = static_cast<int>(i);
break;
}
}
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
Player* olmTank = nullptr;
Player* blindeyeTank = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
else if (botAI->IsAssistTankOfIndex(member, 0)) olmTank = member;
else if (botAI->IsAssistTankOfIndex(member, 1)) blindeyeTank = member;
}
switch (hunterIndex)
{
case 0:
botAI->CastSpell("misdirection", olmTank);
if (bot->HasAura(SPELL_MISDIRECTION))
{
Pet* pet = bot->GetPet();
if (pet && pet->IsAlive() && pet->GetVictim() != blindeye)
{
pet->ClearUnitState(UNIT_STATE_FOLLOW);
pet->AttackStop();
pet->SetTarget(blindeye->GetGUID());
if (pet->GetCharmInfo())
{
pet->GetCharmInfo()->SetIsCommandAttack(true);
pet->GetCharmInfo()->SetIsAtStay(false);
pet->GetCharmInfo()->SetIsFollowing(false);
pet->GetCharmInfo()->SetIsCommandFollow(false);
pet->GetCharmInfo()->SetIsReturning(false);
}
pet->ToCreature()->AI()->AttackStart(blindeye);
}
return botAI->CastSpell("steady shot", olm);
}
break;
case 1:
botAI->CastSpell("misdirection", blindeyeTank);
if (bot->HasAura(SPELL_MISDIRECTION))
return botAI->CastSpell("steady shot", blindeye);
break;
default:
break;
}
return false;
}
// Gruul the Dragonkiller Actions
// Position in center of the room
bool GruulTheDragonkillerMainTankPositionBossAction::Execute(Event event)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (bot->GetVictim() != gruul)
return Attack(gruul);
if (gruul->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::GruulTankPosition;
const float maxDistance = 3.0f;
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
if (distanceToTankPosition > maxDistance)
{
float step = std::min(maxDistance, distanceToTankPosition);
float moveX = bot->GetPositionX() + (dX / distanceToTankPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToTankPosition) * maxDistance;
const float moveZ = tankPosition.z;
return MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(gruul->GetPositionY() - bot->GetPositionY(),
gruul->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(gruul))
{
return MoveTo(gruul->GetMapId(), gruul->GetPositionX(), gruul->GetPositionY(), gruul->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
// Ranged will take initial positions around the middle of the room, 25-40 yards from center
// Ranged should spread out 10 yards from each other
bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
static std::unordered_map<ObjectGuid, Position> initialPositions;
static std::unordered_map<ObjectGuid, bool> hasReachedInitialPosition;
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (gruul && gruul->IsAlive() && gruul->GetHealth() == gruul->GetMaxHealth())
{
initialPositions.clear();
hasReachedInitialPosition.clear();
}
const Location& tankPosition = GruulsLairLocations::GruulTankPosition;
const float centerX = tankPosition.x;
const float centerY = tankPosition.y;
float centerZ = bot->GetPositionZ();
const float minRadius = 25.0f;
const float maxRadius = 40.0f;
std::vector<Player*> members;
Player* closestMember = nullptr;
float closestDist = std::numeric_limits<float>::max();
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
members.push_back(member);
if (member != bot)
{
float dist = bot->GetExactDist2d(member);
if (dist < closestDist)
{
closestDist = dist;
closestMember = member;
}
}
}
if (!initialPositions.count(bot->GetGUID()))
{
auto it = std::find(members.begin(), members.end(), bot);
uint8 botIndex = (it != members.end()) ? std::distance(members.begin(), it) : 0;
uint8 count = members.size();
float angle = 2 * M_PI * botIndex / count;
float radius = minRadius + static_cast<float>(rand()) /
static_cast<float>(RAND_MAX) * (maxRadius - minRadius);
float targetX = centerX + radius * cos(angle);
float targetY = centerY + radius * sin(angle);
initialPositions[bot->GetGUID()] = Position(targetX, targetY, centerZ);
hasReachedInitialPosition[bot->GetGUID()] = false;
}
Position targetPosition = initialPositions[bot->GetGUID()];
if (!hasReachedInitialPosition[bot->GetGUID()])
{
if (!bot->IsWithinDist2d(targetPosition.GetPositionX(), targetPosition.GetPositionY(), 2.0f))
{
float destX = targetPosition.GetPositionX();
float destY = targetPosition.GetPositionY();
float destZ = targetPosition.GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(),
bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ))
return false;
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
hasReachedInitialPosition[bot->GetGUID()] = true;
}
const float minSpreadDistance = 10.0f;
const float movementThreshold = 2.0f;
if (closestMember && closestDist < minSpreadDistance - movementThreshold)
{
return FleePosition(Position(closestMember->GetPositionX(), closestMember->GetPositionY(),
closestMember->GetPositionZ()), minSpreadDistance, 0);
}
return false;
}
// Try to get away from other group members when Ground Slam is cast
bool GruulTheDragonkillerShatterSpreadAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
GuidVector members = AI_VALUE(GuidVector, "group members");
Unit* closestMember = nullptr;
float closestDist = std::numeric_limits<float>::max();
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || bot->GetGUID() == member)
continue;
const float dist = bot->GetExactDist2d(unit);
if (dist < closestDist)
{
closestDist = dist;
closestMember = unit;
}
}
if (closestMember)
{
return FleePosition(Position(closestMember->GetPositionX(), closestMember->GetPositionY(),
closestMember->GetPositionZ()), 6.0f, 0);
}
return false;
}

View File

@@ -0,0 +1,112 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRACTIONS_H
#define _PLAYERBOT_RAIDGRUULSLAIRACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
class HighKingMaulgarMainTankAttackMaulgarAction : public AttackAction
{
public:
HighKingMaulgarMainTankAttackMaulgarAction(PlayerbotAI* botAI, std::string const name = "high king maulgar main tank attack maulgar") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarFirstAssistTankAttackOlmAction : public AttackAction
{
public:
HighKingMaulgarFirstAssistTankAttackOlmAction(PlayerbotAI* botAI, std::string const name = "high king maulgar first assist tank attack olm") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarSecondAssistTankAttackBlindeyeAction : public AttackAction
{
public:
HighKingMaulgarSecondAssistTankAttackBlindeyeAction(PlayerbotAI* botAI, std::string const name = "high king maulgar second assist tank attack blindeye") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarMageTankAttackKroshAction : public AttackAction
{
public:
HighKingMaulgarMageTankAttackKroshAction(PlayerbotAI* botAI, std::string const name = "high king maulgar mage tank attack krosh") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarMoonkinTankAttackKigglerAction : public AttackAction
{
public:
HighKingMaulgarMoonkinTankAttackKigglerAction(PlayerbotAI* botAI, std::string const name = "high king maulgar moonkin tank attack kiggler") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarAssignDPSPriorityAction : public AttackAction
{
public:
HighKingMaulgarAssignDPSPriorityAction(PlayerbotAI* botAI, std::string const name = "high king maulgar assign dps priority") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarHealerFindSafePositionAction : public MovementAction
{
public:
HighKingMaulgarHealerFindSafePositionAction(PlayerbotAI* botAI, std::string const name = "high king maulgar healer find safe position") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarRunAwayFromWhirlwindAction : public MovementAction
{
public:
HighKingMaulgarRunAwayFromWhirlwindAction(PlayerbotAI* botAI, std::string const name = "high king maulgar run away from whirlwind") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarBanishFelstalkerAction : public AttackAction
{
public:
HighKingMaulgarBanishFelstalkerAction(PlayerbotAI* botAI, std::string const name = "high king maulgar banish felstalker") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarMisdirectOlmAndBlindeyeAction : public AttackAction
{
public:
HighKingMaulgarMisdirectOlmAndBlindeyeAction(PlayerbotAI* botAI, std::string const name = "high king maulgar misdirect olm and blindeye") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class GruulTheDragonkillerMainTankPositionBossAction : public AttackAction
{
public:
GruulTheDragonkillerMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller main tank position boss") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class GruulTheDragonkillerSpreadRangedAction : public MovementAction
{
public:
GruulTheDragonkillerSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller spread ranged") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
class GruulTheDragonkillerShatterSpreadAction : public MovementAction
{
public:
GruulTheDragonkillerShatterSpreadAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller shatter spread") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,110 @@
#include "RaidGruulsLairMultipliers.h"
#include "RaidGruulsLairActions.h"
#include "RaidGruulsLairHelpers.h"
#include "ChooseTargetActions.h"
#include "DruidBearActions.h"
#include "DruidCatActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "Playerbots.h"
#include "WarriorActions.h"
using namespace GruulsLairHelpers;
static bool IsChargeAction(Action* action)
{
return dynamic_cast<CastChargeAction*>(action) ||
dynamic_cast<CastInterceptAction*>(action) ||
dynamic_cast<CastFeralChargeBearAction*>(action) ||
dynamic_cast<CastFeralChargeCatAction*>(action);
}
float HighKingMaulgarDisableTankAssistMultiplier::GetValue(Action* action)
{
if (IsAnyOgreBossAlive(botAI) && dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
// Don't run back in during Whirlwind
float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (maulgar && maulgar->HasAura(SPELL_WHIRLWIND) &&
(!kiggler || !kiggler->IsAlive()) &&
(!krosh || !krosh->IsAlive()) &&
(!olm || !olm->IsAlive()) &&
(!blindeye || !blindeye->IsAlive()))
{
if (IsChargeAction(action) || (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<HighKingMaulgarRunAwayFromWhirlwindAction*>(action)))
return 0.0f;
}
return 1.0f;
}
// Arcane Shot will remove Spell Shield, which the mage tank needs to survive
float HighKingMaulgarDisableArcaneShotOnKroshMultiplier::GetValue(Action* action)
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
Unit* target = AI_VALUE(Unit*, "current target");
if (krosh && target && target->GetGUID() == krosh->GetGUID() && dynamic_cast<CastArcaneShotAction*>(action))
return 0.0f;
return 1.0f;
}
float HighKingMaulgarDisableMageTankAOEMultiplier::GetValue(Action* action)
{
if (IsKroshMageTank(botAI, bot) &&
(dynamic_cast<CastFrostNovaAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
dynamic_cast<CastConeOfColdAction*>(action) || dynamic_cast<CastFlamestrikeAction*>(action) ||
dynamic_cast<CastDragonsBreathAction*>(action) || dynamic_cast<CastBlastWaveAction*>(action)))
return 0.0f;
return 1.0f;
}
float GruulTheDragonkillerMainTankMovementMultiplier::GetValue(Action* action)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul)
return 1.0f;
if (botAI->IsMainTank(bot))
{
if (gruul->GetVictim() == bot && dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
}
return 1.0f;
}
float GruulTheDragonkillerGroundSlamMultiplier::GetValue(Action* action)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul)
return 1.0f;
if (bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2))
{
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) ||
IsChargeAction(action))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,48 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRMULTIPLIERS_H
#define _PLAYERBOT_RAIDGRUULSLAIRMULTIPLIERS_H
#include "Multiplier.h"
class HighKingMaulgarDisableTankAssistMultiplier : public Multiplier
{
public:
HighKingMaulgarDisableTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable tank assist multiplier") {}
float GetValue(Action* action) override;
};
class HighKingMaulgarAvoidWhirlwindMultiplier : public Multiplier
{
public:
HighKingMaulgarAvoidWhirlwindMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar avoid whirlwind multiplier") {}
float GetValue(Action* action) override;
};
class HighKingMaulgarDisableArcaneShotOnKroshMultiplier : public Multiplier
{
public:
HighKingMaulgarDisableArcaneShotOnKroshMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable arcane shot on krosh multiplier") {}
float GetValue(Action* action) override;
};
class HighKingMaulgarDisableMageTankAOEMultiplier : public Multiplier
{
public:
HighKingMaulgarDisableMageTankAOEMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable mage tank aoe multiplier") {}
float GetValue(Action* action) override;
};
class GruulTheDragonkillerMainTankMovementMultiplier : public Multiplier
{
public:
GruulTheDragonkillerMainTankMovementMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller main tank movement multiplier") {}
float GetValue(Action* action) override;
};
class GruulTheDragonkillerGroundSlamMultiplier : public Multiplier
{
public:
GruulTheDragonkillerGroundSlamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller ground slam multiplier") {}
float GetValue(Action* action) override;
};
#endif

View File

@@ -0,0 +1,49 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRACTIONCONTEXT_H
#define _PLAYERBOT_RAIDGRUULSLAIRACTIONCONTEXT_H
#include "RaidGruulsLairActions.h"
#include "NamedObjectContext.h"
class RaidGruulsLairActionContext : public NamedObjectContext<Action>
{
public:
RaidGruulsLairActionContext()
{
// High King Maulgar
creators["high king maulgar main tank attack maulgar"] = &RaidGruulsLairActionContext::high_king_maulgar_main_tank_attack_maulgar;
creators["high king maulgar first assist tank attack olm"] = &RaidGruulsLairActionContext::high_king_maulgar_first_assist_tank_attack_olm;
creators["high king maulgar second assist tank attack blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_second_assist_tank_attack_blindeye;
creators["high king maulgar mage tank attack krosh"] = &RaidGruulsLairActionContext::high_king_maulgar_mage_tank_attack_krosh;
creators["high king maulgar moonkin tank attack kiggler"] = &RaidGruulsLairActionContext::high_king_maulgar_moonkin_tank_attack_kiggler;
creators["high king maulgar assign dps priority"] = &RaidGruulsLairActionContext::high_king_maulgar_assign_dps_priority;
creators["high king maulgar healer find safe position"] = &RaidGruulsLairActionContext::high_king_maulgar_healer_find_safe_position;
creators["high king maulgar run away from whirlwind"] = &RaidGruulsLairActionContext::high_king_maulgar_run_away_from_whirlwind;
creators["high king maulgar banish felstalker"] = &RaidGruulsLairActionContext::high_king_maulgar_banish_felstalker;
creators["high king maulgar misdirect olm and blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_misdirect_olm_and_blindeye;
// Gruul the Dragonkiller
creators["gruul the dragonkiller main tank position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_main_tank_position_boss;
creators["gruul the dragonkiller spread ranged"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_spread_ranged;
creators["gruul the dragonkiller shatter spread"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_shatter_spread;
}
private:
// High King Maulgar
static Action* high_king_maulgar_main_tank_attack_maulgar(PlayerbotAI* botAI) { return new HighKingMaulgarMainTankAttackMaulgarAction(botAI); }
static Action* high_king_maulgar_first_assist_tank_attack_olm(PlayerbotAI* botAI) { return new HighKingMaulgarFirstAssistTankAttackOlmAction(botAI); }
static Action* high_king_maulgar_second_assist_tank_attack_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarSecondAssistTankAttackBlindeyeAction(botAI); }
static Action* high_king_maulgar_mage_tank_attack_krosh(PlayerbotAI* botAI) { return new HighKingMaulgarMageTankAttackKroshAction(botAI); }
static Action* high_king_maulgar_moonkin_tank_attack_kiggler(PlayerbotAI* botAI) { return new HighKingMaulgarMoonkinTankAttackKigglerAction(botAI); }
static Action* high_king_maulgar_assign_dps_priority(PlayerbotAI* botAI) { return new HighKingMaulgarAssignDPSPriorityAction(botAI); }
static Action* high_king_maulgar_healer_find_safe_position(PlayerbotAI* botAI) { return new HighKingMaulgarHealerFindSafePositionAction(botAI); }
static Action* high_king_maulgar_run_away_from_whirlwind(PlayerbotAI* botAI) { return new HighKingMaulgarRunAwayFromWhirlwindAction(botAI); }
static Action* high_king_maulgar_banish_felstalker(PlayerbotAI* botAI) { return new HighKingMaulgarBanishFelstalkerAction(botAI); }
static Action* high_king_maulgar_misdirect_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarMisdirectOlmAndBlindeyeAction(botAI); }
// Gruul the Dragonkiller
static Action* gruul_the_dragonkiller_main_tank_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerMainTankPositionBossAction(botAI); }
static Action* gruul_the_dragonkiller_spread_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerSpreadRangedAction(botAI); }
static Action* gruul_the_dragonkiller_shatter_spread(PlayerbotAI* botAI) { return new GruulTheDragonkillerShatterSpreadAction(botAI); }
};
#endif

View File

@@ -0,0 +1,49 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H
#include "RaidGruulsLairTriggers.h"
#include "AiObjectContext.h"
class RaidGruulsLairTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidGruulsLairTriggerContext() : NamedObjectContext<Trigger>()
{
// High King Maulgar
creators["high king maulgar is main tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_main_tank;
creators["high king maulgar is first assist tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_first_assist_tank;
creators["high king maulgar is second assist tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_second_assist_tank;
creators["high king maulgar is mage tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_mage_tank;
creators["high king maulgar is moonkin tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_moonkin_tank;
creators["high king maulgar determining kill order"] = &RaidGruulsLairTriggerContext::high_king_maulgar_determining_kill_order;
creators["high king maulgar healer in danger"] = &RaidGruulsLairTriggerContext::high_king_maulgar_healer_in_danger;
creators["high king maulgar boss channeling whirlwind"] = &RaidGruulsLairTriggerContext::high_king_maulgar_boss_channeling_whirlwind;
creators["high king maulgar wild felstalker spawned"] = &RaidGruulsLairTriggerContext::high_king_maulgar_wild_felstalker_spawned;
creators["high king maulgar pulling olm and blindeye"] = &RaidGruulsLairTriggerContext::high_king_maulgar_pulling_olm_and_blindeye;
// Gruul the Dragonkiller
creators["gruul the dragonkiller boss engaged by main tank"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_main_tank;
creators["gruul the dragonkiller boss engaged by range"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_range;
creators["gruul the dragonkiller incoming shatter"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_incoming_shatter;
}
private:
// High King Maulgar
static Trigger* high_king_maulgar_is_main_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMainTankTrigger(botAI); }
static Trigger* high_king_maulgar_is_first_assist_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsFirstAssistTankTrigger(botAI); }
static Trigger* high_king_maulgar_is_second_assist_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsSecondAssistTankTrigger(botAI); }
static Trigger* high_king_maulgar_is_mage_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMageTankTrigger(botAI); }
static Trigger* high_king_maulgar_is_moonkin_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMoonkinTankTrigger(botAI); }
static Trigger* high_king_maulgar_determining_kill_order(PlayerbotAI* botAI) { return new HighKingMaulgarDeterminingKillOrderTrigger(botAI); }
static Trigger* high_king_maulgar_healer_in_danger(PlayerbotAI* botAI) { return new HighKingMaulgarHealerInDangerTrigger(botAI); }
static Trigger* high_king_maulgar_boss_channeling_whirlwind(PlayerbotAI* botAI) { return new HighKingMaulgarBossChannelingWhirlwindTrigger(botAI); }
static Trigger* high_king_maulgar_wild_felstalker_spawned(PlayerbotAI* botAI) { return new HighKingMaulgarWildFelstalkerSpawnedTrigger(botAI); }
static Trigger* high_king_maulgar_pulling_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarPullingOlmAndBlindeyeTrigger(botAI); }
// Gruul the Dragonkiller
static Trigger* gruul_the_dragonkiller_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByMainTankTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_boss_engaged_by_range(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangeTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_incoming_shatter(PlayerbotAI* botAI) { return new GruulTheDragonkillerIncomingShatterTrigger(botAI); }
};
#endif

View File

@@ -0,0 +1,56 @@
#include "RaidGruulsLairStrategy.h"
#include "RaidGruulsLairMultipliers.h"
void RaidGruulsLairStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// High King Maulgar
triggers.push_back(new TriggerNode("high king maulgar is main tank", {
NextAction("high king maulgar main tank attack maulgar", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar is first assist tank", {
NextAction("high king maulgar first assist tank attack olm", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar is second assist tank", {
NextAction("high king maulgar second assist tank attack blindeye", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar is mage tank", {
NextAction("high king maulgar mage tank attack krosh", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar is moonkin tank", {
NextAction("high king maulgar moonkin tank attack kiggler", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar determining kill order", {
NextAction("high king maulgar assign dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar healer in danger", {
NextAction("high king maulgar healer find safe position", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar boss channeling whirlwind", {
NextAction("high king maulgar run away from whirlwind", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("high king maulgar wild felstalker spawned", {
NextAction("high king maulgar banish felstalker", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("high king maulgar pulling olm and blindeye", {
NextAction("high king maulgar misdirect olm and blindeye", ACTION_RAID + 2) }));
// Gruul the Dragonkiller
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by main tank", {
NextAction("gruul the dragonkiller main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by range", {
NextAction("gruul the dragonkiller spread ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller incoming shatter", {
NextAction("gruul the dragonkiller shatter spread", ACTION_EMERGENCY + 6) }));
}
void RaidGruulsLairStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new HighKingMaulgarDisableTankAssistMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarAvoidWhirlwindMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarDisableArcaneShotOnKroshMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarDisableMageTankAOEMultiplier(botAI));
multipliers.push_back(new GruulTheDragonkillerMainTankMovementMultiplier(botAI));
multipliers.push_back(new GruulTheDragonkillerGroundSlamMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H
#define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H
#include "Strategy.h"
#include "Multiplier.h"
class RaidGruulsLairStrategy : public Strategy
{
public:
RaidGruulsLairStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "gruulslair"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -0,0 +1,160 @@
#include "RaidGruulsLairTriggers.h"
#include "RaidGruulsLairHelpers.h"
#include "Playerbots.h"
using namespace GruulsLairHelpers;
// High King Maulgar Triggers
bool HighKingMaulgarIsMainTankTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive();
}
bool HighKingMaulgarIsFirstAssistTankTrigger::IsActive()
{
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
return botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive();
}
bool HighKingMaulgarIsSecondAssistTankTrigger::IsActive()
{
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
return botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive();
}
bool HighKingMaulgarIsMageTankTrigger::IsActive()
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive();
}
bool HighKingMaulgarIsMoonkinTankTrigger::IsActive()
{
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
return IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive();
}
bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return (botAI->IsDps(bot) || botAI->IsTank(bot)) &&
!(botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive()) &&
!(botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive()) &&
!(botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive()) &&
!(IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive()) &&
!(IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive());
}
bool HighKingMaulgarHealerInDangerTrigger::IsActive()
{
return botAI->IsHeal(bot) && IsAnyOgreBossAlive(botAI);
}
bool HighKingMaulgarBossChannelingWhirlwindTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return maulgar && maulgar->IsAlive() && maulgar->HasAura(SPELL_WHIRLWIND) &&
!botAI->IsMainTank(bot);
}
bool HighKingMaulgarWildFelstalkerSpawnedTrigger::IsActive()
{
Unit* felStalker = AI_VALUE2(Unit*, "find target", "wild fel stalker");
return felStalker && felStalker->IsAlive() && bot->getClass() == CLASS_WARLOCK;
}
bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
{
Group* group = bot->GetGroup();
if (!group || bot->getClass() != CLASS_HUNTER)
return false;
std::vector<Player*> hunters;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member))
hunters.push_back(member);
}
int hunterIndex = -1;
for (size_t i = 0; i < hunters.size(); ++i)
{
if (hunters[i] == bot)
{
hunterIndex = static_cast<int>(i);
break;
}
}
if (hunterIndex == -1 || hunterIndex > 1)
return false;
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
Player* olmTank = nullptr;
Player* blindeyeTank = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
else if (botAI->IsAssistTankOfIndex(member, 0)) olmTank = member;
else if (botAI->IsAssistTankOfIndex(member, 1)) blindeyeTank = member;
}
switch (hunterIndex)
{
case 0:
return olm && olm->IsAlive() && olm->GetHealthPct() > 98.0f &&
olmTank && olmTank->IsAlive() && botAI->CanCastSpell("misdirection", olmTank);
case 1:
return blindeye && blindeye->IsAlive() && blindeye->GetHealthPct() > 90.0f &&
blindeyeTank && blindeyeTank->IsAlive() && botAI->CanCastSpell("misdirection", blindeyeTank);
default:
break;
}
return false;
}
// Gruul the Dragonkiller Triggers
bool GruulTheDragonkillerBossEngagedByMainTankTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsMainTank(bot);
}
bool GruulTheDragonkillerBossEngagedByRangeTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsRanged(bot);
}
bool GruulTheDragonkillerIncomingShatterTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() &&
(bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2));
}

View File

@@ -0,0 +1,97 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRTRIGGERS_H
#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERS_H
#include "Trigger.h"
class HighKingMaulgarIsMainTankTrigger : public Trigger
{
public:
HighKingMaulgarIsMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is main tank") {}
bool IsActive() override;
};
class HighKingMaulgarIsFirstAssistTankTrigger : public Trigger
{
public:
HighKingMaulgarIsFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is first assist tank") {}
bool IsActive() override;
};
class HighKingMaulgarIsSecondAssistTankTrigger : public Trigger
{
public:
HighKingMaulgarIsSecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is second assist tank") {}
bool IsActive() override;
};
class HighKingMaulgarIsMageTankTrigger : public Trigger
{
public:
HighKingMaulgarIsMageTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is mage tank") {}
bool IsActive() override;
};
class HighKingMaulgarIsMoonkinTankTrigger : public Trigger
{
public:
HighKingMaulgarIsMoonkinTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is moonkin tank") {}
bool IsActive() override;
};
class HighKingMaulgarDeterminingKillOrderTrigger : public Trigger
{
public:
HighKingMaulgarDeterminingKillOrderTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar determining kill order") {}
bool IsActive() override;
};
class HighKingMaulgarHealerInDangerTrigger : public Trigger
{
public:
HighKingMaulgarHealerInDangerTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar healers in danger") {}
bool IsActive() override;
};
class HighKingMaulgarBossChannelingWhirlwindTrigger : public Trigger
{
public:
HighKingMaulgarBossChannelingWhirlwindTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar boss channeling whirlwind") {}
bool IsActive() override;
};
class HighKingMaulgarWildFelstalkerSpawnedTrigger : public Trigger
{
public:
HighKingMaulgarWildFelstalkerSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar wild felstalker spawned") {}
bool IsActive() override;
};
class HighKingMaulgarPullingOlmAndBlindeyeTrigger : public Trigger
{
public:
HighKingMaulgarPullingOlmAndBlindeyeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar pulling olm and blindeye") {}
bool IsActive() override;
};
class GruulTheDragonkillerBossEngagedByMainTankTrigger : public Trigger
{
public:
GruulTheDragonkillerBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by main tank") {}
bool IsActive() override;
};
class GruulTheDragonkillerBossEngagedByRangeTrigger : public Trigger
{
public:
GruulTheDragonkillerBossEngagedByRangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by range") {}
bool IsActive() override;
};
class GruulTheDragonkillerIncomingShatterTrigger : public Trigger
{
public:
GruulTheDragonkillerIncomingShatterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller incoming shatter") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,241 @@
#include "RaidGruulsLairHelpers.h"
#include "AiFactory.h"
#include "GroupReference.h"
#include "Playerbots.h"
#include "Unit.h"
namespace GruulsLairHelpers
{
namespace GruulsLairLocations
{
// Olm does not chase properly due to the Core's caster movement issues
// Thus, the below "OlmTankPosition" is beyond the actual desired tanking location
// It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location
// "MaulgarRoomCenter" is to keep healers in a centralized location
const Location MaulgarTankPosition = { 90.686f, 167.047f, -13.234f };
const Location OlmTankPosition = { 87.485f, 234.942f, -3.635f };
const Location BlindeyeTankPosition = { 99.681f, 213.989f, -10.345f };
const Location KroshTankPosition = { 116.880f, 166.208f, -14.231f };
const Location MaulgarRoomCenter = { 88.754f, 150.759f, -11.569f };
const Location GruulTankPosition = { 241.238f, 365.025f, -4.220f };
}
bool IsAnyOgreBossAlive(PlayerbotAI* botAI)
{
const char* ogreBossNames[] =
{
"high king maulgar",
"kiggler the crazed",
"krosh firehand",
"olm the summoner",
"blindeye the seer"
};
for (const char* name : ogreBossNames)
{
Unit* boss = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", name)->Get();
if (!boss || !boss->IsAlive())
continue;
return true;
}
return false;
}
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
Group* group = bot->GetGroup();
if (!target || !group)
return;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
{
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return false;
Player* highestHpMage = nullptr;
uint32 highestHp = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
if (member->getClass() == CLASS_MAGE)
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMage || hp > highestHp)
{
highestHpMage = member;
highestHp = hp;
}
}
}
return highestHpMage == bot;
}
bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return false;
Player* highestHpMoonkin = nullptr;
uint32 highestHp = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
if (member->getClass() == CLASS_DRUID)
{
int tab = AiFactory::GetPlayerSpecTab(member);
if (tab == DRUID_TAB_BALANCE)
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp)
{
highestHpMoonkin = member;
highestHp = hp;
}
}
}
}
return highestHpMoonkin == bot;
}
bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos)
{
const float KROSH_SAFE_DISTANCE = 20.0f;
const float MAULGAR_SAFE_DISTANCE = 10.0f;
bool isSafe = true;
Unit* krosh = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "krosh firehand")->Get();
if (krosh && krosh->IsAlive())
{
float dist = sqrt(pow(pos.GetPositionX() - krosh->GetPositionX(), 2) + pow(pos.GetPositionY() - krosh->GetPositionY(), 2));
if (dist < KROSH_SAFE_DISTANCE)
isSafe = false;
}
Unit* maulgar = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "high king maulgar")->Get();
if (botAI->IsRanged(bot) && maulgar && maulgar->IsAlive())
{
float dist = sqrt(pow(pos.GetPositionX() - maulgar->GetPositionX(), 2) + pow(pos.GetPositionY() - maulgar->GetPositionY(), 2));
if (dist < MAULGAR_SAFE_DISTANCE)
isSafe = false;
}
return isSafe;
}
bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos)
{
const float SEARCH_RADIUS = 30.0f;
const uint8 NUM_POSITIONS = 32;
outPos = { bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ() };
if (IsPositionSafe(botAI, bot, outPos))
{
outPos = Position();
return false;
}
float bestScore = std::numeric_limits<float>::max();
bool foundSafeSpot = false;
Position bestPos;
for (int i = 0; i < NUM_POSITIONS; ++i)
{
float angle = 2 * M_PI * i / NUM_POSITIONS;
Position candidatePos;
candidatePos.m_positionX = bot->GetPositionX() + SEARCH_RADIUS * cos(angle);
candidatePos.m_positionY = bot->GetPositionY() + SEARCH_RADIUS * sin(angle);
candidatePos.m_positionZ = bot->GetPositionZ();
float destX = candidatePos.m_positionX, destY = candidatePos.m_positionY, destZ = candidatePos.m_positionZ;
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ, true))
continue;
if (destX != candidatePos.m_positionX || destY != candidatePos.m_positionY)
continue;
candidatePos.m_positionX = destX;
candidatePos.m_positionY = destY;
candidatePos.m_positionZ = destZ;
if (IsPositionSafe(botAI, bot, candidatePos))
{
float movementDistance = sqrt(pow(destX - bot->GetPositionX(), 2) + pow(destY - bot->GetPositionY(), 2));
if (movementDistance < bestScore)
{
bestScore = movementDistance;
bestPos = candidatePos;
foundSafeSpot = true;
}
}
}
if (foundSafeSpot)
{
outPos = bestPos;
return true;
}
outPos = Position();
return false;
}
}

View File

@@ -0,0 +1,62 @@
#ifndef RAID_GRUULSLAIRHELPERS_H
#define RAID_GRUULSLAIRHELPERS_H
#include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace GruulsLairHelpers
{
enum GruulsLairSpells
{
// High King Maulgar
SPELL_WHIRLWIND = 33238,
// Krosh Firehand
SPELL_SPELL_SHIELD = 33054,
// Hunter
SPELL_MISDIRECTION = 35079,
// Warlock
SPELL_BANISH = 18647, // Rank 2
// Gruul the Dragonkiller
SPELL_GROUND_SLAM_1 = 33525,
SPELL_GROUND_SLAM_2 = 39187,
};
enum GruulsLairNPCs
{
NPC_WILD_FEL_STALKER = 18847,
};
bool IsAnyOgreBossAlive(PlayerbotAI* botAI);
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot);
bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot);
bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos);
bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos);
struct Location
{
float x, y, z;
};
namespace GruulsLairLocations
{
extern const Location MaulgarTankPosition;
extern const Location OlmTankPosition;
extern const Location BlindeyeTankPosition;
extern const Location KroshTankPosition;
extern const Location MaulgarRoomCenter;
extern const Location GruulTankPosition;
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,669 @@
#ifndef _PLAYERBOT_RAIDICCACTIONS_H
#define _PLAYERBOT_RAIDICCACTIONS_H
#include "Action.h"
#include "MovementActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "AttackAction.h"
#include "LastMovementValue.h"
#include "ObjectGuid.h"
#include "PlayerbotAIConfig.h"
#include "RaidIccStrategy.h"
#include "ScriptedCreature.h"
#include "SharedDefines.h"
#include "Trigger.h"
#include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Vehicle.h"
#include "RaidIccTriggers.h"
const Position ICC_LM_TANK_POSITION = Position(-391.0f, 2259.0f, 42.0f);
const Position ICC_DARK_RECKONING_SAFE_POSITION = Position(-523.33386f, 2211.2031f, 62.823116f);
const Position ICC_LDW_TANK_POSTION = Position(-570.1f, 2211.2456f, 49.476616f); //-590.0f
const Position ICC_ROTTING_FROST_GIANT_TANK_POSITION = Position(-328.5085f, 2225.5142f, 199.97298f);
const Position ICC_GUNSHIP_TELEPORT_ALLY = Position (-370.04645f, 1993.3536f, 466.65656f);
const Position ICC_GUNSHIP_TELEPORT_ALLY2 = Position (-392.66208f, 2064.893f, 466.5672f, 5.058196f);
const Position ICC_GUNSHIP_TELEPORT_HORDE = Position (-449.5343f, 2477.2024f, 470.17648f);
const Position ICC_GUNSHIP_TELEPORT_HORDE2 = Position (-429.81586f, 2400.6804f, 471.56537f);
const Position ICC_DBS_TANK_POSITION = Position(-494.26517f, 2211.549f, 541.11414f);
const Position ICC_FESTERGUT_TANK_POSITION = Position(4269.1772f, 3144.7673f, 360.38577f);
const Position ICC_FESTERGUT_RANGED_SPORE = Position(4261.143f, 3109.4146f, 360.38605f);
const Position ICC_FESTERGUT_MELEE_SPORE = Position(4269.1772f, 3144.7673f, 360.38577f);
const Position ICC_ROTFACE_TANK_POSITION = Position(4447.061f, 3150.9758f, 360.38568f);
const Position ICC_ROTFACE_BIG_OOZE_POSITION = Position(4432.687f, 3142.3035f, 360.38623f);
const Position ICC_ROTFACE_SAFE_POSITION = Position(4446.557f, 3065.6594f, 360.51843f);
const Position ICC_ROTFACE_CENTER_POSITION = Position(4446.0547f, 3144.8677f, 360.38593f); //actual center 4.74089 4445.6616f, 3137.1526f, 360.38608
const Position ICC_PUTRICIDE_TANK_POSITION = Position(4373.227f, 3222.058f, 389.4029f);
const Position ICC_PUTRICIDE_GREEN_POSITION = Position(4423.4126f, 3194.2715f, 389.37683f);
const Position ICC_PUTRICIDE_BAD_POSITION = Position(4356.1724f, 3261.5232f, 389.3985f);
//const Position ICC_PUTRICIDE_GAS3_POSITION = Position(4367.753f, 3177.5894f, 389.39575f);
//const Position ICC_PUTRICIDE_GAS4_POSITION = Position(4321.8486f, 3206.464f, 389.3982f);
const Position ICC_BPC_OT_POSITION = Position(4649.2236f, 2796.0972f, 361.1815f);
const Position ICC_BPC_MT_POSITION = Position(4648.5674f, 2744.847f, 361.18222f);
const Position ICC_BQL_CENTER_POSITION = Position(4595.0f, 2769.0f, 400.0f);
const Position ICC_BQL_LWALL1_POSITION = Position(4624.685f, 2789.4895f, 400.13834f);
const Position ICC_BQL_LWALL2_POSITION = Position(4600.749f, 2805.7568f, 400.1374f);
const Position ICC_BQL_LWALL3_POSITION = Position(4572.857f, 2797.3872f, 400.1374f);
const Position ICC_BQL_RWALL1_POSITION = Position(4625.724f, 2748.9917f, 400.13693f);
const Position ICC_BQL_RWALL2_POSITION = Position(4608.3774f, 2735.7466f, 400.13693f);
const Position ICC_BQL_RWALL3_POSITION = Position(4576.813f, 2739.6067f, 400.13693f);
const Position ICC_BQL_LRWALL4_POSITION = Position(4539.345f, 2769.3853f, 403.7267f);
const Position ICC_BQL_TANK_POSITION = Position(4629.746f, 2769.6396f, 401.7479f); //old just in front of stairs 4616.102f, 2768.9167f, 400.13797f
const Position ICC_VDW_HEAL_POSITION = Position(4203.752f, 2483.4343f, 364.87274f);
const Position ICC_VDW_GROUP1_POSITION = Position(4203.585f, 2464.422f, 364.86887f);
const Position ICC_VDW_GROUP2_POSITION = Position(4203.5806f, 2505.2383f, 364.87677f);
const Position ICC_VDW_PORTALSTART_POSITION = Position(4202.637f, 2488.171f, 375.00256f);
const Position ICC_SINDRAGOSA_TANK_POSITION = Position(4408.016f, 2508.0647f, 203.37955f);
const Position ICC_SINDRAGOSA_FLYING_POSITION = Position(4525.6f, 2485.15f, 245.082f);
const Position ICC_SINDRAGOSA_RANGED_POSITION = Position(4441.572f, 2484.482f, 203.37836f);
const Position ICC_SINDRAGOSA_MELEE_POSITION = Position(4423.4546f, 2491.7175f, 203.37686f);
const Position ICC_SINDRAGOSA_BLISTERING_COLD_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f);
const Position ICC_SINDRAGOSA_THOMB1_POSITION = Position(4433.6484f, 2469.4133f, 203.3806f);
const Position ICC_SINDRAGOSA_THOMB2_POSITION = Position(4434.143f, 2486.201f, 203.37473f);
const Position ICC_SINDRAGOSA_THOMB3_POSITION = Position(4436.1147f, 2501.464f, 203.38266f);
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC1_POSITION = Position(4444.9707f, 2455.7322f, 203.38701f);
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC2_POSITION = Position(4461.3945f, 2463.5513f, 203.38727f);
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC3_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f);
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC4_POSITION = Position(4459.9336f, 2507.409f, 203.38606f);
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC5_POSITION = Position(4442.3096f, 2512.4688f, 203.38647f);
const Position ICC_SINDRAGOSA_CENTER_POSITION = Position(4408.0464f, 2484.478f, 203.37529f);
const Position ICC_SINDRAGOSA_THOMBMB2_POSITION = Position(4436.895f, 2498.1401f, 203.38133f);
const Position ICC_SINDRAGOSA_FBOMB_POSITION = Position(4449.3647f, 2486.4524f, 203.379f);
const Position ICC_SINDRAGOSA_FBOMB10_POSITION = Position(4449.3647f, 2486.4524f, 203.379f);
const Position ICC_SINDRAGOSA_LOS2_POSITION = Position(4441.8286f, 2501.946f, 203.38435f);
const Position ICC_LICH_KING_ADDS_POSITION = Position(476.7332f, -2095.3894f, 840.857f); // old 486.63647f, -2095.7915f, 840.857f
const Position ICC_LICH_KING_MELEE_POSITION = Position(503.5546f, -2106.8213f, 840.857f);
const Position ICC_LICH_KING_RANGED_POSITION = Position(501.3563f, -2085.1816f, 840.857f);
const Position ICC_LICH_KING_ASSISTHC_POSITION = Position(517.2145f, -2125.0674f, 840.857f);
const Position ICC_LK_FROST1_POSITION = Position(503.96548f, -2183.216f, 840.857f);
const Position ICC_LK_FROST2_POSITION = Position(563.07166f, -2125.7578f, 840.857f);
const Position ICC_LK_FROST3_POSITION = Position(503.40182f, -2067.3435f, 840.857f);
const Position ICC_LK_FROSTR1_POSITION = Position(481.168f, -2177.8723f, 840.857f);
const Position ICC_LK_FROSTR2_POSITION = Position(562.20807f, -2100.2393f, 840.857f);
const Position ICC_LK_FROSTR3_POSITION = Position(526.35297f, -2071.0317f, 840.857f);
//Lord Marrogwar
class IccLmTankPositionAction : public AttackAction
{
public:
IccLmTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc lm tank position")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool MoveTowardPosition(const Position& position, float incrementSize);
};
class IccSpikeAction : public AttackAction
{
public:
IccSpikeAction(PlayerbotAI* botAI) : AttackAction(botAI, "icc spike") {}
bool Execute(Event event) override;
bool HandleSpikeTargeting(Unit* boss);
bool MoveTowardPosition(const Position& position, float incrementSize);
void UpdateRaidTargetIcon(Unit* target);
};
//Lady Deathwhisper
class IccDarkReckoningAction : public MovementAction
{
public:
IccDarkReckoningAction(PlayerbotAI* botAI, std::string const name = "icc dark reckoning")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class IccRangedPositionLadyDeathwhisperAction : public AttackAction
{
public:
IccRangedPositionLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc ranged position lady deathwhisper")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool MaintainRangedSpacing();
};
class IccAddsLadyDeathwhisperAction : public AttackAction
{
public:
IccAddsLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc adds lady deathwhisper")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool IsTargetedByShade(uint32 shadeEntry);
bool MoveTowardPosition(const Position& position, float incrementSize);
bool HandleAddTargeting(Unit* boss);
void UpdateRaidTargetIcon(Unit* target);
};
class IccShadeLadyDeathwhisperAction : public MovementAction
{
public:
IccShadeLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc shade lady deathwhisper")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
//Gunship Battle
class IccRottingFrostGiantTankPositionAction : public AttackAction
{
public:
IccRottingFrostGiantTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotting frost giant tank position")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class IccCannonFireAction : public Action
{
public:
IccCannonFireAction(PlayerbotAI* botAI, std::string const name = "icc cannon fire")
: Action(botAI, name) {}
bool Execute(Event event) override;
Unit* FindValidCannonTarget();
bool TryCastCannonSpell(uint32 spellId, Unit* target, Unit* vehicleBase);
};
class IccGunshipEnterCannonAction : public MovementAction
{
public:
IccGunshipEnterCannonAction(PlayerbotAI* botAI, std::string const name = "icc gunship enter cannon")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool EnterVehicle(Unit* vehicleBase, bool moveIfFar);
Unit* FindBestAvailableCannon();
bool IsValidCannon(Unit* vehicle, const uint32 validEntries[]);
};
class IccGunshipTeleportAllyAction : public AttackAction
{
public:
IccGunshipTeleportAllyAction(PlayerbotAI* botAI, std::string const name = "icc gunship teleport ally")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool TeleportTo(const Position& position);
void CleanupSkullIcon(uint8_t SKULL_ICON_INDEX);
void UpdateBossSkullIcon(Unit* boss, uint8_t SKULL_ICON_INDEX);
};
class IccGunshipTeleportHordeAction : public AttackAction
{
public:
IccGunshipTeleportHordeAction(PlayerbotAI* botAI, std::string const name = "icc gunship teleport horde")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool TeleportTo(const Position& position);
void CleanupSkullIcon(uint8_t SKULL_ICON_INDEX);
void UpdateBossSkullIcon(Unit* boss, uint8_t SKULL_ICON_INDEX);
};
//DBS
class IccDbsTankPositionAction : public AttackAction
{
public:
IccDbsTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc dbs tank position")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool CrowdControlBloodBeasts();
bool EvadeBloodBeasts();
bool PositionInRangedFormation();
};
class IccAddsDbsAction : public AttackAction
{
public:
IccAddsDbsAction(PlayerbotAI* botAI, std::string const name = "icc adds dbs")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
Unit* FindPriorityTarget(Unit* boss);
void UpdateSkullMarker(Unit* priorityTarget);
};
//FESTERGUT
class IccFestergutGroupPositionAction : public AttackAction
{
public:
IccFestergutGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc festergut group position")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool HasSporesInGroup();
bool PositionNonTankMembers();
int CalculatePositionIndex(Group* group);
};
class IccFestergutSporeAction : public AttackAction
{
public:
IccFestergutSporeAction(PlayerbotAI* botAI, std::string const name = "icc festergut spore")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
Position CalculateSpreadPosition();
struct SporeInfo
{
std::vector<Unit*> sporedPlayers;
ObjectGuid lowestGuid;
bool hasLowestGuid = false;
};
SporeInfo FindSporedPlayers();
Position DetermineTargetPosition(bool hasSpore, const SporeInfo& sporeInfo, const Position& spreadRangedPos);
bool CheckMainTankSpore();
};
//Rotface
class IccRotfaceTankPositionAction : public AttackAction
{
public:
IccRotfaceTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface tank position")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
void MarkBossWithSkull(Unit* boss);
bool PositionMainTankAndMelee(Unit *boss);
bool HandleAssistTankPositioning(Unit* boss);
bool HandleBigOozePositioning(Unit* boss);
};
class IccRotfaceGroupPositionAction : public AttackAction
{
public:
IccRotfaceGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface group position")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
//bool MoveAwayFromBigOoze(Unit* bigOoze);
bool HandlePuddleAvoidance(Unit* boss);
bool MoveAwayFromPuddle(Unit* boss, Unit* puddle, float puddleDistance);
bool HandleOozeTargeting();
bool HandleOozeMemberPositioning();
bool PositionRangedAndHealers(Unit* boss,Unit* smallOoze);
bool FindAndMoveFromClosestMember(Unit* boss, Unit* smallOoze);
};
class IccRotfaceMoveAwayFromExplosionAction : public MovementAction
{
public:
IccRotfaceMoveAwayFromExplosionAction(PlayerbotAI* botAI, std::string const name = "icc rotface move away from explosion")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool MoveToRandomSafeLocation();
};
//PP
class IccPutricideGrowingOozePuddleAction : public AttackAction
{
public:
IccPutricideGrowingOozePuddleAction(PlayerbotAI* botAI, std::string const name = "icc putricide growing ooze puddle")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
Unit* FindClosestThreateningPuddle();
Position CalculateSafeMovePosition(Unit* closestPuddle);
bool IsPositionTooCloseToOtherPuddles(float x, float y, Unit* ignorePuddle);
};
class IccPutricideVolatileOozeAction : public AttackAction
{
public:
IccPutricideVolatileOozeAction(PlayerbotAI* botAI, std::string const name = "icc putricide volatile ooze")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
void MarkOozeWithSkull(Unit* ooze);
Unit* FindAuraTarget();
};
class IccPutricideGasCloudAction : public AttackAction
{
public:
IccPutricideGasCloudAction(PlayerbotAI* botAI, std::string const name = "icc putricide gas cloud")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool HandleGaseousBloatMovement(Unit* gasCloud);
Position CalculateEmergencyPosition(const Position& botPos, float dx, float dy);
bool FindSafeMovementPosition(const Position& botPos, const Position& cloudPos, float dx, float dy, int numAngles,
Position& resultPos);
bool HandleGroupAuraSituation(Unit* gasCloud);
bool GroupHasGaseousBloat(Group* group);
};
class IccPutricideAvoidMalleableGooAction : public MovementAction
{
public:
IccPutricideAvoidMalleableGooAction(PlayerbotAI* botAI, std::string const name = "icc putricide avoid malleable goo")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool HandleTankPositioning(Unit* boss);
bool HandleUnboundPlague(Unit* boss);
bool HandleBossPositioning(Unit* boss);
Position CalculateBossPosition(Unit* boss, float distance);
bool HasObstacleBetween(const Position& from, const Position& to);
bool IsOnPath(const Position& from, const Position& to, const Position& point, float threshold);
Position CalculateArcPoint(const Position& current, const Position& target, const Position& center);
Position CalculateIncrementalMove(const Position& current, const Position& target, float maxDistance);
};
//BPC
class IccBpcKelesethTankAction : public AttackAction
{
public:
IccBpcKelesethTankAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc bpc keleseth tank") {}
bool Execute(Event event) override;
};
class IccBpcMainTankAction : public AttackAction
{
public:
IccBpcMainTankAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc bpc main tank") {}
bool Execute(Event event) override;
void MarkEmpoweredPrince();
};
class IccBpcEmpoweredVortexAction : public MovementAction
{
public:
IccBpcEmpoweredVortexAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc bpc empowered vortex") {}
bool Execute(Event event) override;
bool MaintainRangedSpacing();
bool HandleEmpoweredVortexSpread();
};
class IccBpcKineticBombAction : public AttackAction
{
public:
IccBpcKineticBombAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc bpc kinetic bomb") {}
bool Execute(Event event) override;
Unit* FindOptimalKineticBomb();
bool IsBombAlreadyHandled(Unit* bomb, Group* group);
};
class IccBpcBallOfFlameAction : public MovementAction
{
public:
IccBpcBallOfFlameAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc bpc ball of flame") {}
bool Execute(Event event) override;
};
//Blood Queen Lana'thel
class IccBqlGroupPositionAction : public AttackAction
{
public:
IccBqlGroupPositionAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc group tank position") {}
bool Execute(Event event) override;
bool HandleTankPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura);
bool HandleShadowsMovement();
Position AdjustControlPoint(const Position& wall, const Position& center, float factor);
Position CalculateBezierPoint(float t, const Position path[4]);
bool HandleGroupPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura);
private:
// Evaluate curves
struct CurveInfo
{
Position moveTarget;
int curveIdx = 0;
bool foundSafe = false;
float minDist = 0.0f;
float score = 0.0f;
Position closestPoint;
float t_closest = 0.0f;
};
};
class IccBqlPactOfDarkfallenAction : public MovementAction
{
public:
IccBqlPactOfDarkfallenAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc bql pact of darkfallen") {}
bool Execute(Event event) override;
void CalculateCenterPosition(Position& targetPos, const std::vector<Player*>& playersWithAura);
bool MoveToTargetPosition(const Position& targetPos, int auraCount);
};
class IccBqlVampiricBiteAction : public AttackAction
{
public:
IccBqlVampiricBiteAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc bql vampiric bite") {}
bool Execute(Event event) override;
Player* FindBestBiteTarget(Group* group);
bool IsInvalidTarget(Player* player);
bool MoveTowardsTarget(Player* target);
bool CastVampiricBite(Player* target);
};
// Sister Svalna
class IccValkyreSpearAction : public AttackAction
{
public:
IccValkyreSpearAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc valkyre spear") {}
bool Execute(Event event) override;
};
class IccSisterSvalnaAction : public AttackAction
{
public:
IccSisterSvalnaAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc sister svalna") {}
bool Execute(Event event) override;
};
// Valithria Dreamwalker
class IccValithriaGroupAction : public AttackAction
{
public:
IccValithriaGroupAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc valithria group") {}
bool Execute(Event event) override;
bool MoveTowardsPosition(const Position& pos, float increment);
bool Handle25ManGroupLogic();
bool HandleMarkingLogic(bool inGroup1, bool inGroup2, const Position& group1Pos, const Position& group2Pos);
bool Handle10ManGroupLogic();
};
class IccValithriaPortalAction : public MovementAction
{
public:
IccValithriaPortalAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc valithria portal") {}
bool Execute(Event event) override;
};
class IccValithriaHealAction : public AttackAction
{
public:
IccValithriaHealAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc valithria heal") {}
bool Execute(Event event) override;
};
class IccValithriaDreamCloudAction : public MovementAction
{
public:
IccValithriaDreamCloudAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc valithria dream cloud") {}
bool Execute(Event event) override;
};
//Sindragosa
class IccSindragosaGroupPositionAction : public AttackAction
{
public:
IccSindragosaGroupPositionAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc sindragosa group position") {}
bool Execute(Event event) override;
bool HandleTankPositioning(Unit* boss);
bool HandleNonTankPositioning();
bool MoveIncrementallyToPosition(const Position& targetPos, float maxStep);
};
class IccSindragosaFrostBeaconAction : public MovementAction
{
public:
IccSindragosaFrostBeaconAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc sindragosa frost beacon") {}
bool Execute(Event event) override;
void HandleSupportActions();
bool HandleBeaconedPlayer(const Unit* boss);
bool HandleNonBeaconedPlayer(const Unit* boss);
bool MoveToPositionIfNeeded(const Position& position, float tolerance);
bool MoveToPosition(const Position& position);
bool IsBossFlying(const Unit* boss);
private:
static constexpr uint32 FROST_BEACON_AURA_ID = SPELL_FROST_BEACON;
static constexpr uint32 HAND_OF_FREEDOM_SPELL_ID = 1044;
static constexpr float POSITION_TOLERANCE = 1.0f;
static constexpr float TOMB_POSITION_TOLERANCE = 0.5f;
static constexpr float MIN_SAFE_DISTANCE = 13.0f;
static constexpr float MOVE_TOLERANCE = 2.0f;
};
class IccSindragosaBlisteringColdAction : public MovementAction
{
public:
IccSindragosaBlisteringColdAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc sindragosa blistering cold") {}
bool Execute(Event event) override;
};
class IccSindragosaUnchainedMagicAction : public AttackAction
{
public:
IccSindragosaUnchainedMagicAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc sindragosa unchained magic") {}
bool Execute(Event event) override;
};
class IccSindragosaChilledToTheBoneAction : public AttackAction
{
public:
IccSindragosaChilledToTheBoneAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc sindragosa chilled to the bone") {}
bool Execute(Event event) override;
};
class IccSindragosaMysticBuffetAction : public MovementAction
{
public:
IccSindragosaMysticBuffetAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc sindragosa mystic buffet") {}
bool Execute(Event event) override;
};
class IccSindragosaFrostBombAction : public MovementAction
{
public:
IccSindragosaFrostBombAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc sindragosa frost bomb") {}
bool Execute(Event event) override;
};
class IccSindragosaTankSwapPositionAction : public AttackAction
{
public:
IccSindragosaTankSwapPositionAction(PlayerbotAI* botAI)
: AttackAction(botAI, "sindragosa tank swap position") {}
bool Execute(Event event) override;
};
//LK
class IccLichKingShadowTrapAction : public MovementAction
{
public:
IccLichKingShadowTrapAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc lich king shadow trap") {}
bool Execute(Event event) override;
};
class IccLichKingNecroticPlagueAction : public MovementAction
{
public:
IccLichKingNecroticPlagueAction(PlayerbotAI* botAI)
: MovementAction(botAI, "icc lich king necrotic plague") {}
bool Execute(Event event) override;
};
class IccLichKingWinterAction : public AttackAction
{
public:
IccLichKingWinterAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc lich king winter") {}
bool Execute(Event event) override;
void HandlePositionCorrection();
bool IsValidCollectibleAdd(Unit* unit);
bool IsPositionSafeFromDefile(float x, float y, float z, float minSafeDistance);
void HandleTankPositioning();
void HandleMeleePositioning();
void HandleRangedPositioning();
void HandleMainTankAddManagement(Unit* boss, const Position* tankPos);
void HandleAssistTankAddManagement(Unit* boss, const Position* tankPos);
private:
const Position* GetMainTankPosition();
const Position* GetMainTankRangedPosition();
bool TryMoveToPosition(float targetX, float targetY, float targetZ, bool isForced = true);
};
class IccLichKingAddsAction : public AttackAction
{
public:
IccLichKingAddsAction(PlayerbotAI* botAI)
: AttackAction(botAI, "icc lich king adds") {}
bool Execute(Event event) override;
void HandleTeleportationFixes(Difficulty diff, Unit* terenasMenethilHC);
bool HandleSpiritBombAvoidance(Difficulty diff, Unit* terenasMenethilHC);
void HandleHeroicNonTankPositioning(Difficulty diff, Unit* terenasMenethilHC);
void HandleSpiritMarkingAndTargeting(Difficulty diff, Unit* terenasMenethilHC);
bool HandleQuakeMechanics(Unit* boss);
void HandleShamblingHorrors(Unit* boss, bool hasPlague);
bool HandleAssistTankAddManagement(Unit* boss, Difficulty diff);
void HandleMeleePositioning(Unit* boss, bool hasPlague, Difficulty diff);
void HandleMainTankTargeting(Unit* boss, Difficulty diff);
void HandleNonTankHeroicPositioning(Unit* boss, Difficulty diff, bool hasPlague);
void HandleRangedPositioning(Unit* boss, bool hasPlague, Difficulty diff);
void HandleDefileMechanics(Unit* boss, Difficulty diff);
void HandleValkyrMechanics(Difficulty diff);
std::vector<size_t> CalculateBalancedGroupSizes(size_t totalAssist, size_t numValkyrs);
size_t GetAssignedValkyrIndex(size_t assistIndex, const std::vector<size_t>& groupSizes);
std::string GetRTIValueForValkyr(size_t valkyrIndex);
void HandleValkyrMarking(const std::vector<Unit*>& grabbingValkyrs, Difficulty diff);
void HandleValkyrAssignment(const std::vector<Unit*>& grabbingValkyrs);
void ApplyCCToValkyr(Unit* valkyr);
bool IsValkyr(Unit* unit);
void HandleVileSpiritMechanics();
};
#endif

View File

@@ -0,0 +1,875 @@
#include "RaidIccMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "PriestActions.h"
#include "RaidIccActions.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ScriptedCreature.h"
#include "ShamanActions.h"
#include "UseMeetingStoneAction.h"
#include "WarriorActions.h"
#include "PlayerbotAI.h"
#include "RaidIccTriggers.h"
// LK global variables
namespace
{
uint32 g_lastPlagueTime = 0;
bool g_plagueAllowedToCure = false;
std::map<ObjectGuid, uint32> g_plagueTimes;
std::map<ObjectGuid, bool> g_allowCure;
std::mutex g_plagueMutex; // Lock before accessing shared variables
}
// Lady Deathwhisper
float IccLadyDeathwhisperMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper");
if (!boss)
return 1.0f;
if (dynamic_cast<FleeAction*>(action) || dynamic_cast<FollowAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
static constexpr uint32 VENGEFUL_SHADE_ID = NPC_SHADE;
// Get the nearest hostile NPCs
const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
// Allow the IccShadeLadyDeathwhisperAction to run
if (dynamic_cast<IccShadeLadyDeathwhisperAction*>(action))
return 1.0f;
for (auto const& npcGuid : npcs)
{
Unit* shade = botAI->GetUnit(npcGuid);
if (!shade || shade->GetEntry() != VENGEFUL_SHADE_ID)
continue;
if (!shade->GetVictim() || shade->GetVictim()->GetGUID() != bot->GetGUID())
continue;
return 0.0f; // Cancel all other actions when we need to handle Vengeful Shade
}
return 1.0f;
}
// dbs
float IccAddsDbsMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang");
if (!boss)
return 1.0f;
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastHurricaneAction*>(action) ||
dynamic_cast<CastVolleyAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (botAI->IsRanged(bot))
if (dynamic_cast<ReachSpellAction*>(action))
return 0.0f;
if (botAI->IsMainTank(bot))
{
Aura* aura = botAI->GetAura("rune of blood", bot);
if (aura)
{
if (dynamic_cast<MovementAction*>(action))
return 1.0f;
else
return 0.0f;
}
}
return 1.0f;
}
// dogs
float IccDogsMultiplier::GetValue(Action* action)
{
bool bossPresent = false;
if (AI_VALUE2(Unit*, "find target", "stinky") || AI_VALUE2(Unit*, "find target", "precious"))
bossPresent = true;
if (!bossPresent)
return 1.0f;
if (botAI->IsMainTank(bot))
{
Aura* aura = botAI->GetAura("mortal wound", bot, false, true);
if (aura && aura->GetStackAmount() >= 8)
{
if (dynamic_cast<MovementAction*>(action))
return 1.0f;
else
return 0.0f;
}
}
return 1.0f;
}
// Festergut
float IccFestergutMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
if (!boss)
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action))
return 0.0f;
if (dynamic_cast<FleeAction*>(action))
return 0.0f;
if (botAI->IsMainTank(bot))
{
Aura* aura = botAI->GetAura("gastric bloat", bot, false, true);
if (aura && aura->GetStackAmount() >= 6)
{
if (dynamic_cast<MovementAction*>(action))
return 1.0f;
else
return 0.0f;
}
}
if (dynamic_cast<IccFestergutSporeAction*>(action))
return 1.0f;
if (bot->HasAura(SPELL_GAS_SPORE))
return 0.0f;
return 1.0f;
}
// Rotface
float IccRotfaceMultiplier::GetValue(Action* action)
{
Unit* boss1 = AI_VALUE2(Unit*, "find target", "rotface");
if (!boss1)
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if (dynamic_cast<FleeAction*>(action) && !(bot->getClass() == CLASS_HUNTER))
return 0.0f;
if (dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
if (botAI->IsAssistTank(bot) && (dynamic_cast<AttackRtiTargetAction*>(action) || dynamic_cast<TankAssistAction*>(action)))
return 0.0f;
Unit* boss = AI_VALUE2(Unit*, "find target", "big ooze");
if (!boss)
return 1.0f;
static std::map<ObjectGuid, uint32> lastExplosionTimes;
static std::map<ObjectGuid, bool> hasMoved;
ObjectGuid botGuid = bot->GetGUID();
// When cast starts, record the time
if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION))
{
if (lastExplosionTimes[botGuid] == 0) // Only set if not already set
{
lastExplosionTimes[botGuid] = time(nullptr);
hasMoved[botGuid] = false;
}
}
// If explosion cast is no longer active, reset the timers
if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION))
{
if (lastExplosionTimes[botGuid] > 0 && time(nullptr) - lastExplosionTimes[botGuid] >= 16)
{
lastExplosionTimes[botGuid] = 0;
hasMoved[botGuid] = false;
return 1.0f; // Allow normal actions to resume
}
}
// If 9 seconds have passed since cast start and we haven't moved yet
if (lastExplosionTimes[botGuid] > 0 && !hasMoved[botGuid] && time(nullptr) - lastExplosionTimes[botGuid] >= 9)
{
if (dynamic_cast<MovementAction*>(action)
&& !dynamic_cast<IccRotfaceMoveAwayFromExplosionAction*>(action))
{
return 0.0f; // Block other movement actions
}
hasMoved[botGuid] = true; // Mark that we've initiated movement
}
// Continue blocking other movements for 7 seconds after moving
if (hasMoved[botGuid] && time(nullptr) - lastExplosionTimes[botGuid] < 16 // 9 seconds wait + 7 seconds stay
&& dynamic_cast<MovementAction*>(action)
&& !dynamic_cast<IccRotfaceMoveAwayFromExplosionAction*>(action))
return 0.0f;
return 1.0f;
}
// pp
float IccAddsPutricideMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide");
if (!boss)
return 1.0f;
bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot);
bool hasUnboundPlague = botAI->HasAura("Unbound Plague", bot);
if (!(bot->getClass() == CLASS_HUNTER) && dynamic_cast<FleeAction*>(action))
return 0.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if (dynamic_cast<CastDisengageAction*>(action))
return 0.0f;
if (dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
if (botAI->IsMainTank(bot))
{
Aura* aura = botAI->GetAura("mutated plague", bot, false, true);
if (aura && aura->GetStackAmount() >= 4)
{
if (dynamic_cast<MovementAction*>(action))
return 1.0f;
else
return 0.0f;
}
}
if (hasGaseousBloat)
{
if (dynamic_cast<IccPutricideGasCloudAction*>(action))
return 1.0f;
if (dynamic_cast<IccPutricideGrowingOozePuddleAction*>(action))
return 1.0f;
if (botAI->IsHeal(bot))
return 1.0f;
else
return 0.0f; // Cancel all other actions when we need to handle Gaseous Bloat
}
if (hasUnboundPlague && boss && !boss->HealthBelowPct(35))
{
if (dynamic_cast<IccPutricideAvoidMalleableGooAction*>(action))
return 1.0f;
else
return 0.0f; // Cancel all other actions when we need to handle Unbound Plague
}
if (dynamic_cast<IccPutricideVolatileOozeAction*>(action))
{
if (dynamic_cast<IccPutricideAvoidMalleableGooAction*>(action))
return 0.0f;
if (dynamic_cast<IccPutricideGrowingOozePuddleAction*>(action) && !botAI->IsMainTank(bot))
return 0.0f;
//if (dynamic_cast<IccPutricideGasCloudAction*>(action) && !hasGaseousBloat)
//return 0.0f;
}
return 1.0f;
}
// bpc
float IccBpcAssistMultiplier::GetValue(Action* action)
{
Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth");
if (!keleseth)
return 1.0f;
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastHurricaneAction*>(action) ||
dynamic_cast<CastVolleyAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action))
return 0.0f;
Aura* aura = botAI->GetAura("Shadow Prison", bot, false, true);
if (aura)
{
if (aura->GetStackAmount() > 18 && botAI->IsTank(bot))
{
if (dynamic_cast<MovementAction*>(action))
return 0.0f;
}
if (aura->GetStackAmount() > 12 && !botAI->IsTank(bot))
{
if (dynamic_cast<MovementAction*>(action))
return 0.0f;
}
}
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
if (!valanar)
return 1.0f;
if (valanar && valanar->HasUnitState(UNIT_STATE_CASTING) &&
(valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) ||
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) ||
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) ||
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4)))
{
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<IccBpcEmpoweredVortexAction*>(action))
return 1.0f;
else
return 0.0f; // Cancel all other actions when we need to handle Empowered Vortex
}
Unit* flame1 = bot->FindNearestCreature(NPC_BALL_OF_FLAME, 100.0f);
Unit* flame2 = bot->FindNearestCreature(NPC_BALL_OF_INFERNO_FLAME, 100.0f);
bool ballOfFlame = flame1 && flame1->GetVictim() == bot;
bool infernoFlame = flame2 && flame2->GetVictim() == bot;
if (flame2)
{
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<IccBpcKineticBombAction*>(action))
return 0.0f;
if (dynamic_cast<IccBpcBallOfFlameAction*>(action))
return 1.0f;
}
if (ballOfFlame || infernoFlame)
{
// If bot is tank, do nothing special
if (dynamic_cast<IccBpcBallOfFlameAction*>(action))
return 1.0f;
else
return 0.0f; // Cancel all other actions when we need to handle Ball of Flame
}
static const std::array<uint32, 4> bombEntries = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, NPC_KINETIC_BOMB3,
NPC_KINETIC_BOMB4};
const GuidVector bombs = AI_VALUE(GuidVector, "possible targets no los");
bool bombFound = false;
for (const auto entry : bombEntries)
{
for (auto const& guid : bombs)
{
if (Unit* unit = botAI->GetUnit(guid))
{
if (unit->GetEntry() == entry)
{
// Check if bomb is within valid Z-axis range
if (unit->GetPositionZ() - bot->GetPositionZ() < 25.0f)
{
bombFound = true;
break;
}
}
}
}
if (bombFound)
break;
}
if (bombFound && !(aura && aura->GetStackAmount() > 12) && !botAI->IsTank(bot))
{
// If kinetic bomb action is active, disable these actions
if (dynamic_cast<IccBpcKineticBombAction*>(action))
return 1.0f;
if (dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<AttackRtiTargetAction*>(action))
return 0.0f;
}
// For assist tank during BPC fight
if (botAI->IsAssistTank(bot) && !(aura && aura->GetStackAmount() > 18))
{
// Allow BPC-specific actions
if (dynamic_cast<IccBpcKelesethTankAction*>(action))
return 1.0f;
// Disable normal assist behavior
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<AttackRtiTargetAction*>(action) ||
dynamic_cast<CastConsecrationAction*>(action))
return 0.0f;
}
return 1.0f;
}
//BQL
float IccBqlMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel");
if (!boss)
return 1.0f;
Aura* aura2 = botAI->GetAura("Swarming Shadows", bot);
Aura* aura = botAI->GetAura("Frenzied Bloodthirst", bot);
if (botAI->IsRanged(bot))
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<CastDisengageAction*>(action))
return 0.0f;
// If bot has Pact of Darkfallen aura, return 0 for all other actions
if (bot->HasAura(SPELL_PACT_OF_THE_DARKFALLEN))
{
if (dynamic_cast<IccBqlPactOfDarkfallenAction*>(action))
return 1.0f; // Allow Pact of Darkfallen action
else
return 0.0f; // Cancel all other actions when we need to handle Pact of Darkfallen
}
if (botAI->IsMelee(bot) && ((boss->GetPositionZ() - ICC_BQL_CENTER_POSITION.GetPositionZ()) > 5.0f) && !aura)
{
if (dynamic_cast<IccBqlGroupPositionAction*>(action))
return 1.0f;
else
return 0.0f;
}
// If bot has frenzied bloodthirst, allow highest priority for bite action
if (aura) // If bot has frenzied bloodthirst
{
if (dynamic_cast<IccBqlVampiricBiteAction*>(action))
return 1.0f;
else
return 0.0f;
}
if (aura2 && !aura)
{
if (dynamic_cast<IccBqlGroupPositionAction*>(action))
return 1.0f;
else
return 0.0f; // Cancel all other actions when we need to handle Swarming Shadows
}
if ((boss->GetExactDist2d(ICC_BQL_TANK_POSITION.GetPositionX(), ICC_BQL_TANK_POSITION.GetPositionY()) > 10.0f) &&
botAI->IsRanged(bot) && !((boss->GetPositionZ() - bot->GetPositionZ()) > 5.0f))
{
if (dynamic_cast<FleeAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
return 1.0f;
}
//VDW
float IccValithriaDreamCloudMultiplier::GetValue(Action* action)
{
Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
Aura* twistedNightmares = botAI->GetAura("Twisted Nightmares", bot);
Aura* emeraldVigor = botAI->GetAura("Emerald Vigor", bot);
if (!boss && !bot->HasAura(SPELL_DREAM_STATE))
return 1.0f;
if (dynamic_cast<FollowAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if (botAI->IsTank(bot))
{
if (dynamic_cast<AttackRtiTargetAction*>(action))
return 0.0f;
}
if (botAI->IsHeal(bot) && (twistedNightmares || emeraldVigor))
if (dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<AttackRtiTargetAction*>(action))
return 0.0f;
if (bot->HasAura(SPELL_DREAM_STATE) && !bot->HealthBelowPct(50))
{
if (dynamic_cast<IccValithriaDreamCloudAction*>(action))
return 1.0f; // Allow Dream Cloud action
else
return 0.0f; // Cancel all other actions when we need to handle Dream Cloud
}
return 1.0f;
}
//SINDRAGOSA
float IccSindragosaMultiplier::GetValue(Action* action)
{
Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f);
if (!boss)
return 1.0f;
Aura* aura = botAI->GetAura("Unchained Magic", bot, false, true);
Difficulty diff = bot->GetRaidDifficulty();
if (boss->HealthBelowPct(95))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action) || dynamic_cast<CastStarfallAction*>(action))
return 0.0f;
}
if (aura && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC) &&
!dynamic_cast<IccSindragosaFrostBombAction*>(action))
{
if (dynamic_cast<MovementAction*>(action) || dynamic_cast<IccSindragosaUnchainedMagicAction*>(action))
return 1.0f;
else
return 0.0f;
}
// Check if boss is casting blistering cold (using both normal and heroic spell IDs)
if (boss->HasUnitState(UNIT_STATE_CASTING) &&
(boss->FindCurrentSpellBySpellId(70123) || boss->FindCurrentSpellBySpellId(71047) ||
boss->FindCurrentSpellBySpellId(71048) || boss->FindCurrentSpellBySpellId(71049)))
{
// If this is the blistering cold action, give it highest priority
if (dynamic_cast<IccSindragosaBlisteringColdAction*>(action) ||
dynamic_cast<HealPartyMemberAction*>(action) ||
dynamic_cast<ReachPartyMemberToHealAction*>(action) ||
dynamic_cast<IccSindragosaTankSwapPositionAction*>(action))
return 1.0f;
// Disable all other actions while blistering cold is casting
return 0.0f;
}
// Highest priority if we have beacon
if (bot->HasAura(SPELL_FROST_BEACON))
{
if (dynamic_cast<IccSindragosaFrostBeaconAction*>(action))
return 1.0f;
else
return 0.0f;
}
Group* group = bot->GetGroup();
// Check if anyone in group has Frost Beacon (SPELL_FROST_BEACON)
bool anyoneHasFrostBeacon = false;
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON))
{
anyoneHasFrostBeacon = true;
break;
}
}
}
if (anyoneHasFrostBeacon && boss &&
boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(),
ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f &&
!boss->HealthBelowPct(25) && !boss->HealthAbovePct(99))
{
if (dynamic_cast<IccSindragosaFrostBeaconAction*>(action))
return 1.0f;
else
return 0.0f;
}
if (anyoneHasFrostBeacon && !botAI->IsMainTank(bot))
{
if (dynamic_cast<IccSindragosaGroupPositionAction*>(action))
return 0.0f;
}
if (botAI->IsMainTank(bot))
{
Aura* aura = botAI->GetAura("mystic buffet", bot, false, true);
if (aura && aura->GetStackAmount() >= 6)
{
if (dynamic_cast<MovementAction*>(action))
return 1.0f;
else
return 0.0f;
}
}
if (!botAI->IsTank(bot) && boss && boss->HealthBelowPct(35))
{
if (dynamic_cast<IccSindragosaGroupPositionAction*>(action))
return 0.0f;
}
if (boss && botAI->IsTank(bot))
{
if (boss->HealthBelowPct(35))
{
if (dynamic_cast<IccSindragosaTankSwapPositionAction*>(action) || dynamic_cast<TankFaceAction*>(action) ||
dynamic_cast<AttackAction*>(action) || dynamic_cast<MovementAction*>(action))
return 1.0f;
else
return 0.0f;
}
}
if (boss && boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f && !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99))
{
if (dynamic_cast<IccSindragosaFrostBombAction*>(action))
return 1.0f;
if (dynamic_cast<FollowAction*>(action) || dynamic_cast<IccSindragosaBlisteringColdAction*>(action) ||
dynamic_cast<IccSindragosaChilledToTheBoneAction*>(action) || dynamic_cast<IccSindragosaMysticBuffetAction*>(action) ||
dynamic_cast<IccSindragosaFrostBeaconAction*>(action) || dynamic_cast<IccSindragosaUnchainedMagicAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<PetAttackAction*>(action) ||
dynamic_cast<IccSindragosaGroupPositionAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastHurricaneAction*>(action) ||
dynamic_cast<CastVolleyAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastConsecrationAction*>(action) ||
dynamic_cast<CastFlamestrikeAction*>(action) || dynamic_cast<CastExplosiveTrapAction*>(action) ||
dynamic_cast<CastExplosiveShotAction*>(action))
return 0.0f;
}
return 1.0f;
}
float IccLichKingAddsMultiplier::GetValue(Action* action)
{
Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f);
if (!terenasMenethilHC)
if (dynamic_cast<CastStarfallAction*>(action))
return 0.0f;
if (terenasMenethilHC)
{
Unit* mainTank = AI_VALUE(Unit*, "main tank");
if (!botAI->IsMainTank(bot) && mainTank && bot->GetExactDist2d(mainTank->GetPositionX(), mainTank->GetPositionY()) < 2.0f)
{
if (dynamic_cast<MovementAction*>(action))
return 0.0f;
}
if (botAI->IsMelee(bot) || (bot->getClass() == CLASS_WARLOCK))
{
if (dynamic_cast<MovementAction*>(action) || dynamic_cast<IccLichKingAddsAction*>(action))
return 1.0f;
else
return 0.0f;
}
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<CastChargeAction*>(action) ||
dynamic_cast<CastFeralChargeBearAction*>(action) || dynamic_cast<CastIceBlockAction*>(action) ||
dynamic_cast<CastRevivePetAction*>(action) || dynamic_cast<TankAssistAction*>(action))
return 0.0f;
}
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
if (!boss)
return 1.0f;
// Handle cure actions
if (dynamic_cast<CurePartyMemberAction*>(action) || dynamic_cast<CastCleanseDiseaseAction*>(action) ||
dynamic_cast<CastCleanseDiseaseOnPartyAction*>(action) ||
dynamic_cast<CastCleanseSpiritCurseOnPartyAction*>(action) || dynamic_cast<CastCleanseSpiritAction*>(action))
{
Group* group = bot->GetGroup();
if (!group)
return 1.0f;
// Check if any bot in the group has plague
bool anyBotHasPlague = false;
ObjectGuid plaguedPlayerGuid; // Track who has plague
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (Player* member = ref->GetSource())
{
if (botAI->HasAura("Necrotic Plague", member))
{
anyBotHasPlague = true;
plaguedPlayerGuid = member->GetGUID(); // Changed from GetObjectGuid()
break;
}
}
}
uint32 currentTime = getMSTime();
// Reset state if no one has plague
if (!anyBotHasPlague)
{
std::lock_guard<std::mutex> lock(g_plagueMutex); // Properly initialized
g_plagueTimes.clear();
g_allowCure.clear();
return 1.0f;
}
{ // New scope for lock_guard
std::lock_guard<std::mutex> lock(g_plagueMutex); // Properly initialized
// Start timer if this is a new plague
if (g_plagueTimes.find(plaguedPlayerGuid) == g_plagueTimes.end())
{
g_plagueTimes[plaguedPlayerGuid] = currentTime;
g_allowCure[plaguedPlayerGuid] = false;
return 0.0f;
}
// Once we allow cure, keep allowing it until plague is gone
if (g_allowCure[plaguedPlayerGuid])
{
return 1.0f;
}
// Check if enough time has passed (2,5 seconds)
if (currentTime - g_plagueTimes[plaguedPlayerGuid] >= 2500)
{
g_allowCure[plaguedPlayerGuid] = true;
return 1.0f;
}
} // lock_guard is automatically released here
return 0.0f;
}
if (dynamic_cast<FleeAction*>(action) && (bot->getClass() != CLASS_HUNTER))
return 0.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) || dynamic_cast<CastDisengageAction*>(action))
return 0.0f;
if (boss && !boss->HealthBelowPct(71))
{
if (!botAI->IsTank(bot))
if (dynamic_cast<CastConsecrationAction*>(action))
return 0.0f;
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastHurricaneAction*>(action) ||
dynamic_cast<CastVolleyAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastFlamestrikeAction*>(action) ||
dynamic_cast<CastExplosiveTrapAction*>(action) || dynamic_cast<CastExplosiveShotAction*>(action))
return 0.0f;
}
Unit* currentTarget = AI_VALUE(Unit*, "current target");
bool hasWinterAura = false;
if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) ||
boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4)))
hasWinterAura = true;
bool hasWinter2Aura = false;
if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) ||
boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8)))
hasWinter2Aura = true;
bool isCasting = false;
if (boss && boss->HasUnitState(UNIT_STATE_CASTING))
isCasting = true;
bool isWinter = false;
if (boss && (boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) ||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) ||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) ||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) ||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) ||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) ||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) ||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8)))
isWinter = true;
if (hasWinterAura || hasWinter2Aura || (isCasting && isWinter))
{
if (dynamic_cast<IccLichKingWinterAction*>(action) || dynamic_cast<SetFacingTargetAction*>(action))
return 1.0f;
if (botAI->IsAssistTank(bot) && dynamic_cast<TankAssistAction*>(action))
return 0.0f;
if (dynamic_cast<IccLichKingAddsAction*>(action))
return 0.0f;
if (currentTarget && boss && bot->GetDistance2d(boss->GetPositionX(), boss->GetPositionY()) > 50.0f && currentTarget == boss)
{
if (dynamic_cast<AttackRtiTargetAction*>(action) || dynamic_cast<ReachSpellAction*>(action) ||
dynamic_cast<ReachMeleeAction*>(action) || dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<TankAssistAction*>(action) || dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<MovementAction*>(action))
return 0.0f;
}
if (currentTarget && (currentTarget->GetEntry() == NPC_ICE_SPHERE1 || currentTarget->GetEntry() == NPC_ICE_SPHERE2 ||
currentTarget->GetEntry() == NPC_ICE_SPHERE3 || currentTarget->GetEntry() == NPC_ICE_SPHERE4))
{
if (dynamic_cast<MovementAction*>(action) || dynamic_cast<ReachMeleeAction*>(action) ||
dynamic_cast<TankAssistAction*>(action))
return 0.0f;
}
}
if (botAI->IsRanged(bot) && !botAI->GetAura("Harvest Soul", bot, false, false))
{
// Check for defile presence
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
bool defilePresent = false;
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->IsAlive() && unit->GetEntry() == DEFILE_NPC_ID) // Defile entry
{
defilePresent = true;
break;
}
}
// Only disable movement if defile is present
if (defilePresent && (
dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<MoveRandomAction*>(action) ||
dynamic_cast<MoveFromGroupAction*>(action)))
{
return 0.0f;
}
}
if (botAI->IsAssistTank(bot) && boss && !boss->HealthBelowPct(71) && currentTarget == boss)
{
if (dynamic_cast<AttackRtiTargetAction*>(action))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,102 @@
#ifndef _PLAYERBOT_RAIDICCMULTIPLIERS_H
#define _PLAYERBOT_RAIDICCMULTIPLIERS_H
#include "Multiplier.h"
//Lady Deathwhisper
class IccLadyDeathwhisperMultiplier : public Multiplier
{
public:
IccLadyDeathwhisperMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc lady deathwhisper") {}
virtual float GetValue(Action* action);
};
//DBS
class IccAddsDbsMultiplier : public Multiplier
{
public:
IccAddsDbsMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc adds dbs") {}
virtual float GetValue(Action* action);
};
//DOGS
class IccDogsMultiplier : public Multiplier
{
public:
IccDogsMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc dogs") {}
virtual float GetValue(Action* action);
};
//FESTERGUT
class IccFestergutMultiplier : public Multiplier
{
public:
IccFestergutMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc festergut") {}
virtual float GetValue(Action* action);
};
//ROTFACE
class IccRotfaceMultiplier : public Multiplier
{
public:
IccRotfaceMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc rotface") {}
virtual float GetValue(Action* action);
};
/*class IccRotfaceGroupPositionMultiplier : public Multiplier
{
public:
IccRotfaceGroupPositionMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc rotface group position") {}
virtual float GetValue(Action* action);
};*/
//PP
class IccAddsPutricideMultiplier : public Multiplier
{
public:
IccAddsPutricideMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc adds putricide") {}
virtual float GetValue(Action* action);
};
//BPC
class IccBpcAssistMultiplier : public Multiplier
{
public:
IccBpcAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "icc bpc assist") {}
virtual float GetValue(Action* action);
};
//BQL
class IccBqlMultiplier : public Multiplier
{
public:
IccBqlMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "icc bql multiplier") {}
virtual float GetValue(Action* action) override;
};
//VDW
class IccValithriaDreamCloudMultiplier : public Multiplier
{
public:
IccValithriaDreamCloudMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc valithria dream cloud") {}
virtual float GetValue(Action* action);
};
//SINDRAGOSA
class IccSindragosaMultiplier : public Multiplier
{
public:
IccSindragosaMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc sindragosa") {}
virtual float GetValue(Action* action);
};
//LK
class IccLichKingAddsMultiplier : public Multiplier
{
public:
IccLichKingAddsMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc lich king adds") {}
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,139 @@
#ifndef _PLAYERBOT_RAIDICCACTIONCONTEXT_H
#define _PLAYERBOT_RAIDICCACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidIccActions.h"
class RaidIccActionContext : public NamedObjectContext<Action>
{
public:
RaidIccActionContext()
{
creators["icc lm tank position"] = &RaidIccActionContext::icc_lm_tank_position;
creators["icc spike"] = &RaidIccActionContext::icc_spike;
creators["icc dark reckoning"] = &RaidIccActionContext::icc_dark_reckoning;
creators["icc ranged position lady deathwhisper"] = &RaidIccActionContext::icc_ranged_position_lady_deathwhisper;
creators["icc adds lady deathwhisper"] = &RaidIccActionContext::icc_adds_lady_deathwhisper;
creators["icc shade lady deathwhisper"] = &RaidIccActionContext::icc_shade_lady_deathwhisper;
creators["icc rotting frost giant tank position"] = &RaidIccActionContext::icc_rotting_frost_giant_tank_position;
creators["icc cannon fire"] = &RaidIccActionContext::icc_cannon_fire;
creators["icc gunship enter cannon"] = &RaidIccActionContext::icc_gunship_enter_cannon;
creators["icc gunship teleport ally"] = &RaidIccActionContext::icc_gunship_teleport_ally;
creators["icc gunship teleport horde"] = &RaidIccActionContext::icc_gunship_teleport_horde;
creators["icc dbs tank position"] = &RaidIccActionContext::icc_dbs_tank_position;
creators["icc adds dbs"] = &RaidIccActionContext::icc_adds_dbs;
creators["icc festergut group position"] = &RaidIccActionContext::icc_festergut_group_position;
creators["icc festergut spore"] = &RaidIccActionContext::icc_festergut_spore;
creators["icc rotface tank position"] = &RaidIccActionContext::icc_rotface_tank_position;
creators["icc rotface group position"] = &RaidIccActionContext::icc_rotface_group_position;
creators["icc rotface move away from explosion"] = &RaidIccActionContext::icc_rotface_move_away_from_explosion;
creators["icc putricide volatile ooze"] = &RaidIccActionContext::icc_putricide_volatile_ooze;
creators["icc putricide gas cloud"] = &RaidIccActionContext::icc_putricide_gas_cloud;
creators["icc putricide growing ooze puddle"] = &RaidIccActionContext::icc_putricide_growing_ooze_puddle;
creators["icc putricide avoid malleable goo"] = &RaidIccActionContext::icc_putricide_avoid_malleable_goo;
creators["icc bpc keleseth tank"] = &RaidIccActionContext::icc_bpc_keleseth_tank;
creators["icc bpc main tank"] = &RaidIccActionContext::icc_bpc_main_tank;
creators["icc bpc empowered vortex"] = &RaidIccActionContext::icc_bpc_empowered_vortex;
creators["icc bpc kinetic bomb"] = &RaidIccActionContext::icc_bpc_kinetic_bomb;
creators["icc bpc ball of flame"] = &RaidIccActionContext::icc_bpc_ball_of_flame;
creators["icc bql group position"] = &RaidIccActionContext::icc_bql_group_position;
creators["icc bql pact of darkfallen"] = &RaidIccActionContext::icc_bql_pact_of_darkfallen;
creators["icc bql vampiric bite"] = &RaidIccActionContext::icc_bql_vampiric_bite;
creators["icc valkyre spear"] = &RaidIccActionContext::icc_valkyre_spear;
creators["icc sister svalna"] = &RaidIccActionContext::icc_sister_svalna;
creators["icc valithria group"] = &RaidIccActionContext::icc_valithria_group;
creators["icc valithria portal"] = &RaidIccActionContext::icc_valithria_portal;
creators["icc valithria heal"] = &RaidIccActionContext::icc_valithria_heal;
creators["icc valithria dream cloud"] = &RaidIccActionContext::icc_valithria_dream_cloud;
creators["icc sindragosa group position"] = &RaidIccActionContext::icc_sindragosa_group_position;
creators["icc sindragosa frost beacon"] = &RaidIccActionContext::icc_sindragosa_frost_beacon;
creators["icc sindragosa blistering cold"] = &RaidIccActionContext::icc_sindragosa_blistering_cold;
creators["icc sindragosa unchained magic"] = &RaidIccActionContext::icc_sindragosa_unchained_magic;
creators["icc sindragosa chilled to the bone"] = &RaidIccActionContext::icc_sindragosa_chilled_to_the_bone;
creators["icc sindragosa mystic buffet"] = &RaidIccActionContext::icc_sindragosa_mystic_buffet;
creators["icc sindragosa frost bomb"] = &RaidIccActionContext::icc_sindragosa_frost_bomb;
creators["icc sindragosa tank swap position"] = &RaidIccActionContext::icc_sindragosa_tank_swap_position;
creators["icc lich king shadow trap"] = &RaidIccActionContext::icc_lich_king_shadow_trap;
creators["icc lich king necrotic plague"] = &RaidIccActionContext::icc_lich_king_necrotic_plague;
creators["icc lich king winter"] = &RaidIccActionContext::icc_lich_king_winter;
creators["icc lich king adds"] = &RaidIccActionContext::icc_lich_king_adds;
}
private:
static Action* icc_lm_tank_position(PlayerbotAI* ai) { return new IccLmTankPositionAction(ai); }
static Action* icc_spike(PlayerbotAI* ai) { return new IccSpikeAction(ai); }
static Action* icc_dark_reckoning(PlayerbotAI* ai) { return new IccDarkReckoningAction(ai); }
static Action* icc_ranged_position_lady_deathwhisper(PlayerbotAI* ai) { return new IccRangedPositionLadyDeathwhisperAction(ai); }
static Action* icc_adds_lady_deathwhisper(PlayerbotAI* ai) { return new IccAddsLadyDeathwhisperAction(ai); }
static Action* icc_shade_lady_deathwhisper(PlayerbotAI* ai) { return new IccShadeLadyDeathwhisperAction(ai); }
static Action* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionAction(ai); }
static Action* icc_cannon_fire(PlayerbotAI* ai) { return new IccCannonFireAction(ai); }
static Action* icc_gunship_enter_cannon(PlayerbotAI* ai) { return new IccGunshipEnterCannonAction(ai); }
static Action* icc_gunship_teleport_ally(PlayerbotAI* ai) { return new IccGunshipTeleportAllyAction(ai); }
static Action* icc_gunship_teleport_horde(PlayerbotAI* ai) { return new IccGunshipTeleportHordeAction(ai); }
static Action* icc_dbs_tank_position(PlayerbotAI* ai) { return new IccDbsTankPositionAction(ai); }
static Action* icc_adds_dbs(PlayerbotAI* ai) { return new IccAddsDbsAction(ai); }
static Action* icc_festergut_group_position(PlayerbotAI* ai) { return new IccFestergutGroupPositionAction(ai); }
static Action* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeAction(ai); }
static Action* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionAction(ai); }
static Action* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionAction(ai); }
static Action* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionAction(ai); }
static Action* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeAction(ai); }
static Action* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudAction(ai); }
static Action* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleAction(ai); }
static Action* icc_putricide_avoid_malleable_goo(PlayerbotAI* ai) { return new IccPutricideAvoidMalleableGooAction(ai); }
static Action* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankAction(ai); }
static Action* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankAction(ai); }
static Action* icc_bpc_empowered_vortex(PlayerbotAI* ai) { return new IccBpcEmpoweredVortexAction(ai); }
static Action* icc_bpc_kinetic_bomb(PlayerbotAI* ai) { return new IccBpcKineticBombAction(ai); }
static Action* icc_bpc_ball_of_flame(PlayerbotAI* ai) { return new IccBpcBallOfFlameAction(ai); }
static Action* icc_bql_group_position(PlayerbotAI* ai) { return new IccBqlGroupPositionAction(ai); }
static Action* icc_bql_pact_of_darkfallen(PlayerbotAI* ai) { return new IccBqlPactOfDarkfallenAction(ai); }
static Action* icc_bql_vampiric_bite(PlayerbotAI* ai) { return new IccBqlVampiricBiteAction(ai); }
static Action* icc_valkyre_spear(PlayerbotAI* ai) { return new IccValkyreSpearAction(ai); }
static Action* icc_sister_svalna(PlayerbotAI* ai) { return new IccSisterSvalnaAction(ai); }
static Action* icc_valithria_group(PlayerbotAI* ai) { return new IccValithriaGroupAction(ai); }
static Action* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalAction(ai); }
static Action* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealAction(ai); }
static Action* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudAction(ai); }
static Action* icc_sindragosa_group_position(PlayerbotAI* ai) { return new IccSindragosaGroupPositionAction(ai); }
static Action* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconAction(ai); }
static Action* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdAction(ai); }
static Action* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicAction(ai); }
static Action* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneAction(ai); }
static Action* icc_sindragosa_mystic_buffet(PlayerbotAI* ai) { return new IccSindragosaMysticBuffetAction(ai); }
static Action* icc_sindragosa_frost_bomb(PlayerbotAI* ai) { return new IccSindragosaFrostBombAction(ai); }
static Action* icc_sindragosa_tank_swap_position(PlayerbotAI* ai) { return new IccSindragosaTankSwapPositionAction(ai); }
static Action* icc_lich_king_shadow_trap(PlayerbotAI* ai) { return new IccLichKingShadowTrapAction(ai); }
static Action* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueAction(ai); }
static Action* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterAction(ai); }
static Action* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsAction(ai); }
};
#endif

View File

@@ -0,0 +1,6 @@
#ifndef _PLAYERBOT_RAIDICCSCRIPTS_H
#define _PLAYERBOT_RAIDICCSCRIPTS_H
#include "../../../../src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h"
#endif

View File

@@ -0,0 +1,143 @@
#ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidIccTriggers.h"
class RaidIccTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidIccTriggerContext()
{
creators["icc lm"] = &RaidIccTriggerContext::icc_lm;
creators["icc dark reckoning"] = &RaidIccTriggerContext::icc_dark_reckoning;
creators["icc lady deathwhisper"] = &RaidIccTriggerContext::icc_lady_deathwhisper;
creators["icc rotting frost giant tank position"] = &RaidIccTriggerContext::icc_rotting_frost_giant_tank_position;
creators["icc in cannon"] = &RaidIccTriggerContext::icc_in_cannon;
creators["icc gunship cannon near"] = &RaidIccTriggerContext::icc_gunship_cannon_near;
creators["icc gunship teleport ally"] = &RaidIccTriggerContext::icc_gunship_teleport_ally;
creators["icc gunship teleport horde"] = &RaidIccTriggerContext::icc_gunship_teleport_horde;
creators["icc dbs"] = &RaidIccTriggerContext::icc_dbs;
creators["icc dbs main tank rune of blood"] = &RaidIccTriggerContext::icc_dbs_main_tank_rune_of_blood;
creators["icc stinky precious main tank mortal wound"] = &RaidIccTriggerContext::icc_stinky_precious_main_tank_mortal_wound;
creators["icc festergut group position"] = &RaidIccTriggerContext::icc_festergut_group_position;
creators["icc festergut main tank gastric bloat"] = &RaidIccTriggerContext::icc_festergut_main_tank_gastric_bloat;
creators["icc festergut spore"] = &RaidIccTriggerContext::icc_festergut_spore;
creators["icc rotface tank position"] = &RaidIccTriggerContext::icc_rotface_tank_position;
creators["icc rotface group position"] = &RaidIccTriggerContext::icc_rotface_group_position;
creators["icc rotface move away from explosion"] = &RaidIccTriggerContext::icc_rotface_move_away_from_explosion;
creators["icc putricide volatile ooze"] = &RaidIccTriggerContext::icc_putricide_volatile_ooze;
creators["icc putricide gas cloud"] = &RaidIccTriggerContext::icc_putricide_gas_cloud;
creators["icc putricide growing ooze puddle"] = &RaidIccTriggerContext::icc_putricide_growing_ooze_puddle;
creators["icc putricide main tank mutated plague"] = &RaidIccTriggerContext::icc_putricide_main_tank_mutated_plague;
creators["icc putricide malleable goo"] = &RaidIccTriggerContext::icc_putricide_malleable_goo;
creators["icc bpc keleseth tank"] = &RaidIccTriggerContext::icc_bpc_keleseth_tank;
creators["icc bpc main tank"] = &RaidIccTriggerContext::icc_bpc_main_tank;
creators["icc bpc empowered vortex"] = &RaidIccTriggerContext::icc_bpc_empowered_vortex;
creators["icc bpc kinetic bomb"] = &RaidIccTriggerContext::icc_bpc_kinetic_bomb;
creators["icc bpc ball of flame"] = &RaidIccTriggerContext::icc_bpc_ball_of_flame;
creators["icc bql group position"] = &RaidIccTriggerContext::icc_bql_group_position;
creators["icc bql pact of darkfallen"] = &RaidIccTriggerContext::icc_bql_pact_of_darkfallen;
creators["icc bql vampiric bite"] = &RaidIccTriggerContext::icc_bql_vampiric_bite;
creators["icc valkyre spear"] = &RaidIccTriggerContext::icc_valkyre_spear;
creators["icc sister svalna"] = &RaidIccTriggerContext::icc_sister_svalna;
creators["icc valithria group"] = &RaidIccTriggerContext::icc_valithria_group;
creators["icc valithria portal"] = &RaidIccTriggerContext::icc_valithria_portal;
creators["icc valithria heal"] = &RaidIccTriggerContext::icc_valithria_heal;
creators["icc valithria dream cloud"] = &RaidIccTriggerContext::icc_valithria_dream_cloud;
creators["icc sindragosa group position"] = &RaidIccTriggerContext::icc_sindragosa_group_position;
creators["icc sindragosa frost beacon"] = &RaidIccTriggerContext::icc_sindragosa_frost_beacon;
creators["icc sindragosa blistering cold"] = &RaidIccTriggerContext::icc_sindragosa_blistering_cold;
creators["icc sindragosa unchained magic"] = &RaidIccTriggerContext::icc_sindragosa_unchained_magic;
creators["icc sindragosa chilled to the bone"] = &RaidIccTriggerContext::icc_sindragosa_chilled_to_the_bone;
creators["icc sindragosa mystic buffet"] = &RaidIccTriggerContext::icc_sindragosa_mystic_buffet;
creators["icc sindragosa main tank mystic buffet"] = &RaidIccTriggerContext::icc_sindragosa_main_tank_mystic_buffet;
creators["icc sindragosa frost bomb"] = &RaidIccTriggerContext::icc_sindragosa_frost_bomb;
creators["icc sindragosa tank swap position"] = &RaidIccTriggerContext::icc_sindragosa_tank_swap_position;
creators["icc lich king shadow trap"] = &RaidIccTriggerContext::icc_lich_king_shadow_trap;
creators["icc lich king necrotic plague"] = &RaidIccTriggerContext::icc_lich_king_necrotic_plague;
creators["icc lich king winter"] = &RaidIccTriggerContext::icc_lich_king_winter;
creators["icc lich king adds"] = &RaidIccTriggerContext::icc_lich_king_adds;
}
private:
static Trigger* icc_lm(PlayerbotAI* ai) { return new IccLmTrigger(ai); }
static Trigger* icc_dark_reckoning(PlayerbotAI* ai) { return new IccDarkReckoningTrigger(ai); }
static Trigger* icc_lady_deathwhisper(PlayerbotAI* ai) { return new IccLadyDeathwhisperTrigger(ai); }
static Trigger* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionTrigger(ai); }
static Trigger* icc_in_cannon(PlayerbotAI* ai) { return new IccInCannonTrigger(ai); }
static Trigger* icc_gunship_cannon_near(PlayerbotAI* ai) { return new IccGunshipCannonNearTrigger(ai); }
static Trigger* icc_gunship_teleport_ally(PlayerbotAI* ai) { return new IccGunshipTeleportAllyTrigger(ai); }
static Trigger* icc_gunship_teleport_horde(PlayerbotAI* ai) { return new IccGunshipTeleportHordeTrigger(ai); }
static Trigger* icc_dbs(PlayerbotAI* ai) { return new IccDbsTrigger(ai); }
static Trigger* icc_dbs_main_tank_rune_of_blood(PlayerbotAI* ai) { return new IccDbsMainTankRuneOfBloodTrigger(ai); }
static Trigger* icc_stinky_precious_main_tank_mortal_wound(PlayerbotAI* ai) { return new IccStinkyPreciousMainTankMortalWoundTrigger(ai); }
static Trigger* icc_festergut_group_position(PlayerbotAI* ai) { return new IccFestergutGroupPositionTrigger(ai); }
static Trigger* icc_festergut_main_tank_gastric_bloat(PlayerbotAI* ai) { return new IccFestergutMainTankGastricBloatTrigger(ai); }
static Trigger* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeTrigger(ai); }
static Trigger* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionTrigger(ai); }
static Trigger* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionTrigger(ai); }
static Trigger* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionTrigger(ai); }
static Trigger* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeTrigger(ai); }
static Trigger* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudTrigger(ai); }
static Trigger* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleTrigger(ai); }
static Trigger* icc_putricide_main_tank_mutated_plague(PlayerbotAI* ai) { return new IccPutricideMainTankMutatedPlagueTrigger(ai); }
static Trigger* icc_putricide_malleable_goo(PlayerbotAI* ai) { return new IccPutricideMalleableGooTrigger(ai); }
static Trigger* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankTrigger(ai); }
static Trigger* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankTrigger(ai); }
static Trigger* icc_bpc_empowered_vortex(PlayerbotAI* ai) { return new IccBpcEmpoweredVortexTrigger(ai); }
static Trigger* icc_bpc_kinetic_bomb(PlayerbotAI* ai) { return new IccBpcKineticBombTrigger(ai); }
static Trigger* icc_bpc_ball_of_flame(PlayerbotAI* ai) { return new IccBpcBallOfFlameTrigger(ai); }
static Trigger* icc_bql_group_position(PlayerbotAI* ai) { return new IccBqlGroupPositionTrigger(ai); }
static Trigger* icc_bql_pact_of_darkfallen(PlayerbotAI* ai) { return new IccBqlPactOfDarkfallenTrigger(ai); }
static Trigger* icc_bql_vampiric_bite(PlayerbotAI* ai) { return new IccBqlVampiricBiteTrigger(ai); }
static Trigger* icc_valkyre_spear(PlayerbotAI* ai) { return new IccValkyreSpearTrigger(ai); }
static Trigger* icc_sister_svalna(PlayerbotAI* ai) { return new IccSisterSvalnaTrigger(ai); }
static Trigger* icc_valithria_group(PlayerbotAI* ai) { return new IccValithriaGroupTrigger(ai); }
static Trigger* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalTrigger(ai); }
static Trigger* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealTrigger(ai); }
static Trigger* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudTrigger(ai); }
static Trigger* icc_sindragosa_group_position(PlayerbotAI* ai) { return new IccSindragosaGroupPositionTrigger(ai); }
static Trigger* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconTrigger(ai); }
static Trigger* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdTrigger(ai); }
static Trigger* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicTrigger(ai); }
static Trigger* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneTrigger(ai); }
static Trigger* icc_sindragosa_mystic_buffet(PlayerbotAI* ai) { return new IccSindragosaMysticBuffetTrigger(ai); }
static Trigger* icc_sindragosa_main_tank_mystic_buffet(PlayerbotAI* ai) { return new IccSindragosaMainTankMysticBuffetTrigger(ai); }
static Trigger* icc_sindragosa_frost_bomb(PlayerbotAI* ai) { return new IccSindragosaFrostBombTrigger(ai); }
static Trigger* icc_sindragosa_tank_swap_position(PlayerbotAI* ai) { return new IccSindragosaTankSwapPositionTrigger(ai); }
static Trigger* icc_lich_king_shadow_trap(PlayerbotAI* ai) { return new IccLichKingShadowTrapTrigger(ai); }
static Trigger* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueTrigger(ai); }
static Trigger* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterTrigger(ai); }
static Trigger* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,186 @@
#include "RaidIccStrategy.h"
#include "RaidIccMultipliers.h"
void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
//Lord Marrogwar
triggers.push_back(new TriggerNode("icc lm",
{ NextAction("icc lm tank position", ACTION_RAID + 5),
NextAction("icc spike", ACTION_RAID + 3) }));
//Lady Deathwhisper
triggers.push_back(new TriggerNode("icc dark reckoning",
{ NextAction("icc dark reckoning", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("icc lady deathwhisper",
{ NextAction("icc ranged position lady deathwhisper", ACTION_MOVE + 2),
NextAction("icc adds lady deathwhisper", ACTION_RAID + 3),
NextAction("icc shade lady deathwhisper", ACTION_RAID + 4) }));
//Gunship Battle
triggers.push_back(new TriggerNode("icc rotting frost giant tank position",
{ NextAction("icc rotting frost giant tank position", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("icc gunship cannon near",
{ NextAction("icc gunship enter cannon", ACTION_RAID + 6) }));
triggers.push_back( new TriggerNode("icc in cannon",
{ NextAction("icc cannon fire", ACTION_RAID+5) }));
triggers.push_back(new TriggerNode("icc gunship teleport ally",
{ NextAction("icc gunship teleport ally", ACTION_RAID + 4) }));
triggers.push_back(new TriggerNode("icc gunship teleport horde",
{ NextAction("icc gunship teleport horde", ACTION_RAID + 4) }));
//DBS
triggers.push_back(new TriggerNode("icc dbs",
{ NextAction("icc dbs tank position", ACTION_RAID + 3),
NextAction("icc adds dbs", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("icc dbs main tank rune of blood",
{ NextAction("taunt spell", ACTION_EMERGENCY + 4) }));
//DOGS
triggers.push_back(new TriggerNode("icc stinky precious main tank mortal wound",
{ NextAction("taunt spell", ACTION_EMERGENCY + 4) }));
//FESTERGUT
triggers.push_back(new TriggerNode("icc festergut group position",
{ NextAction("icc festergut group position", ACTION_MOVE + 4) }));
triggers.push_back(new TriggerNode("icc festergut main tank gastric bloat",
{ NextAction("taunt spell", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("icc festergut spore",
{ NextAction("icc festergut spore", ACTION_MOVE + 5) }));
//ROTFACE
triggers.push_back(new TriggerNode("icc rotface tank position",
{ NextAction("icc rotface tank position", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("icc rotface group position",
{ NextAction("icc rotface group position", ACTION_RAID + 6) }));
triggers.push_back(new TriggerNode("icc rotface move away from explosion",
{ NextAction("icc rotface move away from explosion", ACTION_RAID +7) }));
//PP
triggers.push_back(new TriggerNode("icc putricide volatile ooze",
{ NextAction("icc putricide volatile ooze", ACTION_RAID + 4) }));
triggers.push_back(new TriggerNode("icc putricide gas cloud",
{ NextAction("icc putricide gas cloud", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("icc putricide growing ooze puddle",
{ NextAction("icc putricide growing ooze puddle", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("icc putricide main tank mutated plague",
{ NextAction("taunt spell", ACTION_RAID + 10) }));
triggers.push_back(new TriggerNode("icc putricide malleable goo",
{ NextAction("icc putricide avoid malleable goo", ACTION_RAID + 2) }));
//BPC
triggers.push_back(new TriggerNode("icc bpc keleseth tank",
{ NextAction("icc bpc keleseth tank", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("icc bpc main tank",
{ NextAction("icc bpc main tank", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("icc bpc empowered vortex",
{ NextAction("icc bpc empowered vortex", ACTION_RAID + 4) }));
triggers.push_back(new TriggerNode("icc bpc kinetic bomb",
{ NextAction("icc bpc kinetic bomb", ACTION_RAID + 6) }));
triggers.push_back(new TriggerNode("icc bpc ball of flame",
{ NextAction("icc bpc ball of flame", ACTION_RAID + 7) }));
//BQL
triggers.push_back(new TriggerNode("icc bql group position",
{ NextAction("icc bql group position", ACTION_RAID) }));
triggers.push_back(new TriggerNode("icc bql pact of darkfallen",
{ NextAction("icc bql pact of darkfallen", ACTION_RAID +1) }));
triggers.push_back(new TriggerNode("icc bql vampiric bite",
{ NextAction("icc bql vampiric bite", ACTION_EMERGENCY + 5) }));
//Sister Svalna
triggers.push_back(new TriggerNode("icc valkyre spear",
{ NextAction("icc valkyre spear", ACTION_EMERGENCY + 5) }));
triggers.push_back(new TriggerNode("icc sister svalna",
{ NextAction("icc sister svalna", ACTION_RAID + 5) }));
//VDW
triggers.push_back(new TriggerNode("icc valithria group",
{ NextAction("icc valithria group", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("icc valithria portal",
{ NextAction("icc valithria portal", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("icc valithria heal",
{ NextAction("icc valithria heal", ACTION_RAID+2) }));
triggers.push_back(new TriggerNode("icc valithria dream cloud",
{ NextAction("icc valithria dream cloud", ACTION_RAID + 4) }));
//SINDRAGOSA
triggers.push_back(new TriggerNode("icc sindragosa group position",
{ NextAction("icc sindragosa group position", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("icc sindragosa frost beacon",
{ NextAction("icc sindragosa frost beacon", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("icc sindragosa blistering cold",
{ NextAction("icc sindragosa blistering cold", ACTION_EMERGENCY + 4) }));
triggers.push_back(new TriggerNode("icc sindragosa unchained magic",
{ NextAction("icc sindragosa unchained magic", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("icc sindragosa chilled to the bone",
{ NextAction("icc sindragosa chilled to the bone", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("icc sindragosa mystic buffet",
{ NextAction("icc sindragosa mystic buffet", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("icc sindragosa main tank mystic buffet",
{ NextAction("taunt spell", ACTION_EMERGENCY + 3) }));
triggers.push_back(new TriggerNode("icc sindragosa frost bomb",
{ NextAction("icc sindragosa frost bomb", ACTION_RAID + 7) }));
triggers.push_back(new TriggerNode("icc sindragosa tank swap position",
{ NextAction("icc sindragosa tank swap position", ACTION_EMERGENCY + 2) }));
//LICH KING
triggers.push_back(new TriggerNode("icc lich king shadow trap",
{ NextAction("icc lich king shadow trap", ACTION_RAID + 6) }));
triggers.push_back(new TriggerNode("icc lich king necrotic plague",
{ NextAction("icc lich king necrotic plague", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("icc lich king winter",
{ NextAction("icc lich king winter", ACTION_RAID +5) }));
triggers.push_back(new TriggerNode("icc lich king adds",
{ NextAction("icc lich king adds", ACTION_RAID +2) }));
}
void RaidIccStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new IccLadyDeathwhisperMultiplier(botAI));
multipliers.push_back(new IccAddsDbsMultiplier(botAI));
multipliers.push_back(new IccDogsMultiplier(botAI));
multipliers.push_back(new IccFestergutMultiplier(botAI));
multipliers.push_back(new IccRotfaceMultiplier(botAI));
multipliers.push_back(new IccAddsPutricideMultiplier(botAI));
multipliers.push_back(new IccBpcAssistMultiplier(botAI));
multipliers.push_back(new IccBqlMultiplier(botAI));
multipliers.push_back(new IccValithriaDreamCloudMultiplier(botAI));
multipliers.push_back(new IccSindragosaMultiplier(botAI));
multipliers.push_back(new IccLichKingAddsMultiplier(botAI));
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,578 @@
#ifndef _PLAYERBOT_RAIDICCTRIGGERS_H
#define _PLAYERBOT_RAIDICCTRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
enum CreatureIdsICC
{
// Lord Marrowgar
NPC_SPIKE1 = 36619,
NPC_SPIKE2 = 38711,
NPC_SPIKE3 = 38712,
// Lady Deathwhisper
NPC_SHADE = 38222,
// Gunship Battle
NPC_KOR_KRON_BATTLE_MAGE = 37117,
NPC_KOR_KRON_AXETHROWER = 36968,
NPC_KOR_KRON_ROCKETEER = 36982,
NPC_SKYBREAKER_SORCERER = 37116,
NPC_SKYBREAKER_RIFLEMAN = 36969,
NPC_SKYBREAKER_MORTAR_SOLDIER = 36978,
NPC_IGB_HIGH_OVERLORD_SAURFANG = 36939,
NPC_IGB_MURADIN_BRONZEBEARD = 36948,
NPC_CANNONA = 36838,
NPC_CANNONH = 36839,
NPC_MURADIN_BRONZEBEARD = 36948,
NPC_HIGH_OVERLORD_SAURFANG = 36939,
// Deathbringer Saurfang
NPC_BLOOD_BEAST1 = 38508,
NPC_BLOOD_BEAST2 = 38596,
NPC_BLOOD_BEAST3 = 38597,
NPC_BLOOD_BEAST4 = 38598,
// Rotface
NPC_PUDDLE = 37013,
NPC_BIG_OOZE = 36899,
// Putricide
NPC_MALLEABLE_OOZE_STALKER = 38556,
NPC_GROWING_OOZE_PUDDLE = 37690,
NPC_CHOKING_GAS_BOMB = 38159,
// Blood Prince Council
NPC_DARK_NUCLEUS = 38369,
NPC_PRINCE_KELESETH = 37972,
NPC_PRINCE_TALDARAM = 37973,
NPC_PRINCE_VALANAR = 37970,
NPC_KINETIC_BOMB1 = 38454,
NPC_KINETIC_BOMB2 = 38775,
NPC_KINETIC_BOMB3 = 38776,
NPC_KINETIC_BOMB4 = 38777,
NPC_BALL_OF_FLAME = 38332,
NPC_BALL_OF_INFERNO_FLAME = 38451,
// Blood Queen Lana'thel
NPC_SWARMING_SHADOWS = 38163,
// Sister Svalna
NPC_SPEAR = 38248,
ITEM_SPEAR = 50307,
// Valithria Dreamwalker
NPC_VALITHRIA_DREAMWALKER = 36789,
NPC_DREAM_PORTAL = 37945,
NPC_NIGHTMARE_PORTAL = 38430,
NPC_DREAM_CLOUD = 37985,
NPC_NIGHTMARE_CLOUD = 38421,
NPC_RISEN_ARCHMAGE = 37868,
NPC_BLAZING_SKELETON = 36791,
NPC_SUPPRESSER = 37863,
NPC_BLISTERING_ZOMBIE = 37934,
NPC_GLUTTONOUS_ABOMINATION = 37886,
NPC_ROT_WORM = 37907,
NPC_COLUMN_OF_FROST = 37918,
NPC_MANA_VOID = 38068,
NPC_DREAM_PORTAL_PRE_EFFECT = 38186,
NPC_NIGHTMARE_PORTAL_PRE_EFFECT = 38429,
// Sindragosa
NPC_SINDRAGOSA = 36853,
NPC_TOMB1 = 36980,
NPC_TOMB2 = 38320,
NPC_TOMB3 = 38321,
NPC_TOMB4 = 38322,
// Lich King
NPC_THE_LICH_KING = 36597,
NPC_TERENAS_MENETHIL = 36823,
NPC_TERENAS_MENETHIL_HC = 39217,
NPC_SPIRIT_BOMB = 39189,
NPC_WICKED_SPIRIT1 = 39190,
NPC_WICKED_SPIRIT2 = 39287,
NPC_WICKED_SPIRIT3 = 39288,
NPC_WICKED_SPIRIT4 = 39289,
NPC_SHADOW_TRAP = 39137,
NPC_SHAMBLING_HORROR1 = 37698,
NPC_SHAMBLING_HORROR2 = 39299,
NPC_SHAMBLING_HORROR3 = 39300,
NPC_SHAMBLING_HORROR4 = 39301,
NPC_ICE_SPHERE1 = 36633,
NPC_ICE_SPHERE2 = 39305,
NPC_ICE_SPHERE3 = 39306,
NPC_ICE_SPHERE4 = 39307,
NPC_RAGING_SPIRIT1 = 36701,
NPC_RAGING_SPIRIT2 = 39302,
NPC_RAGING_SPIRIT3 = 39303,
NPC_RAGING_SPIRIT4 = 39304,
NPC_DRUDGE_GHOUL1 = 37695,
NPC_DRUDGE_GHOUL2 = 39309,
NPC_DRUDGE_GHOUL3 = 39310,
NPC_DRUDGE_GHOUL4 = 39311,
NPC_VALKYR_SHADOWGUARD1 = 36609,
NPC_VALKYR_SHADOWGUARD2 = 39120,
NPC_VALKYR_SHADOWGUARD3 = 39121,
NPC_VALKYR_SHADOWGUARD4 = 39122,
NPC_VILE_SPIRIT1 = 37799,
NPC_VILE_SPIRIT2 = 39284,
NPC_VILE_SPIRIT3 = 39285,
NPC_VILE_SPIRIT4 = 39286,
};
enum SpellIdsICC
{
// ICC cheat spells
SPELL_EMPOWERED_BLOOD = 70227, //70304 -->50%, 70227 /*100% more dmg, 100% more att speed*/
SPELL_EXPERIENCED = 71188, //dmg 30% 20% att speed
SPELL_NO_THREAT = 70115, //reduce threat
SPELL_SPITEFULL_FURY = 36886, //500% more threat
SPELL_NITRO_BOOSTS = 54861, //Speed
SPELL_PAIN_SUPPRESION = 69910, //40% dmg reduction
SPELL_AGEIS_OF_DALARAN = 71638, //268 all ress
SPELL_CYCLONE = 33786,
SPELL_HAMMER_OF_JUSTICE = 10308, //stun
// Lady Deathwhisper
SPELL_DARK_RECKONING = 69483,
// Gunship Battle
SPELL_DEATH_PLAGUE = 72865,
SPELL_BELOW_ZERO = 69705,
// Festergut
SPELL_GAS_SPORE = 69279,
// Rotface
SPELL_SLIME_SPRAY = 69508,
SPELL_OOZE_FLOOD = 71215,
SPELL_UNSTABLE_OOZE_EXPLOSION = 69839,
SPELL_OOZE_FLOOD_VISUAL = 69785,
// Putricide
SPELL_MALLEABLE_GOO = 70852,
SPELL_GROW_AURA = 70347,
// Blood Prince Council
SPELL_EMPOWERED_SHOCK_VORTEX1 = 72039,
SPELL_EMPOWERED_SHOCK_VORTEX2 = 73037,
SPELL_EMPOWERED_SHOCK_VORTEX3 = 73038,
SPELL_EMPOWERED_SHOCK_VORTEX4 = 73039,
// Blood Queen Lana'thel
SPELL_PACT_OF_THE_DARKFALLEN = 71340,
// Sister Svalna
SPELL_AETHER_SHIELD = 71463,
// Valithria Dreamwalker
SPELL_DREAM_STATE = 70766,
SPELL_EMERALD_VIGOR = 70873,
// Sindragosa
SPELL_FROST_BEACON = 70126,
SPELL_ICE_TOMB = 70157,
SPELL_FROST_BOMB_VISUAL = 70022,
SPELL_BLISTERING_COLD1 = 70123,
SPELL_BLISTERING_COLD2 = 71047,
SPELL_BLISTERING_COLD3 = 71048,
SPELL_BLISTERING_COLD4 = 71049,
// The Lich King
SPELL_HARVEST_SOUL_VALKYR = 68985,
SPELL_QUAKE = 72262,
SPELL_REMORSELESS_WINTER1 = 72259,
SPELL_REMORSELESS_WINTER2 = 74273,
SPELL_REMORSELESS_WINTER3 = 74274,
SPELL_REMORSELESS_WINTER4 = 74275,
SPELL_REMORSELESS_WINTER5 = 68981,
SPELL_REMORSELESS_WINTER6 = 74270,
SPELL_REMORSELESS_WINTER7 = 74271,
SPELL_REMORSELESS_WINTER8 = 74272,
};
const uint32 DEFILE_AURAS[] = {72756, 74162, 74163, 74164};
const uint32 DEFILE_CAST_ID = 72762;
const uint32 DEFILE_NPC_ID = 38757;
const size_t DEFILE_AURA_COUNT = 4;
// All fanatics and adherents entry ids Lady Deathwhisper
static const std::array<uint32, 23> addEntriesLady = {
37949, 38394, 38625, 38626, 38010, 38397, 39000, 39001,
38136, 38396, 38632, 38633, 37890, 38393, 38628, 38629,
38135, 38395, 38634, 38009, 38398, 38630, 38631};
const std::vector<uint32> spellEntriesFlood = {
69782, 69783, 69796, 69797, 69798,
69799, 69801, 69802, 69795};
const std::vector<uint32> availableTargetsGS = {
NPC_KOR_KRON_AXETHROWER, NPC_KOR_KRON_ROCKETEER, NPC_KOR_KRON_BATTLE_MAGE, NPC_IGB_HIGH_OVERLORD_SAURFANG,
NPC_SKYBREAKER_RIFLEMAN, NPC_SKYBREAKER_MORTAR_SOLDIER, NPC_SKYBREAKER_SORCERER, NPC_IGB_MURADIN_BRONZEBEARD};
static std::vector<ObjectGuid> sporeOrder;
//Lord Marrowgar
class IccLmTrigger : public Trigger
{
public:
IccLmTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lm") {}
bool IsActive() override;
};
//Lady Deathwhisper
class IccDarkReckoningTrigger : public Trigger
{
public:
IccDarkReckoningTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc dark reckoning") {}
bool IsActive() override;
};
class IccLadyDeathwhisperTrigger : public Trigger
{
public:
IccLadyDeathwhisperTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lady deathwhisper") {}
bool IsActive() override;
};
//Gunship Battle
class IccRottingFrostGiantTankPositionTrigger : public Trigger
{
public:
IccRottingFrostGiantTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotting frost giant tank position") {}
bool IsActive() override;
};
class IccInCannonTrigger : public Trigger
{
public:
IccInCannonTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc in cannon") {}
bool IsActive() override;
};
class IccGunshipCannonNearTrigger : public Trigger
{
public:
IccGunshipCannonNearTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc in cannon") {}
bool IsActive() override;
};
class IccGunshipTeleportAllyTrigger : public Trigger
{
public:
IccGunshipTeleportAllyTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship teleport ally") {}
bool IsActive() override;
};
class IccGunshipTeleportHordeTrigger : public Trigger
{
public:
IccGunshipTeleportHordeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship teleport horde") {}
bool IsActive() override;
};
//DBS
class IccDbsTrigger : public Trigger
{
public:
IccDbsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc dbs") {}
bool IsActive() override;
};
class IccDbsMainTankRuneOfBloodTrigger : public Trigger
{
public:
IccDbsMainTankRuneOfBloodTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc dbs main tank rune of blood") {}
bool IsActive() override;
};
//DOGS
class IccStinkyPreciousMainTankMortalWoundTrigger : public Trigger
{
public:
IccStinkyPreciousMainTankMortalWoundTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc stinky precious main tank mortal wound") {}
bool IsActive() override;
};
//FESTERGUT
class IccFestergutGroupPositionTrigger : public Trigger
{
public:
IccFestergutGroupPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc festergut group position") {}
bool IsActive() override;
};
class IccFestergutMainTankGastricBloatTrigger : public Trigger
{
public:
IccFestergutMainTankGastricBloatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc festergut main tank gastric bloat") {}
bool IsActive() override;
};
class IccFestergutSporeTrigger : public Trigger
{
public:
IccFestergutSporeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc festergut spore") {}
bool IsActive() override;
};
//ROTFACE
class IccRotfaceTankPositionTrigger : public Trigger
{
public:
IccRotfaceTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotface tank position") {}
bool IsActive() override;
};
class IccRotfaceGroupPositionTrigger : public Trigger
{
public:
IccRotfaceGroupPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotface group position") {}
bool IsActive() override;
};
class IccRotfaceMoveAwayFromExplosionTrigger : public Trigger
{
public:
IccRotfaceMoveAwayFromExplosionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotface move away from explosion") {}
bool IsActive() override;
};
//PP
class IccPutricideVolatileOozeTrigger : public Trigger
{
public:
IccPutricideVolatileOozeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide volatile ooze") {}
bool IsActive() override;
};
class IccPutricideGasCloudTrigger : public Trigger
{
public:
IccPutricideGasCloudTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide gas cloud") {}
bool IsActive() override;
};
class IccPutricideGrowingOozePuddleTrigger : public Trigger
{
public:
IccPutricideGrowingOozePuddleTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide growing ooze puddle") {}
bool IsActive() override;
};
class IccPutricideMainTankMutatedPlagueTrigger : public Trigger
{
public:
IccPutricideMainTankMutatedPlagueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide main tank mutated plague") {}
bool IsActive() override;
};
class IccPutricideMalleableGooTrigger : public Trigger
{
public:
IccPutricideMalleableGooTrigger(PlayerbotAI* ai) : Trigger(ai, "icc putricide malleable goo") {}
bool IsActive() override;
};
//BPC
class IccBpcKelesethTankTrigger : public Trigger
{
public:
IccBpcKelesethTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc keleseth tank") {}
bool IsActive() override;
};
class IccBpcMainTankTrigger : public Trigger
{
public:
IccBpcMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc main tank") {}
bool IsActive() override;
};
class IccBpcEmpoweredVortexTrigger : public Trigger
{
public:
IccBpcEmpoweredVortexTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc empowered vortex") {}
bool IsActive() override;
};
class IccBpcKineticBombTrigger : public Trigger
{
public:
IccBpcKineticBombTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc kinetic bomb") {}
bool IsActive() override;
};
class IccBpcBallOfFlameTrigger : public Trigger
{
public:
IccBpcBallOfFlameTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc ball of flame") {}
bool IsActive() override;
};
//Bql
class IccBqlGroupPositionTrigger : public Trigger
{
public:
IccBqlGroupPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bql tank position") {}
bool IsActive() override;
};
class IccBqlPactOfDarkfallenTrigger : public Trigger
{
public:
IccBqlPactOfDarkfallenTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bql pact of darkfallen") {}
bool IsActive() override;
};
class IccBqlVampiricBiteTrigger : public Trigger
{
public:
IccBqlVampiricBiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bql vampiric bite") {}
bool IsActive() override;
};
// Sister Svalna
class IccValkyreSpearTrigger : public Trigger
{
public:
IccValkyreSpearTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valkyre spear") {}
bool IsActive() override;
};
class IccSisterSvalnaTrigger : public Trigger
{
public:
IccSisterSvalnaTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sister svalna") {}
bool IsActive() override;
};
// Valithria Dreamwalker
class IccValithriaGroupTrigger : public Trigger
{
public:
IccValithriaGroupTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria group") {}
bool IsActive() override;
};
class IccValithriaPortalTrigger : public Trigger
{
public:
IccValithriaPortalTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria portal") {}
bool IsActive() override;
};
class IccValithriaHealTrigger : public Trigger
{
public:
IccValithriaHealTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria heal") {}
bool IsActive() override;
};
class IccValithriaDreamCloudTrigger : public Trigger
{
public:
IccValithriaDreamCloudTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria dream cloud") {}
bool IsActive() override;
};
//SINDRAGOSA
class IccSindragosaGroupPositionTrigger : public Trigger
{
public:
IccSindragosaGroupPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa group position") {}
bool IsActive() override;
};
class IccSindragosaFrostBeaconTrigger : public Trigger
{
public:
IccSindragosaFrostBeaconTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa frost beacon") {}
bool IsActive() override;
};
class IccSindragosaBlisteringColdTrigger : public Trigger
{
public:
IccSindragosaBlisteringColdTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa blistering cold") {}
bool IsActive() override;
};
class IccSindragosaUnchainedMagicTrigger : public Trigger
{
public:
IccSindragosaUnchainedMagicTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa unchained magic") {}
bool IsActive() override;
};
class IccSindragosaChilledToTheBoneTrigger : public Trigger
{
public:
IccSindragosaChilledToTheBoneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa chilled to the bone") {}
bool IsActive() override;
};
class IccSindragosaMysticBuffetTrigger : public Trigger
{
public:
IccSindragosaMysticBuffetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa mystic buffet") {}
bool IsActive() override;
};
class IccSindragosaMainTankMysticBuffetTrigger : public Trigger
{
public:
IccSindragosaMainTankMysticBuffetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa main tank mystic buffet") {}
bool IsActive() override;
};
class IccSindragosaTankSwapPositionTrigger : public Trigger
{
public:
IccSindragosaTankSwapPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa tank swap position") {}
bool IsActive() override;
};
class IccSindragosaFrostBombTrigger : public Trigger
{
public:
IccSindragosaFrostBombTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa frost bomb") {}
bool IsActive() override;
};
//LICH KING
class IccLichKingShadowTrapTrigger : public Trigger
{
public:
IccLichKingShadowTrapTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lich king shadow trap") {}
bool IsActive() override;
};
class IccLichKingNecroticPlagueTrigger : public Trigger
{
public:
IccLichKingNecroticPlagueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lich king necrotic plague") {}
bool IsActive() override;
};
class IccLichKingWinterTrigger : public Trigger
{
public:
IccLichKingWinterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lich king winter") {}
bool IsActive() override;
};
class IccLichKingAddsTrigger : public Trigger
{
public:
IccLichKingAddsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lich king adds") {}
bool IsActive() override;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,322 @@
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_H
#define _PLAYERBOT_RAIDKARAZHANACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
class ManaWarpStunCreatureBeforeWarpBreachAction : public AttackAction
{
public:
ManaWarpStunCreatureBeforeWarpBreachAction(
PlayerbotAI* botAI, std::string const name = "mana warp stun creature before warp breach") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AttumenTheHuntsmanMarkTargetAction : public AttackAction
{
public:
AttumenTheHuntsmanMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "attumen the huntsman mark target") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AttumenTheHuntsmanSplitBossesAction : public AttackAction
{
public:
AttumenTheHuntsmanSplitBossesAction(
PlayerbotAI* botAI, std::string const name = "attumen the huntsman split bosses") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AttumenTheHuntsmanStackBehindAction : public MovementAction
{
public:
AttumenTheHuntsmanStackBehindAction(
PlayerbotAI* botAI, std::string const name = "attumen the huntsman stack behind") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class AttumenTheHuntsmanManageDpsTimerAction : public Action
{
public:
AttumenTheHuntsmanManageDpsTimerAction(
PlayerbotAI* botAI, std::string const name = "attumen the huntsman manage dps timer") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class MoroesMainTankAttackBossAction : public AttackAction
{
public:
MoroesMainTankAttackBossAction(
PlayerbotAI* botAI, std::string const name = "moroes main tank attack boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class MoroesMarkTargetAction : public Action
{
public:
MoroesMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "moroes mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class MaidenOfVirtueMoveBossToHealerAction : public AttackAction
{
public:
MaidenOfVirtueMoveBossToHealerAction(
PlayerbotAI* botAI, std::string const name = "maiden of virtue move boss to healer") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class MaidenOfVirtuePositionRangedAction : public MovementAction
{
public:
MaidenOfVirtuePositionRangedAction(
PlayerbotAI* botAI, std::string const name = "maiden of virtue position ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class BigBadWolfPositionBossAction : public AttackAction
{
public:
BigBadWolfPositionBossAction(
PlayerbotAI* botAI, std::string const name = "big bad wolf position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class BigBadWolfRunAwayFromBossAction : public MovementAction
{
public:
BigBadWolfRunAwayFromBossAction(
PlayerbotAI* botAI, std::string const name = "big bad wolf run away from boss") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class RomuloAndJulianneMarkTargetAction : public Action
{
public:
RomuloAndJulianneMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "romulo and julianne mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class WizardOfOzMarkTargetAction : public Action
{
public:
WizardOfOzMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "wizard of oz mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class WizardOfOzScorchStrawmanAction : public Action
{
public:
WizardOfOzScorchStrawmanAction(
PlayerbotAI* botAI, std::string const name = "wizard of oz scorch strawman") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorMarkAstralFlareAction : public Action
{
public:
TheCuratorMarkAstralFlareAction(
PlayerbotAI* botAI, std::string const name = "the curator mark astral flare") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorPositionBossAction : public AttackAction
{
public:
TheCuratorPositionBossAction(
PlayerbotAI* botAI, std::string const name = "the curator position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorSpreadRangedAction : public MovementAction
{
public:
TheCuratorSpreadRangedAction(
PlayerbotAI* botAI, std::string const name = "the curator spread ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class TerestianIllhoofMarkTargetAction : public Action
{
public:
TerestianIllhoofMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "terestian illhoof mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranRunAwayFromArcaneExplosionAction : public MovementAction
{
public:
ShadeOfAranRunAwayFromArcaneExplosionAction(
PlayerbotAI* botAI, std::string const name = "shade of aran run away from arcane explosion") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranStopMovingDuringFlameWreathAction : public MovementAction
{
public:
ShadeOfAranStopMovingDuringFlameWreathAction(
PlayerbotAI* botAI, std::string const name = "shade of aran stop moving during flame wreath") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranMarkConjuredElementalAction : public Action
{
public:
ShadeOfAranMarkConjuredElementalAction(
PlayerbotAI* botAI, std::string const name = "shade of aran mark conjured elemental") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranRangedMaintainDistanceAction : public MovementAction
{
public:
ShadeOfAranRangedMaintainDistanceAction(
PlayerbotAI* botAI, std::string const name = "shade of aran ranged maintain distance") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class NetherspiteBlockRedBeamAction : public MovementAction
{
public:
NetherspiteBlockRedBeamAction(
PlayerbotAI* botAI, std::string const name = "netherspite block red beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
Position GetPositionOnBeam(Unit* netherspite, Unit* portal, float distanceFromBoss);
std::unordered_map<ObjectGuid, bool> _wasBlockingRedBeam;
};
class NetherspiteBlockBlueBeamAction : public MovementAction
{
public:
NetherspiteBlockBlueBeamAction(
PlayerbotAI* botAI, std::string const name = "netherspite block blue beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
std::unordered_map<ObjectGuid, bool> _wasBlockingBlueBeam;
};
class NetherspiteBlockGreenBeamAction : public MovementAction
{
public:
NetherspiteBlockGreenBeamAction(
PlayerbotAI* botAI, std::string const name = "netherspite block green beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
std::unordered_map<ObjectGuid, bool> _wasBlockingGreenBeam;
};
class NetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
{
public:
NetherspiteAvoidBeamAndVoidZoneAction(
PlayerbotAI* botAI, std::string const name = "netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
struct BeamAvoid
{
Unit* portal;
float minDist, maxDist;
};
bool IsAwayFromBeams(float x, float y, const std::vector<BeamAvoid>& beams, Unit* netherspite);
};
class NetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
{
public:
NetherspiteBanishPhaseAvoidVoidZoneAction(
PlayerbotAI* botAI, std::string const name = "netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class NetherspiteManageTimersAndTrackersAction : public Action
{
public:
NetherspiteManageTimersAndTrackersAction(
PlayerbotAI* botAI, std::string const name = "netherspite manage timers and trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class PrinceMalchezaarEnfeebledAvoidHazardAction : public MovementAction
{
public:
PrinceMalchezaarEnfeebledAvoidHazardAction(
PlayerbotAI* botAI, std::string const name = "prince malchezaar enfeebled avoid hazard") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class PrinceMalchezaarNonTankAvoidInfernalAction : public MovementAction
{
public:
PrinceMalchezaarNonTankAvoidInfernalAction(
PlayerbotAI* botAI, std::string const name = "prince malchezaar non tank avoid infernal") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class PrinceMalchezaarMainTankMovementAction : public AttackAction
{
public:
PrinceMalchezaarMainTankMovementAction(
PlayerbotAI* botAI, std::string const name = "prince malchezaar main tank movement") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class NightbaneGroundPhasePositionBossAction : public AttackAction
{
public:
NightbaneGroundPhasePositionBossAction(
PlayerbotAI* botAI, std::string const name = "nightbane ground phase position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class NightbaneGroundPhaseRotateRangedPositionsAction : public MovementAction
{
public:
NightbaneGroundPhaseRotateRangedPositionsAction(
PlayerbotAI* botAI, std::string const name = "nightbane ground phase rotate ranged positions") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class NightbaneCastFearWardOnMainTankAction : public Action
{
public:
NightbaneCastFearWardOnMainTankAction(
PlayerbotAI* botAI, std::string const name = "nightbane cast fear ward on main tank") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class NightbaneControlPetAggressionAction : public Action
{
public:
NightbaneControlPetAggressionAction(
PlayerbotAI* botAI, std::string const name = "nightbane control pet aggression") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class NightbaneFlightPhaseMovementAction : public MovementAction
{
public:
NightbaneFlightPhaseMovementAction(
PlayerbotAI* botAI, std::string const name = "nightbane flight phase movement") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class NightbaneManageTimersAndTrackersAction : public Action
{
public:
NightbaneManageTimersAndTrackersAction(
PlayerbotAI* botAI, std::string const name = "nightbane manage timers and trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,363 @@
#include "RaidKarazhanMultipliers.h"
#include "RaidKarazhanActions.h"
#include "RaidKarazhanHelpers.h"
#include "AttackAction.h"
#include "ChooseTargetActions.h"
#include "DruidActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "Playerbots.h"
#include "PriestActions.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ShamanActions.h"
using namespace KarazhanHelpers;
// Keep tanks from jumping back and forth between Attumen and Midnight
float AttumenTheHuntsmanDisableTankAssistMultiplier::GetValue(Action* action)
{
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
if (!midnight)
return 1.0f;
Unit* attumen = AI_VALUE2(Unit*, "find target", "attumen the huntsman");
if (!attumen)
return 1.0f;
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
// Try to get rid of jittering when bots are stacked behind Attumen
float AttumenTheHuntsmanStayStackedMultiplier::GetValue(Action* action)
{
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (!attumenMounted)
return 1.0f;
if (!botAI->IsMainTank(bot) && attumenMounted->GetVictim() != bot)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Give the main tank 8 seconds to grab aggro when Attumen mounts Midnight
// In reality it's shorter because it takes Attumen a few seconds to aggro after mounting
float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (!attumenMounted)
return 1.0f;
const uint32 instanceId = attumenMounted->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8;
auto it = attumenDpsWaitTimer.find(instanceId);
if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot))
{
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
// The assist tank should stay on the boss to be 2nd on aggro and tank Hateful Bolts
float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
{
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
if (!curator)
return 1.0f;
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
// Save Bloodlust/Heroism for Evocation (100% increased damage)
float TheCuratorDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
if (!curator)
return 1.0f;
if (!curator->HasAura(SPELL_CURATOR_EVOCATION))
{
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Don't charge back in when running from Arcane Explosion
float ShadeOfAranArcaneExplosionDisableChargeMultiplier::GetValue(Action* action)
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
if (!aran)
return 1.0f;
if (aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (bot->GetDistance2d(aran) >= 20.0f)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
}
}
return 1.0f;
}
// I will not move when Flame Wreath is cast or the raid blows up
float ShadeOfAranFlameWreathDisableMovementMultiplier::GetValue(Action* action)
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
if (!aran)
return 1.0f;
if (IsFlameWreathActive(botAI, bot))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<AvoidAoeAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Try to rid of the jittering when blocking beams
float NetherspiteKeepBlockingBeamMultiplier::GetValue(Action* action)
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f;
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
if (bot == redBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
if (bot == blueBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action))
return 0.0f;
}
if (bot == greenBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Give tanks 5 seconds to get aggro during phase transitions
float NetherspiteWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f;
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 5;
auto it = netherspiteDpsWaitTimer.find(instanceId);
if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsTank(bot))
{
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
// Disable standard "avoid aoe" strategy, which may interfere with scripted avoidance
float PrinceMalchezaarDisableAvoidAoeMultiplier::GetValue(Action* action)
{
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!malchezaar)
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
return 1.0f;
}
// Don't run back into Shadow Nova when Enfeebled
float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action)
{
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!malchezaar)
return 1.0f;
if (bot->HasAura(SPELL_ENFEEBLE))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Wait until Phase 3 to use Bloodlust/Heroism
float PrinceMalchezaarDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!malchezaar)
return 1.0f;
if (malchezaar->GetHealthPct() > 30.0f)
{
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Pets tend to run out of bounds and cause skeletons to spawn off the map
// Pets also tend to pull adds from inside of the tower through the floor
// This multiplier DOES NOT impact Hunter and Warlock pets
// Hunter and Warlock pets are addressed in ControlPetAggressionAction
float NightbaneDisablePetsMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return 1.0f;
if (dynamic_cast<CastForceOfNatureAction*>(action) ||
dynamic_cast<CastFeralSpiritAction*>(action) ||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
dynamic_cast<CastSummonWaterElementalAction*>(action) ||
dynamic_cast<CastShadowfiendAction*>(action))
return 0.0f;
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
{
if (dynamic_cast<PetAttackAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Give the main tank 8 seconds to get aggro during phase transitions
float NightbaneWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
return 1.0f;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8;
auto it = nightbaneDpsWaitTimer.find(instanceId);
if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot))
{
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
// The "avoid aoe" strategy must be disabled for the main tank
// Otherwise, the main tank will spin Nightbane to avoid Charred Earth and wipe the raid
// It is also disabled for all bots during the flight phase
float NightbaneDisableAvoidAoeMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return 1.0f;
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z || botAI->IsMainTank(bot))
{
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Disable some movement actions that conflict with the strategies
float NightbaneDisableMovementMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return 1.0f;
if (dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
// Disable CombatFormationMoveAction for all bots except:
// (1) main tank and (2) only during the ground phase, other melee
if (botAI->IsRanged(bot) ||
(botAI->IsMelee(bot) && !botAI->IsMainTank(bot) &&
nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z))
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,134 @@
#ifndef _PLAYERBOT_RAIDKARAZHANMULTIPLIERS_H
#define _PLAYERBOT_RAIDKARAZHANMULTIPLIERS_H
#include "Multiplier.h"
class AttumenTheHuntsmanDisableTankAssistMultiplier : public Multiplier
{
public:
AttumenTheHuntsmanDisableTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman disable tank assist multiplier") {}
virtual float GetValue(Action* action);
};
class AttumenTheHuntsmanStayStackedMultiplier : public Multiplier
{
public:
AttumenTheHuntsmanStayStackedMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman stay stacked multiplier") {}
virtual float GetValue(Action* action);
};
class AttumenTheHuntsmanWaitForDpsMultiplier : public Multiplier
{
public:
AttumenTheHuntsmanWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class TheCuratorDisableTankAssistMultiplier : public Multiplier
{
public:
TheCuratorDisableTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the curator disable tank assist multiplier") {}
virtual float GetValue(Action* action);
};
class TheCuratorDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
TheCuratorDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the curator delay bloodlust and heroism multiplier") {}
virtual float GetValue(Action* action);
};
class ShadeOfAranArcaneExplosionDisableChargeMultiplier : public Multiplier
{
public:
ShadeOfAranArcaneExplosionDisableChargeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran arcane explosion disable charge multiplier") {}
virtual float GetValue(Action* action);
};
class ShadeOfAranFlameWreathDisableMovementMultiplier : public Multiplier
{
public:
ShadeOfAranFlameWreathDisableMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran flame wreath disable movement multiplier") {}
virtual float GetValue(Action* action);
};
class NetherspiteKeepBlockingBeamMultiplier : public Multiplier
{
public:
NetherspiteKeepBlockingBeamMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite keep blocking beam multiplier") {}
virtual float GetValue(Action* action);
};
class NetherspiteWaitForDpsMultiplier : public Multiplier
{
public:
NetherspiteWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarDisableAvoidAoeMultiplier : public Multiplier
{
public:
PrinceMalchezaarDisableAvoidAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar disable avoid aoe multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarEnfeebleKeepDistanceMultiplier : public Multiplier
{
public:
PrinceMalchezaarEnfeebleKeepDistanceMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar enfeeble keep distance multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar delay bloodlust and heroism multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisablePetsMultiplier : public Multiplier
{
public:
NightbaneDisablePetsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable pets multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneWaitForDpsMultiplier : public Multiplier
{
public:
NightbaneWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisableAvoidAoeMultiplier : public Multiplier
{
public:
NightbaneDisableAvoidAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable avoid aoe multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisableMovementMultiplier : public Multiplier
{
public:
NightbaneDisableMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable movement multiplier") {}
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,263 @@
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H
#define _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H
#include "RaidKarazhanActions.h"
#include "NamedObjectContext.h"
class RaidKarazhanActionContext : public NamedObjectContext<Action>
{
public:
RaidKarazhanActionContext()
{
// Trash
creators["mana warp stun creature before warp breach"] =
&RaidKarazhanActionContext::mana_warp_stun_creature_before_warp_breach;
// Attumen the Huntsman
creators["attumen the huntsman mark target"] =
&RaidKarazhanActionContext::attumen_the_huntsman_mark_target;
creators["attumen the huntsman split bosses"] =
&RaidKarazhanActionContext::attumen_the_huntsman_split_bosses;
creators["attumen the huntsman stack behind"] =
&RaidKarazhanActionContext::attumen_the_huntsman_stack_behind;
creators["attumen the huntsman manage dps timer"] =
&RaidKarazhanActionContext::attumen_the_huntsman_manage_dps_timer;
// Moroes
creators["moroes main tank attack boss"] =
&RaidKarazhanActionContext::moroes_main_tank_attack_boss;
creators["moroes mark target"] =
&RaidKarazhanActionContext::moroes_mark_target;
// Maiden of Virtue
creators["maiden of virtue move boss to healer"] =
&RaidKarazhanActionContext::maiden_of_virtue_move_boss_to_healer;
creators["maiden of virtue position ranged"] =
&RaidKarazhanActionContext::maiden_of_virtue_position_ranged;
// The Big Bad Wolf
creators["big bad wolf position boss"] =
&RaidKarazhanActionContext::big_bad_wolf_position_boss;
creators["big bad wolf run away from boss"] =
&RaidKarazhanActionContext::big_bad_wolf_run_away_from_boss;
// Romulo and Julianne
creators["romulo and julianne mark target"] =
&RaidKarazhanActionContext::romulo_and_julianne_mark_target;
// The Wizard of Oz
creators["wizard of oz mark target"] =
&RaidKarazhanActionContext::wizard_of_oz_mark_target;
creators["wizard of oz scorch strawman"] =
&RaidKarazhanActionContext::wizard_of_oz_scorch_strawman;
// The Curator
creators["the curator mark astral flare"] =
&RaidKarazhanActionContext::the_curator_mark_astral_flare;
creators["the curator position boss"] =
&RaidKarazhanActionContext::the_curator_position_boss;
creators["the curator spread ranged"] =
&RaidKarazhanActionContext::the_curator_spread_ranged;
// Terestian Illhoof
creators["terestian illhoof mark target"] =
&RaidKarazhanActionContext::terestian_illhoof_mark_target;
// Shade of Aran
creators["shade of aran run away from arcane explosion"] =
&RaidKarazhanActionContext::shade_of_aran_run_away_from_arcane_explosion;
creators["shade of aran stop moving during flame wreath"] =
&RaidKarazhanActionContext::shade_of_aran_stop_moving_during_flame_wreath;
creators["shade of aran mark conjured elemental"] =
&RaidKarazhanActionContext::shade_of_aran_mark_conjured_elemental;
creators["shade of aran ranged maintain distance"] =
&RaidKarazhanActionContext::shade_of_aran_ranged_maintain_distance;
// Netherspite
creators["netherspite block red beam"] =
&RaidKarazhanActionContext::netherspite_block_red_beam;
creators["netherspite block blue beam"] =
&RaidKarazhanActionContext::netherspite_block_blue_beam;
creators["netherspite block green beam"] =
&RaidKarazhanActionContext::netherspite_block_green_beam;
creators["netherspite avoid beam and void zone"] =
&RaidKarazhanActionContext::netherspite_avoid_beam_and_void_zone;
creators["netherspite banish phase avoid void zone"] =
&RaidKarazhanActionContext::netherspite_banish_phase_avoid_void_zone;
creators["netherspite manage timers and trackers"] =
&RaidKarazhanActionContext::netherspite_manage_timers_and_trackers;
// Prince Malchezaar
creators["prince malchezaar enfeebled avoid hazard"] =
&RaidKarazhanActionContext::prince_malchezaar_enfeebled_avoid_hazard;
creators["prince malchezaar non tank avoid infernal"] =
&RaidKarazhanActionContext::prince_malchezaar_non_tank_avoid_infernal;
creators["prince malchezaar main tank movement"] =
&RaidKarazhanActionContext::prince_malchezaar_main_tank_movement;
// Nightbane
creators["nightbane ground phase position boss"] =
&RaidKarazhanActionContext::nightbane_ground_phase_position_boss;
creators["nightbane ground phase rotate ranged positions"] =
&RaidKarazhanActionContext::nightbane_ground_phase_rotate_ranged_positions;
creators["nightbane cast fear ward on main tank"] =
&RaidKarazhanActionContext::nightbane_cast_fear_ward_on_main_tank;
creators["nightbane control pet aggression"] =
&RaidKarazhanActionContext::nightbane_control_pet_aggression;
creators["nightbane flight phase movement"] =
&RaidKarazhanActionContext::nightbane_flight_phase_movement;
creators["nightbane manage timers and trackers"] =
&RaidKarazhanActionContext::nightbane_manage_timers_and_trackers;
}
private:
// Trash
static Action* mana_warp_stun_creature_before_warp_breach(
PlayerbotAI* botAI) { return new ManaWarpStunCreatureBeforeWarpBreachAction(botAI); }
// Attumen the Huntsman
static Action* attumen_the_huntsman_mark_target(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanMarkTargetAction(botAI); }
static Action* attumen_the_huntsman_split_bosses(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanSplitBossesAction(botAI); }
static Action* attumen_the_huntsman_stack_behind(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanStackBehindAction(botAI); }
static Action* attumen_the_huntsman_manage_dps_timer(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanManageDpsTimerAction(botAI); }
// Moroes
static Action* moroes_main_tank_attack_boss(
PlayerbotAI* botAI) { return new MoroesMainTankAttackBossAction(botAI); }
static Action* moroes_mark_target(
PlayerbotAI* botAI) { return new MoroesMarkTargetAction(botAI); }
// Maiden of Virtue
static Action* maiden_of_virtue_move_boss_to_healer(
PlayerbotAI* botAI) { return new MaidenOfVirtueMoveBossToHealerAction(botAI); }
static Action* maiden_of_virtue_position_ranged(
PlayerbotAI* botAI) { return new MaidenOfVirtuePositionRangedAction(botAI); }
// The Big Bad Wolf
static Action* big_bad_wolf_position_boss(
PlayerbotAI* botAI) { return new BigBadWolfPositionBossAction(botAI); }
static Action* big_bad_wolf_run_away_from_boss(
PlayerbotAI* botAI) { return new BigBadWolfRunAwayFromBossAction(botAI); }
// Romulo and Julianne
static Action* romulo_and_julianne_mark_target(
PlayerbotAI* botAI) { return new RomuloAndJulianneMarkTargetAction(botAI); }
// The Wizard of Oz
static Action* wizard_of_oz_mark_target(
PlayerbotAI* botAI) { return new WizardOfOzMarkTargetAction(botAI); }
static Action* wizard_of_oz_scorch_strawman(
PlayerbotAI* botAI) { return new WizardOfOzScorchStrawmanAction(botAI); }
// The Curator
static Action* the_curator_mark_astral_flare(
PlayerbotAI* botAI) { return new TheCuratorMarkAstralFlareAction(botAI); }
static Action* the_curator_position_boss(
PlayerbotAI* botAI) { return new TheCuratorPositionBossAction(botAI); }
static Action* the_curator_spread_ranged(
PlayerbotAI* botAI) { return new TheCuratorSpreadRangedAction(botAI); }
// Terestian Illhoof
static Action* terestian_illhoof_mark_target(
PlayerbotAI* botAI) { return new TerestianIllhoofMarkTargetAction(botAI); }
// Shade of Aran
static Action* shade_of_aran_run_away_from_arcane_explosion(
PlayerbotAI* botAI) { return new ShadeOfAranRunAwayFromArcaneExplosionAction(botAI); }
static Action* shade_of_aran_stop_moving_during_flame_wreath(
PlayerbotAI* botAI) { return new ShadeOfAranStopMovingDuringFlameWreathAction(botAI); }
static Action* shade_of_aran_mark_conjured_elemental(
PlayerbotAI* botAI) { return new ShadeOfAranMarkConjuredElementalAction(botAI); }
static Action* shade_of_aran_ranged_maintain_distance(
PlayerbotAI* botAI) { return new ShadeOfAranRangedMaintainDistanceAction(botAI); }
// Netherspite
static Action* netherspite_block_red_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockRedBeamAction(botAI); }
static Action* netherspite_block_blue_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockBlueBeamAction(botAI); }
static Action* netherspite_block_green_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockGreenBeamAction(botAI); }
static Action* netherspite_avoid_beam_and_void_zone(
PlayerbotAI* botAI) { return new NetherspiteAvoidBeamAndVoidZoneAction(botAI); }
static Action* netherspite_banish_phase_avoid_void_zone(
PlayerbotAI* botAI) { return new NetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
static Action* netherspite_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NetherspiteManageTimersAndTrackersAction(botAI); }
// Prince Malchezaar
static Action* prince_malchezaar_enfeebled_avoid_hazard(
PlayerbotAI* botAI) { return new PrinceMalchezaarEnfeebledAvoidHazardAction(botAI); }
static Action* prince_malchezaar_non_tank_avoid_infernal(
PlayerbotAI* botAI) { return new PrinceMalchezaarNonTankAvoidInfernalAction(botAI); }
static Action* prince_malchezaar_main_tank_movement(
PlayerbotAI* botAI) { return new PrinceMalchezaarMainTankMovementAction(botAI); }
// Nightbane
static Action* nightbane_ground_phase_position_boss(
PlayerbotAI* botAI) { return new NightbaneGroundPhasePositionBossAction(botAI); }
static Action* nightbane_ground_phase_rotate_ranged_positions(
PlayerbotAI* botAI) { return new NightbaneGroundPhaseRotateRangedPositionsAction(botAI); }
static Action* nightbane_cast_fear_ward_on_main_tank(
PlayerbotAI* botAI) { return new NightbaneCastFearWardOnMainTankAction(botAI); }
static Action* nightbane_control_pet_aggression(
PlayerbotAI* botAI) { return new NightbaneControlPetAggressionAction(botAI); }
static Action* nightbane_flight_phase_movement(
PlayerbotAI* botAI) { return new NightbaneFlightPhaseMovementAction(botAI); }
static Action* nightbane_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NightbaneManageTimersAndTrackersAction(botAI); }
};
#endif

View File

@@ -0,0 +1,263 @@
#ifndef _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H
#include "RaidKarazhanTriggers.h"
#include "AiObjectContext.h"
class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidKarazhanTriggerContext()
{
// Trash
creators["mana warp is about to explode"] =
&RaidKarazhanTriggerContext::mana_warp_is_about_to_explode;
// Attumen the Huntsman
creators["attumen the huntsman need target priority"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_need_target_priority;
creators["attumen the huntsman attumen spawned"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_spawned;
creators["attumen the huntsman attumen is mounted"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_is_mounted;
creators["attumen the huntsman boss wipes aggro when mounting"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_boss_wipes_aggro_when_mounting;
// Moroes
creators["moroes boss engaged by main tank"] =
&RaidKarazhanTriggerContext::moroes_boss_engaged_by_main_tank;
creators["moroes need target priority"] =
&RaidKarazhanTriggerContext::moroes_need_target_priority;
// Maiden of Virtue
creators["maiden of virtue healers are stunned by repentance"] =
&RaidKarazhanTriggerContext::maiden_of_virtue_healers_are_stunned_by_repentance;
creators["maiden of virtue holy wrath deals chain damage"] =
&RaidKarazhanTriggerContext::maiden_of_virtue_holy_wrath_deals_chain_damage;
// The Big Bad Wolf
creators["big bad wolf boss engaged by tank"] =
&RaidKarazhanTriggerContext::big_bad_wolf_boss_engaged_by_tank;
creators["big bad wolf boss is chasing little red riding hood"] =
&RaidKarazhanTriggerContext::big_bad_wolf_boss_is_chasing_little_red_riding_hood;
// Romulo and Julianne
creators["romulo and julianne both bosses revived"] =
&RaidKarazhanTriggerContext::romulo_and_julianne_both_bosses_revived;
// The Wizard of Oz
creators["wizard of oz need target priority"] =
&RaidKarazhanTriggerContext::wizard_of_oz_need_target_priority;
creators["wizard of oz strawman is vulnerable to fire"] =
&RaidKarazhanTriggerContext::wizard_of_oz_strawman_is_vulnerable_to_fire;
// The Curator
creators["the curator astral flare spawned"] =
&RaidKarazhanTriggerContext::the_curator_astral_flare_spawned;
creators["the curator boss engaged by tanks"] =
&RaidKarazhanTriggerContext::the_curator_boss_engaged_by_tanks;
creators["the curator astral flares cast arcing sear"] =
&RaidKarazhanTriggerContext::the_curator_astral_flares_cast_arcing_sear;
// Terestian Illhoof
creators["terestian illhoof need target priority"] =
&RaidKarazhanTriggerContext::terestian_illhoof_need_target_priority;
// Shade of Aran
creators["shade of aran arcane explosion is casting"] =
&RaidKarazhanTriggerContext::shade_of_aran_arcane_explosion_is_casting;
creators["shade of aran flame wreath is active"] =
&RaidKarazhanTriggerContext::shade_of_aran_flame_wreath_is_active;
creators["shade of aran conjured elementals summoned"] =
&RaidKarazhanTriggerContext::shade_of_aran_conjured_elementals_summoned;
creators["shade of aran boss uses counterspell and blizzard"] =
&RaidKarazhanTriggerContext::shade_of_aran_boss_uses_counterspell_and_blizzard;
// Netherspite
creators["netherspite red beam is active"] =
&RaidKarazhanTriggerContext::netherspite_red_beam_is_active;
creators["netherspite blue beam is active"] =
&RaidKarazhanTriggerContext::netherspite_blue_beam_is_active;
creators["netherspite green beam is active"] =
&RaidKarazhanTriggerContext::netherspite_green_beam_is_active;
creators["netherspite bot is not beam blocker"] =
&RaidKarazhanTriggerContext::netherspite_bot_is_not_beam_blocker;
creators["netherspite boss is banished"] =
&RaidKarazhanTriggerContext::netherspite_boss_is_banished;
creators["netherspite need to manage timers and trackers"] =
&RaidKarazhanTriggerContext::netherspite_need_to_manage_timers_and_trackers;
// Prince Malchezaar
creators["prince malchezaar bot is enfeebled"] =
&RaidKarazhanTriggerContext::prince_malchezaar_bot_is_enfeebled;
creators["prince malchezaar infernals are spawned"] =
&RaidKarazhanTriggerContext::prince_malchezaar_infernals_are_spawned;
creators["prince malchezaar boss engaged by main tank"] =
&RaidKarazhanTriggerContext::prince_malchezaar_boss_engaged_by_main_tank;
// Nightbane
creators["nightbane boss engaged by main tank"] =
&RaidKarazhanTriggerContext::nightbane_boss_engaged_by_main_tank;
creators["nightbane ranged bots are in charred earth"] =
&RaidKarazhanTriggerContext::nightbane_ranged_bots_are_in_charred_earth;
creators["nightbane main tank is susceptible to fear"] =
&RaidKarazhanTriggerContext::nightbane_main_tank_is_susceptible_to_fear;
creators["nightbane pets ignore collision to chase flying boss"] =
&RaidKarazhanTriggerContext::nightbane_pets_ignore_collision_to_chase_flying_boss;
creators["nightbane boss is flying"] =
&RaidKarazhanTriggerContext::nightbane_boss_is_flying;
creators["nightbane need to manage timers and trackers"] =
&RaidKarazhanTriggerContext::nightbane_need_to_manage_timers_and_trackers;
}
private:
// Trash
static Trigger* mana_warp_is_about_to_explode(
PlayerbotAI* botAI) { return new ManaWarpIsAboutToExplodeTrigger(botAI); }
// Attumen the Huntsman
static Trigger* attumen_the_huntsman_need_target_priority(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanNeedTargetPriorityTrigger(botAI); }
static Trigger* attumen_the_huntsman_attumen_spawned(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenSpawnedTrigger(botAI); }
static Trigger* attumen_the_huntsman_attumen_is_mounted(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenIsMountedTrigger(botAI); }
static Trigger* attumen_the_huntsman_boss_wipes_aggro_when_mounting(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(botAI); }
// Moroes
static Trigger* moroes_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new MoroesBossEngagedByMainTankTrigger(botAI); }
static Trigger* moroes_need_target_priority(
PlayerbotAI* botAI) { return new MoroesNeedTargetPriorityTrigger(botAI); }
// Maiden of Virtue
static Trigger* maiden_of_virtue_healers_are_stunned_by_repentance(
PlayerbotAI* botAI) { return new MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(botAI); }
static Trigger* maiden_of_virtue_holy_wrath_deals_chain_damage(
PlayerbotAI* botAI) { return new MaidenOfVirtueHolyWrathDealsChainDamageTrigger(botAI); }
// The Big Bad Wolf
static Trigger* big_bad_wolf_boss_engaged_by_tank(
PlayerbotAI* botAI) { return new BigBadWolfBossEngagedByTankTrigger(botAI); }
static Trigger* big_bad_wolf_boss_is_chasing_little_red_riding_hood(
PlayerbotAI* botAI) { return new BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(botAI); }
// Romulo and Julianne
static Trigger* romulo_and_julianne_both_bosses_revived(
PlayerbotAI* botAI) { return new RomuloAndJulianneBothBossesRevivedTrigger(botAI); }
// The Wizard of Oz
static Trigger* wizard_of_oz_need_target_priority(
PlayerbotAI* botAI) { return new WizardOfOzNeedTargetPriorityTrigger(botAI); }
static Trigger* wizard_of_oz_strawman_is_vulnerable_to_fire(
PlayerbotAI* botAI) { return new WizardOfOzStrawmanIsVulnerableToFireTrigger(botAI); }
// The Curator
static Trigger* the_curator_astral_flare_spawned(
PlayerbotAI* botAI) { return new TheCuratorAstralFlareSpawnedTrigger(botAI); }
static Trigger* the_curator_boss_engaged_by_tanks(
PlayerbotAI* botAI) { return new TheCuratorBossEngagedByTanksTrigger(botAI); }
static Trigger* the_curator_astral_flares_cast_arcing_sear(
PlayerbotAI* botAI) { return new TheCuratorBossAstralFlaresCastArcingSearTrigger(botAI); }
// Terestian Illhoof
static Trigger* terestian_illhoof_need_target_priority(
PlayerbotAI* botAI) { return new TerestianIllhoofNeedTargetPriorityTrigger(botAI); }
// Shade of Aran
static Trigger* shade_of_aran_arcane_explosion_is_casting(
PlayerbotAI* botAI) { return new ShadeOfAranArcaneExplosionIsCastingTrigger(botAI); }
static Trigger* shade_of_aran_flame_wreath_is_active(
PlayerbotAI* botAI) { return new ShadeOfAranFlameWreathIsActiveTrigger(botAI); }
static Trigger* shade_of_aran_conjured_elementals_summoned(
PlayerbotAI* botAI) { return new ShadeOfAranConjuredElementalsSummonedTrigger(botAI); }
static Trigger* shade_of_aran_boss_uses_counterspell_and_blizzard(
PlayerbotAI* botAI) { return new ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(botAI); }
// Netherspite
static Trigger* netherspite_red_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteRedBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_blue_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteBlueBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_green_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteGreenBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_bot_is_not_beam_blocker(
PlayerbotAI* botAI) { return new NetherspiteBotIsNotBeamBlockerTrigger(botAI); }
static Trigger* netherspite_boss_is_banished(
PlayerbotAI* botAI) { return new NetherspiteBossIsBanishedTrigger(botAI); }
static Trigger* netherspite_need_to_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NetherspiteNeedToManageTimersAndTrackersTrigger(botAI); }
// Prince Malchezaar
static Trigger* prince_malchezaar_bot_is_enfeebled(
PlayerbotAI* botAI) { return new PrinceMalchezaarBotIsEnfeebledTrigger(botAI); }
static Trigger* prince_malchezaar_infernals_are_spawned(
PlayerbotAI* botAI) { return new PrinceMalchezaarInfernalsAreSpawnedTrigger(botAI); }
static Trigger* prince_malchezaar_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new PrinceMalchezaarBossEngagedByMainTankTrigger(botAI); }
// Nightbane
static Trigger* nightbane_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new NightbaneBossEngagedByMainTankTrigger(botAI); }
static Trigger* nightbane_ranged_bots_are_in_charred_earth(
PlayerbotAI* botAI) { return new NightbaneRangedBotsAreInCharredEarthTrigger(botAI); }
static Trigger* nightbane_main_tank_is_susceptible_to_fear(
PlayerbotAI* botAI) { return new NightbaneMainTankIsSusceptibleToFearTrigger(botAI); }
static Trigger* nightbane_pets_ignore_collision_to_chase_flying_boss(
PlayerbotAI* botAI) { return new NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(botAI); }
static Trigger* nightbane_boss_is_flying(
PlayerbotAI* botAI) { return new NightbaneBossIsFlyingTrigger(botAI); }
static Trigger* nightbane_need_to_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NightbaneNeedToManageTimersAndTrackersTrigger(botAI); }
};
#endif

View File

@@ -0,0 +1,162 @@
#include "RaidKarazhanStrategy.h"
#include "RaidKarazhanMultipliers.h"
void RaidKarazhanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// Trash
triggers.push_back(new TriggerNode("mana warp is about to explode",
{ NextAction("mana warp stun creature before warp breach", ACTION_EMERGENCY + 6) }
));
// Attumen the Huntsman
triggers.push_back(new TriggerNode("attumen the huntsman need target priority",
{ NextAction("attumen the huntsman mark target", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("attumen the huntsman attumen spawned",
{ NextAction("attumen the huntsman split bosses", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("attumen the huntsman attumen is mounted",
{ NextAction("attumen the huntsman stack behind", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("attumen the huntsman boss wipes aggro when mounting",
{ NextAction("attumen the huntsman manage dps timer", ACTION_RAID + 2) }
));
// Moroes
triggers.push_back(new TriggerNode("moroes boss engaged by main tank",
{ NextAction("moroes main tank attack boss", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("moroes need target priority",
{ NextAction("moroes mark target", ACTION_RAID + 1) }
));
// Maiden of Virtue
triggers.push_back(new TriggerNode("maiden of virtue healers are stunned by repentance",
{ NextAction("maiden of virtue move boss to healer", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("maiden of virtue holy wrath deals chain damage",
{ NextAction("maiden of virtue position ranged", ACTION_RAID + 1) }
));
// The Big Bad Wolf
triggers.push_back(new TriggerNode("big bad wolf boss is chasing little red riding hood",
{ NextAction("big bad wolf run away from boss", ACTION_EMERGENCY + 6) }
));
triggers.push_back(new TriggerNode("big bad wolf boss engaged by tank",
{ NextAction("big bad wolf position boss", ACTION_RAID + 1) }
));
// Romulo and Julianne
triggers.push_back(new TriggerNode("romulo and julianne both bosses revived",
{ NextAction("romulo and julianne mark target", ACTION_RAID + 1) }
));
// The Wizard of Oz
triggers.push_back(new TriggerNode("wizard of oz need target priority",
{ NextAction("wizard of oz mark target", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("wizard of oz strawman is vulnerable to fire",
{ NextAction("wizard of oz scorch strawman", ACTION_RAID + 2) }
));
// The Curator
triggers.push_back(new TriggerNode("the curator astral flare spawned",
{ NextAction("the curator mark astral flare", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("the curator boss engaged by tanks",
{ NextAction("the curator position boss", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("the curator astral flares cast arcing sear",
{ NextAction("the curator spread ranged", ACTION_RAID + 2) }
));
// Terestian Illhoof
triggers.push_back(new TriggerNode("terestian illhoof need target priority",
{ NextAction("terestian illhoof mark target", ACTION_RAID + 1) }
));
// Shade of Aran
triggers.push_back(new TriggerNode("shade of aran arcane explosion is casting",
{ NextAction("shade of aran run away from arcane explosion", ACTION_EMERGENCY + 6) }
));
triggers.push_back(new TriggerNode("shade of aran flame wreath is active",
{ NextAction("shade of aran stop moving during flame wreath", ACTION_EMERGENCY + 7) }
));
triggers.push_back(new TriggerNode("shade of aran conjured elementals summoned",
{ NextAction("shade of aran mark conjured elemental", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("shade of aran boss uses counterspell and blizzard",
{ NextAction("shade of aran ranged maintain distance", ACTION_RAID + 2) }
));
// Netherspite
triggers.push_back(new TriggerNode("netherspite red beam is active",
{ NextAction("netherspite block red beam", ACTION_EMERGENCY + 8) }
));
triggers.push_back(new TriggerNode("netherspite blue beam is active",
{ NextAction("netherspite block blue beam", ACTION_EMERGENCY + 8) }
));
triggers.push_back(new TriggerNode("netherspite green beam is active",
{ NextAction("netherspite block green beam", ACTION_EMERGENCY + 8) }
));
triggers.push_back(new TriggerNode("netherspite bot is not beam blocker",
{ NextAction("netherspite avoid beam and void zone", ACTION_EMERGENCY + 7) }
));
triggers.push_back(new TriggerNode("netherspite boss is banished",
{ NextAction("netherspite banish phase avoid void zone", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("netherspite need to manage timers and trackers",
{ NextAction("netherspite manage timers and trackers", ACTION_EMERGENCY + 10) }
));
// Prince Malchezaar
triggers.push_back(new TriggerNode("prince malchezaar bot is enfeebled",
{ NextAction("prince malchezaar enfeebled avoid hazard", ACTION_EMERGENCY + 6) }
));
triggers.push_back(new TriggerNode("prince malchezaar infernals are spawned",
{ NextAction("prince malchezaar non tank avoid infernal", ACTION_EMERGENCY + 1) }
));
triggers.push_back(new TriggerNode("prince malchezaar boss engaged by main tank",
{ NextAction("prince malchezaar main tank movement", ACTION_EMERGENCY + 6) }
));
// Nightbane
triggers.push_back(new TriggerNode("nightbane boss engaged by main tank",
{ NextAction("nightbane ground phase position boss", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("nightbane ranged bots are in charred earth",
{ NextAction("nightbane ground phase rotate ranged positions", ACTION_EMERGENCY + 1) }
));
triggers.push_back(new TriggerNode("nightbane main tank is susceptible to fear",
{ NextAction("nightbane cast fear ward on main tank", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("nightbane pets ignore collision to chase flying boss",
{ NextAction("nightbane control pet aggression", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("nightbane boss is flying",
{ NextAction("nightbane flight phase movement", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("nightbane need to manage timers and trackers",
{ NextAction("nightbane manage timers and trackers", ACTION_EMERGENCY + 10) }
));
}
void RaidKarazhanStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new AttumenTheHuntsmanDisableTankAssistMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanStayStackedMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanWaitForDpsMultiplier(botAI));
multipliers.push_back(new TheCuratorDisableTankAssistMultiplier(botAI));
multipliers.push_back(new TheCuratorDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new ShadeOfAranArcaneExplosionDisableChargeMultiplier(botAI));
multipliers.push_back(new ShadeOfAranFlameWreathDisableMovementMultiplier(botAI));
multipliers.push_back(new NetherspiteKeepBlockingBeamMultiplier(botAI));
multipliers.push_back(new NetherspiteWaitForDpsMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarDisableAvoidAoeMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarEnfeebleKeepDistanceMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new NightbaneDisablePetsMultiplier(botAI));
multipliers.push_back(new NightbaneWaitForDpsMultiplier(botAI));
multipliers.push_back(new NightbaneDisableAvoidAoeMultiplier(botAI));
multipliers.push_back(new NightbaneDisableMovementMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_
#define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_
#include "Strategy.h"
#include "Multiplier.h"
class RaidKarazhanStrategy : public Strategy
{
public:
RaidKarazhanStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "karazhan"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -0,0 +1,385 @@
#include "RaidKarazhanTriggers.h"
#include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h"
#include "Playerbots.h"
using namespace KarazhanHelpers;
bool ManaWarpIsAboutToExplodeTrigger::IsActive()
{
Unit* manaWarp = AI_VALUE2(Unit*, "find target", "mana warp");
return manaWarp && manaWarp->GetHealthPct() < 15;
}
bool AttumenTheHuntsmanNeedTargetPriorityTrigger::IsActive()
{
if (botAI->IsHeal(bot))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
return midnight != nullptr;
}
bool AttumenTheHuntsmanAttumenSpawnedTrigger::IsActive()
{
if (!botAI->IsAssistTankOfIndex(bot, 0))
return false;
Unit* attumen = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN);
return attumen != nullptr;
}
bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
{
if (botAI->IsMainTank(bot))
return false;
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
return attumenMounted && attumenMounted->GetVictim() != bot;
}
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
return midnight != nullptr;
}
bool MoroesBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes");
return moroes != nullptr;
}
bool MoroesNeedTargetPriorityTrigger::IsActive()
{
if (!botAI->IsDps(bot))
return false;
Unit* dorothea = AI_VALUE2(Unit*, "find target", "baroness dorothea millstipe");
Unit* catriona = AI_VALUE2(Unit*, "find target", "lady catriona von'indi");
Unit* keira = AI_VALUE2(Unit*, "find target", "lady keira berrybuck");
Unit* rafe = AI_VALUE2(Unit*, "find target", "baron rafe dreuger");
Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris");
Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference");
Unit* target = GetFirstAliveUnit({ dorothea, catriona, keira, rafe, robin, crispin });
return target != nullptr;
}
bool MaidenOfVirtueHealersAreStunnedByRepentanceTrigger::IsActive()
{
if (!botAI->IsTank(bot))
return false;
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue");
return maiden && maiden->GetVictim() == bot;
}
bool MaidenOfVirtueHolyWrathDealsChainDamageTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue");
return maiden != nullptr;
}
bool BigBadWolfBossEngagedByTankTrigger::IsActive()
{
if (!botAI->IsTank(bot) || bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
return false;
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
return wolf != nullptr;
}
bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
{
if (!bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
return false;
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
return wolf != nullptr;
}
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
if (!romulo)
return false;
Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne");
if (!julianne)
return false;
return true;
}
bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
Unit* tito = AI_VALUE2(Unit*, "find target", "tito");
Unit* roar = AI_VALUE2(Unit*, "find target", "roar");
Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman");
Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead");
Unit* crone = AI_VALUE2(Unit*, "find target", "the crone");
Unit* target = GetFirstAliveUnit({ dorothee, tito, roar, strawman, tinhead, crone });
return target != nullptr;
}
bool WizardOfOzStrawmanIsVulnerableToFireTrigger::IsActive()
{
if (bot->getClass() != CLASS_MAGE)
return false;
Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman");
return strawman && strawman->IsAlive();
}
bool TheCuratorAstralFlareSpawnedTrigger::IsActive()
{
if (!botAI->IsDps(bot))
return false;
Unit* flare = AI_VALUE2(Unit*, "find target", "astral flare");
return flare != nullptr;
}
bool TheCuratorBossEngagedByTanksTrigger::IsActive()
{
if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0))
return false;
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
return curator != nullptr;
}
bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
return curator != nullptr;
}
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
return illhoof != nullptr;
}
bool ShadeOfAranArcaneExplosionIsCastingTrigger::IsActive()
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION) &&
!IsFlameWreathActive(botAI, bot);
}
bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && IsFlameWreathActive(botAI, bot);
}
// Exclusion of Banish is so the player may Banish elementals if they wish
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
return elemental && elemental->IsAlive() &&
!elemental->HasAura(SPELL_WARLOCK_BANISH);
}
bool ShadeOfAranBossUsesCounterspellAndBlizzardTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && !(aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) &&
!IsFlameWreathActive(botAI, bot);
}
bool NetherspiteRedBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
return redPortal != nullptr;
}
bool NetherspiteBlueBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
return bluePortal != nullptr;
}
bool NetherspiteGreenBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
return greenPortal != nullptr;
}
bool NetherspiteBotIsNotBeamBlockerTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
return bot != redBlocker && bot != blueBlocker && bot != greenBlocker;
}
bool NetherspiteBossIsBanishedTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || !netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
std::vector<Unit*> voidZones = GetAllVoidZones(botAI, bot);
for (Unit* vz : voidZones)
{
if (bot->GetExactDist2d(vz) < 4.0f)
return true;
}
return false;
}
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
{
if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot))
return false;
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
return netherspite != nullptr;
}
bool PrinceMalchezaarBotIsEnfeebledTrigger::IsActive()
{
return bot->HasAura(SPELL_ENFEEBLE);
}
bool PrinceMalchezaarInfernalsAreSpawnedTrigger::IsActive()
{
if (botAI->IsMainTank(bot) || bot->HasAura(SPELL_ENFEEBLE))
return false;
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
return malchezaar != nullptr;
}
bool PrinceMalchezaarBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
return malchezaar != nullptr;
}
bool NightbaneBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
}
bool NightbaneRangedBotsAreInCharredEarthTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
}
bool NightbaneMainTankIsSusceptibleToFearTrigger::IsActive()
{
if (bot->getClass() != CLASS_PRIEST)
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return false;
Player* mainTank = nullptr;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && botAI->IsMainTank(member))
{
mainTank = member;
break;
}
}
}
return mainTank && !mainTank->HasAura(SPELL_FEAR_WARD) &&
botAI->CanCastSpell("fear ward", mainTank);
}
bool NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return false;
Pet* pet = bot->GetPet();
return pet && pet->IsAlive();
}
bool NightbaneBossIsFlyingTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
return false;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 flightPhaseDurationSeconds = 35;
return nightbaneFlightPhaseStartTimer.find(instanceId) != nightbaneFlightPhaseStartTimer.end() &&
(now - nightbaneFlightPhaseStartTimer[instanceId] < flightPhaseDurationSeconds);
}
bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane != nullptr;
}

View File

@@ -0,0 +1,301 @@
#ifndef _PLAYERBOT_RAIDKARAZHANTRIGGERS_H
#define _PLAYERBOT_RAIDKARAZHANTRIGGERS_H
#include "Trigger.h"
class ManaWarpIsAboutToExplodeTrigger : public Trigger
{
public:
ManaWarpIsAboutToExplodeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "mana warp is about to explode") {}
bool IsActive() override;
};
class AttumenTheHuntsmanNeedTargetPriorityTrigger : public Trigger
{
public:
AttumenTheHuntsmanNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman need target priority") {}
bool IsActive() override;
};
class AttumenTheHuntsmanAttumenSpawnedTrigger : public Trigger
{
public:
AttumenTheHuntsmanAttumenSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen spawned") {}
bool IsActive() override;
};
class AttumenTheHuntsmanAttumenIsMountedTrigger : public Trigger
{
public:
AttumenTheHuntsmanAttumenIsMountedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen is mounted") {}
bool IsActive() override;
};
class AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger : public Trigger
{
public:
AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman boss wipes aggro when mounting") {}
bool IsActive() override;
};
class MoroesBossEngagedByMainTankTrigger : public Trigger
{
public:
MoroesBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "moroes boss engaged by main tank") {}
bool IsActive() override;
};
class MoroesNeedTargetPriorityTrigger : public Trigger
{
public:
MoroesNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "moroes need target priority") {}
bool IsActive() override;
};
class MaidenOfVirtueHealersAreStunnedByRepentanceTrigger : public Trigger
{
public:
MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue healers are stunned by repentance") {}
bool IsActive() override;
};
class MaidenOfVirtueHolyWrathDealsChainDamageTrigger : public Trigger
{
public:
MaidenOfVirtueHolyWrathDealsChainDamageTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue holy wrath deals chain damage") {}
bool IsActive() override;
};
class BigBadWolfBossEngagedByTankTrigger : public Trigger
{
public:
BigBadWolfBossEngagedByTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss engaged by tank") {}
bool IsActive() override;
};
class BigBadWolfBossIsChasingLittleRedRidingHoodTrigger : public Trigger
{
public:
BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss is chasing little red riding hood") {}
bool IsActive() override;
};
class RomuloAndJulianneBothBossesRevivedTrigger : public Trigger
{
public:
RomuloAndJulianneBothBossesRevivedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "romulo and julianne both bosses revived") {}
bool IsActive() override;
};
class WizardOfOzNeedTargetPriorityTrigger : public Trigger
{
public:
WizardOfOzNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz need target priority") {}
bool IsActive() override;
};
class WizardOfOzStrawmanIsVulnerableToFireTrigger : public Trigger
{
public:
WizardOfOzStrawmanIsVulnerableToFireTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz strawman is vulnerable to fire") {}
bool IsActive() override;
};
class TheCuratorAstralFlareSpawnedTrigger : public Trigger
{
public:
TheCuratorAstralFlareSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flare spawned") {}
bool IsActive() override;
};
class TheCuratorBossEngagedByTanksTrigger : public Trigger
{
public:
TheCuratorBossEngagedByTanksTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator boss engaged by tanks") {}
bool IsActive() override;
};
class TheCuratorBossAstralFlaresCastArcingSearTrigger : public Trigger
{
public:
TheCuratorBossAstralFlaresCastArcingSearTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flares cast arcing sear") {}
bool IsActive() override;
};
class TerestianIllhoofNeedTargetPriorityTrigger : public Trigger
{
public:
TerestianIllhoofNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "terestian illhoof need target priority") {}
bool IsActive() override;
};
class ShadeOfAranArcaneExplosionIsCastingTrigger : public Trigger
{
public:
ShadeOfAranArcaneExplosionIsCastingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran arcane explosion is casting") {}
bool IsActive() override;
};
class ShadeOfAranFlameWreathIsActiveTrigger : public Trigger
{
public:
ShadeOfAranFlameWreathIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran flame wreath is active") {}
bool IsActive() override;
};
class ShadeOfAranConjuredElementalsSummonedTrigger : public Trigger
{
public:
ShadeOfAranConjuredElementalsSummonedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran conjured elementals summoned") {}
bool IsActive() override;
};
class ShadeOfAranBossUsesCounterspellAndBlizzardTrigger : public Trigger
{
public:
ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran boss uses counterspell and blizzard") {}
bool IsActive() override;
};
class NetherspiteRedBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteRedBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite red beam is active") {}
bool IsActive() override;
};
class NetherspiteBlueBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteBlueBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite blue beam is active") {}
bool IsActive() override;
};
class NetherspiteGreenBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteGreenBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite green beam is active") {}
bool IsActive() override;
};
class NetherspiteBotIsNotBeamBlockerTrigger : public Trigger
{
public:
NetherspiteBotIsNotBeamBlockerTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite bot is not beam blocker") {}
bool IsActive() override;
};
class NetherspiteBossIsBanishedTrigger : public Trigger
{
public:
NetherspiteBossIsBanishedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite boss is banished") {}
bool IsActive() override;
};
class NetherspiteNeedToManageTimersAndTrackersTrigger : public Trigger
{
public:
NetherspiteNeedToManageTimersAndTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite need to manage timers and trackers") {}
bool IsActive() override;
};
class PrinceMalchezaarBotIsEnfeebledTrigger : public Trigger
{
public:
PrinceMalchezaarBotIsEnfeebledTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar bot is enfeebled") {}
bool IsActive() override;
};
class PrinceMalchezaarInfernalsAreSpawnedTrigger : public Trigger
{
public:
PrinceMalchezaarInfernalsAreSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar infernals are spawned") {}
bool IsActive() override;
};
class PrinceMalchezaarBossEngagedByMainTankTrigger : public Trigger
{
public:
PrinceMalchezaarBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar boss engaged by main tank") {}
bool IsActive() override;
};
class NightbaneBossEngagedByMainTankTrigger : public Trigger
{
public:
NightbaneBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss engaged by main tank") {}
bool IsActive() override;
};
class NightbaneRangedBotsAreInCharredEarthTrigger : public Trigger
{
public:
NightbaneRangedBotsAreInCharredEarthTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane ranged bots are in charred earth") {}
bool IsActive() override;
};
class NightbaneMainTankIsSusceptibleToFearTrigger : public Trigger
{
public:
NightbaneMainTankIsSusceptibleToFearTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane main tank is susceptible to fear") {}
bool IsActive() override;
};
class NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger : public Trigger
{
public:
NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane pets ignore collision to chase flying boss") {}
bool IsActive() override;
};
class NightbaneBossIsFlyingTrigger : public Trigger
{
public:
NightbaneBossIsFlyingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss is flying") {}
bool IsActive() override;
};
class NightbaneNeedToManageTimersAndTrackersTrigger : public Trigger
{
public:
NightbaneNeedToManageTimersAndTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane need to manage timers and trackers") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,490 @@
#include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
namespace KarazhanHelpers
{
// Attumen the Huntsman
std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
// Big Bad Wolf
std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
// Netherspite
std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer;
std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer;
std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways;
// Nightbane
std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
const Position MAIDEN_OF_VIRTUE_BOSS_POSITION = { -10945.881f, -2103.782f, 92.712f };
const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8] =
{
{ -10931.178f, -2116.580f, 92.179f },
{ -10925.828f, -2102.425f, 92.180f },
{ -10933.089f, -2088.502f, 92.180f },
{ -10947.590f, -2082.815f, 92.180f },
{ -10960.912f, -2090.437f, 92.179f },
{ -10966.017f, -2105.288f, 92.175f },
{ -10959.242f, -2119.617f, 92.180f },
{ -10944.495f, -2123.857f, 92.180f },
};
const Position BIG_BAD_WOLF_BOSS_POSITION = { -10913.391f, -1773.508f, 90.477f };
const Position BIG_BAD_WOLF_RUN_POSITION[4] =
{
{ -10875.456f, -1779.036f, 90.477f },
{ -10872.281f, -1751.638f, 90.477f },
{ -10910.492f, -1747.401f, 90.477f },
{ -10913.391f, -1773.508f, 90.477f },
};
const Position THE_CURATOR_BOSS_POSITION = { -11139.463f, -1884.645f, 165.765f };
const Position NIGHTBANE_TRANSITION_BOSS_POSITION = { -11160.646f, -1932.773f, 91.473f }; // near some ribs
const Position NIGHTBANE_FINAL_BOSS_POSITION = { -11173.530f, -1940.707f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION1 = { -11145.949f, -1970.927f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION2 = { -11143.594f, -1954.981f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION3 = { -11159.778f, -1961.031f, 91.473f };
const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel
const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f };
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSkull(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
// Only one bot is needed to set/reset instance-wide timers
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return false;
}
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units)
{
for (Unit* unit : units)
{
if (unit && unit->IsAlive())
return unit;
}
return nullptr;
}
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
{
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
return unit;
}
return nullptr;
}
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot)
{
Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get();
Spell* currentSpell = aran ? aran->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
if (currentSpell && currentSpell->m_spellInfo &&
currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH_CAST)
return true;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (member->HasAura(SPELL_FLAME_WREATH_AURA))
return true;
}
}
return false;
}
// Red beam blockers: tank bots, no Nether Exhaustion Red
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot)
{
std::vector<Player*> redBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) ||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
continue;
redBlockers.push_back(member);
}
}
return redBlockers;
}
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and <24 stacks of Blue Beam debuff
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot)
{
std::vector<Player*> blueBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 24;
bool isDps = botAI->IsDps(member);
bool isWarrior = member->getClass() == CLASS_WARRIOR;
bool isRogue = member->getClass() == CLASS_ROGUE;
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
blueBlockers.push_back(member);
}
}
return blueBlockers;
}
// Green beam blockers:
// (1) Prioritize Rogues and non-tank Warrior bots, no Nether Exhaustion Green
// (2) Then assign Healer bots, no Nether Exhaustion Green and <24 stacks of Green Beam debuff
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot)
{
std::vector<Player*> greenBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
bool isRogue = member->getClass() == CLASS_ROGUE;
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
if (eligibleRogueWarrior)
greenBlockers.push_back(member);
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 24;
bool isHealer = botAI->IsHeal(member);
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
if (eligibleHealer)
greenBlockers.push_back(member);
}
}
return greenBlockers;
}
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot)
{
static ObjectGuid currentRedBlocker;
static ObjectGuid currentGreenBlocker;
static ObjectGuid currentBlueBlocker;
Player* redBlocker = nullptr;
Player* greenBlocker = nullptr;
Player* blueBlocker = nullptr;
std::vector<Player*> redBlockers = GetRedBlockers(botAI, bot);
if (!redBlockers.empty())
{
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* player)
{
return player && player->GetGUID() == currentRedBlocker;
});
if (it != redBlockers.end())
redBlocker = *it;
else
redBlocker = redBlockers.front();
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
currentRedBlocker = ObjectGuid::Empty;
redBlocker = nullptr;
}
std::vector<Player*> greenBlockers = GetGreenBlockers(botAI, bot);
if (!greenBlockers.empty())
{
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* player)
{
return player && player->GetGUID() == currentGreenBlocker;
});
if (it != greenBlockers.end())
greenBlocker = *it;
else
greenBlocker = greenBlockers.front();
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
currentGreenBlocker = ObjectGuid::Empty;
greenBlocker = nullptr;
}
std::vector<Player*> blueBlockers = GetBlueBlockers(botAI, bot);
if (!blueBlockers.empty())
{
auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* player)
{
return player && player->GetGUID() == currentBlueBlocker;
});
if (it != blueBlockers.end())
blueBlocker = *it;
else
blueBlocker = blueBlockers.front();
currentBlueBlocker = blueBlocker ? blueBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
currentBlueBlocker = ObjectGuid::Empty;
blueBlocker = nullptr;
}
return std::make_tuple(redBlocker, greenBlocker, blueBlocker);
}
std::vector<Unit*> GetAllVoidZones(PlayerbotAI* botAI, Player* bot)
{
std::vector<Unit*> voidZones;
const float radius = 30.0f;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (!unit || unit->GetEntry() != NPC_VOID_ZONE)
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
voidZones.push_back(unit);
}
return voidZones;
}
bool IsSafePosition(float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius)
{
for (Unit* hazard : hazards)
{
float dist = hazard->GetExactDist2d(x, y);
if (dist < hazardRadius)
return false;
}
return true;
}
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI)
{
std::vector<Unit*> infernals;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
infernals.push_back(unit);
}
return infernals;
}
bool IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards,
float hazardRadius, float stepSize)
{
float sx = start.GetPositionX();
float sy = start.GetPositionY();
float sz = start.GetPositionZ();
float tx = target.GetPositionX();
float ty = target.GetPositionY();
float tz = target.GetPositionZ();
const float totalDist = start.GetExactDist2d(target.GetPositionX(), target.GetPositionY());
if (totalDist == 0.0f)
return true;
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
{
float t = checkDist / totalDist;
float checkX = sx + (tx - sx) * t;
float checkY = sy + (ty - sy) * t;
float checkZ = sz + (tz - sz) * t;
for (Unit* hazard : hazards)
{
const float hx = checkX - hazard->GetPositionX();
const float hy = checkY - hazard->GetPositionY();
if ((hx*hx + hy*hy) < hazardRadius * hazardRadius)
return false;
}
}
return true;
}
bool TryFindSafePositionWithSafePath(
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ)
{
float bestMoveDist = std::numeric_limits<float>::max();
bool found = false;
for (int i = 0; i < numAngles; ++i)
{
float angle = (2.0f * M_PI * i) / numAngles;
float dx = cos(angle);
float dy = sin(angle);
for (float dist = stepSize; dist <= maxSampleDist; dist += stepSize)
{
float x = centerX + dx * dist;
float y = centerY + dy * dist;
float z = centerZ;
float destX = x, destY = y, destZ = z;
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, centerX, centerY, centerZ,
destX, destY, destZ, true))
continue;
if (!IsSafePosition(destX, destY, destZ, hazards, safeDistance))
continue;
if (requireSafePath)
{
if (!IsStraightPathSafe(Position(originX, originY, originZ), Position(destX, destY, destZ),
hazards, safeDistance, stepSize))
continue;
}
const float moveDist = Position(originX, originY, originZ).GetExactDist2d(destX, destY);
if (moveDist < bestMoveDist)
{
bestMoveDist = moveDist;
bestDestX = destX;
bestDestY = destY;
bestDestZ = destZ;
found = true;
}
}
}
return found;
}
}

View File

@@ -0,0 +1,136 @@
#ifndef _PLAYERBOT_RAIDKARAZHANHELPERS_H_
#define _PLAYERBOT_RAIDKARAZHANHELPERS_H_
#include <ctime>
#include <unordered_map>
#include "AiObject.h"
#include "Position.h"
#include "Unit.h"
namespace KarazhanHelpers
{
enum KarazhanSpells
{
// Maiden of Virtue
SPELL_REPENTANCE = 29511,
// Opera Event
SPELL_LITTLE_RED_RIDING_HOOD = 30756,
// The Curator
SPELL_CURATOR_EVOCATION = 30254,
// Shade of Aran
SPELL_FLAME_WREATH_CAST = 30004,
SPELL_FLAME_WREATH_AURA = 29946,
SPELL_ARCANE_EXPLOSION = 29973,
// Netherspite
SPELL_RED_BEAM_DEBUFF = 30421, // "Nether Portal - Perseverance" (player aura)
SPELL_GREEN_BEAM_DEBUFF = 30422, // "Nether Portal - Serenity" (player aura)
SPELL_BLUE_BEAM_DEBUFF = 30423, // "Nether Portal - Dominance" (player aura)
SPELL_GREEN_BEAM_HEAL = 30467, // "Nether Portal - Serenity" (Netherspite aura)
SPELL_NETHER_EXHAUSTION_RED = 38637,
SPELL_NETHER_EXHAUSTION_GREEN = 38638,
SPELL_NETHER_EXHAUSTION_BLUE = 38639,
SPELL_NETHERSPITE_BANISHED = 39833, // "Vortex Shade Black"
// Prince Malchezaar
SPELL_ENFEEBLE = 30843,
// Nightbane
SPELL_CHARRED_EARTH = 30129,
SPELL_BELLOWING_ROAR = 36922,
SPELL_RAIN_OF_BONES = 37091,
// Warlock
SPELL_WARLOCK_BANISH = 18647,
// Priest
SPELL_FEAR_WARD = 6346,
};
enum KarazhanNPCs
{
// Trash
NPC_SPECTRAL_RETAINER = 16410,
NPC_MANA_WARP = 16530,
// Attumen the Huntsman
NPC_ATTUMEN_THE_HUNTSMAN = 15550,
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Shade of Aran
NPC_CONJURED_ELEMENTAL = 17167,
// Netherspite
NPC_VOID_ZONE = 16697,
NPC_GREEN_PORTAL = 17367, // "Nether Portal - Serenity <Healing Portal>"
NPC_BLUE_PORTAL = 17368, // "Nether Portal - Dominance <Damage Portal>"
NPC_RED_PORTAL = 17369, // "Nether Portal - Perseverance <Tanking Portal>"
// Prince Malchezaar
NPC_NETHERSPITE_INFERNAL = 17646,
};
const uint32 KARAZHAN_MAP_ID = 532;
const float NIGHTBANE_FLIGHT_Z = 95.0f;
// Attumen the Huntsman
extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
// Big Bad Wolf
extern std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
// Netherspite
extern std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer;
extern std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer;
extern std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways;
// Nightbane
extern std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
extern std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
extern std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
extern std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
extern std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
extern const Position MAIDEN_OF_VIRTUE_BOSS_POSITION;
extern const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8];
extern const Position BIG_BAD_WOLF_BOSS_POSITION;
extern const Position BIG_BAD_WOLF_RUN_POSITION[4];
extern const Position THE_CURATOR_BOSS_POSITION;
extern const Position NIGHTBANE_TRANSITION_BOSS_POSITION;
extern const Position NIGHTBANE_FINAL_BOSS_POSITION;
extern const Position NIGHTBANE_RANGED_POSITION1;
extern const Position NIGHTBANE_RANGED_POSITION2;
extern const Position NIGHTBANE_RANGED_POSITION3;
extern const Position NIGHTBANE_FLIGHT_STACK_POSITION;
extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION;
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSkull(Player* bot, Unit* target);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot);
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Unit*> GetAllVoidZones(PlayerbotAI *botAI, Player* bot);
bool IsSafePosition (float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius);
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI);
bool IsStraightPathSafe(
const Position& start, const Position& target,
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
bool TryFindSafePositionWithSafePath(
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ);
}
#endif

View File

@@ -0,0 +1,695 @@
#include "RaidMagtheridonActions.h"
#include "RaidMagtheridonHelpers.h"
#include "Creature.h"
#include "ObjectAccessor.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
using namespace MagtheridonHelpers;
bool MagtheridonMainTankAttackFirstThreeChannelersAction::Execute(Event event)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon)
return false;
Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER);
if (channelerSquare && channelerSquare->IsAlive())
MarkTargetWithSquare(bot, channelerSquare);
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
if (channelerStar && channelerStar->IsAlive())
MarkTargetWithStar(bot, channelerStar);
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
if (channelerCircle && channelerCircle->IsAlive())
MarkTargetWithCircle(bot, channelerCircle);
// After first three channelers are dead, wait for Magtheridon to activate
if ((!channelerSquare || !channelerSquare->IsAlive()) &&
(!channelerStar || !channelerStar->IsAlive()) &&
(!channelerCircle || !channelerCircle->IsAlive()))
{
const Location& position = MagtheridonsLairLocations::WaitingForMagtheridonPosition;
if (!bot->IsWithinDist2d(position.x, position.y, 2.0f))
{
return MoveTo(bot->GetMapId(), position.x, position.y, position.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
bot->SetFacingTo(position.orientation);
return true;
}
Creature* currentTarget = nullptr;
std::string rtiName;
if (channelerSquare && channelerSquare->IsAlive())
{
currentTarget = channelerSquare;
rtiName = "square";
}
else if (channelerStar && channelerStar->IsAlive())
{
currentTarget = channelerStar;
rtiName = "star";
}
else if (channelerCircle && channelerCircle->IsAlive())
{
currentTarget = channelerCircle;
rtiName = "circle";
}
SetRtiTarget(botAI, rtiName, currentTarget);
if (currentTarget && bot->GetVictim() != currentTarget)
return Attack(currentTarget);
return false;
}
bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
{
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
if (!channelerDiamond || !channelerDiamond->IsAlive())
return false;
MarkTargetWithDiamond(bot, channelerDiamond);
SetRtiTarget(botAI, "diamond", channelerDiamond);
if (bot->GetVictim() != channelerDiamond)
return Attack(channelerDiamond);
if (channelerDiamond->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::NWChannelerTankPosition;
const float maxDistance = 3.0f;
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
return false;
}
bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event event)
{
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (!channelerTriangle || !channelerTriangle->IsAlive())
return false;
MarkTargetWithTriangle(bot, channelerTriangle);
SetRtiTarget(botAI, "triangle", channelerTriangle);
if (bot->GetVictim() != channelerTriangle)
return Attack(channelerTriangle);
if (channelerTriangle->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::NEChannelerTankPosition;
const float maxDistance = 3.0f;
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
return false;
}
// Misdirect West & East Channelers to Main Tank
bool MagtheridonMisdirectHellfireChannelers::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
std::vector<Player*> hunters;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member))
hunters.push_back(member);
}
int hunterIndex = -1;
for (size_t i = 0; i < hunters.size(); ++i)
{
if (hunters[i] == bot)
{
hunterIndex = static_cast<int>(i);
break;
}
}
Player* mainTank = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsMainTank(member))
{
mainTank = member;
break;
}
}
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
switch (hunterIndex)
{
case 0:
if (mainTank && channelerStar && channelerStar->IsAlive() &&
channelerStar->GetVictim() != mainTank)
{
if (botAI->CanCastSpell("misdirection", mainTank))
return botAI->CastSpell("misdirection", mainTank);
if (!bot->HasAura(SPELL_MISDIRECTION))
return false;
if (botAI->CanCastSpell("steady shot", channelerStar))
return botAI->CastSpell("steady shot", channelerStar);
}
break;
case 1:
if (mainTank && channelerCircle && channelerCircle->IsAlive() &&
channelerCircle->GetVictim() != mainTank)
{
if (botAI->CanCastSpell("misdirection", mainTank))
return botAI->CastSpell("misdirection", mainTank);
if (!bot->HasAura(SPELL_MISDIRECTION))
return false;
if (botAI->CanCastSpell("steady shot", channelerCircle))
return botAI->CastSpell("steady shot", channelerCircle);
}
break;
default:
break;
}
return false;
}
bool MagtheridonAssignDPSPriorityAction::Execute(Event event)
{
// Listed in order of priority
Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER);
if (channelerSquare && channelerSquare->IsAlive())
{
SetRtiTarget(botAI, "square", channelerSquare);
if (bot->GetTarget() != channelerSquare->GetGUID())
{
bot->SetSelection(channelerSquare->GetGUID());
return Attack(channelerSquare);
}
return false;
}
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
if (channelerStar && channelerStar->IsAlive())
{
SetRtiTarget(botAI, "star", channelerStar);
if (bot->GetTarget() != channelerStar->GetGUID())
{
bot->SetSelection(channelerStar->GetGUID());
return Attack(channelerStar);
}
return false;
}
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
if (channelerCircle && channelerCircle->IsAlive())
{
SetRtiTarget(botAI, "circle", channelerCircle);
if (bot->GetTarget() != channelerCircle->GetGUID())
{
bot->SetSelection(channelerCircle->GetGUID());
return Attack(channelerCircle);
}
return false;
}
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
if (channelerDiamond && channelerDiamond->IsAlive())
{
SetRtiTarget(botAI, "diamond", channelerDiamond);
if (bot->GetTarget() != channelerDiamond->GetGUID())
{
bot->SetSelection(channelerDiamond->GetGUID());
return Attack(channelerDiamond);
}
return false;
}
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (channelerTriangle && channelerTriangle->IsAlive())
{
SetRtiTarget(botAI, "triangle", channelerTriangle);
if (bot->GetTarget() != channelerTriangle->GetGUID())
{
bot->SetSelection(channelerTriangle->GetGUID());
return Attack(channelerTriangle);
}
return false;
}
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE) &&
(!channelerSquare || !channelerSquare->IsAlive()) &&
(!channelerStar || !channelerStar->IsAlive()) &&
(!channelerCircle || !channelerCircle->IsAlive()) &&
(!channelerDiamond || !channelerDiamond->IsAlive()) &&
(!channelerTriangle || !channelerTriangle->IsAlive()))
{
SetRtiTarget(botAI, "cross", magtheridon);
if (bot->GetTarget() != magtheridon->GetGUID())
{
bot->SetSelection(magtheridon->GetGUID());
return Attack(magtheridon);
}
}
return false;
}
// Assign Burning Abyssals to Warlocks to Banish
// Burning Abyssals in excess of Warlocks in party will be Feared
bool MagtheridonWarlockCCBurningAbyssalAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
std::vector<Unit*> abyssals;
for (auto const& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == NPC_BURNING_ABYSSAL && unit->IsAlive())
abyssals.push_back(unit);
}
std::vector<Player*> warlocks;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_WARLOCK && GET_PLAYERBOT_AI(member))
warlocks.push_back(member);
}
int warlockIndex = -1;
for (size_t i = 0; i < warlocks.size(); ++i)
{
if (warlocks[i] == bot)
{
warlockIndex = static_cast<int>(i);
break;
}
}
if (warlockIndex >= 0 && warlockIndex < abyssals.size())
{
Unit* assignedAbyssal = abyssals[warlockIndex];
if (!assignedAbyssal->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedAbyssal, true))
return botAI->CastSpell("banish", assignedAbyssal);
}
for (size_t i = warlocks.size(); i < abyssals.size(); ++i)
{
Unit* excessAbyssal = abyssals[i];
if (!excessAbyssal->HasAura(SPELL_BANISH) && !excessAbyssal->HasAura(SPELL_FEAR) &&
botAI->CanCastSpell(SPELL_FEAR, excessAbyssal, true))
return botAI->CastSpell("fear", excessAbyssal);
}
return false;
}
// Main tank will back up to the Northern point of the room
bool MagtheridonMainTankPositionBossAction::Execute(Event event)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon)
return false;
MarkTargetWithCross(bot, magtheridon);
SetRtiTarget(botAI, "cross", magtheridon);
if (bot->GetVictim() != magtheridon)
return Attack(magtheridon);
if (magtheridon->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::MagtheridonTankPosition;
const float maxDistance = 2.0f;
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, true);
}
bot->SetFacingTo(position.orientation);
}
return false;
}
// Ranged DPS will remain within 25 yards of the center of the room
// Healers will remain within 15 yards of a position that is between ranged DPS and the boss
std::unordered_map<ObjectGuid, Position> MagtheridonSpreadRangedAction::initialPositions;
std::unordered_map<ObjectGuid, bool> MagtheridonSpreadRangedAction::hasReachedInitialPosition;
bool MagtheridonSpreadRangedAction::Execute(Event event)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon)
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
// Wait for 6 seconds after Magtheridon activates to spread
const uint8 spreadWaitSeconds = 6;
auto it = spreadWaitTimer.find(instanceId);
if (it == spreadWaitTimer.end() ||
(time(nullptr) - it->second) < spreadWaitSeconds)
return false;
auto cubeIt = botToCubeAssignment.find(bot->GetGUID());
if (cubeIt != botToCubeAssignment.end())
{
time_t now = time(nullptr);
auto timerIt = blastNovaTimer.find(instanceId);
if (timerIt != blastNovaTimer.end())
{
time_t lastBlastNova = timerIt->second;
if (now - lastBlastNova >= 49)
return false;
}
}
std::vector<Player*> members;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive())
members.push_back(member);
}
bool isHealer = botAI->IsHeal(bot);
const Location& center = isHealer
? MagtheridonsLairLocations::HealerSpreadPosition
: MagtheridonsLairLocations::RangedSpreadPosition;
float maxSpreadRadius = isHealer ? 15.0f : 20.0f;
float centerX = center.x;
float centerY = center.y;
float centerZ = bot->GetPositionZ();
const float radiusBuffer = 3.0f;
if (!initialPositions.count(bot->GetGUID()))
{
auto it = std::find(members.begin(), members.end(), bot);
uint8 botIndex = (it != members.end()) ? std::distance(members.begin(), it) : 0;
uint8 count = members.size();
float angle = 2 * M_PI * botIndex / count;
float radius = static_cast<float>(rand()) / RAND_MAX * maxSpreadRadius;
float targetX = centerX + radius * cos(angle);
float targetY = centerY + radius * sin(angle);
initialPositions[bot->GetGUID()] = Position(targetX, targetY, centerZ);
hasReachedInitialPosition[bot->GetGUID()] = false;
}
Position targetPosition = initialPositions[bot->GetGUID()];
if (!hasReachedInitialPosition[bot->GetGUID()])
{
if (!bot->IsWithinDist2d(targetPosition.GetPositionX(), targetPosition.GetPositionY(), 2.0f))
{
float destX = targetPosition.GetPositionX();
float destY = targetPosition.GetPositionY();
float destZ = targetPosition.GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(),
bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ))
return false;
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
hasReachedInitialPosition[bot->GetGUID()] = true;
}
float distToCenter = bot->GetExactDist2d(centerX, centerY);
if (distToCenter > maxSpreadRadius + radiusBuffer)
{
float angle = static_cast<float>(rand()) / RAND_MAX * 2.0f * M_PI;
float radius = static_cast<float>(rand()) / RAND_MAX * maxSpreadRadius;
float targetX = centerX + radius * cos(angle);
float targetY = centerY + radius * sin(angle);
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), targetX, targetY, centerZ))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), targetX, targetY, centerZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
return false;
}
// For bots that are assigned to click cubes
// Magtheridon casts Blast Nova every 54.35 to 55.40s, with a 2s cast time
bool MagtheridonUseManticronCubeAction::Execute(Event event)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon)
return false;
auto it = botToCubeAssignment.find(bot->GetGUID());
const CubeInfo& cubeInfo = it->second;
GameObject* cube = botAI->GetGameObject(cubeInfo.guid);
if (!cube)
return false;
// Release cubes after Blast Nova is interrupted
if (HandleCubeRelease(magtheridon, cube))
return true;
// Check if cube logic should be active (49+ second rule)
if (!ShouldActivateCubeLogic(magtheridon))
return false;
// Handle active cube logic based on Blast Nova casting state
bool blastNovaActive = magtheridon->HasUnitState(UNIT_STATE_CASTING) &&
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
if (!blastNovaActive)
// After 49 seconds, wait at safe distance from cube
return HandleWaitingPhase(cubeInfo);
else
// Blast Nova is casting - move to and click cube
return HandleCubeInteraction(cubeInfo, cube);
return false;
}
bool MagtheridonUseManticronCubeAction::HandleCubeRelease(Unit* magtheridon, GameObject* cube)
{
if (bot->HasAura(SPELL_SHADOW_GRASP) &&
!(magtheridon->HasUnitState(UNIT_STATE_CASTING) &&
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA)))
{
uint32 delay = urand(200, 3000);
botAI->AddTimedEvent(
[this]
{
botAI->Reset();
},
delay);
botAI->SetNextCheckDelay(delay + 50);
return true;
}
return false;
}
bool MagtheridonUseManticronCubeAction::ShouldActivateCubeLogic(Unit* magtheridon)
{
auto timerIt = blastNovaTimer.find(magtheridon->GetMap()->GetInstanceId());
if (timerIt == blastNovaTimer.end())
return false;
time_t now = time(nullptr);
time_t lastBlastNova = timerIt->second;
return (now - lastBlastNova >= 49);
}
bool MagtheridonUseManticronCubeAction::HandleWaitingPhase(const CubeInfo& cubeInfo)
{
const float safeWaitDistance = 8.0f;
float cubeDist = bot->GetExactDist2d(cubeInfo.x, cubeInfo.y);
if (fabs(cubeDist - safeWaitDistance) > 1.0f)
{
for (int i = 0; i < 12; ++i)
{
float angle = i * M_PI / 6.0f;
float targetX = cubeInfo.x + cos(angle) * safeWaitDistance;
float targetY = cubeInfo.y + sin(angle) * safeWaitDistance;
float targetZ = bot->GetPositionZ();
if (IsSafeFromMagtheridonHazards(botAI, bot, targetX, targetY, targetZ))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
float angle = static_cast<float>(rand()) / RAND_MAX * 2.0f * M_PI;
float fallbackX = cubeInfo.x + cos(angle) * safeWaitDistance;
float fallbackY = cubeInfo.y + sin(angle) * safeWaitDistance;
float fallbackZ = bot->GetPositionZ();
return MoveTo(bot->GetMapId(), fallbackX, fallbackY, fallbackZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return true;
}
bool MagtheridonUseManticronCubeAction::HandleCubeInteraction(const CubeInfo& cubeInfo, GameObject* cube)
{
const float interactDistance = 1.0f;
float cubeDist = bot->GetExactDist2d(cubeInfo.x, cubeInfo.y);
if (cubeDist > interactDistance)
{
if (cubeDist <= interactDistance + 1.0f)
{
uint32 delay = urand(200, 1500);
botAI->AddTimedEvent(
[this, cube]
{
bot->StopMoving();
cube->Use(bot);
},
delay);
botAI->SetNextCheckDelay(delay + 50);
return true;
}
float angle = atan2(cubeInfo.y - bot->GetPositionY(), cubeInfo.x - bot->GetPositionX());
float targetX = cubeInfo.x - cos(angle) * interactDistance;
float targetY = cubeInfo.y - sin(angle) * interactDistance;
float targetZ = bot->GetPositionZ();
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
}
return false;
}
// The Blast Nova timer resets when Magtheridon stops casting it, which is needed to ensure that bots use cubes.
// However, Magtheridon's Blast Nova cooldown actually runs from when he starts casting it. This means that if a Blast Nova
// is not interrupted or takes too long to interrupt, the timer will be thrown off for the rest of the encounter.
// Correcting this issue is complicated and probably would need some rewriting--I have not done so and
// and view the current solution as sufficient since in TBC a missed Blast Nova would be a guaranteed wipe anyway.
bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon)
return false;
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
const time_t now = time(nullptr);
bool blastNovaActive = magtheridon->HasUnitState(UNIT_STATE_CASTING) &&
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
bool lastBlastNova = lastBlastNovaState[instanceId];
if (lastBlastNova && !blastNovaActive && IsInstanceTimerManager(botAI, bot))
blastNovaTimer[instanceId] = now;
lastBlastNovaState[instanceId] = blastNovaActive;
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
{
if (IsInstanceTimerManager(botAI, bot))
{
spreadWaitTimer.try_emplace(instanceId, now);
blastNovaTimer.try_emplace(instanceId, now);
dpsWaitTimer.try_emplace(instanceId, now);
}
}
else
{
MagtheridonSpreadRangedAction::initialPositions.clear();
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
botToCubeAssignment.clear();
if (IsInstanceTimerManager(botAI, bot))
{
spreadWaitTimer.erase(instanceId);
blastNovaTimer.erase(instanceId);
dpsWaitTimer.erase(instanceId);
}
}
return false;
}

View File

@@ -0,0 +1,100 @@
#ifndef _PLAYERBOT_RAIDMAGTHERIDONACTIONS_H
#define _PLAYERBOT_RAIDMAGTHERIDONACTIONS_H
#include "RaidMagtheridonHelpers.h"
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
using namespace MagtheridonHelpers;
class MagtheridonMainTankAttackFirstThreeChannelersAction : public AttackAction
{
public:
MagtheridonMainTankAttackFirstThreeChannelersAction(PlayerbotAI* botAI, std::string const name = "magtheridon main tank attack first three channelers") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class MagtheridonFirstAssistTankAttackNWChannelerAction : public AttackAction
{
public:
MagtheridonFirstAssistTankAttackNWChannelerAction(PlayerbotAI* botAI, std::string const name = "magtheridon first assist tank attack nw channeler") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class MagtheridonSecondAssistTankAttackNEChannelerAction : public AttackAction
{
public:
MagtheridonSecondAssistTankAttackNEChannelerAction(PlayerbotAI* botAI, std::string const name = "magtheridon second assist tank attack ne channeler") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class MagtheridonMisdirectHellfireChannelers : public AttackAction
{
public:
MagtheridonMisdirectHellfireChannelers(PlayerbotAI* botAI, std::string const name = "magtheridon misdirect hellfire channelers") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class MagtheridonAssignDPSPriorityAction : public AttackAction
{
public:
MagtheridonAssignDPSPriorityAction(PlayerbotAI* botAI, std::string const name = "magtheridon assign dps priority") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class MagtheridonWarlockCCBurningAbyssalAction : public AttackAction
{
public:
MagtheridonWarlockCCBurningAbyssalAction(PlayerbotAI* botAI, std::string const name = "magtheridon warlock cc burning abyssal") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class MagtheridonMainTankPositionBossAction : public AttackAction
{
public:
MagtheridonMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "magtheridon main tank position boss") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class MagtheridonSpreadRangedAction : public MovementAction
{
public:
static std::unordered_map<ObjectGuid, Position> initialPositions;
static std::unordered_map<ObjectGuid, bool> hasReachedInitialPosition;
MagtheridonSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "magtheridon spread ranged") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
class MagtheridonUseManticronCubeAction : public MovementAction
{
public:
MagtheridonUseManticronCubeAction(PlayerbotAI* botAI, std::string const name = "magtheridon use manticron cube") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
private:
bool HandleCubeRelease(Unit* magtheridon, GameObject* cube);
bool ShouldActivateCubeLogic(Unit* magtheridon);
bool HandleWaitingPhase(const CubeInfo& cubeInfo);
bool HandleCubeInteraction(const CubeInfo& cubeInfo, GameObject* cube);
};
class MagtheridonManageTimersAndAssignmentsAction : public Action
{
public:
MagtheridonManageTimersAndAssignmentsAction(PlayerbotAI* botAI, std::string const name = "magtheridon manage timers and assignments") : Action(botAI, name) {};
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,71 @@
#include <unordered_map>
#include <ctime>
#include "RaidMagtheridonMultipliers.h"
#include "RaidMagtheridonActions.h"
#include "RaidMagtheridonHelpers.h"
#include "ChooseTargetActions.h"
#include "GenericSpellActions.h"
#include "Playerbots.h"
#include "WarlockActions.h"
using namespace MagtheridonHelpers;
// Don't do anything other than clicking cubes when Magtheridon is casting Blast Nova
float MagtheridonUseManticronCubeMultiplier::GetValue(Action* action)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon)
return 1.0f;
if (magtheridon->HasUnitState(UNIT_STATE_CASTING) &&
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA))
{
auto it = botToCubeAssignment.find(bot->GetGUID());
if (it != botToCubeAssignment.end())
{
if (dynamic_cast<MagtheridonUseManticronCubeAction*>(action))
return 1.0f;
return 0.0f;
}
}
return 1.0f;
}
// Bots will wait for 6 seconds after Magtheridon becomes attackable before engaging
float MagtheridonWaitToAttackMultiplier::GetValue(Action* action)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
return 1.0f;
const uint8 dpsWaitSeconds = 6;
auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId());
if (it == dpsWaitTimer.end() ||
(time(nullptr) - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot) && (dynamic_cast<AttackAction*>(action) ||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action))))
return 0.0f;
}
return 1.0f;
}
// No tank assist for offtanks during the channeler phase
// So they don't try to pull channelers from each other or the main tank
float MagtheridonDisableOffTankAssistMultiplier::GetValue(Action* action)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
if (!magtheridon)
return 1.0f;
if ((botAI->IsAssistTankOfIndex(bot, 0) || botAI->IsAssistTankOfIndex(bot, 1)) &&
dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}

View File

@@ -0,0 +1,27 @@
#ifndef _PLAYERBOT_RAIDMAGTHERIDONMULTIPLIERS_H
#define _PLAYERBOT_RAIDMAGTHERIDONMULTIPLIERS_H
#include "Multiplier.h"
class MagtheridonUseManticronCubeMultiplier : public Multiplier
{
public:
MagtheridonUseManticronCubeMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "magtheridon use manticron cube multiplier") {}
float GetValue(Action* action) override;
};
class MagtheridonWaitToAttackMultiplier : public Multiplier
{
public:
MagtheridonWaitToAttackMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "magtheridon wait to attack multiplier") {}
float GetValue(Action* action) override;
};
class MagtheridonDisableOffTankAssistMultiplier : public Multiplier
{
public:
MagtheridonDisableOffTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "magtheridon disable off tank assist multiplier") {}
float GetValue(Action* action) override;
};
#endif

View File

@@ -0,0 +1,37 @@
#ifndef _PLAYERBOT_RAIDMAGTHERIDONACTIONCONTEXT_H
#define _PLAYERBOT_RAIDMAGTHERIDONACTIONCONTEXT_H
#include "RaidMagtheridonActions.h"
#include "NamedObjectContext.h"
class RaidMagtheridonActionContext : public NamedObjectContext<Action>
{
public:
RaidMagtheridonActionContext()
{
creators["magtheridon main tank attack first three channelers"] = &RaidMagtheridonActionContext::magtheridon_main_tank_attack_first_three_channelers;
creators["magtheridon first assist tank attack nw channeler"] = &RaidMagtheridonActionContext::magtheridon_first_assist_tank_attack_nw_channeler;
creators["magtheridon second assist tank attack ne channeler"] = &RaidMagtheridonActionContext::magtheridon_second_assist_tank_attack_ne_channeler;
creators["magtheridon misdirect hellfire channelers"] = &RaidMagtheridonActionContext::magtheridon_misdirect_hellfire_channelers;
creators["magtheridon assign dps priority"] = &RaidMagtheridonActionContext::magtheridon_assign_dps_priority;
creators["magtheridon warlock cc burning abyssal"] = &RaidMagtheridonActionContext::magtheridon_warlock_cc_burning_abyssal;
creators["magtheridon main tank position boss"] = &RaidMagtheridonActionContext::magtheridon_main_tank_position_boss;
creators["magtheridon spread ranged"] = &RaidMagtheridonActionContext::magtheridon_spread_ranged;
creators["magtheridon use manticron cube"] = &RaidMagtheridonActionContext::magtheridon_use_manticron_cube;
creators["magtheridon manage timers and assignments"] = &RaidMagtheridonActionContext::magtheridon_manage_timers_and_assignments;
}
private:
static Action* magtheridon_main_tank_attack_first_three_channelers(PlayerbotAI* botAI) { return new MagtheridonMainTankAttackFirstThreeChannelersAction(botAI); }
static Action* magtheridon_first_assist_tank_attack_nw_channeler(PlayerbotAI* botAI) { return new MagtheridonFirstAssistTankAttackNWChannelerAction(botAI); }
static Action* magtheridon_second_assist_tank_attack_ne_channeler(PlayerbotAI* botAI) { return new MagtheridonSecondAssistTankAttackNEChannelerAction(botAI); }
static Action* magtheridon_misdirect_hellfire_channelers(PlayerbotAI* botAI) { return new MagtheridonMisdirectHellfireChannelers(botAI); }
static Action* magtheridon_assign_dps_priority(PlayerbotAI* botAI) { return new MagtheridonAssignDPSPriorityAction(botAI); }
static Action* magtheridon_warlock_cc_burning_abyssal(PlayerbotAI* botAI) { return new MagtheridonWarlockCCBurningAbyssalAction(botAI); }
static Action* magtheridon_main_tank_position_boss(PlayerbotAI* botAI) { return new MagtheridonMainTankPositionBossAction(botAI); }
static Action* magtheridon_spread_ranged(PlayerbotAI* botAI) { return new MagtheridonSpreadRangedAction(botAI); }
static Action* magtheridon_use_manticron_cube(PlayerbotAI* botAI) { return new MagtheridonUseManticronCubeAction(botAI); }
static Action* magtheridon_manage_timers_and_assignments(PlayerbotAI* botAI) { return new MagtheridonManageTimersAndAssignmentsAction(botAI); }
};
#endif

View File

@@ -0,0 +1,37 @@
#ifndef _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H
#include "RaidMagtheridonTriggers.h"
#include "AiObjectContext.h"
class RaidMagtheridonTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidMagtheridonTriggerContext() : NamedObjectContext<Trigger>()
{
creators["magtheridon first three channelers engaged by main tank"] = &RaidMagtheridonTriggerContext::magtheridon_first_three_channelers_engaged_by_main_tank;
creators["magtheridon nw channeler engaged by first assist tank"] = &RaidMagtheridonTriggerContext::magtheridon_nw_channeler_engaged_by_first_assist_tank;
creators["magtheridon ne channeler engaged by second assist tank"] = &RaidMagtheridonTriggerContext::magtheridon_ne_channeler_engaged_by_second_assist_tank;
creators["magtheridon pulling west and east channelers"] = &RaidMagtheridonTriggerContext::magtheridon_pull_west_and_east_channelers;
creators["magtheridon determining kill order"] = &RaidMagtheridonTriggerContext::magtheridon_determining_kill_order;
creators["magtheridon burning abyssal spawned"] = &RaidMagtheridonTriggerContext::magtheridon_burning_abyssal_spawned;
creators["magtheridon boss engaged by main tank"] = &RaidMagtheridonTriggerContext::magtheridon_boss_engaged_by_main_tank;
creators["magtheridon boss engaged by ranged"] = &RaidMagtheridonTriggerContext::magtheridon_boss_engaged_by_ranged;
creators["magtheridon incoming blast nova"] = &RaidMagtheridonTriggerContext::magtheridon_incoming_blast_nova;
creators["magtheridon need to manage timers and assignments"] = &RaidMagtheridonTriggerContext::magtheridon_need_to_manage_timers_and_assignments;
}
private:
static Trigger* magtheridon_first_three_channelers_engaged_by_main_tank(PlayerbotAI* botAI) { return new MagtheridonFirstThreeChannelersEngagedByMainTankTrigger(botAI); }
static Trigger* magtheridon_nw_channeler_engaged_by_first_assist_tank(PlayerbotAI* botAI) { return new MagtheridonNWChannelerEngagedByFirstAssistTankTrigger(botAI); }
static Trigger* magtheridon_ne_channeler_engaged_by_second_assist_tank(PlayerbotAI* botAI) { return new MagtheridonNEChannelerEngagedBySecondAssistTankTrigger(botAI); }
static Trigger* magtheridon_pull_west_and_east_channelers(PlayerbotAI* botAI) { return new MagtheridonPullingWestAndEastChannelersTrigger(botAI); }
static Trigger* magtheridon_determining_kill_order(PlayerbotAI* botAI) { return new MagtheridonDeterminingKillOrderTrigger(botAI); }
static Trigger* magtheridon_burning_abyssal_spawned(PlayerbotAI* botAI) { return new MagtheridonBurningAbyssalSpawnedTrigger(botAI); }
static Trigger* magtheridon_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new MagtheridonBossEngagedByMainTankTrigger(botAI); }
static Trigger* magtheridon_boss_engaged_by_ranged(PlayerbotAI* botAI) { return new MagtheridonBossEngagedByRangedTrigger(botAI); }
static Trigger* magtheridon_incoming_blast_nova(PlayerbotAI* botAI) { return new MagtheridonIncomingBlastNovaTrigger(botAI); }
static Trigger* magtheridon_need_to_manage_timers_and_assignments(PlayerbotAI* botAI) { return new MagtheridonNeedToManageTimersAndAssignmentsTrigger(botAI); }
};
#endif

View File

@@ -0,0 +1,42 @@
#include "RaidMagtheridonStrategy.h"
#include "RaidMagtheridonMultipliers.h"
void RaidMagtheridonStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("magtheridon incoming blast nova", {
NextAction("magtheridon use manticron cube", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("magtheridon need to manage timers and assignments", {
NextAction("magtheridon manage timers and assignments", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("magtheridon burning abyssal spawned", {
NextAction("magtheridon warlock cc burning abyssal", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("magtheridon boss engaged by ranged", {
NextAction("magtheridon spread ranged", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("magtheridon pulling west and east channelers", {
NextAction("magtheridon misdirect hellfire channelers", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("magtheridon boss engaged by main tank", {
NextAction("magtheridon main tank position boss", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("magtheridon first three channelers engaged by main tank", {
NextAction("magtheridon main tank attack first three channelers", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("magtheridon nw channeler engaged by first assist tank", {
NextAction("magtheridon first assist tank attack nw channeler", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("magtheridon ne channeler engaged by second assist tank", {
NextAction("magtheridon second assist tank attack ne channeler", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("magtheridon determining kill order", {
NextAction("magtheridon assign dps priority", ACTION_RAID + 1) }));
}
void RaidMagtheridonStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new MagtheridonUseManticronCubeMultiplier(botAI));
multipliers.push_back(new MagtheridonWaitToAttackMultiplier(botAI));
multipliers.push_back(new MagtheridonDisableOffTankAssistMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_RAIDMAGTHERIDONSTRATEGY_H
#define _PLAYERBOT_RAIDMAGTHERIDONSTRATEGY_H
#include "Strategy.h"
#include "Multiplier.h"
class RaidMagtheridonStrategy : public Strategy
{
public:
RaidMagtheridonStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "magtheridon"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -0,0 +1,128 @@
#include "RaidMagtheridonTriggers.h"
#include "RaidMagtheridonHelpers.h"
#include "Playerbots.h"
using namespace MagtheridonHelpers;
bool MagtheridonFirstThreeChannelersEngagedByMainTankTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
return magtheridon && botAI->IsMainTank(bot) &&
magtheridon->HasAura(SPELL_SHADOW_CAGE);
}
bool MagtheridonNWChannelerEngagedByFirstAssistTankTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
return magtheridon && botAI->IsAssistTankOfIndex(bot, 0) &&
channelerDiamond && channelerDiamond->IsAlive();
}
bool MagtheridonNEChannelerEngagedBySecondAssistTankTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
return magtheridon && botAI->IsAssistTankOfIndex(bot, 1) &&
channelerTriangle && channelerTriangle->IsAlive();
}
bool MagtheridonPullingWestAndEastChannelersTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
return magtheridon && bot->getClass() == CLASS_HUNTER &&
((channelerStar && channelerStar->IsAlive()) ||
(channelerCircle && channelerCircle->IsAlive()));
}
bool MagtheridonDeterminingKillOrderTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (!magtheridon || botAI->IsHeal(bot) || botAI->IsMainTank(bot) ||
(botAI->IsAssistTankOfIndex(bot, 0) && channelerDiamond && channelerDiamond->IsAlive()) ||
(botAI->IsAssistTankOfIndex(bot, 1) && channelerTriangle && channelerTriangle->IsAlive()))
return false;
return (channeler && channeler->IsAlive()) || (magtheridon &&
!magtheridon->HasAura(SPELL_SHADOW_CAGE));
}
bool MagtheridonBurningAbyssalSpawnedTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon || bot->getClass() != CLASS_WARLOCK)
return false;
const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
return std::any_of(npcs.begin(), npcs.end(), [this](const ObjectGuid& npc)
{
Unit* unit = botAI->GetUnit(npc);
return unit && unit->GetEntry() == NPC_BURNING_ABYSSAL;
});
}
bool MagtheridonBossEngagedByMainTankTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
return magtheridon && botAI->IsMainTank(bot) &&
!magtheridon->HasAura(SPELL_SHADOW_CAGE);
}
bool MagtheridonBossEngagedByRangedTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
return magtheridon && botAI->IsRanged(bot) &&
!(channeler && channeler->IsAlive());
}
bool MagtheridonIncomingBlastNovaTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Group* group = bot->GetGroup();
if (!group || !magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
return false;
bool needsReassign = botToCubeAssignment.empty();
if (!needsReassign)
{
for (auto const& pair : botToCubeAssignment)
{
Player* assigned = ObjectAccessor::FindPlayer(pair.first);
if (!assigned || !assigned->IsAlive())
{
needsReassign = true;
break;
}
}
}
if (needsReassign)
{
std::vector<CubeInfo> cubes = GetAllCubeInfosByDbGuids(bot->GetMap(), MANTICRON_CUBE_DB_GUIDS);
AssignBotsToCubesByGuidAndCoords(group, cubes, botAI);
}
return botToCubeAssignment.find(bot->GetGUID()) != botToCubeAssignment.end();
}
bool MagtheridonNeedToManageTimersAndAssignmentsTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
return magtheridon;
}

View File

@@ -0,0 +1,77 @@
#ifndef _PLAYERBOT_RAIDMAGTHERIDONTRIGGERS_H
#define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAI.h"
class MagtheridonFirstThreeChannelersEngagedByMainTankTrigger : public Trigger
{
public:
MagtheridonFirstThreeChannelersEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon first three channelers engaged by main tank") {};
bool IsActive() override;
};
class MagtheridonNWChannelerEngagedByFirstAssistTankTrigger : public Trigger
{
public:
MagtheridonNWChannelerEngagedByFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon nw channeler engaged by first assist tank") {};
bool IsActive() override;
};
class MagtheridonNEChannelerEngagedBySecondAssistTankTrigger : public Trigger
{
public:
MagtheridonNEChannelerEngagedBySecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon ne channeler engaged by second assist tank") {};
bool IsActive() override;
};
class MagtheridonPullingWestAndEastChannelersTrigger : public Trigger
{
public:
MagtheridonPullingWestAndEastChannelersTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon pulling west and east channelers") {};
bool IsActive() override;
};
class MagtheridonDeterminingKillOrderTrigger : public Trigger
{
public:
MagtheridonDeterminingKillOrderTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon determining kill order") {};
bool IsActive() override;
};
class MagtheridonBurningAbyssalSpawnedTrigger : public Trigger
{
public:
MagtheridonBurningAbyssalSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon burning abyssal spawned") {};
bool IsActive() override;
};
class MagtheridonBossEngagedByMainTankTrigger : public Trigger
{
public:
MagtheridonBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon boss engaged by main tank") {};
bool IsActive() override;
};
class MagtheridonBossEngagedByRangedTrigger : public Trigger
{
public:
MagtheridonBossEngagedByRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon boss engaged by ranged") {};
bool IsActive() override;
};
class MagtheridonIncomingBlastNovaTrigger : public Trigger
{
public:
MagtheridonIncomingBlastNovaTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon incoming blast nova") {};
bool IsActive() override;
};
class MagtheridonNeedToManageTimersAndAssignmentsTrigger : public Trigger
{
public:
MagtheridonNeedToManageTimersAndAssignmentsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "magtheridon need to manage timers and assignments") {};
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,226 @@
#include "RaidMagtheridonHelpers.h"
#include "Creature.h"
#include "GameObject.h"
#include "GroupReference.h"
#include "Map.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
namespace MagtheridonHelpers
{
namespace MagtheridonsLairLocations
{
const Location WaitingForMagtheridonPosition = { 1.359f, 2.048f, -0.406f, 3.135f };
const Location MagtheridonTankPosition = { 22.827f, 2.105f, -0.406f, 3.135f };
const Location NWChannelerTankPosition = { -11.764f, 30.818f, -0.411f, 0.0f };
const Location NEChannelerTankPosition = { -12.490f, -26.211f, -0.411f, 0.0f };
const Location RangedSpreadPosition = { -14.890f, 1.995f, -0.406f, 0.0f };
const Location HealerSpreadPosition = { -2.265f, 1.874f, -0.404f, 0.0f };
}
// Identify channelers by their database GUIDs
Creature* GetChanneler(Player* bot, uint32 dbGuid)
{
Map* map = bot->GetMap();
if (!map)
return nullptr;
auto it = map->GetCreatureBySpawnIdStore().find(dbGuid);
if (it == map->GetCreatureBySpawnIdStore().end())
return nullptr;
return it->second;
}
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
Group* group = bot->GetGroup();
if (!target || !group)
return;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void MarkTargetWithCross(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex);
}
const std::vector<uint32> MANTICRON_CUBE_DB_GUIDS = { 43157, 43158, 43159, 43160, 43161 };
// Get the positions of all Manticron Cubes by their database GUIDs
std::vector<CubeInfo> GetAllCubeInfosByDbGuids(Map* map, const std::vector<uint32>& cubeDbGuids)
{
std::vector<CubeInfo> cubes;
if (!map)
return cubes;
for (uint32 dbGuid : cubeDbGuids)
{
auto bounds = map->GetGameObjectBySpawnIdStore().equal_range(dbGuid);
if (bounds.first == bounds.second)
continue;
GameObject* go = bounds.first->second;
if (!go)
continue;
CubeInfo info;
info.guid = go->GetGUID();
info.x = go->GetPositionX();
info.y = go->GetPositionY();
info.z = go->GetPositionZ();
cubes.push_back(info);
}
return cubes;
}
std::unordered_map<ObjectGuid, CubeInfo> botToCubeAssignment;
void AssignBotsToCubesByGuidAndCoords(Group* group, const std::vector<CubeInfo>& cubes, PlayerbotAI* botAI)
{
botToCubeAssignment.clear();
if (!group)
return;
size_t cubeIndex = 0;
std::vector<Player*> candidates;
// Assign ranged DPS (excluding Warlocks) to cubes first
for (GroupReference* ref = group->GetFirstMember(); ref && cubeIndex < cubes.size(); ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !botAI->IsRangedDps(member, true) ||
member->getClass() == CLASS_WARLOCK || !GET_PLAYERBOT_AI(member))
continue;
candidates.push_back(member);
if (candidates.size() >= cubes.size())
break;
}
// If there are still cubes left, assign any other non-tank bots
if (candidates.size() < cubes.size())
{
for (GroupReference* ref = group->GetFirstMember();
ref && candidates.size() < cubes.size(); ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || botAI->IsTank(member))
continue;
if (std::find(candidates.begin(), candidates.end(), member) == candidates.end())
candidates.push_back(member);
}
}
for (Player* member : candidates)
{
if (cubeIndex >= cubes.size())
break;
if (!member || !member->IsAlive())
continue;
botToCubeAssignment[member->GetGUID()] = cubes[cubeIndex++];
}
}
std::unordered_map<uint32, bool> lastBlastNovaState;
std::unordered_map<uint32, time_t> blastNovaTimer;
std::unordered_map<uint32, time_t> spreadWaitTimer;
std::unordered_map<uint32, time_t> dpsWaitTimer;
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z)
{
// Debris
std::vector<Unit*> debrisHazards;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (!unit || unit->GetEntry() != NPC_TARGET_TRIGGER)
continue;
debrisHazards.push_back(unit);
}
for (Unit* hazard : debrisHazards)
{
float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2));
if (dist < 9.0f)
return false;
}
// Conflagration
GuidVector gos = *botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest game objects");
for (auto const& goGuid : gos)
{
GameObject* go = botAI->GetGameObject(goGuid);
if (!go || go->GetEntry() != GO_BLAZE)
continue;
float dist = std::sqrt(std::pow(x - go->GetPositionX(), 2) + std::pow(y - go->GetPositionY(), 2));
if (dist < 5.0f)
return false;
}
return true;
}
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return true;
}
}

View File

@@ -0,0 +1,90 @@
#ifndef _PLAYERBOT_RAIDMAGTHERIDONHELPERS_H
#define _PLAYERBOT_RAIDMAGTHERIDONHELPERS_H
#include <ctime>
#include <unordered_map>
#include <vector>
#include "Group.h"
#include "ObjectGuid.h"
#include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace MagtheridonHelpers
{
enum MagtheridonSpells
{
// Magtheridon
SPELL_SHADOW_CAGE = 30205,
SPELL_BLAST_NOVA = 30616,
SPELL_SHADOW_GRASP = 30410,
// Warlock
SPELL_BANISH = 18647,
SPELL_FEAR = 6215,
// Hunter
SPELL_MISDIRECTION = 35079,
};
enum MagtheridonNPCs
{
NPC_BURNING_ABYSSAL = 17454,
NPC_TARGET_TRIGGER = 17474,
};
enum MagtheridonObjects
{
GO_BLAZE = 181832,
};
constexpr uint32 SOUTH_CHANNELER = 90978;
constexpr uint32 WEST_CHANNELER = 90979;
constexpr uint32 NORTHWEST_CHANNELER = 90980;
constexpr uint32 EAST_CHANNELER = 90982;
constexpr uint32 NORTHEAST_CHANNELER = 90981;
Creature* GetChanneler(Player* bot, uint32 dbGuid);
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void MarkTargetWithCross(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
struct Location
{
float x, y, z, orientation;
};
namespace MagtheridonsLairLocations
{
extern const Location WaitingForMagtheridonPosition;
extern const Location MagtheridonTankPosition;
extern const Location NWChannelerTankPosition;
extern const Location NEChannelerTankPosition;
extern const Location RangedSpreadPosition;
extern const Location HealerSpreadPosition;
}
struct CubeInfo
{
ObjectGuid guid;
float x, y, z;
};
extern const std::vector<uint32> MANTICRON_CUBE_DB_GUIDS;
extern std::unordered_map<ObjectGuid, CubeInfo> botToCubeAssignment;
std::vector<CubeInfo> GetAllCubeInfosByDbGuids(Map* map, const std::vector<uint32>& cubeDbGuids);
void AssignBotsToCubesByGuidAndCoords(Group* group, const std::vector<CubeInfo>& cubes, PlayerbotAI* botAI);
extern std::unordered_map<uint32, bool> lastBlastNovaState;
extern std::unordered_map<uint32, time_t> blastNovaTimer;
extern std::unordered_map<uint32, time_t> spreadWaitTimer;
extern std::unordered_map<uint32, time_t> dpsWaitTimer;
}
#endif

View File

@@ -0,0 +1,215 @@
#include "RaidMcActions.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
#include "RaidMcTriggers.h"
#include "RaidMcHelpers.h"
static constexpr float LIVING_BOMB_DISTANCE = 20.0f;
static constexpr float INFERNO_DISTANCE = 20.0f;
// don't get hit by Arcane Explosion but still be in casting range
static constexpr float ARCANE_EXPLOSION_DISTANCE = 26.0f;
// dedicated tank positions; prevents assist tanks from positioning Core Ragers on steep walls on pull
static const Position GOLEMAGG_TANK_POSITION{795.7308, -994.8848, -207.18661};
static const Position CORE_RAGER_TANK_POSITION{846.6453, -1019.0639, -198.9819};
static constexpr float GOLEMAGGS_TRUST_DISTANCE = 30.0f;
static constexpr float CORE_RAGER_STEP_DISTANCE = 5.0f;
using namespace MoltenCoreHelpers;
bool McMoveFromGroupAction::Execute(Event event)
{
return MoveFromGroup(LIVING_BOMB_DISTANCE);
}
bool McMoveFromBaronGeddonAction::Execute(Event event)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
{
float distToTravel = INFERNO_DISTANCE - bot->GetDistance2d(boss);
if (distToTravel > 0)
{
// Stop current spell first
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveAway(boss, distToTravel);
}
}
return false;
}
bool McShazzrahMoveAwayAction::Execute(Event event)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "shazzrah"))
{
float distToTravel = ARCANE_EXPLOSION_DISTANCE - bot->GetDistance2d(boss);
if (distToTravel > 0)
return MoveAway(boss, distToTravel);
}
return false;
}
bool McGolemaggMarkBossAction::Execute(Event event)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
{
if (Group* group = bot->GetGroup())
{
ObjectGuid currentSkullGuid = group->GetTargetIcon(RtiTargetValue::skullIndex);
if (currentSkullGuid.IsEmpty() || currentSkullGuid != boss->GetGUID())
{
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), boss->GetGUID());
return true;
}
}
}
return false;
}
bool McGolemaggTankAction::MoveUnitToPosition(Unit* target, const Position& tankPosition, float maxDistance,
float stepDistance)
{
if (bot->GetVictim() != target)
return Attack(target);
if (target->GetVictim() == bot)
{
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.GetPositionX(), tankPosition.GetPositionY());
if (distanceToTankPosition > maxDistance)
{
float dX = tankPosition.GetPositionX() - bot->GetPositionX();
float dY = tankPosition.GetPositionY() - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * stepDistance;
float moveY = bot->GetPositionY() + (dY / dist) * stepDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT, true,
true);
}
}
else if (botAI->DoSpecificAction("taunt spell", Event(), true))
return true;
return false;
}
bool McGolemaggTankAction::FindCoreRagers(Unit*& coreRager1, Unit*& coreRager2) const
{
coreRager1 = coreRager2 = nullptr;
for (auto const& target : AI_VALUE(GuidVector, "possible targets no los"))
{
Unit* unit = botAI->GetUnit(target);
if (unit && unit->IsAlive() && unit->GetEntry() == NPC_CORE_RAGER)
{
if (coreRager1 == nullptr)
coreRager1 = unit;
else if (coreRager2 == nullptr)
{
coreRager2 = unit;
break; // There should be no third Core Rager.
}
}
}
return coreRager1 != nullptr && coreRager2 != nullptr;
}
bool McGolemaggMainTankAttackGolemaggAction::Execute(Event event)
{
// At this point, we know we are not the last living tank in the group.
if (Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
{
Unit* coreRager1;
Unit* coreRager2;
if (!FindCoreRagers(coreRager1, coreRager2))
return false; // safety check
// We only need to move if the Core Ragers still have Golemagg's Trust
if (coreRager1->HasAura(SPELL_GOLEMAGGS_TRUST) || coreRager2->HasAura(SPELL_GOLEMAGGS_TRUST))
return MoveUnitToPosition(boss, GOLEMAGG_TANK_POSITION, boss->GetCombatReach());
}
return false;
}
bool McGolemaggAssistTankAttackCoreRagerAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "golemagg the incinerator");
if (!boss)
return false;
// Step 0: Filter additional assist tanks. We only need 2.
bool isFirstAssistTank = PlayerbotAI::IsAssistTankOfIndex(bot, 0, true);
bool isSecondAssistTank = PlayerbotAI::IsAssistTankOfIndex(bot, 1, true);
if (!isFirstAssistTank && !isSecondAssistTank)
return Attack(boss);
// Step 1: Find both Core Ragers
Unit* coreRager1;
Unit* coreRager2;
if (!FindCoreRagers(coreRager1, coreRager2))
return false; // safety check
// Step 2: Assign Core Rager to bot
Unit* myCoreRager = nullptr;
Unit* otherCoreRager = nullptr;
if (isFirstAssistTank)
{
myCoreRager = coreRager1;
otherCoreRager = coreRager2;
}
else // isSecondAssistTank is always true here
{
myCoreRager = coreRager2;
otherCoreRager = coreRager1;
}
// Step 3: Select the right target
if (myCoreRager->GetVictim() != bot)
{
// Step 3.1: My Core Rager isn't attacking me. Attack until it does.
if (bot->GetVictim() != myCoreRager)
return Attack(myCoreRager);
return botAI->DoSpecificAction("taunt spell", event, true);
}
Unit* otherCoreRagerVictim = otherCoreRager->GetVictim();
if (otherCoreRagerVictim) // Core Rager victim can be NULL
{
// Step 3.2: Check if the other Core Rager isn't attacking its assist tank.
Player* otherCoreRagerPlayerVictim = otherCoreRagerVictim->ToPlayer();
if (otherCoreRagerPlayerVictim &&
!PlayerbotAI::IsAssistTankOfIndex(otherCoreRagerPlayerVictim, 0, true) &&
!PlayerbotAI::IsAssistTankOfIndex(otherCoreRagerPlayerVictim, 1, true))
{
// Assume we are the only assist tank or the other assist tank is dead => pick up other Core Rager!
if (bot->GetVictim() != otherCoreRager)
return Attack(otherCoreRager);
return botAI->DoSpecificAction("taunt spell", event, true);
}
}
if (bot->GetVictim() != myCoreRager)
return Attack(myCoreRager); // Step 3.3: Attack our Core Rager in case we previously switched in 3.2.
// Step 4: Prevent Golemagg's Trust on Core Ragers
if (myCoreRager->HasAura(SPELL_GOLEMAGGS_TRUST) ||
(otherCoreRagerVictim == bot && otherCoreRager->HasAura(SPELL_GOLEMAGGS_TRUST)))
{
// Step 4.1: Move Core Ragers to dedicated tank position (only if Golemagg is far enough away from said position)
float bossDistanceToCoreRagerTankPosition = boss->GetExactDist2d(
CORE_RAGER_TANK_POSITION.GetPositionX(), CORE_RAGER_TANK_POSITION.GetPositionY());
if (bossDistanceToCoreRagerTankPosition > GOLEMAGGS_TRUST_DISTANCE)
{
float distanceToTankPosition = bot->GetExactDist2d(CORE_RAGER_TANK_POSITION.GetPositionX(),
CORE_RAGER_TANK_POSITION.GetPositionY());
if (distanceToTankPosition > CORE_RAGER_STEP_DISTANCE)
return MoveUnitToPosition(myCoreRager, CORE_RAGER_TANK_POSITION, CORE_RAGER_STEP_DISTANCE);
}
// Step 4.2: if boss is too close to tank position, or we are already there, move away from Golemagg to try to out-range Golemagg's Trust
return MoveAway(boss, CORE_RAGER_STEP_DISTANCE, true);
}
return false;
}

View File

@@ -0,0 +1,67 @@
#ifndef _PLAYERBOT_RAIDMCACTIONS_H
#define _PLAYERBOT_RAIDMCACTIONS_H
#include "AttackAction.h"
#include "MovementActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
class McMoveFromGroupAction : public MovementAction
{
public:
McMoveFromGroupAction(PlayerbotAI* botAI, std::string const name = "mc move from group")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class McMoveFromBaronGeddonAction : public MovementAction
{
public:
McMoveFromBaronGeddonAction(PlayerbotAI* botAI, std::string const name = "mc move from baron geddon")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class McShazzrahMoveAwayAction : public MovementAction
{
public:
McShazzrahMoveAwayAction(PlayerbotAI* botAI, std::string const name = "mc shazzrah move away")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class McGolemaggMarkBossAction : public Action
{
public:
McGolemaggMarkBossAction(PlayerbotAI* botAI, std::string const name = "mc golemagg mark boss")
: Action(botAI, name) {};
bool Execute(Event event) override;
};
class McGolemaggTankAction : public AttackAction
{
public:
McGolemaggTankAction(PlayerbotAI* botAI, std::string const name)
: AttackAction(botAI, name) {}
protected:
bool MoveUnitToPosition(Unit* target, const Position& tankPosition, float maxDistance, float stepDistance = 3.0f);
bool FindCoreRagers(Unit*& coreRager1, Unit*& coreRager2) const;
};
class McGolemaggMainTankAttackGolemaggAction : public McGolemaggTankAction
{
public:
McGolemaggMainTankAttackGolemaggAction(PlayerbotAI* botAI, std::string const name = "mc golemagg main tank attack golemagg")
: McGolemaggTankAction(botAI, name) {};
bool Execute(Event event) override;
};
class McGolemaggAssistTankAttackCoreRagerAction : public McGolemaggTankAction
{
public:
McGolemaggAssistTankAttackCoreRagerAction(PlayerbotAI* botAI, std::string const name = "mc golemagg assist tank attack core rager")
: McGolemaggTankAction(botAI, name) {};
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,117 @@
#include "RaidMcMultipliers.h"
#include "Playerbots.h"
#include "ChooseTargetActions.h"
#include "GenericSpellActions.h"
#include "DruidActions.h"
#include "HunterActions.h"
#include "PaladinActions.h"
#include "ShamanActions.h"
#include "WarriorActions.h"
#include "DKActions.h"
#include "RaidMcActions.h"
#include "RaidMcHelpers.h"
using namespace MoltenCoreHelpers;
static bool IsDpsBotWithAoeAction(Player* bot, Action* action)
{
if (PlayerbotAI::IsDps(bot))
{
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastConsecrationAction*>(action) ||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<CastWhirlwindAction*>(action) ||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastExplosiveTrapAction*>(action) ||
dynamic_cast<CastDeathAndDecayAction*>(action))
return true;
if (auto castSpellAction = dynamic_cast<CastSpellAction*>(action))
{
if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return true;
}
}
return false;
}
float GarrDisableDpsAoeMultiplier::GetValue(Action* action)
{
if (AI_VALUE2(Unit*, "find target", "garr"))
{
if (IsDpsBotWithAoeAction(bot, action))
return 0.0f;
}
return 1.0f;
}
static bool IsAllowedGeddonMovementAction(Action* action)
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<McMoveFromGroupAction*>(action) &&
!dynamic_cast<McMoveFromBaronGeddonAction*>(action))
return false;
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return false;
return true;
}
float BaronGeddonAbilityMultiplier::GetValue(Action* action)
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
{
if (boss->HasAura(SPELL_INFERNO))
{
if (!IsAllowedGeddonMovementAction(action))
return 0.0f;
}
}
// No check for Baron Geddon, because bots may have the bomb even after Geddon died.
if (bot->HasAura(SPELL_LIVING_BOMB))
{
if (!IsAllowedGeddonMovementAction(action))
return 0.0f;
}
return 1.0f;
}
static bool IsSingleLivingTankInGroup(Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
if (PlayerbotAI::IsTank(member))
return false;
}
}
return true;
}
float GolemaggMultiplier::GetValue(Action* action)
{
if (AI_VALUE2(Unit*, "find target", "golemagg the incinerator"))
{
if (PlayerbotAI::IsTank(bot) && IsSingleLivingTankInGroup(bot))
{
// Only one tank => Pick up Golemagg and the two Core Ragers
if (dynamic_cast<McGolemaggMainTankAttackGolemaggAction*>(action) ||
dynamic_cast<McGolemaggAssistTankAttackCoreRagerAction*>(action))
return 0.0f;
}
if (PlayerbotAI::IsAssistTank(bot))
{
// The first two assist tanks manage the Core Ragers. The remaining assist tanks attack the boss.
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
}
if (IsDpsBotWithAoeAction(bot, action))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,27 @@
#ifndef _PLAYERBOT_RAIDMCMULTIPLIERS_H
#define _PLAYERBOT_RAIDMCMULTIPLIERS_H
#include "Multiplier.h"
class GarrDisableDpsAoeMultiplier : public Multiplier
{
public:
GarrDisableDpsAoeMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "garr disable dps aoe multiplier") {}
float GetValue(Action* action) override;
};
class BaronGeddonAbilityMultiplier : public Multiplier
{
public:
BaronGeddonAbilityMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "baron geddon ability multiplier") {}
float GetValue(Action* action) override;
};
class GolemaggMultiplier : public Multiplier
{
public:
GolemaggMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "golemagg multiplier") {}
float GetValue(Action* action) override;
};
#endif

View File

@@ -0,0 +1,48 @@
#ifndef _PLAYERBOT_RAIDMCACTIONCONTEXT_H
#define _PLAYERBOT_RAIDMCACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidMcActions.h"
class RaidMcActionContext : public NamedObjectContext<Action>
{
public:
RaidMcActionContext()
{
creators["mc lucifron shadow resistance"] = &RaidMcActionContext::lucifron_shadow_resistance;
creators["mc magmadar fire resistance"] = &RaidMcActionContext::magmadar_fire_resistance;
creators["mc gehennas shadow resistance"] = &RaidMcActionContext::gehennas_shadow_resistance;
creators["mc garr fire resistance"] = &RaidMcActionContext::garr_fire_resistance;
creators["mc baron geddon fire resistance"] = &RaidMcActionContext::baron_geddon_fire_resistance;
creators["mc move from group"] = &RaidMcActionContext::check_should_move_from_group;
creators["mc move from baron geddon"] = &RaidMcActionContext::move_from_baron_geddon;
creators["mc shazzrah move away"] = &RaidMcActionContext::shazzrah_move_away;
creators["mc sulfuron harbinger fire resistance"] = &RaidMcActionContext::sulfuron_harbinger_fire_resistance;
creators["mc golemagg fire resistance"] = &RaidMcActionContext::golemagg_fire_resistance;
creators["mc golemagg mark boss"] = &RaidMcActionContext::golemagg_mark_boss;
creators["mc golemagg main tank attack golemagg"] = &RaidMcActionContext::golemagg_main_tank_attack_golemagg;
creators["mc golemagg assist tank attack core rager"] = &RaidMcActionContext::golemagg_assist_tank_attack_core_rager;
creators["mc majordomo shadow resistance"] = &RaidMcActionContext::majordomo_shadow_resistance;
creators["mc ragnaros fire resistance"] = &RaidMcActionContext::ragnaros_fire_resistance;
}
private:
static Action* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "lucifron"); }
static Action* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "magmadar"); }
static Action* gehennas_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "gehennas"); }
static Action* garr_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "garr"); }
static Action* baron_geddon_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "baron geddon"); }
static Action* check_should_move_from_group(PlayerbotAI* botAI) { return new McMoveFromGroupAction(botAI); }
static Action* move_from_baron_geddon(PlayerbotAI* botAI) { return new McMoveFromBaronGeddonAction(botAI); }
static Action* shazzrah_move_away(PlayerbotAI* botAI) { return new McShazzrahMoveAwayAction(botAI); }
static Action* sulfuron_harbinger_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "sulfuron harbinger"); }
static Action* golemagg_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "golemagg the incinerator"); }
static Action* golemagg_mark_boss(PlayerbotAI* botAI) { return new McGolemaggMarkBossAction(botAI); }
static Action* golemagg_main_tank_attack_golemagg(PlayerbotAI* botAI) { return new McGolemaggMainTankAttackGolemaggAction(botAI); }
static Action* golemagg_assist_tank_attack_core_rager(PlayerbotAI* botAI) { return new McGolemaggAssistTankAttackCoreRagerAction(botAI); }
static Action* majordomo_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceAction(botAI, "majordomo executus"); }
static Action* ragnaros_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceAction(botAI, "ragnaros"); }
};
#endif

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_RAIDMCHELPERS_H
#define _PLAYERBOT_RAIDMCHELPERS_H
namespace MoltenCoreHelpers
{
enum MoltenCoreNPCs
{
// Golemagg
NPC_CORE_RAGER = 11672,
};
enum MoltenCoreSpells
{
// Baron Geddon
SPELL_INFERNO = 19695,
SPELL_LIVING_BOMB = 20475,
// Golemagg
SPELL_GOLEMAGGS_TRUST = 20553,
};
}
#endif

View File

@@ -0,0 +1,48 @@
#ifndef _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidMcTriggers.h"
class RaidMcTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidMcTriggerContext()
{
creators["mc lucifron shadow resistance"] = &RaidMcTriggerContext::lucifron_shadow_resistance;
creators["mc magmadar fire resistance"] = &RaidMcTriggerContext::magmadar_fire_resistance;
creators["mc gehennas shadow resistance"] = &RaidMcTriggerContext::gehennas_shadow_resistance;
creators["mc garr fire resistance"] = &RaidMcTriggerContext::garr_fire_resistance;
creators["mc baron geddon fire resistance"] = &RaidMcTriggerContext::baron_geddon_fire_resistance;
creators["mc living bomb debuff"] = &RaidMcTriggerContext::living_bomb_debuff;
creators["mc baron geddon inferno"] = &RaidMcTriggerContext::baron_geddon_inferno;
creators["mc shazzrah ranged"] = &RaidMcTriggerContext::shazzrah_ranged;
creators["mc sulfuron harbinger fire resistance"] = &RaidMcTriggerContext::sulfuron_harbinger_fire_resistance;
creators["mc golemagg fire resistance"] = &RaidMcTriggerContext::golemagg_fire_resistance;
creators["mc golemagg mark boss"] = &RaidMcTriggerContext::golemagg_mark_boss;
creators["mc golemagg is main tank"] = &RaidMcTriggerContext::golemagg_is_main_tank;
creators["mc golemagg is assist tank"] = &RaidMcTriggerContext::golemagg_is_assist_tank;
creators["mc majordomo shadow resistance"] = &RaidMcTriggerContext::majordomo_shadow_resistance;
creators["mc ragnaros fire resistance"] = &RaidMcTriggerContext::ragnaros_fire_resistance;
}
private:
static Trigger* lucifron_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "lucifron"); }
static Trigger* magmadar_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "magmadar"); }
static Trigger* gehennas_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "gehennas"); }
static Trigger* garr_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "garr"); }
static Trigger* baron_geddon_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "baron geddon"); }
static Trigger* living_bomb_debuff(PlayerbotAI* botAI) { return new McLivingBombDebuffTrigger(botAI); }
static Trigger* baron_geddon_inferno(PlayerbotAI* botAI) { return new McBaronGeddonInfernoTrigger(botAI); }
static Trigger* shazzrah_ranged(PlayerbotAI* botAI) { return new McShazzrahRangedTrigger(botAI); }
static Trigger* sulfuron_harbinger_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "sulfuron harbinger"); }
static Trigger* golemagg_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "golemagg the incinerator"); }
static Trigger* golemagg_mark_boss(PlayerbotAI* botAI) { return new McGolemaggMarkBossTrigger(botAI); }
static Trigger* golemagg_is_main_tank(PlayerbotAI* botAI) { return new McGolemaggIsMainTankTrigger(botAI); }
static Trigger* golemagg_is_assist_tank(PlayerbotAI* botAI) { return new McGolemaggIsAssistTankTrigger(botAI); }
static Trigger* majordomo_shadow_resistance(PlayerbotAI* botAI) { return new BossShadowResistanceTrigger(botAI, "majordomo executus"); }
static Trigger* ragnaros_fire_resistance(PlayerbotAI* botAI) { return new BossFireResistanceTrigger(botAI, "ragnaros"); }
};
#endif

View File

@@ -0,0 +1,81 @@
#include "RaidMcStrategy.h"
#include "RaidMcMultipliers.h"
#include "Strategy.h"
void RaidMcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// Lucifron
triggers.push_back(
new TriggerNode("mc lucifron shadow resistance",
{ NextAction("mc lucifron shadow resistance", ACTION_RAID) }));
// Magmadar
// TODO: Fear ward / tremor totem, or general anti-fear strat development. Same as King Dred (Drak'Tharon) and faction commander (Nexus).
triggers.push_back(
new TriggerNode("mc magmadar fire resistance",
{ NextAction("mc magmadar fire resistance", ACTION_RAID) }));
// Gehennas
triggers.push_back(
new TriggerNode("mc gehennas shadow resistance",
{ NextAction("mc gehennas shadow resistance", ACTION_RAID) }));
// Garr
triggers.push_back(
new TriggerNode("mc garr fire resistance",
{ NextAction("mc garr fire resistance", ACTION_RAID) }));
// Baron Geddon
triggers.push_back(
new TriggerNode("mc baron geddon fire resistance",
{ NextAction("mc baron geddon fire resistance", ACTION_RAID) }));
triggers.push_back(
new TriggerNode("mc living bomb debuff",
{ NextAction("mc move from group", ACTION_RAID) }));
triggers.push_back(
new TriggerNode("mc baron geddon inferno",
{ NextAction("mc move from baron geddon", ACTION_RAID) }));
// Shazzrah
triggers.push_back(
new TriggerNode("mc shazzrah ranged",
{ NextAction("mc shazzrah move away", ACTION_RAID) }));
// Sulfuron Harbinger
// Alternatively, shadow resistance is also possible.
triggers.push_back(
new TriggerNode("mc sulfuron harbinger fire resistance",
{ NextAction("mc sulfuron harbinger fire resistance", ACTION_RAID) }));
// Golemagg the Incinerator
triggers.push_back(
new TriggerNode("mc golemagg fire resistance",
{ NextAction("mc golemagg fire resistance", ACTION_RAID) }));
triggers.push_back(
new TriggerNode("mc golemagg mark boss",
{ NextAction("mc golemagg mark boss", ACTION_RAID) }));
triggers.push_back(
new TriggerNode("mc golemagg is main tank",
{ NextAction("mc golemagg main tank attack golemagg", ACTION_RAID) }));
triggers.push_back(
new TriggerNode("mc golemagg is assist tank",
{ NextAction("mc golemagg assist tank attack core rager", ACTION_RAID) }));
// Majordomo Executus
triggers.push_back(
new TriggerNode("mc majordomo shadow resistance",
{ NextAction("mc majordomo shadow resistance", ACTION_RAID) }));
// Ragnaros
triggers.push_back(
new TriggerNode("mc ragnaros fire resistance",
{ NextAction("mc ragnaros fire resistance", ACTION_RAID) }));
}
void RaidMcStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new GarrDisableDpsAoeMultiplier(botAI));
multipliers.push_back(new BaronGeddonAbilityMultiplier(botAI));
multipliers.push_back(new GolemaggMultiplier(botAI));
}

View File

@@ -0,0 +1,17 @@
#ifndef _PLAYERBOT_RAIDMCSTRATEGY_H
#define _PLAYERBOT_RAIDMCSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidMcStrategy : public Strategy
{
public:
RaidMcStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "moltencore"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -0,0 +1,40 @@
#include "RaidMcTriggers.h"
#include "SharedDefines.h"
#include "RaidMcHelpers.h"
using namespace MoltenCoreHelpers;
bool McLivingBombDebuffTrigger::IsActive()
{
// No check for Baron Geddon, because bots may have the bomb even after Geddon died.
return bot->HasAura(SPELL_LIVING_BOMB);
}
bool McBaronGeddonInfernoTrigger::IsActive()
{
if (Unit* boss = AI_VALUE2(Unit*, "find target", "baron geddon"))
return boss->HasAura(SPELL_INFERNO);
return false;
}
bool McShazzrahRangedTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "shazzrah") && PlayerbotAI::IsRanged(bot);
}
bool McGolemaggMarkBossTrigger::IsActive()
{
// any tank may mark the boss
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsTank(bot);
}
bool McGolemaggIsMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsMainTank(bot);
}
bool McGolemaggIsAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "golemagg the incinerator") && PlayerbotAI::IsAssistTank(bot);
}

View File

@@ -0,0 +1,50 @@
#ifndef _PLAYERBOT_RAIDMCTRIGGERS_H
#define _PLAYERBOT_RAIDMCTRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
class McLivingBombDebuffTrigger : public Trigger
{
public:
McLivingBombDebuffTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc living bomb debuff") {}
bool IsActive() override;
};
class McBaronGeddonInfernoTrigger : public Trigger
{
public:
McBaronGeddonInfernoTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc baron geddon inferno") {}
bool IsActive() override;
};
class McShazzrahRangedTrigger : public Trigger
{
public:
McShazzrahRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc shazzrah ranged") {}
bool IsActive() override;
};
class McGolemaggMarkBossTrigger : public Trigger
{
public:
McGolemaggMarkBossTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg mark boss") {}
bool IsActive() override;
};
class McGolemaggIsMainTankTrigger : public Trigger
{
public:
McGolemaggIsMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg is main tank") {}
bool IsActive() override;
};
class McGolemaggIsAssistTankTrigger : public Trigger
{
public:
McGolemaggIsAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "mc golemagg is assist tank") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,246 @@
#include "RaidOsActions.h"
#include "RaidOsTriggers.h"
#include "Playerbots.h"
bool SartharionTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
// Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron");
// Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron");
// Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon");
Unit* shadron = nullptr;
Unit* tenebron = nullptr;
Unit* vesperon = nullptr;
// Detect incoming drakes before they are on aggro table
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_SHADRON:
shadron = unit;
continue;
case NPC_TENEBRON:
tenebron = unit;
continue;
case NPC_VESPERON:
vesperon = unit;
continue;
default:
continue;
}
}
Position currentPos = bot->GetPosition();
// Adjustable, this is the acceptable distance to stack point that will be accepted as "safe"
float looseDistance = 12.0f;
if (botAI->IsMainTank(bot))
{
if (bot->GetExactDist2d(SARTHARION_MAINTANK_POSITION.first, SARTHARION_MAINTANK_POSITION.second) > looseDistance)
{
return MoveTo(OS_MAP_ID, SARTHARION_MAINTANK_POSITION.first, SARTHARION_MAINTANK_POSITION.second, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
// Offtank grab drakes
else if (shadron || tenebron || vesperon)
{
float triggerDistance = 100.0f;
// Prioritise threat before positioning
if (tenebron && bot->GetExactDist2d(tenebron) < triggerDistance &&
tenebron->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != tenebron)
{
return Attack(tenebron);
}
if (shadron && bot->GetExactDist2d(shadron) < triggerDistance &&
shadron->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != shadron)
{
return Attack(shadron);
}
if (vesperon && bot->GetExactDist2d(vesperon) < triggerDistance &&
vesperon->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != vesperon)
{
return Attack(vesperon);
}
bool drakeInCombat = (tenebron && bot->GetExactDist2d(tenebron) < triggerDistance) ||
(shadron && bot->GetExactDist2d(shadron) < triggerDistance) ||
(vesperon && bot->GetExactDist2d(vesperon) < triggerDistance);
// Offtank has threat on drakes, check positioning
if (drakeInCombat && bot->GetExactDist2d(SARTHARION_OFFTANK_POSITION.first, SARTHARION_OFFTANK_POSITION.second) > looseDistance)
{
return MoveTo(OS_MAP_ID, SARTHARION_OFFTANK_POSITION.first, SARTHARION_OFFTANK_POSITION.second, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}
bool AvoidTwilightFissureAction::Execute(Event event)
{
const float radius = 5.0f;
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == NPC_TWILIGHT_FISSURE)
{
float currentDistance = bot->GetDistance2d(unit);
if (currentDistance < radius)
{
return MoveAway(unit, radius - currentDistance);
}
}
}
return false;
}
bool AvoidFlameTsunamiAction::Execute(Event event)
{
// Adjustable, this is the acceptable distance to stack point that will be accepted as "safe"
float looseDistance = 4.0f;
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == NPC_FLAME_TSUNAMI)
{
Position currentPos = bot->GetPosition();
// I think these are centrepoints for the wave segments. Either way they uniquely identify the wave
// direction as they have different coords for the left and right waves
// int casting is not a mistake, need to avoid FP errors somehow.
// I always saw these accurate to around 6 decimal places, but if there are issues,
// can switch this to abs comparison of floats which would technically be more robust.
int posY = (int) unit->GetPositionY();
if (posY == 505 || posY == 555) // RIGHT WAVE
{
bool wavePassed = currentPos.GetPositionX() > unit->GetPositionX();
if (wavePassed)
{
return false;
}
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
else // LEFT WAVE
{
bool wavePassed = currentPos.GetPositionX() < unit->GetPositionX();
if (wavePassed)
{
return false;
}
if (botAI->IsMelee(bot))
{
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
else // Ranged/healers
{
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
}
}
return false;
}
bool SartharionAttackPriorityAction::Execute(Event event)
{
Unit* sartharion = AI_VALUE2(Unit*, "find target", "sartharion");
Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron");
Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron");
Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon");
Unit* acolyte = AI_VALUE2(Unit*, "find target", "acolyte of shadron");
Unit* target = nullptr;
if (acolyte)
{
target = acolyte;
}
else if (vesperon)
{
target = vesperon;
}
else if (tenebron)
{
target = tenebron;
}
else if (shadron)
{
target = shadron;
}
else if (sartharion)
{
target = sartharion;
}
if (target && AI_VALUE(Unit*, "current target") != target)
{
return Attack(target);
}
return false;
}
bool EnterTwilightPortalAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss || !boss->HasAura(SPELL_GIFT_OF_TWILIGHT_FIRE)) { return false; }
GameObject* portal = bot->FindNearestGameObject(GO_TWILIGHT_PORTAL, 100.0f);
if (!portal) { return false; }
if (!portal->IsAtInteractDistance(bot))
{
return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f));
}
// Go through portal
WorldPacket data1(CMSG_GAMEOBJ_USE);
data1 << portal->GetGUID();
bot->GetSession()->HandleGameObjectUseOpcode(data1);
return true;
}
bool ExitTwilightPortalAction::Execute(Event event)
{
GameObject* portal = bot->FindNearestGameObject(GO_NORMAL_PORTAL, 100.0f);
if (!portal) { return false; }
if (!portal->IsAtInteractDistance(bot))
{
return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f));
}
// Go through portal
WorldPacket data1(CMSG_GAMEOBJ_USE);
data1 << portal->GetGUID();
bot->GetSession()->HandleGameObjectUseOpcode(data1);
return true;
}

View File

@@ -0,0 +1,64 @@
#ifndef _PLAYERBOT_RAIDOSACTIONS_H
#define _PLAYERBOT_RAIDOSACTIONS_H
#include "MovementActions.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
const float TSUNAMI_LEFT_SAFE_MELEE = 552.0f;
const float TSUNAMI_LEFT_SAFE_RANGED = 504.0f;
const float TSUNAMI_RIGHT_SAFE_ALL = 529.0f;
const std::pair<float, float> SARTHARION_MAINTANK_POSITION = {3258.5f, 532.5f};
const std::pair<float, float> SARTHARION_OFFTANK_POSITION = {3230.0f, 526.0f};
const std::pair<float, float> SARTHARION_RANGED_POSITION = {3248.0f, 507.0f};
class SartharionTankPositionAction : public AttackAction
{
public:
SartharionTankPositionAction(PlayerbotAI* botAI, std::string const name = "sartharion tank position")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AvoidTwilightFissureAction : public MovementAction
{
public:
AvoidTwilightFissureAction(PlayerbotAI* botAI, std::string const name = "avoid twilight fissure")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class AvoidFlameTsunamiAction : public MovementAction
{
public:
AvoidFlameTsunamiAction(PlayerbotAI* botAI, std::string const name = "avoid flame tsunami")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class SartharionAttackPriorityAction : public AttackAction
{
public:
SartharionAttackPriorityAction(PlayerbotAI* botAI, std::string const name = "sartharion attack priority")
: AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class EnterTwilightPortalAction : public MovementAction
{
public:
EnterTwilightPortalAction(PlayerbotAI* botAI, std::string const name = "enter twilight portal")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class ExitTwilightPortalAction : public MovementAction
{
public:
ExitTwilightPortalAction(PlayerbotAI* botAI, std::string const name = "exit twilight portal")
: MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,49 @@
#include "RaidOsMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "RaidOsActions.h"
#include "RaidOsTriggers.h"
#include "ReachTargetActions.h"
#include "ScriptedCreature.h"
#include "WarriorActions.h"
float SartharionMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return 1.0f; }
Unit* target = action->GetTarget();
if (botAI->IsMainTank(bot) && dynamic_cast<TankFaceAction*>(action))
{
// return 0.0f;
}
if (botAI->IsDps(bot) && dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
if (botAI->IsMainTank(bot) && target && target != boss &&
(dynamic_cast<TankAssistAction*>(action) || dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action)))
{
return 0.0f;
}
if (botAI->IsAssistTank(bot) && target && target == boss &&
(dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action)))
{
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,16 @@
#ifndef _PLAYERRBOT_RAIDOSMULTIPLIERS_H
#define _PLAYERRBOT_RAIDOSMULTIPLIERS_H
#include "Multiplier.h"
class SartharionMultiplier : public Multiplier
{
public:
SartharionMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sartharion") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,30 @@
#ifndef _PLAYERBOT_RAIDOSACTIONCONTEXT_H
#define _PLAYERBOT_RAIDOSACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidOsActions.h"
class RaidOsActionContext : public NamedObjectContext<Action>
{
public:
RaidOsActionContext()
{
creators["sartharion tank position"] = &RaidOsActionContext::tank_position;
creators["avoid twilight fissure"] = &RaidOsActionContext::avoid_twilight_fissure;
creators["avoid flame tsunami"] = &RaidOsActionContext::avoid_flame_tsunami;
creators["sartharion attack priority"] = &RaidOsActionContext::attack_priority;
creators["enter twilight portal"] = &RaidOsActionContext::enter_twilight_portal;
creators["exit twilight portal"] = &RaidOsActionContext::exit_twilight_portal;
}
private:
static Action* tank_position(PlayerbotAI* ai) { return new SartharionTankPositionAction(ai); }
static Action* avoid_twilight_fissure(PlayerbotAI* ai) { return new AvoidTwilightFissureAction(ai); }
static Action* avoid_flame_tsunami(PlayerbotAI* ai) { return new AvoidFlameTsunamiAction(ai); }
static Action* attack_priority(PlayerbotAI* ai) { return new SartharionAttackPriorityAction(ai); }
static Action* enter_twilight_portal(PlayerbotAI* ai) { return new EnterTwilightPortalAction(ai); }
static Action* exit_twilight_portal(PlayerbotAI* ai) { return new ExitTwilightPortalAction(ai); }
};
#endif

View File

@@ -0,0 +1,32 @@
#ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidOsTriggers.h"
class RaidOsTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidOsTriggerContext()
{
creators["sartharion tank"] = &RaidOsTriggerContext::sartharion_tank;
creators["flame tsunami"] = &RaidOsTriggerContext::flame_tsunami;
creators["twilight fissure"] = &RaidOsTriggerContext::twilight_fissure;
creators["sartharion dps"] = &RaidOsTriggerContext::sartharion_dps;
creators["sartharion melee positioning"] = &RaidOsTriggerContext::sartharion_melee;
creators["twilight portal enter"] = &RaidOsTriggerContext::twilight_portal_enter;
creators["twilight portal exit"] = &RaidOsTriggerContext::twilight_portal_exit;
}
private:
static Trigger* sartharion_tank(PlayerbotAI* ai) { return new SartharionTankTrigger(ai); }
static Trigger* flame_tsunami(PlayerbotAI* ai) { return new FlameTsunamiTrigger(ai); }
static Trigger* twilight_fissure(PlayerbotAI* ai) { return new TwilightFissureTrigger(ai); }
static Trigger* sartharion_dps(PlayerbotAI* ai) { return new SartharionDpsTrigger(ai); }
static Trigger* sartharion_melee(PlayerbotAI* ai) { return new SartharionMeleePositioningTrigger(ai); }
static Trigger* twilight_portal_enter(PlayerbotAI* ai) { return new TwilightPortalEnterTrigger(ai); }
static Trigger* twilight_portal_exit(PlayerbotAI* ai) { return new TwilightPortalExitTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,32 @@
#include "RaidOsStrategy.h"
#include "RaidOsMultipliers.h"
#include "Strategy.h"
void RaidOsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("sartharion tank",
{ NextAction("sartharion tank position", ACTION_MOVE) }));
triggers.push_back(
new TriggerNode("twilight fissure",
{ NextAction("avoid twilight fissure", ACTION_RAID + 2) }));
triggers.push_back(
new TriggerNode("flame tsunami",
{ NextAction("avoid flame tsunami", ACTION_RAID + 1) }));
triggers.push_back(
new TriggerNode("sartharion dps",
{ NextAction("sartharion attack priority", ACTION_RAID) }));
// Flank dragon positioning
triggers.push_back(new TriggerNode("sartharion melee positioning",
{ NextAction("rear flank", ACTION_MOVE + 4) }));
triggers.push_back(new TriggerNode("twilight portal enter",
{ NextAction("enter twilight portal", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("twilight portal exit",
{ NextAction("exit twilight portal", ACTION_RAID + 1) }));
}
void RaidOsStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new SartharionMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,125 @@
#include "RaidOsTriggers.h"
#include "SharedDefines.h"
bool SartharionTankTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
return botAI->IsTank(bot);
}
bool FlameTsunamiTrigger::IsActive()
{
if (botAI->IsTank(bot)) { return false; }
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit)
{
if (unit->GetEntry() == NPC_FLAME_TSUNAMI)
{
return true;
}
}
}
return false;
}
bool TwilightFissureTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit)
{
if (unit->GetEntry() == NPC_TWILIGHT_FISSURE)
{
return true;
}
}
}
return false;
}
bool SartharionDpsTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
return botAI->IsDps(bot);
}
bool SartharionMeleePositioningTrigger::IsActive()
{
if (!botAI->IsMelee(bot) || !botAI->IsDps(bot)) { return false; }
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron");
Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron");
Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon");
return !(shadron || tenebron || vesperon);
}
bool TwilightPortalEnterTrigger::IsActive()
{
if (botAI->IsMainTank(bot) || botAI->IsHealAssistantOfIndex(bot, 0)) { return false; }
// In 25-man, take two healers in. Otherwise just take one
// if (bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL)
// {
// if (botAI->IsHealAssistantOfIndex(bot, 0) || botAI->IsHealAssistantOfIndex(bot, 1))
// {
// return false;
// }
// }
// else
// {
// if (botAI->IsHealAssistantOfIndex(bot, 0))
// {
// return false;
// }
// }
// Don't enter portal until drakes are dead
if (bot->HasAura(SPELL_POWER_OF_SHADRON) ||
bot->HasAura(SPELL_POWER_OF_TENEBRON) ||
bot->HasAura(SPELL_POWER_OF_VESPERON))
{
return false;
}
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; }
// GuidVector objects = AI_VALUE(GuidVector, "nearest game objects no los");
// for (auto& object : objects)
// {
// GameObject* go = botAI->GetGameObject(object);
// if (go && go->GetEntry() == GO_TWILIGHT_PORTAL)
// {
// return true;
// }
// }
return bool(bot->FindNearestGameObject(GO_TWILIGHT_PORTAL, 100.0f));
}
bool TwilightPortalExitTrigger::IsActive()
{
return bot->HasAura(SPELL_TWILIGHT_SHIFT) && !AI_VALUE2(Unit*, "find target", "acolyte of shadron");
}

View File

@@ -0,0 +1,120 @@
#ifndef _PLAYERBOT_RAIDOSTRIGGERS_H
#define _PLAYERBOT_RAIDOSTRIGGERS_H
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "Trigger.h"
enum ObsidianSanctumIDs
{
// Bosses
NPC_SARTHARION = 28860,
NPC_SHADRON = 30451,
NPC_TENEBRON = 30452,
NPC_VESPERON = 30449,
// Mini-boss shared
SPELL_SHADOW_BREATH = 57570,
SPELL_SHADOW_FISSURE = 57579,
SPELL_SUMMON_TWILIGHT_WHELP = 58035,
SPELL_GIFT_OF_TWILIGHT_SHADOW = 57835,
SPELL_TWILIGHT_TORMENT_VESPERON = 57935,
// Sartharion
SPELL_SARTHARION_CLEAVE = 56909,
SPELL_SARTHARION_FLAME_BREATH = 56908,
SPELL_SARTHARION_TAIL_LASH = 56910,
SPELL_CYCLONE_AURA_PERIODIC = 57598,
SPELL_LAVA_STRIKE_DUMMY = 57578,
SPELL_LAVA_STRIKE_DUMMY_TRIGGER = 57697,
SPELL_LAVA_STRIKE_SUMMON = 57572,
SPELL_SARTHARION_PYROBUFFET = 56916,
SPELL_SARTHARION_BERSERK = 61632,
SPELL_SARTHARION_TWILIGHT_REVENGE = 60639,
// Sartharion with drakes
SPELL_WILL_OF_SARTHARION = 61254,
SPELL_POWER_OF_TENEBRON = 61248,
SPELL_POWER_OF_VESPERON = 61251,
SPELL_POWER_OF_SHADRON = 58105,
SPELL_GIFT_OF_TWILIGHT_FIRE = 58766,
// Visuals
SPELL_EGG_MARKER_VISUAL = 58547,
SPELL_FLAME_TSUNAMI_VISUAL = 57494,
// Misc
SPELL_FADE_ARMOR = 60708,
SPELL_FLAME_TSUNAMI_DAMAGE_AURA = 57492,
SPELL_FLAME_TSUNAMI_LEAP = 60241,
SPELL_SARTHARION_PYROBUFFET_TRIGGER = 57557,
NPC_TWILIGHT_EGG = 30882,
NPC_TWILIGHT_WHELP = 30890,
NPC_DISCIPLE_OF_SHADRON = 30688,
NPC_DISCIPLE_OF_VESPERON = 30858,
NPC_ACOLYTE_OF_SHADRON = 31218,
NPC_ACOLYTE_OF_VESPERON = 31219,
// Sartharion fight
NPC_LAVA_BLAZE = 30643,
NPC_FLAME_TSUNAMI = 30616,
NPC_SAFE_AREA_TRIGGER = 30494,
NPC_TWILIGHT_FISSURE = 30641,
GO_TWILIGHT_PORTAL = 193988,
GO_NORMAL_PORTAL = 193989,
SPELL_TWILIGHT_SHIFT = 57874,
};
const uint32 OS_MAP_ID = 615;
class SartharionTankTrigger : public Trigger
{
public:
SartharionTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion tank") {}
bool IsActive() override;
};
class FlameTsunamiTrigger : public Trigger
{
public:
FlameTsunamiTrigger(PlayerbotAI* botAI) : Trigger(botAI, "flame tsunami") {}
bool IsActive() override;
};
class TwilightFissureTrigger : public Trigger
{
public:
TwilightFissureTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight fissure") {}
bool IsActive() override;
};
class SartharionDpsTrigger : public Trigger
{
public:
SartharionDpsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion dps") {}
bool IsActive() override;
};
class SartharionMeleePositioningTrigger : public Trigger
{
public:
SartharionMeleePositioningTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion melee positioning") {}
bool IsActive() override;
};
class TwilightPortalEnterTrigger : public Trigger
{
public:
TwilightPortalEnterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight portal enter") {}
bool IsActive() override;
};
class TwilightPortalExitTrigger : public Trigger
{
public:
TwilightPortalExitTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight portal exit") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,147 @@
// RaidOnyxiaActions.cpp
#include "RaidOnyxiaActions.h"
#include "GenericSpellActions.h"
#include "LastMovementValue.h"
#include "MovementActions.h"
#include "Playerbots.h"
#include "PositionAction.h"
bool RaidOnyxiaMoveToSideAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss)
return false;
float angleToBot = boss->GetAngle(bot);
float bossFacing = boss->GetOrientation();
float diff = fabs(angleToBot - bossFacing);
if (diff > M_PI)
diff = 2 * M_PI - diff;
float distance = bot->GetDistance(boss);
// Too close (30 yards) and either in front or behind
if (distance <= 30.0f && (diff < M_PI / 4 || diff > 3 * M_PI / 4))
{
float offsetAngle = bossFacing + M_PI_2; // 90° to the right
float offsetDist = 15.0f;
float sideX = boss->GetPositionX() + offsetDist * cos(offsetAngle);
float sideY = boss->GetPositionY() + offsetDist * sin(offsetAngle);
// bot->Yell("Too close to front or tail — moving to side of Onyxia!", LANG_UNIVERSAL);
return MoveTo(boss->GetMapId(), sideX, sideY, boss->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool RaidOnyxiaSpreadOutAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss)
return false;
Player* target = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL)->m_targets.GetUnitTarget()->ToPlayer();
if (target != bot)
return false;
// bot->Yell("Spreading out — I'm the Fireball target!", LANG_UNIVERSAL);
return MoveFromGroup(9.0f); // move 9 yards
}
bool RaidOnyxiaMoveToSafeZoneAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss)
return false;
Spell* currentSpell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!currentSpell)
return false;
uint32 spellId = currentSpell->m_spellInfo->Id;
std::vector<SafeZone> safeZones = GetSafeZonesForBreath(spellId);
if (safeZones.empty())
return false;
// Find closest safe zone
SafeZone* bestZone = nullptr;
float bestDist = std::numeric_limits<float>::max();
for (auto& zone : safeZones)
{
float dist = bot->GetExactDist2d(zone.pos.GetPositionX(), zone.pos.GetPositionY());
if (dist < bestDist)
{
bestDist = dist;
bestZone = &zone;
}
}
if (!bestZone)
return false;
if (bot->IsWithinDist2d(bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->radius))
return false; // Already safe
// bot->Yell("Moving to Safe Zone!", LANG_UNIVERSAL);
return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
bool RaidOnyxiaKillWhelpsAction::Execute(Event event)
{
Unit* currentTarget = AI_VALUE(Unit*, "current target");
// If already attacking a whelp, don't swap targets
if (currentTarget && currentTarget->GetEntry() == 11262)
{
return false;
}
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
for (ObjectGuid guid : targets)
{
Creature* unit = botAI->GetCreature(guid);
if (!unit || !unit->IsAlive() || !unit->IsInWorld())
continue;
if (unit->GetEntry() == 11262) // Onyxia Whelp
{
// bot->Yell("Attacking Whelps!", LANG_UNIVERSAL);
return Attack(unit);
}
}
return false;
}
bool OnyxiaAvoidEggsAction::Execute(Event event)
{
Position botPos = Position(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
float x, y;
// get safe zone slightly away from eggs (Can this be dynamic?)
if (botPos.GetExactDist2d(-36.0f, -164.0f) <= 5.0f)
{
x = -10.0f;
y = -180.0f;
}
else if (botPos.GetExactDist2d(-34.0f, -262.0f) <= 5.0f)
{
x = -16.0f;
y = -250.0f;
}
else
{
return false; // Not in danger zone
}
// bot->Yell("Too close to eggs — backing off!", LANG_UNIVERSAL);
return MoveTo(bot->GetMapId(), x, y, bot->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
}

View File

@@ -0,0 +1,107 @@
// RaidOnyxiaActions.h
#ifndef _PLAYERBOT_RAIDONYXIAACTIONS_H_
#define _PLAYERBOT_RAIDONYXIAACTIONS_H_
#include "Action.h"
#include "AttackAction.h"
#include "GenericSpellActions.h"
#include "MovementActions.h"
class PlayerbotAI;
class RaidOnyxiaMoveToSideAction : public MovementAction
{
public:
RaidOnyxiaMoveToSideAction(PlayerbotAI* botAI, std::string const name = "ony move to side")
: MovementAction(botAI, name)
{
}
bool Execute(Event event) override;
};
class RaidOnyxiaSpreadOutAction : public MovementAction
{
public:
RaidOnyxiaSpreadOutAction(PlayerbotAI* botAI, std::string const name = "ony spread out")
: MovementAction(botAI, name)
{
}
bool Execute(Event event) override;
};
struct SafeZone
{
Position pos;
float radius;
};
class RaidOnyxiaMoveToSafeZoneAction : public MovementAction
{
public:
RaidOnyxiaMoveToSafeZoneAction(PlayerbotAI* botAI, std::string const name = "ony move to safe zone")
: MovementAction(botAI, name)
{
}
bool Execute(Event event) override;
private:
std::vector<SafeZone> GetSafeZonesForBreath(uint32 spellId)
{
// Define your safe zone coordinates based on the map
// Example assumes Onyxia's lair map coordinates
float z = bot->GetPositionZ(); // Stay at current height
switch (spellId)
{
case 17086: // N to S
case 18351: // S to N
return {SafeZone{Position(-10.0f, -180.0f, z), 5.0f},
SafeZone{Position(-20.0f, -250.0f, z), 5.0f}}; // Bottom Safe Zone
case 18576: // E to W
case 18609: // W to E
return {
SafeZone{Position(20.0f, -210.0f, z), 5.0f},
SafeZone{Position(-75.0f, -210.0f, z), 5.0f},
}; // Left Safe Zone
case 18564: // SE to NW
case 18584: // NW to SE
return {
SafeZone{Position(-60.0f, -195.0f, z), 5.0f},
SafeZone{Position(10.0f, -240.0f, z), 5.0f},
}; // NW Safe Zone
case 18596: // SW to NE
case 18617: // NE to SW
return {
SafeZone{Position(7.0f, -185.0f, z), 5.0f},
SafeZone{Position(-60.0f, -240.0f, z), 5.0f},
}; // NE Safe Zone
default:
return {SafeZone{Position(0.0f, 0.0f, z), 5.0f}}; // Fallback center - shouldn't ever happen
}
}
};
class RaidOnyxiaKillWhelpsAction : public AttackAction
{
public:
RaidOnyxiaKillWhelpsAction(PlayerbotAI* botAI, std::string const name = "ony kill whelps")
: AttackAction(botAI, name)
{
}
bool Execute(Event event) override;
};
class OnyxiaAvoidEggsAction : public MovementAction
{
public:
OnyxiaAvoidEggsAction(PlayerbotAI* botAI) : MovementAction(botAI, "ony avoid eggs move") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,28 @@
#ifndef _PLAYERBOT_RAIDONYXIAACTIONS_CONTEXT_H
#define _PLAYERBOT_RAIDONYXIAACTIONS_CONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidOnyxiaActions.h"
class RaidOnyxiaActionContext : public NamedObjectContext<Action>
{
public:
RaidOnyxiaActionContext()
{
creators["ony move to side"] = &RaidOnyxiaActionContext::move_to_side;
creators["ony spread out"] = &RaidOnyxiaActionContext::spread_out;
creators["ony move to safe zone"] = &RaidOnyxiaActionContext::move_to_safe_zone;
creators["ony kill whelps"] = &RaidOnyxiaActionContext::kill_whelps;
creators["ony avoid eggs move"] = &RaidOnyxiaActionContext::avoid_eggs;
}
private:
static Action* move_to_side(PlayerbotAI* ai) { return new RaidOnyxiaMoveToSideAction(ai); }
static Action* spread_out(PlayerbotAI* ai) { return new RaidOnyxiaSpreadOutAction(ai); }
static Action* move_to_safe_zone(PlayerbotAI* ai) { return new RaidOnyxiaMoveToSafeZoneAction(ai); }
static Action* kill_whelps(PlayerbotAI* ai) { return new RaidOnyxiaKillWhelpsAction(ai); }
static Action* avoid_eggs(PlayerbotAI* ai) { return new OnyxiaAvoidEggsAction(ai); }
};
#endif

View File

@@ -0,0 +1,28 @@
#ifndef _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidOnyxiaTriggers.h"
class RaidOnyxiaTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidOnyxiaTriggerContext()
{
creators["ony near tail"] = &RaidOnyxiaTriggerContext::near_tail;
creators["ony deep breath warning"] = &RaidOnyxiaTriggerContext::deep_breath;
creators["ony fireball splash incoming"] = &RaidOnyxiaTriggerContext::fireball_splash;
creators["ony whelps spawn"] = &RaidOnyxiaTriggerContext::whelps_spawn;
creators["ony avoid eggs"] = &RaidOnyxiaTriggerContext::avoid_eggs;
}
private:
static Trigger* near_tail(PlayerbotAI* ai) { return new OnyxiaNearTailTrigger(ai); }
static Trigger* deep_breath(PlayerbotAI* ai) { return new OnyxiaDeepBreathTrigger(ai); }
static Trigger* fireball_splash(PlayerbotAI* ai) { return new RaidOnyxiaFireballSplashTrigger(ai); }
static Trigger* whelps_spawn(PlayerbotAI* ai) { return new RaidOnyxiaWhelpsSpawnTrigger(ai); }
static Trigger* avoid_eggs(PlayerbotAI* ai) { return new OnyxiaAvoidEggsTrigger(ai); }
};
#endif

Some files were not shown because too many files have changed in this diff Show More