From d919f71149e25c4c59263d96f84865f839c89553 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 26 Oct 2023 17:49:15 -0700 Subject: [PATCH] fix: Fixes map iterators for Items (#1564) ### Summary Modifying a ValueLinkList using one of the methods will bump the "version". This field is used by iterators (foreach loops) to determine if the link list was modified while iterating. The sector.Items (and in the future other lists), will no longer be safe to modify while iterating. The server will _CRASH_ if the ValueLinkList is modified. Thanks to @stefanomerotta for help! ### Screenshots image --- .../Tests/Collections/ValueLinkListTests.cs | 300 +++++++++++++++--- Projects/Server/Collections/ValueLinkList.cs | 141 +++++--- Projects/Server/IEntity.cs | 14 + Projects/Server/Items/Item.cs | 17 +- Projects/Server/Maps/Map.ItemEnumerator.cs | 201 ++++++------ Projects/Server/Maps/Map.cs | 36 +-- Projects/Server/Maps/PooledEnumeration.cs | 1 + Projects/Server/Mobiles/Mobile.cs | 173 ++++++---- .../Commands/Object Creation/GenTeleporter.cs | 13 +- Projects/UOContent/Commands/SignParser.cs | 10 +- .../Engines/ConPVP/Games/BombingRun.cs | 4 +- .../UOContent/Engines/Doom/GenGauntlet.cs | 6 +- .../Doom/LeverPuzzle/LeverPuzzleController.cs | 2 +- .../Engines/Factions/Core/Generator.cs | 2 +- .../Factions/Items/Traps/BaseFactionTrap.cs | 2 +- .../Engines/Harvest/Core/HarvestSystem.cs | 2 +- .../UOContent/Engines/Khaldun/KhaldunGen.cs | 10 +- .../UOContent/Engines/ML Quests/MLQuest.cs | 4 +- .../UOContent/Engines/Pathing/Movement.cs | 158 ++------- .../Quests/The Summoning/Mobiles/Victoria.cs | 8 +- .../Commands/GenerateSpawnersCommand.cs | 2 +- .../Christmas/2010/Addons/FireFliesDeed.cs | 4 +- .../UOContent/Items/Addons/SHTeleporter.cs | 2 +- .../Items/Containers/MarkContainer.cs | 2 +- Projects/UOContent/Items/Misc/OilFlask.cs | 2 +- Projects/UOContent/Items/Misc/WarningItem.cs | 15 +- Projects/UOContent/Mobiles/AI/BaseAI.cs | 17 +- .../ML/Humanoid/Magic/InterredGrizzle .cs | 2 +- .../Mobiles/Monsters/ML/Special/Ilhenir.cs | 2 +- .../UOContent/Multis/Houses/HousePlacement.cs | 6 +- .../UOContent/Spells/Seventh/GateTravel.cs | 2 +- .../Spells/Spellweaving/ArcaneCircle.cs | 2 +- Projects/UOContent/Spells/Third/Teleport.cs | 12 +- 33 files changed, 706 insertions(+), 468 deletions(-) diff --git a/Projects/Server.Tests/Tests/Collections/ValueLinkListTests.cs b/Projects/Server.Tests/Tests/Collections/ValueLinkListTests.cs index d96ba57d8..9683c8712 100644 --- a/Projects/Server.Tests/Tests/Collections/ValueLinkListTests.cs +++ b/Projects/Server.Tests/Tests/Collections/ValueLinkListTests.cs @@ -1,3 +1,4 @@ +using System; using Server.Collections; using Xunit; @@ -32,20 +33,35 @@ public class ValueLinkListTests Assert.True(entity1.OnLinkList); Assert.Equal(1, linkList.Count); - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity1, linkList.Last); + + Assert.Collection(linkList.ToArray(), item => + { + Assert.Equal(entity1, item); + Assert.Null(item.Previous); + Assert.Null(item.Next); + } + ); var entity2 = new TestEntity(2); - linkList.AddFirst(entity2); Assert.True(entity2.OnLinkList); Assert.Equal(2, linkList.Count); - Assert.Equal(entity1, linkList.Last); - Assert.Equal(entity2, entity1.Previous); - Assert.Equal(entity2, linkList.First); - Assert.Equal(entity1, entity2.Next); + Assert.Collection(linkList.ToArray(), + item => + { + Assert.Equal(entity2, item); + Assert.Null(item.Previous); + Assert.Equal(item.Next, entity1); + }, + item => + { + Assert.Equal(entity1, item); + Assert.Equal(item.Previous, entity2); + Assert.Null(item.Next); + } + ); } [Fact] @@ -58,8 +74,14 @@ public class ValueLinkListTests Assert.True(entity1.OnLinkList); Assert.Equal(1, linkList.Count); - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity1, linkList.Last); + + Assert.Collection(linkList.ToArray(), item => + { + Assert.Equal(entity1, item); + Assert.Null(item.Previous); + Assert.Null(item.Next); + } + ); var entity2 = new TestEntity(2); @@ -67,11 +89,21 @@ public class ValueLinkListTests Assert.True(entity2.OnLinkList); Assert.Equal(2, linkList.Count); - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity2, entity1.Next); - Assert.Equal(entity2, linkList.Last); - Assert.Equal(entity1, entity2.Previous); + Assert.Collection(linkList.ToArray(), + item => + { + Assert.Equal(entity1, item); + Assert.Null(item.Previous); + Assert.Equal(item.Next, entity2); + }, + item => + { + Assert.Equal(entity2, item); + Assert.Equal(item.Previous, entity1); + Assert.Null(item.Next); + } + ); } [Fact] @@ -87,20 +119,48 @@ public class ValueLinkListTests Assert.True(entity1.OnLinkList); Assert.True(entity2.OnLinkList); Assert.Equal(2, linkList.Count); - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity2, linkList.Last); + + Assert.Collection(linkList.ToArray(), + item => + { + Assert.Equal(entity1, item); + Assert.Null(item.Previous); + Assert.Equal(item.Next, entity2); + }, + item => + { + Assert.Equal(entity2, item); + Assert.Equal(item.Previous, entity1); + Assert.Null(item.Next); + } + ); + var entity3 = new TestEntity(3); linkList.AddBefore(entity2, entity3); Assert.True(entity3.OnLinkList); Assert.Equal(3, linkList.Count); - Assert.Equal(entity1, entity3.Previous); - Assert.Equal(entity2, entity3.Next); - - // First and Last should not have changed - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity2, linkList.Last); + Assert.Collection(linkList.ToArray(), + item => + { + Assert.Equal(entity1, item); + Assert.Null(item.Previous); + Assert.Equal(item.Next, entity3); + }, + item => + { + Assert.Equal(entity3, item); + Assert.Equal(item.Previous, entity1); + Assert.Equal(item.Next, entity2); + }, + item => + { + Assert.Equal(entity2, item); + Assert.Equal(item.Previous, entity3); + Assert.Null(item.Next); + } + ); } [Fact] @@ -113,23 +173,31 @@ public class ValueLinkListTests linkList.AddFirst(entity1); linkList.AddLast(entity2); - Assert.True(entity1.OnLinkList); - Assert.True(entity2.OnLinkList); - Assert.Equal(2, linkList.Count); - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity2, linkList.Last); - var entity3 = new TestEntity(3); linkList.AddAfter(entity1, entity3); Assert.True(entity3.OnLinkList); Assert.Equal(3, linkList.Count); - Assert.Equal(entity1, entity3.Previous); - Assert.Equal(entity2, entity3.Next); - - // First and Last should not have changed - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity2, linkList.Last); + Assert.Collection(linkList.ToArray(), + item => + { + Assert.Equal(entity1, item); + Assert.Null(item.Previous); + Assert.Equal(item.Next, entity3); + }, + item => + { + Assert.Equal(entity3, item); + Assert.Equal(item.Previous, entity1); + Assert.Equal(item.Next, entity2); + }, + item => + { + Assert.Equal(entity2, item); + Assert.Equal(item.Previous, entity3); + Assert.Null(item.Next); + } + ); } [Fact] @@ -157,12 +225,47 @@ public class ValueLinkListTests Assert.Equal(1, linkList.Count); Assert.Equal(5, linkList2.Count); - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity1, linkList.Last); + Assert.Collection(linkList.ToArray(), + item => + { + Assert.Equal(entity1, item); + Assert.Null(item.Previous); + Assert.Null(item.Next); + } + ); - Assert.Equal(entity2, entity6.Next); - Assert.Equal(entity6, entity2.Previous); - Assert.Equal(entity4, linkList2.Last); + Assert.Collection(linkList2.ToArray(), + item => + { + Assert.Equal(entity5, item); + Assert.Null(item.Previous); + Assert.Equal(item.Next, entity6); + }, + item => + { + Assert.Equal(entity6, item); + Assert.Equal(item.Previous, entity5); + Assert.Equal(item.Next, entity2); + }, + item => + { + Assert.Equal(entity2, item); + Assert.Equal(item.Previous, entity6); + Assert.Equal(item.Next, entity3); + }, + item => + { + Assert.Equal(entity3, item); + Assert.Equal(item.Previous, entity2); + Assert.Equal(item.Next, entity4); + }, + item => + { + Assert.Equal(entity4, item); + Assert.Equal(item.Previous, entity3); + Assert.Null(item.Next); + } + ); } [Fact] @@ -184,10 +287,20 @@ public class ValueLinkListTests linkList.RemoveAllBefore(entity4); Assert.Equal(2, linkList.Count); - Assert.Equal(entity4, linkList.First); - Assert.Equal(entity5, linkList.Last); - Assert.Null(entity4.Previous); - Assert.Null(entity5.Next); + Assert.Collection(linkList.ToArray(), + item => + { + Assert.Equal(entity4, item); + Assert.Null(item.Previous); + Assert.Equal(item.Next, entity5); + }, + item => + { + Assert.Equal(entity5, item); + Assert.Equal(item.Previous, entity4); + Assert.Null(item.Next); + } + ); } [Fact] @@ -209,9 +322,104 @@ public class ValueLinkListTests linkList.RemoveAllAfter(entity2); Assert.Equal(2, linkList.Count); - Assert.Equal(entity1, linkList.First); - Assert.Equal(entity2, linkList.Last); - Assert.Null(entity1.Previous); - Assert.Null(entity2.Next); + Assert.Collection(linkList.ToArray(), + item => + { + Assert.Equal(entity1, item); + Assert.Null(item.Previous); + Assert.Equal(item.Next, entity2); + }, + item => + { + Assert.Equal(entity2, item); + Assert.Equal(item.Previous, entity1); + Assert.Null(item.Next); + } + ); + } + + [Fact] + public void TestVersionIncrements() + { + var linkList = new ValueLinkList(); + + var entity1 = new TestEntity(1); + var entity2 = new TestEntity(2); + var entity3 = new TestEntity(3); + var entity4 = new TestEntity(4); + + var version = 0; + Assert.Equal(version, linkList.Version); + + linkList.AddFirst(entity1); // 1 + Assert.Equal(++version, linkList.Version); + + linkList.AddLast(entity2); // 1, 2 + Assert.Equal(++version, linkList.Version); + + linkList.AddBefore(entity2, entity3); // 1, 3, 2 + Assert.Equal(++version, linkList.Version); + + linkList.AddAfter(entity3, entity4); // 1, 3, 4, 2 + Assert.Equal(++version, linkList.Version); + + linkList.Remove(entity1); // 3, 4, 2 + Assert.Equal(++version, linkList.Version); + + linkList.RemoveAllAfter(entity4); // 3, 4 + Assert.Equal(++version, linkList.Version); + + linkList.RemoveAllBefore(entity4); // 4 + Assert.Equal(++version, linkList.Version); + + linkList.RemoveAll(); // None + Assert.Equal(++version, linkList.Version); + } + + [Fact] + public void TestThrowsIfModifiedWhileIterating() + { + Assert.Throws( + () => + { + var linkList = new ValueLinkList(); + + var entity1 = new TestEntity(1); + var entity2 = new TestEntity(2); + + linkList.AddFirst(entity1); + linkList.AddLast(entity2); + + foreach (var item in linkList) + { + linkList.Remove(item); + } + } + ); + } + + [Fact] + public void TestThrowsIfModifiedWhileIteratingNested() + { + Assert.Throws( + () => + { + var linkList = new ValueLinkList(); + + var entity1 = new TestEntity(1); + var entity2 = new TestEntity(2); + + linkList.AddFirst(entity1); + linkList.AddLast(entity2); + + foreach (var item in linkList) + { + foreach (var nestedItem in linkList) + { + linkList.Remove(nestedItem); + } + } + } + ); } } diff --git a/Projects/Server/Collections/ValueLinkList.cs b/Projects/Server/Collections/ValueLinkList.cs index 00727ab0a..0accf691f 100644 --- a/Projects/Server/Collections/ValueLinkList.cs +++ b/Projects/Server/Collections/ValueLinkList.cs @@ -28,8 +28,10 @@ public interface IValueLinkListNode where T : class public struct ValueLinkList where T : class, IValueLinkListNode { public int Count { get; internal set; } - public T First { get; internal set; } - public T Last { get; internal set; } + internal T _first; + internal T _last; + + public int Version { get; private set; } public void Remove(T node) { @@ -46,19 +48,19 @@ public struct ValueLinkList where T : class, IValueLinkListNode if (node.Previous == null) { // If previous is null, then it is the first element. - if (First != node) + if (_first != node) { throw new ArgumentException("Attempted to remove a node that is not on the list."); } - if (First == Last) + if (_first == _last) { - Last = null; - First = null; + _last = null; + _first = null; } else { - First = node.Next; + _first = node.Next; } if (node.Next != null) @@ -73,7 +75,7 @@ public struct ValueLinkList where T : class, IValueLinkListNode // If next is null, then it is the last element. if (node.Next == null) { - Last = node.Previous; + _last = node.Previous; } else { @@ -85,6 +87,7 @@ public struct ValueLinkList where T : class, IValueLinkListNode node.Previous = null; node.OnLinkList = false; Count--; + Version++; if (Count < 0) { @@ -130,7 +133,8 @@ public struct ValueLinkList where T : class, IValueLinkListNode current = previous; } - First = e; + _first = e; + Version++; } // Remove all entries after this node, not including this node. @@ -171,7 +175,8 @@ public struct ValueLinkList where T : class, IValueLinkListNode current = next; } - Last = e; + _last = e; + Version++; } public void AddLast(T e) @@ -186,15 +191,16 @@ public struct ValueLinkList where T : class, IValueLinkListNode throw new ArgumentException("Attempted to add a node that is already on a list."); } - if (Last != null) + if (_last != null) { - AddAfter(Last, e); + AddAfter(_last, e); } else { - First = e; - Last = e; + _first = e; + _last = e; Count = 1; + Version++; e.OnLinkList = true; } } @@ -211,15 +217,16 @@ public struct ValueLinkList where T : class, IValueLinkListNode throw new ArgumentException("Attempted to add a node that is already on a list."); } - if (First != null) + if (_first != null) { - AddBefore(First, e); + AddBefore(_first, e); } else { - First = e; - Last = e; + _first = e; + _last = e; Count = 1; + Version++; e.OnLinkList = true; } } @@ -252,12 +259,13 @@ public struct ValueLinkList where T : class, IValueLinkListNode } else { - First = node; + _first = node; } existing.Previous = node; node.OnLinkList = true; Count++; + Version++; } public void AddAfter(T existing, T node) @@ -288,17 +296,18 @@ public struct ValueLinkList where T : class, IValueLinkListNode } else { - Last = node; + _last = node; } existing.Next = node; node.OnLinkList = true; Count++; + Version++; } public void RemoveAll() { - var current = First; + var current = _first; while (current != null) { var next = current.Next; @@ -309,15 +318,16 @@ public struct ValueLinkList where T : class, IValueLinkListNode current = next; } - First = null; - Last = null; + _first = null; + _last = null; Count = 0; + Version++; } public void AddLast(ref ValueLinkList otherList, T start, T end) { // Should we check if start and end actually exist on the other list? - if (otherList.Count == 0 || otherList.Count == 1 && (start != end || otherList.First != start)) + if (otherList.Count == 0 || otherList.Count == 1 && (start != end || otherList._first != start)) { throw new ArgumentException("Attempted to add nodes that are not on the specified linklist."); } @@ -329,7 +339,7 @@ public struct ValueLinkList where T : class, IValueLinkListNode else { // Start is first - otherList.First = end.Next; + otherList._first = end.Next; } if (end.Next != null) @@ -338,7 +348,7 @@ public struct ValueLinkList where T : class, IValueLinkListNode } else { - otherList.Last = start.Previous; + otherList._last = start.Previous; } var count = 1; @@ -358,45 +368,62 @@ public struct ValueLinkList where T : class, IValueLinkListNode throw new Exception("Count is negative!"); } - if (Last != null) + if (_last != null) { - Last.Next = start; - start.Previous = Last; + _last.Next = start; + start.Previous = _last; } else { - First = start; + _first = start; } - Last = end; + _last = end; Count += count; + Version++; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ValueListEnumerator GetEnumerator() => new(First); + public T[] ToArray() + { + var arr = new T[Count]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public DescendingValueListEnumerator ByDescending() => new(Last); + var index = 0; + foreach (var t in this) + { + arr[index++] = t; + } + + return arr; + } public ref struct ValueListEnumerator { - private T _head; + private bool _started; private T _current; + private ref readonly ValueLinkList _linkList; + private int _version; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ValueListEnumerator(T head) + public ValueListEnumerator(in ValueLinkList linkList) { - _head = head; + _linkList = ref linkList; + _started = false; _current = null; + _version = 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { - if (_current == null) + if (!_started) { - _current = _head; - _head = null; + _current = _linkList._first; + _started = true; + _version = _linkList.Version; + } + else if (_linkList.Version != _version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); } else { @@ -415,23 +442,32 @@ public struct ValueLinkList where T : class, IValueLinkListNode public ref struct DescendingValueListEnumerator { - private T _tail; + private bool _started; private T _current; + private ref readonly ValueLinkList _linkList; + private int _version; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public DescendingValueListEnumerator(T head) + public DescendingValueListEnumerator(in ValueLinkList linkList) { - _tail = head; + _linkList = ref linkList; + _started = false; _current = null; + _version = 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { - if (_current == null) + if (!_started) { - _current = _tail; - _tail = null; + _current = _linkList._last; + _started = true; + _version = _linkList.Version; + } + else if (_linkList.Version != _version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); } else { @@ -451,3 +487,14 @@ public struct ValueLinkList where T : class, IValueLinkListNode public DescendingValueListEnumerator GetEnumerator() => this; } } + +public static class ValueLinkListExt +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ValueLinkList.ValueListEnumerator GetEnumerator(this in ValueLinkList linkList) + where T : class, IValueLinkListNode => new(in linkList); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ValueLinkList.DescendingValueListEnumerator ByDescending(this in ValueLinkList linkList) + where T : class, IValueLinkListNode => new(in linkList); +} diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs index d34fdaa14..511748427 100644 --- a/Projects/Server/IEntity.cs +++ b/Projects/Server/IEntity.cs @@ -30,6 +30,12 @@ public interface IEntity : IPoint3D, ISerializable bool InRange(Point3D p, int range); void RemoveItem(Item item); + + bool OnMoveOff(Mobile m); + + bool OnMoveOver(Mobile m); + + public void OnMovement(Mobile m, Point3D oldLocation); } public class Entity : IEntity @@ -83,6 +89,14 @@ public class Entity : IEntity { } + public bool OnMoveOff(Mobile m) => true; + + public bool OnMoveOver(Mobile m) => true; + + public void OnMovement(Mobile m, Point3D oldLocation) + { + } + public bool InRange(Point2D p, int range) => p.m_X >= Location.m_X - range && p.m_X <= Location.m_X + range diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 4875452dc..faf042d15 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -2241,7 +2241,6 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt public virtual bool CanDecay() => Decays && Parent == null && Map != Map.Internal; - public virtual bool OnDecay() => CanDecay() && Region.Find(Location, Map).OnDecay(this); @@ -2474,12 +2473,20 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Map.ItemEnumerable GetItemsInRange(int range) => - m_Map == null ? Map.ItemEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); + public Map.ItemAtEnumerable GetItemsAt() => + m_Map == null ? Map.ItemAtEnumerable.Empty : m_Map.GetItemsAt(m_Parent == null ? m_Location : GetWorldLocation()); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Map.ItemEnumerable GetItemsInRange(int range) where T : Item => - m_Map == null ? Map.ItemEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); + public Map.ItemAtEnumerable GetItemsAt() where T : Item => + m_Map == null ? Map.ItemAtEnumerable.Empty : m_Map.GetItemsAt(m_Parent == null ? m_Location : GetWorldLocation()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemBoundsEnumerable GetItemsInRange(int range) => + m_Map == null ? Map.ItemBoundsEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemBoundsEnumerable GetItemsInRange(int range) where T : Item => + m_Map == null ? Map.ItemBoundsEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); public IPooledEnumerable GetMobilesInRange(int range) { diff --git a/Projects/Server/Maps/Map.ItemEnumerator.cs b/Projects/Server/Maps/Map.ItemEnumerator.cs index 5be8c34a7..69fa938cf 100644 --- a/Projects/Server/Maps/Map.ItemEnumerator.cs +++ b/Projects/Server/Maps/Map.ItemEnumerator.cs @@ -13,140 +13,159 @@ * along with this program. If not, see . * *************************************************************************/ -using System.Collections.Generic; +using System; using System.Runtime.CompilerServices; +using Server.Collections; namespace Server; public partial class Map { - private int _iteratingItems; - private readonly List<(MapAction, Point3D, Item)> _delayedItemActions = new(); - - public bool IsIteratingItems - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _iteratingItems > 0; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ItemAtEnumerable GetItemsAt(Point3D p) => GetItemsAt(p); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsAt(Point3D p) => GetItemsInRange(p, 0); + public ItemAtEnumerable GetItemsAt(Point3D p) where T : Item => GetItemsAt(new Point2D(p.X, p.Y)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsAt(Point3D p) where T : Item => GetItemsInRange(p, 0); + public ItemAtEnumerable GetItemsAt(int x, int y) => GetItemsAt(new Point2D(x, y)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(Point3D p) => GetItemsInRange(p); + public ItemAtEnumerable GetItemsAt(int x, int y) where T : Item => GetItemsAt(new Point2D(x, y)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(Point3D p, int range) => GetItemsInRange(p, range); + public ItemAtEnumerable GetItemsAt(Point2D p) => GetItemsAt(p); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(Point3D p) where T : Item => GetItemsInRange(p, Core.GlobalMaxUpdateRange); + public ItemAtEnumerable GetItemsAt(Point2D p) where T : Item => new(this, p); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(Point3D p, int range) where T : Item => + public ItemBoundsEnumerable GetItemsInRange(Point3D p) => GetItemsInRange(p); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ItemBoundsEnumerable GetItemsInRange(Point3D p, int range) => GetItemsInRange(p, range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ItemBoundsEnumerable GetItemsInRange(Point3D p) where T : Item => GetItemsInRange(p, Core.GlobalMaxUpdateRange); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ItemBoundsEnumerable GetItemsInRange(Point3D p, int range) where T : Item => GetItemsInRange(p.m_X, p.m_Y, range); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsAt(Point2D p) => GetItemsInRange(p, 0); + public ItemBoundsEnumerable GetItemsInRange(Point2D p) => GetItemsInRange(p); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsAt(Point2D p) where T : Item => GetItemsInRange(p, 0); + public ItemBoundsEnumerable GetItemsInRange(Point2D p, int range) => GetItemsInRange(p, range); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(Point2D p) => GetItemsInRange(p); + public ItemBoundsEnumerable GetItemsInRange(Point2D p) where T : Item => GetItemsInRange(p, Core.GlobalMaxUpdateRange); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(Point2D p, int range) => GetItemsInRange(p, range); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(Point2D p) where T : Item => GetItemsInRange(p, Core.GlobalMaxUpdateRange); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(Point2D p, int range) where T : Item => + public ItemBoundsEnumerable GetItemsInRange(Point2D p, int range) where T : Item => GetItemsInRange(p.m_X, p.m_Y, range); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsAt(int x, int y) => GetItemsAt(x, y); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsAt(int x, int y) where T : Item => GetItemsInRange(x, y, 0); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInRange(int x, int y, int range) where T : Item => + public ItemBoundsEnumerable GetItemsInRange(int x, int y, int range) where T : Item => GetItemsInBounds(new Rectangle2D(x - range, y - range, range * 2 + 1, range * 2 + 1)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds(bounds); + public ItemBoundsEnumerable GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds(bounds); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ItemEnumerable GetItemsInBounds(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item => + public ItemBoundsEnumerable GetItemsInBounds(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item => new(this, bounds, makeBoundsInclusive); - private void BeginIteratingItems() + public ref struct ItemAtEnumerable where T : Item { -#if THREADGUARD - if (Thread.CurrentThread != Core.Thread) - { - Utility.PushColor(ConsoleColor.Red); - Console.WriteLine($"Iterating through items on {this} from an invalid thread!"); - Console.WriteLine(new StackTrace()); - Utility.PopColor(); - return; - } -#endif + public static ItemAtEnumerable Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(); + } - _iteratingItems++; + private Map _map; + private Point2D _location; + + public ItemAtEnumerable(Map map, Point2D loc) + { + _map = map; + _location = loc; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ItemAtEnumerator GetEnumerator() => new(_map, _location); } - private void EndIteratingItems() + public ref struct ItemAtEnumerator where T : Item { -#if THREADGUARD - if (Thread.CurrentThread != Core.Thread) - { - Utility.PushColor(ConsoleColor.Red); - Console.WriteLine($"Iterating through items on {this} from an invalid thread!"); - Console.WriteLine(new StackTrace()); - Utility.PopColor(); - return; - } -#endif + private bool _started; + private Point2D _location; + private ref readonly ValueLinkList _linkList; + private int _version; + private T _current; - _iteratingItems--; - - // Finished iterating, check for deferred actions - if (_iteratingItems == 0 && _delayedItemActions.Count > 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ItemAtEnumerator(Map map, Point2D loc) { - foreach (var (a, p, i) in _delayedItemActions) + _started = false; + _location = loc; + _linkList = ref map.GetRealSector(loc.m_X, loc.m_Y).Items; + _version = 0; + _current = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + ref var loc = ref _location; + Item current; + + if (!_started) { - switch (a) + current = _linkList._first; + _started = true; + _version = _linkList.Version; + + if (current is T { Deleted: false, Parent: null } o && o.X == loc.m_X && o.Y == loc.m_Y) { - case MapAction.Enter: - { - OnEnter(p, i); - break; - } - case MapAction.Leave: - { - OnLeave(p, i); - break; - } - case MapAction.Move: - { - OnMove(p, i); - break; - } + _current = o; + return true; + } + } + else if (_linkList.Version != _version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + else + { + current = _current; + } + + while (current != null) + { + current = current.Next; + + if (current is T { Deleted: false, Parent: null } o && o.X == loc.m_X && o.Y == loc.m_Y) + { + _current = o; + return true; } } - _delayedItemActions.Clear(); + return false; + } + + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _current; } } - public ref struct ItemEnumerable where T : Item + public ref struct ItemBoundsEnumerable where T : Item { - public static ItemEnumerable Empty + public static ItemBoundsEnumerable Empty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(null, Rectangle2D.Empty, false); @@ -156,14 +175,13 @@ public partial class Map private Rectangle2D _bounds; private bool _makeBoundsInclusive; - public ItemEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive) + public ItemBoundsEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive) { _map = map; _bounds = bounds; _makeBoundsInclusive = makeBoundsInclusive; } - // The enumerator MUST be disposed. Not disposing it will damage the sector irreparably. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ItemEnumerator GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive); } @@ -178,6 +196,9 @@ public partial class Map private int _currentSectorX; private int _currentSectorY; + + private ref readonly ValueLinkList _linkList; + private int _currentVersion; private T _current; [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -199,8 +220,6 @@ public partial class Map // We start the X sector one short because it gets incremented immediately in MoveNext() _currentSectorX = _sectorStartX - 1; _currentSectorY = _sectorStartY; - - _map.BeginIteratingItems(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -242,7 +261,14 @@ public partial class Map return false; } - current = map.GetRealSector(currentSectorX, currentSectorY).Items.First; + _linkList = ref map.GetRealSector(currentSectorX, currentSectorY).Items; + _currentVersion = _linkList.Version; + current = _linkList._first; + } + + if (_linkList.Version != _currentVersion) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); } if (current is T { Deleted: false, Parent: null } o && bounds.Contains(o.Location)) @@ -253,9 +279,6 @@ public partial class Map } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() => _map.EndIteratingItems(); - public T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Projects/Server/Maps/Map.cs b/Projects/Server/Maps/Map.cs index 99f2b2928..6a5d5be1a 100644 --- a/Projects/Server/Maps/Map.cs +++ b/Projects/Server/Maps/Map.cs @@ -627,12 +627,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public void OnEnter(Item item) { - if (IsIteratingItems) - { - _delayedItemActions.Add((MapAction.Enter, item.Location, item)); - return; - } - OnEnter(item.Location, item); } @@ -669,12 +663,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public void OnLeave(Item item) { - if (IsIteratingItems) - { - _delayedItemActions.Add((MapAction.Leave, item.Location, item)); - return; - } - OnLeave(item.Location, item); } @@ -765,12 +753,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa if (oldSector != newSector) { - if (IsIteratingItems) - { - _delayedItemActions.Add((MapAction.Move, item.Location, item)); - return; - } - oldSector.OnLeave(item); newSector.OnEnter(item); } @@ -787,12 +769,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa if (oldStart != start || oldEnd != end) { - if (IsIteratingItems) - { - _delayedItemActions.Add((MapAction.Move, oldLocation, item)); - return; - } - RemoveMulti(m, oldStart, oldEnd); AddMulti(m, start, end); } @@ -1038,8 +1014,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public Sector GetSector(Point2D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); - // public Sector GetSector(IPoint2D p) => InternalGetSector(p.X >> SectorShift, p.Y >> SectorShift); - public Sector GetSector(int x, int y) => InternalGetSector(x >> SectorShift, y >> SectorShift); public Sector GetRealSector(int x, int y) => InternalGetSector(x, y); @@ -1459,14 +1433,6 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa return false; } - private enum MapAction - { - None, - Enter, - Leave, - Move - } - public class Sector { // TODO: Can we avoid this? @@ -1495,7 +1461,7 @@ public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsa public List Mobiles => _mobiles ?? m_DefaultMobileList; - public ref ValueLinkList Items => ref _items; + internal ref readonly ValueLinkList Items => ref _items; public List Clients => _clients ?? m_DefaultClientList; diff --git a/Projects/Server/Maps/PooledEnumeration.cs b/Projects/Server/Maps/PooledEnumeration.cs index 8f5eeb818..ef718e30b 100644 --- a/Projects/Server/Maps/PooledEnumeration.cs +++ b/Projects/Server/Maps/PooledEnumeration.cs @@ -17,6 +17,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using Server.Collections; using Server.Items; using Server.Network; diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 60a4dd9e7..4b3ff0eab 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -194,9 +194,6 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro private static readonly TimeSpan ExpireCombatantDelay = TimeSpan.FromMinutes(1.0); private static readonly TimeSpan ExpireAggressorsDelay = TimeSpan.FromSeconds(5.0); - private static readonly List m_MoveList = new(); - private static readonly List m_MoveClientList = new(); - private static readonly object m_GhostMutateContext = new(); private static readonly List m_Hears = new(); @@ -3567,6 +3564,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro public bool InLOS(Point3D target) => !Deleted && m_Map != null && (m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); + public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y; + public bool BeginAction() => BeginAction(typeof(T)); public bool BeginAction(object toLock) @@ -4154,12 +4153,20 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro if (oldSector != newSector) { + using var queue = PooledRefQueue.Create(2048); for (var i = 0; i < oldSector.Mobiles.Count; ++i) { var m = oldSector.Mobiles[i]; - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && - !m.OnMoveOff(this)) + if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z) + { + queue.Enqueue(m); + } + } + + while (queue.Count > 0) + { + if (!queue.Dequeue().OnMoveOff(this)) { return false; } @@ -4168,8 +4175,15 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro foreach (var item in oldSector.Items) { if (item.AtWorldPoint(oldX, oldY) && - (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && - !item.OnMoveOff(this)) + (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z)) + { + queue.Enqueue(item); + } + } + + while (queue.Count > 0) + { + if (!queue.Dequeue().OnMoveOff(this)) { return false; } @@ -4179,7 +4193,15 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro { var m = newSector.Mobiles[i]; - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) + if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z) + { + queue.Enqueue(m); + } + } + + while (queue.Count > 0) + { + if (!queue.Dequeue().OnMoveOver(this)) { return false; } @@ -4188,8 +4210,15 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro foreach (var item in newSector.Items) { if (item.AtWorldPoint(x, y) && - (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && - !item.OnMoveOver(this)) + (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z)) + { + queue.Enqueue(item); + } + } + + while (queue.Count > 0) + { + if (!queue.Dequeue().OnMoveOver(this)) { return false; } @@ -4197,17 +4226,36 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } else { + using var queue = PooledRefQueue<(IEntity, byte)>.Create(2048); for (var i = 0; i < oldSector.Mobiles.Count; ++i) { var m = oldSector.Mobiles[i]; - - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && - !m.OnMoveOff(this)) + byte flag; + if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z) { - return false; + flag = 1; + } + else + { + flag = 0; } - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) + if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z) + { + flag += 2; + } + + if (flag > 0) + { + queue.Enqueue((m, flag)); + } + } + + while (queue.Count > 0) + { + var (entity, flag) = queue.Dequeue(); + + if (flag > 0 && !entity.OnMoveOff(this) || flag > 1 && !entity.OnMoveOver(this)) { return false; } @@ -4215,16 +4263,34 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro foreach (var item in oldSector.Items) { + byte flag; if (item.AtWorldPoint(oldX, oldY) && - (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && - !item.OnMoveOff(this)) + (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z)) { - return false; + flag = 1; + } + else + { + flag = 0; } if (item.AtWorldPoint(x, y) && - (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && - !item.OnMoveOver(this)) + (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z)) + { + flag += 2; + } + + if (flag > 0) + { + queue.Enqueue((item, flag)); + } + } + + while (queue.Count > 0) + { + var (entity, flag) = queue.Dequeue(); + + if (flag > 0 && !entity.OnMoveOff(this) || flag > 1 && !entity.OnMoveOver(this)) { return false; } @@ -4281,6 +4347,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro if (m_Map != null) { + using var moveQueue = PooledRefQueue.Create(2048); + using var moveClientQueue = PooledRefQueue.Create(2048); var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); foreach (var o in eable) @@ -4294,54 +4362,39 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro { if (mob.NetState != null) { - m_MoveClientList.Add(mob); + moveClientQueue.Enqueue(mob); } - m_MoveList.Add(mob); + moveQueue.Enqueue(mob); } else if (o is Item item && item.HandlesOnMovement) { - m_MoveList.Add(item); + moveQueue.Enqueue(item); } } - const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength; - const int width = OutgoingMobilePackets.MobileMovingPacketLength; - - var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width); - - foreach (var m in m_MoveClientList) + if (moveClientQueue.Count > 0) { - var ns = m.NetState; + const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength; + const int width = OutgoingMobilePackets.MobileMovingPacketLength; - if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) + var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width); + + while (moveClientQueue.Count > 0) { - ns.SendMobileMovingUsingCache(mobileMovingCache, m, this); + var m = moveClientQueue.Dequeue(); + var ns = m.NetState; + + if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) + { + ns.SendMobileMovingUsingCache(mobileMovingCache, m, this); + } } } - for (var i = 0; i < m_MoveList.Count; ++i) + while (moveQueue.Count > 0) { - var o = m_MoveList[i]; - - if (o is Mobile mobile) - { - mobile.OnMovement(this, oldLocation); - } - else if (o is Item item) - { - item.OnMovement(this, oldLocation); - } - } - - if (m_MoveList.Count > 0) - { - m_MoveList.Clear(); - } - - if (m_MoveClientList.Count > 0) - { - m_MoveClientList.Clear(); + moveQueue.Dequeue().OnMovement(this, oldLocation); } } @@ -8059,11 +8112,19 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Map.ItemEnumerable GetItemsInRange(int range) => GetItemsInRange(range); + public Map.ItemAtEnumerable GetItemsAt() => + m_Map == null ? Map.ItemAtEnumerable.Empty : m_Map.GetItemsAt(m_Location); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Map.ItemEnumerable GetItemsInRange(int range) where T : Item => - m_Map == null ? Map.ItemEnumerable.Empty : m_Map.GetItemsInRange(m_Location, range); + public Map.ItemAtEnumerable GetItemsAt() where T : Item => + m_Map == null ? Map.ItemAtEnumerable.Empty : m_Map.GetItemsAt(m_Location); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemBoundsEnumerable GetItemsInRange(int range) => GetItemsInRange(range); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemBoundsEnumerable GetItemsInRange(int range) where T : Item => + m_Map == null ? Map.ItemBoundsEnumerable.Empty : m_Map.GetItemsInRange(m_Location, range); public IPooledEnumerable GetObjectsInRange(int range) => m_Map?.GetObjectsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable.Instance; diff --git a/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs b/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs index fea3077e5..2f99d037a 100644 --- a/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs +++ b/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text.Json.Serialization; +using Server.Collections; using Server.Items; using Server.Json; using Server.Network; @@ -121,16 +122,20 @@ namespace Server.Commands public static int DeleteTeleporters(WorldLocation worldLocation) { - var count = 0; - foreach (var item in worldLocation.Map.GetItemsInRange(worldLocation, 0)) + using var queue = PooledRefQueue.Create(); + foreach (var item in worldLocation.Map.GetItemsAt(worldLocation)) { if (item is not (KeywordTeleporter or SkillTeleporter) && IsWithinZ(item.Z - worldLocation.Z)) { - count++; - item.Delete(); + queue.Enqueue(item); } } + var count = queue.Count; + while (queue.Count > 0) + { + queue.Dequeue().Delete(); + } return count; } diff --git a/Projects/UOContent/Commands/SignParser.cs b/Projects/UOContent/Commands/SignParser.cs index 704c38f1b..c98df5159 100644 --- a/Projects/UOContent/Commands/SignParser.cs +++ b/Projects/UOContent/Commands/SignParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Server.Collections; using Server.Items; using Server.Network; @@ -8,8 +9,6 @@ namespace Server.Commands { public static class SignParser { - private static readonly Queue m_ToDelete = new(); - public static void Initialize() { CommandSystem.Register("SignGen", AccessLevel.Administrator, SignGen_OnCommand); @@ -91,17 +90,18 @@ namespace Server.Commands public static void Add_Static(int itemID, Point3D location, Map map, string name) { + using var queue = PooledRefQueue.Create(); foreach (var item in map.GetItemsInRange(location, 0)) { if (item is Sign && item.Z == location.Z && item.ItemID == itemID) { - m_ToDelete.Enqueue(item); + queue.Enqueue(item); } } - while (m_ToDelete.Count > 0) + while (queue.Count > 0) { - m_ToDelete.Dequeue().Delete(); + queue.Dequeue().Delete(); } Item sign; diff --git a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs index 29dd6d84e..483136a1c 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs @@ -468,7 +468,7 @@ namespace Server.Engines.ConPVP if (landTile.ID == 0x244 && statics.Length == 0) // 0x244 = invalid land tile { var empty = true; - foreach (var item in Map.GetItemsInRange(point, 0)) + foreach (var item in Map.GetItemsAt(point)) { if (item != this) { @@ -660,7 +660,7 @@ namespace Server.Engines.ConPVP } } - foreach (var item in GetItemsInRange(0)) + foreach (var item in GetItemsAt()) { if (item.Visible && item != this) { diff --git a/Projects/UOContent/Engines/Doom/GenGauntlet.cs b/Projects/UOContent/Engines/Doom/GenGauntlet.cs index 04f0baebd..d177c4fc4 100644 --- a/Projects/UOContent/Engines/Doom/GenGauntlet.cs +++ b/Projects/UOContent/Engines/Doom/GenGauntlet.cs @@ -180,7 +180,7 @@ namespace Server.Engines.Doom public static void RemoveDoorSet(int x, int y) { var loc = new Point3D(x, y, -1); - foreach (var item in Map.Malas.GetItemsInRange(loc, 0)) + foreach (var item in Map.Malas.GetItemsAt(loc)) { if (item is BaseDoor door) { @@ -226,9 +226,9 @@ namespace Server.Engines.Doom public static void RemoveItem(int x, int y, int z = -1) where T : Item { - foreach (var item in Map.Malas.GetItemsInRange(new Point3D(x, y, z), 0)) + foreach (var item in Map.Malas.GetItemsAt(x, y)) { - if (item is T) + if (item is T && z == -1 || item.Z == z) { item.Delete(); break; diff --git a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs index 58501ba18..29a803fde 100644 --- a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs +++ b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs @@ -197,7 +197,7 @@ public partial class LeverPuzzleController : Item [Usage("GenLeverPuzzle"), Description("Generates lamp room and lever puzzle in doom.")] public static void GenLampPuzzle_OnCommand(CommandEventArgs e) { - foreach (var item in Map.Malas.GetItemsInRange(lp_Center, 0)) + foreach (var item in Map.Malas.GetItemsAt(lp_Center)) { if (item is LeverPuzzleController) { diff --git a/Projects/UOContent/Engines/Factions/Core/Generator.cs b/Projects/UOContent/Engines/Factions/Core/Generator.cs index dc26bb0b1..a94cc2d22 100644 --- a/Projects/UOContent/Engines/Factions/Core/Generator.cs +++ b/Projects/UOContent/Engines/Factions/Core/Generator.cs @@ -78,7 +78,7 @@ namespace Server.Factions private static bool CheckExistence(Point3D loc, Map facet, Type type) { - foreach (var item in facet.GetItemsInRange(loc, 0)) + foreach (var item in facet.GetItemsAt(loc)) { if (type.IsInstanceOfType(item)) { diff --git a/Projects/UOContent/Engines/Factions/Items/Traps/BaseFactionTrap.cs b/Projects/UOContent/Engines/Factions/Items/Traps/BaseFactionTrap.cs index baa605d1c..05ba279c4 100644 --- a/Projects/UOContent/Engines/Factions/Items/Traps/BaseFactionTrap.cs +++ b/Projects/UOContent/Engines/Factions/Items/Traps/BaseFactionTrap.cs @@ -114,7 +114,7 @@ namespace Server.Factions if (Core.ML) { - foreach (var item in m.GetItemsInRange(p, 0)) + foreach (var item in m.GetItemsAt(p)) { if (item is BaseFactionTrap trap && trap.Faction == Faction) { diff --git a/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs b/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs index 947080454..99ccdd10a 100644 --- a/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs +++ b/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs @@ -307,7 +307,7 @@ namespace Server.Engines.Harvest return false; } - foreach (var i in m.GetItemsInRange(0)) + foreach (var i in m.GetItemsAt()) { if (i.StackWith(m, i, false)) { diff --git a/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs b/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs index febd01cbb..4c1ea7639 100644 --- a/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs +++ b/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs @@ -15,9 +15,9 @@ namespace Server.Commands public static bool FindMorphItem(int x, int y, int z, int inactiveItemID, int activeItemID) { var found = false; - foreach (var item in Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0)) + foreach (var morphItem in Map.Felucca.GetItemsAt(x, y)) { - if (item is MorphItem morphItem && morphItem.Z == z && morphItem.InactiveItemId == inactiveItemID && morphItem.ActiveItemId == activeItemID) + if (morphItem.Z == z && morphItem.InactiveItemId == inactiveItemID && morphItem.ActiveItemId == activeItemID) { found = true; break; @@ -30,9 +30,9 @@ namespace Server.Commands public static bool FindEffectController(int x, int y, int z) { var found = false; - foreach (var item in Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0)) + foreach (var item in Map.Felucca.GetItemsAt(x, y)) { - if (item is EffectController && item.Z == z) + if (item.Z == z) { found = true; break; @@ -44,7 +44,7 @@ namespace Server.Commands public static T TryCreateItem(int x, int y, int z, T srcItem) where T : Item { - foreach (var item in Map.Felucca.GetItemsInBounds(new Rectangle2D(x, y, 1, 1))) + foreach (var item in Map.Felucca.GetItemsAt(x, y)) { srcItem.Delete(); return item; diff --git a/Projects/UOContent/Engines/ML Quests/MLQuest.cs b/Projects/UOContent/Engines/ML Quests/MLQuest.cs index 46789014d..e714cae40 100644 --- a/Projects/UOContent/Engines/ML Quests/MLQuest.cs +++ b/Projects/UOContent/Engines/ML Quests/MLQuest.cs @@ -267,7 +267,7 @@ namespace Server.Engines.MLQuests var name = $"MLQS-{GetType().Name}"; using var queue = PooledRefQueue.Create(); - foreach (var item in map.GetItemsInRange(loc, 0)) + foreach (var item in map.GetItemsAt(loc)) { // This predates GUIDs. Let's build these spawners and export them so we can delete this code! if (item is BaseSpawner spawner && spawner.Name == name) @@ -288,7 +288,7 @@ namespace Server.Engines.MLQuests public static void PutDeco(Item deco, Point3D loc, Map map) { using var queue = PooledRefQueue.Create(); - foreach (var item in map.GetItemsInRange(loc, 0)) + foreach (var item in map.GetItemsAt(loc)) { if (item.ItemID == deco.ItemID && item.Z == loc.Z) { diff --git a/Projects/UOContent/Engines/Pathing/Movement.cs b/Projects/UOContent/Engines/Pathing/Movement.cs index 91afe95d5..79a3c411a 100644 --- a/Projects/UOContent/Engines/Pathing/Movement.cs +++ b/Projects/UOContent/Engines/Pathing/Movement.cs @@ -22,8 +22,6 @@ namespace Server.Movement private readonly List[] _pools = { new(), new(), new(), new() }; - private readonly HashSet _sectors = new(); - private MovementImpl() { } @@ -83,149 +81,55 @@ namespace Server.Movement var checkMobs = (m as BaseCreature)?.Controlled == false && (xForward != _goal.X || yForward != _goal.Y); - if (checkDiagonals) + foreach (var entity in map.GetObjectsInRange(loc, 1)) { - var sectorStart = map.GetSector(xStart, yStart); - var sectorForward = map.GetSector(xForward, yForward); - var sectorLeft = map.GetSector(xLeft, yLeft); - var sectorRight = map.GetSector(xRight, yRight); - - _sectors.Add(sectorStart); - - _sectors.Add(sectorForward); - _sectors.Add(sectorLeft); - _sectors.Add(sectorRight); - - foreach (var sector in _sectors) - { - foreach (var item in sector.Items) - { - if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) - { - continue; - } - - if (!item.ItemData[reqFlags]) - { - continue; - } - - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) - { - continue; - } - - if (sector == sectorStart && item.AtWorldPoint(xStart, yStart)) - { - itemsStart.Add(item); - } - else if (sector == sectorForward && item.AtWorldPoint(xForward, yForward)) - { - itemsForward.Add(item); - } - else if (sector == sectorLeft && item.AtWorldPoint(xLeft, yLeft)) - { - itemsLeft.Add(item); - } - else if (sector == sectorRight && item.AtWorldPoint(xRight, yRight)) - { - itemsRight.Add(item); - } - } - - if (checkMobs) - { - for (var j = 0; j < sector.Mobiles.Count; ++j) - { - var mob = sector.Mobiles[j]; - - if (sector == sectorForward && mob.X == xForward && mob.Y == yForward) - { - mobsForward.Add(mob); - } - else if (sector == sectorLeft && mob.X == xLeft && mob.Y == yLeft) - { - mobsLeft.Add(mob); - } - else if (sector == sectorRight && mob.X == xRight && mob.Y == yRight) - { - mobsRight.Add(mob); - } - } - } - } - - _sectors.Clear(); - } - else - { - var sectorStart = map.GetSector(xStart, yStart); - var sectorForward = map.GetSector(xForward, yForward); - var sectorStartIsForward = sectorStart == sectorForward; - - if (!sectorStartIsForward) - { - foreach (var item in sectorForward.Items) - { - if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) - { - continue; - } - - if (!item.ItemData[reqFlags]) - { - continue; - } - - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) - { - continue; - } - - if (item.AtWorldPoint(xForward, yForward)) - { - itemsForward.Add(item); - } - } - } - - foreach (var item in sectorStart.Items) + if (entity is Item item) { if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) { continue; } - if (!item.ItemData[reqFlags]) + if (!item.ItemData[reqFlags] || item.ItemID > TileData.MaxItemValue || item.Parent != null) { continue; } - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) + if (item is BaseMulti) { continue; } - if (item.AtWorldPoint(xStart, yStart)) + if (item.AtPoint(xStart, yStart)) { itemsStart.Add(item); } - else if (sectorStartIsForward && item.AtWorldPoint(xForward, yForward)) + else if (item.AtPoint(xForward, yForward)) { itemsForward.Add(item); } - } - - if (checkMobs) - { - for (var i = 0; i < sectorForward.Mobiles.Count; ++i) + else if (checkDiagonals && item.AtPoint(xLeft, yLeft)) { - var mob = sectorForward.Mobiles[i]; - - if (mob.X == xForward && mob.Y == yForward) - { - mobsForward.Add(mob); - } + itemsLeft.Add(item); + } + else if (checkDiagonals && item.AtPoint(xRight, yRight)) + { + itemsRight.Add(item); + } + } + else if (checkMobs && entity is Mobile mob) + { + if (mob.AtPoint(xForward, yForward)) + { + mobsForward.Add(mob); + } + else if (checkDiagonals && mob.AtPoint(xLeft, yLeft)) + { + mobsLeft.Add(mob); + } + else if (checkDiagonals && mob.AtPoint(xRight, yRight)) + { + mobsRight.Add(mob); } } } @@ -274,7 +178,7 @@ namespace Server.Movement public bool CheckMovement(Mobile m, Direction d, out int newZ) => CheckMovement(m, m.Map, m.Location, d, out newZ); - private bool IsOk( + private static bool IsOk( bool ignoreDoors, bool ignoreSpellFields, int ourZ, int ourTop, StaticTile[] tiles, List items ) { @@ -326,7 +230,7 @@ namespace Server.Movement return true; } - private bool Check( + private static bool Check( Map map, Mobile m, List items, @@ -596,7 +500,7 @@ namespace Server.Movement private static bool CanMoveOver(Mobile m, Mobile t) => !t.Alive || !m.Alive || t.IsDeadBondedPet || m.IsDeadBondedPet || t.Hidden && t.AccessLevel > AccessLevel.Player; - private void GetStartZ(Mobile m, Map map, Point3D loc, List itemList, out int zLow, out int zTop) + private static void GetStartZ(Mobile m, Map map, Point3D loc, List itemList, out int zLow, out int zTop) { int xCheck = loc.X, yCheck = loc.Y; @@ -694,7 +598,7 @@ namespace Server.Movement } } - public void Offset(Direction d, ref int x, ref int y) + public static void Offset(Direction d, ref int x, ref int y) { switch (d & Direction.Mask) { diff --git a/Projects/UOContent/Engines/Quests/The Summoning/Mobiles/Victoria.cs b/Projects/UOContent/Engines/Quests/The Summoning/Mobiles/Victoria.cs index 69df4fed4..ad1c54305 100644 --- a/Projects/UOContent/Engines/Quests/The Summoning/Mobiles/Victoria.cs +++ b/Projects/UOContent/Engines/Quests/The Summoning/Mobiles/Victoria.cs @@ -29,13 +29,9 @@ public partial class Victoria : BaseQuester if (_altar?.Deleted != false || _altar.Map != Map || !Utility.InRange(_altar.Location, Location, AltarRange)) { - foreach (var item in GetItemsInRange(AltarRange)) + foreach (var altar in GetItemsInRange(AltarRange)) { - if (item is SummoningAltar altar) - { - _altar = altar; - break; - } + _altar = altar; } } diff --git a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs index c81ead0e1..74deb41cc 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs @@ -147,7 +147,7 @@ namespace Server.Engines.Spawners // Delete all spawners at this location. // Probably shouldn't do this outside of migrations? Is there a better way to find/fix spawners? - foreach (var spawner in map.GetItemsInRange(location, 0)) + foreach (var spawner in map.GetItemsAt(location)) { if (spawner.GetType() == type) { diff --git a/Projects/UOContent/Holiday Stuff/Christmas/2010/Addons/FireFliesDeed.cs b/Projects/UOContent/Holiday Stuff/Christmas/2010/Addons/FireFliesDeed.cs index 2e88a0a9b..2ce94fa80 100644 --- a/Projects/UOContent/Holiday Stuff/Christmas/2010/Addons/FireFliesDeed.cs +++ b/Projects/UOContent/Holiday Stuff/Christmas/2010/Addons/FireFliesDeed.cs @@ -199,9 +199,9 @@ namespace Server.Items bool isclear = true; - foreach (Item item in Map.Malas.GetItemsInRange(p3d, 0)) + foreach (Fireflies fireflies in Map.Malas.GetItemsAt(p3d)) { - if (item is Fireflies) + if (fireflies.Z == p3d.Z) { isclear = false; } diff --git a/Projects/UOContent/Items/Addons/SHTeleporter.cs b/Projects/UOContent/Items/Addons/SHTeleporter.cs index 9d6f936c2..9dbd7303d 100644 --- a/Projects/UOContent/Items/Addons/SHTeleporter.cs +++ b/Projects/UOContent/Items/Addons/SHTeleporter.cs @@ -267,7 +267,7 @@ namespace Server.Items public static SHTeleporter FindSHTeleporter(Map map, Point3D p) { - foreach (var teleporter in map.GetItemsInRange(p, 0)) + foreach (var teleporter in map.GetItemsAt(p)) { if (teleporter.Z == p.Z) { diff --git a/Projects/UOContent/Items/Containers/MarkContainer.cs b/Projects/UOContent/Items/Containers/MarkContainer.cs index 78e43f920..c193f967e 100644 --- a/Projects/UOContent/Items/Containers/MarkContainer.cs +++ b/Projects/UOContent/Items/Containers/MarkContainer.cs @@ -130,7 +130,7 @@ public partial class MarkContainer : LockableContainer private static bool FindMarkContainer(Point3D p, Map map) { - foreach (var item in map.GetItemsInRange(p, 0)) + foreach (var item in map.GetItemsAt(p)) { if (item.Z == p.Z) { diff --git a/Projects/UOContent/Items/Misc/OilFlask.cs b/Projects/UOContent/Items/Misc/OilFlask.cs index 35b16c664..c890f0861 100644 --- a/Projects/UOContent/Items/Misc/OilFlask.cs +++ b/Projects/UOContent/Items/Misc/OilFlask.cs @@ -50,7 +50,7 @@ public partial class OilFlask : Item { var didStack = false; - foreach (var i in from.GetItemsInRange(0)) + foreach (var i in from.GetItemsAt()) { if (i.StackWith(from, this, false)) { diff --git a/Projects/UOContent/Items/Misc/WarningItem.cs b/Projects/UOContent/Items/Misc/WarningItem.cs index 5194a0926..5d0b491a4 100644 --- a/Projects/UOContent/Items/Misc/WarningItem.cs +++ b/Projects/UOContent/Items/Misc/WarningItem.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; using ModernUO.Serialization; +using Server.Collections; namespace Server.Items; @@ -83,19 +83,18 @@ public partial class WarningItem : Item if (NeighborRange >= 0) { - var list = new List(); - - foreach (var item in GetItemsInRange(NeighborRange)) + using var queue = PooledRefQueue.Create(); + foreach (var warningItem in GetItemsInRange(NeighborRange)) { - if (item != this && item is WarningItem warningItem) + if (warningItem != this) { - list.Add(warningItem); + queue.Enqueue(warningItem); } } - for (var i = 0; i < list.Count; i++) + while (queue.Count > 0) { - list[i].Broadcast(triggerer); + queue.Dequeue().Broadcast(triggerer); } } diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index dda4cbd3d..5a10ddac4 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Collections; using Server.ContextMenus; using Server.Engines.Quests; using Server.Engines.Quests.Necro; @@ -94,7 +95,6 @@ public abstract class BaseAI SkillName.Meditation }; - private static readonly Queue m_Obstacles = new(); protected ActionType m_Action; public BaseCreature m_Mobile; @@ -2156,8 +2156,9 @@ public abstract class BaseAI int x = m_Mobile.X, y = m_Mobile.Y; Movement.Movement.Offset(d, ref x, ref y); + using var queue = PooledRefQueue.Create(); var destroyables = 0; - foreach (var item in map.GetItemsInRange(new Point3D(x, y, m_Mobile.Location.Z), 1)) + foreach (var item in map.GetItemsInRange(new Point2D(x, y), 1)) { if (canOpenDoors && item is BaseDoor door && door.Z + door.ItemData.Height > m_Mobile.Z && m_Mobile.Z + 16 > door.Z) @@ -2169,7 +2170,7 @@ public abstract class BaseAI if (!door.Locked || !door.UseLocks()) { - m_Obstacles.Enqueue(door); + queue.Enqueue(door); } if (!canDestroyObstacles) @@ -2185,7 +2186,7 @@ public abstract class BaseAI continue; } - m_Obstacles.Enqueue(item); + queue.Enqueue(item); ++destroyables; } } @@ -2195,14 +2196,14 @@ public abstract class BaseAI Effects.PlaySound(new Point3D(x, y, m_Mobile.Z), m_Mobile.Map, 0x3B3); } - if (m_Obstacles.Count > 0) + if (queue.Count > 0) { blocked = false; // retry movement } - while (m_Obstacles.Count > 0) + while (queue.Count > 0) { - var item = m_Obstacles.Dequeue(); + var item = queue.Dequeue(); if (item is BaseDoor door) { @@ -2238,7 +2239,7 @@ public abstract class BaseAI if (check.Movable && check.ItemData.Impassable && cont.Z + check.ItemData.Height > m_Mobile.Z) { - m_Obstacles.Enqueue(check); + queue.Enqueue(check); } } diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs b/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs index cddab9e31..6df9e3d77 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs @@ -118,7 +118,7 @@ namespace Server.Mobiles p = GetSpawnPosition(2); var atLocation = false; - foreach (var item in Map.GetItemsInRange(p, 0)) + foreach (var item in Map.GetItemsAt(p)) { atLocation = true; break; diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs b/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs index c54756028..1a241281d 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs @@ -235,7 +235,7 @@ namespace Server.Mobiles p = GetSpawnPosition(2); var atLocation = false; - foreach (var item in Map.GetItemsInRange(p, 0)) + foreach (var item in Map.GetItemsAt(p)) { atLocation = true; break; diff --git a/Projects/UOContent/Multis/Houses/HousePlacement.cs b/Projects/UOContent/Multis/Houses/HousePlacement.cs index 206a3cd62..e21a1c046 100644 --- a/Projects/UOContent/Multis/Houses/HousePlacement.cs +++ b/Projects/UOContent/Multis/Houses/HousePlacement.cs @@ -358,11 +358,9 @@ namespace Server.Multis } } - var sector = map.GetSector(borderPoint.X, borderPoint.Y); - - foreach (var item in sector.Items) + foreach (var item in map.GetItemsAt(borderPoint)) { - if (item.X != borderPoint.X || item.Y != borderPoint.Y || item.Movable) + if (item.Movable) { continue; } diff --git a/Projects/UOContent/Spells/Seventh/GateTravel.cs b/Projects/UOContent/Spells/Seventh/GateTravel.cs index f4b0afac6..40b33ca07 100644 --- a/Projects/UOContent/Spells/Seventh/GateTravel.cs +++ b/Projects/UOContent/Spells/Seventh/GateTravel.cs @@ -141,7 +141,7 @@ namespace Server.Spells.Seventh private static bool GateExistsAt(Map map, Point3D loc) { - foreach (var item in map.GetItemsInRange(loc, 0)) + foreach (var item in map.GetItemsAt(loc)) { if (item is Moongate or PublicMoongate) { diff --git a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs index b7a615ef5..e51e7f936 100644 --- a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs +++ b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs @@ -97,7 +97,7 @@ namespace Server.Spells.Spellweaving } } - foreach (var item in map.GetItemsInRange(location, 0)) + foreach (var item in map.GetItemsAt(location)) { if (item.Z + item.ItemData.CalcHeight == location.Z && IsValidTile(item.ItemID)) { diff --git a/Projects/UOContent/Spells/Third/Teleport.cs b/Projects/UOContent/Spells/Third/Teleport.cs index cab15f8c9..b7c21bcf2 100644 --- a/Projects/UOContent/Spells/Third/Teleport.cs +++ b/Projects/UOContent/Spells/Third/Teleport.cs @@ -1,3 +1,4 @@ +using Server.Collections; using Server.Factions; using Server.Items; using Server.Misc; @@ -96,14 +97,21 @@ namespace Server.Spells.Third m.PlaySound(0x1FE); - foreach (var item in m.GetItemsInRange(0)) + using var queue = PooledRefQueue.Create(); + foreach (var item in m.GetItemsAt()) { if (item is ParalyzeFieldSpell.InternalItem or PoisonFieldSpell.InternalItem or FireFieldSpell.FireFieldItem) { - item.OnMoveOver(m); + // Use a queue just in case OnMoveOver changes the item's sector + queue.Enqueue(item); } } + + while (queue.Count > 0) + { + queue.Dequeue().OnMoveOver(m); + } } FinishSequence();