diff --git a/Projects/UOContent.Tests/Tests/Skills/Tracking/TrackingGumpTests.cs b/Projects/UOContent.Tests/Tests/Skills/Tracking/TrackingGumpTests.cs deleted file mode 100644 index c55cf740c6..0000000000 --- a/Projects/UOContent.Tests/Tests/Skills/Tracking/TrackingGumpTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Server.Gumps; -using Server.Mobiles; -using Server.SkillHandlers; -using Server.Tests; -using Server.Tests.Network; -using Xunit; - -namespace UOContent.Tests; - -public class TrackingGumpTests : IClassFixture -{ - [Fact] - // Regression test used to identify an issue with AddItem compilation of the packet - public void TestTrackingGump() - { - var pm = new PlayerMobile(); - pm.Skills.Tracking.BaseFixedPoint = 1000; - var g = new TrackWhatGump(pm); - - var ns = PacketTestUtilities.CreateTestNetState(); - - var expected = g.Compile(ns).Compile(); - ns.SendGump(g); - - var result = ns.SendPipe.Reader.AvailableToRead(); - AssertThat.Equal(result, expected); - } -} diff --git a/Projects/UOContent/Skills/Tracking/Tracking.cs b/Projects/UOContent/Skills/Tracking/Tracking.cs index f59f3462a2..0cb1f792aa 100644 --- a/Projects/UOContent/Skills/Tracking/Tracking.cs +++ b/Projects/UOContent/Skills/Tracking/Tracking.cs @@ -7,350 +7,349 @@ using Server.Spells; using Server.Spells.Necromancy; -namespace Server.SkillHandlers +namespace Server.SkillHandlers; + +public static class Tracking { - public static class Tracking - { - private static readonly Dictionary _table = new(); + private static readonly Dictionary _table = new(); - public static unsafe void Configure() - { - IncomingExtendedCommandPackets.RegisterExtended(0x07, true, &QuestArrow); - } + public static unsafe void Configure() + { + IncomingExtendedCommandPackets.RegisterExtended(0x07, true, &QuestArrow); + } - public static void Initialize() - { - SkillInfo.Table[(int)SkillName.Tracking].Callback = OnUse; - } + public static void Initialize() + { + SkillInfo.Table[(int)SkillName.Tracking].Callback = OnUse; + } - public static void QuestArrow(NetState state, SpanReader reader) + public static void QuestArrow(NetState state, SpanReader reader) + { + if (state.Mobile is PlayerMobile from) { - if (state.Mobile is PlayerMobile from) - { - var rightClick = reader.ReadBoolean(); + var rightClick = reader.ReadBoolean(); - from.QuestArrow?.OnClick(rightClick); - } + from.QuestArrow?.OnClick(rightClick); } + } - public static TimeSpan OnUse(Mobile m) + public static TimeSpan OnUse(Mobile m) + { + if (m is PlayerMobile pm) { - if (m is PlayerMobile pm) - { - m.SendLocalizedMessage(1011350); // What do you wish to track? + m.SendLocalizedMessage(1011350); // What do you wish to track? - var gumps = pm.GetGumps(); - - gumps.Close(); - gumps.Close(); - gumps.Send(new TrackWhatGump(pm)); - } + var gumps = pm.GetGumps(); - return TimeSpan.FromSeconds(10.0); // 10 second delay before being able to re-use a skill + gumps.Close(); + gumps.Close(); + gumps.Send(new TrackWhatGump()); } - public static void AddInfo(Mobile tracker, Mobile target) + return TimeSpan.FromSeconds(10.0); // 10 second delay before being able to re-use a skill + } + + public static void AddInfo(Mobile tracker, Mobile target) + { + var info = new TrackingInfo(tracker, target); + _table[tracker] = info; + } + + public static double GetStalkingBonus(Mobile tracker, Mobile target) + { + if (!_table.Remove(tracker, out var info) || info._target != target || info._map != target.Map) { - var info = new TrackingInfo(tracker, target); - _table[tracker] = info; + return 0.0; } - public static double GetStalkingBonus(Mobile tracker, Mobile target) - { - if (!_table.Remove(tracker, out var info) || info._target != target || info._map != target.Map) - { - return 0.0; - } + var xDelta = info._location.X - target.X; + var yDelta = info._location.Y - target.Y; - var xDelta = info._location.X - target.X; - var yDelta = info._location.Y - target.Y; + var bonus = Math.Sqrt(xDelta * xDelta + yDelta * yDelta); - var bonus = Math.Sqrt(xDelta * xDelta + yDelta * yDelta); + return Core.ML ? Math.Min(bonus, 10 + tracker.Skills.Tracking.Value / 10) : bonus; + } - return Core.ML ? Math.Min(bonus, 10 + tracker.Skills.Tracking.Value / 10) : bonus; - } + public static void ClearTrackingInfo(Mobile tracker) + { + _table.Remove(tracker); + } - public static void ClearTrackingInfo(Mobile tracker) - { - _table.Remove(tracker); - } + private class TrackingInfo + { + public Point2D _location; + public readonly Map _map; + public readonly Mobile _target; + public Mobile _tracker; - private class TrackingInfo + public TrackingInfo(Mobile tracker, Mobile target) { - public Point2D _location; - public readonly Map _map; - public readonly Mobile _target; - public Mobile _tracker; - - public TrackingInfo(Mobile tracker, Mobile target) - { - _tracker = tracker; - _target = target; - _location = new Point2D(target); - _map = target.Map; - } + _tracker = tracker; + _target = target; + _location = new Point2D(target); + _map = target.Map; } } +} - public class TrackWhatGump : Gump +public class TrackWhatGump : StaticGump +{ + public TrackWhatGump() : base(20, 30) { - private readonly PlayerMobile _from; - private readonly bool _success; - - public TrackWhatGump(PlayerMobile from) : base(20, 30) - { - _from = from; - _success = from.CheckSkill(SkillName.Tracking, 0.0, 21.1); + } - AddPage(0); + protected override void BuildLayout(ref StaticGumpBuilder builder) + { + builder.AddPage(); - AddBackground(0, 0, 440, 135, 5054); + builder.AddBackground(0, 0, 440, 135, 5054); - AddBackground(10, 10, 420, 75, 2620); - AddBackground(10, 85, 420, 25, 3000); + builder.AddBackground(10, 10, 420, 75, 2620); + builder.AddBackground(10, 85, 420, 25, 3000); - AddItem(20, 20, 9682); - AddButton(20, 110, 4005, 4007, 1); - AddHtmlLocalized(20, 90, 100, 20, 1018087); // Animals + builder.AddItem(20, 20, 9682); + builder.AddButton(20, 110, 4005, 4007, 1); + builder.AddHtmlLocalized(20, 90, 100, 20, 1018087); // Animals - AddItem(120, 20, 9607); - AddButton(120, 110, 4005, 4007, 2); - AddHtmlLocalized(120, 90, 100, 20, 1018088); // Monsters + builder.AddItem(120, 20, 9607); + builder.AddButton(120, 110, 4005, 4007, 2); + builder.AddHtmlLocalized(120, 90, 100, 20, 1018088); // Monsters - AddItem(220, 20, 8454); - AddButton(220, 110, 4005, 4007, 3); - AddHtmlLocalized(220, 90, 100, 20, 1018089); // Human NPCs + builder.AddItem(220, 20, 8454); + builder.AddButton(220, 110, 4005, 4007, 3); + builder.AddHtmlLocalized(220, 90, 100, 20, 1018089); // Human NPCs - AddItem(320, 20, 8455); - AddButton(320, 110, 4005, 4007, 4); - AddHtmlLocalized(320, 90, 100, 20, 1018090); // Players - } + builder.AddItem(320, 20, 8455); + builder.AddButton(320, 110, 4005, 4007, 4); + builder.AddHtmlLocalized(320, 90, 100, 20, 1018090); // Players + } - public override void OnResponse(NetState state, in RelayInfo info) + public override void OnResponse(NetState state, in RelayInfo info) + { + if (info.ButtonID is >= 1 and <= 4 && state.Mobile is PlayerMobile pm) { - if (info.ButtonID >= 1 && info.ButtonID <= 4) - { - TrackWhoGump.DisplayTo(_success, _from, info.ButtonID - 1); - } + var success = pm.CheckSkill(SkillName.Tracking, 0.0, 21.1); + TrackWhoGump.DisplayTo(success, pm, info.ButtonID - 1); } } +} - public class TrackWhoGump : Gump - { - private const int MaxClosest = 12; +public class TrackWhoGump : DynamicGump +{ + private const int MaxClosest = 12; - private readonly PlayerMobile _from; - private readonly Mobile[] _targets; - private readonly int _range; + private readonly Mobile[] _targets; + private readonly int _range; - private TrackWhoGump(PlayerMobile from, Mobile[] targets, int range) : base(20, 30) - { - _from = from; - _targets = targets; - _range = range; + private TrackWhoGump(Mobile[] targets, int range) : base(20, 30) + { + _targets = targets; + _range = range; + } - AddPage(0); + public static void DisplayTo(bool success, PlayerMobile from, int type) + { + if (!success) + { + from.SendLocalizedMessage(1018092); // You see no evidence of those in the area. + return; + } - AddBackground(0, 0, 440, 155, 5054); + var map = from.Map; - AddBackground(10, 10, 420, 75, 2620); - AddBackground(10, 85, 420, 45, 3000); + if (map == null) + { + return; + } - if (targets.Length > 4) - { - AddBackground(0, 155, 440, 155, 5054); + from.CheckSkill(SkillName.Tracking, 21.1, 100.0); // Passive gain - AddBackground(10, 165, 420, 75, 2620); - AddBackground(10, 240, 420, 45, 3000); + var range = 10 + (int)(from.Skills.Tracking.Value / 10); - if (targets.Length > 8) - { - AddBackground(0, 310, 440, 155, 5054); + var mobs = GetClosestMobs(from, range, type); - AddBackground(10, 320, 420, 75, 2620); - AddBackground(10, 395, 420, 45, 3000); - } - } + if (mobs.Length > 0) + { + from.SendGump(new TrackWhoGump(mobs, range)); + from.SendLocalizedMessage(1018093); // Select the one you would like to track. + } + else if (type == 0) + { + from.SendLocalizedMessage(502991); // You see no evidence of animals in the area. + } + else if (type == 1) + { + from.SendLocalizedMessage(502993); // You see no evidence of creatures in the area. + } + else + { + from.SendLocalizedMessage(502995); // You see no evidence of people in the area. + } + } - for (var i = 0; i < targets.Length; ++i) - { - var m = targets[i]; + protected override void BuildLayout(ref DynamicGumpBuilder builder) + { + builder.AddPage(); - AddItem(20 + i % 4 * 100, 20 + i / 4 * 155, ShrinkTable.Lookup(m)); - AddButton(20 + i % 4 * 100, 130 + i / 4 * 155, 4005, 4007, i + 1); + builder.AddBackground(0, 0, 440, 155, 5054); - if (m.Name != null) - { - AddHtml(20 + i % 4 * 100, 90 + i / 4 * 155, 90, 40, m.Name); - } - } - } + builder.AddBackground(10, 10, 420, 75, 2620); + builder.AddBackground(10, 85, 420, 45, 3000); - public static void DisplayTo(bool success, PlayerMobile from, int type) + if (_targets.Length > 4) { - if (!success) - { - from.SendLocalizedMessage(1018092); // You see no evidence of those in the area. - return; - } + builder.AddBackground(0, 155, 440, 155, 5054); - var map = from.Map; + builder.AddBackground(10, 165, 420, 75, 2620); + builder.AddBackground(10, 240, 420, 45, 3000); - if (map == null) + if (_targets.Length > 8) { - return; - } + builder.AddBackground(0, 310, 440, 155, 5054); - from.CheckSkill(SkillName.Tracking, 21.1, 100.0); // Passive gain + builder.AddBackground(10, 320, 420, 75, 2620); + builder.AddBackground(10, 395, 420, 45, 3000); + } + } - var range = 10 + (int)(from.Skills.Tracking.Value / 10); + for (var i = 0; i < _targets.Length; ++i) + { + var m = _targets[i]; - var mobs = GetClosestMobs(from, range, type); + builder.AddItem(20 + i % 4 * 100, 20 + i / 4 * 155, ShrinkTable.Lookup(m)); + builder.AddButton(20 + i % 4 * 100, 130 + i / 4 * 155, 4005, 4007, i + 1); - if (mobs.Length > 0) - { - from.SendGump(new TrackWhoGump(from, mobs, range)); - from.SendLocalizedMessage(1018093); // Select the one you would like to track. - } - else if (type == 0) - { - from.SendLocalizedMessage(502991); // You see no evidence of animals in the area. - } - else if (type == 1) - { - from.SendLocalizedMessage(502993); // You see no evidence of creatures in the area. - } - else + if (m.Name != null) { - from.SendLocalizedMessage(502995); // You see no evidence of people in the area. + builder.AddHtml(20 + i % 4 * 100, 90 + i / 4 * 155, 90, 40, m.Name); } } + } - private static Mobile[] GetClosestMobs(Mobile from, int range, int type) - { - var loc = from.Location; + private static Mobile[] GetClosestMobs(Mobile from, int range, int type) + { + var loc = from.Location; - // We only track the closest 12 - var mobs = new Mobile[MaxClosest]; - Span distances = stackalloc double[MaxClosest]; - distances.Fill(double.MaxValue); // Fill with max values - var total = 0; + // We only track the closest 12 + var mobs = new Mobile[MaxClosest]; + Span distances = stackalloc double[MaxClosest]; + distances.Fill(double.MaxValue); // Fill with max values + var total = 0; - foreach (var m in from.GetMobilesInRange(range)) + foreach (var m in from.GetMobilesInRange(range)) + { + if (m == from || Core.AOS && !m.Alive || + m.Hidden && m.AccessLevel != AccessLevel.Player && from.AccessLevel <= m.AccessLevel || + !IsValidMobileType(m, type) || !CheckDifficulty(from, m)) { - if (m == from || Core.AOS && !m.Alive || - m.Hidden && m.AccessLevel != AccessLevel.Player && from.AccessLevel <= m.AccessLevel || - !IsValidMobileType(m, type) || !CheckDifficulty(from, m)) - { - continue; - } + continue; + } - total++; + total++; - var distance = m.GetDistanceToSqrt(loc); - for (var i = 0; i < MaxClosest; i++) + var distance = m.GetDistanceToSqrt(loc); + for (var i = 0; i < MaxClosest; i++) + { + if (distance < distances[i]) { - if (distance < distances[i]) + // Shift down the rest + for (int j = MaxClosest - 1; j > i; j--) { - // Shift down the rest - for (int j = MaxClosest - 1; j > i; j--) - { - mobs[j] = mobs[j - 1]; - distances[j] = distances[j - 1]; - } - - mobs[i] = m; - distances[i] = distance; - break; + mobs[j] = mobs[j - 1]; + distances[j] = distances[j - 1]; } - } - } - if (total < MaxClosest) - { - Array.Resize(ref mobs, total); + mobs[i] = m; + distances[i] = distance; + break; + } } + } - return mobs; + if (total < MaxClosest) + { + Array.Resize(ref mobs, total); } - // Tracking players uses tracking and detect hidden vs. hiding and stealth - private static bool CheckDifficulty(Mobile from, Mobile m) + return mobs; + } + + // Tracking players uses tracking and detect hidden vs. hiding and stealth + private static bool CheckDifficulty(Mobile from, Mobile m) + { + if (!Core.AOS || !m.Player) { - if (!Core.AOS || !m.Player) - { - return true; - } + return true; + } - var tracking = from.Skills.Tracking.Fixed; - var detectHidden = from.Skills.DetectHidden.Fixed; + var tracking = from.Skills.Tracking.Fixed; + var detectHidden = from.Skills.DetectHidden.Fixed; - if (Core.ML && m.Race == Race.Elf) - { - tracking /= 2; // The 'Guide' says that it requires twice as Much tracking SKILL to track an elf. Not the total difficulty to track. - } + if (Core.ML && m.Race == Race.Elf) + { + tracking /= 2; // The 'Guide' says that it requires twice as Much tracking SKILL to track an elf. Not the total difficulty to track. + } - var hiding = m.Skills.Hiding.Fixed; - var stealth = m.Skills.Stealth.Fixed; - var divisor = hiding + stealth; + var hiding = m.Skills.Hiding.Fixed; + var stealth = m.Skills.Stealth.Fixed; + var divisor = hiding + stealth; - // Necromancy forms affect tracking difficulty - if (TransformationSpellHelper.UnderTransformation(m, typeof(HorrificBeastSpell))) - { - divisor -= 200; - } - else if (TransformationSpellHelper.UnderTransformation(m, typeof(VampiricEmbraceSpell)) && divisor < 500) - { - divisor = 500; - } - else if (TransformationSpellHelper.UnderTransformation(m, typeof(WraithFormSpell)) && divisor <= 2000) - { - divisor += 200; - } + // Necromancy forms affect tracking difficulty + if (TransformationSpellHelper.UnderTransformation(m, typeof(HorrificBeastSpell))) + { + divisor -= 200; + } + else if (TransformationSpellHelper.UnderTransformation(m, typeof(VampiricEmbraceSpell)) && divisor < 500) + { + divisor = 500; + } + else if (TransformationSpellHelper.UnderTransformation(m, typeof(WraithFormSpell)) && divisor <= 2000) + { + divisor += 200; + } - int chance; - if (divisor > 0) + int chance; + if (divisor > 0) + { + if (Core.SE) { - if (Core.SE) - { - chance = 50 * (tracking * 2 + detectHidden) / divisor; - } - else - { - chance = 50 * (tracking + detectHidden + 10 * Utility.RandomMinMax(1, 20)) / divisor; - } + chance = 50 * (tracking * 2 + detectHidden) / divisor; } else { - chance = 100; + chance = 50 * (tracking + detectHidden + 10 * Utility.RandomMinMax(1, 20)) / divisor; } - - return chance >= 100 || chance > Utility.Random(100); + } + else + { + chance = 100; } - private static bool IsValidMobileType(Mobile m, int type) => - type switch - { - 0 => !m.Player && m.Body.IsAnimal, - 1 => !m.Player && m.Body.IsMonster, - 2 => !m.Player && m.Body.IsHuman, - _ => m.Player - }; + return chance >= 100 || chance > Utility.Random(100); + } - public override void OnResponse(NetState state, in RelayInfo info) + private static bool IsValidMobileType(Mobile m, int type) => + type switch { - var index = info.ButtonID - 1; + 0 => !m.Player && m.Body.IsAnimal, + 1 => !m.Player && m.Body.IsMonster, + 2 => !m.Player && m.Body.IsHuman, + _ => m.Player + }; - if (index >= 0 && index < _targets.Length && index < 12) - { - var m = _targets[index]; + public override void OnResponse(NetState state, in RelayInfo info) + { + var index = info.ButtonID - 1; - _from.QuestArrow = new TrackArrow(_from, m, _range * 2); + if (index >= 0 && index < _targets.Length && index < 12 && state.Mobile is PlayerMobile pm) + { + var m = _targets[index]; - if (Core.SE) - { - Tracking.AddInfo(_from, m); - } + pm.QuestArrow = new TrackArrow(pm, m, _range * 2); + + if (Core.SE) + { + Tracking.AddInfo(pm, m); } } } diff --git a/Projects/UOContent/Spells/Necromancy/SummonFamiliar.cs b/Projects/UOContent/Spells/Necromancy/SummonFamiliar.cs index 900d7e8cb6..b9050af35e 100644 --- a/Projects/UOContent/Spells/Necromancy/SummonFamiliar.cs +++ b/Projects/UOContent/Spells/Necromancy/SummonFamiliar.cs @@ -80,7 +80,7 @@ public override void OnCast() public class SummonFamiliarEntry { - public SummonFamiliarEntry(Type type, object name, double reqNecromancy, double reqSpiritSpeak) + public SummonFamiliarEntry(Type type, TextDefinition name, double reqNecromancy, double reqSpiritSpeak) { Type = type; Name = name; @@ -90,14 +90,14 @@ public SummonFamiliarEntry(Type type, object name, double reqNecromancy, double public Type Type { get; } - public object Name { get; } + public TextDefinition Name { get; } public double ReqNecromancy { get; } public double ReqSpiritSpeak { get; } } -public class SummonFamiliarGump : Gump +public class SummonFamiliarGump : DynamicGump { private const int EnabledColor16 = 0x0F20; private const int DisabledColor16 = 0x262A; @@ -105,8 +105,8 @@ public class SummonFamiliarGump : Gump private const int EnabledColor32 = 0x18CD00; private const int DisabledColor32 = 0x4A8B52; - private readonly SummonFamiliarEntry[] _entries; private readonly Mobile _from; + private readonly SummonFamiliarEntry[] _entries; private readonly SummonFamiliarSpell _spell; @@ -117,49 +117,47 @@ public SummonFamiliarGump(Mobile from, SummonFamiliarEntry[] entries, SummonFami _from = from; _entries = entries; _spell = spell; + } - AddPage(0); + protected override void BuildLayout(ref DynamicGumpBuilder builder) + { + builder.AddPage(); - AddBackground(10, 10, 250, 178, 9270); - AddAlphaRegion(20, 20, 230, 158); + builder.AddBackground(10, 10, 250, 178, 9270); + builder.AddAlphaRegion(20, 20, 230, 158); - AddImage(220, 20, 10464); - AddImage(220, 72, 10464); - AddImage(220, 124, 10464); + builder.AddImage(220, 20, 10464); + builder.AddImage(220, 72, 10464); + builder.AddImage(220, 124, 10464); - AddItem(188, 16, 6883); - AddItem(198, 168, 6881); - AddItem(8, 15, 6882); - AddItem(2, 168, 6880); + builder.AddItem(188, 16, 6883); + builder.AddItem(198, 168, 6881); + builder.AddItem(8, 15, 6882); + builder.AddItem(2, 168, 6880); - AddHtmlLocalized(30, 26, 200, 20, 1060147, EnabledColor16); // Chose thy familiar... + builder.AddHtmlLocalized(30, 26, 200, 20, 1060147, EnabledColor16); // Chose thy familiar... - var necro = from.Skills.Necromancy.Value; - var spirit = from.Skills.SpiritSpeak.Value; + var necro = _from.Skills.Necromancy.Value; + var spirit = _from.Skills.SpiritSpeak.Value; - for (var i = 0; i < entries.Length; ++i) + for (var i = 0; i < _entries.Length; ++i) { - var entry = entries[i]; + var entry = _entries[i]; var name = entry.Name; var enabled = necro >= entry.ReqNecromancy && spirit >= entry.ReqSpiritSpeak; - AddButton(27, 53 + i * 21, 9702, 9703, i + 1); - - if (name is int intName) - { - AddHtmlLocalized(50, 51 + i * 21, 150, 20, intName, enabled ? EnabledColor16 : DisabledColor16); - } - else if (name is string strName) - { - AddHtml( - 50, - 51 + i * 21, - 150, - 20, - strName.Color(enabled ? EnabledColor32 : DisabledColor32) - ); - } + builder.AddButton(27, 53 + i * 21, 9702, 9703, i + 1); + + name.AddHtmlText( + ref builder, + 50, + 51 + i * 21, + 150, + 20, + numberColor: enabled ? EnabledColor16 : DisabledColor16, + stringColor: enabled ? EnabledColor32 : DisabledColor32 + ); } } @@ -190,13 +188,12 @@ public override void OnResponse(NetState sender, in RelayInfo info) // That familiar requires ~1_NECROMANCY~ Necromancy and ~2_SPIRIT~ Spirit Speak. _from.SendLocalizedMessage(1061606, $"{entry.ReqNecromancy:F1}\t{entry.ReqSpiritSpeak:F1}"); - _from.SendGump(new SummonFamiliarGump(_from, SummonFamiliarSpell.Entries, _spell)); + _from.SendGump(this); } else if (entry.Type == null) { _from.SendMessage("That familiar has not yet been defined."); - - _from.SendGump(new SummonFamiliarGump(_from, SummonFamiliarSpell.Entries, _spell)); + _from.SendGump(this); } else {