using System.Collections.Generic;
using System;
using System.Linq;

using Server;
using Server.Mobiles;
using Server.ContextMenus;
using Server.Gumps;
using Server.Multis;
using Server.Network;

namespace Server.Items
{
    public enum CannonAction
    {
        None,
        Stop,
        Fail,
        Finish
    }

    public abstract class BaseShipCannon : Container, IShipCannon
    {
        private int m_Hits;

        [CommandProperty(AccessLevel.GameMaster)]
        public int Hits { get { return m_Hits; } set { m_Hits = value; InvalidateDamageState(); } }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool Processing { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public CannonAction Prepered { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public CannonAction Charged { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public CannonAction Loaded { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public CannonAction Primed { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public AmmunitionType AmmoType { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public BaseGalleon Galleon { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public ShipPosition Position { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public DamageLevel DamageState { get; set; }

        [CommandProperty(AccessLevel.GameMaster)]
        public Direction Facing { get { return GetFacing(); } }

        public virtual bool HitMultipleMobs { get { return false; } }

        public abstract ShipCannonDeed GetDeed { get; }
        public abstract int Range { get; }
        public abstract CannonPower Power { get; }

        public virtual int MaxHits { get { return 100; } }
        public virtual TimeSpan ActionTime { get { return TimeSpan.FromSeconds(1.5); } }

        [CommandProperty(AccessLevel.GameMaster)]
        public bool CanLight => Prepered == CannonAction.Finish && Loaded == CannonAction.Finish && Charged == CannonAction.Finish && Primed == CannonAction.Finish;

        [CommandProperty(AccessLevel.GameMaster)]
        public bool Empty { get { return !CanLight && Items.Count == 0; } }

        [CommandProperty(AccessLevel.GameMaster)]
        public double Durability { get { return ((double)m_Hits / (double)MaxHits) * 100.0; } }

        public override bool ForceShowProperties { get { return true; } }
        public override int DefaultGumpID { get { return 0x9CE7; } }
        public override bool DisplaysContent { get { return false; } }
        public override int DefaultMaxWeight { get { return 300; } }

        public List<Mobile> Viewing { get; set; } = new List<Mobile>();

        public BaseShipCannon(BaseGalleon galleon)
            : base(0x2198)
        {
            Movable = false;
            Galleon = galleon;
            AmmoType = AmmunitionType.Empty;
            m_Hits = MaxHits;
            DamageState = DamageLevel.Pristine;

            LiftOverride = true;
            MaxItems = 3;
        }

        public void TryPrep(Mobile m)
        {
            Processing = true;

            m.Animate(AnimationType.Attack, 3);

            if (Prepered == CannonAction.Stop)
                AddAction(m, 1149679); // Preparation resumed.
            else
                AddAction(m, 1149641); // Preparing to fire...

            Timer.DelayCall(TimeSpan.FromSeconds(4.0), () =>
            {
                bool ramrod = m.Backpack.GetAmount(typeof(Ramrod)) >= 1;

                if (ramrod)
                {
                    if (Galleon.Contains(m))
                    {
                        Prepered = CannonAction.Finish;
                        TryCharge(m);
                        ResendGump(m);
                    }
                    else
                    {
                        Processing = false;
                        Prepered = CannonAction.Stop;
                        AddAction(m, 1149675); // Preparation stopped.
                        ResendGump(m);
                    }
                }
                else
                {
                    Processing = false;
                    AddAction(m, 1149660); // You need a ramrod.
                    m.SendLocalizedMessage(1149660); // You need a ramrod.
                    ResendGump(m);
                }
            });
        }

        public void TryCharge(Mobile m)
        {
            Processing = true;

            m.Animate(AnimationType.Attack, 3);

            if (Charged == CannonAction.Stop)
                AddAction(m, 1149680); // Charging resumed.
            else
                AddAction(m, 1149644); // Charging started.

            Timer.DelayCall(ActionTime, () =>
            {
                var charge = FindItemByType<PowderCharge>();

                if (charge != null)
                {
                    if (Galleon.Contains(m))
                    {
                        charge.Consume();
                        Charged = CannonAction.Finish;
                        AddAction(m, 1149646); // Charging finished.
                        InvalidateProperties();

                        TryLoad(m);
                        ResendGump(m);
                    }
                    else
                    {
                        Processing = false;
                        Charged = CannonAction.Stop;
                        AddAction(m, 1149676); // Charging stopped.
                        DoAreaMessage(1116052, 10, null); // The effort to charge the cannon has paused.
                        ResendGump(m);
                    }
                }
                else
                {
                    Processing = false;
                    m.SendLocalizedMessage(1116014); // The magazine does not have a powder charge to charge this cannon with.

                    AddAction(m, 1149665); // Need powder charge.
                    ResendGump(m);
                }
            });
        }

        public void TryLoad(Mobile m)
        {
            Processing = true;

            m.Animate(AnimationType.Attack, 3);

            if (Loaded == CannonAction.Stop)
                AddAction(m, 1149681); // Loading resumed.
            else
                AddAction(m, 1149647); // Loading started.

            AmmoType = AmmunitionType.Empty;

            Timer.DelayCall(ActionTime, () =>
            {
                ICannonAmmo ammo = null;

                var cannon = FindItemByType<Cannonball>();
                var grapeshot = FindItemByType<Grapeshot>();

                if (cannon != null)
                {
                    ammo = cannon;
                }
                else if (grapeshot != null)
                {
                    ammo = grapeshot;
                }
                else
                {
                    cannon = m.Backpack.FindItemByType<Cannonball>();
                    grapeshot = m.Backpack.FindItemByType<Grapeshot>();

                    if (cannon != null)
                    {
                        ammo = cannon;
                    }
                    else if (grapeshot != null)
                    {
                        ammo = grapeshot;
                    }
                }

                if (ammo != null && ammo is Item)
                {
                    if (Galleon.Contains(m))
                    {
                        Loaded = CannonAction.Finish;
                        AmmoType = ammo.AmmoType;
                        ((Item)ammo).Consume();
                        InvalidateProperties();
                        AddAction(m, 1149649); // Loading finished.

                        TryPrime(m);
                        ResendGump(m);
                    }
                    else
                    {
                        Processing = false;
                        Loaded = CannonAction.Stop;
                        DoAreaMessage(1116053, 10, null); // The effort to load the cannon has paused.
                        AddAction(m, 1149677); // Loading stopped.
                        ResendGump(m);
                    }
                }
                else
                {
                    Processing = false;
                    Loaded = CannonAction.Fail;
                    m.SendLocalizedMessage(1158933); // The magazine does not have ammo to load this cannon with.
                    AddAction(m, 1158933); // Need ammo.
                    ResendGump(m);
                }
            });
        }

        public void TryPrime(Mobile m)
        {
            Processing = true;

            m.Animate(AnimationType.Attack, 3);

            if (Primed == CannonAction.Stop)
                AddAction(m, 1149682); // Priming resumed.
            else
                AddAction(m, 1149650); // Priming started.

            Timer.DelayCall(ActionTime, () =>
            {
                var fuse = FindItemByType<FuseCord>();

                if (fuse != null)
                {
                    if (Galleon.Contains(m))
                    {
                        fuse.Consume();
                        Primed = CannonAction.Finish;
                        InvalidateProperties();

                        AddAction(m, 1149652); // Ready to fire.
                        ResendGump(m);
                    }
                    else
                    {
                        Primed = CannonAction.Stop;
                        DoAreaMessage(1116053, 10, null); // The effort to load the cannon has paused.
                        AddAction(m, 1149678); // Priming stopped.
                        ResendGump(m);
                    }
                }
                else
                {
                    Primed = CannonAction.Fail;
                    AddAction(m, 1149661); // You need fuse.
                    m.SendLocalizedMessage(1158939); // The magazine does not have a fuse to prime the cannon with.
                    ResendGump(m);
                }

                Processing = false;
            });
        }

        public void Unload(Mobile m)
        {
            Item item;

            if (Primed == CannonAction.Finish)
            {
                item = new FuseCord();

                if (!TryDropItem(m, item, false))
                {
                    m.AddToBackpack(item);
                }

                AddAction(m, 1149686); // Fuse removed.
                Primed = CannonAction.None;
            }

            if (Loaded == CannonAction.Finish)
            {
                switch (AmmoType)
                {
                    default: item = null; break;
                    case AmmunitionType.Grapeshot: item = new Cannonball(); break;
                    case AmmunitionType.Cannonball: item = new Cannonball(); break;
                    case AmmunitionType.FrostCannonball: item = new FrostCannonball(); break;
                    case AmmunitionType.FlameCannonball: item = new FlameCannonball(); break;
                }

                if (item != null)
                {
                    AddAction(m, 1149685); // Ammunition removed.

                    if (!TryDropItem(m, item, false))
                    {
                        m.AddToBackpack(item);
                    }
                }

                AmmoType = AmmunitionType.Empty;
                Loaded = CannonAction.None;
            }

            if (Charged == CannonAction.Finish)
            {
                item = new PowderCharge();

                if (!TryDropItem(m, item, false))
                {
                    m.AddToBackpack(item);
                }

                AddAction(m, 1149684); // Powder charge removed.
                Charged = CannonAction.None;
            }
        }

        public override bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight)
        {
            if (!CheckType(item))
            {
                if (message)
                {
                    m.SendLocalizedMessage(1074836); // The container can not hold that type of object.
                }

                return false;
            }
            else if (Items.Count == 3)
            {
                if (message)
                {
                    m.SendLocalizedMessage(1080017); // That container cannot hold more items.
                }

                return false;
            }

            return base.CheckHold(m, item, message, checkItems, plusItems, plusWeight);
        }

        private bool CheckType(Item item)
        {
            return _Types.Any(t => t == item.GetType() || item.GetType().IsSubclassOf(t));
        }

        private Type[] _Types =
        {
            typeof(Cannonball), typeof(Grapeshot), typeof(PowderCharge), typeof(FuseCord)
        };

        public override void OnDoubleClickDead(Mobile m)
        {
            OnDoubleClick(m);
        }

        public override void OnDoubleClick(Mobile from)
        {
            if (Galleon.GetSecurityLevel(from) >= SecurityLevel.Crewman)
            {
                base.OnDoubleClick(from);

                if (!Viewing.Contains(from))
                {
                    Viewing.Add(from);
                }

                AddAction(from, from.Alive && Galleon.Contains(from) && from.InRange(Location, 2) ? 1149653 : 1149654); // You are now operating the cannon. : You are too far away.
                ResendGump(from, TimeSpan.FromMilliseconds(500));
            }
            else
            {
                from.Say(1010436); // You do not have permission to do this.
            }
        }

        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 void DoAreaMessage(int cliloc, int range, Mobile from)
        {
            if (from == null)
                return;

            Galleon.GetEntitiesOnBoard().OfType<PlayerMobile>().Where(x => x != from && Galleon.GetSecurityLevel(x) > SecurityLevel.Denied)
                .ToList().ForEach(y =>
                {
                    if (from != null)
                        y.SendLocalizedMessage(cliloc, from.Name);
                    else
                        y.SendLocalizedMessage(cliloc);
                });
        }

        public void TryLightFuse(Mobile from)
        {
            if (from == null)
                return;

            Container pack = from.Backpack;

            if (pack != null)
            {
                Item[] items = pack.FindItemsByType(typeof(Matches));

                if (items != null)
                {
                    foreach (Item item in items)
                    {
                        if (item is Matches && ((Matches)item).IsLight)
                        {
                            LightFuse(from);
                            return;
                        }
                    }
                }

                items = pack.FindItemsByType(typeof(Torch));

                if (items != null)
                {
                    foreach (Item item in items)
                    {
                        if (item is Torch && ((Torch)item).Burning)
                        {
                            LightFuse(from);
                            return;
                        }
                    }
                }
            }

            Item i = from.FindItemOnLayer(Layer.TwoHanded);

            if (i != null && i is Matches && ((Matches)i).IsLight)
            {
                LightFuse(from);
                return;
            }
            else if (i != null && i is Torch && ((Torch)i).Burning)
            {
                LightFuse(from);
                return;
            }

            AddAction(from, 1149669); // Need a lighted fire source.
            from.SendLocalizedMessage(1149669); // Need a lighted fire source.
        }

        public void LightFuse(Mobile from)
        {
            AddAction(from, 1149683); // The fuse is lit!
            Timer.DelayCall(TimeSpan.FromSeconds(1.5), Shoot, from);
        }

        public virtual void Shoot(object cannoneer)
        {
            AmmoInfo ammo = AmmoInfo.GetAmmoInfo(AmmoType);

            if (ammo == null)
                return;

            Mobile shooter = null;

            if (cannoneer is Mobile)
                shooter = (Mobile)cannoneer;

            if (shooter != null && shooter.Player)
                m_Hits -= Utility.RandomMinMax(0, 4);

            DoShootEffects();
            AddAction(shooter, 1149691); // Fired successfully.

            int xOffset = 0; int yOffset = 0;
            int currentRange = 0;
            Point3D pnt = Location;
            Map map = Map;
            Direction d = GetFacing();

            switch (d)
            {
                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;

            int latDist = ammo.LateralOffset;

            int range = Range;
            bool hit = false;

            while (currentRange++ <= range)
            {
                xOffset = xo;
                yOffset = yo;

                if (currentRange % latDist == 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>();
                            List<IDamageable> damageables = new List<IDamageable>();

                            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);

                                BaseBoat b = FindValidBoatTarget(newPoint, map, ammo);

                                if (b != null && b != Galleon && b.IsEnemy(Galleon))
                                    list.Add(b);

                                damageables.AddRange(FindDamageables(shooter, newPoint, map, false, false, false, true, true));
                            }

                            foreach (var m in damageables)
                            {
                                list.Add(m);
                            }

                            if (list.Count > 0)
                            {
                                IEntity toHit = list[Utility.Random(list.Count)];

                                if (toHit is Mobile)
                                {
                                    Timer.DelayCall(delay, new TimerStateCallback(OnMobileHit), new object[] { (Mobile)toHit, newPoint, ammo, shooter });
                                    hit = true;
                                }
                                else if (toHit is BaseBoat)
                                {
                                    Timer.DelayCall(delay, new TimerStateCallback(OnShipHit), new object[] { (BaseBoat)toHit, newPoint, ammo, shooter });
                                    hit = true;
                                }
                                else if (toHit is DamageableItem)
                                {
                                    Timer.DelayCall(delay, new TimerStateCallback(OnDamageableItemHit), new object[] { (DamageableItem)toHit, newPoint, ammo, shooter });
                                    hit = true;
                                }
                            }
                        }
                        break;
                    case AmmunitionType.Grapeshot:
                        {
                            Point3D newPoint = pnt;
                            List<IEntity> list = new List<IEntity>();
                            List<IDamageable> damageables = new List<IDamageable>();

                            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);

                                BaseBoat b = FindValidBoatTarget(newPoint, map, ammo);

                                if (b != null && b != Galleon && b.IsEnemy(Galleon))
                                    list.Add(b);

                                damageables.AddRange(FindDamageables(shooter, newPoint, map, true, true, false, true, true));
                            }

                            foreach (var m in damageables)
                            {
                                list.Add(m);
                            }

                            if (list.Count > 0)
                            {
                                IEntity toHit = list[Utility.Random(list.Count)];

                                if (toHit is Mobile)
                                {
                                    Timer.DelayCall(delay, new TimerStateCallback(OnMobileHit), new object[] { (Mobile)toHit, newPoint, ammo, shooter });
                                    hit = true;
                                }
                                else if (toHit is BaseBoat)
                                {
                                    Timer.DelayCall(delay, new TimerStateCallback(OnShipHit), new object[] { (BaseBoat)toHit, newPoint, ammo, shooter });
                                    hit = true;
                                }
                                else if (toHit is DamageableItem)
                                {
                                    Timer.DelayCall(delay, new TimerStateCallback(OnDamageableItemHit), new object[] { (DamageableItem)toHit, newPoint, ammo, shooter });
                                    hit = true;
                                }
                            }
                        }
                        break;
                }

                if (hit && ammo.SingleTarget)
                    break;
            }

            ClearCannon();
            InvalidateDamageState();

            if (shooter != null)
            {
                ResendGump(shooter);
            }
        }

        private BaseBoat FindValidBoatTarget(Point3D newPoint, Map map, AmmoInfo info)
        {
            BaseBoat boat = BaseBoat.FindBoatAt(newPoint, map);

            if (boat != null && info.RequiresSurface)
            {
                int d = boat is BritannianShip ? 3 : 2;
                switch (boat.Facing)
                {
                    case Direction.North:
                    case Direction.South:
                        if (newPoint.X <= boat.X - d || newPoint.X >= boat.X + d)
                            return null;
                        break;
                    case Direction.East:
                    case Direction.West:
                        if (newPoint.Y <= boat.Y - d || newPoint.Y >= boat.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 boat;
                    }
                }

                return null;
            }

            return boat;
        }

        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.SendPacket(p, map, new GraphicalEffect(EffectType.FixedXYZ, Serial.Zero, Serial.Zero, 0x3728, p, p, 14, 14, true, true));
            Effects.PlaySound(Location, map, 0x11C);
        }

        public void InvalidateDamageState()
        {
            InvalidateDamageState(null);
        }

        public void InvalidateDamageState(Mobile from)
        {
            if (Durability >= 100)
                DamageState = DamageLevel.Pristine;
            else if (Durability >= 75.0)
                DamageState = DamageLevel.Slightly;
            else if (Durability >= 50.0)
                DamageState = DamageLevel.Moderately;
            else if (Durability >= 25.0)
                DamageState = DamageLevel.Heavily;
            else
                DamageState = DamageLevel.Severely;

            if (Durability <= 0)
            {
                DoAreaMessage(1116297, 5, null); // The ship cannon has been destroyed!
                Delete();

                if (from != null && from.InRange(Location, 5))
                    from.SendLocalizedMessage(1116297); // The ship cannon has been destroyed!
            }

            InvalidateProperties();
        }

        public void ClearCannon()
        {
            Prepered = CannonAction.None;
            Charged = CannonAction.None;
            Loaded = CannonAction.None;
            AmmoType = AmmunitionType.Empty;
            Primed = CannonAction.None;

            InvalidateProperties();
        }

        public virtual void OnShipHit(object obj)
        {
            object[] list = (object[])obj;
            BaseBoat target = list[0] as BaseBoat;
            Point3D pnt = (Point3D)list[1];
            AmmoInfo ammoInfo = list[2] as AmmoInfo;
            Mobile shooter = list[3] as Mobile;

            if (target != null && Galleon != null)
            {
                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;
                    }
                }

                int damage = 0;

                if (ammoInfo.AmmoType == AmmunitionType.Grapeshot)
                {
                    for (int count = 15; count > 0; count--)
                    {
                        damage = (int)(ammoInfo.GetDamage(this) * Galleon.CannonDamageMod);
                        Point3D loc = new Point3D(hit.X + Utility.RandomMinMax(0, 4), hit.Y + Utility.RandomMinMax(0, 4), hit.Z);
                        Effects.SendPacket(loc, target.Map, new GraphicalEffect(EffectType.FixedXYZ, Serial.Zero, Serial.Zero, 0x36CB, loc, loc, 15, 15, true, true));
                        target.OnTakenDamage(shooter, damage);
                        MobileOnBoardDamage(shooter, loc, ammoInfo);
                    }
                }
                else
                {
                    damage = (int)(ammoInfo.GetDamage(this) * Galleon.CannonDamageMod);
                    Effects.SendPacket(hit, target.Map, new GraphicalEffect(EffectType.FixedXYZ, Serial.Zero, Serial.Zero, 0x36CB, hit, hit, 15, 15, true, true));
                    target.OnTakenDamage(shooter, damage);
                }

                if (Galleon.Map != null)
                {
                    IPooledEnumerable eable = Galleon.Map.GetItemsInRange(hit, 1);

                    foreach (Item item in eable)
                    {
                        if (item is IShipCannon && !Galleon.Contains(item))
                        {
                            ((IShipCannon)item).OnDamage(damage, shooter);
                        }
                    }

                    eable.Free();
                }
            }
        }

