Merge pull request #24 from RebirthUO/feat/issue-9-casting-focus
feat(items): add Casting Focus property
This commit is contained in:
commit
81231bedb3
6 changed files with 772 additions and 3 deletions
|
|
@ -0,0 +1,402 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Server;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using Server.Spells;
|
||||
using Server.Spells.Second;
|
||||
using Server.Tests;
|
||||
using Server.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace UOContent.Tests;
|
||||
|
||||
[Collection("Sequential UOContent Tests")]
|
||||
public class CastingFocusPropertyTests
|
||||
{
|
||||
private const int CastingFocusCliloc = 1113696;
|
||||
|
||||
[Fact]
|
||||
public void AbsorptionAttributes_StoresDupesAndSerializesCastingFocusOnArmor()
|
||||
{
|
||||
var armor = new LeatherChest();
|
||||
var dupe = new LeatherChest();
|
||||
var deserialized = new LeatherChest();
|
||||
|
||||
try
|
||||
{
|
||||
armor.AbsorptionAttributes.CastingFocus = 3;
|
||||
|
||||
armor.Dupe(dupe);
|
||||
Assert.Equal(3, dupe.AbsorptionAttributes.CastingFocus);
|
||||
|
||||
var writer = new BufferWriter(true);
|
||||
armor.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(3, deserialized.AbsorptionAttributes.CastingFocus);
|
||||
}
|
||||
finally
|
||||
{
|
||||
armor.Delete();
|
||||
dupe.Delete();
|
||||
deserialized.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AbsorptionAttributes_AreStaffEditableThroughCommandProperties()
|
||||
{
|
||||
var containerProperty = typeof(BaseArmor).GetProperty(nameof(BaseArmor.AbsorptionAttributes));
|
||||
Assert.NotNull(containerProperty);
|
||||
var containerCommandProperty = containerProperty.GetCustomAttribute<CommandPropertyAttribute>();
|
||||
Assert.NotNull(containerCommandProperty);
|
||||
Assert.True(containerCommandProperty.CanModify);
|
||||
|
||||
var property = typeof(AbsorptionAttributes).GetProperty(nameof(AbsorptionAttributes.CastingFocus));
|
||||
Assert.NotNull(property);
|
||||
var attribute = property.GetCustomAttribute<CommandPropertyAttribute>();
|
||||
Assert.NotNull(attribute);
|
||||
Assert.Equal(AccessLevel.GameMaster, attribute.ReadLevel);
|
||||
Assert.Equal(AccessLevel.GameMaster, attribute.WriteLevel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AbsorptionAttributes_GetValue_SumsEquippedArmorAndRespectsStygianAbyssGate()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var caster = CreateMobile(player: true);
|
||||
var chest = new LeatherChest();
|
||||
var legs = new LeatherLegs();
|
||||
|
||||
try
|
||||
{
|
||||
chest.AbsorptionAttributes.CastingFocus = 5;
|
||||
legs.AbsorptionAttributes.CastingFocus = 10;
|
||||
caster.AddItem(chest);
|
||||
caster.AddItem(legs);
|
||||
|
||||
Core.Expansion = Expansion.ML;
|
||||
Assert.Equal(0, AbsorptionAttributes.GetValue(caster, AbsorptionAttribute.CastingFocus));
|
||||
|
||||
Core.Expansion = Expansion.SA;
|
||||
Assert.Equal(15, AbsorptionAttributes.GetValue(caster, AbsorptionAttribute.CastingFocus));
|
||||
Assert.Equal(
|
||||
AOS.CastingFocusChanceCap,
|
||||
Math.Min(
|
||||
AOS.CastingFocusChanceCap,
|
||||
AbsorptionAttributes.GetValue(caster, AbsorptionAttribute.CastingFocus)
|
||||
)
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
chest.Delete();
|
||||
legs.Delete();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BaseArmor_GetProperties_GatesCastingFocusTooltipToStygianAbyss()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var armor = new LeatherChest();
|
||||
|
||||
try
|
||||
{
|
||||
armor.AbsorptionAttributes.CastingFocus = 3;
|
||||
|
||||
Core.Expansion = Expansion.ML;
|
||||
var preStygianAbyss = new RecordingPropertyList();
|
||||
armor.GetProperties(preStygianAbyss);
|
||||
Assert.DoesNotContain(preStygianAbyss.Entries, entry => entry.Number == CastingFocusCliloc);
|
||||
|
||||
Core.Expansion = Expansion.SA;
|
||||
var stygianAbyss = new RecordingPropertyList();
|
||||
armor.GetProperties(stygianAbyss);
|
||||
Assert.Contains(stygianAbyss.Entries, entry =>
|
||||
entry.Number == CastingFocusCliloc && entry.Argument == "3"
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
armor.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnCasterHurt_CastingFocusSuccessPreservesCastWithoutPreventingDamage()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
using var random = new PredictableRandom(0);
|
||||
var caster = CreateMobile(player: true);
|
||||
var armor = EquipCastingFocusArmor(caster, 1);
|
||||
var spell = SetCastingSpell(caster);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
var hits = caster.Hits;
|
||||
|
||||
caster.Damage(10);
|
||||
|
||||
Assert.Equal(hits - 10, caster.Hits);
|
||||
Assert.Equal(SpellState.Casting, spell.State);
|
||||
Assert.Same(spell, caster.Spell);
|
||||
Assert.Equal(0, spell.DisturbCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
armor.Delete();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnCasterHurt_CastingFocusFailedRollDisturbsNormally()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
using var random = new PredictableRandom(99);
|
||||
var caster = CreateMobile(player: true);
|
||||
var armor = EquipCastingFocusArmor(caster, 12);
|
||||
var spell = SetCastingSpell(caster);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
|
||||
caster.Damage(10);
|
||||
|
||||
Assert.Equal(SpellState.None, spell.State);
|
||||
Assert.Null(caster.Spell);
|
||||
Assert.Equal(1, spell.DisturbCount);
|
||||
Assert.Equal(DisturbType.Hurt, spell.LastDisturbType);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
armor.Delete();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnCasterHurt_PreStygianAbyssCastingFocusDoesNotPreserveCast()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
using var random = new PredictableRandom(0);
|
||||
var caster = CreateMobile(player: true);
|
||||
var armor = EquipCastingFocusArmor(caster, 12);
|
||||
var spell = SetCastingSpell(caster);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.ML;
|
||||
|
||||
caster.Damage(10);
|
||||
|
||||
Assert.Equal(SpellState.None, spell.State);
|
||||
Assert.Null(caster.Spell);
|
||||
Assert.Equal(1, spell.DisturbCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
armor.Delete();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnCasterHurt_ProtectionSuccessStillPreservesBeforeCastingFocus()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
using var random = new PredictableRandom(99);
|
||||
var caster = CreateMobile(player: true);
|
||||
var armor = EquipCastingFocusArmor(caster, 0);
|
||||
var spell = SetCastingSpell(caster);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
ProtectionSpell.Registry[caster] = 1000;
|
||||
|
||||
caster.Damage(10);
|
||||
|
||||
Assert.Equal(SpellState.Casting, spell.State);
|
||||
Assert.Same(spell, caster.Spell);
|
||||
Assert.Equal(0, spell.DisturbCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ProtectionSpell.Registry.Remove(caster);
|
||||
Core.Expansion = previousExpansion;
|
||||
armor.Delete();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnCasterHurt_CastingFocusCanPreserveAfterProtectionFails()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
using var random = new PredictableRandom(1);
|
||||
var caster = CreateMobile(player: true);
|
||||
var armor = EquipCastingFocusArmor(caster, 12);
|
||||
var spell = SetCastingSpell(caster);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
ProtectionSpell.Registry[caster] = 0;
|
||||
|
||||
caster.Damage(10);
|
||||
|
||||
Assert.Equal(SpellState.Casting, spell.State);
|
||||
Assert.Same(spell, caster.Spell);
|
||||
Assert.Equal(0, spell.DisturbCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ProtectionSpell.Registry.Remove(caster);
|
||||
Core.Expansion = previousExpansion;
|
||||
armor.Delete();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnCasterHurt_NonPlayerCasterRemainsUnchanged()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
using var random = new PredictableRandom(99);
|
||||
var caster = CreateMobile(player: false);
|
||||
var armor = EquipCastingFocusArmor(caster, 12);
|
||||
var spell = SetCastingSpell(caster);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
|
||||
caster.Damage(10);
|
||||
|
||||
Assert.Equal(SpellState.Casting, spell.State);
|
||||
Assert.Same(spell, caster.Spell);
|
||||
Assert.Equal(0, spell.DisturbCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
armor.Delete();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static Mobile CreateMobile(bool player)
|
||||
{
|
||||
var mobile = new Mobile(World.NewMobile);
|
||||
mobile.DefaultMobileInit();
|
||||
mobile.Player = player;
|
||||
mobile.RawStr = 100;
|
||||
mobile.Hits = mobile.HitsMax;
|
||||
return mobile;
|
||||
}
|
||||
|
||||
private static LeatherChest EquipCastingFocusArmor(Mobile mobile, int value)
|
||||
{
|
||||
var armor = new LeatherChest();
|
||||
armor.AbsorptionAttributes.CastingFocus = value;
|
||||
mobile.AddItem(armor);
|
||||
return armor;
|
||||
}
|
||||
|
||||
private static TestSpell SetCastingSpell(Mobile caster)
|
||||
{
|
||||
var spell = new TestSpell(caster)
|
||||
{
|
||||
State = SpellState.Casting
|
||||
};
|
||||
|
||||
caster.Spell = spell;
|
||||
return spell;
|
||||
}
|
||||
|
||||
private sealed class TestSpell : Spell
|
||||
{
|
||||
private static readonly SpellInfo TestInfo = new("Test Spell", "test");
|
||||
|
||||
public TestSpell(Mobile caster) : base(caster, null, TestInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public int DisturbCount { get; private set; }
|
||||
public DisturbType LastDisturbType { get; private set; }
|
||||
public override TimeSpan CastDelayBase => TimeSpan.Zero;
|
||||
public override void OnCast()
|
||||
{
|
||||
}
|
||||
|
||||
public override int GetMana() => 0;
|
||||
|
||||
public override void OnDisturb(DisturbType type, bool message)
|
||||
{
|
||||
DisturbCount++;
|
||||
LastDisturbType = type;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ public partial class BaseArmor
|
|||
_skillBonuses = content.SkillBonuses ?? SkillBonusesDefaultValue();
|
||||
_playerConstructed = content.PlayerConstructed;
|
||||
_negativeAttributes = NegativeAttributesDefaultValue();
|
||||
_absorptionAttributes = AbsorptionAttributesDefaultValue();
|
||||
}
|
||||
|
||||
private void MigrateFrom(V9Content content)
|
||||
|
|
@ -61,6 +62,38 @@ public partial class BaseArmor
|
|||
_skillBonuses = content.SkillBonuses ?? SkillBonusesDefaultValue();
|
||||
_playerConstructed = content.PlayerConstructed;
|
||||
_negativeAttributes = NegativeAttributesDefaultValue();
|
||||
_absorptionAttributes = AbsorptionAttributesDefaultValue();
|
||||
}
|
||||
|
||||
private void MigrateFrom(V10Content 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 = AbsorptionAttributesDefaultValue();
|
||||
}
|
||||
|
||||
// Version 7 (pre-codegen)
|
||||
|
|
@ -192,5 +225,7 @@ public partial class BaseArmor
|
|||
}
|
||||
|
||||
PlayerConstructed = GetSaveFlag(flags, OldSaveFlag.PlayerConstructed);
|
||||
NegativeAttributes = new NegativeAttributes(this);
|
||||
AbsorptionAttributes = new AbsorptionAttributes(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using AMT = Server.Items.ArmorMaterialType;
|
|||
|
||||
namespace Server.Items
|
||||
{
|
||||
[SerializationGenerator(10, false)]
|
||||
[SerializationGenerator(11, false)]
|
||||
public abstract partial class BaseArmor
|
||||
: Item, IScissorable, IFactionItem, ICraftable, IWearableDurability, IAosItem, IIdentifiable
|
||||
{
|
||||
|
|
@ -162,6 +162,17 @@ namespace Server.Items
|
|||
[SerializableFieldDefault(25)]
|
||||
private NegativeAttributes NegativeAttributesDefaultValue() => new(this);
|
||||
|
||||
[SerializedIgnoreDupe]
|
||||
[SerializableField(26, setter: "private")]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster, canModify: true)]
|
||||
private AbsorptionAttributes _absorptionAttributes;
|
||||
|
||||
[SerializableFieldSaveFlag(26)]
|
||||
private bool ShouldSerializeAbsorptionAttributes() => !_absorptionAttributes.IsEmpty;
|
||||
|
||||
[SerializableFieldDefault(26)]
|
||||
private AbsorptionAttributes AbsorptionAttributesDefaultValue() => new(this);
|
||||
|
||||
private FactionItem m_FactionState;
|
||||
|
||||
public BaseArmor(int itemID) : base(itemID)
|
||||
|
|
@ -179,6 +190,7 @@ namespace Server.Items
|
|||
Attributes = new AosAttributes(this);
|
||||
ArmorAttributes = new AosArmorAttributes(this);
|
||||
NegativeAttributes = new NegativeAttributes(this);
|
||||
AbsorptionAttributes = new AbsorptionAttributes(this);
|
||||
SkillBonuses = new AosSkillBonuses(this);
|
||||
}
|
||||
|
||||
|
|
@ -827,6 +839,7 @@ namespace Server.Items
|
|||
armor.Attributes = new AosAttributes(newItem, Attributes);
|
||||
armor.ArmorAttributes = new AosArmorAttributes(newItem, ArmorAttributes);
|
||||
armor.NegativeAttributes = new NegativeAttributes(newItem, NegativeAttributes);
|
||||
armor.AbsorptionAttributes = new AbsorptionAttributes(newItem, AbsorptionAttributes);
|
||||
armor.SkillBonuses = new AosSkillBonuses(newItem, SkillBonuses);
|
||||
|
||||
// Set hue again because of resource
|
||||
|
|
@ -1050,6 +1063,7 @@ namespace Server.Items
|
|||
private void AfterDeserialization()
|
||||
{
|
||||
_negativeAttributes ??= NegativeAttributesDefaultValue();
|
||||
_absorptionAttributes ??= AbsorptionAttributesDefaultValue();
|
||||
|
||||
var m = Parent as Mobile;
|
||||
|
||||
|
|
@ -1344,6 +1358,7 @@ namespace Server.Items
|
|||
}
|
||||
|
||||
NegativeAttributes.GetProperties(list);
|
||||
AbsorptionAttributes.GetProperties(list);
|
||||
ArmorAttributes.GetProperties(list);
|
||||
|
||||
Attributes.GetProperties(list, luckBonus: GetLuckBonus());
|
||||
|
|
|
|||
234
Projects/UOContent/Migrations/Server.Items.BaseArmor.v11.json
generated
Normal file
234
Projects/UOContent/Migrations/Server.Items.BaseArmor.v11.json
generated
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
{
|
||||
"version": 11,
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ namespace Server
|
|||
{
|
||||
public static class AOS
|
||||
{
|
||||
public const int CastingFocusChanceCap = 12;
|
||||
public const int MassiveStrengthRequirement = 125;
|
||||
|
||||
public static void DisableStatInfluences()
|
||||
|
|
@ -1201,6 +1202,75 @@ namespace Server
|
|||
public override string ToString() => "...";
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AbsorptionAttribute
|
||||
{
|
||||
CastingFocus = 0x00000001
|
||||
}
|
||||
|
||||
public sealed class AbsorptionAttributes : BaseAttributes
|
||||
{
|
||||
public AbsorptionAttributes(Item owner) : base(owner)
|
||||
{
|
||||
}
|
||||
|
||||
public AbsorptionAttributes(Item owner, AbsorptionAttributes other) : base(owner, other)
|
||||
{
|
||||
}
|
||||
|
||||
public int this[AbsorptionAttribute attribute]
|
||||
{
|
||||
get => GetValue((int)attribute);
|
||||
set => SetValue((int)attribute, value);
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CastingFocus
|
||||
{
|
||||
get => Owner is BaseArmor ? this[AbsorptionAttribute.CastingFocus] : 0;
|
||||
set => this[AbsorptionAttribute.CastingFocus] = Owner is BaseArmor ? value : 0;
|
||||
}
|
||||
|
||||
public static int GetValue(Mobile m, AbsorptionAttribute attribute)
|
||||
{
|
||||
if (!Core.SA)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var items = m.Items;
|
||||
var value = 0;
|
||||
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
var obj = items[i];
|
||||
|
||||
if (obj is BaseArmor armor)
|
||||
{
|
||||
var attrs = armor.AbsorptionAttributes;
|
||||
|
||||
if (attrs != null)
|
||||
{
|
||||
value += attrs[attribute];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void GetProperties(IPropertyList list)
|
||||
{
|
||||
var castingFocus = CastingFocus;
|
||||
if (Core.SA && castingFocus != 0)
|
||||
{
|
||||
list.Add(1113696, castingFocus); // Casting Focus ~1_val~%
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => "...";
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum NegativeAttribute
|
||||
{
|
||||
|
|
|
|||
|
|
@ -88,10 +88,23 @@ namespace Server.Spells
|
|||
if (Caster.Player && IsCasting)
|
||||
{
|
||||
var hasProtection = ProtectionSpell.Registry.TryGetValue(Caster, out var d);
|
||||
if (!hasProtection || d < 1000 && d < Utility.Random(1000))
|
||||
if (hasProtection && (d >= 1000 || d >= Utility.Random(1000)))
|
||||
{
|
||||
Disturb(DisturbType.Hurt, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
var castingFocus = Math.Min(
|
||||
AOS.CastingFocusChanceCap,
|
||||
AbsorptionAttributes.GetValue(Caster, AbsorptionAttribute.CastingFocus)
|
||||
);
|
||||
|
||||
if (castingFocus > Utility.Random(100))
|
||||
{
|
||||
Caster.SendLocalizedMessage(1113690); // You regain your focus and continue casting the spell.
|
||||
return;
|
||||
}
|
||||
|
||||
Disturb(DisturbType.Hurt, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue