### Summary
* Adds better `SaveFlag` support.
* Fixes `SortedSet` support.
* Adds custom comparer support for `SortedSet`.
* Fixes various bugs.
* Adds support for `struct`, `record`, and `generic` classes.
* Adds support for `readonly` fields by skipping serializaton entirely.
* Adds support for interface fields that inherit ISerializable
* Adds tests.
See: 59a92cc49a
### Summary
Adds `XInRangeByDistance` and `XInBoundsByDistance` methods to `Map.cs`:
**Item Distance Enumeration:**
```cs
ItemDistanceEnumerable<Item> GetItemsInRangeByDistance(Point3D p);
ItemDistanceEnumerable<Item> GetItemsInRangeByDistance(Point3D p, int range);
ItemDistanceEnumerable<T> GetItemsInRangeByDistance<T>(Point3D p) where T : Item;
ItemDistanceEnumerable<T> GetItemsInRangeByDistance<T>(Point3D p, int range) where T : Item;
ItemDistanceEnumerable<Item> GetItemsInRangeByDistance(Point2D p);
ItemDistanceEnumerable<Item> GetItemsInRangeByDistance(Point2D p, int range);
ItemDistanceEnumerable<T> GetItemsInRangeByDistance<T>(Point2D p) where T : Item;
ItemDistanceEnumerable<T> GetItemsInRangeByDistance<T>(Point2D p, int range) where T : Item;
ItemDistanceEnumerable<Item> GetItemsInRangeByDistance(int x, int y, int range);
ItemDistanceEnumerable<T> GetItemsInRangeByDistance<T>(int x, int y, int range) where T : Item;
ItemDistanceEnumerable<Item> GetItemsInBoundsByDistance(Rectangle2D bounds, , bool makeBoundsInclusive = false);
ItemDistanceEnumerable<T> GetItemsInBoundsByDistance<T>(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Item;
```
**Mobile Distance Enumeration:**
```cs
MobileDistanceEnumerable<Mobile> GetMobilesInRangeByDistance(Point3D p);
MobileDistanceEnumerable<Mobile> GetMobilesInRangeByDistance(Point3D p, int range);
MobileDistanceEnumerable<T> GetMobilesInRangeByDistance<T>(Point3D p) where T : Mobile;
MobileDistanceEnumerable<T> GetMobilesInRangeByDistance<T>(Point3D p, int range) where T : Mobile;
MobileDistanceEnumerable<Mobile> GetMobilesInRangeByDistance(Point2D p);
MobileDistanceEnumerable<Mobile> GetMobilesInRangeByDistance(Point2D p, int range);
MobileDistanceEnumerable<T> GetMobilesInRangeByDistance<T>(Point2D p) where T : Mobile;
MobileDistanceEnumerable<T> GetMobilesInRangeByDistance<T>(Point2D p, int range) where T : Mobile;
MobileDistanceEnumerable<Mobile> GetMobilesInRangeByDistance(int x, int y, int range);
MobileDistanceEnumerable<T> GetMobilesInRangeByDistance<T>(int x, int y, int range) where T : Mobile;
MobileDistanceEnumerable<Mobile> GetMobilesInBoundsByDistance(Rectangle2D bounds, bool makeBoundsInclusive = false);
MobileDistanceEnumerable<T> GetMobilesInBoundsByDistance<T>(Rectangle2D bounds, bool makeBoundsInclusive = false) where T : Mobile;
```
**Client Distance Enumeration:**
```cs
ClientDistanceEnumerable GetClientsInRangeByDistance(Point3D p);
ClientDistanceEnumerable GetClientsInRangeByDistance(Point3D p, int range);
ClientDistanceEnumerable GetClientsInRangeByDistance(Point2D p);
ClientDistanceEnumerable GetClientsInRangeByDistance(Point2D p, int range);
ClientDistanceEnumerable GetClientsInRangeByDistance(int x, int y, int range);
ClientDistanceEnumerable GetClientsInBoundsByDistance(Rectangle2D bounds, bool makeBoundsInclusive = false);
```
**Example Usage:**
How to use `minDistance` to terminate early when all subsequent mobiles in the iteration will be at an increasing min distance.
```csharp
var playerLocation = player.Location;
const int maxRange = 100;
const int maxMobiles = 12;
var closestMobiles = new SortedSet<Mobile>(Comparer<Mobile>.Create((x, y) =>
{
var distX = x.GetDistanceToSqrt(playerLocation);
var distY = y.GetDistanceToSqrt(playerLocation);
int result = distX.CompareTo(distY);
if (result == 0)
{
result = (x?.Serial ?? Serial.MinusOne).CompareTo(y?.Serial ?? Serial.MinusOne);
}
return result;
}));
int lastMinDistance = 0;
foreach (var (mobile, minDistance) in map.GetMobilesInRangeByDistance(playerLocation, maxRange))
{
// Stop if we have enough and distance starts increasing
if (closestMobiles.Count >= maxMobiles && minDistance > lastMinDistance)
{
break;
}
closestMobiles.Add(mobile);
lastMinDistance = minDistance;
}
// Results are already ordered by proximity
foreach (var mobile in closestMobiles)
{
var actualDistance = mobile.GetDistanceToSqrt(playerLocation);
Console.WriteLine($"{mobile.Name}: ActualDist={actualDistance:F2}");
}
```
> [!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.
> [!IMPORTANT]
> **Developer Note**
> THIS IS A BREAKING CHANGE TO THE NEW API GUMP.
> Please give us feedback in [discord ](https://muo.gg/discord) if you have issues, need help, or have ideas for a better API change!
### Summary
* Adds support for size/style to dynamic/static builder.
* Drastically simplifies the dynamic/static builder api for AddHtml.
* Cleans up some legacy gump files.
* Initial plan
* Add SerializableFieldDefault attributes for LeftSeeds and LeftResources
This fixes the serialization bug where _leftSeeds and _leftResources were initialized to 8 in the constructor but didn't serialize when their value was 0. Upon deserialization, the constructor would run again and reset these values back to 8.
The SerializableFieldDefault attributes tell the serialization system that the default value is 8, so it will properly serialize 0 values and maintain the correct state across server restarts.
Co-authored-by: kamronbatman <3953314+kamronbatman@users.noreply.github.com>
* Update ShouldSerialize methods to check against default value
Changed ShouldSerializeLeftSeeds() and ShouldSerializeLeftResources() to check if the value is != 8 (the default) instead of != 0. This ensures that only non-default values are serialized, following the same pattern used in BaseWeapon.cs and other classes with SerializableFieldDefault attributes.
Co-authored-by: kamronbatman <3953314+kamronbatman@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kamronbatman <3953314+kamronbatman@users.noreply.github.com>
### Summary
* Moves BOBGump to DynamicGump.
* BOBGump no longer creates a whole new gump context object on every send.
* Moves BODBuyGump to StaticGump.
### Summary
* Replaces the node chain with a PriorityQueue.
* Performance difference is up to 3x faster.
### Benchmarks
```cs
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|-------------------- |---------:|----------:|----------:|-------:|----------:|
| FastAStar_PQueue | 2.112 us | 0.0186 us | 0.0165 us | 0.0038 | 64 B |
| FastAStar_NodeChain | 6.430 us | 0.0800 us | 0.1807 us | - | 64 B |
```
### Video
https://github.com/user-attachments/assets/3edd2524-5146-4775-be45-3516f0b01c27
### Summary
* Refactors AI so it is easier to read and maintain
* Fixes NPC speed issues
* Fixes pet sector AI issue that was causing stuttering
* Fixes direction snapping for Melee/Mage AI
* Refactors pet orders
* Refactors speech commands
* Removes scale speed by dex for HS+ (it was a stupid feature anyways)