        public void MobileOnBoardDamage(Mobile shooter, Point3D pnt, AmmoInfo info)
        {
            List<IDamageable> list = new List<IDamageable>();

            if (Map == null || Map == Map.Internal || Galleon == null)
                return;

            IPooledEnumerable eable = Map.GetObjectsInRange(pnt, 0);

            foreach (IDamageable dam in eable.OfType<IDamageable>())
            {
                Mobile mob = dam as Mobile;

                if (mob != null && (!shooter.CanBeHarmful(mob, false) || Galleon.Contains(mob)))
                    continue;

                if (mob is PlayerMobile || mob is BaseCreature)
                {
                    shooter.DoHarmful(mob);
                    AOS.Damage(mob, shooter, 35, info.PhysicalDamage, info.FireDamage, info.ColdDamage, info.PoisonDamage, info.EnergyDamage);
                }
            }

            eable.Free();
        }

        public virtual void OnMobileHit(object obj)
        {
            object[] objects = (object[])obj;
            var toHit = objects[0] as Mobile;
            var pnt = (Point3D)objects[1];
            var info = objects[2] as AmmoInfo;
            var shooter = objects[3] as Mobile;

            int damage = (int)(Utility.RandomMinMax(info.MinDamage, info.MaxDamage) * Galleon.CannonDamageMod);

            if (info == null)
                return;

            if (toHit != null)
            {
                Effects.SendPacket(toHit.Location, toHit.Map, new GraphicalEffect(EffectType.FixedXYZ, Serial.Zero, Serial.Zero, 0x36CB, toHit.Location, toHit.Location, 15, 15, true, true));
                shooter.DoHarmful(toHit);
                AOS.Damage(toHit, shooter, damage, info.PhysicalDamage, info.FireDamage, info.ColdDamage, info.PoisonDamage, info.EnergyDamage);
            }
        }

