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:
Kamron Batman 2024-06-02 15:04:54 -07:00 committed by GitHub
parent a8d3d2773e
commit 9c7cb5d778
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 502 additions and 248 deletions

View file

@ -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>;

View file

@ -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);

View file

@ -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;

View file

@ -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; }