mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Allow interacting with nearby blocks without pointing at them, unify selection of block/entity interactors so that only one is select at once, rearrange pickup and mount range consts
This commit is contained in:
parent
325695e937
commit
64def3cde4
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -2963,6 +2963,15 @@ dependencies = [
|
|||||||
"num-traits 0.2.12",
|
"num-traits 0.2.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fe9037165d7023b1228bc4ae9a2fa1a2b0095eca6c2998c624723dfd01314a5"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits 0.2.12",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "osascript"
|
name = "osascript"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -3691,7 +3700,7 @@ dependencies = [
|
|||||||
"crossbeam-utils 0.7.2",
|
"crossbeam-utils 0.7.2",
|
||||||
"linked-hash-map",
|
"linked-hash-map",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"ordered-float",
|
"ordered-float 1.1.0",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"stb_truetype",
|
"stb_truetype",
|
||||||
]
|
]
|
||||||
@ -4962,6 +4971,7 @@ dependencies = [
|
|||||||
"native-dialog",
|
"native-dialog",
|
||||||
"num 0.2.1",
|
"num 0.2.1",
|
||||||
"old_school_gfx_glutin_ext",
|
"old_school_gfx_glutin_ext",
|
||||||
|
"ordered-float 2.0.0",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rodio",
|
"rodio",
|
||||||
"ron",
|
"ron",
|
||||||
@ -5014,7 +5024,7 @@ dependencies = [
|
|||||||
"minifb",
|
"minifb",
|
||||||
"noise",
|
"noise",
|
||||||
"num 0.2.1",
|
"num 0.2.1",
|
||||||
"ordered-float",
|
"ordered-float 1.1.0",
|
||||||
"packed_simd_2",
|
"packed_simd_2",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rand_chacha 0.2.2",
|
"rand_chacha 0.2.2",
|
||||||
|
@ -8,9 +8,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use specs::{Component, FlaggedStorage, HashMapStorage};
|
use specs::{Component, FlaggedStorage, HashMapStorage};
|
||||||
use specs_idvs::IdvStorage;
|
use specs_idvs::IdvStorage;
|
||||||
|
|
||||||
// The limit on distance between the entity and a collectible (squared)
|
|
||||||
pub const MAX_PICKUP_RANGE_SQR: f32 = 64.0;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Inventory {
|
pub struct Inventory {
|
||||||
slots: Vec<Option<Item>>,
|
slots: Vec<Option<Item>>,
|
||||||
|
@ -49,13 +49,13 @@ pub use inputs::CanBuild;
|
|||||||
pub use inventory::{
|
pub use inventory::{
|
||||||
item,
|
item,
|
||||||
item::{Item, ItemDrop},
|
item::{Item, ItemDrop},
|
||||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR,
|
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||||
};
|
};
|
||||||
pub use last::Last;
|
pub use last::Last;
|
||||||
pub use location::{Waypoint, WaypointArea};
|
pub use location::{Waypoint, WaypointArea};
|
||||||
pub use misc::Object;
|
pub use misc::Object;
|
||||||
pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
|
pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
|
||||||
pub use player::{Player, MAX_MOUNT_RANGE_SQR};
|
pub use player::Player;
|
||||||
pub use projectile::Projectile;
|
pub use projectile::Projectile;
|
||||||
pub use shockwave::{Shockwave, ShockwaveHitEntities};
|
pub use shockwave::{Shockwave, ShockwaveHitEntities};
|
||||||
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
|
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
|
||||||
|
@ -5,7 +5,6 @@ use specs::{Component, FlaggedStorage, NullStorage};
|
|||||||
use specs_idvs::IdvStorage;
|
use specs_idvs::IdvStorage;
|
||||||
|
|
||||||
const MAX_ALIAS_LEN: usize = 32;
|
const MAX_ALIAS_LEN: usize = 32;
|
||||||
pub const MAX_MOUNT_RANGE_SQR: i32 = 20000;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
|
3
common/src/consts.rs
Normal file
3
common/src/consts.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// The limit on distance between the entity and a collectible (squared)
|
||||||
|
pub const MAX_PICKUP_RANGE: f32 = 8.0;
|
||||||
|
pub const MAX_MOUNT_RANGE: f32 = 14.0;
|
@ -24,6 +24,7 @@ pub mod clock;
|
|||||||
pub mod cmd;
|
pub mod cmd;
|
||||||
pub mod combat;
|
pub mod combat;
|
||||||
pub mod comp;
|
pub mod comp;
|
||||||
|
pub mod consts;
|
||||||
pub mod effect;
|
pub mod effect;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod explosion;
|
pub mod explosion;
|
||||||
|
@ -3,8 +3,9 @@ use common::{
|
|||||||
comp::{
|
comp::{
|
||||||
self, item,
|
self, item,
|
||||||
slot::{self, Slot},
|
slot::{self, Slot},
|
||||||
Pos, MAX_PICKUP_RANGE_SQR,
|
Pos,
|
||||||
},
|
},
|
||||||
|
consts::MAX_PICKUP_RANGE,
|
||||||
msg::ServerGeneral,
|
msg::ServerGeneral,
|
||||||
recipe::default_recipe_book,
|
recipe::default_recipe_book,
|
||||||
sync::{Uid, WorldSyncExt},
|
sync::{Uid, WorldSyncExt},
|
||||||
@ -512,7 +513,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
|
|
||||||
fn within_pickup_range(player_position: Option<&Pos>, item_position: Option<&Pos>) -> bool {
|
fn within_pickup_range(player_position: Option<&Pos>, item_position: Option<&Pos>) -> bool {
|
||||||
match (player_position, item_position) {
|
match (player_position, item_position) {
|
||||||
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_PICKUP_RANGE_SQR,
|
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_PICKUP_RANGE.powi(2),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ hashbrown = {version = "0.7.2", features = ["rayon", "serde", "nightly"]}
|
|||||||
image = {version = "0.23.8", default-features = false, features = ["ico", "png"]}
|
image = {version = "0.23.8", default-features = false, features = ["ico", "png"]}
|
||||||
native-dialog = { version = "0.4.2", default-features = false, optional = true }
|
native-dialog = { version = "0.4.2", default-features = false, optional = true }
|
||||||
num = "0.2"
|
num = "0.2"
|
||||||
|
ordered-float = "2.0.0"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
rodio = {version = "0.11", default-features = false, features = ["wav", "vorbis"]}
|
rodio = {version = "0.11", default-features = false, features = ["wav", "vorbis"]}
|
||||||
ron = {version = "0.6", default-features = false}
|
ron = {version = "0.6", default-features = false}
|
||||||
|
@ -1123,7 +1123,7 @@ impl Hud {
|
|||||||
for (pos, item, distance) in (&entities, &pos, &items)
|
for (pos, item, distance) in (&entities, &pos, &items)
|
||||||
.join()
|
.join()
|
||||||
.map(|(_, pos, item)| (pos, item, pos.0.distance_squared(player_pos)))
|
.map(|(_, pos, item)| (pos, item, pos.0.distance_squared(player_pos)))
|
||||||
.filter(|(_, _, distance)| distance < &common::comp::MAX_PICKUP_RANGE_SQR)
|
.filter(|(_, _, distance)| distance < &common::consts::MAX_PICKUP_RANGE.powi(2))
|
||||||
{
|
{
|
||||||
let overitem_id = overitem_walker.next(
|
let overitem_id = overitem_walker.next(
|
||||||
&mut self.ids.overitems,
|
&mut self.ids.overitems,
|
||||||
|
@ -91,9 +91,10 @@ impl<'a> Widget for Overitem<'a> {
|
|||||||
// ———
|
// ———
|
||||||
|
|
||||||
// scale at max distance is 10, and at min distance is 30
|
// scale at max distance is 10, and at min distance is 30
|
||||||
let scale: f64 =
|
let scale: f64 = ((1.5
|
||||||
((1.5 - (self.distance_from_player_sqr / common::comp::MAX_PICKUP_RANGE_SQR)) * 20.0)
|
- (self.distance_from_player_sqr / common::consts::MAX_PICKUP_RANGE.powi(2)))
|
||||||
.into();
|
* 20.0)
|
||||||
|
.into();
|
||||||
let text_font_size = scale * 1.0;
|
let text_font_size = scale * 1.0;
|
||||||
let text_pos_y = scale * 1.2;
|
let text_pos_y = scale * 1.2;
|
||||||
let btn_rect_size = scale * 0.8;
|
let btn_rect_size = scale * 0.8;
|
||||||
|
@ -13,6 +13,9 @@ pub struct BlocksOfInterest {
|
|||||||
pub beehives: Vec<Vec3<i32>>,
|
pub beehives: Vec<Vec3<i32>>,
|
||||||
pub reeds: Vec<Vec3<i32>>,
|
pub reeds: Vec<Vec3<i32>>,
|
||||||
pub flowers: Vec<Vec3<i32>>,
|
pub flowers: Vec<Vec3<i32>>,
|
||||||
|
// Note: these are only needed for chunks within the iteraction range so this is a potential
|
||||||
|
// area for optimization
|
||||||
|
pub interactables: Vec<Vec3<i32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlocksOfInterest {
|
impl BlocksOfInterest {
|
||||||
@ -24,6 +27,7 @@ impl BlocksOfInterest {
|
|||||||
let mut beehives = Vec::new();
|
let mut beehives = Vec::new();
|
||||||
let mut reeds = Vec::new();
|
let mut reeds = Vec::new();
|
||||||
let mut flowers = Vec::new();
|
let mut flowers = Vec::new();
|
||||||
|
let mut interactables = Vec::new();
|
||||||
|
|
||||||
chunk
|
chunk
|
||||||
.vol_iter(
|
.vol_iter(
|
||||||
@ -34,29 +38,34 @@ impl BlocksOfInterest {
|
|||||||
chunk.get_max_z(),
|
chunk.get_max_z(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.for_each(|(pos, block)| match block.kind() {
|
.for_each(|(pos, block)| {
|
||||||
BlockKind::Leaves => {
|
match block.kind() {
|
||||||
if thread_rng().gen_range(0, 16) == 0 {
|
BlockKind::Leaves => {
|
||||||
leaves.push(pos)
|
if thread_rng().gen_range(0, 16) == 0 {
|
||||||
}
|
leaves.push(pos)
|
||||||
},
|
}
|
||||||
BlockKind::Grass => {
|
},
|
||||||
if thread_rng().gen_range(0, 16) == 0 {
|
BlockKind::Grass => {
|
||||||
grass.push(pos)
|
if thread_rng().gen_range(0, 16) == 0 {
|
||||||
}
|
grass.push(pos)
|
||||||
},
|
}
|
||||||
_ => match block.get_sprite() {
|
},
|
||||||
Some(SpriteKind::Ember) => embers.push(pos),
|
_ => match block.get_sprite() {
|
||||||
Some(SpriteKind::Beehive) => beehives.push(pos),
|
Some(SpriteKind::Ember) => embers.push(pos),
|
||||||
Some(SpriteKind::Reed) => reeds.push(pos),
|
Some(SpriteKind::Beehive) => beehives.push(pos),
|
||||||
Some(SpriteKind::PinkFlower) => flowers.push(pos),
|
Some(SpriteKind::Reed) => reeds.push(pos),
|
||||||
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
|
Some(SpriteKind::PinkFlower) => flowers.push(pos),
|
||||||
Some(SpriteKind::RedFlower) => flowers.push(pos),
|
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
|
||||||
Some(SpriteKind::WhiteFlower) => flowers.push(pos),
|
Some(SpriteKind::RedFlower) => flowers.push(pos),
|
||||||
Some(SpriteKind::YellowFlower) => flowers.push(pos),
|
Some(SpriteKind::WhiteFlower) => flowers.push(pos),
|
||||||
Some(SpriteKind::Sunflower) => flowers.push(pos),
|
Some(SpriteKind::YellowFlower) => flowers.push(pos),
|
||||||
_ => {},
|
Some(SpriteKind::Sunflower) => flowers.push(pos),
|
||||||
},
|
_ => {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if block.is_collectible() {
|
||||||
|
interactables.push(pos);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -66,6 +75,7 @@ impl BlocksOfInterest {
|
|||||||
beehives,
|
beehives,
|
||||||
reeds,
|
reeds,
|
||||||
flowers,
|
flowers,
|
||||||
|
interactables,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,9 @@ use common::{
|
|||||||
assets::Asset,
|
assets::Asset,
|
||||||
comp,
|
comp,
|
||||||
comp::{
|
comp::{
|
||||||
ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel, MAX_MOUNT_RANGE_SQR,
|
ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel,
|
||||||
MAX_PICKUP_RANGE_SQR,
|
|
||||||
},
|
},
|
||||||
|
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
|
||||||
event::EventBus,
|
event::EventBus,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
span,
|
span,
|
||||||
@ -26,6 +26,7 @@ use common::{
|
|||||||
util::Dir,
|
util::Dir,
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
use specs::{Join, WorldExt};
|
use specs::{Join, WorldExt};
|
||||||
use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration};
|
use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
@ -205,6 +206,7 @@ impl PlayState for SessionState {
|
|||||||
|
|
||||||
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
||||||
span!(_guard, "tick", "<Session as PlayState>::tick");
|
span!(_guard, "tick", "<Session as PlayState>::tick");
|
||||||
|
// TODO: let mut client = self.client.borrow_mut();
|
||||||
// NOTE: Not strictly necessary, but useful for hotloading translation changes.
|
// NOTE: Not strictly necessary, but useful for hotloading translation changes.
|
||||||
self.voxygen_i18n = VoxygenLocalization::load_expect(&i18n_asset_key(
|
self.voxygen_i18n = VoxygenLocalization::load_expect(&i18n_asset_key(
|
||||||
&global_state.settings.language.selected_language,
|
&global_state.settings.language.selected_language,
|
||||||
@ -272,16 +274,18 @@ impl PlayState for SessionState {
|
|||||||
.get(self.client.borrow().entity())
|
.get(self.client.borrow().entity())
|
||||||
.is_some();
|
.is_some();
|
||||||
|
|
||||||
// Only highlight collectables
|
let interactable = select_interactable(&self.client.borrow(), self.target_entity, select_pos, &self.scene);
|
||||||
self.scene.set_select_pos(select_pos.filter(|sp| {
|
|
||||||
self.client
|
// Only highlight interactables
|
||||||
.borrow()
|
// unless in build mode where select_pos highlighted
|
||||||
.state()
|
self.scene.set_select_pos(
|
||||||
.terrain()
|
select_pos
|
||||||
.get(*sp)
|
.filter(|_| can_build)
|
||||||
.map(|b| b.is_collectible() || can_build)
|
.or_else(|| match interactable {
|
||||||
.unwrap_or(false)
|
Some(Interactable::Block(_, block_pos)) => Some(block_pos),
|
||||||
}));
|
_ => None,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Handle window events.
|
// Handle window events.
|
||||||
for event in events {
|
for event in events {
|
||||||
@ -457,36 +461,19 @@ impl PlayState for SessionState {
|
|||||||
.copied();
|
.copied();
|
||||||
if let Some(player_pos) = player_pos {
|
if let Some(player_pos) = player_pos {
|
||||||
// Find closest mountable entity
|
// Find closest mountable entity
|
||||||
let mut closest_mountable: Option<(specs::Entity, i32)> = None;
|
let closest_mountable_entity = (
|
||||||
|
|
||||||
for (entity, pos, ms) in (
|
|
||||||
&client.state().ecs().entities(),
|
&client.state().ecs().entities(),
|
||||||
&client.state().ecs().read_storage::<comp::Pos>(),
|
&client.state().ecs().read_storage::<comp::Pos>(),
|
||||||
&client.state().ecs().read_storage::<comp::MountState>(),
|
&client.state().ecs().read_storage::<comp::MountState>(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
.filter(|(entity, _, _)| *entity != client.entity())
|
.filter(|(entity, _, mount_state)| *entity != client.entity()
|
||||||
{
|
&& **mount_state == comp::MountState::Unmounted
|
||||||
if comp::MountState::Unmounted != *ms {
|
)
|
||||||
continue;
|
.map(|(entity, pos, _)| (entity, player_pos.0.distance_squared(pos.0)))
|
||||||
}
|
.filter(|(_, dist_sqr)| *dist_sqr < MAX_MOUNT_RANGE.powi(2))
|
||||||
|
.min_by_key(|(_, dist_sqr)| OrderedFloat(*dist_sqr));
|
||||||
let dist =
|
if let Some((mountee_entity, _)) = closest_mountable_entity {
|
||||||
(player_pos.0.distance_squared(pos.0) * 1000.0) as i32;
|
|
||||||
if dist > MAX_MOUNT_RANGE_SQR {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(previous) = closest_mountable.as_mut() {
|
|
||||||
if dist < previous.1 {
|
|
||||||
*previous = (entity, dist);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
closest_mountable = Some((entity, dist));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((mountee_entity, _)) = closest_mountable {
|
|
||||||
client.mount(mountee_entity);
|
client.mount(mountee_entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -498,40 +485,21 @@ impl PlayState for SessionState {
|
|||||||
self.key_state.collect = state;
|
self.key_state.collect = state;
|
||||||
|
|
||||||
if state {
|
if state {
|
||||||
let mut client = self.client.borrow_mut();
|
if let Some(interactable) = interactable {
|
||||||
|
let mut client = self.client.borrow_mut();
|
||||||
// Collect terrain sprites
|
match interactable {
|
||||||
if let Some(select_pos) = self.scene.select_pos() {
|
Interactable::Block(block, pos) => if block.is_collectible() {
|
||||||
client.collect_block(select_pos);
|
client.collect_block(pos);
|
||||||
}
|
},
|
||||||
|
Interactable::Entity(entity) => if client
|
||||||
// Collect lootable entities
|
.state()
|
||||||
let player_pos = client
|
.ecs()
|
||||||
.state()
|
.read_storage::<comp::Item>()
|
||||||
.read_storage::<comp::Pos>()
|
.get(entity)
|
||||||
.get(client.entity())
|
.is_some()
|
||||||
.copied();
|
{
|
||||||
|
client.pick_up(entity);
|
||||||
if let Some(player_pos) = player_pos {
|
},
|
||||||
let entity = self.target_entity.or_else(|| {
|
|
||||||
(
|
|
||||||
&client.state().ecs().entities(),
|
|
||||||
&client.state().ecs().read_storage::<comp::Pos>(),
|
|
||||||
&client.state().ecs().read_storage::<comp::Item>(),
|
|
||||||
)
|
|
||||||
.join()
|
|
||||||
.filter(|(_, pos, _)| {
|
|
||||||
pos.0.distance_squared(player_pos.0)
|
|
||||||
< MAX_PICKUP_RANGE_SQR
|
|
||||||
})
|
|
||||||
.min_by_key(|(_, pos, _)| {
|
|
||||||
(pos.0.distance_squared(player_pos.0) * 1000.0) as i32
|
|
||||||
})
|
|
||||||
.map(|(entity, _, _)| entity)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(entity) = entity {
|
|
||||||
client.pick_up(entity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1162,6 +1130,7 @@ fn under_cursor(
|
|||||||
Option<Vec3<i32>>,
|
Option<Vec3<i32>>,
|
||||||
Option<(specs::Entity, f32)>,
|
Option<(specs::Entity, f32)>,
|
||||||
) {
|
) {
|
||||||
|
span!(_guard, "under_cursor");
|
||||||
// Choose a spot above the player's head for item distance checks
|
// Choose a spot above the player's head for item distance checks
|
||||||
let player_entity = client.entity();
|
let player_entity = client.entity();
|
||||||
let player_pos = match client
|
let player_pos = match client
|
||||||
@ -1184,7 +1153,7 @@ fn under_cursor(
|
|||||||
// The ray hit something, is it within range?
|
// The ray hit something, is it within range?
|
||||||
let (build_pos, select_pos) = if matches!(cam_ray.1, Ok(Some(_)) if
|
let (build_pos, select_pos) = if matches!(cam_ray.1, Ok(Some(_)) if
|
||||||
player_pos.distance_squared(cam_pos + cam_dir * cam_dist)
|
player_pos.distance_squared(cam_pos + cam_dir * cam_dist)
|
||||||
<= MAX_PICKUP_RANGE_SQR)
|
<= MAX_PICKUP_RANGE.powi(2))
|
||||||
{
|
{
|
||||||
(
|
(
|
||||||
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)),
|
||||||
@ -1253,3 +1222,113 @@ fn under_cursor(
|
|||||||
// TODO: consider setting build/select to None when targeting an entity
|
// TODO: consider setting build/select to None when targeting an entity
|
||||||
(build_pos, select_pos, target_entity)
|
(build_pos, select_pos, target_entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Interactable {
|
||||||
|
Block(Block, Vec3<i32>),
|
||||||
|
Entity(specs::Entity),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 entity (if interactable) (entities can't be target through
|
||||||
|
/// blocks) 2) Selected block (if interactabl)
|
||||||
|
/// 3) Closest of nearest interactable entity/block
|
||||||
|
fn select_interactable(
|
||||||
|
client: &Client,
|
||||||
|
target_entity: Option<specs::Entity>,
|
||||||
|
selected_pos: Option<Vec3<i32>>,
|
||||||
|
scene: &Scene,
|
||||||
|
) -> Option<Interactable> {
|
||||||
|
span!(_guard, "select_interactable");
|
||||||
|
use common::{
|
||||||
|
spiral::Spiral2d,
|
||||||
|
terrain::TerrainChunk,
|
||||||
|
vol::RectRasterableVol,
|
||||||
|
};
|
||||||
|
target_entity.map(Interactable::Entity)
|
||||||
|
.or_else(|| selected_pos.and_then(|sp|
|
||||||
|
client.state().terrain().get(sp).ok().copied()
|
||||||
|
.filter(Block::is_collectible).map(|b| Interactable::Block(b, sp))
|
||||||
|
))
|
||||||
|
.or_else(|| {
|
||||||
|
let ecs = client.state().ecs();
|
||||||
|
let player_entity = client.entity();
|
||||||
|
ecs
|
||||||
|
.read_storage::<comp::Pos>()
|
||||||
|
.get(player_entity).and_then(|player_pos| {
|
||||||
|
let closest_interactable_entity = (
|
||||||
|
&ecs.entities(),
|
||||||
|
&ecs.read_storage::<comp::Pos>(),
|
||||||
|
ecs.read_storage::<comp::Scale>().maybe(),
|
||||||
|
&ecs.read_storage::<comp::Body>(),
|
||||||
|
// Must have this comp to be interactable (for now)
|
||||||
|
&ecs.read_storage::<comp::Item>(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.filter(|(e, _, _, _, _)| *e != player_entity)
|
||||||
|
.map(|(e, p, s, b, _)| {
|
||||||
|
let radius = s.map_or(1.0, |s| s.0) * b.radius();
|
||||||
|
// Distance squared from player to the entity
|
||||||
|
// Note: the position of entities is currently at their feet so this
|
||||||
|
// distance is between their feet positions
|
||||||
|
let dist_sqr = p.0.distance_squared(player_pos.0);
|
||||||
|
(e, radius, dist_sqr)
|
||||||
|
})
|
||||||
|
// Roughly filter out entities farther than interaction distance
|
||||||
|
.filter(|(_, r, d_sqr)| *d_sqr <= MAX_PICKUP_RANGE.powi(2) + 2.0 * MAX_PICKUP_RANGE * r + r.powi(2))
|
||||||
|
// Note: entities are approximated as spheres here
|
||||||
|
// to determine which is closer
|
||||||
|
// Substract sphere radius from distance to the player
|
||||||
|
.map(|(e, r, d_sqr)| (e, d_sqr.sqrt() - r))
|
||||||
|
.min_by_key(|(_, dist)| OrderedFloat(*dist));
|
||||||
|
|
||||||
|
// Only search as far as closest interactable entity
|
||||||
|
let search_dist = closest_interactable_entity
|
||||||
|
.map_or(MAX_PICKUP_RANGE, |(_, dist)| dist);
|
||||||
|
let player_chunk = player_pos.0.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||||
|
(e.floor() as i32).div_euclid(sz as i32)
|
||||||
|
});
|
||||||
|
let terrain = scene.terrain();
|
||||||
|
|
||||||
|
// Find closest interactable block
|
||||||
|
// TODO: consider doing this one first?
|
||||||
|
let closest_interactable_block_pos = Spiral2d::new()
|
||||||
|
// TODO: this formula for the number to take was guessed
|
||||||
|
// Note: assume RECT_SIZE.x == RECT_SIZE.y
|
||||||
|
.take(((search_dist / TerrainChunk::RECT_SIZE.x as f32).ceil() as usize * 2 + 1).pow(2))
|
||||||
|
.flat_map(|offset| {
|
||||||
|
let chunk_pos = player_chunk + offset;
|
||||||
|
let chunk_voxel_pos =
|
||||||
|
Vec3::<i32>::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
||||||
|
terrain.get(chunk_pos).map(|data| (data, chunk_voxel_pos))
|
||||||
|
})
|
||||||
|
// TODO: maybe we could make this more efficient by putting the
|
||||||
|
// interactables is some sort of spatial structure
|
||||||
|
.flat_map(|(chunk_data, chunk_pos)| {
|
||||||
|
chunk_data
|
||||||
|
.blocks_of_interest
|
||||||
|
.interactables
|
||||||
|
.iter()
|
||||||
|
.map(move |block_offset| chunk_pos + block_offset)
|
||||||
|
})
|
||||||
|
// TODO: confirm that adding 0.5 here is correct
|
||||||
|
.map(|block_pos| (
|
||||||
|
block_pos,
|
||||||
|
block_pos.map(|e| e as f32 + 0.5)
|
||||||
|
.distance_squared(player_pos.0)
|
||||||
|
))
|
||||||
|
.min_by_key(|(_, dist_sqr)| OrderedFloat(*dist_sqr));
|
||||||
|
|
||||||
|
// Pick closer one if they exist
|
||||||
|
closest_interactable_block_pos
|
||||||
|
.filter(|(_, dist_sqr)| search_dist.powi(2) > *dist_sqr)
|
||||||
|
.and_then(|(block_pos, _)|
|
||||||
|
client.state().terrain().get(block_pos).ok().copied()
|
||||||
|
.map(|b| Interactable::Block(b, block_pos))
|
||||||
|
)
|
||||||
|
.or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user