fix: nether/end

This commit is contained in:
Alexander Medvedev 2026-06-13 22:44:29 +02:00
parent 38a924e6ce
commit 8634d8740f
13 changed files with 223 additions and 142 deletions

View File

@ -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. /// A seed value to be used with a legacy random generator.
#[inline] #[inline]
#[must_use] #[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 (seed
.wrapping_add((x.wrapping_mul(x).wrapping_mul(4_987_142)) as i64 as u64) .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) .wrapping_add((x.wrapping_mul(5_947_611)) as i64 as u64)

View File

@ -280,6 +280,46 @@ impl TextComponentBase {
text 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. /// Converts this component by resolving all translations.
/// ///
/// # Returns /// # Returns
@ -354,52 +394,16 @@ impl TextComponentBase {
_ => self, // If not a translation, return as is _ => self, // If not a translation, return as is
}; };
// Ensure that the extra components are translated // Ensure that the extra components are translated
let mut extra = vec![]; let extra = component
for extra_component in component.extra { .extra
let translated = extra_component.to_translated(); .into_iter()
extra.push(translated); .map(Self::to_translated)
} .collect();
// If the hover event is present, it will also be translated // If the hover event is present, it will also be translated
let style = match component.style.hover_event { let mut style = component.style;
None => component.style, Self::translate_hover_event(&mut 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
}
};
Self { Self {
content: component.content, content: component.content,
style, style,

View File

@ -13,10 +13,10 @@ pub fn into_level(
seed: i64, seed: i64,
gen_pool: Option<Arc<rayon::ThreadPool>>, gen_pool: Option<Arc<rayon::ThreadPool>>,
) -> Arc<Level> { ) -> Arc<Level> {
if dimension == Dimension::OVERWORLD { if dimension.minecraft_name == Dimension::OVERWORLD.minecraft_name {
} else if dimension == Dimension::THE_NETHER { } else if dimension.minecraft_name == Dimension::THE_NETHER.minecraft_name {
base_directory.push("DIM-1"); 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"); base_directory.push("DIM1");
} }
Level::from_root_folder(level_config, base_directory, seed, dimension, gen_pool) Level::from_root_folder(level_config, base_directory, seed, dimension, gen_pool)

View File

@ -9,7 +9,7 @@ use pumpkin_util::{
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl, get_carver_seed, xoroshiro128::Xoroshiro}, random::{RandomGenerator, RandomImpl, get_carver_seed, xoroshiro128::Xoroshiro},
}; };
use tracing::debug; use tracing::trace;
use crate::generation::structure::structures::stronghold::StrongholdPieceType; use crate::generation::structure::structures::stronghold::StrongholdPieceType;
pub use crate::world::WorldPortalExt; pub use crate::world::WorldPortalExt;
@ -411,7 +411,7 @@ impl StructurePiece {
let block_pos = self.offset_pos(x, y, z); let block_pos = self.offset_pos(x, y, z);
if !box_limit.contains_pos(&block_pos) { if !box_limit.contains_pos(&block_pos) {
debug!("Structure out of bounds"); trace!("Structure out of bounds");
return Block::AIR.default_state; return Block::AIR.default_state;
} }
@ -431,7 +431,7 @@ impl StructurePiece {
// Bounds and logic checks // Bounds and logic checks
if !box_limit.contains_pos(&block_pos) { if !box_limit.contains_pos(&block_pos) {
debug!("Structure out of bounds"); trace!("Structure out of bounds");
return; return;
} }

View File

@ -14,14 +14,23 @@ pub struct EndPortalBlock;
impl BlockBehaviour for EndPortalBlock { impl BlockBehaviour for EndPortalBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { Box::pin(async move {
let world = if args.world.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) args.server.get_world_from_dimension(&Dimension::OVERWORLD)
} else { } else {
args.server.get_world_from_dimension(&Dimension::THE_END) 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 args.entity
.get_entity() .get_entity()
.try_use_portal(0, world, *args.position) .try_use_portal(0, target_world, *args.position)
.await; .await;
}) })
} }

View File