        public virtual void OnDamageableItemHit(object obj)
        {
            object[] objects = (object[])obj;
            var toHit = objects[0] as DamageableItem;
            var pnt = (Point3D)objects[1];
            var info = objects[2] as AmmoInfo;
            var shooter = objects[3] as Mobile;

            if (info == null || toHit == null || toHit.Map == null)
                return;

            int damage = (int)(Utility.RandomMinMax(info.MinDamage, info.MaxDamage) * Galleon.CannonDamageMod);

            shooter.DoHarmful(toHit);
            AOS.Damage(toHit, shooter, damage, info.PhysicalDamage, info.FireDamage, info.ColdDamage, info.PoisonDamage, info.EnergyDamage);
            Effects.SendLocationEffect(new Point3D(toHit.X, toHit.Y, toHit.Z + 5), toHit.Map, Utility.RandomBool() ? 14000 : 14013, 15, 10);
            Effects.PlaySound(toHit.Location, toHit.Map, 0x207);
        }

        public List<IDamageable> FindDamageables(Mobile shooter, Point3D pnt, Map map, bool player, bool pet, bool monsters, bool seacreature, bool items)
        {
            List<IDamageable> list = new List<IDamageable>();

            if (map == null || map == Map.Internal || Galleon == null)
                return list;

            IPooledEnumerable eable = map.GetObjectsInRange(pnt, 0);

            foreach (IDamageable dam in eable.OfType<IDamageable>())
            {
                Mobile mob = dam as Mobile;

                if (mob != null && (!shooter.CanBeHarmful(mob, false) || Galleon.Contains(mob)))
                    continue;

                if (!items && dam is DamageableItem)
                    continue;

                if (items && dam is DamageableItem && ((DamageableItem)dam).CanDamage && !Galleon.Contains(dam))
                    list.Add(dam);

                if (player && mob is PlayerMobile)
                    list.Add(mob);

                if (monsters && mob is BaseCreature && !((BaseCreature)mob).Controlled && !((BaseCreature)mob).Summoned)
                    list.Add(mob);

                if (pet && mob is BaseCreature && (((BaseCreature)mob).Controlled || ((BaseCreature)mob).Summoned))
                    list.Add(mob);

                if (seacreature && (mob is BaseSeaChampion || mob is Kraken))
                    list.Add(mob);
            }

            eable.Free();
            return list;
        }

