Files
mudserver/src/main.rs
AI Agent dd517d8851
All checks were successful
Smoke tests / Build and smoke test (push) Successful in 1h0m43s
Add JSON-RPC interface and refactor for MCP support
2026-03-16 19:19:21 -06:00

116 lines
3.9 KiB
Rust

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;
use mudserver::db;
use mudserver::game;
use mudserver::ssh;
use mudserver::tick;
use mudserver::world;
const DEFAULT_PORT: u16 = 2222;
const DEFAULT_WORLD_DIR: &str = "./world";
const DEFAULT_DB_PATH: &str = "./mudserver.db";
#[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 jsonrpc_port = 2223;
let mut world_dir = PathBuf::from(DEFAULT_WORLD_DIR);
let mut db_path = PathBuf::from(DEFAULT_DB_PATH);
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");
}
"--rpc-port" => {
i += 1;
jsonrpc_port = args
.get(i)
.and_then(|s| s.parse().ok())
.expect("--rpc-port requires a number");
}
"--world" | "-w" => {
i += 1;
world_dir = PathBuf::from(args.get(i).expect("--world requires a path"));
}
"--db" | "-d" => {
i += 1;
db_path = PathBuf::from(args.get(i).expect("--db requires a path"));
}
"--help" => {
eprintln!("Usage: mudserver [OPTIONS]");
eprintln!(" --port, -p SSH listen port (default: {DEFAULT_PORT})");
eprintln!(" --rpc-port JSON-RPC listen port (default: 2223)");
eprintln!(" --world, -w World directory (default: {DEFAULT_WORLD_DIR})");
eprintln!(" --db, -d Database path (default: {DEFAULT_DB_PATH})");
std::process::exit(0);
}
other => {
eprintln!("Unknown argument: {other}");
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);
});
log::info!("Opening database: {}", db_path.display());
let database = db::SqliteDb::open(&db_path).unwrap_or_else(|e| {
eprintln!("Failed to open database: {e}");
std::process::exit(1);
});
let db: Arc<dyn db::GameDb> = Arc::new(database);
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, db)));
// Spawn tick engine
let tick_state = state.clone();
tokio::spawn(async move {
tick::run_tick_engine(tick_state).await;
});
// Spawn JSON-RPC server
let rpc_state = state.clone();
tokio::spawn(async move {
mudserver::jsonrpc::run_jsonrpc_server(rpc_state, jsonrpc_port).await;
});
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();
}