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 for chunk in &self.state.terrain_changes().removed_chunks {
893 rtsim.hook_unload_chunk(*chunk);
894 }
895 }
896
897 let anchors = self.state.ecs().read_storage::<Anchor>();
908 let anchored_anchor_entities: Vec<Entity> = (
909 &self.state.ecs().entities(),
910 &self.state.ecs().read_storage::<Anchor>(),
911 )
912 .join()
913 .filter_map(|(_, anchor)| match anchor {
914 Anchor::Entity(anchor_entity) => Some(*anchor_entity),
915 _ => None,
916 })
917 .filter(|anchor_entity| match anchors.get(*anchor_entity) {
921 Some(Anchor::Entity(_)) => true,
922 Some(Anchor::Chunk(_)) | None => false
923 })
924 .collect();
925 drop(anchors);
926
927 for entity in anchored_anchor_entities {
928 if cfg!(debug_assertions) {
929 panic!("Entity anchor chain detected");
930 }
931 error!(
932 "Detected an anchor entity that itself has an anchor entity - anchor chains are \
933 not currently supported. The entity's Anchor component has been deleted"
934 );
935 self.state.delete_component::<Anchor>(entity);
936 }
937
938 let to_delete = {
941 let terrain = self.state.terrain();
942 (
943 &self.state.ecs().entities(),
944 &self.state.ecs().read_storage::<comp::Pos>(),
945 !&self.state.ecs().read_storage::<comp::Presence>(),
946 self.state.ecs().read_storage::<Anchor>().maybe(),
947 self.state.ecs().read_storage::<Is<VolumeRider>>().maybe(),
948 )
949 .join()
950 .filter(|(_, pos, _, anchor, is_volume_rider)| {
951 let pos = is_volume_rider
952 .and_then(|is_volume_rider| match is_volume_rider.pos.kind {
953 Volume::Terrain => None,
954 Volume::Entity(e) => {
955 let e = self.state.ecs().entity_from_uid(e)?;
956 let pos = self
957 .state
958 .ecs()
959 .read_storage::<comp::Pos>()
960 .get(e)
961 .copied()?;
962
963 Some(pos.0)
964 },
965 })
966 .unwrap_or(pos.0);
967 let chunk_key = terrain.pos_key(pos.map(|e| e.floor() as i32));
968 match anchor {
969 Some(Anchor::Chunk(hc)) => {
970 terrain.get_key_real(chunk_key).is_none()
975 && terrain.get_key_real(*hc).is_none()
976 },
977 Some(Anchor::Entity(entity)) => !self.state.ecs().is_alive(*entity),
978 None => terrain.get_key_real(chunk_key).is_none(),
979 }
980 })
981 .map(|(entity, _, _, _, _)| entity)
982 .collect::<Vec<_>>()
983 };
984
985 #[cfg(feature = "worldgen")]
986 {
987 let mut rtsim = self.state.ecs().write_resource::<rtsim::RtSim>();
988 let rtsim_entities = self.state.ecs().read_storage();
989 for entity in &to_delete {
990 if let Some(rtsim_entity) = rtsim_entities.get(*entity) {
991 rtsim.hook_rtsim_entity_unload(*rtsim_entity);
992 }
993 }
994 }
995
996 for entity in to_delete {
998 if let Err(e) = self.state.delete_entity_recorded(entity) {
999 error!(?e, "Failed to delete agent outside the terrain");
1000 }
1001 }
1002
1003 if let Some(DisconnectType::WithoutPersistence) = disconnect_type {
1004 info!(
1005 "Disconnection of all players without persistence complete, signalling to \
1006 persistence thread that character updates may continue to be processed"
1007 );
1008 self.state
1009 .ecs()
1010 .fetch_mut::<CharacterUpdater>()
1011 .disconnected_success();
1012 }
1013
1014 let before_persistence_updates = Instant::now();
1016
1017 let character_loader = self.state.ecs().read_resource::<CharacterLoader>();
1018
1019 let mut character_updater = self.state.ecs().write_resource::<CharacterUpdater>();
1020 let updater_messages: Vec<CharacterUpdaterMessage> = character_updater.messages().collect();
1021
1022 character_loader
1024 .messages()
1025 .chain(updater_messages)
1026 .for_each(|message| match message {
1027 CharacterUpdaterMessage::DatabaseBatchCompletion(batch_id) => {
1028 character_updater.process_batch_completion(batch_id);
1029 },
1030 CharacterUpdaterMessage::CharacterScreenResponse(response) => {
1031 match response.response_kind {
1032 CharacterScreenResponseKind::CharacterList(result) => match result {
1033 Ok(mut character_list_data) => {
1034 self.parse_locations(&mut character_list_data);
1035 self.notify_client(
1036 response.target_entity,
1037 ServerGeneral::CharacterListUpdate(character_list_data),
1038 )
1039 },
1040 Err(error) => self.notify_client(
1041 response.target_entity,
1042 ServerGeneral::CharacterActionError(error.to_string()),
1043 ),
1044 },
1045 CharacterScreenResponseKind::CharacterCreation(result) => match result {
1046 Ok((character_id, mut list)) => {
1047 self.parse_locations(&mut list);
1048 self.notify_client(
1049 response.target_entity,
1050 ServerGeneral::CharacterListUpdate(list),
1051 );
1052 self.notify_client(
1053 response.target_entity,
1054 ServerGeneral::CharacterCreated(character_id),
1055 );
1056 },
1057 Err(error) => self.notify_client(
1058 response.target_entity,
1059 ServerGeneral::CharacterActionError(error.to_string()),
1060 ),
1061 },
1062 CharacterScreenResponseKind::CharacterEdit(result) => match result {
1063 Ok((character_id, mut list)) => {
1064 self.parse_locations(&mut list);
1065 self.notify_client(
1066 response.target_entity,
1067 ServerGeneral::CharacterListUpdate(list),
1068 );
1069 self.notify_client(
1070 response.target_entity,
1071 ServerGeneral::CharacterEdited(character_id),
1072 );
1073 },
1074 Err(error) => self.notify_client(
1075 response.target_entity,
1076 ServerGeneral::CharacterActionError(error.to_string()),
1077 ),
1078 },
1079 CharacterScreenResponseKind::CharacterData(result) => {
1080 match *result {
1081 Ok((character_data, skill_set_persistence_load_error)) => {
1082 let PersistedComponents {
1083 body,
1084 hardcore,
1085 stats,
1086 skill_set,
1087 inventory,
1088 waypoint,
1089 pets,
1090 active_abilities,
1091 map_marker,
1092 } = character_data;
1093 let character_data = (
1094 body,
1095 hardcore,
1096 stats,
1097 skill_set,
1098 inventory,
1099 waypoint,
1100 pets,
1101 active_abilities,
1102 map_marker,
1103 );
1104 self.state.emit_event_now(UpdateCharacterDataEvent {
1107 entity: response.target_entity,
1108 components: character_data,
1109 metadata: skill_set_persistence_load_error,
1110 })
1111 },
1112 Err(error) => {
1113 self.notify_client(
1117 response.target_entity,
1118 ServerGeneral::CharacterDataLoadResult(Err(
1119 error.to_string()
1120 )),
1121 );
1122
1123 self.state.emit_event_now(ExitIngameEvent {
1125 entity: response.target_entity,
1126 })
1127 },
1128 }
1129 },
1130 }
1131 },
1132 });
1133
1134 drop(character_loader);
1135 drop(character_updater);
1136
1137 {
1138 let index = &mut self.index;
1143 let world = &mut self.world;
1144 let ecs = self.state.ecs_mut();
1145 let slow_jobs = ecs.write_resource::<SlowJobPool>();
1146
1147 index.reload_if_changed(|index| {
1148 let mut chunk_generator = ecs.write_resource::<ChunkGenerator>();
1149 let client = ecs.read_storage::<Client>();
1150 let mut terrain = ecs.write_resource::<common::terrain::TerrainGrid>();
1151 #[cfg(feature = "worldgen")]
1152 let rtsim = ecs.read_resource::<rtsim::RtSim>();
1153 #[cfg(not(feature = "worldgen"))]
1154 let rtsim = ();
1155
1156 chunk_generator.cancel_all();
1158
1159 if client.is_empty() {
1160 terrain.clear();
1162 } else {
1163 terrain.iter().for_each(|(pos, _)| {
1165 chunk_generator.generate_chunk(
1166 None,
1167 pos,
1168 &slow_jobs,
1169 Arc::clone(world),
1170 &rtsim,
1171 index.clone(),
1172 (
1173 *ecs.read_resource::<TimeOfDay>(),
1174 (*ecs.read_resource::<Calendar>()).clone(),
1175 ),
1176 );
1177 });
1178 }
1179 });
1180 }
1181
1182 let end_of_server_tick = Instant::now();
1183
1184 run_now::<sys::metrics::Sys>(self.state.ecs());
1186
1187 {
1188 let tick_metrics = self.state.ecs().read_resource::<TickMetrics>();
1190
1191 let tt = &tick_metrics.tick_time;
1192 tt.with_label_values(&["new connections"])
1193 .set((before_state_tick - before_new_connections).as_nanos() as i64);
1194 tt.with_label_values(&["handle server events"])
1195 .set((before_update_terrain_and_regions - before_handle_events).as_nanos() as i64);
1196 tt.with_label_values(&["update terrain and region map"])
1197 .set((before_sync - before_update_terrain_and_regions).as_nanos() as i64);
1198 tt.with_label_values(&["state"])
1199 .set((before_handle_events - before_state_tick).as_nanos() as i64);
1200 tt.with_label_values(&["world tick"])
1201 .set((before_entity_cleanup - before_world_tick).as_nanos() as i64);
1202 tt.with_label_values(&["entity cleanup"])
1203 .set((before_persistence_updates - before_entity_cleanup).as_nanos() as i64);
1204 tt.with_label_values(&["persistence_updates"])
1205 .set((end_of_server_tick - before_persistence_updates).as_nanos() as i64);
1206 for (label, duration) in state_tick_metrics.timings {
1207 tick_metrics
1208 .state_tick_time
1209 .with_label_values(&[label])
1210 .set(duration.as_nanos() as i64);
1211 }
1212 tick_metrics.tick_time_hist.observe(
1213 end_of_server_tick
1214 .duration_since(before_state_tick)
1215 .as_secs_f64(),
1216 );
1217 }
1218
1219 Ok(frontend_events)
1222 }
1223
1224 pub fn cleanup(&mut self) {
1226 self.state.cleanup();
1228
1229 #[cfg(feature = "persistent_world")]
1231 self.state
1232 .ecs()
1233 .try_fetch_mut::<TerrainPersistence>()
1234 .map(|mut t| t.maintain());
1235 }
1236
1237 fn update_region_map(&mut self) {
1239 prof_span!("Server::update_region_map");
1240 let ecs = self.state().ecs();
1241 ecs.write_resource::<RegionMap>().tick(
1242 ecs.read_storage::<comp::Pos>(),
1243 ecs.read_storage::<comp::Vel>(),
1244 ecs.read_storage::<comp::Presence>(),
1245 ecs.entities(),
1246 );
1247 }
1248
1249 fn initialize_client(&mut self, client: connection_handler::IncomingClient) -> Entity {
1250 let entity = self
1251 .state
1252 .ecs_mut()
1253 .create_entity_synced()
1254 .with(client)
1255 .build();
1256 self.state
1257 .ecs()
1258 .read_resource::<metrics::PlayerMetrics>()
1259 .clients_connected
1260 .inc();
1261 entity
1262 }
1263
1264 fn disconnect_all_clients_if_requested(&mut self) -> Option<DisconnectType> {
1268 let mut character_updater = self.state.ecs().fetch_mut::<CharacterUpdater>();
1269
1270 let disconnect_type = self.get_disconnect_all_clients_requested(&mut character_updater);
1271 if let Some(disconnect_type) = disconnect_type {
1272 let with_persistence = disconnect_type == DisconnectType::WithPersistence;
1273 let clients = self.state.ecs().read_storage::<Client>();
1274 let entities = self.state.ecs().entities();
1275
1276 info!(
1277 "Disconnecting all clients ({} persistence) as requested",
1278 if with_persistence { "with" } else { "without" }
1279 );
1280 for (_, entity) in (&clients, &entities).join() {
1281 info!("Emitting client disconnect event for entity: {:?}", entity);
1282 if with_persistence {
1283 self.state.emit_event_now(ClientDisconnectEvent(
1284 entity,
1285 comp::DisconnectReason::Kicked,
1286 ))
1287 } else {
1288 self.state
1289 .emit_event_now(ClientDisconnectWithoutPersistenceEvent(entity))
1290 };
1291 }
1292
1293 self.disconnect_all_clients_requested = false;
1294 }
1295
1296 disconnect_type
1297 }
1298
1299 fn get_disconnect_all_clients_requested(
1300 &self,
1301 character_updater: &mut CharacterUpdater,
1302 ) -> Option<DisconnectType> {
1303 let without_persistence_requested = character_updater.disconnect_all_clients_requested();
1304 let with_persistence_requested = self.disconnect_all_clients_requested;
1305
1306 if without_persistence_requested {
1307 return Some(DisconnectType::WithoutPersistence);
1308 };
1309 if with_persistence_requested {
1310 return Some(DisconnectType::WithPersistence);
1311 };
1312 None
1313 }
1314
1315 fn handle_new_connections(&mut self, frontend_events: &mut Vec<Event>) {
1317 while let Ok(sender) = self.connection_handler.info_requester_receiver.try_recv() {
1318 trace!("sending info to connection_handler");
1320 let _ = sender.send(connection_handler::ServerInfoPacket {
1321 info: self.get_server_info(),
1322 time: self.state.get_time(),
1323 });
1324 }
1325
1326 while let Ok(incoming) = self.connection_handler.client_receiver.try_recv() {
1327 let entity = self.initialize_client(incoming);
1328 frontend_events.push(Event::ClientConnected { entity });
1329 }
1330 }
1331
1332 pub fn notify_client<S>(&self, entity: EcsEntity, msg: S)
1333 where
1334 S: Into<ServerMsg>,
1335 {
1336 if let Some(client) = self.state.ecs().read_storage::<Client>().get(entity) {
1337 client.send_fallible(msg);
1338 }
1339 }
1340
1341 pub fn notify_players(&mut self, msg: ServerGeneral) { self.state.notify_players(msg); }
1342
1343 pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2<i32>) {
1344 let ecs = self.state.ecs();
1345 let slow_jobs = ecs.read_resource::<SlowJobPool>();
1346 #[cfg(feature = "worldgen")]
1347 let rtsim = ecs.read_resource::<rtsim::RtSim>();
1348 #[cfg(not(feature = "worldgen"))]
1349 let rtsim = ();
1350 ecs.write_resource::<ChunkGenerator>().generate_chunk(
1351 Some(entity),
1352 key,
1353 &slow_jobs,
1354 Arc::clone(&self.world),
1355 &rtsim,
1356 self.index.clone(),
1357 (
1358 *ecs.read_resource::<TimeOfDay>(),
1359 (*ecs.read_resource::<Calendar>()).clone(),
1360 ),
1361 );
1362 }
1363
1364 fn process_command(&mut self, entity: EcsEntity, name: String, args: Vec<String>) {
1365 if let Ok(command) = name.parse::<ServerChatCommand>() {
1367 command.execute(self, entity, args);
1368 } else {
1369 #[cfg(feature = "plugins")]
1370 {
1371 let mut plugin_manager = self.state.ecs().write_resource::<PluginMgr>();
1372 let ecs_world = EcsWorld {
1373 entities: &self.state.ecs().entities(),
1374 health: self.state.ecs().read_component().into(),
1375 uid: self.state.ecs().read_component().into(),
1376 id_maps: &self.state.ecs().read_resource::<IdMaps>().into(),
1377 player: self.state.ecs().read_component().into(),
1378 };
1379 let uid = if let Some(uid) = ecs_world.uid.get(entity).copied() {
1380 uid
1381 } else {
1382 self.notify_client(
1383 entity,
1384 ServerGeneral::server_msg(
1385 comp::ChatType::CommandError,
1386 common::comp::Content::Plain(
1387 "Can't get player UUID (player may be disconnected?)".to_string(),
1388 ),
1389 ),
1390 );
1391 return;
1392 };
1393 match plugin_manager.command_event(&ecs_world, &name, args.as_slice(), uid) {
1394 Err(common_state::plugin::CommandResults::UnknownCommand) => self
1395 .notify_client(
1396 entity,
1397 ServerGeneral::server_msg(
1398 comp::ChatType::CommandError,
1399 common::comp::Content::Plain(format!(
1400 "Unknown command '/{name}'.\nType '/help' for available \
1401 commands",
1402 )),
1403 ),
1404 ),
1405 Ok(value) => {
1406 self.notify_client(
1407 entity,
1408 ServerGeneral::server_msg(
1409 comp::ChatType::CommandInfo,
1410 common::comp::Content::Plain(value.join("\n")),
1411 ),
1412 );
1413 },
1414 Err(common_state::plugin::CommandResults::PluginError(err)) => {
1415 self.notify_client(
1416 entity,
1417 ServerGeneral::server_msg(
1418 comp::ChatType::CommandError,
1419 common::comp::Content::Plain(format!(
1420 "Error occurred while executing command '/{name}'.\n{err}"
1421 )),
1422 ),
1423 );
1424 },
1425 Err(common_state::plugin::CommandResults::HostError(err)) => {
1426 error!(?err, ?name, ?args, "Can't execute command");
1427 self.notify_client(
1428 entity,
1429 ServerGeneral::server_msg(
1430 comp::ChatType::CommandError,
1431 common::comp::Content::Plain(format!(
1432 "Internal error {err:?} while executing '/{name}'.\nContact \
1433 the server administrator",
1434 )),
1435 ),
1436 );
1437 },
1438 }
1439 }
1440 }
1441 }
1442
1443 fn entity_admin_role(&self, entity: EcsEntity) -> Option<comp::AdminRole> {
1444 self.state
1445 .read_component_copied::<comp::Admin>(entity)
1446 .map(|admin| admin.0)
1447 }
1448
1449 pub fn number_of_players(&self) -> i64 {
1450 self.state.ecs().read_storage::<Client>().join().count() as i64
1451 }
1452
1453 pub fn add_admin(&mut self, username: &str, role: comp::AdminRole) {
1456 let mut editable_settings = self.editable_settings_mut();
1457 let login_provider = self.state.ecs().fetch::<LoginProvider>();
1458 let data_dir = self.data_dir();
1459 if let Some(entity) = add_admin(
1460 username,
1461 role,
1462 &login_provider,
1463 &mut editable_settings,
1464 &data_dir.path,
1465 )
1466 .and_then(|uuid| {
1467 let state = &self.state;
1468 (
1469 &state.ecs().entities(),
1470 &state.read_storage::<comp::Player>(),
1471 )
1472 .join()
1473 .find(|(_, player)| player.uuid() == uuid)
1474 .map(|(e, _)| e)
1475 }) {
1476 drop((data_dir, login_provider, editable_settings));
1477 self.state
1480 .write_component_ignore_entity_dead(entity, comp::Admin(role));
1481 };
1482 }
1483
1484 pub fn remove_admin(&self, username: &str) {
1487 let mut editable_settings = self.editable_settings_mut();
1488 let login_provider = self.state.ecs().fetch::<LoginProvider>();
1489 let data_dir = self.data_dir();
1490 if let Some(entity) = remove_admin(
1491 username,
1492 &login_provider,
1493 &mut editable_settings,
1494 &data_dir.path,
1495 )
1496 .and_then(|uuid| {
1497 let state = &self.state;
1498 (
1499 &state.ecs().entities(),
1500 &state.read_storage::<comp::Player>(),
1501 )
1502 .join()
1503 .find(|(_, player)| player.uuid() == uuid)
1504 .map(|(e, _)| e)
1505 }) {
1506 self.state
1508 .ecs()
1509 .write_storage::<comp::Admin>()
1510 .remove(entity);
1511 };
1512 }
1513
1514 #[cfg(feature = "worldgen")]
1519 pub fn create_centered_persister(&mut self, view_distance: u32) {
1520 let world_dims_chunks = self.world.sim().get_size();
1521 let world_dims_blocks = TerrainChunkSize::blocks(world_dims_chunks);
1522 let pos = comp::Pos(Vec3::from(world_dims_blocks.map(|e| e as f32 / 2.0)));
1530 self.state
1531 .create_persister(pos, view_distance, &self.world, &self.index)
1532 .build();
1533 }
1534
1535 pub fn chunks_pending(&mut self) -> bool {
1537 self.state_mut()
1538 .mut_resource::<ChunkGenerator>()
1539 .pending_chunks()
1540 .next()
1541 .is_some()
1542 }
1543
1544 pub fn set_sql_log_mode(&mut self, sql_log_mode: SqlLogMode) {
1546 let mut database_settings = self.database_settings.write().unwrap();
1552 database_settings.sql_log_mode = sql_log_mode;
1553 drop(database_settings);
1556 info!("SQL log mode changed to {:?}", sql_log_mode);
1557 }
1558
1559 pub fn disconnect_all_clients(&mut self) {
1560 info!("Disconnecting all clients due to local console command");
1561 self.disconnect_all_clients_requested = true;
1562 }
1563
1564 pub fn get_battle_mode_for(&mut self, client: EcsEntity) {
1570 let ecs = self.state.ecs();
1571 let time = ecs.read_resource::<Time>();
1572 let settings = ecs.read_resource::<Settings>();
1573 let players = ecs.read_storage::<comp::Player>();
1574 let get_player_result = players.get(client).ok_or_else(|| {
1575 error!("Can't get player component for client.");
1576
1577 Content::Plain("Can't get player component for client.".to_string())
1578 });
1579 let player = match get_player_result {
1580 Ok(player) => player,
1581 Err(content) => {
1582 self.notify_client(
1583 client,
1584 ServerGeneral::server_msg(ChatType::CommandError, content),
1585 );
1586 return;
1587 },
1588 };
1589
1590 let mut msg = format!("Current battle mode: {:?}.", player.battle_mode);
1591
1592 if settings.gameplay.battle_mode.allow_choosing() {
1593 msg.push_str(" Possible to change.");
1594 } else {
1595 msg.push_str(" Global.");
1596 }
1597
1598 if let Some(change) = player.last_battlemode_change {
1599 let Time(time) = *time;
1600 let Time(change) = change;
1601 let elapsed = time - change;
1602 let next = BATTLE_MODE_COOLDOWN - elapsed;
1603
1604 if next > 0.0 {
1605 let notice = format!(" Next change will be available in: {:.0} seconds", next);
1606 msg.push_str(¬ice);
1607 }
1608 }
1609
1610 self.notify_client(
1611 client,
1612 ServerGeneral::server_msg(ChatType::CommandInfo, Content::Plain(msg)),
1613 );
1614 }
1615
1616 pub fn set_battle_mode_for(&mut self, client: EcsEntity, battle_mode: BattleMode) {
1622 let ecs = self.state.ecs();
1623 let time = ecs.read_resource::<Time>();
1624 let settings = ecs.read_resource::<Settings>();
1625
1626 if !settings.gameplay.battle_mode.allow_choosing() {
1627 self.notify_client(
1628 client,
1629 ServerGeneral::server_msg(
1630 ChatType::CommandInfo,
1631 Content::localized("command-disabled-by-settings"),
1632 ),
1633 );
1634
1635 return;
1636 }
1637
1638 #[cfg(feature = "worldgen")]
1639 let in_town = {
1640 let pos = if let Some(pos) = self
1641 .state
1642 .ecs()
1643 .read_storage::<comp::Pos>()
1644 .get(client)
1645 .copied()
1646 {
1647 pos
1648 } else {
1649 self.notify_client(
1650 client,
1651 ServerGeneral::server_msg(
1652 ChatType::CommandInfo,
1653 Content::localized_with_args("command-position-unavailable", [(
1654 "target", "target",
1655 )]),
1656 ),
1657 );
1658
1659 return;
1660 };
1661
1662 let wpos = pos.0.xy().map(|x| x as i32);
1663 let chunk_pos = wpos.wpos_to_cpos();
1664 self.world.civs().sites().any(|site| {
1665 const RADIUS: f32 = 9.0;
1667 let delta = site
1668 .center
1669 .map(|x| x as f32)
1670 .distance(chunk_pos.map(|x| x as f32));
1671 delta < RADIUS
1672 })
1673 };
1674
1675 #[cfg(not(feature = "worldgen"))]
1676 let in_town = true;
1677
1678 if !in_town {
1679 self.notify_client(
1680 client,
1681 ServerGeneral::server_msg(
1682 ChatType::CommandInfo,
1683 Content::localized("command-battlemode-intown"),
1684 ),
1685 );
1686
1687 return;
1688 }
1689
1690 let mut players = ecs.write_storage::<comp::Player>();
1691 let mut player = if let Some(info) = players.get_mut(client) {
1692 info
1693 } else {
1694 error!("Failed to get info for player.");
1695
1696 return;
1697 };
1698
1699 if let Some(Time(last_change)) = player.last_battlemode_change {
1700 let Time(time) = *time;
1701 let elapsed = time - last_change;
1702 if elapsed < BATTLE_MODE_COOLDOWN {
1703 let next = BATTLE_MODE_COOLDOWN - elapsed;
1704
1705 self.notify_client(
1706 client,
1707 ServerGeneral::server_msg(
1708 ChatType::CommandInfo,
1709 Content::Plain(format!(
1710 "Next change will be available in {next:.0} seconds."
1711 )),
1712 ),
1713 );
1714
1715 return;
1716 }
1717 }
1718
1719 if player.battle_mode == battle_mode {
1720 self.notify_client(
1721 client,
1722 ServerGeneral::server_msg(
1723 ChatType::CommandInfo,
1724 Content::localized("command-battlemode-same"),
1725 ),
1726 );
1727
1728 return;
1729 }
1730
1731 player.battle_mode = battle_mode;
1732 player.last_battlemode_change = Some(*time);
1733
1734 self.notify_client(
1735 client,
1736 ServerGeneral::server_msg(
1737 ChatType::CommandInfo,
1738 Content::localized_with_args("command-battlemode-updated", [(
1739 "battlemode",
1740 format!("{battle_mode:?}"),
1741 )]),
1742 ),
1743 );
1744
1745 drop(players);
1746
1747 let uid = ecs.read_storage::<Uid>().get(client).copied().unwrap();
1748
1749 self.state().notify_players(ServerGeneral::PlayerListUpdate(
1750 PlayerListUpdate::UpdateBattleMode(uid, battle_mode),
1751 ));
1752 }
1753}
1754
1755impl Drop for Server {
1756 fn drop(&mut self) {
1757 self.state
1758 .notify_players(ServerGeneral::Disconnect(DisconnectReason::Shutdown));
1759
1760 #[cfg(feature = "persistent_world")]
1761 self.state
1762 .ecs()
1763 .try_fetch_mut::<TerrainPersistence>()
1764 .map(|mut terrain_persistence| {
1765 info!("Unloading terrain persistence...");
1766 terrain_persistence.unload_all()
1767 });
1768
1769 #[cfg(feature = "worldgen")]
1770 {
1771 debug!("Saving rtsim state...");
1772 self.state.ecs().write_resource::<rtsim::RtSim>().save(true);
1773 }
1774 }
1775}
1776
1777#[must_use]
1778pub fn handle_edit<T, S: settings::EditableSetting>(
1779 data: T,
1780 result: Option<(String, Result<(), settings::SettingError<S>>)>,
1781) -> Option<T> {
1782 use crate::settings::SettingError;
1783 let (info, result) = result?;
1784 match result {
1785 Ok(()) => {
1786 info!("{}", info);
1787 Some(data)
1788 },
1789 Err(SettingError::Io(err)) => {
1790 warn!(
1791 ?err,
1792 "Failed to write settings file to disk, but succeeded in memory (success message: \
1793 {})",
1794 info,
1795 );
1796 Some(data)
1797 },
1798 Err(SettingError::Integrity(err)) => {
1799 error!(?err, "Encountered an error while validating the request",);
1800 None
1801 },
1802 }
1803}
1804
1805#[must_use]
1810pub fn add_admin(
1811 username: &str,
1812 role: comp::AdminRole,
1813 login_provider: &LoginProvider,
1814 editable_settings: &mut EditableSettings,
1815 data_dir: &std::path::Path,
1816) -> Option<common::uuid::Uuid> {
1817 use crate::settings::EditableSetting;
1818 let role_ = role.into();
1819 match login_provider.username_to_uuid(username) {
1820 Ok(uuid) => handle_edit(
1821 uuid,
1822 editable_settings.admins.edit(data_dir, |admins| {
1823 match admins.insert(uuid, settings::AdminRecord {
1824 username_when_admined: Some(username.into()),
1825 date: chrono::Utc::now(),
1826 role: role_,
1827 }) {
1828 None => Some(format!(
1829 "Successfully added {} ({}) as {:?}!",
1830 username, uuid, role
1831 )),
1832 Some(old_admin) if old_admin.role == role_ => {
1833 info!("{} ({}) already has role: {:?}!", username, uuid, role);
1834 None
1835 },
1836 Some(old_admin) => Some(format!(
1837 "{} ({}) role changed from {:?} to {:?}!",
1838 username, uuid, old_admin.role, role
1839 )),
1840 }
1841 }),
1842 ),
1843 Err(err) => {
1844 error!(
1845 ?err,
1846 "Could not find uuid for this name; either the user does not exist or there was \
1847 an error communicating with the auth server."
1848 );
1849 None
1850 },
1851 }
1852}
1853
1854#[must_use]
1859pub fn remove_admin(
1860 username: &str,
1861 login_provider: &LoginProvider,
1862 editable_settings: &mut EditableSettings,
1863 data_dir: &std::path::Path,
1864) -> Option<common::uuid::Uuid> {
1865 use crate::settings::EditableSetting;
1866 match login_provider.username_to_uuid(username) {
1867 Ok(uuid) => handle_edit(
1868 uuid,
1869 editable_settings.admins.edit(data_dir, |admins| {
1870 if let Some(admin) = admins.remove(&uuid) {
1871 Some(format!(
1872 "Successfully removed {} ({}) with role {:?} from the admins list",
1873 username, uuid, admin.role,
1874 ))
1875 } else {
1876 info!("{} ({}) is not an admin!", username, uuid);
1877 None
1878 }
1879 }),
1880 ),
1881 Err(err) => {
1882 error!(
1883 ?err,
1884 "Could not find uuid for this name; either the user does not exist or there was \
1885 an error communicating with the auth server."
1886 );
1887 None
1888 },
1889 }
1890}