veloren_voxygen/menu/char_selection/
mod.rs

1mod 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    /// Create a new `CharSelectionState`.
33    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        // Load the player's character list
77        if !self.client.borrow().are_plugins_missing() {
78            self.client.borrow_mut().load_character_list();
79        }
80
81        // Updated localization in case the selected language was changed
82        self.char_selection_ui.update_language(global_state.i18n);
83        // Set scale mode in case it was change
84        self.char_selection_ui
85            .set_scale_mode(global_state.settings.interface.ui_scale);
86
87        // Clear shadow textures since we don't render to them here
88        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            // Handle window events
102            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                    // Pass all other events to the scene
111                    event => {
112                        self.scene.handle_input_event(event);
113                    }, // TODO: Do something if the event wasn't handled?
114                }
115            }
116
117            // Maintain the UI.
118            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                        // Select newly created character
201                        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            // Maintain the scene.
212            {
213                let client = self.client.borrow();
214                let (humanoid_body, loadout) =
215                    Self::get_humanoid_body_inventory(&self.char_selection_ui, &client);
216
217                // Maintain the scene.
218                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            // Tick the client (currently only to keep the connection alive).
244            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                                // NOTE: It's possible we'll lose disconnect messages this way,
271                                // among other things, but oh well.
272                                //
273                                // TODO: Process all messages before returning (from any branch) in
274                                // order to catch disconnect messages.
275                                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                                                // now load characters (plugins might contain items)
296                                                client.load_character_list();
297                                            }
298                                        },
299                                        Err(e) => tracing::error!(?e, "cache_server_plugin"),
300                                    }
301                                }
302                            },
303                            // TODO: See if we should handle StartSpectate here instead.
304                            _ => {},
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            // TODO: make sure rendering is not relying on cleaned up stuff
317            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            // TODO set global_state.info_message
323            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            // Clouds
345            volumetric_pass.draw_clouds();
346        }
347        // Bloom (does nothing if bloom is disabled)
348        drawer.run_bloom_passes();
349        // PostProcess and UI
350        let mut third_pass = drawer.third_pass();
351        third_pass.draw_postprocess();
352        // Draw the UI to the screen.
353        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}