/* * Copyright (C) 2012-2017 CypherCore * * 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 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 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 . */ using Framework.Constants; using Game.AI; using Game.Entities; using Game.Maps; using Game.Movement; using Game.Scripting; using Game.Spells; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; namespace Scripts.Northrend.IcecrownCitadel { namespace Marrorgar { struct Texts { public const int SayEnterZone = 0; public const int SayAggro = 1; public const int SayBoneStorm = 2; public const int SayBonespike = 3; public const int SayKill = 4; public const int SayDeath = 5; public const int SayBerserk = 6; public const int EmoteBoneStorm = 7; } struct Spells { // Lord Marrowgar public const uint BoneSlice = 69055; public const uint BoneStorm = 69076; public const uint BoneSpikeGraveyard = 69057; public const uint ColdflameNormal = 69140; public const uint ColdflameBoneStorm = 72705; // Bone Spike public const uint Impaled = 69065; public const uint RideVehicle = 46598; // Coldflame public const uint ColdflamePassive = 69145; public const uint ColdflameSummon = 69147; } struct Misc { public static uint[] BoneSpikeSummonId = { 69062, 72669, 72670 }; public const uint EventBoneSpikeGraveyard = 1; public const uint EventColdflame = 2; public const uint EventBoneStormBegin = 3; public const uint EventBoneStormMove = 4; public const uint EventBoneStormEnd = 5; public const uint EventEnableBoneSlice = 6; public const uint EventEnrage = 7; public const uint EventWarnBoneStorm = 8; public const uint EventColdflameTrigger = 9; public const uint EventFailBoned = 10; public const uint EventGroupSpecial = 1; public const uint PointTargetBonestormPlayer = 36612631; public const uint PointTargetColdflame = 36672631; public const int DataColdflameGuid = 0; // Manual Marking For Targets Hit By Bone Slice As No Aura Exists For This Purpose // These Units Are The Tanks In This Encounter // And Should Be Immune To Bone Spike Graveyard public const int DataSpikeImmune = 1; //DataSpikeImmune1; = 2; // Reserved & Used //DataSpikeImmune2; = 3; // Reserved & Used public const int ActionClearSpikeImmunities = 1; public const uint MaxBoneSpikeImmune = 3; } class BoneSpikeTargetSelector : ISelector { public BoneSpikeTargetSelector(UnitAI ai) { _ai = ai; } public bool Check(Unit unit) { if (!unit.IsTypeId(TypeId.Player)) return false; if (unit.HasAura(Spells.Impaled)) return false; // Check if it is one of the tanks soaking Bone Slice for (int i = 0; i < Misc.MaxBoneSpikeImmune; ++i) if (unit.GetGUID() == _ai.GetGUID(Misc.DataSpikeImmune + i)) return false; return true; } UnitAI _ai; } [Script] class boss_lord_marrowgar : CreatureScript { public boss_lord_marrowgar() : base("boss_lord_marrowgar") { } public class boss_lord_marrowgarAI : BossAI { public boss_lord_marrowgarAI(Creature creature) : base(creature, Bosses.LordMarrowgar) { _boneStormDuration = RaidMode(20000, 30000, 20000, 30000); _baseSpeed = creature.GetSpeedRate(UnitMoveType.Run); _coldflameLastPos.Relocate(creature); _introDone = false; _boneSlice = false; } public override void Reset() { _Reset(); me.SetSpeedRate(UnitMoveType.Run, _baseSpeed); me.RemoveAurasDueToSpell(Spells.BoneStorm); me.RemoveAurasDueToSpell(InstanceSpells.Berserk); _events.ScheduleEvent(Misc.EventEnableBoneSlice, 10000); _events.ScheduleEvent(Misc.EventBoneSpikeGraveyard, 15000, Misc.EventGroupSpecial); _events.ScheduleEvent(Misc.EventColdflame, 5000, Misc.EventGroupSpecial); _events.ScheduleEvent(Misc.EventWarnBoneStorm, RandomHelper.URand(45000, 50000)); _events.ScheduleEvent(Misc.EventEnrage, 600000); _boneSlice = false; _boneSpikeImmune.Clear(); } public override void EnterCombat(Unit who) { Talk(Texts.SayAggro); me.setActive(true); DoZoneInCombat(); instance.SetBossState(Bosses.LordMarrowgar, EncounterState.InProgress); } public override void JustDied(Unit killer) { Talk(Texts.SayDeath); _JustDied(); } public override void JustReachedHome() { _JustReachedHome(); instance.SetBossState(Bosses.LordMarrowgar, EncounterState.Fail); instance.SetData(DataTypes.BonedAchievement, 1); // reset } public override void KilledUnit(Unit victim) { if (victim.IsTypeId(TypeId.Player)) Talk(Texts.SayKill); } public override void MoveInLineOfSight(Unit who) { if (!_introDone && me.IsWithinDistInMap(who, 70.0f)) { Talk(Texts.SayEnterZone); _introDone = true; } } public override void UpdateAI(uint diff) { if (!UpdateVictim()) return; _events.Update(diff); if (me.HasUnitState(UnitState.Casting)) return; _events.ExecuteEvents(eventId => { switch (eventId) { case Misc.EventBoneSpikeGraveyard: if (IsHeroic() || !me.HasAura(Spells.BoneStorm)) DoCast(me, Spells.BoneSpikeGraveyard); _events.ScheduleEvent(Misc.EventBoneSpikeGraveyard, RandomHelper.URand(15000, 20000), Misc.EventGroupSpecial); break; case Misc.EventColdflame: _coldflameLastPos.Relocate(me); _coldflameTarget.Clear(); if (!me.HasAura(Spells.BoneStorm)) DoCastAOE(Spells.ColdflameNormal); else DoCast(me, Spells.ColdflameBoneStorm); _events.ScheduleEvent(Misc.EventColdflame, 5000, Misc.EventGroupSpecial); break; case Misc.EventWarnBoneStorm: _boneSlice = false; Talk(Texts.SayBoneStorm); me.FinishSpell(CurrentSpellTypes.Melee, false); DoCast(me, Spells.BoneStorm); _events.DelayEvents(3000, Misc.EventGroupSpecial); _events.ScheduleEvent(Misc.EventBoneStormBegin, 3050); _events.ScheduleEvent(Misc.EventWarnBoneStorm, RandomHelper.URand(90000, 95000)); break; case Misc.EventBoneStormBegin: Aura pStorm = me.GetAura(Spells.BoneStorm); if (pStorm != null) pStorm.SetDuration((int)_boneStormDuration); me.SetSpeedRate(UnitMoveType.Run, _baseSpeed * 3.0f); Talk(Texts.SayBoneStorm); _events.ScheduleEvent(Misc.EventBoneStormEnd, _boneStormDuration + 1); goto case Misc.EventBoneStormMove; // no break here case Misc.EventBoneStormMove: { _events.ScheduleEvent(Misc.EventBoneStormMove, _boneStormDuration / 3); Unit unit = SelectTarget(SelectAggroTarget.Random, 0, new NonTankTargetSelector(me)); if (!unit) unit = SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); if (unit) me.GetMotionMaster().MovePoint(Misc.PointTargetBonestormPlayer, unit); break; } case Misc.EventBoneStormEnd: if (me.GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) me.GetMotionMaster().MovementExpired(); me.GetMotionMaster().MoveChase(me.GetVictim()); me.SetSpeedRate(UnitMoveType.Run, _baseSpeed); _events.CancelEvent(Misc.EventBoneStormMove); _events.ScheduleEvent(Misc.EventEnableBoneSlice, 10000); if (!IsHeroic()) _events.RescheduleEvent(Misc.EventBoneSpikeGraveyard, 15000, Misc.EventGroupSpecial); break; case Misc.EventEnableBoneSlice: _boneSlice = true; break; case Misc.EventEnrage: DoCast(me, Texts.SayBerserk, true); Talk(Texts.SayBerserk); break; } }); // We should not melee attack when storming if (me.HasAura(Spells.BoneStorm)) return; // 10 seconds since encounter start Bone Slice replaces melee attacks if (_boneSlice && !me.GetCurrentSpell(CurrentSpellTypes.Melee)) DoCastVictim(Spells.BoneSlice); DoMeleeAttackIfReady(); } public override void MovementInform(MovementGeneratorType type, uint id) { if (type != MovementGeneratorType.Point || id != Misc.PointTargetBonestormPlayer) return; // lock movement me.GetMotionMaster().MoveIdle(); } public Position GetLastColdflamePosition() { return _coldflameLastPos; } public override ObjectGuid GetGUID(int type = 0) { switch (type) { case Misc.DataColdflameGuid: return _coldflameTarget; case Misc.DataSpikeImmune + 0: case Misc.DataSpikeImmune + 1: case Misc.DataSpikeImmune + 2: { int index = type - Misc.DataSpikeImmune; if (index < _boneSpikeImmune.Count) return _boneSpikeImmune[index]; break; } } return ObjectGuid.Empty; } public override void SetGUID(ObjectGuid guid, int type = 0) { switch (type) { case Misc.DataColdflameGuid: _coldflameTarget = guid; break; case Misc.DataSpikeImmune: _boneSpikeImmune.Add(guid); break; } } public override void DoAction(int action) { if (action != Misc.ActionClearSpikeImmunities) return; _boneSpikeImmune.Clear(); } Position _coldflameLastPos = new Position(); List _boneSpikeImmune = new List(); ObjectGuid _coldflameTarget; uint _boneStormDuration; float _baseSpeed; bool _introDone; bool _boneSlice; } public override CreatureAI GetAI(Creature creature) { return InstanceIcecrownCitadel.GetInstanceAI(creature); } } [Script] class npc_coldflame : CreatureScript { public npc_coldflame() : base("npc_coldflame") { } class npc_coldflameAI : ScriptedAI { public npc_coldflameAI(Creature creature) : base(creature) { } public override void IsSummonedBy(Unit owner) { if (!owner.IsTypeId(TypeId.Player)) return; Position pos = new Position(); var marrowgarAI = (boss_lord_marrowgar.boss_lord_marrowgarAI)owner.GetAI(); if (marrowgarAI != null) pos.Relocate(marrowgarAI.GetLastColdflamePosition()); else pos.Relocate(owner); if (owner.HasAura(Spells.BoneStorm)) { float ang = Position.NormalizeOrientation(pos.GetAngle(me)); me.SetOrientation(ang); owner.GetNearPoint2D(out pos.posX, out pos.posY, 5.0f - owner.GetObjectSize(), ang); } else { Player target = Global.ObjAccessor.GetPlayer(owner, owner.GetAI().GetGUID(Misc.DataColdflameGuid)); if (!target) { me.DespawnOrUnsummon(); return; } float ang = Position.NormalizeOrientation(pos.GetAngle(target)); me.SetOrientation(ang); owner.GetNearPoint2D(out pos.posX, out pos.posY, 15.0f - owner.GetObjectSize(), ang); } me.NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), me.GetPositionZ(), me.GetOrientation()); DoCast(Spells.ColdflameSummon); _events.ScheduleEvent(Misc.EventColdflameTrigger, 500); } public override void UpdateAI(uint diff) { _events.Update(diff); if (_events.ExecuteEvent() == Misc.EventColdflameTrigger) { Position newPos = me.GetNearPosition(5.0f, 0.0f); me.NearTeleportTo(newPos.GetPositionX(), newPos.GetPositionY(), me.GetPositionZ(), me.GetOrientation()); DoCast(Spells.ColdflameSummon); _events.ScheduleEvent(Misc.EventColdflameTrigger, 500); } } } public override CreatureAI GetAI(Creature creature) { return InstanceIcecrownCitadel.GetInstanceAI(creature); } } [Script] class npc_bone_spike : CreatureScript { public npc_bone_spike() : base("npc_bone_spike") { } class npc_bone_spikeAI : ScriptedAI { public npc_bone_spikeAI(Creature creature) : base(creature) { _hasTrappedUnit = false; Contract.Assert(creature.GetVehicleKit()); SetCombatMovement(false); } public override void JustDied(Unit killer) { TempSummon summ = me.ToTempSummon(); if (summ) { Unit trapped = summ.GetSummoner(); if (trapped) trapped.RemoveAurasDueToSpell(Spells.Impaled); } me.DespawnOrUnsummon(); } public override void KilledUnit(Unit victim) { me.DespawnOrUnsummon(); victim.RemoveAurasDueToSpell(Spells.Impaled); } public override void IsSummonedBy(Unit summoner) { DoCast(summoner, Spells.Impaled); summoner.CastSpell(me, Spells.RideVehicle, true); _events.ScheduleEvent(Misc.EventFailBoned, 8000); _hasTrappedUnit = true; } public override void PassengerBoarded(Unit passenger, sbyte seat, bool apply) { if (!apply) return; // @HACK - Change passenger offset to the one taken directly from sniffs // Remove this when proper calculations are implemented. // This fixes healing spiked people MoveSplineInit init = new MoveSplineInit(passenger); init.DisableTransportPathTransformations(); init.MoveTo(-0.02206125f, -0.02132235f, 5.514783f, false); init.Launch(); } public override void UpdateAI(uint diff) { if (!_hasTrappedUnit) return; _events.Update(diff); if (_events.ExecuteEvent() == Misc.EventFailBoned) { InstanceScript instance = me.GetInstanceScript(); if (instance != null) instance.SetData(DataTypes.BonedAchievement, 0); } } bool _hasTrappedUnit; } public override CreatureAI GetAI(Creature creature) { return InstanceIcecrownCitadel.GetInstanceAI(creature); } } [Script] class spell_marrowgar_coldflame : SpellScriptLoader { public spell_marrowgar_coldflame() : base("spell_marrowgar_coldflame") { } class spell_marrowgar_coldflame_SpellScript : SpellScript { void SelectTarget(List targets) { targets.Clear(); // select any unit but not the tank (by owners threatlist) Unit target = GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 1, -GetCaster().GetObjectSize(), true, -(int)Spells.Impaled); if (!target) target = GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 0, 0.0f, true); // or the tank if its solo if (!target) return; GetCaster().GetAI().SetGUID(target.GetGUID(), Misc.DataColdflameGuid); targets.Add(target); } void HandleScriptEffect(uint effIndex) { PreventHitDefaultEffect(effIndex); GetCaster().CastSpell(GetHitUnit(), (uint)GetEffectValue(), true); } public override void Register() { OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(SelectTarget, 0, Targets.UnitDestAreaEnemy)); OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); } } public override SpellScript GetSpellScript() { return new spell_marrowgar_coldflame_SpellScript(); } } [Script] class spell_marrowgar_coldflame_bonestorm : SpellScriptLoader { public spell_marrowgar_coldflame_bonestorm() : base("spell_marrowgar_coldflame_bonestorm") { } class spell_marrowgar_coldflame_SpellScript : SpellScript { void HandleScriptEffect(uint effIndex) { PreventHitDefaultEffect(effIndex); for (byte i = 0; i < 4; ++i) GetCaster().CastSpell(GetHitUnit(), (uint)(GetEffectValue() + i), true); } public override void Register() { OnEffectHitTarget.Add(new EffectHandler(HandleScriptEffect, 0, SpellEffectName.ScriptEffect)); } } public override SpellScript GetSpellScript() { return new spell_marrowgar_coldflame_SpellScript(); } } [Script] class spell_marrowgar_coldflame_damage : SpellScriptLoader { public spell_marrowgar_coldflame_damage() : base("spell_marrowgar_coldflame_damage") { } class spell_marrowgar_coldflame_damage_AuraScript : AuraScript { bool CanBeAppliedOn(Unit target) { if (target.HasAura(Spells.Impaled)) return false; if (target.GetExactDist2d(GetOwner()) > GetSpellInfo().GetEffect(target.GetMap().GetDifficultyID(), 0).CalcRadius()) return false; Aura aur = target.GetAura(GetId()); if (aur != null) if (aur.GetOwner() != GetOwner()) return false; return true; } public override void Register() { DoCheckAreaTarget.Add(new CheckAreaTargetHandler(CanBeAppliedOn)); } } public override AuraScript GetAuraScript() { return new spell_marrowgar_coldflame_damage_AuraScript(); } } [Script] class spell_marrowgar_bone_spike_graveyard : SpellScriptLoader { public spell_marrowgar_bone_spike_graveyard() : base("spell_marrowgar_bone_spike_graveyard") { } class spell_marrowgar_bone_spike_graveyard_SpellScript : SpellScript { public override bool Validate(SpellInfo spell) { return ValidateSpellInfo(Misc.BoneSpikeSummonId); } public override bool Load() { return GetCaster().IsTypeId(TypeId.Unit) && GetCaster().IsAIEnabled; } SpellCastResult CheckCast() { return GetCaster().GetAI().SelectTarget(SelectAggroTarget.Random, 0, new BoneSpikeTargetSelector(GetCaster().GetAI())) ? SpellCastResult.SpellCastOk : SpellCastResult.NoValidTargets; } void HandleSpikes(uint effIndex) { PreventHitDefaultEffect(effIndex); Creature marrowgar = GetCaster().ToCreature(); if (marrowgar) { CreatureAI marrowgarAI = marrowgar.GetAI(); byte boneSpikeCount = (byte)(Convert.ToBoolean((int)GetCaster().GetMap().GetSpawnMode() & 1) ? 3 : 1); List targets = marrowgarAI.SelectTargetList(new BoneSpikeTargetSelector(marrowgarAI), boneSpikeCount, SelectAggroTarget.Random); if (targets.Empty()) return; uint i = 0; foreach (var target in targets) { target.CastSpell(target, Misc.BoneSpikeSummonId[i], true); i++; } marrowgarAI.Talk(Texts.SayBonespike); } } public override void Register() { OnCheckCast.Add(new CheckCastHandler(CheckCast)); OnEffectHitTarget.Add(new EffectHandler(HandleSpikes, 1, SpellEffectName.ApplyAura)); } } public override SpellScript GetSpellScript() { return new spell_marrowgar_bone_spike_graveyard_SpellScript(); } } [Script] class spell_marrowgar_bone_storm : SpellScriptLoader { public spell_marrowgar_bone_storm() : base("spell_marrowgar_bone_storm") { } class spell_marrowgar_bone_storm_SpellScript : SpellScript { void RecalculateDamage() { SetHitDamage((int)(GetHitDamage() / Math.Max(Math.Sqrt(GetHitUnit().GetExactDist2d(GetCaster())), 1.0f))); } public override void Register() { OnHit.Add(new HitHandler(RecalculateDamage)); } } public override SpellScript GetSpellScript() { return new spell_marrowgar_bone_storm_SpellScript(); } } [Script] class spell_marrowgar_bone_slice : SpellScriptLoader { public spell_marrowgar_bone_slice() : base("spell_marrowgar_bone_slice") { } class spell_marrowgar_bone_slice_SpellScript : SpellScript { public override bool Load() { _targetCount = 0; return true; } void ClearSpikeImmunities() { GetCaster().GetAI().DoAction(Misc.ActionClearSpikeImmunities); } void CountTargets(List targets) { _targetCount = (uint)Math.Min(targets.Count, GetSpellInfo().MaxAffectedTargets); } void SplitDamage() { // Mark the unit as hit, even if the spell missed or was dodged/parried GetCaster().GetAI().SetGUID(GetHitUnit().GetGUID(), Misc.DataSpikeImmune); if (_targetCount == 0) return; // This spell can miss all targets SetHitDamage((int)(GetHitDamage() / _targetCount)); } public override void Register() { BeforeCast.Add(new CastHandler(ClearSpikeImmunities)); OnObjectAreaTargetSelect.Add(new ObjectAreaTargetSelectHandler(CountTargets, 0, Targets.UnitDestAreaEnemy)); OnHit.Add(new HitHandler(SplitDamage)); } uint _targetCount; } public override SpellScript GetSpellScript() { return new spell_marrowgar_bone_slice_SpellScript(); } } } }