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": "04207c29-fe38-4d43-a08d-8645a3a312e8",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5441, 680, 20],
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "05844644-3d42-4021-bb28-8114d8f7f064",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5420, 746, 5],
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "0a6bf903-a9a3-4252-a4a0-652d89fb9381",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5528, 902, 30],
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "11e8cbb5-45dd-4109-8606-54eb9bada453",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5460, 526, 60],
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "1855dd2e-81b1-4326-bc97-f3e50884e09a",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5391, 905, 30],
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "1adcf3d5-536f-473d-af18-4ae23415ebba",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5505, 747, 5],
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "21b9e0f9-7627-4967-b7bf-5453be3e0266",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5470, 860, 45],
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "310b722e-fe5d-4692-97c7-956d15b76472",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5482, 791, 60],
|
||||
|
|
@ -130,7 +130,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "38967322-06dc-48bc-bed1-6dd60661b1c0",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5387, 697, 15],
|
||||
|
|
@ -147,7 +147,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "38fe4fe8-2f5d-441f-8f4b-8f679a955e96",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5546, 888, 30],
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3d6315d9-3968-4b92-b71a-99a12bab509e",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5590, 871, 45],
|
||||
|
|
@ -180,7 +180,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "441cff6e-d90b-4a74-bd0a-b10165fd6eb4",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5426, 943, 20],
|
||||
|
|
@ -196,7 +196,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4780e4d1-5a3a-4519-a8db-3cc6e77648c7",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5398, 996, 5],
|
||||
|
|
@ -212,7 +212,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4d00942a-33e2-4714-8d03-9d6a40778772",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5390, 587, 45],
|
||||
|
|
@ -229,7 +229,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "51d69140-0708-491e-9dbd-9c5d58349172",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5504, 939, 20],
|
||||
|
|
@ -245,7 +245,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "5c5b2844-5792-434b-9d9e-e5f607f32b6b",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5439, 851, 45],
|
||||
|
|
@ -261,7 +261,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "6a01c304-d63d-4a59-8b00-d004329d1af7",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5483, 711, 15],
|
||||
|
|
@ -277,7 +277,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "6b94a3e0-7977-459b-bc5f-6c13ce0062ff",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5405, 576, 61],
|
||||
|
|
@ -293,7 +293,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "70c7a94c-7535-432d-917a-640d0a6f47ff",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5605, 799, 60],
|
||||
|
|
@ -309,7 +309,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "74032d64-a50f-4f99-ad45-3d9ad9f1d718",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5410, 611, 45],
|
||||
|
|
@ -325,7 +325,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "74fc3f59-8ee2-4ee5-b246-ddf07c415ca5",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5557, 828, 45],
|
||||
|
|
@ -341,7 +341,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7573ab39-270e-45f0-99db-4304837ef7bb",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5487, 902, 30],
|
||||
|
|
@ -357,7 +357,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7c71f2a8-2d6b-40b8-8dca-9eca3aa54d47",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5397, 995, 5],
|
||||
|
|
@ -373,7 +373,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7ec31075-3cef-4d6b-a20e-2c55540052fc",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5391, 660, 20],
|
||||
|
|
@ -389,7 +389,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "831675ca-f594-4e7e-bd6f-b0f15ed60bf0",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5504, 597, 45],
|
||||
|
|
@ -405,7 +405,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "8e65ea3f-2bca-43cd-b6e2-916b8625d69b",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5398, 995, 5],
|
||||
|
|
@ -421,7 +421,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "93356ad6-8774-42cd-ab96-705c2d55e109",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5604, 789, 60],
|
||||
|
|
@ -437,7 +437,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9dbd8c5c-40d7-4908-8fbd-86cd1f149be0",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5503, 529, 60],
|
||||
|
|
@ -455,7 +455,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9df386c1-05f8-4290-960b-8d0b6f0393ce",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5593, 794, 60],
|
||||
|
|
@ -471,7 +471,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9e9bcade-4455-4c85-b7f2-4d9dc6e6c6f4",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5394, 525, 65],
|
||||
|
|
@ -488,7 +488,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9f576fea-09aa-47fb-a3e4-a1b8369b650e",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5386, 612, 45],
|
||||
|
|
@ -506,7 +506,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b328679f-d212-49e8-8900-af2cd1f77b04",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5564, 821, 45],
|
||||
|
|
@ -522,7 +522,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b453194c-94e8-4d58-87cc-3870d6292247",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5507, 656, 20],
|
||||
|
|
@ -539,7 +539,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "ba7fd9a9-bffc-4f04-8606-ae79b4bb830b",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5411, 541, 60],
|
||||
|
|
@ -555,7 +555,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "bb7fc3f7-d80d-44c5-8a63-743a970f5842",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5385, 703, 15],
|
||||
|
|
@ -571,7 +571,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "c045d499-1fe2-4afa-be89-e8e5f3c6f4f8",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5475, 748, 5],
|
||||
|
|
@ -588,7 +588,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "c739ce37-7e4b-4a3a-886b-4f45a049cde6",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5558, 824, 45],
|
||||
|
|
@ -604,7 +604,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "c99bb2a1-1690-4656-95d7-d61b0c5640c8",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5490, 676, 20],
|
||||
|
|
@ -620,7 +620,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "cb91b20f-4aee-45c7-b70b-de22c9adcdae",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5471, 973, 15],
|
||||
|
|
@ -636,7 +636,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d58f315e-8fcf-4282-a4e4-5aaed9c19cd5",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5532, 787, 60],
|
||||
|
|
@ -652,7 +652,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f0922369-d73d-4c7e-a669-28812e0a0559",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5435, 589, 47],
|
||||
|
|
@ -668,7 +668,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f8f62c01-5bf8-41dc-ac7a-37cc7a97134b",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5426, 791, 60],
|
||||
|
|
@ -684,7 +684,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "fb3ee679-fc60-4abb-b6b0-a809f77ca53b",
|
||||
"name": "Spawner (205)",
|
||||
"location": [5464, 600, 45],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue