1pub mod interactable;
2pub mod settings_change;
3mod target;
4
5use std::{cell::RefCell, collections::HashSet, rc::Rc, result::Result, time::Duration};
6
7use itertools::Itertools;
8#[cfg(not(target_os = "macos"))]
9use mumble_link::SharedLink;
10use ordered_float::OrderedFloat;
11use specs::WorldExt;
12use tracing::{error, info};
13use vek::*;
14
15use client::{self, Client};
16use common::{
17 CachedSpatialGrid,
18 comp::{
19 self, CharacterActivity, CharacterState, ChatType, Content, Fluid, InputKind,
20 InventoryUpdateEvent, Pos, PresenceKind, Stats, UtteranceKind, Vel,
21 inventory::slot::{EquipSlot, Slot},
22 invite::InviteKind,
23 item::{ItemDesc, tool::ToolKind},
24 },
25 consts::MAX_MOUNT_RANGE,
26 event::UpdateCharacterMetadata,
27 link::Is,
28 mounting::{Mount, VolumePos},
29 outcome::Outcome,
30 recipe::{self, RecipeBookManifest},
31 terrain::{Block, BlockKind},
32 trade::TradeResult,
33 util::{Dir, Plane},
34 vol::ReadVol,
35};
36use common_base::{prof_span, span};
37use common_net::{msg::server::InviteAnswer, sync::WorldSyncExt};
38
39use crate::{
40 Direction, GlobalState, PlayState, PlayStateResult,
41 audio::sfx::SfxEvent,
42 cmd::run_command,
43 error::Error,
44 game_input::GameInput,
45 hud::{
46 AutoPressBehavior, DebugInfo, Event as HudEvent, Hud, HudCollectFailedReason, HudInfo,
47 LootMessage, PersistedHudState, PromptDialogSettings,
48 },
49 key_state::KeyState,
50 menu::{char_selection::CharSelectionState, main::get_client_msg_error},
51 render::{Drawer, GlobalsBindGroup},
52 scene::{CameraMode, DebugShapeId, Scene, SceneData, camera},
53 session::target::ray_entities,
54 settings::Settings,
55 window::{AnalogGameInput, Event},
56};
57use hashbrown::HashMap;
58use interactable::{BlockInteraction, EntityInteraction, Interactable, get_interactables};
59use settings_change::Language::ChangeLanguage;
60use target::targets_under_cursor;
61#[cfg(feature = "egui-ui")]
62use voxygen_egui::EguiDebugInfo;
63
64const ZOOM_LOCK_SCROLL_DELTA_INTENT: f32 = 14.0;
82
83enum TickAction {
85 Continue,
87 Disconnect,
89}
90
91#[derive(Default)]
92pub struct PlayerDebugLines {
93 pub chunk_normal: Option<DebugShapeId>,
94 pub wind: Option<DebugShapeId>,
95 pub fluid_vel: Option<DebugShapeId>,
96 pub vel: Option<DebugShapeId>,
97}
98
99pub struct SessionState {
100 scene: Scene,
101 pub(crate) client: Rc<RefCell<Client>>,
102 metadata: UpdateCharacterMetadata,
103 pub(crate) hud: Hud,
104 key_state: KeyState,
105 inputs: comp::ControllerInputs,
106 inputs_state: HashSet<GameInput>,
107 selected_block: Block,
108 walk_forward_dir: Vec2<f32>,
109 walk_right_dir: Vec2<f32>,
110 free_look: bool,
111 freecam_pos: Vec3<f32>,
112 auto_walk: bool,
113 walking_speed: bool,
114 camera_clamp: bool,
115 zoom_lock: bool,
116 is_aiming: bool,
117 pub(crate) target_entity: Option<specs::Entity>,
118 pub(crate) selected_entity: Option<(specs::Entity, std::time::Instant)>,
119 pub(crate) viewpoint_entity: Option<specs::Entity>,
120 interactables: interactable::Interactables,
121 #[cfg(not(target_os = "macos"))]
122 mumble_link: SharedLink,
123 hitboxes: HashMap<specs::Entity, DebugShapeId>,
124 lines: PlayerDebugLines,
125 tracks: HashMap<Vec2<i32>, Vec<DebugShapeId>>,
126 gizmos: Vec<(DebugShapeId, common::resources::Time, bool)>,
127}
128
129impl SessionState {
131 pub fn new(
133 global_state: &mut GlobalState,
134 metadata: UpdateCharacterMetadata,
135 client: Rc<RefCell<Client>>,
136 persisted_state: Rc<RefCell<PersistedHudState>>,
137 ) -> Self {
138 let mut scene = Scene::new(
141 global_state.window.renderer_mut(),
142 &mut global_state.lazy_init,
143 &client.borrow(),
144 &global_state.settings,
145 );
146 scene
147 .camera_mut()
148 .set_fov_deg(global_state.settings.graphics.fov);
149 client
150 .borrow_mut()
151 .set_lod_distance(global_state.settings.graphics.lod_distance);
152 #[cfg(not(target_os = "macos"))]
153 let mut mumble_link = SharedLink::new("veloren", "veloren-voxygen");
154 {
155 let mut client = client.borrow_mut();
156 client.request_player_physics(global_state.settings.networking.player_physics_behavior);
157 client.request_lossy_terrain_compression(
158 global_state.settings.networking.lossy_terrain_compression,
159 );
160 #[cfg(not(target_os = "macos"))]
161 if let Some(uid) = client.uid() {
162 let identiy = if let Some(info) = client.player_list().get(&uid) {
163 format!("{}-{}", info.player_alias, uid)
164 } else {
165 format!("unknown-{}", uid)
166 };
167 mumble_link.set_identity(&identiy);
168 }
170 }
171 let hud = Hud::new(global_state, persisted_state, &client.borrow());
172 let walk_forward_dir = scene.camera().forward_xy();
173 let walk_right_dir = scene.camera().right_xy();
174
175 Self {
176 scene,
177 client,
178 key_state: KeyState::default(),
179 inputs: comp::ControllerInputs::default(),
180 inputs_state: HashSet::new(),
181 hud,
182 selected_block: Block::new(BlockKind::Misc, Rgb::broadcast(255)),
183 walk_forward_dir,
184 walk_right_dir,
185 free_look: false,
186 freecam_pos: Vec3::zero(),
187 auto_walk: false,
188 walking_speed: false,
189 camera_clamp: false,
190 zoom_lock: false,
191 is_aiming: false,
192 target_entity: None,
193 selected_entity: None,
194 viewpoint_entity: None,
195 interactables: Default::default(),
196 #[cfg(not(target_os = "macos"))]
197 mumble_link,
198 hitboxes: HashMap::new(),
199 metadata,
200 tracks: HashMap::new(),
201 lines: Default::default(),
202 gizmos: Vec::new(),
203 }
204 }
205
206 fn stop_auto_walk(&mut self) {
207 self.auto_walk = false;
208 self.hud.auto_walk(false);
209 self.key_state.auto_walk = false;
210 }
211
212 fn maybe_auto_zoom_lock(
215 &mut self,
216 zoom_lock_enabled: bool,
217 zoom_lock_behavior: AutoPressBehavior,
218 ) {
219 if let AutoPressBehavior::Auto = zoom_lock_behavior {
220 self.zoom_lock = zoom_lock_enabled && self.should_auto_zoom_lock();
223 } else {
224 self.zoom_lock = zoom_lock_enabled;
227 }
228 }
229
230 fn viewpoint_entity(&self) -> (specs::Entity, bool) {
233 self.viewpoint_entity
234 .map(|e| (e, false))
235 .unwrap_or_else(|| (self.client.borrow().entity(), true))
236 }
237
238 fn controlling_char(&self) -> bool {
239 self.viewpoint_entity.is_none()
240 && self
241 .client
242 .borrow()
243 .presence()
244 .is_some_and(|p| p.controlling_char())
245 }
246
247 fn tick(
249 &mut self,
250 dt: Duration,
251 global_state: &mut GlobalState,
252 outcomes: &mut Vec<Outcome>,
253 ) -> Result<TickAction, Error> {
254 span!(_guard, "tick", "Session::tick");
255
256 let mut client = self.client.borrow_mut();
257 self.scene.maintain_debug_hitboxes(
258 &client,
259 &global_state.settings,
260 &mut self.hitboxes,
261 &mut self.tracks,
262 &mut self.gizmos,
263 );
264 self.scene.maintain_debug_vectors(&client, &mut self.lines);
265 let pos = client.position().unwrap_or_default();
266
267 #[cfg(not(target_os = "macos"))]
268 {
269 let ori = client
271 .state()
272 .read_storage::<comp::Ori>()
273 .get(client.entity())
274 .map_or_else(comp::Ori::default, |o| *o);
275 let front = ori.look_dir().to_vec();
276 let top = ori.up().to_vec();
277 let player_pos = mumble_link::Position {
279 position: [pos.x, pos.z, pos.y],
280 front: [front.x, front.z, front.y],
281 top: [top.x, top.z, top.y],
282 };
283 self.mumble_link.update(player_pos, player_pos);
284 }
285
286 for event in client.tick(self.inputs.clone(), dt)? {
287 match event {
288 client::Event::Chat(m) => {
289 self.hud.new_message(m);
290 },
291 client::Event::GroupInventoryUpdate(item, uid) => {
292 self.hud.new_loot_message(LootMessage {
293 amount: item.amount(),
294 item,
295 taken_by: uid,
296 });
297 },
298 client::Event::InviteComplete {
299 target,
300 answer,
301 kind,
302 } => {
303 let target_name = match client.player_list().get(&target) {
304 Some(info) => info.player_alias.clone(),
305 None => match client.state().ecs().entity_from_uid(target) {
306 Some(entity) => {
307 let stats = client.state().read_storage::<Stats>();
308 stats
309 .get(entity)
310 .map_or(format!("<entity {}>", target), |e| {
311 global_state.i18n.read().get_content(&e.name)
312 })
313 },
314 None => format!("<uid {}>", target),
315 },
316 };
317
318 let msg_key = match (kind, answer) {
319 (InviteKind::Group, InviteAnswer::Accepted) => "hud-group-invite-accepted",
320 (InviteKind::Group, InviteAnswer::Declined) => "hud-group-invite-declined",
321 (InviteKind::Group, InviteAnswer::TimedOut) => "hud-group-invite-timed_out",
322 (InviteKind::Trade, InviteAnswer::Accepted) => "hud-trade-invite-accepted",
323 (InviteKind::Trade, InviteAnswer::Declined) => "hud-trade-invite-declined",
324 (InviteKind::Trade, InviteAnswer::TimedOut) => "hud-trade-invite-timed_out",
325 };
326
327 let msg = global_state
328 .i18n
329 .read()
330 .get_msg_ctx(msg_key, &i18n::fluent_args! { "target" => target_name });
331
332 self.hud.new_message(ChatType::Meta.into_plain_msg(msg));
333 },
334 client::Event::TradeComplete { result, trade: _ } => {
335 self.hud.clear_cursor();
336 self.hud
337 .new_message(ChatType::Meta.into_msg(Content::localized(match result {
338 TradeResult::Completed => "hud-trade-result-completed",
339 TradeResult::Declined => "hud-trade-result-declined",
340 TradeResult::NotEnoughSpace => "hud-trade-result-nospace",
341 })));
342 },
343 client::Event::InventoryUpdated(inv_events) => {
344 let sfx_triggers = self.scene.sfx_mgr.triggers.read();
345
346 for inv_event in inv_events {
347 let sfx_trigger_item =
348 sfx_triggers.0.get_key_value(&SfxEvent::from(&inv_event));
349
350 global_state.audio.emit_ui_sfx(sfx_trigger_item, None, None);
351
352 match inv_event {
353 InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
354 self.hud.add_failed_block_pickup(
355 VolumePos::terrain(pos),
357 HudCollectFailedReason::from_server_reason(
358 &reason,
359 client.state().ecs(),
360 ),
361 );
362 },
363 InventoryUpdateEvent::EntityCollectFailed {
364 entity: uid,
365 reason,
366 } => {
367 if let Some(entity) = client.state().ecs().entity_from_uid(uid) {
368 self.hud.add_failed_entity_pickup(
369 entity,
370 HudCollectFailedReason::from_server_reason(
371 &reason,
372 client.state().ecs(),
373 ),
374 );
375 }
376 },
377 InventoryUpdateEvent::Collected(item) => {
378 global_state.profile.tutorial.event_collect();
379 self.hud.new_loot_message(LootMessage {
380 amount: item.amount(),
381 item,
382 taken_by: client.uid().expect("Client doesn't have a Uid!!!"),
383 });
384 },
385 _ => {},
386 };
387 }
388 },
389 client::Event::Dialogue(sender_uid, dialogue) => {
390 if let Some(sender) = client.state().ecs().entity_from_uid(sender_uid) {
391 self.hud.dialogue(sender, pos, dialogue, global_state);
392 }
393 },
394 client::Event::Disconnect => return Ok(TickAction::Disconnect),
395 client::Event::DisconnectionNotification(time) => {
396 self.hud
397 .new_message(ChatType::CommandError.into_msg(match time {
398 0 => Content::localized("hud-chat-goodbye"),
399 _ => Content::localized_with_args("hud-chat-connection_lost", [(
400 "time", time,
401 )]),
402 }));
403 },
404 client::Event::Notification(n) => {
405 global_state.profile.tutorial.event_notification(&n);
406 self.hud.new_notification(n);
407 },
408 client::Event::SetViewDistance(_vd) => {},
409 client::Event::Outcome(outcome) => {
410 global_state
411 .profile
412 .tutorial
413 .event_outcome(&client, &outcome);
414 outcomes.push(outcome);
415 },
416 client::Event::CharacterCreated(_) => {},
417 client::Event::CharacterEdited(_) => {},
418 client::Event::CharacterError(_) => {},
419 client::Event::CharacterJoined(_) => {
420 self.scene.music_mgr.reset_track(&mut global_state.audio);
421 },
422 client::Event::MapMarker(event) => {
423 self.hud
424 .persisted_state
425 .borrow_mut()
426 .location_markers
427 .update(event);
428 },
429 client::Event::StartSpectate(spawn_point) => {
430 let server_name = &client.server_info().name;
431 let spawn_point = global_state
432 .profile
433 .get_spectate_position(server_name)
434 .unwrap_or(spawn_point);
435
436 client
437 .state()
438 .ecs()
439 .write_storage()
440 .insert(client.entity(), Pos(spawn_point))
441 .expect("This shouldn't exist");
442
443 self.scene.camera_mut().force_focus_pos(spawn_point);
444 },
445 client::Event::SpectatePosition(pos) => {
446 self.scene.camera_mut().force_focus_pos(pos);
447 },
448 client::Event::PluginDataReceived(data) => {
449 tracing::warn!("Received plugin data at wrong time {}", data.len());
450 },
451 client::Event::Gizmos(gizmos) => {
452 self.gizmos.retain(|gizmos| {
453 let keep = gizmos.2;
454 if !keep {
455 self.scene.debug.remove_shape(gizmos.0);
456 }
457 keep
458 });
459 for gizmos in gizmos {
460 let mut add_shape = |shape, pos: Vec3<f32>| {
461 let id = self.scene.debug.add_shape(shape);
462 self.scene.debug.set_context(
463 id,
464 pos.with_w(0.0).into_array(),
465 gizmos.color.map(|c| c as f32 / 255.0).into_array(),
466 [0.0, 0.0, 0.0, 1.0],
467 );
468 self.gizmos.push((
469 id,
470 gizmos.end_time.unwrap_or(common::resources::Time(
471 client.state().get_time() + 1.0,
472 )),
473 gizmos.end_time.is_some(),
474 ));
475 };
476 match gizmos.shape {
477 comp::gizmos::Shape::Sphere(sphere) => {
478 add_shape(
479 crate::scene::DebugShape::CapsulePrism {
480 p0: Vec2::zero(),
481 p1: Vec2::zero(),
482 radius: sphere.radius,
483 height: sphere.radius * 2.0,
484 },
485 sphere.center,
486 );
487 },
488 comp::gizmos::Shape::LineStrip(lines) => {
489 for (a, b) in lines.into_iter().tuple_windows::<(_, _)>() {
490 add_shape(
491 crate::scene::DebugShape::Line([Vec3::zero(), b - a], 0.1),
492 a,
493 );
494 }
495 },
496 }
497 }
498 },
499 }
500 }
501
502 Ok(TickAction::Continue)
503 }
504
505 pub fn cleanup(&mut self) { self.client.borrow_mut().cleanup(); }
507
508 fn should_auto_zoom_lock(&self) -> bool {
509 let inputs_state = &self.inputs_state;
510 for input in inputs_state {
511 match input {
512 GameInput::Primary
513 | GameInput::Secondary
514 | GameInput::Block
515 | GameInput::MoveForward
516 | GameInput::MoveLeft
517 | GameInput::MoveRight
518 | GameInput::MoveBack
519 | GameInput::Jump
520 | GameInput::WallJump
521 | GameInput::Roll
522 | GameInput::Sneak
523 | GameInput::AutoWalk
524 | GameInput::SwimUp
525 | GameInput::SwimDown
526 | GameInput::SwapLoadout
527 | GameInput::ToggleWield
528 | GameInput::Slot1
529 | GameInput::Slot2
530 | GameInput::Slot3
531 | GameInput::Slot4
532 | GameInput::Slot5
533 | GameInput::Slot6
534 | GameInput::Slot7
535 | GameInput::Slot8
536 | GameInput::Slot9
537 | GameInput::Slot10
538 | GameInput::CurrentSlot
539 | GameInput::SpectateViewpoint
540 | GameInput::SpectateSpeedBoost => return true,
541 _ => (),
542 }
543 }
544 false
545 }
546}
547
548impl PlayState for SessionState {
549 fn enter(&mut self, global_state: &mut GlobalState, _: Direction) {
550 global_state.window.grab_cursor(true);
552
553 self.client.borrow_mut().clear_terrain();
554
555 if global_state.settings.send_logon_commands {
557 for cmd in &global_state.settings.logon_commands {
558 self.client.borrow_mut().send_chat(cmd.to_string());
559 }
560 }
561
562 #[cfg(feature = "discord")]
563 {
564 #[cfg(feature = "singleplayer")]
566 let singleplayer = global_state.singleplayer.is_running();
567 #[cfg(not(feature = "singleplayer"))]
568 let singleplayer = false;
569
570 if singleplayer {
571 global_state.discord.join_singleplayer();
572 } else {
573 global_state
574 .discord
575 .join_server(self.client.borrow().server_info().name.clone());
576 }
577 }
578 }
579
580 fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
581 span!(_guard, "tick", "<Session as PlayState>::tick");
582 let (client_presence, client_type, client_registered) = {
585 let client = self.client.borrow();
586 (
587 client.presence(),
588 *client.client_type(),
589 client.registered(),
590 )
591 };
592
593 if let Some(presence) = client_presence {
594 let camera = self.scene.camera_mut();
595
596 if self.camera_clamp {
598 let mut cam_dir = camera.get_orientation();
599 let cam_dir_clamp =
600 (global_state.settings.gameplay.camera_clamp_angle as f32).to_radians();
601 cam_dir.y = cam_dir.y.clamp(-cam_dir_clamp, cam_dir_clamp);
602 camera.set_orientation(cam_dir);
603 }
604
605 let client = self.client.borrow();
606 let player_entity = client.entity();
607
608 let dt = global_state.clock.get_stable_dt().as_secs_f32();
609
610 #[cfg(feature = "discord")]
611 if global_state.discord.is_active()
612 && let Some(chunk) = client.current_chunk()
613 && let Some(location_name) = chunk.meta().name()
614 {
615 global_state
616 .discord
617 .update_location(location_name, client.current_site());
618 }
619
620 if global_state.settings.gameplay.bow_zoom {
621 let mut fov_scaling = 1.0;
622 if let Some(comp::CharacterState::ChargedRanged(cr)) = client
623 .state()
624 .read_storage::<comp::CharacterState>()
625 .get(player_entity)
626 && cr.charge_frac() > 0.5
627 {
628 fov_scaling -= 3.0 * cr.charge_frac() / 5.0;
629 }
630 camera.set_fixate(fov_scaling);
631 } else {
632 camera.set_fixate(1.0);
633 }
634
635 camera.compute_dependents(&client.state().terrain());
637 let camera::Dependents {
638 cam_pos, cam_dir, ..
639 } = self.scene.camera().dependents();
640 let focus_pos = self.scene.camera().get_focus_pos();
641 let focus_off = focus_pos.map(|e| e.trunc());
642 let cam_pos = cam_pos + focus_off;
643
644 let (is_aiming, aim_dir_offset) = {
645 let is_aiming = client
646 .state()
647 .read_storage::<comp::CharacterState>()
648 .get(player_entity)
649 .map(|cs| cs.is_wield())
650 .unwrap_or(false);
651
652 (
653 is_aiming,
654 if is_aiming && self.scene.camera().get_mode() == CameraMode::ThirdPerson {
655 Vec3::unit_z() * 0.025
656 } else {
657 Vec3::zero()
658 },
659 )
660 };
661 self.is_aiming = is_aiming;
662
663 let can_build = client
664 .state()
665 .read_storage::<comp::CanBuild>()
666 .get(player_entity)
667 .map_or_else(|| false, |cb| cb.enabled);
668
669 let active_mine_tool: Option<ToolKind> = if client.is_wielding() == Some(true) {
670 client
671 .inventories()
672 .get(player_entity)
673 .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
674 .and_then(|item| item.tool_info())
675 .filter(|tool_kind| matches!(tool_kind, ToolKind::Pick | ToolKind::Shovel))
676 } else {
677 None
678 };
679
680 let (build_target, collect_target, entity_target, mine_target, terrain_target) =
682 targets_under_cursor(
683 &client,
684 cam_pos,
685 cam_dir,
686 can_build,
687 active_mine_tool,
688 self.viewpoint_entity().0,
689 );
690
691 match get_interactables(
692 &client,
693 collect_target,
694 entity_target,
695 mine_target,
696 terrain_target,
697 &self.scene,
698 ) {
699 Ok(input_map) => {
700 for (_, inter) in input_map.values() {
701 global_state.profile.tutorial.event_find_interactable(inter);
702 }
703
704 let entities = input_map
705 .values()
706 .filter_map(|(_, interactable)| {
707 if let Interactable::Entity { entity, .. } = interactable {
708 Some(*entity)
709 } else {
710 None
711 }
712 })
713 .collect::<HashSet<_>>();
714 self.interactables = interactable::Interactables {
715 input_map,
716 entities,
717 };
718 },
719 Err(error) => {
720 tracing::trace!(?error, "Getting interactables failed");
721 self.interactables = Default::default()
722 },
723 }
724
725 drop(client);
726
727 self.maybe_auto_zoom_lock(
728 global_state.settings.gameplay.zoom_lock,
729 global_state.settings.gameplay.zoom_lock_behavior,
730 );
731
732 if presence == PresenceKind::Spectator {
733 let mut client = self.client.borrow_mut();
734 if client.spectate_position(cam_pos) {
735 let server_name = &client.server_info().name;
736 global_state.profile.set_spectate_position(
737 server_name,
738 Some(self.scene.camera().get_focus_pos()),
739 );
740 }
741 }
742
743 let nearest_block_dist = find_shortest_distance(&[
745 mine_target
746 .filter(|_| active_mine_tool.is_some())
747 .map(|t| t.distance),
748 build_target.filter(|_| can_build).map(|t| t.distance),
749 ]);
750 let nearest_scene_dist = find_shortest_distance(&[
752 nearest_block_dist,
753 collect_target
754 .filter(|_| active_mine_tool.is_none())
755 .map(|t| t.distance),
756 ]);
757 self.inputs.break_block_pos = if let Some(mt) = mine_target
759 .filter(|mt| active_mine_tool.is_some() && nearest_scene_dist == Some(mt.distance))
760 {
761 self.scene.set_select_pos(Some(mt.position_int()));
762 Some(mt.position)
763 } else if let Some(bt) =
764 build_target.filter(|bt| can_build && nearest_scene_dist == Some(bt.distance))
765 {
766 self.scene.set_select_pos(Some(bt.position_int()));
767 None
768 } else if let Some(ct) =
769 collect_target.filter(|ct| nearest_scene_dist == Some(ct.distance))
770 {
771 self.scene.set_select_pos(Some(ct.position_int()));
772 None
773 } else {
774 self.scene.set_select_pos(None);
775 None
776 };
777
778 let default_select_pos = terrain_target.map(|tt| tt.position);
780
781 self.target_entity = entity_target.map(|t| t.kind.0);
783
784 let controlling_char = self.controlling_char();
785
786 for event in events {
788 {
790 let client = self.client.borrow();
791 let inventories = client.inventories();
792 let inventory = inventories.get(client.entity());
793 if self
794 .hud
795 .handle_event(event.clone(), global_state, inventory)
796 {
797 continue;
798 }
799 }
800 match event {
801 Event::Close => {
802 return PlayStateResult::Shutdown;
803 },
804 Event::InputUpdate(input, state)
805 if state != self.inputs_state.contains(&input) =>
806 {
807 if !self.inputs_state.insert(input) {
808 self.inputs_state.remove(&input);
809 }
810 match input {
811 GameInput::Primary => {
812 self.walking_speed = false;
813 let mut client = self.client.borrow_mut();
814 if let Some(build_target) = build_target.filter(|bt| {
818 state && can_build && nearest_block_dist == Some(bt.distance)
819 }) {
820 client.remove_block(build_target.position_int());
821 } else {
822 client.handle_input(
823 InputKind::Primary,
824 state,
825 default_select_pos,
826 self.target_entity,
827 );
828 }
829 },
830 GameInput::Secondary => {
831 self.walking_speed = false;
832 let mut client = self.client.borrow_mut();
833 if let Some(build_target) = build_target.filter(|bt| {
834 state && can_build && nearest_block_dist == Some(bt.distance)
835 }) {
836 let selected_pos = build_target.kind.0;
837 client.place_block(
838 selected_pos.map(|p| p.floor() as i32),
839 self.selected_block,
840 );
841 } else {
842 client.handle_input(
843 InputKind::Secondary,
844 state,
845 default_select_pos,
846 self.target_entity,
847 );
848 }
849 },
850 GameInput::Block => {
851 self.walking_speed = false;
852 self.client.borrow_mut().handle_input(
853 InputKind::Block,
854 state,
855 None,
856 self.target_entity,
857 );
858 },
859 GameInput::Roll => {
860 self.walking_speed = false;
861 let mut client = self.client.borrow_mut();
862 if can_build {
863 if state
864 && let Some(block) = build_target.and_then(|bt| {
865 client
866 .state()
867 .terrain()
868 .get(bt.position_int())
869 .ok()
870 .copied()
871 })
872 {
873 self.selected_block = block;
874 }
875 } else if controlling_char {
876 global_state.profile.tutorial.event_roll();
877 client.handle_input(
878 InputKind::Roll,
879 state,
880 None,
881 self.target_entity,
882 );
883 }
884 },
885 GameInput::GiveUp => {
886 self.key_state.give_up = state.then_some(0.0).filter(|_| {
887 let client = self.client.borrow();
888 comp::is_downed(
889 client.current().as_ref(),
890 client.current().as_ref(),
891 )
892 });
893 },
894 GameInput::Respawn => {
895 self.walking_speed = false;
896 self.stop_auto_walk();
897 if state && self.client.borrow_mut().respawn() {
898 global_state.profile.tutorial.event_respawn();
899 }
900 },
901 GameInput::Jump => {
902 self.walking_speed = false;
903 global_state.profile.tutorial.event_jump();
904 self.client.borrow_mut().handle_input(
905 InputKind::Jump,
906 state,
907 None,
908 self.target_entity,
909 );
910 },
911 GameInput::WallJump => {
912 self.walking_speed = false;
913 self.client.borrow_mut().handle_input(
914 InputKind::WallJump,
915 state,
916 None,
917 self.target_entity,
918 );
919 },
920 GameInput::SwimUp => {
921 self.key_state.swim_up = state;
922 },
923 GameInput::SwimDown => {
924 self.key_state.swim_down = state;
925 },
926 GameInput::Sit => {
927 if state && controlling_char {
928 self.stop_auto_walk();
929 self.client.borrow_mut().toggle_sit();
930 }
931 },
932 GameInput::Crawl => {
933 if state && controlling_char {
934 self.stop_auto_walk();
935 self.client.borrow_mut().toggle_crawl();
936 }
937 },
938 GameInput::Dance => {
939 if state && controlling_char {
940 self.stop_auto_walk();
941 self.client.borrow_mut().toggle_dance();
942 }
943 },
944 GameInput::Greet => {
945 if state {
946 self.client.borrow_mut().utter(UtteranceKind::Greeting);
947 }
948 },
949 GameInput::Sneak => {
950 let is_trading = self.client.borrow().is_trading();
951 if state && !is_trading && controlling_char {
952 self.stop_auto_walk();
953 self.client.borrow_mut().toggle_sneak();
954 }
955 },
956 GameInput::CancelClimb => {
957 if state && controlling_char {
958 self.client.borrow_mut().cancel_climb();
959 }
960 },
961 GameInput::MoveForward => {
962 if state && global_state.settings.gameplay.stop_auto_walk_on_input {
963 self.stop_auto_walk();
964 }
965 self.key_state.up = state
966 },
967 GameInput::MoveBack => {
968 if state && global_state.settings.gameplay.stop_auto_walk_on_input {
969 self.stop_auto_walk();
970 }
971 self.key_state.down = state
972 },
973 GameInput::MoveLeft => {
974 if state && global_state.settings.gameplay.stop_auto_walk_on_input {
975 self.stop_auto_walk();
976 }
977 self.key_state.left = state
978 },
979 GameInput::MoveRight => {
980 if state && global_state.settings.gameplay.stop_auto_walk_on_input {
981 self.stop_auto_walk();
982 }
983 self.key_state.right = state
984 },
985 GameInput::Glide => {
986 self.walking_speed = false;
987 let is_trading = self.client.borrow().is_trading();
988 if state && !is_trading && controlling_char {
989 if global_state.settings.gameplay.stop_auto_walk_on_input {
990 self.stop_auto_walk();
991 }
992 self.client.borrow_mut().toggle_glide();
993 global_state.profile.tutorial.event_open_glider();
994 }
995 },
996 GameInput::Fly => {
997 self.key_state.fly ^= state;
1003 self.client.borrow_mut().handle_input(
1004 InputKind::Fly,
1005 self.key_state.fly,
1006 None,
1007 self.target_entity,
1008 );
1009 },
1010 GameInput::ToggleWield => {
1011 if state && controlling_char {
1012 let mut client = self.client.borrow_mut();
1013 if client.is_wielding().is_some_and(|b| !b) {
1014 self.walking_speed = false;
1015 }
1016 client.toggle_wield();
1017 }
1018 },
1019 GameInput::SwapLoadout => {
1020 if state && controlling_char {
1021 self.client.borrow_mut().swap_loadout();
1022 }
1023 },
1024 GameInput::ToggleLantern if state && controlling_char => {
1025 let mut client = self.client.borrow_mut();
1026 if client.is_lantern_enabled() {
1027 client.disable_lantern();
1028 } else {
1029 global_state.profile.tutorial.event_lantern();
1030 client.enable_lantern();
1031 }
1032 },
1033 GameInput::Mount if state && controlling_char => {
1034 let mut client = self.client.borrow_mut();
1035 if client.is_riding() {
1036 client.unmount();
1037 } else if let Some((_, interactable)) =
1038 self.interactables.input_map.get(&GameInput::Mount)
1039 {
1040 match interactable {
1041 Interactable::Block { volume_pos, .. } => {
1042 client.mount_volume(*volume_pos)
1043 },
1044 Interactable::Entity { entity, .. } => {
1045 client.mount(*entity)
1046 },
1047 }
1048 }
1049 },
1050 GameInput::StayFollow if state => {
1051 let mut client = self.client.borrow_mut();
1052 let player_pos = client
1053 .state()
1054 .read_storage::<Pos>()
1055 .get(client.entity())
1056 .copied();
1057
1058 let mut close_pet = None;
1059 if let Some(player_pos) = player_pos {
1060 let positions = client.state().read_storage::<Pos>();
1061 close_pet = client.state().ecs().read_resource::<CachedSpatialGrid>().0
1062 .in_circle_aabr(player_pos.0.xy(), MAX_MOUNT_RANGE)
1063 .filter(|e|
1064 *e != client.entity()
1065 )
1066 .filter(|e|
1067 matches!(client.state().ecs().read_storage::<comp::Alignment>().get(*e),
1068 Some(comp::Alignment::Owned(owner)) if Some(*owner) == client.uid())
1069 )
1070 .filter(|e|
1071 client.state().ecs().read_storage::<Is<Mount>>().get(*e).is_none()
1072 )
1073 .min_by_key(|e| {
1074 OrderedFloat(positions
1075 .get(*e)
1076 .map_or(MAX_MOUNT_RANGE * MAX_MOUNT_RANGE, |x| {
1077 player_pos.0.distance_squared(x.0)
1078 }
1079 ))
1080 });
1081 }
1082 if let Some(pet_entity) = close_pet
1083 && client
1084 .state()
1085 .read_storage::<Is<Mount>>()
1086 .get(pet_entity)
1087 .is_none()
1088 {
1089 let is_staying = client
1090 .state()
1091 .read_storage::<CharacterActivity>()
1092 .get(pet_entity)
1093 .is_some_and(|activity| activity.is_pet_staying);
1094 client.set_pet_stay(pet_entity, !is_staying);
1095 }
1096 },
1097 GameInput::Interact => {
1098 if state {
1099 let mut client = self.client.borrow_mut();
1100 if let Some((_, interactable)) =
1101 self.interactables.input_map.get(&GameInput::Interact)
1102 {
1103 match interactable {
1104 Interactable::Block {
1105 volume_pos,
1106 block,
1107 interaction,
1108 ..
1109 } => {
1110 match interaction {
1111 BlockInteraction::Collect { .. }
1112 | BlockInteraction::Unlock { .. } => {
1113 if block.is_directly_collectible() {
1114 match volume_pos.kind {
1115 common::mounting::Volume::Terrain => {
1116 client.collect_block(volume_pos.pos);
1117 }
1118 common::mounting::Volume::Entity(_) => {
1119 },
1121 }
1122 }
1123 },
1124 BlockInteraction::Craft(tab) => {
1125 self.hud.show.open_crafting_tab(
1126 *tab,
1127 block
1128 .get_sprite()
1129 .map(|s| (*volume_pos, s)),
1130 )
1131 },
1132 BlockInteraction::Mine(_)
1133 | BlockInteraction::Mount => {},
1134 BlockInteraction::Read(content) => {
1135 match volume_pos.kind {
1136 common::mounting::Volume::Terrain => {
1137 self.hud.show_content_bubble(
1138 volume_pos.pos.as_()
1139 + Vec3::new(
1140 0.5,
1141 0.5,
1142 block.solid_height()
1143 * 0.75,
1144 ),
1145 content.clone(),
1146 )
1147 },
1148 common::mounting::Volume::Entity(_) => {
1151 },
1152 }
1153 },
1154 BlockInteraction::LightToggle(enable) => {
1155 client.toggle_sprite_light(
1156 *volume_pos,
1157 *enable,
1158 );
1159 },
1160 }
1161 },
1162 Interactable::Entity {
1163 entity,
1164 interaction,
1165 ..
1166 } => {
1167 match interaction {
1169 EntityInteraction::HelpDowned => {
1170 client.help_downed(*entity)
1171 },
1172 EntityInteraction::PickupItem => {
1173 client.pick_up(*entity)
1174 },
1175 EntityInteraction::ActivatePortal => {
1176 client.activate_portal(*entity)
1177 },
1178 EntityInteraction::Pet => {
1179 client.do_pet(*entity)
1180 },
1181 EntityInteraction::Talk => {
1182 client.npc_interact(*entity)
1183 },
1184 EntityInteraction::CampfireSit
1185 | EntityInteraction::Trade
1186 | EntityInteraction::StayFollow
1187 | EntityInteraction::Mount => {},
1188 }
1189 },
1190 }
1191 }
1192 }
1193 },
1194 GameInput::Trade => {
1195 if state
1196 && controlling_char
1197 && let Some((_, Interactable::Entity { entity, .. })) =
1198 self.interactables.input_map.get(&GameInput::Trade)
1199 {
1200 let mut client = self.client.borrow_mut();
1201 if let Some(uid) = client.state().ecs().uid_from_entity(*entity)
1202 {
1203 let name = client
1204 .player_list()
1205 .get(&uid)
1206 .map(|info| info.player_alias.clone())
1207 .unwrap_or_else(|| {
1208 let stats = client.state().read_storage::<Stats>();
1209 stats.get(*entity).map_or(
1210 format!("<entity {:?}>", uid),
1211 |e| {
1212 global_state
1213 .i18n
1214 .read()
1215 .get_content(&e.name)
1216 },
1217 )
1218 });
1219
1220 self.hud.new_message(ChatType::Meta.into_msg(
1221 Content::localized_with_args(
1222 "hud-trade-invite_sent",
1223 [("playername", name)],
1224 ),
1225 ));
1226
1227 client.send_invite(uid, InviteKind::Trade)
1228 };
1229 }
1230 },
1231 GameInput::FreeLook => {
1232 let hud = &mut self.hud;
1233 global_state.settings.gameplay.free_look_behavior.update(
1234 state,
1235 &mut self.free_look,
1236 |b| hud.free_look(b),
1237 );
1238 let camera = self.scene.camera_mut();
1239 let ori = camera.get_orientation();
1240 if self.free_look {
1241 self.freecam_pos = ori;
1242 } else {
1243 camera.set_orientation_instant(self.freecam_pos);
1244 }
1245 },
1246 GameInput::AutoWalk => {
1247 let hud = &mut self.hud;
1248 global_state.settings.gameplay.auto_walk_behavior.update(
1249 state,
1250 &mut self.auto_walk,
1251 |b| hud.auto_walk(b),
1252 );
1253
1254 self.key_state.auto_walk =
1255 self.auto_walk && !self.client.borrow().is_gliding();
1256 },
1257 GameInput::ZoomIn => {
1258 if state {
1259 if self.zoom_lock {
1260 self.hud.zoom_lock_reminder();
1261 } else {
1262 self.scene.handle_input_event(
1263 Event::Zoom(-30.0),
1264 &self.client.borrow(),
1265 );
1266 }
1267 }
1268 },
1269 GameInput::ZoomOut => {
1270 if state {
1271 if self.zoom_lock {
1272 self.hud.zoom_lock_reminder();
1273 } else {
1274 self.scene.handle_input_event(
1275 Event::Zoom(30.0),
1276 &self.client.borrow(),
1277 );
1278 }
1279 }
1280 },
1281 GameInput::ZoomLock => {
1282 if state {
1283 global_state.settings.gameplay.zoom_lock ^= true;
1284
1285 self.hud
1286 .zoom_lock_toggle(global_state.settings.gameplay.zoom_lock);
1287 }
1288 },
1289 GameInput::CameraClamp => {
1290 let hud = &mut self.hud;
1291 global_state.settings.gameplay.camera_clamp_behavior.update(
1292 state,
1293 &mut self.camera_clamp,
1294 |b| hud.camera_clamp(b),
1295 );
1296 },
1297 GameInput::CycleCamera if state => {
1298 let camera = self.scene.camera_mut();
1305 let client = self.client.borrow();
1306 camera.next_mode(
1307 client.is_moderator(),
1308 (client.presence() != Some(PresenceKind::Spectator))
1309 || self.viewpoint_entity.is_some(),
1310 );
1311 },
1312 GameInput::Select => {
1313 if !state {
1314 self.selected_entity =
1315 self.target_entity.map(|e| (e, std::time::Instant::now()));
1316 }
1317 },
1318 GameInput::AcceptGroupInvite if state => {
1319 let mut client = self.client.borrow_mut();
1320 if client.invite().is_some() {
1321 client.accept_invite();
1322 }
1323 },
1324 GameInput::DeclineGroupInvite if state => {
1325 let mut client = self.client.borrow_mut();
1326 if client.invite().is_some() {
1327 client.decline_invite();
1328 }
1329 },
1330 GameInput::SpectateViewpoint if state => {
1331 let mut client = self.client.borrow_mut();
1332 if self.viewpoint_entity.is_some() {
1333 client.stop_spectate_entity();
1334 self.viewpoint_entity = None;
1335 self.scene.camera_mut().set_mode(CameraMode::Freefly);
1336 let mut ori = self.scene.camera().get_orientation();
1337 ori.z = 0.0;
1340 self.scene.camera_mut().set_orientation(ori);
1341 } else if let Some(target_entity) = entity_target
1342 && self.scene.camera().get_mode() == CameraMode::Freefly
1343 {
1344 client.start_spectate_entity(target_entity.kind.0);
1347
1348 self.viewpoint_entity = Some(target_entity.kind.0);
1349 self.scene.camera_mut().set_mode(CameraMode::FirstPerson);
1350 }
1351 },
1352 GameInput::ToggleWalk if state => {
1353 global_state
1354 .settings
1355 .gameplay
1356 .walking_speed_behavior
1357 .update(state, &mut self.walking_speed, |_| {});
1358 },
1359 _ => {},
1360 }
1361 },
1362 Event::AnalogGameInput(input) => match input {
1363 AnalogGameInput::MovementX(v) => {
1364 self.key_state.analog_matrix.x = v;
1365 },
1366 AnalogGameInput::MovementY(v) => {
1367 self.key_state.analog_matrix.y = v;
1368 },
1369 other => {
1370 self.scene.handle_input_event(
1371 Event::AnalogGameInput(other),
1372 &self.client.borrow(),
1373 );
1374 },
1375 },
1376
1377 Event::ScreenshotMessage(screenshot_msg) => self
1379 .hud
1380 .new_message(ChatType::CommandInfo.into_plain_msg(screenshot_msg)),
1381
1382 Event::Zoom(delta) if self.zoom_lock => {
1383 if delta.abs() > ZOOM_LOCK_SCROLL_DELTA_INTENT {
1385 self.hud.zoom_lock_reminder();
1386 }
1387 },
1388
1389 event => {
1391 if let Event::Zoom(delta) = &event {
1392 global_state.profile.tutorial.event_zoom(*delta);
1393 }
1394
1395 self.scene.handle_input_event(event, &self.client.borrow());
1396 }, }
1398 }
1399
1400 if let Some(tgt) = self.hud.current_dialogue() {
1402 let mut client = self.client.borrow_mut();
1403 client.do_talk(Some(tgt));
1404 if matches!(
1406 self.scene.camera().get_mode(),
1407 camera::CameraMode::FirstPerson
1408 ) && let Some(activity) = client
1409 .state()
1410 .read_storage::<CharacterActivity>()
1411 .get(client.entity())
1412 && let Some(dir) = activity.look_dir
1413 {
1414 let ori = Vec3::new(dir.x.atan2(dir.y), -dir.z.atan(), 0.0);
1415 self.scene.camera_mut().lerp_toward(ori, dt, 2.5);
1416 }
1417 }
1418
1419 if let Some(viewpoint_entity) = self.viewpoint_entity
1420 && !self
1421 .client
1422 .borrow()
1423 .state()
1424 .ecs()
1425 .read_storage::<Pos>()
1426 .contains(viewpoint_entity)
1427 {
1428 self.client.borrow_mut().stop_spectate_entity();
1429 self.viewpoint_entity = None;
1430 self.scene.camera_mut().set_mode(CameraMode::Freefly);
1431 }
1432
1433 let (viewpoint_entity, mutable_viewpoint) = self.viewpoint_entity();
1434
1435 let input_vec = self.key_state.dir_vec();
1437 if input_vec.magnitude_squared() > 0.5f32.powi(2) {
1438 global_state.profile.tutorial.event_move()
1439 }
1440 let (axis_right, axis_up) = (input_vec[0], input_vec[1]);
1441
1442 if let Some(ref mut timer) = self.key_state.give_up {
1443 *timer += dt;
1444
1445 if *timer > crate::key_state::GIVE_UP_HOLD_TIME {
1446 self.client.borrow_mut().give_up();
1447 }
1448 }
1449
1450 if mutable_viewpoint {
1451 if let Some(dir) = self
1453 .auto_walk
1454 .then_some(self.client.borrow())
1455 .filter(|client| client.is_gliding())
1456 .and_then(|client| {
1457 let ecs = client.state().ecs();
1458 let entity = client.entity();
1459 let fluid = ecs
1460 .read_storage::<comp::PhysicsState>()
1461 .get(entity)?
1462 .in_fluid?;
1463 let vel = *ecs.read_storage::<Vel>().get(entity)?;
1464 let free_look = self.free_look;
1465 let dir_forward_xy = self.scene.camera().forward_xy();
1466 let dir_right = self.scene.camera().right();
1467
1468 auto_glide(fluid, vel, free_look, dir_forward_xy, dir_right)
1469 })
1470 {
1471 self.key_state.auto_walk = false;
1472 self.inputs.move_dir = Vec2::zero();
1473 self.inputs.look_dir = dir;
1474 } else {
1475 self.key_state.auto_walk = self.auto_walk;
1476 if !self.free_look {
1477 self.walk_forward_dir = self.scene.camera().forward_xy();
1478 self.walk_right_dir = self.scene.camera().right_xy();
1479
1480 let client = self.client.borrow();
1481
1482 let holding_ranged = client
1483 .inventories()
1484 .get(player_entity)
1485 .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
1486 .and_then(|item| item.tool_info())
1487 .is_some_and(|tool_kind| {
1488 matches!(
1489 tool_kind,
1490 ToolKind::Bow
1491 | ToolKind::Staff
1492 | ToolKind::Sceptre
1493 | ToolKind::Throwable
1494 )
1495 })
1496 || client
1497 .current::<CharacterState>()
1498 .is_some_and(|char_state| {
1499 matches!(char_state, CharacterState::Throw(_))
1500 });
1501
1502 let dir = if is_aiming
1503 && holding_ranged
1504 && self.scene.camera().get_mode() == CameraMode::ThirdPerson
1505 {
1506 let ray_start = self.scene.camera().get_focus_pos();
1511 let entity_ray_end = ray_start + cam_dir * 1000.0;
1512 let terrain_ray_end = ray_start + cam_dir * 1000.0;
1513
1514 let aim_point = {
1515 let entity_dist =
1517 ray_entities(&client, ray_start, entity_ray_end, 1000.0).0;
1518 let terrain_ray_distance = client
1519 .state()
1520 .terrain()
1521 .ray(ray_start, terrain_ray_end)
1522 .max_iter(1000)
1523 .until(Block::is_solid)
1524 .cast()
1525 .0;
1526
1527 ray_start + cam_dir * entity_dist.min(terrain_ray_distance)
1529 };
1530
1531 let ori = client
1533 .state()
1534 .read_storage::<comp::Ori>()
1535 .get(player_entity)
1536 .copied()
1537 .unwrap();
1538 let scale = client
1540 .state()
1541 .read_storage::<comp::Scale>()
1542 .get(player_entity)
1543 .copied()
1544 .unwrap_or(comp::Scale(1.0));
1545 let body = client
1547 .state()
1548 .read_storage::<comp::Body>()
1549 .get(player_entity)
1550 .copied()
1551 .unwrap();
1552 let body_offsets = body.projectile_offsets(ori.look_vec(), scale.0);
1553
1554 let player_pos = client
1556 .state()
1557 .read_storage::<Pos>()
1558 .get(player_entity)
1559 .copied()
1560 .unwrap();
1561
1562 drop(client);
1563 aim_point - (player_pos.0 + body_offsets)
1564 } else {
1565 cam_dir + aim_dir_offset
1566 };
1567
1568 self.inputs.look_dir = Dir::from_unnormalized(dir).unwrap();
1569 }
1570 }
1571 self.inputs.strafing = matches!(
1572 self.scene.camera().get_mode(),
1573 camera::CameraMode::FirstPerson
1574 );
1575
1576 if global_state.settings.gameplay.auto_camera
1578 && matches!(
1579 self.scene.camera().get_mode(),
1580 camera::CameraMode::ThirdPerson | camera::CameraMode::FirstPerson
1581 )
1582 && input_vec.magnitude_squared() > 0.0
1583 {
1584 let camera = self.scene.camera_mut();
1585 let ori = camera.get_orientation();
1586 camera.set_orientation_instant(Vec3::new(
1587 ori.x
1588 + input_vec.x
1589 * (3.0 - input_vec.y * 1.5 * if is_aiming { 1.5 } else { 1.0 })
1590 * dt,
1591 std::f32::consts::PI * if is_aiming { 0.015 } else { 0.1 },
1592 0.0,
1593 ));
1594 }
1595
1596 self.inputs.move_z =
1597 self.key_state.swim_up as i32 as f32 - self.key_state.swim_down as i32 as f32;
1598 }
1599
1600 match self.scene.camera().get_mode() {
1601 CameraMode::FirstPerson | CameraMode::ThirdPerson => {
1602 if mutable_viewpoint {
1603 self.inputs.move_dir =
1607 self.walk_right_dir * axis_right + self.walk_forward_dir * axis_up;
1608 }
1609 },
1610 CameraMode::Freefly => {
1611 const FREEFLY_SPEED: f32 = 50.0;
1614 const FREEFLY_SPEED_BOOST: f32 = 5.0;
1615
1616 let forward = self.scene.camera().forward().with_z(0.0).normalized();
1617 let right = self.scene.camera().right().with_z(0.0).normalized();
1618 let up = Vec3::unit_z();
1619 let up_axis = self.key_state.swim_up as i32 as f32
1620 - self.key_state.swim_down as i32 as f32;
1621
1622 let dir = (right * axis_right + forward * axis_up + up * up_axis).normalized();
1623
1624 let speed = FREEFLY_SPEED
1625 * if self.inputs_state.contains(&GameInput::SpectateSpeedBoost) {
1626 FREEFLY_SPEED_BOOST
1627 } else {
1628 1.0
1629 };
1630
1631 let pos = self.scene.camera().get_focus_pos();
1632 self.scene
1633 .camera_mut()
1634 .set_focus_pos(pos + dir * dt * speed);
1635
1636 self.inputs.move_dir = Vec2::zero();
1638 },
1639 };
1640
1641 let mut outcomes = Vec::new();
1642
1643 if !global_state.paused() {
1645 match self.tick(
1647 global_state.clock.get_stable_dt(),
1648 global_state,
1649 &mut outcomes,
1650 ) {
1651 Ok(TickAction::Continue) => {}, Ok(TickAction::Disconnect) => return PlayStateResult::Pop, Err(Error::ClientError(error)) => {
1654 error!("[session] Failed to tick the scene: {:?}", error);
1655 global_state.info_message =
1656 Some(get_client_msg_error(error, None, &global_state.i18n.read()));
1657
1658 return PlayStateResult::Pop;
1659 },
1660 Err(err) => {
1661 global_state.info_message = Some(
1662 global_state
1663 .i18n
1664 .read()
1665 .get_msg("common-connection_lost")
1666 .into_owned(),
1667 );
1668 error!("[session] Failed to tick the scene: {:?}", err);
1669
1670 return PlayStateResult::Pop;
1671 },
1672 }
1673 }
1674
1675 if self.walking_speed {
1676 self.key_state.speed_mul = global_state.settings.gameplay.walking_speed;
1677 } else {
1678 self.key_state.speed_mul = 1.0;
1679 }
1680
1681 self.scene
1683 .camera_mut()
1684 .compute_dependents(&self.client.borrow().state().terrain());
1685
1686 let debug_info = global_state.settings.interface.toggle_debug.then(|| {
1690 let client = self.client.borrow();
1691 let ecs = client.state().ecs();
1692 let client_entity = client.entity();
1693 let coordinates = ecs.read_storage::<Pos>().get(viewpoint_entity).cloned();
1694 let velocity = ecs.read_storage::<Vel>().get(viewpoint_entity).cloned();
1695 let ori = ecs
1696 .read_storage::<comp::Ori>()
1697 .get(viewpoint_entity)
1698 .cloned();
1699 let look_dir = if viewpoint_entity == client_entity {
1702 self.inputs.look_dir
1703 } else {
1704 ecs.read_storage::<comp::Controller>()
1705 .get(viewpoint_entity)
1706 .map(|c| c.inputs.look_dir)
1707 .unwrap_or_default()
1708 };
1709 let in_fluid = ecs
1710 .read_storage::<comp::PhysicsState>()
1711 .get(viewpoint_entity)
1712 .and_then(|state| state.in_fluid);
1713 let character_state = ecs
1714 .read_storage::<comp::CharacterState>()
1715 .get(viewpoint_entity)
1716 .cloned();
1717
1718 DebugInfo {
1719 tps: global_state.clock.stats().average_tps,
1720 frame_time: global_state.clock.stats().average_busy_dt,
1721 ping_ms: self.client.borrow().get_ping_ms_rolling_avg(),
1722 coordinates,
1723 velocity,
1724 ori,
1725 look_dir,
1726 character_state,
1727 in_fluid,
1728 num_chunks: self.scene.terrain().chunk_count() as u32,
1729 num_lights: self.scene.lights().len() as u32,
1730 num_visible_chunks: self.scene.terrain().visible_chunk_count() as u32,
1731 num_shadow_chunks: self.scene.terrain().shadow_chunk_count() as u32,
1732 num_figures: self.scene.figure_mgr().figure_count() as u32,
1733 num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32,
1734 num_particles: self.scene.particle_mgr().particle_count() as u32,
1735 num_particles_visible: self.scene.particle_mgr().particle_count_visible()
1736 as u32,
1737 current_track: self.scene.music_mgr().current_track(),
1738 current_artist: self.scene.music_mgr().current_artist(),
1739 active_channels: global_state.audio.get_num_active_channels(),
1740 audio_cpu_usage: global_state.audio.get_cpu_usage(),
1741 }
1742 });
1743
1744 let inverted_interactable_map = self.interactables.inverted_map();
1745
1746 let mut hud_events = self.hud.maintain(
1748 &self.client.borrow(),
1749 global_state,
1750 &debug_info,
1751 self.scene.camera(),
1752 global_state.clock.get_stable_dt(),
1753 HudInfo {
1754 is_aiming,
1755 active_mine_tool,
1756 is_first_person: matches!(
1757 self.scene.camera().get_mode(),
1758 camera::CameraMode::FirstPerson
1759 ),
1760 viewpoint_entity,
1761 mutable_viewpoint,
1762 target_entity: self.target_entity,
1763 selected_entity: self.selected_entity,
1764 persistence_load_error: self.metadata.skill_set_persistence_load_error,
1765 key_state: &self.key_state,
1766 },
1767 inverted_interactable_map,
1768 );
1769
1770 #[cfg(feature = "egui-ui")]
1772 if global_state.settings.interface.egui_enabled() {
1773 let settings_change = global_state.egui_state.maintain(
1774 &mut self.client.borrow_mut(),
1775 &mut self.scene,
1776 global_state.window.window(),
1777 debug_info.map(|debug_info| EguiDebugInfo {
1778 frame_time: debug_info.frame_time,
1779 ping_ms: debug_info.ping_ms,
1780 }),
1781 &global_state.settings,
1782 );
1783
1784 if let Some(settings_change) = settings_change {
1785 settings_change.process(global_state, self);
1786 }
1787 }
1788
1789 if global_state.i18n.reloaded() {
1791 hud_events.push(HudEvent::SettingsChange(
1792 ChangeLanguage(Box::new(global_state.i18n.read().metadata().clone())).into(),
1793 ));
1794 }
1795
1796 let mut has_repaired = false;
1797 let sfx_triggers = self.scene.sfx_mgr.triggers.read();
1798 for event in hud_events {
1800 match event {
1801 HudEvent::SendMessage(msg) => {
1802 self.client.borrow_mut().send_chat(msg);
1804 },
1805 HudEvent::SendCommand(name, args) => {
1806 match run_command(self, global_state, &name, args) {
1807 Ok(Some(info)) => {
1808 self.hud.new_message(ChatType::CommandInfo.into_msg(info))
1809 },
1810 Ok(None) => {}, Err(error) => {
1812 self.hud.new_message(ChatType::CommandError.into_msg(error))
1813 },
1814 };
1815 },
1816 HudEvent::CharacterSelection => {
1817 global_state.audio.stop_all_music();
1818 global_state.audio.stop_all_ambience();
1819 global_state.audio.stop_all_sfx();
1820 self.client.borrow_mut().request_remove_character()
1821 },
1822 HudEvent::Logout => {
1823 self.client.borrow_mut().logout();
1824 global_state.audio.stop_all_ambience();
1828 global_state.audio.stop_all_sfx();
1829 return PlayStateResult::Pop;
1830 },
1831 HudEvent::Quit => {
1832 return PlayStateResult::Shutdown;
1833 },
1834
1835 HudEvent::RemoveBuff(buff_id) => {
1836 self.client.borrow_mut().remove_buff(buff_id);
1837 },
1838 HudEvent::LeaveStance => self.client.borrow_mut().leave_stance(),
1839 HudEvent::UnlockSkill(skill) => {
1840 self.client.borrow_mut().unlock_skill(skill);
1841 },
1842 HudEvent::UseSlot {
1843 slot,
1844 bypass_dialog,
1845 } => {
1846 let mut move_allowed = true;
1847
1848 if !bypass_dialog
1849 && let Some(inventory) = self
1850 .client
1851 .borrow()
1852 .state()
1853 .ecs()
1854 .read_storage::<comp::Inventory>()
1855 .get(self.client.borrow().entity())
1856 {
1857 match slot {
1858 Slot::Inventory(inv_slot) => {
1859 let slot_deficit = inventory.free_after_equip(inv_slot);
1860 if slot_deficit < 0 {
1861 self.hud.set_prompt_dialog(PromptDialogSettings::new(
1862 global_state.i18n.read().get_content(
1863 &Content::localized_with_args(
1864 "hud-bag-use_slot_equip_drop_items",
1865 [(
1866 "slot_deficit",
1867 slot_deficit.unsigned_abs() as u64,
1868 )],
1869 ),
1870 ),
1871 HudEvent::UseSlot {
1872 slot,
1873 bypass_dialog: true,
1874 },
1875 None,
1876 ));
1877 move_allowed = false;
1878 }
1879 },
1880 Slot::Equip(equip_slot) => {
1881 let free_slots =
1884 inventory.free_slots_minus_equipped_item(equip_slot);
1885 if free_slots > 0 {
1886 let slot_deficit = inventory.free_after_unequip(equip_slot);
1887 if slot_deficit < 0 {
1888 self.hud.set_prompt_dialog(PromptDialogSettings::new(
1889 global_state.i18n.read().get_content(
1890 &Content::localized_with_args(
1891 "hud-bag-use_slot_unequip_drop_items",
1892 [(
1893 "slot_deficit",
1894 slot_deficit.unsigned_abs() as u64,
1895 )],
1896 ),
1897 ),
1898 HudEvent::UseSlot {
1899 slot,
1900 bypass_dialog: true,
1901 },
1902 None,
1903 ));
1904 move_allowed = false;
1905 }
1906 } else {
1907 move_allowed = false;
1908 }
1909 },
1910 Slot::Overflow(_) => {},
1911 }
1912 };
1913
1914 if move_allowed {
1915 self.client.borrow_mut().use_slot(slot);
1916 }
1917 },
1918 HudEvent::SwapEquippedWeapons => {
1919 self.client.borrow_mut().swap_loadout();
1920 },
1921 HudEvent::SwapSlots {
1922 slot_a,
1923 slot_b,
1924 bypass_dialog,
1925 } => {
1926 let mut move_allowed = true;
1927 if !bypass_dialog
1928 && let Some(inventory) = self
1929 .client
1930 .borrow()
1931 .state()
1932 .ecs()
1933 .read_storage::<comp::Inventory>()
1934 .get(self.client.borrow().entity())
1935 {
1936 match (slot_a, slot_b) {
1937 (Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
1938 | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
1939 if !inventory.can_swap(inv_slot, equip_slot) {
1940 move_allowed = false;
1941 } else {
1942 let slot_deficit =
1943 inventory.free_after_swap(equip_slot, inv_slot);
1944 if slot_deficit < 0 {
1945 self.hud.set_prompt_dialog(PromptDialogSettings::new(
1946 global_state.i18n.read().get_content(
1947 &Content::localized_with_args(
1948 "hud-bag-swap_slots_drop_items",
1949 [(
1950 "slot_deficit",
1951 slot_deficit.unsigned_abs() as u64,
1952 )],
1953 ),
1954 ),
1955 HudEvent::SwapSlots {
1956 slot_a,
1957 slot_b,
1958 bypass_dialog: true,
1959 },
1960 None,
1961 ));
1962 move_allowed = false;
1963 }
1964 }
1965 },
1966 _ => {},
1967 }
1968 }
1969 if move_allowed {
1970 self.client.borrow_mut().swap_slots(slot_a, slot_b);
1971 }
1972 },
1973 HudEvent::SelectExpBar(skillgroup) => {
1974 global_state.settings.interface.xp_bar_skillgroup = skillgroup;
1975 },
1976 HudEvent::SplitSwapSlots {
1977 slot_a,
1978 slot_b,
1979 bypass_dialog,
1980 } => {
1981 let mut move_allowed = true;
1982 if !bypass_dialog
1983 && let Some(inventory) = self
1984 .client
1985 .borrow()
1986 .state()
1987 .ecs()
1988 .read_storage::<comp::Inventory>()
1989 .get(self.client.borrow().entity())
1990 {
1991 match (slot_a, slot_b) {
1992 (Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
1993 | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
1994 if !inventory.can_swap(inv_slot, equip_slot) {
1995 move_allowed = false;
1996 } else {
1997 let slot_deficit =
1998 inventory.free_after_swap(equip_slot, inv_slot);
1999 if slot_deficit < 0 {
2000 self.hud.set_prompt_dialog(PromptDialogSettings::new(
2001 global_state.i18n.read().get_content(
2002 &Content::localized_with_args(
2003 "hud-bag-split_swap_slots_drop_items",
2004 [(
2005 "slot_deficit",
2006 slot_deficit.unsigned_abs() as u64,
2007 )],
2008 ),
2009 ),
2010 HudEvent::SwapSlots {
2011 slot_a,
2012 slot_b,
2013 bypass_dialog: true,
2014 },
2015 None,
2016 ));
2017 move_allowed = false;
2018 }
2019 }
2020 },
2021 _ => {},
2022 }
2023 };
2024 if move_allowed {
2025 self.client.borrow_mut().split_swap_slots(slot_a, slot_b);
2026 }
2027 },
2028 HudEvent::DropSlot(x) => {
2029 let mut client = self.client.borrow_mut();
2030 client.drop_slot(x);
2031 if let Slot::Equip(EquipSlot::Lantern) = x {
2032 client.disable_lantern();
2033 }
2034 },
2035 HudEvent::SplitDropSlot(x) => {
2036 let mut client = self.client.borrow_mut();
2037 client.split_drop_slot(x);
2038 if let Slot::Equip(EquipSlot::Lantern) = x {
2039 client.disable_lantern();
2040 }
2041 },
2042 HudEvent::SortInventory(sort_order) => {
2043 self.client.borrow_mut().sort_inventory(sort_order);
2044 },
2045 HudEvent::ChangeHotbarState(state) => {
2046 let client = self.client.borrow();
2047
2048 let server_name = &client.server_info().name;
2049 let character_id = match client.presence().unwrap() {
2051 PresenceKind::Character(id) => Some(id),
2052 PresenceKind::LoadingCharacter(id) => Some(id),
2053 PresenceKind::Spectator => {
2054 unreachable!("HUD adaption in Spectator mode!")
2055 },
2056 PresenceKind::Possessor => None,
2057 };
2058
2059 global_state.profile.set_hotbar_slots(
2061 server_name,
2062 character_id,
2063 state.slots,
2064 );
2065
2066 global_state
2067 .profile
2068 .save_to_file_warn(&global_state.config_dir);
2069
2070 info!("Event! -> ChangedHotbarState")
2071 },
2072 HudEvent::TradeAction(action) => {
2073 self.client.borrow_mut().perform_trade_action(action);
2074 },
2075 HudEvent::Ability { idx, state } => {
2076 self.client.borrow_mut().handle_input(
2077 InputKind::Ability(idx),
2078 state,
2079 default_select_pos,
2080 self.target_entity,
2081 );
2082 },
2083
2084 HudEvent::RequestSiteInfo(id) => {
2085 self.client.borrow_mut().request_site_economy(id);
2086 },
2087
2088 HudEvent::CraftRecipe {
2089 recipe_name: recipe,
2090 craft_sprite,
2091 amount,
2092 } => {
2093 let slots = {
2094 let client = self.client.borrow();
2095
2096 if let Some(inventory) = client
2097 .state()
2098 .ecs()
2099 .read_storage::<comp::Inventory>()
2100 .get(client.entity())
2101 {
2102 let rbm =
2103 client.state().ecs().read_resource::<RecipeBookManifest>();
2104 if let Some(recipe) = inventory.get_recipe(&recipe, &rbm) {
2105 recipe.inventory_contains_ingredients(inventory, 1).ok()
2106 } else {
2107 None
2108 }
2109 } else {
2110 None
2111 }
2112 };
2113 if let Some(slots) = slots {
2114 self.client.borrow_mut().craft_recipe(
2115 &recipe,
2116 slots,
2117 craft_sprite,
2118 amount,
2119 );
2120 }
2121 },
2122
2123 HudEvent::CraftModularWeapon {
2124 primary_slot,
2125 secondary_slot,
2126 craft_sprite,
2127 } => {
2128 self.client.borrow_mut().craft_modular_weapon(
2129 primary_slot,
2130 secondary_slot,
2131 craft_sprite,
2132 );
2133 },
2134
2135 HudEvent::CraftModularWeaponComponent {
2136 toolkind,
2137 material,
2138 modifier,
2139 craft_sprite,
2140 } => {
2141 let additional_slots = {
2142 let client = self.client.borrow();
2143 let item_id = |slot| {
2144 client
2145 .inventories()
2146 .get(client.entity())
2147 .and_then(|inv| inv.get(slot))
2148 .and_then(|item| {
2149 item.item_definition_id().itemdef_id().map(String::from)
2150 })
2151 };
2152 if let Some(material_id) = item_id(material) {
2153 let key = recipe::ComponentKey {
2154 toolkind,
2155 material: material_id,
2156 modifier: modifier.and_then(item_id),
2157 };
2158 if let Some(recipe) = client.component_recipe_book().get(&key) {
2159 client.inventories().get(client.entity()).and_then(|inv| {
2160 recipe.inventory_contains_additional_ingredients(inv).ok()
2161 })
2162 } else {
2163 None
2164 }
2165 } else {
2166 None
2167 }
2168 };
2169 if let Some(additional_slots) = additional_slots {
2170 self.client.borrow_mut().craft_modular_weapon_component(
2171 toolkind,
2172 material,
2173 modifier,
2174 additional_slots,
2175 craft_sprite,
2176 );
2177 }
2178 },
2179 HudEvent::SalvageItem { slot, salvage_pos } => {
2180 self.client.borrow_mut().salvage_item(slot, salvage_pos);
2181 },
2182 HudEvent::RepairItem { item, sprite_pos } => {
2183 let slots = {
2184 let client = self.client.borrow();
2185 let slots = (|| {
2186 if let Some(inventory) = client.inventories().get(client.entity()) {
2187 let item = match item {
2188 Slot::Equip(slot) => inventory.equipped(slot),
2189 Slot::Inventory(slot) => inventory.get(slot),
2190 Slot::Overflow(_) => None,
2191 }?;
2192 let repair_recipe =
2193 client.repair_recipe_book().repair_recipe(item)?;
2194 repair_recipe
2195 .inventory_contains_ingredients(item, inventory)
2196 .ok()
2197 } else {
2198 None
2199 }
2200 })();
2201 slots.unwrap_or_default()
2202 };
2203 if !has_repaired {
2204 let sfx_trigger_item = sfx_triggers
2205 .0
2206 .get_key_value(&SfxEvent::from(&InventoryUpdateEvent::Craft));
2207 global_state.audio.emit_ui_sfx(sfx_trigger_item, None, None);
2208 has_repaired = true
2209 };
2210 self.client
2211 .borrow_mut()
2212 .repair_item(item, slots, sprite_pos);
2213 },
2214 HudEvent::InviteMember(uid) => {
2215 self.client.borrow_mut().send_invite(uid, InviteKind::Group);
2216 },
2217 HudEvent::AcceptInvite => {
2218 self.client.borrow_mut().accept_invite();
2219 },
2220 HudEvent::DeclineInvite => {
2221 self.client.borrow_mut().decline_invite();
2222 },
2223 HudEvent::KickMember(uid) => {
2224 self.client.borrow_mut().kick_from_group(uid);
2225 },
2226 HudEvent::LeaveGroup => {
2227 self.client.borrow_mut().leave_group();
2228 },
2229 HudEvent::AssignLeader(uid) => {
2230 self.client.borrow_mut().assign_group_leader(uid);
2231 },
2232 HudEvent::ChangeAbility(slot, new_ability) => {
2233 self.client.borrow_mut().change_ability(slot, new_ability);
2234 },
2235 HudEvent::SettingsChange(settings_change) => {
2236 settings_change.process(global_state, self);
2237 },
2238 HudEvent::AcknowledgePersistenceLoadError => {
2239 self.metadata.skill_set_persistence_load_error = None;
2240 },
2241 HudEvent::MapMarkerEvent(event) => {
2242 self.client.borrow_mut().map_marker_event(event);
2243 },
2244 HudEvent::Dialogue(target, dialogue) => {
2245 self.client.borrow_mut().perform_dialogue(target, dialogue);
2246 },
2247 HudEvent::SetBattleMode(mode) => {
2248 self.client.borrow_mut().set_battle_mode(mode);
2249 },
2250 }
2251 }
2252
2253 {
2254 let client = self.client.borrow();
2255 let scene_data = SceneData {
2256 client: &client,
2257 state: client.state(),
2258 viewpoint_entity,
2259 mutable_viewpoint: mutable_viewpoint || self.free_look,
2260 target_entities: &self.interactables.entities,
2262 loaded_distance: client.loaded_distance(),
2263 terrain_view_distance: client.view_distance().unwrap_or(1),
2264 entity_view_distance: client
2265 .view_distance()
2266 .unwrap_or(1)
2267 .min(global_state.settings.graphics.entity_view_distance),
2268 tick: client.get_tick(),
2269 gamma: global_state.settings.graphics.gamma,
2270 exposure: global_state.settings.graphics.exposure,
2271 ambiance: global_state.settings.graphics.ambiance,
2272 mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable,
2273 sprite_render_distance: global_state.settings.graphics.sprite_render_distance
2274 as f32,
2275 particles_enabled: global_state.settings.graphics.particles_enabled,
2276 weapon_trails_enabled: global_state.settings.graphics.weapon_trails_enabled,
2277 flashing_lights_enabled: global_state
2278 .settings
2279 .graphics
2280 .render_mode
2281 .flashing_lights_enabled,
2282 figure_lod_render_distance: global_state
2283 .settings
2284 .graphics
2285 .figure_lod_render_distance
2286 as f32,
2287 is_aiming,
2288 interpolated_time_of_day: self.scene.interpolated_time_of_day,
2289 wind_vel: self.scene.wind_vel,
2290 };
2291
2292 if !global_state.paused() {
2294 self.scene.maintain(
2295 global_state.window.renderer_mut(),
2296 &mut global_state.audio,
2297 &scene_data,
2298 &client,
2299 &global_state.settings,
2300 );
2301
2302 for outcome in outcomes {
2304 self.scene
2305 .handle_outcome(&outcome, &scene_data, &mut global_state.audio);
2306 self.hud.handle_outcome(&outcome, &scene_data, global_state);
2307 }
2308 }
2309 }
2310
2311 self.cleanup();
2313
2314 PlayStateResult::Continue
2315 } else if client_registered && client_presence.is_none() {
2316 if client_type.can_spectate() && !client_type.can_enter_character() {
2319 PlayStateResult::Pop
2321 } else {
2322 PlayStateResult::Switch(Box::new(CharSelectionState::new(
2323 global_state,
2324 Rc::clone(&self.client),
2325 Rc::clone(&self.hud.persisted_state),
2326 )))
2327 }
2328 } else {
2329 error!("Client not in the expected state, exiting session play state");
2330 PlayStateResult::Pop
2331 }
2332 }
2333
2334 fn name(&self) -> &'static str { "Session" }
2335
2336 fn capped_fps(&self) -> bool { false }
2337
2338 fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
2339
2340 fn render(&self, drawer: &mut Drawer<'_>, settings: &Settings) {
2344 span!(_guard, "render", "<Session as PlayState>::render");
2345
2346 let client = self.client.borrow();
2347
2348 let (viewpoint_entity, mutable_viewpoint) = self.viewpoint_entity();
2349
2350 let scene_data = SceneData {
2351 client: &client,
2352 state: client.state(),
2353 viewpoint_entity,
2354 mutable_viewpoint,
2355 target_entities: &self.interactables.entities,
2357 loaded_distance: client.loaded_distance(),
2358 terrain_view_distance: client.view_distance().unwrap_or(1),
2359 entity_view_distance: client
2360 .view_distance()
2361 .unwrap_or(1)
2362 .min(settings.graphics.entity_view_distance),
2363 tick: client.get_tick(),
2364 gamma: settings.graphics.gamma,
2365 exposure: settings.graphics.exposure,
2366 ambiance: settings.graphics.ambiance,
2367 mouse_smoothing: settings.gameplay.smooth_pan_enable,
2368 sprite_render_distance: settings.graphics.sprite_render_distance as f32,
2369 figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32,
2370 particles_enabled: settings.graphics.particles_enabled,
2371 weapon_trails_enabled: settings.graphics.weapon_trails_enabled,
2372 flashing_lights_enabled: settings.graphics.render_mode.flashing_lights_enabled,
2373 is_aiming: self.is_aiming,
2374 interpolated_time_of_day: self.scene.interpolated_time_of_day,
2375 wind_vel: self.scene.wind_vel,
2376 };
2377
2378 self.scene.render(
2380 drawer,
2381 client.state(),
2382 viewpoint_entity,
2383 client.get_tick(),
2384 &scene_data,
2385 );
2386
2387 if let Some(mut volumetric_pass) = drawer.volumetric_pass() {
2388 prof_span!("clouds");
2390 volumetric_pass.draw_clouds();
2391 }
2392 if let Some(mut transparent_pass) = drawer.transparent_pass() {
2393 prof_span!("trails");
2395 if let Some(mut trail_drawer) = transparent_pass.draw_trails() {
2396 self.scene
2397 .trail_mgr()
2398 .render(&mut trail_drawer, &scene_data);
2399 }
2400 }
2401 {
2403 prof_span!("bloom");
2404 drawer.run_bloom_passes()
2405 }
2406 {
2408 prof_span!("post-process and ui");
2409 let mut third_pass = drawer.third_pass();
2410 third_pass.draw_postprocess();
2411 if let Some(mut ui_drawer) = third_pass.draw_ui() {
2413 self.hud.render(&mut ui_drawer);
2414 }; }
2417 }
2418
2419 fn egui_enabled(&self) -> bool { true }
2420}
2421
2422fn find_shortest_distance(arr: &[Option<f32>]) -> Option<f32> {
2423 arr.iter()
2424 .filter_map(|x| *x)
2425 .min_by(|d1, d2| OrderedFloat(*d1).cmp(&OrderedFloat(*d2)))
2426}
2427
2428fn auto_glide(
2430 fluid: Fluid,
2431 vel: Vel,
2432 free_look: bool,
2433 dir_forward_xy: Vec2<f32>,
2434 dir_right: Vec3<f32>,
2435) -> Option<Dir> {
2436 let Vel(rel_flow) = fluid.relative_flow(&vel);
2437
2438 let is_wind_downwards = rel_flow.z.is_sign_negative();
2439
2440 let dir = if free_look {
2441 if is_wind_downwards {
2442 Vec3::from(-rel_flow.xy())
2443 } else {
2444 -rel_flow
2445 }
2446 } else if is_wind_downwards {
2447 dir_forward_xy.into()
2448 } else {
2449 let windwards = rel_flow * dir_forward_xy.dot(rel_flow.xy()).signum();
2450 Plane::from(Dir::new(dir_right)).projection(windwards)
2451 };
2452
2453 Dir::from_unnormalized(dir)
2454}