From d8b9a2ca62ffed5ec6bf6b2990ae58ec2ecd2824 Mon Sep 17 00:00:00 2001 From: Crome696 Date: Sat, 11 Jul 2026 15:47:44 +0200 Subject: [PATCH] feat(items): implement Unwieldy negative property --- .../Tests/Items/UnwieldyPropertyTests.cs | 314 ++++++++++++++++++ .../Items/Armor/BaseArmor.Migrations.cs | 32 ++ Projects/UOContent/Items/Armor/BaseArmor.cs | 65 +++- .../Items/Weapons/BaseWeapon.Migrations.cs | 38 +++ .../UOContent/Items/Weapons/BaseWeapon.cs | 64 +++- .../Server.Items.BaseArmor.v12.json | 243 ++++++++++++++ .../Server.Items.BaseWeapon.v13.json | 279 ++++++++++++++++ Projects/UOContent/Misc/AOS.cs | 21 +- 8 files changed, 1053 insertions(+), 3 deletions(-) create mode 100644 Projects/UOContent.Tests/Tests/Items/UnwieldyPropertyTests.cs create mode 100644 Projects/UOContent/Migrations/Server.Items.BaseArmor.v12.json create mode 100644 Projects/UOContent/Migrations/Server.Items.BaseWeapon.v13.json diff --git a/Projects/UOContent.Tests/Tests/Items/UnwieldyPropertyTests.cs b/Projects/UOContent.Tests/Tests/Items/UnwieldyPropertyTests.cs new file mode 100644 index 000000000..4e8209a07 --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Items/UnwieldyPropertyTests.cs @@ -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("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 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 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 argument) => Entries.Add(new PropertyEntry(0, argument.ToString())); + public void Add(int number, ReadOnlySpan argument) => Entries.Add(new PropertyEntry(number, argument.ToString())); + public void AddChunked(ReadOnlySpan 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 value) => _interpolated += value; + public void AppendFormatted(T value, string format) => + _interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value; + public void AppendFormatted(T value, int alignment) => _interpolated += value; + public void AppendFormatted(T value, int alignment, string format) => + _interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value; + public void AppendFormatted(ReadOnlySpan value) => _interpolated += value.ToString(); + public void AppendFormatted(ReadOnlySpan 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; + } +} diff --git a/Projects/UOContent/Items/Armor/BaseArmor.Migrations.cs b/Projects/UOContent/Items/Armor/BaseArmor.Migrations.cs index 4968dbf87..b7142af5a 100644 --- a/Projects/UOContent/Items/Armor/BaseArmor.Migrations.cs +++ b/Projects/UOContent/Items/Armor/BaseArmor.Migrations.cs @@ -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) { diff --git a/Projects/UOContent/Items/Armor/BaseArmor.cs b/Projects/UOContent/Items/Armor/BaseArmor.cs index ad9a04db6..24e73e7c9 100644 --- a/Projects/UOContent/Items/Armor/BaseArmor.cs +++ b/Projects/UOContent/Items/Armor/BaseArmor.cs @@ -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)) diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs index 661ebf945..019a79aa2 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs @@ -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; + } } diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 8e78db4c8..946a579ce 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -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; diff --git a/Projects/UOContent/Migrations/Server.Items.BaseArmor.v12.json b/Projects/UOContent/Migrations/Server.Items.BaseArmor.v12.json new file mode 100644 index 000000000..b505eb82a --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.BaseArmor.v12.json @@ -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": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v13.json b/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v13.json new file mode 100644 index 000000000..ad1661c28 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v13.json @@ -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": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Misc/AOS.cs b/Projects/UOContent/Misc/AOS.cs index fd5d8e6d8..1ee77ee4e 100644 --- a/Projects/UOContent/Misc/AOS.cs +++ b/Projects/UOContent/Misc/AOS.cs @@ -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)