Merge pull request #13 from RebirthUO/feat/issue-8-prized

feat(items): add Prized negative property
This commit is contained in:
Crome696 2026-07-08 23:15:22 +02:00 committed by GitHub
commit b4e57ee9e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 983 additions and 4 deletions

View file

@ -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>(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<Item> items)
{
foreach (var item in items)
{
item.Delete();
}
}
private class TestKatana : Katana
{
}
private sealed class RecordingPropertyList : IPropertyList
{
private string _interpolated = string.Empty;
public List<int> 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<char> argument) => Numbers.Add(0);
public void Add(int number, ReadOnlySpan<char> argument) => Numbers.Add(number);
public void AddChunked(ReadOnlySpan<char> 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>(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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

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

View file

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