diff --git a/Projects/Server/FeatureFlags.cs b/Projects/Server/FeatureFlags.cs index 99d6f9226..a70b4e452 100644 --- a/Projects/Server/FeatureFlags.cs +++ b/Projects/Server/FeatureFlags.cs @@ -10,4 +10,5 @@ public static class ServerFeatureFlags public static bool PvPCombat { get; set; } = true; public static bool BankAccess { get; set; } = true; public static bool SpeedhackDetection { get; set; } + public static bool InsuranceEnabled { get; set; } } diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index a3e546bc2..4d5ee13b3 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -1884,7 +1884,7 @@ public partial class Item : IHued, IComparable, ISpawnable, IObjectPropert { list.Add(1049643); // cursed } - else if (Insured) + else if (ServerFeatureFlags.InsuranceEnabled && Insured) { list.Add(1061682); // insured } @@ -4216,12 +4216,12 @@ public partial class Item : IHued, IComparable, ISpawnable, IObjectPropert } public virtual bool CheckBlessed(Mobile m) => - m_LootType == LootType.Blessed || Mobile.InsuranceEnabled && Insured || m != null && m == BlessedFor; + m_LootType == LootType.Blessed || ServerFeatureFlags.InsuranceEnabled && Insured || m != null && m == BlessedFor; public virtual bool CheckNewbied() => m_LootType == LootType.Newbied; public virtual bool IsStandardLoot() => - (!Mobile.InsuranceEnabled || !Insured) && BlessedFor == null && m_LootType == LootType.Regular; + (!ServerFeatureFlags.InsuranceEnabled || !Insured) && BlessedFor == null && m_LootType == LootType.Regular; public override string ToString() => $"{Serial} \"{GetType().Name}\""; diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 3e19548c6..34daaf796 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -953,8 +953,6 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro public static TimeSpan AutoManifestTimeout { get; set; } = TimeSpan.FromSeconds(5.0); - public static bool InsuranceEnabled { get; set; } - public static int ActionDelay { get; set; } = 500; public static VisibleDamageType VisibleDamageType { get; set; } diff --git a/Projects/UOContent/Configuration/ExpansionConfiguration.cs b/Projects/UOContent/Configuration/ExpansionConfiguration.cs index 3ae24bbca..50fec1dc3 100644 --- a/Projects/UOContent/Configuration/ExpansionConfiguration.cs +++ b/Projects/UOContent/Configuration/ExpansionConfiguration.cs @@ -7,7 +7,6 @@ namespace Server { public static void Configure() { - Mobile.InsuranceEnabled = ServerConfiguration.GetSetting("insurance.enable", Core.AOS); ObjectPropertyList.Enabled = ServerConfiguration.GetSetting("opl.enable", Core.AOS); var visibleDamage = ServerConfiguration.GetSetting("visibleDamage", Core.AOS); Mobile.VisibleDamageType = visibleDamage ? VisibleDamageType.Related : VisibleDamageType.None; diff --git a/Projects/UOContent/Engines/FeatureFlags/FeatureFlagManager.cs b/Projects/UOContent/Engines/FeatureFlags/FeatureFlagManager.cs index 0402ad9a1..e599aeeb6 100644 --- a/Projects/UOContent/Engines/FeatureFlags/FeatureFlagManager.cs +++ b/Projects/UOContent/Engines/FeatureFlags/FeatureFlagManager.cs @@ -962,20 +962,21 @@ public static class FeatureFlagManager _ = key.ToLowerInvariant() switch { // Server project flags - "player_trading" => ServerFeatureFlags.PlayerTrading = enabled, - "pvp_combat" => ServerFeatureFlags.PvPCombat = enabled, - "bank_access" => ServerFeatureFlags.BankAccess = enabled, - "speedhack_detection" => ServerFeatureFlags.SpeedhackDetection = enabled, + "player_trading" => ServerFeatureFlags.PlayerTrading = enabled, + "pvp_combat" => ServerFeatureFlags.PvPCombat = enabled, + "bank_access" => ServerFeatureFlags.BankAccess = enabled, + "speedhack_detection" => ServerFeatureFlags.SpeedhackDetection = enabled, + "insurance" => ServerFeatureFlags.InsuranceEnabled = enabled, // UOContent flags - "vendor_purchase" => ContentFeatureFlags.VendorPurchase = enabled, - "vendor_sell" => ContentFeatureFlags.VendorSell = enabled, - "player_vendors" => ContentFeatureFlags.PlayerVendors = enabled, - "house_placement" => ContentFeatureFlags.HousePlacement = enabled, - "boat_placement" => ContentFeatureFlags.BoatPlacement = enabled, - "bulk_orders" => ContentFeatureFlags.BulkOrders = enabled, - "passive_detect_hidden" => ContentFeatureFlags.PassiveDetectHidden = enabled, - "young_player_system" => ContentFeatureFlags.YoungPlayerSystem = enabled, + "vendor_purchase" => ContentFeatureFlags.VendorPurchase = enabled, + "vendor_sell" => ContentFeatureFlags.VendorSell = enabled, + "player_vendors" => ContentFeatureFlags.PlayerVendors = enabled, + "house_placement" => ContentFeatureFlags.HousePlacement = enabled, + "boat_placement" => ContentFeatureFlags.BoatPlacement = enabled, + "bulk_orders" => ContentFeatureFlags.BulkOrders = enabled, + "passive_detect_hidden" => ContentFeatureFlags.PassiveDetectHidden = enabled, + "young_player_system" => ContentFeatureFlags.YoungPlayerSystem = enabled, "bitmap_pathfinding_cache" => ContentFeatureFlags.BitmapPathfindingCache = enabled, }; } diff --git a/Projects/UOContent/Engines/Insurance/Gumps/CancelRenewInventoryInsuranceGump.cs b/Projects/UOContent/Engines/Insurance/Gumps/CancelRenewInventoryInsuranceGump.cs new file mode 100644 index 000000000..e5cad5e68 --- /dev/null +++ b/Projects/UOContent/Engines/Insurance/Gumps/CancelRenewInventoryInsuranceGump.cs @@ -0,0 +1,55 @@ +using Server.Mobiles; +using Server.Network; + +namespace Server.Gumps; + +public class CancelRenewInventoryInsuranceGump : StaticGump +{ + private readonly ItemInsuranceMenuGump _insuranceGump; + + public override bool Singleton => true; + + public CancelRenewInventoryInsuranceGump(ItemInsuranceMenuGump insuranceGump) : base(250, 200) => + _insuranceGump = insuranceGump; + + protected override void BuildLayout(ref StaticGumpBuilder builder) + { + builder.AddBackground(0, 0, 240, 142, 0x13BE); + builder.AddImageTiled(6, 6, 228, 100, 0xA40); + builder.AddImageTiled(6, 116, 228, 20, 0xA40); + builder.AddAlphaRegion(6, 6, 228, 142); + + // You are about to disable inventory insurance auto-renewal. + builder.AddHtmlLocalized(8, 8, 228, 100, 1071021, 0x7FFF); + + builder.AddButton(6, 116, 0xFB1, 0xFB2, 0); + builder.AddHtmlLocalized(40, 118, 450, 20, 1060051, 0x7FFF); // CANCEL + + builder.AddButton(114, 116, 0xFA5, 0xFA7, 1); + builder.AddHtmlLocalized(148, 118, 450, 20, 1071022, 0x7FFF); // DISABLE IT! + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + if (sender.Mobile is not PlayerMobile pm || !pm.CheckAlive()) + { + return; + } + + if (info.ButtonID == 1) + { + // You have cancelled automatically reinsuring all insured items upon death + pm.SendLocalizedMessage(1061075, "", 0x23); + pm.AutoRenewInsurance = false; + } + else + { + pm.SendLocalizedMessage(1042021); // Cancelled. + } + + if (_insuranceGump != null) + { + pm.SendGump(_insuranceGump); + } + } +} diff --git a/Projects/UOContent/Engines/Insurance/Gumps/ItemInsuranceMenuConfirmGump.cs b/Projects/UOContent/Engines/Insurance/Gumps/ItemInsuranceMenuConfirmGump.cs new file mode 100644 index 000000000..4fd597cf6 --- /dev/null +++ b/Projects/UOContent/Engines/Insurance/Gumps/ItemInsuranceMenuConfirmGump.cs @@ -0,0 +1,46 @@ +using Server.Mobiles; +using Server.Network; + +namespace Server.Gumps; + +public class ItemInsuranceMenuConfirmGump : StaticGump +{ + private readonly ItemInsuranceMenuGump _insuranceGump; + + public ItemInsuranceMenuConfirmGump(ItemInsuranceMenuGump insuranceGump) : base(250, 200) => + _insuranceGump = insuranceGump; + + protected override void BuildLayout(ref StaticGumpBuilder builder) + { + builder.AddBackground(0, 0, 240, 142, 0x13BE); + builder.AddImageTiled(6, 6, 228, 100, 0xA40); + builder.AddImageTiled(6, 116, 228, 20, 0xA40); + builder.AddAlphaRegion(6, 6, 228, 142); + + builder.AddHtmlLocalized(8, 8, 228, 100, 1114300, 0x7FFF); // Do you wish to insure all newly selected items? + + builder.AddButton(6, 116, 0xFB1, 0xFB2, 0); + builder.AddHtmlLocalized(40, 118, 450, 20, 1060051, 0x7FFF); // CANCEL + + builder.AddButton(114, 116, 0xFA5, 0xFA7, 1); + builder.AddHtmlLocalized(148, 118, 450, 20, 1073996, 0x7FFF); // ACCEPT + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + if (sender.Mobile is not PlayerMobile pm || !pm.CheckAlive()) + { + return; + } + + if (info.ButtonID == 1) + { + _insuranceGump.ToggleSelected(); + } + else + { + pm.SendLocalizedMessage(1042021); // Cancelled. + pm.SendGump(_insuranceGump); + } + } +} diff --git a/Projects/UOContent/Engines/Insurance/Gumps/ItemInsuranceMenuGump.cs b/Projects/UOContent/Engines/Insurance/Gumps/ItemInsuranceMenuGump.cs new file mode 100644 index 000000000..2d17f63ab --- /dev/null +++ b/Projects/UOContent/Engines/Insurance/Gumps/ItemInsuranceMenuGump.cs @@ -0,0 +1,207 @@ +using Server.Engines.Insurance; +using Server.Mobiles; +using Server.Network; + +namespace Server.Gumps; + +public class ItemInsuranceMenuGump : DynamicGump +{ + private readonly PlayerMobile _from; + private readonly bool[] _insure; + private readonly Item[] _items; + private int _page; + + public override bool Singleton => true; + + public ItemInsuranceMenuGump(PlayerMobile from, Item[] items) : base(25, 50) + { + _from = from; + _items = items; + _insure = new bool[items.Length]; + + for (var i = 0; i < items.Length; ++i) + { + _insure[i] = items[i].Insured; + } + } + + protected override void BuildLayout(ref DynamicGumpBuilder builder) + { + builder.AddPage(); + + builder.AddBackground(0, 0, 520, 510, 0x13BE); + builder.AddImageTiled(10, 10, 500, 30, 0xA40); + builder.AddImageTiled(10, 50, 500, 355, 0xA40); + builder.AddImageTiled(10, 415, 500, 80, 0xA40); + builder.AddAlphaRegion(10, 10, 500, 485); + + builder.AddButton(15, 470, 0xFB1, 0xFB2, 0); + builder.AddHtmlLocalized(50, 472, 80, 20, 1011012, 0x7FFF); // CANCEL + + if (_from.AutoRenewInsurance) + { + builder.AddButton(360, 10, 9723, 9724, 1); + } + else + { + builder.AddButton(360, 10, 9720, 9722, 1); + } + + builder.AddHtmlLocalized(395, 14, 105, 20, 1114122, 0x7FFF); // AUTO REINSURE + + builder.AddButton(395, 470, 0xFA5, 0xFA6, 2); + builder.AddHtmlLocalized(430, 472, 50, 20, 1006044, 0x7FFF); // OK + + builder.AddHtmlLocalized(10, 14, 150, 20, 1114121, 0x7FFF); //
ITEM INSURANCE MENU
+ + builder.AddHtmlLocalized(45, 54, 70, 20, 1062214, 0x7FFF); // Item + builder.AddHtmlLocalized(250, 54, 70, 20, 1061038, 0x7FFF); // Cost + builder.AddHtmlLocalized(400, 54, 70, 20, 1114311, 0x7FFF); // Insured + + var balance = Banker.GetBalance(_from); + var cost = 0; + + for (var i = 0; i < _items.Length; ++i) + { + if (_insure[i]) + { + cost += Insurance.GetInsuranceCost(_from, _items[i]); + } + } + + builder.AddHtmlLocalized(15, 420, 300, 20, 1114310, 0x7FFF); // GOLD AVAILABLE: + builder.AddLabel(215, 420, 0x481, $"{balance}"); + builder.AddHtmlLocalized(15, 435, 300, 20, 1114123, 0x7FFF); // TOTAL COST OF INSURANCE: + builder.AddLabel(215, 435, 0x481, $"{cost}"); + + if (cost != 0) + { + builder.AddHtmlLocalized(15, 450, 300, 20, 1114125, 0x7FFF); // NUMBER OF DEATHS PAYABLE: + builder.AddLabel(215, 450, 0x481, $"{balance / cost}"); + } + + for (int i = _page * 4, y = 72; i < (_page + 1) * 4 && i < _items.Length; ++i, y += 75) + { + var item = _items[i]; + var b = ItemBounds.Bounds[item.ItemID]; + + builder.AddImageTiledButton( + 40, + y, + 0x918, + 0x918, + 0, + GumpButtonType.Page, + 0, + item.ItemID, + item.Hue, + 40 - b.Width / 2 - b.X, + 30 - b.Height / 2 - b.Y + ); + builder.AddItemProperty(item.Serial); + + if (_insure[i]) + { + builder.AddButton(400, y, 9723, 9724, 100 + i); + builder.AddLabel(250, y, 0x481, $"{Insurance.GetInsuranceCost(_from, item)}"); + } + else + { + builder.AddButton(400, y, 9720, 9722, 100 + i); + builder.AddLabel(250, y, 0x66C, $"{Insurance.GetInsuranceCost(_from, item)}"); + } + } + + if (_page >= 1) + { + builder.AddButton(15, 380, 0xFAE, 0xFAF, 3); + builder.AddHtmlLocalized(50, 380, 450, 20, 1044044, 0x7FFF); // PREV PAGE + } + + if ((_page + 1) * 4 < _items.Length) + { + builder.AddButton(400, 380, 0xFA5, 0xFA7, 4); + builder.AddHtmlLocalized(435, 380, 70, 20, 1044045, 0x7FFF); // NEXT PAGE + } + } + + public override void OnResponse(NetState sender, in RelayInfo info) + { + if (info.ButtonID == 0 || !_from.CheckAlive()) + { + return; + } + + switch (info.ButtonID) + { + case 1: // Auto Reinsure + { + if (_from.AutoRenewInsurance) + { + _from.SendGump(new CancelRenewInventoryInsuranceGump(this)); + } + else + { + Insurance.AutoRenewInventoryInsurance(_from); + _from.SendGump(this); + } + + break; + } + case 2: // OK + { + _from.SendGump(new ItemInsuranceMenuConfirmGump(this)); + + break; + } + case 3: // Prev + { + if (_page >= 1) + { + _page--; + _from.SendGump(this); + } + + break; + } + case 4: // Next + { + if ((_page + 1) * 4 < _items.Length) + { + _page++; + _from.SendGump(this); + } + + break; + } + default: + { + var idx = info.ButtonID - 100; + + if (idx >= 0 && idx < _items.Length) + { + _insure[idx] = !_insure[idx]; + } + + _from.SendGump(this); + + break; + } + } + } + + public void ToggleSelected() + { + var items = _items; + var insure = _insure; + for (var i = 0; i < items.Length; ++i) + { + var item = items[i]; + + if (item.Insured != insure[i]) + { + Insurance.ToggleItemInsurance(_from, item, false); + } + } + } +} diff --git a/Projects/UOContent/Engines/Insurance/Insurance.cs b/Projects/UOContent/Engines/Insurance/Insurance.cs new file mode 100644 index 000000000..8ba3985e5 --- /dev/null +++ b/Projects/UOContent/Engines/Insurance/Insurance.cs @@ -0,0 +1,323 @@ +using System.Collections.Generic; +using Server.Collections; +using Server.Factions; +using Server.Gumps; +using Server.Items; +using Server.Mobiles; +using Server.Targeting; + +namespace Server.Engines.Insurance; + +public static class Insurance +{ + public static bool Enabled => ServerFeatureFlags.InsuranceEnabled; + private static readonly Dictionary _insuranceContexts = []; + + public static void Configure() + { + // Legacy, but not expected to be used anymore in favor of the feature flag system + ServerFeatureFlags.InsuranceEnabled = ServerConfiguration.GetSetting("insurance.enable", Core.AOS); + } + + public static int GetInsuranceCost(Mobile from, Item item) => 600; + + public static void CheckInsuranceBeforeDeath(Mobile from) + { + if (!Enabled) + { + return; + } + + var recentDamager = from.FindMostRecentDamager(false); + if (recentDamager == null) + { + return; + } + + if (recentDamager is BaseCreature creature) + { + recentDamager = creature.GetMaster(); + } + + if (recentDamager != from && recentDamager is PlayerMobile insuranceReward) + { + _insuranceContexts[from] = new InsuranceContext(insuranceReward); + } + } + + public static void CheckInsuranceOnDeath(Mobile from) + { + if (!Enabled || !_insuranceContexts.Remove(from, out var context)) + { + return; + } + + if (context.MissedAutoRenewal) + { + from.SendLocalizedMessage(1061115); // You do not have the gold to automatically reinsure all your items. + } + + if (context.InsuranceReward != null && context.InsuranceBonus > 0 && + Banker.Deposit(context.InsuranceReward, context.InsuranceBonus)) + { + // ~1_AMOUNT~ gold has been deposited into your bank box. + context.InsuranceReward.SendLocalizedMessage(1060397, $"{context.InsuranceBonus}"); + } + } + + public static bool CheckItemInsuranceOnDeath(PlayerMobile from, Item item) + { + if (!Enabled || !item.Insured) + { + return false; + } + + if (from.DuelContext?.Registered == true && from.DuelContext.Started && from.DuelPlayer?.Eliminated != true) + { + return true; + } + + if (!_insuranceContexts.TryGetValue(from, out var context)) + { + _insuranceContexts[from] = context = new InsuranceContext(); + } + + if (from.AutoRenewInsurance) + { + var cost = Insurance.GetInsuranceCost(from, item); + + if (context.InsuranceReward != null) + { + cost /= 2; + } + + if (Banker.Withdraw(from, cost)) + { + item.PaidInsurance = true; + // ~1_AMOUNT~ gold has been withdrawn from your bank box. + from.SendLocalizedMessage(1060398, $"{cost}"); + } + else + { + // TODO: Should this spam? + // from.SendLocalizedMessage(1061079, "", 0x23); // You lack the funds to purchase the insurance + item.PaidInsurance = false; + item.Insured = false; + context.MissedAutoRenewal = true; + } + } + else + { + item.PaidInsurance = false; + item.Insured = false; + } + + context.InsuranceBonus += 300; + return true; + } + + public static bool CanInsure(Mobile from, Item item) + { + if (!Enabled) + { + return false; + } + + if (item is Container && item is not BaseQuiver || item is BagOfSending or KeyRing or PotionKeg or Sigil) + { + return false; + } + + if (item.Stackable) + { + return false; + } + + if (item.LootType == LootType.Cursed) + { + return false; + } + + if (item.ItemID == 0x204E) // death shroud + { + return false; + } + + if (item.Layer == Layer.Mount) + { + return false; + } + + return item.LootType != LootType.Blessed && item.LootType != LootType.Newbied && item.BlessedFor != from; + } + + public static void ToggleItemInsurance(Mobile from) + { + if (!from.CheckAlive()) + { + return; + } + + from.BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance); + from.SendLocalizedMessage(1060868); // Target the item you wish to toggle insurance status on to cancel + } + + public static void ToggleItemInsurance(Mobile from, object obj) + { + if (!from.CheckAlive()) + { + return; + } + + ToggleItemInsurance(from, obj as Item, true); + } + + public static void ToggleItemInsurance(Mobile from, Item item, bool target) + { + if (item?.IsChildOf(from) != true) + { + if (target) + { + from.BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance); + } + + // You can only insure items that you have equipped or that are in your backpack + from.SendLocalizedMessage(1060871, "", 0x23); + } + else if (item.Insured) + { + item.Insured = false; + + from.SendLocalizedMessage(1060874, "", 0x35); // You cancel the insurance on the item + + if (target) + { + from.BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance); + // Target the item you wish to toggle insurance status on to cancel + from.SendLocalizedMessage(1060868, "", 0x23); + } + } + else if (!CanInsure(from, item)) + { + if (target) + { + from.BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance); + } + + from.SendLocalizedMessage(1060869, "", 0x23); // You cannot insure that + } + else + { + if (!item.PaidInsurance) + { + var cost = GetInsuranceCost(from, item); + + if (Banker.Withdraw(from, cost)) + { + // ~1_AMOUNT~ gold has been withdrawn from your bank box. + from.SendLocalizedMessage(1060398, $"{cost}"); + item.PaidInsurance = true; + } + else + { + from.SendLocalizedMessage(1061079, "", 0x23); // You lack the funds to purchase the insurance + return; + } + } + + item.Insured = true; + + from.SendLocalizedMessage(1060873, "", 0x23); // You have insured the item + + if (target) + { + from.BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance); + // Target the item you wish to toggle insurance status on to cancel + from.SendLocalizedMessage(1060868, "", 0x23); + } + } + } + + public static void AutoRenewInventoryInsurance(Mobile from) + { + if (!from.CheckAlive()) + { + return; + } + + // You have selected to automatically reinsure all insured items upon death + from.SendLocalizedMessage(1060881, "", 0x23); + (from as PlayerMobile)?.AutoRenewInsurance = true; + } + + public static void CancelRenewInventoryInsurance(Mobile from) + { + if (!from.CheckAlive()) + { + return; + } + + if (Core.SE) + { + from.SendGump(new CancelRenewInventoryInsuranceGump(null)); + } + else + { + // You have cancelled automatically reinsuring all insured items upon death + from.SendLocalizedMessage(1061075, "", 0x23); + (from as PlayerMobile)?.AutoRenewInsurance = false; + } + } + + public static void OpenItemInsuranceMenu(Mobile from) + { + if (!from.CheckAlive() || from.NetState == null) + { + return; + } + + using var queue = PooledRefQueue.Create(128); + + foreach (var item in from.Items) + { + if (DisplayInItemInsuranceGump(from, item)) + { + queue.Enqueue(item); + } + } + + var pack = from.Backpack; + + if (pack != null) + { + foreach (var item in pack.FindItems()) + { + if (DisplayInItemInsuranceGump(from, item)) + { + queue.Enqueue(item); + } + } + } + + if (queue.Count == 0) + { + // None of your current items meet the requirements for insurance. + from.SendLocalizedMessage(1114915, "", 0x35); + } + else if (from is PlayerMobile pm) + { + // TODO: Investigate item sorting + from.SendGump(new ItemInsuranceMenuGump(pm, queue.ToArray())); + } + } + + private static bool DisplayInItemInsuranceGump(Mobile from, Item item) => + (item.Visible || from.AccessLevel >= AccessLevel.GameMaster) && (item.Insured || CanInsure(from, item)); + + private class InsuranceContext(PlayerMobile insuranceReward = null) + { + public PlayerMobile InsuranceReward = insuranceReward; + public int InsuranceBonus { get; set; } + public bool MissedAutoRenewal { get; set; } + } +} diff --git a/Projects/UOContent/Items/Deeds/ClothingBlessDeed.cs b/Projects/UOContent/Items/Deeds/ClothingBlessDeed.cs index 20f8f9fc6..25860f542 100644 --- a/Projects/UOContent/Items/Deeds/ClothingBlessDeed.cs +++ b/Projects/UOContent/Items/Deeds/ClothingBlessDeed.cs @@ -1,4 +1,5 @@ using ModernUO.Serialization; +using Server.Engines.Insurance; using Server.Targeting; namespace Server.Items; @@ -25,7 +26,7 @@ public class ClothingBlessTarget : Target // Create our targeting class (which w } // Check if its already newbied (blessed) - if (item.LootType == LootType.Blessed || item.BlessedFor == from || Mobile.InsuranceEnabled && item.Insured) + if (item.LootType == LootType.Blessed || item.BlessedFor == from || Insurance.Enabled && item.Insured) { from.SendLocalizedMessage(1045113); // That item is already blessed } diff --git a/Projects/UOContent/Mobiles/Abilities/DestroyEquipment.cs b/Projects/UOContent/Mobiles/Abilities/DestroyEquipment.cs index c74698d7c..ff7937ded 100644 --- a/Projects/UOContent/Mobiles/Abilities/DestroyEquipment.cs +++ b/Projects/UOContent/Mobiles/Abilities/DestroyEquipment.cs @@ -1,4 +1,5 @@ using Server.Collections; +using Server.Engines.Insurance; using Server.Items; namespace Server.Mobiles; @@ -23,7 +24,7 @@ public class DestroyEquipment : MonsterAbilitySingleTarget continue; } - if (Mobile.InsuranceEnabled && item.Insured) + if (Insurance.Enabled && item.Insured) { continue; } diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 0a81bb846..83c0e4cca 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -11,6 +11,7 @@ using Server.Engines.CannedEvil; using Server.Engines.ConPVP; using Server.Engines.Craft; using Server.Engines.Help; +using Server.Engines.Insurance; using Server.Engines.MLQuests; using Server.Engines.MLQuests.Gumps; using Server.Engines.PartySystem; @@ -161,7 +162,7 @@ namespace Server.Mobiles /* * a value of zero means, that the mobile is not executing the spell. Otherwise, * the value should match the BaseMana required - */ + */ private RankDefinition m_GuildRank; @@ -169,9 +170,6 @@ namespace Server.Mobiles public DateTime _honorTime; - private Mobile m_InsuranceAward; - private int m_InsuranceBonus; - private int m_LastGlobalLight = -1, m_LastPersonalLight = -1; private bool m_LastProtectedMessage; @@ -190,9 +188,6 @@ namespace Server.Mobiles private bool m_NoDeltaRecursion; - // number of items that could not be automatically reinsured because gold in bank was not enough - private int m_NonAutoreinsuredItems; - private DateTime m_SavagePaintExpiration; private DateTime[] m_StuckMenuUses; @@ -908,17 +903,17 @@ namespace Server.Mobiles { Direction.North => itemIDs[0], Direction.South => itemIDs[0], - Direction.East => itemIDs[1], - Direction.West => itemIDs[1], - _ => item.ItemID + Direction.East => itemIDs[1], + Direction.West => itemIDs[1], + _ => item.ItemID }, 4 => dir switch { Direction.South => itemIDs[0], - Direction.East => itemIDs[1], + Direction.East => itemIDs[1], Direction.North => itemIDs[2], - Direction.West => itemIDs[3], - _ => item.ItemID + Direction.West => itemIDs[3], + _ => item.ItemID }, _ => item.ItemID }; @@ -989,7 +984,7 @@ namespace Server.Mobiles } if (skillId == 35) - // AnimalTaming.DeferredTarget = true; + // AnimalTaming.DeferredTarget = true; { AnimalTaming.DisableMessage = false; } @@ -1845,7 +1840,7 @@ namespace Server.Mobiles // moving, not teleporting var zDrop = Location.Z - loc.Z; - if (zDrop > 20) // we fell more than one story + if (zDrop > 20) // we fell more than one story { Hits -= zDrop / 20 * 10 - 5; // deal some damage; does not kill, disrupt, etc } @@ -1874,7 +1869,7 @@ namespace Server.Mobiles if (Alive) { - if (InsuranceEnabled) + if (Insurance.Enabled) { if (Core.SA) { @@ -2002,7 +1997,7 @@ namespace Server.Mobiles { var house = BaseHouse.FindHouseAt(this); - if (CheckAlive() && house?.IsOwner(this) == true && house.InternalizedVendors.Count > 0 && NetState is NetState { } ns) + if (CheckAlive() && house?.IsOwner(this) == true && house.InternalizedVendors.Count > 0 && NetState != null) { ReclaimVendorGump.DisplayTo(this, house); } @@ -2310,7 +2305,8 @@ namespace Server.Mobiles pm.DuelPlayer.Eliminated) || base.OnMoveOver(m); public override bool CheckShove(Mobile shoved) => - IgnoreMobiles || shoved.IgnoreMobiles || TransformationSpellHelper.UnderTransformation(shoved, typeof(WraithFormSpell)) || + IgnoreMobiles || shoved.IgnoreMobiles || + TransformationSpellHelper.UnderTransformation(shoved, typeof(WraithFormSpell)) || base.CheckShove(shoved); protected override void OnMapChange(Map oldMap) @@ -2401,7 +2397,8 @@ namespace Server.Mobiles [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool FindItems_Callback(Item item) => - !item.Deleted && (item.LootType == LootType.Blessed || item.Insured) && Backpack != item.Parent; + !item.Deleted && Backpack != item.Parent && + (item.LootType == LootType.Blessed || ServerFeatureFlags.InsuranceEnabled && item.Insured); public override bool OnBeforeDeath() { @@ -2422,30 +2419,8 @@ namespace Server.Mobiles } } - EquipSnapshot = new List(Items); - - m_NonAutoreinsuredItems = 0; - m_InsuranceAward = FindMostRecentDamager(false); - - if (m_InsuranceAward is BaseCreature creature) - { - var master = creature.GetMaster(); - - if (master != null) - { - m_InsuranceAward = master; - } - } - - if (m_InsuranceAward != null && (!m_InsuranceAward.Player || m_InsuranceAward == this)) - { - m_InsuranceAward = null; - } - - if (m_InsuranceAward is PlayerMobile mobile) - { - mobile.m_InsuranceBonus = 0; - } + EquipSnapshot = [..Items]; + Insurance.CheckInsuranceBeforeDeath(this); ReceivedHonorContext?.OnTargetKilled(); SentHonorContext?.OnSourceKilled(); @@ -2453,56 +2428,6 @@ namespace Server.Mobiles return base.OnBeforeDeath(); } - private bool CheckInsuranceOnDeath(Item item) - { - if (!InsuranceEnabled || !item.Insured) - { - return false; - } - - if (DuelContext?.Registered == true && DuelContext.Started && - m_DuelPlayer?.Eliminated != true) - { - return true; - } - - if (AutoRenewInsurance) - { - var cost = GetInsuranceCost(item); - - if (m_InsuranceAward != null) - { - cost /= 2; - } - - if (Banker.Withdraw(this, cost)) - { - item.PaidInsurance = true; - // ~1_AMOUNT~ gold has been withdrawn from your bank box. - SendLocalizedMessage(1060398, cost.ToString()); - } - else - { - SendLocalizedMessage(1061079, "", 0x23); // You lack the funds to purchase the insurance - item.PaidInsurance = false; - item.Insured = false; - m_NonAutoreinsuredItems++; - } - } - else - { - item.PaidInsurance = false; - item.Insured = false; - } - - if (m_InsuranceAward is PlayerMobile insurancePm && Banker.Deposit(m_InsuranceAward, 300)) - { - insurancePm.m_InsuranceBonus += 300; - } - - return true; - } - public override DeathMoveResult GetParentMoveResultFor(Item item) { // It seems all items are unmarked on death, even blessed/insured ones @@ -2511,7 +2436,7 @@ namespace Server.Mobiles item.QuestItem = false; } - if (CheckInsuranceOnDeath(item)) + if (Insurance.CheckItemInsuranceOnDeath(this, item)) { return DeathMoveResult.MoveToBackpack; } @@ -2534,7 +2459,7 @@ namespace Server.Mobiles item.QuestItem = false; } - if (CheckInsuranceOnDeath(item)) + if (Insurance.CheckItemInsuranceOnDeath(this, item)) { return DeathMoveResult.MoveToBackpack; } @@ -2554,11 +2479,6 @@ namespace Server.Mobiles public override void OnDeath(Container c) { - if (m_NonAutoreinsuredItems > 0) - { - SendLocalizedMessage(1061115); - } - base.OnDeath(c); EquipSnapshot = null; @@ -2627,11 +2547,7 @@ namespace Server.Mobiles } } - if (m_InsuranceAward is PlayerMobile insurancePm && insurancePm.m_InsuranceBonus > 0) - { - // ~1_AMOUNT~ gold has been deposited into your bank box. - insurancePm.SendLocalizedMessage(1060397, insurancePm.m_InsuranceBonus.ToString()); - } + Insurance.CheckInsuranceOnDeath(this); var killer = FindMostRecentDamager(true); @@ -2911,7 +2827,8 @@ namespace Server.Mobiles for (var i = 0; i < recipeCount; i++) { var r = reader.ReadInt(); - if (version > 33 || reader.ReadBool()) // Don't add in recipes which we haven't gotten or have been removed + // Don't add in recipes which we haven't gotten or have been removed + if (version > 33 || reader.ReadBool()) { _acquiredRecipes.Add(r); } @@ -2926,6 +2843,7 @@ namespace Server.Mobiles { reader.ReadDeltaTime(); // LastHonorLoss - Not even used } + goto case 23; } case 23: @@ -3021,6 +2939,7 @@ namespace Server.Mobiles { virtues.LastCompassionLoss = reader.ReadDeltaTime(); } + goto case 14; } case 14: @@ -3710,213 +3629,13 @@ namespace Server.Mobiles AutoStabled = null; } - private static int GetInsuranceCost(Item item) => 600; + private void ToggleItemInsurance() => Insurance.ToggleItemInsurance(this); - private void ToggleItemInsurance() - { - if (!CheckAlive()) - { - return; - } + private void OpenItemInsuranceMenu() => Insurance.OpenItemInsuranceMenu(this); - BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback); - SendLocalizedMessage(1060868); // Target the item you wish to toggle insurance status on to cancel - } + private void CancelRenewInventoryInsurance() => Insurance.CancelRenewInventoryInsurance(this); - private bool CanInsure(Item item) - { - if (item is Container && item is not BaseQuiver || item is BagOfSending or KeyRing or PotionKeg or Sigil) - { - return false; - } - - if (item.Stackable) - { - return false; - } - - if (item.LootType == LootType.Cursed) - { - return false; - } - - if (item.ItemID == 0x204E) // death shroud - { - return false; - } - - if (item.Layer == Layer.Mount) - { - return false; - } - - return item.LootType != LootType.Blessed && item.LootType != LootType.Newbied && item.BlessedFor != this; - } - - private void ToggleItemInsurance_Callback(Mobile from, object obj) - { - if (!CheckAlive()) - { - return; - } - - ToggleItemInsurance_Callback(from, obj as Item, true); - } - - private void ToggleItemInsurance_Callback(Mobile from, Item item, bool target) - { - if (item?.IsChildOf(this) != true) - { - if (target) - { - BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback); - } - - SendLocalizedMessage( - 1060871, - "", - 0x23 - ); // You can only insure items that you have equipped or that are in your backpack - } - else if (item.Insured) - { - item.Insured = false; - - SendLocalizedMessage(1060874, "", 0x35); // You cancel the insurance on the item - - if (target) - { - BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback); - SendLocalizedMessage( - 1060868, - "", - 0x23 - ); // Target the item you wish to toggle insurance status on to cancel - } - } - else if (!CanInsure(item)) - { - if (target) - { - BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback); - } - - SendLocalizedMessage(1060869, "", 0x23); // You cannot insure that - } - else - { - if (!item.PaidInsurance) - { - var cost = GetInsuranceCost(item); - - if (Banker.Withdraw(from, cost)) - { - SendLocalizedMessage( - 1060398, - cost.ToString() - ); // ~1_AMOUNT~ gold has been withdrawn from your bank box. - item.PaidInsurance = true; - } - else - { - SendLocalizedMessage(1061079, "", 0x23); // You lack the funds to purchase the insurance - return; - } - } - - item.Insured = true; - - SendLocalizedMessage(1060873, "", 0x23); // You have insured the item - - if (target) - { - BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback); - SendLocalizedMessage( - 1060868, - "", - 0x23 - ); // Target the item you wish to toggle insurance status on to cancel - } - } - } - - private void AutoRenewInventoryInsurance() - { - if (!CheckAlive()) - { - return; - } - - // You have selected to automatically reinsure all insured items upon death - SendLocalizedMessage(1060881, "", 0x23); - AutoRenewInsurance = true; - } - - private void CancelRenewInventoryInsurance() - { - if (!CheckAlive()) - { - return; - } - - if (Core.SE) - { - NetState?.SendGump(new CancelRenewInventoryInsuranceGump(null)); - } - else - { - // You have cancelled automatically reinsuring all insured items upon death - SendLocalizedMessage(1061075, "", 0x23); - AutoRenewInsurance = false; - } - } - - private void OpenItemInsuranceMenu() - { - if (!CheckAlive()) - { - return; - } - - using var queue = PooledRefQueue.Create(128); - - foreach (var item in Items) - { - if (DisplayInItemInsuranceGump(item)) - { - queue.Enqueue(item); - } - } - - var pack = Backpack; - - if (pack != null) - { - foreach (var item in pack.FindItems()) - { - if (DisplayInItemInsuranceGump(item)) - { - queue.Enqueue(item); - } - } - } - - // TODO: Investigate item sorting - if (NetState != null) - { - if (queue.Count == 0) - { - SendLocalizedMessage(1114915, "", 0x35); // None of your current items meet the requirements for insurance. - } - else - { - NetState.SendGump(new ItemInsuranceMenuGump(this, queue.ToArray())); - } - } - } - - private bool DisplayInItemInsuranceGump(Item item) => (item.Visible || AccessLevel >= AccessLevel.GameMaster) && - (item.Insured || CanInsure(item)); + private void AutoRenewInventoryInsurance() => Insurance.AutoRenewInventoryInsurance(this); private void ToggleQuestItem() { @@ -4022,7 +3741,7 @@ namespace Server.Mobiles if (EvilOmenSpell.EndEffect(this)) { - poison = PoisonImpl.IncreaseLevel(poison); + poison = Poison.IncreaseLevel(poison); } var result = base.ApplyPoison(from, poison); @@ -4473,7 +4192,9 @@ namespace Server.Mobiles var offset = duration.TotalMilliseconds - roundedSeconds * TimeSpan.MillisecondsPerSecond; if (offset > 0) { - Timer.DelayCall(TimeSpan.FromMilliseconds(offset), () => + Timer.DelayCall( + TimeSpan.FromMilliseconds(offset), + () => { // They are still online, we still have the buff icon in the table, and it is the same buff icon if (NetState != null && m_BuffTable?.GetValueOrDefault(buffInfo.ID) == buffInfo) @@ -4598,295 +4319,5 @@ namespace Server.Mobiles m_Callback?.Invoke(); } } - - private class CancelRenewInventoryInsuranceGump : StaticGump - { - private readonly ItemInsuranceMenuGump _insuranceGump; - - public override bool Singleton => true; - - public CancelRenewInventoryInsuranceGump(ItemInsuranceMenuGump insuranceGump) : base(250, 200) => - _insuranceGump = insuranceGump; - - protected override void BuildLayout(ref StaticGumpBuilder builder) - { - builder.AddBackground(0, 0, 240, 142, 0x13BE); - builder.AddImageTiled(6, 6, 228, 100, 0xA40); - builder.AddImageTiled(6, 116, 228, 20, 0xA40); - builder.AddAlphaRegion(6, 6, 228, 142); - - // You are about to disable inventory insurance auto-renewal. - builder.AddHtmlLocalized(8, 8, 228, 100, 1071021, 0x7FFF); - - builder.AddButton(6, 116, 0xFB1, 0xFB2, 0); - builder.AddHtmlLocalized(40, 118, 450, 20, 1060051, 0x7FFF); // CANCEL - - builder.AddButton(114, 116, 0xFA5, 0xFA7, 1); - builder.AddHtmlLocalized(148, 118, 450, 20, 1071022, 0x7FFF); // DISABLE IT! - } - - public override void OnResponse(NetState sender, in RelayInfo info) - { - if (sender.Mobile is not PlayerMobile pm || !pm.CheckAlive()) - { - return; - } - - if (info.ButtonID == 1) - { - // You have cancelled automatically reinsuring all insured items upon death - pm.SendLocalizedMessage(1061075, "", 0x23); - pm.AutoRenewInsurance = false; - } - else - { - pm.SendLocalizedMessage(1042021); // Cancelled. - } - - if (_insuranceGump != null) - { - pm.SendGump(_insuranceGump); - } - } - } - - private class ItemInsuranceMenuGump : DynamicGump - { - private readonly PlayerMobile _from; - private readonly bool[] _insure; - private readonly Item[] _items; - private int _page; - - public override bool Singleton => true; - - public ItemInsuranceMenuGump(PlayerMobile from, Item[] items) : base(25, 50) - { - _from = from; - _items = items; - _insure = new bool[items.Length]; - - for (var i = 0; i < items.Length; ++i) - { - _insure[i] = items[i].Insured; - } - } - - protected override void BuildLayout(ref DynamicGumpBuilder builder) - { - builder.AddPage(); - - builder.AddBackground(0, 0, 520, 510, 0x13BE); - builder.AddImageTiled(10, 10, 500, 30, 0xA40); - builder.AddImageTiled(10, 50, 500, 355, 0xA40); - builder.AddImageTiled(10, 415, 500, 80, 0xA40); - builder.AddAlphaRegion(10, 10, 500, 485); - - builder.AddButton(15, 470, 0xFB1, 0xFB2, 0); - builder.AddHtmlLocalized(50, 472, 80, 20, 1011012, 0x7FFF); // CANCEL - - if (_from.AutoRenewInsurance) - { - builder.AddButton(360, 10, 9723, 9724, 1); - } - else - { - builder.AddButton(360, 10, 9720, 9722, 1); - } - - builder.AddHtmlLocalized(395, 14, 105, 20, 1114122, 0x7FFF); // AUTO REINSURE - - builder.AddButton(395, 470, 0xFA5, 0xFA6, 2); - builder.AddHtmlLocalized(430, 472, 50, 20, 1006044, 0x7FFF); // OK - - builder.AddHtmlLocalized(10, 14, 150, 20, 1114121, 0x7FFF); //
ITEM INSURANCE MENU
- - builder.AddHtmlLocalized(45, 54, 70, 20, 1062214, 0x7FFF); // Item - builder.AddHtmlLocalized(250, 54, 70, 20, 1061038, 0x7FFF); // Cost - builder.AddHtmlLocalized(400, 54, 70, 20, 1114311, 0x7FFF); // Insured - - var balance = Banker.GetBalance(_from); - var cost = 0; - - for (var i = 0; i < _items.Length; ++i) - { - if (_insure[i]) - { - cost += GetInsuranceCost(_items[i]); - } - } - - builder.AddHtmlLocalized(15, 420, 300, 20, 1114310, 0x7FFF); // GOLD AVAILABLE: - builder.AddLabel(215, 420, 0x481, balance.ToString()); - builder.AddHtmlLocalized(15, 435, 300, 20, 1114123, 0x7FFF); // TOTAL COST OF INSURANCE: - builder.AddLabel(215, 435, 0x481, cost.ToString()); - - if (cost != 0) - { - builder.AddHtmlLocalized(15, 450, 300, 20, 1114125, 0x7FFF); // NUMBER OF DEATHS PAYABLE: - builder.AddLabel(215, 450, 0x481, (balance / cost).ToString()); - } - - for (int i = _page * 4, y = 72; i < (_page + 1) * 4 && i < _items.Length; ++i, y += 75) - { - var item = _items[i]; - var b = ItemBounds.Bounds[item.ItemID]; - - builder.AddImageTiledButton( - 40, - y, - 0x918, - 0x918, - 0, - GumpButtonType.Page, - 0, - item.ItemID, - item.Hue, - 40 - b.Width / 2 - b.X, - 30 - b.Height / 2 - b.Y - ); - builder.AddItemProperty(item.Serial); - - if (_insure[i]) - { - builder.AddButton(400, y, 9723, 9724, 100 + i); - builder.AddLabel(250, y, 0x481, GetInsuranceCost(item).ToString()); - } - else - { - builder.AddButton(400, y, 9720, 9722, 100 + i); - builder.AddLabel(250, y, 0x66C, GetInsuranceCost(item).ToString()); - } - } - - if (_page >= 1) - { - builder.AddButton(15, 380, 0xFAE, 0xFAF, 3); - builder.AddHtmlLocalized(50, 380, 450, 20, 1044044, 0x7FFF); // PREV PAGE - } - - if ((_page + 1) * 4 < _items.Length) - { - builder.AddButton(400, 380, 0xFA5, 0xFA7, 4); - builder.AddHtmlLocalized(435, 380, 70, 20, 1044045, 0x7FFF); // NEXT PAGE - } - } - - public override void OnResponse(NetState sender, in RelayInfo info) - { - if (info.ButtonID == 0 || !_from.CheckAlive()) - { - return; - } - - switch (info.ButtonID) - { - case 1: // Auto Reinsure - { - if (_from.AutoRenewInsurance) - { - _from.SendGump(new CancelRenewInventoryInsuranceGump(this)); - } - else - { - _from.AutoRenewInventoryInsurance(); - _from.SendGump(this); - } - - break; - } - case 2: // OK - { - _from.SendGump(new ItemInsuranceMenuConfirmGump(this)); - - break; - } - case 3: // Prev - { - if (_page >= 1) - { - _page--; - _from.SendGump(this); - } - - break; - } - case 4: // Next - { - if ((_page + 1) * 4 < _items.Length) - { - _page++; - _from.SendGump(this); - } - - break; - } - default: - { - var idx = info.ButtonID - 100; - - if (idx >= 0 && idx < _items.Length) - { - _insure[idx] = !_insure[idx]; - } - - _from.SendGump(this); - - break; - } - } - } - - private class ItemInsuranceMenuConfirmGump : StaticGump - { - private readonly ItemInsuranceMenuGump _parentGump; - - public ItemInsuranceMenuConfirmGump(ItemInsuranceMenuGump parentGump) : base(250, 200) => - _parentGump = parentGump; - - protected override void BuildLayout(ref StaticGumpBuilder builder) - { - builder.AddBackground(0, 0, 240, 142, 0x13BE); - builder.AddImageTiled(6, 6, 228, 100, 0xA40); - builder.AddImageTiled(6, 116, 228, 20, 0xA40); - builder.AddAlphaRegion(6, 6, 228, 142); - - builder.AddHtmlLocalized(8, 8, 228, 100, 1114300, 0x7FFF); // Do you wish to insure all newly selected items? - - builder.AddButton(6, 116, 0xFB1, 0xFB2, 0); - builder.AddHtmlLocalized(40, 118, 450, 20, 1060051, 0x7FFF); // CANCEL - - builder.AddButton(114, 116, 0xFA5, 0xFA7, 1); - builder.AddHtmlLocalized(148, 118, 450, 20, 1073996, 0x7FFF); // ACCEPT - } - - public override void OnResponse(NetState sender, in RelayInfo info) - { - if (sender.Mobile is not PlayerMobile pm || !pm.CheckAlive()) - { - return; - } - - if (info.ButtonID == 1) - { - var items = _parentGump._items; - var insure = _parentGump._insure; - for (var i = 0; i < items.Length; ++i) - { - var item = items[i]; - - if (item.Insured != insure[i]) - { - pm.ToggleItemInsurance_Callback(pm, item, false); - } - } - } - else - { - pm.SendLocalizedMessage(1042021); // Cancelled. - pm.SendGump(_parentGump); - } - } - } - } } }