From 6c43a7cdc5c8e95520cc992c6584f565b8e7fdd3 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 19 Sep 2020 19:35:14 -0400 Subject: [PATCH 01/15] Change projectiles to not be pushed back by collisions, moved group check into projectile system from physics system --- common/src/sys/phys.rs | 31 ++++++++++--------------------- common/src/sys/projectile.rs | 22 +++++++++++++++++++++- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index a2b5947b6e..b06f329b9e 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -1,21 +1,18 @@ use crate::{ comp::{ - BeamSegment, Collider, Gravity, Group, Mass, Mounting, Ori, PhysicsState, Pos, Projectile, - Scale, Sticky, Vel, + BeamSegment, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, Projectile, Scale, + Sticky, Vel, }, event::{EventBus, ServerEvent}, metrics::SysMetrics, span, state::DeltaTime, - sync::{Uid, UidAllocator}, + sync::Uid, terrain::{Block, TerrainGrid}, vol::ReadVol, }; use rayon::iter::ParallelIterator; -use specs::{ - saveload::MarkerAllocator, Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, System, - WriteStorage, -}; +use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, System, WriteStorage}; use std::ops::Range; use vek::*; @@ -55,7 +52,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Uid>, ReadExpect<'a, TerrainGrid>, Read<'a, DeltaTime>, - Read<'a, UidAllocator>, ReadExpect<'a, SysMetrics>, Read<'a, EventBus>, ReadStorage<'a, Scale>, @@ -68,7 +64,6 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, ReadStorage<'a, Mounting>, - ReadStorage<'a, Group>, ReadStorage<'a, Projectile>, ReadStorage<'a, BeamSegment>, ); @@ -82,7 +77,6 @@ impl<'a> System<'a> for Sys { uids, terrain, dt, - uid_allocator, sys_metrics, event_bus, scales, @@ -95,7 +89,6 @@ impl<'a> System<'a> for Sys { mut velocities, mut orientations, mountings, - groups, projectiles, beams, ): Self::SystemData, @@ -161,12 +154,7 @@ impl<'a> System<'a> for Sys { // Resets touch_entities in physics physics.touch_entities.clear(); - // Group to ignore collisions with - let ignore_group = projectile - .filter(|p| p.ignore_group) - .and_then(|p| p.owner) - .and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into())) - .and_then(|e| groups.get(e)); + let is_projectile = projectile.is_some(); let mut vel_delta = Vec3::zero(); @@ -178,7 +166,7 @@ impl<'a> System<'a> for Sys { mass_other, collider_other, _, - group_b, + _, _, ) in ( &entities, @@ -187,13 +175,13 @@ impl<'a> System<'a> for Sys { scales.maybe(), masses.maybe(), colliders.maybe(), + !&projectiles, !&mountings, - groups.maybe(), !&beams, ) .join() { - if entity == entity_other || (ignore_group.is_some() && ignore_group == group_b) { + if entity == entity_other { continue; } @@ -244,7 +232,8 @@ impl<'a> System<'a> for Sys { physics.touch_entities.push(*other); } - if diff.magnitude_squared() > 0.0 { + // Don't apply repulsive force to projectiles + if diff.magnitude_squared() > 0.0 && !is_projectile { let force = 400.0 * (collision_dist - diff.magnitude()) * mass_other / (mass + mass_other); diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index e9677c2394..ba7ece43d1 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -1,6 +1,6 @@ use crate::{ comp::{ - projectile, Damage, DamageSource, Energy, EnergySource, HealthChange, HealthSource, + projectile, Damage, DamageSource, Energy, EnergySource, HealthChange, HealthSource, Group, Loadout, Ori, PhysicsState, Pos, Projectile, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, @@ -34,6 +34,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Projectile>, WriteStorage<'a, Energy>, ReadStorage<'a, Loadout>, + ReadStorage<'a, Group>, ); fn run( @@ -52,6 +53,7 @@ impl<'a> System<'a> for Sys { mut projectiles, mut energies, loadouts, + groups, ): Self::SystemData, ) { let start_time = std::time::Instant::now(); @@ -71,6 +73,24 @@ impl<'a> System<'a> for Sys { { // Hit entity for other in physics.touch_entities.iter().copied() { + if projectile.ignore_group + // Skip if in the same group + && projectile + .owner + // Note: somewhat inefficient since we do the lookup for every touching + // entity, but if we pull this out of the loop we would want to do it only + // if there is at least one touching entity + .and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into())) + .and_then(|e| groups.get(e)) + .map_or(false, |owner_group| + Some(owner_group) == uid_allocator + .retrieve_entity_internal(other.into()) + .and_then(|e| groups.get(e)) + ) + { + continue; + } + if projectile.owner == Some(other) { continue; } From ef74d395a6d0594ae0cc46d52aafea04a3aa989a Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 19 Sep 2020 20:41:46 -0400 Subject: [PATCH 02/15] Improve figure and sprite highlighting --- assets/voxygen/shaders/figure-frag.glsl | 4 ++-- assets/voxygen/shaders/sprite-frag.glsl | 4 ++-- assets/voxygen/shaders/sprite-vert.glsl | 2 +- voxygen/src/scene/figure/mod.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index 315807549a..6a8015773d 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -149,7 +149,7 @@ void main() { DirectionalLight sun_info = get_sun_info(sun_dir, point_shadow * sun_shade_frac, /*sun_pos*/f_pos); DirectionalLight moon_info = get_moon_info(moon_dir, point_shadow * moon_shade_frac/*, light_pos*/); - vec3 surf_color = /*srgb_to_linear*/(model_col.rgb * f_col); + vec3 surf_color = /*srgb_to_linear*/f_col; float alpha = 1.0; const float n2 = 1.5; const float R_s2s0 = pow((1.0 - n2) / (1.0 + n2), 2); @@ -196,7 +196,7 @@ void main() { // diffuse_light += point_light; // reflected_light += point_light; // vec3 surf_color = illuminate(srgb_to_linear(model_col.rgb * f_col), light, diffuse_light, ambient_light); - surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light); + surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light) * model_col.rgb; #if (CLOUD_MODE == CLOUD_MODE_REGULAR) float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); diff --git a/assets/voxygen/shaders/sprite-frag.glsl b/assets/voxygen/shaders/sprite-frag.glsl index fd97f688ac..73bf5ee263 100644 --- a/assets/voxygen/shaders/sprite-frag.glsl +++ b/assets/voxygen/shaders/sprite-frag.glsl @@ -157,7 +157,7 @@ void main() { // vec3 surf_color = srgb_to_linear(vec3(0.2, 0.5, 1.0)); // vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz); float max_light = 0.0; - max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, /*time_of_day.x, *//*cam_to_frag*/view_dir, k_a * f_light/* * (shade_frac * 0.5 + light_frac * 0.5)*/, k_d, k_s, alpha, emitted_light, reflected_light); + max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, /*time_of_day.x, *//*cam_to_frag*/view_dir, k_a/* * (shade_frac * 0.5 + light_frac * 0.5)*/, k_d, k_s, alpha, emitted_light, reflected_light); // reflected_light *= /*vert_light * */point_shadow * shade_frac; // emitted_light *= /*vert_light * */point_shadow * max(shade_frac, MIN_SHADOW); // max_light *= /*vert_light * */point_shadow * shade_frac; @@ -182,7 +182,7 @@ void main() { emitted_light *= ao; reflected_light *= ao; - surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light); + surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light) * f_light; // vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light); #if (CLOUD_MODE == CLOUD_MODE_REGULAR) diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl index e8c4c33270..4146be684d 100644 --- a/assets/voxygen/shaders/sprite-vert.glsl +++ b/assets/voxygen/shaders/sprite-vert.glsl @@ -222,7 +222,7 @@ void main() { // f_light = 1.0; // if (select_pos.w > 0) */{ vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(-offs.xyz, 1)).xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs; - f_light = (select_pos.w > 0 && select_pos.xyz == sprite_pos/* - vec3(0.5, 0.5, 0.0) * SCALE*/) ? 1.0 / PERSISTENT_AMBIANCE : 1.0; + f_light = (select_pos.w > 0 && select_pos.xyz == sprite_pos/* - vec3(0.5, 0.5, 0.0) * SCALE*/) ? 5.0 : 1.0; // } gl_Position = diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 70300051d1..b128ec0b3a 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -669,7 +669,7 @@ impl FigureMgr { .unwrap_or(vek::Rgba::broadcast(1.0)) // Highlight targeted collectible entities * if item.is_some() && scene_data.target_entity.map_or(false, |e| e == entity) { - vek::Rgba::new(2.0, 2.0, 2.0, 1.0) + vek::Rgba::new(5.0, 5.0, 5.0, 1.0) } else { vek::Rgba::one() }; From 873ea1ec46e2f08a679c149a1c02f1ea3573f553 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 19 Sep 2020 22:40:26 -0400 Subject: [PATCH 03/15] Make basic server-cli mode default to not reading input and add interactive option --- server-cli/src/main.rs | 46 ++++++++++++++++++++++-------------- server-cli/src/tui_runner.rs | 43 +++++++++++++++------------------ 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 4d2d6fb8b6..c0d7772aab 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -1,5 +1,6 @@ #![deny(unsafe_code)] #![deny(clippy::clone_on_ref_ptr)] +#![feature(bool_to_option)] mod shutdown_coordinator; mod tui_runner; @@ -47,17 +48,23 @@ fn main() -> io::Result<()> { .help("Disables the tui") .takes_value(false), ) + .arg( + Arg::with_name("interactive") + .short("i") + .long("interactive") + .help("Enables command input for basic mode") + .takes_value(false), + ) .get_matches(); let basic = matches.is_present("basic"); + let interactive = matches.is_present("interactive"); let sigusr1_signal = Arc::new(AtomicBool::new(false)); #[cfg(any(target_os = "linux", target_os = "macos"))] let _ = signal_hook::flag::register(SIGUSR1, Arc::clone(&sigusr1_signal)); - let (mut tui, msg_r) = Tui::new(); - // Init logging let base_exceptions = |env: EnvFilter| { env.add_directive("veloren_world::sim=info".parse().unwrap()) @@ -111,7 +118,7 @@ fn main() -> io::Result<()> { hook(info); })); - tui.run(basic); + let tui = (!basic || interactive).then(|| Tui::run(basic)); info!("Starting server..."); @@ -156,22 +163,25 @@ fn main() -> io::Result<()> { #[cfg(feature = "tracy")] common::util::tracy_client::finish_continuous_frame!(); - match msg_r.try_recv() { - Ok(msg) => match msg { - Message::AbortShutdown => shutdown_coordinator.abort_shutdown(&mut server), - Message::Shutdown { grace_period } => { - // TODO: The TUI parser doesn't support quoted strings so it is not currently - // possible to provide a shutdown reason from the console. - let message = "The server is shutting down".to_owned(); - shutdown_coordinator.initiate_shutdown(&mut server, grace_period, message); + if let Some(tui) = tui.as_ref() { + match tui.msg_r.try_recv() { + Ok(msg) => match msg { + Message::AbortShutdown => shutdown_coordinator.abort_shutdown(&mut server), + Message::Shutdown { grace_period } => { + // TODO: The TUI parser doesn't support quoted strings so it is not + // currently possible to provide a shutdown reason + // from the console. + let message = "The server is shutting down".to_owned(); + shutdown_coordinator.initiate_shutdown(&mut server, grace_period, message); + }, + Message::Quit => { + info!("Closing the server"); + break; + }, }, - Message::Quit => { - info!("Closing the server"); - break; - }, - }, - Err(mpsc::TryRecvError::Empty) | Err(mpsc::TryRecvError::Disconnected) => {}, - }; + Err(mpsc::TryRecvError::Empty) | Err(mpsc::TryRecvError::Disconnected) => {}, + } + } // Wait for the next tick. clock.tick(Duration::from_millis(1000 / TPS)); diff --git a/server-cli/src/tui_runner.rs b/server-cli/src/tui_runner.rs index e5c3d127e3..b867d949e8 100644 --- a/server-cli/src/tui_runner.rs +++ b/server-cli/src/tui_runner.rs @@ -86,26 +86,13 @@ pub const COMMANDS: [Command; 4] = [ ]; pub struct Tui { + pub msg_r: mpsc::Receiver, background: Option>, basic: bool, - msg_s: Option>, running: Arc, } impl Tui { - pub fn new() -> (Self, mpsc::Receiver) { - let (msg_s, msg_r) = mpsc::channel(); - ( - Self { - background: None, - basic: false, - msg_s: Some(msg_s), - running: Arc::new(AtomicBool::new(true)), - }, - msg_r, - ) - } - fn handle_events(input: &mut String, msg_s: &mut mpsc::Sender) { use crossterm::event::*; if let Event::Key(event) = read().unwrap() { @@ -132,15 +119,14 @@ impl Tui { } } - pub fn run(&mut self, basic: bool) { - self.basic = basic; + pub fn run(basic: bool) -> Self { + let (mut msg_s, msg_r) = mpsc::channel(); + let running = Arc::new(AtomicBool::new(true)); + let running2 = running.clone(); - let mut msg_s = self.msg_s.take().unwrap(); - let running = Arc::clone(&self.running); - - if self.basic { + let background = if basic { std::thread::spawn(move || { - while running.load(Ordering::Relaxed) { + while running2.load(Ordering::Relaxed) { let mut line = String::new(); match io::stdin().read_line(&mut line) { @@ -163,8 +149,10 @@ impl Tui { } } }); + + None } else { - self.background = Some(std::thread::spawn(move || { + Some(std::thread::spawn(move || { // Start the tui let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); @@ -180,7 +168,7 @@ impl Tui { error!(?e, "couldn't clean terminal"); }; - while running.load(Ordering::Relaxed) { + while running2.load(Ordering::Relaxed) { if let Err(e) = terminal.draw(|f| { let (log_rect, input_rect) = if f.size().height > 6 { let mut log_rect = f.size(); @@ -227,7 +215,14 @@ impl Tui { Self::handle_events(&mut input, &mut msg_s); }; } - })); + })) + }; + + Self { + msg_r, + background, + basic, + running, } } From 66d2d636eeae7a1c21322e2a45e0c96a6f781ab2 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 20 Sep 2020 00:51:20 -0400 Subject: [PATCH 04/15] Add server-cli option to disable auth --- server-cli/src/main.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index c0d7772aab..7723f1ec91 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -41,24 +41,26 @@ fn main() -> io::Result<()> { .version(common::util::DISPLAY_VERSION_LONG.as_str()) .author("The veloren devs ") .about("The veloren server cli provides an easy to use interface to start a veloren server") - .arg( + .args(&[ Arg::with_name("basic") .short("b") .long("basic") .help("Disables the tui") .takes_value(false), - ) - .arg( Arg::with_name("interactive") .short("i") .long("interactive") .help("Enables command input for basic mode") .takes_value(false), - ) + Arg::with_name("no-auth") + .long("no-auth") + .help("Runs without auth enabled"), + ]) .get_matches(); let basic = matches.is_present("basic"); let interactive = matches.is_present("interactive"); + let no_auth = matches.is_present("no-auth"); let sigusr1_signal = Arc::new(AtomicBool::new(false)); @@ -126,7 +128,12 @@ fn main() -> io::Result<()> { let mut clock = Clock::start(); // Load settings - let settings = ServerSettings::load(); + let mut settings = ServerSettings::load(); + // TODO: make settings file immutable so that this does not overwrite the + // settings + if no_auth { + settings.auth_server_address = None; + } let server_port = &settings.gameserver_address.port(); let metrics_port = &settings.metrics_address.port(); // Create server From 26d59a62fc22f26a4526f715b929f5c4117dff05 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 20 Sep 2020 00:57:26 -0400 Subject: [PATCH 05/15] Downgrade debug -> trace in persistence code --- server/src/persistence/character.rs | 13 +++++++------ server/src/persistence/character_updater.rs | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index e3b7fff2b1..774b9e1b8e 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -25,7 +25,7 @@ use common::character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER}; use core::ops::Range; use diesel::{prelude::*, sql_query, sql_types::BigInt}; use std::sync::Arc; -use tracing::{debug, error}; +use tracing::{error, trace}; /// Private module for very tightly coupled database conversion methods. In /// general, these have many invariants that need to be maintained when they're @@ -444,7 +444,7 @@ fn get_new_entity_ids( ))); } - debug!( + trace!( "Created {} new persistence entity_ids: {}", new_ids.end - new_ids.start, new_ids @@ -533,7 +533,7 @@ pub fn update( })?; // Next, delete any slots we aren't upserting. - debug!("Deleting items for character_id {}", char_id); + trace!("Deleting items for character_id {}", char_id); let existing_items = parent_container_item_id .eq(pseudo_containers.inventory_container_id) .or(parent_container_item_id.eq(pseudo_containers.loadout_container_id)); @@ -546,7 +546,7 @@ pub fn update( let delete_count = diesel::delete(item.filter(existing_items.and(non_upserted_items))) .execute(&*connection)?; - debug!("Deleted {} items", delete_count); + trace!("Deleted {} items", delete_count); // Upsert items let expected_upsert_count = upserts.len(); @@ -557,9 +557,10 @@ pub fn update( .map(|model_pair| (model_pair.model, model_pair.comp)) .unzip(); upserted_comps = upserted_comps_; - debug!( + trace!( "Upserting items {:?} for character_id {}", - upserted_items, char_id + upserted_items, + char_id ); let upsert_count = diesel::replace_into(item) diff --git a/server/src/persistence/character_updater.rs b/server/src/persistence/character_updater.rs index 97dd4fcac5..cb8b8ebe6f 100644 --- a/server/src/persistence/character_updater.rs +++ b/server/src/persistence/character_updater.rs @@ -4,7 +4,7 @@ use common::{character::CharacterId, comp::item::ItemId}; use crate::persistence::{establish_connection, VelorenConnection}; use crossbeam::channel; use std::sync::Arc; -use tracing::{debug, error}; +use tracing::{error, trace}; pub type CharacterUpdateData = (comp::Stats, comp::Inventory, comp::Loadout); @@ -27,9 +27,9 @@ impl CharacterUpdater { let handle = std::thread::spawn(move || { while let Ok(updates) = update_rx.recv() { - debug!("Persistence batch update starting"); + trace!("Persistence batch update starting"); execute_batch_update(updates, &mut conn); - debug!("Persistence batch update finished"); + trace!("Persistence batch update finished"); } }); From 7c14a3f4a4cf523a6557a13e2e9925ec015b6caf Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 5 Oct 2020 03:41:58 -0400 Subject: [PATCH 06/15] New userdata folder that holds voxygen settings and logs and server saves and settings, split up server settings file into parts that are persisted back to their files and parts that are read-only, misc fixes --- .gitignore | 1 + CHANGELOG.md | 2 + Cargo.lock | 1 + common/Cargo.toml | 1 + common/src/util/mod.rs | 1 + common/src/util/userdata_dir.rs | 76 ++++++++++ server-cli/src/logging.rs | 58 +++++++ server-cli/src/main.rs | 80 +++------- server-cli/src/tui_runner.rs | 2 +- server/src/cmd.rs | 37 ++--- server/src/data_dir.rs | 16 ++ server/src/lib.rs | 58 ++++++- server/src/persistence/character_loader.rs | 5 +- server/src/persistence/character_updater.rs | 6 +- server/src/persistence/mod.rs | 24 +-- server/src/settings.rs | 159 +++++++++++++++----- server/src/settings/editable.rs | 89 +++++++++++ server/src/sys/message.rs | 41 +++-- voxygen/Cargo.toml | 1 + voxygen/src/profile.rs | 15 +- voxygen/src/settings.rs | 45 +++--- voxygen/src/singleplayer.rs | 20 +-- 22 files changed, 543 insertions(+), 195 deletions(-) create mode 100644 common/src/util/userdata_dir.rs create mode 100644 server-cli/src/logging.rs create mode 100644 server/src/data_dir.rs create mode 100644 server/src/settings/editable.rs diff --git a/.gitignore b/.gitignore index 2dfe7084fd..f2ba2f0490 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ run.sh maps screenshots todo.txt +userdata # Export data *.csv diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e37153835..dcaeaea728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Overhauled representation of blocks to permit fluid and sprite coexistence - Overhauled sword - Reworked healing sceptre +- Split out the sections of the server settings that can be edtited and saved by the server. +- Revamped structure of where settings, logs, and game saves are stored so that almost everything is in one place. ### Removed diff --git a/Cargo.lock b/Cargo.lock index e45c6f8c12..95b1746eff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4643,6 +4643,7 @@ dependencies = [ "authc", "criterion", "crossbeam", + "directories-next", "dot_vox", "enum-iterator", "hashbrown", diff --git a/common/Cargo.toml b/common/Cargo.toml index 6217569469..1152da8da9 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -15,6 +15,7 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "spec roots = "0.0.6" specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "7a2e348ab2223818bad487695c66c43db88050a5" } vek = { version = "0.12.0", features = ["platform_intrinsics", "serde"] } +directories-next = "1.0.1" dot_vox = "4.0" image = { version = "0.23.8", default-features = false, features = ["png"] } serde = { version = "1.0.110", features = ["derive", "rc"] } diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs index 0167fda1ce..8c4578d4e9 100644 --- a/common/src/util/mod.rs +++ b/common/src/util/mod.rs @@ -1,6 +1,7 @@ mod color; mod dir; mod option; +pub mod userdata_dir; pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash")); pub const GIT_TAG: &str = include_str!(concat!(env!("OUT_DIR"), "/gittag")); diff --git a/common/src/util/userdata_dir.rs b/common/src/util/userdata_dir.rs new file mode 100644 index 0000000000..24b7b304e4 --- /dev/null +++ b/common/src/util/userdata_dir.rs @@ -0,0 +1,76 @@ +use std::path::PathBuf; + +const VELOREN_USERDATA_ENV: &'static str = "VELOREN_USERDATA"; + +// TODO: consider expanding this to a general install strategy variable that is also used for +// finding assets +/// # `VELOREN_USERDATA_STRATEGY` environment variable +/// Read during compilation +/// Useful to set when compiling for distribution +/// "system" => system specific project data directory +/// "executable" => /userdata +/// Note: case insensitive + +/// Determines common user data directory used by veloren frontends +/// The first specified in this list is used +/// 1. The VELOREN_USERDATA environment variable +/// 2. The VELOREN_USERDATA_STRATEGY environment variable +/// 3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata depending on if a +/// workspace if being used +pub fn userdata_dir(workspace: bool, strategy: Option<&str>, manifest_dir: &str) -> PathBuf { + // 1. The VELOREN_USERDATA environment variable + std::env::var_os(VELOREN_USERDATA_ENV) + .map(PathBuf::from) + // 2. The VELOREN_USERDATA_STRATEGY environment variable + .or_else(|| match strategy { + // "system" => system specific project data directory + Some(s) if s.eq_ignore_ascii_case("system") => Some(directories_next::ProjectDirs::from("net", "veloren", "userdata") + .expect("System's $HOME directory path not found!") + .data_dir() + .to_owned() + ), + // "executable" => /userdata + Some(s) if s.eq_ignore_ascii_case("executable") => { + let mut path = std::env::current_exe() + .expect("Failed to retrieve executable directory!"); + path.pop(); + path.push("userdata"); + Some(path) + }, + Some(_) => None, // TODO: panic? catch during compilation? + _ => None, + + }) + // 3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata depending on if a + // workspace if being used + .unwrap_or_else(|| { + let mut path = PathBuf::from(manifest_dir); + if workspace { + path.pop(); + } + path.push("userdata"); + path + }) +} + +#[macro_export] +macro_rules! userdata_dir_workspace { + () => { + $crate::util::userdata_dir::userdata_dir( + true, + option_env!("VELOREN_USERDATA_STRATEGY"), env!("CARGO_MANIFEST_DIR") + ) + }; +} + +#[macro_export] +macro_rules! userdata_dir_no_workspace { + () => { + $crate::util::userdata_dir::userdata_dir( + false, + option_env!("VELOREN_USERDATA_STRATEGY"), env!("CARGO_MANIFEST_DIR") + ) + }; +} + + diff --git a/server-cli/src/logging.rs b/server-cli/src/logging.rs new file mode 100644 index 0000000000..d3fb675587 --- /dev/null +++ b/server-cli/src/logging.rs @@ -0,0 +1,58 @@ +use crate::tuilog::TuiLog; +use tracing::Level; +use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber}; +#[cfg(feature = "tracy")] +use tracing_subscriber::{layer::SubscriberExt, prelude::*}; + +const RUST_LOG_ENV: &str = "RUST_LOG"; + +lazy_static::lazy_static! { + pub static ref LOG: TuiLog<'static> = TuiLog::default(); +} + +pub fn init(basic: bool) { + // Init logging + let base_exceptions = |env: EnvFilter| { + env.add_directive("veloren_world::sim=info".parse().unwrap()) + .add_directive("veloren_world::civ=info".parse().unwrap()) + .add_directive("uvth=warn".parse().unwrap()) + .add_directive("tiny_http=warn".parse().unwrap()) + .add_directive("mio::sys::windows=debug".parse().unwrap()) + .add_directive(LevelFilter::INFO.into()) + }; + + #[cfg(not(feature = "tracy"))] + let filter = match std::env::var_os(RUST_LOG_ENV).map(|s| s.into_string()) { + Some(Ok(env)) => { + let mut filter = base_exceptions(EnvFilter::new("")); + for s in env.split(',').into_iter() { + match s.parse() { + Ok(d) => filter = filter.add_directive(d), + Err(err) => println!("WARN ignoring log directive: `{}`: {}", s, err), + }; + } + filter + }, + _ => base_exceptions(EnvFilter::from_env(RUST_LOG_ENV)), + }; + + #[cfg(feature = "tracy")] + tracing_subscriber::registry() + .with(tracing_tracy::TracyLayer::new().with_stackdepth(0)) + .init(); + + #[cfg(not(feature = "tracy"))] + // TODO: when tracing gets per Layer filters re-enable this when the tracy feature is being + // used (and do the same in voxygen) + { + let subscriber = FmtSubscriber::builder() + .with_max_level(Level::ERROR) + .with_env_filter(filter); + + if basic { + subscriber.init(); + } else { + subscriber.with_writer(|| LOG.clone()).init(); + } + } +} diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 7723f1ec91..6cf6cf3641 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -2,39 +2,28 @@ #![deny(clippy::clone_on_ref_ptr)] #![feature(bool_to_option)] +mod logging; mod shutdown_coordinator; mod tui_runner; mod tuilog; -#[macro_use] extern crate lazy_static; - use crate::{ shutdown_coordinator::ShutdownCoordinator, tui_runner::{Message, Tui}, - tuilog::TuiLog, }; +use clap::{App, Arg}; use common::clock::Clock; -use server::{Event, Input, Server, ServerSettings}; +use server::{DataDir, Event, Input, Server, ServerSettings}; #[cfg(any(target_os = "linux", target_os = "macos"))] use signal_hook::SIGUSR1; -use tracing::{info, Level}; -use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber}; -#[cfg(feature = "tracy")] -use tracing_subscriber::{layer::SubscriberExt, prelude::*}; - -use clap::{App, Arg}; use std::{ io, sync::{atomic::AtomicBool, mpsc, Arc}, time::Duration, }; +use tracing::info; const TPS: u64 = 30; -const RUST_LOG_ENV: &str = "RUST_LOG"; - -lazy_static! { - static ref LOG: TuiLog<'static> = TuiLog::default(); -} fn main() -> io::Result<()> { let matches = App::new("Veloren server cli") @@ -67,50 +56,7 @@ fn main() -> io::Result<()> { #[cfg(any(target_os = "linux", target_os = "macos"))] let _ = signal_hook::flag::register(SIGUSR1, Arc::clone(&sigusr1_signal)); - // Init logging - let base_exceptions = |env: EnvFilter| { - env.add_directive("veloren_world::sim=info".parse().unwrap()) - .add_directive("veloren_world::civ=info".parse().unwrap()) - .add_directive("uvth=warn".parse().unwrap()) - .add_directive("tiny_http=warn".parse().unwrap()) - .add_directive("mio::sys::windows=debug".parse().unwrap()) - .add_directive(LevelFilter::INFO.into()) - }; - - #[cfg(not(feature = "tracy"))] - let filter = match std::env::var_os(RUST_LOG_ENV).map(|s| s.into_string()) { - Some(Ok(env)) => { - let mut filter = base_exceptions(EnvFilter::new("")); - for s in env.split(',').into_iter() { - match s.parse() { - Ok(d) => filter = filter.add_directive(d), - Err(err) => println!("WARN ignoring log directive: `{}`: {}", s, err), - }; - } - filter - }, - _ => base_exceptions(EnvFilter::from_env(RUST_LOG_ENV)), - }; - - #[cfg(feature = "tracy")] - tracing_subscriber::registry() - .with(tracing_tracy::TracyLayer::new().with_stackdepth(0)) - .init(); - - #[cfg(not(feature = "tracy"))] - // TODO: when tracing gets per Layer filters re-enable this when the tracy feature is being - // used (and do the same in voxygen) - { - let subscriber = FmtSubscriber::builder() - .with_max_level(Level::ERROR) - .with_env_filter(filter); - - if basic { - subscriber.init(); - } else { - subscriber.with_writer(|| LOG.clone()).init(); - } - } + logging::init(basic); // Panic hook to ensure that console mode is set back correctly if in non-basic // mode @@ -127,17 +73,25 @@ fn main() -> io::Result<()> { // Set up an fps clock let mut clock = Clock::start(); + // Determine folder to save server data in + let server_data_dir = DataDir::from({ + let mut path = common::userdata_dir_workspace!(); + path.push(server::DEFAULT_DATA_DIR_NAME); + path + }); + // Load settings - let mut settings = ServerSettings::load(); - // TODO: make settings file immutable so that this does not overwrite the - // settings + let mut settings = ServerSettings::load(server_data_dir.as_ref()); + if no_auth { settings.auth_server_address = None; } + let server_port = &settings.gameserver_address.port(); let metrics_port = &settings.metrics_address.port(); // Create server - let mut server = Server::new(settings).expect("Failed to create server instance!"); + let mut server = + Server::new(settings, server_data_dir).expect("Failed to create server instance!"); info!( ?server_port, diff --git a/server-cli/src/tui_runner.rs b/server-cli/src/tui_runner.rs index b867d949e8..1c17788ecd 100644 --- a/server-cli/src/tui_runner.rs +++ b/server-cli/src/tui_runner.rs @@ -1,4 +1,4 @@ -use crate::LOG; +use crate::logging::LOG; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture}, execute, diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 5daa4bd384..925102da86 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2,7 +2,7 @@ //! To implement a new command, add an instance of `ChatCommand` to //! `CHAT_COMMANDS` and provide a handler function. -use crate::{client::Client, Server, StateExt}; +use crate::{client::Client, settings::EditableSetting, Server, StateExt}; use chrono::{NaiveTime, Timelike}; use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, @@ -266,7 +266,7 @@ fn handle_motd( ) { server.notify_client( client, - ChatType::CommandError.server_msg(server.settings().server_description.clone()), + ChatType::CommandError.server_msg((**server.server_description()).clone()), ); } @@ -277,18 +277,21 @@ fn handle_set_motd( args: String, action: &ChatCommand, ) { + let data_dir = server.data_dir(); match scan_fmt!(&args, &action.arg_fmt(), String) { Ok(msg) => { server - .settings_mut() - .edit(|s| s.server_description = msg.clone()); + .server_description_mut() + .edit(data_dir.as_ref(), |d| **d = msg.clone()); server.notify_client( client, ChatType::CommandError.server_msg(format!("Server description set to \"{}\"", msg)), ); }, Err(_) => { - server.settings_mut().edit(|s| s.server_description.clear()); + server + .server_description_mut() + .edit(data_dir.as_ref(), |d| d.clear()); server.notify_client( client, ChatType::CommandError.server_msg("Removed server description".to_string()), @@ -1825,17 +1828,18 @@ fn handle_whitelist( if let Ok((whitelist_action, username)) = scan_fmt!(&args, &action.arg_fmt(), String, String) { if whitelist_action.eq_ignore_ascii_case("add") { server - .settings_mut() - .edit(|s| s.whitelist.push(username.clone())); + .whitelist_mut() + .edit(server.data_dir().as_ref(), |w| w.push(username.clone())); server.notify_client( client, ChatType::CommandInfo.server_msg(format!("\"{}\" added to whitelist", username)), ); } else if whitelist_action.eq_ignore_ascii_case("remove") { - server.settings_mut().edit(|s| { - s.whitelist - .retain(|x| !x.eq_ignore_ascii_case(&username.clone())) - }); + server + .whitelist_mut() + .edit(server.data_dir().as_ref(), |w| { + w.retain(|x| !x.eq_ignore_ascii_case(&username.clone())) + }); server.notify_client( client, ChatType::CommandInfo @@ -1926,16 +1930,15 @@ fn handle_ban( .username_to_uuid(&target_alias); if let Ok(uuid) = uuid_result { - if server.settings().banlist.contains_key(&uuid) { + if server.banlist().contains_key(&uuid) { server.notify_client( client, ChatType::CommandError .server_msg(format!("{} is already on the banlist", target_alias)), ) } else { - server.settings_mut().edit(|s| { - s.banlist - .insert(uuid, (target_alias.clone(), reason.clone())); + server.banlist_mut().edit(server.data_dir().as_ref(), |b| { + b.insert(uuid, (target_alias.clone(), reason.clone())); }); server.notify_client( client, @@ -1987,8 +1990,8 @@ fn handle_unban( .username_to_uuid(&username); if let Ok(uuid) = uuid_result { - server.settings_mut().edit(|s| { - s.banlist.remove(&uuid); + server.banlist_mut().edit(server.data_dir().as_ref(), |b| { + b.remove(&uuid); }); server.notify_client( client, diff --git a/server/src/data_dir.rs b/server/src/data_dir.rs new file mode 100644 index 0000000000..c934638d7c --- /dev/null +++ b/server/src/data_dir.rs @@ -0,0 +1,16 @@ +use std::path::{Path, PathBuf}; + +/// Used so that different server frontends can share the same server saves, +/// etc. +pub const DEFAULT_DATA_DIR_NAME: &'static str = "server"; + +/// Indicates where maps, saves, and server_config folders are to be stored +pub struct DataDir { + pub path: PathBuf, +} +impl> From for DataDir { + fn from(t: T) -> Self { Self { path: t.into() } } +} +impl AsRef for DataDir { + fn as_ref(&self) -> &Path { &self.path } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 3ed6a917dd..21d16408cc 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -6,6 +6,7 @@ pub mod alias_validator; mod character_creator; +mod data_dir; pub mod chunk_generator; pub mod client; pub mod cmd; @@ -21,7 +22,7 @@ pub mod sys; #[cfg(not(feature = "worldgen"))] mod test_world; // Reexports -pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings}; +pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings, data_dir::{DataDir, DEFAULT_DATA_DIR_NAME}}; use crate::{ alias_validator::AliasValidator, @@ -29,6 +30,7 @@ use crate::{ client::{Client, RegionSubscription}, cmd::ChatCommandExt, login_provider::LoginProvider, + settings::{Banlist, EditableSetting, ServerDescription, Whitelist}, state_ext::StateExt, sys::sentinel::{DeletedEntities, TrackedComps}, }; @@ -102,10 +104,15 @@ impl Server { /// Create a new `Server` #[allow(clippy::expect_fun_call)] // TODO: Pending review in #587 #[allow(clippy::needless_update)] // TODO: Pending review in #587 - pub fn new(settings: ServerSettings) -> Result { + pub fn new(settings: ServerSettings, data_dir: DataDir) -> Result { + info!("Server is data dir is: {}", data_dir.path.display()); + + // persistence_db_dir is relative to data_dir + let persistence_db_dir = data_dir.path.join(&settings.persistence_db_dir); + // Run pending DB migrations (if any) debug!("Running DB migrations..."); - if let Some(e) = persistence::run_migrations(&settings.persistence_db_dir).err() { + if let Some(e) = persistence::run_migrations(&persistence_db_dir).err() { panic!("Migration error: {:?}", e); } @@ -116,6 +123,10 @@ impl Server { let mut state = State::default(); state.ecs_mut().insert(settings.clone()); + state.ecs_mut().insert(Whitelist::load(&data_dir.path)); + state.ecs_mut().insert(Banlist::load(&data_dir.path)); + state.ecs_mut().insert(ServerDescription::load(&data_dir.path)); + state.ecs_mut().insert(data_dir); state.ecs_mut().insert(EventBus::::default()); state .ecs_mut() @@ -128,10 +139,10 @@ impl Server { .insert(ChunkGenerator::new(chunk_gen_metrics)); state .ecs_mut() - .insert(CharacterUpdater::new(settings.persistence_db_dir.clone())?); + .insert(CharacterUpdater::new(&persistence_db_dir)?); state .ecs_mut() - .insert(CharacterLoader::new(settings.persistence_db_dir.clone())?); + .insert(CharacterLoader::new(&persistence_db_dir)?); state .ecs_mut() .insert(comp::AdminList(settings.admins.clone())); @@ -340,9 +351,10 @@ impl Server { pub fn get_server_info(&self) -> ServerInfo { let settings = self.state.ecs().fetch::(); + let server_description = self.state.ecs().fetch::(); ServerInfo { name: settings.server_name.clone(), - description: settings.server_description.clone(), + description: (**server_description).clone(), git_hash: common::util::GIT_HASH.to_string(), git_date: common::util::GIT_DATE.to_string(), auth_provider: settings.auth_server_address.clone(), @@ -360,8 +372,38 @@ impl Server { } /// Get a mutable reference to the server's settings - pub fn settings_mut(&mut self) -> impl DerefMut + '_ { - self.state.ecs_mut().fetch_mut::() + pub fn settings_mut(&self) -> impl DerefMut + '_ { + self.state.ecs().fetch_mut::() + } + + /// Get a mutable reference to the server's whitelist + pub fn whitelist_mut(&self) -> impl DerefMut + '_ { + self.state.ecs().fetch_mut::() + } + + /// Get a reference to the server's banlist + pub fn banlist(&self) -> impl Deref + '_ { + self.state.ecs().fetch::() + } + + /// Get a mutable reference to the server's banlist + pub fn banlist_mut(&self) -> impl DerefMut + '_ { + self.state.ecs().fetch_mut::() + } + + /// Get a reference to the server's description + pub fn server_description(&self) -> impl Deref + '_ { + self.state.ecs().fetch::() + } + + /// Get a mutable reference to the server's description + pub fn server_description_mut(&self) -> impl DerefMut + '_ { + self.state.ecs().fetch_mut::() + } + + /// Get path to the directory that the server info into + pub fn data_dir(&self) -> impl Deref + '_ { + self.state.ecs().fetch::() } /// Get a reference to the server's game state. diff --git a/server/src/persistence/character_loader.rs b/server/src/persistence/character_loader.rs index d0b667fb93..049a0c47c1 100644 --- a/server/src/persistence/character_loader.rs +++ b/server/src/persistence/character_loader.rs @@ -5,6 +5,7 @@ use crate::persistence::{ }; use common::character::{CharacterId, CharacterItem}; use crossbeam::{channel, channel::TryIter}; +use std::path::Path; use tracing::error; pub(crate) type CharacterListResult = Result, Error>; @@ -64,11 +65,11 @@ pub struct CharacterLoader { } impl CharacterLoader { - pub fn new(db_dir: String) -> diesel::QueryResult { + pub fn new(db_dir: &Path) -> diesel::QueryResult { let (update_tx, internal_rx) = channel::unbounded::(); let (internal_tx, update_rx) = channel::unbounded::(); - let mut conn = establish_connection(&db_dir)?; + let mut conn = establish_connection(db_dir)?; std::thread::spawn(move || { for request in internal_rx { diff --git a/server/src/persistence/character_updater.rs b/server/src/persistence/character_updater.rs index cb8b8ebe6f..493042d854 100644 --- a/server/src/persistence/character_updater.rs +++ b/server/src/persistence/character_updater.rs @@ -3,7 +3,7 @@ use common::{character::CharacterId, comp::item::ItemId}; use crate::persistence::{establish_connection, VelorenConnection}; use crossbeam::channel; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; use tracing::{error, trace}; pub type CharacterUpdateData = (comp::Stats, comp::Inventory, comp::Loadout); @@ -19,11 +19,11 @@ pub struct CharacterUpdater { } impl CharacterUpdater { - pub fn new(db_dir: String) -> diesel::QueryResult { + pub fn new(db_dir: &Path) -> diesel::QueryResult { let (update_tx, update_rx) = channel::unbounded::>(); - let mut conn = establish_connection(&db_dir)?; + let mut conn = establish_connection(db_dir)?; let handle = std::thread::spawn(move || { while let Ok(updates) = update_rx.recv() { diff --git a/server/src/persistence/mod.rs b/server/src/persistence/mod.rs index b7a87c199a..2c01dc38ce 100644 --- a/server/src/persistence/mod.rs +++ b/server/src/persistence/mod.rs @@ -19,7 +19,10 @@ extern crate diesel; use common::comp; use diesel::{connection::SimpleConnection, prelude::*}; use diesel_migrations::embed_migrations; -use std::{env, fs, path::PathBuf}; +use std::{ + env, fs, + path::{Path, PathBuf}, +}; use tracing::{info, warn}; /// A tuple of the components that are persisted to the DB for each character @@ -45,9 +48,9 @@ impl std::io::Write for TracingOut { } /// Runs any pending database migrations. This is executed during server startup -pub fn run_migrations(db_dir: &str) -> Result<(), diesel_migrations::RunMigrationsError> { +pub fn run_migrations(db_dir: &Path) -> Result<(), diesel_migrations::RunMigrationsError> { let db_dir = &apply_saves_dir_override(db_dir); - let _ = fs::create_dir(format!("{}/", db_dir)); + let _ = fs::create_dir(format!("{}/", db_dir.display())); embedded_migrations::run_with_output( &establish_connection(db_dir) @@ -91,9 +94,9 @@ impl<'a> core::ops::Deref for VelorenTransaction<'a> { fn deref(&self) -> &Self::Target { &self.0 } } -pub fn establish_connection(db_dir: &str) -> QueryResult { +pub fn establish_connection(db_dir: &Path) -> QueryResult { let db_dir = &apply_saves_dir_override(db_dir); - let database_url = format!("{}/db.sqlite", db_dir); + let database_url = format!("{}/db.sqlite", db_dir.display()); let connection = SqliteConnection::establish(&database_url) .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)); @@ -117,16 +120,13 @@ pub fn establish_connection(db_dir: &str) -> QueryResult { Ok(VelorenConnection(connection)) } -fn apply_saves_dir_override(db_dir: &str) -> String { +fn apply_saves_dir_override(db_dir: &Path) -> PathBuf { if let Some(saves_dir) = env::var_os("VELOREN_SAVES_DIR") { - let path = PathBuf::from(saves_dir.clone()); + let path = PathBuf::from(&saves_dir); if path.exists() || path.parent().map(|x| x.exists()).unwrap_or(false) { - // Only allow paths with valid unicode characters - if let Some(path) = path.to_str() { - return path.to_owned(); - } + return path; } warn!(?saves_dir, "VELOREN_SAVES_DIR points to an invalid path."); } - db_dir.to_string() + db_dir.to_owned() } diff --git a/server/src/settings.rs b/server/src/settings.rs index 6f32d83d51..bea63f1f31 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -1,12 +1,28 @@ +mod editable; + +pub use editable::EditableSetting; + use authc::Uuid; use hashbrown::HashMap; use portpicker::pick_unused_port; use serde::{Deserialize, Serialize}; -use std::{fs, io::prelude::*, net::SocketAddr, path::PathBuf, time::Duration}; +use std::{ + fs, + net::SocketAddr, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, + time::Duration, +}; use tracing::{error, warn}; use world::sim::FileOpts; const DEFAULT_WORLD_SEED: u32 = 59686; +//const CONFIG_DIR_ENV: &'static str = "VELOREN_SERVER_CONFIG"; +const /*DEFAULT_*/CONFIG_DIR: &'static str = "server_config"; +const SETTINGS_FILENAME: &'static str = "settings.ron"; +const WHITELIST_FILENAME: &'static str = "whitelist.ron"; +const BANLIST_FILENAME: &'static str = "banlist.ron"; +const SERVER_DESCRIPTION_FILENAME: &'static str = "description.ron"; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] @@ -18,14 +34,12 @@ pub struct ServerSettings { pub world_seed: u32, //pub pvp_enabled: bool, pub server_name: String, - pub server_description: String, pub start_time: f64, pub admins: Vec, - pub whitelist: Vec, - pub banlist: HashMap, /// When set to None, loads the default map file (if available); otherwise, /// uses the value of the file options to decide how to proceed. pub map_file: Option, + /// Relative paths are relative to the server data dir pub persistence_db_dir: String, pub max_view_distance: Option, pub banned_words_files: Vec, @@ -42,15 +56,12 @@ impl Default for ServerSettings { metrics_address: SocketAddr::from(([0; 4], 14005)), auth_server_address: Some("https://auth.veloren.net".into()), world_seed: DEFAULT_WORLD_SEED, - server_name: "Veloren Alpha".to_owned(), - server_description: "This is the best Veloren server.".to_owned(), + server_name: "Veloren Alpha".into(), max_players: 100, start_time: 9.0 * 3600.0, map_file: None, admins: Vec::new(), - whitelist: Vec::new(), - banlist: HashMap::new(), - persistence_db_dir: "saves".to_owned(), + persistence_db_dir: "saves".into(), max_view_distance: Some(30), banned_words_files: Vec::new(), max_player_group_size: 6, @@ -62,41 +73,53 @@ impl Default for ServerSettings { } impl ServerSettings { - #[allow(clippy::single_match)] // TODO: Pending review in #587 - pub fn load() -> Self { - let path = ServerSettings::get_settings_path(); + /// path: Directory that contains the server config directory + pub fn load(path: &Path) -> Self { + let path = Self::get_settings_path(path); - if let Ok(file) = fs::File::open(path) { + if let Ok(file) = fs::File::open(&path) { match ron::de::from_reader(file) { Ok(x) => x, Err(e) => { - warn!(?e, "Failed to parse setting file! Fallback to default"); - Self::default() + warn!( + ?e, + "Failed to parse setting file! Falling back to default settings and \ + creating a template file for you to migrate your current settings file" + ); + let default_settings = Self::default(); + let template_path = path.with_extension("template.ron"); + if let Err(e) = default_settings.save_to_file(&template_path) { + error!(?e, "Failed to create template settings file") + } + default_settings }, } } else { let default_settings = Self::default(); - match default_settings.save_to_file() { - Err(e) => error!(?e, "Failed to create default setting file!"), - _ => {}, + if let Err(e) = default_settings.save_to_file(&path) { + error!(?e, "Failed to create default settings file!"); } 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()) + fn save_to_file(&self, path: &Path) -> std::io::Result<()> { + // Create dir if it doesn't exist + if let Some(dir) = path.parent() { + fs::create_dir_all(dir)?; + } + let ron = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()) .expect("Failed serialize settings."); - config_file.write_all(s.as_bytes())?; + + fs::write(path, ron.as_bytes())?; + Ok(()) } - pub fn singleplayer(persistence_db_dir: String) -> Self { - let load = Self::load(); + /// path: Directory that contains the server config directory + pub fn singleplayer(path: &Path) -> Self { + let load = Self::load(&path); Self { //BUG: theoretically another process can grab the port between here and server // creation, however the timewindow is quite small @@ -116,24 +139,90 @@ impl ServerSettings { DEFAULT_WORLD_SEED }, server_name: "Singleplayer".to_owned(), - server_description: "Who needs friends anyway?".to_owned(), + //server_description: "Who needs friends anyway?".to_owned(), max_players: 100, start_time: 9.0 * 3600.0, admins: vec!["singleplayer".to_string()], /* TODO: Let the player choose if they want * to use admin commands or not */ - persistence_db_dir, max_view_distance: None, client_timeout: Duration::from_secs(180), ..load // Fill in remaining fields from server_settings.ron. } } - fn get_settings_path() -> PathBuf { PathBuf::from(r"server_settings.ron") } - - pub fn edit(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { - let r = f(self); - self.save_to_file() - .unwrap_or_else(|err| warn!("Failed to save settings: {:?}", err)); - r + fn get_settings_path(path: &Path) -> PathBuf { + let mut path = with_config_dir(path); + path.push(SETTINGS_FILENAME); + path } } + +fn with_config_dir(path: &Path) -> PathBuf { + let mut path = PathBuf::from(path); + //if let Some(path) = std::env::var_os(CONFIG_DIR_ENV) { + // let config_dir = PathBuf::from(path); + // if config_dir.exists() { + // return config_dir; + // } + // warn!(?path, "VELROREN_SERVER_CONFIG points to invalid path."); + //} + path.push(/* DEFAULT_ */ CONFIG_DIR); + //PathBuf::from(DEFAULT_CONFIG_DIR) + path +} + +#[derive(Deserialize, Serialize, Default)] +#[serde(transparent)] +pub struct Whitelist(Vec); +#[derive(Deserialize, Serialize, Default)] +#[serde(transparent)] +pub struct Banlist(HashMap); +#[derive(Deserialize, Serialize)] +#[serde(transparent)] +pub struct ServerDescription(String); + +impl Default for ServerDescription { + fn default() -> Self { Self("This is the best Veloren server".into()) } +} + +impl EditableSetting for Whitelist { + const FILENAME: &'static str = WHITELIST_FILENAME; +} + +impl EditableSetting for Banlist { + const FILENAME: &'static str = BANLIST_FILENAME; +} + +impl EditableSetting for ServerDescription { + const FILENAME: &'static str = SERVER_DESCRIPTION_FILENAME; +} + +impl Deref for Whitelist { + type Target = Vec; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for Whitelist { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +impl Deref for Banlist { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for Banlist { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +impl Deref for ServerDescription { + type Target = String; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for ServerDescription { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} diff --git a/server/src/settings/editable.rs b/server/src/settings/editable.rs new file mode 100644 index 0000000000..ce8ff36431 --- /dev/null +++ b/server/src/settings/editable.rs @@ -0,0 +1,89 @@ +use serde::{de::DeserializeOwned, Serialize}; +use std::{ + fs, + path::{Path, PathBuf}, +}; +use tracing::{error, warn}; + +pub trait EditableSetting: Serialize + DeserializeOwned + Default { + const FILENAME: &'static str; + + fn load(data_dir: &Path) -> Self { + let path = Self::get_path(data_dir); + + if let Ok(file) = fs::File::open(&path) { + match ron::de::from_reader(file) { + Ok(setting) => setting, + Err(e) => { + warn!( + ?e, + "Failed to parse setting file! Falling back to default and moving \ + existing file to a .invalid" + ); + + // Rename existing file to .invalid.ron + let mut new_path = path.with_extension("invalid.ron"); + + // If invalid path already exists append number + for i in 1.. { + if !new_path.exists() { + break; + } + + warn!( + ?new_path, + "Path to move invalid settings exists, appending number" + ); + new_path = path.with_extension(format!("invalid{}.ron", i)); + } + + warn!("Renaming invalid settings file to: {}", path.display()); + if let Err(e) = fs::rename(&path, &new_path) { + warn!(?e, ?path, ?new_path, "Failed to rename settings file."); + } + + create_and_save_default(&path) + }, + } + } else { + create_and_save_default(&path) + } + } + + fn edit(&mut self, data_dir: &Path, f: impl FnOnce(&mut Self) -> R) -> R { + let path = Self::get_path(data_dir); + + let r = f(self); + save_to_file(&*self, &path) + .unwrap_or_else(|err| warn!("Failed to save setting: {:?}", err)); + r + } + + fn get_path(data_dir: &Path) -> PathBuf { + let mut path = super::with_config_dir(data_dir); + path.push(Self::FILENAME); + path + } +} + +fn save_to_file(setting: &S, path: &Path) -> std::io::Result<()> { + // Create dir if it doesn't exist + if let Some(dir) = path.parent() { + fs::create_dir_all(dir)?; + } + + let ron = ron::ser::to_string_pretty(setting, ron::ser::PrettyConfig::default()) + .expect("Failed serialize setting."); + + fs::write(path, ron.as_bytes())?; + + Ok(()) +} + +fn create_and_save_default(path: &Path) -> S { + let default = S::default(); + if let Err(e) = save_to_file(&default, path) { + error!(?e, "Failed to create default setting file!"); + } + default +} diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 34fcd9fad8..557f8b603b 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -6,6 +6,7 @@ use crate::{ login_provider::LoginProvider, metrics::{NetworkRequestMetrics, PlayerMetrics}, persistence::character_loader::CharacterLoader, + settings::{Banlist, ServerDescription, Whitelist}, ServerSettings, }; use common::{ @@ -66,6 +67,9 @@ impl Sys { controllers: &mut WriteStorage<'_, Controller>, settings: &Read<'_, ServerSettings>, alias_validator: &ReadExpect<'_, AliasValidator>, + whitelist: &Whitelist, + banlist: &Banlist, + server_description: &ServerDescription, ) -> Result<(), crate::error::Error> { loop { let msg = client.recv().await?; @@ -96,17 +100,14 @@ impl Sys { view_distance, token_or_username, } => { - let (username, uuid) = match login_provider.try_login( - &token_or_username, - &settings.whitelist, - &settings.banlist, - ) { - Err(err) => { - client.error_state(RequestStateError::RegisterDenied(err)); - break Ok(()); - }, - Ok((username, uuid)) => (username, uuid), - }; + let (username, uuid) = + match login_provider.try_login(&token_or_username, &whitelist, &banlist) { + Err(err) => { + client.error_state(RequestStateError::RegisterDenied(err)); + break Ok(()); + }, + Ok((username, uuid)) => (username, uuid), + }; let vd = view_distance.map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd))); @@ -206,10 +207,9 @@ impl Sys { }); // Give the player a welcome message - if !settings.server_description.is_empty() { + if !server_description.is_empty() { client.notify( - ChatType::CommandInfo - .server_msg(settings.server_description.clone()), + ChatType::CommandInfo.server_msg(String::from(&**server_description)), ); } @@ -450,6 +450,11 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Controller>, Read<'a, ServerSettings>, ReadExpect<'a, AliasValidator>, + ( + ReadExpect<'a, Whitelist>, + ReadExpect<'a, Banlist>, + ReadExpect<'a, ServerDescription> + ), ); #[allow(clippy::match_ref_pats)] // TODO: Pending review in #587 @@ -483,6 +488,11 @@ impl<'a> System<'a> for Sys { mut controllers, settings, alias_validator, + ( + whitelist, + banlist, + server_description, + ), ): Self::SystemData, ) { span!(_guard, "run", "message::Sys::run"); @@ -543,6 +553,9 @@ impl<'a> System<'a> for Sys { &mut controllers, &settings, &alias_validator, + &whitelist, + &banlist, + &server_description, ); select!( _ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()), diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index a6a0cbad51..ceb2da01c2 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -53,6 +53,7 @@ chrono = "0.4.9" cpal = "0.11" crossbeam = "=0.7.2" deunicode = "1.0" +# TODO: remove directories-next = "1.0.1" dot_vox = "4.0" enum-iterator = "0.6" diff --git a/voxygen/src/profile.rs b/voxygen/src/profile.rs index e94291404c..34990f1de2 100644 --- a/voxygen/src/profile.rs +++ b/voxygen/src/profile.rs @@ -1,9 +1,8 @@ -use crate::hud; +use crate::{hud, settings}; use common::character::CharacterId; -use directories_next::ProjectDirs; use hashbrown::HashMap; use serde_derive::{Deserialize, Serialize}; -use std::{fs, io::prelude::*, path::PathBuf}; +use std::{fs, io::Write, path::PathBuf}; use tracing::warn; /// Represents a character in the profile. @@ -177,13 +176,9 @@ impl Profile { warn!(?path, "VOXYGEN_CONFIG points to invalid path."); } - let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen") - .expect("System's $HOME directory path not found!"); - - proj_dirs - .config_dir() - .join("profile.ron") - .with_extension("ron") + let mut path = settings::voxygen_data_dir(); + path.push("profile.ron"); + path } } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 6d49acd4b3..5acdca42df 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -5,7 +5,7 @@ use crate::{ ui::ScaleMode, window::{FullScreenSettings, GameInput, KeyMouse}, }; -use directories_next::{ProjectDirs, UserDirs}; +use directories_next::UserDirs; use hashbrown::{HashMap, HashSet}; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, path::PathBuf}; @@ -584,18 +584,18 @@ pub struct Log { } impl Default for Log { - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 fn default() -> Self { - let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen") - .expect("System's $HOME directory path not found!"); - // Chooses a path to store the logs by the following order: // - The VOXYGEN_LOGS environment variable // - The ProjectsDirs data local directory // This only selects if there isn't already an entry in the settings file let logs_path = std::env::var_os("VOXYGEN_LOGS") .map(PathBuf::from) - .unwrap_or(proj_dirs.data_local_dir().join("logs")); + .unwrap_or_else(|| { + let mut path = voxygen_data_dir(); + path.push("logs"); + path + }); Self { log_to_file: true, @@ -718,8 +718,6 @@ pub struct Settings { } impl Default for Settings { - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 - fn default() -> Self { let user_dirs = UserDirs::new().expect("System's $HOME directory path not found!"); @@ -730,10 +728,12 @@ impl Default for Settings { // This only selects if there isn't already an entry in the settings file let screenshots_path = std::env::var_os("VOXYGEN_SCREENSHOT") .map(PathBuf::from) - .or(user_dirs.picture_dir().map(|dir| dir.join("veloren"))) - .or(std::env::current_exe() - .ok() - .and_then(|dir| dir.parent().map(PathBuf::from))) + .or_else(|| user_dirs.picture_dir().map(|dir| dir.join("veloren"))) + .or_else(|| { + std::env::current_exe() + .ok() + .and_then(|dir| dir.parent().map(PathBuf::from)) + }) .expect("Couldn't choose a place to store the screenshots"); Settings { @@ -766,7 +766,7 @@ impl Settings { let mut new_path = path.to_owned(); new_path.pop(); new_path.push("settings.invalid.ron"); - if let Err(e) = std::fs::rename(path.clone(), new_path.clone()) { + if let Err(e) = std::fs::rename(&path, &new_path) { warn!(?e, ?path, ?new_path, "Failed to rename settings file."); } }, @@ -800,18 +800,23 @@ impl Settings { pub fn get_settings_path() -> PathBuf { if let Some(path) = std::env::var_os("VOXYGEN_CONFIG") { - let settings = PathBuf::from(path.clone()).join("settings.ron"); + let settings = PathBuf::from(&path).join("settings.ron"); if settings.exists() || settings.parent().map(|x| x.exists()).unwrap_or(false) { return settings; } warn!(?path, "VOXYGEN_CONFIG points to invalid path."); } - let proj_dirs = ProjectDirs::from("net", "veloren", "voxygen") - .expect("System's $HOME directory path not found!"); - proj_dirs - .config_dir() - .join("settings") - .with_extension("ron") + let mut path = voxygen_data_dir(); + path.push("settings.ron"); + path } } + +pub fn voxygen_data_dir() -> PathBuf { + // Note: since voxygen is technically a lib we made need to lift this up to + // run.rs + let mut path = common::userdata_dir_workspace!(); + path.push("voxygen"); + path +} diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index a9892cf594..76d4e24549 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -1,7 +1,7 @@ use client::Client; use common::clock::Clock; use crossbeam::channel::{bounded, unbounded, Receiver, Sender, TryRecvError}; -use server::{Error as ServerError, Event, Input, Server, ServerSettings}; +use server::{DataDir, Error as ServerError, Event, Input, Server, ServerSettings}; use std::{ sync::{ atomic::{AtomicBool, Ordering}, @@ -32,15 +32,15 @@ impl Singleplayer { pub fn new(client: Option<&Client>) -> (Self, ServerSettings) { let (sender, receiver) = unbounded(); + // Determine folder to save server data in + let server_data_dir = DataDir::from({ + let mut path = common::userdata_dir_workspace!(); + path.push("singleplayer"); + path + }); + // Create server - let settings = ServerSettings::singleplayer( - crate::settings::Settings::get_settings_path() - .parent() - .unwrap() - .join("saves") - .to_string_lossy() - .to_string(), - ); + let settings = ServerSettings::singleplayer(server_data_dir.as_ref()); let thread_pool = client.map(|c| c.thread_pool().clone()); let settings2 = settings.clone(); @@ -52,7 +52,7 @@ impl Singleplayer { let thread = thread::spawn(move || { let mut server = None; - if let Err(e) = result_sender.send(match Server::new(settings2) { + if let Err(e) = result_sender.send(match Server::new(settings2, server_data_dir) { Ok(s) => { server = Some(s); Ok(()) From 308cca0dc99996c9762964fb7d74f25935a72d1e Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 5 Oct 2020 04:35:24 -0400 Subject: [PATCH 07/15] Put server-cli specific settings in their own structure, serde_derive -> serde --- Cargo.lock | 3 +- server-cli/Cargo.toml | 2 + server-cli/src/main.rs | 18 ++++--- server-cli/src/settings.rs | 75 ++++++++++++++++++++++++++ server-cli/src/shutdown_coordinator.rs | 11 ++-- server/src/settings.rs | 4 -- voxygen/Cargo.toml | 3 +- voxygen/src/controller.rs | 2 +- voxygen/src/hud/hotbar.rs | 2 +- voxygen/src/hud/item_imgs.rs | 2 +- voxygen/src/i18n.rs | 2 +- voxygen/src/meta.rs | 2 +- voxygen/src/profile.rs | 2 +- voxygen/src/render/mod.rs | 2 +- voxygen/src/scene/figure/load.rs | 2 +- voxygen/src/settings.rs | 16 +++--- voxygen/src/ui/scale.rs | 2 +- voxygen/src/window.rs | 2 +- 18 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 server-cli/src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 95b1746eff..d797ede87e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4713,6 +4713,8 @@ dependencies = [ "clap", "crossterm", "lazy_static", + "ron", + "serde", "signal-hook", "tracing", "tracing-subscriber", @@ -4761,7 +4763,6 @@ dependencies = [ "rodio", "ron", "serde", - "serde_derive", "specs", "specs-idvs", "tracing", diff --git a/server-cli/Cargo.toml b/server-cli/Cargo.toml index 236c23f79f..b35d06d537 100644 --- a/server-cli/Cargo.toml +++ b/server-cli/Cargo.toml @@ -20,6 +20,8 @@ lazy_static = "1" signal-hook = "0.1.16" tracing = { version = "0.1", default-features = false } tracing-subscriber = { version = "0.2.3", default-features = false, features = ["env-filter", "fmt", "chrono", "ansi", "smallvec"] } +ron = {version = "0.6", default-features = false} +serde = {version = "1.0", features = [ "rc", "derive" ]} # Tracy tracing-tracy = { version = "0.2.0", optional = true } diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 6cf6cf3641..7c54e03843 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -3,6 +3,7 @@ #![feature(bool_to_option)] mod logging; +mod settings; mod shutdown_coordinator; mod tui_runner; mod tuilog; @@ -58,6 +59,9 @@ fn main() -> io::Result<()> { logging::init(basic); + // Load settings + let settings = settings::Settings::load(); + // Panic hook to ensure that console mode is set back correctly if in non-basic // mode let hook = std::panic::take_hook(); @@ -80,18 +84,18 @@ fn main() -> io::Result<()> { path }); - // Load settings - let mut settings = ServerSettings::load(server_data_dir.as_ref()); + // Load server settings + let mut server_settings = ServerSettings::load(server_data_dir.as_ref()); if no_auth { - settings.auth_server_address = None; + server_settings.auth_server_address = None; } - let server_port = &settings.gameserver_address.port(); - let metrics_port = &settings.metrics_address.port(); + let server_port = &server_settings.gameserver_address.port(); + let metrics_port = &server_settings.metrics_address.port(); // Create server let mut server = - Server::new(settings, server_data_dir).expect("Failed to create server instance!"); + Server::new(server_settings, server_data_dir).expect("Failed to create server instance!"); info!( ?server_port, @@ -103,7 +107,7 @@ fn main() -> io::Result<()> { loop { // Terminate the server if instructed to do so by the shutdown coordinator - if shutdown_coordinator.check(&mut server) { + if shutdown_coordinator.check(&mut server, &settings) { break; } diff --git a/server-cli/src/settings.rs b/server-cli/src/settings.rs new file mode 100644 index 0000000000..3cca9ec466 --- /dev/null +++ b/server-cli/src/settings.rs @@ -0,0 +1,75 @@ +use serde::{Deserialize, Serialize}; +use std::{fs, path::PathBuf}; +use tracing::warn; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct Settings { + pub update_shutdown_grace_period_secs: u32, + pub update_shutdown_message: String, +} + +impl Default for Settings { + fn default() -> Self { + Self { + update_shutdown_grace_period_secs: 120, + update_shutdown_message: "The server is restarting for an update".to_owned(), + } + } +} + +impl Settings { + pub fn load() -> Self { + let path = Self::get_settings_path(); + + if let Ok(file) = fs::File::open(&path) { + match ron::de::from_reader(file) { + Ok(s) => return s, + Err(e) => { + warn!(?e, "Failed to parse setting file! Fallback to default."); + // Rename the corrupted settings file + let mut new_path = path.to_owned(); + new_path.pop(); + new_path.push("settings.invalid.ron"); + if let Err(e) = std::fs::rename(&path, &new_path) { + warn!(?e, ?path, ?new_path, "Failed to rename settings file."); + } + }, + } + } + // This is reached if either: + // - The file can't be opened (presumably it doesn't exist) + // - Or there was an error parsing the file + let default_settings = Self::default(); + default_settings.save_to_file_warn(); + default_settings + } + + pub fn save_to_file_warn(&self) { + if let Err(e) = self.save_to_file() { + warn!(?e, "Failed to save settings"); + } + } + + fn save_to_file(&self) -> std::io::Result<()> { + let path = Self::get_settings_path(); + if let Some(dir) = path.parent() { + fs::create_dir_all(dir)?; + } + + let ron = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap(); + fs::write(path, ron.as_bytes()) + } + + pub fn get_settings_path() -> PathBuf { + let mut path = data_dir(); + path.push("settings.ron"); + path + } +} + +pub fn data_dir() -> PathBuf { + let mut path = common::userdata_dir_workspace!(); + path.push("server-cli"); + path +} diff --git a/server-cli/src/shutdown_coordinator.rs b/server-cli/src/shutdown_coordinator.rs index 1716ea1f04..59705c2003 100644 --- a/server-cli/src/shutdown_coordinator.rs +++ b/server-cli/src/shutdown_coordinator.rs @@ -1,3 +1,4 @@ +use crate::settings::Settings; use common::comp::chat::ChatType; use server::Server; use std::{ @@ -78,9 +79,9 @@ impl ShutdownCoordinator { /// shutdown. If the grace period for an initiated shutdown has expired, /// returns `true` which triggers the loop in `main.rs` to break and /// exit the server process. - pub fn check(&mut self, server: &mut Server) -> bool { + pub fn check(&mut self, server: &mut Server, settings: &Settings) -> bool { // Check whether SIGUSR1 has been set - self.check_sigusr1_signal(server); + self.check_sigusr1_signal(server, settings); // If a shutdown is in progress, check whether it's time to send another warning // message or shut down if the grace period has expired. @@ -112,12 +113,12 @@ impl ShutdownCoordinator { /// Veloren server to send SIGUSR1 instead of SIGTERM which allows us to /// react specifically to shutdowns that are for an update. /// NOTE: SIGUSR1 is not supported on Windows - fn check_sigusr1_signal(&mut self, server: &mut Server) { + fn check_sigusr1_signal(&mut self, server: &mut Server, settings: &Settings) { if self.sigusr1_signal.load(Ordering::Relaxed) && self.shutdown_initiated_at.is_none() { info!("Received SIGUSR1 signal, initiating graceful shutdown"); let grace_period = - Duration::from_secs(server.settings().update_shutdown_grace_period_secs); - let shutdown_message = server.settings().update_shutdown_message.to_owned(); + Duration::from_secs(u64::from(settings.update_shutdown_grace_period_secs)); + let shutdown_message = settings.update_shutdown_message.to_owned(); self.initiate_shutdown(server, grace_period, shutdown_message); // Reset the SIGUSR1 signal indicator in case shutdown is aborted and we need to diff --git a/server/src/settings.rs b/server/src/settings.rs index bea63f1f31..c03bfbd72c 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -45,8 +45,6 @@ pub struct ServerSettings { pub banned_words_files: Vec, pub max_player_group_size: u32, pub client_timeout: Duration, - pub update_shutdown_grace_period_secs: u64, - pub update_shutdown_message: String, } impl Default for ServerSettings { @@ -66,8 +64,6 @@ impl Default for ServerSettings { banned_words_files: Vec::new(), max_player_group_size: 6, client_timeout: Duration::from_secs(40), - update_shutdown_grace_period_secs: 120, - update_shutdown_message: "The server is restarting for an update".to_owned(), } } } diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index ceb2da01c2..49c2136acf 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -67,8 +67,7 @@ num = "0.2" rand = "0.7" rodio = {version = "0.11", default-features = false, features = ["wav", "vorbis"]} ron = {version = "0.6", default-features = false} -serde = {version = "1.0", features = [ "rc" ]} -serde_derive = "1.0" +serde = {version = "1.0", features = [ "rc", "derive" ]} treeculler = "0.1.0" uvth = "3.1.1" # vec_map = { version = "0.8.2" } diff --git a/voxygen/src/controller.rs b/voxygen/src/controller.rs index 44b286c2e2..86f3020a3f 100644 --- a/voxygen/src/controller.rs +++ b/voxygen/src/controller.rs @@ -4,7 +4,7 @@ use crate::window::{GameInput, MenuInput}; use gilrs::{ev::Code as GilCode, Axis as GilAxis, Button as GilButton}; use hashbrown::HashMap; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; /// Contains all controller related settings and keymaps #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs index ab02df0c70..321b6451d2 100644 --- a/voxygen/src/hud/hotbar.rs +++ b/voxygen/src/hud/hotbar.rs @@ -1,4 +1,4 @@ -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, PartialEq)] pub enum Slot { diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index 12de1f50cf..b86fa53eb8 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -12,7 +12,7 @@ use conrod_core::image::Id; use dot_vox::DotVoxData; use hashbrown::HashMap; use image::DynamicImage; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::{fs::File, io::BufReader, sync::Arc}; use tracing::{error, warn}; use vek::*; diff --git a/voxygen/src/i18n.rs b/voxygen/src/i18n.rs index ca554f4d5b..479fef3121 100644 --- a/voxygen/src/i18n.rs +++ b/voxygen/src/i18n.rs @@ -1,7 +1,7 @@ use common::assets::{self, Asset}; use deunicode::deunicode; use ron::de::from_reader; -use serde_derive::*; +use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, fs::File, diff --git a/voxygen/src/meta.rs b/voxygen/src/meta.rs index 30ef1a3c7b..4794e6725d 100644 --- a/voxygen/src/meta.rs +++ b/voxygen/src/meta.rs @@ -1,6 +1,6 @@ use common::comp; use directories::ProjectDirs; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::{fs, io::Write, path::PathBuf}; use tracing::warn; diff --git a/voxygen/src/profile.rs b/voxygen/src/profile.rs index 34990f1de2..dbb7267b11 100644 --- a/voxygen/src/profile.rs +++ b/voxygen/src/profile.rs @@ -1,7 +1,7 @@ use crate::{hud, settings}; use common::character::CharacterId; use hashbrown::HashMap; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::{fs, io::Write, path::PathBuf}; use tracing::warn; diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 1c747e78d0..a7c36497e5 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -62,7 +62,7 @@ pub trait Pipeline { type Vertex: Clone + gfx::traits::Pod + gfx::pso::buffer::Structure; } -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; /// Anti-aliasing modes #[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] pub enum AaMode { diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index bc87ab9312..32c3c997d4 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -20,7 +20,7 @@ use common::{ }; use dot_vox::DotVoxData; use hashbrown::HashMap; -use serde_derive::Deserialize; +use serde::Deserialize; use std::sync::Arc; use tracing::{error, warn}; use vek::*; diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 5acdca42df..be4eed60db 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -7,8 +7,8 @@ use crate::{ }; use directories_next::UserDirs; use hashbrown::{HashMap, HashSet}; -use serde_derive::{Deserialize, Serialize}; -use std::{fs, io::prelude::*, path::PathBuf}; +use serde::{Deserialize, Serialize}; +use std::{fs, path::PathBuf}; use tracing::warn; use winit::event::{MouseButton, VirtualKeyCode}; @@ -291,7 +291,7 @@ impl Default for GamepadSettings { pub mod con_settings { use crate::controller::*; use gilrs::{Axis as GilAxis, Button as GilButton}; - use serde_derive::{Deserialize, Serialize}; + use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] @@ -755,7 +755,7 @@ impl Default for Settings { impl Settings { pub fn load() -> Self { - let path = Settings::get_settings_path(); + let path = Self::get_settings_path(); if let Ok(file) = fs::File::open(&path) { match ron::de::from_reader(file) { @@ -787,15 +787,13 @@ impl Settings { } pub fn save_to_file(&self) -> std::io::Result<()> { - let path = Settings::get_settings_path(); + let path = Self::get_settings_path(); if let Some(dir) = path.parent() { fs::create_dir_all(dir)?; } - 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(()) + let ron = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap(); + fs::write(path, ron.as_bytes()) } pub fn get_settings_path() -> PathBuf { diff --git a/voxygen/src/ui/scale.rs b/voxygen/src/ui/scale.rs index 01eb21afba..2845585b77 100644 --- a/voxygen/src/ui/scale.rs +++ b/voxygen/src/ui/scale.rs @@ -1,5 +1,5 @@ use crate::{render::Renderer, window::Window}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use vek::*; /// Type of scaling to use. diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index d0215f2934..f9007e43af 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -10,7 +10,7 @@ use gilrs::{EventType, Gilrs}; use hashbrown::HashMap; use itertools::Itertools; use old_school_gfx_glutin_ext::{ContextBuilderExt, WindowInitExt, WindowUpdateExt}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::fmt; use tracing::{error, info, warn}; use vek::*; From 522880a0abe23db7b5591f6df8ed0a3d03d26b74 Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 5 Oct 2020 04:48:29 -0400 Subject: [PATCH 08/15] Update CI and docker files to work with changes to data folder structure --- .gitlab/CI/build.gitlab-ci.yml | 6 +++--- server-cli/docker-compose.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab/CI/build.gitlab-ci.yml b/.gitlab/CI/build.gitlab-ci.yml index 011dda3f81..0a3f2bc637 100644 --- a/.gitlab/CI/build.gitlab-ci.yml +++ b/.gitlab/CI/build.gitlab-ci.yml @@ -34,7 +34,7 @@ benchmarks: .tlinux: script: - ln -s /dockercache/cache-release-linux target - - cargo build --release + - VELOREN_USERDATA_STRATEGY=executable cargo build --release - cp -r target/release/veloren-server-cli $CI_PROJECT_DIR - cp -r target/release/veloren-voxygen $CI_PROJECT_DIR artifacts: @@ -48,7 +48,7 @@ benchmarks: .twindows: script: - ln -s /dockercache/cache-release-windows target - - cargo build --target=x86_64-pc-windows-gnu --release + - VELOREN_USERDATA_STRATEGY=executable cargo build --target=x86_64-pc-windows-gnu --release - cp -r target/x86_64-pc-windows-gnu/release/veloren-server-cli.exe $CI_PROJECT_DIR - cp -r target/x86_64-pc-windows-gnu/release/veloren-voxygen.exe $CI_PROJECT_DIR artifacts: @@ -62,7 +62,7 @@ benchmarks: .tmacos: script: - ln -s /dockercache/cache-release-macos target - - PATH="/dockercache/osxcross/target/bin:$PATH" COREAUDIO_SDK_PATH=/dockercache/osxcross/target/SDK/MacOSX10.13.sdk CC=o64-clang CXX=o64-clang++ cargo build --target x86_64-apple-darwin --release + - VELOREN_USERDATA_STRATEGY=executable PATH="/dockercache/osxcross/target/bin:$PATH" COREAUDIO_SDK_PATH=/dockercache/osxcross/target/SDK/MacOSX10.13.sdk CC=o64-clang CXX=o64-clang++ cargo build --target x86_64-apple-darwin --release - cp -r target/x86_64-apple-darwin/release/veloren-server-cli $CI_PROJECT_DIR - cp -r target/x86_64-apple-darwin/release/veloren-voxygen $CI_PROJECT_DIR artifacts: diff --git a/server-cli/docker-compose.yml b/server-cli/docker-compose.yml index 355ea0f304..93baaab42a 100644 --- a/server-cli/docker-compose.yml +++ b/server-cli/docker-compose.yml @@ -9,7 +9,7 @@ services: - "14005:14005" restart: on-failure:0 volumes: - - "./saves:/opt/saves" + - "./userdata:/opt/userdata" environment: - RUST_LOG=debug,common::net=info watchtower: From fb2cf1a2928d86f64a8392fc4e2519804cafec88 Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 5 Oct 2020 04:56:28 -0400 Subject: [PATCH 09/15] Fix clippy warnings --- CHANGELOG.md | 2 +- common/src/sys/projectile.rs | 4 ++-- common/src/util/userdata_dir.rs | 23 +++++++++++------------ server-cli/src/tui_runner.rs | 2 +- server/src/data_dir.rs | 2 +- server/src/lib.rs | 16 ++++++++++++---- server/src/settings.rs | 10 +++++----- server/src/sys/message.rs | 11 ++++------- 8 files changed, 37 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcaeaea728..4db936c888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Overhauled representation of blocks to permit fluid and sprite coexistence - Overhauled sword - Reworked healing sceptre -- Split out the sections of the server settings that can be edtited and saved by the server. +- Split out the sections of the server settings that can be edited and saved by the server. - Revamped structure of where settings, logs, and game saves are stored so that almost everything is in one place. ### Removed diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index ba7ece43d1..ffa55c286d 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -1,6 +1,6 @@ use crate::{ comp::{ - projectile, Damage, DamageSource, Energy, EnergySource, HealthChange, HealthSource, Group, + projectile, Damage, DamageSource, Energy, EnergySource, Group, HealthChange, HealthSource, Loadout, Ori, PhysicsState, Pos, Projectile, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, @@ -82,7 +82,7 @@ impl<'a> System<'a> for Sys { // if there is at least one touching entity .and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into())) .and_then(|e| groups.get(e)) - .map_or(false, |owner_group| + .map_or(false, |owner_group| Some(owner_group) == uid_allocator .retrieve_entity_internal(other.into()) .and_then(|e| groups.get(e)) diff --git a/common/src/util/userdata_dir.rs b/common/src/util/userdata_dir.rs index 24b7b304e4..81a9ae90ca 100644 --- a/common/src/util/userdata_dir.rs +++ b/common/src/util/userdata_dir.rs @@ -1,9 +1,9 @@ use std::path::PathBuf; -const VELOREN_USERDATA_ENV: &'static str = "VELOREN_USERDATA"; +const VELOREN_USERDATA_ENV: &str = "VELOREN_USERDATA"; -// TODO: consider expanding this to a general install strategy variable that is also used for -// finding assets +// TODO: consider expanding this to a general install strategy variable that is +// also used for finding assets /// # `VELOREN_USERDATA_STRATEGY` environment variable /// Read during compilation /// Useful to set when compiling for distribution @@ -15,8 +15,8 @@ const VELOREN_USERDATA_ENV: &'static str = "VELOREN_USERDATA"; /// The first specified in this list is used /// 1. The VELOREN_USERDATA environment variable /// 2. The VELOREN_USERDATA_STRATEGY environment variable -/// 3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata depending on if a -/// workspace if being used +/// 3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata +/// depending on if a workspace if being used pub fn userdata_dir(workspace: bool, strategy: Option<&str>, manifest_dir: &str) -> PathBuf { // 1. The VELOREN_USERDATA environment variable std::env::var_os(VELOREN_USERDATA_ENV) @@ -39,11 +39,10 @@ pub fn userdata_dir(workspace: bool, strategy: Option<&str>, manifest_dir: &str) }, Some(_) => None, // TODO: panic? catch during compilation? _ => None, - }) // 3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata depending on if a // workspace if being used - .unwrap_or_else(|| { + .unwrap_or_else(|| { let mut path = PathBuf::from(manifest_dir); if workspace { path.pop(); @@ -58,7 +57,8 @@ macro_rules! userdata_dir_workspace { () => { $crate::util::userdata_dir::userdata_dir( true, - option_env!("VELOREN_USERDATA_STRATEGY"), env!("CARGO_MANIFEST_DIR") + option_env!("VELOREN_USERDATA_STRATEGY"), + env!("CARGO_MANIFEST_DIR"), ) }; } @@ -68,9 +68,8 @@ macro_rules! userdata_dir_no_workspace { () => { $crate::util::userdata_dir::userdata_dir( false, - option_env!("VELOREN_USERDATA_STRATEGY"), env!("CARGO_MANIFEST_DIR") + option_env!("VELOREN_USERDATA_STRATEGY"), + env!("CARGO_MANIFEST_DIR"), ) }; -} - - +} diff --git a/server-cli/src/tui_runner.rs b/server-cli/src/tui_runner.rs index 1c17788ecd..b3bc502583 100644 --- a/server-cli/src/tui_runner.rs +++ b/server-cli/src/tui_runner.rs @@ -122,7 +122,7 @@ impl Tui { pub fn run(basic: bool) -> Self { let (mut msg_s, msg_r) = mpsc::channel(); let running = Arc::new(AtomicBool::new(true)); - let running2 = running.clone(); + let running2 = Arc::clone(&running); let background = if basic { std::thread::spawn(move || { diff --git a/server/src/data_dir.rs b/server/src/data_dir.rs index c934638d7c..3c134ca870 100644 --- a/server/src/data_dir.rs +++ b/server/src/data_dir.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; /// Used so that different server frontends can share the same server saves, /// etc. -pub const DEFAULT_DATA_DIR_NAME: &'static str = "server"; +pub const DEFAULT_DATA_DIR_NAME: &str = "server"; /// Indicates where maps, saves, and server_config folders are to be stored pub struct DataDir { diff --git a/server/src/lib.rs b/server/src/lib.rs index 21d16408cc..9bb8ad3acd 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -6,10 +6,10 @@ pub mod alias_validator; mod character_creator; -mod data_dir; pub mod chunk_generator; pub mod client; pub mod cmd; +mod data_dir; pub mod error; pub mod events; pub mod input; @@ -22,7 +22,13 @@ pub mod sys; #[cfg(not(feature = "worldgen"))] mod test_world; // Reexports -pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings, data_dir::{DataDir, DEFAULT_DATA_DIR_NAME}}; +pub use crate::{ + data_dir::{DataDir, DEFAULT_DATA_DIR_NAME}, + error::Error, + events::Event, + input::Input, + settings::ServerSettings, +}; use crate::{ alias_validator::AliasValidator, @@ -106,7 +112,7 @@ impl Server { #[allow(clippy::needless_update)] // TODO: Pending review in #587 pub fn new(settings: ServerSettings, data_dir: DataDir) -> Result { info!("Server is data dir is: {}", data_dir.path.display()); - + // persistence_db_dir is relative to data_dir let persistence_db_dir = data_dir.path.join(&settings.persistence_db_dir); @@ -125,7 +131,9 @@ impl Server { state.ecs_mut().insert(settings.clone()); state.ecs_mut().insert(Whitelist::load(&data_dir.path)); state.ecs_mut().insert(Banlist::load(&data_dir.path)); - state.ecs_mut().insert(ServerDescription::load(&data_dir.path)); + state + .ecs_mut() + .insert(ServerDescription::load(&data_dir.path)); state.ecs_mut().insert(data_dir); state.ecs_mut().insert(EventBus::::default()); state diff --git a/server/src/settings.rs b/server/src/settings.rs index c03bfbd72c..1fcf7cc5b2 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -18,11 +18,11 @@ use world::sim::FileOpts; const DEFAULT_WORLD_SEED: u32 = 59686; //const CONFIG_DIR_ENV: &'static str = "VELOREN_SERVER_CONFIG"; -const /*DEFAULT_*/CONFIG_DIR: &'static str = "server_config"; -const SETTINGS_FILENAME: &'static str = "settings.ron"; -const WHITELIST_FILENAME: &'static str = "whitelist.ron"; -const BANLIST_FILENAME: &'static str = "banlist.ron"; -const SERVER_DESCRIPTION_FILENAME: &'static str = "description.ron"; +const /*DEFAULT_*/CONFIG_DIR: &str = "server_config"; +const SETTINGS_FILENAME: &str = "settings.ron"; +const WHITELIST_FILENAME: &str = "whitelist.ron"; +const BANLIST_FILENAME: &str = "banlist.ron"; +const SERVER_DESCRIPTION_FILENAME: &str = "description.ron"; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 557f8b603b..f40a18cfa3 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -209,7 +209,8 @@ impl Sys { // Give the player a welcome message if !server_description.is_empty() { client.notify( - ChatType::CommandInfo.server_msg(String::from(&**server_description)), + ChatType::CommandInfo + .server_msg(String::from(&**server_description)), ); } @@ -453,7 +454,7 @@ impl<'a> System<'a> for Sys { ( ReadExpect<'a, Whitelist>, ReadExpect<'a, Banlist>, - ReadExpect<'a, ServerDescription> + ReadExpect<'a, ServerDescription>, ), ); @@ -488,11 +489,7 @@ impl<'a> System<'a> for Sys { mut controllers, settings, alias_validator, - ( - whitelist, - banlist, - server_description, - ), + (whitelist, banlist, server_description), ): Self::SystemData, ) { span!(_guard, "run", "message::Sys::run"); From a3ee5a4006aa3f7f6516e106b4bc3b434a1abbdf Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 5 Oct 2020 05:04:22 -0400 Subject: [PATCH 10/15] Rename model_col -> highligh_col to better reflect its usage --- assets/voxygen/shaders/figure-frag.glsl | 6 +++--- assets/voxygen/shaders/figure-vert.glsl | 2 +- assets/voxygen/shaders/light-shadows-figure-vert.glsl | 2 +- assets/voxygen/shaders/player-shadow-frag.glsl | 2 +- voxygen/src/render/pipelines/figure.rs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index 6a8015773d..813aebe54a 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -48,7 +48,7 @@ uniform sampler2D t_col_light; layout (std140) uniform u_locals { mat4 model_mat; - vec4 model_col; + vec4 highlight_col; ivec4 atlas_offs; vec3 model_pos; // bit 0 - is player @@ -195,8 +195,8 @@ void main() { // light += point_light; // diffuse_light += point_light; // reflected_light += point_light; - // vec3 surf_color = illuminate(srgb_to_linear(model_col.rgb * f_col), light, diffuse_light, ambient_light); - surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light) * model_col.rgb; + // vec3 surf_color = illuminate(srgb_to_linear(highlight_col.rgb * f_col), light, diffuse_light, ambient_light); + surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light) * highlight_col.rgb; #if (CLOUD_MODE == CLOUD_MODE_REGULAR) float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); diff --git a/assets/voxygen/shaders/figure-vert.glsl b/assets/voxygen/shaders/figure-vert.glsl index cb4f4414b0..c44315989f 100644 --- a/assets/voxygen/shaders/figure-vert.glsl +++ b/assets/voxygen/shaders/figure-vert.glsl @@ -26,7 +26,7 @@ in uint v_ao_bone; */ layout (std140) uniform u_locals { mat4 model_mat; - vec4 model_col; + vec4 highlight_col; ivec4 atlas_offs; vec3 model_pos; // bit 0 - is player diff --git a/assets/voxygen/shaders/light-shadows-figure-vert.glsl b/assets/voxygen/shaders/light-shadows-figure-vert.glsl index b7d9d1a21f..852e0a23f9 100644 --- a/assets/voxygen/shaders/light-shadows-figure-vert.glsl +++ b/assets/voxygen/shaders/light-shadows-figure-vert.glsl @@ -38,7 +38,7 @@ in uint v_atlas_pos; layout (std140) uniform u_locals { mat4 model_mat; - vec4 model_col; + vec4 highlight_col; ivec4 atlas_offs; vec3 model_pos; // bit 0 - is player diff --git a/assets/voxygen/shaders/player-shadow-frag.glsl b/assets/voxygen/shaders/player-shadow-frag.glsl index 693bc30409..69115d7323 100644 --- a/assets/voxygen/shaders/player-shadow-frag.glsl +++ b/assets/voxygen/shaders/player-shadow-frag.glsl @@ -24,7 +24,7 @@ in float f_ao; layout (std140) uniform u_locals { mat4 model_mat; - vec4 model_col; + vec4 highlight_col; ivec4 atlas_offs; vec3 model_pos; int flags; diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs index f4de569913..74bb1b7a79 100644 --- a/voxygen/src/render/pipelines/figure.rs +++ b/voxygen/src/render/pipelines/figure.rs @@ -12,7 +12,7 @@ use vek::*; gfx_defines! { constant Locals { model_mat: [[f32; 4]; 4] = "model_mat", - model_col: [f32; 4] = "model_col", + highlight_col: [f32; 4] = "highlight_col", atlas_offs: [i32; 4] = "atlas_offs", model_pos: [f32; 3] = "model_pos", flags: u32 = "flags", @@ -64,7 +64,7 @@ impl Locals { Self { model_mat: model_mat.into_col_arrays(), - model_col: col.into_array(), + highlight_col: col.into_array(), model_pos: pos.into_array(), atlas_offs: Vec4::from(atlas_offs).into_array(), flags, From 83fb26c4f9414833801bc71c46673f655178d2b0 Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 5 Oct 2020 22:59:47 -0400 Subject: [PATCH 11/15] Group editable server settings into one struct that is stored in the ecs, don't expose DataDir, use Uuid in the whitelist instead of usernames, replace Banlist record tuple with new type with named fields, remove commented code --- server-cli/src/main.rs | 13 ++--- server/src/cmd.rs | 94 +++++++++++++++++++++++++----------- server/src/data_dir.rs | 3 -- server/src/lib.rs | 67 +++++++++++-------------- server/src/login_provider.rs | 9 ++-- server/src/settings.rs | 59 ++++++++++++++-------- server/src/sys/message.rs | 51 +++++++++---------- voxygen/src/singleplayer.rs | 25 +++++----- 8 files changed, 181 insertions(+), 140 deletions(-) diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 7c54e03843..50ac1e1985 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -14,7 +14,7 @@ use crate::{ }; use clap::{App, Arg}; use common::clock::Clock; -use server::{DataDir, Event, Input, Server, ServerSettings}; +use server::{Event, Input, Server}; #[cfg(any(target_os = "linux", target_os = "macos"))] use signal_hook::SIGUSR1; use std::{ @@ -78,14 +78,15 @@ fn main() -> io::Result<()> { let mut clock = Clock::start(); // Determine folder to save server data in - let server_data_dir = DataDir::from({ + let server_data_dir = { let mut path = common::userdata_dir_workspace!(); path.push(server::DEFAULT_DATA_DIR_NAME); path - }); + }; // Load server settings - let mut server_settings = ServerSettings::load(server_data_dir.as_ref()); + let mut server_settings = server::Settings::load(&server_data_dir); + let editable_settings = server::EditableSettings::load(&server_data_dir); if no_auth { server_settings.auth_server_address = None; @@ -94,8 +95,8 @@ fn main() -> io::Result<()> { let server_port = &server_settings.gameserver_address.port(); let metrics_port = &server_settings.metrics_address.port(); // Create server - let mut server = - Server::new(server_settings, server_data_dir).expect("Failed to create server instance!"); + let mut server = Server::new(server_settings, editable_settings, &server_data_dir) + .expect("Failed to create server instance!"); info!( ?server_port, diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 925102da86..11eb4c29d4 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2,7 +2,11 @@ //! To implement a new command, add an instance of `ChatCommand` to //! `CHAT_COMMANDS` and provide a handler function. -use crate::{client::Client, settings::EditableSetting, Server, StateExt}; +use crate::{ + client::Client, + settings::{BanRecord, EditableSetting}, + Server, StateExt, +}; use chrono::{NaiveTime, Timelike}; use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, @@ -266,7 +270,7 @@ fn handle_motd( ) { server.notify_client( client, - ChatType::CommandError.server_msg((**server.server_description()).clone()), + ChatType::CommandError.server_msg((*server.editable_settings().server_description).clone()), ); } @@ -281,7 +285,8 @@ fn handle_set_motd( match scan_fmt!(&args, &action.arg_fmt(), String) { Ok(msg) => { server - .server_description_mut() + .editable_settings_mut() + .server_description .edit(data_dir.as_ref(), |d| **d = msg.clone()); server.notify_client( client, @@ -290,7 +295,8 @@ fn handle_set_motd( }, Err(_) => { server - .server_description_mut() + .editable_settings_mut() + .server_description .edit(data_dir.as_ref(), |d| d.clear()); server.notify_client( client, @@ -1826,25 +1832,48 @@ fn handle_whitelist( action: &ChatCommand, ) { if let Ok((whitelist_action, username)) = scan_fmt!(&args, &action.arg_fmt(), String, String) { + let lookup_uuid = || { + server + .state + .ecs() + .read_resource::() + .username_to_uuid(&username) + .map_err(|_| { + server.notify_client( + client, + ChatType::CommandError.server_msg(format!( + "Unable to determine UUID for username \"{}\"", + &username + )), + ) + }) + .ok() + }; + if whitelist_action.eq_ignore_ascii_case("add") { - server - .whitelist_mut() - .edit(server.data_dir().as_ref(), |w| w.push(username.clone())); - server.notify_client( - client, - ChatType::CommandInfo.server_msg(format!("\"{}\" added to whitelist", username)), - ); + if let Some(uuid) = lookup_uuid() { + server + .editable_settings_mut() + .whitelist + .edit(server.data_dir().as_ref(), |w| w.push(uuid)); + server.notify_client( + client, + ChatType::CommandInfo + .server_msg(format!("\"{}\" added to whitelist", username)), + ); + } } else if whitelist_action.eq_ignore_ascii_case("remove") { - server - .whitelist_mut() - .edit(server.data_dir().as_ref(), |w| { - w.retain(|x| !x.eq_ignore_ascii_case(&username.clone())) - }); - server.notify_client( - client, - ChatType::CommandInfo - .server_msg(format!("\"{}\" removed from whitelist", username)), - ); + if let Some(uuid) = lookup_uuid() { + server + .editable_settings_mut() + .whitelist + .edit(server.data_dir().as_ref(), |w| w.retain(|x| x != &uuid)); + server.notify_client( + client, + ChatType::CommandInfo + .server_msg(format!("\"{}\" removed from whitelist", username)), + ); + } } else { server.notify_client( client, @@ -1930,16 +1959,22 @@ fn handle_ban( .username_to_uuid(&target_alias); if let Ok(uuid) = uuid_result { - if server.banlist().contains_key(&uuid) { + if server.editable_settings().banlist.contains_key(&uuid) { server.notify_client( client, ChatType::CommandError .server_msg(format!("{} is already on the banlist", target_alias)), ) } else { - server.banlist_mut().edit(server.data_dir().as_ref(), |b| { - b.insert(uuid, (target_alias.clone(), reason.clone())); - }); + server + .editable_settings_mut() + .banlist + .edit(server.data_dir().as_ref(), |b| { + b.insert(uuid, BanRecord { + username_when_banned: target_alias.clone(), + reason: reason.clone(), + }); + }); server.notify_client( client, ChatType::CommandInfo.server_msg(format!( @@ -1990,9 +2025,12 @@ fn handle_unban( .username_to_uuid(&username); if let Ok(uuid) = uuid_result { - server.banlist_mut().edit(server.data_dir().as_ref(), |b| { - b.remove(&uuid); - }); + server + .editable_settings_mut() + .banlist + .edit(server.data_dir().as_ref(), |b| { + b.remove(&uuid); + }); server.notify_client( client, ChatType::CommandInfo.server_msg(format!("{} was successfully unbanned", username)), diff --git a/server/src/data_dir.rs b/server/src/data_dir.rs index 3c134ca870..1b543707fc 100644 --- a/server/src/data_dir.rs +++ b/server/src/data_dir.rs @@ -8,9 +8,6 @@ pub const DEFAULT_DATA_DIR_NAME: &str = "server"; pub struct DataDir { pub path: PathBuf, } -impl> From for DataDir { - fn from(t: T) -> Self { Self { path: t.into() } } -} impl AsRef for DataDir { fn as_ref(&self) -> &Path { &self.path } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 9bb8ad3acd..a9a40b1ed3 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -23,11 +23,11 @@ pub mod sys; // Reexports pub use crate::{ - data_dir::{DataDir, DEFAULT_DATA_DIR_NAME}, + data_dir::DEFAULT_DATA_DIR_NAME, error::Error, events::Event, input::Input, - settings::ServerSettings, + settings::{EditableSettings, Settings}, }; use crate::{ @@ -35,8 +35,8 @@ use crate::{ chunk_generator::ChunkGenerator, client::{Client, RegionSubscription}, cmd::ChatCommandExt, + data_dir::DataDir, login_provider::LoginProvider, - settings::{Banlist, EditableSetting, ServerDescription, Whitelist}, state_ext::StateExt, sys::sentinel::{DeletedEntities, TrackedComps}, }; @@ -110,11 +110,15 @@ impl Server { /// Create a new `Server` #[allow(clippy::expect_fun_call)] // TODO: Pending review in #587 #[allow(clippy::needless_update)] // TODO: Pending review in #587 - pub fn new(settings: ServerSettings, data_dir: DataDir) -> Result { - info!("Server is data dir is: {}", data_dir.path.display()); + pub fn new( + settings: Settings, + editable_settings: EditableSettings, + data_dir: &std::path::Path, + ) -> Result { + info!("Server is data dir is: {}", data_dir.display()); // persistence_db_dir is relative to data_dir - let persistence_db_dir = data_dir.path.join(&settings.persistence_db_dir); + let persistence_db_dir = data_dir.join(&settings.persistence_db_dir); // Run pending DB migrations (if any) debug!("Running DB migrations..."); @@ -129,12 +133,10 @@ impl Server { let mut state = State::default(); state.ecs_mut().insert(settings.clone()); - state.ecs_mut().insert(Whitelist::load(&data_dir.path)); - state.ecs_mut().insert(Banlist::load(&data_dir.path)); - state - .ecs_mut() - .insert(ServerDescription::load(&data_dir.path)); - state.ecs_mut().insert(data_dir); + state.ecs_mut().insert(editable_settings); + state.ecs_mut().insert(DataDir { + path: data_dir.to_owned(), + }); state.ecs_mut().insert(EventBus::::default()); state .ecs_mut() @@ -358,11 +360,11 @@ impl Server { } pub fn get_server_info(&self) -> ServerInfo { - let settings = self.state.ecs().fetch::(); - let server_description = self.state.ecs().fetch::(); + let settings = self.state.ecs().fetch::(); + let editable_settings = self.state.ecs().fetch::(); ServerInfo { name: settings.server_name.clone(), - description: (**server_description).clone(), + description: (&*editable_settings.server_description).clone(), git_hash: common::util::GIT_HASH.to_string(), git_date: common::util::GIT_DATE.to_string(), auth_provider: settings.auth_server_address.clone(), @@ -375,38 +377,23 @@ impl Server { } /// Get a reference to the server's settings - pub fn settings(&self) -> impl Deref + '_ { - self.state.ecs().fetch::() + pub fn settings(&self) -> impl Deref + '_ { + self.state.ecs().fetch::() } /// Get a mutable reference to the server's settings - pub fn settings_mut(&self) -> impl DerefMut + '_ { - self.state.ecs().fetch_mut::() + pub fn settings_mut(&self) -> impl DerefMut + '_ { + self.state.ecs().fetch_mut::() } - /// Get a mutable reference to the server's whitelist - pub fn whitelist_mut(&self) -> impl DerefMut + '_ { - self.state.ecs().fetch_mut::() + /// Get a mutable reference to the server's editable settings + pub fn editable_settings_mut(&self) -> impl DerefMut + '_ { + self.state.ecs().fetch_mut::() } - /// Get a reference to the server's banlist - pub fn banlist(&self) -> impl Deref + '_ { - self.state.ecs().fetch::() - } - - /// Get a mutable reference to the server's banlist - pub fn banlist_mut(&self) -> impl DerefMut + '_ { - self.state.ecs().fetch_mut::() - } - - /// Get a reference to the server's description - pub fn server_description(&self) -> impl Deref + '_ { - self.state.ecs().fetch::() - } - - /// Get a mutable reference to the server's description - pub fn server_description_mut(&self) -> impl DerefMut + '_ { - self.state.ecs().fetch_mut::() + /// Get a reference to the server's editable settings + pub fn editable_settings(&self) -> impl Deref + '_ { + self.state.ecs().fetch::() } /// Get path to the directory that the server info into diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs index 5b4e2c0cea..1b061b1a1f 100644 --- a/server/src/login_provider.rs +++ b/server/src/login_provider.rs @@ -1,3 +1,4 @@ +use crate::settings::BanRecord; use authc::{AuthClient, AuthClientError, AuthToken, Uuid}; use common::msg::RegisterError; use hashbrown::HashMap; @@ -52,8 +53,8 @@ impl LoginProvider { pub fn try_login( &mut self, username_or_token: &str, - whitelist: &[String], - banlist: &HashMap, + whitelist: &[Uuid], + banlist: &HashMap, ) -> Result<(String, Uuid), RegisterError> { self // resolve user information @@ -63,12 +64,12 @@ impl LoginProvider { // user cannot join if they are listed on the banlist if let Some(ban_record) = banlist.get(&uuid) { // Pull reason string out of ban record and send a copy of it - return Err(RegisterError::Banned(ban_record.1.clone())); + return Err(RegisterError::Banned(ban_record.reason.clone())); } // user can only join if he is admin, the whitelist is empty (everyone can join) // or his name is in the whitelist - if !whitelist.is_empty() && !whitelist.contains(&username) { + if !whitelist.is_empty() && !whitelist.contains(&uuid) { return Err(RegisterError::NotOnWhitelist); } diff --git a/server/src/settings.rs b/server/src/settings.rs index 1fcf7cc5b2..c0b1cdbaab 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -17,8 +17,7 @@ use tracing::{error, warn}; use world::sim::FileOpts; const DEFAULT_WORLD_SEED: u32 = 59686; -//const CONFIG_DIR_ENV: &'static str = "VELOREN_SERVER_CONFIG"; -const /*DEFAULT_*/CONFIG_DIR: &str = "server_config"; +const CONFIG_DIR: &str = "server_config"; const SETTINGS_FILENAME: &str = "settings.ron"; const WHITELIST_FILENAME: &str = "whitelist.ron"; const BANLIST_FILENAME: &str = "banlist.ron"; @@ -26,7 +25,7 @@ const SERVER_DESCRIPTION_FILENAME: &str = "description.ron"; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] -pub struct ServerSettings { +pub struct Settings { pub gameserver_address: SocketAddr, pub metrics_address: SocketAddr, pub auth_server_address: Option, @@ -47,7 +46,7 @@ pub struct ServerSettings { pub client_timeout: Duration, } -impl Default for ServerSettings { +impl Default for Settings { fn default() -> Self { Self { gameserver_address: SocketAddr::from(([0; 4], 14004)), @@ -68,7 +67,7 @@ impl Default for ServerSettings { } } -impl ServerSettings { +impl Settings { /// path: Directory that contains the server config directory pub fn load(path: &Path) -> Self { let path = Self::get_settings_path(path); @@ -135,7 +134,6 @@ impl ServerSettings { DEFAULT_WORLD_SEED }, server_name: "Singleplayer".to_owned(), - //server_description: "Who needs friends anyway?".to_owned(), max_players: 100, start_time: 9.0 * 3600.0, admins: vec!["singleplayer".to_string()], /* TODO: Let the player choose if they want @@ -155,28 +153,51 @@ impl ServerSettings { fn with_config_dir(path: &Path) -> PathBuf { let mut path = PathBuf::from(path); - //if let Some(path) = std::env::var_os(CONFIG_DIR_ENV) { - // let config_dir = PathBuf::from(path); - // if config_dir.exists() { - // return config_dir; - // } - // warn!(?path, "VELROREN_SERVER_CONFIG points to invalid path."); - //} - path.push(/* DEFAULT_ */ CONFIG_DIR); - //PathBuf::from(DEFAULT_CONFIG_DIR) + path.push(CONFIG_DIR); path } +#[derive(Deserialize, Serialize)] +pub struct BanRecord { + pub username_when_banned: String, + pub reason: String, +} + #[derive(Deserialize, Serialize, Default)] #[serde(transparent)] -pub struct Whitelist(Vec); +pub struct Whitelist(Vec); #[derive(Deserialize, Serialize, Default)] #[serde(transparent)] -pub struct Banlist(HashMap); +pub struct Banlist(HashMap); #[derive(Deserialize, Serialize)] #[serde(transparent)] pub struct ServerDescription(String); +/// Combines all the editable settings into one struct that is stored in the ecs +pub struct EditableSettings { + pub whitelist: Whitelist, + pub banlist: Banlist, + pub server_description: ServerDescription, +} + +impl EditableSettings { + pub fn load(data_dir: &Path) -> Self { + Self { + whitelist: Whitelist::load(data_dir), + banlist: Banlist::load(data_dir), + server_description: ServerDescription::load(data_dir), + } + } + + pub fn singleplayer(data_dir: &Path) -> Self { + let load = Self::load(data_dir); + Self { + server_description: ServerDescription("Who needs friends anyway?".into()), + ..load + } + } +} + impl Default for ServerDescription { fn default() -> Self { Self("This is the best Veloren server".into()) } } @@ -194,7 +215,7 @@ impl EditableSetting for ServerDescription { } impl Deref for Whitelist { - type Target = Vec; + type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } @@ -204,7 +225,7 @@ impl DerefMut for Whitelist { } impl Deref for Banlist { - type Target = HashMap; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.0 } } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index f40a18cfa3..fd2e3bdae5 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -6,8 +6,7 @@ use crate::{ login_provider::LoginProvider, metrics::{NetworkRequestMetrics, PlayerMetrics}, persistence::character_loader::CharacterLoader, - settings::{Banlist, ServerDescription, Whitelist}, - ServerSettings, + EditableSettings, Settings, }; use common::{ comp::{ @@ -65,11 +64,9 @@ impl Sys { orientations: &mut WriteStorage<'_, Ori>, players: &mut WriteStorage<'_, Player>, controllers: &mut WriteStorage<'_, Controller>, - settings: &Read<'_, ServerSettings>, + settings: &Read<'_, Settings>, alias_validator: &ReadExpect<'_, AliasValidator>, - whitelist: &Whitelist, - banlist: &Banlist, - server_description: &ServerDescription, + editable_settings: &EditableSettings, ) -> Result<(), crate::error::Error> { loop { let msg = client.recv().await?; @@ -100,14 +97,17 @@ impl Sys { view_distance, token_or_username, } => { - let (username, uuid) = - match login_provider.try_login(&token_or_username, &whitelist, &banlist) { - Err(err) => { - client.error_state(RequestStateError::RegisterDenied(err)); - break Ok(()); - }, - Ok((username, uuid)) => (username, uuid), - }; + let (username, uuid) = match login_provider.try_login( + &token_or_username, + &*editable_settings.whitelist, + &*editable_settings.banlist, + ) { + Err(err) => { + client.error_state(RequestStateError::RegisterDenied(err)); + break Ok(()); + }, + Ok((username, uuid)) => (username, uuid), + }; let vd = view_distance.map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd))); @@ -207,11 +207,10 @@ impl Sys { }); // Give the player a welcome message - if !server_description.is_empty() { - client.notify( - ChatType::CommandInfo - .server_msg(String::from(&**server_description)), - ); + if !editable_settings.server_description.is_empty() { + client.notify(ChatType::CommandInfo.server_msg(String::from( + &*editable_settings.server_description, + ))); } // Only send login message if it wasn't already @@ -449,13 +448,9 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Player>, WriteStorage<'a, Client>, WriteStorage<'a, Controller>, - Read<'a, ServerSettings>, + Read<'a, Settings>, ReadExpect<'a, AliasValidator>, - ( - ReadExpect<'a, Whitelist>, - ReadExpect<'a, Banlist>, - ReadExpect<'a, ServerDescription>, - ), + ReadExpect<'a, EditableSettings>, ); #[allow(clippy::match_ref_pats)] // TODO: Pending review in #587 @@ -489,7 +484,7 @@ impl<'a> System<'a> for Sys { mut controllers, settings, alias_validator, - (whitelist, banlist, server_description), + editable_settings, ): Self::SystemData, ) { span!(_guard, "run", "message::Sys::run"); @@ -550,9 +545,7 @@ impl<'a> System<'a> for Sys { &mut controllers, &settings, &alias_validator, - &whitelist, - &banlist, - &server_description, + &editable_settings, ); select!( _ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()), diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index 76d4e24549..6f45188287 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -1,7 +1,7 @@ use client::Client; use common::clock::Clock; use crossbeam::channel::{bounded, unbounded, Receiver, Sender, TryRecvError}; -use server::{DataDir, Error as ServerError, Event, Input, Server, ServerSettings}; +use server::{Error as ServerError, Event, Input, Server}; use std::{ sync::{ atomic::{AtomicBool, Ordering}, @@ -29,18 +29,19 @@ pub struct Singleplayer { } impl Singleplayer { - pub fn new(client: Option<&Client>) -> (Self, ServerSettings) { + pub fn new(client: Option<&Client>) -> (Self, server::Settings) { let (sender, receiver) = unbounded(); // Determine folder to save server data in - let server_data_dir = DataDir::from({ + let server_data_dir = { let mut path = common::userdata_dir_workspace!(); path.push("singleplayer"); path - }); + }; // Create server - let settings = ServerSettings::singleplayer(server_data_dir.as_ref()); + let settings = server::Settings::singleplayer(&server_data_dir); + let editable_settings = server::EditableSettings::singleplayer(&server_data_dir); let thread_pool = client.map(|c| c.thread_pool().clone()); let settings2 = settings.clone(); @@ -52,13 +53,15 @@ impl Singleplayer { let thread = thread::spawn(move || { let mut server = None; - if let Err(e) = result_sender.send(match Server::new(settings2, server_data_dir) { - Ok(s) => { - server = Some(s); - Ok(()) + if let Err(e) = result_sender.send( + match Server::new(settings2, editable_settings, &server_data_dir) { + Ok(s) => { + server = Some(s); + Ok(()) + }, + Err(e) => Err(e), }, - Err(e) => Err(e), - }) { + ) { warn!( ?e, "Failed to send singleplayer server initialization result. Most likely the \ From ca2bf937e6697d343867d9970568a29425cf7bb3 Mon Sep 17 00:00:00 2001 From: Imbris Date: Mon, 5 Oct 2020 23:42:09 -0400 Subject: [PATCH 12/15] Apply saves dir override when loading settings, change whitelist to a HashSet, let admins login even if they are not on the whitelist to reflect the comments in login code --- server/src/cmd.rs | 4 ++-- server/src/lib.rs | 3 +++ server/src/login_provider.rs | 7 ++++--- server/src/persistence/mod.rs | 22 ++-------------------- server/src/settings.rs | 21 +++++++++++++++++---- server/src/sys/message.rs | 1 + 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 11eb4c29d4..704b290d9e 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1855,7 +1855,7 @@ fn handle_whitelist( server .editable_settings_mut() .whitelist - .edit(server.data_dir().as_ref(), |w| w.push(uuid)); + .edit(server.data_dir().as_ref(), |w| w.insert(uuid)); server.notify_client( client, ChatType::CommandInfo @@ -1867,7 +1867,7 @@ fn handle_whitelist( server .editable_settings_mut() .whitelist - .edit(server.data_dir().as_ref(), |w| w.retain(|x| x != &uuid)); + .edit(server.data_dir().as_ref(), |w| w.remove(&uuid)); server.notify_client( client, ChatType::CommandInfo diff --git a/server/src/lib.rs b/server/src/lib.rs index a9a40b1ed3..e1ec27bab5 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -116,6 +116,9 @@ impl Server { data_dir: &std::path::Path, ) -> Result { info!("Server is data dir is: {}", data_dir.display()); + if settings.auth_server_address.is_none() { + info!("Authentication is disabled"); + } // persistence_db_dir is relative to data_dir let persistence_db_dir = data_dir.join(&settings.persistence_db_dir); diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs index 1b061b1a1f..47c46698e1 100644 --- a/server/src/login_provider.rs +++ b/server/src/login_provider.rs @@ -1,7 +1,7 @@ use crate::settings::BanRecord; use authc::{AuthClient, AuthClientError, AuthToken, Uuid}; use common::msg::RegisterError; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use std::str::FromStr; use tracing::{error, info}; @@ -53,7 +53,8 @@ impl LoginProvider { pub fn try_login( &mut self, username_or_token: &str, - whitelist: &[Uuid], + admins: &[String], + whitelist: &HashSet, banlist: &HashMap, ) -> Result<(String, Uuid), RegisterError> { self @@ -69,7 +70,7 @@ impl LoginProvider { // user can only join if he is admin, the whitelist is empty (everyone can join) // or his name is in the whitelist - if !whitelist.is_empty() && !whitelist.contains(&uuid) { + if !whitelist.is_empty() && !whitelist.contains(&uuid) && !admins.contains(&username) { return Err(RegisterError::NotOnWhitelist); } diff --git a/server/src/persistence/mod.rs b/server/src/persistence/mod.rs index 2c01dc38ce..797b4eefb0 100644 --- a/server/src/persistence/mod.rs +++ b/server/src/persistence/mod.rs @@ -14,16 +14,11 @@ mod json_models; mod models; mod schema; -extern crate diesel; - use common::comp; use diesel::{connection::SimpleConnection, prelude::*}; use diesel_migrations::embed_migrations; -use std::{ - env, fs, - path::{Path, PathBuf}, -}; -use tracing::{info, warn}; +use std::{fs, path::Path}; +use tracing::info; /// A tuple of the components that are persisted to the DB for each character pub type PersistedComponents = (comp::Body, comp::Stats, comp::Inventory, comp::Loadout); @@ -49,7 +44,6 @@ impl std::io::Write for TracingOut { /// Runs any pending database migrations. This is executed during server startup pub fn run_migrations(db_dir: &Path) -> Result<(), diesel_migrations::RunMigrationsError> { - let db_dir = &apply_saves_dir_override(db_dir); let _ = fs::create_dir(format!("{}/", db_dir.display())); embedded_migrations::run_with_output( @@ -95,7 +89,6 @@ impl<'a> core::ops::Deref for VelorenTransaction<'a> { } pub fn establish_connection(db_dir: &Path) -> QueryResult { - let db_dir = &apply_saves_dir_override(db_dir); let database_url = format!("{}/db.sqlite", db_dir.display()); let connection = SqliteConnection::establish(&database_url) @@ -119,14 +112,3 @@ pub fn establish_connection(db_dir: &Path) -> QueryResult { Ok(VelorenConnection(connection)) } - -fn apply_saves_dir_override(db_dir: &Path) -> PathBuf { - if let Some(saves_dir) = env::var_os("VELOREN_SAVES_DIR") { - let path = PathBuf::from(&saves_dir); - if path.exists() || path.parent().map(|x| x.exists()).unwrap_or(false) { - return path; - } - warn!(?saves_dir, "VELOREN_SAVES_DIR points to an invalid path."); - } - db_dir.to_owned() -} diff --git a/server/src/settings.rs b/server/src/settings.rs index c0b1cdbaab..92a3be8ecd 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -3,7 +3,7 @@ mod editable; pub use editable::EditableSetting; use authc::Uuid; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use portpicker::pick_unused_port; use serde::{Deserialize, Serialize}; use std::{ @@ -39,7 +39,7 @@ pub struct Settings { /// uses the value of the file options to decide how to proceed. pub map_file: Option, /// Relative paths are relative to the server data dir - pub persistence_db_dir: String, + pub persistence_db_dir: PathBuf, pub max_view_distance: Option, pub banned_words_files: Vec, pub max_player_group_size: u32, @@ -97,6 +97,7 @@ impl Settings { } default_settings } + .apply_saves_dir_override() } fn save_to_file(&self, path: &Path) -> std::io::Result<()> { @@ -149,6 +150,18 @@ impl Settings { path.push(SETTINGS_FILENAME); path } + + fn apply_saves_dir_override(mut self) -> Self { + if let Some(saves_dir) = std::env::var_os("VELOREN_SAVES_DIR") { + let path = PathBuf::from(&saves_dir); + if path.exists() || path.parent().map(|x| x.exists()).unwrap_or(false) { + self.persistence_db_dir = path; + } else { + warn!(?saves_dir, "VELOREN_SAVES_DIR points to an invalid path."); + } + } + self + } } fn with_config_dir(path: &Path) -> PathBuf { @@ -165,7 +178,7 @@ pub struct BanRecord { #[derive(Deserialize, Serialize, Default)] #[serde(transparent)] -pub struct Whitelist(Vec); +pub struct Whitelist(HashSet); #[derive(Deserialize, Serialize, Default)] #[serde(transparent)] pub struct Banlist(HashMap); @@ -215,7 +228,7 @@ impl EditableSetting for ServerDescription { } impl Deref for Whitelist { - type Target = Vec; + type Target = HashSet; fn deref(&self) -> &Self::Target { &self.0 } } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index fd2e3bdae5..6f13bfa00b 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -99,6 +99,7 @@ impl Sys { } => { let (username, uuid) = match login_provider.try_login( &token_or_username, + &settings.admins, &*editable_settings.whitelist, &*editable_settings.banlist, ) { From 71251ca6a69219a829649cc34b69659470efc8cc Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 10 Oct 2020 02:10:04 -0400 Subject: [PATCH 13/15] Change admins from Vec into Hashset, add commands to server-cli-bin for adding and removing admins --- common/src/comp/admin.rs | 10 ---- common/src/comp/mod.rs | 2 +- server-cli/src/admin.rs | 28 +++++++++++ server-cli/src/main.rs | 93 ++++++++++++++++++++++++++---------- server-cli/src/tui_runner.rs | 21 +++++++- server/src/lib.rs | 66 +++++++++++++++++++++++-- server/src/login_provider.rs | 4 +- server/src/settings.rs | 44 +++++++++++++---- server/src/sys/message.rs | 10 ++-- 9 files changed, 221 insertions(+), 57 deletions(-) create mode 100644 server-cli/src/admin.rs diff --git a/common/src/comp/admin.rs b/common/src/comp/admin.rs index f411c3f07d..5ce3c59327 100644 --- a/common/src/comp/admin.rs +++ b/common/src/comp/admin.rs @@ -1,5 +1,4 @@ use specs::{Component, NullStorage}; -use std::ops::Deref; #[derive(Clone, Copy, Default)] pub struct Admin; @@ -7,12 +6,3 @@ pub struct Admin; impl Component for Admin { type Storage = NullStorage; } - -/// List of admin usernames. This is stored as a specs resource so that the list -/// can be read by specs systems. -pub struct AdminList(pub Vec); -impl Deref for AdminList { - type Target = Vec; - - fn deref(&self) -> &Vec { &self.0 } -} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index e2d403d1d8..9c85f094cf 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -24,7 +24,7 @@ pub mod visual; // Reexports pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout}; -pub use admin::{Admin, AdminList}; +pub use admin::Admin; pub use agent::{Agent, Alignment}; pub use beam::{Beam, BeamSegment}; pub use body::{ diff --git a/server-cli/src/admin.rs b/server-cli/src/admin.rs new file mode 100644 index 0000000000..3fcbd6b35c --- /dev/null +++ b/server-cli/src/admin.rs @@ -0,0 +1,28 @@ +pub fn admin_subcommand( + sub_m: &clap::ArgMatches, + server_settings: &server::Settings, + editable_settings: &mut server::EditableSettings, + data_dir: &std::path::Path, +) { + let login_provider = + server::login_provider::LoginProvider::new(server_settings.auth_server_address.clone()); + + match sub_m.subcommand() { + ("add", Some(sub_m)) => { + if let Some(username) = sub_m.value_of("username") { + server::add_admin(username, &login_provider, editable_settings, data_dir) + } + }, + ("remove", Some(sub_m)) => { + if let Some(username) = sub_m.value_of("username") { + server::remove_admin(username, &login_provider, editable_settings, data_dir) + } + }, + // TODO: can clap enforce this? + // or make this list current admins or something + _ => tracing::error!( + "Invalid input, use one of the subcommands listed using: \nveloren-server-cli help \ + admin" + ), + } +} diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 50ac1e1985..769d081933 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -2,6 +2,7 @@ #![deny(clippy::clone_on_ref_ptr)] #![feature(bool_to_option)] +mod admin; mod logging; mod settings; mod shutdown_coordinator; @@ -12,11 +13,9 @@ use crate::{ shutdown_coordinator::ShutdownCoordinator, tui_runner::{Message, Tui}, }; -use clap::{App, Arg}; +use clap::{App, Arg, SubCommand}; use common::clock::Clock; use server::{Event, Input, Server}; -#[cfg(any(target_os = "linux", target_os = "macos"))] -use signal_hook::SIGUSR1; use std::{ io, sync::{atomic::AtomicBool, mpsc, Arc}, @@ -35,48 +34,54 @@ fn main() -> io::Result<()> { Arg::with_name("basic") .short("b") .long("basic") - .help("Disables the tui") - .takes_value(false), + .help("Disables the tui"), Arg::with_name("interactive") .short("i") .long("interactive") - .help("Enables command input for basic mode") - .takes_value(false), + .help("Enables command input for basic mode"), Arg::with_name("no-auth") .long("no-auth") .help("Runs without auth enabled"), ]) + .subcommand( + SubCommand::with_name("admin") + .about("Add or remove admins") + .subcommands(vec![ + SubCommand::with_name("add").about("Adds an admin").arg( + Arg::with_name("username") + .help("Name of the admin to add") + .required(true), + ), + SubCommand::with_name("remove") + .about("Removes an admin") + .arg( + Arg::with_name("username") + .help("Name of the admin to remove") + .required(true), + ), + ]), + ) .get_matches(); - let basic = matches.is_present("basic"); + let basic = matches.is_present("basic") + // Default to basic with these subcommands + || matches + .subcommand_name() + .filter(|name| ["admin"].contains(name)) + .is_some(); let interactive = matches.is_present("interactive"); let no_auth = matches.is_present("no-auth"); let sigusr1_signal = Arc::new(AtomicBool::new(false)); #[cfg(any(target_os = "linux", target_os = "macos"))] - let _ = signal_hook::flag::register(SIGUSR1, Arc::clone(&sigusr1_signal)); + let _ = signal_hook::flag::register(signal_hook::SIGUSR1, Arc::clone(&sigusr1_signal)); logging::init(basic); // Load settings let settings = settings::Settings::load(); - // Panic hook to ensure that console mode is set back correctly if in non-basic - // mode - let hook = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |info| { - Tui::shutdown(basic); - hook(info); - })); - - let tui = (!basic || interactive).then(|| Tui::run(basic)); - - info!("Starting server..."); - - // Set up an fps clock - let mut clock = Clock::start(); - // Determine folder to save server data in let server_data_dir = { let mut path = common::userdata_dir_workspace!(); @@ -86,7 +91,33 @@ fn main() -> io::Result<()> { // Load server settings let mut server_settings = server::Settings::load(&server_data_dir); - let editable_settings = server::EditableSettings::load(&server_data_dir); + let mut editable_settings = server::EditableSettings::load(&server_data_dir); + match matches.subcommand() { + ("admin", Some(sub_m)) => { + admin::admin_subcommand( + sub_m, + &server_settings, + &mut editable_settings, + &server_data_dir, + ); + return Ok(()); + }, + _ => {}, + } + + // Panic hook to ensure that console mode is set back correctly if in non-basic + // mode + if !basic { + let hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + Tui::shutdown(basic); + hook(info); + })); + } + + let tui = (!basic || interactive).then(|| Tui::run(basic)); + + info!("Starting server..."); if no_auth { server_settings.auth_server_address = None; @@ -106,6 +137,12 @@ fn main() -> io::Result<()> { let mut shutdown_coordinator = ShutdownCoordinator::new(Arc::clone(&sigusr1_signal)); + // Set up an fps clock + let mut clock = Clock::start(); + // Wait for a tick so we don't start with a zero dt + // TODO: consider integrating this into Clock::start? + clock.tick(Duration::from_millis(1000 / TPS)); + loop { // Terminate the server if instructed to do so by the shutdown coordinator if shutdown_coordinator.check(&mut server, &settings) { @@ -144,6 +181,12 @@ fn main() -> io::Result<()> { info!("Closing the server"); break; }, + Message::AddAdmin(username) => { + server.add_admin(&username); + }, + Message::RemoveAdmin(username) => { + server.remove_admin(&username); + }, }, Err(mpsc::TryRecvError::Empty) | Err(mpsc::TryRecvError::Disconnected) => {}, } diff --git a/server-cli/src/tui_runner.rs b/server-cli/src/tui_runner.rs index b3bc502583..834fd7fd62 100644 --- a/server-cli/src/tui_runner.rs +++ b/server-cli/src/tui_runner.rs @@ -26,6 +26,8 @@ pub enum Message { AbortShutdown, Shutdown { grace_period: Duration }, Quit, + AddAdmin(String), + RemoveAdmin(String), } pub struct Command<'a> { @@ -37,7 +39,8 @@ pub struct Command<'a> { pub cmd: fn(Vec, &mut mpsc::Sender), } -pub const COMMANDS: [Command; 4] = [ +// TODO: mabye we could be using clap here? +pub const COMMANDS: [Command; 5] = [ Command { name: "quit", description: "Closes the server", @@ -70,6 +73,22 @@ pub const COMMANDS: [Command; 4] = [ args: 0, cmd: |_, sender| sender.send(Message::AbortShutdown).unwrap(), }, + Command { + name: "admin", + description: "Add or remove an admin via \'admin add/remove \'", + split_spaces: true, + args: 2, + cmd: |args, sender| match args.get(..2) { + Some([op, username]) if op == "add" => { + sender.send(Message::AddAdmin(username.clone())).unwrap() + }, + Some([op, username]) if op == "remove" => { + sender.send(Message::RemoveAdmin(username.clone())).unwrap() + }, + Some(_) => error!("First arg must be add or remove"), + _ => error!("Not enough args, should be unreachable"), + }, + }, Command { name: "help", description: "List all command available", diff --git a/server/src/lib.rs b/server/src/lib.rs index e1ec27bab5..b43a19999c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -156,9 +156,6 @@ impl Server { state .ecs_mut() .insert(CharacterLoader::new(&persistence_db_dir)?); - state - .ecs_mut() - .insert(comp::AdminList(settings.admins.clone())); state.ecs_mut().insert(Vec::::new()); // System timers for performance monitoring @@ -935,6 +932,20 @@ impl Server { pub fn number_of_players(&self) -> i64 { self.state.ecs().read_storage::().join().count() as i64 } + + pub fn add_admin(&self, username: &str) { + let mut editable_settings = self.editable_settings_mut(); + let login_provider = self.state.ecs().fetch::(); + let data_dir = self.data_dir(); + add_admin(username, &login_provider, &mut editable_settings, &data_dir.path); + } + + pub fn remove_admin(&self, username: &str) { + let mut editable_settings = self.editable_settings_mut(); + let login_provider = self.state.ecs().fetch::(); + let data_dir = self.data_dir(); + remove_admin(username, &login_provider, &mut editable_settings, &data_dir.path); + } } impl Drop for Server { @@ -943,3 +954,52 @@ impl Drop for Server { .notify_registered_clients(ServerMsg::Disconnect(DisconnectReason::Shutdown)); } } + +pub fn add_admin( + username: &str, + login_provider: &LoginProvider, + editable_settings: &mut EditableSettings, + data_dir: &std::path::Path, +) { + use crate::settings::EditableSetting; + match login_provider.username_to_uuid(username) { + Ok(uuid) => editable_settings.admins.edit(data_dir, |admins| { + if admins.insert(uuid.clone()) { + info!("Successfully added {} ({}) as an admin!", username, uuid); + } else { + info!("{} ({}) is already an admin!", username, uuid); + } + }), + Err(err) => error!( + ?err, + "Could not find uuid for this name either the user does not exist or \ + there was an error communicating with the auth server." + ), + } +} + +pub fn remove_admin( + username: &str, + login_provider: &LoginProvider, + editable_settings: &mut EditableSettings, + data_dir: &std::path::Path, +) { + use crate::settings::EditableSetting; + match login_provider.username_to_uuid(username) { + Ok(uuid) => editable_settings.admins.edit(data_dir, |admins| { + if admins.remove(&uuid) { + info!( + "Successfully removed {} ({}) from the admins", + username, uuid + ); + } else { + info!("{} ({}) is not an admin!", username, uuid); + } + }), + Err(err) => error!( + ?err, + "Could not find uuid for this name either the user does not exist or \ + there was an error communicating with the auth server." + ), + } +} diff --git a/server/src/login_provider.rs b/server/src/login_provider.rs index 47c46698e1..30d84c510a 100644 --- a/server/src/login_provider.rs +++ b/server/src/login_provider.rs @@ -53,7 +53,7 @@ impl LoginProvider { pub fn try_login( &mut self, username_or_token: &str, - admins: &[String], + admins: &HashSet, whitelist: &HashSet, banlist: &HashMap, ) -> Result<(String, Uuid), RegisterError> { @@ -70,7 +70,7 @@ impl LoginProvider { // user can only join if he is admin, the whitelist is empty (everyone can join) // or his name is in the whitelist - if !whitelist.is_empty() && !whitelist.contains(&uuid) && !admins.contains(&username) { + if !whitelist.is_empty() && !whitelist.contains(&uuid) && !admins.contains(&uuid) { return Err(RegisterError::NotOnWhitelist); } diff --git a/server/src/settings.rs b/server/src/settings.rs index 92a3be8ecd..c2d18f7179 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -22,6 +22,7 @@ const SETTINGS_FILENAME: &str = "settings.ron"; const WHITELIST_FILENAME: &str = "whitelist.ron"; const BANLIST_FILENAME: &str = "banlist.ron"; const SERVER_DESCRIPTION_FILENAME: &str = "description.ron"; +const ADMINS_FILENAME: &str = "admins.ron"; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] @@ -34,7 +35,6 @@ pub struct Settings { //pub pvp_enabled: bool, pub server_name: String, pub start_time: f64, - pub admins: Vec, /// When set to None, loads the default map file (if available); otherwise, /// uses the value of the file options to decide how to proceed. pub map_file: Option, @@ -57,7 +57,6 @@ impl Default for Settings { max_players: 100, start_time: 9.0 * 3600.0, map_file: None, - admins: Vec::new(), persistence_db_dir: "saves".into(), max_view_distance: Some(30), banned_words_files: Vec::new(), @@ -137,8 +136,6 @@ impl Settings { server_name: "Singleplayer".to_owned(), max_players: 100, start_time: 9.0 * 3600.0, - admins: vec!["singleplayer".to_string()], /* TODO: Let the player choose if they want - * to use admin commands or not */ max_view_distance: None, client_timeout: Duration::from_secs(180), ..load // Fill in remaining fields from server_settings.ron. @@ -179,18 +176,28 @@ pub struct BanRecord { #[derive(Deserialize, Serialize, Default)] #[serde(transparent)] pub struct Whitelist(HashSet); + #[derive(Deserialize, Serialize, Default)] #[serde(transparent)] pub struct Banlist(HashMap); + #[derive(Deserialize, Serialize)] #[serde(transparent)] pub struct ServerDescription(String); +impl Default for ServerDescription { + fn default() -> Self { Self("This is the best Veloren server".into()) } +} + +#[derive(Deserialize, Serialize, Default)] +#[serde(transparent)] +pub struct Admins(HashSet); /// Combines all the editable settings into one struct that is stored in the ecs pub struct EditableSettings { pub whitelist: Whitelist, pub banlist: Banlist, pub server_description: ServerDescription, + pub admins: Admins, } impl EditableSettings { @@ -199,6 +206,7 @@ impl EditableSettings { whitelist: Whitelist::load(data_dir), banlist: Banlist::load(data_dir), server_description: ServerDescription::load(data_dir), + admins: Admins::load(data_dir), } } @@ -206,15 +214,21 @@ impl EditableSettings { let load = Self::load(data_dir); Self { server_description: ServerDescription("Who needs friends anyway?".into()), + // TODO: Let the player choose if they want to use admin commands or not + admins: Admins( + std::iter::once( + // TODO: hacky + crate::login_provider::LoginProvider::new(None) + .username_to_uuid("singleplayer") + .unwrap(), + ) + .collect(), + ), ..load } } } -impl Default for ServerDescription { - fn default() -> Self { Self("This is the best Veloren server".into()) } -} - impl EditableSetting for Whitelist { const FILENAME: &'static str = WHITELIST_FILENAME; } @@ -227,6 +241,10 @@ impl EditableSetting for ServerDescription { const FILENAME: &'static str = SERVER_DESCRIPTION_FILENAME; } +impl EditableSetting for Admins { + const FILENAME: &'static str = ADMINS_FILENAME; +} + impl Deref for Whitelist { type Target = HashSet; @@ -256,3 +274,13 @@ impl Deref for ServerDescription { impl DerefMut for ServerDescription { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } + +impl Deref for Admins { + type Target = HashSet; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for Admins { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 6f13bfa00b..de131a5a9b 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -10,7 +10,7 @@ use crate::{ }; use common::{ comp::{ - Admin, AdminList, CanBuild, ChatMode, ChatType, ControlEvent, Controller, ForceUpdate, Ori, + Admin, CanBuild, ChatMode, ChatType, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats, UnresolvedChatMsg, Vel, }, event::{EventBus, ServerEvent}, @@ -57,7 +57,6 @@ impl Sys { chat_modes: &ReadStorage<'_, ChatMode>, login_provider: &mut WriteExpect<'_, LoginProvider>, block_changes: &mut Write<'_, BlockChange>, - admin_list: &ReadExpect<'_, AdminList>, admins: &mut WriteStorage<'_, Admin>, positions: &mut WriteStorage<'_, Pos>, velocities: &mut WriteStorage<'_, Vel>, @@ -99,7 +98,7 @@ impl Sys { } => { let (username, uuid) = match login_provider.try_login( &token_or_username, - &settings.admins, + &*editable_settings.admins, &*editable_settings.whitelist, &*editable_settings.banlist, ) { @@ -112,8 +111,8 @@ impl Sys { let vd = view_distance.map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd))); + let is_admin = editable_settings.admins.contains(&uuid); let player = Player::new(username.clone(), None, vd, uuid); - let is_admin = admin_list.contains(&username); if !player.is_valid() { // Invalid player @@ -441,7 +440,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, ChatMode>, WriteExpect<'a, LoginProvider>, Write<'a, BlockChange>, - ReadExpect<'a, AdminList>, WriteStorage<'a, Admin>, WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, @@ -475,7 +473,6 @@ impl<'a> System<'a> for Sys { chat_modes, mut accounts, mut block_changes, - admin_list, mut admins, mut positions, mut velocities, @@ -537,7 +534,6 @@ impl<'a> System<'a> for Sys { &chat_modes, &mut accounts, &mut block_changes, - &admin_list, &mut admins, &mut positions, &mut velocities, From 39f78e91163866edd62a36c5f44d95008625a554 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 10 Oct 2020 02:15:54 -0400 Subject: [PATCH 14/15] Remove saves dir env var and setting from server settings and just always use saves as the dir name --- server/src/lib.rs | 5 +++-- server/src/settings.rs | 16 ---------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index b43a19999c..3b52937a57 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -120,8 +120,9 @@ impl Server { info!("Authentication is disabled"); } - // persistence_db_dir is relative to data_dir - let persistence_db_dir = data_dir.join(&settings.persistence_db_dir); + // Relative to data_dir + const PERSISTENCE_DB_DIR: &str = "saves"; + let persistence_db_dir = data_dir.join(PERSISTENCE_DB_DIR); // Run pending DB migrations (if any) debug!("Running DB migrations..."); diff --git a/server/src/settings.rs b/server/src/settings.rs index c2d18f7179..379ca1d15e 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -38,8 +38,6 @@ pub struct Settings { /// When set to None, loads the default map file (if available); otherwise, /// uses the value of the file options to decide how to proceed. pub map_file: Option, - /// Relative paths are relative to the server data dir - pub persistence_db_dir: PathBuf, pub max_view_distance: Option, pub banned_words_files: Vec, pub max_player_group_size: u32, @@ -57,7 +55,6 @@ impl Default for Settings { max_players: 100, start_time: 9.0 * 3600.0, map_file: None, - persistence_db_dir: "saves".into(), max_view_distance: Some(30), banned_words_files: Vec::new(), max_player_group_size: 6, @@ -96,7 +93,6 @@ impl Settings { } default_settings } - .apply_saves_dir_override() } fn save_to_file(&self, path: &Path) -> std::io::Result<()> { @@ -147,18 +143,6 @@ impl Settings { path.push(SETTINGS_FILENAME); path } - - fn apply_saves_dir_override(mut self) -> Self { - if let Some(saves_dir) = std::env::var_os("VELOREN_SAVES_DIR") { - let path = PathBuf::from(&saves_dir); - if path.exists() || path.parent().map(|x| x.exists()).unwrap_or(false) { - self.persistence_db_dir = path; - } else { - warn!(?saves_dir, "VELOREN_SAVES_DIR points to an invalid path."); - } - } - self - } } fn with_config_dir(path: &Path) -> PathBuf { From fa45edb54194f393750f0796cb0ceb9f36252d8f Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 10 Oct 2020 03:01:30 -0400 Subject: [PATCH 15/15] Fix clippy, minor fmt, add TODO comments --- server-cli/src/main.rs | 1 + server/src/lib.rs | 30 +++++++++++++++++++++--------- server/src/sys/message.rs | 4 ++-- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 769d081933..73bdc649a4 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -92,6 +92,7 @@ fn main() -> io::Result<()> { // Load server settings let mut server_settings = server::Settings::load(&server_data_dir); let mut editable_settings = server::EditableSettings::load(&server_data_dir); + #[allow(clippy::single_match)] // Note: remove this when there are more subcommands match matches.subcommand() { ("admin", Some(sub_m)) => { admin::admin_subcommand( diff --git a/server/src/lib.rs b/server/src/lib.rs index 3b52937a57..2cb6b1e1d9 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -934,18 +934,30 @@ impl Server { self.state.ecs().read_storage::().join().count() as i64 } + // TODO: add Admin comp if ingame pub fn add_admin(&self, username: &str) { let mut editable_settings = self.editable_settings_mut(); let login_provider = self.state.ecs().fetch::(); let data_dir = self.data_dir(); - add_admin(username, &login_provider, &mut editable_settings, &data_dir.path); + add_admin( + username, + &login_provider, + &mut editable_settings, + &data_dir.path, + ); } + // TODO: remove Admin comp if ingame pub fn remove_admin(&self, username: &str) { let mut editable_settings = self.editable_settings_mut(); let login_provider = self.state.ecs().fetch::(); let data_dir = self.data_dir(); - remove_admin(username, &login_provider, &mut editable_settings, &data_dir.path); + remove_admin( + username, + &login_provider, + &mut editable_settings, + &data_dir.path, + ); } } @@ -958,14 +970,14 @@ impl Drop for Server { pub fn add_admin( username: &str, - login_provider: &LoginProvider, + login_provider: &LoginProvider, editable_settings: &mut EditableSettings, data_dir: &std::path::Path, ) { use crate::settings::EditableSetting; match login_provider.username_to_uuid(username) { Ok(uuid) => editable_settings.admins.edit(data_dir, |admins| { - if admins.insert(uuid.clone()) { + if admins.insert(uuid) { info!("Successfully added {} ({}) as an admin!", username, uuid); } else { info!("{} ({}) is already an admin!", username, uuid); @@ -973,15 +985,15 @@ pub fn add_admin( }), Err(err) => error!( ?err, - "Could not find uuid for this name either the user does not exist or \ - there was an error communicating with the auth server." + "Could not find uuid for this name either the user does not exist or there was an \ + error communicating with the auth server." ), } } pub fn remove_admin( username: &str, - login_provider: &LoginProvider, + login_provider: &LoginProvider, editable_settings: &mut EditableSettings, data_dir: &std::path::Path, ) { @@ -999,8 +1011,8 @@ pub fn remove_admin( }), Err(err) => error!( ?err, - "Could not find uuid for this name either the user does not exist or \ - there was an error communicating with the auth server." + "Could not find uuid for this name either the user does not exist or there was an \ + error communicating with the auth server." ), } } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index de131a5a9b..ed6eb8ee4a 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -10,8 +10,8 @@ use crate::{ }; use common::{ comp::{ - Admin, CanBuild, ChatMode, ChatType, ControlEvent, Controller, ForceUpdate, Ori, - Player, Pos, Stats, UnresolvedChatMsg, Vel, + Admin, CanBuild, ChatMode, ChatType, ControlEvent, Controller, ForceUpdate, Ori, Player, + Pos, Stats, UnresolvedChatMsg, Vel, }, event::{EventBus, ServerEvent}, msg::{