464 lines
14 KiB
C#
464 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
using Server.Items;
|
|
using Server.Network;
|
|
using Server.Spells;
|
|
|
|
namespace Server.Mobiles
|
|
{
|
|
[CorpseName("a stygian dragon corpse")]
|
|
public class StygianDragon : BaseSABoss
|
|
{
|
|
private DateTime m_Delay;
|
|
|
|
[Constructable]
|
|
public StygianDragon()
|
|
: base(AIType.AI_Mage, FightMode.Closest, 10, 1, 0.3, 0.5)
|
|
{
|
|
Name = "Stygian Dragon";
|
|
Body = 826;
|
|
BaseSoundID = 362;
|
|
|
|
SetStr(702);
|
|
SetDex(250);
|
|
SetInt(180);
|
|
|
|
SetHits(30000);
|
|
SetStam(431);
|
|
SetMana(180);
|
|
|
|
SetDamage(33, 55);
|
|
|
|
SetDamageType(ResistanceType.Physical, 25);
|
|
SetDamageType(ResistanceType.Fire, 50);
|
|
SetDamageType(ResistanceType.Energy, 25);
|
|
|
|
SetResistance(ResistanceType.Physical, 80, 90);
|
|
SetResistance(ResistanceType.Fire, 80, 90);
|
|
SetResistance(ResistanceType.Cold, 60, 70);
|
|
SetResistance(ResistanceType.Poison, 80, 90);
|
|
SetResistance(ResistanceType.Energy, 80, 90);
|
|
|
|
SetSkill(SkillName.Anatomy, 100.0);
|
|
SetSkill(SkillName.MagicResist, 150.0, 155.0);
|
|
SetSkill(SkillName.Tactics, 120.7, 125.0);
|
|
SetSkill(SkillName.Wrestling, 115.0, 117.7);
|
|
|
|
Fame = 15000;
|
|
Karma = -15000;
|
|
|
|
VirtualArmor = 60;
|
|
|
|
Tamable = false;
|
|
|
|
SetWeaponAbility(WeaponAbility.Bladeweave);
|
|
SetWeaponAbility(WeaponAbility.TalonStrike);
|
|
SetSpecialAbility(SpecialAbility.DragonBreath);
|
|
}
|
|
|
|
public StygianDragon(Serial serial)
|
|
: base(serial)
|
|
{
|
|
}
|
|
|
|
public override Type[] UniqueSAList
|
|
{
|
|
get
|
|
{
|
|
return new Type[]
|
|
{
|
|
typeof(BurningAmber), typeof(DraconisWrath), typeof(DragonHideShield), typeof(FallenMysticsSpellbook),
|
|
typeof(LifeSyphon), typeof(GargishSignOfOrder), typeof(HumanSignOfOrder), typeof(VampiricEssence)
|
|
};
|
|
}
|
|
}
|
|
public override Type[] SharedSAList
|
|
{
|
|
get
|
|
{
|
|
return new Type[]
|
|
{
|
|
typeof(AxesOfFury), typeof(SummonersKilt), typeof(GiantSteps),
|
|
typeof(TokenOfHolyFavor)
|
|
};
|
|
}
|
|
}
|
|
|
|
public override bool AlwaysMurderer { get { return true; } }
|
|
public override bool Unprovokable { get { return false; } }
|
|
public override bool BardImmune { get { return false; } }
|
|
public override bool AutoDispel { get { return !Controlled; } }
|
|
public override int Meat { get { return 19; } }
|
|
public override int Hides { get { return 30; } }
|
|
public override HideType HideType { get { return HideType.Barbed; } }
|
|
public override int Scales { get { return 7; } }
|
|
public override ScaleType ScaleType { get { return (Body == 12 ? ScaleType.Yellow : ScaleType.Red); } }
|
|
public override int DragonBlood { get { return 48; } }
|
|
public override bool CanFlee { get { return false; } }
|
|
|
|
public override void GenerateLoot()
|
|
{
|
|
AddLoot(LootPack.AosSuperBoss, 4);
|
|
AddLoot(LootPack.Gems, 8);
|
|
}
|
|
|
|
public override void OnThink()
|
|
{
|
|
base.OnThink();
|
|
|
|
if (Combatant == null || !(Combatant is Mobile))
|
|
return;
|
|
|
|
if (DateTime.UtcNow > m_Delay)
|
|
{
|
|
switch (Utility.Random(3))
|
|
{
|
|
case 0: CrimsonMeteor(this, (Mobile)Combatant, 70, 125); break;
|
|
case 1: DoStygianFireball(); break;
|
|
case 2: DoFireColumn(); break;
|
|
}
|
|
|
|
m_Delay = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(30, 60));
|
|
}
|
|
}
|
|
|
|
public override void OnDeath(Container c)
|
|
{
|
|
base.OnDeath(c);
|
|
|
|
c.DropItem(new StygianDragonHead());
|
|
|
|
if ( Paragon.ChestChance > Utility.RandomDouble() )
|
|
c.DropItem( new ParagonChest( Name, 5 ) );
|
|
}
|
|
|
|
public override void Serialize(GenericWriter writer)
|
|
{
|
|
base.Serialize(writer);
|
|
writer.Write((int)1);
|
|
}
|
|
|
|
public override void Deserialize(GenericReader reader)
|
|
{
|
|
base.Deserialize(reader);
|
|
int version = reader.ReadInt();
|
|
}
|
|
|
|
#region Crimson Meteor
|
|
public static void CrimsonMeteor(Mobile owner, Mobile combatant, int minDamage, int maxDamage)
|
|
{
|
|
if (!combatant.Alive || combatant.Map == null || combatant.Map == Map.Internal)
|
|
return;
|
|
|
|
new CrimsonMeteorTimer(owner, combatant.Location, minDamage, maxDamage).Start();
|
|
}
|
|
|
|
public class CrimsonMeteorTimer : Timer
|
|
{
|
|
private Mobile m_From;
|
|
private Map m_Map;
|
|
private int m_Count;
|
|
private int m_MaxCount;
|
|
private bool m_DoneDamage;
|
|
private Point3D m_LastTarget;
|
|
private Rectangle2D m_ShowerArea;
|
|
private List<Mobile> m_ToDamage;
|
|
|
|
private int m_MinDamage, m_MaxDamage;
|
|
|
|
public CrimsonMeteorTimer(Mobile from, Point3D loc, int min, int max)
|
|
: base(TimeSpan.FromMilliseconds(250.0), TimeSpan.FromMilliseconds(250.0))
|
|
{
|
|
m_From = from;
|
|
m_Map = from.Map;
|
|
m_Count = 0;
|
|
m_MaxCount = 25; // in ticks
|
|
m_LastTarget = loc;
|
|
m_DoneDamage = false;
|
|
m_ShowerArea = new Rectangle2D(loc.X - 2, loc.Y - 2, 4, 4);
|
|
|
|
m_MinDamage = min;
|
|
m_MaxDamage = max;
|
|
|
|
m_ToDamage = new List<Mobile>();
|
|
|
|
IPooledEnumerable eable = m_Map.GetMobilesInBounds(m_ShowerArea);
|
|
|
|
foreach (Mobile m in eable)
|
|
{
|
|
if (m != from && m_From.CanBeHarmful(m))
|
|
m_ToDamage.Add(m);
|
|
}
|
|
|
|
eable.Free();
|
|
}
|
|
|
|
protected override void OnTick()
|
|
{
|
|
if (m_From == null || m_From.Deleted || m_Map == null || m_Map == Map.Internal)
|
|
{
|
|
Stop();
|
|
return;
|
|
}
|
|
|
|
if (0.33 > Utility.RandomDouble())
|
|
{
|
|
var field = new FireField(m_From, 25, Utility.RandomBool());
|
|
field.MoveToWorld(m_LastTarget, m_Map);
|
|
}
|
|
|
|
Point3D start = new Point3D();
|
|
Point3D finish = new Point3D();
|
|
|
|
finish.X = m_ShowerArea.X + Utility.Random(m_ShowerArea.Width);
|
|
finish.Y = m_ShowerArea.Y + Utility.Random(m_ShowerArea.Height);
|
|
finish.Z = m_From.Z;
|
|
|
|
SpellHelper.AdjustField(ref finish, m_Map, 16, false);
|
|
|
|
//objects move from upper right/right to left as per OSI
|
|
start.X = finish.X + Utility.RandomMinMax(-4, 4);
|
|
start.Y = finish.Y - 15;
|
|
start.Z = finish.Z + 50;
|
|
|
|
Effects.SendMovingParticles(
|
|
new Entity(Serial.Zero, start, m_Map),
|
|
new Entity(Serial.Zero, finish, m_Map),
|
|
0x36D4, 15, 0, false, false, 0, 0, 9502, 1, 0, (EffectLayer)255, 0x100);
|
|
|
|
Effects.PlaySound(finish, m_Map, 0x11D);
|
|
|
|
m_LastTarget = finish;
|
|
m_Count++;
|
|
|
|
if (m_Count >= m_MaxCount / 2 && !m_DoneDamage)
|
|
{
|
|
if (m_ToDamage != null && m_ToDamage.Count > 0)
|
|
{
|
|
int damage;
|
|
|
|
foreach (Mobile mob in m_ToDamage)
|
|
{
|
|
damage = Utility.RandomMinMax(m_MinDamage, m_MaxDamage);
|
|
|
|
m_From.DoHarmful(mob);
|
|
AOS.Damage(mob, m_From, damage, 0, 100, 0, 0, 0);
|
|
|
|
mob.FixedParticles(0x36BD, 1, 15, 9502, 0, 3, (EffectLayer)255);
|
|
}
|
|
}
|
|
|
|
m_DoneDamage = true;
|
|
return;
|
|
}
|
|
|
|
if (m_Count >= m_MaxCount)
|
|
Stop();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Fire Column
|
|
public void DoFireColumn()
|
|
{
|
|
var map = Map;
|
|
|
|
if (map == null)
|
|
return;
|
|
|
|
Direction columnDir = Utility.GetDirection(this, Combatant);
|
|
|
|
Packet flash = ScreenLightFlash.Instance;
|
|
IPooledEnumerable e = Map.GetClientsInRange(Location, Core.GlobalUpdateRange);
|
|
|
|
foreach (NetState ns in e)
|
|
{
|
|
if (ns.Mobile != null)
|
|
ns.Mobile.Send(flash);
|
|
}
|
|
|
|
e.Free();
|
|
|
|
int x = X;
|
|
int y = Y;
|
|
bool south = columnDir == Direction.East || columnDir == Direction.West;
|
|
|
|
Movement.Movement.Offset(columnDir, ref x, ref y);
|
|
Point3D p = new Point3D(x, y, Z);
|
|
SpellHelper.AdjustField(ref p, map, 16, false);
|
|
|
|
var fire = new FireField(this, Utility.RandomMinMax(25, 32), south);
|
|
fire.MoveToWorld(p, map);
|
|
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
Movement.Movement.Offset(columnDir, ref x, ref y);
|
|
|
|
p = new Point3D(x, y, Z);
|
|
SpellHelper.AdjustField(ref p, map, 16, false);
|
|
|
|
fire = new FireField(this, Utility.RandomMinMax(25, 32), south);
|
|
fire.MoveToWorld(p, map);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Fire Field
|
|
public class FireField : Item
|
|
{
|
|
private Mobile m_Owner;
|
|
private Timer m_Timer;
|
|
private DateTime m_Destroy;
|
|
|
|
[Constructable]
|
|
public FireField(Mobile owner, int duration, bool south)
|
|
: base(GetItemID(south))
|
|
{
|
|
Movable = false;
|
|
m_Destroy = DateTime.UtcNow + TimeSpan.FromSeconds(duration);
|
|
|
|
m_Owner = owner;
|
|
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), new TimerCallback(OnTick));
|
|
}
|
|
|
|
private static int GetItemID(bool south)
|
|
{
|
|
if (south)
|
|
return 0x398C;
|
|
else
|
|
return 0x3996;
|
|
}
|
|
|
|
public override void OnAfterDelete()
|
|
{
|
|
if (m_Timer != null)
|
|
m_Timer.Stop();
|
|
}
|
|
|
|
private void OnTick()
|
|
{
|
|
if (DateTime.UtcNow > m_Destroy)
|
|
{
|
|
Delete();
|
|
}
|
|
else
|
|
{
|
|
IPooledEnumerable eable = GetMobilesInRange(0);
|
|
List<Mobile> list = new List<Mobile>();
|
|
|
|
foreach (Mobile m in eable)
|
|
{
|
|
if (m == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (m_Owner == null || CanTargetMob(m))
|
|
{
|
|
list.Add(m);
|
|
}
|
|
}
|
|
|
|
eable.Free();
|
|
|
|
foreach (var mob in list)
|
|
{
|
|
DealDamage(mob);
|
|
}
|
|
|
|
ColUtility.Free(list);
|
|
}
|
|
}
|
|
|
|
public override bool OnMoveOver(Mobile m)
|
|
{
|
|
DealDamage(m);
|
|
|
|
return true;
|
|
}
|
|
|
|
public void DealDamage(Mobile m)
|
|
{
|
|
if (m != m_Owner && (m_Owner == null || CanTargetMob(m)))
|
|
AOS.Damage(m, m_Owner, Utility.RandomMinMax(2, 4), 0, 100, 0, 0, 0);
|
|
}
|
|
|
|
public bool CanTargetMob(Mobile m)
|
|
{
|
|
return m != m_Owner && m_Owner.CanBeHarmful(m, false) && (m is PlayerMobile || (m is BaseCreature && ((BaseCreature)m).GetMaster() is PlayerMobile));
|
|
}
|
|
|
|
public FireField(Serial serial)
|
|
: base(serial)
|
|
{
|
|
}
|
|
|
|
public override void Serialize(GenericWriter writer)
|
|
{
|
|
// Unsaved.
|
|
}
|
|
|
|
public override void Deserialize(GenericReader reader)
|
|
{
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Stygian Fireball
|
|
public void DoStygianFireball()
|
|
{
|
|
if (!(Combatant is Mobile) || !InRange(Combatant.Location, 10))
|
|
return;
|
|
|
|
new StygianFireballTimer(this, (Mobile)Combatant);
|
|
PlaySound(0x1F3);
|
|
}
|
|
|
|
private class StygianFireballTimer : Timer
|
|
{
|
|
private StygianDragon m_Dragon;
|
|
private Mobile m_Combatant;
|
|
private int m_Ticks;
|
|
|
|
public StygianFireballTimer(StygianDragon dragon, Mobile combatant)
|
|
: base(TimeSpan.FromMilliseconds(200), TimeSpan.FromMilliseconds(200))
|
|
{
|
|
m_Dragon = dragon;
|
|
m_Combatant = combatant;
|
|
m_Ticks = 0;
|
|
Start();
|
|
}
|
|
|
|
protected override void OnTick()
|
|
{
|
|
m_Dragon.MovingParticles(m_Combatant, 0x46E6, 7, 0, false, true, 1265, 0, 9502, 4019, 0x026, 0);
|
|
|
|
if (m_Ticks >= 10)
|
|
{
|
|
int damage = Utility.RandomMinMax(120, 150);
|
|
|
|
Timer.DelayCall(TimeSpan.FromSeconds(.20), new TimerStateCallback(DoDamage_Callback), new object[] { m_Combatant, m_Dragon, damage });
|
|
|
|
Stop();
|
|
}
|
|
|
|
m_Ticks++;
|
|
}
|
|
|
|
public void DoDamage_Callback(object state)
|
|
{
|
|
object[] obj = (object[])state;
|
|
|
|
Mobile c = (Mobile)obj[0];
|
|
Mobile d = (Mobile)obj[1];
|
|
int dam = (int)obj[2];
|
|
|
|
d.DoHarmful(c);
|
|
AOS.Damage(c, d, dam, false, 0, 0, 0, 0, 0, 100, 0, false);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|