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.cs *
* *
@ -1355,11 +1355,13 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
{
// TODO: Can we avoid this?
private static readonly List<Region> m_DefaultRectList = new();
private static readonly List<BaseMulti> m_DefaultMultiList = new();
private bool m_Active;
private ValueLinkList<NetState> _clients;
private ValueLinkList<Item> _items;
private ValueLinkList<Mobile> _mobiles;
private List<BaseMulti> _multis = new();
private List<BaseMulti> _multis;
private int _multisVersion;
private List<Region> _regions;
public Sector(int x, int y, Map owner)
@ -1372,9 +1374,11 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public List<Region> Regions => _regions ?? m_DefaultRectList;
internal List<BaseMulti> Multis => _multis;
internal List<BaseMulti> Multis => _multis ?? m_DefaultMultiList;
internal ref ValueLinkList<Mobile> Mobiles => ref _mobiles;
internal int MultisVersion => _multisVersion;
internal ref readonly ValueLinkList<Mobile> Mobiles => ref _mobiles;
internal ref readonly ValueLinkList<Item> Items => ref _items;
@ -1503,12 +1507,17 @@ public sealed partial class Map : IComparable<Map>, ISpanFormattable, ISpanParsa
public void OnMultiEnter(BaseMulti multi)
{
_multis ??= new List<BaseMulti>();
_multis.Add(multi);
_multisVersion++;
}
public void OnMultiLeave(BaseMulti multi)
{
_multis.Remove(multi);
if (_multis?.Remove(multi) == true)
{
_multisVersion++;
}
}
public void Activate()