        public void TryRepairCannon(Mobile from)
        {
            Container pack = from.Backpack;
            Container hold = Galleon.GalleonHold;

            if (pack == null)
                return;

            double ingotsNeeded = 36 * (int)DamageState;

            ingotsNeeded -= ((double)from.Skills[SkillName.Blacksmith].Value / 200.0) * ingotsNeeded;

            double min = ingotsNeeded / 10;
            double ingots1 = pack.GetAmount(typeof(IronIngot));
            double ingots2 = hold != null ? hold.GetAmount(typeof(IronIngot)) : 0;
            double ingots = ingots1 + ingots2;
            double ingotsUsed, percRepaired;

            if (ingots < min)
            {
                from.SendLocalizedMessage(1116603, ((int)min).ToString()); //You need a minimum of ~1_METAL~ iron ingots to repair this cannon.
                return;
            }

            if (ingots >= ingotsNeeded)
            {
                ingotsUsed = ingotsNeeded;
                percRepaired = 100;
            }
            else
            {
                ingotsUsed = ingots;
                percRepaired = (ingots / ingotsNeeded) * 100;
            }

            double toConsume = 0;
            double temp = ingotsUsed;

            if (ingotsUsed > 0 && ingots1 > 0)
            {
                toConsume = Math.Min(ingotsUsed, ingots1);
                pack.ConsumeTotal(typeof(IronIngot), (int)toConsume);
                ingotsUsed -= toConsume;
            }

            if (hold != null && ingotsUsed > 0 && ingots2 > 0)
            {
                toConsume = Math.Min(ingotsUsed, ingots2);
                hold.ConsumeTotal(typeof(IronIngot), (int)toConsume);
            }

            m_Hits += (int)((MaxHits - m_Hits) * (percRepaired / 100));
            if (m_Hits > MaxHits) m_Hits = MaxHits;
            InvalidateDamageState();

            percRepaired += Durability;
            if (percRepaired > 100) percRepaired = 100;

            from.SendLocalizedMessage(1116605, string.Format("{0}\t{1}", ((int)temp).ToString(), ((int)percRepaired).ToString())); //You make repairs to the cannon using ~1_METAL~ ingots. The cannon is now ~2_DMGPCT~% repaired.
        }

        public void ResendGump(Mobile from)
        {
            ResendGump(from, TimeSpan.Zero);
        }

        public void ResendGump(Mobile from, TimeSpan delay)
        {
            if (!Galleon.Contains(from))
            {
                Viewing.Remove(from);
            }

            if (!Viewing.Contains(from))
            {
                Viewing.Add(from);
            }

            foreach (var pm in Viewing.OfType<PlayerMobile>())
            {
                var gump = BaseGump.GetGump<ShipCannonGump>(pm, g => g.Cannon == this);

                if (gump != null)
                {
                    if (delay != TimeSpan.Zero)
                    {
                        Timer.DelayCall(delay, () => gump.Refresh());
                    }
                    else
                    {
                        gump.Refresh();
                    }
                }
                else
                {
                    if (delay != TimeSpan.Zero)
                    {
                        Timer.DelayCall(delay, () => BaseGump.SendGump(new ShipCannonGump(pm, this)));
                    }
                    else
                    {
                        BaseGump.SendGump(new ShipCannonGump(pm, this));
                    }
                }
            }
        }

        public void OnDamage(int damage, Mobile from)
        {
            m_Hits -= damage;
            InvalidateDamageState(from);
        }

        public Dictionary<Mobile, List<int>> Actions { get { return m_Actions; } }
        private Dictionary<Mobile, List<int>> m_Actions = new Dictionary<Mobile, List<int>>();

