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": "20f986bd-7976-415c-bf34-2df8bde4025e",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5150, 742, 0],
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "28f3eccc-1c12-4576-ae46-e9cfb11fb19f",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5163, 567, 0],
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3dfec3c5-2cc6-4908-b975-51182b394218",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5286, 542, 0],
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4563aa47-639e-47f3-a301-c430a6e1777a",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5187, 603, 0],
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "462bded2-9162-4b79-a1a3-7d35a889f899",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5147, 579, -50],
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4afcb2a6-cecf-4d42-a449-de7e061ccd6c",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5190, 655, 0],
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4fb75ec8-f949-4480-b573-064a299b34c0",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5327, 542, 0],
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "5018fced-3b89-4654-88ac-143ceed1e566",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5212, 531, 0],
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "564f98cb-f8fe-43dd-a180-9da4652a4f1e",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5342, 582, 0],
|
||||
|
|
@ -159,7 +159,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "591e02f3-9855-42a6-ae25-d11d0fbe788b",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5323, 571, 0],
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "5b1e99f8-bde4-41f5-a76d-78b9dca9844c",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5145, 618, -50],
|
||||
|
|
@ -193,7 +193,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "5c3582a8-fa9c-4839-aff6-82457f87f023",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5145, 622, -50],
|
||||
|
|
@ -210,7 +210,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "5da72c5b-46fd-4a13-962f-3accc945f462",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5147, 598, -50],
|
||||
|
|
@ -227,7 +227,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "6006bae6-ddb4-4c35-9c38-c1378f71e584",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5267, 690, 0],
|
||||
|
|
@ -245,7 +245,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "6ac0a6d7-ac00-48c9-bb46-827c103b44ef",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5283, 583, 0],
|
||||
|
|
@ -262,7 +262,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7038def8-3dd9-481a-aff9-b344e58e0122",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5151, 718, 0],
|
||||
|
|
@ -281,7 +281,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7a2d79d7-d842-4977-8449-5d7735e73c6f",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5280, 675, 8],
|
||||
|
|
@ -297,7 +297,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7f6975e8-9689-441b-b484-e0c803cf29db",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5170, 678, 0],
|
||||
|
|
@ -316,7 +316,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "8a63e78b-f33d-4502-9760-81c36f272658",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5299, 603, 0],
|
||||
|
|
@ -335,7 +335,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "8f579b88-cb8e-4c17-b795-fd4d1d3d69d2",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5320, 708, 0],
|
||||
|
|
@ -351,7 +351,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "94066b8f-d45a-4273-b649-ab593fbe55e8",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5186, 575, 0],
|
||||
|
|
@ -367,7 +367,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9f4cf647-abba-4181-aa0a-b477d21800c4",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5322, 626, 0],
|
||||
|
|
@ -387,7 +387,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a3e5c4ff-6bd0-4380-aebd-47ee3c1c1421",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5314, 748, -20],
|
||||
|
|
@ -407,7 +407,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a44b5c06-0cda-4edc-8468-fa4a00a52b1e",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5295, 623, -5],
|
||||
|
|
@ -423,7 +423,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b094c982-2cfd-42ff-ab54-b175a134de30",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5318, 578, 0],
|
||||
|
|
@ -443,7 +443,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b0cf1788-6671-41ad-aa47-4e8287fbac5e",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5212, 674, -20],
|
||||
|
|
@ -462,7 +462,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b83d71f9-6539-4a90-a43a-4feb8ee48678",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5145, 553, -40],
|
||||
|
|
@ -479,7 +479,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "bf4c7226-e5d1-4d90-b70c-cc4d3ad472cb",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5307, 675, -20],
|
||||
|
|
@ -496,7 +496,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "cd67b487-cfbb-436c-9623-b05e87602698",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5219, 734, -20],
|
||||
|
|
@ -512,7 +512,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d2d34809-99ba-41b9-9f74-ed1a65248ca0",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5139, 661, 0],
|
||||
|
|
@ -531,7 +531,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d729f42a-87ef-4a42-8a3b-b65ae177ab40",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5219, 552, 0],
|
||||
|
|
@ -550,7 +550,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d846b9eb-23d3-4b86-8a61-20331e2cf8db",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5307, 546, 0],
|
||||
|
|
@ -569,7 +569,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f2dac00d-2532-4fc4-8ae2-bde51687b3e1",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5219, 573, 0],
|
||||
|
|
@ -588,7 +588,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f3f74896-465e-4cc7-bb83-63d508157390",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5183, 727, 0],
|
||||
|
|
@ -605,7 +605,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f4c26861-33c2-4f4b-af35-62f556e385cb",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5187, 545, 0],
|
||||
|
|
@ -621,7 +621,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f6f496d1-7f72-4e0a-be1f-e8249bc31633",
|
||||
"name": "Spawner (104)",
|
||||
"location": [5192, 695, 0],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue