modernuo/Projects/Server/Maps/Map.ClientByDistanceEnumerator.cs
2026-03-05 19:36:54 -08:00

299 lines
10 KiB
C#

/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Map.ClientByDistanceEnumerator.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 Server.Collections;
using Server.Network;
namespace Server;
public partial class Map
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerable GetClientsInRangeByDistance(Point3D p) =>
GetClientsInRangeByDistance(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerable GetClientsInRangeByDistance(Point3D p, int range) =>
GetClientsInRangeByDistance(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerable GetClientsInRangeByDistance(Point2D p) =>
GetClientsInRangeByDistance(p, Core.GlobalMaxUpdateRange);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerable GetClientsInRangeByDistance(Point2D p, int range) =>
GetClientsInRangeByDistance(p.m_X, p.m_Y, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerable GetClientsInRangeByDistance(int x, int y, int range)
{
var clampedRange = Math.Max(0, range);
var edge = clampedRange * 2 + 1;
return GetClientsInBoundsByDistance(
new Rectangle2D(x - clampedRange, y - clampedRange, edge, edge),
new Point2D(x, y)
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerable GetClientsInBoundsByDistance(Rectangle2D bounds, bool makeBoundsInclusive = false) =>
GetClientsInBoundsByDistance(bounds, new Point2D(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2), makeBoundsInclusive);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ClientDistanceEnumerable GetClientsInBoundsByDistance(
Rectangle2D bounds, Point2D center, bool makeBoundsInclusive = false
) => new(this, bounds, center, makeBoundsInclusive);
public ref struct ClientDistanceEnumerable
{
private readonly Map _map;
private readonly Rectangle2D _bounds;
private readonly Point2D _center;
private readonly bool _makeBoundsInclusive;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerable(Map map, Rectangle2D bounds, Point2D center, bool makeBoundsInclusive)
{
_map = map;
_bounds = bounds;
_center = center;
_makeBoundsInclusive = makeBoundsInclusive;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerator GetEnumerator() => new(_map, _bounds, _center, _makeBoundsInclusive);
}
public ref struct ClientDistanceEnumerator
{
private Map _map;
private Point2D _center;
private Rectangle2D _bounds;
private int _sectorStartX;
private int _maxRing;
private int _ring; // -1 = uninitialized, then 0.._maxRing
private int _ringIndex; // Current index within the ring
private int _currentSectorX;
private int _currentSectorY;
private ref readonly ValueLinkList<NetState> _linkList;
private int _currentVersion;
private NetState _current;
private int _minDistance;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientDistanceEnumerator(Map map, Rectangle2D bounds, Point2D center, bool makeBoundsInclusive)
{
_map = map;
_center = center;
_bounds = makeBoundsInclusive
? new Rectangle2D(bounds.X, bounds.Y, bounds.Width + 1, bounds.Height + 1)
: bounds;
_current = null;
if (map != null)
{
var centerSectorX = center.m_X / SectorSize;
var centerSectorY = center.m_Y / SectorSize;
map.CalculateSectors(_bounds, out _sectorStartX, out var sectorStartY, out var sectorEndX, out var sectorEndY);
// Calculate max ring based on bounds
var dx = Math.Max(centerSectorX - _sectorStartX, sectorEndX - centerSectorX);
var dy = Math.Max(centerSectorY - sectorStartY, sectorEndY - centerSectorY);
_maxRing = Math.Max(dx, dy);
}
_ring = -1;
_ringIndex = -1;
_currentSectorX = 0;
_currentSectorY = 0;
_currentVersion = 0;
_minDistance = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
var map = _map;
if (map == null)
{
return false;
}
if (!Unsafe.IsNullRef(in _linkList) && _linkList.Version != _currentVersion)
{
throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion);
}
var current = _current;
while (true)
{
current = current?.Next;
while (current == null)
{
while (!TryNextSectorInRing(out _currentSectorX, out _currentSectorY))
{
// Current ring exhausted, try next ring
if (_ring >= _maxRing)
{
return false; // No more rings to search
}
_ring++;
_ringIndex = -1;
}
_linkList = ref map.GetRealSector(_currentSectorX, _currentSectorY).Clients;
_currentVersion = _linkList.Version;
current = _linkList._first;
if (current != null)
{
_minDistance = MinDistToSectorSqrt(_center.m_X, _center.m_Y, _currentSectorX, _currentSectorY);
}
}
var m = current.Mobile;
if (m?.Deleted == false && _bounds.Contains(m.Location))
{
_current = current;
return true;
}
}
}
public (NetState Value, int MinDistance) Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (_current, _minDistance);
}
private bool TryNextSectorInRing(out int sx, out int sy)
{
if (_ring == 0)
{
// Center sector
if (_ringIndex < 0)
{
_ringIndex = 0;
sx = _center.m_X / SectorSize;
sy = _center.m_Y / SectorSize;
return sx >= _sectorStartX;
}
sx = sy = 0;
return false;
}
var totalSectors = _ring * 8;
// Keep trying sectors in this ring until we find a valid one or exhaust the ring
while (true)
{
var nextIndex = _ringIndex + 1;
if (nextIndex >= totalSectors)
{
sx = sy = 0;
return false;
}
_ringIndex = nextIndex;
CalculatePositionFromIndex(nextIndex, out sx, out sy);
if (sx >= _sectorStartX)
{
return true;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CalculatePositionFromIndex(int index, out int x, out int y)
{
var centerSectorX = _center.m_X / SectorSize;
var centerSectorY = _center.m_Y / SectorSize;
var ringSize = _ring * 2;
var startX = centerSectorX - _ring;
var startY = centerSectorY - _ring;
if (index <= ringSize) // Top edge
{
x = startX + index;
y = startY;
}
else if (index <= ringSize * 2) // Right edge
{
x = startX + ringSize;
y = startY + (index - ringSize);
}
else if (index <= ringSize * 3) // Bottom edge
{
x = startX + ringSize - (index - ringSize * 2);
y = startY + ringSize;
}
else // Left edge
{
x = startX;
y = startY + ringSize - (index - ringSize * 3);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int MinDistToSectorSqrt(int cx, int cy, int sectorX, int sectorY)
{
var x0 = sectorX * SectorSize;
var y0 = sectorY * SectorSize;
var x1 = x0 + (SectorSize - 1);
var y1 = y0 + (SectorSize - 1);
var dx = 0;
if (cx < x0)
{
dx = x0 - cx;
}
else if (cx > x1)
{
dx = cx - x1;
}
var dy = 0;
if (cy < y0)
{
dy = y0 - cy;
}
else if (cy > y1)
{
dy = cy - y1;
}
return (int)Math.Sqrt(dx * dx + dy * dy);
}
}
}