refactor: keep Spell Focusing reusable without sash
This commit is contained in:
parent
dfee8f09c5
commit
7f40274c5c
5 changed files with 206 additions and 713 deletions
|
|
@ -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<int> Numbers { get; } = [];
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SpellFocusingSash> 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<ContextMenuEntry> 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<SpellFocusingSash>.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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue