#ifndef _PLAYERBOT_RAIDNAXXBOSSHELPER_H #define _PLAYERBOT_RAIDNAXXBOSSHELPER_H #include #include "AiObject.h" #include "AiObjectContext.h" #include "EventMap.h" #include "Log.h" #include "NamedObjectContext.h" #include "ObjectGuid.h" #include "Player.h" #include "PlayerbotAI.h" #include "Playerbots.h" #include "ScriptedCreature.h" #include "SharedDefines.h" #include "Spell.h" #include "Timer.h" #include "NaxxSpellIds.h" const uint32 NAXX_MAP_ID = 533; template class GenericBossHelper : public AiObject { public: GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {} virtual bool UpdateBossAI() { if (!bot->IsInCombat()) _unit = nullptr; if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive())) _unit = nullptr; if (!_unit) { _unit = AI_VALUE2(Unit*, "find target", _name); if (!_unit) return false; _target = _unit->ToCreature(); if (!_target) return false; _ai = dynamic_cast(_target->GetAI()); if (!_ai) return false; _event_map = &_ai->events; if (!_event_map) return false; } if (!_event_map) return false; _timer = getMSTime(); return true; } virtual void Reset() { _unit = nullptr; _target = nullptr; _ai = nullptr; _event_map = nullptr; _timer = 0; } protected: std::string _name; Unit* _unit = nullptr; Creature* _target = nullptr; BossAiType* _ai = nullptr; EventMap* _event_map = nullptr; uint32 _timer = 0; }; class KelthuzadBossHelper : public AiObject { public: KelthuzadBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} const std::pair center = {3716.19f, -5106.58f}; const std::pair tank_pos = {3709.19f, -5104.86f}; const std::pair assist_tank_pos = {3746.05f, -5112.74f}; bool UpdateBossAI() { if (!bot->IsInCombat()) Reset(); if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive())) Reset(); if (!_unit) _unit = AI_VALUE2(Unit*, "find target", "kel'thuzad"); return _unit != nullptr; } bool IsPhaseOne() { return _unit && _unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); } bool IsPhaseTwo() { return _unit && !_unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); } Unit* GetAnyShadowFissure() { Unit* shadow_fissure = nullptr; GuidVector units = *context->GetValue("nearest triggers"); for (auto i = units.begin(); i != units.end(); i++) { Unit* unit = botAI->GetUnit(*i); if (!unit) continue; if (botAI->EqualLowercaseName(unit->GetName(), "shadow fissure")) shadow_fissure = unit; } return shadow_fissure; } private: void Reset() { _unit = nullptr; } Unit* _unit = nullptr; }; class RazuviousBossHelper : public AiObject { public: RazuviousBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} bool UpdateBossAI() { if (!bot->IsInCombat()) Reset(); if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive())) Reset(); if (!_unit) _unit = AI_VALUE2(Unit*, "find target", "instructor razuvious"); return _unit != nullptr; } private: void Reset() { _unit = nullptr; } Unit* _unit = nullptr; }; class SapphironBossHelper : public AiObject { public: const std::pair mainTankPos = {3512.07f, -5274.06f}; const std::pair center = {3517.31f, -5253.74f}; const float GENERIC_HEIGHT = 137.29f; SapphironBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} bool UpdateBossAI() { if (!bot->IsInCombat()) Reset(); if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive())) Reset(); if (!_unit) { _unit = AI_VALUE2(Unit*, "find target", "sapphiron"); if (!_unit) return false; } bool now_flying = _unit->IsFlying(); if (_was_flying && !now_flying) _last_land_ms = getMSTime(); _was_flying = now_flying; return true; } bool IsPhaseGround() { return _unit && !_unit->IsFlying(); } bool IsPhaseFlight() { return _unit && _unit->IsFlying(); } bool JustLanded() { if (!_last_land_ms) return false; return getMSTime() - _last_land_ms <= POSITION_TIME_AFTER_LANDED; } bool WaitForExplosion() { if (!IsPhaseFlight()) return false; Group* group = bot->GetGroup(); if (!group) return false; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); if (member && (NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) || botAI->HasAura("icebolt", member, false, false, -1, true))) { return true; } } return false; } bool FindPosToAvoidChill(std::vector& dest) { Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill10, NaxxSpellIds::Chill25}); if (!aura) { // Fallback to name for custom spell data. aura = botAI->GetAura("chill", bot); } if (!aura) return false; DynamicObject* dyn_obj = aura->GetDynobjOwner(); if (!dyn_obj) return false; Unit* currentTarget = AI_VALUE(Unit*, "current target"); float angle = 0; uint32 index = botAI->GetGroupSlotIndex(bot); if (currentTarget) { if (botAI->IsRanged(bot)) { if (bot->GetExactDist2d(currentTarget) <= 45.0f) angle = bot->GetAngle(dyn_obj) - M_PI + (rand_norm() - 0.5) * M_PI / 2; else { if (index % 2 == 0) angle = bot->GetAngle(currentTarget) + M_PI / 2; else angle = bot->GetAngle(currentTarget) - M_PI / 2; } } else { if (index % 3 == 0) angle = bot->GetAngle(currentTarget); else if (index % 3 == 1) angle = bot->GetAngle(currentTarget) + M_PI / 2; else angle = bot->GetAngle(currentTarget) - M_PI / 2; } } else angle = bot->GetAngle(dyn_obj) - M_PI + (rand_norm() - 0.5) * M_PI / 2; dest = {bot->GetPositionX() + cos(angle) * 5.0f, bot->GetPositionY() + sin(angle) * 5.0f, bot->GetPositionZ()}; return true; } private: void Reset() { _unit = nullptr; _was_flying = false; _last_land_ms = 0; } const uint32 POSITION_TIME_AFTER_LANDED = 5000; Unit* _unit = nullptr; bool _was_flying = false; uint32 _last_land_ms = 0; }; class GluthBossHelper : public AiObject { public: const std::pair mainTankPos25 = {3331.48f, -3109.06f}; const std::pair mainTankPos10 = {3278.29f, -3162.06f}; const std::pair beforeDecimatePos = {3267.34f, -3175.68f}; const std::pair leftSlowDownPos = {3290.68f, -3141.65f}; const std::pair rightSlowDownPos = {3300.78f, -3151.98f}; const std::pair rangedPos = {3301.45f, -3139.29f}; const std::pair healPos = {3303.09f, -3135.24f}; const float decimatedZombiePct = 10.0f; GluthBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} bool UpdateBossAI() { if (!bot->IsInCombat()) Reset(); if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive())) Reset(); if (!_unit) { _unit = AI_VALUE2(Unit*, "find target", "gluth"); if (!_unit) return false; } if (_unit->IsInCombat()) { if (_combat_start_ms == 0) _combat_start_ms = getMSTime(); } else _combat_start_ms = 0; return true; } bool BeforeDecimate() { if (!_unit || !_unit->HasUnitState(UNIT_STATE_CASTING)) return false; Spell* spell = _unit->GetCurrentSpell(CURRENT_GENERIC_SPELL); if (!spell) spell = _unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL); if (!spell) return false; SpellInfo const* info = spell->GetSpellInfo(); if (!info) return false; if (NaxxSpellIds::MatchesAnySpellId( info, {NaxxSpellIds::Decimate10, NaxxSpellIds::Decimate25, NaxxSpellIds::Decimate25Alt})) return true; // Fallback to name for custom spell data. return info->SpellName[LOCALE_enUS] && botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "decimate"); } bool JustStartCombat() const { return _combat_start_ms != 0 && getMSTime() - _combat_start_ms < 10000; } bool IsZombieChow(Unit* unit) const { return unit && botAI->EqualLowercaseName(unit->GetName(), "zombie chow"); } private: void Reset() { _unit = nullptr; _combat_start_ms = 0; } Unit* _unit = nullptr; uint32 _combat_start_ms = 0; }; class LoathebBossHelper : public AiObject { public: const std::pair mainTankPos = {2877.57f, -3967.00f}; const std::pair rangePos = {2896.96f, -3980.61f}; LoathebBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} bool UpdateBossAI() { if (!bot->IsInCombat()) Reset(); if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive())) Reset(); if (!_unit) _unit = AI_VALUE2(Unit*, "find target", "loatheb"); return _unit != nullptr; } private: void Reset() { _unit = nullptr; } Unit* _unit = nullptr; }; class FourHorsemenBossHelper : public AiObject { public: const float posZ = 241.27f; const std::pair attractPos[2] = {{2502.03f, -2910.90f}, {2484.61f, -2947.07f}}; // left (sir zeliek), right (lady blaumeux) FourHorsemenBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} bool UpdateBossAI() { if (!bot->IsInCombat()) Reset(); else if (_combat_start_ms == 0) _combat_start_ms = getMSTime(); if (_sir && (!_sir->IsInWorld() || !_sir->IsAlive())) Reset(); if (!_sir) { _sir = AI_VALUE2(Unit*, "find target", "sir zeliek"); if (!_sir) return false; } _lady = AI_VALUE2(Unit*, "find target", "lady blaumeux"); return true; } void Reset() { _sir = nullptr; _lady = nullptr; _combat_start_ms = 0; posToGo = 0; } bool IsAttracter(Player* bot) { Difficulty diff = bot->GetRaidDifficulty(); if (diff == RAID_DIFFICULTY_25MAN_NORMAL) { return botAI->IsAssistRangedDpsOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 1) || botAI->IsAssistHealOfIndex(bot, 2); } return botAI->IsAssistRangedDpsOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 0); } void CalculatePosToGo(Player* bot) { bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL; Unit* lady = _lady; if (!lady) posToGo = 0; else { uint32 elapsed_ms = _combat_start_ms ? getMSTime() - _combat_start_ms : 0; // Interval: 24s - 15s - 15s - ... posToGo = !(elapsed_ms <= 9000 || ((elapsed_ms - 9000) / 67500) % 2 == 0); if (botAI->IsAssistRangedDpsOfIndex(bot, 0) || (raid25 && botAI->IsAssistHealOfIndex(bot, 1))) posToGo = 1 - posToGo; } } std::pair CurrentAttractPos() { bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL; float posX = attractPos[posToGo].first, posY = attractPos[posToGo].second; if (posToGo == 1) { float offset_x = 0.0f; float offset_y = 0.0f; float bias = 4.5f; if (raid25) { offset_x = -bias; offset_y = bias; } posX += offset_x; posY += offset_y; } return {posX, posY}; } Unit* CurrentAttackTarget() { if (posToGo == 0) return _sir; return _lady; } protected: Unit* _sir = nullptr; Unit* _lady = nullptr; uint32 _combat_start_ms = 0; int posToGo = 0; }; class ThaddiusBossHelper : public AiObject { public: const std::pair tankPosFeugen = {3522.94f, -3002.60f}; const std::pair tankPosStalagg = {3436.14f, -2919.98f}; const std::pair rangedPosFeugen = {3500.45f, -2997.92f}; const std::pair rangedPosStalagg = {3441.01f, -2942.04f}; const float tankPosZ = 312.61f; ThaddiusBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} bool UpdateBossAI() { if (!bot->IsInCombat()) Reset(); if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive())) Reset(); if (!_unit) { _unit = AI_VALUE2(Unit*, "find target", "thaddius"); if (!_unit) return false; } feugen = AI_VALUE2(Unit*, "find target", "feugen"); stalagg = AI_VALUE2(Unit*, "find target", "stalagg"); return true; } bool IsPhasePet() { return (feugen && feugen->IsAlive()) || (stalagg && stalagg->IsAlive()); } bool IsPhaseTransition() { if (IsPhasePet()) return false; return _unit && _unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); } bool IsPhaseThaddius() { return !IsPhasePet() && !IsPhaseTransition(); } Unit* GetNearestPet() { Unit* unit = nullptr; if (feugen && feugen->IsAlive()) unit = feugen; if (stalagg && stalagg->IsAlive() && (!feugen || !feugen->IsAlive() || bot->GetDistance(stalagg) < bot->GetDistance(feugen))) unit = stalagg; return unit; } std::pair PetPhaseGetPosForTank() { if (GetNearestPet() == feugen) return tankPosFeugen; return tankPosStalagg; } std::pair PetPhaseGetPosForRanged() { if (GetNearestPet() == feugen) return rangedPosFeugen; return rangedPosStalagg; } protected: void Reset() { _unit = nullptr; feugen = nullptr; stalagg = nullptr; } Unit* _unit = nullptr; Unit* feugen = nullptr; Unit* stalagg = nullptr; }; #endif