1#![deny(unsafe_code)]
2#![expect(
3 clippy::option_map_unit_fn,
4 clippy::needless_pass_by_ref_mut )]
6#![deny(clippy::clone_on_ref_ptr)]
7#![feature(box_patterns, let_chains, option_zip, const_type_name)]
8
9pub mod automod;
10mod character_creator;
11pub mod chat;
12pub mod chunk_generator;
13mod chunk_serialize;
14pub mod client;
15pub mod cmd;
16pub mod connection_handler;
17mod data_dir;
18pub mod error;
19pub mod events;
20pub mod input;
21pub mod location;
22pub mod lod;
23pub mod login_provider;
24pub mod metrics;
25pub mod persistence;
26mod pet;
27pub mod presence;
28pub mod rtsim;
29pub mod settings;
30pub mod state_ext;
31pub mod sys;
32#[cfg(feature = "persistent_world")]
33pub mod terrain_persistence;
34#[cfg(not(feature = "worldgen"))] mod test_world;
35
36#[cfg(feature = "worldgen")] mod weather;
37
38pub mod wiring;
39
40pub use crate::{
42 data_dir::DEFAULT_DATA_DIR_NAME,
43 error::Error,
44 events::Event,
45 input::Input,
46 settings::{CalendarMode, EditableSettings, Settings},
47};
48
49#[cfg(feature = "persistent_world")]
50use crate::terrain_persistence::TerrainPersistence;
51use crate::{
52 automod::AutoMod,
53 chunk_generator::ChunkGenerator,
54 client::Client,
55 cmd::ChatCommandExt,
56 connection_handler::ConnectionHandler,
57 data_dir::DataDir,
58 location::Locations,
59 login_provider::LoginProvider,
60 persistence::PersistedComponents,
61 presence::{RegionSubscription, RepositionOnChunkLoad},
62 state_ext::StateExt,
63 sys::sentinel::DeletedEntities,
64};
65use censor::Censor;
66#[cfg(not(feature = "worldgen"))]
67use common::grid::Grid;
68#[cfg(feature = "worldgen")]
69use common::terrain::CoordinateConversions;
70#[cfg(feature = "worldgen")]
71use common::terrain::TerrainChunkSize;
72use common::{
73 assets::AssetExt,
74 calendar::Calendar,
75 character::{CharacterId, CharacterItem},
76 cmd::ServerChatCommand,
77 comp::{self, ChatType, Content},
78 event::{
79 ClientDisconnectEvent, ClientDisconnectWithoutPersistenceEvent, EventBus, ExitIngameEvent,
80 UpdateCharacterDataEvent,
81 },
82 link::Is,
83 mounting::{Volume, VolumeRider},
84 region::RegionMap,
85 resources::{BattleMode, GameMode, Time, TimeOfDay},
86 rtsim::RtSimEntity,
87 shared_server_config::ServerConstants,
88 slowjob::SlowJobPool,
89 terrain::TerrainChunk,
90 uid::Uid,
91 util::GIT_DATE_TIMESTAMP,
92 vol::RectRasterableVol,
93};
94use common_base::prof_span;
95use common_ecs::run_now;
96use common_net::{
97 msg::{ClientType, DisconnectReason, PlayerListUpdate, ServerGeneral, ServerInfo, ServerMsg},
98 sync::WorldSyncExt,
99};
100use common_state::{AreasContainer, BlockDiff, BuildArea, State};
101use common_systems::add_local_systems;
102use metrics::{EcsSystemMetrics, PhysicsMetrics, TickMetrics};
103use network::{ListenAddr, Network, Pid};
104use persistence::{
105 character_loader::{CharacterLoader, CharacterUpdaterMessage},
106 character_updater::CharacterUpdater,
107};
108use prometheus::Registry;
109use rustls::pki_types::{CertificateDer, PrivateKeyDer};
110use specs::{
111 Builder, Entity as EcsEntity, Entity, Join, LendJoin, WorldExt, shred::SendDispatcher,
112};
113use std::{
114 ops::{Deref, DerefMut},
115 sync::{Arc, Mutex},
116 time::{Duration, Instant},
117};
118#[cfg(not(feature = "worldgen"))]
119use test_world::{IndexOwned, World};
120use tokio::runtime::Runtime;
121use tracing::{debug, error, info, trace, warn};
122use vek::*;
123use veloren_query_server::server::QueryServer;
124pub use world::{WorldGenerateStage, civ::WorldCivStage, sim::WorldSimStage};
125
126use crate::{
127 persistence::{DatabaseSettings, SqlLogMode},
128 sys::terrain,
129};
130use hashbrown::HashMap;
131use std::sync::RwLock;
132
133use crate::settings::Protocol;
134
135#[cfg(feature = "plugins")]
136use {
137 common::uid::IdMaps,
138 common_state::plugin::{PluginMgr, memory_manager::EcsWorld},
139};
140
141use crate::{chat::ChatCache, persistence::character_loader::CharacterScreenResponseKind};
142use common::comp::Anchor;
143#[cfg(feature = "worldgen")]
144pub use world::{
145 IndexOwned, World,
146 sim::{DEFAULT_WORLD_MAP, DEFAULT_WORLD_SEED, FileOpts, GenOpts, WorldOpts},
147};
148
149const BATTLE_MODE_COOLDOWN: f64 = 60.0 * 5.0;
154
155#[derive(Copy, Clone)]
159pub struct SpawnPoint(pub Vec3<f32>);
160
161impl Default for SpawnPoint {
162 fn default() -> Self { Self(Vec3::new(0.0, 0.0, 256.0)) }
163}
164
165pub const MIN_VD: u32 = 6;
170
171#[derive(Copy, Clone, Default)]
174pub struct Tick(u64);
175
176#[derive(Clone)]
177pub struct HwStats {
178 hardware_threads: u32,
179 rayon_threads: u32,
180}
181
182#[derive(Clone, Copy, PartialEq)]
183enum DisconnectType {
184 WithPersistence,
185 WithoutPersistence,
186}
187
188#[derive(Copy, Clone)]
190pub struct TickStart(Instant);
191
192#[derive(Clone, Default, Debug)]
194pub struct BattleModeBuffer {
195 map: HashMap<CharacterId, (BattleMode, Time)>,
196}
197
198impl BattleModeBuffer {
199 pub fn push(&mut self, char_id: CharacterId, save: (BattleMode, Time)) {
200 self.map.insert(char_id, save);
201 }
202
203 pub fn get(&self, char_id: &CharacterId) -> Option<&(BattleMode, Time)> {
204 self.map.get(char_id)
205 }
206
207 pub fn pop(&mut self, char_id: &CharacterId) -> Option<(BattleMode, Time)> {
208 self.map.remove(char_id)
209 }
210}
211
212pub struct ChunkRequest {
213 entity: EcsEntity,
214 key: Vec2<i32>,
215}
216
217#[derive(Debug)]
218pub enum ServerInitStage {
219 DbMigrations,
220 DbVacuum,
221 WorldGen(WorldGenerateStage),
222 StartingSystems,
223}
224
225pub struct Server {
226 state: State,
227 world: Arc<World>,
228 index: IndexOwned,
229
230 connection_handler: ConnectionHandler,
231
232 runtime: Arc<Runtime>,
233
234 metrics_registry: Arc<Registry>,
235 chat_cache: ChatCache,
236 database_settings: Arc<RwLock<DatabaseSettings>>,
237 disconnect_all_clients_requested: bool,
238
239 event_dispatcher: SendDispatcher<'static>,
240}
241
242impl Server {
243 pub fn new(
245 settings: Settings,
246 editable_settings: EditableSettings,
247 database_settings: DatabaseSettings,
248 data_dir: &std::path::Path,
249 report_stage: &(dyn Fn(ServerInitStage) + Send + Sync),
250 runtime: Arc<Runtime>,
251 ) -> Result<Self, Error> {
252 prof_span!("Server::new");
253 info!("Server data dir is: {}", data_dir.display());
254 if settings.auth_server_address.is_none() {
255 info!("Authentication is disabled");
256 }
257
258 report_stage(ServerInitStage::DbMigrations);
259 debug!("Running DB migrations...");
261 persistence::run_migrations(&database_settings);
262
263 report_stage(ServerInitStage::DbVacuum);
264 debug!("Vacuuming database...");
266 persistence::vacuum_database(&database_settings);
267
268 let database_settings = Arc::new(RwLock::new(database_settings));
269
270 let registry = Arc::new(Registry::new());
271 let chunk_gen_metrics = metrics::ChunkGenMetrics::new(®istry).unwrap();
272 let job_metrics = metrics::JobMetrics::new(®istry).unwrap();
273 let network_request_metrics = metrics::NetworkRequestMetrics::new(®istry).unwrap();
274 let player_metrics = metrics::PlayerMetrics::new(®istry).unwrap();
275 let ecs_system_metrics = EcsSystemMetrics::new(®istry).unwrap();
276 let tick_metrics = TickMetrics::new(®istry).unwrap();
277 let physics_metrics = PhysicsMetrics::new(®istry).unwrap();
278 let server_event_metrics = metrics::ServerEventMetrics::new(®istry).unwrap();
279 let query_server_metrics = metrics::QueryServerMetrics::new(®istry).unwrap();
280
281 let battlemode_buffer = BattleModeBuffer::default();
282
283 let pools = State::pools(GameMode::Server);
284
285 #[cfg(feature = "plugins")]
287 let plugin_mgr = PluginMgr::from_asset_or_default();
288
289 debug!("Generating world, seed: {}", settings.world_seed);
290 #[cfg(feature = "worldgen")]
291 let (world, index) = World::generate(
292 settings.world_seed,
293 WorldOpts {
294 seed_elements: true,
295 world_file: if let Some(ref opts) = settings.map_file {
296 opts.clone()
297 } else {
298 FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into())
300 },
301 calendar: Some(settings.calendar_mode.calendar_now()),
302 },
303 &pools,
304 &|stage| {
305 report_stage(ServerInitStage::WorldGen(stage));
306 },
307 );
308 #[cfg(not(feature = "worldgen"))]
309 let (world, index) = World::generate(settings.world_seed);
310
311 #[cfg(feature = "worldgen")]
312 let map = world.get_map_data(index.as_index_ref(), &pools);
313 #[cfg(not(feature = "worldgen"))]
314 let map = common_net::msg::WorldMapMsg {
315 dimensions_lg: Vec2::zero(),
316 max_height: 1.0,
317 rgba: Grid::new(Vec2::new(1, 1), 1),
318 horizons: [(vec![0], vec![0]), (vec![0], vec![0])],
319 alt: Grid::new(Vec2::new(1, 1), 1),
320 sites: Vec::new(),
321 possible_starting_sites: Vec::new(),
322 pois: Vec::new(),
323 default_chunk: Arc::new(world.generate_oob_chunk()),
324 };
325
326 #[cfg(feature = "worldgen")]
327 let map_size_lg = world.sim().map_size_lg();
328 #[cfg(not(feature = "worldgen"))]
329 let map_size_lg = world.map_size_lg();
330
331 let lod = lod::Lod::from_world(&world, index.as_index_ref(), &pools);
332
333 report_stage(ServerInitStage::StartingSystems);
334
335 let mut state = State::server(
336 Arc::clone(&pools),
337 map_size_lg,
338 Arc::clone(&map.default_chunk),
339 |dispatcher_builder| {
340 add_local_systems(dispatcher_builder);
341 sys::msg::add_server_systems(dispatcher_builder);
342 sys::add_server_systems(dispatcher_builder);
343 #[cfg(feature = "worldgen")]
344 {
345 rtsim::add_server_systems(dispatcher_builder);
346 weather::add_server_systems(dispatcher_builder);
347 }
348 },
349 #[cfg(feature = "plugins")]
350 plugin_mgr,
351 );
352 events::register_event_busses(state.ecs_mut());
353 state.ecs_mut().insert(battlemode_buffer);
354 state.ecs_mut().insert(settings.clone());
355 state.ecs_mut().insert(editable_settings);
356 state.ecs_mut().insert(DataDir {
357 path: data_dir.to_owned(),
358 });
359
360 state.ecs_mut().insert(Vec::<ChunkRequest>::new());
361 state
362 .ecs_mut()
363 .insert(EventBus::<chunk_serialize::ChunkSendEntry>::default());
364 state.ecs_mut().insert(Locations::default());
365 state.ecs_mut().insert(LoginProvider::new(
366 settings.auth_server_address.clone(),
367 Arc::clone(&runtime),
368 ));
369 state.ecs_mut().insert(HwStats {
370 hardware_threads: num_cpus::get() as u32,
371 rayon_threads: num_cpus::get() as u32,
372 });
373 state.ecs_mut().insert(ServerConstants {
374 day_cycle_coefficient: settings.day_cycle_coefficient(),
375 });
376 state.ecs_mut().insert(Tick(0));
377 state.ecs_mut().insert(TickStart(Instant::now()));
378 state.ecs_mut().insert(job_metrics);
379 state.ecs_mut().insert(network_request_metrics);
380 state.ecs_mut().insert(player_metrics);
381 state.ecs_mut().insert(ecs_system_metrics);
382 state.ecs_mut().insert(tick_metrics);
383 state.ecs_mut().insert(physics_metrics);
384 state.ecs_mut().insert(server_event_metrics);
385 state.ecs_mut().insert(query_server_metrics);
386 if settings.experimental_terrain_persistence {
387 #[cfg(feature = "persistent_world")]
388 {
389 warn!(
390 "Experimental terrain persistence support is enabled. This feature may break, \
391 be disabled, or otherwise change under your feet at *any time*. \
392 Additionally, it is expected to be replaced in the future *without* \
393 migration or warning. You have been warned."
394 );
395 state
396 .ecs_mut()
397 .insert(TerrainPersistence::new(data_dir.to_owned()));
398 }
399 #[cfg(not(feature = "persistent_world"))]
400 error!(
401 "Experimental terrain persistence support was requested, but the server was not \
402 compiled with the feature. Terrain modifications will *not* be persisted."
403 );
404 }
405 {
406 let pool = state.ecs_mut().write_resource::<SlowJobPool>();
407 pool.configure("CHUNK_DROP", |_n| 1);
408 pool.configure("CHUNK_GENERATOR", |n| n / 2 + n / 4);
409 pool.configure("CHUNK_SERIALIZER", |n| n / 2);
410 pool.configure("RTSIM_SAVE", |_| 1);
411 pool.configure("WEATHER", |_| 1);
412 }
413 state
414 .ecs_mut()
415 .insert(ChunkGenerator::new(chunk_gen_metrics));
416 {
417 let (sender, receiver) =
418 crossbeam_channel::bounded::<chunk_serialize::SerializedChunk>(10_000);
419 state.ecs_mut().insert(sender);
420 state.ecs_mut().insert(receiver);
421 }
422
423 state.ecs_mut().insert(CharacterUpdater::new(
424 Arc::<RwLock<DatabaseSettings>>::clone(&database_settings),
425 )?);
426
427 let ability_map = comp::item::tool::AbilityMap::<comp::AbilityItem>::load_expect_cloned(
428 "common.abilities.ability_set_manifest",
429 );
430 state.ecs_mut().insert(ability_map);
431
432 let msm = comp::inventory::item::MaterialStatManifest::load().cloned();
433 state.ecs_mut().insert(msm);
434
435 let rbm = common::recipe::RecipeBookManifest::load().cloned();
436 state.ecs_mut().insert(rbm);
437
438 state.ecs_mut().insert(CharacterLoader::new(
439 Arc::<RwLock<DatabaseSettings>>::clone(&database_settings),
440 )?);
441
442 state
444 .ecs_mut()
445 .insert(sys::PersistenceScheduler::every(Duration::from_secs(10)));
446
447 state.ecs_mut().insert(RegionMap::new());
449
450 state.ecs_mut().register::<RegionSubscription>();
452 state.ecs_mut().register::<Client>();
453 state.ecs_mut().register::<comp::Presence>();
454 state.ecs_mut().register::<wiring::WiringElement>();
455 state.ecs_mut().register::<wiring::Circuit>();
456 state.ecs_mut().register::<Anchor>();
457 state.ecs_mut().register::<comp::Pet>();
458 state.ecs_mut().register::<login_provider::PendingLogin>();
459 state.ecs_mut().register::<RepositionOnChunkLoad>();
460 state.ecs_mut().register::<RtSimEntity>();
461
462 let banned_words = settings.moderation.load_banned_words(data_dir);
464 let censor = Arc::new(Censor::Custom(banned_words.into_iter().collect()));
465 state.ecs_mut().insert(Arc::clone(&censor));
466
467 state
469 .ecs_mut()
470 .insert(AutoMod::new(&settings.moderation, censor));
471
472 state.ecs_mut().insert(map);
473
474 #[cfg(feature = "worldgen")]
475 let spawn_point = SpawnPoint({
476 use world::civ::SiteKind;
477
478 let index = index.as_index_ref();
479 let center_chunk = world.sim().map_size_lg().chunks().map(i32::from) / 2;
486 let spawn_chunk = world
487 .civs()
488 .sites()
489 .filter(|site| matches!(site.kind, SiteKind::Settlement | SiteKind::Refactor))
490 .map(|site| site.center)
491 .min_by_key(|site_pos| site_pos.distance_squared(center_chunk))
492 .unwrap_or(center_chunk);
493
494 world.find_accessible_pos(index, TerrainChunkSize::center_wpos(spawn_chunk), false)
495 });
496 #[cfg(not(feature = "worldgen"))]
497 let spawn_point = SpawnPoint::default();
498
499 state.ecs_mut().insert(spawn_point);
501
502 {
505 #[cfg(feature = "worldgen")]
506 let size = world.sim().get_size();
507 #[cfg(not(feature = "worldgen"))]
508 let size = world.map_size_lg().chunks().map(u32::from);
509
510 let world_size = size.map(|e| e as i32) * TerrainChunk::RECT_SIZE.map(|e| e as i32);
511 let world_aabb = Aabb {
512 min: Vec3::new(0, 0, -32768),
513 max: Vec3::new(world_size.x, world_size.y, 32767),
514 }
515 .made_valid();
516
517 state
518 .ecs()
519 .write_resource::<AreasContainer<BuildArea>>()
520 .insert("world".to_string(), world_aabb)
521 .expect("The initial insert should always work.");
522 }
523
524 let world = Arc::new(world);
526 state.ecs_mut().insert(Arc::clone(&world));
527 state.ecs_mut().insert(lod);
528 state.ecs_mut().insert(index.clone());
529
530 state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.world.start_time;
532
533 sys::sentinel::UpdateTrackers::register(state.ecs_mut());
535
536 state.ecs_mut().insert(DeletedEntities::default());
537
538 let network = Network::new_with_registry(Pid::new(), &runtime, ®istry);
539 let (chat_cache, chat_tracker) = ChatCache::new(Duration::from_secs(60), &runtime);
540 state.ecs_mut().insert(chat_tracker);
541
542 let mut printed_quic_warning = false;
543 for protocol in &settings.gameserver_protocols {
544 match protocol {
545 Protocol::Tcp { address } => {
546 runtime.block_on(network.listen(ListenAddr::Tcp(*address)))?;
547 },
548 Protocol::Quic {
549 address,
550 cert_file_path,
551 key_file_path,
552 } => {
553 use rustls_pemfile::Item;
554 use std::fs;
555
556 match || -> Result<_, Box<dyn std::error::Error>> {
557 let key = fs::read(key_file_path)?;
558 let key = if key_file_path.extension().is_some_and(|x| x == "der") {
559 PrivateKeyDer::try_from(key).map_err(|_| "No valid pem key in file")?
560 } else {
561 debug!("convert pem key to der");
562 rustls_pemfile::read_all(&mut key.as_slice())
563 .find_map(|item| match item {
564 Ok(Item::Pkcs1Key(v)) => Some(PrivateKeyDer::Pkcs1(v)),
565 Ok(Item::Pkcs8Key(v)) => Some(PrivateKeyDer::Pkcs8(v)),
566 Ok(Item::Sec1Key(v)) => Some(PrivateKeyDer::Sec1(v)),
567 Ok(Item::Crl(_)) => None,
568 Ok(Item::Csr(_)) => None,
569 Ok(Item::X509Certificate(_)) => None,
570 Ok(_) => None,
571 Err(e) => {
572 tracing::warn!(?e, "error while reading key_file");
573 None
574 },
575 })
576 .ok_or("No valid pem key in file")?
577 };
578 let cert_chain = fs::read(cert_file_path)?;
579 let cert_chain = if cert_file_path.extension().is_some_and(|x| x == "der") {
580 vec![CertificateDer::from(cert_chain)]
581 } else {
582 debug!("convert pem cert to der");
583 rustls_pemfile::certs(&mut cert_chain.as_slice())
584 .filter_map(|item| match item {
585 Ok(cert) => Some(cert),
586 Err(e) => {
587 tracing::warn!(?e, "error while reading cert_file");
588 None
589 },
590 })
591 .collect()
592 };
593 let server_config = quinn::ServerConfig::with_single_cert(cert_chain, key)?;
594 Ok(server_config)
595 }() {
596 Ok(server_config) => {
597 runtime.block_on(
598 network.listen(ListenAddr::Quic(*address, server_config.clone())),
599 )?;
600
601 if !printed_quic_warning {
602 warn!(
603 "QUIC is enabled. This is experimental and not recommended in \
604 production"
605 );
606 printed_quic_warning = true;
607 }
608 },
609 Err(e) => {
610 error!(
611 ?e,
612 "Failed to load the TLS certificate, running without QUIC {}",
613 *address
614 );
615 },
616 }
617 },
618 }
619 }
620
621 if let Some(addr) = settings.query_address {
622 use veloren_query_server::proto::ServerInfo;
623
624 const QUERY_SERVER_RATELIMIT: u16 = 120;
625
626 let (query_server_info_tx, query_server_info_rx) =
627 tokio::sync::watch::channel(ServerInfo {
628 git_hash: *sys::server_info::GIT_HASH,
629 git_timestamp: *GIT_DATE_TIMESTAMP,
630 players_count: 0,
631 player_cap: settings.max_players,
632 battlemode: settings.gameplay.battle_mode.into(),
633 });
634 let mut query_server =
635 QueryServer::new(addr, query_server_info_rx, QUERY_SERVER_RATELIMIT);
636 let query_server_metrics =
637 Arc::new(Mutex::new(veloren_query_server::server::Metrics::default()));
638 let query_server_metrics2 = Arc::clone(&query_server_metrics);
639 runtime.spawn(async move {
640 let err = query_server.run(query_server_metrics2).await.err();
641 error!(?err, "Query server stopped unexpectedly");
642 });
643 state.ecs_mut().insert(query_server_info_tx);
644 state.ecs_mut().insert(query_server_metrics);
645 }
646
647 runtime.block_on(network.listen(ListenAddr::Mpsc(14004)))?;
648
649 let connection_handler = ConnectionHandler::new(network, &runtime);
650
651 #[cfg(feature = "worldgen")]
653 {
654 match rtsim::RtSim::new(
655 &settings.world,
656 index.as_index_ref(),
657 &world,
658 data_dir.to_owned(),
659 ) {
660 Ok(rtsim) => {
661 state.ecs_mut().insert(rtsim.state().data().time_of_day);
662 state.ecs_mut().insert(rtsim);
663 },
664 Err(err) => {
665 error!("Failed to load rtsim: {}", err);
666 return Err(Error::RtsimError(err));
667 },
668 }
669 weather::init(&mut state);
670 }
671
672 let this = Self {
673 state,
674 world,
675 index,
676 connection_handler,
677 runtime,
678
679 metrics_registry: registry,
680 chat_cache,
681 database_settings,
682 disconnect_all_clients_requested: false,
683
684 event_dispatcher: Self::create_event_dispatcher(pools),
685 };
686
687 debug!(?settings, "created veloren server with");
688
689 let git_hash = *common::util::GIT_HASH;
690 let git_date = common::util::GIT_DATE.clone();
691 let git_time = *common::util::GIT_TIME;
692 let version = common::util::DISPLAY_VERSION_LONG.clone();
693 info!(?version, "Server version");
694 debug!(?git_hash, ?git_date, ?git_time, "detailed Server version");
695
696 Ok(this)
697 }
698
699 pub fn get_server_info(&self) -> ServerInfo {
700 let settings = self.state.ecs().fetch::<Settings>();
701
702 ServerInfo {
703 name: settings.server_name.clone(),
704 git_hash: common::util::GIT_HASH.to_string(),
705 git_date: common::util::GIT_DATE.to_string(),
706 auth_provider: settings.auth_server_address.clone(),
707 }
708 }
709
710 pub fn settings(&self) -> impl Deref<Target = Settings> + '_ {
712 self.state.ecs().fetch::<Settings>()
713 }
714
715 pub fn settings_mut(&self) -> impl DerefMut<Target = Settings> + '_ {
717 self.state.ecs().fetch_mut::<Settings>()
718 }
719
720 pub fn editable_settings_mut(&self) -> impl DerefMut<Target = EditableSettings> + '_ {
722 self.state.ecs().fetch_mut::<EditableSettings>()
723 }
724
725 pub fn editable_settings(&self) -> impl Deref<Target = EditableSettings> + '_ {
727 self.state.ecs().fetch::<EditableSettings>()
728 }
729
730 pub fn data_dir(&self) -> impl Deref<Target = DataDir> + '_ {
732 self.state.ecs().fetch::<DataDir>()
733 }
734
735 pub fn state(&self) -> &State { &self.state }
737
738 pub fn state_mut(&mut self) -> &mut State { &mut self.state }
740
741 pub fn world(&self) -> &World { &self.world }
743
744 pub fn metrics_registry(&self) -> &Arc<Registry> { &self.metrics_registry }
746
747 pub fn chat_cache(&self) -> &ChatCache { &self.chat_cache }
749
750 fn parse_locations(&self, character_list_data: &mut [CharacterItem]) {
751 character_list_data.iter_mut().for_each(|c| {
752 let name = c
753 .location
754 .as_ref()
755 .and_then(|s| {
756 persistence::parse_waypoint(s)
757 .ok()
758 .and_then(|(waypoint, _)| waypoint.map(|w| w.get_pos()))
759 })
760 .and_then(|wpos| {
761 self.world
762 .get_location_name(self.index.as_index_ref(), wpos.xy().as_::<i32>())
763 });
764 c.location = name;
765 });
766 }
767
768 pub fn tick(&mut self, _input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
771 self.state.ecs().write_resource::<Tick>().0 += 1;
772 self.state.ecs().write_resource::<TickStart>().0 = Instant::now();
773
774 let new_calendar = self
778 .state
779 .ecs()
780 .read_resource::<Settings>()
781 .calendar_mode
782 .calendar_now();
783 *self.state.ecs_mut().write_resource::<Calendar>() = new_calendar;
784
785 let mut frontend_events = Vec::new();
808
809 let before_new_connections = Instant::now();
812
813 self.handle_new_connections(&mut frontend_events);
815
816 let before_state_tick = Instant::now();
817
818 fn on_block_update(ecs: &specs::World, changes: Vec<BlockDiff>) {
819 if changes
821 .iter()
822 .any(|c| c.old.get_rtsim_resource() != c.new.get_rtsim_resource())
823 {
824 ecs.write_resource::<rtsim::RtSim>().hook_block_update(
825 &ecs.read_resource::<Arc<world::World>>(),
826 ecs.read_resource::<world::IndexOwned>().as_index_ref(),
827 changes,
828 );
829 }
830 }
831
832 let mut state_tick_metrics = Default::default();
836 let server_constants = (*self.state.ecs().read_resource::<ServerConstants>()).clone();
837 self.state.tick(
838 dt,
839 false,
840 Some(&mut state_tick_metrics),
841 &server_constants,
842 on_block_update,
843 );
844
845 let before_handle_events = Instant::now();
846
847 let disconnect_type = self.disconnect_all_clients_if_requested();
850
851 self.state.maintain_links();
853
854 frontend_events.append(&mut self.handle_events());
856
857 let before_update_terrain_and_regions = Instant::now();
858
859 self.update_region_map();
864 self.state.apply_terrain_changes(on_block_update);
867
868 let before_sync = Instant::now();
869
870 sys::run_sync_systems(self.state.ecs_mut());
872
873 let before_world_tick = Instant::now();
874
875 self.world.tick(dt);
877
878 let before_entity_cleanup = Instant::now();
879
880 if let Some(DisconnectType::WithoutPersistence) = disconnect_type {
885 run_now::<terrain::Sys>(self.state.ecs_mut());
886 }
887
888 #[cfg(feature = "worldgen")]
890 {
891 let mut rtsim = self.state.ecs().write_resource::<rtsim::RtSim>();
892 let world = self.state.ecs().read_resource::<Arc<World>>();
893 for chunk in &self.state.terrain_changes().removed_chunks {
894 rtsim.hook_unload_chunk(*chunk, &world);
895 }
896 }
897
898 let anchors = self.state.ecs().read_storage::<Anchor>();
909 let anchored_anchor_entities: Vec<Entity> = (
910 &self.state.ecs().entities(),
911 &self.state.ecs().read_storage::<Anchor>(),
912 )
913 .join()
914 .filter_map(|(_, anchor)| match anchor {
915 Anchor::Entity(anchor_entity) => Some(*anchor_entity),
916 _ => None,
917 })
918 .filter(|anchor_entity| match anchors.get(*anchor_entity) {
922 Some(Anchor::Entity(_)) => true,
923 Some(Anchor::Chunk(_)) | None => false
924 })
925 .collect();
926 drop(anchors);
927
928 for entity in anchored_anchor_entities {
929 if cfg!(debug_assertions) {
930 panic!("Entity anchor chain detected");
931 }
932 error!(
933 "Detected an anchor entity that itself has an anchor entity - anchor chains are \
934 not currently supported. The entity's Anchor component has been deleted"
935 );
936 self.state.delete_component::<Anchor>(entity);
937 }
938
939 let to_delete = {
942 let terrain = self.state.terrain();
943 (
944 &self.state.ecs().entities(),
945 &self.state.ecs().read_storage::<comp::Pos>(),
946 !&self.state.ecs().read_storage::<comp::Presence>(),
947 self.state.ecs().read_storage::<Anchor>().maybe(),
948 self.state.ecs().read_storage::<Is<VolumeRider>>().maybe(),
949 )
950 .join()
951 .filter(|(_, pos, _, anchor, is_volume_rider)| {
952 let pos = is_volume_rider
953 .and_then(|is_volume_rider| match is_volume_rider.pos.kind {
954 Volume::Terrain => None,
955 Volume::Entity(e) => {
956 let e = self.state.ecs().entity_from_uid(e)?;
957 let pos = self
958 .state
959 .ecs()
960 .read_storage::<comp::Pos>()
961 .get(e)
962 .copied()?;
963
964 Some(pos.0)
965 },
966 })
967 .unwrap_or(pos.0);
968 let chunk_key = terrain.pos_key(pos.map(|e| e.floor() as i32));
969 match anchor {
970 Some(Anchor::Chunk(hc)) => {
971 terrain.get_key_real(chunk_key).is_none()
976 && terrain.get_key_real(*hc).is_none()
977 },
978 Some(Anchor::Entity(entity)) => !self.state.ecs().is_alive(*entity),
979 None => terrain.get_key_real(chunk_key).is_none(),
980 }
981 })
982 .map(|(entity, _, _, _, _)| entity)
983 .collect::<Vec<_>>()
984 };
985
986 #[cfg(feature = "worldgen")]
987 {
988 let mut rtsim = self.state.ecs().write_resource::<rtsim::RtSim>();
989 let rtsim_entities = self.state.ecs().read_storage();
990 for entity in &to_delete {
991 if let Some(rtsim_entity) = rtsim_entities.get(*entity) {
992 rtsim.hook_rtsim_entity_unload(*rtsim_entity);
993 }
994 }
995 }
996
997 for entity in to_delete {
999 if let Err(e) = self.state.delete_entity_recorded(entity) {
1000 error!(?e, "Failed to delete agent outside the terrain");
1001 }
1002 }
1003
1004 if let Some(DisconnectType::WithoutPersistence) = disconnect_type {
1005 info!(
1006 "Disconnection of all players without persistence complete, signalling to \
1007 persistence thread that character updates may continue to be processed"
1008 );
1009 self.state
1010 .ecs()
1011 .fetch_mut::<CharacterUpdater>()
1012 .disconnected_success();
1013 }
1014
1015 let before_persistence_updates = Instant::now();
1017
1018 let character_loader = self.state.ecs().read_resource::<CharacterLoader>();
1019
1020 let mut character_updater = self.state.ecs().write_resource::<CharacterUpdater>();
1021 let updater_messages: Vec<CharacterUpdaterMessage> = character_updater.messages().collect();
1022
1023 character_loader
1025 .messages()
1026 .chain(updater_messages)
1027 .for_each(|message| match message {
1028 CharacterUpdaterMessage::DatabaseBatchCompletion(batch_id) => {
1029 character_updater.process_batch_completion(batch_id);
1030 },
1031 CharacterUpdaterMessage::CharacterScreenResponse(response) => {
1032 match response.response_kind {
1033 CharacterScreenResponseKind::CharacterList(result) => match result {
1034 Ok(mut character_list_data) => {
1035 self.parse_locations(&mut character_list_data);
1036 self.notify_client(
1037 response.target_entity,
1038 ServerGeneral::CharacterListUpdate(character_list_data),
1039 )
1040 },
1041 Err(error) => self.notify_client(
1042 response.target_entity,
1043 ServerGeneral::CharacterActionError(error.to_string()),
1044 ),
1045 },
1046 CharacterScreenResponseKind::CharacterCreation(result) => match result {
1047 Ok((character_id, mut list)) => {
1048 self.parse_locations(&mut list);
1049 self.notify_client(
1050 response.target_entity,
1051 ServerGeneral::CharacterListUpdate(list),
1052 );
1053 self.notify_client(
1054 response.target_entity,
1055 ServerGeneral::CharacterCreated(character_id),
1056 );
1057 },
1058 Err(error) => self.notify_client(
1059 response.target_entity,
1060 ServerGeneral::CharacterActionError(error.to_string()),
1061 ),
1062 },
1063 CharacterScreenResponseKind::CharacterEdit(result) => match result {
1064 Ok((character_id, mut list)) => {
1065 self.parse_locations(&mut list);
1066 self.notify_client(
1067 response.target_entity,
1068 ServerGeneral::CharacterListUpdate(list),
1069 );
1070 self.notify_client(
1071 response.target_entity,
1072 ServerGeneral::CharacterEdited(character_id),
1073 );
1074 },
1075 Err(error) => self.notify_client(
1076 response.target_entity,
1077 ServerGeneral::CharacterActionError(error.to_string()),
1078 ),
1079 },
1080 CharacterScreenResponseKind::CharacterData(result) => {
1081 match *result {
1082 Ok((character_data, skill_set_persistence_load_error)) => {
1083 let PersistedComponents {
1084 body,
1085 hardcore,
1086 stats,
1087 skill_set,
1088 inventory,
1089 waypoint,
1090 pets,
1091 active_abilities,
1092 map_marker,
1093 } = character_data;
1094 let character_data = (
1095 body,
1096 hardcore,
1097 stats,
1098 skill_set,
1099 inventory,
1100 waypoint,
1101 pets,
1102 active_abilities,
1103 map_marker,
1104 );
1105 self.state.emit_event_now(UpdateCharacterDataEvent {
1108 entity: response.target_entity,
1109 components: character_data,
1110 metadata: skill_set_persistence_load_error,
1111 })
1112 },
1113 Err(error) => {
1114 self.notify_client(
1118 response.target_entity,
1119 ServerGeneral::CharacterDataLoadResult(Err(
1120 error.to_string()
1121 )),
1122 );
1123
1124 self.state.emit_event_now(ExitIngameEvent {
1126 entity: response.target_entity,
1127 })
1128 },
1129 }
1130 },
1131 }
1132 },
1133 });
1134
1135 drop(character_loader);
1136 drop(character_updater);
1137
1138 {
1139 let index = &mut self.index;
1144 let world = &mut self.world;
1145 let ecs = self.state.ecs_mut();
1146 let slow_jobs = ecs.write_resource::<SlowJobPool>();
1147
1148 index.reload_if_changed(|index| {
1149 let mut chunk_generator = ecs.write_resource::<ChunkGenerator>();
1150 let client = ecs.read_storage::<Client>();
1151 let mut terrain = ecs.write_resource::<common::terrain::TerrainGrid>();
1152 #[cfg(feature = "worldgen")]
1153 let rtsim = ecs.read_resource::<rtsim::RtSim>();
1154 #[cfg(not(feature = "worldgen"))]
1155 let rtsim = ();
1156
1157 chunk_generator.cancel_all();
1159
1160 if client.is_empty() {
1161 terrain.clear();
1163 } else {
1164 terrain.iter().for_each(|(pos, _)| {
1166 chunk_generator.generate_chunk(
1167 None,
1168 pos,
1169 &slow_jobs,
1170 Arc::clone(world),
1171 &rtsim,
1172 index.clone(),
1173 (
1174 *ecs.read_resource::<TimeOfDay>(),
1175 (*ecs.read_resource::<Calendar>()).clone(),
1176 ),
1177 );
1178 });
1179 }
1180 });
1181 }
1182
1183 let end_of_server_tick = Instant::now();
1184
1185 run_now::<sys::metrics::Sys>(self.state.ecs());
1187
1188 {
1189 let tick_metrics = self.state.ecs().read_resource::<TickMetrics>();
1191
1192 let tt = &tick_metrics.tick_time;
1193 tt.with_label_values(&["new connections"])
1194 .set((before_state_tick - before_new_connections).as_nanos() as i64);
1195 tt.with_label_values(&["handle server events"])
1196 .set((before_update_terrain_and_regions - before_handle_events).as_nanos() as i64);
1197 tt.with_label_values(&["update terrain and region map"])
1198 .set((before_sync - before_update_terrain_and_regions).as_nanos() as i64);
1199 tt.with_label_values(&["state"])
1200 .set((before_handle_events - before_state_tick).as_nanos() as i64);
1201 tt.with_label_values(&["world tick"])
1202 .set((before_entity_cleanup - before_world_tick).as_nanos() as i64);
1203 tt.with_label_values(&["entity cleanup"])
1204 .set((before_persistence_updates - before_entity_cleanup).as_nanos() as i64);
1205 tt.with_label_values(&["persistence_updates"])
1206 .set((end_of_server_tick - before_persistence_updates).as_nanos() as i64);
1207 for (label, duration) in state_tick_metrics.timings {
1208 tick_metrics
1209 .state_tick_time
1210 .with_label_values(&[label])
1211 .set(duration.as_nanos() as i64);
1212 }
1213 tick_metrics.tick_time_hist.observe(
1214 end_of_server_tick
1215 .duration_since(before_state_tick)
1216 .as_secs_f64(),
1217 );
1218 }
1219
1220 Ok(frontend_events)
1223 }
1224
1225 pub fn cleanup(&mut self) {
1227 self.state.cleanup();
1229
1230 #[cfg(feature = "persistent_world")]
1232 self.state
1233 .ecs()
1234 .try_fetch_mut::<TerrainPersistence>()
1235 .map(|mut t| t.maintain());
1236 }
1237
1238 fn update_region_map(&mut self) {
1240 prof_span!("Server::update_region_map");
1241 let ecs = self.state().ecs();
1242 ecs.write_resource::<RegionMap>().tick(
1243 ecs.read_storage::<comp::Pos>(),
1244 ecs.read_storage::<comp::Vel>(),
1245 ecs.read_storage::<comp::Presence>(),
1246 ecs.entities(),
1247 );
1248 }
1249
1250 fn initialize_client(&mut self, client: connection_handler::IncomingClient) -> Entity {
1251 let entity = self
1252 .state
1253 .ecs_mut()
1254 .create_entity_synced()
1255 .with(client)
1256 .build();
1257 self.state
1258 .ecs()
1259 .read_resource::<metrics::PlayerMetrics>()
1260 .clients_connected
1261 .inc();
1262 entity
1263 }
1264
1265 fn disconnect_all_clients_if_requested(&mut self) -> Option<DisconnectType> {
1269 let mut character_updater = self.state.ecs().fetch_mut::<CharacterUpdater>();
1270
1271 let disconnect_type = self.get_disconnect_all_clients_requested(&mut character_updater);
1272 if let Some(disconnect_type) = disconnect_type {
1273 let with_persistence = disconnect_type == DisconnectType::WithPersistence;
1274 let clients = self.state.ecs().read_storage::<Client>();
1275 let entities = self.state.ecs().entities();
1276
1277 info!(
1278 "Disconnecting all clients ({} persistence) as requested",
1279 if with_persistence { "with" } else { "without" }
1280 );
1281 for (_, entity) in (&clients, &entities).join() {
1282 info!("Emitting client disconnect event for entity: {:?}", entity);
1283 if with_persistence {
1284 self.state.emit_event_now(ClientDisconnectEvent(
1285 entity,
1286 comp::DisconnectReason::Kicked,
1287 ))
1288 } else {
1289 self.state
1290 .emit_event_now(ClientDisconnectWithoutPersistenceEvent(entity))
1291 };
1292 }
1293
1294 self.disconnect_all_clients_requested = false;
1295 }
1296
1297 disconnect_type
1298 }
1299
1300 fn get_disconnect_all_clients_requested(
1301 &self,
1302 character_updater: &mut CharacterUpdater,
1303 ) -> Option<DisconnectType> {
1304 let without_persistence_requested = character_updater.disconnect_all_clients_requested();
1305 let with_persistence_requested = self.disconnect_all_clients_requested;
1306
1307 if without_persistence_requested {
1308 return Some(DisconnectType::WithoutPersistence);
1309 };
1310 if with_persistence_requested {
1311 return Some(DisconnectType::WithPersistence);
1312 };
1313 None
1314 }
1315
1316 fn handle_new_connections(&mut self, frontend_events: &mut Vec<Event>) {
1318 while let Ok(sender) = self.connection_handler.info_requester_receiver.try_recv() {
1319 trace!("sending info to connection_handler");
1321 let _ = sender.send(connection_handler::ServerInfoPacket {
1322 info: self.get_server_info(),
1323 time: self.state.get_time(),
1324 });
1325 }
1326
1327 while let Ok(incoming) = self.connection_handler.client_receiver.try_recv() {
1328 let entity = self.initialize_client(incoming);
1329 frontend_events.push(Event::ClientConnected { entity });
1330 }
1331 }
1332
1333 pub fn notify_client<S>(&self, entity: EcsEntity, msg: S)
1334 where
1335 S: Into<ServerMsg>,
1336 {
1337 if let Some(client) = self.state.ecs().read_storage::<Client>().get(entity) {
1338 client.send_fallible(msg);
1339 }
1340 }
1341
1342 pub fn notify_players(&mut self, msg: ServerGeneral) { self.state.notify_players(msg); }
1343
1344 pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2<i32>) {
1345 let ecs = self.state.ecs();
1346 let slow_jobs = ecs.read_resource::<SlowJobPool>();
1347 #[cfg(feature = "worldgen")]
1348 let rtsim = ecs.read_resource::<rtsim::RtSim>();
1349 #[cfg(not(feature = "worldgen"))]
1350 let rtsim = ();
1351 ecs.write_resource::<ChunkGenerator>().generate_chunk(
1352 Some(entity),
1353 key,
1354 &slow_jobs,
1355 Arc::clone(&self.world),
1356 &rtsim,
1357 self.index.clone(),
1358 (
1359 *ecs.read_resource::<TimeOfDay>(),
1360 (*ecs.read_resource::<Calendar>()).clone(),
1361 ),
1362 );
1363 }
1364
1365 fn process_command(&mut self, entity: EcsEntity, name: String, args: Vec<String>) {
1366 if let Ok(command) = name.parse::<ServerChatCommand>() {
1368 command.execute(self, entity, args);
1369 } else {
1370 #[cfg(feature = "plugins")]
1371 {
1372 let mut plugin_manager = self.state.ecs().write_resource::<PluginMgr>();
1373 let ecs_world = EcsWorld {
1374 entities: &self.state.ecs().entities(),
1375 health: self.state.ecs().read_component().into(),
1376 uid: self.state.ecs().read_component().into(),
1377 id_maps: &self.state.ecs().read_resource::<IdMaps>().into(),
1378 player: self.state.ecs().read_component().into(),
1379 };
1380 let uid = if let Some(uid) = ecs_world.uid.get(entity).copied() {
1381 uid
1382 } else {
1383 self.notify_client(
1384 entity,
1385 ServerGeneral::server_msg(
1386 comp::ChatType::CommandError,
1387 common::comp::Content::Plain(
1388 "Can't get player UUID (player may be disconnected?)".to_string(),
1389 ),
1390 ),
1391 );
1392 return;
1393 };
1394 match plugin_manager.command_event(&ecs_world, &name, args.as_slice(), uid) {
1395 Err(common_state::plugin::CommandResults::UnknownCommand) => self
1396 .notify_client(
1397 entity,
1398 ServerGeneral::server_msg(
1399 comp::ChatType::CommandError,
1400 common::comp::Content::Plain(format!(
1401 "Unknown command '/{name}'.\nType '/help' for available \
1402 commands",
1403 )),
1404 ),
1405 ),
1406 Ok(value) => {
1407 self.notify_client(
1408 entity,
1409 ServerGeneral::server_msg(
1410 comp::ChatType::CommandInfo,
1411 common::comp::Content::Plain(value.join("\n")),
1412 ),
1413 );
1414 },
1415 Err(common_state::plugin::CommandResults::PluginError(err)) => {
1416 self.notify_client(
1417 entity,
1418 ServerGeneral::server_msg(
1419 comp::ChatType::CommandError,
1420 common::comp::Content::Plain(format!(
1421 "Error occurred while executing command '/{name}'.\n{err}"
1422 )),
1423 ),
1424 );
1425 },
1426 Err(common_state::plugin::CommandResults::HostError(err)) => {
1427 error!(?err, ?name, ?args, "Can't execute command");
1428 self.notify_client(
1429 entity,
1430 ServerGeneral::server_msg(
1431 comp::ChatType::CommandError,
1432 common::comp::Content::Plain(format!(
1433 "Internal error {err:?} while executing '/{name}'.\nContact \
1434 the server administrator",
1435 )),
1436 ),
1437 );
1438 },
1439 }
1440 }
1441 }
1442 }
1443
1444 fn entity_admin_role(&self, entity: EcsEntity) -> Option<comp::AdminRole> {
1445 self.state
1446 .read_component_copied::<comp::Admin>(entity)
1447 .map(|admin| admin.0)
1448 }
1449
1450 pub fn number_of_players(&self) -> i64 {
1451 self.state.ecs().read_storage::<Client>().join().count() as i64
1452 }
1453
1454 pub fn add_admin(&mut self, username: &str, role: comp::AdminRole) {
1457 let mut editable_settings = self.editable_settings_mut();
1458 let login_provider = self.state.ecs().fetch::<LoginProvider>();
1459 let data_dir = self.data_dir();
1460 if let Some(entity) = add_admin(
1461 username,
1462 role,
1463 &login_provider,
1464 &mut editable_settings,
1465 &data_dir.path,
1466 )
1467 .and_then(|uuid| {
1468 let state = &self.state;
1469 (
1470 &state.ecs().entities(),
1471 &state.read_storage::<comp::Player>(),
1472 )
1473 .join()
1474 .find(|(_, player)| player.uuid() == uuid)
1475 .map(|(e, _)| e)
1476 }) {
1477 drop((data_dir, login_provider, editable_settings));
1478 self.state
1481 .write_component_ignore_entity_dead(entity, comp::Admin(role));
1482 };
1483 }
1484
1485 pub fn remove_admin(&self, username: &str) {
1488 let mut editable_settings = self.editable_settings_mut();
1489 let login_provider = self.state.ecs().fetch::<LoginProvider>();
1490 let data_dir = self.data_dir();
1491 if let Some(entity) = remove_admin(
1492 username,
1493 &login_provider,
1494 &mut editable_settings,
1495 &data_dir.path,
1496 )
1497 .and_then(|uuid| {
1498 let state = &self.state;
1499 (
1500 &state.ecs().entities(),
1501 &state.read_storage::<comp::Player>(),
1502 )
1503 .join()
1504 .find(|(_, player)| player.uuid() == uuid)
1505 .map(|(e, _)| e)
1506 }) {
1507 self.state
1509 .ecs()
1510 .write_storage::<comp::Admin>()
1511 .remove(entity);
1512 };
1513 }
1514
1515 #[cfg(feature = "worldgen")]
1520 pub fn create_centered_persister(&mut self, view_distance: u32) {
1521 let world_dims_chunks = self.world.sim().get_size();
1522 let world_dims_blocks = TerrainChunkSize::blocks(world_dims_chunks);
1523 let pos = comp::Pos(Vec3::from(world_dims_blocks.map(|e| e as f32 / 2.0)));
1531 self.state
1532 .create_persister(pos, view_distance, &self.world, &self.index)
1533 .build();
1534 }
1535
1536 pub fn chunks_pending(&mut self) -> bool {
1538 self.state_mut()
1539 .mut_resource::<ChunkGenerator>()
1540 .pending_chunks()
1541 .next()
1542 .is_some()
1543 }
1544
1545 pub fn set_sql_log_mode(&mut self, sql_log_mode: SqlLogMode) {
1547 let mut database_settings = self.database_settings.write().unwrap();
1553 database_settings.sql_log_mode = sql_log_mode;
1554 drop(database_settings);
1557 info!("SQL log mode changed to {:?}", sql_log_mode);
1558 }
1559
1560 pub fn disconnect_all_clients(&mut self) {
1561 info!("Disconnecting all clients due to local console command");
1562 self.disconnect_all_clients_requested = true;
1563 }
1564
1565 pub fn get_battle_mode_for(&mut self, client: EcsEntity) {
1571 let ecs = self.state.ecs();
1572 let time = ecs.read_resource::<Time>();
1573 let settings = ecs.read_resource::<Settings>();
1574 let players = ecs.read_storage::<comp::Player>();
1575 let get_player_result = players.get(client).ok_or_else(|| {
1576 error!("Can't get player component for client.");
1577
1578 Content::Plain("Can't get player component for client.".to_string())
1579 });
1580 let player = match get_player_result {
1581 Ok(player) => player,
1582 Err(content) => {
1583 self.notify_client(
1584 client,
1585 ServerGeneral::server_msg(ChatType::CommandError, content),
1586 );
1587 return;
1588 },
1589 };
1590
1591 let mut msg = format!("Current battle mode: {:?}.", player.battle_mode);
1592
1593 if settings.gameplay.battle_mode.allow_choosing() {
1594 msg.push_str(" Possible to change.");
1595 } else {
1596 msg.push_str(" Global.");
1597 }
1598
1599 if let Some(change) = player.last_battlemode_change {
1600 let Time(time) = *time;
1601 let Time(change) = change;
1602 let elapsed = time - change;
1603 let next = BATTLE_MODE_COOLDOWN - elapsed;
1604
1605 if next > 0.0 {
1606 let notice = format!(" Next change will be available in: {:.0} seconds", next);
1607 msg.push_str(¬ice);
1608 }
1609 }
1610
1611 self.notify_client(
1612 client,
1613 ServerGeneral::server_msg(ChatType::CommandInfo, Content::Plain(msg)),
1614 );
1615 }
1616
1617 pub fn set_battle_mode_for(&mut self, client: EcsEntity, battle_mode: BattleMode) {
1623 let ecs = self.state.ecs();
1624 let time = ecs.read_resource::<Time>();
1625 let settings = ecs.read_resource::<Settings>();
1626
1627 if !settings.gameplay.battle_mode.allow_choosing() {
1628 self.notify_client(
1629 client,
1630 ServerGeneral::server_msg(
1631 ChatType::CommandInfo,
1632 Content::localized("command-disabled-by-settings"),
1633 ),
1634 );
1635
1636 return;
1637 }
1638
1639 #[cfg(feature = "worldgen")]
1640 let in_town = {
1641 let pos = if let Some(pos) = self
1642 .state
1643 .ecs()
1644 .read_storage::<comp::Pos>()
1645 .get(client)
1646 .copied()
1647 {
1648 pos
1649 } else {
1650 self.notify_client(
1651 client,
1652 ServerGeneral::server_msg(
1653 ChatType::CommandInfo,
1654 Content::localized_with_args("command-position-unavailable", [(
1655 "target", "target",
1656 )]),
1657 ),
1658 );
1659
1660 return;
1661 };
1662
1663 let wpos = pos.0.xy().map(|x| x as i32);
1664 let chunk_pos = wpos.wpos_to_cpos();
1665 self.world.civs().sites().any(|site| {
1666 const RADIUS: f32 = 9.0;
1668 let delta = site
1669 .center
1670 .map(|x| x as f32)
1671 .distance(chunk_pos.map(|x| x as f32));
1672 delta < RADIUS
1673 })
1674 };
1675
1676 #[cfg(not(feature = "worldgen"))]
1677 let in_town = true;
1678
1679 if !in_town {
1680 self.notify_client(
1681 client,
1682 ServerGeneral::server_msg(
1683 ChatType::CommandInfo,
1684 Content::localized("command-battlemode-intown"),
1685 ),
1686 );
1687
1688 return;
1689 }
1690
1691 let mut players = ecs.write_storage::<comp::Player>();
1692 let mut player = if let Some(info) = players.get_mut(client) {
1693 info
1694 } else {
1695 error!("Failed to get info for player.");
1696
1697 return;
1698 };
1699
1700 if let Some(Time(last_change)) = player.last_battlemode_change {
1701 let Time(time) = *time;
1702 let elapsed = time - last_change;
1703 if elapsed < BATTLE_MODE_COOLDOWN {
1704 let next = BATTLE_MODE_COOLDOWN - elapsed;
1705
1706 self.notify_client(
1707 client,
1708 ServerGeneral::server_msg(
1709 ChatType::CommandInfo,
1710 Content::Plain(format!(
1711 "Next change will be available in {next:.0} seconds."
1712 )),
1713 ),
1714 );
1715
1716 return;
1717 }
1718 }
1719
1720 if player.battle_mode == battle_mode {
1721 self.notify_client(
1722 client,
1723 ServerGeneral::server_msg(
1724 ChatType::CommandInfo,
1725 Content::localized("command-battlemode-same"),
1726 ),
1727 );
1728
1729 return;
1730 }
1731
1732 player.battle_mode = battle_mode;
1733 player.last_battlemode_change = Some(*time);
1734
1735 self.notify_client(
1736 client,
1737 ServerGeneral::server_msg(
1738 ChatType::CommandInfo,
1739 Content::localized_with_args("command-battlemode-updated", [(
1740 "battlemode",
1741 format!("{battle_mode:?}"),
1742 )]),
1743 ),
1744 );
1745
1746 drop(players);
1747
1748 let uid = ecs.read_storage::<Uid>().get(client).copied().unwrap();
1749
1750 self.state().notify_players(ServerGeneral::PlayerListUpdate(
1751 PlayerListUpdate::UpdateBattleMode(uid, battle_mode),
1752 ));
1753 }
1754}
1755
1756impl Drop for Server {
1757 fn drop(&mut self) {
1758 self.state
1759 .notify_players(ServerGeneral::Disconnect(DisconnectReason::Shutdown));
1760
1761 #[cfg(feature = "persistent_world")]
1762 self.state
1763 .ecs()
1764 .try_fetch_mut::<TerrainPersistence>()
1765 .map(|mut terrain_persistence| {
1766 info!("Unloading terrain persistence...");
1767 terrain_persistence.unload_all()
1768 });
1769
1770 #[cfg(feature = "worldgen")]
1771 {
1772 debug!("Saving rtsim state...");
1773 self.state.ecs().write_resource::<rtsim::RtSim>().save(true);
1774 }
1775 }
1776}
1777
1778#[must_use]
1779pub fn handle_edit<T, S: settings::EditableSetting>(
1780 data: T,
1781 result: Option<(String, Result<(), settings::SettingError<S>>)>,
1782) -> Option<T> {
1783 use crate::settings::SettingError;
1784 let (info, result) = result?;
1785 match result {
1786 Ok(()) => {
1787 info!("{}", info);
1788 Some(data)
1789 },
1790 Err(SettingError::Io(err)) => {
1791 warn!(
1792 ?err,
1793 "Failed to write settings file to disk, but succeeded in memory (success message: \
1794 {})",
1795 info,
1796 );
1797 Some(data)
1798 },
1799 Err(SettingError::Integrity(err)) => {
1800 error!(?err, "Encountered an error while validating the request",);
1801 None
1802 },
1803 }
1804}
1805
1806#[must_use]
1811pub fn add_admin(
1812 username: &str,
1813 role: comp::AdminRole,
1814 login_provider: &LoginProvider,
1815 editable_settings: &mut EditableSettings,
1816 data_dir: &std::path::Path,
1817) -> Option<common::uuid::Uuid> {
1818 use crate::settings::EditableSetting;
1819 let role_ = role.into();
1820 match login_provider.username_to_uuid(username) {
1821 Ok(uuid) => handle_edit(
1822 uuid,
1823 editable_settings.admins.edit(data_dir, |admins| {
1824 match admins.insert(uuid, settings::AdminRecord {
1825 username_when_admined: Some(username.into()),
1826 date: chrono::Utc::now(),
1827 role: role_,
1828 }) {
1829 None => Some(format!(
1830 "Successfully added {} ({}) as {:?}!",
1831 username, uuid, role
1832 )),
1833 Some(old_admin) if old_admin.role == role_ => {
1834 info!("{} ({}) already has role: {:?}!", username, uuid, role);
1835 None
1836 },
1837 Some(old_admin) => Some(format!(
1838 "{} ({}) role changed from {:?} to {:?}!",
1839 username, uuid, old_admin.role, role
1840 )),
1841 }
1842 }),
1843 ),
1844 Err(err) => {
1845 error!(
1846 ?err,
1847 "Could not find uuid for this name; either the user does not exist or there was \
1848 an error communicating with the auth server."
1849 );
1850 None
1851 },
1852 }
1853}
1854
1855#[must_use]
1860pub fn remove_admin(
1861 username: &str,
1862 login_provider: &LoginProvider,
1863 editable_settings: &mut EditableSettings,
1864 data_dir: &std::path::Path,
1865) -> Option<common::uuid::Uuid> {
1866 use crate::settings::EditableSetting;
1867 match login_provider.username_to_uuid(username) {
1868 Ok(uuid) => handle_edit(
1869 uuid,
1870 editable_settings.admins.edit(data_dir, |admins| {
1871 if let Some(admin) = admins.remove(&uuid) {
1872 Some(format!(
1873 "Successfully removed {} ({}) with role {:?} from the admins list",
1874 username, uuid, admin.role,
1875 ))
1876 } else {
1877 info!("{} ({}) is not an admin!", username, uuid);
1878 None
1879 }
1880 }),
1881 ),
1882 Err(err) => {
1883 error!(
1884 ?err,
1885 "Could not find uuid for this name; either the user does not exist or there was \
1886 an error communicating with the auth server."
1887 );
1888 None
1889 },
1890 }
1891}