feat(items): implement Unwieldy negative property
This commit is contained in:
parent
e9d0d85385
commit
d8b9a2ca62
8 changed files with 1053 additions and 3 deletions
314
Projects/UOContent.Tests/Tests/Items/UnwieldyPropertyTests.cs
Normal file
314
Projects/UOContent.Tests/Tests/Items/UnwieldyPropertyTests.cs
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using Server.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace UOContent.Tests;
|
||||
|
||||
[Collection("Sequential UOContent Tests")]
|
||||
public class UnwieldyPropertyTests
|
||||
{
|
||||
private const int SingleWeightCliloc = 1072788;
|
||||
private const int MultipleWeightCliloc = 1072789;
|
||||
private const double OriginalWeight = 7.25;
|
||||
private const double UnwieldyWeight = 50;
|
||||
|
||||
[Fact]
|
||||
public void NegativeAttribute_ReservesTheNextNonCollidingBitForUnwieldy()
|
||||
{
|
||||
Assert.True(Enum.TryParse<NegativeAttribute>("Unwieldy", out var unwieldy));
|
||||
Assert.Equal(0x00000010, (int)unwieldy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NegativeAttributes_StoresDupesAndSerializesUnwieldyOnSupportedFamilies()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.HS;
|
||||
|
||||
AssertStoresDupesAndSerializesUnwieldy(new TestKatana(), new TestKatana(), new TestKatana());
|
||||
AssertStoresDupesAndSerializesUnwieldy(new LeatherChest(), new LeatherChest(), new LeatherChest());
|
||||
AssertStoresDupesAndSerializesUnwieldy(new Buckler(), new Buckler(), new Buckler());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NegativeAttributes_DoesNotStoreUnwieldyOnUnsupportedJewelry()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var ring = new GoldRing { Weight = OriginalWeight };
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.HS;
|
||||
ring.NegativeAttributes.Unwieldy = 1;
|
||||
|
||||
Assert.Equal(0, ring.NegativeAttributes.Unwieldy);
|
||||
Assert.Equal(OriginalWeight, ring.Weight);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
ring.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unwieldy_UsesStandardWeightTotalsAndWeightPresentationInHighSeas()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.HS;
|
||||
|
||||
AssertUsesStandardWeightTotalsAndPresentation(new TestKatana());
|
||||
AssertUsesStandardWeightTotalsAndPresentation(new LeatherChest());
|
||||
AssertUsesStandardWeightTotalsAndPresentation(new Buckler());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unwieldy_IsNoOpBeforeHighSeasIncludingAfterDeserialization()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var source = new TestKatana { Weight = OriginalWeight };
|
||||
var direct = new TestKatana { Weight = OriginalWeight };
|
||||
var deserialized = new TestKatana();
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.HS;
|
||||
source.NegativeAttributes.Unwieldy = 1;
|
||||
|
||||
var writer = new BufferWriter(true);
|
||||
source.Serialize(writer);
|
||||
var buffer = new byte[writer.Position];
|
||||
writer.Buffer.AsSpan(0, (int)writer.Position).CopyTo(buffer);
|
||||
|
||||
Core.Expansion = Expansion.SA;
|
||||
direct.NegativeAttributes.Unwieldy = 1;
|
||||
|
||||
Assert.Equal(1, direct.NegativeAttributes.Unwieldy);
|
||||
Assert.Equal(OriginalWeight, direct.Weight);
|
||||
AssertShowsWeightWithoutUnwieldyRow(direct, (int)Math.Ceiling(OriginalWeight));
|
||||
|
||||
var reader = new BufferReader(buffer);
|
||||
deserialized.Deserialize(reader);
|
||||
|
||||
Assert.Equal(buffer.Length, reader.Position);
|
||||
Assert.Equal(1, deserialized.NegativeAttributes.Unwieldy);
|
||||
Assert.Equal(OriginalWeight, deserialized.Weight);
|
||||
AssertShowsWeightWithoutUnwieldyRow(deserialized, (int)Math.Ceiling(OriginalWeight));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
source.Delete();
|
||||
direct.Delete();
|
||||
deserialized.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunicAttributeGeneration_DoesNotRollUnwieldy()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var weapon = new TestKatana();
|
||||
var armor = new LeatherChest();
|
||||
var shield = new Buckler();
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.HS;
|
||||
|
||||
BaseRunicTool.ApplyAttributesTo(weapon, false, 0, 25, 100, 100);
|
||||
BaseRunicTool.ApplyAttributesTo(armor, false, 0, 25, 100, 100);
|
||||
BaseRunicTool.ApplyAttributesTo(shield, false, 0, 25, 100, 100);
|
||||
|
||||
Assert.Equal(0, weapon.NegativeAttributes.Unwieldy);
|
||||
Assert.Equal(0, armor.NegativeAttributes.Unwieldy);
|
||||
Assert.Equal(0, shield.NegativeAttributes.Unwieldy);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
weapon.Delete();
|
||||
armor.Delete();
|
||||
shield.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertStoresDupesAndSerializesUnwieldy<TItem>(TItem source, TItem dupe, TItem deserialized)
|
||||
where TItem : Item
|
||||
{
|
||||
try
|
||||
{
|
||||
source.Weight = OriginalWeight;
|
||||
GetNegativeAttributes(source).Unwieldy = 1;
|
||||
|
||||
source.Dupe(dupe);
|
||||
|
||||
Assert.Equal(1, GetNegativeAttributes(dupe).Unwieldy);
|
||||
Assert.Equal(UnwieldyWeight, dupe.Weight);
|
||||
GetNegativeAttributes(dupe).Unwieldy = 0;
|
||||
Assert.Equal(OriginalWeight, dupe.Weight);
|
||||
|
||||
var writer = new BufferWriter(true);
|
||||
source.Serialize(writer);
|
||||
var buffer = new byte[writer.Position];
|
||||
writer.Buffer.AsSpan(0, (int)writer.Position).CopyTo(buffer);
|
||||
|
||||
var reader = new BufferReader(buffer);
|
||||
deserialized.Deserialize(reader);
|
||||
|
||||
Assert.Equal(buffer.Length, reader.Position);
|
||||
Assert.Equal(1, GetNegativeAttributes(deserialized).Unwieldy);
|
||||
Assert.Equal(UnwieldyWeight, deserialized.Weight);
|
||||
GetNegativeAttributes(deserialized).Unwieldy = 0;
|
||||
Assert.Equal(OriginalWeight, deserialized.Weight);
|
||||
}
|
||||
finally
|
||||
{
|
||||
source.Delete();
|
||||
dupe.Delete();
|
||||
deserialized.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertUsesStandardWeightTotalsAndPresentation(Item item)
|
||||
{
|
||||
var player = CreatePlayerMobile();
|
||||
|
||||
try
|
||||
{
|
||||
item.Weight = OriginalWeight;
|
||||
player.Backpack.AddItem(item);
|
||||
|
||||
var originalPileWeight = item.PileWeight;
|
||||
var originalContainerWeight = player.Backpack.TotalWeight;
|
||||
var originalMobileWeight = player.TotalWeight;
|
||||
|
||||
GetNegativeAttributes(item).Unwieldy = 1;
|
||||
|
||||
Assert.Equal(UnwieldyWeight, item.Weight);
|
||||
Assert.Equal((int)UnwieldyWeight, item.PileWeight);
|
||||
Assert.Equal(originalContainerWeight - originalPileWeight + (int)UnwieldyWeight, player.Backpack.TotalWeight);
|
||||
Assert.Equal(originalMobileWeight - originalPileWeight + (int)UnwieldyWeight, player.TotalWeight);
|
||||
AssertShowsWeightWithoutUnwieldyRow(item, (int)UnwieldyWeight);
|
||||
|
||||
GetNegativeAttributes(item).Unwieldy = 2;
|
||||
Assert.Equal(1, GetNegativeAttributes(item).Unwieldy);
|
||||
Assert.Equal((int)UnwieldyWeight, item.PileWeight);
|
||||
Assert.Equal(originalContainerWeight - originalPileWeight + (int)UnwieldyWeight, player.Backpack.TotalWeight);
|
||||
Assert.Equal(originalMobileWeight - originalPileWeight + (int)UnwieldyWeight, player.TotalWeight);
|
||||
|
||||
GetNegativeAttributes(item).Unwieldy = 0;
|
||||
|
||||
Assert.Equal(OriginalWeight, item.Weight);
|
||||
Assert.Equal(originalPileWeight, item.PileWeight);
|
||||
Assert.Equal(originalContainerWeight, player.Backpack.TotalWeight);
|
||||
Assert.Equal(originalMobileWeight, player.TotalWeight);
|
||||
AssertShowsWeightWithoutUnwieldyRow(item, originalPileWeight);
|
||||
|
||||
GetNegativeAttributes(item).Unwieldy = 0;
|
||||
Assert.Equal(0, GetNegativeAttributes(item).Unwieldy);
|
||||
Assert.Equal(OriginalWeight, item.Weight);
|
||||
Assert.Equal(originalContainerWeight, player.Backpack.TotalWeight);
|
||||
Assert.Equal(originalMobileWeight, player.TotalWeight);
|
||||
}
|
||||
finally
|
||||
{
|
||||
item.Delete();
|
||||
player.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertShowsWeightWithoutUnwieldyRow(Item item, int weight)
|
||||
{
|
||||
var properties = new RecordingPropertyList();
|
||||
item.GetProperties(properties);
|
||||
|
||||
Assert.Contains(properties.Entries, entry =>
|
||||
entry.Number == (weight == 1 ? SingleWeightCliloc : MultipleWeightCliloc) && entry.Argument == weight.ToString());
|
||||
Assert.DoesNotContain(properties.Entries, entry =>
|
||||
entry.Argument.Contains("Unwieldy", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static NegativeAttributes GetNegativeAttributes(Item item) => item switch
|
||||
{
|
||||
BaseWeapon weapon => weapon.NegativeAttributes,
|
||||
BaseArmor armor => armor.NegativeAttributes,
|
||||
_ => throw new ArgumentException($"Unsupported Unwieldy host: {item.GetType().FullName}", nameof(item))
|
||||
};
|
||||
|
||||
private static PlayerMobile CreatePlayerMobile()
|
||||
{
|
||||
var player = new PlayerMobile(World.NewMobile);
|
||||
player.DefaultMobileInit();
|
||||
player.AddItem(new Backpack());
|
||||
return player;
|
||||
}
|
||||
|
||||
private class TestKatana : Katana
|
||||
{
|
||||
}
|
||||
|
||||
private sealed record PropertyEntry(int Number, string Argument);
|
||||
|
||||
private sealed class RecordingPropertyList : IPropertyList
|
||||
{
|
||||
private string _interpolated = string.Empty;
|
||||
|
||||
public List<PropertyEntry> Entries { get; } = [];
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
}
|
||||
|
||||
public void Add(int number) => Entries.Add(new PropertyEntry(number, string.Empty));
|
||||
public void Add(int number, string argument) => Entries.Add(new PropertyEntry(number, argument));
|
||||
public void Add(ReadOnlySpan<char> argument) => Entries.Add(new PropertyEntry(0, argument.ToString()));
|
||||
public void Add(int number, ReadOnlySpan<char> argument) => Entries.Add(new PropertyEntry(number, argument.ToString()));
|
||||
public void AddChunked(ReadOnlySpan<char> text) => Entries.Add(new PropertyEntry(0, text.ToString()));
|
||||
public OplTextBlock TextBlock() => new(this);
|
||||
public void Add(int number, int value) => Entries.Add(new PropertyEntry(number, value.ToString()));
|
||||
public void AddLocalized(int value) => Entries.Add(new PropertyEntry(0, value.ToString()));
|
||||
public void AddLocalized(int number, int value) => Entries.Add(new PropertyEntry(number, value.ToString()));
|
||||
public void Add(ref IPropertyList.InterpolatedStringHandler handler) => Entries.Add(new PropertyEntry(0, _interpolated));
|
||||
public void Add(int number, ref IPropertyList.InterpolatedStringHandler handler) =>
|
||||
Entries.Add(new PropertyEntry(number, _interpolated));
|
||||
public void InitializeInterpolation(int literalLength, int formattedCount) => _interpolated = string.Empty;
|
||||
public void AppendLiteral(string value) => _interpolated += value;
|
||||
public void AppendFormatted<T>(T value) => _interpolated += value;
|
||||
public void AppendFormatted<T>(T value, string format) =>
|
||||
_interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value;
|
||||
public void AppendFormatted<T>(T value, int alignment) => _interpolated += value;
|
||||
public void AppendFormatted<T>(T value, int alignment, string format) =>
|
||||
_interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value;
|
||||
public void AppendFormatted(ReadOnlySpan<char> value) => _interpolated += value.ToString();
|
||||
public void AppendFormatted(ReadOnlySpan<char> value, int alignment, string format = null) =>
|
||||
_interpolated += value.ToString();
|
||||
public void AppendFormatted(object value, int alignment = 0, string format = null) => _interpolated += value;
|
||||
public void AppendFormatted(string value) => _interpolated += value;
|
||||
public void AppendFormatted(string value, int alignment, string format = null) => _interpolated += value;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
243
Projects/UOContent/Migrations/Server.Items.BaseArmor.v12.json
generated
Normal file
243
Projects/UOContent/Migrations/Server.Items.BaseArmor.v12.json
generated
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
{
|
||||
"version": 12,
|
||||
"type": "Server.Items.BaseArmor",
|
||||
"properties": [
|
||||
{
|
||||
"name": "Attributes",
|
||||
"type": "Server.AosAttributes",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ArmorAttributes",
|
||||
"type": "Server.AosArmorAttributes",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PhysicalBonus",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FireBonus",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ColdBonus",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PoisonBonus",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EnergyBonus",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Identified",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MaxHitPoints",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "HitPoints",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Crafter",
|
||||
"type": "string",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Quality",
|
||||
"type": "Server.Items.ArmorQuality",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Durability",
|
||||
"type": "Server.Items.ArmorDurabilityLevel",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "ProtectionLevel",
|
||||
"type": "Server.Items.ArmorProtectionLevel",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Resource",
|
||||
"type": "Server.Items.CraftResource",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "BaseArmorRating",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "StrBonus",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DexBonus",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IntBonus",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "StrRequirement",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DexRequirement",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IntRequirement",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"EncodedInt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MeditationAllowance",
|
||||
"type": "Server.Items.ArmorMeditationAllowance",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "SkillBonuses",
|
||||
"type": "Server.AosSkillBonuses",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PlayerConstructed",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NegativeAttributes",
|
||||
"type": "Server.NegativeAttributes",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AbsorptionAttributes",
|
||||
"type": "Server.AbsorptionAttributes",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UnwieldyOriginalWeight",
|
||||
"type": "double",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
279
Projects/UOContent/Migrations/Server.Items.BaseWeapon.v13.json
generated
Normal file
279
Projects/UOContent/Migrations/Server.Items.BaseWeapon.v13.json
generated
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
{
|
||||
"version": 13,
|
||||
"type": "Server.Items.BaseWeapon",
|
||||
"properties": [
|
||||
{
|
||||
"name": "DamageLevel",
|
||||
"type": "Server.Items.WeaponDamageLevel",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "AccuracyLevel",
|
||||
"type": "Server.Items.WeaponAccuracyLevel",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "DurabilityLevel",
|
||||
"type": "Server.Items.WeaponDurabilityLevel",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Quality",
|
||||
"type": "Server.Items.WeaponQuality",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "HitPoints",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MaxHitPoints",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Slayer",
|
||||
"type": "Server.Items.SlayerName",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Poison",
|
||||
"type": "Server.Poison",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveUOTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Poison"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PoisonCharges",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Crafter",
|
||||
"type": "string",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Identified",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "StrRequirement",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DexRequirement",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IntRequirement",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MinDamage",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MaxDamage",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "HitSound",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MissSound",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Speed",
|
||||
"type": "float",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MaxRange",
|
||||
"type": "int",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Skill",
|
||||
"type": "Server.SkillName",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Type",
|
||||
"type": "Server.Items.WeaponType",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Animation",
|
||||
"type": "Server.Items.WeaponAnimation",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Resource",
|
||||
"type": "Server.Items.CraftResource",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Attributes",
|
||||
"type": "Server.AosAttributes",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "WeaponAttributes",
|
||||
"type": "Server.AosWeaponAttributes",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PlayerConstructed",
|
||||
"type": "bool",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SkillBonuses",
|
||||
"type": "Server.AosSkillBonuses",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Slayer2",
|
||||
"type": "Server.Items.SlayerName",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "AosElementDamages",
|
||||
"type": "Server.AosElementAttributes",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EngravedText",
|
||||
"type": "string",
|
||||
"usesSaveFlag": true,
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ExtendedWeaponAttributes",
|
||||
"type": "Server.ExtendedWeaponAttributes",
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NegativeAttributes",
|
||||
"type": "Server.NegativeAttributes",
|
||||
"rule": "RawSerializableMigrationRule",
|
||||
"ruleArguments": [
|
||||
"DeserializationRequiresParent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "UnwieldyOriginalWeight",
|
||||
"type": "double",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue