diff --git a/.gitignore b/.gitignore index ea8c4bf..a082e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +server-config.json diff --git a/Cargo.lock b/Cargo.lock index 29c0d30..6380124 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,8 @@ dependencies = [ "half", "parking_lot", "rand", + "serde", + "serde_json", "tokio", ] @@ -131,6 +133,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "libc" version = "0.2.153" @@ -290,12 +298,49 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" diff --git a/Cargo.toml b/Cargo.toml index 61c53b1..ebdf94f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,6 @@ flate2 = "1" half = "2" parking_lot = "0.12.1" rand = "0.8" +serde = { version = "1", features = ["derive"] } +serde_json = "1" tokio = {version = "1", features = ["full"]} diff --git a/src/main.rs b/src/main.rs index 1b86db4..9f7d464 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,32 @@ -use server::Server; +use std::path::PathBuf; + +use server::{config::ServerConfig, Server}; mod level; mod packet; mod player; mod server; +const CONFIG_FILE: &str = "./server-config.json"; + #[tokio::main] async fn main() -> std::io::Result<()> { - let mut server = Server::new().await?; + let config_path = PathBuf::from(CONFIG_FILE); + let config = if config_path.exists() { + serde_json::from_str(&std::fs::read_to_string(config_path)?) + .expect("failed to deserialize config") + } else { + let config = ServerConfig::default(); + std::fs::write( + config_path, + serde_json::to_string_pretty(&config).expect("failed to serialize default config"), + )?; + config + }; + + println!("starting server with config: {config:#?}"); + + let mut server = Server::new(config).await?; server.run().await?; diff --git a/src/server.rs b/src/server.rs index 64b0846..9afafcb 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,3 +1,4 @@ +pub mod config; mod network; use std::sync::Arc; @@ -8,6 +9,8 @@ use tokio::{net::TcpListener, sync::RwLock}; use crate::{level::Level, player::Player}; +use self::config::ServerConfig; + const DEFAULT_SERVER_SIZE: usize = 128; /// the server @@ -28,18 +31,25 @@ pub struct ServerData { pub players: Vec, /// list of player ids which have been freed up pub free_player_ids: Vec, + /// the server's config + pub config: ServerConfig, } impl Server { /// creates a new server with a generated level - pub async fn new() -> std::io::Result { + pub async fn new(config: ServerConfig) -> std::io::Result { println!("generating level"); let mut rng = rand::thread_rng(); - let mut level = Level::new( - DEFAULT_SERVER_SIZE, - DEFAULT_SERVER_SIZE, - DEFAULT_SERVER_SIZE, - ); + let (level_x, level_y, level_z) = if let Some(size) = &config.level_size { + (size.x, size.y, size.z) + } else { + ( + DEFAULT_SERVER_SIZE, + DEFAULT_SERVER_SIZE, + DEFAULT_SERVER_SIZE, + ) + }; + let mut level = Level::new(level_x, level_y, level_z); for x in 0..level.x_size { for y in 0..(level.y_size / 2) { for z in 0..level.z_size { @@ -49,11 +59,11 @@ impl Server { } println!("done!"); - Self::new_with_level(level).await + Self::new_with_level(config, level).await } /// creates a new server with the given level - pub async fn new_with_level(level: Level) -> std::io::Result { + pub async fn new_with_level(config: ServerConfig, level: Level) -> std::io::Result { let listener = TcpListener::bind("127.0.0.1:25565").await?; Ok(Self { @@ -61,6 +71,7 @@ impl Server { level, players: Default::default(), free_player_ids: Vec::new(), + config, })), listener, }) diff --git a/src/server/config.rs b/src/server/config.rs new file mode 100644 index 0000000..15acc11 --- /dev/null +++ b/src/server/config.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; + +/// configuration for the server +#[derive(Debug, Serialize, Deserialize)] +pub struct ServerConfig { + /// the server's name + pub name: String, + /// the server's motd + pub motd: String, + /// the server's password, if any + pub password: Option, + /// the level's size + pub level_size: Option, + /// the level's spawn point + pub spawn: Option, +} + +impl Default for ServerConfig { + fn default() -> Self { + Self { + name: "classic server wowie".to_string(), + motd: "here's the default server motd".to_string(), + password: None, + level_size: None, + spawn: None, + } + } +} + +/// coordinates as stored in configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConfigCoordinates { + /// the X coordinate + pub x: usize, + /// the Y coordinate + pub y: usize, + /// the Z coordinate + pub z: usize, +} diff --git a/src/server/network.rs b/src/server/network.rs index 7eb5d1d..768f57f 100644 --- a/src/server/network.rs +++ b/src/server/network.rs @@ -98,12 +98,11 @@ async fn handle_stream_inner( let mut reader = PacketReader::new(&read_buf); if let Some(packet) = ClientPacket::read(packet_buf[0], &mut reader) { - // println!("{packet:#?}"); match packet { ClientPacket::PlayerIdentification { protocol_version, username, - verification_key: _, + verification_key, _unused, } => { if protocol_version != 0x07 { @@ -114,6 +113,12 @@ async fn handle_stream_inner( let mut data = data.write().await; + if let Some(password) = &data.config.password { + if verification_key != *password { + return Ok(Some("Incorrect password!".to_string())); + } + } + for player in &data.players { if player.username == username { return Ok(Some( @@ -144,8 +149,8 @@ async fn handle_stream_inner( reply_queue.push_back(ServerPacket::ServerIdentification { protocol_version: 0x07, - server_name: "test server".to_string(), - server_motd: "whoaaaaaa".to_string(), + server_name: data.config.name.clone(), + server_motd: data.config.motd.clone(), user_type: PlayerType::Normal, }); @@ -156,12 +161,19 @@ async fn handle_stream_inner( let username = player.username.clone(); data.players.push(player); + let (spawn_x, spawn_y, spawn_z) = + if let Some(spawn) = &data.config.spawn { + (spawn.x, spawn.y, spawn.z) + } else { + (16, data.level.y_size / 2 + 2, 16) + }; + let spawn_packet = ServerPacket::SpawnPlayer { player_id: *own_id, player_name: username.clone(), - x: f16::from_f32(16.5), - y: f16::from_f32((data.level.y_size / 2 + 2) as f32), - z: f16::from_f32(16.5), + x: f16::from_f32(spawn_x as f32 + 0.5), + y: f16::from_f32(spawn_y as f32), + z: f16::from_f32(spawn_z as f32 + 0.5), yaw: 0, pitch: 0, };