Initial commit: SSH MUD server with data-driven world

Rust-based MUD server accepting SSH connections on port 2222.
Players connect with any SSH client, get dropped into a
data-driven world loaded from TOML files at startup.

Binary systems: SSH handling (russh), command parser, game state,
multiplayer broadcast, ANSI terminal rendering.

Data layer: world/ directory with regions, rooms, NPCs, and objects
defined as individual TOML files — no recompile needed to modify.

Commands: look, movement (n/s/e/w/u/d), say, who, help, quit.
Made-with: Cursor
This commit is contained in:
AI Agent
2026-03-14 13:24:34 -06:00
commit c82f57a720
23 changed files with 3477 additions and 0 deletions

70
src/game.rs Normal file
View File

@@ -0,0 +1,70 @@
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use russh::server::Handle;
use russh::ChannelId;
use crate::world::World;
pub struct Player {
pub name: String,
pub room_id: String,
}
pub struct PlayerConnection {
pub player: Player,
pub channel: ChannelId,
pub handle: Handle,
}
pub struct GameState {
pub world: World,
pub players: HashMap<usize, PlayerConnection>,
}
pub type SharedState = Arc<Mutex<GameState>>;
impl GameState {
pub fn new(world: World) -> Self {
GameState {
world,
players: HashMap::new(),
}
}
pub fn spawn_room(&self) -> &str {
&self.world.spawn_room
}
pub fn add_player(&mut self, id: usize, name: String, channel: ChannelId, handle: Handle) {
let room_id = self.world.spawn_room.clone();
self.players.insert(
id,
PlayerConnection {
player: Player { name, room_id },
channel,
handle,
},
);
}
pub fn remove_player(&mut self, id: usize) -> Option<PlayerConnection> {
self.players.remove(&id)
}
pub fn players_in_room(&self, room_id: &str, exclude_id: usize) -> Vec<&PlayerConnection> {
self.players
.iter()
.filter(|(&id, conn)| conn.player.room_id == room_id && id != exclude_id)
.map(|(_, conn)| conn)
.collect()
}
pub fn all_player_names(&self) -> Vec<&str> {
self.players
.values()
.map(|c| c.player.name.as_str())
.collect()
}
}