fix: many bugs

This commit is contained in:
Alexander Medvedev 2026-08-27 21:23:24 +02:00
parent 7034b698d0
commit 37437ee0a0
104 changed files with 2794 additions and 2005 deletions

View File

@ -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<i32> {
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<i32> {
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<i32>,
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<StructurePosition> {
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<StructureTemplate>,
middle_template: Option<Arc<StructureTemplate>>,
bottom_template: Option<Arc<StructureTemplate>>,
rotation: Rotation,
has_basement: bool,
ladder_segments: u8,
pub piece: StructurePiece,
pub template: Arc<StructureTemplate>,
pub template_name: String,
pub place_settings: StructurePlaceSettings,
pub template_position: Vector3<i32>,
}
impl IglooPiece {
#[must_use]
pub fn new(
template: Arc<StructureTemplate>,
template_name: String,
position: Vector3<i32>,
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<i32>, depth: i32) -> Vector3<i32> {
position + get_offset(template_location) - Vector3::new(0, depth, 0)
}

View File

@ -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<StructureTemplate>,
rotation: BlockRotation,
initial_y: i32,
sea_level: i32,
pub struct NetherFossilPiece {
pub piece: StructurePiece,
pub template: Arc<StructureTemplate>,
pub template_name: String,
pub place_settings: StructurePlaceSettings,
pub template_position: Vector3<i32>,
pub initial_y: i32,
pub sea_level: i32,
}
impl NetherFossilPiece {
#[must_use]
pub fn new(
template: Arc<StructureTemplate>,
template_name: String,
template_position: Vector3<i32>,
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<i32> {
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,
},
]))
}

View File

@ -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<StructureTemplate>,
rotation: Rotation,
variant: pumpkin_data::structures::StructureKeys,
pub piece: StructurePiece,
pub template: Arc<StructureTemplate>,
pub template_name: String,
pub place_settings: StructurePlaceSettings,
pub template_position: Vector3<i32>,
pub vertical_placement: VerticalPlacement,
pub properties: RuinedPortalProperties,
}
impl RuinedPortalPiece {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
template: Arc<StructureTemplate>,
template_name: String,
template_position: Vector3<i32>,
vertical_placement: VerticalPlacement,
properties: RuinedPortalProperties,
rotation: Rotation,
mirror: Mirror,
pivot: Vector3<i32>,
) -> 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<i32>,
) -> 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<i32>,
) {
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<i32>,
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<i32>) {
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<i32>,
) {
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(&center) {
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<i32>,
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
}

View File

@ -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<i32> = 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<StructurePosition> {
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<StructureTemplate>,
rotation: Rotation,
template_position: Vector3<i32>,
is_beached: bool,
height_adjusted: bool,
}
impl ShipwreckPiece {
#[must_use]
pub fn new(
template: Arc<StructureTemplate>,
position: Vector3<i32>,
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<i32>,
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);
}
}
}
}
}

View File