        public void AddAction(Mobile from, int action)
        {
            if (from == null)
                return;

            if (!m_Actions.ContainsKey(from))
                m_Actions[from] = new List<int>();

            var list = m_Actions[from];

            list.Insert(0, action);

            while (list.Count > 3)
            {
                list.RemoveAt(list.Count - 1);
            }
        }

        public override void GetProperties(ObjectPropertyList list)
        {
            base.GetProperties(list);

            list.Add(1116026, Charged == CannonAction.Finish ? "#1116031" : "#1116032"); // Charged: ~1_VALUE~
            list.Add(1116027, string.Format("{0}", AmmoInfo.GetAmmoName(this).ToString())); // Ammo: ~1_VALUE~
            list.Add(1116028, Primed == CannonAction.Finish ? "#1116031" : "#1116032"); //Primed: ~1_VALUE~
            list.Add(1116580 + (int)DamageState);
            list.Add(1072241, "{0}\t{1}\t{2}\t{3}", TotalItems, MaxItems, TotalWeight, MaxWeight);
        }

        public override void GetContextMenuEntries(Mobile from, List<ContextMenuEntry> list)
        {
            base.GetContextMenuEntries(from, list);

            if (Galleon.GetSecurityLevel(from) >= SecurityLevel.Officer)
            {
                list.Add(new UnloadContext(this, from));
                list.Add(new DismantleContext(this, from));
                list.Add(new RepairContext(this, from));
            }
        }

        private class UnloadContext : ContextMenuEntry
        {
            public Mobile From { get; set; }
            public BaseShipCannon Cannon { get; set; }

