## Summary Adopts ModernUO.Serialization 4.0.0 across the engine. Three commits, reviewable independently: 1. **Package + tool bump to 4.0.0** (`Server.csproj`, `UOContent.csproj`, `dotnet-tools.json`). 2. **Timers → `[DeserializeTimer]`** — the 8 drifting timers (BaseLight, TreasureMapChest, MarkContainer, FillableContainer, DeathRobe, DecayedCorpse, Corpse, BaseEscortable) now store their next tick as **anchored time**: server downtime no longer consumes the remaining delay, and idle-world saves are byte-stable. This changes their wire format, so each class bumps its serialization version with a `MigrateFrom` that replays the old delta-time read through the migration schema (the new `vN.json` files carry `@AnchoredTimer`; the old ones keep `@TimerDrift`, which the generator reads forever). The 2 wall-clock timers (Aquarium, FountainOfLife) keep their exact format via `wallClock: true` — no bump. Restart methods drop their `TimeSpan.MinValue` sentinel checks: v4 invokes them **only when a timer was actually running at save**. 3. **Linkage → field-side declarations** — 175 conversions across 25 files: `[SerializableFieldSaveFlag(order)]`/`[SerializableFieldDefault(order)]` become `[SaveFlag(nameof(...), nameof(...))]` on the field, and `[SerializableFieldChanged(order)]` becomes the `fieldChanged:` argument of `[SerializableField]`. **Wire-neutral: zero migration schemas changed.** ## Verification - Solution builds with **0 errors, 0 warnings**; all three 4.0.0 packages verified indexed on nuget.org (no local feed needed). - **835 + 708 tests green.** - Generated output inspected: old-version content structs replay `ReadDeltaTime` (e.g. `V3Content.DecayTimerNext = reader.ReadDeltaTime()`), current versions write/read anchored time with the gated restart, and the wall-clock classes emit byte-identical `Write`/`ReadDateTime` framing. - Schema tool run is committed (CI's `git diff --exit-code` schema check passes): exactly the 8 expected new `vN.json` files, nothing else touched. - The conversion was scripted with a class-scoped resolver (order → same-class `[SerializableField(order)]`/`[SerializableProperty(order)]`); it planned 175/175 with zero ambiguities before applying. ## Notes - New `MigrateFrom`s use the content structs' provided `XxxDelay` property, matching the pre-existing idiom in Corpse's and TreasureMapChest's older migrations. - Follow-up candidate (separate PR, wire-neutral, any time): fold the ~150 eligible hand-written `[SerializableProperty]` setters (clamps, post-change side effects) down to `[SerializableField]` with `allowFieldChange`/`fieldChanged` hooks.
137 lines
No EOL
3.1 KiB
JSON
Generated
137 lines
No EOL
3.1 KiB
JSON
Generated
{
|
|
"version": 18,
|
|
"type": "Server.Items.Corpse",
|
|
"properties": [
|
|
{
|
|
"name": "RestoreEquip",
|
|
"type": "System.Collections.Generic.List\u003CServer.Item\u003E",
|
|
"rule": "ListMigrationRule",
|
|
"ruleArguments": [
|
|
"Server.Item",
|
|
"SerializableInterfaceMigrationRule"
|
|
]
|
|
},
|
|
{
|
|
"name": "Flags",
|
|
"type": "Server.Items.CorpseFlag",
|
|
"rule": "EnumMigrationRule"
|
|
},
|
|
{
|
|
"name": "TimeOfDeath",
|
|
"type": "System.DateTime",
|
|
"rule": "PrimitiveTypeMigrationRule",
|
|
"ruleArguments": [
|
|
"DeltaTime"
|
|
]
|
|
},
|
|
{
|
|
"name": "RestoreTable",
|
|
"type": "System.Collections.Generic.Dictionary\u003CServer.Item, Server.Point3D\u003E",
|
|
"rule": "DictionaryMigrationRule",
|
|
"ruleArguments": [
|
|
"Server.Item",
|
|
"SerializableInterfaceMigrationRule",
|
|
"0",
|
|
"Server.Point3D",
|
|
"PrimitiveUOTypeMigrationRule",
|
|
"1",
|
|
"Point3D"
|
|
]
|
|
},
|
|
{
|
|
"name": "DecayTimer",
|
|
"type": "Server.Timer",
|
|
"rule": "TimerMigrationRule",
|
|
"ruleArguments": [
|
|
"@AnchoredTimer"
|
|
]
|
|
},
|
|
{
|
|
"name": "Looters",
|
|
"type": "System.Collections.Generic.HashSet\u003CServer.Mobile\u003E",
|
|
"rule": "HashSetMigrationRule",
|
|
"ruleArguments": [
|
|
"Server.Mobile",
|
|
"SerializableInterfaceMigrationRule"
|
|
]
|
|
},
|
|
{
|
|
"name": "Killer",
|
|
"type": "Server.Mobile",
|
|
"rule": "SerializableInterfaceMigrationRule"
|
|
},
|
|
{
|
|
"name": "Aggressors",
|
|
"type": "System.Collections.Generic.List\u003CServer.Mobile\u003E",
|
|
"rule": "ListMigrationRule",
|
|
"ruleArguments": [
|
|
"Server.Mobile",
|
|
"SerializableInterfaceMigrationRule"
|
|
]
|
|
},
|
|
{
|
|
"name": "Owner",
|
|
"type": "Server.Mobile",
|
|
"rule": "SerializableInterfaceMigrationRule"
|
|
},
|
|
{
|
|
"name": "CorpseName",
|
|
"type": "string",
|
|
"rule": "PrimitiveTypeMigrationRule",
|
|
"ruleArguments": [
|
|
""
|
|
]
|
|
},
|
|
{
|
|
"name": "AccessLevel",
|
|
"type": "Server.AccessLevel",
|
|
"rule": "EnumMigrationRule"
|
|
},
|
|
{
|
|
"name": "Guild",
|
|
"type": "Server.Guilds.Guild",
|
|
"rule": "SerializableInterfaceMigrationRule"
|
|
},
|
|
{
|
|
"name": "EquipItems",
|
|
"type": "System.Collections.Generic.List\u003CServer.Item\u003E",
|
|
"rule": "ListMigrationRule",
|
|
"ruleArguments": [
|
|
"Server.Item",
|
|
"SerializableInterfaceMigrationRule"
|
|
]
|
|
},
|
|
{
|
|
"name": "HairItemId",
|
|
"type": "int",
|
|
"rule": "PrimitiveTypeMigrationRule",
|
|
"ruleArguments": [
|
|
""
|
|
]
|
|
},
|
|
{
|
|
"name": "HairHue",
|
|
"type": "int",
|
|
"rule": "PrimitiveTypeMigrationRule",
|
|
"ruleArguments": [
|
|
""
|
|
]
|
|
},
|
|
{
|
|
"name": "FacialHairItemId",
|
|
"type": "int",
|
|
"rule": "PrimitiveTypeMigrationRule",
|
|
"ruleArguments": [
|
|
""
|
|
]
|
|
},
|
|
{
|
|
"name": "FacialHairHue",
|
|
"type": "int",
|
|
"rule": "PrimitiveTypeMigrationRule",
|
|
"ruleArguments": [
|
|
""
|
|
]
|
|
}
|
|
]
|
|
} |