feat(items): implement Unwieldy negative property

This commit is contained in:
Crome696 2026-07-11 15:47:44 +02:00
parent e9d0d85385
commit d8b9a2ca62
8 changed files with 1053 additions and 3 deletions

View file

@ -0,0 +1,314 @@
using System;
using System.Collections.Generic;
using Server;
using Server.Items;
using Server.Mobiles;
using Server.Text;
using Xunit;
namespace UOContent.Tests;
[Collection("Sequential UOContent Tests")]
public class UnwieldyPropertyTests
{
private const int SingleWeightCliloc = 1072788;
private const int MultipleWeightCliloc = 1072789;
private const double OriginalWeight = 7.25;
private const double UnwieldyWeight = 50;
[Fact]
public void NegativeAttribute_ReservesTheNextNonCollidingBitForUnwieldy()
{
Assert.True(Enum.TryParse<NegativeAttribute>("Unwieldy", out var unwieldy));
Assert.Equal(0x00000010, (int)unwieldy);
}
[Fact]
public void NegativeAttributes_StoresDupesAndSerializesUnwieldyOnSupportedFamilies()
{
var previousExpansion = Core.Expansion;
try
{
Core.Expansion = Expansion.HS;
AssertStoresDupesAndSerializesUnwieldy(new TestKatana(), new TestKatana(), new TestKatana());
AssertStoresDupesAndSerializesUnwieldy(new LeatherChest(), new LeatherChest(), new LeatherChest());
AssertStoresDupesAndSerializesUnwieldy(new Buckler(), new Buckler(), new Buckler());
}
finally
{
Core.Expansion = previousExpansion;
}
}
[Fact]
public void NegativeAttributes_DoesNotStoreUnwieldyOnUnsupportedJewelry()
{
var previousExpansion = Core.Expansion;
var ring = new GoldRing { Weight = OriginalWeight };
try
{
Core.Expansion = Expansion.HS;
ring.NegativeAttributes.Unwieldy = 1;
Assert.Equal(0, ring.NegativeAttributes.Unwieldy);
Assert.Equal(OriginalWeight, ring.Weight);
}
finally
{
Core.Expansion = previousExpansion;
ring.Delete();
}
}
[Fact]
public void Unwieldy_UsesStandardWeightTotalsAndWeightPresentationInHighSeas()
{
var previousExpansion = Core.Expansion;
try
{
Core.Expansion = Expansion.HS;
AssertUsesStandardWeightTotalsAndPresentation(new TestKatana());
AssertUsesStandardWeightTotalsAndPresentation(new LeatherChest());
AssertUsesStandardWeightTotalsAndPresentation(new Buckler());
}
finally
{
Core.Expansion = previousExpansion;
}
}
[Fact]
public void Unwieldy_IsNoOpBeforeHighSeasIncludingAfterDeserialization()
{
var previousExpansion = Core.Expansion;
var source = new TestKatana { Weight = OriginalWeight };
var direct = new TestKatana { Weight = OriginalWeight };
var deserialized = new TestKatana();
try
{
Core.Expansion = Expansion.HS;
source.NegativeAttributes.Unwieldy = 1;
var writer = new BufferWriter(true);
source.Serialize(writer);
var buffer = new byte[writer.Position];
writer.Buffer.AsSpan(0, (int)writer.Position).CopyTo(buffer);
Core.Expansion = Expansion.SA;
direct.NegativeAttributes.Unwieldy = 1;
Assert.Equal(1, direct.NegativeAttributes.Unwieldy);
Assert.Equal(OriginalWeight, direct.Weight);
AssertShowsWeightWithoutUnwieldyRow(direct, (int)Math.Ceiling(OriginalWeight));
var reader = new BufferReader(buffer);
deserialized.Deserialize(reader);
Assert.Equal(buffer.Length, reader.Position);
Assert.Equal(1, deserialized.NegativeAttributes.Unwieldy);
Assert.Equal(OriginalWeight, deserialized.Weight);
AssertShowsWeightWithoutUnwieldyRow(deserialized, (int)Math.Ceiling(OriginalWeight));
}
finally
{
Core.Expansion = previousExpansion;
source.Delete();
direct.Delete();
deserialized.Delete();
}
}
[Fact]
public void RunicAttributeGeneration_DoesNotRollUnwieldy()
{
var previousExpansion = Core.Expansion;
var weapon = new TestKatana();
var armor = new LeatherChest();
var shield = new Buckler();
try
{
Core.Expansion = Expansion.HS;
BaseRunicTool.ApplyAttributesTo(weapon, false, 0, 25, 100, 100);
BaseRunicTool.ApplyAttributesTo(armor, false, 0, 25, 100, 100);
BaseRunicTool.ApplyAttributesTo(shield, false, 0, 25, 100, 100);
Assert.Equal(0, weapon.NegativeAttributes.Unwieldy);
Assert.Equal(0, armor.NegativeAttributes.Unwieldy);
Assert.Equal(0, shield.NegativeAttributes.Unwieldy);
}
finally
{
Core.Expansion = previousExpansion;
weapon.Delete();
armor.Delete();
shield.Delete();
}
}
private static void AssertStoresDupesAndSerializesUnwieldy<TItem>(TItem source, TItem dupe, TItem deserialized)
where TItem : Item
{
try
{
source.Weight = OriginalWeight;
GetNegativeAttributes(source).Unwieldy = 1;
source.Dupe(dupe);
Assert.Equal(1, GetNegativeAttributes(dupe).Unwieldy);
Assert.Equal(UnwieldyWeight, dupe.Weight);
GetNegativeAttributes(dupe).Unwieldy = 0;
Assert.Equal(OriginalWeight, dupe.Weight);
var writer = new BufferWriter(true);
source.Serialize(writer);
var buffer = new byte[writer.Position];
writer.Buffer.AsSpan(0, (int)writer.Position).CopyTo(buffer);
var reader = new BufferReader(buffer);
deserialized.Deserialize(reader);
Assert.Equal(buffer.Length, reader.Position);
Assert.Equal(1, GetNegativeAttributes(deserialized).Unwieldy);
Assert.Equal(UnwieldyWeight, deserialized.Weight);
GetNegativeAttributes(deserialized).Unwieldy = 0;
Assert.Equal(OriginalWeight, deserialized.Weight);
}
finally
{
source.Delete();
dupe.Delete();
deserialized.Delete();
}
}
private static void AssertUsesStandardWeightTotalsAndPresentation(Item item)
{
var player = CreatePlayerMobile();
try
{
item.Weight = OriginalWeight;
player.Backpack.AddItem(item);
var originalPileWeight = item.PileWeight;
var originalContainerWeight = player.Backpack.TotalWeight;
var originalMobileWeight = player.TotalWeight;
GetNegativeAttributes(item).Unwieldy = 1;
Assert.Equal(UnwieldyWeight, item.Weight);
Assert.Equal((int)UnwieldyWeight, item.PileWeight);
Assert.Equal(originalContainerWeight - originalPileWeight + (int)UnwieldyWeight, player.Backpack.TotalWeight);
Assert.Equal(originalMobileWeight - originalPileWeight + (int)UnwieldyWeight, player.TotalWeight);
AssertShowsWeightWithoutUnwieldyRow(item, (int)UnwieldyWeight);
GetNegativeAttributes(item).Unwieldy = 2;
Assert.Equal(1, GetNegativeAttributes(item).Unwieldy);
Assert.Equal((int)UnwieldyWeight, item.PileWeight);
Assert.Equal(originalContainerWeight - originalPileWeight + (int)UnwieldyWeight, player.Backpack.TotalWeight);
Assert.Equal(originalMobileWeight - originalPileWeight + (int)UnwieldyWeight, player.TotalWeight);
GetNegativeAttributes(item).Unwieldy = 0;
Assert.Equal(OriginalWeight, item.Weight);
Assert.Equal(originalPileWeight, item.PileWeight);
Assert.Equal(originalContainerWeight, player.Backpack.TotalWeight);
Assert.Equal(originalMobileWeight, player.TotalWeight);
AssertShowsWeightWithoutUnwieldyRow(item, originalPileWeight);
GetNegativeAttributes(item).Unwieldy = 0;
Assert.Equal(0, GetNegativeAttributes(item).Unwieldy);
Assert.Equal(OriginalWeight, item.Weight);
Assert.Equal(originalContainerWeight, player.Backpack.TotalWeight);
Assert.Equal(originalMobileWeight, player.TotalWeight);
}
finally
{
item.Delete();
player.Delete();
}
}
private static void AssertShowsWeightWithoutUnwieldyRow(Item item, int weight)
{
var properties = new RecordingPropertyList();
item.GetProperties(properties);
Assert.Contains(properties.Entries, entry =>
entry.Number == (weight == 1 ? SingleWeightCliloc : MultipleWeightCliloc) && entry.Argument == weight.ToString());
Assert.DoesNotContain(properties.Entries, entry =>
entry.Argument.Contains("Unwieldy", StringComparison.OrdinalIgnoreCase));
}
private static NegativeAttributes GetNegativeAttributes(Item item) => item switch
{
BaseWeapon weapon => weapon.NegativeAttributes,
BaseArmor armor => armor.NegativeAttributes,
_ => throw new ArgumentException($"Unsupported Unwieldy host: {item.GetType().FullName}", nameof(item))
};
private static PlayerMobile CreatePlayerMobile()
{
var player = new PlayerMobile(World.NewMobile);
player.DefaultMobileInit();
player.AddItem(new Backpack());
return player;
}
private class TestKatana : Katana
{
}
private sealed record PropertyEntry(int Number, string Argument);
private sealed class RecordingPropertyList : IPropertyList
{
private string _interpolated = string.Empty;
public List<PropertyEntry> Entries { get; } = [];
public void Reset()
{
}
public void Terminate()
{
}
public void Add(int number) => Entries.Add(new PropertyEntry(number, string.Empty));
public void Add(int number, string argument) => Entries.Add(new PropertyEntry(number, argument));
public void Add(ReadOnlySpan<char> argument) => Entries.Add(new PropertyEntry(0, argument.ToString()));
public void Add(int number, ReadOnlySpan<char> argument) => Entries.Add(new PropertyEntry(number, argument.ToString()));
public void AddChunked(ReadOnlySpan<char> text) => Entries.Add(new PropertyEntry(0, text.ToString()));
public OplTextBlock TextBlock() => new(this);
public void Add(int number, int value) => Entries.Add(new PropertyEntry(number, value.ToString()));
public void AddLocalized(int value) => Entries.Add(new PropertyEntry(0, value.ToString()));
public void AddLocalized(int number, int value) => Entries.Add(new PropertyEntry(number, value.ToString()));
public void Add(ref IPropertyList.InterpolatedStringHandler handler) => Entries.Add(new PropertyEntry(0, _interpolated));
public void Add(int number, ref IPropertyList.InterpolatedStringHandler handler) =>
Entries.Add(new PropertyEntry(number, _interpolated));
public void InitializeInterpolation(int literalLength, int formattedCount) => _interpolated = string.Empty;
public void AppendLiteral(string value) => _interpolated += value;
public void AppendFormatted<T>(T value) => _interpolated += value;
public void AppendFormatted<T>(T value, string format) =>
_interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value;
public void AppendFormatted<T>(T value, int alignment) => _interpolated += value;
public void AppendFormatted<T>(T value, int alignment, string format) =>
_interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value;
public void AppendFormatted(ReadOnlySpan<char> value) => _interpolated += value.ToString();
public void AppendFormatted(ReadOnlySpan<char> value, int alignment, string format = null) =>
_interpolated += value.ToString();
public void AppendFormatted(object value, int alignment = 0, string format = null) => _interpolated += value;
public void AppendFormatted(string value) => _interpolated += value;
public void AppendFormatted(string value, int alignment, string format = null) => _interpolated += value;
}
}

