diff --git a/common/src/mounting.rs b/common/src/mounting.rs index 39461c1b07..cd45231c6a 100644 --- a/common/src/mounting.rs +++ b/common/src/mounting.rs @@ -137,7 +137,7 @@ impl Link for Mounting { let old_pos = pos.0.map(|e| e.floor() as i32); pos.0 = safe_pos .map(|p| p.0.map(|e| e.floor())) - .unwrap_or_else(|| terrain.find_space(old_pos).map(|e| e as f32)) + .unwrap_or_else(|| terrain.find_ground(old_pos).map(|e| e as f32)) + Vec3::new(0.5, 0.5, 0.0); if let Some(force_update) = force_update.get_mut(rider) { force_update.update(); diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 7f05512f40..4b2577c02a 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -222,11 +222,13 @@ impl TerrainChunkMeta { pub type TerrainChunk = chonk::Chonk; pub type TerrainGrid = VolGrid2d; +const TERRAIN_GRID_SEARCH_DIST: i32 = 63; + impl TerrainGrid { /// Find a location suitable for spawning an entity near the given /// position (but in the same chunk). - pub fn find_space(&self, pos: Vec3) -> Vec3 { - self.try_find_space(pos).unwrap_or(pos) + pub fn find_ground(&self, pos: Vec3) -> Vec3 { + self.try_find_ground(pos).unwrap_or(pos) } pub fn is_space(&self, pos: Vec3) -> bool { @@ -237,8 +239,14 @@ impl TerrainGrid { } pub fn try_find_space(&self, pos: Vec3) -> Option> { - const SEARCH_DIST: i32 = 63; - (0..SEARCH_DIST * 2 + 1) + (0..TERRAIN_GRID_SEARCH_DIST * 2 + 1) + .map(|i| if i % 2 == 0 { i } else { -i } / 2) + .map(|z_diff| pos + Vec3::unit_z() * z_diff) + .find(|pos| self.is_space(*pos)) + } + + pub fn try_find_ground(&self, pos: Vec3) -> Option> { + (0..TERRAIN_GRID_SEARCH_DIST * 2 + 1) .map(|i| if i % 2 == 0 { i } else { -i } / 2) .map(|z_diff| pos + Vec3::unit_z() * z_diff) .find(|pos| { diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index cfab97daf7..61901ca597 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -1,6 +1,6 @@ use crate::{ - client::Client, events::player::handle_exit_ingame, persistence::PersistedComponents, sys, - CharacterUpdater, Server, StateExt, presence::RepositionOnChunkLoad, + client::Client, events::player::handle_exit_ingame, persistence::PersistedComponents, + presence::RepositionOnChunkLoad, sys, CharacterUpdater, Server, StateExt, }; use common::{ character::CharacterId, @@ -131,7 +131,9 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) -> }; let entity = if let Some(rtsim_entity) = npc.rtsim_entity { - entity.with(rtsim_entity).with(RepositionOnChunkLoad) + entity.with(rtsim_entity).with(RepositionOnChunkLoad { + needs_ground: false, + }) } else { entity }; diff --git a/server/src/presence.rs b/server/src/presence.rs index 732225a205..cb6deb51eb 100644 --- a/server/src/presence.rs +++ b/server/src/presence.rs @@ -1,6 +1,6 @@ use hashbrown::HashSet; use serde::{Deserialize, Serialize}; -use specs::{Component, NullStorage}; +use specs::{Component, VecStorage}; use vek::*; // Distance from fuzzy_chunk before snapping to current chunk @@ -20,8 +20,10 @@ impl Component for RegionSubscription { } #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] -pub struct RepositionOnChunkLoad; +pub struct RepositionOnChunkLoad { + pub needs_ground: bool, +} impl Component for RepositionOnChunkLoad { - type Storage = NullStorage; + type Storage = VecStorage; } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index f9bbc2551c..4dfb2513e9 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -659,7 +659,9 @@ impl StateExt for State { ); if let Some(waypoint) = waypoint { - self.write_component_ignore_entity_dead(entity, RepositionOnChunkLoad); + self.write_component_ignore_entity_dead(entity, RepositionOnChunkLoad { + needs_ground: true, + }); self.write_component_ignore_entity_dead(entity, waypoint); self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos())); self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero())); diff --git a/server/src/sys/pets.rs b/server/src/sys/pets.rs index 67b7862a61..5235dc3f52 100644 --- a/server/src/sys/pets.rs +++ b/server/src/sys/pets.rs @@ -66,7 +66,7 @@ impl<'a> System<'a> for Sys { // TODO: Create a teleportation event to handle this instead of // processing the entity position move here pet_pos.0 = terrain - .find_space(owner_pos.0.map(|e| e.floor() as i32)) + .find_ground(owner_pos.0.map(|e| e.floor() as i32)) .map(|e| e as f32); } } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 4c45e1c796..e26eb9ef21 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -228,11 +228,11 @@ impl<'a> System<'a> for Sys { // TODO: Consider putting this in another system since this forces us to take // positions by write rather than read access. - let repositioned = (&entities, &mut positions, (&mut force_update).maybe(), reposition_on_load.mask()) + let repositioned = (&entities, &mut positions, (&mut force_update).maybe(), &mut reposition_on_load) // TODO: Consider using par_bridge() because Rayon has very poor work splitting for // sparse joins. .par_join() - .filter_map(|(entity, pos, force_update, _)| { + .filter_map(|(entity, pos, force_update, reposition)| { // NOTE: We use regular as casts rather than as_ because we want to saturate on // overflow. let entity_pos = pos.0.map(|x| x as i32); @@ -240,10 +240,11 @@ impl<'a> System<'a> for Sys { // from having just logged in), reposition them. let chunk_pos = TerrainGrid::chunk_key(entity_pos); let chunk = terrain.get_key(chunk_pos)?; - let new_pos = terrain - .try_find_space(entity_pos) - .map(|x| x.as_::()) - .unwrap_or_else(|| chunk.find_accessible_pos(entity_pos.xy(), false)); + let new_pos = if reposition.needs_ground { + terrain.try_find_ground(entity_pos) + } else { + terrain.try_find_space(entity_pos) + }.map(|x| x.as_::()).unwrap_or_else(|| chunk.find_accessible_pos(entity_pos.xy(), false)); pos.0 = new_pos; force_update.map(|force_update| force_update.update()); Some((entity, new_pos))