veloren_voxygen/singleplayer/
mod.rs1use common::{clock::Clock, match_some};
2use crossbeam_channel::{Receiver, Sender, TryRecvError, bounded, unbounded};
3use i18n::LocalizationHandle;
4use rand::seq::IteratorRandom;
5use server::{
6 Error as ServerError, Event, Input, Server, ServerInitStage,
7 persistence::{DatabaseSettings, SqlLogMode},
8 settings::server_description::ServerDescription,
9};
10
11use std::{
12 sync::{
13 Arc,
14 atomic::{AtomicBool, Ordering},
15 },
16 thread::{self, JoinHandle},
17 time::Duration,
18};
19use tokio::runtime::Runtime;
20use tracing::{error, info, trace, warn};
21
22mod singleplayer_world;
23pub use singleplayer_world::{SingleplayerWorld, SingleplayerWorlds};
24
25const TPS: u64 = 30;
26
27pub struct Singleplayer {
30 _server_thread: JoinHandle<()>,
31 stop_server_s: Sender<()>,
32 pub receiver: Receiver<Result<(), ServerError>>,
33 pub init_stage_receiver: Receiver<ServerInitStage>,
34 paused: Arc<AtomicBool>,
36}
37
38impl Singleplayer {
39 pub fn is_paused(&self) -> bool { self.paused.load(Ordering::SeqCst) }
41
42 pub fn pause(&self, state: bool) { self.paused.store(state, Ordering::SeqCst); }
45}
46
47impl Drop for Singleplayer {
48 fn drop(&mut self) {
49 let _ = self.stop_server_s.send(());
51 }
52}
53
54#[derive(Default)]
55pub enum SingleplayerState {
56 #[default]
57 None,
58 Init(SingleplayerWorlds),
59 Running(Singleplayer),
60}
61
62impl SingleplayerState {
63 pub fn init() -> Self {
64 let dir = common_base::userdata_dir();
65
66 Self::Init(SingleplayerWorlds::load(&dir))
67 }
68
69 pub fn run(
70 &mut self,
71 runtime: &Arc<Runtime>,
72 selected_language: &String,
73 i18n: &LocalizationHandle,
74 ) {
75 if let Self::Init(worlds) = self {
76 let Some(world) = worlds.current() else {
77 error!("Failed to get the current world.");
78 return;
79 };
80 let server_data_dir = world.path.clone();
81
82 let mut settings = server::Settings::singleplayer(&server_data_dir);
83 let mut editable_settings = server::EditableSettings::singleplayer(&server_data_dir);
84
85 let i18n = i18n.read();
86 let motd = ["hud-chat-singleplayer-motd1", "hud-chat-singleplayer-motd2"]
87 .iter()
88 .choose(&mut rand::rng())
89 .expect("Message of the day don't wanna play.");
90
91 editable_settings.server_description.descriptions.insert(
92 selected_language.to_string(),
93 ServerDescription {
94 motd: i18n.get_msg(motd).to_string(),
95 rules: None,
96 },
97 );
98
99 let file_opts = if let Some(gen_opts) = &world.gen_opts
100 && !world.is_generated
101 {
102 server::FileOpts::Save(world.map_path.clone(), gen_opts.clone())
103 } else {
104 if !world.is_generated && world.gen_opts.is_none() {
105 world.copy_default_world();
106 }
107 server::FileOpts::Load(world.map_path.clone())
108 };
109
110 settings.map_file = Some(file_opts);
111 settings.world_seed = world.seed;
112 settings.day_length = world.day_length;
113
114 let (stop_server_s, stop_server_r) = unbounded();
115
116 let (server_stage_tx, server_stage_rx) = unbounded();
117
118 const PERSISTENCE_DB_DIR: &str = "saves";
122
123 let database_settings = DatabaseSettings {
124 db_dir: server_data_dir.join(PERSISTENCE_DB_DIR),
125 sql_log_mode: SqlLogMode::Disabled, };
131
132 let paused = Arc::new(AtomicBool::new(false));
133 let paused1 = Arc::clone(&paused);
134
135 let (result_sender, result_receiver) = bounded(1);
136
137 let builder = thread::Builder::new().name("singleplayer-server-thread".into());
138 let runtime = Arc::clone(runtime);
139 let thread = builder
140 .spawn(move || {
141 trace!("starting singleplayer server thread");
142
143 let (server, init_result) = match Server::new(
144 settings,
145 editable_settings,
146 database_settings,
147 &server_data_dir,
148 &|init_stage| {
149 let _ = server_stage_tx.send(init_stage);
150 },
151 runtime,
152 ) {
153 Ok(server) => (Some(server), Ok(())),
154 Err(err) => (None, Err(err)),
155 };
156
157 match (result_sender.send(init_result), server) {
158 (Err(e), _) => warn!(
159 ?e,
160 "Failed to send singleplayer server initialization result. Most \
161 likely the channel was closed by cancelling server creation. \
162 Stopping Server"
163 ),
164 (Ok(()), None) => (),
165 (Ok(()), Some(server)) => run_server(server, stop_server_r, paused1),
166 }
167
168 trace!("ending singleplayer server thread");
169 })
170 .unwrap();
171
172 *self = SingleplayerState::Running(Singleplayer {
173 _server_thread: thread,
174 stop_server_s,
175 init_stage_receiver: server_stage_rx,
176 receiver: result_receiver,
177 paused,
178 });
179 } else {
180 error!("SingleplayerState::run was called, but singleplayer is already running!");
181 }
182 }
183
184 pub fn as_running(&self) -> Option<&Singleplayer> {
185 match_some!(self, SingleplayerState::Running(s) => s)
186 }
187
188 pub fn as_init(&self) -> Option<&SingleplayerWorlds> {
189 match_some!(self, SingleplayerState::Init(s) => s)
190 }
191
192 pub fn is_running(&self) -> bool { matches!(self, SingleplayerState::Running(_)) }
193}
194
195fn run_server(mut server: Server, stop_server_r: Receiver<()>, paused: Arc<AtomicBool>) {
196 info!("Starting server-cli...");
197
198 let mut clock = Clock::new(Duration::from_secs_f64(1.0 / TPS as f64));
200
201 loop {
202 match stop_server_r.try_recv() {
204 Ok(()) => break,
205 Err(TryRecvError::Disconnected) => break,
206 Err(TryRecvError::Empty) => (),
207 }
208
209 clock.tick();
211
212 if paused.load(Ordering::SeqCst) && server.number_of_players() < 2 {
214 continue;
215 } else if server.number_of_players() > 1 {
216 paused.store(false, Ordering::SeqCst);
217 }
218
219 let events = server
220 .tick(Input::default(), clock.dt())
221 .expect("Failed to tick server!");
222
223 for event in events {
224 match event {
225 Event::ClientConnected { .. } => info!("Client connected!"),
226 Event::ClientDisconnected { .. } => info!("Client disconnected!"),
227 Event::Chat { entity: _, msg } => info!("[Client] {}", msg),
228 }
229 }
230
231 server.cleanup();
233 }
234}