Items & Mobiles
This guide covers the most common content creation tasks: building items and creatures for your shard.
Creating an Item
Minimal Item
Every item needs [SerializationGenerator], a partial class, and a [Constructible] constructor:
using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0)]
public partial class SimpleItem : Item
{
[Constructible]
public SimpleItem() : base(0x1234)
{
Weight = 1.0;
}
public override string DefaultName => "a simple item";
}
0x1234is the item graphic ID from UO art files.DefaultNamesets the tooltip name. UseLabelNumberfor cliloc-based names instead.
Full Item Example
A complete item with serialized fields, a timer, property list, and double-click behavior:
using ModernUO.Serialization;
namespace Server.Items;
[SerializationGenerator(0)]
public partial class MagicLantern : Item
{
[SerializableField(0)]
[InvalidateProperties]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private int _charges;
[SerializableField(1)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private Mobile _owner;
private TimerExecutionToken _glowTimer;
[Constructible]
public MagicLantern() : base(0xA25)
{
_charges = Utility.RandomMinMax(5, 15);
Weight = 2.0;
Light = LightType.Circle300;
StartGlow();
}
public override string DefaultName => "a magic lantern";
private void StartGlow()
{
Timer.StartTimer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3), Glow, out _glowTimer);
}
[AfterDeserialization]
private void AfterDeserialization() => StartGlow();
public override void OnAfterDelete()
{
_glowTimer.Cancel();
base.OnAfterDelete();
}
private void Glow()
{
if (_charges > 0)
{
Effects.SendLocationParticles(this, 0x376A, 9, 10, 5042);
}
}
public override void OnDoubleClick(Mobile from)
{
if (!IsChildOf(from.Backpack))
{
from.SendLocalizedMessage(1042001); // Must be in your backpack
return;
}
if (_charges <= 0)
{
from.SendMessage("The lantern is depleted.");
return;
}
Charges--;
from.SendMessage("The lantern flares brightly!");
from.FixedParticles(0x376A, 9, 32, 5042, EffectLayer.Waist);
}
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
list.Add(1060741, $"{_charges}"); // "charges: ~1_val~"
}
}
Key patterns:
TimerExecutionTokenis never serialized -- restart it in[AfterDeserialization].[InvalidateProperties]auto-refreshes the tooltip whenChargeschanges.[SerializedCommandProperty]exposes the field to the[Propsgump for GMs.OnAfterDeletecancels the timer to prevent it firing on a deleted entity.
Common Base Classes
| Base Class | Use For |
|---|---|
Item | Generic items |
BaseWeapon | Melee weapons |
BaseRanged | Ranged weapons (bows, crossbows) |
BaseArmor | Armor pieces |
BaseShield | Shields |
BaseClothing | Wearable clothing |
BaseJewel | Rings, bracelets, necklaces |
BaseContainer | Containers (bags, boxes, chests) |
BasePotion | Potions |
Food | Edible items |
SpellScroll | Spell scrolls |
Key Item Properties
Set these in the constructor:
Weight = 1.0; // Weight in stones
Stackable = true; // Can stack with same type
Amount = 1; // Stack amount
Movable = true; // Can be picked up
Hue = 0; // Color (0 = default)
LootType = LootType.Regular; // Regular, Newbied, Blessed, Cursed
Layer = Layer.OneHanded; // Equipment layer
Light = LightType.Circle300; // Light emission
Creating a Creature
Basic Creature
Creatures extend BaseCreature and define stats, resistances, skills, and loot:
using ModernUO.Serialization;
using Server.Items;
namespace Server.Mobiles;
[SerializationGenerator(0)]
public partial class ForestWolf : BaseCreature
{
[Constructible]
public ForestWolf() : base(AIType.AI_Melee, FightMode.Closest)
{
Body = 225;
BaseSoundID = 0xE5;
SetStr(80, 120);
SetDex(90, 110);
SetInt(20, 40);
SetHits(60, 80);
SetMana(0);
SetDamage(8, 14);
SetDamageType(ResistanceType.Physical, 100);
SetResistance(ResistanceType.Physical, 25, 35);
SetResistance(ResistanceType.Fire, 5, 10);
SetResistance(ResistanceType.Cold, 15, 25);
SetResistance(ResistanceType.Poison, 10, 15);
SetResistance(ResistanceType.Energy, 5, 10);
SetSkill(SkillName.MagicResist, 30.0, 50.0);
SetSkill(SkillName.Tactics, 50.0, 70.0);
SetSkill(SkillName.Wrestling, 50.0, 70.0);
Fame = 600;
Karma = 0;
VirtualArmor = 28;
Tamable = true;
ControlSlots = 1;
MinTameSkill = 50.1;
}
public override string CorpseName => "a wolf corpse";
public override string DefaultName => "a forest wolf";
public override int Meat => 1;
public override int Hides => 6;
public override HideType HideType => HideType.Regular;
public override FoodType FavoriteFood => FoodType.Meat;
public override PackInstinct PackInstinct => PackInstinct.Canine;
public override void GenerateLoot()
{
AddLoot(LootPack.Meager);
}
}
Optional Creature Overrides
public override Poison PoisonImmune => Poison.Regular;
public override Poison HitPoison => Poison.Lesser;
public override double HitPoisonChance => 0.2;
public override bool CanRummageCorpses => true;
public override bool BardImmune => true;
public override bool Unprovokable => true;
public override bool CanFly => true;
public override int TreasureMapLevel => 3;
public override double WeaponAbilityChance => 0.4;
AI Types
| AIType | Use For |
|---|---|
AI_Melee | Warriors, melee fighters |
AI_Mage | Spellcasters |
AI_Archer | Ranged attackers |
AI_Animal | Passive animals (flee when hurt) |
AI_Predator | Hunting animals |
AI_Healer | Healing NPCs |
AI_Vendor | Shop NPCs |
Fight Modes
| FightMode | Behavior |
|---|---|
None | Never attacks |
Aggressor | Only retaliates when attacked |
Strongest | Targets highest-stat enemy |
Weakest | Targets lowest-stat enemy |
Closest | Targets nearest enemy |
Evil | Attacks aggressors or evil-karma targets |
Creature Stats Guide
Use these ranges as a baseline when creating creatures:
| Level | Str | Dex | Int | Hits | Damage | Fame |
|---|---|---|---|---|---|---|
| Weak | 30--60 | 30--50 | 10--20 | 20--40 | 2--6 | 100--300 |
| Average | 80--120 | 60--90 | 20--40 | 60--100 | 6--14 | 500--1,500 |
| Strong | 150--250 | 80--120 | 50--100 | 120--200 | 12--22 | 2,000--5,000 |
| Elite | 300--500 | 100--150 | 100--200 | 250--500 | 18--30 | 5,000--15,000 |
| Boss | 500--1,000 | 150--250 | 200--400 | 500--2,000 | 25--40 | 15,000+ |
Loot System
Predefined Loot Packs
Use AddLoot in GenerateLoot() to assign standard loot tiers:
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); // ~1,000 gold equivalent
AddLoot(LootPack.UltraRich); // ~2,000 gold equivalent
AddLoot(LootPack.SuperBoss); // Boss-level loot
// Auxiliary packs
AddLoot(LootPack.Gems, 2); // 2 random gems
AddLoot(LootPack.Potions); // Random potion
AddLoot(LootPack.LowScrolls); // Circle 1--4 scroll
AddLoot(LootPack.MedScrolls); // Circle 5--6 scroll
AddLoot(LootPack.HighScrolls); // Circle 7--8 scroll
}
Packs automatically select era-appropriate loot based on the server expansion.
Specific Items
For items that always drop, add them directly:
PackItem(new Arrow(Utility.RandomMinMax(20, 40)));
PackGold(100, 200);
PackItem(new Bandage(Utility.RandomMinMax(5, 10)));
Property Lists (Tooltips)
Override GetProperties to customize what players see when hovering over your item:
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list); // Always call base first
// Cliloc with a value argument
list.Add(1060741, $"{_charges}"); // "charges: ~1_val~"
// Key-value pair (string constants must be holes)
list.Add(1060658, $"{"Quality"}\t{_quality}"); // "~1_val~: ~2_val~"
// Raw string line
list.Add($"{"Crafted with care"}");
}
String literals in interpolated property list arguments must be wrapped as holes: $"{"Map"}\t{value}" not $"Map\t{value}". The handler treats bare text as delimiters and {} holes as arguments. Only \t should be a bare literal.
Use [InvalidateProperties] on serialized fields to auto-refresh tooltips when values change.
Entity Lifecycle
Entities go through a two-phase deletion process:
// Phase 1: Pre-removal -- cancel timers, unregister from systems
public override void OnDelete()
{
_timerToken.Cancel();
base.OnDelete();
}
// Phase 2: Post-removal -- null out references
public override void OnAfterDelete()
{
_timer?.Stop();
_timer = null;
_owner = null;
base.OnAfterDelete();
}
| Phase | Method | What to Do |
|---|---|---|
| Pre-removal | OnDelete() | Cancel TimerExecutionToken, unregister from tracking systems |
| Post-removal | OnAfterDelete() | Stop and null Timer references, null Item/Mobile references |
File Organization
Place new content files under Projects/UOContent/ following this structure:
Projects/UOContent/
Items/
Weapons/Swords/ # Swords
Weapons/Maces/ # Maces
Weapons/Ranged/ # Bows, crossbows
Armor/Plate/ # Plate armor
Armor/Leather/ # Leather armor
Clothing/ # Wearable clothing
Containers/ # Bags, boxes, chests
Misc/ # General items
Special/ # Unique or quest items
Resources/ # Crafting materials
Mobiles/
Animals/Bears/ # Bears
Animals/Birds/ # Birds
Monsters/AOS/ # AOS-era monsters
Monsters/SE/ # SE-era monsters
Monsters/ML/ # ML-era monsters
Special/ # Champions, bosses
Vendors/ # NPC vendors
Townfolk/ # NPCs
Naming rules:
- File name matches the primary class name.
- One primary class per file.
- Group related items in subdirectories.
- Era-specific content goes in era-named subdirectories.