using System; using System.Collections; using System.Collections.Generic; using Server; using Server.Items; using Server.Movement; using Server.Network; using Server.Mobiles; namespace Server.Multis { public enum BoatOrder { Move, Course, Single } public abstract class BaseBoat : BaseMulti { private static Rectangle2D[] m_SeaWrap = new Rectangle2D[]{ new Rectangle2D( 16, 16, 7168 - 32, 4096 - 32 ) }; private static TimeSpan BoatDecayDelay = TimeSpan.FromDays( Server.Misc.Settings.BoatDelete() ); public static BaseBoat FindBoatAt( IPoint2D loc, Map map ) { Sector sector = map.GetSector( loc ); for ( int i = 0; i < sector.Multis.Count; i++ ) { BaseBoat boat = sector.Multis[i] as BaseBoat; if ( boat != null && boat.Contains( loc.X, loc.Y ) ) return boat; } return null; } private Hold m_Hold; private TillerMan m_TillerMan; private Mobile m_Owner; private Direction m_Facing; private Direction m_Moving; private int m_Speed; private bool m_Anchored; private string m_ShipName; private BoatOrder m_Order; private MapItem m_MapItem; private int m_NextNavPoint; private Plank m_PPlank, m_SPlank; private DateTime m_DecayTime; private Timer m_TurnTimer; private Timer m_MoveTimer; [CommandProperty( AccessLevel.GameMaster )] public Hold Hold{ get{ return m_Hold; } set{ m_Hold = value; } } [CommandProperty( AccessLevel.GameMaster )] public TillerMan TillerMan{ get{ return m_TillerMan; } set{ m_TillerMan = value; } } [CommandProperty( AccessLevel.GameMaster )] public Plank PPlank{ get{ return m_PPlank; } set{ m_PPlank = value; } } [CommandProperty( AccessLevel.GameMaster )] public Plank SPlank{ get{ return m_SPlank; } set{ m_SPlank = value; } } [CommandProperty( AccessLevel.GameMaster )] public Mobile Owner{ get{ return m_Owner; } set{ m_Owner = value; } } [CommandProperty( AccessLevel.GameMaster )] public Direction Facing{ get{ return m_Facing; } set{ SetFacing( value ); } } [CommandProperty( AccessLevel.GameMaster )] public Direction Moving{ get{ return m_Moving; } set{ m_Moving = value; } } [CommandProperty( AccessLevel.GameMaster )] public bool IsMoving{ get{ return ( m_MoveTimer != null ); } } [CommandProperty( AccessLevel.GameMaster )] public int Speed{ get{ return m_Speed; } set{ m_Speed = value; } } [CommandProperty( AccessLevel.GameMaster )] public bool Anchored{ get{ return m_Anchored; } set{ m_Anchored = value; } } [CommandProperty( AccessLevel.GameMaster )] public string ShipName{ get{ return m_ShipName; } set{ m_ShipName = value; if ( m_TillerMan != null ) m_TillerMan.InvalidateProperties(); } } [CommandProperty( AccessLevel.GameMaster )] public BoatOrder Order{ get{ return m_Order; } set{ m_Order = value; } } [CommandProperty( AccessLevel.GameMaster )] public MapItem MapItem{ get{ return m_MapItem; } set{ m_MapItem = value; } } [CommandProperty( AccessLevel.GameMaster )] public int NextNavPoint{ get{ return m_NextNavPoint; } set{ m_NextNavPoint = value; } } [CommandProperty( AccessLevel.GameMaster )] public DateTime TimeOfDecay{ get{ return m_DecayTime; } set{ m_DecayTime = value; if ( m_TillerMan != null ) m_TillerMan.InvalidateProperties(); } } public int Status { get { DateTime start = TimeOfDecay - BoatDecayDelay; if ( DateTime.Now - start < TimeSpan.FromHours( 1.0 ) ) return 1043010; // This structure is like new. if ( DateTime.Now - start < TimeSpan.FromDays( 2.0 ) ) return 1043011; // This structure is slightly worn. if ( DateTime.Now - start < TimeSpan.FromDays( 3.0 ) ) return 1043012; // This structure is somewhat worn. if ( DateTime.Now - start < TimeSpan.FromDays( 4.0 ) ) return 1043013; // This structure is fairly worn. if ( DateTime.Now - start < TimeSpan.FromDays( 5.0 ) ) return 1043014; // This structure is greatly worn. return 1043015; // This structure is in danger of collapsing. } } public virtual int NorthID{ get{ return 0; } } public virtual int EastID{ get{ return 0; } } public virtual int SouthID{ get{ return 0; } } public virtual int WestID{ get{ return 0; } } public virtual int HoldDistance{ get{ return 0; } } public virtual int TillerManDistance{ get{ return 0; } } public virtual Point2D StarboardOffset{ get{ return Point2D.Zero; } } public virtual Point2D PortOffset{ get{ return Point2D.Zero; } } public virtual Point3D MarkOffset{ get{ return Point3D.Zero; } } public virtual BaseDockedBoat DockedBoat{ get{ return null; } } private static List m_Instances = new List(); public static List Boats{ get{ return m_Instances; } } public BaseBoat() : base( 0x0 ) { m_DecayTime = DateTime.Now + BoatDecayDelay; m_TillerMan = new TillerMan( this ); m_Hold = new Hold( this ); m_PPlank = new Plank( this, PlankSide.Port, 0 ); m_SPlank = new Plank( this, PlankSide.Starboard, 0 ); m_PPlank.MoveToWorld( new Point3D( X + PortOffset.X, Y + PortOffset.Y, Z ), Map ); m_SPlank.MoveToWorld( new Point3D( X + StarboardOffset.X, Y + StarboardOffset.Y, Z ), Map ); Facing = Direction.North; m_NextNavPoint = -1; Movable = false; m_Instances.Add( this ); } public BaseBoat( Serial serial ) : base( serial ) { } public Point3D GetRotatedLocation( int x, int y ) { Point3D p = new Point3D( X + x, Y + y, Z ); return Rotate( p, (int)m_Facing / 2 ); } public void UpdateComponents() { if ( m_PPlank != null ) { m_PPlank.MoveToWorld( GetRotatedLocation( PortOffset.X, PortOffset.Y ), Map ); m_PPlank.SetFacing( m_Facing ); } if ( m_SPlank != null ) { m_SPlank.MoveToWorld( GetRotatedLocation( StarboardOffset.X, StarboardOffset.Y ), Map ); m_SPlank.SetFacing( m_Facing ); } int xOffset = 0, yOffset = 0; Movement.Movement.Offset( m_Facing, ref xOffset, ref yOffset ); if ( m_TillerMan != null ) { m_TillerMan.Location = new Point3D( X + (xOffset * TillerManDistance) + (m_Facing == Direction.North ? 1 : 0), Y + (yOffset * TillerManDistance), m_TillerMan.Z ); m_TillerMan.SetFacing( m_Facing ); m_TillerMan.InvalidateProperties(); } if ( m_Hold != null ) { m_Hold.Location = new Point3D( X + (xOffset * HoldDistance), Y + (yOffset * HoldDistance), m_Hold.Z ); m_Hold.SetFacing( m_Facing ); } } public override void Serialize( GenericWriter writer ) { base.Serialize( writer ); writer.Write( (int) 3 ); writer.Write( (Item) m_MapItem ); writer.Write( (int) m_NextNavPoint ); writer.Write( (int) m_Facing ); writer.WriteDeltaTime( m_DecayTime ); writer.Write( m_Owner ); writer.Write( m_PPlank ); writer.Write( m_SPlank ); writer.Write( m_TillerMan ); writer.Write( m_Hold ); writer.Write( m_Anchored ); writer.Write( m_ShipName ); CheckDecay(); } public override void Deserialize( GenericReader reader ) { base.Deserialize( reader ); int version = reader.ReadInt(); switch ( version ) { case 3: { m_MapItem = (MapItem) reader.ReadItem(); m_NextNavPoint = reader.ReadInt(); goto case 2; } case 2: { m_Facing = (Direction)reader.ReadInt(); goto case 1; } case 1: { m_DecayTime = reader.ReadDeltaTime(); goto case 0; } case 0: { if ( version < 3 ) m_NextNavPoint = -1; if ( version < 2 ) { if ( ItemID == NorthID ) m_Facing = Direction.North; else if ( ItemID == SouthID ) m_Facing = Direction.South; else if ( ItemID == EastID ) m_Facing = Direction.East; else if ( ItemID == WestID ) m_Facing = Direction.West; } m_Owner = reader.ReadMobile(); m_PPlank = reader.ReadItem() as Plank; m_SPlank = reader.ReadItem() as Plank; m_TillerMan = reader.ReadItem() as TillerMan; m_Hold = reader.ReadItem() as Hold; m_Anchored = reader.ReadBool(); m_ShipName = reader.ReadString(); if ( version < 1) Refresh(); break; } } m_Instances.Add( this ); } public static bool NearDock( Mobile m ) { if ( m is PlayerMobile ) { if ( ((PlayerMobile)m).NpcGuild == NpcGuild.MarinersGuild ) return true; } int range = 30; bool dock = false; IPooledEnumerable eable = (m.Map).GetItemsInRange( m.Location, range ); foreach ( Item item in eable ) { if ( item is BaseHouse ) { BaseHouse house = (BaseHouse)item; if ( house.IsOwner( m ) || house.IsCoOwner( m ) || house.IsFriend( m ) || house.IsGuildMember( m ) ) { dock = true; break; } } } eable.Free(); if ( !dock ) { for ( int x = -range; !dock && x <= range; ++x ) { for ( int y = -range; !dock && y <= range; ++y ) { StaticTile[] tiles = (m.Map).Tiles.GetStaticTiles( m.X+x, m.Y+y, true ); for ( int i = 0; !dock && i < tiles.Length; ++i ) { int id = tiles[i].ID; bool isDock = ( id == 0x14F7 || id == 0x14F9 ); if ( isDock ) { dock = true; break; } } } } } return dock; } public void RemoveKeys( Mobile m ) { uint keyValue = 0; if ( m_PPlank != null ) keyValue = m_PPlank.KeyValue; if ( keyValue == 0 && m_SPlank != null ) keyValue = m_SPlank.KeyValue; Key.RemoveKeys( m, keyValue ); } public uint CreateKeys( Mobile m ) { uint value = Key.RandomValue(); Key packKey = new Key( KeyType.Gold, value, this ); Key innKey = new Key( KeyType.Gold, value, this ); packKey.MaxRange = 10; innKey.MaxRange = 10; packKey.Name = "a ship key"; innKey.Name = "a ship key"; InnBox box = m.InnBox; if ( !box.TryDropItem( m, innKey, false ) ) innKey.Delete(); else m.LocalOverheadMessage( MessageType.Regular, 0x3B2, 502484 ); // A ship's key is now in my safety deposit box. if ( m.AddToBackpack( packKey ) ) m.LocalOverheadMessage( MessageType.Regular, 0x3B2, 502485 ); // A ship's key is now in my backpack. else m.LocalOverheadMessage( MessageType.Regular, 0x3B2, 502483 ); // A ship's key is now at my feet. return value; } public override void OnAfterDelete() { if ( m_TillerMan != null ) m_TillerMan.Delete(); if ( m_Hold != null ) m_Hold.Delete(); if ( m_PPlank != null ) m_PPlank.Delete(); if ( m_SPlank != null ) m_SPlank.Delete(); if ( m_TurnTimer != null ) m_TurnTimer.Stop(); if ( m_MoveTimer != null ) m_MoveTimer.Stop(); m_Instances.Remove( this ); } public override void OnLocationChange( Point3D old ) { if ( m_TillerMan != null ) m_TillerMan.Location = new Point3D( X + (m_TillerMan.X - old.X), Y + (m_TillerMan.Y - old.Y), Z + (m_TillerMan.Z - old.Z ) ); if ( m_Hold != null ) m_Hold.Location = new Point3D( X + (m_Hold.X - old.X), Y + (m_Hold.Y - old.Y), Z + (m_Hold.Z - old.Z ) ); if ( m_PPlank != null ) m_PPlank.Location = new Point3D( X + (m_PPlank.X - old.X), Y + (m_PPlank.Y - old.Y), Z + (m_PPlank.Z - old.Z ) ); if ( m_SPlank != null ) m_SPlank.Location = new Point3D( X + (m_SPlank.X - old.X), Y + (m_SPlank.Y - old.Y), Z + (m_SPlank.Z - old.Z ) ); } public override void OnMapChange() { if ( m_TillerMan != null ) m_TillerMan.Map = Map; if ( m_Hold != null ) m_Hold.Map = Map; if ( m_PPlank != null ) m_PPlank.Map = Map; if ( m_SPlank != null ) m_SPlank.Map = Map; } public bool CanCommand( Mobile m ) { return true; } public Point3D GetMarkedLocation() { Point3D p = new Point3D( X + MarkOffset.X, Y + MarkOffset.Y, Z + MarkOffset.Z ); return Rotate( p, (int)m_Facing / 2 ); } public bool CheckKey( uint keyValue ) { if ( m_SPlank != null && m_SPlank.KeyValue == keyValue ) return true; if ( m_PPlank != null && m_PPlank.KeyValue == keyValue ) return true; return false; } private static TimeSpan SlowInterval = TimeSpan.FromSeconds( 0.75 ); private static TimeSpan FastInterval = TimeSpan.FromSeconds( 0.75 ); private static int SlowSpeed = 1; private static int FastSpeed = 3; private static TimeSpan SlowDriftInterval = TimeSpan.FromSeconds( 1.50 ); private static TimeSpan FastDriftInterval = TimeSpan.FromSeconds( 0.75 ); private static int SlowDriftSpeed = 1; private static int FastDriftSpeed = 1; private static Direction Forward = Direction.North; private static Direction ForwardLeft = Direction.Up; private static Direction ForwardRight = Direction.Right; private static Direction Backward = Direction.South; private static Direction BackwardLeft = Direction.Left; private static Direction BackwardRight = Direction.Down; private static Direction Left = Direction.West; private static Direction Right = Direction.East; private static Direction Port = Left; private static Direction Starboard = Right; private bool m_Decaying; public void Refresh() { m_DecayTime = DateTime.Now + BoatDecayDelay; if( m_TillerMan != null ) m_TillerMan.InvalidateProperties(); } private class DecayTimer : Timer { private BaseBoat m_Boat; private int m_Count; public DecayTimer( BaseBoat boat ) : base( TimeSpan.FromSeconds( 1.0 ), TimeSpan.FromSeconds( 5.0 ) ) { m_Boat = boat; Priority = TimerPriority.TwoFiftyMS; } protected override void OnTick() { if ( m_Count == 5 ) { m_Boat.Delete(); Stop(); } else { m_Boat.Location = new Point3D( m_Boat.X, m_Boat.Y, m_Boat.Z - 1 ); if ( m_Boat.TillerMan != null ) m_Boat.TillerMan.Say( 1007168 + m_Count ); ++m_Count; } } } public bool CheckDecay() { if ( m_Decaying ) return true; if ( !IsMoving && DateTime.Now >= m_DecayTime ) { new DecayTimer( this ).Start(); m_Decaying = true; return true; } return false; } public bool LowerAnchor( bool message ) { if ( CheckDecay() ) return false; if ( m_Anchored ) { if ( message && m_TillerMan != null ) m_TillerMan.Say( 501445 ); // Ar, the anchor was already dropped sir. return false; } StopMove( false ); m_Anchored = true; if ( message && m_TillerMan != null ) m_TillerMan.Say( 501444 ); // Ar, anchor dropped sir. return true; } public bool RaiseAnchor( bool message ) { if ( CheckDecay() ) return false; if ( !m_Anchored ) { if ( message && m_TillerMan != null ) m_TillerMan.Say( 501447 ); // Ar, the anchor has not been dropped sir. return false; } m_Anchored = false; if ( message && m_TillerMan != null ) m_TillerMan.Say( 501446 ); // Ar, anchor raised sir. return true; } public bool StartMove( Direction dir, bool fast ) { if ( CheckDecay() ) return false; bool drift = ( dir != Forward && dir != ForwardLeft && dir != ForwardRight ); TimeSpan interval = (fast ? (drift ? FastDriftInterval : FastInterval) : (drift ? SlowDriftInterval : SlowInterval)); int speed = (fast ? (drift ? FastDriftSpeed : FastSpeed) : (drift ? SlowDriftSpeed : SlowSpeed)); if ( StartMove( dir, speed, interval, false, true ) ) { if ( m_TillerMan != null ) m_TillerMan.Say( 501429 ); // Aye aye sir. return true; } return false; } public bool OneMove( Direction dir ) { if ( CheckDecay() ) return false; bool drift = ( dir != Forward ); TimeSpan interval = drift ? FastDriftInterval : FastInterval; int speed = drift ? FastDriftSpeed : FastSpeed; if ( StartMove( dir, speed, interval, true, true ) ) { if ( m_TillerMan != null ) m_TillerMan.Say( 501429 ); // Aye aye sir. return true; } return false; } public void BeginRename( Mobile from ) { if ( CheckDecay() ) return; if ( from.AccessLevel < AccessLevel.GameMaster && from != m_Owner ) { if ( m_TillerMan != null ) m_TillerMan.Say( Utility.Random( 1042876, 4 ) ); // Arr, don't do that! | Arr, leave me alone! | Arr, watch what thour'rt doing, matey! | Arr! Do that again and I'll throw ye overhead! return; } if ( m_TillerMan != null ) m_TillerMan.Say( 502580 ); // What dost thou wish to name thy ship? from.Prompt = new RenameBoatPrompt( this ); } public void EndRename( Mobile from, string newName ) { if ( Deleted || CheckDecay() ) return; if ( from.AccessLevel < AccessLevel.GameMaster && from != m_Owner ) { if ( m_TillerMan != null ) m_TillerMan.Say( 1042880 ); // Arr! Only the owner of the ship may change its name! return; } else if ( !from.Alive ) { if ( m_TillerMan != null ) m_TillerMan.Say( 502582 ); // You appear to be dead. return; } newName = newName.Trim(); if ( newName.Length == 0 ) newName = null; Rename( newName ); } public enum DryDockResult{ Valid, Dead, NoKey, NotAnchored, Mobiles, Items, Hold, Decaying } public DryDockResult CheckDryDock( Mobile from ) { if ( CheckDecay() ) return DryDockResult.Decaying; if ( !from.Alive ) return DryDockResult.Dead; Container pack = from.Backpack; if ( (m_SPlank == null || !Key.ContainsKey( pack, m_SPlank.KeyValue )) && (m_PPlank == null || !Key.ContainsKey( pack, m_PPlank.KeyValue )) ) return DryDockResult.NoKey; if ( !m_Anchored ) return DryDockResult.NotAnchored; if ( m_Hold != null && m_Hold.Items.Count > 0 ) return DryDockResult.Hold; Map map = Map; if ( map == null || map == Map.Internal ) return DryDockResult.Items; MultiComponentList mcl = Components; IPooledEnumerable eable = map.GetObjectsInBounds( new Rectangle2D( X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height ) ); foreach ( object o in eable ) { if ( o == this || o == m_Hold || o == m_SPlank || o == m_PPlank || o == m_TillerMan ) continue; if ( o is Item && Contains( (Item)o ) ) { eable.Free(); return DryDockResult.Items; } else if ( o is Mobile && Contains( (Mobile)o ) ) { eable.Free(); return DryDockResult.Mobiles; } } eable.Free(); return DryDockResult.Valid; } public void BeginDryDock( Mobile from ) { if ( CheckDecay() ) return; if ( !NearDock( from ) ) { from.SendMessage( "You cannot dock a ship here" ); return; } DryDockResult result = CheckDryDock( from ); if ( result == DryDockResult.Dead ) from.SendLocalizedMessage( 502493 ); // You appear to be dead. else if ( result == DryDockResult.NoKey ) from.SendLocalizedMessage( 502494 ); // You must have a key to the ship to dock the boat. else if ( result == DryDockResult.NotAnchored ) from.SendLocalizedMessage( 1010570 ); // You must lower the anchor to dock the boat. else if ( result == DryDockResult.Mobiles ) from.SendLocalizedMessage( 502495 ); // You cannot dock the ship with beings on board! else if ( result == DryDockResult.Items ) from.SendLocalizedMessage( 502496 ); // You cannot dock the ship with a cluttered deck. else if ( result == DryDockResult.Hold ) from.SendLocalizedMessage( 502497 ); // Make sure your hold is empty, and try again! else if ( result == DryDockResult.Valid ) from.SendGump( new ConfirmDryDockGump( from, this ) ); } public void EndDryDock( Mobile from ) { if ( Deleted || CheckDecay() ) return; if ( !NearDock( from ) ) { from.SendMessage( "You cannot dock a ship here" ); return; } DryDockResult result = CheckDryDock( from ); if ( result == DryDockResult.Dead ) from.SendLocalizedMessage( 502493 ); // You appear to be dead. else if ( result == DryDockResult.NoKey ) from.SendLocalizedMessage( 502494 ); // You must have a key to the ship to dock the boat. else if ( result == DryDockResult.NotAnchored ) from.SendLocalizedMessage( 1010570 ); // You must lower the anchor to dock the boat. else if ( result == DryDockResult.Mobiles ) from.SendLocalizedMessage( 502495 ); // You cannot dock the ship with beings on board! else if ( result == DryDockResult.Items ) from.SendLocalizedMessage( 502496 ); // You cannot dock the ship with a cluttered deck. else if ( result == DryDockResult.Hold ) from.SendLocalizedMessage( 502497 ); // Make sure your hold is empty, and try again! if ( result != DryDockResult.Valid ) return; BaseDockedBoat boat = DockedBoat; if ( boat == null ) return; Effects.PlaySound( from.Location, from.Map, 0x026 ); RemoveKeys( from ); from.AddToBackpack( boat ); Delete(); } public void SetName( SpeechEventArgs e ) { if ( CheckDecay() ) return; if ( e.Mobile.AccessLevel < AccessLevel.GameMaster && e.Mobile != m_Owner ) { if ( m_TillerMan != null ) m_TillerMan.Say( 1042880 ); // Arr! Only the owner of the ship may change its name! return; } else if ( !e.Mobile.Alive ) { if ( m_TillerMan != null ) m_TillerMan.Say( 502582 ); // You appear to be dead. return; } if ( e.Speech.Length > 8 ) { string newName = e.Speech.Substring( 8 ).Trim(); if ( newName.Length == 0 ) newName = null; Rename( newName ); } } public void Rename( string newName ) { if ( CheckDecay() ) return; if ( newName != null && newName.Length > 40 ) newName = newName.Substring( 0, 40 ); if ( m_ShipName == newName ) { if ( m_TillerMan != null ) m_TillerMan.Say( 502531 ); // Yes, sir. return; } ShipName = newName; if ( m_TillerMan != null && m_ShipName != null ) m_TillerMan.Say( 1042885, m_ShipName ); // This ship is now called the ~1_NEW_SHIP_NAME~. else if ( m_TillerMan != null ) m_TillerMan.Say( 502534 ); // This ship now has no name. } public void RemoveName( Mobile m ) { if ( CheckDecay() ) return; if ( m.AccessLevel < AccessLevel.GameMaster && m != m_Owner ) { if ( m_TillerMan != null ) m_TillerMan.Say( 1042880 ); // Arr! Only the owner of the ship may change its name! return; } else if ( !m.Alive ) { if ( m_TillerMan != null ) m_TillerMan.Say( 502582 ); // You appear to be dead. return; } if ( m_ShipName == null ) { if ( m_TillerMan != null ) m_TillerMan.Say( 502526 ); // Ar, this ship has no name. return; } ShipName = null; if ( m_TillerMan != null ) m_TillerMan.Say( 502534 ); // This ship now has no name. } public void GiveName( Mobile m ) { if ( m_TillerMan == null || CheckDecay() ) return; if ( m_ShipName == null ) m_TillerMan.Say( 502526 ); // Ar, this ship has no name. else m_TillerMan.Say( 1042881, m_ShipName ); // This is the ~1_BOAT_NAME~. } public void GiveNavPoint() { if ( TillerMan == null || CheckDecay() ) return; if ( NextNavPoint < 0 ) TillerMan.Say( 1042882 ); // I have no current nav point. else TillerMan.Say( 1042883, (NextNavPoint + 1).ToString() ); // My current destination navpoint is nav ~1_NAV_POINT_NUM~. } public void AssociateMap( MapItem map ) { if ( CheckDecay() ) return; if ( map is BlankMap ) { if ( TillerMan != null ) TillerMan.Say( 502575 ); // Ar, that is not a map, tis but a blank piece of paper! } else if ( map.Pins.Count == 0 ) { if ( TillerMan != null ) TillerMan.Say( 502576 ); // Arrrr, this map has no course on it! } else { StopMove( false ); MapItem = map; NextNavPoint = -1; if ( TillerMan != null ) TillerMan.Say( 502577 ); // A map! } } public bool StartCourse( string navPoint, bool single, bool message ) { int number = -1; int start = -1; for ( int i = 0; i < navPoint.Length; i++ ) { if ( Char.IsDigit( navPoint[i] ) ) { start = i; break; } } if ( start != -1 ) { string sNumber = navPoint.Substring( start ); if ( !int.TryParse( sNumber, out number ) ) number = -1; if ( number != -1 ) { number--; if ( MapItem == null || number < 0 || number >= MapItem.Pins.Count ) { number = -1; } } } if ( number == -1 ) { if ( message && TillerMan != null ) TillerMan.Say( 1042551 ); // I don't see that navpoint, sir. return false; } NextNavPoint = number; return StartCourse( single, message ); } public bool StartCourse( bool single, bool message ) { if ( CheckDecay() ) return false; if ( Anchored ) { if ( message && TillerMan != null ) TillerMan.Say( 501419 ); // Ar, the anchor is down sir! return false; } else if ( MapItem == null || MapItem.Deleted ) { if ( message && TillerMan != null ) TillerMan.Say( 502513 ); // I have seen no map, sir. return false; } else if ( this.Map != MapItem.Map || !this.Contains( MapItem.GetWorldLocation() ) ) { if ( message && TillerMan != null ) TillerMan.Say( 502514 ); // The map is too far away from me, sir. return false; } else if ( this.Map != Map.Britannia || NextNavPoint < 0 || NextNavPoint >= MapItem.Pins.Count ) { if ( message && TillerMan != null ) TillerMan.Say( 1042551 ); // I don't see that navpoint, sir. return false; } Speed = FastSpeed; Order = single ? BoatOrder.Single : BoatOrder.Course; if ( m_MoveTimer != null ) m_MoveTimer.Stop(); m_MoveTimer = new MoveTimer( this, FastInterval, false ); m_MoveTimer.Start(); if ( message && TillerMan != null ) TillerMan.Say( 501429 ); // Aye aye sir. return true; } public override bool HandlesOnSpeech{ get{ return true; } } public override void OnSpeech( SpeechEventArgs e ) { if ( CheckDecay() ) return; Mobile from = e.Mobile; if ( CanCommand( from ) && Contains( from ) ) { for ( int i = 0; i < e.Keywords.Length; ++i ) { int keyword = e.Keywords[i]; if ( keyword >= 0x42 && keyword <= 0x6B ) { switch ( keyword ) { case 0x42: SetName( e ); break; case 0x43: RemoveName( e.Mobile ); break; case 0x44: GiveName( e.Mobile ); break; case 0x45: StartMove( Forward, true ); break; case 0x46: StartMove( Backward, true ); break; case 0x47: StartMove( Left, true ); break; case 0x48: StartMove( Right, true ); break; case 0x4B: StartMove( ForwardLeft, true ); break; case 0x4C: StartMove( ForwardRight, true ); break; case 0x4D: StartMove( BackwardLeft, true ); break; case 0x4E: StartMove( BackwardRight, true ); break; case 0x4F: StopMove( true ); break; case 0x50: StartMove( Left, false ); break; case 0x51: StartMove( Right, false ); break; case 0x52: StartMove( Forward, false ); break; case 0x53: StartMove( Backward, false ); break; case 0x54: StartMove( ForwardLeft, false ); break; case 0x55: StartMove( ForwardRight, false ); break; case 0x56: StartMove( BackwardRight, false ); break; case 0x57: StartMove( BackwardLeft, false ); break; case 0x58: OneMove( Left ); break; case 0x59: OneMove( Right ); break; case 0x5A: OneMove( Forward ); break; case 0x5B: OneMove( Backward ); break; case 0x5C: OneMove( ForwardLeft ); break; case 0x5D: OneMove( ForwardRight ); break; case 0x5E: OneMove( BackwardRight ); break; case 0x5F: OneMove( BackwardLeft ); break; case 0x49: case 0x65: StartTurn( 2, true ); break; // turn right case 0x4A: case 0x66: StartTurn( -2, true ); break; // turn left case 0x67: StartTurn( -4, true ); break; // turn around, come about case 0x68: StartMove( Forward, true ); break; case 0x69: StopMove( true ); break; case 0x6A: LowerAnchor( true ); break; case 0x6B: RaiseAnchor( true ); break; case 0x60: GiveNavPoint(); break; // nav case 0x61: NextNavPoint = 0; StartCourse( false, true ); break; // start case 0x62: StartCourse( false, true ); break; // continue case 0x63: StartCourse( e.Speech, false, true ); break; // goto* case 0x64: StartCourse( e.Speech, true, true ); break; // single* } break; } } } } public bool StartTurn( int offset, bool message ) { if ( CheckDecay() ) return false; if ( m_Anchored ) { if ( message ) m_TillerMan.Say( 501419 ); // Ar, the anchor is down sir! return false; } else { if ( m_MoveTimer != null && this.Order != BoatOrder.Move ) { m_MoveTimer.Stop(); m_MoveTimer = null; } if ( m_TurnTimer != null ) m_TurnTimer.Stop(); m_TurnTimer = new TurnTimer( this, offset ); m_TurnTimer.Start(); if ( message && TillerMan != null ) TillerMan.Say( 501429 ); // Aye aye sir. return true; } } public bool Turn( int offset, bool message ) { if ( m_TurnTimer != null ) { m_TurnTimer.Stop(); m_TurnTimer = null; } if ( CheckDecay() ) return false; if ( m_Anchored ) { if ( message ) m_TillerMan.Say( 501419 ); // Ar, the anchor is down sir! return false; } else if ( SetFacing( (Direction)(((int)m_Facing + offset) & 0x7) ) ) { return true; } else { if ( message ) m_TillerMan.Say( 501423 ); // Ar, can't turn sir. return false; } } private class TurnTimer : Timer { private BaseBoat m_Boat; private int m_Offset; public TurnTimer( BaseBoat boat, int offset ) : base( TimeSpan.FromSeconds( 0.5 ) ) { m_Boat = boat; m_Offset = offset; Priority = TimerPriority.TenMS; } protected override void OnTick() { if ( !m_Boat.Deleted ) m_Boat.Turn( m_Offset, true ); } } public bool StartMove( Direction dir, int speed, TimeSpan interval, bool single, bool message ) { if ( CheckDecay() ) return false; if ( m_Anchored ) { if ( message && m_TillerMan != null ) m_TillerMan.Say( 501419 ); // Ar, the anchor is down sir! return false; } m_Moving = dir; m_Speed = speed; m_Order = BoatOrder.Move; if ( m_MoveTimer != null ) m_MoveTimer.Stop(); m_MoveTimer = new MoveTimer( this, interval, single ); m_MoveTimer.Start(); return true; } public bool StopMove( bool message ) { if ( CheckDecay() ) return false; if ( m_MoveTimer == null ) { if ( message && m_TillerMan != null ) m_TillerMan.Say( 501443 ); // Er, the ship is not moving sir. return false; } m_Moving = Direction.North; m_Speed = 0; m_MoveTimer.Stop(); m_MoveTimer = null; if ( message && m_TillerMan != null ) m_TillerMan.Say( 501429 ); // Aye aye sir. return true; } public bool CanFit( Point3D p, Map map, int itemID ) { if ( map == null || map == Map.Internal || Deleted || CheckDecay() ) return false; MultiComponentList newComponents = MultiData.GetComponents( itemID ); for ( int x = 0; x < newComponents.Width; ++x ) { for ( int y = 0; y < newComponents.Height; ++y ) { int tx = p.X + newComponents.Min.X + x; int ty = p.Y + newComponents.Min.Y + y; if ( newComponents.Tiles[x][y].Length == 0 || Contains( tx, ty ) ) continue; LandTile landTile = map.Tiles.GetLandTile( tx, ty ); StaticTile[] tiles = map.Tiles.GetStaticTiles( tx, ty, true ); bool hasWater = false; if ( landTile.Z == p.Z && ((landTile.ID >= 168 && landTile.ID <= 171) || (landTile.ID >= 310 && landTile.ID <= 311)) ) hasWater = true; int z = p.Z; //int landZ = 0, landAvg = 0, landTop = 0; //map.GetAverageZ( tx, ty, ref landZ, ref landAvg, ref landTop ); //if ( !landTile.Ignored && top > landZ && landTop > z ) // return false; for ( int i = 0; i < tiles.Length; ++i ) { StaticTile tile = tiles[i]; bool isWater = ( tile.ID >= 0x1796 && tile.ID <= 0x17B2 ); if ( tile.Z == p.Z && isWater ) hasWater = true; else if ( tile.Z >= p.Z && !isWater ) return false; } if ( !hasWater ) return false; } } IPooledEnumerable eable = map.GetItemsInBounds( new Rectangle2D( p.X + newComponents.Min.X, p.Y + newComponents.Min.Y, newComponents.Width, newComponents.Height ) ); foreach ( Item item in eable ) { if ( item is BaseMulti || item.ItemID > TileData.MaxItemValue || item.Z < p.Z || !item.Visible ) continue; int x = item.X - p.X + newComponents.Min.X; int y = item.Y - p.Y + newComponents.Min.Y; if ( x >= 0 && x < newComponents.Width && y >= 0 && y < newComponents.Height && newComponents.Tiles[x][y].Length == 0 ) continue; else if ( Contains( item ) ) continue; eable.Free(); return false; } eable.Free(); return true; } public Point3D Rotate( Point3D p, int count ) { int rx = p.X - Location.X; int ry = p.Y - Location.Y; for ( int i = 0; i < count; ++i ) { int temp = rx; rx = -ry; ry = temp; } return new Point3D( Location.X + rx, Location.Y + ry, p.Z ); } public override bool Contains( int x, int y ) { if ( base.Contains( x, y ) ) return true; if ( m_TillerMan != null && x == m_TillerMan.X && y == m_TillerMan.Y ) return true; if ( m_Hold != null && x == m_Hold.X && y == m_Hold.Y ) return true; if ( m_PPlank != null && x == m_PPlank.X && y == m_PPlank.Y ) return true; if ( m_SPlank != null && x == m_SPlank.X && y == m_SPlank.Y ) return true; return false; } public static bool IsValidLocation( Point3D p, Map map ) { Rectangle2D[] wrap = GetWrapFor( map ); for ( int i = 0; i < wrap.Length; ++i ) { if ( wrap[i].Contains( p ) ) return true; } return false; } public static Rectangle2D[] GetWrapFor( Map m ) { return m_SeaWrap; } public Direction GetMovementFor( int x, int y, out int maxSpeed ) { int dx = x - this.X; int dy = y - this.Y; int adx = Math.Abs( dx ); int ady = Math.Abs( dy ); Direction dir = Utility.GetDirection( this, new Point2D( x, y ) ); int iDir = (int) dir; // Compute the maximum distance we can travel without going too far away if ( iDir % 2 == 0 ) // North, East, South and West maxSpeed = Math.Abs( adx - ady ); else // Right, Down, Left and Up maxSpeed = Math.Min( adx, ady ); return (Direction) ((iDir - (int)Facing) & 0x7); } public bool DoMovement( bool message ) { Direction dir; int speed; if ( this.Order == BoatOrder.Move ) { dir = this.Moving; speed = this.Speed; } else if ( MapItem == null || MapItem.Deleted ) { if ( message && TillerMan != null ) TillerMan.Say( 502513 ); // I have seen no map, sir. return false; } else if ( this.Map != MapItem.Map || !this.Contains( MapItem.GetWorldLocation() ) ) { if ( message && TillerMan != null ) TillerMan.Say( 502514 ); // The map is too far away from me, sir. return false; } else if ( this.Map != Map.Britannia || NextNavPoint < 0 || NextNavPoint >= MapItem.Pins.Count ) { if ( message && TillerMan != null ) TillerMan.Say( 1042551 ); // I don't see that navpoint, sir. return false; } else { Point2D dest = (Point2D) MapItem.Pins[NextNavPoint]; int x, y; MapItem.ConvertToWorld( dest.X, dest.Y, out x, out y ); int maxSpeed; dir = GetMovementFor( x, y, out maxSpeed ); if ( maxSpeed == 0 ) { if ( message && this.Order == BoatOrder.Single && TillerMan != null ) TillerMan.Say( 1042874, (NextNavPoint + 1).ToString() ); // We have arrived at nav point ~1_POINT_NUM~ , sir. if ( NextNavPoint + 1 < MapItem.Pins.Count ) { NextNavPoint++; if ( this.Order == BoatOrder.Course ) { if ( message && TillerMan != null ) TillerMan.Say( 1042875, (NextNavPoint + 1).ToString() ); // Heading to nav point ~1_POINT_NUM~, sir. return true; } return false; } else { NextNavPoint = -1; if ( message && this.Order == BoatOrder.Course && TillerMan != null ) TillerMan.Say( 502515 ); // The course is completed, sir. return false; } } if ( dir == Left || dir == BackwardLeft || dir == Backward ) return Turn( -2, true ); else if ( dir == Right || dir == BackwardRight ) return Turn( 2, true ); speed = Math.Min( this.Speed, maxSpeed ); } return Move( dir, speed, true ); } public bool Move( Direction dir, int speed, bool message ) { Map map = Map; if ( map == null || Deleted || CheckDecay() ) return false; if ( m_Anchored ) { if ( message && m_TillerMan != null ) m_TillerMan.Say( 501419 ); // Ar, the anchor is down sir! return false; } int rx = 0, ry = 0; Movement.Movement.Offset( (Direction)(((int)m_Facing + (int)dir) & 0x7), ref rx, ref ry ); for ( int i = 1; i <= speed; ++i ) { if ( !CanFit( new Point3D( X + (i * rx), Y + (i * ry), Z ), Map, ItemID ) ) { if ( i == 1 ) { if ( message && m_TillerMan != null ) m_TillerMan.Say( 501424 ); // Ar, we've stopped sir. return false; } speed = i - 1; break; } } int xOffset = speed*rx; int yOffset = speed*ry; int newX = X + xOffset; int newY = Y + yOffset; Rectangle2D[] wrap = GetWrapFor( map ); for ( int i = 0; i < wrap.Length; ++i ) { Rectangle2D rect = wrap[i]; if ( rect.Contains( new Point2D( X, Y ) ) && !rect.Contains( new Point2D( newX, newY ) ) ) { if ( newX < rect.X ) newX = rect.X + rect.Width - 1; else if ( newX >= rect.X + rect.Width ) newX = rect.X; if ( newY < rect.Y ) newY = rect.Y + rect.Height - 1; else if ( newY >= rect.Y + rect.Height ) newY = rect.Y; for ( int j = 1; j <= speed; ++j ) { if ( !CanFit( new Point3D( newX + (j * rx), newY + (j * ry), Z ), Map, ItemID ) ) { if ( message && m_TillerMan != null ) m_TillerMan.Say( 501424 ); // Ar, we've stopped sir. return false; } } xOffset = newX - X; yOffset = newY - Y; } } Teleport( xOffset, yOffset, 0 ); return true; } public void Teleport( int xOffset, int yOffset, int zOffset ) { MultiComponentList mcl = Components; ArrayList toMove = new ArrayList(); IPooledEnumerable eable = this.Map.GetObjectsInBounds( new Rectangle2D( X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height ) ); foreach ( object o in eable ) { if ( o != this && !(o is TillerMan || o is Hold || o is Plank) ) toMove.Add( o ); } eable.Free(); for ( int i = 0; i < toMove.Count; ++i ) { object o = toMove[i]; if ( o is Item ) { Item item = (Item)o; if ( Contains( item ) && item.Visible && item.Z >= Z ) item.Location = new Point3D( item.X + xOffset, item.Y + yOffset, item.Z + zOffset ); } else if ( o is Mobile ) { Mobile m = (Mobile)o; if ( Contains( m ) ) m.Location = new Point3D( m.X + xOffset, m.Y + yOffset, m.Z + zOffset ); } } Location = new Point3D( X + xOffset, Y + yOffset, Z + zOffset ); } public bool SetFacing( Direction facing ) { if ( Parent != null || this.Map == null ) return false; if ( CheckDecay() ) return false; if ( Map != Map.Internal ) { switch ( facing ) { case Direction.North: if ( !CanFit( Location, Map, NorthID ) ) return false; break; case Direction.East: if ( !CanFit( Location, Map, EastID ) ) return false; break; case Direction.South: if ( !CanFit( Location, Map, SouthID ) ) return false; break; case Direction.West: if ( !CanFit( Location, Map, WestID ) ) return false; break; } } this.Map.OnLeave( this ); Direction old = m_Facing; m_Facing = facing; if ( m_TillerMan != null ) m_TillerMan.SetFacing( facing ); if ( m_Hold != null ) m_Hold.SetFacing( facing ); if ( m_PPlank != null ) m_PPlank.SetFacing( facing ); if ( m_SPlank != null ) m_SPlank.SetFacing( facing ); MultiComponentList mcl = Components; ArrayList toMove = new ArrayList(); toMove.Add( m_PPlank ); toMove.Add( m_SPlank ); IPooledEnumerable eable = Map.GetObjectsInBounds( new Rectangle2D( X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height ) ); foreach ( object o in eable ) { if ( o is Item ) { Item item = (Item)o; if ( item != this && Contains( item ) && item.Visible && item.Z >= Z && !(item is TillerMan || item is Hold || item is Plank) ) toMove.Add( item ); } else if ( o is Mobile && Contains( (Mobile)o ) ) { toMove.Add( o ); ((Mobile)o).Direction = (Direction)((int)((Mobile)o).Direction - (int)old + (int)facing); } } eable.Free(); int xOffset = 0, yOffset = 0; Movement.Movement.Offset( facing, ref xOffset, ref yOffset ); if ( m_TillerMan != null ) m_TillerMan.Location = new Point3D( X + (xOffset * TillerManDistance) + (facing == Direction.North ? 1 : 0), Y + (yOffset * TillerManDistance), m_TillerMan.Z ); if ( m_Hold != null ) m_Hold.Location = new Point3D( X + (xOffset * HoldDistance), Y + (yOffset * HoldDistance), m_Hold.Z ); int count = (int)(m_Facing - old) & 0x7; count /= 2; for ( int i = 0; i < toMove.Count; ++i ) { object o = toMove[i]; if ( o is Item ) ((Item)o).Location = Rotate( ((Item)o).Location, count ); else if ( o is Mobile ) ((Mobile)o).Location = Rotate( ((Mobile)o).Location, count ); } switch ( facing ) { case Direction.North: ItemID = NorthID; break; case Direction.East: ItemID = EastID; break; case Direction.South: ItemID = SouthID; break; case Direction.West: ItemID = WestID; break; } this.Map.OnEnter( this ); return true; } private class MoveTimer : Timer { private BaseBoat m_Boat; public MoveTimer( BaseBoat boat, TimeSpan interval, bool single ) : base( interval, interval, single ? 1 : 0 ) { m_Boat = boat; Priority = TimerPriority.TwentyFiveMS; } protected override void OnTick() { if ( !m_Boat.DoMovement( true ) ) m_Boat.StopMove( false ); } } public static void UpdateAllComponents() { for ( int i = m_Instances.Count - 1; i >= 0; --i ) m_Instances[i].UpdateComponents(); } public static void Initialize() { new UpdateAllTimer().Start(); EventSink.WorldSave += new WorldSaveEventHandler( EventSink_WorldSave ); } private static void EventSink_WorldSave( WorldSaveEventArgs e ) { new UpdateAllTimer().Start(); } public class UpdateAllTimer : Timer { public UpdateAllTimer() : base( TimeSpan.FromSeconds( 1.0 ) ) { } protected override void OnTick() { UpdateAllComponents(); } } } }