ModernUO/dev-docs/runuo-migration-docs/07-commands-events.md
Kamron Batman 4f9bc1d9f6
feat: Adds AI skills to migrate from RunUO (#2366)
## Summary

Adds comprehensive RunUO → ModernUO migration documentation and Claude AI skills to help shard owners and script authors convert RunUO 2.7 code to ModernUO.

- **10 migration skills** (`dev-docs/claude-skills/migrate-from-runuo/`) — system-by-system conversion guides (foundation, serialization, timers, gumps, packets, property lists, commands/events, persistence, items/mobiles, systems/engines)
- **12 reference docs** (`dev-docs/runuo-migration-docs/`) — deep-reference with before/after examples, API mapping tables, edge cases, and gotchas
- **Updated existing skills** — `modernuo-timers`, `modernuo-serialization`, and `modernuo-threading` now document that `Serialize()` runs on background threads and timers are not thread-safe
- **Updated `CLAUDE.md`** — added migration skill lookup table

### Key migration patterns covered
- Manual `Serialize()`/`Deserialize()` → source-generated `[SerializableField]`
- `Packet` class hierarchy → static `SpanWriter`/`SpanReader` methods
- `Timer` subclasses → `TimerExecutionToken` fire-and-forget
- `Gump` → `StaticGump<T>`/`DynamicGump` with builders
- `EventSink.WorldSave` → `GenericPersistence`
- `ObjectPropertyList` → `IPropertyList` with string hole rules
- Universal changes: naming (`m_` → `_`), `[Constructable]` → `[Constructible]`, logging, spatial queries
2026-03-13 00:33:45 -07:00

211 lines
7 KiB
Markdown

# Commands & Events Migration
## Overview
Commands are largely similar between RunUO and ModernUO — the `CommandSystem.Register()` API is the same. The main changes are handler attribute conventions and that events now use `Action<T>` delegates instead of custom delegate types.
## Command Changes
### Registration (Same Pattern)
```csharp
// Both RunUO and ModernUO
public static void Configure() // ModernUO uses Configure(), RunUO may use Initialize()
{
CommandSystem.Register("MyCommand", AccessLevel.GameMaster, MyCommand_OnCommand);
}
```
**Key difference**: ModernUO prefers `Configure()` for registration. RunUO often uses `Initialize()`. Both work, but `Configure()` runs earlier in the startup sequence and is the convention.
### Handler Attributes
```csharp
// RunUO
[Usage("MyCommand <arg>")]
[Description("Does something")]
// ModernUO — same attributes, plus optional Aliases
[Usage("MyCommand <arg>")]
[Description("Does something")]
[Aliases("mc", "mycmd")]
```
### CommandEventArgs (Same)
```csharp
public static void MyCommand_OnCommand(CommandEventArgs e)
{
var from = e.Mobile;
var name = e.GetString(0);
var count = e.Length > 1 ? e.GetInt32(1) : 1;
}
```
No changes needed for command handlers themselves.
## Event System Migration
### RunUO EventSink Pattern
```csharp
// RunUO — custom delegate types
public static void Initialize()
{
EventSink.WorldSave += new WorldSaveEventHandler(OnWorldSave);
EventSink.WorldLoad += new WorldLoadEventHandler(OnWorldLoad);
EventSink.Login += new LoginEventHandler(OnLogin);
EventSink.Logout += new LogoutEventHandler(OnLogout);
EventSink.Speech += new SpeechEventHandler(OnSpeech);
EventSink.Movement += new MovementEventHandler(OnMovement);
EventSink.ServerStarted += new ServerStartedEventHandler(OnServerStarted);
EventSink.Crashed += new CrashedEventHandler(OnCrashed);
}
private static void OnWorldSave(WorldSaveEventArgs e)
{
// Save data to file
}
private static void OnLogin(LoginEventArgs e)
{
Mobile m = e.Mobile;
m.SendMessage("Welcome!");
}
```
### ModernUO EventSink Pattern
```csharp
// ModernUO — Action<T> delegates, changed event names
public static void Configure()
{
EventSink.WorldSave += OnWorldSave; // Action (no args)
EventSink.WorldLoad += OnWorldLoad; // Action (no args)
EventSink.Connected += OnConnected; // Action<Mobile> — was Login
EventSink.Disconnected += OnDisconnected; // Action<Mobile> — was Logout
EventSink.Speech += OnSpeech; // Action<SpeechEventArgs>
EventSink.Movement += OnMovement; // Action<MovementEventArgs>
EventSink.ServerStarted += OnServerStarted; // Action (no args)
EventSink.ServerCrashed += OnCrashed; // Action<ServerCrashedEventArgs>
}
private static void OnWorldSave()
{
// For persistence, use GenericPersistence instead (see 08-persistence.md)
}
private static void OnConnected(Mobile m)
{
m.SendMessage("Welcome!");
}
```
## Event Migration Mapping
| RunUO Event | ModernUO Event | Signature Change |
|---|---|---|
| `EventSink.WorldSave` | `EventSink.WorldSave` | `WorldSaveEventArgs``Action` (no args) |
| `EventSink.WorldLoad` | `EventSink.WorldLoad` | `WorldLoadEventArgs``Action` (no args) |
| `EventSink.Login` | `EventSink.Connected` | `LoginEventArgs``Action<Mobile>` |
| `EventSink.Logout` | `EventSink.Disconnected` | `LogoutEventArgs``Action<Mobile>` |
| `EventSink.Speech` | `EventSink.Speech` | `SpeechEventArgs` (mostly same) |
| `EventSink.Movement` | `EventSink.Movement` | `MovementEventArgs` (mostly same) |
| `EventSink.ServerStarted` | `EventSink.ServerStarted` | `Action` (no args) |
| `EventSink.Crashed` | `EventSink.ServerCrashed` | Renamed |
| `EventSink.AggressiveAction` | `EventSink.AggressiveAction` | `AggressiveActionEventArgs` |
| `EventSink.AccountLogin` | `EventSink.AccountLogin` | `AccountLoginEventArgs` |
| `EventSink.SocketConnect` | `EventSink.SocketConnect` | `SocketConnectEventArgs` |
| `EventSink.BeforeWorldSave` | Removed | Use `WorldSave` event directly |
| `EventSink.Shutdown` | `EventSink.Shutdown` | `Action` |
| `EventSink.CharacterCreated` | Removed/Restructured | Check current source |
| `EventSink.OpenDoorMacroUsed` | Removed | Handle in movement/speech |
| `EventSink.PlayerDeath` | Use CodeGeneratedEvents | `PlayerMobile.PlayerDeathEvent` |
| `EventSink.CreatureDeath` | Use CodeGeneratedEvents | `BaseCreature.CreatureDeathEvent` |
## Step-by-Step Conversion
### Step 1: Change Initialize() to Configure()
```csharp
// RunUO
public static void Initialize()
// ModernUO
public static void Configure()
```
### Step 2: Remove Delegate Type Constructors
```csharp
// RunUO
EventSink.Login += new LoginEventHandler(OnLogin);
// ModernUO
EventSink.Connected += OnConnected;
```
### Step 3: Update Event Names
Rename `Login` to `Connected`, `Logout` to `Disconnected`, etc. (see mapping table).
### Step 4: Update Handler Signatures
```csharp
// RunUO
private static void OnLogin(LoginEventArgs e)
{
Mobile m = e.Mobile;
}
// ModernUO
private static void OnConnected(Mobile m)
{
// Mobile is passed directly
}
```
### Step 5: WorldSave/WorldLoad → GenericPersistence
If the event handler was saving/loading data to binary files, convert to `GenericPersistence` instead. See `08-persistence.md`.
```csharp
// RunUO — manual file persistence via EventSink
EventSink.WorldSave += new WorldSaveEventHandler(Save);
EventSink.WorldLoad += new WorldLoadEventHandler(Load);
// ModernUO — use GenericPersistence class (see 08-persistence.md)
// Don't use EventSink.WorldSave for custom persistence
```
## CodeGeneratedEvents
For entity-specific events, ModernUO uses source-generated events:
```csharp
// Subscribing to a generated event
[OnEvent(nameof(PlayerMobile.PlayerLoginEvent))]
public static void HandlePlayerLogin(PlayerMobile player)
{
// Handle player login
}
```
Known generated events:
- `PlayerMobile.PlayerLoginEvent`
- `PlayerMobile.PlayerDeathEvent`
- `BaseCreature.CreatureDeathEvent`
## Edge Cases & Gotchas
### 1. WorldSave No Longer Has EventArgs
RunUO's `WorldSaveEventArgs` contained the save path. In ModernUO, use `WorldSavePostSnapshot` event if you need paths, or better yet, use `GenericPersistence`.
### 2. Login/Logout → Connected/Disconnected
The names changed AND the signatures changed. `LoginEventArgs.Mobile` → direct `Mobile` parameter.
### 3. Subscribe in Configure(), Not Initialize()
`Configure()` runs before `Initialize()`. Event subscriptions should happen early.
### 4. Pooled EventArgs
Some events (Movement, AggressiveAction) use pooled args. Don't store references to them — they get recycled.
### 5. Handled/Blocked Properties
`SpeechEventArgs.Handled` and `.Blocked` work the same. Set `Handled = true` to consume, `Blocked = true` to block.
## See Also
- `dev-docs/events.md` — Complete ModernUO event system reference
- `dev-docs/commands-targeting.md` — Complete command/targeting reference
- `08-persistence.md` — Converting WorldSave persistence
- `01-foundation-changes.md` — Foundation changes