535 lines
18 KiB
C#
535 lines
18 KiB
C#
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<IEntity> list = new List<IEntity>();
|
|
|
|
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<Target> mobiles = new List<Target>();
|
|
|
|
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<Mobile> GetTargets(Point3D newPoint, Map map)
|
|
{
|
|
if (Operator != null)
|
|
{
|
|
foreach (Mobile m in SpellHelper.AcquireIndirectTargets(Operator, newPoint, map, 0).OfType<Mobile>())
|
|
{
|
|
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<Mobile> candidates = new List<Mobile>();
|
|
SecurityLevel highest = SecurityLevel.Passenger;
|
|
|
|
foreach (var mob in target.GetMobilesOnBoard().OfType<PlayerMobile>().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();
|
|
}
|
|
}
|
|
}
|