diff --git a/Source/Scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/CorenDirebrew.cs b/Source/Scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/CorenDirebrew.cs index 3e93997a7..5d7e35815 100644 --- a/Source/Scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/CorenDirebrew.cs +++ b/Source/Scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/CorenDirebrew.cs @@ -355,9 +355,9 @@ namespace Scripts.EasternKingdoms.BlackrockMountain.BlackrockDepths.CorenDirebre } } - class go_direbrew_mole_machineAI : GameObjectAI + class go_direbrew_mole_machine : GameObjectAI { - public go_direbrew_mole_machineAI(GameObject go) : base(go) { } + public go_direbrew_mole_machine(GameObject go) : base(go) { } public override void Reset() { diff --git a/Source/Scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/Magmus.cs b/Source/Scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/Magmus.cs index 5b53ef0b3..02fe0026f 100644 --- a/Source/Scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/Magmus.cs +++ b/Source/Scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/Magmus.cs @@ -100,12 +100,12 @@ namespace Scripts.EasternKingdoms.BlackrockMountain.BlackrockDepths.Magmus } [Script] - class npc_ironhand_guardianAI : ScriptedAI + class npc_ironhand_guardian : ScriptedAI { InstanceScript _instance; bool _active; - public npc_ironhand_guardianAI(Creature creature) : base(creature) + public npc_ironhand_guardian(Creature creature) : base(creature) { _instance = me.GetInstanceScript(); _active = false; diff --git a/Source/Scripts/EasternKingdoms/Karazhan/Curator.cs b/Source/Scripts/EasternKingdoms/Karazhan/Curator.cs new file mode 100644 index 000000000..45b62258f --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/Curator.cs @@ -0,0 +1,158 @@ +/* + * 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.Karazhan.Curator +{ + struct SpellIds + { + public const uint HatefulBolt = 30383; + public const uint Evocation = 30254; + public const uint ArcaneInfusion = 30403; + public const uint Berserk = 26662; + public const uint SummonAstralFlareNe = 30236; + public const uint SummonAstralFlareNw = 30239; + public const uint SummonAstralFlareSe = 30240; + public const uint SummonAstralFlareSw = 30241; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySummon = 1; + public const uint SayEvocate = 2; + public const uint SayEnrage = 3; + public const uint SayKill = 4; + public const uint SayDeath = 5; + } + + struct MiscConst + { + public const uint GroupAstralFlare = 1; + } + + [Script] + class boss_curator : BossAI + { + public boss_curator(Creature creature) : base(creature, DataTypes.Curator) { } + + public override void Reset() + { + _Reset(); + _infused = false; + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsPlayer()) + Talk(TextIds.SayKill); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + Talk(TextIds.SayDeath); + } + + public override void JustEngagedWith(Unit who) + { + base.JustEngagedWith(who); + Talk(TextIds.SayAggro); + + _scheduler.Schedule(TimeSpan.FromSeconds(12), task => + { + Unit target = SelectTarget(SelectTargetMethod.MaxThreat, 1); + if (target) + DoCast(target, SpellIds.HatefulBolt); + task.Repeat(TimeSpan.FromSeconds(7), TimeSpan.FromSeconds(15)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), MiscConst.GroupAstralFlare, task => + { + if (RandomHelper.randChance(50)) + Talk(TextIds.SaySummon); + + DoCastSelf(RandomHelper.RAND(SpellIds.SummonAstralFlareNe, SpellIds.SummonAstralFlareNw, SpellIds.SummonAstralFlareSe, SpellIds.SummonAstralFlareSw), new CastSpellExtraArgs(true)); + + int mana = (me.GetMaxPower(PowerType.Mana) / 10); + if (mana != 0) + { + me.ModifyPower(PowerType.Mana, -mana); + + if (me.GetPower(PowerType.Mana) * 100 / me.GetMaxPower(PowerType.Mana) < 10) + { + Talk(TextIds.SayEvocate); + me.InterruptNonMeleeSpells(false); + DoCastSelf(SpellIds.Evocation); + } + } + task.Repeat(TimeSpan.FromSeconds(10)); + }); + _scheduler.Schedule(TimeSpan.FromMinutes(12), ScheduleTasks => + { + Talk(TextIds.SayEnrage); + DoCastSelf(SpellIds.Berserk, new CastSpellExtraArgs(true)); + }); + } + + public override void DamageTaken(Unit attacker, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + if (!HealthAbovePct(15) && !_infused) + { + _infused = true; + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), task => DoCastSelf(SpellIds.ArcaneInfusion, new CastSpellExtraArgs(true))); + _scheduler.CancelGroup(MiscConst.GroupAstralFlare); + } + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + + bool _infused; + } + + [Script] + class npc_curator_astral_flare : ScriptedAI + { + public npc_curator_astral_flare(Creature creature) : base(creature) + { + me.SetReactState(ReactStates.Passive); + } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + me.SetReactState(ReactStates.Aggressive); + me.RemoveUnitFlag(UnitFlags.Uninteractible); + DoZoneInCombat(); + }); + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/InstanceKarazhan.cs b/Source/Scripts/EasternKingdoms/Karazhan/InstanceKarazhan.cs new file mode 100644 index 000000000..a26a2c1d1 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/InstanceKarazhan.cs @@ -0,0 +1,391 @@ +/* + * 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; + +namespace Scripts.EasternKingdoms.Karazhan +{ + struct DataTypes + { + public const uint Attumen = 0; + public const uint Moroes = 1; + public const uint MaidenOfVirtue = 2; + public const uint OptionalBoss = 3; + public const uint OperaPerformance = 4; + public const uint Curator = 5; + public const uint Aran = 6; + public const uint Terestian = 7; + public const uint Netherspite = 8; + public const uint Chess = 9; + public const uint Malchezzar = 10; + public const uint Nightbane = 11; + + public const uint OperaOzDeathcount = 14; + + public const uint Kilrek = 15; + public const uint GoCurtains = 18; + public const uint GoStagedoorleft = 19; + public const uint GoStagedoorright = 20; + public const uint GoLibraryDoor = 21; + public const uint GoMassiveDoor = 22; + public const uint GoNetherDoor = 23; + public const uint GoGameDoor = 24; + public const uint GoGameExitDoor = 25; + + public const uint ImageOfMedivh = 26; + public const uint MastersTerraceDoor1 = 27; + public const uint MastersTerraceDoor2 = 28; + public const uint GoSideEntranceDoor = 29; + public const uint GoBlackenedUrn = 30; + } + + struct CreatureIds + { + public const uint HyakissTheLurker = 16179; + public const uint RokadTheRavager = 16181; + public const uint ShadikithTheGlider = 16180; + public const uint TerestianIllhoof = 15688; + public const uint Moroes = 15687; + public const uint Nightbane = 17225; + public const uint AttumenUnmounted = 15550; + public const uint AttumenMounted = 16152; + public const uint Midnight = 16151; + + // Trash + public const uint ColdmistWidow = 16171; + public const uint ColdmistStalker = 16170; + public const uint Shadowbat = 16173; + public const uint VampiricShadowbat = 16175; + public const uint GreaterShadowbat = 16174; + public const uint PhaseHound = 16178; + public const uint Dreadbeast = 16177; + public const uint Shadowbeast = 16176; + public const uint Kilrek = 17229; + } + + struct GameObjectIds + { + public const uint StageCurtain = 183932; + public const uint StageDoorLeft = 184278; + public const uint StageDoorRight = 184279; + public const uint PrivateLibraryDoor = 184517; + public const uint MassiveDoor = 185521; + public const uint GamesmanHallDoor = 184276; + public const uint GamesmanHallExitDoor = 184277; + public const uint NetherspaceDoor = 185134; + public const uint MastersTerraceDoor = 184274; + public const uint MastersTerraceDoor2 = 184280; + public const uint SideEntranceDoor = 184275; + public const uint DustCoveredChest = 185119; + public const uint BlackenedUrn = 194092; + } + + enum KZMisc + { + OptionalBossRequiredDeathCount = 50 + } + + [Script] + class instance_karazhan : InstanceMapScript + { + public static Position[] OptionalSpawn = + { + new Position(-10960.981445f, -1940.138428f, 46.178097f, 4.12f), // Hyakiss the Lurker + new Position(-10945.769531f, -2040.153320f, 49.474438f, 0.077f), // Shadikith the Glider + new Position(-10899.903320f, -2085.573730f, 49.474449f, 1.38f) // Rokad the Ravager + }; + + public instance_karazhan() : base(nameof(instance_karazhan), 532) { } + + class instance_karazhan_InstanceMapScript : InstanceScript + { + uint OperaEvent; + uint OzDeathCount; + uint OptionalBossCount; + ObjectGuid CurtainGUID; + ObjectGuid StageDoorLeftGUID; + ObjectGuid StageDoorRightGUID; + ObjectGuid KilrekGUID; + ObjectGuid TerestianGUID; + ObjectGuid MoroesGUID; + ObjectGuid NightbaneGUID; + ObjectGuid LibraryDoor; // Door at Shade of Aran + ObjectGuid MassiveDoor; // Door at Netherspite + ObjectGuid SideEntranceDoor; // Side Entrance + ObjectGuid GamesmansDoor; // Door before Chess + ObjectGuid GamesmansExitDoor; // Door after Chess + ObjectGuid NetherspaceDoor; // Door at Malchezaar + ObjectGuid[] MastersTerraceDoor = new ObjectGuid[2]; + ObjectGuid ImageGUID; + ObjectGuid DustCoveredChest; + ObjectGuid BlackenedUrnGUID; + + public instance_karazhan_InstanceMapScript(InstanceMap map) : base(map) + { + SetHeaders("KZ"); + SetBossNumber(12); + + // 1 - Oz, 2 - Hood, 3 - Raj, this never gets altered. + OperaEvent = RandomHelper.URand(1, 3); + OzDeathCount = 0; + OptionalBossCount = 0; + } + + public override void OnCreatureCreate(Creature creature) + { + switch (creature.GetEntry()) + { + case CreatureIds.Kilrek: + KilrekGUID = creature.GetGUID(); + break; + case CreatureIds.TerestianIllhoof: + TerestianGUID = creature.GetGUID(); + break; + case CreatureIds.Moroes: + MoroesGUID = creature.GetGUID(); + break; + case CreatureIds.Nightbane: + NightbaneGUID = creature.GetGUID(); + break; + default: + break; + } + } + + public override void OnUnitDeath(Unit unit) + { + Creature creature = unit.ToCreature(); + if (!creature) + return; + + switch (creature.GetEntry()) + { + case CreatureIds.ColdmistWidow: + case CreatureIds.ColdmistStalker: + case CreatureIds.Shadowbat: + case CreatureIds.VampiricShadowbat: + case CreatureIds.GreaterShadowbat: + case CreatureIds.PhaseHound: + case CreatureIds.Dreadbeast: + case CreatureIds.Shadowbeast: + if (GetBossState(DataTypes.OptionalBoss) == EncounterState.ToBeDecided) + { + ++OptionalBossCount; + if (OptionalBossCount == (uint)KZMisc.OptionalBossRequiredDeathCount) + { + switch (RandomHelper.URand(CreatureIds.HyakissTheLurker, CreatureIds.RokadTheRavager)) + { + case CreatureIds.HyakissTheLurker: + instance.SummonCreature(CreatureIds.HyakissTheLurker, OptionalSpawn[0]); + break; + case CreatureIds.ShadikithTheGlider: + instance.SummonCreature(CreatureIds.ShadikithTheGlider, OptionalSpawn[1]); + break; + case CreatureIds.RokadTheRavager: + instance.SummonCreature(CreatureIds.RokadTheRavager, OptionalSpawn[2]); + break; + } + } + } + break; + default: + break; + } + } + + public override void SetData(uint type, uint data) + { + switch (type) + { + case DataTypes.OperaOzDeathcount: + if (data == (uint)EncounterState.Special) + ++OzDeathCount; + else if (data == (uint)EncounterState.InProgress) + OzDeathCount = 0; + break; + } + } + + public override bool SetBossState(uint type, EncounterState state) + { + if (!base.SetBossState(type, state)) + return false; + + switch (type) + { + case DataTypes.OperaPerformance: + if (state == EncounterState.Done) + { + HandleGameObject(StageDoorLeftGUID, true); + HandleGameObject(StageDoorRightGUID, true); + GameObject sideEntrance = instance.GetGameObject(SideEntranceDoor); + if (sideEntrance) + sideEntrance.RemoveFlag(GameObjectFlags.Locked); + UpdateEncounterStateForKilledCreature(16812, null); + } + break; + case DataTypes.Chess: + if (state == EncounterState.Done) + DoRespawnGameObject(DustCoveredChest, TimeSpan.FromHours(24)); + break; + default: + break; + } + + return true; + } + + public override void SetGuidData(uint type, ObjectGuid data) + { + if (type == DataTypes.ImageOfMedivh) + ImageGUID = data; + } + + public override void OnGameObjectCreate(GameObject go) + { + switch (go.GetEntry()) + { + case GameObjectIds.StageCurtain: + CurtainGUID = go.GetGUID(); + break; + case GameObjectIds.StageDoorLeft: + StageDoorLeftGUID = go.GetGUID(); + if (GetBossState(DataTypes.OperaPerformance) == EncounterState.Done) + go.SetGoState(GameObjectState.Active); + break; + case GameObjectIds.StageDoorRight: + StageDoorRightGUID = go.GetGUID(); + if (GetBossState(DataTypes.OperaPerformance) == EncounterState.Done) + go.SetGoState(GameObjectState.Active); + break; + case GameObjectIds.PrivateLibraryDoor: + LibraryDoor = go.GetGUID(); + break; + case GameObjectIds.MassiveDoor: + MassiveDoor = go.GetGUID(); + break; + case GameObjectIds.GamesmanHallDoor: + GamesmansDoor = go.GetGUID(); + break; + case GameObjectIds.GamesmanHallExitDoor: + GamesmansExitDoor = go.GetGUID(); + break; + case GameObjectIds.NetherspaceDoor: + NetherspaceDoor = go.GetGUID(); + break; + case GameObjectIds.MastersTerraceDoor: + MastersTerraceDoor[0] = go.GetGUID(); + break; + case GameObjectIds.MastersTerraceDoor2: + MastersTerraceDoor[1] = go.GetGUID(); + break; + case GameObjectIds.SideEntranceDoor: + SideEntranceDoor = go.GetGUID(); + if (GetBossState(DataTypes.OperaPerformance) == EncounterState.Done) + go.SetFlag(GameObjectFlags.Locked); + else + go.RemoveFlag(GameObjectFlags.Locked); + break; + case GameObjectIds.DustCoveredChest: + DustCoveredChest = go.GetGUID(); + break; + case GameObjectIds.BlackenedUrn: + BlackenedUrnGUID = go.GetGUID(); + break; + } + + switch (OperaEvent) + { + /// @todo Set Object visibilities for Opera based on performance + case 1: + break; + + case 2: + break; + + case 3: + break; + } + } + + public override uint GetData(uint type) + { + switch (type) + { + case DataTypes.OperaPerformance: + return OperaEvent; + case DataTypes.OperaOzDeathcount: + return OzDeathCount; + } + + return 0; + } + + public override ObjectGuid GetGuidData(uint type) + { + switch (type) + { + case DataTypes.Kilrek: + return KilrekGUID; + case DataTypes.Terestian: + return TerestianGUID; + case DataTypes.Moroes: + return MoroesGUID; + case DataTypes.Nightbane: + return NightbaneGUID; + case DataTypes.GoStagedoorleft: + return StageDoorLeftGUID; + case DataTypes.GoStagedoorright: + return StageDoorRightGUID; + case DataTypes.GoCurtains: + return CurtainGUID; + case DataTypes.GoLibraryDoor: + return LibraryDoor; + case DataTypes.GoMassiveDoor: + return MassiveDoor; + case DataTypes.GoSideEntranceDoor: + return SideEntranceDoor; + case DataTypes.GoGameDoor: + return GamesmansDoor; + case DataTypes.GoGameExitDoor: + return GamesmansExitDoor; + case DataTypes.GoNetherDoor: + return NetherspaceDoor; + case DataTypes.MastersTerraceDoor1: + return MastersTerraceDoor[0]; + case DataTypes.MastersTerraceDoor2: + return MastersTerraceDoor[1]; + case DataTypes.ImageOfMedivh: + return ImageGUID; + case DataTypes.GoBlackenedUrn: + return BlackenedUrnGUID; + } + + return ObjectGuid.Empty; + } + } + + public override InstanceScript GetInstanceScript(InstanceMap map) + { + return new instance_karazhan_InstanceMapScript(map); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/MaidenOfVirtue.cs b/Source/Scripts/EasternKingdoms/Karazhan/MaidenOfVirtue.cs new file mode 100644 index 000000000..614cf3c94 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/MaidenOfVirtue.cs @@ -0,0 +1,100 @@ +/* + * 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 Game.AI; +using Game.Entities; +using Game.Scripting; +using Game.Spells; +using System; + +namespace Scripts.EasternKingdoms.Karazhan.MaidenOfVirtue +{ + struct SpellIds + { + public const uint Repentance = 29511; + public const uint Holyfire = 29522; + public const uint Holywrath = 32445; + public const uint Holyground = 29523; + public const uint Berserk = 26662; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySlay = 1; + public const uint SayRepentance = 2; + public const uint SayDeath = 3; + } + + [Script] + class boss_maiden_of_virtue : BossAI + { + public boss_maiden_of_virtue(Creature creature) : base(creature, DataTypes.MaidenOfVirtue) { } + + public override void KilledUnit(Unit Victim) + { + if (RandomHelper.randChance(50)) + Talk(TextIds.SaySlay); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + _JustDied(); + } + + public override void JustEngagedWith(Unit who) + { + base.JustEngagedWith(who); + Talk(TextIds.SayAggro); + + DoCastSelf(SpellIds.Holyground, new CastSpellExtraArgs(true)); + _scheduler.Schedule(TimeSpan.FromSeconds(33), TimeSpan.FromSeconds(45), task => + { + DoCastVictim(SpellIds.Repentance); + Talk(TextIds.SayRepentance); + task.Repeat(TimeSpan.FromSeconds(35)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 50, true); + if (target) + DoCast(target, SpellIds.Holyfire); + task.Repeat(TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(19)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(25), task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 80, true); + if (target) + DoCast(target, SpellIds.Holywrath); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(25)); + }); + _scheduler.Schedule(TimeSpan.FromMinutes(10), task => + { + DoCastSelf(SpellIds.Berserk, new CastSpellExtraArgs(true)); + }); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/Midnight.cs b/Source/Scripts/EasternKingdoms/Karazhan/Midnight.cs new file mode 100644 index 000000000..55b9f2453 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/Midnight.cs @@ -0,0 +1,366 @@ +/* + * 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; +using System.Collections.Generic; + +namespace Scripts.EasternKingdoms.Karazhan.Midnight +{ + struct SpellIds + { + // Attumen + public const uint Shadowcleave = 29832; + public const uint IntangiblePresence = 29833; + public const uint SpawnSmoke = 10389; + public const uint Charge = 29847; + + // Midnight + public const uint Knockdown = 29711; + public const uint SummonAttumen = 29714; + public const uint Mount = 29770; + public const uint SummonAttumenMounted = 29799; + } + + struct TextIds + { + public const uint SayKill = 0; + public const uint SayRandom = 1; + public const uint SayDisarmed = 2; + public const uint SayMidnightKill = 3; + public const uint SayAppear = 4; + public const uint SayMount = 5; + + public const uint SayDeath = 3; + + // Midnight + public const uint EmoteCallAttumen = 0; + public const uint EmoteMountUp = 1; + } + + enum Phases + { + None, + AttumenEngages, + Mounted + } + + [Script] + class boss_attumen : BossAI + { + ObjectGuid _midnightGUID; + Phases _phase; + + public boss_attumen(Creature creature) : base(creature, DataTypes.Attumen) + { + Initialize(); + } + + void Initialize() + { + _midnightGUID.Clear(); + _phase = Phases.None; + } + + public override void Reset() + { + Initialize(); + base.Reset(); + } + + public override void EnterEvadeMode(EvadeReason why) + { + Creature midnight = ObjectAccessor.GetCreature(me, _midnightGUID); + if (midnight) + base._DespawnAtEvade(TimeSpan.FromSeconds(10), midnight); + + me.DespawnOrUnsummon(); + } + + public override void ScheduleTasks() + { + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(25), task => + { + DoCastVictim(SpellIds.Shadowcleave); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(25)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(25), TimeSpan.FromSeconds(45), task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0); + if (target) + DoCast(target, SpellIds.IntangiblePresence); + + task.Repeat(TimeSpan.FromSeconds(25), TimeSpan.FromSeconds(45)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(60), task => + { + Talk(TextIds.SayRandom); + task.Repeat(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(60)); + }); + } + + public override void DamageTaken(Unit attacker, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + // Attumen does not die until he mounts Midnight, let health fall to 1 and prevent further damage. + if (damage >= me.GetHealth() && _phase != Phases.Mounted) + damage = (uint)(me.GetHealth() - 1); + + if (_phase == Phases.AttumenEngages && me.HealthBelowPctDamaged(25, damage)) + { + _phase = Phases.None; + + Creature midnight = ObjectAccessor.GetCreature(me, _midnightGUID); + if (midnight) + midnight.GetAI().DoCastAOE(SpellIds.Mount, new CastSpellExtraArgs(true)); + } + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayKill); + } + + public override void JustSummoned(Creature summon) + { + if (summon.GetEntry() == CreatureIds.AttumenMounted) + { + Creature midnight = ObjectAccessor.GetCreature(me, _midnightGUID); + if (midnight) + { + if (midnight.GetHealth() > me.GetHealth()) + summon.SetHealth(midnight.GetHealth()); + else + summon.SetHealth(me.GetHealth()); + + summon.GetAI().DoZoneInCombat(); + summon.GetAI().SetGUID(_midnightGUID, (int)CreatureIds.Midnight); + } + } + + base.JustSummoned(summon); + } + + public override void IsSummonedBy(WorldObject summoner) + { + if (summoner.GetEntry() == CreatureIds.Midnight) + _phase = Phases.AttumenEngages; + + if (summoner.GetEntry() == CreatureIds.AttumenUnmounted) + { + _phase = Phases.Mounted; + DoCastSelf(SpellIds.SpawnSmoke); + + _scheduler.Schedule(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25), task => + { + Unit target = null; + List targetList = new(); + + foreach (var refe in me.GetThreatManager().GetSortedThreatList()) + { + target = refe.GetVictim(); + if (target && !target.IsWithinDist(me, 8.00f, false) && target.IsWithinDist(me, 25.0f, false)) + targetList.Add(target); + + target = null; + } + + if (!targetList.Empty()) + target = targetList.SelectRandom(); + + DoCast(target, SpellIds.Charge); + task.Repeat(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(25)); + }); + + _scheduler.Schedule(TimeSpan.FromSeconds(25), TimeSpan.FromSeconds(35), task => + { + DoCastVictim(SpellIds.Knockdown); + task.Repeat(TimeSpan.FromSeconds(25), TimeSpan.FromSeconds(35)); + }); + } + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + Unit midnight = Global.ObjAccessor.GetUnit(me, _midnightGUID); + if (midnight) + midnight.KillSelf(); + + _JustDied(); + } + + public override void SetGUID(ObjectGuid guid, int id) + { + if (id == CreatureIds.Midnight) + _midnightGUID = guid; + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && _phase != Phases.None) + return; + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + + public override void SpellHit(WorldObject caster, SpellInfo spellInfo) + { + if (spellInfo.Mechanic == Mechanics.Disarm) + Talk(TextIds.SayDisarmed); + + if (spellInfo.Id == SpellIds.Mount) + { + Creature midnight = ObjectAccessor.GetCreature(me, _midnightGUID); + if (midnight) + { + _phase = Phases.None; + _scheduler.CancelAll(); + + midnight.AttackStop(); + midnight.RemoveAllAttackers(); + midnight.SetReactState(ReactStates.Passive); + midnight.GetMotionMaster().MoveFollow(me, 2.0f, 0.0f); + midnight.GetAI().Talk(TextIds.EmoteMountUp); + + me.AttackStop(); + me.RemoveAllAttackers(); + me.SetReactState(ReactStates.Passive); + me.GetMotionMaster().MoveFollow(midnight, 2.0f, 0.0f); + Talk(TextIds.SayMount); + + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + Creature midnight = ObjectAccessor.GetCreature(me, _midnightGUID); + if (midnight) + { + if (me.IsWithinDist2d(midnight, 5.0f)) + { + DoCastAOE(SpellIds.SummonAttumenMounted); + me.SetVisible(false); + me.GetMotionMaster().Clear(); + midnight.SetVisible(false); + } + else + { + midnight.GetMotionMaster().MoveFollow(me, 2.0f, 0.0f); + me.GetMotionMaster().MoveFollow(midnight, 2.0f, 0.0f); + task.Repeat(); + } + } + }); + } + } + } + } + + [Script] + class boss_midnight : BossAI + { + ObjectGuid _attumenGUID; + Phases _phase; + + public boss_midnight(Creature creature) : base(creature, DataTypes.Attumen) + { + Initialize(); + } + + void Initialize() + { + _phase = Phases.None; + } + + public override void Reset() + { + Initialize(); + base.Reset(); + me.SetVisible(true); + me.SetReactState(ReactStates.Defensive); + } + + public override void DamageTaken(Unit attacker, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + // Midnight never dies, let health fall to 1 and prevent further damage. + if (damage >= me.GetHealth()) + damage = (uint)(me.GetHealth() - 1); + + if (_phase == Phases.None && me.HealthBelowPctDamaged(95, damage)) + { + _phase = Phases.AttumenEngages; + Talk(TextIds.EmoteCallAttumen); + DoCastAOE(SpellIds.SummonAttumen); + } + else if (_phase == Phases.AttumenEngages && me.HealthBelowPctDamaged(25, damage)) + { + _phase = Phases.Mounted; + DoCastAOE(SpellIds.Mount, new CastSpellExtraArgs(true)); + } + } + + public override void JustSummoned(Creature summon) + { + if (summon.GetEntry() == CreatureIds.AttumenUnmounted) + { + _attumenGUID = summon.GetGUID(); + summon.GetAI().SetGUID(me.GetGUID(), (int)CreatureIds.Midnight); + summon.GetAI().AttackStart(me.GetVictim()); + summon.GetAI().Talk(TextIds.SayAppear); + } + + base.JustSummoned(summon); + } + + public override void JustEngagedWith(Unit who) + { + base.JustEngagedWith(who); + + _scheduler.Schedule(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(25), task => + { + DoCastVictim(SpellIds.Knockdown); + task.Repeat(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(25)); + }); + } + + public override void EnterEvadeMode(EvadeReason why) + { + base._DespawnAtEvade(TimeSpan.FromSeconds(10)); + } + + public override void KilledUnit(Unit victim) + { + if (_phase == Phases.AttumenEngages) + { + Unit unit = Global.ObjAccessor.GetUnit(me, _attumenGUID); + if (unit) + Talk(TextIds.SayMidnightKill, unit); + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() || _phase == Phases.Mounted) + return; + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/Moroes.cs b/Source/Scripts/EasternKingdoms/Karazhan/Moroes.cs new file mode 100644 index 000000000..ac40cc6b3 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/Moroes.cs @@ -0,0 +1,737 @@ +/* + * 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; +using System.Collections.Generic; +using System.Linq; + +namespace Scripts.EasternKingdoms.Karazhan.Moroes +{ + struct SpellIds + { + public const uint Vanish = 29448; + public const uint Garrote = 37066; + public const uint Blind = 34694; + public const uint Gouge = 29425; + public const uint Frenzy = 37023; + + // Adds + public const uint Manaburn = 29405; + public const uint Mindfly = 29570; + public const uint Swpain = 34441; + public const uint Shadowform = 29406; + + public const uint Hammerofjustice = 13005; + public const uint Judgementofcommand = 29386; + public const uint Sealofcommand = 29385; + + public const uint Dispelmagic = 15090; + public const uint Greaterheal = 29564; + public const uint Holyfire = 29563; + public const uint Pwshield = 29408; + + public const uint Cleanse = 29380; + public const uint Greaterblessofmight = 29381; + public const uint Holylight = 29562; + public const uint Divineshield = 41367; + + public const uint Hamstring = 9080; + public const uint Mortalstrike = 29572; + public const uint Whirlwind = 29573; + + public const uint Disarm = 8379; + public const uint Heroicstrike = 29567; + public const uint Shieldbash = 11972; + public const uint Shieldwall = 29390; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SaySpecial = 1; + public const uint SayKill = 2; + public const uint SayDeath = 3; + } + + struct MiscConst + { + public const uint GroupNonEnrage = 1; + + public static Position[] Locations = + { + new Position(-10991.0f, -1884.33f, 81.73f, 0.614315f), + new Position(-10989.4f, -1885.88f, 81.73f, 0.904913f), + new Position(-10978.1f, -1887.07f, 81.73f, 2.035550f), + new Position(-10975.9f, -1885.81f, 81.73f, 2.253890f) + }; + + public static uint[] Adds = + { + 17007, + 19872, + 19873, + 19874, + 19875, + 19876, + }; + } + + [Script] + class boss_moroes : BossAI + { + public ObjectGuid[] AddGUID = new ObjectGuid[4]; + uint[] AddId = new uint[4]; + + bool InVanish; + bool Enrage; + + public boss_moroes(Creature creature) : base(creature, DataTypes.Moroes) + { + Initialize(); + } + + void Initialize() + { + Enrage = false; + InVanish = false; + } + + public override void Reset() + { + Initialize(); + if (me.IsAlive()) + SpawnAdds(); + + base.Reset(); + } + + public override void JustEngagedWith(Unit who) + { + base.JustEngagedWith(who); + + _scheduler.Schedule(TimeSpan.FromSeconds(5), MiscConst.GroupNonEnrage, task => + { + for (byte i = 0; i < 4; ++i) + { + if (!AddGUID[i].IsEmpty()) + { + Creature temp = ObjectAccessor.GetCreature(me, AddGUID[i]); + if (temp && temp.IsAlive()) + if (!temp.GetVictim()) + temp.GetAI().AttackStart(me.GetVictim()); + } + } + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(23), MiscConst.GroupNonEnrage, task => + { + DoCastVictim(SpellIds.Gouge); + task.Repeat(TimeSpan.FromSeconds(40)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(30), MiscConst.GroupNonEnrage, task => + { + DoCast(me, SpellIds.Vanish); + InVanish = true; + + task.Schedule(TimeSpan.FromSeconds(5), garroteTask => + { + Talk(TextIds.SaySpecial); + + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + target.CastSpell(target, SpellIds.Garrote, true); + + InVanish = false; + }); + + task.Repeat(); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(35), MiscConst.GroupNonEnrage, task => + { + Unit target = SelectTarget(SelectTargetMethod.MinDistance, 0, 0.0f, true, false); + if (target) + DoCast(target, SpellIds.Blind); + task.Repeat(TimeSpan.FromSeconds(40)); + }); + + Talk(TextIds.SayAggro); + AddsAttack(); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayKill); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + + base.JustDied(killer); + + DeSpawnAdds(); + + //remove aura from spell Garrote when Moroes dies + instance.DoRemoveAurasDueToSpellOnPlayers(SpellIds.Garrote); + } + + void SpawnAdds() + { + DeSpawnAdds(); + + if (isAddlistEmpty()) + { + List AddList = MiscConst.Adds.ToList(); + AddList.RandomResize(4); + + byte i = 0; + foreach (var entry in AddList) + { + Creature creature = me.SummonCreature(entry, MiscConst.Locations[i], TempSummonType.CorpseTimedDespawn, TimeSpan.FromSeconds(10)); + if (creature) + { + AddGUID[i] = creature.GetGUID(); + AddId[i] = entry; + } + } + } + else + { + for (byte i = 0; i < 4; ++i) + { + Creature creature = me.SummonCreature(AddId[i], MiscConst.Locations[i], TempSummonType.CorpseTimedDespawn, TimeSpan.FromSeconds(10)); + if (creature) + AddGUID[i] = creature.GetGUID(); + } + } + } + + bool isAddlistEmpty() + { + for (byte i = 0; i < 4; ++i) + if (AddId[i] == 0) + return true; + + return false; + } + + void DeSpawnAdds() + { + for (byte i = 0; i < 4; ++i) + { + if (!AddGUID[i].IsEmpty()) + { + Creature temp = ObjectAccessor.GetCreature(me, AddGUID[i]); + if (temp) + temp.DespawnOrUnsummon(); + } + } + } + + void AddsAttack() + { + for (byte i = 0; i < 4; ++i) + { + if (!AddGUID[i].IsEmpty()) + { + Creature temp = ObjectAccessor.GetCreature((me), AddGUID[i]); + if (temp && temp.IsAlive()) + { + temp.GetAI().AttackStart(me.GetVictim()); + DoZoneInCombat(temp); + } + else + EnterEvadeMode(); + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (!Enrage && HealthBelowPct(30)) + { + DoCast(me, SpellIds.Frenzy); + Enrage = true; + _scheduler.CancelGroup(MiscConst.GroupNonEnrage); + } + + _scheduler.Update(diff, () => + { + if (!InVanish) + DoMeleeAttackIfReady(); + }); + } + } + + [Script] + class boss_moroes_guest : ScriptedAI + { + InstanceScript instance; + + ObjectGuid[] GuestGUID = new ObjectGuid[4]; + + public boss_moroes_guest(Creature creature) : base(creature) + { + instance = creature.GetInstanceScript(); + } + + public override void Reset() + { + instance.SetBossState(DataTypes.Moroes, EncounterState.NotStarted); + } + + public void AcquireGUID() + { + Creature Moroes = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.Moroes)); + if (Moroes) + { + for (byte i = 0; i < 4; ++i) + { + ObjectGuid Guid = Moroes.GetAI().AddGUID[i]; + if (!Guid.IsEmpty()) + GuestGUID[i] = Guid; + } + } + } + + public Unit SelectGuestTarget() + { + ObjectGuid TempGUID = GuestGUID[RandomHelper.Rand32() % 4]; + if (!TempGUID.IsEmpty()) + { + Unit unit = Global.ObjAccessor.GetUnit(me, TempGUID); + if (unit && unit.IsAlive()) + return unit; + } + + return me; + } + + public override void UpdateAI(uint diff) + { + if (instance.GetBossState(DataTypes.Moroes) != EncounterState.InProgress) + EnterEvadeMode(); + + DoMeleeAttackIfReady(); + } + } + + [Script] + class boss_baroness_dorothea_millstipe : boss_moroes_guest + { + //Shadow Priest + public boss_baroness_dorothea_millstipe(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + ManaBurn_Timer = 7000; + MindFlay_Timer = 1000; + ShadowWordPain_Timer = 6000; + } + + uint ManaBurn_Timer; + uint MindFlay_Timer; + uint ShadowWordPain_Timer; + + public override void Reset() + { + Initialize(); + + DoCast(me, SpellIds.Shadowform, new CastSpellExtraArgs(true)); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (MindFlay_Timer <= diff) + { + DoCastVictim(SpellIds.Mindfly); + MindFlay_Timer = 12000; // 3 sec channeled + } + else MindFlay_Timer -= diff; + + if (ManaBurn_Timer <= diff) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + if (target.GetPowerType() == PowerType.Mana) + DoCast(target, SpellIds.Manaburn); + ManaBurn_Timer = 5000; // 3 sec cast + } + else ManaBurn_Timer -= diff; + + if (ShadowWordPain_Timer <= diff) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + { + DoCast(target, SpellIds.Swpain); + ShadowWordPain_Timer = 7000; + } + } + else ShadowWordPain_Timer -= diff; + } + } + + [Script] + class boss_baron_rafe_dreuger : boss_moroes_guest + { + //Retr Pally + public boss_baron_rafe_dreuger(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + HammerOfJustice_Timer = 1000; + SealOfCommand_Timer = 7000; + JudgementOfCommand_Timer = SealOfCommand_Timer + 29000; + } + + uint HammerOfJustice_Timer; + uint SealOfCommand_Timer; + uint JudgementOfCommand_Timer; + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (SealOfCommand_Timer <= diff) + { + DoCast(me, SpellIds.Sealofcommand); + SealOfCommand_Timer = 32000; + JudgementOfCommand_Timer = 29000; + } + else SealOfCommand_Timer -= diff; + + if (JudgementOfCommand_Timer <= diff) + { + DoCastVictim(SpellIds.Judgementofcommand); + JudgementOfCommand_Timer = SealOfCommand_Timer + 29000; + } + else JudgementOfCommand_Timer -= diff; + + if (HammerOfJustice_Timer <= diff) + { + DoCastVictim(SpellIds.Hammerofjustice); + HammerOfJustice_Timer = 12000; + } + else HammerOfJustice_Timer -= diff; + } + } + + [Script] + class boss_lady_catriona_von_indi : boss_moroes_guest + { + //Holy Priest + public boss_lady_catriona_von_indi(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + DispelMagic_Timer = 11000; + GreaterHeal_Timer = 1500; + HolyFire_Timer = 5000; + PowerWordShield_Timer = 1000; + } + + uint DispelMagic_Timer; + uint GreaterHeal_Timer; + uint HolyFire_Timer; + uint PowerWordShield_Timer; + + public override void Reset() + { + Initialize(); + + AcquireGUID(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (PowerWordShield_Timer <= diff) + { + DoCast(me, SpellIds.Pwshield); + PowerWordShield_Timer = 15000; + } + else PowerWordShield_Timer -= diff; + + if (GreaterHeal_Timer <= diff) + { + Unit target = SelectGuestTarget(); + + DoCast(target, SpellIds.Greaterheal); + GreaterHeal_Timer = 17000; + } + else GreaterHeal_Timer -= diff; + + if (HolyFire_Timer <= diff) + { + DoCastVictim(SpellIds.Holyfire); + HolyFire_Timer = 22000; + } + else HolyFire_Timer -= diff; + + if (DispelMagic_Timer <= diff) + { + Unit target = RandomHelper.RAND(SelectGuestTarget(), SelectTarget(SelectTargetMethod.Random, 0, 100, true)); + if (target) + DoCast(target, SpellIds.Dispelmagic); + + DispelMagic_Timer = 25000; + } + else DispelMagic_Timer -= diff; + } + } + + [Script] + class boss_lady_keira_berrybuck : boss_moroes_guest + { + //Holy Pally + public boss_lady_keira_berrybuck(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + Cleanse_Timer = 13000; + GreaterBless_Timer = 1000; + HolyLight_Timer = 7000; + DivineShield_Timer = 31000; + } + + uint Cleanse_Timer; + uint GreaterBless_Timer; + uint HolyLight_Timer; + uint DivineShield_Timer; + + public override void Reset() + { + Initialize(); + + AcquireGUID(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (DivineShield_Timer <= diff) + { + DoCast(me, SpellIds.Divineshield); + DivineShield_Timer = 31000; + } + else DivineShield_Timer -= diff; + + if (HolyLight_Timer <= diff) + { + Unit target = SelectGuestTarget(); + + DoCast(target, SpellIds.Holylight); + HolyLight_Timer = 10000; + } + else HolyLight_Timer -= diff; + + if (GreaterBless_Timer <= diff) + { + Unit target = SelectGuestTarget(); + + DoCast(target, SpellIds.Greaterblessofmight); + + GreaterBless_Timer = 50000; + } + else GreaterBless_Timer -= diff; + + if (Cleanse_Timer <= diff) + { + Unit target = SelectGuestTarget(); + + DoCast(target, SpellIds.Cleanse); + + Cleanse_Timer = 10000; + } + else Cleanse_Timer -= diff; + } + } + + [Script] + class boss_lord_robin_daris : boss_moroes_guest + { + //Arms Warr + public boss_lord_robin_daris(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + Hamstring_Timer = 7000; + MortalStrike_Timer = 10000; + WhirlWind_Timer = 21000; + } + + uint Hamstring_Timer; + uint MortalStrike_Timer; + uint WhirlWind_Timer; + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (Hamstring_Timer <= diff) + { + DoCastVictim(SpellIds.Hamstring); + Hamstring_Timer = 12000; + } + else Hamstring_Timer -= diff; + + if (MortalStrike_Timer <= diff) + { + DoCastVictim(SpellIds.Mortalstrike); + MortalStrike_Timer = 18000; + } + else MortalStrike_Timer -= diff; + + if (WhirlWind_Timer <= diff) + { + DoCast(me, SpellIds.Whirlwind); + WhirlWind_Timer = 21000; + } + else WhirlWind_Timer -= diff; + } + } + + [Script] + class boss_lord_crispin_ference : boss_moroes_guest + { + //Arms Warr + public boss_lord_crispin_ference(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + Disarm_Timer = 6000; + HeroicStrike_Timer = 10000; + ShieldBash_Timer = 8000; + ShieldWall_Timer = 4000; + } + + uint Disarm_Timer; + uint HeroicStrike_Timer; + uint ShieldBash_Timer; + uint ShieldWall_Timer; + + public override void Reset() + { + Initialize(); + + base.Reset(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + base.UpdateAI(diff); + + if (Disarm_Timer <= diff) + { + DoCastVictim(SpellIds.Disarm); + Disarm_Timer = 12000; + } + else Disarm_Timer -= diff; + + if (HeroicStrike_Timer <= diff) + { + DoCastVictim(SpellIds.Heroicstrike); + HeroicStrike_Timer = 10000; + } + else HeroicStrike_Timer -= diff; + + if (ShieldBash_Timer <= diff) + { + DoCastVictim(SpellIds.Shieldbash); + ShieldBash_Timer = 13000; + } + else ShieldBash_Timer -= diff; + + if (ShieldWall_Timer <= diff) + { + DoCast(me, SpellIds.Shieldwall); + ShieldWall_Timer = 21000; + } + else ShieldWall_Timer -= diff; + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/Netherspite.cs b/Source/Scripts/EasternKingdoms/Karazhan/Netherspite.cs new file mode 100644 index 000000000..02484e258 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/Netherspite.cs @@ -0,0 +1,358 @@ +/* + * 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; +using System.Numerics; + +namespace Scripts.EasternKingdoms.Karazhan.Netherspite +{ + struct SpellIds + { + public const uint NetherburnAura = 30522; + public const uint Voidzone = 37063; + public const uint NetherInfusion = 38688; + public const uint Netherbreath = 38523; + public const uint BanishVisual = 39833; + public const uint BanishRoot = 42716; + public const uint Empowerment = 38549; + public const uint NetherspiteRoar = 38684; + } + + struct TextIds + { + public const uint EmotePhasePortal = 0; + public const uint EmotePhaseBanish = 1; + } + + enum Portals + { + Red = 0, // Perseverence + Green = 1, // Serenity + Blue = 2 // Dominance + } + + struct MiscConst + { + public static Vector3[] PortalCoord = + { + new Vector3(-11195.353516f, -1613.237183f, 278.237258f), // Left side + new Vector3(-11137.846680f, -1685.607422f, 278.239258f), // Right side + new Vector3(-11094.493164f, -1591.969238f, 279.949188f) // Back side + }; + + public static uint[] PortalID = { 17369, 17367, 17368 }; + public static uint[] PortalVisual = { 30487, 30490, 30491 }; + public static uint[] PortalBeam = { 30465, 30464, 30463 }; + public static uint[] PlayerBuff = { 30421, 30422, 30423 }; + public static uint[] NetherBuff = { 30466, 30467, 30468 }; + public static uint[] PlayerDebuff = { 38637, 38638, 38639 }; + } + + [Script] + class boss_netherspite : ScriptedAI + { + InstanceScript instance; + + bool PortalPhase; + bool Berserk; + uint PhaseTimer; // timer for phase switching + uint VoidZoneTimer; + uint NetherInfusionTimer; // berserking timer + uint NetherbreathTimer; + uint EmpowermentTimer; + uint PortalTimer; // timer for beam checking + ObjectGuid[] PortalGUID = new ObjectGuid[3]; // guid's of portals + ObjectGuid[] BeamerGUID = new ObjectGuid[3]; // guid's of auxiliary beaming portals + ObjectGuid[] BeamTarget = new ObjectGuid[3]; // guid's of portals' current targets + + public boss_netherspite(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + + PortalPhase = false; + PhaseTimer = 0; + EmpowermentTimer = 0; + PortalTimer = 0; + } + + void Initialize() + { + Berserk = false; + NetherInfusionTimer = 540000; + VoidZoneTimer = 15000; + NetherbreathTimer = 3000; + } + + bool IsBetween(WorldObject u1, WorldObject target, WorldObject u2) // the in-line checker + { + if (!u1 || !u2 || !target) + return false; + + float xn, yn, xp, yp, xh, yh; + xn = u1.GetPositionX(); + yn = u1.GetPositionY(); + xp = u2.GetPositionX(); + yp = u2.GetPositionY(); + xh = target.GetPositionX(); + yh = target.GetPositionY(); + + // check if target is between (not checking distance from the beam yet) + if (dist(xn, yn, xh, yh) >= dist(xn, yn, xp, yp) || dist(xp, yp, xh, yh) >= dist(xn, yn, xp, yp)) + return false; + // check distance from the beam + return (Math.Abs((xn - xp) * yh + (yp - yn) * xh - xn * yp + xp * yn) / dist(xn, yn, xp, yp) < 1.5f); + } + + float dist(float xa, float ya, float xb, float yb) // auxiliary method for distance + { + return MathF.Sqrt((xa - xb) * (xa - xb) + (ya - yb) * (ya - yb)); + } + + public override void Reset() + { + Initialize(); + + HandleDoors(true); + DestroyPortals(); + } + + void SummonPortals() + { + uint r = RandomHelper.Rand32() % 4; + int[] pos = new int[3]; + pos[(int)Portals.Red] = ((r % 2) != 0 ? (r > 1 ? 2 : 1) : 0); + pos[(int)Portals.Green] = ((r % 2) != 0 ? 0 : (r > 1 ? 2 : 1)); + pos[(int)Portals.Blue] = (r > 1 ? 1 : 2); // Blue Portal not on the left side (0) + + for (int i = 0; i < 3; ++i) + { + Creature portal = me.SummonCreature(MiscConst.PortalID[i], MiscConst.PortalCoord[pos[i]].X, MiscConst.PortalCoord[pos[i]].Y, MiscConst.PortalCoord[pos[i]].Z, 0, TempSummonType.TimedDespawn, TimeSpan.FromMinutes(1)); + if (portal) + { + PortalGUID[i] = portal.GetGUID(); + portal.AddAura(MiscConst.PortalVisual[i], portal); + } + } + } + + void DestroyPortals() + { + for (int i = 0; i < 3; ++i) + { + Creature portal = ObjectAccessor.GetCreature(me, PortalGUID[i]); + if (portal) + portal.DisappearAndDie(); + + Creature portal1 = ObjectAccessor.GetCreature(me, BeamerGUID[i]); + if (portal1) + portal1.DisappearAndDie(); + + PortalGUID[i].Clear(); + BeamTarget[i].Clear(); + } + } + + void UpdatePortals() // Here we handle the beams' behavior + { + for (int j = 0; j < 3; ++j) // j = color + { + Creature portal = ObjectAccessor.GetCreature(me, PortalGUID[j]); + if (portal) + { + // the one who's been cast upon before + Unit current = Global.ObjAccessor.GetUnit(portal, BeamTarget[j]); + // temporary store for the best suitable beam reciever + Unit target = me; + + var players = me.GetMap().GetPlayers(); + + // get the best suitable target + foreach (var player in players) + { + if (player && player.IsAlive() // alive + && (!target || target.GetDistance2d(portal) > player.GetDistance2d(portal)) // closer than current best + && !player.HasAura(MiscConst.PlayerDebuff[j]) // not exhausted + && !player.HasAura(MiscConst.PlayerBuff[(j + 1) % 3]) // not on another beam + && !player.HasAura(MiscConst.PlayerBuff[(j + 2) % 3]) + && IsBetween(me, player, portal)) // on the beam + target = player; + } + + // buff the target + if (target.IsPlayer()) + target.AddAura(MiscConst.PlayerBuff[j], target); + else + target.AddAura(MiscConst.NetherBuff[j], target); + // cast visual beam on the chosen target if switched + // simple target switching isn't working . using BeamerGUID to cast (workaround) + if (!current || target != current) + { + BeamTarget[j] = target.GetGUID(); + // remove currently beaming portal + Creature beamer = ObjectAccessor.GetCreature(portal, BeamerGUID[j]); + if (beamer) + { + beamer.CastSpell(target, MiscConst.PortalBeam[j], false); + beamer.DisappearAndDie(); + BeamerGUID[j].Clear(); + } + // create new one and start beaming on the target + Creature beamer1 = portal.SummonCreature(MiscConst.PortalID[j], portal.GetPositionX(), portal.GetPositionY(), portal.GetPositionZ(), portal.GetOrientation(), TempSummonType.TimedDespawn, TimeSpan.FromMinutes(1)); + if (beamer1) + { + beamer1.CastSpell(target, MiscConst.PortalBeam[j], false); + BeamerGUID[j] = beamer1.GetGUID(); + } + } + // aggro target if Red Beam + if (j == (int)Portals.Red && me.GetVictim() != target && target.IsPlayer()) + AddThreat(target, 100000.0f); + } + } + } + + void SwitchToPortalPhase() + { + me.RemoveAurasDueToSpell(SpellIds.BanishRoot); + me.RemoveAurasDueToSpell(SpellIds.BanishVisual); + SummonPortals(); + PhaseTimer = 60000; + PortalPhase = true; + PortalTimer = 10000; + EmpowermentTimer = 10000; + Talk(TextIds.EmotePhasePortal); + } + + void SwitchToBanishPhase() + { + me.RemoveAurasDueToSpell(SpellIds.Empowerment); + me.RemoveAurasDueToSpell(SpellIds.NetherburnAura); + DoCast(me, SpellIds.BanishVisual, new CastSpellExtraArgs(true)); + DoCast(me, SpellIds.BanishRoot, new CastSpellExtraArgs(true)); + DestroyPortals(); + PhaseTimer = 30000; + PortalPhase = false; + Talk(TextIds.EmotePhaseBanish); + + for (byte i = 0; i < 3; ++i) + me.RemoveAurasDueToSpell(MiscConst.NetherBuff[i]); + } + + void HandleDoors(bool open) // Massive Door switcher + { + GameObject Door = ObjectAccessor.GetGameObject(me, instance.GetGuidData(DataTypes.GoMassiveDoor)); + if (Door) + Door.SetGoState(open ? GameObjectState.Active : GameObjectState.Ready); + } + + public override void JustEngagedWith(Unit who) + { + HandleDoors(false); + SwitchToPortalPhase(); + } + + public override void JustDied(Unit killer) + { + HandleDoors(true); + DestroyPortals(); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + // Void Zone + if (VoidZoneTimer <= diff) + { + DoCast(SelectTarget(SelectTargetMethod.Random, 1, 45, true), SpellIds.Voidzone, new CastSpellExtraArgs(true)); + VoidZoneTimer = 15000; + } + else VoidZoneTimer -= diff; + + // NetherInfusion Berserk + if (!Berserk && NetherInfusionTimer <= diff) + { + me.AddAura(SpellIds.NetherInfusion, me); + DoCast(me, SpellIds.NetherspiteRoar); + Berserk = true; + } + else NetherInfusionTimer -= diff; + + if (PortalPhase) // Portal Phase + { + // Distribute beams and buffs + if (PortalTimer <= diff) + { + UpdatePortals(); + PortalTimer = 1000; + } + else PortalTimer -= diff; + + // Empowerment & Nether Burn + if (EmpowermentTimer <= diff) + { + DoCast(me, SpellIds.Empowerment); + me.AddAura(SpellIds.NetherburnAura, me); + EmpowermentTimer = 90000; + } + else EmpowermentTimer -= diff; + + if (PhaseTimer <= diff) + { + if (!me.IsNonMeleeSpellCast(false)) + { + SwitchToBanishPhase(); + return; + } + } + else PhaseTimer -= diff; + } + else // Banish Phase + { + // Netherbreath + if (NetherbreathTimer <= diff) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 40, true); + if (target) + DoCast(target, SpellIds.Netherbreath); + NetherbreathTimer = RandomHelper.URand(5000, 7000); + } + else NetherbreathTimer -= diff; + + if (PhaseTimer <= diff) + { + if (!me.IsNonMeleeSpellCast(false)) + { + SwitchToPortalPhase(); + return; + } + } + else PhaseTimer -= diff; + } + + DoMeleeAttackIfReady(); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/Nightbane.cs b/Source/Scripts/EasternKingdoms/Karazhan/Nightbane.cs new file mode 100644 index 000000000..1cbe3e052 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/Nightbane.cs @@ -0,0 +1,386 @@ +/* + * 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.Karazhan.Nightbane +{ + struct SpellIds + { + public const uint BellowingRoar = 36922; + public const uint CharredEarth = 30129; + public const uint Cleave = 30131; + public const uint DistractingAsh = 30130; + public const uint RainOfBones = 37098; + public const uint SmokingBlast = 30128; + public const uint SmokingBlastT = 37057; + public const uint SmolderingBreath = 30210; + public const uint SummonSkeleton = 30170; + public const uint TailSweep = 25653; + } + + struct TextIds + { + public const uint EmoteSummon = 0; + public const uint YellAggro = 1; + public const uint YellFlyPhase = 2; + public const uint YellLandPhase = 3; + public const uint EmoteBreath = 4; + } + + struct PointIds + { + public const uint IntroStart = 0; + public const uint IntroEnd = 1; + public const uint IntroLanding = 2; + public const uint PhaseTwoFly = 3; + public const uint PhaseTwoPreFly = 4; + public const uint PhaseTwoLanding = 5; + public const uint PhaseTwoEnd = 6; + } + + struct SplineChainIds + { + public const uint IntroStart = 1; + public const uint IntroEnd = 2; + public const uint IntroLanding = 3; + public const uint SecondLanding = 4; + public const uint PhaseTwo = 5; + } + + enum NightbanePhases + { + Intro = 0, + Ground, + Fly + } + + struct MiscConst + { + public const int ActionSummon = 0; + public const uint PathPhaseTwo = 13547500; + + public const uint GroupGround = 1; + public const uint GroupFly = 2; + + public static Position FlyPosition = new Position(-11160.13f, -1870.683f, 97.73876f, 0.0f); + public static Position FlyPositionLeft = new Position(-11094.42f, -1866.992f, 107.8375f, 0.0f); + public static Position FlyPositionRight = new Position(-11193.77f, -1921.983f, 107.9845f, 0.0f); + } + + [Script] + class boss_nightbane : BossAI + { + byte _flyCount; + NightbanePhases phase; + + public boss_nightbane(Creature creature) : base(creature, DataTypes.Nightbane) { } + + public override void Reset() + { + _Reset(); + _flyCount = 0; + me.SetDisableGravity(true); + HandleTerraceDoors(true); + GameObject urn = ObjectAccessor.GetGameObject(me, instance.GetGuidData(DataTypes.GoBlackenedUrn)); + if (urn) + urn.RemoveFlag(GameObjectFlags.InUse); + } + + public override void EnterEvadeMode(EvadeReason why) + { + me.SetDisableGravity(true); + base.EnterEvadeMode(why); + } + + public override void JustReachedHome() + { + _DespawnAtEvade(); + } + + public override void JustDied(Unit killer) + { + _JustDied(); + HandleTerraceDoors(true); + } + + public override void DoAction(int action) + { + if (action == MiscConst.ActionSummon) + { + Talk(TextIds.EmoteSummon); + phase = NightbanePhases.Intro; + me.SetActive(true); + me.SetFarVisible(true); + me.RemoveUnitFlag(UnitFlags.Uninteractible); + me.GetMotionMaster().MoveAlongSplineChain(PointIds.IntroStart, SplineChainIds.IntroStart, false); + HandleTerraceDoors(false); + } + } + + void SetupGroundPhase() + { + phase = NightbanePhases.Ground; + _scheduler.Schedule(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(15), MiscConst.GroupGround, task => + { + DoCastVictim(SpellIds.Cleave); + task.Repeat(TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(15)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(23), MiscConst.GroupGround, task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 0.0f, true); + if (target) + if (!me.HasInArc(MathF.PI, target)) + DoCast(target, SpellIds.TailSweep); + task.Repeat(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(30)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(48), MiscConst.GroupGround, task => + { + DoCastAOE(SpellIds.BellowingRoar); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(18), MiscConst.GroupGround, task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 0.0f, true); + if (target) + DoCast(target, SpellIds.CharredEarth); + task.Repeat(TimeSpan.FromSeconds(18), TimeSpan.FromSeconds(21)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(26), TimeSpan.FromSeconds(30), MiscConst.GroupGround, task => + { + DoCastVictim(SpellIds.SmolderingBreath); + task.Repeat(TimeSpan.FromSeconds(28), TimeSpan.FromSeconds(40)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(82), MiscConst.GroupGround, task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 0.0f, true); + if (target) + DoCast(target, SpellIds.DistractingAsh); + }); + } + + void HandleTerraceDoors(bool open) + { + instance.HandleGameObject(instance.GetGuidData(DataTypes.MastersTerraceDoor1), open); + instance.HandleGameObject(instance.GetGuidData(DataTypes.MastersTerraceDoor2), open); + } + + public override void JustEngagedWith(Unit who) + { + base.JustEngagedWith(who); + Talk(TextIds.YellAggro); + SetupGroundPhase(); + } + + public override void DamageTaken(Unit attacker, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + if (phase == NightbanePhases.Fly) + { + if (damage >= me.GetHealth()) + damage = (uint)(me.GetHealth() - 1); + return; + } + + if ((_flyCount == 0 && HealthBelowPct(75)) || (_flyCount == 1 && HealthBelowPct(50)) || (_flyCount == 2 && HealthBelowPct(25))) + { + phase = NightbanePhases.Fly; + StartPhaseFly(); + } + } + + public override void MovementInform(MovementGeneratorType type, uint pointId) + { + if (type == MovementGeneratorType.SplineChain) + { + switch (pointId) + { + case PointIds.IntroStart: + me.SetStandState(UnitStandStateType.Stand); + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), task => + { + me.GetMotionMaster().MoveAlongSplineChain(PointIds.IntroEnd, SplineChainIds.IntroEnd, false); + }); + break; + case PointIds.IntroEnd: + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + me.GetMotionMaster().MoveAlongSplineChain(PointIds.IntroLanding, SplineChainIds.IntroLanding, false); + }); + break; + case PointIds.IntroLanding: + me.SetDisableGravity(false); + me.HandleEmoteCommand(Emote.OneshotLand); + _scheduler.Schedule(TimeSpan.FromSeconds(3), task => + { + me.SetImmuneToPC(false); + DoZoneInCombat(); + }); + break; + case PointIds.PhaseTwoLanding: + phase = NightbanePhases.Ground; + me.SetDisableGravity(false); + me.HandleEmoteCommand(Emote.OneshotLand); + _scheduler.Schedule(TimeSpan.FromSeconds(3), task => + { + SetupGroundPhase(); + me.SetReactState(ReactStates.Aggressive); + }); + break; + case PointIds.PhaseTwoEnd: + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), task => + { + me.GetMotionMaster().MoveAlongSplineChain(PointIds.PhaseTwoLanding, SplineChainIds.SecondLanding, false); + }); + break; + default: + break; + } + } + else if (type == MovementGeneratorType.Point) + { + if (pointId == PointIds.PhaseTwoFly) + { + _scheduler.Schedule(TimeSpan.FromSeconds(33), MiscConst.GroupFly, task => + { + _scheduler.CancelGroup(MiscConst.GroupFly); + _scheduler.Schedule(TimeSpan.FromSeconds(2), MiscConst.GroupGround, landTask => + { + Talk(TextIds.YellLandPhase); + me.SetDisableGravity(true); + me.GetMotionMaster().MoveAlongSplineChain(PointIds.PhaseTwoEnd, SplineChainIds.PhaseTwo, false); + }); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(2), MiscConst.GroupFly, task => + { + Talk(TextIds.EmoteBreath); + task.Schedule(TimeSpan.FromSeconds(3), MiscConst.GroupFly, somethingTask => + { + ResetThreatList(); + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 0.0f, true); + if (target) + { + me.SetFacingToObject(target); + DoCast(target, SpellIds.RainOfBones); + } + }); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(21), MiscConst.GroupFly, task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 0.0f, true); + if (target) + DoCast(target, SpellIds.SmokingBlastT); + task.Repeat(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(7)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(17), MiscConst.GroupFly, task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 0.0f, true); + if (target) + DoCast(target, SpellIds.SmokingBlast); + task.Repeat(TimeSpan.FromMilliseconds(1400)); + }); + } + else if (pointId == PointIds.PhaseTwoPreFly) + { + _scheduler.Schedule(TimeSpan.FromMilliseconds(1), task => + { + me.GetMotionMaster().MovePoint(PointIds.PhaseTwoFly, MiscConst.FlyPosition, true); + }); + } + } + } + + void StartPhaseFly() + { + ++_flyCount; + Talk(TextIds.YellFlyPhase); + _scheduler.CancelGroup(MiscConst.GroupGround); + me.InterruptNonMeleeSpells(false); + me.HandleEmoteCommand(Emote.OneshotLiftoff); + me.SetDisableGravity(true); + me.SetReactState(ReactStates.Passive); + me.AttackStop(); + + if (me.GetDistance(MiscConst.FlyPositionLeft) < me.GetDistance(MiscConst.FlyPosition)) + me.GetMotionMaster().MovePoint(PointIds.PhaseTwoPreFly, MiscConst.FlyPositionLeft, true); + else if (me.GetDistance(MiscConst.FlyPositionRight) < me.GetDistance(MiscConst.FlyPosition)) + me.GetMotionMaster().MovePoint(PointIds.PhaseTwoPreFly, MiscConst.FlyPositionRight, true); + else + me.GetMotionMaster().MovePoint(PointIds.PhaseTwoFly, MiscConst.FlyPosition, true); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() && phase != NightbanePhases.Intro) + return; + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] // 37098 - Rain of Bones + class spell_rain_of_bones_AuraScript : AuraScript + { + public override bool Validate(SpellInfo spellInfo) + { + return ValidateSpellInfo(SpellIds.SummonSkeleton); + } + + void OnTrigger(AuraEffect aurEff) + { + if (aurEff.GetTickNumber() % 5 == 0) + GetTarget().CastSpell(GetTarget(), SpellIds.SummonSkeleton, true); + } + + public override void Register() + { + OnEffectPeriodic.Add(new EffectPeriodicHandler(OnTrigger, 1, AuraType.PeriodicTriggerSpell)); + } + } + + [Script] + class go_blackened_urn : GameObjectAI + { + InstanceScript instance; + + public go_blackened_urn(GameObject go) : base(go) + { + instance = go.GetInstanceScript(); + } + + public override bool OnGossipHello(Player player) + { + if (me.HasFlag(GameObjectFlags.InUse)) + return false; + + if (instance.GetBossState(DataTypes.Nightbane) == EncounterState.Done || instance.GetBossState(DataTypes.Nightbane) == EncounterState.InProgress) + return false; + + Creature nightbane = ObjectAccessor.GetCreature(me, instance.GetGuidData(DataTypes.Nightbane)); + if (nightbane) + { + me.SetFlag(GameObjectFlags.InUse); + nightbane.GetAI().DoAction(MiscConst.ActionSummon); + } + return false; + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/Opera.cs b/Source/Scripts/EasternKingdoms/Karazhan/Opera.cs new file mode 100644 index 000000000..c819f603b --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/Opera.cs @@ -0,0 +1,1453 @@ +/* + * 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.Karazhan.EsOpera +{ + struct TextIds + { + public const uint SayDorotheeDeath = 0; + public const uint SayDorotheeSummon = 1; + public const uint SayDorotheeTitoDeath = 2; + public const uint SayDorotheeAggro = 3; + + public const uint SayRoarAggro = 0; + public const uint SayRoarDeath = 1; + public const uint SayRoarSlay = 2; + + public const uint SayStrawmanAggro = 0; + public const uint SayStrawmanDeath = 1; + public const uint SayStrawmanSlay = 2; + + public const uint SayTinheadAggro = 0; + public const uint SayTinheadDeath = 1; + public const uint SayTinheadSlay = 2; + public const uint EmoteRust = 3; + + public const uint SayCroneAggro = 0; + public const uint SayCroneDeath = 1; + public const uint SayCroneSlay = 2; + + //RedRidingHood + public const uint SayWolfAggro = 0; + public const uint SayWolfSlay = 1; + public const uint SayWolfHood = 2; + public const uint OptionWhatPhatLewtsYouHave = 7443; + + //Romulo & Julianne + public const uint SayJulianneAggro = 0; + public const uint SayJulianneEnter = 1; + public const uint SayJulianneDeath01 = 2; + public const uint SayJulianneDeath02 = 3; + public const uint SayJulianneResurrect = 4; + public const uint SayJulianneSlay = 5; + + public const uint SayRomuloAggro = 0; + public const uint SayRomuloDeath = 1; + public const uint SayRomuloEnter = 2; + public const uint SayRomuloResurrect = 3; + public const uint SayRomuloSlay = 4; + } + + struct SpellIds + { + // Dorothee + public const uint Waterbolt = 31012; + public const uint Scream = 31013; + public const uint Summontito = 31014; + + // Tito + public const uint Yipping = 31015; + + // Strawman + public const uint BrainBash = 31046; + public const uint BrainWipe = 31069; + public const uint BurningStraw = 31075; + + // Tinhead + public const uint Cleave = 31043; + public const uint Rust = 31086; + + // Roar + public const uint Mangle = 31041; + public const uint Shred = 31042; + public const uint FrightenedScream = 31013; + + // Crone + public const uint ChainLightning = 32337; + + // Cyclone + public const uint Knockback = 32334; + public const uint CycloneVisual = 32332; + + //Red Riding Hood + public const uint LittleRedRidingHood = 30768; + public const uint TerrifyingHowl = 30752; + public const uint WideSwipe = 30761; + + //Romulo & Julianne + public const uint BlindingPassion = 30890; + public const uint Devotion = 30887; + public const uint EternalAffection = 30878; + public const uint PowerfulAttraction = 30889; + public const uint DrinkPoison = 30907; + + public const uint BackwardLunge = 30815; + public const uint Daring = 30841; + public const uint DeadlySwathe = 30817; + public const uint PoisonThrust = 30822; + + public const uint UndyingLove = 30951; + public const uint ResVisual = 24171; + } + + struct CreatureIds + { + public const uint Tito = 17548; + public const uint Cyclone = 18412; + public const uint Crone = 18168; + + //Red Riding Hood + public const uint BigBadWolf = 17521; + + //Romulo & Julianne + public const uint Romulo = 17533; + } + + struct MiscConst + { + //Red Riding Hood + public const uint SoundWolfDeath = 9275; + + //Romulo & Julianne + public const int RomuloX = -10900; + public const int RomuloY = -1758; + + public static void SummonCroneIfReady(InstanceScript instance, Creature creature) + { + instance.SetData(DataTypes.OperaOzDeathcount, (uint)EncounterState.Special); // Increment DeathCount + + if (instance.GetData(DataTypes.OperaOzDeathcount) == 4) + { + Creature pCrone = creature.SummonCreature(CreatureIds.Crone, -10891.96f, -1755.95f, creature.GetPositionZ(), 4.64f, TempSummonType.TimedOrDeadDespawn, TimeSpan.FromHours(2)); + if (pCrone) + { + if (creature.GetVictim()) + pCrone.GetAI().AttackStart(creature.GetVictim()); + } + } + } + + public static void PretendToDie(Creature creature) + { + creature.InterruptNonMeleeSpells(true); + creature.RemoveAllAuras(); + creature.SetHealth(0); + creature.SetUnitFlag(UnitFlags.Uninteractible); + creature.GetMotionMaster().Clear(); + creature.GetMotionMaster().MoveIdle(); + creature.SetStandState(UnitStandStateType.Dead); + } + + public static void Resurrect(Creature target) + { + target.RemoveUnitFlag(UnitFlags.Uninteractible); + target.SetFullHealth(); + target.SetStandState(UnitStandStateType.Stand); + target.CastSpell(target, SpellIds.ResVisual, true); + if (target.GetVictim()) + { + target.GetMotionMaster().MoveChase(target.GetVictim()); + target.GetAI().AttackStart(target.GetVictim()); + } + else + target.GetMotionMaster().Initialize(); + } + } + + enum RAJPhase + { + Julianne = 0, + Romulo = 1, + Both = 2, + } + + [Script] + class boss_dorothee : ScriptedAI + { + InstanceScript instance; + + uint AggroTimer; + + uint WaterBoltTimer; + uint FearTimer; + uint SummonTitoTimer; + + public bool SummonedTito; + public bool TitoDied; + + public boss_dorothee(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + AggroTimer = 500; + + WaterBoltTimer = 5000; + FearTimer = 15000; + SummonTitoTimer = 47500; + + SummonedTito = false; + TitoDied = false; + } + + public override void Reset() + { + Initialize(); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayDorotheeAggro); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDorotheeDeath); + + MiscConst.SummonCroneIfReady(instance, me); + } + + public override void AttackStart(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void UpdateAI(uint diff) + { + if (AggroTimer != 0) + { + if (AggroTimer <= diff) + { + me.RemoveUnitFlag(UnitFlags.NonAttackable); + AggroTimer = 0; + } + else AggroTimer -= diff; + } + + if (!UpdateVictim()) + return; + + if (WaterBoltTimer <= diff) + { + DoCast(SelectTarget(SelectTargetMethod.Random, 0), SpellIds.Waterbolt); + WaterBoltTimer = TitoDied ? 1500 : 5000u; + } + else WaterBoltTimer -= diff; + + if (FearTimer <= diff) + { + DoCastVictim(SpellIds.Scream); + FearTimer = 30000; + } + else FearTimer -= diff; + + if (!SummonedTito) + { + if (SummonTitoTimer <= diff) + SummonTito(); + else SummonTitoTimer -= diff; + } + + DoMeleeAttackIfReady(); + } + + void SummonTito() + { + Creature pTito = me.SummonCreature(CreatureIds.Tito, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOutOfCombat, TimeSpan.FromSeconds(30)); + if (pTito) + { + Talk(TextIds.SayDorotheeSummon); + pTito.GetAI().DorotheeGUID = me.GetGUID(); + pTito.GetAI().AttackStart(me.GetVictim()); + SummonedTito = true; + TitoDied = false; + } + } + } + + [Script] + class npc_tito : ScriptedAI + { + public ObjectGuid DorotheeGUID; + uint YipTimer; + + public npc_tito(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + DorotheeGUID.Clear(); + YipTimer = 10000; + } + + public override void Reset() + { + Initialize(); + } + + public override void JustEngagedWith(Unit who) { } + + public override void JustDied(Unit killer) + { + if (!DorotheeGUID.IsEmpty()) + { + Creature Dorothee = ObjectAccessor.GetCreature(me, DorotheeGUID); + if (Dorothee && Dorothee.IsAlive()) + { + Dorothee.GetAI().TitoDied = true; + Talk(TextIds.SayDorotheeTitoDeath, Dorothee); + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (YipTimer <= diff) + { + DoCastVictim(SpellIds.Yipping); + YipTimer = 10000; + } + else YipTimer -= diff; + + DoMeleeAttackIfReady(); + } + } + + [Script] + class boss_strawman : ScriptedAI + { + InstanceScript instance; + + uint AggroTimer; + uint BrainBashTimer; + uint BrainWipeTimer; + + public boss_strawman(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + AggroTimer = 13000; + BrainBashTimer = 5000; + BrainWipeTimer = 7000; + } + + public override void Reset() + { + Initialize(); + } + + public override void AttackStart(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayStrawmanAggro); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void SpellHit(WorldObject caster, SpellInfo spellInfo) + { + if ((spellInfo.SchoolMask == SpellSchoolMask.Fire) && ((RandomHelper.Rand32() % 10) == 0)) + DoCast(me, SpellIds.BurningStraw, new CastSpellExtraArgs(true)); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayStrawmanDeath); + + MiscConst.SummonCroneIfReady(instance, me); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayStrawmanSlay); + } + + public override void UpdateAI(uint diff) + { + if (AggroTimer != 0) + { + if (AggroTimer <= diff) + { + me.RemoveUnitFlag(UnitFlags.NonAttackable); + AggroTimer = 0; + } + else AggroTimer -= diff; + } + + if (!UpdateVictim()) + return; + + if (BrainBashTimer <= diff) + { + DoCastVictim(SpellIds.BrainBash); + BrainBashTimer = 15000; + } + else BrainBashTimer -= diff; + + if (BrainWipeTimer <= diff) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + DoCast(target, SpellIds.BrainWipe); + BrainWipeTimer = 20000; + } + else BrainWipeTimer -= diff; + + DoMeleeAttackIfReady(); + } + } + + [Script] + class boss_tinhead : ScriptedAI + { + InstanceScript instance; + + uint AggroTimer; + uint CleaveTimer; + uint RustTimer; + + byte RustCount; + + public boss_tinhead(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + AggroTimer = 15000; + CleaveTimer = 5000; + RustTimer = 30000; + + RustCount = 0; + } + + public override void Reset() + { + Initialize(); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayTinheadAggro); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void AttackStart(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayTinheadDeath); + + MiscConst.SummonCroneIfReady(instance, me); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayTinheadSlay); + } + + public override void UpdateAI(uint diff) + { + if (AggroTimer != 0) + { + if (AggroTimer <= diff) + { + me.RemoveUnitFlag(UnitFlags.NonAttackable); + AggroTimer = 0; + } + else AggroTimer -= diff; + } + + if (!UpdateVictim()) + return; + + if (CleaveTimer <= diff) + { + DoCastVictim(SpellIds.Cleave); + CleaveTimer = 5000; + } + else CleaveTimer -= diff; + + if (RustCount < 8) + { + if (RustTimer <= diff) + { + ++RustCount; + Talk(TextIds.EmoteRust); + DoCast(me, SpellIds.Rust); + RustTimer = 6000; + } + else RustTimer -= diff; + } + + DoMeleeAttackIfReady(); + } + } + + [Script] + class boss_roar : ScriptedAI + { + InstanceScript instance; + + uint AggroTimer; + uint MangleTimer; + uint ShredTimer; + uint ScreamTimer; + + public boss_roar(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + AggroTimer = 20000; + MangleTimer = 5000; + ShredTimer = 10000; + ScreamTimer = 15000; + } + + public override void Reset() + { + Initialize(); + } + + public override void MoveInLineOfSight(Unit who) + + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void AttackStart(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayRoarAggro); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayRoarDeath); + + MiscConst.SummonCroneIfReady(instance, me); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayRoarSlay); + } + + public override void UpdateAI(uint diff) + { + if (AggroTimer != 0) + { + if (AggroTimer <= diff) + { + me.RemoveUnitFlag(UnitFlags.NonAttackable); + AggroTimer = 0; + } + else AggroTimer -= diff; + } + + if (!UpdateVictim()) + return; + + if (MangleTimer <= diff) + { + DoCastVictim(SpellIds.Mangle); + MangleTimer = RandomHelper.URand(5000, 8000); + } + else MangleTimer -= diff; + + if (ShredTimer <= diff) + { + DoCastVictim(SpellIds.Shred); + ShredTimer = RandomHelper.URand(10000, 15000); + } + else ShredTimer -= diff; + + if (ScreamTimer <= diff) + { + DoCastVictim(SpellIds.FrightenedScream); + ScreamTimer = RandomHelper.URand(20000, 30000); + } + else ScreamTimer -= diff; + + DoMeleeAttackIfReady(); + } + } + + [Script] + class boss_crone : ScriptedAI + { + InstanceScript instance; + + uint CycloneTimer; + uint ChainLightningTimer; + + public boss_crone(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + // Hello, developer from the future! It's me again! + // This time, you're fixing Karazhan scripts. Awesome. These are a mess of hacks. An amalgamation of hacks, so to speak. Maybe even a Patchwerk thereof. + // Anyway, I digress. + // @todo This line below is obviously a hack. Duh. I'm just coming in here to hackfix the encounter to actually be completable. + // It needs a rewrite. Badly. Please, take good care of it. + me.RemoveUnitFlag(UnitFlags.NonAttackable); + me.SetImmuneToPC(false); + CycloneTimer = 30000; + ChainLightningTimer = 10000; + } + + public override void Reset() + { + Initialize(); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayCroneSlay); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayCroneAggro); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayCroneDeath); + instance.SetBossState(DataTypes.OperaPerformance, EncounterState.Done); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (CycloneTimer <= diff) + { + Creature Cyclone = DoSpawnCreature(CreatureIds.Cyclone, RandomHelper.URand(0, 9), RandomHelper.URand(0, 9), 0, 0, TempSummonType.TimedDespawn, TimeSpan.FromSeconds(15)); + if (Cyclone) + Cyclone.CastSpell(Cyclone, SpellIds.CycloneVisual, true); + CycloneTimer = 30000; + } + else CycloneTimer -= diff; + + if (ChainLightningTimer <= diff) + { + DoCastVictim(SpellIds.ChainLightning); + ChainLightningTimer = 15000; + } + else ChainLightningTimer -= diff; + + DoMeleeAttackIfReady(); + } + } + + [Script] + class npc_cyclone : ScriptedAI + { + uint MoveTimer; + + public npc_cyclone(Creature creature) : base(creature) + { + Initialize(); + } + + void Initialize() + { + MoveTimer = 1000; + } + + public override void Reset() + { + Initialize(); + } + + public override void JustEngagedWith(Unit who) { } + + public override void MoveInLineOfSight(Unit who) + + { + } + + public override void UpdateAI(uint diff) + { + if (!me.HasAura(SpellIds.Knockback)) + DoCast(me, SpellIds.Knockback, new CastSpellExtraArgs(true)); + + if (MoveTimer <= diff) + { + Position pos = me.GetRandomNearPosition(10); + me.GetMotionMaster().MovePoint(0, pos); + MoveTimer = RandomHelper.URand(5000, 8000); + } + else MoveTimer -= diff; + } + } + + [Script] + class npc_grandmother : ScriptedAI + { + public npc_grandmother(Creature creature) : base(creature) { } + + public override bool OnGossipSelect(Player player, uint menuId, uint gossipListId) + { + if (menuId == TextIds.OptionWhatPhatLewtsYouHave && gossipListId == 0) + { + player.CloseGossipMenu(); + + Creature pBigBadWolf = me.SummonCreature(CreatureIds.BigBadWolf, me.GetPositionX(), me.GetPositionY(), me.GetPositionZ(), me.GetOrientation(), TempSummonType.TimedOrDeadDespawn, TimeSpan.FromHours(2)); + if (pBigBadWolf) + pBigBadWolf.GetAI().AttackStart(player); + + me.DespawnOrUnsummon(); + } + return false; + } + } + + [Script] + class boss_bigbadwolf : ScriptedAI + { + InstanceScript instance; + + uint ChaseTimer; + uint FearTimer; + uint SwipeTimer; + + ObjectGuid HoodGUID; + float TempThreat; + + bool IsChasing; + + public boss_bigbadwolf(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + ChaseTimer = 30000; + FearTimer = RandomHelper.URand(25000, 35000); + SwipeTimer = 5000; + + HoodGUID.Clear(); + TempThreat = 0; + + IsChasing = false; + } + + public override void Reset() + { + Initialize(); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayWolfAggro); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayWolfSlay); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void JustDied(Unit killer) + { + DoPlaySoundToSet(me, MiscConst.SoundWolfDeath); + instance.SetBossState(DataTypes.OperaPerformance, EncounterState.Done); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); + + if (ChaseTimer <= diff) + { + if (!IsChasing) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + { + Talk(TextIds.SayWolfHood); + DoCast(target, SpellIds.LittleRedRidingHood, new CastSpellExtraArgs(true)); + TempThreat = GetThreat(target); + if (TempThreat != 0f) + ModifyThreatByPercent(target, -100); + HoodGUID = target.GetGUID(); + AddThreat(target, 1000000.0f); + ChaseTimer = 20000; + IsChasing = true; + } + } + else + { + IsChasing = false; + + Unit target = Global.ObjAccessor.GetUnit(me, HoodGUID); + if (target) + { + HoodGUID.Clear(); + if (GetThreat(target) != 0f) + ModifyThreatByPercent(target, -100); + AddThreat(target, TempThreat); + TempThreat = 0; + } + + ChaseTimer = 40000; + } + } + else ChaseTimer -= diff; + + if (IsChasing) + return; + + if (FearTimer <= diff) + { + DoCastVictim(SpellIds.TerrifyingHowl); + FearTimer = RandomHelper.URand(25000, 35000); + } + else FearTimer -= diff; + + if (SwipeTimer <= diff) + { + DoCastVictim(SpellIds.WideSwipe); + SwipeTimer = RandomHelper.URand(25000, 30000); + } + else SwipeTimer -= diff; + } + } + + [Script] + class boss_julianne : ScriptedAI + { + InstanceScript instance; + + uint EntryYellTimer; + uint AggroYellTimer; + + ObjectGuid RomuloGUID; + + RAJPhase Phase; + + uint BlindingPassionTimer; + uint DevotionTimer; + uint EternalAffectionTimer; + uint PowerfulAttractionTimer; + uint SummonRomuloTimer; + public uint ResurrectTimer; + uint DrinkPoisonTimer; + public uint ResurrectSelfTimer; + + public bool IsFakingDeath; + bool SummonedRomulo; + public bool RomuloDead; + + public boss_julianne(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + EntryYellTimer = 1000; + AggroYellTimer = 10000; + IsFakingDeath = false; + ResurrectTimer = 0; + } + + void Initialize() + { + RomuloGUID.Clear(); + Phase = RAJPhase.Julianne; + + BlindingPassionTimer = 30000; + DevotionTimer = 15000; + EternalAffectionTimer = 25000; + PowerfulAttractionTimer = 5000; + SummonRomuloTimer = 10000; + DrinkPoisonTimer = 0; + ResurrectSelfTimer = 0; + + SummonedRomulo = false; + RomuloDead = false; + } + + public override void Reset() + { + Initialize(); + if (IsFakingDeath) + { + MiscConst.Resurrect(me); + IsFakingDeath = false; + } + } + + public override void JustEngagedWith(Unit who) { } + + public override void AttackStart(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.AttackStart(who); + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void SpellHit(WorldObject caster, SpellInfo spellInfo) + { + if (spellInfo.Id == SpellIds.DrinkPoison) + { + Talk(TextIds.SayJulianneDeath01); + DrinkPoisonTimer = 2500; + } + } + + public override void DamageTaken(Unit done_by, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + if (damage < me.GetHealth()) + return; + + //anything below only used if incoming damage will kill + + if (Phase == RAJPhase.Julianne) + { + damage = 0; + + //this means already drinking, so return + if (IsFakingDeath) + return; + + me.InterruptNonMeleeSpells(true); + DoCast(me, SpellIds.DrinkPoison); + + IsFakingDeath = true; + //Is This Usefull? Creature Julianne = (ObjectAccessor.GetCreature((me), JulianneGUID)); + return; + } + + if (Phase == RAJPhase.Romulo) + { + Log.outError(LogFilter.Scripts, "boss_julianneAI: cannot take damage in PhaseRomulo, why was i here?"); + damage = 0; + return; + } + + if (Phase == RAJPhase.Both) + { + //if this is true then we have to kill romulo too + if (RomuloDead) + { + Creature Romulo = ObjectAccessor.GetCreature(me, RomuloGUID); + if (Romulo) + { + Romulo.RemoveUnitFlag(UnitFlags.Uninteractible); + Romulo.GetMotionMaster().Clear(); + Romulo.SetDeathState(DeathState.JustDied); + Romulo.CombatStop(true); + Romulo.ReplaceAllDynamicFlags(UnitDynFlags.Lootable); + } + + return; + } + + //if not already returned, then romulo is alive and we can pretend die + Creature Romulo1 = (ObjectAccessor.GetCreature((me), RomuloGUID)); + if (Romulo1) + { + MiscConst.PretendToDie(me); + IsFakingDeath = true; + Romulo1.GetAI().ResurrectTimer = 10000; + Romulo1.GetAI().JulianneDead = true; + damage = 0; + return; + } + } + Log.outError(LogFilter.Scripts, "boss_julianneAI: DamageTaken reach end of code, that should not happen."); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayJulianneDeath02); + instance.SetBossState(DataTypes.OperaPerformance, EncounterState.Done); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayJulianneSlay); + } + + public override void UpdateAI(uint diff) + { + if (EntryYellTimer != 0) + { + if (EntryYellTimer <= diff) + { + Talk(TextIds.SayJulianneEnter); + EntryYellTimer = 0; + } + else EntryYellTimer -= diff; + } + + if (AggroYellTimer != 0) + { + if (AggroYellTimer <= diff) + { + Talk(TextIds.SayJulianneAggro); + me.RemoveUnitFlag(UnitFlags.NonAttackable); + me.SetFaction((uint)FactionTemplates.Monster2); + AggroYellTimer = 0; + } + else AggroYellTimer -= diff; + } + + if (DrinkPoisonTimer != 0) + { + //will do this TimeSpan.FromSeconds(2s)ecs after spell hit. this is time to display visual as expected + if (DrinkPoisonTimer <= diff) + { + MiscConst.PretendToDie(me); + Phase = RAJPhase.Romulo; + SummonRomuloTimer = 10000; + DrinkPoisonTimer = 0; + } + else DrinkPoisonTimer -= diff; + } + + if (Phase == RAJPhase.Romulo && !SummonedRomulo) + { + if (SummonRomuloTimer <= diff) + { + Creature pRomulo = me.SummonCreature(CreatureIds.Romulo, MiscConst.RomuloX, MiscConst.RomuloY, me.GetPositionZ(), 0, TempSummonType.TimedOrDeadDespawn, TimeSpan.FromHours(2)); + if (pRomulo) + { + RomuloGUID = pRomulo.GetGUID(); + pRomulo.GetAI().JulianneGUID = me.GetGUID(); + pRomulo.GetAI().Phase = RAJPhase.Romulo; + DoZoneInCombat(pRomulo); + + pRomulo.SetFaction((uint)FactionTemplates.Monster2); + } + SummonedRomulo = true; + } + else SummonRomuloTimer -= diff; + } + + if (ResurrectSelfTimer != 0) + { + if (ResurrectSelfTimer <= diff) + { + MiscConst.Resurrect(me); + Phase = RAJPhase.Both; + IsFakingDeath = false; + + if (me.GetVictim()) + AttackStart(me.GetVictim()); + + ResurrectSelfTimer = 0; + ResurrectTimer = 1000; + } + else ResurrectSelfTimer -= diff; + } + + if (!UpdateVictim() || IsFakingDeath) + return; + + if (RomuloDead) + { + if (ResurrectTimer <= diff) + { + Creature Romulo = ObjectAccessor.GetCreature(me, RomuloGUID); + if (Romulo && Romulo.GetAI().IsFakingDeath) + { + Talk(TextIds.SayJulianneResurrect); + MiscConst.Resurrect(Romulo); + Romulo.GetAI().IsFakingDeath = false; + RomuloDead = false; + ResurrectTimer = 10000; + } + } + else ResurrectTimer -= diff; + } + + if (BlindingPassionTimer <= diff) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + DoCast(target, SpellIds.BlindingPassion); + BlindingPassionTimer = RandomHelper.URand(30000, 45000); + } + else BlindingPassionTimer -= diff; + + if (DevotionTimer <= diff) + { + DoCast(me, SpellIds.Devotion); + DevotionTimer = RandomHelper.URand(15000, 45000); + } + else DevotionTimer -= diff; + + if (PowerfulAttractionTimer <= diff) + { + DoCast(SelectTarget(SelectTargetMethod.Random, 0), SpellIds.PowerfulAttraction); + PowerfulAttractionTimer = RandomHelper.URand(5000, 30000); + } + else PowerfulAttractionTimer -= diff; + + if (EternalAffectionTimer <= diff) + { + if (RandomHelper.URand(0, 1) != 0 && SummonedRomulo) + { + Creature Romulo = (ObjectAccessor.GetCreature((me), RomuloGUID)); + if (Romulo && Romulo.IsAlive() && !RomuloDead) + DoCast(Romulo, SpellIds.EternalAffection); + } + else DoCast(me, SpellIds.EternalAffection); + + EternalAffectionTimer = RandomHelper.URand(45000, 60000); + } + else EternalAffectionTimer -= diff; + + DoMeleeAttackIfReady(); + } + } + + [Script] + class boss_romulo : ScriptedAI + { + InstanceScript instance; + + public ObjectGuid JulianneGUID; + public RAJPhase Phase; + + uint BackwardLungeTimer; + uint DaringTimer; + uint DeadlySwatheTimer; + uint PoisonThrustTimer; + public uint ResurrectTimer; + + public bool IsFakingDeath; + public bool JulianneDead; + + public boss_romulo(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + JulianneGUID.Clear(); + Phase = RAJPhase.Romulo; + + BackwardLungeTimer = 15000; + DaringTimer = 20000; + DeadlySwatheTimer = 25000; + PoisonThrustTimer = 10000; + ResurrectTimer = 10000; + + IsFakingDeath = false; + JulianneDead = false; + } + + public override void Reset() + { + Initialize(); + } + + public override void JustReachedHome() + { + me.DespawnOrUnsummon(); + } + + public override void DamageTaken(Unit done_by, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + if (damage < me.GetHealth()) + return; + + //anything below only used if incoming damage will kill + + if (Phase == RAJPhase.Romulo) + { + Talk(TextIds.SayRomuloDeath); + MiscConst.PretendToDie(me); + IsFakingDeath = true; + Phase = RAJPhase.Both; + + Creature Julianne = ObjectAccessor.GetCreature(me, JulianneGUID); + if (Julianne) + { + Julianne.GetAI().RomuloDead = true; + Julianne.GetAI().ResurrectSelfTimer = 10000; + } + + damage = 0; + return; + } + + if (Phase == RAJPhase.Both) + { + if (JulianneDead) + { + Creature Julianne = ObjectAccessor.GetCreature(me, JulianneGUID); + if (Julianne) + { + Julianne.RemoveUnitFlag(UnitFlags.Uninteractible); + Julianne.GetMotionMaster().Clear(); + Julianne.SetDeathState(DeathState.JustDied); + Julianne.CombatStop(true); + Julianne.ReplaceAllDynamicFlags(UnitDynFlags.Lootable); + } + return; + } + + Creature Julianne1 = ObjectAccessor.GetCreature(me, JulianneGUID); + if (Julianne1) + { + MiscConst.PretendToDie(me); + IsFakingDeath = true; + Julianne1.GetAI().ResurrectTimer = 10000; + Julianne1.GetAI().RomuloDead = true; + damage = 0; + return; + } + } + + Log.outError(LogFilter.Scenario, "boss_romulo: DamageTaken reach end of code, that should not happen."); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayRomuloAggro); + if (!JulianneGUID.IsEmpty()) + { + Creature Julianne = ObjectAccessor.GetCreature(me, JulianneGUID); + if (Julianne && Julianne.GetVictim()) + { + AddThreat(Julianne.GetVictim(), 1.0f); + AttackStart(Julianne.GetVictim()); + } + } + } + + public override void MoveInLineOfSight(Unit who) + { + if (me.HasUnitFlag(UnitFlags.NonAttackable)) + return; + + base.MoveInLineOfSight(who); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayRomuloDeath); + instance.SetBossState(DataTypes.OperaPerformance, EncounterState.Done); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayRomuloSlay); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim() || IsFakingDeath) + return; + + if (JulianneDead) + { + if (ResurrectTimer <= diff) + { + Creature Julianne = (ObjectAccessor.GetCreature((me), JulianneGUID)); + if (Julianne && Julianne.GetAI().IsFakingDeath) + { + Talk(TextIds.SayRomuloResurrect); + MiscConst.Resurrect(Julianne); + Julianne.GetAI().IsFakingDeath = false; + JulianneDead = false; + ResurrectTimer = 10000; + } + } + else ResurrectTimer -= diff; + } + + if (BackwardLungeTimer <= diff) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 1, 100, true); + if (target && !me.HasInArc(MathF.PI, target)) + { + DoCast(target, SpellIds.BackwardLunge); + BackwardLungeTimer = RandomHelper.URand(15000, 30000); + } + } + else BackwardLungeTimer -= diff; + + if (DaringTimer <= diff) + { + DoCast(me, SpellIds.Daring); + DaringTimer = RandomHelper.URand(20000, 40000); + } + else DaringTimer -= diff; + + if (DeadlySwatheTimer <= diff) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + DoCast(target, SpellIds.DeadlySwathe); + DeadlySwatheTimer = RandomHelper.URand(15000, 25000); + } + else DeadlySwatheTimer -= diff; + + if (PoisonThrustTimer <= diff) + { + DoCastVictim(SpellIds.PoisonThrust); + PoisonThrustTimer = RandomHelper.URand(10000, 20000); + } + else PoisonThrustTimer -= diff; + + DoMeleeAttackIfReady(); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/PrinceMalchezaar.cs b/Source/Scripts/EasternKingdoms/Karazhan/PrinceMalchezaar.cs new file mode 100644 index 000000000..447498285 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/PrinceMalchezaar.cs @@ -0,0 +1,567 @@ +/* + * 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; +using System.Collections.Generic; +using System.Numerics; + +namespace Scripts.EasternKingdoms.Karazhan.PrinceMalchezaar +{ + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayAxeToss1 = 1; + public const uint SayAxeToss2 = 2; + //public const uint SaySpecial1 = 3; Not used, needs to be implemented, but I don't know where it should be used. + //public const uint SaySpecial2 = 4; Not used, needs to be implemented, but I don't know where it should be used. + //public const uint SaySpecial3 = 5; Not used, needs to be implemented, but I don't know where it should be used. + public const uint SaySlay = 6; + public const uint SaySummon = 7; + public const uint SayDeath = 8; + } + + struct SpellIds + { + public const uint Enfeeble = 30843; //Enfeeble during phase 1 and 2 + public const uint EnfeebleEffect = 41624; + + public const uint Shadownova = 30852; //Shadownova used during all phases + public const uint SwPain = 30854; //Shadow word pain during phase 1 and 3 (different targeting rules though) + public const uint ThrashPassive = 12787; //Extra attack chance during phase 2 + public const uint SunderArmor = 30901; //Sunder armor during phase 2 + public const uint ThrashAura = 12787; //Passive proc chance for thrash + public const uint EquipAxes = 30857; //Visual for axe equiping + public const uint AmplifyDamage = 39095; //Amplifiy during phase 3 + public const uint Cleave = 30131; //Same as Nightbane. + public const uint Hellfire = 30859; //Infenals' hellfire aura + + public const uint InfernalRelay = 30834; + } + + struct MiscConst + { + public const uint TotalInfernalPoints = 18; + public const uint NetherspiteInfernal = 17646; //The netherspite infernal creature + public const uint MalchezarsAxe = 17650; //Malchezar's axes (creatures), summoned during phase 3 + + public const uint InfernalModelInvisible = 11686; //Infernal Effects + public const int EquipIdAxe = 33542; //Axes info + } + + [Script] + class netherspite_infernal : ScriptedAI + { + public ObjectGuid Malchezaar; + public Vector2 Point; + + public netherspite_infernal(Creature creature) : base(creature) { } + + public override void Reset() { } + + public override void JustEngagedWith(Unit who) { } + + public override void MoveInLineOfSight(Unit who) { } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + + public override void KilledUnit(Unit who) + { + Unit unit = Global.ObjAccessor.GetUnit(me, Malchezaar); + if (unit) + { + Creature creature = unit.ToCreature(); + if (creature) + creature.GetAI().KilledUnit(who); + } + } + + public override void SpellHit(WorldObject caster, SpellInfo spellInfo) + { + if (spellInfo.Id == SpellIds.InfernalRelay) + { + me.SetDisplayId(me.GetNativeDisplayId()); + me.SetUnitFlag(UnitFlags.Uninteractible); + + _scheduler.Schedule(TimeSpan.FromSeconds(4), task => DoCast(me, SpellIds.Hellfire)); + _scheduler.Schedule(TimeSpan.FromSeconds(170), task => + { + Creature pMalchezaar = ObjectAccessor.GetCreature(me, Malchezaar); + + if (pMalchezaar && pMalchezaar.IsAlive()) + pMalchezaar.GetAI().Cleanup(me, Point); + }); + } + } + + public override void DamageTaken(Unit done_by, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + if (!done_by || done_by.GetGUID() != Malchezaar) + damage = 0; + } + } + + [Script] + class boss_malchezaar : ScriptedAI + { + static Vector2[] InfernalPoints = + { + new Vector2(-10922.8f, -1985.2f), + new Vector2(-10916.2f, -1996.2f), + new Vector2(-10932.2f, -2008.1f), + new Vector2(-10948.8f, -2022.1f), + new Vector2(-10958.7f, -1997.7f), + new Vector2(-10971.5f, -1997.5f), + new Vector2(-10990.8f, -1995.1f), + new Vector2(-10989.8f, -1976.5f), + new Vector2(-10971.6f, -1973.0f), + new Vector2(-10955.5f, -1974.0f), + new Vector2(-10939.6f, -1969.8f), + new Vector2(-10958.0f, -1952.2f), + new Vector2(-10941.7f, -1954.8f), + new Vector2(-10943.1f, -1988.5f), + new Vector2(-10948.8f, -2005.1f), + new Vector2(-10984.0f, -2019.3f), + new Vector2(-10932.8f, -1979.6f), + new Vector2(-10935.7f, -1996.0f) + }; + + InstanceScript instance; + uint EnfeebleTimer; + uint EnfeebleResetTimer; + uint ShadowNovaTimer; + uint SWPainTimer; + uint SunderArmorTimer; + uint AmplifyDamageTimer; + uint Cleave_Timer; + uint InfernalTimer; + uint AxesTargetSwitchTimer; + uint InfernalCleanupTimer; + + List infernals = new(); + List positions = new(); + + ObjectGuid[] axes = new ObjectGuid[2]; + ObjectGuid[] enfeeble_targets = new ObjectGuid[5]; + ulong[] enfeeble_health = new ulong[5]; + + uint phase; + + public boss_malchezaar(Creature creature) : base(creature) + { + Initialize(); + + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + EnfeebleTimer = 30000; + EnfeebleResetTimer = 38000; + ShadowNovaTimer = 35500; + SWPainTimer = 20000; + AmplifyDamageTimer = 5000; + Cleave_Timer = 8000; + InfernalTimer = 40000; + InfernalCleanupTimer = 47000; + AxesTargetSwitchTimer = RandomHelper.URand(7500, 20000); + SunderArmorTimer = RandomHelper.URand(5000, 10000); + phase = 1; + + for (byte i = 0; i < 5; ++i) + { + enfeeble_targets[i].Clear(); + enfeeble_health[i] = 0; + } + } + + public override void Reset() + { + AxesCleanup(); + ClearWeapons(); + InfernalCleanup(); + positions.Clear(); + + Initialize(); + + for (byte i = 0; i < MiscConst.TotalInfernalPoints; ++i) + positions.Add(InfernalPoints[i]); + + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoNetherDoor), true); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SaySlay); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + + AxesCleanup(); + ClearWeapons(); + InfernalCleanup(); + positions.Clear(); + + for (byte i = 0; i < MiscConst.TotalInfernalPoints; ++i) + positions.Add(InfernalPoints[i]); + + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoNetherDoor), true); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayAggro); + + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoNetherDoor), false); // Open the door leading further in + } + + void InfernalCleanup() + { + //Infernal Cleanup + foreach (var guid in infernals) + { + Unit pInfernal = Global.ObjAccessor.GetUnit(me, guid); + if (pInfernal && pInfernal.IsAlive()) + { + pInfernal.SetVisible(false); + pInfernal.SetDeathState(DeathState.JustDied); + } + } + + infernals.Clear(); + } + + void AxesCleanup() + { + for (byte i = 0; i < 2; ++i) + { + Unit axe = Global.ObjAccessor.GetUnit(me, axes[i]); + if (axe && axe.IsAlive()) + axe.KillSelf(); + axes[i].Clear(); + } + } + + void ClearWeapons() + { + SetEquipmentSlots(false, 0, 0); + me.SetCanDualWield(false); + } + + void EnfeebleHealthEffect() + { + SpellInfo info = Global.SpellMgr.GetSpellInfo(SpellIds.EnfeebleEffect, GetDifficulty()); + if (info == null) + return; + + Unit tank = me.GetThreatManager().GetCurrentVictim(); + List targets = new(); + + foreach (var refe in me.GetThreatManager().GetSortedThreatList()) + { + Unit target = refe.GetVictim(); + if (target != tank && target.IsAlive() && target.IsPlayer()) + targets.Add(target); + } + + if (targets.Empty()) + return; + + //cut down to size if we have more than 5 targets + targets.RandomResize(5); + + uint i = 0; + foreach (var target in targets) + { + if (target) + { + enfeeble_targets[i] = target.GetGUID(); + enfeeble_health[i] = target.GetHealth(); + + CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); + args.OriginalCaster = me.GetGUID(); + target.CastSpell(target, SpellIds.Enfeeble, args); + target.SetHealth(1); + } + + i++; + } + } + + void EnfeebleResetHealth() + { + for (byte i = 0; i < 5; ++i) + { + Unit target = Global.ObjAccessor.GetUnit(me, enfeeble_targets[i]); + if (target && target.IsAlive()) + target.SetHealth(enfeeble_health[i]); + enfeeble_targets[i].Clear(); + enfeeble_health[i] = 0; + } + } + + void SummonInfernal(uint diff) + { + Vector2 point = Vector2.Zero; + Position pos = null; + if ((me.GetMapId() != 532) || positions.Empty()) + pos = me.GetRandomNearPosition(60); + else + { + point = positions.SelectRandom(); + pos.Relocate(point.X, point.Y, 275.5f, RandomHelper.FRand(0.0f, (MathF.PI * 2))); + } + + Creature infernal = me.SummonCreature(MiscConst.NetherspiteInfernal, pos, TempSummonType.TimedDespawn, TimeSpan.FromMinutes(3)); + + if (infernal) + { + infernal.SetDisplayId(MiscConst.InfernalModelInvisible); + infernal.SetFaction(me.GetFaction()); + if (point != Vector2.Zero) + infernal.GetAI().Point = point; + infernal.GetAI().Malchezaar = me.GetGUID(); + + infernals.Add(infernal.GetGUID()); + DoCast(infernal, SpellIds.InfernalRelay); + } + + Talk(TextIds.SaySummon); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (EnfeebleResetTimer != 0 && EnfeebleResetTimer <= diff) // Let's not forget to reset that + { + EnfeebleResetHealth(); + EnfeebleResetTimer = 0; + } + else EnfeebleResetTimer -= diff; + + if (me.HasUnitState(UnitState.Stunned)) // While shifting to phase 2 malchezaar stuns himself + return; + + if (me.GetVictim() && me.GetTarget() != me.GetVictim().GetGUID()) + me.SetTarget(me.GetVictim().GetGUID()); + + if (phase == 1) + { + if (HealthBelowPct(60)) + { + me.InterruptNonMeleeSpells(false); + + phase = 2; + + //animation + DoCast(me, SpellIds.EquipAxes); + + //text + Talk(TextIds.SayAxeToss1); + + //passive thrash aura + DoCast(me, SpellIds.ThrashAura, new CastSpellExtraArgs(true)); + + //models + SetEquipmentSlots(false, MiscConst.EquipIdAxe, MiscConst.EquipIdAxe); + + me.SetBaseAttackTime(WeaponAttackType.OffAttack, (me.GetBaseAttackTime(WeaponAttackType.BaseAttack) * 150) / 100); + me.SetCanDualWield(true); + } + } + else if (phase == 2) + { + if (HealthBelowPct(30)) + { + InfernalTimer = 15000; + + phase = 3; + + ClearWeapons(); + + //remove thrash + me.RemoveAurasDueToSpell(SpellIds.ThrashAura); + + Talk(TextIds.SayAxeToss2); + + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + for (byte i = 0; i < 2; ++i) + { + Creature axe = me.SummonCreature(MiscConst.MalchezarsAxe, me.GetPositionX(), me.GetPositionY(), me.GetPositionZ(), 0, TempSummonType.TimedDespawnOutOfCombat, TimeSpan.FromSeconds(1)); + if (axe) + { + axe.SetUnitFlag(UnitFlags.Uninteractible); + axe.SetFaction(me.GetFaction()); + axes[i] = axe.GetGUID(); + if (target) + { + axe.GetAI().AttackStart(target); + AddThreat(target, 10000000.0f, axe); + } + } + } + + if (ShadowNovaTimer > 35000) + ShadowNovaTimer = EnfeebleTimer + 5000; + + return; + } + + if (SunderArmorTimer <= diff) + { + DoCastVictim(SpellIds.SunderArmor); + SunderArmorTimer = RandomHelper.URand(10000, 18000); + } + else SunderArmorTimer -= diff; + + if (Cleave_Timer <= diff) + { + DoCastVictim(SpellIds.Cleave); + Cleave_Timer = RandomHelper.URand(6000, 12000); + } + else Cleave_Timer -= diff; + } + else + { + if (AxesTargetSwitchTimer <= diff) + { + AxesTargetSwitchTimer = RandomHelper.URand(7500, 20000); + + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + { + for (byte i = 0; i < 2; ++i) + { + Unit axe = Global.ObjAccessor.GetUnit(me, axes[i]); + if (axe) + { + if (axe.GetVictim()) + ResetThreat(axe.GetVictim(), axe); + AddThreat(target, 1000000.0f, axe); + } + } + } + } + else AxesTargetSwitchTimer -= diff; + + if (AmplifyDamageTimer <= diff) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + DoCast(target, SpellIds.AmplifyDamage); + AmplifyDamageTimer = RandomHelper.URand(20000, 30000); + } + else AmplifyDamageTimer -= diff; + } + + //Time for global and double timers + if (InfernalTimer <= diff) + { + SummonInfernal(diff); + InfernalTimer = phase == 3 ? 14500 : 44500u; // 15 secs in phase 3, 45 otherwise + } + else InfernalTimer -= diff; + + if (ShadowNovaTimer <= diff) + { + DoCastVictim(SpellIds.Shadownova); + ShadowNovaTimer = phase == 3 ? 31000 : uint.MaxValue; + } + else ShadowNovaTimer -= diff; + + if (phase != 2) + { + if (SWPainTimer <= diff) + { + Unit target; + if (phase == 1) + target = me.GetVictim(); // the tank + else // anyone but the tank + target = SelectTarget(SelectTargetMethod.Random, 1, 100, true); + + if (target) + DoCast(target, SpellIds.SwPain); + + SWPainTimer = 20000; + } + else SWPainTimer -= diff; + } + + if (phase != 3) + { + if (EnfeebleTimer <= diff) + { + EnfeebleHealthEffect(); + EnfeebleTimer = 30000; + ShadowNovaTimer = 5000; + EnfeebleResetTimer = 9000; + } + else EnfeebleTimer -= diff; + } + + if (phase == 2) + DoMeleeAttacksIfReady(); + else + DoMeleeAttackIfReady(); + } + + void DoMeleeAttacksIfReady() + { + if (me.IsWithinMeleeRange(me.GetVictim()) && !me.IsNonMeleeSpellCast(false)) + { + //Check for base attack + if (me.IsAttackReady() && me.GetVictim()) + { + me.AttackerStateUpdate(me.GetVictim()); + me.ResetAttackTimer(); + } + //Check for offhand attack + if (me.IsAttackReady(WeaponAttackType.OffAttack) && me.GetVictim()) + { + me.AttackerStateUpdate(me.GetVictim(), WeaponAttackType.OffAttack); + me.ResetAttackTimer(WeaponAttackType.OffAttack); + } + } + } + + public void Cleanup(Creature infernal, Vector2 point) + { + foreach (var guid in infernals) + { + if (guid == infernal.GetGUID()) + { + infernals.Remove(guid); + break; + } + } + + positions.Add(point); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/ShadeOfAran.cs b/Source/Scripts/EasternKingdoms/Karazhan/ShadeOfAran.cs new file mode 100644 index 000000000..8cb4ada39 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/ShadeOfAran.cs @@ -0,0 +1,568 @@ +/* + * 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; +using System.Collections.Generic; + +namespace Scripts.EasternKingdoms.Karazhan.ShadeOfAran +{ + struct SpellIds + { + public const uint Frostbolt = 29954; + public const uint Fireball = 29953; + public const uint Arcmissle = 29955; + public const uint Chainsofice = 29991; + public const uint Dragonsbreath = 29964; + public const uint Massslow = 30035; + public const uint FlameWreath = 29946; + public const uint AoeCs = 29961; + public const uint Playerpull = 32265; + public const uint Aexplosion = 29973; + public const uint MassPoly = 29963; + public const uint BlinkCenter = 29967; + public const uint Elementals = 29962; + public const uint Conjure = 29975; + public const uint Drink = 30024; + public const uint Potion = 32453; + public const uint AoePyroblast = 29978; + + public const uint CircularBlizzard = 29951; + public const uint Waterbolt = 31012; + public const uint ShadowPyro = 29978; + } + + struct CreatureIds + { + public const uint WaterElemental = 17167; + public const uint ShadowOfAran = 18254; + public const uint AranBlizzard = 17161; + } + + struct TextIds + { + public const uint SayAggro = 0; + public const uint SayFlamewreath = 1; + public const uint SayBlizzard = 2; + public const uint SayExplosion = 3; + public const uint SayDrink = 4; + public const uint SayElementals = 5; + public const uint SayKill = 6; + public const uint SayTimeover = 7; + public const uint SayDeath = 8; + public const uint SayAtiesh = 9; + } + + enum SuperSpell + { + Flame = 0, + Blizzard, + Ae, + } + + [Script] + class boss_aran : ScriptedAI + { + static uint[] AtieshStaves = + { + 22589, //ItemAtieshMage, + 22630, //ItemAtieshWarlock, + 22631, //ItemAtieshPriest, + 22632 //ItemAtieshDruid, + }; + + InstanceScript instance; + + uint SecondarySpellTimer; + uint NormalCastTimer; + uint SuperCastTimer; + uint BerserkTimer; + uint CloseDoorTimer; // Don't close the door right on aggro in case some people are still entering. + + SuperSpell LastSuperSpell; + + uint FlameWreathTimer; + uint FlameWreathCheckTime; + ObjectGuid[] FlameWreathTarget = new ObjectGuid[3]; + float[] FWTargPosX = new float[3]; + float[] FWTargPosY = new float[3]; + + uint CurrentNormalSpell; + uint ArcaneCooldown; + uint FireCooldown; + uint FrostCooldown; + + uint DrinkInterruptTimer; + + bool ElementalsSpawned; + bool Drinking; + bool DrinkInturrupted; + bool SeenAtiesh; + + public boss_aran(Creature creature) : base(creature) + { + Initialize(); + instance = creature.GetInstanceScript(); + } + + void Initialize() + { + SecondarySpellTimer = 5000; + NormalCastTimer = 0; + SuperCastTimer = 35000; + BerserkTimer = 720000; + CloseDoorTimer = 15000; + + LastSuperSpell = (SuperSpell)(RandomHelper.Rand32() % 3); + + FlameWreathTimer = 0; + FlameWreathCheckTime = 0; + + CurrentNormalSpell = 0; + ArcaneCooldown = 0; + FireCooldown = 0; + FrostCooldown = 0; + + DrinkInterruptTimer = 10000; + + ElementalsSpawned = false; + Drinking = false; + DrinkInturrupted = false; + } + + public override void Reset() + { + Initialize(); + + // Not in progress + instance.SetBossState(DataTypes.Aran, EncounterState.NotStarted); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoLibraryDoor), true); + } + + public override void KilledUnit(Unit victim) + { + Talk(TextIds.SayKill); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + + instance.SetBossState(DataTypes.Aran, EncounterState.Done); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoLibraryDoor), true); + } + + public override void JustEngagedWith(Unit who) + { + Talk(TextIds.SayAggro); + + instance.SetBossState(DataTypes.Aran, EncounterState.InProgress); + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoLibraryDoor), false); + } + + void FlameWreathEffect() + { + List targets = new(); + //store the threat list in a different container + foreach (var refe in me.GetThreatManager().GetSortedThreatList()) + { + Unit target = refe.GetVictim(); + if (refe.GetVictim().IsPlayer() && refe.GetVictim().IsAlive()) + targets.Add(target); + } + + //cut down to size if we have more than 3 targets + targets.RandomResize(3); + + uint i = 0; + foreach (var unit in targets) + { + if (unit) + { + FlameWreathTarget[i] = unit.GetGUID(); + FWTargPosX[i] = unit.GetPositionX(); + FWTargPosY[i] = unit.GetPositionY(); + DoCast(unit, SpellIds.FlameWreath, new CastSpellExtraArgs(true)); + ++i; + } + } + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + if (CloseDoorTimer != 0) + { + if (CloseDoorTimer <= diff) + { + instance.HandleGameObject(instance.GetGuidData(DataTypes.GoLibraryDoor), false); + CloseDoorTimer = 0; + } + else CloseDoorTimer -= diff; + } + + //Cooldowns for casts + if (ArcaneCooldown != 0) + { + if (ArcaneCooldown >= diff) + ArcaneCooldown -= diff; + else ArcaneCooldown = 0; + } + + if (FireCooldown != 0) + { + if (FireCooldown >= diff) + FireCooldown -= diff; + else FireCooldown = 0; + } + + if (FrostCooldown != 0) + { + if (FrostCooldown >= diff) + FrostCooldown -= diff; + else FrostCooldown = 0; + } + + if (!Drinking && me.GetMaxPower(PowerType.Mana) != 0 && me.GetPowerPct(PowerType.Mana) < 20.0f) + { + Drinking = true; + me.InterruptNonMeleeSpells(false); + + Talk(TextIds.SayDrink); + + if (!DrinkInturrupted) + { + DoCast(me, SpellIds.MassPoly, new CastSpellExtraArgs(true)); + DoCast(me, SpellIds.Conjure, new CastSpellExtraArgs(false)); + DoCast(me, SpellIds.Drink, new CastSpellExtraArgs(false)); + me.SetStandState(UnitStandStateType.Sit); + DrinkInterruptTimer = 10000; + } + } + + //Drink Interrupt + if (Drinking && DrinkInturrupted) + { + Drinking = false; + me.RemoveAurasDueToSpell(SpellIds.Drink); + me.SetStandState(UnitStandStateType.Stand); + me.SetPower(PowerType.Mana, me.GetMaxPower(PowerType.Mana) - 32000); + DoCast(me, SpellIds.Potion, new CastSpellExtraArgs(false)); + } + + //Drink Interrupt Timer + if (Drinking && !DrinkInturrupted) + { + if (DrinkInterruptTimer >= diff) + DrinkInterruptTimer -= diff; + else + { + me.SetStandState(UnitStandStateType.Stand); + DoCast(me, SpellIds.Potion, new CastSpellExtraArgs(true)); + DoCast(me, SpellIds.AoePyroblast, new CastSpellExtraArgs(false)); + DrinkInturrupted = true; + Drinking = false; + } + } + + //Don't execute any more code if we are drinking + if (Drinking) + return; + + //Normal casts + if (NormalCastTimer <= diff) + { + if (!me.IsNonMeleeSpellCast(false)) + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (!target) + return; + + uint[] Spells = new uint[3]; + byte AvailableSpells = 0; + + //Check for what spells are not on cooldown + if (ArcaneCooldown == 0) + { + Spells[AvailableSpells] = SpellIds.Arcmissle; + ++AvailableSpells; + } + if (FireCooldown == 0) + { + Spells[AvailableSpells] = SpellIds.Fireball; + ++AvailableSpells; + } + if (FrostCooldown == 0) + { + Spells[AvailableSpells] = SpellIds.Frostbolt; + ++AvailableSpells; + } + + //If no available spells wait 1 second and try again + if (AvailableSpells != 0) + { + CurrentNormalSpell = Spells[RandomHelper.Rand32() % AvailableSpells]; + DoCast(target, CurrentNormalSpell); + } + } + NormalCastTimer = 1000; + } + else NormalCastTimer -= diff; + + if (SecondarySpellTimer <= diff) + { + switch (RandomHelper.URand(0, 1)) + { + case 0: + DoCast(me, SpellIds.AoeCs); + break; + case 1: + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100, true); + if (target) + DoCast(target, SpellIds.Chainsofice); + break; + } + SecondarySpellTimer = RandomHelper.URand(5000, 20000); + } + else SecondarySpellTimer -= diff; + + if (SuperCastTimer <= diff) + { + SuperSpell[] Available = new SuperSpell[2]; + + switch (LastSuperSpell) + { + case SuperSpell.Ae: + Available[0] = SuperSpell.Flame; + Available[1] = SuperSpell.Blizzard; + break; + case SuperSpell.Flame: + Available[0] = SuperSpell.Ae; + Available[1] = SuperSpell.Blizzard; + break; + case SuperSpell.Blizzard: + Available[0] = SuperSpell.Flame; + Available[1] = SuperSpell.Ae; + break; + default: + Available[0] = 0; + Available[1] = 0; + break; + } + + LastSuperSpell = Available[RandomHelper.URand(0, 1)]; + + switch (LastSuperSpell) + { + case SuperSpell.Ae: + Talk(TextIds.SayExplosion); + + DoCast(me, SpellIds.BlinkCenter, new CastSpellExtraArgs(true)); + DoCast(me, SpellIds.Playerpull, new CastSpellExtraArgs(true)); + DoCast(me, SpellIds.Massslow, new CastSpellExtraArgs(true)); + DoCast(me, SpellIds.Aexplosion, new CastSpellExtraArgs(false)); + break; + + case SuperSpell.Flame: + Talk(TextIds.SayFlamewreath); + + FlameWreathTimer = 20000; + FlameWreathCheckTime = 500; + + FlameWreathTarget[0].Clear(); + FlameWreathTarget[1].Clear(); + FlameWreathTarget[2].Clear(); + + FlameWreathEffect(); + break; + + case SuperSpell.Blizzard: + Talk(TextIds.SayBlizzard); + + Creature pSpawn = me.SummonCreature(CreatureIds.AranBlizzard, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawn, TimeSpan.FromSeconds(25)); + if (pSpawn) + { + pSpawn.SetFaction(me.GetFaction()); + pSpawn.CastSpell(pSpawn, SpellIds.CircularBlizzard, false); + } + break; + } + + SuperCastTimer = RandomHelper.URand(35000, 40000); + } + else SuperCastTimer -= diff; + + if (!ElementalsSpawned && HealthBelowPct(40)) + { + ElementalsSpawned = true; + + for (uint i = 0; i < 4; ++i) + { + Creature unit = me.SummonCreature(CreatureIds.WaterElemental, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawn, TimeSpan.FromSeconds(90)); + if (unit) + { + unit.Attack(me.GetVictim(), true); + unit.SetFaction(me.GetFaction()); + } + } + + Talk(TextIds.SayElementals); + } + + if (BerserkTimer <= diff) + { + for (uint i = 0; i < 5; ++i) + { + Creature unit = me.SummonCreature(CreatureIds.ShadowOfAran, 0.0f, 0.0f, 0.0f, 0.0f, TempSummonType.TimedDespawnOutOfCombat, TimeSpan.FromSeconds(5)); + if (unit) + { + unit.Attack(me.GetVictim(), true); + unit.SetFaction(me.GetFaction()); + } + } + + Talk(TextIds.SayTimeover); + + BerserkTimer = 60000; + } + else BerserkTimer -= diff; + + //Flame Wreath check + if (FlameWreathTimer != 0) + { + if (FlameWreathTimer >= diff) + FlameWreathTimer -= diff; + else FlameWreathTimer = 0; + + if (FlameWreathCheckTime <= diff) + { + for (byte i = 0; i < 3; ++i) + { + if (FlameWreathTarget[i].IsEmpty()) + continue; + + Unit unit = Global.ObjAccessor.GetUnit(me, FlameWreathTarget[i]); + if (unit && !unit.IsWithinDist2d(FWTargPosX[i], FWTargPosY[i], 3)) + { + unit.CastSpell(unit, 20476, new CastSpellExtraArgs(TriggerCastFlags.FullMask) + .SetOriginalCaster(me.GetGUID())); + unit.CastSpell(unit, 11027, true); + FlameWreathTarget[i].Clear(); + } + } + FlameWreathCheckTime = 500; + } + else FlameWreathCheckTime -= diff; + } + + if (ArcaneCooldown != 0 && FireCooldown != 0 && FrostCooldown != 0) + DoMeleeAttackIfReady(); + } + + public override void DamageTaken(Unit pAttacker, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null) + { + if (!DrinkInturrupted && Drinking && damage != 0) + DrinkInturrupted = true; + } + + public override void SpellHit(WorldObject caster, SpellInfo spellInfo) + { + //We only care about interrupt effects and only if they are durring a spell currently being cast + if (!spellInfo.HasEffect(SpellEffectName.InterruptCast) || !me.IsNonMeleeSpellCast(false)) + return; + + //Interrupt effect + me.InterruptNonMeleeSpells(false); + + //Normally we would set the cooldown equal to the spell duration + //but we do not have access to the DurationStore + + switch (CurrentNormalSpell) + { + case SpellIds.Arcmissle: ArcaneCooldown = 5000; break; + case SpellIds.Fireball: FireCooldown = 5000; break; + case SpellIds.Frostbolt: FrostCooldown = 5000; break; + } + } + + public override void MoveInLineOfSight(Unit who) + { + base.MoveInLineOfSight(who); + + if (SeenAtiesh || me.IsInCombat() || me.GetDistance2d(who) > me.GetAttackDistance(who) + 10.0f) + return; + + Player player = who.ToPlayer(); + if (!player) + return; + + foreach (uint id in AtieshStaves) + { + if (!PlayerHasWeaponEquipped(player, id)) + continue; + + SeenAtiesh = true; + Talk(TextIds.SayAtiesh); + me.SetFacingTo(me.GetAbsoluteAngle(player)); + me.ClearUnitState(UnitState.Moving); + me.GetMotionMaster().MoveDistract(7 * Time.InMilliseconds, me.GetAbsoluteAngle(who)); + break; + } + } + + bool PlayerHasWeaponEquipped(Player player, uint itemEntry) + { + Item item = player.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); + if (item && item.GetEntry() == itemEntry) + return true; + + return false; + } + } + + [Script] + class water_elemental : ScriptedAI + { + public water_elemental(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromMilliseconds(2000 + (RandomHelper.Rand32() % 3000)), task => + { + DoCastVictim(SpellIds.Waterbolt); + task.Repeat(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5)); + }); + } + + public override void JustEngagedWith(Unit who) { } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff); + } + } +} \ No newline at end of file diff --git a/Source/Scripts/EasternKingdoms/Karazhan/TerestianIllhoof.cs b/Source/Scripts/EasternKingdoms/Karazhan/TerestianIllhoof.cs new file mode 100644 index 000000000..a1a97ef97 --- /dev/null +++ b/Source/Scripts/EasternKingdoms/Karazhan/TerestianIllhoof.cs @@ -0,0 +1,259 @@ +/* + * 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.Karazhan.TerestianIllhoof +{ + struct SpellIds + { + public const uint ShadowBolt = 30055; + public const uint SummonImp = 30066; + public const uint FiendishPortal1 = 30171; + public const uint FiendishPortal2 = 30179; + public const uint Berserk = 32965; + public const uint SummonFiendishImp = 30184; + public const uint BrokenPact = 30065; + public const uint AmplifyFlames = 30053; + public const uint Firebolt = 30050; + public const uint SummonDemonchains = 30120; + public const uint DemonChains = 30206; + public const uint Sacrifice = 30115; + } + + struct TextIds + { + public const uint SaySlay = 0; + public const uint SayDeath = 1; + public const uint SayAggro = 2; + public const uint SaySacrifice = 3; + public const uint SaySummonPortal = 4; + } + + struct MiscConst + { + public const uint NpcFiendishPortal = 17265; + public const int ActionDespawnImps = 1; + } + + [Script] + class boss_terestian : BossAI + { + public boss_terestian(Creature creature) : base(creature, DataTypes.Terestian) { } + + public override void Reset() + { + EntryCheckPredicate pred = new(MiscConst.NpcFiendishPortal); + summons.DoAction(MiscConst.ActionDespawnImps, pred); + _Reset(); + + _scheduler.Schedule(TimeSpan.FromSeconds(1), task => + { + Unit target = SelectTarget(SelectTargetMethod.MaxThreat, 0); + if (target) + DoCast(target, SpellIds.ShadowBolt); + task.Repeat(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(10)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(3), task => + { + me.RemoveAurasDueToSpell(SpellIds.BrokenPact); + DoCastAOE(SpellIds.SummonImp, new CastSpellExtraArgs(true)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(30), task => + { + Unit target = SelectTarget(SelectTargetMethod.Random, 0, 100.0f, true); + if (target) + { + DoCast(target, SpellIds.Sacrifice, new CastSpellExtraArgs(true)); + target.CastSpell(target, SpellIds.SummonDemonchains, true); + Talk(TextIds.SaySacrifice); + } + task.Repeat(TimeSpan.FromSeconds(42)); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(10), task => + { + Talk(TextIds.SaySummonPortal); + DoCastAOE(SpellIds.FiendishPortal1); + }); + _scheduler.Schedule(TimeSpan.FromSeconds(11), task => + { + DoCastAOE(SpellIds.FiendishPortal2, new CastSpellExtraArgs(true)); + }); + _scheduler.Schedule(TimeSpan.FromMinutes(10), task => + { + DoCastSelf(SpellIds.Berserk, new CastSpellExtraArgs(true)); + }); + } + + public override void JustEngagedWith(Unit who) + { + base.JustEngagedWith(who); + Talk(TextIds.SayAggro); + } + + public override void SpellHit(WorldObject caster, SpellInfo spellInfo) + { + if (spellInfo.Id == SpellIds.BrokenPact) + { + _scheduler.Schedule(TimeSpan.FromSeconds(32), task => + { + me.RemoveAurasDueToSpell(SpellIds.BrokenPact); + DoCastAOE(SpellIds.SummonImp, new CastSpellExtraArgs(true)); + }); + } + } + + public override void KilledUnit(Unit victim) + { + if (victim.IsPlayer()) + Talk(TextIds.SaySlay); + } + + public override void JustDied(Unit killer) + { + Talk(TextIds.SayDeath); + EntryCheckPredicate pred = new(MiscConst.NpcFiendishPortal); + summons.DoAction(MiscConst.ActionDespawnImps, pred); + _JustDied(); + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } + + [Script] + class npc_kilrek : ScriptedAI + { + public npc_kilrek(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromSeconds(8), task => + { + DoCastVictim(SpellIds.AmplifyFlames); + task.Repeat(TimeSpan.FromSeconds(9)); + }); + } + + public override void JustDied(Unit killer) + { + DoCastAOE(SpellIds.BrokenPact, new CastSpellExtraArgs(true)); + me.DespawnOrUnsummon(TimeSpan.FromSeconds(15)); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff, () => + { + DoMeleeAttackIfReady(); + }); + } + } + + [Script] + class npc_demon_chain : PassiveAI + { + ObjectGuid _sacrificeGUID; + + public npc_demon_chain(Creature creature) : base(creature) { } + + public override void IsSummonedBy(WorldObject summoner) + { + _sacrificeGUID = summoner.GetGUID(); + DoCastSelf(SpellIds.DemonChains, new CastSpellExtraArgs(true)); + } + + public override void JustDied(Unit killer) + { + Unit sacrifice = Global.ObjAccessor.GetUnit(me, _sacrificeGUID); + if (sacrifice) + sacrifice.RemoveAurasDueToSpell(SpellIds.Sacrifice); + } + } + + [Script] + class npc_fiendish_portal : PassiveAI + { + SummonList _summons; + + public npc_fiendish_portal(Creature creature) : base(creature) + { + _summons = new(me); + } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromMilliseconds(2400), TimeSpan.FromSeconds(8), task => + { + DoCastAOE(SpellIds.SummonFiendishImp, new CastSpellExtraArgs(true)); + task.Repeat(); + }); + } + + public override void DoAction(int action) + { + if (action == MiscConst.ActionDespawnImps) + _summons.DespawnAll(); + } + + public override void JustSummoned(Creature summon) + { + _summons.Summon(summon); + DoZoneInCombat(summon); + } + + public override void UpdateAI(uint diff) + { + _scheduler.Update(diff); + } + } + + [Script] + class npc_fiendish_imp : ScriptedAI + { + public npc_fiendish_imp(Creature creature) : base(creature) { } + + public override void Reset() + { + _scheduler.Schedule(TimeSpan.FromSeconds(2), task => + { + DoCastVictim(SpellIds.Firebolt); + task.Repeat(TimeSpan.FromMilliseconds(2400)); + }); + + me.ApplySpellImmune(0, SpellImmunity.School, SpellSchoolMask.Fire, true); + } + + public override void UpdateAI(uint diff) + { + if (!UpdateVictim()) + return; + + _scheduler.Update(diff, () => DoMeleeAttackIfReady()); + } + } +} \ No newline at end of file