veloren_voxygen/menu/char_selection/
mod.rs1mod ui;
2
3use crate::{
4 Direction, GlobalState, PlayState, PlayStateResult, hud,
5 menu::{
6 main::{get_client_msg_error, rand_bg_image_spec},
7 server_info::ServerInfoState,
8 },
9 render::{Drawer, GlobalsBindGroup},
10 scene::simple::{self as scene, Scene},
11 session::SessionState,
12 settings::Settings,
13 window::Event as WinEvent,
14};
15use client::{self, Client};
16use common::{comp, event::UpdateCharacterMetadata, resources::DeltaTime};
17use common_base::span;
18#[cfg(feature = "plugins")]
19use common_state::plugin::PluginMgr;
20use specs::WorldExt;
21use std::{cell::RefCell, rc::Rc};
22use tracing::error;
23use ui::CharSelectionUi;
24
25pub struct CharSelectionState {
26 char_selection_ui: CharSelectionUi,
27 client: Rc<RefCell<Client>>,
28 persisted_state: Rc<RefCell<hud::PersistedHudState>>,
29 scene: Scene,
30}
31
32impl CharSelectionState {
33 pub fn new(
35 global_state: &mut GlobalState,
36 client: Rc<RefCell<Client>>,
37 persisted_state: Rc<RefCell<hud::PersistedHudState>>,
38 ) -> Self {
39 let sprite_render_context = (global_state.lazy_init)(global_state.window.renderer_mut());
40 let scene = Scene::new(
41 global_state.window.renderer_mut(),
42 &mut client.borrow_mut(),
43 &global_state.settings,
44 sprite_render_context,
45 );
46 let char_selection_ui = CharSelectionUi::new(global_state, &client.borrow());
47
48 Self {
49 char_selection_ui,
50 client,
51 persisted_state,
52 scene,
53 }
54 }
55
56 fn get_humanoid_body_inventory<'a>(
57 char_selection_ui: &'a CharSelectionUi,
58 client: &'a Client,
59 ) -> (
60 Option<comp::humanoid::Body>,
61 Option<&'a comp::inventory::Inventory>,
62 ) {
63 char_selection_ui
64 .display_body_inventory(&client.character_list().characters)
65 .map(|(body, inventory)| {
66 (
67 match body {
68 comp::Body::Humanoid(body) => Some(body),
69 _ => None,
70 },
71 Some(inventory),
72 )
73 })
74 .unwrap_or_default()
75 }
76
77 pub fn client(&self) -> &RefCell<Client> { &self.client }
78}
79
80impl PlayState for CharSelectionState {
81 fn enter(&mut self, global_state: &mut GlobalState, _: Direction) {
82 if !self.client.borrow().are_plugins_missing() {
84 self.client.borrow_mut().load_character_list();
85 }
86
87 self.char_selection_ui.update_language(global_state.i18n);
89 self.char_selection_ui
91 .set_scale_mode(global_state.settings.interface.ui_scale);
92
93 global_state.clear_shadows_next_frame = true;
95
96 #[cfg(feature = "discord")]
97 global_state.discord.enter_character_selection();
98 }
99
100 fn tick(&mut self, global_state: &mut GlobalState, events: Vec<WinEvent>) -> PlayStateResult {
101 span!(_guard, "tick", "<CharSelectionState as PlayState>::tick");
102 let client_registered = {
103 let client = self.client.borrow();
104 client.registered()
105 };
106 if client_registered {
107 for event in events {
109 if self.char_selection_ui.handle_event(event.clone()) {
110 continue;
111 }
112 match event {
113 WinEvent::Close => {
114 return PlayStateResult::Shutdown;
115 },
116 event => {
118 self.scene.handle_input_event(event);
119 }, }
121 }
122
123 let events = self
125 .char_selection_ui
126 .maintain(global_state, &self.client.borrow());
127
128 for event in events {
129 match event {
130 ui::Event::Logout => {
131 return PlayStateResult::Pop;
132 },
133 ui::Event::AddCharacter {
134 alias,
135 mainhand,
136 offhand,
137 body,
138 hardcore,
139 start_site,
140 } => {
141 self.client
142 .borrow_mut()
143 .create_character(alias, mainhand, offhand, body, hardcore, start_site);
144 },
145 ui::Event::EditCharacter {
146 alias,
147 character_id,
148 body,
149 } => {
150 self.client
151 .borrow_mut()
152 .edit_character(alias, character_id, body);
153 },
154 ui::Event::DeleteCharacter(character_id) => {
155 self.client.borrow_mut().delete_character(character_id);
156 },
157 ui::Event::Play(character_id) => {
158 let mut c = self.client.borrow_mut();
159 let graphics = &global_state.settings.graphics;
160 c.request_character(character_id, common::ViewDistances {
161 terrain: graphics.terrain_view_distance,
162 entity: graphics.entity_view_distance,
163 });
164 },
165 ui::Event::Spectate => {
166 {
167 let mut c = self.client.borrow_mut();
168 c.request_spectate(global_state.settings.graphics.view_distances());
169 }
170 return PlayStateResult::Switch(Box::new(SessionState::new(
171 global_state,
172 UpdateCharacterMetadata::default(),
173 Rc::clone(&self.client),
174 Rc::clone(&self.persisted_state),
175 )));
176 },
177 ui::Event::ShowRules => {
178 let client = self.client.borrow();
179
180 let server_info = client.server_info().clone();
181 let server_description = client.server_description().clone();
182
183 drop(client);
184
185 let char_select = CharSelectionState::new(
186 global_state,
187 Rc::clone(&self.client),
188 Rc::clone(&self.persisted_state),
189 );
190
191 let new_state = ServerInfoState::try_from_server_info(
192 global_state,
193 rand_bg_image_spec(),
194 char_select,
195 server_info,
196 server_description,
197 true,
198 )
199 .map(|s| Box::new(s) as _)
200 .unwrap_or_else(|s| Box::new(s) as _);
201
202 return PlayStateResult::Switch(new_state);
203 },
204 ui::Event::ClearCharacterListError => {
205 self.char_selection_ui.error = None;
206 },
207 ui::Event::SelectCharacter(selected) => {
208 let client = self.client.borrow();
209 let server_name = &client.server_info().name;
210 global_state
212 .profile
213 .set_selected_character(server_name, selected);
214 global_state
215 .profile
216 .save_to_file_warn(&global_state.config_dir);
217 },
218 }
219 }
220
221 {
223 let client = self.client.borrow();
224 let (humanoid_body, loadout) =
225 Self::get_humanoid_body_inventory(&self.char_selection_ui, &client);
226
227 let scene_data = scene::SceneData {
229 time: client.state().get_time(),
230 delta_time: client.state().ecs().read_resource::<DeltaTime>().0,
231 tick: client.get_tick(),
232 slow_job_pool: &client.state().slow_job_pool(),
233 body: humanoid_body,
234 gamma: global_state.settings.graphics.gamma,
235 exposure: global_state.settings.graphics.exposure,
236 ambiance: global_state.settings.graphics.ambiance,
237 mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable,
238 figure_lod_render_distance: global_state
239 .settings
240 .graphics
241 .figure_lod_render_distance
242 as f32,
243 };
244
245 self.scene.maintain(
246 global_state.window.renderer_mut(),
247 scene_data,
248 loadout,
249 &client,
250 );
251 }
252
253 let localized_strings = &global_state.i18n.read();
255
256 let res = self
257 .client
258 .borrow_mut()
259 .tick(comp::ControllerInputs::default(), global_state.clock.dt());
260 match res {
261 Ok(events) => {
262 let mut join_metadata = None;
263 for event in events {
264 match event {
265 client::Event::SetViewDistance(_vd) => {},
266 client::Event::Disconnect => {
267 global_state.info_message = Some(
268 localized_strings
269 .get_msg("main-login-server_shut_down")
270 .into_owned(),
271 );
272 return PlayStateResult::Pop;
273 },
274 client::Event::Chat(m) => self
275 .persisted_state
276 .borrow_mut()
277 .message_backlog
278 .new_message(&self.client.borrow(), &global_state.profile, m),
279 client::Event::MapMarker(marker_event) => self
280 .persisted_state
281 .borrow_mut()
282 .location_markers
283 .update(marker_event),
284 client::Event::CharacterCreated(character_id) => {
285 self.char_selection_ui.select_character(character_id);
286 },
287 client::Event::CharacterError(error) => {
288 self.char_selection_ui.display_error(error);
289 },
290 client::Event::CharacterJoined(metadata) => {
291 join_metadata = Some(metadata);
292 },
293 #[cfg_attr(not(feature = "plugins"), expect(unused_variables))]
294 client::Event::PluginDataReceived(data) => {
295 #[cfg(feature = "plugins")]
296 {
297 tracing::info!("plugin data {}", data.len());
298 let mut client = self.client.borrow_mut();
299 let hash = client
300 .state()
301 .ecs()
302 .write_resource::<PluginMgr>()
303 .cache_server_plugin(&global_state.config_dir, data);
304 match hash {
305 Ok(hash) => {
306 if client.plugin_received(hash) == 0 {
307 client.load_character_list();
309 }
310 },
311 Err(e) => tracing::error!(?e, "cache_server_plugin"),
312 }
313 }
314 },
315 _ => {},
317 }
318 }
319
320 if let Some(metadata) = join_metadata {
321 return PlayStateResult::Switch(Box::new(SessionState::new(
322 global_state,
323 metadata,
324 Rc::clone(&self.client),
325 Rc::clone(&self.persisted_state),
326 )));
327 }
328 },
329 Err(err) => {
330 error!(?err, "[char_selection] Failed to tick the client");
331 global_state.info_message =
332 Some(get_client_msg_error(err, None, &global_state.i18n.read()));
333 return PlayStateResult::Pop;
334 },
335 }
336
337 self.client.borrow_mut().cleanup();
339
340 PlayStateResult::Continue
341 } else {
342 error!("Client not in pending, or registered state. Popping char selection play state");
343 PlayStateResult::Pop
345 }
346 }
347
348 fn name(&self) -> &'static str { "Character Selection" }
349
350 fn capped_fps(&self) -> bool { true }
351
352 fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
353
354 fn render(&self, drawer: &mut Drawer<'_>, _: &Settings) {
355 let client = self.client.borrow();
356 let (humanoid_body, loadout) =
357 Self::get_humanoid_body_inventory(&self.char_selection_ui, &client);
358
359 if let Some(mut first_pass) = drawer.first_pass() {
360 self.scene
361 .render(&mut first_pass, client.get_tick(), humanoid_body, loadout);
362 }
363
364 if let Some(mut volumetric_pass) = drawer.volumetric_pass() {
365 volumetric_pass.draw_clouds();
367 }
368 drawer.run_bloom_passes();
370 let mut third_pass = drawer.third_pass();
372 third_pass.draw_postprocess();
373 if let Some(mut ui_drawer) = third_pass.draw_ui() {
375 self.char_selection_ui.render(&mut ui_drawer);
376 };
377 }
378
379 fn egui_enabled(&self) -> bool { false }
380}