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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<IBOBEntry> _entries;
|
||||
|
|
@ -40,12 +42,39 @@ public partial class BulkOrderBook : Item, ISecurable
|
|||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
|
||||
_entries = new List<IBOBEntry>();
|
||||
_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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ public enum PlantStatus
|
|||
[SerializationGenerator(3, false)]
|
||||
public partial class PlantItem : Item, ISecurable
|
||||
{
|
||||
public static List<PlantItem> 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<PlantItem> 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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<SpawnerEntry> _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<ISpawnable, SpawnerEntry> 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<ISpawnable, SpawnerEntry>();
|
||||
newSpawner.Entries = new List<SpawnerEntry>();
|
||||
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<SpawnerEntry>();
|
||||
Entries = [];
|
||||
Spawned = new Dictionary<ISpawnable, SpawnerEntry>();
|
||||
|
||||
DoTimer(TimeSpan.FromSeconds(1));
|
||||
|
|
@ -394,7 +398,7 @@ public abstract partial class BaseSpawner : Item, ISpawner
|
|||
|
||||
public void Defrag()
|
||||
{
|
||||
Entries ??= new List<SpawnerEntry>();
|
||||
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<string>()
|
||||
? []
|
||||
: 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<string>()
|
||||
? []
|
||||
: entry.Parameters.Trim().Split(' ');
|
||||
|
||||
if (paramargs.Length == 0)
|
||||
|
|
|
|||
|
|
@ -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<int> _events;
|
||||
|
||||
|
|
@ -90,7 +94,7 @@ namespace Server.Items
|
|||
|
||||
_water.Maintain = Utility.RandomMinMax(1, 3);
|
||||
|
||||
_events = new List<int>();
|
||||
_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<int>();
|
||||
_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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace Server.Items
|
|||
if (_state != value)
|
||||
{
|
||||
_state = Math.Clamp(value, 0, 4);
|
||||
this.MarkDirty();
|
||||
MarkDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<BookPageInfo>();
|
||||
private BookPageInfo[] PagesDefaultValue() => DefaultContent?.Copy() ?? Array.Empty<BookPageInfo>();
|
||||
|
||||
[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
|
||||
|
|
|
|||
|
|
@ -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<string>();
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<PlayerBBMessage> _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<ContextMenuEntry> list)
|
||||
{
|
||||
base.GetContextMenuEntries(from, list);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<RunebookEntry> _entries;
|
||||
|
||||
|
|
@ -281,7 +283,7 @@ public partial class Runebook : Item, ISecurable, ICraftable
|
|||
return;
|
||||
}
|
||||
|
||||
book.Entries = new List<RunebookEntry>();
|
||||
book.Entries = [];
|
||||
|
||||
for (var i = 0; i < Entries.Count; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -99,9 +99,11 @@ public partial class DawnsMusicBox : Item, ISecurable
|
|||
MusicName.SelimsBar, MusicName.SerpentIsleCombat_U7, MusicName.ValoriaShips
|
||||
};
|
||||
|
||||
[SerializedIgnoreDupe]
|
||||
[SerializableField(0, setter: "private")]
|
||||
private List<MusicName> _tracks;
|
||||
|
||||
[SerializedIgnoreDupe]
|
||||
[SerializableField(1)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private SecureLevel _level;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Server.Items;
|
|||
[SerializationGenerator(1)]
|
||||
public partial class TapestryOfSosaria : Item, ISecurable
|
||||
{
|
||||
[SerializedIgnoreDupe]
|
||||
[SerializableField(0)]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private SecureLevel _level;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue