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

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

View file

@ -40,7 +40,7 @@ namespace Server.Items
if (_state != value)
{
_state = Math.Clamp(value, 0, 4);
this.MarkDirty();
MarkDirty();
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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++)
{

View file

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

View file

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

View file

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

View file

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

View file

@ -10,6 +10,7 @@ namespace Server.Items;
[SerializationGenerator(1)]
public partial class TapestryOfSosaria : Item, ISecurable
{
[SerializedIgnoreDupe]
[SerializableField(0)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private SecureLevel _level;

View file

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

View file

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

View file

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