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 message_backlog: Rc<RefCell<hud::MessageBacklog>>,
29 scene: Scene,
30}
31
32impl CharSelectionState {
33 pub fn new(
35 global_state: &mut GlobalState,
36 client: Rc<RefCell<Client>>,
37 message_backlog: Rc<RefCell<hud::MessageBacklog>>,
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 message_backlog,
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.message_backlog),
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.message_backlog),
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 for event in events {
263 match event {
264 client::Event::SetViewDistance(_vd) => {},
265 client::Event::Disconnect => {
266 global_state.info_message = Some(
267 localized_strings
268 .get_msg("main-login-server_shut_down")
269 .into_owned(),
270 );
271 return PlayStateResult::Pop;
272 },
273 client::Event::Chat(m) => self
274 .message_backlog
275 .borrow_mut()
276 .new_message(&self.client.borrow(), &global_state.profile, m),
277 client::Event::CharacterCreated(character_id) => {
278 self.char_selection_ui.select_character(character_id);
279 },
280 client::Event::CharacterError(error) => {
281 self.char_selection_ui.display_error(error);
282 },
283 client::Event::CharacterJoined(metadata) => {
284 return PlayStateResult::Switch(Box::new(SessionState::new(
290 global_state,
291 metadata,
292 Rc::clone(&self.client),
293 Rc::clone(&self.message_backlog),
294 )));
295 },
296 #[cfg_attr(not(feature = "plugins"), expect(unused_variables))]
297 client::Event::PluginDataReceived(data) => {
298 #[cfg(feature = "plugins")]
299 {
300 tracing::info!("plugin data {}", data.len());
301 let mut client = self.client.borrow_mut();
302 let hash = client
303 .state()
304 .ecs()
305 .write_resource::<PluginMgr>()
306 .cache_server_plugin(&global_state.config_dir, data);
307 match hash {
308 Ok(hash) => {
309 if client.plugin_received(hash) == 0 {
310 client.load_character_list();
312 }
313 },
314 Err(e) => tracing::error!(?e, "cache_server_plugin"),
315 }
316 }
317 },
318 _ => {},
320 }
321 }
322 },
323 Err(err) => {
324 error!(?err, "[char_selection] Failed to tick the client");
325 global_state.info_message =
326 Some(get_client_msg_error(err, None, &global_state.i18n.read()));
327 return PlayStateResult::Pop;
328 },
329 }
330
331 self.client.borrow_mut().cleanup();
333
334 PlayStateResult::Continue
335 } else {
336 error!("Client not in pending, or registered state. Popping char selection play state");
337 PlayStateResult::Pop
339 }
340 }
341
342 fn name(&self) -> &'static str { "Character Selection" }
343
344 fn capped_fps(&self) -> bool { true }
345
346 fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
347
348 fn render(&self, drawer: &mut Drawer<'_>, _: &Settings) {
349 let client = self.client.borrow();
350 let (humanoid_body, loadout) =
351 Self::get_humanoid_body_inventory(&self.char_selection_ui, &client);
352
353 if let Some(mut first_pass) = drawer.first_pass() {
354 self.scene
355 .render(&mut first_pass, client.get_tick(), humanoid_body, loadout);
356 }
357
358 if let Some(mut volumetric_pass) = drawer.volumetric_pass() {
359 volumetric_pass.draw_clouds();
361 }
362 drawer.run_bloom_passes();
364 let mut third_pass = drawer.third_pass();
366 third_pass.draw_postprocess();
367 if let Some(mut ui_drawer) = third_pass.draw_ui() {
369 self.char_selection_ui.render(&mut ui_drawer);
370 };
371 }
372
373 fn egui_enabled(&self) -> bool { false }
374}