From 6ddfd631d6ea8897c532230f8ed4c8d9891ea5e3 Mon Sep 17 00:00:00 2001 From: Isse Date: Wed, 8 Feb 2023 21:56:22 +0100 Subject: [PATCH] don't sit through walls --- common/src/states/utils.rs | 230 ++++++++++++++-------------- voxygen/src/session/interactable.rs | 5 +- voxygen/src/session/mod.rs | 5 +- 3 files changed, 117 insertions(+), 123 deletions(-) diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 861135ac39..4ae31e270c 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -21,9 +21,9 @@ use crate::{ event::{LocalEvent, ServerEvent}, outcome::Outcome, states::{behavior::JoinData, utils::CharacterState::Idle, *}, - terrain::{TerrainGrid, UnlockKind}, + terrain::{TerrainGrid, UnlockKind, SpriteKind}, util::Dir, - vol::ReadVol, terrain::SpriteKind, + vol::ReadVol, }; use core::hash::BuildHasherDefault; use fxhash::FxHasher64; @@ -796,14 +796,15 @@ pub fn sprite_mount_points(sprite_kind: SpriteKind, pos: Vec3, ori: u8) -> }) } -pub const SPRITE_MOUNT_DIST_SQR: f32 = 5.0; +const SPRITE_MOUNT_RANGE: f32 = 2.0; +pub const SPRITE_MOUNT_RANGE_SQR: f32 = SPRITE_MOUNT_RANGE * SPRITE_MOUNT_RANGE; pub fn attempt_mount_sprite(data: &JoinData<'_>, update: &mut StateUpdate, pos: Vec3) { if let Some((kind, ori)) = data.terrain.get(pos).ok().and_then(|block| block.get_sprite().zip(block.get_ori())) { if let Some((mount_pos, mount_dir)) = sprite_mount_points(kind, pos, ori).min_by_key(|(pos, _)| { OrderedFloat(data.pos.0.distance_squared(*pos)) }) { - if mount_pos.distance_squared(data.pos.0) < SPRITE_MOUNT_DIST_SQR { + if reach_block(data.pos.0, pos, SPRITE_MOUNT_RANGE_SQR, data.body, data.terrain) { update.character = CharacterState::MountSprite(mount_sprite::Data { static_data: mount_sprite::StaticData { mount_pos, @@ -886,6 +887,71 @@ pub fn attempt_swap_equipped_weapons(data: &JoinData<'_>, update: &mut StateUpda } } +fn reach_block(player_pos: Vec3, block_pos: Vec3, range: f32, body: &Body, terrain: &TerrainGrid) -> bool { + let block_pos_f32 = block_pos.map(|x| x as f32 + 0.5); + // Closure to check if distance between a point and the block is less than + // MAX_PICKUP_RANGE and the radius of the body + let block_range_check = |pos: Vec3| { + (block_pos_f32 - pos).magnitude_squared() + < (range + body.max_radius()).powi(2) + }; + + // Checks if player's feet or head is near to block + let close_to_block = block_range_check(player_pos) + || block_range_check(player_pos + Vec3::new(0.0, 0.0, body.height())); + if close_to_block { + // Do a check that a path can be found between sprite and entity + // interacting with sprite Use manhattan distance * 1.5 for number + // of iterations + let iters = + (3.0 * (block_pos_f32 - player_pos).map(|x| x.abs()).sum()) as usize; + // Heuristic compares manhattan distance of start and end pos + let heuristic = move |pos: &Vec3, _: &Vec3| { + (block_pos - pos).map(|x| x.abs()).sum() as f32 + }; + + let mut astar = Astar::new( + iters, + player_pos.map(|x| x.floor() as i32), + BuildHasherDefault::::default(), + ); + + // Neighbors are all neighboring blocks that are air + let neighbors = |pos: &Vec3| { + const DIRS: [Vec3; 6] = [ + Vec3::new(1, 0, 0), + Vec3::new(-1, 0, 0), + Vec3::new(0, 1, 0), + Vec3::new(0, -1, 0), + Vec3::new(0, 0, 1), + Vec3::new(0, 0, -1), + ]; + let pos = *pos; + DIRS.iter().map(move |dir| dir + pos).filter(|pos| { + terrain + .get(*pos) + .ok() + .map_or(false, |block| !block.is_filled()) + }) + }; + // Transition uses manhattan distance as the cost, with a slightly lower cost + // for z transitions + let transition = |a: Vec3, b: Vec3| { + let (a, b) = (a.map(|x| x as f32), b.map(|x| x as f32)); + ((a - b) * Vec3::new(1.0, 1.0, 0.9)).map(|e| e.abs()).sum() + }; + // Pathing satisfied when it reaches the block position + let satisfied = |pos: &Vec3| *pos == block_pos; + + astar + .poll(iters, heuristic, neighbors, transition, satisfied) + .into_path() + .is_some() + } else { + false + } +} + /// Handles inventory manipulations that affect the loadout pub fn handle_manipulate_loadout( data: &JoinData<'_>, @@ -927,90 +993,21 @@ pub fn handle_manipulate_loadout( } }, InventoryAction::Collect(sprite_pos) => { - let sprite_pos_f32 = sprite_pos.map(|x| x as f32 + 0.5); - // Closure to check if distance between a point and the sprite is less than - // MAX_PICKUP_RANGE and the radius of the body - let sprite_range_check = |pos: Vec3| { - (sprite_pos_f32 - pos).magnitude_squared() - < (MAX_PICKUP_RANGE + data.body.max_radius()).powi(2) - }; - - // Checks if player's feet or head is near to sprite - let close_to_sprite = sprite_range_check(data.pos.0) - || sprite_range_check(data.pos.0 + Vec3::new(0.0, 0.0, data.body.height())); - if close_to_sprite { - // First, get sprite data for position, if there is a sprite - use sprite_interact::SpriteInteractKind; - let sprite_chunk_pos = TerrainGrid::chunk_offs(sprite_pos); - let sprite_cfg = data - .terrain - .pos_chunk(sprite_pos) - .and_then(|chunk| chunk.meta().sprite_cfg_at(sprite_chunk_pos)); - let sprite_at_pos = data - .terrain - .get(sprite_pos) - .ok() - .copied() - .and_then(|b| b.get_sprite()); - - // Checks if position has a collectible sprite as well as what sprite is at the - // position - let sprite_interact = sprite_at_pos.and_then(Option::::from); - - if let Some(sprite_interact) = sprite_interact { - // Do a check that a path can be found between sprite and entity - // interacting with sprite Use manhattan distance * 1.5 for number - // of iterations - let iters = - (3.0 * (sprite_pos_f32 - data.pos.0).map(|x| x.abs()).sum()) as usize; - // Heuristic compares manhattan distance of start and end pos - let heuristic = move |pos: &Vec3, _: &Vec3| { - (sprite_pos - pos).map(|x| x.abs()).sum() as f32 - }; - - let mut astar = Astar::new( - iters, - data.pos.0.map(|x| x.floor() as i32), - BuildHasherDefault::::default(), - ); - - // Transition uses manhattan distance as the cost, with a slightly lower cost - // for z transitions - let transition = |a: Vec3, b: Vec3| { - let (a, b) = (a.map(|x| x as f32), b.map(|x| x as f32)); - ((a - b) * Vec3::new(1.0, 1.0, 0.9)).map(|e| e.abs()).sum() - }; - // Neighbors are all neighboring blocks that are air - let neighbors = |pos: &Vec3| { - const DIRS: [Vec3; 6] = [ - Vec3::new(1, 0, 0), - Vec3::new(-1, 0, 0), - Vec3::new(0, 1, 0), - Vec3::new(0, -1, 0), - Vec3::new(0, 0, 1), - Vec3::new(0, 0, -1), - ]; - let pos = *pos; - DIRS.iter() - .map(move |dir| { - let dest = dir + pos; - (dest, transition(pos, dest)) - }) - .filter(|(pos, _)| { - data.terrain - .get(*pos) - .ok() - .map_or(false, |block| !block.is_filled()) - }) - }; - // Pathing satisfied when it reaches the sprite position - let satisfied = |pos: &Vec3| *pos == sprite_pos; - - let not_blocked_by_terrain = astar - .poll(iters, heuristic, neighbors, satisfied) - .into_path() - .is_some(); - + // First, get sprite data for position, if there is a sprite + let sprite_at_pos = data.terrain + .get(sprite_pos) + .ok() + .copied() + .and_then(|b| b.get_sprite()); + // Checks if position has a collectible sprite as well as what sprite is at the + // position + let sprite_interact = sprite_at_pos.and_then(Option::::from); + if let Some(sprite_interact) = sprite_interact { + if reach_block(data.pos.0, sprite_pos, MAX_PICKUP_RANGE, data.body, data.terrain) { + let sprite_chunk_pos = TerrainGrid::chunk_offs(sprite_pos); + let sprite_cfg = data.terrain + .pos_chunk(sprite_pos) + .and_then(|chunk| chunk.meta().sprite_cfg_at(sprite_chunk_pos)); let required_item = sprite_at_pos.and_then(|s| match s.unlock_condition(sprite_cfg.cloned()) { UnlockKind::Free => None, @@ -1029,38 +1026,33 @@ pub fn handle_manipulate_loadout( .map(|slot| Some((item_id, slot, consume))), None => Some(None), }; + if let Some(required_item) = has_required_items { + // If the sprite is collectible, enter the sprite interaction character + // state TODO: Handle cases for sprite being + // interactible, but not collectible (none currently + // exist) + let (buildup_duration, use_duration, recover_duration) = + sprite_interact.durations(); - // If path can be found between entity interacting with sprite and entity, start - // interaction with sprite - if not_blocked_by_terrain { - if let Some(required_item) = has_required_items { - // If the sprite is collectible, enter the sprite interaction character - // state TODO: Handle cases for sprite being - // interactible, but not collectible (none currently - // exist) - let (buildup_duration, use_duration, recover_duration) = - sprite_interact.durations(); - - update.character = - CharacterState::SpriteInteract(sprite_interact::Data { - static_data: sprite_interact::StaticData { - buildup_duration, - use_duration, - recover_duration, - sprite_pos, - sprite_kind: sprite_interact, - was_wielded: data.character.is_wield(), - was_sneak: data.character.is_stealthy(), - required_item, - }, - timer: Duration::default(), - stage_section: StageSection::Buildup, - }) - } else { - output_events.emit_local(LocalEvent::CreateOutcome( - Outcome::FailedSpriteUnlock { pos: sprite_pos }, - )); - } + update.character = + CharacterState::SpriteInteract(sprite_interact::Data { + static_data: sprite_interact::StaticData { + buildup_duration, + use_duration, + recover_duration, + sprite_pos, + sprite_kind: sprite_interact, + was_wielded: data.character.is_wield(), + was_sneak: data.character.is_stealthy(), + required_item, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + }) + } else { + output_events.emit_local(LocalEvent::CreateOutcome( + Outcome::FailedSpriteUnlock { pos: sprite_pos }, + )); } } } diff --git a/voxygen/src/session/interactable.rs b/voxygen/src/session/interactable.rs index 769864413a..fbd10ec991 100644 --- a/voxygen/src/session/interactable.rs +++ b/voxygen/src/session/interactable.rs @@ -13,9 +13,10 @@ use common::{ consts::MAX_PICKUP_RANGE, link::Is, mounting::Mount, + states::utils::SPRITE_MOUNT_RANGE_SQR, terrain::{Block, TerrainGrid, UnlockKind}, util::find_dist::{Cube, Cylinder, FindDist}, - vol::ReadVol, states::utils::SPRITE_MOUNT_DIST_SQR, + vol::ReadVol, }; use common_base::span; @@ -257,7 +258,7 @@ pub(super) fn select_interactable( } else if let Interaction::Mount(mount_points) = interaction { mount_points.iter() .map(|p| (p + pos.as_()).distance_squared(player_pos)) - .filter(|dist| *dist < SPRITE_MOUNT_DIST_SQR) + .filter(|dist| *dist < SPRITE_MOUNT_RANGE_SQR) .min_by_key(|dist| OrderedFloat(*dist)) .map(|dist| (pos, dist)) .zip(Some(interaction)) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 6e76761562..7ca1ae450b 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -887,14 +887,15 @@ impl PlayState for SessionState { let mut client = self.client.borrow_mut(); if client.is_riding() { client.unmount(); - } else if client.stand_if_mounted() { } else { + } else if client.stand_if_mounted() { + } else { if let Some(interactable) = &self.interactable { match interactable { Interactable::Block(_, pos, interaction) => { if matches!(interaction, BlockInteraction::Mount(_)) { client.mount_sprite(*pos) } - } + }, Interactable::Entity(entity) => client.mount(*entity), } }