Summary
- Adds CLAUDE.md at repo root with 14 terse code audit rules (always loaded, low token cost)
- Adds pointer files for other AI tools: AGENTS.md (Codex), GEMINI.md, .github/COPILOT-INSTRUCTIONS.md (Copilot), .cursorrules (Cursor) — all redirect to CLAUDE.md as single source of truth
- Gitignores /.claude so personal AI config isn't distributed
- Moves Claude skills to dev-docs/claude-skills/ (opt-in, not auto-loaded)
- Adds 14 dev-docs covering codebase conventions
Code Audit Rules (in CLAUDE.md)
1. LINQ tiered rules (Tier 1 free, Tier 2 warm, Tier 3 forbidden)
2. No Console.WriteLine — use LogFactory.GetLogger()
3. No concurrency primitives in game code
4. No World.Mobiles/World.Items iteration
5. Clean up refs in OnDelete()/OnAfterDelete()
6. Cancel timers in OnDelete()/OnAfterDelete()
7. STArrayPool<T>.Shared not ArrayPool<T>.Shared
8. PooledRefList<T> not new List<T>() on hot paths
9. Serialization: partial class, [Constructible], no serialized TimerExecutionToken
10. No Task.Run/new Thread() in game code
11. Never assume era — ask which expansion
12. _camelCase fields, PascalCase properties/methods
13. No empty gumps — use DisplayTo() pattern
14. PropertyList string literals must be {} holes, cliloc-as-argument uses :#
5.5 KiB
5.5 KiB
| name | description |
|---|---|
| modernuo-events | Trigger when subscribing to or creating game events, working with EventSink or generated events. When hooking into player login, death, speech, or other game events. |
ModernUO Events System
When This Activates
- Subscribing to game events (login, logout, death, speech)
- Creating new events
- Working with
EventSinkstatic events - Using
[GeneratedEvent]/[OnEvent]attributes
Key Rules
- Subscribe to events in
Configure()static method - EventSink events are
static event Action<T>-- subscribe with+= - Event handlers must match the delegate signature
- Unsubscribe if your system can be disabled (prevent leaks)
EventSink Events
Subscribe in Configure():
public static void Configure()
{
EventSink.Connected += OnPlayerConnected;
EventSink.Disconnected += OnPlayerDisconnected;
EventSink.Logout += OnLogout;
EventSink.ServerStarted += OnServerStarted;
}
Available Events
Core Lifecycle
EventSink.ServerStarted // Action -- Server fully started
EventSink.Shutdown // Action -- Server shutting down
EventSink.WorldLoad // Action -- World loaded from saves
EventSink.WorldSave // Action -- World save triggered
EventSink.WorldSavePostSnapshot // Action<WorldSavePostSnapshotEventArgs>
Player Connection
EventSink.Connected // Action<Mobile> -- Player connected
EventSink.BeforeDisconnected // Action<Mobile> -- About to disconnect
EventSink.Disconnected // Action<Mobile> -- Player disconnected
EventSink.Logout // Action<Mobile> -- Player logged out
Account
EventSink.AccountLogin // Action<AccountLoginEventArgs>
// AccountLoginEventArgs: .State (NetState), .Username, .Password, .Accepted (set), .RejectReason (set)
Communication
EventSink.Speech // Action<SpeechEventArgs>
// SpeechEventArgs: .Mobile, .Speech, .Type, .Hue, .Keywords, .Handled (set), .Blocked (set)
Combat
EventSink.AggressiveAction // Action<AggressiveActionEventArgs>
// AggressiveActionEventArgs: .Aggressed, .Aggressor, .Criminal
Movement
EventSink.Movement // Action<MovementEventArgs>
// MovementEventArgs: .Mobile, .Direction, .Blocked (set)
Network
EventSink.SocketConnect // Action<SocketConnectEventArgs>
// SocketConnectEventArgs: .Address, .AllowConnection (set)
EventSink.ServerCrashed // Action<ServerCrashedEventArgs>
// ServerCrashedEventArgs: .Exception, .Close (set)
UI
EventSink.PaperdollRequest // Action<Mobile, Mobile> -- (beholder, beheld)
Event Handler Pattern
public static class MyEventSystem
{
public static void Configure()
{
EventSink.Connected += OnConnected;
EventSink.Speech += OnSpeech;
}
private static void OnConnected(Mobile m)
{
if (m is PlayerMobile pm)
{
pm.SendMessage("Welcome back!");
}
}
private static void OnSpeech(SpeechEventArgs e)
{
if (e.Speech.InsensitiveContains("help"))
{
e.Mobile.SendMessage("How can I help you?");
e.Handled = true; // Prevent further processing
}
}
}
Generated Events (Code-Generated)
For custom events on game entities, use the CodeGeneratedEvents package:
Defining Events
// On the class that fires the event:
[GeneratedEvent(nameof(PlayerLoginEvent))]
public static partial void PlayerLoginEvent(PlayerMobile player);
Subscribing to Events
// On the handler class:
[OnEvent(nameof(PlayerMobile.PlayerLoginEvent))]
public static void HandleLogin(PlayerMobile player)
{
// Handle the event
}
Known Generated Events
PlayerMobile.PlayerLoginEventPlayerMobile.PlayerDeathEventBaseCreature.CreatureDeathEvent
External reference: https://github.com/modernuo/CodeGeneratedEvents
EventArgs Pool Pattern
Some EventArgs use object pooling to avoid allocation:
// Movement uses pooling:
var args = MovementEventArgs.Create(mobile, dir);
EventSink.InvokeMovement(args);
args.Free(); // Return to pool
// AggressiveAction uses pooling:
var args = AggressiveActionEventArgs.Create(aggressed, aggressor, criminal);
EventSink.InvokeAggressiveAction(args);
args.Free();
Anti-Patterns
- Subscribing outside
Configure(): Won't be called during startup - Not checking player type:
EventSink.Connectedfires for all mobiles, cast toPlayerMobileif needed - Blocking in event handlers: Event handlers run on the game loop -- keep them fast
- Not unsubscribing: If system can be disabled, unsubscribe to prevent leaks
Real Examples
- EventSink core:
Projects/Server/Events/EventSink.cs - Speech events:
Projects/Server/Events/SpeechEvent.cs - Movement events:
Projects/Server/Events/MovementEvent.cs - Account events:
Projects/Server/Events/AccountLoginEvent.cs - World events:
Projects/Server/Events/EventSink.cs(WorldLoad, WorldSave, ServerStarted, Shutdown) - Connection events:
Projects/Server/Events/SocketConnectionEvent.cs - GumpSystem subscription:
Projects/UOContent/Gumps/Base/GumpSystem.cs
See Also
dev-docs/events.md- Complete events documentationdev-docs/claude-skills/modernuo-content-patterns.md- Content hooksdev-docs/claude-skills/modernuo-configuration.md- Configure() pattern