AI Agent bdd1a85ea2 Add optional target argument to look command
look with no args still shows the room. With an argument, it inspects
NPCs (showing stats/attitude), objects (description), exits (destination),
players, or inventory items. Replaces the need to use examine for quick
inspection while keeping full room view as the default.

Made-with: Cursor
2026-03-14 15:42:35 -06:00

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 server
  • mudtool — 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 values
  • server_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.

Description
No description provided
Readme 397 KiB
Languages
Rust 98.8%
Shell 1.2%