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