2020-10-05 07:41:58 +00:00
|
|
|
mod editable;
|
|
|
|
|
|
|
|
pub use editable::EditableSetting;
|
|
|
|
|
2020-08-11 17:02:21 +00:00
|
|
|
use authc::Uuid;
|
2020-10-06 03:42:09 +00:00
|
|
|
use hashbrown::{HashMap, HashSet};
|
2019-10-11 12:19:55 +00:00
|
|
|
use portpicker::pick_unused_port;
|
2020-07-06 14:23:08 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2020-10-05 07:41:58 +00:00
|
|
|
use std::{
|
|
|
|
fs,
|
|
|
|
net::SocketAddr,
|
|
|
|
ops::{Deref, DerefMut},
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
time::Duration,
|
|
|
|
};
|
2020-06-21 14:26:06 +00:00
|
|
|
use tracing::{error, warn};
|
2020-01-18 18:41:37 +00:00
|
|
|
use world::sim::FileOpts;
|
|
|
|
|
2021-03-27 15:09:34 +00:00
|
|
|
const DEFAULT_WORLD_SEED: u32 = 25269;
|
2020-10-06 02:59:47 +00:00
|
|
|
const CONFIG_DIR: &str = "server_config";
|
2020-10-05 08:56:28 +00:00
|
|
|
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";
|
2020-10-10 06:10:04 +00:00
|
|
|
const ADMINS_FILENAME: &str = "admins.ron";
|
2019-06-29 16:41:26 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(default)]
|
2020-10-06 02:59:47 +00:00
|
|
|
pub struct Settings {
|
2019-10-11 12:19:55 +00:00
|
|
|
pub gameserver_address: SocketAddr,
|
|
|
|
pub metrics_address: SocketAddr,
|
2019-12-21 17:02:39 +00:00
|
|
|
pub auth_server_address: Option<String>,
|
2019-07-01 11:19:26 +00:00
|
|
|
pub max_players: usize,
|
2019-06-29 16:41:26 +00:00
|
|
|
pub world_seed: u32,
|
|
|
|
//pub pvp_enabled: bool,
|
2019-07-01 09:37:17 +00:00
|
|
|
pub server_name: String,
|
2019-07-12 13:03:35 +00:00
|
|
|
pub start_time: f64,
|
2020-02-01 20:39:39 +00:00
|
|
|
/// When set to None, loads the default map file (if available); otherwise,
|
|
|
|
/// uses the value of the file options to decide how to proceed.
|
2020-01-18 18:41:37 +00:00
|
|
|
pub map_file: Option<FileOpts>,
|
2020-06-25 11:20:09 +00:00
|
|
|
pub max_view_distance: Option<u32>,
|
2020-07-16 14:05:35 +00:00
|
|
|
pub banned_words_files: Vec<PathBuf>,
|
2020-08-07 01:59:28 +00:00
|
|
|
pub max_player_group_size: u32,
|
2020-09-06 19:24:52 +00:00
|
|
|
pub client_timeout: Duration,
|
2021-03-25 22:09:27 +00:00
|
|
|
pub spawn_town: Option<String>,
|
2021-04-17 21:54:11 +00:00
|
|
|
pub safe_spawn: bool,
|
2019-06-29 16:41:26 +00:00
|
|
|
}
|
|
|
|
|
2020-10-06 02:59:47 +00:00
|
|
|
impl Default for Settings {
|
2019-06-29 16:41:26 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2019-10-11 12:19:55 +00:00
|
|
|
gameserver_address: SocketAddr::from(([0; 4], 14004)),
|
|
|
|
metrics_address: SocketAddr::from(([0; 4], 14005)),
|
2021-03-11 15:57:50 +00:00
|
|
|
auth_server_address: Some("https://auth.veloren.net".into()),
|
2020-01-18 18:41:37 +00:00
|
|
|
world_seed: DEFAULT_WORLD_SEED,
|
2020-10-05 07:41:58 +00:00
|
|
|
server_name: "Veloren Alpha".into(),
|
2019-07-28 09:21:17 +00:00
|
|
|
max_players: 100,
|
2019-07-28 10:46:03 +00:00
|
|
|
start_time: 9.0 * 3600.0,
|
2019-12-11 09:14:50 +00:00
|
|
|
map_file: None,
|
2021-03-20 15:21:41 +00:00
|
|
|
max_view_distance: Some(65),
|
2020-07-16 14:05:35 +00:00
|
|
|
banned_words_files: Vec::new(),
|
2020-08-07 01:59:28 +00:00
|
|
|
max_player_group_size: 6,
|
2020-09-06 19:24:52 +00:00
|
|
|
client_timeout: Duration::from_secs(40),
|
2021-03-25 22:09:27 +00:00
|
|
|
spawn_town: None,
|
2021-04-17 21:54:11 +00:00
|
|
|
safe_spawn: true,
|
2019-06-29 16:41:26 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-01 09:37:17 +00:00
|
|
|
}
|
2019-06-29 16:41:26 +00:00
|
|
|
|
2020-10-06 02:59:47 +00:00
|
|
|
impl Settings {
|
2020-10-05 07:41:58 +00:00
|
|
|
/// path: Directory that contains the server config directory
|
|
|
|
pub fn load(path: &Path) -> Self {
|
|
|
|
let path = Self::get_settings_path(path);
|
2019-06-29 16:41:26 +00:00
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
if let Ok(file) = fs::File::open(&path) {
|
2019-07-04 17:37:56 +00:00
|
|
|
match ron::de::from_reader(file) {
|
|
|
|
Ok(x) => x,
|
|
|
|
Err(e) => {
|
2020-10-25 20:19:39 +00:00
|
|
|
let default_settings = Self::default();
|
|
|
|
let template_path = path.with_extension("template.ron");
|
2020-10-05 07:41:58 +00:00
|
|
|
warn!(
|
|
|
|
?e,
|
|
|
|
"Failed to parse setting file! Falling back to default settings and \
|
2020-10-25 20:19:39 +00:00
|
|
|
creating a template file for you to migrate your current settings file: \
|
|
|
|
{}",
|
|
|
|
template_path.display()
|
2020-10-05 07:41:58 +00:00
|
|
|
);
|
|
|
|
if let Err(e) = default_settings.save_to_file(&template_path) {
|
|
|
|
error!(?e, "Failed to create template settings file")
|
|
|
|
}
|
|
|
|
default_settings
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2019-07-04 17:37:56 +00:00
|
|
|
}
|
2019-06-29 16:41:26 +00:00
|
|
|
} else {
|
2019-07-04 17:37:56 +00:00
|
|
|
let default_settings = Self::default();
|
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
if let Err(e) = default_settings.save_to_file(&path) {
|
|
|
|
error!(?e, "Failed to create default settings file!");
|
2019-07-04 17:37:56 +00:00
|
|
|
}
|
|
|
|
default_settings
|
2019-06-29 16:41:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
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())
|
2019-10-23 18:23:31 +00:00
|
|
|
.expect("Failed serialize settings.");
|
2020-10-05 07:41:58 +00:00
|
|
|
|
|
|
|
fs::write(path, ron.as_bytes())?;
|
|
|
|
|
2019-06-29 16:41:26 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
/// path: Directory that contains the server config directory
|
|
|
|
pub fn singleplayer(path: &Path) -> Self {
|
|
|
|
let load = Self::load(&path);
|
2019-08-14 15:51:59 +00:00
|
|
|
Self {
|
2020-02-01 20:39:39 +00:00
|
|
|
//BUG: theoretically another process can grab the port between here and server
|
|
|
|
// creation, however the timewindow is quite small
|
2019-10-11 12:19:55 +00:00
|
|
|
gameserver_address: SocketAddr::from((
|
|
|
|
[127, 0, 0, 1],
|
|
|
|
pick_unused_port().expect("Failed to find unused port!"),
|
|
|
|
)),
|
|
|
|
metrics_address: SocketAddr::from((
|
|
|
|
[127, 0, 0, 1],
|
|
|
|
pick_unused_port().expect("Failed to find unused port!"),
|
|
|
|
)),
|
2019-12-21 17:02:39 +00:00
|
|
|
auth_server_address: None,
|
2020-01-24 02:45:29 +00:00
|
|
|
// If loading the default map file, make sure the seed is also default.
|
|
|
|
world_seed: if load.map_file.is_some() {
|
|
|
|
load.world_seed
|
|
|
|
} else {
|
|
|
|
DEFAULT_WORLD_SEED
|
|
|
|
},
|
2019-08-14 15:51:59 +00:00
|
|
|
server_name: "Singleplayer".to_owned(),
|
|
|
|
max_players: 100,
|
|
|
|
start_time: 9.0 * 3600.0,
|
2020-07-06 08:37:44 +00:00
|
|
|
max_view_distance: None,
|
2020-09-06 19:24:52 +00:00
|
|
|
client_timeout: Duration::from_secs(180),
|
2020-04-15 11:31:16 +00:00
|
|
|
..load // Fill in remaining fields from server_settings.ron.
|
2019-08-14 15:51:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
fn get_settings_path(path: &Path) -> PathBuf {
|
|
|
|
let mut path = with_config_dir(path);
|
|
|
|
path.push(SETTINGS_FILENAME);
|
|
|
|
path
|
2020-06-25 13:56:21 +00:00
|
|
|
}
|
2019-07-01 09:37:17 +00:00
|
|
|
}
|
2020-10-05 07:41:58 +00:00
|
|
|
|
|
|
|
fn with_config_dir(path: &Path) -> PathBuf {
|
|
|
|
let mut path = PathBuf::from(path);
|
2020-10-06 02:59:47 +00:00
|
|
|
path.push(CONFIG_DIR);
|
2020-10-05 07:41:58 +00:00
|
|
|
path
|
|
|
|
}
|
|
|
|
|
2020-10-06 02:59:47 +00:00
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
pub struct BanRecord {
|
|
|
|
pub username_when_banned: String,
|
|
|
|
pub reason: String,
|
|
|
|
}
|
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
#[derive(Deserialize, Serialize, Default)]
|
|
|
|
#[serde(transparent)]
|
2020-10-06 03:42:09 +00:00
|
|
|
pub struct Whitelist(HashSet<Uuid>);
|
2020-10-10 06:10:04 +00:00
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
#[derive(Deserialize, Serialize, Default)]
|
|
|
|
#[serde(transparent)]
|
2020-10-06 02:59:47 +00:00
|
|
|
pub struct Banlist(HashMap<Uuid, BanRecord>);
|
2020-10-10 06:10:04 +00:00
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
#[serde(transparent)]
|
|
|
|
pub struct ServerDescription(String);
|
2020-10-10 06:10:04 +00:00
|
|
|
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>);
|
2020-10-05 07:41:58 +00:00
|
|
|
|
2020-10-06 02:59:47 +00:00
|
|
|
/// 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,
|
2020-10-10 06:10:04 +00:00
|
|
|
pub admins: Admins,
|
2020-10-06 02:59:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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),
|
2020-10-10 06:10:04 +00:00
|
|
|
admins: Admins::load(data_dir),
|
2020-10-06 02:59:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn singleplayer(data_dir: &Path) -> Self {
|
|
|
|
let load = Self::load(data_dir);
|
|
|
|
Self {
|
|
|
|
server_description: ServerDescription("Who needs friends anyway?".into()),
|
2020-10-10 06:10:04 +00:00
|
|
|
// TODO: Let the player choose if they want to use admin commands or not
|
|
|
|
admins: Admins(
|
2021-03-11 00:08:12 +00:00
|
|
|
std::iter::once(crate::login_provider::derive_singleplayer_uuid()).collect(),
|
2020-10-10 06:10:04 +00:00
|
|
|
),
|
2020-10-06 02:59:47 +00:00
|
|
|
..load
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-10-10 06:10:04 +00:00
|
|
|
impl EditableSetting for Admins {
|
|
|
|
const FILENAME: &'static str = ADMINS_FILENAME;
|
|
|
|
}
|
|
|
|
|
2020-10-05 07:41:58 +00:00
|
|
|
impl Deref for Whitelist {
|
2020-10-06 03:42:09 +00:00
|
|
|
type Target = HashSet<Uuid>;
|
2020-10-05 07:41:58 +00:00
|
|
|
|
|
|
|
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 {
|
2020-10-06 02:59:47 +00:00
|
|
|
type Target = HashMap<Uuid, BanRecord>;
|
2020-10-05 07:41:58 +00:00
|
|
|
|
|
|
|
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 }
|
|
|
|
}
|
2020-10-10 06:10:04 +00:00
|
|
|
|
|
|
|
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 }
|
|
|
|
}
|