diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index f192208b5c..1b79359eb8 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -8,6 +8,9 @@ use specs::{Component, FlaggedStorage, HashMapStorage}; use specs_idvs::IDVStorage; use std::ops::Not; +// The limit on distance between the entity and a collectible (squared) +pub const MAX_PICKUP_RANGE_SQR: f32 = 64.0; + #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Inventory { pub slots: Vec>, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 2871761a7a..1c49afab0b 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -28,7 +28,9 @@ pub use controller::{ }; pub use energy::{Energy, EnergySource}; pub use inputs::CanBuild; -pub use inventory::{item, Inventory, InventoryUpdate, InventoryUpdateEvent, Item, ItemKind}; +pub use inventory::{ + item, Inventory, InventoryUpdate, InventoryUpdateEvent, Item, ItemKind, MAX_PICKUP_RANGE_SQR, +}; pub use last::Last; pub use location::{Waypoint, WaypointArea}; pub use phys::{ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel}; diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 09998ca6aa..8e6570f89f 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -1,6 +1,6 @@ use crate::{Server, StateExt}; use common::{ - comp, + comp::{self, Pos, MAX_PICKUP_RANGE_SQR}, sync::WorldSyncExt, terrain::block::Block, vol::{ReadVol, Vox}, @@ -16,7 +16,6 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv match manip { comp::InventoryManip::Pickup(uid) => { - // TODO: enforce max pickup range let item_entity = if let (Some((item, item_entity)), Some(inv)) = ( state .ecs() @@ -33,7 +32,11 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .write_storage::() .get_mut(entity), ) { - if inv.push(item).is_none() { + if within_pickup_range( + state.ecs().read_storage::().get(entity), + state.ecs().read_storage::().get(item_entity), + ) && inv.push(item).is_none() + { Some(item_entity) } else { None @@ -56,6 +59,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv comp::InventoryManip::Collect(pos) => { let block = state.terrain().get(pos).ok().copied(); + if let Some(block) = block { if block.is_collectible() && state @@ -231,3 +235,39 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .build(); } } + +fn within_pickup_range(player_position: Option<&Pos>, item_position: Option<&Pos>) -> bool { + match (player_position, item_position) { + (Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_PICKUP_RANGE_SQR, + _ => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use common::comp::Pos; + use vek::Vec3; + + #[test] + fn pickup_distance_within_range() { + let player_position = Pos(Vec3::zero()); + let item_position = Pos(Vec3::one()); + + assert_eq!( + within_pickup_range(Some(&player_position), Some(&item_position)), + true + ); + } + + #[test] + fn pickup_distance_not_within_range() { + let player_position = Pos(Vec3::zero()); + let item_position = Pos(Vec3::one() * 500.0); + + assert_eq!( + within_pickup_range(Some(&player_position), Some(&item_position)), + false + ); + } +} diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index cf47210669..0fd0cbe2e3 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -13,7 +13,7 @@ use common::{ assets::{load_watched, watch}, clock::Clock, comp, - comp::{Pos, Vel}, + comp::{Pos, Vel, MAX_PICKUP_RANGE_SQR}, msg::ClientState, terrain::{Block, BlockKind}, vol::ReadVol, @@ -155,27 +155,52 @@ impl PlayState for SessionState { let camera::Dependents { view_mat, cam_pos, .. } = self.scene.camera().dependents(); + + // Choose a spot above the player's head for item distance checks + let player_pos = match self + .client + .borrow() + .state() + .read_storage::() + .get(self.client.borrow().entity()) + { + Some(pos) => pos.0 + (Vec3::unit_z() * 2.0), + _ => cam_pos, // Should never happen, but a safe fallback + }; + let cam_dir: Vec3 = Vec3::from(view_mat.inverted() * -Vec4::unit_z()); // Check to see whether we're aiming at anything let (build_pos, select_pos) = { let client = self.client.borrow(); let terrain = client.state().terrain(); - let ray = terrain + + let cam_ray = terrain .ray(cam_pos, cam_pos + cam_dir * 100.0) .until(|block| block.is_tangible()) .cast(); - let dist = ray.0; - if let Ok(Some(_)) = ray.1 { - // Hit something! + + let cam_dist = cam_ray.0; + + if let Ok(Some(_)) = cam_ray.1 { + // The ray hit something, is it within pickup range? + let select_pos = if player_pos.distance_squared(cam_pos + cam_dir * cam_dist) + <= MAX_PICKUP_RANGE_SQR + { + Some((cam_pos + cam_dir * cam_dist).map(|e| e.floor() as i32)) + } else { + None + }; + ( - Some((cam_pos + cam_dir * (dist - 0.01)).map(|e| e.floor() as i32)), - Some((cam_pos + cam_dir * dist).map(|e| e.floor() as i32)), + Some((cam_pos + cam_dir * (cam_dist - 0.01)).map(|e| e.floor() as i32)), + select_pos, ) } else { (None, None) } }; + // Only highlight collectables self.scene.set_select_pos(select_pos.filter(|sp| { self.client