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
|
|
@ -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 <count>")]
|
||||
[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 <count>")]
|
||||
[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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue