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    persisted_state: Rc<RefCell<hud::PersistedHudState>>,
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        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        // 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.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                        // 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                    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                                                // now load characters (plugins might contain items)
308                                                client.load_character_list();
309                                            }
310                                        },
311                                        Err(e) => tracing::error!(?e, "cache_server_plugin"),
312                                    }
313                                }
314                            },
315                            // TODO: See if we should handle StartSpectate here instead.
316                            _ => {},
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            // TODO: make sure rendering is not relying on cleaned up stuff
338            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            // TODO set global_state.info_message
344            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            // Clouds
366            volumetric_pass.draw_clouds();
367        }
368        // Bloom (does nothing if bloom is disabled)
369        drawer.run_bloom_passes();
370        // PostProcess and UI
371        let mut third_pass = drawer.third_pass();
372        third_pass.draw_postprocess();
373        // Draw the UI to the screen.
374        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}