feat(spawners): add typed JSON binding for RegionSpawner

Add RegionSpawner.Json.cs with a `region` shadow property (serializes
SpawnRegion.Name) and OnAfterJsonDeserialize override that resolves the
region by name using _jsonMap from BaseSpawner. Mark RegionSpawner with
[JsonDiscoverableType] and [JsonConstructor] on its parameterless ctor
so SpawnerJsonSerializer discovers it for STJ polymorphism.

Add RegionSpawnerRoundTripTests: create+register a test BaseRegion on
Felucca (game regions are not loaded in the test environment), serialize
a RegionSpawner, assert $type and region name in JSON, deserialize, and
assert SpawnRegion.Name round-trips correctly. RED → GREEN confirmed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-06-25 10:38:23 -07:00
parent 6b36afa77f
commit 644488dde7
3 changed files with 91 additions and 0 deletions

View file

@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Text.Json;
using Server;
using Server.Engines.Spawners;
using Server.Regions;
using Xunit;
namespace UOContent.Tests.Engines.Spawners.Json;
[Collection("Sequential UOContent Tests")]
public class RegionSpawnerRoundTripTests
{
[Fact]
public void RegionSpawner_RoundTrips_RegionByName()
{
// The test environment does not load game regions (no AssemblyHandler.Invoke("Initialize")),
// so we create and register a test BaseRegion on Felucca directly.
var region = new BaseRegion(
"TestSpawnRegion",
Map.Felucca,
50,
new Rectangle3D(1400, 1670, -128, 40, 40, 256)
);
region.Register();
try
{
var spawner = new RegionSpawner("Fisherman") { SpawnRegion = region };
spawner.MoveToWorld(new Point3D(1416, 1683, 0), Map.Felucca);
var json = JsonSerializer.Serialize<List<BaseSpawner>>(
new List<BaseSpawner> { spawner }, SpawnerJsonSerializer.Options);
Assert.Contains("\"$type\": \"RegionSpawner\"", json);
Assert.Contains(region.Name, json);
var rt = JsonSerializer.Deserialize<List<BaseSpawner>>(json, SpawnerJsonSerializer.Options);
var s = Assert.IsType<RegionSpawner>(Assert.Single(rt));
Assert.Equal(region.Name, s.SpawnRegion?.Name);
s.Delete();
spawner.Delete();
}
finally
{
region.Unregister();
}
}
}

View file

@ -0,0 +1,41 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: RegionSpawner.Json.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Text.Json.Serialization;
using Server.Regions;
namespace Server.Engines.Spawners;
public partial class RegionSpawner
{
private string _jsonRegion;
[JsonInclude]
[JsonPropertyName("region")]
public string JsonRegion
{
get => SpawnRegion?.Name;
set => _jsonRegion = value;
}
protected internal override void OnAfterJsonDeserialize()
{
base.OnAfterJsonDeserialize();
_spawnRegion = Region.Find(_jsonRegion, _jsonMap) as BaseRegion;
_spawnRegion?.InitRectangles();
SpawnRegionName = _spawnRegion?.Name;
}
}

View file

@ -22,6 +22,7 @@ using Server.Regions;
namespace Server.Engines.Spawners;
[SerializationGenerator(0)]
[JsonDiscoverableType]
public partial class RegionSpawner : Spawner
{
[SerializableField(0, getter: "private", setter: "private")]
@ -32,6 +33,7 @@ public partial class RegionSpawner : Spawner
public override Region Region => _spawnRegion;
[Constructible(AccessLevel.Developer)]
[System.Text.Json.Serialization.JsonConstructor]
public RegionSpawner()
{
}