mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
choose nearest target, from a specific subset of targets, for scene highlighting vs interactable vs game primary/secondary key input
This commit is contained in:
parent
6362df4ffc
commit
aa86c86cb6
@ -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<T>(client: &Client, target: Target<T>) -> Option<Block> {
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
{
|
||||
|
@ -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<T>(shortest_dist: f32, target: Target<T>) -> 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<f32>]) -> Option<f32> {
|
||||
arr.iter()
|
||||
.filter_map(|x| *x)
|
||||
.min_by(|d1, d2| OrderedFloat(*d1).cmp(&OrderedFloat(*d2)))
|
||||
}
|
||||
|
@ -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<Target<Entity>>,
|
||||
Option<Target<Mine>>,
|
||||
Option<Target<Terrain>>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user