fix: Fixes dupe property copying. Adds IgnoreDupe (#1811)
## Summary ### Changes - Adds `[IgnoreDupe]` and `[SerializedIgnoreDupe]` - Updates all _known_ classes that need the attribute. Some might be missing, please helps us find them! - Adds `Item.Dupe()` command and encapsulates `CopyProperties` and `OnAfterDuped`. This is also overridable. - Updates Dupe command to use the new logic. - Fixes duping multiple kinds of objects that used to be outright broken. ### Bug Fixes - Fixes issue with durability after duping - Fixes issue with hue after duping > [!Note] > **Developer Note** > Customizing how duping an item works now requires two steps: > 1. Add `[IgnoreDupe]` or `[SerializedIgnoreDupe]` to the property/field > 2. Add custom logic in an `OnAfterDuped` override > > When do you need to do this? > *When the property being copied is not a primitive, and you need to manually deep-clone the contents of the property such as with Lists, Dictionaries, or sub classes.*
This commit is contained in:
parent
a8d3d2773e
commit
9c7cb5d778
34 changed files with 502 additions and 248 deletions
|
|
@ -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<IgnoreDupeAttribute>;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<Item>, ISpawnable, IObjectPropertyListEnt
|
|||
}
|
||||
|
||||
// Sectors
|
||||
[IgnoreDupe]
|
||||
public Item Next { get; set; }
|
||||
|
||||
[IgnoreDupe]
|
||||
public Item Previous { get; set; }
|
||||
|
||||
[IgnoreDupe]
|
||||
public bool OnLinkList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -586,6 +592,7 @@ public class Item : IHued, IComparable<Item>, 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<Item>, 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));
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -768,13 +776,17 @@ public class Item : IHued, IComparable<Item>, 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<Item>, ISpawnable, IObjectPropertyListEnt
|
|||
ClearProperties();
|
||||
}
|
||||
|
||||
[IgnoreDupe]
|
||||
public ISpawner Spawner
|
||||
{
|
||||
get => LookupCompactInfo()?.m_Spawner;
|
||||
|
|
@ -3341,21 +3354,41 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
|
|||
}
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _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;
|
||||
|
|
|
|||
|
|
@ -2256,13 +2256,17 @@ public partial class Mobile : IHued, IComparable<Mobile>, 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; }
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue