From 7d6f44ab0926d26e98beebc110f320f3ff5148d4 Mon Sep 17 00:00:00 2001 From: Noscopezz Date: Tue, 17 Jun 2025 01:18:59 +0200 Subject: [PATCH] ICC Fix/Improve (#1380) * ICC PP WIP WIP * added mutated plague for PP * BPC added (kinetic and boss targeting need to be done by player) OT collects dark nucles, bots spread on vortex and other stuff they do ok on their own. Tested only on 10NM, should work on 25NM * Tank pos. correction * BQL, ranged spread, link, flame, bite, tanking tested 10NM to do (better fire spread, hc tacti, melee spread when in air) * LDW improved improved shadow logic, ranged spread for easier shadow handling * dbs update, fixed teleporting Bots should only go and teleport to the mage that is actually below zero now * DBS ranged fix Ranged should spread more quickly and freak out less * Festergut && DBS fixed ranged spread (both) fixed spore logic (fester) * Rotface fix Improved big ooze tanking (static pos, todo kiting) ooze explosion spread mechanic fix ooze pool fix Player needs to mark rotface with skull icon, oterwise bots try to attack oozes * BQL fixed for 25nm todo: better melee logic in air phase, better melee flame spread * VDW, Sister Svalna, Sindy update Sister Svalna, bots can pickup spears and throw at svalna when she has shield up VDW added healer strats to use portal and heal boss (atm druids are for raid healing only, so use druide + any other healer, ideally player should be healer) todo (focus on supressers, add healer rotations, atm they use quickest spell they can) Sindragosa Added tank boss manipulation (boss orientation and position) bots detect (buffet, unchained magic and chilled to the bone and act accordingly) bots detect frost beacon move to safe spot and los frost bombs around them, while dpsing tombs (todo stop dps if only one tomb is left, if we have frost bombs around, not a big deal atm since in nm they dont one shot) Last phase bots los behind tomb to loose buffet, tanks swap when they have hi buffet count. Player should tell bots with skull rti if they should kill tomb or focus boss. todo (dynamic tomb los in last phase so that healers can see tank but also hide behind tomb to break los from boss) Removed some debug messages, improved LM spike action (nearest bots also try to help kill it) Improved Lady Deathwshiper shade action (only targeted bots will run away instead of every bot that is near it) dbs improved tank switch I recommend to use 3 healers (just to be safe) and 2 paladin tanks (warr seems to struggle with agro) in 10 man 25 man 6-7 healers (just to be safe) Since most of the bosses are about survival and not dps * LK Update (doable) LK added Improved tank switching for all bosses Fixed PP gas cloud kiting Malleable goo todo (dont know how to detect it since its not object or npc) just summon ranged bots to safe position to bypass BPC fixed OT sometimes not tanking kele kinectic bombs todo (for now player should take care of them) Sindragosa fixed rare case when she is in air phase but tombs went to last phase position LK Bots can handle necrotic Bots can handle adds Bots should focus valkyre that actually grabbed someone (if unlucky and player just use attack command and summon bots to you if they are far away from you) if they grab bots you can either summon to make them useless or let bots cc them and do it legit way. Defile should be watched by player and once it was cast just summon bots to you Vile spirits for some reason go to the ground and get nuked by bots aoe spells so there is not much to be done **Player needs to be alive the whole LK fight since you will have to watch out for frost spehers (sometimes bots ignore them), summon bots when defile is up and summon ranged bots if they get stuck near shambling or raging spirits since their aoe will wipe you) all in all LK is doable both 10 and 25nm, player needs to have knowledge of lk fight and needs to know how to use multibot addon and make macros for eg summoning or commanding groups of bots or individual bots) Dont forget frost/shadow/nature resist auras in whole ICC since it will help alot I have done whole icc 10 and 25 with 2 pala tanks, 2/5 heals and rest dps, if you use +1 or +2 heals it should be easier (since I was testing I did close to 0 dmg in fights same with heals) * fixed changes made by mistake fix * Malleable fix (simple spread mechanic) Malleable mechanic added (simple spread for now) Gas cloud fixed (Bots sometimes got stuck between puddle and kite location) * Defile Update Bots detect and avoid defile (they struggle to find a way back to the boss around it tho, use summon to help them) Melee bots should be able to stand behind/to the flank of shambling/spirits * GS fixed bots not returning to their ship for A and H Bots will return back to ship after killing mage * PP gas cloud kiting improved PP gas cloud kiting improved * BPC targeting fixed Bots will mark valid prince with skull RTI now * BQL added melee spread in air pahse BQL added melee spread * VDW healing rotation improved Healers will now use strong heals and hots * Fixed Necrotic Plague Fixed issue with Necrotic where it would get dispelled too soon, or would not get dispelled at all * LK Update Refined defile logic Added 3 points for ranged and melee in winter phase (east, west, south when facing throne) fixed frost spheres targeting (hunter will focus them) Atm bots will reset z axis if they fall underground or if they get teleported by lk Better positioning in 1st phase * 10HC update until PP LK defile improved for 10nm (bots sometimes stood 2 close to defile until it grew few times) Improved rotface for HC PP remade for 10HC. Gas cloud is now properly kited Fixed a rare case of server crash when there were ooze and gas cloud alive at same time. Bots will move around puddles according to its size now. Bots that get unboud plague will simply move away from raid and die thus loosing it from raid. Volatile ooze improved stacking. Fixed ranged sometimes glitching thru walls when spreading out from other members. 10HC PP is now doable but its hard without summoning (summoning break gascloud and ooze targets so its easier to do). You need to watch boss so you dont phase 2 soon otherwise you will get 2x ooze and cloud which is almoust always a wipe. If abo is not played near perfection bots will struggle with oozes and gas clouds if they are not slowed on time. Always save energy to slow gas cloud since it will wipe the group if it reach its target. Bots will sometimes stand in puddle, just command them to move and they will figure out what to do. todo (proper malleable handling) * Up until Sindy 10HC BPC added shadow prison handling, bots stop moving if more than 12 stack, tanks more than 18 Improved spreading logic VDW fixed issue where bots in portal wold move at half speed compared to real player * fixed accidental change * LK 10HC update Added Shadow trap logic (if they stand in it, not a big deal since bots wont get yeeted only players will) When harvest soul, only player will be in another dimension (you must survive) **Sindy and LK can be done, but I must dissapoint purist, at my skill level I could not achieve to do HC without using summon or other "cheat" bot functions. Other bosses are all doable now in 10hc * ICC fixes GS, PP; BQL, SINDY Minor fixes, bite action improved * ICC improve Sindy Bots will now choose non beacon position based on difficulty, 10/25 * ICC fixed missing A/H buff Fixed missing ICC buff for A and H Buff will only be present when logged on and in ICC, once any bot or player leave ICC the buff is gone to prevent abuse. This will make ICC easier now and with recent DPS update and movement improvement bots will now actully do decent dps and even greater healing. Ally buff https://www.wowhead.com/wotlk/spell=73828/strength-of-wrynn Horde buff https://www.wowhead.com/wotlk/spell=73822/hellscreams-warsong * revert last change revert buff * ICC improve Rotface Bots will now mark Rotface with skull icon, which will make them focus boss instead of oozes automatically * ICC Festergut 10 Man fix There was a rare case in 10 Man when 2 tanks would get spores which made them both stack at melee spot. Now the code will check if any of the spored player is main tank, if it is, it will stay at melee and other spores will go at ranged spot since off tank doesn't really need to stand near main tank all the time. * ICC BPC major update, fix and improve Fixed main tank sometimes not tanking both bosses (vala and talda) Improved marking of current prince Empowered vortex: bots will now spread out when it is being cast, instead of always spreading(ranged). This will make melee also spread better now since bots will calculate and move to optimal positions. Added Kinetic bomb handling. Hunters will take care of bombs, if no hunter is present then any ranged dps will take care of kinetic bombs. * ICC BQL/VDW major update + minor fixes/improvements LDW improved spreading for ranged, tanks will move boss in the middle of the room now when shield is depleted which will make bots bug less around pillars GS Assist tank will not teleport to enemy ship and will keep tanking adds on our ship now DBS ranged spread improved (they will calcualte spot for each bot and stick with it reducing movment to minimum), fixed bug where in 25 man ranged bots would go behind walls, making them unable to dps boss, improved rune of blood tanking Festergut ranged spread improved (they will calcualte spot for each bot and stick with it reducing movment to minimum) BQL Melee spread improved, bite logic improved, added swarming shadows logic (not perfect but at least it wont be all over the room anymore), Tanks will properly handle blood mirror now VDW boss and raid healers will be automatically assinged depening by number of healers(if more than 3 healers in group, 2 will focus on raid and others will heal boss, otherwise one healer will heal raid). Druid will be assigned to raid healing if no druid present then some other random heal class. Added rotations for druid healers if they end up healing the boss. Raid healers will not use portals anymore. Healers will come to the ground now after using portals (they were stuck in air) * ICC LK minor update PP Removed pre stacking for ranged when volatile ooze spawn (they will kill it much faster now and only stack if someone actually gets targeted by ooze) LK Hunters will use trnaq shot to remove enrage from shamblings Improved valkyr cc Minimized ping-ponging during winter phase * ICC minor Sindy improve Reduced position tolerances and forced movement for beacon and non beacon positions which will make bots move to spot that they actually need to be at instead of randomly running to sindragosa or beaconed players. * ICC minor update GS Bots will mark mage with skull rti Rotface Fixed bots glitching thru walls and floors (added check if position is valid before moving) PP Bots will mark volatile ooze with skull rti now which will help them focus it and kill asap (usefull for heroic when both volatile ooze and gas cloud are present at the same time) VDW Added default group position in the middle if the room so that bots don't spread out too much which will force them to focus supressers more Fixed Boss healers not keeping themself alive when low on HP * ICC LK minor update Commented out z axis bypass since it was fixed with recent core updates. Bots no longer fall thru buggy platforms so its no longer needed which in return makes valkyrs works as they should. * ICC LM & LDW Improved LM Removed Attack Action since it was buggy and replaced it with RTI. (Improved DPS) Simplified trigger for spike action, since attack logic is handled by skull RTI now. Bots (tanks) will mark spikes with skull RTI, which will make bots instantly switch to Spike targets. If no spike is present, the boss will be marked. Added logic that will make non-tank bots move away from LM to a fixed position behind LM if they happened to be in front of LM (No more insta kills with cleave). If LM is casting Bone Storm, bots will ignore the above logic and do max dps. LDW Removed Attack Action since it was buggy and replaced it with RTI. (Improved DPS) Bots (tanks) will mark adds with skull RTI, which will make bots instantly switch to adds targets. If no spike is present, the boss will be marked, which will help with keeping allies alive when they get mind controlled by LDW. Moved 2nd phase position deeper into the room and reduced boss hp trigger to 95%, which will keep tanks where they should be during 1st phase (sometimes the boss dropped below 98% and tanks would ping pong around the room). Ranged bots will now spread closer to each other now and will ignore spreading if they are 2 far or 2 close to the boss so they can properly reposition. These changes should also fix recent bugs with bots not casting/standing still on LM and LDW. * ICC DBS Improve Replaced attack action with RTI. (Improved DPS) Ranged bots will move away from blood beast if targeted by it now. * ICC Sindragosa and LK Improve Sindragosa Improved tomb los for frost bombs. Bots will now mark tomb with moon rti to avoid killing it too soon. (If they still attack the tomb, simply reset and after bombs are over, simply attack the tomb to kill or mark with a skull.) Main Tank will reset mystic buffet stacks now to avoid tank swapping since it's really unreliable which will make sindy less frustrating to kill (other bots will need to do mechanics properly by hiding behind ice tombs). LK Necrotic plague action Bots will now check before moving to the Shambling if they are in front of the Shambling to avoid getting one shoted by shockwave. Improved multiplier, which will now properly handle dispelling of necrotic plague. Winter phase action Bots will properly move behind adds to avoid getting one shoted by shamblings or raging spirits. If there is a hunter in the grouphunter will focus on spheres; if not, any ranged DPS will focus spheres. Adds action Bots will now be in proper positions during the 1st phase, and when the off tank is done collecting shamblings, it will move away from the group. Bots (non-tanks) will move behind the shambling if they are in front of it to avoid getting one shoted in the 1st phase. Restored action for checking if bots are at ground level, since they were still sometimes glitching through the floor (valkrys will be able to carry bots and drop them off the edge if not killed in time). Improved defile calculations for 10 man and 25 man raids since defile was growing differently for 10 and 25, which would make bots in 25 man move very far away from somewhat small defile puddle. Bots will now properly CC any valkyr that wasn't CC'd, and they won't freak out anymore if multiple valkyrs are present. (They can't tell which valkyr is holding bots/ players, so we should command an attack on valkyrs that are holding someone.) Bots will now run away from Vile Spirits if they are targeted by them during the last phase. * ICC 25HC ldw shades fix Fixed bots could not detect shade if there were multiple present * ICC trigger optimization Optimized trigger and action for: KineticBomb OozePuddle * ICC LK Improve Improved Shadow Trap, Winter phase and 1st phase. Bots will now have fixed positions for 1st phase if no shadow trap is present. Tanks will now take care of taunting adds during winter phase. (Assist tank will collect adds and bring them to main tank, main tank will take over the adds so we have minimal chance of adds rotating towards the raid) Fixed shadow trap detection. * ToC dungeon update Added TOC dungeon strategies for 1st encounter. Other encounters are pretty easy and straightforward so they do not require strategies. They can be all done with follow, reset, summon, attack, skull icon etc... Bots (A/H) will automatically check if they have lance equiped or in inventory. If they don't have lance they will pick it up from nearest lance stand and equip it. Bots will mount nearest mount if they have lance equipped. Once mounted they will keep Defend aura at 3 stacks (top priority). In fight they will cast charge if more than 5f away, they will cast shield break if target has Defended aura active and they will use Thrust if they cant use anything else. In short they will melt any target and enemies wont stand a chance keeping their defend aura up. Once champions are defeated bot will automatically equip best weapon they have in inventory (weapon that was swapped with lance) * ICC minor update LM Bots will now move in smaller increments to get behind boss They will still move towards fixed position, but as soon they are not in front of boss, they will stop moving and do other actions. GS Fixed cannon trigger, now will return true if cannon is 100f from bots. It used to return true all the time. * ToC added Eadric strat bots will look away during radiance cast * ICC LK minor fix fixed crash in icclichkingsaddsaction * ICC GS fix While encountering edge case when playing horde, the logic was to detect mages, there are usually 2 around, but the longer the fight last the bigger the chance that cannons would kill all the mages thus once we kill the mage that was casting deep freeze bots could not execute action that would return them back to the ship, so the boss had the time to nuke them one by one. Even tanks have hard time surviving the boss. I have changed it now (both A/H) so that we keep track of the enemy boss (which is hard if not impossible to kill as main trigger) and we are keeping track of the mage that is actually casting deep freeze, we mark it with skull, and now as soon the mage is dead, bots will remove the mark so there is 0 confusion about what to do next on their side, and they will come back asap. * FoS Improved and fixed, ICC LM, LDW and PP updates FoS Improved/fixed triggers, Improved/fixed actions. Bronjahm Tank will properly kite soul fragment around the room Replaced attack action with skull rti to focus soul fragments Non-tank bots will only try to stay within 10f of boss only once we enter last phase, tank will be in the middle of the room if it is not kiting soul fragment Devourer of Souls DPS bots will stop dps if boss is casting mirrored soul Laser can be easily avoided with follow/summon ICC LM Tank will only move to tank position if it actually have the agro, it used to move there without agro and few of the bots would get killed since tank was unable to reach bot LDW Fixed edge case where tank would be target of shade and it would be unable to move away it from it due to tank position restrictions, now if Tank is targeted by shade it will be able to move and run away from it (in hc this could cause wipes since explosion is aoe + tons of dmg) PP When oozes spawn, tank will move towards green ooze spawn to keep raid near green ooze (faster kill) and far away from gas cloud (better chance to kill if abo didn't manage to slow it) * PoS Strategies Ick and Krick All boss mechanics implemented Forgemaster Garfrost Easily doable with follow/stay/summon Scourgelord Tyrannus Ranged bots will spread so that they all don't get frozen by dragon They avoid everything else on their own * ICC Refactor, All bosses improved NM and HC Major Update * Update playerbots.conf.dist * ICC Fixes/Improvements LM Assist tank moving to kill spikes (it should always stack with main tank to soak dmg) LDW Improved shade evasion (added los checks before moving to avoid clipping thru walls and floor) Fixed tanks moving to tank position befor having aggro Festergut Fixed ranged position overlaping with each other Fixed sometimes hunters were placed to close to the boss PP Improved gas cloud kiting, bots should not get stuck in corners anymore while moving away Gas cloud target will use nitro boosts now, so that players dont wipe if abo fails to slow gas cloud Due to possible oozes duplications in hc caused by bad phasing, bots will kill duplicates to make encounter a bit easier BQL Fixed bots failing to link up in 25man VDW Group 1 and Group 2 can move freely now around the room, but once group set their marker they will focus left or right side depending on their assigned position (Positions were to limiting in 25 man and they could not kill adds fast enough) Fixed marker overlaping Optimized cloud collecting for portal bots LK Fixed in winter phase tank not collecting adds if more than one add is present * Hotfix Possible crash fix prevention Increased kiting radius for assist tank on Rotface * minor improve Added nitro boosts on sindy for beaconed player in last phase (they could be all over the place and wipe raid since they could not reach position in time) --- .../raids/icecrown/RaidIccActions.cpp | 1008 +++++++++++++---- src/strategy/raids/icecrown/RaidIccActions.h | 2 +- .../raids/icecrown/RaidIccMultipliers.cpp | 69 +- .../raids/icecrown/RaidIccTriggers.cpp | 64 +- src/strategy/raids/icecrown/RaidIccTriggers.h | 2 +- 5 files changed, 899 insertions(+), 246 deletions(-) diff --git a/src/strategy/raids/icecrown/RaidIccActions.cpp b/src/strategy/raids/icecrown/RaidIccActions.cpp index 833e1d8a..5207290a 100644 --- a/src/strategy/raids/icecrown/RaidIccActions.cpp +++ b/src/strategy/raids/icecrown/RaidIccActions.cpp @@ -27,7 +27,7 @@ bool IccLmTankPositionAction::Execute(Event event) if (isBossInBoneStorm) return false; - if (botAI->HasAggro(boss) && botAI->IsMainTank(bot)) + if (botAI->HasAggro(boss) && botAI->IsMainTank(bot) && boss->GetVictim() == bot) { const float maxDistanceThreshold = 3.0f; const float distance = bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); @@ -39,12 +39,18 @@ bool IccLmTankPositionAction::Execute(Event event) if (botAI->IsAssistTank(bot)) { const float maxDistanceThreshold = 3.0f; - const float distance = - bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); + const float distance = bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY()); if (distance > maxDistanceThreshold) return MoveTowardPosition(ICC_LM_TANK_POSITION, maxDistanceThreshold); + + if (distance < maxDistanceThreshold) + { + bot->SetFacingToObject(boss); + return true; + } } + return false; } @@ -308,7 +314,7 @@ bool IccAddsLadyDeathwhisperAction::Execute(Event event) const uint32 shadeEntryId = NPC_SHADE; - if (botAI->IsTank(bot) && boss->HealthBelowPct(95)) + if (botAI->IsTank(bot) && boss && boss->HealthBelowPct(95) && boss->GetVictim() == bot) { // Check if the bot is not the victim of a shade if (IsTargetedByShade(shadeEntryId)) @@ -317,8 +323,7 @@ bool IccAddsLadyDeathwhisperAction::Execute(Event event) const float maxDistanceToTankPosition = 20.0f; const float moveIncrement = 3.0f; - const float distance = - bot->GetExactDist2d(ICC_LDW_TANK_POSTION.GetPositionX(), ICC_LDW_TANK_POSTION.GetPositionY()); + const float distance = bot->GetExactDist2d(ICC_LDW_TANK_POSTION.GetPositionX(), ICC_LDW_TANK_POSTION.GetPositionY()); if (distance > maxDistanceToTankPosition) { @@ -441,9 +446,27 @@ bool IccShadeLadyDeathwhisperAction::Execute(Event event) // Move away from the Vengeful Shade if the bot is too close if (currentDistance < SAFE_DISTANCE) { - // Forces bot to stop channeling or getting locked by any other action - botAI->Reset(); - return MoveAway(shade, SAFE_DISTANCE - currentDistance); + // Calculate direction away from shade + float dx = bot->GetPositionX() - shade->GetPositionX(); + float dy = bot->GetPositionY() - shade->GetPositionY(); + float dist = std::sqrt(dx * dx + dy * dy); + + if (dist < 0.001f) + continue; + + dx /= dist; + dy /= dist; + + float moveDistance = SAFE_DISTANCE - currentDistance; + float targetX = bot->GetPositionX() + dx * moveDistance; + float targetY = bot->GetPositionY() + dy * moveDistance; + float targetZ = bot->GetPositionZ(); + + if (bot->IsWithinLOS(targetX, targetY, targetZ)) + { + botAI->Reset(); + MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } } } @@ -1329,9 +1352,9 @@ bool IccFestergutGroupPositionAction::PositionNonTankMembers() // Position calculation parameters constexpr float tankToBossAngle = 4.58f; - constexpr float minBossDistance = 20.0f; + constexpr float minBossDistance = 15.0f; constexpr float spreadDistance = 10.0f; - constexpr int columnsPerRow = 5; + constexpr int columnsPerRow = 6; // Calculate grid position int row = positionIndex / columnsPerRow; @@ -1365,57 +1388,126 @@ bool IccFestergutGroupPositionAction::PositionNonTankMembers() int IccFestergutGroupPositionAction::CalculatePositionIndex(Group* group) { - int healerIndex = -1; - int rangedDpsIndex = -1; - int currentHealerIndex = 0; - int currentRangedDpsIndex = 0; - int totalHealers = 0; + std::vector healerGuids; + std::vector rangedDpsGuids; + std::vector hunterGuids; - // First pass: count total healers + // Collect all eligible members with their GUIDs for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* member = itr->GetSource(); if (!member || !member->IsAlive() || botAI->IsTank(member)) continue; + ObjectGuid memberGuid = member->GetGUID(); + if (botAI->IsHeal(member)) - totalHealers++; - } - - // Second pass: determine position - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || botAI->IsTank(member)) - continue; - - if (member == bot) { - if (botAI->IsHeal(bot)) - healerIndex = currentHealerIndex; - else if (botAI->IsRanged(bot)) - rangedDpsIndex = currentRangedDpsIndex; - break; + healerGuids.push_back(memberGuid); } - - if (botAI->IsHeal(member)) - currentHealerIndex++; else if (botAI->IsRanged(member)) - currentRangedDpsIndex++; + { + if (member->getClass() == CLASS_HUNTER) + { + hunterGuids.push_back(memberGuid); + } + else + { + rangedDpsGuids.push_back(memberGuid); + } + } } - // Calculate final position index - if (healerIndex != -1) + // Sort GUIDs for consistent ordering + std::sort(healerGuids.begin(), healerGuids.end()); + std::sort(rangedDpsGuids.begin(), rangedDpsGuids.end()); + std::sort(hunterGuids.begin(), hunterGuids.end()); + + ObjectGuid botGuid = bot->GetGUID(); + + // Find bot's position among healers + auto healerIt = std::find(healerGuids.begin(), healerGuids.end(), botGuid); + if (healerIt != healerGuids.end()) { - // Healers in first two rows - int healersPerRow = (totalHealers + 1) / 2; // Round up - healerIndex = (healerIndex < healersPerRow) ? healerIndex : healerIndex - healersPerRow + 5; - return healerIndex; + int healerIndex = std::distance(healerGuids.begin(), healerIt); + int totalHealers = healerGuids.size(); + + // Healers in first two rows, distributed evenly + int healersPerRow = (totalHealers + 1) / 2; // Round up for first row + + if (healerIndex < healersPerRow) + { + // First row of healers (positions 0-5) + return healerIndex; + } + else + { + // Second row of healers (positions 6-11) + return healerIndex - healersPerRow + 6; + } } - else if (rangedDpsIndex != -1) + + // Find bot's position among non-hunter ranged DPS + auto rangedIt = std::find(rangedDpsGuids.begin(), rangedDpsGuids.end(), botGuid); + if (rangedIt != rangedDpsGuids.end()) { - // Ranged DPS after healers - return totalHealers + rangedDpsIndex; + int rangedIndex = std::distance(rangedDpsGuids.begin(), rangedIt); + int totalHealers = healerGuids.size(); + + // Non-hunter ranged DPS fill remaining spots in healer rows first + int healerSpotsUsed = totalHealers; + int remainingSpotsInHealerRows = 12 - healerSpotsUsed; // 2 rows of 6 spots each + + if (rangedIndex < remainingSpotsInHealerRows) + { + // Fill remaining spots in healer rows + if (healerSpotsUsed < 6) + { + // Fill remaining spots in first row + return healerSpotsUsed + rangedIndex; + } + else + { + // Fill remaining spots in second row + int spotsInFirstRow = 6; + int spotsInSecondRow = healerSpotsUsed - 6; + int remainingInSecondRow = 6 - spotsInSecondRow; + + if (rangedIndex < remainingInSecondRow) + { + return 6 + spotsInSecondRow + rangedIndex; + } + else + { + // Move to third row + return 12 + (rangedIndex - remainingInSecondRow); + } + } + } + else + { + // Start new rows for remaining ranged DPS + return 12 + (rangedIndex - remainingSpotsInHealerRows); + } + } + + // Find bot's position among hunters + auto hunterIt = std::find(hunterGuids.begin(), hunterGuids.end(), botGuid); + if (hunterIt != hunterGuids.end()) + { + int hunterIndex = std::distance(hunterGuids.begin(), hunterIt); + int totalHealers = healerGuids.size(); + int totalRangedDps = rangedDpsGuids.size(); + + // Hunters start after healers and non-hunter ranged DPS + // But ensure they're at least in the second row (position 6+) + int basePosition = totalHealers + totalRangedDps + hunterIndex; + + // If position would be in first row (0-5), move to second row minimum + if (basePosition < 6) + basePosition = 6 + hunterIndex; + + return basePosition; } return -1; @@ -1444,8 +1536,11 @@ bool IccFestergutSporeAction::Execute(Event event) // Move to position if not already there if (bot->GetExactDist2d(targetPos) > POSITION_TOLERANCE) + { + botAI->Reset(); return MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(), true, false, false, true, MovementPriority::MOVEMENT_FORCED); + } return hasSpore; } @@ -1567,7 +1662,9 @@ void IccRotfaceTankPositionAction::MarkBossWithSkull(Unit* boss) bool IccRotfaceTankPositionAction::PositionMainTankAndMelee(Unit* boss) { - bool isBossCasting = boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY); + bool isBossCasting = false; + if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY)) + bool isBossCasting = true; if (bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION) > 7.0f && botAI->HasAggro(boss) && botAI->IsMainTank(bot)) MoveTo(bot->GetMapId(), ICC_ROTFACE_CENTER_POSITION.GetPositionX(), @@ -1649,8 +1746,8 @@ bool IccRotfaceTankPositionAction::HandleBigOozePositioning(Unit* boss) // If we have the ooze's aggro, kite it in a larger circular pattern between 20f and 30f from the center if (bigOoze->GetVictim() == bot) { - const float minRadius = 17.0f; - const float maxRadius = 25.0f; + const float minRadius = 24.0f; + const float maxRadius = 34.0f; const float safeDistanceFromOoze = 13.0f; const float puddleSafeDistance = 30.0f; const Position centerPosition = ICC_ROTFACE_CENTER_POSITION; @@ -1922,7 +2019,10 @@ bool IccRotfaceGroupPositionAction::PositionRangedAndHealers(Unit* boss,Unit *sm return false; Difficulty diff = bot->GetRaidDifficulty(); - bool isBossCasting = boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY); + bool isBossCasting = false; + if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY)) + bool isBossCasting = true; + bool isHeroic = (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC); // Move to the exact same position as the boss during slime spray @@ -2342,6 +2442,25 @@ bool IccPutricideVolatileOozeAction::Execute(Event event) if (botAI->HasAura("Gaseous Bloat", bot) || botAI->HasAura("Unbound Plague", bot)) return false; + // Find all alive oozes + std::vector aliveOozes; + const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (const auto& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == ooze->GetEntry()) + aliveOozes.push_back(unit); + } + + // If more than one ooze is alive, kill all but one + if (aliveOozes.size() > 1) + { + for (size_t i = 0; i < aliveOozes.size() - 1; ++i) + { + bot->Kill(bot, aliveOozes[i]); + } + } + // Mark ooze with skull MarkOozeWithSkull(ooze); @@ -2438,6 +2557,25 @@ bool IccPutricideGasCloudAction::Execute(Event event) bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot); Unit* volatileOoze = AI_VALUE2(Unit*, "find target", "volatile ooze"); + // Find all alive gasCloud + std::vector aliveGasCloud; + const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (const auto& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == gasCloud->GetEntry()) + aliveGasCloud.push_back(unit); + } + + // If more than one GasCloud is alive, kill all but one + if (aliveGasCloud.size() > 1) + { + for (size_t i = 0; i < aliveGasCloud.size() - 1; ++i) + { + bot->Kill(bot, aliveGasCloud[i]); + } + } + // Skip if we have no aura but ooze exists if (!hasGaseousBloat && volatileOoze) return false; @@ -2450,10 +2588,20 @@ bool IccPutricideGasCloudAction::Execute(Event event) bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) { - static const int NUM_ANGLES = 16; - static const float MIN_SAFE_DISTANCE = 25.0f; - static const float EMERGENCY_DISTANCE = 8.0f; + bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot); + + if (!hasGaseousBloat) + return false; + + if (hasGaseousBloat && !bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, + bot); // to make it a bit easier when abo fails to slow or bots take forever to kill oozes + + static const int NUM_ANGLES = 32; // Increased from 16 for better corner escape + static const float MIN_SAFE_DISTANCE = 35.0f; + static const float EMERGENCY_DISTANCE = 15.0f; static const float GAS_BOMB_SAFE_DIST = 6.0f; + static const float MOVEMENT_INCREMENT = 5.0f; // Fixed movement increment Position botPos = bot->GetPosition(); Position cloudPos = gasCloud->GetPosition(); @@ -2483,10 +2631,10 @@ bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) dx /= dist; dy /= dist; - // Try to find safe movement position + // Try to find safe movement position with strict corner avoidance Position bestPos; bool foundPath = false; - float bestGasBombDist = 0.0f; + float bestScore = 0.0f; for (int i = 0; i < NUM_ANGLES; i++) { @@ -2494,7 +2642,8 @@ bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) float rotatedDx = dx * cos(angle) - dy * sin(angle); float rotatedDy = dx * sin(angle) + dy * cos(angle); - for (float testDist = 5.0f; testDist <= 15.0f; testDist += 5.0f) + // Only test positions at fixed increments of 5.0f + for (float testDist = MOVEMENT_INCREMENT; testDist <= 20.0f; testDist += MOVEMENT_INCREMENT) { float testX = botPos.GetPositionX() + rotatedDx * testDist; float testY = botPos.GetPositionY() + rotatedDy * testDist; @@ -2515,11 +2664,44 @@ bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) if (newCloudDist > cloudDist && minGasBombDist >= GAS_BOMB_SAFE_DIST && bot->IsWithinLOS(testX, testY, testZ)) { - // Prefer positions with greater minGasBombDist - if (!foundPath || minGasBombDist > bestGasBombDist) + // Strict corner avoidance - test movement freedom thoroughly + int freeDirections = 0; + static const int CHECK_DIRS = 16; // More directions for better detection + static const float CHECK_DIST = 8.0f; // Longer distance to detect walls/corners early + + for (int j = 0; j < CHECK_DIRS; j++) + { + float checkAngle = (2 * M_PI * j) / CHECK_DIRS; + float checkX = testX + cos(checkAngle) * CHECK_DIST; + float checkY = testY + sin(checkAngle) * CHECK_DIST; + if (bot->IsWithinLOS(checkX, checkY, testZ)) + freeDirections++; + } + + float freedomScore = (float)freeDirections / CHECK_DIRS; + + // STRICT: Reject any position that looks like a corner or restricted area + // Require at least 80% of directions to be free (13 out of 16) + if (freedomScore < 0.8f) + continue; // Skip this position entirely + + // Also check for "dead ends" - positions that might lead to corners + // Test if we can move further in the same direction + bool canContinueMoving = true; + float continueX = testX + rotatedDx * MOVEMENT_INCREMENT; + float continueY = testY + rotatedDy * MOVEMENT_INCREMENT; + if (!bot->IsWithinLOS(continueX, continueY, testZ)) + canContinueMoving = false; + + // Bonus for positions that allow continued movement in the same direction + float continuityBonus = canContinueMoving ? 5.0f : 0.0f; + + float combinedScore = newCloudDist + (freedomScore * 15.0f) + minGasBombDist + continuityBonus; + + if (!foundPath || combinedScore > bestScore) { bestPos = testPos; - bestGasBombDist = minGasBombDist; + bestScore = combinedScore; foundPath = true; } } @@ -2527,26 +2709,33 @@ bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) } if (foundPath) + { + botAI->Reset(); return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); - - // Emergency movement if too close + } + // Emergency movement - only when very close to cloud if (cloudDist < EMERGENCY_DISTANCE) { Position emergencyPos = CalculateEmergencyPosition(botPos, dx, dy); - // Also check gas bomb distance for emergency move - float minGasBombDist = FLT_MAX; + + // Even for emergency, avoid corners + float minEmergencyGasBombDist = FLT_MAX; for (Unit* bomb : gasBombs) { float bombDist = sqrt(pow(emergencyPos.GetPositionX() - bomb->GetPositionX(), 2) + pow(emergencyPos.GetPositionY() - bomb->GetPositionY(), 2)); - if (bombDist < minGasBombDist) - minGasBombDist = bombDist; + if (bombDist < minEmergencyGasBombDist) + minEmergencyGasBombDist = bombDist; } - if (minGasBombDist >= GAS_BOMB_SAFE_DIST && + + if (minEmergencyGasBombDist >= GAS_BOMB_SAFE_DIST && bot->IsWithinLOS(emergencyPos.GetPositionX(), emergencyPos.GetPositionY(), emergencyPos.GetPositionZ())) + { + botAI->Reset(); return MoveTo(bot->GetMapId(), emergencyPos.GetPositionX(), emergencyPos.GetPositionY(), emergencyPos.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } } return false; @@ -2555,9 +2744,10 @@ bool IccPutricideGasCloudAction::HandleGaseousBloatMovement(Unit* gasCloud) bool IccPutricideGasCloudAction::FindSafeMovementPosition(const Position& botPos, const Position& cloudPos, float dx, float dy, int numAngles, Position& resultPos) { - float bestDist = cloudPos.GetExactDist2d(botPos); + float bestScore = 0.0f; bool foundPath = false; resultPos = botPos; + static const float MOVEMENT_INCREMENT = 5.0f; // Fixed movement increment for (int i = 0; i < numAngles; i++) { @@ -2565,18 +2755,49 @@ bool IccPutricideGasCloudAction::FindSafeMovementPosition(const Position& botPos float rotatedDx = dx * cos(angle) - dy * sin(angle); float rotatedDy = dx * sin(angle) + dy * cos(angle); - for (float testDist = 5.0f; testDist <= 15.0f; testDist += 5.0f) + for (float testDist = MOVEMENT_INCREMENT; testDist <= 20.0f; testDist += MOVEMENT_INCREMENT) { Position testPos(botPos.GetPositionX() + rotatedDx * testDist, botPos.GetPositionY() + rotatedDy * testDist, botPos.GetPositionZ()); float newCloudDist = cloudPos.GetExactDist2d(testPos); - if (newCloudDist > bestDist && + if (newCloudDist > cloudPos.GetExactDist2d(botPos) && bot->IsWithinLOS(testPos.GetPositionX(), testPos.GetPositionY(), testPos.GetPositionZ())) { - resultPos = testPos; - bestDist = newCloudDist; - foundPath = true; + // Strict corner prevention - test movement freedom thoroughly + int freeDirections = 0; + static const int CHECK_DIRECTIONS = 16; + static const float CHECK_DISTANCE = 8.0f; + + for (int j = 0; j < CHECK_DIRECTIONS; j++) + { + float checkAngle = (2 * M_PI * j) / CHECK_DIRECTIONS; + float checkX = testPos.GetPositionX() + cos(checkAngle) * CHECK_DISTANCE; + float checkY = testPos.GetPositionY() + sin(checkAngle) * CHECK_DISTANCE; + if (bot->IsWithinLOS(checkX, checkY, testPos.GetPositionZ())) + freeDirections++; + } + + float freedomScore = (float)freeDirections / CHECK_DIRECTIONS; + + // REJECT positions that could lead to corners - require high freedom + if (freedomScore < 0.8f) // 80% of directions must be free + continue; + + // Check if we can continue moving in the same direction (avoid dead ends) + float continueX = testPos.GetPositionX() + rotatedDx * MOVEMENT_INCREMENT; + float continueY = testPos.GetPositionY() + rotatedDy * MOVEMENT_INCREMENT; + bool canContinue = bot->IsWithinLOS(continueX, continueY, testPos.GetPositionZ()); + + float continuityBonus = canContinue ? 3.0f : 0.0f; + float combinedScore = newCloudDist + (freedomScore * 12.0f) + continuityBonus; + + if (!foundPath || combinedScore > bestScore) + { + resultPos = testPos; + bestScore = combinedScore; + foundPath = true; + } } } } @@ -2586,7 +2807,50 @@ bool IccPutricideGasCloudAction::FindSafeMovementPosition(const Position& botPos Position IccPutricideGasCloudAction::CalculateEmergencyPosition(const Position& botPos, float dx, float dy) { - return Position(botPos.GetPositionX() + dx * 10.0f, botPos.GetPositionY() + dy * 10.0f, botPos.GetPositionZ()); + // For emergency, still try to avoid corners but prioritize getting away from immediate danger + Position bestPos = + Position(botPos.GetPositionX() + dx * 15.0f, botPos.GetPositionY() + dy * 15.0f, botPos.GetPositionZ()); + float bestFreedom = 0.0f; + static const float MOVEMENT_INCREMENT = 5.0f; // Fixed movement increment + + // Try fewer directions for emergency but still avoid corners + for (int i = 0; i < 8; i++) + { + float angle = (2 * M_PI * i) / 8; + float rotatedDx = dx * cos(angle) - dy * sin(angle); + float rotatedDy = dx * sin(angle) + dy * cos(angle); + + Position testPos(botPos.GetPositionX() + rotatedDx * 15.0f, botPos.GetPositionY() + rotatedDy * 15.0f, + botPos.GetPositionZ()); + + if (bot->IsWithinLOS(testPos.GetPositionX(), testPos.GetPositionY(), testPos.GetPositionZ())) + { + // Quick freedom check for emergency + int freeDirections = 0; + static const int EMERGENCY_CHECK_DIRS = 8; + static const float EMERGENCY_CHECK_DIST = 6.0f; + + for (int j = 0; j < EMERGENCY_CHECK_DIRS; j++) + { + float checkAngle = (2 * M_PI * j) / EMERGENCY_CHECK_DIRS; + float checkX = testPos.GetPositionX() + cos(checkAngle) * EMERGENCY_CHECK_DIST; + float checkY = testPos.GetPositionY() + sin(checkAngle) * EMERGENCY_CHECK_DIST; + if (bot->IsWithinLOS(checkX, checkY, testPos.GetPositionZ())) + freeDirections++; + } + + float freedom = (float)freeDirections / EMERGENCY_CHECK_DIRS; + + // For emergency, accept positions with at least 60% freedom + if (freedom >= 0.6f && freedom > bestFreedom) + { + bestPos = testPos; + bestFreedom = freedom; + } + } + } + + return bestPos; } bool IccPutricideGasCloudAction::HandleGroupAuraSituation(Unit* gasCloud) @@ -2610,18 +2874,63 @@ bool IccPutricideGasCloudAction::HandleGroupAuraSituation(Unit* gasCloud) } } + float currentDist = gasCloud ? bot->GetDistance(gasCloud) : 0.0f; + const float MIN_SAFE_DISTANCE = 15.0f; + + // Always maintain minimum distance when group doesn't have bloat + if (!GroupHasGaseousBloat(group) && gasCloud && currentDist < MIN_SAFE_DISTANCE) + { + // Move away from gas cloud + float angle = bot->GetAngle(gasCloud); + float x = bot->GetPositionX() + cos(angle) * -MIN_SAFE_DISTANCE; + float y = bot->GetPositionY() + sin(angle) * -MIN_SAFE_DISTANCE; + return MoveTo(bot->GetMapId(), x, y, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + if (GroupHasGaseousBloat(group) && gasCloud) { bot->SetTarget(gasCloud->GetGUID()); bot->SetFacingToObject(gasCloud); - // Added movement logic for ranged attackers - if (botAI->IsRanged(bot) && !botAI->IsHeal(bot)) - return Attack(gasCloud); - - // Added movement logic for melee attackers - if (botAI->IsMelee(bot) && !botAI->IsTank(bot)) - return Attack(gasCloud); + // Attack logic for group with Gaseous Bloat + if (botAI->IsRanged(bot)) + { + // For ranged attackers, maintain optimal distance (15-25 yards) + if (currentDist > 25.0f) + { + // Move closer if too far + return MoveTo(bot->GetMapId(), gasCloud->GetPositionX(), gasCloud->GetPositionY(), + gasCloud->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + else if (currentDist < MIN_SAFE_DISTANCE) + { + // Move away if too close (but stay closer than normal safe distance since we need to attack) + float angle = bot->GetAngle(gasCloud); + float x = bot->GetPositionX() + cos(angle) * -12.0f; + float y = bot->GetPositionY() + sin(angle) * -12.0f; + return MoveTo(bot->GetMapId(), x, y, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT); + } + else + { + // Attack if at good range + return Attack(gasCloud); + } + } + else if (botAI->IsMelee(bot) && !botAI->IsTank(bot)) + { + // For melee attackers, move to attack range (0-5 yards) + if (currentDist > 5.0f) + { + return MoveTo(bot->GetMapId(), gasCloud->GetPositionX(), gasCloud->GetPositionY(), + gasCloud->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + else + { + return Attack(gasCloud); + } + } } return false; @@ -2710,7 +3019,10 @@ bool IccPutricideAvoidMalleableGooAction::HandleUnboundPlague(Unit* boss) } if (!closestPlayer || closestDistance >= UNBOUND_PLAGUE_DISTANCE) + { + bot->Kill(bot, bot); return true; + } // Calculate move away position float dx = bot->GetPositionX() - closestPlayer->GetPositionX(); @@ -2933,7 +3245,9 @@ bool IccBpcKelesethTankAction::Execute(Event event) if (!boss) return false; - bool isBossVictim = boss && boss->GetVictim() == bot; + bool isBossVictim = false; + if (boss && boss->GetVictim() == bot) + isBossVictim = true; // If not actively tanking, attack the boss if (boss->GetVictim() != bot) @@ -3012,8 +3326,13 @@ bool IccBpcMainTankAction::Execute(Event event) auto* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram"); // Check if we're the target of both princes - const bool isVictimOfValanar = valanar && valanar->GetVictim() == bot; - const bool isVictimOfTaldaram = taldaram && taldaram->GetVictim() == bot; + bool isVictimOfValanar = false; + if (valanar && valanar->GetVictim() == bot) + isVictimOfValanar = true; + + bool isVictimOfTaldaram = false; + if (taldaram && taldaram->GetVictim() == bot) + isVictimOfTaldaram = true; // Move to MT position if targeted by both princes and not already close if (isVictimOfValanar && isVictimOfTaldaram && bot->GetExactDist2d(ICC_BPC_MT_POSITION) > 15.0f) @@ -3121,11 +3440,13 @@ bool IccBpcEmpoweredVortexAction::Execute(Event event) return false; // Check if boss is casting empowered vortex - bool isCastingVortex = valanar->HasUnitState(UNIT_STATE_CASTING) && - (valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) || - valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4)); + bool isCastingVortex = false; + if (valanar && valanar->HasUnitState(UNIT_STATE_CASTING) && + (valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) || + valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4))) + isCastingVortex = true; if (isCastingVortex) { @@ -4293,51 +4614,55 @@ bool IccBqlPactOfDarkfallenAction::Execute(Event event) // Check if bot has Pact of the Darkfallen if (!botAI->GetAura("Pact of the Darkfallen", bot)) return false; - Group* group = bot->GetGroup(); if (!group) return false; // Find other players with Pact of the Darkfallen Player* tankWithAura = nullptr; - Player* otherPlayerWithAura = nullptr; - int auraCount = 0; + std::vector playersWithAura; for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* member = itr->GetSource(); if (!member || member == bot) continue; - if (botAI->GetAura("Pact of the Darkfallen", member)) { - auraCount++; + playersWithAura.push_back(member); if (botAI->IsTank(member)) tankWithAura = member; - else if (!otherPlayerWithAura) - otherPlayerWithAura = member; } } - if (auraCount == 0) + if (playersWithAura.empty()) return false; // Determine target position Position targetPos; - if (tankWithAura && !botAI->IsTank(bot)) + if (tankWithAura) { - // Move to tank if we're not a tank - targetPos.Relocate(tankWithAura); + // If there's a tank with aura, everyone moves to the tank (including the tank itself for center positioning) + if (botAI->IsTank(bot)) + { + // If current bot is the tank, stay put or move slightly for better positioning + targetPos.Relocate(bot); + } + else + { + // Non-tank bots move to the tank + targetPos.Relocate(tankWithAura); + } } - else if (auraCount >= 2) + else if (playersWithAura.size() >= 2) { - // Calculate center position if multiple players have aura - CalculateCenterPosition(targetPos, otherPlayerWithAura); + // Calculate center position of all players with aura (including bot) + CalculateCenterPosition(targetPos, playersWithAura); } - else if (otherPlayerWithAura) + else if (playersWithAura.size() == 1) { // Move to the single other player with aura - targetPos.Relocate(otherPlayerWithAura); + targetPos.Relocate(playersWithAura[0]); } else { @@ -4346,22 +4671,33 @@ bool IccBqlPactOfDarkfallenAction::Execute(Event event) } // Move to target position if needed - return MoveToTargetPosition(targetPos, auraCount); + return MoveToTargetPosition(targetPos, playersWithAura.size() + 1); // +1 to include the bot itself } -void IccBqlPactOfDarkfallenAction::CalculateCenterPosition(Position& targetPos, Player* otherPlayer) +void IccBqlPactOfDarkfallenAction::CalculateCenterPosition(Position& targetPos, + const std::vector& playersWithAura) { - float sumX = bot->GetPositionX() + otherPlayer->GetPositionX(); - float sumY = bot->GetPositionY() + otherPlayer->GetPositionY(); - float sumZ = bot->GetPositionZ() + otherPlayer->GetPositionZ(); - targetPos.Relocate(sumX / 2, sumY / 2, sumZ / 2); + float sumX = bot->GetPositionX(); + float sumY = bot->GetPositionY(); + float sumZ = bot->GetPositionZ(); + + // Add positions of all other players with aura + for (Player* player : playersWithAura) + { + sumX += player->GetPositionX(); + sumY += player->GetPositionY(); + sumZ += player->GetPositionZ(); + } + + // Calculate average position (center) + int totalPlayers = playersWithAura.size() + 1; // +1 for the bot itself + targetPos.Relocate(sumX / totalPlayers, sumY / totalPlayers, sumZ / totalPlayers); } bool IccBqlPactOfDarkfallenAction::MoveToTargetPosition(const Position& targetPos, int auraCount) { const float POSITION_TOLERANCE = 0.1f; float distance = bot->GetDistance(targetPos); - if (distance <= POSITION_TOLERANCE) return true; @@ -4372,7 +4708,7 @@ bool IccBqlPactOfDarkfallenAction::MoveToTargetPosition(const Position& targetPo float len = sqrt(dx * dx + dy * dy); float moveX, moveY, moveZ; - if (len > 5.0f && auraCount<=2) + if (len > 5.0f && auraCount <= 2) { dx /= len; dy /= len; @@ -4389,7 +4725,6 @@ bool IccBqlPactOfDarkfallenAction::MoveToTargetPosition(const Position& targetPo botAI->Reset(); MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); - return false; } @@ -4597,8 +4932,7 @@ bool IccValithriaGroupAction::Execute(Event event) }; // Find portals and enemies - Creature* portal = findNearestCreature( - {NPC_DREAM_PORTAL, NPC_DREAM_PORTAL_PRE_EFFECT, NPC_NIGHTMARE_PORTAL, NPC_NIGHTMARE_PORTAL_PRE_EFFECT}, 100.0f); + Creature* portal = findNearestCreature({NPC_DREAM_PORTAL, NPC_DREAM_PORTAL_PRE_EFFECT, NPC_NIGHTMARE_PORTAL, NPC_NIGHTMARE_PORTAL_PRE_EFFECT}, 100.0f); Creature* worm = bot->FindNearestCreature(NPC_ROT_WORM, 100.0f); Creature* zombie = bot->FindNearestCreature(NPC_BLISTERING_ZOMBIE, 100.0f); @@ -4650,8 +4984,10 @@ bool IccValithriaGroupAction::Execute(Event event) } // Healer movement logic - if (botAI->IsHeal(bot) && bot->GetExactDist2d(ICC_VDW_HEAL_POSITION) > 45.0f && !portal) - MoveTowardsPosition(ICC_VDW_HEAL_POSITION, 10.0f); + if (botAI->IsHeal(bot) && bot->GetExactDist2d(ICC_VDW_HEAL_POSITION) > 30.0f && !portal) + return MoveTo(bot->GetMapId(), ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY(), + ICC_VDW_HEAL_POSITION.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_NORMAL); // Avoidance behaviors if (manaVoid && bot->GetExactDist2d(manaVoid) < 10.0f && @@ -4832,20 +5168,29 @@ bool IccValithriaGroupAction::Handle25ManGroupLogic() if (botAI->IsTank(bot) || botAI->IsDps(bot)) HandleMarkingLogic(inGroup1, inGroup2, group1Pos, group2Pos); - + // Movement logic for non-healers if (!botAI->IsHeal(bot)) { if (inGroup1) { - return MoveTowardsPosition(group1Pos, 5.0f); + float distance = bot->GetDistance(group1Pos); + if (distance > 25.0f) + { + // If far away, move directly to position + MoveTowardsPosition(group1Pos, 5.0f); + } } else if (inGroup2) { - return MoveTowardsPosition(group2Pos, 5.0f); + float distance = bot->GetDistance(group2Pos); + if (distance > 25.0f) + { + MoveTowardsPosition(group2Pos, 5.0f); + } } } - + return false; } @@ -4890,7 +5235,7 @@ bool IccValithriaGroupAction::HandleMarkingLogic(bool inGroup1, bool inGroup2, c if (Unit* unit = botAI->GetUnit(guid)) { if (unit->IsAlive() && unit->GetEntry() == entry && - unit->GetExactDist2d(groupPos->GetPositionX(), groupPos->GetPositionY()) <= 25.0f) + unit->GetExactDist2d(groupPos->GetPositionX(), groupPos->GetPositionY()) <= 40.0f) { priorityTarget = unit; break; @@ -4908,8 +5253,23 @@ bool IccValithriaGroupAction::HandleMarkingLogic(bool inGroup1, bool inGroup2, c ObjectGuid currentIcon = group->GetTargetIcon(iconIndex); Unit* currentIconUnit = botAI->GetUnit(currentIcon); - if (!currentIconUnit || !currentIconUnit->IsAlive() || currentIconUnit != priorityTarget) + // Check if the target already has any raid icon + bool hasOtherIcon = false; + for (uint8 i = 0; i < 8; ++i) + { + if (i == iconIndex) + continue; // Skip our own icon index + if (group->GetTargetIcon(i) == priorityTarget->GetGUID()) + { + hasOtherIcon = true; + break; + } + } + + if (!hasOtherIcon && (!currentIconUnit || !currentIconUnit->IsAlive() || currentIconUnit != priorityTarget)) + { group->SetTargetIcon(iconIndex, bot->GetGUID(), priorityTarget->GetGUID()); + } } return false; @@ -4961,7 +5321,7 @@ bool IccValithriaGroupAction::Handle10ManGroupLogic() } // Movement logic - if (bot->GetExactDist2d(ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY()) > 35.0f) + if (bot->GetExactDist2d(ICC_VDW_HEAL_POSITION.GetPositionX(), ICC_VDW_HEAL_POSITION.GetPositionY()) > 25.0f) MoveTowardsPosition(ICC_VDW_HEAL_POSITION, 5.0f); return false; @@ -5224,7 +5584,7 @@ bool IccValithriaDreamCloudAction::Execute(Event event) size_t myIndex = std::distance(dreamBots.begin(), it); // Check if all dream bots are stacked within 3f of the current leader (lowest guid) - constexpr float STACK_RADIUS = 5.0f; + constexpr float STACK_RADIUS = 2.0f; Unit* leader = dreamBots.front(); bool allStacked = true; for (Unit* member : dreamBots) @@ -5233,7 +5593,7 @@ bool IccValithriaDreamCloudAction::Execute(Event event) Player* player = member->ToPlayer(); if (player && !player->GetSession()) // is a bot { - if (member->GetExactDist2d(leader) > STACK_RADIUS) + if (member->GetDistance(leader) > STACK_RADIUS) { allStacked = false; break; @@ -5242,63 +5602,232 @@ bool IccValithriaDreamCloudAction::Execute(Event event) } // If not all stacked, everyone moves to the leader's position (clouds' position) - constexpr float PORTALSTART_TOLERANCE = 5.0f; + constexpr float PORTALSTART_TOLERANCE = 1.0f; if (!allStacked) { if (bot != leader) { - if (bot->GetExactDist2d(leader) > PORTALSTART_TOLERANCE) + if (bot->GetDistance(leader) > PORTALSTART_TOLERANCE) { - MoveTo(bot->GetMapId(), leader->GetPositionX(), leader->GetPositionY(), leader->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + bot->TeleportTo(bot->GetMapId(), leader->GetPositionX(), leader->GetPositionY(), leader->GetPositionZ(), + bot->GetOrientation()); } - // Wait at leader's position until all are stacked - return true; } - // Leader waits for others to stack - return true; } + // All stacked: leader (lowest guid) moves to next cloud, others follow and stack at leader's new position - Creature* dreamCloud = bot->FindNearestCreature(NPC_DREAM_CLOUD, 100.0f); - Creature* nightmareCloud = bot->FindNearestCreature(NPC_NIGHTMARE_CLOUD, 100.0f); + // Find all dream and nightmare clouds + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector dreamClouds; + std::vector nightmareClouds; + + for (int i = 0; i < npcs.size(); ++i) + { + Unit* unit = botAI->GetUnit(npcs[i]); + if (unit && unit->IsAlive()) + { + if (Creature* creature = unit->ToCreature()) + { + if (creature->GetEntry() == NPC_DREAM_CLOUD) + dreamClouds.push_back(creature); + else if (creature->GetEntry() == NPC_NIGHTMARE_CLOUD) + nightmareClouds.push_back(creature); + } + } + } + + // Sort clouds by distance + std::sort(dreamClouds.begin(), dreamClouds.end(), + [this](Creature* a, Creature* b) { return bot->GetExactDist2d(a) < bot->GetExactDist2d(b); }); + + std::sort(nightmareClouds.begin(), nightmareClouds.end(), + [this](Creature* a, Creature* b) { return bot->GetExactDist2d(a) < bot->GetExactDist2d(b); }); // Only the leader moves to the next cloud if (bot == leader) { + // Use GUID to determine which cloud type to prefer + bool preferDream = (bot->GetGUID().GetCounter() % 2 == 0); + + // Check if we're close to any cloud + bool atDreamCloud = false; + bool atNightmareCloud = false; + + for (Creature* cloud : dreamClouds) + { + if (bot->GetExactDist2d(cloud) <= 2.0f) + { + atDreamCloud = true; + break; + } + } + + for (Creature* cloud : nightmareClouds) + { + if (bot->GetExactDist2d(cloud) <= 2.0f) + { + atNightmareCloud = true; + break; + } + } + // If we have emerald vigor, prioritize dream clouds if (bot->HasAura(SPELL_EMERALD_VIGOR)) { - if (dreamCloud && bot->GetExactDist2d(dreamCloud) > 2.0f) - MoveTo(dreamCloud->GetMapId(), dreamCloud->GetPositionX(), dreamCloud->GetPositionY(), - dreamCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL); - if (nightmareCloud && bot->GetExactDist2d(nightmareCloud) > 2.0f) - MoveTo(nightmareCloud->GetMapId(), nightmareCloud->GetPositionX(), - nightmareCloud->GetPositionY(), nightmareCloud->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); + // If at dream cloud, move to 2nd closest dream cloud or closest nightmare cloud + if (atDreamCloud) + { + Creature* targetCloud = nullptr; + // Try 2nd closest dream cloud first + if (dreamClouds.size() >= 2 && bot->GetExactDist2d(dreamClouds[1]) > 2.0f) + targetCloud = dreamClouds[1]; + // Otherwise move to closest nightmare cloud + else if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) + targetCloud = nightmareClouds[0]; + + if (targetCloud) + MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), + targetCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + } + // If at nightmare cloud, move to closest dream cloud or 2nd closest nightmare cloud + else if (atNightmareCloud) + { + Creature* targetCloud = nullptr; + // Try closest dream cloud first + if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) + targetCloud = dreamClouds[0]; + // Otherwise move to 2nd closest nightmare cloud + else if (nightmareClouds.size() >= 2 && bot->GetExactDist2d(nightmareClouds[1]) > 2.0f) + targetCloud = nightmareClouds[1]; + + if (targetCloud) + MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), + targetCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + } + // If not at any cloud, move to closest dream cloud or nightmare cloud + else + { + if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) + MoveTo(dreamClouds[0]->GetMapId(), dreamClouds[0]->GetPositionX(), dreamClouds[0]->GetPositionY(), + dreamClouds[0]->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + else if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) + MoveTo(nightmareClouds[0]->GetMapId(), nightmareClouds[0]->GetPositionX(), + nightmareClouds[0]->GetPositionY(), nightmareClouds[0]->GetPositionZ(), false, false, false, + true, MovementPriority::MOVEMENT_NORMAL); + } } - // Otherwise prioritize nightmare clouds + // Otherwise use GUID-based preference else { - if (nightmareCloud && bot->GetExactDist2d(nightmareCloud) > 2.0f) - MoveTo(nightmareCloud->GetMapId(), nightmareCloud->GetPositionX(), - nightmareCloud->GetPositionY(), nightmareCloud->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_NORMAL); + // If prefer dream clouds based on GUID + if (preferDream) + { + // If at dream cloud, move to 2nd closest dream cloud or closest nightmare cloud + if (atDreamCloud) + { + Creature* targetCloud = nullptr; + // Try 2nd closest dream cloud first + if (dreamClouds.size() >= 2 && bot->GetExactDist2d(dreamClouds[1]) > 2.0f) + targetCloud = dreamClouds[1]; + // Otherwise move to closest nightmare cloud + else if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) + targetCloud = nightmareClouds[0]; - if (dreamCloud && bot->GetExactDist2d(dreamCloud) > 2.0f) - MoveTo(dreamCloud->GetMapId(), dreamCloud->GetPositionX(), dreamCloud->GetPositionY(), - dreamCloud->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + if (targetCloud) + MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), + targetCloud->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + } + // If at nightmare cloud, move to closest dream cloud or 2nd closest nightmare cloud + else if (atNightmareCloud) + { + Creature* targetCloud = nullptr; + // Try closest dream cloud first + if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) + targetCloud = dreamClouds[0]; + // Otherwise move to 2nd closest nightmare cloud + else if (nightmareClouds.size() >= 2 && bot->GetExactDist2d(nightmareClouds[1]) > 2.0f) + targetCloud = nightmareClouds[1]; + + if (targetCloud) + MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), + targetCloud->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + } + // If not at any cloud, move to closest dream cloud or nightmare cloud based on preference + else + { + if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) + MoveTo(dreamClouds[0]->GetMapId(), dreamClouds[0]->GetPositionX(), + dreamClouds[0]->GetPositionY(), dreamClouds[0]->GetPositionZ(), false, false, false, + true, MovementPriority::MOVEMENT_NORMAL); + else if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) + MoveTo(nightmareClouds[0]->GetMapId(), nightmareClouds[0]->GetPositionX(), + nightmareClouds[0]->GetPositionY(), nightmareClouds[0]->GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_NORMAL); + } + } + // If prefer nightmare clouds based on GUID + else + { + // If at nightmare cloud, move to 2nd closest nightmare cloud or closest dream cloud + if (atNightmareCloud) + { + Creature* targetCloud = nullptr; + // Try 2nd closest nightmare cloud first + if (nightmareClouds.size() >= 2 && bot->GetExactDist2d(nightmareClouds[1]) > 2.0f) + targetCloud = nightmareClouds[1]; + // Otherwise move to closest dream cloud + else if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) + targetCloud = dreamClouds[0]; + + if (targetCloud) + MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), + targetCloud->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + } + // If at dream cloud, move to closest nightmare cloud or 2nd closest dream cloud + else if (atDreamCloud) + { + Creature* targetCloud = nullptr; + // Try closest nightmare cloud first + if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) + targetCloud = nightmareClouds[0]; + // Otherwise move to 2nd closest dream cloud + else if (dreamClouds.size() >= 2 && bot->GetExactDist2d(dreamClouds[1]) > 2.0f) + targetCloud = dreamClouds[1]; + + if (targetCloud) + MoveTo(targetCloud->GetMapId(), targetCloud->GetPositionX(), targetCloud->GetPositionY(), + targetCloud->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_NORMAL); + } + // If not at any cloud, move to closest nightmare cloud or dream cloud based on preference + else + { + if (!nightmareClouds.empty() && bot->GetExactDist2d(nightmareClouds[0]) > 2.0f) + MoveTo(nightmareClouds[0]->GetMapId(), nightmareClouds[0]->GetPositionX(), + nightmareClouds[0]->GetPositionY(), nightmareClouds[0]->GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_NORMAL); + else if (!dreamClouds.empty() && bot->GetExactDist2d(dreamClouds[0]) > 2.0f) + MoveTo(dreamClouds[0]->GetMapId(), dreamClouds[0]->GetPositionX(), + dreamClouds[0]->GetPositionY(), dreamClouds[0]->GetPositionZ(), false, false, false, + true, MovementPriority::MOVEMENT_NORMAL); + } + } } } else { // Non-leader bots follow and stack at leader's position - if (bot->GetExactDist2d(leader) > PORTALSTART_TOLERANCE) + if (bot->GetDistance(leader) > PORTALSTART_TOLERANCE) { - MoveTo(bot->GetMapId(), leader->GetPositionX(), leader->GetPositionY(), leader->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_NORMAL); + botAI->Reset(); + bot->TeleportTo(bot->GetMapId(), leader->GetPositionX(), leader->GetPositionY(), leader->GetPositionZ(), + bot->GetOrientation()); } - return true; } return false; @@ -5669,6 +6198,9 @@ bool IccSindragosaFrostBeaconAction::HandleBeaconedPlayer(const Unit* boss) // Phase 3 positioning (below 35% health, not flying) if (boss->HealthBelowPct(35) && !IsBossFlying(boss)) { + if (!bot->HasAura(SPELL_NITRO_BOOSTS)) + bot->AddAura(SPELL_NITRO_BOOSTS, bot); + botAI->Reset(); return MoveToPositionIfNeeded(ICC_SINDRAGOSA_THOMBMB2_POSITION, POSITION_TOLERANCE); } @@ -5754,7 +6286,9 @@ bool IccSindragosaFrostBeaconAction::HandleNonBeaconedPlayer(const Unit* boss) if (!bot->HasAura(FROST_BEACON_AURA_ID)) { const Difficulty diff = bot->GetRaidDifficulty(); - const bool is25Man = (diff == RAID_DIFFICULTY_25MAN_NORMAL) || (diff == RAID_DIFFICULTY_25MAN_HEROIC); + bool is25Man = false; + if (diff && (diff == RAID_DIFFICULTY_25MAN_NORMAL || diff == RAID_DIFFICULTY_25MAN_HEROIC)) + is25Man = true; const Position& safePosition = is25Man ? ICC_SINDRAGOSA_FBOMB_POSITION : ICC_SINDRAGOSA_FBOMB10_POSITION; @@ -6237,7 +6771,7 @@ bool IccSindragosaFrostBombAction::Execute(Event event) context->GetValue("rti")->Set(rtiValue); - // Find a tomb in our group with 65% or more HP to mark + // Find a tomb in our group with 45% or more HP to mark Unit* tombToMark = nullptr; for (size_t i = 0; i < myTombs.size(); ++i) { @@ -6262,7 +6796,7 @@ bool IccSindragosaFrostBombAction::Execute(Event event) } else { - // No tombs above 65% HP, remove marker if one exists + // No tombs above 45% HP, remove marker if one exists ObjectGuid currentIcon = group->GetTargetIcon(iconIndex); if (!currentIcon.IsEmpty()) { @@ -6466,7 +7000,9 @@ bool IccLichKingWinterAction::Execute(Event event) Unit* iceSphere = AI_VALUE2(Unit*, "find target", "ice sphere"); - bool isVictim = iceSphere && iceSphere->GetVictim() == bot && !botAI->IsTank(bot); + bool isVictim = false; + if (iceSphere && iceSphere->GetVictim() == bot && !botAI->IsTank(bot)) + isVictim = true; // First priority: Get out of Defile if we're in one if (!IsPositionSafeFromDefile(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 3.0f)) @@ -7031,60 +7567,126 @@ void IccLichKingWinterAction::HandleRangedPositioning() } } -// NEW METHOD: Main tank add management - stays at position void IccLichKingWinterAction::HandleMainTankAddManagement(Unit* boss, const Position* tankPos) { if (!botAI->IsMainTank(bot)) return; - // Look for adds near tank position (within 5 yards) - Unit* nearbyAdd = nullptr; - float closestDist = 8.0f; // Only consider adds within 8 yards of tank position + // First, ensure we're at the correct tank position + float distToTankPos = bot->GetDistance2d(tankPos->GetPositionX(), tankPos->GetPositionY()); + if (distToTankPos > 3.0f) + { + TryMoveToPosition(tankPos->GetPositionX(), tankPos->GetPositionY(), 840.857f, true); + return; // Wait until we're in position + } + // Get all valid adds in the encounter area GuidVector targets = AI_VALUE(GuidVector, "possible targets"); + std::vector validAdds; + Unit* currentTarget = bot->GetVictim(); + // Collect all valid adds for (auto i = targets.begin(); i != targets.end(); ++i) { Unit* unit = botAI->GetUnit(*i); if (!IsValidCollectibleAdd(unit)) continue; - // Check distance from tank position (not bot position) - float distFromTankPos = unit->GetDistance2d(tankPos->GetPositionX(), tankPos->GetPositionY()); + validAdds.push_back(unit); + } - // Take adds that are near tank position - if (distFromTankPos < closestDist) + // If we have no adds, clear target if needed + if (validAdds.empty()) + { + if (currentTarget && !IsValidCollectibleAdd(currentTarget)) { - nearbyAdd = unit; - closestDist = distFromTankPos; + bot->SetTarget(ObjectGuid::Empty); + } + return; + } + + // Strategy for add management: + // 1. First priority: Adds attacking non-tanks + // 2. Second priority: Adds not attacking us + // 3. Third priority: All other valid adds + Unit* priorityAdd = nullptr; + Unit* secondaryAdd = nullptr; + Unit* otherAdd = nullptr; + + for (Unit* add : validAdds) + { + Unit* addVictim = add->GetVictim(); + + // Highest priority: Adds attacking non-tanks + if (addVictim && addVictim->IsPlayer() && !botAI->IsTank(addVictim->ToPlayer())) + { + if (!priorityAdd || bot->GetDistance(add) < bot->GetDistance(priorityAdd)) + { + priorityAdd = add; + } + continue; + } + + // Medium priority: Adds not attacking us + if (addVictim != bot) + { + if (!secondaryAdd || bot->GetDistance(add) < bot->GetDistance(secondaryAdd)) + { + secondaryAdd = add; + } + continue; + } + + // Lowest priority: All other valid adds + if (!otherAdd || bot->GetDistance(add) < bot->GetDistance(otherAdd)) + { + otherAdd = add; } } - // Attack nearby add or continue with current target - if (nearbyAdd) + // Select the highest priority add available + Unit* targetAdd = priorityAdd ? priorityAdd : (secondaryAdd ? secondaryAdd : otherAdd); + + if (targetAdd) { - bot->SetTarget(nearbyAdd->GetGUID()); - bot->SetFacingToObject(nearbyAdd); - Attack(nearbyAdd); - } - else - { - // No nearby adds, check current target - Unit* currentTarget = bot->GetVictim(); - if (currentTarget && IsValidCollectibleAdd(currentTarget)) + float addDist = bot->GetDistance(targetAdd); + + // If add is close enough (within melee range), attack it + if (addDist < 10.0f) { - // Continue attacking current add if it's valid and reasonably close - if (bot->GetDistance(currentTarget) < 10.0f) + // If we're not already attacking this add, switch to it + if (currentTarget != targetAdd) { - bot->SetFacingToObject(currentTarget); - Attack(currentTarget); - } - else - { - // Clear target if it's not a valid nearby add - bot->SetTarget(ObjectGuid::Empty); + bot->SetTarget(targetAdd->GetGUID()); + bot->SetFacingToObject(targetAdd); + Attack(targetAdd); } } + else + { + // Add is too far - move toward it while staying near tank position + float moveX = targetAdd->GetPositionX(); + float moveY = targetAdd->GetPositionY(); + + // Don't move too far from tank position (max 15 yards) + float maxPullDistance = 15.0f; + float pullRatio = std::min(1.0f, maxPullDistance / addDist); + + float adjustedX = tankPos->GetPositionX() + (moveX - tankPos->GetPositionX()) * pullRatio; + float adjustedY = tankPos->GetPositionY() + (moveY - tankPos->GetPositionY()) * pullRatio; + + TryMoveToPosition(adjustedX, adjustedY, 840.857f, false); + + // Still try to attack while moving + bot->SetTarget(targetAdd->GetGUID()); + bot->SetFacingToObject(targetAdd); + Attack(targetAdd); + } + } + else if (currentTarget && !IsValidCollectibleAdd(currentTarget)) + { + // Clear invalid target + bot->SetTarget(ObjectGuid::Empty); } } @@ -7197,6 +7799,8 @@ bool IccLichKingAddsAction::Execute(Event event) if (bot->HasAura(SPELL_HARVEST_SOUL_VALKYR)) // Don't process actions if bot is picked up by Val'kyr return false; + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + Difficulty diff = bot->GetRaidDifficulty(); if (sPlayerbotAIConfig->EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) @@ -7208,6 +7812,9 @@ bool IccLichKingAddsAction::Execute(Event event) if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN)) bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot); + if (boss && boss->HealthBelowPct(60) && boss->HealthAbovePct(40) && !bot->HasAura(SPELL_EMPOWERED_BLOOD)) + bot->AddAura(SPELL_EMPOWERED_BLOOD, bot); + if (!bot->HasAura(SPELL_NO_THREAT) && !botAI->IsTank(bot)) bot->AddAura(SPELL_NO_THREAT, bot); @@ -7216,7 +7823,6 @@ bool IccLichKingAddsAction::Execute(Event event) //------CHEAT------- } - Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); Unit* spiritWarden = AI_VALUE2(Unit*, "find target", "spirit warden"); bool hasPlague = botAI->HasAura("Necrotic Plague", bot); Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f); @@ -7242,10 +7848,15 @@ bool IccLichKingAddsAction::Execute(Event event) } } - bool hasWinterAura = boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || boss->HasAura(SPELL_REMORSELESS_WINTER3) || - boss->HasAura(SPELL_REMORSELESS_WINTER4)); - bool hasWinter2Aura = boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || boss->HasAura(SPELL_REMORSELESS_WINTER7) || - boss->HasAura(SPELL_REMORSELESS_WINTER8)); + bool hasWinterAura = false; + if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || + boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4))) + hasWinterAura = true; + + bool hasWinter2Aura = false; + if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || + boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8))) + hasWinter2Aura = true; if (boss && boss->GetHealthPct() < 70 && boss->GetHealthPct() > 40 && !hasWinterAura && !hasWinter2Aura) // If boss is in p2, check if bot has been thrown off platform @@ -8395,6 +9006,7 @@ void IccLichKingAddsAction::HandleValkyrMechanics(Difficulty diff) { GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); std::vector grabbingValkyrs; + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); // Find grabbing Val'kyrs for (auto& npc : npcs) @@ -8429,13 +9041,13 @@ void IccLichKingAddsAction::HandleValkyrMechanics(Difficulty diff) return; // If no grabbing Val'kyrs, mark the Lich King with skull - if (grabbingValkyrs.empty()) + if (grabbingValkyrs.empty() || (boss && boss->HealthBelowPct(43))) { // Find the Lich King for (auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_THE_LICH_KING && unit->HealthBelowPct(65) && unit->HealthAbovePct(40)) + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_THE_LICH_KING && unit->HealthBelowPct(68) && unit->HealthAbovePct(40)) { ObjectGuid currentSkull = group->GetTargetIcon(7); // Skull icon if (currentSkull != unit->GetGUID()) @@ -8527,6 +9139,10 @@ void IccLichKingAddsAction::HandleValkyrAssignment(const std::vector& gra if (!group) return; + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); + if (boss && boss->HealthBelowPct(40)) + return; + // Double-check that all Val'kyrs in the list are actually alive and valid targets std::vector validValkyrs; for (Unit* valkyr : grabbingValkyrs) diff --git a/src/strategy/raids/icecrown/RaidIccActions.h b/src/strategy/raids/icecrown/RaidIccActions.h index d4a8791f..00961e04 100644 --- a/src/strategy/raids/icecrown/RaidIccActions.h +++ b/src/strategy/raids/icecrown/RaidIccActions.h @@ -442,7 +442,7 @@ public: : MovementAction(botAI, "icc bql pact of darkfallen") {} bool Execute(Event event) override; - void CalculateCenterPosition(Position& targetPos, Player* otherPlayer); + void CalculateCenterPosition(Position& targetPos, const std::vector& playersWithAura); bool MoveToTargetPosition(const Position& targetPos, int auraCount); }; diff --git a/src/strategy/raids/icecrown/RaidIccMultipliers.cpp b/src/strategy/raids/icecrown/RaidIccMultipliers.cpp index 8b3b3edf..8370d717 100644 --- a/src/strategy/raids/icecrown/RaidIccMultipliers.cpp +++ b/src/strategy/raids/icecrown/RaidIccMultipliers.cpp @@ -91,12 +91,10 @@ float IccAddsDbsMultiplier::GetValue(Action* action) Aura* aura = botAI->GetAura("rune of blood", bot); if (aura) { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) - { + if (dynamic_cast(action)) + return 1.0f; + else return 0.0f; - } } } @@ -118,12 +116,10 @@ float IccDogsMultiplier::GetValue(Action* action) 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) || - dynamic_cast(action) || dynamic_cast(action)) - { + if (dynamic_cast(action)) + return 1.0f; + else return 0.0f; - } } } return 1.0f; @@ -147,12 +143,10 @@ float IccFestergutMultiplier::GetValue(Action* action) 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) || - dynamic_cast(action) || dynamic_cast(action)) - { + if (dynamic_cast(action)) + return 1.0f; + else return 0.0f; - } } } @@ -262,12 +256,10 @@ float IccAddsPutricideMultiplier::GetValue(Action* action) 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) || - dynamic_cast(action) || dynamic_cast(action)) - { + if (dynamic_cast(action)) + return 1.0f; + else return 0.0f; - } } } @@ -619,9 +611,9 @@ float IccSindragosaMultiplier::GetValue(Action* action) Aura* aura = botAI->GetAura("mystic buffet", bot, false, true); if (aura && aura->GetStackAmount() >= 6) { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) + if (dynamic_cast(action)) + return 1.0f; + else return 0.0f; } } @@ -793,13 +785,30 @@ float IccLichKingAddsMultiplier::GetValue(Action* action) Unit* currentTarget = AI_VALUE(Unit*, "current target"); - bool hasWinterAura = boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4)); - bool hasWinter2Aura = boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8)); - bool isCasting = boss && boss->HasUnitState(UNIT_STATE_CASTING); - bool isWinter = boss && (boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8)); + bool hasWinterAura = false; + if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || + boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4))) + hasWinterAura = true; + + bool hasWinter2Aura = false; + if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || + boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8))) + hasWinter2Aura = true; + + bool isCasting = false; + if (boss && boss->HasUnitState(UNIT_STATE_CASTING)) + isCasting = true; + + bool isWinter = false; + if (boss && (boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8))) + isWinter = true; if (hasWinterAura || hasWinter2Aura || (isCasting && isWinter)) { diff --git a/src/strategy/raids/icecrown/RaidIccTriggers.cpp b/src/strategy/raids/icecrown/RaidIccTriggers.cpp index d8b72cf5..94eed772 100644 --- a/src/strategy/raids/icecrown/RaidIccTriggers.cpp +++ b/src/strategy/raids/icecrown/RaidIccTriggers.cpp @@ -897,11 +897,16 @@ bool IccSindragosaBlisteringColdTrigger::IsActive() if (dist >= 33.0f) return false; - bool isCasting = boss && boss->HasUnitState(UNIT_STATE_CASTING); - bool isBlisteringCold = boss && (boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4)); + bool isCasting = false; + if (boss && boss->HasUnitState(UNIT_STATE_CASTING)) + isCasting = true; + + bool isBlisteringCold = false; + if (boss && (boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4))) + isBlisteringCold = true; return isCasting && isBlisteringCold; } @@ -916,10 +921,12 @@ bool IccSindragosaUnchainedMagicTrigger::IsActive() if (!aura) return false; - bool isBlisteringCold = boss && (boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4)); + bool isBlisteringCold = false; + if (boss && (boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4))) + isBlisteringCold = true; if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && isBlisteringCold) return false; @@ -937,10 +944,12 @@ bool IccSindragosaChilledToTheBoneTrigger::IsActive() if (!aura) return false; - bool isBlisteringCold = boss && (boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) || - boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4)); + bool isBlisteringCold = false; + if (boss && (boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) || + boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4))) + isBlisteringCold = true; if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && isBlisteringCold) return false; @@ -1121,11 +1130,30 @@ bool IccLichKingWinterTrigger::IsActive() return false; // Check for either Remorseless Winter - bool hasWinterAura = boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4); - bool hasWinter2Aura = boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8); - bool isCasting = boss->HasUnitState(UNIT_STATE_CASTING); - bool isWinter = boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || - boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8); + bool hasWinterAura = false; + if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) || + boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4))) + hasWinterAura = true; + + bool hasWinter2Aura = false; + if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) || + boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8))) + hasWinter2Aura = true; + + bool isCasting = false; + if (boss && boss->HasUnitState(UNIT_STATE_CASTING)) + isCasting = true; + + bool isWinter = false; + if (boss && boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) || + boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8)) + isWinter = true; if (hasWinterAura || hasWinter2Aura) return true; diff --git a/src/strategy/raids/icecrown/RaidIccTriggers.h b/src/strategy/raids/icecrown/RaidIccTriggers.h index 35761aa5..582d47e7 100644 --- a/src/strategy/raids/icecrown/RaidIccTriggers.h +++ b/src/strategy/raids/icecrown/RaidIccTriggers.h @@ -128,7 +128,7 @@ enum CreatureIdsICC enum SpellIdsICC { // ICC cheat spells - SPELL_EMPOWERED_BLOOD = 70304, //70304 -->50%, 70227 /*100% more dmg, 100% more att speed*/ + SPELL_EMPOWERED_BLOOD = 70227, //70304 -->50%, 70227 /*100% more dmg, 100% more att speed*/ SPELL_EXPERIENCED = 71188, //dmg 30% 20% att speed SPELL_NO_THREAT = 70115, //reduce threat SPELL_SPITEFULL_FURY = 36886, //500% more threat