Files

551 lines
19 KiB
C#

// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
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 != null)
{
Creature creature = unit.ToCreature();
if (creature != null)
creature.GetAI().KilledUnit(who);
}
}
public override void SpellHit(WorldObject caster, SpellInfo spellInfo)
{
if (spellInfo.Id == SpellIds.InfernalRelay)
{
me.SetDisplayId(me.GetNativeDisplayId());
me.SetUninteractible(true);
_scheduler.Schedule(TimeSpan.FromSeconds(4), task => DoCast(me, SpellIds.Hellfire));
_scheduler.Schedule(TimeSpan.FromSeconds(170), task =>
{
Creature pMalchezaar = ObjectAccessor.GetCreature(me, Malchezaar);
if (pMalchezaar != null && pMalchezaar.IsAlive())
pMalchezaar.GetAI<boss_malchezaar>().Cleanup(me, Point);
});
}
}
public override void DamageTaken(Unit done_by, ref uint damage, DamageEffectType damageType, SpellInfo spellInfo = null)
{
if (done_by == null || 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<ObjectGuid> infernals = new();
List<Vector2> 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 != null && 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 != null && 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<Unit> 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 != null)
{
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 != null && 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 != null)
{
infernal.SetDisplayId(MiscConst.InfernalModelInvisible);
infernal.SetFaction(me.GetFaction());
if (point != Vector2.Zero)
infernal.GetAI<netherspite_infernal>().Point = point;
infernal.GetAI<netherspite_infernal>().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() != null && 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 != null)
{
axe.SetUninteractible(true);
axe.SetFaction(me.GetFaction());
axes[i] = axe.GetGUID();
if (target != null)
{
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 != null)
{
for (byte i = 0; i < 2; ++i)
{
Unit axe = Global.ObjAccessor.GetUnit(me, axes[i]);
if (axe != null)
{
if (axe.GetVictim() != null)
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 != null)
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 != null)
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();
}
void DoMeleeAttacksIfReady()
{
if (me.IsWithinMeleeRange(me.GetVictim()) && !me.IsNonMeleeSpellCast(false))
{
//Check for base attack
if (me.IsAttackReady() && me.GetVictim() != null)
{
me.AttackerStateUpdate(me.GetVictim());
me.ResetAttackTimer();
}
//Check for offhand attack
if (me.IsAttackReady(WeaponAttackType.OffAttack) && me.GetVictim() != null)
{
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);
}
}
}