504 lines
16 KiB
C++
504 lines
16 KiB
C++
/*
|
|
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Affero General Public License as published by the
|
|
* Free Software Foundation; either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include "CreatureScript.h"
|
|
#include "MoveSplineInit.h"
|
|
#include "ScriptedCreature.h"
|
|
#include "SpellScriptLoader.h"
|
|
#include "WaypointMgr.h"
|
|
#include "the_eye.h"
|
|
|
|
enum Spells
|
|
{
|
|
SPELL_BERSERK = 45078,
|
|
SPELL_FLAME_QUILLS = 34229,
|
|
SPELL_QUILL_MISSILE_1 = 34269, // 21
|
|
SPELL_QUILL_MISSILE_2 = 34314, // 3
|
|
SPELL_CLEAR_ALL_DEBUFFS = 34098,
|
|
SPELL_FLAME_BUFFET = 34121,
|
|
SPELL_EMBER_BLAST = 34341,
|
|
SPELL_REBIRTH_PHASE2 = 34342,
|
|
SPELL_MELT_ARMOR = 35410,
|
|
SPELL_CHARGE = 35412,
|
|
SPELL_REBIRTH_DIVE = 35369,
|
|
SPELL_DIVE_BOMB_VISUAL = 35367,
|
|
SPELL_DIVE_BOMB = 35181
|
|
};
|
|
|
|
// @todo: Alar doesnt seem to move to waypoints but instead to the triggers in p1
|
|
const Position alarPoints[9] =
|
|
{
|
|
{335.638f, 59.4879f, 17.9319f, 4.60f}, //first platform
|
|
{388.751007f, 31.731199f, 20.263599f, 1.61f},
|
|
{388.790985f, -33.105900f, 20.263599f, 0.52f},
|
|
{332.722992f, -61.159f, 17.979099f, 5.71f},
|
|
{258.959015f, -38.687099f, 20.262899f, 5.21f}, //pre-nerf only
|
|
{259.2277997, 35.879002f, 20.263f, 4.81f}, //pre-nerf only
|
|
{332.0f, 0.01f, 43.0f, 0.0f}, //quill
|
|
{331.0f, 0.01f, -2.38f, 0.0f}, //middle (p2)
|
|
{332.0f, 0.01f, 43.0f, 0.0f} // dive
|
|
};
|
|
|
|
enum Misc
|
|
{
|
|
DISPLAYID_INVISIBLE = 23377,
|
|
NPC_EMBER_OF_ALAR = 19551,
|
|
NPC_FLAME_PATCH = 20602,
|
|
|
|
POINT_PLATFORM = 0,
|
|
POINT_QUILL = 6,
|
|
POINT_MIDDLE = 7,
|
|
POINT_DIVE = 8,
|
|
|
|
EVENT_RELOCATE_MIDDLE = 1,
|
|
EVENT_REBIRTH = 2,
|
|
EVENT_SPELL_BERSERK = 3,
|
|
|
|
EVENT_MOVE_TO_PHASE_2 = 4,
|
|
EVENT_FINISH_DIVE = 5,
|
|
EVENT_INVISIBLE = 6
|
|
};
|
|
|
|
enum GroupAlar
|
|
{
|
|
GROUP_FLAME_BUFFET = 1
|
|
};
|
|
|
|
// Xinef: Ruse of the Ashtongue (10946)
|
|
enum qruseoftheAshtongue
|
|
{
|
|
SPELL_ASHTONGUE_RUSE = 42090,
|
|
QUEST_RUSE_OF_THE_ASHTONGUE = 10946,
|
|
};
|
|
|
|
const float INNER_CIRCLE_RADIUS = 60.0f;
|
|
|
|
struct boss_alar : public BossAI
|
|
{
|
|
|
|
boss_alar(Creature* creature) : BossAI(creature, DATA_ALAR)
|
|
{
|
|
me->SetCombatMovement(false);
|
|
scheduler.SetValidator([this]
|
|
{
|
|
return !me->HasUnitState(UNIT_STATE_CASTING);
|
|
});
|
|
}
|
|
|
|
void JustReachedHome() override
|
|
{
|
|
BossAI::JustReachedHome();
|
|
if (me->IsEngaged())
|
|
{
|
|
ConstructWaypointsAndMove();
|
|
}
|
|
}
|
|
|
|
void Reset() override
|
|
{
|
|
BossAI::Reset();
|
|
_canAttackCooldown = true;
|
|
_baseAttackOverride = false;
|
|
_spawnPhoenixes = false;
|
|
_transitionScheduler.CancelAll();
|
|
_platform = 0;
|
|
_noMelee = false;
|
|
_platformRoll = 0;
|
|
_noQuillTimes = 0;
|
|
_platformMoveRepeatTimer = 16s;
|
|
me->SetModelVisible(true);
|
|
me->SetReactState(REACT_AGGRESSIVE);
|
|
ConstructWaypointsAndMove();
|
|
}
|
|
|
|
void JustEngagedWith(Unit* who) override
|
|
{
|
|
BossAI::JustEngagedWith(who);
|
|
scheduler.Schedule(0s, [this](TaskContext context)
|
|
{
|
|
if (roll_chance_i(20 * _noQuillTimes))
|
|
{
|
|
_noQuillTimes = 0;
|
|
_platformRoll = RAND(0, 1);
|
|
_platform = _platformRoll ? 0 : 3;
|
|
me->GetMotionMaster()->MovePoint(POINT_QUILL, alarPoints[POINT_QUILL], false, true);
|
|
_platformMoveRepeatTimer = 16s;
|
|
}
|
|
else
|
|
{
|
|
if (_noQuillTimes++ > 0)
|
|
{
|
|
me->SetOrientation(alarPoints[_platform].GetOrientation());
|
|
SpawnPhoenixes(1, me);
|
|
}
|
|
me->GetMotionMaster()->MovePoint(POINT_PLATFORM, alarPoints[_platform], false, true);
|
|
_platform = (_platform+1)%4;
|
|
_platformMoveRepeatTimer = 30s;
|
|
}
|
|
context.Repeat(_platformMoveRepeatTimer);
|
|
});
|
|
ScheduleMainSpellAttack(0s);
|
|
}
|
|
|
|
void JustDied(Unit* killer) override
|
|
{
|
|
BossAI::JustDied(killer);
|
|
me->SetModelVisible(true);
|
|
|
|
if (Map* map = me->GetMap())
|
|
{
|
|
map->DoForAllPlayers([&](Player* player)
|
|
{
|
|
if (player->GetQuestStatus(QUEST_RUSE_OF_THE_ASHTONGUE) == QUEST_STATUS_INCOMPLETE)
|
|
{
|
|
if (player->HasAura(SPELL_ASHTONGUE_RUSE))
|
|
{
|
|
player->AreaExploredOrEventHappens(QUEST_RUSE_OF_THE_ASHTONGUE);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void MoveInLineOfSight(Unit* /*who*/) override { }
|
|
|
|
void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override
|
|
{
|
|
if (damage >= me->GetHealth() && _platform < POINT_MIDDLE)
|
|
{
|
|
damage = 0;
|
|
DoCastSelf(SPELL_EMBER_BLAST, true);
|
|
PretendToDie(me);
|
|
_transitionScheduler.Schedule(1s, [this](TaskContext)
|
|
{
|
|
me->SetVisible(false);
|
|
}).Schedule(8s, [this](TaskContext)
|
|
{
|
|
me->SetPosition(alarPoints[POINT_MIDDLE]);
|
|
}).Schedule(12s, [this](TaskContext)
|
|
{
|
|
me->SetStandState(UNIT_STAND_STATE_STAND);
|
|
me->SetVisible(true);
|
|
DoCastSelf(SPELL_CLEAR_ALL_DEBUFFS, true);
|
|
DoCastSelf(SPELL_REBIRTH_PHASE2);
|
|
}).Schedule(16001ms, [this](TaskContext)
|
|
{
|
|
me->SetHealth(me->GetMaxHealth());
|
|
me->SetReactState(REACT_AGGRESSIVE);
|
|
_noMelee = false;
|
|
me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
|
|
_platform = POINT_MIDDLE;
|
|
me->GetMotionMaster()->MoveChase(me->GetVictim());
|
|
ScheduleAbilities();
|
|
});
|
|
}
|
|
}
|
|
|
|
void PretendToDie(Creature* creature)
|
|
{
|
|
_noMelee = true;
|
|
scheduler.CancelAll();
|
|
creature->InterruptNonMeleeSpells(true);
|
|
creature->RemoveAllAuras();
|
|
creature->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
|
|
creature->SetReactState(REACT_PASSIVE);
|
|
creature->GetMotionMaster()->MovementExpired(false);
|
|
creature->GetMotionMaster()->MoveIdle();
|
|
creature->SetStandState(UNIT_STAND_STATE_DEAD);
|
|
}
|
|
|
|
void ScheduleAbilities()
|
|
{
|
|
ScheduleTimedEvent(57s, [&]
|
|
{
|
|
DoCastVictim(SPELL_MELT_ARMOR);
|
|
}, 60s);
|
|
ScheduleTimedEvent(10s, [&]
|
|
{
|
|
DoCastRandomTarget(SPELL_CHARGE, 0, 50.0f);
|
|
}, 30s);
|
|
ScheduleTimedEvent(20s, [&]
|
|
{
|
|
// find spell from sniffs?
|
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 50.0f, true))
|
|
{
|
|
me->SummonCreature(NPC_FLAME_PATCH, *target, TEMPSUMMON_TIMED_DESPAWN, 2 * MINUTE * IN_MILLISECONDS);
|
|
}
|
|
}, 30s);
|
|
ScheduleTimedEvent(50s, [&]
|
|
{
|
|
me->GetMotionMaster()->MovePoint(POINT_DIVE, alarPoints[POINT_DIVE], false, true);
|
|
scheduler.DelayAll(15s);
|
|
}, 50s);
|
|
ScheduleUniqueTimedEvent(10min, [&]
|
|
{
|
|
DoCastSelf(SPELL_BERSERK);
|
|
}, EVENT_SPELL_BERSERK);
|
|
ScheduleMainSpellAttack(0s);
|
|
}
|
|
|
|
void SpawnPhoenixes(uint8 count, Unit* targetToSpawnAt)
|
|
{
|
|
if (targetToSpawnAt)
|
|
{
|
|
Position spawnPosition = DeterminePhoenixPosition(targetToSpawnAt->GetPosition());
|
|
for (uint8 i = 0; i < count; ++i)
|
|
{
|
|
me->SummonCreature(NPC_EMBER_OF_ALAR, spawnPosition, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 6000);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoDiveBomb()
|
|
{
|
|
_noMelee = true;
|
|
scheduler.Schedule(2s, [this](TaskContext)
|
|
{
|
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 110.0f, true))
|
|
{
|
|
SpawnPhoenixes(2, target);
|
|
}
|
|
}).Schedule(6s, [this](TaskContext)
|
|
{
|
|
me->SetModelVisible(true);
|
|
DoCastSelf(SPELL_REBIRTH_DIVE);
|
|
}).Schedule(10s, [this](TaskContext)
|
|
{
|
|
me->GetMotionMaster()->MoveChase(me->GetVictim());
|
|
_noMelee = false;
|
|
});
|
|
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 90.0f, true))
|
|
{
|
|
DoCast(target, SPELL_DIVE_BOMB);
|
|
me->SetPosition(*target);
|
|
me->StopMovingOnCurrentPos();
|
|
}
|
|
me->RemoveAurasDueToSpell(SPELL_DIVE_BOMB_VISUAL);
|
|
|
|
}
|
|
|
|
void MovementInform(uint32 type, uint32 id) override
|
|
{
|
|
if (type != POINT_MOTION_TYPE)
|
|
{
|
|
if (type == ESCORT_MOTION_TYPE && me->movespline->Finalized() && !me->IsInCombat())
|
|
{
|
|
ConstructWaypointsAndMove();
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch(id)
|
|
{
|
|
case POINT_QUILL:
|
|
scheduler.CancelGroup(GROUP_FLAME_BUFFET);
|
|
scheduler.Schedule(1s, [this](TaskContext)
|
|
{
|
|
DoCastSelf(SPELL_FLAME_QUILLS);
|
|
});
|
|
ScheduleMainSpellAttack(13s);
|
|
break;
|
|
case POINT_DIVE:
|
|
scheduler.Schedule(1s, [this](TaskContext)
|
|
{
|
|
DoCastSelf(SPELL_DIVE_BOMB_VISUAL);
|
|
}).Schedule(5s, [this](TaskContext)
|
|
{
|
|
DoDiveBomb();
|
|
});
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ScheduleMainSpellAttack(std::chrono::seconds timer)
|
|
{
|
|
scheduler.Schedule(timer, GROUP_FLAME_BUFFET, [this](TaskContext context)
|
|
{
|
|
if (!me->SelectNearestTarget(me->GetCombatReach()) && !me->isMoving())
|
|
{
|
|
DoCastVictim(SPELL_FLAME_BUFFET);
|
|
}
|
|
context.Repeat(2s);
|
|
});
|
|
}
|
|
|
|
void ConstructWaypointsAndMove()
|
|
{
|
|
me->StopMoving();
|
|
if (WaypointPath const* i_path = sWaypointMgr->GetPath(me->GetWaypointPath()))
|
|
{
|
|
Movement::PointsArray pathPoints;
|
|
pathPoints.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
|
|
for (uint8 i = 0; i < i_path->size(); ++i)
|
|
{
|
|
WaypointData const* node = i_path->at(i);
|
|
pathPoints.push_back(G3D::Vector3(node->x, node->y, node->z));
|
|
}
|
|
me->GetMotionMaster()->MoveSplinePath(&pathPoints);
|
|
}
|
|
}
|
|
|
|
void UpdateAI(uint32 diff) override
|
|
{
|
|
_transitionScheduler.Update(diff);
|
|
|
|
if (!UpdateVictim())
|
|
{
|
|
return;
|
|
}
|
|
|
|
scheduler.Update(diff);
|
|
|
|
if (me->HasUnitState(UNIT_STATE_CASTING))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!_noMelee)
|
|
{
|
|
DoMeleeAttackIfReady();
|
|
}
|
|
}
|
|
|
|
Position DeterminePhoenixPosition(Position playerPosition)
|
|
{
|
|
// set finalPosition to playerPosition in case the fraction fails
|
|
Position finalPosition = playerPosition;
|
|
float playerXPosition = playerPosition.GetPositionX();
|
|
float playerYPosition = playerPosition.GetPositionY();
|
|
float centreXPosition = alarPoints[POINT_MIDDLE].GetPositionX();
|
|
float centreYPosition = alarPoints[POINT_MIDDLE].GetPositionY();
|
|
float deltaX = std::abs(playerXPosition-centreXPosition);
|
|
float deltaY = std::abs(playerYPosition-centreYPosition);
|
|
int8 signMultiplier[2] = {1, 1};
|
|
// if fraction has x position 0.0f we get nan as a result
|
|
if (float playerFraction = deltaX/deltaY)
|
|
{
|
|
// player angle based on delta X and delta Y
|
|
float playerAngle = std::atan(playerFraction);
|
|
float phoenixDeltaYPosition = std::cos(playerAngle)*INNER_CIRCLE_RADIUS;
|
|
float phoenixDeltaXPosition = std::sin(playerAngle)*INNER_CIRCLE_RADIUS;
|
|
// as calculations are absolute values we have to multiply in the end
|
|
// should be negative if player position was further down than centre
|
|
if (playerXPosition < centreXPosition)
|
|
signMultiplier[0] = -1;
|
|
if (playerYPosition < centreYPosition)
|
|
signMultiplier[1] = -1;
|
|
// phoenix position based on set distance
|
|
finalPosition = {centreXPosition+signMultiplier[0]*phoenixDeltaXPosition, centreYPosition+signMultiplier[1]*phoenixDeltaYPosition, 0.0f, 0.0f};
|
|
}
|
|
return finalPosition;
|
|
}
|
|
|
|
private:
|
|
bool _canAttackCooldown;
|
|
bool _baseAttackOverride;
|
|
bool _spawnPhoenixes;
|
|
bool _noMelee;
|
|
uint8 _platform;
|
|
uint8 _platformRoll;
|
|
uint8 _noQuillTimes;
|
|
std::chrono::seconds _platformMoveRepeatTimer;
|
|
TaskScheduler _transitionScheduler;
|
|
};
|
|
|
|
class CastQuill : public BasicEvent
|
|
{
|
|
public:
|
|
CastQuill(Unit* caster, uint32 spellId) : _caster(caster), _spellId(spellId){ }
|
|
|
|
bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override
|
|
{
|
|
_caster->CastSpell(_caster, _spellId, true);
|
|
return true;
|
|
}
|
|
private:
|
|
Unit* _caster;
|
|
uint32 _spellId;
|
|
};
|
|
|
|
class spell_alar_flame_quills : public AuraScript
|
|
{
|
|
PrepareAuraScript(spell_alar_flame_quills);
|
|
|
|
void HandlePeriodic(AuraEffect const* /*aurEff*/)
|
|
{
|
|
PreventDefaultAction();
|
|
|
|
// 24 spells in total
|
|
for (uint8 i = 0; i < 21; ++i)
|
|
GetUnitOwner()->m_Events.AddEvent(new CastQuill(GetUnitOwner(), SPELL_QUILL_MISSILE_1 + i), GetUnitOwner()->m_Events.CalculateTime(i * 40));
|
|
GetUnitOwner()->m_Events.AddEvent(new CastQuill(GetUnitOwner(), SPELL_QUILL_MISSILE_2 + 0), GetUnitOwner()->m_Events.CalculateTime(22 * 40));
|
|
GetUnitOwner()->m_Events.AddEvent(new CastQuill(GetUnitOwner(), SPELL_QUILL_MISSILE_2 + 1), GetUnitOwner()->m_Events.CalculateTime(23 * 40));
|
|
GetUnitOwner()->m_Events.AddEvent(new CastQuill(GetUnitOwner(), SPELL_QUILL_MISSILE_2 + 2), GetUnitOwner()->m_Events.CalculateTime(24 * 40));
|
|
}
|
|
|
|
void Register() override
|
|
{
|
|
OnEffectPeriodic += AuraEffectPeriodicFn(spell_alar_flame_quills::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
|
|
}
|
|
};
|
|
|
|
class spell_alar_ember_blast : public SpellScript
|
|
{
|
|
PrepareSpellScript(spell_alar_ember_blast);
|
|
|
|
void HandleForceCast(SpellEffIndex effIndex)
|
|
{
|
|
PreventHitEffect(effIndex);
|
|
if (InstanceScript* instance = GetCaster()->GetInstanceScript())
|
|
{
|
|
if (Creature* alar = instance->GetCreature(DATA_ALAR))
|
|
{
|
|
Unit::DealDamage(GetCaster(), alar, alar->CountPctFromMaxHealth(2));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Register() override
|
|
{
|
|
OnEffectHitTarget += SpellEffectFn(spell_alar_ember_blast::HandleForceCast, EFFECT_2, SPELL_EFFECT_FORCE_CAST);
|
|
}
|
|
};
|
|
|
|
class spell_alar_dive_bomb : public AuraScript
|
|
{
|
|
PrepareAuraScript(spell_alar_dive_bomb);
|
|
|
|
void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
|
{
|
|
GetUnitOwner()->SetModelVisible(false);
|
|
GetUnitOwner()->SetDisplayId(DISPLAYID_INVISIBLE);
|
|
}
|
|
|
|
void Register() override
|
|
{
|
|
OnEffectApply += AuraEffectApplyFn(spell_alar_dive_bomb::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
|
|
}
|
|
};
|
|
|
|
void AddSC_boss_alar()
|
|
{
|
|
RegisterTheEyeAI(boss_alar);
|
|
RegisterSpellScript(spell_alar_flame_quills);
|
|
RegisterSpellScript(spell_alar_ember_blast);
|
|
RegisterSpellScript(spell_alar_dive_bomb);
|
|
}
|
|
|