docs: update serialization docs and skills for generator v4

Replaces order-based linkage ([SerializableFieldSaveFlag]/[SerializableFieldDefault])
with field-side [SaveFlag(nameof(...))], [TimerDrift]/[DeserializeTimerField] with
[DeserializeTimer(nameof(Method), wallClock)], and documents the [SerializableField]
setter hooks (allowFieldChange/fieldChanged), the anchored-time semantics for
drifting timers, [AnchoredDateTime], and the timer MigrateFrom pattern. Narrows
[SerializableProperty] guidance to custom getters. Also fixes the documented
[SerializableField] signature (the saveIf parameter never existed) and refreshes
real-code examples that were converted in the v4 migration PRs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-08-22 18:26:56 -07:00
parent b042edcf0b
commit 76bbcd88c4
No known key found for this signature in database
GPG key ID: 7D81DF26D9A5D94A
7 changed files with 219 additions and 63 deletions

View file

@ -457,16 +457,24 @@ set
```
Without this, changes won't be saved.
Most RunUO custom setters only clamp the value or run side effects after assignment. Those
convert to a plain `[SerializableField]` with the `allowFieldChange`/`fieldChanged` hooks,
which handle the equality check and `MarkDirty()` for you -- reserve `[SerializableProperty]`
for custom getters (see `dev-docs/serialization.md`).
### 3. Field Ordering
The `[SerializableField(N)]` index determines serialization order. Choose a logical order and don't change it after the first save — or increment the version.
### 4. Conditional Serialization
Use `[SerializableFieldSaveFlag]` and `[SerializableFieldDefault]` to skip default values:
Use `[SaveFlag]` on the serializable field to skip default values (the second method is
optional -- omit it and the field keeps its default at load):
```csharp
[SerializableFieldSaveFlag(0)]
[SerializableField(0)]
[SaveFlag(nameof(ShouldSerializeMaxItems), nameof(MaxItemsDefaultValue))]
private int _maxItems;
private bool ShouldSerializeMaxItems() => _maxItems != -1;
[SerializableFieldDefault(0)]
private int MaxItemsDefaultValue() => -1;
```
@ -479,12 +487,15 @@ private List<Mobile> _followers;
```
### 6. DateTime Fields
Use `[DeltaDateTime]` to survive server restarts:
Use `[AnchoredDateTime]` to survive server restarts -- the value is shifted by downtime at
load, so the remaining time is preserved and idle saves stay byte-stable:
```csharp
[DeltaDateTime]
[AnchoredDateTime]
[SerializableField(0)]
private DateTime _expireTime;
```
(`[DeltaDateTime]` is the legacy equivalent; it rewrites bytes on every save. Converting an
existing field between the two changes the wire format and requires a version bump.)
### 7. Keeping Manual Serialization (Rare)
Some edge cases still need manual serialization. If a type has complex conditional logic that can't be expressed with attributes, you can implement `ISerializable` manually. But this is rare — try attributes first.