From 37437ee0a013f3d8a776227516be151f5a490b4c Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 27 Aug 2026 21:23:24 +0200 Subject: [PATCH] fix: many bugs --- .../generation/structure/structures/igloo.rs | 517 ++++++++----- .../structure/structures/nether_fossil.rs | 300 ++++---- .../structure/structures/ruined_portal.rs | 708 ++++++++++++++++-- .../structure/structures/shipwreck.rs | 413 ++++++++-- .../structure/template/template_piece.rs | 198 +++-- crates/pumpkin-world/src/level.rs | 21 +- crates/pumpkin-world/src/world.rs | 4 - crates/pumpkin/src/block/blocks/bed.rs | 2 +- crates/pumpkin/src/block/blocks/doors.rs | 2 +- crates/pumpkin/src/block/entities/hopper.rs | 2 +- crates/pumpkin/src/block/registry.rs | 5 +- .../src/command/commands/fetchprofile.rs | 38 +- crates/pumpkin/src/command/commands/plugin.rs | 8 +- crates/pumpkin/src/command/commands/rotate.rs | 10 +- .../pumpkin/src/command/commands/saveall.rs | 6 +- .../pumpkin/src/command/commands/spectate.rs | 12 +- .../src/command/commands/spreadplayers.rs | 4 +- .../pumpkin/src/command/commands/teleport.rs | 52 +- crates/pumpkin/src/data/advancement_data.rs | 23 +- crates/pumpkin/src/data/player_server.rs | 41 +- .../pumpkin/src/entity/area_effect_cloud.rs | 2 +- .../pumpkin/src/entity/boss/ender_dragon.rs | 2 +- crates/pumpkin/src/entity/boss/wither.rs | 2 +- crates/pumpkin/src/entity/breath.rs | 11 +- .../pumpkin/src/entity/decoration/display.rs | 6 +- .../src/entity/decoration/item_frame.rs | 67 +- .../src/entity/decoration/leash_knot.rs | 2 +- crates/pumpkin/src/entity/experience_orb.rs | 2 +- crates/pumpkin/src/entity/falling.rs | 2 +- crates/pumpkin/src/entity/hunger.rs | 6 +- crates/pumpkin/src/entity/interaction.rs | 2 +- crates/pumpkin/src/entity/item.rs | 116 ++- crates/pumpkin/src/entity/lightning.rs | 2 +- crates/pumpkin/src/entity/living.rs | 256 +++---- crates/pumpkin/src/entity/marker.rs | 15 +- crates/pumpkin/src/entity/mob/bat.rs | 2 +- crates/pumpkin/src/entity/mob/creaking.rs | 2 +- crates/pumpkin/src/entity/mob/creeper.rs | 2 +- crates/pumpkin/src/entity/mob/enderman.rs | 4 +- crates/pumpkin/src/entity/mob/evoker.rs | 2 +- crates/pumpkin/src/entity/mob/magma_cube.rs | 2 +- crates/pumpkin/src/entity/mob/mod.rs | 4 +- crates/pumpkin/src/entity/mob/piglin.rs | 2 +- crates/pumpkin/src/entity/mob/piglin_brute.rs | 2 +- crates/pumpkin/src/entity/mob/shulker.rs | 2 +- crates/pumpkin/src/entity/mob/slime.rs | 2 +- crates/pumpkin/src/entity/mob/witch.rs | 2 +- crates/pumpkin/src/entity/mod.rs | 348 ++++----- .../pumpkin/src/entity/passive/armadillo.rs | 2 +- crates/pumpkin/src/entity/passive/chicken.rs | 2 +- crates/pumpkin/src/entity/passive/fox.rs | 2 +- crates/pumpkin/src/entity/passive/frog.rs | 2 +- .../pumpkin/src/entity/passive/happy_ghast.rs | 2 +- .../pumpkin/src/entity/passive/iron_golem.rs | 2 +- crates/pumpkin/src/entity/passive/nautilus.rs | 2 +- crates/pumpkin/src/entity/passive/sniffer.rs | 2 +- .../src/entity/passive/villager/mod.rs | 2 +- .../src/entity/passive/wandering_trader.rs | 2 +- crates/pumpkin/src/entity/player.rs | 322 ++++---- .../pumpkin/src/entity/player/advancement.rs | 30 +- crates/pumpkin/src/entity/projectile/arrow.rs | 2 +- crates/pumpkin/src/entity/projectile/egg.rs | 4 +- .../src/entity/projectile/ender_pearl.rs | 120 ++- .../src/entity/projectile/evoker_fangs.rs | 3 +- .../src/entity/projectile/eye_of_ender.rs | 61 +- .../pumpkin/src/entity/projectile/fireball.rs | 4 +- .../src/entity/projectile/firework_rocket.rs | 8 +- .../src/entity/projectile/fishing_bobber.rs | 7 +- .../src/entity/projectile/lingering_potion.rs | 4 +- .../src/entity/projectile/llama_spit.rs | 3 +- crates/pumpkin/src/entity/projectile/mod.rs | 4 +- .../src/entity/projectile/shulker_bullet.rs | 2 +- .../src/entity/projectile/small_fireball.rs | 3 +- .../pumpkin/src/entity/projectile/snowball.rs | 3 +- .../src/entity/projectile/splash_potion.rs | 2 +- .../pumpkin/src/entity/projectile/trident.rs | 2 +- .../src/entity/projectile/wind_charge.rs | 2 +- .../src/entity/projectile/wither_skull.rs | 3 +- crates/pumpkin/src/entity/tnt.rs | 11 +- crates/pumpkin/src/entity/vehicle/boat.rs | 2 +- crates/pumpkin/src/entity/vehicle/minecart.rs | 16 +- crates/pumpkin/src/lib.rs | 31 +- crates/pumpkin/src/net/bedrock/mod.rs | 49 +- .../src/net/bedrock/play/player_action.rs | 22 +- .../src/net/bedrock/play/player_auth_input.rs | 2 +- .../src/net/java/config/known_packs.rs | 87 +-- crates/pumpkin/src/net/java/mod.rs | 100 +-- .../src/net/java/play/player_action.rs | 19 +- .../src/net/java/play/player_position.rs | 8 +- .../src/net/java/play/spectate_entity.rs | 8 +- .../src/net/java/play/teleport_to_entity.rs | 4 +- crates/pumpkin/src/net/mod.rs | 26 +- crates/pumpkin/src/plugin/cache.rs | 9 +- .../loader/wasm/wasm_host/wit/v0_1/entity.rs | 14 +- .../loader/wasm/wasm_host/wit/v0_1/mob.rs | 207 ++--- .../loader/wasm/wasm_host/wit/v0_1/player.rs | 4 +- .../loader/wasm/wasm_host/wit/v0_1/server.rs | 2 +- crates/pumpkin/src/plugin/mod.rs | 16 +- crates/pumpkin/src/server/mod.rs | 196 ++--- crates/pumpkin/src/server/scheduler.rs | 2 +- crates/pumpkin/src/server/ticker.rs | 1 + crates/pumpkin/src/world/mod.rs | 115 +-- crates/pumpkin/src/world/natural_spawner.rs | 2 +- crates/pumpkin/src/world/portal/mod.rs | 2 +- 104 files changed, 2794 insertions(+), 2005 deletions(-) diff --git a/crates/pumpkin-world/src/generation/structure/structures/igloo.rs b/crates/pumpkin-world/src/generation/structure/structures/igloo.rs index 0454fa774..9130b4626 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/igloo.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/igloo.rs @@ -1,64 +1,118 @@ -//! Igloo structure generator for snowy biomes. -//! -//! Generates igloos matching vanilla Minecraft behavior using NBT templates: -//! - Snow block dome with interior furnishings (igloo/top.nbt) -//! - Optional basement (50% chance) with ladder shaft and secret room -//! - Ladder segments (igloo/middle.nbt) repeated 4-11 times -//! - Basement room (igloo/bottom.nbt) - use std::sync::Arc; -use pumpkin_data::block_rotation::Rotation; +use pumpkin_data::{Block, BlockState, Mirror, Rotation}; use pumpkin_util::{ math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, - random::RandomGenerator, + random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand}, }; -use serde::Deserialize; use crate::{ ProtoChunk, generation::{ - positions::chunk_pos::{get_center_x, get_center_z}, + positions::chunk_pos::{start_block_x, start_block_z}, structure::{ piece::StructurePieceType, - shiftable_piece::ShiftableStructurePiece, structures::{ StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, StructurePiecesCollector, StructurePosition, WorldPortalExt, }, - template::{StructureTemplate, get_template, place_template}, + template::{ + BlockStateResolver, StructurePlaceSettings, StructureTemplate, get_template, + processor::{IgnoredBlock, ProcessorContext, StructureProcessor}, + }, }, }, }; -use pumpkin_util::random::RandomImpl; +pub const GENERATION_HEIGHT: i32 = 90; +pub const STRUCTURE_LOCATION_IGLOO: &str = "igloo/top"; +pub const STRUCTURE_LOCATION_LADDER: &str = "igloo/middle"; +pub const STRUCTURE_LOCATION_LABORATORY: &str = "igloo/bottom"; -/// Igloo dome dimensions (from vanilla igloo/top.nbt). -const DOME_WIDTH: i32 = 7; -const DOME_HEIGHT: i32 = 5; -const DOME_DEPTH: i32 = 8; +#[must_use] +pub const fn get_pivot(location: &str) -> Vector3 { + if const_str_eq(location, STRUCTURE_LOCATION_IGLOO) { + Vector3::new(3, 5, 5) + } else if const_str_eq(location, STRUCTURE_LOCATION_LADDER) { + Vector3::new(1, 3, 1) + } else if const_str_eq(location, STRUCTURE_LOCATION_LABORATORY) { + Vector3::new(3, 6, 7) + } else { + Vector3::new(0, 0, 0) + } +} -/// Height of each ladder shaft segment (from vanilla igloo/middle.nbt). -const SHAFT_HEIGHT: i32 = 3; +#[must_use] +pub const fn get_offset(location: &str) -> Vector3 { + if const_str_eq(location, STRUCTURE_LOCATION_IGLOO) { + Vector3::new(0, 0, 0) + } else if const_str_eq(location, STRUCTURE_LOCATION_LADDER) { + Vector3::new(2, -3, 4) + } else if const_str_eq(location, STRUCTURE_LOCATION_LABORATORY) { + Vector3::new(0, -3, -2) + } else { + Vector3::new(0, 0, 0) + } +} -/// Basement room dimensions (from vanilla igloo/bottom.nbt). -const BASEMENT_HEIGHT: i32 = 6; -const BASEMENT_DEPTH: i32 = 9; +const fn const_str_eq(a: &str, b: &str) -> bool { + let a = a.as_bytes(); + let b = b.as_bytes(); + if a.len() != b.len() { + return false; + } + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + i += 1; + } + true +} -/// Vanilla pivot offset for igloo/top template alignment. -const PIVOT_OFFSET_X: i32 = 3; -const PIVOT_OFFSET_Z: i32 = 5; +pub fn add_pieces( + collector: &mut StructurePiecesCollector, + position: Vector3, + rotation: Rotation, + random: &mut RandomGenerator, +) { + if random.next_f64() < 0.5 { + let depth = random.next_bounded_i32(8) + 4; + if let Some(template) = get_template(STRUCTURE_LOCATION_LABORATORY) { + collector.add_piece(Box::new(IglooPiece::new( + template, + STRUCTURE_LOCATION_LABORATORY.to_string(), + position, + rotation, + depth * 3, + ))); + } -/// Offset from dome to shaft entrance (vanilla: `OFFSETS_FROM_TOP` for middle). -const SHAFT_OFFSET_X: i32 = 2; -const SHAFT_OFFSET_Z: i32 = 4; + for i in 0..(depth - 1) { + if let Some(template) = get_template(STRUCTURE_LOCATION_LADDER) { + collector.add_piece(Box::new(IglooPiece::new( + template, + STRUCTURE_LOCATION_LADDER.to_string(), + position, + rotation, + i * 3, + ))); + } + } + } -/// Offset from dome to basement (vanilla: `OFFSETS_FROM_TOP` for bottom). -const BASEMENT_OFFSET_X: i32 = 0; -const BASEMENT_OFFSET_Z: i32 = -2; + if let Some(template) = get_template(STRUCTURE_LOCATION_IGLOO) { + collector.add_piece(Box::new(IglooPiece::new( + template, + STRUCTURE_LOCATION_IGLOO.to_string(), + position, + rotation, + 0, + ))); + } +} -/// Generator for igloo structures in snowy biomes. -#[derive(Deserialize)] pub struct IglooGenerator; impl StructureGenerator for IglooGenerator { @@ -66,204 +120,263 @@ impl StructureGenerator for IglooGenerator { &self, mut context: StructureGeneratorContext<'_>, ) -> Option { - let chunk_center_x = get_center_x(context.chunk_x); - let chunk_center_z = get_center_z(context.chunk_z); - - // Apply pivot offset - structure origin is offset from chunk center - let x = chunk_center_x - PIVOT_OFFSET_X; - let z = chunk_center_z - PIVOT_OFFSET_Z; - - // IMPORTANT: Random call order must match vanilla for deterministic placement: - // 1. Rotation first (vanilla: BlockRotation.random(random) calls nextInt(4)) - let rotation_index = context.random.next_bounded_i32(4) as u8; - let rotation = Rotation::from_index(rotation_index); - - // 2. Basement check (vanilla: random.nextDouble() < 0.5) - let has_basement = context.random.next_f64() < 0.5; - - // 3. Ladder segments: 4-11 if has basement (vanilla: random.nextInt(8) + 4) - let ladder_segments = if has_basement { - context.random.next_bounded_i32(8) as u8 + 4 - } else { - 0 - }; + let block_pos = Vector3::new( + start_block_x(context.chunk_x), + GENERATION_HEIGHT, + start_block_z(context.chunk_z), + ); + let rotation_idx = context.random.next_bounded_i32(4) as u8; + let rotation = Rotation::from_index(rotation_idx); let mut collector = StructurePiecesCollector::default(); - - // Load templates from cache - let top_template = get_template("igloo/top")?; - - let piece = IglooPiece { - shiftable_structure_piece: ShiftableStructurePiece::new( - StructurePieceType::Igloo, - x, - 64, - z, - DOME_WIDTH, - DOME_HEIGHT, - DOME_DEPTH, - rotation.to_axis(), - ), - top_template, - middle_template: if has_basement { - get_template("igloo/middle") - } else { - None - }, - bottom_template: if has_basement { - get_template("igloo/bottom") - } else { - None - }, - rotation, - has_basement, - ladder_segments, - }; - - collector.add_piece(Box::new(piece)); + add_pieces(&mut collector, block_pos, rotation, &mut context.random); Some(StructurePosition { - start_pos: BlockPos::new(chunk_center_x, 64, chunk_center_z), + start_pos: BlockPos::new(block_pos.x, block_pos.y, block_pos.z), collector: Arc::new(collector.into()), }) } } -/// Single igloo structure piece containing dome and optional basement. pub struct IglooPiece { - shiftable_structure_piece: ShiftableStructurePiece, - top_template: Arc, - middle_template: Option>, - bottom_template: Option>, - rotation: Rotation, - has_basement: bool, - ladder_segments: u8, + pub piece: StructurePiece, + pub template: Arc, + pub template_name: String, + pub place_settings: StructurePlaceSettings, + pub template_position: Vector3, +} + +impl IglooPiece { + #[must_use] + pub fn new( + template: Arc, + template_name: String, + position: Vector3, + rotation: Rotation, + depth: i32, + ) -> Self { + let place_settings = make_settings(rotation, &template_name); + let template_position = make_position(&template_name, position, depth); + let bounding_box = template.get_bounding_box(&place_settings, template_position); + + Self { + piece: StructurePiece::new(StructurePieceType::Igloo, bounding_box, 0), + template, + template_name, + place_settings, + template_position, + } + } + + #[expect(clippy::too_many_lines)] + fn place_blocks( + &self, + chunk: &mut ProtoChunk, + chunk_box: &BlockBox, + random: &mut RandomGenerator, + ) { + let rotation = self.place_settings.get_rotation(); + let mirror = self.place_settings.get_mirror(); + let pivot = self.place_settings.get_rotation_pivot(); + + let mut context_rng = LegacyRand::from_seed(hash_block_pos( + self.template_position.x, + self.template_position.y, + self.template_position.z, + ) as u64); + let mut context = ProcessorContext::new( + self.template_position, + self.place_settings.get_processors(), + &mut context_rng, + ); + + for block in &self.template.blocks { + let palette_entry = &self.template.palette[block.state as usize]; + + let local_pos = + StructureTemplate::transform_block_pos(block.pos, mirror, rotation, pivot); + let world_pos = self.template_position + local_pos; + + if !chunk_box.contains_pos(&world_pos) { + continue; + } + + if palette_entry.name == "minecraft:structure_block" + && let Some(nbt) = &block.nbt + { + let mode = nbt.get_string("mode").unwrap_or(""); + let metadata = nbt.get_string("metadata").unwrap_or(""); + if mode == "DATA" && metadata == "chest" { + chunk.set_block_state( + world_pos.x, + world_pos.y, + world_pos.z, + Block::AIR.default_state, + ); + let below_pos = world_pos - Vector3::new(0, 1, 0); + let mut chest_nbt = pumpkin_nbt::compound::NbtCompound::new(); + chest_nbt.put_string("id", "minecraft:chest".to_string()); + chest_nbt.put_int("x", below_pos.x); + chest_nbt.put_int("y", below_pos.y); + chest_nbt.put_int("z", below_pos.z); + chest_nbt.put_string("LootTable", "minecraft:chests/igloo_chest".to_string()); + chest_nbt.put_long("LootTableSeed", random.next_i64()); + chunk.add_block_entity(chest_nbt); + } + continue; + } + + let mut block_entity_nbt = block.nbt.clone(); + let placed_entry = palette_entry.clone(); + + let Some(state) = BlockStateResolver::resolve(&placed_entry, rotation, mirror) else { + continue; + }; + + let mut processed_state = Some(state); + let mut capped_idx = 0; + for processor in self.place_settings.get_processors() { + let Some(current_state) = processed_state else { + break; + }; + processed_state = processor.process_with_context( + chunk, + world_pos, + current_state, + &mut block_entity_nbt, + &mut context, + &mut capped_idx, + &mut context_rng, + ); + } + + let Some(final_state) = processed_state else { + continue; + }; + + chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, final_state); + + let final_block = Block::from_id(final_state.id.to_block_id()); + let block_entity_id = + crate::generation::structure::template::get_block_entity_id(final_block.name); + if block_entity_nbt.is_some() || block_entity_id.is_some() { + let fallback_id = block_entity_id.unwrap_or(final_block.name); + let mut placed_nbt = pumpkin_nbt::compound::NbtCompound::new(); + + placed_nbt.put_string("id", fallback_id.to_string()); + placed_nbt.put_int("x", world_pos.x); + placed_nbt.put_int("y", world_pos.y); + placed_nbt.put_int("z", world_pos.z); + + if let Some(template_nbt) = &block_entity_nbt { + for (key, value) in &template_nbt.child_tags { + if key.as_ref() != "x" + && key.as_ref() != "y" + && key.as_ref() != "z" + && key.as_ref() != "id" + { + placed_nbt.child_tags.insert(key.clone(), value.clone()); + } + } + } + + if placed_nbt.get_string("LootTable").is_some() + && placed_nbt.get_long("LootTableSeed").is_none() + { + let mut rng = + LegacyRand::from_seed( + hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64, + ); + placed_nbt.put_long("LootTableSeed", rng.next_i64()); + } + + chunk.add_block_entity(placed_nbt); + } + } + + crate::generation::structure::template::place_template_entities( + chunk, + &self.template, + self.template_position, + rotation, + chunk_box, + ); + } } impl StructurePieceBase for IglooPiece { fn as_any(&self) -> &dyn std::any::Any { self } + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece + } + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece + } fn place( &mut self, chunk: &mut ProtoChunk, _block_registry: &dyn WorldPortalExt, - _random: &mut RandomGenerator, + random: &mut RandomGenerator, _seed: i64, chunk_box: &BlockBox, ) { - let origin = self.shiftable_structure_piece.piece.bounding_box.min; - - // Vanilla samples height at the entrance position (3, 0, 5 in template space) - let (entrance_x, entrance_z) = self.rotation.rotate_offset(3, 5); - let surface_y = chunk.get_top_y( + let settings = make_settings(self.place_settings.get_rotation(), &self.template_name); + let offset = get_offset(&self.template_name); + let entrance_rel = StructureTemplate::calculate_relative_position( + &settings, + Vector3::new(3 - offset.x, 0, -offset.z), + ); + let entrance_pos = self.template_position + entrance_rel; + let height = chunk.get_top_y( &pumpkin_util::HeightMap::WorldSurfaceWg, - origin.x + entrance_x, - origin.z + entrance_z, + entrance_pos.x, + entrance_pos.z, ); - // Place floor at surface - 1 (so the floor block is at ground level) - let offset = surface_y - 1 - origin.y; - self.shiftable_structure_piece.piece.bounding_box.min.y += offset; - self.shiftable_structure_piece.piece.bounding_box.max.y += offset; + let old_template_pos = self.template_position; + self.template_position.y += height - GENERATION_HEIGHT - 1; + self.piece.bounding_box = self + .template + .get_bounding_box(&self.place_settings, self.template_position); - let origin = self.shiftable_structure_piece.piece.bounding_box.min; - let dome_floor_y = origin.y; + self.place_blocks(chunk, chunk_box, random); - // Place dome at origin (no offset) - place_template( - chunk, - &self.top_template, - origin, - (0, 0), - self.rotation, - false, - false, - &[], - Some(chunk_box), - ); - crate::generation::structure::template::place_template_entities( - chunk, - &self.top_template, - origin, - self.rotation, - chunk_box, - ); - - // Place basement components if present - if self.has_basement { - // Extend bounding box to include basement area - let basement_depth = (self.ladder_segments as i32 * SHAFT_HEIGHT) + BASEMENT_HEIGHT + 1; - self.shiftable_structure_piece.piece.bounding_box.min.y -= basement_depth; - - let (_, bz) = self - .rotation - .rotate_offset(BASEMENT_OFFSET_X, BASEMENT_OFFSET_Z); - if bz < 0 { - self.shiftable_structure_piece.piece.bounding_box.min.z += bz; - } else { - self.shiftable_structure_piece.piece.bounding_box.max.z += BASEMENT_DEPTH; - } - - // Place ladder shaft segments - if let Some(middle) = &self.middle_template { - for segment in 0..self.ladder_segments { - let segment_y = dome_floor_y - 1 - (segment as i32 * SHAFT_HEIGHT); - let shaft_origin = Vector3::new(origin.x, segment_y, origin.z); - place_template( - chunk, - middle, - shaft_origin, - (SHAFT_OFFSET_X, SHAFT_OFFSET_Z), - self.rotation, - false, - false, - &[], - Some(chunk_box), - ); - } - } - - // Place basement - if let Some(bottom) = &self.bottom_template { - let total_shaft_depth = self.ladder_segments as i32 * SHAFT_HEIGHT; - let basement_y = dome_floor_y - total_shaft_depth - BASEMENT_HEIGHT + 1; - let basement_origin = Vector3::new(origin.x, basement_y, origin.z); - place_template( - chunk, - bottom, - basement_origin, - (BASEMENT_OFFSET_X, BASEMENT_OFFSET_Z), - self.rotation, - false, - false, - &[], - Some(chunk_box), - ); - let (rotated_ox, rotated_oz) = self - .rotation - .rotate_offset(BASEMENT_OFFSET_X, BASEMENT_OFFSET_Z); - let placement_origin = Vector3::new( - basement_origin.x + rotated_ox, - basement_origin.y, - basement_origin.z + rotated_oz, - ); - crate::generation::structure::template::place_template_entities( - chunk, - bottom, - placement_origin, - self.rotation, - chunk_box, + if self.template_name == STRUCTURE_LOCATION_IGLOO { + let trap_door_rel = + StructureTemplate::calculate_relative_position(&settings, Vector3::new(3, 0, 5)); + let trap_door_pos = self.template_position + trap_door_rel; + let below_pos = trap_door_pos - Vector3::new(0, 1, 0); + let below_state = BlockState::from_id(chunk.get_block_state(&below_pos)); + if !below_state.is_air() + && below_state.id.to_block_id() != Block::LADDER.id + && chunk_box.contains_pos(&trap_door_pos) + { + chunk.set_block_state( + trap_door_pos.x, + trap_door_pos.y, + trap_door_pos.z, + Block::SNOW_BLOCK.default_state, ); } } - } - fn get_structure_piece(&self) -> &StructurePiece { - &self.shiftable_structure_piece.piece - } - - fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { - &mut self.shiftable_structure_piece.piece + self.template_position = old_template_pos; } } + +fn make_settings(rotation: Rotation, template_location: &str) -> StructurePlaceSettings { + StructurePlaceSettings::new() + .set_rotation(rotation) + .set_mirror(Mirror::None) + .set_rotation_pivot(get_pivot(template_location)) + .add_processor(StructureProcessor::BlockIgnore(vec![IgnoredBlock { + block_id: Block::STRUCTURE_BLOCK.id, + properties: None, + }])) +} + +fn make_position(template_location: &str, position: Vector3, depth: i32) -> Vector3 { + position + get_offset(template_location) - Vector3::new(0, depth, 0) +} diff --git a/crates/pumpkin-world/src/generation/structure/structures/nether_fossil.rs b/crates/pumpkin-world/src/generation/structure/structures/nether_fossil.rs index 9cb389e05..73047f6d6 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/nether_fossil.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/nether_fossil.rs @@ -1,9 +1,11 @@ use std::sync::Arc; -use pumpkin_data::{Block, BlockDirection, BlockState}; +use pumpkin_data::{Block, BlockDirection, BlockState, Mirror, Rotation}; use pumpkin_util::{ math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, - random::{RandomDeriverImpl, RandomGenerator, RandomImpl}, + random::{ + RandomDeriverImpl, RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand, + }, }; use crate::{ @@ -12,18 +14,19 @@ use crate::{ positions::chunk_pos::{start_block_x, start_block_z}, structure::{ piece::StructurePieceType, - shiftable_piece::ShiftableStructurePiece, structures::{ StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, - StructurePiecesCollector, StructurePosition, + StructurePiecesCollector, StructurePosition, WorldPortalExt, + }, + template::{ + BlockStateResolver, StructurePlaceSettings, StructureTemplate, get_template, + processor::{IgnoredBlock, ProcessorContext, StructureProcessor}, }, - template::{BlockRotation, StructureTemplate, get_template, place_template}, }, }, - world::WorldPortalExt, }; -const TEMPLATE_NAMES: [&str; 14] = [ +pub const FOSSILS: [&str; 14] = [ "nether_fossils/fossil_1", "nether_fossils/fossil_2", "nether_fossils/fossil_3", @@ -58,7 +61,7 @@ impl StructureGenerator for NetherFossilGenerator { // 2. nextInt(16) for Z offset within chunk // 3. height.get(random) for initial Y (uniform 32..254) // 4. Column scan (no random calls) - // 5. BlockRotation.random(random) - nextInt(4) + // 5. Rotation.getRandom(random) - nextInt(4) // 6. Util.getRandom(FOSSILS, random) - nextInt(14) let x = start_block_x(context.chunk_x) + context.random.next_bounded_i32(16); @@ -75,36 +78,25 @@ impl StructureGenerator for NetherFossilGenerator { HEIGHT_MIN + context.random.next_bounded_i32(height_range) }; - // Column scan is deferred to place() since we don't have block data here. - // Consume random in vanilla order for determinism. let rotation_index = context.random.next_bounded_i32(4) as u8; - let rotation = BlockRotation::from_index(rotation_index); + let rotation = Rotation::from_index(rotation_index); - let template_index = context.random.next_bounded_i32(14) as usize; - let template_name = TEMPLATE_NAMES[template_index]; + let template_index = context.random.next_bounded_i32(FOSSILS.len() as i32) as usize; + let template_name = FOSSILS[template_index]; let template = get_template(template_name)?; - - let rotated_size = rotation.transform_size(template.size); + let position = Vector3::new(x, initial_y, z); let mut collector = StructurePiecesCollector::default(); - let piece = NetherFossilPiece { - shiftable_structure_piece: ShiftableStructurePiece::new( - StructurePieceType::NetherFossil, - x, - initial_y, - z, - rotated_size.x, - rotated_size.y, - rotated_size.z, - rotation.to_axis(), - ), + let piece = NetherFossilPiece::new( template, + template_name.to_string(), + position, rotation, initial_y, - sea_level: context.sea_level, - }; + context.sea_level, + ); collector.add_piece(Box::new(piece)); @@ -115,35 +107,48 @@ impl StructureGenerator for NetherFossilGenerator { } } -struct NetherFossilPiece { - shiftable_structure_piece: ShiftableStructurePiece, - template: Arc, - rotation: BlockRotation, - initial_y: i32, - sea_level: i32, +pub struct NetherFossilPiece { + pub piece: StructurePiece, + pub template: Arc, + pub template_name: String, + pub place_settings: StructurePlaceSettings, + pub template_position: Vector3, + pub initial_y: i32, + pub sea_level: i32, } impl NetherFossilPiece { + #[must_use] + pub fn new( + template: Arc, + template_name: String, + template_position: Vector3, + rotation: Rotation, + initial_y: i32, + sea_level: i32, + ) -> Self { + let place_settings = make_settings(rotation); + let bounding_box = template.get_bounding_box(&place_settings, template_position); + + Self { + piece: StructurePiece::new(StructurePieceType::NetherFossil, bounding_box, 0), + template, + template_name, + place_settings, + template_position, + initial_y, + sea_level, + } + } + /// Vanilla column scan: search downward from `initial_y` for air above (soul sand OR solid block). /// Returns the Y of the support block, or None if no valid position found above sea level. - /// - /// Mirrors vanilla's pre-decrement loop: - /// ```java - /// while (l > k) { - /// BlockState lv5 = lv3.getState(l); - /// BlockState lv6 = lv3.getState(--l); // pre-decrement - /// if (lv5.isAir() && (lv6.isOf(SOUL_SAND) || lv6.isSideSolidFullSquare(...))) break; - /// } - /// if (l <= k) return empty; - /// ``` - /// After the loop, l is the support block Y. Vanilla rejects if l <= `sea_level`. fn find_placement_y(&self, chunk: &ProtoChunk) -> Option { - let origin = self.shiftable_structure_piece.piece.bounding_box.min; + let origin = self.template_position; let mut y = self.initial_y; while y > self.sea_level { let upper = chunk.get_block_state(&Vector3::new(origin.x, y, origin.z)); - // Pre-decrement: y now points to the lower block (vanilla's --l) y -= 1; let lower = chunk.get_block_state(&Vector3::new(origin.x, y, origin.z)); @@ -158,106 +163,151 @@ impl NetherFossilPiece { } } - // Vanilla: if (l <= k) return empty if y <= self.sea_level { None } else { Some(y) } } + + fn place_blocks(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) { + let rotation = self.place_settings.get_rotation(); + let mirror = self.place_settings.get_mirror(); + let pivot = self.place_settings.get_rotation_pivot(); + + let mut context_rng = LegacyRand::from_seed(hash_block_pos( + self.template_position.x, + self.template_position.y, + self.template_position.z, + ) as u64); + let mut context = ProcessorContext::new( + self.template_position, + self.place_settings.get_processors(), + &mut context_rng, + ); + + for block in &self.template.blocks { + let palette_entry = &self.template.palette[block.state as usize]; + + let mut block_entity_nbt = block.nbt.clone(); + let placed_entry = palette_entry.clone(); + + let Some(state) = BlockStateResolver::resolve(&placed_entry, rotation, mirror) else { + continue; + }; + + let local_pos = + StructureTemplate::transform_block_pos(block.pos, mirror, rotation, pivot); + let world_pos = self.template_position + local_pos; + + if !chunk_box.contains_pos(&world_pos) { + continue; + } + + let mut processed_state = Some(state); + let mut capped_idx = 0; + for processor in self.place_settings.get_processors() { + let Some(current_state) = processed_state else { + break; + }; + processed_state = processor.process_with_context( + chunk, + world_pos, + current_state, + &mut block_entity_nbt, + &mut context, + &mut capped_idx, + &mut context_rng, + ); + } + + let Some(final_state) = processed_state else { + continue; + }; + + chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, final_state); + } + } + + fn place_dried_ghast( + chunk: &mut ProtoChunk, + seed: i64, + fossil_bb: &BlockBox, + chunk_bb: &BlockBox, + ) { + use pumpkin_util::random::xoroshiro128::Xoroshiro; + + let center_x = i32::midpoint(fossil_bb.min.x, fossil_bb.max.x); + let center_y = i32::midpoint(fossil_bb.min.y, fossil_bb.max.y); + let center_z = i32::midpoint(fossil_bb.min.z, fossil_bb.max.z); + + let mut rng = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64)); + let splitter = rng.next_splitter(); + let mut positional_random = splitter.split_pos(center_x, center_y, center_z); + + if positional_random.next_f32() < 0.5 { + let x_span = (fossil_bb.max.x - fossil_bb.min.x + 1).max(1); + let z_span = (fossil_bb.max.z - fossil_bb.min.z + 1).max(1); + let x = fossil_bb.min.x + positional_random.next_bounded_i32(x_span); + let y = fossil_bb.min.y; + let z = fossil_bb.min.z + positional_random.next_bounded_i32(z_span); + let random_pos = Vector3::new(x, y, z); + + let block_at = chunk.get_block_state(&random_pos); + if BlockState::from_id(block_at).is_air() && chunk_bb.contains_pos(&random_pos) { + let rot_idx = positional_random.next_bounded_i32(4) as u8; + let rot = Rotation::from_index(rot_idx); + let state = Block::DRIED_GHAST.default_state.rotate(rot); + chunk.set_block_state(random_pos.x, random_pos.y, random_pos.z, state); + } + } + } } impl StructurePieceBase for NetherFossilPiece { fn as_any(&self) -> &dyn std::any::Any { self } + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece + } + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece + } fn place( &mut self, chunk: &mut ProtoChunk, - block_registry: &dyn WorldPortalExt, + _block_registry: &dyn WorldPortalExt, _random: &mut RandomGenerator, seed: i64, chunk_box: &BlockBox, ) { - // Vanilla column scan: find air above soul sand or solid block let Some(placement_y) = self.find_placement_y(chunk) else { return; }; - // Adjust bounding box to placement Y - let current_y = self.shiftable_structure_piece.piece.bounding_box.min.y; - let offset = placement_y - current_y; - self.shiftable_structure_piece.piece.bounding_box.min.y += offset; - self.shiftable_structure_piece.piece.bounding_box.max.y += offset; + self.template_position.y = placement_y; + self.piece.bounding_box = self + .template + .get_bounding_box(&self.place_settings, self.template_position); - let origin = self.shiftable_structure_piece.piece.bounding_box.min; + let fossil_bb = self.piece.bounding_box; + let mut enlarged_box = *chunk_box; + enlarged_box.encompass(&fossil_bb); - // Vanilla uses IGNORE_AIR_AND_STRUCTURE_BLOCKS processor - place_template( - chunk, - &self.template, - origin, - (0, 0), - self.rotation, - true, - false, - &[], - Some(chunk_box), - ); - - // Vanilla: 50% chance to place a dried ghast block at the fossil base. - // Uses a deterministic random seeded from world seed + bounding box center. - self.try_place_dried_ghast(chunk, block_registry, seed); - } - - fn get_structure_piece(&self) -> &StructurePiece { - &self.shiftable_structure_piece.piece - } - - fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { - &mut self.shiftable_structure_piece.piece + self.place_blocks(chunk, &enlarged_box); + Self::place_dried_ghast(chunk, seed, &fossil_bb, chunk_box); } } -impl NetherFossilPiece { - fn try_place_dried_ghast( - &self, - chunk: &mut ProtoChunk, - _block_registry: &dyn WorldPortalExt, - seed: i64, - ) { - use pumpkin_util::random::xoroshiro128::Xoroshiro; - - let bbox = self.shiftable_structure_piece.piece.bounding_box; - let center_x = i32::midpoint(bbox.min.x, bbox.max.x); - let center_y = i32::midpoint(bbox.min.y, bbox.max.y); - let center_z = i32::midpoint(bbox.min.z, bbox.max.z); - - // Vanilla: Random.create(world.getSeed()).nextSplitter().split(box.getCenter()) - let mut rng = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64)); - let splitter = rng.next_splitter(); - let mut rng = splitter.split_pos(center_x, center_y, center_z); - - if rng.next_f32() >= 0.5 { - return; - } - - let block_count_x = (bbox.max.x - bbox.min.x + 1).max(1); - let block_count_z = (bbox.max.z - bbox.min.z + 1).max(1); - let x = bbox.min.x + rng.next_bounded_i32(block_count_x); - let y = bbox.min.y; - let z = bbox.min.z + rng.next_bounded_i32(block_count_z); - - // Vanilla: chunkBox.contains(pos) - only place if within current chunk - let chunk_x = start_block_x(chunk.x); - let chunk_z = start_block_z(chunk.z); - if x < chunk_x || x > chunk_x + 15 || z < chunk_z || z > chunk_z + 15 { - return; - } - - let block_at = chunk.get_block_state(&Vector3::new(x, y, z)); - if !BlockState::from_id(block_at).is_air() { - return; - } - - // Place dried ghast with random rotation (vanilla: Blocks.DRIED_GHAST.getDefaultState().rotate()) - // Dried ghast default state is sufficient since rotation is cosmetic - chunk.set_block_state(x, y, z, Block::DRIED_GHAST.default_state); - } +fn make_settings(rotation: Rotation) -> StructurePlaceSettings { + StructurePlaceSettings::new() + .set_rotation(rotation) + .set_mirror(Mirror::None) + .add_processor(StructureProcessor::BlockIgnore(vec![ + IgnoredBlock { + block_id: Block::STRUCTURE_BLOCK.id, + properties: None, + }, + IgnoredBlock { + block_id: Block::AIR.id, + properties: None, + }, + ])) } diff --git a/crates/pumpkin-world/src/generation/structure/structures/ruined_portal.rs b/crates/pumpkin-world/src/generation/structure/structures/ruined_portal.rs index 5f0f5ce5e..404a63b21 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/ruined_portal.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/ruined_portal.rs @@ -1,9 +1,10 @@ use std::sync::Arc; -use pumpkin_data::block_rotation::Rotation; +use pumpkin_data::{Block, Mirror, Rotation, structures::StructureKeys}; use pumpkin_util::{ - math::{block_box::BlockBox, position::BlockPos}, - random::{RandomGenerator, RandomImpl}, + HeightMap, + math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand}, }; use crate::{ @@ -16,7 +17,14 @@ use crate::{ StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, StructurePiecesCollector, StructurePosition, WorldPortalExt, }, - template::{StructureTemplate, get_template, place_template}, + template::{ + BlockStateResolver, PaletteEntry, StructurePlaceSettings, StructureTemplate, + get_template, + processor::{ + IgnoredBlock, PosRuleTest, ProcessorContext, ProcessorRule, RuleTest, + StructureProcessor, + }, + }, }, }, }; @@ -37,8 +45,127 @@ const PORTALS: &[&str] = &[ "ruined_portal/giant_portal_3", ]; +const NETHERRACK_PROBABILITY_BY_DISTANCE: &[f32] = &[ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9, 0.9, 0.8, 0.7, 0.6, 0.4, 0.2, +]; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum VerticalPlacement { + OnLandSurface, + PartlyBuried, + OnOceanFloor, + InMountain, + Underground, + InNether, +} + +impl VerticalPlacement { + #[must_use] + pub const fn get_heightmap_type(self) -> HeightMap { + match self { + Self::OnOceanFloor => HeightMap::OceanFloorWg, + _ => HeightMap::WorldSurfaceWg, + } + } +} + +#[derive(Clone, Debug)] +pub struct RuinedPortalProperties { + pub cold: bool, + pub mossiness: f32, + pub air_pocket: bool, + pub overgrown: bool, + pub vines: bool, + pub replace_with_blackstone: bool, +} + +impl RuinedPortalProperties { + #[must_use] + pub const fn for_variant(variant: StructureKeys) -> (VerticalPlacement, Self) { + match variant { + StructureKeys::RuinedPortalDesert => ( + VerticalPlacement::PartlyBuried, + Self { + cold: false, + mossiness: 0.0, + air_pocket: false, + overgrown: false, + vines: false, + replace_with_blackstone: false, + }, + ), + StructureKeys::RuinedPortalJungle => ( + VerticalPlacement::OnLandSurface, + Self { + cold: false, + mossiness: 0.8, + air_pocket: false, + overgrown: true, + vines: true, + replace_with_blackstone: false, + }, + ), + StructureKeys::RuinedPortalSwamp => ( + VerticalPlacement::OnLandSurface, + Self { + cold: false, + mossiness: 0.5, + air_pocket: false, + overgrown: false, + vines: true, + replace_with_blackstone: false, + }, + ), + StructureKeys::RuinedPortalMountain => ( + VerticalPlacement::InMountain, + Self { + cold: true, + mossiness: 0.2, + air_pocket: true, + overgrown: false, + vines: false, + replace_with_blackstone: false, + }, + ), + StructureKeys::RuinedPortalOcean => ( + VerticalPlacement::OnOceanFloor, + Self { + cold: false, + mossiness: 0.8, + air_pocket: false, + overgrown: false, + vines: false, + replace_with_blackstone: false, + }, + ), + StructureKeys::RuinedPortalNether => ( + VerticalPlacement::InNether, + Self { + cold: false, + mossiness: 0.0, + air_pocket: false, + overgrown: false, + vines: false, + replace_with_blackstone: true, + }, + ), + _ => ( + VerticalPlacement::OnLandSurface, + Self { + cold: false, + mossiness: 0.2, + air_pocket: false, + overgrown: false, + vines: false, + replace_with_blackstone: false, + }, + ), + } + } +} + pub struct RuinedPortalGenerator { - pub variant: pumpkin_data::structures::StructureKeys, + pub variant: StructureKeys, } impl StructureGenerator for RuinedPortalGenerator { @@ -51,41 +178,399 @@ impl StructureGenerator for RuinedPortalGenerator { let rotation_idx = context.random.next_bounded_i32(4) as u8; let rotation = Rotation::from_index(rotation_idx); + let mirror = if context.random.next_f32() < 0.5 { + Mirror::None + } else { + Mirror::FrontBack + }; + + let (vertical_placement, properties) = RuinedPortalProperties::for_variant(self.variant); let template_idx = context.random.next_bounded_i32(PORTALS.len() as i32) as usize; let template_name = PORTALS[template_idx]; let template = get_template(template_name)?; - let size = template.size; - let bounding_box = BlockBox::new( - chunk_center_x - size.x / 2, - context.min_y, - chunk_center_z - size.z / 2, - chunk_center_x + size.x / 2, - 256, - chunk_center_z + size.z / 2, + let pivot = Vector3::new(template.size.x / 2, 0, template.size.z / 2); + + let y = match vertical_placement { + VerticalPlacement::OnOceanFloor => context + .height_sampler + .as_deref_mut() + .map_or(context.sea_level, |s| { + s.estimate_ocean_floor_height(chunk_center_x, chunk_center_z) + }), + VerticalPlacement::InNether => context.random.next_bounded_i32(45) + 45, + VerticalPlacement::PartlyBuried => { + let surface_y = context + .height_sampler + .as_deref_mut() + .map_or(64, |s| s.estimate_height(chunk_center_x, chunk_center_z)); + surface_y - 2 - context.random.next_bounded_i32(3) + } + _ => { + let surface_y = context + .height_sampler + .as_deref_mut() + .map_or(64, |s| s.estimate_height(chunk_center_x, chunk_center_z)); + surface_y - 1 + } + }; + + let template_position = Vector3::new(chunk_center_x - pivot.x, y, chunk_center_z - pivot.z); + + let piece = RuinedPortalPiece::new( + template, + template_name.to_string(), + template_position, + vertical_placement, + properties, + rotation, + mirror, + pivot, ); let mut collector = StructurePiecesCollector::default(); - collector.add_piece(Box::new(RuinedPortalPiece { - piece: StructurePiece::new(StructurePieceType::RuinedPortal, bounding_box, 0), - template, - rotation, - variant: self.variant, - })); + collector.add_piece(Box::new(piece)); Some(StructurePosition { - start_pos: BlockPos::new(chunk_center_x, 64, chunk_center_z), + start_pos: BlockPos::new(chunk_center_x, y, chunk_center_z), collector: Arc::new(collector.into()), }) } } pub struct RuinedPortalPiece { - piece: StructurePiece, - template: Arc, - rotation: Rotation, - variant: pumpkin_data::structures::StructureKeys, + pub piece: StructurePiece, + pub template: Arc, + pub template_name: String, + pub place_settings: StructurePlaceSettings, + pub template_position: Vector3, + pub vertical_placement: VerticalPlacement, + pub properties: RuinedPortalProperties, +} + +impl RuinedPortalPiece { + #[must_use] + #[allow(clippy::too_many_arguments)] + pub fn new( + template: Arc, + template_name: String, + template_position: Vector3, + vertical_placement: VerticalPlacement, + properties: RuinedPortalProperties, + rotation: Rotation, + mirror: Mirror, + pivot: Vector3, + ) -> Self { + let place_settings = + make_settings(mirror, rotation, vertical_placement, pivot, &properties); + let bounding_box = template.get_bounding_box(&place_settings, template_position); + + Self { + piece: StructurePiece::new(StructurePieceType::RuinedPortal, bounding_box, 0), + template, + template_name, + place_settings, + template_position, + vertical_placement, + properties, + } + } + + fn place_blocks(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) { + let rotation = self.place_settings.get_rotation(); + let mirror = self.place_settings.get_mirror(); + let pivot = self.place_settings.get_rotation_pivot(); + + let mut context_rng = LegacyRand::from_seed(hash_block_pos( + self.template_position.x, + self.template_position.y, + self.template_position.z, + ) as u64); + let mut context = ProcessorContext::new( + self.template_position, + self.place_settings.get_processors(), + &mut context_rng, + ); + + for block in &self.template.blocks { + let palette_entry = &self.template.palette[block.state as usize]; + + let mut block_entity_nbt = block.nbt.clone(); + let placed_entry = palette_entry.clone(); + + let Some(mut state) = BlockStateResolver::resolve(&placed_entry, rotation, mirror) + else { + continue; + }; + + let local_pos = + StructureTemplate::transform_block_pos(block.pos, mirror, rotation, pivot); + let world_pos = self.template_position + local_pos; + + if !chunk_box.contains_pos(&world_pos) { + continue; + } + + let mut processed_state = Some(state); + let mut capped_idx = 0; + for processor in self.place_settings.get_processors() { + let Some(current_state) = processed_state else { + break; + }; + processed_state = processor.process_with_context( + chunk, + world_pos, + current_state, + &mut block_entity_nbt, + &mut context, + &mut capped_idx, + &mut context_rng, + ); + } + + let Some(final_state) = processed_state else { + continue; + }; + + if chunk.get_block_state(&world_pos).to_block_id() == Block::WATER.id + && let Some((_, waterlogged)) = placed_entry + .properties + .iter() + .find(|(name, _)| name == "waterlogged") + && waterlogged == "true" + { + if let Some(waterlogged_state) = + BlockStateResolver::resolve(&placed_entry, rotation, mirror) + { + state = waterlogged_state; + } else { + state = final_state; + } + } else { + state = final_state; + } + + chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state); + + let final_block = Block::from_id(state.id.to_block_id()); + let block_entity_id = + crate::generation::structure::template::get_block_entity_id(final_block.name); + if block_entity_nbt.is_some() || block_entity_id.is_some() { + let fallback_id = block_entity_id.unwrap_or(final_block.name); + let mut placed_nbt = pumpkin_nbt::compound::NbtCompound::new(); + + placed_nbt.put_string("id", fallback_id.to_string()); + placed_nbt.put_int("x", world_pos.x); + placed_nbt.put_int("y", world_pos.y); + placed_nbt.put_int("z", world_pos.z); + + if let Some(template_nbt) = &block_entity_nbt { + for (key, value) in &template_nbt.child_tags { + if key.as_ref() != "x" + && key.as_ref() != "y" + && key.as_ref() != "z" + && key.as_ref() != "id" + { + placed_nbt.child_tags.insert(key.clone(), value.clone()); + } + } + } + + if placed_nbt.get_string("LootTable").is_some() + && placed_nbt.get_long("LootTableSeed").is_none() + { + let mut rng = + LegacyRand::from_seed( + hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64, + ); + placed_nbt.put_long("LootTableSeed", rng.next_i64()); + } + + chunk.add_block_entity(placed_nbt); + } + } + } + + fn spread_netherrack( + &self, + random: &mut RandomGenerator, + chunk: &mut ProtoChunk, + chunk_box: &BlockBox, + ) { + let follow_ground_surface = self.vertical_placement == VerticalPlacement::OnLandSurface + || self.vertical_placement == VerticalPlacement::OnOceanFloor; + let bb = self.piece.bounding_box; + let center_x = i32::midpoint(bb.min.x, bb.max.x); + let center_z = i32::midpoint(bb.min.z, bb.max.z); + + let max_distance = NETHERRACK_PROBABILITY_BY_DISTANCE.len() as i32; + let x_span = bb.max.x - bb.min.x + 1; + let z_span = bb.max.z - bb.min.z + 1; + let average_width = i32::midpoint(x_span, z_span); + let max_adj = (8 - average_width / 2).max(1); + let distance_adjustment = random.next_bounded_i32(max_adj); + + let heightmap = self.vertical_placement.get_heightmap_type(); + + for x in (center_x - max_distance)..=(center_x + max_distance) { + for z in (center_z - max_distance)..=(center_z + max_distance) { + let distance = (x - center_x).abs() + (z - center_z).abs(); + let adjusted_distance = (distance + distance_adjustment).max(0); + if adjusted_distance < max_distance { + let prob = NETHERRACK_PROBABILITY_BY_DISTANCE[adjusted_distance as usize]; + if random.next_f64() < prob as f64 { + let surface_y = chunk.get_top_y(&heightmap, x, z) - 1; + let y = if follow_ground_surface { + surface_y + } else { + bb.min.y.min(surface_y) + }; + let pos = Vector3::new(x, y, z); + if (y - bb.min.y).abs() <= 3 + && chunk_box.contains_pos(&pos) + && self.can_block_be_replaced_by_netherrack_or_magma(chunk, pos) + { + self.place_netherrack_or_magma(random, chunk, pos); + if self.properties.overgrown { + Self::maybe_add_leaves_above(random, chunk, pos); + } + self.add_netherrack_drip_column( + random, + chunk, + pos + Vector3::new(0, -1, 0), + chunk_box, + ); + } + } + } + } + } + } + + fn can_block_be_replaced_by_netherrack_or_magma( + &self, + chunk: &ProtoChunk, + pos: Vector3, + ) -> bool { + let state = chunk.get_block_state(&pos); + let block_id = state.to_block_id(); + block_id != Block::AIR.id + && block_id != Block::OBSIDIAN.id + && block_id != Block::BEDROCK.id + && (self.vertical_placement == VerticalPlacement::InNether + || block_id != Block::LAVA.id) + } + + fn place_netherrack_or_magma( + &self, + random: &mut RandomGenerator, + chunk: &mut ProtoChunk, + pos: Vector3, + ) { + if !self.properties.cold && random.next_f32() < 0.07 { + chunk.set_block_state(pos.x, pos.y, pos.z, Block::MAGMA_BLOCK.default_state); + } else { + chunk.set_block_state(pos.x, pos.y, pos.z, Block::NETHERRACK.default_state); + } + } + + fn add_netherrack_drip_columns_below_portal( + &self, + random: &mut RandomGenerator, + chunk: &mut ProtoChunk, + chunk_box: &BlockBox, + ) { + let bb = self.piece.bounding_box; + for x in (bb.min.x + 1)..bb.max.x { + for z in (bb.min.z + 1)..bb.max.z { + let pos = Vector3::new(x, bb.min.y, z); + if chunk.get_block_state(&pos).to_block_id() == Block::NETHERRACK.id { + self.add_netherrack_drip_column( + random, + chunk, + Vector3::new(x, bb.min.y - 1, z), + chunk_box, + ); + } + } + } + } + + fn add_netherrack_drip_column( + &self, + random: &mut RandomGenerator, + chunk: &mut ProtoChunk, + start_pos: Vector3, + chunk_box: &BlockBox, + ) { + let mut cur = start_pos; + if chunk_box.contains_pos(&cur) { + self.place_netherrack_or_magma(random, chunk, cur); + } + let mut remaining = 8; + while remaining > 0 && random.next_f32() < 0.5 { + cur.y -= 1; + remaining -= 1; + if chunk_box.contains_pos(&cur) { + self.place_netherrack_or_magma(random, chunk, cur); + } + } + } + + fn maybe_add_vines(random: &mut RandomGenerator, chunk: &mut ProtoChunk, pos: Vector3) { + let state = chunk.get_block_state(&pos); + let block_id = state.to_block_id(); + if block_id != Block::AIR.id && block_id != Block::VINE.id { + let dir_idx = random.next_bounded_i32(4); + let (dir_offset, vine_face) = match dir_idx { + 0 => (Vector3::new(0, 0, -1), "south"), + 1 => (Vector3::new(0, 0, 1), "north"), + 2 => (Vector3::new(-1, 0, 0), "east"), + _ => (Vector3::new(1, 0, 0), "west"), + }; + let neighbor_pos = pos + dir_offset; + if chunk.get_block_state(&neighbor_pos).to_block_id() == Block::AIR.id { + let entry = PaletteEntry::with_properties( + "minecraft:vine".to_string(), + vec![(vine_face.to_string(), "true".to_string())], + ); + if let Some(vine_state) = + BlockStateResolver::resolve(&entry, Rotation::None, Mirror::None) + { + chunk.set_block_state( + neighbor_pos.x, + neighbor_pos.y, + neighbor_pos.z, + vine_state, + ); + } + } + } + } + + fn maybe_add_leaves_above( + random: &mut RandomGenerator, + chunk: &mut ProtoChunk, + pos: Vector3, + ) { + if random.next_f32() < 0.5 + && chunk.get_block_state(&pos).to_block_id() == Block::NETHERRACK.id + { + let above = pos + Vector3::new(0, 1, 0); + if chunk.get_block_state(&above).to_block_id() == Block::AIR.id { + let entry = PaletteEntry::with_properties( + "minecraft:jungle_leaves".to_string(), + vec![("persistent".to_string(), "true".to_string())], + ); + if let Some(leaves_state) = + BlockStateResolver::resolve(&entry, Rotation::None, Mirror::None) + { + chunk.set_block_state(above.x, above.y, above.z, leaves_state); + } + } + } + } } impl StructurePieceBase for RuinedPortalPiece { @@ -102,45 +587,148 @@ impl StructurePieceBase for RuinedPortalPiece { &mut self, chunk: &mut ProtoChunk, _block_registry: &dyn WorldPortalExt, - _random: &mut RandomGenerator, + random: &mut RandomGenerator, _seed: i64, chunk_box: &BlockBox, ) { - let origin = self.piece.bounding_box.min; - let (height_map_type, vertical_offset) = match self.variant { - pumpkin_data::structures::StructureKeys::RuinedPortalOcean => { - (pumpkin_util::HeightMap::OceanFloorWg, -2) - } - pumpkin_data::structures::StructureKeys::RuinedPortalDesert => { - (pumpkin_util::HeightMap::WorldSurfaceWg, -3) // partly buried - } - pumpkin_data::structures::StructureKeys::RuinedPortalNether => { - (pumpkin_util::HeightMap::WorldSurfaceWg, 0) - } - _ => (pumpkin_util::HeightMap::WorldSurfaceWg, -1), - }; - - let sample_y = - if self.variant == pumpkin_data::structures::StructureKeys::RuinedPortalNether { - 45 - } else { - chunk.get_top_y(&height_map_type, origin.x, origin.z) - }; - - let target_y = sample_y + vertical_offset; - let mut final_origin = origin; - final_origin.y = target_y; - - place_template( - chunk, - &self.template, - final_origin, - (0, 0), - self.rotation, - true, // skip_air - false, // no waterlogging - &[], - Some(chunk_box), + let bounding_box = self + .template + .get_bounding_box(&self.place_settings, self.template_position); + let center = Vector3::new( + i32::midpoint(bounding_box.min.x, bounding_box.max.x), + i32::midpoint(bounding_box.min.y, bounding_box.max.y), + i32::midpoint(bounding_box.min.z, bounding_box.max.z), ); + + if chunk_box.contains_pos(¢er) { + let mut enlarged_box = *chunk_box; + enlarged_box.encompass(&bounding_box); + + self.place_blocks(chunk, &enlarged_box); + self.spread_netherrack(random, chunk, &enlarged_box); + self.add_netherrack_drip_columns_below_portal(random, chunk, &enlarged_box); + + if self.properties.vines || self.properties.overgrown { + for x in self.piece.bounding_box.min.x..=self.piece.bounding_box.max.x { + for y in self.piece.bounding_box.min.y..=self.piece.bounding_box.max.y { + for z in self.piece.bounding_box.min.z..=self.piece.bounding_box.max.z { + let pos = Vector3::new(x, y, z); + if enlarged_box.contains_pos(&pos) { + if self.properties.vines { + Self::maybe_add_vines(random, chunk, pos); + } + if self.properties.overgrown { + Self::maybe_add_leaves_above(random, chunk, pos); + } + } + } + } + } + } + } } } + +fn make_settings( + mirror: Mirror, + rotation: Rotation, + vertical_placement: VerticalPlacement, + pivot: Vector3, + properties: &RuinedPortalProperties, +) -> StructurePlaceSettings { + let ignore_processor = if properties.air_pocket { + StructureProcessor::BlockIgnore(vec![IgnoredBlock { + block_id: Block::STRUCTURE_BLOCK.id, + properties: None, + }]) + } else { + StructureProcessor::BlockIgnore(vec![ + IgnoredBlock { + block_id: Block::STRUCTURE_BLOCK.id, + properties: None, + }, + IgnoredBlock { + block_id: Block::AIR.id, + properties: None, + }, + ]) + }; + + let mut rules = Vec::new(); + rules.push(ProcessorRule { + position_predicate: PosRuleTest::AlwaysTrue, + input_predicate: RuleTest::RandomBlockMatch { + block: Block::GOLD_BLOCK.id, + probability: 0.3, + }, + location_predicate: RuleTest::AlwaysTrue, + output_state: Block::AIR.default_state, + block_entity_modifier: None, + }); + + let lava_rule = match vertical_placement { + VerticalPlacement::OnOceanFloor => ProcessorRule { + position_predicate: PosRuleTest::AlwaysTrue, + input_predicate: RuleTest::BlockMatch(Block::LAVA.id), + location_predicate: RuleTest::AlwaysTrue, + output_state: Block::MAGMA_BLOCK.default_state, + block_entity_modifier: None, + }, + _ => { + if properties.cold { + ProcessorRule { + position_predicate: PosRuleTest::AlwaysTrue, + input_predicate: RuleTest::BlockMatch(Block::LAVA.id), + location_predicate: RuleTest::AlwaysTrue, + output_state: Block::NETHERRACK.default_state, + block_entity_modifier: None, + } + } else { + ProcessorRule { + position_predicate: PosRuleTest::AlwaysTrue, + input_predicate: RuleTest::RandomBlockMatch { + block: Block::LAVA.id, + probability: 0.2, + }, + location_predicate: RuleTest::AlwaysTrue, + output_state: Block::MAGMA_BLOCK.default_state, + block_entity_modifier: None, + } + } + } + }; + rules.push(lava_rule); + + if !properties.cold { + rules.push(ProcessorRule { + position_predicate: PosRuleTest::AlwaysTrue, + input_predicate: RuleTest::RandomBlockMatch { + block: Block::NETHERRACK.id, + probability: 0.07, + }, + location_predicate: RuleTest::AlwaysTrue, + output_state: Block::MAGMA_BLOCK.default_state, + block_entity_modifier: None, + }); + } + + let mut settings = StructurePlaceSettings::new() + .set_rotation(rotation) + .set_mirror(mirror) + .set_rotation_pivot(pivot) + .add_processor(ignore_processor) + .add_processor(StructureProcessor::Rule(rules)) + .add_processor(StructureProcessor::BlockAge { + mossiness: properties.mossiness, + }) + .add_processor(StructureProcessor::ProtectedBlocks( + "#minecraft:features_cannot_replace".to_string(), + )) + .add_processor(StructureProcessor::LavaSubmergedBlock); + + if properties.replace_with_blackstone { + settings = settings.add_processor(StructureProcessor::BlackstoneReplace); + } + + settings +} diff --git a/crates/pumpkin-world/src/generation/structure/structures/shipwreck.rs b/crates/pumpkin-world/src/generation/structure/structures/shipwreck.rs index fcfe368a8..53ced600c 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/shipwreck.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/shipwreck.rs @@ -1,47 +1,72 @@ use std::sync::Arc; -use pumpkin_data::block_rotation::Rotation; +use pumpkin_data::{Mirror, Rotation}; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::{ - math::{block_box::BlockBox, position::BlockPos}, - random::{RandomGenerator, RandomImpl}, + HeightMap, + math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand}, }; use crate::{ ProtoChunk, generation::{ - positions::chunk_pos::{get_center_x, get_center_z}, + positions::chunk_pos::{get_center_x, get_center_z, start_block_x, start_block_z}, structure::{ piece::StructurePieceType, structures::{ StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, StructurePiecesCollector, StructurePosition, WorldPortalExt, }, - template::{StructureTemplate, get_template, place_template}, + template::{BlockStateResolver, StructureTemplate, get_template}, }, }, }; -const TEMPLATES: &[&str] = &[ - "shipwreck/rightsideup_backhalf", - "shipwreck/rightsideup_backhalf_degraded", - "shipwreck/rightsideup_fronthalf", - "shipwreck/rightsideup_fronthalf_degraded", - "shipwreck/rightsideup_full", - "shipwreck/rightsideup_full_degraded", - "shipwreck/sideways_backhalf", - "shipwreck/sideways_backhalf_degraded", - "shipwreck/sideways_fronthalf", - "shipwreck/sideways_fronthalf_degraded", - "shipwreck/sideways_full", - "shipwreck/sideways_full_degraded", - "shipwreck/upsidedown_backhalf", - "shipwreck/upsidedown_backhalf_degraded", - "shipwreck/upsidedown_fronthalf", - "shipwreck/upsidedown_fronthalf_degraded", - "shipwreck/upsidedown_full", - "shipwreck/upsidedown_full_degraded", +const NUMBER_OF_BLOCKS_ALLOWED_IN_WORLD_GEN_REGION: i32 = 32; +pub const PIVOT: Vector3 = Vector3::new(4, 0, 15); + +const STRUCTURE_LOCATION_BEACHED: &[&str] = &[ "shipwreck/with_mast", + "shipwreck/sideways_full", + "shipwreck/sideways_fronthalf", + "shipwreck/sideways_backhalf", + "shipwreck/rightsideup_full", + "shipwreck/rightsideup_fronthalf", + "shipwreck/rightsideup_backhalf", "shipwreck/with_mast_degraded", + "shipwreck/rightsideup_full_degraded", + "shipwreck/rightsideup_fronthalf_degraded", + "shipwreck/rightsideup_backhalf_degraded", +]; + +const STRUCTURE_LOCATION_OCEAN: &[&str] = &[ + "shipwreck/with_mast", + "shipwreck/upsidedown_full", + "shipwreck/upsidedown_fronthalf", + "shipwreck/upsidedown_backhalf", + "shipwreck/sideways_full", + "shipwreck/sideways_fronthalf", + "shipwreck/sideways_backhalf", + "shipwreck/rightsideup_full", + "shipwreck/rightsideup_fronthalf", + "shipwreck/rightsideup_backhalf", + "shipwreck/with_mast_degraded", + "shipwreck/upsidedown_full_degraded", + "shipwreck/upsidedown_fronthalf_degraded", + "shipwreck/upsidedown_backhalf_degraded", + "shipwreck/sideways_full_degraded", + "shipwreck/sideways_fronthalf_degraded", + "shipwreck/sideways_backhalf_degraded", + "shipwreck/rightsideup_full_degraded", + "shipwreck/rightsideup_fronthalf_degraded", + "shipwreck/rightsideup_backhalf_degraded", +]; + +const MARKERS_TO_LOOT: &[(&str, &str)] = &[ + ("map_chest", "minecraft:chests/shipwreck_map"), + ("treasure_chest", "minecraft:chests/shipwreck_treasure"), + ("supply_chest", "minecraft:chests/shipwreck_supply"), ]; pub struct ShipwreckGenerator { @@ -53,37 +78,70 @@ impl StructureGenerator for ShipwreckGenerator { &self, mut context: StructureGeneratorContext<'_>, ) -> Option { - let chunk_center_x = get_center_x(context.chunk_x); - let chunk_center_z = get_center_z(context.chunk_z); + let center_x = get_center_x(context.chunk_x); + let center_z = get_center_z(context.chunk_z); + + let center_y = if self.is_beached { + context + .height_sampler + .as_deref_mut() + .map_or(64, |s| s.estimate_height(center_x, center_z)) + } else { + context + .height_sampler + .as_deref_mut() + .map_or(context.sea_level, |s| { + s.estimate_ocean_floor_height(center_x, center_z) + }) + }; - // Deterministically select rotation and template let rotation_idx = context.random.next_bounded_i32(4) as u8; let rotation = Rotation::from_index(rotation_idx); - let template_idx = context.random.next_bounded_i32(TEMPLATES.len() as i32) as usize; - let template_name = TEMPLATES[template_idx]; + let start_x = start_block_x(context.chunk_x); + let start_z = start_block_z(context.chunk_z); + let position = Vector3::new(start_x, 90, start_z); + + let pool = if self.is_beached { + STRUCTURE_LOCATION_BEACHED + } else { + STRUCTURE_LOCATION_OCEAN + }; + let template_idx = context.random.next_bounded_i32(pool.len() as i32) as usize; + let template_name = pool[template_idx]; let template = get_template(template_name)?; - let size = template.size; - let bounding_box = BlockBox::new( - chunk_center_x - size.x / 2, - context.min_y, - chunk_center_z - size.z / 2, - chunk_center_x + size.x / 2, - 256, - chunk_center_z + size.z / 2, - ); + let mut piece = ShipwreckPiece::new(template, position, rotation, self.is_beached); + + if piece.is_too_big_to_fit_in_world_gen_region() { + let bb = piece.piece.bounding_box; + let height = if self.is_beached { + let min_y = context.height_sampler.as_deref_mut().map_or(64, |sampler| { + let c0 = sampler.estimate_height(bb.min.x, bb.min.z); + let c1 = sampler.estimate_height(bb.min.x, bb.max.z); + let c2 = sampler.estimate_height(bb.max.x, bb.min.z); + let c3 = sampler.estimate_height(bb.max.x, bb.max.z); + c0.min(c1).min(c2).min(c3) + }); + piece.calculate_beached_position(min_y, &mut context.random) + } else if let Some(sampler) = context.height_sampler.as_deref_mut() { + let c0 = sampler.estimate_height(bb.min.x, bb.min.z); + let c1 = sampler.estimate_height(bb.min.x, bb.max.z); + let c2 = sampler.estimate_height(bb.max.x, bb.min.z); + let c3 = sampler.estimate_height(bb.max.x, bb.max.z); + (c0 + c1 + c2 + c3) / 4 + } else { + context.sea_level + }; + + piece.adjust_position_height(height); + } let mut collector = StructurePiecesCollector::default(); - collector.add_piece(Box::new(ShipwreckPiece { - piece: StructurePiece::new(StructurePieceType::Shipwreck, bounding_box, 0), - template, - rotation, - is_beached: self.is_beached, - })); + collector.add_piece(Box::new(piece)); Some(StructurePosition { - start_pos: BlockPos::new(chunk_center_x, 64, chunk_center_z), + start_pos: BlockPos::new(center_x, center_y, center_z), collector: Arc::new(collector.into()), }) } @@ -93,53 +151,266 @@ pub struct ShipwreckPiece { piece: StructurePiece, template: Arc, rotation: Rotation, + template_position: Vector3, is_beached: bool, + height_adjusted: bool, +} + +impl ShipwreckPiece { + #[must_use] + pub fn new( + template: Arc, + position: Vector3, + rotation: Rotation, + is_beached: bool, + ) -> Self { + let bounding_box = StructureTemplate::get_bounding_box_static( + position, + rotation, + PIVOT, + Mirror::None, + template.size, + ); + + Self { + piece: StructurePiece::new(StructurePieceType::Shipwreck, bounding_box, 0), + template, + rotation, + template_position: position, + is_beached, + height_adjusted: false, + } + } + + #[must_use] + pub fn is_too_big_to_fit_in_world_gen_region(&self) -> bool { + self.template.size.x > NUMBER_OF_BLOCKS_ALLOWED_IN_WORLD_GEN_REGION + || self.template.size.y > NUMBER_OF_BLOCKS_ALLOWED_IN_WORLD_GEN_REGION + } + + #[must_use] + pub fn calculate_beached_position(&self, min_y: i32, random: &mut RandomGenerator) -> i32 { + min_y - self.template.size.y / 2 - random.next_bounded_i32(3) + } + + pub fn adjust_position_height(&mut self, new_height: i32) { + self.height_adjusted = true; + self.template_position.y = new_height; + self.piece.bounding_box = StructureTemplate::get_bounding_box_static( + self.template_position, + self.rotation, + PIVOT, + Mirror::None, + self.template.size, + ); + } + + fn handle_data_marker( + marker_id: &str, + position: Vector3, + chunk: &mut ProtoChunk, + chunk_box: &BlockBox, + ) { + let Some((_, loot_table)) = MARKERS_TO_LOOT.iter().find(|(k, _)| *k == marker_id) else { + return; + }; + + let chest_pos = Vector3::new(position.x, position.y - 1, position.z); + if !chunk_box.contains_pos(&chest_pos) { + return; + } + + if let Some(existing) = chunk.pending_block_entities.iter_mut().find(|nbt| { + nbt.get_int("x") == Some(chest_pos.x) + && nbt.get_int("y") == Some(chest_pos.y) + && nbt.get_int("z") == Some(chest_pos.z) + }) { + existing.put_string("LootTable", (*loot_table).to_string()); + let mut rng = + LegacyRand::from_seed(hash_block_pos(chest_pos.x, chest_pos.y, chest_pos.z) as u64); + existing.put_long("LootTableSeed", rng.next_i64()); + } else { + let mut chest_nbt = NbtCompound::new(); + chest_nbt.put_string("id", "minecraft:chest".to_string()); + chest_nbt.put_int("x", chest_pos.x); + chest_nbt.put_int("y", chest_pos.y); + chest_nbt.put_int("z", chest_pos.z); + chest_nbt.put_string("LootTable", (*loot_table).to_string()); + let mut rng = + LegacyRand::from_seed(hash_block_pos(chest_pos.x, chest_pos.y, chest_pos.z) as u64); + chest_nbt.put_long("LootTableSeed", rng.next_i64()); + chunk.add_block_entity(chest_nbt); + } + } } impl StructurePieceBase for ShipwreckPiece { fn as_any(&self) -> &dyn std::any::Any { self } + fn get_structure_piece(&self) -> &StructurePiece { &self.piece } + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { &mut self.piece } + + #[expect(clippy::too_many_lines)] fn place( &mut self, chunk: &mut ProtoChunk, _block_registry: &dyn WorldPortalExt, - _random: &mut RandomGenerator, + random: &mut RandomGenerator, _seed: i64, chunk_box: &BlockBox, ) { - let origin = self.piece.bounding_box.min; - let height_map_type = if self.is_beached { - pumpkin_util::HeightMap::WorldSurfaceWg - } else { - pumpkin_util::HeightMap::OceanFloorWg - }; + if !self.height_adjusted && !self.is_too_big_to_fit_in_world_gen_region() { + let heightmap_type = if self.is_beached { + HeightMap::WorldSurfaceWg + } else { + HeightMap::OceanFloorWg + }; - let sample_y = chunk.get_top_y(&height_map_type, origin.x, origin.z); - let target_y = if self.is_beached { - sample_y - 1 - } else { - sample_y - 3 - }; - let mut final_origin = origin; - final_origin.y = target_y; + let template_size = self.template.size; + let base_size = template_size.x * template_size.z; - place_template( - chunk, - &self.template, - final_origin, - (0, 0), - self.rotation, - true, - !self.is_beached, - &[], - Some(chunk_box), - ); + let (mean, min_y) = if base_size == 0 { + let h = chunk.get_top_y( + &heightmap_type, + self.template_position.x, + self.template_position.z, + ); + (h, h) + } else { + let mut min_y = i32::MAX; + let mut total = 0; + for x in self.template_position.x..self.template_position.x + template_size.x { + for z in self.template_position.z..self.template_position.z + template_size.z { + let h = chunk.get_top_y(&heightmap_type, x, z); + total += h; + min_y = min_y.min(h); + } + } + (total / base_size, min_y) + }; + + let target_y = if self.is_beached { + self.calculate_beached_position(min_y, random) + } else { + mean + }; + + self.adjust_position_height(target_y); + } + + // Place non-marker blocks + let box_limit = self.piece.bounding_box; + for block in &self.template.blocks { + let palette_entry = &self.template.palette[block.state as usize]; + + if palette_entry.name == "minecraft:structure_block" + || palette_entry.name == "minecraft:air" + || palette_entry.name == "minecraft:structure_void" + || palette_entry.name == "structure_void" + { + continue; + } + + let rel_pos = StructureTemplate::transform_block_pos( + block.pos, + Mirror::None, + self.rotation, + PIVOT, + ); + let world_pos = self.template_position + rel_pos; + + if !box_limit.contains_pos(&world_pos) || !chunk_box.contains_pos(&world_pos) { + continue; + } + + let Some(mut state) = + BlockStateResolver::resolve(palette_entry, self.rotation, Mirror::None) + else { + continue; + }; + + if chunk.get_block_state(&world_pos).to_block_id() == pumpkin_data::Block::WATER.id { + let mut modified_entry = palette_entry.clone(); + if let Some((_, waterlogged)) = modified_entry + .properties + .iter_mut() + .find(|(name, _)| name == "waterlogged") + { + *waterlogged = "true".to_string(); + if let Some(waterlogged_state) = + BlockStateResolver::resolve(&modified_entry, self.rotation, Mirror::None) + { + state = waterlogged_state; + } + } + } + + chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state); + + let final_block = pumpkin_data::Block::from_id(state.id.to_block_id()); + let block_entity_id = + crate::generation::structure::template::get_block_entity_id(final_block.name); + if block.nbt.is_some() || block_entity_id.is_some() { + let fallback_id = block_entity_id.unwrap_or(final_block.name); + let mut placed_nbt = NbtCompound::new(); + + placed_nbt.put_string("id", fallback_id.to_string()); + placed_nbt.put_int("x", world_pos.x); + placed_nbt.put_int("y", world_pos.y); + placed_nbt.put_int("z", world_pos.z); + + if let Some(template_nbt) = &block.nbt { + for (key, value) in &template_nbt.child_tags { + if key.as_ref() != "x" + && key.as_ref() != "y" + && key.as_ref() != "z" + && key.as_ref() != "id" + { + placed_nbt.child_tags.insert(key.clone(), value.clone()); + } + } + } + + if placed_nbt.get_string("LootTable").is_some() + && placed_nbt.get_long("LootTableSeed").is_none() + { + let mut rng = + LegacyRand::from_seed( + hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64, + ); + placed_nbt.put_long("LootTableSeed", rng.next_i64()); + } + + chunk.add_block_entity(placed_nbt); + } + } + + // Process data markers + for block in &self.template.blocks { + let palette_entry = &self.template.palette[block.state as usize]; + if palette_entry.name == "minecraft:structure_block" + && let Some(nbt) = &block.nbt + { + let mode = nbt.get_string("mode").unwrap_or(""); + if mode == "DATA" { + let metadata = nbt.get_string("metadata").unwrap_or(""); + let rel_pos = StructureTemplate::transform_block_pos( + block.pos, + Mirror::None, + self.rotation, + PIVOT, + ); + let world_pos = self.template_position + rel_pos; + Self::handle_data_marker(metadata, world_pos, chunk, chunk_box); + } + } + } } } diff --git a/crates/pumpkin-world/src/generation/structure/template/template_piece.rs b/crates/pumpkin-world/src/generation/structure/template/template_piece.rs index bd605ec72..e3f9357b6 100644 --- a/crates/pumpkin-world/src/generation/structure/template/template_piece.rs +++ b/crates/pumpkin-world/src/generation/structure/template/template_piece.rs @@ -1,4 +1,4 @@ -//! Template-based structure piece for placing NBT templates. +//! Template-based structure piece matching vanilla Minecraft `TemplateStructurePiece`. //! //! This module provides `TemplatePiece`, which implements `StructurePieceBase` //! to place blocks from a loaded structure template into the world. @@ -10,7 +10,6 @@ use pumpkin_util::{ math::{block_box::BlockBox, vector3::Vector3}, random::RandomGenerator, }; -use tracing::debug; use crate::{ ProtoChunk, @@ -21,34 +20,35 @@ use crate::{ world::WorldPortalExt, }; -use super::{BlockStateResolver, StructureTemplate}; +use super::{BlockStateResolver, PaletteEntry, StructurePlaceSettings, StructureTemplate}; -/// A structure piece that places blocks from an NBT template. +/// A structure piece that places blocks from an NBT template matching vanilla `TemplateStructurePiece`. /// /// This piece handles: /// - Loading blocks from a `StructureTemplate` -/// - Applying rotation and mirror transformations +/// - Applying rotation, mirror, and pivot transformations from `StructurePlaceSettings` /// - Placing blocks at world coordinates +/// - Replacing jigsaw blocks with their `final_state` /// - Skipping structure void blocks pub struct TemplatePiece { /// The underlying structure piece metadata. pub piece: StructurePiece, + /// The template identifier/name. + pub template_name: String, + /// The template to place. pub template: Arc, - /// Rotation to apply when placing. - pub rotation: Rotation, + /// Placement settings (rotation, mirror, pivot, processors, etc.). + pub place_settings: StructurePlaceSettings, - /// Mirror to apply when placing. - pub mirror: Mirror, + /// The origin position of the template in the world. + pub template_position: Vector3, } impl TemplatePiece { - /// Creates a new template piece from the given `template` at `origin`. - /// - /// The `rotation` and `mirror` transforms are applied when placing blocks. - /// The `piece_type` identifies this piece within the structure system. + /// Creates a new template piece from the given `template` at `origin` with legacy rotation & mirror. #[must_use] pub fn new( template: Arc, @@ -57,23 +57,37 @@ impl TemplatePiece { origin: Vector3, piece_type: StructurePieceType, ) -> Self { - // Calculate the bounding box based on template size and rotation - let rotated_size = rotation.transform_size(template.size); - - let bounding_box = BlockBox::new( - origin.x, - origin.y, - origin.z, - origin.x + rotated_size.x - 1, - origin.y + rotated_size.y - 1, - origin.z + rotated_size.z - 1, - ); + let place_settings = StructurePlaceSettings::new() + .set_rotation(rotation) + .set_mirror(mirror); + let bounding_box = template.get_bounding_box(&place_settings, origin); Self { piece: StructurePiece::new(piece_type, bounding_box, 0), + template_name: String::new(), template, - rotation, - mirror, + place_settings, + template_position: origin, + } + } + + /// Creates a new template piece with full `StructurePlaceSettings` matching vanilla `TemplateStructurePiece`. + #[must_use] + pub fn from_settings( + template: Arc, + template_name: String, + place_settings: StructurePlaceSettings, + origin: Vector3, + piece_type: StructurePieceType, + ) -> Self { + let bounding_box = template.get_bounding_box(&place_settings, origin); + + Self { + piece: StructurePiece::new(piece_type, bounding_box, 0), + template_name, + template, + place_settings, + template_position: origin, } } @@ -95,23 +109,55 @@ impl TemplatePiece { /// Returns the template's size after applying rotation. #[must_use] pub fn rotated_size(&self) -> Vector3 { - self.rotation.transform_size(self.template.size) + self.place_settings + .get_rotation() + .transform_size(self.template.size) + } + + /// Gets the rotation configured in place settings. + #[must_use] + pub const fn get_rotation(&self) -> Rotation { + self.place_settings.get_rotation() + } + + /// Gets a reference to the loaded template. + #[must_use] + pub const fn template(&self) -> &Arc { + &self.template + } + + /// Gets the world template position. + #[must_use] + pub const fn template_position(&self) -> Vector3 { + self.template_position + } + + /// Gets a reference to the structure placement settings. + #[must_use] + pub const fn place_settings(&self) -> &StructurePlaceSettings { + &self.place_settings + } + + /// Gets a mutable reference to the structure placement settings. + pub const fn place_settings_mut(&mut self) -> &mut StructurePlaceSettings { + &mut self.place_settings } /// Transforms a template-relative position to world coordinates. - fn transform_pos(&self, local_pos: Vector3) -> Vector3 { - // First apply mirror - let mirrored = self.mirror.transform_pos(local_pos, self.template.size); + #[must_use] + pub fn transform_pos(&self, local_pos: Vector3) -> Vector3 { + StructureTemplate::transform_block_pos( + local_pos, + self.place_settings.get_mirror(), + self.place_settings.get_rotation(), + self.place_settings.get_rotation_pivot(), + ) + self.template_position + } - // Then apply rotation - let rotated = self.rotation.transform_pos(mirrored, self.template.size); - - // Add world offset - Vector3::new( - self.piece.bounding_box.min.x + rotated.x, - self.piece.bounding_box.min.y + rotated.y, - self.piece.bounding_box.min.z + rotated.z, - ) + /// Moves the template position and bounding box by `(dx, dy, dz)`. + pub fn move_piece(&mut self, dx: i32, dy: i32, dz: i32) { + self.piece.translate(dx, dy, dz); + self.template_position += Vector3::new(dx, dy, dz); } /// Checks if a block name is structure void (should not be placed). @@ -119,23 +165,49 @@ impl TemplatePiece { name == "minecraft:structure_void" || name == "structure_void" } - /// Places all blocks from the template into the chunk. - fn place_blocks(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) { + /// Places all blocks from the template into the chunk matching vanilla `postProcess`. + fn place_blocks(&mut self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) { + self.place_settings = self + .place_settings + .clone() + .set_bounding_box(Some(*chunk_box)); + self.piece.bounding_box = self + .template + .get_bounding_box(&self.place_settings, self.template_position); let box_limit = self.piece.bounding_box; for block in &self.template.blocks { let palette_entry = &self.template.palette[block.state as usize]; + // Structure blocks are data markers + if palette_entry.name == "minecraft:structure_block" { + continue; + } + + let mut placed_entry = palette_entry.clone(); + let mut block_entity_nbt = block.nbt.clone(); + + // Jigsaw blocks are replaced with final_state + if palette_entry.name == "minecraft:jigsaw" { + let final_state = block_entity_nbt + .as_ref() + .and_then(|nbt| nbt.get_string("final_state")) + .unwrap_or("minecraft:air"); + placed_entry = PaletteEntry::from_string(final_state); + block_entity_nbt = None; + } + // Skip structure void blocks - if Self::is_structure_void(&palette_entry.name) { + if Self::is_structure_void(&placed_entry.name) { continue; } // Resolve the block state with rotation/mirror - let Some(state) = - BlockStateResolver::resolve(palette_entry, self.rotation, self.mirror) - else { - debug!("Failed to resolve block: {}", palette_entry.name); + let Some(mut state) = BlockStateResolver::resolve( + &placed_entry, + self.place_settings.get_rotation(), + self.place_settings.get_mirror(), + ) else { continue; }; @@ -147,20 +219,39 @@ impl TemplatePiece { continue; } + // Handle waterlogging if enabled + if self.place_settings.should_apply_waterlogging() + && chunk.get_block_state(&world_pos).to_block_id() == pumpkin_data::Block::WATER.id + && let Some((_, waterlogged)) = placed_entry + .properties + .iter_mut() + .find(|(name, _)| name == "waterlogged") + { + *waterlogged = "true".to_string(); + if let Some(waterlogged_state) = BlockStateResolver::resolve( + &placed_entry, + self.place_settings.get_rotation(), + self.place_settings.get_mirror(), + ) { + state = waterlogged_state; + } + } + // Place the block chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state); - let block_entity_id = super::get_block_entity_id(&palette_entry.name); - if block.nbt.is_some() || block_entity_id.is_some() { - let block_entity_id = block_entity_id.unwrap_or(&palette_entry.name); + let final_block = pumpkin_data::Block::from_id(state.id.to_block_id()); + let block_entity_id = super::get_block_entity_id(final_block.name); + if block_entity_nbt.is_some() || block_entity_id.is_some() { + let fallback_id = block_entity_id.unwrap_or(final_block.name); let mut placed_nbt = pumpkin_nbt::compound::NbtCompound::new(); - placed_nbt.put_string("id", block_entity_id.to_string()); + placed_nbt.put_string("id", fallback_id.to_string()); placed_nbt.put_int("x", world_pos.x); placed_nbt.put_int("y", world_pos.y); placed_nbt.put_int("z", world_pos.z); - if let Some(template_nbt) = &block.nbt { + if let Some(template_nbt) = &block_entity_nbt { for (key, value) in &template_nbt.child_tags { if key.as_ref() != "x" && key.as_ref() != "y" @@ -195,6 +286,7 @@ impl StructurePieceBase for TemplatePiece { fn as_any(&self) -> &dyn std::any::Any { self } + fn place( &mut self, chunk: &mut ProtoChunk, @@ -206,6 +298,10 @@ impl StructurePieceBase for TemplatePiece { self.place_blocks(chunk, chunk_box); } + fn translate(&mut self, x: i32, y: i32, z: i32) { + self.move_piece(x, y, z); + } + fn get_structure_piece(&self) -> &StructurePiece { &self.piece } diff --git a/crates/pumpkin-world/src/level.rs b/crates/pumpkin-world/src/level.rs index b5f8ff456..a521316df 100644 --- a/crates/pumpkin-world/src/level.rs +++ b/crates/pumpkin-world/src/level.rs @@ -355,17 +355,20 @@ impl Level { let handle_count = handles.len(); info!("Joining {} threads for {}...", handle_count, world_id); - let join_task = tokio::task::spawn_blocking(move || { - let mut failed_count = 0; - for handle in handles { - if handle.join().is_err() { - failed_count += 1; + let (tx, rx) = tokio::sync::oneshot::channel(); + let _ = std::thread::Builder::new() + .name("Thread-Joiner".into()) + .spawn(move || { + let mut failed_count = 0; + for handle in handles { + if handle.join().is_err() { + failed_count += 1; + } } - } - failed_count - }); + let _ = tx.send(failed_count); + }); - match timeout(Duration::from_secs(3), join_task).await { + match timeout(Duration::from_secs(3), rx).await { Ok(Ok(failed_count)) => { if failed_count > 0 { warn!( diff --git a/crates/pumpkin-world/src/world.rs b/crates/pumpkin-world/src/world.rs index 5d5d05458..d16b7bd4c 100644 --- a/crates/pumpkin-world/src/world.rs +++ b/crates/pumpkin-world/src/world.rs @@ -1,5 +1,3 @@ -use std::pin::Pin; - use crate::generation::proto_chunk::GenerationCache; use bitflags::bitflags; use pumpkin_data::{Block, BlockState, BlockStateId, Mirror, Rotation, chunk::Biome}; @@ -54,8 +52,6 @@ impl std::fmt::Display for GetBlockError { } } -pub type WorldFuture<'a, T> = Pin + Send + 'a>>; - pub trait WorldPortalExt: Send + Sync { fn can_place_at( &self, diff --git a/crates/pumpkin/src/block/blocks/bed.rs b/crates/pumpkin/src/block/blocks/bed.rs index e133e0bd2..80509fea7 100644 --- a/crates/pumpkin/src/block/blocks/bed.rs +++ b/crates/pumpkin/src/block/blocks/bed.rs @@ -157,7 +157,7 @@ impl BlockBehaviour for BedBlock { }; args.world - .break_block(&other_half_pos, Some(args.player.clone()), flags); + .break_block(&other_half_pos, Some(args.player), flags); } fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) { diff --git a/crates/pumpkin/src/block/blocks/doors.rs b/crates/pumpkin/src/block/blocks/doors.rs index 25e1dfafd..285a09051 100644 --- a/crates/pumpkin/src/block/blocks/doors.rs +++ b/crates/pumpkin/src/block/blocks/doors.rs @@ -281,7 +281,7 @@ impl BlockBehaviour for DoorBlock { }; args.world - .break_block(&other_half_pos, Some(args.player.clone()), flags); + .break_block(&other_half_pos, Some(args.player), flags); } fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) { diff --git a/crates/pumpkin/src/block/entities/hopper.rs b/crates/pumpkin/src/block/entities/hopper.rs index 1da4c45af..04ed056a8 100644 --- a/crates/pumpkin/src/block/entities/hopper.rs +++ b/crates/pumpkin/src/block/entities/hopper.rs @@ -233,7 +233,7 @@ impl HopperBlockEntity { ); let entities = world.get_entities_at_box(&search_box); for entity_base in entities { - if let Some(item_entity) = entity_base.clone().get_item_entity() { + if let Some(item_entity) = entity_base.get_item_entity() { let (is_empty, registry_key) = { let stack = item_entity .get_item_stack() diff --git a/crates/pumpkin/src/block/registry.rs b/crates/pumpkin/src/block/registry.rs index fd0fcb131..c5d95fa38 100644 --- a/crates/pumpkin/src/block/registry.rs +++ b/crates/pumpkin/src/block/registry.rs @@ -50,7 +50,7 @@ use crate::block::blocks::ice::{FrostedIceBlock, IceBlock}; use crate::block::blocks::infested::InfestedBlock; use crate::block::blocks::iron_bars::IronBarsBlock; use crate::block::blocks::jigsaw::JigsawBlock; -use crate::block::blocks::leaves::LeavesBlock; +// use crate::block::blocks::leaves::LeavesBlock; use crate::block::blocks::logs::LogBlock; use crate::block::blocks::loom::LoomBlock; use crate::block::blocks::magma::MagmaBlock; @@ -261,7 +261,8 @@ pub fn default_registry() -> Arc { manager.register(InfestedBlock); manager.register(JukeboxBlock); manager.register(LogBlock); - manager.register(LeavesBlock); + // FIX despawn + // manager.register(LeavesBlock); manager.register(BambooBlock); manager.register(BambooSaplingBlock); manager.register(BannerBlock); diff --git a/crates/pumpkin/src/command/commands/fetchprofile.rs b/crates/pumpkin/src/command/commands/fetchprofile.rs index 6bbb4bde5..c69a9fac2 100644 --- a/crates/pumpkin/src/command/commands/fetchprofile.rs +++ b/crates/pumpkin/src/command/commands/fetchprofile.rs @@ -142,7 +142,7 @@ fn report_resolved_profile( source.send_feedback(msg, false); } -async fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option { +fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option { if let Some(player) = server.get_player_by_name(name) { return Some(player.gameprofile.clone()); } @@ -155,14 +155,7 @@ async fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option Option Option Option { +fn fetch_profile_by_id_helper(server: &Server, id: Uuid) -> Option { if let Some(player) = server.get_player_by_uuid(id) { return Some(player.gameprofile.clone()); } @@ -233,11 +223,7 @@ async fn fetch_profile_by_id_helper(server: &Server, id: Uuid) -> Option { report_resolved_profile( @@ -307,9 +292,8 @@ impl CommandExecutor for ResolveIdExecutor { let source = context.source.clone(); let id_component = TextComponent::text(id.to_string()); - let runtime = server.runtime.clone(); - runtime.spawn(async move { - let result = fetch_profile_by_id_helper(&server, id).await; + rayon::spawn(move || { + let result = fetch_profile_by_id_helper(&server, id); match result { Some(profile) => { report_resolved_profile( diff --git a/crates/pumpkin/src/command/commands/plugin.rs b/crates/pumpkin/src/command/commands/plugin.rs index 4b4de6b36..3891a8994 100644 --- a/crates/pumpkin/src/command/commands/plugin.rs +++ b/crates/pumpkin/src/command/commands/plugin.rs @@ -111,7 +111,7 @@ impl CommandExecutor for LoadExecutor { let sender_clone = sender.clone(); let plugin_name_clone = plugin_name; let server_clone = server_arc.clone(); - server_arc.runtime.spawn(async move { + server_arc.spawn_task(async move { let result = server_clone .plugin_manager .try_load_plugin(&server_clone, Path::new(&plugin_name_clone)) @@ -171,7 +171,7 @@ impl CommandExecutor for UnloadExecutor { let sender_clone = sender.clone(); let plugin_name_clone = plugin_name; let server_clone = server_arc.clone(); - server_arc.runtime.spawn(async move { + server_arc.spawn_task(async move { let result = server_clone .plugin_manager .unload_plugin(&plugin_name_clone) @@ -221,7 +221,7 @@ impl CommandExecutor for HotReloadExecutor { let sender_clone = sender.clone(); let server_clone = server_arc.clone(); if enabled { - server_arc.runtime.spawn(async move { + server_arc.spawn_task(async move { if let Err(e) = server_clone.plugin_manager.start_watcher(&server_clone).await { sender_clone.send_message(TextComponent::text(format!( "Failed to start plugin watcher: {e}" @@ -241,7 +241,7 @@ impl CommandExecutor for HotReloadExecutor { ); }); } else { - server_arc.runtime.spawn(async move { + server_arc.spawn_task(async move { server_clone.plugin_manager.stop_watcher().await; sender_clone.send_message( TextComponent::text("Hot reloading has been disabled.") diff --git a/crates/pumpkin/src/command/commands/rotate.rs b/crates/pumpkin/src/command/commands/rotate.rs index 3c659ee3b..822ca7997 100644 --- a/crates/pumpkin/src/command/commands/rotate.rs +++ b/crates/pumpkin/src/command/commands/rotate.rs @@ -77,17 +77,9 @@ fn rotate_entity( }; // Use teleport with same position to update rotation - // This properly handles both players (sends CPlayerPosition) and other entities let pos = entity.pos.load(); let world = entity.world.load_full(); - if let Some(player) = target.get_player() { - let fut = target - .clone() - .teleport(pos, Some(final_yaw), Some(final_pitch), world); - player.spawn_task(fut); - } else { - entity.teleport(pos, Some(final_yaw), Some(final_pitch), world); - } + target.teleport(pos, Some(final_yaw), Some(final_pitch), world); } /// Sends success message for the rotate command. diff --git a/crates/pumpkin/src/command/commands/saveall.rs b/crates/pumpkin/src/command/commands/saveall.rs index d1d8aaf7f..a18a3bdbd 100644 --- a/crates/pumpkin/src/command/commands/saveall.rs +++ b/crates/pumpkin/src/command/commands/saveall.rs @@ -27,10 +27,10 @@ impl CommandExecutor for SaveAllExecutor { ); let server_arc = context.server().clone(); + let server_clone = server_arc.clone(); let source = context.source.clone(); - let runtime = server_arc.runtime.clone(); - runtime.spawn(async move { - if let Err(err) = server_arc.save_all().await { + server_arc.spawn_task(async move { + if let Err(err) = server_clone.save_all().await { error!("Failed to save server data: {err}"); source.send_error(TextComponent::translate_cross( translation::java::COMMANDS_SAVE_FAILED, diff --git a/crates/pumpkin/src/command/commands/spectate.rs b/crates/pumpkin/src/command/commands/spectate.rs index 33e0003fc..b8ad2a83d 100644 --- a/crates/pumpkin/src/command/commands/spectate.rs +++ b/crates/pumpkin/src/command/commands/spectate.rs @@ -105,11 +105,7 @@ impl CommandExecutor for SpectateTargetSelfExecutor { let yaw = target_entity.yaw.load(); let pitch = target_entity.pitch.load(); player.try_send_client_packet(&CSetCamera::new(target_id.into())); - player.spawn_task( - player - .clone() - .teleport(pos, Some(yaw), Some(pitch), player_world), - ); + player.teleport(pos, Some(yaw), Some(pitch), player_world); let target_name = target.get_display_name(); sender.send_message(TextComponent::translate_cross( @@ -178,11 +174,7 @@ impl CommandExecutor for SpectateTargetOtherExecutor { let pitch = target_entity.pitch.load(); let player_world = player.world(); player.try_send_client_packet(&CSetCamera::new(target_id.into())); - player.spawn_task( - player - .clone() - .teleport(pos, Some(yaw), Some(pitch), player_world), - ); + player.teleport(pos, Some(yaw), Some(pitch), player_world); succeeded += 1; } diff --git a/crates/pumpkin/src/command/commands/spreadplayers.rs b/crates/pumpkin/src/command/commands/spreadplayers.rs index 821136d22..dfb192249 100644 --- a/crates/pumpkin/src/command/commands/spreadplayers.rs +++ b/crates/pumpkin/src/command/commands/spreadplayers.rs @@ -269,12 +269,12 @@ impl CommandExecutor for SpreadPlayersExecutor { for (index, target) in targets.iter().enumerate() { let pile = piles[index % pile_count]; let y = surface_ys[index % pile_count]; - context.server().runtime.spawn(target.clone().teleport( + target.teleport( Vector3::new(pile.x.floor() + 0.5, f64::from(y), pile.z.floor() + 0.5), None, None, world.clone(), - )); + ); } let average_distance = average_min_distance(&piles); diff --git a/crates/pumpkin/src/command/commands/teleport.rs b/crates/pumpkin/src/command/commands/teleport.rs index 2f093e0d3..22751e5e3 100644 --- a/crates/pumpkin/src/command/commands/teleport.rs +++ b/crates/pumpkin/src/command/commands/teleport.rs @@ -82,7 +82,7 @@ impl CommandExecutor for EntitiesToEntityExecutor { fn execute( &self, sender: &CommandSender, - server: &crate::server::Server, + _server: &crate::server::Server, args: &ConsumedArgs, ) -> CommandResult { let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?; @@ -101,12 +101,7 @@ impl CommandExecutor for EntitiesToEntityExecutor { ))); } for target in targets { - server.runtime.spawn(target.clone().teleport( - pos, - Some(yaw), - Some(pitch), - world.clone(), - )); + target.teleport(pos, Some(yaw), Some(pitch), world.clone()); } let (key, target_arg) = success_key_and_arg( @@ -148,12 +143,7 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor { let world = resolve_sender_world(sender, server)?; for target in targets { - server.runtime.spawn(target.clone().teleport( - pos, - Some(yaw), - Some(pitch), - world.clone(), - )); + target.teleport(pos, Some(yaw), Some(pitch), world.clone()); } let (key, target_arg) = success_key_and_arg( @@ -200,12 +190,7 @@ impl CommandExecutor for EntitiesToPosFacingEntityExecutor { let world = resolve_sender_world(sender, server)?; for target in targets { - server.runtime.spawn(target.clone().teleport( - pos, - Some(yaw), - Some(pitch), - world.clone(), - )); + target.teleport(pos, Some(yaw), Some(pitch), world.clone()); } let (key, target_arg) = success_key_and_arg( @@ -253,12 +238,7 @@ impl CommandExecutor for EntitiesToPosWithRotationExecutor { let world = resolve_sender_world(sender, server)?; for target in targets { - server.runtime.spawn(target.clone().teleport( - pos, - Some(yaw), - Some(pitch), - world.clone(), - )); + target.teleport(pos, Some(yaw), Some(pitch), world.clone()); } let (key, target_arg) = success_key_and_arg( @@ -304,12 +284,7 @@ impl CommandExecutor for EntitiesToPosExecutor { for target in targets { let yaw = target.get_entity().yaw.load(); let pitch = target.get_entity().pitch.load(); - server.runtime.spawn(target.clone().teleport( - pos, - Some(yaw), - Some(pitch), - world.clone(), - )); + target.teleport(pos, Some(yaw), Some(pitch), world.clone()); } let (key, target_arg) = success_key_and_arg( @@ -338,7 +313,7 @@ impl CommandExecutor for SelfToEntityExecutor { fn execute( &self, sender: &CommandSender, - server: &crate::server::Server, + _server: &crate::server::Server, args: &ConsumedArgs, ) -> CommandResult { let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?; @@ -357,9 +332,7 @@ impl CommandExecutor for SelfToEntityExecutor { [], ))); } - server - .runtime - .spawn(player.clone().teleport(pos, Some(yaw), Some(pitch), world)); + player.teleport(pos, Some(yaw), Some(pitch), world); sender.send_message(TextComponent::translate_cross( translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE, @@ -387,7 +360,7 @@ impl CommandExecutor for SelfToPosExecutor { fn execute( &self, sender: &CommandSender, - server: &crate::server::Server, + _server: &crate::server::Server, args: &ConsumedArgs, ) -> CommandResult { match sender { @@ -403,12 +376,7 @@ impl CommandExecutor for SelfToPosExecutor { ))); } let player_world = player.world(); - server.runtime.spawn(player.clone().teleport( - pos, - Some(yaw), - Some(pitch), - player_world, - )); + player.teleport(pos, Some(yaw), Some(pitch), player_world); sender.send_message(TextComponent::translate_cross( translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE, diff --git a/crates/pumpkin/src/data/advancement_data.rs b/crates/pumpkin/src/data/advancement_data.rs index a01e63a87..defc5df97 100644 --- a/crates/pumpkin/src/data/advancement_data.rs +++ b/crates/pumpkin/src/data/advancement_data.rs @@ -65,19 +65,16 @@ impl AdvancementManager { to_write.push((guard.path.clone(), json)); } - let advancement_path = self.advancement_path.clone(); - tokio::task::spawn_blocking(move || { - if let Err(e) = create_dir_all(advancement_path) { - error!("Failed to create player advancement directory : {e}"); - return Err(AdvancementDataError::Io(e)); - } - for (path, json) in to_write { - std::fs::write(&path, json).map_err(AdvancementDataError::Io)?; - } - Ok(()) - }) - .await - .unwrap_or(Ok(())) + if let Err(e) = tokio::fs::create_dir_all(&self.advancement_path).await { + error!("Failed to create player advancement directory : {e}"); + return Err(AdvancementDataError::Io(e)); + } + for (path, json) in to_write { + tokio::fs::write(&path, json) + .await + .map_err(AdvancementDataError::Io)?; + } + Ok(()) } /// Saves the advancements of a specific player. diff --git a/crates/pumpkin/src/data/player_server.rs b/crates/pumpkin/src/data/player_server.rs index 4602515d3..0899fa9c1 100644 --- a/crates/pumpkin/src/data/player_server.rs +++ b/crates/pumpkin/src/data/player_server.rs @@ -44,7 +44,7 @@ impl ServerPlayerData { /// # Returns /// /// A Result indicating success or the error that occurred. - pub async fn handle_player_leave(&self, player: &Arc) -> Result<(), PlayerDataError> { + pub fn handle_player_leave(&self, player: &Arc) -> Result<(), PlayerDataError> { player .player_screen_handler .lock() @@ -55,14 +55,7 @@ impl ServerPlayerData { let mut nbt = NbtCompound::new(); player.write_nbt(&mut nbt); - let storage = self.storage.clone(); - let uuid = player.gameprofile.id; - // Save to disk - tokio::task::spawn_blocking(move || storage.save_player_data(&uuid, nbt)) - .await - .unwrap_or(Err(PlayerDataError::Io(std::io::Error::from( - std::io::ErrorKind::Other, - ))))?; + self.storage.save_player_data(&player.gameprofile.id, nbt)?; Ok(()) } @@ -94,7 +87,7 @@ impl ServerPlayerData { } let storage = self.storage.clone(); - tokio::task::spawn_blocking(move || { + rayon::spawn(move || { for (uuid, nbt) in snapshots { if let Err(e) = storage.save_player_data(&uuid, nbt) { error!("Failed to save player data for {uuid}: {e}"); @@ -109,13 +102,13 @@ impl ServerPlayerData { /// /// This function immediately saves all online players' data to disk. /// Useful for server shutdown or backup operations. - pub async fn save_all_players(&self, server: &Server) -> Result<(), PlayerDataError> { + pub fn save_all_players(&self, server: &Server) -> Result<(), PlayerDataError> { let mut total_players = 0; // Save players from all worlds for world in server.worlds.load().iter() { for player in world.players.load().iter() { - self.extract_data_and_save_player(player).await?; + self.extract_data_and_save_player(player)?; total_players += 1; } } @@ -136,15 +129,8 @@ impl ServerPlayerData { /// # Returns /// /// A Result indicating success or the error that occurred. - pub async fn load_data( - &self, - uuid: &uuid::Uuid, - ) -> Result, PlayerDataError> { - let storage = self.storage.clone(); - let uuid = *uuid; - let result = tokio::task::spawn_blocking(move || storage.load_player_data(&uuid)) - .await - .unwrap_or(Ok((false, NbtCompound::new()))); + pub fn load_data(&self, uuid: &uuid::Uuid) -> Result, PlayerDataError> { + let result = self.storage.load_player_data(uuid); match result { Ok((should_load, data)) => { @@ -179,10 +165,7 @@ impl ServerPlayerData { /// # Returns /// /// A Result indicating success or the error that occurred. - pub async fn extract_data_and_save_player( - &self, - player: &Player, - ) -> Result<(), PlayerDataError> { + pub fn extract_data_and_save_player(&self, player: &Player) -> Result<(), PlayerDataError> { if !self.storage.is_save_enabled() { return Ok(()); } @@ -191,13 +174,7 @@ impl ServerPlayerData { let mut nbt = NbtCompound::new(); player.write_nbt(&mut nbt); - let storage = self.storage.clone(); - tokio::task::spawn_blocking(move || storage.save_player_data(&uuid, nbt)) - .await - .unwrap_or(Err(PlayerDataError::Io(std::io::Error::from( - std::io::ErrorKind::Other, - ))))?; - + self.storage.save_player_data(&uuid, nbt)?; Ok(()) } } diff --git a/crates/pumpkin/src/entity/area_effect_cloud.rs b/crates/pumpkin/src/entity/area_effect_cloud.rs index 3da01ae62..c02ab9f6d 100644 --- a/crates/pumpkin/src/entity/area_effect_cloud.rs +++ b/crates/pumpkin/src/entity/area_effect_cloud.rs @@ -216,7 +216,7 @@ impl EntityBase for AreaEffectCloudEntity { #[allow(clippy::too_many_lines)] #[allow(clippy::semicolon_outside_block)] - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) { + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) { // Age & duration handling { let mut age = self diff --git a/crates/pumpkin/src/entity/boss/ender_dragon.rs b/crates/pumpkin/src/entity/boss/ender_dragon.rs index 49eacbd99..ce3d725ab 100644 --- a/crates/pumpkin/src/entity/boss/ender_dragon.rs +++ b/crates/pumpkin/src/entity/boss/ender_dragon.rs @@ -850,7 +850,7 @@ impl Mob for EnderDragonEntity { &self.mob_entity } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.ai_step(); } diff --git a/crates/pumpkin/src/entity/boss/wither.rs b/crates/pumpkin/src/entity/boss/wither.rs index 0170aa4b8..cdb3a7478 100644 --- a/crates/pumpkin/src/entity/boss/wither.rs +++ b/crates/pumpkin/src/entity/boss/wither.rs @@ -606,7 +606,7 @@ impl Mob for WitherEntity { ); } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.tick_wither(); } diff --git a/crates/pumpkin/src/entity/breath.rs b/crates/pumpkin/src/entity/breath.rs index 03779d726..5a2d6fcd4 100644 --- a/crates/pumpkin/src/entity/breath.rs +++ b/crates/pumpkin/src/entity/breath.rs @@ -8,7 +8,6 @@ use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::java::client::play::Metadata; use pumpkin_util::GameMode; use pumpkin_util::math::position::BlockPos; -use std::sync::Arc; use std::sync::atomic::{AtomicI32, Ordering}; pub const MAX_AIR: i32 = 300; @@ -32,7 +31,7 @@ impl Default for BreathManager { } impl BreathManager { - pub fn tick(&self, player: &Arc) { + pub fn tick(&self, player: &Player) { let mode = player.gamemode.load(); if matches!(mode, GameMode::Creative | GameMode::Spectator) { @@ -84,11 +83,9 @@ impl BreathManager { if t >= DROWNING_INTERVAL { self.drowning_tick.store(0, Ordering::Relaxed); - player.living_entity.damage( - player.as_ref(), - DROWNING_DAMAGE, - DamageType::DROWN, - ); + player + .living_entity + .damage(player, DROWNING_DAMAGE, DamageType::DROWN); } } } else { diff --git a/crates/pumpkin/src/entity/decoration/display.rs b/crates/pumpkin/src/entity/decoration/display.rs index 79744e24a..151698061 100644 --- a/crates/pumpkin/src/entity/decoration/display.rs +++ b/crates/pumpkin/src/entity/decoration/display.rs @@ -808,7 +808,7 @@ impl EntityBase for BlockDisplayEntity { } } - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) {} + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {} fn init_data_tracker(&self) { self.display.init_display_data_tracker(); @@ -949,7 +949,7 @@ impl EntityBase for ItemDisplayEntity { } } - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) {} + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {} fn init_data_tracker(&self) { self.display.init_display_data_tracker(); @@ -1264,7 +1264,7 @@ impl EntityBase for TextDisplayEntity { self.flags.store(flags, Ordering::Relaxed); } - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) {} + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {} fn init_data_tracker(&self) { self.display.init_display_data_tracker(); diff --git a/crates/pumpkin/src/entity/decoration/item_frame.rs b/crates/pumpkin/src/entity/decoration/item_frame.rs index a9ec34ad7..314c9cddb 100644 --- a/crates/pumpkin/src/entity/decoration/item_frame.rs +++ b/crates/pumpkin/src/entity/decoration/item_frame.rs @@ -2,19 +2,19 @@ use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Mutex}; use crate::entity::player::Player; -use crate::entity::{Entity, EntityBase, EntityBaseFuture, living::LivingEntity}; +use crate::entity::{Entity, EntityBase, living::LivingEntity}; use crossbeam::atomic::AtomicCell; use pumpkin_data::BlockDirection; use pumpkin_data::damage::DamageType; use pumpkin_data::entity::EntityType; use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; -use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_data::sound::Sound; use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_protocol::java::client::play::{CSetEntityMetadata, Metadata}; use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::version::JavaMinecraftVersion; /// An item frame or glow item frame. /// @@ -359,46 +359,39 @@ impl EntityBase for ItemFrameEntity { ); } - fn send_java_spawn_packet<'a>( - &'a self, - client: &'a crate::net::java::JavaClient, - ) -> EntityBaseFuture<'a, ()> { - Box::pin(async move { - let spawn_packet = self.entity.create_spawn_packet(); - if let Ok(data) = client.serialize_packet(&spawn_packet) { - client.enqueue_packet(data).await; - } + fn send_java_spawn_packet(&self, client: &crate::net::java::JavaClient) { + let spawn_packet = self.entity.create_spawn_packet(); + if let Ok(data) = client.serialize_packet(&spawn_packet) { + client.try_enqueue_packet(data); + } - let ver = client.version.load(); - if ver >= CURRENT_MC_VERSION { - let item_serializer = ItemStackSerializer::from( - self.item_stack - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .clone(), - ); - let rotation = self.get_rotation() as i32; + let ver = client.version.load(); + if ver >= JavaMinecraftVersion::V_1_21 { + let item_serializer = ItemStackSerializer::from( + self.item_stack + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(), + ); + let rotation = self.get_rotation() as i32; - let mut data = Vec::new(); - let meta_item = Metadata::new( - pumpkin_data::tracked_data::item_frame::ITEM, - item_serializer, - ); - let meta_rot = - Metadata::new(pumpkin_data::tracked_data::item_frame::ROTATION, rotation); + let mut data = Vec::new(); + let meta_item = Metadata::new( + pumpkin_data::tracked_data::item_frame::ITEM, + item_serializer, + ); + let meta_rot = + Metadata::new(pumpkin_data::tracked_data::item_frame::ROTATION, rotation); - if meta_item.write(&mut data, &ver).is_ok() - && meta_rot.write(&mut data, &ver).is_ok() - { - data.push(255); - let meta_packet = - CSetEntityMetadata::new(self.entity.entity_id.into(), data.into()); - if let Ok(meta_data) = client.serialize_packet(&meta_packet) { - client.enqueue_packet(meta_data).await; - } + if meta_item.write(&mut data, &ver).is_ok() && meta_rot.write(&mut data, &ver).is_ok() { + data.push(255); + let meta_packet = + CSetEntityMetadata::new(self.entity.entity_id.into(), data.into()); + if let Ok(meta_data) = client.serialize_packet(&meta_packet) { + client.try_enqueue_packet(meta_data); } } - }) + } } fn interact(&self, player: &Arc, item_stack: &mut ItemStack) -> bool { diff --git a/crates/pumpkin/src/entity/decoration/leash_knot.rs b/crates/pumpkin/src/entity/decoration/leash_knot.rs index f32c99766..7c6a92f13 100644 --- a/crates/pumpkin/src/entity/decoration/leash_knot.rs +++ b/crates/pumpkin/src/entity/decoration/leash_knot.rs @@ -93,7 +93,7 @@ impl EntityBase for LeashKnotEntity { None } - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) { + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) { let world = self.entity.world.load(); let block = world.get_block(&self.pos); if !block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_FENCES) { diff --git a/crates/pumpkin/src/entity/experience_orb.rs b/crates/pumpkin/src/entity/experience_orb.rs index e8a3a4828..b6cadffdf 100644 --- a/crates/pumpkin/src/entity/experience_orb.rs +++ b/crates/pumpkin/src/entity/experience_orb.rs @@ -66,7 +66,7 @@ impl ExperienceOrbEntity { } impl EntityBase for ExperienceOrbEntity { - fn tick(&self, caller: &Arc, server: &Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { let entity = &self.entity; entity.tick(caller, server); let bounding_box = entity.bounding_box.load(); diff --git a/crates/pumpkin/src/entity/falling.rs b/crates/pumpkin/src/entity/falling.rs index a487b6976..03c01b8db 100644 --- a/crates/pumpkin/src/entity/falling.rs +++ b/crates/pumpkin/src/entity/falling.rs @@ -46,7 +46,7 @@ impl FallingEntity { } impl EntityBase for FallingEntity { - fn tick(&self, caller: &Arc, server: &Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { let entity = &self.entity; let mut velo = entity.velocity.load(); velo.y -= self.get_gravity(); diff --git a/crates/pumpkin/src/entity/hunger.rs b/crates/pumpkin/src/entity/hunger.rs index 061696b03..7331e33b4 100644 --- a/crates/pumpkin/src/entity/hunger.rs +++ b/crates/pumpkin/src/entity/hunger.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use super::{NBTStorage, NBTStorageInit, player::Player}; use crossbeam::atomic::AtomicCell; use pumpkin_data::damage::DamageType; @@ -29,7 +27,7 @@ impl Default for HungerManager { } impl HungerManager { - pub fn tick(&self, player: &Arc) { + pub fn tick(&self, player: &Player) { let mut level = self.level.load(); let mut saturation = self.saturation.load(); let mut exhaustion = self.exhaustion.load(); @@ -109,7 +107,7 @@ impl HungerManager { if damage_amount > 0.0 { player .living_entity - .damage(player.as_ref(), damage_amount, DamageType::STARVE); + .damage(player, damage_amount, DamageType::STARVE); } } diff --git a/crates/pumpkin/src/entity/interaction.rs b/crates/pumpkin/src/entity/interaction.rs index 74fa7d3e7..c2d0a669b 100644 --- a/crates/pumpkin/src/entity/interaction.rs +++ b/crates/pumpkin/src/entity/interaction.rs @@ -236,7 +236,7 @@ impl EntityBase for InteractionEntity { } } - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) {} + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {} fn init_data_tracker(&self) { let width = *self diff --git a/crates/pumpkin/src/entity/item.rs b/crates/pumpkin/src/entity/item.rs index a7df8b122..7aa64657b 100644 --- a/crates/pumpkin/src/entity/item.rs +++ b/crates/pumpkin/src/entity/item.rs @@ -1,11 +1,10 @@ use crate::entity::player::statistics::StatisticCategory; -use crate::{entity::EntityBaseFuture, server::Server}; +use crate::server::Server; use core::f32; use pumpkin_data::damage::DamageType; use pumpkin_data::data_component_impl::DamageResistantImpl; use pumpkin_data::data_component_impl::DamageResistantType; use pumpkin_data::item_stack::ItemStack; -use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::bedrock::client::CAddItemActor; use pumpkin_protocol::bedrock::network_item::ItemStackWrapper; @@ -15,6 +14,7 @@ use pumpkin_protocol::codec::var_ulong::VarULong; use pumpkin_protocol::java::client::play::{CSetEntityMetadata, Metadata}; use pumpkin_util::math::atomic_f32::AtomicF32; use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::version::JavaMinecraftVersion; use std::sync::atomic::Ordering::{AcqRel, Relaxed}; use std::sync::{ @@ -142,10 +142,10 @@ impl ItemEntity { let world = self.entity.world.load(); let entities = world.entities.load(); - let items: Vec> = entities + let items: Vec<&Self> = entities .iter() .filter_map(|entity: &Arc| { - entity.clone().get_item_entity().filter(|item| { + entity.get_item_entity().filter(|item| { item.entity.entity_id != self.entity.entity_id && !item.never_despawn.load(Ordering::Relaxed) && item.entity.bounding_box.load().intersects(&bounding_box) @@ -158,7 +158,7 @@ impl ItemEntity { if let Some(this_base) = world.get_entity_by_id(self.entity.entity_id) && let Some(this_item) = this_base.get_item_entity() { - this_item.try_merge_with(&item); + this_item.try_merge_with(item); } if self.entity.removed.load(Ordering::SeqCst) { @@ -343,7 +343,7 @@ impl ItemEntity { fn move_and_apply_friction( &self, - caller: &Arc, + caller: &dyn EntityBase, server: &Server, move_velo: Vector3, ) { @@ -415,7 +415,7 @@ impl ItemEntity { true } - fn sync_motion_if_dirty(&self, caller: &Arc, original_velo: Vector3) { + fn sync_motion_if_dirty(&self, caller: &dyn EntityBase, original_velo: Vector3) { let entity = &self.entity; entity.update_fluid_state(caller); @@ -443,7 +443,7 @@ impl ItemEntity { } impl EntityBase for ItemEntity { - fn tick(&self, caller: &Arc, server: &Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { let entity = &self.entity; self.decrement_pickup_delay(); @@ -591,7 +591,7 @@ impl EntityBase for ItemEntity { None } - fn get_item_entity(self: Arc) -> Option> { + fn get_item_entity(&self) -> Option<&ItemEntity> { Some(self) } @@ -646,65 +646,55 @@ impl EntityBase for ItemEntity { self } - fn send_bedrock_spawn_packet<'a>( - &'a self, - client: &'a crate::net::bedrock::BedrockClient, - ) -> EntityBaseFuture<'a, ()> { - Box::pin(async move { - let entity = &self.entity; - let runtime_id = entity.entity_id as u64; - let data = { - let item_stack = self - .item_stack - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let packet = CAddItemActor { - target_actor_id: VarLong(runtime_id as i64), - target_runtime_id: VarULong(runtime_id), - item: ItemStackWrapper::from(&*item_stack), - position: entity.pos.load().to_f32_lossy(), - velocity: entity.velocity.load().to_f32_lossy(), - entity_data: entity.bedrock_metadata(), - is_from_fishing: false, - }; - client.serialize_packet(&packet).ok() + fn send_bedrock_spawn_packet(&self, client: &crate::net::bedrock::BedrockClient) { + let entity = &self.entity; + let runtime_id = entity.entity_id as u64; + let data = { + let item_stack = self + .item_stack + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let packet = CAddItemActor { + target_actor_id: VarLong(runtime_id as i64), + target_runtime_id: VarULong(runtime_id), + item: ItemStackWrapper::from(&*item_stack), + position: entity.pos.load().to_f32_lossy(), + velocity: entity.velocity.load().to_f32_lossy(), + entity_data: entity.bedrock_metadata(), + is_from_fishing: false, }; - if let Some(data) = data { - client.send_game_packet(data).await; - } - }) + client.serialize_packet(&packet).ok() + }; + if let Some(data) = data { + client.try_enqueue_packet(data); + } } - fn send_java_spawn_packet<'a>( - &'a self, - client: &'a crate::net::java::JavaClient, - ) -> EntityBaseFuture<'a, ()> { - Box::pin(async move { - let spawn_packet = self.entity.create_spawn_packet(); - if let Ok(data) = client.serialize_packet(&spawn_packet) { - client.enqueue_packet(data).await; - } + fn send_java_spawn_packet(&self, client: &crate::net::java::JavaClient) { + let spawn_packet = self.entity.create_spawn_packet(); + if let Ok(data) = client.serialize_packet(&spawn_packet) { + client.try_enqueue_packet(data); + } - if client.version.load() >= CURRENT_MC_VERSION { - let metadata = Metadata::new( - pumpkin_data::tracked_data::item::ITEM, - ItemStackSerializer::from( - self.item_stack - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .clone(), - ), - ); - let mut data = Vec::new(); - if metadata.write(&mut data, &client.version.load()).is_ok() { - data.push(255); - let meta_packet = - CSetEntityMetadata::new(self.entity.entity_id.into(), data.into()); - if let Ok(meta_data) = client.serialize_packet(&meta_packet) { - client.enqueue_packet(meta_data).await; - } + if client.version.load() >= JavaMinecraftVersion::V_1_21 { + let metadata = Metadata::new( + pumpkin_data::tracked_data::item::ITEM, + ItemStackSerializer::from( + self.item_stack + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(), + ), + ); + let mut data = Vec::new(); + if metadata.write(&mut data, &client.version.load()).is_ok() { + data.push(255); + let meta_packet = + CSetEntityMetadata::new(self.entity.entity_id.into(), data.into()); + if let Ok(meta_data) = client.serialize_packet(&meta_packet) { + client.try_enqueue_packet(meta_data); } } - }) + } } } diff --git a/crates/pumpkin/src/entity/lightning.rs b/crates/pumpkin/src/entity/lightning.rs index 4ad752d81..6b144268b 100644 --- a/crates/pumpkin/src/entity/lightning.rs +++ b/crates/pumpkin/src/entity/lightning.rs @@ -192,7 +192,7 @@ impl LightningBoltEntity { } impl EntityBase for LightningBoltEntity { - fn tick(&self, _caller: &Arc, _server: &Server) { + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) { let entity = &self.entity; let life = self.life.load(Ordering::Relaxed); diff --git a/crates/pumpkin/src/entity/living.rs b/crates/pumpkin/src/entity/living.rs index 0185ea366..bc95f055c 100644 --- a/crates/pumpkin/src/entity/living.rs +++ b/crates/pumpkin/src/entity/living.rs @@ -958,7 +958,7 @@ impl LivingEntity { .has_tag(&tag::EntityType::MINECRAFT_FALL_DAMAGE_IMMUNE) } - fn get_effective_gravity(&self, caller: &Arc) -> f64 { + fn get_effective_gravity(&self, caller: &dyn EntityBase) -> f64 { let final_gravity = caller.get_gravity(); if self.entity.velocity.load().y <= 0.0 && self.has_effect(&StatusEffect::SLOW_FALLING) { @@ -986,7 +986,7 @@ impl LivingEntity { world.broadcast_editioned(&je_packet, &be_packet); } - fn tick_movement(&self, server: &Server, caller: &Arc) { + fn tick_movement(&self, server: &Server, caller: &dyn EntityBase) { if self.jumping_cooldown.load(Relaxed) != 0 { self.jumping_cooldown.fetch_sub(1, Relaxed); } @@ -1063,11 +1063,11 @@ impl LivingEntity { let suffocating = self.entity.tick_block_collisions(caller, server); if suffocating { - caller.damage(&**caller, 1.0, DamageType::IN_WALL); + caller.damage(caller, 1.0, DamageType::IN_WALL); } } - fn travel_in_air<'a>(&'a self, caller: &'a Arc) { + fn travel_in_air(&self, caller: &dyn EntityBase) { // applyMovementInput let effective_speed = self.get_attribute_value(&Attributes::MOVEMENT_SPEED); @@ -1104,7 +1104,7 @@ impl LivingEntity { let mut velo = self.entity.velocity.load(); let can_powder_snow_climb = if self.entity.was_in_powder_snow.load(Relaxed) { - crate::block::blocks::powder_snow::can_entity_walk_on_powder_snow(caller.as_ref()) + crate::block::blocks::powder_snow::can_entity_walk_on_powder_snow(caller) } else { false }; @@ -1144,7 +1144,7 @@ impl LivingEntity { self.entity.velocity.store(velo); } - fn travel_in_fluid<'a>(&'a self, caller: &'a Arc, water: bool) { + fn travel_in_fluid(&self, caller: &dyn EntityBase, water: bool) { let movement_input = self.movement_input.load(); let falling = self.entity.velocity.load().y <= 0.0; @@ -1240,7 +1240,7 @@ impl LivingEntity { } } - fn make_move<'a>(&'a self, caller: &'a Arc) { + fn make_move(&self, caller: &dyn EntityBase) { self.entity.move_entity(caller, self.entity.velocity.load()); self.check_climbing(); @@ -2635,7 +2635,7 @@ impl EntityBase for LivingEntity { } #[allow(clippy::too_many_lines)] - fn tick(&self, caller: &Arc, server: &Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.entity.tick(caller, server); // Only tick movement if the entity is alive. This prevents a dead "corpse" @@ -2646,7 +2646,7 @@ impl EntityBase for LivingEntity { if is_alive || (in_death_animation && self.entity.entity_type != &EntityType::PLAYER) { self.tick_movement(server, caller); // Vanilla-like order: freeze logic runs after movement/collisions. - self.entity.tick_frozen(caller.as_ref()); + self.entity.tick_frozen(caller); } // TODO @@ -2670,14 +2670,9 @@ impl EntityBase for LivingEntity { let world = self.entity.world.load_full(); let (block, state) = world.get_block_and_state(&supporting); - world.block_registry.on_entity_step( - block, - &world, - caller.as_ref() as &dyn EntityBase, - &supporting, - state, - false, - ); + world + .block_registry + .on_entity_step(block, &world, caller, &supporting, state, false); // Check slightly below supporting_pos for additional supporting blocks (blocks under carpets and the like) if !block.is_solid() { @@ -2688,7 +2683,7 @@ impl EntityBase for LivingEntity { world.block_registry.on_entity_step( below_block, &world, - caller.as_ref() as &dyn EntityBase, + caller, &below_supporting, below_state, true, // below supporting block @@ -2702,128 +2697,123 @@ impl EntityBase for LivingEntity { if self.item_use_time.load(Ordering::Relaxed) > 0 && self.item_use_time.fetch_sub(1, Ordering::Relaxed) <= 1 { - let caller_clone = caller.clone(); - let entity_id = self.entity.entity_id; - let world = self.entity.world.load_full(); - tokio::spawn(async move { - if let Some(entity) = world.get_entity_by_id(entity_id) - && let Some(living) = entity.get_living_entity() + let item_in_use = self + .item_in_use + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(); + if let Some(item) = item_in_use.as_ref() { + // Consume item + let mut is_potion = false; + if let Some(food) = item.get_data_component::() + && let Some(player) = caller.get_player() { - let item_in_use = living - .item_in_use - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .clone(); - if let Some(item) = item_in_use.as_ref() { - // Consume item - let mut is_potion = false; - if let Some(food) = item.get_data_component::() - && let Some(player) = caller_clone.get_player() - { - player.hunger_manager.eat( - player, - food.nutrition as u8, - food.saturation, - ); - } + player + .hunger_manager + .eat(player, food.nutrition as u8, food.saturation); + } - living.apply_consumable_effects(&caller_clone, item).await; + self.apply_consumable_effects(caller, item); - // Handle potion consumption - if item.get_data_component::().is_some() { - let effects = crate::item::potion::PotionContents::read_potion_effects(item); - crate::item::potion::PotionContents::apply_effects_to(living, effects, 1.0, crate::item::potion::PotionApplicationSource::Normal); - is_potion = true; - } + // Handle potion consumption + if item + .get_data_component::() + .is_some() + { + let effects = crate::item::potion::PotionContents::read_potion_effects(item); + crate::item::potion::PotionContents::apply_effects_to( + self, + effects, + 1.0, + crate::item::potion::PotionApplicationSource::Normal, + ); + is_potion = true; + } - if let Some(player) = caller_clone.get_player() { - player.trigger_advancement( - crate::entity::player::advancement::trigger::AdvancementTrigger::ConsumeItem { - item_id: format!("minecraft:{}", item.item.registry_key), - }, - ); + if let Some(player) = caller.get_player() { + player.trigger_advancement( + crate::entity::player::advancement::trigger::AdvancementTrigger::ConsumeItem { + item_id: format!("minecraft:{}", item.item.registry_key), + }, + ); - // Prefer modifying the exact stack that matches the consumed item: - // 1) selected hotbar (held_item) - // 2) off-hand - // 3) fallback to active_hand if the above didn't match - let mut handled = false; + // Prefer modifying the exact stack that matches the consumed item: + // 1) selected hotbar (held_item) + // 2) off-hand + // 3) fallback to active_hand if the above didn't match + let mut handled = false; - // Check main hand (hotbar selected) - let mut held = player.inventory.held_item(); - if held.are_items_and_components_equal(item) { - if is_potion { - if player.gamemode.load() != GameMode::Creative { - held.decrement(1); - if held.is_empty() { - held = ItemStack::new(1, &Item::GLASS_BOTTLE); - } - } - } else { - held.decrement_unless_creative(player.gamemode.load(), 1); - } - player.inventory.set_held_item(held); - handled = true; - } - - if !handled { - // Check off-hand - let mut off_hand = player.inventory.off_hand_item(); - if off_hand.are_items_and_components_equal(item) { - if is_potion { - if player.gamemode.load() != GameMode::Creative { - off_hand.decrement(1); - if off_hand.is_empty() { - off_hand = ItemStack::new(1, &Item::GLASS_BOTTLE); - } - } - } else { - off_hand - .decrement_unless_creative(player.gamemode.load(), 1); - } - player.inventory.set_stack_in_hand(Hand::Left, off_hand); - handled = true; + // Check main hand (hotbar selected) + let mut held = player.inventory.held_item(); + if held.are_items_and_components_equal(item) { + if is_potion { + if player.gamemode.load() != GameMode::Creative { + held.decrement(1); + if held.is_empty() { + held = ItemStack::new(1, &Item::GLASS_BOTTLE); } } - - if !handled { - // Use stored active_hand (as a fallback) - let active_hand = *living - .active_hand - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let hand_to_modify = active_hand.unwrap_or(Hand::Right); - let mut item_stack = - living.get_stack_in_hand(caller_clone.as_ref(), hand_to_modify); - - if is_potion { - if player.gamemode.load() != GameMode::Creative { - item_stack.decrement(1); - if item_stack.is_empty() { - item_stack = ItemStack::new(1, &Item::GLASS_BOTTLE); - } - } - } else { - item_stack.decrement_unless_creative(player.gamemode.load(), 1); - } - player - .inventory - .set_stack_in_hand(hand_to_modify, item_stack); - } - - if let Some(cooldown) = item.get_use_cooldown() { - let group = cooldown - .cooldown_group - .clone() - .unwrap_or_else(|| item.item.registry_key.to_string()); - player.start_cooldown(group, (cooldown.seconds * 20.0) as i32); - } + } else { + held.decrement_unless_creative(player.gamemode.load(), 1); } + player.inventory.set_held_item(held); + handled = true; + } - living.clear_active_hand(); + if !handled { + // Check off-hand + let mut off_hand = player.inventory.off_hand_item(); + if off_hand.are_items_and_components_equal(item) { + if is_potion { + if player.gamemode.load() != GameMode::Creative { + off_hand.decrement(1); + if off_hand.is_empty() { + off_hand = ItemStack::new(1, &Item::GLASS_BOTTLE); + } + } + } else { + off_hand.decrement_unless_creative(player.gamemode.load(), 1); + } + player.inventory.set_stack_in_hand(Hand::Left, off_hand); + handled = true; + } + } + + if !handled { + // Use stored active_hand (as a fallback) + let active_hand = *self + .active_hand + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let hand_to_modify = active_hand.unwrap_or(Hand::Right); + let mut item_stack = self.get_stack_in_hand(caller, hand_to_modify); + + if is_potion { + if player.gamemode.load() != GameMode::Creative { + item_stack.decrement(1); + if item_stack.is_empty() { + item_stack = ItemStack::new(1, &Item::GLASS_BOTTLE); + } + } + } else { + item_stack.decrement_unless_creative(player.gamemode.load(), 1); + } + player + .inventory + .set_stack_in_hand(hand_to_modify, item_stack); + } + + if let Some(cooldown) = item.get_use_cooldown() { + let group = cooldown + .cooldown_group + .clone() + .unwrap_or_else(|| item.item.registry_key.to_string()); + player.start_cooldown(group, (cooldown.seconds * 20.0) as i32); } } - }); + + self.clear_active_hand(); + } } if self.hurt_cooldown.load(Relaxed) > 0 { @@ -2873,7 +2863,7 @@ impl EntityBase for LivingEntity { impl LivingEntity { /// Applies data-driven `apply_effects` consume effects after an item completes use. /// Vanilla: `Consumable.onConsume` invokes every configured effect server-side. - async fn apply_consumable_effects(&self, caller: &Arc, item: &ItemStack) { + fn apply_consumable_effects(&self, caller: &dyn EntityBase, item: &ItemStack) { let Some(consumable) = item.get_data_component::() else { return; }; @@ -2927,8 +2917,7 @@ impl LivingEntity { if let Some(vehicle) = vehicle { vehicle .get_entity() - .remove_passenger_before_teleport(caller.get_entity().entity_id) - .await; + .remove_passenger_sync(caller.get_entity().entity_id); if caller.get_entity().has_vehicle() { continue; } @@ -2940,10 +2929,7 @@ impl LivingEntity { }; let (yaw, pitch) = (self.entity.yaw.load(), self.entity.pitch.load()); let world = self.entity.world.load_full(); - caller - .clone() - .teleport(pos, Some(yaw), Some(pitch), world.clone()) - .await; + caller.teleport(pos, Some(yaw), Some(pitch), world.clone()); let destination = self.entity.pos.load(); if destination != center { diff --git a/crates/pumpkin/src/entity/marker.rs b/crates/pumpkin/src/entity/marker.rs index 59283c9b2..9dafbaeb0 100644 --- a/crates/pumpkin/src/entity/marker.rs +++ b/crates/pumpkin/src/entity/marker.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, Mutex, atomic::Ordering}; use crate::{ - entity::{Entity, EntityBase, EntityBaseFuture, living::LivingEntity}, + entity::{Entity, EntityBase, living::LivingEntity}, net::{bedrock::BedrockClient, java::JavaClient}, server::Server, }; @@ -44,7 +44,7 @@ impl EntityBase for MarkerEntity { } } - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) {} + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {} fn init_data_tracker(&self) {} @@ -88,14 +88,7 @@ impl EntityBase for MarkerEntity { false } - fn send_java_spawn_packet<'a>(&'a self, _client: &'a JavaClient) -> EntityBaseFuture<'a, ()> { - Box::pin(async move {}) - } + fn send_java_spawn_packet(&self, _client: &JavaClient) {} - fn send_bedrock_spawn_packet<'a>( - &'a self, - _client: &'a BedrockClient, - ) -> EntityBaseFuture<'a, ()> { - Box::pin(async move {}) - } + fn send_bedrock_spawn_packet(&self, _client: &BedrockClient) {} } diff --git a/crates/pumpkin/src/entity/mob/bat.rs b/crates/pumpkin/src/entity/mob/bat.rs index 960d3c44c..df95a0d81 100644 --- a/crates/pumpkin/src/entity/mob/bat.rs +++ b/crates/pumpkin/src/entity/mob/bat.rs @@ -232,7 +232,7 @@ impl Mob for BatEntity { &self.mob_entity } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; let block_pos = entity.block_pos.load(); let above_pos = BlockPos::new(block_pos.0.x, block_pos.0.y + 1, block_pos.0.z); diff --git a/crates/pumpkin/src/entity/mob/creaking.rs b/crates/pumpkin/src/entity/mob/creaking.rs index 35444824b..2eae1db2a 100644 --- a/crates/pumpkin/src/entity/mob/creaking.rs +++ b/crates/pumpkin/src/entity/mob/creaking.rs @@ -421,7 +421,7 @@ impl Mob for CreakingEntity { &self.mob_entity } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; if !entity.is_alive() { return; diff --git a/crates/pumpkin/src/entity/mob/creeper.rs b/crates/pumpkin/src/entity/mob/creeper.rs index 5a739a0b4..90ff7f830 100644 --- a/crates/pumpkin/src/entity/mob/creeper.rs +++ b/crates/pumpkin/src/entity/mob/creeper.rs @@ -174,7 +174,7 @@ impl Mob for CreeperEntity { .on_lightning_strike(caller, lightning); } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; if !entity.is_alive() { return; diff --git a/crates/pumpkin/src/entity/mob/enderman.rs b/crates/pumpkin/src/entity/mob/enderman.rs index d734bff7c..d9973afc2 100644 --- a/crates/pumpkin/src/entity/mob/enderman.rs +++ b/crates/pumpkin/src/entity/mob/enderman.rs @@ -433,7 +433,7 @@ impl Mob for EndermanEntity { } // TODO: sunlight avoidance, carried block drop on death, angerable system, ambient sound override - fn mob_tick<'a>(&'a self, caller: &'a Arc) { + fn mob_tick(&self, caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; if !entity.is_alive() { return; @@ -443,7 +443,7 @@ impl Mob for EndermanEntity { let raining_at_feet = world.is_raining_at(&entity.block_pos.load()); let raining_at_head = world.is_raining_at(&entity.bounding_box.load().max_block_pos()); if entity.touching_water.load(Ordering::SeqCst) || raining_at_feet || raining_at_head { - caller.damage(caller.as_ref(), 1.0, DamageType::DROWN); + caller.damage(caller, 1.0, DamageType::DROWN); } } diff --git a/crates/pumpkin/src/entity/mob/evoker.rs b/crates/pumpkin/src/entity/mob/evoker.rs index 1ff9f2a0d..3c27da430 100644 --- a/crates/pumpkin/src/entity/mob/evoker.rs +++ b/crates/pumpkin/src/entity/mob/evoker.rs @@ -177,7 +177,7 @@ impl Mob for EvokerEntity { &self.mob_entity } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let ticks = self.spell_casting_tick_count.load(Ordering::Relaxed); if ticks > 0 { self.spell_casting_tick_count diff --git a/crates/pumpkin/src/entity/mob/magma_cube.rs b/crates/pumpkin/src/entity/mob/magma_cube.rs index 5eab67b9a..2c900f97c 100644 --- a/crates/pumpkin/src/entity/mob/magma_cube.rs +++ b/crates/pumpkin/src/entity/mob/magma_cube.rs @@ -21,7 +21,7 @@ impl Mob for MagmaCubeEntity { self.slime.get_mob_entity() } - fn mob_tick<'a>(&'a self, caller: &'a Arc) { + fn mob_tick(&self, caller: &dyn EntityBase) { self.slime.mob_tick(caller); } diff --git a/crates/pumpkin/src/entity/mob/mod.rs b/crates/pumpkin/src/entity/mob/mod.rs index 195399577..e97d8c693 100644 --- a/crates/pumpkin/src/entity/mob/mod.rs +++ b/crates/pumpkin/src/entity/mob/mod.rs @@ -634,7 +634,7 @@ pub trait Mob: EntityBase + Send + Sync { fn set_saddled(&self, _saddled: bool) {} /// Per-mob tick hook called each tick before AI runs. Override for mob-specific logic. - fn mob_tick<'a>(&'a self, _caller: &'a Arc) {} + fn mob_tick(&self, _caller: &dyn EntityBase) {} fn post_tick(&self) {} @@ -1008,7 +1008,7 @@ impl EntityBase for T { } #[allow(clippy::too_many_lines)] - fn tick(&self, caller: &Arc, server: &Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { let mob_entity = self.get_mob_entity(); mob_entity.living_entity.entity.tick_leash(); mob_entity.tick_sun_burn(); diff --git a/crates/pumpkin/src/entity/mob/piglin.rs b/crates/pumpkin/src/entity/mob/piglin.rs index 9f744f89e..4e9b1d62e 100644 --- a/crates/pumpkin/src/entity/mob/piglin.rs +++ b/crates/pumpkin/src/entity/mob/piglin.rs @@ -656,7 +656,7 @@ impl Mob for PiglinEntity { self.mob_entity.mob_interact(player, item_stack) } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; if !entity.is_alive() { return; diff --git a/crates/pumpkin/src/entity/mob/piglin_brute.rs b/crates/pumpkin/src/entity/mob/piglin_brute.rs index af49cfa83..d989cbd2b 100644 --- a/crates/pumpkin/src/entity/mob/piglin_brute.rs +++ b/crates/pumpkin/src/entity/mob/piglin_brute.rs @@ -216,7 +216,7 @@ impl Mob for PiglinBruteEntity { } } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; if !entity.is_alive() { return; diff --git a/crates/pumpkin/src/entity/mob/shulker.rs b/crates/pumpkin/src/entity/mob/shulker.rs index 579e053e2..f46dd2d9a 100644 --- a/crates/pumpkin/src/entity/mob/shulker.rs +++ b/crates/pumpkin/src/entity/mob/shulker.rs @@ -366,7 +366,7 @@ impl Mob for ShulkerEntity { 0.0 } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; if !entity.is_alive() { diff --git a/crates/pumpkin/src/entity/mob/slime.rs b/crates/pumpkin/src/entity/mob/slime.rs index c31c712ed..54d166aad 100644 --- a/crates/pumpkin/src/entity/mob/slime.rs +++ b/crates/pumpkin/src/entity/mob/slime.rs @@ -282,7 +282,7 @@ impl Mob for SlimeEntity { &self.entity } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.o_squish.store(self.squish.load()); self.squish .store(self.squish.load() + (self.target_squish.load() - self.squish.load()) * 0.5); diff --git a/crates/pumpkin/src/entity/mob/witch.rs b/crates/pumpkin/src/entity/mob/witch.rs index 8d4168f80..71415bede 100644 --- a/crates/pumpkin/src/entity/mob/witch.rs +++ b/crates/pumpkin/src/entity/mob/witch.rs @@ -227,7 +227,7 @@ impl Mob for WitchEntity { amount } - fn mob_tick<'a>(&'a self, caller: &'a Arc) { + fn mob_tick(&self, caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; let living = &self.mob_entity.living_entity; let world = entity.world.load(); diff --git a/crates/pumpkin/src/entity/mod.rs b/crates/pumpkin/src/entity/mod.rs index 06406c226..59575bfbc 100644 --- a/crates/pumpkin/src/entity/mod.rs +++ b/crates/pumpkin/src/entity/mod.rs @@ -21,7 +21,6 @@ use pumpkin_data::dimension::Dimension; use pumpkin_data::entity::EntityStatus; use pumpkin_data::fluid::Fluid; use pumpkin_data::item_stack::ItemStack; -use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_data::tag::{self, Taggable}; use pumpkin_data::tracked_data; use pumpkin_data::{Block, BlockDirection}; @@ -71,7 +70,6 @@ use pumpkin_util::text::TextComponent; use pumpkin_util::text::hover::HoverEvent; use pumpkin_util::version::JavaMinecraftVersion; use std::collections::{BTreeMap, HashSet}; -use std::pin::Pin; use std::sync::{ Arc, atomic::{ @@ -131,10 +129,6 @@ pub const fn equipment_break_status(slot: &EquipmentSlot) -> EntityStatus { } } -pub type EntityBaseFuture<'a, T> = Pin + Send + 'a>>; - -pub type TeleportFuture = Pin + Send>>; - pub trait EntityBase: Send + Sync + std::any::Any { fn write_nbt(&self, nbt: &mut NbtCompound) { self.get_entity().write_nbt(nbt); @@ -162,7 +156,7 @@ pub trait EntityBase: Send + Sync + std::any::Any { /// but in some scenarios (e.g., interactions or events), it might be a different entity. /// /// The `server` parameter provides access to the game server instance. - fn tick(&self, caller: &Arc, server: &Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { if let Some(living) = self.get_living_entity() { living.tick(caller, server); } else { @@ -215,20 +209,14 @@ pub trait EntityBase: Send + Sync + std::any::Any { } fn set_variant_name(&self, _name: &str) {} - // This method takes ownership of Arc, so the lifetime bounds are different. fn teleport( - self: Arc, + &self, position: Vector3, yaw: Option, pitch: Option, world: Arc, - ) -> TeleportFuture - where - Self: 'static, - { - Box::pin(async move { - self.get_entity().teleport(position, yaw, pitch, world); - }) + ) { + self.get_entity().teleport(position, yaw, pitch, world); } fn is_pushed_by_fluids(&self) -> bool { @@ -298,82 +286,75 @@ pub trait EntityBase: Send + Sync + std::any::Any { None } - fn send_bedrock_spawn_packet<'a>( - &'a self, - client: &'a BedrockClient, - ) -> EntityBaseFuture<'a, ()> { - Box::pin(async move { - let entity = self.get_entity(); - let runtime_id = entity.entity_id as u64; - let identifier = self - .get_mob() - .and_then(mob::Mob::mob_bedrock_identifier) - .unwrap_or(entity.entity_type.resource_name); - let mut metadata = entity.bedrock_metadata(); - if let Some(mob) = self.get_mob() - && let Some(mob_metadata) = mob.mob_bedrock_spawn_metadata() - { - metadata.0.extend(mob_metadata.0); - } - let packet = CAddActor { - target_actor_id: VarLong(runtime_id as i64), - target_runtime_id: VarULong(runtime_id), - actor_type: identifier.to_string(), - position: entity.pos.load().to_f32_lossy(), - velocity: entity.velocity.load().to_f32_lossy(), - rotation: Vector2::new(entity.pitch.load(), entity.yaw.load()), - y_head_rotation: entity.head_yaw.load(), - y_body_rotation: entity.body_yaw.load(), - attributes_list: Vec::new(), - actor_data: metadata, - synced_properties: PropertySyncData { - int_entries_list: std::collections::HashMap::new(), - float_entries_list: std::collections::HashMap::new(), - }, - actor_links: Vec::new(), - }; - if let Ok(data) = client.serialize_packet(&packet) { - client.send_game_packet(data).await; - } - }) + fn send_bedrock_spawn_packet(&self, client: &BedrockClient) { + let entity = self.get_entity(); + let runtime_id = entity.entity_id as u64; + let identifier = self + .get_mob() + .and_then(mob::Mob::mob_bedrock_identifier) + .unwrap_or(entity.entity_type.resource_name); + let mut metadata = entity.bedrock_metadata(); + if let Some(mob) = self.get_mob() + && let Some(mob_metadata) = mob.mob_bedrock_spawn_metadata() + { + metadata.0.extend(mob_metadata.0); + } + let packet = CAddActor { + target_actor_id: VarLong(runtime_id as i64), + target_runtime_id: VarULong(runtime_id), + actor_type: identifier.to_string(), + position: entity.pos.load().to_f32_lossy(), + velocity: entity.velocity.load().to_f32_lossy(), + rotation: Vector2::new(entity.pitch.load(), entity.yaw.load()), + y_head_rotation: entity.head_yaw.load(), + y_body_rotation: entity.body_yaw.load(), + attributes_list: Vec::new(), + actor_data: metadata, + synced_properties: PropertySyncData { + int_entries_list: std::collections::HashMap::new(), + float_entries_list: std::collections::HashMap::new(), + }, + actor_links: Vec::new(), + }; + if let Ok(data) = client.serialize_packet(&packet) { + client.try_enqueue_packet(data); + } } - fn send_java_spawn_packet<'a>(&'a self, client: &'a JavaClient) -> EntityBaseFuture<'a, ()> { - Box::pin(async move { - let entity = self.get_entity(); - let version = client.version.load(); - let is_mob = entity.entity_type.mob || self.get_mob().is_some(); - if version < JavaMinecraftVersion::V_1_19 && is_mob { - let metadata = self - .get_mob() - .and_then(|mob| mob.mob_java_spawn_metadata(version)); - let spawn_packet = entity.create_spawn_living_packet(metadata.clone()); - if let Ok(data) = client.serialize_packet(&spawn_packet) { - client.enqueue_packet(data).await; - } - if version >= JavaMinecraftVersion::V_1_15 - && let Some(meta) = metadata - { - let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), meta); - if let Ok(meta_data) = client.serialize_packet(&meta_packet) { - client.enqueue_packet(meta_data).await; - } - } - } else { - let spawn_packet = entity.create_spawn_packet(); - if let Ok(data) = client.serialize_packet(&spawn_packet) { - client.enqueue_packet(data).await; - } - if let Some(mob) = self.get_mob() - && let Some(metadata) = mob.mob_java_spawn_metadata(version) - { - let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), metadata); - if let Ok(meta_data) = client.serialize_packet(&meta_packet) { - client.enqueue_packet(meta_data).await; - } + fn send_java_spawn_packet(&self, client: &JavaClient) { + let entity = self.get_entity(); + let version = client.version.load(); + let is_mob = entity.entity_type.mob || self.get_mob().is_some(); + if version < JavaMinecraftVersion::V_1_19 && is_mob { + let metadata = self + .get_mob() + .and_then(|mob| mob.mob_java_spawn_metadata(version)); + let spawn_packet = entity.create_spawn_living_packet(metadata.clone()); + if let Ok(data) = client.serialize_packet(&spawn_packet) { + client.try_enqueue_packet(data); + } + if version >= JavaMinecraftVersion::V_1_15 + && let Some(meta) = metadata + { + let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), meta); + if let Ok(meta_data) = client.serialize_packet(&meta_packet) { + client.try_enqueue_packet(meta_data); } } - }) + } else { + let spawn_packet = entity.create_spawn_packet(); + if let Ok(data) = client.serialize_packet(&spawn_packet) { + client.try_enqueue_packet(data); + } + if let Some(mob) = self.get_mob() + && let Some(metadata) = mob.mob_java_spawn_metadata(version) + { + let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), metadata); + if let Ok(meta_data) = client.serialize_packet(&meta_packet) { + client.try_enqueue_packet(meta_data); + } + } + } } fn damage_with_context( @@ -442,12 +423,12 @@ pub trait EntityBase: Send + Sync + std::any::Any { self.get_entity().has_passengers() } - fn has_passenger(&self, other: &Arc) -> bool { + fn has_passenger(&self, other: &dyn EntityBase) -> bool { self.get_entity() .has_passenger(other.get_entity().entity_id) } - fn move_entity(&self, caller: &Arc, motion: Vector3) { + fn move_entity(&self, caller: &dyn EntityBase, motion: Vector3) { self.get_entity().move_entity(caller, motion); } @@ -455,7 +436,7 @@ pub trait EntityBase: Send + Sync + std::any::Any { false } - fn push(&self, entity: &Arc) { + fn push(&self, entity: &dyn EntityBase) { let self_entity = self.get_entity(); let other_entity = entity.get_entity(); @@ -506,7 +487,7 @@ pub trait EntityBase: Send + Sync + std::any::Any { } #[allow(clippy::too_many_lines)] - fn push_entities(&self, dyn_self: &Arc) -> bool { + fn push_entities(&self, dyn_self: &dyn EntityBase) -> bool { let mut picked_up = false; let mut pushed = false; let self_entity = self.get_entity(); @@ -555,10 +536,9 @@ pub trait EntityBase: Send + Sync + std::any::Any { && !other.is_passenger() && other.is_pushable() && other.get_entity().riding_cooldown.load(Relaxed) == 0 + && let Some(self_arc) = world.get_entity_by_id(self_entity.entity_id) { - dyn_self - .get_entity() - .add_passenger(dyn_self.clone(), other.clone()); + self_entity.add_passenger(self_arc, other.clone()); picked_up = true; break; } @@ -582,14 +562,14 @@ pub trait EntityBase: Send + Sync + std::any::Any { || !other.get_entity().has_vehicle()) && other.is_pushable() { - dyn_self.push(&other); + dyn_self.push(other.as_ref()); pushed = true; } - } else if !self.has_passenger(&other) + } else if !self.has_passenger(other.as_ref()) && other.is_pushable() && is_other_minecart { - dyn_self.push(&other); + dyn_self.push(other.as_ref()); pushed = true; } } @@ -598,8 +578,7 @@ pub trait EntityBase: Send + Sync + std::any::Any { let players = world.get_players_at_box(&push_bb); for player in players { if player.get_entity().entity_id != self_entity.entity_id && is_rideable_minecart { - let player_base: Arc = player.clone(); - dyn_self.push(&player_base); + dyn_self.push(player.as_ref()); pushed = true; // Non-rideable minecarts (hoppers, chests) do not push players in vanilla. } @@ -608,7 +587,7 @@ pub trait EntityBase: Send + Sync + std::any::Any { let other_entities = world.get_entities_at_box(&entity_bb); for other in other_entities { if other.get_entity().entity_id != self_entity.entity_id { - dyn_self.push(&other); + dyn_self.push(other.as_ref()); pushed = true; } } @@ -616,8 +595,7 @@ pub trait EntityBase: Send + Sync + std::any::Any { let players = world.get_players_at_box(&entity_bb); for player in players { if player.get_entity().entity_id != self_entity.entity_id { - let player_base: Arc = player.clone(); - dyn_self.push(&player_base); + dyn_self.push(player.as_ref()); pushed = true; } } @@ -652,7 +630,7 @@ pub trait EntityBase: Send + Sync + std::any::Any { fn cast_any(&self) -> &dyn std::any::Any; - fn get_item_entity(self: Arc) -> Option> { + fn get_item_entity(&self) -> Option<&ItemEntity> { None } @@ -1524,14 +1502,14 @@ impl Entity { } #[expect(dead_code)] - fn tick_block_underneath(_caller: &Arc) { + fn tick_block_underneath(_caller: &dyn EntityBase) { // let world = self.world.read(); // let (pos, block, state) = self.get_block_with_y_offset(0.2); // world // .block_registry - // .on_stepped_on(&world, caller.as_ref(), pos, block, state) + // .on_stepped_on(&world, caller, pos, block, state) // ; // TODO: Add this to on_stepped_on @@ -1587,7 +1565,7 @@ impl Entity { */ } - pub fn tick_block_collisions(&self, caller: &Arc, _server: &Server) -> bool { + pub fn tick_block_collisions(&self, caller: &dyn EntityBase, _server: &Server) -> bool { if !self.is_affected_by_blocks() { return false; } @@ -1630,10 +1608,7 @@ impl Entity { ); let collision_shape = if block == &Block::POWDER_SNOW { - crate::block::blocks::powder_snow::inside_collision_shape_for_entity( - caller.as_ref(), - &pos, - ) + crate::block::blocks::powder_snow::inside_collision_shape_for_entity(caller, &pos) } else { world .block_registry @@ -1648,7 +1623,7 @@ impl Entity { world.block_registry.on_entity_collision( block, &world, - caller.as_ref(), + caller, &pos, state, &server_arc, @@ -1940,7 +1915,7 @@ impl Entity { // updateWaterState() in yarn - fn update_fluid_state(&self, caller: &Arc) { + fn update_fluid_state(&self, caller: &dyn EntityBase) { let is_pushed = caller.is_pushed_by_fluids(); let mut fluids = BTreeMap::new(); @@ -2018,7 +1993,7 @@ impl Entity { for (_, fluid) in fluids { world .block_registry - .on_entity_collision_fluid(fluid, caller.as_ref()); + .on_entity_collision_fluid(fluid, caller); } let lava_speed = if world.dimension == Dimension::THE_NETHER { @@ -2220,7 +2195,7 @@ impl Entity { // Move by a delta, adjust for collisions, and send // Does not send movement. That must be done separately - pub fn move_entity(&self, caller: &Arc, mut motion: Vector3) { + pub fn move_entity(&self, caller: &dyn EntityBase, mut motion: Vector3) { if caller.get_player().is_some() { return; } @@ -2245,7 +2220,7 @@ impl Entity { self.velocity.store(Vector3::default()); } - let final_move = self.adjust_movement_for_collisions(motion, caller.as_ref()); + let final_move = self.adjust_movement_for_collisions(motion, caller); self.move_pos(final_move); @@ -2255,7 +2230,7 @@ impl Entity { if let Some(living) = caller.get_living_entity() { let on_ground = self.on_ground.load(Ordering::SeqCst); - living.fall(caller.as_ref(), final_move.y, on_ground, false); + living.fall(caller, final_move.y, on_ground, false); } if motion.y != final_move.y { @@ -2263,7 +2238,7 @@ impl Entity { let block = self.get_block_with_y_offset(0.2).1; world .block_registry - .update_entity_movement_after_fall_on(block, caller.as_ref()); + .update_entity_movement_after_fall_on(block, caller); } } @@ -2322,7 +2297,7 @@ impl Entity { self.velocity.store(velo); } - fn tick_portal(&self, caller: &Arc) { + fn tick_portal(&self, caller: &dyn EntityBase) { if self.portal_cooldown.load(Ordering::Relaxed) > 0 { self.portal_cooldown.fetch_sub(1, Ordering::Relaxed); } @@ -2331,15 +2306,10 @@ impl Entity { }; let mut should_remove = false; if let Some(portal_processor) = manager_guard.as_mut() { - if portal_processor.process_portal_teleportation( - &self.world.load(), - caller.as_ref(), - true, - ) { + if portal_processor.process_portal_teleportation(&self.world.load(), caller, true) { self.portal_cooldown .store(self.default_portal_cooldown(), Ordering::Relaxed); - let caller_clone = caller.clone(); let world_clone = self.world.load_full(); let portal_type = portal_processor.portal_type; let dest_world_opt = portal_processor.destination_world.clone(); @@ -2348,21 +2318,17 @@ impl Entity { let entity_id = self.entity_id; let yaw = self.yaw.load(); - tokio::spawn(async move { - let world_for_dest = world_clone.clone(); - let caller_for_dest = caller_clone.clone(); - let (tx, rx) = tokio::sync::oneshot::channel(); - rayon::spawn(move || { - let dest = portal_type.get_portal_destination( - &world_for_dest, - dest_world_opt, - &caller_for_dest, - entry_pos, - src_portal.as_ref(), - ); - let _ = tx.send(dest); - }); - let transition = rx.await.ok().flatten(); + rayon::spawn(move || { + let Some(entity_arc) = world_clone.get_entity_by_id(entity_id) else { + return; + }; + let transition = portal_type.get_portal_destination( + &world_clone, + dest_world_opt, + entity_arc.as_ref(), + entry_pos, + src_portal.as_ref(), + ); if let Some(transition) = transition { let dest_world = transition.new_world.clone(); @@ -2371,21 +2337,16 @@ impl Entity { let teleport_pos = transition.position; // Teleport the main entity - caller_clone - .teleport(teleport_pos, yaw_val, pitch, dest_world.clone()) - .await; + entity_arc.teleport(teleport_pos, yaw_val, pitch, dest_world.clone()); // Teleport all passengers recursively along with the vehicle - if let Some(entity) = world_clone.get_entity_by_id(entity_id) { - let yaw_delta = yaw_val.map(|y| y - yaw); - Self::teleport_passengers_recursive( - entity.get_entity(), - teleport_pos, - yaw_delta, - &dest_world, - ) - .await; - } + let yaw_delta = yaw_val.map(|y| y - yaw); + Self::teleport_passengers_recursive( + entity_arc.get_entity(), + teleport_pos, + yaw_delta, + &dest_world, + ); } }); } else if portal_processor.portal_time == 0 { @@ -2398,50 +2359,40 @@ impl Entity { } /// Recursively teleports all passengers (and their passengers) to the destination - fn teleport_passengers_recursive<'a>( - entity: &'a Self, + fn teleport_passengers_recursive( + entity: &Self, position: Vector3, yaw_delta: Option, - dest_world: &'a Arc, - ) -> std::pin::Pin + Send + 'a>> { - Box::pin(async move { - let passengers = entity + dest_world: &Arc, + ) { + let passengers = entity + .passengers + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(); + for passenger in passengers { + let passenger_entity = passenger.get_entity(); + let passenger_yaw = yaw_delta.map(|delta| passenger_entity.yaw.load() + delta); + passenger_entity.portal_cooldown.store( + passenger_entity.default_portal_cooldown(), + Ordering::Relaxed, + ); + + // Get nested passengers before teleporting + let nested_passengers = passenger_entity .passengers .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .clone(); - for passenger in passengers { - let passenger_entity = passenger.get_entity(); - let passenger_yaw = yaw_delta.map(|delta| passenger_entity.yaw.load() + delta); - passenger_entity.portal_cooldown.store( - passenger_entity.default_portal_cooldown(), - Ordering::Relaxed, - ); - // Get nested passengers before teleporting - let nested_passengers = passenger_entity - .passengers - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .clone(); + passenger.teleport(position, passenger_yaw, None, dest_world.clone()); - passenger - .teleport(position, passenger_yaw, None, dest_world.clone()) - .await; - - // Recursively teleport nested passengers - for nested in nested_passengers { - let nested_entity = nested.get_entity(); - Self::teleport_passengers_recursive( - nested_entity, - position, - yaw_delta, - dest_world, - ) - .await; - } + // Recursively teleport nested passengers + for nested in nested_passengers { + let nested_entity = nested.get_entity(); + Self::teleport_passengers_recursive(nested_entity, position, yaw_delta, dest_world); } - }) + } } pub fn try_use_portal(&self, _portal_delay: u32, portal_world: Arc, pos: BlockPos) { @@ -2986,7 +2937,7 @@ impl Entity { World::collect_java_recipients_by_version(java_recipients.into_iter()); for (version, recipients) in recipients_by_version { - if version < CURRENT_MC_VERSION { + if version < JavaMinecraftVersion::V_1_21 { continue; } let mut buf = Vec::new(); @@ -4060,7 +4011,7 @@ impl Entity { } impl EntityBase for Entity { - fn tick(&self, caller: &Arc, _server: &Server) { + fn tick(&self, caller: &dyn EntityBase, _server: &Server) { // Recomputed during movement/block-collision handling in the same tick. let was_in_powder_snow = self.is_in_powder_snow.load(Ordering::Relaxed); self.was_in_powder_snow @@ -4070,7 +4021,7 @@ impl EntityBase for Entity { self.update_last_pos(); self.tick_portal(caller); self.update_fluid_state(caller); - self.check_out_of_world(&**caller); + self.check_out_of_world(caller); let fire_ticks = self.fire_ticks.load(Ordering::Relaxed); // Check for fire immunity (or if the specific entity is) @@ -4083,7 +4034,7 @@ impl EntityBase for Entity { } } else { if fire_ticks % 20 == 0 { - caller.damage(&**caller, 1.0, DamageType::ON_FIRE); + caller.damage(caller, 1.0, DamageType::ON_FIRE); } self.fire_ticks.store(fire_ticks - 1, Ordering::Relaxed); @@ -4101,19 +4052,6 @@ impl EntityBase for Entity { } } - fn teleport( - self: Arc, - position: Vector3, - yaw: Option, - pitch: Option, - world: Arc, - ) -> TeleportFuture { - // TODO: handle world change - Box::pin(async move { - self.get_entity().teleport(position, yaw, pitch, world); - }) - } - fn get_entity(&self) -> &Entity { self } diff --git a/crates/pumpkin/src/entity/passive/armadillo.rs b/crates/pumpkin/src/entity/passive/armadillo.rs index 49e0e269b..9c7b28af6 100644 --- a/crates/pumpkin/src/entity/passive/armadillo.rs +++ b/crates/pumpkin/src/entity/passive/armadillo.rs @@ -349,7 +349,7 @@ impl Mob for ArmadilloEntity { } } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.ageable_ai_step(); self.in_state_ticks.fetch_add(1, Ordering::Relaxed); diff --git a/crates/pumpkin/src/entity/passive/chicken.rs b/crates/pumpkin/src/entity/passive/chicken.rs index c6147d365..d8a0db691 100644 --- a/crates/pumpkin/src/entity/passive/chicken.rs +++ b/crates/pumpkin/src/entity/passive/chicken.rs @@ -167,7 +167,7 @@ impl Mob for ChickenEntity { ); } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { if self.mob_entity.living_entity.dead.load(Relaxed) { return; } diff --git a/crates/pumpkin/src/entity/passive/fox.rs b/crates/pumpkin/src/entity/passive/fox.rs index b686f5c9a..7c163da6c 100644 --- a/crates/pumpkin/src/entity/passive/fox.rs +++ b/crates/pumpkin/src/entity/passive/fox.rs @@ -278,7 +278,7 @@ impl Mob for FoxEntity { self.set_variant(FoxVariant::from_name(name)); } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.ageable_ai_step(); } diff --git a/crates/pumpkin/src/entity/passive/frog.rs b/crates/pumpkin/src/entity/passive/frog.rs index 6ea373a79..8049297f5 100644 --- a/crates/pumpkin/src/entity/passive/frog.rs +++ b/crates/pumpkin/src/entity/passive/frog.rs @@ -172,7 +172,7 @@ impl Mob for FrogEntity { self.set_variant(FrogVariant::from_name(name)); } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.ageable_ai_step(); } diff --git a/crates/pumpkin/src/entity/passive/happy_ghast.rs b/crates/pumpkin/src/entity/passive/happy_ghast.rs index aba9fceb7..d06480960 100644 --- a/crates/pumpkin/src/entity/passive/happy_ghast.rs +++ b/crates/pumpkin/src/entity/passive/happy_ghast.rs @@ -153,7 +153,7 @@ impl Mob for HappyGhastEntity { &self.mob_entity } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.ageable_ai_step(); let leash_time = self.leash_holder_time.load(Ordering::Relaxed); diff --git a/crates/pumpkin/src/entity/passive/iron_golem.rs b/crates/pumpkin/src/entity/passive/iron_golem.rs index 01812e857..e5aed283e 100644 --- a/crates/pumpkin/src/entity/passive/iron_golem.rs +++ b/crates/pumpkin/src/entity/passive/iron_golem.rs @@ -133,7 +133,7 @@ impl Mob for IronGolemEntity { &self.mob_entity } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let attack_tick = self.attack_animation_tick.load(Ordering::Relaxed); if attack_tick > 0 { self.attack_animation_tick.fetch_sub(1, Ordering::Relaxed); diff --git a/crates/pumpkin/src/entity/passive/nautilus.rs b/crates/pumpkin/src/entity/passive/nautilus.rs index da99dd9b5..a6cb27305 100644 --- a/crates/pumpkin/src/entity/passive/nautilus.rs +++ b/crates/pumpkin/src/entity/passive/nautilus.rs @@ -267,7 +267,7 @@ impl Mob for NautilusEntity { ); } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { let entity = &self.mob_entity.living_entity.entity; let Ok(passengers) = entity.passengers.try_lock() else { diff --git a/crates/pumpkin/src/entity/passive/sniffer.rs b/crates/pumpkin/src/entity/passive/sniffer.rs index 547643d83..65e7401c7 100644 --- a/crates/pumpkin/src/entity/passive/sniffer.rs +++ b/crates/pumpkin/src/entity/passive/sniffer.rs @@ -357,7 +357,7 @@ impl Mob for SnifferEntity { &self.mob_entity } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.ageable_ai_step(); let state = self.get_state(); match state { diff --git a/crates/pumpkin/src/entity/passive/villager/mod.rs b/crates/pumpkin/src/entity/passive/villager/mod.rs index 626cb8c68..6f1894cab 100644 --- a/crates/pumpkin/src/entity/passive/villager/mod.rs +++ b/crates/pumpkin/src/entity/passive/villager/mod.rs @@ -2288,7 +2288,7 @@ impl Mob for VillagerEntity { } } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { self.villager_mob_tick(); } diff --git a/crates/pumpkin/src/entity/passive/wandering_trader.rs b/crates/pumpkin/src/entity/passive/wandering_trader.rs index a9c6bb323..8b9dcfe2a 100644 --- a/crates/pumpkin/src/entity/passive/wandering_trader.rs +++ b/crates/pumpkin/src/entity/passive/wandering_trader.rs @@ -784,7 +784,7 @@ impl Mob for WanderingTraderEntity { true } - fn mob_tick<'a>(&'a self, _caller: &'a Arc) { + fn mob_tick(&self, _caller: &dyn EntityBase) { // Despawn delay handling (vanilla aiStep / maybeDespawn) if !self.is_trading.load(Ordering::Relaxed) { let delay = self.despawn_delay.load(Ordering::Relaxed); diff --git a/crates/pumpkin/src/entity/player.rs b/crates/pumpkin/src/entity/player.rs index 96cd9c389..e476a2755 100644 --- a/crates/pumpkin/src/entity/player.rs +++ b/crates/pumpkin/src/entity/player.rs @@ -290,7 +290,6 @@ use crate::command::context::command_source::CommandSource; use crate::command::node::dispatcher::CommandDispatcher; use crate::command::{CommandSender, client_suggestions}; use crate::data::SaveJSONConfiguration; -use crate::entity::TeleportFuture; use crate::net::{ClientPlatform, GameProfile}; use crate::net::{DisconnectReason, PlayerConfig}; use crate::plugin::player::exp_change::PlayerExpChangeEvent; @@ -918,11 +917,11 @@ impl Player { } #[expect(clippy::too_many_lines, clippy::items_after_statements)] - pub async fn new( + pub fn new( client: Arc, gameprofile: GameProfile, config: PlayerConfig, - world: Arc, + world: &Arc, gamemode: GameMode, ) -> Self { let inventory_changed = Arc::new(AtomicBool::new(true)); @@ -986,13 +985,9 @@ impl Player { let mut abilities = Abilities::default(); abilities.set_for_gamemode(gamemode); - let properties = gameprofile.properties.load().clone(); - let mut bedrock_skin = tokio::task::spawn_blocking(move || { - Self::fetch_skin(&properties) - .unwrap_or_else(pumpkin_protocol::bedrock::client::Skin::steve) - }) - .await - .unwrap_or_else(|_| pumpkin_protocol::bedrock::client::Skin::steve()); + let properties = gameprofile.properties.load(); + let mut bedrock_skin = Self::fetch_skin(&properties) + .unwrap_or_else(pumpkin_protocol::bedrock::client::Skin::steve); // Standard_Custom is a shared placeholder. Give fallback skins a stable, // per-player identity so Bedrock never sees duplicate skin IDs. @@ -1646,7 +1641,7 @@ impl Player { // Vanilla `Player#attack` ends the successful-hit branch with // `causeFoodExhaustion(0.1F)`. Only landed hits exhaust; the miss/no-damage // case returned early above. - self.add_exhaustion(0.1).await; + self.add_exhaustion(0.1); if config.swing {} } @@ -2456,7 +2451,7 @@ impl Player { if player_pos != target_pos { self.living_entity.entity.set_pos(target_pos); if let Some(p) = self.world().get_player_by_uuid(self.gameprofile.id) { - server.runtime.spawn(async move { + server.spawn_task(async move { crate::world::chunker::update_position(&p).await; }); } @@ -2530,7 +2525,7 @@ impl Player { && !chunk_of_chunks.is_empty() { let client = self.client.clone(); - server.runtime.spawn(async move { + server.spawn_task(async move { client.send_chunks(&chunk_of_chunks).await; }); if let ClientPlatform::Bedrock(bedrock_client) = self.client.as_ref() @@ -2559,71 +2554,64 @@ impl Player { self.sleeping_since.store(Some(sleeping_since + 1)); } - let player_arc = self.world().get_player_by_uuid(self.gameprofile.id); if self.mining.load(Ordering::Relaxed) - && let Some(p) = player_arc.clone() + && let Some(p) = self.world().get_player_by_uuid(self.gameprofile.id) { let world_clone = p.world(); let server_clone = world_clone.server.upgrade(); - server.runtime.spawn(async move { - let pos = *p - .mining_pos - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let world = p.world(); - let state = world.get_block_state(&pos); - // Is the block broken? - if state.is_air() { + let pos = *p + .mining_pos + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let world = p.world(); + let state = world.get_block_state(&pos); + // Is the block broken? + if state.is_air() { + p.stop_mining(); + } else { + let finished = p.continue_mining( + pos, + &world, + state, + p.start_mining_time.load(Ordering::Relaxed), + ); + if finished && matches!(p.client.as_ref(), ClientPlatform::Bedrock(_)) { p.stop_mining(); - } else { - let finished = p.continue_mining( - pos, - &world, - state, - p.start_mining_time.load(Ordering::Relaxed), - ); - if finished && matches!(p.client.as_ref(), ClientPlatform::Bedrock(_)) { - p.stop_mining(); - let block = Block::from_state_id(state.id); - let can_harvest = p.can_harvest(state, block); - let flags = if can_harvest { - pumpkin_world::world::BlockFlags::NOTIFY_NEIGHBORS - } else { - pumpkin_world::world::BlockFlags::SKIP_DROPS - | pumpkin_world::world::BlockFlags::NOTIFY_NEIGHBORS - }; - if world.break_block(&pos, Some(p.clone()), flags).is_some() { - if let Some(server) = server_clone { - server - .block_registry - .broken(&world, block, &p, &pos, &server, state); - } - p.apply_tool_damage_for_block_break(state); - if can_harvest { - p.add_exhaustion(MINE_BLOCK_EXHAUSTION).await; - } + let block = Block::from_state_id(state.id); + let can_harvest = p.can_harvest(state, block); + let flags = if can_harvest { + pumpkin_world::world::BlockFlags::NOTIFY_NEIGHBORS + } else { + pumpkin_world::world::BlockFlags::SKIP_DROPS + | pumpkin_world::world::BlockFlags::NOTIFY_NEIGHBORS + }; + if world.break_block(&pos, Some(&p), flags).is_some() { + if let Some(server) = server_clone { + server + .block_registry + .broken(&world, block, &p, &pos, &server, state); + } + p.apply_tool_damage_for_block_break(state); + if can_harvest { + p.add_exhaustion(MINE_BLOCK_EXHAUSTION); } } } - }); + } } self.last_attacked_ticks.fetch_add(1, Ordering::Relaxed); - if let Some(ref p_arc) = player_arc { - let caller: Arc = p_arc.clone(); - self.living_entity.tick(&caller, server); - self.breath_manager.tick(p_arc); - self.hunger_manager.tick(p_arc); - } + self.living_entity.tick(self, server); + + self.breath_manager.tick(self); + self.hunger_manager.tick(self); // Vanilla updates pose in PlayerEntity#tick after super.tick(). self.update_player_pose(); self.check_inventory_advancements(); - if let Some(ref p_arc) = player_arc - && let Ok(mut adv) = self.advancements.try_lock() - { - adv.flush_dirty(p_arc, true); + if let Ok(mut adv) = self.advancements.try_lock() { + adv.flush_dirty(self, true); } // experience handling @@ -2649,10 +2637,8 @@ impl Player { let idle_timeout_minutes = server.player_idle_timeout.load(Ordering::Relaxed); if idle_timeout_minutes > 0 { let idle_duration = now.duration_since(self.last_action_time.load()); - if idle_duration >= Duration::from_secs(idle_timeout_minutes as u64 * 60) - && let Some(ref p_arc) = player_arc - { - p_arc.kick( + if idle_duration >= Duration::from_secs(idle_timeout_minutes as u64 * 60) { + self.kick( DisconnectReason::KickedForIdle, &TextComponent::translate_cross( translation::java::MULTIPLAYER_DISCONNECT_IDLING, @@ -2714,27 +2700,27 @@ impl Player { } } - pub async fn jump(&self) { + pub fn jump(&self) { self.stats .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .increment_custom(statistics::CustomStatistic::Jump, 1); if self.living_entity.entity.is_sprinting() { - self.add_exhaustion(0.2).await; + self.add_exhaustion(0.2); } else { - self.add_exhaustion(0.05).await; + self.add_exhaustion(0.05); } } - pub async fn progress_motion(&self, delta_pos: Vector3) { + pub fn progress_motion(&self, delta_pos: Vector3) { // TODO: Swimming, gliding... if self.living_entity.entity.on_ground.load(Ordering::Relaxed) { let delta = (delta_pos.horizontal_length() * 100.0).round() as f32; if delta > 0.0 { if self.living_entity.entity.is_sprinting() { - self.add_exhaustion(0.1 * delta * 0.01).await; + self.add_exhaustion(0.1 * delta * 0.01); } else { - self.add_exhaustion(0.0 * delta * 0.01).await; + self.add_exhaustion(0.0 * delta * 0.01); } } } @@ -3637,7 +3623,7 @@ impl Player { self.send_permission_lvl_update(); - player.clone().request_teleport(position, yaw, pitch).await; + player.request_teleport(position, yaw, pitch); player.get_entity().last_pos.store(position); self.send_abilities_update(); @@ -3658,78 +3644,74 @@ impl Player { /// `yaw` and `pitch` are in degrees. /// Rarly used, for example when waking up the player from a bed or their first time spawn. Otherwise, the `teleport` method should be used. /// The player should respond with the `SConfirmTeleport` packet. - pub async fn request_teleport(self: &Arc, position: Vector3, yaw: f32, pitch: f32) { + pub fn request_teleport(&self, position: Vector3, yaw: f32, pitch: f32) { // This is the ultra special magic code used to create the teleport id // This returns the old value // This operation wraps around on overflow. let Some(server) = self.world().server.upgrade() else { return; }; - send_cancellable! {{ - server; - PlayerTeleportEvent { - player: self.clone(), + if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id) { + let mut event = PlayerTeleportEvent { + player: player_arc, from: self.living_entity.entity.pos.load(), to: position, cancelled: false, }; + server.plugin_manager.fire_blocking(&server, &mut event); + if event.cancelled { + return; + } + } - 'after: { - let position = event.to; - let i = self - .teleport_id_count - .fetch_add(1, Ordering::Relaxed); - let teleport_id = i + 1; - self.living_entity.entity.set_pos(position); - let entity = &self.living_entity.entity; - entity.set_rotation(yaw, pitch); - match self.client.as_ref() { - ClientPlatform::Java(client) => { - *self - .awaiting_teleport - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) = - Some((teleport_id.into(), position)); - let packet = CPlayerPosition::new( - teleport_id.into(), - position, - Vector3::new(0.0, 0.0, 0.0), - yaw, - pitch, - // TODO - Vec::new(), - ); - if let Ok(data) = client.serialize_packet(&packet) { - client.send_packet_now(data).await; - } - } - ClientPlatform::Bedrock(client) => { - let packet = CBedrockMovePlayer::new( - VarULong(self.entity_id() as u64), - Vector3::new( - position.x as f32, - position.y as f32 + entity.entity_type.eye_height, - position.z as f32, - ), - pitch, - yaw, - yaw, - CBedrockMovePlayer::MODE_TELEPORT, - false, - VarULong(0), - 0, - 0, - VarULong( - self.tick_counter.load(Ordering::Relaxed).max(0) as u64, - ), - ); - if let Ok(data) = client.serialize_packet(&packet) { - client.send_game_packet(data).await; - } - } + let i = self.teleport_id_count.fetch_add(1, Ordering::Relaxed); + let teleport_id = i + 1; + self.living_entity.entity.set_pos(position); + let entity = &self.living_entity.entity; + entity.set_rotation(yaw, pitch); + match self.client.as_ref() { + ClientPlatform::Java(client) => { + *self + .awaiting_teleport + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = + Some((teleport_id.into(), position)); + let packet = CPlayerPosition::new( + teleport_id.into(), + position, + Vector3::new(0.0, 0.0, 0.0), + yaw, + pitch, + // TODO + Vec::new(), + ); + if let Ok(data) = client.serialize_packet(&packet) { + client.try_enqueue_packet(data); } } - }} + ClientPlatform::Bedrock(client) => { + let packet = CBedrockMovePlayer::new( + VarULong(self.entity_id() as u64), + Vector3::new( + position.x as f32, + position.y as f32 + entity.entity_type.eye_height, + position.z as f32, + ), + pitch, + yaw, + yaw, + CBedrockMovePlayer::MODE_TELEPORT, + false, + VarULong(0), + 0, + 0, + VarULong(self.tick_counter.load(Ordering::Relaxed).max(0) as u64), + ); + if let Ok(data) = client.serialize_packet(&packet) { + client.try_enqueue_packet(data); + } + } + } } pub fn block_interaction_range(&self) -> f64 { @@ -3818,7 +3800,7 @@ impl Player { health > 0.0 && health < max_health } - pub async fn add_exhaustion(&self, exhaustion: f32) { + pub fn add_exhaustion(&self, exhaustion: f32) { if self .abilities .lock() @@ -3835,8 +3817,7 @@ impl Player { if let Some(server) = self.world().server.upgrade() { server .plugin_manager - .fire(&server, &mut exhaustion_event) - .await; + .fire_blocking(&server, &mut exhaustion_event); } if exhaustion_event.cancelled { return; @@ -6188,55 +6169,36 @@ impl EntityBase for Player { } fn teleport( - self: Arc, + &self, position: Vector3, yaw: Option, pitch: Option, world: Arc, - ) -> TeleportFuture { - Box::pin(async move { - if Arc::ptr_eq(&world, &self.world()) { - // Same world - let yaw = yaw.unwrap_or(self.living_entity.entity.yaw.load()); - let pitch = pitch.unwrap_or(self.living_entity.entity.pitch.load()); - let Some(server) = self.world().server.upgrade() else { - return; - }; - send_cancellable! {{ - server; - PlayerTeleportEvent { - player: self.clone(), - from: self.living_entity.entity.pos.load(), - to: position, - cancelled: false, - }; - 'after: { - let position = event.to; - let entity = self.get_entity(); - self.request_teleport(position, yaw, pitch).await; - let chunk_pos = entity.chunk_pos.load(); - entity - .world - .load() - .broadcast_to_chunk_except( - chunk_pos, - &[self.living_entity.entity.entity_uuid], - &CEntityPositionSync::new( - self.living_entity.entity.entity_id.into(), - position, - Vector3::new(0.0, 0.0, 0.0), - yaw, - pitch, - entity.on_ground.load(Ordering::SeqCst), - ) - ) - ; - } - }} - } else { - self.teleport_world(world, position, yaw, pitch).await; - } - }) + ) { + if Arc::ptr_eq(&world, &self.world()) { + // Same world + let yaw = yaw.unwrap_or_else(|| self.living_entity.entity.yaw.load()); + let pitch = pitch.unwrap_or_else(|| self.living_entity.entity.pitch.load()); + self.request_teleport(position, yaw, pitch); + let entity = self.get_entity(); + let chunk_pos = entity.chunk_pos.load(); + entity.world.load().broadcast_to_chunk_except( + chunk_pos, + &[self.living_entity.entity.entity_uuid], + &CEntityPositionSync::new( + self.living_entity.entity.entity_id.into(), + position, + Vector3::new(0.0, 0.0, 0.0), + yaw, + pitch, + entity.on_ground.load(Ordering::SeqCst), + ), + ); + } else if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id) { + self.spawn_task(async move { + player_arc.teleport_world(world, position, yaw, pitch).await; + }); + } } fn get_entity(&self) -> &Entity { diff --git a/crates/pumpkin/src/entity/player/advancement.rs b/crates/pumpkin/src/entity/player/advancement.rs index 0d376e0fb..b5b2c08fa 100644 --- a/crates/pumpkin/src/entity/player/advancement.rs +++ b/crates/pumpkin/src/entity/player/advancement.rs @@ -19,11 +19,10 @@ use serde::ser::SerializeMap; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::to_string_pretty; use std::collections::{HashMap, HashSet}; -use std::fs::{create_dir_all, read, write}; +use std::fs::read; use std::path::PathBuf; use std::sync::{Arc, Weak}; use std::time::{SystemTime, UNIX_EPOCH}; -use tokio::task::spawn_blocking; use tracing::{error, warn}; use uuid::Uuid; @@ -271,19 +270,17 @@ impl PlayerAdvancement { return Ok(()); } let json = to_string_pretty(self).map_err(AdvancementDataError::Json)?; - let path = self.path.clone(); - spawn_blocking(move || { - let Some(parent) = path.parent() else { - return Ok(()); - }; - if let Err(e) = create_dir_all(parent) { - error!("Failed to create player advancement directory : {e}"); - return Err(AdvancementDataError::Io(e)); - } - write(path, json).map_err(AdvancementDataError::Io) - }) - .await - .unwrap_or(Ok(())) + let Some(parent) = self.path.parent() else { + return Ok(()); + }; + if let Err(e) = tokio::fs::create_dir_all(parent).await { + error!("Failed to create player advancement directory : {e}"); + return Err(AdvancementDataError::Io(e)); + } + tokio::fs::write(&self.path, json) + .await + .map_err(AdvancementDataError::Io)?; + Ok(()) } /// Loads the player's advancement progress from disk. @@ -352,7 +349,7 @@ impl PlayerAdvancement { } /// Flushes any pending advancement state down to the client. - pub fn flush_dirty(&mut self, player: &Arc, show_advancement: bool) { + pub fn flush_dirty(&mut self, player: &Player, show_advancement: bool) { if self.is_first_packet || !self.roots_to_update.is_empty() { let mut progress: HashMap = HashMap::new(); let mut added: Vec<&Advancement> = Vec::new(); @@ -368,7 +365,6 @@ impl PlayerAdvancement { } self.progress_changed.clear(); if !progress.is_empty() || !added.is_empty() || !removed.is_empty() { - let player = player.clone(); let parsed_progress: Vec = progress .into_iter() .map(|(key, val)| AdvancementProgressData { diff --git a/crates/pumpkin/src/entity/projectile/arrow.rs b/crates/pumpkin/src/entity/projectile/arrow.rs index 6bf2bf47a..3a190219d 100644 --- a/crates/pumpkin/src/entity/projectile/arrow.rs +++ b/crates/pumpkin/src/entity/projectile/arrow.rs @@ -286,7 +286,7 @@ impl EntityBase for ArrowEntity { } } #[allow(clippy::too_many_lines)] - fn tick<'a>(&'a self, caller: &'a Arc, _server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, _server: &Server) { let entity = self.get_entity(); let world = entity.world.load(); diff --git a/crates/pumpkin/src/entity/projectile/egg.rs b/crates/pumpkin/src/entity/projectile/egg.rs index c55b0754a..80c559dce 100644 --- a/crates/pumpkin/src/entity/projectile/egg.rs +++ b/crates/pumpkin/src/entity/projectile/egg.rs @@ -1,5 +1,5 @@ +use std::sync::RwLock; use std::sync::atomic::AtomicBool; -use std::sync::{Arc, RwLock}; use crate::plugin::player::egg_throw::PlayerEggThrowEvent; use crate::{ @@ -80,7 +80,7 @@ impl EntityBase for EggEntity { ); } - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.thrown.process_tick(caller, server); } diff --git a/crates/pumpkin/src/entity/projectile/ender_pearl.rs b/crates/pumpkin/src/entity/projectile/ender_pearl.rs index aff527783..bbccd2c26 100644 --- a/crates/pumpkin/src/entity/projectile/ender_pearl.rs +++ b/crates/pumpkin/src/entity/projectile/ender_pearl.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use std::sync::atomic::AtomicBool; use crate::entity::projectile::ProjectileHit; @@ -47,7 +46,7 @@ impl EnderPearlEntity { } impl EntityBase for EnderPearlEntity { - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.thrown.process_tick(caller, server); } @@ -94,76 +93,69 @@ impl EntityBase for EnderPearlEntity { ); } - let world_clone = world.clone(); let owner_id = self.thrown.owner_id; let teleport_pos = entity.last_pos.load(); - tokio::spawn(async move { - if let ( - ProjectileHit::Entity { - entity: hit_entity, - hit_pos, - .. - }, - Some(owner), - ) = (&hit, attacker) - { - let victim_ref = &**hit_entity; - hit_entity.damage_with_context( - victim_ref, - 0.0, - DamageType::THROWN, - Some(*hit_pos), - Some(owner.get_entity()), - Some(victim_ref), + if let ( + ProjectileHit::Entity { + entity: hit_entity, + hit_pos, + .. + }, + Some(owner), + ) = (&hit, attacker) + { + let victim_ref = &**hit_entity; + hit_entity.damage_with_context( + victim_ref, + 0.0, + DamageType::THROWN, + Some(*hit_pos), + Some(owner.get_entity()), + Some(victim_ref), + ); + } + + if let Some(owner_id) = owner_id + && let Some(owner) = world.get_entity_by_id(owner_id) + && owner.get_entity().is_alive() + && owner.get_living_entity().is_none_or(|living| { + living.health.load() > 0.0 && owner.get_entity().pose.load() != EntityPose::Sleeping + }) + { + let should_spawn_endermite = rand::random::() < ENDERMITE_SPAWN_CHANCE; + if world.should_spawn_monsters() && should_spawn_endermite { + let entity = Entity::new( + world.clone(), + owner.get_entity().pos.load(), + &EntityType::ENDERMITE, ); + let endermite = EndermiteEntity::new(entity); + world.spawn_entity(endermite); } - if let Some(owner_id) = owner_id - && let Some(owner) = world_clone.get_entity_by_id(owner_id) - && owner.get_entity().is_alive() - && owner.get_living_entity().is_none_or(|living| { - living.health.load() > 0.0 - && owner.get_entity().pose.load() != EntityPose::Sleeping - }) - { - let should_spawn_endermite = rand::random::() < ENDERMITE_SPAWN_CHANCE; - if world_clone.should_spawn_monsters() && should_spawn_endermite { - let entity = Entity::new( - world_clone.clone(), - owner.get_entity().pos.load(), - &EntityType::ENDERMITE, - ); - let endermite = EndermiteEntity::new(entity); - world_clone.spawn_entity(endermite); - } + // In vanilla, teleport handles everything including sound + owner.teleport( + teleport_pos, + Some(owner.get_entity().yaw.load()), + Some(owner.get_entity().pitch.load()), + world.clone(), + ); - // In vanilla, teleport handles everything including sound - owner - .clone() - .teleport( - teleport_pos, - Some(owner.get_entity().yaw.load()), - Some(owner.get_entity().pitch.load()), - world_clone.clone(), - ) - .await; + // Play teleport sound at new position + world.play_sound( + Sound::EntityPlayerTeleport, + SoundCategory::Players, + &teleport_pos, + ); - // Play teleport sound at new position - world_clone.play_sound( - Sound::EntityPlayerTeleport, - SoundCategory::Players, - &teleport_pos, - ); - - // Deal 5 damage to owner - owner.damage( - owner.as_ref(), - 5.0, - pumpkin_data::damage::DamageType::ENDER_PEARL, - ); - } - }); + // Deal 5 damage to owner + owner.damage( + owner.as_ref(), + 5.0, + pumpkin_data::damage::DamageType::ENDER_PEARL, + ); + } world.send_entity_status(entity, EntityStatus::Death, Some(ActorEventID::Death)); } diff --git a/crates/pumpkin/src/entity/projectile/evoker_fangs.rs b/crates/pumpkin/src/entity/projectile/evoker_fangs.rs index 6f86f0a6b..8c4cc095b 100644 --- a/crates/pumpkin/src/entity/projectile/evoker_fangs.rs +++ b/crates/pumpkin/src/entity/projectile/evoker_fangs.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use pumpkin_data::damage::DamageType; @@ -42,7 +41,7 @@ impl EntityBase for EvokerFangsEntity { } } - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) { + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) { let entity = &self.entity; let world = entity.world.load(); diff --git a/crates/pumpkin/src/entity/projectile/eye_of_ender.rs b/crates/pumpkin/src/entity/projectile/eye_of_ender.rs index a1fd2247d..e4e185a5e 100644 --- a/crates/pumpkin/src/entity/projectile/eye_of_ender.rs +++ b/crates/pumpkin/src/entity/projectile/eye_of_ender.rs @@ -1,7 +1,7 @@ use crate::entity::item::ItemEntity; use crate::entity::living::LivingEntity; use crate::entity::player::Player; -use crate::{entity::EntityBaseFuture, server::Server}; +use crate::server::Server; use core::f64; use pumpkin_data::damage::DamageType; use pumpkin_data::entity::EntityType; @@ -122,7 +122,7 @@ fn lerp(t: f64, start: f64, end: f64) -> f64 { } impl EntityBase for EyeOfEnder { - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { let entity = &self.entity; entity.tick(caller, server); @@ -212,46 +212,37 @@ impl EntityBase for EyeOfEnder { None } - fn get_item_entity(self: Arc) -> Option> { - None - } - fn cast_any(&self) -> &dyn std::any::Any { self } - fn send_java_spawn_packet<'a>( - &'a self, - client: &'a crate::net::java::JavaClient, - ) -> EntityBaseFuture<'a, ()> { - Box::pin(async move { - let spawn_packet = self.entity.create_spawn_packet(); - if let Ok(data) = client.serialize_packet(&spawn_packet) { - client.enqueue_packet(data).await; - } + fn send_java_spawn_packet(&self, client: &crate::net::java::JavaClient) { + let spawn_packet = self.entity.create_spawn_packet(); + if let Ok(data) = client.serialize_packet(&spawn_packet) { + client.try_enqueue_packet(data); + } - if client.version.load() >= pumpkin_data::packet::CURRENT_MC_VERSION { - let metadata = Metadata::new( - pumpkin_data::tracked_data::eye_of_ender::ITEM_STACK, - ItemStackSerializer::from( - self.item_stack - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .clone(), - ), + if client.version.load() >= pumpkin_util::version::JavaMinecraftVersion::V_1_21 { + let metadata = Metadata::new( + pumpkin_data::tracked_data::eye_of_ender::ITEM_STACK, + ItemStackSerializer::from( + self.item_stack + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone(), + ), + ); + let mut data = Vec::new(); + if metadata.write(&mut data, &client.version.load()).is_ok() { + data.push(255); + let meta_packet = pumpkin_protocol::java::client::play::CSetEntityMetadata::new( + self.entity.entity_id.into(), + data.into(), ); - let mut data = Vec::new(); - if metadata.write(&mut data, &client.version.load()).is_ok() { - data.push(255); - let meta_packet = pumpkin_protocol::java::client::play::CSetEntityMetadata::new( - self.entity.entity_id.into(), - data.into(), - ); - if let Ok(meta_data) = client.serialize_packet(&meta_packet) { - client.enqueue_packet(meta_data).await; - } + if let Ok(meta_data) = client.serialize_packet(&meta_packet) { + client.try_enqueue_packet(meta_data); } } - }) + } } } diff --git a/crates/pumpkin/src/entity/projectile/fireball.rs b/crates/pumpkin/src/entity/projectile/fireball.rs index 315639bd9..865ac9687 100644 --- a/crates/pumpkin/src/entity/projectile/fireball.rs +++ b/crates/pumpkin/src/entity/projectile/fireball.rs @@ -1,5 +1,5 @@ +use std::sync::RwLock; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, RwLock}; use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; @@ -206,7 +206,7 @@ impl EntityBase for FireballEntity { ); } - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { let entity = self.get_entity(); let mut velocity = entity.velocity.load(); diff --git a/crates/pumpkin/src/entity/projectile/firework_rocket.rs b/crates/pumpkin/src/entity/projectile/firework_rocket.rs index 63d01ff18..c2da6d3f1 100644 --- a/crates/pumpkin/src/entity/projectile/firework_rocket.rs +++ b/crates/pumpkin/src/entity/projectile/firework_rocket.rs @@ -10,11 +10,7 @@ use pumpkin_util::{ math::vector3::Vector3, random::{RandomGenerator, RandomImpl, get_seed, xoroshiro128::Xoroshiro}, }; -use std::sync::atomic::AtomicBool; -use std::sync::{ - Arc, - atomic::{AtomicU32, Ordering}, -}; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; const GRAVITY: f64 = 0.0; @@ -95,7 +91,7 @@ impl FireworkRocketEntity { } impl EntityBase for FireworkRocketEntity { - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.entity.process_tick(caller, server); let entity = self.get_entity(); diff --git a/crates/pumpkin/src/entity/projectile/fishing_bobber.rs b/crates/pumpkin/src/entity/projectile/fishing_bobber.rs index 4a11ac94a..0babe3118 100644 --- a/crates/pumpkin/src/entity/projectile/fishing_bobber.rs +++ b/crates/pumpkin/src/entity/projectile/fishing_bobber.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use crate::entity::projectile::{ProjectileHit, is_projectile}; @@ -92,7 +91,7 @@ impl FishingBobberEntity { } #[expect(clippy::too_many_lines)] - pub fn process_tick<'a>(&'a self, caller: &'a Arc, _server: &'a Server) { + pub fn process_tick(&self, caller: &dyn EntityBase, _server: &Server) { let entity = self.get_entity(); let world = entity.world.load(); @@ -180,7 +179,7 @@ impl FishingBobberEntity { .expand(0.3, 0.3, 0.3); // Basic block collision to stop bobber - let (block_cols, _) = world.get_block_collisions(search_box, caller.as_ref()); + let (block_cols, _) = world.get_block_collisions(search_box, caller); if !block_cols.is_empty() { self.in_ground.store(true, Ordering::Relaxed); entity.velocity.store(Vector3::new(0.0, 0.0, 0.0)); @@ -234,7 +233,7 @@ impl EntityBase for FishingBobberEntity { self.has_hit.store(true, Ordering::Relaxed); } - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.process_tick(caller, server); } } diff --git a/crates/pumpkin/src/entity/projectile/lingering_potion.rs b/crates/pumpkin/src/entity/projectile/lingering_potion.rs index ad1e04ea3..47c3192f6 100644 --- a/crates/pumpkin/src/entity/projectile/lingering_potion.rs +++ b/crates/pumpkin/src/entity/projectile/lingering_potion.rs @@ -1,5 +1,5 @@ +use std::sync::RwLock; use std::sync::atomic::AtomicBool; -use std::sync::{Arc, RwLock}; use crate::entity::projectile::splash_potion::extinguish_fire_if_water_potion; use crate::{ @@ -83,7 +83,7 @@ impl EntityBase for LingeringPotionEntity { ); } - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.thrown.process_tick(caller, server); } diff --git a/crates/pumpkin/src/entity/projectile/llama_spit.rs b/crates/pumpkin/src/entity/projectile/llama_spit.rs index c78e88d9a..5006367f7 100644 --- a/crates/pumpkin/src/entity/projectile/llama_spit.rs +++ b/crates/pumpkin/src/entity/projectile/llama_spit.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use pumpkin_data::damage::DamageType; @@ -57,7 +56,7 @@ impl LlamaSpitEntity { } impl EntityBase for LlamaSpitEntity { - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { if self.get_entity().touching_water.load(Ordering::Relaxed) { self.get_entity().remove(); return; diff --git a/crates/pumpkin/src/entity/projectile/mod.rs b/crates/pumpkin/src/entity/projectile/mod.rs index 34d0364ae..02349c836 100644 --- a/crates/pumpkin/src/entity/projectile/mod.rs +++ b/crates/pumpkin/src/entity/projectile/mod.rs @@ -118,7 +118,7 @@ impl ThrownItemEntity { impl ThrownItemEntity { /// Process a tick for projectile movement and collisions - pub fn process_tick<'a>(&'a self, caller: &'a Arc, _server: &'a Server) { + pub fn process_tick(&self, caller: &dyn EntityBase, _server: &Server) { let entity = self.get_entity(); let world = entity.world.load(); @@ -169,7 +169,7 @@ impl ThrownItemEntity { let mut hit = None; // Block collisions - let (block_cols, block_positions) = world.get_block_collisions(search_box, caller.as_ref()); + let (block_cols, block_positions) = world.get_block_collisions(search_box, caller); for (idx, bb) in block_cols.iter().enumerate() { if let Some(t) = calculate_ray_intersection(&start_pos, &delta, bb) && t < closest_t diff --git a/crates/pumpkin/src/entity/projectile/shulker_bullet.rs b/crates/pumpkin/src/entity/projectile/shulker_bullet.rs index 5fc5d289b..3a631851d 100644 --- a/crates/pumpkin/src/entity/projectile/shulker_bullet.rs +++ b/crates/pumpkin/src/entity/projectile/shulker_bullet.rs @@ -318,7 +318,7 @@ impl EntityBase for ShulkerBulletEntity { true } #[allow(clippy::too_many_lines)] - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) { + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) { if self.has_hit.load(Ordering::Relaxed) { return; } diff --git a/crates/pumpkin/src/entity/projectile/small_fireball.rs b/crates/pumpkin/src/entity/projectile/small_fireball.rs index e73ab6fb8..bc9e203dd 100644 --- a/crates/pumpkin/src/entity/projectile/small_fireball.rs +++ b/crates/pumpkin/src/entity/projectile/small_fireball.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use std::sync::atomic::AtomicBool; use crate::{ @@ -37,7 +36,7 @@ impl SmallFireballEntity { } impl EntityBase for SmallFireballEntity { - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.thrown.process_tick(caller, server); } diff --git a/crates/pumpkin/src/entity/projectile/snowball.rs b/crates/pumpkin/src/entity/projectile/snowball.rs index da67c594c..c96ba6128 100644 --- a/crates/pumpkin/src/entity/projectile/snowball.rs +++ b/crates/pumpkin/src/entity/projectile/snowball.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use std::sync::atomic::AtomicBool; use crate::entity::projectile::ProjectileHit; @@ -42,7 +41,7 @@ impl SnowballEntity { } impl EntityBase for SnowballEntity { - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.thrown.process_tick(caller, server); } diff --git a/crates/pumpkin/src/entity/projectile/splash_potion.rs b/crates/pumpkin/src/entity/projectile/splash_potion.rs index d09085ad7..4ec65fc6d 100644 --- a/crates/pumpkin/src/entity/projectile/splash_potion.rs +++ b/crates/pumpkin/src/entity/projectile/splash_potion.rs @@ -118,7 +118,7 @@ impl EntityBase for SplashPotionEntity { ); } - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.thrown.process_tick(caller, server); } diff --git a/crates/pumpkin/src/entity/projectile/trident.rs b/crates/pumpkin/src/entity/projectile/trident.rs index 3c1d46319..915ffd446 100644 --- a/crates/pumpkin/src/entity/projectile/trident.rs +++ b/crates/pumpkin/src/entity/projectile/trident.rs @@ -152,7 +152,7 @@ impl TridentEntity { } impl EntityBase for TridentEntity { - fn tick<'a>(&'a self, caller: &'a Arc, _server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, _server: &Server) { let entity = self.get_entity(); let world = entity.world.load(); diff --git a/crates/pumpkin/src/entity/projectile/wind_charge.rs b/crates/pumpkin/src/entity/projectile/wind_charge.rs index d436119de..8b25b099c 100644 --- a/crates/pumpkin/src/entity/projectile/wind_charge.rs +++ b/crates/pumpkin/src/entity/projectile/wind_charge.rs @@ -126,7 +126,7 @@ impl WindChargeEntity { } impl EntityBase for WindChargeEntity { - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.thrown_item_entity.process_tick(caller, server); if let Some(cooldown) = self.deflect_cooldown() { diff --git a/crates/pumpkin/src/entity/projectile/wither_skull.rs b/crates/pumpkin/src/entity/projectile/wither_skull.rs index 34aea68ba..c6e5cb6b1 100644 --- a/crates/pumpkin/src/entity/projectile/wither_skull.rs +++ b/crates/pumpkin/src/entity/projectile/wither_skull.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use pumpkin_data::{damage::DamageType, effect::StatusEffect, potion::Effect, tracked_data}; @@ -102,7 +101,7 @@ impl EntityBase for WitherSkullEntity { ); } - fn tick<'a>(&'a self, caller: &'a Arc, server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { self.thrown.process_tick(caller, server); } diff --git a/crates/pumpkin/src/entity/tnt.rs b/crates/pumpkin/src/entity/tnt.rs index 282c0a937..a928e4af6 100644 --- a/crates/pumpkin/src/entity/tnt.rs +++ b/crates/pumpkin/src/entity/tnt.rs @@ -6,12 +6,9 @@ use pumpkin_protocol::{codec::var_int::VarInt, java::client::play::Metadata}; use pumpkin_util::math::vector3::Vector3; use std::{ f64::consts::TAU, - sync::{ - Arc, - atomic::{ - AtomicU32, - Ordering::{self, Relaxed}, - }, + sync::atomic::{ + AtomicU32, + Ordering::{self, Relaxed}, }, }; @@ -32,7 +29,7 @@ impl TNTEntity { } impl EntityBase for TNTEntity { - fn tick(&self, caller: &Arc, server: &Server) { + fn tick(&self, caller: &dyn EntityBase, server: &Server) { let entity = &self.entity; let mut velo = entity.velocity.load(); diff --git a/crates/pumpkin/src/entity/vehicle/boat.rs b/crates/pumpkin/src/entity/vehicle/boat.rs index faae09c2d..792ba0a92 100644 --- a/crates/pumpkin/src/entity/vehicle/boat.rs +++ b/crates/pumpkin/src/entity/vehicle/boat.rs @@ -60,7 +60,7 @@ impl EntityBase for BoatEntity { None } - fn tick<'a>(&'a self, _caller: &'a Arc, _server: &'a Server) { + fn tick(&self, _caller: &dyn EntityBase, _server: &Server) { self.vehicle.tick(); let underwater = self.ticks_underwater.load(); diff --git a/crates/pumpkin/src/entity/vehicle/minecart.rs b/crates/pumpkin/src/entity/vehicle/minecart.rs index f833a31c1..a9cee5a95 100644 --- a/crates/pumpkin/src/entity/vehicle/minecart.rs +++ b/crates/pumpkin/src/entity/vehicle/minecart.rs @@ -132,7 +132,7 @@ impl EntityBase for MinecartEntity { } #[allow(clippy::too_many_lines)] - fn tick<'a>(&'a self, caller: &'a Arc, _server: &'a Server) { + fn tick(&self, caller: &dyn EntityBase, _server: &Server) { self.vehicle.tick(); if let MinecartKind::Furnace(minecart) = &self.kind { minecart.tick(&self.vehicle.entity); @@ -222,13 +222,11 @@ impl EntityBase for MinecartEntity { if !p_ids.is_empty() { let world = self.vehicle.entity.world.load(); let vid = self.vehicle.entity.entity_id; - tokio::spawn(async move { - if let Some(v) = world.get_entity_by_id(vid) { - for pid in p_ids { - v.get_entity().remove_passenger(pid).await; - } + if let Some(v) = world.get_entity_by_id(vid) { + for pid in p_ids { + v.get_entity().remove_passenger_sync(pid); } - }); + } } } if self.vehicle.get_hurt_time() == 0 { @@ -503,7 +501,7 @@ impl EntityBase for MinecartEntity { } #[allow(clippy::too_many_lines)] - fn push(&self, entity: &Arc) { + fn push(&self, entity: &dyn EntityBase) { let self_entity = self.get_entity(); let other_entity = entity.get_entity(); @@ -800,7 +798,7 @@ impl EntityBase for MinecartEntity { } } - fn move_entity(&self, caller: &Arc, motion: Vector3) { + fn move_entity(&self, caller: &dyn EntityBase, motion: Vector3) { let to_position = self.vehicle.entity.pos.load().add(&motion); self.vehicle.entity.move_entity(caller, motion); let entity_id = self.vehicle.entity.entity_id; diff --git a/crates/pumpkin/src/lib.rs b/crates/pumpkin/src/lib.rs index 2ac9d7d37..3a5cb3b97 100644 --- a/crates/pumpkin/src/lib.rs +++ b/crates/pumpkin/src/lib.rs @@ -435,7 +435,7 @@ impl PumpkinServer { "The input is not a TTY; falling back to simple logger and ignoring `use_tty` setting" ); } - setup_stdin_console(self.server.clone()); + setup_stdin_console(&self.server); } } @@ -479,7 +479,6 @@ impl PumpkinServer { .server .player_data_storage .save_all_players(&self.server) - .await { error!("Error saving all players during shutdown: {e}"); } @@ -586,11 +585,12 @@ impl PumpkinServer { } player.remove().await; server_clone.remove_player(&player).await; - if let Err(e) = server_clone.player_data_storage + if let Err(e) = server_clone + .player_data_storage .handle_player_leave(&player) - .await { - error!("Failed to save player data on disconnect: {e}"); - } + { + error!("Failed to save player data on disconnect: {e}"); + } if let Err(e) = server_clone.advancement_manager .save_player(&player) .await { @@ -686,10 +686,7 @@ impl PumpkinServer { client.await_tasks().await; player.remove().await; server.remove_player(&player).await; - if let Err(error) = server - .player_data_storage - .handle_player_leave(&player) - .await + if let Err(error) = server.player_data_storage.handle_player_leave(&player) { error!("Failed to save player data on disconnect: {error}"); } @@ -700,7 +697,7 @@ impl PumpkinServer { } } -fn setup_stdin_console(server: Arc) { +fn setup_stdin_console(server: &Arc) { let (tx, mut rx) = tokio::sync::mpsc::channel(1); let rt = tokio::runtime::Handle::current(); std::thread::spawn(move || { @@ -720,15 +717,19 @@ fn setup_stdin_console(server: Arc) { let _ = rt.block_on(tx.send(line.trim().to_string())); } }); - tokio::spawn(async move { + let server_clone = server.clone(); + server.spawn_task(async move { while !SHOULD_STOP.load(Ordering::Relaxed) && let Some(command) = rx.recv().await { let mut event = ServerCommandEvent::new(command.clone()); - server.plugin_manager.fire(&server, &mut event).await; + server_clone + .plugin_manager + .fire(&server_clone, &mut event) + .await; if !event.cancelled { - server.command_dispatcher.load().handle_command( - &command::CommandSender::Console.into_source(&server), + server_clone.command_dispatcher.load().handle_command( + &command::CommandSender::Console.into_source(&server_clone), command.as_str(), ); } diff --git a/crates/pumpkin/src/net/bedrock/mod.rs b/crates/pumpkin/src/net/bedrock/mod.rs index 49c1bc8e7..6f73e4388 100644 --- a/crates/pumpkin/src/net/bedrock/mod.rs +++ b/crates/pumpkin/src/net/bedrock/mod.rs @@ -343,26 +343,32 @@ impl BedrockClient { let cache_enabled = server.advanced_config.networking.bedrock.chunk_caching && self.client_cache_supported.load(Ordering::Relaxed); - let mut serialize_tasks = Vec::with_capacity(valid_chunks.len()); - for chunk in valid_chunks { - let block_actors = player.world().bedrock_chunk_block_actors(&chunk); - serialize_tasks.push(tokio::task::spawn_blocking(move || { - CLevelChunk::encode_chunk(&chunk, bedrock_dimension, cache_enabled, &block_actors) - })); - } - - let mut encoded_payloads = Vec::with_capacity(serialize_tasks.len()); - let mut new_blobs = Vec::new(); - for task in serialize_tasks { - match task.await { - Ok(Ok((payload, blobs))) => { - encoded_payloads.push(payload); - new_blobs.extend(blobs); + let world = player.world(); + let (tx, rx) = tokio::sync::oneshot::channel(); + rayon::spawn(move || { + let mut encoded_payloads = Vec::with_capacity(valid_chunks.len()); + let mut new_blobs = Vec::new(); + for chunk in valid_chunks { + let block_actors = world.bedrock_chunk_block_actors(&chunk); + match CLevelChunk::encode_chunk( + &chunk, + bedrock_dimension, + cache_enabled, + &block_actors, + ) { + Ok((payload, blobs)) => { + encoded_payloads.push(payload); + new_blobs.extend(blobs); + } + Err(e) => error!("Failed to serialize Bedrock chunk: {:?}", e), } - Ok(Err(e)) => error!("Failed to serialize Bedrock chunk: {:?}", e), - Err(e) => error!("Join error in Bedrock chunk serialization: {:?}", e), } - } + let _ = tx.send((encoded_payloads, new_blobs)); + }); + + let Ok((encoded_payloads, new_blobs)) = rx.await else { + return; + }; if !new_blobs.is_empty() { let mut cache = self.blob_cache.lock().await; @@ -528,11 +534,8 @@ impl BedrockClient { self.close_token.is_cancelled() || self.session.is_closed() } - pub fn enqueue_spawn_packet(self: &Arc, entity: Arc) { - let client = self.clone(); - self.spawn_task(async move { - entity.send_bedrock_spawn_packet(&client).await; - }); + pub fn enqueue_spawn_packet(&self, entity: &dyn crate::entity::EntityBase) { + entity.send_bedrock_spawn_packet(self); } async fn process_batch( diff --git a/crates/pumpkin/src/net/bedrock/play/player_action.rs b/crates/pumpkin/src/net/bedrock/play/player_action.rs index 2e4be1435..6ea773434 100644 --- a/crates/pumpkin/src/net/bedrock/play/player_action.rs +++ b/crates/pumpkin/src/net/bedrock/play/player_action.rs @@ -45,7 +45,7 @@ impl BedrockClient { if player.gamemode.load() == GameMode::Creative { let new_state = world.break_block( &location, - Some(player.clone()), + Some(player), BlockFlags::NOTIFY_NEIGHBORS | BlockFlags::SKIP_DROPS, ); if new_state.is_some() { @@ -59,11 +59,12 @@ impl BedrockClient { player.stop_mining(); let broken_state = world.get_block_state(&location); let can_harvest = player.can_harvest(broken_state, block); - let new_state = world.break_block( - &location, - Some(player.clone()), - BlockFlags::NOTIFY_NEIGHBORS, - ); + let flags = if can_harvest { + BlockFlags::NOTIFY_NEIGHBORS + } else { + BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS + }; + let new_state = world.break_block(&location, Some(player), flags); if new_state.is_some() { server.block_registry.broken( &world, @@ -75,7 +76,7 @@ impl BedrockClient { ); player.apply_tool_damage_for_block_break(broken_state); if can_harvest { - player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await; + player.add_exhaustion(MINE_BLOCK_EXHAUSTION); } } } else { @@ -157,16 +158,13 @@ impl BedrockClient { } else { BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS }; - if world - .break_block(&location, Some(player.clone()), flags) - .is_some() - { + if world.break_block(&location, Some(player), flags).is_some() { server .block_registry .broken(&world, block, player, &location, server, state); player.apply_tool_damage_for_block_break(state); if can_harvest { - player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await; + player.add_exhaustion(MINE_BLOCK_EXHAUSTION); } } } else { diff --git a/crates/pumpkin/src/net/bedrock/play/player_auth_input.rs b/crates/pumpkin/src/net/bedrock/play/player_auth_input.rs index 0fa5d82bc..2a1a17f85 100644 --- a/crates/pumpkin/src/net/bedrock/play/player_auth_input.rs +++ b/crates/pumpkin/src/net/bedrock/play/player_auth_input.rs @@ -145,7 +145,7 @@ impl BedrockClient { if pos_changed { chunker::update_position(player).await; - player.progress_motion(delta).await; + player.progress_motion(delta); } } diff --git a/crates/pumpkin/src/net/java/config/known_packs.rs b/crates/pumpkin/src/net/java/config/known_packs.rs index c347f569a..5f2ac435d 100644 --- a/crates/pumpkin/src/net/java/config/known_packs.rs +++ b/crates/pumpkin/src/net/java/config/known_packs.rs @@ -13,57 +13,52 @@ impl JavaClient { if version.supports_configuration_state() { self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()])) .await; - let registry_packets = tokio::task::spawn_blocking(move || { - let registry = Registry::get_synced(version); - let mut packets = Vec::new(); - let mut sent_dimension_type = false; - for reg in ®istry { - if reg.registry_id == "minecraft:dimension_type" { - sent_dimension_type = true; - } - let packet = CRegistryData::new(®.registry_id, ®.registry_entries); - if let Ok(data) = Self::serialize_packet_for_version(&packet, version) { - packets.push(data); - } + let registry = Registry::get_synced(version); + let mut packets = Vec::new(); + let mut sent_dimension_type = false; + for reg in ®istry { + if reg.registry_id == "minecraft:dimension_type" { + sent_dimension_type = true; } - if !sent_dimension_type { - let dims = [ - &pumpkin_data::dimension::Dimension::OVERWORLD, - &pumpkin_data::dimension::Dimension::OVERWORLD_CAVES, - &pumpkin_data::dimension::Dimension::THE_END, - &pumpkin_data::dimension::Dimension::THE_NETHER, - ]; - let dim_entries: Vec = dims - .iter() - .map(|dim| pumpkin_data::registry::RegistryEntryData { - entry_id: dim.minecraft_name.to_string(), - data: Some(build_dimension_nbt(dim).into_boxed_slice()), - }) - .collect(); - let dim_type = "minecraft:dimension_type".to_string(); - let packet = CRegistryData::new(&dim_type, &dim_entries); - if let Ok(data) = Self::serialize_packet_for_version(&packet, version) { - packets.push(data); - } - } - let mut tags = Vec::new(); - for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS { - if pumpkin_data::tag::get_registry_key_tags(version, key) - .is_some_and(|map| !map.is_empty()) - { - tags.push(key); - } - } - let packet = CUpdateTags::new(&tags); + let packet = CRegistryData::new(®.registry_id, ®.registry_entries); if let Ok(data) = Self::serialize_packet_for_version(&packet, version) { packets.push(data); } - packets - }) - .await - .unwrap_or_default(); + } + if !sent_dimension_type { + let dims = [ + &pumpkin_data::dimension::Dimension::OVERWORLD, + &pumpkin_data::dimension::Dimension::OVERWORLD_CAVES, + &pumpkin_data::dimension::Dimension::THE_END, + &pumpkin_data::dimension::Dimension::THE_NETHER, + ]; + let dim_entries: Vec = dims + .iter() + .map(|dim| pumpkin_data::registry::RegistryEntryData { + entry_id: dim.minecraft_name.to_string(), + data: Some(build_dimension_nbt(dim).into_boxed_slice()), + }) + .collect(); + let dim_type = "minecraft:dimension_type".to_string(); + let packet = CRegistryData::new(&dim_type, &dim_entries); + if let Ok(data) = Self::serialize_packet_for_version(&packet, version) { + packets.push(data); + } + } + let mut tags = Vec::new(); + for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS { + if pumpkin_data::tag::get_registry_key_tags(version, key) + .is_some_and(|map| !map.is_empty()) + { + tags.push(key); + } + } + let packet = CUpdateTags::new(&tags); + if let Ok(data) = Self::serialize_packet_for_version(&packet, version) { + packets.push(data); + } - for packet_data in registry_packets { + for packet_data in packets { self.send_packet_now(packet_data).await; } } diff --git a/crates/pumpkin/src/net/java/mod.rs b/crates/pumpkin/src/net/java/mod.rs index f14d9395e..ad4f2e6ce 100644 --- a/crates/pumpkin/src/net/java/mod.rs +++ b/crates/pumpkin/src/net/java/mod.rs @@ -358,66 +358,66 @@ impl JavaClient { } let version = self.version.load(); - // Offload CPU-heavy packet serialization (palette packing, lighting, NBT heightmaps) - // off Tokio worker threads onto blocking/Rayon worker threads - let serialize_tasks: Vec<_> = valid_chunks - .into_iter() - .map(|chunk| { - tokio::task::spawn_blocking(move || { - let mut buf = Vec::with_capacity(32 * 1024); - if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) { - error!("Failed to write chunk data id: {err:?}"); - return None; - } - if let Err(err) = CChunkData(&chunk).write_packet_data(&mut buf, &version) { - error!("Failed to write chunk data: {err:?}"); - return None; - } + let (tx, rx) = oneshot::channel(); + rayon::spawn(move || { + let mut serialized = Vec::with_capacity(valid_chunks.len()); + for chunk in valid_chunks { + let mut buf = Vec::with_capacity(32 * 1024); + if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) { + error!("Failed to write chunk data id: {err:?}"); + continue; + } + if let Err(err) = CChunkData(&chunk).write_packet_data(&mut buf, &version) { + error!("Failed to write chunk data: {err:?}"); + continue; + } - let light_buf = if version >= JavaMinecraftVersion::V_1_14 - && version < JavaMinecraftVersion::V_1_18 - { - match CLightUpdate::from_chunk(&chunk, version) { - Ok(light_packet) => { - let mut light_buf = Vec::new(); - if let Err(err) = - light_buf.write_var_int(&VarInt(CLightUpdate::to_id(version))) - { - error!("Failed to write light update id: {err:?}"); - None - } else if let Err(err) = - light_packet.write_packet_data(&mut light_buf, &version) - { - error!("Failed to write light update data: {err:?}"); - None - } else { - Some(Bytes::from(light_buf)) - } - } - Err(err) => { - error!("Failed to create light update packet: {err:?}"); + let light_buf = if version >= JavaMinecraftVersion::V_1_14 + && version < JavaMinecraftVersion::V_1_18 + { + match CLightUpdate::from_chunk(&chunk, version) { + Ok(light_packet) => { + let mut light_buf = Vec::new(); + if let Err(err) = + light_buf.write_var_int(&VarInt(CLightUpdate::to_id(version))) + { + error!("Failed to write light update id: {err:?}"); None + } else if let Err(err) = + light_packet.write_packet_data(&mut light_buf, &version) + { + error!("Failed to write light update data: {err:?}"); + None + } else { + Some(Bytes::from(light_buf)) } } - } else { - None - }; + Err(err) => { + error!("Failed to create light update packet: {err:?}"); + None + } + } + } else { + None + }; - Some((Bytes::from(buf), light_buf)) - }) - }) - .collect(); + serialized.push((Bytes::from(buf), light_buf)); + } + let _ = tx.send(serialized); + }); + + let Ok(serialized) = rx.await else { + return; + }; if version >= JavaMinecraftVersion::V_1_20_2 { self.send_packet(&CChunkBatchStart).await; } - for task in serialize_tasks { - if let Ok(Some((chunk_data, light_data))) = task.await { - self.send_packet_now_data(chunk_data).await; - if let Some(light_data) = light_data { - self.send_packet_now_data(light_data).await; - } + for (chunk_data, light_data) in serialized { + self.enqueue_packet(chunk_data).await; + if let Some(light_data) = light_data { + self.enqueue_packet(light_data).await; } } diff --git a/crates/pumpkin/src/net/java/play/player_action.rs b/crates/pumpkin/src/net/java/play/player_action.rs index 7b719ceb2..8adee03e4 100644 --- a/crates/pumpkin/src/net/java/play/player_action.rs +++ b/crates/pumpkin/src/net/java/play/player_action.rs @@ -78,7 +78,7 @@ impl JavaClient { // Block break & play sound let new_state = world.break_block( &position, - Some(player.clone()), + Some(player), BlockFlags::NOTIFY_NEIGHBORS | BlockFlags::SKIP_DROPS, ); if new_state.is_some() { @@ -100,11 +100,12 @@ impl JavaClient { if speed >= 1.0 { let broken_state = world.get_block_state(&position); let can_harvest = player.can_harvest(broken_state, block); - let new_state = world.break_block( - &position, - Some(player.clone()), - BlockFlags::NOTIFY_NEIGHBORS, - ); + let flags = if can_harvest { + BlockFlags::NOTIFY_NEIGHBORS + } else { + BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS + }; + let new_state = world.break_block(&position, Some(player), flags); if new_state.is_some() { server.block_registry.broken( &world, @@ -116,7 +117,7 @@ impl JavaClient { ); player.apply_tool_damage_for_block_break(broken_state); if can_harvest { - player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await; + player.add_exhaustion(MINE_BLOCK_EXHAUSTION); } let item_id = player.inventory().held_item().item.id; player.increment_stat(StatisticCategory::Used, item_id as i32, 1); @@ -195,7 +196,7 @@ impl JavaClient { let new_state = world.break_block( &location, - Some(player.clone()), + Some(player), if block_drop { BlockFlags::NOTIFY_NEIGHBORS } else { @@ -209,7 +210,7 @@ impl JavaClient { player.apply_tool_damage_for_block_break(state); if block_drop { - player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await; + player.add_exhaustion(MINE_BLOCK_EXHAUSTION); } let item_id = player.inventory().held_item().item.id; player.increment_stat(StatisticCategory::Used, item_id as i32, 1); diff --git a/crates/pumpkin/src/net/java/play/player_position.rs b/crates/pumpkin/src/net/java/play/player_position.rs index 74260fe60..517ef1af6 100644 --- a/crates/pumpkin/src/net/java/play/player_position.rs +++ b/crates/pumpkin/src/net/java/play/player_position.rs @@ -105,7 +105,7 @@ impl JavaClient { let height_difference = pos.y - last_pos.y; if entity.on_ground.load(Ordering::Relaxed) && packet.collision & FLAG_ON_GROUND == 0 && height_difference > 0.0 { - player.jump().await; + player.jump(); } let new_on_ground = packet.collision & FLAG_ON_GROUND != 0; @@ -167,7 +167,7 @@ impl JavaClient { if delta.length_squared() > 1.0E-5 { player.update_last_action_time(); } - player.progress_motion(delta).await; + player.progress_motion(delta); } 'cancelled: { @@ -247,7 +247,7 @@ impl JavaClient { && (packet.collision & FLAG_ON_GROUND) != 0 && height_difference > 0.0 { - player.jump().await; + player.jump(); } entity .on_ground @@ -324,7 +324,7 @@ impl JavaClient { if delta.length_squared() > 1.0E-5 { player.update_last_action_time(); } - player.progress_motion(delta).await; + player.progress_motion(delta); } 'cancelled: { diff --git a/crates/pumpkin/src/net/java/play/spectate_entity.rs b/crates/pumpkin/src/net/java/play/spectate_entity.rs index 43a7ec84b..5e90eebea 100644 --- a/crates/pumpkin/src/net/java/play/spectate_entity.rs +++ b/crates/pumpkin/src/net/java/play/spectate_entity.rs @@ -31,9 +31,7 @@ impl JavaClient { .send_client_packet(&CSetCamera::new(target_id.into())) .await; - player - .request_teleport(target_pos, target_yaw, target_pitch) - .await; + player.request_teleport(target_pos, target_yaw, target_pitch); } else if let Some(target_player) = server.get_player_by_uuid(packet.target) { let target_pos = target_player.living_entity.entity.pos.load(); let target_yaw = target_player.living_entity.entity.yaw.load(); @@ -45,9 +43,7 @@ impl JavaClient { .send_client_packet(&CSetCamera::new(target_id.into())) .await; - player - .request_teleport(target_pos, target_yaw, target_pitch) - .await; + player.request_teleport(target_pos, target_yaw, target_pitch); } } } diff --git a/crates/pumpkin/src/net/java/play/teleport_to_entity.rs b/crates/pumpkin/src/net/java/play/teleport_to_entity.rs index 95234cfdf..ae0088ebf 100644 --- a/crates/pumpkin/src/net/java/play/teleport_to_entity.rs +++ b/crates/pumpkin/src/net/java/play/teleport_to_entity.rs @@ -28,9 +28,7 @@ impl JavaClient { .send_client_packet(&CSetCamera::new(target_id.into())) .await; - player - .request_teleport(target_pos, target_yaw, target_pitch) - .await; + player.request_teleport(target_pos, target_yaw, target_pitch); } } } diff --git a/crates/pumpkin/src/net/mod.rs b/crates/pumpkin/src/net/mod.rs index 69e8d4b90..a7804b1bc 100644 --- a/crates/pumpkin/src/net/mod.rs +++ b/crates/pumpkin/src/net/mod.rs @@ -251,31 +251,13 @@ impl ClientPlatform { } pub fn try_enqueue_spawn_packet(&self, entity: &Arc) { - match self { - Self::Java(java) => { - let ent = entity.get_entity(); - let version = java.version.load(); - let is_mob = ent.entity_type.mob || entity.get_mob().is_some(); - if version < JavaMinecraftVersion::V_1_19 && is_mob { - let packet = ent.create_spawn_living_packet(None); - if let Ok(data) = java.serialize_packet(&packet) { - java.try_enqueue_packet(data); - } - } else { - let packet = ent.create_spawn_packet(); - if let Ok(data) = java.serialize_packet(&packet) { - java.try_enqueue_packet(data); - } - } - } - Self::Bedrock(bedrock) => bedrock.enqueue_spawn_packet(entity.clone()), - } + self.enqueue_spawn_packet(entity); } - pub async fn enqueue_spawn_packet(&self, entity: &Arc) { + pub fn enqueue_spawn_packet(&self, entity: &Arc) { match self { - Self::Java(java) => entity.send_java_spawn_packet(java).await, - Self::Bedrock(bedrock) => entity.send_bedrock_spawn_packet(bedrock).await, + Self::Java(java) => entity.send_java_spawn_packet(java), + Self::Bedrock(bedrock) => entity.send_bedrock_spawn_packet(bedrock), } } diff --git a/crates/pumpkin/src/plugin/cache.rs b/crates/pumpkin/src/plugin/cache.rs index b8b9a1bd8..583fccae4 100644 --- a/crates/pumpkin/src/plugin/cache.rs +++ b/crates/pumpkin/src/plugin/cache.rs @@ -30,13 +30,8 @@ impl PermissionCache { } pub async fn calculate_hash(path: &Path) -> tokio::io::Result { - let path = path.to_path_buf(); - tokio::task::spawn_blocking(move || { - let bytes = std::fs::read(path)?; - Ok(calculate_hash_for_bytes(&bytes)) - }) - .await - .map_err(std::io::Error::other)? + let bytes = fs::read(path).await?; + Ok(calculate_hash_for_bytes(&bytes)) } #[must_use] diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs index b971533e1..98476dfc9 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs @@ -178,14 +178,12 @@ impl HostEntity for PluginHostState { ) .map_err(|_| wasmtime::Error::msg("invalid world resource handle"))?; let world = world.provider.clone(); - entity_base - .teleport( - pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2), - None, - None, - world, - ) - .await; + entity_base.teleport( + pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2), + None, + None, + world, + ); Ok(()) } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mob.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mob.rs index baadc501b..c20a9d115 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mob.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mob.rs @@ -224,45 +224,48 @@ impl Goal for CustomWasmGoal { if let Some(entity_arc) = current_mob_entity(mob) { let plugin = self.plugin.clone(); let goal_id = self.goal_id; - tokio::spawn(async move { - let mut store = plugin.store.lock().await; - match plugin.plugin_instance { - PluginInstance::V0_1(ref plugin_inst) => { - let Some(server) = store.data_mut().server.clone() else { - return; - }; - let Ok(server_res) = store.data_mut().add_server(server) else { - return; - }; - let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else { + let world = entity_arc.get_entity().world.load(); + if let Some(server) = world.server.upgrade() { + server.spawn_task(async move { + let mut store = plugin.store.lock().await; + match plugin.plugin_instance { + PluginInstance::V0_1(ref plugin_inst) => { + let Some(server) = store.data_mut().server.clone() else { + return; + }; + let Ok(server_res) = store.data_mut().add_server(server) else { + return; + }; + let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else { + let _ = store + .data_mut() + .resource_table + .delete::( + wasmtime::component::Resource::new_own(server_res.rep()), + ); + return; + }; + let server_rep = server_res.rep(); + let entity_rep = entity_res.rep(); + let _ = plugin_inst + .call_handle_ai_goal_start(&mut *store, goal_id, server_res, entity_res) + .await; let _ = store .data_mut() .resource_table .delete::( - wasmtime::component::Resource::new_own(server_res.rep()), + wasmtime::component::Resource::new_own(server_rep), ); - return; - }; - let server_rep = server_res.rep(); - let entity_rep = entity_res.rep(); - let _ = plugin_inst - .call_handle_ai_goal_start(&mut *store, goal_id, server_res, entity_res) - .await; - let _ = store - .data_mut() - .resource_table - .delete::( - wasmtime::component::Resource::new_own(server_rep), - ); - let _ = store - .data_mut() - .resource_table - .delete::( - wasmtime::component::Resource::new_own(entity_rep), - ); + let _ = store + .data_mut() + .resource_table + .delete::( + wasmtime::component::Resource::new_own(entity_rep), + ); + } } - } - }); + }); + } } } @@ -270,45 +273,48 @@ impl Goal for CustomWasmGoal { if let Some(entity_arc) = current_mob_entity(mob) { let plugin = self.plugin.clone(); let goal_id = self.goal_id; - tokio::spawn(async move { - let mut store = plugin.store.lock().await; - match plugin.plugin_instance { - PluginInstance::V0_1(ref plugin_inst) => { - let Some(server) = store.data_mut().server.clone() else { - return; - }; - let Ok(server_res) = store.data_mut().add_server(server) else { - return; - }; - let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else { + let world = entity_arc.get_entity().world.load(); + if let Some(server) = world.server.upgrade() { + server.spawn_task(async move { + let mut store = plugin.store.lock().await; + match plugin.plugin_instance { + PluginInstance::V0_1(ref plugin_inst) => { + let Some(server) = store.data_mut().server.clone() else { + return; + }; + let Ok(server_res) = store.data_mut().add_server(server) else { + return; + }; + let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else { + let _ = store + .data_mut() + .resource_table + .delete::( + wasmtime::component::Resource::new_own(server_res.rep()), + ); + return; + }; + let server_rep = server_res.rep(); + let entity_rep = entity_res.rep(); + let _ = plugin_inst + .call_handle_ai_goal_tick(&mut *store, goal_id, server_res, entity_res) + .await; let _ = store .data_mut() .resource_table .delete::( - wasmtime::component::Resource::new_own(server_res.rep()), + wasmtime::component::Resource::new_own(server_rep), ); - return; - }; - let server_rep = server_res.rep(); - let entity_rep = entity_res.rep(); - let _ = plugin_inst - .call_handle_ai_goal_tick(&mut *store, goal_id, server_res, entity_res) - .await; - let _ = store - .data_mut() - .resource_table - .delete::( - wasmtime::component::Resource::new_own(server_rep), - ); - let _ = store - .data_mut() - .resource_table - .delete::( - wasmtime::component::Resource::new_own(entity_rep), - ); + let _ = store + .data_mut() + .resource_table + .delete::( + wasmtime::component::Resource::new_own(entity_rep), + ); + } } - } - }); + }); + } } } @@ -316,45 +322,48 @@ impl Goal for CustomWasmGoal { if let Some(entity_arc) = current_mob_entity(mob) { let plugin = self.plugin.clone(); let goal_id = self.goal_id; - tokio::spawn(async move { - let mut store = plugin.store.lock().await; - match plugin.plugin_instance { - PluginInstance::V0_1(ref plugin_inst) => { - let Some(server) = store.data_mut().server.clone() else { - return; - }; - let Ok(server_res) = store.data_mut().add_server(server) else { - return; - }; - let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else { + let world = entity_arc.get_entity().world.load(); + if let Some(server) = world.server.upgrade() { + server.spawn_task(async move { + let mut store = plugin.store.lock().await; + match plugin.plugin_instance { + PluginInstance::V0_1(ref plugin_inst) => { + let Some(server) = store.data_mut().server.clone() else { + return; + }; + let Ok(server_res) = store.data_mut().add_server(server) else { + return; + }; + let Ok(entity_res) = store.data_mut().add_entity(entity_arc) else { + let _ = store + .data_mut() + .resource_table + .delete::( + wasmtime::component::Resource::new_own(server_res.rep()), + ); + return; + }; + let server_rep = server_res.rep(); + let entity_rep = entity_res.rep(); + let _ = plugin_inst + .call_handle_ai_goal_stop(&mut *store, goal_id, server_res, entity_res) + .await; let _ = store .data_mut() .resource_table .delete::( - wasmtime::component::Resource::new_own(server_res.rep()), + wasmtime::component::Resource::new_own(server_rep), ); - return; - }; - let server_rep = server_res.rep(); - let entity_rep = entity_res.rep(); - let _ = plugin_inst - .call_handle_ai_goal_stop(&mut *store, goal_id, server_res, entity_res) - .await; - let _ = store - .data_mut() - .resource_table - .delete::( - wasmtime::component::Resource::new_own(server_rep), - ); - let _ = store - .data_mut() - .resource_table - .delete::( - wasmtime::component::Resource::new_own(entity_rep), - ); + let _ = store + .data_mut() + .resource_table + .delete::( + wasmtime::component::Resource::new_own(entity_rep), + ); + } } - } - }); + }); + } } } } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs index a50a41df3..a9b5010ab 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs @@ -2190,9 +2190,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { ) -> wasmtime::Result<()> { let player = player_from_resource(self, &player)?; let world = world_from_resource(self, &world); - player - .teleport(from_wasm_position(position), yaw, pitch, world) - .await; + player.teleport(from_wasm_position(position), yaw, pitch, world); Ok(()) } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs index 63ddfa3d4..a5e858fd7 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs @@ -237,7 +237,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState { Dimension::End => pumpkin_data::dimension::Dimension::THE_END, }; - let world = server.create_world(name, internal_dim).await; + let world = server.create_world(name, internal_dim); self.add_world(world) .map_err(|_| wasmtime::Error::msg("failed to add world resource")) } diff --git a/crates/pumpkin/src/plugin/mod.rs b/crates/pumpkin/src/plugin/mod.rs index ce6171ec9..1426816d4 100644 --- a/crates/pumpkin/src/plugin/mod.rs +++ b/crates/pumpkin/src/plugin/mod.rs @@ -295,8 +295,8 @@ impl PluginManager { .map_err(|e| ManagerError::IoError(std::io::Error::other(e)))?; let manager = self.clone(); - let server = server.clone(); - let task = tokio::spawn(async move { + let server_clone = Arc::clone(server); + let task = server.spawn_task(async move { // Keep watcher alive by moving it into the task let _watcher = watcher; @@ -333,7 +333,9 @@ impl PluginManager { // For now, we just try to load it. If it's already loaded, // the loader might handle it or we might get a duplicate. // Most WASM loaders will just create a new instance. - if let Err(e) = manager.start_loading_plugin(&server, &path).await { + if let Err(e) = + manager.start_loading_plugin(&server_clone, &path).await + { error!("Failed to hot-reload plugin {:?}: {}", path, e); } } @@ -542,7 +544,7 @@ impl PluginManager { let context = Arc::new(Context::new( metadata.clone(), - server, + server.clone(), Arc::clone(&self.handlers), Arc::clone(self), Arc::clone(&LOGGER_IMPL), @@ -571,7 +573,7 @@ impl PluginManager { let plugin_name = metadata.name.clone(); let loader_clone = loader.clone(); - let task = tokio::spawn(async move { + let task = server.spawn_task(async move { // Initialize the plugin match instance.on_load(context.clone()).await { Ok(()) => { @@ -616,9 +618,7 @@ impl PluginManager { // Try to unload the plugin data if let Some(data) = loader_data { - tokio::spawn(async move { - loader_clone.unload(data).await.ok(); - }); + loader_clone.unload(data).await.ok(); } { diff --git a/crates/pumpkin/src/server/mod.rs b/crates/pumpkin/src/server/mod.rs index f034ca79b..02f6151f1 100644 --- a/crates/pumpkin/src/server/mod.rs +++ b/crates/pumpkin/src/server/mod.rs @@ -317,30 +317,24 @@ impl Server { // Fetch / generate keys in background tasks to avoid blocking startup let server_clone = server.clone(); - tokio::spawn(async move { - let key_store = tokio::task::spawn_blocking(|| Arc::new(KeyStore::new())) - .await - .unwrap_or_else(|_| Arc::new(KeyStore::new())); + server.spawn_task(async move { + let key_store = Arc::new(KeyStore::new()); let _ = server_clone.key_store.set(key_store); }); if server.basic_config.allow_chat_reports { let server_clone = server.clone(); - tokio::spawn(async move { + server.spawn_task(async move { let auth_config = server_clone .advanced_config .networking .java .authentication .clone(); - let keys = tokio::task::spawn_blocking(move || { - fetch_mojang_public_keys(&auth_config).unwrap_or_else(|e| { - error!("Failed to fetch Mojang keys: {e}"); - Vec::new() - }) - }) - .await - .unwrap_or_default(); + let keys = fetch_mojang_public_keys(&auth_config).unwrap_or_else(|e| { + error!("Failed to fetch Mojang keys: {e}"); + Vec::new() + }); server_clone.mojang_public_keys.store(Arc::new(keys)); }); } @@ -354,68 +348,47 @@ impl Server { .enabled { let server_clone = server.clone(); - tokio::spawn(async move { + server.spawn_task(async move { let auth = server_clone .advanced_config .networking .bedrock .authentication .clone(); - let keys = match tokio::task::spawn_blocking(move || { - pumpkin_util::jwt::fetch_oidc_jwks( - auth.url.as_deref(), - auth.connect_timeout, - auth.read_timeout, - ) - }) - .await - { - Ok(Ok(keys)) => keys, - Ok(Err(error)) => { + let keys = match pumpkin_util::jwt::fetch_oidc_jwks( + auth.url.as_deref(), + auth.connect_timeout, + auth.read_timeout, + ) { + Ok(keys) => keys, + Err(error) => { error!("Failed to fetch Bedrock OIDC keys: {error}"); (String::new(), pumpkin_util::jwt::Jwks { keys: Vec::new() }) } - Err(join_err) => { - error!("Bedrock OIDC key task failed: {join_err}"); - (String::new(), pumpkin_util::jwt::Jwks { keys: Vec::new() }) - } }; let _ = server_clone.bedrock_oidc_keys.set(keys); }); } - let world_loader = |dim: Dimension| { - let path = world_path.clone(); - let registry = block_registry.clone(); - let l_info = server.level_info.clone(); // Access from struct - let weak = Arc::downgrade(&server); - let config = Arc::new(server.advanced_config.world.clone()); - - tokio::task::spawn_blocking(move || { - info!( - "Loading {}", - TextComponent::text(dim.minecraft_name.to_string()) - .color_named(NamedColor::DarkGreen) - .to_pretty_console() - ); - let level = into_level(dim.clone(), &config, path, seed); - let world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak)); - let portal: Arc = Arc::new(WorldPortal(world.clone())); - level.world_portal.store(Arc::new(Some(portal))); - world - }) - }; - - info!("Starting parallel world load..."); - let mut world_futures = Vec::new(); - for dim in &server.dimensions { - world_futures.push(world_loader(dim.clone())); - } - - let worlds_results = futures::future::join_all(world_futures).await; - let mut worlds_vec = Vec::new(); - for world in worlds_results.into_iter().flatten() { + for dim in &server.dimensions { + info!( + "Loading {}", + TextComponent::text(dim.minecraft_name.to_string()) + .color_named(NamedColor::DarkGreen) + .to_pretty_console() + ); + let config = Arc::new(server.advanced_config.world.clone()); + let level = into_level(dim.clone(), &config, world_path.clone(), seed); + let world = Arc::new(World::load( + level.clone(), + server.level_info.clone(), + dim.clone(), + block_registry.clone(), + Arc::downgrade(&server), + )); + let portal: Arc = Arc::new(WorldPortal(world.clone())); + level.world_portal.store(Arc::new(Some(portal))); worlds_vec.push(world); } @@ -475,7 +448,7 @@ impl Server { }) } - pub async fn create_world(self: &Arc, name: String, dimension: Dimension) -> Arc { + pub fn create_world(self: &Arc, name: String, dimension: Dimension) -> Arc { { let worlds = self.worlds.load(); let world = worlds @@ -487,37 +460,28 @@ impl Server { } } - let server = self.clone(); - let name_clone = name.clone(); - tokio::task::spawn_blocking(move || { - let world_path = server.basic_config.get_world_path().join(name_clone); - let registry = server.block_registry.clone(); - let l_info = server.level_info.clone(); - let weak = Arc::downgrade(&server); - let config = Arc::new(server.advanced_config.world.clone()); - let seed = server.level_info.load().world_gen_settings.seed; + let world_path = self.basic_config.get_world_path().join(name); + let registry = self.block_registry.clone(); + let l_info = self.level_info.clone(); + let weak = Arc::downgrade(self); + let config = Arc::new(self.advanced_config.world.clone()); + let seed = self.level_info.load().world_gen_settings.seed; - let level = - pumpkin_world::dimension::into_level(dimension.clone(), &config, world_path, seed); - let world: World = World::load(level.clone(), l_info, dimension, registry, weak); - let world = Arc::new(world); - let portal: Arc = Arc::new(WorldPortal(world.clone())); - level.world_portal.store(Arc::new(Some(portal))); - server.worlds.rcu(|worlds| { - let mut new_worlds = (**worlds).clone(); - new_worlds.push(world.clone()); - new_worlds - }); - let mut event = - crate::plugin::api::events::world::world_init::WorldInitEvent::new(world.clone()); - server.plugin_manager.fire_blocking(&server, &mut event); - world - }) - .await - .unwrap_or_else(|_| { - error!("World creation failed"); - std::process::exit(1); - }) + let level = + pumpkin_world::dimension::into_level(dimension.clone(), &config, world_path, seed); + let world: World = World::load(level.clone(), l_info, dimension, registry, weak); + let world = Arc::new(world); + let portal: Arc = Arc::new(WorldPortal(world.clone())); + level.world_portal.store(Arc::new(Some(portal))); + self.worlds.rcu(|worlds| { + let mut new_worlds = (**worlds).clone(); + new_worlds.push(world.clone()); + new_worlds + }); + let mut event = + crate::plugin::api::events::world::world_init::WorldInitEvent::new(world.clone()); + self.plugin_manager.fire_blocking(self, &mut event); + world } pub async fn unload_world(&self, name: &str) -> Result<(), String> { @@ -590,7 +554,7 @@ impl Server { return Err(format!("Failed to save world info: {err}")); } - if let Err(err) = self.player_data_storage.save_all_players(self).await { + if let Err(err) = self.player_data_storage.save_all_players(self) { error!("Failed to save player data: {err}"); return Err(format!("Failed to save player data: {err}")); } @@ -650,33 +614,31 @@ impl Server { let first_world = self.worlds.load().first().cloned()?; - let (world, nbt) = - if let Ok(Some(data)) = self.player_data_storage.load_data(&profile.id).await { - if let Some(dimension_key) = data.get_string("Dimension") { - if let Some(dimension) = Dimension::from_name(dimension_key) { - let world = self.get_world_from_dimension(dimension); - (world, Some(data)) - } else { - warn!("Invalid dimension key in player data: {dimension_key}"); - (first_world, Some(data)) - } + let (world, nbt) = if let Ok(Some(data)) = self.player_data_storage.load_data(&profile.id) { + if let Some(dimension_key) = data.get_string("Dimension") { + if let Some(dimension) = Dimension::from_name(dimension_key) { + let world = self.get_world_from_dimension(dimension); + (world, Some(data)) } else { - // Player data exists but doesn't have a "Dimension" key. + warn!("Invalid dimension key in player data: {dimension_key}"); (first_world, Some(data)) } } else { - // No player data found or an error occurred, default to the Overworld. - (first_world, None) - }; + // Player data exists but doesn't have a "Dimension" key. + (first_world, Some(data)) + } + } else { + // No player data found or an error occurred, default to the Overworld. + (first_world, None) + }; let mut player = Player::new( client, profile, config.clone().unwrap_or_default(), - world.clone(), + &world, gamemode, - ) - .await; + ); if let Some(mut nbt_data) = nbt { player.read_nbt(&mut nbt_data); @@ -1059,23 +1021,19 @@ impl Server { /// This includes player ticking (network, keep-alives) and flushing world updates to clients. pub fn tick_players_and_network(self: &Arc) { let worlds = self.worlds.load(); + let handle = self.runtime.clone(); for world in worlds.iter() { world.flush_block_updates(); world.flush_synced_block_events(); - } - let mut all_players = Vec::new(); - for world in worlds.iter() { let players = world.players.load(); - all_players.extend(players.iter().cloned()); + let player_handle = handle.clone(); + players.par_iter().for_each(|player| { + let _guard = player_handle.enter(); + player.tick(self); + }); } - - let handle = self.runtime.clone(); - all_players.par_iter().for_each(|player| { - let _guard = handle.enter(); - player.tick(self); - }); } /// Ticks the game logic for all worlds. This is the part that is affected by `/tick freeze`. diff --git a/crates/pumpkin/src/server/scheduler.rs b/crates/pumpkin/src/server/scheduler.rs index efda036e8..9063a3fff 100644 --- a/crates/pumpkin/src/server/scheduler.rs +++ b/crates/pumpkin/src/server/scheduler.rs @@ -162,7 +162,7 @@ impl TaskScheduler { let handler_id = task.handler_id; let server_clone = server.clone(); - tokio::spawn(async move { + server.spawn_task(async move { let mut store = plugin.store.lock().await; match plugin.plugin_instance { crate::plugin::loader::wasm::wasm_host::PluginInstance::V0_1(ref instance) => { diff --git a/crates/pumpkin/src/server/ticker.rs b/crates/pumpkin/src/server/ticker.rs index 4214a26d7..648d7b1b6 100644 --- a/crates/pumpkin/src/server/ticker.rs +++ b/crates/pumpkin/src/server/ticker.rs @@ -15,6 +15,7 @@ pub struct Ticker; impl Ticker { /// Runs the main server tick loop on a dedicated thread. pub fn run(server: &Arc) { + let _guard = server.runtime.enter(); let mut next_tick = Instant::now(); 'ticker: loop { diff --git a/crates/pumpkin/src/world/mod.rs b/crates/pumpkin/src/world/mod.rs index 552b24e5d..7a459580b 100644 --- a/crates/pumpkin/src/world/mod.rs +++ b/crates/pumpkin/src/world/mod.rs @@ -66,7 +66,6 @@ use pumpkin_data::{ entity::{EntityStatus, EntityType}, fluid::Fluid, item_stack::ItemStack, - packet::CURRENT_MC_VERSION, particle::Particle, sound::{Sound, SoundCategory}, sound_id_remap::remap_sound_id_for_version, @@ -407,7 +406,7 @@ impl World { } } - pub fn update_active_chunks(self: &Arc) { + pub fn update_active_chunks(&self) { let mut active_chunks = FxHashSet::default(); let sim_dist = self.server.upgrade().map_or(10, |s| { s.advanced_config.networking.java.simulation_distance.get() @@ -999,7 +998,7 @@ impl World { Self::collect_java_recipients_by_version(java_recipients.into_iter()); for (version, recipients) in recipients_by_version { - if version < CURRENT_MC_VERSION { + if version < JavaMinecraftVersion::V_1_21 { continue; } let mut buf = Vec::new(); @@ -1247,21 +1246,19 @@ impl World { let players = self.players.load(); let player_count = players.len(); - let players_cache = Arc::new( - players - .par_iter() - .map(|player| { - let entity = player.get_entity(); - let pos = entity.pos.load(); - let bb = entity.bounding_box.load().expand(1.0, 0.5, 1.0); - let chunk_pos = Vector2::new( - get_section_cord(pos.x.floor() as i32), - get_section_cord(pos.z.floor() as i32), - ); - (player.clone(), pos, bb, chunk_pos) - }) - .collect::>(), - ); + let players_cache: Vec<_> = players + .par_iter() + .map(|player| { + let entity = player.get_entity(); + let pos = entity.pos.load(); + let bb = entity.bounding_box.load().expand(1.0, 0.5, 1.0); + let chunk_pos = Vector2::new( + get_section_cord(pos.x.floor() as i32), + get_section_cord(pos.z.floor() as i32), + ); + (player, pos, bb, chunk_pos) + }) + .collect(); let t_players = std::time::Instant::now(); let player_handle = handle.clone(); @@ -1273,7 +1270,6 @@ impl World { let entities_to_tick = self.entities.load(); let entity_count = entities_to_tick.len(); - let server_for_entities = (*server).clone(); let active_chunks = self.active_chunks.load(); let level_for_entities = self.level.clone(); let entity_handle = handle.clone(); @@ -1293,26 +1289,25 @@ impl World { if !level_for_entities.is_chunk_loaded(&entity_chunk) { return None; } - Some((entity.clone(), entity_chunk)) + Some((entity, entity_chunk)) }) .collect(); + let server_ref = server.as_ref(); tickable .par_chunks(ENTITY_TICK_BATCH_SIZE) .for_each(|batch| { let _guard = entity_handle.enter(); - let s_clone = server_for_entities.clone(); - let p_cache = players_cache.clone(); for (entity, entity_chunk) in batch { entity.get_entity().age.fetch_add(1, Relaxed); - entity.tick(entity, &s_clone); + entity.tick(entity.as_ref(), server_ref); let entity_inner = entity.get_entity(); let entity_pos = entity_inner.pos.load(); let entity_bb = entity_inner.bounding_box.load(); - for (player, player_pos, player_bb, player_chunk) in p_cache.iter() { + for (player, player_pos, player_bb, player_chunk) in &players_cache { if (player_chunk.x - entity_chunk.x).abs() <= 1 && (player_chunk.y - entity_chunk.y).abs() <= 1 && (player_pos.x - entity_pos.x).abs() < 5.0 @@ -1337,13 +1332,11 @@ impl World { let block_entity_count = block_entities.len(); let t_be = std::time::Instant::now(); - let world_for_be = self.clone(); let be_handle = handle; block_entities.par_chunks(16).for_each(|batch| { let _guard = be_handle.enter(); - let w_clone = world_for_be.clone(); for be in batch { - be.tick(&w_clone); + be.tick(self); } }); let block_entity_elapsed = t_be.elapsed(); @@ -1568,10 +1561,12 @@ impl World { let cleaned_chunks = self.level.clean_memory(); if !cleaned_chunks.is_empty() { let world_clone = self.clone(); - tokio::spawn(async move { - world_clone.remove_entities_in_chunks(&cleaned_chunks).await; - world_clone.level.clean_entity_chunks(&cleaned_chunks); - }); + if let Some(server) = self.server.upgrade() { + server.spawn_task(async move { + world_clone.remove_entities_in_chunks(&cleaned_chunks).await; + world_clone.level.clean_entity_chunks(&cleaned_chunks); + }); + } } // If autosave is configured and this tick will trigger an autosave, don't double notify if self.level.autosave_ticks == 0 { @@ -1787,7 +1782,7 @@ impl World { }); } - pub fn check_fluid_collision(self: &Arc, bounding_box: BoundingBox) -> bool { + pub fn check_fluid_collision(&self, bounding_box: BoundingBox) -> bool { let min = bounding_box.min_block_pos(); let max = bounding_box.max_block_pos(); @@ -3149,20 +3144,16 @@ impl World { && client.version.load() >= JavaMinecraftVersion::V_1_13 { let version = client.version.load(); - if let Ok(Ok(packet_data)) = tokio::task::spawn_blocking(move || { - let mut tags = Vec::new(); - for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS { - if pumpkin_data::tag::get_registry_key_tags(version, key) - .is_some_and(|map| !map.is_empty()) - { - tags.push(key); - } + let mut tags = Vec::new(); + for &key in pumpkin_data::tag::RegistryKey::NETWORK_KEYS { + if pumpkin_data::tag::get_registry_key_tags(version, key) + .is_some_and(|map| !map.is_empty()) + { + tags.push(key); } - let packet = pumpkin_protocol::java::client::play::CUpdateTagsPlay::new(&tags); - JavaClient::serialize_packet_for_version(&packet, version) - }) - .await - { + } + let packet = pumpkin_protocol::java::client::play::CUpdateTagsPlay::new(&tags); + if let Ok(packet_data) = JavaClient::serialize_packet_for_version(&packet, version) { client.send_packet_now(packet_data).await; } } @@ -3217,7 +3208,7 @@ impl World { let velocity = player.living_entity.entity.velocity.load(); debug!("Sending player teleport to {}", player.gameprofile.name); - player.request_teleport(position, yaw, pitch).await; + player.request_teleport(position, yaw, pitch); let gameprofile = &player.gameprofile; let bedrock_player_list = CPlayerList { @@ -3574,7 +3565,7 @@ impl World { ) .await; - if client.version.load() >= CURRENT_MC_VERSION { + if client.version.load() >= JavaMinecraftVersion::V_1_21 { let config = existing_player.config.load(); let mut buf = Vec::new(); { @@ -4244,7 +4235,7 @@ impl World { } // Send teleport packet after at least the center chunk was delivered - player.request_teleport(position, yaw, pitch).await; + player.request_teleport(position, yaw, pitch); } /// Returns true if enough players are sleeping and we should skip the night. @@ -4378,7 +4369,7 @@ impl World { // stale data. base_entity.velocity.store(Vector3::default()); - player.client.enqueue_spawn_packet(&entity).await; + player.client.enqueue_spawn_packet(&entity); player.try_restore_vehicle(&entity); entities_to_add.push(entity); } @@ -4397,7 +4388,7 @@ impl World { for entity in world.entities.load().iter() { let base_entity = entity.get_entity(); if base_entity.chunk_pos.load() == position { - player.client.enqueue_spawn_packet(entity).await; + player.client.enqueue_spawn_packet(entity); player.try_restore_vehicle(entity); } } @@ -5081,15 +5072,31 @@ impl World { pub fn break_block( self: &Arc, position: &BlockPos, - _cause: Option>, + cause: Option<&Player>, flags: BlockFlags, ) -> Option { - let (broken_block, broken_block_state) = self.get_block_and_state_id(position); - if is_air(broken_block_state) { + let (broken_block, broken_block_state) = self.get_block_and_state(position); + if broken_block_state.is_air() { return None; } + + if !flags.contains(BlockFlags::SKIP_DROPS) { + let tool = cause.and_then(|p| { + let item = p.inventory().held_item(); + if item.is_empty() { None } else { Some(item) } + }); + let params = crate::world::loot::LootContextParameters { + tool, + block_state: Some(broken_block_state), + position: Some(position.to_f64()), + killed_by_player: Some(cause.is_some()), + ..Default::default() + }; + crate::block::drop_loot(self, broken_block, position, true, params); + } + let new_state_id = if broken_block - .properties(broken_block_state) + .properties(broken_block_state.id) .and_then(|properties| { properties .to_props() diff --git a/crates/pumpkin/src/world/natural_spawner.rs b/crates/pumpkin/src/world/natural_spawner.rs index 06ca03fcf..9b21dbd31 100644 --- a/crates/pumpkin/src/world/natural_spawner.rs +++ b/crates/pumpkin/src/world/natural_spawner.rs @@ -337,7 +337,7 @@ impl SpawnState { pub fn new( chunk_count: i32, entities: &ArcSwap>>, - world: &Arc, + world: &World, ) -> Self { let potential = PotentialCalculator::default(); let local_mob_cap = LocalMobCapCalculator::default(); diff --git a/crates/pumpkin/src/world/portal/mod.rs b/crates/pumpkin/src/world/portal/mod.rs index ba2d80523..7583142fe 100644 --- a/crates/pumpkin/src/world/portal/mod.rs +++ b/crates/pumpkin/src/world/portal/mod.rs @@ -72,7 +72,7 @@ impl PortalType { &self, current_level: &World, dest_world: Arc, - caller: &Arc, + caller: &dyn crate::entity::EntityBase, _portal_entry_pos: BlockPos, source_portal: Option<&SourcePortalInfo>, ) -> Option {