using System; using System.Collections.Generic; using System.Runtime.InteropServices; using ModernUO.Serialization; namespace Server.Engines.Spawners; [SerializationGenerator(2)] public partial class Spawner : BaseSpawner { /// /// When true, enables proactive spiral scanning to find valid spawn positions. /// Only relevant when SpawnPositionMode is Automatic or Enabled. /// private bool ShouldSerializeUseSpiralScan() => _useSpiralScan; [SerializableField(0)] [SaveFlag(nameof(ShouldSerializeUseSpiralScan))] [SerializedCommandProperty(AccessLevel.Developer)] private bool _useSpiralScan; private bool ShouldSerializeSpawnBounds() => _spawnBounds != default; [SerializableProperty(1)] [SaveFlag(nameof(ShouldSerializeSpawnBounds))] [CommandProperty(AccessLevel.Developer)] public override Rectangle3D SpawnBounds { get => _spawnBounds; set { _spawnBounds = value; InvalidateProperties(); this.MarkDirty(); } } // Owned by the concrete class so subclasses can store their own entry type; null until the first entry. [SerializedIgnoreDupe] [SerializableField(2, getter: "protected", setter: "private")] private List _entryList; [Constructible(AccessLevel.Developer)] public Spawner() { } [Constructible(AccessLevel.Developer)] public Spawner(string spawnedName) : base(spawnedName) { } [Constructible(AccessLevel.Developer)] public Spawner( int amount, TimeSpan minDelay, TimeSpan maxDelay, int team = 0, Rectangle3D spawnBounds = default, params ReadOnlySpan spawnedNames ) : base(amount, minDelay, maxDelay, team, spawnBounds, spawnedNames) { } public override Region Region => Region.Find(Location, Map); protected override bool SupportsSpiralScan => _useSpiralScan; protected override Rectangle3D GetBoundsForSpawnAttempt() => SpawnBounds; protected override ReadOnlySpan GetAllSpawnBounds() => new(ref _spawnBounds); public override IReadOnlyList Entries => _entryList ?? (IReadOnlyList)Array.Empty(); protected override ReadOnlySpan EntrySpan => CollectionsMarshal.AsSpan(_entryList); protected override SpawnerEntry CreateEntry( string name, int probability, int maxCount, string properties, string parameters ) => new(this, name, probability, maxCount, properties, parameters); protected override void AddEntryCore(SpawnerEntry entry) { EntryList ??= []; AddToEntryList(entry); } protected override bool RemoveEntryCore(SpawnerEntry entry) { if (_entryList?.Contains(entry) != true) { return false; } RemoveFromEntryList(entry); return true; } protected override void ClearEntriesCore() { if (_entryList?.Count > 0) { ClearEntryList(); } } protected override void AdoptEntries(IReadOnlyList entries) { if (entries.Count == 0) { EntryList = null; return; } // Copy, never alias the caller's list. var list = new List(entries); for (var i = 0; i < list.Count; i++) { list[i].SetParent(this); } EntryList = list; } private void MigrateFrom(V0Content content) { // v0 had no fields. } private void MigrateFrom(V1Content content) { _useSpiralScan = content.UseSpiralScan; _spawnBounds = content.SpawnBounds ?? default; // _entryList was already adopted by BaseSpawner.MigrateFrom(V12Content). } [AfterDeserialization] private void AfterDeserialization() => RebuildSpawned(); }