diff --git a/.gitignore b/.gitignore index 7851bea8dc..153d29e4a5 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,6 @@ # Veloren *.rar *.log +settings.ron run.sh screenshots diff --git a/Cargo.lock b/Cargo.lock index 463af2c31a..a1c93e5cbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2766,7 +2766,10 @@ version = "0.2.0" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "scan_fmt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)", "specs 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)", "uvth 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "vek 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/client/src/error.rs b/client/src/error.rs index 1f74ae62b9..20f3319649 100644 --- a/client/src/error.rs +++ b/client/src/error.rs @@ -6,6 +6,8 @@ pub enum Error { ServerWentMad, ServerTimeout, ServerShutdown, + TooManyPlayers, + //TODO: InvalidAlias, Other(String), } diff --git a/client/src/lib.rs b/client/src/lib.rs index 977829a722..720d4022b2 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -9,7 +9,7 @@ pub use specs::Entity as EcsEntity; use common::{ comp, - msg::{ClientMsg, ClientState, ServerInfo, ServerMsg}, + msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg}, net::PostBox, state::State, terrain::{block::Block, chonk::ChonkMetrics, TerrainChunk, TerrainChunkSize}, @@ -73,6 +73,9 @@ impl Client { .ok_or(Error::ServerWentMad)?; (state, entity, server_info) } + Some(ServerMsg::Error(ServerError::TooManyPlayers)) => { + return Err(Error::TooManyPlayers) + } _ => return Err(Error::ServerWentMad), }; @@ -361,8 +364,12 @@ impl Client { if new_msgs.len() > 0 { for msg in new_msgs { match msg { - ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad), + ServerMsg::Error(e) => match e { + ServerError::TooManyPlayers => return Err(Error::ServerWentMad), + //TODO: ServerError::InvalidAlias => return Err(Error::InvalidAlias), + }, ServerMsg::Shutdown => return Err(Error::ServerShutdown), + ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad), ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong), ServerMsg::Pong => { self.last_ping_delta = Instant::now() diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs index 0761273cf7..e9b7a1d5c7 100644 --- a/common/src/comp/player.rs +++ b/common/src/comp/player.rs @@ -19,7 +19,6 @@ impl Player { pub fn is_valid(&self) -> bool { self.alias.chars().all(|c| c.is_alphanumeric() || c == '_') && self.alias.len() <= MAX_ALIAS_LEN - // TODO: Check view distance here based on server config too } } diff --git a/common/src/msg/mod.rs b/common/src/msg/mod.rs index 2cad42a63c..25644b36de 100644 --- a/common/src/msg/mod.rs +++ b/common/src/msg/mod.rs @@ -5,7 +5,7 @@ pub mod server; // Reexports pub use self::client::ClientMsg; pub use self::ecs_packet::{EcsCompPacket, EcsResPacket}; -pub use self::server::{RequestStateError, ServerInfo, ServerMsg}; +pub use self::server::{RequestStateError, ServerError, ServerInfo, ServerMsg}; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum ClientState { diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 0fadce7afa..e48423fca4 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -41,6 +41,13 @@ pub enum ServerMsg { key: Vec2, chunk: Box, }, + Error(ServerError), Disconnect, Shutdown, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ServerError { + TooManyPlayers, + //TODO: InvalidAlias, +} diff --git a/server-cli/Cargo.toml b/server-cli/Cargo.toml index 62ad0c6368..7e32d07f5f 100644 --- a/server-cli/Cargo.toml +++ b/server-cli/Cargo.toml @@ -9,4 +9,4 @@ server = { package = "veloren-server", path = "../server" } common = { package = "veloren-common", path = "../common" } log = "0.4" -pretty_env_logger = "0.3" +pretty_env_logger = "0.3" \ No newline at end of file diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index d53aca5d91..952c7c3152 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -1,6 +1,6 @@ use common::clock::Clock; use log::info; -use server::{Event, Input, Server}; +use server::{Event, Input, Server, ServerSettings}; use std::time::Duration; const TPS: u64 = 30; @@ -14,8 +14,11 @@ fn main() { // Set up an fps clock let mut clock = Clock::start(); + // Load settings + let settings = ServerSettings::load(); + // Create server - let mut server = Server::new().expect("Failed to create server instance!"); + let mut server = Server::new(settings).expect("Failed to create server instance!"); loop { let events = server diff --git a/server/Cargo.toml b/server/Cargo.toml index bd5ab709cc..b0b4738783 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -14,3 +14,6 @@ vek = "0.9" uvth = "3.1.0" lazy_static = "1.3.0" scan_fmt = "0.1.3" +ron = "0.5.1" +serde = "1.0" +serde_derive = "1.0" \ No newline at end of file diff --git a/server/src/client.rs b/server/src/client.rs index e22d8d2e95..fb43465ebb 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -41,6 +41,10 @@ impl Clients { } } + pub fn len(&mut self) -> usize { + self.clients.len() + } + pub fn add(&mut self, entity: EcsEntity, client: Client) { self.clients.insert(entity, client); } diff --git a/server/src/lib.rs b/server/src/lib.rs index a0afc058c2..f9da79dbc1 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -4,9 +4,10 @@ pub mod client; pub mod cmd; pub mod error; pub mod input; +pub mod settings; // Reexports -pub use crate::{error::Error, input::Input}; +pub use crate::{error::Error, input::Input, settings::ServerSettings}; use crate::{ client::{Client, Clients}, @@ -14,9 +15,9 @@ use crate::{ }; use common::{ comp, - msg::{ClientMsg, ClientState, RequestStateError, ServerInfo, ServerMsg}, + msg::{ClientMsg, ClientState, RequestStateError, ServerError, ServerInfo, ServerMsg}, net::PostOffice, - state::{State, TerrainChange, Uid}, + state::{State, TerrainChange, TimeOfDay, Uid}, terrain::{block::Block, TerrainChunk, TerrainChunkSize, TerrainMap}, vol::VolSize, vol::Vox, @@ -36,8 +37,6 @@ use world::World; const CLIENT_TIMEOUT: f64 = 20.0; // Seconds -const DEFAULT_WORLD_SEED: u32 = 1337; - pub enum Event { ClientConnected { entity: EcsEntity, @@ -66,19 +65,20 @@ pub struct Server { chunk_rx: mpsc::Receiver<(Vec2, TerrainChunk)>, pending_chunks: HashSet>, + server_settings: ServerSettings, server_info: ServerInfo, } impl Server { /// Create a new `Server` bound to the default socket. #[allow(dead_code)] - pub fn new() -> Result { - Self::bind(SocketAddr::from(([0; 4], 59003))) + pub fn new(settings: ServerSettings) -> Result { + Self::bind(settings.address, settings) } /// Create a new server bound to the given socket. #[allow(dead_code)] - pub fn bind>(addrs: A) -> Result { + pub fn bind>(addrs: A, settings: ServerSettings) -> Result { let (chunk_tx, chunk_rx) = mpsc::channel(); let mut state = State::default(); @@ -86,9 +86,12 @@ impl Server { .ecs_mut() .add_resource(SpawnPoint(Vec3::new(16_384.0, 16_384.0, 380.0))); + // Set starting time for the server. + state.ecs_mut().write_resource::().0 = settings.start_time; + let this = Self { state, - world: Arc::new(World::generate(DEFAULT_WORLD_SEED)), + world: Arc::new(World::generate(settings.world_seed)), postoffice: PostOffice::bind(addrs.into())?, clients: Clients::empty(), @@ -101,9 +104,10 @@ impl Server { pending_chunks: HashSet::new(), server_info: ServerInfo { - name: "Server name".to_owned(), - description: "This is the best Veloren server.".to_owned(), + name: settings.server_name.clone(), + description: settings.server_description.clone(), }, + server_settings: settings, }; Ok(this) @@ -356,16 +360,20 @@ impl Server { last_ping: self.state.get_time(), }; - // Return the state of the current world (all of the components that Sphynx tracks). - client.notify(ServerMsg::InitialSync { - ecs_state: self.state.ecs().gen_state_package(), - entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail. - server_info: self.server_info.clone(), - }); + if self.server_settings.max_players <= self.clients.len() { + client.notify(ServerMsg::Error(ServerError::TooManyPlayers)); + } else { + // Return the state of the current world (all of the components that Sphynx tracks). + client.notify(ServerMsg::InitialSync { + ecs_state: self.state.ecs().gen_state_package(), + entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail. + server_info: self.server_info.clone(), + }); + + frontend_events.push(Event::ClientConnected { entity }); + } self.clients.add(entity, client); - - frontend_events.push(Event::ClientConnected { entity }); } Ok(frontend_events) diff --git a/server/src/settings.rs b/server/src/settings.rs new file mode 100644 index 0000000000..4187648bd2 --- /dev/null +++ b/server/src/settings.rs @@ -0,0 +1,65 @@ +use serde_derive::{Deserialize, Serialize}; +use std::{fs, io::prelude::*, net::SocketAddr, path::PathBuf}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct ServerSettings { + pub address: SocketAddr, + pub max_players: usize, + pub world_seed: u32, + //pub pvp_enabled: bool, + pub server_name: String, + pub server_description: String, + //pub login_server: whatever + pub start_time: f64, +} + +impl Default for ServerSettings { + fn default() -> Self { + Self { + address: SocketAddr::from(([0; 4], 59003)), + world_seed: 1337, + server_name: "Server name".to_owned(), + server_description: "This is the best Veloren server.".to_owned(), + max_players: 16, + start_time: 0.0, + } + } +} + +impl ServerSettings { + pub fn load() -> Self { + let path = ServerSettings::get_settings_path(); + + if let Ok(file) = fs::File::open(path) { + match ron::de::from_reader(file) { + Ok(x) => x, + Err(e) => { + log::warn!("Failed to parse setting file! Fallback to default. {}", e); + Self::default() + } + } + } else { + let default_settings = Self::default(); + + match default_settings.save_to_file() { + Err(e) => log::error!("Failed to create default setting file! {}", e), + _ => {} + } + default_settings + } + } + + pub fn save_to_file(&self) -> std::io::Result<()> { + let path = ServerSettings::get_settings_path(); + let mut config_file = fs::File::create(path)?; + + let s: &str = &ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap(); + config_file.write_all(s.as_bytes()).unwrap(); + Ok(()) + } + + fn get_settings_path() -> PathBuf { + PathBuf::from(r"settings.ron") + } +} diff --git a/voxygen/src/menu/main/client_init.rs b/voxygen/src/menu/main/client_init.rs index 86ab1af88a..ee07025a55 100644 --- a/voxygen/src/menu/main/client_init.rs +++ b/voxygen/src/menu/main/client_init.rs @@ -20,6 +20,7 @@ pub enum Error { // Parsing/host name resolution successful but could not connect. ConnectionFailed(ClientError), ClientCrashed, + ServerIsFull, } // Used to asynchronously parse the server address, resolve host names, @@ -76,6 +77,10 @@ impl ClientInit { ClientError::Network(_) => { last_err = Some(Error::ConnectionFailed(err)) } + ClientError::TooManyPlayers => { + last_err = Some(Error::ServerIsFull); + break; + } // TODO: Handle errors? _ => panic!( "Unexpected non-network error when creating client: {:?}", diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index b46b076e69..ed27a97ea6 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -64,6 +64,7 @@ impl PlayState for MainMenuState { self.main_menu_ui.login_error( match err { InitError::BadAddress(_) | InitError::NoAddress => "Server not found", + InitError::ServerIsFull => "Server is Full!", InitError::ConnectionFailed(_) => "Connection failed", InitError::ClientCrashed => "Client crashed", } diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index ce300a3d5b..0b294b02f6 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -2,7 +2,7 @@ use client::Client; use common::clock::Clock; use log::info; use portpicker::pick_unused_port; -use server::{Event, Input, Server}; +use server::{Event, Input, Server, ServerSettings}; use std::{ net::SocketAddr, sync::mpsc::{channel, Receiver, Sender, TryRecvError}, @@ -36,7 +36,8 @@ impl Singleplayer { )); // Create server - let server = Server::bind(sock.clone()).expect("Failed to create server instance!"); + let server = Server::bind(sock.clone(), ServerSettings::default()) + .expect("Failed to create server instance!"); let server = match client { Some(client) => server.with_thread_pool(client.thread_pool().clone()),