veloren_voxygen/menu/char_selection/
mod.rs

1mod 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    /// Create a new `CharSelectionState`.
34    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        // Load the player's character list
83        if !self.client.borrow().are_plugins_missing() {
84            self.client.borrow_mut().load_character_list();
85        }
86
87        // Updated localization in case the selected language was changed
88        self.char_selection_ui.update_language(global_state.i18n);
89        // Set scale mode in case it was change
90        self.char_selection_ui
91            .set_scale_mode(global_state.settings.interface.ui_scale);
92
93        // Clear shadow textures since we don't render to them here
94        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            // Handle window events
108            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                    // Pass all other events to the scene
117                    event => {
118                        self.scene.handle_input_event(event);
119                    }, // TODO: Do something if the event wasn't handled?
120                }
121            }
122
123            // Maintain the UI.
124            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                        // Select newly created character
211                        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            // Maintain the scene.
222            {
223                let client = self.client.borrow();
224                let (humanoid_body, loadout) =
225                    Self::get_humanoid_body_inventory(&self.char_selection_ui, &client);
226
227                // Maintain the scene.
228                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            // Tick the client (currently only to keep the connection alive).
254            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                                // NOTE: It's possible we'll lose disconnect messages this way,
285                                // among other things, but oh well.
286                                //
287                                // TODO: Process all messages before returning (from any branch) in
288                                // order to catch disconnect messages.
289                                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                                                // now load characters (plugins might contain items)
311                                                client.load_character_list();
312                                            }
313                                        },
314                                        Err(e) => tracing::error!(?e, "cache_server_plugin"),
315                                    }
316                                }
317                            },
318                            // TODO: See if we should handle StartSpectate here instead.
319                            _ => {},
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            // TODO: make sure rendering is not relying on cleaned up stuff
332            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            // TODO set global_state.info_message
338            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            // Clouds
360            volumetric_pass.draw_clouds();
361        }
362        // Bloom (does nothing if bloom is disabled)
363        drawer.run_bloom_passes();
364        // PostProcess and UI
365        let mut third_pass = drawer.third_pass();
366        third_pass.draw_postprocess();
367        // Draw the UI to the screen.
368        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}