diff --git a/Projects/UOContent/Items/Weapons/Axes/BaseAxe.cs b/Projects/UOContent/Items/Weapons/Axes/BaseAxe.cs index 77cce8193..64b19aa89 100644 --- a/Projects/UOContent/Items/Weapons/Axes/BaseAxe.cs +++ b/Projects/UOContent/Items/Weapons/Axes/BaseAxe.cs @@ -105,10 +105,6 @@ namespace Server.Items } } - private void Deserialize(IGenericReader reader, int version) - { - } - public override void OnHit(Mobile attacker, Mobile defender, double damageBonus = 1) { base.OnHit(attacker, defender, damageBonus); diff --git a/Projects/UOContent/Migrations/Server.Items.HousePlacementTool.v0.json b/Projects/UOContent/Migrations/Server.Items.HousePlacementTool.v0.json new file mode 100644 index 000000000..4efca7370 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.HousePlacementTool.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Items.HousePlacementTool" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.HouseTeleporter.v2.json b/Projects/UOContent/Migrations/Server.Items.HouseTeleporter.v2.json new file mode 100644 index 000000000..2ee54d866 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.HouseTeleporter.v2.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "type": "Server.Items.HouseTeleporter", + "properties": [ + { + "name": "Level", + "type": "Server.Multis.SecureLevel", + "rule": "EnumMigrationRule" + }, + { + "name": "Target", + "type": "Server.Item", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.ToggleItem.v0.json b/Projects/UOContent/Migrations/Server.Items.ToggleItem.v0.json new file mode 100644 index 000000000..c5cc3cb56 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.ToggleItem.v0.json @@ -0,0 +1,30 @@ +{ + "version": 0, + "type": "Server.Items.ToggleItem", + "properties": [ + { + "name": "InactiveItemId", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "ActiveItemId", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "PlayersCanToggle", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Misc.ShardPollOption.v1.json b/Projects/UOContent/Migrations/Server.Misc.ShardPollOption.v1.json new file mode 100644 index 000000000..800db2033 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Misc.ShardPollOption.v1.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "type": "Server.Misc.ShardPollOption", + "properties": [ + { + "name": "Title", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Voters", + "type": "System.Net.IPAddress[]", + "rule": "ArrayMigrationRule", + "ruleArguments": [ + "System.Net.IPAddress", + "PrimitiveTypeMigrationRule" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Misc.ShardPoller.v1.json b/Projects/UOContent/Migrations/Server.Misc.ShardPoller.v1.json new file mode 100644 index 000000000..46345e368 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Misc.ShardPoller.v1.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "type": "Server.Misc.ShardPoller", + "properties": [ + { + "name": "Title", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Duration", + "type": "System.TimeSpan", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "StartTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Active", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Options", + "type": "Server.Misc.ShardPollOption[]", + "rule": "ArrayMigrationRule", + "ruleArguments": [ + "Server.Misc.ShardPollOption", + "RawSerializableMigrationRule", + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendor.v3.json b/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendor.v3.json new file mode 100644 index 000000000..aee2b1da6 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendor.v3.json @@ -0,0 +1,62 @@ +{ + "version": 3, + "type": "Server.Mobiles.PlayerVendor", + "properties": [ + { + "name": "ShopName", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "NextPayTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "DeltaTime" + ] + }, + { + "name": "House", + "type": "Server.Multis.BaseHouse", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Owner", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "BankAccount", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "HoldGold", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "SellItems", + "type": "System.Collections.Generic.Dictionary\u003CServer.Item, Server.Mobiles.VendorItem\u003E", + "rule": "DictionaryMigrationRule", + "ruleArguments": [ + "Server.Item", + "SerializableInterfaceMigrationRule", + "0", + "Server.Mobiles.VendorItem", + "RawSerializableMigrationRule", + "1", + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendorPlaceholder.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendorPlaceholder.v0.json new file mode 100644 index 000000000..bf6971759 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendorPlaceholder.v0.json @@ -0,0 +1,11 @@ +{ + "version": 0, + "type": "Server.Mobiles.PlayerVendorPlaceholder", + "properties": [ + { + "name": "Vendor", + "type": "Server.Mobiles.PlayerVendor", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.VendorBackpack.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.VendorBackpack.v0.json new file mode 100644 index 000000000..f0c9f6baf --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.VendorBackpack.v0.json @@ -0,0 +1,4 @@ +{ + "version": 0, + "type": "Server.Mobiles.VendorBackpack" +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.VendorItem.v0.json b/Projects/UOContent/Migrations/Server.Mobiles.VendorItem.v0.json new file mode 100644 index 000000000..b1fa351fc --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.VendorItem.v0.json @@ -0,0 +1,35 @@ +{ + "version": 0, + "type": "Server.Mobiles.VendorItem", + "properties": [ + { + "name": "Item", + "type": "Server.Item", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Price", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Description", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Created", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Multis.HouseSign.v0.json b/Projects/UOContent/Migrations/Server.Multis.HouseSign.v0.json new file mode 100644 index 000000000..4a1fca756 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Multis.HouseSign.v0.json @@ -0,0 +1,16 @@ +{ + "version": 0, + "type": "Server.Multis.HouseSign", + "properties": [ + { + "name": "Owner", + "type": "Server.Multis.BaseHouse", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "OriginalOwner", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Misc/ShardPoller.cs b/Projects/UOContent/Misc/ShardPoller.cs index 3c12dc172..85696ca0a 100644 --- a/Projects/UOContent/Misc/ShardPoller.cs +++ b/Projects/UOContent/Misc/ShardPoller.cs @@ -2,670 +2,612 @@ using System; using System.Collections.Generic; using System.Net; using System.Text.RegularExpressions; +using ModernUO.Serialization; using Server.Gumps; using Server.Network; using Server.Prompts; -namespace Server.Misc +namespace Server.Misc; + +[SerializationGenerator(1, false)] +public partial class ShardPoller : Item { - public class ShardPoller : Item + private static readonly List _activePollers = new(); + + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + private TimeSpan _duration; + + [SerializableField(2)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + private DateTime _startTime; + + [SerializableField(4)] + private ShardPollOption[] _options; + + [Constructible(AccessLevel.Administrator)] + public ShardPoller() : base(0x1047) { - private static readonly List m_ActivePollers = new(); + _duration = TimeSpan.FromHours(24.0); + _options = []; - private bool m_Active; - private string m_Title; + Movable = false; + } - [Constructible(AccessLevel.Administrator)] - public ShardPoller() : base(0x1047) + [SerializableProperty(0)] + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public string Title + { + get => _title; + set { - Duration = TimeSpan.FromHours(24.0); - Options = Array.Empty(); - Addresses = Array.Empty(); - - Movable = false; + _title = ShardPollPrompt.UrlToHref(value); + this.MarkDirty(); } + } - public ShardPoller(Serial serial) : base(serial) + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public TimeSpan TimeRemaining => + StartTime == DateTime.MinValue || !_active + ? TimeSpan.Zero + : Utility.Max(StartTime + Duration - Core.Now, TimeSpan.Zero); + + [SerializableProperty(3)] + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Active + { + get => _active; + set { - } - - public ShardPollOption[] Options { get; set; } - - public IPAddress[] Addresses { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public string Title - { - get => m_Title; - set => m_Title = ShardPollPrompt.UrlToHref(value); - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public TimeSpan Duration { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public DateTime StartTime { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public TimeSpan TimeRemaining => - StartTime == DateTime.MinValue || !m_Active - ? TimeSpan.Zero - : Utility.Max(StartTime + Duration - Core.Now, TimeSpan.Zero); - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Active - { - get => m_Active; - set - { - if (m_Active == value) - { - return; - } - - m_Active = value; - - if (m_Active) - { - StartTime = Core.Now; - m_ActivePollers.Add(this); - } - else - { - m_ActivePollers.Remove(this); - } - } - } - - public override string DefaultName => "shard poller"; - - public bool HasAlreadyVoted(NetState ns) - { - for (var i = 0; i < Options.Length; ++i) - { - if (Options[i].HasAlreadyVoted(ns)) - { - return true; - } - } - - return false; - } - - public void AddVote(NetState ns, ShardPollOption option) - { - option.AddVote(ns); - } - - public void RemoveOption(ShardPollOption option) - { - var index = Array.IndexOf(Options, option); - - if (index < 0) + if (_active == value) { return; } - var old = Options; - Options = new ShardPollOption[old.Length - 1]; + _active = value; - for (var i = 0; i < index; ++i) + if (_active) { - Options[i] = old[i]; + StartTime = Core.Now; + _activePollers.Add(this); + } + else + { + _activePollers.Remove(this); } - for (var i = index; i < Options.Length; ++i) + this.MarkDirty(); + } + } + + public override string DefaultName => "shard poller"; + + public bool HasAlreadyVoted(NetState ns) + { + for (var i = 0; i < Options.Length; ++i) + { + if (Options[i].HasAlreadyVoted(ns)) { - Options[i] = old[i + 1]; + return true; } } - public void AddOption(ShardPollOption option) + return false; + } + + public void AddVote(NetState ns, ShardPollOption option) + { + option.AddVote(ns); + } + + public void RemoveOption(ShardPollOption option) + { + var index = Array.IndexOf(Options, option); + + if (index < 0) { - var old = Options; - Options = new ShardPollOption[old.Length + 1]; - - for (var i = 0; i < old.Length; ++i) - { - Options[i] = old[i]; - } - - Options[old.Length] = option; + return; } - public static void Initialize() + var old = Options; + Options = new ShardPollOption[old.Length - 1]; + Array.Copy(old, Options, index); + Array.Copy(old, index + 1, Options, index, Options.Length - index); + + } + + public void AddOption(ShardPollOption option) + { + var old = Options; + Options = new ShardPollOption[old.Length + 1]; + Array.Copy(old, Options, old.Length); + + Options[^1] = option; + } + + public static void Initialize() + { + EventSink.Login += EventSink_Login; + } + + private static void EventSink_Login(Mobile m) + { + if (_activePollers.Count == 0) { - EventSink.Login += EventSink_Login; + return; } - private static void EventSink_Login(Mobile m) - { - if (m_ActivePollers.Count == 0) - { - return; - } + Timer.StartTimer(TimeSpan.FromSeconds(1.0), () => EventSink_Login_Callback(m)); + } - Timer.StartTimer(TimeSpan.FromSeconds(1.0), () => EventSink_Login_Callback(m)); + private static void EventSink_Login_Callback(Mobile from) + { + var ns = from.NetState; + + if (ns == null) + { + return; } - private static void EventSink_Login_Callback(Mobile from) - { - var ns = from.NetState; + ShardPollGump spg = null; - if (ns == null) + for (var i = 0; i < _activePollers.Count; ++i) + { + var poller = _activePollers[i]; + + if (poller.Deleted || !poller.Active) { - return; + continue; } - ShardPollGump spg = null; - - for (var i = 0; i < m_ActivePollers.Count; ++i) + if (poller.TimeRemaining > TimeSpan.Zero) { - var poller = m_ActivePollers[i]; - - if (poller.Deleted || !poller.Active) + if (poller.HasAlreadyVoted(ns)) { continue; } - if (poller.TimeRemaining > TimeSpan.Zero) + if (spg == null) { - if (poller.HasAlreadyVoted(ns)) - { - continue; - } - - if (spg == null) - { - spg = new ShardPollGump(from, poller, false, null); - from.SendGump(spg); - } - else - { - spg.QueuePoll(poller); - } + spg = new ShardPollGump(from, poller, false, null); + from.SendGump(spg); } else { - poller.Active = false; + spg.QueuePoll(poller); } } - } - - public override void OnDoubleClick(Mobile from) - { - if (from.AccessLevel >= AccessLevel.Administrator) + else { - from.SendGump(new ShardPollGump(from, this, true, null)); + poller.Active = false; } } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - - writer.Write(m_Title); - writer.Write(Duration); - writer.Write(StartTime); - writer.Write(m_Active); - - writer.Write(Options.Length); - - for (var i = 0; i < Options.Length; ++i) - { - Options[i].Serialize(writer); - } - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - m_Title = reader.ReadString(); - Duration = reader.ReadTimeSpan(); - StartTime = reader.ReadDateTime(); - m_Active = reader.ReadBool(); - - Options = new ShardPollOption[reader.ReadInt()]; - - for (var i = 0; i < Options.Length; ++i) - { - Options[i] = new ShardPollOption(reader); - } - - if (m_Active) - { - m_ActivePollers.Add(this); - } - - break; - } - } - } - - public override void OnDelete() - { - base.OnDelete(); - - Active = false; - } } - public class ShardPollOption + public override void OnDoubleClick(Mobile from) { - private string m_Title; - - public ShardPollOption(string title) + if (from.AccessLevel >= AccessLevel.Administrator) { - m_Title = title; - LineBreaks = GetBreaks(m_Title); - Voters = Array.Empty(); + from.SendGump(new ShardPollGump(from, this, true, null)); } + } - public ShardPollOption(IGenericReader reader) + private void Deserialize(IGenericReader reader, int version) + { + _title = reader.ReadString(); + _duration = reader.ReadTimeSpan(); + _startTime = reader.ReadDateTime(); + _active = reader.ReadBool(); + + _options = new ShardPollOption[reader.ReadInt()]; + + for (var i = 0; i < _options.Length; ++i) { - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - m_Title = reader.ReadString(); - LineBreaks = GetBreaks(m_Title); - - Voters = new IPAddress[reader.ReadInt()]; - - for (var i = 0; i < Voters.Length; ++i) - { - Voters[i] = Utility.Intern(reader.ReadIPAddress()); - } - - break; - } - } + var option = _options[i] = new ShardPollOption(); + option.Deserialize(reader); } + } - public string Title + [AfterDeserialization] + private void AfterDeserialization() + { + if (_active) { - get => m_Title; - set - { - m_Title = value; - LineBreaks = GetBreaks(m_Title); - } + _activePollers.Add(this); } + } - public int LineBreaks { get; private set; } + public override void OnDelete() + { + base.OnDelete(); - public int Votes => Voters.Length; - public IPAddress[] Voters { get; set; } + Active = false; + } +} - public bool HasAlreadyVoted(NetState ns) +[SerializationGenerator(1, false)] +public partial class ShardPollOption +{ + private int _lineBreaks = -1; + + [SerializableField(1)] + private IPAddress[] _voters; + + public ShardPollOption() => _voters = []; + + public ShardPollOption(string title) + { + _title = title; + _voters = []; + } + + private void Deserialize(IGenericReader reader, int version) + { + _title = reader.ReadString(); + + _voters = new IPAddress[reader.ReadInt()]; + + for (var i = 0; i < _voters.Length; ++i) { - if (ns == null) - { - return false; - } + _voters[i] = Utility.Intern(reader.ReadIPAddress()); + } + } - var ipAddress = ns.Address; + [SerializableProperty(0)] + public string Title + { + get => _title; + set + { + _title = value; + _lineBreaks = -1; + } + } - for (var i = 0; i < Voters.Length; ++i) - { - if (Voters[i].MatchClassC(ipAddress)) - { - return true; - } - } + public int Votes => Voters.Length; + public bool HasAlreadyVoted(NetState ns) + { + if (ns == null) + { return false; } - public void AddVote(NetState ns) + var ipAddress = ns.Address; + + for (var i = 0; i < Voters.Length; ++i) { - if (ns == null) + if (Voters[i].MatchClassC(ipAddress)) + { + return true; + } + } + + return false; + } + + public void AddVote(NetState ns) + { + if (ns == null) + { + return; + } + + var old = Voters; + Voters = new IPAddress[old.Length + 1]; + + for (var i = 0; i < old.Length; ++i) + { + Voters[i] = old[i]; + } + + Voters[old.Length] = ns.Address; + } + + public int ComputeHeight() + { + var height = GetBreaks() * 18; + + if (height > 30) + { + return height; + } + + return 30; + } + + public int GetBreaks() + { + var title = _title; + if (title == null) + { + return 1; + } + + if (_lineBreaks > -1) + { + return _lineBreaks; + } + + var count = 0; + var index = -1; + + do + { + ++count; + index = title.IndexOfOrdinal("
", index + 1); + } while (index >= 0); + + return _lineBreaks = count; + } +} + +public class ShardPollGump : Gump +{ + private const int LabelColor32 = 0xFFFFFF; + private readonly Mobile _from; + private readonly ShardPoller _poller; + private Queue _polls; + + public ShardPollGump(Mobile from, ShardPoller poller, bool editing, Queue polls) : base(50, 50) + { + _from = from; + _poller = poller; + Editing = editing; + _polls = polls; + + Closable = false; + + AddPage(0); + + var totalVotes = 0; + var totalOptionHeight = 0; + + for (var i = 0; i < poller.Options.Length; ++i) + { + totalVotes += poller.Options[i].Votes; + totalOptionHeight += poller.Options[i].ComputeHeight() + 5; + } + + var isViewingResults = editing && poller.Active; + var isCompleted = totalVotes > 0 && !poller.Active; + + if (editing && !isViewingResults) + { + totalOptionHeight += 35; + } + + var height = 115 + totalOptionHeight; + + AddBackground(1, 1, 398, height - 2, 3600); + AddAlphaRegion(16, 15, 369, height - 31); + + AddItem(308, 30, 0x1E5E); + + string title; + + if (editing) + { + title = isCompleted ? "Poll Completed" : "Poll Editor"; + } + else + { + title = "Shard Poll"; + } + + AddHtml(22, 22, 294, 20, title.Center(LabelColor32)); + + if (editing) + { + AddHtml(22, 22, 294, 20, $"{totalVotes} total".Color(LabelColor32)); + AddButton(287, 23, 0x2622, 0x2623, 2); + } + + AddHtml(22, 50, 294, 40, poller.Title.Color(0x99CC66)); + + AddImageTiled(32, 88, 264, 1, 9107); + AddImageTiled(42, 90, 264, 1, 9157); + + var y = 100; + + for (var i = 0; i < poller.Options.Length; ++i) + { + var option = poller.Options[i]; + var text = option.Title; + + if (editing && totalVotes > 0) + { + var perc = option.Votes / (double)totalVotes; + + text = $"[{option.Votes}: {(int)(perc * 100)}%] {text}"; + } + + var optHeight = option.ComputeHeight(); + + y += optHeight / 2; + + if (isViewingResults) + { + AddImage(24, y - 15, 0x25FE); + } + else + { + AddRadio(24, y - 15, 0x25F9, 0x25FC, false, 1 + i); + } + + var lineBreaks = option.GetBreaks(); + + AddHtml(60, y - 9 * lineBreaks, 250, 18 * lineBreaks, text.Color(LabelColor32)); + + y += optHeight / 2; + y += 5; + } + + if (editing && !isViewingResults) + { + AddRadio(24, y + 15 - 15, 0x25F9, 0x25FC, false, 1 + poller.Options.Length); + AddHtml(60, y + 15 - 9, 250, 18, "Create new option.".Color(0x99CC66)); + } + + AddButton(314, height - 73, 247, 248, 1); + AddButton(314, height - 47, 242, 241, 0); + } + + public bool Editing { get; } + + public void QueuePoll(ShardPoller poller) + { + _polls ??= new Queue(4); + _polls.Enqueue(poller); + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + if (_polls?.Count > 0) + { + var shardPoller = _polls.Dequeue(); + + if (shardPoller != null) + { + Timer.StartTimer( + TimeSpan.FromSeconds(1.0), + () => _from.SendGump(new ShardPollGump(_from, shardPoller, false, _polls)) + ); + } + } + + if (info.ButtonID == 1) + { + var switches = info.Switches; + + if (switches.Length == 0) { return; } - var old = Voters; - Voters = new IPAddress[old.Length + 1]; + var switched = switches[0] - 1; + ShardPollOption opt = null; - for (var i = 0; i < old.Length; ++i) + if (switched >= 0 && switched < _poller.Options.Length) { - Voters[i] = old[i]; + opt = _poller.Options[switched]; } - Voters[old.Length] = ns.Address; - } - - public int ComputeHeight() - { - var height = LineBreaks * 18; - - if (height > 30) + if (opt == null && !Editing) { - return height; + return; } - return 30; - } - - public int GetBreaks(string title) - { - if (title == null) + if (Editing) { - return 1; - } - - var count = 0; - var index = -1; - - do - { - ++count; - index = title.IndexOfOrdinal("
", index + 1); - } while (index >= 0); - - return count; - } - - public void Serialize(IGenericWriter writer) - { - writer.Write(0); // version - - writer.Write(m_Title); - - writer.Write(Voters.Length); - - for (var i = 0; i < Voters.Length; ++i) - { - writer.Write(Voters[i]); - } - } - } - - public class ShardPollGump : Gump - { - private const int LabelColor32 = 0xFFFFFF; - private readonly Mobile m_From; - private readonly ShardPoller m_Poller; - private Queue m_Polls; - - public ShardPollGump(Mobile from, ShardPoller poller, bool editing, Queue polls) : base(50, 50) - { - m_From = from; - m_Poller = poller; - Editing = editing; - m_Polls = polls; - - Closable = false; - - AddPage(0); - - var totalVotes = 0; - var totalOptionHeight = 0; - - for (var i = 0; i < poller.Options.Length; ++i) - { - totalVotes += poller.Options[i].Votes; - totalOptionHeight += poller.Options[i].ComputeHeight() + 5; - } - - var isViewingResults = editing && poller.Active; - var isCompleted = totalVotes > 0 && !poller.Active; - - if (editing && !isViewingResults) - { - totalOptionHeight += 35; - } - - var height = 115 + totalOptionHeight; - - AddBackground(1, 1, 398, height - 2, 3600); - AddAlphaRegion(16, 15, 369, height - 31); - - AddItem(308, 30, 0x1E5E); - - string title; - - if (editing) - { - title = isCompleted ? "Poll Completed" : "Poll Editor"; - } - else - { - title = "Shard Poll"; - } - - AddHtml(22, 22, 294, 20, title.Center(LabelColor32)); - - if (editing) - { - AddHtml(22, 22, 294, 20, $"{totalVotes} total".Color(LabelColor32)); - AddButton(287, 23, 0x2622, 0x2623, 2); - } - - AddHtml(22, 50, 294, 40, poller.Title.Color(0x99CC66)); - - AddImageTiled(32, 88, 264, 1, 9107); - AddImageTiled(42, 90, 264, 1, 9157); - - var y = 100; - - for (var i = 0; i < poller.Options.Length; ++i) - { - var option = poller.Options[i]; - var text = option.Title; - - if (editing && totalVotes > 0) + if (!_poller.Active) { - var perc = option.Votes / (double)totalVotes; - - text = $"[{option.Votes}: {(int)(perc * 100)}%] {text}"; - } - - var optHeight = option.ComputeHeight(); - - y += optHeight / 2; - - if (isViewingResults) - { - AddImage(24, y - 15, 0x25FE); - } - else - { - AddRadio(24, y - 15, 0x25F9, 0x25FC, false, 1 + i); - } - - AddHtml(60, y - 9 * option.LineBreaks, 250, 18 * option.LineBreaks, text.Color(LabelColor32)); - - y += optHeight / 2; - y += 5; - } - - if (editing && !isViewingResults) - { - AddRadio(24, y + 15 - 15, 0x25F9, 0x25FC, false, 1 + poller.Options.Length); - AddHtml(60, y + 15 - 9, 250, 18, "Create new option.".Color(0x99CC66)); - } - - AddButton(314, height - 73, 247, 248, 1); - AddButton(314, height - 47, 242, 241, 0); - } - - public bool Editing { get; } - - public void QueuePoll(ShardPoller poller) - { - m_Polls ??= new Queue(4); - - m_Polls.Enqueue(poller); - } - - public override void OnResponse(NetState sender, in RelayInfo info) - { - if (m_Polls?.Count > 0) - { - var shardPoller = m_Polls.Dequeue(); - - if (shardPoller != null) - { - Timer.StartTimer( - TimeSpan.FromSeconds(1.0), - () => m_From.SendGump(new ShardPollGump(m_From, shardPoller, false, m_Polls)) - ); - } - } - - if (info.ButtonID == 1) - { - var switches = info.Switches; - - if (switches.Length == 0) - { - return; - } - - var switched = switches[0] - 1; - ShardPollOption opt = null; - - if (switched >= 0 && switched < m_Poller.Options.Length) - { - opt = m_Poller.Options[switched]; - } - - if (opt == null && !Editing) - { - return; - } - - if (Editing) - { - if (!m_Poller.Active) + if (opt == null) { - if (opt == null) - { - m_From.SendMessage($"Enter a title for the option. Escape to cancel."); - } - else - { - m_From.SendMessage($"Enter a title for the option. Escape to cancel. Use \"DEL\" to delete."); - } - - m_From.Prompt = new ShardPollPrompt(m_Poller, opt); + _from.SendMessage("Enter a title for the option. Escape to cancel."); } else { - m_From.SendMessage("You may not edit an active poll. Deactivate it first."); - m_From.SendGump(new ShardPollGump(m_From, m_Poller, Editing, m_Polls)); + _from.SendMessage("Enter a title for the option. Escape to cancel. Use \"DEL\" to delete."); } + + _from.Prompt = new ShardPollPrompt(_poller, opt); } else { - if (!m_Poller.Active) - { - m_From.SendMessage("The poll has been deactivated."); - } - else if (m_Poller.HasAlreadyVoted(sender)) - { - m_From.SendMessage("You have already voted on this poll."); - } - else - { - m_Poller.AddVote(sender, opt); - } - } - } - else if (info.ButtonID == 2 && Editing) - { - m_From.SendGump(new ShardPollGump(m_From, m_Poller, Editing, m_Polls)); - m_From.SendGump(new PropertiesGump(m_From, m_Poller)); - } - } - } - - public class ShardPollPrompt : Prompt - { - private static readonly Regex m_UrlRegex = - new(@"\[url(?:=(.*?))?\](.*?)\[/url\]", RegexOptions.IgnoreCase | RegexOptions.Compiled); - - private readonly ShardPollOption m_Option; - private readonly ShardPoller m_Poller; - - public ShardPollPrompt(ShardPoller poller, ShardPollOption opt) - { - m_Poller = poller; - m_Option = opt; - } - - public override void OnCancel(Mobile from) - { - from.SendGump(new ShardPollGump(from, m_Poller, true, null)); - } - - private static string UrlRegex_Match(Match m) - { - if (m.Groups[1].Success) - { - if (m.Groups[2].Success) - { - return $"{m.Groups[2].Value}"; - } - } - else if (m.Groups[2].Success) - { - return $"{m.Groups[2].Value}"; - } - - return m.Value; - } - - public static string UrlToHref(string text) - { - if (text == null) - { - return null; - } - - return m_UrlRegex.Replace(text, UrlRegex_Match); - } - - public override void OnResponse(Mobile from, string text) - { - if (m_Poller.Active) - { - from.SendMessage("You may not edit an active poll. Deactivate it first."); - } - else if (text == "DEL") - { - if (m_Option != null) - { - m_Poller.RemoveOption(m_Option); + _from.SendMessage("You may not edit an active poll. Deactivate it first."); + _from.SendGump(new ShardPollGump(_from, _poller, Editing, _polls)); } } else { - text = UrlToHref(text); - - if (m_Option == null) + if (!_poller.Active) { - m_Poller.AddOption(new ShardPollOption(text)); + _from.SendMessage("The poll has been deactivated."); + } + else if (_poller.HasAlreadyVoted(sender)) + { + _from.SendMessage("You have already voted on this poll."); } else { - m_Option.Title = text; + _poller.AddVote(sender, opt); } } - - from.SendGump(new ShardPollGump(from, m_Poller, true, null)); + } + else if (info.ButtonID == 2 && Editing) + { + _from.SendGump(new ShardPollGump(_from, _poller, Editing, _polls)); + _from.SendGump(new PropertiesGump(_from, _poller)); } } } + +public partial class ShardPollPrompt : Prompt +{ + private readonly ShardPollOption _option; + private readonly ShardPoller _poller; + + public ShardPollPrompt(ShardPoller poller, ShardPollOption opt) + { + _poller = poller; + _option = opt; + } + + public override void OnCancel(Mobile from) + { + from.SendGump(new ShardPollGump(from, _poller, true, null)); + } + + private static string UrlRegex_Match(Match m) + { + if (m.Groups[1].Success) + { + if (m.Groups[2].Success) + { + return $"{m.Groups[2].Value}"; + } + } + else if (m.Groups[2].Success) + { + return $"{m.Groups[2].Value}"; + } + + return m.Value; + } + + public static string UrlToHref(string text) => text == null ? null : UrlHrefRegex().Replace(text, UrlRegex_Match); + + public override void OnResponse(Mobile from, string text) + { + if (_poller.Active) + { + from.SendMessage("You may not edit an active poll. Deactivate it first."); + } + else if (text == "DEL") + { + if (_option != null) + { + _poller.RemoveOption(_option); + } + } + else + { + text = UrlToHref(text); + + if (_option == null) + { + _poller.AddOption(new ShardPollOption(text)); + } + else + { + _option.Title = text; + } + } + + from.SendGump(new ShardPollGump(from, _poller, true, null)); + } + + [GeneratedRegex(@"\[url(?:=(.*?))?\](.*?)\[/url\]", RegexOptions.IgnoreCase | RegexOptions.Compiled)] + private static partial Regex UrlHrefRegex(); +} diff --git a/Projects/UOContent/Misc/ToggleItem.cs b/Projects/UOContent/Misc/ToggleItem.cs index cf6d475e3..a2ecac79f 100644 --- a/Projects/UOContent/Misc/ToggleItem.cs +++ b/Projects/UOContent/Misc/ToggleItem.cs @@ -1,109 +1,87 @@ +using ModernUO.Serialization; using Server.Commands.Generic; -namespace Server.Items +namespace Server.Items; + +[SerializationGenerator(0, false)] +public partial class ToggleItem : Item { - public class ToggleItem : Item + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private int _inactiveItemId; + + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private int _activeItemId; + + [SerializableField(2)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private bool _playersCanToggle; + + [Constructible] + public ToggleItem(int inactiveItemID, int activeItemID, bool playersCanToggle = false) : base(inactiveItemID) { - [Constructible] - public ToggleItem(int inactiveItemID, int activeItemID, bool playersCanToggle = false) - : base(inactiveItemID) - { - Movable = false; + Movable = false; - InactiveItemID = inactiveItemID; - ActiveItemID = activeItemID; - PlayersCanToggle = playersCanToggle; + _inactiveItemId = inactiveItemID; + _activeItemId = activeItemID; + _playersCanToggle = playersCanToggle; + } + + public static void Configure() + { + TargetCommands.Register(new ToggleCommand()); + } + + public override void OnDoubleClick(Mobile from) + { + if (from.AccessLevel >= AccessLevel.GameMaster) + { + Toggle(); + } + else if (!PlayersCanToggle) + { + return; } - public ToggleItem(Serial serial) - : base(serial) + if (from.InRange(GetWorldLocation(), 1)) { + Toggle(); + } + else + { + from.SendLocalizedMessage(500446); // That is too far away. + } + } + + public void Toggle() + { + ItemID = ItemID == _activeItemId ? _inactiveItemId : _activeItemId; + Visible = ItemID != 0x1; + } + + public class ToggleCommand : BaseCommand + { + public ToggleCommand() + { + AccessLevel = AccessLevel.GameMaster; + Supports = CommandSupport.AllItems; + Commands = ["Toggle"]; + ObjectTypes = ObjectTypes.Items; + Usage = "Toggle"; + Description = "Toggles a targeted ToggleItem."; } - [CommandProperty(AccessLevel.GameMaster)] - public int InactiveItemID { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int ActiveItemID { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool PlayersCanToggle { get; set; } - - public static void Configure() + public override void Execute(CommandEventArgs e, object obj) { - TargetCommands.Register(new ToggleCommand()); - } - - public override void OnDoubleClick(Mobile from) - { - if (from.AccessLevel >= AccessLevel.GameMaster) + if (obj is ToggleItem item) { - Toggle(); + item.Toggle(); + AddResponse("The item has been toggled."); } - else if (PlayersCanToggle) + else { - if (from.InRange(GetWorldLocation(), 1)) - { - Toggle(); - } - else - { - from.SendLocalizedMessage(500446); // That is too far away. - } - } - } - - public void Toggle() - { - ItemID = ItemID == ActiveItemID ? InactiveItemID : ActiveItemID; - Visible = ItemID != 0x1; - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - - writer.Write(InactiveItemID); - writer.Write(ActiveItemID); - writer.Write(PlayersCanToggle); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - InactiveItemID = reader.ReadInt(); - ActiveItemID = reader.ReadInt(); - PlayersCanToggle = reader.ReadBool(); - } - - public class ToggleCommand : BaseCommand - { - public ToggleCommand() - { - AccessLevel = AccessLevel.GameMaster; - Supports = CommandSupport.AllItems; - Commands = new[] { "Toggle" }; - ObjectTypes = ObjectTypes.Items; - Usage = "Toggle"; - Description = "Toggles a targeted ToggleItem."; - } - - public override void Execute(CommandEventArgs e, object obj) - { - if (obj is ToggleItem item) - { - item.Toggle(); - AddResponse("The item has been toggled."); - } - else - { - LogFailure("That is not a ToggleItem."); - } + LogFailure("That is not a ToggleItem."); } } } diff --git a/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs b/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs index 7c3ce7506..0b7f1ed1f 100644 --- a/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs +++ b/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using ModernUO.Serialization; using Server.ContextMenus; using Server.Engines.BulkOrders; -using Server.Ethics; using Server.Gumps; using Server.Items; using Server.Misc; @@ -12,877 +10,518 @@ using Server.Multis; using Server.Prompts; using Server.Targeting; -namespace Server.Mobiles +namespace Server.Mobiles; + +[AttributeUsage(AttributeTargets.Class)] +public class PlayerVendorTargetAttribute : Attribute; + +/** + * Note: When upgrading player vendors to the new system (Expansion enter AOS+), bump the serialization version up by 1. + * Next, uncomment the MigrateFrom function and change the `V3Content` type to match the serialization version + * before it was bumped. Then run publish.cmd to generate the migration file. + */ +[SerializationGenerator(3, false)] +public partial class PlayerVendor : Mobile { - [AttributeUsage(AttributeTargets.Class)] - public class PlayerVendorTargetAttribute : Attribute + private Timer _payTimer; + + [InvalidateProperties] + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private string _shopName; + + [DeltaDateTime] + [SerializableField(1, setter: "private")] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private DateTime _nextPayTime; + + [SerializableField(3)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private Mobile _owner; + + [SerializableField(4)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private int _bankAccount; + + [SerializableField(5)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private int _holdGold; + + [SerializableField(6)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private Dictionary _sellItems; + + public PlayerVendor(Mobile owner, BaseHouse house) { + Owner = owner; + House = house; + + if (BaseHouse.NewVendorSystem) + { + BankAccount = 0; + HoldGold = 4; + } + else + { + BankAccount = 1000; + HoldGold = 0; + } + + ShopName = "Shop Not Yet Named"; + + _sellItems = new Dictionary(); + + CantWalk = true; + + if (!Core.AOS) + { + NameHue = 0x35; + } + + InitStats(100, 100, 25); + InitBody(); + InitOutfit(); + + var delay = PayTimer.GetInterval(); + + _payTimer = new PayTimer(this, delay); + _payTimer.Start(); + + NextPayTime = Core.Now + delay; } - public class VendorItem + public PlayerVendorPlaceholder Placeholder { get; set; } + + [SerializableProperty(2)] + public BaseHouse House { - private string m_Description; - - public VendorItem(Item item, int price, string description, DateTime created) + get => _house; + set { - Item = item; - Price = price; - m_Description = description ?? ""; - Created = created; - Valid = true; - } + _house?.PlayerVendors.Remove(this); + value?.PlayerVendors.Add(this); - public Item Item { get; } - - public int Price { get; } - - public string FormattedPrice => - Core.ML ? Price.ToString("N0", CultureInfo.GetCultureInfo("en-US")) : Price.ToString(); - - public string Description - { - get => m_Description; - set - { - m_Description = value ?? ""; - - if (Valid) - { - Item.InvalidateProperties(); - } - } - } - - public DateTime Created { get; } - - public bool IsForSale => Price >= 0; - public bool IsForFree => Price == 0; - - public bool Valid { get; private set; } - - public void Invalidate() - { - Valid = false; + _house = value; + this.MarkDirty(); } } - public class VendorBackpack : Backpack + public int ChargePerDay { - public VendorBackpack() + get { - Layer = Layer.Backpack; - Weight = 1.0; - } - - public VendorBackpack(Serial serial) : base(serial) - { - } - - public override int DefaultMaxWeight => 0; - - public override bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) - { - if (!base.CheckHold(m, item, message, checkItems, plusItems, plusWeight)) + if (BaseHouse.NewVendorSystem) { - return false; + return ChargePerRealWorldDay / 12; } - if (Ethic.IsImbued(item, true)) + long total = 0; + foreach (var value in _sellItems.Values) { - if (message) + total += value.Price; + } + + return (int)(20 + Math.Max(total - 500, 0) / 500); + } + } + + public int ChargePerRealWorldDay + { + get + { + if (BaseHouse.NewVendorSystem) + { + long total = 0; + foreach (var value in _sellItems.Values) { - m.SendMessage("Imbued items may not be sold here."); + total += value.Price; } - return false; + return (int)(60 + total / 500 * 3); } - if (!BaseHouse.NewVendorSystem && Parent is PlayerVendor vendor) - { - var house = vendor.House; + return ChargePerDay * 12; + } + } - if (house?.IsAosRules == true && !house.CheckAosStorage(1 + item.TotalItems + plusItems)) - { - if (message) - { - m.SendLocalizedMessage(1061839); // This action would exceed the secure storage limit of the house. - } + /* + // Uncomment this to migrate to the new vendor system. + // Make sure to update V3Content to match the serialization version before the bump. + private void MigrateFrom(V3Content content) + { + MigrateToNewVendorSystem(); + } + */ - return false; - } - } + private void MigrateToNewVendorSystem() + { + if (BaseHouse.NewVendorSystem) + { + Timer.StartTimer(FixDresswear); + NextPayTime = Core.Now + PayTimer.GetInterval(); + HoldGold += BankAccount; + BankAccount = 0; + } + } + + private void Deserialize(IGenericReader reader, int version) + { + reader.ReadBool(); // New vendor system? + _shopName = reader.ReadString(); + _nextPayTime = reader.ReadDeltaTime(); + _house = reader.ReadEntity(); + _owner = reader.ReadEntity(); + _bankAccount = reader.ReadInt(); + _holdGold = reader.ReadInt(); + var count = reader.ReadInt(); + + _sellItems = new Dictionary(count); + for (var i = 0; i < count; i++) + { + var item = reader.ReadEntity(); + var vi = new VendorItem(); + vi.Deserialize(reader); + _sellItems[item] = vi; + } + } + + [AfterDeserialization] + private void AfterDeserialization() + { + var delay = _nextPayTime - Core.Now; + + _payTimer = new PayTimer(this, delay > TimeSpan.Zero ? delay : TimeSpan.Zero); + _payTimer.Start(); + + Blessed = false; + + if (Core.AOS && NameHue == 0x35) + { + NameHue = -1; + } + } + + public void InitBody() + { + Hue = Race.Human.RandomSkinHue(); + SpeechHue = 0x3B2; + + if (!Core.AOS) + { + NameHue = 0x35; + } + + if (Female = Utility.RandomBool()) + { + Body = 0x191; + Name = NameList.RandomName("female"); + } + else + { + Body = 0x190; + Name = NameList.RandomName("male"); + } + } + + public virtual void InitOutfit() + { + Item item = new FancyShirt(Utility.RandomNeutralHue()); + item.Layer = Layer.InnerTorso; + AddItem(item); + AddItem(new LongPants(Utility.RandomNeutralHue())); + AddItem(new BodySash(Utility.RandomNeutralHue())); + AddItem(new Boots(Utility.RandomNeutralHue())); + AddItem(new Cloak(Utility.RandomNeutralHue())); + + Utility.AssignRandomHair(this); + + Container pack = new VendorBackpack(); + pack.Movable = false; + AddItem(pack); + } + + public virtual bool IsOwner(Mobile m) + { + if (m.AccessLevel >= AccessLevel.GameMaster) + { return true; } - public override bool IsAccessibleTo(Mobile m) => true; - - public override bool CheckItemUse(Mobile from, Item item) + if (BaseHouse.NewVendorSystem && House != null) { - if (!base.CheckItemUse(from, item)) - { - return false; - } - - if (item is Container or BulkOrderBook) - { - return true; - } - - from.SendLocalizedMessage(500447); // That is not accessible. - return false; + return House.IsOwner(m); } - public override bool CheckTarget(Mobile from, Target targ, object targeted) => - base.CheckTarget(from, targ, targeted) && - (from.AccessLevel >= AccessLevel.GameMaster || - targ.GetType().IsDefined(typeof(PlayerVendorTargetAttribute), false)); - - public override void GetChildContextMenuEntries(Mobile from, List list, Item item) - { - base.GetChildContextMenuEntries(from, list, item); - - if (RootParent is not PlayerVendor pv || pv.IsOwner(from)) - { - return; - } - - var vi = pv.GetVendorItem(item); - - if (vi != null) - { - list.Add(new BuyEntry(item)); - } - } - - public override void GetChildNameProperties(IPropertyList list, Item item) - { - base.GetChildNameProperties(list, item); - - var pv = RootParent as PlayerVendor; - - var vi = pv?.GetVendorItem(item); - - if (vi == null) - { - return; - } - - if (!vi.IsForSale) - { - list.Add(1043307); // Price: Not for sale. - } - else if (vi.IsForFree) - { - list.Add(1043306); // Price: FREE! - } - else - { - list.Add(1043304, vi.FormattedPrice); // Price: ~1_COST~ - } - } - - public override void GetChildProperties(IPropertyList list, Item item) - { - base.GetChildProperties(list, item); - - var pv = RootParent as PlayerVendor; - - var vi = pv?.GetVendorItem(item); - - if (vi?.Description?.Length > 0) - { - list.Add(1043305, vi.Description); //
Seller's Description:
"~1_DESC~" - } - } - - public override void OnSingleClickContained(Mobile from, Item item) - { - if (RootParent is PlayerVendor vendor) - { - var vi = vendor.GetVendorItem(item); - - if (vi != null) - { - if (!vi.IsForSale) - { - item.LabelTo(from, 1043307); // Price: Not for sale. - } - else if (vi.IsForFree) - { - item.LabelTo(from, 1043306); // Price: FREE! - } - else - { - item.LabelTo(from, 1043304, vi.FormattedPrice); // Price: ~1_COST~ - } - - if (!string.IsNullOrEmpty(vi.Description)) - { - item.LabelTo(from, $"Description: {vi.Description}"); - } - } - } - - base.OnSingleClickContained(from, item); - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } - - private class BuyEntry : ContextMenuEntry - { - private readonly Item m_Item; - - public BuyEntry(Item item) : base(6103) => m_Item = item; - - public override bool NonLocalUse => true; - - public override void OnClick() - { - if (m_Item.Deleted) - { - return; - } - - PlayerVendor.TryToBuy(m_Item, Owner.From); - } - } + return m == Owner; } - public class PlayerVendor : Mobile + protected List GetItems() { - private BaseHouse m_House; + var list = new List(); - private Timer m_PayTimer; - private Dictionary m_SellItems; - - private string m_ShopName; - - public PlayerVendor(Mobile owner, BaseHouse house) + foreach (var item in Items) { - Owner = owner; - House = house; - - if (BaseHouse.NewVendorSystem) + if (item.Movable && item != Backpack && item.Layer != Layer.Hair && item.Layer != Layer.FacialHair) { - BankAccount = 0; - HoldGold = 4; - } - else - { - BankAccount = 1000; - HoldGold = 0; - } - - ShopName = "Shop Not Yet Named"; - - m_SellItems = new Dictionary(); - - CantWalk = true; - - if (!Core.AOS) - { - NameHue = 0x35; - } - - InitStats(100, 100, 25); - InitBody(); - InitOutfit(); - - var delay = PayTimer.GetInterval(); - - m_PayTimer = new PayTimer(this, delay); - m_PayTimer.Start(); - - NextPayTime = Core.Now + delay; - } - - public PlayerVendor(Serial serial) : base(serial) - { - } - - [CommandProperty(AccessLevel.GameMaster)] - public Mobile Owner { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int BankAccount { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int HoldGold { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public string ShopName - { - get => m_ShopName; - set - { - m_ShopName = value ?? ""; - - InvalidateProperties(); + list.Add(item); } } - [CommandProperty(AccessLevel.GameMaster)] - public DateTime NextPayTime { get; private set; } - - public PlayerVendorPlaceholder Placeholder { get; set; } - - public BaseHouse House + if (Backpack != null) { - get => m_House; - set - { - m_House?.PlayerVendors.Remove(this); - - value?.PlayerVendors.Add(this); - - m_House = value; - } + list.AddRange(Backpack.Items); } - public int ChargePerDay - { - get - { - if (BaseHouse.NewVendorSystem) - { - return ChargePerRealWorldDay / 12; - } + return list; + } - var total = m_SellItems.Values.Aggregate( - 0, - (current, vi) => - current + vi.Price - ) - 500; + public virtual void Destroy(bool toBackpack) + { + Return(); - return (int)(20 + Math.Max(total, 0) / 500); - } - } - - public int ChargePerRealWorldDay - { - get - { - if (BaseHouse.NewVendorSystem) - { - var total = m_SellItems.Values.Aggregate(0, (current, vi) => current + vi.Price); - - return (int)(60 + total / 500 * 3); - } - - return ChargePerDay * 12; - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(2); // version - - writer.Write(BaseHouse.NewVendorSystem); - writer.Write(m_ShopName); - writer.WriteDeltaTime(NextPayTime); - writer.Write(House); - - writer.Write(Owner); - writer.Write(BankAccount); - writer.Write(HoldGold); - - writer.Write(m_SellItems.Count); - foreach (var vi in m_SellItems.Values) - { - writer.Write(vi.Item); - writer.Write(vi.Price); - writer.Write(vi.Description); - - writer.Write(vi.Created); - } - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - var newVendorSystem = false; - - switch (version) - { - case 2: - case 1: - { - newVendorSystem = reader.ReadBool(); - m_ShopName = reader.ReadString(); - NextPayTime = reader.ReadDeltaTime(); - House = (BaseHouse)reader.ReadEntity(); - - goto case 0; - } - case 0: - { - Owner = reader.ReadEntity(); - BankAccount = reader.ReadInt(); - HoldGold = reader.ReadInt(); - - var count = reader.ReadInt(); - - m_SellItems = new Dictionary(count); - - for (var i = 0; i < count; i++) - { - var item = reader.ReadEntity(); - - var price = reader.ReadInt(); - if (price > 100000000) - { - price = 100000000; - } - - var description = reader.ReadString(); - - var created = version < 1 ? Core.Now : reader.ReadDateTime(); - - if (item != null) - { - SetVendorItem(item, version < 1 && price <= 0 ? -1 : price, description, created); - } - } - - break; - } - } - - var newVendorSystemActivated = BaseHouse.NewVendorSystem && !newVendorSystem; - - if (version < 1 || newVendorSystemActivated) - { - if (version < 1) - { - m_ShopName = "Shop Not Yet Named"; - Timer.StartTimer(() => UpgradeFromVersion0(newVendorSystemActivated)); - } - else - { - Timer.StartTimer(FixDresswear); - } - - NextPayTime = Core.Now + PayTimer.GetInterval(); - - if (newVendorSystemActivated) - { - HoldGold += BankAccount; - BankAccount = 0; - } - } - - if (version < 2 && RawStr == 75 && RawDex == 75 && RawInt == 75) - { - InitStats(100, 100, 25); - } - - var delay = NextPayTime - Core.Now; - - m_PayTimer = new PayTimer(this, delay > TimeSpan.Zero ? delay : TimeSpan.Zero); - m_PayTimer.Start(); - - Blessed = false; - - if (Core.AOS && NameHue == 0x35) - { - NameHue = -1; - } - } - - private void UpgradeFromVersion0(bool newVendorSystem) - { - var toRemove = new List(); - - foreach (var vi in m_SellItems.Values) - { - if (!CanBeVendorItem(vi.Item)) - { - toRemove.Add(vi.Item); - } - else - { - vi.Description = vi.Description.FixHtml(); - } - } - - foreach (var item in toRemove) - { - RemoveVendorItem(item); - } - - House = BaseHouse.FindHouseAt(this); - - if (newVendorSystem) - { - ActivateNewVendorSystem(); - } - } - - private void ActivateNewVendorSystem() + if (!BaseHouse.NewVendorSystem) { FixDresswear(); - - if (House?.IsOwner(Owner) == false) - { - Destroy(false); - } } - public void InitBody() + /* Possible cases regarding item return: + * + * 1. No item must be returned + * -> do nothing. + * 2. ( toBackpack is false OR the vendor is in the internal map ) AND the vendor is associated with a AOS house + * -> put the items into the moving crate or a vendor inventory, + * depending on whether the vendor owner is also the house owner. + * 3. ( toBackpack is true OR the vendor isn't associated with any AOS house ) AND the vendor isn't in the internal map + * -> put the items into a backpack. + * 4. The vendor isn't associated with any house AND it's in the internal map + * -> do nothing (we can't do anything). + */ + + var list = GetItems(); + + if (list.Count > 0 || HoldGold > 0) // No case 1 { - Hue = Race.Human.RandomSkinHue(); - SpeechHue = 0x3B2; - - if (!Core.AOS) + if ((!toBackpack || Map == Map.Internal) && House?.IsAosRules == true) // Case 2 { - NameHue = 0x35; - } - - if (Female = Utility.RandomBool()) - { - Body = 0x191; - Name = NameList.RandomName("female"); - } - else - { - Body = 0x190; - Name = NameList.RandomName("male"); - } - } - - public virtual void InitOutfit() - { - Item item = new FancyShirt(Utility.RandomNeutralHue()); - item.Layer = Layer.InnerTorso; - AddItem(item); - AddItem(new LongPants(Utility.RandomNeutralHue())); - AddItem(new BodySash(Utility.RandomNeutralHue())); - AddItem(new Boots(Utility.RandomNeutralHue())); - AddItem(new Cloak(Utility.RandomNeutralHue())); - - Utility.AssignRandomHair(this); - - Container pack = new VendorBackpack(); - pack.Movable = false; - AddItem(pack); - } - - public virtual bool IsOwner(Mobile m) - { - if (m.AccessLevel >= AccessLevel.GameMaster) - { - return true; - } - - if (BaseHouse.NewVendorSystem && House != null) - { - return House.IsOwner(m); - } - - return m == Owner; - } - - protected List GetItems() - { - var list = new List(); - - foreach (var item in Items) - { - if (item.Movable && item != Backpack && item.Layer != Layer.Hair && item.Layer != Layer.FacialHair) + if (House.IsOwner(Owner)) // Move to moving crate { - list.Add(item); - } - } - - if (Backpack != null) - { - list.AddRange(Backpack.Items); - } - - return list; - } - - public virtual void Destroy(bool toBackpack) - { - Return(); - - if (!BaseHouse.NewVendorSystem) - { - FixDresswear(); - } - - /* Possible cases regarding item return: - * - * 1. No item must be returned - * -> do nothing. - * 2. ( toBackpack is false OR the vendor is in the internal map ) AND the vendor is associated with a AOS house - * -> put the items into the moving crate or a vendor inventory, - * depending on whether the vendor owner is also the house owner. - * 3. ( toBackpack is true OR the vendor isn't associated with any AOS house ) AND the vendor isn't in the internal map - * -> put the items into a backpack. - * 4. The vendor isn't associated with any house AND it's in the internal map - * -> do nothing (we can't do anything). - */ - - var list = GetItems(); - - if (list.Count > 0 || HoldGold > 0) // No case 1 - { - if ((!toBackpack || Map == Map.Internal) && House?.IsAosRules == true) // Case 2 - { - if (House.IsOwner(Owner)) // Move to moving crate - { - House.MovingCrate ??= new MovingCrate(House); - - if (HoldGold > 0) - { - Banker.Deposit(House.MovingCrate, HoldGold); - } - - foreach (var item in list) - { - House.MovingCrate.DropItem(item); - } - } - else // Move to vendor inventory - { - var inventory = new VendorInventory(House, Owner, Name, ShopName); - inventory.Gold = HoldGold; - - foreach (var item in list) - { - inventory.AddItem(item); - } - - House.VendorInventories.Add(inventory); - } - } - else if ((toBackpack || House?.IsAosRules != true) && Map != Map.Internal) // Case 3 - Move to backpack - { - Container backpack = new Backpack(); + House.MovingCrate ??= new MovingCrate(House); if (HoldGold > 0) { - Banker.Deposit(backpack, HoldGold); + Banker.Deposit(House.MovingCrate, HoldGold); } foreach (var item in list) { - backpack.DropItem(item); + House.MovingCrate.DropItem(item); + } + } + else // Move to vendor inventory + { + var inventory = new VendorInventory(House, Owner, Name, ShopName); + inventory.Gold = HoldGold; + + foreach (var item in list) + { + inventory.AddItem(item); } - backpack.MoveToWorld(Location, Map); + House.VendorInventories.Add(inventory); } } - - Delete(); - } - - private void FixDresswear() - { - for (var i = 0; i < Items.Count; ++i) + else if ((toBackpack || House?.IsAosRules != true) && Map != Map.Internal) // Case 3 - Move to backpack { - var item = Items[i]; + var backpack = new Backpack(); - item.Layer = item switch + if (HoldGold > 0) { - BaseHat => Layer.Helm, - BaseMiddleTorso => Layer.MiddleTorso, - BaseOuterLegs => Layer.OuterLegs, - BaseOuterTorso => Layer.OuterTorso, - BasePants => Layer.Pants, - BaseShirt => Layer.Shirt, - BaseWaist => Layer.Waist, - BaseShoes => Layer.Shoes, - _ => item.Layer - }; - - if (item is Sandals) - { - item.Hue = 0; + Banker.Deposit(backpack, HoldGold); } + + foreach (var item in list) + { + backpack.DropItem(item); + } + + backpack.MoveToWorld(Location, Map); } } - public override void OnAfterDelete() + Delete(); + } + + private void FixDresswear() + { + for (var i = 0; i < Items.Count; ++i) { - base.OnAfterDelete(); + var item = Items[i]; - m_PayTimer.Stop(); - - House = null; - - Placeholder?.Delete(); - } - - public override bool IsSnoop(Mobile from) => false; - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - if (BaseHouse.NewVendorSystem) + item.Layer = item switch { - list.Add(1062449, ShopName); // Shop Name: ~1_NAME~ + BaseHat => Layer.Helm, + BaseMiddleTorso => Layer.MiddleTorso, + BaseOuterLegs => Layer.OuterLegs, + BaseOuterTorso => Layer.OuterTorso, + BasePants => Layer.Pants, + BaseShirt => Layer.Shirt, + BaseWaist => Layer.Waist, + BaseShoes => Layer.Shoes, + _ => item.Layer + }; + + if (item is Sandals) + { + item.Hue = 0; } } + } - public VendorItem GetVendorItem(Item item) + public override void OnAfterDelete() + { + base.OnAfterDelete(); + + _payTimer.Stop(); + + House = null; + + Placeholder?.Delete(); + } + + public override bool IsSnoop(Mobile from) => false; + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + if (BaseHouse.NewVendorSystem) { - m_SellItems.TryGetValue(item, out var v); - return v; + list.Add(1062449, ShopName); // Shop Name: ~1_NAME~ } + } - private VendorItem SetVendorItem(Item item, int price, string description) => - SetVendorItem(item, price, description, Core.Now); + public VendorItem GetVendorItem(Item item) + { + _sellItems.TryGetValue(item, out var v); + return v; + } - private VendorItem SetVendorItem(Item item, int price, string description, DateTime created) + private VendorItem SetVendorItem(Item item, int price, string description) => + SetVendorItem(item, price, description, Core.Now); + + private VendorItem SetVendorItem(Item item, int price, string description, DateTime created) + { + RemoveVendorItem(item); + + var vi = new VendorItem(item, price, description, created); + _sellItems[item] = vi; + + item.InvalidateProperties(); + + return vi; + } + + private void RemoveVendorItem(Item item) + { + var vi = GetVendorItem(item); + + if (vi != null) { - RemoveVendorItem(item); + vi.Invalidate(); + _sellItems.Remove(item); - var vi = new VendorItem(item, price, description, created); - m_SellItems[item] = vi; + foreach (var subItem in item.Items) + { + RemoveVendorItem(subItem); + } item.InvalidateProperties(); + } + } - return vi; + private bool CanBeVendorItem(Item item) + { + var parent = item.Parent as Item; + + if (parent == Backpack) + { + return true; } - private void RemoveVendorItem(Item item) + if (parent is Container) { - var vi = GetVendorItem(item); + var parentVI = GetVendorItem(parent); - if (vi != null) + if (parentVI != null) { - vi.Invalidate(); - m_SellItems.Remove(item); - - foreach (var subItem in item.Items) - { - RemoveVendorItem(subItem); - } - - item.InvalidateProperties(); + return !parentVI.IsForSale; } } - private bool CanBeVendorItem(Item item) + return false; + } + + public override void OnSubItemAdded(Item item) + { + base.OnSubItemAdded(item); + + if (GetVendorItem(item) == null && CanBeVendorItem(item)) { - var parent = item.Parent as Item; + SetVendorItem(item, 999, ""); + } + } - if (parent == Backpack) + public override void OnSubItemRemoved(Item item) + { + base.OnSubItemRemoved(item); + + if (item.GetBounce() == null) + { + RemoveVendorItem(item); + } + } + + public override void OnSubItemBounceCleared(Item item) + { + base.OnSubItemBounceCleared(item); + + if (!CanBeVendorItem(item)) + { + RemoveVendorItem(item); + } + } + + public override void OnItemRemoved(Item item) + { + base.OnItemRemoved(item); + + if (item == Backpack) + { + foreach (var subItem in item.Items) { - return true; - } - - if (parent is Container) - { - var parentVI = GetVendorItem(parent); - - if (parentVI != null) - { - return !parentVI.IsForSale; - } + RemoveVendorItem(subItem); } + } + } + public override bool OnDragDrop(Mobile from, Item item) + { + if (!IsOwner(from)) + { + SayTo(from, 503209); // I can only take item from the shop owner. return false; } - public override void OnSubItemAdded(Item item) + if (item is Gold) { - base.OnSubItemAdded(item); - - if (GetVendorItem(item) == null && CanBeVendorItem(item)) + if (BaseHouse.NewVendorSystem) { - SetVendorItem(item, 999, ""); - } - } - - public override void OnSubItemRemoved(Item item) - { - base.OnSubItemRemoved(item); - - if (item.GetBounce() == null) - { - RemoveVendorItem(item); - } - } - - public override void OnSubItemBounceCleared(Item item) - { - base.OnSubItemBounceCleared(item); - - if (!CanBeVendorItem(item)) - { - RemoveVendorItem(item); - } - } - - public override void OnItemRemoved(Item item) - { - base.OnItemRemoved(item); - - if (item == Backpack) - { - foreach (var subItem in item.Items) - { - RemoveVendorItem(subItem); - } - } - } - - public override bool OnDragDrop(Mobile from, Item item) - { - if (!IsOwner(from)) - { - SayTo(from, 503209); // I can only take item from the shop owner. - return false; - } - - if (item is Gold) - { - if (BaseHouse.NewVendorSystem) - { - if (HoldGold < 1000000) - { - SayTo(from, 503210); // I'll take that to fund my services. - - HoldGold += item.Amount; - item.Delete(); - - return true; - } - - from.SendLocalizedMessage( - 1062493 - ); // Your vendor has sufficient funds for operation and cannot accept this gold. - - return false; - } - - if (BankAccount < 1000000) + if (HoldGold < 1000000) { SayTo(from, 503210); // I'll take that to fund my services. - BankAccount += item.Amount; + HoldGold += item.Amount; item.Delete(); return true; @@ -895,817 +534,750 @@ namespace Server.Mobiles return false; } - var newItem = GetVendorItem(item) == null; - - if (Backpack?.TryDropItem(from, item, false) == true) + if (BankAccount < 1000000) { - if (newItem) - { - OnItemGiven(from, item); - } + SayTo(from, 503210); // I'll take that to fund my services. + + BankAccount += item.Amount; + item.Delete(); return true; } - SayTo(from, 503211); // I can't carry any more. + from.SendLocalizedMessage( + 1062493 + ); // Your vendor has sufficient funds for operation and cannot accept this gold. + return false; } - public override bool CheckNonlocalDrop(Mobile from, Item item, Item target) + var newItem = GetVendorItem(item) == null; + + if (Backpack?.TryDropItem(from, item, false) == true) { - if (IsOwner(from)) + if (newItem) { - if (GetVendorItem(item) == null) - { - Timer.StartTimer(() => OnItemGiven(from, item)); - } - - return true; - } - - SayTo(from, 503209); // I can only take item from the shop owner. - return false; - } - - private void OnItemGiven(Mobile from, Item item) - { - var vi = GetVendorItem(item); - - if (vi == null) - { - return; - } - - var name = item.Name.DefaultIfNullOrEmpty($"#{item.LabelNumber}"); - - from.SendLocalizedMessage(1043303, name); // Type in a price and description for ~1_ITEM~ (ESC=not for sale) - from.Prompt = new VendorPricePrompt(this, vi); - } - - public override bool AllowEquipFrom(Mobile from) => - BaseHouse.NewVendorSystem && IsOwner(from) || base.AllowEquipFrom(from); - - public override bool CheckNonlocalLift(Mobile from, Item item) - { - if (item.IsChildOf(Backpack)) - { - if (IsOwner(from)) - { - return true; - } - - SayTo(from, 503223); // If you'd like to purchase an item, just ask. - return false; - } - - if (BaseHouse.NewVendorSystem && IsOwner(from)) - { - return true; - } - - return base.CheckNonlocalLift(from, item); - } - - public bool CanInteractWith(Mobile from, bool ownerOnly) - { - if (!from.CanSee(this) || !Utility.InUpdateRange(from.Location, Location) || !from.CheckAlive()) - { - return false; - } - - if (ownerOnly) - { - return IsOwner(from); - } - - if (House?.IsBanned(from) == true && !IsOwner(from)) - { - from.SendLocalizedMessage( - 1062674 - ); // You can't shop from this home as you have been banned from this establishment. - return false; + OnItemGiven(from, item); } return true; } - public override void OnDoubleClick(Mobile from) + SayTo(from, 503211); // I can't carry any more. + return false; + } + + public override bool CheckNonlocalDrop(Mobile from, Item item, Item target) + { + if (IsOwner(from)) + { + if (GetVendorItem(item) == null) + { + Timer.StartTimer(() => OnItemGiven(from, item)); + } + + return true; + } + + SayTo(from, 503209); // I can only take item from the shop owner. + return false; + } + + private void OnItemGiven(Mobile from, Item item) + { + var vi = GetVendorItem(item); + + if (vi == null) + { + return; + } + + var name = item.Name.DefaultIfNullOrEmpty($"#{item.LabelNumber}"); + + from.SendLocalizedMessage(1043303, name); // Type in a price and description for ~1_ITEM~ (ESC=not for sale) + from.Prompt = new VendorPricePrompt(this, vi); + } + + public override bool AllowEquipFrom(Mobile from) => + BaseHouse.NewVendorSystem && IsOwner(from) || base.AllowEquipFrom(from); + + public override bool CheckNonlocalLift(Mobile from, Item item) + { + if (item.IsChildOf(Backpack)) + { + if (IsOwner(from)) + { + return true; + } + + SayTo(from, 503223); // If you'd like to purchase an item, just ask. + return false; + } + + if (BaseHouse.NewVendorSystem && IsOwner(from)) + { + return true; + } + + return base.CheckNonlocalLift(from, item); + } + + public bool CanInteractWith(Mobile from, bool ownerOnly) + { + if (!from.CanSee(this) || !Utility.InUpdateRange(from.Location, Location) || !from.CheckAlive()) + { + return false; + } + + if (ownerOnly) + { + return IsOwner(from); + } + + if (House?.IsBanned(from) == true && !IsOwner(from)) + { + from.SendLocalizedMessage( + 1062674 + ); // You can't shop from this home as you have been banned from this establishment. + return false; + } + + return true; + } + + public override void OnDoubleClick(Mobile from) + { + if (IsOwner(from)) + { + SendOwnerGump(from); + } + else if (CanInteractWith(from, false)) + { + OpenBackpack(from); + } + } + + public override void DisplayPaperdollTo(Mobile m) + { + if (BaseHouse.NewVendorSystem) + { + base.DisplayPaperdollTo(m); + } + else if (CanInteractWith(m, false)) + { + OpenBackpack(m); + } + } + + public void SendOwnerGump(Mobile to) + { + if (BaseHouse.NewVendorSystem) + { + to.CloseGump(); + to.CloseGump(); + + to.SendGump(new NewPlayerVendorOwnerGump(this)); + } + else + { + to.CloseGump(); + to.CloseGump(); + + to.SendGump(new PlayerVendorOwnerGump(this)); + } + } + + public void OpenBackpack(Mobile from) + { + if (Backpack != null) + { + SayTo(from, IsOwner(from) ? 1010642 : 503208); // Take a look at my/your goods. + + Backpack.DisplayTo(from); + } + } + + public static void TryToBuy(Item item, Mobile from) + { + if (item.RootParent is not PlayerVendor vendor || !vendor.CanInteractWith(from, false)) + { + return; + } + + if (vendor.IsOwner(from)) + { + vendor.SayTo(from, 503212); // You own this shop, just take what you want. + return; + } + + var vi = vendor.GetVendorItem(item); + + if (vi == null) + { + vendor.SayTo(from, 503216); // You can't buy that. + } + else if (!vi.IsForSale) + { + vendor.SayTo(from, 503202); // This item is not for sale. + } + else if (vi.Created + TimeSpan.FromMinutes(1.0) > Core.Now) + { + from.SendMessage("You cannot buy this item right now. Please wait one minute and try again."); + } + else + { + from.CloseGump(); + from.SendGump(new PlayerVendorBuyGump(vendor, vi)); + } + } + + public void CollectGold(Mobile to) + { + if (HoldGold > 0) + { + SayTo(to, $"How much of the {HoldGold} that I'm holding would you like?"); + to.SendMessage("Enter the amount of gold you wish to withdraw (ESC = CANCEL):"); + + to.Prompt = new CollectGoldPrompt(this); + } + else + { + SayTo(to, 503215); // I am holding no gold for you. + } + } + + public int GiveGold(Mobile to, int amount) + { + if (amount <= 0) + { + return 0; + } + + if (amount > HoldGold) + { + SayTo(to, $"I'm sorry, but I'm only holding {HoldGold} gold for you."); + return 0; + } + + var amountGiven = Banker.DepositUpTo(to, amount); + HoldGold -= amountGiven; + + if (amountGiven > 0) + { + to.SendLocalizedMessage( + 1060397, + amountGiven.ToString() + ); // ~1_AMOUNT~ gold has been deposited into your bank box. + } + + if (amountGiven == 0) + { + SayTo( + to, + 1070755 + ); // Your bank box cannot hold the gold you are requesting. I will keep the gold until you can take it. + } + else if (amount > amountGiven) + { + SayTo( + to, + 1070756 + ); // I can only give you part of the gold now, as your bank box is too full to hold the full amount. + } + else if (HoldGold > 0) + { + SayTo(to, 1042639); // Your gold has been transferred. + } + else + { + SayTo(to, 503234); // All the gold I have been carrying for you has been deposited into your bank account. + } + + return amountGiven; + } + + public void Dismiss(Mobile from) + { + var pack = Backpack; + + if (pack?.Items.Count > 0) + { + SayTo(from, 1038325); // You cannot dismiss me while I am holding your goods. + return; + } + + if (HoldGold > 0) + { + GiveGold(from, HoldGold); + + if (HoldGold > 0) + { + return; + } + } + + Destroy(true); + } + + public void Rename(Mobile from) + { + from.SendLocalizedMessage(1062494); // Enter a new name for your vendor (20 characters max): + + from.Prompt = new VendorNamePrompt(this); + } + + public void RenameShop(Mobile from) + { + from.SendLocalizedMessage(1062433); // Enter a new name for your shop (20 chars max): + + from.Prompt = new ShopNamePrompt(this); + } + + public bool CheckTeleport(Mobile to) + { + if (Deleted || !IsOwner(to) || House == null || Map == Map.Internal) + { + return false; + } + + if (House.IsInside(to) || to.Map != House.Map || !House.InRange(to.Location, 5)) + { + return false; + } + + if (Placeholder == null) + { + Placeholder = new PlayerVendorPlaceholder(this); + Placeholder.MoveToWorld(Location, Map); + + MoveToWorld(to.Location, to.Map); + + to.SendLocalizedMessage( + 1062431 + ); // This vendor has been moved out of the house to your current location temporarily. The vendor will return home automatically after two minutes have passed once you are done managing its inventory or customizing it. + } + else + { + Placeholder.RestartTimer(); + + to.SendLocalizedMessage( + 1062430 + ); // This vendor is currently temporarily in a location outside its house. The vendor will return home automatically after two minutes have passed once you are done managing its inventory or customizing it. + } + + return true; + } + + public void Return() + { + Placeholder?.Delete(); + } + + public override void GetContextMenuEntries(Mobile from, List list) + { + if (from.Alive && Placeholder != null && IsOwner(from)) + { + list.Add(new ReturnVendorEntry(this)); + } + + base.GetContextMenuEntries(from, list); + } + + public override bool HandlesOnSpeech(Mobile from) => from.Alive && from.GetDistanceToSqrt(this) <= 3; + + public bool WasNamed(string speech) => Name != null && speech.InsensitiveStartsWith(Name); + + public override void OnSpeech(SpeechEventArgs e) + { + var from = e.Mobile; + + if (e.Handled || !from.Alive || from.GetDistanceToSqrt(this) > 3) + { + return; + } + + if (e.HasKeyword(0x3C) || e.HasKeyword(0x171) && WasNamed(e.Speech)) // vendor buy, *buy* + { + if (IsOwner(from)) + { + SayTo(from, 503212); // You own this shop, just take what you want. + } + else if (House?.IsBanned(from) != true) + { + from.SendLocalizedMessage(503213); // Select the item you wish to buy. + from.Target = new PVBuyTarget(); + + e.Handled = true; + } + } + else if (e.HasKeyword(0x3D) || e.HasKeyword(0x172) && WasNamed(e.Speech)) // vendor browse, *browse + { + if (House?.IsBanned(from) == true && !IsOwner(from)) + { + SayTo(from, 1062674); // You can't shop from this home as you have been banned from this establishment. + } + else + { + if (WasNamed(e.Speech)) + { + OpenBackpack(from); + } + else + { + foreach (var m in e.Mobile.GetMobilesInRange(2)) + { + if (m.CanSee(e.Mobile) && m.InLOS(e.Mobile)) + { + m.OpenBackpack(from); + } + } + } + + e.Handled = true; + } + } + else if (e.HasKeyword(0x3E) || e.HasKeyword(0x173) && WasNamed(e.Speech)) // vendor collect, *collect + { + if (IsOwner(from)) + { + CollectGold(from); + + e.Handled = true; + } + } + else if (e.HasKeyword(0x3F) || e.HasKeyword(0x174) && WasNamed(e.Speech)) // vendor status, *status { if (IsOwner(from)) { SendOwnerGump(from); + + e.Handled = true; } - else if (CanInteractWith(from, false)) + else { - OpenBackpack(from); + SayTo(from, 503226); // What do you care? You don't run this shop. } } + else if (e.HasKeyword(0x40) || e.HasKeyword(0x175) && WasNamed(e.Speech)) // vendor dismiss, *dismiss + { + if (IsOwner(from)) + { + Dismiss(from); - public override void DisplayPaperdollTo(Mobile m) + e.Handled = true; + } + } + else if (e.HasKeyword(0x41) || e.HasKeyword(0x176) && WasNamed(e.Speech)) // vendor cycle, *cycle + { + if (IsOwner(from)) + { + Direction = GetDirectionTo(from); + + e.Handled = true; + } + } + } + + public override bool CanBeDamaged() => false; + + private class ReturnVendorEntry : ContextMenuEntry + { + private readonly PlayerVendor m_Vendor; + + public ReturnVendorEntry(PlayerVendor vendor) : base(6214) => m_Vendor = vendor; + + public override void OnClick() + { + var from = Owner.From; + + if (!m_Vendor.Deleted && m_Vendor.IsOwner(from) && from.CheckAlive()) + { + m_Vendor.Return(); + } + } + } + + private class PayTimer : Timer + { + private readonly PlayerVendor m_Vendor; + + public PayTimer(PlayerVendor vendor, TimeSpan delay) : base(delay, GetInterval()) + { + m_Vendor = vendor; + } + + public static TimeSpan GetInterval() { if (BaseHouse.NewVendorSystem) { - base.DisplayPaperdollTo(m); - } - else if (CanInteractWith(m, false)) - { - OpenBackpack(m); + return TimeSpan.FromDays(1.0); } + + return TimeSpan.FromMinutes(Clock.MinutesPerUODay); } - public void SendOwnerGump(Mobile to) + protected override void OnTick() { + m_Vendor.NextPayTime = Core.Now + Interval; + + int pay; + int totalGold; if (BaseHouse.NewVendorSystem) { - to.CloseGump(); - to.CloseGump(); - - to.SendGump(new NewPlayerVendorOwnerGump(this)); + pay = m_Vendor.ChargePerRealWorldDay; + totalGold = m_Vendor.HoldGold; } else { - to.CloseGump(); - to.CloseGump(); - - to.SendGump(new PlayerVendorOwnerGump(this)); - } - } - - public void OpenBackpack(Mobile from) - { - if (Backpack != null) - { - SayTo(from, IsOwner(from) ? 1010642 : 503208); // Take a look at my/your goods. - - Backpack.DisplayTo(from); - } - } - - public static void TryToBuy(Item item, Mobile from) - { - if (item.RootParent is not PlayerVendor vendor || !vendor.CanInteractWith(from, false)) - { - return; + pay = m_Vendor.ChargePerDay; + totalGold = m_Vendor.BankAccount + m_Vendor.HoldGold; } - if (vendor.IsOwner(from)) + if (pay > totalGold) { - vendor.SayTo(from, 503212); // You own this shop, just take what you want. - return; - } - - var vi = vendor.GetVendorItem(item); - - if (vi == null) - { - vendor.SayTo(from, 503216); // You can't buy that. - } - else if (!vi.IsForSale) - { - vendor.SayTo(from, 503202); // This item is not for sale. - } - else if (vi.Created + TimeSpan.FromMinutes(1.0) > Core.Now) - { - from.SendMessage("You cannot buy this item right now. Please wait one minute and try again."); + m_Vendor.Destroy(!BaseHouse.NewVendorSystem); } else { - from.CloseGump(); - from.SendGump(new PlayerVendorBuyGump(vendor, vi)); - } - } - - public void CollectGold(Mobile to) - { - if (HoldGold > 0) - { - SayTo(to, $"How much of the {HoldGold} that I'm holding would you like?"); - to.SendMessage("Enter the amount of gold you wish to withdraw (ESC = CANCEL):"); - - to.Prompt = new CollectGoldPrompt(this); - } - else - { - SayTo(to, 503215); // I am holding no gold for you. - } - } - - public int GiveGold(Mobile to, int amount) - { - if (amount <= 0) - { - return 0; - } - - if (amount > HoldGold) - { - SayTo(to, $"I'm sorry, but I'm only holding {HoldGold} gold for you."); - return 0; - } - - var amountGiven = Banker.DepositUpTo(to, amount); - HoldGold -= amountGiven; - - if (amountGiven > 0) - { - to.SendLocalizedMessage( - 1060397, - amountGiven.ToString() - ); // ~1_AMOUNT~ gold has been deposited into your bank box. - } - - if (amountGiven == 0) - { - SayTo( - to, - 1070755 - ); // Your bank box cannot hold the gold you are requesting. I will keep the gold until you can take it. - } - else if (amount > amountGiven) - { - SayTo( - to, - 1070756 - ); // I can only give you part of the gold now, as your bank box is too full to hold the full amount. - } - else if (HoldGold > 0) - { - SayTo(to, 1042639); // Your gold has been transferred. - } - else - { - SayTo(to, 503234); // All the gold I have been carrying for you has been deposited into your bank account. - } - - return amountGiven; - } - - public void Dismiss(Mobile from) - { - var pack = Backpack; - - if (pack?.Items.Count > 0) - { - SayTo(from, 1038325); // You cannot dismiss me while I am holding your goods. - return; - } - - if (HoldGold > 0) - { - GiveGold(from, HoldGold); - - if (HoldGold > 0) + if (!BaseHouse.NewVendorSystem) { - return; - } - } - - Destroy(true); - } - - public void Rename(Mobile from) - { - from.SendLocalizedMessage(1062494); // Enter a new name for your vendor (20 characters max): - - from.Prompt = new VendorNamePrompt(this); - } - - public void RenameShop(Mobile from) - { - from.SendLocalizedMessage(1062433); // Enter a new name for your shop (20 chars max): - - from.Prompt = new ShopNamePrompt(this); - } - - public bool CheckTeleport(Mobile to) - { - if (Deleted || !IsOwner(to) || House == null || Map == Map.Internal) - { - return false; - } - - if (House.IsInside(to) || to.Map != House.Map || !House.InRange(to.Location, 5)) - { - return false; - } - - if (Placeholder == null) - { - Placeholder = new PlayerVendorPlaceholder(this); - Placeholder.MoveToWorld(Location, Map); - - MoveToWorld(to.Location, to.Map); - - to.SendLocalizedMessage( - 1062431 - ); // This vendor has been moved out of the house to your current location temporarily. The vendor will return home automatically after two minutes have passed once you are done managing its inventory or customizing it. - } - else - { - Placeholder.RestartTimer(); - - to.SendLocalizedMessage( - 1062430 - ); // This vendor is currently temporarily in a location outside its house. The vendor will return home automatically after two minutes have passed once you are done managing its inventory or customizing it. - } - - return true; - } - - public void Return() - { - Placeholder?.Delete(); - } - - public override void GetContextMenuEntries(Mobile from, List list) - { - if (from.Alive && Placeholder != null && IsOwner(from)) - { - list.Add(new ReturnVendorEntry(this)); - } - - base.GetContextMenuEntries(from, list); - } - - public override bool HandlesOnSpeech(Mobile from) => from.Alive && from.GetDistanceToSqrt(this) <= 3; - - public bool WasNamed(string speech) => Name != null && speech.InsensitiveStartsWith(Name); - - public override void OnSpeech(SpeechEventArgs e) - { - var from = e.Mobile; - - if (e.Handled || !from.Alive || from.GetDistanceToSqrt(this) > 3) - { - return; - } - - if (e.HasKeyword(0x3C) || e.HasKeyword(0x171) && WasNamed(e.Speech)) // vendor buy, *buy* - { - if (IsOwner(from)) - { - SayTo(from, 503212); // You own this shop, just take what you want. - } - else if (House?.IsBanned(from) != true) - { - from.SendLocalizedMessage(503213); // Select the item you wish to buy. - from.Target = new PVBuyTarget(); - - e.Handled = true; - } - } - else if (e.HasKeyword(0x3D) || e.HasKeyword(0x172) && WasNamed(e.Speech)) // vendor browse, *browse - { - if (House?.IsBanned(from) == true && !IsOwner(from)) - { - SayTo(from, 1062674); // You can't shop from this home as you have been banned from this establishment. - } - else - { - if (WasNamed(e.Speech)) + if (m_Vendor.BankAccount >= pay) { - OpenBackpack(from); + m_Vendor.BankAccount -= pay; + pay = 0; } else { - foreach (var m in e.Mobile.GetMobilesInRange(2)) - { - if (m.CanSee(e.Mobile) && m.InLOS(e.Mobile)) - { - m.OpenBackpack(from); - } - } + pay -= m_Vendor.BankAccount; + m_Vendor.BankAccount = 0; } + } - e.Handled = true; - } - } - else if (e.HasKeyword(0x3E) || e.HasKeyword(0x173) && WasNamed(e.Speech)) // vendor collect, *collect - { - if (IsOwner(from)) - { - CollectGold(from); - - e.Handled = true; - } - } - else if (e.HasKeyword(0x3F) || e.HasKeyword(0x174) && WasNamed(e.Speech)) // vendor status, *status - { - if (IsOwner(from)) - { - SendOwnerGump(from); - - e.Handled = true; - } - else - { - SayTo(from, 503226); // What do you care? You don't run this shop. - } - } - else if (e.HasKeyword(0x40) || e.HasKeyword(0x175) && WasNamed(e.Speech)) // vendor dismiss, *dismiss - { - if (IsOwner(from)) - { - Dismiss(from); - - e.Handled = true; - } - } - else if (e.HasKeyword(0x41) || e.HasKeyword(0x176) && WasNamed(e.Speech)) // vendor cycle, *cycle - { - if (IsOwner(from)) - { - Direction = GetDirectionTo(from); - - e.Handled = true; - } + m_Vendor.HoldGold -= pay; } } + } - public override bool CanBeDamaged() => false; + [PlayerVendorTarget] + private class PVBuyTarget : Target + { + public PVBuyTarget() : base(3, false, TargetFlags.None) => AllowNonlocal = true; - private class ReturnVendorEntry : ContextMenuEntry + protected override void OnTarget(Mobile from, object targeted) { - private readonly PlayerVendor m_Vendor; - - public ReturnVendorEntry(PlayerVendor vendor) : base(6214) => m_Vendor = vendor; - - public override void OnClick() + if (targeted is Item item) { - var from = Owner.From; - - if (!m_Vendor.Deleted && m_Vendor.IsOwner(from) && from.CheckAlive()) - { - m_Vendor.Return(); - } + TryToBuy(item, from); } } + } - private class PayTimer : Timer + private class VendorPricePrompt : Prompt + { + private readonly PlayerVendor m_Vendor; + private readonly VendorItem m_VI; + + public VendorPricePrompt(PlayerVendor vendor, VendorItem vi) { - private readonly PlayerVendor m_Vendor; + m_Vendor = vendor; + m_VI = vi; + } - public PayTimer(PlayerVendor vendor, TimeSpan delay) : base(delay, GetInterval()) + public override void OnResponse(Mobile from, string text) + { + if (!m_VI.Valid || !m_Vendor.CanInteractWith(from, true)) { - m_Vendor = vendor; + return; } - public static TimeSpan GetInterval() - { - if (BaseHouse.NewVendorSystem) - { - return TimeSpan.FromDays(1.0); - } + string firstWord; - return TimeSpan.FromMinutes(Clock.MinutesPerUODay); + var sep = text.IndexOfAny(new[] { ' ', ',' }); + firstWord = sep >= 0 ? text[..sep] : text; + + string description; + + if (int.TryParse(firstWord, out var price)) + { + description = sep >= 0 ? text[(sep + 1)..].Trim() : ""; + } + else + { + price = -1; + description = text.Trim(); } - protected override void OnTick() + SetInfo(from, price, description.FixHtml()); + } + + public override void OnCancel(Mobile from) + { + if (!m_VI.Valid || !m_Vendor.CanInteractWith(from, true)) { - m_Vendor.NextPayTime = Core.Now + Interval; + return; + } - int pay; - int totalGold; - if (BaseHouse.NewVendorSystem) - { - pay = m_Vendor.ChargePerRealWorldDay; - totalGold = m_Vendor.HoldGold; - } - else - { - pay = m_Vendor.ChargePerDay; - totalGold = m_Vendor.BankAccount + m_Vendor.HoldGold; - } + SetInfo(from, -1, ""); + } - if (pay > totalGold) + private void SetInfo(Mobile from, int price, string description) + { + var item = m_VI.Item; + + var setPrice = false; + + if (price < 0) // Not for sale + { + price = -1; + + if (item is Container) { - m_Vendor.Destroy(!BaseHouse.NewVendorSystem); - } - else - { - if (!BaseHouse.NewVendorSystem) + if (item is LockableContainer container && container.Locked) { - if (m_Vendor.BankAccount >= pay) - { - m_Vendor.BankAccount -= pay; - pay = 0; - } - else - { - pay -= m_Vendor.BankAccount; - m_Vendor.BankAccount = 0; - } + m_Vendor.SayTo(from, 1043298); // Locked items may not be made not-for-sale. } - - m_Vendor.HoldGold -= pay; - } - } - } - - [PlayerVendorTarget] - private class PVBuyTarget : Target - { - public PVBuyTarget() : base(3, false, TargetFlags.None) => AllowNonlocal = true; - - protected override void OnTarget(Mobile from, object targeted) - { - if (targeted is Item item) - { - TryToBuy(item, from); - } - } - } - - private class VendorPricePrompt : Prompt - { - private readonly PlayerVendor m_Vendor; - private readonly VendorItem m_VI; - - public VendorPricePrompt(PlayerVendor vendor, VendorItem vi) - { - m_Vendor = vendor; - m_VI = vi; - } - - public override void OnResponse(Mobile from, string text) - { - if (!m_VI.Valid || !m_Vendor.CanInteractWith(from, true)) - { - return; - } - - string firstWord; - - var sep = text.IndexOfAny(new[] { ' ', ',' }); - firstWord = sep >= 0 ? text[..sep] : text; - - string description; - - if (int.TryParse(firstWord, out var price)) - { - description = sep >= 0 ? text[(sep + 1)..].Trim() : ""; - } - else - { - price = -1; - description = text.Trim(); - } - - SetInfo(from, price, description.FixHtml()); - } - - public override void OnCancel(Mobile from) - { - if (!m_VI.Valid || !m_Vendor.CanInteractWith(from, true)) - { - return; - } - - SetInfo(from, -1, ""); - } - - private void SetInfo(Mobile from, int price, string description) - { - var item = m_VI.Item; - - var setPrice = false; - - if (price < 0) // Not for sale - { - price = -1; - - if (item is Container) + else if (item.Items.Count > 0) { - if (item is LockableContainer container && container.Locked) - { - m_Vendor.SayTo(from, 1043298); // Locked items may not be made not-for-sale. - } - else if (item.Items.Count > 0) - { - m_Vendor.SayTo(from, 1043299); // To be not for sale, all items in a container must be for sale. - } - else - { - setPrice = true; - } + m_Vendor.SayTo(from, 1043299); // To be not for sale, all items in a container must be for sale. } - else if (item is BaseBook or BulkOrderBook) + else { setPrice = true; } - else - { - m_Vendor.SayTo( - from, - 1043301 - ); // Only the following may be made not-for-sale: books, containers, keyrings, and items in for-sale containers. - } } - else + else if (item is BaseBook or BulkOrderBook) { - if (price > 100000000) - { - price = 100000000; - from.SendMessage("You cannot price items above 100,000,000 gold. The price has been adjusted."); - } - setPrice = true; } - - if (setPrice) - { - m_Vendor.SetVendorItem(item, price, description); - } else { - m_VI.Description = description; + m_Vendor.SayTo( + from, + 1043301 + ); // Only the following may be made not-for-sale: books, containers, keyrings, and items in for-sale containers. } } - } - - private class CollectGoldPrompt : Prompt - { - private readonly PlayerVendor m_Vendor; - - public CollectGoldPrompt(PlayerVendor vendor) => m_Vendor = vendor; - - public override void OnResponse(Mobile from, string text) + else { - if (!m_Vendor.CanInteractWith(from, true)) + if (price > 100000000) { - return; + price = 100000000; + from.SendMessage("You cannot price items above 100,000,000 gold. The price has been adjusted."); } - text = text.Trim(); - - if (!int.TryParse(text, out var amount)) - { - amount = 0; - } - - GiveGold(from, amount); + setPrice = true; } - public override void OnCancel(Mobile from) + if (setPrice) { - if (!m_Vendor.CanInteractWith(from, true)) - { - return; - } - - GiveGold(from, 0); + m_Vendor.SetVendorItem(item, price, description); } - - private void GiveGold(Mobile to, int amount) + else { - if (amount <= 0) - { - m_Vendor.SayTo(to, "Very well. I will hold on to the money for now then."); - } - else - { - m_Vendor.GiveGold(to, amount); - } - } - } - - private class VendorNamePrompt : Prompt - { - private readonly PlayerVendor m_Vendor; - - public VendorNamePrompt(PlayerVendor vendor) => m_Vendor = vendor; - - public override void OnResponse(Mobile from, string text) - { - if (!m_Vendor.CanInteractWith(from, true)) - { - return; - } - - var name = text.Trim(); - - if (!NameVerification.Validate(name, 1, 20, true, true, true, 0, NameVerification.Empty)) - { - m_Vendor.SayTo(from, "That name is unacceptable."); - return; - } - - m_Vendor.Name = name.FixHtml(); - - from.SendLocalizedMessage(1062496); // Your vendor has been renamed. - - from.SendGump(new NewPlayerVendorOwnerGump(m_Vendor)); - } - } - - private class ShopNamePrompt : Prompt - { - private readonly PlayerVendor m_Vendor; - - public ShopNamePrompt(PlayerVendor vendor) => m_Vendor = vendor; - - public override void OnResponse(Mobile from, string text) - { - if (!m_Vendor.CanInteractWith(from, true)) - { - return; - } - - var name = text.Trim(); - - if (!NameVerification.Validate(name, 1, 20, true, true, true, 0, NameVerification.Empty)) - { - m_Vendor.SayTo(from, "That name is unacceptable."); - return; - } - - m_Vendor.ShopName = name.FixHtml(); - - from.SendGump(new NewPlayerVendorOwnerGump(m_Vendor)); + m_VI.Description = description; } } } - public class PlayerVendorPlaceholder : Item + private class CollectGoldPrompt : Prompt { - private readonly ExpireTimer m_Timer; + private readonly PlayerVendor m_Vendor; - public PlayerVendorPlaceholder(PlayerVendor vendor) : base(0x1F28) + public CollectGoldPrompt(PlayerVendor vendor) => m_Vendor = vendor; + + public override void OnResponse(Mobile from, string text) { - Hue = 0x672; - Movable = false; - - Vendor = vendor; - - m_Timer = new ExpireTimer(this); - m_Timer.Start(); - } - - public PlayerVendorPlaceholder(Serial serial) : base(serial) - { - } - - [CommandProperty(AccessLevel.GameMaster)] - public PlayerVendor Vendor { get; private set; } - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - if (Vendor != null) + if (!m_Vendor.CanInteractWith(from, true)) { - list.Add(1062498, Vendor.Name); // reserved for vendor ~1_NAME~ - } - } - - public void RestartTimer() - { - m_Timer.Stop(); - m_Timer.Start(); - } - - public override void OnDelete() - { - if (Vendor?.Deleted == false) - { - Vendor.MoveToWorld(Location, Map); - Vendor.Placeholder = null; - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.WriteEncodedInt(0); - - writer.Write(Vendor); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadEncodedInt(); - - Vendor = (PlayerVendor)reader.ReadEntity(); - - Timer.StartTimer(Delete); - } - - private class ExpireTimer : Timer - { - private readonly PlayerVendorPlaceholder m_Placeholder; - - public ExpireTimer(PlayerVendorPlaceholder placeholder) : base(TimeSpan.FromMinutes(2.0)) - { - m_Placeholder = placeholder; + return; } - protected override void OnTick() + text = text.Trim(); + + if (!int.TryParse(text, out var amount)) { - m_Placeholder.Delete(); + amount = 0; } + + GiveGold(from, amount); + } + + public override void OnCancel(Mobile from) + { + if (!m_Vendor.CanInteractWith(from, true)) + { + return; + } + + GiveGold(from, 0); + } + + private void GiveGold(Mobile to, int amount) + { + if (amount <= 0) + { + m_Vendor.SayTo(to, "Very well. I will hold on to the money for now then."); + } + else + { + m_Vendor.GiveGold(to, amount); + } + } + } + + private class VendorNamePrompt : Prompt + { + private readonly PlayerVendor m_Vendor; + + public VendorNamePrompt(PlayerVendor vendor) => m_Vendor = vendor; + + public override void OnResponse(Mobile from, string text) + { + if (!m_Vendor.CanInteractWith(from, true)) + { + return; + } + + var name = text.Trim(); + + if (!NameVerification.Validate(name, 1, 20, true, true, true, 0, NameVerification.Empty)) + { + m_Vendor.SayTo(from, "That name is unacceptable."); + return; + } + + m_Vendor.Name = name.FixHtml(); + + from.SendLocalizedMessage(1062496); // Your vendor has been renamed. + + from.SendGump(new NewPlayerVendorOwnerGump(m_Vendor)); + } + } + + private class ShopNamePrompt : Prompt + { + private readonly PlayerVendor m_Vendor; + + public ShopNamePrompt(PlayerVendor vendor) => m_Vendor = vendor; + + public override void OnResponse(Mobile from, string text) + { + if (!m_Vendor.CanInteractWith(from, true)) + { + return; + } + + var name = text.Trim(); + + if (!NameVerification.Validate(name, 1, 20, true, true, true, 0, NameVerification.Empty)) + { + m_Vendor.SayTo(from, "That name is unacceptable."); + return; + } + + m_Vendor.ShopName = name.FixHtml(); + + from.SendGump(new NewPlayerVendorOwnerGump(m_Vendor)); } } } diff --git a/Projects/UOContent/Mobiles/Vendors/PlayerVendorPlaceholder.cs b/Projects/UOContent/Mobiles/Vendors/PlayerVendorPlaceholder.cs new file mode 100644 index 000000000..f4eb07985 --- /dev/null +++ b/Projects/UOContent/Mobiles/Vendors/PlayerVendorPlaceholder.cs @@ -0,0 +1,52 @@ +using System; +using ModernUO.Serialization; + +namespace Server.Mobiles; + +[SerializationGenerator(0)] +public partial class PlayerVendorPlaceholder : Item +{ + private readonly Timer _timer; + + [SerializableField(0, setter: "private")] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private PlayerVendor _vendor; + + public PlayerVendorPlaceholder(PlayerVendor vendor) : base(0x1F28) + { + Hue = 0x672; + Movable = false; + + _vendor = vendor; + + _timer = Timer.DelayCall(TimeSpan.FromMinutes(2.0), Delete); + } + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + if (Vendor != null) + { + list.Add(1062498, Vendor.Name); // reserved for vendor ~1_NAME~ + } + } + + public void RestartTimer() + { + _timer.Stop(); + _timer.Start(); + } + + public override void OnDelete() + { + if (Vendor?.Deleted == false) + { + Vendor.MoveToWorld(Location, Map); + Vendor.Placeholder = null; + } + } + + [AfterDeserialization(false)] + private void AfterDeserialization() => Delete(); +} diff --git a/Projects/UOContent/Mobiles/Vendors/VendorBackpack.cs b/Projects/UOContent/Mobiles/Vendors/VendorBackpack.cs new file mode 100644 index 000000000..4d628bccf --- /dev/null +++ b/Projects/UOContent/Mobiles/Vendors/VendorBackpack.cs @@ -0,0 +1,188 @@ +using System.Collections.Generic; +using ModernUO.Serialization; +using Server.ContextMenus; +using Server.Engines.BulkOrders; +using Server.Ethics; +using Server.Items; +using Server.Multis; +using Server.Targeting; + +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class VendorBackpack : Backpack +{ + public VendorBackpack() + { + Layer = Layer.Backpack; + Weight = 1.0; + } + + public override int DefaultMaxWeight => 0; + + public override bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) + { + if (!base.CheckHold(m, item, message, checkItems, plusItems, plusWeight)) + { + return false; + } + + if (Ethic.IsImbued(item, true)) + { + if (message) + { + m.SendMessage("Imbued items may not be sold here."); + } + + return false; + } + + if (!BaseHouse.NewVendorSystem && Parent is PlayerVendor vendor) + { + var house = vendor.House; + + if (house?.IsAosRules == true && !house.CheckAosStorage(1 + item.TotalItems + plusItems)) + { + if (message) + { + m.SendLocalizedMessage(1061839); // This action would exceed the secure storage limit of the house. + } + + return false; + } + } + + return true; + } + + public override bool IsAccessibleTo(Mobile m) => true; + + public override bool CheckItemUse(Mobile from, Item item) + { + if (!base.CheckItemUse(from, item)) + { + return false; + } + + if (item is Container or BulkOrderBook) + { + return true; + } + + from.SendLocalizedMessage(500447); // That is not accessible. + return false; + } + + public override bool CheckTarget(Mobile from, Target targ, object targeted) => + base.CheckTarget(from, targ, targeted) && + (from.AccessLevel >= AccessLevel.GameMaster || + targ.GetType().IsDefined(typeof(PlayerVendorTargetAttribute), false)); + + public override void GetChildContextMenuEntries(Mobile from, List list, Item item) + { + base.GetChildContextMenuEntries(from, list, item); + + if (RootParent is not PlayerVendor pv || pv.IsOwner(from)) + { + return; + } + + var vi = pv.GetVendorItem(item); + + if (vi != null) + { + list.Add(new BuyEntry(item)); + } + } + + public override void GetChildNameProperties(IPropertyList list, Item item) + { + base.GetChildNameProperties(list, item); + + var pv = RootParent as PlayerVendor; + + var vi = pv?.GetVendorItem(item); + + if (vi == null) + { + return; + } + + if (!vi.IsForSale) + { + list.Add(1043307); // Price: Not for sale. + } + else if (vi.IsForFree) + { + list.Add(1043306); // Price: FREE! + } + else + { + list.Add(1043304, vi.FormattedPrice); // Price: ~1_COST~ + } + } + + public override void GetChildProperties(IPropertyList list, Item item) + { + base.GetChildProperties(list, item); + + var pv = RootParent as PlayerVendor; + + var vi = pv?.GetVendorItem(item); + + if (vi?.Description?.Length > 0) + { + list.Add(1043305, vi.Description); //
Seller's Description:
"~1_DESC~" + } + } + + public override void OnSingleClickContained(Mobile from, Item item) + { + if (RootParent is PlayerVendor vendor) + { + var vi = vendor.GetVendorItem(item); + + if (vi != null) + { + if (!vi.IsForSale) + { + item.LabelTo(from, 1043307); // Price: Not for sale. + } + else if (vi.IsForFree) + { + item.LabelTo(from, 1043306); // Price: FREE! + } + else + { + item.LabelTo(from, 1043304, vi.FormattedPrice); // Price: ~1_COST~ + } + + if (!string.IsNullOrEmpty(vi.Description)) + { + item.LabelTo(from, $"Description: {vi.Description}"); + } + } + } + + base.OnSingleClickContained(from, item); + } + + private class BuyEntry : ContextMenuEntry + { + private readonly Item m_Item; + + public BuyEntry(Item item) : base(6103) => m_Item = item; + + public override bool NonLocalUse => true; + + public override void OnClick() + { + if (m_Item.Deleted) + { + return; + } + + PlayerVendor.TryToBuy(m_Item, Owner.From); + } + } +} diff --git a/Projects/UOContent/Mobiles/Vendors/VendorItem.cs b/Projects/UOContent/Mobiles/Vendors/VendorItem.cs new file mode 100644 index 000000000..6ff848213 --- /dev/null +++ b/Projects/UOContent/Mobiles/Vendors/VendorItem.cs @@ -0,0 +1,64 @@ +using System; +using System.Globalization; +using ModernUO.Serialization; + +namespace Server.Mobiles; + +[SerializationGenerator(0, false)] +public partial class VendorItem +{ + [SerializableField(0)] + private Item _item; + + [SerializableField(1)] + private int _price; + + [SerializableField(3)] + private DateTime _created; + + public VendorItem() + { + } + + public VendorItem(Item item, int price, string description, DateTime created) + { + _item = item; + _price = price; + _description = description ?? ""; + _created = created; + Valid = true; + } + + public string FormattedPrice => + Core.ML ? Price.ToString("N0", CultureInfo.GetCultureInfo("en-US")) : Price.ToString(); + + [SerializableProperty(2)] + public string Description + { + get => _description; + set + { + _description = value ?? ""; + + if (Valid) + { + Item.InvalidateProperties(); + } + } + } + + public bool IsForSale => Price >= 0; + public bool IsForFree => Price == 0; + public bool Valid { get; private set; } + + public void Invalidate() => Valid = false; + + [AfterDeserialization] + private void AfterDeserialization() + { + if (_price > 100000000) + { + _price = 100000000; + } + } +} diff --git a/Projects/UOContent/Multis/Houses/HousePlacementTool.cs b/Projects/UOContent/Multis/Houses/HousePlacementTool.cs index ed0a06862..d03a61597 100644 --- a/Projects/UOContent/Multis/Houses/HousePlacementTool.cs +++ b/Projects/UOContent/Multis/Houses/HousePlacementTool.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using ModernUO.Serialization; using Server.Gumps; using Server.Mobiles; using Server.Multis; @@ -7,2305 +8,2281 @@ using Server.Network; using Server.Regions; using Server.Targeting; -namespace Server.Items +namespace Server.Items; + +[SerializationGenerator(0, false)] +public partial class HousePlacementTool : Item { - public class HousePlacementTool : Item + [Constructible] + public HousePlacementTool() : base(0x14F6) { - [Constructible] - public HousePlacementTool() : base(0x14F6) - { - Weight = 3.0; - LootType = LootType.Blessed; - } - - public HousePlacementTool(Serial serial) : base(serial) - { - } - - public override int LabelNumber => 1060651; // a house placement tool - - public override void OnDoubleClick(Mobile from) - { - if (IsChildOf(from.Backpack)) - { - from.SendGump(new HousePlacementCategoryGump(from)); - } - else - { - from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - if (Weight == 0.0) - { - Weight = 3.0; - } - } + Weight = 3.0; + LootType = LootType.Blessed; } - public class HousePlacementCategoryGump : Gump + public override int LabelNumber => 1060651; // a house placement tool + + public override void OnDoubleClick(Mobile from) { - private const int LabelColor = 0x7FFF; - private const int LabelColorDisabled = 0x4210; - private readonly Mobile m_From; - - public HousePlacementCategoryGump(Mobile from) : base(50, 50) + if (IsChildOf(from.Backpack)) { - m_From = from; - - from.CloseGump(); - from.CloseGump(); - - AddPage(0); - - AddBackground(0, 0, 270, 145, 5054); - - AddImageTiled(10, 10, 250, 125, 2624); - AddAlphaRegion(10, 10, 250, 125); - - AddHtmlLocalized(10, 10, 250, 20, 1060239, LabelColor); //
HOUSE PLACEMENT TOOL
- - AddButton(10, 110, 4017, 4019, 0); - AddHtmlLocalized(45, 110, 150, 20, 3000363, LabelColor); // Close - - AddButton(10, 40, 4005, 4007, 1); - AddHtmlLocalized(45, 40, 200, 20, 1060390, LabelColor); // Classic Houses - - AddButton(10, 60, 4005, 4007, 2); - AddHtmlLocalized(45, 60, 200, 20, 1060391, LabelColor); // 2-Story Customizable Houses - - AddButton(10, 80, 4005, 4007, 3); - AddHtmlLocalized(45, 80, 200, 20, 1060392, LabelColor); // 3-Story Customizable Houses + from.SendGump(new HousePlacementCategoryGump(from)); } - - public override void OnResponse(NetState sender, in RelayInfo info) + else { - if (!m_From.CheckAlive() || m_From.Backpack?.FindItemByType() == null) - { - return; - } - - switch (info.ButtonID) - { - case 1: // Classic Houses - { - var entry = Core.EJ ? HousePlacementEntry.HousesEJ : HousePlacementEntry.ClassicHouses; - m_From.SendGump(new HousePlacementListGump(m_From, entry)); - - break; - } - case 2: // 2-Story Customizable Houses - { - m_From.SendGump(new HousePlacementListGump(m_From, HousePlacementEntry.TwoStoryFoundations)); - break; - } - case 3: // 3-Story Customizable Houses - { - m_From.SendGump(new HousePlacementListGump(m_From, HousePlacementEntry.ThreeStoryFoundations)); - break; - } - } - } - } - - public class HousePlacementListGump : Gump - { - private const int LabelColor = 0x7FFF; - private const int LabelHue = 0x480; - private readonly HousePlacementEntry[] m_Entries; - private readonly Mobile m_From; - - public HousePlacementListGump(Mobile from, HousePlacementEntry[] entries) : base(50, 50) - { - m_From = from; - m_Entries = entries; - - from.CloseGump(); - from.CloseGump(); - - AddPage(0); - - AddBackground(0, 0, 520, 420, 5054); - - AddImageTiled(10, 10, 500, 20, 2624); - AddAlphaRegion(10, 10, 500, 20); - - AddHtmlLocalized(10, 10, 500, 20, 1060239, LabelColor); //
HOUSE PLACEMENT TOOL
- - AddImageTiled(10, 40, 500, 20, 2624); - AddAlphaRegion(10, 40, 500, 20); - - AddHtmlLocalized(50, 40, 225, 20, 1060235, LabelColor); // House Description - AddHtmlLocalized(275, 40, 75, 20, 1060236, LabelColor); // Storage - AddHtmlLocalized(350, 40, 75, 20, 1060237, LabelColor); // Lockdowns - AddHtmlLocalized(425, 40, 75, 20, 1060034, LabelColor); // Cost - - AddImageTiled(10, 70, 500, 280, 2624); - AddAlphaRegion(10, 70, 500, 280); - - AddImageTiled(10, 360, 500, 20, 2624); - AddAlphaRegion(10, 360, 500, 20); - - AddHtmlLocalized(10, 360, 250, 20, 1060645, LabelColor); // Bank Balance: - AddLabel(250, 360, LabelHue, Banker.GetBalance(from).ToString()); - - AddImageTiled(10, 390, 500, 20, 2624); - AddAlphaRegion(10, 390, 500, 20); - - AddButton(10, 390, 4017, 4019, 0); - AddHtmlLocalized(50, 390, 100, 20, 3000363, LabelColor); // Close - - for (var i = 0; i < entries.Length; ++i) - { - var page = 1 + i / 14; - var index = i % 14; - - if (index == 0) - { - if (page > 1) - { - AddButton(450, 390, 4005, 4007, 0, GumpButtonType.Page, page); - AddHtmlLocalized(400, 390, 100, 20, 3000406, LabelColor); // Next - } - - AddPage(page); - - if (page > 1) - { - AddButton(200, 390, 4014, 4016, 0, GumpButtonType.Page, page - 1); - AddHtmlLocalized(250, 390, 100, 20, 3000405, LabelColor); // Previous - } - } - - var entry = entries[i]; - - var y = 70 + index * 20; - - AddButton(10, y, 4005, 4007, 1 + i); - AddHtmlLocalized(50, y, 225, 20, entry.Description, LabelColor); - AddLabel(275, y, LabelHue, entry.Storage.ToString()); - AddLabel(350, y, LabelHue, entry.Lockdowns.ToString()); - AddLabel(425, y, LabelHue, entry.Cost.ToString()); - } - } - - public override void OnResponse(NetState sender, in RelayInfo info) - { - if (!m_From.CheckAlive() || m_From.Backpack?.FindItemByType() == null) - { - return; - } - - var index = info.ButtonID - 1; - - if (index >= 0 && index < m_Entries.Length) - { - if (m_From.AccessLevel < AccessLevel.GameMaster && BaseHouse.HasAccountHouse(m_From)) - { - m_From.SendLocalizedMessage(501271); // You already own a house, you may not place another! - } - else - { - m_From.Target = new NewHousePlacementTarget(m_Entries, m_Entries[index]); - } - } - else - { - m_From.SendGump(new HousePlacementCategoryGump(m_From)); - } - } - } - - public class NewHousePlacementTarget : MultiTarget - { - private readonly HousePlacementEntry[] m_Entries; - private readonly HousePlacementEntry m_Entry; - - private bool m_Placed; - - public NewHousePlacementTarget(HousePlacementEntry[] entries, HousePlacementEntry entry) : base( - entry.MultiID, - entry.Offset - ) - { - Range = 14; - - m_Entries = entries; - m_Entry = entry; - } - - protected override void OnTarget(Mobile from, object o) - { - if (o is not IPoint3D ip) - { - return; - } - - if (!from.CheckAlive() || from.Backpack?.FindItemByType() == null) - { - return; - } - - Point3D p = ip switch - { - Item item => item.GetWorldTop(), - Mobile m => m.Location, - _ => new Point3D(ip) - }; - - var reg = Region.Find(p, from.Map); - - if (from.AccessLevel >= AccessLevel.GameMaster || reg.AllowHousing(from, p)) - { - m_Placed = m_Entry.OnPlacement(from, p); - } - else if (reg.IsPartOf()) - { - // Lord British has decreed a 'no build' period, thus you cannot build this house at this time. - from.SendLocalizedMessage(501270); - } - else if (reg.IsPartOf() || reg.IsPartOf()) - { - // The house could not be created here. Either something is blocking the house, or the house would not be on valid terrain. - from.SendLocalizedMessage(1043287); - } - else if (reg.IsPartOf()) - { - from.SendLocalizedMessage(1150493); // You must have a deed for this plot of land in order to build here. - } - else - { - from.SendLocalizedMessage(501265); // Housing can not be created in this area. - } - } - - protected override void OnTargetFinish(Mobile from) - { - if (!from.CheckAlive() || from.Backpack?.FindItemByType() == null) - { - return; - } - - if (!m_Placed) - { - from.SendGump(new HousePlacementListGump(from, m_Entries)); - } - } - } - - public class HousePlacementEntry - { - private static readonly Dictionary m_Table; - private readonly int m_Lockdowns; - private readonly int m_NewLockdowns; - private readonly int m_NewStorage; - private readonly int m_Storage; - - static HousePlacementEntry() - { - m_Table = new Dictionary(); - - FillTable(Core.EJ ? HousesEJ : ClassicHouses); - FillTable(TwoStoryFoundations); - FillTable(ThreeStoryFoundations); - } - - public HousePlacementEntry( - Type type, int description, int storage, int lockdowns, int newStorage, int newLockdowns, - int vendors, int cost, int xOffset, int yOffset, int zOffset, int multiID - ) - { - Type = type; - Description = description; - m_Storage = storage; - m_Lockdowns = lockdowns; - m_NewStorage = newStorage; - m_NewLockdowns = newLockdowns; - Vendors = vendors; - Cost = cost; - - Offset = new Point3D(xOffset, yOffset, zOffset); - - MultiID = multiID; - } - - public Type Type { get; } - - public int Description { get; } - - public int Storage => BaseHouse.NewVendorSystem ? m_NewStorage : m_Storage; - public int Lockdowns => BaseHouse.NewVendorSystem ? m_NewLockdowns : m_Lockdowns; - public int Vendors { get; } - - public int Cost { get; } - - public int MultiID { get; } - - public Point3D Offset { get; } - - public static HousePlacementEntry[] ClassicHouses { get; } = - { - new(typeof(SmallOldHouse), 1011303, 425, 212, 489, 244, 10, 37000, 0, 4, 0, 0x0064), - new(typeof(SmallOldHouse), 1011304, 425, 212, 489, 244, 10, 37000, 0, 4, 0, 0x0066), - new(typeof(SmallOldHouse), 1011305, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x0068), - new(typeof(SmallOldHouse), 1011306, 425, 212, 489, 244, 10, 35250, 0, 4, 0, 0x006A), - new(typeof(SmallOldHouse), 1011307, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x006C), - new(typeof(SmallOldHouse), 1011308, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x006E), - new(typeof(SmallShop), 1011321, 425, 212, 489, 244, 10, 50500, -1, 4, 0, 0x00A0), - new(typeof(SmallShop), 1011322, 425, 212, 489, 244, 10, 52500, 0, 4, 0, 0x00A2), - new(typeof(SmallTower), 1011317, 580, 290, 667, 333, 14, 73500, 3, 4, 0, 0x0098), - new(typeof(TwoStoryVilla), 1011319, 1100, 550, 1265, 632, 24, 113750, 3, 6, 0, 0x009E), - new(typeof(SandStonePatio), 1011320, 850, 425, 1265, 632, 24, 76500, -1, 4, 0, 0x009C), - new(typeof(LogCabin), 1011318, 1100, 550, 1265, 632, 24, 81750, 1, 6, 0, 0x009A), - new(typeof(GuildHouse), 1011309, 1370, 685, 1576, 788, 28, 131500, -1, 7, 0, 0x0074), - new(typeof(TwoStoryHouse), 1011310, 1370, 685, 1576, 788, 28, 162750, -3, 7, 0, 0x0076), - new(typeof(TwoStoryHouse), 1011311, 1370, 685, 1576, 788, 28, 162000, -3, 7, 0, 0x0078), - new(typeof(LargePatioHouse), 1011315, 1370, 685, 1576, 788, 28, 129250, -4, 7, 0, 0x008C), - new(typeof(LargeMarbleHouse), 1011316, 1370, 685, 1576, 788, 28, 160500, -4, 7, 0, 0x0096), - new(typeof(Tower), 1011312, 2119, 1059, 2437, 1218, 42, 366500, 0, 7, 0, 0x007A), - new(typeof(Keep), 1011313, 2625, 1312, 3019, 1509, 52, 572750, 0, 11, 0, 0x007C), - new(typeof(Castle), 1011314, 4076, 2038, 4688, 2344, 78, 865250, 0, 16, 0, 0x007E) - }; - - public static HousePlacementEntry[] HousesEJ { get; } = - { - new(typeof(SmallOldHouse), 1011303, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x0064), - new(typeof(SmallOldHouse), 1011304, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x0066), - new(typeof(SmallOldHouse), 1011305, 425, 212, 489, 244, 10, 36500, 0, 4, 0, 0x0068), - new(typeof(SmallOldHouse), 1011306, 425, 212, 489, 244, 10, 35000, 0, 4, 0, 0x006A), - new(typeof(SmallOldHouse), 1011307, 425, 212, 489, 244, 10, 36500, 0, 4, 0, 0x006C), - new(typeof(SmallOldHouse), 1011308, 425, 212, 489, 244, 10, 36500, 0, 4, 0, 0x006E), - new(typeof(SmallShop), 1011321, 425, 212, 489, 244, 10, 50250, -1, 4, 0, 0x00A0), - new(typeof(SmallShop), 1011322, 425, 212, 489, 244, 10, 52250, 0, 4, 0, 0x00A2), - new(typeof(SmallTower), 1011317, 580, 290, 667, 333, 14, 73250, 3, 4, 0, 0x0098), - new(typeof(TwoStoryVilla), 1011319, 1100, 550, 1265, 632, 24, 113500, 3, 6, 0, 0x009E), - new(typeof(SandStonePatio), 1011320, 850, 425, 1265, 632, 24, 76250, -1, 4, 0, 0x009C), - new(typeof(LogCabin), 1011318, 1100, 550, 1265, 632, 24, 81250, 1, 6, 0, 0x009A), - new(typeof(GuildHouse), 1011309, 1370, 685, 1576, 788, 28, 131250, -1, 7, 0, 0x0074), - new(typeof(TwoStoryHouse), 1011310, 1370, 685, 1576, 788, 28, 162500, -3, 7, 0, 0x0076), - new(typeof(TwoStoryHouse), 1011311, 1370, 685, 1576, 788, 28, 162750, -3, 7, 0, 0x0078), - new(typeof(LargePatioHouse), 1011315, 1370, 685, 1576, 788, 28, 129000, -4, 7, 0, 0x008C), - new(typeof(LargeMarbleHouse), 1011316, 1370, 685, 1576, 788, 28, 160250, -4, 7, 0, 0x0096), - new(typeof(Tower), 1011312, 2119, 1059, 2437, 1218, 42, 366250, 0, 7, 0, 0x007A), - new(typeof(Keep), 1011313, 2625, 1312, 3019, 1509, 52, 562500, 0, 11, 0, 0x007C), - new(typeof(Castle), 1011314, 4076, 2038, 4688, 2344, 78, 865000, 0, 16, 0, 0x007E), - - new(typeof(TrinsicKeep), 1158748, 2625, 1312, 3019, 1509, 52, 29643750, 0, 11, 0, 0x147E), - new( - typeof(GothicRoseCastle), - 1158749, - 4076, - 2038, - 4688, - 2344, - 78, - 44808750, - 0, - 16, - 0, - 0x147F - ), - new(typeof(ElsaCastle), 1158750, 4076, 2038, 4688, 2344, 78, 45450000, 0, 16, 0, 0x1480), - new(typeof(Spires), 1158761, 4076, 2038, 4688, 2344, 78, 47025000, 0, 16, 0, 0x1481), - new( - typeof(CastleOfOceania), - 1158760, - 4076, - 2038, - 4688, - 2344, - 78, - 48971250, - 0, - 16, - 0, - 0x1482 - ), - new(typeof(FeudalCastle), 1158762, 4076, 2038, 4688, 2344, 78, 27337500, 0, 16, 0, 0x1483), - new(typeof(RobinsNest), 1158850, 2625, 1312, 3019, 1509, 52, 25301250, 0, 11, 0, 0x1484), - new( - typeof(TraditionalKeep), - 1158851, - 2625, - 1312, - 3019, - 1509, - 52, - 26685000, - 0, - 11, - 0, - 0x1485 - ), - new(typeof(VillaCrowley), 1158852, 2625, 1312, 3019, 1509, 52, 21813750, 0, 11, 0, 0x1486), - new(typeof(DarkthornKeep), 1158853, 2625, 1312, 3019, 1509, 52, 27990000, 0, 11, 0, 0x1487), - new(typeof(SandalwoodKeep), 1158854, 2625, 1312, 3019, 1509, 52, 23456250, 0, 11, 0, 0x1488), - new(typeof(CasaMoga), 1158855, 2625, 1312, 3019, 1509, 52, 26313750, 0, 11, 0, 0x1489), - - new(typeof(RobinsRoost), 1158960, 4076, 2038, 4688, 2344, 78, 43863750, 0, 16, 0, 0x148A), - new(typeof(Camelot), 1158961, 4076, 2038, 4688, 2344, 78, 47092500, 0, 16, 0, 0x148B), - new( - typeof(LacrimaeInCaelo), - 1158962, - 4076, - 2038, - 4688, - 2344, - 78, - 45315000, - 0, - 16, - 0, - 0x148C - ), - new( - typeof(OkinawaSweetDreamCastle), - 1158963, - 4076, - 2038, - 4688, - 2344, - 78, - 40128750, - 0, - 16, - 0, - 0x148D - ), - new( - typeof(TheSandstoneCastle), - 1158964, - 4076, - 2038, - 4688, - 2344, - 78, - 48690000, - 0, - 16, - 0, - 0x148E - ), - new( - typeof(GrimswindSisters), - 1158965, - 4076, - 2038, - 4688, - 2344, - 78, - 42142500, - 0, - 16, - 0, - 0x148F - ) - }; - - public static HousePlacementEntry[] TwoStoryFoundations { get; } = - { - new( - typeof(HouseFoundation), - 1060241, - 425, - 212, - 489, - 244, - 10, - 30500, - 0, - 4, - 0, - 0x13EC - ), // 7x7 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060242, - 580, - 290, - 667, - 333, - 14, - 34500, - 0, - 5, - 0, - 0x13ED - ), // 7x8 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060243, - 650, - 325, - 748, - 374, - 16, - 38500, - 0, - 5, - 0, - 0x13EE - ), // 7x9 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060244, - 700, - 350, - 805, - 402, - 16, - 42500, - 0, - 6, - 0, - 0x13EF - ), // 7x10 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060245, - 750, - 375, - 863, - 431, - 16, - 46500, - 0, - 6, - 0, - 0x13F0 - ), // 7x11 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060246, - 800, - 400, - 920, - 460, - 18, - 50500, - 0, - 7, - 0, - 0x13F1 - ), // 7x12 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060253, - 580, - 290, - 667, - 333, - 14, - 34500, - 0, - 4, - 0, - 0x13F8 - ), // 8x7 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060254, - 650, - 325, - 748, - 374, - 16, - 39000, - 0, - 5, - 0, - 0x13F9 - ), // 8x8 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060255, - 700, - 350, - 805, - 402, - 16, - 43500, - 0, - 5, - 0, - 0x13FA - ), // 8x9 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060256, - 750, - 375, - 863, - 431, - 16, - 48000, - 0, - 6, - 0, - 0x13FB - ), // 8x10 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060257, - 800, - 400, - 920, - 460, - 18, - 52500, - 0, - 6, - 0, - 0x13FC - ), // 8x11 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060258, - 850, - 425, - 1265, - 632, - 24, - 57000, - 0, - 7, - 0, - 0x13FD - ), // 8x12 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060259, - 1100, - 550, - 1265, - 632, - 24, - 61500, - 0, - 7, - 0, - 0x13FE - ), // 8x13 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060265, - 650, - 325, - 748, - 374, - 16, - 38500, - 0, - 4, - 0, - 0x1404 - ), // 9x7 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060266, - 700, - 350, - 805, - 402, - 16, - 43500, - 0, - 5, - 0, - 0x1405 - ), // 9x8 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060267, - 750, - 375, - 863, - 431, - 16, - 48500, - 0, - 5, - 0, - 0x1406 - ), // 9x9 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060268, - 800, - 400, - 920, - 460, - 18, - 53500, - 0, - 6, - 0, - 0x1407 - ), // 9x10 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060269, - 850, - 425, - 1265, - 632, - 24, - 58500, - 0, - 6, - 0, - 0x1408 - ), // 9x11 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060270, - 1100, - 550, - 1265, - 632, - 24, - 63500, - 0, - 7, - 0, - 0x1409 - ), // 9x12 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060271, - 1100, - 550, - 1265, - 632, - 24, - 68500, - 0, - 7, - 0, - 0x140A - ), // 9x13 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060277, - 700, - 350, - 805, - 402, - 16, - 42500, - 0, - 4, - 0, - 0x1410 - ), // 10x7 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060278, - 750, - 375, - 863, - 431, - 16, - 48000, - 0, - 5, - 0, - 0x1411 - ), // 10x8 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060279, - 800, - 400, - 920, - 460, - 18, - 53500, - 0, - 5, - 0, - 0x1412 - ), // 10x9 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060280, - 850, - 425, - 1265, - 632, - 24, - 59000, - 0, - 6, - 0, - 0x1413 - ), // 10x10 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060281, - 1100, - 550, - 1265, - 632, - 24, - 64500, - 0, - 6, - 0, - 0x1414 - ), // 10x11 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060282, - 1100, - 550, - 1265, - 632, - 24, - 70000, - 0, - 7, - 0, - 0x1415 - ), // 10x12 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060283, - 1150, - 575, - 1323, - 661, - 24, - 75500, - 0, - 7, - 0, - 0x1416 - ), // 10x13 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060289, - 750, - 375, - 863, - 431, - 16, - 46500, - 0, - 4, - 0, - 0x141C - ), // 11x7 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060290, - 800, - 400, - 920, - 460, - 18, - 52500, - 0, - 5, - 0, - 0x141D - ), // 11x8 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060291, - 850, - 425, - 1265, - 632, - 24, - 58500, - 0, - 5, - 0, - 0x141E - ), // 11x9 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060292, - 1100, - 550, - 1265, - 632, - 24, - 64500, - 0, - 6, - 0, - 0x141F - ), // 11x10 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060293, - 1100, - 550, - 1265, - 632, - 24, - 70500, - 0, - 6, - 0, - 0x1420 - ), // 11x11 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060294, - 1150, - 575, - 1323, - 661, - 24, - 76500, - 0, - 7, - 0, - 0x1421 - ), // 11x12 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060295, - 1200, - 600, - 1380, - 690, - 26, - 82500, - 0, - 7, - 0, - 0x1422 - ), // 11x13 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060301, - 800, - 400, - 920, - 460, - 18, - 50500, - 0, - 4, - 0, - 0x1428 - ), // 12x7 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060302, - 850, - 425, - 1265, - 632, - 24, - 57000, - 0, - 5, - 0, - 0x1429 - ), // 12x8 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060303, - 1100, - 550, - 1265, - 632, - 24, - 63500, - 0, - 5, - 0, - 0x142A - ), // 12x9 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060304, - 1100, - 550, - 1265, - 632, - 24, - 70000, - 0, - 6, - 0, - 0x142B - ), // 12x10 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060305, - 1150, - 575, - 1323, - 661, - 24, - 76500, - 0, - 6, - 0, - 0x142C - ), // 12x11 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060306, - 1200, - 600, - 1380, - 690, - 26, - 83000, - 0, - 7, - 0, - 0x142D - ), // 12x12 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060307, - 1250, - 625, - 1438, - 719, - 26, - 89500, - 0, - 7, - 0, - 0x142E - ), // 12x13 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060314, - 1100, - 550, - 1265, - 632, - 24, - 61500, - 0, - 5, - 0, - 0x1435 - ), // 13x8 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060315, - 1100, - 550, - 1265, - 632, - 24, - 68500, - 0, - 5, - 0, - 0x1436 - ), // 13x9 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060316, - 1150, - 575, - 1323, - 661, - 24, - 75500, - 0, - 6, - 0, - 0x1437 - ), // 13x10 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060317, - 1200, - 600, - 1380, - 690, - 26, - 82500, - 0, - 6, - 0, - 0x1438 - ), // 13x11 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060318, - 1250, - 625, - 1438, - 719, - 26, - 89500, - 0, - 7, - 0, - 0x1439 - ), // 13x12 2-Story Customizable House - new( - typeof(HouseFoundation), - 1060319, - 1300, - 650, - 1495, - 747, - 28, - 96500, - 0, - 7, - 0, - 0x143A - ) // 13x13 2-Story Customizable House - }; - - public static HousePlacementEntry[] ThreeStoryFoundations { get; } = - { - new( - typeof(HouseFoundation), - 1060272, - 1150, - 575, - 1323, - 661, - 24, - 73500, - 0, - 8, - 0, - 0x140B - ), // 9x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060284, - 1200, - 600, - 1380, - 690, - 26, - 81000, - 0, - 8, - 0, - 0x1417 - ), // 10x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060285, - 1250, - 625, - 1438, - 719, - 26, - 86500, - 0, - 8, - 0, - 0x1418 - ), // 10x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060296, - 1250, - 625, - 1438, - 719, - 26, - 88500, - 0, - 8, - 0, - 0x1423 - ), // 11x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060297, - 1300, - 650, - 1495, - 747, - 28, - 94500, - 0, - 8, - 0, - 0x1424 - ), // 11x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060298, - 1350, - 675, - 1553, - 776, - 28, - 100500, - 0, - 9, - 0, - 0x1425 - ), // 11x16 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060308, - 1300, - 650, - 1495, - 747, - 28, - 96000, - 0, - 8, - 0, - 0x142F - ), // 12x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060309, - 1350, - 675, - 1553, - 776, - 28, - 102500, - 0, - 8, - 0, - 0x1430 - ), // 12x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060310, - 1370, - 685, - 1576, - 788, - 28, - 109000, - 0, - 9, - 0, - 0x1431 - ), // 12x16 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060311, - 1370, - 685, - 1576, - 788, - 28, - 115500, - 0, - 9, - 0, - 0x1432 - ), // 12x17 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060320, - 1350, - 675, - 1553, - 776, - 28, - 103500, - 0, - 8, - 0, - 0x143B - ), // 13x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060321, - 1370, - 685, - 1576, - 788, - 28, - 110500, - 0, - 8, - 0, - 0x143C - ), // 13x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060322, - 1370, - 685, - 1576, - 788, - 28, - 117500, - 0, - 9, - 0, - 0x143D - ), // 13x16 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060323, - 2119, - 1059, - 2437, - 1218, - 42, - 124500, - 0, - 9, - 0, - 0x143E - ), // 13x17 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060324, - 2119, - 1059, - 2437, - 1218, - 42, - 131500, - 0, - 10, - 0, - 0x143F - ), // 13x18 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060327, - 1150, - 575, - 1323, - 661, - 24, - 73500, - 0, - 5, - 0, - 0x1442 - ), // 14x9 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060328, - 1200, - 600, - 1380, - 690, - 26, - 81000, - 0, - 6, - 0, - 0x1443 - ), // 14x10 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060329, - 1250, - 625, - 1438, - 719, - 26, - 88500, - 0, - 6, - 0, - 0x1444 - ), // 14x11 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060330, - 1300, - 650, - 1495, - 747, - 28, - 96000, - 0, - 7, - 0, - 0x1445 - ), // 14x12 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060331, - 1350, - 675, - 1553, - 776, - 28, - 103500, - 0, - 7, - 0, - 0x1446 - ), // 14x13 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060332, - 1370, - 685, - 1576, - 788, - 28, - 111000, - 0, - 8, - 0, - 0x1447 - ), // 14x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060333, - 1370, - 685, - 1576, - 788, - 28, - 118500, - 0, - 8, - 0, - 0x1448 - ), // 14x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060334, - 2119, - 1059, - 2437, - 1218, - 42, - 126000, - 0, - 9, - 0, - 0x1449 - ), // 14x16 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060335, - 2119, - 1059, - 2437, - 1218, - 42, - 133500, - 0, - 9, - 0, - 0x144A - ), // 14x17 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060336, - 2119, - 1059, - 2437, - 1218, - 42, - 141000, - 0, - 10, - 0, - 0x144B - ), // 14x18 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060340, - 1250, - 625, - 1438, - 719, - 26, - 86500, - 0, - 6, - 0, - 0x144F - ), // 15x10 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060341, - 1300, - 650, - 1495, - 747, - 28, - 94500, - 0, - 6, - 0, - 0x1450 - ), // 15x11 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060342, - 1350, - 675, - 1553, - 776, - 28, - 102500, - 0, - 7, - 0, - 0x1451 - ), // 15x12 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060343, - 1370, - 685, - 1576, - 788, - 28, - 110500, - 0, - 7, - 0, - 0x1452 - ), // 15x13 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060344, - 1370, - 685, - 1576, - 788, - 28, - 118500, - 0, - 8, - 0, - 0x1453 - ), // 15x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060345, - 2119, - 1059, - 2437, - 1218, - 42, - 126500, - 0, - 8, - 0, - 0x1454 - ), // 15x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060346, - 2119, - 1059, - 2437, - 1218, - 42, - 134500, - 0, - 9, - 0, - 0x1455 - ), // 15x16 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060347, - 2119, - 1059, - 2437, - 1218, - 42, - 142500, - 0, - 9, - 0, - 0x1456 - ), // 15x17 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060348, - 2119, - 1059, - 2437, - 1218, - 42, - 150500, - 0, - 10, - 0, - 0x1457 - ), // 15x18 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060353, - 1350, - 675, - 1553, - 776, - 28, - 100500, - 0, - 6, - 0, - 0x145C - ), // 16x11 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060354, - 1370, - 685, - 1576, - 788, - 28, - 109000, - 0, - 7, - 0, - 0x145D - ), // 16x12 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060355, - 1370, - 685, - 1576, - 788, - 28, - 117500, - 0, - 7, - 0, - 0x145E - ), // 16x13 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060356, - 2119, - 1059, - 2437, - 1218, - 42, - 126000, - 0, - 8, - 0, - 0x145F - ), // 16x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060357, - 2119, - 1059, - 2437, - 1218, - 42, - 134500, - 0, - 8, - 0, - 0x1460 - ), // 16x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060358, - 2119, - 1059, - 2437, - 1218, - 42, - 143000, - 0, - 9, - 0, - 0x1461 - ), // 16x16 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060359, - 2119, - 1059, - 2437, - 1218, - 42, - 151500, - 0, - 9, - 0, - 0x1462 - ), // 16x17 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060360, - 2119, - 1059, - 2437, - 1218, - 42, - 160000, - 0, - 10, - 0, - 0x1463 - ), // 16x18 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060366, - 1370, - 685, - 1576, - 788, - 28, - 115500, - 0, - 7, - 0, - 0x1469 - ), // 17x12 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060367, - 2119, - 1059, - 2437, - 1218, - 42, - 124500, - 0, - 7, - 0, - 0x146A - ), // 17x13 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060368, - 2119, - 1059, - 2437, - 1218, - 42, - 133500, - 0, - 8, - 0, - 0x146B - ), // 17x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060369, - 2119, - 1059, - 2437, - 1218, - 42, - 142500, - 0, - 8, - 0, - 0x146C - ), // 17x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060370, - 2119, - 1059, - 2437, - 1218, - 42, - 151500, - 0, - 9, - 0, - 0x146D - ), // 17x16 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060371, - 2119, - 1059, - 2437, - 1218, - 42, - 160500, - 0, - 9, - 0, - 0x146E - ), // 17x17 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060372, - 2119, - 1059, - 2437, - 1218, - 42, - 169500, - 0, - 10, - 0, - 0x146F - ), // 17x18 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060379, - 2119, - 1059, - 2437, - 1218, - 42, - 131500, - 0, - 7, - 0, - 0x1476 - ), // 18x13 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060380, - 2119, - 1059, - 2437, - 1218, - 42, - 141000, - 0, - 8, - 0, - 0x1477 - ), // 18x14 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060381, - 2119, - 1059, - 2437, - 1218, - 42, - 150500, - 0, - 8, - 0, - 0x1478 - ), // 18x15 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060382, - 2119, - 1059, - 2437, - 1218, - 42, - 160000, - 0, - 9, - 0, - 0x1479 - ), // 18x16 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060383, - 2119, - 1059, - 2437, - 1218, - 42, - 169500, - 0, - 9, - 0, - 0x147A - ), // 18x17 3-Story Customizable House - new( - typeof(HouseFoundation), - 1060384, - 2119, - 1059, - 2437, - 1218, - 42, - 179000, - 0, - 10, - 0, - 0x147B - ) // 18x18 3-Story Customizable House - }; - - public BaseHouse ConstructHouse(Mobile from) - { - try - { - object[] args; - - if (Type == typeof(HouseFoundation)) - { - args = new object[] { from, MultiID, m_Storage, m_Lockdowns }; - } - else if (Type == typeof(SmallOldHouse) || Type == typeof(SmallShop) || Type == typeof(TwoStoryHouse)) - { - args = new object[] { from, MultiID }; - } - else - { - args = new object[] { from }; - } - - return Type.CreateInstance(args); - } - catch - { - // ignored - } - - return null; - } - - public void PlacementWarning_Callback(Mobile from, bool okay, PreviewHouse prevHouse) - { - if (!from.CheckAlive() || from.Backpack?.FindItemByType() == null) - { - return; - } - - if (!okay) - { - prevHouse.Delete(); - return; - } - - if (prevHouse.Deleted) - { - from.SendGump(new HousePlacementTimeoutNoticeGump()); - return; - } - - var center = prevHouse.Location; - - prevHouse.Delete(); - - // Point3D center = new Point3D( p.X - m_Offset.X, p.Y - m_Offset.Y, p.Z - m_Offset.Z ); - var res = HousePlacement.Check(from, MultiID, center, out var toMove); - - switch (res) - { - case HousePlacementResult.Valid: - { - if (from.AccessLevel < AccessLevel.GameMaster && BaseHouse.HasAccountHouse(from)) - { - from.SendLocalizedMessage(501271); // You already own a house, you may not place another! - } - else - { - var house = ConstructHouse(from); - - if (house == null) - { - return; - } - - house.Price = Cost; - - if (from.AccessLevel >= AccessLevel.GameMaster) - { - from.SendMessage( - $"{Cost} gold would have been withdrawn from your bank if you were not a GM." - ); - } - else - { - if (Banker.Withdraw(from, Cost)) - { - // ~1_AMOUNT~ gold has been withdrawn from your bank box. - from.SendLocalizedMessage(1060398, Cost.ToString()); - } - else - { - house.RemoveKeys(from); - house.Delete(); - // You do not have the funds available in your bank box to purchase this house. Try placing a smaller house, or adding gold or checks to your bank box. - from.SendLocalizedMessage(1060646); - return; - } - } - - house.MoveToWorld(center, from.Map); - - for (var i = 0; i < toMove.Count; ++i) - { - object o = toMove[i]; - - if (o is Mobile mobile) - { - mobile.Location = house.BanLocation; - } - else if (o is Item item) - { - item.Location = house.BanLocation; - } - } - } - - break; - } - case HousePlacementResult.BadItem: - case HousePlacementResult.BadLand: - case HousePlacementResult.BadStatic: - case HousePlacementResult.BadRegionHidden: - case HousePlacementResult.NoSurface: - { - // The house could not be created here. Either something is blocking the house, or the house would not be on valid terrain. - from.SendLocalizedMessage(1043287); - break; - } - case HousePlacementResult.BadRegion: - { - from.SendLocalizedMessage(501265); // Housing cannot be created in this area. - break; - } - case HousePlacementResult.BadRegionTemp: - { - // Lord British has decreed a 'no build' period, thus you cannot build this house at this time. - from.SendLocalizedMessage(501270); - break; - } - case HousePlacementResult.BadRegionRaffle: - { - // You must have a deed for this plot of land in order to build here. - from.SendLocalizedMessage(1150493); - break; - } - case HousePlacementResult.InvalidCastleKeep: - { - from.SendLocalizedMessage(1061122); // Castles and keeps cannot be created here. - break; - } - } - } - - public bool OnPlacement(Mobile from, Point3D p) - { - if (!from.CheckAlive() || from.Backpack?.FindItemByType() == null) - { - return false; - } - - var center = new Point3D(p.X - Offset.X, p.Y - Offset.Y, p.Z - Offset.Z); - var res = HousePlacement.Check(from, MultiID, center, out var toMove); - - switch (res) - { - case HousePlacementResult.Valid: - { - if (from.AccessLevel < AccessLevel.GameMaster && BaseHouse.HasAccountHouse(from)) - { - from.SendLocalizedMessage(501271); // You already own a house, you may not place another! - } - else - { - from.SendLocalizedMessage(1011576); // This is a valid location. - - var prev = new PreviewHouse(MultiID); - - var mcl = prev.Components; - - var banLoc = new Point3D(center.X + mcl.Min.X, center.Y + mcl.Max.Y + 1, center.Z); - - for (var i = 0; i < mcl.List.Length; ++i) - { - var entry = mcl.List[i]; - - int itemID = entry.ItemId; - - if (itemID >= 0xBA3 && itemID <= 0xC0E) - { - banLoc = new Point3D(center.X + entry.OffsetX, center.Y + entry.OffsetY, center.Z); - break; - } - } - - for (var i = 0; i < toMove.Count; ++i) - { - object o = toMove[i]; - - if (o is Mobile mobile) - { - mobile.Location = banLoc; - } - else if (o is Item item) - { - item.Location = banLoc; - } - } - - prev.MoveToWorld(center, from.Map); - - from.SendGump( - new CondemnWarningGump(okay => PlacementWarning_Callback(from, okay, prev)) - ); - - return true; - } - - break; - } - case HousePlacementResult.BadItem: - case HousePlacementResult.BadLand: - case HousePlacementResult.BadStatic: - case HousePlacementResult.BadRegionHidden: - case HousePlacementResult.NoSurface: - { - // The house could not be created here. Either something is blocking the house, or the house would not be on valid terrain. - from.SendLocalizedMessage(1043287); - break; - } - case HousePlacementResult.BadRegion: - { - from.SendLocalizedMessage(501265); // Housing cannot be created in this area. - break; - } - case HousePlacementResult.BadRegionTemp: - { - // Lord British has decreed a 'no build' period, thus you cannot build this house at this time. - from.SendLocalizedMessage(501270); - break; - } - case HousePlacementResult.BadRegionRaffle: - { - // You must have a deed for this plot of land in order to build here. - from.SendLocalizedMessage(1150493); - break; - } - case HousePlacementResult.InvalidCastleKeep: - { - from.SendLocalizedMessage(1061122); // Castles and keeps cannot be created here. - break; - } - } - - return false; - } - - public static HousePlacementEntry Find(BaseHouse house) - { - m_Table.TryGetValue(house.GetType(), out var obj); - - if (obj is HousePlacementEntry entry) - { - return entry; - } - - if (obj is List list) - { - foreach (var hpe in list) - { - if (hpe.MultiID == house.ItemID) - { - return hpe; - } - } - - return null; - } - - if (obj is Dictionary table) - { - return table[house.ItemID]; - } - - return null; - } - - private static void FillTable(HousePlacementEntry[] entries) - { - for (var i = 0; i < entries.Length; ++i) - { - var e = entries[i]; - - if (!m_Table.TryGetValue(e.Type, out var obj)) - { - m_Table[e.Type] = e; - } - else if (obj is HousePlacementEntry entry) - { - var list = new List { entry, e }; - - m_Table[e.Type] = list; - } - else if (obj is List list) - { - if (list.Count == 8) - { - var table = new Dictionary(); - - foreach (var t in list) - { - table[t.MultiID] = t; - } - - table[e.MultiID] = e; - - m_Table[e.Type] = table; - } - else - { - list.Add(e); - } - } - else if (obj is Dictionary table) - { - table[e.MultiID] = e; - } - } - } - - private class CondemnWarningGump : StaticWarningGump - { - /* - * You are about to place a new house. - * Placing this house will condemn any and all of your other houses that you may have. - * All of your houses on all shards will be affected.

In addition, you will not be able to place - * another house or have one transferred to you for one (1) real-life week.

- * Once you accept these terms, these effects cannot be reversed. - * Re-deeding or transferring your new house will not uncondemn your other house(s) nor will the one - * week timer be removed.

If you are absolutely certain you wish to proceed, click the button next to OKAY below. - * If you do not wish to trade for this house, click CANCEL. - */ - - public override int StaticLocalizedContent => 1049583; - public override int Width => 420; - public override int Height => 280; - - public CondemnWarningGump(Action callback) : base(callback) - { - } - } - - private class HousePlacementTimeoutNoticeGump : StaticWarningGump - { - // Too much time has passed and the test house you created has been deleted. Please try again! - public override int StaticLocalizedContent => 1060647; - public override int Width => 320; - public override int Height => 180; + from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. } } } + +public class HousePlacementCategoryGump : Gump +{ + private const int LabelColor = 0x7FFF; + private const int LabelColorDisabled = 0x4210; + private readonly Mobile _from; + + public HousePlacementCategoryGump(Mobile from) : base(50, 50) + { + _from = from; + + from.CloseGump(); + from.CloseGump(); + + AddPage(0); + + AddBackground(0, 0, 270, 145, 5054); + + AddImageTiled(10, 10, 250, 125, 2624); + AddAlphaRegion(10, 10, 250, 125); + + AddHtmlLocalized(10, 10, 250, 20, 1060239, LabelColor); //
HOUSE PLACEMENT TOOL
+ + AddButton(10, 110, 4017, 4019, 0); + AddHtmlLocalized(45, 110, 150, 20, 3000363, LabelColor); // Close + + AddButton(10, 40, 4005, 4007, 1); + AddHtmlLocalized(45, 40, 200, 20, 1060390, LabelColor); // Classic Houses + + AddButton(10, 60, 4005, 4007, 2); + AddHtmlLocalized(45, 60, 200, 20, 1060391, LabelColor); // 2-Story Customizable Houses + + AddButton(10, 80, 4005, 4007, 3); + AddHtmlLocalized(45, 80, 200, 20, 1060392, LabelColor); // 3-Story Customizable Houses + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + if (!_from.CheckAlive() || _from.Backpack?.FindItemByType() == null) + { + return; + } + + switch (info.ButtonID) + { + case 1: // Classic Houses + { + var entry = Core.EJ ? HousePlacementEntry.HousesEJ : HousePlacementEntry.ClassicHouses; + _from.SendGump(new HousePlacementListGump(_from, entry)); + + break; + } + case 2: // 2-Story Customizable Houses + { + _from.SendGump(new HousePlacementListGump(_from, HousePlacementEntry.TwoStoryFoundations)); + break; + } + case 3: // 3-Story Customizable Houses + { + _from.SendGump(new HousePlacementListGump(_from, HousePlacementEntry.ThreeStoryFoundations)); + break; + } + } + } +} + +public class HousePlacementListGump : Gump +{ + private const int LabelColor = 0x7FFF; + private const int LabelHue = 0x480; + private readonly HousePlacementEntry[] _entries; + private readonly Mobile _from; + + public HousePlacementListGump(Mobile from, HousePlacementEntry[] entries) : base(50, 50) + { + _from = from; + _entries = entries; + + from.CloseGump(); + from.CloseGump(); + + AddPage(0); + + AddBackground(0, 0, 520, 420, 5054); + + AddImageTiled(10, 10, 500, 20, 2624); + AddAlphaRegion(10, 10, 500, 20); + + AddHtmlLocalized(10, 10, 500, 20, 1060239, LabelColor); //
HOUSE PLACEMENT TOOL
+ + AddImageTiled(10, 40, 500, 20, 2624); + AddAlphaRegion(10, 40, 500, 20); + + AddHtmlLocalized(50, 40, 225, 20, 1060235, LabelColor); // House Description + AddHtmlLocalized(275, 40, 75, 20, 1060236, LabelColor); // Storage + AddHtmlLocalized(350, 40, 75, 20, 1060237, LabelColor); // Lockdowns + AddHtmlLocalized(425, 40, 75, 20, 1060034, LabelColor); // Cost + + AddImageTiled(10, 70, 500, 280, 2624); + AddAlphaRegion(10, 70, 500, 280); + + AddImageTiled(10, 360, 500, 20, 2624); + AddAlphaRegion(10, 360, 500, 20); + + AddHtmlLocalized(10, 360, 250, 20, 1060645, LabelColor); // Bank Balance: + AddLabel(250, 360, LabelHue, Banker.GetBalance(from).ToString()); + + AddImageTiled(10, 390, 500, 20, 2624); + AddAlphaRegion(10, 390, 500, 20); + + AddButton(10, 390, 4017, 4019, 0); + AddHtmlLocalized(50, 390, 100, 20, 3000363, LabelColor); // Close + + for (var i = 0; i < entries.Length; ++i) + { + var page = 1 + i / 14; + var index = i % 14; + + if (index == 0) + { + if (page > 1) + { + AddButton(450, 390, 4005, 4007, 0, GumpButtonType.Page, page); + AddHtmlLocalized(400, 390, 100, 20, 3000406, LabelColor); // Next + } + + AddPage(page); + + if (page > 1) + { + AddButton(200, 390, 4014, 4016, 0, GumpButtonType.Page, page - 1); + AddHtmlLocalized(250, 390, 100, 20, 3000405, LabelColor); // Previous + } + } + + var entry = entries[i]; + + var y = 70 + index * 20; + + AddButton(10, y, 4005, 4007, 1 + i); + AddHtmlLocalized(50, y, 225, 20, entry.Description, LabelColor); + AddLabel(275, y, LabelHue, entry.Storage.ToString()); + AddLabel(350, y, LabelHue, entry.Lockdowns.ToString()); + AddLabel(425, y, LabelHue, entry.Cost.ToString()); + } + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + if (!_from.CheckAlive() || _from.Backpack?.FindItemByType() == null) + { + return; + } + + var index = info.ButtonID - 1; + + if (index >= 0 && index < _entries.Length) + { + if (_from.AccessLevel < AccessLevel.GameMaster && BaseHouse.HasAccountHouse(_from)) + { + _from.SendLocalizedMessage(501271); // You already own a house, you may not place another! + } + else + { + _from.Target = new NewHousePlacementTarget(_entries, _entries[index]); + } + } + else + { + _from.SendGump(new HousePlacementCategoryGump(_from)); + } + } +} + +public class NewHousePlacementTarget : MultiTarget +{ + private readonly HousePlacementEntry[] _entries; + private readonly HousePlacementEntry _entry; + + private bool _placed; + + public NewHousePlacementTarget(HousePlacementEntry[] entries, HousePlacementEntry entry) : base( + entry.MultiID, + entry.Offset + ) + { + Range = 14; + + _entries = entries; + _entry = entry; + } + + protected override void OnTarget(Mobile from, object o) + { + if (o is not IPoint3D ip) + { + return; + } + + if (!from.CheckAlive() || from.Backpack?.FindItemByType() == null) + { + return; + } + + Point3D p = ip switch + { + Item item => item.GetWorldTop(), + Mobile m => m.Location, + _ => new Point3D(ip) + }; + + var reg = Region.Find(p, from.Map); + + if (from.AccessLevel >= AccessLevel.GameMaster || reg.AllowHousing(from, p)) + { + _placed = _entry.OnPlacement(from, p); + } + else if (reg.IsPartOf()) + { + // Lord British has decreed a 'no build' period, thus you cannot build this house at this time. + from.SendLocalizedMessage(501270); + } + else if (reg.IsPartOf() || reg.IsPartOf()) + { + // The house could not be created here. Either something is blocking the house, or the house would not be on valid terrain. + from.SendLocalizedMessage(1043287); + } + else if (reg.IsPartOf()) + { + from.SendLocalizedMessage(1150493); // You must have a deed for this plot of land in order to build here. + } + else + { + from.SendLocalizedMessage(501265); // Housing can not be created in this area. + } + } + + protected override void OnTargetFinish(Mobile from) + { + if (!from.CheckAlive() || from.Backpack?.FindItemByType() == null) + { + return; + } + + if (!_placed) + { + from.SendGump(new HousePlacementListGump(from, _entries)); + } + } +} + +public class HousePlacementEntry +{ + private static readonly Dictionary _table; + private readonly int _lockdowns; + private readonly int _newLockdowns; + private readonly int _newStorage; + private readonly int _storage; + + static HousePlacementEntry() + { + _table = new Dictionary(); + + FillTable(Core.EJ ? HousesEJ : ClassicHouses); + FillTable(TwoStoryFoundations); + FillTable(ThreeStoryFoundations); + } + + public HousePlacementEntry( + Type type, int description, int storage, int lockdowns, int newStorage, int newLockdowns, + int vendors, int cost, int xOffset, int yOffset, int zOffset, int multiID + ) + { + Type = type; + Description = description; + _storage = storage; + _lockdowns = lockdowns; + _newStorage = newStorage; + _newLockdowns = newLockdowns; + Vendors = vendors; + Cost = cost; + + Offset = new Point3D(xOffset, yOffset, zOffset); + + MultiID = multiID; + } + + public Type Type { get; } + + public int Description { get; } + + public int Storage => BaseHouse.NewVendorSystem ? _newStorage : _storage; + public int Lockdowns => BaseHouse.NewVendorSystem ? _newLockdowns : _lockdowns; + public int Vendors { get; } + + public int Cost { get; } + + public int MultiID { get; } + + public Point3D Offset { get; } + + public static HousePlacementEntry[] ClassicHouses { get; } = + { + new(typeof(SmallOldHouse), 1011303, 425, 212, 489, 244, 10, 37000, 0, 4, 0, 0x0064), + new(typeof(SmallOldHouse), 1011304, 425, 212, 489, 244, 10, 37000, 0, 4, 0, 0x0066), + new(typeof(SmallOldHouse), 1011305, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x0068), + new(typeof(SmallOldHouse), 1011306, 425, 212, 489, 244, 10, 35250, 0, 4, 0, 0x006A), + new(typeof(SmallOldHouse), 1011307, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x006C), + new(typeof(SmallOldHouse), 1011308, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x006E), + new(typeof(SmallShop), 1011321, 425, 212, 489, 244, 10, 50500, -1, 4, 0, 0x00A0), + new(typeof(SmallShop), 1011322, 425, 212, 489, 244, 10, 52500, 0, 4, 0, 0x00A2), + new(typeof(SmallTower), 1011317, 580, 290, 667, 333, 14, 73500, 3, 4, 0, 0x0098), + new(typeof(TwoStoryVilla), 1011319, 1100, 550, 1265, 632, 24, 113750, 3, 6, 0, 0x009E), + new(typeof(SandStonePatio), 1011320, 850, 425, 1265, 632, 24, 76500, -1, 4, 0, 0x009C), + new(typeof(LogCabin), 1011318, 1100, 550, 1265, 632, 24, 81750, 1, 6, 0, 0x009A), + new(typeof(GuildHouse), 1011309, 1370, 685, 1576, 788, 28, 131500, -1, 7, 0, 0x0074), + new(typeof(TwoStoryHouse), 1011310, 1370, 685, 1576, 788, 28, 162750, -3, 7, 0, 0x0076), + new(typeof(TwoStoryHouse), 1011311, 1370, 685, 1576, 788, 28, 162000, -3, 7, 0, 0x0078), + new(typeof(LargePatioHouse), 1011315, 1370, 685, 1576, 788, 28, 129250, -4, 7, 0, 0x008C), + new(typeof(LargeMarbleHouse), 1011316, 1370, 685, 1576, 788, 28, 160500, -4, 7, 0, 0x0096), + new(typeof(Tower), 1011312, 2119, 1059, 2437, 1218, 42, 366500, 0, 7, 0, 0x007A), + new(typeof(Keep), 1011313, 2625, 1312, 3019, 1509, 52, 572750, 0, 11, 0, 0x007C), + new(typeof(Castle), 1011314, 4076, 2038, 4688, 2344, 78, 865250, 0, 16, 0, 0x007E) + }; + + public static HousePlacementEntry[] HousesEJ { get; } = + { + new(typeof(SmallOldHouse), 1011303, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x0064), + new(typeof(SmallOldHouse), 1011304, 425, 212, 489, 244, 10, 36750, 0, 4, 0, 0x0066), + new(typeof(SmallOldHouse), 1011305, 425, 212, 489, 244, 10, 36500, 0, 4, 0, 0x0068), + new(typeof(SmallOldHouse), 1011306, 425, 212, 489, 244, 10, 35000, 0, 4, 0, 0x006A), + new(typeof(SmallOldHouse), 1011307, 425, 212, 489, 244, 10, 36500, 0, 4, 0, 0x006C), + new(typeof(SmallOldHouse), 1011308, 425, 212, 489, 244, 10, 36500, 0, 4, 0, 0x006E), + new(typeof(SmallShop), 1011321, 425, 212, 489, 244, 10, 50250, -1, 4, 0, 0x00A0), + new(typeof(SmallShop), 1011322, 425, 212, 489, 244, 10, 52250, 0, 4, 0, 0x00A2), + new(typeof(SmallTower), 1011317, 580, 290, 667, 333, 14, 73250, 3, 4, 0, 0x0098), + new(typeof(TwoStoryVilla), 1011319, 1100, 550, 1265, 632, 24, 113500, 3, 6, 0, 0x009E), + new(typeof(SandStonePatio), 1011320, 850, 425, 1265, 632, 24, 76250, -1, 4, 0, 0x009C), + new(typeof(LogCabin), 1011318, 1100, 550, 1265, 632, 24, 81250, 1, 6, 0, 0x009A), + new(typeof(GuildHouse), 1011309, 1370, 685, 1576, 788, 28, 131250, -1, 7, 0, 0x0074), + new(typeof(TwoStoryHouse), 1011310, 1370, 685, 1576, 788, 28, 162500, -3, 7, 0, 0x0076), + new(typeof(TwoStoryHouse), 1011311, 1370, 685, 1576, 788, 28, 162750, -3, 7, 0, 0x0078), + new(typeof(LargePatioHouse), 1011315, 1370, 685, 1576, 788, 28, 129000, -4, 7, 0, 0x008C), + new(typeof(LargeMarbleHouse), 1011316, 1370, 685, 1576, 788, 28, 160250, -4, 7, 0, 0x0096), + new(typeof(Tower), 1011312, 2119, 1059, 2437, 1218, 42, 366250, 0, 7, 0, 0x007A), + new(typeof(Keep), 1011313, 2625, 1312, 3019, 1509, 52, 562500, 0, 11, 0, 0x007C), + new(typeof(Castle), 1011314, 4076, 2038, 4688, 2344, 78, 865000, 0, 16, 0, 0x007E), + + new(typeof(TrinsicKeep), 1158748, 2625, 1312, 3019, 1509, 52, 29643750, 0, 11, 0, 0x147E), + new( + typeof(GothicRoseCastle), + 1158749, + 4076, + 2038, + 4688, + 2344, + 78, + 44808750, + 0, + 16, + 0, + 0x147F + ), + new(typeof(ElsaCastle), 1158750, 4076, 2038, 4688, 2344, 78, 45450000, 0, 16, 0, 0x1480), + new(typeof(Spires), 1158761, 4076, 2038, 4688, 2344, 78, 47025000, 0, 16, 0, 0x1481), + new( + typeof(CastleOfOceania), + 1158760, + 4076, + 2038, + 4688, + 2344, + 78, + 48971250, + 0, + 16, + 0, + 0x1482 + ), + new(typeof(FeudalCastle), 1158762, 4076, 2038, 4688, 2344, 78, 27337500, 0, 16, 0, 0x1483), + new(typeof(RobinsNest), 1158850, 2625, 1312, 3019, 1509, 52, 25301250, 0, 11, 0, 0x1484), + new( + typeof(TraditionalKeep), + 1158851, + 2625, + 1312, + 3019, + 1509, + 52, + 26685000, + 0, + 11, + 0, + 0x1485 + ), + new(typeof(VillaCrowley), 1158852, 2625, 1312, 3019, 1509, 52, 21813750, 0, 11, 0, 0x1486), + new(typeof(DarkthornKeep), 1158853, 2625, 1312, 3019, 1509, 52, 27990000, 0, 11, 0, 0x1487), + new(typeof(SandalwoodKeep), 1158854, 2625, 1312, 3019, 1509, 52, 23456250, 0, 11, 0, 0x1488), + new(typeof(CasaMoga), 1158855, 2625, 1312, 3019, 1509, 52, 26313750, 0, 11, 0, 0x1489), + + new(typeof(RobinsRoost), 1158960, 4076, 2038, 4688, 2344, 78, 43863750, 0, 16, 0, 0x148A), + new(typeof(Camelot), 1158961, 4076, 2038, 4688, 2344, 78, 47092500, 0, 16, 0, 0x148B), + new( + typeof(LacrimaeInCaelo), + 1158962, + 4076, + 2038, + 4688, + 2344, + 78, + 45315000, + 0, + 16, + 0, + 0x148C + ), + new( + typeof(OkinawaSweetDreamCastle), + 1158963, + 4076, + 2038, + 4688, + 2344, + 78, + 40128750, + 0, + 16, + 0, + 0x148D + ), + new( + typeof(TheSandstoneCastle), + 1158964, + 4076, + 2038, + 4688, + 2344, + 78, + 48690000, + 0, + 16, + 0, + 0x148E + ), + new( + typeof(GrimswindSisters), + 1158965, + 4076, + 2038, + 4688, + 2344, + 78, + 42142500, + 0, + 16, + 0, + 0x148F + ) + }; + + public static HousePlacementEntry[] TwoStoryFoundations { get; } = + { + new( + typeof(HouseFoundation), + 1060241, + 425, + 212, + 489, + 244, + 10, + 30500, + 0, + 4, + 0, + 0x13EC + ), // 7x7 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060242, + 580, + 290, + 667, + 333, + 14, + 34500, + 0, + 5, + 0, + 0x13ED + ), // 7x8 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060243, + 650, + 325, + 748, + 374, + 16, + 38500, + 0, + 5, + 0, + 0x13EE + ), // 7x9 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060244, + 700, + 350, + 805, + 402, + 16, + 42500, + 0, + 6, + 0, + 0x13EF + ), // 7x10 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060245, + 750, + 375, + 863, + 431, + 16, + 46500, + 0, + 6, + 0, + 0x13F0 + ), // 7x11 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060246, + 800, + 400, + 920, + 460, + 18, + 50500, + 0, + 7, + 0, + 0x13F1 + ), // 7x12 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060253, + 580, + 290, + 667, + 333, + 14, + 34500, + 0, + 4, + 0, + 0x13F8 + ), // 8x7 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060254, + 650, + 325, + 748, + 374, + 16, + 39000, + 0, + 5, + 0, + 0x13F9 + ), // 8x8 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060255, + 700, + 350, + 805, + 402, + 16, + 43500, + 0, + 5, + 0, + 0x13FA + ), // 8x9 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060256, + 750, + 375, + 863, + 431, + 16, + 48000, + 0, + 6, + 0, + 0x13FB + ), // 8x10 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060257, + 800, + 400, + 920, + 460, + 18, + 52500, + 0, + 6, + 0, + 0x13FC + ), // 8x11 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060258, + 850, + 425, + 1265, + 632, + 24, + 57000, + 0, + 7, + 0, + 0x13FD + ), // 8x12 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060259, + 1100, + 550, + 1265, + 632, + 24, + 61500, + 0, + 7, + 0, + 0x13FE + ), // 8x13 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060265, + 650, + 325, + 748, + 374, + 16, + 38500, + 0, + 4, + 0, + 0x1404 + ), // 9x7 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060266, + 700, + 350, + 805, + 402, + 16, + 43500, + 0, + 5, + 0, + 0x1405 + ), // 9x8 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060267, + 750, + 375, + 863, + 431, + 16, + 48500, + 0, + 5, + 0, + 0x1406 + ), // 9x9 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060268, + 800, + 400, + 920, + 460, + 18, + 53500, + 0, + 6, + 0, + 0x1407 + ), // 9x10 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060269, + 850, + 425, + 1265, + 632, + 24, + 58500, + 0, + 6, + 0, + 0x1408 + ), // 9x11 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060270, + 1100, + 550, + 1265, + 632, + 24, + 63500, + 0, + 7, + 0, + 0x1409 + ), // 9x12 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060271, + 1100, + 550, + 1265, + 632, + 24, + 68500, + 0, + 7, + 0, + 0x140A + ), // 9x13 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060277, + 700, + 350, + 805, + 402, + 16, + 42500, + 0, + 4, + 0, + 0x1410 + ), // 10x7 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060278, + 750, + 375, + 863, + 431, + 16, + 48000, + 0, + 5, + 0, + 0x1411 + ), // 10x8 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060279, + 800, + 400, + 920, + 460, + 18, + 53500, + 0, + 5, + 0, + 0x1412 + ), // 10x9 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060280, + 850, + 425, + 1265, + 632, + 24, + 59000, + 0, + 6, + 0, + 0x1413 + ), // 10x10 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060281, + 1100, + 550, + 1265, + 632, + 24, + 64500, + 0, + 6, + 0, + 0x1414 + ), // 10x11 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060282, + 1100, + 550, + 1265, + 632, + 24, + 70000, + 0, + 7, + 0, + 0x1415 + ), // 10x12 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060283, + 1150, + 575, + 1323, + 661, + 24, + 75500, + 0, + 7, + 0, + 0x1416 + ), // 10x13 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060289, + 750, + 375, + 863, + 431, + 16, + 46500, + 0, + 4, + 0, + 0x141C + ), // 11x7 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060290, + 800, + 400, + 920, + 460, + 18, + 52500, + 0, + 5, + 0, + 0x141D + ), // 11x8 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060291, + 850, + 425, + 1265, + 632, + 24, + 58500, + 0, + 5, + 0, + 0x141E + ), // 11x9 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060292, + 1100, + 550, + 1265, + 632, + 24, + 64500, + 0, + 6, + 0, + 0x141F + ), // 11x10 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060293, + 1100, + 550, + 1265, + 632, + 24, + 70500, + 0, + 6, + 0, + 0x1420 + ), // 11x11 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060294, + 1150, + 575, + 1323, + 661, + 24, + 76500, + 0, + 7, + 0, + 0x1421 + ), // 11x12 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060295, + 1200, + 600, + 1380, + 690, + 26, + 82500, + 0, + 7, + 0, + 0x1422 + ), // 11x13 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060301, + 800, + 400, + 920, + 460, + 18, + 50500, + 0, + 4, + 0, + 0x1428 + ), // 12x7 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060302, + 850, + 425, + 1265, + 632, + 24, + 57000, + 0, + 5, + 0, + 0x1429 + ), // 12x8 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060303, + 1100, + 550, + 1265, + 632, + 24, + 63500, + 0, + 5, + 0, + 0x142A + ), // 12x9 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060304, + 1100, + 550, + 1265, + 632, + 24, + 70000, + 0, + 6, + 0, + 0x142B + ), // 12x10 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060305, + 1150, + 575, + 1323, + 661, + 24, + 76500, + 0, + 6, + 0, + 0x142C + ), // 12x11 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060306, + 1200, + 600, + 1380, + 690, + 26, + 83000, + 0, + 7, + 0, + 0x142D + ), // 12x12 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060307, + 1250, + 625, + 1438, + 719, + 26, + 89500, + 0, + 7, + 0, + 0x142E + ), // 12x13 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060314, + 1100, + 550, + 1265, + 632, + 24, + 61500, + 0, + 5, + 0, + 0x1435 + ), // 13x8 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060315, + 1100, + 550, + 1265, + 632, + 24, + 68500, + 0, + 5, + 0, + 0x1436 + ), // 13x9 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060316, + 1150, + 575, + 1323, + 661, + 24, + 75500, + 0, + 6, + 0, + 0x1437 + ), // 13x10 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060317, + 1200, + 600, + 1380, + 690, + 26, + 82500, + 0, + 6, + 0, + 0x1438 + ), // 13x11 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060318, + 1250, + 625, + 1438, + 719, + 26, + 89500, + 0, + 7, + 0, + 0x1439 + ), // 13x12 2-Story Customizable House + new( + typeof(HouseFoundation), + 1060319, + 1300, + 650, + 1495, + 747, + 28, + 96500, + 0, + 7, + 0, + 0x143A + ) // 13x13 2-Story Customizable House + }; + + public static HousePlacementEntry[] ThreeStoryFoundations { get; } = + { + new( + typeof(HouseFoundation), + 1060272, + 1150, + 575, + 1323, + 661, + 24, + 73500, + 0, + 8, + 0, + 0x140B + ), // 9x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060284, + 1200, + 600, + 1380, + 690, + 26, + 81000, + 0, + 8, + 0, + 0x1417 + ), // 10x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060285, + 1250, + 625, + 1438, + 719, + 26, + 86500, + 0, + 8, + 0, + 0x1418 + ), // 10x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060296, + 1250, + 625, + 1438, + 719, + 26, + 88500, + 0, + 8, + 0, + 0x1423 + ), // 11x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060297, + 1300, + 650, + 1495, + 747, + 28, + 94500, + 0, + 8, + 0, + 0x1424 + ), // 11x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060298, + 1350, + 675, + 1553, + 776, + 28, + 100500, + 0, + 9, + 0, + 0x1425 + ), // 11x16 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060308, + 1300, + 650, + 1495, + 747, + 28, + 96000, + 0, + 8, + 0, + 0x142F + ), // 12x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060309, + 1350, + 675, + 1553, + 776, + 28, + 102500, + 0, + 8, + 0, + 0x1430 + ), // 12x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060310, + 1370, + 685, + 1576, + 788, + 28, + 109000, + 0, + 9, + 0, + 0x1431 + ), // 12x16 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060311, + 1370, + 685, + 1576, + 788, + 28, + 115500, + 0, + 9, + 0, + 0x1432 + ), // 12x17 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060320, + 1350, + 675, + 1553, + 776, + 28, + 103500, + 0, + 8, + 0, + 0x143B + ), // 13x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060321, + 1370, + 685, + 1576, + 788, + 28, + 110500, + 0, + 8, + 0, + 0x143C + ), // 13x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060322, + 1370, + 685, + 1576, + 788, + 28, + 117500, + 0, + 9, + 0, + 0x143D + ), // 13x16 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060323, + 2119, + 1059, + 2437, + 1218, + 42, + 124500, + 0, + 9, + 0, + 0x143E + ), // 13x17 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060324, + 2119, + 1059, + 2437, + 1218, + 42, + 131500, + 0, + 10, + 0, + 0x143F + ), // 13x18 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060327, + 1150, + 575, + 1323, + 661, + 24, + 73500, + 0, + 5, + 0, + 0x1442 + ), // 14x9 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060328, + 1200, + 600, + 1380, + 690, + 26, + 81000, + 0, + 6, + 0, + 0x1443 + ), // 14x10 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060329, + 1250, + 625, + 1438, + 719, + 26, + 88500, + 0, + 6, + 0, + 0x1444 + ), // 14x11 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060330, + 1300, + 650, + 1495, + 747, + 28, + 96000, + 0, + 7, + 0, + 0x1445 + ), // 14x12 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060331, + 1350, + 675, + 1553, + 776, + 28, + 103500, + 0, + 7, + 0, + 0x1446 + ), // 14x13 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060332, + 1370, + 685, + 1576, + 788, + 28, + 111000, + 0, + 8, + 0, + 0x1447 + ), // 14x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060333, + 1370, + 685, + 1576, + 788, + 28, + 118500, + 0, + 8, + 0, + 0x1448 + ), // 14x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060334, + 2119, + 1059, + 2437, + 1218, + 42, + 126000, + 0, + 9, + 0, + 0x1449 + ), // 14x16 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060335, + 2119, + 1059, + 2437, + 1218, + 42, + 133500, + 0, + 9, + 0, + 0x144A + ), // 14x17 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060336, + 2119, + 1059, + 2437, + 1218, + 42, + 141000, + 0, + 10, + 0, + 0x144B + ), // 14x18 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060340, + 1250, + 625, + 1438, + 719, + 26, + 86500, + 0, + 6, + 0, + 0x144F + ), // 15x10 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060341, + 1300, + 650, + 1495, + 747, + 28, + 94500, + 0, + 6, + 0, + 0x1450 + ), // 15x11 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060342, + 1350, + 675, + 1553, + 776, + 28, + 102500, + 0, + 7, + 0, + 0x1451 + ), // 15x12 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060343, + 1370, + 685, + 1576, + 788, + 28, + 110500, + 0, + 7, + 0, + 0x1452 + ), // 15x13 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060344, + 1370, + 685, + 1576, + 788, + 28, + 118500, + 0, + 8, + 0, + 0x1453 + ), // 15x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060345, + 2119, + 1059, + 2437, + 1218, + 42, + 126500, + 0, + 8, + 0, + 0x1454 + ), // 15x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060346, + 2119, + 1059, + 2437, + 1218, + 42, + 134500, + 0, + 9, + 0, + 0x1455 + ), // 15x16 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060347, + 2119, + 1059, + 2437, + 1218, + 42, + 142500, + 0, + 9, + 0, + 0x1456 + ), // 15x17 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060348, + 2119, + 1059, + 2437, + 1218, + 42, + 150500, + 0, + 10, + 0, + 0x1457 + ), // 15x18 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060353, + 1350, + 675, + 1553, + 776, + 28, + 100500, + 0, + 6, + 0, + 0x145C + ), // 16x11 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060354, + 1370, + 685, + 1576, + 788, + 28, + 109000, + 0, + 7, + 0, + 0x145D + ), // 16x12 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060355, + 1370, + 685, + 1576, + 788, + 28, + 117500, + 0, + 7, + 0, + 0x145E + ), // 16x13 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060356, + 2119, + 1059, + 2437, + 1218, + 42, + 126000, + 0, + 8, + 0, + 0x145F + ), // 16x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060357, + 2119, + 1059, + 2437, + 1218, + 42, + 134500, + 0, + 8, + 0, + 0x1460 + ), // 16x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060358, + 2119, + 1059, + 2437, + 1218, + 42, + 143000, + 0, + 9, + 0, + 0x1461 + ), // 16x16 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060359, + 2119, + 1059, + 2437, + 1218, + 42, + 151500, + 0, + 9, + 0, + 0x1462 + ), // 16x17 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060360, + 2119, + 1059, + 2437, + 1218, + 42, + 160000, + 0, + 10, + 0, + 0x1463 + ), // 16x18 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060366, + 1370, + 685, + 1576, + 788, + 28, + 115500, + 0, + 7, + 0, + 0x1469 + ), // 17x12 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060367, + 2119, + 1059, + 2437, + 1218, + 42, + 124500, + 0, + 7, + 0, + 0x146A + ), // 17x13 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060368, + 2119, + 1059, + 2437, + 1218, + 42, + 133500, + 0, + 8, + 0, + 0x146B + ), // 17x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060369, + 2119, + 1059, + 2437, + 1218, + 42, + 142500, + 0, + 8, + 0, + 0x146C + ), // 17x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060370, + 2119, + 1059, + 2437, + 1218, + 42, + 151500, + 0, + 9, + 0, + 0x146D + ), // 17x16 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060371, + 2119, + 1059, + 2437, + 1218, + 42, + 160500, + 0, + 9, + 0, + 0x146E + ), // 17x17 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060372, + 2119, + 1059, + 2437, + 1218, + 42, + 169500, + 0, + 10, + 0, + 0x146F + ), // 17x18 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060379, + 2119, + 1059, + 2437, + 1218, + 42, + 131500, + 0, + 7, + 0, + 0x1476 + ), // 18x13 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060380, + 2119, + 1059, + 2437, + 1218, + 42, + 141000, + 0, + 8, + 0, + 0x1477 + ), // 18x14 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060381, + 2119, + 1059, + 2437, + 1218, + 42, + 150500, + 0, + 8, + 0, + 0x1478 + ), // 18x15 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060382, + 2119, + 1059, + 2437, + 1218, + 42, + 160000, + 0, + 9, + 0, + 0x1479 + ), // 18x16 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060383, + 2119, + 1059, + 2437, + 1218, + 42, + 169500, + 0, + 9, + 0, + 0x147A + ), // 18x17 3-Story Customizable House + new( + typeof(HouseFoundation), + 1060384, + 2119, + 1059, + 2437, + 1218, + 42, + 179000, + 0, + 10, + 0, + 0x147B + ) // 18x18 3-Story Customizable House + }; + + public BaseHouse ConstructHouse(Mobile from) + { + try + { + object[] args; + + if (Type == typeof(HouseFoundation)) + { + args = new object[] { from, MultiID, _storage, _lockdowns }; + } + else if (Type == typeof(SmallOldHouse) || Type == typeof(SmallShop) || Type == typeof(TwoStoryHouse)) + { + args = new object[] { from, MultiID }; + } + else + { + args = new object[] { from }; + } + + return Type.CreateInstance(args); + } + catch + { + // ignored + } + + return null; + } + + public void PlacementWarning_Callback(Mobile from, bool okay, PreviewHouse prevHouse) + { + if (!from.CheckAlive() || from.Backpack?.FindItemByType() == null) + { + return; + } + + if (!okay) + { + prevHouse.Delete(); + return; + } + + if (prevHouse.Deleted) + { + from.SendGump(new HousePlacementTimeoutNoticeGump()); + return; + } + + var center = prevHouse.Location; + + prevHouse.Delete(); + + var res = HousePlacement.Check(from, MultiID, center, out var toMove); + + switch (res) + { + case HousePlacementResult.Valid: + { + if (from.AccessLevel < AccessLevel.GameMaster && BaseHouse.HasAccountHouse(from)) + { + from.SendLocalizedMessage(501271); // You already own a house, you may not place another! + } + else + { + var house = ConstructHouse(from); + + if (house == null) + { + return; + } + + house.Price = Cost; + + if (from.AccessLevel >= AccessLevel.GameMaster) + { + from.SendMessage( + $"{Cost} gold would have been withdrawn from your bank if you were not a GM." + ); + } + else + { + if (Banker.Withdraw(from, Cost)) + { + // ~1_AMOUNT~ gold has been withdrawn from your bank box. + from.SendLocalizedMessage(1060398, Cost.ToString()); + } + else + { + house.RemoveKeys(from); + house.Delete(); + // You do not have the funds available in your bank box to purchase this house. Try placing a smaller house, or adding gold or checks to your bank box. + from.SendLocalizedMessage(1060646); + return; + } + } + + house.MoveToWorld(center, from.Map); + + for (var i = 0; i < toMove.Count; ++i) + { + object o = toMove[i]; + + if (o is Mobile mobile) + { + mobile.Location = house.BanLocation; + } + else if (o is Item item) + { + item.Location = house.BanLocation; + } + } + } + + break; + } + case HousePlacementResult.BadItem: + case HousePlacementResult.BadLand: + case HousePlacementResult.BadStatic: + case HousePlacementResult.BadRegionHidden: + case HousePlacementResult.NoSurface: + { + // The house could not be created here. Either something is blocking the house, or the house would not be on valid terrain. + from.SendLocalizedMessage(1043287); + break; + } + case HousePlacementResult.BadRegion: + { + from.SendLocalizedMessage(501265); // Housing cannot be created in this area. + break; + } + case HousePlacementResult.BadRegionTemp: + { + // Lord British has decreed a 'no build' period, thus you cannot build this house at this time. + from.SendLocalizedMessage(501270); + break; + } + case HousePlacementResult.BadRegionRaffle: + { + // You must have a deed for this plot of land in order to build here. + from.SendLocalizedMessage(1150493); + break; + } + case HousePlacementResult.InvalidCastleKeep: + { + from.SendLocalizedMessage(1061122); // Castles and keeps cannot be created here. + break; + } + } + } + + public bool OnPlacement(Mobile from, Point3D p) + { + if (!from.CheckAlive() || from.Backpack?.FindItemByType() == null) + { + return false; + } + + var center = new Point3D(p.X - Offset.X, p.Y - Offset.Y, p.Z - Offset.Z); + var res = HousePlacement.Check(from, MultiID, center, out var toMove); + + switch (res) + { + case HousePlacementResult.Valid: + { + if (from.AccessLevel < AccessLevel.GameMaster && BaseHouse.HasAccountHouse(from)) + { + from.SendLocalizedMessage(501271); // You already own a house, you may not place another! + } + else + { + from.SendLocalizedMessage(1011576); // This is a valid location. + + var prev = new PreviewHouse(MultiID); + + var mcl = prev.Components; + + var banLoc = new Point3D(center.X + mcl.Min.X, center.Y + mcl.Max.Y + 1, center.Z); + + for (var i = 0; i < mcl.List.Length; ++i) + { + var entry = mcl.List[i]; + + int itemID = entry.ItemId; + + if (itemID >= 0xBA3 && itemID <= 0xC0E) + { + banLoc = new Point3D(center.X + entry.OffsetX, center.Y + entry.OffsetY, center.Z); + break; + } + } + + for (var i = 0; i < toMove.Count; ++i) + { + object o = toMove[i]; + + if (o is Mobile mobile) + { + mobile.Location = banLoc; + } + else if (o is Item item) + { + item.Location = banLoc; + } + } + + prev.MoveToWorld(center, from.Map); + + from.SendGump( + new CondemnWarningGump(okay => PlacementWarning_Callback(from, okay, prev)) + ); + + return true; + } + + break; + } + case HousePlacementResult.BadItem: + case HousePlacementResult.BadLand: + case HousePlacementResult.BadStatic: + case HousePlacementResult.BadRegionHidden: + case HousePlacementResult.NoSurface: + { + // The house could not be created here. Either something is blocking the house, or the house would not be on valid terrain. + from.SendLocalizedMessage(1043287); + break; + } + case HousePlacementResult.BadRegion: + { + from.SendLocalizedMessage(501265); // Housing cannot be created in this area. + break; + } + case HousePlacementResult.BadRegionTemp: + { + // Lord British has decreed a 'no build' period, thus you cannot build this house at this time. + from.SendLocalizedMessage(501270); + break; + } + case HousePlacementResult.BadRegionRaffle: + { + // You must have a deed for this plot of land in order to build here. + from.SendLocalizedMessage(1150493); + break; + } + case HousePlacementResult.InvalidCastleKeep: + { + from.SendLocalizedMessage(1061122); // Castles and keeps cannot be created here. + break; + } + } + + return false; + } + + public static HousePlacementEntry Find(BaseHouse house) + { + _table.TryGetValue(house.GetType(), out var obj); + + if (obj is HousePlacementEntry entry) + { + return entry; + } + + if (obj is List list) + { + foreach (var hpe in list) + { + if (hpe.MultiID == house.ItemID) + { + return hpe; + } + } + + return null; + } + + if (obj is Dictionary table) + { + return table[house.ItemID]; + } + + return null; + } + + private static void FillTable(HousePlacementEntry[] entries) + { + for (var i = 0; i < entries.Length; ++i) + { + var e = entries[i]; + + if (!_table.TryGetValue(e.Type, out var obj)) + { + _table[e.Type] = e; + } + else if (obj is HousePlacementEntry entry) + { + var list = new List { entry, e }; + + _table[e.Type] = list; + } + else if (obj is List list) + { + if (list.Count == 8) + { + var table = new Dictionary(); + + foreach (var t in list) + { + table[t.MultiID] = t; + } + + table[e.MultiID] = e; + + _table[e.Type] = table; + } + else + { + list.Add(e); + } + } + else if (obj is Dictionary table) + { + table[e.MultiID] = e; + } + } + } + + private class CondemnWarningGump : StaticWarningGump + { + /* + * You are about to place a new house. + * Placing this house will condemn any and all of your other houses that you may have. + * All of your houses on all shards will be affected.

In addition, you will not be able to place + * another house or have one transferred to you for one (1) real-life week.

+ * Once you accept these terms, these effects cannot be reversed. + * Re-deeding or transferring your new house will not uncondemn your other house(s) nor will the one + * week timer be removed.

If you are absolutely certain you wish to proceed, click the button next to OKAY below. + * If you do not wish to trade for this house, click CANCEL. + */ + + public override int StaticLocalizedContent => 1049583; + public override int Width => 420; + public override int Height => 280; + + public CondemnWarningGump(Action callback) : base(callback) + { + } + } + + private class HousePlacementTimeoutNoticeGump : StaticWarningGump + { + // Too much time has passed and the test house you created has been deleted. Please try again! + public override int StaticLocalizedContent => 1060647; + public override int Width => 320; + public override int Height => 180; + } +} diff --git a/Projects/UOContent/Multis/Houses/HouseSign.cs b/Projects/UOContent/Multis/Houses/HouseSign.cs index 8737b048b..2cacd3c5e 100644 --- a/Projects/UOContent/Multis/Houses/HouseSign.cs +++ b/Projects/UOContent/Multis/Houses/HouseSign.cs @@ -1,311 +1,274 @@ using System; using System.Collections.Generic; +using ModernUO.Serialization; using Server.ContextMenus; using Server.Gumps; -namespace Server.Multis +namespace Server.Multis; + +[SerializationGenerator(0, false)] +public partial class HouseSign : Item { - public class HouseSign : Item + [SerializableField(0, setter: "private")] + private BaseHouse _owner; + + [SerializableField(1, setter: "private")] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private Mobile _originalOwner; + + public HouseSign(BaseHouse owner) : base(0xBD2) { - public HouseSign(BaseHouse owner) : base(0xBD2) - { - Owner = owner; - OriginalOwner = Owner.Owner; - Movable = false; - } + Owner = owner; + OriginalOwner = Owner.Owner; + Movable = false; + } - public HouseSign(Serial serial) : base(serial) - { - } - - public BaseHouse Owner { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool RestrictDecay - { - get => Owner?.RestrictDecay == true; - set - { - if (Owner != null) - { - Owner.RestrictDecay = value; - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Mobile OriginalOwner { get; private set; } - - public override bool ForceShowProperties => ObjectPropertyList.Enabled; - - public bool GettingProperties { get; private set; } - - public string GetName() => Name ?? "An Unnamed House"; - - public override void OnAfterDelete() - { - base.OnAfterDelete(); - - if (Owner?.Deleted == false) - { - Owner.Delete(); - } - } - - public override void AddNameProperty(IPropertyList list) - { - list.Add(1061638); // A House Sign - } - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - list.Add(1061639, GetName().FixHtml()); // Name: ~1_NAME~ - list.Add(1061640, Owner?.Owner == null ? "nobody" : Owner.Owner.Name); // Owner: ~1_OWNER~ - - if (Owner != null) - { - list.Add(Owner.Public ? 1061641 : 1061642); // This House is Open to the Public : This is a Private Home - - GettingProperties = true; - var level = Owner.DecayLevel; - GettingProperties = false; - - if (level == DecayLevel.DemolitionPending) - { - list.Add(1062497); // Demolition Pending - } - else if (level != DecayLevel.Ageless) - { - if (level == DecayLevel.Collapsed) - { - level = DecayLevel.IDOC; - } - - list.AddLocalized(1062028, 1043009 + (int)level); // Condition: This structure is ... - } - } - } - - public override void OnSingleClick(Mobile from) - { - if (BaseHouse.DecayEnabled && Owner != null && Owner.DecayPeriod != TimeSpan.Zero) - { - var message = Owner.DecayLevel switch - { - DecayLevel.Ageless => "ageless", - DecayLevel.Fairly => "fairly worn", - DecayLevel.Greatly => "greatly worn", - DecayLevel.LikeNew => "like new", - DecayLevel.Slightly => "slightly worn", - DecayLevel.Somewhat => "somewhat worn", - _ => "in danger of collapsing" - }; - - LabelTo(from, $"This house is {message}."); - } - - base.OnSingleClick(from); - } - - public void ShowSign(Mobile m) + [CommandProperty(AccessLevel.GameMaster)] + public bool RestrictDecay + { + get => Owner?.RestrictDecay == true; + set { if (Owner != null) { - if (Owner.IsFriend(m) && m.AccessLevel < AccessLevel.GameMaster) - { - if (Core.ML && Owner.IsOwner(m) || !Core.ML) - { - Owner.RefreshDecay(); - } - - if (!Core.AOS) - { - m.SendLocalizedMessage(501293); // Welcome back to the house, friend! - } - } - - if (Owner.IsAosRules) - { - m.SendGump(new HouseGumpAOS(HouseGumpPageAOS.Information, m, Owner)); - } - else - { - m.SendGump(new HouseGump(m, Owner)); - } + Owner.RestrictDecay = value; } } + } - public void ClaimGump_Callback(Mobile from, bool okay) + public int LabelNumber => 1061638; // A House Sign + + public override bool ForceShowProperties => ObjectPropertyList.Enabled; + + public bool GettingProperties { get; private set; } + + public string GetName() => Name ?? "An Unnamed House"; + + public override void OnAfterDelete() + { + base.OnAfterDelete(); + + if (Owner?.Deleted == false) { - if (okay && Owner != null && Owner.Owner == null && Owner.DecayLevel != DecayLevel.DemolitionPending) + Owner.Delete(); + } + } + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + list.Add(1061639, GetName().FixHtml()); // Name: ~1_NAME~ + list.Add(1061640, Owner?.Owner?.Name ?? "nobody"); // Owner: ~1_OWNER~ + + if (Owner != null) + { + list.Add(Owner.Public ? 1061641 : 1061642); // This House is Open to the Public : This is a Private Home + + GettingProperties = true; + var level = Owner.DecayLevel; + GettingProperties = false; + + if (level == DecayLevel.DemolitionPending) { - var canClaim = Owner.CoOwners?.Count > 0 && Owner.IsCoOwner(from) || Owner.IsFriend(from); - - if (canClaim && !BaseHouse.HasAccountHouse(from)) - { - Owner.Owner = from; - Owner.LastTraded = Core.Now; - } + list.Add(1062497); // Demolition Pending } + else if (level != DecayLevel.Ageless) + { + if (level == DecayLevel.Collapsed) + { + level = DecayLevel.IDOC; + } - ShowSign(from); + list.AddLocalized(1062028, 1043009 + (int)level); // Condition: This structure is ... + } + } + } + + public override void OnSingleClick(Mobile from) + { + if (BaseHouse.DecayEnabled && Owner != null && Owner.DecayPeriod != TimeSpan.Zero) + { + var message = Owner.DecayLevel switch + { + DecayLevel.Ageless => "ageless", + DecayLevel.Fairly => "fairly worn", + DecayLevel.Greatly => "greatly worn", + DecayLevel.LikeNew => "like new", + DecayLevel.Slightly => "slightly worn", + DecayLevel.Somewhat => "somewhat worn", + _ => "in danger of collapsing" + }; + + LabelTo(from, $"This house is {message}."); } - public override void OnDoubleClick(Mobile m) + base.OnSingleClick(from); + } + + public void ShowSign(Mobile m) + { + if (Owner == null) { - if (Owner == null) + return; + } + + if (Owner.IsFriend(m) && m.AccessLevel < AccessLevel.GameMaster) + { + if (Core.ML && Owner.IsOwner(m) || !Core.ML) + { + Owner.RefreshDecay(); + } + + if (!Core.AOS) + { + m.SendLocalizedMessage(501293); // Welcome back to the house, friend! + } + } + + if (Owner.IsAosRules) + { + m.SendGump(new HouseGumpAOS(HouseGumpPageAOS.Information, m, Owner)); + } + else + { + m.SendGump(new HouseGump(m, Owner)); + } + } + + public void ClaimGump_Callback(Mobile from, bool okay) + { + if (okay && Owner != null && Owner.Owner == null && Owner.DecayLevel != DecayLevel.DemolitionPending) + { + var canClaim = Owner.CoOwners?.Count > 0 && Owner.IsCoOwner(from) || Owner.IsFriend(from); + + if (canClaim && !BaseHouse.HasAccountHouse(from)) + { + Owner.Owner = from; + Owner.LastTraded = Core.Now; + } + } + + ShowSign(from); + } + + public override void OnDoubleClick(Mobile m) + { + if (Owner == null) + { + return; + } + + if (m.AccessLevel < AccessLevel.GameMaster && Owner.Owner == null && + Owner.DecayLevel != DecayLevel.DemolitionPending) + { + var canClaim = Owner.IsCoOwner(m) || Owner.IsFriend(m); + + if (canClaim && !BaseHouse.HasAccountHouse(m)) + { + m.SendGump( + new ClaimHouseWarningGump(okay => ClaimGump_Callback(m, okay)) + ); + } + } + + ShowSign(m); + } + + private class ClaimHouseWarningGump : StaticWarningGump + { + public override int Header => 501036; // Claim house + public override int HeaderColor => 0x7F00; + + /* + * You do not currently own any house on any shard with this account, and this house currently does not have an owner. + * If you wish, you may choose to claim this house and become its rightful owner. + * If you do this, it will become your Primary house and automatically refresh. + * If you claim this house, you will be unable to place another house or have another house transferred to you for the next 7 days. + * Do you wish to claim this house? + */ + public override int StaticLocalizedContent => 1049719; + + public override int Width => 420; + public override int Height => 280; + + public ClaimHouseWarningGump(Action callback) : base(callback) + { + } + } + + public override void GetContextMenuEntries(Mobile from, List list) + { + base.GetContextMenuEntries(from, list); + + if (!BaseHouse.NewVendorSystem || !from.Alive || Owner?.IsAosRules != true) + { + return; + } + + if (Owner.AreThereAvailableVendorsFor(from)) + { + list.Add(new VendorsEntry(this)); + } + + if (Owner.VendorInventories.Count > 0) + { + list.Add(new ReclaimVendorInventoryEntry(this)); + } + } + + private class VendorsEntry : ContextMenuEntry + { + private readonly HouseSign m_Sign; + + public VendorsEntry(HouseSign sign) : base(6211) => m_Sign = sign; + + public override void OnClick() + { + var from = Owner.From; + + if (!from.CheckAlive() || m_Sign.Deleted || m_Sign.Owner?.AreThereAvailableVendorsFor(from) != true) { return; } - if (m.AccessLevel < AccessLevel.GameMaster && Owner.Owner == null && - Owner.DecayLevel != DecayLevel.DemolitionPending) + if (from.Map != m_Sign.Map || !from.InRange(m_Sign, 5)) { - var canClaim = Owner.IsCoOwner(m) || Owner.IsFriend(m); - - if (canClaim && !BaseHouse.HasAccountHouse(m)) - { - m.SendGump( - new ClaimHouseWarningGump(okay => ClaimGump_Callback(m, okay)) - ); - } + // You must be within five paces of the house sign to use this option. + from.SendLocalizedMessage(1062429); } - - ShowSign(m); - } - - private class ClaimHouseWarningGump : StaticWarningGump - { - public override int Header => 501036; // Claim house - public override int HeaderColor => 0x7F00; - - /* - * You do not currently own any house on any shard with this account, and this house currently does not have an owner. - * If you wish, you may choose to claim this house and become its rightful owner. - * If you do this, it will become your Primary house and automatically refresh. - * If you claim this house, you will be unable to place another house or have another house transferred to you for the next 7 days. - * Do you wish to claim this house? - */ - public override int StaticLocalizedContent => 1049719; - - public override int Width => 420; - public override int Height => 280; - - public ClaimHouseWarningGump(Action callback) : base(callback) + else { + from.SendGump(new HouseGumpAOS(HouseGumpPageAOS.Vendors, from, m_Sign.Owner)); } } + } - public override void GetContextMenuEntries(Mobile from, List list) + private class ReclaimVendorInventoryEntry : ContextMenuEntry + { + private readonly HouseSign m_Sign; + + public ReclaimVendorInventoryEntry(HouseSign sign) : base(6213) => m_Sign = sign; + + public override void OnClick() { - base.GetContextMenuEntries(from, list); + var from = Owner.From; - if (!BaseHouse.NewVendorSystem || !from.Alive || Owner?.IsAosRules != true) + if (m_Sign.Deleted || m_Sign.Owner == null || m_Sign.Owner.VendorInventories.Count == 0 || + !from.CheckAlive()) { return; } - if (Owner.AreThereAvailableVendorsFor(from)) + if (from.Map != m_Sign.Map || !from.InRange(m_Sign, 5)) { - list.Add(new VendorsEntry(this)); + // You must be within five paces of the house sign to use this option. + from.SendLocalizedMessage(1062429); } - - if (Owner.VendorInventories.Count > 0) + else { - list.Add(new ReclaimVendorInventoryEntry(this)); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - - writer.Write(Owner); - writer.Write(OriginalOwner); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - Owner = reader.ReadEntity(); - OriginalOwner = reader.ReadEntity(); - - break; - } - } - - if (Name == "a house sign") - { - Name = null; - } - } - - private class VendorsEntry : ContextMenuEntry - { - private readonly HouseSign m_Sign; - - public VendorsEntry(HouseSign sign) : base(6211) => m_Sign = sign; - - public override void OnClick() - { - var from = Owner.From; - - if (!from.CheckAlive() || m_Sign.Deleted || m_Sign.Owner?.AreThereAvailableVendorsFor(from) != true) - { - return; - } - - if (from.Map != m_Sign.Map || !from.InRange(m_Sign, 5)) - { - from.SendLocalizedMessage( - 1062429 - ); // You must be within five paces of the house sign to use this option. - } - else - { - from.SendGump(new HouseGumpAOS(HouseGumpPageAOS.Vendors, from, m_Sign.Owner)); - } - } - } - - private class ReclaimVendorInventoryEntry : ContextMenuEntry - { - private readonly HouseSign m_Sign; - - public ReclaimVendorInventoryEntry(HouseSign sign) : base(6213) => m_Sign = sign; - - public override void OnClick() - { - var from = Owner.From; - - if (m_Sign.Deleted || m_Sign.Owner == null || m_Sign.Owner.VendorInventories.Count == 0 || - !from.CheckAlive()) - { - return; - } - - if (from.Map != m_Sign.Map || !from.InRange(m_Sign, 5)) - { - from.SendLocalizedMessage( - 1062429 - ); // You must be within five paces of the house sign to use this option. - } - else - { - from.CloseGump(); - from.SendGump(new VendorInventoryGump(m_Sign.Owner, from)); - } + from.CloseGump(); + from.SendGump(new VendorInventoryGump(m_Sign.Owner, from)); } } } diff --git a/Projects/UOContent/Multis/Houses/HouseTeleporter.cs b/Projects/UOContent/Multis/Houses/HouseTeleporter.cs index 5e7826120..087cf3cdf 100644 --- a/Projects/UOContent/Multis/Houses/HouseTeleporter.cs +++ b/Projects/UOContent/Multis/Houses/HouseTeleporter.cs @@ -1,198 +1,163 @@ using System; using System.Collections.Generic; +using ModernUO.Serialization; using Server.ContextMenus; using Server.Gumps; using Server.Mobiles; using Server.Multis; -namespace Server.Items +namespace Server.Items; + +[SerializationGenerator(2, false)] +public partial class HouseTeleporter : Item, ISecurable { - public class HouseTeleporter : Item, ISecurable + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private SecureLevel _level; + + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.GameMaster)] + private Item _target; + + public HouseTeleporter(int itemID, Item target = null) : base(itemID) { - public HouseTeleporter(int itemID, Item target = null) : base(itemID) + Movable = false; + _level = SecureLevel.Anyone; + _target = target; + } + + public bool CheckAccess(Mobile m) + { + var house = BaseHouse.FindHouseAt(this); + + return (house == null || house.Public && !house.IsBanned(m) || house.HasAccess(m)) && + house?.HasSecureAccess(m, Level) == true; + } + + public override bool OnMoveOver(Mobile m) + { + if (Target?.Deleted != false) { - Movable = false; - - Level = SecureLevel.Anyone; - - Target = target; - } - - public HouseTeleporter(Serial serial) : base(serial) - { - } - - [CommandProperty(AccessLevel.GameMaster)] - public Item Target { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public SecureLevel Level { get; set; } - - public bool CheckAccess(Mobile m) - { - var house = BaseHouse.FindHouseAt(this); - - return (house == null || house.Public && !house.IsBanned(m) || house.HasAccess(m)) && - house?.HasSecureAccess(m, Level) == true; - } - - public override bool OnMoveOver(Mobile m) - { - if (Target?.Deleted == false) - { - if (CheckAccess(m)) - { - if (!m.Hidden || m.AccessLevel == AccessLevel.Player) - { - new EffectTimer(Location, Map, 2023, 0x1F0, TimeSpan.FromSeconds(0.4)).Start(); - } - - new DelayTimer(this, m).Start(); - } - else - { - m.SendLocalizedMessage(1061637); // You are not allowed to access this. - } - } - return true; } - public override void GetContextMenuEntries(Mobile from, List list) + if (!CheckAccess(m)) { - base.GetContextMenuEntries(from, list); - SetSecureLevelEntry.AddTo(from, this, list); + m.SendLocalizedMessage(1061637); // You are not allowed to access this. + return false; } - public override void Serialize(IGenericWriter writer) + if (!m.Hidden || m.AccessLevel == AccessLevel.Player) { - base.Serialize(writer); - - writer.Write(1); // version - - writer.Write((int)Level); - - writer.Write(Target); + new EffectTimer(Location, Map, 2023, 0x1F0, TimeSpan.FromSeconds(0.4)).Start(); } - public override void Deserialize(IGenericReader reader) + new DelayTimer(this, m).Start(); + return true; + } + + public override void GetContextMenuEntries(Mobile from, List list) + { + base.GetContextMenuEntries(from, list); + SetSecureLevelEntry.AddTo(from, this, list); + } + + private void Deserialize(IGenericReader reader, int version) + { + Level = (SecureLevel)reader.ReadInt(); + Target = reader.ReadEntity(); + } + + private class EffectTimer : Timer + { + private readonly int _effectId; + private readonly Point3D _location; + private readonly Map _map; + private readonly int _soundId; + + public EffectTimer(Point3D p, Map map, int effectID, int soundID, TimeSpan delay) : base(delay) { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 1: - { - Level = (SecureLevel)reader.ReadInt(); - goto case 0; - } - case 0: - { - Target = reader.ReadEntity(); - - if (version < 1) - { - Level = SecureLevel.Anyone; - } - - break; - } - } + _location = p; + _map = map; + _effectId = effectID; + _soundId = soundID; } - private class EffectTimer : Timer + protected override void OnTick() { - private readonly int m_EffectID; - private readonly Point3D m_Location; - private readonly Map m_Map; - private readonly int m_SoundID; + Effects.SendLocationParticles( + EffectItem.Create(_location, _map, EffectItem.DefaultDuration), + 0x3728, + 10, + 10, + _effectId, + 0 + ); - public EffectTimer(Point3D p, Map map, int effectID, int soundID, TimeSpan delay) : base(delay) + if (_soundId != -1) { - m_Location = p; - m_Map = map; - m_EffectID = effectID; - m_SoundID = soundID; - } - - protected override void OnTick() - { - Effects.SendLocationParticles( - EffectItem.Create(m_Location, m_Map, EffectItem.DefaultDuration), - 0x3728, - 10, - 10, - m_EffectID, - 0 - ); - - if (m_SoundID != -1) - { - Effects.PlaySound(m_Location, m_Map, m_SoundID); - } - } - } - - private class DelayTimer : Timer - { - private readonly Mobile m_Mobile; - private readonly HouseTeleporter m_Teleporter; - - public DelayTimer(HouseTeleporter tp, Mobile m) : base(TimeSpan.FromSeconds(1.0)) - { - m_Teleporter = tp; - m_Mobile = m; - } - - protected override void OnTick() - { - var target = m_Teleporter.Target; - - if (target?.Deleted != false) - { - return; - } - - if (m_Mobile.Location != m_Teleporter.Location || m_Mobile.Map != m_Teleporter.Map) - { - return; - } - - var p = target.GetWorldTop(); - var map = target.Map; - - BaseCreature.TeleportPets(m_Mobile, p, map); - - m_Mobile.MoveToWorld(p, map); - - if (m_Mobile.Hidden && m_Mobile.AccessLevel != AccessLevel.Player) - { - return; - } - - Effects.PlaySound(target.Location, target.Map, 0x1FE); - - Effects.SendLocationParticles( - EffectItem.Create(m_Teleporter.Location, m_Teleporter.Map, EffectItem.DefaultDuration), - 0x3728, - 10, - 10, - 2023, - 0 - ); - Effects.SendLocationParticles( - EffectItem.Create(target.Location, target.Map, EffectItem.DefaultDuration), - 0x3728, - 10, - 10, - 5023, - 0 - ); - - new EffectTimer(target.Location, target.Map, 2023, -1, TimeSpan.FromSeconds(0.4)).Start(); + Effects.PlaySound(_location, _map, _soundId); } } } + + private class DelayTimer : Timer + { + private readonly Mobile _mobile; + private readonly HouseTeleporter _teleporter; + + public DelayTimer(HouseTeleporter tp, Mobile m) : base(TimeSpan.FromSeconds(1.0)) + { + _teleporter = tp; + _mobile = m; + } + + protected override void OnTick() + { + var target = _teleporter.Target; + + if (target?.Deleted != false) + { + return; + } + + if (_mobile.Location != _teleporter.Location || _mobile.Map != _teleporter.Map) + { + return; + } + + var p = target.GetWorldTop(); + var map = target.Map; + + BaseCreature.TeleportPets(_mobile, p, map); + + _mobile.MoveToWorld(p, map); + + if (_mobile.Hidden && _mobile.AccessLevel != AccessLevel.Player) + { + return; + } + + Effects.PlaySound(target.Location, target.Map, 0x1FE); + + Effects.SendLocationParticles( + EffectItem.Create(_teleporter.Location, _teleporter.Map, EffectItem.DefaultDuration), + 0x3728, + 10, + 10, + 2023, + 0 + ); + Effects.SendLocationParticles( + EffectItem.Create(target.Location, target.Map, EffectItem.DefaultDuration), + 0x3728, + 10, + 10, + 5023, + 0 + ); + + new EffectTimer(target.Location, target.Map, 2023, -1, TimeSpan.FromSeconds(0.4)).Start(); + } + } }