Feat/onyxia raid strategy init (#1182)

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* feat: init onyxia raid strategy

* Feat/onyxia raid strategy init fix (#1)

* feat: init onyxia raid strategy

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix

* feat: ony raid strategy init fix
This commit is contained in:
Jered
2025-04-20 00:23:14 -06:00
committed by GitHub
parent 27e3b802b7
commit 9be4b26424
11 changed files with 523 additions and 0 deletions

View File

@@ -1498,6 +1498,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
std::string strategyName;
switch (mapId)
{
case 249:
strategyName = "onyxia";
break;
case 409:
strategyName = "mc";
break;

View File

@@ -28,6 +28,8 @@
#include "raids/obsidiansanctum/RaidOsTriggerContext.h"
#include "raids/eyeofeternity/RaidEoEActionContext.h"
#include "raids/vaultofarchavon/RaidVoATriggerContext.h"
#include "raids/onyxia/RaidOnyxiaActionContext.h"
#include "raids/onyxia/RaidOnyxiaTriggerContext.h"
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
#include "raids/moltencore/RaidMcActionContext.h"
@@ -52,6 +54,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new WorldPacketActionContext());
actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidOnyxiaActionContext());
actionContexts.Add(new RaidAq20ActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext());
@@ -78,6 +81,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new WorldPacketTriggerContext());
triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidOnyxiaTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());

View File

@@ -1,6 +1,7 @@
#ifndef _PLAYERBOT_RAIDSTRATEGYCONTEXT_H_
#define _PLAYERBOT_RAIDSTRATEGYCONTEXT_H_
#include "RaidOnyxiaStrategy.h"
#include "RaidUlduarStrategy.h"
#include "Strategy.h"
#include "RaidBwlStrategy.h"
@@ -29,6 +30,7 @@ public:
creators["voa"] = &RaidStrategyContext::voa;
creators["uld"] = &RaidStrategyContext::uld;
creators["icc"] = &RaidStrategyContext::icc;
creators["onyxia"] = &RaidStrategyContext::onyxia;
}
private:
@@ -41,6 +43,7 @@ private:
static Strategy* voa(PlayerbotAI* botAI) { return new RaidVoAStrategy(botAI); }
static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* icc(PlayerbotAI* botAI) { return new RaidIccStrategy(botAI); }
static Strategy* onyxia(PlayerbotAI* botAI) { return new RaidOnyxiaStrategy(botAI); }
};
#endif

View File

@@ -0,0 +1,28 @@
#ifndef _PLAYERBOT_RAIDONYXIAACTIONS_CONTEXT_H
#define _PLAYERBOT_RAIDONYXIAACTIONS_CONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidOnyxiaActions.h"
class RaidOnyxiaActionContext : public NamedObjectContext<Action>
{
public:
RaidOnyxiaActionContext()
{
creators["ony move to side"] = &RaidOnyxiaActionContext::move_to_side;
creators["ony spread out"] = &RaidOnyxiaActionContext::spread_out;
creators["ony move to safe zone"] = &RaidOnyxiaActionContext::move_to_safe_zone;
creators["ony kill whelps"] = &RaidOnyxiaActionContext::kill_whelps;
creators["ony avoid eggs move"] = &RaidOnyxiaActionContext::avoid_eggs;
}
private:
static Action* move_to_side(PlayerbotAI* ai) { return new RaidOnyxiaMoveToSideAction(ai); }
static Action* spread_out(PlayerbotAI* ai) { return new RaidOnyxiaSpreadOutAction(ai); }
static Action* move_to_safe_zone(PlayerbotAI* ai) { return new RaidOnyxiaMoveToSafeZoneAction(ai); }
static Action* kill_whelps(PlayerbotAI* ai) { return new RaidOnyxiaKillWhelpsAction(ai); }
static Action* avoid_eggs(PlayerbotAI* ai) { return new OnyxiaAvoidEggsAction(ai); }
};
#endif

View File

