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