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 head_ratio: 1.0,
486 height: sphere.radius * 2.0,
487 },
488 sphere.center,
489 );
490 },
491 comp::gizmos::Shape::LineStrip(lines) => {
492 for (a, b) in lines.into_iter().tuple_windows::<(_, _)>() {
493 add_shape(
494 crate::scene::DebugShape::Line([Vec3::zero(), b - a], 0.1),
495 a,
496 );
497 }
498 },
499 }
500 }
501 },
502 }
503 }
504
505 Ok(TickAction::Continue)
506 }
507
508 pub fn cleanup(&mut self) { self.client.borrow_mut().cleanup(); }
510
511 fn should_auto_zoom_lock(&self) -> bool {
512 let inputs_state = &self.inputs_state;
513 for input in inputs_state {
514 match input {
515 GameInput::Primary
516 | GameInput::Secondary
517 | GameInput::Block
518 | GameInput::MoveForward
519 | GameInput::MoveLeft
520 | GameInput::MoveRight
521 | GameInput::MoveBack
522 | GameInput::Jump
523 | GameInput::WallJump
524 | GameInput::Roll
525 | GameInput::Sneak
526 | GameInput::AutoWalk
527 | GameInput::SwimUp
528 | GameInput::SwimDown
529 | GameInput::SwapLoadout
530 | GameInput::ToggleWield
531 | GameInput::Slot1
532 | GameInput::Slot2
533 | GameInput::Slot3
534 | GameInput::Slot4
535 | GameInput::Slot5
536 | GameInput::Slot6
537 | GameInput::Slot7
538 | GameInput::Slot8
539 | GameInput::Slot9
540 | GameInput::Slot10
541 | GameInput::CurrentSlot
542 | GameInput::SpectateViewpoint
543 | GameInput::SpectateSpeedBoost => return true,
544 _ => (),
545 }
546 }
547 false
548 }
549}
550
551impl PlayState for SessionState {
552 fn enter(&mut self, global_state: &mut GlobalState, _: Direction) {
553 global_state.window.grab_cursor(true);
555
556 self.client.borrow_mut().clear_terrain();
557
558 if global_state.settings.send_logon_commands {
560 for cmd in &global_state.settings.logon_commands {
561 self.client.borrow_mut().send_chat(cmd.to_string());
562 }
563 }
564
565 #[cfg(feature = "discord")]
566 {
567 #[cfg(feature = "singleplayer")]
569 let singleplayer = global_state.singleplayer.is_running();
570 #[cfg(not(feature = "singleplayer"))]
571 let singleplayer = false;
572
573 if singleplayer {
574 global_state.discord.join_singleplayer();
575 } else {
576 global_state
577 .discord
578 .join_server(self.client.borrow().server_info().name.clone());
579 }
580 }
581 }
582
583 fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
584 span!(_guard, "tick", "<Session as PlayState>::tick");
585 let (client_presence, client_type, client_registered) = {
588 let client = self.client.borrow();
589 (
590 client.presence(),
591 *client.client_type(),
592 client.registered(),
593 )
594 };
595
596 if let Some(presence) = client_presence {
597 let camera = self.scene.camera_mut();
598
599 if self.camera_clamp {
601 let mut cam_dir = camera.get_orientation();
602 let cam_dir_clamp =
603 (global_state.settings.gameplay.camera_clamp_angle as f32).to_radians();
604 cam_dir.y = cam_dir.y.clamp(-cam_dir_clamp, cam_dir_clamp);
605 camera.set_orientation(cam_dir);
606 }
607
608 let client = self.client.borrow();
609 let player_entity = client.entity();
610
611 let dt = global_state.clock.get_stable_dt().as_secs_f32();
612
613 #[cfg(feature = "discord")]
614 if global_state.discord.is_active()
615 && let Some(chunk) = client.current_chunk()
616 && let Some(location_name) = chunk.meta().name()
617 {
618 global_state
619 .discord
620 .update_location(location_name, client.current_site());
621 }
622
623 if global_state.settings.gameplay.bow_zoom {
624 let mut fov_scaling = 1.0;
625 if let Some(comp::CharacterState::ChargedRanged(cr)) = client
626 .state()
627 .read_storage::<comp::CharacterState>()
628 .get(player_entity)
629 && cr.charge_frac() > 0.5
630 {
631 fov_scaling -= 3.0 * cr.charge_frac() / 5.0;
632 }
633 camera.set_fixate(fov_scaling);
634 } else {
635 camera.set_fixate(1.0);
636 }
637
638 camera.compute_dependents(&client.state().terrain());
640 let camera::Dependents {
641 cam_pos, cam_dir, ..
642 } = self.scene.camera().dependents();
643 let focus_pos = self.scene.camera().get_focus_pos();
644 let focus_off = focus_pos.map(|e| e.trunc());
645 let cam_pos = cam_pos + focus_off;
646
647 let (is_aiming, aim_dir_offset) = {
648 let is_aiming = client
649 .state()
650 .read_storage::<comp::CharacterState>()
651 .get(player_entity)
652 .map(|cs| cs.is_wield())
653 .unwrap_or(false);
654
655 (
656 is_aiming,
657 if is_aiming && self.scene.camera().get_mode() == CameraMode::ThirdPerson {
658 Vec3::unit_z() * 0.025
659 } else {
660 Vec3::zero()
661 },
662 )
663 };
664 self.is_aiming = is_aiming;
665
666 let can_build = client
667 .state()
668 .read_storage::<comp::CanBuild>()
669 .get(player_entity)
670 .map_or_else(|| false, |cb| cb.enabled);
671
672 let active_mine_tool: Option<ToolKind> = if client.is_wielding() == Some(true) {
673 client
674 .inventories()
675 .get(player_entity)
676 .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
677 .and_then(|item| item.tool_info())
678 .filter(|tool_kind| matches!(tool_kind, ToolKind::Pick | ToolKind::Shovel))
679 } else {
680 None
681 };
682
683 let (build_target, collect_target, entity_target, mine_target, terrain_target) =
685 targets_under_cursor(
686 &client,
687 cam_pos,
688 cam_dir,
689 can_build,
690 active_mine_tool,
691 self.viewpoint_entity().0,
692 );
693
694 match get_interactables(
695 &client,
696 collect_target,
697 entity_target,
698 mine_target,
699 terrain_target,
700 &self.scene,
701 ) {
702 Ok(input_map) => {
703 for (_, inter) in input_map.values() {
704 global_state.profile.tutorial.event_find_interactable(inter);
705 }
706
707 let entities = input_map
708 .values()
709 .filter_map(|(_, interactable)| {
710 if let Interactable::Entity { entity, .. } = interactable {
711 Some(*entity)
712 } else {
713 None
714 }
715 })
716 .collect::<HashSet<_>>();
717 self.interactables = interactable::Interactables {
718 input_map,
719 entities,
720 };
721 },
722 Err(error) => {
723 tracing::trace!(?error, "Getting interactables failed");
724 self.interactables = Default::default()
725 },
726 }
727
728 drop(client);
729
730 self.maybe_auto_zoom_lock(
731 global_state.settings.gameplay.zoom_lock,
732 global_state.settings.gameplay.zoom_lock_behavior,
733 );
734
735 if presence == PresenceKind::Spectator {
736 let mut client = self.client.borrow_mut();
737 if client.spectate_position(cam_pos) {
738 let server_name = &client.server_info().name;
739 global_state.profile.set_spectate_position(
740 server_name,
741 Some(self.scene.camera().get_focus_pos()),
742 );
743 }
744 }
745
746 let nearest_block_dist = find_shortest_distance(&[
748 mine_target
749 .filter(|_| active_mine_tool.is_some())
750 .map(|t| t.distance),
751 build_target.filter(|_| can_build).map(|t| t.distance),
752 ]);
753 let nearest_scene_dist = find_shortest_distance(&[
755 nearest_block_dist,
756 collect_target
757 .filter(|_| active_mine_tool.is_none())
758 .map(|t| t.distance),
759 ]);
760 self.inputs.break_block_pos = if let Some(mt) = mine_target
762 .filter(|mt| active_mine_tool.is_some() && nearest_scene_dist == Some(mt.distance))
763 {
764 self.scene.set_select_pos(Some(mt.position_int()));
765 Some(mt.position)
766 } else if let Some(bt) =
767 build_target.filter(|bt| can_build && nearest_scene_dist == Some(bt.distance))
768 {
769 self.scene.set_select_pos(Some(bt.position_int()));
770 None
771 } else if let Some(ct) =
772 collect_target.filter(|ct| nearest_scene_dist == Some(ct.distance))
773 {
774 self.scene.set_select_pos(Some(ct.position_int()));
775 None
776 } else {
777 self.scene.set_select_pos(None);
778 None
779 };
780
781 let default_select_pos = terrain_target.map(|tt| tt.position);
783
784 self.target_entity = entity_target.map(|t| t.kind.0);
786
787 let controlling_char = self.controlling_char();
788
789 for event in events {
791 {
793 let client = self.client.borrow();
794 let inventories = client.inventories();
795 let inventory = inventories.get(client.entity());
796 if self
797 .hud
798 .handle_event(event.clone(), global_state, inventory)
799 {
800 continue;
801 }
802 }
803 match event {
804 Event::Close => {
805 return PlayStateResult::Shutdown;
806 },
807 Event::InputUpdate(input, state)
808 if state != self.inputs_state.contains(&input) =>
809 {
810 if !self.inputs_state.insert(input) {
811 self.inputs_state.remove(&input);
812 }
813 match input {
814 GameInput::Primary => {
815 self.walking_speed = false;
816 let mut client = self.client.borrow_mut();
817 if let Some(build_target) = build_target.filter(|bt| {
821 state && can_build && nearest_block_dist == Some(bt.distance)
822 }) {
823 client.remove_block(build_target.position_int());
824 } else {
825 client.handle_input(
826 InputKind::Primary,
827 state,
828 default_select_pos,
829 self.target_entity,
830 );
831 }
832 },
833 GameInput::Secondary => {
834 self.walking_speed = false;
835 let mut client = self.client.borrow_mut();
836 if let Some(build_target) = build_target.filter(|bt| {
837 state && can_build && nearest_block_dist == Some(bt.distance)
838 }) {
839 let selected_pos = build_target.kind.0;
840 client.place_block(
841 selected_pos.map(|p| p.floor() as i32),
842 self.selected_block,
843 );
844 } else {
845 client.handle_input(
846 InputKind::Secondary,
847 state,
848 default_select_pos,
849 self.target_entity,
850 );
851 }
852 },
853 GameInput::Block => {
854 self.walking_speed = false;
855 self.client.borrow_mut().handle_input(
856 InputKind::Block,
857 state,
858 default_select_pos,
859 self.target_entity,
860 );
861 },
862 GameInput::Roll => {
863 self.walking_speed = false;
864 let mut client = self.client.borrow_mut();
865 if can_build {
866 if state
867 && let Some(block) = build_target.and_then(|bt| {
868 client
869 .state()
870 .terrain()
871 .get(bt.position_int())
872 .ok()
873 .copied()
874 })
875 {
876 self.selected_block = block;
877 }
878 } else if controlling_char {
879 global_state.profile.tutorial.event_roll();
880 client.handle_input(
881 InputKind::Roll,
882 state,
883 default_select_pos,
884 self.target_entity,
885 );
886 }
887 },
888 GameInput::GiveUp => {
889 self.key_state.give_up = state.then_some(0.0).filter(|_| {
890 let client = self.client.borrow();
891 comp::is_downed(
892 client.current().as_ref(),
893 client.current().as_ref(),
894 )
895 });
896 },
897 GameInput::Respawn => {
898 self.walking_speed = false;
899 self.stop_auto_walk();
900 if state && self.client.borrow_mut().respawn() {
901 global_state.profile.tutorial.event_respawn();
902 }
903 },
904 GameInput::Jump => {
905 self.walking_speed = false;
906 global_state.profile.tutorial.event_jump();
907 self.client.borrow_mut().handle_input(
908 InputKind::Jump,
909 state,
910 default_select_pos,
911 self.target_entity,
912 );
913 },
914 GameInput::WallJump => {
915 self.walking_speed = false;
916 self.client.borrow_mut().handle_input(
917 InputKind::WallJump,
918 state,
919 default_select_pos,
920 self.target_entity,
921 );
922 },
923 GameInput::SwimUp => {
924 self.key_state.swim_up = state;
925 },
926 GameInput::SwimDown => {
927 self.key_state.swim_down = state;
928 },
929 GameInput::Sit => {
930 if state && controlling_char {
931 self.stop_auto_walk();
932 self.client.borrow_mut().toggle_sit();
933 }
934 },
935 GameInput::Crawl => {
936 if state && controlling_char {
937 self.stop_auto_walk();
938 self.client.borrow_mut().toggle_crawl();
939 }
940 },
941 GameInput::Dance => {
942 if state && controlling_char {
943 self.stop_auto_walk();
944 self.client.borrow_mut().toggle_dance();
945 }
946 },
947 GameInput::Greet => {
948 if state {
949 self.client.borrow_mut().utter(UtteranceKind::Greeting);
950 }
951 },
952 GameInput::Sneak => {
953 let is_trading = self.client.borrow().is_trading();
954 if state && !is_trading && controlling_char {
955 self.stop_auto_walk();
956 self.client.borrow_mut().toggle_sneak();
957 }
958 },
959 GameInput::CancelClimb => {
960 if state && controlling_char {
961 self.client.borrow_mut().cancel_climb();
962 }
963 },
964 GameInput::MoveForward => {
965 if state && global_state.settings.gameplay.stop_auto_walk_on_input {
966 self.stop_auto_walk();
967 }
968 self.key_state.up = state
969 },
970 GameInput::MoveBack => {
971 if state && global_state.settings.gameplay.stop_auto_walk_on_input {
972 self.stop_auto_walk();
973 }
974 self.key_state.down = state
975 },
976 GameInput::MoveLeft => {
977 if state && global_state.settings.gameplay.stop_auto_walk_on_input {
978 self.stop_auto_walk();
979 }
980 self.key_state.left = state
981 },
982 GameInput::MoveRight => {
983 if state && global_state.settings.gameplay.stop_auto_walk_on_input {
984 self.stop_auto_walk();
985 }
986 self.key_state.right = state
987 },
988 GameInput::Glide => {
989 self.walking_speed = false;
990 let is_trading = self.client.borrow().is_trading();
991 if state && !is_trading && controlling_char {
992 if global_state.settings.gameplay.stop_auto_walk_on_input {
993 self.stop_auto_walk();
994 }
995 self.client.borrow_mut().toggle_glide();
996 global_state.profile.tutorial.event_open_glider();
997 }
998 },
999 GameInput::Fly => {
1000 self.key_state.fly ^= state;
1006 self.client.borrow_mut().handle_input(
1007 InputKind::Fly,
1008 self.key_state.fly,
1009 default_select_pos,
1010 self.target_entity,
1011 );
1012 },
1013 GameInput::ToggleWield => {
1014 if state && controlling_char {
1015 let mut client = self.client.borrow_mut();
1016 if client.is_wielding().is_some_and(|b| !b) {
1017 self.walking_speed = false;
1018 }
1019 client.toggle_wield();
1020 }
1021 },
1022 GameInput::SwapLoadout => {
1023 if state && controlling_char {
1024 self.client.borrow_mut().swap_loadout();
1025 }
1026 },
1027 GameInput::ToggleLantern if state && controlling_char => {
1028 let mut client = self.client.borrow_mut();
1029 if client.is_lantern_enabled() {
1030 client.disable_lantern();
1031 } else {
1032 global_state.profile.tutorial.event_lantern();
1033 client.enable_lantern();
1034 }
1035 },
1036 GameInput::Mount if state && controlling_char => {
1037 let mut client = self.client.borrow_mut();
1038 if client.is_riding() {
1039 client.unmount();
1040 } else if let Some((_, interactable)) =
1041 self.interactables.input_map.get(&GameInput::Mount)
1042 {
1043 match interactable {
1044 Interactable::Block { volume_pos, .. } => {
1045 client.mount_volume(*volume_pos)
1046 },
1047 Interactable::Entity { entity, .. } => {
1048 client.mount(*entity)
1049 },
1050 }
1051 }
1052 },
1053 GameInput::StayFollow if state => {
1054 let mut client = self.client.borrow_mut();
1055 let player_pos = client
1056 .state()
1057 .read_storage::<Pos>()
1058 .get(client.entity())
1059 .copied();
1060
1061 let mut close_pet = None;
1062 if let Some(player_pos) = player_pos {
1063 let positions = client.state().read_storage::<Pos>();
1064 close_pet = client.state().ecs().read_resource::<CachedSpatialGrid>().0
1065 .in_circle_aabr(player_pos.0.xy(), MAX_MOUNT_RANGE)
1066 .filter(|e|
1067 *e != client.entity()
1068 )
1069 .filter(|e|
1070 matches!(client.state().ecs().read_storage::<comp::Alignment>().get(*e),
1071 Some(comp::Alignment::Owned(owner)) if Some(*owner) == client.uid())
1072 )
1073 .filter(|e|
1074 client.state().ecs().read_storage::<Is<Mount>>().get(*e).is_none()
1075 )
1076 .min_by_key(|e| {
1077 OrderedFloat(positions
1078 .get(*e)
1079 .map_or(MAX_MOUNT_RANGE * MAX_MOUNT_RANGE, |x| {
1080 player_pos.0.distance_squared(x.0)
1081 }
1082 ))
1083 });
1084 }
1085 if let Some(pet_entity) = close_pet
1086 && client
1087 .state()
1088 .read_storage::<Is<Mount>>()
1089 .get(pet_entity)
1090 .is_none()
1091 {
1092 let is_staying = client
1093 .state()
1094 .read_storage::<CharacterActivity>()
1095 .get(pet_entity)
1096 .is_some_and(|activity| activity.is_pet_staying);
1097 client.set_pet_stay(pet_entity, !is_staying);
1098 }
1099 },
1100 GameInput::Interact => {
1101 if state {
1102 let mut client = self.client.borrow_mut();
1103 if let Some((_, interactable)) =
1104 self.interactables.input_map.get(&GameInput::Interact)
1105 {
1106 match interactable {
1107 Interactable::Block {
1108 volume_pos,
1109 block,
1110 interaction,
1111 ..
1112 } => {
1113 match interaction {
1114 BlockInteraction::Collect { .. }
1115 | BlockInteraction::Unlock { .. } => {
1116 if block.is_directly_collectible() {
1117 match volume_pos.kind {
1118 common::mounting::Volume::Terrain => {
1119 client.collect_block(volume_pos.pos);
1120 }
1121 common::mounting::Volume::Entity(_) => {
1122 },
1124 }
1125 }
1126 },
1127 BlockInteraction::Craft(tab) => {
1128 self.hud.show.open_crafting_tab(
1129 *tab,
1130 block
1131 .get_sprite()
1132 .map(|s| (*volume_pos, s)),
1133 )
1134 },
1135 BlockInteraction::Mine(_)
1136 | BlockInteraction::Mount => {},
1137 BlockInteraction::Read(content) => {
1138 match volume_pos.kind {
1139 common::mounting::Volume::Terrain => {
1140 self.hud.show_content_bubble(
1141 volume_pos.pos.as_()
1142 + Vec3::new(
1143 0.5,
1144 0.5,
1145 block.solid_height()
1146 * 0.75,
1147 ),
1148 content.clone(),
1149 )
1150 },
1151 common::mounting::Volume::Entity(_) => {
1154 },
1155 }
1156 },
1157 BlockInteraction::LightToggle(enable) => {
1158 client.toggle_sprite_light(
1159 *volume_pos,
1160 *enable,
1161 );
1162 },
1163 }
1164 },
1165 Interactable::Entity {
1166 entity,
1167 interaction,
1168 ..
1169 } => {
1170 match interaction {
1172 EntityInteraction::HelpDowned => {
1173 client.help_downed(*entity)
1174 },
1175 EntityInteraction::PickupItem => {
1176 client.pick_up(*entity)
1177 },
1178 EntityInteraction::ActivatePortal => {
1179 client.activate_portal(*entity)
1180 },
1181 EntityInteraction::Pet => {
1182 client.do_pet(*entity)
1183 },
1184 EntityInteraction::Talk => {
1185 client.npc_interact(*entity)
1186 },
1187 EntityInteraction::CampfireSit
1188 | EntityInteraction::Trade
1189 | EntityInteraction::StayFollow
1190 | EntityInteraction::Mount => {},
1191 }
1192 },
1193 }
1194 }
1195 }
1196 },
1197 GameInput::Trade => {
1198 if state
1199 && controlling_char
1200 && let Some((_, Interactable::Entity { entity, .. })) =
1201 self.interactables.input_map.get(&GameInput::Trade)
1202 {
1203 let mut client = self.client.borrow_mut();
1204 if let Some(uid) = client.state().ecs().uid_from_entity(*entity)
1205 {
1206 let name = client
1207 .player_list()
1208 .get(&uid)
1209 .map(|info| info.player_alias.clone())
1210 .unwrap_or_else(|| {
1211 let stats = client.state().read_storage::<Stats>();
1212 stats.get(*entity).map_or(
1213 format!("<entity {:?}>", uid),
1214 |e| {
1215 global_state
1216 .i18n
1217 .read()
1218 .get_content(&e.name)
1219 },
1220 )
1221 });
1222
1223 self.hud.new_message(ChatType::Meta.into_msg(
1224 Content::localized_with_args(
1225 "hud-trade-invite_sent",
1226 [("playername", name)],
1227 ),
1228 ));
1229
1230 client.send_invite(uid, InviteKind::Trade)
1231 };
1232 }
1233 },
1234 GameInput::FreeLook => {
1235 let hud = &mut self.hud;
1236 global_state.settings.gameplay.free_look_behavior.update(
1237 state,
1238 &mut self.free_look,
1239 |b| hud.free_look(b),
1240 );
1241 let camera = self.scene.camera_mut();
1242 let ori = camera.get_orientation();
1243 if self.free_look {
1244 self.freecam_pos = ori;
1245 } else {
1246 camera.set_orientation_instant(self.freecam_pos);
1247 }
1248 },
1249 GameInput::AutoWalk => {
1250 let hud = &mut self.hud;
1251 global_state.settings.gameplay.auto_walk_behavior.update(
1252 state,
1253 &mut self.auto_walk,
1254 |b| hud.auto_walk(b),
1255 );
1256
1257 self.key_state.auto_walk =
1258 self.auto_walk && !self.client.borrow().is_gliding();
1259 },
1260 GameInput::ZoomIn => {
1261 if state {
1262 if self.zoom_lock {
1263 self.hud.zoom_lock_reminder();
1264 } else {
1265 self.scene.handle_input_event(
1266 Event::Zoom(-30.0),
1267 &self.client.borrow(),
1268 );
1269 }
1270 }
1271 },
1272 GameInput::ZoomOut => {
1273 if state {
1274 if self.zoom_lock {
1275 self.hud.zoom_lock_reminder();
1276 } else {
1277 self.scene.handle_input_event(
1278 Event::Zoom(30.0),
1279 &self.client.borrow(),
1280 );
1281 }
1282 }
1283 },
1284 GameInput::ZoomLock => {
1285 if state {
1286 global_state.settings.gameplay.zoom_lock ^= true;
1287
1288 self.hud
1289 .zoom_lock_toggle(global_state.settings.gameplay.zoom_lock);
1290 }
1291 },
1292 GameInput::CameraClamp => {
1293 let hud = &mut self.hud;
1294 global_state.settings.gameplay.camera_clamp_behavior.update(
1295 state,
1296 &mut self.camera_clamp,
1297 |b| hud.camera_clamp(b),
1298 );
1299 },
1300 GameInput::CycleCamera if state => {
1301 let camera = self.scene.camera_mut();
1308 let client = self.client.borrow();
1309 camera.next_mode(
1310 client.is_moderator(),
1311 (client.presence() != Some(PresenceKind::Spectator))
1312 || self.viewpoint_entity.is_some(),
1313 );
1314 },
1315 GameInput::Select => {
1316 if !state {
1317 self.selected_entity =
1318 self.target_entity.map(|e| (e, std::time::Instant::now()));
1319 }
1320 },
1321 GameInput::AcceptGroupInvite if state => {
1322 let mut client = self.client.borrow_mut();
1323 if client.invite().is_some() {
1324 client.accept_invite();
1325 }
1326 },
1327 GameInput::DeclineGroupInvite if state => {
1328 let mut client = self.client.borrow_mut();
1329 if client.invite().is_some() {
1330 client.decline_invite();
1331 }
1332 },
1333 GameInput::SpectateViewpoint if state => {
1334 let mut client = self.client.borrow_mut();
1335 if self.viewpoint_entity.is_some() {
1336 client.stop_spectate_entity();
1337 self.viewpoint_entity = None;
1338 self.scene.camera_mut().set_mode(CameraMode::Freefly);
1339 let mut ori = self.scene.camera().get_orientation();
1340 ori.z = 0.0;
1343 self.scene.camera_mut().set_orientation(ori);
1344 } else if let Some(target_entity) = entity_target
1345 && self.scene.camera().get_mode() == CameraMode::Freefly
1346 {
1347 client.start_spectate_entity(target_entity.kind.0);
1350
1351 self.viewpoint_entity = Some(target_entity.kind.0);
1352 self.scene.camera_mut().set_mode(CameraMode::FirstPerson);
1353 }
1354 },
1355 GameInput::ToggleWalk if state => {
1356 global_state
1357 .settings
1358 .gameplay
1359 .walking_speed_behavior
1360 .update(state, &mut self.walking_speed, |_| {});
1361 },
1362 _ => {},
1363 }
1364 },
1365 Event::AnalogGameInput(input) => match input {
1366 AnalogGameInput::MovementX(v) => {
1367 self.key_state.analog_matrix.x = v;
1368 },
1369 AnalogGameInput::MovementY(v) => {
1370 self.key_state.analog_matrix.y = v;
1371 },
1372 other => {
1373 self.scene.handle_input_event(
1374 Event::AnalogGameInput(other),
1375 &self.client.borrow(),
1376 );
1377 },
1378 },
1379
1380 Event::ScreenshotMessage(screenshot_msg) => self
1382 .hud
1383 .new_message(ChatType::CommandInfo.into_plain_msg(screenshot_msg)),
1384
1385 Event::Zoom(delta) if self.zoom_lock => {
1386 if delta.abs() > ZOOM_LOCK_SCROLL_DELTA_INTENT {
1388 self.hud.zoom_lock_reminder();
1389 }
1390 },
1391
1392 event => {
1394 if let Event::Zoom(delta) = &event {
1395 global_state.profile.tutorial.event_zoom(*delta);
1396 }
1397
1398 self.scene.handle_input_event(event, &self.client.borrow());
1399 }, }
1401 }
1402
1403 if let Some(tgt) = self.hud.current_dialogue() {
1405 let mut client = self.client.borrow_mut();
1406 client.do_talk(Some(tgt));
1407 if matches!(
1409 self.scene.camera().get_mode(),
1410 camera::CameraMode::FirstPerson
1411 ) && let Some(activity) = client
1412 .state()
1413 .read_storage::<CharacterActivity>()
1414 .get(client.entity())
1415 && let Some(dir) = activity.look_dir
1416 {
1417 let ori = Vec3::new(dir.x.atan2(dir.y), -dir.z.atan(), 0.0);
1418 self.scene.camera_mut().lerp_toward(ori, dt, 2.5);
1419 }
1420 }
1421
1422 if let Some(viewpoint_entity) = self.viewpoint_entity
1423 && !self
1424 .client
1425 .borrow()
1426 .state()
1427 .ecs()
1428 .read_storage::<Pos>()
1429 .contains(viewpoint_entity)
1430 {
1431 self.client.borrow_mut().stop_spectate_entity();
1432 self.viewpoint_entity = None;
1433 self.scene.camera_mut().set_mode(CameraMode::Freefly);
1434 }
1435
1436 let (viewpoint_entity, mutable_viewpoint) = self.viewpoint_entity();
1437
1438 let input_vec = self.key_state.dir_vec();
1440 if input_vec.magnitude_squared() > 0.5f32.powi(2) {
1441 global_state.profile.tutorial.event_move()
1442 }
1443 let (axis_right, axis_up) = (input_vec[0], input_vec[1]);
1444
1445 if let Some(ref mut timer) = self.key_state.give_up {
1446 *timer += dt;
1447
1448 if *timer > crate::key_state::GIVE_UP_HOLD_TIME {
1449 self.client.borrow_mut().give_up();
1450 }
1451 }
1452
1453 if mutable_viewpoint {
1454 if let Some(dir) = self
1456 .auto_walk
1457 .then_some(self.client.borrow())
1458 .filter(|client| client.is_gliding())
1459 .and_then(|client| {
1460 let ecs = client.state().ecs();
1461 let entity = client.entity();
1462 let fluid = ecs
1463 .read_storage::<comp::PhysicsState>()
1464 .get(entity)?
1465 .in_fluid?;
1466 let vel = *ecs.read_storage::<Vel>().get(entity)?;
1467 let free_look = self.free_look;
1468 let dir_forward_xy = self.scene.camera().forward_xy();
1469 let dir_right = self.scene.camera().right();
1470
1471 auto_glide(fluid, vel, free_look, dir_forward_xy, dir_right)
1472 })
1473 {
1474 self.key_state.auto_walk = false;
1475 self.inputs.move_dir = Vec2::zero();
1476 self.inputs.look_dir = dir;
1477 } else {
1478 self.key_state.auto_walk = self.auto_walk;
1479 if !self.free_look {
1480 self.walk_forward_dir = self.scene.camera().forward_xy();
1481 self.walk_right_dir = self.scene.camera().right_xy();
1482
1483 let client = self.client.borrow();
1484
1485 let holding_ranged = client
1486 .inventories()
1487 .get(player_entity)
1488 .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
1489 .and_then(|item| item.tool_info())
1490 .is_some_and(|tool_kind| {
1491 matches!(
1492 tool_kind,
1493 ToolKind::Bow
1494 | ToolKind::Staff
1495 | ToolKind::Sceptre
1496 | ToolKind::Throwable
1497 )
1498 })
1499 || client
1500 .current::<CharacterState>()
1501 .is_some_and(|char_state| {
1502 matches!(char_state, CharacterState::Throw(_))
1503 });
1504
1505 let dir = if is_aiming
1506 && holding_ranged
1507 && self.scene.camera().get_mode() == CameraMode::ThirdPerson
1508 {
1509 let ray_start = self.scene.camera().get_focus_pos();
1514 let entity_ray_end = ray_start + cam_dir * 1000.0;
1515 let terrain_ray_end = ray_start + cam_dir * 1000.0;
1516
1517 let aim_point = {
1518 let entity_dist =
1520 ray_entities(&client, ray_start, entity_ray_end, 1000.0).0;
1521 let terrain_ray_distance = client
1522 .state()
1523 .terrain()
1524 .ray(ray_start, terrain_ray_end)
1525 .max_iter(1000)
1526 .until(Block::is_solid)
1527 .cast()
1528 .0;
1529
1530 ray_start + cam_dir * entity_dist.min(terrain_ray_distance)
1532 };
1533
1534 let ori = client
1536 .state()
1537 .read_storage::<comp::Ori>()
1538 .get(player_entity)
1539 .copied()
1540 .unwrap();
1541 let scale = client
1543 .state()
1544 .read_storage::<comp::Scale>()
1545 .get(player_entity)
1546 .copied()
1547 .unwrap_or(comp::Scale(1.0));
1548 let body = client
1550 .state()
1551 .read_storage::<comp::Body>()
1552 .get(player_entity)
1553 .copied()
1554 .unwrap();
1555 let body_offsets = body.projectile_offsets(ori.look_vec(), scale.0);
1556
1557 let player_pos = client
1559 .state()
1560 .read_storage::<Pos>()
1561 .get(player_entity)
1562 .copied()
1563 .unwrap();
1564
1565 drop(client);
1566 aim_point - (player_pos.0 + body_offsets)
1567 } else {
1568 cam_dir + aim_dir_offset
1569 };
1570
1571 self.inputs.look_dir = Dir::from_unnormalized(dir).unwrap();
1572 }
1573 }
1574 self.inputs.strafing = matches!(
1575 self.scene.camera().get_mode(),
1576 camera::CameraMode::FirstPerson
1577 );
1578
1579 if global_state.settings.gameplay.auto_camera
1581 && matches!(
1582 self.scene.camera().get_mode(),
1583 camera::CameraMode::ThirdPerson | camera::CameraMode::FirstPerson
1584 )
1585 && input_vec.magnitude_squared() > 0.0
1586 {
1587 let camera = self.scene.camera_mut();
1588 let ori = camera.get_orientation();
1589 camera.set_orientation_instant(Vec3::new(
1590 ori.x
1591 + input_vec.x
1592 * (3.0 - input_vec.y * 1.5 * if is_aiming { 1.5 } else { 1.0 })
1593 * dt,
1594 std::f32::consts::PI * if is_aiming { 0.015 } else { 0.1 },
1595 0.0,
1596 ));
1597 }
1598
1599 self.inputs.move_z =
1600 self.key_state.swim_up as i32 as f32 - self.key_state.swim_down as i32 as f32;
1601 }
1602
1603 match self.scene.camera().get_mode() {
1604 CameraMode::FirstPerson | CameraMode::ThirdPerson => {
1605 if mutable_viewpoint {
1606 self.inputs.move_dir =
1610 self.walk_right_dir * axis_right + self.walk_forward_dir * axis_up;
1611 }
1612 },
1613 CameraMode::Freefly => {
1614 const FREEFLY_SPEED: f32 = 50.0;
1617 const FREEFLY_SPEED_BOOST: f32 = 5.0;
1618
1619 let forward = self.scene.camera().forward().with_z(0.0).normalized();
1620 let right = self.scene.camera().right().with_z(0.0).normalized();
1621 let up = Vec3::unit_z();
1622 let up_axis = self.key_state.swim_up as i32 as f32
1623 - self.key_state.swim_down as i32 as f32;
1624
1625 let dir = (right * axis_right + forward * axis_up + up * up_axis).normalized();
1626
1627 let speed = FREEFLY_SPEED
1628 * if self.inputs_state.contains(&GameInput::SpectateSpeedBoost) {
1629 FREEFLY_SPEED_BOOST
1630 } else {
1631 1.0
1632 };
1633
1634 let pos = self.scene.camera().get_focus_pos();
1635 self.scene
1636 .camera_mut()
1637 .set_focus_pos(pos + dir * dt * speed);
1638
1639 self.inputs.move_dir = Vec2::zero();
1641 },
1642 };
1643
1644 let mut outcomes = Vec::new();
1645
1646 if !global_state.paused() {
1648 match self.tick(
1650 global_state.clock.get_stable_dt(),
1651 global_state,
1652 &mut outcomes,
1653 ) {
1654 Ok(TickAction::Continue) => {}, Ok(TickAction::Disconnect) => return PlayStateResult::Pop, Err(Error::ClientError(error)) => {
1657 error!("[session] Failed to tick the scene: {:?}", error);
1658 global_state.info_message =
1659 Some(get_client_msg_error(error, None, &global_state.i18n.read()));
1660
1661 return PlayStateResult::Pop;
1662 },
1663 Err(err) => {
1664 global_state.info_message = Some(
1665 global_state
1666 .i18n
1667 .read()
1668 .get_msg("common-connection_lost")
1669 .into_owned(),
1670 );
1671 error!("[session] Failed to tick the scene: {:?}", err);
1672
1673 return PlayStateResult::Pop;
1674 },
1675 }
1676 }
1677
1678 if self.walking_speed {
1679 self.key_state.speed_mul = global_state.settings.gameplay.walking_speed;
1680 } else {
1681 self.key_state.speed_mul = 1.0;
1682 }
1683
1684 self.scene
1686 .camera_mut()
1687 .compute_dependents(&self.client.borrow().state().terrain());
1688
1689 let debug_info = global_state.settings.interface.toggle_debug.then(|| {
1693 let client = self.client.borrow();
1694 let ecs = client.state().ecs();
1695 let client_entity = client.entity();
1696 let coordinates = ecs.read_storage::<Pos>().get(viewpoint_entity).cloned();
1697 let velocity = ecs.read_storage::<Vel>().get(viewpoint_entity).cloned();
1698 let ori = ecs
1699 .read_storage::<comp::Ori>()
1700 .get(viewpoint_entity)
1701 .cloned();
1702 let look_dir = if viewpoint_entity == client_entity {
1705 self.inputs.look_dir
1706 } else {
1707 ecs.read_storage::<comp::Controller>()
1708 .get(viewpoint_entity)
1709 .map(|c| c.inputs.look_dir)
1710 .unwrap_or_default()
1711 };
1712 let in_fluid = ecs
1713 .read_storage::<comp::PhysicsState>()
1714 .get(viewpoint_entity)
1715 .and_then(|state| state.in_fluid);
1716 let character_state = ecs
1717 .read_storage::<comp::CharacterState>()
1718 .get(viewpoint_entity)
1719 .cloned();
1720
1721 DebugInfo {
1722 tps: global_state.clock.stats().average_tps,
1723 frame_time: global_state.clock.stats().average_busy_dt,
1724 ping_ms: self.client.borrow().get_ping_ms_rolling_avg(),
1725 coordinates,
1726 velocity,
1727 ori,
1728 look_dir,
1729 character_state,
1730 in_fluid,
1731 num_chunks: self.scene.terrain().chunk_count() as u32,
1732 num_lights: self.scene.lights().len() as u32,
1733 num_visible_chunks: self.scene.terrain().visible_chunk_count() as u32,
1734 num_shadow_chunks: self.scene.terrain().shadow_chunk_count() as u32,
1735 num_figures: self.scene.figure_mgr().figure_count() as u32,
1736 num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32,
1737 num_particles: self.scene.particle_mgr().particle_count() as u32,
1738 num_particles_visible: self.scene.particle_mgr().particle_count_visible()
1739 as u32,
1740 current_track: self.scene.music_mgr().current_track(),
1741 current_artist: self.scene.music_mgr().current_artist(),
1742 active_channels: global_state.audio.get_num_active_channels(),
1743 audio_cpu_usage: global_state.audio.get_cpu_usage(),
1744 }
1745 });
1746
1747 let inverted_interactable_map = self.interactables.inverted_map();
1748
1749 let mut hud_events = self.hud.maintain(
1751 &self.client.borrow(),
1752 global_state,
1753 &debug_info,
1754 self.scene.camera(),
1755 global_state.clock.get_stable_dt(),
1756 HudInfo {
1757 is_aiming,
1758 active_mine_tool,
1759 is_first_person: matches!(
1760 self.scene.camera().get_mode(),
1761 camera::CameraMode::FirstPerson
1762 ),
1763 viewpoint_entity,
1764 mutable_viewpoint,
1765 target_entity: self.target_entity,
1766 selected_entity: self.selected_entity,
1767 persistence_load_error: self.metadata.skill_set_persistence_load_error,
1768 key_state: &self.key_state,
1769 },
1770 inverted_interactable_map,
1771 );
1772
1773 #[cfg(feature = "egui-ui")]
1775 if global_state.settings.interface.egui_enabled() {
1776 let settings_change = global_state.egui_state.maintain(
1777 &mut self.client.borrow_mut(),
1778 &mut self.scene,
1779 global_state.window.window(),
1780 debug_info.map(|debug_info| EguiDebugInfo {
1781 frame_time: debug_info.frame_time,
1782 ping_ms: debug_info.ping_ms,
1783 }),
1784 &global_state.settings,
1785 );
1786
1787 if let Some(settings_change) = settings_change {
1788 settings_change.process(global_state, self);
1789 }
1790 }
1791
1792 if global_state.i18n.reloaded() {
1794 hud_events.push(HudEvent::SettingsChange(
1795 ChangeLanguage(Box::new(global_state.i18n.read().metadata().clone())).into(),
1796 ));
1797 }
1798
1799 let mut has_repaired = false;
1800 let sfx_triggers = self.scene.sfx_mgr.triggers.read();
1801 for event in hud_events {
1803 match event {
1804 HudEvent::SendMessage(msg) => {
1805 self.client.borrow_mut().send_chat(msg);
1807 },
1808 HudEvent::SendCommand(name, args) => {
1809 match run_command(self, global_state, &name, args) {
1810 Ok(Some(info)) => {
1811 self.hud.new_message(ChatType::CommandInfo.into_msg(info))
1812 },
1813 Ok(None) => {}, Err(error) => {
1815 self.hud.new_message(ChatType::CommandError.into_msg(error))
1816 },
1817 };
1818 },
1819 HudEvent::CharacterSelection => {
1820 global_state.audio.stop_all_music();
1821 global_state.audio.stop_all_ambience();
1822 global_state.audio.stop_all_sfx();
1823 self.client.borrow_mut().request_remove_character()
1824 },
1825 HudEvent::Logout => {
1826 self.client.borrow_mut().logout();
1827 global_state.audio.stop_all_ambience();
1831 global_state.audio.stop_all_sfx();
1832 return PlayStateResult::Pop;
1833 },
1834 HudEvent::Quit => {
1835 return PlayStateResult::Shutdown;
1836 },
1837
1838 HudEvent::RemoveBuff(buff_id) => {
1839 self.client.borrow_mut().remove_buff(buff_id);
1840 },
1841 HudEvent::LeaveStance => self.client.borrow_mut().leave_stance(),
1842 HudEvent::UnlockSkill(skill) => {
1843 self.client.borrow_mut().unlock_skill(skill);
1844 },
1845 HudEvent::UseSlot {
1846 slot,
1847 bypass_dialog,
1848 } => {
1849 let mut move_allowed = true;
1850
1851 if !bypass_dialog
1852 && let Some(inventory) = self
1853 .client
1854 .borrow()
1855 .state()
1856 .ecs()
1857 .read_storage::<comp::Inventory>()
1858 .get(self.client.borrow().entity())
1859 {
1860 match slot {
1861 Slot::Inventory(inv_slot) => {
1862 let slot_deficit = inventory.free_after_equip(inv_slot);
1863 if slot_deficit < 0 {
1864 self.hud.set_prompt_dialog(PromptDialogSettings::new(
1865 global_state.i18n.read().get_content(
1866 &Content::localized_with_args(
1867 "hud-bag-use_slot_equip_drop_items",
1868 [(
1869 "slot_deficit",
1870 slot_deficit.unsigned_abs() as u64,
1871 )],
1872 ),
1873 ),
1874 HudEvent::UseSlot {
1875 slot,
1876 bypass_dialog: true,
1877 },
1878 None,
1879 ));
1880 move_allowed = false;
1881 }
1882 },
1883 Slot::Equip(equip_slot) => {
1884 let free_slots =
1887 inventory.free_slots_minus_equipped_item(equip_slot);
1888 if free_slots > 0 {
1889 let slot_deficit = inventory.free_after_unequip(equip_slot);
1890 if slot_deficit < 0 {
1891 self.hud.set_prompt_dialog(PromptDialogSettings::new(
1892 global_state.i18n.read().get_content(
1893 &Content::localized_with_args(
1894 "hud-bag-use_slot_unequip_drop_items",
1895 [(
1896 "slot_deficit",
1897 slot_deficit.unsigned_abs() as u64,
1898 )],
1899 ),
1900 ),
1901 HudEvent::UseSlot {
1902 slot,
1903 bypass_dialog: true,
1904 },
1905 None,
1906 ));
1907 move_allowed = false;
1908 }
1909 } else {
1910 move_allowed = false;
1911 }
1912 },
1913 Slot::Overflow(_) => {},
1914 }
1915 };
1916
1917 if move_allowed {
1918 self.client.borrow_mut().use_slot(slot);
1919 }
1920 },
1921 HudEvent::SwapEquippedWeapons => {
1922 self.client.borrow_mut().swap_loadout();
1923 },
1924 HudEvent::SwapSlots {
1925 slot_a,
1926 slot_b,
1927 bypass_dialog,
1928 } => {
1929 let mut move_allowed = true;
1930 if !bypass_dialog
1931 && let Some(inventory) = self
1932 .client
1933 .borrow()
1934 .state()
1935 .ecs()
1936 .read_storage::<comp::Inventory>()
1937 .get(self.client.borrow().entity())
1938 {
1939 match (slot_a, slot_b) {
1940 (Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
1941 | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
1942 if !inventory.can_swap(inv_slot, equip_slot) {
1943 move_allowed = false;
1944 } else {
1945 let slot_deficit =
1946 inventory.free_after_swap(equip_slot, inv_slot);
1947 if slot_deficit < 0 {
1948 self.hud.set_prompt_dialog(PromptDialogSettings::new(
1949 global_state.i18n.read().get_content(
1950 &Content::localized_with_args(
1951 "hud-bag-swap_slots_drop_items",
1952 [(
1953 "slot_deficit",
1954 slot_deficit.unsigned_abs() as u64,
1955 )],
1956 ),
1957 ),
1958 HudEvent::SwapSlots {
1959 slot_a,
1960 slot_b,
1961 bypass_dialog: true,
1962 },
1963 None,
1964 ));
1965 move_allowed = false;
1966 }
1967 }
1968 },
1969 _ => {},
1970 }
1971 }
1972 if move_allowed {
1973 self.client.borrow_mut().swap_slots(slot_a, slot_b);
1974 }
1975 },
1976 HudEvent::SelectExpBar(skillgroup) => {
1977 global_state.settings.interface.xp_bar_skillgroup = skillgroup;
1978 },
1979 HudEvent::SplitSwapSlots {
1980 slot_a,
1981 slot_b,
1982 bypass_dialog,
1983 } => {
1984 let mut move_allowed = true;
1985 if !bypass_dialog
1986 && let Some(inventory) = self
1987 .client
1988 .borrow()
1989 .state()
1990 .ecs()
1991 .read_storage::<comp::Inventory>()
1992 .get(self.client.borrow().entity())
1993 {
1994 match (slot_a, slot_b) {
1995 (Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
1996 | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
1997 if !inventory.can_swap(inv_slot, equip_slot) {
1998 move_allowed = false;
1999 } else {
2000 let slot_deficit =
2001 inventory.free_after_swap(equip_slot, inv_slot);
2002 if slot_deficit < 0 {
2003 self.hud.set_prompt_dialog(PromptDialogSettings::new(
2004 global_state.i18n.read().get_content(
2005 &Content::localized_with_args(
2006 "hud-bag-split_swap_slots_drop_items",
2007 [(
2008 "slot_deficit",
2009 slot_deficit.unsigned_abs() as u64,
2010 )],
2011 ),
2012 ),
2013 HudEvent::SwapSlots {
2014 slot_a,
2015 slot_b,
2016 bypass_dialog: true,
2017 },
2018 None,
2019 ));
2020 move_allowed = false;
2021 }
2022 }
2023 },
2024 _ => {},
2025 }
2026 };
2027 if move_allowed {
2028 self.client.borrow_mut().split_swap_slots(slot_a, slot_b);
2029 }
2030 },
2031 HudEvent::DropSlot(x) => {
2032 let mut client = self.client.borrow_mut();
2033 client.drop_slot(x);
2034 if let Slot::Equip(EquipSlot::Lantern) = x {
2035 client.disable_lantern();
2036 }
2037 },
2038 HudEvent::SplitDropSlot(x) => {
2039 let mut client = self.client.borrow_mut();
2040 client.split_drop_slot(x);
2041 if let Slot::Equip(EquipSlot::Lantern) = x {
2042 client.disable_lantern();
2043 }
2044 },
2045 HudEvent::SortInventory(sort_order) => {
2046 self.client.borrow_mut().sort_inventory(sort_order);
2047 },
2048 HudEvent::ChangeHotbarState(state) => {
2049 let client = self.client.borrow();
2050
2051 let server_name = &client.server_info().name;
2052 let character_id = match client.presence().unwrap() {
2054 PresenceKind::Character(id) => Some(id),
2055 PresenceKind::LoadingCharacter(id) => Some(id),
2056 PresenceKind::Spectator => {
2057 unreachable!("HUD adaption in Spectator mode!")
2058 },
2059 PresenceKind::Possessor => None,
2060 };
2061
2062 global_state.profile.set_hotbar_slots(
2064 server_name,
2065 character_id,
2066 state.slots,
2067 );
2068
2069 global_state
2070 .profile
2071 .save_to_file_warn(&global_state.config_dir);
2072
2073 info!("Event! -> ChangedHotbarState")
2074 },
2075 HudEvent::TradeAction(action) => {
2076 self.client.borrow_mut().perform_trade_action(action);
2077 },
2078 HudEvent::Ability { idx, state } => {
2079 self.client.borrow_mut().handle_input(
2080 InputKind::Ability(idx),
2081 state,
2082 default_select_pos,
2083 self.target_entity,
2084 );
2085 },
2086
2087 HudEvent::RequestSiteInfo(id) => {
2088 self.client.borrow_mut().request_site_economy(id);
2089 },
2090
2091 HudEvent::CraftRecipe {
2092 recipe_name: recipe,
2093 craft_sprite,
2094 amount,
2095 } => {
2096 let slots = {
2097 let client = self.client.borrow();
2098
2099 if let Some(inventory) = client
2100 .state()
2101 .ecs()
2102 .read_storage::<comp::Inventory>()
2103 .get(client.entity())
2104 {
2105 let rbm =
2106 client.state().ecs().read_resource::<RecipeBookManifest>();
2107 if let Some(recipe) = inventory.get_recipe(&recipe, &rbm) {
2108 recipe.inventory_contains_ingredients(inventory, 1).ok()
2109 } else {
2110 None
2111 }
2112 } else {
2113 None
2114 }
2115 };
2116 if let Some(slots) = slots {
2117 self.client.borrow_mut().craft_recipe(
2118 &recipe,
2119 slots,
2120 craft_sprite,
2121 amount,
2122 );
2123 }
2124 },
2125
2126 HudEvent::CraftModularWeapon {
2127 primary_slot,
2128 secondary_slot,
2129 craft_sprite,
2130 } => {
2131 self.client.borrow_mut().craft_modular_weapon(
2132 primary_slot,
2133 secondary_slot,
2134 craft_sprite,
2135 );
2136 },
2137
2138 HudEvent::CraftModularWeaponComponent {
2139 toolkind,
2140 material,
2141 modifier,
2142 craft_sprite,
2143 } => {
2144 let additional_slots = {
2145 let client = self.client.borrow();
2146 let item_id = |slot| {
2147 client
2148 .inventories()
2149 .get(client.entity())
2150 .and_then(|inv| inv.get(slot))
2151 .and_then(|item| {
2152 item.item_definition_id().itemdef_id().map(String::from)
2153 })
2154 };
2155 if let Some(material_id) = item_id(material) {
2156 let key = recipe::ComponentKey {
2157 toolkind,
2158 material: material_id,
2159 modifier: modifier.and_then(item_id),
2160 };
2161 if let Some(recipe) = client.component_recipe_book().get(&key) {
2162 client.inventories().get(client.entity()).and_then(|inv| {
2163 recipe.inventory_contains_additional_ingredients(inv).ok()
2164 })
2165 } else {
2166 None
2167 }
2168 } else {
2169 None
2170 }
2171 };
2172 if let Some(additional_slots) = additional_slots {
2173 self.client.borrow_mut().craft_modular_weapon_component(
2174 toolkind,
2175 material,
2176 modifier,
2177 additional_slots,
2178 craft_sprite,
2179 );
2180 }
2181 },
2182 HudEvent::SalvageItem { slot, salvage_pos } => {
2183 self.client.borrow_mut().salvage_item(slot, salvage_pos);
2184 },
2185 HudEvent::RepairItem { item, sprite_pos } => {
2186 let slots = {
2187 let client = self.client.borrow();
2188 let slots = (|| {
2189 if let Some(inventory) = client.inventories().get(client.entity()) {
2190 let item = match item {
2191 Slot::Equip(slot) => inventory.equipped(slot),
2192 Slot::Inventory(slot) => inventory.get(slot),
2193 Slot::Overflow(_) => None,
2194 }?;
2195 let repair_recipe =
2196 client.repair_recipe_book().repair_recipe(item)?;
2197 repair_recipe
2198 .inventory_contains_ingredients(item, inventory)
2199 .ok()
2200 } else {
2201 None
2202 }
2203 })();
2204 slots.unwrap_or_default()
2205 };
2206 if !has_repaired {
2207 let sfx_trigger_item = sfx_triggers
2208 .0
2209 .get_key_value(&SfxEvent::from(&InventoryUpdateEvent::Craft));
2210 global_state.audio.emit_ui_sfx(sfx_trigger_item, None, None);
2211 has_repaired = true
2212 };
2213 self.client
2214 .borrow_mut()
2215 .repair_item(item, slots, sprite_pos);
2216 },
2217 HudEvent::InviteMember(uid) => {
2218 self.client.borrow_mut().send_invite(uid, InviteKind::Group);
2219 },
2220 HudEvent::AcceptInvite => {
2221 self.client.borrow_mut().accept_invite();
2222 },
2223 HudEvent::DeclineInvite => {
2224 self.client.borrow_mut().decline_invite();
2225 },
2226 HudEvent::KickMember(uid) => {
2227 self.client.borrow_mut().kick_from_group(uid);
2228 },
2229 HudEvent::LeaveGroup => {
2230 self.client.borrow_mut().leave_group();
2231 },
2232 HudEvent::AssignLeader(uid) => {
2233 self.client.borrow_mut().assign_group_leader(uid);
2234 },
2235 HudEvent::ChangeAbility(slot, new_ability) => {
2236 self.client.borrow_mut().change_ability(slot, new_ability);
2237 },
2238 HudEvent::SettingsChange(settings_change) => {
2239 settings_change.process(global_state, self);
2240 },
2241 HudEvent::AcknowledgePersistenceLoadError => {
2242 self.metadata.skill_set_persistence_load_error = None;
2243 },
2244 HudEvent::MapMarkerEvent(event) => {
2245 self.client.borrow_mut().map_marker_event(event);
2246 },
2247 HudEvent::Dialogue(target, dialogue) => {
2248 self.client.borrow_mut().perform_dialogue(target, dialogue);
2249 },
2250 HudEvent::SetBattleMode(mode) => {
2251 self.client.borrow_mut().set_battle_mode(mode);
2252 },
2253 }
2254 }
2255
2256 {
2257 let client = self.client.borrow();
2258 let scene_data = SceneData {
2259 client: &client,
2260 state: client.state(),
2261 viewpoint_entity,
2262 mutable_viewpoint: mutable_viewpoint || self.free_look,
2263 target_entities: &self.interactables.entities,
2265 loaded_distance: client.loaded_distance(),
2266 terrain_view_distance: client.view_distance().unwrap_or(1),
2267 entity_view_distance: client
2268 .view_distance()
2269 .unwrap_or(1)
2270 .min(global_state.settings.graphics.entity_view_distance),
2271 tick: client.get_tick(),
2272 gamma: global_state.settings.graphics.gamma,
2273 exposure: global_state.settings.graphics.exposure,
2274 ambiance: global_state.settings.graphics.ambiance,
2275 mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable,
2276 sprite_render_distance: global_state.settings.graphics.sprite_render_distance
2277 as f32,
2278 particles_enabled: global_state.settings.graphics.particles_enabled,
2279 weapon_trails_enabled: global_state.settings.graphics.weapon_trails_enabled,
2280 flashing_lights_enabled: global_state
2281 .settings
2282 .graphics
2283 .render_mode
2284 .flashing_lights_enabled,
2285 figure_lod_render_distance: global_state
2286 .settings
2287 .graphics
2288 .figure_lod_render_distance
2289 as f32,
2290 is_aiming,
2291 interpolated_time_of_day: self.scene.interpolated_time_of_day,
2292 wind_vel: self.scene.wind_vel,
2293 };
2294
2295 if !global_state.paused() {
2297 self.scene.maintain(
2298 global_state.window.renderer_mut(),
2299 &mut global_state.audio,
2300 &scene_data,
2301 &client,
2302 &global_state.settings,
2303 );
2304
2305 for outcome in outcomes {
2307 self.scene
2308 .handle_outcome(&outcome, &scene_data, &mut global_state.audio);
2309 self.hud.handle_outcome(&outcome, &scene_data, global_state);
2310 }
2311 }
2312 }
2313
2314 self.cleanup();
2316
2317 PlayStateResult::Continue
2318 } else if client_registered && client_presence.is_none() {
2319 if client_type.can_spectate() && !client_type.can_enter_character() {
2322 PlayStateResult::Pop
2324 } else {
2325 PlayStateResult::Switch(Box::new(CharSelectionState::new(
2326 global_state,
2327 Rc::clone(&self.client),
2328 Rc::clone(&self.hud.persisted_state),
2329 )))
2330 }
2331 } else {
2332 error!("Client not in the expected state, exiting session play state");
2333 PlayStateResult::Pop
2334 }
2335 }
2336
2337 fn name(&self) -> &'static str { "Session" }
2338
2339 fn capped_fps(&self) -> bool { false }
2340
2341 fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
2342
2343 fn render(&self, drawer: &mut Drawer<'_>, settings: &Settings) {
2347 span!(_guard, "render", "<Session as PlayState>::render");
2348
2349 let client = self.client.borrow();
2350
2351 let (viewpoint_entity, mutable_viewpoint) = self.viewpoint_entity();
2352
2353 let scene_data = SceneData {
2354 client: &client,
2355 state: client.state(),
2356 viewpoint_entity,
2357 mutable_viewpoint,
2358 target_entities: &self.interactables.entities,
2360 loaded_distance: client.loaded_distance(),
2361 terrain_view_distance: client.view_distance().unwrap_or(1),
2362 entity_view_distance: client
2363 .view_distance()
2364 .unwrap_or(1)
2365 .min(settings.graphics.entity_view_distance),
2366 tick: client.get_tick(),
2367 gamma: settings.graphics.gamma,
2368 exposure: settings.graphics.exposure,
2369 ambiance: settings.graphics.ambiance,
2370 mouse_smoothing: settings.gameplay.smooth_pan_enable,
2371 sprite_render_distance: settings.graphics.sprite_render_distance as f32,
2372 figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32,
2373 particles_enabled: settings.graphics.particles_enabled,
2374 weapon_trails_enabled: settings.graphics.weapon_trails_enabled,
2375 flashing_lights_enabled: settings.graphics.render_mode.flashing_lights_enabled,
2376 is_aiming: self.is_aiming,
2377 interpolated_time_of_day: self.scene.interpolated_time_of_day,
2378 wind_vel: self.scene.wind_vel,
2379 };
2380
2381 self.scene.render(
2383 drawer,
2384 client.state(),
2385 viewpoint_entity,
2386 client.get_tick(),
2387 &scene_data,
2388 );
2389
2390 if let Some(mut volumetric_pass) = drawer.volumetric_pass() {
2391 prof_span!("clouds");
2393 volumetric_pass.draw_clouds();
2394 }
2395 if let Some(mut transparent_pass) = drawer.transparent_pass() {
2396 prof_span!("trails");
2398 if let Some(mut trail_drawer) = transparent_pass.draw_trails() {
2399 self.scene
2400 .trail_mgr()
2401 .render(&mut trail_drawer, &scene_data);
2402 }
2403 }
2404 {
2406 prof_span!("bloom");
2407 drawer.run_bloom_passes()
2408 }
2409 {
2411 prof_span!("post-process and ui");
2412 let mut third_pass = drawer.third_pass();
2413 third_pass.draw_postprocess();
2414 if let Some(mut ui_drawer) = third_pass.draw_ui() {
2416 self.hud.render(&mut ui_drawer);
2417 }; }
2420 }
2421
2422 fn egui_enabled(&self) -> bool { true }
2423}
2424
2425fn find_shortest_distance(arr: &[Option<f32>]) -> Option<f32> {
2426 arr.iter()
2427 .filter_map(|x| *x)
2428 .min_by(|d1, d2| OrderedFloat(*d1).cmp(&OrderedFloat(*d2)))
2429}
2430
2431fn auto_glide(
2433 fluid: Fluid,
2434 vel: Vel,
2435 free_look: bool,
2436 dir_forward_xy: Vec2<f32>,
2437 dir_right: Vec3<f32>,
2438) -> Option<Dir> {
2439 let Vel(rel_flow) = fluid.relative_flow(&vel);
2440
2441 let is_wind_downwards = rel_flow.z.is_sign_negative();
2442
2443 let dir = if free_look {
2444 if is_wind_downwards {
2445 Vec3::from(-rel_flow.xy())
2446 } else {
2447 -rel_flow
2448 }
2449 } else if is_wind_downwards {
2450 dir_forward_xy.into()
2451 } else {
2452 let windwards = rel_flow * dir_forward_xy.dot(rel_flow.xy()).signum();
2453 Plane::from(Dir::new(dir_right)).projection(windwards)
2454 };
2455
2456 Dir::from_unnormalized(dir)
2457}