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