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:
parent
658f564f34
commit
d919f71149
33 changed files with 706 additions and 468 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue