refactor(spawners): replace DynamicJson with typed SpawnerDto records (#2505)
## Summary Removes the dated `DynamicJson` JSON helper and migrates spawner JSON (de)serialization to a polymorphic `record SpawnerDto` hierarchy. `DynamicJson` was the last remaining consumer (regions moved off it in #1400). The key correctness improvement: **System.Text.Json deserializes plain DTO records, never a live `Item`.** Previously, deserializing directly into an `Item` meant STJ constructed world-registered objects *before* the data was validated — a malformed/hand-edited spawn file could leave orphaned spawner Items in the world save. Now a parse failure is GC-only; `dto.ToSpawner()` constructs the spawner only from a fully-validated DTO and self-cleans on failure. ## What changed - **New:** `SpawnerDto` (abstract) + `SpawnerDataDto` / `RegionSpawnerDto` / `ProximitySpawnerDto`, each marked with a reusable `[JsonDiscoverableType]` opt-in attribute. Auto-discovered at the `Configure` phase — no manual registration list (avoids the regions `Register<T>()` footgun), open to custom spawner subtypes. - **Symmetric mapping:** `BaseSpawner.ToDto()` (export) ⇄ `SpawnerDto.ToSpawner()` (import). `ToSpawner()` deletes-and-rethrows on any failure, so the importer can never orphan an Item. - **`SpawnerJsonSerializer`** wires `$type` polymorphism on the `SpawnerDto` root with loud collision/constructibility validation. - **Import/export commands** rewired to the typed DTO path (reflection `FindTypeByName`/`CreateInstance` removed). - **Data migration:** the 109 `Distribution/Data/Spawns/**` files moved from `"type"`→`"$type"` and legacy `homeRange`→`spawnBounds`. The runtime still *reads* legacy `homeRange` for external files. The `homeRange→spawnBounds` formula is proven equivalent to the runtime conversion (`BoundsEquivalenceTests`, hr=0/1/3/7). - **Deleted:** `Projects/Server/Json/DynamicJson.cs`. Sparse export output matches the legacy `ToJson` (nullable DTO properties + `WhenWritingNull`). Binary world-save serialization is untouched. ## Tests - DTO round-trip per spawner type; sparse-default omission; legacy `homeRange` read; export/import file round-trip. - `Import_MalformedFile_LeaksNoWorldItems` — proves a mid-array parse failure constructs zero world Items. - `AllSpawnFilesLoadTests` — every migrated spawn file deserializes and builds. - Duplicate-discriminator validation. - UOContent.Tests 485/485, Server.Tests 710/710, build clean. ## Follow-up (not in this PR) `Rectangle3DConverter.Write` (in `Projects/Server/`) omits `z1/z2` when `Start.Z == -128`, so a future server-side export of a `homeRange`-style spawner round-trips depth 256→255. Pre-existing and out of scope here (Server change); the migrated data reads correctly. Worth a separate small converter PR.
This commit is contained in:
parent
a26219c837
commit
d8a64f3316
135 changed files with 7654 additions and 6079 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[
|
||||
[
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "03fd7675-5b18-4bd1-b886-549fff24a24e",
|
||||
"name": "Spawner (103)",
|
||||
"location": [2513, 862, 0],
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "156f3558-d5ea-4117-bcdb-f6f2f2ff25bd",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5399, 1859, 0],
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "15e19e7f-7de5-412c-873e-e38fd7f4c99d",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5528, 1808, 0],
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "16f79bca-0bcf-4440-a5df-ac2a73ece818",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5411, 1932, 0],
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "1b287d2c-4992-4071-a577-23a460b7af73",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5560, 1843, 0],
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "1fc3857a-ad6d-467c-9feb-df23f3192492",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5460, 1975, 0],
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "22bd9b72-f463-431e-bd87-17e25e1c077f",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5473, 1991, 0],
|
||||
|
|
@ -129,7 +129,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "234f3c85-84fb-4f3d-bb0e-c90cc95c16b0",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5472, 1877, 0],
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "2e2d21b4-67af-4720-b7ff-d8aa07544362",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5445, 2030, -3],
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "2e3bb584-e75f-47d2-b3ca-9926aef55326",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5448, 2027, 0],
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "368ab9e9-2c6c-4cc2-9418-8d978ddaea67",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5474, 2035, 0],
|
||||
|
|
@ -195,7 +195,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3828dfe6-a240-4615-bf09-4fd7eccd4b90",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5452, 1891, 0],
|
||||
|
|
@ -212,7 +212,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "41f90fd5-f4fe-47f3-b05f-4e00f83dd415",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5453, 1905, 0],
|
||||
|
|
@ -228,7 +228,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "43148509-fa06-41d1-9ae9-f6cbf2b9cd63",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5560, 1876, 0],
|
||||
|
|
@ -250,7 +250,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "437c19eb-a47b-4477-88f2-90e3f8171a94",
|
||||
"name": "Spawner (103)",
|
||||
"location": [2493, 883, 0],
|
||||
|
|
@ -266,7 +266,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4755ff86-849d-443e-aaf9-4da5a523f28c",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5387, 1911, 0],
|
||||
|
|
@ -283,7 +283,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "49d2dedd-e659-40b1-8faa-92ec79d5ebc6",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5413, 2000, 0],
|
||||
|
|
@ -300,7 +300,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4d0223bc-6ca5-4794-ae48-16a6969f44e3",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5461, 1818, 0],
|
||||
|
|
@ -317,7 +317,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "58e61f09-1d8e-4eb0-bdee-18678d4d7ca4",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5503, 2002, 0],
|
||||
|
|
@ -334,7 +334,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "5cb18cd8-da7a-414d-9f4d-f9b20709b796",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5443, 1918, 0],
|
||||
|
|
@ -350,7 +350,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "60678191-e2a8-48ee-9dc7-56ff237b3ddb",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5579, 1859, 0],
|
||||
|
|
@ -368,7 +368,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "6e4df3f9-6915-4733-9efb-f999f6725e3e",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5457, 1971, 0],
|
||||
|
|
@ -386,7 +386,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "6e763f3e-fd66-40af-9c05-e3bd8604b646",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5452, 1799, 0],
|
||||
|
|
@ -403,7 +403,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "792c60a4-bf05-4088-a0f1-295a8cbcc26d",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5596, 1903, 0],
|
||||
|
|
@ -425,7 +425,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7a96822d-8531-469e-b32b-42ca4c914e5c",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5615, 1838, 0],
|
||||
|
|
@ -446,7 +446,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7d782c02-bb11-4a7b-a864-c6aa0d9372b6",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5429, 1989, 0],
|
||||
|
|
@ -463,7 +463,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "807182c4-b6fd-4053-a8a3-14b83dff0d1e",
|
||||
"name": "Spawner (103)",
|
||||
"location": [2456, 861, 0],
|
||||
|
|
@ -479,7 +479,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "824e3fe5-d203-4b2c-82b1-4c6af496a131",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5417, 1975, 0],
|
||||
|
|
@ -495,7 +495,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "84ba2d57-7801-4461-8ed9-c7d45c95f473",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5400, 1937, 0],
|
||||
|
|
@ -511,7 +511,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "8a7ef603-1a81-4834-afba-20cbe04710ba",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5560, 1903, 0],
|
||||
|
|
@ -533,7 +533,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "94f3b7ec-a856-4779-8ad6-0f90895790a0",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5617, 1861, 0],
|
||||
|
|
@ -552,7 +552,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a1bc2e58-de79-4851-9b49-119ff6a2609a",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5603, 1875, 0],
|
||||
|
|
@ -571,7 +571,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a827c58c-f729-41d8-a600-7e85d4fa2fb5",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5504, 1808, 0],
|
||||
|
|
@ -589,7 +589,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "af277400-a7ad-4048-bdb1-fdcc10b20756",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5393, 1921, 0],
|
||||
|
|
@ -605,7 +605,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b005fb7e-e674-496d-89f1-ee8d3b8961e0",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5440, 2023, 0],
|
||||
|
|
@ -622,7 +622,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b180d203-9331-4077-86a1-5cec055f0fc8",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5593, 2017, 0],
|
||||
|
|
@ -638,7 +638,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "bc45c254-4539-4c60-a37f-2711c2e2ddec",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5418, 1876, 0],
|
||||
|
|
@ -655,7 +655,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "be80b563-6951-4564-869e-e64efe2b116a",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5411, 1901, 0],
|
||||
|
|
@ -672,7 +672,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "ca031256-07f4-4a12-8303-54c2c2606afc",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5436, 1972, 0],
|
||||
|
|
@ -688,7 +688,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "cdffe875-e196-4511-b07b-bafe2547279b",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5469, 2019, 0],
|
||||
|
|
@ -704,7 +704,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d56c22bc-e344-47d6-ae5f-d65acdea6940",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5498, 1998, 0],
|
||||
|
|
@ -721,7 +721,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "de81051f-f31d-4254-b09b-3a629e29f763",
|
||||
"name": "Spawner (103)",
|
||||
"location": [2480, 863, 0],
|
||||
|
|
@ -737,7 +737,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "df30762f-98e0-4753-97ef-d2e548612633",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5558, 2025, 0],
|
||||
|
|
@ -753,7 +753,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "ec453c02-9ef8-40bb-8e12-06c74c0ce96e",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5577, 2006, 0],
|
||||
|
|
@ -769,7 +769,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "fc2ab61a-331b-4b29-8639-552cb33a9a7b",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5388, 2012, 0],
|
||||
|
|
@ -785,7 +785,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "fce765ed-6ec5-45ae-94e6-762879f661a9",
|
||||
"name": "Spawner (103)",
|
||||
"location": [5425, 1992, 0],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue