diff --git a/src/strategy/raids/icecrown/RaidIccActionContext.h b/src/strategy/raids/icecrown/RaidIccActionContext.h index 2fb45d3a1..182aa5c13 100644 --- a/src/strategy/raids/icecrown/RaidIccActionContext.h +++ b/src/strategy/raids/icecrown/RaidIccActionContext.h @@ -10,13 +10,100 @@ class RaidIccActionContext : public NamedObjectContext public: RaidIccActionContext() { + creators["icc lm tank position"] = &RaidIccActionContext::icc_lm_tank_position; + creators["icc spike"] = &RaidIccActionContext::icc_spike; + creators["icc dark reckoning"] = &RaidIccActionContext::icc_dark_reckoning; + creators["icc ranged position lady deathwhisper"] = &RaidIccActionContext::icc_ranged_position_lady_deathwhisper; + creators["icc adds lady deathwhisper"] = &RaidIccActionContext::icc_adds_lady_deathwhisper; + creators["icc shade lady deathwhisper"] = &RaidIccActionContext::icc_shade_lady_deathwhisper; + creators["icc rotting frost giant tank position"] = &RaidIccActionContext::icc_rotting_frost_giant_tank_position; creators["icc cannon fire"] = &RaidIccActionContext::icc_cannon_fire; creators["icc gunship enter cannon"] = &RaidIccActionContext::icc_gunship_enter_cannon; + creators["icc gunship teleport ally"] = &RaidIccActionContext::icc_gunship_teleport_ally; + creators["icc gunship teleport horde"] = &RaidIccActionContext::icc_gunship_teleport_horde; + creators["icc dbs tank position"] = &RaidIccActionContext::icc_dbs_tank_position; + creators["icc adds dbs"] = &RaidIccActionContext::icc_adds_dbs; + creators["icc festergut tank position"] = &RaidIccActionContext::icc_festergut_tank_position; + creators["icc festergut spore"] = &RaidIccActionContext::icc_festergut_spore; + creators["icc rotface tank position"] = &RaidIccActionContext::icc_rotface_tank_position; + creators["icc rotface group position"] = &RaidIccActionContext::icc_rotface_group_position; + creators["icc rotface move away from explosion"] = &RaidIccActionContext::icc_rotface_move_away_from_explosion; + creators["icc putricide volatile ooze"] = &RaidIccActionContext::icc_putricide_volatile_ooze; + creators["icc putricide gas cloud"] = &RaidIccActionContext::icc_putricide_gas_cloud; + creators["icc putricide growing ooze puddle"] = &RaidIccActionContext::icc_putricide_growing_ooze_puddle; + creators["avoid malleable goo"] = &RaidIccActionContext::avoid_malleable_goo; + creators["icc bpc keleseth tank"] = &RaidIccActionContext::icc_bpc_keleseth_tank; + creators["icc bpc nucleus"] = &RaidIccActionContext::icc_bpc_nucleus; + creators["icc bpc main tank"] = &RaidIccActionContext::icc_bpc_main_tank; + creators["icc bpc empowered vortex"] = &RaidIccActionContext::icc_bpc_empowered_vortex; + creators["icc bql tank position"] = &RaidIccActionContext::icc_bql_tank_position; + creators["icc bql pact of darkfallen"] = &RaidIccActionContext::icc_bql_pact_of_darkfallen; + creators["icc bql vampiric bite"] = &RaidIccActionContext::icc_bql_vampiric_bite; + creators["icc valkyre spear"] = &RaidIccActionContext::icc_valkyre_spear; + creators["icc sister svalna"] = &RaidIccActionContext::icc_sister_svalna; + creators["icc valithria portal"] = &RaidIccActionContext::icc_valithria_portal; + creators["icc valithria heal"] = &RaidIccActionContext::icc_valithria_heal; + creators["icc valithria dream cloud"] = &RaidIccActionContext::icc_valithria_dream_cloud; + creators["icc sindragosa tank position"] = &RaidIccActionContext::icc_sindragosa_tank_position; + creators["icc sindragosa frost beacon"] = &RaidIccActionContext::icc_sindragosa_frost_beacon; + creators["icc sindragosa blistering cold"] = &RaidIccActionContext::icc_sindragosa_blistering_cold; + creators["icc sindragosa unchained magic"] = &RaidIccActionContext::icc_sindragosa_unchained_magic; + creators["icc sindragosa chilled to the bone"] = &RaidIccActionContext::icc_sindragosa_chilled_to_the_bone; + creators["icc sindragosa mystic buffet"] = &RaidIccActionContext::icc_sindragosa_mystic_buffet; + creators["icc sindragosa frost bomb"] = &RaidIccActionContext::icc_sindragosa_frost_bomb; + creators["icc sindragosa tank swap position"] = &RaidIccActionContext::icc_sindragosa_tank_swap_position; + creators["icc lich king necrotic plague"] = &RaidIccActionContext::icc_lich_king_necrotic_plague; + creators["icc lich king winter"] = &RaidIccActionContext::icc_lich_king_winter; + creators["icc lich king adds"] = &RaidIccActionContext::icc_lich_king_adds; } private: + static Action* icc_lm_tank_position(PlayerbotAI* ai) { return new IccLmTankPositionAction(ai); } + static Action* icc_spike(PlayerbotAI* ai) { return new IccSpikeAction(ai); } + static Action* icc_dark_reckoning(PlayerbotAI* ai) { return new IccDarkReckoningAction(ai); } + static Action* icc_ranged_position_lady_deathwhisper(PlayerbotAI* ai) { return new IccRangedPositionLadyDeathwhisperAction(ai); } + static Action* icc_adds_lady_deathwhisper(PlayerbotAI* ai) { return new IccAddsLadyDeathwhisperAction(ai); } + static Action* icc_shade_lady_deathwhisper(PlayerbotAI* ai) { return new IccShadeLadyDeathwhisperAction(ai); } + static Action* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionAction(ai); } static Action* icc_cannon_fire(PlayerbotAI* ai) { return new IccCannonFireAction(ai); } static Action* icc_gunship_enter_cannon(PlayerbotAI* ai) { return new IccGunshipEnterCannonAction(ai); } + static Action* icc_gunship_teleport_ally(PlayerbotAI* ai) { return new IccGunshipTeleportAllyAction(ai); } + static Action* icc_gunship_teleport_horde(PlayerbotAI* ai) { return new IccGunshipTeleportHordeAction(ai); } + static Action* icc_dbs_tank_position(PlayerbotAI* ai) { return new IccDbsTankPositionAction(ai); } + static Action* icc_adds_dbs(PlayerbotAI* ai) { return new IccAddsDbsAction(ai); } + static Action* icc_festergut_tank_position(PlayerbotAI* ai) { return new IccFestergutTankPositionAction(ai); } + static Action* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeAction(ai); } + static Action* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionAction(ai); } + static Action* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionAction(ai); } + static Action* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionAction(ai); } + static Action* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeAction(ai); } + static Action* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudAction(ai); } + static Action* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleAction(ai); } + static Action* avoid_malleable_goo(PlayerbotAI* ai) { return new AvoidMalleableGooAction(ai); } + static Action* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankAction(ai); } + static Action* icc_bpc_nucleus(PlayerbotAI* ai) { return new IccBpcNucleusAction(ai); } + static Action* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankAction(ai); } + static Action* icc_bpc_empowered_vortex(PlayerbotAI* ai) { return new IccBpcEmpoweredVortexAction(ai); } + static Action* icc_bql_tank_position(PlayerbotAI* ai) { return new IccBqlTankPositionAction(ai); } + static Action* icc_bql_pact_of_darkfallen(PlayerbotAI* ai) { return new IccBqlPactOfDarkfallenAction(ai); } + static Action* icc_bql_vampiric_bite(PlayerbotAI* ai) { return new IccBqlVampiricBiteAction(ai); } + static Action* icc_valkyre_spear(PlayerbotAI* ai) { return new IccValkyreSpearAction(ai); } + static Action* icc_sister_svalna(PlayerbotAI* ai) { return new IccSisterSvalnaAction(ai); } + static Action* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalAction(ai); } + static Action* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealAction(ai); } + static Action* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudAction(ai); } + static Action* icc_sindragosa_tank_position(PlayerbotAI* ai) { return new IccSindragosaTankPositionAction(ai); } + static Action* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconAction(ai); } + static Action* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdAction(ai); } + static Action* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicAction(ai); } + static Action* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneAction(ai); } + static Action* icc_sindragosa_mystic_buffet(PlayerbotAI* ai) { return new IccSindragosaMysticBuffetAction(ai); } + static Action* icc_sindragosa_frost_bomb(PlayerbotAI* ai) { return new IccSindragosaFrostBombAction(ai); } + static Action* icc_sindragosa_tank_swap_position(PlayerbotAI* ai) { return new IccSindragosaTankSwapPositionAction(ai); } + static Action* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueAction(ai); } + static Action* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterAction(ai); } + static Action* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsAction(ai); } + }; #endif diff --git a/src/strategy/raids/icecrown/RaidIccActions.cpp b/src/strategy/raids/icecrown/RaidIccActions.cpp index 87a8abb9c..fe8bad27f 100644 --- a/src/strategy/raids/icecrown/RaidIccActions.cpp +++ b/src/strategy/raids/icecrown/RaidIccActions.cpp @@ -1,8 +1,10 @@ #include "RaidIccActions.h" - +#include "RaidIccStrategy.h" #include "Playerbots.h" #include "Timer.h" #include "Vehicle.h" +#include "GenericSpellActions.h" +#include "GenericActions.h" enum CreatureIds { NPC_KOR_KRON_BATTLE_MAGE = 37117, @@ -23,6 +25,263 @@ const std::vector availableTargets = { NPC_SKYBREAKER_SORCERER, NPC_IGB_MURADIN_BRONZEBEARD }; +static std::vector 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) > 5.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 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 ((botAI->IsHeal(bot) || botAI->IsDps(bot)) && bot->GetExactDist2d(unit) < radius) + { + return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit)); + } + } + return Attack(boss); +} + +//Gunship bool IccCannonFireAction::Execute(Event event) { Unit* vehicleBase = bot->GetVehicleBase(); @@ -133,4 +392,2293 @@ bool IccGunshipEnterCannonAction::EnterVehicle(Unit* vehicleBase, bool moveIfFar WorldPacket emptyPacket; bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); return true; -} \ No newline at end of file +} + +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))) + return false; + + 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))) + return false; + + 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 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 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 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_FORCED); + } + + // Assist tank positioning for big ooze + if (botAI->IsAssistTank(bot)) + { + // If we have the ooze flood aura, move away + if (bot->HasAura(71215)) + { + 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_FORCED); + } + + 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_FORCED); + } + 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"); + + 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 && (bot->HasAura(71215) || bot->HasAura(69789))) + { + 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, true, MovementPriority::MOVEMENT_FORCED); + } + } + } + } + + // Check if we're targeted by little ooze + Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze"); + if (smallOoze && smallOoze->GetVictim() == bot) + { + 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_FORCED); + } + return true; // Stay at position + } + + if(botAI->IsRanged(bot) || botAI->IsHeal(bot)) + { + if (!bot->HasAura(71215) && !bot->HasAura(69789)) // ooze flood id + { + 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 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, true, MovementPriority::MOVEMENT_FORCED); +} + +//PP + +bool IccPutricideGrowingOozePuddleAction::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() == 37690) //growing ooze puddle ID + { + float currentDistance = bot->GetDistance2d(unit); + // Move away from the puddle if the bot is too close + if (currentDistance < radius) + { + return MoveAway(unit, radius - currentDistance); + } + } + } + return false; +} + +bool IccPutricideVolatileOozeAction::Execute(Event event) +{ + const float POSITION_TOLERANCE = 5.0f; + const float STACK_DISTANCE = 8.0f; + const float SAFE_DISTANCE = 20.0f; + + // 1. Main tank positioning + if (botAI->IsMainTank(bot)) + return MoveTo(bot->GetMapId(), ICC_PUTRICIDE_TANK_OOZE_POSITION.GetPositionX(), + ICC_PUTRICIDE_TANK_OOZE_POSITION.GetPositionY(), + ICC_PUTRICIDE_TANK_OOZE_POSITION.GetPositionZ(), + false, true, false, true, MovementPriority::MOVEMENT_NORMAL); + + // Find the ooze + Unit* ooze = AI_VALUE2(Unit*, "find target", "volatile ooze"); + + // If bot is melee (and not main tank), always attack ooze if it exists + if (botAI->IsMelee(bot) && ooze) + { + bot->SetTarget(ooze->GetGUID()); + return Attack(ooze); + } + + // Check for aura on any group member + Group* group = bot->GetGroup(); + if (!group) + return false; + + Unit* auraTarget = nullptr; + bool anyoneHasAura = false; + bool botHasAura = bot->HasAura(70447); // Check if this bot has the aura + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member) + continue; + + if (member->HasAura(70447)) // Volatile Ooze Adhesive + { + anyoneHasAura = true; + auraTarget = member; + break; + } + } + + // For ranged and healers + if (botAI->IsRanged(bot) || botAI->IsHeal(bot)) + { + // If bot has aura or someone else has aura, stack with aura target + if (botHasAura || (anyoneHasAura && auraTarget)) + { + Position targetPos; + targetPos.m_positionX = auraTarget->GetPositionX(); + targetPos.m_positionY = auraTarget->GetPositionY(); + targetPos.m_positionZ = auraTarget->GetPositionZ(); + + if (bot->GetExactDist2d(targetPos) > STACK_DISTANCE) + { + bot->AttackStop(); + return MoveTo(bot->GetMapId(), targetPos.GetPositionX(), + targetPos.GetPositionY(), targetPos.GetPositionZ(), + false, true, false, true, MovementPriority::MOVEMENT_NORMAL); + } + } + // If no one has aura and ooze exists, maintain safe distance + else if (ooze) + { + float currentDist = bot->GetExactDist2d(ooze); + if (abs(currentDist - SAFE_DISTANCE) > POSITION_TOLERANCE) + { + // Calculate position 20 yards from ooze + float angle = ooze->GetAngle(bot); + float moveX = ooze->GetPositionX() + (SAFE_DISTANCE * cos(angle)); + float moveY = ooze->GetPositionY() + (SAFE_DISTANCE * sin(angle)); + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), + false, true, false, true, MovementPriority::MOVEMENT_NORMAL); + } + } + + // If in position and ooze exists, attack it (except healers) + if (ooze && !botAI->IsHeal(bot)) + { + bot->SetTarget(ooze->GetGUID()); + return Attack(ooze); + } + else if (botAI->IsHeal(bot)) + { + return false; // Allow healer to continue with normal healing actions + } + } + + return false; +} + +uint8_t IccPutricideGasCloudAction::lastKnownPosition = 0; + +bool IccPutricideGasCloudAction::Execute(Event event) +{ + if (botAI->IsMainTank(bot)) + return false; + + // Find the gas cloud + Unit* gasCloud = AI_VALUE2(Unit*, "find target", "gas cloud"); + if (!gasCloud) + return false; + + // Check if this bot has Gaseous Bloat + bool botHasAura = botAI->HasAura("Gaseous Bloat", bot); + + // If bot has aura, handle movement between positions + if (botHasAura) + { + // Get current position + float dist1 = bot->GetExactDist2d(ICC_PUTRICIDE_GAS1_POSITION.GetPositionX(), ICC_PUTRICIDE_GAS1_POSITION.GetPositionY()); + float dist2 = bot->GetExactDist2d(ICC_PUTRICIDE_GAS2_POSITION.GetPositionX(), ICC_PUTRICIDE_GAS2_POSITION.GetPositionY()); + float dist3 = bot->GetExactDist2d(ICC_PUTRICIDE_GAS3_POSITION.GetPositionX(), ICC_PUTRICIDE_GAS3_POSITION.GetPositionY()); + float dist4 = bot->GetExactDist2d(ICC_PUTRICIDE_GAS4_POSITION.GetPositionX(), ICC_PUTRICIDE_GAS4_POSITION.GetPositionY()); + + uint8_t currentPosition = 0; + const Position* nextPos = nullptr; + + // Determine current position + if (dist1 < 5.0f) currentPosition = 1; + else if (dist2 < 5.0f) currentPosition = 2; + else if (dist3 < 5.0f) currentPosition = 3; + else if (dist4 < 5.0f) currentPosition = 4; + + // If we're at a new position, update last known position + if (currentPosition != 0 && currentPosition != lastKnownPosition) + { + lastKnownPosition = currentPosition; + } + + // If we haven't reached our last known position yet, don't start new movement + if (lastKnownPosition != 0 && lastKnownPosition != currentPosition) + { + return false; + } + + // Determine next position to move to + if (gasCloud->GetExactDist2d(bot) < 18.0f) + { + switch(currentPosition) + { + case 0: // Not at any position + case 4: // At position 4, go to 1 + nextPos = &ICC_PUTRICIDE_GAS1_POSITION; + lastKnownPosition = 1; + break; + case 1: + nextPos = &ICC_PUTRICIDE_GAS2_POSITION; + lastKnownPosition = 2; + break; + case 2: + nextPos = &ICC_PUTRICIDE_GAS3_POSITION; + lastKnownPosition = 3; + break; + case 3: + nextPos = &ICC_PUTRICIDE_GAS4_POSITION; + lastKnownPosition = 4; + break; + } + + if (nextPos) + { + return MoveTo(bot->GetMapId(), nextPos->GetPositionX(), nextPos->GetPositionY(), nextPos->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + } + return false; + } + // If bot doesn't have aura, check if anyone else does + 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 someone has aura but not this bot, attack gas cloud (except healers) + if (someoneHasAura && !botAI->IsHeal(bot)) + { + lastKnownPosition = 0; + return Attack(gasCloud); + } + // If no one has aura yet, everyone stays at position 1 + else if (!someoneHasAura) + { + lastKnownPosition = 0; + return MoveTo(bot->GetMapId(), ICC_PUTRICIDE_GAS1_POSITION.GetPositionX(), + ICC_PUTRICIDE_GAS1_POSITION.GetPositionY(), + ICC_PUTRICIDE_GAS1_POSITION.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + } + } + + return false; +} + +bool AvoidMalleableGooAction::Execute(Event event) +{ + if (botAI->IsRanged(bot) || botAI->IsHeal(bot)) + { + float radius = 7.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 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)) + return false; + + // 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 + if (valanar) + return Attack(valanar); + if (taldaram) + return Attack(taldaram); + + return false; +} + +bool IccBpcEmpoweredVortexAction::Execute(Event event) +{ + // Double check that we're not a tank + if (botAI->IsMainTank(bot) || botAI->IsAssistTank(bot)) + return false; + + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + if (!valanar) + return false; + + float radius = 12.0f; + float moveIncrement = 3.0f; + bool isRanged = botAI->IsRanged(bot); + + GuidVector members = AI_VALUE(GuidVector, "group members"); + if (isRanged) + { + // Ranged: spread from other ranged + for (auto& member : members) + { + Unit* unit = botAI->GetUnit(member); + if (!unit || !unit->IsAlive() || unit == bot || + botAI->IsMainTank(bot) || botAI->IsAssistTank(bot) || !botAI->IsRanged(bot)) + continue; + + float dist = bot->GetExactDist2d(unit); + if (dist < radius) + { + float moveDistance = std::min(moveIncrement, radius - dist + 1.0f); + return MoveAway(unit, moveDistance); + } + } + } + else + { + // Melee: move opposite to ranged group + float avgX = 0, avgY = 0; + int rangedCount = 0; + + for (auto& member : members) + { + Unit* unit = botAI->GetUnit(member); + if (!unit || !unit->IsAlive() || !botAI->IsRanged(bot)) + continue; + + avgX += unit->GetPositionX(); + avgY += unit->GetPositionY(); + rangedCount++; + } + + if (rangedCount > 0) + { + avgX /= rangedCount; + avgY /= rangedCount; + + // Direction from ranged to Valanar + float dx = valanar->GetPositionX() - avgX; + float dy = valanar->GetPositionY() - avgY; + float len = sqrt(dx*dx + dy*dy); + + if (len > 0) + { + dx /= len; + dy /= len; + float targetX = valanar->GetPositionX() + dx * 5.0f; + float targetY = valanar->GetPositionY() + dy * 5.0f; + float targetZ = valanar->GetPositionZ(); + bot->UpdateAllowedPositionZ(targetX, targetY, targetZ); + + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ); + } + } + } + + return false; +} + +//BQL + +bool IccBqlTankPositionAction::Execute(Event event) +{ + // Only tanks should move to tank position + if (!(botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot) || botAI->IsRanged(bot))) + return false; + + // 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); + + GuidVector members = AI_VALUE(GuidVector, "group members"); + if (isRanged && !(bot->HasAura(70877) || bot->HasAura(71474))) + { + // 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 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 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 dpsTargets; + std::vector 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 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 to the portal if the bot is not at the interact distance + if (!portal->IsWithinDistInMap(bot, INTERACTION_DISTANCE)) + { + return MoveTo(portal, INTERACTION_DISTANCE); + } + + // 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; + + // Find Valithria + if (Creature* valithria = bot->FindNearestCreature(36789, 100.0f)) + { + switch (bot->getClass()) + { + case CLASS_SHAMAN: + return botAI->CastSpell(49276, valithria); // Lesser Healing Wave + case CLASS_PRIEST: + return botAI->CastSpell(48071, valithria); // Flash Heal + case CLASS_PALADIN: + return botAI->CastSpell(48782, valithria); // Holy 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; + + // 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, true, MovementPriority::MOVEMENT_NORMAL); + } + + // 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, true, MovementPriority::MOVEMENT_NORMAL); + } + + // 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, true, MovementPriority::MOVEMENT_NORMAL); + } + 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, true, MovementPriority::MOVEMENT_NORMAL); + } + 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, true, MovementPriority::MOVEMENT_NORMAL); + } + 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->IsAssistTankOfIndex(bot, 0)) + 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, true, MovementPriority::MOVEMENT_FORCED); + } + + 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, true, 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, true, 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 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, true, MovementPriority::MOVEMENT_FORCED); + } + } + 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, true, MovementPriority::MOVEMENT_FORCED); + } + } + } + } + + 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->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot)) + return false; + + float dist = bot->GetDistance(boss); + // Only move if we're too close to the boss (< 26 yards) + if (dist < 26.0f) + { + float const MAX_STEP = 5.0f; + float distToSafeSpot = bot->GetDistance2d(ICC_SINDRAGOSA_BLISTERING_COLD_POSITION.GetPositionX(), + ICC_SINDRAGOSA_BLISTERING_COLD_POSITION.GetPositionY()); + + if (distToSafeSpot > 0.1f) // Avoid division by zero + { + float ratio = std::min(MAX_STEP / distToSafeSpot, 1.0f); + float moveX = bot->GetPositionX() + (ICC_SINDRAGOSA_BLISTERING_COLD_POSITION.GetPositionX() - bot->GetPositionX()) * ratio; + float moveY = bot->GetPositionY() + (ICC_SINDRAGOSA_BLISTERING_COLD_POSITION.GetPositionY() - bot->GetPositionY()) * ratio; + + return MoveTo(bot->GetMapId(), moveX, moveY, ICC_SINDRAGOSA_BLISTERING_COLD_POSITION.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + } + return false; +} + +bool IccSindragosaUnchainedMagicAction::Execute(Event event) +{ + /*if (bot->HasAura(69762)) // unchained magic + { + float const SAFE_DISTANCE = 20.0f; + float moveIncrement = 5.0f; + bool needsToMove = false; + float moveX = bot->GetPositionX(); + float moveY = bot->GetPositionY(); + + // Check distance to other players + 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 < SAFE_DISTANCE) + { + needsToMove = true; + float moveDistance = std::min(moveIncrement, SAFE_DISTANCE - dist + 1.0f); + return MoveAway(unit, moveDistance); + } + } + }*/ + + if (Aura* aura = bot->GetAura(69766)) + { + if (aura->GetStackAmount() >= 6) + 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) + return false; + + if (!bot || !bot->IsAlive()) + return false; + + // Check if we have Mystic Buffet + Aura* aura = bot->GetAura(70127); + Aura* aura2 = bot->GetAura(72528); + if (!aura && !aura2) + return false; + + if (bot->HasAura(70126)) // Ice Block + return false; + + //if tank and is victim of boss, do nothing + if (botAI->IsTank(bot) && (boss->GetVictim() == bot)) + return false; + + // For non-tanks, require 3+ stacks + if (!((aura && aura->GetStackAmount() >= 3) || (aura2 && aura2->GetStackAmount() >= 3))) + return false; + + // Find nearest player with ice tomb + Unit* nearestTomb = nullptr; + float minDist = 150.0f; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + 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, return false + if (!nearestTomb) + { + return false; + } + + if (nearestTomb->IsWithinDist2d(ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionX(), + ICC_SINDRAGOSA_THOMBMB2_POSITION.GetPositionY(), 1.0f)) + { + // Check if already at LOS2 position + if (bot->IsWithinDist2d(ICC_SINDRAGOSA_LOS2_POSITION.GetPositionX(), + ICC_SINDRAGOSA_LOS2_POSITION.GetPositionY(), 1.0f)) + { + return false; + } + + return MoveTo(bot->GetMapId(), + ICC_SINDRAGOSA_LOS2_POSITION.GetPositionX(), + ICC_SINDRAGOSA_LOS2_POSITION.GetPositionY(), + ICC_SINDRAGOSA_LOS2_POSITION.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + + return false; // Tomb not at MB2 position +} + +bool IccSindragosaFrostBombAction::Execute(Event event) +{ + if (!bot || !bot->IsAlive() || bot->HasAura(70157)) // Skip if dead or in Ice Tomb + return false; + + // Find frost bomb marker + Unit* marker = nullptr; + Player* nearestTombPlayer = nullptr; + float minDist = 150.0f; + int tombPlayerCount = 0; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + if (npcs.empty()) + return false; + + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + if (unit->HasAura(70022)) // Frost bomb visual + { + marker = unit; + break; + } + } + + if (!marker) + return false; + + // Find players in ice tomb + Group* group = bot->GetGroup(); + if (!group) + return false; + + 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 aura + { + tombPlayerCount++; + float dist = bot->GetDistance(member); + if (dist < minDist) + { + minDist = dist; + nearestTombPlayer = member; + } + } + } + + // If no tombs or only one tomb, stop + if (!nearestTombPlayer) + return false; + + if (tombPlayerCount <= 1) + { + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + // If boss HP below 20% and we're already not in LOS of marker, stay in position + if (boss->GetHealthPct() < 20 && !marker->IsWithinLOS(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())) + { + bot->AttackStop(); + return true; + } + + // Otherwise handle normal single tomb case + if (!marker->IsWithinLOS(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())) + { + bot->AttackStop(); + return true; + } + + // If we have LOS with marker, we need to move behind tomb + float angle = marker->GetAngle(nearestTombPlayer); + float posX = nearestTombPlayer->GetPositionX() + cos(angle) * 5.0f; + float posY = nearestTombPlayer->GetPositionY() + sin(angle) * 5.0f; + float posZ = nearestTombPlayer->GetPositionZ(); + + return MoveTo(bot->GetMapId(), posX, posY, posZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + + // Calculate position behind tomb for normal case (more than 1 tomb) + float angle = marker->GetAngle(nearestTombPlayer); + float posX = nearestTombPlayer->GetPositionX() + cos(angle) * 5.0f; + float posY = nearestTombPlayer->GetPositionY() + sin(angle) * 5.0f; + float posZ = nearestTombPlayer->GetPositionZ(); + + // If we're already in position and not in LOS of marker, stop + if (bot->GetDistance2d(posX, posY) < 2.0f && !marker->IsWithinLOS(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())) + { + bot->AttackStop(); + return true; + } + + // Otherwise move to position + return MoveTo(bot->GetMapId(), posX, posY, posZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED); +} + +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 + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + Unit* closestHorror = nullptr; + float minDistance = 100.0f; + + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit) + continue; + + if (!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 < minDistance) + { + minDistance = distance; + closestHorror = 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 (minDistance > 3.0f) + { + // Use forced movement to ensure we get there quickly + 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"); + if (!boss) + return false; + + float currentDistance = bot->GetDistance2d(boss); + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + + + // Move away from the Lich King if the bot is too close + if (currentDistance < 48.0f) + { + return MoveAway(boss, 48.0f - currentDistance); + } + + // Check for spheres first + if (botAI->IsRangedDps(bot)) + { + // Find closest frost sphere + Unit* closestSphere = nullptr; + float closestDist = 100.0f; // Initialize with a large value + + GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + // Check if unit is one of the sphere types + 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 we found a sphere, attack it + if (closestSphere) + { + return Attack(closestSphere); + } + } + + if(botAI->IsMelee(bot) && botAI->IsDps(bot)) + { + // Check for raging spirits or shambling horrors + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + 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)) // Raging Spirit entry + { + // Check if we're in front of the add + if (unit->HasInArc(M_PI/3, bot)) // 60 degree frontal arc + { + // Move to behind the add + float angle = unit->GetOrientation() + M_PI; // Opposite of add's facing + float distance = 3.0f; // Close enough to melee but behind + + float newX = unit->GetPositionX() - distance * cos(angle); + float newY = unit->GetPositionY() - distance * sin(angle); + float newZ = unit->GetPositionZ(); + + bot->UpdateAllowedPositionZ(newX, newY, newZ); + + if (bot->IsWithinLOS(newX, newY, newZ)) + { + return MoveTo(bot->GetMapId(), newX, newY, newZ); + } + } + } + } + } + + + + if (botAI->IsRanged(bot)) + { + // Find assist tank + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* assistTank = nullptr; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + + if (botAI->IsAssistTank(member)) + { + assistTank = member; + break; + } + } + + if (assistTank) + { + float distance = bot->GetDistance(assistTank); + const float TARGET_DISTANCE = 21.0f; + const float DISTANCE_TOLERANCE = 6.0f; + const float MOVE_INCREMENT = 5.0f; + + if (std::abs(distance - TARGET_DISTANCE) > DISTANCE_TOLERANCE) + { + // Calculate current angle between tank and bot + float angle = assistTank->GetAngle(bot); + float moveDistance = 0.0f; + + if (distance < TARGET_DISTANCE) + { + moveDistance = std::min(TARGET_DISTANCE - distance, MOVE_INCREMENT); + } + else + { + moveDistance = -std::min(distance - TARGET_DISTANCE, MOVE_INCREMENT); + } + + // Calculate new position + float newX = bot->GetPositionX() + moveDistance * cos(angle); + float newY = bot->GetPositionY() + moveDistance * sin(angle); + float newZ = bot->GetPositionZ(); + + // Update Z coordinate properly + bot->UpdateAllowedPositionZ(newX, newY, newZ); + + // Check if new position is valid + if (bot->IsWithinLOS(newX, newY, newZ)) + { + return MoveTo(bot->GetMapId(), newX, newY, newZ); + } + + // If position invalid, don't move + return false; + } + } + } + + return false; +} + +bool IccLichKingAddsAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss) + return false; + + GuidVector npcs1 = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs1) + { + Unit* unit = botAI->GetUnit(npc); + if (unit && unit->IsAlive() && unit->GetEntry() == 38757) // Defile NPC + { + if (bot->GetDistance(unit) < 20.0f) // If within 20 yards + { + // Get predicted position from MoveAway before actually moving + float angle = unit->GetAngle(bot); + float testX = bot->GetPositionX() + 8.0f * cos(angle); + float testY = bot->GetPositionY() + 8.0f * sin(angle); + float testZ = bot->GetPositionZ(); + + bot->UpdateAllowedPositionZ(testX, testY, testZ); + + // Only move if the position is valid + if (bot->IsWithinLOS(testX, testY, testZ) && + fabs(testZ - bot->GetPositionZ()) < 5.0f) // Prevent too large Z changes + { + return MoveAway(unit, 8.0f); + } + else + { + // Try smaller distance if first fails + testX = bot->GetPositionX() + 5.0f * cos(angle); + testY = bot->GetPositionY() + 5.0f * sin(angle); + testZ = bot->GetPositionZ(); + + bot->UpdateAllowedPositionZ(testX, testY, testZ); + + if (bot->IsWithinLOS(testX, testY, testZ) && + fabs(testZ - bot->GetPositionZ()) < 5.0f) + { + return MoveAway(unit, 5.0f); + } + } + } + } + } + + // 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::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) + { + // 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(70)) + { + // Check for necrotic plague + bool hasPlague = botAI->HasAura("Necrotic Plague", bot); + + // Only execute if we 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); + } + } + } + + // 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) + return Attack(unit); // Pick up any shambling that isn't targeting us + } + } + + // Return to adds position if we're too far + if (bot->GetExactDist2d(ICC_LICH_KING_ADDS_POSITION) > 3.0f && !boss->HealthBelowPct(70)) + return MoveTo(bot->GetMapId(), ICC_LICH_KING_ADDS_POSITION.GetPositionX(), + ICC_LICH_KING_ADDS_POSITION.GetPositionY(), + ICC_LICH_KING_ADDS_POSITION.GetPositionZ()); + } + + if (botAI->IsMainTank(bot) && !boss->HealthBelowPct(70)) + { + float currentDist = bot->GetDistance(ICC_LICH_KING_ADDS_POSITION); + if (currentDist < 17.0f || currentDist > 23.0f) // 20 yards ±3 yards tolerance + { + float angle = ICC_LICH_KING_ADDS_POSITION.GetAngle(bot); + float targetDist = 20.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); + } + } + return false; +} diff --git a/src/strategy/raids/icecrown/RaidIccActions.h b/src/strategy/raids/icecrown/RaidIccActions.h index 14dbb4ea0..a7a1fd2cb 100644 --- a/src/strategy/raids/icecrown/RaidIccActions.h +++ b/src/strategy/raids/icecrown/RaidIccActions.h @@ -1,9 +1,114 @@ #ifndef _PLAYERBOT_RAIDICCACTIONS_H #define _PLAYERBOT_RAIDICCACTIONS_H +#include "Action.h" #include "MovementActions.h" #include "PlayerbotAI.h" #include "Playerbots.h" +#include "AttackAction.h" +#include "LastMovementValue.h" +#include "ObjectGuid.h" +#include "PlayerbotAIConfig.h" +#include "RaidIccStrategy.h" +#include "ScriptedCreature.h" +#include "SharedDefines.h" + + +const Position ICC_LM_TANK_POSITION = Position(-391.0f, 2259.0f, 42.0f); +const Position ICC_DARK_RECKONING_SAFE_POSITION = Position(-523.33386f, 2211.2031f, 62.823116f); +const Position ICC_ROTTING_FROST_GIANT_TANK_POSITION = Position(-265.90125f, 2209.0605f, 199.97006f); +const Position ICC_GUNSHIP_TELEPORT_ALLY = Position (-370.04645f, 1993.3536f, 466.65656f); +const Position ICC_GUNSHIP_TELEPORT_HORDE = Position (-449.5343f, 2477.2024f, 470.17648f); +const Position ICC_DBS_TANK_POSITION = Position(-494.26517f, 2211.549f, 541.11414f); +const Position ICC_FESTERGUT_TANK_POSITION = Position(4269.1772f, 3144.7673f, 360.38577f); +const Position ICC_FESTERGUT_RANGED_SPORE = Position(4261.143f, 3109.4146f, 360.38605f); +const Position ICC_FESTERGUT_MELEE_SPORE = Position(4269.1772f, 3144.7673f, 360.38577f); +const Position ICC_ROTFACE_TANK_POSITION = Position(4447.061f, 3150.9758f, 360.38568f); +const Position ICC_ROTFACE_BIG_OOZE_POSITION = Position(4432.687f, 3142.3035f, 360.38623f); +const Position ICC_ROTFACE_SAFE_POSITION = Position(4446.557f, 3065.6594f, 360.51843f); +const Position ICC_PUTRICIDE_TANK_OOZE_POSITION = Position(4362.709f, 3229.1448f, 389.4083f); +const Position ICC_PUTRICIDE_TANK_GAS_CLOUD_POSITION = Position(4397.0386f, 3221.385f, 389.3999f); +const Position ICC_PUTRICIDE_GAS1_POSITION = Position(4350.772f, 3249.9773f, 389.39508f); +const Position ICC_PUTRICIDE_GAS2_POSITION = Position(4390.002f, 3204.8855f, 389.39938f); +const Position ICC_PUTRICIDE_GAS3_POSITION = Position(4367.753f, 3177.5894f, 389.39575f); +const Position ICC_PUTRICIDE_GAS4_POSITION = Position(4321.8486f, 3206.464f, 389.3982f); +const Position ICC_BPC_OT_POSITION = Position(4649.2236f, 2796.0972f, 361.1815f); +const Position ICC_BPC_MT_POSITION = Position(4648.5674f, 2744.847f, 361.18222f); +const Position ICC_BQL_CENTER_POSITION = Position(4595.0f, 2769.0f, 400.0f); +const Position ICC_BQL_TANK_POSITION = Position(4616.102f, 2768.9167f, 400.13797f); +const Position ICC_SINDRAGOSA_TANK_POSITION = Position(4408.016f, 2508.0647f, 203.37955f); +const Position ICC_SINDRAGOSA_RANGED_POSITION = Position(4373.7686f, 2498.0042f, 203.38176f); +const Position ICC_SINDRAGOSA_MELEE_POSITION = Position(4389.22f, 2499.5237f, 203.38033f); +const Position ICC_SINDRAGOSA_BLISTERING_COLD_POSITION = Position(4357.036f, 2484.5574f, 203.4777f); +const Position ICC_SINDRAGOSA_THOMB1_POSITION = Position(4381.819f, 2471.1448f, 203.37704f); // Westmost position +const Position ICC_SINDRAGOSA_THOMB2_POSITION = Position(4381.819f, 2483.1448f, 203.37704f); // 12y east from pos1 +const Position ICC_SINDRAGOSA_THOMB3_POSITION = Position(4381.819f, 2471.1448f, 203.37704f); // Same as pos1 +const Position ICC_SINDRAGOSA_THOMB4_POSITION = Position(4381.819f, 2483.1448f, 203.37704f); // Same as pos2 +const Position ICC_SINDRAGOSA_THOMB5_POSITION = Position(4381.819f, 2495.1448f, 203.37704f); // 12y east from pos2/4 +const Position ICC_SINDRAGOSA_CENTER_POSITION = Position(4408.0464f, 2484.478f, 203.37529f); +const Position ICC_SINDRAGOSA_THOMBMB2_POSITION = Position(4382.6113f, 2505.4922f, 203.38197f); +const Position ICC_SINDRAGOSA_FBOMB_POSITION = Position(4400.031f, 2507.0295f, 203.37929f); +const Position ICC_SINDRAGOSA_LOS2_POSITION = Position(4376.0938f, 2511.103f, 203.38303f); +const Position ICC_LICH_KING_ADDS_POSITION = Position(486.63647f, -2095.7915f, 840.857f); + + +//Lord Marrogwar +class IccLmTankPositionAction : public AttackAction +{ +public: + IccLmTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc lm tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccSpikeAction : public AttackAction +{ +public: + IccSpikeAction(PlayerbotAI* botAI) : AttackAction(botAI, "icc spike") {} + bool Execute(Event event) override; +}; + +//Lady Deathwhisper +class IccDarkReckoningAction : public MovementAction +{ +public: + IccDarkReckoningAction(PlayerbotAI* botAI, std::string const name = "icc dark reckoning") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccRangedPositionLadyDeathwhisperAction : public AttackAction +{ +public: + IccRangedPositionLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc ranged position lady deathwhisper") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccAddsLadyDeathwhisperAction : public AttackAction +{ +public: + IccAddsLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc adds lady deathwhisper") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccShadeLadyDeathwhisperAction : public MovementAction +{ +public: + IccShadeLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc shade lady deathwhisper") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +//Gunship Battle +class IccRottingFrostGiantTankPositionAction : public AttackAction +{ +public: + IccRottingFrostGiantTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotting frost giant tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; class IccCannonFireAction : public Action { @@ -22,4 +127,308 @@ class IccGunshipEnterCannonAction : public MovementAction bool EnterVehicle(Unit* vehicleBase, bool moveIfFar); }; +class IccGunshipTeleportAllyAction : public AttackAction +{ +public: + IccGunshipTeleportAllyAction(PlayerbotAI* botAI, std::string const name = "icc gunship teleport ally") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccGunshipTeleportHordeAction : public AttackAction +{ +public: + IccGunshipTeleportHordeAction(PlayerbotAI* botAI, std::string const name = "icc gunship teleport horde") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +//DBS +class IccDbsTankPositionAction : public AttackAction +{ +public: + IccDbsTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc dbs tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccAddsDbsAction : public AttackAction +{ +public: + IccAddsDbsAction(PlayerbotAI* botAI, std::string const name = "icc adds dbs") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +//FESTERGUT +class IccFestergutTankPositionAction : public AttackAction +{ +public: + IccFestergutTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc festergut tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccFestergutSporeAction : public AttackAction +{ +public: + IccFestergutSporeAction(PlayerbotAI* botAI, std::string const name = "icc festergut spore") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +//Rotface +class IccRotfaceTankPositionAction : public AttackAction +{ +public: + IccRotfaceTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccRotfaceGroupPositionAction : public AttackAction +{ +public: + IccRotfaceGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface group position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccRotfaceMoveAwayFromExplosionAction : public MovementAction +{ + public: + IccRotfaceMoveAwayFromExplosionAction(PlayerbotAI* botAI, std::string const name = "icc rotface move away from explosion") + : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +//PP + +class IccPutricideGrowingOozePuddleAction : public AttackAction +{ +public: + IccPutricideGrowingOozePuddleAction(PlayerbotAI* botAI, std::string const name = "icc putricide growing ooze puddle") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccPutricideVolatileOozeAction : public AttackAction +{ +public: + IccPutricideVolatileOozeAction(PlayerbotAI* botAI, std::string const name = "icc putricide volatile ooze") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class IccPutricideGasCloudAction : public AttackAction +{ + public: + IccPutricideGasCloudAction(PlayerbotAI* botAI, std::string const name = "icc putricide gas cloud") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; + private: + static uint8_t lastKnownPosition; // 0 = none, 1-4 = positions 1-4 +}; + +class AvoidMalleableGooAction : public MovementAction +{ + public: + AvoidMalleableGooAction(PlayerbotAI* ai) : MovementAction(ai, "avoid malleable goo") {} + bool Execute(Event event) override; +}; + +//BPC +class IccBpcKelesethTankAction : public AttackAction +{ +public: + IccBpcKelesethTankAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bpc keleseth tank") {} + bool Execute(Event event) override; +}; + +class IccBpcNucleusAction : public AttackAction +{ +public: + IccBpcNucleusAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bpc nucleus") {} + bool Execute(Event event) override; +}; + +class IccBpcMainTankAction : public AttackAction +{ +public: + IccBpcMainTankAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bpc main tank") {} + bool Execute(Event event) override; +}; + +//BPC Vortex +class IccBpcEmpoweredVortexAction : public MovementAction +{ +public: + IccBpcEmpoweredVortexAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc bpc empowered vortex") {} + bool Execute(Event event) override; +}; + +//Blood Queen Lana'thel +class IccBqlTankPositionAction : public AttackAction +{ +public: + IccBqlTankPositionAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bql tank position") {} + bool Execute(Event event) override; +}; + +class IccBqlPactOfDarkfallenAction : public MovementAction +{ +public: + IccBqlPactOfDarkfallenAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc bql pact of darkfallen") {} + bool Execute(Event event) override; +}; + +class IccBqlVampiricBiteAction : public AttackAction +{ +public: + IccBqlVampiricBiteAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bql vampiric bite") {} + bool Execute(Event event) override; +}; + +//VDW +class IccValkyreSpearAction : public AttackAction +{ +public: + IccValkyreSpearAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc valkyre spear") {} + bool Execute(Event event) override; +}; + +class IccSisterSvalnaAction : public AttackAction +{ +public: + IccSisterSvalnaAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sister svalna") {} + bool Execute(Event event) override; +}; + +class IccValithriaPortalAction : public MovementAction +{ +public: + IccValithriaPortalAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc valithria portal") {} + bool Execute(Event event) override; +}; + +class IccValithriaHealAction : public AttackAction +{ +public: + IccValithriaHealAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc valithria heal") {} + bool Execute(Event event) override; +}; + +class IccValithriaDreamCloudAction : public MovementAction +{ +public: + IccValithriaDreamCloudAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc valithria dream cloud") {} + bool Execute(Event event) override; +}; + +//Sindragosa +class IccSindragosaTankPositionAction : public AttackAction +{ +public: + IccSindragosaTankPositionAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa tank position") {} + bool Execute(Event event) override; +}; + +class IccSindragosaFrostBeaconAction : public AttackAction +{ +public: + IccSindragosaFrostBeaconAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa frost beacon") {} + bool Execute(Event event) override; +}; + +class IccSindragosaBlisteringColdAction : public AttackAction +{ +public: + IccSindragosaBlisteringColdAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa blistering cold") {} + bool Execute(Event event) override; +}; + +class IccSindragosaUnchainedMagicAction : public AttackAction +{ +public: + IccSindragosaUnchainedMagicAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa unchained magic") {} + bool Execute(Event event) override; +}; + +class IccSindragosaChilledToTheBoneAction : public AttackAction +{ +public: + IccSindragosaChilledToTheBoneAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa chilled to the bone") {} + bool Execute(Event event) override; +}; + +class IccSindragosaMysticBuffetAction : public AttackAction +{ +public: + IccSindragosaMysticBuffetAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa mystic buffet") {} + bool Execute(Event event) override; +}; + +class IccSindragosaFrostBombAction : public AttackAction +{ +public: + IccSindragosaFrostBombAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc sindragosa frost bomb") {} + bool Execute(Event event) override; +}; + +class IccSindragosaTankSwapPositionAction : public AttackAction +{ + public: + IccSindragosaTankSwapPositionAction(PlayerbotAI* botAI) + : AttackAction(botAI, "sindragosa tank swap position") {} + bool Execute(Event event) override; +}; + + +//LK +class IccLichKingNecroticPlagueAction : public MovementAction +{ + public: + IccLichKingNecroticPlagueAction(PlayerbotAI* botAI) + : MovementAction(botAI, "icc lich king necrotic plague") {} + bool Execute(Event event) override; +}; + +class IccLichKingWinterAction : public AttackAction +{ + public: + IccLichKingWinterAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc lich king winter") {} + bool Execute(Event event) override; +}; + +class IccLichKingAddsAction : public AttackAction +{ + public: + IccLichKingAddsAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc lich king adds") {} + bool Execute(Event event) override; +}; + + + #endif diff --git a/src/strategy/raids/icecrown/RaidIccMultipliers.cpp b/src/strategy/raids/icecrown/RaidIccMultipliers.cpp new file mode 100644 index 000000000..edf528d0d --- /dev/null +++ b/src/strategy/raids/icecrown/RaidIccMultipliers.cpp @@ -0,0 +1,632 @@ +#include "RaidIccMultipliers.h" + +#include "ChooseTargetActions.h" +#include "DKActions.h" +#include "DruidActions.h" +#include "DruidBearActions.h" +#include "FollowActions.h" +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "HunterActions.h" +#include "MageActions.h" +#include "MovementActions.h" +#include "PaladinActions.h" +#include "PriestActions.h" +#include "RaidIccActions.h" +#include "ReachTargetActions.h" +#include "RogueActions.h" +#include "ScriptedCreature.h" +#include "ShamanActions.h" +#include "UseMeetingStoneAction.h" +#include "WarriorActions.h" +#include "PlayerbotAI.h" + +float IccLadyDeathwhisperMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss) + { + return 1.0f; + } + + if (dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +float IccAddsDbsMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); + if (!boss) + { + return 1.0f; + } + + if (dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + + if (botAI->IsMainTank(bot)) + { + Aura* aura = botAI->GetAura("rune of blood", bot); + if (aura) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + } + + if (botAI->IsDps(bot)) + { + GuidVector targets = AI_VALUE(GuidVector, "possible targets"); + bool hasAdds = false; + for (auto& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && ( + unit->GetEntry() == 38508 || //blood beast + unit->GetEntry() == 38596 || + unit->GetEntry() == 38597 || + unit->GetEntry() == 38598)) + { + hasAdds = true; + break; + } + } + + if (hasAdds && !botAI->IsMainTank(bot)) + { + if (dynamic_cast(action)) + return 2.0f; + else if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + } + return 1.0f; +} + +float IccDogsMultiplier::GetValue(Action* action) +{ + bool bossPresent = false; + if (AI_VALUE2(Unit*, "find target", "stinky") || AI_VALUE2(Unit*, "find target", "precious")) + bossPresent = true; + + if (!bossPresent) + return 1.0f; + + if (botAI->IsMainTank(bot)) + { + Aura* aura = botAI->GetAura("mortal wound", bot, false, true); + if (aura && aura->GetStackAmount() >= 8) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + } + return 1.0f; +} + +float IccFestergutMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); + if (!boss) + { + return 1.0f; + } + + if (dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + + if (botAI->IsMainTank(bot)) + { + Aura* aura = botAI->GetAura("gastric bloat", bot, false, true); + if (aura && aura->GetStackAmount() >= 6) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + } + + if (bot->HasAura(69279) && (bot->getClass() == CLASS_HUNTER)) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + } + return 1.0f; +} + +float IccRotfaceMultiplier::GetValue(Action* action) +{ + // If we're already executing the escape movement, don't interrupt it + if (dynamic_cast(action)) + return 1.0f; + + Unit* boss = AI_VALUE2(Unit*, "find target", "big ooze"); + if (!boss) { return 1.0f; } + + if (dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + + if (dynamic_cast(action)) + { + return 0.0f; + } + + static std::map lastExplosionTimes; + static std::map hasMoved; + + ObjectGuid botGuid = bot->GetGUID(); + + // When cast starts, record the time + if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(69839)) + { + if (lastExplosionTimes[botGuid] == 0) // Only set if not already set + { + lastExplosionTimes[botGuid] = time(nullptr); + hasMoved[botGuid] = false; + } + } + + // If explosion cast is no longer active, reset the timers + if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(69839)) + { + if (lastExplosionTimes[botGuid] > 0 && time(nullptr) - lastExplosionTimes[botGuid] >= 16) + { + lastExplosionTimes[botGuid] = 0; + hasMoved[botGuid] = false; + return 1.0f; // Allow normal actions to resume + } + } + + // If 9 seconds have passed since cast start and we haven't moved yet + if (lastExplosionTimes[botGuid] > 0 && !hasMoved[botGuid] && time(nullptr) - lastExplosionTimes[botGuid] >= 9) + { + if (dynamic_cast(action) + && !dynamic_cast(action)) + { + return 0.0f; // Block other movement actions + } + hasMoved[botGuid] = true; // Mark that we've initiated movement + } + + // Continue blocking other movements for 7 seconds after moving + if (hasMoved[botGuid] && time(nullptr) - lastExplosionTimes[botGuid] < 16 // 9 seconds wait + 7 seconds stay + && dynamic_cast(action) + && !dynamic_cast(action)) + { + return 0.0f; + } + + return 1.0f; +} + +/*float IccRotfaceGroupPositionMultiplier::GetValue(Action* action) +{ + if (dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +}*/ + +float IccAddsPutricideMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) + { + return 1.0f; + } + + if (botAI->IsMainTank(bot)) + { + Aura* aura = botAI->GetAura("mutated plague", bot, false, true); + if (aura && aura->GetStackAmount() >= 4) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + } + + if (dynamic_cast(action) || dynamic_cast(action)) + { + if (dynamic_cast(action)) + return 0.0f; + } + + if (botAI->IsDps(bot)) + { + GuidVector targets = AI_VALUE(GuidVector, "possible targets"); + bool hasAdds = false; + for (auto& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && (unit->GetEntry() == 37697 || unit->GetEntry() == 38604 || unit->GetEntry() == 38758 || unit->GetEntry() == 38759 ||//volatile ooze + unit->GetEntry() == 37562 || unit->GetEntry() == 38602 || unit->GetEntry() == 38760 || unit->GetEntry() == 38761)) //gas cloud + { + hasAdds = true; + break; + } + } + + if (hasAdds) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 2.0f; + else if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + } + return 1.0f; +} + +//bpc +float IccBpcAssistMultiplier::GetValue(Action* action) +{ + if (!action) + return 1.0f; + + Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth"); + if (!keleseth || !keleseth->IsAlive()) + return 1.0f; + + // For assist tank during BPC fight + if (botAI->IsAssistTank(bot)) + { + // Allow BPC-specific actions + if (dynamic_cast(action) || + dynamic_cast(action)) + return 1.0f; + + // Disable normal assist behavior + if (dynamic_cast(action)) + return 0.0f; + + // Disable following + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +//BQL +float IccBqlPactOfDarkfallenMultiplier::GetValue(Action* action) +{ + if (!action) + return 1.0f; + + if (action->getName() == "icc bql pact of darkfallen") + return 1.0f; + + // If bot has Pact of Darkfallen aura, return 0 for all other actions + if (bot->HasAura(71340)) + return 0.0f; + + return 1.0f; +} + +float IccBqlVampiricBiteMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); + if (!boss) + return 1.0f; + + if (bot->HasAura(70877) || bot->HasAura(71474)) // If bot has frenzied bloodthirst + { + if (dynamic_cast(action)) + return 5.0f; // Highest priority for bite action + else if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; // Disable all formation/movement actions + else + return 0.0f; // Disable all other actions + } + + return 1.0f; +} + +//VDW +float IccValithriaDreamCloudMultiplier::GetValue(Action* action) +{ + if (!bot->HasAura(70766)) + return 1.0f; + + // If bot is in dream state, prioritize cloud collection over other actions + if (bot->HasAura(70766) && dynamic_cast(action)) + return 2.0f; + else if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +//SINDRAGOSA +float IccSindragosaTankPositionMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + else if (dynamic_cast(action)) + return 0.0f; + return 1.0f; +} + +float IccSindragosaFrostBeaconMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return 1.0f; + + if (!dynamic_cast(action)) + return 1.0f; + + // Highest priority if we have beacon + if (bot->HasAura(70126)) + return 2.0f; + + // Lower priority for non-beaconed players + float const MIN_SAFE_DISTANCE = 11.0f; + GuidVector members = AI_VALUE(GuidVector, "group members"); + + for (auto& member : members) + { + Unit* player = botAI->GetUnit(member); + if (!player || player->GetGUID() == bot->GetGUID()) + continue; + + if (player->HasAura(70126)) // Frost Beacon + { + float dist = bot->GetExactDist2d(player); + if (dist < MIN_SAFE_DISTANCE) + return 1.5f; // Medium priority if too close to beaconed player + } + } + + return 1.0f; +} + +float IccSindragosaBlisteringColdPriorityMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return 1.0f; + + // Check if boss is casting blistering cold (using both normal and heroic spell IDs) + if (boss->HasUnitState(UNIT_STATE_CASTING) && + (boss->FindCurrentSpellBySpellId(70123) || boss->FindCurrentSpellBySpellId(71047) || + boss->FindCurrentSpellBySpellId(71048) || boss->FindCurrentSpellBySpellId(71049))) + { + // If this is the blistering cold action, give it highest priority + if (dynamic_cast(action) || + dynamic_cast(action)) + return 5.0f; + + // Disable all other actions while blistering cold is casting + return 0.0f; + } + + return 1.0f; +} + +float IccSindragosaMysticBuffetMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return 1.0f; + + if (botAI->IsMainTank(bot)) + { + Aura* aura = botAI->GetAura("mystic buffet", bot, false, true); + if (aura && aura->GetStackAmount() >= 8) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + } + + if (boss->GetVictim() == bot) + return 1.0f; + + // Only modify actions if we have buffet stacks + Aura* aura = bot->GetAura(70127); + Aura* aura2 = bot->GetAura(72528); + + // Return normal priority if no auras or not enough stacks + if (!aura && !aura2) + return 1.0f; + + bool hasEnoughStacks = (aura && aura->GetStackAmount() >= 3) || (aura2 && aura2->GetStackAmount() >= 3); + if (!hasEnoughStacks) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action)) + return 5.0f; + else if (dynamic_cast(action) || + dynamic_cast(action) + || dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + return 1.0f; +} + +float IccSindragosaFrostBombMultiplier::GetValue(Action* action) +{ + if (!action || !bot || !bot->IsAlive()) + return 1.0f; + + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return 1.0f; + + float const MAX_REACTION_RANGE = 200.0f; + + // Check if there's an active frost bomb marker within range + bool hasMarkerInRange = false; + float closestDist = std::numeric_limits::max(); + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + if (unit->HasAura(70022)) // Frost bomb visual + { + float dist = bot->GetDistance(unit); + if (dist <= MAX_REACTION_RANGE && dist < closestDist) + { + hasMarkerInRange = true; + closestDist = dist; + } + } + } + + if (!hasMarkerInRange) + return 1.0f; + + if (dynamic_cast(action)) + return 5.0f; + else if (dynamic_cast(action) || + dynamic_cast(action) + || dynamic_cast(action) + || dynamic_cast(action)) + return 0.0f; + return 1.0f; +} + +float IccLichKingNecroticPlagueMultiplier::GetValue(Action* action) +{ + // Allow plague movement action to proceed + if (dynamic_cast(action)) + return 1.0f; + // Block combat formation and cure actions by default + else if (dynamic_cast(action)) + return 0.0f; + + // Handle cure actions + if (dynamic_cast(action)) + { + static std::map plagueTimes; + static std::map allowCure; + + Unit* target = action->GetTarget(); + if (!target || !target->IsPlayer()) + return 0.0f; + + ObjectGuid targetGuid = target->GetGUID(); + uint32 currentTime = getMSTime(); + + // Check if target has plague + bool hasPlague = target->HasAura(70338) || target->HasAura(73785) || + target->HasAura(73786) || target->HasAura(73787) || + target->HasAura(70337) || target->HasAura(73912) || + target->HasAura(73913) || target->HasAura(73914); + + // If no plague, reset timers and block cure + if (!hasPlague) + { + plagueTimes.erase(targetGuid); + allowCure.erase(targetGuid); + return 0.0f; + } + + // If we haven't seen this plague yet, start the timer + if (plagueTimes.find(targetGuid) == plagueTimes.end()) + { + plagueTimes[targetGuid] = currentTime; + allowCure[targetGuid] = false; + return 0.0f; + } + + // If we've already allowed cure for this plague instance, keep allowing it + if (allowCure[targetGuid]) + { + return 1.0f; + } + + // Check if enough time has passed + uint32 timeSincePlague = currentTime - plagueTimes[targetGuid]; + if (timeSincePlague >= 3000) + { + allowCure[targetGuid] = true; + return 1.0f; + } + else + { + return 0.0f; + } + } + + return 1.0f; +} + +float IccLichKingAddsMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss) + return 1.0f; + + if (botAI->IsMainTank(bot) && dynamic_cast(action)) + { + if (dynamic_cast(action)) + return 0.0f; + } + + if (botAI->IsAssistTank(bot)) + { + // Allow BPC-specific actions + if (dynamic_cast(action)) + return 1.0f; + + // Disable normal assist behavior + if (dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + + // Disable following + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +//raging spirit, Ice sphere, valkyere shadowguard, sphere 36633 39305 39306 39307 \ No newline at end of file diff --git a/src/strategy/raids/icecrown/RaidIccMultipliers.h b/src/strategy/raids/icecrown/RaidIccMultipliers.h new file mode 100644 index 000000000..beaead63e --- /dev/null +++ b/src/strategy/raids/icecrown/RaidIccMultipliers.h @@ -0,0 +1,152 @@ +#ifndef _PLAYERBOT_RAIDICCMULTIPLIERS_H +#define _PLAYERBOT_RAIDICCMULTIPLIERS_H + +#include "Multiplier.h" + +//Lady Deathwhisper +class IccLadyDeathwhisperMultiplier : public Multiplier +{ +public: + IccLadyDeathwhisperMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc lady deathwhisper") {} + virtual float GetValue(Action* action); +}; + +//DBS +class IccAddsDbsMultiplier : public Multiplier +{ +public: + IccAddsDbsMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc adds dbs") {} + virtual float GetValue(Action* action); +}; + +//DOGS + +class IccDogsMultiplier : public Multiplier +{ +public: + IccDogsMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc dogs") {} + virtual float GetValue(Action* action); +}; + +//FESTERGUT +class IccFestergutMultiplier : public Multiplier +{ +public: + IccFestergutMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc festergut") {} + virtual float GetValue(Action* action); +}; + +//ROTFACE +class IccRotfaceMultiplier : public Multiplier +{ +public: + IccRotfaceMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc rotface") {} + virtual float GetValue(Action* action); +}; + +/*class IccRotfaceGroupPositionMultiplier : public Multiplier +{ +public: + IccRotfaceGroupPositionMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc rotface group position") {} + virtual float GetValue(Action* action); +};*/ + +//PP +class IccAddsPutricideMultiplier : public Multiplier +{ +public: + IccAddsPutricideMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc adds putricide") {} + virtual float GetValue(Action* action); +}; + +//BPC +class IccBpcAssistMultiplier : public Multiplier +{ +public: + IccBpcAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "icc bpc assist") {} + virtual float GetValue(Action* action); +}; + +//BQL +class IccBqlPactOfDarkfallenMultiplier : public Multiplier +{ +public: + IccBqlPactOfDarkfallenMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "icc bql pact of darkfallen multiplier") {} + virtual float GetValue(Action* action) override; +}; + +class IccBqlVampiricBiteMultiplier : public Multiplier +{ +public: + IccBqlVampiricBiteMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc bql vampiric bite") {} + virtual float GetValue(Action* action); +}; + +//VDW +class IccValithriaDreamCloudMultiplier : public Multiplier +{ +public: + IccValithriaDreamCloudMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc valithria dream cloud") {} + virtual float GetValue(Action* action); +}; + +//SINDRAGOSA +class IccSindragosaTankPositionMultiplier : public Multiplier +{ +public: + IccSindragosaTankPositionMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc sindragosa tank position") {} + virtual float GetValue(Action* action); +}; + +class IccSindragosaFrostBeaconMultiplier : public Multiplier +{ +public: + IccSindragosaFrostBeaconMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc sindragosa frost beacon") {} + virtual float GetValue(Action* action); +}; + +/*class IccSindragosaFlyingMultiplier : public Multiplier +{ +public: + IccSindragosaFlyingMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc sindragosa flying") {} + virtual float GetValue(Action* action); +};*/ + +class IccSindragosaMysticBuffetMultiplier : public Multiplier +{ +public: + IccSindragosaMysticBuffetMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc sindragosa mystic buffet") {} + virtual float GetValue(Action* action); +}; + +class IccSindragosaBlisteringColdPriorityMultiplier : public Multiplier +{ +public: + IccSindragosaBlisteringColdPriorityMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sindragosa blistering cold priority") {} + + virtual float GetValue(Action* action) override; +}; + +class IccSindragosaFrostBombMultiplier : public Multiplier +{ +public: + IccSindragosaFrostBombMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc sindragosa frost bomb") {} + virtual float GetValue(Action* action); +}; + +class IccLichKingNecroticPlagueMultiplier : public Multiplier +{ +public: + IccLichKingNecroticPlagueMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc lich king necrotic plague") {} + virtual float GetValue(Action* action); +}; + +class IccLichKingAddsMultiplier : public Multiplier +{ +public: + IccLichKingAddsMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc lich king adds") {} + virtual float GetValue(Action* action); +}; + + +#endif \ No newline at end of file diff --git a/src/strategy/raids/icecrown/RaidIccStrategy.cpp b/src/strategy/raids/icecrown/RaidIccStrategy.cpp index b5333cc8a..a3a482b27 100644 --- a/src/strategy/raids/icecrown/RaidIccStrategy.cpp +++ b/src/strategy/raids/icecrown/RaidIccStrategy.cpp @@ -1,14 +1,192 @@ #include "RaidIccStrategy.h" -#include "Strategy.h" +#include "RaidIccMultipliers.h" void RaidIccStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode( - "icc gunship cannon near", + //Lord Marrogwar + triggers.push_back(new TriggerNode("icc lm tank position", + NextAction::array(0, new NextAction("icc lm tank position", ACTION_RAID + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc spike near", + NextAction::array(0, new NextAction("icc spike", ACTION_RAID + 5), nullptr))); + + //Lady Deathwhisper + triggers.push_back(new TriggerNode("icc dark reckoning", + NextAction::array(0, new NextAction("icc dark reckoning", ACTION_MOVE + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc ranged position lady deathwhisper", + NextAction::array(0, new NextAction("icc ranged position lady deathwhisper", ACTION_MOVE + 2), nullptr))); + + triggers.push_back(new TriggerNode("icc adds lady deathwhisper", + NextAction::array(0, new NextAction("icc adds lady deathwhisper", ACTION_RAID + 3), nullptr))); + + triggers.push_back(new TriggerNode("icc shade lady deathwhisper", + NextAction::array(0, new NextAction("icc shade lady deathwhisper", ACTION_MOVE + 5), nullptr))); + + //Gunship Battle + triggers.push_back(new TriggerNode("icc rotting frost giant tank position", + NextAction::array(0, new NextAction("icc rotting frost giant tank position", ACTION_RAID + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc gunship cannon near", NextAction::array(0, new NextAction("icc gunship enter cannon", ACTION_RAID + 2), nullptr))); - triggers.push_back( - new TriggerNode("icc in cannon", - NextAction::array(0, new NextAction("icc cannon fire", ACTION_RAID), nullptr))); -} \ No newline at end of file + triggers.push_back( new TriggerNode("icc in cannon", + NextAction::array(0, new NextAction("icc cannon fire", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode("icc gunship teleport ally", + NextAction::array(0, new NextAction("icc gunship teleport ally", ACTION_RAID + 4), nullptr))); + + triggers.push_back(new TriggerNode("icc gunship teleport horde", + NextAction::array(0, new NextAction("icc gunship teleport horde", ACTION_RAID + 4), nullptr))); + + //DBS + triggers.push_back(new TriggerNode("icc dbs tank position", + NextAction::array(0, new NextAction("icc dbs tank position", ACTION_RAID + 3), nullptr))); + + triggers.push_back(new TriggerNode("icc dbs main tank rune of blood", + NextAction::array(0, new NextAction("taunt spell", ACTION_EMERGENCY + 4), nullptr))); + + triggers.push_back(new TriggerNode("icc adds dbs", + NextAction::array(0, new NextAction("icc adds dbs", ACTION_RAID + 5), nullptr))); + + //DOGS + triggers.push_back(new TriggerNode("icc stinky precious main tank mortal wound", + NextAction::array(0, new NextAction("taunt spell", ACTION_EMERGENCY + 4), nullptr))); + + //FESTERGUT + triggers.push_back(new TriggerNode("icc festergut tank position", + NextAction::array(0, new NextAction("icc festergut tank position", ACTION_MOVE + 4), nullptr))); + + triggers.push_back(new TriggerNode("icc festergut main tank gastric bloat", + NextAction::array(0, new NextAction("taunt spell", ACTION_EMERGENCY + 6), nullptr))); + + triggers.push_back(new TriggerNode("icc festergut spore", + NextAction::array(0, new NextAction("icc festergut spore", ACTION_MOVE + 5), nullptr))); + + //ROTFACE + triggers.push_back(new TriggerNode("icc rotface tank position", + NextAction::array(0, new NextAction("icc rotface tank position", ACTION_RAID + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc rotface group position", + NextAction::array(0, new NextAction("icc rotface group position", ACTION_RAID + 6), nullptr))); + + triggers.push_back(new TriggerNode("icc rotface move away from explosion", + NextAction::array(0, new NextAction("icc rotface move away from explosion", ACTION_RAID +7), nullptr))); + + //PP + triggers.push_back(new TriggerNode("icc putricide volatile ooze", + NextAction::array(0, new NextAction("icc putricide volatile ooze", ACTION_EMERGENCY + 4), nullptr))); + + triggers.push_back(new TriggerNode("icc putricide gas cloud", + NextAction::array(0, new NextAction("icc putricide gas cloud", ACTION_EMERGENCY + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc putricide growing ooze puddle", + NextAction::array(0, new NextAction("icc putricide growing ooze puddle", ACTION_EMERGENCY + 3), nullptr))); + + triggers.push_back(new TriggerNode("icc putricide main tank mutated plague", + NextAction::array(0, new NextAction("taunt spell", ACTION_EMERGENCY + 6), nullptr))); + + triggers.push_back(new TriggerNode("icc putricide malleable goo", + NextAction::array(0, new NextAction("avoid malleable goo", ACTION_EMERGENCY + 2), nullptr))); + + //BPC + triggers.push_back(new TriggerNode("icc bpc keleseth tank", + NextAction::array(0, new NextAction("icc bpc keleseth tank", ACTION_EMERGENCY + 1), nullptr))); + + triggers.push_back(new TriggerNode("icc bpc nucleus", + NextAction::array(0, new NextAction("icc bpc nucleus", ACTION_EMERGENCY + 3), nullptr))); + + triggers.push_back(new TriggerNode("icc bpc main tank", + NextAction::array(0, new NextAction("icc bpc main tank", ACTION_EMERGENCY + 2), nullptr))); + + triggers.push_back(new TriggerNode("icc bpc empowered vortex", + NextAction::array(0, new NextAction("icc bpc empowered vortex", ACTION_INTERRUPT), nullptr))); + + //BQL + triggers.push_back(new TriggerNode("icc bql tank position", + NextAction::array(0, new NextAction("icc bql tank position", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode("icc bql pact of darkfallen", + NextAction::array(0, new NextAction("icc bql pact of darkfallen", ACTION_EMERGENCY +1), nullptr))); + + triggers.push_back(new TriggerNode("icc bql vampiric bite", + NextAction::array(0, new NextAction("icc bql vampiric bite", ACTION_EMERGENCY + 5), nullptr))); + + //VDW + triggers.push_back(new TriggerNode("icc valkyre spear", + NextAction::array(0, new NextAction("icc valkyre spear", ACTION_EMERGENCY + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc sister svalna", + NextAction::array(0, new NextAction("icc sister svalna", ACTION_RAID + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc valithria portal", + NextAction::array(0, new NextAction("icc valithria portal", ACTION_RAID + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc valithria heal", + NextAction::array(0, new NextAction("icc valithria heal", ACTION_RAID+1), nullptr))); + + triggers.push_back(new TriggerNode("icc valithria dream cloud", + NextAction::array(0, new NextAction("icc valithria dream cloud", ACTION_RAID + 4), nullptr))); + + //SINDRAGOSA + triggers.push_back(new TriggerNode("icc sindragosa tank position", + NextAction::array(0, new NextAction("icc sindragosa tank position", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("icc sindragosa frost beacon", + NextAction::array(0, new NextAction("icc sindragosa frost beacon", ACTION_RAID + 5), nullptr))); + + triggers.push_back(new TriggerNode("icc sindragosa blistering cold", + NextAction::array(0, new NextAction("icc sindragosa blistering cold", ACTION_EMERGENCY + 4), nullptr))); + + triggers.push_back(new TriggerNode("icc sindragosa unchained magic", + NextAction::array(0, new NextAction("icc sindragosa unchained magic", ACTION_RAID + 2), nullptr))); + + triggers.push_back(new TriggerNode("icc sindragosa chilled to the bone", + NextAction::array(0, new NextAction("icc sindragosa chilled to the bone", ACTION_RAID + 2), nullptr))); + + triggers.push_back(new TriggerNode("icc sindragosa mystic buffet", + NextAction::array(0, new NextAction("icc sindragosa mystic buffet", ACTION_RAID + 3), nullptr))); + + triggers.push_back(new TriggerNode("icc sindragosa main tank mystic buffet", + NextAction::array(0, new NextAction("taunt spell", ACTION_EMERGENCY + 3), nullptr))); + + triggers.push_back(new TriggerNode("icc sindragosa frost bomb", + NextAction::array(0, new NextAction("icc sindragosa frost bomb", ACTION_RAID + 7), nullptr))); + + triggers.push_back(new TriggerNode("icc sindragosa tank swap position", + NextAction::array(0, new NextAction("icc sindragosa tank swap position", ACTION_EMERGENCY + 2), nullptr))); + + //LICH KING + triggers.push_back(new TriggerNode("icc lich king necrotic plague", + NextAction::array(0, new NextAction("icc lich king necrotic plague", ACTION_EMERGENCY + 3), nullptr))); + + triggers.push_back(new TriggerNode("icc lich king winter", + NextAction::array(0, new NextAction("icc lich king winter", ACTION_RAID +1), nullptr))); + + triggers.push_back(new TriggerNode("icc lich king adds", + NextAction::array(0, new NextAction("icc lich king adds", ACTION_RAID +2), nullptr))); +} + +void RaidIccStrategy::InitMultipliers(std::vector& multipliers) +{ + multipliers.push_back(new IccLadyDeathwhisperMultiplier(botAI)); + multipliers.push_back(new IccAddsDbsMultiplier(botAI)); + multipliers.push_back(new IccDogsMultiplier(botAI)); + multipliers.push_back(new IccFestergutMultiplier(botAI)); + multipliers.push_back(new IccRotfaceMultiplier(botAI)); + //multipliers.push_back(new IccRotfaceGroupPositionMultiplier(botAI)); + multipliers.push_back(new IccAddsPutricideMultiplier(botAI)); + multipliers.push_back(new IccBpcAssistMultiplier(botAI)); + multipliers.push_back(new IccBqlPactOfDarkfallenMultiplier(botAI)); + multipliers.push_back(new IccBqlVampiricBiteMultiplier(botAI)); + multipliers.push_back(new IccValithriaDreamCloudMultiplier(botAI)); + multipliers.push_back(new IccSindragosaTankPositionMultiplier(botAI)); + multipliers.push_back(new IccSindragosaFrostBeaconMultiplier(botAI)); + //multipliers.push_back(new IccSindragosaFlyingMultiplier(botAI)); + multipliers.push_back(new IccSindragosaMysticBuffetMultiplier(botAI)); + multipliers.push_back(new IccSindragosaBlisteringColdPriorityMultiplier(botAI)); + multipliers.push_back(new IccSindragosaFrostBombMultiplier(botAI)); + multipliers.push_back(new IccLichKingNecroticPlagueMultiplier(botAI)); + multipliers.push_back(new IccLichKingAddsMultiplier(botAI)); +} diff --git a/src/strategy/raids/icecrown/RaidIccStrategy.h b/src/strategy/raids/icecrown/RaidIccStrategy.h index db72cecc9..53967c334 100644 --- a/src/strategy/raids/icecrown/RaidIccStrategy.h +++ b/src/strategy/raids/icecrown/RaidIccStrategy.h @@ -4,6 +4,7 @@ #include "AiObjectContext.h" #include "Multiplier.h" #include "Strategy.h" +#include "RaidIccMultipliers.h" class RaidIccStrategy : public Strategy { @@ -11,7 +12,7 @@ class RaidIccStrategy : public Strategy RaidIccStrategy(PlayerbotAI* ai) : Strategy(ai) {} virtual std::string const getName() override { return "icc"; } virtual void InitTriggers(std::vector& triggers) override; - // virtual void InitMultipliers(std::vector &multipliers) override; + virtual void InitMultipliers(std::vector &multipliers) override; }; #endif diff --git a/src/strategy/raids/icecrown/RaidIccTriggerContext.h b/src/strategy/raids/icecrown/RaidIccTriggerContext.h index 4deac391c..63cc39c72 100644 --- a/src/strategy/raids/icecrown/RaidIccTriggerContext.h +++ b/src/strategy/raids/icecrown/RaidIccTriggerContext.h @@ -10,13 +10,110 @@ class RaidIccTriggerContext : public NamedObjectContext public: RaidIccTriggerContext() { + creators["icc lm tank position"] = &RaidIccTriggerContext::icc_lm_tank_position; + creators["icc spike near"] = &RaidIccTriggerContext::icc_spike_near; + creators["icc dark reckoning"] = &RaidIccTriggerContext::icc_dark_reckoning; + creators["icc ranged position lady deathwhisper"] = &RaidIccTriggerContext::icc_ranged_position_lady_deathwhisper; + creators["icc adds lady deathwhisper"] = &RaidIccTriggerContext::icc_adds_lady_deathwhisper; + creators["icc shade lady deathwhisper"] = &RaidIccTriggerContext::icc_shade_lady_deathwhisper; + creators["icc rotting frost giant tank position"] = &RaidIccTriggerContext::icc_rotting_frost_giant_tank_position; creators["icc in cannon"] = &RaidIccTriggerContext::icc_in_cannon; creators["icc gunship cannon near"] = &RaidIccTriggerContext::icc_gunship_cannon_near; + creators["icc gunship teleport ally"] = &RaidIccTriggerContext::icc_gunship_teleport_ally; + creators["icc gunship teleport horde"] = &RaidIccTriggerContext::icc_gunship_teleport_horde; + creators["icc dbs tank position"] = &RaidIccTriggerContext::icc_dbs_tank_position; + creators["icc dbs main tank rune of blood"] = &RaidIccTriggerContext::icc_dbs_main_tank_rune_of_blood; + creators["icc adds dbs"] = &RaidIccTriggerContext::icc_adds_dbs; + creators["icc stinky precious main tank mortal wound"] = &RaidIccTriggerContext::icc_stinky_precious_main_tank_mortal_wound; + creators["icc festergut tank position"] = &RaidIccTriggerContext::icc_festergut_tank_position; + creators["icc festergut main tank gastric bloat"] = &RaidIccTriggerContext::icc_festergut_main_tank_gastric_bloat; + creators["icc festergut spore"] = &RaidIccTriggerContext::icc_festergut_spore; + creators["icc rotface tank position"] = &RaidIccTriggerContext::icc_rotface_tank_position; + creators["icc rotface group position"] = &RaidIccTriggerContext::icc_rotface_group_position; + creators["icc rotface move away from explosion"] = &RaidIccTriggerContext::icc_rotface_move_away_from_explosion; + creators["icc putricide volatile ooze"] = &RaidIccTriggerContext::icc_putricide_volatile_ooze; + creators["icc putricide gas cloud"] = &RaidIccTriggerContext::icc_putricide_gas_cloud; + creators["icc putricide growing ooze puddle"] = &RaidIccTriggerContext::icc_putricide_growing_ooze_puddle; + creators["icc putricide main tank mutated plague"] = &RaidIccTriggerContext::icc_putricide_main_tank_mutated_plague; + creators["icc putricide malleable goo"] = &RaidIccTriggerContext::icc_putricide_malleable_goo; + creators["icc bpc keleseth tank"] = &RaidIccTriggerContext::icc_bpc_keleseth_tank; + creators["icc bpc nucleus"] = &RaidIccTriggerContext::icc_bpc_nucleus; + creators["icc bpc main tank"] = &RaidIccTriggerContext::icc_bpc_main_tank; + creators["icc bpc empowered vortex"] = &RaidIccTriggerContext::icc_bpc_empowered_vortex; + creators["icc bql tank position"] = &RaidIccTriggerContext::icc_bql_tank_position; + creators["icc bql pact of darkfallen"] = &RaidIccTriggerContext::icc_bql_pact_of_darkfallen; + creators["icc bql vampiric bite"] = &RaidIccTriggerContext::icc_bql_vampiric_bite; + creators["icc valkyre spear"] = &RaidIccTriggerContext::icc_valkyre_spear; + creators["icc sister svalna"] = &RaidIccTriggerContext::icc_sister_svalna; + creators["icc valithria portal"] = &RaidIccTriggerContext::icc_valithria_portal; + creators["icc valithria heal"] = &RaidIccTriggerContext::icc_valithria_heal; + creators["icc valithria dream cloud"] = &RaidIccTriggerContext::icc_valithria_dream_cloud; + creators["icc sindragosa tank position"] = &RaidIccTriggerContext::icc_sindragosa_tank_position; + creators["icc sindragosa frost beacon"] = &RaidIccTriggerContext::icc_sindragosa_frost_beacon; + creators["icc sindragosa blistering cold"] = &RaidIccTriggerContext::icc_sindragosa_blistering_cold; + creators["icc sindragosa unchained magic"] = &RaidIccTriggerContext::icc_sindragosa_unchained_magic; + creators["icc sindragosa chilled to the bone"] = &RaidIccTriggerContext::icc_sindragosa_chilled_to_the_bone; + creators["icc sindragosa mystic buffet"] = &RaidIccTriggerContext::icc_sindragosa_mystic_buffet; + creators["icc sindragosa main tank mystic buffet"] = &RaidIccTriggerContext::icc_sindragosa_main_tank_mystic_buffet; + creators["icc sindragosa frost bomb"] = &RaidIccTriggerContext::icc_sindragosa_frost_bomb; + creators["icc sindragosa tank swap position"] = &RaidIccTriggerContext::icc_sindragosa_tank_swap_position; + creators["icc lich king necrotic plague"] = &RaidIccTriggerContext::icc_lich_king_necrotic_plague; + creators["icc lich king winter"] = &RaidIccTriggerContext::icc_lich_king_winter; + creators["icc lich king adds"] = &RaidIccTriggerContext::icc_lich_king_adds; } private: + static Trigger* icc_lm_tank_position(PlayerbotAI* ai) { return new IccLmTankPositionTrigger(ai); } + static Trigger* icc_spike_near(PlayerbotAI* ai) { return new IccSpikeNearTrigger(ai); } + static Trigger* icc_dark_reckoning(PlayerbotAI* ai) { return new IccDarkReckoningTrigger(ai); } + static Trigger* icc_ranged_position_lady_deathwhisper(PlayerbotAI* ai) { return new IccRangedPositionLadyDeathwhisperTrigger(ai); } + static Trigger* icc_adds_lady_deathwhisper(PlayerbotAI* ai) { return new IccAddsLadyDeathwhisperTrigger(ai); } + static Trigger* icc_shade_lady_deathwhisper(PlayerbotAI* ai) { return new IccShadeLadyDeathwhisperTrigger(ai); } + static Trigger* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionTrigger(ai); } static Trigger* icc_in_cannon(PlayerbotAI* ai) { return new IccInCannonTrigger(ai); } static Trigger* icc_gunship_cannon_near(PlayerbotAI* ai) { return new IccGunshipCannonNearTrigger(ai); } + static Trigger* icc_gunship_teleport_ally(PlayerbotAI* ai) { return new IccGunshipTeleportAllyTrigger(ai); } + static Trigger* icc_gunship_teleport_horde(PlayerbotAI* ai) { return new IccGunshipTeleportHordeTrigger(ai); } + static Trigger* icc_dbs_tank_position(PlayerbotAI* ai) { return new IccDbsTankPositionTrigger(ai); } + static Trigger* icc_dbs_main_tank_rune_of_blood(PlayerbotAI* ai) { return new IccDbsMainTankRuneOfBloodTrigger(ai); } + static Trigger* icc_adds_dbs(PlayerbotAI* ai) { return new IccAddsDbsTrigger(ai); } + static Trigger* icc_stinky_precious_main_tank_mortal_wound(PlayerbotAI* ai) { return new IccStinkyPreciousMainTankMortalWoundTrigger(ai); } + static Trigger* icc_festergut_tank_position(PlayerbotAI* ai) { return new IccFestergutTankPositionTrigger(ai); } + static Trigger* icc_festergut_main_tank_gastric_bloat(PlayerbotAI* ai) { return new IccFestergutMainTankGastricBloatTrigger(ai); } + static Trigger* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeTrigger(ai); } + static Trigger* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionTrigger(ai); } + static Trigger* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionTrigger(ai); } + static Trigger* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionTrigger(ai); } + static Trigger* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeTrigger(ai); } + static Trigger* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudTrigger(ai); } + static Trigger* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleTrigger(ai); } + static Trigger* icc_putricide_main_tank_mutated_plague(PlayerbotAI* ai) { return new IccPutricideMainTankMutatedPlagueTrigger(ai); } + static Trigger* icc_putricide_malleable_goo(PlayerbotAI* ai) { return new IccPutricideMalleableGooTrigger(ai); } + static Trigger* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankTrigger(ai); } + static Trigger* icc_bpc_nucleus(PlayerbotAI* ai) { return new IccBpcNucleusTrigger(ai); } + static Trigger* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankTrigger(ai); } + static Trigger* icc_bpc_empowered_vortex(PlayerbotAI* ai) { return new IccBpcEmpoweredVortexTrigger(ai); } + static Trigger* icc_bql_tank_position(PlayerbotAI* ai) { return new IccBqlTankPositionTrigger(ai); } + static Trigger* icc_bql_pact_of_darkfallen(PlayerbotAI* ai) { return new IccBqlPactOfDarkfallenTrigger(ai); } + static Trigger* icc_bql_vampiric_bite(PlayerbotAI* ai) { return new IccBqlVampiricBiteTrigger(ai); } + static Trigger* icc_valkyre_spear(PlayerbotAI* ai) { return new IccValkyreSpearTrigger(ai); } + static Trigger* icc_sister_svalna(PlayerbotAI* ai) { return new IccSisterSvalnaTrigger(ai); } + static Trigger* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalTrigger(ai); } + static Trigger* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealTrigger(ai); } + static Trigger* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudTrigger(ai); } + static Trigger* icc_sindragosa_tank_position(PlayerbotAI* ai) { return new IccSindragosaTankPositionTrigger(ai); } + static Trigger* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconTrigger(ai); } + static Trigger* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdTrigger(ai); } + static Trigger* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicTrigger(ai); } + static Trigger* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneTrigger(ai); } + static Trigger* icc_sindragosa_mystic_buffet(PlayerbotAI* ai) { return new IccSindragosaMysticBuffetTrigger(ai); } + static Trigger* icc_sindragosa_main_tank_mystic_buffet(PlayerbotAI* ai) { return new IccSindragosaMainTankMysticBuffetTrigger(ai); } + static Trigger* icc_sindragosa_frost_bomb(PlayerbotAI* ai) { return new IccSindragosaFrostBombTrigger(ai); } + static Trigger* icc_sindragosa_tank_swap_position(PlayerbotAI* ai) { return new IccSindragosaTankSwapPositionTrigger(ai); } + static Trigger* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueTrigger(ai); } + static Trigger* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterTrigger(ai); } + static Trigger* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsTrigger(ai); } + }; #endif diff --git a/src/strategy/raids/icecrown/RaidIccTriggers.cpp b/src/strategy/raids/icecrown/RaidIccTriggers.cpp index 4261718ee..01d03e589 100644 --- a/src/strategy/raids/icecrown/RaidIccTriggers.cpp +++ b/src/strategy/raids/icecrown/RaidIccTriggers.cpp @@ -1,4 +1,107 @@ #include "RaidIccTriggers.h" +#include "RaidIccActions.h" + +#include "PlayerbotAIConfig.h" +#include "GenericTriggers.h" +#include "DungeonStrategyUtils.h" +#include "EventMap.h" +#include "Playerbots.h" +#include "ScriptedCreature.h" +#include "Trigger.h" + +//Lord Marrogwar +bool IccLmTankPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar"); + if (!boss) { return false; } + + return botAI->IsTank(bot); +} + +bool IccSpikeNearTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar"); + if (!boss) + return false; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit) + { + if (unit->GetEntry() == 36619 || unit->GetEntry() == 38711 || unit->GetEntry() == 38712 ) //spike ID + { + if (unit->GetDistance(bot) <= 20.0f) + { + return botAI->IsDps(bot); + } + + return botAI->IsRangedDps(bot); + } + } + } + + return false; +} + +//Lady Deathwhisper +bool IccDarkReckoningTrigger::IsActive() +{ + Unit* add = AI_VALUE2(Unit*, "find target", "deathspeaker high priest"); + if (add || bot->HasAura(69483)) + return true; + + return false; +} + +bool IccRangedPositionLadyDeathwhisperTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss) + return false; + + return (botAI->IsRangedDps(bot) || botAI->IsHeal(bot)); +} + +bool IccAddsLadyDeathwhisperTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss) { return false; } + + return true; +} + +bool IccShadeLadyDeathwhisperTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper"); + if (!boss) { return false; } + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit) + { + if (unit->GetEntry() == 38222 ) //vengeful sahde ID + { + return true; + } + } + } + + return false; +} + + +//Gunship Battle +bool IccRottingFrostGiantTankPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "rotting frost giant"); + if (!boss) { return false; } + + return true; +} bool IccInCannonTrigger::IsActive() { @@ -27,3 +130,620 @@ bool IccGunshipCannonNearTrigger::IsActive() return true; } + +bool IccGunshipTeleportAllyTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "kor'kron battle-mage"); + if (!boss) { return false; } + + return true; +} + +bool IccGunshipTeleportHordeTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "skybreaker sorcerer"); + if (!boss) { return false; } + + return true; +} + +//DBS +bool IccDbsTankPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); + if (!boss) { return false; } + + return true; +} + +bool IccDbsMainTankRuneOfBloodTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); + + if (!boss) + return false; + + if (!botAI->IsAssistTankOfIndex(bot, 0)) + return false; + + Unit* mt = AI_VALUE(Unit*, "main tank"); + if (!mt) + return false; + + Aura* aura = botAI->GetAura("rune of blood", mt); + if (!aura) + return false; + + return true; +} + +bool IccAddsDbsTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang"); + if (!boss) { return false; } + + return true; +} + +//DOGS +bool IccStinkyPreciousMainTankMortalWoundTrigger::IsActive() +{ + bool bossPresent = false; + if (AI_VALUE2(Unit*, "find target", "stinky") || AI_VALUE2(Unit*, "find target", "precious")) + bossPresent = true; + + if (!bossPresent) + return false; + + if (!botAI->IsAssistTankOfIndex(bot, 0)) + { + return false; + } + Unit* mt = AI_VALUE(Unit*, "main tank"); + if (!mt) + { + return false; + } + Aura* aura = botAI->GetAura("mortal wound", mt, false, true); + if (!aura || aura->GetStackAmount() < 8) + { + return false; + } + return true; +} + +//FESTERGUT +bool IccFestergutTankPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); + if (!boss || !(botAI->IsTank(bot) || botAI->IsRanged(bot) || botAI->IsHeal(bot))) + return false; + + return true; +} + +bool IccFestergutMainTankGastricBloatTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); + if (!boss) + { + return false; + } + if (!botAI->IsAssistTankOfIndex(bot, 0)) + { + return false; + } + Unit* mt = AI_VALUE(Unit*, "main tank"); + if (!mt) + { + return false; + } + Aura* aura = botAI->GetAura("Gastric Bloat", mt, false, true); + if (!aura || aura->GetStackAmount() < 6) + { + return false; + } + return true; +} + +bool IccFestergutSporeTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "festergut"); + if (!boss || botAI->IsTank(bot)) + return false; + + // Check for spore aura (ID: 69279) on any bot in the group + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member) + continue; + + if (member->HasAura(69279)) // Spore aura ID + return true; + } + + return false; +} + +//ROTFACE +bool IccRotfaceTankPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "rotface"); + if (!boss || !(botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot))) { return false; } + + return true; +} + +bool IccRotfaceGroupPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "rotface"); + if (!boss) { return false; } + + return true; +} + +bool IccRotfaceMoveAwayFromExplosionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "big ooze"); + if (!boss) { return false; } + + return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(69839); +} + +//PP + +bool IccPutricideGrowingOozePuddleTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + bool botHasAura = botAI->HasAura("Gaseous Bloat", bot); + + if (!boss || botHasAura) + return false; + + return true; +} + +bool IccPutricideVolatileOozeTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "volatile ooze"); + if (!boss) { return false; } + + return true; +} + +bool IccPutricideGasCloudTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "gas cloud"); + if (!boss) { return false; } + + return true; +} + +bool IccPutricideMainTankMutatedPlagueTrigger::IsActive() +{ + bool bossPresent = false; + if (AI_VALUE2(Unit*, "find target", "professor putricide")) + bossPresent = true; + + if (!bossPresent) + return false; + + if (!botAI->IsAssistTankOfIndex(bot, 0)) + { + return false; + } + Unit* mt = AI_VALUE(Unit*, "main tank"); + if (!mt) + { + return false; + } + Aura* aura = botAI->GetAura("Mutated Plague", mt, false, true); + if (!aura || aura->GetStackAmount() < 4) + { + return false; + } + return true; +} + +bool IccPutricideMalleableGooTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide"); + if (!boss) + return false; + + Unit* boss1 = AI_VALUE2(Unit*, "find target", "volatile ooze"); + if (boss1) + return false; + + Unit* boss2 = AI_VALUE2(Unit*, "find target", "gas cloud"); + if (boss2) + return false; + + return true; +} + +//BPC +bool IccBpcKelesethTankTrigger::IsActive() +{ + if (!botAI->IsAssistTank(bot)) + return false; + + // First priority is to check for nucleuses that need to be picked up + 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 false; // Don't tank Keleseth if there's a nucleus to grab + } + } + + Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth"); + if (!boss || boss->GetEntry() != 37972) // Verify it's actually Keleseth + return false; + + return true; +} + +bool IccBpcNucleusTrigger::IsActive() +{ + 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 true; // Found a nucleus that needs to be picked up + } + } + + return false; +} + +bool IccBpcMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram"); + + return valanar != nullptr || taldaram != nullptr; +} + +bool IccBpcEmpoweredVortexTrigger::IsActive() +{ + // Tanks should ignore this mechanic + if (botAI->IsMainTank(bot) || botAI->IsAssistTank(bot)) + return false; + + Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + if (!valanar || !valanar->IsAlive()) + return false; + + // For ranged, spread whenever Valanar is empowered + if (botAI->IsRanged(bot)) + return valanar->HasAura(70952); // Invocation of Blood + + // For melee, only spread during vortex cast + if (valanar->HasAura(70952) && // Invocation of Blood + valanar->GetCurrentSpell(CURRENT_GENERIC_SPELL) && + valanar->GetCurrentSpell(CURRENT_GENERIC_SPELL)->m_spellInfo->Id == 72039) + { + return true; + } + + return false; +} + +//BQL +bool IccBqlTankPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); + if (!boss || !(botAI->IsTank(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTank(bot) || botAI->IsRanged(bot))) + return false; + + return true; +} + +bool IccBqlPactOfDarkfallenTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); + if (!boss || !bot->HasAura(71340)) + return false; + + return true; +} + +bool IccBqlVampiricBiteTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); + if (!boss) + return false; + + // Only trigger when bot has Frenzied Bloodthirst + if (!(bot->HasAura(70877) || bot->HasAura(71474))) + return false; + + return true; +} + +//VDW +bool IccValkyreSpearTrigger::IsActive() +{ + // Check if there's a spear nearby + if (Creature* spear = bot->FindNearestCreature(38248, 100.0f)) + return true; + + return false; +} + +bool IccSisterSvalnaTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sister svalna"); + if (!boss) + { + return false; + } + return true; +} + +bool IccValithriaPortalTrigger::IsActive() +{ + // Only healers should use portals + if (!botAI->IsHeal(bot) || bot->HasAura(70766)) + return false; + + // Find the nearest portal creature + Creature* portal = bot->FindNearestCreature(37945, 100.0f); // Only check within 10 yards + Creature* portal2 = bot->FindNearestCreature(38430, 100.0f); // Only check within 10 yards + + return portal || portal2; +} + +bool IccValithriaHealTrigger::IsActive() +{ + // Only healers should use healing + if (!botAI->IsHeal(bot) || bot->HasAura(70766)) + return false; + + // If no portal is found within 100 yards, we should heal + if (!bot->FindNearestCreature(37945, 100.0f) && !bot->FindNearestCreature(38430, 100.0f)) + return true; + + // If portal is nearby (10 yards), don't heal + if (bot->FindNearestCreature(37945, 20.0f) || bot->FindNearestCreature(38430, 10.0f)) + return false; + + // If portal is far but within 100 yards, heal while moving to it + return true; +} + +bool IccValithriaDreamCloudTrigger::IsActive() +{ + // Only active if we're in dream state + if (!bot->HasAura(70766)) + return false; + + // Find nearest cloud of either type + Creature* dreamCloud = bot->FindNearestCreature(37985, 100.0f); + Creature* nightmareCloud = bot->FindNearestCreature(38421, 100.0f); + + return (dreamCloud || nightmareCloud); +} + +//SINDRAGOSA +bool IccSindragosaTankPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss || bot->HasAura(70126) /*|| bot->HasAura(69762)*/ || boss->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY)) + return false; + + return true; +} + +bool IccSindragosaFrostBeaconTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); ++itr) + { + Player* member = ObjectAccessor::FindPlayer(itr->guid); + if (!member || !member->IsAlive()) + continue; + + if (member->HasAura(70126)) // If any player has Frost Beacon, keep trigger active + return true; + } + return false; +} + +bool IccSindragosaBlisteringColdTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + if (botAI->IsMainTank(bot) || botAI->IsAssistTank(bot) || botAI->IsTank(bot)) + return false; + + // Don't move if any bot in group has ice tomb + Group* group = bot->GetGroup(); + if (!group) + return false; + + bool isCasting = boss->HasUnitState(UNIT_STATE_CASTING); + bool isBlisteringCold = boss->FindCurrentSpellBySpellId(70123) || boss->FindCurrentSpellBySpellId(71047) || + boss->FindCurrentSpellBySpellId(71048) || boss->FindCurrentSpellBySpellId(71049); + + return isCasting && isBlisteringCold; +} + +bool IccSindragosaUnchainedMagicTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss || (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(70123))) + return false; + + return bot->HasAura(69762); +} + +bool IccSindragosaChilledToTheBoneTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss || (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(70123))) + return false; + + return bot->HasAura(70106); +} + +bool IccSindragosaMysticBuffetTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + Aura* aura = bot->GetAura(70127); + Aura* aura2 = bot->GetAura(72528); + if (!aura && !aura2) + return false; + + if (bot->HasAura(70126)) // Ice Block + return false; + + if ((aura && aura->GetStackAmount() >= 3) || (aura2 && aura2->GetStackAmount() >= 3)) + return true; + + return false; +} + +bool IccSindragosaMainTankMysticBuffetTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) + return false; + + if (!botAI->IsAssistTankOfIndex(bot, 0)) + { + return false; + } + Unit* mt = AI_VALUE(Unit*, "main tank"); + if (!mt) + { + return false; + } + Aura* aura = botAI->GetAura("mystic buffet", mt, false, true); + if (!aura || aura->GetStackAmount() < 8) + { + return false; + } + + // Only taunt if we're in position + float distToTankPos = bot->GetExactDist2d(ICC_SINDRAGOSA_TANK_POSITION); + if (distToTankPos > 3.0f) + { + return false; + } + + return true; +} + +bool IccSindragosaTankSwapPositionTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa"); + if (!boss) return false; + + if (!botAI->IsAssistTankOfIndex(bot, 0)) return false; + + // Don't tank swap if we have frost beacon + if (bot->HasAura(70126)) return false; // Frost Beacon + + // First check our own stacks - don't try to tank if we have too many + Aura* selfAura = botAI->GetAura("mystic buffet", bot, false, true); + if (selfAura && selfAura->GetStackAmount() >= 8) return false; + + // Check if main tank has high stacks + Unit* mt = AI_VALUE(Unit*, "main tank"); + if (!mt) return false; + + Aura* aura = botAI->GetAura("mystic buffet", mt, false, true); + if (!aura) return false; + + uint32 stacks = aura->GetStackAmount(); + return (stacks >= 7); // Start moving at 7 stacks +} + +bool IccSindragosaFrostBombTrigger::IsActive() +{ + if (!bot->IsAlive() || bot->HasAura(70157)) // Skip if dead or in Ice Tomb + return false; + + // Simply check if frost bomb marker exists + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (!unit || !unit->IsAlive()) + continue; + + if (unit->HasAura(70022)) // Frost bomb visual + return true; + } + + return false; +} + +bool IccLichKingNecroticPlagueTrigger::IsActive() +{ + if (!bot || !bot->IsAlive()) + return false; + + // Check for plague by name instead of ID + bool hasPlague = botAI->HasAura("Necrotic Plague", bot); + + return hasPlague; +} + +bool IccLichKingWinterTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss) + return false; + + // Check for either Remorseless Winter + bool hasWinterAura = boss->HasAura(72259) || boss->HasAura(74273) || boss->HasAura(74274) || boss->HasAura(74275); + bool hasWinter2Aura = boss->HasAura(68981) || boss->HasAura(74270) || boss->HasAura(74271) || boss->HasAura(74272); + + if (!hasWinterAura && !hasWinter2Aura) + return false; + + return true; +} + +bool IccLichKingAddsTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (!boss) + return false; + + return true; +} \ No newline at end of file diff --git a/src/strategy/raids/icecrown/RaidIccTriggers.h b/src/strategy/raids/icecrown/RaidIccTriggers.h index 9d71809df..604830131 100644 --- a/src/strategy/raids/icecrown/RaidIccTriggers.h +++ b/src/strategy/raids/icecrown/RaidIccTriggers.h @@ -5,6 +5,57 @@ #include "Playerbots.h" #include "Trigger.h" +//Lord Marrowgar +class IccLmTankPositionTrigger : public Trigger +{ +public: + IccLmTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lm tank position") {} + bool IsActive() override; +}; + +class IccSpikeNearTrigger : public Trigger +{ +public: + IccSpikeNearTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc spike near") {} + bool IsActive() override; +}; + +//Lady Deathwhisper +class IccDarkReckoningTrigger : public Trigger +{ +public: + IccDarkReckoningTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc dark reckoning") {} + bool IsActive() override; +}; + +class IccRangedPositionLadyDeathwhisperTrigger : public Trigger +{ +public: + IccRangedPositionLadyDeathwhisperTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc ranged position lady deathwhisper") {} + bool IsActive() override; +}; + +class IccAddsLadyDeathwhisperTrigger : public Trigger +{ +public: + IccAddsLadyDeathwhisperTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc adds lady deathwhisper") {} + bool IsActive() override; +}; + +class IccShadeLadyDeathwhisperTrigger : public Trigger +{ +public: + IccShadeLadyDeathwhisperTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc shade lady deathwhisper") {} + bool IsActive() override; +}; + +//Gunship Battle +class IccRottingFrostGiantTankPositionTrigger : public Trigger +{ +public: + IccRottingFrostGiantTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotting frost giant tank position") {} + bool IsActive() override; +}; class IccInCannonTrigger : public Trigger { @@ -20,4 +71,305 @@ class IccGunshipCannonNearTrigger : public Trigger bool IsActive() override; }; +class IccGunshipTeleportAllyTrigger : public Trigger +{ +public: + IccGunshipTeleportAllyTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship teleport ally") {} + bool IsActive() override; +}; + +class IccGunshipTeleportHordeTrigger : public Trigger +{ +public: + IccGunshipTeleportHordeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship teleport horde") {} + bool IsActive() override; +}; + +//DBS +class IccDbsTankPositionTrigger : public Trigger +{ +public: + IccDbsTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc dbs tank position") {} + bool IsActive() override; +}; + +class IccDbsMainTankRuneOfBloodTrigger : public Trigger +{ +public: + IccDbsMainTankRuneOfBloodTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc dbs main tank rune of blood") {} + bool IsActive() override; +}; + +class IccAddsDbsTrigger : public Trigger +{ +public: + IccAddsDbsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc adds dbs") {} + bool IsActive() override; +}; + +//DOGS +class IccStinkyPreciousMainTankMortalWoundTrigger : public Trigger +{ +public: + IccStinkyPreciousMainTankMortalWoundTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc stinky precious main tank mortal wound") {} + bool IsActive() override; +}; + +//FESTERGUT +class IccFestergutTankPositionTrigger : public Trigger +{ +public: + IccFestergutTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc festergut tank position") {} + bool IsActive() override; +}; + +class IccFestergutMainTankGastricBloatTrigger : public Trigger +{ +public: + IccFestergutMainTankGastricBloatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc festergut main tank gastric bloat") {} + bool IsActive() override; +}; + +class IccFestergutSporeTrigger : public Trigger +{ +public: + IccFestergutSporeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc festergut spore") {} + bool IsActive() override; +}; + +//ROTFACE +class IccRotfaceTankPositionTrigger : public Trigger +{ +public: + IccRotfaceTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotface tank position") {} + bool IsActive() override; +}; + +class IccRotfaceGroupPositionTrigger : public Trigger +{ +public: + IccRotfaceGroupPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotface group position") {} + bool IsActive() override; +}; + + +class IccRotfaceMoveAwayFromExplosionTrigger : public Trigger +{ +public: + IccRotfaceMoveAwayFromExplosionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotface move away from explosion") {} + bool IsActive() override; +}; + +//PP +class IccPutricideVolatileOozeTrigger : public Trigger +{ +public: + IccPutricideVolatileOozeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide volatile ooze") {} + bool IsActive() override; +}; + +class IccPutricideGasCloudTrigger : public Trigger +{ +public: + IccPutricideGasCloudTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide gas cloud") {} + bool IsActive() override; +}; + + +class IccPutricideGrowingOozePuddleTrigger : public Trigger +{ +public: + IccPutricideGrowingOozePuddleTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide growing ooze puddle") {} + bool IsActive() override; +}; + +class IccPutricideMainTankMutatedPlagueTrigger : public Trigger +{ +public: + IccPutricideMainTankMutatedPlagueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide main tank mutated plague") {} + bool IsActive() override; +}; + +class IccPutricideMalleableGooTrigger : public Trigger +{ +public: + IccPutricideMalleableGooTrigger(PlayerbotAI* ai) : Trigger(ai, "icc putricide malleable goo") {} + bool IsActive() override; +}; + +//BPC +class IccBpcKelesethTankTrigger : public Trigger +{ +public: + IccBpcKelesethTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc keleseth tank") {} + bool IsActive() override; +}; + +class IccBpcNucleusTrigger : public Trigger +{ +public: + IccBpcNucleusTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc nucleus") {} + bool IsActive() override; +}; + +class IccBpcMainTankTrigger : public Trigger +{ +public: + IccBpcMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc main tank") {} + bool IsActive() override; +}; + +class IccBpcEmpoweredVortexTrigger : public Trigger +{ +public: + IccBpcEmpoweredVortexTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc empowered vortex") {} + bool IsActive() override; +}; + +//Bql +class IccBqlTankPositionTrigger : public Trigger +{ +public: + IccBqlTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bql tank position") {} + bool IsActive() override; +}; + +class IccBqlPactOfDarkfallenTrigger : public Trigger +{ +public: + IccBqlPactOfDarkfallenTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bql pact of darkfallen") {} + bool IsActive() override; +}; + +class IccBqlVampiricBiteTrigger : public Trigger +{ +public: + IccBqlVampiricBiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bql vampiric bite") {} + bool IsActive() override; +}; + +//VDW +class IccValkyreSpearTrigger : public Trigger +{ +public: + IccValkyreSpearTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valkyre spear") {} + bool IsActive() override; +}; + +class IccSisterSvalnaTrigger : public Trigger +{ +public: + IccSisterSvalnaTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sister svalna") {} + bool IsActive() override; +}; + +class IccValithriaPortalTrigger : public Trigger +{ +public: + IccValithriaPortalTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria portal") {} + bool IsActive() override; +}; + +class IccValithriaHealTrigger : public Trigger +{ +public: + IccValithriaHealTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria heal") {} + bool IsActive() override; +}; + +class IccValithriaDreamCloudTrigger : public Trigger +{ +public: + IccValithriaDreamCloudTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria dream cloud") {} + bool IsActive() override; +}; + + +//SINDRAGOSA +class IccSindragosaTankPositionTrigger : public Trigger +{ +public: + IccSindragosaTankPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa tank position") {} + bool IsActive() override; +}; + +class IccSindragosaFrostBeaconTrigger : public Trigger +{ +public: + IccSindragosaFrostBeaconTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa frost beacon") {} + bool IsActive() override; +}; + +class IccSindragosaBlisteringColdTrigger : public Trigger +{ +public: + IccSindragosaBlisteringColdTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa blistering cold") {} + bool IsActive() override; +}; + +class IccSindragosaUnchainedMagicTrigger : public Trigger +{ +public: + IccSindragosaUnchainedMagicTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa unchained magic") {} + bool IsActive() override; +}; + +class IccSindragosaChilledToTheBoneTrigger : public Trigger +{ +public: + IccSindragosaChilledToTheBoneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa chilled to the bone") {} + bool IsActive() override; +}; + +class IccSindragosaMysticBuffetTrigger : public Trigger +{ +public: + IccSindragosaMysticBuffetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa mystic buffet") {} + bool IsActive() override; +}; + +class IccSindragosaMainTankMysticBuffetTrigger : public Trigger +{ +public: + IccSindragosaMainTankMysticBuffetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa main tank mystic buffet") {} + bool IsActive() override; +}; + +class IccSindragosaTankSwapPositionTrigger : public Trigger +{ +public: + IccSindragosaTankSwapPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa tank swap position") {} + bool IsActive() override; +}; + +class IccSindragosaFrostBombTrigger : public Trigger +{ +public: + IccSindragosaFrostBombTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa frost bomb") {} + bool IsActive() override; +}; + + +//LICH KING +class IccLichKingNecroticPlagueTrigger : public Trigger +{ +public: + IccLichKingNecroticPlagueTrigger(PlayerbotAI* ai) : Trigger(ai, "icc lich king necrotic plague") {} + bool IsActive() override; +}; + +class IccLichKingWinterTrigger : public Trigger +{ +public: + IccLichKingWinterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lich king winter") {} + bool IsActive() override; +}; + +class IccLichKingAddsTrigger : public Trigger +{ +public: + IccLichKingAddsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lich king adds") {} + bool IsActive() override; +}; + #endif diff --git a/src/strategy/raids/obsidiansanctum/RaidOsActions.cpp b/src/strategy/raids/obsidiansanctum/RaidOsActions.cpp index 2ee68bca4..724b91902 100644 --- a/src/strategy/raids/obsidiansanctum/RaidOsActions.cpp +++ b/src/strategy/raids/obsidiansanctum/RaidOsActions.cpp @@ -243,4 +243,4 @@ bool ExitTwilightPortalAction::Execute(Event event) bot->GetSession()->HandleGameObjectUseOpcode(data1); return true; -} +} \ No newline at end of file