Add entity targeting

This commit is contained in:
Imbris 2020-04-24 04:02:47 -04:00 committed by Monty Marz
parent eaa5bac8d6
commit 1741384d00
4 changed files with 121 additions and 41 deletions

View File

@ -242,6 +242,7 @@ pub struct DebugInfo {
pub struct HudInfo {
pub is_aiming: bool,
pub is_first_person: bool,
pub target_entity: Option<specs::Entity>,
}
pub enum Event {
@ -1045,7 +1046,9 @@ impl Hud {
&uids,
)
.join()
.filter(|(entity, _, _, stats, _, _, _, _, _, _)| *entity != me && !stats.is_dead)
.filter(|(entity, _, _, stats, _, _, _, _, _, _)| *entity != me && !stats.is_dead
&& (stats.health.current() != stats.health.maximum() || info.target_entity.map_or(false, |e| e == *entity))
)
// Don't show outside a certain range
.filter(|(_, pos, _, _, _, _, _, _, hpfl, _)| {
pos.0.distance_squared(player_pos)

View File

@ -22,8 +22,8 @@ use anim::{
};
use common::{
comp::{
item::ItemKind, Body, CharacterState, Last, LightAnimation, LightEmitter, Loadout, Ori,
PhysicsState, Pos, Scale, Stats, Vel,
item::ItemKind, Body, CharacterState, Item, Last, LightAnimation, LightEmitter, Loadout,
Ori, PhysicsState, Pos, Scale, Stats, Vel,
},
state::{DeltaTime, State},
states::triple_strike,
@ -192,7 +192,6 @@ impl FigureMgr {
.read_storage::<Pos>()
.get(scene_data.player_entity)
.map_or(Vec3::zero(), |pos| pos.0);
for (
i,
(
@ -207,6 +206,7 @@ impl FigureMgr {
physics,
stats,
loadout,
item,
),
) in (
&ecs.entities(),
@ -220,6 +220,7 @@ impl FigureMgr {
&ecs.read_storage::<PhysicsState>(),
ecs.read_storage::<Stats>().maybe(),
ecs.read_storage::<Loadout>().maybe(),
ecs.read_storage::<Item>().maybe(),
)
.join()
.enumerate()
@ -519,7 +520,13 @@ impl FigureMgr {
(c / (1.0 + DAMAGE_FADE_COEFFICIENT * s.health.last_change.0)) as f32
})
})
.unwrap_or(Rgba::broadcast(1.0));
.unwrap_or(Rgba::broadcast(1.0))
// Highlight targeted collectible entities
* if item.is_some() && scene_data.target_entity.map_or(false, |e| e == entity) {
Rgba::new(2.0, 2.0, 2.0, 1.0)
} else {
Rgba::one()
};
let scale = scale.map(|s| s.0).unwrap_or(1.0);

View File

@ -70,6 +70,7 @@ pub struct Scene {
pub struct SceneData<'a> {
pub state: &'a State,
pub player_entity: specs::Entity,
pub target_entity: Option<specs::Entity>,
pub loaded_distance: f32,
pub view_distance: u32,
pub tick: u64,

View File

@ -208,18 +208,6 @@ impl PlayState for SessionState {
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::<comp::Pos>()
.get(self.client.borrow().entity())
{
Some(pos) => pos.0 + (Vec3::unit_z() * 2.0),
_ => cam_pos, // Should never happen, but a safe fallback
};
let (is_aiming, aim_dir_offset) = {
let client = self.client.borrow();
let is_aiming = client
@ -243,30 +231,10 @@ impl PlayState for SessionState {
let cam_dir: Vec3<f32> = 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 cam_ray = terrain
.ray(cam_pos, cam_pos + cam_dir * 100.0)
.until(|block| block.is_tangible())
.cast();
let cam_dist = cam_ray.0;
match cam_ray.1 {
Ok(Some(_))
if player_pos.distance_squared(cam_pos + cam_dir * cam_dist)
<= MAX_PICKUP_RANGE_SQR =>
{
(
Some((cam_pos + cam_dir * (cam_dist - 0.01)).map(|e| e.floor() as i32)),
Some((cam_pos + cam_dir * (cam_dist + 0.01)).map(|e| e.floor() as i32)),
)
},
_ => (None, None),
}
};
let (build_pos, select_pos, target_entity) =
under_cursor(&self.client.borrow(), cam_pos, cam_dir);
// Throw out distance info, it will be useful in the future
let target_entity = target_entity.map(|x| x.0);
let can_build = self
.client
@ -708,6 +676,7 @@ impl PlayState for SessionState {
self.scene.camera().get_mode(),
camera::CameraMode::FirstPerson
),
target_entity,
},
);
@ -979,6 +948,7 @@ impl PlayState for SessionState {
let scene_data = SceneData {
state: client.state(),
player_entity: client.entity(),
target_entity,
loaded_distance: client.loaded_distance(),
view_distance: client.view_distance().unwrap_or(1),
tick: client.get_tick(),
@ -1055,3 +1025,102 @@ impl PlayState for SessionState {
self.hud.render(renderer, self.scene.globals());
}
}
/// Max distance an entity can be "targeted"
const MAX_TARGET_RANGE: f32 = 30.0;
/// Calculate what the cursor is pointing at within the 3d scene
fn under_cursor(
client: &Client,
cam_pos: Vec3<f32>,
cam_dir: Vec3<f32>,
) -> (
Option<Vec3<i32>>,
Option<Vec3<i32>>,
Option<(specs::Entity, f32)>,
) {
// Choose a spot above the player's head for item distance checks
let player_entity = client.entity();
let player_pos = match client
.state()
.read_storage::<comp::Pos>()
.get(player_entity)
{
Some(pos) => pos.0 + (Vec3::unit_z() * 2.0),
_ => cam_pos, // Should never happen, but a safe fallback
};
let terrain = client.state().terrain();
let cam_ray = terrain
.ray(cam_pos, cam_pos + cam_dir * 100.0)
.until(|block| block.is_tangible())
.cast();
let cam_dist = cam_ray.0;
// The ray hit something, is it within range?
let (build_pos, select_pos) = if matches!(cam_ray.1, Ok(Some(_)) if
player_pos.distance_squared(cam_pos + cam_dir * cam_dist)
<= MAX_PICKUP_RANGE_SQR)
{
(
Some((cam_pos + cam_dir * (cam_dist - 0.01)).map(|e| e.floor() as i32)),
Some((cam_pos + cam_dir * (cam_dist + 0.01)).map(|e| e.floor() as i32)),
)
} else {
(None, None)
};
// See if ray hits entities
// Currently treated as spheres
let ecs = client.state().ecs();
// Don't cast through blocks
// Could check for intersection with entity from last frame to narrow this down
let cast_dist = if let Ok(Some(_)) = cam_ray.1 {
cam_dist.min(MAX_TARGET_RANGE)
} else {
MAX_TARGET_RANGE
};
// Need to raycast by distance to cam
// But also filter out by distance to the player (but this only needs to be done
// on final result)
let mut nearby = (
&ecs.entities(),
&ecs.read_storage::<comp::Pos>(),
ecs.read_storage::<comp::Scale>().maybe(),
&ecs.read_storage::<comp::Body>()
)
.join()
.filter(|(e, _, _, _)| *e != player_entity)
.map(|(e, p, s, b)| {
const RADIUS_SCALE: f32 = 1.2;
let radius = s.map_or(1.0, |s| s.0) * b.radius() * RADIUS_SCALE;
// Move position up from the feet
let pos = Vec3::new(p.0.x, p.0.y, p.0.z + radius);
let dist_sqr = pos.distance_squared(cam_pos);
(e, pos, radius, dist_sqr)
})
// Ignore entities intersecting the camera
.filter(|(_, _, r, d_sqr)| *d_sqr > r.powi(2))
.filter(|(_, _, r, d_sqr)| *d_sqr <= cast_dist.powi(2) + 2.0 * cast_dist * r + r.powi(2))
.map(|(e, p, r, d_sqr)| (e, p, r, d_sqr.sqrt() - r))
.collect::<Vec<_>>();
nearby.sort_unstable_by(|a, b| a.3.partial_cmp(&b.3).unwrap());
let seg_ray = LineSegment3 {
start: cam_pos,
end: cam_pos + cam_dir * cam_dist,
};
// TODO: fuzzy borders
let target_entity = nearby
.iter()
.map(|(e, p, r, _)| (e, *p, r))
.find(|(_, p, r)| seg_ray.projected_point(*p).distance_squared(*p) < r.powi(2))
.and_then(|(e, p, r)| {
let dist_to_player = p.distance(player_pos);
(dist_to_player - r < MAX_TARGET_RANGE).then_some((*e, dist_to_player))
});
// TODO: consider setting build/select to None when targeting an entity
(build_pos, select_pos, target_entity)
}