diff --git a/voxygen/src/session/interactable.rs b/voxygen/src/session/interactable.rs index 36a4dc72ca..54ac3dcfbb 100644 --- a/voxygen/src/session/interactable.rs +++ b/voxygen/src/session/interactable.rs @@ -3,7 +3,7 @@ use specs::{Join, WorldExt}; use vek::*; use super::target::{self, Target}; -use client::{self, Client}; +use client::Client; use common::{ comp, consts::MAX_PICKUP_RANGE, @@ -61,42 +61,39 @@ pub(super) fn select_interactable( if let Some(interactable) = entity_target .and_then(|t| { if t.distance < MAX_PICKUP_RANGE { - let entity = t.typed.0; + let entity = t.kind.0; Some(Interactable::Entity(entity)) } else { None } }) .or_else(|| { - collect_target - .map(|t| { - get_block(client, t).map(|b| { - Interactable::Block(b, t.position_int(), Some(Interaction::Collect)) - }) - }) - .unwrap_or(None) + collect_target.and_then(|t| { + get_block(client, t) + .map(|b| Interactable::Block(b, t.position_int(), Some(Interaction::Collect))) + }) }) .or_else(|| { - mine_target - .map(|t| { - get_block(client, t).and_then(|b| { - // Handling edge detection. sometimes the casting (in Target mod) returns a - // position which is actually empty, which we do not want labeled as an - // interactable. We are only returning the mineable air - // elements (e.g. minerals). The mineable weakrock are used - // in the terrain selected_pos, but is not an interactable. - if b.mine_tool().is_some() && b.is_air() { - Some(Interactable::Block(b, t.position_int(), None)) - } else { - None - } - }) + mine_target.and_then(|t| { + get_block(client, t).and_then(|b| { + // Handling edge detection. sometimes the casting (in Target mod) returns a + // position which is actually empty, which we do not want labeled as an + // interactable. We are only returning the mineable air + // elements (e.g. minerals). The mineable weakrock are used + // in the terrain selected_pos, but is not an interactable. + if b.mine_tool().is_some() && b.is_air() { + Some(Interactable::Block(b, t.position_int(), None)) + } else { + None + } }) - .unwrap_or(None) + }) }) { Some(interactable) } else { + // If there are no directly targeted interactables select the closest one if any + // are in range let ecs = client.state().ecs(); let player_entity = client.entity(); let positions = ecs.read_storage::<comp::Pos>(); diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 51ec4cf5c2..48010786fb 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -425,28 +425,26 @@ impl PlayState for SessionState { drop(client); - fn is_nearest_target<T>(shortest_dist: f32, target: Option<Target<T>>) -> bool { - target - .map(|t| (t.distance <= shortest_dist)) - .unwrap_or(false) + fn is_nearest_target<T>(shortest_dist: f32, target: Target<T>) -> bool { + target.distance <= shortest_dist } // Only highlight terrain blocks which can be interacted with - if is_mining && is_nearest_target(shortest_dist, mine_target) { - mine_target.map(|mt| self.scene.set_select_pos(Some(mt.position_int()))); - } else if can_build && is_nearest_target(shortest_dist, build_target) { - build_target.map(|bt| self.scene.set_select_pos(Some(bt.position_int()))); + if let Some(mt) = + mine_target.filter(|mt| is_mining && is_nearest_target(shortest_dist, *mt)) + { + self.scene.set_select_pos(Some(mt.position_int())); + } else if let Some(bt) = + build_target.filter(|bt| can_build && is_nearest_target(shortest_dist, *bt)) + { + self.scene.set_select_pos(Some(bt.position_int())); } else { self.scene.set_select_pos(None); self.inputs.select_pos = entity_target.map(|et| et.position); } // Throw out distance info, it will be useful in the future - self.target_entity = if let Some(target::Entity(e)) = entity_target.map(|t| t.typed) { - Some(e) - } else { - None - }; + self.target_entity = entity_target.map(|t| t.kind.0); // Handle window events. for event in events { @@ -471,15 +469,17 @@ impl PlayState for SessionState { let mut client = self.client.borrow_mut(); // Mine and build targets can be the same block. make building take // precedence. - if state - && can_build - && is_nearest_target(shortest_dist, build_target) - { - self.inputs.select_pos = build_target.map(|t| t.position); - client.remove_block(build_target.unwrap().position_int()); + // Order of precedence: build, then mining, then attack. + if let Some(build_target) = build_target.filter(|bt| { + state && can_build && is_nearest_target(shortest_dist, *bt) + }) { + self.inputs.select_pos = Some(build_target.position); + client.remove_block(build_target.position_int()); } else { - if is_mining && is_nearest_target(shortest_dist, mine_target) { - self.inputs.select_pos = mine_target.map(|t| t.position); + if let Some(mine_target) = mine_target.filter(|mt| { + is_mining && is_nearest_target(shortest_dist, *mt) + }) { + self.inputs.select_pos = Some(mine_target.position); } client.handle_input( InputKind::Primary, @@ -491,17 +491,14 @@ impl PlayState for SessionState { }, GameInput::Secondary => { let mut client = self.client.borrow_mut(); - if state - && can_build - && is_nearest_target(shortest_dist, build_target) - { - if let Some(build_target) = build_target { - self.inputs.select_pos = Some(build_target.position); - client.place_block( - build_target.position_int(), - self.selected_block, - ); - } + if let Some(build_target) = build_target.filter(|bt| { + state && can_build && is_nearest_target(shortest_dist, *bt) + }) { + self.inputs.select_pos = Some(build_target.position); + client.place_block( + build_target.position_int(), + self.selected_block, + ); } else { client.handle_input( InputKind::Secondary, diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index a79208579a..cef45106cf 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -1,3 +1,4 @@ +use ordered_float::OrderedFloat; use specs::{Join, WorldExt}; use vek::*; @@ -13,7 +14,7 @@ use common_base::span; #[derive(Clone, Copy, Debug)] pub struct Target<T> { - pub typed: T, + pub kind: T, pub distance: f32, pub position: Vec3<f32>, } @@ -98,9 +99,7 @@ pub(super) fn targets_under_cursor( let (mine_pos, _, mine_cam_ray) = is_mining .then(|| find_pos(|b: Block| b.mine_tool().is_some())) .unwrap_or((None, None, None)); - let (_, solid_pos, solid_cam_ray) = can_build - .then(|| find_pos(|b: Block| b.is_solid())) - .unwrap_or((None, None, None)); + let (_, solid_pos, solid_cam_ray) = find_pos(|b: Block| b.is_solid()); // Find shortest cam_dist of non-entity targets. // Note that some of these targets can technically be in Air, such as the @@ -117,7 +116,7 @@ pub(super) fn targets_under_cursor( Some((d, Ok(Some(_)))) => Some(d), _ => None, }) - .min_by(|d1, d2| d1.partial_cmp(d2).unwrap()) + .min_by(|d1, d2| OrderedFloat(*d1).cmp(&OrderedFloat(*d2))) .unwrap_or(MAX_PICKUP_RANGE); // See if ray hits entities @@ -189,16 +188,16 @@ pub(super) fn targets_under_cursor( let dist_to_player = player_cylinder.min_distance(target_cylinder); if dist_to_player < MAX_TARGET_RANGE { Some(Target { - typed: Entity(*e), + kind: Entity(*e), position: p, distance: dist_to_player, }) } else { None } }); - let build_target = if let (Some(position), Some(ray)) = (solid_pos, solid_cam_ray) { - Some(Target { - typed: Build, + let build_target = if can_build { + solid_pos.zip(solid_cam_ray).map(|(position, ray)| Target { + kind: Build, distance: ray.0, position, }) @@ -206,25 +205,19 @@ pub(super) fn targets_under_cursor( None }; - let collect_target = if let (Some(position), Some(ray)) = (collect_pos, collect_cam_ray) { - Some(Target { - typed: Collectable, + let collect_target = collect_pos + .zip(collect_cam_ray) + .map(|(position, ray)| Target { + kind: Collectable, distance: ray.0, position, - }) - } else { - None - }; + }); - let mine_target = if let (Some(position), Some(ray)) = (mine_pos, mine_cam_ray) { - Some(Target { - typed: Mine, - distance: ray.0, - position, - }) - } else { - None - }; + let mine_target = mine_pos.zip(mine_cam_ray).map(|(position, ray)| Target { + kind: Mine, + distance: ray.0, + position, + }); // Return multiple possible targets // GameInput events determine which target to use.