mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 09:07:19 +00:00
* Temp fix valithria find target * Improve spread with FleePosition * Fix Valithria trigger
3235 lines
113 KiB
C++
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;
|
|
}
|