### 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)
101 lines
3.2 KiB
C#
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;
|
|
}
|
|
}
|