README covers building, running, connecting, world data format, game mechanics, database schema, mudtool usage, and admin system. AGENTS.md provides architecture overview, concurrency model, coding conventions, and step-by-step guides for extending the codebase. Made-with: Cursor
277 lines
7.4 KiB
Markdown
277 lines
7.4 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
./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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```toml
|
|
name = "The Shattered Realm"
|
|
spawn_room = "town:town_square"
|
|
```
|
|
|
|
### Room
|
|
|
|
```toml
|
|
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
|
|
|
|
```toml
|
|
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
|
|
|
|
```toml
|
|
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
|
|
|
|
```toml
|
|
name = "Dwarf"
|
|
description = "Stout and unyielding."
|
|
|
|
[stats]
|
|
strength = 1
|
|
dexterity = -1
|
|
constitution = 2
|
|
```
|
|
|
|
### Class
|
|
|
|
```toml
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.
|