fix: Fixes searching multis/clients. Adds missing map enumeration tests (#2278)

> [!IMPORTANT]
> **Dev Note:** This is an **important** patch as the bug could lead to major issues like:
> * Multis/Players disappearing from view or not being counted during game logic.
> * World processes (e.g., area checks, targeting) failing to detect entities correctly.
> * General stability and correctness concerns for core map functionality.
>
> **Important Breaking Change**: Multis now properly use the map link list. This means modifying a multi while iterating will cause the server to crash. The crash _is expected_. Please modify/fix code accordingly to create a list using `PooledRefQueue` or `PooledRefList` instead of moving/deleting multis while inside the foreach.

### Summary

* Fixes a bug where deleting/moving a multi (boat/house) in some circumstances can use undefined behavior due to unsafe changes to List<BaseMulti>
* Fixes a bug where Multis may not be considered while searching due to a bug causing the sector search to end early.
This commit is contained in:
Kamron Batman 2025-11-27 11:59:47 -08:00 committed by GitHub
parent 5aed018232
commit d3fdb180b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1923 additions and 571 deletions

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2025 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Map.ClientEnumerator.cs *
* *
@ -76,6 +76,7 @@ public partial class Map
public ref struct ClientAtEnumerator
{
private readonly Map _map;
private bool _started;
private Point2D _location;
private ref readonly ValueLinkList<NetState> _linkList;
@ -85,9 +86,15 @@ public partial class Map
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClientAtEnumerator(Map map, Point2D loc)
{
_map = map;
_started = false;
_location = loc;
_linkList = ref map.GetSector(loc.m_X, loc.m_Y).Clients;
if (map != null)
{
_linkList = ref map.GetSector(loc.m_X, loc.m_Y).Clients;
}
_version = 0;
_current = null;
}
@ -95,6 +102,11 @@ public partial class Map
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (_map == null)
{
return false;
}
ref var loc = ref _location;
NetState current;
Mobile m;
@ -105,7 +117,7 @@ public partial class Map
_started = true;
_version = _linkList.Version;
m = current.Mobile;
m = current?.Mobile;
if (m?.Deleted == false && m.X == loc.m_X && m.Y == loc.m_Y)
{
_current = current;
@ -125,7 +137,7 @@ public partial class Map
{
current = current.Next;
m = current.Mobile;
m = current?.Mobile;
if (m?.Deleted == false && m.X == loc.m_X && m.Y == loc.m_Y)
{
_current = current;
@ -163,10 +175,10 @@ public partial class Map
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MobileEnumerator GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive);
public ClientBoundsEnumerator GetEnumerator() => new(_map, _bounds, _makeBoundsInclusive);
}
public ref struct MobileEnumerator
public ref struct ClientBoundsEnumerator
{
private readonly Map _map;
private readonly int _sectorStartX;
@ -182,7 +194,7 @@ public partial class Map
private NetState _current;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MobileEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
public ClientBoundsEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
{
_map = map;
_bounds = bounds;