ModernUO/Projects/UOContent/Engines/Spawners/BaseSpawner.Migrations.cs
Kamron Batman 3e8d548f38
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)
2025-12-28 02:40:21 -08:00

101 lines
3.2 KiB
C#

using System.Collections.Generic;
using Server.Items;
namespace Server.Engines.Spawners;
public abstract partial class BaseSpawner
{
// Static dictionary to track pending HomeRange migrations from v10
// Keyed by spawner instance, stores the HomeRange value to convert to SpawnBounds
private static readonly Dictionary<BaseSpawner, int> _pendingHomeRangeMigrations = new();
private void MigrateFrom(V10Content content)
{
_guid = content.Guid;
_returnOnDeactivate = content.ReturnOnDeactivate;
_entries = content.Entries;
_walkingRange = content.WalkingRange;
_wayPoint = content.WayPoint;
_group = content.Group;
_minDelay = content.MinDelay;
_maxDelay = content.MaxDelay;
_count = content.Count;
_team = content.Team;
_running = content.Running;
_end = _running ? content.End : Core.Now;
// Defer SpawnBounds calculation to AfterDeserialization when Location is available
if (content.HomeRange > 0)
{
_pendingHomeRangeMigrations[this] = content.HomeRange;
}
_spawnLocationIsHome = false;
// New v12 fields default to automatic/default
_spawnPositionMode = SpawnPositionMode.Automatic;
_maxSpawnAttempts = DefaultMaxSpawnAttempts;
}
private void MigrateFrom(V11Content content)
{
_guid = content.Guid;
_returnOnDeactivate = content.ReturnOnDeactivate;
_entries = content.Entries;
_walkingRange = content.WalkingRange;
_wayPoint = content.WayPoint;
_group = content.Group;
_minDelay = content.MinDelay;
_maxDelay = content.MaxDelay;
_count = content.Count;
_team = content.Team;
// Moved to Spawner
if (this is Spawner spawner)
{
spawner.SpawnBounds = content.SpawnBounds;
}
_running = content.Running;
_spawnLocationIsHome = content.SpawnLocationIsHome;
_end = _running ? content.End : Core.Now;
// New v12 fields default to automatic/default
_spawnPositionMode = SpawnPositionMode.Automatic;
_maxSpawnAttempts = DefaultMaxSpawnAttempts;
}
private void Deserialize(IGenericReader reader, int version)
{
_guid = reader.ReadGuid();
_returnOnDeactivate = reader.ReadBool();
var count = reader.ReadInt();
_entries = new List<SpawnerEntry>(count);
for (var i = 0; i < count; ++i)
{
var entry = new SpawnerEntry(this);
entry.Deserialize(reader);
_entries.Add(entry);
}
_walkingRange = reader.ReadInt();
_wayPoint = reader.ReadEntity<WayPoint>();
_group = reader.ReadBool();
_minDelay = reader.ReadTimeSpan();
_maxDelay = reader.ReadTimeSpan();
_count = reader.ReadInt();
_team = reader.ReadInt();
// Store homeRange for migration in AfterDeserialization when Location is available
var homeRange = reader.ReadInt();
if (homeRange > 0)
{
_pendingHomeRangeMigrations[this] = homeRange;
}
_running = reader.ReadBool();
_end = _running ? reader.ReadDeltaTime() : Core.Now;
}
}