/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 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 .
*/
#include "CreatureScript.h"
#include "Player.h"
#include "ScriptedCreature.h"
#include "Spell.h"
#include "SpellAuras.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "TaskScheduler.h"
#include "zulgurub.h"
enum Says
{
SAY_AGGRO = 0,
SAY_DING_KILL = 1,
SAY_WATCH = 2,
SAY_WATCH_WHISPER = 3,
SAY_OHGAN_DEAD = 4,
SAY_GRATS_JINDO = 0,
};
enum Spells
{
SPELL_CHARGE = 24408,
SPELL_OVERPOWER = 24407,
SPELL_FRIGHTENING_SHOUT = 19134,
SPELL_WHIRLWIND = 13736, // triggers 15589
SPELL_MORTAL_STRIKE = 16856,
SPELL_FRENZY = 24318,
SPELL_WATCH = 24314, // triggers 24315 and 24316
SPELL_WATCH_CHARGE = 24315, // triggers 24316
SPELL_LEVEL_UP = 24312,
SPELL_EXECUTE = 7160,
SPELL_MANDOKIR_CLEAVE = 20691,
SPELL_SUMMON_PLAYER = 25104,
SPELL_REVIVE = 24341 // chained spirit
};
enum Events
{
EVENT_CHECK_SPEAKER = 1,
EVENT_CHECK_START = 2,
EVENT_STARTED = 3,
EVENT_OVERPOWER = 4,
EVENT_MORTAL_STRIKE = 5,
EVENT_WHIRLWIND = 6,
EVENT_CHECK_OHGAN = 7,
EVENT_WATCH_PLAYER = 8,
EVENT_CHARGE_PLAYER = 9,
EVENT_EXECUTE = 10,
EVENT_CLEAVE = 11
};
enum Action
{
ACTION_START_REVIVE = 1, // broodlord mandokir
ACTION_REVIVE = 2 // chained spirit
};
enum Misc
{
POINT_START_REVIVE = 1, // chained spirit
MODEL_OHGAN_MOUNT = 15271,
PATH_MANDOKIR = 492861,
POINT_MANDOKIR_END = 25,
CHAINED_SPIRIT_COUNT = 20,
ACTION_CHARGE = 1
};
Position const PosSummonChainedSpirits[CHAINED_SPIRIT_COUNT] =
{
{ -12167.17f, -1979.330f, 133.0992f, 2.268928f },
{ -12262.74f, -1953.394f, 133.5496f, 0.593412f },
{ -12176.89f, -1983.068f, 133.7841f, 2.129302f },
{ -12226.45f, -1977.933f, 132.7982f, 1.466077f },
{ -12204.74f, -1890.431f, 135.7569f, 4.415683f },
{ -12216.70f, -1891.806f, 136.3496f, 4.677482f },
{ -12236.19f, -1892.034f, 134.1041f, 5.044002f },
{ -12248.24f, -1893.424f, 134.1182f, 5.270895f },
{ -12257.36f, -1897.663f, 133.1484f, 5.462881f },
{ -12265.84f, -1903.077f, 133.1649f, 5.654867f },
{ -12158.69f, -1972.707f, 133.8751f, 2.408554f },
{ -12178.82f, -1891.974f, 134.1786f, 3.944444f },
{ -12193.36f, -1890.039f, 135.1441f, 4.188790f },
{ -12275.59f, -1932.845f, 134.9017f, 0.174533f },
{ -12273.51f, -1941.539f, 136.1262f, 0.314159f },
{ -12247.02f, -1963.497f, 133.9476f, 0.872665f },
{ -12238.68f, -1969.574f, 133.6273f, 1.134464f },
{ -12192.78f, -1982.116f, 132.6966f, 1.919862f },
{ -12210.81f, -1979.316f, 133.8700f, 1.797689f },
{ -12283.51f, -1924.839f, 133.5170f, 0.069813f }
};
Position const PosMandokir[2] =
{
{ -12167.8f, -1927.25f, 153.73f, 3.76991f },
{ -12197.86f, -1949.392f, 130.2745f, 0.0f }
};
void RevivePlayer(Unit* victim, ObjectGuid& reviveGUID)
{
std::list chainedSpirits;
GetCreatureListWithEntryInGrid(chainedSpirits, victim, NPC_CHAINED_SPIRIT, 200.f);
if (chainedSpirits.empty())
return;
// Sort the list by distance to the victim.
chainedSpirits.sort([victim](Creature const* c1, Creature const* c2)
{
return c1->GetDistance2d(victim) < c2->GetDistance2d(victim);
});
// Now we have to check if the spirit is already reviving someone...
for (Creature* spirit : chainedSpirits)
{
if (!spirit->isMoving() && !spirit->HasUnitState(UNIT_STATE_CASTING))
{
spirit->AI()->SetGUID(reviveGUID);
spirit->AI()->DoAction(ACTION_REVIVE);
reviveGUID.Clear();
break;
}
}
}
class boss_mandokir : public CreatureScript
{
public:
boss_mandokir() : CreatureScript("boss_mandokir") { }
struct boss_mandokirAI : public BossAI
{
boss_mandokirAI(Creature* creature) : BossAI(creature, DATA_MANDOKIR) { }
void Reset() override
{
BossAI::Reset();
killCount = 0;
if (me->GetPositionZ() > 140.0f)
{
events.ScheduleEvent(EVENT_CHECK_START, 1s);
if (Creature* speaker = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_VILEBRANCH_SPEAKER)))
{
if (!speaker->IsAlive())
{
speaker->Respawn(true);
}
}
}
me->RemoveAurasDueToSpell(SPELL_FRENZY);
me->SetImmuneToAll(false);
instance->SetBossState(DATA_OHGAN, NOT_STARTED);
me->Mount(MODEL_OHGAN_MOUNT);
reviveGUID.Clear();
_useExecute = false;
_chargeTarget.first.Clear();
}
void JustDied(Unit* /*killer*/) override
{
std::list chainedSpirits;
GetCreatureListWithEntryInGrid(chainedSpirits, me, NPC_CHAINED_SPIRIT, 200.f);
for (Creature* spirit : chainedSpirits)
spirit->DespawnOrUnsummon();
instance->SetBossState(DATA_MANDOKIR, DONE);
instance->SaveToDB();
}
void JustEngagedWith(Unit* /*who*/) override
{
_JustEngagedWith();
events.ScheduleEvent(EVENT_OVERPOWER, 1s);
events.ScheduleEvent(EVENT_MORTAL_STRIKE, 14s, 28s);
events.ScheduleEvent(EVENT_WHIRLWIND, 24s, 30s);
events.ScheduleEvent(EVENT_CHECK_OHGAN, 1s);
events.ScheduleEvent(EVENT_WATCH_PLAYER, 12s, 24s);
events.ScheduleEvent(EVENT_CHARGE_PLAYER, 30s, 40s);
events.ScheduleEvent(EVENT_CLEAVE, 1s);
me->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation());
Talk(SAY_AGGRO);
me->Dismount();
// Summon Ohgan (Spell missing) TEMP HACK
me->SummonCreature(NPC_OHGAN, me->GetPositionX() - 3, me->GetPositionY(), me->GetPositionZ(), me->GetOrientation(), TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 35000);
for (int i = 0; i < CHAINED_SPIRIT_COUNT; ++i)
{
me->SummonCreature(NPC_CHAINED_SPIRIT, PosSummonChainedSpirits[i], TEMPSUMMON_CORPSE_DESPAWN);
}
DoZoneInCombat();
}
void KilledUnit(Unit* victim) override
{
if (!victim->IsPlayer())
return;
reviveGUID = victim->GetGUID();
RevivePlayer(victim, reviveGUID);
if (++killCount == 3)
{
Talk(SAY_DING_KILL);
if (Creature* jindo = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_JINDO)))
{
if (jindo->IsAlive())
{
jindo->AI()->Talk(SAY_GRATS_JINDO);
}
}
DoCastSelf(SPELL_LEVEL_UP, true);
killCount = 0;
}
}
void DoAction(int32 action) override
{
if (action == ACTION_START_REVIVE)
{
std::list creatures;
GetCreatureListWithEntryInGrid(creatures, me, NPC_CHAINED_SPIRIT, 200.0f);
if (creatures.empty())
return;
for (std::list::iterator itr = creatures.begin(); itr != creatures.end(); ++itr)
{
if (Creature* chainedSpirit = ObjectAccessor::GetCreature(*me, (*itr)->GetGUID()))
{
chainedSpirit->AI()->SetGUID(reviveGUID);
chainedSpirit->AI()->DoAction(ACTION_REVIVE);
reviveGUID.Clear();
}
}
}
}
void SetGUID(ObjectGuid const& guid, int32 type) override
{
if (type == ACTION_CHARGE)
{
if (_chargeTarget.first == guid && _chargeTarget.second > 0.f)
{
if (Unit* target = ObjectAccessor::GetUnit(*me, _chargeTarget.first))
{
me->RemoveAurasDueToSpell(SPELL_WHIRLWIND);
DoCast(target, SPELL_WATCH_CHARGE, true);
}
}
}
else
{
reviveGUID = guid;
}
}
void MovementInform(uint32 type, uint32 id) override
{
if (type == WAYPOINT_MOTION_TYPE)
{
me->SetWalk(false);
if (id == POINT_MANDOKIR_END)
{
me->SetHomePosition(PosMandokir[0]);
instance->SetBossState(DATA_MANDOKIR, NOT_STARTED);
}
}
}
void CalculateThreat(Unit* hatedUnit, float& threat, SpellInfo const* threatSpell) override
{
if (_chargeTarget.first == hatedUnit->GetGUID())
{
// Do not count DOTs/HOTs
if (!(threatSpell && (threatSpell->HasAura(SPELL_AURA_DAMAGE_SHIELD) || threatSpell->HasAttribute(SPELL_ATTR0_CU_NO_INITIAL_THREAT))))
{
_chargeTarget.second += threat;
}
}
}
void DamageDealt(Unit* doneTo, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override
{
if (doneTo && doneTo == me->GetVictim())
{
if (doneTo->HealthBelowPctDamaged(20, damage))
{
if (!_useExecute)
{
_useExecute = true;
events.ScheduleEvent(EVENT_EXECUTE, 1s);
}
}
else if (_useExecute)
{
_useExecute = false;
events.CancelEvent(EVENT_EXECUTE);
}
}
}
bool OnTeleportUnreacheablePlayer(Player* player) override
{
DoCast(player, SPELL_SUMMON_PLAYER, true);
return true;
}
void DoMeleeAttackIfReady(bool ignoreCasting)
{
if (!ignoreCasting && me->HasUnitState(UNIT_STATE_CASTING))
{
return;
}
Unit* victim = me->GetVictim();
if (!victim || !victim->IsInWorld())
return;
if (!me->IsWithinMeleeRange(victim))
return;
//Make sure our attack is ready and we aren't currently casting before checking distance
if (me->isAttackReady())
{
// xinef: prevent base and off attack in same time, delay attack at 0.2 sec
if (me->haveOffhandWeapon())
if (me->getAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
me->setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
me->AttackerStateUpdate(victim, BASE_ATTACK, false, ignoreCasting);
me->resetAttackTimer();
}
if (me->haveOffhandWeapon() && me->isAttackReady(OFF_ATTACK))
{
// xinef: delay main hand attack if both will hit at the same time (players code)
if (me->getAttackTimer(BASE_ATTACK) < ATTACK_DISPLAY_DELAY)
me->setAttackTimer(BASE_ATTACK, ATTACK_DISPLAY_DELAY);
me->AttackerStateUpdate(victim, OFF_ATTACK, false, ignoreCasting);
me->resetAttackTimer(OFF_ATTACK);
}
}
void UpdateAI(uint32 diff) override
{
events.Update(diff);
if (!UpdateVictim())
{
if (instance->GetBossState(DATA_MANDOKIR) == NOT_STARTED || instance->GetBossState(DATA_MANDOKIR) == SPECIAL)
{
while (uint32 eventId = events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_CHECK_START:
if (instance->GetBossState(DATA_MANDOKIR) == SPECIAL)
{
me->GetMotionMaster()->MovePoint(0, PosMandokir[1].m_positionX, PosMandokir[1].m_positionY, PosMandokir[1].m_positionZ);
events.ScheduleEvent(EVENT_STARTED, 6s);
}
else
{
events.ScheduleEvent(EVENT_CHECK_START, 1s);
}
break;
case EVENT_STARTED:
me->SetImmuneToAll(false);
me->SetInCombatWithZone();
break;
default:
break;
}
}
}
return;
}
if (me->HasUnitState(UNIT_STATE_CASTING) || me->HasUnitState(UNIT_STATE_CHARGING))
{
if (me->GetCurrentSpellCastTime(SPELL_WATCH) >= 0)
{
DoMeleeAttackIfReady(true);
}
return;
}
while (uint32 eventId = events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_OVERPOWER:
if (DoCastVictim(SPELL_OVERPOWER) == SPELL_CAST_OK)
{
events.ScheduleEvent(EVENT_OVERPOWER, 6s, 8s);
}
else
{
events.ScheduleEvent(EVENT_OVERPOWER, 1s);
}
break;
case EVENT_MORTAL_STRIKE:
DoCastVictim(SPELL_MORTAL_STRIKE);
events.ScheduleEvent(EVENT_MORTAL_STRIKE, 14s, 28s);
break;
case EVENT_WHIRLWIND:
DoCast(me, SPELL_WHIRLWIND);
events.ScheduleEvent(EVENT_WHIRLWIND, 22s, 26s);
break;
case EVENT_CHECK_OHGAN:
if (instance->GetBossState(DATA_OHGAN) == DONE)
{
DoCast(me, SPELL_FRENZY);
Talk(SAY_OHGAN_DEAD);
}
else
{
events.ScheduleEvent(EVENT_CHECK_OHGAN, 1s);
}
break;
case EVENT_WATCH_PLAYER:
if (Unit* player = SelectTarget(SelectTargetMethod::Random, 0, 100, true))
{
DoCast(player, SPELL_WATCH);
Talk(SAY_WATCH, player);
_chargeTarget = std::make_pair(player->GetGUID(), 0.f);
}
events.ScheduleEvent(EVENT_WATCH_PLAYER, 12s, 24s);
break;
case EVENT_CHARGE_PLAYER:
if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, [this](Unit const* target)
{
if (!me || !target)
return false;
if (!target->IsPlayer() || !me->IsWithinLOSInMap(target))
return false;
return true;
}))
{
DoCast(target, SPELL_CHARGE);
events.DelayEvents(1500ms);
if (Unit* mainTarget = SelectTarget(SelectTargetMethod::MaxThreat, 0, 100.0f))
{
me->GetThreatMgr().ModifyThreatByPercent(mainTarget, -100);
}
}
events.ScheduleEvent(EVENT_CHARGE_PLAYER, 30s, 40s);
break;
case EVENT_EXECUTE:
DoCastVictim(SPELL_EXECUTE, true);
events.ScheduleEvent(EVENT_EXECUTE, 7s, 14s);
break;
case EVENT_CLEAVE:
{
std::list meleeRangeTargets;
auto i = me->GetThreatMgr().GetThreatList().begin();
for (; i != me->GetThreatMgr().GetThreatList().end(); ++i)
{
Unit* target = (*i)->getTarget();
if (me->IsWithinMeleeRange(target))
{
meleeRangeTargets.push_back(target);
}
}
if (meleeRangeTargets.size() >= 5)
{
DoCastVictim(SPELL_MANDOKIR_CLEAVE);
events.ScheduleEvent(EVENT_CLEAVE, 10s, 20s);
}
else
{
events.ScheduleEvent(EVENT_CLEAVE, 1s);
}
break;
}
default:
break;
}
}
DoMeleeAttackIfReady(false);
}
private:
uint8 killCount;
ObjectGuid reviveGUID;
bool _useExecute;
std::pair _chargeTarget;
};
CreatureAI* GetAI(Creature* creature) const override
{
return GetZulGurubAI(creature);
}
};
// Ohgan
enum OhganSpells
{
SPELL_SUNDERARMOR = 24317,
SPELL_THRASH = 3391
};
class npc_ohgan : public CreatureScript
{
public:
npc_ohgan() : CreatureScript("npc_ohgan") { }
struct npc_ohganAI : public ScriptedAI
{
npc_ohganAI(Creature* creature) : ScriptedAI(creature), instance(creature->GetInstanceScript()) { }
void Reset() override
{
_scheduler.CancelAll();
_scheduler.SetValidator([this]
{
return !me->HasUnitState(UNIT_STATE_CASTING);
});
reviveGUID.Clear();
}
void JustEngagedWith(Unit* who) override
{
if (!who->IsPlayer())
return;
_scheduler.Schedule(6s, 12s, [this](TaskContext context)
{
DoCastVictim(SPELL_SUNDERARMOR);
context.Repeat(6s, 12s);
});
_scheduler.Schedule(12s, 18s, [this](TaskContext context)
{
DoCastSelf(SPELL_THRASH);
context.Repeat(12s, 18s);
});
}
void KilledUnit(Unit* victim) override
{
if (!victim->IsPlayer())
return;
reviveGUID = victim->GetGUID();
RevivePlayer(victim, reviveGUID);
}
void SetGUID(ObjectGuid const& guid, int32 /*type = 0 */) override
{
reviveGUID = guid;
}
void JustDied(Unit* /*killer*/) override
{
instance->SetBossState(DATA_OHGAN, DONE);
}
void UpdateAI(uint32 diff) override
{
_scheduler.Update(diff);
if (!UpdateVictim())
{
return;
}
DoMeleeAttackIfReady();
}
private:
InstanceScript* instance;
ObjectGuid reviveGUID;
TaskScheduler _scheduler;
};
CreatureAI* GetAI(Creature* creature) const override
{
return GetZulGurubAI(creature);
}
};
struct npc_chained_spirit : public ScriptedAI
{
public:
npc_chained_spirit(Creature* creature) : ScriptedAI(creature)
{
instance = me->GetInstanceScript();
me->AddUnitMovementFlag(MOVEMENTFLAG_HOVER);
}
void Reset() override
{
revivePlayerGUID.Clear();
}
void SetGUID(ObjectGuid const& guid, int32 /*id*/) override
{
revivePlayerGUID = guid;
}
void DoAction(int32 action) override
{
if (action == ACTION_REVIVE)
{
if (Player* target = ObjectAccessor::GetPlayer(*me, revivePlayerGUID))
{
Position pos;
target->GetNearPoint(me, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0.0f, 0.0f, target->GetAbsoluteAngle(me));
me->GetMotionMaster()->MovePoint(POINT_START_REVIVE, pos);
}
}
}
void MovementInform(uint32 type, uint32 pointId) override
{
if (type != POINT_MOTION_TYPE || !revivePlayerGUID)
return;
if (pointId == POINT_START_REVIVE)
{
if (Player* target = ObjectAccessor::GetPlayer(*me, revivePlayerGUID))
{
DoCast(target, SPELL_REVIVE);
}
me->DespawnOrUnsummon(1s);
}
}
void JustDied(Unit* /*killer*/) override
{
me->DespawnOrUnsummon();
}
void UpdateAI(uint32 /*diff*/) override { }
private:
InstanceScript* instance;
ObjectGuid revivePlayerGUID;
};
enum VilebranchSpells
{
SPELL_DEMORALIZING_SHOUT = 13730,
SPELL_CLEAVE = 15284
};
struct npc_vilebranch_speaker : public ScriptedAI
{
npc_vilebranch_speaker(Creature* creature) : ScriptedAI(creature), instance(creature->GetInstanceScript()) { }
void Reset() override
{
_scheduler.CancelAll();
}
void JustEngagedWith(Unit* /*who*/) override
{
_scheduler
.Schedule(2s, 4s, [this](TaskContext context)
{
DoCastAOE(SPELL_DEMORALIZING_SHOUT);
context.Repeat(22s, 30s);
})
.Schedule(5s, 8s, [this](TaskContext context)
{
DoCastVictim(SPELL_CLEAVE, true);
context.Repeat(6s, 9s);
});
}
void JustDied(Unit* /*killer*/) override
{
instance->SetBossState(DATA_MANDOKIR, SPECIAL);
}
void UpdateAI(uint32 diff) override
{
// Return since we have no target
if (!UpdateVictim())
return;
_scheduler.Update(diff, [this]
{
DoMeleeAttackIfReady();
});
}
private:
TaskScheduler _scheduler;
InstanceScript* instance;
};
class spell_threatening_gaze_aura : public AuraScript
{
PrepareAuraScript(spell_threatening_gaze_aura);
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_EXPIRE)
{
if (Unit* target = GetTarget())
{
if (Unit* caster = GetCaster())
{
if (Creature* cCaster = caster->ToCreature())
{
if (cCaster->IsAIEnabled)
{
cCaster->AI()->SetGUID(target->GetGUID(), ACTION_CHARGE);
}
}
}
}
}
}
void Register() override
{
OnEffectRemove += AuraEffectRemoveFn(spell_threatening_gaze_aura::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
class spell_mandokir_charge : public SpellScript
{
PrepareSpellScript(spell_mandokir_charge);
void LaunchHit(SpellEffIndex /*effIndex*/)
{
Unit* caster = GetCaster();
Unit* target = GetHitUnit();
if (caster && target)
caster->CastSpell(target, SPELL_FRIGHTENING_SHOUT, true);
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_mandokir_charge::LaunchHit, EFFECT_0, SPELL_EFFECT_CHARGE);
}
};
class spell_threatening_gaze_charge : public SpellScript
{
PrepareSpellScript(spell_threatening_gaze_charge)
void PreventLaunchHit(SpellEffIndex effIndex)
{
PreventHitDefaultEffect(effIndex);
}
void LaunchHit(SpellEffIndex effIndex)
{
if (Unit* caster = GetCaster())
if (Unit* target = GetHitUnit())
caster->CastSpell(target, GetSpellInfo()->Effects[effIndex].TriggerSpell, true);
}
void Register() override
{
OnEffectLaunchTarget += SpellEffectFn(spell_threatening_gaze_charge::PreventLaunchHit, EFFECT_1, SPELL_EFFECT_TRIGGER_SPELL);
OnEffectHitTarget += SpellEffectFn(spell_threatening_gaze_charge::LaunchHit, EFFECT_1, SPELL_EFFECT_TRIGGER_SPELL);
}
};
void AddSC_boss_mandokir()
{
new boss_mandokir();
new npc_ohgan();
RegisterZulGurubCreatureAI(npc_chained_spirit);
RegisterZulGurubCreatureAI(npc_vilebranch_speaker);
RegisterSpellScript(spell_threatening_gaze_aura);
RegisterSpellScript(spell_mandokir_charge);
RegisterSpellScript(spell_threatening_gaze_charge);
}