diff --git a/Projects/Server/Geometry/Rectangle2D.cs b/Projects/Server/Geometry/Rectangle2D.cs index fdf04dbc9..959873a18 100644 --- a/Projects/Server/Geometry/Rectangle2D.cs +++ b/Projects/Server/Geometry/Rectangle2D.cs @@ -22,6 +22,8 @@ namespace Server; [PropertyObject] public struct Rectangle2D : IEquatable, ISpanFormattable, ISpanParsable { + public static Rectangle2D Empty => new(); + private Point2D _start; private Point2D _end; diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 5d928c2d4..767efa6df 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -2473,13 +2473,13 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt : map.GetObjectsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); } - public IPooledEnumerable GetItemsInRange(int range) - { - var map = m_Map; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemEnumerable GetItemsInRange(int range) => + m_Map == null ? Map.ItemEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); - return map?.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? PooledEnumeration.NullEnumerable.Instance; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemEnumerable GetItemsInRange(int range) where T : Item => + m_Map == null ? Map.ItemEnumerable.Empty : m_Map.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); public IPooledEnumerable GetMobilesInRange(int range) { @@ -3547,10 +3547,8 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt z = top; } - var eable = map.GetItemsInRange(p, 0); - - var items = new List(); - foreach (var item in eable) + using var items = PooledRefList.Create(); + foreach (var item in map.GetItemsInRange(p, 0)) { if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) { @@ -3611,7 +3609,7 @@ public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEnt m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); } - for (var i = 0; i < items.Count; ++i) + for (var i = 0; i < items.Count; i++) { var item = items[i]; var id = item.ItemData; diff --git a/Projects/Server/Maps/Map.ItemEnumerator.cs b/Projects/Server/Maps/Map.ItemEnumerator.cs new file mode 100644 index 000000000..ba1147bc7 --- /dev/null +++ b/Projects/Server/Maps/Map.ItemEnumerator.cs @@ -0,0 +1,222 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2023 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Map.Enumerators.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 . * + *************************************************************************/ + +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Server; + +public partial class Map +{ + private int _iteratingItems; + private List<(MapAction, Point3D, Item)> _delayedItemActions = new(); + + public bool IsIteratingItems + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _iteratingItems > 0; + } + + public ItemEnumerable GetItemsInRange(Point3D p) => GetItemsInRange(p, Core.GlobalMaxUpdateRange); + + public ItemEnumerable GetItemsInRange(Point3D p, int range) => GetItemsInRange(p, range); + + public ItemEnumerable GetItemsInRange(Point3D p, int range) where T : Item => + GetItemsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + + public ItemEnumerable GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds(bounds); + + public ItemEnumerable GetItemsInBounds(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item => + new(this, bounds, makeBoundsInclusive); + + private void BeginIteratingItems() + { +#if THREADGUARD + if (Thread.CurrentThread != Core.Thread) + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine($"Iterating through items on {this} from an invalid thread!"); + Console.WriteLine(new StackTrace()); + Utility.PopColor(); + return; + } +#endif + + _iteratingItems++; + } + + private void EndIteratingItems() + { +#if THREADGUARD + if (Thread.CurrentThread != Core.Thread) + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine($"Iterating through items on {this} from an invalid thread!"); + Console.WriteLine(new StackTrace()); + Utility.PopColor(); + return; + } +#endif + + _iteratingItems--; + + // Finished iterating, check for deferred actions + if (_iteratingItems == 0 && _delayedItemActions.Count > 0) + { + foreach (var (a, p, i) in _delayedItemActions) + { + switch (a) + { + case MapAction.Enter: + { + OnEnter(p, i); + break; + } + case MapAction.Leave: + { + OnLeave(p, i); + break; + } + case MapAction.Move: + { + OnMove(p, i); + break; + } + } + } + + _delayedItemActions.Clear(); + } + } + + public ref struct ItemEnumerable where T : Item + { + public static ItemEnumerable Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(null, Rectangle2D.Empty, false); + } + + private Map _map; + private Rectangle2D _bounds; + private bool _makeBoundsInclusive; + + public ItemEnumerable(Map map, Rectangle2D bounds, bool makeBoundsInclusive) + { + _map = map; + _bounds = bounds; + _makeBoundsInclusive = makeBoundsInclusive; + } + + // The enumerator MUST be disposed. Not disposing it will damage the sector irreparably. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ItemEnumerator GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive); + } + + public ref struct ItemEnumerator where T : Item + { + private Map _map; + private int _sectorStartX; + private int _sectorEndX; + private int _sectorEndY; + private Rectangle2D _bounds; + + private int _currentSectorX; + private int _currentSectorY; + private T _current; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ItemEnumerator(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; + + _map.BeginIteratingItems(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + var map = _map; + + if (map == null) + { + return false; + } + + Item current = _current; + ref Rectangle2D bounds = ref _bounds; + var currentSectorX = _currentSectorX; + var currentSectorY = _currentSectorY; + var sectorEndX = _sectorEndX; + var sectorEndY = _sectorEndY; + + while (true) + { + current = current?.Next; + + while (current == null) + { + // 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; + } + + current = map.GetRealSector(currentSectorX, currentSectorY).Items.First; + } + + if (current is T { Deleted: false, Parent: null } o && bounds.Contains(o.Location)) + { + _current = o; + return true; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() => _map.EndIteratingItems(); + + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _current; + } + } +} diff --git a/Projects/Server/Maps/Map.cs b/Projects/Server/Maps/Map.cs index 59be13ca9..30ccecc61 100644 --- a/Projects/Server/Maps/Map.cs +++ b/Projects/Server/Maps/Map.cs @@ -38,7 +38,7 @@ public enum MapRules FeluccaRules = None } -public sealed class Map : IComparable, ISpanFormattable, ISpanParsable +public sealed partial class Map : IComparable, ISpanFormattable, ISpanParsable { public const int SectorSize = 16; public const int SectorShift = 4; @@ -297,9 +297,6 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable return v / 2; } - public IPooledEnumerable GetMultiTilesAt(int x, int y) => - PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1)); - private static void AcquireFixItems(Map map, int x, int y, Item[] pool, out int length) { length = 0; @@ -308,8 +305,8 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable return; } - var eable = map.GetItemsInRange(new Point3D(x, y, 0), 0); - foreach (var item in eable) + var p = new Point3D(x, y, 0); + foreach (var item in map.GetItemsInRange(p, 0)) { if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue) { @@ -489,28 +486,28 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable var sector = GetSector(p.X, p.Y); - for (var i = 0; i < sector.Items.Count; i++) + foreach (var item in sector.Items) { - var item = sector.Items[i]; - - if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(p.X, p.Y) && - !item.Movable) + if (item is BaseMulti || item.ItemID > TileData.MaxItemValue || !item.AtWorldPoint(p.X, p.Y) || + item.Movable) { - var id = item.ItemData; + continue; + } - if (id.Surface || id.Wet) + var id = item.ItemData; + + if (id.Surface || id.Wet) + { + var itemZ = item.Z + id.CalcHeight; + + if (itemZ > surfaceZ && itemZ <= p.Z) { - var itemZ = item.Z + id.CalcHeight; + surface = item; + surfaceZ = itemZ; - if (itemZ > surfaceZ && itemZ <= p.Z) + if (surfaceZ == p.Z) { - surface = item; - surfaceZ = itemZ; - - if (surfaceZ == p.Z) - { - return surface; - } + return surface; } } } @@ -538,6 +535,29 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable return new Point2D(x, y); } + private void CalculateSectors( + Rectangle2D bounds, + out int sectorStartX, out int sectorStartY, + out int sectorEndX, out int sectorEndY) + { + int left = bounds.Start.X; + int top = bounds.Start.Y; + int right = bounds.End.X; + int bottom = bounds.End.Y; + + // Limit the coordinates to inside the valid map region + Bound(left, top, out left, out top); + Bound(right - 1, bottom - 1, out right, out bottom); + + // Calculate the top left sector + sectorStartX = left >> SectorShift; + sectorStartY = top >> SectorShift; + + // Calculate the bottom right sector. + sectorEndX = right >> SectorShift; + sectorEndY = bottom >> SectorShift; + } + public void ActivateSectors(int cx, int cy) { for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) @@ -593,22 +613,36 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable } } - public void OnEnter(Mobile m) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnEnter(Mobile m) => OnEnter(m.Location, m); + + public void OnEnter(Point3D p, Mobile m) { if (this != Internal) { - GetSector(m.Location).OnEnter(m); + GetSector(p).OnEnter(m); } } public void OnEnter(Item item) + { + if (IsIteratingItems) + { + _delayedItemActions.Add((MapAction.Enter, item.Location, item)); + return; + } + + OnEnter(item.Location, item); + } + + public void OnEnter(Point3D p, Item item) { if (this == Internal) { return; } - GetSector(item.Location).OnEnter(item); + GetSector(p).OnEnter(item); if (item is BaseMulti m) { @@ -621,22 +655,36 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable } } - public void OnLeave(Mobile m) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnLeave(Mobile m) => OnLeave(m.Location, m); + + public void OnLeave(Point3D p, Mobile m) { if (this != Internal) { - GetSector(m.Location).OnLeave(m); + GetSector(p).OnLeave(m); } } public void OnLeave(Item item) + { + if (IsIteratingItems) + { + _delayedItemActions.Add((MapAction.Leave, item.Location, item)); + return; + } + + OnLeave(item.Location, item); + } + + public void OnLeave(Point3D p, Item item) { if (this == Internal) { return; } - GetSector(item.Location).OnLeave(item); + GetSector(p).OnLeave(item); if (item is BaseMulti m) { @@ -716,6 +764,12 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable if (oldSector != newSector) { + if (IsIteratingItems) + { + _delayedItemActions.Add((MapAction.Move, item.Location, item)); + return; + } + oldSector.OnLeave(item); newSector.OnEnter(item); } @@ -732,6 +786,12 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable if (oldStart != start || oldEnd != end) { + if (IsIteratingItems) + { + _delayedItemActions.Add((MapAction.Enter, oldLocation, item)); + return; + } + RemoveMulti(m, oldStart, oldEnd); AddMulti(m, start, end); } @@ -821,6 +881,9 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable return p; } + public IPooledEnumerable GetMultiTilesAt(int x, int y) => + PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1)); + public IPooledEnumerable GetObjectsInRange(Point3D p) => GetObjectsInRange(p, Core.GlobalMaxUpdateRange); public IPooledEnumerable GetObjectsInRange(Point3D p, int range) => @@ -837,18 +900,6 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) => PooledEnumeration.GetClients(this, bounds); - public IPooledEnumerable GetItemsInRange(Point3D p) => GetItemsInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetItemsInRange(Point3D p, int range) => GetItemsInRange(p, range); - - public IPooledEnumerable GetItemsInRange(Point3D p, int range) where T : Item => - GetItemsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds(bounds); - - public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) where T : Item => - PooledEnumeration.GetItems(this, bounds); - public IPooledEnumerable GetMobilesInRange(Point3D p) => GetMobilesInRange(p, Core.GlobalMaxUpdateRange); public IPooledEnumerable GetMobilesInRange(Point3D p, int range) => GetMobilesInRange(p, range); @@ -926,29 +977,28 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable } var sector = GetSector(x, y); - var items = sector.Items; var mobs = sector.Mobiles; - for (var i = 0; i < items.Count; ++i) + foreach (var item in sector.Items) { - var item = items[i]; - - if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(x, y)) + if (item is BaseMulti || item.ItemID > TileData.MaxItemValue || !item.AtWorldPoint(x, y)) { - var id = item.ItemData; - surface = id.Surface; - impassable = id.Impassable; + continue; + } - if ((surface || impassable || checkBlocksFit && item.BlocksFit) && item.Z + id.CalcHeight > z && - z + height > item.Z) - { - return false; - } + var id = item.ItemData; + surface = id.Surface; + impassable = id.Impassable; - if (surface && !impassable && !item.Movable && z == item.Z + id.CalcHeight) - { - hasSurface = true; - } + if ((surface || impassable || checkBlocksFit && item.BlocksFit) && item.Z + id.CalcHeight > z && + z + height > item.Z) + { + return false; + } + + if (surface && !impassable && !item.Movable && z == item.Z + id.CalcHeight) + { + hasSurface = true; } } @@ -1140,9 +1190,7 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable if (contains && statics.Length == 0) { - var eable = GetItemsInRange(point, 0); - - foreach (Item item in eable) + foreach (Item item in GetItemsInRange(point, 0)) { if (item.Visible) { @@ -1180,9 +1228,7 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable var rect = new Rectangle2D(pTop.m_X, pTop.m_Y, pBottom.m_X - pTop.m_X + 1, pBottom.m_Y - pTop.m_Y + 1); - var area = GetItemsInBounds(rect); - - foreach (var i in area) + foreach (var i in GetItemsInBounds(rect)) { if (!i.Visible) { @@ -1412,39 +1458,27 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable return false; } - public class RegionRect : IComparable + private enum MapAction { - private Rectangle3D m_Rect; - - public RegionRect(Region region, Rectangle3D rect) - { - Region = region; - m_Rect = rect; - } - - public Region Region { get; } - - public Rectangle3D Rect => m_Rect; - - public int CompareTo(RegionRect regRect) => regRect == null ? 1 : Region.CompareTo(regRect.Region); - - public bool Contains(Point3D loc) => m_Rect.Contains(loc); + None, + Enter, + Leave, + Move } public class Sector { // TODO: Can we avoid this? private static readonly List m_DefaultMobileList = new(); - private static readonly List m_DefaultItemList = new(); private static readonly List m_DefaultClientList = new(); private static readonly List m_DefaultMultiList = new(); - private static readonly List m_DefaultRectList = new(); + private static readonly List m_DefaultRectList = new(); private bool m_Active; - private List m_Clients; - private List m_Items; - private List m_Mobiles; - private List m_Multis; - private List m_RegionRects; + private List _clients; + private ValueLinkList _items; + private List _mobiles; + private List _multis; + private List _regions; public Sector(int x, int y, Map owner) { @@ -1454,17 +1488,17 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable m_Active = false; } - public List RegionRects => m_RegionRects ?? m_DefaultRectList; + public List Regions => _regions ?? m_DefaultRectList; - public List Multis => m_Multis ?? m_DefaultMultiList; + public List Multis => _multis ?? m_DefaultMultiList; - public List Mobiles => m_Mobiles ?? m_DefaultMobileList; + public List Mobiles => _mobiles ?? m_DefaultMobileList; - public List Items => m_Items ?? m_DefaultItemList; + public ref ValueLinkList Items => ref _items; - public List Clients => m_Clients ?? m_DefaultClientList; + public List Clients => _clients ?? m_DefaultClientList; - public bool Active => m_Active && Owner != Map.Internal; + public bool Active => m_Active && Owner != Internal; public Map Owner { get; } @@ -1474,26 +1508,26 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable public void OnClientChange(NetState oldState, NetState newState) { - Utility.Replace(ref m_Clients, oldState, newState); + Utility.Replace(ref _clients, oldState, newState); } public void OnEnter(Item item) { - Utility.Add(ref m_Items, item); + _items.AddLast(item); } public void OnLeave(Item item) { - Utility.Remove(ref m_Items, item); + _items.Remove(item); } public void OnEnter(Mobile mob) { - Utility.Add(ref m_Mobiles, mob); + Utility.Add(ref _mobiles, mob); if (mob.NetState != null) { - Utility.Add(ref m_Clients, mob.NetState); + Utility.Add(ref _clients, mob.NetState); Owner.ActivateSectors(X, Y); } @@ -1501,11 +1535,11 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable public void OnLeave(Mobile mob) { - Utility.Remove(ref m_Mobiles, mob); + Utility.Remove(ref _mobiles, mob); if (mob.NetState != null) { - Utility.Remove(ref m_Clients, mob.NetState); + Utility.Remove(ref _clients, mob.NetState); Owner.DeactivateSectors(X, Y); } @@ -1513,31 +1547,31 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable public void OnEnter(Region region, Rectangle3D rect) { - Utility.Add(ref m_RegionRects, new RegionRect(region, rect)); + Utility.Add(ref _regions, region); - m_RegionRects.Sort(); + _regions.Sort(); UpdateMobileRegions(); } public void OnLeave(Region region) { - if (m_RegionRects != null) + if (_regions != null) { - for (var i = m_RegionRects.Count - 1; i >= 0; i--) + for (var i = _regions.Count - 1; i >= 0; i--) { - var regRect = m_RegionRects[i]; + var r = _regions[i]; - if (regRect.Region == region) + if (r == region) { - m_RegionRects.RemoveAt(i); + _regions.RemoveAt(i); break; } } - if (m_RegionRects.Count == 0) + if (_regions.Count == 0) { - m_RegionRects = null; + _regions = null; } } @@ -1546,10 +1580,10 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable private void UpdateMobileRegions() { - if (m_Mobiles != null) + if (_mobiles != null) { - using var queue = PooledRefQueue.Create(m_Mobiles.Count); - foreach (var mob in m_Mobiles) + using var queue = PooledRefQueue.Create(_mobiles.Count); + foreach (var mob in _mobiles) { queue.Enqueue(mob); } @@ -1563,29 +1597,26 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable public void OnMultiEnter(BaseMulti multi) { - Utility.Add(ref m_Multis, multi); + Utility.Add(ref _multis, multi); } public void OnMultiLeave(BaseMulti multi) { - Utility.Remove(ref m_Multis, multi); + Utility.Remove(ref _multis, multi); } public void Activate() { if (!Active) { - if (m_Items != null) + foreach (var item in _items) { - foreach (var item in m_Items) - { - item.OnSectorActivate(); - } + item.OnSectorActivate(); } - if (m_Mobiles != null) + if (_mobiles != null) { - foreach (var mob in m_Mobiles) + foreach (var mob in _mobiles) { mob.OnSectorActivate(); } @@ -1599,17 +1630,14 @@ public sealed class Map : IComparable, ISpanFormattable, ISpanParsable { if (Active) { - if (m_Items != null) + foreach (var item in _items) { - foreach (var item in m_Items) - { - item.OnSectorDeactivate(); - } + item.OnSectorDeactivate(); } - if (m_Mobiles != null) + if (_mobiles != null) { - foreach (var mob in m_Mobiles) + foreach (var mob in _mobiles) { mob.OnSectorDeactivate(); } diff --git a/Projects/Server/Maps/PooledEnumeration.cs b/Projects/Server/Maps/PooledEnumeration.cs index b9fe406b7..8f5eeb818 100644 --- a/Projects/Server/Maps/PooledEnumeration.cs +++ b/Projects/Server/Maps/PooledEnumeration.cs @@ -35,7 +35,6 @@ public static class PooledEnumeration ClientSelector = SelectClients; EntitySelector = SelectEntities; MobileSelector = SelectMobiles; - ItemSelector = SelectItems; MultiSelector = SelectMultis; MultiTileSelector = SelectMultiTiles; } @@ -43,7 +42,6 @@ public static class PooledEnumeration public static Selector ClientSelector { get; set; } public static Selector EntitySelector { get; set; } public static Selector MobileSelector { get; set; } - public static Selector ItemSelector { get; set; } public static Selector MultiSelector { get; set; } public static Selector MultiTileSelector { get; set; } @@ -66,26 +64,20 @@ public static class PooledEnumeration public static IEnumerable SelectEntities(Map.Sector s, Rectangle2D bounds) { var entities = new List(s.Mobiles.Count + s.Items.Count); - for (int i = s.Mobiles.Count - 1, j = s.Items.Count - 1; i >= 0 || j >= 0; --i, --j) + for (int i = s.Mobiles.Count - 1; i >= 0; --i) { - if (j >= 0) + Mobile mob = s.Mobiles[i]; + if (mob is { Deleted: false } && bounds.Contains(mob.Location)) { - Item item = s.Items[j]; - if (item is { Deleted: false, Parent: null } && bounds.Contains(item.Location)) - { - entities.Add(item); - } - } - - if (i >= 0) - { - Mobile mob = s.Mobiles[i]; - if (mob is { Deleted: false } && bounds.Contains(mob.Location)) - { - entities.Add(mob); - } + entities.Add(mob); } } + + foreach (var item in s.Items) + { + entities.Add(item); + } + return entities; } @@ -102,19 +94,6 @@ public static class PooledEnumeration return entities; } - public static IEnumerable SelectItems(Map.Sector s, Rectangle2D bounds) where T : Item - { - var entities = new List(s.Items.Count); - for (int i = s.Items.Count - 1; i >= 0; --i) - { - if (s.Items[i] is T { Deleted: false, Parent: null } item && bounds.Contains(item.Location)) - { - entities.Add(item); - } - } - return entities; - } - public static IEnumerable SelectMultis(Map.Sector s, Rectangle2D bounds) { var entities = new List(s.Multis.Count); @@ -195,9 +174,6 @@ public static class PooledEnumeration public static PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) where T : Mobile => PooledEnumerable.Instantiate(map, bounds, SelectMobiles); - public static PooledEnumerable GetItems(Map map, Rectangle2D bounds) where T : Item => - PooledEnumerable.Instantiate(map, bounds, SelectItems); - public static PooledEnumerable GetMultis(Map map, Rectangle2D bounds) => PooledEnumerable.Instantiate(map, bounds, MultiSelector ?? SelectMultis); @@ -363,7 +339,7 @@ public static class PooledEnumeration #pragma warning disable CA1000 // Do not declare static members on generic types public static PooledEnumerable Instantiate( - Map map, Rectangle2D bounds, PooledEnumeration.Selector selector + Map map, Rectangle2D bounds, Selector selector ) { PooledEnumerable e = null; @@ -376,7 +352,7 @@ public static class PooledEnumeration } } - var pool = PooledEnumeration.EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds)); + var pool = EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds)); if (e == null) { diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 5bc962481..6a99ca045 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -4165,10 +4165,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } } - for (var i = 0; i < oldSector.Items.Count; ++i) + foreach (var item in oldSector.Items) { - var item = oldSector.Items[i]; - if (item.AtWorldPoint(oldX, oldY) && (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && !item.OnMoveOff(this)) @@ -4187,10 +4185,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } } - for (var i = 0; i < newSector.Items.Count; ++i) + foreach (var item in newSector.Items) { - var item = newSector.Items[i]; - if (item.AtWorldPoint(x, y) && (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && !item.OnMoveOver(this)) @@ -4217,10 +4213,8 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro } } - for (var i = 0; i < oldSector.Items.Count; ++i) + foreach (var item in oldSector.Items) { - var item = oldSector.Items[i]; - if (item.AtWorldPoint(oldX, oldY) && (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && !item.OnMoveOff(this)) @@ -8064,10 +8058,12 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro return -1; } - public IPooledEnumerable GetItemsInRange(int range) => GetItemsInRange(range); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemEnumerable GetItemsInRange(int range) => GetItemsInRange(range); - public IPooledEnumerable GetItemsInRange(int range) where T : Item => - m_Map?.GetItemsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable.Instance; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Map.ItemEnumerable GetItemsInRange(int range) where T : Item => + m_Map == null ? Map.ItemEnumerable.Empty : m_Map.GetItemsInRange(m_Location, range); public IPooledEnumerable GetObjectsInRange(int range) => m_Map?.GetObjectsInRange(m_Location, range) ?? PooledEnumeration.NullEnumerable.Instance; diff --git a/Projects/Server/Regions/Region.cs b/Projects/Server/Regions/Region.cs index e45c1955a..f1999ad6f 100644 --- a/Projects/Server/Regions/Region.cs +++ b/Projects/Server/Regions/Region.cs @@ -295,15 +295,15 @@ public class Region : IComparable, IValueLinkListNode } var sector = map.GetSector(p); - var list = sector.RegionRects; + var list = sector.Regions; for (var i = 0; i < list.Count; ++i) { - var regRect = list[i]; + var region = list[i]; - if (regRect.Contains(p)) + if (region.Contains(p)) { - return regRect.Region; + return region; } } diff --git a/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs b/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs index f33234a1b..0fd996bfc 100644 --- a/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs +++ b/Projects/UOContent.Tests/Tests/Multis/Boats/BoatPacketTests.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Server; diff --git a/Projects/UOContent/Commands/Object Creation/Decorate.cs b/Projects/UOContent/Commands/Object Creation/Decorate.cs index 492c773de..67ac954da 100644 --- a/Projects/UOContent/Commands/Object Creation/Decorate.cs +++ b/Projects/UOContent/Commands/Object Creation/Decorate.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using Microsoft.Toolkit.HighPerformance; +using Server.Collections; using Server.Engines.Quests.Haven; using Server.Engines.Quests.Necro; using Server.Engines.Spawners; @@ -78,8 +79,6 @@ namespace Server.Commands private static readonly Type typeofCannon = typeof(Cannon); private static readonly Type typeofSerpentPillar = typeof(SerpentPillar); - private static readonly Queue m_DeleteQueue = new(); - private static readonly string[] m_EmptyParams = Array.Empty(); private List m_Entries; private int m_ItemID; @@ -1037,16 +1036,17 @@ namespace Server.Commands private static bool FindItem(int x, int y, int z, Map map, Item srcItem) { var itemID = srcItem.ItemID; + var lt = srcItem.Light; + var srcName = srcItem.ItemData.Name; + var type = srcItem.GetType(); var res = false; - IPooledEnumerable eable; + using var queue = PooledRefQueue.Create(); - if (srcItem is BaseDoor) + foreach (var item in map.GetItemsInRange(new Point3D(x, y, z), 1)) { - eable = map.GetItemsInRange(new Point3D(x, y, z), 1); - - foreach (var item in eable) + if (srcItem is BaseDoor) { if (!(item is BaseDoor)) { @@ -1079,18 +1079,10 @@ namespace Server.Commands } else if ((item.Z - z).Abs() < 8) { - m_DeleteQueue.Enqueue(item); + queue.Enqueue(item); } } - } - else if (TileData.ItemTable[itemID & TileData.MaxItemValue].LightSource) - { - eable = map.GetItemsInRange(new Point3D(x, y, z), 0); - - var lt = srcItem.Light; - var srcName = srcItem.ItemData.Name; - - foreach (var item in eable) + else if (TileData.ItemTable[itemID & TileData.MaxItemValue].LightSource) { if (item.Z == z) { @@ -1098,7 +1090,7 @@ namespace Server.Commands { if (item.Light != lt) { - m_DeleteQueue.Enqueue(item); + queue.Enqueue(item); } else { @@ -1107,24 +1099,17 @@ namespace Server.Commands } else if (item.ItemData.LightSource && item.ItemData.Name == srcName) { - m_DeleteQueue.Enqueue(item); + queue.Enqueue(item); } } } - } - else if (srcItem is Teleporter or FillableContainer or BaseBook) - { - eable = map.GetItemsInRange(new Point3D(x, y, z), 0); - - var type = srcItem.GetType(); - - foreach (var item in eable) + else if (srcItem is Teleporter or FillableContainer or BaseBook) { if (item.Z == z && item.ItemID == itemID) { if (item.GetType() != type) { - m_DeleteQueue.Enqueue(item); + queue.Enqueue(item); } else { @@ -1132,12 +1117,7 @@ namespace Server.Commands } } } - } - else - { - eable = map.GetItemsInRange(new Point3D(x, y, z), 0); - - foreach (var item in eable) + else { if (item.Z == z && item.ItemID == itemID) { @@ -1146,9 +1126,9 @@ namespace Server.Commands } } - while (m_DeleteQueue.Count > 0) + while (queue.Count > 0) { - m_DeleteQueue.Dequeue().Delete(); + queue.Dequeue().Delete(); } return res; @@ -1196,7 +1176,6 @@ namespace Server.Commands if (item is BaseDoor door) { var eable = maps[j].GetItemsInRange(loc, 1); - var itemType = door.GetType(); foreach (var link in eable) diff --git a/Projects/UOContent/Commands/Object Creation/DecorateMag.cs b/Projects/UOContent/Commands/Object Creation/DecorateMag.cs index 7b10f49a6..6cd025e73 100644 --- a/Projects/UOContent/Commands/Object Creation/DecorateMag.cs +++ b/Projects/UOContent/Commands/Object Creation/DecorateMag.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using Microsoft.Toolkit.HighPerformance; +using Server.Collections; using Server.Engines.Quests.Haven; using Server.Engines.Quests.Necro; using Server.Engines.Spawners; @@ -74,8 +75,6 @@ namespace Server.Commands private static readonly Type typeofCannon = typeof(Cannon); private static readonly Type typeofSerpentPillar = typeof(SerpentPillar); - private static readonly Queue m_DeleteQueue = new(); - private static readonly string[] m_EmptyParams = Array.Empty(); private List m_Entries; private int m_ItemID; @@ -1033,16 +1032,16 @@ namespace Server.Commands private static bool FindItem(int x, int y, int z, Map map, Item srcItem) { var itemID = srcItem.ItemID; + var lt = srcItem.Light; + var srcName = srcItem.ItemData.Name; + var type = srcItem.GetType(); var res = false; - IPooledEnumerable eable; - - if (srcItem is BaseDoor) + using var queue = PooledRefQueue.Create(); + foreach (var item in map.GetItemsInRange(new Point3D(x, y, z), 1)) { - eable = map.GetItemsInRange(new Point3D(x, y, z), 1); - - foreach (var item in eable) + if (srcItem is BaseDoor) { if (!(item is BaseDoor)) { @@ -1075,18 +1074,10 @@ namespace Server.Commands } else if ((item.Z - z).Abs() < 8) { - m_DeleteQueue.Enqueue(item); + queue.Enqueue(item); } } - } - else if (TileData.ItemTable[itemID & TileData.MaxItemValue].LightSource) - { - eable = map.GetItemsInRange(new Point3D(x, y, z), 0); - - var lt = srcItem.Light; - var srcName = srcItem.ItemData.Name; - - foreach (var item in eable) + else if (TileData.ItemTable[itemID & TileData.MaxItemValue].LightSource) { if (item.Z == z) { @@ -1094,7 +1085,7 @@ namespace Server.Commands { if (item.Light != lt) { - m_DeleteQueue.Enqueue(item); + queue.Enqueue(item); } else { @@ -1103,24 +1094,17 @@ namespace Server.Commands } else if (item.ItemData.LightSource && item.ItemData.Name == srcName) { - m_DeleteQueue.Enqueue(item); + queue.Enqueue(item); } } } - } - else if (srcItem is Teleporter or FillableContainer or BaseBook) - { - eable = map.GetItemsInRange(new Point3D(x, y, z), 0); - - var type = srcItem.GetType(); - - foreach (var item in eable) + else if (srcItem is Teleporter or FillableContainer or BaseBook) { if (item.Z == z && item.ItemID == itemID) { if (item.GetType() != type) { - m_DeleteQueue.Enqueue(item); + queue.Enqueue(item); } else { @@ -1128,12 +1112,7 @@ namespace Server.Commands } } } - } - else - { - eable = map.GetItemsInRange(new Point3D(x, y, z), 0); - - foreach (var item in eable) + else { if (item.Z == z && item.ItemID == itemID) { @@ -1142,9 +1121,9 @@ namespace Server.Commands } } - while (m_DeleteQueue.Count > 0) + while (queue.Count > 0) { - m_DeleteQueue.Dequeue()?.Delete(); + queue.Dequeue()?.Delete(); } return res; @@ -1181,11 +1160,9 @@ namespace Server.Commands if (item is BaseDoor door) { - var eable = maps[j].GetItemsInRange(loc, 1); - var itemType = door.GetType(); - foreach (var link in eable) + foreach (var link in maps[j].GetItemsInRange(loc, 1)) { if (link != item && link.Z == door.Z && link.GetType() == itemType) { diff --git a/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs b/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs index 6456571eb..fea3077e5 100644 --- a/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs +++ b/Projects/UOContent/Commands/Object Creation/GenTeleporter.cs @@ -121,17 +121,16 @@ namespace Server.Commands public static int DeleteTeleporters(WorldLocation worldLocation) { - var eable = worldLocation.Map.GetItemsInRange(worldLocation, 0); - var count = 0; - foreach (var item in eable) + foreach (var item in worldLocation.Map.GetItemsInRange(worldLocation, 0)) { - if (!(item is KeywordTeleporter or SkillTeleporter) && IsWithinZ(item.Z - worldLocation.Z)) + if (item is not (KeywordTeleporter or SkillTeleporter) && IsWithinZ(item.Z - worldLocation.Z)) { count++; item.Delete(); } } + return count; } diff --git a/Projects/UOContent/Commands/SignParser.cs b/Projects/UOContent/Commands/SignParser.cs index b08a3dec8..704c38f1b 100644 --- a/Projects/UOContent/Commands/SignParser.cs +++ b/Projects/UOContent/Commands/SignParser.cs @@ -91,9 +91,7 @@ namespace Server.Commands public static void Add_Static(int itemID, Point3D location, Map map, string name) { - var eable = map.GetItemsInRange(location, 0); - - foreach (var item in eable) + foreach (var item in map.GetItemsInRange(location, 0)) { if (item is Sign && item.Z == location.Z && item.ItemID == itemID) { diff --git a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs index 9aa20fcc3..29dd6d84e 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs @@ -467,10 +467,8 @@ namespace Server.Engines.ConPVP if (landTile.ID == 0x244 && statics.Length == 0) // 0x244 = invalid land tile { - var eable = Map.GetItemsInRange(point, 0); - var empty = true; - foreach (var item in eable) + foreach (var item in Map.GetItemsInRange(point, 0)) { if (item != this) { @@ -513,8 +511,7 @@ namespace Server.Engines.ConPVP var rect = new Rectangle2D(pTop.X, pTop.Y, pBottom.X - pTop.X + 1, pBottom.Y - pTop.Y + 1); - var area = Map.GetItemsInBounds(rect); - foreach (var i in area) + foreach (var i in Map.GetItemsInBounds(rect)) { if (i == this || i.ItemID >= 0x4000) { @@ -567,6 +564,7 @@ namespace Server.Engines.ConPVP { continue; } + if (i is BRGoal goal) { var oldLoc = new Point3D(GetWorldLocation()); @@ -662,8 +660,7 @@ namespace Server.Engines.ConPVP } } - var eable = GetItemsInRange(0); - foreach (var item in eable) + foreach (var item in GetItemsInRange(0)) { if (item.Visible && item != this) { diff --git a/Projects/UOContent/Engines/Craft/Core/CraftItem.cs b/Projects/UOContent/Engines/Craft/Core/CraftItem.cs index 675914c75..b88325d14 100644 --- a/Projects/UOContent/Engines/Craft/Core/CraftItem.cs +++ b/Projects/UOContent/Engines/Craft/Core/CraftItem.cs @@ -387,8 +387,7 @@ namespace Server.Engines.Craft return false; } - var eable = map.GetItemsInRange(from.Location, 2); - foreach (var item in eable) + foreach (var item in map.GetItemsInRange(from.Location, 2)) { if (item.Z + 16 > item.Z && item.Z + 16 > item.Z && Find(item.ItemID, itemIDs)) { diff --git a/Projects/UOContent/Engines/Craft/DefBlacksmithy.cs b/Projects/UOContent/Engines/Craft/DefBlacksmithy.cs index 9f07947a7..75fa1ed26 100644 --- a/Projects/UOContent/Engines/Craft/DefBlacksmithy.cs +++ b/Projects/UOContent/Engines/Craft/DefBlacksmithy.cs @@ -39,9 +39,7 @@ public class DefBlacksmithy : CraftSystem return; } - var eable = map.GetItemsInRange(from.Location, range); - - foreach (var item in eable) + foreach (var item in map.GetItemsInRange(from.Location, range)) { var type = item.GetType(); diff --git a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs index 8c893844e..58501ba18 100644 --- a/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs +++ b/Projects/UOContent/Engines/Doom/LeverPuzzle/LeverPuzzleController.cs @@ -197,9 +197,7 @@ public partial class LeverPuzzleController : Item [Usage("GenLeverPuzzle"), Description("Generates lamp room and lever puzzle in doom.")] public static void GenLampPuzzle_OnCommand(CommandEventArgs e) { - var eable = Map.Malas.GetItemsInRange(lp_Center, 0); - - foreach (var item in eable) + foreach (var item in Map.Malas.GetItemsInRange(lp_Center, 0)) { if (item is LeverPuzzleController) { diff --git a/Projects/UOContent/Engines/Ethics/Core/Ethic.cs b/Projects/UOContent/Engines/Ethics/Core/Ethic.cs index 6cac73b22..3e54dd974 100644 --- a/Projects/UOContent/Engines/Ethics/Core/Ethic.cs +++ b/Projects/UOContent/Engines/Ethics/Core/Ethic.cs @@ -155,10 +155,9 @@ namespace Server.Ethics continue; } - var eable = e.Mobile.GetItemsInRange(2); var found = false; - foreach (var item in eable) + foreach (var item in e.Mobile.GetItemsInRange(2)) { if (item is AnkhNorth or AnkhWest) { diff --git a/Projects/UOContent/Engines/Factions/Core/Generator.cs b/Projects/UOContent/Engines/Factions/Core/Generator.cs index b7931ac74..dc26bb0b1 100644 --- a/Projects/UOContent/Engines/Factions/Core/Generator.cs +++ b/Projects/UOContent/Engines/Factions/Core/Generator.cs @@ -78,14 +78,14 @@ namespace Server.Factions private static bool CheckExistence(Point3D loc, Map facet, Type type) { - var eable = facet.GetItemsInRange(loc, 0); - foreach (var item in eable) + foreach (var item in facet.GetItemsInRange(loc, 0)) { if (type.IsInstanceOfType(item)) { return true; } } + return false; } } diff --git a/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs b/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs index f10a3254d..947080454 100644 --- a/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs +++ b/Projects/UOContent/Engines/Harvest/Core/HarvestSystem.cs @@ -307,8 +307,7 @@ namespace Server.Engines.Harvest return false; } - var eable = m.GetItemsInRange(0); - foreach (var i in eable) + foreach (var i in m.GetItemsInRange(0)) { if (i.StackWith(m, i, false)) { diff --git a/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs b/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs index d140eda0f..febd01cbb 100644 --- a/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs +++ b/Projects/UOContent/Engines/Khaldun/KhaldunGen.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Server.Items; namespace Server.Commands @@ -15,10 +14,8 @@ namespace Server.Commands public static bool FindMorphItem(int x, int y, int z, int inactiveItemID, int activeItemID) { - var eable = Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0); - var found = false; - foreach (var item in eable) + foreach (var item in Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0)) { if (item is MorphItem morphItem && morphItem.Z == z && morphItem.InactiveItemId == inactiveItemID && morphItem.ActiveItemId == activeItemID) { @@ -26,15 +23,14 @@ namespace Server.Commands break; } } + return found; } public static bool FindEffectController(int x, int y, int z) { - var eable = Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0); - var found = false; - foreach (var item in eable) + foreach (var item in Map.Felucca.GetItemsInRange(new Point3D(x, y, z), 0)) { if (item is EffectController && item.Z == z) { @@ -42,22 +38,20 @@ namespace Server.Commands break; } } + return found; } public static T TryCreateItem(int x, int y, int z, T srcItem) where T : Item { - var eable = Map.Felucca.GetItemsInBounds(new Rectangle2D(x, y, 1, 1)); - var t = eable.FirstOrDefault(item => item.GetType() == srcItem.GetType()); - if (t != null) + foreach (var item in Map.Felucca.GetItemsInBounds(new Rectangle2D(x, y, 1, 1))) { srcItem.Delete(); - return t; + return item; } srcItem.MoveToWorld(new Point3D(x, y, z), Map.Felucca); m_Count++; - return srcItem; } diff --git a/Projects/UOContent/Engines/ML Quests/MLQuest.cs b/Projects/UOContent/Engines/ML Quests/MLQuest.cs index 26ccdb2f5..46789014d 100644 --- a/Projects/UOContent/Engines/ML Quests/MLQuest.cs +++ b/Projects/UOContent/Engines/ML Quests/MLQuest.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; +using Server.Collections; using Server.Engines.MLQuests.Gumps; using Server.Engines.MLQuests.Objectives; using Server.Engines.MLQuests.Rewards; @@ -266,25 +266,39 @@ namespace Server.Engines.MLQuests { var name = $"MLQS-{GetType().Name}"; - var toDelete = map.GetItemsInRange(loc, 0).Where(item => item is BaseSpawner && item.Name == name); - - foreach (var item in toDelete) + using var queue = PooledRefQueue.Create(); + foreach (var item in map.GetItemsInRange(loc, 0)) { - item.Delete(); + // 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) + { + queue.Enqueue(item); + } + } + + while (queue.Count > 0) + { + queue.Dequeue().Delete(); } s.Name = name; s.MoveToWorld(loc, map); } - public void PutDeco(Item deco, Point3D loc, Map map) + public static void PutDeco(Item deco, Point3D loc, Map map) { - // Auto cleanup on regeneration - var toDelete = map.GetItemsInRange(loc, 0).Where(item => item.ItemID == deco.ItemID && item.Z == loc.Z); - - foreach (var item in toDelete) + using var queue = PooledRefQueue.Create(); + foreach (var item in map.GetItemsInRange(loc, 0)) { - item.Delete(); + if (item.ItemID == deco.ItemID && item.Z == loc.Z) + { + queue.Enqueue(item); + } + } + + while (queue.Count > 0) + { + queue.Dequeue().Delete(); } deco.MoveToWorld(loc, map); diff --git a/Projects/UOContent/Engines/Pathing/Movement.cs b/Projects/UOContent/Engines/Pathing/Movement.cs index f790e7d2d..91afe95d5 100644 --- a/Projects/UOContent/Engines/Pathing/Movement.cs +++ b/Projects/UOContent/Engines/Pathing/Movement.cs @@ -98,10 +98,8 @@ namespace Server.Movement foreach (var sector in _sectors) { - for (var j = 0; j < sector.Items.Count; ++j) + foreach (var item in sector.Items) { - var item = sector.Items[j]; - if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) { continue; @@ -167,10 +165,8 @@ namespace Server.Movement if (!sectorStartIsForward) { - for (var i = 0; i < sectorForward.Items.Count; ++i) + foreach (var item in sectorForward.Items) { - var item = sectorForward.Items[i]; - if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) { continue; @@ -193,10 +189,8 @@ namespace Server.Movement } } - for (var i = 0; i < sectorStart.Items.Count; ++i) + foreach (var item in sectorStart.Items) { - var item = sectorStart.Items[i]; - if (ignoreMovableImpassables && item.Movable && item.ItemData.ImpassableSurface) { continue; diff --git a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs index 98bcf3833..c81ead0e1 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs @@ -147,8 +147,7 @@ namespace Server.Engines.Spawners // Delete all spawners at this location. // Probably shouldn't do this outside of migrations? Is there a better way to find/fix spawners? - var eable = map.GetItemsInRange(location, 0); - foreach (var spawner in eable) + foreach (var spawner in map.GetItemsInRange(location, 0)) { if (spawner.GetType() == type) { diff --git a/Projects/UOContent/Holiday Stuff/Halloween/2009/Engines/PumpkinPatch.cs b/Projects/UOContent/Holiday Stuff/Halloween/2009/Engines/PumpkinPatch.cs index 59328f062..f19759704 100644 --- a/Projects/UOContent/Holiday Stuff/Halloween/2009/Engines/PumpkinPatch.cs +++ b/Projects/UOContent/Holiday Stuff/Halloween/2009/Engines/PumpkinPatch.cs @@ -50,9 +50,8 @@ namespace Server.Engines.Events var rect = m_PumpkinFields[i]; var spawncount = rect.Height * rect.Width / 20; - var eable = map.GetItemsInBounds(rect); var pumpkins = 0; - foreach (var p in eable) + foreach (var _ in map.GetItemsInBounds(rect)) { if (pumpkins++ >= spawncount) { diff --git a/Projects/UOContent/Items/Addons/SHTeleporter.cs b/Projects/UOContent/Items/Addons/SHTeleporter.cs index c5d02d565..9d6f936c2 100644 --- a/Projects/UOContent/Items/Addons/SHTeleporter.cs +++ b/Projects/UOContent/Items/Addons/SHTeleporter.cs @@ -1,4 +1,3 @@ -using System.Linq; using ModernUO.Serialization; using Server.Mobiles; @@ -268,9 +267,15 @@ namespace Server.Items public static SHTeleporter FindSHTeleporter(Map map, Point3D p) { - var eable = map.GetItemsInRange(p, 0); - var teleporter = eable.FirstOrDefault(item => item.Z == p.Z); - return teleporter; + foreach (var teleporter in map.GetItemsInRange(p, 0)) + { + if (teleporter.Z == p.Z) + { + return teleporter; + } + } + + return null; } public SHTeleporter AddSHT(Map map, bool ext, int x, int y, int z) diff --git a/Projects/UOContent/Items/Construction/Doors/BaseDoor.cs b/Projects/UOContent/Items/Construction/Doors/BaseDoor.cs index c444838fe..d9b6ab406 100644 --- a/Projects/UOContent/Items/Construction/Doors/BaseDoor.cs +++ b/Projects/UOContent/Items/Construction/Doors/BaseDoor.cs @@ -332,13 +332,10 @@ public abstract partial class BaseDoor : Item, ILockable, ITelekinesisable var z = p.Z; var sector = map.GetSector(x, y); - var items = sector.Items; var mobs = sector.Mobiles; - for (var i = 0; i < items.Count; ++i) + foreach (var item in sector.Items) { - var item = items[i]; - if (item is not BaseMulti && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(x, y) && item is not BaseDoor) { diff --git a/Projects/UOContent/Items/Containers/MarkContainer.cs b/Projects/UOContent/Items/Containers/MarkContainer.cs index a8f2f288e..78e43f920 100644 --- a/Projects/UOContent/Items/Containers/MarkContainer.cs +++ b/Projects/UOContent/Items/Containers/MarkContainer.cs @@ -130,15 +130,14 @@ public partial class MarkContainer : LockableContainer private static bool FindMarkContainer(Point3D p, Map map) { - var eable = map.GetItemsInRange(p, 0); - - foreach (var item in eable) + foreach (var item in map.GetItemsInRange(p, 0)) { if (item.Z == p.Z) { return true; } } + return false; } diff --git a/Projects/UOContent/Items/Misc/OilFlask.cs b/Projects/UOContent/Items/Misc/OilFlask.cs index edbffe794..35b16c664 100644 --- a/Projects/UOContent/Items/Misc/OilFlask.cs +++ b/Projects/UOContent/Items/Misc/OilFlask.cs @@ -49,9 +49,8 @@ public partial class OilFlask : Item if (!from.PlaceInBackpack(emptyFlask)) { var didStack = false; - var eable = from.GetItemsInRange(0); - foreach (var i in eable) + foreach (var i in from.GetItemsInRange(0)) { if (i.StackWith(from, this, false)) { diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index c12b31070..dda4cbd3d 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -2157,10 +2157,7 @@ public abstract class BaseAI Movement.Movement.Offset(d, ref x, ref y); var destroyables = 0; - - var eable = map.GetItemsInRange(new Point3D(x, y, m_Mobile.Location.Z), 1); - - foreach (var item in eable) + foreach (var item in map.GetItemsInRange(new Point3D(x, y, m_Mobile.Location.Z), 1)) { if (canOpenDoors && item is BaseDoor door && door.Z + door.ItemData.Height > m_Mobile.Z && m_Mobile.Z + 16 > door.Z) diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 13ce7e3fd..eec2652f1 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -3638,9 +3638,8 @@ namespace Server.Mobiles return false; } - var eable = GetItemsInRange(2); Corpse toRummage = null; - foreach (var c in eable) + foreach (var c in GetItemsInRange(2)) { if (c.Items.Count > 0) { diff --git a/Projects/UOContent/Mobiles/Familiars/HordeMinion.cs b/Projects/UOContent/Mobiles/Familiars/HordeMinion.cs index 127132753..e0fcf847b 100644 --- a/Projects/UOContent/Mobiles/Familiars/HordeMinion.cs +++ b/Projects/UOContent/Mobiles/Familiars/HordeMinion.cs @@ -74,9 +74,8 @@ public partial class HordeMinionFamiliar : BaseFamiliar return; } - var eable = GetItemsInRange(2); using var queue = PooledRefQueue.Create(); - foreach (var item in eable) + foreach (var item in GetItemsInRange(2)) { if (item.Movable && item.Stackable) { diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs b/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs index d7bdb27b8..cddab9e31 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Humanoid/Magic/InterredGrizzle .cs @@ -117,9 +117,13 @@ namespace Server.Mobiles { p = GetSpawnPosition(2); - var eable = Map.GetItemsInRange(p, 0); - using var enumerator = eable.GetEnumerator(); - bool atLocation = enumerator.MoveNext(); + var atLocation = false; + foreach (var item in Map.GetItemsInRange(p, 0)) + { + atLocation = true; + break; + } + if (!atLocation) { break; diff --git a/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs b/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs index 6723cb02c..c54756028 100644 --- a/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs +++ b/Projects/UOContent/Mobiles/Monsters/ML/Special/Ilhenir.cs @@ -234,9 +234,13 @@ namespace Server.Mobiles { p = GetSpawnPosition(2); - var eable = Map.GetItemsInRange(p, 0); - using var enumerator = eable.GetEnumerator(); - bool atLocation = enumerator.MoveNext(); + var atLocation = false; + foreach (var item in Map.GetItemsInRange(p, 0)) + { + atLocation = true; + break; + } + if (!atLocation) { break; diff --git a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/PlagueBeast.cs b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/PlagueBeast.cs index 0359564b2..cb469a55a 100644 --- a/Projects/UOContent/Mobiles/Monsters/Misc/Melee/PlagueBeast.cs +++ b/Projects/UOContent/Mobiles/Monsters/Misc/Melee/PlagueBeast.cs @@ -165,18 +165,13 @@ namespace Server.Mobiles base.OnThink(); // Check to see if we need to devour any corpses - var eable = GetItemsInRange(3); // Get all corpses in range - - foreach (var item in eable) - // Ensure that the corpse was killed by us + foreach (var item in GetItemsInRange(3)) // Get all corpses in range { + // Ensure that the corpse was killed by us if (item.Killer == this && item.Owner != null && !item.DevourCorpse() && !item.Devoured) { - PublicOverheadMessage( - MessageType.Emote, - 0x3B2, - 1053032 - ); // * The plague beast attempts to absorb the remains, but cannot! * + // * The plague beast attempts to absorb the remains, but cannot! * + PublicOverheadMessage(MessageType.Emote, 0x3B2, 1053032); } } } diff --git a/Projects/UOContent/Multis/Boats/BaseBoat.cs b/Projects/UOContent/Multis/Boats/BaseBoat.cs index 00276ade4..bdd8b5fbd 100644 --- a/Projects/UOContent/Multis/Boats/BaseBoat.cs +++ b/Projects/UOContent/Multis/Boats/BaseBoat.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using Server.Engines.Spawners; using Server.Items; @@ -1502,31 +1501,32 @@ namespace Server.Multis } } - var eable = map.GetItemsInBounds( - new Rectangle2D( - p.X + newComponents.Min.X, - p.Y + newComponents.Min.Y, - newComponents.Width, - newComponents.Height - ) + var bounds = new Rectangle2D( + p.X + newComponents.Min.X, + p.Y + newComponents.Min.Y, + newComponents.Width, + newComponents.Height ); - var canFit = eable.All( - item => + foreach (var item in map.GetItemsInBounds(bounds)) + { + if (item is BaseMulti || item.ItemID > TileData.MaxItemValue || item.Z < p.Z || !item.Visible) { - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue || item.Z < p.Z || !item.Visible) - { - return true; - } - - var x = item.X - p.X + newComponents.Min.X; - var y = item.Y - p.Y + newComponents.Min.Y; - - return x >= 0 && x < newComponents.Width && y >= 0 && y < newComponents.Height && - newComponents.Tiles[x][y].Length == 0 || Contains(item); + continue; } - ); - return canFit; + + var x = item.X - p.X + newComponents.Min.X; + var y = item.Y - p.Y + newComponents.Min.Y; + + // Out of bounds, return false - cannot fit + if ((x < 0 || x >= newComponents.Width || y < 0 || y >= newComponents.Height || + newComponents.Tiles[x][y].Length != 0) && !Contains(item)) + { + return false; + } + } + + return true; } public Point3D Rotate(Point3D p, int count) diff --git a/Projects/UOContent/Multis/Houses/BaseHouse.cs b/Projects/UOContent/Multis/Houses/BaseHouse.cs index 5c14377ac..b740a2d6f 100644 --- a/Projects/UOContent/Multis/Houses/BaseHouse.cs +++ b/Projects/UOContent/Multis/Houses/BaseHouse.cs @@ -1141,6 +1141,7 @@ namespace Server.Multis MovingCrate.DropItem(item); } + // TODO: Convert to a ref struct enumerator public List GetItems() { if (Map == null || Map == Map.Internal) @@ -1152,8 +1153,14 @@ namespace Server.Multis var end = new Point2D(X + Components.Max.X + 1, Y + Components.Max.Y + 1); var rect = new Rectangle2D(start, end); - var eable = Map.GetItemsInBounds(rect); - var list = eable.Where(item => item.Movable && IsInside(item)).ToList(); + var list = new List(); + foreach (var item in Map.GetItemsInBounds(rect)) + { + if (item.Movable && IsInside(item)) + { + list.Add(item); + } + } return list; } @@ -3653,11 +3660,14 @@ namespace Server.Multis } var mcl = Components; - var eable = - map.GetItemsInBounds(new Rectangle2D(X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height)); + var bounds = new Rectangle2D(X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height); - var item = eable.FirstOrDefault(Contains); - return item; + foreach (var gs in map.GetItemsInBounds(bounds)) + { + return gs; + } + + return null; } public void ResetDynamicDecay() diff --git a/Projects/UOContent/Multis/Houses/HousePlacement.cs b/Projects/UOContent/Multis/Houses/HousePlacement.cs index 980197e2f..9144cabaa 100644 --- a/Projects/UOContent/Multis/Houses/HousePlacement.cs +++ b/Projects/UOContent/Multis/Houses/HousePlacement.cs @@ -142,10 +142,8 @@ namespace Server.Multis items.Clear(); - for (var i = 0; i < sector.Items.Count; ++i) + foreach (var item in sector.Items) { - var item = sector.Items[i]; - if (item.Visible && item.X == tileX && item.Y == tileY) { items.Add(item); @@ -366,12 +364,9 @@ namespace Server.Multis } var sector = map.GetSector(borderPoint.X, borderPoint.Y); - var sectorItems = sector.Items; - for (var j = 0; j < sectorItems.Count; ++j) + foreach (var item in sector.Items) { - var item = sectorItems[j]; - if (item.X != borderPoint.X || item.Y != borderPoint.Y || item.Movable) { continue; diff --git a/Projects/UOContent/Skills/DetectHidden.cs b/Projects/UOContent/Skills/DetectHidden.cs index 3883536be..adf5f8e65 100644 --- a/Projects/UOContent/Skills/DetectHidden.cs +++ b/Projects/UOContent/Skills/DetectHidden.cs @@ -83,17 +83,15 @@ namespace Server.SkillHandlers if (Faction.Find(src) != null) { - var itemsInRange = src.Map.GetItemsInRange(p, range); - - foreach (var trap in itemsInRange) + foreach (var trap in src.Map.GetItemsInRange(p, range)) { if (src.CheckTargetSkill(SkillName.DetectHidden, trap, 80.0, 100.0)) { src.SendLocalizedMessage( - 1042712, + 1042712, // You reveal a trap placed by a faction: true, $" {(trap.Faction == null ? "" : trap.Faction.Definition.FriendlyName)}" - ); // You reveal a trap placed by a faction: + ); trap.Visible = true; trap.BeginConceal(); diff --git a/Projects/UOContent/Skills/SpiritSpeak.cs b/Projects/UOContent/Skills/SpiritSpeak.cs index ca0907793..53590c0df 100644 --- a/Projects/UOContent/Skills/SpiritSpeak.cs +++ b/Projects/UOContent/Skills/SpiritSpeak.cs @@ -124,9 +124,8 @@ namespace Server.SkillHandlers public override void OnCast() { - var eable = Caster.GetItemsInRange(3); Corpse toChannel = null; - foreach (var corpse in eable) + foreach (var corpse in Caster.GetItemsInRange(3)) { if (!corpse.Channeled) { diff --git a/Projects/UOContent/Spells/Seventh/GateTravel.cs b/Projects/UOContent/Spells/Seventh/GateTravel.cs index b53dc5f0f..f4b0afac6 100644 --- a/Projects/UOContent/Spells/Seventh/GateTravel.cs +++ b/Projects/UOContent/Spells/Seventh/GateTravel.cs @@ -141,9 +141,7 @@ namespace Server.Spells.Seventh private static bool GateExistsAt(Map map, Point3D loc) { - var eable = map.GetItemsInRange(loc, 0); - - foreach (var item in eable) + foreach (var item in map.GetItemsInRange(loc, 0)) { if (item is Moongate or PublicMoongate) { diff --git a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs index aa9363852..b7a615ef5 100644 --- a/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs +++ b/Projects/UOContent/Spells/Spellweaving/ArcaneCircle.cs @@ -97,9 +97,7 @@ namespace Server.Spells.Spellweaving } } - var eable = map.GetItemsInRange(location, 0); - - foreach (var item in eable) + foreach (var item in map.GetItemsInRange(location, 0)) { if (item.Z + item.ItemData.CalcHeight == location.Z && IsValidTile(item.ItemID)) { diff --git a/Projects/UOContent/Spells/Third/Teleport.cs b/Projects/UOContent/Spells/Third/Teleport.cs index 4f816d6fe..cab15f8c9 100644 --- a/Projects/UOContent/Spells/Third/Teleport.cs +++ b/Projects/UOContent/Spells/Third/Teleport.cs @@ -96,9 +96,7 @@ namespace Server.Spells.Third m.PlaySound(0x1FE); - var eable = m.GetItemsInRange(0); - - foreach (var item in eable) + foreach (var item in m.GetItemsInRange(0)) { if (item is ParalyzeFieldSpell.InternalItem or PoisonFieldSpell.InternalItem or FireFieldSpell.FireFieldItem)