@ -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<StructureTemplate>,
/// 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<i32>,
}
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<StructureTemplate>,
@ -57,23 +57,37 @@ impl TemplatePiece {
origin: Vector3<i32>,
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<StructureTemplate>,
template_name: String,
place_settings: StructurePlaceSettings,
origin: Vector3<i32>,
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<i32> {
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<StructureTemplate> {
&self.template
}
/// Gets the world template position.
#[must_use]
pub const fn template_position(&self) -> Vector3<i32> {
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<i32>) -> Vector3<i32> {
// First apply mirror
let mirrored = self.mirror.transform_pos(local_pos, self.template.size);
#[must_use]
pub fn transform_pos(&self, local_pos: Vector3<i32>) -> Vector3<i32> {
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
}

View File

@ -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!(

View File

@ -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<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait WorldPortalExt: Send + Sync {
fn can_place_at(
&self,

View File

@ -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<'_>) {

View File

@ -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<'_>) {

View File

@ -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()

View File

@ -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<BlockRegistry> {
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);

View File

@ -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<GameProfile> {
fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<GameProfile> {
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<Gam
.java
.authentication
.clone();
let name_string = name.to_string();
let mojang_res =
tokio::task::spawn_blocking(move || lookup_profile_by_name(&name_string, &auth_config))
.await
.ok()
.and_then(Result::ok)
.flatten();
let mojang_res = lookup_profile_by_name(name, &auth_config).ok().flatten();
if let Some((uuid, resolved_name)) = mojang_res {
server
@ -177,12 +170,9 @@ async fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<Gam
.java
.authentication
.clone();
let full_profile =
tokio::task::spawn_blocking(move || fetch_profile_by_uuid(uuid, &auth_config_clone))
.await
.ok()
.and_then(Result::ok)
.flatten();
let full_profile = fetch_profile_by_uuid(uuid, &auth_config_clone)
.ok()
.flatten();
return Some(full_profile.unwrap_or_else(|| GameProfile {
id: uuid,
@ -222,7 +212,7 @@ async fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<Gam
None
}
async fn fetch_profile_by_id_helper(server: &Server, id: Uuid) -> Option<GameProfile> {
fn fetch_profile_by_id_helper(server: &Server, id: Uuid) -> Option<GameProfile> {
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<GamePro
.java
.authentication
.clone();
let mojang_res = tokio::task::spawn_blocking(move || fetch_profile_by_uuid(id, &auth_config))
.await
.ok()
.and_then(Result::ok)
.flatten();
let mojang_res = fetch_profile_by_uuid(id, &auth_config).ok().flatten();
if let Some(profile) = mojang_res {
server
@ -272,9 +258,8 @@ impl CommandExecutor for ResolveNameExecutor {
let name_owned = name.to_string();
let name_component = TextComponent::text(name_owned.clone());
let runtime = server.runtime.clone();
runtime.spawn(async move {
let result = fetch_profile_by_name_helper(&server, &name_owned).await;
rayon::spawn(move || {
let result = fetch_profile_by_name_helper(&server, &name_owned);
match result {
Some(profile) => {
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(

View File

@ -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.")

View File

@ -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.

View File

@ -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,

View File

@ -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;
}

View File

@ -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);

View File

@ -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,

View File

@ -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.

View File

@ -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<Player>) -> Result<(), PlayerDataError> {
pub fn handle_player_leave(&self, player: &Arc<Player>) -> 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<Option<NbtCompound>, 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<Option<NbtCompound>, 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(())
}
}

View File

@ -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<dyn EntityBase>, _server: &'a Server) {
fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {
// Age & duration handling
{
let mut age = self

View File

@ -850,7 +850,7 @@ impl Mob for EnderDragonEntity {
&self.mob_entity
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
self.ai_step();
}

View File

@ -606,7 +606,7 @@ impl Mob for WitherEntity {
);
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
self.tick_wither();
}

View File

@ -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<Player>) {
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 {

View File

@ -808,7 +808,7 @@ impl EntityBase for BlockDisplayEntity {
}
}
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _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<dyn EntityBase>, _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<dyn EntityBase>, _server: &'a Server) {}
fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {}
fn init_data_tracker(&self) {
self.display.init_display_data_tracker();

View File

@ -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<Player>, item_stack: &mut ItemStack) -> bool {

View File

@ -93,7 +93,7 @@ impl EntityBase for LeashKnotEntity {
None
}
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _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) {

View File

@ -66,7 +66,7 @@ impl ExperienceOrbEntity {
}
impl EntityBase for ExperienceOrbEntity {
fn tick(&self, caller: &Arc<dyn EntityBase>, 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();

View File

@ -46,7 +46,7 @@ impl FallingEntity {
}
impl EntityBase for FallingEntity {
fn tick(&self, caller: &Arc<dyn EntityBase>, 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();

View File

@ -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<Player>) {
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);
}
}

View File

@ -236,7 +236,7 @@ impl EntityBase for InteractionEntity {
}
}
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {}
fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {}
fn init_data_tracker(&self) {
let width = *self

View File

@ -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<Arc<Self>> = entities
let items: Vec<&Self> = entities
.iter()
.filter_map(|entity: &Arc<dyn EntityBase>| {
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<dyn EntityBase>,
caller: &dyn EntityBase,
server: &Server,
move_velo: Vector3<f64>,
) {
@ -415,7 +415,7 @@ impl ItemEntity {
true
}
fn sync_motion_if_dirty(&self, caller: &Arc<dyn EntityBase>, original_velo: Vector3<f64>) {
fn sync_motion_if_dirty(&self, caller: &dyn EntityBase, original_velo: Vector3<f64>) {
let entity = &self.entity;
entity.update_fluid_state(caller);
@ -443,7 +443,7 @@ impl ItemEntity {
}
impl EntityBase for ItemEntity {
fn tick(&self, caller: &Arc<dyn EntityBase>, 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<Self>) -> Option<Arc<ItemEntity>> {
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);
}
}
})
}
}
}

View File

@ -192,7 +192,7 @@ impl LightningBoltEntity {
}
impl EntityBase for LightningBoltEntity {
fn tick(&self, _caller: &Arc<dyn EntityBase>, _server: &Server) {
fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {
let entity = &self.entity;
let life = self.life.load(Ordering::Relaxed);

View File

@ -958,7 +958,7 @@ impl LivingEntity {
.has_tag(&tag::EntityType::MINECRAFT_FALL_DAMAGE_IMMUNE)
}
fn get_effective_gravity(&self, caller: &Arc<dyn EntityBase>) -> 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<dyn EntityBase>) {
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<dyn EntityBase>) {
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<dyn EntityBase>, 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<dyn EntityBase>) {
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<dyn EntityBase>, 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::<FoodImpl>()
&& 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::<FoodImpl>()
&& 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::<pumpkin_data::data_component_impl::PotionContentsImpl>().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::<pumpkin_data::data_component_impl::PotionContentsImpl>()
.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<dyn EntityBase>, item: &ItemStack) {
fn apply_consumable_effects(&self, caller: &dyn EntityBase, item: &ItemStack) {
let Some(consumable) = item.get_data_component::<ConsumableImpl>() 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 {

View File

@ -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<dyn EntityBase>, _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) {}
}

View File

@ -232,7 +232,7 @@ impl Mob for BatEntity {
&self.mob_entity
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
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);

View File

@ -421,7 +421,7 @@ impl Mob for CreakingEntity {
&self.mob_entity
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() {
return;

View File

@ -174,7 +174,7 @@ impl Mob for CreeperEntity {
.on_lightning_strike(caller, lightning);
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() {
return;

View File

@ -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<dyn EntityBase>) {
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);
}
}

View File

@ -177,7 +177,7 @@ impl Mob for EvokerEntity {
&self.mob_entity
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
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

View File

@ -21,7 +21,7 @@ impl Mob for MagmaCubeEntity {
self.slime.get_mob_entity()
}
fn mob_tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, caller: &dyn EntityBase) {
self.slime.mob_tick(caller);
}

View File

@ -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<dyn EntityBase>) {}
fn mob_tick(&self, _caller: &dyn EntityBase) {}
fn post_tick(&self) {}
@ -1008,7 +1008,7 @@ impl<T: Mob + Send + 'static> EntityBase for T {
}
#[allow(clippy::too_many_lines)]
fn tick(&self, caller: &Arc<dyn EntityBase>, 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();

View File

@ -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<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() {
return;

View File

@ -216,7 +216,7 @@ impl Mob for PiglinBruteEntity {
}
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() {
return;

View File

@ -366,7 +366,7 @@ impl Mob for ShulkerEntity {
0.0
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() {

View File

@ -282,7 +282,7 @@ impl Mob for SlimeEntity {
&self.entity
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
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);

View File

@ -227,7 +227,7 @@ impl Mob for WitchEntity {
amount
}
fn mob_tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>) {
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();

View File

@ -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<Box<dyn Future<Output = T> + Send + 'a>>;
pub type TeleportFuture = Pin<Box<dyn Future<Output = ()> + 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<dyn EntityBase>, 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<Self>, so the lifetime bounds are different.
fn teleport(
self: Arc<Self>,
&self,
position: Vector3<f64>,
yaw: Option<f32>,
pitch: Option<f32>,
world: Arc<World>,
) -> 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<dyn EntityBase>) -> 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<dyn EntityBase>, motion: Vector3<f64>) {
fn move_entity(&self, caller: &dyn EntityBase, motion: Vector3<f64>) {
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<dyn EntityBase>) {
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<dyn EntityBase>) -> 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<dyn EntityBase> = 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<dyn EntityBase> = 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<Self>) -> Option<Arc<ItemEntity>> {
fn get_item_entity(&self) -> Option<&ItemEntity> {
None
}
@ -1524,14 +1502,14 @@ impl Entity {
}
#[expect(dead_code)]
fn tick_block_underneath(_caller: &Arc<dyn EntityBase>) {
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<dyn EntityBase>, _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<dyn EntityBase>) {
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<dyn EntityBase>, mut motion: Vector3<f64>) {
pub fn move_entity(&self, caller: &dyn EntityBase, mut motion: Vector3<f64>) {
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<dyn EntityBase>) {
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<f64>,
yaw_delta: Option<f32>,
dest_world: &'a Arc<World>,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
let passengers = entity
dest_world: &Arc<World>,
) {
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<World>, 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<dyn EntityBase>, _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<Self>,
position: Vector3<f64>,
yaw: Option<f32>,
pitch: Option<f32>,
world: Arc<World>,
) -> TeleportFuture {
// TODO: handle world change
Box::pin(async move {
self.get_entity().teleport(position, yaw, pitch, world);
})
}
fn get_entity(&self) -> &Entity {
self
}

View File

@ -349,7 +349,7 @@ impl Mob for ArmadilloEntity {
}
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
self.ageable_ai_step();
self.in_state_ticks.fetch_add(1, Ordering::Relaxed);

View File

@ -167,7 +167,7 @@ impl Mob for ChickenEntity {
);
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
if self.mob_entity.living_entity.dead.load(Relaxed) {
return;
}

View File

@ -278,7 +278,7 @@ impl Mob for FoxEntity {
self.set_variant(FoxVariant::from_name(name));
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
self.ageable_ai_step();
}

View File

@ -172,7 +172,7 @@ impl Mob for FrogEntity {
self.set_variant(FrogVariant::from_name(name));
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
self.ageable_ai_step();
}

View File

@ -153,7 +153,7 @@ impl Mob for HappyGhastEntity {
&self.mob_entity
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
self.ageable_ai_step();
let leash_time = self.leash_holder_time.load(Ordering::Relaxed);

View File

@ -133,7 +133,7 @@ impl Mob for IronGolemEntity {
&self.mob_entity
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
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);

View File

@ -267,7 +267,7 @@ impl Mob for NautilusEntity {
);
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
let entity = &self.mob_entity.living_entity.entity;
let Ok(passengers) = entity.passengers.try_lock() else {

View File

@ -357,7 +357,7 @@ impl Mob for SnifferEntity {
&self.mob_entity
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
self.ageable_ai_step();
let state = self.get_state();
match state {

View File

@ -2288,7 +2288,7 @@ impl Mob for VillagerEntity {
}
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
fn mob_tick(&self, _caller: &dyn EntityBase) {
self.villager_mob_tick();
}

View File

@ -784,7 +784,7 @@ impl Mob for WanderingTraderEntity {
true
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
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);

View File

@ -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<ClientPlatform>,
gameprofile: GameProfile,
config: PlayerConfig,
world: Arc<World>,
world: &Arc<World>,
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<dyn EntityBase> = 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<f64>) {
pub fn progress_motion(&self, delta_pos: Vector3<f64>) {
// 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<Self>, position: Vector3<f64>, yaw: f32, pitch: f32) {
pub fn request_teleport(&self, position: Vector3<f64>, 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>,
&self,
position: Vector3<f64>,
yaw: Option<f32>,
pitch: Option<f32>,
world: Arc<World>,
) -> 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 {

View File

@ -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<Player>, 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<Identifier, &AdvancementProgress> = 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<AdvancementProgressData> = progress
.into_iter()
.map(|(key, val)| AdvancementProgressData {

View File

@ -286,7 +286,7 @@ impl EntityBase for ArrowEntity {
}
}
#[allow(clippy::too_many_lines)]
fn tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
let entity = self.get_entity();
let world = entity.world.load();

View File

@ -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<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
}

View File

@ -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<dyn EntityBase>, 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::<f32>() < 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::<f32>() < 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));
}

View File

@ -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<dyn EntityBase>, _server: &'a Server) {
fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {
let entity = &self.entity;
let world = entity.world.load();

View File

@ -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<dyn EntityBase>, 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<Self>) -> Option<Arc<ItemEntity>> {
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);
}
}
})
}
}
}

View File

@ -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<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
let entity = self.get_entity();
let mut velocity = entity.velocity.load();

View File

@ -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<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.entity.process_tick(caller, server);
let entity = self.get_entity();

View File

@ -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<dyn EntityBase>, _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<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.process_tick(caller, server);
}
}

View File

@ -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<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
}

View File

@ -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<dyn EntityBase>, 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;

View File

@ -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<dyn EntityBase>, _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

View File

@ -318,7 +318,7 @@ impl EntityBase for ShulkerBulletEntity {
true
}
#[allow(clippy::too_many_lines)]
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {
fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {
if self.has_hit.load(Ordering::Relaxed) {
return;
}

View File

@ -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<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
}

View File

@ -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<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
}

View File

@ -118,7 +118,7 @@ impl EntityBase for SplashPotionEntity {
);
}
fn tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
}

View File

@ -152,7 +152,7 @@ impl TridentEntity {
}
impl EntityBase for TridentEntity {
fn tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
let entity = self.get_entity();
let world = entity.world.load();

View File

@ -126,7 +126,7 @@ impl WindChargeEntity {
}
impl EntityBase for WindChargeEntity {
fn tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>, 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() {

View File

@ -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<dyn EntityBase>, server: &'a Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
}

View File

@ -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<dyn EntityBase>, server: &Server) {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
let entity = &self.entity;
let mut velo = entity.velocity.load();

View File

@ -60,7 +60,7 @@ impl EntityBase for BoatEntity {
None
}
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {
fn tick(&self, _caller: &dyn EntityBase, _server: &Server) {
self.vehicle.tick();
let underwater = self.ticks_underwater.load();

View File

@ -132,7 +132,7 @@ impl EntityBase for MinecartEntity {
}
#[allow(clippy::too_many_lines)]
fn tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>, _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<dyn EntityBase>) {
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<dyn EntityBase>, motion: Vector3<f64>) {
fn move_entity(&self, caller: &dyn EntityBase, motion: Vector3<f64>) {
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;

View File

@ -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<Server>) {
fn setup_stdin_console(server: &Arc<Server>) {
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<Server>) {
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(),
);
}

View File

@ -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<Self>, entity: Arc<dyn crate::entity::EntityBase>) {
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(

View File

@ -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 {

View File

@ -145,7 +145,7 @@ impl BedrockClient {
if pos_changed {
chunker::update_position(player).await;
player.progress_motion(delta).await;
player.progress_motion(delta);
}
}

View File

@ -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 &registry {
if reg.registry_id == "minecraft:dimension_type" {
sent_dimension_type = true;
}
let packet = CRegistryData::new(&reg.registry_id, &reg.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 &registry {
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<pumpkin_data::registry::RegistryEntryData> = 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(&reg.registry_id, &reg.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<pumpkin_data::registry::RegistryEntryData> = 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;
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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: {

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -251,31 +251,13 @@ impl ClientPlatform {
}
pub fn try_enqueue_spawn_packet(&self, entity: &Arc<dyn crate::entity::EntityBase>) {
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<dyn crate::entity::EntityBase>) {
pub fn enqueue_spawn_packet(&self, entity: &Arc<dyn crate::entity::EntityBase>) {
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),
}
}

View File

@ -30,13 +30,8 @@ impl PermissionCache {
}
pub async fn calculate_hash(path: &Path) -> tokio::io::Result<String> {
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]

View File

@ -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(())
}

View File

@ -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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
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::<crate::plugin::loader::wasm::wasm_host::state::ServerResource>(
wasmtime::component::Resource::new_own(server_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
let _ = store
.data_mut()
.resource_table
.delete::<crate::plugin::loader::wasm::wasm_host::state::EntityResource>(
wasmtime::component::Resource::new_own(entity_rep),
);
}
}
}
});
});
}
}
}
}

View File

@ -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(())
}

View File

@ -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"))
}

View File

@ -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();
}
{

View File

@ -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<dyn WorldPortalExt> = 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<dyn WorldPortalExt> = 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<Self>, name: String, dimension: Dimension) -> Arc<World> {
pub fn create_world(self: &Arc<Self>, name: String, dimension: Dimension) -> Arc<World> {
{
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<dyn WorldPortalExt> = 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<dyn WorldPortalExt> = 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<Self>) {
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`.

View File

@ -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) => {

Some files were not shown because too many files have changed in this diff Show More