From aa86c86cb6aff4a07565aeaea5e81320f3ac99b6 Mon Sep 17 00:00:00 2001 From: anomaluridae Date: Tue, 14 Sep 2021 23:10:55 -0700 Subject: [PATCH] choose nearest target, from a specific subset of targets, for scene highlighting vs interactable vs game primary/secondary key input --- voxygen/src/session/interactable.rs | 56 +++++++++++++++++++---------- voxygen/src/session/mod.rs | 56 ++++++++++++++++++----------- voxygen/src/session/target.rs | 21 ----------- 3 files changed, 72 insertions(+), 61 deletions(-) diff --git a/voxygen/src/session/interactable.rs b/voxygen/src/session/interactable.rs index b2d8fee519..52b0b7a631 100644 --- a/voxygen/src/session/interactable.rs +++ b/voxygen/src/session/interactable.rs @@ -2,7 +2,10 @@ use ordered_float::OrderedFloat; use specs::{Join, WorldExt}; use vek::*; -use super::target::{self, MAX_TARGET_RANGE, Target}; +use super::{ + find_shortest_distance, + target::{self, Target, MAX_TARGET_RANGE}, +}; use client::Client; use common::{ comp, @@ -34,11 +37,11 @@ impl Interactable { /// Select interactable to hightlight, display interaction text for, and to /// interact with if the interact key is pressed -/// Selected in the following order -/// 1) Targeted items, in order of preference: +/// Selected in the following order: +/// 1) Targeted items, in order of nearest under cursor: /// (a) entity (if within range) /// (b) collectable -/// (c) can be mined +/// (c) can be mined, and is a mine sprite (Air) not a weak rock. /// 2) outside of targeted cam ray /// -> closest of nearest interactable entity/block pub(super) fn select_interactable( @@ -51,6 +54,12 @@ pub(super) fn select_interactable( span!(_guard, "select_interactable"); use common::{spiral::Spiral2d, terrain::TerrainChunk, vol::RectRasterableVol}; + let nearest_dist = find_shortest_distance(&mut [ + mine_target.map(|t| t.distance), + entity_target.map(|t| t.distance), + collect_target.map(|t| t.distance), + ]); + fn get_block(client: &Client, target: Target) -> Option { client .state() @@ -62,7 +71,7 @@ pub(super) fn select_interactable( if let Some(interactable) = entity_target .and_then(|t| { - if t.distance < MAX_TARGET_RANGE { + if t.distance < MAX_TARGET_RANGE && Some(t.distance) == nearest_dist { let entity = t.kind.0; Some(Interactable::Entity(entity)) } else { @@ -71,24 +80,33 @@ pub(super) fn select_interactable( }) .or_else(|| { collect_target.and_then(|t| { - get_block(client, t) - .map(|b| Interactable::Block(b, t.position_int(), Some(Interaction::Collect))) + if Some(t.distance) == nearest_dist { + get_block(client, t).map(|b| { + Interactable::Block(b, t.position_int(), Some(Interaction::Collect)) + }) + } else { + None + } }) }) .or_else(|| { 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 - } - }) + if Some(t.distance) == nearest_dist { + 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 + } + }) + } else { + None + } }) }) { diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 1f6cc70362..803ede58d6 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -50,7 +50,7 @@ use crate::{ use hashbrown::HashMap; use interactable::{select_interactable, Interactable}; use settings_change::Language::ChangeLanguage; -use target::{targets_under_cursor, Target}; +use target::targets_under_cursor; #[cfg(feature = "egui-ui")] use voxygen_egui::EguiDebugInfo; @@ -412,14 +412,8 @@ impl PlayState for SessionState { && client.is_wielding() == Some(true); // Check to see whether we're aiming at anything - let ( - build_target, - collect_target, - entity_target, - mine_target, - terrain_target, - shortest_dist, - ) = targets_under_cursor(&client, cam_pos, cam_dir, can_build, is_mining); + let (build_target, collect_target, entity_target, mine_target, terrain_target) = + targets_under_cursor(&client, cam_pos, cam_dir, can_build, is_mining); self.interactable = select_interactable( &client, @@ -431,23 +425,37 @@ impl PlayState for SessionState { drop(client); - fn is_nearest_target(shortest_dist: f32, target: Target) -> bool { - target.distance <= shortest_dist - } - - // Only highlight terrain blocks which can be interacted with - if let Some(mt) = - mine_target.filter(|mt| is_mining && is_nearest_target(shortest_dist, *mt)) + // Nearest block to consider with GameInput primary or secondary key. + let nearest_block_dist = find_shortest_distance(&mut [ + mine_target.filter(|_| is_mining).map(|t| t.distance), + build_target.filter(|_| can_build).map(|t| t.distance), + ]); + // Nearest block to be highlighted in the scene (self.scene.set_select_pos). + let nearest_scene_dist = find_shortest_distance(&mut [ + nearest_block_dist, + collect_target.filter(|_| !is_mining).map(|t| t.distance), + ]); + // Set break_block_pos only if mining is closest. + self.inputs.break_block_pos = if let Some(mt) = + mine_target.filter(|mt| is_mining && nearest_scene_dist == Some(mt.distance)) { self.scene.set_select_pos(Some(mt.position_int())); - self.inputs.break_block_pos = Some(mt.position); + Some(mt.position) } else if let Some(bt) = - build_target.filter(|bt| can_build && is_nearest_target(shortest_dist, *bt)) + build_target.filter(|bt| can_build && nearest_scene_dist == Some(bt.distance)) { self.scene.set_select_pos(Some(bt.position_int())); + None + } else if let Some(ct) = + collect_target.filter(|ct| nearest_scene_dist == Some(ct.distance)) + { + self.scene.set_select_pos(Some(ct.position_int())); + None } else { self.scene.set_select_pos(None); - } + None + }; + // filled block in line of sight let default_select_pos = terrain_target.map(|tt| tt.position); @@ -479,7 +487,7 @@ impl PlayState for SessionState { // precedence. // 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) + state && can_build && nearest_block_dist == Some(bt.distance) }) { client.remove_block(build_target.position_int()); } else { @@ -494,7 +502,7 @@ impl PlayState for SessionState { GameInput::Secondary => { let mut client = self.client.borrow_mut(); if let Some(build_target) = build_target.filter(|bt| { - state && can_build && is_nearest_target(shortest_dist, *bt) + state && can_build && nearest_block_dist == Some(bt.distance) }) { let selected_pos = build_target.kind.0; client.place_block( @@ -1533,3 +1541,9 @@ impl PlayState for SessionState { fn egui_enabled(&self) -> bool { true } } + +fn find_shortest_distance(arr: &mut [Option]) -> Option { + arr.iter() + .filter_map(|x| *x) + .min_by(|d1, d2| OrderedFloat(*d1).cmp(&OrderedFloat(*d2))) +} diff --git a/voxygen/src/session/target.rs b/voxygen/src/session/target.rs index 2c8a767cc5..4ccc97784b 100644 --- a/voxygen/src/session/target.rs +++ b/voxygen/src/session/target.rs @@ -1,4 +1,3 @@ -use ordered_float::OrderedFloat; use specs::{Join, WorldExt}; use vek::*; @@ -56,7 +55,6 @@ pub(super) fn targets_under_cursor( Option>, Option>, Option>, - f32, ) { span!(_guard, "targets_under_cursor"); // Choose a spot above the player's head for item distance checks @@ -107,24 +105,6 @@ pub(super) fn targets_under_cursor( .unwrap_or((None, None, None)); let (solid_pos, place_block_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 - // collectable. - let shortest_cam_dist = [&collect_cam_ray, &solid_cam_ray] - .iter() - .chain( - is_mining - .then(|| [&mine_cam_ray]) - .unwrap_or([&solid_cam_ray]) - .iter(), - ) - .filter_map(|x| match **x { - Some((d, Ok(Some(_)))) => Some(d), - _ => None, - }) - .min_by(|d1, d2| OrderedFloat(*d1).cmp(&OrderedFloat(*d2))) - .unwrap_or(MAX_PICKUP_RANGE); - // See if ray hits entities // Don't cast through blocks, (hence why use shortest_cam_dist from non-entity // targets) Could check for intersection with entity from last frame to @@ -246,6 +226,5 @@ pub(super) fn targets_under_cursor( entity_target, mine_target, terrain_target, - shortest_cam_dist, ) }