using System; using System.Collections.Generic; using System.Linq; using Server; using Server.Mobiles; using Server.Multis; using Server.Spells; namespace Server.Items { public abstract class MannedCannon : Item { public virtual TimeSpan ScanDelay { get { return TimeSpan.FromSeconds(Utility.RandomMinMax(5, 10)); } } [CommandProperty(AccessLevel.GameMaster)] public Mobile Operator { get; set; } [CommandProperty(AccessLevel.GameMaster)] public Direction Facing { get { return GetFacing(); } } public DateTime NextScan { get; set; } public bool CanFireUnmanned { get; set; } public abstract CannonPower Power { get; } public abstract int Range { get; } public virtual AmmunitionType AmmoType { get { return AmmunitionType.Cannonball; } } public virtual int LateralOffset { get { return 1; } } public MannedCannon(Mobile opera, Direction facing) : base(0) { ItemID = GetID(facing, Power); Operator = opera; Movable = false; } private static int GetID(Direction facing, CannonPower power) { switch (facing) { default: case Direction.South: return BaseGalleon.CannonIDs[0][(int)power]; case Direction.West: return BaseGalleon.CannonIDs[1][(int)power]; case Direction.North: return BaseGalleon.CannonIDs[2][(int)power]; case Direction.East: return BaseGalleon.CannonIDs[3][(int)power]; } } public Direction GetFacing() { if (BaseGalleon.CannonIDs[0].Any(id => id == ItemID)) { return Direction.South; } if (BaseGalleon.CannonIDs[1].Any(id => id == ItemID)) { return Direction.West; } if (BaseGalleon.CannonIDs[2].Any(id => id == ItemID)) { return Direction.North; } return Direction.East; } public bool Scan(bool shoot) { var targets = AcquireTarget(); bool acquiredTarget = false; if (targets != null && targets.Length > 0) { foreach (var t in targets) { if (t.Entity is BaseGalleon && AmmoType != AmmunitionType.Grapeshot) { if (shoot) { DoShootEffects(); TimeSpan delay = TimeSpan.FromSeconds((double)t.Range / 10.0); Timer.DelayCall(delay, new TimerStateCallback(OnShipHit), new object[] { (BaseGalleon)t.Entity, t.Location, AmmoType }); } acquiredTarget = true; } else if (t.Entity is Mobile && AmmoType == AmmunitionType.Grapeshot) { var m = t.Entity as Mobile; if (shoot) { DoShootEffects(); TimeSpan delay = TimeSpan.FromSeconds((double)t.Range / 10.0); Timer.DelayCall(delay, new TimerStateCallback(OnMobileHit), new object[] { m, t.Location, AmmoType }); } acquiredTarget = true; } } } return acquiredTarget; } public Target[] AcquireTarget() { AmmoInfo ammo = AmmoInfo.GetAmmoInfo(AmmoType); int xOffset = 0; int yOffset = 0; int currentRange = 0; Point3D pnt = Location; Map map = Map; switch (GetFacing()) { case Direction.North: xOffset = 0; yOffset = -1; break; case Direction.South: xOffset = 0; yOffset = 1; break; case Direction.West: xOffset = -1; yOffset = 0; break; case Direction.East: xOffset = 1; yOffset = 0; break; } int xo = xOffset; int yo = yOffset; int lateralOffset = 1; while (currentRange++ <= Range) { xOffset = xo; yOffset = yo; if (LateralOffset > 1 && currentRange % LateralOffset == 0) lateralOffset++; TimeSpan delay = TimeSpan.FromSeconds((double)currentRange / 10.0); switch (AmmoType) { case AmmunitionType.Empty: break; case AmmunitionType.Cannonball: case AmmunitionType.FrostCannonball: case AmmunitionType.FlameCannonball: { Point3D newPoint = pnt; //List list = new List(); for (int i = -lateralOffset; i <= lateralOffset; i++) { if (xOffset == 0) newPoint = new Point3D(pnt.X + (xOffset + i), pnt.Y + (yOffset * currentRange), pnt.Z); else newPoint = new Point3D(pnt.X + (xOffset * currentRange), pnt.Y + (yOffset + i), pnt.Z); BaseGalleon g = FindValidBoatTarget(newPoint, map, ammo); if (g != null && g.DamageTaken < DamageLevel.Severely && g.Owner is PlayerMobile) { var target = new Target(); target.Entity = g; target.Location = newPoint; target.Range = currentRange; return new Target[] { target }; } } } break; case AmmunitionType.Grapeshot: { Point3D newPoint = pnt; List mobiles = new List(); for (int i = -lateralOffset; i <= lateralOffset; i++) { if (xOffset == 0) newPoint = new Point3D(pnt.X + (xOffset + i), pnt.Y + (yOffset * currentRange), pnt.Z); else newPoint = new Point3D(pnt.X + (xOffset * currentRange), pnt.Y + (yOffset + i), pnt.Z); foreach (Mobile m in GetTargets(newPoint, map)) { var target = new Target(); target.Entity = m; target.Location = newPoint; target.Range = currentRange; mobiles.Add(target); } if (mobiles.Count > 0 && ammo.SingleTarget) { var toHit = mobiles[Utility.Random(mobiles.Count)]; ColUtility.Free(mobiles); return new Target[] { toHit }; } } if (mobiles.Count > 0) { return mobiles.ToArray(); } } break; } } return null; } private IEnumerable GetTargets(Point3D newPoint, Map map) { if (Operator != null) { foreach (Mobile m in SpellHelper.AcquireIndirectTargets(Operator, newPoint, map, 0).OfType()) { yield return m; } yield break; } IPooledEnumerable eable = map.GetMobilesInRange(newPoint, 0); foreach (Mobile m in eable) { if (m is PlayerMobile || (m is BaseCreature && ((BaseCreature)m).GetMaster() is PlayerMobile)) { yield return m; } } } public struct Target { public IEntity Entity { get; set; } public Point3D Location { get; set; } public int Range { get; set; } } private BaseGalleon FindValidBoatTarget(Point3D newPoint, Map map, AmmoInfo info) { BaseGalleon galleon = BaseGalleon.FindGalleonAt(newPoint, map); if (galleon != null && info.RequiresSurface) { int d = galleon is BritannianShip ? 3 : 2; switch (galleon.Facing) { case Direction.North: case Direction.South: if (newPoint.X <= galleon.X - d || newPoint.X >= galleon.X + d) return null; break; case Direction.East: case Direction.West: if (newPoint.Y <= galleon.Y - d || newPoint.Y >= galleon.Y + d) return null; break; } StaticTile[] tiles = map.Tiles.GetStaticTiles(newPoint.X, newPoint.Y, true); foreach (StaticTile tile in tiles) { ItemData id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; bool isWater = (tile.ID >= 0x1796 && tile.ID <= 0x17B2); if (!isWater && id.Surface && !id.Impassable) { return galleon; } } return null; } return galleon; } public virtual void OnShipHit(object obj) { object[] list = (object[])obj; BaseBoat target = list[0] as BaseBoat; Point3D pnt = (Point3D)list[1]; var ammoInfo = AmmoInfo.GetAmmoInfo((AmmunitionType)list[2]); if (ammoInfo != null && target != null) { int damage = (Utility.RandomMinMax(ammoInfo.MinDamage, ammoInfo.MaxDamage)); damage /= 7; target.OnTakenDamage(Operator, damage); int z = target.ZSurface; if (target.TillerMan != null && target.TillerMan is IEntity) { z = ((IEntity)target.TillerMan).Z; } Direction d = Utility.GetDirection(this, pnt); int xOffset = 0; int yOffset = 0; Point3D hit = pnt; if (!ammoInfo.RequiresSurface) { switch (d) { default: case Direction.North: xOffset = Utility.RandomMinMax(-1, 1); yOffset = Utility.RandomMinMax(-2, 0); hit = new Point3D(pnt.X + xOffset, pnt.Y + yOffset, z); break; case Direction.South: xOffset = Utility.RandomMinMax(-1, 1); yOffset = Utility.RandomMinMax(0, 2); hit = new Point3D(pnt.X + xOffset, pnt.Y + yOffset, z); break; case Direction.East: xOffset = Utility.RandomMinMax(0, 2); yOffset = Utility.RandomMinMax(-1, 1); hit = new Point3D(pnt.X + xOffset, pnt.Y + yOffset, z); break; case Direction.West: xOffset = Utility.RandomMinMax(-2, 0); yOffset = Utility.RandomMinMax(-1, 1); hit = new Point3D(pnt.X + xOffset, pnt.Y + yOffset, z); break; } } Effects.SendLocationEffect(hit, target.Map, Utility.RandomBool() ? 14000 : 14013, 15, 10); Effects.PlaySound(hit, target.Map, 0x207); if (Operator != null && (!Operator.Deleted || CanFireUnmanned)) { Mobile victim = target.Owner; if (victim != null && target.Contains(victim) && Operator.CanBeHarmful(victim, false)) { Operator.DoHarmful(victim); } else { List candidates = new List(); SecurityLevel highest = SecurityLevel.Passenger; foreach (var mob in target.GetMobilesOnBoard().OfType().Where(pm => Operator.CanBeHarmful(pm, false))) { if (target is BaseGalleon && ((BaseGalleon)target).GetSecurityLevel(mob) > highest) { candidates.Insert(0, mob); } else { candidates.Add(mob); } } if (candidates.Count > 0) { Operator.DoHarmful(candidates[0]); } else if (victim != null && Operator.IsHarmfulCriminal(victim)) { Operator.CriminalAction(false); } ColUtility.Free(candidates); } } } } public virtual void OnMobileHit(object obj) { object[] objects = (object[])obj; Mobile toHit = objects[0] as Mobile; Point3D pnt = (Point3D)objects[1]; AmmoInfo ammoInfo = AmmoInfo.GetAmmoInfo((AmmunitionType)objects[2]); if (ammoInfo != null) { int damage = (int)(Utility.RandomMinMax(ammoInfo.MinDamage, ammoInfo.MaxDamage)); if (Operator != null) { Operator.DoHarmful(toHit); } AOS.Damage(toHit, Operator, damage, ammoInfo.PhysicalDamage, ammoInfo.FireDamage, ammoInfo.ColdDamage, ammoInfo.PoisonDamage, ammoInfo.EnergyDamage); Effects.SendLocationEffect(toHit.Location, toHit.Map, Utility.RandomBool() ? 14000 : 14013, 15, 10); Effects.PlaySound(toHit.Location, toHit.Map, 0x207); } } public void DoShootEffects() { Point3D p = Location; Map map = Map; p.Z -= 3; switch (Facing) { case Direction.North: p.Y--; break; case Direction.East: p.X++; break; case Direction.South: p.Y++; break; case Direction.West: p.X--; break; } Effects.SendLocationEffect(p, map, 14120, 15, 10); Effects.PlaySound(p, map, 0x664); } public MannedCannon(Serial serial) : base(serial) { } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write((int)1); writer.Write(Operator); writer.Write(CanFireUnmanned); } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); switch (version) { case 1: Operator = reader.ReadMobile(); CanFireUnmanned = reader.ReadBool(); break; } } } public class MannedCulverin : MannedCannon { public override int Range { get { return 10; } } public override CannonPower Power { get { return CannonPower.Light; } } public MannedCulverin(Mobile oper, Direction facing) : base(oper, facing) { } public MannedCulverin(Serial serial) : base(serial) { } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write((int)0); } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); } } public class MannedCarronade : MannedCannon { public override int Range { get { return 10; } } public override CannonPower Power { get { return CannonPower.Heavy; } } public MannedCarronade(Mobile oper, Direction facing) : base(oper, facing) { } public MannedCarronade(Serial serial) : base(serial) { } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write((int)0); } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); } } public class MannedBlundercannon : MannedCannon { public override int LabelNumber { get { return 1158942; } } // Blundercannon public override int Range { get { return 12; } } public override CannonPower Power { get { return CannonPower.Massive; } } public MannedBlundercannon(Mobile oper, Direction facing) : base(oper, facing) { } public MannedBlundercannon(Serial serial) : base(serial) { } public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write((int)0); } public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); } } }