### Summary - Adds CanSpawnMobile(x, y, minZ, maxZ, canSwim, cantWalk, out spawnZ) overload for finding spawn surfaces within a Z range - Adds CanSpawnItem(x, y, minZ, maxZ, out spawnZ) for item spawning with Surface+Impassable support (tables, furniture) - Uses bitmask optimization inspired by Item.DropToWorld's m_OpenSlots pattern for O(1) surface/blocker checks - HomeRange spawners now use surface detection to set proper Z bounds ### Key Changes Map.cs: - CanSpawnMobile with Z-range finds lowest valid surface for mobiles - CanSpawnItem with Z-range finds lowest valid surface for items (including tables) - CanFitItem for point-check item placement on Surface+Impassable tiles - Bitmask approach eliminates nested loops and stackalloc arrays Spawners: - Simplified GetSpawnPosition using new Z-range methods - HomeRange setter detects surface below spawner for proper Z bounds - Consistent handling for mobiles and items ### Bug Fixes - Water tiles (Impassable | Wet) no longer block swimming mobs - Items can now spawn on tables/furniture (Surface+Impassable) ### Test Plan - Run dotnet test - 631 tests pass - Manual testing: multi-story spawning, water mobs, item spawning on tables - Verify HomeRange spawner movement shifts bounds correctly
88 lines
2.4 KiB
C#
88 lines
2.4 KiB
C#
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<string> spawnedNames
|
|
) : base(amount, minDelay, maxDelay, team, spawnBounds, spawnedNames)
|
|
{
|
|
}
|
|
|
|
public Spawner(DynamicJson json, JsonSerializerOptions options) : base(json, options)
|
|
{
|
|
}
|
|
|
|
/*
|
|
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;
|
|
}
|
|
|
|
var bounds = SpawnBounds;
|
|
|
|
// No bounds = HomeRange of 0, spawn at spawner location
|
|
if (bounds == default)
|
|
{
|
|
return Location;
|
|
}
|
|
|
|
// Z range from SpawnBounds (supports multi-story buildings)
|
|
var minZ = bounds.Start.Z;
|
|
var maxZ = bounds.End.Z - 1;
|
|
|
|
// Try 10 times to find a valid location.
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
var x = Utility.RandomMinMax(bounds.Start.X, bounds.End.X - 1);
|
|
var y = Utility.RandomMinMax(bounds.Start.Y, bounds.End.Y - 1);
|
|
|
|
if (spawned is Mobile mob && map.CanSpawnMobile(x, y, minZ, maxZ, mob.CanSwim, mob.CantWalk, out var spawnZ)
|
|
|| spawned is Item && map.CanSpawnItem(x, y, minZ, maxZ, out spawnZ))
|
|
{
|
|
return new Point3D(x, y, spawnZ);
|
|
}
|
|
}
|
|
|
|
return Location;
|
|
}
|
|
}
|