make Target generic, over the specific typed targets

This commit is contained in:
anomaluridae 2021-08-11 23:52:50 -07:00
parent ba4af5ee8e
commit 8b83b48b9b
3 changed files with 72 additions and 68 deletions

View File

@ -2,7 +2,7 @@ use ordered_float::OrderedFloat;
use specs::{Join, WorldExt};
use vek::*;
use super::target::{Target, TargetType};
use super::target::{self, Target};
use client::{self, Client};
use common::{
comp,
@ -28,38 +28,6 @@ impl Interactable {
Self::Block(_, _, _) => None,
}
}
pub fn from_target(target: Target, client: &Client) -> Option<Interactable> {
match target.typed {
TargetType::Collectable => client
.state()
.terrain()
.get(target.position_int())
.ok()
.copied()
.map(|b| Interactable::Block(b, target.position_int(), Some(Interaction::Collect))),
TargetType::Entity(e) => Some(Interactable::Entity(e)),
TargetType::Mine => client
.state()
.terrain()
.get(target.position_int())
.ok()
.copied()
.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, target.position_int(), None))
} else {
None
}
}),
TargetType::Build => None,
}
}
}
/// Select interactable to hightlight, display interaction text for, and to
@ -73,30 +41,57 @@ impl Interactable {
/// -> closest of nearest interactable entity/block
pub(super) fn select_interactable(
client: &Client,
collect_target: Option<Target>,
entity_target: Option<Target>,
mine_target: Option<Target>,
collect_target: Option<Target<target::Collectable>>,
entity_target: Option<Target<target::Entity>>,
mine_target: Option<Target<target::Mine>>,
scene: &Scene,
) -> Option<Interactable> {
span!(_guard, "select_interactable");
use common::{spiral::Spiral2d, terrain::TerrainChunk, vol::RectRasterableVol};
fn get_block<T>(client: &Client, target: Target<T>) -> Option<Block> {
client
.state()
.terrain()
.get(target.position_int())
.ok()
.copied()
}
if let Some(interactable) = entity_target
.and_then(|t| {
if t.distance < MAX_PICKUP_RANGE {
Interactable::from_target(t, client)
let entity = t.typed.0;
Some(Interactable::Entity(entity))
} else {
None
}
})
.or_else(|| {
collect_target
.map(|t| Interactable::from_target(t, client))
.map(|t| {
get_block(client, t).map(|b| {
Interactable::Block(b, t.position_int(), Some(Interaction::Collect))
})
})
.unwrap_or(None)
})
.or_else(|| {
mine_target
.map(|t| Interactable::from_target(t, client))
.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
}
})
})
.unwrap_or(None)
})
{

View File

@ -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, TargetType};
use target::{targets_under_cursor, Target};
#[cfg(feature = "egui-ui")]
use voxygen_egui::EguiDebugInfo;
@ -425,16 +425,16 @@ impl PlayState for SessionState {
drop(client);
let is_nearest_target = |target: Option<Target>| {
fn is_nearest_target<T>(shortest_dist: f32, target: Option<Target<T>>) -> bool {
target
.map(|t| (t.distance <= shortest_dist))
.unwrap_or(false)
};
}
// Only highlight terrain blocks which can be interacted with
if is_mining && is_nearest_target(mine_target) {
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(build_target) {
} else if can_build && is_nearest_target(shortest_dist, build_target) {
build_target.map(|bt| self.scene.set_select_pos(Some(bt.position_int())));
} else {
self.scene.set_select_pos(None);
@ -442,8 +442,7 @@ impl PlayState for SessionState {
}
// Throw out distance info, it will be useful in the future
self.target_entity = if let Some(TargetType::Entity(e)) = entity_target.map(|t| t.typed)
{
self.target_entity = if let Some(target::Entity(e)) = entity_target.map(|t| t.typed) {
Some(e)
} else {
None
@ -472,11 +471,14 @@ 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(build_target) {
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());
} else {
if is_mining && is_nearest_target(mine_target) {
if is_mining && is_nearest_target(shortest_dist, mine_target) {
self.inputs.select_pos = mine_target.map(|t| t.position);
}
client.handle_input(
@ -489,7 +491,10 @@ impl PlayState for SessionState {
},
GameInput::Secondary => {
let mut client = self.client.borrow_mut();
if state && can_build && is_nearest_target(build_target) {
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(

View File

@ -12,21 +12,25 @@ use common::{
use common_base::span;
#[derive(Clone, Copy, Debug)]
pub enum TargetType {
Build,
Collectable,
Entity(specs::Entity),
Mine,
}
#[derive(Clone, Copy, Debug)]
pub struct Target {
pub typed: TargetType,
pub struct Target<T> {
pub typed: T,
pub distance: f32,
pub position: Vec3<f32>,
}
impl Target {
#[derive(Clone, Copy, Debug)]
pub struct Build;
#[derive(Clone, Copy, Debug)]
pub struct Collectable;
#[derive(Clone, Copy, Debug)]
pub struct Entity(pub specs::Entity);
#[derive(Clone, Copy, Debug)]
pub struct Mine;
impl<T> Target<T> {
pub fn position_int(self) -> Vec3<i32> { self.position.map(|p| p.floor() as i32) }
}
@ -41,10 +45,10 @@ pub(super) fn targets_under_cursor(
can_build: bool,
is_mining: bool,
) -> (
Option<Target>,
Option<Target>,
Option<Target>,
Option<Target>,
Option<Target<Build>>,
Option<Target<Collectable>>,
Option<Target<Entity>>,
Option<Target<Mine>>,
f32,
) {
span!(_guard, "targets_under_cursor");
@ -185,7 +189,7 @@ 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: TargetType::Entity(*e),
typed: Entity(*e),
position: p,
distance: dist_to_player,
})
@ -194,7 +198,7 @@ pub(super) fn targets_under_cursor(
let build_target = if let (Some(position), Some(ray)) = (solid_pos, solid_cam_ray) {
Some(Target {
typed: TargetType::Build,
typed: Build,
distance: ray.0,
position,
})
@ -204,7 +208,7 @@ pub(super) fn targets_under_cursor(
let collect_target = if let (Some(position), Some(ray)) = (collect_pos, collect_cam_ray) {
Some(Target {
typed: TargetType::Collectable,
typed: Collectable,
distance: ray.0,
position,
})
@ -214,7 +218,7 @@ pub(super) fn targets_under_cursor(
let mine_target = if let (Some(position), Some(ray)) = (mine_pos, mine_cam_ray) {
Some(Target {
typed: TargetType::Mine,
typed: Mine,
distance: ray.0,
position,
})