diff --git a/Projects/Server/Attributes.cs b/Projects/Server/Attributes.cs index 84ba2fc12..daab18e7e 100644 --- a/Projects/Server/Attributes.cs +++ b/Projects/Server/Attributes.cs @@ -21,19 +21,13 @@ using ModernUO.Serialization; namespace Server; [AttributeUsage(AttributeTargets.Property)] -public class HueAttribute : Attribute -{ -} +public class HueAttribute : Attribute; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] -public class PropertyObjectAttribute : Attribute -{ -} +public class PropertyObjectAttribute : Attribute; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] -public class NoSortAttribute : Attribute -{ -} +public class NoSortAttribute : Attribute; [AttributeUsage(AttributeTargets.Method)] public class CallPriorityAttribute : Attribute @@ -193,3 +187,9 @@ public class SerializedCommandPropertyAttribute : SerializedPropertyAttrAttribut public bool ReadOnly { get; } public bool CanModify { get; } } + +[AttributeUsage(AttributeTargets.Property)] +public class IgnoreDupeAttribute : Attribute; + +[AttributeUsage(AttributeTargets.Field)] +public class SerializedIgnoreDupeAttribute : SerializedPropertyAttrAttribute; diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs index 29370d183..1a793cc49 100644 --- a/Projects/Server/Guild.cs +++ b/Projects/Server/Guild.cs @@ -44,14 +44,18 @@ public abstract class BaseGuild : ISerializable public bool Deleted => Disbanded; + [IgnoreDupe] [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } + [IgnoreDupe] [CommandProperty(AccessLevel.GameMaster, readOnly: true)] public DateTime Created { get; set; } = Core.Now; + [IgnoreDupe] public long SavePosition { get; set; } = -1; + [IgnoreDupe] public BufferWriter SaveBuffer { get; set; } public abstract void Serialize(IGenericWriter writer); diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 82f0c0ad9..e6d0cd771 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Runtime.CompilerServices; using Server.Collections; using Server.ContextMenus; @@ -254,8 +255,13 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt } // Sectors + [IgnoreDupe] public Item Next { get; set; } + + [IgnoreDupe] public Item Previous { get; set; } + + [IgnoreDupe] public bool OnLinkList { get; set; } /// @@ -586,6 +592,7 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt } // Note: Setting the parent via command/props causes problems. + [IgnoreDupe] [CommandProperty(AccessLevel.GameMaster, readOnly: true)] public IEntity Parent { @@ -754,6 +761,7 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt public int CompareTo(Item other) => other == null ? -1 : Serial.CompareTo(other.Serial); public virtual int HuedItemID => m_ItemID; + public ObjectPropertyList PropertyList => m_PropertyList ??= InitializePropertyList(new ObjectPropertyList(this)); /// @@ -768,13 +776,17 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt AddNameProperties(list); } + [IgnoreDupe] [CommandProperty(AccessLevel.GameMaster, readOnly: true)] public DateTime Created { get; set; } = Core.Now; + [IgnoreDupe] public long SavePosition { get; set; } = -1; + [IgnoreDupe] public BufferWriter SaveBuffer { get; set; } + [IgnoreDupe] [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } @@ -1445,6 +1457,7 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt ClearProperties(); } + [IgnoreDupe] public ISpawner Spawner { get => LookupCompactInfo()?.m_Spawner; @@ -3341,21 +3354,41 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt } } - private static readonly HashSet _excludedProperties = - [ - "SaveBuffer", - "Parent", - "Next", - "Previous", - "OnLinkList" - ]; - - public virtual bool DupeExcludedProperty(string propertyName) => _excludedProperties.Contains(propertyName); - public virtual void OnAfterDuped(Item newItem) { } + // Warning: This uses reflection and is slow! + public virtual void Dupe(Item newItem) + { + CopyProperties(this, newItem); + OnAfterDuped(newItem); + } + + // Warning: This uses reflection and is slow! + public static void CopyProperties(Item src, Item dest) + { + var props = src.GetType().GetProperties(); + + for (var i = 0; i < props.Length; i++) + { + var p = props[i]; + if (p.GetCustomAttribute(typeof(IgnoreDupeAttribute), true) != null || !p.CanRead || !p.CanWrite) + { + continue; + } + + try + { + p.SetValue(dest, p.GetValue(src, null), null); + } + catch + { + // ignored + } + } + } + public virtual bool OnDragLift(Mobile from) => true; public virtual bool OnEquip(Mobile from) => true; diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index f9b38d089..11dbbd635 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -2256,13 +2256,17 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro AddNameProperties(list); } + [IgnoreDupe] [CommandProperty(AccessLevel.GameMaster, readOnly: true)] public DateTime Created { get; set; } = Core.Now; + [IgnoreDupe] public long SavePosition { get; set; } = -1; + [IgnoreDupe] public BufferWriter SaveBuffer { get; set; } + [IgnoreDupe] [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } diff --git a/Projects/UOContent.Tests/Tests/Items/Books/BookPacketTests.cs b/Projects/UOContent.Tests/Tests/Items/Books/BookPacketTests.cs index 65ebb162b..d8025bcf4 100644 --- a/Projects/UOContent.Tests/Tests/Items/Books/BookPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/Books/BookPacketTests.cs @@ -11,7 +11,7 @@ public class TestBook : BaseBook { public TestBook(int itemID, int pageCount = 20, bool writable = true) : base(itemID, pageCount, writable) { - } + } public TestBook(int itemID, string title, string author, int pageCount, bool writable) : base(itemID, title, author, pageCount, writable) { @@ -89,4 +89,4 @@ public class BookPacketTests : IClassFixture var result = ns.SendPipe.Reader.AvailableToRead(); AssertThat.Equal(result, expected); } -} \ No newline at end of file +} diff --git a/Projects/UOContent/Commands/Dupe.cs b/Projects/UOContent/Commands/Dupe.cs index 092f84bb3..2ab656651 100644 --- a/Projects/UOContent/Commands/Dupe.cs +++ b/Projects/UOContent/Commands/Dupe.cs @@ -2,162 +2,137 @@ using System; using Server.Items; using Server.Targeting; -namespace Server.Commands +namespace Server.Commands; + +public static class Dupe { - public static class Dupe + public static void Configure() { - public static void Configure() + CommandSystem.Register("Dupe", AccessLevel.GameMaster, Dupe_OnCommand); + CommandSystem.Register("DupeInBag", AccessLevel.GameMaster, DupeInBag_OnCommand); + } + + [Usage("Dupe [amount]")] + [Description("Dupes a targeted item.")] + private static void Dupe_OnCommand(CommandEventArgs e) + { + var amount = 1; + if (e.Length >= 1) { - CommandSystem.Register("Dupe", AccessLevel.GameMaster, Dupe_OnCommand); - CommandSystem.Register("DupeInBag", AccessLevel.GameMaster, DupeInBag_OnCommand); + amount = e.GetInt32(0); } - [Usage("Dupe [amount]")] - [Description("Dupes a targeted item.")] - private static void Dupe_OnCommand(CommandEventArgs e) + e.Mobile.Target = new DupeTarget(false, amount > 0 ? amount : 1); + e.Mobile.SendMessage("What do you wish to dupe?"); + } + + [Usage("DupeInBag ")] + [Description("Dupes an item at it's current location (count) number of times.")] + private static void DupeInBag_OnCommand(CommandEventArgs e) + { + var amount = 1; + if (e.Length >= 1) { - var amount = 1; - if (e.Length >= 1) + amount = e.GetInt32(0); + } + + e.Mobile.Target = new DupeTarget(true, amount > 0 ? amount : 1); + e.Mobile.SendMessage("What do you wish to dupe?"); + } + + private class DupeTarget : Target + { + private readonly int m_Amount; + private readonly bool m_InBag; + + public DupeTarget(bool inbag, int amount) + : base(15, false, TargetFlags.None) + { + m_InBag = inbag; + m_Amount = amount; + } + + protected override void OnTarget(Mobile from, object targ) + { + var done = false; + if (targ is not Item copy) { - amount = e.GetInt32(0); + from.SendMessage("You can only dupe items."); + return; } - e.Mobile.Target = new DupeTarget(false, amount > 0 ? amount : 1); - e.Mobile.SendMessage("What do you wish to dupe?"); - } + CommandLogging.WriteLine( + from, + $"{from.AccessLevel} {CommandLogging.Format(from)} duping {CommandLogging.Format(copy)} (inBag={m_InBag}; amount={m_Amount})" + ); - [Usage("DupeInBag ")] - [Description("Dupes an item at it's current location (count) number of times.")] - private static void DupeInBag_OnCommand(CommandEventArgs e) - { - var amount = 1; - if (e.Length >= 1) + Container pack = null; + + if (m_InBag) { - amount = e.GetInt32(0); + pack = copy.Parent switch + { + Container cont => cont, + Mobile m => m.Backpack, + _ => null + }; + } + else + { + pack = from.Backpack; } - e.Mobile.Target = new DupeTarget(true, amount > 0 ? amount : 1); - e.Mobile.SendMessage("What do you wish to dupe?"); - } - - public static void CopyProperties(Item dest, Item src) - { - var props = src.GetType().GetProperties(); - - for (var i = 0; i < props.Length; i++) + var c = copy.GetType().GetConstructor(out var paramCount); + if (c != null) { - var p = props[i]; + var args = paramCount == 0 ? null : new object[paramCount]; + if (args != null) + { + Array.Fill(args, Type.Missing); + } + try { - // Do not set the parent since it screws up mobile/container totals and weights. - if (p.CanRead && p.CanWrite && !src.DupeExcludedProperty(p.Name) && !dest.DupeExcludedProperty(p.Name)) + from.SendMessage($"Duping {m_Amount}..."); + for (var i = 0; i < m_Amount; i++) { - p.SetValue(dest, p.GetValue(src, null), null); + if (c.Invoke(args) is Item newItem) + { + copy.Dupe(newItem); + + if (pack != null) + { + pack.DropItem(newItem); + } + else + { + newItem.MoveToWorld(from.Location, from.Map); + } + + newItem.UpdateTotals(); + newItem.InvalidateProperties(); + newItem.Delta(ItemDelta.Update); + + CommandLogging.WriteLine( + from, + $"{from.AccessLevel} {CommandLogging.Format(from)} duped {CommandLogging.Format(copy)} creating {CommandLogging.Format(newItem)}" + ); + } } + + from.SendMessage("Done"); + done = true; } catch { - // ignored - } - } - } - - private class DupeTarget : Target - { - private readonly int m_Amount; - private readonly bool m_InBag; - - public DupeTarget(bool inbag, int amount) - : base(15, false, TargetFlags.None) - { - m_InBag = inbag; - m_Amount = amount; - } - - protected override void OnTarget(Mobile from, object targ) - { - var done = false; - if (!(targ is Item)) - { - from.SendMessage("You can only dupe items."); + from.SendMessage("Error!"); return; } + } - CommandLogging.WriteLine( - from, - $"{from.AccessLevel} {CommandLogging.Format(from)} duping {CommandLogging.Format(targ)} (inBag={m_InBag}; amount={m_Amount})" - ); - - var copy = (Item)targ; - Container pack = null; - - if (m_InBag) - { - pack = copy.Parent switch - { - Container cont => cont, - Mobile m => m.Backpack, - _ => pack - }; - } - else - { - pack = from.Backpack; - } - - var c = copy.GetType().GetConstructor(out var paramCount); - if (c != null) - { - var args = paramCount == 0 ? null : new object[paramCount]; - if (args != null) - { - Array.Fill(args, Type.Missing); - } - - try - { - from.SendMessage($"Duping {m_Amount}..."); - for (var i = 0; i < m_Amount; i++) - { - if (c.Invoke(args) is Item newItem) - { - CopyProperties(newItem, copy); - copy.OnAfterDuped(newItem); - - if (pack != null) - { - pack.DropItem(newItem); - } - else - { - newItem.MoveToWorld(from.Location, from.Map); - } - - newItem.UpdateTotals(); - newItem.InvalidateProperties(); - newItem.Delta(ItemDelta.Update); - - CommandLogging.WriteLine( - from, - $"{from.AccessLevel} {CommandLogging.Format(from)} duped {CommandLogging.Format(targ)} creating {CommandLogging.Format(newItem)}" - ); - } - } - - from.SendMessage("Done"); - done = true; - } - catch - { - from.SendMessage("Error!"); - return; - } - } - - if (!done) - { - from.SendMessage("Unable to dupe. Item must have a constructor with zero required parameters."); - } + if (!done) + { + from.SendMessage("Unable to dupe. Item must have a constructor with zero required parameters."); } } } diff --git a/Projects/UOContent/Engines/Bulk Orders/Books/BulkOrderBook.cs b/Projects/UOContent/Engines/Bulk Orders/Books/BulkOrderBook.cs index 2ffa7e602..04ead61d2 100644 --- a/Projects/UOContent/Engines/Bulk Orders/Books/BulkOrderBook.cs +++ b/Projects/UOContent/Engines/Bulk Orders/Books/BulkOrderBook.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using ModernUO.Serialization; using Server.ContextMenus; using Server.Gumps; -using Server.Items; using Server.Mobiles; using Server.Multis; using Server.Prompts; @@ -16,6 +15,7 @@ public partial class BulkOrderBook : Item, ISecurable [SerializableField(0)] private int _itemCount; + [SerializedIgnoreDupe] [InvalidateProperties] [SerializableField(1)] [SerializedCommandProperty(AccessLevel.GameMaster)] @@ -26,10 +26,12 @@ public partial class BulkOrderBook : Item, ISecurable [SerializedCommandProperty(AccessLevel.GameMaster)] private string _bookName; + [SerializedIgnoreDupe] [SerializableField(3)] [SerializedCommandProperty(AccessLevel.GameMaster)] private BOBFilter _filter; + [SerializedIgnoreDupe] [SerializableField(4)] [SerializedCommandProperty(AccessLevel.GameMaster)] private List _entries; @@ -40,12 +42,39 @@ public partial class BulkOrderBook : Item, ISecurable Weight = 1.0; LootType = LootType.Blessed; - _entries = new List(); + _entries = []; _filter = new BOBFilter(); _level = SecureLevel.CoOwners; } + public override void OnAfterDuped(Item newItem) + { + if (newItem is not BulkOrderBook book) + { + return; + } + + var filter = book._filter; + filter.Material = Filter.Material; + filter.Quality = Filter.Quality; + filter.Quantity = Filter.Quantity; + filter.Type = Filter.Type; + + for (var i = 0; i < Entries.Count; i++) + { + // Recreate the BOD + var bod = Entries[i].Reconstruct(); + + // Recreate the entry + IBOBEntry newEntry = bod is LargeBOD largeBod ? new BOBLargeEntry(largeBod) : new BOBSmallEntry((SmallBOD)bod); + book.AddToEntries(newEntry); + + // Delete the new BOD + bod.Delete(); + } + } + public override void OnDoubleClick(Mobile from) { if (!from.InRange(GetWorldLocation(), 2)) @@ -169,13 +198,21 @@ public partial class BulkOrderBook : Item, ISecurable } } - public void InvalidateContainers(IEntity parent) + public static void InvalidateContainers(IEntity parent) { - if (parent is Container c) + do { - c.InvalidateProperties(); - InvalidateContainers(c.Parent); - } + if (parent is Item item) + { + item.InvalidateProperties(); + parent = item.Parent; + } + else if (parent is Mobile m) + { + m.InvalidateProperties(); + return; + } + } while (parent != null); } private void Deserialize(IGenericReader reader, int version) diff --git a/Projects/UOContent/Engines/Plants/MainPlantGump.cs b/Projects/UOContent/Engines/Plants/MainPlantGump.cs index 01ddf1fdf..0e831898b 100644 --- a/Projects/UOContent/Engines/Plants/MainPlantGump.cs +++ b/Projects/UOContent/Engines/Plants/MainPlantGump.cs @@ -195,11 +195,15 @@ namespace Server.Engines.Plants switch (value) { case 1: - AddLabel(x, y, 0x35, "+"); - break; + { + AddLabel(x, y, 0x35, "+"); + break; + } case 2: - AddLabel(x, y, 0x21, "+"); - break; + { + AddLabel(x, y, 0x21, "+"); + break; + } } } @@ -208,17 +212,25 @@ namespace Server.Engines.Plants switch (value) { case 0: - AddLabel(x, y, 0x21, "-"); - break; + { + AddLabel(x, y, 0x21, "-"); + break; + } case 1: - AddLabel(x, y, 0x35, "-"); - break; + { + AddLabel(x, y, 0x35, "-"); + break; + } case 3: - AddLabel(x, y, 0x35, "+"); - break; + { + AddLabel(x, y, 0x35, "+"); + break; + } case 4: - AddLabel(x, y, 0x21, "+"); - break; + { + AddLabel(x, y, 0x21, "+"); + break; + } } } @@ -236,21 +248,33 @@ namespace Server.Engines.Plants switch (m_Plant.PlantSystem.GrowthIndicator) { + default: + case PlantGrowthIndicator.None: case PlantGrowthIndicator.InvalidLocation: - AddLabel(x, y, 0x21, "!"); - break; + { + AddLabel(x, y, 0x21, "!"); + break; + } case PlantGrowthIndicator.NotHealthy: - AddLabel(x, y, 0x21, "-"); - break; + { + AddLabel(x, y, 0x21, "-"); + break; + } case PlantGrowthIndicator.Delay: - AddLabel(x, y, 0x35, "-"); - break; + { + AddLabel(x, y, 0x35, "-"); + break; + } case PlantGrowthIndicator.Grown: - AddLabel(x, y, 0x3, "+"); - break; + { + AddLabel(x, y, 0x3, "+"); + break; + } case PlantGrowthIndicator.DoubleGrown: - AddLabel(x, y, 0x3F, "+"); - break; + { + AddLabel(x, y, 0x3F, "+"); + break; + } } } diff --git a/Projects/UOContent/Engines/Plants/PlantItem.cs b/Projects/UOContent/Engines/Plants/PlantItem.cs index a37917d72..8a8183a66 100644 --- a/Projects/UOContent/Engines/Plants/PlantItem.cs +++ b/Projects/UOContent/Engines/Plants/PlantItem.cs @@ -32,6 +32,9 @@ public enum PlantStatus [SerializationGenerator(3, false)] public partial class PlantItem : Item, ISecurable { + public static List Plants { get; } = []; + + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; @@ -39,6 +42,7 @@ public partial class PlantItem : Item, ISecurable [SerializableFieldSaveFlag(0)] private bool ShouldSerializeSecureLevel() => (int)_level != 0; + [SerializedIgnoreDupe] [SerializableField(5, setter: "private")] private PlantSystem _plantSystem; @@ -192,7 +196,10 @@ public partial class PlantItem : Item, ISecurable [CommandProperty(AccessLevel.GameMaster)] public bool Reproduces => PlantHueInfo.CanReproduce(PlantHue) && PlantTypeInfo.CanReproduce(PlantType); - public static List Plants { get; } = new(); + public override void OnAfterDuped(Item newItem) + { + PlantSystem.OnAfterDuped(newItem); + } public override void OnSingleClick(Mobile from) { @@ -231,7 +238,7 @@ public partial class PlantItem : Item, ISecurable }; } - public int GetLocalizedContainerType() => 1150435; + public static int GetLocalizedContainerType() => 1150435; private void Update() { diff --git a/Projects/UOContent/Engines/Plants/PlantSystem.cs b/Projects/UOContent/Engines/Plants/PlantSystem.cs index 6a5d05826..3579d31a5 100644 --- a/Projects/UOContent/Engines/Plants/PlantSystem.cs +++ b/Projects/UOContent/Engines/Plants/PlantSystem.cs @@ -66,9 +66,9 @@ namespace Server.Engines.Plants private void Deserialize(IGenericReader reader, int version) { _fertileDirt = reader.ReadBool(); - NextGrowth = reader.ReadDateTime(); + _nextGrowth = reader.ReadDateTime(); - GrowthIndicator = (PlantGrowthIndicator)reader.ReadInt(); + _growthIndicator = (PlantGrowthIndicator)reader.ReadInt(); _water = reader.ReadInt(); @@ -139,16 +139,14 @@ namespace Server.Engines.Plants public int MaxHits => 10 + (int)Plant.PlantStatus * 2; - public PlantHealth Health - { - get => (_hits * 100 / MaxHits) switch + public PlantHealth Health => + (_hits * 100 / MaxHits) switch { < 33 => PlantHealth.Dying, < 66 => PlantHealth.Wilted, < 100 => PlantHealth.Healthy, _ => PlantHealth.Vibrant }; - } [SerializableProperty(5)] public int Infestation @@ -369,6 +367,41 @@ namespace Server.Engines.Plants LeftResources = 8; } + public void OnAfterDuped(Item newItem) + { + if (newItem is not PlantItem plant) + { + return; + } + + // Copy all properties from this.PlantSystem to plantItem.PlantSystem + var plantSystem = plant.PlantSystem; + plantSystem.Water = Water; + plantSystem.Hits = Hits; + plantSystem.Infestation = Infestation; + plantSystem.Fungus = Fungus; + plantSystem.PoisonPotion = PoisonPotion; + plantSystem.Disease = Disease; + plantSystem.PoisonPotion = PoisonPotion; + plantSystem.CurePotion = CurePotion; + plantSystem.HealPotion = HealPotion; + plantSystem.StrengthPotion = StrengthPotion; + + plantSystem.Pollinated = Pollinated; + + // Do not use getter since it has computed logic + plantSystem.SeedType = _seedType; + plantSystem.SeedHue = _seedHue; + + plantSystem.AvailableSeeds = AvailableSeeds; + plantSystem.LeftSeeds = LeftSeeds; + plantSystem.AvailableResources = AvailableResources; + plantSystem.LeftResources = LeftResources; + plantSystem.FertileDirt = FertileDirt; + plantSystem.NextGrowth = NextGrowth; + plantSystem.GrowthIndicator = GrowthIndicator; + } + public int GetLocalizedDirtStatus() => Water switch { diff --git a/Projects/UOContent/Engines/Plants/Seed.cs b/Projects/UOContent/Engines/Plants/Seed.cs index 9822da7f6..043ad445b 100644 --- a/Projects/UOContent/Engines/Plants/Seed.cs +++ b/Projects/UOContent/Engines/Plants/Seed.cs @@ -173,9 +173,8 @@ public partial class Seed : Item return; } - newSeed.PlantType = _plantType; newSeed.PlantHue = _plantHue; - newSeed.ShowType = _showType; + newSeed.Hue = Hue; } private void Deserialize(IGenericReader reader, int version) diff --git a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs index 928d4fe07..fa3387ab3 100644 --- a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs +++ b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs @@ -14,6 +14,7 @@ namespace Server.Engines.Spawners; [SerializationGenerator(10, false)] public abstract partial class BaseSpawner : Item, ISpawner { + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.Developer)] private Guid _guid; @@ -22,6 +23,7 @@ public abstract partial class BaseSpawner : Item, ISpawner [SerializedCommandProperty(AccessLevel.Developer)] private bool _returnOnDeactivate; + [SerializedIgnoreDupe] [SerializableField(2, setter: "private")] private List _entries; @@ -141,6 +143,7 @@ public abstract partial class BaseSpawner : Item, ISpawner public bool IsFull => Spawned?.Count >= _count; public bool IsEmpty => Spawned?.Count == 0; + [IgnoreDupe] public Dictionary Spawned { get; private set; } [SerializableProperty(8)] @@ -269,17 +272,18 @@ public abstract partial class BaseSpawner : Item, ISpawner { newSpawner._guid = Guid.NewGuid(); newSpawner.Spawned = new Dictionary(); - newSpawner.Entries = new List(); + newSpawner.Entries = []; for (var i = 0; i < Entries.Count; i++) { + var entry = Entries[i]; newSpawner.AddEntry( - Entries[i].SpawnedName, - Entries[i].SpawnedProbability, - Entries[i].SpawnedMaxCount, + entry.SpawnedName, + entry.SpawnedProbability, + entry.SpawnedMaxCount, false, - Entries[i].Properties, - Entries[i].Parameters + entry.Properties, + entry.Parameters ); } } @@ -295,7 +299,7 @@ public abstract partial class BaseSpawner : Item, ISpawner ) { var entry = new SpawnerEntry(this, creaturename, probability, amount, properties, parameters); - Entries.Add(entry); + AddToEntries(entry); if (dotimer) { DoTimer(TimeSpan.FromSeconds(1)); @@ -315,7 +319,7 @@ public abstract partial class BaseSpawner : Item, ISpawner _count = amount; _team = team; _homeRange = homeRange; - Entries = new List(); + Entries = []; Spawned = new Dictionary(); DoTimer(TimeSpan.FromSeconds(1)); @@ -394,7 +398,7 @@ public abstract partial class BaseSpawner : Item, ISpawner public void Defrag() { - Entries ??= new List(); + Entries ??= []; for (var i = 0; i < Entries.Count; ++i) { @@ -590,7 +594,7 @@ public abstract partial class BaseSpawner : Item, ISpawner IEntity entity = null; var propargs = string.IsNullOrEmpty(entry.Properties) - ? Array.Empty() + ? [] : CommandSystem.Split(entry.Properties.Trim()); var props = FormatProperties(propargs); @@ -604,7 +608,7 @@ public abstract partial class BaseSpawner : Item, ISpawner } var paramargs = string.IsNullOrEmpty(entry.Parameters) - ? Array.Empty() + ? [] : entry.Parameters.Trim().Split(' '); if (paramargs.Length == 0) diff --git a/Projects/UOContent/Items/Aquarium/Aquarium.cs b/Projects/UOContent/Items/Aquarium/Aquarium.cs index 3446c9c0b..dd1baf0b1 100644 --- a/Projects/UOContent/Items/Aquarium/Aquarium.cs +++ b/Projects/UOContent/Items/Aquarium/Aquarium.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using ModernUO.Serialization; using Server.ContextMenus; using Server.Multis; @@ -13,7 +14,7 @@ namespace Server.Items public static readonly TimeSpan EvaluationInterval = TimeSpan.FromDays(1); private static readonly Type[] m_Decorations = - { + [ typeof(FishBones), typeof(WaterloggedBoots), typeof(CaptainBlackheartsFishingPole), @@ -23,7 +24,7 @@ namespace Server.Items typeof(IslandStatue), typeof(Shell), typeof(ToyBoat) - }; + ]; private bool m_EvaluateDay; @@ -45,16 +46,19 @@ namespace Server.Items [SerializedCommandProperty(AccessLevel.GameMaster)] private int _vacationLeft; + [SerializedIgnoreDupe] [InvalidateProperties] [SerializableField(3, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster)] private AquariumState _food; + [SerializedIgnoreDupe] [InvalidateProperties] [SerializableField(4, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster)] private AquariumState _water; + [SerializedIgnoreDupe] [SerializableField(5, setter: "private")] private List _events; @@ -90,7 +94,7 @@ namespace Server.Items _water.Maintain = Utility.RandomMinMax(1, 3); - _events = new List(); + _events = []; _evaluateTimer = Timer.DelayCall(EvaluationInterval, EvaluationInterval, Evaluate); } @@ -145,9 +149,9 @@ namespace Server.Items public override double DefaultWeight => 10.0; private static int[] FishHues = - { + [ 0x1C2, 0x1C3, 0x2A3, 0x47E, 0x51D - }; + ]; public override void OnDelete() { @@ -471,6 +475,29 @@ namespace Server.Items } } + public override void OnAfterDuped(Item newItem) + { + if (newItem is not Aquarium aquarium) + { + return; + } + + aquarium.Food.Added = Food.Added; + aquarium.Food.Improve = Food.Improve; + aquarium.Food.Maintain = Food.Maintain; + aquarium.Food.State = Food.State; + + aquarium.Water.Added = Water.Added; + aquarium.Water.Improve = Water.Improve; + aquarium.Water.Maintain = Water.Maintain; + aquarium.Water.State = Water.State; + + for (var i = 0; i < _events.Count; i++) + { + aquarium.AddToEvents(_events[i]); + } + } + private void Deserialize(IGenericReader reader, int version) { switch (version) @@ -500,7 +527,7 @@ namespace Server.Items _water = new AquariumState(this); _water.Deserialize(reader); - _events = new List(); + _events = []; var count = reader.ReadInt(); for (var i = 0; i < count; i++) { @@ -856,7 +883,8 @@ namespace Server.Items from.PlaySound(0x5A4); } - public virtual bool AddFish(BaseFish fish) => AddFish(null, fish); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool AddFish(BaseFish fish) => AddFish(null, fish); public virtual bool AddFish(Mobile from, BaseFish fish) { @@ -877,16 +905,15 @@ namespace Server.Items LiveCreatures += 1; - from?.SendLocalizedMessage( - 1073632, - $"#{fish.LabelNumber}" - ); // You add the following creature to your aquarium: ~1_FISH~ + // You add the following creature to your aquarium: ~1_FISH~ + from?.SendLocalizedMessage(1073632, $"#{fish.LabelNumber}"); InvalidateProperties(); return true; } - public virtual bool AddDecoration(Item item) => AddDecoration(null, item); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool AddDecoration(Item item) => AddDecoration(null, item); public virtual bool AddDecoration(Mobile from, Item item) { diff --git a/Projects/UOContent/Items/Aquarium/AquariumState.cs b/Projects/UOContent/Items/Aquarium/AquariumState.cs index 432f5d618..cdd59d9d9 100644 --- a/Projects/UOContent/Items/Aquarium/AquariumState.cs +++ b/Projects/UOContent/Items/Aquarium/AquariumState.cs @@ -40,7 +40,7 @@ namespace Server.Items if (_state != value) { _state = Math.Clamp(value, 0, 4); - this.MarkDirty(); + MarkDirty(); } } } diff --git a/Projects/UOContent/Items/Armor/BaseArmor.cs b/Projects/UOContent/Items/Armor/BaseArmor.cs index 82c34b067..7c5e45446 100644 --- a/Projects/UOContent/Items/Armor/BaseArmor.cs +++ b/Projects/UOContent/Items/Armor/BaseArmor.cs @@ -13,6 +13,7 @@ namespace Server.Items [SerializationGenerator(9, false)] public abstract partial class BaseArmor : Item, IScissorable, IFactionItem, ICraftable, IWearableDurability, IAosItem { + [SerializedIgnoreDupe] [SerializableField(0, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosAttributes _attributes; @@ -23,6 +24,7 @@ namespace Server.Items [SerializableFieldDefault(0)] private AosAttributes AttributesDefaultValue() => new(this); + [SerializedIgnoreDupe] [SerializableField(1, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosArmorAttributes _armorAttributes; @@ -128,6 +130,7 @@ namespace Server.Items // Field 22 private AMA _meditate = (AMA)(-1); + [SerializedIgnoreDupe] [SerializableField(23, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] public AosSkillBonuses _skillBonuses; @@ -788,6 +791,12 @@ namespace Server.Items armor.Attributes = new AosAttributes(newItem, Attributes); armor.ArmorAttributes = new AosArmorAttributes(newItem, ArmorAttributes); armor.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses); + + // Set hue again because of resource + armor.Hue = Hue; + // Set HP/Max again because of durability + armor.HitPoints = HitPoints; + armor.MaxHitPoints = MaxHitPoints; } public int ComputeStatReq(StatType type) diff --git a/Projects/UOContent/Items/Books/BaseBook.cs b/Projects/UOContent/Items/Books/BaseBook.cs index dbf5f2dc3..a2f79607c 100644 --- a/Projects/UOContent/Items/Books/BaseBook.cs +++ b/Projects/UOContent/Items/Books/BaseBook.cs @@ -11,6 +11,7 @@ namespace Server.Items [SerializationGenerator(5, false)] public partial class BaseBook : Item, ISecurable { + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; @@ -45,6 +46,7 @@ namespace Server.Items [SerializableFieldSaveFlag(3)] private bool ShouldSerializeWritable() => _writable; + [SerializedIgnoreDupe] [SerializableField(4, setter: "protected")] private BookPageInfo[] _pages; @@ -52,7 +54,7 @@ namespace Server.Items private bool ShouldSerializePages() => DefaultContent?.IsMatch(_pages) != true; [SerializableFieldDefault(4)] - private BookPageInfo[] PagesDefaultvalue() => DefaultContent?.Copy() ?? Array.Empty(); + private BookPageInfo[] PagesDefaultValue() => DefaultContent?.Copy() ?? Array.Empty(); [Constructible] public BaseBook(int itemID, int pageCount = 20, bool writable = true) : this(itemID, null, null, pageCount, writable) @@ -91,6 +93,29 @@ namespace Server.Items [CommandProperty(AccessLevel.GameMaster)] public int PagesCount => _pages.Length; + public override void OnAfterDuped(Item newItem) + { + if (newItem is not BaseBook book) + { + return; + } + + book.Pages = new BookPageInfo[book.PagesCount]; + + for (var i = 0; i < _pages.Length; ++i) + { + var oldPage = _pages[i]; + var oldLines = _pages[i].Lines; + var lines = new string[oldPage.Lines.Length]; + for (var j = 0; j < oldLines.Length; j++) + { + lines[j] = oldLines[j]; + } + + book._pages[i] = new BookPageInfo(lines); + } + } + public virtual BookContent DefaultContent => null; public string ContentAsString diff --git a/Projects/UOContent/Items/Books/BookPageInfo.cs b/Projects/UOContent/Items/Books/BookPageInfo.cs index d5c7e24f4..ff07ca843 100644 --- a/Projects/UOContent/Items/Books/BookPageInfo.cs +++ b/Projects/UOContent/Items/Books/BookPageInfo.cs @@ -1,35 +1,32 @@ -using System; +namespace Server.Items; -namespace Server.Items +public class BookPageInfo { - public class BookPageInfo + public BookPageInfo() => Lines = []; + + public BookPageInfo(params string[] lines) => Lines = lines; + + public BookPageInfo(IGenericReader reader) { - public BookPageInfo() => Lines = Array.Empty(); + var length = reader.ReadInt(); - public BookPageInfo(params string[] lines) => Lines = lines; + Lines = new string[length]; - public BookPageInfo(IGenericReader reader) + for (var i = 0; i < Lines.Length; ++i) { - var length = reader.ReadInt(); - - Lines = new string[length]; - - for (var i = 0; i < Lines.Length; ++i) - { - Lines[i] = reader.ReadString().Intern(); - } + Lines[i] = reader.ReadString().Intern(); } + } - public string[] Lines { get; set; } + public string[] Lines { get; set; } - public void Serialize(IGenericWriter writer) + public void Serialize(IGenericWriter writer) + { + writer.Write(Lines.Length); + + for (var i = 0; i < Lines.Length; ++i) { - writer.Write(Lines.Length); - - for (var i = 0; i < Lines.Length; ++i) - { - writer.Write(Lines[i]); - } + writer.Write(Lines[i]); } } } diff --git a/Projects/UOContent/Items/Clothing/BaseClothing.cs b/Projects/UOContent/Items/Clothing/BaseClothing.cs index c5b7f6e01..d975ab95f 100644 --- a/Projects/UOContent/Items/Clothing/BaseClothing.cs +++ b/Projects/UOContent/Items/Clothing/BaseClothing.cs @@ -29,6 +29,7 @@ namespace Server.Items [SerializableFieldSaveFlag(0)] private bool ShouldSerializeResource() => _resource != DefaultResource; + [SerializedIgnoreDupe] [SerializableField(1, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosAttributes _attributes; @@ -39,6 +40,7 @@ namespace Server.Items [SerializableFieldDefault(1)] private AosAttributes AttributesDefaultValue() => new(this); + [SerializedIgnoreDupe] [SerializableField(2, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosArmorAttributes _clothingAttributes; @@ -49,6 +51,7 @@ namespace Server.Items [SerializableFieldDefault(2)] private AosArmorAttributes ClothingAttributesDefaultValue() => new(this); + [SerializedIgnoreDupe] [SerializableField(3, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosSkillBonuses _skillBonuses; @@ -59,6 +62,7 @@ namespace Server.Items [SerializableFieldDefault(3)] private AosSkillBonuses SkillBonusesDefaultValue() => new(this); + [SerializedIgnoreDupe] [SerializableField(4, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosElementAttributes _resistances; @@ -637,6 +641,12 @@ namespace Server.Items clothing.Resistances = new AosElementAttributes(newItem, Resistances); clothing.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses); clothing.ClothingAttributes = new AosArmorAttributes(newItem, ClothingAttributes); + + // Set hue again because of resource + clothing.Hue = Hue; + // Set HP/Max again because of durability + clothing.HitPoints = HitPoints; + clothing.MaxHitPoints = MaxHitPoints; } public override bool AllowEquippedCast(Mobile from) => diff --git a/Projects/UOContent/Items/Construction/Doors/HouseDoors.cs b/Projects/UOContent/Items/Construction/Doors/HouseDoors.cs index a111d28fc..0661ff346 100644 --- a/Projects/UOContent/Items/Construction/Doors/HouseDoors.cs +++ b/Projects/UOContent/Items/Construction/Doors/HouseDoors.cs @@ -58,6 +58,7 @@ public partial class GenericHouseDoor : BaseHouseDoor [SerializationGenerator(2, false)] public abstract partial class BaseHouseDoor : BaseDoor, ISecurable { + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; diff --git a/Projects/UOContent/Items/Games/BaseBoard.cs b/Projects/UOContent/Items/Games/BaseBoard.cs index 0d71c0a8f..fe493c587 100644 --- a/Projects/UOContent/Items/Games/BaseBoard.cs +++ b/Projects/UOContent/Items/Games/BaseBoard.cs @@ -11,6 +11,7 @@ namespace Server.Items; [SerializationGenerator(2, false)] public abstract partial class BaseBoard : Container, ISecurable { + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; diff --git a/Projects/UOContent/Items/Games/Mahjong/MahjongGame.cs b/Projects/UOContent/Items/Games/Mahjong/MahjongGame.cs index 34292774c..4bf19d996 100644 --- a/Projects/UOContent/Items/Games/Mahjong/MahjongGame.cs +++ b/Projects/UOContent/Items/Games/Mahjong/MahjongGame.cs @@ -13,22 +13,28 @@ public partial class MahjongGame : Item, ISecurable public const int MaxPlayers = 4; public const int BaseScore = 30000; + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; + [SerializedIgnoreDupe] [SerializableField(1, setter: "private")] private MahjongTile[] _tiles; + [SerializedIgnoreDupe] [SerializableField(2, setter: "private")] private MahjongDealerIndicator _dealerIndicator; + [SerializedIgnoreDupe] [SerializableField(3, setter: "private")] private MahjongWallBreakIndicator _wallBreakIndicator; + [SerializedIgnoreDupe] [SerializableField(4, setter: "private")] private MahjongDices _dices; + [SerializedIgnoreDupe] [SerializableField(5, setter: "private")] private MahjongPlayers _players; diff --git a/Projects/UOContent/Items/Jewels/BaseJewel.cs b/Projects/UOContent/Items/Jewels/BaseJewel.cs index 6b8b9b172..9e4bf0365 100644 --- a/Projects/UOContent/Items/Jewels/BaseJewel.cs +++ b/Projects/UOContent/Items/Jewels/BaseJewel.cs @@ -32,14 +32,17 @@ public abstract partial class BaseJewel : Item, ICraftable, IAosItem [SerializedCommandProperty(AccessLevel.GameMaster)] private GemType _gemType; + [SerializedIgnoreDupe] [SerializableField(4, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosAttributes _attributes; + [SerializedIgnoreDupe] [SerializableField(5, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosElementAttributes _resistances; + [SerializedIgnoreDupe] [SerializableField(6, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosSkillBonuses _skillBonuses; @@ -193,6 +196,9 @@ public abstract partial class BaseJewel : Item, ICraftable, IAosItem jewel.Attributes = new AosAttributes(newItem, Attributes); jewel.Resistances = new AosElementAttributes(newItem, Resistances); jewel.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses); + + // Set hue again because of resource + jewel.Hue = Hue; } public override void OnAdded(IEntity parent) diff --git a/Projects/UOContent/Items/Misc/PlayerBulletinBoards.cs b/Projects/UOContent/Items/Misc/PlayerBulletinBoards.cs index ec1c07fa0..6652dc2ee 100644 --- a/Projects/UOContent/Items/Misc/PlayerBulletinBoards.cs +++ b/Projects/UOContent/Items/Misc/PlayerBulletinBoards.cs @@ -31,6 +31,7 @@ public partial class PlayerBBEast : BasePlayerBB [SerializationGenerator(0, false)] public abstract partial class BasePlayerBB : Item, ISecurable { + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; @@ -40,10 +41,12 @@ public abstract partial class BasePlayerBB : Item, ISecurable private string _title; [CanBeNull] + [SerializedIgnoreDupe] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private PlayerBBMessage _greeting; + [SerializedIgnoreDupe] [SerializableField(3)] private List _messages; @@ -53,6 +56,25 @@ public abstract partial class BasePlayerBB : Item, ISecurable _level = SecureLevel.Anyone; } + public override void OnAfterDuped(Item newItem) + { + if (newItem is not BasePlayerBB board) + { + return; + } + + if (_greeting != null) + { + board.Greeting = new PlayerBBMessage(_greeting.Time, _greeting.Poster, _greeting.Message); + } + + for (var i = 0; i < _messages.Count; i++) + { + var message = _messages[i]; + board.AddToMessages(new PlayerBBMessage(message.Time, message.Poster, message.Message)); + } + } + public override void GetContextMenuEntries(Mobile from, List list) { base.GetContextMenuEntries(from, list); diff --git a/Projects/UOContent/Items/Quivers/BaseQuiver.cs b/Projects/UOContent/Items/Quivers/BaseQuiver.cs index 9748ddff2..cd29b6079 100644 --- a/Projects/UOContent/Items/Quivers/BaseQuiver.cs +++ b/Projects/UOContent/Items/Quivers/BaseQuiver.cs @@ -9,6 +9,7 @@ public partial class BaseQuiver : Container, ICraftable, IAosItem { private static Type[] m_Ammo = { typeof(Arrow), typeof(Bolt) }; + [SerializedIgnoreDupe] [SerializableField(0, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosAttributes _attributes; diff --git a/Projects/UOContent/Items/Skill Items/Magical/Runebook.cs b/Projects/UOContent/Items/Skill Items/Magical/Runebook.cs index 1443e41b3..3a9007ec6 100644 --- a/Projects/UOContent/Items/Skill Items/Magical/Runebook.cs +++ b/Projects/UOContent/Items/Skill Items/Magical/Runebook.cs @@ -24,10 +24,12 @@ public partial class Runebook : Item, ISecurable, ICraftable [SerializedCommandProperty(AccessLevel.GameMaster)] private string _crafter; + [SerializedIgnoreDupe] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; + [SerializedIgnoreDupe] [SerializableField(3, setter: "private")] private List _entries; @@ -281,7 +283,7 @@ public partial class Runebook : Item, ISecurable, ICraftable return; } - book.Entries = new List(); + book.Entries = []; for (var i = 0; i < Entries.Count; i++) { diff --git a/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs b/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs index 87edfd564..901a9d82e 100644 --- a/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs +++ b/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs @@ -96,10 +96,12 @@ public partial class Spellbook : Item, ICraftable, ISlayer, IAosItem [SerializedCommandProperty(AccessLevel.GameMaster)] private SlayerName _slayer2; + [SerializedIgnoreDupe] [SerializableField(5, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosAttributes _attributes; + [SerializedIgnoreDupe] [SerializableField(6, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosSkillBonuses _skillBonuses; @@ -539,13 +541,6 @@ public partial class Spellbook : Item, ICraftable, ISlayer, IAosItem book.Attributes = new AosAttributes(newItem, _attributes); book.SkillBonuses = new AosSkillBonuses(newItem, _skillBonuses); - book.Content = _content; - book.SpellCount = _spellCount; - book.Slayer = _slayer; - book.Slayer2 = _slayer2; - book.Quality = _quality; - book.EngravedText = _engravedText; - book.Crafter = _crafter; } public override void OnAdded(IEntity parent) diff --git a/Projects/UOContent/Items/Skill Items/Tailor Items/Dyetubs/DyeTub.cs b/Projects/UOContent/Items/Skill Items/Tailor Items/Dyetubs/DyeTub.cs index 25d7c9882..2f1f1a303 100644 --- a/Projects/UOContent/Items/Skill Items/Tailor Items/Dyetubs/DyeTub.cs +++ b/Projects/UOContent/Items/Skill Items/Tailor Items/Dyetubs/DyeTub.cs @@ -15,6 +15,7 @@ namespace Server.Items [SerializationGenerator(2, false)] public partial class DyeTub : Item, ISecurable { + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; diff --git a/Projects/UOContent/Items/Special/8th Anniversary Items/Dawn's Music Box/DawnsMusicBox.cs b/Projects/UOContent/Items/Special/8th Anniversary Items/Dawn's Music Box/DawnsMusicBox.cs index ba49d027c..262f85f73 100644 --- a/Projects/UOContent/Items/Special/8th Anniversary Items/Dawn's Music Box/DawnsMusicBox.cs +++ b/Projects/UOContent/Items/Special/8th Anniversary Items/Dawn's Music Box/DawnsMusicBox.cs @@ -99,9 +99,11 @@ public partial class DawnsMusicBox : Item, ISecurable MusicName.SelimsBar, MusicName.SerpentIsleCombat_U7, MusicName.ValoriaShips }; + [SerializedIgnoreDupe] [SerializableField(0, setter: "private")] private List _tracks; + [SerializedIgnoreDupe] [SerializableField(1)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; diff --git a/Projects/UOContent/Items/Special/Gifts/RoseOfTrinsic.cs b/Projects/UOContent/Items/Special/Gifts/RoseOfTrinsic.cs index a8e0d8872..3131288b8 100644 --- a/Projects/UOContent/Items/Special/Gifts/RoseOfTrinsic.cs +++ b/Projects/UOContent/Items/Special/Gifts/RoseOfTrinsic.cs @@ -18,6 +18,7 @@ public partial class RoseOfTrinsic : Item, ISecurable [SerializableField(1, setter: "private")] private DateTime _nextSpawnTime; + [SerializedIgnoreDupe] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; @@ -34,6 +35,16 @@ public partial class RoseOfTrinsic : Item, ISecurable public override int LabelNumber => 1062913; // Rose of Trinsic + public override void OnAfterDuped(Item newItem) + { + if (newItem is not RoseOfTrinsic rose) + { + return; + } + + rose.NextSpawnTime = NextSpawnTime; + } + [SerializableProperty(0)] [CommandProperty(AccessLevel.GameMaster)] public int Petals diff --git a/Projects/UOContent/Items/Special/Gifts/TapestryOfSosaria.cs b/Projects/UOContent/Items/Special/Gifts/TapestryOfSosaria.cs index bcc185df1..53281154b 100644 --- a/Projects/UOContent/Items/Special/Gifts/TapestryOfSosaria.cs +++ b/Projects/UOContent/Items/Special/Gifts/TapestryOfSosaria.cs @@ -10,6 +10,7 @@ namespace Server.Items; [SerializationGenerator(1)] public partial class TapestryOfSosaria : Item, ISecurable { + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; diff --git a/Projects/UOContent/Items/Special/SoulStone.cs b/Projects/UOContent/Items/Special/SoulStone.cs index 78cee0ec1..813a6f21e 100644 --- a/Projects/UOContent/Items/Special/SoulStone.cs +++ b/Projects/UOContent/Items/Special/SoulStone.cs @@ -17,6 +17,7 @@ public partial class SoulStone : Item, ISecurable [SerializedCommandProperty(AccessLevel.GameMaster)] private string _lastUserName; + [SerializedIgnoreDupe] [SerializableField(1)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; diff --git a/Projects/UOContent/Items/Talismans/BaseTalisman.cs b/Projects/UOContent/Items/Talismans/BaseTalisman.cs index 6b1b55202..7c91e6c4a 100644 --- a/Projects/UOContent/Items/Talismans/BaseTalisman.cs +++ b/Projects/UOContent/Items/Talismans/BaseTalisman.cs @@ -125,6 +125,7 @@ public partial class BaseTalisman : Item, IAosItem SkillName.Tinkering }; + [SerializedIgnoreDupe] [SerializableField(0, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosAttributes _attributes; @@ -135,6 +136,7 @@ public partial class BaseTalisman : Item, IAosItem [SerializableFieldDefault(0)] private AosAttributes AttributesDefaultValue() => new(this); + [SerializedIgnoreDupe] [SerializableField(1, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosSkillBonuses _skillBonuses; @@ -145,6 +147,7 @@ public partial class BaseTalisman : Item, IAosItem [SerializableFieldDefault(1)] private AosSkillBonuses SkillBonusesDefaultValue() => new(this); + [SerializedIgnoreDupe] [InvalidateProperties] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] @@ -156,6 +159,7 @@ public partial class BaseTalisman : Item, IAosItem [SerializableFieldDefault(2)] private TalismanAttribute ProtectionDefaultValue() => new(); + [SerializedIgnoreDupe] [InvalidateProperties] [SerializableField(3)] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] @@ -167,6 +171,7 @@ public partial class BaseTalisman : Item, IAosItem [SerializableFieldDefault(3)] private TalismanAttribute KillerDefaultValue() => new(); + [SerializedIgnoreDupe] [InvalidateProperties] [SerializableField(4)] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 270ab6ed5..cbc6fabd5 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -115,6 +115,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ShouldSerializeIdentified() => _identified; + [SerializedIgnoreDupe] [SerializableField(24, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosAttributes _attributes; @@ -126,6 +127,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab [SerializableFieldDefault(24)] private AosAttributes AttributesDefaultValue() => new(this); + [SerializedIgnoreDupe] [SerializableField(25, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosWeaponAttributes _weaponAttributes; @@ -145,6 +147,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ShouldSerializePlayerConstructed() => _playerConstructed; + [SerializedIgnoreDupe] [SerializableField(27, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosSkillBonuses _skillBonuses; @@ -165,6 +168,7 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ShouldSerializeSlayer2() => _slayer2 != SlayerName.None; + [SerializedIgnoreDupe] [SerializableField(29, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] private AosElementAttributes _aosElementDamages; @@ -932,6 +936,12 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab weap.AosElementDamages = new AosElementAttributes(newItem, AosElementDamages); weap.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses); weap.WeaponAttributes = new AosWeaponAttributes(newItem, WeaponAttributes); + + // Set hue again because of resource + weap.Hue = Hue; + // Set HP/Max again because of durability + weap.HitPoints = HitPoints; + weap.MaxHitPoints = MaxHitPoints; } public int GetDurabilityBonus() diff --git a/Projects/UOContent/Multis/Houses/HouseTeleporter.cs b/Projects/UOContent/Multis/Houses/HouseTeleporter.cs index 087cf3cdf..17a2d9be8 100644 --- a/Projects/UOContent/Multis/Houses/HouseTeleporter.cs +++ b/Projects/UOContent/Multis/Houses/HouseTeleporter.cs @@ -11,6 +11,7 @@ namespace Server.Items; [SerializationGenerator(2, false)] public partial class HouseTeleporter : Item, ISecurable { + [SerializedIgnoreDupe] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level;