1pub mod admin;
2pub mod banlist;
3mod editable;
4pub mod server_description;
5pub mod server_physics;
6pub mod whitelist;
7
8pub use editable::{EditableSetting, Error as SettingError};
9
10pub use admin::{AdminRecord, Admins};
11pub use banlist::{
12 Ban, BanEntry, BanError, BanErrorKind, BanInfo, BanKind, BanOperation, BanOperationError,
13 BanRecord, Banlist,
14};
15pub use server_description::ServerDescriptions;
16pub use whitelist::{Whitelist, WhitelistInfo, WhitelistRecord};
17
18use chrono::Utc;
19use common::{
20 calendar::{Calendar, CalendarEvent},
21 consts::DAY_LENGTH_DEFAULT,
22 resources::BattleMode,
23 rtsim::WorldSettings,
24};
25use core::time::Duration;
26use portpicker::pick_unused_port;
27use serde::{Deserialize, Serialize};
28use std::{
29 fmt::Display,
30 fs,
31 net::{Ipv4Addr, Ipv6Addr, SocketAddr},
32 path::{Path, PathBuf},
33};
34use tracing::{error, warn};
35use world::sim::{DEFAULT_WORLD_SEED, FileOpts};
36
37use self::server_description::ServerDescription;
38
39use self::server_physics::ServerPhysicsForceList;
40
41const CONFIG_DIR: &str = "server_config";
42const SETTINGS_FILENAME: &str = "settings.ron";
43const WHITELIST_FILENAME: &str = "whitelist.ron";
44const BANLIST_FILENAME: &str = "banlist.ron";
45const SERVER_DESCRIPTION_FILENAME: &str = "description.ron";
46const ADMINS_FILENAME: &str = "admins.ron";
47const SERVER_PHYSICS_FORCE_FILENAME: &str = "server_physics_force.ron";
48
49pub const SINGLEPLAYER_SERVER_NAME: &str = "Singleplayer";
50
51#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
52pub enum ServerBattleMode {
53 Global(BattleMode),
54 PerPlayer { default: BattleMode },
55}
56
57impl Default for ServerBattleMode {
58 fn default() -> Self { Self::Global(BattleMode::PvP) }
59}
60
61impl ServerBattleMode {
62 pub fn allow_choosing(&self) -> bool {
63 match self {
64 ServerBattleMode::Global { .. } => false,
65 ServerBattleMode::PerPlayer { .. } => true,
66 }
67 }
68
69 pub fn default_mode(&self) -> BattleMode {
70 match self {
71 ServerBattleMode::Global(mode) => *mode,
72 ServerBattleMode::PerPlayer { default: mode } => *mode,
73 }
74 }
75}
76
77impl From<ServerBattleMode> for veloren_query_server::proto::ServerBattleMode {
78 fn from(value: ServerBattleMode) -> Self {
79 use veloren_query_server::proto::ServerBattleMode as QueryBattleMode;
80
81 match value {
82 ServerBattleMode::Global(mode) => match mode {
83 BattleMode::PvP => QueryBattleMode::GlobalPvP,
84 BattleMode::PvE => QueryBattleMode::GlobalPvE,
85 },
86 ServerBattleMode::PerPlayer { .. } => QueryBattleMode::PerPlayer,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub enum Protocol {
93 Quic {
94 address: SocketAddr,
95 cert_file_path: PathBuf,
96 key_file_path: PathBuf,
97 },
98 Tcp {
99 address: SocketAddr,
100 },
101}
102
103#[derive(Clone, Debug, Serialize, Deserialize)]
104pub struct GameplaySettings {
105 #[serde(default)]
106 pub battle_mode: ServerBattleMode,
107 #[serde(default)]
108 pub explosion_burn_marks: bool,
110}
111
112impl Default for GameplaySettings {
113 fn default() -> Self {
114 Self {
115 battle_mode: ServerBattleMode::default(),
116 explosion_burn_marks: true,
117 }
118 }
119}
120
121#[derive(Clone, Debug, Serialize, Deserialize)]
122pub struct ModerationSettings {
123 #[serde(default)]
124 pub banned_words_files: Vec<PathBuf>,
125 #[serde(default)]
126 pub automod: bool,
127 #[serde(default)]
128 pub admins_exempt: bool,
129}
130
131impl ModerationSettings {
132 pub fn load_banned_words(&self, data_dir: &Path) -> Vec<String> {
133 let mut banned_words = Vec::new();
134 for fname in self.banned_words_files.iter() {
135 let mut path = with_config_dir(data_dir);
136 path.push(fname);
137 match std::fs::File::open(&path) {
138 Ok(file) => match ron::de::from_reader(&file) {
139 Ok(mut words) => banned_words.append(&mut words),
140 Err(error) => error!(?error, ?file, "Couldn't read banned words file"),
141 },
142 Err(error) => error!(?error, ?path, "Couldn't open banned words file"),
143 }
144 }
145 banned_words
146 }
147}
148
149impl Default for ModerationSettings {
150 fn default() -> Self {
151 Self {
152 banned_words_files: Vec::new(),
153 automod: false,
154 admins_exempt: true,
155 }
156 }
157}
158
159#[derive(Clone, Debug, Serialize, Deserialize)]
160pub enum CalendarMode {
161 None,
162 Auto,
163 Timezone(chrono_tz::Tz),
164 Events(Vec<CalendarEvent>),
165}
166
167impl Default for CalendarMode {
168 fn default() -> Self { Self::Auto }
169}
170
171impl CalendarMode {
172 pub fn calendar_now(&self) -> Calendar {
173 match self {
174 CalendarMode::None => Calendar::default(),
175 CalendarMode::Auto => Calendar::from_tz(None),
176 CalendarMode::Timezone(tz) => Calendar::from_tz(Some(*tz)),
177 CalendarMode::Events(events) => Calendar::from_events(events.clone()),
178 }
179 }
180}
181
182#[derive(Clone, Debug, Serialize, Deserialize)]
183#[serde(default)]
184pub struct Settings {
185 pub gameserver_protocols: Vec<Protocol>,
186 pub auth_server_address: Option<String>,
187 pub query_address: Option<SocketAddr>,
188 pub max_players: u16,
189 pub world_seed: u32,
190 pub server_name: String,
191 pub start_time: f64,
192 pub day_length: f64,
194 pub map_file: Option<FileOpts>,
197 pub max_view_distance: Option<u32>,
198 pub max_player_group_size: u32,
199 pub client_timeout: Duration,
200 pub max_player_for_kill_broadcast: Option<usize>,
201 pub calendar_mode: CalendarMode,
202
203 #[serde(default, skip_serializing)]
206 pub experimental_terrain_persistence: bool,
207
208 #[serde(default)]
209 pub gameplay: GameplaySettings,
210 #[serde(default)]
211 pub moderation: ModerationSettings,
212
213 #[serde(default)]
214 pub world: WorldSettings,
215}
216
217impl Default for Settings {
218 fn default() -> Self {
219 Self {
220 gameserver_protocols: vec![
221 Protocol::Tcp {
222 address: SocketAddr::from((Ipv6Addr::UNSPECIFIED, 14004)),
223 },
224 Protocol::Tcp {
225 address: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 14004)),
226 },
227 ],
228 auth_server_address: Some("https://auth.veloren.net".into()),
229 query_address: Some(SocketAddr::from((Ipv4Addr::UNSPECIFIED, 14006))),
230 world_seed: DEFAULT_WORLD_SEED,
231 server_name: "Veloren Server".into(),
232 max_players: 100,
233 day_length: DAY_LENGTH_DEFAULT,
234 start_time: 9.0 * 3600.0,
235 map_file: None,
236 max_view_distance: Some(65),
237 max_player_group_size: 6,
238 calendar_mode: CalendarMode::Auto,
239 client_timeout: Duration::from_secs(40),
240 max_player_for_kill_broadcast: None,
241 experimental_terrain_persistence: false,
242 gameplay: GameplaySettings::default(),
243 moderation: ModerationSettings::default(),
244 world: WorldSettings::default(),
245 }
246 }
247}
248
249impl Settings {
250 pub fn load(path: &Path) -> Self {
252 let path = Self::get_settings_path(path);
253
254 let mut settings = if let Ok(file) = fs::File::open(&path) {
255 match ron::de::from_reader(file) {
256 Ok(x) => x,
257 Err(e) => {
258 let default_settings = Self::default();
259 let template_path = path.with_extension("template.ron");
260 warn!(
261 ?e,
262 "Failed to parse setting file! Falling back to default settings and \
263 creating a template file for you to migrate your current settings file: \
264 {}",
265 template_path.display()
266 );
267 if let Err(e) = default_settings.save_to_file(&template_path) {
268 error!(?e, "Failed to create template settings file")
269 }
270 default_settings
271 },
272 }
273 } else {
274 let default_settings = Self::default();
275
276 if let Err(e) = default_settings.save_to_file(&path) {
277 error!(?e, "Failed to create default settings file!");
278 }
279 default_settings
280 };
281
282 settings.validate();
283 settings
284 }
285
286 fn save_to_file(&self, path: &Path) -> std::io::Result<()> {
287 if let Some(dir) = path.parent() {
289 fs::create_dir_all(dir)?;
290 }
291 let ron = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
292 .expect("Failed serialize settings.");
293
294 fs::write(path, ron.as_bytes())?;
295
296 Ok(())
297 }
298
299 pub fn singleplayer(path: &Path) -> Self {
301 let load = Self::load(path);
302 Self {
303 gameserver_protocols: vec![Protocol::Tcp {
306 address: SocketAddr::from((
307 Ipv4Addr::LOCALHOST,
308 pick_unused_port().expect("Failed to find unused port!"),
309 )),
310 }],
311 auth_server_address: None,
312 world_seed: if load.map_file.is_some() {
314 load.world_seed
315 } else {
316 DEFAULT_WORLD_SEED
317 },
318 server_name: SINGLEPLAYER_SERVER_NAME.to_owned(),
319 max_players: 100,
320 max_view_distance: None,
321 client_timeout: Duration::from_secs(180),
322 ..load }
324 }
325
326 fn get_settings_path(path: &Path) -> PathBuf {
327 let mut path = with_config_dir(path);
328 path.push(SETTINGS_FILENAME);
329 path
330 }
331
332 fn validate(&mut self) {
333 const INVALID_SETTING_MSG: &str =
334 "Invalid value for setting in userdata/server/server_config/settings.ron.";
335
336 let default_values = Settings::default();
337
338 if self.day_length <= 0.0 {
339 warn!(
340 "{} Setting: day_length, Value: {}. Set day_length to it's default value of {}. \
341 Help: day_length must be a positive floating point value above 0.",
342 INVALID_SETTING_MSG, self.day_length, default_values.day_length
343 );
344 self.day_length = default_values.day_length;
345 }
346 }
347
348 pub fn day_cycle_coefficient(&self) -> f64 { 1440.0 / self.day_length }
351}
352
353pub enum InvalidSettingsError {
354 InvalidDayDuration,
355}
356impl Display for InvalidSettingsError {
357 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358 match self {
359 InvalidSettingsError::InvalidDayDuration => {
360 f.write_str("Invalid settings error: Day length was invalid (zero or negative).")
361 },
362 }
363 }
364}
365
366pub fn with_config_dir(path: &Path) -> PathBuf {
367 let mut path = PathBuf::from(path);
368 path.push(CONFIG_DIR);
369 path
370}
371
372const MIGRATION_UPGRADE_GUARANTEE: &str = "Any valid file of an old verison should be able to \
382 successfully migrate to the latest version.";
383
384#[derive(Clone)]
386pub struct EditableSettings {
387 pub whitelist: Whitelist,
388 pub banlist: Banlist,
389 pub server_description: ServerDescriptions,
390 pub admins: Admins,
391 pub server_physics_force_list: ServerPhysicsForceList,
392}
393
394impl EditableSettings {
395 pub fn load(data_dir: &Path) -> Self {
396 Self {
397 whitelist: Whitelist::load(data_dir),
398 banlist: Banlist::load(data_dir),
399 server_description: ServerDescriptions::load(data_dir),
400 admins: Admins::load(data_dir),
401 server_physics_force_list: ServerPhysicsForceList::load(data_dir),
402 }
403 }
404
405 pub fn singleplayer(data_dir: &Path) -> Self {
406 let load = Self::load(data_dir);
407
408 let mut server_description = ServerDescriptions::default();
409 server_description
410 .descriptions
411 .insert("en".to_string(), ServerDescription {
412 motd: "Who needs friends anyway?".to_string(),
413 rules: None,
414 });
415
416 let mut admins = Admins::default();
417 admins.insert(
419 crate::login_provider::derive_singleplayer_uuid(),
420 AdminRecord {
421 username_when_admined: Some("singleplayer".into()),
422 date: Utc::now(),
423 role: admin::Role::Admin,
424 },
425 );
426
427 Self {
428 server_description,
429 admins,
430 ..load
431 }
432 }
433}