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
<img width="588" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/83ee0b6e-ff4f-4768-9e29-84456e04b1ec">
This commit is contained in:
Kamron Batman 2023-10-26 17:49:15 -07:00 committed by GitHub
parent 658f564f34
commit d919f71149
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 706 additions and 468 deletions

View file

@ -1,3 +1,4 @@
using System;
using Server.Collections; using Server.Collections;
using Xunit; using Xunit;
@ -32,20 +33,35 @@ public class ValueLinkListTests
Assert.True(entity1.OnLinkList); Assert.True(entity1.OnLinkList);
Assert.Equal(1, linkList.Count); 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); var entity2 = new TestEntity(2);
linkList.AddFirst(entity2); linkList.AddFirst(entity2);
Assert.True(entity2.OnLinkList); Assert.True(entity2.OnLinkList);
Assert.Equal(2, linkList.Count); Assert.Equal(2, linkList.Count);
Assert.Equal(entity1, linkList.Last);
Assert.Equal(entity2, entity1.Previous);
Assert.Equal(entity2, linkList.First); Assert.Collection(linkList.ToArray(),
Assert.Equal(entity1, entity2.Next); 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] [Fact]
@ -58,8 +74,14 @@ public class ValueLinkListTests
Assert.True(entity1.OnLinkList); Assert.True(entity1.OnLinkList);
Assert.Equal(1, linkList.Count); 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); var entity2 = new TestEntity(2);
@ -67,11 +89,21 @@ public class ValueLinkListTests
Assert.True(entity2.OnLinkList); Assert.True(entity2.OnLinkList);
Assert.Equal(2, linkList.Count); Assert.Equal(2, linkList.Count);
Assert.Equal(entity1, linkList.First);
Assert.Equal(entity2, entity1.Next);
Assert.Equal(entity2, linkList.Last); Assert.Collection(linkList.ToArray(),
Assert.Equal(entity1, entity2.Previous); 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] [Fact]
@ -87,20 +119,48 @@ public class ValueLinkListTests
Assert.True(entity1.OnLinkList); Assert.True(entity1.OnLinkList);
Assert.True(entity2.OnLinkList); Assert.True(entity2.OnLinkList);
Assert.Equal(2, linkList.Count); 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); var entity3 = new TestEntity(3);
linkList.AddBefore(entity2, entity3); linkList.AddBefore(entity2, entity3);
Assert.True(entity3.OnLinkList); Assert.True(entity3.OnLinkList);
Assert.Equal(3, linkList.Count); Assert.Equal(3, linkList.Count);
Assert.Equal(entity1, entity3.Previous); Assert.Collection(linkList.ToArray(),
Assert.Equal(entity2, entity3.Next); item =>
{
// First and Last should not have changed Assert.Equal(entity1, item);
Assert.Equal(entity1, linkList.First); Assert.Null(item.Previous);
Assert.Equal(entity2, linkList.Last); 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] [Fact]
@ -113,23 +173,31 @@ public class ValueLinkListTests
linkList.AddFirst(entity1); linkList.AddFirst(entity1);
linkList.AddLast(entity2); 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); var entity3 = new TestEntity(3);
linkList.AddAfter(entity1, entity3); linkList.AddAfter(entity1, entity3);
Assert.True(entity3.OnLinkList); Assert.True(entity3.OnLinkList);
Assert.Equal(3, linkList.Count); Assert.Equal(3, linkList.Count);
Assert.Equal(entity1, entity3.Previous); Assert.Collection(linkList.ToArray(),
Assert.Equal(entity2, entity3.Next); item =>
{
// First and Last should not have changed Assert.Equal(entity1, item);
Assert.Equal(entity1, linkList.First); Assert.Null(item.Previous);
Assert.Equal(entity2, linkList.Last); 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] [Fact]
@ -157,12 +225,47 @@ public class ValueLinkListTests
Assert.Equal(1, linkList.Count); Assert.Equal(1, linkList.Count);
Assert.Equal(5, linkList2.Count); Assert.Equal(5, linkList2.Count);
Assert.Equal(entity1, linkList.First); Assert.Collection(linkList.ToArray(),
Assert.Equal(entity1, linkList.Last); item =>
{
Assert.Equal(entity1, item);
Assert.Null(item.Previous);
Assert.Null(item.Next);
}
);
Assert.Equal(entity2, entity6.Next); Assert.Collection(linkList2.ToArray(),
Assert.Equal(entity6, entity2.Previous); item =>
Assert.Equal(entity4, linkList2.Last); {
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] [Fact]
@ -184,10 +287,20 @@ public class ValueLinkListTests
linkList.RemoveAllBefore(entity4); linkList.RemoveAllBefore(entity4);
Assert.Equal(2, linkList.Count); Assert.Equal(2, linkList.Count);
Assert.Equal(entity4, linkList.First); Assert.Collection(linkList.ToArray(),
Assert.Equal(entity5, linkList.Last); item =>
Assert.Null(entity4.Previous); {
Assert.Null(entity5.Next); 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] [Fact]
@ -209,9 +322,104 @@ public class ValueLinkListTests
linkList.RemoveAllAfter(entity2); linkList.RemoveAllAfter(entity2);
Assert.Equal(2, linkList.Count); Assert.Equal(2, linkList.Count);
Assert.Equal(entity1, linkList.First); Assert.Collection(linkList.ToArray(),
Assert.Equal(entity2, linkList.Last); item =>
Assert.Null(entity1.Previous); {
Assert.Null(entity2.Next); 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<TestEntity>();
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<InvalidOperationException>(
() =>
{
var linkList = new ValueLinkList<TestEntity>();
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<InvalidOperationException>(
() =>
{
var linkList = new ValueLinkList<TestEntity>();
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);
}
}
}
);
} }
} }

View file

