docs: Updates CLAUDE dev-docs/skills for serialization (#2372)

This commit is contained in:
Kamron Batman 2026-03-15 01:05:03 -07:00 committed by GitHub
parent ff10811d3d
commit af35c25ca2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 342 additions and 79 deletions

View file

@ -79,7 +79,7 @@ using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0, false)]
[SerializationGenerator(2, false)] // Old version was 1 → bump to 2; false because old saves used ReadInt()
public partial class ChargedGem : Item
{
[SerializableField(0)]
@ -98,6 +98,24 @@ public partial class ChargedGem : Item
Weight = 1.0;
}
// Handles loading saves from BEFORE the SerializationGenerator conversion
private void Deserialize(IGenericReader reader, int version)
{
switch (version)
{
case 1:
{
_owner = reader.ReadEntity<Mobile>();
goto case 0;
}
case 0:
{
_charges = reader.ReadInt();
break;
}
}
}
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
@ -124,7 +142,7 @@ public partial class ChargedGem : Item
| `reader.ReadInt()` | `reader.ReadInt()` | Same for manual cases |
| `reader.ReadMobile()` | `reader.ReadEntity<Mobile>()` | Generic method |
| `reader.ReadItem()` | `reader.ReadEntity<Item>()` | Generic method |
| `writer.Write((int)0)` version | `[SerializationGenerator(0, false)]` | In attribute |
| `writer.Write((int)0)` version | `[SerializationGenerator(N, false)]` | Version bumped +1; `false` because old saves used `ReadInt()` |
## Step-by-Step Conversion
@ -134,16 +152,17 @@ using ModernUO.Serialization;
```
### Step 2: Add Class Attributes and `partial`
Read the old `Serialize()` to find the version number it writes. Bump it by 1 for the `[SerializationGenerator]` first parameter. If the old `Deserialize()` used `reader.ReadInt()` (not `ReadEncodedInt()`), pass `false` as the second parameter.
```csharp
// Change:
public class MyItem : Item
// To:
[SerializationGenerator(0, false)]
// To (old Serialize wrote version 0, old Deserialize used ReadInt()):
[SerializationGenerator(1, false)]
public partial class MyItem : Item
```
The version number should be `0` for a fresh migration (you're defining a new serialization schema). Use `false` as the second argument for Item/Mobile subclasses.
### Step 3: Delete Serial Constructor
Remove `public MyItem(Serial serial) : base(serial) { }` entirely.
@ -171,8 +190,29 @@ Add `[InvalidateProperties]` if the RunUO setter called `InvalidateProperties()`
private int _charges;
```
### Step 5: Delete Serialize and Deserialize Methods
Remove both override methods entirely. The source generator creates them.
### Step 5: Migrate Serialize and Deserialize Methods
Delete the `Serialize()` override entirely — the source generator creates it.
For `Deserialize()`: if there are existing saves to support, convert it to `private void Deserialize(IGenericReader reader, int version)` (remove the `override`, change the signature). This method handles loading saves from before the SerializationGenerator conversion. Remove the `base.Deserialize(reader)` call and the version reading line — the generator handles those.
```csharp
// Old RunUO:
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
m_Charges = reader.ReadInt();
}
// Converted — keeps backward compat with old saves:
private void Deserialize(IGenericReader reader, int version)
{
_charges = reader.ReadInt();
}
```
If there are no existing saves to worry about (fresh world), you can delete `Deserialize()` entirely.
### Step 6: Change [Constructable] to [Constructible]
```csharp
@ -266,7 +306,7 @@ using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0, false)]
[SerializationGenerator(1, false)] // Old version was 0 → bump to 1; false because old saves used ReadInt()
public partial class SimpleGem : Item
{
[Constructible]
@ -276,6 +316,12 @@ public partial class SimpleGem : Item
}
public override string DefaultName => "a simple gem";
// Handles loading saves from BEFORE the SerializationGenerator conversion
private void Deserialize(IGenericReader reader, int version)
{
// Version 0 had no custom fields — nothing to read
}
}
```
@ -337,7 +383,7 @@ using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0, false)] // Version 0 — new schema
[SerializationGenerator(3, false)] // Old version was 2 → bump to 3; false because old saves used ReadInt()
public partial class MagicGem : Item
{
[SerializableField(0)]
@ -360,10 +406,37 @@ public partial class MagicGem : Item
_charges = 10;
_quality = GemQuality.Rough;
}
// Handles loading saves from BEFORE the SerializationGenerator conversion
private void Deserialize(IGenericReader reader, int version)
{
switch (version)
{
case 2:
{
_quality = (GemQuality)reader.ReadInt();
goto case 1;
}
case 1:
{
_owner = reader.ReadEntity<Mobile>();
goto case 0;
}
case 0:
{
_charges = reader.ReadInt();
break;
}
}
}
}
```
**Important**: When migrating RunUO code, the ModernUO version starts at 0 because you're defining a new serialization schema. The old version numbers from RunUO are irrelevant — the source generator doesn't read the old format. The old saves must be re-saved or a migration schema must be created.
**Important**: When migrating RunUO code:
- Read the old `Serialize()` to find the version it writes, then bump it by 1 for the `[SerializationGenerator]` first parameter
- Pass `false` as the second parameter if the old `Deserialize()` used `reader.ReadInt()` (not `ReadEncodedInt()`) — this tells the generator how old saves encoded the version number
- Keep the old deserialization logic as `private void Deserialize(IGenericReader reader, int version)` to handle loading pre-codegen saves
- This `Deserialize` method is called automatically for old saves; the generator handles new saves
## Edge Cases & Gotchas

View file

@ -82,7 +82,7 @@ using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0, false)]
[SerializationGenerator(0)]
public partial class MyItem : Item
{
private TimerExecutionToken _timerToken;

View file

@ -16,7 +16,7 @@ Most RunUO migration work involves converting Item, Mobile, and BaseCreature sub
### 2. Add Serialization Attributes
```csharp
[SerializationGenerator(0, false)] // Version 0, Item subclass
[SerializationGenerator(N, false)] // N = old version + 1; false if old Deserialize used ReadInt()
public partial class MyItem : Item // Add partial
```
@ -223,7 +223,7 @@ using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0, false)]
[SerializationGenerator(0)]
public partial class MagicLantern : Item
{
[SerializableField(0)]
@ -296,7 +296,7 @@ public partial class MagicLantern : Item
**What changed:**
- File-scoped namespace
- `partial class` + `[SerializationGenerator(0, false)]`
- `partial class` + `[SerializationGenerator(0)]` (omit `encoded` parameter)
- `[Constructable]``[Constructible]`
- `m_Charges`/`m_Owner``_charges`/`_owner` with `[SerializableField]`
- Manual properties → auto-generated with `[SerializedCommandProperty]`
@ -396,7 +396,7 @@ using ModernUO.Serialization;
namespace Server.Mobiles;
[SerializationGenerator(0, false)]
[SerializationGenerator(0)]
public partial class ForestWolf : BaseCreature
{
[Constructible]
@ -492,7 +492,7 @@ Weapons and armor follow the same item pattern but inherit from specialized base
```csharp
// ModernUO weapon example
[SerializationGenerator(0, false)]
[SerializationGenerator(0)]
public partial class MySpecialSword : BaseSword
{
[Constructible]
@ -530,7 +530,7 @@ public partial class MySpecialSword : BaseSword
If a class changed namespace or name, use `[TypeAlias]`:
```csharp
[TypeAlias("Server.Items.OldName")]
[SerializationGenerator(0, false)]
[SerializationGenerator(0)]
public partial class NewName : Item { }
```

View file

@ -171,7 +171,7 @@ using Server.Gumps;
namespace Server.Custom.BountySystem;
[SerializationGenerator(0, false)]
[SerializationGenerator(0)]
public partial class BountyBoard : Item
{
[Constructible]