diff --git a/assets/voxygen/i18n/en/common.ftl b/assets/voxygen/i18n/en/common.ftl index 23b0a68b95..337d9ca0d3 100644 --- a/assets/voxygen/i18n/en/common.ftl +++ b/assets/voxygen/i18n/en/common.ftl @@ -112,3 +112,4 @@ common-material-stone = Stone common-material-cloth = Cloth common-material-hide = Hide common-sprite-chest = Chest +common-sprite-chair = Chair diff --git a/client/src/lib.rs b/client/src/lib.rs index af7ca5e7cb..9bada06935 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1530,7 +1530,7 @@ impl Client { .ecs() .read_storage::() .get(self.entity()) - .map(|cs| matches!(cs, CharacterState::Sit)); + .map(|cs| matches!(cs, CharacterState::Sit | CharacterState::MountSprite(_))); match is_sitting { Some(true) => self.control_action(ControlAction::Stand), @@ -1539,6 +1539,21 @@ impl Client { } } + pub fn stand_if_mounted(&mut self) { + let is_sitting = self + .state + .ecs() + .read_storage::() + .get(self.entity()) + .map(|cs| matches!(cs, CharacterState::MountSprite(_))); + + match is_sitting { + Some(true) => self.control_action(ControlAction::Stand), + Some(false) => {}, + None => warn!("Can't stand, client entity doesn't have a `CharacterState`"), + } + } + pub fn toggle_dance(&mut self) { let is_dancing = self .state @@ -1722,6 +1737,10 @@ impl Client { ))); } + pub fn mount_sprite(&mut self, pos: Vec3) { + self.control_action(ControlAction::MountSprite(pos)); + } + pub fn change_ability(&mut self, slot: usize, new_ability: comp::ability::AuxiliaryAbility) { let auxiliary_key = self .inventories() diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 3ff759d6d4..38675ba8b5 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -470,6 +470,7 @@ impl From<&CharacterState> for CharacterAbilityType { | CharacterState::SpriteSummon(_) | CharacterState::UseItem(_) | CharacterState::SpriteInteract(_) + | CharacterState::MountSprite(_) | CharacterState::Skate(_) | CharacterState::Wallrun(_) => Self::Other, } diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index e914d4b32b..2d78018a50 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -130,6 +130,8 @@ pub enum CharacterState { /// Handles logic for interacting with a sprite, e.g. using a chest or /// picking a plant SpriteInteract(sprite_interact::Data), + // Mounted to a sprite + MountSprite(mount_sprite::Data), /// Runs on the wall Wallrun(wallrun::Data), /// Ice skating or skiing @@ -470,6 +472,7 @@ impl CharacterState { CharacterState::SpriteSummon(data) => data.behavior(j, output_events), CharacterState::UseItem(data) => data.behavior(j, output_events), CharacterState::SpriteInteract(data) => data.behavior(j, output_events), + CharacterState::MountSprite(data) => data.behavior(j, output_events), CharacterState::Skate(data) => data.behavior(j, output_events), CharacterState::Music(data) => data.behavior(j, output_events), CharacterState::FinisherMelee(data) => data.behavior(j, output_events), @@ -524,6 +527,7 @@ impl CharacterState { CharacterState::SpriteSummon(data) => data.handle_event(j, output_events, action), CharacterState::UseItem(data) => data.handle_event(j, output_events, action), CharacterState::SpriteInteract(data) => data.handle_event(j, output_events, action), + CharacterState::MountSprite(data) => data.handle_event(j, output_events, action), CharacterState::Skate(data) => data.handle_event(j, output_events, action), CharacterState::Music(data) => data.handle_event(j, output_events, action), CharacterState::FinisherMelee(data) => data.handle_event(j, output_events, action), @@ -578,6 +582,7 @@ impl CharacterState { CharacterState::SpriteSummon(data) => Some(data.static_data.ability_info), CharacterState::UseItem(_) => None, CharacterState::SpriteInteract(_) => None, + CharacterState::MountSprite(_) => None, CharacterState::FinisherMelee(data) => Some(data.static_data.ability_info), CharacterState::Music(data) => Some(data.static_data.ability_info), CharacterState::DiveMelee(data) => Some(data.static_data.ability_info), @@ -623,6 +628,7 @@ impl CharacterState { CharacterState::SpriteSummon(data) => Some(data.stage_section), CharacterState::UseItem(data) => Some(data.stage_section), CharacterState::SpriteInteract(data) => Some(data.stage_section), + CharacterState::MountSprite(_) => None, CharacterState::FinisherMelee(data) => Some(data.stage_section), CharacterState::Music(data) => Some(data.stage_section), CharacterState::DiveMelee(data) => Some(data.stage_section), @@ -794,6 +800,7 @@ impl CharacterState { recover: Some(data.static_data.recover_duration), ..Default::default() }), + CharacterState::MountSprite(_) => None, CharacterState::FinisherMelee(data) => Some(DurationsInfo { buildup: Some(data.static_data.buildup_duration), action: Some(data.static_data.swing_duration), @@ -862,6 +869,7 @@ impl CharacterState { CharacterState::SpriteSummon(data) => Some(data.timer), CharacterState::UseItem(data) => Some(data.timer), CharacterState::SpriteInteract(data) => Some(data.timer), + CharacterState::MountSprite(_) => None, CharacterState::FinisherMelee(data) => Some(data.timer), CharacterState::Music(data) => Some(data.timer), CharacterState::DiveMelee(data) => Some(data.timer), @@ -881,6 +889,7 @@ impl CharacterState { CharacterState::GlideWield(_) => None, CharacterState::Stunned(_) => None, CharacterState::Sit => None, + CharacterState::MountSprite(_) => None, CharacterState::Dance => None, CharacterState::BasicBlock(_) => None, CharacterState::Roll(_) => None, diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 6689c3f781..6210401ef7 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -165,6 +165,7 @@ pub enum ControlAction { GlideWield, Unwield, Sit, + MountSprite(Vec3), Dance, Sneak, Stand, diff --git a/common/src/states/behavior.rs b/common/src/states/behavior.rs index 0746a94b84..edae6eed80 100644 --- a/common/src/states/behavior.rs +++ b/common/src/states/behavior.rs @@ -59,6 +59,9 @@ pub trait CharacterBehavior { fn talk(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate { StateUpdate::from(data) } + fn mount_sprite(&self, data: &JoinData, _output_events: &mut OutputEvents, _pos: Vec3) -> StateUpdate { + StateUpdate::from(data) + } // start_input has custom implementation in the following character states that // may also need to be modified when changes are made here: ComboMelee2 fn start_input( @@ -111,6 +114,7 @@ pub trait CharacterBehavior { select_pos, } => self.start_input(data, input, target_entity, select_pos), ControlAction::CancelInput(input) => self.cancel_input(data, input), + ControlAction::MountSprite(pos) => self.mount_sprite(data, output_events, pos), } } } diff --git a/common/src/states/dance.rs b/common/src/states/dance.rs index c151f794fd..938c5897f8 100644 --- a/common/src/states/dance.rs +++ b/common/src/states/dance.rs @@ -7,6 +7,7 @@ use crate::{ }, }; use serde::{Deserialize, Serialize}; +use vek::Vec3; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] pub struct Data; @@ -50,6 +51,12 @@ impl CharacterBehavior for Data { update } + fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_mount_sprite(data, &mut update, pos); + update + } + fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); // Try to Fall/Stand up/Move diff --git a/common/src/states/glide_wield.rs b/common/src/states/glide_wield.rs index a47c151084..2da4df6e38 100644 --- a/common/src/states/glide_wield.rs +++ b/common/src/states/glide_wield.rs @@ -12,6 +12,7 @@ use crate::{ }, }; use serde::{Deserialize, Serialize}; +use vek::Vec3; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data { @@ -110,6 +111,12 @@ impl CharacterBehavior for Data { update } + fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_mount_sprite(data, &mut update, pos); + update + } + fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); attempt_dance(data, &mut update); diff --git a/common/src/states/idle.rs b/common/src/states/idle.rs index c89263f2af..faf3a6b95e 100644 --- a/common/src/states/idle.rs +++ b/common/src/states/idle.rs @@ -8,6 +8,7 @@ use crate::{ states::behavior::{CharacterBehavior, JoinData}, }; use serde::{Deserialize, Serialize}; +use vek::Vec3; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)] pub struct Data { @@ -86,6 +87,12 @@ impl CharacterBehavior for Data { update } + fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_mount_sprite(data, &mut update, pos); + update + } + fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); attempt_dance(data, &mut update); diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index 4a49159f19..e9f610c3b1 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -40,3 +40,4 @@ pub mod use_item; pub mod utils; pub mod wallrun; pub mod wielding; +pub mod mount_sprite; diff --git a/common/src/states/mount_sprite.rs b/common/src/states/mount_sprite.rs new file mode 100644 index 0000000000..3253a5e841 --- /dev/null +++ b/common/src/states/mount_sprite.rs @@ -0,0 +1,48 @@ +use serde::{Serialize, Deserialize}; +use vek::Vec3; + +use crate::{comp::{character_state::OutputEvents, StateUpdate, CharacterState}, util::Dir}; + +use super::{behavior::{CharacterBehavior, JoinData}, utils::{handle_orientation, end_ability, handle_wield}, idle}; + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + pub mount_pos: Vec3, + pub mount_dir: Vec3, + /// Position sprite is located at + pub sprite_pos: Vec3, +} + + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, +} + +impl CharacterBehavior for Data { + fn behavior(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate { + let mut update = StateUpdate::from(data); + update.pos.0 = self.static_data.mount_pos; + + handle_orientation(data, &mut update, 1.0, Some(Dir::new(self.static_data.mount_dir))); + + handle_wield(data, &mut update); + + // Try to Fall/Stand up/Move + if data.physics.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 { + update.character = CharacterState::Idle(idle::Data::default()); + } + + update + } + + fn stand(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate { + let mut update = StateUpdate::from(data); + + end_ability(data, &mut update); + + update + } +} \ No newline at end of file diff --git a/common/src/states/skate.rs b/common/src/states/skate.rs index 8d877675ad..9db55ec8e3 100644 --- a/common/src/states/skate.rs +++ b/common/src/states/skate.rs @@ -10,6 +10,7 @@ use crate::{ }, }; use serde::{Deserialize, Serialize}; +use vek::Vec3; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data { @@ -108,6 +109,12 @@ impl CharacterBehavior for Data { update } + fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_mount_sprite(data, &mut update, pos); + update + } + fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); // Try to Fall/Stand up/Move diff --git a/common/src/states/talk.rs b/common/src/states/talk.rs index bab5b59f4d..dd665c20ff 100644 --- a/common/src/states/talk.rs +++ b/common/src/states/talk.rs @@ -7,6 +7,7 @@ use crate::{ }, }; use serde::{Deserialize, Serialize}; +use vek::Vec3; const TURN_RATE: f32 = 40.0; @@ -47,6 +48,13 @@ impl CharacterBehavior for Data { update } + fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3) -> StateUpdate { + let mut update = StateUpdate::from(data); + update.character = CharacterState::Idle(idle::Data::default()); + attempt_mount_sprite(data, &mut update, pos); + update + } + fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); update.character = CharacterState::Idle(idle::Data::default()); diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 43d759d177..861135ac39 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -23,10 +23,11 @@ use crate::{ states::{behavior::JoinData, utils::CharacterState::Idle, *}, terrain::{TerrainGrid, UnlockKind}, util::Dir, - vol::ReadVol, + vol::ReadVol, terrain::SpriteKind, }; use core::hash::BuildHasherDefault; use fxhash::FxHasher64; +use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; use std::{ f32::consts::PI, @@ -784,6 +785,37 @@ pub fn attempt_sit(data: &JoinData<'_>, update: &mut StateUpdate) { } } +pub fn sprite_mount_points(sprite_kind: SpriteKind, pos: Vec3, ori: u8) -> impl ExactSizeIterator, Vec3)> { + let mat = Mat4::identity() + .rotated_z(std::f32::consts::PI * 0.25 * ori as f32) + .translated_3d( + pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0) + ); + sprite_kind.mount_offsets().iter().map(move |(pos, dir)| { + ((mat * pos.with_w(1.0)).xyz(), (mat * dir.with_w(0.0)).xyz()) + }) +} + +pub const SPRITE_MOUNT_DIST_SQR: f32 = 5.0; + +pub fn attempt_mount_sprite(data: &JoinData<'_>, update: &mut StateUpdate, pos: Vec3) { + if let Some((kind, ori)) = data.terrain.get(pos).ok().and_then(|block| block.get_sprite().zip(block.get_ori())) { + if let Some((mount_pos, mount_dir)) = sprite_mount_points(kind, pos, ori).min_by_key(|(pos, _)| { + OrderedFloat(data.pos.0.distance_squared(*pos)) + }) { + if mount_pos.distance_squared(data.pos.0) < SPRITE_MOUNT_DIST_SQR { + update.character = CharacterState::MountSprite(mount_sprite::Data { + static_data: mount_sprite::StaticData { + mount_pos, + mount_dir, + sprite_pos: pos, + }, + }); + } + } + } +} + pub fn attempt_dance(data: &JoinData<'_>, update: &mut StateUpdate) { if data.physics.on_ground.is_some() && data.body.is_humanoid() { update.character = CharacterState::Dance; diff --git a/common/src/states/wielding.rs b/common/src/states/wielding.rs index c8ad0b16f5..7c8c161ce6 100644 --- a/common/src/states/wielding.rs +++ b/common/src/states/wielding.rs @@ -11,6 +11,7 @@ use crate::{ }, }; use serde::{Deserialize, Serialize}; +use vek::Vec3; #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Data { @@ -93,6 +94,12 @@ impl CharacterBehavior for Data { update } + fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_mount_sprite(data, &mut update, pos); + update + } + fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); attempt_dance(data, &mut update); diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 9ca94ee0e6..7fadfcea64 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -420,6 +420,12 @@ impl Block { self.collectible_id().is_some() && self.mine_tool().is_none() } + #[inline] + pub fn is_mountable(&self) -> bool { + self.get_sprite() + .map_or(false, |s| s.is_mountable()) + } + #[inline] pub fn is_bonkable(&self) -> bool { match self.get_sprite() { diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index e15cbb1439..c3c446a658 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -12,6 +12,7 @@ use num_derive::FromPrimitive; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt}; use strum::EnumIter; +use vek::Vec3; make_case_elim!( sprite_kind, @@ -474,6 +475,39 @@ impl SpriteKind { matches!(self.collectible_id(), Some(Some(LootSpec::LootTable(_)))) } + /// Is the sprite a container that will emit a mystery item? + #[inline] + pub fn mount_offsets(&self) -> &'static [(Vec3, Vec3)] { + const UNIT_Y: Vec3 = Vec3 { + x: 0.0, + y: -1.0, + z: 0.0, + }; + match self { + SpriteKind::ChairSingle => &[(Vec3 { + x: 0.0, + y: 0.0, + z: 0.5, + }, UNIT_Y)], + SpriteKind::ChairDouble => &[(Vec3 { + x: -0.5, + y: 0.0, + z: 0.6, + }, UNIT_Y), + (Vec3 { + x: 0.5, + y: -0.1, + z: 0.6, + }, UNIT_Y)], + _ => &[], + } + } + + #[inline] + pub fn is_mountable(&self) -> bool { + !self.mount_offsets().is_empty() + } + /// Which tool (if any) is needed to collect this sprite? #[inline] pub fn mine_tool(&self) -> Option { diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 6bf2cf9707..21bb4f5f3f 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -366,7 +366,8 @@ impl<'a> PhysicsData<'a> { char_state_maybe, )| { let is_sticky = sticky.is_some(); - let is_immovable = immovable.is_some(); + let is_immovable = immovable.is_some() + || matches!(char_state_maybe, Some(CharacterState::MountSprite(_))); let is_mid_air = physics.on_surface().is_none(); let mut entity_entity_collision_checks = 0; let mut entity_entity_collisions = 0; @@ -621,6 +622,7 @@ impl<'a> PhysicsData<'a> { !&read.is_ridings, ) .par_join() + .filter(|tuple| !matches!(tuple.4, Some(CharacterState::MountSprite(_)))) .for_each_init( || { prof_span!(guard, "velocity update rayon job"); @@ -751,7 +753,10 @@ impl<'a> PhysicsData<'a> { !&read.is_ridings, ) .par_join() - .filter(|tuple| tuple.3.is_voxel() == terrain_like_entities) + .filter(|tuple| { + tuple.3.is_voxel() == terrain_like_entities + && !matches!(tuple.8, Some(CharacterState::MountSprite(_))) + }) .map_init( || { prof_span!(guard, "physics e<>t rayon job"); diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index c5cd629fe9..6b55c3a193 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -145,7 +145,7 @@ impl<'a> System<'a> for Sys { { match character_state { // Sitting accelerates recharging energy the most - CharacterState::Sit => { + CharacterState::Sit | CharacterState::MountSprite(_) => { if energy.needs_regen() { energy.regen(SIT_ENERGY_REGEN_ACCEL, dt); } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 95e0a9b7b0..6318b623b4 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -71,10 +71,9 @@ use crate::{ render::UiDrawer, scene::camera::{self, Camera}, session::{ - interactable::{BlockInteraction, Interactable}, settings_change::{ Audio, Chat as ChatChange, Interface as InterfaceChange, SettingsChange, - }, + }, interactable::{Interactable, BlockInteraction}, }, settings::chat::ChatFilter, ui::{ @@ -2107,10 +2106,34 @@ impl Hud { } } }, + BlockInteraction::Mount(_) => vec![( + Some(GameInput::Interact), + i18n.get_msg("hud-sit").to_string(), + )], }; // This is only done once per frame, so it's not a performance issue if let Some(desc) = block + .get_sprite() + .filter(|s| s.is_mountable()) + .and_then(|s| get_sprite_desc(s, i18n)) + { + overitem::Overitem::new( + desc, + overitem::TEXT_COLOR, + pos.distance_squared(player_pos), + &self.fonts, + i18n, + &global_state.settings.controls, + overitem_properties, + self.pulse, + &global_state.window.key_layout, + vec![(Some(GameInput::Interact), i18n.get_msg("hud-sit").to_string())], + ) + .x_y(0.0, 100.0) + .position_ingame(over_pos) + .set(overitem_id, ui_widgets); + } else if let Some(desc) = block .get_sprite() .filter(|s| s.is_container()) .and_then(|s| get_sprite_desc(s, i18n)) @@ -5130,6 +5153,7 @@ pub fn get_sprite_desc(sprite: SpriteKind, localized_strings: &Localization) -> | SpriteKind::DungeonChest3 | SpriteKind::DungeonChest4 | SpriteKind::DungeonChest5 => "common-sprite-chest", + SpriteKind::ChairSingle | SpriteKind::ChairDouble => "common-sprite-chair", sprite => return Some(Cow::Owned(format!("{:?}", sprite))), }; Some(localized_strings.get_msg(i18n_key)) diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index ba4aae6a1f..1e84172d2f 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1977,7 +1977,7 @@ impl FigureMgr { skeleton_attr, ) }, - CharacterState::Sit { .. } => { + CharacterState::Sit { .. } | CharacterState::MountSprite(_) => { anim::character::SitAnimation::update_skeleton( &target_base, (active_tool_kind, second_tool_kind, time), diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index 5b0ec09b90..d003578f79 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -1,16 +1,20 @@ use crate::hud::CraftingTab; -use common::terrain::{BlockKind, SpriteKind, TerrainChunk}; +use common::{ + states::utils::sprite_mount_points, + terrain::{BlockKind, SpriteKind, TerrainChunk}, +}; use common_base::span; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use vek::*; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Interaction { /// This covers mining, unlocking, and regular collectable things (e.g. /// twigs). Collect, Craft(CraftingTab), + Mount(Vec>), } pub enum FireplaceType { @@ -168,6 +172,21 @@ impl BlocksOfInterest { Some(SpriteKind::RepairBench) => { interactables.push((pos, Interaction::Craft(CraftingTab::All))) }, + Some( + sprite_kind @ SpriteKind::ChairSingle + | sprite_kind @ SpriteKind::ChairDouble, + ) => interactables.push(( + pos, + Interaction::Mount( + sprite_mount_points( + sprite_kind, + Vec3::zero(), + block.get_ori().unwrap_or(0), + ) + .map(|(pos, _)| pos) + .collect(), + ), + )), _ => {}, }, } diff --git a/voxygen/src/session/interactable.rs b/voxygen/src/session/interactable.rs index a660af5300..769864413a 100644 --- a/voxygen/src/session/interactable.rs +++ b/voxygen/src/session/interactable.rs @@ -9,13 +9,13 @@ use super::{ use client::Client; use common::{ comp, - comp::tool::ToolKind, + comp::{tool::ToolKind, CharacterState}, consts::MAX_PICKUP_RANGE, link::Is, mounting::Mount, terrain::{Block, TerrainGrid, UnlockKind}, util::find_dist::{Cube, Cylinder, FindDist}, - vol::ReadVol, + vol::ReadVol, states::utils::SPRITE_MOUNT_DIST_SQR, }; use common_base::span; @@ -32,6 +32,7 @@ pub enum BlockInteraction { // TODO: mining blocks don't use the interaction key, so it might not be the best abstraction // to have them here, will see how things turn out Mine(ToolKind), + Mount(Vec>), } #[derive(Clone, Debug)] @@ -82,6 +83,7 @@ impl Interactable { } }, Interaction::Craft(tab) => BlockInteraction::Craft(tab), + Interaction::Mount(points) => BlockInteraction::Mount(points), }; Some(Self::Block(block, pos, block_interaction)) } @@ -176,11 +178,12 @@ pub(super) fn select_interactable( let items = ecs.read_storage::(); let stats = ecs.read_storage::(); + let player_char_state = char_states.get(player_entity); let player_cylinder = Cylinder::from_components( player_pos, scales.get(player_entity).copied(), colliders.get(player_entity), - char_states.get(player_entity), + player_char_state, ); let closest_interactable_entity = ( @@ -248,11 +251,24 @@ pub(super) fn select_interactable( .interactables .iter() .map(move |(block_offset, interaction)| (chunk_pos + block_offset, interaction)) + .filter_map(|(pos, interaction)| { + if matches!(player_char_state, Some(CharacterState::MountSprite(_))) && matches!(interaction, Interaction::Mount(_) | Interaction::Collect) { + None + } else if let Interaction::Mount(mount_points) = interaction { + mount_points.iter() + .map(|p| (p + pos.as_()).distance_squared(player_pos)) + .filter(|dist| *dist < SPRITE_MOUNT_DIST_SQR) + .min_by_key(|dist| OrderedFloat(*dist)) + .map(|dist| (pos, dist)) + .zip(Some(interaction)) + } else { + Some(((pos, (pos.as_() + 0.5).distance_squared(player_pos)), interaction)) + } + }) }) - .map(|(block_pos, interaction)| ( + .map(|((block_pos, dis), interaction)| ( block_pos, - block_pos.map(|e| e as f32 + 0.5) - .distance_squared(player_pos), + dis, interaction, )) .min_by_key(|(_, dist_sqr, _)| OrderedFloat(*dist_sqr)) @@ -267,7 +283,7 @@ pub(super) fn select_interactable( }) < search_dist }) .and_then(|(block_pos, interaction)| { - Interactable::from_block_pos(&terrain, block_pos, *interaction) + Interactable::from_block_pos(&terrain, block_pos, interaction.clone()) }) .or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e))) } diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index e47fb0d43d..bffafd5652 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -921,8 +921,8 @@ impl PlayState for SessionState { }, GameInput::Interact => { if state { + let mut client = self.client.borrow_mut(); if let Some(interactable) = &self.interactable { - let mut client = self.client.borrow_mut(); match interactable { Interactable::Block(block, pos, interaction) => { match interaction { @@ -938,6 +938,11 @@ impl PlayState for SessionState { block.get_sprite().map(|s| (*pos, s)), ) }, + BlockInteraction::Mount(_) => { + if block.is_mountable() { + client.mount_sprite(*pos); + } + }, BlockInteraction::Mine(_) => {}, } }, @@ -964,6 +969,8 @@ impl PlayState for SessionState { } }, } + } else { + client.stand_if_mounted() } } },