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:
83
src/main.rs
Normal file
83
src/main.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
mod ansi;
|
||||
mod commands;
|
||||
mod game;
|
||||
mod ssh;
|
||||
mod world;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use russh::keys::ssh_key::rand_core::OsRng;
|
||||
use russh::server::Server as _;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
const DEFAULT_PORT: u16 = 2222;
|
||||
const DEFAULT_WORLD_DIR: &str = "./world";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let mut port = DEFAULT_PORT;
|
||||
let mut world_dir = PathBuf::from(DEFAULT_WORLD_DIR);
|
||||
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let mut i = 1;
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--port" | "-p" => {
|
||||
i += 1;
|
||||
port = args
|
||||
.get(i)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.expect("--port requires a number");
|
||||
}
|
||||
"--world" | "-w" => {
|
||||
i += 1;
|
||||
world_dir = PathBuf::from(
|
||||
args.get(i).expect("--world requires a path"),
|
||||
);
|
||||
}
|
||||
"--help" => {
|
||||
eprintln!("Usage: mudserver [--port PORT] [--world PATH]");
|
||||
eprintln!(" --port, -p Listen port (default: {DEFAULT_PORT})");
|
||||
eprintln!(" --world, -w World directory (default: {DEFAULT_WORLD_DIR})");
|
||||
std::process::exit(0);
|
||||
}
|
||||
other => {
|
||||
eprintln!("Unknown argument: {other}");
|
||||
eprintln!("Run with --help for usage.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
log::info!("Loading world from: {}", world_dir.display());
|
||||
let loaded_world = world::World::load(&world_dir).unwrap_or_else(|e| {
|
||||
eprintln!("Failed to load world: {e}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let key =
|
||||
russh::keys::PrivateKey::random(&mut OsRng, russh::keys::Algorithm::Ed25519).unwrap();
|
||||
|
||||
let config = russh::server::Config {
|
||||
inactivity_timeout: Some(std::time::Duration::from_secs(3600)),
|
||||
auth_rejection_time: std::time::Duration::from_secs(1),
|
||||
auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)),
|
||||
keys: vec![key],
|
||||
..Default::default()
|
||||
};
|
||||
let config = Arc::new(config);
|
||||
|
||||
let state = Arc::new(Mutex::new(game::GameState::new(loaded_world)));
|
||||
let mut server = ssh::MudServer::new(state);
|
||||
|
||||
let listener = TcpListener::bind(("0.0.0.0", port)).await.unwrap();
|
||||
log::info!("MUD server listening on 0.0.0.0:{port}");
|
||||
log::info!("Connect with: ssh <username>@localhost -p {port}");
|
||||
|
||||
server.run_on_socket(config, &listener).await.unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user