diff --git a/Projects/UOContent.Tests/Tests/Items/SpellFocusingPropertyTests.cs b/Projects/UOContent.Tests/Tests/Items/SpellFocusingPropertyTests.cs index cf43be1ed..c3680b526 100644 --- a/Projects/UOContent.Tests/Tests/Items/SpellFocusingPropertyTests.cs +++ b/Projects/UOContent.Tests/Tests/Items/SpellFocusingPropertyTests.cs @@ -112,6 +112,191 @@ public class SpellFocusingPropertyTests } } + [Fact] + public void SpellFocusing_UsesThePvMSequenceAndResetsAfterThePeak() + { + var previousExpansion = Core.Expansion; + var caster = CreateMobile(player: true); + var target = CreateMobile(player: false); + var item = new Katana(); + var spell = new TestSpell(caster); + + try + { + Core.Expansion = Expansion.AOS; + caster.AddItem(item); + item.Attributes.SpellFocusing = 1; + + var expected = new[] + { + -30, -24, -18, -12, -6, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 + }; + + foreach (var expectedOffset in expected) + { + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var offset)); + Assert.Equal(expectedOffset, offset); + } + + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var resetOffset)); + Assert.Equal(-30, resetOffset); + } + finally + { + Core.Expansion = previousExpansion; + item.Delete(); + target.Delete(); + caster.Delete(); + } + } + + [Fact] + public void SpellFocusing_HoldsThePvPCapAndResetsOnTargetChange() + { + var previousExpansion = Core.Expansion; + var caster = CreateMobile(player: true); + var target = CreateMobile(player: true); + var secondTarget = CreateMobile(player: true); + var item = new Katana(); + var spell = new TestSpell(caster); + + try + { + Core.Expansion = Expansion.AOS; + caster.AddItem(item); + item.Attributes.SpellFocusing = 1; + + for (var i = 0; i < 15; i++) + { + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out _)); + } + + for (var i = 0; i < 6; i++) + { + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var offset)); + Assert.Equal(20, offset); + } + + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var resetOffset)); + Assert.Equal(-30, resetOffset); + + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, secondTarget, out var targetResetOffset)); + Assert.Equal(-30, targetResetOffset); + } + finally + { + Core.Expansion = previousExpansion; + item.Delete(); + secondTarget.Delete(); + target.Delete(); + caster.Delete(); + } + } + + [Fact] + public void SpellFocusing_AppliesTheModifierAtTheSpellDamageHook() + { + var previousExpansion = Core.Expansion; + var caster = CreateMobile(player: true); + var target = CreateMobile(player: false); + var item = new Katana(); + var spell = new TestSpell(caster); + + try + { + Core.Expansion = Expansion.AOS; + caster.AddItem(item); + item.Attributes.SpellFocusing = 1; + var startingHits = target.Hits; + + SpellHelper.Damage(spell, target, 100, 100, 0, 0, 0, 0); + + Assert.Equal(startingHits - 70, target.Hits); + } + finally + { + Core.Expansion = previousExpansion; + item.Delete(); + target.Delete(); + caster.Delete(); + } + } + + [Fact] + public void SpellFocusing_ClearsTheSequenceWhenTheCasterIsCleared() + { + var previousExpansion = Core.Expansion; + var caster = CreateMobile(player: true); + var target = CreateMobile(player: false); + var item = new Katana(); + var spell = new TestSpell(caster); + + try + { + Core.Expansion = Expansion.AOS; + caster.AddItem(item); + item.Attributes.SpellFocusing = 1; + + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var firstOffset)); + Assert.Equal(-30, firstOffset); + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var secondOffset)); + Assert.Equal(-24, secondOffset); + + SpellFocusing.Clear(caster); + + Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var resetOffset)); + Assert.Equal(-30, resetOffset); + } + finally + { + Core.Expansion = previousExpansion; + item.Delete(); + target.Delete(); + caster.Delete(); + } + } + + [Fact] + public void SpellFocusing_RequiresAnEligibleSpellAndTheAoSExpansion() + { + var previousExpansion = Core.Expansion; + var caster = CreateMobile(player: true); + var target = CreateMobile(player: false); + var item = new Katana(); + var qualifyingSpell = new TestSpell(caster); + var nonQualifyingSpell = new NonQualifyingSpell(caster); + + try + { + caster.AddItem(item); + item.Attributes.SpellFocusing = 1; + + Core.Expansion = Expansion.UOR; + Assert.False(SpellFocusing.TryGetDamageOffset(qualifyingSpell, caster, target, out _)); + + Core.Expansion = Expansion.AOS; + Assert.False(SpellFocusing.TryGetDamageOffset(nonQualifyingSpell, caster, target, out _)); + } + finally + { + Core.Expansion = previousExpansion; + item.Delete(); + target.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 sealed class TestSpell : Spell { private static readonly SpellInfo TestInfo = new("Test Spell", "test"); @@ -130,6 +315,23 @@ public class SpellFocusingPropertyTests public override int GetMana() => 0; } + private sealed class NonQualifyingSpell : Spell + { + private static readonly SpellInfo TestInfo = new("Non-Qualifying Spell", "no"); + + public NonQualifyingSpell(Mobile caster) : base(caster, null, TestInfo) + { + } + + public override TimeSpan CastDelayBase => TimeSpan.Zero; + + public override void OnCast() + { + } + + public override int GetMana() => 0; + } + private sealed class RecordingPropertyList : IPropertyList { public List Numbers { get; } = []; diff --git a/Projects/UOContent.Tests/Tests/Items/SpellFocusingSashTests.cs b/Projects/UOContent.Tests/Tests/Items/SpellFocusingSashTests.cs deleted file mode 100644 index a623680a1..000000000 --- a/Projects/UOContent.Tests/Tests/Items/SpellFocusingSashTests.cs +++ /dev/null @@ -1,388 +0,0 @@ -using System; -using System.Collections.Generic; -using Server; -using Server.Collections; -using Server.ContextMenus; -using Server.Items; -using Server.Spells; -using Server.Tests; -using Server.Text; -using Xunit; - -namespace UOContent.Tests; - -[Collection("Sequential UOContent Tests")] -public class SpellFocusingSashTests -{ - private const int SpellFocusingCliloc = 1150058; - private const int BrittleCliloc = 1116209; - private const int ManaIncreaseCliloc = 1060439; - private const int DefendChanceCliloc = 1060408; - private const int StrengthRequirementCliloc = 1061170; - private const int DurabilityCliloc = 1060639; - - [Fact] - public void SpellFocusingSash_HasArtifactStatsAndPropertyOrder() - { - var previousExpansion = Core.Expansion; - var caster = CreateMobile(player: true); - var sash = new SpellFocusingSash(); - - try - { - Core.Expansion = Expansion.SA; - caster.AddItem(sash); - var list = new RecordingPropertyList(); - sash.GetProperties(list); - - Assert.Equal(1.0, sash.DefaultWeight); - Assert.Equal(1, sash.Attributes.BonusMana); - Assert.Equal(5, sash.Attributes.DefendChance); - Assert.Equal(10, sash.StrRequirement); - Assert.Equal(255, sash.HitPoints); - Assert.Equal(255, sash.MaxHitPoints); - Assert.Equal(1, AosAttributes.GetValue(caster, AosAttribute.BonusMana)); - Assert.Equal(5, AosAttributes.GetValue(caster, AosAttribute.DefendChance)); - Assert.True(NegativeAttributes.IsBrittle(sash)); - - var spellFocusingIndex = list.Entries.FindIndex(entry => entry.Number == SpellFocusingCliloc); - var brittleIndex = list.Entries.FindIndex(entry => entry.Number == BrittleCliloc); - var defendChanceIndex = list.Entries.FindIndex(entry => entry.Number == DefendChanceCliloc); - var manaIncreaseIndex = list.Entries.FindIndex(entry => entry.Number == ManaIncreaseCliloc); - - Assert.True(spellFocusingIndex > 0); - Assert.Equal(1072788, list.Entries[spellFocusingIndex - 1].Number); - Assert.True(spellFocusingIndex < brittleIndex); - Assert.True(brittleIndex < manaIncreaseIndex); - Assert.True(manaIncreaseIndex < defendChanceIndex); - Assert.Contains(list.Entries, entry => entry.Number == StrengthRequirementCliloc && entry.Argument == "10"); - Assert.Contains(list.Entries, entry => entry.Number == DurabilityCliloc && entry.Argument == "255\t255"); - - Core.Expansion = Expansion.ML; - var preStygianAbyss = new RecordingPropertyList(); - sash.GetProperties(preStygianAbyss); - Assert.DoesNotContain(preStygianAbyss.Entries, entry => entry.Number == SpellFocusingCliloc); - Assert.DoesNotContain(preStygianAbyss.Entries, entry => entry.Number == ManaIncreaseCliloc); - Assert.DoesNotContain(preStygianAbyss.Entries, entry => entry.Number == DefendChanceCliloc); - Assert.Equal(0, AosAttributes.GetValue(caster, AosAttribute.BonusMana)); - Assert.Equal(0, AosAttributes.GetValue(caster, AosAttribute.DefendChance)); - Assert.False(NegativeAttributes.IsBrittle(sash)); - } - finally - { - Core.Expansion = previousExpansion; - sash.Delete(); - caster.Delete(); - } - } - - [Fact] - public void SpellFocusingSash_EnabledStateSerializesAndContextMenuTogglesIt() - { - var previousExpansion = Core.Expansion; - var caster = CreateMobile(player: true); - var sash = new SpellFocusingSash(); - var deserialized = new SpellFocusingSash(); - - try - { - Core.Expansion = Expansion.SA; - caster.AddItem(sash); - - var menu = ContextMenuSystem.CreateContextMenu(caster, sash); - var entry = Assert.Single(menu.Entries, e => e.Number == 3006151); - entry.OnClick(caster, sash); - Assert.False(sash.Enabled); - - var writer = new BufferWriter(true); - sash.Serialize(writer); - var buffer = new byte[writer.Position]; - writer.Buffer.AsSpan(0, (int)writer.Position).CopyTo(buffer); - - deserialized.Deserialize(new BufferReader(buffer)); - Assert.False(deserialized.Enabled); - } - finally - { - Core.Expansion = previousExpansion; - sash.Delete(); - deserialized.Delete(); - caster.Delete(); - } - } - - [Fact] - public void SpellFocusingSash_AppliesPvMSequenceAndResetsAfterPeak() - { - var previousExpansion = Core.Expansion; - var caster = CreateMobile(player: true); - var target = CreateMobile(player: false); - var sash = new SpellFocusingSash(); - var spell = new TestSpell(caster); - - try - { - Core.Expansion = Expansion.SA; - caster.AddItem(sash); - var expected = new[] { -30, -24, -18, -12, -6, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }; - - foreach (var expectedOffset in expected) - { - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var actual)); - Assert.Equal(expectedOffset, actual); - } - - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var resetOffset)); - Assert.Equal(-30, resetOffset); - } - finally - { - Core.Expansion = previousExpansion; - sash.Delete(); - target.Delete(); - caster.Delete(); - } - } - - [Fact] - public void SpellHelperDamage_AppliesTheSashModifierBeforeResists() - { - var previousExpansion = Core.Expansion; - var caster = CreateMobile(player: true); - var target = CreateMobile(player: false); - var sash = new SpellFocusingSash(); - var spell = new TestSpell(caster); - - try - { - Core.Expansion = Expansion.SA; - caster.AddItem(sash); - var startingHits = target.Hits; - - SpellHelper.Damage(spell, target, 100, 100, 0, 0, 0, 0); - - Assert.Equal(startingHits - 70, target.Hits); - } - finally - { - Core.Expansion = previousExpansion; - sash.Delete(); - target.Delete(); - caster.Delete(); - } - } - - [Fact] - public void SpellFocusingSash_HoldsPvPCapForFiveAdditionalSpellsAndResetsOnTargetChange() - { - var previousExpansion = Core.Expansion; - var caster = CreateMobile(player: true); - var target = CreateMobile(player: true); - var secondTarget = CreateMobile(player: true); - var sash = new SpellFocusingSash(); - var spell = new TestSpell(caster); - - try - { - Core.Expansion = Expansion.SA; - caster.AddItem(sash); - - for (var i = 0; i < 15; i++) - { - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out _)); - } - - for (var i = 0; i < 6; i++) - { - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var offset)); - Assert.Equal(20, offset); - } - - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var resetOffset)); - Assert.Equal(-30, resetOffset); - - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, secondTarget, out var targetResetOffset)); - Assert.Equal(-30, targetResetOffset); - - sash.Enabled = false; - Assert.False(SpellFocusing.TryGetDamageOffset(spell, caster, target, out _)); - } - finally - { - Core.Expansion = previousExpansion; - sash.Delete(); - secondTarget.Delete(); - target.Delete(); - caster.Delete(); - } - } - - [Fact] - public void SpellFocusingSash_ResetsWhenTheCasterIsCleared() - { - var previousExpansion = Core.Expansion; - var caster = CreateMobile(player: true); - var target = CreateMobile(player: false); - var sash = new SpellFocusingSash(); - var spell = new TestSpell(caster); - - try - { - Core.Expansion = Expansion.SA; - caster.AddItem(sash); - - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var firstOffset)); - Assert.Equal(-30, firstOffset); - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var secondOffset)); - Assert.Equal(-24, secondOffset); - - SpellFocusingSash.Clear(target); - - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var targetResetOffset)); - Assert.Equal(-30, targetResetOffset); - - SpellFocusingSash.Clear(caster); - - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, target, out var casterResetOffset)); - Assert.Equal(-30, casterResetOffset); - - target.Delete(); - Assert.False(SpellFocusing.TryGetDamageOffset(spell, caster, target, out _)); - - var replacement = CreateMobile(player: false); - try - { - Assert.True(SpellFocusing.TryGetDamageOffset(spell, caster, replacement, out var invalidTargetReset)); - Assert.Equal(-30, invalidTargetReset); - } - finally - { - replacement.Delete(); - } - } - finally - { - Core.Expansion = previousExpansion; - sash.Delete(); - target.Delete(); - caster.Delete(); - } - } - - [Fact] - public void SpellFocusingSash_ExcludesNonQualifyingSpellsAndPreStygianAbyss() - { - var previousExpansion = Core.Expansion; - var caster = CreateMobile(player: true); - var target = CreateMobile(player: false); - var sash = new SpellFocusingSash(); - - try - { - caster.AddItem(sash); - var spell = new NonQualifyingSpell(caster); - var qualifyingSpell = new TestSpell(caster); - - Core.Expansion = Expansion.ML; - Assert.False(SpellFocusing.TryGetDamageOffset(qualifyingSpell, caster, target, out _)); - Assert.Equal(0, AosAttributes.GetValue(caster, AosAttribute.BonusMana)); - Assert.Equal(0, AosAttributes.GetValue(caster, AosAttribute.DefendChance)); - Assert.False(SpellFocusing.TryGetDamageOffset(spell, caster, target, out _)); - - Core.Expansion = Expansion.SA; - Assert.False(SpellFocusing.TryGetDamageOffset(spell, caster, target, out _)); - } - finally - { - Core.Expansion = previousExpansion; - sash.Delete(); - target.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 sealed class TestSpell : Spell - { - private static readonly SpellInfo TestInfo = new("Test Spell", "test"); - - public TestSpell(Mobile caster) : base(caster, null, TestInfo) - { - } - - public override bool SpellFocusingEligible => true; - public override TimeSpan CastDelayBase => TimeSpan.Zero; - - public override void OnCast() - { - } - - public override int GetMana() => 0; - } - - private sealed class NonQualifyingSpell : Spell - { - private static readonly SpellInfo TestInfo = new("Non-Qualifying Spell", "no"); - - public NonQualifyingSpell(Mobile caster) : base(caster, null, TestInfo) - { - } - - public override TimeSpan CastDelayBase => TimeSpan.Zero; - - public override void OnCast() - { - } - - public override int GetMana() => 0; - } - - private sealed record PropertyEntry(int Number, string Argument); - - private sealed class RecordingPropertyList : IPropertyList - { - private string _interpolated = string.Empty; - - public List Entries { get; } = []; - - public void Reset() - { - } - - public void Terminate() - { - } - - public void Add(int number) => Entries.Add(new PropertyEntry(number, string.Empty)); - public void Add(int number, string argument) => Entries.Add(new PropertyEntry(number, argument)); - public void Add(ReadOnlySpan argument) => Entries.Add(new PropertyEntry(0, argument.ToString())); - public void Add(int number, ReadOnlySpan argument) => Entries.Add(new PropertyEntry(number, argument.ToString())); - public void AddChunked(ReadOnlySpan text) => Entries.Add(new PropertyEntry(0, text.ToString())); - public OplTextBlock TextBlock() => new(this); - public void Add(int number, int value) => Entries.Add(new PropertyEntry(number, value.ToString())); - public void AddLocalized(int value) => Entries.Add(new PropertyEntry(0, value.ToString())); - public void AddLocalized(int number, int value) => Entries.Add(new PropertyEntry(number, value.ToString())); - public void Add(ref IPropertyList.InterpolatedStringHandler handler) => Entries.Add(new PropertyEntry(0, _interpolated)); - public void Add(int number, ref IPropertyList.InterpolatedStringHandler handler) => Entries.Add(new PropertyEntry(number, _interpolated)); - public void InitializeInterpolation(int literalLength, int formattedCount) => _interpolated = string.Empty; - public void AppendLiteral(string value) => _interpolated += value; - public void AppendFormatted(T value) => _interpolated += value; - public void AppendFormatted(T value, string format) => _interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value; - public void AppendFormatted(T value, int alignment) => _interpolated += value; - public void AppendFormatted(T value, int alignment, string format) => _interpolated += value is IFormattable formattable ? formattable.ToString(format, null) : value; - public void AppendFormatted(ReadOnlySpan value) => _interpolated += value.ToString(); - public void AppendFormatted(ReadOnlySpan value, int alignment, string format = null) => _interpolated += value.ToString(); - public void AppendFormatted(object value, int alignment = 0, string format = null) => _interpolated += value; - public void AppendFormatted(string value) => _interpolated += value; - public void AppendFormatted(string value, int alignment, string format = null) => _interpolated += value; - } -} diff --git a/Projects/UOContent/Items/Clothing/SpellFocusingSash.cs b/Projects/UOContent/Items/Clothing/SpellFocusingSash.cs deleted file mode 100644 index cc4016137..000000000 --- a/Projects/UOContent/Items/Clothing/SpellFocusingSash.cs +++ /dev/null @@ -1,289 +0,0 @@ -using System.Collections.Generic; -using ModernUO.CodeGeneratedEvents; -using ModernUO.Serialization; -using Server.Collections; -using Server.ContextMenus; -using Server.Engines.BuffIcons; -using Server.Mobiles; -using Server.Spells; - -namespace Server.Items; - -[Flippable(0x1541, 0x1542)] -[SerializationGenerator(0, false)] -public partial class SpellFocusingSash : BaseMiddleTorso -{ - private const int SpellFocusingCliloc = 1150058; - private const int BrittleCliloc = 1116209; - private const int ResetMessage = 1150117; - private const int TunedMessage = 1150118; - private const int PeakMessage = 1150116; - private const int BuffTitleCliloc = 1151391; - private const int BuffSecondaryCliloc = 1151392; - private const int SequenceLength = 21; - - private static readonly HashSet ActiveSashes = []; - - private Mobile _spellCastTarget; - private int _spellCastCount; - private bool _enabled = true; - - [Constructible] - public SpellFocusingSash() : base(0x1541) - { - Attributes.BonusMana = 1; - Attributes.DefendChance = 5; - HitPoints = MaxHitPoints = 255; - } - - public override int LabelNumber => 1150059; - public override double DefaultWeight => 1.0; - public override int InitMinHits => 255; - public override int InitMaxHits => 255; - public override int AosStrReq => 10; - - [SerializableProperty(0, useField: nameof(_enabled))] - [CommandProperty(AccessLevel.GameMaster)] - public bool Enabled - { - get => _enabled; - set - { - if (_enabled == value) - { - return; - } - - _enabled = value; - ResetSequence(Parent as Mobile); - InvalidateProperties(); - this.MarkDirty(); - } - } - - public static void Configure() - { - EventSink.Logout += Clear; - } - - public override void AddNameProperties(IPropertyList list) - { - base.AddNameProperties(list); - - if (Core.SA) - { - list.Add(SpellFocusingCliloc); - list.Add(BrittleCliloc); - - if (Attributes.BonusMana != 0) - { - list.Add(1060439, Attributes.BonusMana); - } - - if (Attributes.DefendChance != 0) - { - list.Add(1060408, Attributes.DefendChance); - } - } - } - - public override void GetContextMenuEntries(Mobile from, ref PooledRefList list) - { - base.GetContextMenuEntries(from, ref list); - - if (Core.SA && from == Parent && from.Alive) - { - list.Add(new ToggleSpellFocusingEntry(this)); - } - } - - public override void OnAdded(IEntity parent) - { - base.OnAdded(parent); - - if (parent is Mobile mobile) - { - ResetSequence(mobile); - } - } - - public override void OnRemoved(IEntity parent) - { - if (parent is Mobile mobile) - { - ResetSequence(mobile); - } - - base.OnRemoved(parent); - } - - public override void OnDelete() - { - ResetSequence(Parent as Mobile); - base.OnDelete(); - } - - public static bool TryGetDamageOffset(Spell spell, Mobile caster, Mobile target, out int offset) - { - offset = 0; - - if (!Core.SA || spell?.SpellFocusingEligible != true || caster?.Deleted != false) - { - return false; - } - - if (caster.FindItemOnLayer(Layer.MiddleTorso) is not SpellFocusingSash sash || !sash.Enabled) - { - return false; - } - - if (target?.Deleted != false || !target.Alive || !caster.Alive) - { - sash.ResetSequence(caster); - return false; - } - - return sash.TryGetDamageOffset(caster, target, out offset); - } - - [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))] - [OnEvent(nameof(PlayerMobile.PlayerDeletedEvent))] - [OnEvent(nameof(BaseCreature.CreatureDeathEvent))] - [OnEvent(nameof(BaseCreature.CreatureDeletedEvent))] - public static void Clear(Mobile mobile) - { - if (mobile == null) - { - return; - } - - if (mobile.FindItemOnLayer(Layer.MiddleTorso) is SpellFocusingSash sash) - { - sash.ResetSequence(mobile); - } - - if (ActiveSashes.Count == 0) - { - return; - } - - using var sashes = PooledRefList.Create(); - - foreach (var activeSash in ActiveSashes) - { - if (activeSash._spellCastTarget == mobile) - { - sashes.Add(activeSash); - } - } - - for (var i = 0; i < sashes.Count; i++) - { - var activeSash = sashes[i]; - activeSash.ResetSequence(activeSash.Parent as Mobile); - } - } - - private bool TryGetDamageOffset(Mobile caster, Mobile target, out int offset) - { - offset = 0; - - if (_spellCastTarget?.Deleted != false || !_spellCastTarget.Alive) - { - ResetSequence(caster); - } - - if (_spellCastTarget != target) - { - if (_spellCastTarget != null) - { - caster.SendLocalizedMessage(ResetMessage); - } - - ResetSequence(caster); - _spellCastTarget = target; - ActiveSashes.Add(this); - } - - offset = GetDamageOffset(_spellCastCount, target.Player); - _spellCastCount++; - - if (offset == 0) - { - caster.SendLocalizedMessage(TunedMessage); - } - - if (_spellCastCount >= SequenceLength) - { - caster.SendLocalizedMessage(PeakMessage); - ResetSequence(caster); - } - else - { - RefreshBuffInfo(caster, offset); - } - - return true; - } - - private static int GetDamageOffset(int castCount, bool playerTarget) - { - if (castCount < 6) - { - return -30 + castCount * 6; - } - - var offset = (castCount - 5) * 2; - return playerTarget ? System.Math.Min(offset, 20) : System.Math.Min(offset, 30); - } - - private void ResetSequence(Mobile caster) - { - ActiveSashes.Remove(this); - _spellCastTarget = null; - _spellCastCount = 0; - - if (caster is PlayerMobile player) - { - player.RemoveBuff(BuffIcon.SpellFocusingBuff); - player.RemoveBuff(BuffIcon.SpellFocusingDebuff); - } - } - - private void RefreshBuffInfo(Mobile caster, int offset) - { - if (caster is not PlayerMobile player || caster != Parent || !Enabled) - { - return; - } - - player.RemoveBuff(offset < 0 ? BuffIcon.SpellFocusingBuff : BuffIcon.SpellFocusingDebuff); - player.AddBuff( - new BuffInfo( - offset < 0 ? BuffIcon.SpellFocusingDebuff : BuffIcon.SpellFocusingBuff, - BuffTitleCliloc, - BuffSecondaryCliloc, - args: $"{_spellCastTarget?.Name ?? "None"}\t{offset}" - ) - ); - } - - private sealed class ToggleSpellFocusingEntry : ContextMenuEntry - { - private readonly SpellFocusingSash _sash; - - public ToggleSpellFocusingEntry(SpellFocusingSash sash) : base(sash.Enabled ? 3006151 : 3006150, 2) - { - _sash = sash; - } - - public override void OnClick(Mobile from, IEntity target) - { - if (target is SpellFocusingSash sash && sash == _sash && from == sash.Parent && from.Alive) - { - sash.Enabled = !sash.Enabled; - from.SendMessage(sash.Enabled ? "Spell Focusing enabled." : "Spell Focusing disabled."); - } - } - } -} diff --git a/Projects/UOContent/Misc/AOS.cs b/Projects/UOContent/Misc/AOS.cs index b89f2975f..165106294 100644 --- a/Projects/UOContent/Misc/AOS.cs +++ b/Projects/UOContent/Misc/AOS.cs @@ -615,8 +615,7 @@ namespace Server list.Add(1060401, prop); // damage increase ~1_val~% } - // The SA event sash emits its fixed Mana/Defense lines in artifact order from AddNameProperties. - if (Owner is not SpellFocusingSash && (prop = DefendChance) != 0) + if ((prop = DefendChance) != 0) { list.Add(1060408, prop); // defense chance increase ~1_val~% } @@ -671,7 +670,7 @@ namespace Server list.Add(1060436, prop); // luck ~1_val~ } - if (Owner is not SpellFocusingSash && (prop = BonusMana) != 0) + if ((prop = BonusMana) != 0) { list.Add(1060439, prop); // mana increase ~1_val~ } @@ -792,7 +791,7 @@ namespace Server { var attrs = clothing.Attributes; - if (attrs != null && (clothing is not SpellFocusingSash || Core.SA)) + if (attrs != null) { value += attrs[attribute]; } @@ -1604,11 +1603,6 @@ namespace Server public static bool IsBrittle(Item item) { - if (item is SpellFocusingSash) - { - return Core.SA; - } - if (!Core.HS) { return false; diff --git a/Projects/UOContent/Spells/Base/SpellFocusing.cs b/Projects/UOContent/Spells/Base/SpellFocusing.cs index d5a1fadf9..9d0facc2a 100644 --- a/Projects/UOContent/Spells/Base/SpellFocusing.cs +++ b/Projects/UOContent/Spells/Base/SpellFocusing.cs @@ -37,41 +37,15 @@ public static class SpellFocusing { offset = 0; - if (!Core.AOS || spell?.SpellFocusingEligible != true) + if (!Core.AOS || spell?.SpellFocusingEligible != true || caster?.Alive != true || target?.Alive != true) { return false; } - if (caster?.Alive != true) - { - if (caster != null) - { - SpellFocusingSash.Clear(caster); - } - - return false; - } - - if (target?.Alive != true) - { - SpellFocusingSash.Clear(caster); - return false; - } - var items = caster.Items; for (var i = 0; i < items.Count; i++) { - if (items[i] is SpellFocusingSash) - { - if (SpellFocusingSash.TryGetDamageOffset(spell, caster, target, out offset)) - { - return true; - } - - continue; - } - if (items[i] is IAosItem { Attributes.SpellFocusing: not 0 } item) { offset = item.Attributes.GetSpellFocusingOffset(caster, target);