#include "RaidUlduarActions.h" #include #include "AiObjectContext.h" #include "DBCEnums.h" #include "GameObject.h" #include "Group.h" #include "LastMovementValue.h" #include "ObjectDefines.h" #include "ObjectGuid.h" #include "PlayerbotAI.h" #include "PlayerbotAIConfig.h" #include "Playerbots.h" #include "Position.h" #include "RaidUlduarBossHelper.h" #include "RaidUlduarScripts.h" #include "RaidUlduarStrategy.h" #include "RaidUlduarTriggers.h" #include "RtiValue.h" #include "ScriptedCreature.h" #include "ServerFacade.h" #include "SharedDefines.h" #include "Unit.h" #include "Vehicle.h" const std::vector availableVehicles = {NPC_VEHICLE_CHOPPER, NPC_SALVAGED_DEMOLISHER, NPC_SALVAGED_DEMOLISHER_TURRET, NPC_SALVAGED_SIEGE_ENGINE, NPC_SALVAGED_SIEGE_ENGINE_TURRET}; const std::vector corners = { {183.53f, 66.53f, 409.80f}, {383.03f, 75.10f, 411.71f}, {379.74f, -133.05f, 410.88f}, {158.67f, -137.54f, 409.80f}}; const Position ULDUAR_KOLOGARN_RESTORE_POSITION = Position(1764.3749f, -24.02903f, 448.0f, 0.00087690353f); const Position ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION = Position(1781.2051f, 9.34402f, 449.0f, 0.00087690353f); const Position ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION = Position(1763.2561f, -24.44305f, 449.0f, 0.00087690353f); bool FlameLeviathanVehicleAction::Execute(Event event) { vehicleBase_ = bot->GetVehicleBase(); vehicle_ = bot->GetVehicle(); if (!vehicleBase_ || !vehicle_) return false; GuidVector attackers = context->GetValue("attackers")->Get(); Unit* target = nullptr; Unit* flame = nullptr; for (auto i = attackers.begin(); i != attackers.end(); ++i) { Unit* unit = botAI->GetUnit(*i); if (!unit) continue; if (unit->GetEntry() == 33139) // Flame Leviathan Turret continue; if (unit->GetEntry() == 33142) // Leviathan Defense Turret continue; if (unit->GetEntry() == 33113) // Flame Leviathan { flame = unit; continue; } if (!target || bot->GetExactDist(target) > bot->GetExactDist(unit)) { target = unit; } } // Flame Leviathan is chasing me if (flame && flame->GetVictim() == vehicleBase_) if (MoveAvoidChasing(flame)) return true; uint32 entry = vehicleBase_->GetEntry(); switch (entry) { case NPC_SALVAGED_DEMOLISHER: return DemolisherAction(flame ? flame : target); case NPC_SALVAGED_DEMOLISHER_TURRET: return DemolisherTurretAction(target ? target: flame); case NPC_SALVAGED_SIEGE_ENGINE: return SiegeEngineAction(flame ? flame : target); case NPC_SALVAGED_SIEGE_ENGINE_TURRET: return SiegeEngineTurretAction(target ? target: flame); case NPC_VEHICLE_CHOPPER: return ChopperAction(target ? target: flame); default: break; } return false; } bool FlameLeviathanVehicleAction::MoveAvoidChasing(Unit* target) { if (!target) return false; if (avoidChaseIdx == -1) { for (int i = 0; i < corners.size(); i++) { if (bot->GetExactDist(corners[i]) > target->GetExactDist(corners[i])) continue; if (avoidChaseIdx == -1 || bot->GetExactDist(corners[i]) > bot->GetExactDist(corners[avoidChaseIdx])) avoidChaseIdx = i; } if (avoidChaseIdx == -1) avoidChaseIdx = 0; } else { if (bot->GetExactDist(corners[avoidChaseIdx]) < 5.0f && target->GetExactDist(bot) < 50.0f) avoidChaseIdx = (avoidChaseIdx + 1) % corners.size(); } const Position& to = corners[avoidChaseIdx]; return MoveTo(bot->GetMap()->GetId(), to.GetPositionX(), to.GetPositionY(), to.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); } bool FlameLeviathanVehicleAction::DemolisherAction(Unit* target) { if (!target) return false; Aura* bluePyrite = target->GetAura(68605); if (!bluePyrite || (vehicleBase_->GetPower(POWER_ENERGY) >= 20) || bluePyrite->GetDuration() <= 5000) { uint32 spellId = 62490; if (botAI->CanCastVehicleSpell(spellId, target)) if (botAI->CastVehicleSpell(spellId, target)) { vehicleBase_->AddSpellCooldown(spellId, 0, 1000); return true; } } uint32 spellId = 62306; if (botAI->CanCastVehicleSpell(spellId, target)) if (botAI->CastVehicleSpell(spellId, target)) { vehicleBase_->AddSpellCooldown(spellId, 0, 1000); return true; } return false; } bool FlameLeviathanVehicleAction::DemolisherTurretAction(Unit* target) { int32 liquidCount = 0; { GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); for (auto i = npcs.begin(); i != npcs.end(); i++) { Unit* unit = botAI->GetUnit(*i); if (!unit) continue; if (unit->GetEntry() != 33189) continue; if (unit->GetDistance(bot) >= 49.0f) continue; ++liquidCount; if (vehicleBase_->GetPower(POWER_ENERGY) <= 25) // Liquid Pyrite { uint32 spellId = 62479; if (botAI->CanCastVehicleSpell(spellId, unit)) if (botAI->CastVehicleSpell(spellId, unit)) { vehicleBase_->AddSpellCooldown(spellId, 0, 1000); return true; } } } } if (liquidCount <= 10) { GuidVector targets = AI_VALUE(GuidVector, "possible targets"); for (auto i = targets.begin(); i != targets.end(); i++) { Unit* unit = botAI->GetUnit(*i); if (!unit) continue; if (unit->GetEntry() == 33214) // Mechanolift 304-A { uint32 spellId = 64979; if (botAI->CanCastVehicleSpell(spellId, unit)) if (botAI->CastVehicleSpell(spellId, unit)) { vehicleBase_->AddSpellCooldown(spellId, 0, 1000); return true; } } } } if (!target) return false; uint32 spellId = 62634; if (botAI->CanCastVehicleSpell(spellId, target)) if (botAI->CastVehicleSpell(spellId, target)) { vehicleBase_->AddSpellCooldown(spellId, 0, 1000); return true; } return false; } bool FlameLeviathanVehicleAction::SiegeEngineAction(Unit* target) { if (!target) return false; if (target->GetCurrentSpell(CURRENT_CHANNELED_SPELL) || target->HasAura(62396)) { uint32 spellId = 62522; if (botAI->CanCastVehicleSpell(spellId, target)) if (botAI->CastVehicleSpell(spellId, target)) { vehicleBase_->AddSpellCooldown(spellId, 0, 10000); return true; } } uint32 spellId = 62345; if (vehicleBase_->GetPower(POWER_ENERGY) >= 80 && botAI->CanCastVehicleSpell(spellId, target)) if (botAI->CastVehicleSpell(spellId, target)) { vehicleBase_->AddSpellCooldown(spellId, 0, 1000); return true; } return false; } bool FlameLeviathanVehicleAction::SiegeEngineTurretAction(Unit* target) { if (!target) return false; uint32 spellId = 62358; if (botAI->CanCastVehicleSpell(spellId, target)) if (botAI->CastVehicleSpell(spellId, target)) { vehicleBase_->AddSpellCooldown(spellId, 0, 1000); return true; } return false; } bool FlameLeviathanVehicleAction::ChopperAction(Unit* target) { if (!target) return false; uint32 spellId = 62286; if (botAI->CanCastVehicleSpell(spellId, target)) if (botAI->CastVehicleSpell(spellId, target)) { vehicleBase_->AddSpellCooldown(spellId, 0, 15000); return true; } spellId = 62974; if (botAI->CanCastVehicleSpell(spellId, target)) if (botAI->CastVehicleSpell(spellId, target)) { vehicleBase_->AddSpellCooldown(spellId, 0, 1000); return true; } return false; } bool FlameLeviathanEnterVehicleAction::Execute(Event event) { // do not switch vehicles yet if (bot->GetVehicle()) return false; Unit* vehicleToEnter = nullptr; GuidVector npcs = AI_VALUE(GuidVector, "nearest vehicles far"); for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++) { Unit* vehicleBase = botAI->GetUnit(*i); if (!vehicleBase) continue; if (vehicleBase->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) continue; if (!ShouldEnter(vehicleBase)) continue; if (!vehicleToEnter || bot->GetExactDist(vehicleToEnter) > bot->GetExactDist(vehicleBase)) vehicleToEnter = vehicleBase; } if (!vehicleToEnter) return false; if (EnterVehicle(vehicleToEnter, true)) return true; return false; } bool FlameLeviathanEnterVehicleAction::EnterVehicle(Unit* vehicleBase, bool moveIfFar) { float dist = bot->GetDistance(vehicleBase); if (dist > INTERACTION_DISTANCE && !moveIfFar) return false; if (dist > INTERACTION_DISTANCE) return MoveTo(vehicleBase); botAI->RemoveShapeshift(); // Use HandleSpellClick instead of Unit::EnterVehicle to handle special vehicle script (ulduar) vehicleBase->HandleSpellClick(bot); if (!bot->IsOnVehicle(vehicleBase)) return false; // dismount because bots can enter vehicle on mount WorldPacket emptyPacket; bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket); return true; } bool FlameLeviathanEnterVehicleAction::ShouldEnter(Unit* target) { Vehicle* vehicleKit = target->GetVehicleKit(); if (!vehicleKit) return false; bool isMelee = botAI->IsMelee(bot); bool allMain = AllMainVehiclesOnUse(); bool inUse = vehicleKit->IsVehicleInUse(); int32 entry = target->GetEntry(); if (entry != NPC_SALVAGED_DEMOLISHER && entry != NPC_SALVAGED_SIEGE_ENGINE && entry != NPC_VEHICLE_CHOPPER) return false; // two phase enter (make all main vehicles in use -> next player enter) if (!allMain) { if (inUse) return false; if (entry != NPC_SALVAGED_DEMOLISHER && entry != NPC_SALVAGED_SIEGE_ENGINE) return false; if (entry == NPC_SALVAGED_DEMOLISHER && isMelee) return false; if (entry == NPC_SALVAGED_SIEGE_ENGINE && !isMelee) return false; return true; } if (!vehicleKit->GetAvailableSeatCount()) return false; // do not enter useless seat if (entry == NPC_SALVAGED_SIEGE_ENGINE) { Unit* turret = vehicleKit->GetPassenger(7); if (!turret) return false; Vehicle* turretVehicle = turret->GetVehicleKit(); if (!turretVehicle) return false; if (turretVehicle->IsVehicleInUse()) return false; return true; } if (entry == NPC_SALVAGED_DEMOLISHER) { if (vehicleKit->GetPassenger(0)) { Unit* target2 = vehicleKit->GetPassenger(1); if (!target2) return false; Vehicle* vehicle2 = target2->GetVehicleKit(); if (!vehicle2) return false; if (vehicle2->GetPassenger(0)) return false; } return true; } if (entry == NPC_VEHICLE_CHOPPER && vehicleKit->GetAvailableSeatCount() <= 1) return false; return true; } bool FlameLeviathanEnterVehicleAction::AllMainVehiclesOnUse() { Group* group = bot->GetGroup(); if (!group) return false; int demolisher = 0; int siege = 0; for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* player = gref->GetSource(); if (!player) continue; Unit* vehicleBase = player->GetVehicleBase(); if (!vehicleBase) continue; if (vehicleBase->GetEntry() == NPC_SALVAGED_DEMOLISHER) ++demolisher; else if (vehicleBase->GetEntry() == NPC_SALVAGED_SIEGE_ENGINE) ++siege; } Difficulty diff = bot->GetRaidDifficulty(); int maxC = (diff == RAID_DIFFICULTY_10MAN_NORMAL || diff == RAID_DIFFICULTY_10MAN_HEROIC) ? 2 : 5; return demolisher >= maxC && siege >= maxC; } bool RazorscaleAvoidDevouringFlameAction::Execute(Event event) { RazorscaleBossHelper razorscaleHelper(botAI); if (!razorscaleHelper.UpdateBossAI()) { return false; } bool isMainTank = botAI->IsMainTank(bot); const float flameRadius = 3.5f; // Main tank moves further so they can hold adds away from flames, but only during the air phases const float safeDistanceMultiplier = (isMainTank && !razorscaleHelper.IsGroundPhase()) ? 2.3f : 1.0f; const float safeDistance = flameRadius * safeDistanceMultiplier; // Get the boss Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); if (!boss) { return false; } GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); Unit* closestFlame = nullptr; float closestDistance = std::numeric_limits::max(); // Find the closest Devouring Flame for (auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DEVOURING_FLAME) { float distance = bot->GetDistance2d(unit); if (distance < closestDistance) { closestDistance = distance; closestFlame = unit; } } } // Off tanks are following the main tank during grounded and should prioritise stacking if (razorscaleHelper.IsGroundPhase() && (botAI->IsTank(bot) && !botAI->IsMainTank(bot))) { return false; } // Handle movement from flames if (closestDistance < safeDistance) { return MoveAway(closestFlame, safeDistance); } return false; } bool RazorscaleAvoidDevouringFlameAction::isUseful() { bool isMainTank = botAI->IsMainTank(bot); const float flameRadius = 3.5f; const float safeDistanceMultiplier = isMainTank ? 2.3f : 1.0f; const float safeDistance = flameRadius * safeDistanceMultiplier; GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); for (auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DEVOURING_FLAME) { float distance = bot->GetDistance2d(unit); if (distance < safeDistance) { return true; // Bot is within the danger distance } } } return false; // No nearby flames or bot is at a safe distance } bool RazorscaleAvoidSentinelAction::Execute(Event event) { bool isMainTank = botAI->IsMainTank(bot); bool isRanged = botAI->IsRanged(bot); const float radius = 8.0f; GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); Unit* lowestHealthSentinel = nullptr; uint32 lowestHealth = UINT32_MAX; bool movedAway = false; // Iterate through all nearby NPCs for (auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL) { // Check if this sentinel has the lowest health if (unit->GetHealth() < lowestHealth) { lowestHealth = unit->GetHealth(); lowestHealthSentinel = unit; } // Move away if ranged and too close if (isRanged && bot->GetDistance2d(unit) < radius) { movedAway = MoveAway(unit, radius) || movedAway; } } } // Check if the main tank is a human player Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr; if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a real player { // Iterate through the first 3 bot tanks to assign the Skull marker for (int i = 0; i < 3; ++i) { if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank { Group* group = bot->GetGroup(); if (group && lowestHealthSentinel) { int8 skullIndex = 7; // Skull ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex); // If there's no skull set yet, or the skull is on a different target, set the sentinel if (!currentSkullTarget || (lowestHealthSentinel->GetGUID() != currentSkullTarget)) { group->SetTargetIcon(skullIndex, bot->GetGUID(), lowestHealthSentinel->GetGUID()); } } break; // Stop after finding the first valid bot tank } } } else if (isMainTank && lowestHealthSentinel) // Bot is the main tank { Group* group = bot->GetGroup(); if (group) { int8 skullIndex = 7; // Skull ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex); // If there's no skull set yet, or the skull is on a different target, set the sentinel if (!currentSkullTarget || (lowestHealthSentinel->GetGUID() != currentSkullTarget)) { group->SetTargetIcon(skullIndex, bot->GetGUID(), lowestHealthSentinel->GetGUID()); } } } return movedAway; // Return true if moved } bool RazorscaleAvoidSentinelAction::isUseful() { bool isMainTank = botAI->IsMainTank(bot); Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr; // If this bot is the main tank, it should always try to mark if (isMainTank) { return true; } // If the main tank is a human, check if this bot is one of the first three valid bot tanks if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player { for (int i = 0; i < 3; ++i) { if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank { return true; // This bot should assist with marking } } } bool isRanged = botAI->IsRanged(bot); const float radius = 8.0f; GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); for (auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL) { if (isRanged && bot->GetDistance2d(unit) < radius) { return true; } } } return false; } bool RazorscaleAvoidWhirlwindAction::Execute(Event event) { if (botAI->IsTank(bot)) { return false; } const float radius = 8.0f; GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); for (auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL) { float currentDistance = bot->GetDistance2d(unit); if (currentDistance < radius) { return MoveAway(unit, radius); } } } return false; } bool RazorscaleAvoidWhirlwindAction::isUseful() { // Tanks do not avoid Whirlwind if (botAI->IsTank(bot)) { return false; } const float radius = 8.0f; GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); for (auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL) { if (unit->HasAura(RazorscaleBossHelper::SPELL_SENTINEL_WHIRLWIND) || unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) { if (bot->GetDistance2d(unit) < radius) { return true; } } } } return false; } bool RazorscaleIgnoreBossAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); if (!boss) { return false; } // Check if the boss is flying if (boss->GetPositionZ() >= RazorscaleBossHelper::RAZORSCALE_FLYING_Z_THRESHOLD) { // Check if the bot is outside the designated area if (bot->GetDistance2d( RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) > RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f) { return true; // Movement to the center is the top priority for all bots } if (!botAI->IsTank(bot)) { return false; } Group* group = bot->GetGroup(); if (!group) { return false; } // Check if the boss is already set as the moon marker int8 moonIndex = 4; // Moon marker index ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); if (currentMoonTarget == boss->GetGUID()) { return false; // Moon marker is already correctly set, no further action needed } // Proceed to tank-specific logic Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr; // If this bot is the main tank, it needs to set the moon marker if (mainTankUnit == bot) { return true; } // If the main tank is a human, check if this bot is the lowest-indexed bot tank if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player { for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes { if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Valid bot tank { return true; // This bot should assign the marker } } } } return false; } bool RazorscaleIgnoreBossAction::Execute(Event event) { if (!bot) { return false; } Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); if (!boss) { return false; } Group* group = bot->GetGroup(); if (!group) { return false; } // Check if the bot is outside the designated area and move inside first if (bot->GetDistance2d( RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y) > RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS + 25.0f) { return MoveInside( ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(), RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f, MovementPriority::MOVEMENT_NORMAL ); } if (!botAI->IsTank(bot)) { return false; } // Check if the boss is already set as the moon marker int8 moonIndex = 4; ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); if (currentMoonTarget == boss->GetGUID()) { return false; // Moon marker is already correctly set } // Get the main tank and determine role Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr; // If the main tank is a human, assign the moon marker using the lowest-indexed bot tank if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a real player { for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes { if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank { group->SetTargetIcon(moonIndex, bot->GetGUID(), boss->GetGUID()); SetNextMovementDelay(1000); break; // Assign the moon marker and stop } } } else if (mainTankUnit == bot) // If this bot is the main tank { group->SetTargetIcon(moonIndex, bot->GetGUID(), boss->GetGUID()); SetNextMovementDelay(1000); } // Tanks move inside the arena return MoveInside( ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(), RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f, MovementPriority::MOVEMENT_NORMAL ); } bool RazorscaleGroundedAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); if (!boss || !boss->IsAlive() || boss->GetPositionZ() > RazorscaleBossHelper::RAZORSCALE_FLYING_Z_THRESHOLD) { return false; } if (botAI->IsMainTank(bot)) { Group* group = bot->GetGroup(); if (!group) return false; // Check if the boss is marked with Moon int8 moonIndex = 4; ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); // Useful only if the boss is currently marked with Moon return currentMoonTarget == boss->GetGUID(); } if (botAI->IsTank(bot) && !botAI->IsMainTank(bot)) { Group* group = bot->GetGroup(); if (!group) return false; // Find the main tank Player* mainTank = nullptr; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); if (member && botAI->IsMainTank(member)) { mainTank = member; break; } } if (mainTank) { constexpr float maxDistance = 2.0f; float distanceToMainTank = bot->GetDistance2d(mainTank); return (distanceToMainTank > maxDistance); } } if (botAI->IsMelee(bot)) { return false; } if (botAI->IsRanged(bot)) { constexpr float landingX = 588.0f; constexpr float landingY = -166.0f; constexpr float landingZ = 391.1f; float bossX = boss->GetPositionX(); float bossY = boss->GetPositionY(); float bossZ = boss->GetPositionZ(); bool atInitialLandingPosition = (fabs(bossX - landingX) < 2.0f) && (fabs(bossY - landingY) < 2.0f) && (fabs(bossZ - landingZ) < 1.0f); constexpr float initialLandingRadius = 14.0f; constexpr float normalRadius = 12.0f; if (atInitialLandingPosition) { float adjustedCenterX = RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X; float adjustedCenterY = RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y - 20.0f; float distanceToAdjustedCenter = bot->GetDistance2d(adjustedCenterX, adjustedCenterY); return distanceToAdjustedCenter > initialLandingRadius; } float distanceToCenter = bot->GetDistance2d(RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y); return distanceToCenter > normalRadius; } return false; } bool RazorscaleGroundedAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); if (!boss || !boss->IsAlive() || boss->GetPositionZ() > RazorscaleBossHelper::RAZORSCALE_FLYING_Z_THRESHOLD) return false; Group* group = bot->GetGroup(); if (!group) return false; Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr; if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player { // Iterate through the first 3 bot tanks to handle the moon marker for (int i = 0; i < 3; ++i) { if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank { int8 moonIndex = 4; ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); // If the moon marker is set to the boss, reset it if (currentMoonTarget == boss->GetGUID()) { group->SetTargetIcon(moonIndex, bot->GetGUID(), ObjectGuid::Empty); SetNextMovementDelay(1000); return true; } } } } else if (botAI->IsMainTank(bot)) // Bot is the main tank { int8 moonIndex = 4; ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); // If the moon marker is set to the boss, reset it if (currentMoonTarget == boss->GetGUID()) { group->SetTargetIcon(moonIndex, bot->GetGUID(), ObjectGuid::Empty); SetNextMovementDelay(1000); return true; } } if (mainTank && (botAI->IsTank(bot) && !botAI->IsMainTank(bot))) { constexpr float followDistance = 2.0f; return MoveNear(mainTank, followDistance, MovementPriority::MOVEMENT_COMBAT); } if (botAI->IsRanged(bot)) { constexpr float landingX = 588.0f; constexpr float landingY = -166.0f; constexpr float landingZ = 391.1f; float bossX = boss->GetPositionX(); float bossY = boss->GetPositionY(); float bossZ = boss->GetPositionZ(); bool atInitialLandingPosition = (fabs(bossX - landingX) < 2.0f) && (fabs(bossY - landingY) < 2.0f) && (fabs(bossZ - landingZ) < 1.0f); if (atInitialLandingPosition) { // If at the initial landing position, use 12-yard radius with a // 20 yard offset on the Y axis so everyone is behind the boss return MoveInside( ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y - 20.0f, bot->GetPositionZ(), RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 12.0f, MovementPriority::MOVEMENT_COMBAT ); } // Otherwise, move inside a 12-yard radius around the arena center return MoveInside( ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(), 12.0f, MovementPriority::MOVEMENT_COMBAT ); } return false; } bool RazorscaleHarpoonAction::Execute(Event event) { if (!bot) { return false; } RazorscaleBossHelper razorscaleHelper(botAI); // Update the boss AI context if (!razorscaleHelper.UpdateBossAI()) return false; Unit* boss = razorscaleHelper.GetBoss(); if (!boss || !boss->IsAlive()) return false; // Retrieve harpoon data from the helper const std::vector& harpoonData = razorscaleHelper.GetHarpoonData(); GameObject* closestHarpoon = nullptr; float minDistance = std::numeric_limits::max(); // Find the nearest harpoon that hasn't been fired and is not on cooldown for (const auto& harpoon : harpoonData) { if (razorscaleHelper.IsHarpoonFired(harpoon.chainSpellId)) continue; if (GameObject* harpoonGO = bot->FindNearestGameObject(harpoon.gameObjectEntry, 200.0f)) { if (RazorscaleBossHelper::IsHarpoonReady(harpoonGO)) { float distance = bot->GetDistance2d(harpoonGO); if (distance < minDistance) { minDistance = distance; closestHarpoon = harpoonGO; } } } } if (!closestHarpoon) return false; // Find the nearest ranged DPS (not a healer) to the harpoon Player* closestRangedDPS = nullptr; minDistance = std::numeric_limits::max(); GuidVector groupBots = AI_VALUE(GuidVector, "group members"); for (auto& guid : groupBots) { Player* member = ObjectAccessor::FindPlayer(guid); if (member && member->IsAlive() && botAI->IsRanged(member) && botAI->IsDps(member) && !botAI->IsHeal(member)) { float distance = member->GetDistance2d(closestHarpoon); if (distance < minDistance) { minDistance = distance; closestRangedDPS = member; } } } // Only proceed if this bot is the closest ranged DPS if (closestRangedDPS != bot) return false; float botDist = bot->GetDistance(closestHarpoon); if (botDist > INTERACTION_DISTANCE - 1.0f) { return MoveTo(bot->GetMapId(), closestHarpoon->GetPositionX(), closestHarpoon->GetPositionY(), closestHarpoon->GetPositionZ()); } SetNextMovementDelay(1000); // Interact with the harpoon { WorldPacket usePacket(CMSG_GAMEOBJ_USE); usePacket << closestHarpoon->GetGUID(); bot->GetSession()->HandleGameObjectUseOpcode(usePacket); } { WorldPacket reportPacket(CMSG_GAMEOBJ_REPORT_USE); reportPacket << closestHarpoon->GetGUID(); bot->GetSession()->HandleGameobjectReportUse(reportPacket); } RazorscaleBossHelper::SetHarpoonOnCooldown(closestHarpoon); return true; } bool RazorscaleHarpoonAction::isUseful() { RazorscaleBossHelper razorscaleHelper(botAI); // Update the boss AI context to ensure we have the latest info if (!razorscaleHelper.UpdateBossAI()) return false; Unit* boss = razorscaleHelper.GetBoss(); if (!boss || !boss->IsAlive()) return false; const std::vector& harpoonData = razorscaleHelper.GetHarpoonData(); for (const auto& harpoon : harpoonData) { if (razorscaleHelper.IsHarpoonFired(harpoon.chainSpellId)) continue; if (GameObject* harpoonGO = bot->FindNearestGameObject(harpoon.gameObjectEntry, 200.0f)) { if (RazorscaleBossHelper::IsHarpoonReady(harpoonGO)) { // Check if this bot is a ranged DPS (not a healer) if (botAI->IsRanged(bot) && botAI->IsDps(bot) && !botAI->IsHeal(bot)) return true; } } } return false; } bool RazorscaleFuseArmorAction::isUseful() { // If this bot cannot tank at all, no need to do anything if (!botAI->IsTank(bot)) return false; // If this bot is the main tank AND has Fuse Armor at the threshold, return true immediately if (botAI->IsMainTank(bot)) { Aura* fuseArmor = bot->GetAura(RazorscaleBossHelper::SPELL_FUSEARMOR); if (fuseArmor && fuseArmor->GetStackAmount() >= RazorscaleBossHelper::FUSEARMOR_THRESHOLD) return true; } // Otherwise, check if there's any other main tank with high Fuse Armor Group* group = bot->GetGroup(); if (!group) return false; for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); if (!member) continue; if (botAI->IsMainTank(member) && member != bot) { Aura* fuseArmor = member->GetAura(RazorscaleBossHelper::SPELL_FUSEARMOR); if (fuseArmor && fuseArmor->GetStackAmount() >= RazorscaleBossHelper::FUSEARMOR_THRESHOLD) { // There is another main tank with high Fuse Armor return true; } } } return false; } bool RazorscaleFuseArmorAction::Execute(Event event) { // We already know from isUseful() that: // 1) This bot can tank, AND // 2) There is at least one main tank (possibly this bot) with Fuse Armor >= threshold. RazorscaleBossHelper bossHelper(botAI); // Attempt to reassign the roles based on health/Fuse Armor debuff bossHelper.AssignRolesBasedOnHealth(); return true; } bool IronAssemblyLightningTendrilsAction::isUseful() { IronAssemblyLightningTendrilsTrigger ironAssemblyLightningTendrilsTrigger(botAI); return ironAssemblyLightningTendrilsTrigger.IsActive(); } bool IronAssemblyLightningTendrilsAction::Execute(Event event) { const float radius = 18.0f + 10.0f; // 18 yards + 10 yards for safety Unit* boss = AI_VALUE2(Unit*, "find target", "stormcaller brundir"); if (!boss) return false; float currentDistance = bot->GetDistance2d(boss); if (currentDistance < radius) { return MoveAway(boss, radius - currentDistance); } return false; } bool IronAssemblyOverloadAction::isUseful() { IronAssemblyOverloadTrigger ironAssemblyOverloadTrigger(botAI); return ironAssemblyOverloadTrigger.IsActive(); } bool IronAssemblyOverloadAction::Execute(Event event) { const float radius = 20.0f + 5.0f; // 20 yards + 5 yards for safety Unit* boss = AI_VALUE2(Unit*, "find target", "stormcaller brundir"); if (!boss) return false; float currentDistance = bot->GetDistance2d(boss); if (currentDistance < radius) { return MoveAway(boss, radius - currentDistance); } return false; } bool KologarnMarkDpsTargetAction::isUseful() { KologarnMarkDpsTargetTrigger kologarnMarkDpsTargetTrigger(botAI); return kologarnMarkDpsTargetTrigger.IsActive(); } bool KologarnMarkDpsTargetAction::Execute(Event event) { Unit* targetToMark = nullptr; Unit* additionalTargetToMark = nullptr; Unit* targetToCcMark = nullptr; int8 skullIndex = 7; // Skull int8 crossIndex = 6; // Cross int8 moonIndex = 4; // Moon Unit* boss = AI_VALUE2(Unit*, "find target", "kologarn"); if (!boss || !boss->IsAlive()) return false; // Check that there is rubble to mark GuidVector targets = AI_VALUE(GuidVector, "possible targets"); Unit* target = nullptr; for (auto i = targets.begin(); i != targets.end(); ++i) { target = botAI->GetUnit(*i); if (!target) continue; uint32 creatureId = target->GetEntry(); if (target->GetEntry() == NPC_RUBBLE && target->IsAlive()) { targetToMark = target; additionalTargetToMark = boss; } } if (!targetToMark) { Unit* rightArm = AI_VALUE2(Unit*, "find target", "right arm"); if (rightArm && rightArm->IsAlive()) { targetToMark = rightArm; additionalTargetToMark = boss; } } if (!targetToMark) { Unit* boss = AI_VALUE2(Unit*, "find target", "kologarn"); if (boss && boss->IsAlive()) { targetToMark = boss; } } if (!targetToMark) { return false; // No target to mark } Unit* leftArm = AI_VALUE2(Unit*, "find target", "left arm"); if (leftArm && leftArm->IsAlive()) { targetToCcMark = leftArm; } bool isMainTank = botAI->IsMainTank(bot); Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr; if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a real player { // Iterate through the first 3 bot tanks to assign the Skull marker for (int i = 0; i < 3; ++i) { if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank { Group* group = bot->GetGroup(); if (group) { group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); if (targetToCcMark) { group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID()); } if (additionalTargetToMark) { group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID()); } return true; } break; // Stop after finding the first valid bot tank } } } else if (isMainTank && bot->IsAlive()) // Bot is the main tank { Group* group = bot->GetGroup(); if (group) { group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); if (targetToCcMark) { group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID()); } if (additionalTargetToMark) { group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID()); } return true; } } else { for (int i = 0; i < 3; ++i) { if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot) && bot->IsAlive()) // Bot is a valid tank { Group* group = bot->GetGroup(); if (group) { group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); if (targetToCcMark) { group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID()); } if (additionalTargetToMark) { group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID()); } return true; } break; // Stop after finding the first valid bot tank } } } return false; } bool KologarnFallFromFloorAction::Execute(Event event) { return bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionX(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionY(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionZ(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetOrientation()); } bool KologarnFallFromFloorAction::isUseful() { KologarnFallFromFloorTrigger kologarnFallFromFloorTrigger(botAI); return kologarnFallFromFloorTrigger.IsActive(); } bool KologarnRubbleSlowdownAction::Execute(Event event) { Group* group = bot->GetGroup(); if (!group) return false; int8 skullIndex = 7; ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex); Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); if (!currentSkullUnit || !currentSkullUnit->IsAlive() || currentSkullUnit->GetEntry() != NPC_RUBBLE) return false; return botAI->CastSpell("frost trap", currentSkullUnit); } bool KologarnEyebeamAction::Execute(Event event) { float distanceToLeftPoint = bot->GetExactDist(ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION); float distanceToRightPoint = bot->GetExactDist(ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION); bool runToLeftSide; if (!distanceToLeftPoint) { runToLeftSide = true; } else if (!distanceToRightPoint) { runToLeftSide = false; } else { runToLeftSide = distanceToRightPoint > distanceToLeftPoint; } bool teleportedToPoint; KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI); if (runToLeftSide) { teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionX(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionY(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionZ(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetOrientation()); } else { teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionX(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionY(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionZ(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetOrientation()); } if (teleportedToPoint) SetNextMovementDelay(5000); return teleportedToPoint; } bool KologarnEyebeamAction::isUseful() { KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI); return kologarnEyebeamTrigger.IsActive(); } bool KologarnRtiTargetAction::isUseful() { KologarnRtiTargetTrigger kologarnRtiTargetTrigger(botAI); return kologarnRtiTargetTrigger.IsActive(); } bool KologarnRtiTargetAction::Execute(Event event) { if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) { context->GetValue("rti")->Set("cross"); return true; } context->GetValue("rti")->Set("skull"); return true; } bool KologarnCrunchArmorAction::isUseful() { KologarnCrunchArmorTrigger kologarnCrunchArmorTrigger(botAI); return kologarnCrunchArmorTrigger.IsActive(); } bool KologarnCrunchArmorAction::Execute(Event event) { bot->RemoveAura(SPELL_CRUNCH_ARMOR); return true; } bool HodirMoveSnowpackedIcicleAction::isUseful() { // Check boss and it is alive Unit* boss = AI_VALUE2(Unit*, "find target", "hodir"); if (!boss || !boss->IsAlive()) { return false; } // Check if boss is casting Flash Freeze if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_FLASH_FREEZE)) { return false; } // Find the nearest Snowpacked Icicle Target Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f); if (!target) return false; // Check that bot is stacked on Snowpacked Icicle if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= 5.0f) { return false; } return true; } bool HodirMoveSnowpackedIcicleAction::Execute(Event event) { Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f); if (!target) return false; return MoveTo(target->GetMapId(), target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL, true); } bool HodirBitingColdJumpAction::Execute(Event event) { bot->RemoveAurasDueToSpell(SPELL_BITING_COLD_PLAYER_AURA); return true; // Backup when the overall strategy without cheat will be more vialable // int mapId = bot->GetMap()->GetId(); // int x = bot->GetPositionX(); // int y = bot->GetPositionY(); // int z = bot->GetPositionZ() + 3.98f; // float speed = 7.96f; // UpdateMovementState(); // if (!IsMovingAllowed(mapId, x, y, z)) //{ // return false; // } // MovementPriority priority; // if (IsWaitingForLastMove(priority)) //{ // return false; // } // MotionMaster& mm = *bot->GetMotionMaster(); // mm.Clear(); // mm.MoveJump(x, y, z, speed, speed, 1, AI_VALUE(Unit*, "current target")); // mm.MoveFall(0, true); // AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), 1000, priority); // return true; } bool FreyaMoveAwayNatureBombAction::isUseful() { // Check boss and it is alive Unit* boss = AI_VALUE2(Unit*, "find target", "freya"); if (!boss || !boss->IsAlive()) { return false; } // Find the nearest Nature Bomb GameObject* target = bot->FindNearestGameObject(GOBJECT_NATURE_BOMB, 12.0f); if (!target) return false; return true; } bool FreyaMoveAwayNatureBombAction::Execute(Event event) { GameObject* target = bot->FindNearestGameObject(GOBJECT_NATURE_BOMB, 12.0f); if (!target) return false; return FleePosition(target->GetPosition(), 13.0f); } bool FreyaMarkDpsTargetAction::isUseful() { FreyaMarkDpsTargetTrigger freyaMarkDpsTargetTrigger(botAI); return freyaMarkDpsTargetTrigger.IsActive(); } bool FreyaMarkDpsTargetAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "freya"); if (!boss || !boss->IsAlive()) return false; Unit* targetToMark = nullptr; // Check which adds is up Unit* eonarsGift = nullptr; Unit* ancientConservator = nullptr; Unit* snaplasher = nullptr; Unit* ancientWaterSpirit = nullptr; Unit* stormLasher = nullptr; Unit* firstDetonatingLasher = nullptr; GuidVector targets = AI_VALUE(GuidVector, "possible targets"); Unit* target = nullptr; for (auto i = targets.begin(); i != targets.end(); ++i) { target = botAI->GetUnit(*i); if (!target || !target->IsAlive()) continue; if (target->GetEntry() == NPC_EONARS_GIFT) { eonarsGift = target; } else if (target->GetEntry() == NPC_ANCIENT_CONSERVATOR) { ancientConservator = target; } else if (target->GetEntry() == NPC_SNAPLASHER) { snaplasher = target; } else if (target->GetEntry() == NPC_ANCIENT_WATER_SPIRIT) { ancientWaterSpirit = target; } else if (target->GetEntry() == NPC_STORM_LASHER) { stormLasher = target; } else if (target->GetEntry() == NPC_DETONATING_LASHER && !firstDetonatingLasher) { firstDetonatingLasher = target; } } // Check that eonars gift is need to be mark if (eonarsGift) { targetToMark = eonarsGift; } // Check that ancient conservator is need to be mark if (ancientConservator && !targetToMark) { targetToMark = ancientConservator; } // Check that trio of adds is need to be mark if ((snaplasher || ancientWaterSpirit || stormLasher) && !targetToMark) { Unit* highestHealthUnit = nullptr; uint32 highestHealth = 0; if (snaplasher && snaplasher->GetHealth() > highestHealth) { highestHealth = snaplasher->GetHealth(); highestHealthUnit = snaplasher; } if (ancientWaterSpirit && ancientWaterSpirit->GetHealth() > highestHealth) { highestHealth = ancientWaterSpirit->GetHealth(); highestHealthUnit = ancientWaterSpirit; } if (stormLasher && stormLasher->GetHealth() > highestHealth) { highestHealthUnit = stormLasher; } // If the highest health unit is not already marked, mark it if (highestHealthUnit) { targetToMark = highestHealthUnit; } } // Check that detonating lasher is need to be mark if (firstDetonatingLasher && !targetToMark) { targetToMark = firstDetonatingLasher; } if (!targetToMark) { return false; // No target to mark } bool isMainTank = botAI->IsMainTank(bot); Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Player* mainTank = mainTankUnit ? mainTankUnit->ToPlayer() : nullptr; int8 squareIndex = 5; // Square int8 skullIndex = 7; // Skull if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a real player { // Iterate through the first 3 bot tanks to assign the Skull marker for (int i = 0; i < 3; ++i) { if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank { Group* group = bot->GetGroup(); if (group) { ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex); if (!currentSkullTarget || (targetToMark->GetGUID() != currentSkullTarget)) { group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); group->SetTargetIcon(squareIndex, bot->GetGUID(), boss->GetGUID()); return true; } } break; } } } else if (isMainTank) // Bot is the main tank { Group* group = bot->GetGroup(); if (group) { ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex); if (!currentSkullTarget || (targetToMark->GetGUID() != currentSkullTarget)) { group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); group->SetTargetIcon(squareIndex, bot->GetGUID(), boss->GetGUID()); botAI->GetAiObjectContext()->GetValue("rti")->Set("square"); return true; } } } return false; } bool FreyaMoveToHealingSporeAction::isUseful() { FreyaMoveToHealingSporeTrigger freyaMoveToHealingSporeTrigger(botAI); return freyaMoveToHealingSporeTrigger.IsActive(); } bool FreyaMoveToHealingSporeAction::Execute(Event event) { GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); Creature* nearestSpore = nullptr; float nearestDistance = std::numeric_limits::max(); for (auto guid : targets) { Unit* unit = botAI->GetUnit(guid); if (!unit) continue; // Check if this unit is a healthy spore and alive if (unit->GetEntry() != NPC_HEALTHY_SPORE || !unit->IsAlive()) continue; float distance = bot->GetDistance2d(unit); if (distance < nearestDistance) { nearestDistance = distance; nearestSpore = static_cast(unit); } } if (!nearestSpore) return false; return MoveTo(nearestSpore->GetMapId(), nearestSpore->GetPositionX(), nearestSpore->GetPositionY(), nearestSpore->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); }