diff --git a/Projects/UOContent.Tests/Tests/Items/PrizedPropertyTests.cs b/Projects/UOContent.Tests/Tests/Items/PrizedPropertyTests.cs new file mode 100644 index 000000000..da43699b7 --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Items/PrizedPropertyTests.cs @@ -0,0 +1,220 @@ +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 PrizedPropertyTests +{ + private const int PrizedCliloc = 1154910; + + [Fact] + public void NegativeAttributes_StoresAndDupesPrizedOnSupportedFamilies() + { + AssertStoresAndDupesPrized(new TestKatana(), new TestKatana()); + AssertStoresAndDupesPrized(new LeatherChest(), new LeatherChest()); + AssertStoresAndDupesPrized(new Buckler(), new Buckler()); + AssertStoresAndDupesPrized(new GoldRing(), new GoldRing()); + } + + [Fact] + public void GetProperties_GatesPrizedTooltipToHighSeasOnSupportedFamilies() + { + var previousExpansion = Core.Expansion; + var items = CreateSupportedItems(); + + try + { + foreach (var item in items) + { + GetNegativeAttributes(item).Prized = 1; + + Core.Expansion = Expansion.ML; + var preHighSeas = new RecordingPropertyList(); + item.GetProperties(preHighSeas); + Assert.DoesNotContain(preHighSeas.Numbers, number => number == PrizedCliloc); + + Core.Expansion = Expansion.HS; + var highSeas = new RecordingPropertyList(); + item.GetProperties(highSeas); + Assert.Contains(highSeas.Numbers, number => number == PrizedCliloc); + } + } + finally + { + Core.Expansion = previousExpansion; + DeleteItems(items); + } + } + + [Fact] + public void GetInsuranceCost_DoublesPrizedItemsOnlyInHighSeas() + { + var previousExpansion = Core.Expansion; + var normal = new TestKatana(); + var prized = new TestKatana(); + + try + { + prized.NegativeAttributes.Prized = 1; + + Core.Expansion = Expansion.ML; + Assert.Equal(600, PlayerMobile.GetInsuranceCost(prized)); + Assert.False(NegativeAttributes.IsPrized(prized)); + + Core.Expansion = Expansion.HS; + Assert.Equal(600, PlayerMobile.GetInsuranceCost(normal)); + Assert.Equal(1200, PlayerMobile.GetInsuranceCost(prized)); + Assert.True(NegativeAttributes.IsPrized(prized)); + } + finally + { + Core.Expansion = previousExpansion; + normal.Delete(); + prized.Delete(); + } + } + + [Fact] + public void Prized_DoesNotChangeBlessedCursedOrInsuranceState() + { + var previousExpansion = Core.Expansion; + var weapon = new TestKatana(); + + try + { + Core.Expansion = Expansion.HS; + weapon.NegativeAttributes.Prized = 1; + + Assert.Equal(LootType.Regular, weapon.LootType); + Assert.False(weapon.Insured); + Assert.False(weapon.PaidInsurance); + Assert.Null(weapon.BlessedFor); + } + finally + { + Core.Expansion = previousExpansion; + weapon.Delete(); + } + } + + [Fact] + public void RunicAttributeGeneration_DoesNotRollPrized() + { + var previousExpansion = Core.Expansion; + var weapon = new TestKatana(); + var armor = new LeatherChest(); + var jewel = new GoldRing(); + + try + { + Core.Expansion = Expansion.HS; + + BaseRunicTool.ApplyAttributesTo(weapon, false, 0, 25, 100, 100); + BaseRunicTool.ApplyAttributesTo(armor, false, 0, 25, 100, 100); + BaseRunicTool.ApplyAttributesTo(jewel, false, 0, 25, 100, 100); + + Assert.Equal(0, weapon.NegativeAttributes.Prized); + Assert.Equal(0, armor.NegativeAttributes.Prized); + Assert.Equal(0, jewel.NegativeAttributes.Prized); + } + finally + { + Core.Expansion = previousExpansion; + weapon.Delete(); + armor.Delete(); + jewel.Delete(); + } + } + + private static void AssertStoresAndDupesPrized(TItem source, TItem dupe) where TItem : Item + { + try + { + GetNegativeAttributes(source).Prized = 1; + + source.Dupe(dupe); + + Assert.Equal(1, GetNegativeAttributes(source).Prized); + Assert.Equal(1, GetNegativeAttributes(dupe).Prized); + } + finally + { + source.Delete(); + dupe.Delete(); + } + } + + private static Item[] CreateSupportedItems() => + [ + new TestKatana(), + new LeatherChest(), + new Buckler(), + new GoldRing() + ]; + + private static NegativeAttributes GetNegativeAttributes(Item item) => item switch + { + BaseWeapon weapon => weapon.NegativeAttributes, + BaseArmor armor => armor.NegativeAttributes, + BaseJewel jewel => jewel.NegativeAttributes, + _ => throw new ArgumentException($"Unsupported Prized host: {item.GetType().FullName}", nameof(item)) + }; + + private static void DeleteItems(IEnumerable items) + { + foreach (var item in items) + { + item.Delete(); + } + } + + private class TestKatana : Katana + { + } + + private sealed class RecordingPropertyList : IPropertyList + { + private string _interpolated = string.Empty; + + public List Numbers { get; } = []; + + public void Reset() + { + } + + public void Terminate() + { + } + + public void Add(int number) => Numbers.Add(number); + public void Add(int number, string argument) => Numbers.Add(number); + public void Add(ReadOnlySpan argument) => Numbers.Add(0); + public void Add(int number, ReadOnlySpan argument) => Numbers.Add(number); + public void AddChunked(ReadOnlySpan text) => Numbers.Add(0); + public OplTextBlock TextBlock() => new(this); + public void Add(int number, int value) => Numbers.Add(number); + public void AddLocalized(int value) => Numbers.Add(0); + public void AddLocalized(int number, int value) => Numbers.Add(number); + public void Add(ref IPropertyList.InterpolatedStringHandler handler) => Numbers.Add(0); + public void Add(int number, ref IPropertyList.InterpolatedStringHandler handler) => Numbers.Add(number); + 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 3135850a9..42cf1b13c 100644 --- a/Projects/UOContent/Items/Armor/BaseArmor.Migrations.cs +++ b/Projects/UOContent/Items/Armor/BaseArmor.Migrations.cs @@ -30,6 +30,37 @@ public partial class BaseArmor _meditate = content.MeditationAllowance ?? (AMA)(-1); _skillBonuses = content.SkillBonuses ?? SkillBonusesDefaultValue(); _playerConstructed = content.PlayerConstructed; + _negativeAttributes = NegativeAttributesDefaultValue(); + } + + private void MigrateFrom(V9Content 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 = NegativeAttributesDefaultValue(); } // Version 7 (pre-codegen) diff --git a/Projects/UOContent/Items/Armor/BaseArmor.cs b/Projects/UOContent/Items/Armor/BaseArmor.cs index 7f01648a0..3341b2961 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(9, false)] + [SerializationGenerator(10, false)] public abstract partial class BaseArmor : Item, IScissorable, IFactionItem, ICraftable, IWearableDurability, IAosItem, IIdentifiable { @@ -151,6 +151,17 @@ namespace Server.Items [SerializableFieldSaveFlag(24)] private bool ShouldSerializePlayerConstructed() => _playerConstructed; + [SerializedIgnoreDupe] + [SerializableField(25, setter: "private")] + [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] + private NegativeAttributes _negativeAttributes; + + [SerializableFieldSaveFlag(25)] + private bool ShouldSerializeNegativeAttributes() => !_negativeAttributes.IsEmpty; + + [SerializableFieldDefault(25)] + private NegativeAttributes NegativeAttributesDefaultValue() => new(this); + private FactionItem m_FactionState; public BaseArmor(int itemID) : base(itemID) @@ -167,6 +178,7 @@ namespace Server.Items Attributes = new AosAttributes(this); ArmorAttributes = new AosArmorAttributes(this); + NegativeAttributes = new NegativeAttributes(this); SkillBonuses = new AosSkillBonuses(this); } @@ -814,6 +826,7 @@ namespace Server.Items armor.Attributes = new AosAttributes(newItem, Attributes); armor.ArmorAttributes = new AosArmorAttributes(newItem, ArmorAttributes); + armor.NegativeAttributes = new NegativeAttributes(newItem, NegativeAttributes); armor.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses); // Set hue again because of resource @@ -1031,6 +1044,8 @@ namespace Server.Items [AfterDeserialization] private void AfterDeserialization() { + _negativeAttributes ??= NegativeAttributesDefaultValue(); + var m = Parent as Mobile; if (Core.AOS && m != null) @@ -1323,6 +1338,7 @@ namespace Server.Items list.Add(1061078, prop); // artifact rarity ~1_val~ } + NegativeAttributes.GetProperties(list); ArmorAttributes.GetProperties(list); Attributes.GetProperties(list, luckBonus: GetLuckBonus()); diff --git a/Projects/UOContent/Items/Jewels/BaseJewel.cs b/Projects/UOContent/Items/Jewels/BaseJewel.cs index 4eb01cbe0..cff85e125 100644 --- a/Projects/UOContent/Items/Jewels/BaseJewel.cs +++ b/Projects/UOContent/Items/Jewels/BaseJewel.cs @@ -18,7 +18,7 @@ public enum GemType Diamond } -[SerializationGenerator(5, false)] +[SerializationGenerator(6, false)] public abstract partial class BaseJewel : Item, ICraftable, IAosItem { [EncodedInt] @@ -52,11 +52,20 @@ public abstract partial class BaseJewel : Item, ICraftable, IAosItem [SerializedCommandProperty(AccessLevel.GameMaster)] private int _gemCount; + [SerializedIgnoreDupe] + [SerializableField(8, setter: "private")] + [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] + private NegativeAttributes _negativeAttributes; + + [SerializableFieldDefault(8)] + private NegativeAttributes NegativeAttributesDefaultValue() => new(this); + public BaseJewel(int itemID, Layer layer) : base(itemID) { _attributes = new AosAttributes(this); _resistances = new AosElementAttributes(this); _skillBonuses = new AosSkillBonuses(this); + NegativeAttributes = new NegativeAttributes(this); _resource = CraftResource.Iron; Hue = CraftResources.GetHue(_resource); _gemType = GemType.None; @@ -321,6 +330,7 @@ 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); + jewel.NegativeAttributes = new NegativeAttributes(newItem, NegativeAttributes); // Set hue again because of resource jewel.Hue = Hue; @@ -389,6 +399,7 @@ public abstract partial class BaseJewel : Item, ICraftable, IAosItem list.Add(1061078, prop); // artifact rarity ~1_val~ } + NegativeAttributes.GetProperties(list); Attributes.GetProperties(list); AddResistanceProperties(list); @@ -423,11 +434,27 @@ public abstract partial class BaseJewel : Item, ICraftable, IAosItem _resistances = content.Resistances; _skillBonuses = content.SkillBonuses; // _gemCount defaults to 0 + _negativeAttributes = NegativeAttributesDefaultValue(); + } + + private void MigrateFrom(V5Content content) + { + _maxHitPoints = content.MaxHitPoints; + _hitPoints = content.HitPoints; + _resource = content.Resource; + _gemType = content.GemType; + _attributes = content.Attributes; + _resistances = content.Resistances; + _skillBonuses = content.SkillBonuses; + _gemCount = content.GemCount; + _negativeAttributes = NegativeAttributesDefaultValue(); } [AfterDeserialization] private void AfterDeserialization() { + _negativeAttributes ??= NegativeAttributesDefaultValue(); + var m = Parent as Mobile; if (Core.AOS && m != null) diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs index af0adbf06..661ebf945 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.Migrations.cs @@ -36,5 +36,43 @@ public abstract partial class BaseWeapon _aosElementDamages = content.AosElementDamages ?? AosElementAttributesDefaultValue(); _engravedText = content.EngravedText; _extendedWeaponAttributes = ExtendedWeaponAttributesDefaultValue(); + _negativeAttributes = NegativeAttributesDefaultValue(); + } + + private void MigrateFrom(V11Content 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 = NegativeAttributesDefaultValue(); } } diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 4f8acceb2..9ab07aa26 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -27,7 +27,7 @@ public interface ISlayer SlayerName Slayer2 { get; set; } } -[SerializationGenerator(11, false)] +[SerializationGenerator(12, false)] public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftable, ISlayer, IDurability, IAosItem, IIdentifiable { @@ -200,6 +200,15 @@ public abstract partial class BaseWeapon [SerializableFieldDefault(31)] private ExtendedWeaponAttributes ExtendedWeaponAttributesDefaultValue() => new(this); + // Field 32 intentionally has no save flag; BaseWeapon's legacy save-flag mask is already at the high bit. + [SerializedIgnoreDupe] + [SerializableField(32, setter: "private")] + [SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)] + private NegativeAttributes _negativeAttributes; + + [SerializableFieldDefault(32)] + private NegativeAttributes NegativeAttributesDefaultValue() => new(this); + private FactionItem m_FactionState; private SkillMod m_SkillMod, m_MageMod; @@ -228,6 +237,7 @@ public abstract partial class BaseWeapon Attributes = new AosAttributes(this); WeaponAttributes = new AosWeaponAttributes(this); ExtendedWeaponAttributes = new ExtendedWeaponAttributes(this); + NegativeAttributes = new NegativeAttributes(this); SkillBonuses = new AosSkillBonuses(this); AosElementDamages = new AosElementAttributes(this); } @@ -945,6 +955,7 @@ public abstract partial class BaseWeapon weap.Attributes = new AosAttributes(newItem, Attributes); weap.AosElementDamages = new AosElementAttributes(newItem, AosElementDamages); weap.ExtendedWeaponAttributes = new ExtendedWeaponAttributes(newItem, ExtendedWeaponAttributes); + weap.NegativeAttributes = new NegativeAttributes(newItem, NegativeAttributes); weap.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses); weap.WeaponAttributes = new AosWeaponAttributes(newItem, WeaponAttributes); @@ -3062,6 +3073,7 @@ public abstract partial class BaseWeapon list.Add(1072792); // Balanced } + NegativeAttributes.GetProperties(list); ExtendedWeaponAttributes.GetProperties(list); WeaponAttributes.GetProperties(list); @@ -3865,6 +3877,7 @@ public abstract partial class BaseWeapon private void AfterDeserialization() { _extendedWeaponAttributes ??= ExtendedWeaponAttributesDefaultValue(); + _negativeAttributes ??= NegativeAttributesDefaultValue(); var parentMobile = Parent as Mobile; diff --git a/Projects/UOContent/Migrations/Server.Items.BaseArmor.v10.json b/Projects/UOContent/Migrations/Server.Items.BaseArmor.v10.json new file mode 100644 index 000000000..a2f4de301 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.BaseArmor.v10.json @@ -0,0 +1,225 @@ +{ + "version": 10, + "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" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.BaseJewel.v6.json b/Projects/UOContent/Migrations/Server.Items.BaseJewel.v6.json new file mode 100644 index 000000000..05b7c9dc1 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.BaseJewel.v6.json @@ -0,0 +1,72 @@ +{ + "version": 6, + "type": "Server.Items.BaseJewel", + "properties": [ + { + "name": "MaxHitPoints", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "EncodedInt" + ] + }, + { + "name": "HitPoints", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "EncodedInt" + ] + }, + { + "name": "Resource", + "type": "Server.Items.CraftResource", + "rule": "EnumMigrationRule" + }, + { + "name": "GemType", + "type": "Server.Items.GemType", + "rule": "EnumMigrationRule" + }, + { + "name": "Attributes", + "type": "Server.AosAttributes", + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "Resistances", + "type": "Server.AosElementAttributes", + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "SkillBonuses", + "type": "Server.AosSkillBonuses", + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + }, + { + "name": "GemCount", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "EncodedInt" + ] + }, + { + "name": "NegativeAttributes", + "type": "Server.NegativeAttributes", + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "DeserializationRequiresParent" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v12.json b/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v12.json new file mode 100644 index 000000000..89bbc16ec --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.BaseWeapon.v12.json @@ -0,0 +1,271 @@ +{ + "version": 12, + "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" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Misc/AOS.cs b/Projects/UOContent/Misc/AOS.cs index a8ce1125b..4b969c9d0 100644 --- a/Projects/UOContent/Misc/AOS.cs +++ b/Projects/UOContent/Misc/AOS.cs @@ -1173,6 +1173,62 @@ namespace Server public override string ToString() => "..."; } + [Flags] + public enum NegativeAttribute + { + Prized = 0x00000001 + } + + public sealed class NegativeAttributes : BaseAttributes + { + public NegativeAttributes(Item owner) : base(owner) + { + } + + public NegativeAttributes(Item owner, NegativeAttributes other) : base(owner, other) + { + } + + public int this[NegativeAttribute attribute] + { + get => GetValue((int)attribute); + set => SetValue((int)attribute, value); + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Prized + { + get => this[NegativeAttribute.Prized]; + set => this[NegativeAttribute.Prized] = value; + } + + public static bool IsPrized(Item item) + { + if (!Core.HS) + { + return false; + } + + return item switch + { + BaseWeapon weapon => weapon.NegativeAttributes.Prized != 0, + BaseArmor armor => armor.NegativeAttributes.Prized != 0, + BaseJewel jewel => jewel.NegativeAttributes.Prized != 0, + _ => false + }; + } + + public void GetProperties(IPropertyList list) + { + if (Core.HS && Prized != 0) + { + list.Add(1154910); // Prized + } + } + + public override string ToString() => "..."; + } + [Flags] public enum AosArmorAttribute { diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 0a81bb846..55927fb44 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -3710,7 +3710,17 @@ namespace Server.Mobiles AutoStabled = null; } - private static int GetInsuranceCost(Item item) => 600; + internal static int GetInsuranceCost(Item item) + { + var cost = 600; + + if (NegativeAttributes.IsPrized(item)) + { + cost *= 2; + } + + return cost; + } private void ToggleItemInsurance() {