From 47254b14baa395c33e5b9d969a0a715a5d1fa407 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Mon, 18 Aug 2025 17:59:14 +0200 Subject: [PATCH] Structures: begin adding Nether Fortress --- pumpkin-world/src/chunk/palette.rs | 19 +-- pumpkin-world/src/generation/proto_chunk.rs | 1 + .../generation/structure/structures/mod.rs | 57 +++++++- .../structure/structures/nether_fortress.rs | 123 +++++++++++++++++- pumpkin/src/block/blocks/candle_cakes.rs | 3 +- pumpkin/src/command/commands/damage.rs | 2 +- pumpkin/src/command/commands/data.rs | 2 +- pumpkin/src/command/commands/experience.rs | 2 +- pumpkin/src/command/commands/setworldspawn.rs | 2 +- pumpkin/src/command/commands/tick.rs | 10 +- pumpkin/src/command/dispatcher.rs | 4 +- pumpkin/src/data/banlist_serializer.rs | 4 +- pumpkin/src/data/player_server_data.rs | 2 +- pumpkin/src/entity/living.rs | 9 +- pumpkin/src/entity/mod.rs | 20 +-- pumpkin/src/entity/player.rs | 29 +++-- pumpkin/src/lib.rs | 3 + pumpkin/src/main.rs | 6 + pumpkin/src/net/bedrock/mod.rs | 2 +- pumpkin/src/net/bedrock/play.rs | 3 +- pumpkin/src/net/java/login.rs | 1 + pumpkin/src/net/java/mod.rs | 3 +- pumpkin/src/net/java/play.rs | 46 ++++--- pumpkin/src/net/mod.rs | 7 +- pumpkin/src/net/rcon/mod.rs | 6 +- pumpkin/src/plugin/api/context.rs | 5 +- pumpkin/src/plugin/mod.rs | 29 +++-- pumpkin/src/server/mod.rs | 60 ++++----- pumpkin/src/world/mod.rs | 96 +++++++------- pumpkin/src/world/natural_spawner.rs | 54 +++++--- 30 files changed, 418 insertions(+), 192 deletions(-) diff --git a/pumpkin-world/src/chunk/palette.rs b/pumpkin-world/src/chunk/palette.rs index 518648f4e..57ceee133 100644 --- a/pumpkin-world/src/chunk/palette.rs +++ b/pumpkin-world/src/chunk/palette.rs @@ -112,34 +112,27 @@ impl PalettedContainer debug_assert!(bits_per_entry >= encompassing_bits(data.counts.len())); debug_assert!(bits_per_entry <= 15); - let palette: Box<[V]> = data.palette.iter().copied().collect(); - - let key_to_index_map: HashMap = palette - .iter() - .enumerate() - .map(|(index, key)| (*key, index)) - .collect(); - + // Don't use HashMap's here, because its slow let blocks_per_i64 = 64 / bits_per_entry; - let packed_indices = data + let packed_indices: Box<[i64]> = data .cube .as_flattened() .as_flattened() .chunks(blocks_per_i64 as usize) .map(|chunk| { chunk.iter().enumerate().fold(0, |acc, (index, key)| { - let key_index = key_to_index_map.get(key).unwrap(); - debug_assert!((1 << bits_per_entry) > *key_index); + let key_index = data.palette.iter().position(|&x| x == *key).unwrap(); + debug_assert!((1 << bits_per_entry) > key_index); let packed_offset_index = - (*key_index as u64) << (bits_per_entry as u64 * index as u64); + (key_index as u64) << (bits_per_entry as u64 * index as u64); acc | packed_offset_index as i64 }) }) .collect(); - (palette, packed_indices) + (data.palette.clone().into_boxed_slice(), packed_indices) } } } diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index 5bb19d3bc..aa8122fa4 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -737,6 +737,7 @@ impl<'a> ProtoChunk<'a> { Xoroshiro::get_population_seed(self.random_config.seed, block_pos.0.x, block_pos.0.z); for (_structure, (pos, stype)) in self.structure_starts.clone() { + dbg!("generating structure"); stype.generate(pos.clone(), self); } diff --git a/pumpkin-world/src/generation/structure/structures/mod.rs b/pumpkin-world/src/generation/structure/structures/mod.rs index ba821cf3f..635210c65 100644 --- a/pumpkin-world/src/generation/structure/structures/mod.rs +++ b/pumpkin-world/src/generation/structure/structures/mod.rs @@ -1,6 +1,7 @@ -use pumpkin_util::math::position::BlockPos; +use pumpkin_data::BlockState; +use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; -use crate::ProtoChunk; +use crate::{ProtoChunk, generation::height_limit::HeightLimitView}; pub mod buried_treasure; pub mod nether_fortress; @@ -11,6 +12,58 @@ pub trait StructureGenerator { fn generate(&self, position: BlockPos, chunk: &mut crate::ProtoChunk); } +#[expect(clippy::too_many_arguments)] +pub fn fill_with_outline( + min_x: i32, + min_y: i32, + min_z: i32, + max_x: i32, + max_y: i32, + max_z: i32, + outline: &BlockState, + inside: &BlockState, + chunk: &mut crate::ProtoChunk, +) { + for y in min_y..=max_y { + for x in min_x..=max_x { + for z in min_z..=max_z { + if y == min_y || y == max_y || x == min_x || x == max_x || z == min_z || z == max_z + { + chunk.set_block_state(&Vector3::new(x, y, z), outline); + } else { + chunk.set_block_state(&Vector3::new(x, y, z), inside); + } + } + } + } +} + +#[expect(clippy::too_many_arguments)] +pub fn fill( + min_x: i32, + min_y: i32, + min_z: i32, + max_x: i32, + max_y: i32, + max_z: i32, + state: &BlockState, + chunk: &mut crate::ProtoChunk, +) { + for y in min_y..=max_y { + for x in min_x..=max_x { + for z in min_z..=max_z { + chunk.set_block_state(&Vector3::new(x, y, z), state); + } + } + } +} + +pub fn fill_downwards(x: i32, y: i32, z: i32, state: &BlockState, chunk: &mut crate::ProtoChunk) { + for y in y..chunk.bottom_y() as i32 { + chunk.set_block_state(&Vector3::new(x, y, z), state); + } +} + #[derive(Clone)] pub struct StructurePosition { pub position: BlockPos, diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress.rs index 05a257385..f757d2cd6 100644 --- a/pumpkin-world/src/generation/structure/structures/nether_fortress.rs +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress.rs @@ -1,11 +1,14 @@ -use pumpkin_util::math::position::BlockPos; +use pumpkin_data::Block; +use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; use serde::Deserialize; use crate::{ ProtoChunk, generation::{ positions::chunk_pos, - structure::structures::{StructureGenerator, StructurePiecesCollector, StructurePosition}, + structure::structures::{ + StructureGenerator, StructurePiecesCollector, StructurePosition, fill_with_outline, + }, }, }; @@ -26,5 +29,119 @@ impl StructureGenerator for NetherFortressGenerator { generator, } } - fn generate(&self, _position: BlockPos, _chunk: &mut crate::ProtoChunk) {} + fn generate(&self, position: BlockPos, chunk: &mut crate::ProtoChunk) { + BridgePlatform::generate(&BridgePlatform, position, chunk); + } +} + +pub struct BridgePlatform; + +impl BridgePlatform { + fn generate(&self, _position: BlockPos, chunk: &mut crate::ProtoChunk) { + super::fill(0, 2, 0, 6, 7, 7, Block::AIR.default_state, chunk); + super::fill(1, 0, 0, 5, 1, 7, Block::NETHER_BRICKS.default_state, chunk); + super::fill(1, 2, 1, 5, 2, 7, Block::NETHER_BRICKS.default_state, chunk); + super::fill(1, 3, 2, 5, 3, 7, Block::NETHER_BRICKS.default_state, chunk); + super::fill(1, 4, 3, 5, 4, 7, Block::NETHER_BRICKS.default_state, chunk); + super::fill(1, 2, 0, 1, 4, 2, Block::NETHER_BRICKS.default_state, chunk); + super::fill(5, 2, 0, 5, 4, 2, Block::NETHER_BRICKS.default_state, chunk); + super::fill(1, 5, 2, 1, 5, 3, Block::NETHER_BRICKS.default_state, chunk); + super::fill(5, 5, 2, 5, 5, 3, Block::NETHER_BRICKS.default_state, chunk); + super::fill(0, 5, 3, 0, 5, 8, Block::NETHER_BRICKS.default_state, chunk); + super::fill(6, 5, 3, 6, 5, 8, Block::NETHER_BRICKS.default_state, chunk); + super::fill(1, 5, 8, 5, 5, 8, Block::NETHER_BRICKS.default_state, chunk); + // TODO + let block_state = Block::NETHER_BRICK_FENCE.default_state; + let block_state2 = Block::NETHER_BRICK_FENCE.default_state; + + chunk.set_block_state( + &Vector3::new(1, 6, 3), + Block::NETHER_BRICK_FENCE.default_state, + ); + chunk.set_block_state( + &Vector3::new(5, 6, 3), + Block::NETHER_BRICK_FENCE.default_state, + ); + chunk.set_block_state( + &Vector3::new(0, 6, 3), + Block::NETHER_BRICK_FENCE.default_state, + ); + chunk.set_block_state( + &Vector3::new(6, 6, 3), + Block::NETHER_BRICK_FENCE.default_state, + ); + + fill_with_outline(0, 6, 4, 0, 6, 7, block_state2, block_state2, chunk); + fill_with_outline(6, 6, 4, 6, 6, 7, block_state2, block_state2, chunk); + + chunk.set_block_state( + &Vector3::new(0, 6, 8), + Block::NETHER_BRICK_FENCE.default_state, + ); + chunk.set_block_state( + &Vector3::new(6, 6, 8), + Block::NETHER_BRICK_FENCE.default_state, + ); + + fill_with_outline(1, 6, 8, 5, 6, 8, block_state, block_state, chunk); + + chunk.set_block_state( + &Vector3::new(1, 7, 8), + Block::NETHER_BRICK_FENCE.default_state, + ); + + fill_with_outline(2, 7, 8, 4, 7, 8, block_state, block_state, chunk); + + chunk.set_block_state( + &Vector3::new(5, 7, 8), + Block::NETHER_BRICK_FENCE.default_state, + ); + chunk.set_block_state( + &Vector3::new(2, 8, 8), + Block::NETHER_BRICK_FENCE.default_state, + ); + chunk.set_block_state(&Vector3::new(3, 8, 8), block_state); + chunk.set_block_state( + &Vector3::new(4, 8, 8), + Block::NETHER_BRICK_FENCE.default_state, + ); + + // Blaze Spawner + chunk.set_block_state( + &Vector3::new(3, 5, 5), // ? + Block::SPAWNER.default_state, + ); + + for x in 0..6 { + for z in 0..6 { + super::fill_downwards(x, -1, z, Block::NETHER_BRICKS.default_state, chunk); + } + } + } +} + +pub struct SmallCorridor; + +impl SmallCorridor { + fn generate(&self, _position: BlockPos, chunk: &mut crate::ProtoChunk) { + super::fill(0, 0, 0, 4, 1, 4, Block::NETHER_BRICKS.default_state, chunk); + super::fill(0, 2, 0, 4, 5, 4, Block::AIR.default_state, chunk); + + // TODO + let block_state = Block::NETHER_BRICK_FENCE.default_state; + + super::fill(0, 2, 0, 0, 5, 4, Block::NETHER_BRICKS.default_state, chunk); + super::fill(4, 2, 0, 4, 5, 4, Block::NETHER_BRICKS.default_state, chunk); + super::fill(0, 3, 1, 0, 4, 1, block_state, chunk); + super::fill(0, 3, 3, 0, 4, 3, block_state, chunk); + super::fill(4, 3, 1, 4, 4, 1, block_state, chunk); + super::fill(4, 3, 3, 4, 4, 3, block_state, chunk); + super::fill(0, 6, 0, 4, 6, 4, Block::NETHER_BRICKS.default_state, chunk); + + for i in 0..=4 { + for j in 0..=4 { + super::fill_downwards(i, -1, j, Block::NETHER_BRICKS.default_state, chunk); + } + } + } } diff --git a/pumpkin/src/block/blocks/candle_cakes.rs b/pumpkin/src/block/blocks/candle_cakes.rs index e273b11ef..644947805 100644 --- a/pumpkin/src/block/blocks/candle_cakes.rs +++ b/pumpkin/src/block/blocks/candle_cakes.rs @@ -103,7 +103,8 @@ impl CandleCakeBlock { #[async_trait] impl BlockBehaviour for CandleCakeBlock { async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { - match args.item_stack.lock().await.item.id { + let item_id = args.item_stack.lock().await.item.id; + match item_id { id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => { BlockActionResult::Pass } // Item::FIRE_CHARGE | Item::FLINT_AND_STEEL diff --git a/pumpkin/src/command/commands/damage.rs b/pumpkin/src/command/commands/damage.rs index c2cfb80de..93b0912c4 100644 --- a/pumpkin/src/command/commands/damage.rs +++ b/pumpkin/src/command/commands/damage.rs @@ -36,7 +36,7 @@ struct LocationExecutor; struct EntityExecutor(bool); async fn send_damage_result( - sender: &mut CommandSender, + sender: &CommandSender, success: bool, amount: f32, target_name: TextComponent, diff --git a/pumpkin/src/command/commands/data.rs b/pumpkin/src/command/commands/data.rs index de9ea82c8..117968cdf 100644 --- a/pumpkin/src/command/commands/data.rs +++ b/pumpkin/src/command/commands/data.rs @@ -176,7 +176,7 @@ pub fn snbt_colorful_display(tag: &NbtTag, depth: usize) -> Result { let int_array_format = TextComponent::text("I").color_named(NamedColor::Red); let mut content = TextComponent::text("[") - .add_child(int_array_format.clone()) + .add_child(int_array_format) .add_child(TextComponent::text("; ")); for (index, int) in value.iter().take(128).enumerate() { diff --git a/pumpkin/src/command/commands/experience.rs b/pumpkin/src/command/commands/experience.rs index a20381bd2..7f056f4ce 100644 --- a/pumpkin/src/command/commands/experience.rs +++ b/pumpkin/src/command/commands/experience.rs @@ -45,7 +45,7 @@ struct Executor { } impl Executor { - async fn handle_query(&self, sender: &mut CommandSender, target: &Player, exp_type: ExpType) { + async fn handle_query(&self, sender: &CommandSender, target: &Player, exp_type: ExpType) { match exp_type { ExpType::Levels => { let level = target.experience_level.load(Ordering::Relaxed); diff --git a/pumpkin/src/command/commands/setworldspawn.rs b/pumpkin/src/command/commands/setworldspawn.rs index 4f416ef26..3de19739b 100644 --- a/pumpkin/src/command/commands/setworldspawn.rs +++ b/pumpkin/src/command/commands/setworldspawn.rs @@ -93,7 +93,7 @@ impl CommandExecutor for AngleWorldSpawnExecutor { } async fn setworldspawn( - sender: &mut CommandSender, + sender: &CommandSender, server: &Server, block_pos: BlockPos, yaw: f32, diff --git a/pumpkin/src/command/commands/tick.rs b/pumpkin/src/command/commands/tick.rs index 702fcaa07..cbcd47975 100644 --- a/pumpkin/src/command/commands/tick.rs +++ b/pumpkin/src/command/commands/tick.rs @@ -56,7 +56,7 @@ struct TickExecutor(SubCommand); impl TickExecutor { async fn handle_query( &self, - sender: &mut CommandSender, + sender: &CommandSender, server: &crate::server::Server, manager: &crate::server::tick_rate_manager::ServerTickRateManager, ) -> Result<(), CommandError> { @@ -102,7 +102,7 @@ impl TickExecutor { } async fn handle_non_sprinting_status( &self, - sender: &mut CommandSender, + sender: &CommandSender, manager: &crate::server::tick_rate_manager::ServerTickRateManager, avg_tick_nanos: i64, ) { @@ -121,7 +121,7 @@ impl TickExecutor { } } - async fn send_percentiles(&self, sender: &mut CommandSender, server: &crate::server::Server) { + async fn send_percentiles(&self, sender: &CommandSender, server: &crate::server::Server) { let tick_count = server.tick_count.load(Ordering::Relaxed); let sample_size = (tick_count as usize).min(100); @@ -149,7 +149,7 @@ impl TickExecutor { } async fn handle_step_command( &self, - sender: &mut CommandSender, + sender: &CommandSender, server: &crate::server::Server, manager: &crate::server::tick_rate_manager::ServerTickRateManager, ticks: i32, @@ -173,7 +173,7 @@ impl TickExecutor { } async fn handle_sprint_command( &self, - sender: &mut CommandSender, + sender: &CommandSender, server: &crate::server::Server, manager: &crate::server::tick_rate_manager::ServerTickRateManager, ticks: i32, diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index 0d6e1197c..92fc92c60 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -83,7 +83,7 @@ impl CommandDispatcher { /// - do not query suggestions for the same consumer multiple times just because they are on different paths through the tree pub(crate) async fn find_suggestions<'a>( &'a self, - src: &mut CommandSender, + src: &CommandSender, server: &'a Server, cmd: &'a str, ) -> Vec { @@ -357,7 +357,7 @@ impl CommandDispatcher { } async fn try_find_suggestions_on_path<'a>( - src: &mut CommandSender, + src: &CommandSender, server: &'a Server, path: &[usize], tree: &'a CommandTree, diff --git a/pumpkin/src/data/banlist_serializer.rs b/pumpkin/src/data/banlist_serializer.rs index 3cca8c667..b3c9ae60e 100644 --- a/pumpkin/src/data/banlist_serializer.rs +++ b/pumpkin/src/data/banlist_serializer.rs @@ -83,7 +83,7 @@ mod format { date: &OffsetDateTime, serializer: S, ) -> Result { - let s = date.format(DATE_FORMAT).unwrap().to_string(); + let s = date.format(DATE_FORMAT).unwrap(); serializer.serialize_str(&s) } @@ -107,7 +107,7 @@ mod format { serializer: S, ) -> Result { if let Some(date) = date { - let s = date.format(DATE_FORMAT).unwrap().to_string(); + let s = date.format(DATE_FORMAT).unwrap(); serializer.serialize_str(&s) } else { serializer.serialize_str("forever") diff --git a/pumpkin/src/data/player_server_data.rs b/pumpkin/src/data/player_server_data.rs index e7f137fae..e2d5bffcb 100644 --- a/pumpkin/src/data/player_server_data.rs +++ b/pumpkin/src/data/player_server_data.rs @@ -295,7 +295,7 @@ mod test { let path = temp_dir.path().to_path_buf(); let uuid = Uuid::new_v4(); - let mut storage = PlayerDataStorage::new(path.clone()); + let mut storage = PlayerDataStorage::new(path); storage.set_save_enabled(true); // Create and save player data diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index cbf2a7616..0e8c1e0d1 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -164,14 +164,15 @@ impl LivingEntity { } pub async fn add_effect(&self, effect: Effect) { - let mut effects = self.active_effects.lock().await; - effects.insert(effect.effect_type, effect); + self.active_effects + .lock() + .await + .insert(effect.effect_type, effect); // TODO broadcast metadata } pub async fn remove_effect(&self, effect_type: &'static StatusEffect) { - let mut effects = self.active_effects.lock().await; - effects.remove(&effect_type); + self.active_effects.lock().await.remove(&effect_type); self.entity .world .read() diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index ce7a50bd1..763b8306f 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -757,8 +757,6 @@ impl Entity { let max = aabb.max_block_pos(); - let world = self.world.read().await; - let mut eye_level_box = aabb; let eye_height = f64::from(self.standing_eye_height); @@ -767,6 +765,7 @@ impl Entity { eye_level_box.max.y = eye_level_box.min.y; + let world = self.world.read().await; for x in min.0.x..=max.0.x { for y in min.0.y..=max.0.y { for z in min.0.z..=max.0.z { @@ -1126,9 +1125,12 @@ impl Entity { #[allow(clippy::float_cmp)] async fn get_velocity_multiplier(&self) -> f32 { - let world = self.world.read().await; - - let block = world.get_block(&self.block_pos.load()).await; + let block = self + .world + .read() + .await + .get_block(&self.block_pos.load()) + .await; let multiplier = block.velocity_multiplier; @@ -1143,9 +1145,10 @@ impl Entity { #[allow(clippy::float_cmp)] async fn get_jump_velocity_multiplier(&self) -> f32 { - let world = self.world.read().await; - - let f = world + let f = self + .world + .read() + .await .get_block(&self.block_pos.load()) .await .jump_velocity_multiplier; @@ -1302,6 +1305,7 @@ impl Entity { portal_manager.portal_world.clone(), ) .await; + drop(portal_manager); } else if portal_manager.ticks_in_portal == 0 { should_remove = true; } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 46d5bf1da..f652d5474 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1075,8 +1075,7 @@ impl Player { pitch: Option, ) { let current_world = self.living_entity.entity.world.read().await.clone(); - let info = &new_world.level_info.read().await; - let yaw = yaw.unwrap_or(info.spawn_angle); + let yaw = yaw.unwrap_or(new_world.level_info.read().await.spawn_angle); let pitch = pitch.unwrap_or(10.0); send_cancellable! {{ @@ -1212,8 +1211,7 @@ impl Player { } pub async fn add_exhaustion(&self, exhaustion: f32) { - let abilities = self.abilities.lock().await; - if abilities.invulnerable { + if self.abilities.lock().await.invulnerable { return; } self.hunger_manager.add_exhaustion(exhaustion); @@ -1456,7 +1454,7 @@ impl Player { pub async fn drop_held_item(&self, drop_stack: bool) { // should be locked first otherwise cause deadlock in tick() (this thread lock stack, that thread lock screen_handler) - let screen_binding = self.current_screen_handler.lock().await; + let binding = self.inventory.held_item(); let mut item_stack = binding.lock().await; @@ -1467,6 +1465,7 @@ impl Player { item_stack.decrement(drop_amount); let selected_slot = self.inventory.get_selected_slot(); let inv: Arc = self.inventory.clone(); + let screen_binding = self.current_screen_handler.lock().await; let mut screen_handler = screen_binding.lock().await; let slot_index = screen_handler .get_slot_index(&inv, selected_slot as usize) @@ -1838,6 +1837,7 @@ impl Player { screen_handler.update_to_client().await; } else { screen_handler.send_content_updates().await; + drop(screen_handler); } } @@ -1963,18 +1963,19 @@ impl NBTStorage for PlayerInventory { let mut item_compound = NbtCompound::new(); item_compound.put_byte("Slot", i as i8); stack.write_item_stack(&mut item_compound); + drop(stack); vec.push(NbtTag::Compound(item_compound)); } } let mut equipment_compound = NbtCompound::new(); for slot in self.equipment_slots.values() { - let equipment_binding = self.entity_equipment.lock().await; - let stack_binding = equipment_binding.get(slot); + let stack_binding = self.entity_equipment.lock().await.get(slot); let stack = stack_binding.lock().await; if !stack.is_empty() { let mut item_compound = NbtCompound::new(); stack.write_item_stack(&mut item_compound); + drop(stack); match slot { EquipmentSlot::OffHand(_) => { equipment_compound.put_component("offhand", item_compound); @@ -2067,8 +2068,12 @@ impl EntityBase for Player { if self.abilities.lock().await.invulnerable && damage_type != DamageType::GENERIC_KILL { return false; } - let world = self.living_entity.entity.world.read().await; - let dyn_self = world + let dyn_self = self + .living_entity + .entity + .world + .read() + .await .get_entity_by_id(self.living_entity.entity.entity_id) .await .expect("Entity not found in world"); @@ -2380,14 +2385,14 @@ impl LastSeen { pub async fn indexed_for(&self, recipient: &Arc) -> Box<[PreviousMessage]> { let mut indexed = Vec::new(); for signature in &self.0 { - if let Some(index) = recipient + let index = recipient .signature_cache .lock() .await .full_cache .iter() - .position(|s| s == signature) - { + .position(|s| s == signature); + if let Some(index) = index { indexed.push(PreviousMessage { // Send ID reference to recipient's cache (index + 1 because 0 is reserved for full signature) id: VarInt(1 + index as i32), diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index 2a9117fc0..7d34cc7c9 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -190,6 +190,9 @@ impl PumpkinServer { } if rcon.enabled { + log::warn!( + "RCON is enabled, but it's highly insecure as it transmits passwords and commands in plain text. This makes it vulnerable to interception and exploitation by anyone on the network" + ); let rcon_server = server.clone(); server.spawn_task(async move { RCONServer::run(&rcon, rcon_server).await.unwrap(); diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index f016114a0..e9f7f0f56 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -15,6 +15,12 @@ #![deny(clippy::branches_sharing_code)] #![deny(clippy::equatable_if_let)] #![deny(clippy::option_if_let_else)] +#![deny(clippy::needless_pass_by_ref_mut)] +#![deny(clippy::needless_collect)] +#![deny(clippy::redundant_clone)] +#![deny(clippy::branches_sharing_code)] +#![deny(clippy::set_contains_or_insert)] +#![deny(clippy::significant_drop_in_scrutinee)] // use log crate #![deny(clippy::print_stdout)] #![deny(clippy::print_stderr)] diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index 12337114d..89c689eb5 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -387,7 +387,7 @@ impl BedrockClient { self.tasks.wait().await; self.be_clients.lock().await.remove(&self.address); - if let Some(player) = self.player.lock().await.clone() { + if let Some(player) = self.player.lock().await.as_ref() { player.remove().await; } } diff --git a/pumpkin/src/net/bedrock/play.rs b/pumpkin/src/net/bedrock/play.rs index e370ce192..5022ebc40 100644 --- a/pumpkin/src/net/bedrock/play.rs +++ b/pumpkin/src/net/bedrock/play.rs @@ -106,7 +106,6 @@ impl BedrockClient { ); let entity = &player.living_entity.entity; - let world = &entity.world.read().await; if BASIC_CONFIG.allow_chat_reports { //TODO Alex help, what is this? //world.broadcast_secure_player_chat(player, &message, decorated_message).await; @@ -120,7 +119,7 @@ impl BedrockClient { message, gameprofile.name.clone() ); - world.broadcast_editioned(&je_packet, &be_packet).await; + entity.world.read().await.broadcast_editioned(&je_packet, &be_packet).await; } } }} diff --git a/pumpkin/src/net/java/login.rs b/pumpkin/src/net/java/login.rs index 799406a1b..4b52212d1 100644 --- a/pumpkin/src/net/java/login.rs +++ b/pumpkin/src/net/java/login.rs @@ -327,6 +327,7 @@ impl JavaClient { self.finish_login(&profile).await; *self.gameprofile.lock().await = Some(profile); *address = new_address; + drop(address); } Err(error) => self.kick(TextComponent::text(error.to_string())).await, } diff --git a/pumpkin/src/net/java/mod.rs b/pumpkin/src/net/java/mod.rs index d901edebf..c4e570ccd 100644 --- a/pumpkin/src/net/java/mod.rs +++ b/pumpkin/src/net/java/mod.rs @@ -342,8 +342,7 @@ impl JavaClient { } ConnectionState::Config => self.handle_config_packet(server, packet).await, ConnectionState::Play => { - let player = self.player.lock().await; - if let Some(player) = player.as_ref() { + if let Some(player) = self.player.lock().await.as_ref() { match self.handle_play_packet(player, server, packet).await { Ok(()) => {} Err(e) => { diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index 7abe3d0fc..9e1328e12 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -183,6 +183,7 @@ impl JavaClient { player.living_entity.entity.set_pos(*position); *awaiting_teleport = None; + drop(awaiting_teleport); } else { self.kick(TextComponent::text("Wrong teleport id")).await; } @@ -542,8 +543,7 @@ impl JavaClient { // Some commands can take a long time to execute. If they do, they block packet processing for the player. // That's why we will spawn a task instead. server.spawn_task(async move { - let dispatcher = server_clone.command_dispatcher.read().await; - dispatcher + server_clone.command_dispatcher.read().await .handle_command( &mut CommandSender::Player(player_clone), &server_clone, @@ -888,10 +888,7 @@ impl JavaClient { } // Update the chat session fields - let mut chat_session = player.chat_session.lock().await; // Await the lock - - // Update the chat session fields - *chat_session = ChatSession::new( + *player.chat_session.lock().await = ChatSession::new( session.session_id, session.expires_at, session.public_key.clone(), @@ -1246,8 +1243,10 @@ impl JavaClient { } player.mining.store(false, Ordering::Relaxed); let entity = &player.living_entity.entity; - let world = &entity.world.read().await; - world + entity + .world + .read() + .await .set_block_breaking(entity, player_action.position, -1) .await; self.update_sequence(player, player_action.sequence.0); @@ -1272,14 +1271,14 @@ impl JavaClient { world.set_block_breaking(entity, location, -1).await; let (block, state) = world.get_block_and_state(&location).await; - let drop = player.gamemode.load() != GameMode::Creative + let block_drop = player.gamemode.load() != GameMode::Creative && player.can_harvest(state, block).await; world .break_block( &location, Some(player.clone()), - if drop { + if block_drop { BlockFlags::NOTIFY_NEIGHBORS } else { BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS @@ -1448,14 +1447,16 @@ impl JavaClient { .await; // Check if the item is a block, because not every item can be placed :D - if let Some(block) = Block::from_item_id(item.lock().await.item.id) { + let item_id = item.lock().await.item.id; + if let Some(block) = Block::from_item_id(item_id) { should_try_decrement = self .run_is_block_place(player, block, server, use_item_on, position, face) .await?; } // Check if the item is a spawn egg - if let Some(entity) = entity_from_egg(item.lock().await.item.id) { + let item_id = item.lock().await.item.id; + if let Some(entity) = entity_from_egg(item_id) { self.spawn_entity_from_egg(player, entity, position, face) .await; should_try_decrement = true; @@ -1655,6 +1656,7 @@ impl JavaClient { .await; player_screen_handler.set_received_stack(packet.slot as usize, item_stack); player_screen_handler.send_content_updates().await; + drop(player_screen_handler); } else if is_negative && is_legal { // Item drop player.drop_item(item_stack).await; @@ -1663,8 +1665,11 @@ impl JavaClient { } pub async fn handle_chunk_batch(&self, player: &Player, packet: SChunkBatch) { - let mut chunk_manager = player.chunk_manager.lock().await; - chunk_manager.handle_acknowledge(packet.chunks_per_tick); + player + .chunk_manager + .lock() + .await + .handle_acknowledge(packet.chunks_per_tick); log::trace!( "Client requested {} chunks per tick", packet.chunks_per_tick @@ -1686,7 +1691,7 @@ impl JavaClient { packet: SCommandSuggestion, server: &Arc, ) { - let mut src = CommandSender::Player(player.clone()); + let src = CommandSender::Player(player.clone()); let Some(cmd) = &packet.command.get(1..) else { return; }; @@ -1696,8 +1701,12 @@ impl JavaClient { return; }; - let dispatcher = server.command_dispatcher.read().await; - let suggestions = dispatcher.find_suggestions(&mut src, server, cmd).await; + let suggestions = server + .command_dispatcher + .read() + .await + .find_suggestions(&src, server, cmd) + .await; let response = CCommandSuggestions::new( packet.id, @@ -1762,7 +1771,6 @@ impl JavaClient { face: BlockDirection, ) -> Result { let entity = &player.living_entity.entity; - let world = &entity.world.read().await; // Check if the block is under the world if location.0.y + face.to_offset().y < i32::from(Self::WORLD_LOWEST_Y) { @@ -1792,6 +1800,8 @@ impl JavaClient { } let clicked_block_pos = BlockPos(location.0); + let world = &entity.world.read().await; + let (clicked_block, clicked_block_state) = world.get_block_and_state(&clicked_block_pos).await; diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs index 8b79ecb2c..e72c67da8 100644 --- a/pumpkin/src/net/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -194,7 +194,7 @@ pub async fn can_not_join( Some(expires) => text.add_child(TextComponent::translate( "multiplayer.disconnect.banned.expiration", [TextComponent::text( - expires.format(FORMAT_DESCRIPTION).unwrap().to_string(), + expires.format(FORMAT_DESCRIPTION).unwrap(), )], )), None => text, @@ -214,8 +214,7 @@ pub async fn can_not_join( } } - let mut banned_ips = BANNED_IP_LIST.write().await; - if let Some(entry) = banned_ips.get_entry(&address.ip()) { + if let Some(entry) = BANNED_IP_LIST.write().await.get_entry(&address.ip()) { let text = TextComponent::translate( "multiplayer.disconnect.banned_ip.reason", [TextComponent::text(entry.reason.clone())], @@ -224,7 +223,7 @@ pub async fn can_not_join( Some(expires) => text.add_child(TextComponent::translate( "multiplayer.disconnect.banned_ip.expiration", [TextComponent::text( - expires.format(FORMAT_DESCRIPTION).unwrap().to_string(), + expires.format(FORMAT_DESCRIPTION).unwrap(), )], )), None => text, diff --git a/pumpkin/src/net/rcon/mod.rs b/pumpkin/src/net/rcon/mod.rs index 55a830841..18d17403f 100644 --- a/pumpkin/src/net/rcon/mod.rs +++ b/pumpkin/src/net/rcon/mod.rs @@ -126,8 +126,10 @@ impl RCONClient { let output_clone = output.clone(); let packet_body = packet.get_body().to_owned(); tokio::spawn(async move { - let dispatcher = server_clone.command_dispatcher.read().await; - dispatcher + server_clone + .command_dispatcher + .read() + .await .handle_command( &mut crate::command::CommandSender::Rcon(output_clone), &server_clone, diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index 30a452134..aec062a09 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -187,9 +187,8 @@ impl Context { )); } - let manager = self.permission_manager.read().await; - let mut registry = manager.registry.write().await; - registry.register_permission(permission) + let registry = &self.permission_manager.read().await.registry; + registry.write().await.register_permission(permission) } /// Check if a player has a permission diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index fc380c6c7..cbefebda7 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -302,16 +302,13 @@ impl PluginManager { } /// Start loading a plugin asynchronously + #[allow(clippy::too_many_lines)] async fn start_loading_plugin( &self, path: &Path, ) -> Result, ManagerError> { - let loaders = self.loaders.read().await; - for loader in loaders.iter() { + for loader in self.loaders.read().await.iter() { if loader.can_load(path) { - let server = self.server.read().await; - let self_ref = self.self_ref.read().await; - let (mut instance, metadata, loader_data) = loader.load(path).await?; // Mark plugin as loading @@ -320,15 +317,25 @@ impl PluginManager { .await .insert(metadata.name.to_string(), PluginState::Loading); - let self_ref = self_ref - .as_ref() + let self_ref = self + .self_ref + .read() + .await + .clone() .ok_or(ManagerError::ServerNotInitialized)?; let context = Arc::new(Context::new( metadata.clone(), - Arc::clone(server.as_ref().ok_or(ManagerError::ServerNotInitialized)?), + Arc::clone( + &self + .server + .read() + .await + .clone() + .ok_or(ManagerError::ServerNotInitialized)?, + ), Arc::clone(&self.handlers), - Arc::clone(self_ref), + Arc::clone(&self_ref), Arc::clone(&PERMISSION_MANAGER), )); @@ -352,7 +359,7 @@ impl PluginManager { self.unloaded_files.write().await.remove(path); // Spawn async task for plugin initialization - let self_ref_clone = Arc::clone(self_ref); + let self_ref_clone = Arc::clone(&self_ref); let state_notify = Arc::clone(&self.state_notify); let plugin_name = metadata.name.to_string(); let loader_clone = loader.clone(); @@ -447,7 +454,7 @@ impl PluginManager { PluginState::Loaded => return Ok(()), PluginState::Failed(error) => { return Err(ManagerError::LoaderError( - LoaderError::InitializationFailed(error.clone()), + LoaderError::InitializationFailed(error), )); } PluginState::Loading => { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 5271ccd2c..55257cd7e 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -213,10 +213,10 @@ impl Server { log::info!("Loading End: {seed}"); let end = World::load( Dimension::End.into_level(world_path.clone(), block_registry.clone(), seed), - level_info.clone(), + level_info, VanillaDimensionType::TheEnd, - block_registry.clone(), - weak.clone(), + block_registry, + weak, ); *server .worlds @@ -239,13 +239,14 @@ impl Server { pub async fn get_world_from_dimension(&self, dimension: VanillaDimensionType) -> Arc { // TODO: this is really bad let world_guard = self.worlds.read().await; - let world = match dimension { + match dimension { VanillaDimensionType::Overworld => world_guard.first(), VanillaDimensionType::OverworldCaves => todo!(), VanillaDimensionType::TheEnd => world_guard.get(2), VanillaDimensionType::TheNether => world_guard.get(1), - }; - world.cloned().unwrap() + } + .cloned() + .unwrap() } #[allow(clippy::if_then_some_else_none)] @@ -291,27 +292,36 @@ impl Server { (world, Some(data)) } else { log::warn!("Invalid dimension key in player data: {dimension_key}"); - let default_world_guard = self.worlds.read().await; - let default_world = default_world_guard + let default_world = self + .worlds + .read() + .await .first() - .expect("Default world should exist"); - (default_world.clone(), Some(data)) + .expect("Default world should exist") + .clone(); + (default_world, Some(data)) } } else { // Player data exists but doesn't have a "Dimension" key. - let default_world_guard = self.worlds.read().await; - let default_world = default_world_guard + let default_world = self + .worlds + .read() + .await .first() - .expect("Default world should exist"); - (default_world.clone(), Some(data)) + .expect("Default world should exist") + .clone(); + (default_world, Some(data)) } } else { // No player data found or an error occurred, default to the Overworld. - let default_world_guard = self.worlds.read().await; - let default_world = default_world_guard + let default_world = self + .worlds + .read() + .await .first() - .expect("Default world should exist"); - (default_world.clone(), None) + .expect("Default world should exist") + .clone(); + (default_world, None) }; let mut player = Player::new( @@ -457,6 +467,7 @@ impl Server { level_info.difficulty_locked, )) .await; + drop(level_info); } } @@ -708,15 +719,7 @@ impl Server { EntitySelectorType::AllEntities => { let mut entities = Vec::new(); for world in self.worlds.read().await.iter() { - entities.extend( - world - .entities - .read() - .await - .values() - .cloned() - .collect::>(), - ); + entities.extend(world.entities.read().await.values().cloned()); entities.extend( world .players @@ -724,8 +727,7 @@ impl Server { .await .values() .cloned() - .map(|p| p as Arc) - .collect::>(), + .map(|p| p as Arc), ); } entities.into_iter() diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 37716c53c..2a6d0e98a 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -283,6 +283,7 @@ impl World { chunk.mark_dirty(true); if old_chunk == current_chunk_coordinate { chunk.data.remove(&entity.entity_uuid); + drop(chunk); } else { let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await; let mut chunk = chunk.write().await; @@ -433,10 +434,13 @@ impl World { .await .add_seen_signature(&chat_message.signature.clone().unwrap()); // Unwrap is safe because we check for None in validate_chat_message - let recipient_signature_cache = &mut recipient.signature_cache.lock().await; if recipient.gameprofile.id != sender.gameprofile.id { // Sender may update recipient on signatures recipient hasn't seen - recipient_signature_cache.cache_signatures(sender_last_seen.as_ref()); + recipient + .signature_cache + .lock() + .await + .cache_signatures(sender_last_seen.as_ref()); } recipient.chat_session.lock().await.messages_received += 1; } @@ -587,9 +591,11 @@ impl World { if weather.weather_cycle_enabled && (weather.raining || weather.thundering) { weather.reset_weather_cycle(self).await; + drop(weather); } } else if level_time.world_age % 20 == 0 { level_time.send_time(self).await; + drop(level_time); } } @@ -1246,6 +1252,8 @@ impl World { scenario_id: String::new(), owner_id: String::new(), }; + drop(level_info); + drop(weather); let client = player.client.bedrock(); client @@ -1436,9 +1444,7 @@ impl World { f64::from(info.spawn_z) + 0.5, ); let yaw = info.spawn_angle; - let pitch = 0.0; - - (position, yaw, pitch) + (position, yaw, 0.0) // Pitch }; let velocity = player.living_entity.entity.velocity.load(); @@ -1470,11 +1476,9 @@ impl World { }], )) .await; - + let current_players = self.players.read().await; // Here, we send all the infos of players who already joined. { - let current_players = self.players.read().await; - let mut current_player_data = Vec::new(); for (_, player) in current_players @@ -1499,6 +1503,7 @@ impl World { signature: chat_session.signature.clone(), }))); } + drop(chat_session); current_player_data.push((&player.gameprofile.id, player_actions)); } @@ -1544,7 +1549,7 @@ impl World { // Spawn players for our client. let id = player.gameprofile.id; - for (_, existing_player) in self.players.read().await.iter().filter(|c| c.0 != &id) { + for (_, existing_player) in current_players.iter().filter(|c| c.0 != &id) { let entity = &existing_player.living_entity.entity; let pos = entity.pos.load(); let gameprofile = &existing_player.gameprofile; @@ -1563,34 +1568,37 @@ impl World { entity.velocity.load(), )) .await; - let config = existing_player.config.read().await; - let mut buf = Vec::new(); - for meta in [ - Metadata::new( - DATA_PLAYER_MODE_CUSTOMISATION, - MetaDataType::Byte, - config.skin_parts, - ), - Metadata::new( - DATA_PLAYER_MAIN_HAND, - MetaDataType::Byte, - config.main_hand as u8, - ), - ] { - let mut serializer_buf = Vec::new(); + { + let config = existing_player.config.read().await; + let mut buf = Vec::new(); + for meta in [ + Metadata::new( + DATA_PLAYER_MODE_CUSTOMISATION, + MetaDataType::Byte, + config.skin_parts, + ), + Metadata::new( + DATA_PLAYER_MAIN_HAND, + MetaDataType::Byte, + config.main_hand as u8, + ), + ] { + let mut serializer_buf = Vec::new(); - let mut serializer = Serializer::new(&mut serializer_buf); - meta.serialize(&mut serializer).unwrap(); - buf.extend(serializer_buf); + let mut serializer = Serializer::new(&mut serializer_buf); + meta.serialize(&mut serializer).unwrap(); + buf.extend(serializer_buf); + } + drop(config); + // END + buf.put_u8(255); + client + .enqueue_packet(&CSetEntityMetadata::new( + existing_player.get_entity().entity_id.into(), + buf.into(), + )) + .await; } - // END - buf.put_u8(255); - client - .enqueue_packet(&CSetEntityMetadata::new( - existing_player.get_entity().entity_id.into(), - buf.into(), - )) - .await; } player.send_client_information().await; @@ -1622,6 +1630,7 @@ impl World { // Calculate rain and thunder levels directly from public fields let rain_level = weather.rain_level.clamp(0.0, 1.0); let thunder_level = weather.thunder_level.clamp(0.0, 1.0); + drop(weather); client .enqueue_packet(&CGameEvent::new(GameEvent::RainLevelChange, rain_level)) @@ -1658,8 +1667,7 @@ impl World { )); for (slot, item_arc_mutex) in &from.inventory.entity_equipment.lock().await.equipment { - let item_guard = item_arc_mutex.lock().await; - let item_stack = item_guard.clone(); + let item_stack = item_arc_mutex.lock().await.clone(); equipment_list.push((slot.discriminant(), item_stack)); } @@ -1829,6 +1837,7 @@ impl World { .is_some_and(|since| since >= 100) }) .count(); + drop(players); // TODO: sleep ratio sleeping_player_count == player_count @@ -1943,8 +1952,8 @@ impl World { }); let mut entity_receiver = self.level.receive_entity_chunks(chunks); let level = self.level.clone(); - let player = player1.clone(); - let world = world1.clone(); + let player = player1; + let world = world1; player.clone().spawn_task(async move { 'main: loop { let recv_result = tokio::select! { @@ -1971,9 +1980,8 @@ impl World { ); let mut ids = Vec::new(); // Remove all the entities from the world - let entity_chunk = chunk.read().await; let mut entities = world.entities.write().await; - for (uuid, entity_nbt) in &entity_chunk.data { + for (uuid, entity_nbt) in &chunk.read().await.data { let Some(id) = entity_nbt.get_string("id") else { log::warn!("Entity has no ID"); continue; @@ -2273,9 +2281,7 @@ impl World { let event = PLUGIN_MANAGER.fire(event).await; if !event.cancelled { - let current_players = current_players.clone(); - let players = current_players.read().await; - for player in players.values() { + for player in current_players.read().await.values() { player.send_system_message(&event.join_message).await; } log::info!("{}", event.join_message.to_pretty_console()); @@ -2330,6 +2336,7 @@ impl World { for player in players.values() { player.send_system_message(&event.leave_message).await; } + drop(players); log::info!("{}", event.leave_message.to_pretty_console()); } } @@ -2352,6 +2359,7 @@ impl World { entity.write_nbt(&mut nbt).await; chunk.data.insert(base_entity.entity_uuid, nbt); chunk.mark_dirty(true); + drop(chunk); let mut current_entities = self.entities.write().await; current_entities.insert(base_entity.entity_uuid, entity); diff --git a/pumpkin/src/world/natural_spawner.rs b/pumpkin/src/world/natural_spawner.rs index 777acf8a8..797557816 100644 --- a/pumpkin/src/world/natural_spawner.rs +++ b/pumpkin/src/world/natural_spawner.rs @@ -9,6 +9,7 @@ use pumpkin_data::tag::Taggable; use pumpkin_data::tag::WorldgenBiome::MINECRAFT_REDUCE_WATER_AMBIENT_SPAWNS; use pumpkin_data::{Block, BlockDirection, BlockState}; use pumpkin_util::GameMode; +use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions}; use pumpkin_util::math::get_section_cord; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector2::Vector2; @@ -505,14 +506,34 @@ pub async fn is_valid_spawn_position_for_type( distance: f64, ) -> bool { // TODO !SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random) - // TODO level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5)) - !(category == &MobCategory::MISC - || (!entity_type.can_spawn_far_from_player - && distance - > f64::from(entity_type.category.despawn_distance) - * f64::from(entity_type.category.despawn_distance)) - || !entity_type.summonable - || !is_spawn_position_ok(world, block_pos, entity_type).await) + if category == &MobCategory::MISC { + return false; + } + if !entity_type.can_spawn_far_from_player + && distance + > f64::from(entity_type.category.despawn_distance) + * f64::from(entity_type.category.despawn_distance) + { + return false; + } + if !entity_type.summonable { + return false; + } + if !is_spawn_position_ok(world, block_pos, entity_type).await { + return false; + } + // TODO: we should use getSpawnBox, but this is only modified for slimes and magma slimes + world + .is_space_empty(BoundingBox::new_from_pos( + f64::from(block_pos.0.x) + 0.5, + f64::from(block_pos.0.y), + f64::from(block_pos.0.z) + 0.5, + &EntityDimensions { + width: entity_type.dimension[0], + height: entity_type.dimension[1], + }, + )) + .await } pub async fn is_spawn_position_ok( @@ -536,6 +557,7 @@ pub async fn is_spawn_position_ok( let down = world.get_block_state(&block_pos.down()).await; let up = world.get_block_state(&block_pos.up()).await; let cur = world.get_block_state(block_pos).await; + // TODO: blockState.allowsSpawning if down.is_side_solid(BlockDirection::Up) { is_valid_empty_spawn_block(cur) && is_valid_empty_spawn_block(up) } else { @@ -548,16 +570,10 @@ pub async fn is_spawn_position_ok( #[must_use] pub fn is_valid_empty_spawn_block(state: &'static BlockState) -> bool { - if state.is_solid() { - false - } else if false { - // TODO isSignalSource - false - } else if state.is_liquid() { - false - } else { - // TODO !entityType.isBlockDangerous(blockState); - !Block::from_state_id(state.id) - .is_tagged_with_by_tag(&MINECRAFT_PREVENT_MOB_SPAWNING_INSIDE) + // TODO: emitsRedstonePower + if state.is_full_cube() || state.is_liquid() { + return false; } + // TODO !entityType.isBlockDangerous(blockState); + !Block::from_state_id(state.id).is_tagged_with_by_tag(&MINECRAFT_PREVENT_MOB_SPAWNING_INSIDE) }