diff --git a/src/strategy/raids/icecrown/RaidIccActionContext.h b/src/strategy/raids/icecrown/RaidIccActionContext.h index fab7ae8f..18097e6f 100644 --- a/src/strategy/raids/icecrown/RaidIccActionContext.h +++ b/src/strategy/raids/icecrown/RaidIccActionContext.h @@ -36,6 +36,7 @@ public: 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 bpc kinetic bomb"] = &RaidIccActionContext::icc_bpc_kinetic_bomb; 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; @@ -85,6 +86,7 @@ private: 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_bpc_kinetic_bomb(PlayerbotAI* ai) { return new IccBpcKineticBombAction(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); } diff --git a/src/strategy/raids/icecrown/RaidIccActions.cpp b/src/strategy/raids/icecrown/RaidIccActions.cpp index 969a6a69..5a7d824c 100644 --- a/src/strategy/raids/icecrown/RaidIccActions.cpp +++ b/src/strategy/raids/icecrown/RaidIccActions.cpp @@ -1,4 +1,6 @@ #include "RaidIccActions.h" +#include "strategy/values/NearestNpcsValue.h" +#include "ObjectAccessor.h" #include "RaidIccStrategy.h" #include "Playerbots.h" #include "Timer.h" @@ -1426,22 +1428,23 @@ bool IccBpcMainTankAction::Execute(Event event) 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); + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram"); - Unit* currentTarget = AI_VALUE(Unit*, "current target"); - // Keep current prince if we have one - if (currentTarget && (currentTarget == valanar || currentTarget == taldaram)) - return Attack(currentTarget); - - // Pick a new prince that isn't targeting us - if (valanar && (!valanar->GetVictim() || valanar->GetVictim() != bot)) + // Attack any prince that's not targeting us + if (valanar && valanar->IsAlive() && (!valanar->GetVictim() || valanar->GetVictim() != bot)) return Attack(valanar); - if (taldaram && (!taldaram->GetVictim() || taldaram->GetVictim() != bot)) + if (taldaram && taldaram->IsAlive() && (!taldaram->GetVictim() || taldaram->GetVictim() != bot)) return Attack(taldaram); + // If both princes are targeting us or dead, maintain current target + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + if (currentTarget && currentTarget->IsAlive() && + (currentTarget == valanar || currentTarget == taldaram)) + return Attack(currentTarget); + return false; } @@ -1450,23 +1453,6 @@ bool IccBpcMainTankAction::Execute(Event event) Unit* currentTarget = AI_VALUE(Unit*, "current target"); GuidVector targets = AI_VALUE(GuidVector, "possible targets"); - // First check if skull-marked target is a valid empowered prince - Unit* skullTarget = nullptr; - if (Group* group = bot->GetGroup()) - { - if (ObjectGuid skullGuid = group->GetTargetIcon(7)) // 7 = skull - { - skullTarget = botAI->GetUnit(skullGuid); - if (skullTarget && skullTarget->IsAlive() && skullTarget->HasAura(71596) && - (skullTarget->GetEntry() == 37972 || // Keleseth - skullTarget->GetEntry() == 37973 || // Taldaram - skullTarget->GetEntry() == 37970)) // Valanar - { - return Attack(skullTarget); - } - } - } - // If no valid skull target, search for empowered prince Unit* empoweredPrince = nullptr; for (auto i = targets.begin(); i != targets.end(); ++i) @@ -1483,80 +1469,200 @@ bool IccBpcMainTankAction::Execute(Event event) { empoweredPrince = unit; - // Mark empowered prince with skull if in group + // Mark empowered prince with skull if in group and not already marked if (Group* group = bot->GetGroup()) { + ObjectGuid currentSkullGuid = group->GetTargetIcon(7); + if (currentSkullGuid.IsEmpty() || currentSkullGuid != unit->GetGUID()) + { group->SetTargetIcon(7, bot->GetGUID(), unit->GetGUID()); // 7 = skull } + } break; } } } - - // Attack empowered prince if found and current target doesn't have aura - if (empoweredPrince) - { - // Only switch if current target doesn't have the aura - if (!currentTarget || !currentTarget->HasAura(71596)) - { - return Attack(empoweredPrince); - } - else - { - return Attack(currentTarget); - } - } - - // Keep current prince target if no empowered prince found - if (currentTarget && (currentTarget->GetEntry() == 37972 || // Keleseth - currentTarget->GetEntry() == 37973 || // Taldaram - currentTarget->GetEntry() == 37970)) // Valanar - { - return Attack(currentTarget); - } - } return false; } bool IccBpcEmpoweredVortexAction::Execute(Event event) { - // Double check that we're not a tank - if (botAI->IsMainTank(bot) || botAI->IsAssistTank(bot) || botAI->IsTank(bot)) - return false; - Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); - if (!valanar) + if (!valanar || !valanar->HasUnitState(UNIT_STATE_CASTING)) return false; - float radius = 12.0f; - GuidVector members = AI_VALUE(GuidVector, "group members"); + float const MIN_SPREAD = 12.0f; + float const MOVE_INCREMENT = 10.0f; + + // Use MT position as reference point to move away from + Position const* mtPos = &ICC_BPC_MT_POSITION; + float centerX = mtPos->GetPositionX(); + float centerY = mtPos->GetPositionY(); + float centerZ = mtPos->GetPositionZ(); - for (auto& member : members) + Group* group = bot->GetGroup(); + if (!group) + return false; + + // Get all alive group members and sort by GUID for consistent movement directions + std::vector> sortedMembers; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { - Unit* unit = botAI->GetUnit(member); - if (!unit || !unit->IsAlive() || unit == bot) - continue; + Player* member = itr->GetSource(); + if (member && member->IsAlive() && !botAI->IsTank(member)) + { + sortedMembers.push_back(std::make_pair(member->GetGUID(), member)); + } + } + std::sort(sortedMembers.begin(), sortedMembers.end()); - float dist = bot->GetExactDist2d(unit); - if (dist < radius) - { - float moveDistance = radius - dist + 1.0f; - - // Calculate potential new position - float angle = bot->GetAngle(unit); - float newX = bot->GetPositionX() + cos(angle + M_PI) * moveDistance; - float newY = bot->GetPositionY() + sin(angle + M_PI) * moveDistance; - - // Only move if we have line of sight - if (bot->IsWithinLOS(newX, newY, bot->GetPositionZ())) + // Find this bot's index to determine movement direction + int botIndex = -1; + for (size_t i = 0; i < sortedMembers.size(); ++i) + { + if (sortedMembers[i].first == bot->GetGUID()) + { + botIndex = i; + break; + } + } + + if (botIndex == -1) + return false; + + // Calculate base angle based on bot index (split into 12 directions) + float baseAngle = botIndex * (2.0f * M_PI / 12.0f); + + // Calculate current distance from MT position + float currentDist = bot->GetDistance2d(centerX, centerY); + + // If too close to others, move further out + bool needToMove = false; + if (currentDist < MIN_SPREAD) + needToMove = true; + else + { + // Check distance to other players + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + + if (bot->GetDistance2d(member) < MIN_SPREAD) { - return FleePosition(unit->GetPosition(), moveDistance); - // return MoveAway(unit, moveDistance); + needToMove = true; + break; } } } + if (!needToMove) + return false; + + // Calculate new position further out in our assigned direction + float moveDistance = std::max(MOVE_INCREMENT, currentDist + MOVE_INCREMENT); + float targetX = centerX + cos(baseAngle) * moveDistance; + float targetY = centerY + sin(baseAngle) * moveDistance; + float targetZ = centerZ; + + // Update Z coordinate and check LOS + bot->UpdateAllowedPositionZ(targetX, targetY, targetZ); + if (!bot->IsWithinLOS(targetX, targetY, targetZ)) + { + // Try adjusting angle if LOS fails + for (float angleAdjust = -M_PI/6; angleAdjust <= M_PI/6; angleAdjust += M_PI/12) + { + if (angleAdjust == 0) + continue; + + float newX = centerX + cos(baseAngle + angleAdjust) * moveDistance; + float newY = centerY + sin(baseAngle + angleAdjust) * moveDistance; + float newZ = centerZ; + + bot->UpdateAllowedPositionZ(newX, newY, newZ); + if (bot->IsWithinLOS(newX, newY, newZ)) + { + targetX = newX; + targetY = newY; + targetZ = newZ; + break; + } + } + } + + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); +} + +bool IccBpcKineticBombAction::Execute(Event event) +{ + // Only allow ranged DPS to handle bombs + if (!botAI->IsRangedDps(bot)) + return false; + + //for some reason they sometimes decide to move up in the air when they attack the kinetic bomb and that will make everyone tp to entrance... + if (bot->GetPositionZ() > 371.16473f) + return bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), + bot->GetPositionY(), 366.16473f, bot->GetOrientation()); + + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + + // If we're already attacking a bomb and it's still in range, stick with it + if (currentTarget && currentTarget->IsAlive() && currentTarget->GetName() == "Kinetic Bomb") + { + float heightDiff = currentTarget->GetPositionZ() - bot->GetPositionZ(); + if (heightDiff < 25.0f) + return false; // Continue current attack + } + + GuidVector targets = AI_VALUE(GuidVector, "possible targets"); + + // Find the lowest reachable bomb + Unit* bestBomb = nullptr; + float lowestHeightDiff = 25.0f; // Maximum height we care about + + for (auto& guid : targets) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || unit->GetName() != "Kinetic Bomb") + continue; + + float heightDiff = unit->GetPositionZ() - bot->GetPositionZ(); + if (heightDiff < lowestHeightDiff) + { + // Check if any closer ranged DPS is already attacking this bomb + bool alreadyHandled = false; + Group* group = bot->GetGroup(); + if (group) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || member == bot || !member->IsAlive() || !botAI->IsRangedDps(member)) + continue; + + if (member->GetTarget() == unit->GetGUID() && member->GetDistance(unit) < bot->GetDistance(unit)) + { + alreadyHandled = true; + break; + } + } + } + + if (!alreadyHandled) + { + bestBomb = unit; + lowestHeightDiff = heightDiff; + } + } + } + + // Attack the lowest unhandled bomb if found + if (bestBomb) + return Attack(bestBomb); + return false; } diff --git a/src/strategy/raids/icecrown/RaidIccActions.h b/src/strategy/raids/icecrown/RaidIccActions.h index fa80646d..c1347acf 100644 --- a/src/strategy/raids/icecrown/RaidIccActions.h +++ b/src/strategy/raids/icecrown/RaidIccActions.h @@ -270,7 +270,6 @@ public: bool Execute(Event event) override; }; -//BPC Vortex class IccBpcEmpoweredVortexAction : public MovementAction { public: @@ -279,6 +278,14 @@ public: bool Execute(Event event) override; }; +class IccBpcKineticBombAction : public AttackAction +{ +public: + IccBpcKineticBombAction(PlayerbotAI* botAI) + : AttackAction(botAI, "icc bpc kinetic bomb") {} + bool Execute(Event event) override; +}; + //Blood Queen Lana'thel class IccBqlTankPositionAction : public AttackAction { diff --git a/src/strategy/raids/icecrown/RaidIccMultipliers.cpp b/src/strategy/raids/icecrown/RaidIccMultipliers.cpp index c277f82c..da27c079 100644 --- a/src/strategy/raids/icecrown/RaidIccMultipliers.cpp +++ b/src/strategy/raids/icecrown/RaidIccMultipliers.cpp @@ -323,6 +323,48 @@ float IccBpcAssistMultiplier::GetValue(Action* action) } } + Unit* Valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); + if (!Valanar || !Valanar->IsAlive()) + return 1.0f; + + Aura* auraValanar = botAI->GetAura("Invocation of Blood", Valanar); + + if (!botAI->IsTank(bot) && auraValanar && Valanar->HasUnitState(UNIT_STATE_CASTING)) + { + if (dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + if (botAI->IsRangedDps(bot)) + { + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit) + { + if (unit->GetName() == "Kinetic Bomb" && ((unit->GetPositionZ() - bot->GetPositionZ()) < 25.0f)) + { + if (dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + } + } + } + // For assist tank during BPC fight if (botAI->IsAssistTank(bot)) { diff --git a/src/strategy/raids/icecrown/RaidIccStrategy.cpp b/src/strategy/raids/icecrown/RaidIccStrategy.cpp index 3807a830..2e8338c7 100644 --- a/src/strategy/raids/icecrown/RaidIccStrategy.cpp +++ b/src/strategy/raids/icecrown/RaidIccStrategy.cpp @@ -98,10 +98,13 @@ void RaidIccStrategy::InitTriggers(std::vector& triggers) NextAction::array(0, new NextAction("icc bpc nucleus", ACTION_RAID + 2), nullptr))); triggers.push_back(new TriggerNode("icc bpc main tank", - NextAction::array(0, new NextAction("icc bpc main tank", ACTION_RAID + 4), nullptr))); + NextAction::array(0, new NextAction("icc bpc main tank", ACTION_RAID + 3), nullptr))); triggers.push_back(new TriggerNode("icc bpc empowered vortex", - NextAction::array(0, new NextAction("icc bpc empowered vortex", ACTION_RAID + 3), nullptr))); + NextAction::array(0, new NextAction("icc bpc empowered vortex", ACTION_RAID + 4), nullptr))); + + triggers.push_back(new TriggerNode("icc bpc kinetic bomb", + NextAction::array(0, new NextAction("icc bpc kinetic bomb", ACTION_RAID + 6), nullptr))); //BQL triggers.push_back(new TriggerNode("icc bql tank position", diff --git a/src/strategy/raids/icecrown/RaidIccTriggerContext.h b/src/strategy/raids/icecrown/RaidIccTriggerContext.h index 9e10674a..2b0d800f 100644 --- a/src/strategy/raids/icecrown/RaidIccTriggerContext.h +++ b/src/strategy/raids/icecrown/RaidIccTriggerContext.h @@ -40,6 +40,7 @@ public: 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 bpc kinetic bomb"] = &RaidIccTriggerContext::icc_bpc_kinetic_bomb; 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; @@ -94,6 +95,7 @@ private: 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_bpc_kinetic_bomb(PlayerbotAI* ai) { return new IccBpcKineticBombTrigger(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); } diff --git a/src/strategy/raids/icecrown/RaidIccTriggers.cpp b/src/strategy/raids/icecrown/RaidIccTriggers.cpp index 3f7fc448..e0525b9b 100644 --- a/src/strategy/raids/icecrown/RaidIccTriggers.cpp +++ b/src/strategy/raids/icecrown/RaidIccTriggers.cpp @@ -1,7 +1,8 @@ #include "RaidIccTriggers.h" #include "RaidIccActions.h" - +#include "strategy/values/NearestNpcsValue.h" #include "PlayerbotAIConfig.h" +#include "ObjectAccessor.h" #include "GenericTriggers.h" #include "DungeonStrategyUtils.h" #include "EventMap.h" @@ -463,7 +464,7 @@ bool IccBpcEmpoweredVortexTrigger::IsActive() return false; Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar"); - if (!valanar || !valanar->IsAlive()) + if (!valanar) return false; Aura* aura = botAI->GetAura("Shadow Prison", bot, false, true); @@ -471,20 +472,61 @@ bool IccBpcEmpoweredVortexTrigger::IsActive() if (aura->GetStackAmount() > 12) return false; + Aura* auraValanar = botAI->GetAura("Invocation of Blood", valanar); + if (!auraValanar) + return false; + // For ranged, spread whenever Valanar is empowered - if (botAI->IsRanged(bot)) - return valanar->HasAura(71596); // Invocation of Blood + //if (botAI->IsRanged(bot) && auraValanar) + //return true; // For melee, only spread during vortex cast - if (valanar->HasAura(71596) && valanar->HasUnitState(UNIT_STATE_CASTING) && valanar->FindCurrentSpellBySpellId(72039)) - { + if (auraValanar && valanar->HasUnitState(UNIT_STATE_CASTING)) return true; - } return false; } -//BQL +bool IccBpcKineticBombTrigger::IsActive() +{ + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + // Check for hunters first + bool hasHunter = false; + Group* group = bot->GetGroup(); + if (group) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->getClass() == CLASS_HUNTER && member->IsAlive()) + { + hasHunter = true; + break; + } + } + } + + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit && unit->GetName() == "Kinetic Bomb" && + ((unit->GetPositionZ() - bot->GetPositionZ()) < 25.0f)) + { + if (hasHunter) + { + return bot->getClass() == CLASS_HUNTER; // Only hunters can handle bombs + } + else if (botAI->IsRangedDps(bot)) + { + return true; // If no hunters, any ranged DPS can handle bombs + } + } + } + return false; +} + + //BQL bool IccBqlTankPositionTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel"); diff --git a/src/strategy/raids/icecrown/RaidIccTriggers.h b/src/strategy/raids/icecrown/RaidIccTriggers.h index 07452263..465ebc57 100644 --- a/src/strategy/raids/icecrown/RaidIccTriggers.h +++ b/src/strategy/raids/icecrown/RaidIccTriggers.h @@ -226,6 +226,13 @@ public: bool IsActive() override; }; +class IccBpcKineticBombTrigger : public Trigger +{ +public: + IccBpcKineticBombTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc bpc kinetic bomb") {} + bool IsActive() override; +}; + //Bql class IccBqlTankPositionTrigger : public Trigger {