feat: Add spawn position caching and spiral scan optimization (#2295)
### Summary Adds spawn position caching and optimization for constrained spawners (e.g., those near houses, water, or blocked terrain). ### Key features: - Sector-based bitmap cache (32 bytes per 16x16 sector) stores valid spawn positions - Spiral scan progressively discovers positions from spawner center outward - Automatic mode detects constrained spawners after 5+ non-transient failures - Prevents mob spawning inside private houses (allows public AoS buildings) - Deduplicates sector lookups for multi-bounds spawners (RegionSpawner) - Cache invalidation on house placement/demolition - Moves SpawnBounds to Spawner ### New spawner properties: - SpawnPositionMode: Automatic (default), Enabled, Disabled, Abandoned - MaxSpawnAttempts: Configurable attempts before optimization engages (default: 5)
This commit is contained in:
parent
6d51b33cf8
commit
3e8d548f38
23 changed files with 1938 additions and 153 deletions
|
|
@ -1,6 +1,6 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright 2019-2023 - ModernUO Development Team *
|
||||
* Copyright 2019-2025 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: RegionSpawner.cs *
|
||||
* *
|
||||
|
|
@ -74,6 +74,48 @@ public partial class RegionSpawner : Spawner
|
|||
}
|
||||
}
|
||||
|
||||
// RegionSpawner does not support spiral scan (disjoint rectangles make it ineffective)
|
||||
protected override bool SupportsSpiralScan => false;
|
||||
|
||||
protected override Rectangle3D GetBoundsForSpawnAttempt()
|
||||
{
|
||||
if (_spawnRegion == null || _spawnRegion.TotalWeight <= 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// Pick a weighted random rectangle from the region
|
||||
var rand = Utility.Random(_spawnRegion.TotalWeight);
|
||||
|
||||
for (var j = 0; j < _spawnRegion.RectangleWeights.Length; j++)
|
||||
{
|
||||
var curWeight = _spawnRegion.RectangleWeights[j];
|
||||
|
||||
if (rand < curWeight)
|
||||
{
|
||||
return _spawnRegion.Rectangles[j];
|
||||
}
|
||||
|
||||
rand -= curWeight;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override ReadOnlySpan<Rectangle3D> GetAllSpawnBounds() => _spawnRegion?.Rectangles;
|
||||
|
||||
public override Point3D GetSpawnPosition(ISpawnable spawned, Map map)
|
||||
{
|
||||
// Check for region/map mismatch before delegating to base
|
||||
if (_spawnRegion == null || map == null || map == Map.Internal ||
|
||||
map != _spawnRegion.Map || _spawnRegion.TotalWeight <= 0)
|
||||
{
|
||||
return Location;
|
||||
}
|
||||
|
||||
return base.GetSpawnPosition(spawned, map);
|
||||
}
|
||||
|
||||
public override void ToJson(DynamicJson json, JsonSerializerOptions options)
|
||||
{
|
||||
base.ToJson(json, options);
|
||||
|
|
@ -90,55 +132,6 @@ public partial class RegionSpawner : Spawner
|
|||
}
|
||||
}
|
||||
|
||||
public override Point3D GetSpawnPosition(ISpawnable spawned, Map map)
|
||||
{
|
||||
if (_spawnRegion == null || map == null || map == Map.Internal || map != _spawnRegion.Map ||
|
||||
_spawnRegion.TotalWeight <= 0)
|
||||
{
|
||||
return Location;
|
||||
}
|
||||
|
||||
// Try 10 times to find a valid location.
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var rand = Utility.Random(_spawnRegion.TotalWeight);
|
||||
|
||||
var x = int.MinValue;
|
||||
var y = int.MinValue;
|
||||
var minZ = (int)sbyte.MinValue;
|
||||
var maxZ = (int)sbyte.MaxValue;
|
||||
|
||||
for (var j = 0; j < _spawnRegion.RectangleWeights.Length; j++)
|
||||
{
|
||||
var curWeight = _spawnRegion.RectangleWeights[j];
|
||||
|
||||
if (rand < curWeight)
|
||||
{
|
||||
var rect = _spawnRegion.Rectangles[j];
|
||||
|
||||
x = rect.Start.X + rand % rect.Width;
|
||||
y = rect.Start.Y + rand / rect.Width;
|
||||
|
||||
// Use rectangle's Z range for multi-floor region support
|
||||
minZ = rect.Start.Z;
|
||||
maxZ = rect.End.Z - 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
rand -= curWeight;
|
||||
}
|
||||
|
||||
if (spawned is Mobile mob && map.CanSpawnMobile(x, y, minZ, maxZ, mob.CanSwim, mob.CantWalk, out var spawnZ)
|
||||
|| spawned is Item && map.CanSpawnItem(x, y, minZ, maxZ, out spawnZ))
|
||||
{
|
||||
return new Point3D(x, y, spawnZ);
|
||||
}
|
||||
}
|
||||
|
||||
return Location;
|
||||
}
|
||||
|
||||
[AfterDeserialization(false)]
|
||||
private void AfterDeserialization()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue