using System; using System.Text.Json; using ModernUO.Serialization; using Server.Json; namespace Server.Engines.Spawners; [SerializationGenerator(0)] public partial class Spawner : BaseSpawner { [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 Spawner(DynamicJson json, JsonSerializerOptions options) : base(json, options) { } public static bool IsValidWater(Map map, int x, int y, int z) { if (!Region.Find(new Point3D(x, y, z), map).AllowSpawn() || !map.CanFit(x, y, z, 16, false, true, false)) { return false; } var landTile = map.Tiles.GetLandTile(x, y); if (landTile.Z == z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Wet) != 0) { return true; } foreach (var staticTile in map.Tiles.GetStaticAndMultiTiles(x, y)) { if (staticTile.Z == z && TileData.ItemTable[staticTile.ID & TileData.MaxItemValue].Wet) { return true; } } return false; } /* public override bool OnDefragSpawn(ISpawnable spawned, bool remove) { // To despawn a mob that was lured 4x away from its spawner // TODO: Move this to a config if (spawned is BaseCreature c && c.Combatant == null && c.GetDistanceToSqrt( Location ) > c.RangeHome * 4) { c.Delete(); remove = true; } return base.OnDefragSpawn(entry, spawned, remove); } */ public override Region Region => Region.Find(Location, Map); public override Point3D GetSpawnPosition(ISpawnable spawned, Map map) { if (map == null || map == Map.Internal) { return Location; } bool waterMob, waterOnlyMob; if (spawned is Mobile mob) { waterMob = mob.CanSwim; waterOnlyMob = mob.CanSwim && mob.CantWalk; } else { waterMob = false; waterOnlyMob = false; } var bounds = SpawnBounds; var hasBounds = bounds != default; // Try 10 times to find a valid location. for (var i = 0; i < 10; i++) { int x, y; if (hasBounds) { // Use SpawnBounds for X/Y selection x = Utility.RandomMinMax(bounds.Start.X, bounds.End.X - 1); y = Utility.RandomMinMax(bounds.Start.Y, bounds.End.Y - 1); } else { // No bounds set - spawn at spawner location x = Location.X; y = Location.Y; } // Note: Z-level logic uses spawner Z and map average Z. // Multi-story building support (using bounds Z range) is planned for a future PR. var mapZ = map.GetAverageZ(x, y); if (waterMob) { if (IsValidWater(map, x, y, Z)) { return new Point3D(x, y, Z); } if (IsValidWater(map, x, y, mapZ)) { return new Point3D(x, y, mapZ); } } if (!waterOnlyMob) { if (map.CanSpawnMobile(x, y, Z)) { return new Point3D(x, y, Z); } if (map.CanSpawnMobile(x, y, mapZ)) { return new Point3D(x, y, mapZ); } } } return Location; } }