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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "osascript"
|
||||
version = "0.3.0"
|
||||
@ -3691,7 +3700,7 @@ dependencies = [
|
||||
"crossbeam-utils 0.7.2",
|
||||
"linked-hash-map",
|
||||
"num_cpus",
|
||||
"ordered-float",
|
||||
"ordered-float 1.1.0",
|
||||
"rustc-hash",
|
||||
"stb_truetype",
|
||||
]
|
||||
@ -4962,6 +4971,7 @@ dependencies = [
|
||||
"native-dialog",
|
||||
"num 0.2.1",
|
||||
"old_school_gfx_glutin_ext",
|
||||
"ordered-float 2.0.0",
|
||||
"rand 0.7.3",
|
||||
"rodio",
|
||||
"ron",
|
||||
@ -5014,7 +5024,7 @@ dependencies = [
|
||||
"minifb",
|
||||
"noise",
|
||||
"num 0.2.1",
|
||||
"ordered-float",
|
||||
"ordered-float 1.1.0",
|
||||
"packed_simd_2",
|
||||
"rand 0.7.3",
|
||||
"rand_chacha 0.2.2",
|
||||
|
@ -8,9 +8,6 @@ use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage, HashMapStorage};
|
||||
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)]
|
||||
pub struct Inventory {
|
||||
slots: Vec<Option<Item>>,
|
||||
|
@ -49,13 +49,13 @@ pub use inputs::CanBuild;
|
||||
pub use inventory::{
|
||||
item,
|
||||
item::{Item, ItemDrop},
|
||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR,
|
||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||
};
|
||||
pub use last::Last;
|
||||
pub use location::{Waypoint, WaypointArea};
|
||||
pub use misc::Object;
|
||||
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 shockwave::{Shockwave, ShockwaveHitEntities};
|
||||
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
|
||||
|
@ -5,7 +5,6 @@ use specs::{Component, FlaggedStorage, NullStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
|
||||
const MAX_ALIAS_LEN: usize = 32;
|
||||
pub const MAX_MOUNT_RANGE_SQR: i32 = 20000;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
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 combat;
|
||||
pub mod comp;
|
||||
pub mod consts;
|
||||
pub mod effect;
|
||||
pub mod event;
|
||||
pub mod explosion;
|
||||
|
@ -3,8 +3,9 @@ use common::{
|
||||
comp::{
|
||||
self, item,
|
||||
slot::{self, Slot},
|
||||
Pos, MAX_PICKUP_RANGE_SQR,
|
||||
Pos,
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
msg::ServerGeneral,
|
||||
recipe::default_recipe_book,
|
||||
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 {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ hashbrown = {version = "0.7.2", features = ["rayon", "serde", "nightly"]}
|
||||
image = {version = "0.23.8", default-features = false, features = ["ico", "png"]}
|
||||
native-dialog = { version = "0.4.2", default-features = false, optional = true }
|
||||
num = "0.2"
|
||||
ordered-float = "2.0.0"
|
||||
rand = "0.7"
|
||||
rodio = {version = "0.11", default-features = false, features = ["wav", "vorbis"]}
|
||||
ron = {version = "0.6", default-features = false}
|
||||
|
@ -1123,7 +1123,7 @@ impl Hud {
|
||||
for (pos, item, distance) in (&entities, &pos, &items)
|
||||
.join()
|
||||
.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(
|
||||
&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
|
||||
let scale: f64 =
|
||||
((1.5 - (self.distance_from_player_sqr / common::comp::MAX_PICKUP_RANGE_SQR)) * 20.0)
|
||||
.into();
|
||||
let scale: f64 = ((1.5
|
||||
- (self.distance_from_player_sqr / common::consts::MAX_PICKUP_RANGE.powi(2)))
|
||||
* 20.0)
|
||||
.into();
|
||||
let text_font_size = scale * 1.0;
|
||||
let text_pos_y = scale * 1.2;
|
||||
let btn_rect_size = scale * 0.8;
|
||||
|
@ -13,6 +13,9 @@ pub struct BlocksOfInterest {
|
||||
pub beehives: Vec<Vec3<i32>>,
|
||||
pub reeds: 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 {
|
||||
@ -24,6 +27,7 @@ impl BlocksOfInterest {
|
||||
let mut beehives = Vec::new();
|
||||
let mut reeds = Vec::new();
|
||||
let mut flowers = Vec::new();
|
||||
let mut interactables = Vec::new();
|
||||
|
||||
chunk
|
||||
.vol_iter(
|
||||
@ -34,29 +38,34 @@ impl BlocksOfInterest {
|
||||
chunk.get_max_z(),
|
||||
),
|
||||
)
|
||||
.for_each(|(pos, block)| match block.kind() {
|
||||
BlockKind::Leaves => {
|
||||
if thread_rng().gen_range(0, 16) == 0 {
|
||||
leaves.push(pos)
|
||||
}
|
||||
},
|
||||
BlockKind::Grass => {
|
||||
if thread_rng().gen_range(0, 16) == 0 {
|
||||
grass.push(pos)
|
||||
}
|
||||
},
|
||||
_ => match block.get_sprite() {
|
||||
Some(SpriteKind::Ember) => embers.push(pos),
|
||||
Some(SpriteKind::Beehive) => beehives.push(pos),
|
||||
Some(SpriteKind::Reed) => reeds.push(pos),
|
||||
Some(SpriteKind::PinkFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::RedFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::WhiteFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::YellowFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::Sunflower) => flowers.push(pos),
|
||||
_ => {},
|
||||
},
|
||||
.for_each(|(pos, block)| {
|
||||
match block.kind() {
|
||||
BlockKind::Leaves => {
|
||||
if thread_rng().gen_range(0, 16) == 0 {
|
||||
leaves.push(pos)
|
||||
}
|
||||
},
|
||||
BlockKind::Grass => {
|
||||
if thread_rng().gen_range(0, 16) == 0 {
|
||||
grass.push(pos)
|
||||
}
|
||||
},
|
||||
_ => match block.get_sprite() {
|
||||
Some(SpriteKind::Ember) => embers.push(pos),
|
||||
Some(SpriteKind::Beehive) => beehives.push(pos),
|
||||
Some(SpriteKind::Reed) => reeds.push(pos),
|
||||
Some(SpriteKind::PinkFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::RedFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::WhiteFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::YellowFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::Sunflower) => flowers.push(pos),
|
||||
_ => {},
|
||||
},
|
||||
}
|
||||
if block.is_collectible() {
|
||||
interactables.push(pos);
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
@ -66,6 +75,7 @@ impl BlocksOfInterest {
|
||||
beehives,
|
||||
reeds,
|
||||
flowers,
|
||||
interactables,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,9 @@ use common::{
|
||||
assets::Asset,
|
||||
comp,
|
||||
comp::{
|
||||
ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel, MAX_MOUNT_RANGE_SQR,
|
||||
MAX_PICKUP_RANGE_SQR,
|
||||
ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel,
|
||||
},
|
||||
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
|
||||
event::EventBus,
|
||||
outcome::Outcome,
|
||||
span,
|
||||
@ -26,6 +26,7 @@ use common::{
|
||||
util::Dir,
|
||||
vol::ReadVol,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use specs::{Join, WorldExt};
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration};
|
||||
use tracing::{error, info};
|
||||
@ -205,6 +206,7 @@ impl PlayState for SessionState {
|
||||
|
||||
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
||||
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.
|
||||
self.voxygen_i18n = VoxygenLocalization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
@ -272,16 +274,18 @@ impl PlayState for SessionState {
|
||||
.get(self.client.borrow().entity())
|
||||
.is_some();
|
||||
|
||||
// Only highlight collectables
|
||||
self.scene.set_select_pos(select_pos.filter(|sp| {
|
||||
self.client
|
||||
.borrow()
|
||||
.state()
|
||||
.terrain()
|
||||
.get(*sp)
|
||||
.map(|b| b.is_collectible() || can_build)
|
||||
.unwrap_or(false)
|
||||
}));
|
||||
let interactable = select_interactable(&self.client.borrow(), self.target_entity, select_pos, &self.scene);
|
||||
|
||||
// Only highlight interactables
|
||||
// unless in build mode where select_pos highlighted
|
||||
self.scene.set_select_pos(
|
||||
select_pos
|
||||
.filter(|_| can_build)
|
||||
.or_else(|| match interactable {
|
||||
Some(Interactable::Block(_, block_pos)) => Some(block_pos),
|
||||
_ => None,
|
||||
})
|
||||
);
|
||||
|
||||
// Handle window events.
|
||||
for event in events {
|
||||
@ -457,36 +461,19 @@ impl PlayState for SessionState {
|
||||
.copied();
|
||||
if let Some(player_pos) = player_pos {
|
||||
// Find closest mountable entity
|
||||
let mut closest_mountable: Option<(specs::Entity, i32)> = None;
|
||||
|
||||
for (entity, pos, ms) in (
|
||||
let closest_mountable_entity = (
|
||||
&client.state().ecs().entities(),
|
||||
&client.state().ecs().read_storage::<comp::Pos>(),
|
||||
&client.state().ecs().read_storage::<comp::MountState>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(entity, _, _)| *entity != client.entity())
|
||||
{
|
||||
if comp::MountState::Unmounted != *ms {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dist =
|
||||
(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 {
|
||||
.filter(|(entity, _, mount_state)| *entity != client.entity()
|
||||
&& **mount_state == comp::MountState::Unmounted
|
||||
)
|
||||
.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));
|
||||
if let Some((mountee_entity, _)) = closest_mountable_entity {
|
||||
client.mount(mountee_entity);
|
||||
}
|
||||
}
|
||||
@ -498,40 +485,21 @@ impl PlayState for SessionState {
|
||||
self.key_state.collect = state;
|
||||
|
||||
if state {
|
||||
let mut client = self.client.borrow_mut();
|
||||
|
||||
// Collect terrain sprites
|
||||
if let Some(select_pos) = self.scene.select_pos() {
|
||||
client.collect_block(select_pos);
|
||||
}
|
||||
|
||||
// Collect lootable entities
|
||||
let player_pos = client
|
||||
.state()
|
||||
.read_storage::<comp::Pos>()
|
||||
.get(client.entity())
|
||||
.copied();
|
||||
|
||||
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);
|
||||
if let Some(interactable) = interactable {
|
||||
let mut client = self.client.borrow_mut();
|
||||
match interactable {
|
||||
Interactable::Block(block, pos) => if block.is_collectible() {
|
||||
client.collect_block(pos);
|
||||
},
|
||||
Interactable::Entity(entity) => if client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<comp::Item>()
|
||||
.get(entity)
|
||||
.is_some()
|
||||
{
|
||||
client.pick_up(entity);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1162,6 +1130,7 @@ fn under_cursor(
|
||||
Option<Vec3<i32>>,
|
||||
Option<(specs::Entity, f32)>,
|
||||
) {
|
||||
span!(_guard, "under_cursor");
|
||||
// Choose a spot above the player's head for item distance checks
|
||||
let player_entity = client.entity();
|
||||
let player_pos = match client
|
||||
@ -1184,7 +1153,7 @@ fn under_cursor(
|
||||
// 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)
|
||||
<= MAX_PICKUP_RANGE.powi(2))
|
||||
{
|
||||
(
|
||||
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
|
||||
(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