fix: enumerators that end early, repeat entries, or never end (#2685)
Fixes #2682. Thanks @DatMoshu for the detailed report and repro. While fixing it I audited the other map, item and multi enumerators for the same class of defect. None repeats #2682 itself, but four others turned up. All are fixed here. ## Fixes - **`StaticTileEnumerator` (#2682)**: `SetMulti` returned `SetTile()` for the first multi whose bounds contained the point. If that multi had no tile at the cell, the enumeration ended and every later multi in the sector was skipped. Movement and line of sight lost a floor that stood inside another multi's bounding box. It now moves on to the next multi. - **`MultiBoundsEnumerator`**: deduplication used one static `HashSet` that every new query cleared. A query run inside another made the outer query skip multis or return them twice. Each multi is now yielded only from the sector that holds its `Location`; the bounds filter guarantees that sector is visited. A per-enumerator set covers a multi that isn't registered in that sector (components changed after placement). Normal queries allocate nothing. - **`Item`/`Mobile`/`ClientDistanceEnumerator`**: ring sectors were bounded only by `sx >= _sectorStartX`. Ring cells past the map edge resolved to the shared invalid sector, once per cell. Entities at off-map coordinates were returned more than once, and sectors outside the query were created lazily. The check now covers all four sides. Results match the bounds enumerators, which never returned off-map entities. - **`BaseBoat.MovingEntitiesEnumerator`**: `GetMovingEntities` handles a null map, but the enumerator constructor still called `boat.Map.GetMobilesInBounds` and threw. A boat with no map now gets empty enumerators. - **`BaseDoor.ChainEnumerator`**: the walk stopped only on returning to the start door. Links are set in pairs, so `[ChainLink A B C` followed by `[Link B C` leaves A→B→C→B, and enumerating that chain never ended. The walk now stops on any door it has already yielded. Only reachable when `UseChainedFunctionality` is true, which is off in base and in all shipped content. ## Testing - A regression test for each fix except the boat null guard. Each failed before its fix and passes after. - Server.Tests 910/910. UOContent.Tests 1185 passed, 2 skipped. - Test note: the `MultiComponentList(List<MultiTileEntry>)` constructor drops entries after the first whose `Flags` are 0. The new test multis use non-zero flags.
This commit is contained in:
parent
edd7553c6f
commit
d4531cd94b
11 changed files with 279 additions and 36 deletions
|
|
@ -85,6 +85,9 @@ public partial class Map
|
|||
private Rectangle2D _bounds;
|
||||
|
||||
private int _sectorStartX;
|
||||
private int _sectorStartY;
|
||||
private int _sectorEndX;
|
||||
private int _sectorEndY;
|
||||
private int _maxRing;
|
||||
|
||||
private int _ring; // -1 = uninitialized, then 0.._maxRing
|
||||
|
|
@ -115,11 +118,11 @@ public partial class Map
|
|||
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);
|
||||
map.CalculateSectors(_bounds, out _sectorStartX, out _sectorStartY, out _sectorEndX, out _sectorEndY);
|
||||
|
||||
// Calculate max ring based on bounds
|
||||
var dx = Math.Max(centerSectorX - _sectorStartX, sectorEndX - centerSectorX);
|
||||
var dy = Math.Max(centerSectorY - sectorStartY, sectorEndY - centerSectorY);
|
||||
var dx = Math.Max(centerSectorX - _sectorStartX, _sectorEndX - centerSectorX);
|
||||
var dy = Math.Max(centerSectorY - _sectorStartY, _sectorEndY - centerSectorY);
|
||||
_maxRing = Math.Max(dx, dy);
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +205,7 @@ public partial class Map
|
|||
_ringIndex = 0;
|
||||
sx = _center.m_X / SectorSize;
|
||||
sy = _center.m_Y / SectorSize;
|
||||
return sx >= _sectorStartX;
|
||||
return IsSectorInBounds(sx, sy);
|
||||
}
|
||||
|
||||
sx = sy = 0;
|
||||
|
|
@ -225,7 +228,7 @@ public partial class Map
|
|||
_ringIndex = nextIndex;
|
||||
CalculatePositionFromIndex(nextIndex, out sx, out sy);
|
||||
|
||||
if (sx >= _sectorStartX)
|
||||
if (IsSectorInBounds(sx, sy))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -233,6 +236,11 @@ public partial class Map
|
|||
}
|
||||
|
||||
|
||||
// Ring cells outside the clamped sector range would resolve to the shared invalid sector, once per cell
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsSectorInBounds(int sx, int sy) =>
|
||||
sx >= _sectorStartX && sx <= _sectorEndX && sy >= _sectorStartY && sy <= _sectorEndY;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CalculatePositionFromIndex(int index, out int x, out int y)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue