164 lines
5.6 KiB
Rust
164 lines
5.6 KiB
Rust
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use tokio::sync::Mutex;
|
|
|
|
use flexi_logger::writers::FileLogWriter;
|
|
use flexi_logger::{Cleanup, Criterion, Duplicate, FileSpec, Logger, Naming, WriteMode};
|
|
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() {
|
|
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 mut log_dir = "logs".to_string();
|
|
let mut log_level = "info".to_string();
|
|
|
|
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"));
|
|
}
|
|
"--log-dir" => {
|
|
i += 1;
|
|
log_dir = args.get(i).expect("--log-dir requires a path").to_string();
|
|
}
|
|
"--log-level" => {
|
|
i += 1;
|
|
log_level = args.get(i).expect("--log-level requires a level").to_string();
|
|
}
|
|
"--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})");
|
|
eprintln!(" --log-dir Directory for log files (default: logs)");
|
|
eprintln!(" --log-level Logging level (default: info)");
|
|
std::process::exit(0);
|
|
}
|
|
other => {
|
|
eprintln!("Unknown argument: {other}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
i += 1;
|
|
}
|
|
|
|
// Ensure log directory exists
|
|
std::fs::create_dir_all(&log_dir).unwrap_or_else(|e| {
|
|
eprintln!("Failed to create log directory: {e}");
|
|
std::process::exit(1);
|
|
});
|
|
|
|
// Initialize logger
|
|
let combat_writer = FileLogWriter::builder(FileSpec::default().directory(&log_dir).basename("combat"))
|
|
.rotate(
|
|
Criterion::Size(10_000_000), // 10 MB
|
|
Naming::Numbers,
|
|
Cleanup::KeepLogFiles(7),
|
|
)
|
|
.append()
|
|
.write_mode(WriteMode::Direct)
|
|
.try_build()
|
|
.unwrap();
|
|
|
|
Logger::try_with_str(&log_level)
|
|
.unwrap()
|
|
.log_to_file(FileSpec::default().directory(&log_dir).basename("mudserver"))
|
|
.append()
|
|
.duplicate_to_stderr(Duplicate::All)
|
|
.rotate(
|
|
Criterion::Size(10_000_000), // 10 MB
|
|
Naming::Numbers,
|
|
Cleanup::KeepLogFiles(7),
|
|
)
|
|
.write_mode(WriteMode::Direct)
|
|
.add_writer("combat", Box::new(combat_writer))
|
|
.start()
|
|
.unwrap_or_else(|e| {
|
|
eprintln!("Failed to initialize logger: {e}");
|
|
std::process::exit(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();
|
|
}
|