@ -70,12 +70,22 @@ impl BlockBehaviour for NetherPortalBlock {
fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> { fn on_entity_collision<'a>(&'a self, args: OnEntityCollisionArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move { Box::pin(async move {
let target_world = if args.world.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) args.server.get_world_from_dimension(&Dimension::OVERWORLD)
} else { } else {
args.server.get_world_from_dimension(&Dimension::THE_NETHER) 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); let portal_delay = Self::get_portal_time(args.world, args.entity);
args.entity args.entity

View File

@ -44,23 +44,8 @@ impl MoveControlTrait for MoveControl {
let mob_entity = mob.get_mob_entity(); let mob_entity = mob.get_mob_entity();
let living_entity = &mob_entity.living_entity; let living_entity = &mob_entity.living_entity;
let entity = &living_entity.entity; let entity = &living_entity.entity;
if self.operation == Operation::Strafe { 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 // TODO: is_walkable check
living_entity.movement_input.store(Vector3::new( living_entity.movement_input.store(Vector3::new(
self.strafe_right as f64, self.strafe_right as f64,
0.0, 0.0,
@ -121,11 +106,13 @@ impl MoveControlTrait for MoveControl {
} }
impl MoveControl { impl MoveControl {
#[must_use]
pub fn has_wanted(&self) -> bool { pub fn has_wanted(&self) -> bool {
self.operation == Operation::MoveTo self.operation == Operation::MoveTo
} }
pub fn get_speed_modifier(&self) -> f64 { #[must_use]
pub const fn get_speed_modifier(&self) -> f64 {
self.speed_modifier 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.operation = Operation::Strafe;
self.strafe_forwards = forwards; self.strafe_forwards = forwards;
self.strafe_right = right; self.strafe_right = right;

View File

@ -42,7 +42,7 @@ impl TrackTargetGoal {
Self::new(check_visibility, false) 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.max_time_without_visibility = ticks;
self self
} }

View File

@ -224,7 +224,7 @@ impl MobEntity {
let dimension = &world.dimension; let dimension = &world.dimension;
let block_light_limit = dimension.monster_spawn_block_light_limit; 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 { if block_light_limit < 15 && block_light > block_light_limit {
return false; return false;
} }

View File

@ -187,13 +187,12 @@ impl SlimeEntity {
false false
} }
fn get_spawn_chance(moon_phase: i64) -> f32 { const fn get_spawn_chance(moon_phase: i64) -> f32 {
match moon_phase { match moon_phase {
0 => 1.0, 0 => 1.0,
1 | 7 => 0.75, 1 | 7 => 0.75,
2 | 6 => 0.5, 2 | 6 => 0.5,
3 | 5 => 0.25, 3 | 5 => 0.25,
4 => 0.0,
_ => 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) 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); let mut diff = (end - start).rem_euclid(360.0);
if diff > 180.0 { if diff > 180.0 {
diff -= 360.0; diff -= 360.0;
@ -363,7 +362,7 @@ impl Mob for SlimeEntity {
pos.z + zd as f64, pos.z + zd as f64,
); );
let new_entity = Entity::new(world.clone(), new_pos, &EntityType::SLIME); 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.set_size(half_size, true);
slime slime
.entity .entity
@ -383,7 +382,8 @@ pub struct SlimeMoveControl {
} }
impl SlimeMoveControl { impl SlimeMoveControl {
pub fn new(slime: Weak<SlimeEntity>) -> Self { #[must_use]
pub const fn new(slime: Weak<SlimeEntity>) -> Self {
Self { slime } Self { slime }
} }
} }
@ -400,7 +400,7 @@ impl MoveControlTrait for SlimeMoveControl {
let entity = &living_entity.entity; let entity = &living_entity.entity;
let current_yaw = entity.yaw.load(); 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.yaw.store(new_yaw);
entity.head_yaw.store(new_yaw); entity.head_yaw.store(new_yaw);
entity.body_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); let current_delay = slime.jump_delay.load(Ordering::Relaxed);
if current_delay <= 0 { if current_delay <= 0 {
// Start jump // 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) { if slime.is_aggressive.load(Ordering::Relaxed) {
next_delay /= 3; next_delay /= 3;
} }
@ -455,7 +455,7 @@ pub struct SlimeFloatGoal {
} }
impl SlimeFloatGoal { impl SlimeFloatGoal {
pub fn new(slime: Arc<SlimeEntity>) -> Self { pub const fn new(slime: Arc<SlimeEntity>) -> Self {
Self { slime } Self { slime }
} }
} }
@ -497,7 +497,7 @@ pub struct SlimeAttackGoal {
} }
impl SlimeAttackGoal { impl SlimeAttackGoal {
pub fn new(slime: Arc<SlimeEntity>) -> Self { pub const fn new(slime: Arc<SlimeEntity>) -> Self {
Self { Self {
slime, slime,
grow_tired_timer: 0, grow_tired_timer: 0,
@ -558,7 +558,7 @@ pub struct SlimeRandomDirectionGoal {
} }
impl SlimeRandomDirectionGoal { impl SlimeRandomDirectionGoal {
pub fn new(slime: Arc<SlimeEntity>) -> Self { pub const fn new(slime: Arc<SlimeEntity>) -> Self {
Self { Self {
slime, slime,
chosen_degrees: 0.0, chosen_degrees: 0.0,
@ -618,7 +618,7 @@ pub struct SlimeKeepOnJumpingGoal {
} }
impl SlimeKeepOnJumpingGoal { impl SlimeKeepOnJumpingGoal {
pub fn new(slime: Arc<SlimeEntity>) -> Self { pub const fn new(slime: Arc<SlimeEntity>) -> Self {
Self { slime } Self { slime }
} }
} }

View File

@ -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::<Vec<_>>()
);
let server = Self { let server = Self {
basic_config, basic_config,
advanced_config, advanced_config,
@ -234,11 +252,7 @@ impl Server {
recipe_manager: Arc::new(recipe::RecipeManager::new()), recipe_manager: Arc::new(recipe::RecipeManager::new()),
map_id: level_info.load().map_id.into(), map_id: level_info.load().map_id.into(),
worlds: ArcSwap::from_pointee(vec![]), worlds: ArcSwap::from_pointee(vec![]),
dimensions: vec![ dimensions,
Dimension::OVERWORLD,
Dimension::THE_NETHER,
Dimension::THE_END,
],
command_dispatcher, command_dispatcher,
block_registry: block_registry.clone(), block_registry: block_registry.clone(),
item_registry: super::item::items::default_registry(), item_registry: super::item::items::default_registry(),
@ -305,18 +319,19 @@ impl Server {
}; };
info!("Starting parallel world load..."); info!("Starting parallel world load...");
let (overworld, nether, end, keys) = tokio::join!( let mut world_futures = Vec::new();
world_loader(Dimension::OVERWORLD), for dim in &server.dimensions {
world_loader(Dimension::THE_NETHER), world_futures.push(world_loader(dim.clone()));
world_loader(Dimension::THE_END), }
mojang_keys_task
); 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)); server.worlds.store(Arc::new(worlds_vec));
if let Ok(k) = keys { if let Ok(k) = keys {
server.mojang_public_keys.store(Arc::new(k)); server.mojang_public_keys.store(Arc::new(k));
@ -354,18 +369,18 @@ impl Server {
} }
pub fn get_world_from_dimension(&self, dimension: &Dimension) -> Arc<World> { pub fn get_world_from_dimension(&self, dimension: &Dimension) -> Arc<World> {
// TODO: this is really bad self.worlds
let world_guard = self.worlds.load(); .load()
.iter()
if dimension == &Dimension::OVERWORLD { .find(|w| w.dimension.minecraft_name == dimension.minecraft_name)
world_guard.first()
} else if dimension == &Dimension::THE_NETHER {
world_guard.get(1)
} else {
world_guard.get(2)
}
.cloned() .cloned()
.unwrap() .unwrap_or_else(|| {
self.worlds
.load()
.first()
.expect("Default world should exist")
.clone()
})
} }
pub async fn create_world(self: &Arc<Self>, name: String, dimension: Dimension) -> Arc<World> { pub async fn create_world(self: &Arc<Self>, name: String, dimension: Dimension) -> Arc<World> {

View File

@ -232,6 +232,45 @@ impl PartialEq for World {
impl Eq for World {} impl Eq for World {}
impl 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] #[must_use]
pub fn load( pub fn load(
level: Arc<Level>, level: Arc<Level>,
@ -245,8 +284,8 @@ impl World {
// Load portal POI from disk (PoiStorage::new automatically loads from disk if files exist) // 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 portal_poi = portal::PortalPoiStorage::new(&level.level_folder.root_folder);
let dragon_fight = let dragon_fight = (dimension.minecraft_name == Dimension::THE_END.minecraft_name)
(dimension == Dimension::THE_END).then(|| Mutex::new(dragon_fight::DragonFight::new())); .then(|| Mutex::new(dragon_fight::DragonFight::new()));
Self { Self {
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
level, level,

View File

@ -12,7 +12,7 @@ use pumpkin_world::{chunk::ChunkHeightmapType, world::BlockFlags};
use crate::world::World; use crate::world::World;
const SEARCH_RADIUS_NETHER: i32 = 16; const SEARCH_RADIUS_NETHER: i32 = 128;
const SEARCH_RADIUS_OVERWORLD: i32 = 128; const SEARCH_RADIUS_OVERWORLD: i32 = 128;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -467,6 +467,11 @@ impl NetherPortal {
world: &Arc<World>, world: &Arc<World>,
target_pos: BlockPos, target_pos: BlockPos,
) -> Option<PortalSearchResult> { ) -> Option<PortalSearchResult> {
tracing::debug!(
"Searching for portal in {:?} around {:?}",
world.dimension.minecraft_name,
target_pos
);
let min_y = world.min_y; let min_y = world.min_y;
let max_y = min_y + world.dimension.height - 1; let max_y = min_y + world.dimension.height - 1;
let worldborder = world.worldborder.lock().await; let worldborder = world.worldborder.lock().await;
@ -545,6 +550,11 @@ impl NetherPortal {
target_pos: BlockPos, target_pos: BlockPos,
axis: HorizontalAxis, axis: HorizontalAxis,
) -> Option<(BlockPos, HorizontalAxis, bool)> { ) -> 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 min_y = world.min_y;
let max_y = min_y + world.dimension.height - 1; let max_y = min_y + world.dimension.height - 1;
let worldborder = world.worldborder.lock().await; let worldborder = world.worldborder.lock().await;
@ -564,8 +574,8 @@ impl NetherPortal {
let mut ideal_pos: Option<(BlockPos, HorizontalAxis, f64)> = None; let mut ideal_pos: Option<(BlockPos, HorizontalAxis, f64)> = None;
let mut acceptable_pos: Option<(BlockPos, HorizontalAxis, f64)> = None; let mut acceptable_pos: Option<(BlockPos, HorizontalAxis, f64)> = None;
for offset_x in -16..=16 { for offset_x in -32..=32 {
for offset_z in -16..=16 { for offset_z in -32..=32 {
let check_x = target_pos.0.x + offset_x; let check_x = target_pos.0.x + offset_x;
let check_z = target_pos.0.z + offset_z; let check_z = target_pos.0.z + offset_z;
@ -579,23 +589,25 @@ impl NetherPortal {
continue; continue;
} }
let heightmap_y = world.get_heightmap_height( let heightmap_y = world
.get_heightmap_height_async(
ChunkHeightmapType::MotionBlocking, ChunkHeightmapType::MotionBlocking,
check_x, check_x,
check_z, check_z,
); )
.await;
let start_y = heightmap_y.min(top_y_limit); let start_y = heightmap_y.min(top_y_limit);
let mut y = start_y; let mut y = start_y;
while y >= min_y { while y >= min_y {
let pos = BlockPos(Vector3::new(check_x, y, check_z)); 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) { if Self::is_valid_portal_air(state) {
let mut bottom_y = y; let mut bottom_y = y;
while bottom_y > min_y { while bottom_y > min_y {
let below = BlockPos(Vector3::new(check_x, bottom_y - 1, check_z)); 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) { if !Self::is_valid_portal_air(below_state) {
break; break;
} }
@ -607,18 +619,23 @@ impl NetherPortal {
let floor_pos = BlockPos(Vector3::new(check_x, bottom_y, check_z)); let floor_pos = BlockPos(Vector3::new(check_x, bottom_y, check_z));
for check_axis in [HorizontalAxis::X, HorizontalAxis::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( let dist = f64::from(target_pos.0.squared_distance_to(
floor_pos.0.x, floor_pos.0.x,
floor_pos.0.y, floor_pos.0.y,
floor_pos.0.z, floor_pos.0.z,
)); ));
let is_ideal = let is_ideal = Self::is_valid_portal_pos_async(
Self::is_valid_portal_pos(world, floor_pos, check_axis, -1) world, floor_pos, check_axis, -1,
&& Self::is_valid_portal_pos( )
.await
&& Self::is_valid_portal_pos_async(
world, floor_pos, check_axis, 1, world, floor_pos, check_axis, 1,
); )
.await;
if is_ideal { if is_ideal {
if ideal_pos.is_none() if ideal_pos.is_none()
@ -669,7 +686,7 @@ impl NetherPortal {
state.replaceable() && !state.is_liquid() state.replaceable() && !state.is_liquid()
} }
fn is_valid_portal_pos( async fn is_valid_portal_pos_async(
world: &Arc<World>, world: &Arc<World>,
floor_pos: BlockPos, floor_pos: BlockPos,
axis: HorizontalAxis, axis: HorizontalAxis,
@ -693,7 +710,7 @@ impl NetherPortal {
.offset_dir(perpendicular.to_offset(), perpendicular_offset) .offset_dir(perpendicular.to_offset(), perpendicular_offset)
.offset_dir(BlockDirection::Up.to_offset(), height); .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 height < 0 {
if !state.is_solid_block() { if !state.is_solid_block() {