fix: Adds multis to map iterators, fixes searching empty nested containers (#1581)

### Summary
Eliminates `IPooledEnumerable<BaseMulti>` and `eable.Free()` from `Map` for multis. This drastically simplifies code that iterates in range, for example:

```cs
foreach (var m in m.GetMultisInRange(5))
{
}
```
The code above no longer requires an eable and calling `Free()`.


### Bug Fixes
- [X] Fixes an issue with searching through nested empty containers.
This commit is contained in:
Kamron Batman 2023-11-05 08:17:34 -08:00 committed by GitHub
parent 1f04a13f67
commit ca3df9cfa7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 960 additions and 860 deletions

View file

@ -15,430 +15,489 @@
using System;
using System.Runtime.CompilerServices;
using Server.Collections;
namespace Server.Items;
// Adds support for the specific value link list on sectors for multis, separate from items
public partial class BaseMulti : BaseMulti.ISectorMultiLinkListNode<BaseMulti>
public partial class BaseMulti
{
public interface ISectorMultiLinkListNode<T> where T : class
{
public T SectorMultiNext { get; set; }
public T SectorMultiPrevious { get; set; }
public bool OnSectorMultiLinkList { get; set; }
}
public struct SectorMultiLinkList<T> where T : class, ISectorMultiLinkListNode<T>
{
public int Count { get; internal set; }
public T First { get; internal set; }
public T Last { get; internal set; }
public void Remove(T node)
{
if (node == null)
{
return;
}
if (!node.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to remove a node that is not on the list.");
}
if (node.SectorMultiPrevious == null)
{
// If previous is null, then it is the first element.
if (First != node)
{
throw new ArgumentException("Attempted to remove a node that is not on the list.");
}
if (First == Last)
{
Last = null;
First = null;
}
else
{
First = node.SectorMultiNext;
}
if (node.SectorMultiNext != null)
{
node.SectorMultiNext.SectorMultiPrevious = null;
}
}
else
{
node.SectorMultiPrevious.SectorMultiNext = node.SectorMultiNext;
// If next is null, then it is the last element.
if (node.SectorMultiNext == null)
{
Last = node.SectorMultiPrevious;
}
else
{
node.SectorMultiNext.SectorMultiPrevious = node.SectorMultiPrevious;
}
}
node.SectorMultiNext = null;
node.SectorMultiPrevious = null;
node.OnSectorMultiLinkList = false;
Count--;
}
// Remove all entries before this node, not including this node.
public void RemoveAllBefore(T e)
{
if (e == null)
{
return;
}
if (!e.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to remove nodes before a node that is not on the list.");
}
if (e.SectorMultiPrevious == null)
{
return;
}
var current = e.SectorMultiPrevious;
e.SectorMultiPrevious = null;
while (current != null)
{
var previous = current.SectorMultiPrevious;
current.OnSectorMultiLinkList = false;
current.SectorMultiNext = null;
current.SectorMultiPrevious = null;
Count--;
current = previous;
}
First = e;
}
// Remove all entries after this node, not including this node.
public void RemoveAllAfter(T e)
{
if (e == null)
{
return;
}
if (!e.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to remove nodes after a node that is not on the list.");
}
if (e.SectorMultiNext == null)
{
return;
}
var current = e.SectorMultiNext;
e.SectorMultiNext = null;
while (current != null)
{
var next = current.SectorMultiNext;
current.OnSectorMultiLinkList = false;
current.SectorMultiNext = null;
current.SectorMultiPrevious = null;
Count--;
current = next;
}
Last = e;
}
public void AddLast(T e)
{
if (e == null)
{
return;
}
if (e.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
if (Last != null)
{
AddAfter(Last, e);
}
else
{
First = e;
Last = e;
Count++;
}
e.OnSectorMultiLinkList = true;
}
public void AddFirst(T e)
{
if (e == null)
{
return;
}
if (e.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
if (First != null)
{
AddBefore(First, e);
}
else
{
First = e;
Last = e;
Count++;
}
e.OnSectorMultiLinkList = true;
}
public void AddBefore(T existing, T node)
{
if (node == null)
{
return;
}
ArgumentNullException.ThrowIfNull(existing);
if (!existing.OnSectorMultiLinkList)
{
throw new ArgumentException($"Argument '{nameof(existing)}' must be a node on a list.");
}
if (node.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
node.SectorMultiNext = existing;
node.SectorMultiPrevious = existing.SectorMultiPrevious;
if (existing.SectorMultiPrevious != null)
{
existing.SectorMultiPrevious.SectorMultiNext = node;
}
else
{
First = node;
}
existing.SectorMultiPrevious = node;
node.OnSectorMultiLinkList = true;
Count++;
}
public void AddAfter(T existing, T node)
{
if (node == null)
{
return;
}
ArgumentNullException.ThrowIfNull(existing);
if (!existing.OnSectorMultiLinkList)
{
throw new ArgumentException($"Argument '{nameof(existing)}' must be a node on a list.");
}
if (node.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
node.SectorMultiPrevious = existing;
node.SectorMultiNext = existing.SectorMultiNext;
if (existing.SectorMultiNext != null)
{
existing.SectorMultiNext.SectorMultiPrevious = node;
}
else
{
Last = node;
}
existing.SectorMultiNext = node;
node.OnSectorMultiLinkList = true;
Count++;
}
public void RemoveAll()
{
var current = First;
while (current != null)
{
var next = current.SectorMultiNext;
current.OnSectorMultiLinkList = false;
current.SectorMultiNext = null;
current.SectorMultiPrevious = null;
current = next;
}
First = null;
Last = null;
Count = 0;
}
public void AddLast(ref SectorMultiLinkList<T> otherList, T start, T end)
{
// Should we check if start and end actually exist on the other list?
if (otherList.Count == 0 || otherList.Count == 1 && (start != end || otherList.First != start))
{
throw new ArgumentException("Attempted to add nodes that are not on the specified linklist.");
}
if (start.SectorMultiPrevious != null)
{
start.SectorMultiPrevious.SectorMultiNext = end.SectorMultiNext;
}
else
{
// Start is first
otherList.First = end.SectorMultiNext;
}
if (end.SectorMultiNext != null)
{
end.SectorMultiNext.SectorMultiPrevious = start.SectorMultiPrevious;
}
else
{
otherList.Last = start.SectorMultiPrevious;
}
var count = 1;
var current = start;
// Assume start and end are in the right order, or bad things happen (crash).
while (current != end)
{
count++;
current = current.SectorMultiNext;
}
otherList.Count -= count;
if (Last != null)
{
Last.SectorMultiNext = start;
start.SectorMultiPrevious = Last;
}
else
{
First = start;
}
Last = end;
Count += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SectorMultiListEnumerator GetEnumerator() => new(First);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DescendingSectorMultiListEnumerator ByDescending() => new(Last);
public ref struct SectorMultiListEnumerator
{
private T _head;
private T _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SectorMultiListEnumerator(T head)
{
_head = head;
_current = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (_current == null)
{
_current = _head;
_head = null;
}
else
{
_current = _current.SectorMultiNext;
}
return _current != null;
}
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
}
public ref struct DescendingSectorMultiListEnumerator
{
private T _tail;
private T _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DescendingSectorMultiListEnumerator(T head)
{
_tail = head;
_current = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (_current == null)
{
_current = _tail;
_tail = null;
}
else
{
_current = _current.SectorMultiPrevious;
}
return _current != null;
}
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DescendingSectorMultiListEnumerator GetEnumerator() => this;
}
}
// Sectors, specifically for multis
public BaseMulti SectorMultiNext { get; set; }
public BaseMulti SectorMultiPrevious { get; set; }
public bool OnSectorMultiLinkList { get; set; }
}
public struct SectorMultiValueLinkList
{
public int Count { get; internal set; }
internal BaseMulti _first;
internal BaseMulti _last;
public int Version { get; private set; }
public void Remove(BaseMulti node)
{
if (node == null)
{
return;
}
if (!node.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to remove a node that is not on the list.");
}
if (node.SectorMultiPrevious == null)
{
// If SectorMultiPrevious is null, then it is the first element.
if (_first != node)
{
throw new ArgumentException("Attempted to remove a node that is not on the list.");
}
if (_first == _last)
{
_last = null;
_first = null;
}
else
{
_first = node.SectorMultiNext;
}
if (node.SectorMultiNext != null)
{
node.SectorMultiNext.SectorMultiPrevious = null;
}
}
else
{
node.SectorMultiPrevious.SectorMultiNext = node.SectorMultiNext;
// If SectorMultiNext is null, then it is the last element.
if (node.SectorMultiNext == null)
{
_last = node.SectorMultiPrevious;
}
else
{
node.SectorMultiNext.SectorMultiPrevious = node.SectorMultiPrevious;
}
}
node.SectorMultiNext = null;
node.SectorMultiPrevious = null;
node.OnSectorMultiLinkList = false;
Count--;
Version++;
if (Count < 0)
{
throw new Exception("Count is negative!");
}
}
// Remove all entries before this node, not including this node.
public void RemoveAllBefore(BaseMulti e)
{
if (e == null)
{
return;
}
if (!e.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to remove nodes before a node that is not on the list.");
}
if (e.SectorMultiPrevious == null)
{
return;
}
var current = e.SectorMultiPrevious;
e.SectorMultiPrevious = null;
while (current != null)
{
var SectorMultiPrevious = current.SectorMultiPrevious;
current.OnSectorMultiLinkList = false;
current.SectorMultiNext = null;
current.SectorMultiPrevious = null;
Count--;
if (Count < 0)
{
throw new Exception("Count is negative!");
}
current = SectorMultiPrevious;
}
_first = e;
Version++;
}
// Remove all entries after this node, not including this node.
public void RemoveAllAfter(BaseMulti e)
{
if (e == null)
{
return;
}
if (!e.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to remove nodes after a node that is not on the list.");
}
if (e.SectorMultiNext == null)
{
return;
}
var current = e.SectorMultiNext;
e.SectorMultiNext = null;
while (current != null)
{
var SectorMultiNext = current.SectorMultiNext;
current.OnSectorMultiLinkList = false;
current.SectorMultiNext = null;
current.SectorMultiPrevious = null;
Count--;
if (Count < 0)
{
throw new Exception("Count is negative!");
}
current = SectorMultiNext;
}
_last = e;
Version++;
}
public void AddLast(BaseMulti e)
{
if (e == null)
{
return;
}
if (e.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
if (_last != null)
{
AddAfter(_last, e);
}
else
{
_first = e;
_last = e;
Count = 1;
Version++;
e.OnSectorMultiLinkList = true;
}
}
public void AddFirst(BaseMulti e)
{
if (e == null)
{
return;
}
if (e.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
if (_first != null)
{
AddBefore(_first, e);
}
else
{
_first = e;
_last = e;
Count = 1;
Version++;
e.OnSectorMultiLinkList = true;
}
}
public void AddBefore(BaseMulti existing, BaseMulti node)
{
if (node == null)
{
return;
}
ArgumentNullException.ThrowIfNull(existing);
if (!existing.OnSectorMultiLinkList)
{
throw new ArgumentException($"Argument '{nameof(existing)}' must be a node on a list.");
}
if (node.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
node.SectorMultiNext = existing;
node.SectorMultiPrevious = existing.SectorMultiPrevious;
if (existing.SectorMultiPrevious != null)
{
existing.SectorMultiPrevious.SectorMultiNext = node;
}
else
{
_first = node;
}
existing.SectorMultiPrevious = node;
node.OnSectorMultiLinkList = true;
Count++;
Version++;
}
public void AddAfter(BaseMulti existing, BaseMulti node)
{
if (node == null)
{
return;
}
ArgumentNullException.ThrowIfNull(existing);
if (!existing.OnSectorMultiLinkList)
{
throw new ArgumentException($"Argument '{nameof(existing)}' must be a node on a list.");
}
if (node.OnSectorMultiLinkList)
{
throw new ArgumentException("Attempted to add a node that is already on a list.");
}
node.SectorMultiPrevious = existing;
node.SectorMultiNext = existing.SectorMultiNext;
if (existing.SectorMultiNext != null)
{
existing.SectorMultiNext.SectorMultiPrevious = node;
}
else
{
_last = node;
}
existing.SectorMultiNext = node;
node.OnSectorMultiLinkList = true;
Count++;
Version++;
}
public void RemoveAll()
{
var current = _first;
while (current != null)
{
var SectorMultiNext = current.SectorMultiNext;
current.OnSectorMultiLinkList = false;
current.SectorMultiNext = null;
current.SectorMultiPrevious = null;
current = SectorMultiNext;
}
_first = null;
_last = null;
Count = 0;
Version++;
}
public void AddLast(ref SectorMultiValueLinkList otherList, BaseMulti start, BaseMulti end)
{
// Should we check if start and end actually exist on the other list?
if (otherList.Count == 0 || otherList.Count == 1 && (start != end || otherList._first != start))
{
throw new ArgumentException("Attempted to add nodes that are not on the specified linklist.");
}
if (start.SectorMultiPrevious != null)
{
start.SectorMultiPrevious.SectorMultiNext = end.SectorMultiNext;
}
else
{
// Start is first
otherList._first = end.SectorMultiNext;
}
if (end.SectorMultiNext != null)
{
end.SectorMultiNext.SectorMultiPrevious = start.SectorMultiPrevious;
}
else
{
otherList._last = start.SectorMultiPrevious;
}
var count = 1;
var current = start;
// Assume start and end are in the right order, or bad things happen (crash).
while (current != end)
{
count++;
current = current.SectorMultiNext;
}
otherList.Count -= count;
if (otherList.Count < 0)
{
throw new Exception("Count is negative!");
}
if (_last != null)
{
_last.SectorMultiNext = start;
start.SectorMultiPrevious = _last;
}
else
{
_first = start;
}
_last = end;
Count += count;
Version++;
}
public BaseMulti[] ToArray()
{
var arr = new BaseMulti[Count];
var index = 0;
foreach (var t in this)
{
arr[index++] = t;
}
return arr;
}
public ref struct SectorMultiValueListEnumerator
{
private bool _started;
private BaseMulti _current;
private ref readonly SectorMultiValueLinkList _linkList;
private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SectorMultiValueListEnumerator(in SectorMultiValueLinkList linkList)
{
_linkList = ref linkList;
_started = false;
_current = null;
_version = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (!_started)
{
_current = _linkList._first;
_started = true;
_version = _linkList.Version;
}
else if (_linkList.Version != _version)
{
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
else
{
_current = _current.SectorMultiNext;
}
return _current != null;
}
public BaseMulti Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
}
public ref struct DescendingSectorMultiValueListEnumerator
{
private bool _started;
private BaseMulti _current;
private ref readonly SectorMultiValueLinkList _linkList;
private int _version;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DescendingSectorMultiValueListEnumerator(in SectorMultiValueLinkList linkList)
{
_linkList = ref linkList;
_started = false;
_current = null;
_version = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (!_started)
{
_current = _linkList._last;
_started = true;
_version = _linkList.Version;
}
else if (_linkList.Version != _version)
{
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
else
{
_current = _current.SectorMultiPrevious;
}
return _current != null;
}
public BaseMulti Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DescendingSectorMultiValueListEnumerator GetEnumerator() => this;
}
}
public static class SectorMultiValueLinkListExt
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SectorMultiValueLinkList.SectorMultiValueListEnumerator GetEnumerator(this in SectorMultiValueLinkList linkList)
=> new(in linkList);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SectorMultiValueLinkList.DescendingSectorMultiValueListEnumerator ByDescending(this in SectorMultiValueLinkList linkList)
=> new(in linkList);
}

View file

@ -161,14 +161,17 @@ public partial class Container
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool SetNextContainer()
{
if (!_containers.TryDequeue(out var c))
while (_containers.TryDequeue(out var c))
{
return false;
_items = CollectionsMarshal.AsSpan(c.m_Items);
_index = 0;
if (SetNextItem())
{
return true;
}
}
_items = CollectionsMarshal.AsSpan(c.m_Items);
_index = 0;
return SetNextItem();
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View file

@ -0,0 +1,263 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Map.MultiEnumerator.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Server.Items;
namespace Server;
public partial class Map
{
private static SectorMultiValueLinkList _emptyMultiLinkList = new();
public static ref readonly SectorMultiValueLinkList EmptyMultiLinkList => ref _emptyMultiLinkList;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<BaseMulti> GetMultisAt(Point3D p) => GetMultisAt<BaseMulti>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<T> GetMultisAt<T>(Point3D p) where T : BaseMulti => GetMultisAt<T>(new Point2D(p.X, p.Y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<BaseMulti> GetMultisAt(int x, int y) => GetMultisAt<BaseMulti>(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<T> GetMultisAt<T>(int x, int y) where T : BaseMulti => GetMultisAt<T>(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<BaseMulti> GetMultisAt(Point2D p) => GetMultisAt<BaseMulti>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerable<T> GetMultisAt<T>(Point2D p) where T : BaseMulti => new(this, p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInRange(Point3D p) => GetMultisInRange<BaseMulti>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInRange(Point3D p, int range) => GetMultisInRange<BaseMulti>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(Point3D p) where T : BaseMulti => GetMultisInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(Point3D p, int range) where T : BaseMulti =>
GetMultisInRange<T>(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInRange(Point2D p) => GetMultisInRange<BaseMulti>(p);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInRange(Point2D p, int range) => GetMultisInRange<BaseMulti>(p, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(Point2D p) where T : BaseMulti => GetMultisInRange<T>(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(Point2D p, int range) where T : BaseMulti =>
GetMultisInRange<T>(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInRange<T>(int x, int y, int range) where T : BaseMulti =>
GetMultisInBounds<T>(new Rectangle2D(x - range, y - range, range * 2 + 1, range * 2 + 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<BaseMulti> GetMultisInBounds(Rectangle2D bounds) => GetMultisInBounds<BaseMulti>(bounds);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiBoundsEnumerable<T> GetMultisInBounds<T>(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : BaseMulti =>
new(this, bounds, makeBoundsInclusive);
public ref struct MultiAtEnumerable<T> where T : BaseMulti
{
public static MultiAtEnumerable<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new();
}
private readonly Map _map;
private readonly Point2D _location;
public MultiAtEnumerable(Map map, Point2D loc)
{
_map = map;
_location = loc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerator<T> GetEnumerator() => new(_map, _location);
}
public ref struct MultiAtEnumerator<T> where T : BaseMulti
{
private Point2D _location;
private readonly Span<BaseMulti> _list;
private int _index;
private T _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiAtEnumerator(Map map, Point2D loc)
{
_location = loc;
_list = map == null
? Span<BaseMulti>.Empty
: CollectionsMarshal.AsSpan(map.GetRealSector(loc.m_X, loc.m_Y).Multis);
_index = 0;
_current = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
ref var loc = ref _location;
while ((uint)_index < (uint)_list.Length)
{
var current = _list[_index++];
if (current is T { Deleted: false } o && o.X == loc.m_X && o.Y == loc.m_Y)
{
_current = o;
return true;
}
}
return false;
}
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
}
public ref struct MultiBoundsEnumerable<T> where T : BaseMulti
{
public static MultiBoundsEnumerable<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(null, Rectangle2D.Empty, false);
}
private Map _map;
private Rectangle2D _bounds;
private bool _makeBoundsInclusive;
public MultiBoundsEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
{
_map = map;
_bounds = bounds;
_makeBoundsInclusive = makeBoundsInclusive;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiEnumerator<T> GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive);
}
public ref struct MultiEnumerator<T> where T : BaseMulti
{
private readonly Map _map;
private readonly int _sectorStartX;
private readonly int _sectorEndX;
private readonly int _sectorEndY;
private Rectangle2D _bounds;
private int _currentSectorX;
private int _currentSectorY;
private Span<BaseMulti> _list;
private T _current;
private int _index;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
{
_map = map;
_bounds = bounds;
if (makeBoundsInclusive)
{
++bounds.Width;
++bounds.Height;
}
_bounds = bounds;
map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
// We start the X sector one short because it gets incremented immediately in MoveNext()
_currentSectorX = _sectorStartX - 1;
_currentSectorY = _sectorStartY;
_index = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool GetMulti()
{
ref Rectangle2D bounds = ref _bounds;
while ((uint)_index < (uint)_list.Length)
{
var current = _list[_index++];
if (current is T { Deleted: false } o && bounds.Contains(o.Location))
{
_current = o;
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool GetSector()
{
var currentSectorX = _currentSectorX;
var currentSectorY = _currentSectorY;
var sectorEndX = _sectorEndX;
var sectorEndY = _sectorEndY;
// Move to next sector
if (currentSectorX < sectorEndX)
{
_currentSectorX = ++currentSectorX;
}
else if (currentSectorY < sectorEndY)
{
_currentSectorX = currentSectorX = _sectorStartX;
_currentSectorY = ++currentSectorY;
}
else
{
// Ran out of sectors
return false;
}
_list = CollectionsMarshal.AsSpan(_map.GetRealSector(currentSectorX, currentSectorY).Multis);
return GetMulti();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => _map != null && (GetMulti() || GetSector());
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
}
}

View file

@ -0,0 +1,121 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Map.MultiTileEnumerator.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Runtime.CompilerServices;
using Server.Items;
namespace Server;
public partial class Map
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiTilesAtEnumerable GetMultiTilesAt(int x, int y) => GetMultiTilesAt(new Point2D(x, y));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiTilesAtEnumerable GetMultiTilesAt(Point2D p) => new(this, p);
public ref struct MultiTilesAtEnumerable
{
public static MultiTilesAtEnumerable Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new();
}
private readonly Map _map;
private readonly Point2D _location;
public MultiTilesAtEnumerable(Map map, Point2D loc)
{
_map = map;
_location = loc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiTilesAtEnumerator GetEnumerator() => new(_map, _location);
}
public ref struct MultiTilesAtEnumerator
{
private Point2D _location;
private MultiAtEnumerator<BaseMulti> _multis;
private BaseMulti _currentMulti;
private StaticTile[] _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MultiTilesAtEnumerator(Map map, Point2D loc)
{
_multis = (map == null ? MultiAtEnumerable<BaseMulti>.Empty : map.GetMultisAt(loc)).GetEnumerator();
_current = null;
_location = loc;
_currentMulti = null;
}
private bool SetStaticTiles()
{
var mcl = _currentMulti.Components;
var x = _location.X;
var xo = x - (_currentMulti.X + mcl.Min.X);
var y = _location.Y;
if (xo < 0 || xo >= mcl.Width)
{
return false;
}
var yo = y - (_currentMulti.Y + mcl.Min.Y);
if (yo < 0 || yo >= mcl.Height)
{
return false;
}
var t = mcl.Tiles[xo][yo];
// TODO: Remove the allocation.
var r = new StaticTile[t.Length];
for (var i = 0; i < t.Length; i++)
{
r[i] = t[i];
r[i].Z += _currentMulti.Z;
}
_current = r;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
while (_multis.MoveNext())
{
_currentMulti = _multis.Current;
if (SetStaticTiles())
{
return true;
}
}
return false;
}
public StaticTile[] Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _current;
}
}
}

View file

@ -69,7 +69,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
m_Name = name;
Rules = rules;
Regions = new Dictionary<string, Region>(StringComparer.OrdinalIgnoreCase);
InvalidSector = new Sector(0, 0, this);
_invalidSector = new Sector(0, 0, this);
m_SectorsWidth = width >> SectorShift;
m_SectorsHeight = height >> SectorShift;
m_Sectors = new Sector[m_SectorsWidth][];
@ -123,7 +123,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public MapRules Rules { get; set; }
public Sector InvalidSector { get; }
private readonly Sector _invalidSector;
public string Name
{
@ -566,7 +566,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y)
{
var sect = GetRealSector(x, y);
if (sect != InvalidSector)
if (sect != _invalidSector)
{
sect.Activate();
}
@ -581,7 +581,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y)
{
var sect = GetRealSector(x, y);
if (sect != InvalidSector && !PlayersInRange(sect, SectorActiveRange))
if (sect != _invalidSector && !PlayersInRange(sect, SectorActiveRange))
{
sect.Deactivate();
}
@ -596,7 +596,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
for (var y = sect.Y - range; y <= sect.Y + range; ++y)
{
var check = GetRealSector(x, y);
if (check != InvalidSector && check.Clients.Count > 0)
if (check != _invalidSector && check.Clients.Count > 0)
{
return true;
}
@ -690,7 +690,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
}
}
public void RemoveMulti(BaseMulti m, Sector start, Sector end)
private void RemoveMulti(BaseMulti m, Sector start, Sector end)
{
if (this == Internal)
{
@ -706,7 +706,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
}
}
public void AddMulti(BaseMulti m, Sector start, Sector end)
private void AddMulti(BaseMulti m, Sector start, Sector end)
{
if (this == Internal)
{
@ -862,9 +862,6 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
return p;
}
public IPooledEnumerable<StaticTile[]> GetMultiTilesAt(int x, int y) =>
PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1));
public bool CanFit(
Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true,
bool requireSurface = true
@ -1011,7 +1008,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
return sec;
}
return InvalidSector;
return _invalidSector;
}
public bool LineOfSight(Point3D org, Point3D dest)
@ -1408,13 +1405,12 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public class Sector
{
// TODO: Can we avoid this?
private static readonly List<BaseMulti> m_DefaultMultiList = new();
private static readonly List<Region> m_DefaultRectList = new();
private bool m_Active;
private ValueLinkList<NetState> _clients;
private ValueLinkList<Item> _items;
private ValueLinkList<Mobile> _mobiles;
private List<BaseMulti> _multis;
private List<BaseMulti> _multis = new();
private List<Region> _regions;
public Sector(int x, int y, Map owner)
@ -1427,7 +1423,7 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public List<Region> Regions => _regions ?? m_DefaultRectList;
public List<BaseMulti> Multis => _multis ?? m_DefaultMultiList;
internal List<BaseMulti> Multis => _multis;
internal ref ValueLinkList<Mobile> Mobiles => ref _mobiles;
@ -1553,12 +1549,12 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public void OnMultiEnter(BaseMulti multi)
{
Utility.Add(ref _multis, multi);
_multis.Add(multi);
}
public void OnMultiLeave(BaseMulti multi)
{
Utility.Remove(ref _multis, multi);
_multis.Remove(multi);
}
public void Activate()

View file

@ -1,298 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PooledEnumeration.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Server.Items;
namespace Server;
public interface IPooledEnumerable<T> : IEnumerable<T>, IDisposable
{
}
public static class PooledEnumeration
{
public delegate IEnumerable<T> Selector<out T>(Map.Sector sector, Rectangle2D bounds);
static PooledEnumeration()
{
MultiSelector = SelectMultis;
MultiTileSelector = SelectMultiTiles;
}
public static Selector<BaseMulti> MultiSelector { get; set; }
public static Selector<StaticTile[]> MultiTileSelector { get; set; }
public static IEnumerable<BaseMulti> SelectMultis(Map.Sector s, Rectangle2D bounds)
{
var entities = new List<BaseMulti>(s.Multis.Count);
for (int i = s.Multis.Count - 1; i >= 0; --i)
{
BaseMulti multi = s.Multis[i];
if (multi is { Deleted: false } && bounds.Contains(multi.Location))
{
entities.Add(multi);
}
}
return entities;
}
public static IEnumerable<StaticTile[]> SelectMultiTiles(Map.Sector s, Rectangle2D bounds)
{
for (int l = s.Multis.Count - 1; l >= 0; --l)
{
BaseMulti o = s.Multis[l];
if (o?.Deleted != false)
{
continue;
}
MultiComponentList c = o.Components;
int x, y, xo, yo;
StaticTile[] t, r;
for (x = bounds.Start.X; x < bounds.End.X; x++)
{
xo = x - (o.X + c.Min.X);
if (xo < 0 || xo >= c.Width)
{
continue;
}
for (y = bounds.Start.Y; y < bounds.End.Y; y++)
{
yo = y - (o.Y + c.Min.Y);
if (yo < 0 || yo >= c.Height)
{
continue;
}
t = c.Tiles[xo][yo];
if (t.Length <= 0)
{
continue;
}
r = new StaticTile[t.Length];
for (var i = 0; i < t.Length; i++)
{
r[i] = t[i];
r[i].Z += o.Z;
}
yield return r;
}
}
}
}
public static PooledEnumerable<BaseMulti> GetMultis(Map map, Rectangle2D bounds) =>
PooledEnumerable<BaseMulti>.Instantiate(map, bounds, MultiSelector ?? SelectMultis);
public static PooledEnumerable<StaticTile[]> GetMultiTiles(Map map, Rectangle2D bounds) =>
PooledEnumerable<StaticTile[]>.Instantiate(map, bounds, MultiTileSelector ?? SelectMultiTiles);
public static IEnumerable<Map.Sector> EnumerateSectors(Map map, Rectangle2D bounds)
{
if (map == null || map == Map.Internal)
{
yield break;
}
var x1 = bounds.Start.X;
var y1 = bounds.Start.Y;
var x2 = bounds.End.X;
var y2 = bounds.End.Y;
if (!Bound(map, ref x1, ref y1, ref x2, ref y2, out var xSector, out var ySector))
{
yield break;
}
var index = 0;
while (NextSector(map, x1, y1, x2, y2, ref index, ref xSector, ref ySector, out var s))
{
yield return s;
}
}
public static bool Bound(
Map map,
ref int x1,
ref int y1,
ref int x2,
ref int y2,
out int xSector,
out int ySector
)
{
if (map == null || map == Map.Internal)
{
xSector = ySector = 0;
return false;
}
map.Bound(x1, y1, out x1, out y1);
map.Bound(x2 - 1, y2 - 1, out x2, out y2);
x1 >>= Map.SectorShift;
y1 >>= Map.SectorShift;
x2 >>= Map.SectorShift;
y2 >>= Map.SectorShift;
xSector = x1;
ySector = y1;
return true;
}
private static bool NextSector(
Map map,
int x1,
int y1,
int x2,
int y2,
ref int index,
ref int xSector,
ref int ySector,
out Map.Sector s
)
{
if (map == null)
{
s = null;
xSector = ySector = 0;
return false;
}
if (map == Map.Internal)
{
s = map.InvalidSector;
xSector = ySector = 0;
return false;
}
if (index++ > 0)
{
if (++ySector > y2)
{
ySector = y1;
if (++xSector > x2)
{
xSector = x1;
s = map.InvalidSector;
return false;
}
}
}
s = map.GetRealSector(xSector, ySector);
return true;
}
public class NullEnumerable<T> : IPooledEnumerable<T>
{
public static readonly NullEnumerable<T> Instance = new();
private readonly IEnumerable<T> m_Empty = Enumerable.Empty<T>();
IEnumerator IEnumerable.GetEnumerator() => m_Empty.GetEnumerator();
public IEnumerator<T> GetEnumerator() => m_Empty.GetEnumerator();
public void Dispose()
{
}
}
public sealed class PooledEnumerable<T> : IPooledEnumerable<T>
{
private static readonly Queue<PooledEnumerable<T>> _Buffer = new(0x400);
private bool m_IsDisposed;
private List<T> m_Pool = new(0x40);
public PooledEnumerable(IEnumerable<T> pool)
{
m_Pool.AddRange(pool);
}
public void Dispose()
{
if (m_IsDisposed)
{
return;
}
m_IsDisposed = true;
m_Pool.Clear();
m_Pool.Capacity = Math.Max(m_Pool.Capacity, 0x100);
lock (((ICollection)_Buffer).SyncRoot)
{
_Buffer.Enqueue(this);
}
}
~PooledEnumerable()
{
Dispose();
}
IEnumerator IEnumerable.GetEnumerator() => m_Pool.GetEnumerator();
public IEnumerator<T> GetEnumerator() => m_Pool.GetEnumerator();
#pragma warning disable CA1000 // Do not declare static members on generic types
public static PooledEnumerable<T> Instantiate(
Map map, Rectangle2D bounds, Selector<T> selector
)
{
PooledEnumerable<T> e = null;
lock (((ICollection)_Buffer).SyncRoot)
{
if (_Buffer.Count > 0)
{
e = _Buffer.Dequeue();
}
}
var pool = EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds));
if (e == null)
{
return new PooledEnumerable<T>(pool);
}
e.m_Pool.AddRange(pool);
return e;
}
}
}

View file

@ -1,3 +1,18 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TileList.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -6,33 +21,17 @@ namespace Server;
public class TileList
{
private static readonly StaticTile[] m_EmptyTiles = Array.Empty<StaticTile>();
private StaticTile[] m_Tiles;
public TileList()
{
m_Tiles = new StaticTile[8];
Count = 0;
}
private static readonly StaticTile[] _emptyTiles = Array.Empty<StaticTile>();
private StaticTile[] _tiles;
public int Count { get; private set; }
public void AddRange(StaticTile[] tiles)
public void AddRange(ReadOnlySpan<StaticTile> tiles)
{
if (Count + tiles.Length > m_Tiles.Length)
{
var old = m_Tiles;
m_Tiles = new StaticTile[(Count + tiles.Length) * 2];
for (var i = 0; i < old.Length; ++i)
{
m_Tiles[i] = old[i];
}
}
TryResize(tiles.Length);
for (var i = 0; i < tiles.Length; ++i)
{
m_Tiles[Count++] = tiles[i];
_tiles[Count++] = tiles[i];
}
}
@ -41,15 +40,15 @@ public class TileList
public void Add(StaticTile tile)
{
TryResize();
m_Tiles[Count] = tile;
TryResize(1);
_tiles[Count] = tile;
++Count;
}
public void Add(ushort id, byte x, byte y, sbyte z, short hue = 0)
{
TryResize();
ref var tile = ref m_Tiles[Count];
TryResize(1);
ref var tile = ref _tiles[Count];
tile.m_ID = id;
tile.m_X = x;
tile.m_Y = y;
@ -60,22 +59,24 @@ public class TileList
public void Add(ushort id, sbyte z)
{
TryResize();
m_Tiles[Count].m_ID = id;
m_Tiles[Count].m_Z = z;
TryResize(1);
_tiles[Count].m_ID = id;
_tiles[Count].m_Z = z;
++Count;
}
private void TryResize()
private void TryResize(int length)
{
if (Count + 1 > m_Tiles.Length)
_tiles ??= new StaticTile[length];
if (Count + length > _tiles.Length)
{
var old = m_Tiles;
m_Tiles = new StaticTile[old.Length * 2];
var old = _tiles;
_tiles = new StaticTile[old.Length * 2];
for (var i = 0; i < old.Length; ++i)
{
m_Tiles[i] = old[i];
_tiles[i] = old[i];
}
}
}
@ -84,16 +85,13 @@ public class TileList
{
if (Count == 0)
{
return m_EmptyTiles;
return _emptyTiles;
}
var tiles = new StaticTile[Count];
for (var i = 0; i < Count; ++i)
{
tiles[i] = m_Tiles[i];
}
Array.Resize(ref _tiles, Count);
var tiles = _tiles;
_tiles = null;
Count = 0;
return tiles;

View file

@ -240,38 +240,27 @@ public class TileMatrix
{
var tiles = GetStaticBlock(x >> 3, y >> 3);
if (multis)
if (!multis)
{
var eable = _map.GetMultiTilesAt(x, y);
if (eable == PooledEnumeration.NullEnumerable<StaticTile[]>.Instance)
{
return tiles[x & 0x7][y & 0x7];
}
var any = false;
foreach (var multiTiles in eable)
{
if (!any)
{
any = true;
}
m_TilesList.AddRange(multiTiles);
}
if (!any)
{
return tiles[x & 0x7][y & 0x7];
}
m_TilesList.AddRange(tiles[x & 0x7][y & 0x7]);
return m_TilesList.ToArray();
return tiles[x & 0x7][y & 0x7];
}
return tiles[x & 0x7][y & 0x7];
var any = false;
foreach (var multiTiles in _map.GetMultiTilesAt(x, y))
{
any = true;
m_TilesList.AddRange(multiTiles);
}
if (!any)
{
return tiles[x & 0x7][y & 0x7];
}
m_TilesList.AddRange(tiles[x & 0x7][y & 0x7]);
return m_TilesList.ToArray();
}
[MethodImpl(MethodImplOptions.Synchronized)]

View file

@ -243,11 +243,9 @@ namespace Server.Multis
public static BaseBoat FindBoatAt(Point3D loc, Map map)
{
var sector = map.GetSector(loc);
for (var i = 0; i < sector.Multis.Count; i++)
foreach (var boat in map.GetMultisAt<BaseBoat>(loc))
{
if (sector.Multis[i] is BaseBoat boat && boat.Contains(loc.X, loc.Y))
if (boat.Contains(loc.X, loc.Y))
{
return boat;
}

View file

@ -1401,11 +1401,9 @@ namespace Server.Multis
return null;
}
var sector = map.GetSector(loc);
for (var i = 0; i < sector.Multis.Count; ++i)
foreach (var house in map.GetMultisAt<BaseHouse>(loc))
{
if (sector.Multis[i] is BaseHouse house && house.IsInside(loc, height))
if (house.IsInside(loc, height))
{
return house;
}

View file

@ -370,58 +370,35 @@ namespace Server.Multis
}
}
var _sectors = new List<Map.Sector>();
var _houses = new List<BaseHouse>();
for (var i = 0; i < yard.Count; i++)
{
var sector = map.GetSector(yard[i]);
var yardPoint = yard[i];
if (!_sectors.Contains(sector))
foreach (var house in map.GetMultisAt<BaseHouse>(yardPoint))
{
_sectors.Add(sector);
for (var j = 0; j < sector.Multis?.Count; j++)
{
if (sector.Multis[j] is BaseHouse)
{
var _house = (BaseHouse)sector.Multis[j];
if (!_houses.Contains(_house))
{
_houses.Add(_house);
}
}
}
}
}
for (var i = 0; i < yard.Count; ++i)
{
foreach (var b in _houses)
{
if (b.Contains(yard[i]))
if (house.Contains(yard[i]))
{
return HousePlacementResult.BadStatic; // Broke rule #3
}
}
}
/*Point2D yardPoint = yard[i];
IPooledEnumerable eable = map.GetMultiTilesAt( yardPoint.X, yardPoint.Y );
foreach ( StaticTile[] tile in eable )
{
for ( int j = 0; j < tile.Length; ++j )
{
if ((TileData.ItemTable[tile[j].ID & TileData.MaxItemValue].Flags & (TileFlag.Impassable | TileFlag.Surface)) != 0)
{
eable.Free();
return HousePlacementResult.BadStatic; // Broke rule #3
}
}
}
eable.Free();*/
// TODO: Should we check for MultiTilesAt each yard point?
// for (var i = 0; i < yard.Count; i++)
// {
// var yardPoint = yard[i];
//
// foreach (var tiles in map.GetMultiTilesAt(yardPoint))
// {
// for (int j = 0; j < tiles.Length; ++j)
// {
// if ((TileData.ItemTable[tiles[j].ID & TileData.MaxItemValue].Flags & (TileFlag.Impassable | TileFlag.Surface)) != 0)
// {
// return HousePlacementResult.BadStatic; // Broke rule #3
// }
// }
// }
// }
return HousePlacementResult.Valid;
}

View file

@ -158,12 +158,8 @@ namespace Server.Spells
return false;
}
var sector = map.GetSector(p.X, p.Y);
for (var i = 0; i < sector.Multis.Count; ++i)
foreach (var multi in map.GetMultisAt(p))
{
var multi = sector.Multis[i];
if (multi is BaseHouse bh)
{
if (houses && bh.IsInside(p, 16) || housingrange > 0 && bh.InRange(p, housingrange))