From 8634d8740f7a9d442f2320c3f00b25a366b00b56 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sat, 13 Jun 2026 22:44:29 +0200 Subject: [PATCH] fix: nether/end --- pumpkin-util/src/random/mod.rs | 2 +- pumpkin-util/src/text/mod.rs | 94 ++++++++++--------- pumpkin-world/src/dimension.rs | 6 +- .../generation/structure/structures/mod.rs | 6 +- pumpkin/src/block/blocks/end_portal.rs | 21 +++-- pumpkin/src/block/blocks/nether_portal.rs | 20 +++- pumpkin/src/entity/ai/control/move_control.rs | 21 +---- pumpkin/src/entity/ai/goal/track_target.rs | 2 +- pumpkin/src/entity/mob/mod.rs | 2 +- pumpkin/src/entity/mob/slime.rs | 24 ++--- pumpkin/src/server/mod.rs | 71 ++++++++------ pumpkin/src/world/mod.rs | 43 ++++++++- pumpkin/src/world/portal/nether.rs | 53 +++++++---- 13 files changed, 223 insertions(+), 142 deletions(-) diff --git a/pumpkin-util/src/random/mod.rs b/pumpkin-util/src/random/mod.rs index ce96a1ca8..53faee915 100644 --- a/pumpkin-util/src/random/mod.rs +++ b/pumpkin-util/src/random/mod.rs @@ -159,7 +159,7 @@ pub fn get_region_seed(world_seed: u64, region_x: i32, region_z: i32, salt: u32) /// A seed value to be used with a legacy random generator. #[inline] #[must_use] -pub fn seed_slime_chunk(x: i32, z: i32, seed: u64, salt: u64) -> u64 { +pub const fn seed_slime_chunk(x: i32, z: i32, seed: u64, salt: u64) -> u64 { (seed .wrapping_add((x.wrapping_mul(x).wrapping_mul(4_987_142)) as i64 as u64) .wrapping_add((x.wrapping_mul(5_947_611)) as i64 as u64) diff --git a/pumpkin-util/src/text/mod.rs b/pumpkin-util/src/text/mod.rs index 961bb3c4e..0138ac689 100644 --- a/pumpkin-util/src/text/mod.rs +++ b/pumpkin-util/src/text/mod.rs @@ -280,6 +280,46 @@ impl TextComponentBase { text } + /// Converts this component by resolving all translations. + /// + /// # Returns + /// A new component with all translations resolved. + fn translate_hover_event(style: &mut Style) { + if let Some(ref hover) = style.hover_event { + style.hover_event = match hover { + HoverEvent::ShowText { value } => { + let mut hover_components = vec![]; + for hover_component in value { + hover_components.push(hover_component.to_owned().to_translated()); + } + Some(HoverEvent::ShowText { + value: hover_components, + }) + } + HoverEvent::ShowEntity { name, id, uuid } => name.as_ref().map_or_else( + || { + Some(HoverEvent::ShowEntity { + name: None, + id: id.clone(), + uuid: uuid.clone(), + }) + }, + |name| { + Some(HoverEvent::ShowEntity { + name: Some(name.iter().map(|x| x.to_owned().to_translated()).collect()), + id: id.clone(), + uuid: uuid.clone(), + }) + }, + ), + HoverEvent::ShowItem { id, count } => Some(HoverEvent::ShowItem { + id: id.clone(), + count: count.to_owned(), + }), + }; + } + } + /// Converts this component by resolving all translations. /// /// # Returns @@ -354,52 +394,16 @@ impl TextComponentBase { _ => self, // If not a translation, return as is }; // Ensure that the extra components are translated - let mut extra = vec![]; - for extra_component in component.extra { - let translated = extra_component.to_translated(); - extra.push(translated); - } + let extra = component + .extra + .into_iter() + .map(Self::to_translated) + .collect(); + // If the hover event is present, it will also be translated - let style = match component.style.hover_event { - None => component.style, - Some(ref hover) => { - let mut style = component.style.clone(); - style.hover_event = match hover { - HoverEvent::ShowText { value } => { - let mut hover_components = vec![]; - for hover_component in value { - hover_components.push(hover_component.to_owned().to_translated()); - } - Some(HoverEvent::ShowText { - value: hover_components, - }) - } - HoverEvent::ShowEntity { name, id, uuid } => name.as_ref().map_or_else( - || { - Some(HoverEvent::ShowEntity { - name: None, - id: id.clone(), - uuid: uuid.clone(), - }) - }, - |name| { - Some(HoverEvent::ShowEntity { - name: Some( - name.iter().map(|x| x.to_owned().to_translated()).collect(), - ), - id: id.clone(), - uuid: uuid.clone(), - }) - }, - ), - HoverEvent::ShowItem { id, count } => Some(HoverEvent::ShowItem { - id: id.clone(), - count: count.to_owned(), - }), - }; - style - } - }; + let mut style = component.style; + Self::translate_hover_event(&mut style); + Self { content: component.content, style, diff --git a/pumpkin-world/src/dimension.rs b/pumpkin-world/src/dimension.rs index 67d811607..f5107730b 100644 --- a/pumpkin-world/src/dimension.rs +++ b/pumpkin-world/src/dimension.rs @@ -13,10 +13,10 @@ pub fn into_level( seed: i64, gen_pool: Option>, ) -> Arc { - if dimension == Dimension::OVERWORLD { - } else if dimension == Dimension::THE_NETHER { + if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name { + } else if dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name { base_directory.push("DIM-1"); - } else if dimension == Dimension::THE_END { + } else if dimension.minecraft_name == Dimension::THE_END.minecraft_name { base_directory.push("DIM1"); } Level::from_root_folder(level_config, base_directory, seed, dimension, gen_pool) diff --git a/pumpkin-world/src/generation/structure/structures/mod.rs b/pumpkin-world/src/generation/structure/structures/mod.rs index b17e55397..f8f74f891 100644 --- a/pumpkin-world/src/generation/structure/structures/mod.rs +++ b/pumpkin-world/src/generation/structure/structures/mod.rs @@ -9,7 +9,7 @@ use pumpkin_util::{ math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, random::{RandomGenerator, RandomImpl, get_carver_seed, xoroshiro128::Xoroshiro}, }; -use tracing::debug; +use tracing::trace; use crate::generation::structure::structures::stronghold::StrongholdPieceType; pub use crate::world::WorldPortalExt; @@ -411,7 +411,7 @@ impl StructurePiece { let block_pos = self.offset_pos(x, y, z); if !box_limit.contains_pos(&block_pos) { - debug!("Structure out of bounds"); + trace!("Structure out of bounds"); return Block::AIR.default_state; } @@ -431,7 +431,7 @@ impl StructurePiece { // Bounds and logic checks if !box_limit.contains_pos(&block_pos) { - debug!("Structure out of bounds"); + trace!("Structure out of bounds"); return; } diff --git a/pumpkin/src/block/blocks/end_portal.rs b/pumpkin/src/block/blocks/end_portal.rs index b32d98b3a..5da4b1b71 100644 --- a/pumpkin/src/block/blocks/end_portal.rs +++ b/pumpkin/src/block/blocks/end_portal.rs @@ -14,14 +14,23 @@ pub struct EndPortalBlock; impl BlockBehaviour for EndPortalBlock { fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { Box::pin(async move { - let world = if args.world.dimension == Dimension::THE_END { - args.server.get_world_from_dimension(&Dimension::OVERWORLD) - } else { - args.server.get_world_from_dimension(&Dimension::THE_END) - }; + let target_world = + if args.world.dimension.minecraft_name == Dimension::THE_END.minecraft_name { + args.server.get_world_from_dimension(&Dimension::OVERWORLD) + } else { + args.server.get_world_from_dimension(&Dimension::THE_END) + }; + if Arc::ptr_eq(&target_world, args.world) { + return; + } + tracing::info!( + "End portal collision at {:?}, targeting world {:?}", + args.position, + target_world.dimension.minecraft_name + ); args.entity .get_entity() - .try_use_portal(0, world, *args.position) + .try_use_portal(0, target_world, *args.position) .await; }) } diff --git a/pumpkin/src/block/blocks/nether_portal.rs b/pumpkin/src/block/blocks/nether_portal.rs index 75b8c5e5c..386c09d0d 100644 --- a/pumpkin/src/block/blocks/nether_portal.rs +++ b/pumpkin/src/block/blocks/nether_portal.rs @@ -70,12 +70,22 @@ impl BlockBehaviour for NetherPortalBlock { fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { Box::pin(async move { - let target_world = if args.world.dimension == Dimension::THE_NETHER { - args.server.get_world_from_dimension(&Dimension::OVERWORLD) - } else { - args.server.get_world_from_dimension(&Dimension::THE_NETHER) - }; + let target_world = + if args.world.dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name { + args.server.get_world_from_dimension(&Dimension::OVERWORLD) + } else { + args.server.get_world_from_dimension(&Dimension::THE_NETHER) + }; + if Arc::ptr_eq(&target_world, args.world) { + return; + } + + tracing::info!( + "Nether portal collision at {:?}, targeting world {:?}", + args.position, + target_world.dimension.minecraft_name + ); let portal_delay = Self::get_portal_time(args.world, args.entity); args.entity diff --git a/pumpkin/src/entity/ai/control/move_control.rs b/pumpkin/src/entity/ai/control/move_control.rs index edcadfd2a..84039bb5c 100644 --- a/pumpkin/src/entity/ai/control/move_control.rs +++ b/pumpkin/src/entity/ai/control/move_control.rs @@ -44,23 +44,8 @@ impl MoveControlTrait for MoveControl { let mob_entity = mob.get_mob_entity(); let living_entity = &mob_entity.living_entity; let entity = &living_entity.entity; - if self.operation == Operation::Strafe { - let movement_speed = living_entity.get_attribute_value(&Attributes::MOVEMENT_SPEED); - let speed_modified = (self.speed_modifier * movement_speed) as f32; - let xa = self.strafe_forwards; - let za = self.strafe_right; - let mut dist = xa.hypot(za); - if dist < 1.0 { - dist = 1.0; - } - - let dist_norm = speed_modified / dist; - let _xa_norm = xa * dist_norm; - let _za_norm = za * dist_norm; - // TODO: is_walkable check - living_entity.movement_input.store(Vector3::new( self.strafe_right as f64, 0.0, @@ -121,11 +106,13 @@ impl MoveControlTrait for MoveControl { } impl MoveControl { + #[must_use] pub fn has_wanted(&self) -> bool { self.operation == Operation::MoveTo } - pub fn get_speed_modifier(&self) -> f64 { + #[must_use] + pub const fn get_speed_modifier(&self) -> f64 { self.speed_modifier } @@ -139,7 +126,7 @@ impl MoveControl { } } - pub fn strafe(&mut self, forwards: f32, right: f32) { + pub const fn strafe(&mut self, forwards: f32, right: f32) { self.operation = Operation::Strafe; self.strafe_forwards = forwards; self.strafe_right = right; diff --git a/pumpkin/src/entity/ai/goal/track_target.rs b/pumpkin/src/entity/ai/goal/track_target.rs index a52454104..2fc262321 100644 --- a/pumpkin/src/entity/ai/goal/track_target.rs +++ b/pumpkin/src/entity/ai/goal/track_target.rs @@ -42,7 +42,7 @@ impl TrackTargetGoal { Self::new(check_visibility, false) } - pub fn set_unseen_memory_ticks(mut self, ticks: i32) -> Self { + pub const fn set_unseen_memory_ticks(mut self, ticks: i32) -> Self { self.max_time_without_visibility = ticks; self } diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index 918a2f640..8327032b2 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -224,7 +224,7 @@ impl MobEntity { let dimension = &world.dimension; let block_light_limit = dimension.monster_spawn_block_light_limit; - let block_light = world.get_block_light_level(pos).unwrap(); + let block_light = world.get_block_light_level(pos).unwrap_or(0); if block_light_limit < 15 && block_light > block_light_limit { return false; } diff --git a/pumpkin/src/entity/mob/slime.rs b/pumpkin/src/entity/mob/slime.rs index 3ea60bc79..9e975313d 100644 --- a/pumpkin/src/entity/mob/slime.rs +++ b/pumpkin/src/entity/mob/slime.rs @@ -187,13 +187,12 @@ impl SlimeEntity { false } - fn get_spawn_chance(moon_phase: i64) -> f32 { + const fn get_spawn_chance(moon_phase: i64) -> f32 { match moon_phase { 0 => 1.0, 1 | 7 => 0.75, 2 | 6 => 0.5, 3 | 5 => 0.25, - 4 => 0.0, _ => 0.0, } } @@ -206,11 +205,11 @@ impl SlimeEntity { } } - fn get_jump_delay(&self) -> i32 { + fn get_jump_delay() -> i32 { rand::random_range(10..30) } - fn rot_lerp(&self, start: f32, end: f32, max_step: f32) -> f32 { + fn rot_lerp(start: f32, end: f32, max_step: f32) -> f32 { let mut diff = (end - start).rem_euclid(360.0); if diff > 180.0 { diff -= 360.0; @@ -363,7 +362,7 @@ impl Mob for SlimeEntity { pos.z + zd as f64, ); let new_entity = Entity::new(world.clone(), new_pos, &EntityType::SLIME); - let slime = SlimeEntity::new(new_entity); + let slime = Self::new(new_entity); slime.set_size(half_size, true); slime .entity @@ -383,7 +382,8 @@ pub struct SlimeMoveControl { } impl SlimeMoveControl { - pub fn new(slime: Weak) -> Self { + #[must_use] + pub const fn new(slime: Weak) -> Self { Self { slime } } } @@ -400,7 +400,7 @@ impl MoveControlTrait for SlimeMoveControl { let entity = &living_entity.entity; let current_yaw = entity.yaw.load(); - let new_yaw = slime.rot_lerp(current_yaw, slime.target_yaw.load(), 90.0); + let new_yaw = SlimeEntity::rot_lerp(current_yaw, slime.target_yaw.load(), 90.0); entity.yaw.store(new_yaw); entity.head_yaw.store(new_yaw); entity.body_yaw.store(new_yaw); @@ -415,7 +415,7 @@ impl MoveControlTrait for SlimeMoveControl { let current_delay = slime.jump_delay.load(Ordering::Relaxed); if current_delay <= 0 { // Start jump - let mut next_delay = slime.get_jump_delay(); + let mut next_delay = SlimeEntity::get_jump_delay(); if slime.is_aggressive.load(Ordering::Relaxed) { next_delay /= 3; } @@ -455,7 +455,7 @@ pub struct SlimeFloatGoal { } impl SlimeFloatGoal { - pub fn new(slime: Arc) -> Self { + pub const fn new(slime: Arc) -> Self { Self { slime } } } @@ -497,7 +497,7 @@ pub struct SlimeAttackGoal { } impl SlimeAttackGoal { - pub fn new(slime: Arc) -> Self { + pub const fn new(slime: Arc) -> Self { Self { slime, grow_tired_timer: 0, @@ -558,7 +558,7 @@ pub struct SlimeRandomDirectionGoal { } impl SlimeRandomDirectionGoal { - pub fn new(slime: Arc) -> Self { + pub const fn new(slime: Arc) -> Self { Self { slime, chosen_degrees: 0.0, @@ -618,7 +618,7 @@ pub struct SlimeKeepOnJumpingGoal { } impl SlimeKeepOnJumpingGoal { - pub fn new(slime: Arc) -> Self { + pub const fn new(slime: Arc) -> Self { Self { slime } } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 6715b21b6..8cef1b89c 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -221,6 +221,24 @@ impl Server { } }); + let dimensions = { + let mut dimensions = vec![Dimension::OVERWORLD]; + if basic_config.allow_nether { + dimensions.push(Dimension::THE_NETHER); + } + if basic_config.allow_end { + dimensions.push(Dimension::THE_END); + } + dimensions + }; + info!( + "Enabled dimensions: {:?}", + dimensions + .iter() + .map(|d| d.minecraft_name) + .collect::>() + ); + let server = Self { basic_config, advanced_config, @@ -234,11 +252,7 @@ impl Server { recipe_manager: Arc::new(recipe::RecipeManager::new()), map_id: level_info.load().map_id.into(), worlds: ArcSwap::from_pointee(vec![]), - dimensions: vec![ - Dimension::OVERWORLD, - Dimension::THE_NETHER, - Dimension::THE_END, - ], + dimensions, command_dispatcher, block_registry: block_registry.clone(), item_registry: super::item::items::default_registry(), @@ -305,18 +319,19 @@ impl Server { }; info!("Starting parallel world load..."); - let (overworld, nether, end, keys) = tokio::join!( - world_loader(Dimension::OVERWORLD), - world_loader(Dimension::THE_NETHER), - world_loader(Dimension::THE_END), - mojang_keys_task - ); + let mut world_futures = Vec::new(); + for dim in &server.dimensions { + world_futures.push(world_loader(dim.clone())); + } + + let (worlds_results, keys) = + tokio::join!(futures::future::join_all(world_futures), mojang_keys_task); + + let mut worlds_vec = Vec::new(); + for world_result in worlds_results { + worlds_vec.push(world_result.expect("World loading panicked")); + } - let worlds_vec = vec![ - overworld.expect("Overworld panicked"), - nether.expect("Nether panicked"), - end.expect("End panicked"), - ]; server.worlds.store(Arc::new(worlds_vec)); if let Ok(k) = keys { server.mojang_public_keys.store(Arc::new(k)); @@ -354,18 +369,18 @@ impl Server { } pub fn get_world_from_dimension(&self, dimension: &Dimension) -> Arc { - // TODO: this is really bad - let world_guard = self.worlds.load(); - - if dimension == &Dimension::OVERWORLD { - world_guard.first() - } else if dimension == &Dimension::THE_NETHER { - world_guard.get(1) - } else { - world_guard.get(2) - } - .cloned() - .unwrap() + self.worlds + .load() + .iter() + .find(|w| w.dimension.minecraft_name == dimension.minecraft_name) + .cloned() + .unwrap_or_else(|| { + self.worlds + .load() + .first() + .expect("Default world should exist") + .clone() + }) } pub async fn create_world(self: &Arc, name: String, dimension: Dimension) -> Arc { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 4553c744f..e1c65865d 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -232,6 +232,45 @@ impl PartialEq for World { impl Eq for World {} impl World { + pub async fn get_block_state_id_async(&self, position: &BlockPos) -> BlockStateId { + if !self.is_in_build_limit(*position) { + return Block::AIR.default_state.id; + } + + let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); + self.level + .get_or_fetch_chunk(chunk_coordinate, |chunk| { + chunk + .section + .get_block_absolute_y(relative.x as usize, relative.y, relative.z as usize) + .unwrap_or(Block::AIR.default_state.id) + }) + .await + } + + pub async fn get_block_state_async(&self, position: &BlockPos) -> &'static BlockState { + let id = self.get_block_state_id_async(position).await; + BlockState::from_id(id) + } + + pub async fn get_heightmap_height_async( + &self, + height_map: ChunkHeightmapType, + x: i32, + z: i32, + ) -> i32 { + let chunk_pos = Vector2::new(x >> 4, z >> 4); + self.level + .get_or_fetch_chunk(chunk_pos, |chunk| { + chunk + .heightmap + .lock() + .unwrap() + .get(height_map, x, z, self.min_y) + }) + .await + } + #[must_use] pub fn load( level: Arc, @@ -245,8 +284,8 @@ impl World { // Load portal POI from disk (PoiStorage::new automatically loads from disk if files exist) let portal_poi = portal::PortalPoiStorage::new(&level.level_folder.root_folder); - let dragon_fight = - (dimension == Dimension::THE_END).then(|| Mutex::new(dragon_fight::DragonFight::new())); + let dragon_fight = (dimension.minecraft_name == Dimension::THE_END.minecraft_name) + .then(|| Mutex::new(dragon_fight::DragonFight::new())); Self { uuid: Uuid::new_v4(), level, diff --git a/pumpkin/src/world/portal/nether.rs b/pumpkin/src/world/portal/nether.rs index 8d358961d..102de1102 100644 --- a/pumpkin/src/world/portal/nether.rs +++ b/pumpkin/src/world/portal/nether.rs @@ -12,7 +12,7 @@ use pumpkin_world::{chunk::ChunkHeightmapType, world::BlockFlags}; use crate::world::World; -const SEARCH_RADIUS_NETHER: i32 = 16; +const SEARCH_RADIUS_NETHER: i32 = 128; const SEARCH_RADIUS_OVERWORLD: i32 = 128; #[derive(Debug, Clone)] @@ -467,6 +467,11 @@ impl NetherPortal { world: &Arc, target_pos: BlockPos, ) -> Option { + tracing::debug!( + "Searching for portal in {:?} around {:?}", + world.dimension.minecraft_name, + target_pos + ); let min_y = world.min_y; let max_y = min_y + world.dimension.height - 1; let worldborder = world.worldborder.lock().await; @@ -545,6 +550,11 @@ impl NetherPortal { target_pos: BlockPos, axis: HorizontalAxis, ) -> Option<(BlockPos, HorizontalAxis, bool)> { + tracing::debug!( + "Finding safe location for portal in {:?} around {:?}", + world.dimension.minecraft_name, + target_pos + ); let min_y = world.min_y; let max_y = min_y + world.dimension.height - 1; let worldborder = world.worldborder.lock().await; @@ -564,8 +574,8 @@ impl NetherPortal { let mut ideal_pos: Option<(BlockPos, HorizontalAxis, f64)> = None; let mut acceptable_pos: Option<(BlockPos, HorizontalAxis, f64)> = None; - for offset_x in -16..=16 { - for offset_z in -16..=16 { + for offset_x in -32..=32 { + for offset_z in -32..=32 { let check_x = target_pos.0.x + offset_x; let check_z = target_pos.0.z + offset_z; @@ -579,23 +589,25 @@ impl NetherPortal { continue; } - let heightmap_y = world.get_heightmap_height( - ChunkHeightmapType::MotionBlocking, - check_x, - check_z, - ); + let heightmap_y = world + .get_heightmap_height_async( + ChunkHeightmapType::MotionBlocking, + check_x, + check_z, + ) + .await; let start_y = heightmap_y.min(top_y_limit); let mut y = start_y; while y >= min_y { let pos = BlockPos(Vector3::new(check_x, y, check_z)); - let state = world.get_block_state(&pos); + let state = world.get_block_state_async(&pos).await; if Self::is_valid_portal_air(state) { let mut bottom_y = y; while bottom_y > min_y { let below = BlockPos(Vector3::new(check_x, bottom_y - 1, check_z)); - let below_state = world.get_block_state(&below); + let below_state = world.get_block_state_async(&below).await; if !Self::is_valid_portal_air(below_state) { break; } @@ -607,18 +619,23 @@ impl NetherPortal { let floor_pos = BlockPos(Vector3::new(check_x, bottom_y, check_z)); for check_axis in [HorizontalAxis::X, HorizontalAxis::Z] { - if Self::is_valid_portal_pos(world, floor_pos, check_axis, 0) { + if Self::is_valid_portal_pos_async(world, floor_pos, check_axis, 0) + .await + { let dist = f64::from(target_pos.0.squared_distance_to( floor_pos.0.x, floor_pos.0.y, floor_pos.0.z, )); - let is_ideal = - Self::is_valid_portal_pos(world, floor_pos, check_axis, -1) - && Self::is_valid_portal_pos( - world, floor_pos, check_axis, 1, - ); + let is_ideal = Self::is_valid_portal_pos_async( + world, floor_pos, check_axis, -1, + ) + .await + && Self::is_valid_portal_pos_async( + world, floor_pos, check_axis, 1, + ) + .await; if is_ideal { if ideal_pos.is_none() @@ -669,7 +686,7 @@ impl NetherPortal { state.replaceable() && !state.is_liquid() } - fn is_valid_portal_pos( + async fn is_valid_portal_pos_async( world: &Arc, floor_pos: BlockPos, axis: HorizontalAxis, @@ -693,7 +710,7 @@ impl NetherPortal { .offset_dir(perpendicular.to_offset(), perpendicular_offset) .offset_dir(BlockDirection::Up.to_offset(), height); - let state = world.get_block_state(&pos); + let state = world.get_block_state_async(&pos).await; if height < 0 { if !state.is_solid_block() {