@ -28,8 +28,10 @@ public interface IValueLinkListNode<T> where T : class
public struct ValueLinkList<T> where T : class, IValueLinkListNode<T> public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
{ {
public int Count { get; internal set; } public int Count { get; internal set; }
public T First { get; internal set; } internal T _first;
public T Last { get; internal set; } internal T _last;
public int Version { get; private set; }
public void Remove(T node) public void Remove(T node)
{ {
@ -46,19 +48,19 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
if (node.Previous == null) if (node.Previous == null)
{ {
// If previous is null, then it is the first element. // 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."); throw new ArgumentException("Attempted to remove a node that is not on the list.");
} }
if (First == Last) if (_first == _last)
{ {
Last = null; _last = null;
First = null; _first = null;
} }
else else
{ {
First = node.Next; _first = node.Next;
} }
if (node.Next != null) if (node.Next != null)
@ -73,7 +75,7 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
// If next is null, then it is the last element. // If next is null, then it is the last element.
if (node.Next == null) if (node.Next == null)
{ {
Last = node.Previous; _last = node.Previous;
} }
else else
{ {
@ -85,6 +87,7 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
node.Previous = null; node.Previous = null;
node.OnLinkList = false; node.OnLinkList = false;
Count--; Count--;
Version++;
if (Count < 0) if (Count < 0)
{ {
@ -130,7 +133,8 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
current = previous; current = previous;
} }
First = e; _first = e;
Version++;
} }
// Remove all entries after this node, not including this node. // Remove all entries after this node, not including this node.
@ -171,7 +175,8 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
current = next; current = next;
} }
Last = e; _last = e;
Version++;
} }
public void AddLast(T e) public void AddLast(T e)
@ -186,15 +191,16 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
throw new ArgumentException("Attempted to add a node that is already on a list."); 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 else
{ {
First = e; _first = e;
Last = e; _last = e;
Count = 1; Count = 1;
Version++;
e.OnLinkList = true; e.OnLinkList = true;
} }
} }
@ -211,15 +217,16 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
throw new ArgumentException("Attempted to add a node that is already on a list."); 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 else
{ {
First = e; _first = e;
Last = e; _last = e;
Count = 1; Count = 1;
Version++;
e.OnLinkList = true; e.OnLinkList = true;
} }
} }
@ -252,12 +259,13 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
} }
else else
{ {
First = node; _first = node;
} }
existing.Previous = node; existing.Previous = node;
node.OnLinkList = true; node.OnLinkList = true;
Count++; Count++;
Version++;
} }
public void AddAfter(T existing, T node) public void AddAfter(T existing, T node)
@ -288,17 +296,18 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
} }
else else
{ {
Last = node; _last = node;
} }
existing.Next = node; existing.Next = node;
node.OnLinkList = true; node.OnLinkList = true;
Count++; Count++;
Version++;
} }
public void RemoveAll() public void RemoveAll()
{ {
var current = First; var current = _first;
while (current != null) while (current != null)
{ {
var next = current.Next; var next = current.Next;
@ -309,15 +318,16 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
current = next; current = next;
} }
First = null; _first = null;
Last = null; _last = null;
Count = 0; Count = 0;
Version++;
} }
public void AddLast(ref ValueLinkList<T> otherList, T start, T end) public void AddLast(ref ValueLinkList<T> otherList, T start, T end)
{ {
// Should we check if start and end actually exist on the other list? // 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."); throw new ArgumentException("Attempted to add nodes that are not on the specified linklist.");
} }
@ -329,7 +339,7 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
else else
{ {
// Start is first // Start is first
otherList.First = end.Next; otherList._first = end.Next;
} }
if (end.Next != null) if (end.Next != null)
@ -338,7 +348,7 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
} }
else else
{ {
otherList.Last = start.Previous; otherList._last = start.Previous;
} }
var count = 1; var count = 1;
@ -358,45 +368,62 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
throw new Exception("Count is negative!"); throw new Exception("Count is negative!");
} }
if (Last != null) if (_last != null)
{ {
Last.Next = start; _last.Next = start;
start.Previous = Last; start.Previous = _last;
} }
else else
{ {
First = start; _first = start;
} }
Last = end; _last = end;
Count += count; Count += count;
Version++;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public T[] ToArray()
public ValueListEnumerator GetEnumerator() => new(First); {
var arr = new T[Count];
[MethodImpl(MethodImplOptions.AggressiveInlining)] var index = 0;
public DescendingValueListEnumerator ByDescending() => new(Last); foreach (var t in this)
{
arr[index++] = t;
}
return arr;
}
public ref struct ValueListEnumerator public ref struct ValueListEnumerator
{ {
private T _head; private bool _started;
private T _current; private T _current;
private ref readonly ValueLinkList<T> _linkList;
private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ValueListEnumerator(T head) public ValueListEnumerator(in ValueLinkList<T> linkList)
{ {
_head = head; _linkList = ref linkList;
_started = false;
_current = null; _current = null;
_version = 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() public bool MoveNext()
{ {
if (_current == null) if (!_started)
{ {
_current = _head; _current = _linkList._first;
_head = null; _started = true;
_version = _linkList.Version;
}
else if (_linkList.Version != _version)
{
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
} }
else else
{ {
@ -415,23 +442,32 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
public ref struct DescendingValueListEnumerator public ref struct DescendingValueListEnumerator
{ {
private T _tail; private bool _started;
private T _current; private T _current;
private ref readonly ValueLinkList<T> _linkList;
private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public DescendingValueListEnumerator(T head) public DescendingValueListEnumerator(in ValueLinkList<T> linkList)
{ {
_tail = head; _linkList = ref linkList;
_started = false;
_current = null; _current = null;
_version = 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() public bool MoveNext()
{ {
if (_current == null) if (!_started)
{ {
_current = _tail; _current = _linkList._last;
_tail = null; _started = true;
_version = _linkList.Version;
}
else if (_linkList.Version != _version)
{
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
} }
else else
{ {
@ -451,3 +487,14 @@ public struct ValueLinkList<T> where T : class, IValueLinkListNode<T>
public DescendingValueListEnumerator GetEnumerator() => this; public DescendingValueListEnumerator GetEnumerator() => this;
} }
} }
public static class ValueLinkListExt
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueLinkList<T>.ValueListEnumerator GetEnumerator<T>(this in ValueLinkList<T> linkList)
where T : class, IValueLinkListNode<T> => new(in linkList);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueLinkList<T>.DescendingValueListEnumerator ByDescending<T>(this in ValueLinkList<T> linkList)
where T : class, IValueLinkListNode<T> => new(in linkList);
}

View file

@ -30,6 +30,12 @@ public interface IEntity : IPoint3D, ISerializable
bool InRange(Point3D p, int range); bool InRange(Point3D p, int range);
void RemoveItem(Item item); void RemoveItem(Item item);
bool OnMoveOff(Mobile m);
bool OnMoveOver(Mobile m);
public void OnMovement(Mobile m, Point3D oldLocation);
} }
public class Entity : IEntity 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) => public bool InRange(Point2D p, int range) =>
p.m_X >= Location.m_X - range p.m_X >= Location.m_X - range
&& p.m_X <= Location.m_X + range && p.m_X <= Location.m_X + range

View file

@ -2241,7 +2241,6 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
public virtual bool CanDecay() => Decays && Parent == null && Map != Map.Internal; public virtual bool CanDecay() => Decays && Parent == null && Map != Map.Internal;
public virtual bool OnDecay() => public virtual bool OnDecay() =>
CanDecay() && Region.Find(Location, Map).OnDecay(this); CanDecay() && Region.Find(Location, Map).OnDecay(this);
@ -2474,12 +2473,20 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemEnumerable<Item> GetItemsInRange(int range) => public Map.ItemAtEnumerable<Item> GetItemsAt() =>
m_Map == null ? Map.ItemEnumerable<Item>.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); m_Map == null ? Map.ItemAtEnumerable<Item>.Empty : m_Map.GetItemsAt(m_Parent == null ? m_Location : GetWorldLocation());
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemEnumerable<T> GetItemsInRange<T>(int range) where T : Item => public Map.ItemAtEnumerable<T> GetItemsAt<T>() where T : Item =>
m_Map == null ? Map.ItemEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Parent == null ? m_Location : GetWorldLocation(), range); m_Map == null ? Map.ItemAtEnumerable<T>.Empty : m_Map.GetItemsAt<T>(m_Parent == null ? m_Location : GetWorldLocation());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemBoundsEnumerable<Item> GetItemsInRange(int range) =>
m_Map == null ? Map.ItemBoundsEnumerable<Item>.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemBoundsEnumerable<T> GetItemsInRange<T>(int range) where T : Item =>
m_Map == null ? Map.ItemBoundsEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Parent == null ? m_Location : GetWorldLocation(), range);
public IPooledEnumerable<Mobile> GetMobilesInRange(int range) public IPooledEnumerable<Mobile> GetMobilesInRange(int range)
{ {

View file

@ -13,140 +13,159 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * * along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/ *************************************************************************/
using System.Collections.Generic; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Server.Collections;
namespace Server; namespace Server;
public partial class Map public partial class Map
{ {
private int _iteratingItems; [MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly List<(MapAction, Point3D, Item)> _delayedItemActions = new(); public ItemAtEnumerable<Item> GetItemsAt(Point3D p) => GetItemsAt<Item>(p);
public bool IsIteratingItems
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _iteratingItems > 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsAt(Point3D p) => GetItemsInRange(p, 0); public ItemAtEnumerable<T> GetItemsAt<T>(Point3D p) where T : Item => GetItemsAt<T>(new Point2D(p.X, p.Y));
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsAt<T>(Point3D p) where T : Item => GetItemsInRange<T>(p, 0); public ItemAtEnumerable<Item> GetItemsAt(int x, int y) => GetItemsAt<Item>(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInRange(Point3D p) => GetItemsInRange<Item>(p); public ItemAtEnumerable<T> GetItemsAt<T>(int x, int y) where T : Item => GetItemsAt<T>(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInRange(Point3D p, int range) => GetItemsInRange<Item>(p, range); public ItemAtEnumerable<Item> GetItemsAt(Point2D p) => GetItemsAt<Item>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(Point3D p) where T : Item => GetItemsInRange<T>(p, Core.GlobalMaxUpdateRange); public ItemAtEnumerable<T> GetItemsAt<T>(Point2D p) where T : Item => new(this, p);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(Point3D p, int range) where T : Item => public ItemBoundsEnumerable<Item> GetItemsInRange(Point3D p) => GetItemsInRange<Item>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemBoundsEnumerable<Item> GetItemsInRange(Point3D p, int range) => GetItemsInRange<Item>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemBoundsEnumerable<T> GetItemsInRange<T>(Point3D p) where T : Item => GetItemsInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemBoundsEnumerable<T> GetItemsInRange<T>(Point3D p, int range) where T : Item =>
GetItemsInRange<T>(p.m_X, p.m_Y, range); GetItemsInRange<T>(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsAt(Point2D p) => GetItemsInRange(p, 0); public ItemBoundsEnumerable<Item> GetItemsInRange(Point2D p) => GetItemsInRange<Item>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsAt<T>(Point2D p) where T : Item => GetItemsInRange<T>(p, 0); public ItemBoundsEnumerable<Item> GetItemsInRange(Point2D p, int range) => GetItemsInRange<Item>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInRange(Point2D p) => GetItemsInRange<Item>(p); public ItemBoundsEnumerable<T> GetItemsInRange<T>(Point2D p) where T : Item => GetItemsInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInRange(Point2D p, int range) => GetItemsInRange<Item>(p, range); public ItemBoundsEnumerable<T> GetItemsInRange<T>(Point2D p, int range) where T : Item =>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(Point2D p) where T : Item => GetItemsInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(Point2D p, int range) where T : Item =>
GetItemsInRange<T>(p.m_X, p.m_Y, range); GetItemsInRange<T>(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsAt(int x, int y) => GetItemsAt<Item>(x, y); public ItemBoundsEnumerable<T> GetItemsInRange<T>(int x, int y, int range) where T : Item =>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsAt<T>(int x, int y) where T : Item => GetItemsInRange<T>(x, y, 0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInRange<T>(int x, int y, int range) where T : Item =>
GetItemsInBounds<T>(new Rectangle2D(x - range, y - range, range * 2 + 1, range * 2 + 1)); GetItemsInBounds<T>(new Rectangle2D(x - range, y - range, range * 2 + 1, range * 2 + 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<Item> GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds<Item>(bounds); public ItemBoundsEnumerable<Item> GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds<Item>(bounds);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerable<T> GetItemsInBounds<T>(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item => public ItemBoundsEnumerable<T> GetItemsInBounds<T>(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item =>
new(this, bounds, makeBoundsInclusive); new(this, bounds, makeBoundsInclusive);
private void BeginIteratingItems() public ref struct ItemAtEnumerable<T> where T : Item
{ {
#if THREADGUARD public static ItemAtEnumerable<T> Empty
if (Thread.CurrentThread != Core.Thread) {
{ [MethodImpl(MethodImplOptions.AggressiveInlining)]
Utility.PushColor(ConsoleColor.Red); get => new();
Console.WriteLine($"Iterating through items on {this} from an invalid thread!"); }
Console.WriteLine(new StackTrace());
Utility.PopColor();
return;
}
#endif
_iteratingItems++; private Map _map;
private Point2D _location;
public ItemAtEnumerable(Map map, Point2D loc)
{
_map = map;
_location = loc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemAtEnumerator<T> GetEnumerator() => new(_map, _location);
} }
private void EndIteratingItems() public ref struct ItemAtEnumerator<T> where T : Item
{ {
#if THREADGUARD private bool _started;
if (Thread.CurrentThread != Core.Thread) private Point2D _location;
{ private ref readonly ValueLinkList<Item> _linkList;
Utility.PushColor(ConsoleColor.Red); private int _version;
Console.WriteLine($"Iterating through items on {this} from an invalid thread!"); private T _current;
Console.WriteLine(new StackTrace());
Utility.PopColor();
return;
}
#endif
_iteratingItems--; [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemAtEnumerator(Map map, Point2D loc)
// Finished iterating, check for deferred actions
if (_iteratingItems == 0 && _delayedItemActions.Count > 0)
{ {
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: _current = o;
{ return true;
OnEnter(p, i); }
break; }
} else if (_linkList.Version != _version)
case MapAction.Leave: {
{ throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
OnLeave(p, i); }
break; else
} {
case MapAction.Move: current = _current;
{ }
OnMove(p, i);
break; 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<T> where T : Item public ref struct ItemBoundsEnumerable<T> where T : Item
{ {
public static ItemEnumerable<T> Empty public static ItemBoundsEnumerable<T> Empty
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(null, Rectangle2D.Empty, false); get => new(null, Rectangle2D.Empty, false);
@ -156,14 +175,13 @@ public partial class Map
private Rectangle2D _bounds; private Rectangle2D _bounds;
private bool _makeBoundsInclusive; private bool _makeBoundsInclusive;
public ItemEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive) public ItemBoundsEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
{ {
_map = map; _map = map;
_bounds = bounds; _bounds = bounds;
_makeBoundsInclusive = makeBoundsInclusive; _makeBoundsInclusive = makeBoundsInclusive;
} }
// The enumerator MUST be disposed. Not disposing it will damage the sector irreparably.
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ItemEnumerator<T> GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive); public ItemEnumerator<T> GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive);
} }
@ -178,6 +196,9 @@ public partial class Map
private int _currentSectorX; private int _currentSectorX;
private int _currentSectorY; private int _currentSectorY;
private ref readonly ValueLinkList<Item> _linkList;
private int _currentVersion;
private T _current; private T _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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() // We start the X sector one short because it gets incremented immediately in MoveNext()
_currentSectorX = _sectorStartX - 1; _currentSectorX = _sectorStartX - 1;
_currentSectorY = _sectorStartY; _currentSectorY = _sectorStartY;
_map.BeginIteratingItems();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -242,7 +261,14 @@ public partial class Map
return false; 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)) 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 public T Current
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View file

@ -627,12 +627,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public void OnEnter(Item item) public void OnEnter(Item item)
{ {
if (IsIteratingItems)
{
_delayedItemActions.Add((MapAction.Enter, item.Location, item));
return;
}
OnEnter(item.Location, item); OnEnter(item.Location, item);
} }
@ -669,12 +663,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public void OnLeave(Item item) public void OnLeave(Item item)
{ {
if (IsIteratingItems)
{
_delayedItemActions.Add((MapAction.Leave, item.Location, item));
return;
}
OnLeave(item.Location, item); OnLeave(item.Location, item);
} }
@ -765,12 +753,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
if (oldSector != newSector) if (oldSector != newSector)
{ {
if (IsIteratingItems)
{
_delayedItemActions.Add((MapAction.Move, item.Location, item));
return;
}
oldSector.OnLeave(item); oldSector.OnLeave(item);
newSector.OnEnter(item); newSector.OnEnter(item);
} }
@ -787,12 +769,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
if (oldStart != start || oldEnd != end) if (oldStart != start || oldEnd != end)
{ {
if (IsIteratingItems)
{
_delayedItemActions.Add((MapAction.Move, oldLocation, item));
return;
}
RemoveMulti(m, oldStart, oldEnd); RemoveMulti(m, oldStart, oldEnd);
AddMulti(m, start, end); AddMulti(m, start, end);
} }
@ -1038,8 +1014,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public Sector GetSector(Point2D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); 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 GetSector(int x, int y) => InternalGetSector(x >> SectorShift, y >> SectorShift);
public Sector GetRealSector(int x, int y) => InternalGetSector(x, y); public Sector GetRealSector(int x, int y) => InternalGetSector(x, y);
@ -1459,14 +1433,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
return false; return false;
} }
private enum MapAction
{
None,
Enter,
Leave,
Move
}
public class Sector public class Sector
{ {
// TODO: Can we avoid this? // TODO: Can we avoid this?
@ -1495,7 +1461,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public List<Mobile> Mobiles => _mobiles ?? m_DefaultMobileList; public List<Mobile> Mobiles => _mobiles ?? m_DefaultMobileList;
public ref ValueLinkList<Item> Items => ref _items; internal ref readonly ValueLinkList<Item> Items => ref _items;
public List<NetState> Clients => _clients ?? m_DefaultClientList; public List<NetState> Clients => _clients ?? m_DefaultClientList;

View file

@ -17,6 +17,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Server.Collections;
using Server.Items; using Server.Items;
using Server.Network; using Server.Network;

View file

@ -194,9 +194,6 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
private static readonly TimeSpan ExpireCombatantDelay = TimeSpan.FromMinutes(1.0); private static readonly TimeSpan ExpireCombatantDelay = TimeSpan.FromMinutes(1.0);
private static readonly TimeSpan ExpireAggressorsDelay = TimeSpan.FromSeconds(5.0); private static readonly TimeSpan ExpireAggressorsDelay = TimeSpan.FromSeconds(5.0);
private static readonly List<IEntity> m_MoveList = new();
private static readonly List<Mobile> m_MoveClientList = new();
private static readonly object m_GhostMutateContext = new(); private static readonly object m_GhostMutateContext = new();
private static readonly List<Mobile> m_Hears = new(); private static readonly List<Mobile> m_Hears = new();
@ -3567,6 +3564,8 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
public bool InLOS(Point3D target) => public bool InLOS(Point3D target) =>
!Deleted && m_Map != null && (m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, 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<T>() => BeginAction(typeof(T)); public bool BeginAction<T>() => BeginAction(typeof(T));
public bool BeginAction(object toLock) public bool BeginAction(object toLock)
@ -4154,12 +4153,20 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
if (oldSector != newSector) if (oldSector != newSector)
{ {
using var queue = PooledRefQueue<IEntity>.Create(2048);
for (var i = 0; i < oldSector.Mobiles.Count; ++i) for (var i = 0; i < oldSector.Mobiles.Count; ++i)
{ {
var m = oldSector.Mobiles[i]; var m = oldSector.Mobiles[i];
if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z)
!m.OnMoveOff(this)) {
queue.Enqueue(m);
}
}
while (queue.Count > 0)
{
if (!queue.Dequeue().OnMoveOff(this))
{ {
return false; return false;
} }
@ -4168,8 +4175,15 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
foreach (var item in oldSector.Items) foreach (var item in oldSector.Items)
{ {
if (item.AtWorldPoint(oldX, oldY) && if (item.AtWorldPoint(oldX, oldY) &&
(item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z))
!item.OnMoveOff(this)) {
queue.Enqueue(item);
}
}
while (queue.Count > 0)
{
if (!queue.Dequeue().OnMoveOff(this))
{ {
return false; return false;
} }
@ -4179,7 +4193,15 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
{ {
var m = newSector.Mobiles[i]; 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; return false;
} }
@ -4188,8 +4210,15 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
foreach (var item in newSector.Items) foreach (var item in newSector.Items)
{ {
if (item.AtWorldPoint(x, y) && if (item.AtWorldPoint(x, y) &&
(item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z))
!item.OnMoveOver(this)) {
queue.Enqueue(item);
}
}
while (queue.Count > 0)
{
if (!queue.Dequeue().OnMoveOver(this))
{ {
return false; return false;
} }
@ -4197,17 +4226,36 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
} }
else else
{ {
using var queue = PooledRefQueue<(IEntity, byte)>.Create(2048);
for (var i = 0; i < oldSector.Mobiles.Count; ++i) for (var i = 0; i < oldSector.Mobiles.Count; ++i)
{ {
var m = oldSector.Mobiles[i]; var m = oldSector.Mobiles[i];
byte flag;
if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z)
!m.OnMoveOff(this))
{ {
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; return false;
} }
@ -4215,16 +4263,34 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
foreach (var item in oldSector.Items) foreach (var item in oldSector.Items)
{ {
byte flag;
if (item.AtWorldPoint(oldX, oldY) && if (item.AtWorldPoint(oldX, oldY) &&
(item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z))
!item.OnMoveOff(this))
{ {
return false; flag = 1;
}
else
{
flag = 0;
} }
if (item.AtWorldPoint(x, y) && if (item.AtWorldPoint(x, y) &&
(item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z))
!item.OnMoveOver(this)) {
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; return false;
} }
@ -4281,6 +4347,8 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
if (m_Map != null) if (m_Map != null)
{ {
using var moveQueue = PooledRefQueue<IEntity>.Create(2048);
using var moveClientQueue = PooledRefQueue<Mobile>.Create(2048);
var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange);
foreach (var o in eable) foreach (var o in eable)
@ -4294,54 +4362,39 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
{ {
if (mob.NetState != null) 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) else if (o is Item item && item.HandlesOnMovement)
{ {
m_MoveList.Add(item); moveQueue.Enqueue(item);
} }
} }
const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength; if (moveClientQueue.Count > 0)
const int width = OutgoingMobilePackets.MobileMovingPacketLength;
var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width);
foreach (var m in m_MoveClientList)
{ {
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]; moveQueue.Dequeue().OnMovement(this, oldLocation);
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();
} }
} }
@ -8059,11 +8112,19 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemEnumerable<Item> GetItemsInRange(int range) => GetItemsInRange<Item>(range); public Map.ItemAtEnumerable<Item> GetItemsAt() =>
m_Map == null ? Map.ItemAtEnumerable<Item>.Empty : m_Map.GetItemsAt(m_Location);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemEnumerable<T> GetItemsInRange<T>(int range) where T : Item => public Map.ItemAtEnumerable<T> GetItemsAt<T>() where T : Item =>
m_Map == null ? Map.ItemEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Location, range); m_Map == null ? Map.ItemAtEnumerable<T>.Empty : m_Map.GetItemsAt<T>(m_Location);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemBoundsEnumerable<Item> GetItemsInRange(int range) => GetItemsInRange<Item>(range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ItemBoundsEnumerable<T> GetItemsInRange<T>(int range) where T : Item =>
m_Map == null ? Map.ItemBoundsEnumerable<T>.Empty : m_Map.GetItemsInRange<T>(m_Location, range);
public IPooledEnumerable<IEntity> GetObjectsInRange(int range) => public IPooledEnumerable<IEntity> GetObjectsInRange(int range) =>
m_Map?.GetObjectsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable<IEntity>.Instance; m_Map?.GetObjectsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable<IEntity>.Instance;

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Server.Collections;
using Server.Items; using Server.Items;
using Server.Json; using Server.Json;
using Server.Network; using Server.Network;
@ -121,16 +122,20 @@ namespace Server.Commands
public static int DeleteTeleporters(WorldLocation worldLocation) public static int DeleteTeleporters(WorldLocation worldLocation)
{ {
var count = 0; using var queue = PooledRefQueue<Item>.Create();
foreach (var item in worldLocation.Map.GetItemsInRange<Teleporter>(worldLocation, 0)) foreach (var item in worldLocation.Map.GetItemsAt<Teleporter>(worldLocation))
{ {
if (item is not (KeywordTeleporter or SkillTeleporter) && IsWithinZ(item.Z - worldLocation.Z)) if (item is not (KeywordTeleporter or SkillTeleporter) && IsWithinZ(item.Z - worldLocation.Z))
{ {
count++; queue.Enqueue(item);
item.Delete();
} }
} }
var count = queue.Count;
while (queue.Count > 0)
{
queue.Dequeue().Delete();
}
return count; return count;
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Server.Collections;
using Server.Items; using Server.Items;
using Server.Network; using Server.Network;
@ -8,8 +9,6 @@ namespace Server.Commands
{ {
public static class SignParser public static class SignParser
{ {
private static readonly Queue<Item> m_ToDelete = new();
public static void Initialize() public static void Initialize()
{ {
CommandSystem.Register("SignGen", AccessLevel.Administrator, SignGen_OnCommand); 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) public static void Add_Static(int itemID, Point3D location, Map map, string name)
{ {
using var queue = PooledRefQueue<Item>.Create();
foreach (var item in map.GetItemsInRange(location, 0)) foreach (var item in map.GetItemsInRange(location, 0))
{ {
if (item is Sign && item.Z == location.Z && item.ItemID == itemID) 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; Item sign;

View file

@ -468,7 +468,7 @@ namespace Server.Engines.ConPVP
if (landTile.ID == 0x244 && statics.Length == 0) // 0x244 = invalid land tile if (landTile.ID == 0x244 && statics.Length == 0) // 0x244 = invalid land tile
{ {
var empty = true; var empty = true;
foreach (var item in Map.GetItemsInRange(point, 0)) foreach (var item in Map.GetItemsAt(point))
{ {
if (item != this) 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) if (item.Visible && item != this)
{ {

View file

@ -180,7 +180,7 @@ namespace Server.Engines.Doom
public static void RemoveDoorSet(int x, int y) public static void RemoveDoorSet(int x, int y)
{ {
var loc = new Point3D(x, y, -1); 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) if (item is BaseDoor door)
{ {
@ -226,9 +226,9 @@ namespace Server.Engines.Doom
public static void RemoveItem<T>(int x, int y, int z = -1) where T : Item public static void RemoveItem<T>(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(); item.Delete();
break; break;

View file

@ -197,7 +197,7 @@ public partial class LeverPuzzleController : Item
[Usage("GenLeverPuzzle"), Description("Generates lamp room and lever puzzle in doom.")] [Usage("GenLeverPuzzle"), Description("Generates lamp room and lever puzzle in doom.")]
public static void GenLampPuzzle_OnCommand(CommandEventArgs e) 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) if (item is LeverPuzzleController)
{ {

View file

@ -78,7 +78,7 @@ namespace Server.Factions
private static bool CheckExistence(Point3D loc, Map facet, Type type) 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)) if (type.IsInstanceOfType(item))
{ {

View file

@ -114,7 +114,7 @@ namespace Server.Factions
if (Core.ML) 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) if (item is BaseFactionTrap trap && trap.Faction == Faction)
{ {

View file

@ -307,7 +307,7 @@ namespace Server.Engines.Harvest
return false; return false;
} }
foreach (var i in m.GetItemsInRange(0)) foreach (var i in m.GetItemsAt())
{ {
if (i.StackWith(m, i, false)) if (i.StackWith(m, i, false))
{ {

View file

@ -15,9 +15,9 @@ namespace Server.Commands
public static bool FindMorphItem(int x, int y, int z, int inactiveItemID, int activeItemID) public static bool FindMorphItem(int x, int y, int z, int inactiveItemID, int activeItemID)
{ {
var found = false; var found = false;
foreach (var item in Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0)) foreach (var morphItem in Map.Felucca.GetItemsAt<MorphItem>(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; found = true;
break; break;
@ -30,9 +30,9 @@ namespace Server.Commands
public static bool FindEffectController(int x, int y, int z) public static bool FindEffectController(int x, int y, int z)
{ {
var found = false; var found = false;
foreach (var item in Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0)) foreach (var item in Map.Felucca.GetItemsAt<EffectController>(x, y))
{ {
if (item is EffectController && item.Z == z) if (item.Z == z)
{ {
found = true; found = true;
break; break;
@ -44,7 +44,7 @@ namespace Server.Commands
public static T TryCreateItem<T>(int x, int y, int z, T srcItem) where T : Item public static T TryCreateItem<T>(int x, int y, int z, T srcItem) where T : Item
{ {
foreach (var item in Map.Felucca.GetItemsInBounds<T>(new Rectangle2D(x, y, 1, 1))) foreach (var item in Map.Felucca.GetItemsAt<T>(x, y))
{ {
srcItem.Delete(); srcItem.Delete();
return item; return item;

View file

@ -267,7 +267,7 @@ namespace Server.Engines.MLQuests
var name = $"MLQS-{GetType().Name}"; var name = $"MLQS-{GetType().Name}";
using var queue = PooledRefQueue<Item>.Create(); using var queue = PooledRefQueue<Item>.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! // 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) 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) public static void PutDeco(Item deco, Point3D loc, Map map)
{ {
using var queue = PooledRefQueue<Item>.Create(); using var queue = PooledRefQueue<Item>.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) if (item.ItemID == deco.ItemID && item.Z == loc.Z)
{ {

View file

@ -22,8 +22,6 @@ namespace Server.Movement
private readonly List<Item>[] _pools = { new(), new(), new(), new() }; private readonly List<Item>[] _pools = { new(), new(), new(), new() };
private readonly HashSet<Map.Sector> _sectors = new();
private MovementImpl() private MovementImpl()
{ {
} }
@ -83,149 +81,55 @@ namespace Server.Movement
var checkMobs = (m as BaseCreature)?.Controlled == false && (xForward != _goal.X || yForward != _goal.Y); 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); if (entity is Item item)
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 (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface)
{ {
continue; continue;
} }
if (!item.ItemData[reqFlags]) if (!item.ItemData[reqFlags] || item.ItemID > TileData.MaxItemValue || item.Parent != null)
{ {
continue; continue;
} }
if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) if (item is BaseMulti)
{ {
continue; continue;
} }
if (item.AtWorldPoint(xStart, yStart)) if (item.AtPoint(xStart, yStart))
{ {
itemsStart.Add(item); itemsStart.Add(item);
} }
else if (sectorStartIsForward && item.AtWorldPoint(xForward, yForward)) else if (item.AtPoint(xForward, yForward))
{ {
itemsForward.Add(item); itemsForward.Add(item);
} }
} else if (checkDiagonals && item.AtPoint(xLeft, yLeft))
if (checkMobs)
{
for (var i = 0; i < sectorForward.Mobiles.Count; ++i)
{ {
var mob = sectorForward.Mobiles[i]; itemsLeft.Add(item);
}
if (mob.X == xForward && mob.Y == yForward) else if (checkDiagonals && item.AtPoint(xRight, yRight))
{ {
mobsForward.Add(mob); 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); 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<Item> items bool ignoreDoors, bool ignoreSpellFields, int ourZ, int ourTop, StaticTile[] tiles, List<Item> items
) )
{ {
@ -326,7 +230,7 @@ namespace Server.Movement
return true; return true;
} }
private bool Check( private static bool Check(
Map map, Map map,
Mobile m, Mobile m,
List<Item> items, List<Item> items,
@ -596,7 +500,7 @@ namespace Server.Movement
private static bool CanMoveOver(Mobile m, Mobile t) => private static bool CanMoveOver(Mobile m, Mobile t) =>
!t.Alive || !m.Alive || t.IsDeadBondedPet || m.IsDeadBondedPet || t.Hidden && t.AccessLevel > AccessLevel.Player; !t.Alive || !m.Alive || t.IsDeadBondedPet || m.IsDeadBondedPet || t.Hidden && t.AccessLevel > AccessLevel.Player;
private void GetStartZ(Mobile m, Map map, Point3D loc, List<Item> itemList, out int zLow, out int zTop) private static void GetStartZ(Mobile m, Map map, Point3D loc, List<Item> itemList, out int zLow, out int zTop)
{ {
int xCheck = loc.X, yCheck = loc.Y; 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) switch (d & Direction.Mask)
{ {

View file

@ -29,13 +29,9 @@ public partial class Victoria : BaseQuester
if (_altar?.Deleted != false || _altar.Map != Map || if (_altar?.Deleted != false || _altar.Map != Map ||
!Utility.InRange(_altar.Location, Location, AltarRange)) !Utility.InRange(_altar.Location, Location, AltarRange))
{ {
foreach (var item in GetItemsInRange(AltarRange)) foreach (var altar in GetItemsInRange<SummoningAltar>(AltarRange))
{ {
if (item is SummoningAltar altar) _altar = altar;
{
_altar = altar;
break;
}
} }
} }

View file

@ -147,7 +147,7 @@ namespace Server.Engines.Spawners
// Delete all spawners at this location. // Delete all spawners at this location.
// Probably shouldn't do this outside of migrations? Is there a better way to find/fix spawners? // Probably shouldn't do this outside of migrations? Is there a better way to find/fix spawners?
foreach (var spawner in map.GetItemsInRange<BaseSpawner>(location, 0)) foreach (var spawner in map.GetItemsAt<BaseSpawner>(location))
{ {
if (spawner.GetType() == type) if (spawner.GetType() == type)
{ {

View file

@ -199,9 +199,9 @@ namespace Server.Items
bool isclear = true; bool isclear = true;
foreach (Item item in Map.Malas.GetItemsInRange(p3d, 0)) foreach (Fireflies fireflies in Map.Malas.GetItemsAt<Fireflies>(p3d))
{ {
if (item is Fireflies) if (fireflies.Z == p3d.Z)
{ {
isclear = false; isclear = false;
} }

View file

@ -267,7 +267,7 @@ namespace Server.Items
public static SHTeleporter FindSHTeleporter(Map map, Point3D p) public static SHTeleporter FindSHTeleporter(Map map, Point3D p)
{ {
foreach (var teleporter in map.GetItemsInRange<SHTeleporter>(p, 0)) foreach (var teleporter in map.GetItemsAt<SHTeleporter>(p))
{ {
if (teleporter.Z == p.Z) if (teleporter.Z == p.Z)
{ {

View file

@ -130,7 +130,7 @@ public partial class MarkContainer : LockableContainer
private static bool FindMarkContainer(Point3D p, Map map) private static bool FindMarkContainer(Point3D p, Map map)
{ {
foreach (var item in map.GetItemsInRange<MarkContainer>(p, 0)) foreach (var item in map.GetItemsAt<MarkContainer>(p))
{ {
if (item.Z == p.Z) if (item.Z == p.Z)
{ {

View file

@ -50,7 +50,7 @@ public partial class OilFlask : Item
{ {
var didStack = false; var didStack = false;
foreach (var i in from.GetItemsInRange(0)) foreach (var i in from.GetItemsAt())
{ {
if (i.StackWith(from, this, false)) if (i.StackWith(from, this, false))
{ {

View file

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic;
using ModernUO.Serialization; using ModernUO.Serialization;
using Server.Collections;
namespace Server.Items; namespace Server.Items;
@ -83,19 +83,18 @@ public partial class WarningItem : Item
if (NeighborRange >= 0) if (NeighborRange >= 0)
{ {
var list = new List<WarningItem>(); using var queue = PooledRefQueue<WarningItem>.Create();
foreach (var warningItem in GetItemsInRange<WarningItem>(NeighborRange))
foreach (var item 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);
} }
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Server.Collections;
using Server.ContextMenus; using Server.ContextMenus;
using Server.Engines.Quests; using Server.Engines.Quests;
using Server.Engines.Quests.Necro; using Server.Engines.Quests.Necro;
@ -94,7 +95,6 @@ public abstract class BaseAI
SkillName.Meditation SkillName.Meditation
}; };
private static readonly Queue<Item> m_Obstacles = new();
protected ActionType m_Action; protected ActionType m_Action;
public BaseCreature m_Mobile; public BaseCreature m_Mobile;
@ -2156,8 +2156,9 @@ public abstract class BaseAI
int x = m_Mobile.X, y = m_Mobile.Y; int x = m_Mobile.X, y = m_Mobile.Y;
Movement.Movement.Offset(d, ref x, ref y); Movement.Movement.Offset(d, ref x, ref y);
using var queue = PooledRefQueue<Item>.Create();
var destroyables = 0; 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 && if (canOpenDoors && item is BaseDoor door && door.Z + door.ItemData.Height > m_Mobile.Z &&
m_Mobile.Z + 16 > door.Z) m_Mobile.Z + 16 > door.Z)
@ -2169,7 +2170,7 @@ public abstract class BaseAI
if (!door.Locked || !door.UseLocks()) if (!door.Locked || !door.UseLocks())
{ {
m_Obstacles.Enqueue(door); queue.Enqueue(door);
} }
if (!canDestroyObstacles) if (!canDestroyObstacles)
@ -2185,7 +2186,7 @@ public abstract class BaseAI
continue; continue;
} }
m_Obstacles.Enqueue(item); queue.Enqueue(item);
++destroyables; ++destroyables;
} }
} }
@ -2195,14 +2196,14 @@ public abstract class BaseAI
Effects.PlaySound(new Point3D(x, y, m_Mobile.Z), m_Mobile.Map, 0x3B3); 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 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) if (item is BaseDoor door)
{ {
@ -2238,7 +2239,7 @@ public abstract class BaseAI
if (check.Movable && check.ItemData.Impassable && if (check.Movable && check.ItemData.Impassable &&
cont.Z + check.ItemData.Height > m_Mobile.Z) cont.Z + check.ItemData.Height > m_Mobile.Z)
{ {
m_Obstacles.Enqueue(check); queue.Enqueue(check);
} }
} }

View file

@ -118,7 +118,7 @@ namespace Server.Mobiles
p = GetSpawnPosition(2); p = GetSpawnPosition(2);
var atLocation = false; var atLocation = false;
foreach (var item in Map.GetItemsInRange<StainedOoze>(p, 0)) foreach (var item in Map.GetItemsAt<StainedOoze>(p))
{ {
atLocation = true; atLocation = true;
break; break;

View file

@ -235,7 +235,7 @@ namespace Server.Mobiles
p = GetSpawnPosition(2); p = GetSpawnPosition(2);
var atLocation = false; var atLocation = false;
foreach (var item in Map.GetItemsInRange<StainedOoze>(p, 0)) foreach (var item in Map.GetItemsAt<StainedOoze>(p))
{ {
atLocation = true; atLocation = true;
break; break;

View file

@ -358,11 +358,9 @@ namespace Server.Multis
} }
} }
var sector = map.GetSector(borderPoint.X, borderPoint.Y); foreach (var item in map.GetItemsAt(borderPoint))
foreach (var item in sector.Items)
{ {
if (item.X != borderPoint.X || item.Y != borderPoint.Y || item.Movable) if (item.Movable)
{ {
continue; continue;
} }

View file

@ -141,7 +141,7 @@ namespace Server.Spells.Seventh
private static bool GateExistsAt(Map map, Point3D loc) 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) if (item is Moongate or PublicMoongate)
{ {

View file

@ -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)) if (item.Z + item.ItemData.CalcHeight == location.Z && IsValidTile(item.ItemID))
{ {

View file

@ -1,3 +1,4 @@
using Server.Collections;
using Server.Factions; using Server.Factions;
using Server.Items; using Server.Items;
using Server.Misc; using Server.Misc;
@ -96,14 +97,21 @@ namespace Server.Spells.Third
m.PlaySound(0x1FE); m.PlaySound(0x1FE);
foreach (var item in m.GetItemsInRange(0)) using var queue = PooledRefQueue<Item>.Create();
foreach (var item in m.GetItemsAt())
{ {
if (item is ParalyzeFieldSpell.InternalItem or if (item is ParalyzeFieldSpell.InternalItem or
PoisonFieldSpell.InternalItem or FireFieldSpell.FireFieldItem) 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(); FinishSequence();