ModernUO/dev-docs/claude-skills/modernuo-era-expansion.md
Kamron Batman 1391c563fe
chore: Adds AI instructions and SKILLs for ModernUO codebase (#2347)
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 :#
2026-03-01 11:42:19 -08:00

3.8 KiB

name description
modernuo-era-expansion Trigger when writing era-conditional code, using Core.AOS/SE/ML etc., or when user hasn't specified target era. Always ask which expansion to target if not specified.

ModernUO Era & Expansion Support

When This Activates

  • Writing code that depends on game era (damage formulas, skill caps, mechanics)
  • Using Core.AOS, Core.SE, Core.ML, etc.
  • User asks for a feature without specifying era
  • Implementing mechanics that changed across expansions

CRITICAL RULE

Never assume era. If the user hasn't specified which expansion to target, ASK before writing era-dependent code.

Expansion Enum

public enum Expansion
{
    None,    // 0 - Pre-T2A
    T2A,     // 1 - The Second Age
    UOR,     // 2 - Renaissance
    UOTD,    // 3 - Third Dawn
    LBR,     // 4 - Blackthorn's Revenge
    AOS,     // 5 - Age of Shadows (major overhaul)
    SE,      // 6 - Samurai Empire
    ML,      // 7 - Mondain's Legacy
    SA,      // 8 - Stygian Abyss
    HS,      // 9 - High Seas
    TOL,     // 10 - Time of Legends
    EJ       // 11 - Endless Journey
}

Era Check Properties

Each returns true when Core.Expansion >= that era:

Core.T2A   // >= The Second Age
Core.UOR   // >= Renaissance
Core.UOTD  // >= Third Dawn
Core.LBR   // >= Blackthorn's Revenge
Core.AOS   // >= Age of Shadows
Core.SE    // >= Samurai Empire
Core.ML    // >= Mondain's Legacy
Core.SA    // >= Stygian Abyss
Core.HS    // >= High Seas
Core.TOL   // >= Time of Legends
Core.EJ    // >= Endless Journey

For exact expansion: Core.Expansion == Expansion.AOS

Key Era Boundaries

AOS (Age of Shadows) -- Most Significant Change

  • Complete combat overhaul: resistance-based damage system
  • Property-based item system (magic properties)
  • New damage formula: GetNewAosDamage() vs flat Utility.Random()
  • Luck system for loot
  • Insurance system

SE (Samurai Empire)

  • Bushido / Ninjitsu skills
  • Samurai/Ninja classes
  • Loot pack adjustments

ML (Mondain's Legacy)

  • Spellweaving
  • Adjusted skill gain chances
  • Container weight display changes

Pattern: Era-Conditional Code

// Ternary for simple values
var delay = Core.SE ? 250 : Core.AOS ? 500 : 1000;

// If/else for logic branches
if (Core.AOS)
{
    damage = GetNewAosDamage(10, 1, 4, target);
}
else
{
    damage = Utility.Random(4, 4);
    if (CheckResisted(target))
        damage *= 0.75;
}

// Property display varies by era
if (Core.ML)
{
    list.Add(1072241, $"{TotalItems}\t{MaxItems}\t{TotalWeight}\t{MaxWeight}");
}
else
{
    list.Add(1050044, $"{TotalItems}\t{TotalWeight}");
}

Pattern: Era-Dependent Loot

LootPack properties auto-select based on expansion:

// These automatically pick the right era variant
LootPack.Poor       // OldPoor / AosPoor / SePoor
LootPack.Average    // OldAverage / AosAverage / SeAverage
LootPack.Rich       // OldRich / AosRich / SeRich
LootPack.FilthyRich // OldFilthyRich / AosFilthyRich / SeFilthyRich
LootPack.UltraRich  // OldUltraRich / AosUltraRich / SeUltraRich

Anti-Patterns

  • Hardcoding mechanics for one era without conditional checks
  • Assuming AOS when the user might want pre-AOS
  • Using era-specific APIs without checking (e.g., GetNewAosDamage() pre-AOS)

Real Examples

  • Era-conditional damage: Projects/UOContent/Spells/First/MagicArrow.cs
  • Era-conditional properties: Projects/Server/Items/Container.cs (GetProperties)
  • Era-conditional values: Projects/UOContent/Items/Weapons/Ranged/BaseRanged.cs
  • Expansion enum: Projects/Server/ExpansionInfo.cs
  • Stat config by era: Projects/UOContent/Skills/SkillCheck.cs

See Also

  • dev-docs/era-expansion.md - Complete expansion documentation
  • dev-docs/claude-skills/modernuo-content-patterns.md - Content templates
  • dev-docs/claude-skills/modernuo-configuration.md - Configuration system