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": "0d9c6323-7da9-4e12-861a-76fd30b3e3dc",
|
||||
"name": "Spawner (405)",
|
||||
"location": [950, 520, -55],
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "0db7d5e9-beaa-43a2-ac46-2802ac962fcb",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2068, 1372, -75],
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3d301c77-1ade-4823-b2ff-f1d4e0ed2bf0",
|
||||
"name": "Spawner (405)",
|
||||
"location": [1029, 520, -55],
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3d69c950-70d4-48c5-8729-be8848330c1a",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2045, 1397, -90],
|
||||
|
|
@ -67,7 +67,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3dcbbc92-14ac-4798-8753-41aa3b6b2450",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2023, 1379, -80],
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "54deea77-bc1e-446b-8274-ea23bddb369e",
|
||||
"name": "Spawner (405)",
|
||||
"location": [989, 527, -50],
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "55fab330-6d2a-4823-a666-3dfa33dceeb6",
|
||||
"name": "Spawner (405)",
|
||||
"location": [1977, 1365, -80],
|
||||
|
|
@ -118,7 +118,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "72a7f47d-d4cb-4530-9ba6-3878482279b2",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2078, 1327, -80],
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "79aa68b8-d2bd-4a4d-ad84-422588aeaaff",
|
||||
"name": "Spawner (405)",
|
||||
"location": [1016, 514, -70],
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7a10d380-53e3-48c9-9c5b-07a3c215032a",
|
||||
"name": "Spawner (405)",
|
||||
"location": [1027, 494, -70],
|
||||
|
|
@ -167,7 +167,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7bd799ab-c7fb-446e-b6cb-580d1fe2f7fb",
|
||||
"name": "Spawner (405)",
|
||||
"location": [976, 512, -50],
|
||||
|
|
@ -184,7 +184,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "81e4af4e-3310-4864-8517-a032d487716f",
|
||||
"name": "Spawner (405)",
|
||||
"location": [1992, 1315, -90],
|
||||
|
|
@ -200,7 +200,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "8ed338dc-51c1-40b3-a91a-7ebd8d91131b",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2060, 1283, -80],
|
||||
|
|
@ -218,7 +218,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9347985d-23a4-4044-9a5c-79939c9f7df0",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2037, 1311, -85],
|
||||
|
|
@ -234,7 +234,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "99cde945-5b9c-4cff-924f-a59142bcbdb9",
|
||||
"name": "Spawner (405)",
|
||||
"location": [989, 520, -50],
|
||||
|
|
@ -251,7 +251,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9ad9f47f-17bc-4790-9a84-1e9aee66478f",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2011, 1326, -80],
|
||||
|
|
@ -268,7 +268,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a0c83d13-2354-494f-9535-d4e587cc7a6b",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2025, 1387, -80],
|
||||
|
|
@ -286,7 +286,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a263ccfc-65e1-4181-93b6-6e1c7914f5b8",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2048, 1343, -85],
|
||||
|
|
@ -303,7 +303,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a4200c02-8035-48bc-b79d-8b45225ffd4f",
|
||||
"name": "Spawner (405)",
|
||||
"location": [1004, 527, -50],
|
||||
|
|
@ -322,7 +322,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b4f71d7e-8c76-47db-a1c1-b81c4484741b",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2027, 1353, -90],
|
||||
|
|
@ -341,7 +341,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b7226aea-9fd0-4079-8e21-70a7a443d0b2",
|
||||
"name": "Spawner (405)",
|
||||
"location": [1056, 1434, -85],
|
||||
|
|
@ -357,7 +357,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "c1bda852-fee4-45ee-aa79-630980bc44e9",
|
||||
"name": "Spawner (405)",
|
||||
"location": [1003, 512, -50],
|
||||
|
|
@ -377,7 +377,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d6de9f30-1ee9-4914-853c-be075bd119ce",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2083, 1322, -80],
|
||||
|
|
@ -395,7 +395,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "df531ac9-d2b0-48ae-bc19-dc043d6af3de",
|
||||
"name": "Spawner (405)",
|
||||
"location": [993, 511, -50],
|
||||
|
|
@ -413,7 +413,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "e08eb38c-0b25-4387-8999-f9d8a0a877aa",
|
||||
"name": "Spawner (405)",
|
||||
"location": [961, 517, -70],
|
||||
|
|
@ -429,7 +429,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "eb50b13f-dd98-42c1-bfa0-2599bdc85de3",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2066, 1282, -80],
|
||||
|
|
@ -446,7 +446,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f62df843-74a7-4bb0-a101-53d1dc4cb806",
|
||||
"name": "Spawner (405)",
|
||||
"location": [976, 527, -50],
|
||||
|
|
@ -466,7 +466,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "fa9dbd50-e0ca-4206-a197-0fe85a349900",
|
||||
"name": "Spawner (405)",
|
||||
"location": [2017, 1356, -90],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue