Merge branch 'imbris/random-fixes2' into 'master'

Random fixes 2.0

Closes #774 and #711

See merge request veloren/veloren!1396
This commit is contained in:
Imbris 2020-10-10 19:46:39 +00:00
commit 0e4b31fb63
51 changed files with 1045 additions and 392 deletions

1
.gitignore vendored
View File

@ -30,6 +30,7 @@ run.sh
maps
screenshots
todo.txt
userdata
# Export data
*.csv

View File

@ -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:

View File

@ -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 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

4
Cargo.lock generated
View File

@ -4643,6 +4643,7 @@ dependencies = [
"authc",
"criterion",
"crossbeam",
"directories-next",
"dot_vox",
"enum-iterator",
"hashbrown",
@ -4712,6 +4713,8 @@ dependencies = [
"clap",
"crossterm",
"lazy_static",
"ron",
"serde",
"signal-hook",
"tracing",
"tracing-subscriber",
@ -4760,7 +4763,6 @@ dependencies = [
"rodio",
"ron",
"serde",
"serde_derive",
"specs",
"specs-idvs",
"tracing",

View File

@ -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
@ -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);
@ -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);
// 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);

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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 =

View File

@ -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"] }

View File

@ -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<Self>;
}
/// 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<String>);
impl Deref for AdminList {
type Target = Vec<String>;
fn deref(&self) -> &Vec<String> { &self.0 }
}

View File

@ -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::{

View File

@ -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<ServerEvent>>,
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);

View File

@ -1,6 +1,6 @@
use crate::{
comp::{
projectile, Damage, DamageSource, Energy, EnergySource, HealthChange, HealthSource,
projectile, Damage, DamageSource, Energy, EnergySource, Group, HealthChange, HealthSource,
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;
}

View File

@ -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"));

View File

@ -0,0 +1,75 @@
use std::path::PathBuf;
const VELOREN_USERDATA_ENV: &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" => <executable dir>/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" => <executable dir>/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"),
)
};
}

View File

@ -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 }

View File

@ -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:

28
server-cli/src/admin.rs Normal file
View File

@ -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"
),
}
}

58
server-cli/src/logging.rs Normal file
View File

@ -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();
}
}
}

View File

@ -1,129 +1,134 @@
#![deny(unsafe_code)]
#![deny(clippy::clone_on_ref_ptr)]
#![feature(bool_to_option)]
mod admin;
mod logging;
mod settings;
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, SubCommand};
use common::clock::Clock;
use server::{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 server::{Event, Input, Server};
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")
.version(common::util::DISPLAY_VERSION_LONG.as_str())
.author("The veloren devs <https://gitlab.com/veloren/veloren>")
.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),
.help("Disables the tui"),
Arg::with_name("interactive")
.short("i")
.long("interactive")
.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));
let (mut tui, msg_r) = Tui::new();
logging::init(basic);
// 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())
// Load settings
let settings = settings::Settings::load();
// Determine folder to save server data in
let server_data_dir = {
let mut path = common::userdata_dir_workspace!();
path.push(server::DEFAULT_DATA_DIR_NAME);
path
};
#[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
// 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(
sub_m,
&server_settings,
&mut editable_settings,
&server_data_dir,
);
return Ok(());
},
_ => 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();
}
_ => {},
}
// 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);
}));
if !basic {
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
Tui::shutdown(basic);
hook(info);
}));
}
tui.run(basic);
let tui = (!basic || interactive).then(|| Tui::run(basic));
info!("Starting server...");
// Set up an fps clock
let mut clock = Clock::start();
if no_auth {
server_settings.auth_server_address = None;
}
// Load settings
let settings = ServerSettings::load();
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).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,
@ -133,9 +138,15 @@ 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) {
if shutdown_coordinator.check(&mut server, &settings) {
break;
}
@ -156,22 +167,31 @@ 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::AddAdmin(username) => {
server.add_admin(&username);
},
Message::RemoveAdmin(username) => {
server.remove_admin(&username);
},
},
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));

View File

@ -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
}

View File

@ -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

View File

@ -1,4 +1,4 @@
use crate::LOG;
use crate::logging::LOG;
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture},
execute,
@ -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<String>, &mut mpsc::Sender<Message>),
}
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 <username>\'",
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",
@ -86,26 +105,13 @@ pub const COMMANDS: [Command; 4] = [
];
pub struct Tui {
pub msg_r: mpsc::Receiver<Message>,
background: Option<std::thread::JoinHandle<()>>,
basic: bool,
msg_s: Option<mpsc::Sender<Message>>,
running: Arc<AtomicBool>,
}
impl Tui {
pub fn new() -> (Self, mpsc::Receiver<Message>) {
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<Message>) {
use crossterm::event::*;
if let Event::Key(event) = read().unwrap() {
@ -132,15 +138,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 = Arc::clone(&running);
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 +168,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 +187,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 +234,14 @@ impl Tui {
Self::handle_events(&mut input, &mut msg_s);
};
}
}));
}))
};
Self {
msg_r,
background,
basic,
running,
}
}

View File

@ -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, 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.settings().server_description.clone()),
ChatType::CommandError.server_msg((*server.editable_settings().server_description).clone()),
);
}
@ -277,18 +281,23 @@ 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());
.editable_settings_mut()
.server_description
.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
.editable_settings_mut()
.server_description
.edit(data_dir.as_ref(), |d| d.clear());
server.notify_client(
client,
ChatType::CommandError.server_msg("Removed server description".to_string()),
@ -1823,24 +1832,48 @@ fn handle_whitelist(
action: &ChatCommand,
) {
if let Ok((whitelist_action, username)) = scan_fmt!(&args, &action.arg_fmt(), String, String) {
if whitelist_action.eq_ignore_ascii_case("add") {
let lookup_uuid = || {
server
.settings_mut()
.edit(|s| s.whitelist.push(username.clone()));
server.notify_client(
client,
ChatType::CommandInfo.server_msg(format!("\"{}\" added to whitelist", username)),
);
.state
.ecs()
.read_resource::<LoginProvider>()
.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") {
if let Some(uuid) = lookup_uuid() {
server
.editable_settings_mut()
.whitelist
.edit(server.data_dir().as_ref(), |w| w.insert(uuid));
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.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.remove(&uuid));
server.notify_client(
client,
ChatType::CommandInfo
.server_msg(format!("\"{}\" removed from whitelist", username)),
);
}
} else {
server.notify_client(
client,
@ -1926,17 +1959,22 @@ fn handle_ban(
.username_to_uuid(&target_alias);
if let Ok(uuid) = uuid_result {
if server.settings().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.settings_mut().edit(|s| {
s.banlist
.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!(
@ -1987,9 +2025,12 @@ fn handle_unban(
.username_to_uuid(&username);
if let Ok(uuid) = uuid_result {
server.settings_mut().edit(|s| {
s.banlist.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)),

13
server/src/data_dir.rs Normal file
View File

@ -0,0 +1,13 @@
use std::path::{Path, PathBuf};
/// Used so that different server frontends can share the same server saves,
/// etc.
pub const DEFAULT_DATA_DIR_NAME: &str = "server";
/// Indicates where maps, saves, and server_config folders are to be stored
pub struct DataDir {
pub path: PathBuf,
}
impl AsRef<Path> for DataDir {
fn as_ref(&self) -> &Path { &self.path }
}

View File

@ -9,6 +9,7 @@ mod character_creator;
pub mod chunk_generator;
pub mod client;
pub mod cmd;
mod data_dir;
pub mod error;
pub mod events;
pub mod input;
@ -21,13 +22,20 @@ 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::{
data_dir::DEFAULT_DATA_DIR_NAME,
error::Error,
events::Event,
input::Input,
settings::{EditableSettings, Settings},
};
use crate::{
alias_validator::AliasValidator,
chunk_generator::ChunkGenerator,
client::{Client, RegionSubscription},
cmd::ChatCommandExt,
data_dir::DataDir,
login_provider::LoginProvider,
state_ext::StateExt,
sys::sentinel::{DeletedEntities, TrackedComps},
@ -102,10 +110,23 @@ 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<Self, Error> {
pub fn new(
settings: Settings,
editable_settings: EditableSettings,
data_dir: &std::path::Path,
) -> Result<Self, Error> {
info!("Server is data dir is: {}", data_dir.display());
if settings.auth_server_address.is_none() {
info!("Authentication is disabled");
}
// 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...");
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 +137,10 @@ impl Server {
let mut state = State::default();
state.ecs_mut().insert(settings.clone());
state.ecs_mut().insert(editable_settings);
state.ecs_mut().insert(DataDir {
path: data_dir.to_owned(),
});
state.ecs_mut().insert(EventBus::<ServerEvent>::default());
state
.ecs_mut()
@ -128,13 +153,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())?);
state
.ecs_mut()
.insert(comp::AdminList(settings.admins.clone()));
.insert(CharacterLoader::new(&persistence_db_dir)?);
state.ecs_mut().insert(Vec::<Outcome>::new());
// System timers for performance monitoring
@ -339,10 +361,11 @@ impl Server {
}
pub fn get_server_info(&self) -> ServerInfo {
let settings = self.state.ecs().fetch::<ServerSettings>();
let settings = self.state.ecs().fetch::<Settings>();
let editable_settings = self.state.ecs().fetch::<EditableSettings>();
ServerInfo {
name: settings.server_name.clone(),
description: settings.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(),
@ -355,13 +378,28 @@ impl Server {
}
/// Get a reference to the server's settings
pub fn settings(&self) -> impl Deref<Target = ServerSettings> + '_ {
self.state.ecs().fetch::<ServerSettings>()
pub fn settings(&self) -> impl Deref<Target = Settings> + '_ {
self.state.ecs().fetch::<Settings>()
}
/// Get a mutable reference to the server's settings
pub fn settings_mut(&mut self) -> impl DerefMut<Target = ServerSettings> + '_ {
self.state.ecs_mut().fetch_mut::<ServerSettings>()
pub fn settings_mut(&self) -> impl DerefMut<Target = Settings> + '_ {
self.state.ecs().fetch_mut::<Settings>()
}
/// Get a mutable reference to the server's editable settings
pub fn editable_settings_mut(&self) -> impl DerefMut<Target = EditableSettings> + '_ {
self.state.ecs().fetch_mut::<EditableSettings>()
}
/// Get a reference to the server's editable settings
pub fn editable_settings(&self) -> impl Deref<Target = EditableSettings> + '_ {
self.state.ecs().fetch::<EditableSettings>()
}
/// Get path to the directory that the server info into
pub fn data_dir(&self) -> impl Deref<Target = DataDir> + '_ {
self.state.ecs().fetch::<DataDir>()
}
/// Get a reference to the server's game state.
@ -895,6 +933,32 @@ impl Server {
pub fn number_of_players(&self) -> i64 {
self.state.ecs().read_storage::<Client>().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::<LoginProvider>();
let data_dir = self.data_dir();
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::<LoginProvider>();
let data_dir = self.data_dir();
remove_admin(
username,
&login_provider,
&mut editable_settings,
&data_dir.path,
);
}
}
impl Drop for Server {
@ -903,3 +967,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) {
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."
),
}
}

View File

@ -1,6 +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};
@ -52,8 +53,9 @@ impl LoginProvider {
pub fn try_login(
&mut self,
username_or_token: &str,
whitelist: &[String],
banlist: &HashMap<Uuid, (String, String)>,
admins: &HashSet<Uuid>,
whitelist: &HashSet<Uuid>,
banlist: &HashMap<Uuid, BanRecord>,
) -> Result<(String, Uuid), RegisterError> {
self
// resolve user information
@ -63,12 +65,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) && !admins.contains(&uuid) {
return Err(RegisterError::NotOnWhitelist);
}

View File

@ -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)

View File

@ -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<Vec<CharacterItem>, Error>;
@ -64,11 +65,11 @@ pub struct CharacterLoader {
}
impl CharacterLoader {
pub fn new(db_dir: String) -> diesel::QueryResult<Self> {
pub fn new(db_dir: &Path) -> diesel::QueryResult<Self> {
let (update_tx, internal_rx) = channel::unbounded::<CharacterLoaderRequest>();
let (internal_tx, update_rx) = channel::unbounded::<CharacterLoaderResponse>();
let mut conn = establish_connection(&db_dir)?;
let mut conn = establish_connection(db_dir)?;
std::thread::spawn(move || {
for request in internal_rx {

View File

@ -3,8 +3,8 @@ 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 std::{path::Path, sync::Arc};
use tracing::{error, trace};
pub type CharacterUpdateData = (comp::Stats, comp::Inventory, comp::Loadout);
@ -19,17 +19,17 @@ pub struct CharacterUpdater {
}
impl CharacterUpdater {
pub fn new(db_dir: String) -> diesel::QueryResult<Self> {
pub fn new(db_dir: &Path) -> diesel::QueryResult<Self> {
let (update_tx, update_rx) =
channel::unbounded::<Vec<(CharacterId, CharacterUpdateData)>>();
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() {
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");
}
});

View File

@ -14,13 +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::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);
@ -45,9 +43,8 @@ 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> {
let db_dir = &apply_saves_dir_override(db_dir);
let _ = fs::create_dir(format!("{}/", db_dir));
pub fn run_migrations(db_dir: &Path) -> Result<(), diesel_migrations::RunMigrationsError> {
let _ = fs::create_dir(format!("{}/", db_dir.display()));
embedded_migrations::run_with_output(
&establish_connection(db_dir)
@ -91,9 +88,8 @@ impl<'a> core::ops::Deref for VelorenTransaction<'a> {
fn deref(&self) -> &Self::Target { &self.0 }
}
pub fn establish_connection(db_dir: &str) -> QueryResult<VelorenConnection> {
let db_dir = &apply_saves_dir_override(db_dir);
let database_url = format!("{}/db.sqlite", db_dir);
pub fn establish_connection(db_dir: &Path) -> QueryResult<VelorenConnection> {
let database_url = format!("{}/db.sqlite", db_dir.display());
let connection = SqliteConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url));
@ -116,17 +112,3 @@ pub fn establish_connection(db_dir: &str) -> QueryResult<VelorenConnection> {
Ok(VelorenConnection(connection))
}
fn apply_saves_dir_override(db_dir: &str) -> String {
if let Some(saves_dir) = env::var_os("VELOREN_SAVES_DIR") {
let path = PathBuf::from(saves_dir.clone());
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();
}
}
warn!(?saves_dir, "VELOREN_SAVES_DIR points to an invalid path.");
}
db_dir.to_string()
}

View File

@ -1,16 +1,32 @@
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::{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: &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";
const ADMINS_FILENAME: &str = "admins.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<String>,
@ -18,85 +34,83 @@ 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<String>,
pub whitelist: Vec<String>,
pub banlist: HashMap<Uuid, (String, String)>,
/// 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<FileOpts>,
pub persistence_db_dir: String,
pub max_view_distance: Option<u32>,
pub banned_words_files: Vec<PathBuf>,
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 {
impl Default for Settings {
fn default() -> Self {
Self {
gameserver_address: SocketAddr::from(([0; 4], 14004)),
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(),
max_view_distance: Some(30),
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(),
}
}
}
impl ServerSettings {
#[allow(clippy::single_match)] // TODO: Pending review in #587
pub fn load() -> Self {
let path = ServerSettings::get_settings_path();
impl Settings {
/// 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 +130,141 @@ 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
* 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<R>(&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);
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(HashSet<Uuid>);
#[derive(Deserialize, Serialize, Default)]
#[serde(transparent)]
pub struct Banlist(HashMap<Uuid, BanRecord>);
#[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<Uuid>);
/// 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 {
pub fn load(data_dir: &Path) -> Self {
Self {
whitelist: Whitelist::load(data_dir),
banlist: Banlist::load(data_dir),
server_description: ServerDescription::load(data_dir),
admins: Admins::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()),
// 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 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 EditableSetting for Admins {
const FILENAME: &'static str = ADMINS_FILENAME;
}
impl Deref for Whitelist {
type Target = HashSet<Uuid>;
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<Uuid, BanRecord>;
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 }
}
impl Deref for Admins {
type Target = HashSet<Uuid>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl DerefMut for Admins {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}

View File

@ -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<R>(&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<S: Serialize>(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<S: EditableSetting>(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
}

View File

@ -6,12 +6,12 @@ use crate::{
login_provider::LoginProvider,
metrics::{NetworkRequestMetrics, PlayerMetrics},
persistence::character_loader::CharacterLoader,
ServerSettings,
EditableSettings, Settings,
};
use common::{
comp::{
Admin, AdminList, 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::{
@ -57,15 +57,15 @@ 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>,
orientations: &mut WriteStorage<'_, Ori>,
players: &mut WriteStorage<'_, Player>,
controllers: &mut WriteStorage<'_, Controller>,
settings: &Read<'_, ServerSettings>,
settings: &Read<'_, Settings>,
alias_validator: &ReadExpect<'_, AliasValidator>,
editable_settings: &EditableSettings,
) -> Result<(), crate::error::Error> {
loop {
let msg = client.recv().await?;
@ -98,8 +98,9 @@ impl Sys {
} => {
let (username, uuid) = match login_provider.try_login(
&token_or_username,
&settings.whitelist,
&settings.banlist,
&*editable_settings.admins,
&*editable_settings.whitelist,
&*editable_settings.banlist,
) {
Err(err) => {
client.error_state(RequestStateError::RegisterDenied(err));
@ -110,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
@ -206,11 +207,10 @@ impl Sys {
});
// Give the player a welcome message
if !settings.server_description.is_empty() {
client.notify(
ChatType::CommandInfo
.server_msg(settings.server_description.clone()),
);
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
@ -440,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>,
@ -448,8 +447,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, EditableSettings>,
);
#[allow(clippy::match_ref_pats)] // TODO: Pending review in #587
@ -473,7 +473,6 @@ impl<'a> System<'a> for Sys {
chat_modes,
mut accounts,
mut block_changes,
admin_list,
mut admins,
mut positions,
mut velocities,
@ -483,6 +482,7 @@ impl<'a> System<'a> for Sys {
mut controllers,
settings,
alias_validator,
editable_settings,
): Self::SystemData,
) {
span!(_guard, "run", "message::Sys::run");
@ -534,7 +534,6 @@ impl<'a> System<'a> for Sys {
&chat_modes,
&mut accounts,
&mut block_changes,
&admin_list,
&mut admins,
&mut positions,
&mut velocities,
@ -543,6 +542,7 @@ impl<'a> System<'a> for Sys {
&mut controllers,
&settings,
&alias_validator,
&editable_settings,
);
select!(
_ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()),

View File

@ -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"
@ -66,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" }

View File

@ -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)]

View File

@ -1,4 +1,4 @@
use serde_derive::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq)]
pub enum Slot {

View File

@ -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::*;

View File

@ -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,

View File

@ -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;

View File

@ -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 serde::{Deserialize, Serialize};
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
}
}

View File

@ -62,7 +62,7 @@ pub trait Pipeline {
type Vertex: Clone + gfx::traits::Pod + gfx::pso::buffer::Structure<gfx::format::Format>;
}
use serde_derive::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
/// Anti-aliasing modes
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum AaMode {

View File

@ -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,

View File

@ -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::*;

View File

@ -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()
};

View File

@ -5,10 +5,10 @@ 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};
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)]
@ -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 {
@ -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) {
@ -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.");
}
},
@ -787,31 +787,34 @@ 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 {
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
}

View File

@ -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::{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 = {
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 = 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) {
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 \

View File

@ -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.

View File

@ -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::*;