ModernUO/Projects/Scripts/Multis/Boats/BaseBoat.cs
Kamron Batman 179cb50557 Updates Packets & Randomizer (#43)
* Fixes a bug with the main loop. Removes unnecessary optimizations for RDRand.

* WIP - Rewriting packets.

* Cleanup and updates README

* Converts more packets

* Fixes header

* Converts Effects packets.

* Forgot the send command

* Adds the start of containers packets.

* Adds message packets with caching

* Extends the send methods

* Starts to add Acquire methods

* Converted the packets

* Cleanup

* Cleanup

* Adds more packets

* Adds more packets

* Fixes clearing arrays from pool. Adds Mobile Incoming

* Converts more packets.

* Moves more packets

* Moves more packets

* Test compression

* Merges

* Migrating to an idiomatic syntax that also supports compression, and proxying.

* Optimize the stack alloc. Changes attributes

* Converted container packets

* Visual Studio doesn't auto save files. I am still getting used to subpar IDEs.

* Adds static packet caching. Will profile later. Converts more packets

* Fixes packets and changes how compression is configured

* WIP - Adds basics for Gumps. Not finished though.

* Fix file

* Fixes formatting

* Changed SpanWriter to be more idiomatic.

* Fixes Span vs RawSpan and missing stackallocs

* Converts over some more gumps

* WIP - Deletes 32bit support.

* Drops RDRand32 support.

* Fixes gump compilation

* Converting gump components

* Removes old huffman compression function

* Revert signature for backwards compatibility

* WIP - Converting more gump components

* Creates ArraySet for the strings. Updates AppendTo to reference that.

* Converts the maining gump components

* Cleans up gump components

* Cleans up directives

* Cleans up ArraySet and moves it. Adds null-coalescing-assignment

* Cleanup, Target Packets, and C# 8 changes.

* Removed OPL Packet

* World packets

* Fixing packet uses

* Cleans up code. Fixes packet uses in various places.

* More cleanup for packets

* Code cleanup

* Converts more packet uses and cleans up more code

* More code cleanup

* Finishes fixing the packets in Item

* Updates secure trade packets

* Updates core and gets it to compile.

* Moved packets to scripts. Fixed account handler use of packets

* Code cleanup

* Updates chat packets

* Code cleanuo

* Adds party packets, but need to implement them.

* Party packets WIP

* Finishes party packets

* Rearrange movement namespaces and classes

* Finishes plant packets

* Code Formatting

* Fixes moving effects

* Code cleanup, eliminates equipinfo

* Adds more packets. Fixes bugs with various packets.

* Finishes mahjon packets

* Fixes mahjong packet

* Code cleanup

* Finishes mahjong packets

* Adds Map packets

* Add multifacet maps and charts

* Cleans up some packets with UTF8

* Optimizes packets

* Cleans up more packets. Moves the MessageHelper

* Removes assistant support. Removes extended protocol. Incorporates MapUO packets as normal packets.

* Updates protocol extensions packet receiver

* Fixes a few bugs. Fixes a few more packets.

* Code cleanup and fixing more packets

* Fixes packet effects

* Cleaned up more code

* Buff Icon cleanup

* Removed unused constructors

* Code Cleanup. Adds BoatHS Packets

* Moves house files. Updates house foundation packets.

* Deployment cleanup

* Fixes:

* Code cleanup

* More code cleanup

* More code cleanup

* Cleaned up BaseHouse

* Enforces styling

* Converts foreach to linq where possible.

* Dont need that

* Goals/Readme updates

* Removes 32bit support at the highest level. Turns on HRT by default.

* Code cleanup. Fixes extended features packet.

* Fixes various bugs

* Code cleanup

* Code cleanup

* Code cleanup. Fixes gump X/Y assignment.

* Code cleanup using |= operator

* More code cleanup

* Cleanup

* Fixes spacing issues. Thanks Visual Studio. You suck.

* Compiler error

* Fixes NPE from RunUO 2.7

* Renames ScriptCompiler to AssemblyHandler. Fixes packets. Updates README

* Fixes more packets. Stupid trailing nulls.

* Fixes various bugs.

* Fixes for gumps

* Fixes more gump stuff. Going to split it out later since it is getting insane

* Recoded the gump writing

* WIP

* *Added output path of scripts project to dev branch
*Activated debugging in code
2019-11-11 12:40:16 +01:00

1747 lines
46 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using Server.Items;
using Server.Network;
namespace Server.Multis
{
public enum BoatOrder
{
Move,
Course,
Single
}
public abstract class BaseBoat : BaseMulti
{
public enum DryDockResult
{
Valid,
Dead,
NoKey,
NotAnchored,
Mobiles,
Items,
Hold,
Decaying
}
private static Rectangle2D[] m_BritWrap =
{ new Rectangle2D(16, 16, 5120 - 32, 4096 - 32), new Rectangle2D(5136, 2320, 992, 1760) };
private static Rectangle2D[] m_IlshWrap = { new Rectangle2D(16, 16, 2304 - 32, 1600 - 32) };
private static Rectangle2D[] m_TokunoWrap = { new Rectangle2D(16, 16, 1448 - 32, 1448 - 32) };
private static TimeSpan BoatDecayDelay = TimeSpan.FromDays(9.0);
private static TimeSpan SlowInterval = TimeSpan.FromSeconds(NewBoatMovement ? 0.50 : 0.75);
private static TimeSpan FastInterval = TimeSpan.FromSeconds(NewBoatMovement ? 0.25 : 0.75);
private static int SlowSpeed = 1;
private static int FastSpeed = NewBoatMovement ? 1 : 3;
private static TimeSpan SlowDriftInterval = TimeSpan.FromSeconds(NewBoatMovement ? 0.50 : 1.50);
private static TimeSpan FastDriftInterval = TimeSpan.FromSeconds(NewBoatMovement ? 0.25 : 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 int m_ClientSpeed;
private bool m_Decaying;
private DateTime m_DecayTime;
private Direction m_Facing;
private Timer m_MoveTimer;
private string m_ShipName;
private Timer m_TurnTimer;
public BaseBoat() : base(0x0)
{
m_DecayTime = DateTime.UtcNow + BoatDecayDelay;
TillerMan = new TillerMan(this);
Hold = new Hold(this);
PPlank = new Plank(this, PlankSide.Port, 0);
SPlank = new Plank(this, PlankSide.Starboard, 0);
PPlank.MoveToWorld(new Point3D(X + PortOffset.X, Y + PortOffset.Y, Z), Map);
SPlank.MoveToWorld(new Point3D(X + StarboardOffset.X, Y + StarboardOffset.Y, Z), Map);
Facing = Direction.North;
NextNavPoint = -1;
Movable = false;
Boats.Add(this);
}
public BaseBoat(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public Hold Hold{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TillerMan TillerMan{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Plank PPlank{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Plank SPlank{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Mobile Owner{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Direction Facing
{
get => m_Facing;
set => SetFacing(value);
}
[CommandProperty(AccessLevel.GameMaster)]
public Direction Moving{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public bool IsMoving => m_MoveTimer != null;
[CommandProperty(AccessLevel.GameMaster)]
public int Speed{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public bool Anchored{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public string ShipName
{
get => m_ShipName;
set
{
m_ShipName = value;
TillerMan?.InvalidateProperties();
}
}
[CommandProperty(AccessLevel.GameMaster)]
public BoatOrder Order{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public MapItem MapItem{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public int NextNavPoint{ get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DateTime TimeOfDecay
{
get => m_DecayTime;
set
{
m_DecayTime = value;
TillerMan?.InvalidateProperties();
}
}
public int Status
{
get
{
DateTime start = TimeOfDecay - BoatDecayDelay;
if (DateTime.UtcNow - start < TimeSpan.FromHours(1.0))
return 1043010; // This structure is like new.
if (DateTime.UtcNow - start < TimeSpan.FromDays(2.0))
return 1043011; // This structure is slightly worn.
if (DateTime.UtcNow - start < TimeSpan.FromDays(3.0))
return 1043012; // This structure is somewhat worn.
if (DateTime.UtcNow - start < TimeSpan.FromDays(4.0))
return 1043013; // This structure is fairly worn.
if (DateTime.UtcNow - 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 => 0;
public virtual int EastID => 0;
public virtual int SouthID => 0;
public virtual int WestID => 0;
public virtual int HoldDistance => 0;
public virtual int TillerManDistance => 0;
public virtual Point2D StarboardOffset => Point2D.Zero;
public virtual Point2D PortOffset => Point2D.Zero;
public virtual Point3D MarkOffset => Point3D.Zero;
public virtual BaseDockedBoat DockedBoat => null;
public static List<BaseBoat> Boats{ get; } = new List<BaseBoat>();
/*
* Intervals:
* drift forward
* fast | 0.25| 0.25
* slow | 0.50| 0.50
*
* Speed:
* drift forward
* fast | 0x4| 0x4
* slow | 0x3| 0x3
*
* Tiles (per interval):
* drift forward
* fast | 1| 1
* slow | 1| 1
*
* 'walking' in piloting mode has a 1s interval, speed 0x2
*/
private static bool NewBoatMovement => Core.HS;
public override bool HandlesOnSpeech => true;
public static BaseBoat FindBoatAt(IPoint2D loc, Map map)
{
Sector sector = map.GetSector(loc);
for (int i = 0; i < sector.Multis.Count; i++)
if (sector.Multis[i] is BaseBoat boat && boat.Contains(loc.X, loc.Y))
return boat;
return null;
}
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 (PPlank != null)
{
PPlank.MoveToWorld(GetRotatedLocation(PortOffset.X, PortOffset.Y), Map);
PPlank.SetFacing(m_Facing);
}
if (SPlank != null)
{
SPlank.MoveToWorld(GetRotatedLocation(StarboardOffset.X, StarboardOffset.Y), Map);
SPlank.SetFacing(m_Facing);
}
int xOffset = 0, yOffset = 0;
Movement.Offset(m_Facing, ref xOffset, ref yOffset);
if (TillerMan != null)
{
TillerMan.Location = new Point3D(X + xOffset * TillerManDistance + (m_Facing == Direction.North ? 1 : 0),
Y + yOffset * TillerManDistance, TillerMan.Z);
TillerMan.SetFacing(m_Facing);
TillerMan.InvalidateProperties();
}
if (Hold != null)
{
Hold.Location = new Point3D(X + xOffset * HoldDistance, Y + yOffset * HoldDistance, Hold.Z);
Hold.SetFacing(m_Facing);
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(3);
writer.Write(MapItem);
writer.Write(NextNavPoint);
writer.Write((int)m_Facing);
writer.WriteDeltaTime(m_DecayTime);
writer.Write(Owner);
writer.Write(PPlank);
writer.Write(SPlank);
writer.Write(TillerMan);
writer.Write(Hold);
writer.Write(Anchored);
writer.Write(m_ShipName);
CheckDecay();
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 3:
{
MapItem = (MapItem)reader.ReadItem();
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)
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;
}
Owner = reader.ReadMobile();
PPlank = reader.ReadItem() as Plank;
SPlank = reader.ReadItem() as Plank;
TillerMan = reader.ReadItem() as TillerMan;
Hold = reader.ReadItem() as Hold;
Anchored = reader.ReadBool();
m_ShipName = reader.ReadString();
if (version < 1)
Refresh();
break;
}
}
Boats.Add(this);
}
public void RemoveKeys(Mobile m)
{
uint keyValue = 0;
if (PPlank != null)
keyValue = PPlank.KeyValue;
if (keyValue == 0 && SPlank != null)
keyValue = SPlank.KeyValue;
Key.RemoveKeys(m, keyValue);
}
public uint CreateKeys(Mobile m)
{
uint value = Key.RandomValue();
Key packKey = new Key(KeyType.Gold, value, this);
Key bankKey = new Key(KeyType.Gold, value, this);
packKey.MaxRange = 10;
bankKey.MaxRange = 10;
packKey.Name = "a ship key";
bankKey.Name = "a ship key";
BankBox box = m.BankBox;
if (!box.TryDropItem(m, bankKey, false))
bankKey.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()
{
TillerMan?.Delete();
Hold?.Delete();
PPlank?.Delete();
SPlank?.Delete();
m_TurnTimer?.Stop();
m_MoveTimer?.Stop();
Boats.Remove(this);
}
public override void OnLocationChange(Point3D old)
{
if (TillerMan != null)
TillerMan.Location = new Point3D(X + (TillerMan.X - old.X), Y + (TillerMan.Y - old.Y),
Z + (TillerMan.Z - old.Z));
if (Hold != null)
Hold.Location = new Point3D(X + (Hold.X - old.X), Y + (Hold.Y - old.Y), Z + (Hold.Z - old.Z));
if (PPlank != null)
PPlank.Location = new Point3D(X + (PPlank.X - old.X), Y + (PPlank.Y - old.Y), Z + (PPlank.Z - old.Z));
if (SPlank != null)
SPlank.Location = new Point3D(X + (SPlank.X - old.X), Y + (SPlank.Y - old.Y), Z + (SPlank.Z - old.Z));
}
public override void OnMapChange()
{
if (TillerMan != null)
TillerMan.Map = Map;
if (Hold != null)
Hold.Map = Map;
if (PPlank != null)
PPlank.Map = Map;
if (SPlank != null)
SPlank.Map = Map;
}
public bool CanCommand(Mobile m) => 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) => SPlank?.KeyValue == keyValue || PPlank?.KeyValue == keyValue;
public void Refresh()
{
m_DecayTime = DateTime.UtcNow + BoatDecayDelay;
TillerMan?.InvalidateProperties();
}
public bool CheckDecay()
{
if (m_Decaying)
return true;
if (!IsMoving && DateTime.UtcNow >= m_DecayTime)
{
new DecayTimer(this).Start();
m_Decaying = true;
return true;
}
return false;
}
public bool LowerAnchor(bool message)
{
if (CheckDecay())
return false;
if (Anchored)
{
if (message)
TillerMan?.Say(501445); // Ar, the anchor was already dropped sir.
return false;
}
StopMove(false);
Anchored = true;
if (message)
TillerMan?.Say(501444); // Ar, anchor dropped sir.
return true;
}
public bool RaiseAnchor(bool message)
{
if (CheckDecay())
return false;
if (!Anchored)
{
if (message)
TillerMan?.Say(501447); // Ar, the anchor has not been dropped sir.
return false;
}
Anchored = false;
if (message)
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;
int clientSpeed = fast ? 0x4 : 0x3;
if (StartMove(dir, speed, clientSpeed, interval, false, true))
{
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, 0x1, interval, true, true))
{
TillerMan?.Say(501429); // Aye aye sir.
return true;
}
return false;
}
public void BeginRename(Mobile from)
{
if (CheckDecay())
return;
if (from.AccessLevel < AccessLevel.GameMaster && from != Owner)
{
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 Ill throw ye overhead!
return;
}
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 != Owner)
{
TillerMan?.Say(1042880); // Arr! Only the owner of the ship may change its name!
return;
}
if (!from.Alive)
{
TillerMan?.Say(502582); // You appear to be dead.
return;
}
newName = newName.Trim();
if (newName.Length == 0)
newName = null;
Rename(newName);
}
public DryDockResult CheckDryDock(Mobile from)
{
if (CheckDecay())
return DryDockResult.Decaying;
if (!from.Alive)
return DryDockResult.Dead;
Container pack = from.Backpack;
if ((SPlank == null || !Key.ContainsKey(pack, SPlank.KeyValue)) &&
(PPlank == null || !Key.ContainsKey(pack, PPlank.KeyValue)))
return DryDockResult.NoKey;
if (!Anchored)
return DryDockResult.NotAnchored;
if (Hold != null && Hold.Items.Count > 0)
return DryDockResult.Hold;
Map map = Map;
if (map == null || map == Map.Internal)
return DryDockResult.Items;
List<IEntity> ents = GetMovingEntities();
if (ents.Count >= 1)
return ents[0] is Mobile ? DryDockResult.Mobiles : DryDockResult.Items;
return DryDockResult.Valid;
}
public void BeginDryDock(Mobile from)
{
if (CheckDecay())
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;
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;
RemoveKeys(from);
from.AddToBackpack(boat);
Delete();
}
public void SetName(SpeechEventArgs e)
{
if (CheckDecay())
return;
if (e.Mobile.AccessLevel < AccessLevel.GameMaster && e.Mobile != Owner)
{
TillerMan?.Say(1042880); // Arr! Only the owner of the ship may change its name!
return;
}
if (!e.Mobile.Alive)
{
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?.Length > 40)
newName = newName.Substring(0, 40);
if (m_ShipName == newName)
{
TillerMan?.Say(502531); // Yes, sir.
return;
}
ShipName = newName;
if (TillerMan != null && m_ShipName != null)
TillerMan.Say(1042885, m_ShipName); // This ship is now called the ~1_NEW_SHIP_NAME~.
else
TillerMan?.Say(502534); // This ship now has no name.
}
public void RemoveName(Mobile m)
{
if (CheckDecay())
return;
if (m.AccessLevel < AccessLevel.GameMaster && m != Owner)
{
TillerMan?.Say(1042880); // Arr! Only the owner of the ship may change its name!
return;
}
if (!m.Alive)
{
TillerMan?.Say(502582); // You appear to be dead.
return;
}
if (m_ShipName == null)
{
TillerMan?.Say(502526); // Ar, this ship has no name.
return;
}
ShipName = null;
TillerMan?.Say(502534); // This ship now has no name.
}
public void GiveName(Mobile m)
{
if (TillerMan == null || CheckDecay())
return;
if (m_ShipName == null)
TillerMan.Say(502526); // Ar, this ship has no name.
else
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)
{
TillerMan?.Say(502575); // Ar, that is not a map, tis but a blank piece of paper!
}
else if (map.Pins.Count == 0)
{
TillerMan?.Say(502576); // Arrrr, this map has no course on it!
}
else
{
StopMove(false);
MapItem = map;
NextNavPoint = -1;
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?.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?.Say(501419); // Ar, the anchor is down sir!
return false;
}
if (MapItem?.Deleted != false)
{
if (message)
TillerMan?.Say(502513); // I have seen no map, sir.
return false;
}
if (Map != MapItem.Map || !Contains(MapItem.GetWorldLocation()))
{
if (message)
TillerMan?.Say(502514); // The map is too far away from me, sir.
return false;
}
if (Map != Map.Trammel && Map != Map.Felucca || NextNavPoint < 0 || NextNavPoint >= MapItem.Pins.Count)
{
if (message)
TillerMan?.Say(1042551); // I don't see that navpoint, sir.
return false;
}
Speed = FastSpeed;
Order = single ? BoatOrder.Single : BoatOrder.Course;
m_MoveTimer?.Stop();
m_MoveTimer = new MoveTimer(this, FastInterval, false);
m_MoveTimer.Start();
if (message)
TillerMan?.Say(501429); // Aye aye sir.
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 (Anchored)
{
if (message)
TillerMan.Say(501419); // Ar, the anchor is down sir!
return false;
}
if (m_MoveTimer != null && Order != BoatOrder.Move)
{
m_MoveTimer.Stop();
m_MoveTimer = null;
}
m_TurnTimer?.Stop();
m_TurnTimer = new TurnTimer(this, offset);
m_TurnTimer.Start();
if (message)
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 (Anchored)
{
if (message)
TillerMan.Say(501419); // Ar, the anchor is down sir!
return false;
}
if (SetFacing((Direction)((int)m_Facing + offset & 0x7))) return true;
if (message)
TillerMan.Say(501423); // Ar, can't turn sir.
return false;
}
public bool StartMove(Direction dir, int speed, int clientSpeed, TimeSpan interval, bool single, bool message)
{
if (CheckDecay())
return false;
if (Anchored)
{
if (message)
TillerMan?.Say(501419); // Ar, the anchor is down sir!
return false;
}
Moving = dir;
Speed = speed;
m_ClientSpeed = clientSpeed;
Order = BoatOrder.Move;
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)
TillerMan?.Say(501443); // Er, the ship is not moving sir.
return false;
}
Moving = Direction.North;
Speed = 0;
m_ClientSpeed = 0;
m_MoveTimer.Stop();
m_MoveTimer = null;
if (message)
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 = landTile.Z == p.Z &&
(landTile.ID >= 168 && landTile.ID <= 171 || landTile.ID >= 310 && landTile.ID <= 311);
// 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<Item> eable = map.GetItemsInBounds(new Rectangle2D(p.X + newComponents.Min.X,
p.Y + newComponents.Min.Y, newComponents.Width, newComponents.Height));
bool canFit = eable.All(item =>
{
if (item is BaseMulti || item.ItemID > TileData.MaxItemValue || item.Z < p.Z || !item.Visible)
return true;
int x = item.X - p.X + newComponents.Min.X;
int y = item.Y - p.Y + newComponents.Min.Y;
return x >= 0 && x < newComponents.Width && y >= 0 && y < newComponents.Height &&
newComponents.Tiles[x][y].Length == 0 || Contains(item);
});
eable.Free();
return canFit;
}
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) =>
base.Contains(x, y) ||
TillerMan?.X == x && y == TillerMan.Y ||
Hold?.X == x && Hold.Y == y ||
PPlank?.X == x && PPlank.Y == y ||
SPlank?.X == x && SPlank.Y == y;
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) => m == Map.Ilshenar ? m_IlshWrap : m == Map.Tokuno ? m_TokunoWrap : m_BritWrap;
public Direction GetMovementFor(int x, int y, out int maxSpeed)
{
int dx = x - X;
int dy = y - 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, clientSpeed;
if (Order == BoatOrder.Move)
{
dir = Moving;
speed = Speed;
clientSpeed = m_ClientSpeed;
}
else if (MapItem?.Deleted != false)
{
if (message)
TillerMan?.Say(502513); // I have seen no map, sir.
return false;
}
else if (Map != MapItem.Map || !Contains(MapItem.GetWorldLocation()))
{
if (message)
TillerMan?.Say(502514); // The map is too far away from me, sir.
return false;
}
else if (Map != Map.Trammel && Map != Map.Felucca || NextNavPoint < 0 || NextNavPoint >= MapItem.Pins.Count)
{
if (message)
TillerMan?.Say(1042551); // I don't see that navpoint, sir.
return false;
}
else
{
Point2D dest = MapItem.Pins[NextNavPoint];
MapItem.ConvertToWorld(dest.X, dest.Y, out int x, out int y);
dir = GetMovementFor(x, y, out int maxSpeed);
if (maxSpeed == 0)
{
if (message && Order == BoatOrder.Single)
TillerMan?.Say(1042874,
(NextNavPoint + 1).ToString()); // We have arrived at nav point ~1_POINT_NUM~ , sir.
if (NextNavPoint + 1 < MapItem.Pins.Count)
{
NextNavPoint++;
if (Order == BoatOrder.Course)
{
if (message)
TillerMan?.Say(1042875,
(NextNavPoint + 1).ToString()); // Heading to nav point ~1_POINT_NUM~, sir.
return true;
}
return false;
}
NextNavPoint = -1;
if (message && Order == BoatOrder.Course)
TillerMan?.Say(502515); // The course is completed, sir.
return false;
}
if (dir == Left || dir == BackwardLeft || dir == Backward)
return Turn(-2, true);
if (dir == Right || dir == BackwardRight)
return Turn(2, true);
speed = Math.Min(Speed, maxSpeed);
clientSpeed = 0x4;
}
return Move(dir, speed, clientSpeed, true);
}
public bool Move(Direction dir, int speed, int clientSpeed, bool message)
{
Map map = Map;
if (map == null || Deleted || CheckDecay())
return false;
if (Anchored)
{
if (message)
TillerMan?.Say(501419); // Ar, the anchor is down sir!
return false;
}
int rx = 0, ry = 0;
Direction d = (Direction)((int)m_Facing + (int)dir & 0x7);
Movement.Movement.Offset(d, 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)
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)
TillerMan?.Say(501424); // Ar, we've stopped sir.
return false;
}
xOffset = newX - X;
yOffset = newY - Y;
}
}
if (!NewBoatMovement || Math.Abs(xOffset) > 1 || Math.Abs(yOffset) > 1)
{
Teleport(xOffset, yOffset, 0);
}
else
{
List<IEntity> toMove = GetMovingEntities();
if (TillerMan != null) toMove.Add(TillerMan);
if (Hold != null) toMove.Add(Hold);
if (PPlank != null) toMove.Add(PPlank);
if (SPlank != null) toMove.Add(SPlank);
// Packet must be sent before actual locations are changed
foreach (NetState ns in Map.GetClientsInRange(Location, GetMaxUpdateRange()))
{
Mobile m = ns.Mobile;
if (ns.HighSeas && m.CanSee(this) && m.InRange(Location, GetUpdateRange(m)))
BoatPackets.SendMoveBoatHS(ns, m, this, d, clientSpeed, toMove, xOffset, yOffset);
}
foreach (IEntity e in toMove)
if (e is Item item)
{
item.NoMoveHS = true;
if (!(item is TillerMan || item is Hold || item is Plank))
item.Location = new Point3D(item.X + xOffset, item.Y + yOffset, item.Z);
}
else if (e is Mobile m)
{
m.NoMoveHS = true;
m.Location = new Point3D(m.X + xOffset, m.Y + yOffset, m.Z);
}
NoMoveHS = true;
Location = new Point3D(X + xOffset, Y + yOffset, Z);
foreach (IEntity e in toMove)
if (e is Item item)
item.NoMoveHS = false;
else if (e is Mobile mobile)
mobile.NoMoveHS = false;
NoMoveHS = false;
}
return true;
}
public void Teleport(int xOffset, int yOffset, int zOffset)
{
List<IEntity> toMove = GetMovingEntities();
for (int i = 0; i < toMove.Count; ++i)
{
IEntity e = toMove[i];
if (e is Item item)
item.Location = new Point3D(item.X + xOffset, item.Y + yOffset, item.Z + zOffset);
else if (e is Mobile m) m.Location = new Point3D(m.X + xOffset, m.Y + yOffset, m.Z + zOffset);
}
Location = new Point3D(X + xOffset, Y + yOffset, Z + zOffset);
}
public List<IEntity> GetMovingEntities()
{
List<IEntity> list = new List<IEntity>();
Map map = Map;
if (map == null || map == Map.Internal)
return list;
MultiComponentList mcl = Components;
IPooledEnumerable eable = map.GetObjectsInBounds(new Rectangle2D(X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height));
foreach (IEntity o in eable)
{
if (o == this || o is TillerMan || o is Hold || o is Plank)
continue;
if (o is Item item)
{
if (Contains(item) && item.Visible && item.Z >= Z)
list.Add(item);
}
else if (o is Mobile m)
{
if (Contains(m))
list.Add(m);
}
}
eable.Free();
return list;
}
public bool SetFacing(Direction facing)
{
if (Parent != null || 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;
}
Direction old = m_Facing;
m_Facing = facing;
TillerMan?.SetFacing(facing);
Hold?.SetFacing(facing);
PPlank?.SetFacing(facing);
SPlank?.SetFacing(facing);
List<IEntity> toMove = GetMovingEntities();
if (PPlank != null) toMove.Add(PPlank);
if (SPlank != null) toMove.Add(SPlank);
int xOffset = 0, yOffset = 0;
Movement.Offset(facing, ref xOffset, ref yOffset);
if (TillerMan != null)
TillerMan.Location = new Point3D(X + xOffset * TillerManDistance + (facing == Direction.North ? 1 : 0),
Y + yOffset * TillerManDistance, TillerMan.Z);
if (Hold != null)
Hold.Location = new Point3D(X + xOffset * HoldDistance, Y + yOffset * HoldDistance, Hold.Z);
int count = m_Facing - old & 0x7;
count /= 2;
for (int i = 0; i < toMove.Count; ++i)
{
IEntity e = toMove[i];
if (e is Item item)
item.Location = Rotate(item.Location, count);
else if (e is Mobile m)
{
m.Direction = m.Direction - old + facing & Direction.Mask;
m.Location = Rotate(m.Location, count);
}
}
ItemID = facing switch
{
Direction.North => NorthID,
Direction.East => EastID,
Direction.South => SouthID,
Direction.West => WestID,
_ => ItemID
};
return true;
}
public static void UpdateAllComponents()
{
for (int i = Boats.Count - 1; i >= 0; --i)
Boats[i].UpdateComponents();
}
public static void Initialize()
{
new UpdateAllTimer().Start();
EventSink.WorldSave += EventSink_WorldSave;
}
private static void EventSink_WorldSave(WorldSaveEventArgs e)
{
new UpdateAllTimer().Start();
}
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);
m_Boat.TillerMan?.Say(1007168 + m_Count);
++m_Count;
}
}
}
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);
}
}
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 class UpdateAllTimer : Timer
{
public UpdateAllTimer() : base(TimeSpan.FromSeconds(1.0))
{
}
protected override void OnTick()
{
UpdateAllComponents();
}
}
public override bool AllowsRelativeDrop => true;
/*
* OSI sends the 0xF7 packet instead, holding 0xF3 packets
* for every entity on the boat. Though, the regular 0xF3
* packets are still being sent as well as entities come
* into sight. Do we really need it?
*/
/*
protected override Packet GetWorldPacketFor( NetState state )
{
if ( NewBoatMovement && state.HighSeas )
return new DisplayBoatHS( state.Mobile, this );
else
return base.GetWorldPacketFor( state );
}
*/
}
}