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:
Kamron Batman 2025-12-28 02:40:21 -08:00 • committed by GitHub
parent 6d51b33cf8
commit 3e8d548f38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1938 additions and 153 deletions

View file

@ -4,6 +4,7 @@ using ModernUO.CodeGeneratedEvents;
using Server.Accounting;
using Server.Collections;
using Server.ContextMenus;
using Server.Engines.Spawners;
using Server.Ethics;
using Server.Guilds;
using Server.Gumps;
@ -1561,6 +1562,19 @@ namespace Server.Multis
UpdateRegion();
// Invalidate spawn position cache for affected sectors
if (Map != null && Map != Map.Internal)
{
var mcl = Components;
var bounds = new Rectangle2D(
X + mcl.Min.X,
Y + mcl.Min.Y,
mcl.Width,
mcl.Height
);
SectorSpawnCacheManager.InvalidateSectors(Map, bounds);
}
if (Sign?.Deleted == false)
{
Sign.Map = Map;
@ -3329,6 +3343,19 @@ namespace Server.Multis
{
RestoreRelocatedEntities();
// Invalidate spawn position cache for affected sectors before deletion
if (Map != null && Map != Map.Internal)
{
var mcl = Components;
var bounds = new Rectangle2D(
X + mcl.Min.X,
Y + mcl.Min.Y,
mcl.Width,
mcl.Height
);
SectorSpawnCacheManager.InvalidateSectors(Map, bounds);
}
new FixColumnTimer(this).Start();
base.OnDelete();