View file

@ -96,6 +96,38 @@ public partial class BaseArmor
_absorptionAttributes = AbsorptionAttributesDefaultValue();
}
private void MigrateFrom(V11Content content)
{
_attributes = content.Attributes ?? AttributesDefaultValue();
_armorAttributes = content.ArmorAttributes ?? ArmorAttributesDefaultValue();
_physicalBonus = content.PhysicalBonus ?? 0;
_fireBonus = content.FireBonus ?? 0;
_coldBonus = content.ColdBonus ?? 0;
_poisonBonus = content.PoisonBonus ?? 0;
_energyBonus = content.EnergyBonus ?? 0;
_identified = content.Identified;
_maxHitPoints = content.MaxHitPoints ?? 0;
_hitPoints = content.HitPoints ?? 0;
_crafter = content.Crafter;
_quality = content.Quality ?? ArmorQuality.Regular;
_durability = content.Durability ?? ArmorDurabilityLevel.Regular;
_protectionLevel = content.ProtectionLevel ?? ArmorProtectionLevel.Regular;
_resource = content.Resource ?? DefaultResource;
_armorBase = content.BaseArmorRating ?? -1;
_strBonus = content.StrBonus ?? -1;
_dexBonus = content.DexBonus ?? -1;
_intBonus = content.IntBonus ?? -1;
_strReq = content.StrRequirement ?? -1;
_dexReq = content.DexRequirement ?? -1;
_intReq = content.IntRequirement ?? -1;
_meditate = content.MeditationAllowance ?? (AMA)(-1);
_skillBonuses = content.SkillBonuses ?? SkillBonusesDefaultValue();
_playerConstructed = content.PlayerConstructed;
_negativeAttributes = content.NegativeAttributes ?? NegativeAttributesDefaultValue();
_absorptionAttributes = content.AbsorptionAttributes ?? AbsorptionAttributesDefaultValue();
_unwieldyOriginalWeight = -1;
}
// Version 7 (pre-codegen)
private void Deserialize(IGenericReader reader, int version)
{

View file

@ -12,7 +12,7 @@ using AMT = Server.Items.ArmorMaterialType;
namespace Server.Items
{
[SerializationGenerator(11, false)]
[SerializationGenerator(12, false)]
public abstract partial class BaseArmor
: Item, IScissorable, IFactionItem, ICraftable, IWearableDurability, IAosItem, IIdentifiable
{
@ -173,6 +173,12 @@ namespace Server.Items
[SerializableFieldDefault(26)]
private AbsorptionAttributes AbsorptionAttributesDefaultValue() => new(this);
[SerializableField(27)]
private double _unwieldyOriginalWeight = -1;
[SerializableFieldSaveFlag(27)]
private bool ShouldSerializeUnwieldyOriginalWeight() => _unwieldyOriginalWeight >= 0;
private FactionItem m_FactionState;
public BaseArmor(int itemID) : base(itemID)
@ -841,6 +847,8 @@ namespace Server.Items
armor.NegativeAttributes = new NegativeAttributes(newItem, NegativeAttributes);
armor.AbsorptionAttributes = new AbsorptionAttributes(newItem, AbsorptionAttributes);
armor.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses);
armor._unwieldyOriginalWeight = _unwieldyOriginalWeight;
armor.ReconcileUnwieldyWeight();
// Set hue again because of resource
armor.Hue = Hue;
@ -1064,6 +1072,7 @@ namespace Server.Items
{
_negativeAttributes ??= NegativeAttributesDefaultValue();
_absorptionAttributes ??= AbsorptionAttributesDefaultValue();
ReconcileUnwieldyWeight();
var m = Parent as Mobile;
@ -1104,6 +1113,60 @@ namespace Server.Items
m?.CheckStatTimers();
}
internal void SetUnwieldy(int value)
{
var wasUnwieldy = NegativeAttributes[NegativeAttribute.Unwieldy] != 0;
var isUnwieldy = value != 0;
NegativeAttributes[NegativeAttribute.Unwieldy] = isUnwieldy ? 1 : 0;
if (!Core.HS)
{
if (!isUnwieldy)
{
_unwieldyOriginalWeight = -1;
}
return;
}
if (isUnwieldy)
{
if (!wasUnwieldy || _unwieldyOriginalWeight < 0)
{
_unwieldyOriginalWeight = Weight;
}
Weight = 50;
}
else if (wasUnwieldy && _unwieldyOriginalWeight >= 0)
{
Weight = _unwieldyOriginalWeight;
_unwieldyOriginalWeight = -1;
}
}
private void ReconcileUnwieldyWeight()
{
if (NegativeAttributes[NegativeAttribute.Unwieldy] == 0)
{
return;
}
if (Core.HS)
{
if (_unwieldyOriginalWeight < 0)
{
_unwieldyOriginalWeight = Weight;
}
Weight = 50;
}
else if (_unwieldyOriginalWeight >= 0)
{
Weight = _unwieldyOriginalWeight;
}
}
public override bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted)
{
if (!Ethic.CheckTrade(from, to, newOwner, this))

View file

@ -75,4 +75,42 @@ public abstract partial class BaseWeapon
_extendedWeaponAttributes = content.ExtendedWeaponAttributes ?? ExtendedWeaponAttributesDefaultValue();
_negativeAttributes = NegativeAttributesDefaultValue();
}
private void MigrateFrom(V12Content content)
{
_damageLevel = content.DamageLevel ?? WeaponDamageLevel.Regular;
_accuracyLevel = content.AccuracyLevel ?? WeaponAccuracyLevel.Regular;
_durabilityLevel = content.DurabilityLevel ?? WeaponDurabilityLevel.Regular;
_quality = content.Quality ?? WeaponQuality.Regular;
_hitPoints = content.HitPoints ?? 0;
_maxHitPoints = content.MaxHitPoints ?? 0;
_slayer = content.Slayer ?? SlayerName.None;
_poison = content.Poison;
_poisonCharges = content.PoisonCharges ?? 0;
_crafter = content.Crafter;
_identified = content.Identified;
_strRequirement = content.StrRequirement ?? -1;
_dexRequirement = content.DexRequirement ?? -1;
_intRequirement = content.IntRequirement ?? -1;
_minDamage = content.MinDamage ?? -1;
_maxDamage = content.MaxDamage ?? -1;
_hitSound = content.HitSound ?? -1;
_missSound = content.MissSound ?? -1;
_speed = content.Speed ?? -1;
_maxRange = content.MaxRange ?? -1;
_skill = content.Skill ?? (SkillName)(-1);
_type = content.Type ?? (WeaponType)(-1);
_animation = content.Animation ?? (WeaponAnimation)(-1);
_resource = content.Resource ?? CraftResource.Iron;
_attributes = content.Attributes ?? AttributesDefaultValue();
_weaponAttributes = content.WeaponAttributes ?? WeaponAttributesDefaultValue();
_playerConstructed = content.PlayerConstructed;
_skillBonuses = content.SkillBonuses ?? SkillBonusesDefaultValue();
_slayer2 = content.Slayer2 ?? SlayerName.None;
_aosElementDamages = content.AosElementDamages ?? AosElementAttributesDefaultValue();
_engravedText = content.EngravedText;
_extendedWeaponAttributes = content.ExtendedWeaponAttributes ?? ExtendedWeaponAttributesDefaultValue();
_negativeAttributes = content.NegativeAttributes ?? NegativeAttributesDefaultValue();
_unwieldyOriginalWeight = -1;
}
}

View file

@ -28,7 +28,7 @@ public interface ISlayer
SlayerName Slayer2 { get; set; }
}
[SerializationGenerator(12, false)]
[SerializationGenerator(13, false)]
public abstract partial class BaseWeapon
: Item, IWeapon, IFactionItem, ICraftable, ISlayer, IDurability, IAosItem, IIdentifiable
{
@ -210,6 +210,10 @@ public abstract partial class BaseWeapon
[SerializableFieldDefault(32)]
private NegativeAttributes NegativeAttributesDefaultValue() => new(this);
// Field 33 intentionally has no save flag; BaseWeapon's legacy save-flag mask is already at the high bit.
[SerializableField(33)]
private double _unwieldyOriginalWeight = -1;
private FactionItem m_FactionState;
private SkillMod m_SkillMod, m_MageMod;
private int _lastParryChance;
@ -985,6 +989,8 @@ public abstract partial class BaseWeapon
weap.NegativeAttributes = new NegativeAttributes(newItem, NegativeAttributes);
weap.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses);
weap.WeaponAttributes = new AosWeaponAttributes(newItem, WeaponAttributes);
weap._unwieldyOriginalWeight = _unwieldyOriginalWeight;
weap.ReconcileUnwieldyWeight();
// Set hue again because of resource
weap.Hue = Hue;
@ -3980,6 +3986,7 @@ public abstract partial class BaseWeapon
{
_extendedWeaponAttributes ??= ExtendedWeaponAttributesDefaultValue();
_negativeAttributes ??= NegativeAttributesDefaultValue();
ReconcileUnwieldyWeight();
var parentMobile = Parent as Mobile;
@ -4031,6 +4038,61 @@ public abstract partial class BaseWeapon
}
}
internal void SetUnwieldy(int value)
{
var wasUnwieldy = NegativeAttributes[NegativeAttribute.Unwieldy] != 0;
var isUnwieldy = value != 0;
NegativeAttributes[NegativeAttribute.Unwieldy] = isUnwieldy ? 1 : 0;
if (!Core.HS)
{
if (!isUnwieldy)
{
_unwieldyOriginalWeight = -1;
}
return;
}
if (isUnwieldy)
{
if (!wasUnwieldy || _unwieldyOriginalWeight < 0)
{
_unwieldyOriginalWeight = Weight;
}
Weight = 50;
}
else if (wasUnwieldy && _unwieldyOriginalWeight >= 0)
{
Weight = _unwieldyOriginalWeight;
_unwieldyOriginalWeight = -1;
}
}
private void ReconcileUnwieldyWeight()
{
if (NegativeAttributes[NegativeAttribute.Unwieldy] == 0)
{
return;
}
if (Core.HS)
{
if (_unwieldyOriginalWeight < 0)
{
_unwieldyOriginalWeight = Weight;
}
Weight = 50;
}
else if (_unwieldyOriginalWeight >= 0)
{
Weight = _unwieldyOriginalWeight;
}
}
private class ResetEquipTimer : Timer
{
private readonly Mobile m_Mobile;

View file

@ -0,0 +1,243 @@
{
"version": 12,
"type": "Server.Items.BaseArmor",
"properties": [
{
"name": "Attributes",
"type": "Server.AosAttributes",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "ArmorAttributes",
"type": "Server.AosArmorAttributes",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "PhysicalBonus",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "FireBonus",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "ColdBonus",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "PoisonBonus",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "EnergyBonus",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "Identified",
"type": "bool",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "MaxHitPoints",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "HitPoints",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "Crafter",
"type": "string",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "Quality",
"type": "Server.Items.ArmorQuality",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "Durability",
"type": "Server.Items.ArmorDurabilityLevel",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "ProtectionLevel",
"type": "Server.Items.ArmorProtectionLevel",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "Resource",
"type": "Server.Items.CraftResource",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "BaseArmorRating",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "StrBonus",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "DexBonus",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "IntBonus",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "StrRequirement",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "DexRequirement",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "IntRequirement",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"EncodedInt"
]
},
{
"name": "MeditationAllowance",
"type": "Server.Items.ArmorMeditationAllowance",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "SkillBonuses",
"type": "Server.AosSkillBonuses",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "PlayerConstructed",
"type": "bool",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "NegativeAttributes",
"type": "Server.NegativeAttributes",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "AbsorptionAttributes",
"type": "Server.AbsorptionAttributes",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "UnwieldyOriginalWeight",
"type": "double",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
}
]
}

View file

@ -0,0 +1,279 @@
{
"version": 13,
"type": "Server.Items.BaseWeapon",
"properties": [
{
"name": "DamageLevel",
"type": "Server.Items.WeaponDamageLevel",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "AccuracyLevel",
"type": "Server.Items.WeaponAccuracyLevel",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "DurabilityLevel",
"type": "Server.Items.WeaponDurabilityLevel",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "Quality",
"type": "Server.Items.WeaponQuality",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "HitPoints",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "MaxHitPoints",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "Slayer",
"type": "Server.Items.SlayerName",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "Poison",
"type": "Server.Poison",
"usesSaveFlag": true,
"rule": "PrimitiveUOTypeMigrationRule",
"ruleArguments": [
"Poison"
]
},
{
"name": "PoisonCharges",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "Crafter",
"type": "string",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "Identified",
"type": "bool",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "StrRequirement",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "DexRequirement",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "IntRequirement",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "MinDamage",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "MaxDamage",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "HitSound",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "MissSound",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "Speed",
"type": "float",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "MaxRange",
"type": "int",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "Skill",
"type": "Server.SkillName",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "Type",
"type": "Server.Items.WeaponType",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "Animation",
"type": "Server.Items.WeaponAnimation",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "Resource",
"type": "Server.Items.CraftResource",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "Attributes",
"type": "Server.AosAttributes",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "WeaponAttributes",
"type": "Server.AosWeaponAttributes",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "PlayerConstructed",
"type": "bool",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "SkillBonuses",
"type": "Server.AosSkillBonuses",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "Slayer2",
"type": "Server.Items.SlayerName",
"usesSaveFlag": true,
"rule": "EnumMigrationRule"
},
{
"name": "AosElementDamages",
"type": "Server.AosElementAttributes",
"usesSaveFlag": true,
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "EngravedText",
"type": "string",
"usesSaveFlag": true,
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
},
{
"name": "ExtendedWeaponAttributes",
"type": "Server.ExtendedWeaponAttributes",
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "NegativeAttributes",
"type": "Server.NegativeAttributes",
"rule": "RawSerializableMigrationRule",
"ruleArguments": [
"DeserializationRequiresParent"
]
},
{
"name": "UnwieldyOriginalWeight",
"type": "double",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
""
]
}
]
}

View file

@ -1563,7 +1563,8 @@ namespace Server
Prized = 0x00000001,
Massive = 0x00000002,
Brittle = 0x00000004,
Antique = 0x00000008
Antique = 0x00000008,
Unwieldy = 0x00000010
}
public sealed class NegativeAttributes : BaseAttributes
@ -1610,6 +1611,24 @@ namespace Server
set => this[NegativeAttribute.Brittle] = Owner is BaseWeapon or BaseArmor ? value : 0;
}
[CommandProperty(AccessLevel.GameMaster)]
public int Unwieldy
{
get => Owner is BaseWeapon or BaseArmor ? this[NegativeAttribute.Unwieldy] : 0;
set
{
switch (Owner)
{
case BaseWeapon weapon:
weapon.SetUnwieldy(value);
break;
case BaseArmor armor:
armor.SetUnwieldy(value);
break;
}
}
}
public static bool IsAntique(Item item)
{
if (!Core.HS)