diff --git a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleDeed.cs b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleDeed.cs index 29054bfcc..a6823090d 100644 --- a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleDeed.cs +++ b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleDeed.cs @@ -1,214 +1,144 @@ using System; +using ModernUO.Serialization; using Server.Gumps; -namespace Server.Items +namespace Server.Items; + +[SerializationGenerator(0, false)] +public partial class HouseRaffleDeed : Item { - public class HouseRaffleDeed : Item + [InvalidateProperties] + [SerializableField(0)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private HouseRaffleStone _stone; + + [InvalidateProperties] + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private Point3D _plotLocation; + + [InvalidateProperties] + [SerializableField(2)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private Map _plotFacet; + + [InvalidateProperties] + [SerializableField(3)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private Mobile _awardedTo; + + [Constructible] + public HouseRaffleDeed(HouseRaffleStone stone = null, Mobile m = null) : base(0x2830) { - private Mobile m_AwardedTo; - private Map m_Facet; - private Point3D m_PlotLocation; - private HouseRaffleStone m_Stone; + _stone = stone; - [Constructible] - public HouseRaffleDeed(HouseRaffleStone stone = null, Mobile m = null) : base(0x2830) + if (stone != null) { - m_Stone = stone; - - if (stone != null) - { - m_PlotLocation = stone.GetPlotCenter(); - m_Facet = stone.PlotFacet; - } - - m_AwardedTo = m; - - LootType = LootType.Blessed; - Hue = 0x501; + _plotLocation = stone.GetPlotCenter(); + _plotFacet = stone.PlotFacet; } - public HouseRaffleDeed(Serial serial) - : base(serial) + _awardedTo = m; + + LootType = LootType.Blessed; + Hue = 0x501; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + public bool IsExpired => _stone?.Deleted != false || _stone.IsExpired; + + public override string DefaultName => "a writ of lease"; + + public override double DefaultWeight => 1.0; + + public bool ValidLocation() => _plotLocation != Point3D.Zero && _plotFacet != null && _plotFacet != Map.Internal; + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + if (ValidLocation()) { + list.Add( + 1060658, // ~1_val~: ~2_val~ + $"{"location"}\t{HouseRaffleStone.FormatLocation(_plotLocation, _plotFacet, false)}" + ); + list.Add(1060659, $"{"facet"}\t{_plotFacet}"); // ~1_val~: ~2_val~ + list.Add(1150486); // [Marked Item] } - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public HouseRaffleStone Stone + if (IsExpired) { - get => m_Stone; - set - { - m_Stone = value; - InvalidateProperties(); - } + list.Add(1150487); // [Expired] } - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public Point3D PlotLocation + // list.Add( 1060660, "shard\t{0}", ServerList.ServerName ); // ~1_val~: ~2_val~ + } + + public override void OnDoubleClick(Mobile from) + { + if (!ValidLocation()) { - get => m_PlotLocation; - set - { - m_PlotLocation = value; - InvalidateProperties(); - } + return; } - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public Map PlotFacet + if (IsChildOf(from.Backpack)) { - get => m_Facet; - set - { - m_Facet = value; - InvalidateProperties(); - } + from.CloseGump(); + from.SendGump(new WritOfLeaseGump(this)); + } + else + { + from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it. + } + } + + private class WritOfLeaseGump : Gump + { + public WritOfLeaseGump(HouseRaffleDeed deed) : base(150, 50) + { + AddPage(0); + + AddImage(0, 0, 9380); + AddImage(114, 0, 9381); + AddImage(171, 0, 9382); + AddImage(0, 140, 9383); + AddImage(114, 140, 9384); + AddImage(171, 140, 9385); + AddImage(0, 182, 9383); + AddImage(114, 182, 9384); + AddImage(171, 182, 9385); + AddImage(0, 224, 9383); + AddImage(114, 224, 9384); + AddImage(171, 224, 9385); + AddImage(0, 266, 9386); + AddImage(114, 266, 9387); + AddImage(171, 266, 9388); + + AddHtmlLocalized(30, 48, 229, 20, 1150484, 200); // WRIT OF LEASE + AddHtml(28, 75, 231, 280, FormatDescription(deed), false, true); } - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public Mobile AwardedTo + private static string FormatDescription(HouseRaffleDeed deed) { - get => m_AwardedTo; - set + if (deed == null) { - m_AwardedTo = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public bool IsExpired => m_Stone?.Deleted != false || m_Stone.IsExpired; - - public override string DefaultName => "a writ of lease"; - - public override double DefaultWeight => 1.0; - - public bool ValidLocation() => m_PlotLocation != Point3D.Zero && m_Facet != null && m_Facet != Map.Internal; - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - if (ValidLocation()) - { - list.Add( - 1060658, // ~1_val~: ~2_val~ - $"{"location"}\t{HouseRaffleStone.FormatLocation(m_PlotLocation, m_Facet, false)}" - ); - list.Add(1060659, $"{"facet"}\t{m_Facet}"); // ~1_val~: ~2_val~ - list.Add(1150486); // [Marked Item] + return string.Empty; } - if (IsExpired) + if (deed.IsExpired) { - list.Add(1150487); // [Expired] - } - - // list.Add( 1060660, "shard\t{0}", ServerList.ServerName ); // ~1_val~: ~2_val~ - } - - public override void OnDoubleClick(Mobile from) - { - if (!ValidLocation()) - { - return; - } - - if (IsChildOf(from.Backpack)) - { - from.CloseGump(); - from.SendGump(new WritOfLeaseGump(this)); - } - 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(1); // version - - writer.Write(m_Stone); - writer.Write(m_PlotLocation); - writer.Write(m_Facet); - writer.Write(m_AwardedTo); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 1: - { - m_Stone = reader.ReadEntity(); - - goto case 0; - } - case 0: - { - m_PlotLocation = reader.ReadPoint3D(); - m_Facet = reader.ReadMap(); - m_AwardedTo = reader.ReadEntity(); - - break; - } - } - } - - private class WritOfLeaseGump : Gump - { - public WritOfLeaseGump(HouseRaffleDeed deed) : base(150, 50) - { - AddPage(0); - - AddImage(0, 0, 9380); - AddImage(114, 0, 9381); - AddImage(171, 0, 9382); - AddImage(0, 140, 9383); - AddImage(114, 140, 9384); - AddImage(171, 140, 9385); - AddImage(0, 182, 9383); - AddImage(114, 182, 9384); - AddImage(171, 182, 9385); - AddImage(0, 224, 9383); - AddImage(114, 224, 9384); - AddImage(171, 224, 9385); - AddImage(0, 266, 9386); - AddImage(114, 266, 9387); - AddImage(171, 266, 9388); - - AddHtmlLocalized(30, 48, 229, 20, 1150484, 200); // WRIT OF LEASE - AddHtml(28, 75, 231, 280, FormatDescription(deed), false, true); - } - - private static string FormatDescription(HouseRaffleDeed deed) - { - if (deed == null) - { - return string.Empty; - } - - if (deed.IsExpired) - { - return - $"This deed once entitled the bearer to build a house on the plot of land located at {HouseRaffleStone.FormatLocation(deed.PlotLocation, deed.PlotFacet, false)} on the {deed.PlotFacet} facet.

The deed has expired, and now the indicated plot of land is subject to normal house construction rules.

This deed functions as a recall rune marked for the location of the plot it represents.
"; - } - - var daysLeft = (int)Math.Ceiling( - (deed.Stone.Started + deed.Stone.Duration + - HouseRaffleStone.ExpirationTime - Core.Now).TotalDays - ); - return - $"This deed entitles the bearer to build a house on the plot of land located at {HouseRaffleStone.FormatLocation(deed.PlotLocation, deed.PlotFacet, false)} on the {deed.PlotFacet} facet.

The deed will expire after {daysLeft} more day{(daysLeft == 1 ? "" : "s")} have passed, and at that time the right to place a house reverts to normal house construction rules.

This deed functions as a recall rune marked for the location of the plot it represents.

To place a house on the deeded plot, you must simply have this deed in your backpack or bank box when using a House Placement Tool there.
"; + $"This deed once entitled the bearer to build a house on the plot of land located at {HouseRaffleStone.FormatLocation(deed.PlotLocation, deed.PlotFacet, false)} on the {deed.PlotFacet} facet.

The deed has expired, and now the indicated plot of land is subject to normal house construction rules.

This deed functions as a recall rune marked for the location of the plot it represents.
"; } + + var daysLeft = (int)Math.Ceiling( + (deed.Stone.Started + deed.Stone.Duration + + HouseRaffleStone.ExpirationTime - Core.Now).TotalDays + ); + + return + $"This deed entitles the bearer to build a house on the plot of land located at {HouseRaffleStone.FormatLocation(deed.PlotLocation, deed.PlotFacet, false)} on the {deed.PlotFacet} facet.

The deed will expire after {daysLeft} more day{(daysLeft == 1 ? "" : "s")} have passed, and at that time the right to place a house reverts to normal house construction rules.

This deed functions as a recall rune marked for the location of the plot it represents.

To place a house on the deeded plot, you must simply have this deed in your backpack or bank box when using a House Placement Tool there.
"; } } } diff --git a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleManagementGump.cs b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleManagementGump.cs index c8af3b143..c5750551d 100644 --- a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleManagementGump.cs +++ b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleManagementGump.cs @@ -1,359 +1,353 @@ using System.Collections.Generic; +using System.Runtime.CompilerServices; using Server.Accounting; using Server.Items; using Server.Network; -namespace Server.Gumps +namespace Server.Gumps; + +public class HouseRaffleManagementGump : Gump { - public class HouseRaffleManagementGump : Gump + public enum SortMethod { - public enum SortMethod + Default, + Name, + Account, + Address + } + + public const int LabelColor = 0xFFFFFF; + public const int HighlightColor = 0x11EE11; + private readonly List _list; + private readonly SortMethod _sort; + + private readonly HouseRaffleStone _stone; + private int _page; + + public HouseRaffleManagementGump(HouseRaffleStone stone, SortMethod sort = SortMethod.Default, int page = 0) + : base(40, 40) + { + _stone = stone; + _page = page; + + _list = new List(_stone.Entries); + _sort = sort; + + switch (_sort) { - Default, - Name, - Account, - Address + case SortMethod.Name: + { + _list.Sort(NameComparer.Instance); + + break; + } + case SortMethod.Account: + { + _list.Sort(AccountComparer.Instance); + + break; + } + case SortMethod.Address: + { + _list.Sort(AddressComparer.Instance); + + break; + } } - public const int LabelColor = 0xFFFFFF; - public const int HighlightColor = 0x11EE11; - private readonly List m_List; - private readonly SortMethod m_Sort; + AddPage(0); - private readonly HouseRaffleStone m_Stone; - private int m_Page; + AddBackground(0, 0, 618, 354, 9270); + AddAlphaRegion(10, 10, 598, 334); - public HouseRaffleManagementGump( - HouseRaffleStone stone, SortMethod sort = SortMethod.Default, - int page = 0 - ) : base(40, 40) + AddHtml(10, 10, 598, 20, Color(Center("Raffle Management"), LabelColor)); + + AddHtml(45, 35, 100, 20, Color("Location:", LabelColor)); + AddHtml(145, 35, 250, 20, Color(_stone.FormatLocation(), LabelColor)); + + AddHtml(45, 55, 100, 20, Color("Ticket Price:", LabelColor)); + AddHtml(145, 55, 250, 20, Color(_stone.FormatPrice(), LabelColor)); + + AddHtml(45, 75, 100, 20, Color("Total Entries:", LabelColor)); + AddHtml(145, 75, 250, 20, Color(_stone.Entries.Count.ToString(), LabelColor)); + + AddButton(440, 33, 0xFA5, 0xFA7, 3); + AddHtml(474, 35, 120, 20, Color("Sort by name", LabelColor)); + + AddButton(440, 53, 0xFA5, 0xFA7, 4); + AddHtml(474, 55, 120, 20, Color("Sort by account", LabelColor)); + + AddButton(440, 73, 0xFA5, 0xFA7, 5); + AddHtml(474, 75, 120, 20, Color("Sort by address", LabelColor)); + + AddImageTiled(13, 99, 592, 242, 9264); + AddImageTiled(14, 100, 590, 240, 9274); + AddAlphaRegion(14, 100, 590, 240); + + AddHtml(14, 100, 590, 20, Color(Center("Entries"), LabelColor)); + + if (page > 0) { - m_Stone = stone; - m_Page = page; - - m_List = new List(m_Stone.Entries); - m_Sort = sort; - - switch (m_Sort) - { - case SortMethod.Name: - { - m_List.Sort(NameComparer.Instance); - - break; - } - case SortMethod.Account: - { - m_List.Sort(AccountComparer.Instance); - - break; - } - case SortMethod.Address: - { - m_List.Sort(AddressComparer.Instance); - - break; - } - } - - AddPage(0); - - AddBackground(0, 0, 618, 354, 9270); - AddAlphaRegion(10, 10, 598, 334); - - AddHtml(10, 10, 598, 20, Color(Center("Raffle Management"), LabelColor)); - - AddHtml(45, 35, 100, 20, Color("Location:", LabelColor)); - AddHtml(145, 35, 250, 20, Color(m_Stone.FormatLocation(), LabelColor)); - - AddHtml(45, 55, 100, 20, Color("Ticket Price:", LabelColor)); - AddHtml(145, 55, 250, 20, Color(m_Stone.FormatPrice(), LabelColor)); - - AddHtml(45, 75, 100, 20, Color("Total Entries:", LabelColor)); - AddHtml(145, 75, 250, 20, Color(m_Stone.Entries.Count.ToString(), LabelColor)); - - AddButton(440, 33, 0xFA5, 0xFA7, 3); - AddHtml(474, 35, 120, 20, Color("Sort by name", LabelColor)); - - AddButton(440, 53, 0xFA5, 0xFA7, 4); - AddHtml(474, 55, 120, 20, Color("Sort by account", LabelColor)); - - AddButton(440, 73, 0xFA5, 0xFA7, 5); - AddHtml(474, 75, 120, 20, Color("Sort by address", LabelColor)); - - AddImageTiled(13, 99, 592, 242, 9264); - AddImageTiled(14, 100, 590, 240, 9274); - AddAlphaRegion(14, 100, 590, 240); - - AddHtml(14, 100, 590, 20, Color(Center("Entries"), LabelColor)); - - if (page > 0) - { - AddButton(567, 104, 0x15E3, 0x15E7, 1); - } - else - { - AddImage(567, 104, 0x25EA); - } - - if ((page + 1) * 10 < m_List.Count) - { - AddButton(584, 104, 0x15E1, 0x15E5, 2); - } - else - { - AddImage(584, 104, 0x25E6); - } - - AddHtml(14, 120, 30, 20, Color(Center("DEL"), LabelColor)); - AddHtml(47, 120, 250, 20, Color("Name", LabelColor)); - AddHtml(295, 120, 100, 20, Color(Center("Address"), LabelColor)); - AddHtml(395, 120, 150, 20, Color(Center("Date"), LabelColor)); - AddHtml(545, 120, 60, 20, Color(Center("Num"), LabelColor)); - - var idx = 0; - var winner = m_Stone.Winner; - - for (var i = page * 10; i >= 0 && i < m_List.Count && i < (page + 1) * 10; ++i, ++idx) - { - var entry = m_List[i]; - - if (entry == null) - { - continue; - } - - AddButton(13, 138 + idx * 20, 4002, 4004, 6 + i); - - var x = 45; - var color = winner != null && entry.From == winner ? HighlightColor : LabelColor; - - string name = null; - - if (entry.From != null) - { - if (entry.From.Account is Account acc) - { - name = $"{entry.From.Name} ({acc})"; - } - else - { - name = entry.From.Name; - } - } - - if (name != null) - { - AddHtml(x + 2, 140 + idx * 20, 250, 20, Color(name, color)); - } - - x += 250; - - if (entry.Address != null) - { - AddHtml(x, 140 + idx * 20, 100, 20, Color(Center(entry.Address.ToString()), color)); - } - - x += 100; - - AddHtml(x, 140 + idx * 20, 150, 20, Color(Center(entry.Date.ToString()), color)); - x += 150; - - AddHtml(x, 140 + idx * 20, 60, 20, Color(Center("1"), color)); - } + AddButton(567, 104, 0x15E3, 0x15E7, 1); + } + else + { + AddImage(567, 104, 0x25EA); } - public string Right(string text) => $"
{text}
"; - - public string Center(string text) => $"
{text}
"; - - public string Color(string text, int color) => $"{text}"; - - public override void OnResponse(NetState sender, RelayInfo info) + if ((page + 1) * 10 < _list.Count) { - var from = sender.Mobile; - var buttonId = info.ButtonID; + AddButton(584, 104, 0x15E1, 0x15E5, 2); + } + else + { + AddImage(584, 104, 0x25E6); + } - switch (buttonId) + AddHtml(14, 120, 30, 20, Color(Center("DEL"), LabelColor)); + AddHtml(47, 120, 250, 20, Color("Name", LabelColor)); + AddHtml(295, 120, 100, 20, Color(Center("Address"), LabelColor)); + AddHtml(395, 120, 150, 20, Color(Center("Date"), LabelColor)); + AddHtml(545, 120, 60, 20, Color(Center("Num"), LabelColor)); + + var idx = 0; + var winner = _stone.Winner; + + for (var i = page * 10; i >= 0 && i < _list.Count && i < (page + 1) * 10; ++i, ++idx) + { + var entry = _list[i]; + + if (entry == null) { - case 1: // Previous + continue; + } + + AddButton(13, 138 + idx * 20, 4002, 4004, 6 + i); + + var x = 45; + var color = winner != null && entry.From == winner ? HighlightColor : LabelColor; + + if (entry.From != null) + { + if (entry.From.Account is Account acc) + { + AddHtml(x + 2, 140 + idx * 20, 250, 20, Color($"{entry.From.RawName} ({acc})", color)); + } + else + { + AddHtml(x + 2, 140 + idx * 20, 250, 20, Color(entry.From.RawName, color)); + } + } + + x += 250; + + if (entry.Address != null) + { + AddHtml(x, 140 + idx * 20, 100, 20, Color(Center(entry.Address.ToString()), color)); + } + + x += 100; + + AddHtml(x, 140 + idx * 20, 150, 20, Color(Center(entry.Date.ToString()), color)); + x += 150; + + AddHtml(x, 140 + idx * 20, 60, 20, Color(Center("1"), color)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string Right(string text) => $"
{text}
"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string Center(string text) => $"
{text}
"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string Color(string text, int color) => $"{text}"; + + public override void OnResponse(NetState sender, RelayInfo info) + { + var from = sender.Mobile; + var buttonId = info.ButtonID; + + switch (buttonId) + { + case 1: // Previous + { + if (_page > 0) { - if (m_Page > 0) + _page--; + } + + from.SendGump(new HouseRaffleManagementGump(_stone, _sort, _page)); + + break; + } + case 2: // Next + { + if ((_page + 1) * 10 < _stone.Entries.Count) + { + _page++; + } + + from.SendGump(new HouseRaffleManagementGump(_stone, _sort, _page)); + + break; + } + case 3: // Sort by name + { + from.SendGump(new HouseRaffleManagementGump(_stone, SortMethod.Name)); + + break; + } + case 4: // Sort by account + { + from.SendGump(new HouseRaffleManagementGump(_stone, SortMethod.Account)); + + break; + } + case 5: // Sort by address + { + from.SendGump(new HouseRaffleManagementGump(_stone, SortMethod.Address)); + + break; + } + default: // Delete + { + buttonId -= 6; + + if (buttonId >= 0 && buttonId < _list.Count) + { + _stone.Entries.Remove(_list[buttonId]); + + if (_page > 0 && _page * 10 >= _list.Count - 1) { - m_Page--; + _page--; } - from.SendGump(new HouseRaffleManagementGump(m_Stone, m_Sort, m_Page)); - - break; + from.SendGump(new HouseRaffleManagementGump(_stone, _sort, _page)); } - case 2: // Next - { - if ((m_Page + 1) * 10 < m_Stone.Entries.Count) - { - m_Page++; - } - from.SendGump(new HouseRaffleManagementGump(m_Stone, m_Sort, m_Page)); - - break; - } - case 3: // Sort by name - { - from.SendGump(new HouseRaffleManagementGump(m_Stone, SortMethod.Name)); - - break; - } - case 4: // Sort by account - { - from.SendGump(new HouseRaffleManagementGump(m_Stone, SortMethod.Account)); - - break; - } - case 5: // Sort by address - { - from.SendGump(new HouseRaffleManagementGump(m_Stone, SortMethod.Address)); - - break; - } - default: // Delete - { - buttonId -= 6; - - if (buttonId >= 0 && buttonId < m_List.Count) - { - m_Stone.Entries.Remove(m_List[buttonId]); - - if (m_Page > 0 && m_Page * 10 >= m_List.Count - 1) - { - m_Page--; - } - - from.SendGump(new HouseRaffleManagementGump(m_Stone, m_Sort, m_Page)); - } - - break; - } - } + break; + } } + } - private class NameComparer : IComparer + private class NameComparer : IComparer + { + public static readonly IComparer Instance = new NameComparer(); + + public int Compare(RaffleEntry x, RaffleEntry y) { - public static readonly IComparer Instance = new NameComparer(); + var xIsNull = x?.From == null; + var yIsNull = y?.From == null; - public int Compare(RaffleEntry x, RaffleEntry y) + if (xIsNull && yIsNull) { - var xIsNull = x?.From == null; - var yIsNull = y?.From == null; - - if (xIsNull && yIsNull) - { - return 0; - } - - if (xIsNull) - { - return -1; - } - - if (yIsNull) - { - return 1; - } - - var result = x.From.Name.InsensitiveCompare(y.From.Name); - - return result == 0 ? x.Date.CompareTo(y.Date) : result; + return 0; } + + if (xIsNull) + { + return -1; + } + + if (yIsNull) + { + return 1; + } + + var result = x.From.Name.InsensitiveCompare(y.From.Name); + + return result == 0 ? x.Date.CompareTo(y.Date) : result; } + } - private class AccountComparer : IComparer + private class AccountComparer : IComparer + { + public static readonly IComparer Instance = new AccountComparer(); + + public int Compare(RaffleEntry x, RaffleEntry y) { - public static readonly IComparer Instance = new AccountComparer(); + var xIsNull = x?.From == null; + var yIsNull = y?.From == null; - public int Compare(RaffleEntry x, RaffleEntry y) + if (xIsNull && yIsNull) { - var xIsNull = x?.From == null; - var yIsNull = y?.From == null; - - if (xIsNull && yIsNull) - { - return 0; - } - - if (xIsNull) - { - return -1; - } - - if (yIsNull) - { - return 1; - } - - var a = x.From.Account as Account; - var b = y.From.Account as Account; - - if (a == null && b == null) - { - return 0; - } - - if (a == null) - { - return -1; - } - - if (b == null) - { - return 1; - } - - var result = a.Username.InsensitiveCompare(b.Username); - - return result == 0 ? x.Date.CompareTo(y.Date) : result; + return 0; } + + if (xIsNull) + { + return -1; + } + + if (yIsNull) + { + return 1; + } + + var a = x.From.Account as Account; + var b = y.From.Account as Account; + + if (a == null && b == null) + { + return 0; + } + + if (a == null) + { + return -1; + } + + if (b == null) + { + return 1; + } + + var result = a.Username.InsensitiveCompare(b.Username); + + return result == 0 ? x.Date.CompareTo(y.Date) : result; } + } - private class AddressComparer : IComparer + private class AddressComparer : IComparer + { + public static readonly IComparer Instance = new AddressComparer(); + + public int Compare(RaffleEntry x, RaffleEntry y) { - public static readonly IComparer Instance = new AddressComparer(); + var xIsNull = x?.Address == null; + var yIsNull = y?.Address == null; - public int Compare(RaffleEntry x, RaffleEntry y) + if (xIsNull && yIsNull) { - var xIsNull = x?.Address == null; - var yIsNull = y?.Address == null; - - if (xIsNull && yIsNull) - { - return 0; - } - - if (xIsNull) - { - return -1; - } - - if (yIsNull) - { - return 1; - } - - var a = x.Address.GetAddressBytes(); - var b = y.Address.GetAddressBytes(); - - for (var i = 0; i < a.Length && i < b.Length; i++) - { - var compare = a[i].CompareTo(b[i]); - - if (compare != 0) - { - return compare; - } - } - - return x.Date.CompareTo(y.Date); + return 0; } + + if (xIsNull) + { + return -1; + } + + if (yIsNull) + { + return 1; + } + + var a = x.Address.GetAddressBytes(); + var b = y.Address.GetAddressBytes(); + + for (var i = 0; i < a.Length && i < b.Length; i++) + { + var compare = a[i].CompareTo(b[i]); + + if (compare != 0) + { + return compare; + } + } + + return x.Date.CompareTo(y.Date); } } } diff --git a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleRegion.cs b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleRegion.cs index 2312ef012..2f21da7dc 100644 --- a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleRegion.cs +++ b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleRegion.cs @@ -2,67 +2,65 @@ using Server.Items; using Server.Spells.Sixth; using Server.Targeting; -namespace Server.Regions +namespace Server.Regions; + +public class HouseRaffleRegion : BaseRegion { - public class HouseRaffleRegion : BaseRegion + private readonly HouseRaffleStone _stone; + + public HouseRaffleRegion(HouseRaffleStone stone) : base(null, stone.PlotFacet, DefaultPriority, stone.PlotBounds) => + _stone = stone; + + public override bool AllowHousing(Mobile from, Point3D p) { - private readonly HouseRaffleStone m_Stone; - - public HouseRaffleRegion(HouseRaffleStone stone) - : base(null, stone.PlotFacet, DefaultPriority, stone.PlotBounds) => - m_Stone = stone; - - public override bool AllowHousing(Mobile from, Point3D p) + if (_stone == null) { - if (m_Stone == null) - { - return false; - } - - if (m_Stone.IsExpired) - { - return true; - } - - if (m_Stone.Deed == null) - { - return false; - } - - var pack = from.Backpack; - - if (pack != null && ContainsDeed(pack)) - { - return true; - } - - var bank = from.FindBankNoCreate(); - - return bank != null && ContainsDeed(bank); - } - - private bool ContainsDeed(Container cont) - { - foreach (var deed in cont.FindItemsByType()) - { - if (deed == m_Stone.Deed) - { - return true; - } - } - return false; } - public override bool OnTarget(Mobile m, Target t, object o) + if (_stone.IsExpired) { - if (m.Spell is MarkSpell && m.AccessLevel == AccessLevel.Player) - { - m.SendLocalizedMessage(501800); // You cannot mark an object at that location. - return false; - } - - return base.OnTarget(m, t, o); + return true; } + + if (_stone.Deed == null) + { + return false; + } + + var pack = from.Backpack; + + if (pack != null && ContainsDeed(pack)) + { + return true; + } + + var bank = from.FindBankNoCreate(); + + return bank != null && ContainsDeed(bank); + } + + private bool ContainsDeed(Container cont) + { + foreach (var deed in cont.FindItemsByType()) + { + if (deed == _stone.Deed) + { + return true; + } + } + + return false; + } + + public override bool OnTarget(Mobile m, Target t, object o) + { + if (m.Spell is MarkSpell && m.AccessLevel == AccessLevel.Player) + { + m.SendLocalizedMessage(501800); // You cannot mark an object at that location. + return false; + } + + return base.OnTarget(m, t, o); } } diff --git a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleStone.cs b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleStone.cs index 949a83d46..54c564b4d 100644 --- a/Projects/UOContent/Items/Special/House Raffle/HouseRaffleStone.cs +++ b/Projects/UOContent/Items/Special/House Raffle/HouseRaffleStone.cs @@ -1,770 +1,666 @@ using System; using System.Collections.Generic; using System.Net; -using System.Text; +using ModernUO.Serialization; using Server.Accounting; +using Server.Buffers; using Server.ContextMenus; using Server.Gumps; using Server.Mobiles; using Server.Network; using Server.Regions; -namespace Server.Items +namespace Server.Items; + +[SerializationGenerator(0)] +public partial class RaffleEntry { - public class RaffleEntry + [SerializableField(0, setter: "private")] + private Mobile _from; + + [SerializableField(1, setter: "private")] + private IPAddress _address; + + [SerializableField(2, setter: "private")] + private DateTime _date; + + public RaffleEntry(Mobile from) { - public RaffleEntry(Mobile from) + _from = from; + _address = from?.NetState?.Address ?? IPAddress.None; + _date = Core.Now; + } + + public RaffleEntry() + { + _from = null; + _address = null; + _date = Core.Now; + } +} + +public enum HouseRaffleState +{ + Inactive, + Active, + Completed +} + +public enum HouseRaffleExpireAction +{ + None, + HideStone, + DeleteStone +} + +[SerializationGenerator(4, false)] +[Flippable(0xEDD, 0xEDE)] +public partial class HouseRaffleStone : Item +{ + private const int EntryLimitPerIP = 4; + private const int DefaultTicketPrice = 5000; + private const int MessageHue = 1153; + + public static readonly TimeSpan DefaultDuration = TimeSpan.FromDays(7.0); // Duration of the raffle itself + public static readonly TimeSpan ExpirationTime = TimeSpan.FromDays(30.0); // Time a person has to use the stone to place the house + + private static Timer _allStonesTimer; + private static HashSet _allStones = null; + + private HouseRaffleRegion _region; + + [SerializableField(1)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private HouseRaffleExpireAction _expireAction; + + [SerializableField(2)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private HouseRaffleDeed _deed; + + [InvalidateProperties] + [SerializableField(5)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private Mobile _winner; + + [InvalidateProperties] + [SerializableField(7)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private DateTime _started; + + [InvalidateProperties] + [SerializableField(8)] + [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + private TimeSpan _duration; + + [SerializableField(9, setter: "private")] + private List _entries; + + [Constructible] + public HouseRaffleStone() : base(0xEDD) + { + _region = null; + _plotBounds = new Rectangle2D(); + _plotFacet = null; + + _winner = null; + _deed = null; + + _currentState = HouseRaffleState.Inactive; + _started = DateTime.MinValue; + _duration = DefaultDuration; + _expireAction = HouseRaffleExpireAction.None; + _ticketPrice = DefaultTicketPrice; + + Entries = new List(); + + Movable = false; + } + + [SerializableProperty(0)] + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + public HouseRaffleState CurrentState + { + get => _currentState; + set { - From = from; - - Address = From.NetState?.Address ?? IPAddress.None; - - Date = Core.Now; - } - - public RaffleEntry(IGenericReader reader, int version) - { - switch (version) + if (_currentState != value) { - case 3: // HouseRaffleStone version changes - case 2: - case 1: - case 0: - { - From = reader.ReadEntity(); - Address = Utility.Intern(reader.ReadIPAddress()); - Date = reader.ReadDateTime(); + if (value == HouseRaffleState.Active) + { + this.Clear(_entries); + Winner = null; + Deed = null; + Started = Core.Now; + AddRaffleStone(this); + } + else + { + RemoveRaffleStone(this); + } - break; - } + _currentState = value; + InvalidateProperties(); + this.MarkDirty(); } } - - public Mobile From { get; } - - public IPAddress Address { get; } - - public DateTime Date { get; } - - public void Serialize(IGenericWriter writer) - { - writer.Write(From); - writer.Write(Address); - writer.Write(Date); - } } - public enum HouseRaffleState + [SerializableProperty(3)] + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + public Rectangle2D PlotBounds { - Inactive, - Active, - Completed + get => _plotBounds; + set + { + _plotBounds = value; + + InvalidateRegion(); + InvalidateProperties(); + this.MarkDirty(); + } } - public enum HouseRaffleExpireAction + [SerializableProperty(4)] + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + public Map PlotFacet { - None, - HideStone, - DeleteStone + get => _plotFacet; + set + { + _plotFacet = value; + + InvalidateRegion(); + InvalidateProperties(); + this.MarkDirty(); + } } - [Flippable(0xEDD, 0xEDE)] - public class HouseRaffleStone : Item + [CommandProperty(AccessLevel.GameMaster)] + public bool IsExpired { - private const int EntryLimitPerIP = 4; - private const int DefaultTicketPrice = 5000; - private const int MessageHue = 1153; - - public static readonly TimeSpan DefaultDuration = TimeSpan.FromDays(7.0); - public static readonly TimeSpan ExpirationTime = TimeSpan.FromDays(30.0); - - private static readonly List m_AllStones = new(); - private Rectangle2D m_Bounds; - private TimeSpan m_Duration; - private Map m_Facet; - - private HouseRaffleRegion m_Region; - private DateTime m_Started; - - private HouseRaffleState m_State; - private int m_TicketPrice; - - private Mobile m_Winner; - - [Constructible] - public HouseRaffleStone() - : base(0xEDD) + get { - m_Region = null; - m_Bounds = new Rectangle2D(); - m_Facet = null; - - m_Winner = null; - Deed = null; - - m_State = HouseRaffleState.Inactive; - m_Started = DateTime.MinValue; - m_Duration = DefaultDuration; - ExpireAction = HouseRaffleExpireAction.None; - m_TicketPrice = DefaultTicketPrice; - - Entries = new List(); - - Movable = false; - - m_AllStones.Add(this); - } - - public HouseRaffleStone(Serial serial) - : base(serial) - { - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public HouseRaffleState CurrentState - { - get => m_State; - set + if (_currentState != HouseRaffleState.Completed) { - if (m_State != value) - { - if (value == HouseRaffleState.Active) - { - Entries.Clear(); - m_Winner = null; - Deed = null; - m_Started = Core.Now; - } - - m_State = value; - InvalidateProperties(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public Rectangle2D PlotBounds - { - get => m_Bounds; - set - { - m_Bounds = value; - - InvalidateRegion(); - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public Map PlotFacet - { - get => m_Facet; - set - { - m_Facet = value; - - InvalidateRegion(); - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public Mobile Winner - { - get => m_Winner; - set - { - m_Winner = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public HouseRaffleDeed Deed { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public DateTime Started - { - get => m_Started; - set - { - m_Started = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public TimeSpan Duration - { - get => m_Duration; - set - { - m_Duration = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool IsExpired - { - get - { - if (m_State != HouseRaffleState.Completed) - { - return false; - } - - return m_Started + m_Duration + ExpirationTime <= Core.Now; - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public HouseRaffleExpireAction ExpireAction { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] - public int TicketPrice - { - get => m_TicketPrice; - set - { - m_TicketPrice = Math.Max(0, value); - InvalidateProperties(); - } - } - - public List Entries { get; private set; } - - public override string DefaultName => "a house raffle stone"; - - public override bool DisplayWeight => false; - - public static void CheckEnd_OnTick() - { - for (var i = 0; i < m_AllStones.Count; i++) - { - m_AllStones[i].CheckEnd(); - } - } - - public static void Initialize() - { - for (var i = m_AllStones.Count - 1; i >= 0; i--) - { - var stone = m_AllStones[i]; - - if (stone.IsExpired) - { - switch (stone.ExpireAction) - { - case HouseRaffleExpireAction.HideStone: - { - if (stone.Visible) - { - stone.Visible = false; - stone.ItemID = 0x1B7B; // Non-blocking ItemID - } - - break; - } - case HouseRaffleExpireAction.DeleteStone: - { - stone.Delete(); - break; - } - } - } + return false; } + return Core.Now > _started + _duration + ExpirationTime; + } + } + + [SerializableProperty(6)] + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Seer)] + public int TicketPrice + { + get => _ticketPrice; + set + { + _ticketPrice = Math.Max(0, value); + InvalidateProperties(); + this.MarkDirty(); + } + } + + public override string DefaultName => "a house raffle stone"; + + public override bool DisplayWeight => false; + + public static void CheckEnd_OnTick() + { + foreach (var stone in _allStones) + { + stone.CheckEnd(); + } + } + + private static void AddRaffleStone(HouseRaffleStone stone) + { + _allStones ??= new HashSet(); + _allStones.Add(stone); + + if (_allStones.Count == 1) + { Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), CheckEnd_OnTick); } + } - public bool ValidLocation() => - m_Bounds.Start != Point2D.Zero && m_Bounds.End != Point2D.Zero && m_Facet != null && - m_Facet != Map.Internal; + private static void RemoveRaffleStone(HouseRaffleStone stone) + { + _allStones.Remove(stone); - private void InvalidateRegion() + if (_allStones.Count == 0) { - if (m_Region != null) - { - m_Region.Unregister(); - m_Region = null; - } + _allStonesTimer.Stop(); + _allStonesTimer = null; + } + } - if (ValidLocation()) - { - m_Region = new HouseRaffleRegion(this); - m_Region.Register(); - } + public bool ValidLocation() => + _plotBounds.Start != Point2D.Zero && + _plotBounds.End != Point2D.Zero && + _plotFacet != null && _plotFacet != Map.Internal; + + private void InvalidateRegion() + { + if (_region != null) + { + _region.Unregister(); + _region = null; } - private bool HasEntered(Mobile from) + if (ValidLocation()) { - if (from.Account is not Account acc) - { - return false; - } - - foreach (var entry in Entries) - { - if (entry.From != null) - { - var entryAcc = entry.From.Account as Account; - - if (entryAcc == acc) - { - return true; - } - } - } + _region = new HouseRaffleRegion(this); + _region.Register(); + } + } + private bool HasEntered(Mobile from) + { + if (from.Account is not Account acc) + { return false; } - private bool IsAtIPLimit(Mobile from) + foreach (var entry in Entries) { - if (from.NetState == null) + if (entry.From != null) { - return false; - } + var entryAcc = entry.From.Account as Account; - var address = from.NetState.Address; - var tickets = 0; - - foreach (var entry in Entries) - { - if (Utility.IPMatchClassC(entry.Address, address)) + if (entryAcc == acc) { - if (++tickets >= EntryLimitPerIP) - { - return true; - } + return true; } } + } + return false; + } + + private bool IsAtIPLimit(Mobile from) + { + if (from.NetState == null) + { return false; } - public static string FormatLocation(Point3D loc, Map map, bool displayMap) + var address = from.NetState.Address; + var tickets = 0; + + foreach (var entry in Entries) { - var result = new StringBuilder(); - - int xLong = 0, yLat = 0; - int xMins = 0, yMins = 0; - bool xEast = false, ySouth = false; - - if (Sextant.Format(loc, map, ref xLong, ref yLat, ref xMins, ref yMins, ref xEast, ref ySouth)) + if (Utility.IPMatchClassC(entry.Address, address)) { - result.AppendFormat( - "{0}°{1}'{2},{3}°{4}'{5}", - yLat, - yMins, - ySouth ? "S" : "N", - xLong, - xMins, - xEast ? "E" : "W" - ); - } - else - { - result.AppendFormat("{0},{1}", loc.X, loc.Y); - } - - if (displayMap) - { - result.AppendFormat(" ({0})", map); - } - - return result.ToString(); - } - - public Point3D GetPlotCenter() - { - var x = m_Bounds.X + m_Bounds.Width / 2; - var y = m_Bounds.Y + m_Bounds.Height / 2; - var z = m_Facet?.GetAverageZ(x, y) ?? 0; - - return new Point3D(x, y, z); - } - - public string FormatLocation() - { - if (!ValidLocation()) - { - return "no location set"; - } - - return FormatLocation(GetPlotCenter(), m_Facet, true); - } - - public string FormatPrice() => m_TicketPrice == 0 ? "FREE" : $"{m_TicketPrice} gold"; - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - if (ValidLocation()) - { - list.Add(FormatLocation()); - } - - switch (m_State) - { - case HouseRaffleState.Active: - { - list.Add(1060658, $"{"ticket price"}\t{FormatPrice()}"); // ~1_val~: ~2_val~ - list.Add(1060659, $"{"ends"}\t{m_Started + m_Duration}"); // ~1_val~: ~2_val~ - break; - } - case HouseRaffleState.Completed: - { - list.Add(1060658, $"winner\t{m_Winner?.Name ?? "Unknown"}"); // ~1_val~: ~2_val~ - break; - } - } - } - - public override void OnSingleClick(Mobile from) - { - base.OnSingleClick(from); - - switch (m_State) - { - case HouseRaffleState.Active: - { - LabelTo(from, 1060658, $"Ends\t{m_Started + m_Duration}"); // ~1_val~: ~2_val~ - break; - } - case HouseRaffleState.Completed: - { - LabelTo( - from, - 1060658, // ~1_val~: ~2_val~ - $"Winner\t{m_Winner?.Name ?? "Unknown"}" - ); - break; - } - } - } - - public override void GetContextMenuEntries(Mobile from, List list) - { - base.GetContextMenuEntries(from, list); - - if (from.AccessLevel >= AccessLevel.Seer) - { - list.Add(new EditEntry(from, this)); - - if (m_State == HouseRaffleState.Inactive) + if (++tickets >= EntryLimitPerIP) { - list.Add(new ActivateEntry(from, this)); - } - else - { - list.Add(new ManagementEntry(from, this)); + return true; } } } - public override void OnDoubleClick(Mobile from) + return false; + } + + public static string FormatLocation(Point3D loc, Map map, bool displayMap) + { + using var result = ValueStringBuilder.Create(); + + var xLong = 0; + var yLat = 0; + int xMins = 0; + var yMins = 0; + bool xEast = false, ySouth = false; + + if (Sextant.Format(loc, map, ref xLong, ref yLat, ref xMins, ref yMins, ref xEast, ref ySouth)) { - if (m_State != HouseRaffleState.Active || !from.CheckAlive()) - { - return; - } - - if (!from.InRange(GetWorldLocation(), 2)) - { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that. - return; - } - - if (HasEntered(from)) - { - from.SendMessage(MessageHue, "You have already entered this plot's raffle."); - } - else if (IsAtIPLimit(from)) - { - from.SendMessage(MessageHue, "You may not enter this plot's raffle."); - } - else - { - from.SendGump( - new WarningGump( - 1150470, - 0x7F00, - $"You are about to purchase a raffle ticket for the house plot located at {FormatLocation()}. The ticket price is {FormatPrice()}. Tickets are non-refundable and you can only purchase one ticket per account. Do you wish to continue?", - 0xFFFFFF, - 420, - 280, - okay => Purchase_Callback(from, okay) - ) - ); // CONFIRM TICKET PURCHASE - } + result.Append($"{yLat}°{yMins}'{(ySouth ? "S" : "N")},{xLong}°{xMins}'{(xEast ? "E" : "W")}"); + } + else + { + result.Append($"{loc.X},{loc.Y}"); } - public void Purchase_Callback(Mobile from, bool okay) + if (displayMap) { - if (Deleted || m_State != HouseRaffleState.Active || !from.CheckAlive() || HasEntered(from) || IsAtIPLimit(from)) - { - return; - } - - if (from.Account is not Account) - { - return; - } - - if (okay) - { - Container bank = from.FindBankNoCreate(); - - if (m_TicketPrice == 0 || from.Backpack?.ConsumeTotal(typeof(Gold), m_TicketPrice) == true || - Banker.Withdraw(from, m_TicketPrice)) - { - Entries.Add(new RaffleEntry(from)); - - from.SendMessage(MessageHue, "You have successfully entered the plot's raffle."); - } - else - { - from.SendMessage(MessageHue, $"You do not have the {FormatPrice()} required to enter the raffle."); - } - } - else - { - from.SendMessage(MessageHue, "You have chosen not to enter the raffle."); - } + result.Append($" ({map})"); } - public void CheckEnd() + return result.ToString(); + } + + public Point3D GetPlotCenter() + { + var x = _plotBounds.X + _plotBounds.Width / 2; + var y = _plotBounds.Y + _plotBounds.Height / 2; + var z = _plotFacet?.GetAverageZ(x, y) ?? 0; + + return new Point3D(x, y, z); + } + + public string FormatLocation() => + !ValidLocation() ? "no location set" : FormatLocation(GetPlotCenter(), _plotFacet, true); + + public string FormatPrice() => _ticketPrice == 0 ? "FREE" : $"{_ticketPrice} gold"; + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + if (ValidLocation()) { - if (m_State != HouseRaffleState.Active || m_Started + m_Duration > Core.Now) - { - return; - } + list.Add(FormatLocation()); + } - m_State = HouseRaffleState.Completed; - - if (m_Region != null && Entries.Count != 0) - { - m_Winner = Entries.RandomElement().From; - - if (m_Winner != null) + switch (_currentState) + { + case HouseRaffleState.Active: { - Deed = new HouseRaffleDeed(this, m_Winner); + list.Add(1060658, $"{"ticket price"}\t{FormatPrice()}"); // ~1_val~: ~2_val~ + list.Add(1060659, $"{"ends"}\t{_started + _duration}"); // ~1_val~: ~2_val~ + break; + } + case HouseRaffleState.Completed: + { + list.Add(1060658, $"winner\t{_winner?.Name ?? "Unknown"}"); // ~1_val~: ~2_val~ + break; + } + } + } - m_Winner.SendMessage( - MessageHue, - $"Congratulations, {m_Winner.Name}! You have won the raffle for the plot located at {FormatLocation()}." + public override void OnSingleClick(Mobile from) + { + base.OnSingleClick(from); + + switch (_currentState) + { + case HouseRaffleState.Active: + { + LabelTo(from, 1060658, $"Ends\t{_started + _duration}"); // ~1_val~: ~2_val~ + break; + } + case HouseRaffleState.Completed: + { + LabelTo( + from, + 1060658, // ~1_val~: ~2_val~ + $"Winner\t{_winner?.Name ?? "Unknown"}" ); + break; + } + } + } - if (m_Winner.AddToBackpack(Deed)) - { - m_Winner.SendMessage(MessageHue, "The writ of lease has been placed in your backpack."); - } - else - { - m_Winner.BankBox.DropItem(Deed); - m_Winner.SendMessage( - MessageHue, - "As your backpack is full, the writ of lease has been placed in your bank box." - ); - } + public override void GetContextMenuEntries(Mobile from, List list) + { + base.GetContextMenuEntries(from, list); + + if (from.AccessLevel >= AccessLevel.Seer) + { + list.Add(new EditEntry(from, this)); + + if (_currentState == HouseRaffleState.Inactive) + { + list.Add(new ActivateEntry(from, this)); + } + else + { + list.Add(new ManagementEntry(from, this)); + } + } + } + + public override void OnDoubleClick(Mobile from) + { + if (_currentState != HouseRaffleState.Active || !from.CheckAlive()) + { + return; + } + + if (!from.InRange(GetWorldLocation(), 2)) + { + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that. + return; + } + + if (HasEntered(from)) + { + from.SendMessage(MessageHue, "You have already entered this plot's raffle."); + } + else if (IsAtIPLimit(from)) + { + from.SendMessage(MessageHue, "You may not enter this plot's raffle."); + } + else + { + from.SendGump( + new WarningGump( + 1150470, // CONFIRM TICKET PURCHASE + 0x7F00, + $"You are about to purchase a raffle ticket for the house plot located at {FormatLocation()}. The ticket price is {FormatPrice()}. Tickets are non-refundable and you can only purchase one ticket per account. Do you wish to continue?", + 0xFFFFFF, + 420, + 280, + okay => Purchase_Callback(from, okay) + ) + ); + } + } + + public void Purchase_Callback(Mobile from, bool okay) + { + if (Deleted || _currentState != HouseRaffleState.Active || !from.CheckAlive() || HasEntered(from) || IsAtIPLimit(from)) + { + return; + } + + if (from.Account is not Account) + { + return; + } + + if (okay) + { + Container bank = from.FindBankNoCreate(); + + if (_ticketPrice == 0 || from.Backpack?.ConsumeTotal(typeof(Gold), _ticketPrice) == true || + Banker.Withdraw(from, _ticketPrice)) + { + this.Add(Entries, new RaffleEntry(from)); + + from.SendMessage(MessageHue, "You have successfully entered the plot's raffle."); + } + else + { + from.SendMessage(MessageHue, $"You do not have the {FormatPrice()} required to enter the raffle."); + } + } + else + { + from.SendMessage(MessageHue, "You have chosen not to enter the raffle."); + } + } + + public void CheckEnd() + { + if (_currentState != HouseRaffleState.Active || Core.Now < _started + _duration) + { + return; + } + + CurrentState = HouseRaffleState.Completed; + + if (_region != null && _entries.Count != 0) + { + Winner = _entries.RandomElement().From; + + if (_winner != null) + { + Deed = new HouseRaffleDeed(this, _winner); + + _winner.SendMessage( + MessageHue, + $"Congratulations, {_winner.Name}! You have won the raffle for the plot located at {FormatLocation()}." + ); + + if (_winner.AddToBackpack(Deed)) + { + _winner.SendMessage(MessageHue, "The writ of lease has been placed in your backpack."); + } + else + { + _winner.BankBox.DropItem(Deed); + _winner.SendMessage( + MessageHue, + "As your backpack is full, the writ of lease has been placed in your bank box." + ); } } - - InvalidateProperties(); } - public override void OnDelete() + InvalidateProperties(); + } + + public override void OnDelete() + { + if (_region != null) { - if (m_Region != null) + _region.Unregister(); + _region = null; + } + + RemoveRaffleStone(this); + + base.OnDelete(); + } + + private void Deserialize(IGenericReader reader, int version) + { + _currentState = (HouseRaffleState)reader.ReadEncodedInt(); + _expireAction = (HouseRaffleExpireAction)reader.ReadEncodedInt(); + _deed = reader.ReadEntity(); + _plotBounds = reader.ReadRect2D(); + _plotFacet = reader.ReadMap(); + _winner = reader.ReadEntity(); + _ticketPrice = reader.ReadInt(); + _started = reader.ReadDateTime(); + _duration = reader.ReadTimeSpan(); + + var entryCount = reader.ReadInt(); + _entries = new List(entryCount); + + for (var i = 0; i < entryCount; i++) + { + var entry = new RaffleEntry(); + entry.Deserialize(reader); + + if (entry.From == null) { - m_Region.Unregister(); - m_Region = null; + continue; // Character was deleted } - m_AllStones.Remove(this); - - base.OnDelete(); + _entries.Add(entry); } + } - public override void Serialize(IGenericWriter writer) + [AfterDeserialization] + private void AfterDeserialization() + { + InvalidateRegion(); + + // Only check for expirations on world load since the wait time is so long (30 days) + if (IsExpired) { - base.Serialize(writer); - - writer.Write(3); // version - - writer.WriteEncodedInt((int)m_State); - writer.WriteEncodedInt((int)ExpireAction); - - writer.Write(Deed); - - writer.Write(m_Bounds); - writer.Write(m_Facet); - - writer.Write(m_Winner); - - writer.Write(m_TicketPrice); - writer.Write(m_Started); - writer.Write(m_Duration); - - writer.Write(Entries.Count); - - foreach (var entry in Entries) + switch (ExpireAction) { - entry.Serialize(writer); - } - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 3: + case HouseRaffleExpireAction.HideStone: { - m_State = (HouseRaffleState)reader.ReadEncodedInt(); - - goto case 2; - } - case 2: - { - ExpireAction = (HouseRaffleExpireAction)reader.ReadEncodedInt(); - - goto case 1; - } - case 1: - { - Deed = reader.ReadEntity(); - - goto case 0; - } - case 0: - { - var oldActive = version < 3 && reader.ReadBool(); - - m_Bounds = reader.ReadRect2D(); - m_Facet = reader.ReadMap(); - - m_Winner = reader.ReadEntity(); - - m_TicketPrice = reader.ReadInt(); - m_Started = reader.ReadDateTime(); - m_Duration = reader.ReadTimeSpan(); - - var entryCount = reader.ReadInt(); - Entries = new List(entryCount); - - for (var i = 0; i < entryCount; i++) + if (Visible) { - var entry = new RaffleEntry(reader, version); - - if (entry.From == null) - { - continue; // Character was deleted - } - - Entries.Add(entry); - } - - InvalidateRegion(); - - m_AllStones.Add(this); - - if (version < 3) - { - if (oldActive) - { - m_State = HouseRaffleState.Active; - } - else if (m_Winner != null) - { - m_State = HouseRaffleState.Completed; - } - else - { - m_State = HouseRaffleState.Inactive; - } + Visible = false; + ItemID = 0x1B7B; // Non-blocking ItemID } break; } + case HouseRaffleExpireAction.DeleteStone: + { + Timer.DelayCall(Delete); + break; + } + } + } + else if (_currentState == HouseRaffleState.Active) + { + AddRaffleStone(this); + } + } + + private class RaffleContextMenuEntry : ContextMenuEntry + { + protected readonly Mobile _from; + protected readonly HouseRaffleStone _stone; + + public RaffleContextMenuEntry(Mobile from, HouseRaffleStone stone, int label) : base(label) + { + _from = from; + _stone = stone; + } + } + + private class EditEntry : RaffleContextMenuEntry + { + public EditEntry(Mobile from, HouseRaffleStone stone) : base(from, stone, 5101) // Edit + { + } + + public override void OnClick() + { + if (_stone.Deleted || _from.AccessLevel < AccessLevel.Seer) + { + return; + } + + _from.SendGump(new PropertiesGump(_from, _stone)); + } + } + + private class ActivateEntry : RaffleContextMenuEntry + { + public ActivateEntry(Mobile from, HouseRaffleStone stone) : base(from, stone, 5113) // Start + { + if (!stone.ValidLocation()) + { + Flags |= CMEFlags.Disabled; } } - private class RaffleContextMenuEntry : ContextMenuEntry + public override void OnClick() { - protected readonly Mobile m_From; - protected readonly HouseRaffleStone m_Stone; - - public RaffleContextMenuEntry(Mobile from, HouseRaffleStone stone, int label) - : base(label) + if (_stone.Deleted || _from.AccessLevel < AccessLevel.Seer || !_stone.ValidLocation()) { - m_From = from; - m_Stone = stone; + return; } + + _stone.CurrentState = HouseRaffleState.Active; + } + } + + private class ManagementEntry : RaffleContextMenuEntry + { + public ManagementEntry(Mobile from, HouseRaffleStone stone) : base(from, stone, 5032) // Game Monitor + { } - private class EditEntry : RaffleContextMenuEntry + public override void OnClick() { - public EditEntry(Mobile from, HouseRaffleStone stone) - : base(from, stone, 5101) // Edit + if (_stone.Deleted || _from.AccessLevel < AccessLevel.Seer) { + return; } - public override void OnClick() - { - if (m_Stone.Deleted || m_From.AccessLevel < AccessLevel.Seer) - { - return; - } - - m_From.SendGump(new PropertiesGump(m_From, m_Stone)); - } - } - - private class ActivateEntry : RaffleContextMenuEntry - { - public ActivateEntry(Mobile from, HouseRaffleStone stone) - : base(from, stone, 5113) // Start - { - if (!stone.ValidLocation()) - { - Flags |= CMEFlags.Disabled; - } - } - - public override void OnClick() - { - if (m_Stone.Deleted || m_From.AccessLevel < AccessLevel.Seer || !m_Stone.ValidLocation()) - { - return; - } - - m_Stone.CurrentState = HouseRaffleState.Active; - } - } - - private class ManagementEntry : RaffleContextMenuEntry - { - public ManagementEntry(Mobile from, HouseRaffleStone stone) - : base(from, stone, 5032) // Game Monitor - { - } - - public override void OnClick() - { - if (m_Stone.Deleted || m_From.AccessLevel < AccessLevel.Seer) - { - return; - } - - m_From.SendGump(new HouseRaffleManagementGump(m_Stone)); - } + _from.SendGump(new HouseRaffleManagementGump(_stone)); } } } diff --git a/Projects/UOContent/Migrations/Server.Items.HouseRaffleDeed.v0.json b/Projects/UOContent/Migrations/Server.Items.HouseRaffleDeed.v0.json new file mode 100644 index 000000000..e0dd23ecd --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.HouseRaffleDeed.v0.json @@ -0,0 +1,32 @@ +{ + "version": 0, + "type": "Server.Items.HouseRaffleDeed", + "properties": [ + { + "name": "Stone", + "type": "Server.Items.HouseRaffleStone", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "PlotLocation", + "type": "Server.Point3D", + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "Point3D" + ] + }, + { + "name": "PlotFacet", + "type": "Server.Map", + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "Map" + ] + }, + { + "name": "AwardedTo", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.HouseRaffleStone.v4.json b/Projects/UOContent/Migrations/Server.Items.HouseRaffleStone.v4.json new file mode 100644 index 000000000..b2d79f227 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.HouseRaffleStone.v4.json @@ -0,0 +1,73 @@ +{ + "version": 4, + "type": "Server.Items.HouseRaffleStone", + "properties": [ + { + "name": "CurrentState", + "type": "Server.Items.HouseRaffleState", + "rule": "EnumMigrationRule" + }, + { + "name": "ExpireAction", + "type": "Server.Items.HouseRaffleExpireAction", + "rule": "EnumMigrationRule" + }, + { + "name": "Deed", + "type": "Server.Items.HouseRaffleDeed", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "PlotBounds", + "type": "Server.Rectangle2D", + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "Rect2D" + ] + }, + { + "name": "PlotFacet", + "type": "Server.Map", + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "Map" + ] + }, + { + "name": "Winner", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "TicketPrice", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Started", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Duration", + "type": "System.TimeSpan", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Entries", + "type": "System.Collections.Generic.List\u003CServer.Items.RaffleEntry\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "Server.Items.RaffleEntry", + "RawSerializableMigrationRule", + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.RaffleEntry.v0.json b/Projects/UOContent/Migrations/Server.Items.RaffleEntry.v0.json new file mode 100644 index 000000000..ce7a72c6b --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.RaffleEntry.v0.json @@ -0,0 +1,24 @@ +{ + "version": 0, + "type": "Server.Items.RaffleEntry", + "properties": [ + { + "name": "From", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Address", + "type": "System.Net.IPAddress", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Date", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file