mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
0e4b31fb63
1
.gitignore
vendored
1
.gitignore
vendored
@ -30,6 +30,7 @@ run.sh
|
||||
maps
|
||||
screenshots
|
||||
todo.txt
|
||||
userdata
|
||||
|
||||
# Export data
|
||||
*.csv
|
||||
|
@ -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:
|
||||
|
@ -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
4
Cargo.lock
generated
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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 =
|
||||
|
@ -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"] }
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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::{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"));
|
||||
|
75
common/src/util/userdata_dir.rs
Normal file
75
common/src/util/userdata_dir.rs
Normal 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"),
|
||||
)
|
||||
};
|
||||
}
|
@ -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 }
|
||||
|
@ -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
28
server-cli/src/admin.rs
Normal 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
58
server-cli/src/logging.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
75
server-cli/src/settings.rs
Normal file
75
server-cli/src/settings.rs
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
13
server/src/data_dir.rs
Normal 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 }
|
||||
}
|
@ -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."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
89
server/src/settings/editable.rs
Normal file
89
server/src/settings/editable.rs
Normal 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
|
||||
}
|
@ -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(()),
|
||||
|
@ -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" }
|
||||
|
@ -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)]
|
||||
|
@ -1,4 +1,4 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Slot {
|
||||
|
@ -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::*;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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::*;
|
||||
|
@ -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()
|
||||
};
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 \
|
||||
|
@ -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.
|
||||
|
@ -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::*;
|
||||
|
Loading…
Reference in New Issue
Block a user