From 23532db6030b67d9edf6ca532b843c22ff39ec95 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 20 Mar 2022 19:20:54 -0700 Subject: [PATCH] fix: Cleans up LINQ calls. (#965) - [X] Removes several `ToList()` uses with `PooledRefQueue` - [X] Adds a `PeekRandom` to PooledRefQueue - [X] Updates EV/BS so they dispel each other in a more efficient manner. - [X] Fixes Firebomb so it works like a normal firefield. - [X] Fixes field spells so they aren't unnecessarily using a Point3D ref more than necessary. - [X] Removes extra allocation in campfire by using reverse loop. - [X] Removes other LINQ calls that aren't needed. --- Projects/Benchmarks/Benchmarks.csproj | 2 +- .../Benchmarks/Map/MapEntitiesSelectors.cs | 6 +- .../Benchmarks/Map/MapItemSelectors.cs | 4 +- .../Benchmarks/Map/MapMobileSelectors.cs | 7 +- .../Benchmarks/Map/MapMultiSelectors.cs | 7 +- .../Benchmarks/Map/MapMultiTilesSelectors.cs | 8 +- Projects/Server.Tests/Server.Tests.csproj | 4 +- .../Tests/Collections/PooledRefQueueTests.cs | 102 +++++++++++++ Projects/Server/Collections/PooledRefQueue.cs | 16 ++ Projects/Server/Random/RandomSources.cs | 12 +- Projects/Server/Utilities/Utility.cs | 11 ++ .../UOContent.Tests/UOContent.Tests.csproj | 4 +- .../Study of the Solen Hive/NestArea.cs | 29 +++- .../Treasures of Tokuno/TreasuresOfTokuno.cs | 86 +++++------ .../Gumps/BaseImageTileButtonsGump.cs | 4 +- .../Halloween/2009/Engines/PumpkinPatch.cs | 24 +-- .../Halloween/2012/Engines/PlayerZombies.cs | 11 +- Projects/UOContent/Items/Misc/Firebomb.cs | 139 ++++-------------- .../Items/Skill Items/Camping/Campfire.cs | 38 +++-- .../Items/Skill Items/Misc/FireHorn.cs | 38 +++-- .../Weapons/Abilities/WhirlwindAttack.cs | 36 +++-- .../Mobiles/Familiars/HordeMinion.cs | 28 ++-- .../UOContent/Mobiles/Familiars/ShadowWisp.cs | 24 +-- .../Monsters/Humanoid/Melee/OrcBrute.cs | 11 +- .../Mobiles/Monsters/LBR/Meers/MeerEternal.cs | 86 ++++++----- .../Monsters/Misc/Melee/BladeSpirits.cs | 30 ++-- .../Monsters/Misc/Melee/EnergyVortex.cs | 30 ++-- .../UOContent/Mobiles/Special/Harrower.cs | 28 ++-- Projects/UOContent/Multis/Houses/BaseHouse.cs | 22 ++- Projects/UOContent/Skills/SpiritSpeak.cs | 11 +- .../UOContent/Skills/Tracking/Tracking.cs | 20 ++- Projects/UOContent/Spells/Base/SpellHelper.cs | 4 +- .../Spells/Bushido/MomentumStrike.cs | 20 ++- .../UOContent/Spells/Chivalry/DispelEvil.cs | 20 ++- .../UOContent/Spells/Eighth/Earthquake.cs | 23 ++- .../UOContent/Spells/Fifth/PoisonField.cs | 11 +- Projects/UOContent/Spells/Fourth/FireField.cs | 23 ++- .../UOContent/Spells/Seventh/EnergyField.cs | 27 ++-- .../UOContent/Spells/Sixth/ParalyzeField.cs | 14 +- .../UOContent/Spells/Third/WallOfStone.cs | 12 +- 40 files changed, 585 insertions(+), 447 deletions(-) create mode 100644 Projects/Server.Tests/Tests/Collections/PooledRefQueueTests.cs diff --git a/Projects/Benchmarks/Benchmarks.csproj b/Projects/Benchmarks/Benchmarks.csproj index ca226bba4..2720cb6da 100644 --- a/Projects/Benchmarks/Benchmarks.csproj +++ b/Projects/Benchmarks/Benchmarks.csproj @@ -11,7 +11,7 @@ - + diff --git a/Projects/Benchmarks/Benchmarks/Map/MapEntitiesSelectors.cs b/Projects/Benchmarks/Benchmarks/Map/MapEntitiesSelectors.cs index 7348824c8..7c70d3056 100644 --- a/Projects/Benchmarks/Benchmarks/Map/MapEntitiesSelectors.cs +++ b/Projects/Benchmarks/Benchmarks/Map/MapEntitiesSelectors.cs @@ -14,7 +14,7 @@ namespace Benchmarks.EntitiesSelectors public class MapEntitiesSelectors { private static readonly Sector sector = new(); - private static readonly Point3D[] locations = new[] { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; + private static readonly Point3D[] locations = { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; public static Rectangle2D[] BoundsArray() => new[] { @@ -501,8 +501,8 @@ namespace Benchmarks.EntitiesSelectors public class Sector { - public List BItems { get; set; } = new List(); - public List Mobiles { get; set; } = new List(); + public List BItems { get; set; } = new(); + public List Mobiles { get; set; } = new(); } public struct BItemWhereHyper : NetFabric.Hyperlinq.IFunction diff --git a/Projects/Benchmarks/Benchmarks/Map/MapItemSelectors.cs b/Projects/Benchmarks/Benchmarks/Map/MapItemSelectors.cs index 8eb1758d2..43f5179c0 100644 --- a/Projects/Benchmarks/Benchmarks/Map/MapItemSelectors.cs +++ b/Projects/Benchmarks/Benchmarks/Map/MapItemSelectors.cs @@ -20,7 +20,7 @@ namespace Benchmarks.ItemSelectors public class MapItemSelectors { private static readonly Sector sector = new(); - private static readonly Point3D[] locations = new[] { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; + private static readonly Point3D[] locations = { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; public static Rectangle2D[] BoundsArray() => new[] { @@ -352,7 +352,7 @@ namespace Benchmarks.ItemSelectors public class Sector { - public List BItems { get; set; } = new List(); + public List BItems { get; set; } = new(); } public struct BItemWhere : StructLinq.IFunction where T : BItem diff --git a/Projects/Benchmarks/Benchmarks/Map/MapMobileSelectors.cs b/Projects/Benchmarks/Benchmarks/Map/MapMobileSelectors.cs index c9932070f..a259981f0 100644 --- a/Projects/Benchmarks/Benchmarks/Map/MapMobileSelectors.cs +++ b/Projects/Benchmarks/Benchmarks/Map/MapMobileSelectors.cs @@ -2,7 +2,6 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using NetFabric.Hyperlinq; using Server; -using StructLinq; using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +14,7 @@ namespace Benchmarks.MobileSelectors public class MapMobileSelectors { private static readonly Sector sector = new(); - private static readonly Point3D[] locations = new[] { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; + private static readonly Point3D[] locations = { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; public static Rectangle2D[] BoundsArray() => new[] { @@ -224,10 +223,10 @@ namespace Benchmarks.MobileSelectors { public MobileDerived(Point3D location) : base(location) { } } - + public class Sector { - public List Mobiles { get; set; } = new List(); + public List Mobiles { get; set; } = new(); } public struct MobileWhereHyper : NetFabric.Hyperlinq.IFunction where T : Mobile diff --git a/Projects/Benchmarks/Benchmarks/Map/MapMultiSelectors.cs b/Projects/Benchmarks/Benchmarks/Map/MapMultiSelectors.cs index e5c6605f5..233d6b5e4 100644 --- a/Projects/Benchmarks/Benchmarks/Map/MapMultiSelectors.cs +++ b/Projects/Benchmarks/Benchmarks/Map/MapMultiSelectors.cs @@ -2,7 +2,6 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using NetFabric.Hyperlinq; using Server; -using StructLinq; using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +14,7 @@ namespace Benchmarks.MultiSelectors public class MapMultiSelectors { private static readonly Sector sector = new(); - private static readonly Point3D[] locations = new[] { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; + private static readonly Point3D[] locations = { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; public static Rectangle2D[] BoundsArray() => new[] { @@ -291,10 +290,10 @@ namespace Benchmarks.MultiSelectors public class Sector { - public List Multis { get; set; } = new List(); + public List Multis { get; set; } = new(); } - public struct MultiWhereHyper : NetFabric.Hyperlinq.IFunction + public struct MultiWhereHyper : IFunction { private readonly Rectangle2D bounds; diff --git a/Projects/Benchmarks/Benchmarks/Map/MapMultiTilesSelectors.cs b/Projects/Benchmarks/Benchmarks/Map/MapMultiTilesSelectors.cs index 553016f93..8f77aee25 100644 --- a/Projects/Benchmarks/Benchmarks/Map/MapMultiTilesSelectors.cs +++ b/Projects/Benchmarks/Benchmarks/Map/MapMultiTilesSelectors.cs @@ -1,8 +1,6 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -using NetFabric.Hyperlinq; using Server; -using StructLinq; using System; using System.Collections.Generic; using System.Linq; @@ -14,7 +12,7 @@ namespace Benchmarks.MultiTilesSelectors public class MapMultiTilesSelectors { private static readonly Sector sector = new(); - private static readonly Point3D[] locations = new[] { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; + private static readonly Point3D[] locations = { new Point3D(0, 0, 0), new Point3D(50, 50, 0) }; public static Rectangle2D[] BoundsArray() => new[] { @@ -55,7 +53,7 @@ namespace Benchmarks.MultiTilesSelectors return toRet; } - + [Benchmark(Baseline = true)] public int SelectMultiTilesLinq() { @@ -347,6 +345,6 @@ namespace Benchmarks.MultiTilesSelectors public class Sector { - public List Multis { get; set; } = new List(); + public List Multis { get; set; } = new(); } } diff --git a/Projects/Server.Tests/Server.Tests.csproj b/Projects/Server.Tests/Server.Tests.csproj index 8c727c281..b310c4f69 100644 --- a/Projects/Server.Tests/Server.Tests.csproj +++ b/Projects/Server.Tests/Server.Tests.csproj @@ -3,8 +3,8 @@ false - - + + diff --git a/Projects/Server.Tests/Tests/Collections/PooledRefQueueTests.cs b/Projects/Server.Tests/Tests/Collections/PooledRefQueueTests.cs new file mode 100644 index 000000000..a9d3576b0 --- /dev/null +++ b/Projects/Server.Tests/Tests/Collections/PooledRefQueueTests.cs @@ -0,0 +1,102 @@ +using System; +using Moq; +using Server.Collections; +using Server.Random; +using Xunit; + +namespace Server.Tests; + +public sealed class PooledRefQueueTests : IDisposable +{ + public void Dispose() => RandomSources.SetRng(null); + + private static void PrepareRng(int queueCount, int rngValue) + { + Mock mockRng = new Mock(); + mockRng + .Setup(rng => rng.Next(It.IsAny())) + .Returns( + (int size) => + { + Assert.Equal(queueCount, size); + return rngValue; + } + ); + + RandomSources.SetRng(mockRng.Object); + } + + [Fact] + public void TestPeekRandom1() + { + // Random value for _head = 0, _tail = 5, _size = 5, + using var queue = PooledRefQueue.Create(10); + queue.Enqueue(0); + queue.Enqueue(1); + queue.Enqueue(2); + queue.Enqueue(3); // <----- + queue.Enqueue(4); + queue.Enqueue(5); + + PrepareRng(6, 3); + Assert.Equal(3, queue.PeekRandom()); + } + + [Fact] + public void TestPeekRandom2() + { + // Random value for _head = 3, _tail = 10, _size = 7, + using var queue = PooledRefQueue.Create(10); + queue.Enqueue(0); + queue.Enqueue(1); + queue.Enqueue(2); + + queue.Enqueue(3); + queue.Enqueue(4); + queue.Enqueue(5); + queue.Enqueue(6); // <--- + queue.Enqueue(7); + queue.Enqueue(8); + queue.Enqueue(9); + + queue.Dequeue(); + queue.Dequeue(); + queue.Dequeue(); + + PrepareRng(7, 3); + Assert.Equal(6, queue.PeekRandom()); + } + + [Theory] + [InlineData(3, 6)] + [InlineData(8, 11)] + [InlineData(6, 9)] + [InlineData(7, 10)] + public void TestPeekRandom3(int rngValue, int expectedIndex) + { + // Random value for _head = 3, _tail = 2, _size = 10, + using var queue = PooledRefQueue.Create(10); + queue.Enqueue(0); + queue.Enqueue(1); + queue.Enqueue(2); + + queue.Enqueue(3); + queue.Enqueue(4); + queue.Enqueue(5); + queue.Enqueue(6); + queue.Enqueue(7); + queue.Enqueue(8); + queue.Enqueue(9); + + queue.Dequeue(); + queue.Dequeue(); + queue.Dequeue(); + + queue.Enqueue(10); + queue.Enqueue(11); + queue.Enqueue(12); + + PrepareRng(10, rngValue); + Assert.Equal(expectedIndex, queue.PeekRandom()); + } +} diff --git a/Projects/Server/Collections/PooledRefQueue.cs b/Projects/Server/Collections/PooledRefQueue.cs index 577e7f6f4..9c8ea81b0 100644 --- a/Projects/Server/Collections/PooledRefQueue.cs +++ b/Projects/Server/Collections/PooledRefQueue.cs @@ -180,6 +180,22 @@ namespace Server.Collections return _array[_head]; } + public T PeekRandom() + { + if (_size == 0) + { + ThrowForEmptyQueue(); + } + + var index = _head + Utility.Random(_size); + if (index >= _array.Length) + { + index -= _array.Length; + } + + return _array[index]; + } + public bool TryPeek([MaybeNullWhen(false)] out T result) { if (_size == 0) diff --git a/Projects/Server/Random/RandomSources.cs b/Projects/Server/Random/RandomSources.cs index d99add499..49f8a6188 100644 --- a/Projects/Server/Random/RandomSources.cs +++ b/Projects/Server/Random/RandomSources.cs @@ -17,10 +17,14 @@ namespace Server.Random { public static class RandomSources { - private static IRandomSource m_Source; - private static IRandomSource m_SecureSource; + private static IRandomSource _source; + private static IRandomSource _secureSource; - public static IRandomSource Source => m_Source ??= new Xoshiro256PlusPlus(); - public static IRandomSource SecureSource => m_SecureSource ??= new SecureRandom(); + public static IRandomSource Source => _source ??= new Xoshiro256PlusPlus(); + public static IRandomSource SecureSource => _secureSource ??= new SecureRandom(); + + public static void SetRng(IRandomSource newSource) => _source = newSource; + + public static void SetSecureRng(IRandomSource newSource) => _secureSource = newSource; } } diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 7d16d62b0..30f25b577 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -920,6 +920,7 @@ namespace Server return total + bonus; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Shuffle(this IList list) { var count = list.Count; @@ -930,6 +931,7 @@ namespace Server } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Shuffle(this Span list) { var count = list.Length; @@ -1094,6 +1096,15 @@ namespace Server [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double RandomDouble() => RandomSources.Source.NextDouble(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point3D RandomPointIn(Rectangle2D rect, Map map) + { + var x = Random(rect.X, rect.Width); + var y = Random(rect.Y, rect.Height); + + return new Point3D(x, y, map.GetAverageZ(x, y)); + } + /// /// Random pink, blue, green, orange, red or yellow hue /// diff --git a/Projects/UOContent.Tests/UOContent.Tests.csproj b/Projects/UOContent.Tests/UOContent.Tests.csproj index fc960bae1..38f0fc1bd 100644 --- a/Projects/UOContent.Tests/UOContent.Tests.csproj +++ b/Projects/UOContent.Tests/UOContent.Tests.csproj @@ -3,8 +3,8 @@ false - - + + diff --git a/Projects/UOContent/Engines/Quests/Study of the Solen Hive/NestArea.cs b/Projects/UOContent/Engines/Quests/Study of the Solen Hive/NestArea.cs index bc93f6289..2a14b34db 100644 --- a/Projects/UOContent/Engines/Quests/Study of the Solen Hive/NestArea.cs +++ b/Projects/UOContent/Engines/Quests/Study of the Solen Hive/NestArea.cs @@ -1,5 +1,3 @@ -using System.Linq; - namespace Server.Engines.Quests.Naturalist { public class NestArea @@ -38,7 +36,22 @@ namespace Server.Engines.Quests.Naturalist m_Rects = rects; } - public static int NonSpecialCount => m_Areas.Count(area => !area.Special); + public static int NonSpecialCount + { + get + { + int count = 0; + foreach (var area in m_Areas) + { + if (!area.Special) + { + count++; + } + } + + return count; + } + } public bool Special { get; } @@ -60,7 +73,15 @@ namespace Server.Engines.Quests.Naturalist public static NestArea Find(Point3D p) { - return m_Areas.FirstOrDefault(area => area.Contains(p)); + foreach (var area in m_Areas) + { + if (area.Contains(p)) + { + return area; + } + } + + return null; } public static NestArea GetByID(int id) diff --git a/Projects/UOContent/Engines/Treasures of Tokuno/TreasuresOfTokuno.cs b/Projects/UOContent/Engines/Treasures of Tokuno/TreasuresOfTokuno.cs index a4d2ede01..4e1ff18fb 100644 --- a/Projects/UOContent/Engines/Treasures of Tokuno/TreasuresOfTokuno.cs +++ b/Projects/UOContent/Engines/Treasures of Tokuno/TreasuresOfTokuno.cs @@ -151,7 +151,7 @@ namespace Server.Misc pm.ToTTotalMonsterFame += (int)(bc.Fame * (1 + Math.Sqrt(pm.Luck) / 100)); - // This is the Exponentional regression with only 2 datapoints. + // This is the Exponential regression with only 2 data points. // A log. func would also work, but it didn't make as much sense. // This function isn't OSI exact being that I don't know OSI's func they used ;p var x = pm.ToTTotalMonsterFame; @@ -269,10 +269,8 @@ namespace Server.Mobiles { if (pm.ToTItemsTurnedIn >= TreasuresOfTokuno.ItemsPerReward) { - SayTo( - pm, - 1070980 - ); // Congratulations! You have turned in enough minor treasures to earn a greater reward. + // Congratulations! You have turned in enough minor treasures to earn a greater reward. + SayTo(pm, 1070980); pm.CloseGump(); // Sanity @@ -285,18 +283,16 @@ namespace Server.Mobiles { if (pm.ToTItemsTurnedIn == 0) { - SayTo( - pm, - 1071013 - ); // Bring me 10 of the lost treasures of Tokuno and I will reward you with a valuable item. + // Bring me 10 of the lost treasures of Tokuno and I will reward you with a valuable item. + SayTo(pm, 1071013); } else { SayTo( pm, - 1070981, + 1070981, // You have turned in ~1_COUNT~ minor artifacts. Turn in ~2_NUM~ to receive a reward. $"{pm.ToTItemsTurnedIn}\t{TreasuresOfTokuno.ItemsPerReward}" - ); // You have turned in ~1_COUNT~ minor artifacts. Turn in ~2_NUM~ to receive a reward. + ); } var buttons = ToTTurnInGump.FindRedeemableItems(pm); @@ -342,34 +338,26 @@ namespace Server.Gumps { private readonly Mobile m_Collector; - public ToTTurnInGump(Mobile collector, List buttons) : base( - 1071012, - buttons.ToList() - ) // Click a minor artifact to give it to Ihara Soko. - => - m_Collector = collector; + // Click a minor artifact to give it to Ihara Soko. + public ToTTurnInGump(Mobile collector, List buttons) + : base(1071012, buttons) => m_Collector = collector; - public static List FindRedeemableItems(Mobile m) + public static List FindRedeemableItems(Mobile m) { var pack = m.Backpack; if (pack == null) { - return new List(); + return new List(); } - var buttons = new List(); + var buttons = new List(); var items = pack.FindItemsByType(TreasuresOfTokuno.LesserArtifactsTotal); for (var i = 0; i < items.Length; i++) { var item = items[i]; - if (item is ChestOfHeirlooms heirlooms && !heirlooms.Locked) - { - continue; - } - - if (item is ChestOfHeirlooms ofHeirlooms && ofHeirlooms.TrapLevel != 10) + if (item is ChestOfHeirlooms heirlooms && (!heirlooms.Locked || heirlooms.TrapLevel != 10)) { continue; } @@ -400,10 +388,8 @@ namespace Server.Gumps if (++pm.ToTItemsTurnedIn >= TreasuresOfTokuno.ItemsPerReward) { - m_Collector.SayTo( - pm, - 1070980 - ); // Congratulations! You have turned in enough minor treasures to earn a greater reward. + // Congratulations! You have turned in enough minor treasures to earn a greater reward. + m_Collector.SayTo(pm, 1070980); pm.CloseGump(); // Sanity @@ -416,9 +402,9 @@ namespace Server.Gumps { m_Collector.SayTo( pm, - 1070981, + 1070981, // You have turned in ~1_COUNT~ minor artifacts. Turn in ~2_NUM~ to receive a reward. $"{pm.ToTItemsTurnedIn}\t{TreasuresOfTokuno.ItemsPerReward}" - ); // You have turned in ~1_COUNT~ minor artifacts. Turn in ~2_NUM~ to receive a reward. + ); var buttons = FindRedeemableItems(pm); @@ -440,19 +426,17 @@ namespace Server.Gumps if (pm.ToTItemsTurnedIn == 0) { - m_Collector.SayTo( - pm, - 1071013 - ); // Bring me 10 of the lost treasures of Tokuno and I will reward you with a valuable item. + // Bring me 10 of the lost treasures of Tokuno and I will reward you with a valuable item. + m_Collector.SayTo(pm, 1071013); } - else if (pm.ToTItemsTurnedIn < TreasuresOfTokuno.ItemsPerReward - ) // This case should ALWAYS be true with this gump, jsut a sanity check + // This case should ALWAYS be true with this gump, just a sanity check + else if (pm.ToTItemsTurnedIn < TreasuresOfTokuno.ItemsPerReward) { m_Collector.SayTo( pm, - 1070981, + 1070981, // You have turned in ~1_COUNT~ minor artifacts. Turn in ~2_NUM~ to receive a reward. $"{pm.ToTItemsTurnedIn}\t{TreasuresOfTokuno.ItemsPerReward}" - ); // You have turned in ~1_COUNT~ minor artifacts. Turn in ~2_NUM~ to receive a reward. + ); } else { @@ -611,11 +595,9 @@ namespace Server.Gumps pm.ToTItemsTurnedIn -= TreasuresOfTokuno.ItemsPerReward; m_Collector.SayTo( pm, - 1070984, - item.Name == null || item.Name.Length <= 0 - ? $"#{item.LabelNumber}" - : item.Name - ); // You have earned the gratitude of the Empire. I have placed the ~1_OBJTYPE~ in your backpack. + 1070984, // You have earned the gratitude of the Empire. I have placed the ~1_OBJTYPE~ in your backpack. + item.Name?.Length > 0 ? item.Name : $"#{item.LabelNumber}" + ); } else { @@ -634,19 +616,17 @@ namespace Server.Gumps if (pm.ToTItemsTurnedIn == 0) { - m_Collector.SayTo( - pm, - 1071013 - ); // Bring me 10 of the lost treasures of Tokuno and I will reward you with a valuable item. + // Bring me 10 of the lost treasures of Tokuno and I will reward you with a valuable item. + m_Collector.SayTo(pm, 1071013); } - else if (pm.ToTItemsTurnedIn < TreasuresOfTokuno.ItemsPerReward - ) // This and above case should ALWAYS be FALSE with this gump, jsut a sanity check + // This and above case should ALWAYS be FALSE with this gump, jsut a sanity check + else if (pm.ToTItemsTurnedIn < TreasuresOfTokuno.ItemsPerReward) { m_Collector.SayTo( pm, - 1070981, + 1070981, // You have turned in ~1_COUNT~ minor artifacts. Turn in ~2_NUM~ to receive a reward. $"{pm.ToTItemsTurnedIn}\t{TreasuresOfTokuno.ItemsPerReward}" - ); // You have turned in ~1_COUNT~ minor artifacts. Turn in ~2_NUM~ to receive a reward. + ); } else { diff --git a/Projects/UOContent/Gumps/BaseImageTileButtonsGump.cs b/Projects/UOContent/Gumps/BaseImageTileButtonsGump.cs index ad2dbcb5c..18602b84a 100644 --- a/Projects/UOContent/Gumps/BaseImageTileButtonsGump.cs +++ b/Projects/UOContent/Gumps/BaseImageTileButtonsGump.cs @@ -53,8 +53,8 @@ namespace Server.Gumps { } - public BaseImageTileButtonsGump(TextDefinition header, ImageTileButtonInfo[] buttons) : - base(10, 10) // Coords are 0, o on OSI, intentional difference + // Coords are 0, 0 on OSI, intentional difference + public BaseImageTileButtonsGump(TextDefinition header, ImageTileButtonInfo[] buttons) : base(10, 10) { Buttons = buttons; AddPage(0); diff --git a/Projects/UOContent/Holiday Stuff/Halloween/2009/Engines/PumpkinPatch.cs b/Projects/UOContent/Holiday Stuff/Halloween/2009/Engines/PumpkinPatch.cs index cf00181eb..93c797c3a 100644 --- a/Projects/UOContent/Holiday Stuff/Halloween/2009/Engines/PumpkinPatch.cs +++ b/Projects/UOContent/Holiday Stuff/Halloween/2009/Engines/PumpkinPatch.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Server.Events.Halloween; using Server.Items; @@ -51,22 +50,23 @@ namespace Server.Engines.Events var rect = m_PumpkinFields[i]; var spawncount = rect.Height * rect.Width / 20; - var pumpkins = map.GetItemsInBounds(rect).OfType().Count(); + var eable = map.GetItemsInBounds(rect); + var pumpkins = 0; + foreach (var p in eable) + { + if (pumpkins++ >= spawncount) + { + break; + } + } + + eable.Free(); if (spawncount > pumpkins) { - new HalloweenPumpkin().MoveToWorld(RandomPointIn(rect, map), map); + new HalloweenPumpkin().MoveToWorld(Utility.RandomPointIn(rect, map), map); } } } - - private static Point3D RandomPointIn(Rectangle2D rect, Map map) - { - var x = Utility.Random(rect.X, rect.Width); - var y = Utility.Random(rect.Y, rect.Height); - var z = map.GetAverageZ(x, y); - - return new Point3D(x, y, z); - } } } diff --git a/Projects/UOContent/Holiday Stuff/Halloween/2012/Engines/PlayerZombies.cs b/Projects/UOContent/Holiday Stuff/Halloween/2012/Engines/PlayerZombies.cs index c24d2572f..20fd8e5ea 100644 --- a/Projects/UOContent/Holiday Stuff/Halloween/2012/Engines/PlayerZombies.cs +++ b/Projects/UOContent/Holiday Stuff/Halloween/2012/Engines/PlayerZombies.cs @@ -115,8 +115,7 @@ namespace Server.Engines.Events } var map = Utility.RandomBool() ? Map.Trammel : Map.Felucca; - - var home = GetRandomPointInRect(m_Cemetaries.RandomElement(), map); + var home = Utility.RandomPointIn(m_Cemetaries.RandomElement(), map); if (map.CanSpawnMobile(home)) { @@ -131,14 +130,6 @@ namespace Server.Engines.Events _deathQueue.Remove(player); } } - - private static Point3D GetRandomPointInRect(Rectangle2D rect, Map map) - { - var x = Utility.Random(rect.X, rect.Width); - var y = Utility.Random(rect.Y, rect.Height); - - return new Point3D(x, y, map.GetAverageZ(x, y)); - } } [Serializable(0, false)] diff --git a/Projects/UOContent/Items/Misc/Firebomb.cs b/Projects/UOContent/Items/Misc/Firebomb.cs index 60433972e..1c7f9a602 100644 --- a/Projects/UOContent/Items/Misc/Firebomb.cs +++ b/Projects/UOContent/Items/Misc/Firebomb.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; +using Server.Collections; using Server.Network; using Server.Spells; +using Server.Spells.Fourth; using Server.Targeting; namespace Server.Items @@ -10,6 +11,7 @@ namespace Server.Items public class Firebomb : Item { private Mobile m_LitBy; + private Point3D _thrownFromLocation; private int m_Ticks; private TimerExecutionToken _timerToken; private List m_Users; @@ -138,24 +140,34 @@ namespace Server.Items else if (RootParent == null) { var eable = Map.GetMobilesInRange(Location, 1); - var toDamage = eable.ToList(); - - eable.Free(); - - for (var i = 0; i < toDamage.Count; ++i) + using var targets = PooledRefQueue.Create(); + foreach (var m in eable) { - var victim = toDamage[i]; - - if (m_LitBy == null || SpellHelper.ValidIndirectTarget(m_LitBy, victim) && - m_LitBy.CanBeHarmful(victim, false)) + if (m_LitBy == null || SpellHelper.ValidIndirectTarget(m_LitBy, m) && + m_LitBy.CanBeHarmful(m, false)) { - m_LitBy?.DoHarmful(victim); - - AOS.Damage(victim, m_LitBy, Utility.Random(3) + 4, 0, 100, 0, 0, 0); + targets.Enqueue(m); } } + eable.Free(); - new FirebombField(m_LitBy, toDamage).MoveToWorld(Location, Map); + while (targets.Count > 0) + { + var victim = targets.Dequeue(); + m_LitBy?.DoHarmful(victim); + AOS.Damage(victim, m_LitBy, Utility.Random(3) + 4, 0, 100, 0, 0, 0); + } + + var loc = _thrownFromLocation; + var eastToWest = SpellHelper.GetEastToWest(loc, Location); + Effects.PlaySound(loc, Map, 0x20C); + var itemID = eastToWest ? 0x398C : 0x3996; + + for (var i = -2; i <= 2; ++i) + { + var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z); + new FireFieldSpell.FireFieldItem(itemID, targetLoc, m_LitBy, Map, TimeSpan.FromSeconds(9), i); + } } _timerToken.Cancel(); @@ -178,12 +190,12 @@ namespace Server.Items } SpellHelper.GetSurfaceTop(ref p); - var loc = new Point3D(p); + _thrownFromLocation = new Point3D(p); var map = Map; from.RevealingAction(); - var to = p as IEntity ?? new Entity(Serial.Zero, loc, map); + var to = p as IEntity ?? new Entity(Serial.Zero, _thrownFromLocation, map); Effects.SendMovingEffect(from, to, ItemID, 7, 0, false, false, Hue); @@ -195,7 +207,7 @@ namespace Server.Items return; } - MoveToWorld(loc, map); + MoveToWorld(_thrownFromLocation, map); } ); Internalize(); @@ -203,9 +215,7 @@ namespace Server.Items private class ThrowTarget : Target { - public ThrowTarget(Firebomb bomb) - : base(12, true, TargetFlags.None) => - Bomb = bomb; + public ThrowTarget(Firebomb bomb) : base(12, true, TargetFlags.None) => Bomb = bomb; public Firebomb Bomb { get; } @@ -215,93 +225,4 @@ namespace Server.Items } } } - - public class FirebombField : Item - { - private readonly List m_Burning; - private readonly DateTime m_Expire; - private readonly Mobile m_LitBy; - private TimerExecutionToken _timerToken; - - public FirebombField(Mobile litBy, List toDamage) : base(0x376A) - { - Movable = false; - m_LitBy = litBy; - m_Expire = Core.Now + TimeSpan.FromSeconds(10); - m_Burning = toDamage; - Timer.StartTimer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), OnFirebombFieldTimerTick, out _timerToken); - } - - public FirebombField(Serial serial) : base(serial) - { - } - - public override void Serialize(IGenericWriter writer) - { - // Don't serialize these... - } - - public override void Deserialize(IGenericReader reader) - { - } - - public override bool OnMoveOver(Mobile m) - { - if (ItemID == 0x398C && m_LitBy == null || - SpellHelper.ValidIndirectTarget(m_LitBy, m) && m_LitBy.CanBeHarmful(m, false)) - { - m_LitBy?.DoHarmful(m); - - AOS.Damage(m, m_LitBy, 2, 0, 100, 0, 0, 0); - m.PlaySound(0x208); - - if (!m_Burning.Contains(m)) - { - m_Burning.Add(m); - } - } - - return true; - } - - private void OnFirebombFieldTimerTick() - { - if (Deleted) - { - _timerToken.Cancel(); - return; - } - - if (ItemID == 0x376A) - { - ItemID = 0x398C; - return; - } - - for (var i = 0; i < m_Burning.Count;) - { - var victim = m_Burning[i]; - - if (victim.Location == Location && victim.Map == Map && - (m_LitBy == null || SpellHelper.ValidIndirectTarget(m_LitBy, victim) && - m_LitBy.CanBeHarmful(victim, false))) - { - m_LitBy?.DoHarmful(victim); - - AOS.Damage(victim, m_LitBy, Utility.Random(3) + 4, 0, 100, 0, 0, 0); - ++i; - } - else - { - m_Burning.RemoveAt(i); - } - } - - if (Core.Now >= m_Expire) - { - _timerToken.Cancel(); - Delete(); - } - } - } } diff --git a/Projects/UOContent/Items/Skill Items/Camping/Campfire.cs b/Projects/UOContent/Items/Skill Items/Camping/Campfire.cs index 424199d15..54b4f0410 100644 --- a/Projects/UOContent/Items/Skill Items/Camping/Campfire.cs +++ b/Projects/UOContent/Items/Skill Items/Camping/Campfire.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Server.Mobiles; namespace Server.Items @@ -58,20 +57,26 @@ namespace Server.Items switch (value) { case CampfireStatus.Burning: - ItemID = 0xDE3; - Light = LightType.Circle300; - break; + { + ItemID = 0xDE3; + Light = LightType.Circle300; + break; + } case CampfireStatus.Extinguishing: - ItemID = 0xDE9; - Light = LightType.Circle150; - break; + { + ItemID = 0xDE9; + Light = LightType.Circle150; + break; + } default: - ItemID = 0xDEA; - Light = LightType.ArchedWindowEast; - ClearEntries(); - break; + { + ItemID = 0xDEA; + Light = LightType.ArchedWindowEast; + ClearEntries(); + break; + } } } } @@ -111,8 +116,10 @@ namespace Server.Items return; } - foreach (var entry in m_Entries.ToList()) + for (var i = m_Entries.Count - 1; i >= 0; i--) { + var entry = m_Entries[i]; + if (!entry.Valid || entry.Player.NetState == null) { RemoveEntry(entry); @@ -149,10 +156,13 @@ namespace Server.Items return; } - foreach (var entry in m_Entries.ToList()) + foreach (var entry in m_Entries) { - RemoveEntry(entry); + m_Table.Remove(entry.Player); } + + m_Entries.Clear(); + m_Entries.TrimExcess(); } public override void OnAfterDelete() diff --git a/Projects/UOContent/Items/Skill Items/Misc/FireHorn.cs b/Projects/UOContent/Items/Skill Items/Misc/FireHorn.cs index 6f35e8b82..edd2eeb4d 100644 --- a/Projects/UOContent/Items/Skill Items/Misc/FireHorn.cs +++ b/Projects/UOContent/Items/Skill Items/Misc/FireHorn.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using Server.Collections; using Server.Network; using Server.Spells; using Server.Targeting; @@ -100,27 +100,23 @@ namespace Server.Items true ); - var eable = from.Map.GetMobilesInRange(new Point3D(loc), 2); - var playerVsPlayer = false; - var targets = eable.Where( - m => + var eable = from.Map.GetMobilesInRange(loc, 2); + + using var targets = PooledRefQueue.Create(); + foreach (var m in eable) + { + if (from != m && SpellHelper.ValidIndirectTarget(from, m) && from.CanBeHarmful(m, false) && + (!Core.AOS || from.InLOS(m))) + { + targets.Enqueue(m); + + if (m.Player) { - if (from == m || !SpellHelper.ValidIndirectTarget(from, m) || !from.CanBeHarmful(m, false) - || Core.AOS && !from.InLOS(m)) - { - return false; - } - - if (m.Player) - { - playerVsPlayer = true; - } - - return true; + playerVsPlayer = true; } - ) - .ToList(); + } + } eable.Free(); @@ -178,9 +174,9 @@ namespace Server.Items damage /= targets.Count; } - for (var i = 0; i < targets.Count; ++i) + while (targets.Count > 0) { - var m = targets[i]; + var m = targets.Dequeue(); var toDeal = damage; diff --git a/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs b/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs index 32496a2a7..4b0004bac 100644 --- a/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs +++ b/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using Server.Collections; using Server.Spells; namespace Server.Items @@ -36,23 +36,30 @@ namespace Server.Items attacker.FixedEffect(0x3728, 10, 15); attacker.PlaySound(0x2A1); - var targets = attacker.GetMobilesInRange(1) - .Where( - m => - m?.Deleted == false && m != defender && m != attacker && - SpellHelper.ValidIndirectTarget(attacker, m) && - m.Map == attacker.Map && m.Alive && attacker.CanSee(m) && attacker.CanBeHarmful(m) && - attacker.InRange(m, weapon.MaxRange) && attacker.InLOS(m) - ) - .ToList(); + var eable = attacker.GetMobilesInRange(1); + using var queue = PooledRefQueue.Create(); - if (targets.Count <= 0) + foreach (var m in eable) + { + if (m?.Deleted == false && m != defender && m != attacker && + m.Map == attacker.Map && m.Alive && + SpellHelper.ValidIndirectTarget(attacker, m) && + attacker.CanSee(m) && attacker.CanBeHarmful(m) && + attacker.InRange(m, weapon.MaxRange) && attacker.InLOS(m)) + { + queue.Enqueue(m); + } + } + + eable.Free(); + + if (queue.Count <= 0) { return; } var bushido = attacker.Skills.Bushido.Value; - var damageBonus = 1.0 + Math.Pow(targets.Count * bushido / 60, 2) / 100; + var damageBonus = 1.0 + Math.Pow(queue.Count * bushido / 60, 2) / 100; if (damageBonus > 2.0) { @@ -61,10 +68,9 @@ namespace Server.Items attacker.RevealingAction(); - for (var i = 0; i < targets.Count; ++i) + while (queue.Count > 0) { - var m = targets[i]; - + var m = queue.Dequeue(); attacker.SendLocalizedMessage(1060161); // The whirling attack strikes a target! m.SendLocalizedMessage(1060162); // You are struck by the whirling attack and take damage! diff --git a/Projects/UOContent/Mobiles/Familiars/HordeMinion.cs b/Projects/UOContent/Mobiles/Familiars/HordeMinion.cs index e1be900ac..96bb1a49d 100644 --- a/Projects/UOContent/Mobiles/Familiars/HordeMinion.cs +++ b/Projects/UOContent/Mobiles/Familiars/HordeMinion.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; +using Server.Collections; using Server.ContextMenus; using Server.Gumps; using Server.Items; @@ -76,12 +76,24 @@ namespace Server.Mobiles return; } - var eable = GetItemsInRange(2).Where(item => item.Movable && item.Stackable); - - var pickedUp = 0; - + var eable = GetItemsInRange(2); + using var queue = PooledRefQueue.Create(); foreach (var item in eable) { + if (item.Movable && item.Stackable) + { + queue.Enqueue(item); + } + } + + eable.Free(); + + var pickedUp = 3; + + while (pickedUp > 0 && queue.Count > 0) + { + var item = queue.Dequeue(); + if (!pack.CheckHold(this, item, false, true)) { return; @@ -97,11 +109,7 @@ namespace Server.Mobiles } Drop(this, Point3D.Zero); - - if (++pickedUp == 3) - { - break; - } + pickedUp--; } } diff --git a/Projects/UOContent/Mobiles/Familiars/ShadowWisp.cs b/Projects/UOContent/Mobiles/Familiars/ShadowWisp.cs index 6ad276cc2..786c2d12b 100644 --- a/Projects/UOContent/Mobiles/Familiars/ShadowWisp.cs +++ b/Projects/UOContent/Mobiles/Familiars/ShadowWisp.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using Server.Collections; namespace Server.Mobiles { @@ -70,16 +70,20 @@ namespace Server.Mobiles return; } - var list = GetMobilesInRange(5) - .Where( - m => - m.Player && m.Alive && !m.IsDeadBondedPet && m.Karma <= 0 && m.AccessLevel < AccessLevel.Counselor - ) - .ToList(); - - for (var i = 0; i < list.Count; ++i) + var eable = GetMobilesInRange(5); + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) { - var m = list[i]; + if (m.Player && m.Alive && !m.IsDeadBondedPet && m.Karma <= 0 && m.AccessLevel < AccessLevel.Counselor) + { + queue.Enqueue(m); + } + } + eable.Free(); + + while (queue.Count > 0) + { + var m = queue.Dequeue(); var friendly = true; for (var j = 0; friendly && j < caster.Aggressors.Count; ++j) diff --git a/Projects/UOContent/Mobiles/Monsters/Humanoid/Melee/OrcBrute.cs b/Projects/UOContent/Mobiles/Monsters/Humanoid/Melee/OrcBrute.cs index 34c4c410a..69deb37ae 100644 --- a/Projects/UOContent/Mobiles/Monsters/Humanoid/Melee/OrcBrute.cs +++ b/Projects/UOContent/Mobiles/Monsters/Humanoid/Melee/OrcBrute.cs @@ -1,4 +1,3 @@ -using System.Linq; using Server.Items; namespace Server.Mobiles @@ -120,8 +119,16 @@ namespace Server.Mobiles } var eable = GetMobilesInRange(10); + var count = 0; + foreach (var m in eable) + { + if (++count == 10) + { + break; + } + } - if (eable.Count() < 10) + if (count < 10) { BaseCreature orc = new SpawnedOrcishLord { Team = Team }; diff --git a/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerEternal.cs b/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerEternal.cs index d7f064620..4b78c5108 100644 --- a/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerEternal.cs +++ b/Projects/UOContent/Mobiles/Monsters/LBR/Meers/MeerEternal.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using Server.Collections; namespace Server.Mobiles { @@ -84,45 +84,43 @@ namespace Server.Mobiles private void DoAreaLeech_Finish() { var eable = GetMobilesInRange(6); - var list = eable.Where(m => CanBeHarmful(m) && IsEnemy(m)).ToList(); + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) + { + if (CanBeHarmful(m) && IsEnemy(m)) + { + queue.Enqueue(m); + } + } eable.Free(); - if (list.Count == 0) + if (queue.Count == 0) { Say(true, "Bah! You have escaped my grasp this time, mortal!"); + return; } - else + + double scalar = queue.Count switch { - double scalar; + 1 => 0.75, + 2 => 0.50, + _ => 0.25 + }; - if (list.Count == 1) - { - scalar = 0.75; - } - else if (list.Count == 2) - { - scalar = 0.50; - } - else - { - scalar = 0.25; - } + while (queue.Count > 0) + { + var m = queue.Dequeue(); - for (var i = 0; i < list.Count; ++i) - { - var m = list[i]; + var damage = (int)(m.Hits * scalar) + Utility.RandomMinMax(-5, 5); - var damage = (int)(m.Hits * scalar) + Utility.RandomMinMax(-5, 5); + m.MovingParticles(this, 0x36F4, 1, 0, false, false, 32, 0, 9535, 1, 0, (EffectLayer)255, 0x100); + m.MovingParticles(this, 0x0001, 1, 0, false, true, 32, 0, 9535, 9536, 0, (EffectLayer)255, 0); - m.MovingParticles(this, 0x36F4, 1, 0, false, false, 32, 0, 9535, 1, 0, (EffectLayer)255, 0x100); - m.MovingParticles(this, 0x0001, 1, 0, false, true, 32, 0, 9535, 9536, 0, (EffectLayer)255, 0); - - DoHarmful(m); - Hits += AOS.Damage(m, this, Math.Max(damage, 1), 100, 0, 0, 0, 0); - } - - Say(true, "If I cannot cleanse thy soul, I will destroy it!"); + DoHarmful(m); + Hits += AOS.Damage(m, this, Math.Max(damage, 1), 100, 0, 0, 0, 0); } + + Say(true, "If I cannot cleanse thy soul, I will destroy it!"); } private void DoFocusedLeech(Mobile combatant, string message) @@ -172,20 +170,28 @@ namespace Server.Mobiles switch (ability) { case 0: - DoFocusedLeech(combatant, "Thine essence will fill my withering body with strength!"); - break; + { + DoFocusedLeech(combatant, "Thine essence will fill my withering body with strength!"); + break; + } case 1: - DoFocusedLeech( - combatant, - "I rebuke thee, worm, and cleanse thy vile spirit of its tainted blood!" - ); - break; + { + DoFocusedLeech( + combatant, + "I rebuke thee, worm, and cleanse thy vile spirit of its tainted blood!" + ); + break; + } case 2: - DoFocusedLeech(combatant, "I devour your life's essence to strengthen my resolve!"); - break; + { + DoFocusedLeech(combatant, "I devour your life's essence to strengthen my resolve!"); + break; + } case 3: - DoAreaLeech(); - break; + { + DoAreaLeech(); + break; + } // TODO: Resurrect ability } } diff --git a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs index 0f88661c9..374ae412e 100644 --- a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs +++ b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/BladeSpirits.cs @@ -1,5 +1,6 @@ using System; -using System.Linq; +using System.Buffers; +using Server.Collections; namespace Server.Mobiles { @@ -72,17 +73,28 @@ namespace Server.Mobiles if (Core.SE && Summoned) { var eable = GetMobilesInRange(5); - var spiritsOrVortexes = eable - .Where(m => m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) - .ToList(); - + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) + { + if (m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) + { + queue.Enqueue(m); + } + } eable.Free(); - while (spiritsOrVortexes.Count > 6) + var amount = queue.Count - 6; + if (amount > 0) { - var random = spiritsOrVortexes.RandomElement(); - Dispel(random); - spiritsOrVortexes.Remove(random); + var mobs = queue.ToPooledArray(); + mobs.Shuffle(); + + while (amount > 0) + { + Dispel(mobs[amount--]); + } + + ArrayPool.Shared.Return(mobs); } } diff --git a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs index f081cedff..145b85b6d 100644 --- a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs +++ b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/EnergyVortex.cs @@ -1,5 +1,6 @@ using System; -using System.Linq; +using System.Buffers; +using Server.Collections; namespace Server.Mobiles { @@ -78,17 +79,28 @@ namespace Server.Mobiles if (Core.SE && Summoned) { var eable = GetMobilesInRange(5); - var spiritsOrVortexes = eable - .Where(m => m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) - .ToList(); - + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) + { + if (m is EnergyVortex or BladeSpirits && ((BaseCreature)m).Summoned) + { + queue.Enqueue(m); + } + } eable.Free(); - while (spiritsOrVortexes.Count > 6) + var amount = queue.Count - 6; + if (amount > 0) { - var random = spiritsOrVortexes.RandomElement(); - Dispel(random); - spiritsOrVortexes.Remove(random); + var mobs = queue.ToPooledArray(); + mobs.Shuffle(); + + while (amount > 0) + { + Dispel(mobs[amount--]); + } + + ArrayPool.Shared.Return(mobs); } } diff --git a/Projects/UOContent/Mobiles/Special/Harrower.cs b/Projects/UOContent/Mobiles/Special/Harrower.cs index cd6ba03c9..ad0779270 100644 --- a/Projects/UOContent/Mobiles/Special/Harrower.cs +++ b/Projects/UOContent/Mobiles/Special/Harrower.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Server.Items; using Server.Spells; @@ -570,8 +569,17 @@ namespace Server.Mobiles return; } - var toTeleport = m_Owner.GetMobilesInRange(16) - .FirstOrDefault(mob => mob != m_Owner && mob.Player && m_Owner.CanBeHarmful(mob) && m_Owner.CanSee(mob)); + var eable = m_Owner.GetMobilesInRange(16); + Mobile toTeleport = null; + foreach (var m in eable) + { + if (m != m_Owner && m.Player && m_Owner.CanBeHarmful(m) && m_Owner.CanSee(m)) + { + toTeleport = m; + break; + } + } + eable.Free(); if (toTeleport == null) { @@ -602,33 +610,31 @@ namespace Server.Mobiles } } - var m = toTeleport; + var from = toTeleport.Location; - var from = m.Location; - - m.Location = to; + toTeleport.Location = to; SpellHelper.Turn(m_Owner, toTeleport); SpellHelper.Turn(toTeleport, m_Owner); - m.ProcessDelta(); + toTeleport.ProcessDelta(); Effects.SendLocationParticles( - EffectItem.Create(from, m.Map, EffectItem.DefaultDuration), + EffectItem.Create(from, toTeleport.Map, EffectItem.DefaultDuration), 0x3728, 10, 10, 2023 ); Effects.SendLocationParticles( - EffectItem.Create(to, m.Map, EffectItem.DefaultDuration), + EffectItem.Create(to, toTeleport.Map, EffectItem.DefaultDuration), 0x3728, 10, 10, 5023 ); - m.PlaySound(0x1FE); + toTeleport.PlaySound(0x1FE); m_Owner.Combatant = toTeleport; } diff --git a/Projects/UOContent/Multis/Houses/BaseHouse.cs b/Projects/UOContent/Multis/Houses/BaseHouse.cs index 66cef968e..272bba2cf 100644 --- a/Projects/UOContent/Multis/Houses/BaseHouse.cs +++ b/Projects/UOContent/Multis/Houses/BaseHouse.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Server.Accounting; +using Server.Collections; using Server.ContextMenus; using Server.Ethics; using Server.Guilds; @@ -3227,16 +3228,21 @@ namespace Server.Multis private void FixLockdowns_Sandbox() { - var conts = LockDowns?.Where(item => item is Container).ToList(); - - if (conts == null) + if (LockDowns?.Count > 0) { - return; - } + using var queue = PooledRefQueue.Create(); + foreach (var item in LockDowns) + { + if (item is Container) + { + queue.Enqueue(item); + } + } - foreach (var cont in conts) - { - SetLockdown(cont, true, true); + while (queue.Count > 0) + { + SetLockdown(queue.Dequeue(), true, true); + } } } diff --git a/Projects/UOContent/Skills/SpiritSpeak.cs b/Projects/UOContent/Skills/SpiritSpeak.cs index 69b40fcf8..4a2ac0ab0 100644 --- a/Projects/UOContent/Skills/SpiritSpeak.cs +++ b/Projects/UOContent/Skills/SpiritSpeak.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Server.Items; using Server.Network; using Server.Spells; @@ -134,7 +133,15 @@ namespace Server.SkillHandlers public override void OnCast() { var eable = Caster.GetItemsInRange(3); - var toChannel = eable.FirstOrDefault(corpse => !corpse.Channeled); + Corpse toChannel = null; + foreach (var corpse in eable) + { + if (!corpse.Channeled) + { + toChannel = corpse; + break; + } + } eable.Free(); var min = 1 + (int)(Caster.Skills.SpiritSpeak.Value * 0.25); diff --git a/Projects/UOContent/Skills/Tracking/Tracking.cs b/Projects/UOContent/Skills/Tracking/Tracking.cs index 8e691702a..4cd3f52ad 100644 --- a/Projects/UOContent/Skills/Tracking/Tracking.cs +++ b/Projects/UOContent/Skills/Tracking/Tracking.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Server.Gumps; using Server.Mobiles; using Server.Network; @@ -210,13 +209,18 @@ namespace Server.SkillHandlers var range = 10 + (int)(from.Skills.Tracking.Value / 10); - var list = from.GetMobilesInRange(range) - .Where( - m => m != from && (!Core.AOS || m.Alive) && - (!m.Hidden || m.AccessLevel == AccessLevel.Player || from.AccessLevel > m.AccessLevel) && - check(m) && CheckDifficulty(from, m) - ) - .ToList(); + var eable = from.GetMobilesInRange(range); + var list = new List(); + foreach (var m in eable) + { + if (m != from && (!Core.AOS || m.Alive) && + (!m.Hidden || m.AccessLevel == AccessLevel.Player || from.AccessLevel > m.AccessLevel) && + check(m) && CheckDifficulty(from, m)) + { + list.Add(m); + } + } + eable.Free(); if (list.Count > 0) { diff --git a/Projects/UOContent/Spells/Base/SpellHelper.cs b/Projects/UOContent/Spells/Base/SpellHelper.cs index ea9ec4131..6cd1a8f5b 100644 --- a/Projects/UOContent/Spells/Base/SpellHelper.cs +++ b/Projects/UOContent/Spells/Base/SpellHelper.cs @@ -256,14 +256,14 @@ namespace Server.Spells return false; } - public static bool GetEastToWest(IPoint3D from,IPoint3D target) + public static bool GetEastToWest(Point3D from, Point3D target) { var dx = from.X - target.X; var dy = from.Y - target.Y; var rx = (dx - dy) * 44; var ry = (dx + dy) * 44; - return (rx >= 0 && ry < 0) || (ry >= 0 && rx < 0); + return rx >= 0 && ry < 0 || ry >= 0 && rx < 0; } public static bool CanRevealCaster(Mobile m) => m is BaseCreature { Controlled: false }; diff --git a/Projects/UOContent/Spells/Bushido/MomentumStrike.cs b/Projects/UOContent/Spells/Bushido/MomentumStrike.cs index 5513c5429..3fbe7b592 100644 --- a/Projects/UOContent/Spells/Bushido/MomentumStrike.cs +++ b/Projects/UOContent/Spells/Bushido/MomentumStrike.cs @@ -1,4 +1,5 @@ using System.Linq; +using Server.Collections; namespace Server.Spells.Bushido { @@ -21,12 +22,17 @@ namespace Server.Spells.Bushido var weapon = attacker.Weapon; - var targets = attacker.GetMobilesInRange(weapon.MaxRange) - .Where(m => m != defender) - .Where(m => m.Combatant == attacker) - .ToList(); + var eable = attacker.GetMobilesInRange(weapon.MaxRange); + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) + { + if (m != defender && m.Combatant == attacker) + { + queue.Enqueue(m); + } + } - if (targets.Count <= 0) + if (queue.Count <= 0) { attacker.SendLocalizedMessage(1063123); // There are no valid targets to attack! return; @@ -37,7 +43,7 @@ namespace Server.Spells.Bushido return; } - var target = targets.RandomElement(); + Mobile target = queue.PeekRandom(); var damageBonus = attacker.Skills.Bushido.Value / 100.0; @@ -47,7 +53,7 @@ namespace Server.Spells.Bushido } attacker.SendLocalizedMessage(1063171); // You transfer the momentum of your weapon into another enemy! - target.SendLocalizedMessage(1063172); // You were hit by the momentum of a Samurai's weapon! + target!.SendLocalizedMessage(1063172); // You were hit by the momentum of a Samurai's weapon! target.FixedParticles(0x37B9, 1, 4, 0x251D, 0, 0, EffectLayer.Waist); diff --git a/Projects/UOContent/Spells/Chivalry/DispelEvil.cs b/Projects/UOContent/Spells/Chivalry/DispelEvil.cs index d70ef6859..b09d6a2dd 100644 --- a/Projects/UOContent/Spells/Chivalry/DispelEvil.cs +++ b/Projects/UOContent/Spells/Chivalry/DispelEvil.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using Server.Collections; using Server.Items; using Server.Mobiles; using Server.Spells.Necromancy; @@ -46,11 +46,21 @@ namespace Server.Spells.Chivalry var chiv = Caster.Skills.Chivalry.Value; - var targets = Caster.GetMobilesInRange(8) - .Where(m => Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false)); - - foreach (var m in targets) + var eable = Caster.GetMobilesInRange(8); + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) { + if (Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false)) + { + queue.Enqueue(m); + } + } + eable.Free(); + + while (queue.Count > 0) + { + var m = queue.Dequeue(); + if (m is BaseCreature bc) { if (bc.Summoned && !bc.IsAnimatedDead) diff --git a/Projects/UOContent/Spells/Eighth/Earthquake.cs b/Projects/UOContent/Spells/Eighth/Earthquake.cs index ffa480feb..f821a49fc 100644 --- a/Projects/UOContent/Spells/Eighth/Earthquake.cs +++ b/Projects/UOContent/Spells/Eighth/Earthquake.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using Server.Collections; namespace Server.Spells.Eighth { @@ -37,14 +37,21 @@ namespace Server.Spells.Eighth return; } - var targets = Caster.GetMobilesInRange(1 + (int)(Caster.Skills.Magery.Value / 15.0)) - .Where( - m => Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false) && - (!Core.AOS || Caster.InLOS(m)) - ); - - foreach (var m in targets) + var eable = Caster.GetMobilesInRange(1 + (int)(Caster.Skills.Magery.Value / 15.0)); + using var queue = PooledRefQueue.Create(); + foreach (var m in eable) { + if (Caster != m && SpellHelper.ValidIndirectTarget(Caster, m) && Caster.CanBeHarmful(m, false) && + (!Core.AOS || Caster.InLOS(m))) + { + queue.Enqueue(m); + } + } + + while (queue.Count > 0) + { + var m = queue.Dequeue(); + int damage; if (Core.AOS) diff --git a/Projects/UOContent/Spells/Fifth/PoisonField.cs b/Projects/UOContent/Spells/Fifth/PoisonField.cs index 8a55f0d0b..80bada423 100644 --- a/Projects/UOContent/Spells/Fifth/PoisonField.cs +++ b/Projects/UOContent/Spells/Fifth/PoisonField.cs @@ -30,22 +30,21 @@ namespace Server.Spells.Fifth if (SpellHelper.CheckTown(p, Caster) && CheckSequence()) { SpellHelper.Turn(Caster, p); - SpellHelper.GetSurfaceTop(ref p); - var eastToWest = SpellHelper.GetEastToWest(Caster.Location, p); + var loc = new Point3D(p); + var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc); - Effects.PlaySound(new Point3D(p), Caster.Map, 0x20B); + Effects.PlaySound(loc, Caster.Map, 0x20B); var itemID = eastToWest ? 0x3915 : 0x3922; - var duration = TimeSpan.FromSeconds(3 + Caster.Skills.Magery.Fixed / 25); for (var i = -2; i <= 2; ++i) { - var loc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z); + var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z); - new InternalItem(itemID, loc, Caster, Caster.Map, duration, i); + new InternalItem(itemID, targetLoc, Caster, Caster.Map, duration, i); } } diff --git a/Projects/UOContent/Spells/Fourth/FireField.cs b/Projects/UOContent/Spells/Fourth/FireField.cs index c3979af90..91ca1b8bb 100644 --- a/Projects/UOContent/Spells/Fourth/FireField.cs +++ b/Projects/UOContent/Spells/Fourth/FireField.cs @@ -33,28 +33,23 @@ namespace Server.Spells.Fourth SpellHelper.GetSurfaceTop(ref p); - var eastToWest = SpellHelper.GetEastToWest(Caster.Location, p); + var loc = new Point3D(p); - Effects.PlaySound(new Point3D(p), Caster.Map, 0x20C); + var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc); + + Effects.PlaySound(loc, Caster.Map, 0x20C); var itemID = eastToWest ? 0x398C : 0x3996; - TimeSpan duration; - - if (Core.AOS) - { - duration = TimeSpan.FromSeconds((15 + Caster.Skills.Magery.Fixed / 5.0) / 4.0); - } - else - { - duration = TimeSpan.FromSeconds(4.0 + Caster.Skills.Magery.Value * 0.5); - } + var duration = Core.AOS + ? TimeSpan.FromSeconds((15 + Caster.Skills.Magery.Fixed / 5.0) / 4.0) + : TimeSpan.FromSeconds(4.0 + Caster.Skills.Magery.Value * 0.5); for (var i = -2; i <= 2; ++i) { - var loc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z); + var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z); - new FireFieldItem(itemID, loc, Caster, Caster.Map, duration, i); + new FireFieldItem(itemID, targetLoc, Caster, Caster.Map, duration, i); } } diff --git a/Projects/UOContent/Spells/Seventh/EnergyField.cs b/Projects/UOContent/Spells/Seventh/EnergyField.cs index bf5c5c019..5c5900a08 100644 --- a/Projects/UOContent/Spells/Seventh/EnergyField.cs +++ b/Projects/UOContent/Spells/Seventh/EnergyField.cs @@ -30,42 +30,35 @@ namespace Server.Spells.Seventh if (SpellHelper.CheckTown(p, Caster) && CheckSequence()) { SpellHelper.Turn(Caster, p); - SpellHelper.GetSurfaceTop(ref p); - var eastToWest = SpellHelper.GetEastToWest(Caster.Location, p); + var loc = new Point3D(p); - Effects.PlaySound(new Point3D(p), Caster.Map, 0x20B); + var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc); - TimeSpan duration; + Effects.PlaySound(loc, Caster.Map, 0x20B); - if (Core.AOS) - { - duration = TimeSpan.FromSeconds((15 + Caster.Skills.Magery.Fixed / 5) / 7.0); - } - else - { - // (28% of magery) + 2.0 seconds - duration = TimeSpan.FromSeconds(Caster.Skills.Magery.Value * 0.28 + 2.0); - } + TimeSpan duration = Core.AOS + ? TimeSpan.FromSeconds((15 + Caster.Skills.Magery.Fixed / 5) / 7.0) + : TimeSpan.FromSeconds(Caster.Skills.Magery.Value * 0.28 + 2.0); var itemID = eastToWest ? 0x3946 : 0x3956; for (var i = -2; i <= 2; ++i) { - var loc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z); - var canFit = SpellHelper.AdjustField(ref loc, Caster.Map, 12, false); + var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z); + var canFit = SpellHelper.AdjustField(ref targetLoc, Caster.Map, 12, false); if (!canFit) { continue; } - Item item = new InternalItem(loc, Caster.Map, duration, itemID, Caster); + Item item = new InternalItem(targetLoc, Caster.Map, duration, itemID, Caster); item.ProcessDelta(); Effects.SendLocationParticles( - EffectItem.Create(loc, Caster.Map, EffectItem.DefaultDuration), + EffectItem.Create(targetLoc, Caster.Map, EffectItem.DefaultDuration), 0x376A, 9, 10, diff --git a/Projects/UOContent/Spells/Sixth/ParalyzeField.cs b/Projects/UOContent/Spells/Sixth/ParalyzeField.cs index 8a018caba..0edd387b3 100644 --- a/Projects/UOContent/Spells/Sixth/ParalyzeField.cs +++ b/Projects/UOContent/Spells/Sixth/ParalyzeField.cs @@ -29,12 +29,12 @@ namespace Server.Spells.Sixth if (SpellHelper.CheckTown(p, Caster) && CheckSequence()) { SpellHelper.Turn(Caster, p); - SpellHelper.GetSurfaceTop(ref p); - var eastToWest = SpellHelper.GetEastToWest(Caster.Location, p); + var loc = new Point3D(p); + var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc); - Effects.PlaySound(new Point3D(p), Caster.Map, 0x20B); + Effects.PlaySound(loc, Caster.Map, 0x20B); var itemID = eastToWest ? 0x3967 : 0x3979; @@ -42,18 +42,18 @@ namespace Server.Spells.Sixth for (var i = -2; i <= 2; ++i) { - var loc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z); + var targetLoc = new Point3D(eastToWest ? loc.X + i : loc.X, eastToWest ? loc.Y : loc.Y + i, loc.Z); - if (!SpellHelper.AdjustField(ref loc, Caster.Map, 12, false)) + if (!SpellHelper.AdjustField(ref targetLoc, Caster.Map, 12, false)) { continue; } - Item item = new InternalItem(Caster, itemID, loc, Caster.Map, duration); + Item item = new InternalItem(Caster, itemID, targetLoc, Caster.Map, duration); item.ProcessDelta(); Effects.SendLocationParticles( - EffectItem.Create(loc, Caster.Map, EffectItem.DefaultDuration), + EffectItem.Create(targetLoc, Caster.Map, EffectItem.DefaultDuration), 0x376A, 9, 10, diff --git a/Projects/UOContent/Spells/Third/WallOfStone.cs b/Projects/UOContent/Spells/Third/WallOfStone.cs index 98d239a07..094374a86 100644 --- a/Projects/UOContent/Spells/Third/WallOfStone.cs +++ b/Projects/UOContent/Spells/Third/WallOfStone.cs @@ -30,14 +30,16 @@ namespace Server.Spells.Third SpellHelper.GetSurfaceTop(ref p); - var eastToWest = SpellHelper.GetEastToWest(Caster.Location, p); + var loc = new Point3D(p); - Effects.PlaySound(new Point3D(p), Caster.Map, 0x1F6); + var eastToWest = SpellHelper.GetEastToWest(Caster.Location, loc); + + Effects.PlaySound(loc, Caster.Map, 0x1F6); for (var i = -1; i <= 1; ++i) { - var loc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z); - var canFit = SpellHelper.AdjustField(ref loc, Caster.Map, 22, true); + var targetLoc = new Point3D(eastToWest ? p.X + i : p.X, eastToWest ? p.Y : p.Y + i, p.Z); + var canFit = SpellHelper.AdjustField(ref targetLoc, Caster.Map, 22, true); // Effects.SendLocationParticles( EffectItem.Create( loc, Caster.Map, EffectItem.DefaultDuration ), 0x376A, 9, 10, 5025 ); @@ -46,7 +48,7 @@ namespace Server.Spells.Third continue; } - Item item = new InternalItem(loc, Caster.Map, Caster); + Item item = new InternalItem(targetLoc, Caster.Map, Caster); Effects.SendLocationParticles(item, 0x376A, 9, 10, 5025);