From 0dccc9ea5046b7360cb75abdc1c6a951720cb41c Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:00:45 +1100 Subject: [PATCH 01/27] Implemented Razorscale logic --- .../raids/ulduar/RaidUlduarActionContext.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarActionContext.h b/src/strategy/raids/ulduar/RaidUlduarActionContext.h index ddcac039..c7d1a3c7 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActionContext.h +++ b/src/strategy/raids/ulduar/RaidUlduarActionContext.h @@ -17,11 +17,25 @@ public: { creators["flame leviathan vehicle"] = &RaidUlduarActionContext::flame_leviathan_vehicle; creators["flame leviathan enter vehicle"] = &RaidUlduarActionContext::flame_leviathan_enter_vehicle; + creators["razorscale avoid devouring flames"] = &RaidUlduarActionContext::razorscale_avoid_devouring_flames; + creators["razorscale avoid sentinel"] = &RaidUlduarActionContext::razorscale_avoid_sentinel; + creators["razorscale ignore flying alone"] = &RaidUlduarActionContext::razorscale_ignore_flying_alone; + creators["razorscale avoid whirlwind"] = &RaidUlduarActionContext::razorscale_avoid_whirlwind; + creators["razorscale grounded"] = &RaidUlduarActionContext::razorscale_grounded; + creators["razorscale harpoon action"] = &RaidUlduarActionContext::razorscale_harpoon_action; + creators["razorscale fuse armor action"] = &RaidUlduarActionContext::razorscale_fuse_armor_action; } private: static Action* flame_leviathan_vehicle(PlayerbotAI* ai) { return new FlameLeviathanVehicleAction(ai); } static Action* flame_leviathan_enter_vehicle(PlayerbotAI* ai) { return new FlameLeviathanEnterVehicleAction(ai); } + static Action* razorscale_avoid_devouring_flames(PlayerbotAI* ai) { return new RazorscaleAvoidDevouringFlameAction(ai); } + static Action* razorscale_avoid_sentinel(PlayerbotAI* ai) { return new RazorscaleAvoidSentinelAction(ai); } + static Action* razorscale_ignore_flying_alone(PlayerbotAI* ai) { return new RazorscaleIgnoreBossAction(ai); } + static Action* razorscale_avoid_whirlwind(PlayerbotAI* ai) { return new RazorscaleAvoidWhirlwindAction(ai); } + static Action* razorscale_grounded(PlayerbotAI* ai) { return new RazorscaleGroundedAction(ai); } + static Action* razorscale_harpoon_action(PlayerbotAI* ai) { return new RazorscaleHarpoonAction(ai); } + static Action* razorscale_fuse_armor_action(PlayerbotAI* ai) { return new RazorscaleFuseArmorAction(ai); } }; -#endif \ No newline at end of file +#endif From e2ec637882f85296bd1783b3146ef4f6b68ab3c1 Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:05:51 +1100 Subject: [PATCH 02/27] Update RaidUlduarActions.cpp --- .../raids/ulduar/RaidUlduarActions.cpp | 629 +++++++++++++++++- 1 file changed, 628 insertions(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/src/strategy/raids/ulduar/RaidUlduarActions.cpp index 84c2bf1f..aea7b2a3 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -3,17 +3,22 @@ #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 "Player.h" #include "Playerbots.h" #include "Position.h" #include "RaidUlduarBossHelper.h" #include "RaidUlduarScripts.h" #include "RaidUlduarStrategy.h" +#include "RtiValue.h" #include "ScriptedCreature.h" #include "ServerFacade.h" #include "SharedDefines.h" @@ -392,4 +397,626 @@ bool FlameLeviathanEnterVehicleAction::AllMainVehiclesOnUse() Difficulty diff = bot->GetRaidDifficulty(); int maxC = (diff == RAID_DIFFICULTY_10MAN_NORMAL || diff == RAID_DIFFICULTY_10MAN_HEROIC) ? 2 : 5; return demolisher >= maxC && siege >= maxC; -} \ No newline at end of file +} +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; + } + } + } + + // 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 isTank = botAI->IsTank(bot); + bool isMainTank = botAI->IsMainTank(bot); + if (isTank && !isMainTank) + { + return false; + } + + 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; + } + } + } + + // Mark the lowest-health sentinel with Skull if main tank + if (isMainTank && lowestHealthSentinel) + { + Group* group = bot->GetGroup(); + if (group) + { + int8 skullIndex = 7; // Skull + ObjectGuid currentSkullTarget = group->GetTargetIcon(skullIndex); + if (currentSkullTarget && lowestHealthSentinel->GetGUID() != currentSkullTarget) + { + group->SetTargetIcon(skullIndex, bot->GetGUID(), lowestHealthSentinel->GetGUID()); + } + } + } + + return movedAway; // Return true if moved +} + +bool RazorscaleAvoidSentinelAction::isUseful() +{ + bool isTank = botAI->IsTank(bot); + bool isMainTank = botAI->IsMainTank(bot); + if (isTank && !isMainTank) + { + return false; + } + + // Main tank always tries to mark sentinel + if (isMainTank) + { + return true; + } + + 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) + { + bool isMainTank = botAI->IsMainTank(bot); + // 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; + } + // Check moon mark if main tank + if (isMainTank) + { + Group* group = bot->GetGroup(); + if (group) + { + int8 moonIndex = 4; + ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); + return currentMoonTarget != boss->GetGUID(); + } + } + } + return false; +} + +bool RazorscaleIgnoreBossAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); + if (!boss) + { + return false; + } + + bool isMainTank = botAI->IsMainTank(bot); + if (!isMainTank) + { + // Move non-tanks inside + return MoveInside( + RazorscaleBossHelper::ULDUAR_MAP_ID, + RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, + RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, + bot->GetPositionZ(), + RazorscaleBossHelper::RAZORSCALE_ARENA_RADIUS - 10.0f, + MovementPriority::MOVEMENT_NORMAL + ); + } + + Group* group = bot->GetGroup(); + if (!group) + { + return false; + } + + // Assign moon rti to boss + int8 moonIndex = 4; + ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); + if (currentMoonTarget != boss->GetGUID()) + { + group->SetTargetIcon(moonIndex, bot->GetGUID(), boss->GetGUID()); + SetNextMovementDelay(1000); + } + + // Move main tank inside + return MoveInside( + RazorscaleBossHelper::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; + + if (botAI->IsMainTank(bot)) + { + int8 moonIndex = 4; + ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); + if (currentMoonTarget == boss->GetGUID()) + { + group->SetTargetIcon(moonIndex, bot->GetGUID(), ObjectGuid::Empty); + SetNextMovementDelay(1000); + return true; + } + } + + if (botAI->IsTank(bot)) + { + 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 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( + RazorscaleBossHelper::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( + RazorscaleBossHelper::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) +{ + 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 (!botAI->IsMainTank(bot)) + return false; + + Aura* fuseArmor = bot->GetAura(RazorscaleBossHelper::SPELL_FUSEARMOR); + if (!fuseArmor) + return false; + + return fuseArmor->GetStackAmount() >= RazorscaleBossHelper::FUSEARMOR_THRESHOLD; +} + +bool RazorscaleFuseArmorAction::Execute(Event event) +{ + if (!botAI->IsMainTank(bot)) + { + return false; + } + + RazorscaleBossHelper bossHelper(botAI); + bossHelper.AssignRolesBasedOnHealth(); + + // Check if the bot is still the main tank after reassignment + if (botAI->IsMainTank(bot)) + { + return false; + } + return true; +} From 67591ffdb35d7d35aa0c15fea3ed7d3b3188fe44 Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:07:34 +1100 Subject: [PATCH 03/27] Update RaidUlduarActions.h --- src/strategy/raids/ulduar/RaidUlduarActions.h | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.h b/src/strategy/raids/ulduar/RaidUlduarActions.h index 0019ff8a..34b0484e 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.h +++ b/src/strategy/raids/ulduar/RaidUlduarActions.h @@ -11,6 +11,10 @@ #include "RaidUlduarBossHelper.h" #include "Vehicle.h" +// +// Flame Leviathan +// + class FlameLeviathanVehicleAction : public MovementAction { public: @@ -42,4 +46,64 @@ protected: bool AllMainVehiclesOnUse(); }; -#endif \ No newline at end of file +// +// Razorscale +// + +class RazorscaleAvoidDevouringFlameAction : public MovementAction +{ +public: + RazorscaleAvoidDevouringFlameAction(PlayerbotAI* botAI) : MovementAction(botAI, "razorscale avoid devouring flames") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class RazorscaleAvoidSentinelAction : public MovementAction +{ +public: + RazorscaleAvoidSentinelAction(PlayerbotAI* botAI) : MovementAction(botAI, "razorscale avoid sentinel") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class RazorscaleIgnoreBossAction : public AttackAction +{ +public: + RazorscaleIgnoreBossAction(PlayerbotAI* botAI) : AttackAction(botAI, "razorscale ignore flying alone") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class RazorscaleAvoidWhirlwindAction : public MovementAction +{ +public: + RazorscaleAvoidWhirlwindAction(PlayerbotAI* botAI) : MovementAction(botAI, "razorscale avoid whirlwind") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class RazorscaleGroundedAction : public AttackAction +{ +public: + RazorscaleGroundedAction(PlayerbotAI* botAI) : AttackAction(botAI, "razorscale grounded") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class RazorscaleHarpoonAction : public MovementAction +{ +public: + RazorscaleHarpoonAction(PlayerbotAI* botAI) : MovementAction(botAI, "razorscale harpoon action") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class RazorscaleFuseArmorAction : public MovementAction +{ +public: + RazorscaleFuseArmorAction(PlayerbotAI* botAI) : MovementAction(botAI, "razorscale fuse armor action") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +#endif From 4d7fba0dcc15fe59970c6e4ddeed97b6f6738876 Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:09:58 +1100 Subject: [PATCH 04/27] Create RaidUlduarBossHelper.cpp --- .../raids/ulduar/RaidUlduarBossHelper.cpp | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp new file mode 100644 index 00000000..f57843eb --- /dev/null +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -0,0 +1,188 @@ +#include "RaidUlduarBossHelper.h" +#include "ObjectAccessor.h" +#include "GameObject.h" +#include "Group.h" +#include "ScriptedCreature.h" +#include "Player.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "World.h" + +std::unordered_map RazorscaleBossHelper::_harpoonCooldowns; + +bool RazorscaleBossHelper::UpdateBossAI() +{ + _boss = AI_VALUE2(Unit*, "find target", "razorscale"); + if (_boss) + { + Group* group = bot->GetGroup(); + if (group && !AreRolesAssigned()) + { + AssignRolesBasedOnHealth(); + } + return true; + } + return false; +} + +Unit* RazorscaleBossHelper::GetBoss() const +{ + return _boss; +} + +bool RazorscaleBossHelper::IsGroundPhase() const +{ + return _boss && _boss->IsAlive() && + (_boss->GetPositionZ() <= RAZORSCALE_FLYING_Z_THRESHOLD) && + (_boss->GetHealthPct() < 50.0f) && + !_boss->HasAura(SPELL_STUN_AURA); +} + +bool RazorscaleBossHelper::IsFlyingPhase() const +{ + return _boss && (!IsGroundPhase() || _boss->GetPositionZ() >= RAZORSCALE_FLYING_Z_THRESHOLD); +} + +bool RazorscaleBossHelper::IsHarpoonFired(uint32 chainSpellId) const +{ + return _boss && _boss->HasAura(chainSpellId); +} + +bool RazorscaleBossHelper::IsHarpoonReady(GameObject* harpoonGO) +{ + if (!harpoonGO) + return false; + + auto it = _harpoonCooldowns.find(harpoonGO->GetGUID()); + if (it != _harpoonCooldowns.end()) + { + time_t currentTime = std::time(nullptr); + time_t elapsedTime = currentTime - it->second; + if (elapsedTime < HARPOON_COOLDOWN_DURATION) + return false; + } + + return harpoonGO->GetGoState() == GO_STATE_READY; +} + +void RazorscaleBossHelper::SetHarpoonOnCooldown(GameObject* harpoonGO) +{ + if (!harpoonGO) + return; + + time_t currentTime = std::time(nullptr); + _harpoonCooldowns[harpoonGO->GetGUID()] = currentTime; +} + +GameObject* RazorscaleBossHelper::FindNearestHarpoon(float x, float y, float z) const +{ + GameObject* nearestHarpoon = nullptr; + float minDistanceSq = std::numeric_limits::max(); + + for (const auto& harpoon : GetHarpoonData()) + { + if (GameObject* harpoonGO = bot->FindNearestGameObject(harpoon.gameObjectEntry, 200.0f)) + { + float dx = harpoonGO->GetPositionX() - x; + float dy = harpoonGO->GetPositionY() - y; + float dz = harpoonGO->GetPositionZ() - z; + float distanceSq = dx * dx + dy * dy + dz * dz; + + if (distanceSq < minDistanceSq) + { + minDistanceSq = distanceSq; + nearestHarpoon = harpoonGO; + } + } + } + + return nearestHarpoon; +} + +const std::vector& RazorscaleBossHelper::GetHarpoonData() +{ + static const std::vector harpoonData = + { + { GO_RAZORSCALE_HARPOON_1, SPELL_CHAIN_1 }, + { GO_RAZORSCALE_HARPOON_2, SPELL_CHAIN_2 }, + { GO_RAZORSCALE_HARPOON_3, SPELL_CHAIN_3 }, + { GO_RAZORSCALE_HARPOON_4, SPELL_CHAIN_4 }, + }; + return harpoonData; +} + +bool RazorscaleBossHelper::AreRolesAssigned() const +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member) + continue; + + if (botAI->IsMainTank(member)) + return true; + } + + return false; +} + +bool RazorscaleBossHelper::CanSwapRoles() const +{ + std::time_t currentTime = std::time(nullptr); + return (currentTime - _lastRoleSwapTime) >= _roleSwapCooldown; +} + +void RazorscaleBossHelper::AssignRolesBasedOnHealth() +{ + if (!CanSwapRoles()) + return; + + Group* group = bot->GetGroup(); + if (!group) + return; + + // Gather all tank bots in the group, excluding those with Fuse Armor + std::vector tankBots; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !botAI->IsTank(member) || !member->IsAlive()) + continue; + + Aura* fuseArmor = member->GetAura(SPELL_FUSEARMOR); + if (fuseArmor && fuseArmor->GetStackAmount() >= FUSEARMOR_THRESHOLD) + continue; + + tankBots.push_back(member); + } + + if (tankBots.empty()) + return; + + // Sort tanks by max health descending + std::sort(tankBots.begin(), tankBots.end(), + [](Player* a, Player* b) + { + return a->GetMaxHealth() > b->GetMaxHealth(); + } + ); + + Player* newMainTank = tankBots[0]; + + // Remove all MAINTANK flags + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && botAI->IsMainTank(member)) + group->SetGroupMemberFlag(member->GetGUID(), false, MEMBER_FLAG_MAINTANK); + } + + // Assign the single main tank + group->SetGroupMemberFlag(newMainTank->GetGUID(), true, MEMBER_FLAG_MAINTANK); + + _lastRoleSwapTime = std::time(nullptr); +} From 1e5e38dcc20b85380f740c1d90b6c098481a3dc0 Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:14:57 +1100 Subject: [PATCH 05/27] Update RaidUlduarBossHelper.h --- .../raids/ulduar/RaidUlduarBossHelper.h | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.h b/src/strategy/raids/ulduar/RaidUlduarBossHelper.h index 3580444d..6b44b0ea 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.h +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.h @@ -2,6 +2,11 @@ #define _PLAYERBOT_RAIDULDUARBOSSHELPER_H #include +#include +#include +#include +#include +#include #include "AiObject.h" #include "AiObjectContext.h" @@ -16,6 +21,84 @@ const uint32 ULDUAR_MAP_ID = 603; +class RazorscaleBossHelper : public AiObject +{ +public: + // Enums and constants specific to Razorscale + enum RazorscaleUnits : uint32 + { + UNIT_RAZORSCALE = 33186, + UNIT_DARK_RUNE_SENTINEL = 33846, + UNIT_DARK_RUNE_WATCHER = 33453, + UNIT_DARK_RUNE_GUARDIAN = 33388, + UNIT_DEVOURING_FLAME = 34188, + }; + + enum RazorscaleGameObjects : uint32 + { + GO_RAZORSCALE_HARPOON_1 = 194519, + GO_RAZORSCALE_HARPOON_2 = 194541, + GO_RAZORSCALE_HARPOON_3 = 194542, + GO_RAZORSCALE_HARPOON_4 = 194543, + }; + + enum RazorscaleSpells : uint32 + { + SPELL_CHAIN_1 = 49679, + SPELL_CHAIN_2 = 49682, + SPELL_CHAIN_3 = 49683, + SPELL_CHAIN_4 = 49684, + SPELL_SENTINEL_WHIRLWIND = 63806, + SPELL_STUN_AURA = 62794, + SPELL_FUSEARMOR = 64771 + }; + + static constexpr uint32 FUSEARMOR_THRESHOLD = 2; + + // Constants for arena parameters + static constexpr float RAZORSCALE_FLYING_Z_THRESHOLD = 440.0f; + static constexpr float RAZORSCALE_ARENA_CENTER_X = 587.54f; + static constexpr float RAZORSCALE_ARENA_CENTER_Y = -175.04f; + static constexpr float RAZORSCALE_ARENA_RADIUS = 30.0f; + + // Harpoon cooldown (seconds) + static constexpr time_t HARPOON_COOLDOWN_DURATION = 5; + + // Structure for harpoon data + struct HarpoonData + { + uint32 gameObjectEntry; + uint32 chainSpellId; + }; + + explicit RazorscaleBossHelper(PlayerbotAI* botAI) + : AiObject(botAI), _boss(nullptr), _lastRoleSwapTime(0), _roleSwapCooldown(10) {} + + bool UpdateBossAI(); + Unit* GetBoss() const; + + bool IsGroundPhase() const; + bool IsFlyingPhase() const; + + bool IsHarpoonFired(uint32 chainSpellId) const; + static bool IsHarpoonReady(GameObject* harpoonGO); + static void SetHarpoonOnCooldown(GameObject* harpoonGO); + GameObject* FindNearestHarpoon(float x, float y, float z) const; + + static const std::vector& GetHarpoonData(); + + void AssignRolesBasedOnHealth(); + bool AreRolesAssigned() const; + bool CanSwapRoles() const; + +private: + Unit* _boss; + std::time_t _lastRoleSwapTime; + const std::time_t _roleSwapCooldown; + + static std::unordered_map _harpoonCooldowns; +}; + // template // class GenericBossHelper : public AiObject // { From 556c9186f2fa1dc452ebadc7cd40c99f8b3adf9e Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:15:36 +1100 Subject: [PATCH 06/27] Moved ULDUAR_MAP_ID definition outside RazorscaleBossHelper RazorscaleBossHelper::ULDUAR_MAP_ID --- src/strategy/raids/ulduar/RaidUlduarActions.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/src/strategy/raids/ulduar/RaidUlduarActions.cpp index aea7b2a3..dfc0b036 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -661,7 +661,7 @@ bool RazorscaleIgnoreBossAction::Execute(Event event) { // Move non-tanks inside return MoveInside( - RazorscaleBossHelper::ULDUAR_MAP_ID, + ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(), @@ -687,7 +687,7 @@ bool RazorscaleIgnoreBossAction::Execute(Event event) // Move main tank inside return MoveInside( - RazorscaleBossHelper::ULDUAR_MAP_ID, + ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(), @@ -843,7 +843,7 @@ bool RazorscaleGroundedAction::Execute(Event event) // 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( - RazorscaleBossHelper::ULDUAR_MAP_ID, + ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y - 20.0f, bot->GetPositionZ(), @@ -854,7 +854,7 @@ bool RazorscaleGroundedAction::Execute(Event event) // Otherwise, move inside a 12-yard radius around the arena center return MoveInside( - RazorscaleBossHelper::ULDUAR_MAP_ID, + ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_Y, bot->GetPositionZ(), From 42156f53067861fd895240aca52f0b5c1c492606 Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:17:02 +1100 Subject: [PATCH 07/27] Update RaidUlduarStrategy.cpp --- .../raids/ulduar/RaidUlduarStrategy.cpp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp b/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp index 0c6ed182..8deca8b1 100644 --- a/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp @@ -4,7 +4,9 @@ void RaidUlduarStrategy::InitTriggers(std::vector& triggers) { + // // Flame Leviathan + // triggers.push_back(new TriggerNode( "flame leviathan vehicle near", NextAction::array(0, new NextAction("flame leviathan enter vehicle", ACTION_RAID + 2), nullptr))); @@ -12,7 +14,38 @@ void RaidUlduarStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode( "flame leviathan on vehicle", NextAction::array(0, new NextAction("flame leviathan vehicle", ACTION_RAID + 1), nullptr))); + + // + // Razorscale + // + triggers.push_back(new TriggerNode( + "razorscale avoid devouring flames", + NextAction::array(0, new NextAction("razorscale avoid devouring flames", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode( + "razorscale avoid sentinel", + NextAction::array(0, new NextAction("razorscale avoid sentinel", ACTION_RAID + 2), nullptr))); + triggers.push_back(new TriggerNode( + "razorscale flying alone", + NextAction::array(0, new NextAction("razorscale ignore flying alone", ACTION_MOVE + 5), nullptr))); + + triggers.push_back(new TriggerNode( + "razorscale avoid whirlwind", + NextAction::array(0, new NextAction("razorscale avoid whirlwind", ACTION_RAID + 3), nullptr))); + + triggers.push_back(new TriggerNode( + "razorscale grounded", + NextAction::array(0, new NextAction("razorscale grounded", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "razorscale harpoon trigger", + NextAction::array(0, new NextAction("razorscale harpoon action", ACTION_MOVE), nullptr))); + + triggers.push_back(new TriggerNode( + "razorscale fuse armor trigger", + NextAction::array(0, new NextAction("razorscale fuse armor action", ACTION_MOVE), nullptr))); + } void RaidUlduarStrategy::InitMultipliers(std::vector& multipliers) From 09f648eb9f2ef25ecc23b84029452f48ac17b3c8 Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:17:41 +1100 Subject: [PATCH 08/27] Update RaidUlduarTriggerContext.h --- .../raids/ulduar/RaidUlduarTriggerContext.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h b/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h index 22c0f044..4b42c401 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h +++ b/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h @@ -17,11 +17,25 @@ public: { creators["flame leviathan on vehicle"] = &RaidUlduarTriggerContext::flame_leviathan_on_vehicle; creators["flame leviathan vehicle near"] = &RaidUlduarTriggerContext::flame_leviathan_vehicle_near; + creators["razorscale flying alone"] = &RaidUlduarTriggerContext::razorscale_flying_alone; + creators["razorscale avoid devouring flames"] = &RaidUlduarTriggerContext::razorscale_avoid_devouring_flames; + creators["razorscale avoid sentinel"] = &RaidUlduarTriggerContext::razorscale_avoid_sentinel; + creators["razorscale avoid whirlwind"] = &RaidUlduarTriggerContext::razorscale_avoid_whirlwind; + creators["razorscale grounded"] = &RaidUlduarTriggerContext::razorscale_grounded; + creators["razorscale harpoon trigger"] = &RaidUlduarTriggerContext::razorscale_harpoon_trigger; + creators["razorscale fuse armor trigger"] = &RaidUlduarTriggerContext::razorscale_fuse_armor_trigger; } private: static Trigger* flame_leviathan_on_vehicle(PlayerbotAI* ai) { return new FlameLeviathanOnVehicleTrigger(ai); } static Trigger* flame_leviathan_vehicle_near(PlayerbotAI* ai) { return new FlameLeviathanVehicleNearTrigger(ai); } + static Trigger* razorscale_flying_alone(PlayerbotAI* ai) { return new RazorscaleFlyingAloneTrigger(ai); } + static Trigger* razorscale_avoid_devouring_flames(PlayerbotAI* ai) { return new RazorscaleDevouringFlamesTrigger(ai); } + static Trigger* razorscale_avoid_sentinel(PlayerbotAI* ai) { return new RazorscaleAvoidSentinelTrigger(ai); } + static Trigger* razorscale_avoid_whirlwind(PlayerbotAI* ai) { return new RazorscaleAvoidWhirlwindTrigger(ai); } + static Trigger* razorscale_grounded(PlayerbotAI* ai) { return new RazorscaleGroundedTrigger(ai); } + static Trigger* razorscale_harpoon_trigger(PlayerbotAI* ai) { return new RazorscaleHarpoonAvailableTrigger(ai); } + static Trigger* razorscale_fuse_armor_trigger(PlayerbotAI* ai) { return new RazorscaleFuseArmorTrigger(ai); } }; #endif From a8d42fbe03c1966eae86a093357d6e7005431e0b Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:18:58 +1100 Subject: [PATCH 09/27] Update RaidUlduarTriggers.cpp --- .../raids/ulduar/RaidUlduarTriggers.cpp | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp index b231f716..49f2e6e3 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp @@ -1,8 +1,11 @@ #include "RaidUlduarTriggers.h" #include "EventMap.h" +#include "GameObject.h" #include "Object.h" +#include "PlayerbotAI.h" #include "Playerbots.h" +#include "RaidUlduarBossHelper.h" #include "RaidUlduarScripts.h" #include "ScriptedCreature.h" #include "SharedDefines.h" @@ -43,3 +46,187 @@ bool FlameLeviathanVehicleNearTrigger::IsActive() return true; } + +bool RazorscaleFlyingAloneTrigger::IsActive() +{ + 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) + { + return false; + } + + // Get the list of attackers + GuidVector attackers = context->GetValue("attackers")->Get(); + if (attackers.empty()) + { + return true; // No attackers implies flying alone + } + + std::vector dark_rune_adds; + + // Loop through attackers to find dark rune adds + for (ObjectGuid const& guid : attackers) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit) + continue; + + uint32 entry = unit->GetEntry(); + + // Check for valid dark rune entries + if (entry == RazorscaleBossHelper::UNIT_DARK_RUNE_WATCHER || + entry == RazorscaleBossHelper::UNIT_DARK_RUNE_GUARDIAN || + entry == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL) + { + dark_rune_adds.push_back(unit); + } + } + + // Return whether there are no dark rune adds + return dark_rune_adds.empty(); +} + + +bool RazorscaleDevouringFlamesTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); + if (!boss) + return false; + + 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) + { + return true; + } + } + + return false; +} + +bool RazorscaleAvoidSentinelTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); + if (!boss) + return false; + + 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) + { + return true; + } + } + + return false; +} + +bool RazorscaleAvoidWhirlwindTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); + if (!boss) + return false; + + 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 && + (unit->HasAura(RazorscaleBossHelper::SPELL_SENTINEL_WHIRLWIND) || unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL))) + { + return true; + } + } + + return false; +} + +bool RazorscaleGroundedTrigger::IsActive() +{ + 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) + { + return true; + } + return false; +} + +bool RazorscaleHarpoonAvailableTrigger::IsActive() +{ + // Get harpoon data from the helper + const std::vector& harpoonData = RazorscaleBossHelper::GetHarpoonData(); + + // Get the boss entity + Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); + if (!boss || !boss->IsAlive()) + { + return false; + } + + // Update the boss AI context in the helper + RazorscaleBossHelper razorscaleHelper(botAI); + + if (!razorscaleHelper.UpdateBossAI()) + { + return false; + } + + // Check each harpoon entry + for (const auto& harpoon : harpoonData) + { + // Skip harpoons whose chain spell is already active on the boss + if (razorscaleHelper.IsHarpoonFired(harpoon.chainSpellId)) + { + continue; + } + + // Find the nearest harpoon GameObject within 200 yards + if (GameObject* harpoonGO = bot->FindNearestGameObject(harpoon.gameObjectEntry, 200.0f)) + { + if (RazorscaleBossHelper::IsHarpoonReady(harpoonGO)) + { + return true; // At least one harpoon is available and ready to be fired + } + } + } + + // No harpoons are available or need to be fired + return false; +} + +bool RazorscaleFuseArmorTrigger::IsActive() +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + // Iterate through group members to find the main tank with Fuse Armor + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* member = gref->GetSource(); + if (!member || !botAI->IsMainTank(member)) + continue; + + Aura* fuseArmor = member->GetAura(RazorscaleBossHelper::SPELL_FUSEARMOR); + if (fuseArmor && fuseArmor->GetStackAmount() >= RazorscaleBossHelper::FUSEARMOR_THRESHOLD) + return true; + } + + return false; +} From 7c8563ef7d00a251a2ede0723f8e35b967f8c434 Mon Sep 17 00:00:00 2001 From: avirar Date: Wed, 18 Dec 2024 16:20:06 +1100 Subject: [PATCH 10/27] Update RaidUlduarTriggers.h --- .../raids/ulduar/RaidUlduarTriggers.h | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggers.h b/src/strategy/raids/ulduar/RaidUlduarTriggers.h index 89b784cf..2dc1e7a6 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggers.h +++ b/src/strategy/raids/ulduar/RaidUlduarTriggers.h @@ -7,7 +7,9 @@ #include "RaidUlduarBossHelper.h" #include "Trigger.h" - +// +// Flame Levi +// class FlameLeviathanOnVehicleTrigger : public Trigger { public: @@ -22,4 +24,56 @@ public: bool IsActive() override; }; -#endif \ No newline at end of file +// +// Razorscale +// +class RazorscaleFlyingAloneTrigger : public Trigger +{ +public: + RazorscaleFlyingAloneTrigger(PlayerbotAI* ai) : Trigger(ai, "razorscale flying alone") {} + bool IsActive() override; +}; + +class RazorscaleDevouringFlamesTrigger : public Trigger +{ +public: + RazorscaleDevouringFlamesTrigger(PlayerbotAI* ai) : Trigger(ai, "razorscale avoid devouring flames") {} + bool IsActive() override; +}; + +class RazorscaleAvoidSentinelTrigger : public Trigger +{ +public: + RazorscaleAvoidSentinelTrigger(PlayerbotAI* ai) : Trigger(ai, "razorscale avoid sentinel") {} + bool IsActive() override; +}; + +class RazorscaleAvoidWhirlwindTrigger : public Trigger +{ +public: + RazorscaleAvoidWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "razorscale avoid whirlwind") {} + bool IsActive() override; +}; + +class RazorscaleGroundedTrigger : public Trigger +{ +public: + RazorscaleGroundedTrigger(PlayerbotAI* ai) : Trigger(ai, "razorscale grounded") {} + bool IsActive() override; +}; + +class RazorscaleHarpoonAvailableTrigger : public Trigger +{ +public: + RazorscaleHarpoonAvailableTrigger(PlayerbotAI* ai) : Trigger(ai, "razorscale harpoon trigger") {} + bool IsActive() override; +}; + +class RazorscaleFuseArmorTrigger : public Trigger +{ +public: + RazorscaleFuseArmorTrigger(PlayerbotAI* ai) : Trigger(ai, "razorscale fuse armor trigger") {} + bool IsActive() override; +}; + +#endif From e71c5e4e915a35a4f94af0a2af6c0c696615a5d3 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 09:32:35 +1100 Subject: [PATCH 11/27] Bots say when giving main tank to a real player as request player taunt --- src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index f57843eb..9146ac46 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -184,5 +184,14 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() // Assign the single main tank group->SetGroupMemberFlag(newMainTank->GetGUID(), true, MEMBER_FLAG_MAINTANK); + // Notify if the new main tank is a real player + if (GET_PLAYERBOT_AI(newMainTank)->IsRealPlayer()) + { + const std::string playerName = newMainTank->GetName(); + const auto text = BOT_TEXT2("%s please taunt Razorscale now!", { playerName }); + botAI->Say(text); + } + _lastRoleSwapTime = std::time(nullptr); } + From 27441e93694fb28019944c6caba21a7879c7191c Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 09:47:39 +1100 Subject: [PATCH 12/27] Update RaidUlduarBossHelper.cpp --- src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index 9146ac46..fbc16b07 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -188,7 +188,7 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() if (GET_PLAYERBOT_AI(newMainTank)->IsRealPlayer()) { const std::string playerName = newMainTank->GetName(); - const auto text = BOT_TEXT2("%s please taunt Razorscale now!", { playerName }); + const std::string text = playerName + ", please taunt Razorscale now!"; botAI->Say(text); } From 10fd4fb01441dceb6b6d6ec50ff61e5cefa9d97d Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 10:29:07 +1100 Subject: [PATCH 13/27] Update RaidUlduarBossHelper.cpp --- .../raids/ulduar/RaidUlduarBossHelper.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index fbc16b07..9a30c8d0 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -145,33 +145,35 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() if (!group) return; - // Gather all tank bots in the group, excluding those with Fuse Armor - std::vector tankBots; + // Gather all tank-capable players (bots and real players) in the group, excluding those with Fuse Armor + std::vector tankCandidates; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !botAI->IsTank(member) || !member->IsAlive()) + if (!member || !botAI->IsTank(member, true) || !member->IsAlive()) continue; Aura* fuseArmor = member->GetAura(SPELL_FUSEARMOR); if (fuseArmor && fuseArmor->GetStackAmount() >= FUSEARMOR_THRESHOLD) continue; - tankBots.push_back(member); + tankCandidates.push_back(member); } - if (tankBots.empty()) + if (tankCandidates.empty()) return; // Sort tanks by max health descending - std::sort(tankBots.begin(), tankBots.end(), + std::sort(tankCandidates.begin(), tankCandidates.end(), [](Player* a, Player* b) { return a->GetMaxHealth() > b->GetMaxHealth(); } ); - Player* newMainTank = tankBots[0]; + Player* newMainTank = tankCandidates[0]; + if (!newMainTank) // Safety check + return; // Remove all MAINTANK flags for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -185,13 +187,11 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() group->SetGroupMemberFlag(newMainTank->GetGUID(), true, MEMBER_FLAG_MAINTANK); // Notify if the new main tank is a real player - if (GET_PLAYERBOT_AI(newMainTank)->IsRealPlayer()) + if (GET_PLAYERBOT_AI(newMainTank) && GET_PLAYERBOT_AI(newMainTank)->IsRealPlayer()) { - const std::string playerName = newMainTank->GetName(); - const std::string text = playerName + ", please taunt Razorscale now!"; + const std::string text = newMainTank->GetName() + ", please taunt Razorscale now!"; botAI->Say(text); } _lastRoleSwapTime = std::time(nullptr); } - From eacce5adf3138e016a3a9b27689eab1ea0c5f72e Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 10:54:19 +1100 Subject: [PATCH 14/27] Fixed bot say --- src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index 9a30c8d0..dc0a7da5 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -190,7 +190,7 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() if (GET_PLAYERBOT_AI(newMainTank) && GET_PLAYERBOT_AI(newMainTank)->IsRealPlayer()) { const std::string text = newMainTank->GetName() + ", please taunt Razorscale now!"; - botAI->Say(text); + bot->Say(text, LANG_UNIVERSAL); } _lastRoleSwapTime = std::time(nullptr); From f72a6bf53bb20f87021b0a8efb5e833b9c0445ae Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 11:15:57 +1100 Subject: [PATCH 15/27] Still fixing bot say --- src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index dc0a7da5..e33f8cf9 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -1,3 +1,4 @@ +#include "ChatHelper.h" #include "RaidUlduarBossHelper.h" #include "ObjectAccessor.h" #include "GameObject.h" @@ -189,7 +190,10 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() // Notify if the new main tank is a real player if (GET_PLAYERBOT_AI(newMainTank) && GET_PLAYERBOT_AI(newMainTank)->IsRealPlayer()) { - const std::string text = newMainTank->GetName() + ", please taunt Razorscale now!"; + const std::string playerName = newMainTank->GetName(); + const std::string text = playerName + " please taunt Razorscale now!"; + + // const std::string text = newMainTank->GetName() + ", please taunt Razorscale now!"; bot->Say(text, LANG_UNIVERSAL); } From 39c6fb8daa31a4681bc1ad92f76b793e0a9dcfb9 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 13:05:14 +1100 Subject: [PATCH 16/27] Resolved issues with IsRealPlayer and GET_PLAYERBOT_AI --- .../raids/ulduar/RaidUlduarBossHelper.cpp | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index e33f8cf9..ba3ac223 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -188,14 +188,23 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() group->SetGroupMemberFlag(newMainTank->GetGUID(), true, MEMBER_FLAG_MAINTANK); // Notify if the new main tank is a real player - if (GET_PLAYERBOT_AI(newMainTank) && GET_PLAYERBOT_AI(newMainTank)->IsRealPlayer()) + // If newMainTank is a real player, GET_PLAYERBOT_AI(...) should be null + PlayerbotAI* newMainTankAI = GET_PLAYERBOT_AI(newMainTank); + + // If this is a normal bot, newMainTankAI won't be nullptr, but it won't be a real player either + bool isRealPlayer = (newMainTankAI == nullptr); + + if (!isRealPlayer) { - const std::string playerName = newMainTank->GetName(); - const std::string text = playerName + " please taunt Razorscale now!"; - - // const std::string text = newMainTank->GetName() + ", please taunt Razorscale now!"; - bot->Say(text, LANG_UNIVERSAL); + return; } + + // Otherwise, we have a real player + const std::string playerName = newMainTank->GetName(); + const std::string text = playerName + " please taunt Razorscale now!"; + + bot->Say("Test message from Razorscale debug!", LANG_UNIVERSAL); + bot->Say(text, LANG_UNIVERSAL); _lastRoleSwapTime = std::time(nullptr); } From a6de87f6fc2d48979fc3715e65db6c41e113c2aa Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 13:37:33 +1100 Subject: [PATCH 17/27] Allowed tank bots to reassign roles if human main tank is affected by fused armor --- .../raids/ulduar/RaidUlduarActions.cpp | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/src/strategy/raids/ulduar/RaidUlduarActions.cpp index dfc0b036..059f2bd4 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -993,30 +993,52 @@ bool RazorscaleHarpoonAction::isUseful() bool RazorscaleFuseArmorAction::isUseful() { - if (!botAI->IsMainTank(bot)) + // If this bot cannot tank at all, no need to do anything + if (!botAI->IsTank(bot)) return false; - Aura* fuseArmor = bot->GetAura(RazorscaleBossHelper::SPELL_FUSEARMOR); - if (!fuseArmor) + // 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; - return fuseArmor->GetStackAmount() >= RazorscaleBossHelper::FUSEARMOR_THRESHOLD; + 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) { - if (!botAI->IsMainTank(bot)) - { - return false; - } + // 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); - bossHelper.AssignRolesBasedOnHealth(); - // Check if the bot is still the main tank after reassignment - if (botAI->IsMainTank(bot)) - { - return false; - } + // Attempt to reassign the roles based on health/Fuse Armor debuff + bossHelper.AssignRolesBasedOnHealth(); return true; } From 5c63e6faf34b24686d41cd32188e13d8e9d84760 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 13:37:37 +1100 Subject: [PATCH 18/27] Allowed tank bots to reassign roles if human main tank is affected by fused armor --- src/strategy/raids/ulduar/RaidUlduarTriggers.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp index 49f2e6e3..d2da0940 100644 --- a/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp @@ -212,6 +212,17 @@ bool RazorscaleHarpoonAvailableTrigger::IsActive() bool RazorscaleFuseArmorTrigger::IsActive() { + // Get the boss entity + Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); + if (!boss || !boss->IsAlive()) + { + return false; + } + + // Only proceed if this bot can actually tank + if (!botAI->IsTank(bot)) + return false; + Group* group = bot->GetGroup(); if (!group) return false; From 67bc4a8e8fa1dff50d234157be02fdfe5958178d Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 13:37:42 +1100 Subject: [PATCH 19/27] Allowed tank bots to reassign roles if human main tank is affected by fused armor --- src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index ba3ac223..2f7411d9 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -202,9 +202,8 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() // Otherwise, we have a real player const std::string playerName = newMainTank->GetName(); const std::string text = playerName + " please taunt Razorscale now!"; - - bot->Say("Test message from Razorscale debug!", LANG_UNIVERSAL); - bot->Say(text, LANG_UNIVERSAL); + + bot->Yell(text, LANG_UNIVERSAL); _lastRoleSwapTime = std::time(nullptr); } From b42a40b8f4ebd40bbe47572a7685eec237a6d502 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 15:00:04 +1100 Subject: [PATCH 20/27] Role assignment cooldown mapped per bot --- src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index 2f7411d9..2b41fc38 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -205,5 +205,9 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() bot->Yell(text, LANG_UNIVERSAL); - _lastRoleSwapTime = std::time(nullptr); + ObjectGuid botGuid = bot->GetGUID(); + if (!botGuid) + return; + + _lastRoleSwapTime[botGuid] = std::time(nullptr); } From 4694de7ce0b210787128221e04c05ec0a339e93d Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 15:17:34 +1100 Subject: [PATCH 21/27] Update RaidUlduarBossHelper.cpp --- .../raids/ulduar/RaidUlduarBossHelper.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index 2b41fc38..3ce25b04 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -133,8 +133,24 @@ bool RazorscaleBossHelper::AreRolesAssigned() const bool RazorscaleBossHelper::CanSwapRoles() const { + // Identify the GUID of the current bot + ObjectGuid botGuid = bot->GetGUID(); + if (!botGuid) + return false; + + // If no entry exists yet for this bot, initialize it to 0 + auto it = _lastRoleSwapTime.find(botGuid); + if (it == _lastRoleSwapTime.end()) + { + _lastRoleSwapTime[botGuid] = 0; + it = _lastRoleSwapTime.find(botGuid); + } + + // Compare the current time against the stored time std::time_t currentTime = std::time(nullptr); - return (currentTime - _lastRoleSwapTime) >= _roleSwapCooldown; + std::time_t lastSwapTime = it->second; + + return (currentTime - lastSwapTime) >= _roleSwapCooldown; } void RazorscaleBossHelper::AssignRolesBasedOnHealth() From b897593d768c1b6d72191524ebd70cd8eb528b92 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 15:19:49 +1100 Subject: [PATCH 22/27] Update RaidUlduarBossHelper.cpp --- .../raids/ulduar/RaidUlduarBossHelper.cpp | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index 3ce25b04..2b41fc38 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -133,24 +133,8 @@ bool RazorscaleBossHelper::AreRolesAssigned() const bool RazorscaleBossHelper::CanSwapRoles() const { - // Identify the GUID of the current bot - ObjectGuid botGuid = bot->GetGUID(); - if (!botGuid) - return false; - - // If no entry exists yet for this bot, initialize it to 0 - auto it = _lastRoleSwapTime.find(botGuid); - if (it == _lastRoleSwapTime.end()) - { - _lastRoleSwapTime[botGuid] = 0; - it = _lastRoleSwapTime.find(botGuid); - } - - // Compare the current time against the stored time std::time_t currentTime = std::time(nullptr); - std::time_t lastSwapTime = it->second; - - return (currentTime - lastSwapTime) >= _roleSwapCooldown; + return (currentTime - _lastRoleSwapTime) >= _roleSwapCooldown; } void RazorscaleBossHelper::AssignRolesBasedOnHealth() From 900a922d3406cd6ed0bdddf453d64bbff0d3e45a Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 19:45:51 +1100 Subject: [PATCH 23/27] Added logic for bots to offtank Bot tanks will mark targets and establish themselves as main tank when necessary, announcing when ever re-assignment occurs --- .../raids/ulduar/RaidUlduarActions.cpp | 212 +++++++++++++----- 1 file changed, 162 insertions(+), 50 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/src/strategy/raids/ulduar/RaidUlduarActions.cpp index 059f2bd4..6e378bd4 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -440,6 +440,12 @@ bool RazorscaleAvoidDevouringFlameAction::Execute(Event event) } } + // 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) { @@ -477,11 +483,6 @@ bool RazorscaleAvoidSentinelAction::Execute(Event event) { bool isTank = botAI->IsTank(bot); bool isMainTank = botAI->IsMainTank(bot); - if (isTank && !isMainTank) - { - return false; - } - bool isRanged = botAI->IsRanged(bot); const float radius = 8.0f; @@ -512,38 +513,76 @@ bool RazorscaleAvoidSentinelAction::Execute(Event event) } } - // Mark the lowest-health sentinel with Skull if main tank - if (isMainTank && lowestHealthSentinel) + // 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 (currentSkullTarget && lowestHealthSentinel->GetGUID() != currentSkullTarget) + + // 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 isTank = botAI->IsTank(bot); bool isMainTank = botAI->IsMainTank(bot); - if (isTank && !isMainTank) - { - return false; - } - - // Main tank always tries to mark sentinel + 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; @@ -621,30 +660,62 @@ bool RazorscaleIgnoreBossAction::isUseful() Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); if (!boss) { - return false; + return false; } // Check if the boss is flying if (boss->GetPositionZ() >= RazorscaleBossHelper::RAZORSCALE_FLYING_Z_THRESHOLD) { - bool isMainTank = botAI->IsMainTank(bot); // 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) + 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; } - // Check moon mark if main tank - if (isMainTank) + + // 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 { - Group* group = bot->GetGroup(); - if (group) + for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes { - int8 moonIndex = 4; - ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); - return currentMoonTarget != boss->GetGUID(); + if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Valid bot tank + { + return true; // This bot should assign the marker + } } } } + return false; } @@ -653,13 +724,20 @@ bool RazorscaleIgnoreBossAction::Execute(Event event) Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); if (!boss) { - return false; + return false; } - bool isMainTank = botAI->IsMainTank(bot); - if (!isMainTank) + 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) { - // Move non-tanks inside return MoveInside( ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, @@ -670,22 +748,43 @@ bool RazorscaleIgnoreBossAction::Execute(Event event) ); } - Group* group = bot->GetGroup(); - if (!group) + if (!botAI->IsTank(bot)) { return false; } - // Assign moon rti to boss + // Check if the boss is already set as the moon marker int8 moonIndex = 4; ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); - if (currentMoonTarget != boss->GetGUID()) + 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); } - // Move main tank inside + // Tanks move inside the arena return MoveInside( ULDUAR_MAP_ID, RazorscaleBossHelper::RAZORSCALE_ARENA_CENTER_X, @@ -792,10 +891,35 @@ bool RazorscaleGroundedAction::Execute(Event event) if (!group) return false; - if (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 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); @@ -804,24 +928,12 @@ bool RazorscaleGroundedAction::Execute(Event event) } } - if (botAI->IsTank(bot)) - { - 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) - { + if (mainTank && (botAI->IsTank(bot) && !botAI->IsMainTank(bot))) + { + constexpr float followDistance = 2.0f; return MoveNear(mainTank, followDistance, MovementPriority::MOVEMENT_COMBAT); - } } if (botAI->IsRanged(bot)) From c265a5e01835e74ba45c983678c49406bd2e39d8 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 19:46:59 +1100 Subject: [PATCH 24/27] Refined assignment cooldowns and logic for bot tanks --- .../raids/ulduar/RaidUlduarBossHelper.cpp | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp index 2b41fc38..5ebb58a6 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.cpp @@ -9,7 +9,11 @@ #include "Playerbots.h" #include "World.h" +// Prevent harpoon spam std::unordered_map RazorscaleBossHelper::_harpoonCooldowns; +// Prevent role assignment spam +std::unordered_map RazorscaleBossHelper::_lastRoleSwapTime; +const std::time_t RazorscaleBossHelper::_roleSwapCooldown; bool RazorscaleBossHelper::UpdateBossAI() { @@ -118,14 +122,15 @@ bool RazorscaleBossHelper::AreRolesAssigned() const if (!group) return false; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + // Retrieve the group member slot list (GUID + flags + other info) + Group::MemberSlotList const& slots = group->GetMemberSlots(); + for (auto const& slot : slots) { - Player* member = ref->GetSource(); - if (!member) - continue; - - if (botAI->IsMainTank(member)) + // Check if this member has the MAINTANK flag + if (slot.flags & MEMBER_FLAG_MAINTANK) + { return true; + } } return false; @@ -133,12 +138,30 @@ bool RazorscaleBossHelper::AreRolesAssigned() const bool RazorscaleBossHelper::CanSwapRoles() const { + // Identify the GUID of the current bot + ObjectGuid botGuid = bot->GetGUID(); + if (!botGuid) + return false; + + // If no entry exists yet for this bot, initialize it to 0 + auto it = _lastRoleSwapTime.find(botGuid); + if (it == _lastRoleSwapTime.end()) + { + _lastRoleSwapTime[botGuid] = 0; + it = _lastRoleSwapTime.find(botGuid); + } + + // Compare the current time against the stored time std::time_t currentTime = std::time(nullptr); - return (currentTime - _lastRoleSwapTime) >= _roleSwapCooldown; + std::time_t lastSwapTime = it->second; + + return (currentTime - lastSwapTime) >= _roleSwapCooldown; } + void RazorscaleBossHelper::AssignRolesBasedOnHealth() { + // Check if enough time has passed since last swap if (!CanSwapRoles()) return; @@ -146,7 +169,7 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() if (!group) return; - // Gather all tank-capable players (bots and real players) in the group, excluding those with Fuse Armor + // Gather all tank-capable players (bots + real players), excluding those with too many Fuse Armor stacks std::vector tankCandidates; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -161,10 +184,11 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() tankCandidates.push_back(member); } + // If there are no viable tanks, do nothing if (tankCandidates.empty()) return; - // Sort tanks by max health descending + // Sort by highest max health first std::sort(tankCandidates.begin(), tankCandidates.end(), [](Player* a, Player* b) { @@ -172,11 +196,12 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() } ); + // Pick the top candidate Player* newMainTank = tankCandidates[0]; if (!newMainTank) // Safety check return; - // Remove all MAINTANK flags + // Unflag everyone from main tank for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); @@ -187,27 +212,15 @@ void RazorscaleBossHelper::AssignRolesBasedOnHealth() // Assign the single main tank group->SetGroupMemberFlag(newMainTank->GetGUID(), true, MEMBER_FLAG_MAINTANK); - // Notify if the new main tank is a real player - // If newMainTank is a real player, GET_PLAYERBOT_AI(...) should be null - PlayerbotAI* newMainTankAI = GET_PLAYERBOT_AI(newMainTank); - - // If this is a normal bot, newMainTankAI won't be nullptr, but it won't be a real player either - bool isRealPlayer = (newMainTankAI == nullptr); - - if (!isRealPlayer) - { - return; - } - - // Otherwise, we have a real player + // Yell a message regardless of whether the new main tank is a bot or a real player const std::string playerName = newMainTank->GetName(); - const std::string text = playerName + " please taunt Razorscale now!"; - + const std::string text = playerName + " set as main tank!"; bot->Yell(text, LANG_UNIVERSAL); ObjectGuid botGuid = bot->GetGUID(); if (!botGuid) return; - + + // Set current time in the cooldown map for this bot to start cooldown _lastRoleSwapTime[botGuid] = std::time(nullptr); } From 11c9ee4d728fc5faca482679e212ace2f2ebebf6 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 19:48:02 +1100 Subject: [PATCH 25/27] Removed Static declarations for role assignment cooldowns --- src/strategy/raids/ulduar/RaidUlduarBossHelper.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarBossHelper.h b/src/strategy/raids/ulduar/RaidUlduarBossHelper.h index 6b44b0ea..2fb84573 100644 --- a/src/strategy/raids/ulduar/RaidUlduarBossHelper.h +++ b/src/strategy/raids/ulduar/RaidUlduarBossHelper.h @@ -72,7 +72,7 @@ public: }; explicit RazorscaleBossHelper(PlayerbotAI* botAI) - : AiObject(botAI), _boss(nullptr), _lastRoleSwapTime(0), _roleSwapCooldown(10) {} + : AiObject(botAI), _boss(nullptr) {} bool UpdateBossAI(); Unit* GetBoss() const; @@ -93,8 +93,12 @@ public: private: Unit* _boss; - std::time_t _lastRoleSwapTime; - const std::time_t _roleSwapCooldown; + + // A map to track the last role swap *per bot* by their GUID + static std::unordered_map _lastRoleSwapTime; + + // The cooldown that applies to every bot + static const std::time_t _roleSwapCooldown = 10; static std::unordered_map _harpoonCooldowns; }; From f9d945ce33a8a5bd2cda353552accef3aba06127 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 19:49:37 +1100 Subject: [PATCH 26/27] Adjusted priority for fuse armor --- src/strategy/raids/ulduar/RaidUlduarStrategy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp b/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp index 8deca8b1..c9f9916b 100644 --- a/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp @@ -44,7 +44,7 @@ void RaidUlduarStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode( "razorscale fuse armor trigger", - NextAction::array(0, new NextAction("razorscale fuse armor action", ACTION_MOVE), nullptr))); + NextAction::array(0, new NextAction("razorscale fuse armor action", ACTION_RAID + 2), nullptr))); } From 6cc52988195ad2d438d63226406522fc053c2029 Mon Sep 17 00:00:00 2001 From: avirar Date: Sun, 22 Dec 2024 19:50:58 +1100 Subject: [PATCH 27/27] Update RaidUlduarTriggers.cpp