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": "03498749-3218-4779-8864-b854b36c79f7",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5839, 159, 0],
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "0614ccac-5ffc-4223-8348-cbb5a0f0cc2d",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5785, 161, -6],
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "1cf83838-f5d3-42f9-8390-f445a01ac889",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5754, 211, -6],
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "33003603-a1fc-43a4-b9d7-2408eb2e5e50",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5848, 226, 0],
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3bf7b937-9eb6-45fc-81e6-d907910ca62a",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5758, 139, 5],
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "3d3f7414-5249-4ee4-9602-c842af0da38c",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5799, 188, -6],
|
||||
|
|
@ -110,7 +110,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "479095c0-02ab-4651-9edc-362d77a65a65",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5763, 187, 0],
|
||||
|
|
@ -126,7 +126,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4ab984eb-913e-4b21-a9b2-5242726ae03f",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5731, 176, -5],
|
||||
|
|
@ -144,7 +144,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "4b8dd28c-f72f-45c5-afc4-84dc637c1e0a",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5684, 181, -5],
|
||||
|
|
@ -160,7 +160,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "51445c30-3ef8-4c0e-95bc-b12e643e39ba",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5669, 330, 0],
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "57c38552-9c17-4451-9e53-3a8d2048d2f6",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5666, 331, 0],
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "5a62f325-4e00-49c3-8576-fdd9fcf330e5",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5771, 224, -2],
|
||||
|
|
@ -208,7 +208,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "61e8233d-f991-4429-9e4e-c7e0d24201d0",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5757, 213, -7],
|
||||
|
|
@ -228,7 +228,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "72473a5f-3d9b-4f69-b3f9-0d266291468a",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5824, 363, 0],
|
||||
|
|
@ -244,7 +244,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "7b080104-77b8-491b-b8f9-44cec8ecbb65",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5680, 306, 0],
|
||||
|
|
@ -260,7 +260,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "89792e6b-89bb-4c0a-8f45-259ab52f6844",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5676, 331, 0],
|
||||
|
|
@ -276,7 +276,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "8aba961b-8ce4-49ba-9ec8-7861204815c4",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5831, 358, -1],
|
||||
|
|
@ -294,7 +294,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "9b93a6e4-67e5-4d22-aa51-ff84ea98bb72",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5751, 140, 10],
|
||||
|
|
@ -310,7 +310,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "a3c447a5-c7bb-4826-a5ae-81d8b3863130",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5854, 227, 0],
|
||||
|
|
@ -326,7 +326,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b8a2691a-996a-4c38-834f-897b972bfe2f",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5867, 218, -4],
|
||||
|
|
@ -344,7 +344,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "b96eb341-ef4c-4b7e-ba01-6fb487be56a7",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5721, 147, -1],
|
||||
|
|
@ -360,7 +360,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "bca4ab21-15b1-4b04-b698-05d4f8df1f6c",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5838, 355, 0],
|
||||
|
|
@ -376,7 +376,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "cb37f510-fff9-4c76-8c51-2d59ddd8f715",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5683, 197, -4],
|
||||
|
|
@ -392,7 +392,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "cdb2ea29-4ee5-48c3-9444-018c5003afbd",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5837, 245, -5],
|
||||
|
|
@ -408,7 +408,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d31d21b1-8768-4d45-a962-182e67f5a5c0",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5850, 219, -3],
|
||||
|
|
@ -424,7 +424,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d5ca5e49-cbb2-4079-81f1-c8679fcecb46",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5696, 303, 0],
|
||||
|
|
@ -440,7 +440,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d7d771c1-605a-4253-965e-dec048f72502",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5752, 144, 9],
|
||||
|
|
@ -456,7 +456,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "d8d3fc0f-231c-4d04-b0a3-7191cc4512b2",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5681, 191, -6],
|
||||
|
|
@ -476,7 +476,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "e111cb63-9804-4eac-96ad-290a0b963c8f",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5814, 237, 0],
|
||||
|
|
@ -492,7 +492,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "e89bcb09-65c3-4474-bded-50e62ce3c153",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5712, 145, -34],
|
||||
|
|
@ -508,7 +508,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "ed18e76c-c568-42fe-9c98-8482c467ff61",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5756, 203, -2],
|
||||
|
|
@ -524,7 +524,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f2eb3f17-d5f4-4c58-a248-825e46a8a0f8",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5772, 188, -3],
|
||||
|
|
@ -540,7 +540,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "f522aee5-3ff7-427c-a3f1-31b162a65fbe",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5682, 314, 0],
|
||||
|
|
@ -556,7 +556,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "Spawner",
|
||||
"$type": "Spawner",
|
||||
"guid": "fe1657a5-a11c-4fe6-9544-a7ed0d2059b2",
|
||||
"name": "Spawner (210)",
|
||||
"location": [5833, 242, -2],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue