mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Improvements and fixes for interacting/collecting
* Inert entities like arrows no longer block interactions like picking up items! Logic looking for the closest entity will skip them. * When pickaxe is not equipped and wielded we now show "Needs Pickaxe" as the hint text for mineable blocks. * Mineable blocks that aren't pointed at now show the mining text hint instead of the text hint used for regular collectible blocks. * Fixed recent bug where all interactables were showing the open text hint. * Split `BlockInteraction` out of the `Interaction` enum in voxygen since we were using this enum for two different things.
This commit is contained in:
parent
19b5ed3487
commit
6b8e22d6cc
@ -38,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Lamps, embers and campfires use glowing indices
|
- Lamps, embers and campfires use glowing indices
|
||||||
- Non-potion drinks no longer heal as much as potions.
|
- Non-potion drinks no longer heal as much as potions.
|
||||||
- Added SFX to the new sword abilities
|
- Added SFX to the new sword abilities
|
||||||
|
- Fixed various issues with showing the correct text hint for interactable blocks.
|
||||||
|
- Intert entities like arrows no longer obstruct interacting with nearby entities/blocks.
|
||||||
|
|
||||||
## [0.14.0] - 2023-01-07
|
## [0.14.0] - 2023-01-07
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ hud-use = Use
|
|||||||
hud-unlock-requires = Open with { $item }
|
hud-unlock-requires = Open with { $item }
|
||||||
hud-unlock-consumes = Use { $item } to open
|
hud-unlock-consumes = Use { $item } to open
|
||||||
hud-mine = Mine
|
hud-mine = Mine
|
||||||
|
hud-needs_pickaxe = Needs Pickaxe
|
||||||
hud-talk = Talk
|
hud-talk = Talk
|
||||||
hud-trade = Trade
|
hud-trade = Trade
|
||||||
hud-mount = Mount
|
hud-mount = Mount
|
||||||
|
@ -17,9 +17,9 @@ use crate::{
|
|||||||
event::{LocalEvent, ServerEvent},
|
event::{LocalEvent, ServerEvent},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
states::{behavior::JoinData, utils::CharacterState::Idle, *},
|
states::{behavior::JoinData, utils::CharacterState::Idle, *},
|
||||||
terrain::{TerrainChunkSize, UnlockKind},
|
terrain::{TerrainGrid, UnlockKind},
|
||||||
util::Dir,
|
util::Dir,
|
||||||
vol::{ReadVol, RectVolSize},
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use core::hash::BuildHasherDefault;
|
use core::hash::BuildHasherDefault;
|
||||||
use fxhash::FxHasher64;
|
use fxhash::FxHasher64;
|
||||||
@ -844,10 +844,7 @@ pub fn handle_manipulate_loadout(
|
|||||||
if close_to_sprite {
|
if close_to_sprite {
|
||||||
// First, get sprite data for position, if there is a sprite
|
// First, get sprite data for position, if there is a sprite
|
||||||
use sprite_interact::SpriteInteractKind;
|
use sprite_interact::SpriteInteractKind;
|
||||||
let sprite_chunk_pos = sprite_pos
|
let sprite_chunk_pos = TerrainGrid::chunk_offs(sprite_pos);
|
||||||
.xy()
|
|
||||||
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.rem_euclid(sz as i32))
|
|
||||||
.with_z(sprite_pos.z);
|
|
||||||
let sprite_cfg = data
|
let sprite_cfg = data
|
||||||
.terrain
|
.terrain
|
||||||
.pos_chunk(sprite_pos)
|
.pos_chunk(sprite_pos)
|
||||||
|
@ -65,12 +65,9 @@ use crate::{
|
|||||||
game_input::GameInput,
|
game_input::GameInput,
|
||||||
hud::{img_ids::ImgsRot, prompt_dialog::DialogOutcomeEvent},
|
hud::{img_ids::ImgsRot, prompt_dialog::DialogOutcomeEvent},
|
||||||
render::UiDrawer,
|
render::UiDrawer,
|
||||||
scene::{
|
scene::camera::{self, Camera},
|
||||||
camera::{self, Camera},
|
|
||||||
terrain::Interaction,
|
|
||||||
},
|
|
||||||
session::{
|
session::{
|
||||||
interactable::Interactable,
|
interactable::{BlockInteraction, Interactable},
|
||||||
settings_change::{
|
settings_change::{
|
||||||
Audio, Chat as ChatChange, Interface as InterfaceChange, SettingsChange,
|
Audio, Chat as ChatChange, Interface as InterfaceChange, SettingsChange,
|
||||||
},
|
},
|
||||||
@ -673,6 +670,7 @@ pub struct DebugInfo {
|
|||||||
|
|
||||||
pub struct HudInfo {
|
pub struct HudInfo {
|
||||||
pub is_aiming: bool,
|
pub is_aiming: bool,
|
||||||
|
pub is_mining: bool,
|
||||||
pub is_first_person: bool,
|
pub is_first_person: bool,
|
||||||
pub viewpoint_entity: specs::Entity,
|
pub viewpoint_entity: specs::Entity,
|
||||||
pub mutable_viewpoint: bool,
|
pub mutable_viewpoint: bool,
|
||||||
@ -2019,7 +2017,10 @@ impl Hud {
|
|||||||
pickup_failed_pulse: self.failed_entity_pickups.get(&entity).cloned(),
|
pickup_failed_pulse: self.failed_entity_pickups.get(&entity).cloned(),
|
||||||
},
|
},
|
||||||
&self.fonts,
|
&self.fonts,
|
||||||
vec![(GameInput::Interact, i18n.get_msg("hud-pick_up").to_string())],
|
vec![(
|
||||||
|
Some(GameInput::Interact),
|
||||||
|
i18n.get_msg("hud-pick_up").to_string(),
|
||||||
|
)],
|
||||||
)
|
)
|
||||||
.set(overitem_id, ui_widgets);
|
.set(overitem_id, ui_widgets);
|
||||||
}
|
}
|
||||||
@ -2039,31 +2040,46 @@ impl Hud {
|
|||||||
let over_pos = pos + Vec3::unit_z() * 0.7;
|
let over_pos = pos + Vec3::unit_z() * 0.7;
|
||||||
|
|
||||||
let interaction_text = || match interaction {
|
let interaction_text = || match interaction {
|
||||||
Interaction::Collect => {
|
BlockInteraction::Collect => {
|
||||||
vec![(GameInput::Interact, i18n.get_msg("hud-collect").to_string())]
|
vec![(
|
||||||
|
Some(GameInput::Interact),
|
||||||
|
i18n.get_msg("hud-collect").to_string(),
|
||||||
|
)]
|
||||||
},
|
},
|
||||||
Interaction::Craft(_) => {
|
BlockInteraction::Craft(_) => {
|
||||||
vec![(GameInput::Interact, i18n.get_msg("hud-use").to_string())]
|
vec![(
|
||||||
|
Some(GameInput::Interact),
|
||||||
|
i18n.get_msg("hud-use").to_string(),
|
||||||
|
)]
|
||||||
},
|
},
|
||||||
Interaction::Unlock(kind) => vec![(GameInput::Interact, match kind {
|
BlockInteraction::Unlock(kind) => {
|
||||||
UnlockKind::Free => i18n.get_msg("hud-open").to_string(),
|
vec![(Some(GameInput::Interact), match kind {
|
||||||
UnlockKind::Requires(item) => i18n
|
UnlockKind::Free => i18n.get_msg("hud-open").to_string(),
|
||||||
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
|
UnlockKind::Requires(item) => i18n
|
||||||
"item" => item.as_ref().itemdef_id()
|
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
|
||||||
.map(|id| Item::new_from_asset_expect(id).describe())
|
"item" => item.as_ref().itemdef_id()
|
||||||
.unwrap_or_else(|| "modular item".to_string()),
|
.map(|id| Item::new_from_asset_expect(id).describe())
|
||||||
})
|
.unwrap_or_else(|| "modular item".to_string()),
|
||||||
.to_string(),
|
})
|
||||||
UnlockKind::Consumes(item) => i18n
|
.to_string(),
|
||||||
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
|
UnlockKind::Consumes(item) => i18n
|
||||||
"item" => item.as_ref().itemdef_id()
|
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
|
||||||
.map(|id| Item::new_from_asset_expect(id).describe())
|
"item" => item.as_ref().itemdef_id()
|
||||||
.unwrap_or_else(|| "modular item".to_string()),
|
.map(|id| Item::new_from_asset_expect(id).describe())
|
||||||
})
|
.unwrap_or_else(|| "modular item".to_string()),
|
||||||
.to_string(),
|
})
|
||||||
})],
|
.to_string(),
|
||||||
Interaction::Mine => {
|
})]
|
||||||
vec![(GameInput::Primary, i18n.get_msg("hud-mine").to_string())]
|
},
|
||||||
|
BlockInteraction::Mine => {
|
||||||
|
if info.is_mining {
|
||||||
|
vec![(
|
||||||
|
Some(GameInput::Primary),
|
||||||
|
i18n.get_msg("hud-mine").to_string(),
|
||||||
|
)]
|
||||||
|
} else {
|
||||||
|
vec![(None, i18n.get_msg("hud-needs_pickaxe").to_string())]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2083,7 +2099,10 @@ impl Hud {
|
|||||||
overitem_properties,
|
overitem_properties,
|
||||||
self.pulse,
|
self.pulse,
|
||||||
&global_state.window.key_layout,
|
&global_state.window.key_layout,
|
||||||
vec![(GameInput::Interact, i18n.get_msg("hud-open").to_string())],
|
vec![(
|
||||||
|
Some(GameInput::Interact),
|
||||||
|
i18n.get_msg("hud-open").to_string(),
|
||||||
|
)],
|
||||||
)
|
)
|
||||||
.x_y(0.0, 100.0)
|
.x_y(0.0, 100.0)
|
||||||
.position_ingame(over_pos)
|
.position_ingame(over_pos)
|
||||||
@ -2152,7 +2171,10 @@ impl Hud {
|
|||||||
overitem_properties,
|
overitem_properties,
|
||||||
self.pulse,
|
self.pulse,
|
||||||
&global_state.window.key_layout,
|
&global_state.window.key_layout,
|
||||||
vec![(GameInput::Interact, i18n.get_msg("hud-sit").to_string())],
|
vec![(
|
||||||
|
Some(GameInput::Interact),
|
||||||
|
i18n.get_msg("hud-sit").to_string(),
|
||||||
|
)],
|
||||||
)
|
)
|
||||||
.x_y(0.0, 100.0)
|
.x_y(0.0, 100.0)
|
||||||
.position_ingame(over_pos)
|
.position_ingame(over_pos)
|
||||||
|
@ -46,7 +46,8 @@ pub struct Overitem<'a> {
|
|||||||
properties: OveritemProperties,
|
properties: OveritemProperties,
|
||||||
pulse: f32,
|
pulse: f32,
|
||||||
key_layout: &'a Option<KeyLayout>,
|
key_layout: &'a Option<KeyLayout>,
|
||||||
interaction_options: Vec<(GameInput, String)>,
|
// GameInput optional so we can just show stuff like "needs pickaxe"
|
||||||
|
interaction_options: Vec<(Option<GameInput>, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Overitem<'a> {
|
impl<'a> Overitem<'a> {
|
||||||
@ -60,7 +61,7 @@ impl<'a> Overitem<'a> {
|
|||||||
properties: OveritemProperties,
|
properties: OveritemProperties,
|
||||||
pulse: f32,
|
pulse: f32,
|
||||||
key_layout: &'a Option<KeyLayout>,
|
key_layout: &'a Option<KeyLayout>,
|
||||||
interaction_options: Vec<(GameInput, String)>,
|
interaction_options: Vec<(Option<GameInput>, String)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
@ -177,19 +178,20 @@ impl<'a> Widget for Overitem<'a> {
|
|||||||
.interaction_options
|
.interaction_options
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(input, action)| {
|
.filter_map(|(input, action)| {
|
||||||
Some((
|
let binding = if let Some(input) = input {
|
||||||
self.controls
|
Some(self.controls.get_binding(*input)?)
|
||||||
.get_binding(*input)
|
} else {
|
||||||
.filter(|_| self.properties.active)?,
|
None
|
||||||
action,
|
};
|
||||||
))
|
Some((binding, action))
|
||||||
})
|
})
|
||||||
.map(|(input, action)| {
|
.map(|(input, action)| {
|
||||||
format!(
|
if let Some(input) = input {
|
||||||
"{} {}",
|
let input = input.display_string(self.key_layout);
|
||||||
input.display_string(self.key_layout).as_str(),
|
format!("{} {action}", input.as_str())
|
||||||
action
|
} else {
|
||||||
)
|
action.to_string()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use crate::hud::CraftingTab;
|
use crate::hud::CraftingTab;
|
||||||
use common::terrain::{BlockKind, SpriteKind, TerrainChunk, UnlockKind};
|
use common::terrain::{BlockKind, SpriteKind, TerrainChunk};
|
||||||
use common_base::span;
|
use common_base::span;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_chacha::ChaCha8Rng;
|
use rand_chacha::ChaCha8Rng;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Interaction {
|
pub enum Interaction {
|
||||||
|
/// This covers mining, unlocking, and regular collectable things (e.g.
|
||||||
|
/// twigs).
|
||||||
Collect,
|
Collect,
|
||||||
Unlock(UnlockKind),
|
|
||||||
Craft(CraftingTab),
|
Craft(CraftingTab),
|
||||||
Mine,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum FireplaceType {
|
pub enum FireplaceType {
|
||||||
@ -169,17 +169,7 @@ impl BlocksOfInterest {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if block.collectible_id().is_some() {
|
if block.collectible_id().is_some() {
|
||||||
interactables.push((
|
interactables.push((pos, Interaction::Collect));
|
||||||
pos,
|
|
||||||
block
|
|
||||||
.get_sprite()
|
|
||||||
.map(|s| {
|
|
||||||
Interaction::Unlock(
|
|
||||||
s.unlock_condition(chunk.meta().sprite_cfg_at(pos).cloned()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or(Interaction::Collect),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if let Some(glow) = block.get_glow() {
|
if let Some(glow) = block.get_glow() {
|
||||||
// Currently, we count filled blocks as 'minor' lights, and sprites as
|
// Currently, we count filled blocks as 'minor' lights, and sprites as
|
||||||
|
@ -12,19 +12,30 @@ use common::{
|
|||||||
consts::MAX_PICKUP_RANGE,
|
consts::MAX_PICKUP_RANGE,
|
||||||
link::Is,
|
link::Is,
|
||||||
mounting::Mount,
|
mounting::Mount,
|
||||||
terrain::Block,
|
terrain::{Block, TerrainGrid, UnlockKind},
|
||||||
util::find_dist::{Cube, Cylinder, FindDist},
|
util::find_dist::{Cube, Cylinder, FindDist},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_base::span;
|
use common_base::span;
|
||||||
|
|
||||||
use crate::scene::{terrain::Interaction, Scene};
|
use crate::{
|
||||||
|
hud::CraftingTab,
|
||||||
|
scene::{terrain::Interaction, Scene},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum BlockInteraction {
|
||||||
|
Collect,
|
||||||
|
Unlock(UnlockKind),
|
||||||
|
Craft(CraftingTab),
|
||||||
|
// TODO: mining blocks don't use the interaction key, so it might not be the best abstraction
|
||||||
|
// to have them here, will see how things turn out
|
||||||
|
Mine,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: extract mining blocks (the None case in the Block variant) from this
|
|
||||||
// enum since they don't use the interaction key
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Interactable {
|
pub enum Interactable {
|
||||||
Block(Block, Vec3<i32>, Interaction),
|
Block(Block, Vec3<i32>, BlockInteraction),
|
||||||
Entity(specs::Entity),
|
Entity(specs::Entity),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +46,44 @@ impl Interactable {
|
|||||||
Self::Block(_, _, _) => None,
|
Self::Block(_, _, _) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_block_pos(
|
||||||
|
terrain: &TerrainGrid,
|
||||||
|
pos: Vec3<i32>,
|
||||||
|
interaction: Interaction,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let Ok(&block) = terrain.get(pos) else { return None };
|
||||||
|
let block_interaction = match interaction {
|
||||||
|
Interaction::Collect => {
|
||||||
|
// Check if this is an unlockable sprite
|
||||||
|
let unlock = block.get_sprite().and_then(|sprite| {
|
||||||
|
let Some(chunk) = terrain.pos_chunk(pos) else { return None };
|
||||||
|
let sprite_chunk_pos = TerrainGrid::chunk_offs(pos);
|
||||||
|
let sprite_cfg = chunk.meta().sprite_cfg_at(sprite_chunk_pos);
|
||||||
|
let unlock_condition = sprite.unlock_condition(sprite_cfg.cloned());
|
||||||
|
// HACK: No other way to distinguish between things that should be unlockable
|
||||||
|
// and regular sprites with the current unlock_condition method so we hack
|
||||||
|
// around that by saying that it is a regular collectible sprite if
|
||||||
|
// `unlock_condition` returns UnlockKind::Free and the cfg was `None`.
|
||||||
|
if sprite_cfg.is_some() || !matches!(&unlock_condition, UnlockKind::Free) {
|
||||||
|
Some(unlock_condition)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(unlock) = unlock {
|
||||||
|
BlockInteraction::Unlock(unlock)
|
||||||
|
} else if block.mine_tool().is_some() {
|
||||||
|
BlockInteraction::Mine
|
||||||
|
} else {
|
||||||
|
BlockInteraction::Collect
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Interaction::Craft(tab) => BlockInteraction::Craft(tab),
|
||||||
|
};
|
||||||
|
Some(Self::Block(block, pos, block_interaction))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select interactable to highlight, display interaction text for, and to
|
/// Select interactable to highlight, display interaction text for, and to
|
||||||
@ -62,13 +111,10 @@ pub(super) fn select_interactable(
|
|||||||
collect_target.map(|t| t.distance),
|
collect_target.map(|t| t.distance),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
fn get_block<T>(client: &Client, target: Target<T>) -> Option<Block> {
|
let terrain = client.state().terrain();
|
||||||
client
|
|
||||||
.state()
|
fn get_block<T>(terrain: &common::terrain::TerrainGrid, target: Target<T>) -> Option<Block> {
|
||||||
.terrain()
|
terrain.get(target.position_int()).ok().copied()
|
||||||
.get(target.position_int())
|
|
||||||
.ok()
|
|
||||||
.copied()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(interactable) = entity_target
|
if let Some(interactable) = entity_target
|
||||||
@ -83,8 +129,9 @@ pub(super) fn select_interactable(
|
|||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
collect_target.and_then(|t| {
|
collect_target.and_then(|t| {
|
||||||
if Some(t.distance) == nearest_dist {
|
if Some(t.distance) == nearest_dist {
|
||||||
get_block(client, t)
|
get_block(&terrain, t).map(|b| {
|
||||||
.map(|b| Interactable::Block(b, t.position_int(), Interaction::Collect))
|
Interactable::Block(b, t.position_int(), BlockInteraction::Collect)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -93,14 +140,18 @@ pub(super) fn select_interactable(
|
|||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
mine_target.and_then(|t| {
|
mine_target.and_then(|t| {
|
||||||
if Some(t.distance) == nearest_dist {
|
if Some(t.distance) == nearest_dist {
|
||||||
get_block(client, t).and_then(|b| {
|
get_block(&terrain, t).and_then(|b| {
|
||||||
// Handling edge detection. sometimes the casting (in Target mod) returns a
|
// Handling edge detection. sometimes the casting (in Target mod) returns a
|
||||||
// position which is actually empty, which we do not want labeled as an
|
// position which is actually empty, which we do not want labeled as an
|
||||||
// interactable. We are only returning the mineable air
|
// interactable. We are only returning the mineable air
|
||||||
// elements (e.g. minerals). The mineable weakrock are used
|
// elements (e.g. minerals). The mineable weakrock are used
|
||||||
// in the terrain selected_pos, but is not an interactable.
|
// in the terrain selected_pos, but is not an interactable.
|
||||||
if b.mine_tool().is_some() && b.is_air() {
|
if b.mine_tool().is_some() && b.is_air() {
|
||||||
Some(Interactable::Block(b, t.position_int(), Interaction::Mine))
|
Some(Interactable::Block(
|
||||||
|
b,
|
||||||
|
t.position_int(),
|
||||||
|
BlockInteraction::Mine,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -124,6 +175,9 @@ pub(super) fn select_interactable(
|
|||||||
let colliders = ecs.read_storage::<comp::Collider>();
|
let colliders = ecs.read_storage::<comp::Collider>();
|
||||||
let char_states = ecs.read_storage::<comp::CharacterState>();
|
let char_states = ecs.read_storage::<comp::CharacterState>();
|
||||||
let is_mount = ecs.read_storage::<Is<Mount>>();
|
let is_mount = ecs.read_storage::<Is<Mount>>();
|
||||||
|
let bodies = ecs.read_storage::<comp::Body>();
|
||||||
|
let items = ecs.read_storage::<comp::Item>();
|
||||||
|
let stats = ecs.read_storage::<comp::Stats>();
|
||||||
|
|
||||||
let player_cylinder = Cylinder::from_components(
|
let player_cylinder = Cylinder::from_components(
|
||||||
player_pos,
|
player_pos,
|
||||||
@ -135,20 +189,39 @@ pub(super) fn select_interactable(
|
|||||||
let closest_interactable_entity = (
|
let closest_interactable_entity = (
|
||||||
&ecs.entities(),
|
&ecs.entities(),
|
||||||
&positions,
|
&positions,
|
||||||
|
&bodies,
|
||||||
scales.maybe(),
|
scales.maybe(),
|
||||||
colliders.maybe(),
|
colliders.maybe(),
|
||||||
char_states.maybe(),
|
char_states.maybe(),
|
||||||
!&is_mount,
|
!&is_mount,
|
||||||
|
(stats.mask() | items.mask()).maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
.filter(|(e, _, _, _, _, _)| *e != player_entity)
|
.filter_map(|(e, p, b, s, c, cs, _, has_stats_or_item)| {
|
||||||
.map(|(e, p, s, c, cs, _)| {
|
// Note, if this becomes expensive to compute do it after the distance check!
|
||||||
|
//
|
||||||
|
// The entities that can be interacted with:
|
||||||
|
// * Sitting at campfires (Body::is_campfire)
|
||||||
|
// * Talking/trading with npcs (note hud code uses Alignment but I can't bring
|
||||||
|
// myself to write more code on that depends on having this on the client so
|
||||||
|
// we just check for presence of Stats component for now, it is okay to have
|
||||||
|
// some false positives here as long as it doesn't frequently prevent us from
|
||||||
|
// interacting with actual interactable entities that are closer by)
|
||||||
|
// * Dropped items that can be picked up (Item component)
|
||||||
|
let is_interactable = b.is_campfire() || has_stats_or_item.is_some();
|
||||||
|
|
||||||
|
if e == player_entity || !is_interactable {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
let cylinder = Cylinder::from_components(p.0, s.copied(), c, cs);
|
let cylinder = Cylinder::from_components(p.0, s.copied(), c, cs);
|
||||||
(e, cylinder)
|
// Roughly filter out entities farther than interaction distance
|
||||||
|
if player_cylinder.approx_in_range(cylinder, MAX_PICKUP_RANGE) {
|
||||||
|
Some((e, player_cylinder.min_distance(cylinder)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// Roughly filter out entities farther than interaction distance
|
|
||||||
.filter(|(_, cylinder)| player_cylinder.approx_in_range(*cylinder, MAX_PICKUP_RANGE))
|
|
||||||
.map(|(e, cylinder)| (e, player_cylinder.min_distance(cylinder)))
|
|
||||||
.min_by_key(|(_, dist)| OrderedFloat(*dist));
|
.min_by_key(|(_, dist)| OrderedFloat(*dist));
|
||||||
|
|
||||||
// Only search as far as closest interactable entity
|
// Only search as far as closest interactable entity
|
||||||
@ -156,7 +229,7 @@ pub(super) fn select_interactable(
|
|||||||
let player_chunk = player_pos.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
let player_chunk = player_pos.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||||
(e.floor() as i32).div_euclid(sz as i32)
|
(e.floor() as i32).div_euclid(sz as i32)
|
||||||
});
|
});
|
||||||
let terrain = scene.terrain();
|
let scene_terrain = scene.terrain();
|
||||||
|
|
||||||
// Find closest interactable block
|
// Find closest interactable block
|
||||||
// TODO: consider doing this one first?
|
// TODO: consider doing this one first?
|
||||||
@ -168,7 +241,7 @@ pub(super) fn select_interactable(
|
|||||||
let chunk_pos = player_chunk + offset;
|
let chunk_pos = player_chunk + offset;
|
||||||
let chunk_voxel_pos =
|
let chunk_voxel_pos =
|
||||||
Vec3::<i32>::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
Vec3::<i32>::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
||||||
terrain.get(chunk_pos).map(|data| (data, chunk_voxel_pos))
|
scene_terrain.get(chunk_pos).map(|data| (data, chunk_voxel_pos))
|
||||||
})
|
})
|
||||||
// TODO: maybe we could make this more efficient by putting the
|
// TODO: maybe we could make this more efficient by putting the
|
||||||
// interactables is some sort of spatial structure
|
// interactables is some sort of spatial structure
|
||||||
@ -180,10 +253,10 @@ pub(super) fn select_interactable(
|
|||||||
.map(move |(block_offset, interaction)| (chunk_pos + block_offset, interaction))
|
.map(move |(block_offset, interaction)| (chunk_pos + block_offset, interaction))
|
||||||
})
|
})
|
||||||
.map(|(block_pos, interaction)| (
|
.map(|(block_pos, interaction)| (
|
||||||
block_pos,
|
block_pos,
|
||||||
block_pos.map(|e| e as f32 + 0.5)
|
block_pos.map(|e| e as f32 + 0.5)
|
||||||
.distance_squared(player_pos),
|
.distance_squared(player_pos),
|
||||||
interaction,
|
interaction,
|
||||||
))
|
))
|
||||||
.min_by_key(|(_, dist_sqr, _)| OrderedFloat(*dist_sqr))
|
.min_by_key(|(_, dist_sqr, _)| OrderedFloat(*dist_sqr))
|
||||||
.map(|(block_pos, _, interaction)| (block_pos, interaction));
|
.map(|(block_pos, _, interaction)| (block_pos, interaction));
|
||||||
@ -197,13 +270,7 @@ pub(super) fn select_interactable(
|
|||||||
}) < search_dist
|
}) < search_dist
|
||||||
})
|
})
|
||||||
.and_then(|(block_pos, interaction)| {
|
.and_then(|(block_pos, interaction)| {
|
||||||
client
|
Interactable::from_block_pos(&terrain, block_pos, *interaction)
|
||||||
.state()
|
|
||||||
.terrain()
|
|
||||||
.get(block_pos)
|
|
||||||
.ok()
|
|
||||||
.copied()
|
|
||||||
.map(|b| Interactable::Block(b, block_pos, interaction.clone()))
|
|
||||||
})
|
})
|
||||||
.or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e)))
|
.or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e)))
|
||||||
}
|
}
|
||||||
|
@ -49,13 +49,13 @@ use crate::{
|
|||||||
key_state::KeyState,
|
key_state::KeyState,
|
||||||
menu::char_selection::CharSelectionState,
|
menu::char_selection::CharSelectionState,
|
||||||
render::{Drawer, GlobalsBindGroup},
|
render::{Drawer, GlobalsBindGroup},
|
||||||
scene::{camera, terrain::Interaction, CameraMode, DebugShapeId, Scene, SceneData},
|
scene::{camera, CameraMode, DebugShapeId, Scene, SceneData},
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
window::{AnalogGameInput, Event},
|
window::{AnalogGameInput, Event},
|
||||||
Direction, GlobalState, PlayState, PlayStateResult,
|
Direction, GlobalState, PlayState, PlayStateResult,
|
||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use interactable::{select_interactable, Interactable};
|
use interactable::{select_interactable, BlockInteraction, Interactable};
|
||||||
use settings_change::Language::ChangeLanguage;
|
use settings_change::Language::ChangeLanguage;
|
||||||
use target::targets_under_cursor;
|
use target::targets_under_cursor;
|
||||||
#[cfg(feature = "egui-ui")]
|
#[cfg(feature = "egui-ui")]
|
||||||
@ -930,19 +930,19 @@ impl PlayState for SessionState {
|
|||||||
match interactable {
|
match interactable {
|
||||||
Interactable::Block(block, pos, interaction) => {
|
Interactable::Block(block, pos, interaction) => {
|
||||||
match interaction {
|
match interaction {
|
||||||
Interaction::Collect
|
BlockInteraction::Collect
|
||||||
| Interaction::Unlock(_) => {
|
| BlockInteraction::Unlock(_) => {
|
||||||
if block.is_collectible() {
|
if block.is_collectible() {
|
||||||
client.collect_block(*pos);
|
client.collect_block(*pos);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Interaction::Craft(tab) => {
|
BlockInteraction::Craft(tab) => {
|
||||||
self.hud.show.open_crafting_tab(
|
self.hud.show.open_crafting_tab(
|
||||||
*tab,
|
*tab,
|
||||||
block.get_sprite().map(|s| (*pos, s)),
|
block.get_sprite().map(|s| (*pos, s)),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
Interaction::Mine => {},
|
BlockInteraction::Mine => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Interactable::Entity(entity) => {
|
Interactable::Entity(entity) => {
|
||||||
@ -1366,6 +1366,7 @@ impl PlayState for SessionState {
|
|||||||
global_state.clock.get_stable_dt(),
|
global_state.clock.get_stable_dt(),
|
||||||
HudInfo {
|
HudInfo {
|
||||||
is_aiming,
|
is_aiming,
|
||||||
|
is_mining,
|
||||||
is_first_person: matches!(
|
is_first_person: matches!(
|
||||||
self.scene.camera().get_mode(),
|
self.scene.camera().get_mode(),
|
||||||
camera::CameraMode::FirstPerson
|
camera::CameraMode::FirstPerson
|
||||||
|
@ -100,8 +100,7 @@ pub(super) fn targets_under_cursor(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (collect_pos, _, collect_cam_ray) =
|
let (collect_pos, _, collect_cam_ray) = find_pos(|b: Block| b.is_collectible());
|
||||||
find_pos(|b: Block| matches!(b.collectible_id(), Some(Some(_))));
|
|
||||||
let (mine_pos, _, mine_cam_ray) = is_mining
|
let (mine_pos, _, mine_cam_ray) = is_mining
|
||||||
.then(|| find_pos(|b: Block| b.mine_tool().is_some()))
|
.then(|| find_pos(|b: Block| b.mine_tool().is_some()))
|
||||||
.unwrap_or((None, None, None));
|
.unwrap_or((None, None, None));
|
||||||
|
Loading…
Reference in New Issue
Block a user