Files
mod-playerbots/src/strategy/raids/icecrown/RaidIccActions.cpp
Yunfan Li f6cc3f6e40 Improve ICC spread and fix Valithria trigger (#884)
* Temp fix valithria find target

* Improve spread with FleePosition

* Fix Valithria trigger
2025-01-21 20:24:06 +01:00

3235 lines
113 KiB
C++

#include "RaidIccActions.h"
#include "RaidIccStrategy.h"
#include "Playerbots.h"
#include "Timer.h"
#include "Vehicle.h"
#include "GenericSpellActions.h"
#include "GenericActions.h"
#include <fstream>
enum CreatureIds {
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,
};
const std::vector<uint32> availableTargets = {
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 Marrowgwar
bool IccLmTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar");
if (!boss)
return false;
bot->SetTarget(boss->GetGUID());
if (botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot))
{
if (bot->GetExactDist2d(ICC_LM_TANK_POSITION) > 15.0f)
return MoveTo(bot->GetMapId(), ICC_LM_TANK_POSITION.GetPositionX(),
ICC_LM_TANK_POSITION.GetPositionY(), ICC_LM_TANK_POSITION.GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_NORMAL);
else
return Attack(boss);
}
return Attack(boss);
}
bool IccSpikeAction::Execute(Event event)
{
// If we're impaled, we can't do anything
if (bot->HasAura(69065) || // Impaled (10N)
bot->HasAura(72669) || // Impaled (25N)
bot->HasAura(72670) || // Impaled (10H)
bot->HasAura(72671)) // Impaled (25H)
{
return false;
}
// Find the bos
Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar");
if (!boss) { return false; }
Unit* currentTarget = AI_VALUE(Unit*, "current target");
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
Unit* spikeTarget = nullptr;
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && (unit->GetEntry() == 36619 ||
unit->GetEntry() == 38711 ||
unit->GetEntry() == 38712))
{
spikeTarget = unit;
break;
}
}
// Prioritize attacking a bone spike if found
if (spikeTarget)
{
if (currentTarget != spikeTarget)
{
if (Attack(spikeTarget))
{
return Attack(spikeTarget);
}
}
return Attack(spikeTarget); // Already attacking a spike
}
// No bone spikes found, attack boss if not already targeting
if (currentTarget != boss)
{
if (Attack(boss))
{
return Attack(boss);
}
}
return false;
}
//Lady
bool IccDarkReckoningAction::Execute(Event event)
{
if (bot->HasAura(69483) && bot->GetExactDist2d(ICC_DARK_RECKONING_SAFE_POSITION) > 2.0f) //dark reckoning spell id
{
return MoveTo(bot->GetMapId(), ICC_DARK_RECKONING_SAFE_POSITION.GetPositionX(),
ICC_DARK_RECKONING_SAFE_POSITION.GetPositionY(), ICC_DARK_RECKONING_SAFE_POSITION.GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_NORMAL);
}
return false;
}
bool IccRangedPositionLadyDeathwhisperAction::Execute(Event event)
{
if (botAI->IsRanged(bot) || botAI->IsHeal(bot))
{
float radius = 5.0f;
float moveIncrement = 3.0f;
bool isRanged = botAI->IsRanged(bot);
GuidVector members = AI_VALUE(GuidVector, "group members");
if (isRanged)
{
// Ranged: spread from other members
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || !unit->IsAlive() || unit == bot)
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
{
float moveDistance = std::min(moveIncrement, radius - dist + 1.0f);
return FleePosition(unit->GetPosition(), moveDistance);
// return MoveAway(unit, moveDistance);
}
}
}
return false; // Everyone is in position
}
return false;
}
bool IccAddsLadyDeathwhisperAction::Execute(Event event)
{
if (botAI->IsMainTank(bot) || botAI->IsHeal(bot))
{
return false;
}
// Find the boss
Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper");
if (!boss) { return false; }
Unit* currentTarget = AI_VALUE(Unit*, "current target");
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
Unit* add = nullptr;
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && (unit->GetEntry() == 37949 || //cult adherent
unit->GetEntry() == 38394 ||
unit->GetEntry() == 38625 ||
unit->GetEntry() == 38626 ||
unit->GetEntry() == 38010 ||
unit->GetEntry() == 38397 ||
unit->GetEntry() == 39000 ||
unit->GetEntry() == 39001 ||
unit->GetEntry() == 38136 ||
unit->GetEntry() == 38396 ||
unit->GetEntry() == 38632 ||
unit->GetEntry() == 38633 ||
unit->GetEntry() == 37890 || //cult fanatic
unit->GetEntry() == 38393 ||
unit->GetEntry() == 38628 ||
unit->GetEntry() == 38629 ||
unit->GetEntry() == 38135 ||
unit->GetEntry() == 38395 ||
unit->GetEntry() == 38634 ||
unit->GetEntry() == 38009 ||
unit->GetEntry() == 38398 ||
unit->GetEntry() == 38630 ||
unit->GetEntry() == 38631))
{
add = unit;
break;
}
}
// Prioritize attacking an add if found
if (add)
{
if (currentTarget != add)
{
if (Attack(add))
{
return Attack(add);
}
}
return Attack(add); // Already attacking an add
}
// No adds found, attack boss if not already targeting
if (currentTarget != boss)
{
if (Attack(boss))
{
return Attack(boss);
}
}
return false;
}
bool IccShadeLadyDeathwhisperAction::Execute(Event event)
{
const float radius = 12.0f;
// Get the nearest hostile NPCs
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == 38222) //vengeful shade ID
{
// Only run away if the shade is targeting us
if (unit->GetVictim() == bot)
{
float currentDistance = bot->GetDistance2d(unit);
// Move away from the Vengeful Shade if the bot is too close
if (currentDistance < radius)
{
return MoveAway(unit, radius - currentDistance);
}
}
}
}
return false;
}
bool IccRottingFrostGiantTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "rotting frost giant");
if (!boss)
return false;
bot->SetTarget(boss->GetGUID());
if (botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot))
{
if (bot->GetExactDist2d(ICC_ROTTING_FROST_GIANT_TANK_POSITION) > 5.0f)
return MoveTo(bot->GetMapId(), ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionX(),
ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionY(), ICC_ROTTING_FROST_GIANT_TANK_POSITION.GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_NORMAL);
}
float radius = 10.0f;
float distanceExtra = 2.0f;
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
if (bot->GetGUID() == member)
{
continue;
}
Unit* unit = botAI->GetUnit(member);
if (unit && (botAI->IsHeal(bot) || botAI->IsDps(bot)) && bot->GetExactDist2d(unit) < radius)
{
return FleePosition(unit->GetPosition(), radius + distanceExtra - bot->GetExactDist2d(unit));
// return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit));
}
}
return Attack(boss);
}
//Gunship
bool IccCannonFireAction::Execute(Event event)
{
Unit* vehicleBase = bot->GetVehicleBase();
Vehicle* vehicle = bot->GetVehicle();
if (!vehicleBase || !vehicle)
return false;
GuidVector attackers = AI_VALUE(GuidVector, "possible targets no los");
Unit* target = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
for (uint32 entry : availableTargets)
{
if (unit->GetEntry() == entry) {
target = unit;
break;
}
}
if (target)
break;
}
if (!target)
return false;
if (vehicleBase->GetPower(POWER_ENERGY) >= 90) {
uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", "incinerating blast");
if (botAI->CanCastVehicleSpell(spellId, target) && botAI->CastVehicleSpell(spellId, target)) {
vehicleBase->AddSpellCooldown(spellId, 0, 1000);
return true;
}
}
uint32 spellId = AI_VALUE2(uint32, "vehicle spell id", "cannon blast");
if (botAI->CanCastVehicleSpell(spellId, target) && botAI->CastVehicleSpell(spellId, target)) {
vehicleBase->AddSpellCooldown(spellId, 0, 1000);
return true;
}
return false;
}
bool IccGunshipEnterCannonAction::Execute(Event event)
{
// do not switch vehicles yet
if (bot->GetVehicle())
return false;
Unit* vehicleToEnter = nullptr;
GuidVector npcs = AI_VALUE(GuidVector, "nearest vehicles");
for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++)
{
Unit* vehicleBase = botAI->GetUnit(*i);
if (!vehicleBase)
continue;
if (vehicleBase->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
continue;
if (!vehicleBase->IsFriendlyTo(bot))
continue;
if (!vehicleBase->GetVehicleKit() || !vehicleBase->GetVehicleKit()->GetAvailableSeatCount())
continue;
uint32 entry = vehicleBase->GetEntry();
if (entry != 36838 && entry != 36839)
continue;
if (vehicleBase->HasAura(69704) || vehicleBase->HasAura(69705))
continue;
if (!vehicleToEnter || bot->GetExactDist(vehicleToEnter) > bot->GetExactDist(vehicleBase))
vehicleToEnter = vehicleBase;
}
if (!vehicleToEnter)
return false;
if (EnterVehicle(vehicleToEnter, true))
return true;
return false;
}
bool IccGunshipEnterCannonAction::EnterVehicle(Unit* vehicleBase, bool moveIfFar)
{
float dist = bot->GetDistance(vehicleBase);
if (dist > INTERACTION_DISTANCE && !moveIfFar)
return false;
if (dist > INTERACTION_DISTANCE)
return MoveTo(vehicleBase);
botAI->RemoveShapeshift();
bot->GetMotionMaster()->Clear();
bot->StopMoving();
vehicleBase->HandleSpellClick(bot);
if (!bot->IsOnVehicle(vehicleBase))
return false;
// dismount because bots can enter vehicle on mount
WorldPacket emptyPacket;
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
return true;
}
bool IccGunshipTeleportAllyAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "kor'kron battle-mage");
if (!boss)
return false;
// Only target the mage that is channeling Below Zero
if (!(boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(69705)))
{
if (bot->GetExactDist2d(ICC_GUNSHIP_TELEPORT_ALLY2) > 45.0f)
return bot->TeleportTo(bot->GetMapId(), ICC_GUNSHIP_TELEPORT_ALLY2.GetPositionX(),
ICC_GUNSHIP_TELEPORT_ALLY2.GetPositionY(), ICC_GUNSHIP_TELEPORT_ALLY2.GetPositionZ(), bot->GetOrientation());
}
bot->SetTarget(boss->GetGUID());
// Check if the bot is targeting a valid boss before teleporting
if (bot->GetTarget() != boss->GetGUID())
return false;
if (bot->GetExactDist2d(ICC_GUNSHIP_TELEPORT_ALLY) > 15.0f)
return bot->TeleportTo(bot->GetMapId(), ICC_GUNSHIP_TELEPORT_ALLY.GetPositionX(),
ICC_GUNSHIP_TELEPORT_ALLY.GetPositionY(), ICC_GUNSHIP_TELEPORT_ALLY.GetPositionZ(), bot->GetOrientation());
else
return Attack(boss);
}
bool IccGunshipTeleportHordeAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "skybreaker sorcerer");
if (!boss)
return false;
// Only target the sorcerer that is channeling Below Zero
if (!(boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(69705)))
{
if (bot->GetExactDist2d(ICC_GUNSHIP_TELEPORT_HORDE2) > 45.0f)
return bot->TeleportTo(bot->GetMapId(), ICC_GUNSHIP_TELEPORT_HORDE2.GetPositionX(),
ICC_GUNSHIP_TELEPORT_HORDE2.GetPositionY(), ICC_GUNSHIP_TELEPORT_HORDE2.GetPositionZ(), bot->GetOrientation());
}
bot->SetTarget(boss->GetGUID());
// Check if the bot is targeting a valid boss before teleporting
if (bot->GetTarget() != boss->GetGUID())
return false;
if (bot->GetExactDist2d(ICC_GUNSHIP_TELEPORT_HORDE) > 15.0f)
return bot->TeleportTo(bot->GetMapId(), ICC_GUNSHIP_TELEPORT_HORDE.GetPositionX(),
ICC_GUNSHIP_TELEPORT_HORDE.GetPositionY(), ICC_GUNSHIP_TELEPORT_HORDE.GetPositionZ(), bot->GetOrientation());
else
return Attack(boss);
}
//DBS
bool IccDbsTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang");
if (!boss)
return false;
bot->SetTarget(boss->GetGUID());
if (botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot))
{
if (bot->GetExactDist2d(ICC_DBS_TANK_POSITION) > 5.0f)
return MoveTo(bot->GetMapId(), ICC_DBS_TANK_POSITION.GetPositionX(),
ICC_DBS_TANK_POSITION.GetPositionY(), ICC_DBS_TANK_POSITION.GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_NORMAL);
}
if (bot->HasAura(71042) || bot->HasAura(72408))
return true;
if (botAI->IsRanged(bot) || botAI->IsHeal(bot))
{
float radius = 9.0f;
float moveIncrement = 3.0f;
bool isRanged = botAI->IsRanged(bot);
GuidVector members = AI_VALUE(GuidVector, "group members");
if (isRanged)
{
// Ranged: spread from other members
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || !unit->IsAlive() || unit == bot || botAI->IsTank(bot) || botAI->IsMelee(bot))
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
{
float moveDistance = std::min(moveIncrement, radius - dist + 1.0f);
return FleePosition(unit->GetPosition(), moveDistance);
// return MoveAway(unit, moveDistance);
}
}
}
return false; // Everyone is in position
}
return false;
}
bool IccAddsDbsAction::Execute(Event event)
{
if (botAI->IsHeal(bot))
{
return false;
}
// Find the boss
Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang");
if (!boss) { return false; }
if (!(bot->HasAura(71042) || bot->HasAura(72408)) && botAI->IsMainTank(bot)) //rune of blood aura
return false;
Unit* currentTarget = AI_VALUE(Unit*, "current target");
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
Unit* add = nullptr;
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && (unit->GetEntry() == 38508 || //blood beast
unit->GetEntry() == 38596 ||
unit->GetEntry() == 38597 ||
unit->GetEntry() == 38598))
{
add = unit;
break;
}
}
// Prioritize attacking an add if found
if (add && !botAI->IsHeal(bot))
{
if (currentTarget != add)
{
if (Attack(add))
{
return Attack(add);
}
}
return Attack(add); // Already attacking an add
}
// No adds found, attack boss if not already targeting
if (currentTarget != boss)
{
if (Attack(boss))
{
return Attack(boss);
}
}
return false;
}
//FESTERGUT
bool IccFestergutTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
if (!boss)
return false;
bot->SetTarget(boss->GetGUID());
if (botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot))
{
if (bot->GetExactDist2d(ICC_FESTERGUT_TANK_POSITION) > 5.0f)
return MoveTo(bot->GetMapId(), ICC_FESTERGUT_TANK_POSITION.GetPositionX(),
ICC_FESTERGUT_TANK_POSITION.GetPositionY(), ICC_FESTERGUT_TANK_POSITION.GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_NORMAL);
}
float radius = 10.0f;
GuidVector members = AI_VALUE(GuidVector, "group members");
// First check if any group members have spores
bool sporesPresent = false;
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (unit && unit->HasAura(69279)) // gas spore
{
sporesPresent = true;
break;
}
}
// Only spread out if no spores are active
if (!sporesPresent && (botAI->IsRanged(bot) || botAI->IsHeal(bot)))
{
// Find closest player (including melee)
Unit* closestPlayer = nullptr;
float minDist = radius;
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || unit == bot)
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < minDist)
{
minDist = dist;
closestPlayer = unit;
}
}
if (closestPlayer)
{
// Move away from closest player, but maintain roughly max range from boss
float distToCenter = bot->GetExactDist2d(ICC_FESTERGUT_TANK_POSITION);
float moveDistance = (distToCenter > 25.0f) ? 2.0f : 3.0f; // Move less if already far from center
return FleePosition(closestPlayer->GetPosition(), moveDistance);
// return MoveAway(closestPlayer, moveDistance);
}
}
return false;
}
bool IccFestergutSporeAction::Execute(Event event)
{
const float POSITION_TOLERANCE = 4.0f;
const float SPREAD_RADIUS = 2.0f; // How far apart ranged should spread
bool hasSpore = bot->HasAura(69279); // gas spore
// If bot has spore, stop attacking
if (hasSpore)
{
bot->AttackStop();
}
// Calculate a unique spread position for ranged
float angle = (bot->GetGUID().GetCounter() % 16) * (M_PI / 8); // Divide circle into 16 positions
Position spreadRangedPos = ICC_FESTERGUT_RANGED_SPORE;
spreadRangedPos.m_positionX += cos(angle) * SPREAD_RADIUS;
spreadRangedPos.m_positionY += sin(angle) * SPREAD_RADIUS;
// Find all spored players and the one with lowest GUID
ObjectGuid lowestGuid;
bool isFirst = true;
std::vector<Unit*> sporedPlayers;
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit)
continue;
if (unit->HasAura(69279))
{
sporedPlayers.push_back(unit);
if (isFirst || unit->GetGUID() < lowestGuid)
{
lowestGuid = unit->GetGUID();
isFirst = false;
}
}
}
// If no spores present at all, return
if (sporedPlayers.empty())
return false;
Position targetPos;
if (hasSpore)
{
// If bot is tank, always go melee
if (botAI->IsTank(bot))
{
targetPos = ICC_FESTERGUT_MELEE_SPORE;
}
// If this bot has the lowest GUID among spored players, it goes melee
else if (bot->GetGUID() == lowestGuid)
{
targetPos = ICC_FESTERGUT_MELEE_SPORE;
}
// All other spored players go ranged
else
{
targetPos = spreadRangedPos;
}
}
else
{
// If bot doesn't have spore, go to position based on role
targetPos = botAI->IsMelee(bot) ? ICC_FESTERGUT_MELEE_SPORE : spreadRangedPos;
}
// Only move if we're not already at the target position
if (bot->GetExactDist2d(targetPos) > POSITION_TOLERANCE)
{
return MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(),
true, false, false, true, MovementPriority::MOVEMENT_FORCED);
}
return hasSpore;
}
//ROTFACE
bool IccRotfaceTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "rotface");
if (!boss)
return false;
// Main tank positioning logic
if (botAI->IsMainTank(bot))
{
if (bot->GetExactDist2d(ICC_ROTFACE_TANK_POSITION) > 7.0f)
return MoveTo(bot->GetMapId(), ICC_ROTFACE_TANK_POSITION.GetPositionX(),
ICC_ROTFACE_TANK_POSITION.GetPositionY(), ICC_ROTFACE_TANK_POSITION.GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_COMBAT);
}
bool hasOozeFlood = botAI->HasAura("Ooze Flood", bot);
// Assist tank positioning for big ooze
if (botAI->IsAssistTank(bot))
{
// If we have the ooze flood aura, move away
if (hasOozeFlood)
{
return MoveTo(boss->GetMapId(), boss->GetPositionX() + 5.0f * cos(bot->GetAngle(boss)),
boss->GetPositionY() + 5.0f * sin(bot->GetAngle(boss)), bot->GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_COMBAT);
}
Unit* bigOoze = AI_VALUE2(Unit*, "find target", "big ooze");
if (bigOoze)
{
// Taunt if not targeting us
if (bigOoze->GetVictim() != bot)
{
if (botAI->CastSpell("taunt", bigOoze))
return true;
return Attack(bigOoze);
}
// Keep big ooze at designated position
if (bigOoze->GetVictim() == bot)
{
if (bot->GetExactDist2d(ICC_ROTFACE_BIG_OOZE_POSITION) > 5.0f)
{
return MoveTo(bot->GetMapId(), ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionX(),
ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionY(), ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionZ(),
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
}
return Attack(bigOoze);
}
return Attack(bigOoze);
}
}
return false;
}
bool IccRotfaceGroupPositionAction::Execute(Event event)
{
// Find Rotface
Unit* boss = AI_VALUE2(Unit*, "find target", "rotface");
if (!boss)
return false;
// Check for puddles and move away if too close
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
bool hasOozeFlood = botAI->HasAura("Ooze Flood", bot);
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit)
{
if (unit->GetEntry() == 37013) // puddle
{
float puddleDistance = bot->GetExactDist2d(unit);
if (puddleDistance < 30.0f && (hasOozeFlood))
{
float dx = boss->GetPositionX() - unit->GetPositionX();
float dy = boss->GetPositionY() - unit->GetPositionY();
float angle = atan2(dy, dx);
// Move away from puddle in smaller increment
float moveDistance = std::min(35.0f - puddleDistance, 5.0f);
float moveX = boss->GetPositionX() + (moveDistance * cos(angle));
float moveY = boss->GetPositionY() + (moveDistance * sin(angle));
return MoveTo(boss->GetMapId(), moveX, moveY, boss->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
}
// Check if we're targeted by little ooze
Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze");
bool hasMutatedInfection = botAI->HasAura("Mutated Infection", bot);
if ((smallOoze && smallOoze->GetVictim() == bot) || hasMutatedInfection)
{
if (bot->GetExactDist2d(ICC_ROTFACE_BIG_OOZE_POSITION) > 3.0f)
{
return MoveTo(bot->GetMapId(), ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionX(),
ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionY(), ICC_ROTFACE_BIG_OOZE_POSITION.GetPositionZ(),
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
}
return true; // Stay at position
}
if(botAI->IsRanged(bot) || botAI->IsHeal(bot))
{
if (!hasOozeFlood)
{
float radius = 10.0f;
Unit* closestMember = nullptr;
float minDist = radius;
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || bot->GetGUID() == member)
continue;
// Skip distance check if the other unit is an assist tank
if (botAI->IsAssistTank(bot))
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < minDist)
{
minDist = dist;
closestMember = unit;
}
}
if (closestMember)
{
float distToCenter = bot->GetExactDist2d(ICC_ROTFACE_TANK_POSITION);
float moveDistance = (distToCenter > 25.0f) ? 2.0f : 3.0f;
// return MoveAway(closestMember, moveDistance);
return FleePosition(closestMember->GetPosition(), moveDistance);
}
return false;
}
}
return false;
}
bool IccRotfaceMoveAwayFromExplosionAction::Execute(Event event)
{
if (botAI->IsMainTank(bot) || bot->HasAura(71215))
{ return false; }
// Stop current actions first
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
// Generate random angle between 0 and 2π
float angle = frand(0, 2 * M_PI);
// Calculate position 20 yards away in random direction
float moveX = bot->GetPositionX() + 20.0f * cos(angle);
float moveY = bot->GetPositionY() + 20.0f * sin(angle);
float moveZ = bot->GetPositionZ();
// Move to the position
return MoveTo(bot->GetMapId(), moveX, moveY, moveZ,
false, false, false, false, MovementPriority::MOVEMENT_FORCED);
}
//PP
bool IccPutricideGrowingOozePuddleAction::Execute(Event event)
{
const float BASE_RADIUS = 2.0f;
const float STACK_MULTIPLIER = 0.5f;
const float MIN_DISTANCE = 0.1f; // Minimum distance to consider when bot is very close or inside puddle
// Get the nearest hostile NPCs
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
float closestDistance = FLT_MAX;
Unit* closestPuddle = nullptr;
float closestSafeDistance = BASE_RADIUS;
// Find the closest puddle and its safe distance
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == 37690) //growing ooze puddle ID
{
// Use GetExactDist instead of GetDistance2d to handle Z-axis
float currentDistance = std::max(MIN_DISTANCE, bot->GetExactDist(unit));
if (currentDistance < closestDistance)
{
closestDistance = currentDistance;
closestPuddle = unit;
// Calculate safe distance for this puddle
if (Aura* grow = unit->GetAura(70347))
{
closestSafeDistance = BASE_RADIUS + (grow->GetStackAmount() * STACK_MULTIPLIER);
}
}
}
}
// If we found a puddle that's too close, move away from it
if (closestPuddle && closestDistance < closestSafeDistance)
{
float botX = bot->GetPositionX();
float botY = bot->GetPositionY();
float botZ = bot->GetPositionZ();
// Calculate vector from puddle to bot
float dx = botX - closestPuddle->GetPositionX();
float dy = botY - closestPuddle->GetPositionY();
float dist = std::max(MIN_DISTANCE, sqrt(dx * dx + dy * dy));
// If we're too close or inside, pick a random direction to move
if (dist < MIN_DISTANCE * 2)
{
float randomAngle = float(rand()) / float(RAND_MAX) * 2 * M_PI;
dx = cos(randomAngle);
dy = sin(randomAngle);
}
else
{
dx /= dist;
dy /= dist;
}
// Try different angles to find a safe path
const int numAngles = 8;
float bestMoveX = botX;
float bestMoveY = botY;
bool foundPath = false;
float moveDistance = closestSafeDistance - closestDistance + 2.0f; // Add 2 yards buffer
for (int i = 0; i < numAngles; i++)
{
float angle = (2 * M_PI * i) / numAngles;
float rotatedDx = dx * cos(angle) - dy * sin(angle);
float rotatedDy = dx * sin(angle) + dy * cos(angle);
float testX = botX + rotatedDx * moveDistance;
float testY = botY + rotatedDy * moveDistance;
float testZ = botZ;
// Check if this move would put us too close to any other puddle
bool tooCloseToOtherPuddle = false;
for (auto& otherNpc : npcs)
{
Unit* otherUnit = botAI->GetUnit(otherNpc);
if (otherUnit && otherUnit->GetEntry() == 37690 && otherUnit != closestPuddle)
{
float otherSafeDistance = BASE_RADIUS;
if (Aura* grow = otherUnit->GetAura(70347))
{
otherSafeDistance = BASE_RADIUS + (grow->GetStackAmount() * STACK_MULTIPLIER);
}
float newDist = sqrt(pow(testX - otherUnit->GetPositionX(), 2) +
pow(testY - otherUnit->GetPositionY(), 2));
if (newDist < otherSafeDistance)
{
tooCloseToOtherPuddle = true;
break;
}
}
}
if (!tooCloseToOtherPuddle && bot->IsWithinLOS(testX, testY, testZ))
{
bestMoveX = testX;
bestMoveY = testY;
foundPath = true;
break;
}
}
if (foundPath)
{
return MoveTo(bot->GetMapId(), bestMoveX, bestMoveY, botZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}
bool IccPutricideVolatileOozeAction::Execute(Event event)
{
const float STACK_DISTANCE = 8.0f;
// Find the ooze
Unit* ooze = AI_VALUE2(Unit*, "find target", "volatile ooze");
bool botHasAura = botAI->HasAura("Volatile Ooze Adhesive", bot);
bool botHasAura2 = botAI->HasAura("Gaseous Bloat", bot);
bool botHasAura3 = botAI->HasAura("Unbound Plague", bot);
if (botHasAura2 || botHasAura3)
return false;
// Check for aura on any group member
Group* group = bot->GetGroup();
if (!group)
return false;
Unit* auraTarget = nullptr;
Unit* stackTarget = nullptr;
bool anyoneHasAura = false;
// First, try to find someone with the aura
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
if (botAI->HasAura("Volatile Ooze Adhesive", member))
{
anyoneHasAura = true;
auraTarget = member;
stackTarget = member;
break;
}
}
// If no one has aura, find a ranged player to stack with
if (!anyoneHasAura && !stackTarget)
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || member == bot ||
botAI->IsTank(member) || botAI->HasAura("Gaseous Bloat", member) ||
botAI->HasAura("Unbound Plague", member))
continue;
if (botAI->IsRanged(member))
{
stackTarget = member;
break;
}
}
}
// For melee
if (botAI->IsMelee(bot) && !botAI->IsMainTank(bot))
{
// If ooze exists and someone has aura, attack the ooze
if (ooze && anyoneHasAura)
{
bot->SetTarget(ooze->GetGUID());
return Attack(ooze);
}
// Otherwise stack with ranged
else if (stackTarget)
{
if (bot->GetDistance2d(stackTarget) > STACK_DISTANCE)
{
return MoveTo(bot->GetMapId(), stackTarget->GetPositionX(),
stackTarget->GetPositionY(), stackTarget->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
// For ranged and healers
if (botAI->IsRanged(bot) || botAI->IsHeal(bot))
{
// Always try to stack
if (stackTarget && bot->GetDistance2d(stackTarget) > STACK_DISTANCE)
{
bot->AttackStop();
return MoveTo(bot->GetMapId(), stackTarget->GetPositionX(),
stackTarget->GetPositionY(), stackTarget->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
// If stacked and ooze exists, attack it (except healers)
if (ooze && !botAI->IsHeal(bot) && stackTarget &&
bot->GetDistance2d(stackTarget) <= STACK_DISTANCE)
{
bot->SetTarget(ooze->GetGUID());
return Attack(ooze);
}
else if (botAI->IsHeal(bot))
{
return false; // Allow healer to continue with normal healing actions
}
}
return false;
}
bool IccPutricideGasCloudAction::Execute(Event event)
{
if (botAI->IsMainTank(bot))
return false;
Unit* gasCloud = AI_VALUE2(Unit*, "find target", "gas cloud");
if (!gasCloud)
return false;
bool botHasAura = botAI->HasAura("Gaseous Bloat", bot);
Unit* volatileOoze = AI_VALUE2(Unit*, "find target", "volatile ooze");
if(!botHasAura && volatileOoze)
return false;
if (botHasAura)
{
float botX = bot->GetPositionX();
float botY = bot->GetPositionY();
float botZ = bot->GetPositionZ();
float cloudX = gasCloud->GetPositionX();
float cloudY = gasCloud->GetPositionY();
float cloudDist = gasCloud->GetExactDist2d(botX, botY);
// Only move if cloud is close enough to be dangerous
if (cloudDist < 25.0f)
{
// Calculate vector from cloud to bot
float dx = botX - cloudX;
float dy = botY - cloudY;
float dist = sqrt(dx * dx + dy * dy);
if (dist > 0)
{
dx /= dist;
dy /= dist;
// Try different angles to find a safe path
const int numAngles = 16; // Increased for more precise movement
float bestMoveX = botX;
float bestMoveY = botY;
float bestDist = cloudDist;
bool foundPath = false;
for (int i = 0; i < numAngles; i++)
{
float angle = (2 * M_PI * i) / numAngles;
float rotatedDx = dx * cos(angle) - dy * sin(angle);
float rotatedDy = dx * sin(angle) + dy * cos(angle);
// Try different distances
for (float testDist = 5.0f; testDist <= 15.0f; testDist += 5.0f)
{
float testX = botX + rotatedDx * testDist;
float testY = botY + rotatedDy * testDist;
float testZ = botZ;
float newCloudDist = gasCloud->GetExactDist2d(testX, testY);
// Check if this position is better
if (newCloudDist > bestDist && bot->IsWithinLOS(testX, testY, testZ))
{
bestMoveX = testX;
bestMoveY = testY;
bestDist = newCloudDist;
foundPath = true;
}
}
}
if (foundPath)
{
return MoveTo(bot->GetMapId(), bestMoveX, bestMoveY, botZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
else if (cloudDist < 8.0f) // Emergency move if very close and no good path found
{
// Try to move directly away
float emergencyX = botX + dx * 10.0f;
float emergencyY = botY + dy * 10.0f;
if (bot->IsWithinLOS(emergencyX, emergencyY, botZ))
{
return MoveTo(bot->GetMapId(), emergencyX, emergencyY, botZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
}
}
else
{
Group* group = bot->GetGroup();
if (!group)
return false;
bool someoneHasAura = false;
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member)
continue;
if (botAI->HasAura("Gaseous Bloat", member))
{
someoneHasAura = true;
break;
}
}
if (someoneHasAura && !botAI->IsHeal(bot))
{
return Attack(gasCloud);
}
}
return false;
}
bool AvoidMalleableGooAction::Execute(Event event)
{
bool hasUnboundPlague = botAI->HasAura("Unbound Plague", bot);
const float UNBOUND_PLAGUE_DISTANCE = 15.0f;
// If bot has unbound plague, keep away from all other players
if (hasUnboundPlague)
{
Group* group = bot->GetGroup();
if (!group)
return false;
float closestDistance = UNBOUND_PLAGUE_DISTANCE;
Unit* closestPlayer = nullptr;
// Find closest player
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float dist = bot->GetDistance2d(member);
if (dist < closestDistance)
{
closestDistance = dist;
closestPlayer = member;
}
}
// Move away from closest player if too close
if (closestPlayer)
{
float dx = bot->GetPositionX() - closestPlayer->GetPositionX();
float dy = bot->GetPositionY() - closestPlayer->GetPositionY();
float dist = sqrt(dx * dx + dy * dy);
if (dist > 0)
{
dx /= dist;
dy /= dist;
float moveDistance = UNBOUND_PLAGUE_DISTANCE - closestDistance + 2.0f;
float moveX = bot->GetPositionX() + dx * moveDistance;
float moveY = bot->GetPositionY() + dy * moveDistance;
if (bot->IsWithinLOS(moveX, moveY, bot->GetPositionZ()))
{
return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
}
if (botAI->IsRanged(bot) || botAI->IsHeal(bot))
{
float radius = 7.0f;
bool isRanged = botAI->IsRanged(bot);
GuidVector members = AI_VALUE(GuidVector, "group members");
if (isRanged)
{
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || !unit->IsAlive() || unit == bot || botAI->IsTank(bot) || botAI->IsMelee(bot))
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
{
float moveDistance = radius - dist + 1.0f;
// Calculate potential new position
float angle = bot->GetAngle(unit);
float newX = bot->GetPositionX() + cos(angle + M_PI) * moveDistance;
float newY = bot->GetPositionY() + sin(angle + M_PI) * moveDistance;
// Only move if we have line of sight
if (bot->IsWithinLOS(newX, newY, bot->GetPositionZ()))
{
return FleePosition(unit->GetPosition(), moveDistance);
// return MoveAway(unit, moveDistance);
}
}
}
}
return false;
}
return false;
}
//BPC
bool IccBpcKelesethTankAction::Execute(Event event)
{
if (!botAI->IsAssistTank(bot))
return false;
Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth");
if (!boss)
return false;
// Check if we're not the victim of Keleseth's attack
if (!(boss->GetVictim() == bot))
return Attack(boss);
// First check for any nucleus that needs to be picked up
bool isCollectingNuclei = false;
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->IsAlive() && unit->GetEntry() == 38369) // Dark Nucleus entry
{
if (!unit->GetVictim() || unit->GetVictim() != bot)
{
isCollectingNuclei = true;
return Attack(unit); // Pick up any nucleus that isn't targeting us
}
}
}
// If not collecting nuclei, move to OT position
if (!isCollectingNuclei && bot->GetExactDist2d(ICC_BPC_OT_POSITION) > 20.0f)
return MoveTo(bot->GetMapId(), ICC_BPC_OT_POSITION.GetPositionX(),
ICC_BPC_OT_POSITION.GetPositionY(), ICC_BPC_OT_POSITION.GetPositionZ(),
false, true, false, true, MovementPriority::MOVEMENT_COMBAT);
return Attack(boss);
}
bool IccBpcNucleusAction::Execute(Event event)
{
if (!botAI->IsAssistTank(bot))
return false;
// Actively look for any nucleus that isn't targeting us
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->IsAlive() && unit->GetEntry() == 38369) // Dark Nucleus entry
{
if (!unit->GetVictim() || unit->GetVictim() != bot)
return Attack(unit); // Pick up any nucleus that isn't targeting us
}
}
return false;
}
bool IccBpcMainTankAction::Execute(Event event)
{
if (botAI->IsMainTank(bot))
{
// Move to MT position if we're not there
if (bot->GetExactDist2d(ICC_BPC_MT_POSITION) > 20.0f)
return MoveTo(bot->GetMapId(), ICC_BPC_MT_POSITION.GetPositionX(),
ICC_BPC_MT_POSITION.GetPositionY(), ICC_BPC_MT_POSITION.GetPositionZ(),
false, true, false, true, MovementPriority::MOVEMENT_COMBAT);
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram");
Unit* currentTarget = AI_VALUE(Unit*, "current target");
// Keep current prince if we have one
if (currentTarget && (currentTarget == valanar || currentTarget == taldaram))
return Attack(currentTarget);
// Pick a new prince that isn't targeting us
if (valanar && (!valanar->GetVictim() || valanar->GetVictim() != bot))
return Attack(valanar);
if (taldaram && (!taldaram->GetVictim() || taldaram->GetVictim() != bot))
return Attack(taldaram);
return false;
}
if (!botAI->IsTank(bot))
{
Unit* currentTarget = AI_VALUE(Unit*, "current target");
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
// First check if skull-marked target is a valid empowered prince
Unit* skullTarget = nullptr;
if (Group* group = bot->GetGroup())
{
if (ObjectGuid skullGuid = group->GetTargetIcon(7)) // 7 = skull
{
skullTarget = botAI->GetUnit(skullGuid);
if (skullTarget && skullTarget->IsAlive() && skullTarget->HasAura(71596) &&
(skullTarget->GetEntry() == 37972 || // Keleseth
skullTarget->GetEntry() == 37973 || // Taldaram
skullTarget->GetEntry() == 37970)) // Valanar
{
return Attack(skullTarget);
}
}
}
// If no valid skull target, search for empowered prince
Unit* empoweredPrince = nullptr;
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit || !unit->IsAlive())
continue;
if (unit->HasAura(71596))
{
if (unit->GetEntry() == 37972 || // Keleseth
unit->GetEntry() == 37973 || // Taldaram
unit->GetEntry() == 37970) // Valanar
{
empoweredPrince = unit;
// Mark empowered prince with skull if in group
if (Group* group = bot->GetGroup())
{
group->SetTargetIcon(7, bot->GetGUID(), unit->GetGUID()); // 7 = skull
}
break;
}
}
}
// Attack empowered prince if found and current target doesn't have aura
if (empoweredPrince)
{
// Only switch if current target doesn't have the aura
if (!currentTarget || !currentTarget->HasAura(71596))
{
return Attack(empoweredPrince);
}
else
{
return Attack(currentTarget);
}
}
// Keep current prince target if no empowered prince found
if (currentTarget && (currentTarget->GetEntry() == 37972 || // Keleseth
currentTarget->GetEntry() == 37973 || // Taldaram
currentTarget->GetEntry() == 37970)) // Valanar
{
return Attack(currentTarget);
}
}
return false;
}
bool IccBpcEmpoweredVortexAction::Execute(Event event)
{
// Double check that we're not a tank
if (botAI->IsMainTank(bot) || botAI->IsAssistTank(bot) || botAI->IsTank(bot))
return false;
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
if (!valanar)
return false;
float radius = 12.0f;
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || !unit->IsAlive() || unit == bot)
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
{
float moveDistance = radius - dist + 1.0f;
// Calculate potential new position
float angle = bot->GetAngle(unit);
float newX = bot->GetPositionX() + cos(angle + M_PI) * moveDistance;
float newY = bot->GetPositionY() + sin(angle + M_PI) * moveDistance;
// Only move if we have line of sight
if (bot->IsWithinLOS(newX, newY, bot->GetPositionZ()))
{
return FleePosition(unit->GetPosition(), moveDistance);
// return MoveAway(unit, moveDistance);
}
}
}
return false;
}
//BQL
bool IccBqlTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel");
// If tank is not at position, move there
if (botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot))
{
if (bot->GetExactDist2d(ICC_BQL_TANK_POSITION) > 20.0f)
return MoveTo(bot->GetMapId(), ICC_BQL_TANK_POSITION.GetPositionX(),
ICC_BQL_TANK_POSITION.GetPositionY(), ICC_BQL_TANK_POSITION.GetPositionZ(),
false, true, false, true, MovementPriority::MOVEMENT_COMBAT);
}
float radius = 8.0f;
float moveIncrement = 3.0f;
bool isRanged = botAI->IsRanged(bot);
bool isMelee = botAI->IsMelee(bot);
GuidVector members = AI_VALUE(GuidVector, "group members");
if (isRanged && !(bot->HasAura(70877) || bot->HasAura(71474) && boss->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))) //frenzied bloodthrist
{
// Ranged: spread from other ranged
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || !unit->IsAlive() || unit == bot || unit->HasAura(70877) || unit->HasAura(71474))
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
{
float moveDistance = std::min(moveIncrement, radius - dist + 1.0f);
return FleePosition(unit->GetPosition(), moveDistance);
// return MoveAway(unit, moveDistance);
}
}
}
if (isMelee && boss->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY)) // melee also spread
{
// Melee: spread from other melee
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || !unit->IsAlive() || unit == bot || unit->HasAura(70877) || unit->HasAura(71474))
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
{
float moveDistance = std::min(moveIncrement, radius - dist + 1.0f);
return FleePosition(unit->GetPosition(), moveDistance);
// return MoveAway(unit, moveDistance);
}
}
}
return false; // Everyone is in position
}
bool IccBqlPactOfDarkfallenAction::Execute(Event event)
{
// Check if bot has Pact of the Darkfallen
if (!bot->HasAura(71340))
return false;
const float POSITION_TOLERANCE = 1.0f; // Within 1 yards to break the link
// Find other players with Pact of the Darkfallen
std::vector<Player*> playersWithAura;
Player* tankWithAura = nullptr;
Group* group = bot->GetGroup();
if (!group)
return false;
// Gather all players with the aura
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || member->GetGUID() == bot->GetGUID())
continue;
if (member->HasAura(71340)) //pact of darkfallen
{
playersWithAura.push_back(member);
// If this player is a tank, store them
if (botAI->IsTank(member))
tankWithAura = member;
}
}
// If we found other players with the aura
if (!playersWithAura.empty())
{
Position targetPos;
if (playersWithAura.size() >= 2) // 3 or more total (including this bot)
{
if (tankWithAura)
{
// Move to tank's position if we're not a tank
if (!botAI->IsTank(bot))
{
targetPos.Relocate(tankWithAura);
}
else
{
// If we are the tank, stay put
return true;
}
}
else
{
// Calculate center position of all affected players
float sumX = bot->GetPositionX();
float sumY = bot->GetPositionY();
float sumZ = bot->GetPositionZ();
int count = 1; // Start with 1 for this bot
for (Player* player : playersWithAura)
{
sumX += player->GetPositionX();
sumY += player->GetPositionY();
sumZ += player->GetPositionZ();
count++;
}
targetPos.Relocate(sumX / count, sumY / count, sumZ / count);
}
}
else // Only one other player has aura
{
targetPos.Relocate(playersWithAura[0]);
}
// Move to target position if we're not already there
if (bot->GetDistance(targetPos) > POSITION_TOLERANCE)
{
return MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(),
false, false, false, true, MovementPriority::MOVEMENT_FORCED);
}
return true;
}
// If no other players found with aura, move to center
if (bot->GetDistance(ICC_BQL_CENTER_POSITION) > POSITION_TOLERANCE)
{
botAI->SetNextCheckDelay(500);
return MoveTo(bot->GetMapId(), ICC_BQL_CENTER_POSITION.GetPositionX(), ICC_BQL_CENTER_POSITION.GetPositionY(), ICC_BQL_CENTER_POSITION.GetPositionZ(),
false, false, false, true, MovementPriority::MOVEMENT_FORCED);
}
return false;
}
bool IccBqlVampiricBiteAction::Execute(Event event)
{
// Only act when bot has Frenzied Bloodthirst
if (!(bot->HasAura(70877) || bot->HasAura(71474)))
return false;
const float BITE_RANGE = 2.0f;
Player* target = nullptr;
Group* group = bot->GetGroup();
if (!group)
return false;
// Create lists for potential targets
std::vector<Player*> dpsTargets;
std::vector<Player*> healTargets;
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || member == bot || !member->IsAlive())
continue;
// Skip if already has essence, frenzy, or is a tank, or uncontrollable frenzy
if (member->HasAura(70867) || member->HasAura(70877) || member->HasAura(70879) || member->HasAura(71473)
|| member->HasAura(71474) || member->HasAura(71525) || member->HasAura(71530) || member->HasAura(71531)
|| member->HasAura(71532) || member->HasAura(71533) || member->HasAura(70923) || botAI->IsTank(member))
{
continue;
}
if (botAI->IsDps(member))
dpsTargets.push_back(member);
else if (botAI->IsHeal(member))
healTargets.push_back(member);
}
// First try DPS targets
if (!dpsTargets.empty())
{
target = dpsTargets[0]; // Take first available DPS
}
// If no DPS available, try healers
else if (!healTargets.empty())
{
target = healTargets[0]; // Take first available healer
}
if (!target)
{
return false;
}
// Double check target is still alive
if (!target->IsAlive())
{
return false;
}
// Check if we can reach the target
float x = target->GetPositionX();
float y = target->GetPositionY();
float z = target->GetPositionZ();
if (bot->IsWithinLOS(x, y, z) && bot->GetExactDist2d(target) > BITE_RANGE)
{
return MoveTo(target->GetMapId(), x, y, z, false, false, false, true, MovementPriority::MOVEMENT_FORCED);
}
// If in range and can see target, cast the bite
if (bot->IsWithinLOS(x, y, z) && bot->GetExactDist2d(target) <= BITE_RANGE)
{
// Final alive check before casting
if (!target->IsAlive())
{
return false;
}
if (botAI->CanCastSpell(70946, target))
{
return botAI->CastSpell(70946, target);
}
}
return false;
}
//VDW
//Valkyre 38248 spear, 50307 spear in inv, Sister Svalna 37126, aether shield 71463
bool IccValkyreSpearAction::Execute(Event event)
{
// Find the nearest spear
Creature* spear = bot->FindNearestCreature(38248, 100.0f);
if (!spear)
return false;
// Move to the spear if not in range
if (!spear->IsWithinDistInMap(bot, INTERACTION_DISTANCE))
{
return MoveTo(spear, INTERACTION_DISTANCE);
}
// Remove shapeshift forms
botAI->RemoveShapeshift();
// Stop movement and click the spear
bot->GetMotionMaster()->Clear();
bot->StopMoving();
spear->HandleSpellClick(bot);
// Dismount if mounted
WorldPacket emptyPacket;
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
return false;
}
bool IccSisterSvalnaAction::Execute(Event event)
{
Unit* svalna = AI_VALUE2(Unit*, "find target", "sister svalna");
if (!svalna || !svalna->HasAura(71463)) // Check for Aether Shield aura
return false;
// Check if bot has the spear item
if (!botAI->HasItemInInventory(50307))
return false;
// Get all items from inventory
std::vector<Item*> items = botAI->GetInventoryItems();
for (Item* item : items)
{
if (item->GetEntry() == 50307) // Spear ID
{
botAI->ImbueItem(item, svalna); // Use spear on Svalna
return false;
}
}
return false;
}
bool IccValithriaPortalAction::Execute(Event event)
{
if (!botAI->IsHeal(bot) || bot->getClass() == CLASS_DRUID || bot->HasAura(70766))
return false;
// Find the nearest portal
Creature* portal = bot->FindNearestCreature(37945, 100.0f); // Dream Portal
if (!portal)
portal = bot->FindNearestCreature(38430, 100.0f); // Nightmare Portal
if (!portal)
return false;
// Move exactly to portal position
float portalX = portal->GetPositionX();
float portalY = portal->GetPositionY();
float portalZ = portal->GetPositionZ();
// If not at exact portal position, move there
if (bot->GetDistance2d(portalX, portalY) > 0.1f)
{
return MoveTo(portal->GetMapId(), portalX, portalY, portalZ, false, false, false, true, MovementPriority::MOVEMENT_NORMAL);
}
// Remove shapeshift forms
botAI->RemoveShapeshift();
// Stop movement and click the portal
bot->GetMotionMaster()->Clear();
bot->StopMoving();
portal->HandleSpellClick(bot);
// Dismount if mounted
WorldPacket emptyPacket;
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
return false;
}
bool IccValithriaHealAction::Execute(Event event)
{
if (!botAI->IsHeal(bot))
return false;
if (!bot->HasAura(70766)) //dream state
{
bot->SetSpeed(MOVE_RUN, 1.0f, true);
bot->SetSpeed(MOVE_WALK, 1.0f, true);
bot->SetSpeed(MOVE_FLIGHT, 1.0f, true);
}
// Find Valithria
if (Creature* valithria = bot->FindNearestCreature(36789, 100.0f))
{
switch (bot->getClass())
{
case CLASS_SHAMAN:
return valithria->HasAura(61301) ? botAI->CastSpell(49273, valithria) : botAI->CastSpell(61301, valithria); // Cast Healing Wave if Riptide is up, otherwise cast Riptide
case CLASS_PRIEST:
return valithria->HasAura(48068) ? botAI->CastSpell(48063, valithria) : botAI->CastSpell(48068, valithria); // Cast Greater Heal if Renew is up, otherwise cast Renew
case CLASS_PALADIN:
return valithria->HasAura(53563) ? botAI->CastSpell(48782, valithria) : botAI->CastSpell(53563, valithria); // Cast Holy Light if Beacon is up, otherwise cast Beacon of Light
default:
return false;
}
}
return false;
}
bool IccValithriaDreamCloudAction::Execute(Event event)
{
// Only execute if we're in dream state
if (!bot->HasAura(70766))
return false;
// Set speed to match players in dream state
if (bot->HasAura(70766))
{
bot->SetSpeed(MOVE_RUN, 2.0f, true);
bot->SetSpeed(MOVE_WALK, 2.0f, true);
bot->SetSpeed(MOVE_FLIGHT, 2.0f, true);
}
// Find nearest cloud of either type that we haven't collected
Creature* dreamCloud = bot->FindNearestCreature(37985, 100.0f);
Creature* nightmareCloud = bot->FindNearestCreature(38421, 100.0f);
// If we have emerald vigor, prioritize dream clouds
if (bot->HasAura(70873))
{
if (dreamCloud)
return MoveTo(dreamCloud->GetMapId(), dreamCloud->GetPositionX(), dreamCloud->GetPositionY(), dreamCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL);
if (nightmareCloud)
return MoveTo(nightmareCloud->GetMapId(), nightmareCloud->GetPositionX(), nightmareCloud->GetPositionY(), nightmareCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL);
}
// Otherwise prioritize nightmare clouds
else
{
if (nightmareCloud)
return MoveTo(nightmareCloud->GetMapId(), nightmareCloud->GetPositionX(), nightmareCloud->GetPositionY(), nightmareCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL);
if (dreamCloud)
return MoveTo(dreamCloud->GetMapId(), dreamCloud->GetPositionX(), dreamCloud->GetPositionY(), dreamCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL);
}
return false;
}
//Sindragosa
bool IccSindragosaTankPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
if (!boss || boss->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
return false;
if ((botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot)) && (boss->GetVictim() == bot))
{
float distBossToCenter = boss->GetExactDist2d(ICC_SINDRAGOSA_CENTER_POSITION);
float distToTankPos = bot->GetExactDist2d(ICC_SINDRAGOSA_TANK_POSITION);
float targetOrientation = M_PI / 2; // We want boss to face east
float currentOrientation = boss->GetOrientation();
// Normalize both orientations to 0-2π range
currentOrientation = fmod(currentOrientation + 2 * M_PI, 2 * M_PI);
targetOrientation = fmod(targetOrientation + 2 * M_PI, 2 * M_PI);
float orientationDiff = currentOrientation - targetOrientation;
// Normalize the difference to be between -PI and PI
while (orientationDiff > M_PI) orientationDiff -= 2 * M_PI;
while (orientationDiff < -M_PI) orientationDiff += 2 * M_PI;
// Stage 1: Move boss to center if too far
if (distBossToCenter > 20.0f)
{
// Calculate direction vector from boss to center
float dirX = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionX() - boss->GetPositionX();
float dirY = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionY() - boss->GetPositionY();
// Move 10 yards beyond center in the same direction
float moveX = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionX() + (dirX / distBossToCenter) * 10.0f;
float moveY = ICC_SINDRAGOSA_CENTER_POSITION.GetPositionY() + (dirY / distBossToCenter) * 10.0f;
return MoveTo(bot->GetMapId(), moveX, moveY, boss->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
// Stage 2: Get to tank position when boss is centered
if (distToTankPos > 5.0f)
{
return MoveTo(bot->GetMapId(), ICC_SINDRAGOSA_TANK_POSITION.GetPositionX(),
ICC_SINDRAGOSA_TANK_POSITION.GetPositionY(),
ICC_SINDRAGOSA_TANK_POSITION.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
// Stage 3: Adjust orientation when in position
bool needsOrientationAdjust = std::abs(orientationDiff) > 0.15f;
if (needsOrientationAdjust)
{
// When we have negative difference (currentOrientation < targetOrientation)
// We need to move south to make the orientation more positive
float currentX = bot->GetPositionX();
float currentY = bot->GetPositionY();
float moveX, moveY;
// For negative difference (need to increase orientation) -> move south
// For positive difference (need to decrease orientation) -> move north
if (orientationDiff < 0)
{
moveX = currentX - 2.0f; // Move south to increase orientation
moveY = currentY;
}
else
{
moveX = currentX + 2.0f; // Move north to decrease orientation
moveY = currentY;
}
return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
// Non-tanks should stay on the left flank to avoid both cleave and tail smash
if (boss && !(boss->GetVictim() == bot) /*&& !bot->HasAura(69762)*/)
{
if (botAI->IsRanged(bot))
{
float const TOLERANCE = 15.0f; // 15 yard tolerance
float const MAX_STEP = 5.0f; // Maximum distance to move in one step
float distToTarget = bot->GetExactDist2d(ICC_SINDRAGOSA_RANGED_POSITION);
// Only move if outside tolerance
if (distToTarget > TOLERANCE)
{
// Calculate direction vector to target
float dirX = ICC_SINDRAGOSA_RANGED_POSITION.GetPositionX() - bot->GetPositionX();
float dirY = ICC_SINDRAGOSA_RANGED_POSITION.GetPositionY() - bot->GetPositionY();
// Normalize direction vector
float length = sqrt(dirX * dirX + dirY * dirY);
dirX /= length;
dirY /= length;
// Calculate intermediate point
float stepSize = std::min(MAX_STEP, distToTarget);
float moveX = bot->GetPositionX() + dirX * stepSize;
float moveY = bot->GetPositionY() + dirY * stepSize;
return MoveTo(bot->GetMapId(), moveX, moveY, ICC_SINDRAGOSA_RANGED_POSITION.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
else
{
float const TOLERANCE = 10.0f; // 10 yard tolerance for melee
float const MAX_STEP = 5.0f; // Maximum distance to move in one step
float distToTarget = bot->GetExactDist2d(ICC_SINDRAGOSA_MELEE_POSITION);
// Only move if outside tolerance
if (distToTarget > TOLERANCE)
{
// Calculate direction vector to target
float dirX = ICC_SINDRAGOSA_MELEE_POSITION.GetPositionX() - bot->GetPositionX();
float dirY = ICC_SINDRAGOSA_MELEE_POSITION.GetPositionY() - bot->GetPositionY();
// Normalize direction vector
float length = sqrt(dirX * dirX + dirY * dirY);
dirX /= length;
dirY /= length;
// Calculate intermediate point
float stepSize = std::min(MAX_STEP, distToTarget);
float moveX = bot->GetPositionX() + dirX * stepSize;
float moveY = bot->GetPositionY() + dirY * stepSize;
return MoveTo(bot->GetMapId(), moveX, moveY, ICC_SINDRAGOSA_MELEE_POSITION.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
}
return false;
}
bool IccSindragosaTankSwapPositionAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
if (!boss)
return false;
// Only for assist tank
if (!botAI->IsAssistTank(bot))
return false;
float distToTankPos = bot->GetExactDist2d(ICC_SINDRAGOSA_TANK_POSITION);
// Move to tank position
if (distToTankPos > 3.0f) // Tighter tolerance for tank swap
{
return MoveTo(bot->GetMapId(), ICC_SINDRAGOSA_TANK_POSITION.GetPositionX(),
ICC_SINDRAGOSA_TANK_POSITION.GetPositionY(),
ICC_SINDRAGOSA_TANK_POSITION.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool IccSindragosaFrostBeaconAction::Execute(Event event)
{
float const POSITION_TOLERANCE = 3.0f; // Increased tolerance to reduce jitter
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
if (!boss)
return false;
// Handle beaconed players
if (bot->HasAura(70126))
{
if (boss && boss->HealthBelowPct(35) && !boss->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
{
// Only move if not already at position (with tolerance)
if (bot->GetExactDist2d(ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionX(), ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionY()) > POSITION_TOLERANCE)
{
return MoveTo(bot->GetMapId(),
ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionX(),
ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionY(),
ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_FORCED);
}
return false;
}
else
{
Position const* tombPosition;
uint8 beaconIndex = 0;
bool foundSelf = false;
Group* group = bot->GetGroup();
if (!group)
return false;
// Find this bot's index among players with Frost Beacon
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || !member->HasAura(70126)) // Only count alive players with Frost Beacon
continue;
if (member == bot)
{
foundSelf = true;
break;
}
beaconIndex++;
}
if (!foundSelf)
return false;
switch (beaconIndex) {
case 0:
tombPosition = &ICC_SINDRAGOSA_THOMB1_POSITION;
break;
case 1:
tombPosition = &ICC_SINDRAGOSA_THOMB2_POSITION;
break;
case 2:
tombPosition = &ICC_SINDRAGOSA_THOMB3_POSITION;
break;
case 3:
tombPosition = &ICC_SINDRAGOSA_THOMB4_POSITION;
break;
default:
tombPosition = &ICC_SINDRAGOSA_THOMB5_POSITION;
break;
}
// Only move if not already at position (with tolerance)
float dist = bot->GetExactDist2d(tombPosition->GetPositionX(), tombPosition->GetPositionY());
if (dist > POSITION_TOLERANCE)
{
return MoveTo(bot->GetMapId(), tombPosition->GetPositionX(),
tombPosition->GetPositionY(),
tombPosition->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_FORCED);
}
return false;
}
}
// Handle non-beaconed players
else
{
float const MIN_SAFE_DISTANCE = 13.0f;
float const MAX_SAFE_DISTANCE = 30.0f;
float const MOVE_TOLERANCE = 2.0f; // Tolerance for movement to reduce jitter
GuidVector members = AI_VALUE(GuidVector, "group members");
std::vector<Unit*> beaconedPlayers;
for (auto& member : members)
{
Unit* player = botAI->GetUnit(member);
if (!player || player->GetGUID() == bot->GetGUID())
continue;
if (player->HasAura(70126)) // Frost Beacon
beaconedPlayers.push_back(player);
}
if (beaconedPlayers.empty())
return false;
// Different behavior for air phase
if (boss->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
{
if (!bot->HasAura(70126)) // If not beaconed, move to safe position
{
float dist = bot->GetExactDist2d(ICC_SINDRAGOSA_FBOMB_POSITION.GetPositionX(),
ICC_SINDRAGOSA_FBOMB_POSITION.GetPositionY());
if (dist > POSITION_TOLERANCE)
{
return MoveTo(bot->GetMapId(), ICC_SINDRAGOSA_FBOMB_POSITION.GetPositionX(),
ICC_SINDRAGOSA_FBOMB_POSITION.GetPositionY(),
ICC_SINDRAGOSA_FBOMB_POSITION.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}
else
{
// Ground phase - use existing vector-based movement
bool needToMove = false;
float moveX = 0, moveY = 0;
for (Unit* beaconedPlayer : beaconedPlayers)
{
float dist = bot->GetExactDist2d(beaconedPlayer);
if (dist < MIN_SAFE_DISTANCE + MOVE_TOLERANCE)
{
needToMove = true;
float angle = bot->GetAngle(beaconedPlayer);
float moveDistance = std::min(5.0f, MIN_SAFE_DISTANCE - dist + MOVE_TOLERANCE);
moveX += cos(angle + M_PI) * moveDistance;
moveY += sin(angle + M_PI) * moveDistance;
}
}
if (needToMove && !bot->HasAura(70126))
{
float posX = bot->GetPositionX() + moveX;
float posY = bot->GetPositionY() + moveY;
float posZ = bot->GetPositionZ();
bot->UpdateAllowedPositionZ(posX, posY, posZ);
// Only move if the change in position is significant
if (std::abs(moveX) > MOVE_TOLERANCE || std::abs(moveY) > MOVE_TOLERANCE)
{
return MoveTo(bot->GetMapId(), posX, posY, posZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
}
return false;
}
bool IccSindragosaBlisteringColdAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
if (!boss)
return false;
// Only non-tanks should move out
if (botAI->IsMainTank(bot))
return false;
float dist = bot->GetDistance(boss);
// If we're already at safe distance, no need to move
if (dist >= 30.0f)
return false;
// Check boss health to determine movement target
bool isPhaseThree = boss->GetHealthPct() <= 35;
Position const& targetPos = isPhaseThree ? ICC_SINDRAGOSA_LOS2_POSITION : ICC_SINDRAGOSA_BLISTERING_COLD_POSITION;
// Only move if we're too close to the boss (< 30 yards)
if (dist < 30.0f)
{
// If in phase 3, check if already at LOS2 position
if (isPhaseThree && bot->IsWithinDist2d(targetPos.GetPositionX(), targetPos.GetPositionY(), 1.0f))
return false;
float const STEP_SIZE = 10.0f;
float distToTarget = bot->GetDistance2d(targetPos.GetPositionX(), targetPos.GetPositionY());
if (distToTarget > 0.1f) // Avoid division by zero
{
// Calculate direction vector
float dirX = targetPos.GetPositionX() - bot->GetPositionX();
float dirY = targetPos.GetPositionY() - bot->GetPositionY();
// Normalize direction vector
float length = sqrt(dirX * dirX + dirY * dirY);
dirX /= length;
dirY /= length;
// Move STEP_SIZE yards in that direction
float moveX = bot->GetPositionX() + dirX * STEP_SIZE;
float moveY = bot->GetPositionY() + dirY * STEP_SIZE;
return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}
bool IccSindragosaUnchainedMagicAction::Execute(Event event)
{
if (bot->HasAura(69762)) // unchained magic
if (Aura* aura = bot->GetAura(69766))
{
if (aura->GetStackAmount() >= 3)
return true; // Stop casting spells
}
return false;
}
bool IccSindragosaChilledToTheBoneAction::Execute(Event event)
{
if (bot->HasAura(70106)) // Chilled to the Bone
{
if (Aura* aura = bot->GetAura(70106))
{
if (aura->GetStackAmount() >= 8)
{
bot->AttackStop();
return true; // Stop casting spells
}
}
}
return false;
}
bool IccSindragosaMysticBuffetAction::Execute(Event event)
{
// Get boss to check if it exists
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
if (!boss || !bot || !bot->IsAlive())
return false;
// Check if we have Mystic Buffet
Aura* aura = botAI->GetAura("mystic buffet", bot, false, true);
if (!aura)
return false;
// Skip if we have Frost Beacon
if (bot->HasAura(70126))
return false;
uint8 stacks = aura->GetStackAmount();
// Check if already at LOS2 position
if (bot->IsWithinDist2d(ICC_SINDRAGOSA_LOS2_POSITION.GetPositionX(),
ICC_SINDRAGOSA_LOS2_POSITION.GetPositionY(), 1.0f))
{
return false;
}
// Find nearest ice tomb
Group* group = bot->GetGroup();
if (!group)
return false;
Unit* nearestTomb = nullptr;
float minDist = 150.0f;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
if (member->HasAura(70157)) // Ice Tomb
{
float dist = bot->GetDistance(member);
if (dist < minDist)
{
minDist = dist;
nearestTomb = member;
}
}
}
// If no tombs found or tomb not at MB2 position, don't move
if (!nearestTomb || !nearestTomb->IsWithinDist2d(ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionX(),
ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionY(), 1.0f))
return false;
// Move to LOS2 position
return MoveTo(bot->GetMapId(),
ICC_SINDRAGOSA_LOS2_POSITION.GetPositionX(),
ICC_SINDRAGOSA_LOS2_POSITION.GetPositionY(),
ICC_SINDRAGOSA_LOS2_POSITION.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_FORCED);
}
bool IccSindragosaFrostBombAction::Execute(Event event)
{
if (!bot || !bot->IsAlive() || bot->HasAura(70157)) // Skip if dead or in Ice Tomb
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
// Find frost bomb marker and tombs in one pass
Unit* marker = nullptr;
Unit* primaryTomb = nullptr;
float highestHealth = 0.0f;
int activeTombCount = 0;
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
if (npcs.empty())
return false;
// Single pass to find marker and tombs
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (!unit || !unit->IsAlive())
continue;
if (unit->HasAura(70022)) // Frost bomb visual
marker = unit;
// Check for any ice tomb variant
if (unit->GetEntry() == 36980 || unit->GetEntry() == 38320 ||
unit->GetEntry() == 38321 || unit->GetEntry() == 38322)
{
activeTombCount++;
if (unit->GetHealthPct() > highestHealth)
{
highestHealth = unit->GetHealthPct();
primaryTomb = unit;
}
}
}
if (!marker || !primaryTomb)
return false;
// Position handling
float angle = marker->GetAngle(primaryTomb);
float posX = primaryTomb->GetPositionX() + cos(angle) * 1.0f;
float posY = primaryTomb->GetPositionY() + sin(angle) * 1.0f;
float posZ = primaryTomb->GetPositionZ();
// Check if we need to move
if (bot->GetDistance2d(posX, posY) > 2.0f)
{
return MoveTo(bot->GetMapId(), posX, posY, posZ,
false, false, false, false, MovementPriority::MOVEMENT_FORCED);
}
// Check if we have LOS to marker from our position
if (!marker->IsWithinLOS(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()))
return true; // Stay in position using tomb for LOS
return true;
}
bool IccLichKingShadowTrapAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
if (!boss)
return false;
//search for all nearby traps
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
std::vector<Unit*> nearbyTraps;
bool needToMove = false;
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (!unit || !unit->IsAlive())
continue;
if (unit->GetEntry() == 39137) //shadow trap
{
float distance = bot->GetDistance(unit);
if (distance < 7.0f)
{
needToMove = true;
nearbyTraps.push_back(unit);
}
}
}
if (!needToMove || nearbyTraps.empty())
return false;
// Try different angles to find a safe spot
float const MOVE_DISTANCE = 4.0f;
float const ANGLE_INCREMENT = M_PI / 8; // 22.5 degrees
float bestAngle = 0;
float maxMinDistance = 0;
// Try 16 different directions
for (int i = 0; i < 16; i++)
{
float tryAngle = i * ANGLE_INCREMENT;
float testX = bot->GetPositionX() + cos(tryAngle) * MOVE_DISTANCE;
float testY = bot->GetPositionY() + sin(tryAngle) * MOVE_DISTANCE;
float testZ = 840.857f;
bot->UpdateAllowedPositionZ(testX, testY, testZ);
// Skip invalid positions
if (!bot->IsWithinLOS(testX, testY, testZ))
continue;
// Find minimum distance to any trap from this position
float minTrapDistance = 100.0f;
for (Unit* trap : nearbyTraps)
{
float dx = testX - trap->GetPositionX();
float dy = testY - trap->GetPositionY();
float distToTrap = sqrt(dx * dx + dy * dy);
minTrapDistance = std::min(minTrapDistance, distToTrap);
}
// If this position keeps us further from all traps than previous positions
if (minTrapDistance > maxMinDistance)
{
maxMinDistance = minTrapDistance;
bestAngle = tryAngle;
}
}
// If we found a safe direction, move there
if (maxMinDistance >= 7.0f)
{
float x = bot->GetPositionX() + cos(bestAngle) * MOVE_DISTANCE;
float y = bot->GetPositionY() + sin(bestAngle) * MOVE_DISTANCE;
float z = 840.857f;
bot->UpdateAllowedPositionZ(x, y, z);
return MoveTo(bot->GetMapId(), x, y, z,
false, false, false, false, MovementPriority::MOVEMENT_FORCED);
}
return false;
}
bool IccLichKingNecroticPlagueAction::Execute(Event event)
{
bool hasPlague = botAI->HasAura("Necrotic Plague", bot);
// Only execute if we have the plague
if (!hasPlague)
return false;
// Find closest shambling and nearest shadow trap
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
Unit* closestHorror = nullptr;
Unit* nearestTrap = nullptr;
float minHorrorDist = 100.0f;
float minTrapDist = 100.0f;
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (!unit || !unit->IsAlive())
continue;
uint32 entry = unit->GetEntry();
if (entry == 37698 || entry == 39299 || entry == 39300 || entry == 39301) //shambling horror
{
float distance = bot->GetDistance(unit);
if (distance < minHorrorDist)
{
minHorrorDist = distance;
closestHorror = unit;
}
}
else if (entry == 39137) //shadow trap
{
float distance = bot->GetDistance(unit);
if (distance < minTrapDist)
{
minTrapDist = distance;
nearestTrap = unit;
}
}
}
// If we found a shambling and we're not close enough, move to it
if (closestHorror)
{
// If we're too far, run to it
if (minHorrorDist > 3.0f)
{
// Check if there's a trap in our path
if (nearestTrap && minTrapDist < 9.0f)
{
// Calculate a safe position that avoids the trap
float angle = nearestTrap->GetAngle(closestHorror);
float safeX = nearestTrap->GetPositionX() + cos(angle + M_PI_2) * 10.0f;
float safeY = nearestTrap->GetPositionY() + sin(angle + M_PI_2) * 10.0f;
float safeZ = 840.857f;
// Move to safe position first
return MoveTo(bot->GetMapId(), safeX, safeY, safeZ,
false, false, false, false, MovementPriority::MOVEMENT_FORCED);
}
// No traps nearby, move directly to horror
return MoveTo(closestHorror, 3.0f, MovementPriority::MOVEMENT_FORCED);
}
else
{
// We're close enough, stop moving
bot->StopMoving();
return true;
}
}
return false;
}
bool IccLichKingWinterAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
float currentDistance = bot->GetDistance2d(boss);
Unit* currentTarget = AI_VALUE(Unit*, "current target");
if (currentDistance < 48.0f)
{
if (botAI->IsRanged(bot))
{
// Calculate distances to group positions
float dist1 = bot->GetDistance2d(ICC_LK_FROSTR1_POSITION.GetPositionX(), ICC_LK_FROSTR1_POSITION.GetPositionY());
float dist2 = bot->GetDistance2d(ICC_LK_FROSTR2_POSITION.GetPositionX(), ICC_LK_FROSTR2_POSITION.GetPositionY());
float dist3 = bot->GetDistance2d(ICC_LK_FROSTR3_POSITION.GetPositionX(), ICC_LK_FROSTR3_POSITION.GetPositionY());
const Position* targetPos = &ICC_LK_FROSTR1_POSITION;
if (dist2 < dist1 && dist2 < dist3)
targetPos = &ICC_LK_FROSTR2_POSITION;
else if (dist3 < dist1 && dist3 < dist2)
targetPos = &ICC_LK_FROSTR3_POSITION;
float distToTarget = bot->GetDistance2d(targetPos->GetPositionX(), targetPos->GetPositionY());
if (distToTarget > 7.0f)
{
float angle = bot->GetAngle(targetPos);
float posX = bot->GetPositionX() + cos(angle) * 5.0f;
float posY = bot->GetPositionY() + sin(angle) * 5.0f;
return MoveTo(bot->GetMapId(), posX, posY, 840.857f,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
else
{
// Calculate distances to tank positions
float dist1 = bot->GetDistance2d(ICC_LK_FROST1_POSITION.GetPositionX(), ICC_LK_FROST1_POSITION.GetPositionY());
float dist2 = bot->GetDistance2d(ICC_LK_FROST2_POSITION.GetPositionX(), ICC_LK_FROST2_POSITION.GetPositionY());
float dist3 = bot->GetDistance2d(ICC_LK_FROST3_POSITION.GetPositionX(), ICC_LK_FROST3_POSITION.GetPositionY());
const Position* targetPos = &ICC_LK_FROST1_POSITION;
if (dist2 < dist1 && dist2 < dist3)
targetPos = &ICC_LK_FROST2_POSITION;
else if (dist3 < dist1 && dist3 < dist2)
targetPos = &ICC_LK_FROST3_POSITION;
float distToTarget = bot->GetDistance2d(targetPos->GetPositionX(), targetPos->GetPositionY());
if (distToTarget > 10.0f)
{
float angle = bot->GetAngle(targetPos);
float posX = bot->GetPositionX() + cos(angle) * 5.0f;
float posY = bot->GetPositionY() + sin(angle) * 5.0f;
return MoveTo(bot->GetMapId(), posX, posY, 840.857f,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
// Check for spheres if we're at a safe distance
if (bot->getClass() == CLASS_HUNTER)
{
Unit* closestSphere = nullptr;
float closestDist = 100.0f;
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (!unit || !unit->IsAlive())
continue;
uint32 entry = unit->GetEntry();
if (entry == 36633 || entry == 39305 || entry == 39306 || entry == 39307)
{
float dist = bot->GetDistance(unit);
if (!closestSphere || dist < closestDist)
{
closestSphere = unit;
closestDist = dist;
}
}
}
if (closestSphere)
{
return Attack(closestSphere);
}
}
return false;
}
bool IccLichKingAddsAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
Unit* spiritWarden = AI_VALUE2(Unit*, "find target", "spirit warden");
//temp solution for bots going underground due to buggy ice platfroms and adds that go underground
if (abs(bot->GetPositionZ() - 840.857f) > 1.0f)
return bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(),
bot->GetPositionY(), 840.857f, bot->GetOrientation());
//temp soultion for bots when they get teleport far away into another dimension (they are unable to attack spirit warden)
Difficulty diff = bot->GetRaidDifficulty();
if (!(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC) && abs(bot->GetPositionY() - -2095.7915f) > 200.0f)
{
return bot->TeleportTo(bot->GetMapId(), ICC_LICH_KING_ADDS_POSITION.GetPositionX(),
ICC_LICH_KING_ADDS_POSITION.GetPositionY(), ICC_LICH_KING_ADDS_POSITION.GetPositionZ(), bot->GetOrientation());
}
if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(72262) && bot->GetExactDist2d(boss) > 20.0f) //quake
{
float angle = bot->GetAngle(boss);
float posX = bot->GetPositionX() + cos(angle) * 10.0f;
float posY = bot->GetPositionY() + sin(angle) * 10.0f;
return MoveTo(bot->GetMapId(), posX, posY, boss->GetPositionZ(),
false, true, false, false, MovementPriority::MOVEMENT_COMBAT);
}
std::vector<Unit*> defiles;
Unit* closestDefile = nullptr;
float closestDistance = std::numeric_limits<float>::max();
// First gather all defiles
GuidVector npcs1 = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs1)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->IsAlive() && unit->GetEntry() == 38757)
{
defiles.push_back(unit);
float dist = bot->GetDistance(unit);
if (dist < closestDistance)
{
closestDistance = dist;
closestDefile = unit;
}
}
}
if (!defiles.empty())
{
float baseRadius = 6.0f;
float safetyMargin = 3.0f; // Fixed 3-yard safety margin
// First, find if we need to move from any defile
bool needToMove = false;
float bestAngle = 0.0f;
float maxSafeDistance = 0.0f;
// Check all defiles first to see if we need to move
for (Unit* defile : defiles)
{
if (!defile || !defile->IsAlive())
continue;
float currentRadius = baseRadius;
Aura* growAura = defile->GetAura(72756);
if (!growAura)
{
growAura = defile->GetAura(74162);
if (!growAura)
growAura = defile->GetAura(74163); //25hc mabye 74164
}
if (growAura)
{
uint8 stacks = growAura->GetStackAmount();
currentRadius = baseRadius + (stacks * 1.3f);
}
float dx = bot->GetPositionX() - defile->GetPositionX();
float dy = bot->GetPositionY() - defile->GetPositionY();
float distanceToCenter = sqrt(dx * dx + dy * dy);
if (distanceToCenter < (currentRadius + safetyMargin))
{
needToMove = true;
break;
}
}
// If we need to move, find the safest direction
if (needToMove)
{
// Try 16 different angles for more precise movement
for (int i = 0; i < 16; i++)
{
float testAngle = i * M_PI / 8;
float moveDistance = 5.0f; // Move in 5-yard increments
float testX = bot->GetPositionX() + moveDistance * cos(testAngle);
float testY = bot->GetPositionY() + moveDistance * sin(testAngle);
float testZ = 840.857f;
bot->UpdateAllowedPositionZ(testX, testY, testZ);
// Skip if not in LOS or too much height difference
if (!bot->IsWithinLOS(testX, testY, testZ) ||
fabs(testZ - bot->GetPositionZ()) >= 5.0f)
{
continue;
}
// Calculate minimum distance to any defile from this position
float minDefileDistance = std::numeric_limits<float>::max();
float distanceToBoss = boss->GetDistance2d(testX, testY);
for (Unit* defile : defiles)
{
if (!defile || !defile->IsAlive())
continue;
float dx = testX - defile->GetPositionX();
float dy = testY - defile->GetPositionY();
float dist = sqrt(dx * dx + dy * dy);
minDefileDistance = std::min(minDefileDistance, dist);
}
// Favor positions that are both safe from defiles and closer to the boss
float safetyScore = minDefileDistance;
float bossScore = 100.0f - std::min(100.0f, distanceToBoss); // Convert distance to a 0-100 score
float totalScore = safetyScore + (bossScore * 0.5f); // Weight safety more than boss proximity
// If this position is better than our previous best, update it
if (totalScore > maxSafeDistance)
{
maxSafeDistance = totalScore;
bestAngle = testAngle;
}
}
// Move in the best direction found
if (maxSafeDistance > 0)
{
float moveDistance = 5.0f;
float testX = bot->GetPositionX() + moveDistance * cos(bestAngle);
float testY = bot->GetPositionY() + moveDistance * sin(bestAngle);
float testZ = 840.857f;
bot->UpdateAllowedPositionZ(testX, testY, testZ);
return MoveTo(bot->GetMapId(), testX, testY, testZ,
false, false, false, true, MovementPriority::MOVEMENT_FORCED);
}
}
}
// Check if LK is casting Defile - make bots spread
if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(72762))
{
uint32 playerCount = 0;
uint32 botIndex = 0;
uint32 currentIndex = 0;
Map::PlayerList const& players = bot->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
{
Player* player = itr->GetSource();
if (!player || !player->IsAlive())
continue;
if (player == bot)
botIndex = currentIndex;
currentIndex++;
playerCount++;
}
// Calculate this bot's preferred angle based on its index
float preferredAngle = (float(botIndex) / float(playerCount)) * 2 * M_PI;
float moveDistance = 12.0f; // Fixed distance for consistent spread
// Start checking from preferred angle, then try adjacent angles if blocked
float bestAngle = preferredAngle;
bool foundSafeSpot = false;
// Try positions at increasing offsets from preferred angle
for (int offset = 0; offset <= 8; offset++) // Try up to 8 positions on each side
{
for (int direction = -1; direction <= 1; direction += 2) // Check both clockwise and counterclockwise
{
if (offset == 0 && direction > 0) // Skip second check of preferred angle
continue;
float testAngle = preferredAngle + (direction * offset * M_PI / 16);
float testX = bot->GetPositionX() + moveDistance * cos(testAngle);
float testY = bot->GetPositionY() + moveDistance * sin(testAngle);
float testZ = 840.857f;
bot->UpdateAllowedPositionZ(testX, testY, testZ);
if (!bot->IsWithinLOS(testX, testY, testZ) ||
fabs(testZ - bot->GetPositionZ()) >= 5.0f)
{
continue;
}
// Check if position is safe from defiles
bool isSafeFromDefiles = true;
for (Unit* defile : defiles)
{
if (!defile || !defile->IsAlive())
continue;
float currentRadius = 6.0f;
Aura* growAura = defile->GetAura(72756);
if (!growAura)
{
growAura = defile->GetAura(74162);
if (!growAura)
growAura = defile->GetAura(74163); //25hc mabye 74164
}
if (growAura)
{
uint8 stacks = growAura->GetStackAmount();
currentRadius = 6.0f + (stacks * 1.3f);
}
float dx = testX - defile->GetPositionX();
float dy = testY - defile->GetPositionY();
float distToDefile = sqrt(dx * dx + dy * dy);
if (distToDefile < (currentRadius + 3.0f))
{
isSafeFromDefiles = false;
break;
}
}
if (!isSafeFromDefiles)
continue;
// Check distance to other players
bool tooCloseToPlayers = false;
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
{
Player* player = itr->GetSource();
if (!player || !player->IsAlive() || player == bot)
continue;
float dx = testX - player->GetPositionX();
float dy = testY - player->GetPositionY();
float dist = sqrt(dx * dx + dy * dy);
if (dist < 5.0f) // Minimum spacing between players
{
tooCloseToPlayers = true;
break;
}
}
if (tooCloseToPlayers)
continue;
float distanceToBoss = boss->GetDistance2d(testX, testY);
if (distanceToBoss > 40.0f)
continue;
// We found a safe spot
bestAngle = testAngle;
foundSafeSpot = true;
break;
}
if (foundSafeSpot)
break;
}
if (foundSafeSpot)
{
float testX = bot->GetPositionX() + moveDistance * cos(bestAngle);
float testY = bot->GetPositionY() + moveDistance * sin(bestAngle);
float testZ = 840.857f;
bot->UpdateAllowedPositionZ(testX, testY, testZ);
return MoveTo(bot->GetMapId(), testX, testY, testZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
// Check for Val'kyr Shadowguard targeting real players
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->IsAlive() &&
(unit->GetEntry() == 36609 || unit->GetEntry() == 39120 || unit->GetEntry() == 39121 ||
unit->GetEntry() == 39122)) // Val'kyr Shadowguard entries
{
GuidVector npcs2 = AI_VALUE(GuidVector, "nearest hostile npcs");
Unit* closestValkyr = nullptr;
float minValkyrDistance = std::numeric_limits<float>::max();
// First find the closest Val'kyr to this bot
for (auto& npc2 : npcs2)
{
Unit* unit2 = botAI->GetUnit(npc2);
if (unit2 && unit2->IsAlive() &&
(unit2->GetEntry() == 36609 || unit2->GetEntry() == 39120 || unit2->GetEntry() == 39121 ||
unit2->GetEntry() == 39122)) // Val'kyr Shadowguard entries
{
float distance = bot->GetDistance(unit2);
if (distance < minValkyrDistance)
{
minValkyrDistance = distance;
closestValkyr = unit2;
}
}
}
if (closestValkyr)
{
// Check if this Val'kyr is grabbing any player
bool isGrabbingPlayer = false;
Map::PlayerList const& playerList = closestValkyr->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr)
{
Player* player = itr->GetSource();
if (!player || !player->IsAlive())
continue;
// Check if player is being lifted by Val'kyr (close horizontally and elevated)
float horizontalDist =
std::sqrt(std::pow(closestValkyr->GetPositionX() - player->GetPositionX(), 2) +
std::pow(closestValkyr->GetPositionY() - player->GetPositionY(), 2));
float verticalDist = player->GetPositionZ() - closestValkyr->GetPositionZ();
if (horizontalDist <= 1.0f && verticalDist > 0.5f) // Player is close horizontally and lifted up
{
isGrabbingPlayer = true;
break;
}
}
if (isGrabbingPlayer)
{
// Check if Valkyr is already CC'd
if (botAI->HasAura("Frost Nova", closestValkyr) || botAI->HasAura("Deep Freeze", closestValkyr) ||
botAI->HasAura("Entangling Roots", closestValkyr) ||
botAI->HasAura("Hammer of Justice", closestValkyr) ||
botAI->HasAura("Hamstring", closestValkyr) ||
botAI->HasAura("Concussive Shot", closestValkyr) ||
botAI->HasAura("Kidney Shot", closestValkyr) || botAI->HasAura("Gouge", closestValkyr) ||
botAI->HasAura("Frost Shock", closestValkyr) || botAI->HasAura("Chains of Ice", closestValkyr))
{
return Attack(closestValkyr);
}
// Try to CC the Val'kyr based on class priority - only stuns and slows
if (bot->getClass() == CLASS_MAGE)
{
if (botAI->CastSpell("Frost Nova", closestValkyr))
return true;
if (botAI->CastSpell("Deep Freeze", closestValkyr))
return true;
}
else if (bot->getClass() == CLASS_DRUID)
{
if (botAI->CastSpell("Entangling Roots", closestValkyr))
return true;
}
else if (bot->getClass() == CLASS_PALADIN)
{
if (botAI->CastSpell("Hammer of Justice", closestValkyr))
return true;
}
else if (bot->getClass() == CLASS_WARRIOR)
{
if (botAI->CastSpell("Hamstring", closestValkyr))
return true;
}
else if (bot->getClass() == CLASS_HUNTER)
{
if (botAI->CastSpell("Concussive Shot", closestValkyr))
return true;
}
else if (bot->getClass() == CLASS_ROGUE)
{
if (botAI->CastSpell("Kidney Shot", closestValkyr))
return true;
if (botAI->CastSpell("Gouge", closestValkyr))
return true;
}
else if (bot->getClass() == CLASS_SHAMAN)
{
if (botAI->CastSpell("Frost Shock", closestValkyr))
return true;
}
else if (bot->getClass() == CLASS_DEATH_KNIGHT)
{
if (botAI->CastSpell("Chains of Ice", closestValkyr))
return true;
}
// If no CC available or all failed, attack the Val'kyr
return Attack(closestValkyr);
}
}
}
}
if (!botAI->IsAssistTank(bot) && !boss->HealthBelowPct(71))
{
// Check for necrotic plague
bool hasPlague = botAI->HasAura("Necrotic Plague", bot);
// Only execute if we dont have the plague
if (!hasPlague)
{
float distanceToPosition = bot->GetDistance2d(ICC_LICH_KING_ADDS_POSITION.GetPositionX(), ICC_LICH_KING_ADDS_POSITION.GetPositionY());
if (distanceToPosition < 15.0f) // Only move if we're closer than 15 yards to adds position
{
// Move directly away from adds position in 5yd increments
float angle = atan2(bot->GetPositionY() - ICC_LICH_KING_ADDS_POSITION.GetPositionY(),
bot->GetPositionX() - ICC_LICH_KING_ADDS_POSITION.GetPositionX());
float moveDistance = 5.0f; // Always move 5 yards away
float x = bot->GetPositionX() + cos(angle) * moveDistance; // Move from current position
float y = bot->GetPositionY() + sin(angle) * moveDistance;
float z = bot->GetPositionZ(); // Use bot's Z to prevent going underground
return MoveTo(bot->GetMapId(), x, y, z,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
// Handle assist tanks - keep them at adds position
if (botAI->IsAssistTank(bot))
{
// Actively look for any shambling/spirit/ghoul that isn't targeting us
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->IsAlive() && (unit->GetEntry() == 37698 || unit->GetEntry() == 39299 ||
unit->GetEntry() == 39300 || unit->GetEntry() == 39301 || // Shambling entry
unit->GetEntry() == 36701 || unit->GetEntry() == 39302 ||
unit->GetEntry() == 39303 || unit->GetEntry() == 39304 || // Spirits entry
unit->GetEntry() == 37695 || unit->GetEntry() == 39309 ||
unit->GetEntry() == 39310 || unit->GetEntry() == 39311)) // Drudge Ghouls entry
{
if (!unit->GetVictim() || unit->GetVictim() != bot)
{
bot->SetFacingToObject(unit);
return Attack(unit); // Pick up any shambling that isn't targeting us
}
}
}
// If we have a current target, make sure we're facing it
if (Unit* currentTarget = bot->GetVictim())
{
bot->SetFacingToObject(currentTarget);
}
// Return to adds position if we're too far
if (bot->GetExactDist2d(ICC_LICH_KING_ADDS_POSITION) > 3.0f && !boss->HealthBelowPct(71))
{
return MoveTo(bot->GetMapId(), ICC_LICH_KING_ADDS_POSITION.GetPositionX(),
ICC_LICH_KING_ADDS_POSITION.GetPositionY(),
ICC_LICH_KING_ADDS_POSITION.GetPositionZ(),
false,true, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false; // Stay in position and keep facing current target
}
if (botAI->IsMainTank(bot) && !boss->HealthBelowPct(71))
{
float currentDist = bot->GetDistance(ICC_LICH_KING_ADDS_POSITION);
if (currentDist < 9.0f || currentDist > 22.0f) // 15 yards ±3 yards tolerance
{
float angle = ICC_LICH_KING_ADDS_POSITION.GetAngle(bot);
float targetDist = 16.0f;
float moveDistance = currentDist < targetDist ? 5.0f : -5.0f; // Move away or towards in 5yd increments
float x = bot->GetPositionX() + cos(angle) * moveDistance;
float y = bot->GetPositionY() + sin(angle) * moveDistance;
float z = ICC_LICH_KING_ADDS_POSITION.GetPositionZ();
return MoveTo(bot->GetMapId(), x, y, z,
false, true, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
if (botAI->IsRanged(bot) && !boss->HealthBelowPct(71))
{
float currentDist = bot->GetDistance(ICC_LICH_KING_ADDS_POSITION);
if (currentDist < 21.0f) // 20 yards ±3 yards tolerance
{
float angle = ICC_LICH_KING_ADDS_POSITION.GetAngle(bot);
float targetDist = 26.0f;
float moveDistance = currentDist < targetDist ? 5.0f : -5.0f; // Move away or towards in 5yd increments
float x = bot->GetPositionX() + cos(angle) * moveDistance;
float y = bot->GetPositionY() + sin(angle) * moveDistance;
float z = ICC_LICH_KING_ADDS_POSITION.GetPositionZ();
return MoveTo(bot->GetMapId(), x, y, z,
false, true, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}