ModernUO/dev-docs/claude-skills/modernuo-content-patterns.md

370 lines
10 KiB
Markdown

---
name: modernuo-content-patterns
description: >
Trigger when creating new items, mobiles, creatures, spells, skills, loot tables, or any game content under Projects/UOContent/. This is the hub skill that connects to all other ModernUO skills.
---
# ModernUO Content Patterns (Hub Skill)
## When This Activates
- Creating new items, weapons, armor, clothing, containers
- Creating new creatures, NPCs, vendors
- Creating new spells
- Implementing skill handlers
- Adding loot tables
- Adding context menus
- Any new game content under `Projects/UOContent/`
## Key Rules
1. **Always ask target era** if the user hasn't specified (see `modernuo-era-expansion.md`)
2. **All serializable classes must be `partial`** with `[SerializationGenerator]`
3. **All Item/Mobile constructors need `[Constructible]`**
4. **Clean up timers and references in `OnDelete()`/`OnAfterDelete()`**
5. **No LINQ** in game logic -- use loops and `PooledRefList<T>`
6. **File placement** matters -- follow the directory conventions below
## New Item Template
```csharp
using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0)]
public partial class MyItem : Item
{
[Constructible]
public MyItem() : base(0x1234) // itemID from art
{
Weight = 1.0;
// Stackable = true; // if stackable
// Amount = 1; // if stackable
}
public override string DefaultName => "a my item";
// OR: public override int LabelNumber => 1234567; // cliloc number
public override void OnDoubleClick(Mobile from)
{
if (!IsChildOf(from.Backpack))
{
from.SendLocalizedMessage(1042001); // Must be in backpack
return;
}
// Item use logic
}
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
// list.Add(1060741, $"{_charges}"); // charges: ~1_val~
}
}
```
## New Creature Template
```csharp
using ModernUO.Serialization;
using Server.Items;
namespace Server.Mobiles;
[SerializationGenerator(0)]
public partial class MyCreature : BaseCreature
{
[Constructible]
public MyCreature() : base(AIType.AI_Melee, FightMode.Closest)
{
Body = 0; // Body graphic ID
BaseSoundID = 0; // Base sound ID
SetStr(100, 150); // Strength min/max
SetDex(80, 100); // Dexterity min/max
SetInt(30, 50); // Intelligence min/max
SetHits(80, 120);
SetMana(0);
SetDamage(8, 14);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 30, 40);
SetResistance(ResistanceType.Fire, 10, 20);
SetResistance(ResistanceType.Cold, 10, 20);
SetResistance(ResistanceType.Poison, 15, 25);
SetResistance(ResistanceType.Energy, 10, 20);
SetSkill(SkillName.MagicResist, 30.0, 50.0);
SetSkill(SkillName.Tactics, 50.0, 70.0);
SetSkill(SkillName.Wrestling, 50.0, 70.0);
Fame = 1000;
Karma = -1000; // Negative = evil, positive = good, 0 = neutral
VirtualArmor = 30;
}
public override string CorpseName => "a creature corpse";
public override string DefaultName => "a creature";
// Optional overrides:
// public override int Meat => 1;
// public override int Hides => 8;
// public override HideType HideType => HideType.Regular;
// public override FoodType FavoriteFood => FoodType.Meat;
// public override PackInstinct PackInstinct => PackInstinct.Canine;
// public override bool CanRummageCorpses => true;
// public override Poison PoisonImmune => Poison.Lesser;
// public override Poison HitPoison => Poison.Regular;
public override void GenerateLoot()
{
AddLoot(LootPack.Average);
AddLoot(LootPack.Gems, 1);
// PackItem(new SpecificItem());
// PackGold(50, 100);
}
}
```
### Tameable Creature Additions
```csharp
// In constructor:
Tamable = true;
ControlSlots = 1; // 1-5, how many pet slots it uses
MinTameSkill = 35.1; // Required Animal Taming skill
```
### AI Types
| AIType | Behavior |
|---|---|
| `AI_Melee` | Charges into melee combat |
| `AI_Mage` | Casts spells, keeps distance |
| `AI_Archer` | Uses ranged attacks |
| `AI_Animal` | Passive, flees or fights back |
| `AI_Predator` | Hunts other creatures |
| `AI_Healer` | Heals allies |
| `AI_Vendor` | NPC vendor behavior |
| `AI_Berserk` | Aggressive, attacks everything |
| `AI_Thief` | Steals from players |
### Fight Modes
| FightMode | Target Selection |
|---|---|
| `None` | Never attacks |
| `Aggressor` | Only attacks those who attack first |
| `Strongest` | Targets highest stats |
| `Weakest` | Targets lowest stats |
| `Closest` | Targets nearest entity |
| `Evil` | Attacks aggressors or negative-karma entities |
## New Spell Template
```csharp
using System;
using Server.Targeting;
namespace Server.Spells.First;
public class MySpell : MagerySpell, ITargetingSpell<Mobile>
{
private static readonly SpellInfo _info = new(
"Spell Name", // Display name
"In Vas Ort", // Power words (mantra)
212, // Cast animation action
9041, // Cast sound
Reagent.Bloodmoss, // Required reagents
Reagent.MandrakeRoot
);
public MySpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
{
}
public override SpellCircle Circle => SpellCircle.First;
public void Target(Mobile m)
{
if (CheckHSequence(m)) // Harmful spell check
{
SpellHelper.Turn(Caster, m);
double damage = GetNewAosDamage(10, 1, 4, m);
SpellHelper.Damage(this, m, damage, 0, 100, 0, 0, 0);
// Damage types: phys, fire, cold, poison, energy (must sum to 100)
}
}
public override void OnCast()
{
Caster.Target = new SpellTarget<Mobile>(this, TargetFlags.Harmful);
}
}
```
### Spell Circles (Magery)
| Circle | Mana Cost | Min Skill |
|---|---|---|
| First | 4 | -50.0 (Pre-ML) / -46.0 (ML+) |
| Second | 6 | -30.0 / -32.0 |
| Third | 9 | 0.0 / -18.0 |
| Fourth | 11 | 10.0 / -4.0 |
| Fifth | 14 | 20.0 / 10.0 |
| Sixth | 20 | 30.0 / 24.0 |
| Seventh | 40 | 40.0 / 38.0 |
| Eighth | 50 | 50.0 / 52.0 |
## Skill Implementation
```csharp
using Server.Targeting;
namespace Server.Skills;
public static class MySkillHandler
{
public static void Initialize()
{
// Register handler delegate
SkillInfo.Table[(int)SkillName.Alchemy].Callback = OnUse;
}
public static TimeSpan OnUse(Mobile from)
{
from.SendMessage("You begin working...");
from.Target = new InternalTarget();
return TimeSpan.FromSeconds(1.0); // Delay before next use
}
private class InternalTarget : Target
{
public InternalTarget() : base(2, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Item item)
{
// from.CheckSkill(SkillName.Alchemy, minSkill, maxSkill)
if (from.CheckSkill(SkillName.Alchemy, 0.0, 100.0))
{
from.SendMessage("Success!");
}
else
{
from.SendMessage("You fail.");
}
}
}
}
}
```
## Loot Packs
Use predefined packs -- they auto-select era-appropriate loot:
```csharp
public override void GenerateLoot()
{
AddLoot(LootPack.Poor); // ~50 gold equivalent
AddLoot(LootPack.Meager); // ~100 gold equivalent
AddLoot(LootPack.Average); // ~250 gold equivalent
AddLoot(LootPack.Rich); // ~500 gold equivalent
AddLoot(LootPack.FilthyRich); // ~1000 gold equivalent
AddLoot(LootPack.UltraRich); // ~2000 gold equivalent
AddLoot(LootPack.SuperBoss); // Boss-level loot
AddLoot(LootPack.Gems, 2); // 2 random gems
AddLoot(LootPack.Potions); // Random potion
// Specific items
PackItem(new Arrow(Utility.RandomMinMax(20, 40)));
PackGold(100, 200);
}
```
## Context Menus
```csharp
public override void GetContextMenuEntries(Mobile from, ref PooledRefList<ContextMenuEntry> list)
{
base.GetContextMenuEntries(from, ref list);
if (from.Alive && from.InRange(this, 2))
{
list.Add(new MyContextMenuEntry(this));
}
}
private class MyContextMenuEntry : ContextMenuEntry
{
private readonly Item _item;
public MyContextMenuEntry(Item item) : base(6100) // Cliloc number
{
_item = item;
}
public override void OnClick(Mobile from, IEntity target)
{
// Handle click
}
}
```
## Two-Phase Deletion
```csharp
public override void OnDelete()
{
_timerToken.Cancel(); // Cancel timers FIRST
base.OnDelete();
}
public override void OnAfterDelete()
{
_timer?.Stop(); // Stop Timer references
_timer = null;
_owner = null; // Clear Mobile/Item references
base.OnAfterDelete();
}
```
## File Placement
| Content Type | Directory |
|---|---|
| Items | `Projects/UOContent/Items/{Category}/` |
| Weapons | `Projects/UOContent/Items/Weapons/{Type}/` |
| Armor | `Projects/UOContent/Items/Armor/{Type}/` |
| Creatures | `Projects/UOContent/Mobiles/{Type}/` |
| Animals | `Projects/UOContent/Mobiles/Animals/{Species}/` |
| Monsters | `Projects/UOContent/Mobiles/Monsters/{Era}/` |
| Spells | `Projects/UOContent/Spells/{School}/` |
| Skills | `Projects/UOContent/Skills/` |
| Engines | `Projects/UOContent/Engines/{SystemName}/` |
| Gumps | `Projects/UOContent/Gumps/` |
## Real Examples
- Simple creature: `Projects/UOContent/Mobiles/Animals/Bears/BlackBear.cs`
- Boss creature: `Projects/UOContent/Mobiles/Special/Barracoon.cs`
- SE creature: `Projects/UOContent/Mobiles/Monsters/SE/RaiJu.cs`
- Spell: `Projects/UOContent/Spells/First/MagicArrow.cs`
- Skill check: `Projects/UOContent/Skills/SkillCheck.cs`
- Loot packs: `Projects/UOContent/Misc/LootPack.cs`
## See Also
- `dev-docs/claude-skills/modernuo-serialization.md` - Serialization details
- `dev-docs/claude-skills/modernuo-era-expansion.md` - Era-conditional code
- `dev-docs/claude-skills/modernuo-timers.md` - Timer patterns
- `dev-docs/claude-skills/modernuo-property-lists.md` - Item tooltips
- `dev-docs/claude-skills/modernuo-gump-system.md` - UI dialogs
- `dev-docs/claude-skills/modernuo-commands-targeting.md` - Commands and targeting
- `dev-docs/claude-skills/modernuo-events.md` - Event system
- `dev-docs/content-patterns.md` - Full content documentation