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": "05d2885b-2b4d-4c4e-907e-2fdbe4d54e69",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5212, 674, -20],
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "0b6e0a59-f918-4f77-8bc8-f5b5d944d3a5",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5219, 573, 0],
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "0be3858f-bd81-4a27-9a8d-3846c0df0603",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5192, 695, 0],
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "19d96c26-d7f8-4209-9071-00e0117ed1d8",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5163, 567, 0],
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "1cbfae9c-7cc7-45cc-842f-0e75a158cd8a",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5307, 546, 0],
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "25b2761d-dc06-443e-b979-049c7f50abf6",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5267, 690, 0],
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "2a7f9024-7d0f-43a0-8721-52b6a3c27c8f",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5307, 675, -20],
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "350cfc3c-1c89-463d-a93a-f2ab22149d19",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5323, 571, 0],
|
||||
|
|
@ -144,7 +144,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "35acdddb-eef4-4687-b6d7-6dea19535b71",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5314, 748, -20],
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3d25517a-5b90-4468-9e23-4041f88774e2",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5299, 603, 0],
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "56939d4e-8a5d-47da-a117-e7f56ff6671b",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5151, 718, 0],
|
||||
|
|
@ -202,7 +202,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "684234a4-21ea-4f94-897b-e92d88a17ced",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5219, 552, 0],
|
||||
|
|
@ -221,7 +221,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7016863d-26dc-4d2e-beda-68031a966ebe",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5318, 578, 0],
|
||||
|
|
@ -241,7 +241,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "750ebeff-4327-4bf0-b285-8c6749530985",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5190, 655, 0],
|
||||
|
|
@ -257,7 +257,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "79dfd73d-ede7-44e2-8651-94cfe5f77314",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5342, 582, 0],
|
||||
|
|
@ -274,7 +274,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "818d1f8c-a5ba-4ef1-a823-2b04bca2f2e2",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5320, 708, 0],
|
||||
|
|
@ -290,7 +290,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "8507dbe4-8220-4439-adbe-5ef6ebb0fec3",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5186, 575, 0],
|
||||
|
|
@ -306,7 +306,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "88b5fff8-7124-4a19-83ad-465e8aaf072b",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5150, 742, 0],
|
||||
|
|
@ -324,7 +324,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9862d38e-e12f-4e04-932f-0020f3121d9a",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5145, 553, -40],
|
||||
|
|
@ -341,7 +341,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9b00a63e-cf2d-495b-8bd9-61cef122f168",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5286, 542, 0],
|
||||
|
|
@ -360,7 +360,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a2d6e670-8282-4b60-bd5f-933e45d3d7a8",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5170, 678, 0],
|
||||
|
|
@ -379,7 +379,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "ab35ec53-2a16-4cdd-a2a1-f1b84922e36c",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5147, 598, -50],
|
||||
|
|
@ -396,7 +396,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "c69344fe-718b-4e69-83b8-d12141250707",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5219, 734, -20],
|
||||
|
|
@ -412,7 +412,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "cacb456a-f52d-4168-9d59-8df80779a1d6",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5183, 727, 0],
|
||||
|
|
@ -429,7 +429,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "cb79321c-1a80-4881-8961-1997174d0458",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5212, 531, 0],
|
||||
|
|
@ -446,7 +446,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d4857128-1876-473c-951f-418107fad15c",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5327, 542, 0],
|
||||
|
|
@ -465,7 +465,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d8ba7cc3-c1f6-4701-b684-850203cb9e37",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5147, 579, -50],
|
||||
|
|
@ -482,7 +482,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d9f4d863-626f-480c-968a-9630e6a9a83d",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5145, 622, -50],
|
||||
|
|
@ -499,7 +499,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "dbb8562b-7557-4347-9faa-a5facf1ba1d4",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5280, 675, 8],
|
||||
|
|
@ -515,7 +515,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "edd28bb3-5aeb-487a-8571-9e4e4aea0783",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5187, 603, 0],
|
||||
|
|
@ -533,7 +533,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "efc2564d-a6df-40a6-ad5e-6e6e81d066e9",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5322, 626, 0],
|
||||
|
|
@ -553,7 +553,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f2fc19bd-75c9-4f68-b59d-6efc65381167",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5283, 583, 0],
|
||||
|
|
@ -570,7 +570,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f5e81dcf-fa17-4d36-8aa1-e73496aad012",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5139, 661, 0],
|
||||
|
|
@ -589,7 +589,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f6b68da6-e7f9-4cb1-870c-079ebff4787b",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5145, 618, -50],
|
||||
|
|
@ -606,7 +606,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "fa668f86-9786-48bc-a676-5a879e77bee2",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5295, 623, -5],
|
||||
|
|
@ -622,7 +622,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "fb409b68-fe6b-422f-bf2d-95c4a92f5e21",
|
||||
"name": "Spawner (204)",
|
||||
"location": [5187, 545, 0],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue