diff --git a/Source/Scripts/Draenor/ZoneDraenorShadowmoonValley.cs b/Source/Scripts/Draenor/ZoneDraenorShadowmoonValley.cs new file mode 100644 index 000000000..48ad1fdc2 --- /dev/null +++ b/Source/Scripts/Draenor/ZoneDraenorShadowmoonValley.cs @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2012-2020 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; +using Game.AI; +using Game.Entities; +using Game.Scripting; + +namespace Scripts.Draenor +{ + // 79243 - Baros Alexston + struct MiscConst + { + // Quest + public const uint QuestEstablishYourGarrison = 34586; + + // Gossip + public const uint GossipOptionEstablishGarrison = 0; + + // Text + public const uint SayStartConstruction = 0; + + // Spells + public const uint SpellQuest34586Killcredit = 161033; + public const uint SpellCreateGarrisonShadowmoonValleyAlliance = 156020; + public const uint SpellDespawnAllSummonsGarrisonIntroOnly = 160938; + + public static Position GarrisonLevelOneCreationPlayerPosition = new Position(1904.58f, 312.906f, 88.9542f, 4.303615f); + } + + [Script] + class npc_baros_alexston : ScriptedAI + { + public npc_baros_alexston(Creature creature) : base(creature) { } + + public override bool OnGossipSelect(Player player, uint menuId, uint gossipListId) + { + if (gossipListId == MiscConst.GossipOptionEstablishGarrison) + { + player.CloseGossipMenu(); + player.CastSpell(player, MiscConst.SpellQuest34586Killcredit, true); + player.CastSpell(player, MiscConst.SpellCreateGarrisonShadowmoonValleyAlliance, true); + player.CastSpell(player, MiscConst.SpellDespawnAllSummonsGarrisonIntroOnly, true); + player.NearTeleportTo(MiscConst.GarrisonLevelOneCreationPlayerPosition); + + PhasingHandler.OnConditionChange(player); + } + + return true; + } + + public override void OnQuestAccept(Player player, Quest quest) + { + if (quest.Id == MiscConst.QuestEstablishYourGarrison) + Talk(MiscConst.SayStartConstruction, player); + } + } + + [Script] // 160938 - Despawn All Summons (Garrison Intro Only) + class spell_despawn_all_summons_garrison_intro_only : SpellScript + { + void HandleScript(uint effIndex) + { + Creature hitCreature = GetHitCreature(); + if (hitCreature) + { + if (hitCreature.GetOwner() == GetCaster()) + hitCreature.DespawnOrUnsummon(); + } + } + + public override void Register() + { + OnEffectHitTarget.Add(new EffectHandler(HandleScript, 0, SpellEffectName.ScriptEffect)); + } + } +} + diff --git a/Source/Scripts/EasternKingdoms/MagistersTerrace/FelbloodKaelthas.cs b/Source/Scripts/EasternKingdoms/MagistersTerrace/FelbloodKaelthas.cs new file mode 100644 index 000000000..8c99ddf75 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/MagistersTerrace/FelbloodKaelthas.cs @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2012-2020 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.Scripting; +using Game.Spells; +using System; + +namespace Scripts.EasternKingdoms.MagistersTerrace.FelbloodKaelthas +{ + struct TextIds + { + // Kael'thas Sunstrider + public const uint SayIntro1 = 0; + public const uint SayIntro2 = 1; + public const uint SayGravityLapse1 = 2; + public const uint SayGravityLapse2 = 3; + public const uint SayPowerFeedback = 4; + public const uint SaySummonPhoenix = 5; + public const uint SayAnnouncePyroblast = 6; + public const uint SayFlameStrike = 7; + public const uint SayDeath = 8; + } + + struct SpellIds + { + // Kael'thas Sunstrider + public const uint Fireball = 44189; + public const uint GravityLapse = 49887; + public const uint HGravityLapse = 44226; + public const uint GravityLapseCenterTeleport = 44218; + public const uint GravityLapseLeftTeleport = 44219; + public const uint GravityLapseFrontLeftTeleport = 44220; + public const uint GravityLapseFrontTeleport = 44221; + public const uint GravityLapseFrontRightTeleport = 44222; + public const uint GravityLapseRightTeleport = 44223; + public const uint GravityLapseInitial = 44224; + public const uint GravityLapseFly = 44227; + public const uint GravityLapseBeamVisualPeriodic = 44251; + public const uint SummonArcaneSphere = 44265; + public const uint FlameStrike = 46162; + public const uint ShockBarrier = 46165; + public const uint PowerFeedback = 44233; + public const uint HPowerFeedback = 47109; + public const uint Pyroblast = 36819; + public const uint Phoenix = 44194; + public const uint EmoteTalkExclamation = 48348; + public const uint EmotePoint = 48349; + public const uint EmoteRoar = 48350; + public const uint ClearFlight = 44232; + public const uint QuiteSuicide = 3617; // Serverside public const uint + + // Flame Strike + public const uint FlameStrikeDummy = 44191; + public const uint FlameStrikeDamage = 44190; + + // Phoenix + public const uint Rebirth = 44196; + public const uint Burn = 44197; + public const uint EmberBlast = 44199; + public const uint SummonPhoenixEgg = 44195; // Serverside public const uint + public const uint FullHeal = 17683; + } + + enum Phase + { + Intro = 0, + One = 1, + Two = 2, + Outro = 3 + } + + struct MiscConst + { + public static uint[] GravityLapseTeleportSpells = + { + SpellIds.GravityLapseLeftTeleport, + SpellIds.GravityLapseFrontLeftTeleport, + SpellIds.GravityLapseFrontTeleport, + SpellIds.GravityLapseFrontRightTeleport, + SpellIds.GravityLapseRightTeleport + }; + } + + [Script] + class boss_felblood_kaelthas : BossAI + { + byte _gravityLapseTargetCount; + bool _firstGravityLapse; + + Phase _phase; + + static uint groupFireBall = 1; + + public boss_felblood_kaelthas(Creature creature) : base(creature, DataTypes.KaelthasSunstrider) + { + Initialize(); + } + + void Initialize() + { + _gravityLapseTargetCount = 0; + _firstGravityLapse = true; + } + + public override void JustEngagedWith(Unit who) + { + base.JustEngagedWith(who); + _phase = Phase.One; + + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), groupFireBall, task => + { + DoCastVictim(SpellIds.Fireball); + task.Repeat(TimeSpan.FromSeconds(2.5)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(44), task => + { + Talk(TextIds.SayFlameStrike); + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 40.0f, true); + if (target) + DoCast(target, SpellIds.FlameStrike); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + Talk(TextIds.SaySummonPhoenix); + DoCastSelf(SpellIds.Phoenix); + task.Repeat(TimeSpan.FromSeconds(45)); + }); + + if (IsHeroic()) + { + _scheduler.Schedule(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(1), task => + { + Talk(TextIds.SayAnnouncePyroblast); + DoCastSelf(SpellIds.ShockBarrier); + task.RescheduleGroup(groupFireBall, TimeSpan.FromSeconds(2.5)); + task.Schedule(TimeSpan.FromSeconds(2), pyroBlastTask => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 40.0f, true); + if (target != null) + DoCast(target, SpellIds.Pyroblast); + }); + task.Repeat(TimeSpan.FromMinutes(1)); + }); + } + } + + public override void Reset() + { + _Reset(); + Initialize(); + _phase = Phase.Intro; + } + + public override void JustDied(Unit killer) + { + // No _JustDied() here because otherwise we would reset the events which will trigger the death sequence twice. + instance.SetBossState(DataTypes.KaelthasSunstrider, EncounterState.Done); + } + + public override void EnterEvadeMode(EvadeReason why) + { + DoCastAOE(SpellIds.ClearFlight, new CastSpellExtraArgs(true)); + _EnterEvadeMode(); + summons.DespawnAll(); + _DespawnAtEvade(); + } + + public override void DamageTaken(Unit attacker, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + // Checking for lethal damage first so we trigger the outro phase without triggering phase two in case of oneshot attacks + if (damage >= me.GetHealth() && _phase != Phase.Outro) + { + me.AttackStop(); + me.SetReactState(ReactStates.Passive); + me.InterruptNonMeleeSpells(true); + me.RemoveAurasDueToSpell(DungeonMode(SpellIds.PowerFeedback, SpellIds.HPowerFeedback)); + summons.DespawnAll(); + DoCastAOE(SpellIds.ClearFlight); + Talk(TextIds.SayDeath); + + _phase = Phase.Outro; + _scheduler.CancelAll(); + + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + DoCastSelf(SpellIds.EmoteTalkExclamation); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(3.8), task => + { + DoCastSelf(SpellIds.EmotePoint); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(7.4), task => + { + DoCastSelf(SpellIds.EmoteRoar); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCastSelf(SpellIds.EmoteRoar); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(11), task => + { + DoCastSelf(SpellIds.QuiteSuicide); + }); + } + + // Phase two checks. Skip phase two if we are in the outro already + if (me.HealthBelowPctDamaged(50, damage) && _phase != Phase.Two && _phase != Phase.Outro) + { + _phase = Phase.Two; + _scheduler.CancelAll(); + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), task => + { + Talk(_firstGravityLapse ? TextIds.SayGravityLapse1 : TextIds.SayGravityLapse2); + _firstGravityLapse = false; + me.SetReactState(ReactStates.Passive); + me.AttackStop(); + me.GetMotionMaster().Clear(); + task.Schedule(TimeSpan.FromSeconds(1), _ => + { + DoCastSelf(SpellIds.GravityLapseCenterTeleport); + task.Schedule(TimeSpan.FromSeconds(1), _ => + { + _gravityLapseTargetCount = 0; + DoCastAOE(SpellIds.GravityLapseInitial); + _scheduler.Schedule(TimeSpan.FromSeconds(4), _ => + { + for (byte i = 0; i < 3; i++) + DoCastSelf(SpellIds.SummonArcaneSphere, new CastSpellExtraArgs(true)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(5), _ => + { + DoCastAOE(SpellIds.GravityLapseBeamVisualPeriodic); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(35), _ => + { + Talk(TextIds.SayPowerFeedback); + DoCastAOE(SpellIds.ClearFlight); + DoCastSelf(DungeonMode(SpellIds.PowerFeedback, SpellIds.HPowerFeedback)); + summons.DespawnEntry(CreatureIds.ArcaneSphere); + task.Repeat(TimeSpan.FromSeconds(11)); + }); + }); + }); + }); + } + + // Kael'thas may only kill himself via Quite Suicide + if (damage >= me.GetHealth() && attacker != me) + damage = (uint)(me.GetHealth() - 1); + } + + public override void SetData(uint type, uint data) + { + if (type == DataTypes.KaelthasIntro) + { + // skip the intro if Kael'thas is engaged already + if (_phase != Phase.Intro) + return; + + me.SetImmuneToPC(true); + _scheduler.Schedule(TimeSpan.FromSeconds(6), task => + { + Talk(TextIds.SayIntro1); + me.SetEmoteState(Emote.StateTalk); + _scheduler.Schedule(TimeSpan.FromSeconds(20.6), _ => + { + Talk(TextIds.SayIntro2); + _scheduler.Schedule(TimeSpan.FromSeconds(15) + TimeSpan.FromMilliseconds(500), _ => + { + me.SetEmoteState(Emote.OneshotNone); + me.SetImmuneToPC(false); + }); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(15.6), _ => me.HandleEmoteCommand(Emote.OneshotLaughNoSheathe)); + }); + } + } + + public override void SpellHitTarget(WorldObject target, SpellInfo spellInfo) + { + Unit unitTarget = target.ToUnit(); + if (!unitTarget) + return; + + switch (spellInfo.Id) + { + case SpellIds.GravityLapseInitial: + { + DoCast(unitTarget, MiscConst.GravityLapseTeleportSpells[_gravityLapseTargetCount], new CastSpellExtraArgs(true)); + target.m_Events.AddEventAtOffset(() => + { + target.CastSpell(target, DungeonMode(SpellIds.GravityLapse, SpellIds.HGravityLapse)); + target.CastSpell(target, SpellIds.GravityLapseFly); + + }, TimeSpan.FromMilliseconds(400)); + _gravityLapseTargetCount++; + break; + } + case SpellIds.ClearFlight: + unitTarget.RemoveAurasDueToSpell(SpellIds.GravityLapseFly); + unitTarget.RemoveAurasDueToSpell(DungeonMode(SpellIds.GravityLapse, SpellIds.HGravityLapse)); + break; + default: + break; + } + } + + public override void JustSummoned(Creature summon) + { + summons.Summon(summon); + + switch (summon.GetEntry()) + { + case CreatureIds.ArcaneSphere: + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 70.0f, true); + if (target) + summon.GetMotionMaster().MoveFollow(target, 0.0f, 0.0f); + break; + case CreatureIds.FlameStrike: + summon.CastSpell(summon, SpellIds.FlameStrikeDummy); + summon.DespawnOrUnsummon(TimeSpan.FromSeconds(15)); + break; + default: + break; + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && _phase != Phase.Intro) + return; + + _scheduler.Update(diff); + } + } + + [Script] + class npc_felblood_kaelthas_phoenix : ScriptedAI + { + InstanceScript _instance; + + bool _isInEgg; + ObjectGuid _eggGUID; + + public npc_felblood_kaelthas_phoenix(Creature creature) : base(creature) + { + _instance = creature.GetInstanceScript(); + Initialize(); + } + + void Initialize() + { + me.SetReactState(ReactStates.Passive); + _isInEgg = false; + } + + public override void IsSummonedBy(WorldObject summoner) + { + DoZoneInCombat(); + DoCastSelf(SpellIds.Burn); + DoCastSelf(SpellIds.Rebirth); + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => me.SetReactState(ReactStates.Aggressive)); + } + + public override void JustEngagedWith(Unit who) { } + + public override void DamageTaken(Unit attacker, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + if (damage >= me.GetHealth()) + { + if (!_isInEgg) + { + me.AttackStop(); + me.SetReactState(ReactStates.Passive); + me.RemoveAllAuras(); + me.SetUnitFlag(UnitFlags.Uninteractible); + DoCastSelf(SpellIds.EmberBlast); + // DoCastSelf(SpellSummonPhoenixEgg); -- We do a manual summon for now. Feel free to move it to spelleffect_dbc + Creature egg = DoSummon(CreatureIds.PhoenixEgg, me.GetPosition(), TimeSpan.FromSeconds(0)); + if (egg) + { + Creature kaelthas = _instance.GetCreature(DataTypes.KaelthasSunstrider); + if (kaelthas) + { + kaelthas.GetAI().JustSummoned(egg); + _eggGUID = egg.GetGUID(); + } + } + + _scheduler.Schedule(TimeSpan.FromSeconds(15), task => + { + Creature egg = ObjectAccessor.GetCreature(me, _eggGUID); + if (egg) + egg.DespawnOrUnsummon(); + + me.RemoveAllAuras(); + task.Schedule(TimeSpan.FromSeconds(2), rebirthTask => + { + DoCastSelf(SpellIds.Rebirth); + rebirthTask.Schedule(TimeSpan.FromSeconds(2), engageTask => + { + _isInEgg = false; + DoCastSelf(SpellIds.FullHeal); + DoCastSelf(SpellIds.Burn); + me.RemoveUnitFlag(UnitFlags.Uninteractible); + engageTask.Schedule(TimeSpan.FromSeconds(2), task => me.SetReactState(ReactStates.Aggressive)); + }); + }); + }); + _isInEgg = true; + } + damage = (uint)(me.GetHealth() - 1); + } + + } + + public override void SummonedCreatureDies(Creature summon, Unit killer) + { + // Egg has been destroyed within 15 seconds so we lose the phoenix. + me.DespawnOrUnsummon(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] // 44191 - Flame Strike + class spell_felblood_kaelthas_flame_strike : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.FlameStrikeDamage); + } + + void AfterRemove(AuraEffect aurEff, AuraEffectHandleModes mode) + { + Unit target = GetTarget(); + if (target) + target.CastSpell(target, SpellIds.FlameStrikeDamage); + } + + public override void Register() + { + AfterEffectRemove.Add(new EffectApplyHandler(AfterRemove, 0, AuraType.Dummy, AuraEffectHandleModes.Real)); + } + } +} + diff --git a/Source/Scripts/EasternKingdoms/MagistersTerrace/InstanceMagistersTerrace.cs b/Source/Scripts/EasternKingdoms/MagistersTerrace/InstanceMagistersTerrace.cs new file mode 100644 index 000000000..0901d3d52 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/MagistersTerrace/InstanceMagistersTerrace.cs @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2012-2020 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.Entities; +using Game.Maps; +using Game.Scripting; +using System; +using System.Collections.Generic; + +namespace Scripts.EasternKingdoms.MagistersTerrace +{ + struct DataTypes + { + // Encounter states + public const uint SelinFireheart = 0; + public const uint Vexallus = 1; + public const uint PriestessDelrissa = 2; + public const uint KaelthasSunstrider = 3; + + // Encounter related + public const uint KaelthasIntro = 4; + public const uint DelrissaDeathCount = 5; + + // Additional data + public const uint Kalecgos = 6; + public const uint EscapeOrb = 7; + } + + struct CreatureIds + { + // Bosses + public const uint KaelthasSunstrider = 24664; + public const uint SelinFireheart = 24723; + public const uint Vexallus = 24744; + public const uint PriestessDelrissa = 24560; + + // Encounter related + // Kael'thas Sunstrider + public const uint ArcaneSphere = 24708; + public const uint FlameStrike = 24666; + public const uint Phoenix = 24674; + public const uint PhoenixEgg = 24675; + + // Selin Fireheart + public const uint FelCrystal = 24722; + + // Event related + public const uint Kalecgos = 24844; + public const uint HumanKalecgos = 24848; + public const uint CoilskarWitch = 24696; + public const uint SunbladeWarlock = 24686; + public const uint SunbladeMageGuard = 24683; + public const uint SisterOfTorment = 24697; + public const uint EthereumSmuggler = 24698; + public const uint SunbladeBloodKnight = 24684; + } + + struct GameObjectIds + { + public const uint AssemblyChamberDoor = 188065; + public const uint SunwellRaidGate2 = 187979; + public const uint SunwellRaidGate4 = 187770; + public const uint SunwellRaidGate5 = 187896; + public const uint AsylumDoor = 188064; + public const uint EscapeOrb = 188173; + } + + struct MiscConst + { + public const uint EventSpawnKalecgos = 16547; + + public const uint SayKalecgosSpawn = 0; + + public const uint PathKalecgosFlight = 248440; + + public static ObjectData[] creatureData = + { + new ObjectData(CreatureIds.SelinFireheart, DataTypes.SelinFireheart), + new ObjectData(CreatureIds.Vexallus, DataTypes.Vexallus), + new ObjectData(CreatureIds.PriestessDelrissa, DataTypes.PriestessDelrissa), + new ObjectData(CreatureIds.KaelthasSunstrider, DataTypes.KaelthasSunstrider), + new ObjectData(CreatureIds.Kalecgos, DataTypes.Kalecgos), + new ObjectData(CreatureIds.HumanKalecgos, DataTypes.Kalecgos), + }; + + public static ObjectData[] gameObjectData = + { + new ObjectData(GameObjectIds.EscapeOrb, DataTypes.EscapeOrb), + }; + + public static DoorData[] doorData = + { + new DoorData(GameObjectIds.SunwellRaidGate2, DataTypes.SelinFireheart, DoorType.Passage), + new DoorData(GameObjectIds.AssemblyChamberDoor, DataTypes.SelinFireheart, DoorType.Room), + new DoorData(GameObjectIds.SunwellRaidGate5, DataTypes.Vexallus, DoorType.Passage), + new DoorData(GameObjectIds.SunwellRaidGate4, DataTypes.PriestessDelrissa, DoorType.Passage), + new DoorData(GameObjectIds.AsylumDoor, DataTypes.KaelthasSunstrider, DoorType.Room), + }; + + public static Position KalecgosSpawnPos = new Position(164.3747f, -397.1197f, 2.151798f, 1.66219f); + public static Position KaelthasTrashGroupDistanceComparisonPos = new Position(150.0f, 141.0f, -14.4f); + } + + [Script] + class instance_magisters_terrace : InstanceMapScript + { + public instance_magisters_terrace() : base(nameof(instance_magisters_terrace), 585) { } + + class instance_magisters_terrace_InstanceMapScript : InstanceScript + { + List _kaelthasPreTrashGUIDs = new(); + byte _delrissaDeathCount; + + public instance_magisters_terrace_InstanceMapScript(InstanceMap map) : base(map) + { + SetHeaders("MT"); + SetBossNumber(4); + LoadObjectData(MiscConst.creatureData, MiscConst.gameObjectData); + LoadDoorData(MiscConst.doorData); + } + + public override uint GetData(uint type) + { + switch (type) + { + case DataTypes.DelrissaDeathCount: + return _delrissaDeathCount; + default: + break; + } + return 0; + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case DataTypes.DelrissaDeathCount: + if (data == (uint)EncounterState.Special) + _delrissaDeathCount++; + else + _delrissaDeathCount = 0; + break; + default: + break; + } + } + + public override void OnCreatureCreate(Creature creature) + { + base.OnCreatureCreate(creature); + + switch (creature.GetEntry()) + { + case CreatureIds.CoilskarWitch: + case CreatureIds.SunbladeWarlock: + case CreatureIds.SunbladeMageGuard: + case CreatureIds.SisterOfTorment: + case CreatureIds.EthereumSmuggler: + case CreatureIds.SunbladeBloodKnight: + if (creature.GetDistance(MiscConst.KaelthasTrashGroupDistanceComparisonPos) < 10.0f) + _kaelthasPreTrashGUIDs.Add(creature.GetGUID()); + break; + default: + break; + } + } + + public override void OnUnitDeath(Unit unit) + { + if (!unit.IsCreature()) + return; + + switch (unit.GetEntry()) + { + case CreatureIds.CoilskarWitch: + case CreatureIds.SunbladeWarlock: + case CreatureIds.SunbladeMageGuard: + case CreatureIds.SisterOfTorment: + case CreatureIds.EthereumSmuggler: + case CreatureIds.SunbladeBloodKnight: + if (_kaelthasPreTrashGUIDs.Contains(unit.GetGUID())) + { + _kaelthasPreTrashGUIDs.Remove(unit.GetGUID()); + if (_kaelthasPreTrashGUIDs.Count == 0) + { + Creature kaelthas = GetCreature(DataTypes.KaelthasSunstrider); + if (kaelthas) + kaelthas.GetAI().SetData(DataTypes.KaelthasIntro, (uint)EncounterState.InProgress); + } + } + break; + default: + break; + } + } + + public override void OnGameObjectCreate(GameObject go) + { + base.OnGameObjectCreate(go); + + switch (go.GetEntry()) + { + case GameObjectIds.EscapeOrb: + if (GetBossState(DataTypes.KaelthasSunstrider) == EncounterState.Done) + go.RemoveFlag(GameObjectFlags.NotSelectable); + break; + default: + break; + } + } + + public override void ProcessEvent(WorldObject obj, uint eventId, WorldObject invoker) + { + if (eventId == MiscConst.EventSpawnKalecgos) + if (!GetCreature(DataTypes.Kalecgos) && _events.Empty()) + _events.ScheduleEvent(MiscConst.EventSpawnKalecgos, TimeSpan.FromMinutes(1)); + } + + public override void Update(uint diff) + { + _events.Update(diff); + + if (_events.ExecuteEvent() == MiscConst.EventSpawnKalecgos) + { + Creature kalecgos = instance.SummonCreature(CreatureIds.Kalecgos, MiscConst.KalecgosSpawnPos); + if (kalecgos) + { + kalecgos.GetMotionMaster().MovePath(MiscConst.PathKalecgosFlight, false); + kalecgos.GetAI().Talk(MiscConst.SayKalecgosSpawn); + } + } + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case DataTypes.PriestessDelrissa: + if (state == EncounterState.InProgress) + _delrissaDeathCount = 0; + break; + case DataTypes.KaelthasSunstrider: + if (state == EncounterState.Done) + { + GameObject orb = GetGameObject(DataTypes.EscapeOrb); + if (orb != null) + orb.RemoveFlag(GameObjectFlags.NotSelectable); + } + break; + default: + break; + } + return true; + } + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_magisters_terrace_InstanceMapScript(map); + } + } +} + diff --git a/Source/Scripts/EasternKingdoms/MagistersTerrace/PriestessDelrissa.cs b/Source/Scripts/EasternKingdoms/MagistersTerrace/PriestessDelrissa.cs new file mode 100644 index 000000000..455432dd1 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/MagistersTerrace/PriestessDelrissa.cs @@ -0,0 +1,1057 @@ +/* + * Copyright (C) 2012-2020 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 Framework.Dynamic; +using Game.AI; +using Game.Entities; +using Game.Maps; +using Game.Scripting; +using Game.Spells; +using System; +using System.Collections.Generic; + +namespace Scripts.EasternKingdoms.MagistersTerrace.PriestessDelrissa +{ + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayDeath = 10; + } + + struct SpellIds + { + public const uint DispelMagic = 27609; + public const uint FlashHeal = 17843; + public const uint SwPainNormal = 14032; + public const uint SwPainHeroic = 15654; + public const uint Shield = 44291; + public const uint RenewNormal = 44174; + public const uint RenewHeroic = 46192; + + // Apoko + public const uint WindfuryTotem = 27621; + public const uint WarStomp = 46026; + public const uint Purge = 27626; + public const uint LesserHealingWave = 44256; + public const uint FrostShock = 21401; + public const uint FireNovaTotem = 44257; + public const uint EarthbindTotem = 15786; + + public const uint HealingPotion = 15503; + + // RogueSpells + public const uint KidneyShot = 27615; + public const uint Gouge = 12540; + public const uint Kick = 27613; + public const uint Vanish = 44290; + public const uint Backstab = 15657; + public const uint Eviscerate = 27611; + + // WarlockSpells + public const uint Immolate = 44267; + public const uint ShadowBolt = 12471; + public const uint SeedOfCorruption = 44141; + public const uint CurseOfAgony = 14875; + public const uint Fear = 38595; + public const uint ImpFireball = 44164; + public const uint SummonImp = 44163; + + // KickDown + public const uint Knockdown = 11428; + public const uint SnapKick = 46182; + + // MageSpells + public const uint Polymorph = 13323; + public const uint IceBlock = 27619; + public const uint Blizzard = 44178; + public const uint IceLance = 46194; + public const uint ConeOfCold = 38384; + public const uint Frostbolt = 15043; + public const uint Blink = 14514; + + // WarriorSpells + public const uint InterceptStun = 27577; + public const uint Disarm = 27581; + public const uint PiercingHowl = 23600; + public const uint FrighteningShout = 19134; + public const uint Hamstring = 27584; + public const uint BattleShout = 27578; + public const uint MortalStrike = 44268; + + // HunterSpells + public const uint AimedShot = 44271; + public const uint Shoot = 15620; + public const uint ConcussiveShot = 27634; + public const uint MultiShot = 31942; + public const uint WingClip = 44286; + public const uint FreezingTrap = 44136; + + // EngineerSpells + public const uint GoblinDragonGun = 44272; + public const uint RocketLaunch = 44137; + public const uint Recombobulate = 44274; + public const uint HighExplosiveSheep = 44276; + public const uint FelIronBomb = 46024; + public const uint SheepExplosion = 44279; + } + + struct CreatureIds + { + public const uint Sliver = 24552; + } + + struct MiscConst + { + public const uint MaxActiveLackey = 4; + + public const float fOrientation = 4.98f; + public const float fZLocation = -19.921f; + + public static float[][] LackeyLocations = + { + new float[] {123.77f, 17.6007f}, + new float[] {131.731f, 15.0827f}, + new float[] {121.563f, 15.6213f}, + new float[] {129.988f, 17.2355f}, + }; + + public static uint[] m_auiAddEntries = + { + 24557, //Kagani Nightstrike + 24558, //Elris Duskhallow + 24554, //Eramas Brightblaze + 24561, //Yazzaj + 24559, //Warlord Salaris + 24555, //Garaxxas + 24553, //Apoko + 24556, //Zelfan + }; + + public static uint[] LackeyDeath = { 1, 2, 3, 4, }; + + public static uint[] PlayerDeath = { 5, 6, 7, 8, 9, }; + } + + [Script] + class boss_priestess_delrissa : BossAI + { + List LackeyEntryList = new(); + public ObjectGuid[] m_auiLackeyGUID = new ObjectGuid[MiscConst.MaxActiveLackey]; + + byte PlayersKilled; + + public boss_priestess_delrissa(Creature creature) : base(creature, DataTypes.PriestessDelrissa) + { + Initialize(); + LackeyEntryList.Clear(); + } + + void Initialize() + { + PlayersKilled = 0; + + _scheduler.Schedule(TimeSpan.FromSeconds(15), task => + { + ulong health = me.GetHealth(); + Unit target = me; + for (byte i = 0; i < m_auiLackeyGUID.Length; ++i) + { + Unit pAdd = Global.ObjAccessor.GetUnit(me, m_auiLackeyGUID[i]); + if (pAdd != null && pAdd.IsAlive() && pAdd.GetHealth() < health) + target = pAdd; + } + + DoCast(target, SpellIds.FlashHeal); + task.Repeat(); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + Unit target = me; + + if (RandomHelper.URand(0, 1) != 0) + { + Unit pAdd = Global.ObjAccessor.GetUnit(me, m_auiLackeyGUID[RandomHelper.Rand32() % m_auiLackeyGUID.Length]); + if (pAdd != null && pAdd.IsAlive()) + target = pAdd; + } + + DoCast(target, SpellIds.RenewNormal); + task.Repeat(TimeSpan.FromSeconds(5)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + Unit target = me; + + if (RandomHelper.URand(0, 1) != 0) + { + Unit pAdd = Global.ObjAccessor.GetUnit(me, m_auiLackeyGUID[RandomHelper.Rand32() % m_auiLackeyGUID.Length]); + if (pAdd != null && pAdd.IsAlive() && !pAdd.HasAura(SpellIds.Shield)) + target = pAdd; + } + + DoCast(target, SpellIds.Shield); + task.Repeat(TimeSpan.FromSeconds(7.5)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target != null) + DoCast(target, SpellIds.SwPainNormal); + + task.Repeat(TimeSpan.FromSeconds(10)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(7.5), task => + { + Unit target = null; + + if (RandomHelper.URand(0, 1) != 0) + target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + else + { + if (RandomHelper.URand(0, 1) != 0) + target = me; + else + { + Unit pAdd = Global.ObjAccessor.GetUnit(me, m_auiLackeyGUID[RandomHelper.Rand32() % m_auiLackeyGUID.Length]); + if (pAdd != null && pAdd.IsAlive()) + target = pAdd; + } + } + + if (target) + DoCast(target, SpellIds.DispelMagic); + + task.Repeat(TimeSpan.FromSeconds(12)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + me.GetHomePosition(out _, out _, out float z, out _); + if (me.GetPositionZ() >= z + 10) + { + EnterEvadeMode(); + return; + } + task.Repeat(); + }); + } + + public override void Reset() + { + Initialize(); + + InitializeLackeys(); + } + + //this mean she at some point evaded + public override void JustReachedHome() + { + instance.SetBossState(DataTypes.PriestessDelrissa, EncounterState.Fail); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayAggro); + + foreach (var lackeyGuid in m_auiLackeyGUID) + { + Unit pAdd = Global.ObjAccessor.GetUnit(me, lackeyGuid); + if (pAdd && !pAdd.IsEngaged()) + AddThreat(who, 0.0f, pAdd); + } + + instance.SetBossState(DataTypes.PriestessDelrissa, EncounterState.InProgress); + } + + void InitializeLackeys() + { + //can be called if Creature are dead, so avoid + if (!me.IsAlive()) + return; + + byte j = 0; + + //it's empty, so first time + if (LackeyEntryList.Empty()) + { + //fill vector array with entries from Creature array + for (byte i = 0; i < LackeyEntryList.Count; ++i) + LackeyEntryList[i] = MiscConst.m_auiAddEntries[i]; + + //remove random entries + LackeyEntryList.RandomResize(MiscConst.MaxActiveLackey); + + //summon all the remaining in vector + foreach (var guid in LackeyEntryList) + { + Creature pAdd = me.SummonCreature(guid, MiscConst.LackeyLocations[j][0], MiscConst.LackeyLocations[j][1], MiscConst.fZLocation, MiscConst.fOrientation, TempSummonType.CorpseDespawn); + if (pAdd != null) + m_auiLackeyGUID[j] = pAdd.GetGUID(); + + ++j; + } + } + else + { + foreach (var guid in LackeyEntryList) + { + Unit pAdd = Global.ObjAccessor.GetUnit(me, m_auiLackeyGUID[j]); + + //object already removed, not exist + if (!pAdd) + { + pAdd = me.SummonCreature(guid, MiscConst.LackeyLocations[j][0], MiscConst.LackeyLocations[j][1], MiscConst.fZLocation, MiscConst.fOrientation, TempSummonType.CorpseDespawn); + if (pAdd != null) + m_auiLackeyGUID[j] = pAdd.GetGUID(); + } + ++j; + } + } + } + + public override void KilledUnit(Unit victim) + { + if (!victim.IsPlayer()) + return; + + Talk(MiscConst.PlayerDeath[PlayersKilled]); + + if (PlayersKilled < 4) + ++PlayersKilled; + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + + if (instance.GetData(DataTypes.DelrissaDeathCount) == MiscConst.MaxActiveLackey) + instance.SetBossState(DataTypes.PriestessDelrissa, EncounterState.Done); + else + me.RemoveDynamicFlag(UnitDynFlags.Lootable); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] //all 8 possible lackey use this common + class boss_priestess_lackey_common : ScriptedAI + { + InstanceScript instance; + bool UsedPotion; + + public ObjectGuid[] m_auiLackeyGUIDs = new ObjectGuid[MiscConst.MaxActiveLackey]; + + public boss_priestess_lackey_common(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + UsedPotion = false; + + // These guys does not follow normal threat system rules + // For later development, some alternative threat system should be made + // We do not know what this system is based upon, but one theory is class (healers=high threat, dps=medium, etc) + // We reset their threat frequently as an alternative until such a system exist + _scheduler.Schedule(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20), task => + { + ResetThreatList(); + task.Repeat(); + }); + } + + public override void Reset() + { + Initialize(); + AcquireGUIDs(); + + // in case she is not alive and Reset was for some reason called, respawn her (most likely party wipe after killing her) + Creature delrissa = instance.GetCreature(DataTypes.PriestessDelrissa); + if (delrissa) + { + if (!delrissa.IsAlive()) + delrissa.Respawn(); + } + } + + public override void JustEngagedWith(Unit who) + { + if (!who) + return; + + foreach (var guid in m_auiLackeyGUIDs) + { + Unit pAdd = Global.ObjAccessor.GetUnit(me, guid); + if (pAdd != null && !pAdd.IsEngaged() && pAdd != me) + AddThreat(who, 0.0f, pAdd); + } + + Creature delrissa = instance.GetCreature(DataTypes.PriestessDelrissa); + if (delrissa) + if (delrissa.IsAlive() && !delrissa.IsEngaged()) + AddThreat(who, 0.0f, delrissa); + } + + public override void JustDied(Unit killer) + { + Creature delrissa = instance.GetCreature(DataTypes.PriestessDelrissa); + uint uiLackeyDeathCount = instance.GetData(DataTypes.DelrissaDeathCount); + + if (!delrissa) + return; + + //should delrissa really yell if dead? + delrissa.GetAI().Talk(MiscConst.LackeyDeath[uiLackeyDeathCount]); + + instance.SetData(DataTypes.DelrissaDeathCount, (uint)EncounterState.Special); + + //increase local var, since we now may have four dead + ++uiLackeyDeathCount; + + if (uiLackeyDeathCount == MiscConst.MaxActiveLackey) + { + //time to make her lootable and complete event if she died before lackeys + if (!delrissa.IsAlive()) + { + delrissa.SetDynamicFlag(UnitDynFlags.Lootable); + + instance.SetBossState(DataTypes.PriestessDelrissa, EncounterState.Done); + } + } + } + + public override void KilledUnit(Unit victim) + { + Creature delrissa = instance.GetCreature(DataTypes.PriestessDelrissa); + if (delrissa) + delrissa.GetAI().KilledUnit(victim); + } + + void AcquireGUIDs() + { + Creature delrissa = instance.GetCreature(DataTypes.PriestessDelrissa); + if (delrissa) + { + for (byte i = 0; i < MiscConst.MaxActiveLackey; ++i) + m_auiLackeyGUIDs[i] = (delrissa.GetAI() as boss_priestess_delrissa).m_auiLackeyGUID[i]; + } + } + + public override void UpdateAI(uint diff) + { + if (!UsedPotion && HealthBelowPct(25)) + { + DoCast(me, SpellIds.HealingPotion); + UsedPotion = true; + } + + _scheduler.Update(diff); + } + } + + [Script] + class boss_kagani_nightstrike : boss_priestess_lackey_common + { + bool InVanish; + + //Rogue + public boss_kagani_nightstrike(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(5.5), task => + { + DoCastVictim(SpellIds.Gouge); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(7), task => + { + DoCastVictim(SpellIds.Kick); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + DoCast(me, SpellIds.Vanish); + + Unit unit = SelectTarget(SelectTargetMethod.Random, 0); + + ResetThreatList(); + + if (unit) + AddThreat(unit, 1000.0f); + + InVanish = true; + task.Repeat(TimeSpan.FromSeconds(30)); + task.Schedule(TimeSpan.FromSeconds(10), waitTask => + { + if (InVanish) + { + DoCastVictim(SpellIds.Backstab, new CastSpellExtraArgs(true)); + DoCastVictim(SpellIds.KidneyShot, new CastSpellExtraArgs(true)); + me.SetVisible(true); // ...? Hacklike + InVanish = false; + } + waitTask.Repeat(); + }); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(6), task => + { + DoCastVictim(SpellIds.Eviscerate); + task.Repeat(TimeSpan.FromSeconds(4)); + }); + + InVanish = false; + } + + public override void Reset() + { + Initialize(); + me.SetVisible(true); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + _scheduler.Update(diff); + + if (!InVanish) + DoMeleeAttackIfReady(); + } + } + + [Script] + class boss_ellris_duskhallow : boss_priestess_lackey_common + { + //Warlock + public boss_ellris_duskhallow(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(6), task => + { + DoCastVictim(SpellIds.Immolate); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(3), task => + { + DoCastVictim(SpellIds.ShadowBolt); + task.Repeat(TimeSpan.FromSeconds(5)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + Unit unit = SelectTarget(SelectTargetMethod.Random, 0); + if (unit) + DoCast(unit, SpellIds.SeedOfCorruption); + + task.Repeat(TimeSpan.FromSeconds(10)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + Unit unit = SelectTarget(SelectTargetMethod.Random, 0); + if (unit) + DoCast(unit, SpellIds.CurseOfAgony); + + task.Repeat(TimeSpan.FromSeconds(13)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + Unit unit = SelectTarget(SelectTargetMethod.Random, 0); + if (unit) + DoCast(unit, SpellIds.Fear); + + task.Repeat(); + }); + } + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void JustEngagedWith(Unit who) + { + DoCast(me, SpellIds.SummonImp); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] + class boss_eramas_brightblaze : boss_priestess_lackey_common + { + //Monk + public boss_eramas_brightblaze(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(6), task => + { + DoCastVictim(SpellIds.Knockdown); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(4.5), task => + { + DoCastVictim(SpellIds.SnapKick); + task.Repeat(); + }); + } + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] + class boss_yazzai : boss_priestess_lackey_common + { + bool HasIceBlocked; + + //Mage + public boss_yazzai(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + HasIceBlocked = false; + + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0); + if (target) + { + DoCast(target, SpellIds.Polymorph); + task.Repeat(TimeSpan.FromSeconds(20)); + } + }); + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + Unit unit = SelectTarget(SelectTargetMethod.Random, 0); + if (unit) + DoCast(unit, SpellIds.Blizzard); + + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + DoCastVictim(SpellIds.IceLance); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCastVictim(SpellIds.ConeOfCold); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(3), task => + { + DoCastVictim(SpellIds.Frostbolt); + task.Repeat(TimeSpan.FromSeconds(8)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + bool InMeleeRange = false; + foreach (var pair in me.GetCombatManager().GetPvECombatRefs()) + { + if (pair.Value.GetOther(me).IsWithinMeleeRange(me)) + { + InMeleeRange = true; + break; + } + } + + //if anybody is in melee range than escape by blink + if (InMeleeRange) + DoCast(me, SpellIds.Blink); + + task.Repeat(); + }); + } + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (HealthBelowPct(35) && !HasIceBlocked) + { + DoCast(me, SpellIds.IceBlock); + HasIceBlocked = true; + } + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] + class boss_warlord_salaris : boss_priestess_lackey_common + { + //Warrior + public boss_warlord_salaris(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromMilliseconds(500), task => + { + bool InMeleeRange = false; + foreach (var pair in me.GetCombatManager().GetPvECombatRefs()) + { + if (pair.Value.GetOther(me).IsWithinMeleeRange(me)) + { + InMeleeRange = true; + break; + } + } + + //if nobody is in melee range than try to use Intercept + if (!InMeleeRange) + { + Unit unit = SelectTarget(SelectTargetMethod.Random, 0); + if (unit) + DoCast(unit, SpellIds.InterceptStun); + } + + task.Repeat(TimeSpan.FromSeconds(10)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(6), task => + { + DoCastVictim(SpellIds.Disarm); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCastVictim(SpellIds.PiercingHowl); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(18), task => + { + DoCastVictim(SpellIds.FrighteningShout); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(4.5), task => + { + DoCastVictim(SpellIds.Hamstring); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + DoCastVictim(SpellIds.MortalStrike); + task.Repeat(TimeSpan.FromSeconds(4.5)); + }); + } + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void JustEngagedWith(Unit who) + { + DoCast(me, SpellIds.BattleShout); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] + class boss_garaxxas : boss_priestess_lackey_common + { + TaskScheduler _meleeScheduler = new(); + + ObjectGuid m_uiPetGUID; + + //Hunter + public boss_garaxxas(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(6), task => + { + DoCastVictim(SpellIds.AimedShot); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(2.5), task => + { + DoCastVictim(SpellIds.Shoot); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + DoCastVictim(SpellIds.ConcussiveShot); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCastVictim(SpellIds.MultiShot); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(4), task => + { + DoCastVictim(SpellIds.WingClip); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(15), task => + { + //attempt find go summoned from spell (cast by me) + GameObject go = me.GetGameObject(SpellIds.FreezingTrap); + + //if we have a go, we need to wait (only one trap at a time) + if (go) + task.Repeat(TimeSpan.FromSeconds(2.5)); + else + { + //if go does not exist, then we can cast + DoCastVictim(SpellIds.FreezingTrap); + task.Repeat(); + } + }); + } + + public override void Reset() + { + Initialize(); + + Unit pPet = Global.ObjAccessor.GetUnit(me, m_uiPetGUID); + if (!pPet) + me.SummonCreature(CreatureIds.Sliver, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.CorpseDespawn); + + base.Reset(); + } + + public override void JustSummoned(Creature summoned) + { + m_uiPetGUID = summoned.GetGUID(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (me.IsWithinDistInMap(me.GetVictim(), SharedConst.AttackDistance)) + _meleeScheduler.Update(diff, () => DoMeleeAttackIfReady()); + else + _scheduler.Update(diff); + } + } + + [Script] + class boss_apoko : boss_priestess_lackey_common + { + byte Totem_Amount; + + //Shaman + public boss_apoko(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + Totem_Amount = 1; + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + DoCast(me, RandomHelper.RAND(SpellIds.WindfuryTotem, SpellIds.FireNovaTotem, SpellIds.EarthbindTotem)); + ++Totem_Amount; + task.Repeat(TimeSpan.FromMilliseconds(Totem_Amount * 2000)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCast(me, SpellIds.WarStomp); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + Unit unit = SelectTarget(SelectTargetMethod.Random, 0); + if (unit) + DoCast(unit, SpellIds.Purge); + + task.Repeat(TimeSpan.FromSeconds(15)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + DoCast(me, SpellIds.LesserHealingWave); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(7), task => + { + DoCastVictim(SpellIds.FrostShock); + task.Repeat(); + }); + } + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] + class boss_zelfan : boss_priestess_lackey_common + { + //Engineer + public boss_zelfan(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + _scheduler.Schedule(TimeSpan.FromSeconds(20), task => + { + DoCastVictim(SpellIds.GoblinDragonGun); + task.Repeat(TimeSpan.FromSeconds(10)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(7), task => + { + DoCastVictim(SpellIds.RocketLaunch); + task.Repeat(TimeSpan.FromSeconds(9)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(4), task => + { + foreach (var guid in m_auiLackeyGUIDs) + { + Unit pAdd = Global.ObjAccessor.GetUnit(me, guid); + if (pAdd != null && pAdd.IsPolymorphed()) + { + DoCast(pAdd, SpellIds.Recombobulate); + break; + } + } + task.Repeat(TimeSpan.FromSeconds(2)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + DoCast(me, SpellIds.HighExplosiveSheep); + task.Repeat(TimeSpan.FromSeconds(65)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(15), task => + { + DoCastVictim(SpellIds.FelIronBomb); + task.Repeat(); + }); + } + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/MagistersTerrace/SelinFireheart.cs b/Source/Scripts/EasternKingdoms/MagistersTerrace/SelinFireheart.cs new file mode 100644 index 000000000..d82a3d5f4 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/MagistersTerrace/SelinFireheart.cs @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2012-2020 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.Scripting; +using System; +using System.Collections.Generic; + +namespace Scripts.EasternKingdoms.MagistersTerrace.SelinFireheart +{ + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayEnergy = 1; + public const uint SayEmpowered = 2; + public const uint SayKill = 3; + public const uint SayDeath = 4; + public const uint EmoteCrystal = 5; + } + + struct SpellIds + { + // Crystal effect spells + public const uint FelCrystalDummy = 44329; + public const uint ManaRage = 44320; // This spell triggers 44321, which changes scale and regens mana Requires an entry in spell_script_target + + // Selin's spells + public const uint DrainLife = 44294; + public const uint FelExplosion = 44314; + + public const uint DrainMana = 46153; // Heroic only + } + + struct PhaseIds + { + public const byte Normal = 1; + public const byte Drain = 2; + } + + struct EventIds + { + public const uint FelExplosion = 1; + public const uint DrainCrystal = 2; + public const uint DrainMana = 3; + public const uint DrainLife = 4; + public const uint Empower = 5; + } + + struct MiscConst + { + public const int ActionSwitchPhase = 1; + } + + [Script] // @todo crystals should really be a Db creature summon group, having them in `creature` like this will cause tons of despawn/respawn bugs + class boss_selin_fireheart : BossAI + { + ObjectGuid CrystalGUID; + bool _scheduledEvents; + + public boss_selin_fireheart(Creature creature) : base(creature, DataTypes.SelinFireheart) { } + + public override void Reset() + { + List crystals = me.GetCreatureListWithEntryInGrid(CreatureIds.FelCrystal, 250.0f); + + foreach (Creature creature in crystals) + creature.Respawn(true); + + _Reset(); + CrystalGUID.Clear(); + _scheduledEvents = false; + } + + public override void DoAction(int action) + { + switch (action) + { + case MiscConst.ActionSwitchPhase: + _events.SetPhase(PhaseIds.Normal); + _events.ScheduleEvent(EventIds.FelExplosion, TimeSpan.FromSeconds(2), 0, PhaseIds.Normal); + AttackStart(me.GetVictim()); + me.GetMotionMaster().MoveChase(me.GetVictim()); + break; + default: + break; + } + } + + void SelectNearestCrystal() + { + Creature crystal = me.FindNearestCreature(CreatureIds.FelCrystal, 250.0f); + if (crystal) + { + Talk(TextIds.SayEnergy); + Talk(TextIds.EmoteCrystal); + + DoCast(crystal, SpellIds.FelCrystalDummy); + CrystalGUID = crystal.GetGUID(); + + float x, y, z; + crystal.GetClosePoint(out x, out y, out z, me.GetCombatReach(), SharedConst.ContactDistance); + + _events.SetPhase(PhaseIds.Drain); + me.SetWalk(false); + me.GetMotionMaster().MovePoint(1, x, y, z); + } + } + + void ShatterRemainingCrystals() + { + List crystals = me.GetCreatureListWithEntryInGrid(CreatureIds.FelCrystal, 250.0f); + + foreach (Creature crystal in crystals) + crystal.KillSelf(); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayAggro); + base.JustEngagedWith(who); + + _events.SetPhase(PhaseIds.Normal); + _events.ScheduleEvent(EventIds.FelExplosion, TimeSpan.FromMilliseconds(2100), 0, PhaseIds.Normal); + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsPlayer()) + Talk(TextIds.SayKill); + } + + public override void MovementInform(MovementGeneratorType type, uint id) + { + if (type == MovementGeneratorType.Point && id == 1) + { + Unit CrystalChosen = Global.ObjAccessor.GetUnit(me, CrystalGUID); + if (CrystalChosen != null && CrystalChosen.IsAlive()) + { + CrystalChosen.RemoveUnitFlag(UnitFlags.Uninteractible); + CrystalChosen.CastSpell(me, SpellIds.ManaRage, true); + _events.ScheduleEvent(EventIds.Empower, TimeSpan.FromSeconds(10), PhaseIds.Drain); + } + } + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + _JustDied(); + + ShatterRemainingCrystals(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me.HasUnitState(UnitState.Casting)) + return; + + _events.ExecuteEvents(eventId => + { + switch (eventId) + { + case EventIds.FelExplosion: + DoCastAOE(SpellIds.FelExplosion); + _events.ScheduleEvent(EventIds.FelExplosion, TimeSpan.FromSeconds(2), 0, PhaseIds.Normal); + break; + case EventIds.DrainCrystal: + SelectNearestCrystal(); + _scheduledEvents = false; + break; + case EventIds.DrainMana: + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 45.0f, true); + if (target != null) + DoCast(target, SpellIds.DrainMana); + _events.ScheduleEvent(EventIds.DrainMana, TimeSpan.FromSeconds(10), 0, PhaseIds.Normal); + break; + } + case EventIds.DrainLife: + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 20.0f, true); + if (target != null) + DoCast(target, SpellIds.DrainLife); + _events.ScheduleEvent(EventIds.DrainLife, TimeSpan.FromSeconds(10), 0, PhaseIds.Normal); + break; + } + case EventIds.Empower: + { + Talk(TextIds.SayEmpowered); + + Creature CrystalChosen = ObjectAccessor.GetCreature(me, CrystalGUID); + if (CrystalChosen && CrystalChosen.IsAlive()) + CrystalChosen.KillSelf(); + + CrystalGUID.Clear(); + + me.GetMotionMaster().Clear(); + me.GetMotionMaster().MoveChase(me.GetVictim()); + break; + } + default: + break; + } + + if (me.HasUnitState(UnitState.Casting)) + return; + }); + + if (me.GetPowerPct(PowerType.Mana) < 10.0f) + { + if (_events.IsInPhase(PhaseIds.Normal) && !_scheduledEvents) + { + _scheduledEvents = true; + TimeSpan timer = RandomHelper.RandTime(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(7)); + _events.ScheduleEvent(EventIds.DrainLife, timer, 0, PhaseIds.Normal); + + if (IsHeroic()) + { + _events.ScheduleEvent(EventIds.DrainCrystal, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15), 0, PhaseIds.Normal); + _events.ScheduleEvent(EventIds.DrainMana, timer + TimeSpan.FromSeconds(5), 0, PhaseIds.Normal); + } + else + _events.ScheduleEvent(EventIds.DrainCrystal, TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(25), 0, PhaseIds.Normal); + } + } + + DoMeleeAttackIfReady(); + } + } + + [Script] + class npc_fel_crystal : ScriptedAI + { + public npc_fel_crystal(Creature creature) : base(creature) { } + + public override void JustDied(Unit killer) + { + InstanceScript instance = me.GetInstanceScript(); + if (instance != null) + { + Creature selin = instance.GetCreature(DataTypes.SelinFireheart); + if (selin && selin.IsAlive()) + selin.GetAI().DoAction(MiscConst.ActionSwitchPhase); + } + } + } +} + diff --git a/Source/Scripts/EasternKingdoms/MagistersTerrace/Vexallus.cs b/Source/Scripts/EasternKingdoms/MagistersTerrace/Vexallus.cs new file mode 100644 index 000000000..3b2d6a709 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/MagistersTerrace/Vexallus.cs @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012-2020 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.Scripting; +using Game.Spells; +using System; + +namespace Scripts.EasternKingdoms.MagistersTerrace.Vexallus +{ + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayEnergy = 1; + public const uint SayOverload = 2; + public const uint SayKill = 3; + public const uint EmoteDischargeEnergy = 4; + } + + struct SpellIds + { + public const uint ChainLightning = 44318; + public const uint Overload = 44353; + public const uint ArcaneShock = 44319; + + public const uint SummonPureEnergy = 44322; // mod scale -10 + public const uint HSummonPureEnergy1 = 46154; // mod scale -5 + public const uint HSummonPureEnergy2 = 46159; // mod scale -5 + + // NpcPureEnergy + public const uint EnergyBolt = 46156; + public const uint EnergyFeedback = 44335; + public const uint PureEnergyPassive = 44326; + } + + struct MiscConst + { + public const uint IntervalModifier = 15; + public const uint IntervalSwitch = 6; + } + + [Script] + class boss_vexallus : BossAI + { + uint _intervalHealthAmount; + bool _enraged; + + public boss_vexallus(Creature creature) : base(creature, DataTypes.Vexallus) + { + _intervalHealthAmount = 1; + _enraged = false; + } + + public override void Reset() + { + _Reset(); + _intervalHealthAmount = 1; + _enraged = false; + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayKill); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayAggro); + base.JustEngagedWith(who); + + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 0.0f, true); + if (target) + DoCast(target, SpellIds.ChainLightning); + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(5), task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 20.0f, true); + if (target) + DoCast(target, SpellIds.ArcaneShock); + task.Repeat(TimeSpan.FromSeconds(8)); + }); + } + + public override void JustSummoned(Creature summoned) + { + Unit temp = SelectTarget(SelectTargetMethod.Random, 0); + if (temp) + summoned.GetMotionMaster().MoveFollow(temp, 0, 0); + + summons.Summon(summoned); + } + + public override void DamageTaken(Unit who, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + if (_enraged) + return; + + // 85%, 70%, 55%, 40%, 25% + if (!HealthAbovePct((int)(100 - MiscConst.IntervalModifier * _intervalHealthAmount))) + { + // increase amount, unless we're at 10%, then we switch and return + if (_intervalHealthAmount == MiscConst.IntervalSwitch) + { + _enraged = true; + _scheduler.CancelAll(); + _scheduler.Schedule(TimeSpan.FromSeconds(1.2), task => + { + DoCastVictim(SpellIds.Overload); + task.Repeat(TimeSpan.FromSeconds(2)); + }); + return; + } + else + ++_intervalHealthAmount; + + Talk(TextIds.SayEnergy); + Talk(TextIds.EmoteDischargeEnergy); + + if (IsHeroic()) + { + DoCast(me, SpellIds.HSummonPureEnergy1); + DoCast(me, SpellIds.HSummonPureEnergy2); + } + else + DoCast(me, SpellIds.SummonPureEnergy); + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] + class npc_pure_energy : ScriptedAI + { + public npc_pure_energy(Creature creature) : base(creature) + { + me.SetDisplayFromModel(1); + } + + public override void JustDied(Unit killer) + { + if (killer) + killer.CastSpell(killer, SpellIds.EnergyFeedback, true); + me.RemoveAurasDueToSpell(SpellIds.PureEnergyPassive); + } + } +} \ No newline at end of file