- Expand race TOML schema: 7 stats, body shape (size/weight/custom slots), natural armor and attacks with damage types, resistances, traits/disadvantages, regen multipliers, vision types, XP rate, guild compatibility - Replace equipped_weapon/equipped_armor with slot-based HashMap<String, Object> - Each race defines available equipment slots; default humanoid slots as fallback - Combat uses natural weapons/armor from race when no gear equipped - DB migration from old weapon/armor columns to equipped_json - Add Dragon race: huge body, custom slots (forelegs/wings/tail), fire breath, natural armor 8, fire immune, slow XP rate for balance - Update all existing races with expanded fields (traits, resistances, vision, regen) - Objects gain optional slot field; kind=weapon/armor still works as fallback - Update chargen to display race traits, size, natural attacks, vision - Update stats display to show equipment and natural bonuses separately - Update TESTING.md and AGENTS.md with race/slot system documentation Made-with: Cursor
MUD Server
A text-based multiplayer RPG (MUD) that accepts connections over SSH. Written in Rust with a data-driven world definition system — rooms, NPCs, objects, races, and classes are all defined in TOML files and can be changed without recompiling.
Requirements
- Rust toolchain (edition 2021+)
- SQLite is bundled via
rusqlite— no system SQLite needed
Building
cargo build # builds both mudserver and mudtool
cargo build --release # optimized build
This produces two binaries:
mudserver— the game servermudtool— database management CLI/TUI
Running the Server
./target/debug/mudserver
Options
| Flag | Default | Description |
|---|---|---|
--port, -p |
2222 |
SSH listen port |
--world, -w |
./world |
Path to world data directory |
--db, -d |
./mudserver.db |
Path to SQLite database file |
The server generates a random SSH host key on each startup. The database is created automatically if it doesn't exist.
Connecting
Any SSH client works. The username becomes the player's character name:
ssh mycharacter@localhost -p 2222
Password and key auth are both accepted (no real authentication — this is a game server, not a secure shell).
Environment
Set RUST_LOG to control log verbosity:
RUST_LOG=info ./target/release/mudserver # default
RUST_LOG=debug ./target/release/mudserver # verbose
World Data
The world is defined entirely in TOML files under a world/ directory. The server reads this at startup — no recompilation needed to change content.
Directory Structure
world/
├── manifest.toml # world name and spawn room
├── races/ # playable races
│ ├── dwarf.toml
│ ├── elf.toml
│ └── ...
├── classes/ # playable classes
│ ├── warrior.toml
│ ├── mage.toml
│ └── ...
└── <region>/ # one directory per region
├── region.toml # region metadata
├── rooms/
│ ├── town_square.toml
│ └── ...
├── npcs/
│ ├── barkeep.toml
│ └── ...
└── objects/
├── rusty_sword.toml
└── ...
manifest.toml
name = "The Shattered Realm"
spawn_room = "town:town_square"
Room
name = "Town Square"
description = "A cobblestone square with a fountain."
[exits]
north = "town:tavern"
south = "town:gate"
east = "town:market"
Room IDs are <region>:<filename_stem>.
NPC
name = "Town Guard"
description = "A bored guard."
room = "town:gate"
base_attitude = "neutral" # friendly, neutral, wary, aggressive, hostile
faction = "guards" # optional — attitude shifts propagate to faction
respawn_secs = 90 # optional — respawn timer after death
[dialogue]
greeting = "Move along."
[combat] # optional — omit for weak default stats (20hp/4atk/2def/5xp)
max_hp = 60
attack = 10
defense = 8
xp_reward = 25
Object
name = "Rusty Sword"
description = "A battered iron blade."
room = "town:cellar"
kind = "weapon" # weapon, armor, consumable, treasure, or omit
takeable = true
[stats]
damage = 5 # for weapons
# armor = 4 # for armor
# heal_amount = 30 # for consumables
Race
name = "Dwarf"
description = "Stout and unyielding."
[stats]
strength = 1
dexterity = -1
constitution = 2
Class
name = "Warrior"
description = "Masters of arms and armor."
[base_stats]
max_hp = 120
attack = 14
defense = 12
[growth]
hp_per_level = 15
attack_per_level = 3
defense_per_level = 2
Game Mechanics
Tick System
The game runs on a 3-second tick cycle. Combat actions, status effects, NPC AI, and passive regeneration all resolve on ticks rather than immediately.
Combat
Combat is tick-based. When a player enters combat (via attack or NPC aggro), they choose actions each tick:
| Command | Effect |
|---|---|
attack / a |
Strike the enemy (default if no action queued) |
defend / def |
Brace — doubles effective defense for the tick |
flee |
Attempt to escape (success chance based on DEF stat) |
use <item> |
Use a consumable during combat |
Any NPC can be attacked. Attacking non-hostile NPCs carries attitude penalties (-30 individual, -15 faction) and a warning message but is not blocked.
Attitude System
Every NPC tracks a per-player attitude value from -100 to +100:
| Range | Label | Behavior |
|---|---|---|
| 50 to 100 | Friendly | Will talk |
| 10 to 49 | Neutral | Will talk |
| -24 to 9 | Wary | Will talk |
| -25 to -74 | Aggressive | Won't talk, attackable |
| -75 to -100 | Hostile | Attacks on sight |
Attitudes shift from combat interactions and propagate through NPC factions.
Status Effects
Effects like poison and regeneration are stored in the database and tick down every cycle — including while the player is offline. Effects are cleared on death.
Passive Regeneration
Players out of combat regenerate 5% of max HP every 5 ticks (~15 seconds).
Database
SQLite with WAL mode. Tables:
players— character data (stats, inventory, equipment, room, admin flag)npc_attitudes— per-player, per-NPC attitude valuesserver_settings— key-value config (e.g.registration_open)status_effects— active effects with remaining tick counters
The database is accessed through a GameDb trait, making backend swaps possible.
mudtool
Database management tool with both CLI and TUI modes.
CLI
mudtool --db ./mudserver.db players list
mudtool --db ./mudserver.db players show hero
mudtool --db ./mudserver.db players set-admin hero true
mudtool --db ./mudserver.db players delete hero
mudtool --db ./mudserver.db settings list
mudtool --db ./mudserver.db settings set registration_open false
mudtool --db ./mudserver.db attitudes list hero
mudtool --db ./mudserver.db attitudes set hero town:guard 50
TUI
mudtool --db ./mudserver.db tui
Interactive interface with tabs for Players, Settings, and Attitudes. Navigate with arrow keys, Tab/1/2/3 to switch tabs, a to toggle admin, d to delete, Enter to edit values, q to quit.
Admin System
Players with the is_admin flag can use in-game admin commands:
admin promote <player> Grant admin
admin demote <player> Revoke admin
admin kick <player> Disconnect player
admin teleport <room_id> Warp to room
admin registration on|off Toggle new player creation
admin announce <message> Broadcast to all
admin heal [player] Full heal (self or target)
admin info <player> Detailed player info
admin setattitude <player> <npc> <value> Set attitude
admin list All players (online + saved)
The first admin must be set via mudtool players set-admin <name> true.
Registration Gate
New player creation can be toggled:
mudtool settings set registration_open false # block new players
mudtool settings set registration_open true # allow new players (default)
Or in-game: admin registration off / admin registration on. Existing players can always log in regardless of this setting.