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