@@ -0,0 +1,147 @@
// RaidOnyxiaActions.cpp
#include "RaidOnyxiaActions.h"
#include "GenericSpellActions.h"
#include "LastMovementValue.h"
#include "MovementActions.h"
#include "Playerbots.h"
#include "PositionAction.h"
bool RaidOnyxiaMoveToSideAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss)
return false;
float angleToBot = boss->GetAngle(bot);
float bossFacing = boss->GetOrientation();
float diff = fabs(angleToBot - bossFacing);
if (diff > M_PI)
diff = 2 * M_PI - diff;
float distance = bot->GetDistance(boss);
// Too close (30 yards) and either in front or behind
if (distance <= 30.0f && (diff < M_PI / 4 || diff > 3 * M_PI / 4))
{
float offsetAngle = bossFacing + M_PI_2; // 90° to the right
float offsetDist = 15.0f;
float sideX = boss->GetPositionX() + offsetDist * cos(offsetAngle);
float sideY = boss->GetPositionY() + offsetDist * sin(offsetAngle);
// bot->Yell("Too close to front or tail — moving to side of Onyxia!", LANG_UNIVERSAL);
return MoveTo(boss->GetMapId(), sideX, sideY, boss->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool RaidOnyxiaSpreadOutAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss)
return false;
Player* target = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL)->m_targets.GetUnitTarget()->ToPlayer();
if (target != bot)
return false;
// bot->Yell("Spreading out — I'm the Fireball target!", LANG_UNIVERSAL);
return MoveFromGroup(9.0f); // move 9 yards
}
bool RaidOnyxiaMoveToSafeZoneAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss)
return false;
Spell* currentSpell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!currentSpell)
return false;
uint32 spellId = currentSpell->m_spellInfo->Id;
std::vector<SafeZone> safeZones = GetSafeZonesForBreath(spellId);
if (safeZones.empty())
return false;
// Find closest safe zone
SafeZone* bestZone = nullptr;
float bestDist = std::numeric_limits<float>::max();
for (auto& zone : safeZones)
{
float dist = bot->GetExactDist2d(zone.pos.GetPositionX(), zone.pos.GetPositionY());
if (dist < bestDist)
{
bestDist = dist;
bestZone = &zone;
}
}
if (!bestZone)
return false;
if (bot->IsWithinDist2d(bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->radius))
return false; // Already safe
// bot->Yell("Moving to Safe Zone!", LANG_UNIVERSAL);
return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
bool RaidOnyxiaKillWhelpsAction::Execute(Event event)
{
Unit* currentTarget = AI_VALUE(Unit*, "current target");
// If already attacking a whelp, don't swap targets
if (currentTarget && currentTarget->GetEntry() == 11262)
{
return false;
}
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
for (ObjectGuid guid : targets)
{
Creature* unit = botAI->GetCreature(guid);
if (!unit || !unit->IsAlive() || !unit->IsInWorld())
continue;
if (unit->GetEntry() == 11262) // Onyxia Whelp
{
// bot->Yell("Attacking Whelps!", LANG_UNIVERSAL);
return Attack(unit);
}
}
return false;
}
bool OnyxiaAvoidEggsAction::Execute(Event event)
{
Position botPos = Position(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
float x, y;
// get safe zone slightly away from eggs (Can this be dynamic?)
if (botPos.GetExactDist2d(-36.0f, -164.0f) <= 5.0f)
{
x = -10.0f;
y = -180.0f;
}
else if (botPos.GetExactDist2d(-34.0f, -262.0f) <= 5.0f)
{
x = -16.0f;
y = -250.0f;
}
else
{
return false; // Not in danger zone
}
// bot->Yell("Too close to eggs — backing off!", LANG_UNIVERSAL);
return MoveTo(bot->GetMapId(), x, y, bot->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
}

View File

@@ -0,0 +1,107 @@
// RaidOnyxiaActions.h
#ifndef _PLAYERBOT_RAIDONYXIAACTIONS_H_
#define _PLAYERBOT_RAIDONYXIAACTIONS_H_
#include "Action.h"
#include "AttackAction.h"
#include "GenericSpellActions.h"
#include "MovementActions.h"
class PlayerbotAI;
class RaidOnyxiaMoveToSideAction : public MovementAction
{
public:
RaidOnyxiaMoveToSideAction(PlayerbotAI* botAI, std::string const name = "ony move to side")
: MovementAction(botAI, name)
{
}
bool Execute(Event event) override;
};
class RaidOnyxiaSpreadOutAction : public MovementAction
{
public:
RaidOnyxiaSpreadOutAction(PlayerbotAI* botAI, std::string const name = "ony spread out")
: MovementAction(botAI, name)
{
}
bool Execute(Event event) override;
};
struct SafeZone
{
Position pos;
float radius;
};
class RaidOnyxiaMoveToSafeZoneAction : public MovementAction
{
public:
RaidOnyxiaMoveToSafeZoneAction(PlayerbotAI* botAI, std::string const name = "ony move to safe zone")
: MovementAction(botAI, name)
{
}
bool Execute(Event event) override;
private:
std::vector<SafeZone> GetSafeZonesForBreath(uint32 spellId)
{
// Define your safe zone coordinates based on the map
// Example assumes Onyxia's lair map coordinates
float z = bot->GetPositionZ(); // Stay at current height
switch (spellId)
{
case 17086: // N to S
case 18351: // S to N
return {SafeZone{Position(-10.0f, -180.0f, z), 5.0f},
SafeZone{Position(-20.0f, -250.0f, z), 5.0f}}; // Bottom Safe Zone
case 18576: // E to W
case 18609: // W to E
return {
SafeZone{Position(20.0f, -210.0f, z), 5.0f},
SafeZone{Position(-75.0f, -210.0f, z), 5.0f},
}; // Left Safe Zone
case 18564: // SE to NW
case 18584: // NW to SE
return {
SafeZone{Position(-60.0f, -195.0f, z), 5.0f},
SafeZone{Position(10.0f, -240.0f, z), 5.0f},
}; // NW Safe Zone
case 18596: // SW to NE
case 18617: // NE to SW
return {
SafeZone{Position(7.0f, -185.0f, z), 5.0f},
SafeZone{Position(-60.0f, -240.0f, z), 5.0f},
}; // NE Safe Zone
default:
return {SafeZone{Position(0.0f, 0.0f, z), 5.0f}}; // Fallback center - shouldn't ever happen
}
}
};
class RaidOnyxiaKillWhelpsAction : public AttackAction
{
public:
RaidOnyxiaKillWhelpsAction(PlayerbotAI* botAI, std::string const name = "ony kill whelps")
: AttackAction(botAI, name)
{
}
bool Execute(Event event) override;
};
class OnyxiaAvoidEggsAction : public MovementAction
{
public:
OnyxiaAvoidEggsAction(PlayerbotAI* botAI) : MovementAction(botAI, "ony avoid eggs move") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,30 @@
#include "RaidOnyxiaStrategy.h"
void RaidOnyxiaStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// ----------- Phase 1 (100% - 65%) -----------
triggers.push_back(new TriggerNode(
"ony near tail", NextAction::array(0, new NextAction("ony move to side", ACTION_RAID + 2), nullptr)));
triggers.push_back(new TriggerNode(
"ony avoid eggs", NextAction::array(0, new NextAction("ony avoid eggs move", ACTION_EMERGENCY + 5), nullptr)));
// ----------- Phase 2 (65% - 40%) -----------
triggers.push_back(
new TriggerNode("ony deep breath warning",
NextAction::array(0, new NextAction("ony move to safe zone", ACTION_EMERGENCY + 5), nullptr)));
triggers.push_back(
new TriggerNode("ony fireball splash incoming",
NextAction::array(0, new NextAction("ony spread out", ACTION_EMERGENCY + 2), nullptr)));
triggers.push_back(new TriggerNode(
"ony whelps spawn", NextAction::array(0, new NextAction("ony kill whelps", ACTION_RAID + 1), nullptr)));
}
void RaidOnyxiaStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
// Empty for now
}

View File

@@ -0,0 +1,19 @@
// RaidOnyxiaStrategy.h
#ifndef _PLAYERBOT_RAIDONYXIASTRATEGY_H_
#define _PLAYERBOT_RAIDONYXIASTRATEGY_H_
#include "Strategy.h"
class RaidOnyxiaStrategy : public Strategy
{
public:
RaidOnyxiaStrategy(PlayerbotAI* ai) : Strategy(ai) {}
std::string const getName() override { return "onyxia"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -0,0 +1,28 @@
#ifndef _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidOnyxiaTriggers.h"
class RaidOnyxiaTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidOnyxiaTriggerContext()
{
creators["ony near tail"] = &RaidOnyxiaTriggerContext::near_tail;
creators["ony deep breath warning"] = &RaidOnyxiaTriggerContext::deep_breath;
creators["ony fireball splash incoming"] = &RaidOnyxiaTriggerContext::fireball_splash;
creators["ony whelps spawn"] = &RaidOnyxiaTriggerContext::whelps_spawn;
creators["ony avoid eggs"] = &RaidOnyxiaTriggerContext::avoid_eggs;
}
private:
static Trigger* near_tail(PlayerbotAI* ai) { return new OnyxiaNearTailTrigger(ai); }
static Trigger* deep_breath(PlayerbotAI* ai) { return new OnyxiaDeepBreathTrigger(ai); }
static Trigger* fireball_splash(PlayerbotAI* ai) { return new RaidOnyxiaFireballSplashTrigger(ai); }
static Trigger* whelps_spawn(PlayerbotAI* ai) { return new RaidOnyxiaWhelpsSpawnTrigger(ai); }
static Trigger* avoid_eggs(PlayerbotAI* ai) { return new OnyxiaAvoidEggsTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,110 @@
#include "RaidOnyxiaTriggers.h"
#include "GenericTriggers.h"
#include "ObjectAccessor.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "strategy/values/NearestNpcsValue.h"
OnyxiaDeepBreathTrigger::OnyxiaDeepBreathTrigger(PlayerbotAI* botAI) : Trigger(botAI, "ony deep breath warning") {}
bool OnyxiaDeepBreathTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss || !boss->HasUnitState(UNIT_STATE_CASTING))
return false;
// Check if Onyxia is casting
Spell* currentSpell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!currentSpell)
return false;
uint32 spellId = currentSpell->m_spellInfo->Id;
if (spellId == 17086 || // North to South
spellId == 18351 || // South to North
spellId == 18576 || // East to West
spellId == 18609 || // West to East
spellId == 18564 || // Southeast to Northwest
spellId == 18584 || // Northwest to Southeast
spellId == 18596 || // Southwest to Northeast
spellId == 18617 // Northeast to Southwest
)
{
return true;
}
return false;
}
OnyxiaNearTailTrigger::OnyxiaNearTailTrigger(PlayerbotAI* botAI) : Trigger(botAI, "ony near tail") {}
bool OnyxiaNearTailTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss || botAI->IsTank(bot))
return false;
// Skip if Onyxia is in air or transitioning
if (!boss->IsInCombat() || boss->IsFlying() || !boss->GetVictim())
return false;
return true;
}
RaidOnyxiaFireballSplashTrigger::RaidOnyxiaFireballSplashTrigger(PlayerbotAI* botAI)
: Trigger(botAI, "ony fireball splash incoming")
{
}
bool RaidOnyxiaFireballSplashTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss || !boss->HasUnitState(UNIT_STATE_CASTING))
return false;
// Check if Onyxia is casting Fireball
Spell* currentSpell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!currentSpell || currentSpell->m_spellInfo->Id != 18392) // 18392 is the classic Fireball ID
return false;
GuidVector nearbyUnits = AI_VALUE(GuidVector, "nearest friendly players");
for (ObjectGuid guid : nearbyUnits)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit || unit == bot || !unit->IsAlive())
continue;
if (bot->GetDistance(unit) < 8.0f)
return true;
}
return false;
}
RaidOnyxiaWhelpsSpawnTrigger::RaidOnyxiaWhelpsSpawnTrigger(PlayerbotAI* botAI) : Trigger(botAI, "ony whelps spawn") {}
bool RaidOnyxiaWhelpsSpawnTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "onyxia");
if (!boss)
return false;
return !botAI->IsHeal(bot) && boss->IsFlying(); // DPS + Tanks only
}
OnyxiaAvoidEggsTrigger::OnyxiaAvoidEggsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "ony avoid eggs") {}
bool OnyxiaAvoidEggsTrigger::IsActive()
{
Position botPos = Position(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
if (botPos.GetExactDist2d(-35.0f, -165.0f) <= 5.0f)
return true;
if (botPos.GetExactDist2d(-35.0f, -260.0f) <= 5.0f)
return true;
return false;
}

View File

@@ -0,0 +1,44 @@
// OnyxiaTriggers.h
#ifndef _PLAYERBOT_ONYXIATRIGGERS_H_
#define _PLAYERBOT_ONYXIATRIGGERS_H_
#include "PlayerbotAI.h"
#include "Trigger.h"
// Mechanics
class OnyxiaDeepBreathTrigger : public Trigger
{
public:
OnyxiaDeepBreathTrigger(PlayerbotAI* botAI);
bool IsActive() override;
};
class OnyxiaNearTailTrigger : public Trigger
{
public:
OnyxiaNearTailTrigger(PlayerbotAI* botAI);
bool IsActive() override;
};
class RaidOnyxiaFireballSplashTrigger : public Trigger
{
public:
RaidOnyxiaFireballSplashTrigger(PlayerbotAI* botAI);
bool IsActive() override;
};
class RaidOnyxiaWhelpsSpawnTrigger : public Trigger
{
public:
RaidOnyxiaWhelpsSpawnTrigger(PlayerbotAI* botAI);
bool IsActive() override;
};
class OnyxiaAvoidEggsTrigger : public Trigger
{
public:
OnyxiaAvoidEggsTrigger(PlayerbotAI* botAI);
bool IsActive() override;
};
#endif