            public UnloadContext(BaseShipCannon cannon, Mobile from)
                : base(1116072, 2) // Unload
            {
                From = from;
                Cannon = cannon;

                Enabled = cannon.CanLight;
            }

            public override void OnClick()
            {
                Cannon.Unload(From);
                Cannon.ResendGump(From);
            }
        }

        private class DismantleContext : ContextMenuEntry
        {
            public Mobile From { get; set; }
            public BaseShipCannon Cannon { get; set; }

            public DismantleContext(BaseShipCannon cannon, Mobile from)
                : base(1116069, 2)
            {
                From = from;
                Cannon = cannon;

                Enabled = cannon.Empty && Cannon.DamageState == DamageLevel.Pristine;
            }

            public override void OnClick()
            {
                ShipCannonDeed deed = Cannon.GetDeed;

                Cannon.DoAreaMessage(1116073, 10, From); //~1_NAME~ dismantles the ship cannon.
                Cannon.Delete();

                Container pack = From.Backpack;

                if (pack == null || !pack.TryDropItem(From, deed, false))
                {
                    deed.MoveToWorld(From.Location, From.Map);
                }
            }

            public override void OnClickDisabled()
            {
                if (Cannon.Galleon.GetSecurityLevel(From) != SecurityLevel.Captain)
                {
                    From.SendLocalizedMessage(1149693); // You must own the ship to do that.
                }
                else if (!Cannon.Empty)
                {
                    From.SendLocalizedMessage(1116321); // The ship cannon and magazine must be fully unloaded before it can be dismantled.
                }
                else if (Cannon.DamageState != DamageLevel.Pristine)
                {
                    From.SendLocalizedMessage(1116322); // The ship cannon must be fully repaired before it can be dismantled.
                }
            }
        }

        private class RepairContext : ContextMenuEntry
        {
            public Mobile From { get; set; }
            public BaseShipCannon Cannon { get; set; }

            public RepairContext(BaseShipCannon cannon, Mobile from)
                : base(1116602, 2)
            {
                From = from;
                Cannon = cannon;

                Enabled = Cannon.DamageState != DamageLevel.Pristine;
            }

            public override void OnClick()
            {
                Cannon.TryRepairCannon(From);
            }

            public override void OnClickDisabled()
            {
                From.SendLocalizedMessage(1116604); //The cannon is in pristine condition and does not need repairs.
            }
        }

        public override void Delete()
        {
            if (Galleon != null)
            {
                Galleon.RemoveCannon(this);
            }

            var list = new List<PlayerMobile>(Viewing.OfType<PlayerMobile>());

            foreach (var pm in list)
            {
                var gump = BaseGump.GetGump<ShipCannonGump>(pm, g => g.Cannon == this);

                if (gump != null)
                {
                    gump.Close();
                }
            }

            ColUtility.Free(list);
            base.Delete();
        }

        public BaseShipCannon(Serial serial) : base(serial) { }

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
            writer.Write(1);

            writer.Write((int)Prepered);
            writer.Write(Galleon);
            writer.Write((int)Charged);
            writer.Write((int)Loaded);
            writer.Write((int)Primed);
            writer.Write((int)AmmoType);
            writer.Write((int)Position);

            writer.Write(m_Hits);
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();

            switch (version)
            {
                case 1:
                    {
                        Prepered = (CannonAction)reader.ReadInt();
                        Galleon = reader.ReadItem() as BaseGalleon;
                        Charged = (CannonAction)reader.ReadInt();
                        Loaded = (CannonAction)reader.ReadInt();
                        Primed = (CannonAction)reader.ReadInt();
                        AmmoType = (AmmunitionType)reader.ReadInt();
                        Position = (ShipPosition)reader.ReadInt();
                        m_Hits = reader.ReadInt();

                        return;
                    }
                case 0:
                    {
                        Galleon = reader.ReadItem() as BaseGalleon;

                        if (reader.ReadBool())
                            Charged = CannonAction.Finish;

                        if (reader.ReadBool())
                            Loaded = CannonAction.Finish;

                        if (reader.ReadBool())
                            Primed = CannonAction.Finish;

                        AmmoType = (AmmunitionType)reader.ReadInt();
                        Position = (ShipPosition)reader.ReadInt();

                        m_Hits = reader.ReadInt();

                        break;
                    }
            }

            InvalidateDamageState();
        }

        public class ShipCannonGump : BaseGump
        {
            public BaseShipCannon Cannon { get; set; }

            public ShipCannonGump(PlayerMobile pm, BaseShipCannon cannon)
                : base(pm, 100, 100)
            {
                Cannon = cannon;
                TypeID = Cannon.Serial;
            }

            public override void AddGumpLayout()
            {
                AddBackground(0, 0, 250, 175, 0x6DB);
                AddHtmlLocalized(10, 10, 230, 18, 1149614 + (int)Cannon.Position, 0x3DFF, false, false);
                AddHtmlLocalized(115, 35, 70, 18, 1158934, 0x7FE7, false, false); // STATUS

                if (Cannon.CanLight)
                {
                    AddButton(10, 35, 0xFA5, 0xFA7, 8, GumpButtonType.Reply, 0);
                    AddHtmlLocalized(45, 35, 70, 18, 1149985, 0x7FFF, false, false); // UNLOAD

                    AddButton(10, 89, 0xFA5, 0xFA7, 6, GumpButtonType.Reply, 0);
                    AddHtmlLocalized(45, 89, 70, 18, 1149638, 0x7FFF, false, false); // FIRE
                }
                else
                {
                    AddButton(10, 35, 0xFA5, 0xFA7, 1, GumpButtonType.Reply, 0);
                    AddHtmlLocalized(45, 35, 70, 18, 1158890, 0x7FFF, false, false); // PREP
                }

                AddHtmlLocalized(115, 53, 115, 18, Cannon.Charged == CannonAction.Finish ? 1149631 : 1149632, Cannon.Charged == CannonAction.Finish ? 0x1FE7 : 0x7CE7, false, false); // Charged / Not Charged
                AddHtmlLocalized(115, 71, 115, 18, 1114057, Cannon.Loaded == CannonAction.Finish ? AmmoInfo.GetAmmoName(Cannon).ToString() : "#1149636", Cannon.Loaded == CannonAction.Finish ? 0x1FE7 : 0x7CE7, false, false); // Cannonball / Not Loaded
                AddHtmlLocalized(115, 89, 115, 18, Cannon.Primed == CannonAction.Finish ? 1149640 : 1149639, Cannon.Primed == CannonAction.Finish ? 0x1FE7 : 0x7CE7, false, false); // Primed / No Fuse

                if (Cannon.Actions.ContainsKey(User))
                {
                    var actual = 0;
                    var list = Cannon.Actions[User];

                    for (int i = list.Count - 1; i >= 0; i--)
                    {
                        AddHtmlLocalized(10, 112 + (actual * 18), 230, 18, Cannon.Actions[User][i], actual == list.Count - 1 ? 0x7FE7 : 0x3DEF, false, false);
                        actual++;
                    }
                }
            }

            public override void OnResponse(RelayInfo info)
            {
                if (info.ButtonID == 0 || !Cannon.Galleon.Contains(User))
                {
                    if (Cannon.Viewing.Contains(User))
                    {
                        Cannon.Viewing.Remove(User);
                    }

                    return;
                }

                if (User.InRange(Cannon.Location, 2) && User.Alive)
                {
                    switch (info.ButtonID)
                    {
                        case 1:
                            {
                                if (!Cannon.Processing)
                                {
                                    if (Cannon.Prepered != CannonAction.Finish)
                                    {
                                        Cannon.TryPrep(User);
                                    }
                                    else if (Cannon.Charged != CannonAction.Finish)
                                    {
                                        Cannon.TryCharge(User);
                                    }
                                    else if (Cannon.Loaded != CannonAction.Finish)
                                    {
                                        Cannon.TryLoad(User);
                                    }
                                    else if (Cannon.Primed != CannonAction.Finish)
                                    {
                                        Cannon.TryPrime(User);
                                    }
                                }
                                break;
                            }
                        case 6:
                            if (Cannon.CanLight)
                            {
                                Cannon.TryLightFuse(User);
                            }
                            break;
                        case 8:
                            Cannon.Unload(User);
                            break;
                    }
                }
                else
                {
                    Cannon.AddAction(User, 1149654); // You are too far away.
                }

                Refresh();
            }

            public override void OnServerClose(NetState owner)
            {
                base.OnServerClose(owner);

                if (owner.Mobile != null && Cannon != null && Cannon.Viewing.Contains(owner.Mobile))
                {
                    Cannon.Viewing.Remove(owner.Mobile);
                }
            }
        }
    }

    public class Culverin : BaseShipCannon
    {
        public override int Range { get { return 10; } }
        public override ShipCannonDeed GetDeed { get { return new CulverinDeed(); } }
        public override CannonPower Power { get { return CannonPower.Light; } }

        public Culverin(BaseGalleon g) : base(g)
        {
        }

        public Culverin(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 Carronade : BaseShipCannon
    {
        public override int Range { get { return 10; } }
        public override ShipCannonDeed GetDeed { get { return new CarronadeDeed(); } }
        public override CannonPower Power { get { return CannonPower.Heavy; } }

        public override TimeSpan ActionTime { get { return TimeSpan.FromSeconds(2.0); } }

        public Carronade(BaseGalleon g) : base(g)
        {
        }

        public Carronade(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 Blundercannon : BaseShipCannon
    {
        public override int LabelNumber { get { return 1158942; } } // Blundercannon

        public override int Range { get { return 12; } }
        public override ShipCannonDeed GetDeed { get { return new BlundercannonDeed(); } }
        public override CannonPower Power { get { return CannonPower.Massive; } }

        public override TimeSpan ActionTime { get { return TimeSpan.FromSeconds(2.0); } }

        public Blundercannon(BaseGalleon g) : base(g)
        {
        }

        public Blundercannon(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();
        }
    }
}
