fix: many bugs
This commit is contained in:
parent
7034b698d0
commit
37437ee0a0
@ -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 std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin_data::block_rotation::Rotation;
|
use pumpkin_data::{Block, BlockState, Mirror, Rotation};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
||||||
random::RandomGenerator,
|
random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand},
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ProtoChunk,
|
ProtoChunk,
|
||||||
generation::{
|
generation::{
|
||||||
positions::chunk_pos::{get_center_x, get_center_z},
|
positions::chunk_pos::{start_block_x, start_block_z},
|
||||||
structure::{
|
structure::{
|
||||||
piece::StructurePieceType,
|
piece::StructurePieceType,
|
||||||
shiftable_piece::ShiftableStructurePiece,
|
|
||||||
structures::{
|
structures::{
|
||||||
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
||||||
StructurePiecesCollector, StructurePosition, WorldPortalExt,
|
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).
|
#[must_use]
|
||||||
const DOME_WIDTH: i32 = 7;
|
pub const fn get_pivot(location: &str) -> Vector3<i32> {
|
||||||
const DOME_HEIGHT: i32 = 5;
|
if const_str_eq(location, STRUCTURE_LOCATION_IGLOO) {
|
||||||
const DOME_DEPTH: i32 = 8;
|
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).
|
#[must_use]
|
||||||
const SHAFT_HEIGHT: i32 = 3;
|
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 fn const_str_eq(a: &str, b: &str) -> bool {
|
||||||
const BASEMENT_HEIGHT: i32 = 6;
|
let a = a.as_bytes();
|
||||||
const BASEMENT_DEPTH: i32 = 9;
|
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.
|
pub fn add_pieces(
|
||||||
const PIVOT_OFFSET_X: i32 = 3;
|
collector: &mut StructurePiecesCollector,
|
||||||
const PIVOT_OFFSET_Z: i32 = 5;
|
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).
|
for i in 0..(depth - 1) {
|
||||||
const SHAFT_OFFSET_X: i32 = 2;
|
if let Some(template) = get_template(STRUCTURE_LOCATION_LADDER) {
|
||||||
const SHAFT_OFFSET_Z: i32 = 4;
|
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).
|
if let Some(template) = get_template(STRUCTURE_LOCATION_IGLOO) {
|
||||||
const BASEMENT_OFFSET_X: i32 = 0;
|
collector.add_piece(Box::new(IglooPiece::new(
|
||||||
const BASEMENT_OFFSET_Z: i32 = -2;
|
template,
|
||||||
|
STRUCTURE_LOCATION_IGLOO.to_string(),
|
||||||
|
position,
|
||||||
|
rotation,
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generator for igloo structures in snowy biomes.
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct IglooGenerator;
|
pub struct IglooGenerator;
|
||||||
|
|
||||||
impl StructureGenerator for IglooGenerator {
|
impl StructureGenerator for IglooGenerator {
|
||||||
@ -66,204 +120,263 @@ impl StructureGenerator for IglooGenerator {
|
|||||||
&self,
|
&self,
|
||||||
mut context: StructureGeneratorContext<'_>,
|
mut context: StructureGeneratorContext<'_>,
|
||||||
) -> Option<StructurePosition> {
|
) -> Option<StructurePosition> {
|
||||||
let chunk_center_x = get_center_x(context.chunk_x);
|
let block_pos = Vector3::new(
|
||||||
let chunk_center_z = get_center_z(context.chunk_z);
|
start_block_x(context.chunk_x),
|
||||||
|
GENERATION_HEIGHT,
|
||||||
// Apply pivot offset - structure origin is offset from chunk center
|
start_block_z(context.chunk_z),
|
||||||
let x = chunk_center_x - PIVOT_OFFSET_X;
|
);
|
||||||
let z = chunk_center_z - PIVOT_OFFSET_Z;
|
let rotation_idx = context.random.next_bounded_i32(4) as u8;
|
||||||
|
let rotation = Rotation::from_index(rotation_idx);
|
||||||
// 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 mut collector = StructurePiecesCollector::default();
|
let mut collector = StructurePiecesCollector::default();
|
||||||
|
add_pieces(&mut collector, block_pos, rotation, &mut context.random);
|
||||||
// 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));
|
|
||||||
|
|
||||||
Some(StructurePosition {
|
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()),
|
collector: Arc::new(collector.into()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Single igloo structure piece containing dome and optional basement.
|
|
||||||
pub struct IglooPiece {
|
pub struct IglooPiece {
|
||||||
shiftable_structure_piece: ShiftableStructurePiece,
|
pub piece: StructurePiece,
|
||||||
top_template: Arc<StructureTemplate>,
|
pub template: Arc<StructureTemplate>,
|
||||||
middle_template: Option<Arc<StructureTemplate>>,
|
pub template_name: String,
|
||||||
bottom_template: Option<Arc<StructureTemplate>>,
|
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,
|
rotation: Rotation,
|
||||||
has_basement: bool,
|
depth: i32,
|
||||||
ladder_segments: u8,
|
) -> 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 {
|
impl StructurePieceBase for IglooPiece {
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
fn get_structure_piece(&self) -> &StructurePiece {
|
||||||
|
&self.piece
|
||||||
|
}
|
||||||
|
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
|
||||||
|
&mut self.piece
|
||||||
|
}
|
||||||
fn place(
|
fn place(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk: &mut ProtoChunk,
|
chunk: &mut ProtoChunk,
|
||||||
_block_registry: &dyn WorldPortalExt,
|
_block_registry: &dyn WorldPortalExt,
|
||||||
_random: &mut RandomGenerator,
|
random: &mut RandomGenerator,
|
||||||
_seed: i64,
|
_seed: i64,
|
||||||
chunk_box: &BlockBox,
|
chunk_box: &BlockBox,
|
||||||
) {
|
) {
|
||||||
let origin = self.shiftable_structure_piece.piece.bounding_box.min;
|
let settings = make_settings(self.place_settings.get_rotation(), &self.template_name);
|
||||||
|
let offset = get_offset(&self.template_name);
|
||||||
// Vanilla samples height at the entrance position (3, 0, 5 in template space)
|
let entrance_rel = StructureTemplate::calculate_relative_position(
|
||||||
let (entrance_x, entrance_z) = self.rotation.rotate_offset(3, 5);
|
&settings,
|
||||||
let surface_y = chunk.get_top_y(
|
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,
|
&pumpkin_util::HeightMap::WorldSurfaceWg,
|
||||||
origin.x + entrance_x,
|
entrance_pos.x,
|
||||||
origin.z + entrance_z,
|
entrance_pos.z,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Place floor at surface - 1 (so the floor block is at ground level)
|
let old_template_pos = self.template_position;
|
||||||
let offset = surface_y - 1 - origin.y;
|
self.template_position.y += height - GENERATION_HEIGHT - 1;
|
||||||
self.shiftable_structure_piece.piece.bounding_box.min.y += offset;
|
self.piece.bounding_box = self
|
||||||
self.shiftable_structure_piece.piece.bounding_box.max.y += offset;
|
.template
|
||||||
|
.get_bounding_box(&self.place_settings, self.template_position);
|
||||||
|
|
||||||
let origin = self.shiftable_structure_piece.piece.bounding_box.min;
|
self.place_blocks(chunk, chunk_box, random);
|
||||||
let dome_floor_y = origin.y;
|
|
||||||
|
|
||||||
// Place dome at origin (no offset)
|
if self.template_name == STRUCTURE_LOCATION_IGLOO {
|
||||||
place_template(
|
let trap_door_rel =
|
||||||
chunk,
|
StructureTemplate::calculate_relative_position(&settings, Vector3::new(3, 0, 5));
|
||||||
&self.top_template,
|
let trap_door_pos = self.template_position + trap_door_rel;
|
||||||
origin,
|
let below_pos = trap_door_pos - Vector3::new(0, 1, 0);
|
||||||
(0, 0),
|
let below_state = BlockState::from_id(chunk.get_block_state(&below_pos));
|
||||||
self.rotation,
|
if !below_state.is_air()
|
||||||
false,
|
&& below_state.id.to_block_id() != Block::LADDER.id
|
||||||
false,
|
&& chunk_box.contains_pos(&trap_door_pos)
|
||||||
&[],
|
{
|
||||||
Some(chunk_box),
|
chunk.set_block_state(
|
||||||
);
|
trap_door_pos.x,
|
||||||
crate::generation::structure::template::place_template_entities(
|
trap_door_pos.y,
|
||||||
chunk,
|
trap_door_pos.z,
|
||||||
&self.top_template,
|
Block::SNOW_BLOCK.default_state,
|
||||||
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
|
self.template_position = old_template_pos;
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin_data::{Block, BlockDirection, BlockState};
|
use pumpkin_data::{Block, BlockDirection, BlockState, Mirror, Rotation};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
||||||
random::{RandomDeriverImpl, RandomGenerator, RandomImpl},
|
random::{
|
||||||
|
RandomDeriverImpl, RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -12,18 +14,19 @@ use crate::{
|
|||||||
positions::chunk_pos::{start_block_x, start_block_z},
|
positions::chunk_pos::{start_block_x, start_block_z},
|
||||||
structure::{
|
structure::{
|
||||||
piece::StructurePieceType,
|
piece::StructurePieceType,
|
||||||
shiftable_piece::ShiftableStructurePiece,
|
|
||||||
structures::{
|
structures::{
|
||||||
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
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_1",
|
||||||
"nether_fossils/fossil_2",
|
"nether_fossils/fossil_2",
|
||||||
"nether_fossils/fossil_3",
|
"nether_fossils/fossil_3",
|
||||||
@ -58,7 +61,7 @@ impl StructureGenerator for NetherFossilGenerator {
|
|||||||
// 2. nextInt(16) for Z offset within chunk
|
// 2. nextInt(16) for Z offset within chunk
|
||||||
// 3. height.get(random) for initial Y (uniform 32..254)
|
// 3. height.get(random) for initial Y (uniform 32..254)
|
||||||
// 4. Column scan (no random calls)
|
// 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)
|
// 6. Util.getRandom(FOSSILS, random) - nextInt(14)
|
||||||
|
|
||||||
let x = start_block_x(context.chunk_x) + context.random.next_bounded_i32(16);
|
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)
|
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_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_index = context.random.next_bounded_i32(FOSSILS.len() as i32) as usize;
|
||||||
let template_name = TEMPLATE_NAMES[template_index];
|
let template_name = FOSSILS[template_index];
|
||||||
|
|
||||||
let template = get_template(template_name)?;
|
let template = get_template(template_name)?;
|
||||||
|
let position = Vector3::new(x, initial_y, z);
|
||||||
let rotated_size = rotation.transform_size(template.size);
|
|
||||||
|
|
||||||
let mut collector = StructurePiecesCollector::default();
|
let mut collector = StructurePiecesCollector::default();
|
||||||
|
|
||||||
let piece = NetherFossilPiece {
|
let piece = NetherFossilPiece::new(
|
||||||
shiftable_structure_piece: ShiftableStructurePiece::new(
|
|
||||||
StructurePieceType::NetherFossil,
|
|
||||||
x,
|
|
||||||
initial_y,
|
|
||||||
z,
|
|
||||||
rotated_size.x,
|
|
||||||
rotated_size.y,
|
|
||||||
rotated_size.z,
|
|
||||||
rotation.to_axis(),
|
|
||||||
),
|
|
||||||
template,
|
template,
|
||||||
|
template_name.to_string(),
|
||||||
|
position,
|
||||||
rotation,
|
rotation,
|
||||||
initial_y,
|
initial_y,
|
||||||
sea_level: context.sea_level,
|
context.sea_level,
|
||||||
};
|
);
|
||||||
|
|
||||||
collector.add_piece(Box::new(piece));
|
collector.add_piece(Box::new(piece));
|
||||||
|
|
||||||
@ -115,35 +107,48 @@ impl StructureGenerator for NetherFossilGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NetherFossilPiece {
|
pub struct NetherFossilPiece {
|
||||||
shiftable_structure_piece: ShiftableStructurePiece,
|
pub piece: StructurePiece,
|
||||||
template: Arc<StructureTemplate>,
|
pub template: Arc<StructureTemplate>,
|
||||||
rotation: BlockRotation,
|
pub template_name: String,
|
||||||
initial_y: i32,
|
pub place_settings: StructurePlaceSettings,
|
||||||
sea_level: i32,
|
pub template_position: Vector3<i32>,
|
||||||
|
pub initial_y: i32,
|
||||||
|
pub sea_level: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetherFossilPiece {
|
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).
|
/// 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.
|
/// 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> {
|
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;
|
let mut y = self.initial_y;
|
||||||
|
|
||||||
while y > self.sea_level {
|
while y > self.sea_level {
|
||||||
let upper = chunk.get_block_state(&Vector3::new(origin.x, y, origin.z));
|
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;
|
y -= 1;
|
||||||
let lower = chunk.get_block_state(&Vector3::new(origin.x, y, origin.z));
|
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) }
|
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 {
|
impl StructurePieceBase for NetherFossilPiece {
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
fn get_structure_piece(&self) -> &StructurePiece {
|
||||||
|
&self.piece
|
||||||
|
}
|
||||||
|
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
|
||||||
|
&mut self.piece
|
||||||
|
}
|
||||||
fn place(
|
fn place(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk: &mut ProtoChunk,
|
chunk: &mut ProtoChunk,
|
||||||
block_registry: &dyn WorldPortalExt,
|
_block_registry: &dyn WorldPortalExt,
|
||||||
_random: &mut RandomGenerator,
|
_random: &mut RandomGenerator,
|
||||||
seed: i64,
|
seed: i64,
|
||||||
chunk_box: &BlockBox,
|
chunk_box: &BlockBox,
|
||||||
) {
|
) {
|
||||||
// Vanilla column scan: find air above soul sand or solid block
|
|
||||||
let Some(placement_y) = self.find_placement_y(chunk) else {
|
let Some(placement_y) = self.find_placement_y(chunk) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adjust bounding box to placement Y
|
self.template_position.y = placement_y;
|
||||||
let current_y = self.shiftable_structure_piece.piece.bounding_box.min.y;
|
self.piece.bounding_box = self
|
||||||
let offset = placement_y - current_y;
|
.template
|
||||||
self.shiftable_structure_piece.piece.bounding_box.min.y += offset;
|
.get_bounding_box(&self.place_settings, self.template_position);
|
||||||
self.shiftable_structure_piece.piece.bounding_box.max.y += offset;
|
|
||||||
|
|
||||||
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
|
self.place_blocks(chunk, &enlarged_box);
|
||||||
place_template(
|
Self::place_dried_ghast(chunk, seed, &fossil_bb, chunk_box);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetherFossilPiece {
|
fn make_settings(rotation: Rotation) -> StructurePlaceSettings {
|
||||||
fn try_place_dried_ghast(
|
StructurePlaceSettings::new()
|
||||||
&self,
|
.set_rotation(rotation)
|
||||||
chunk: &mut ProtoChunk,
|
.set_mirror(Mirror::None)
|
||||||
_block_registry: &dyn WorldPortalExt,
|
.add_processor(StructureProcessor::BlockIgnore(vec![
|
||||||
seed: i64,
|
IgnoredBlock {
|
||||||
) {
|
block_id: Block::STRUCTURE_BLOCK.id,
|
||||||
use pumpkin_util::random::xoroshiro128::Xoroshiro;
|
properties: None,
|
||||||
|
},
|
||||||
let bbox = self.shiftable_structure_piece.piece.bounding_box;
|
IgnoredBlock {
|
||||||
let center_x = i32::midpoint(bbox.min.x, bbox.max.x);
|
block_id: Block::AIR.id,
|
||||||
let center_y = i32::midpoint(bbox.min.y, bbox.max.y);
|
properties: None,
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin_data::block_rotation::Rotation;
|
use pumpkin_data::{Block, Mirror, Rotation, structures::StructureKeys};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::{block_box::BlockBox, position::BlockPos},
|
HeightMap,
|
||||||
random::{RandomGenerator, RandomImpl},
|
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
||||||
|
random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -16,7 +17,14 @@ use crate::{
|
|||||||
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
||||||
StructurePiecesCollector, StructurePosition, WorldPortalExt,
|
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",
|
"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 struct RuinedPortalGenerator {
|
||||||
pub variant: pumpkin_data::structures::StructureKeys,
|
pub variant: StructureKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StructureGenerator for RuinedPortalGenerator {
|
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_idx = context.random.next_bounded_i32(4) as u8;
|
||||||
let rotation = Rotation::from_index(rotation_idx);
|
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_idx = context.random.next_bounded_i32(PORTALS.len() as i32) as usize;
|
||||||
let template_name = PORTALS[template_idx];
|
let template_name = PORTALS[template_idx];
|
||||||
let template = get_template(template_name)?;
|
let template = get_template(template_name)?;
|
||||||
|
|
||||||
let size = template.size;
|
let pivot = Vector3::new(template.size.x / 2, 0, template.size.z / 2);
|
||||||
let bounding_box = BlockBox::new(
|
|
||||||
chunk_center_x - size.x / 2,
|
let y = match vertical_placement {
|
||||||
context.min_y,
|
VerticalPlacement::OnOceanFloor => context
|
||||||
chunk_center_z - size.z / 2,
|
.height_sampler
|
||||||
chunk_center_x + size.x / 2,
|
.as_deref_mut()
|
||||||
256,
|
.map_or(context.sea_level, |s| {
|
||||||
chunk_center_z + size.z / 2,
|
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();
|
let mut collector = StructurePiecesCollector::default();
|
||||||
collector.add_piece(Box::new(RuinedPortalPiece {
|
collector.add_piece(Box::new(piece));
|
||||||
piece: StructurePiece::new(StructurePieceType::RuinedPortal, bounding_box, 0),
|
|
||||||
template,
|
|
||||||
rotation,
|
|
||||||
variant: self.variant,
|
|
||||||
}));
|
|
||||||
|
|
||||||
Some(StructurePosition {
|
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()),
|
collector: Arc::new(collector.into()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RuinedPortalPiece {
|
pub struct RuinedPortalPiece {
|
||||||
piece: StructurePiece,
|
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: Arc<StructureTemplate>,
|
||||||
|
template_name: String,
|
||||||
|
template_position: Vector3<i32>,
|
||||||
|
vertical_placement: VerticalPlacement,
|
||||||
|
properties: RuinedPortalProperties,
|
||||||
rotation: Rotation,
|
rotation: Rotation,
|
||||||
variant: pumpkin_data::structures::StructureKeys,
|
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 {
|
impl StructurePieceBase for RuinedPortalPiece {
|
||||||
@ -102,45 +587,148 @@ impl StructurePieceBase for RuinedPortalPiece {
|
|||||||
&mut self,
|
&mut self,
|
||||||
chunk: &mut ProtoChunk,
|
chunk: &mut ProtoChunk,
|
||||||
_block_registry: &dyn WorldPortalExt,
|
_block_registry: &dyn WorldPortalExt,
|
||||||
_random: &mut RandomGenerator,
|
random: &mut RandomGenerator,
|
||||||
_seed: i64,
|
_seed: i64,
|
||||||
chunk_box: &BlockBox,
|
chunk_box: &BlockBox,
|
||||||
) {
|
) {
|
||||||
let origin = self.piece.bounding_box.min;
|
let bounding_box = self
|
||||||
let (height_map_type, vertical_offset) = match self.variant {
|
.template
|
||||||
pumpkin_data::structures::StructureKeys::RuinedPortalOcean => {
|
.get_bounding_box(&self.place_settings, self.template_position);
|
||||||
(pumpkin_util::HeightMap::OceanFloorWg, -2)
|
let center = Vector3::new(
|
||||||
}
|
i32::midpoint(bounding_box.min.x, bounding_box.max.x),
|
||||||
pumpkin_data::structures::StructureKeys::RuinedPortalDesert => {
|
i32::midpoint(bounding_box.min.y, bounding_box.max.y),
|
||||||
(pumpkin_util::HeightMap::WorldSurfaceWg, -3) // partly buried
|
i32::midpoint(bounding_box.min.z, bounding_box.max.z),
|
||||||
}
|
|
||||||
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),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if chunk_box.contains_pos(¢er) {
|
||||||
|
let mut enlarged_box = *chunk_box;
|
||||||
|
enlarged_box.encompass(&bounding_box);
|
||||||
|
|
||||||
|
self.place_blocks(chunk, &enlarged_box);
|
||||||
|
self.spread_netherrack(random, chunk, &enlarged_box);
|
||||||
|
self.add_netherrack_drip_columns_below_portal(random, chunk, &enlarged_box);
|
||||||
|
|
||||||
|
if self.properties.vines || self.properties.overgrown {
|
||||||
|
for x in self.piece.bounding_box.min.x..=self.piece.bounding_box.max.x {
|
||||||
|
for y in self.piece.bounding_box.min.y..=self.piece.bounding_box.max.y {
|
||||||
|
for z in self.piece.bounding_box.min.z..=self.piece.bounding_box.max.z {
|
||||||
|
let pos = Vector3::new(x, y, z);
|
||||||
|
if enlarged_box.contains_pos(&pos) {
|
||||||
|
if self.properties.vines {
|
||||||
|
Self::maybe_add_vines(random, chunk, pos);
|
||||||
|
}
|
||||||
|
if self.properties.overgrown {
|
||||||
|
Self::maybe_add_leaves_above(random, chunk, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_settings(
|
||||||
|
mirror: Mirror,
|
||||||
|
rotation: Rotation,
|
||||||
|
vertical_placement: VerticalPlacement,
|
||||||
|
pivot: Vector3<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
|
||||||
|
}
|
||||||
|
|||||||
@ -1,47 +1,72 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin_data::block_rotation::Rotation;
|
use pumpkin_data::{Mirror, Rotation};
|
||||||
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
math::{block_box::BlockBox, position::BlockPos},
|
HeightMap,
|
||||||
random::{RandomGenerator, RandomImpl},
|
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
||||||
|
random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ProtoChunk,
|
ProtoChunk,
|
||||||
generation::{
|
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::{
|
structure::{
|
||||||
piece::StructurePieceType,
|
piece::StructurePieceType,
|
||||||
structures::{
|
structures::{
|
||||||
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
||||||
StructurePiecesCollector, StructurePosition, WorldPortalExt,
|
StructurePiecesCollector, StructurePosition, WorldPortalExt,
|
||||||
},
|
},
|
||||||
template::{StructureTemplate, get_template, place_template},
|
template::{BlockStateResolver, StructureTemplate, get_template},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const TEMPLATES: &[&str] = &[
|
const NUMBER_OF_BLOCKS_ALLOWED_IN_WORLD_GEN_REGION: i32 = 32;
|
||||||
"shipwreck/rightsideup_backhalf",
|
pub const PIVOT: Vector3<i32> = Vector3::new(4, 0, 15);
|
||||||
"shipwreck/rightsideup_backhalf_degraded",
|
|
||||||
"shipwreck/rightsideup_fronthalf",
|
const STRUCTURE_LOCATION_BEACHED: &[&str] = &[
|
||||||
"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",
|
|
||||||
"shipwreck/with_mast",
|
"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/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 {
|
pub struct ShipwreckGenerator {
|
||||||
@ -53,37 +78,70 @@ impl StructureGenerator for ShipwreckGenerator {
|
|||||||
&self,
|
&self,
|
||||||
mut context: StructureGeneratorContext<'_>,
|
mut context: StructureGeneratorContext<'_>,
|
||||||
) -> Option<StructurePosition> {
|
) -> Option<StructurePosition> {
|
||||||
let chunk_center_x = get_center_x(context.chunk_x);
|
let center_x = get_center_x(context.chunk_x);
|
||||||
let chunk_center_z = get_center_z(context.chunk_z);
|
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_idx = context.random.next_bounded_i32(4) as u8;
|
||||||
let rotation = Rotation::from_index(rotation_idx);
|
let rotation = Rotation::from_index(rotation_idx);
|
||||||
|
|
||||||
let template_idx = context.random.next_bounded_i32(TEMPLATES.len() as i32) as usize;
|
let start_x = start_block_x(context.chunk_x);
|
||||||
let template_name = TEMPLATES[template_idx];
|
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 template = get_template(template_name)?;
|
||||||
|
|
||||||
let size = template.size;
|
let mut piece = ShipwreckPiece::new(template, position, rotation, self.is_beached);
|
||||||
let bounding_box = BlockBox::new(
|
|
||||||
chunk_center_x - size.x / 2,
|
if piece.is_too_big_to_fit_in_world_gen_region() {
|
||||||
context.min_y,
|
let bb = piece.piece.bounding_box;
|
||||||
chunk_center_z - size.z / 2,
|
let height = if self.is_beached {
|
||||||
chunk_center_x + size.x / 2,
|
let min_y = context.height_sampler.as_deref_mut().map_or(64, |sampler| {
|
||||||
256,
|
let c0 = sampler.estimate_height(bb.min.x, bb.min.z);
|
||||||
chunk_center_z + size.z / 2,
|
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();
|
let mut collector = StructurePiecesCollector::default();
|
||||||
collector.add_piece(Box::new(ShipwreckPiece {
|
collector.add_piece(Box::new(piece));
|
||||||
piece: StructurePiece::new(StructurePieceType::Shipwreck, bounding_box, 0),
|
|
||||||
template,
|
|
||||||
rotation,
|
|
||||||
is_beached: self.is_beached,
|
|
||||||
}));
|
|
||||||
|
|
||||||
Some(StructurePosition {
|
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()),
|
collector: Arc::new(collector.into()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -93,53 +151,266 @@ pub struct ShipwreckPiece {
|
|||||||
piece: StructurePiece,
|
piece: StructurePiece,
|
||||||
template: Arc<StructureTemplate>,
|
template: Arc<StructureTemplate>,
|
||||||
rotation: Rotation,
|
rotation: Rotation,
|
||||||
|
template_position: Vector3<i32>,
|
||||||
is_beached: bool,
|
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 {
|
impl StructurePieceBase for ShipwreckPiece {
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_structure_piece(&self) -> &StructurePiece {
|
fn get_structure_piece(&self) -> &StructurePiece {
|
||||||
&self.piece
|
&self.piece
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
|
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
|
||||||
&mut self.piece
|
&mut self.piece
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
fn place(
|
fn place(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk: &mut ProtoChunk,
|
chunk: &mut ProtoChunk,
|
||||||
_block_registry: &dyn WorldPortalExt,
|
_block_registry: &dyn WorldPortalExt,
|
||||||
_random: &mut RandomGenerator,
|
random: &mut RandomGenerator,
|
||||||
_seed: i64,
|
_seed: i64,
|
||||||
chunk_box: &BlockBox,
|
chunk_box: &BlockBox,
|
||||||
) {
|
) {
|
||||||
let origin = self.piece.bounding_box.min;
|
if !self.height_adjusted && !self.is_too_big_to_fit_in_world_gen_region() {
|
||||||
let height_map_type = if self.is_beached {
|
let heightmap_type = if self.is_beached {
|
||||||
pumpkin_util::HeightMap::WorldSurfaceWg
|
HeightMap::WorldSurfaceWg
|
||||||
} else {
|
} else {
|
||||||
pumpkin_util::HeightMap::OceanFloorWg
|
HeightMap::OceanFloorWg
|
||||||
};
|
};
|
||||||
|
|
||||||
let sample_y = chunk.get_top_y(&height_map_type, origin.x, origin.z);
|
let template_size = self.template.size;
|
||||||
let target_y = if self.is_beached {
|
let base_size = template_size.x * template_size.z;
|
||||||
sample_y - 1
|
|
||||||
} else {
|
|
||||||
sample_y - 3
|
|
||||||
};
|
|
||||||
let mut final_origin = origin;
|
|
||||||
final_origin.y = target_y;
|
|
||||||
|
|
||||||
place_template(
|
let (mean, min_y) = if base_size == 0 {
|
||||||
chunk,
|
let h = chunk.get_top_y(
|
||||||
&self.template,
|
&heightmap_type,
|
||||||
final_origin,
|
self.template_position.x,
|
||||||
(0, 0),
|
self.template_position.z,
|
||||||
self.rotation,
|
|
||||||
true,
|
|
||||||
!self.is_beached,
|
|
||||||
&[],
|
|
||||||
Some(chunk_box),
|
|
||||||
);
|
);
|
||||||
|
(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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`
|
//! This module provides `TemplatePiece`, which implements `StructurePieceBase`
|
||||||
//! to place blocks from a loaded structure template into the world.
|
//! to place blocks from a loaded structure template into the world.
|
||||||
@ -10,7 +10,6 @@ use pumpkin_util::{
|
|||||||
math::{block_box::BlockBox, vector3::Vector3},
|
math::{block_box::BlockBox, vector3::Vector3},
|
||||||
random::RandomGenerator,
|
random::RandomGenerator,
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ProtoChunk,
|
ProtoChunk,
|
||||||
@ -21,34 +20,35 @@ use crate::{
|
|||||||
world::WorldPortalExt,
|
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:
|
/// This piece handles:
|
||||||
/// - Loading blocks from a `StructureTemplate`
|
/// - Loading blocks from a `StructureTemplate`
|
||||||
/// - Applying rotation and mirror transformations
|
/// - Applying rotation, mirror, and pivot transformations from `StructurePlaceSettings`
|
||||||
/// - Placing blocks at world coordinates
|
/// - Placing blocks at world coordinates
|
||||||
|
/// - Replacing jigsaw blocks with their `final_state`
|
||||||
/// - Skipping structure void blocks
|
/// - Skipping structure void blocks
|
||||||
pub struct TemplatePiece {
|
pub struct TemplatePiece {
|
||||||
/// The underlying structure piece metadata.
|
/// The underlying structure piece metadata.
|
||||||
pub piece: StructurePiece,
|
pub piece: StructurePiece,
|
||||||
|
|
||||||
|
/// The template identifier/name.
|
||||||
|
pub template_name: String,
|
||||||
|
|
||||||
/// The template to place.
|
/// The template to place.
|
||||||
pub template: Arc<StructureTemplate>,
|
pub template: Arc<StructureTemplate>,
|
||||||
|
|
||||||
/// Rotation to apply when placing.
|
/// Placement settings (rotation, mirror, pivot, processors, etc.).
|
||||||
pub rotation: Rotation,
|
pub place_settings: StructurePlaceSettings,
|
||||||
|
|
||||||
/// Mirror to apply when placing.
|
/// The origin position of the template in the world.
|
||||||
pub mirror: Mirror,
|
pub template_position: Vector3<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TemplatePiece {
|
impl TemplatePiece {
|
||||||
/// Creates a new template piece from the given `template` at `origin`.
|
/// Creates a new template piece from the given `template` at `origin` with legacy rotation & mirror.
|
||||||
///
|
|
||||||
/// The `rotation` and `mirror` transforms are applied when placing blocks.
|
|
||||||
/// The `piece_type` identifies this piece within the structure system.
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
template: Arc<StructureTemplate>,
|
template: Arc<StructureTemplate>,
|
||||||
@ -57,23 +57,37 @@ impl TemplatePiece {
|
|||||||
origin: Vector3<i32>,
|
origin: Vector3<i32>,
|
||||||
piece_type: StructurePieceType,
|
piece_type: StructurePieceType,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Calculate the bounding box based on template size and rotation
|
let place_settings = StructurePlaceSettings::new()
|
||||||
let rotated_size = rotation.transform_size(template.size);
|
.set_rotation(rotation)
|
||||||
|
.set_mirror(mirror);
|
||||||
let bounding_box = BlockBox::new(
|
let bounding_box = template.get_bounding_box(&place_settings, origin);
|
||||||
origin.x,
|
|
||||||
origin.y,
|
|
||||||
origin.z,
|
|
||||||
origin.x + rotated_size.x - 1,
|
|
||||||
origin.y + rotated_size.y - 1,
|
|
||||||
origin.z + rotated_size.z - 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
piece: StructurePiece::new(piece_type, bounding_box, 0),
|
piece: StructurePiece::new(piece_type, bounding_box, 0),
|
||||||
|
template_name: String::new(),
|
||||||
template,
|
template,
|
||||||
rotation,
|
place_settings,
|
||||||
mirror,
|
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.
|
/// Returns the template's size after applying rotation.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn rotated_size(&self) -> Vector3<i32> {
|
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.
|
/// Transforms a template-relative position to world coordinates.
|
||||||
fn transform_pos(&self, local_pos: Vector3<i32>) -> Vector3<i32> {
|
#[must_use]
|
||||||
// First apply mirror
|
pub fn transform_pos(&self, local_pos: Vector3<i32>) -> Vector3<i32> {
|
||||||
let mirrored = self.mirror.transform_pos(local_pos, self.template.size);
|
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
|
/// Moves the template position and bounding box by `(dx, dy, dz)`.
|
||||||
let rotated = self.rotation.transform_pos(mirrored, self.template.size);
|
pub fn move_piece(&mut self, dx: i32, dy: i32, dz: i32) {
|
||||||
|
self.piece.translate(dx, dy, dz);
|
||||||
// Add world offset
|
self.template_position += Vector3::new(dx, dy, dz);
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a block name is structure void (should not be placed).
|
/// 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"
|
name == "minecraft:structure_void" || name == "structure_void"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Places all blocks from the template into the chunk.
|
/// Places all blocks from the template into the chunk matching vanilla `postProcess`.
|
||||||
fn place_blocks(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
|
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;
|
let box_limit = self.piece.bounding_box;
|
||||||
|
|
||||||
for block in &self.template.blocks {
|
for block in &self.template.blocks {
|
||||||
let palette_entry = &self.template.palette[block.state as usize];
|
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
|
// Skip structure void blocks
|
||||||
if Self::is_structure_void(&palette_entry.name) {
|
if Self::is_structure_void(&placed_entry.name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the block state with rotation/mirror
|
// Resolve the block state with rotation/mirror
|
||||||
let Some(state) =
|
let Some(mut state) = BlockStateResolver::resolve(
|
||||||
BlockStateResolver::resolve(palette_entry, self.rotation, self.mirror)
|
&placed_entry,
|
||||||
else {
|
self.place_settings.get_rotation(),
|
||||||
debug!("Failed to resolve block: {}", palette_entry.name);
|
self.place_settings.get_mirror(),
|
||||||
|
) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -147,20 +219,39 @@ impl TemplatePiece {
|
|||||||
continue;
|
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
|
// Place the block
|
||||||
chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state);
|
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);
|
let final_block = pumpkin_data::Block::from_id(state.id.to_block_id());
|
||||||
if block.nbt.is_some() || block_entity_id.is_some() {
|
let block_entity_id = super::get_block_entity_id(final_block.name);
|
||||||
let block_entity_id = block_entity_id.unwrap_or(&palette_entry.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();
|
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("x", world_pos.x);
|
||||||
placed_nbt.put_int("y", world_pos.y);
|
placed_nbt.put_int("y", world_pos.y);
|
||||||
placed_nbt.put_int("z", world_pos.z);
|
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 {
|
for (key, value) in &template_nbt.child_tags {
|
||||||
if key.as_ref() != "x"
|
if key.as_ref() != "x"
|
||||||
&& key.as_ref() != "y"
|
&& key.as_ref() != "y"
|
||||||
@ -195,6 +286,7 @@ impl StructurePieceBase for TemplatePiece {
|
|||||||
fn as_any(&self) -> &dyn std::any::Any {
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn place(
|
fn place(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk: &mut ProtoChunk,
|
chunk: &mut ProtoChunk,
|
||||||
@ -206,6 +298,10 @@ impl StructurePieceBase for TemplatePiece {
|
|||||||
self.place_blocks(chunk, chunk_box);
|
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 {
|
fn get_structure_piece(&self) -> &StructurePiece {
|
||||||
&self.piece
|
&self.piece
|
||||||
}
|
}
|
||||||
|
|||||||
@ -355,17 +355,20 @@ impl Level {
|
|||||||
|
|
||||||
let handle_count = handles.len();
|
let handle_count = handles.len();
|
||||||
info!("Joining {} threads for {}...", handle_count, world_id);
|
info!("Joining {} threads for {}...", handle_count, world_id);
|
||||||
let join_task = tokio::task::spawn_blocking(move || {
|
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||||
|
let _ = std::thread::Builder::new()
|
||||||
|
.name("Thread-Joiner".into())
|
||||||
|
.spawn(move || {
|
||||||
let mut failed_count = 0;
|
let mut failed_count = 0;
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
if handle.join().is_err() {
|
if handle.join().is_err() {
|
||||||
failed_count += 1;
|
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)) => {
|
Ok(Ok(failed_count)) => {
|
||||||
if failed_count > 0 {
|
if failed_count > 0 {
|
||||||
warn!(
|
warn!(
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use crate::generation::proto_chunk::GenerationCache;
|
use crate::generation::proto_chunk::GenerationCache;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use pumpkin_data::{Block, BlockState, BlockStateId, Mirror, Rotation, chunk::Biome};
|
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 {
|
pub trait WorldPortalExt: Send + Sync {
|
||||||
fn can_place_at(
|
fn can_place_at(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@ -157,7 +157,7 @@ impl BlockBehaviour for BedBlock {
|
|||||||
};
|
};
|
||||||
|
|
||||||
args.world
|
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<'_>) {
|
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
|
||||||
|
|||||||
@ -281,7 +281,7 @@ impl BlockBehaviour for DoorBlock {
|
|||||||
};
|
};
|
||||||
|
|
||||||
args.world
|
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<'_>) {
|
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||||
|
|||||||
@ -233,7 +233,7 @@ impl HopperBlockEntity {
|
|||||||
);
|
);
|
||||||
let entities = world.get_entities_at_box(&search_box);
|
let entities = world.get_entities_at_box(&search_box);
|
||||||
for entity_base in entities {
|
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 (is_empty, registry_key) = {
|
||||||
let stack = item_entity
|
let stack = item_entity
|
||||||
.get_item_stack()
|
.get_item_stack()
|
||||||
|
|||||||
@ -50,7 +50,7 @@ use crate::block::blocks::ice::{FrostedIceBlock, IceBlock};
|
|||||||
use crate::block::blocks::infested::InfestedBlock;
|
use crate::block::blocks::infested::InfestedBlock;
|
||||||
use crate::block::blocks::iron_bars::IronBarsBlock;
|
use crate::block::blocks::iron_bars::IronBarsBlock;
|
||||||
use crate::block::blocks::jigsaw::JigsawBlock;
|
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::logs::LogBlock;
|
||||||
use crate::block::blocks::loom::LoomBlock;
|
use crate::block::blocks::loom::LoomBlock;
|
||||||
use crate::block::blocks::magma::MagmaBlock;
|
use crate::block::blocks::magma::MagmaBlock;
|
||||||
@ -261,7 +261,8 @@ pub fn default_registry() -> Arc<BlockRegistry> {
|
|||||||
manager.register(InfestedBlock);
|
manager.register(InfestedBlock);
|
||||||
manager.register(JukeboxBlock);
|
manager.register(JukeboxBlock);
|
||||||
manager.register(LogBlock);
|
manager.register(LogBlock);
|
||||||
manager.register(LeavesBlock);
|
// FIX despawn
|
||||||
|
// manager.register(LeavesBlock);
|
||||||
manager.register(BambooBlock);
|
manager.register(BambooBlock);
|
||||||
manager.register(BambooSaplingBlock);
|
manager.register(BambooSaplingBlock);
|
||||||
manager.register(BannerBlock);
|
manager.register(BannerBlock);
|
||||||
|
|||||||
@ -142,7 +142,7 @@ fn report_resolved_profile(
|
|||||||
source.send_feedback(msg, false);
|
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) {
|
if let Some(player) = server.get_player_by_name(name) {
|
||||||
return Some(player.gameprofile.clone());
|
return Some(player.gameprofile.clone());
|
||||||
}
|
}
|
||||||
@ -155,14 +155,7 @@ async fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<Gam
|
|||||||
.java
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let name_string = name.to_string();
|
let mojang_res = lookup_profile_by_name(name, &auth_config).ok().flatten();
|
||||||
|
|
||||||
let mojang_res =
|
|
||||||
tokio::task::spawn_blocking(move || lookup_profile_by_name(&name_string, &auth_config))
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.and_then(Result::ok)
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
if let Some((uuid, resolved_name)) = mojang_res {
|
if let Some((uuid, resolved_name)) = mojang_res {
|
||||||
server
|
server
|
||||||
@ -177,11 +170,8 @@ async fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<Gam
|
|||||||
.java
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let full_profile =
|
let full_profile = fetch_profile_by_uuid(uuid, &auth_config_clone)
|
||||||
tokio::task::spawn_blocking(move || fetch_profile_by_uuid(uuid, &auth_config_clone))
|
|
||||||
.await
|
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(Result::ok)
|
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
return Some(full_profile.unwrap_or_else(|| GameProfile {
|
return Some(full_profile.unwrap_or_else(|| GameProfile {
|
||||||
@ -222,7 +212,7 @@ async fn fetch_profile_by_name_helper(server: &Server, name: &str) -> Option<Gam
|
|||||||
None
|
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) {
|
if let Some(player) = server.get_player_by_uuid(id) {
|
||||||
return Some(player.gameprofile.clone());
|
return Some(player.gameprofile.clone());
|
||||||
}
|
}
|
||||||
@ -233,11 +223,7 @@ async fn fetch_profile_by_id_helper(server: &Server, id: Uuid) -> Option<GamePro
|
|||||||
.java
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let mojang_res = tokio::task::spawn_blocking(move || fetch_profile_by_uuid(id, &auth_config))
|
let mojang_res = fetch_profile_by_uuid(id, &auth_config).ok().flatten();
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.and_then(Result::ok)
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
if let Some(profile) = mojang_res {
|
if let Some(profile) = mojang_res {
|
||||||
server
|
server
|
||||||
@ -272,9 +258,8 @@ impl CommandExecutor for ResolveNameExecutor {
|
|||||||
let name_owned = name.to_string();
|
let name_owned = name.to_string();
|
||||||
|
|
||||||
let name_component = TextComponent::text(name_owned.clone());
|
let name_component = TextComponent::text(name_owned.clone());
|
||||||
let runtime = server.runtime.clone();
|
rayon::spawn(move || {
|
||||||
runtime.spawn(async move {
|
let result = fetch_profile_by_name_helper(&server, &name_owned);
|
||||||
let result = fetch_profile_by_name_helper(&server, &name_owned).await;
|
|
||||||
match result {
|
match result {
|
||||||
Some(profile) => {
|
Some(profile) => {
|
||||||
report_resolved_profile(
|
report_resolved_profile(
|
||||||
@ -307,9 +292,8 @@ impl CommandExecutor for ResolveIdExecutor {
|
|||||||
let source = context.source.clone();
|
let source = context.source.clone();
|
||||||
|
|
||||||
let id_component = TextComponent::text(id.to_string());
|
let id_component = TextComponent::text(id.to_string());
|
||||||
let runtime = server.runtime.clone();
|
rayon::spawn(move || {
|
||||||
runtime.spawn(async move {
|
let result = fetch_profile_by_id_helper(&server, id);
|
||||||
let result = fetch_profile_by_id_helper(&server, id).await;
|
|
||||||
match result {
|
match result {
|
||||||
Some(profile) => {
|
Some(profile) => {
|
||||||
report_resolved_profile(
|
report_resolved_profile(
|
||||||
|
|||||||
@ -111,7 +111,7 @@ impl CommandExecutor for LoadExecutor {
|
|||||||
let sender_clone = sender.clone();
|
let sender_clone = sender.clone();
|
||||||
let plugin_name_clone = plugin_name;
|
let plugin_name_clone = plugin_name;
|
||||||
let server_clone = server_arc.clone();
|
let server_clone = server_arc.clone();
|
||||||
server_arc.runtime.spawn(async move {
|
server_arc.spawn_task(async move {
|
||||||
let result = server_clone
|
let result = server_clone
|
||||||
.plugin_manager
|
.plugin_manager
|
||||||
.try_load_plugin(&server_clone, Path::new(&plugin_name_clone))
|
.try_load_plugin(&server_clone, Path::new(&plugin_name_clone))
|
||||||
@ -171,7 +171,7 @@ impl CommandExecutor for UnloadExecutor {
|
|||||||
let sender_clone = sender.clone();
|
let sender_clone = sender.clone();
|
||||||
let plugin_name_clone = plugin_name;
|
let plugin_name_clone = plugin_name;
|
||||||
let server_clone = server_arc.clone();
|
let server_clone = server_arc.clone();
|
||||||
server_arc.runtime.spawn(async move {
|
server_arc.spawn_task(async move {
|
||||||
let result = server_clone
|
let result = server_clone
|
||||||
.plugin_manager
|
.plugin_manager
|
||||||
.unload_plugin(&plugin_name_clone)
|
.unload_plugin(&plugin_name_clone)
|
||||||
@ -221,7 +221,7 @@ impl CommandExecutor for HotReloadExecutor {
|
|||||||
let sender_clone = sender.clone();
|
let sender_clone = sender.clone();
|
||||||
let server_clone = server_arc.clone();
|
let server_clone = server_arc.clone();
|
||||||
if enabled {
|
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 {
|
if let Err(e) = server_clone.plugin_manager.start_watcher(&server_clone).await {
|
||||||
sender_clone.send_message(TextComponent::text(format!(
|
sender_clone.send_message(TextComponent::text(format!(
|
||||||
"Failed to start plugin watcher: {e}"
|
"Failed to start plugin watcher: {e}"
|
||||||
@ -241,7 +241,7 @@ impl CommandExecutor for HotReloadExecutor {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
server_arc.runtime.spawn(async move {
|
server_arc.spawn_task(async move {
|
||||||
server_clone.plugin_manager.stop_watcher().await;
|
server_clone.plugin_manager.stop_watcher().await;
|
||||||
sender_clone.send_message(
|
sender_clone.send_message(
|
||||||
TextComponent::text("Hot reloading has been disabled.")
|
TextComponent::text("Hot reloading has been disabled.")
|
||||||
|
|||||||
@ -77,17 +77,9 @@ fn rotate_entity(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Use teleport with same position to update rotation
|
// Use teleport with same position to update rotation
|
||||||
// This properly handles both players (sends CPlayerPosition) and other entities
|
|
||||||
let pos = entity.pos.load();
|
let pos = entity.pos.load();
|
||||||
let world = entity.world.load_full();
|
let world = entity.world.load_full();
|
||||||
if let Some(player) = target.get_player() {
|
target.teleport(pos, Some(final_yaw), Some(final_pitch), world);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends success message for the rotate command.
|
/// Sends success message for the rotate command.
|
||||||
|
|||||||
@ -27,10 +27,10 @@ impl CommandExecutor for SaveAllExecutor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let server_arc = context.server().clone();
|
let server_arc = context.server().clone();
|
||||||
|
let server_clone = server_arc.clone();
|
||||||
let source = context.source.clone();
|
let source = context.source.clone();
|
||||||
let runtime = server_arc.runtime.clone();
|
server_arc.spawn_task(async move {
|
||||||
runtime.spawn(async move {
|
if let Err(err) = server_clone.save_all().await {
|
||||||
if let Err(err) = server_arc.save_all().await {
|
|
||||||
error!("Failed to save server data: {err}");
|
error!("Failed to save server data: {err}");
|
||||||
source.send_error(TextComponent::translate_cross(
|
source.send_error(TextComponent::translate_cross(
|
||||||
translation::java::COMMANDS_SAVE_FAILED,
|
translation::java::COMMANDS_SAVE_FAILED,
|
||||||
|
|||||||
@ -105,11 +105,7 @@ impl CommandExecutor for SpectateTargetSelfExecutor {
|
|||||||
let yaw = target_entity.yaw.load();
|
let yaw = target_entity.yaw.load();
|
||||||
let pitch = target_entity.pitch.load();
|
let pitch = target_entity.pitch.load();
|
||||||
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
||||||
player.spawn_task(
|
player.teleport(pos, Some(yaw), Some(pitch), player_world);
|
||||||
player
|
|
||||||
.clone()
|
|
||||||
.teleport(pos, Some(yaw), Some(pitch), player_world),
|
|
||||||
);
|
|
||||||
|
|
||||||
let target_name = target.get_display_name();
|
let target_name = target.get_display_name();
|
||||||
sender.send_message(TextComponent::translate_cross(
|
sender.send_message(TextComponent::translate_cross(
|
||||||
@ -178,11 +174,7 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
|
|||||||
let pitch = target_entity.pitch.load();
|
let pitch = target_entity.pitch.load();
|
||||||
let player_world = player.world();
|
let player_world = player.world();
|
||||||
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
||||||
player.spawn_task(
|
player.teleport(pos, Some(yaw), Some(pitch), player_world);
|
||||||
player
|
|
||||||
.clone()
|
|
||||||
.teleport(pos, Some(yaw), Some(pitch), player_world),
|
|
||||||
);
|
|
||||||
succeeded += 1;
|
succeeded += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -269,12 +269,12 @@ impl CommandExecutor for SpreadPlayersExecutor {
|
|||||||
for (index, target) in targets.iter().enumerate() {
|
for (index, target) in targets.iter().enumerate() {
|
||||||
let pile = piles[index % pile_count];
|
let pile = piles[index % pile_count];
|
||||||
let y = surface_ys[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),
|
Vector3::new(pile.x.floor() + 0.5, f64::from(y), pile.z.floor() + 0.5),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
world.clone(),
|
world.clone(),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let average_distance = average_min_distance(&piles);
|
let average_distance = average_min_distance(&piles);
|
||||||
|
|||||||
@ -82,7 +82,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
|
|||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
sender: &CommandSender,
|
sender: &CommandSender,
|
||||||
server: &crate::server::Server,
|
_server: &crate::server::Server,
|
||||||
args: &ConsumedArgs,
|
args: &ConsumedArgs,
|
||||||
) -> CommandResult {
|
) -> CommandResult {
|
||||||
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
|
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
|
||||||
@ -101,12 +101,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
for target in targets {
|
for target in targets {
|
||||||
server.runtime.spawn(target.clone().teleport(
|
target.teleport(pos, Some(yaw), Some(pitch), world.clone());
|
||||||
pos,
|
|
||||||
Some(yaw),
|
|
||||||
Some(pitch),
|
|
||||||
world.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (key, target_arg) = success_key_and_arg(
|
let (key, target_arg) = success_key_and_arg(
|
||||||
@ -148,12 +143,7 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor {
|
|||||||
let world = resolve_sender_world(sender, server)?;
|
let world = resolve_sender_world(sender, server)?;
|
||||||
|
|
||||||
for target in targets {
|
for target in targets {
|
||||||
server.runtime.spawn(target.clone().teleport(
|
target.teleport(pos, Some(yaw), Some(pitch), world.clone());
|
||||||
pos,
|
|
||||||
Some(yaw),
|
|
||||||
Some(pitch),
|
|
||||||
world.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (key, target_arg) = success_key_and_arg(
|
let (key, target_arg) = success_key_and_arg(
|
||||||
@ -200,12 +190,7 @@ impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
|
|||||||
let world = resolve_sender_world(sender, server)?;
|
let world = resolve_sender_world(sender, server)?;
|
||||||
|
|
||||||
for target in targets {
|
for target in targets {
|
||||||
server.runtime.spawn(target.clone().teleport(
|
target.teleport(pos, Some(yaw), Some(pitch), world.clone());
|
||||||
pos,
|
|
||||||
Some(yaw),
|
|
||||||
Some(pitch),
|
|
||||||
world.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (key, target_arg) = success_key_and_arg(
|
let (key, target_arg) = success_key_and_arg(
|
||||||
@ -253,12 +238,7 @@ impl CommandExecutor for EntitiesToPosWithRotationExecutor {
|
|||||||
let world = resolve_sender_world(sender, server)?;
|
let world = resolve_sender_world(sender, server)?;
|
||||||
|
|
||||||
for target in targets {
|
for target in targets {
|
||||||
server.runtime.spawn(target.clone().teleport(
|
target.teleport(pos, Some(yaw), Some(pitch), world.clone());
|
||||||
pos,
|
|
||||||
Some(yaw),
|
|
||||||
Some(pitch),
|
|
||||||
world.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (key, target_arg) = success_key_and_arg(
|
let (key, target_arg) = success_key_and_arg(
|
||||||
@ -304,12 +284,7 @@ impl CommandExecutor for EntitiesToPosExecutor {
|
|||||||
for target in targets {
|
for target in targets {
|
||||||
let yaw = target.get_entity().yaw.load();
|
let yaw = target.get_entity().yaw.load();
|
||||||
let pitch = target.get_entity().pitch.load();
|
let pitch = target.get_entity().pitch.load();
|
||||||
server.runtime.spawn(target.clone().teleport(
|
target.teleport(pos, Some(yaw), Some(pitch), world.clone());
|
||||||
pos,
|
|
||||||
Some(yaw),
|
|
||||||
Some(pitch),
|
|
||||||
world.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (key, target_arg) = success_key_and_arg(
|
let (key, target_arg) = success_key_and_arg(
|
||||||
@ -338,7 +313,7 @@ impl CommandExecutor for SelfToEntityExecutor {
|
|||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
sender: &CommandSender,
|
sender: &CommandSender,
|
||||||
server: &crate::server::Server,
|
_server: &crate::server::Server,
|
||||||
args: &ConsumedArgs,
|
args: &ConsumedArgs,
|
||||||
) -> CommandResult {
|
) -> CommandResult {
|
||||||
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
|
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
|
||||||
@ -357,9 +332,7 @@ impl CommandExecutor for SelfToEntityExecutor {
|
|||||||
[],
|
[],
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
server
|
player.teleport(pos, Some(yaw), Some(pitch), world);
|
||||||
.runtime
|
|
||||||
.spawn(player.clone().teleport(pos, Some(yaw), Some(pitch), world));
|
|
||||||
|
|
||||||
sender.send_message(TextComponent::translate_cross(
|
sender.send_message(TextComponent::translate_cross(
|
||||||
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
|
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
|
||||||
@ -387,7 +360,7 @@ impl CommandExecutor for SelfToPosExecutor {
|
|||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
sender: &CommandSender,
|
sender: &CommandSender,
|
||||||
server: &crate::server::Server,
|
_server: &crate::server::Server,
|
||||||
args: &ConsumedArgs,
|
args: &ConsumedArgs,
|
||||||
) -> CommandResult {
|
) -> CommandResult {
|
||||||
match sender {
|
match sender {
|
||||||
@ -403,12 +376,7 @@ impl CommandExecutor for SelfToPosExecutor {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let player_world = player.world();
|
let player_world = player.world();
|
||||||
server.runtime.spawn(player.clone().teleport(
|
player.teleport(pos, Some(yaw), Some(pitch), player_world);
|
||||||
pos,
|
|
||||||
Some(yaw),
|
|
||||||
Some(pitch),
|
|
||||||
player_world,
|
|
||||||
));
|
|
||||||
|
|
||||||
sender.send_message(TextComponent::translate_cross(
|
sender.send_message(TextComponent::translate_cross(
|
||||||
translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE,
|
translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE,
|
||||||
|
|||||||
@ -65,19 +65,16 @@ impl AdvancementManager {
|
|||||||
to_write.push((guard.path.clone(), json));
|
to_write.push((guard.path.clone(), json));
|
||||||
}
|
}
|
||||||
|
|
||||||
let advancement_path = self.advancement_path.clone();
|
if let Err(e) = tokio::fs::create_dir_all(&self.advancement_path).await {
|
||||||
tokio::task::spawn_blocking(move || {
|
|
||||||
if let Err(e) = create_dir_all(advancement_path) {
|
|
||||||
error!("Failed to create player advancement directory : {e}");
|
error!("Failed to create player advancement directory : {e}");
|
||||||
return Err(AdvancementDataError::Io(e));
|
return Err(AdvancementDataError::Io(e));
|
||||||
}
|
}
|
||||||
for (path, json) in to_write {
|
for (path, json) in to_write {
|
||||||
std::fs::write(&path, json).map_err(AdvancementDataError::Io)?;
|
tokio::fs::write(&path, json)
|
||||||
|
.await
|
||||||
|
.map_err(AdvancementDataError::Io)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap_or(Ok(()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves the advancements of a specific player.
|
/// Saves the advancements of a specific player.
|
||||||
|
|||||||
@ -44,7 +44,7 @@ impl ServerPlayerData {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// A Result indicating success or the error that occurred.
|
/// 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
|
||||||
.player_screen_handler
|
.player_screen_handler
|
||||||
.lock()
|
.lock()
|
||||||
@ -55,14 +55,7 @@ impl ServerPlayerData {
|
|||||||
let mut nbt = NbtCompound::new();
|
let mut nbt = NbtCompound::new();
|
||||||
player.write_nbt(&mut nbt);
|
player.write_nbt(&mut nbt);
|
||||||
|
|
||||||
let storage = self.storage.clone();
|
self.storage.save_player_data(&player.gameprofile.id, nbt)?;
|
||||||
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,
|
|
||||||
))))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +87,7 @@ impl ServerPlayerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let storage = self.storage.clone();
|
let storage = self.storage.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
rayon::spawn(move || {
|
||||||
for (uuid, nbt) in snapshots {
|
for (uuid, nbt) in snapshots {
|
||||||
if let Err(e) = storage.save_player_data(&uuid, nbt) {
|
if let Err(e) = storage.save_player_data(&uuid, nbt) {
|
||||||
error!("Failed to save player data for {uuid}: {e}");
|
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.
|
/// This function immediately saves all online players' data to disk.
|
||||||
/// Useful for server shutdown or backup operations.
|
/// 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;
|
let mut total_players = 0;
|
||||||
|
|
||||||
// Save players from all worlds
|
// Save players from all worlds
|
||||||
for world in server.worlds.load().iter() {
|
for world in server.worlds.load().iter() {
|
||||||
for player in world.players.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;
|
total_players += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,15 +129,8 @@ impl ServerPlayerData {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// A Result indicating success or the error that occurred.
|
/// A Result indicating success or the error that occurred.
|
||||||
pub async fn load_data(
|
pub fn load_data(&self, uuid: &uuid::Uuid) -> Result<Option<NbtCompound>, PlayerDataError> {
|
||||||
&self,
|
let result = self.storage.load_player_data(uuid);
|
||||||
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())));
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok((should_load, data)) => {
|
Ok((should_load, data)) => {
|
||||||
@ -179,10 +165,7 @@ impl ServerPlayerData {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// A Result indicating success or the error that occurred.
|
/// A Result indicating success or the error that occurred.
|
||||||
pub async fn extract_data_and_save_player(
|
pub fn extract_data_and_save_player(&self, player: &Player) -> Result<(), PlayerDataError> {
|
||||||
&self,
|
|
||||||
player: &Player,
|
|
||||||
) -> Result<(), PlayerDataError> {
|
|
||||||
if !self.storage.is_save_enabled() {
|
if !self.storage.is_save_enabled() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -191,13 +174,7 @@ impl ServerPlayerData {
|
|||||||
let mut nbt = NbtCompound::new();
|
let mut nbt = NbtCompound::new();
|
||||||
player.write_nbt(&mut nbt);
|
player.write_nbt(&mut nbt);
|
||||||
|
|
||||||
let storage = self.storage.clone();
|
self.storage.save_player_data(&uuid, nbt)?;
|
||||||
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,
|
|
||||||
))))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -216,7 +216,7 @@ impl EntityBase for AreaEffectCloudEntity {
|
|||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
#[allow(clippy::semicolon_outside_block)]
|
#[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
|
// Age & duration handling
|
||||||
{
|
{
|
||||||
let mut age = self
|
let mut age = self
|
||||||
|
|||||||
@ -850,7 +850,7 @@ impl Mob for EnderDragonEntity {
|
|||||||
&self.mob_entity
|
&self.mob_entity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
|
fn mob_tick(&self, _caller: &dyn EntityBase) {
|
||||||
self.ai_step();
|
self.ai_step();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
self.tick_wither();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ use pumpkin_protocol::codec::var_int::VarInt;
|
|||||||
use pumpkin_protocol::java::client::play::Metadata;
|
use pumpkin_protocol::java::client::play::Metadata;
|
||||||
use pumpkin_util::GameMode;
|
use pumpkin_util::GameMode;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicI32, Ordering};
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
|
|
||||||
pub const MAX_AIR: i32 = 300;
|
pub const MAX_AIR: i32 = 300;
|
||||||
@ -32,7 +31,7 @@ impl Default for BreathManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BreathManager {
|
impl BreathManager {
|
||||||
pub fn tick(&self, player: &Arc<Player>) {
|
pub fn tick(&self, player: &Player) {
|
||||||
let mode = player.gamemode.load();
|
let mode = player.gamemode.load();
|
||||||
|
|
||||||
if matches!(mode, GameMode::Creative | GameMode::Spectator) {
|
if matches!(mode, GameMode::Creative | GameMode::Spectator) {
|
||||||
@ -84,11 +83,9 @@ impl BreathManager {
|
|||||||
|
|
||||||
if t >= DROWNING_INTERVAL {
|
if t >= DROWNING_INTERVAL {
|
||||||
self.drowning_tick.store(0, Ordering::Relaxed);
|
self.drowning_tick.store(0, Ordering::Relaxed);
|
||||||
player.living_entity.damage(
|
player
|
||||||
player.as_ref(),
|
.living_entity
|
||||||
DROWNING_DAMAGE,
|
.damage(player, DROWNING_DAMAGE, DamageType::DROWN);
|
||||||
DamageType::DROWN,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -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) {
|
fn init_data_tracker(&self) {
|
||||||
self.display.init_display_data_tracker();
|
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) {
|
fn init_data_tracker(&self) {
|
||||||
self.display.init_display_data_tracker();
|
self.display.init_display_data_tracker();
|
||||||
@ -1264,7 +1264,7 @@ impl EntityBase for TextDisplayEntity {
|
|||||||
self.flags.store(flags, Ordering::Relaxed);
|
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) {
|
fn init_data_tracker(&self) {
|
||||||
self.display.init_display_data_tracker();
|
self.display.init_display_data_tracker();
|
||||||
|
|||||||
@ -2,19 +2,19 @@ use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::entity::player::Player;
|
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 crossbeam::atomic::AtomicCell;
|
||||||
use pumpkin_data::BlockDirection;
|
use pumpkin_data::BlockDirection;
|
||||||
use pumpkin_data::damage::DamageType;
|
use pumpkin_data::damage::DamageType;
|
||||||
use pumpkin_data::entity::EntityType;
|
use pumpkin_data::entity::EntityType;
|
||||||
use pumpkin_data::item::Item;
|
use pumpkin_data::item::Item;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_data::packet::CURRENT_MC_VERSION;
|
|
||||||
use pumpkin_data::sound::Sound;
|
use pumpkin_data::sound::Sound;
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||||
use pumpkin_protocol::java::client::play::{CSetEntityMetadata, Metadata};
|
use pumpkin_protocol::java::client::play::{CSetEntityMetadata, Metadata};
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
|
|
||||||
/// An item frame or glow item frame.
|
/// An item frame or glow item frame.
|
||||||
///
|
///
|
||||||
@ -359,18 +359,14 @@ impl EntityBase for ItemFrameEntity {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_java_spawn_packet<'a>(
|
fn send_java_spawn_packet(&self, client: &crate::net::java::JavaClient) {
|
||||||
&'a self,
|
|
||||||
client: &'a crate::net::java::JavaClient,
|
|
||||||
) -> EntityBaseFuture<'a, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let spawn_packet = self.entity.create_spawn_packet();
|
let spawn_packet = self.entity.create_spawn_packet();
|
||||||
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
||||||
client.enqueue_packet(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ver = client.version.load();
|
let ver = client.version.load();
|
||||||
if ver >= CURRENT_MC_VERSION {
|
if ver >= JavaMinecraftVersion::V_1_21 {
|
||||||
let item_serializer = ItemStackSerializer::from(
|
let item_serializer = ItemStackSerializer::from(
|
||||||
self.item_stack
|
self.item_stack
|
||||||
.lock()
|
.lock()
|
||||||
@ -387,18 +383,15 @@ impl EntityBase for ItemFrameEntity {
|
|||||||
let meta_rot =
|
let meta_rot =
|
||||||
Metadata::new(pumpkin_data::tracked_data::item_frame::ROTATION, rotation);
|
Metadata::new(pumpkin_data::tracked_data::item_frame::ROTATION, rotation);
|
||||||
|
|
||||||
if meta_item.write(&mut data, &ver).is_ok()
|
if meta_item.write(&mut data, &ver).is_ok() && meta_rot.write(&mut data, &ver).is_ok() {
|
||||||
&& meta_rot.write(&mut data, &ver).is_ok()
|
|
||||||
{
|
|
||||||
data.push(255);
|
data.push(255);
|
||||||
let meta_packet =
|
let meta_packet =
|
||||||
CSetEntityMetadata::new(self.entity.entity_id.into(), data.into());
|
CSetEntityMetadata::new(self.entity.entity_id.into(), data.into());
|
||||||
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
||||||
client.enqueue_packet(meta_data).await;
|
client.try_enqueue_packet(meta_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
|
fn interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
|
||||||
|
|||||||
@ -93,7 +93,7 @@ impl EntityBase for LeashKnotEntity {
|
|||||||
None
|
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 world = self.entity.world.load();
|
||||||
let block = world.get_block(&self.pos);
|
let block = world.get_block(&self.pos);
|
||||||
if !block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_FENCES) {
|
if !block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_FENCES) {
|
||||||
|
|||||||
@ -66,7 +66,7 @@ impl ExperienceOrbEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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;
|
let entity = &self.entity;
|
||||||
entity.tick(caller, server);
|
entity.tick(caller, server);
|
||||||
let bounding_box = entity.bounding_box.load();
|
let bounding_box = entity.bounding_box.load();
|
||||||
|
|||||||
@ -46,7 +46,7 @@ impl FallingEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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 entity = &self.entity;
|
||||||
let mut velo = entity.velocity.load();
|
let mut velo = entity.velocity.load();
|
||||||
velo.y -= self.get_gravity();
|
velo.y -= self.get_gravity();
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::{NBTStorage, NBTStorageInit, player::Player};
|
use super::{NBTStorage, NBTStorageInit, player::Player};
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use pumpkin_data::damage::DamageType;
|
use pumpkin_data::damage::DamageType;
|
||||||
@ -29,7 +27,7 @@ impl Default for HungerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HungerManager {
|
impl HungerManager {
|
||||||
pub fn tick(&self, player: &Arc<Player>) {
|
pub fn tick(&self, player: &Player) {
|
||||||
let mut level = self.level.load();
|
let mut level = self.level.load();
|
||||||
let mut saturation = self.saturation.load();
|
let mut saturation = self.saturation.load();
|
||||||
let mut exhaustion = self.exhaustion.load();
|
let mut exhaustion = self.exhaustion.load();
|
||||||
@ -109,7 +107,7 @@ impl HungerManager {
|
|||||||
if damage_amount > 0.0 {
|
if damage_amount > 0.0 {
|
||||||
player
|
player
|
||||||
.living_entity
|
.living_entity
|
||||||
.damage(player.as_ref(), damage_amount, DamageType::STARVE);
|
.damage(player, damage_amount, DamageType::STARVE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
fn init_data_tracker(&self) {
|
||||||
let width = *self
|
let width = *self
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
use crate::entity::player::statistics::StatisticCategory;
|
use crate::entity::player::statistics::StatisticCategory;
|
||||||
use crate::{entity::EntityBaseFuture, server::Server};
|
use crate::server::Server;
|
||||||
use core::f32;
|
use core::f32;
|
||||||
use pumpkin_data::damage::DamageType;
|
use pumpkin_data::damage::DamageType;
|
||||||
use pumpkin_data::data_component_impl::DamageResistantImpl;
|
use pumpkin_data::data_component_impl::DamageResistantImpl;
|
||||||
use pumpkin_data::data_component_impl::DamageResistantType;
|
use pumpkin_data::data_component_impl::DamageResistantType;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_data::packet::CURRENT_MC_VERSION;
|
|
||||||
use pumpkin_nbt::compound::NbtCompound;
|
use pumpkin_nbt::compound::NbtCompound;
|
||||||
use pumpkin_protocol::bedrock::client::CAddItemActor;
|
use pumpkin_protocol::bedrock::client::CAddItemActor;
|
||||||
use pumpkin_protocol::bedrock::network_item::ItemStackWrapper;
|
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_protocol::java::client::play::{CSetEntityMetadata, Metadata};
|
||||||
use pumpkin_util::math::atomic_f32::AtomicF32;
|
use pumpkin_util::math::atomic_f32::AtomicF32;
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
use std::sync::atomic::Ordering::{AcqRel, Relaxed};
|
use std::sync::atomic::Ordering::{AcqRel, Relaxed};
|
||||||
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
@ -142,10 +142,10 @@ impl ItemEntity {
|
|||||||
|
|
||||||
let world = self.entity.world.load();
|
let world = self.entity.world.load();
|
||||||
let entities = world.entities.load();
|
let entities = world.entities.load();
|
||||||
let items: Vec<Arc<Self>> = entities
|
let items: Vec<&Self> = entities
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entity: &Arc<dyn EntityBase>| {
|
.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.entity.entity_id != self.entity.entity_id
|
||||||
&& !item.never_despawn.load(Ordering::Relaxed)
|
&& !item.never_despawn.load(Ordering::Relaxed)
|
||||||
&& item.entity.bounding_box.load().intersects(&bounding_box)
|
&& 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)
|
if let Some(this_base) = world.get_entity_by_id(self.entity.entity_id)
|
||||||
&& let Some(this_item) = this_base.get_item_entity()
|
&& 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) {
|
if self.entity.removed.load(Ordering::SeqCst) {
|
||||||
@ -343,7 +343,7 @@ impl ItemEntity {
|
|||||||
|
|
||||||
fn move_and_apply_friction(
|
fn move_and_apply_friction(
|
||||||
&self,
|
&self,
|
||||||
caller: &Arc<dyn EntityBase>,
|
caller: &dyn EntityBase,
|
||||||
server: &Server,
|
server: &Server,
|
||||||
move_velo: Vector3<f64>,
|
move_velo: Vector3<f64>,
|
||||||
) {
|
) {
|
||||||
@ -415,7 +415,7 @@ impl ItemEntity {
|
|||||||
true
|
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;
|
let entity = &self.entity;
|
||||||
|
|
||||||
entity.update_fluid_state(caller);
|
entity.update_fluid_state(caller);
|
||||||
@ -443,7 +443,7 @@ impl ItemEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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;
|
let entity = &self.entity;
|
||||||
self.decrement_pickup_delay();
|
self.decrement_pickup_delay();
|
||||||
|
|
||||||
@ -591,7 +591,7 @@ impl EntityBase for ItemEntity {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_item_entity(self: Arc<Self>) -> Option<Arc<ItemEntity>> {
|
fn get_item_entity(&self) -> Option<&ItemEntity> {
|
||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -646,11 +646,7 @@ impl EntityBase for ItemEntity {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_bedrock_spawn_packet<'a>(
|
fn send_bedrock_spawn_packet(&self, client: &crate::net::bedrock::BedrockClient) {
|
||||||
&'a self,
|
|
||||||
client: &'a crate::net::bedrock::BedrockClient,
|
|
||||||
) -> EntityBaseFuture<'a, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let entity = &self.entity;
|
let entity = &self.entity;
|
||||||
let runtime_id = entity.entity_id as u64;
|
let runtime_id = entity.entity_id as u64;
|
||||||
let data = {
|
let data = {
|
||||||
@ -670,22 +666,17 @@ impl EntityBase for ItemEntity {
|
|||||||
client.serialize_packet(&packet).ok()
|
client.serialize_packet(&packet).ok()
|
||||||
};
|
};
|
||||||
if let Some(data) = data {
|
if let Some(data) = data {
|
||||||
client.send_game_packet(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_java_spawn_packet<'a>(
|
fn send_java_spawn_packet(&self, client: &crate::net::java::JavaClient) {
|
||||||
&'a self,
|
|
||||||
client: &'a crate::net::java::JavaClient,
|
|
||||||
) -> EntityBaseFuture<'a, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let spawn_packet = self.entity.create_spawn_packet();
|
let spawn_packet = self.entity.create_spawn_packet();
|
||||||
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
||||||
client.enqueue_packet(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.version.load() >= CURRENT_MC_VERSION {
|
if client.version.load() >= JavaMinecraftVersion::V_1_21 {
|
||||||
let metadata = Metadata::new(
|
let metadata = Metadata::new(
|
||||||
pumpkin_data::tracked_data::item::ITEM,
|
pumpkin_data::tracked_data::item::ITEM,
|
||||||
ItemStackSerializer::from(
|
ItemStackSerializer::from(
|
||||||
@ -701,10 +692,9 @@ impl EntityBase for ItemEntity {
|
|||||||
let meta_packet =
|
let meta_packet =
|
||||||
CSetEntityMetadata::new(self.entity.entity_id.into(), data.into());
|
CSetEntityMetadata::new(self.entity.entity_id.into(), data.into());
|
||||||
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
||||||
client.enqueue_packet(meta_data).await;
|
client.try_enqueue_packet(meta_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -192,7 +192,7 @@ impl LightningBoltEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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 entity = &self.entity;
|
||||||
let life = self.life.load(Ordering::Relaxed);
|
let life = self.life.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
|||||||
@ -958,7 +958,7 @@ impl LivingEntity {
|
|||||||
.has_tag(&tag::EntityType::MINECRAFT_FALL_DAMAGE_IMMUNE)
|
.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();
|
let final_gravity = caller.get_gravity();
|
||||||
|
|
||||||
if self.entity.velocity.load().y <= 0.0 && self.has_effect(&StatusEffect::SLOW_FALLING) {
|
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);
|
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 {
|
if self.jumping_cooldown.load(Relaxed) != 0 {
|
||||||
self.jumping_cooldown.fetch_sub(1, Relaxed);
|
self.jumping_cooldown.fetch_sub(1, Relaxed);
|
||||||
}
|
}
|
||||||
@ -1063,11 +1063,11 @@ impl LivingEntity {
|
|||||||
let suffocating = self.entity.tick_block_collisions(caller, server);
|
let suffocating = self.entity.tick_block_collisions(caller, server);
|
||||||
|
|
||||||
if suffocating {
|
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
|
// applyMovementInput
|
||||||
|
|
||||||
let effective_speed = self.get_attribute_value(&Attributes::MOVEMENT_SPEED);
|
let effective_speed = self.get_attribute_value(&Attributes::MOVEMENT_SPEED);
|
||||||
@ -1104,7 +1104,7 @@ impl LivingEntity {
|
|||||||
let mut velo = self.entity.velocity.load();
|
let mut velo = self.entity.velocity.load();
|
||||||
|
|
||||||
let can_powder_snow_climb = if self.entity.was_in_powder_snow.load(Relaxed) {
|
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 {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
@ -1144,7 +1144,7 @@ impl LivingEntity {
|
|||||||
self.entity.velocity.store(velo);
|
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 movement_input = self.movement_input.load();
|
||||||
|
|
||||||
let falling = self.entity.velocity.load().y <= 0.0;
|
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.entity.move_entity(caller, self.entity.velocity.load());
|
||||||
|
|
||||||
self.check_climbing();
|
self.check_climbing();
|
||||||
@ -2635,7 +2635,7 @@ impl EntityBase for LivingEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[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);
|
self.entity.tick(caller, server);
|
||||||
|
|
||||||
// Only tick movement if the entity is alive. This prevents a dead "corpse"
|
// 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) {
|
if is_alive || (in_death_animation && self.entity.entity_type != &EntityType::PLAYER) {
|
||||||
self.tick_movement(server, caller);
|
self.tick_movement(server, caller);
|
||||||
// Vanilla-like order: freeze logic runs after movement/collisions.
|
// Vanilla-like order: freeze logic runs after movement/collisions.
|
||||||
self.entity.tick_frozen(caller.as_ref());
|
self.entity.tick_frozen(caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@ -2670,14 +2670,9 @@ impl EntityBase for LivingEntity {
|
|||||||
let world = self.entity.world.load_full();
|
let world = self.entity.world.load_full();
|
||||||
let (block, state) = world.get_block_and_state(&supporting);
|
let (block, state) = world.get_block_and_state(&supporting);
|
||||||
|
|
||||||
world.block_registry.on_entity_step(
|
world
|
||||||
block,
|
.block_registry
|
||||||
&world,
|
.on_entity_step(block, &world, caller, &supporting, state, false);
|
||||||
caller.as_ref() as &dyn EntityBase,
|
|
||||||
&supporting,
|
|
||||||
state,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check slightly below supporting_pos for additional supporting blocks (blocks under carpets and the like)
|
// Check slightly below supporting_pos for additional supporting blocks (blocks under carpets and the like)
|
||||||
if !block.is_solid() {
|
if !block.is_solid() {
|
||||||
@ -2688,7 +2683,7 @@ impl EntityBase for LivingEntity {
|
|||||||
world.block_registry.on_entity_step(
|
world.block_registry.on_entity_step(
|
||||||
below_block,
|
below_block,
|
||||||
&world,
|
&world,
|
||||||
caller.as_ref() as &dyn EntityBase,
|
caller,
|
||||||
&below_supporting,
|
&below_supporting,
|
||||||
below_state,
|
below_state,
|
||||||
true, // below supporting block
|
true, // below supporting block
|
||||||
@ -2702,14 +2697,7 @@ impl EntityBase for LivingEntity {
|
|||||||
if self.item_use_time.load(Ordering::Relaxed) > 0
|
if self.item_use_time.load(Ordering::Relaxed) > 0
|
||||||
&& self.item_use_time.fetch_sub(1, Ordering::Relaxed) <= 1
|
&& self.item_use_time.fetch_sub(1, Ordering::Relaxed) <= 1
|
||||||
{
|
{
|
||||||
let caller_clone = caller.clone();
|
let item_in_use = self
|
||||||
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 = living
|
|
||||||
.item_in_use
|
.item_in_use
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
@ -2718,25 +2706,31 @@ impl EntityBase for LivingEntity {
|
|||||||
// Consume item
|
// Consume item
|
||||||
let mut is_potion = false;
|
let mut is_potion = false;
|
||||||
if let Some(food) = item.get_data_component::<FoodImpl>()
|
if let Some(food) = item.get_data_component::<FoodImpl>()
|
||||||
&& let Some(player) = caller_clone.get_player()
|
&& let Some(player) = caller.get_player()
|
||||||
{
|
{
|
||||||
player.hunger_manager.eat(
|
player
|
||||||
player,
|
.hunger_manager
|
||||||
food.nutrition as u8,
|
.eat(player, food.nutrition as u8, food.saturation);
|
||||||
food.saturation,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
living.apply_consumable_effects(&caller_clone, item).await;
|
self.apply_consumable_effects(caller, item);
|
||||||
|
|
||||||
// Handle potion consumption
|
// Handle potion consumption
|
||||||
if item.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().is_some() {
|
if item
|
||||||
|
.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
let effects = crate::item::potion::PotionContents::read_potion_effects(item);
|
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);
|
crate::item::potion::PotionContents::apply_effects_to(
|
||||||
|
self,
|
||||||
|
effects,
|
||||||
|
1.0,
|
||||||
|
crate::item::potion::PotionApplicationSource::Normal,
|
||||||
|
);
|
||||||
is_potion = true;
|
is_potion = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(player) = caller_clone.get_player() {
|
if let Some(player) = caller.get_player() {
|
||||||
player.trigger_advancement(
|
player.trigger_advancement(
|
||||||
crate::entity::player::advancement::trigger::AdvancementTrigger::ConsumeItem {
|
crate::entity::player::advancement::trigger::AdvancementTrigger::ConsumeItem {
|
||||||
item_id: format!("minecraft:{}", item.item.registry_key),
|
item_id: format!("minecraft:{}", item.item.registry_key),
|
||||||
@ -2778,8 +2772,7 @@ impl EntityBase for LivingEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
off_hand
|
off_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||||
.decrement_unless_creative(player.gamemode.load(), 1);
|
|
||||||
}
|
}
|
||||||
player.inventory.set_stack_in_hand(Hand::Left, off_hand);
|
player.inventory.set_stack_in_hand(Hand::Left, off_hand);
|
||||||
handled = true;
|
handled = true;
|
||||||
@ -2788,13 +2781,12 @@ impl EntityBase for LivingEntity {
|
|||||||
|
|
||||||
if !handled {
|
if !handled {
|
||||||
// Use stored active_hand (as a fallback)
|
// Use stored active_hand (as a fallback)
|
||||||
let active_hand = *living
|
let active_hand = *self
|
||||||
.active_hand
|
.active_hand
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
let hand_to_modify = active_hand.unwrap_or(Hand::Right);
|
let hand_to_modify = active_hand.unwrap_or(Hand::Right);
|
||||||
let mut item_stack =
|
let mut item_stack = self.get_stack_in_hand(caller, hand_to_modify);
|
||||||
living.get_stack_in_hand(caller_clone.as_ref(), hand_to_modify);
|
|
||||||
|
|
||||||
if is_potion {
|
if is_potion {
|
||||||
if player.gamemode.load() != GameMode::Creative {
|
if player.gamemode.load() != GameMode::Creative {
|
||||||
@ -2820,11 +2812,9 @@ impl EntityBase for LivingEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
living.clear_active_hand();
|
self.clear_active_hand();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.hurt_cooldown.load(Relaxed) > 0 {
|
if self.hurt_cooldown.load(Relaxed) > 0 {
|
||||||
self.hurt_cooldown.fetch_sub(1, Relaxed);
|
self.hurt_cooldown.fetch_sub(1, Relaxed);
|
||||||
@ -2873,7 +2863,7 @@ impl EntityBase for LivingEntity {
|
|||||||
impl LivingEntity {
|
impl LivingEntity {
|
||||||
/// Applies data-driven `apply_effects` consume effects after an item completes use.
|
/// Applies data-driven `apply_effects` consume effects after an item completes use.
|
||||||
/// Vanilla: `Consumable.onConsume` invokes every configured effect server-side.
|
/// 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 {
|
let Some(consumable) = item.get_data_component::<ConsumableImpl>() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -2927,8 +2917,7 @@ impl LivingEntity {
|
|||||||
if let Some(vehicle) = vehicle {
|
if let Some(vehicle) = vehicle {
|
||||||
vehicle
|
vehicle
|
||||||
.get_entity()
|
.get_entity()
|
||||||
.remove_passenger_before_teleport(caller.get_entity().entity_id)
|
.remove_passenger_sync(caller.get_entity().entity_id);
|
||||||
.await;
|
|
||||||
if caller.get_entity().has_vehicle() {
|
if caller.get_entity().has_vehicle() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -2940,10 +2929,7 @@ impl LivingEntity {
|
|||||||
};
|
};
|
||||||
let (yaw, pitch) = (self.entity.yaw.load(), self.entity.pitch.load());
|
let (yaw, pitch) = (self.entity.yaw.load(), self.entity.pitch.load());
|
||||||
let world = self.entity.world.load_full();
|
let world = self.entity.world.load_full();
|
||||||
caller
|
caller.teleport(pos, Some(yaw), Some(pitch), world.clone());
|
||||||
.clone()
|
|
||||||
.teleport(pos, Some(yaw), Some(pitch), world.clone())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let destination = self.entity.pos.load();
|
let destination = self.entity.pos.load();
|
||||||
if destination != center {
|
if destination != center {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::sync::{Arc, Mutex, atomic::Ordering};
|
use std::sync::{Arc, Mutex, atomic::Ordering};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
entity::{Entity, EntityBase, EntityBaseFuture, living::LivingEntity},
|
entity::{Entity, EntityBase, living::LivingEntity},
|
||||||
net::{bedrock::BedrockClient, java::JavaClient},
|
net::{bedrock::BedrockClient, java::JavaClient},
|
||||||
server::Server,
|
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) {}
|
fn init_data_tracker(&self) {}
|
||||||
|
|
||||||
@ -88,14 +88,7 @@ impl EntityBase for MarkerEntity {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_java_spawn_packet<'a>(&'a self, _client: &'a JavaClient) -> EntityBaseFuture<'a, ()> {
|
fn send_java_spawn_packet(&self, _client: &JavaClient) {}
|
||||||
Box::pin(async move {})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_bedrock_spawn_packet<'a>(
|
fn send_bedrock_spawn_packet(&self, _client: &BedrockClient) {}
|
||||||
&'a self,
|
|
||||||
_client: &'a BedrockClient,
|
|
||||||
) -> EntityBaseFuture<'a, ()> {
|
|
||||||
Box::pin(async move {})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -232,7 +232,7 @@ impl Mob for BatEntity {
|
|||||||
&self.mob_entity
|
&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 entity = &self.mob_entity.living_entity.entity;
|
||||||
let block_pos = entity.block_pos.load();
|
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);
|
let above_pos = BlockPos::new(block_pos.0.x, block_pos.0.y + 1, block_pos.0.z);
|
||||||
|
|||||||
@ -421,7 +421,7 @@ impl Mob for CreakingEntity {
|
|||||||
&self.mob_entity
|
&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 entity = &self.mob_entity.living_entity.entity;
|
||||||
if !entity.is_alive() {
|
if !entity.is_alive() {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -174,7 +174,7 @@ impl Mob for CreeperEntity {
|
|||||||
.on_lightning_strike(caller, lightning);
|
.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;
|
let entity = &self.mob_entity.living_entity.entity;
|
||||||
if !entity.is_alive() {
|
if !entity.is_alive() {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -433,7 +433,7 @@ impl Mob for EndermanEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: sunlight avoidance, carried block drop on death, angerable system, ambient sound override
|
// 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;
|
let entity = &self.mob_entity.living_entity.entity;
|
||||||
if !entity.is_alive() {
|
if !entity.is_alive() {
|
||||||
return;
|
return;
|
||||||
@ -443,7 +443,7 @@ impl Mob for EndermanEntity {
|
|||||||
let raining_at_feet = world.is_raining_at(&entity.block_pos.load());
|
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());
|
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 {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -177,7 +177,7 @@ impl Mob for EvokerEntity {
|
|||||||
&self.mob_entity
|
&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);
|
let ticks = self.spell_casting_tick_count.load(Ordering::Relaxed);
|
||||||
if ticks > 0 {
|
if ticks > 0 {
|
||||||
self.spell_casting_tick_count
|
self.spell_casting_tick_count
|
||||||
|
|||||||
@ -21,7 +21,7 @@ impl Mob for MagmaCubeEntity {
|
|||||||
self.slime.get_mob_entity()
|
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);
|
self.slime.mob_tick(caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -634,7 +634,7 @@ pub trait Mob: EntityBase + Send + Sync {
|
|||||||
fn set_saddled(&self, _saddled: bool) {}
|
fn set_saddled(&self, _saddled: bool) {}
|
||||||
|
|
||||||
/// Per-mob tick hook called each tick before AI runs. Override for mob-specific logic.
|
/// 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) {}
|
fn post_tick(&self) {}
|
||||||
|
|
||||||
@ -1008,7 +1008,7 @@ impl<T: Mob + Send + 'static> EntityBase for T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[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();
|
let mob_entity = self.get_mob_entity();
|
||||||
mob_entity.living_entity.entity.tick_leash();
|
mob_entity.living_entity.entity.tick_leash();
|
||||||
mob_entity.tick_sun_burn();
|
mob_entity.tick_sun_burn();
|
||||||
|
|||||||
@ -656,7 +656,7 @@ impl Mob for PiglinEntity {
|
|||||||
self.mob_entity.mob_interact(player, item_stack)
|
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;
|
let entity = &self.mob_entity.living_entity.entity;
|
||||||
if !entity.is_alive() {
|
if !entity.is_alive() {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -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;
|
let entity = &self.mob_entity.living_entity.entity;
|
||||||
if !entity.is_alive() {
|
if !entity.is_alive() {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -366,7 +366,7 @@ impl Mob for ShulkerEntity {
|
|||||||
0.0
|
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;
|
let entity = &self.mob_entity.living_entity.entity;
|
||||||
|
|
||||||
if !entity.is_alive() {
|
if !entity.is_alive() {
|
||||||
|
|||||||
@ -282,7 +282,7 @@ impl Mob for SlimeEntity {
|
|||||||
&self.entity
|
&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.o_squish.store(self.squish.load());
|
||||||
self.squish
|
self.squish
|
||||||
.store(self.squish.load() + (self.target_squish.load() - self.squish.load()) * 0.5);
|
.store(self.squish.load() + (self.target_squish.load() - self.squish.load()) * 0.5);
|
||||||
|
|||||||
@ -227,7 +227,7 @@ impl Mob for WitchEntity {
|
|||||||
amount
|
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 entity = &self.mob_entity.living_entity.entity;
|
||||||
let living = &self.mob_entity.living_entity;
|
let living = &self.mob_entity.living_entity;
|
||||||
let world = entity.world.load();
|
let world = entity.world.load();
|
||||||
|
|||||||
@ -21,7 +21,6 @@ use pumpkin_data::dimension::Dimension;
|
|||||||
use pumpkin_data::entity::EntityStatus;
|
use pumpkin_data::entity::EntityStatus;
|
||||||
use pumpkin_data::fluid::Fluid;
|
use pumpkin_data::fluid::Fluid;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
use pumpkin_data::packet::CURRENT_MC_VERSION;
|
|
||||||
use pumpkin_data::tag::{self, Taggable};
|
use pumpkin_data::tag::{self, Taggable};
|
||||||
use pumpkin_data::tracked_data;
|
use pumpkin_data::tracked_data;
|
||||||
use pumpkin_data::{Block, BlockDirection};
|
use pumpkin_data::{Block, BlockDirection};
|
||||||
@ -71,7 +70,6 @@ use pumpkin_util::text::TextComponent;
|
|||||||
use pumpkin_util::text::hover::HoverEvent;
|
use pumpkin_util::text::hover::HoverEvent;
|
||||||
use pumpkin_util::version::JavaMinecraftVersion;
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{
|
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 {
|
pub trait EntityBase: Send + Sync + std::any::Any {
|
||||||
fn write_nbt(&self, nbt: &mut NbtCompound) {
|
fn write_nbt(&self, nbt: &mut NbtCompound) {
|
||||||
self.get_entity().write_nbt(nbt);
|
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.
|
/// 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.
|
/// 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() {
|
if let Some(living) = self.get_living_entity() {
|
||||||
living.tick(caller, server);
|
living.tick(caller, server);
|
||||||
} else {
|
} else {
|
||||||
@ -215,20 +209,14 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
}
|
}
|
||||||
fn set_variant_name(&self, _name: &str) {}
|
fn set_variant_name(&self, _name: &str) {}
|
||||||
|
|
||||||
// This method takes ownership of Arc<Self>, so the lifetime bounds are different.
|
|
||||||
fn teleport(
|
fn teleport(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
position: Vector3<f64>,
|
position: Vector3<f64>,
|
||||||
yaw: Option<f32>,
|
yaw: Option<f32>,
|
||||||
pitch: Option<f32>,
|
pitch: Option<f32>,
|
||||||
world: Arc<World>,
|
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 {
|
fn is_pushed_by_fluids(&self) -> bool {
|
||||||
@ -298,11 +286,7 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_bedrock_spawn_packet<'a>(
|
fn send_bedrock_spawn_packet(&self, client: &BedrockClient) {
|
||||||
&'a self,
|
|
||||||
client: &'a BedrockClient,
|
|
||||||
) -> EntityBaseFuture<'a, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
let runtime_id = entity.entity_id as u64;
|
let runtime_id = entity.entity_id as u64;
|
||||||
let identifier = self
|
let identifier = self
|
||||||
@ -333,13 +317,11 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
actor_links: Vec::new(),
|
actor_links: Vec::new(),
|
||||||
};
|
};
|
||||||
if let Ok(data) = client.serialize_packet(&packet) {
|
if let Ok(data) = client.serialize_packet(&packet) {
|
||||||
client.send_game_packet(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_java_spawn_packet<'a>(&'a self, client: &'a JavaClient) -> EntityBaseFuture<'a, ()> {
|
fn send_java_spawn_packet(&self, client: &JavaClient) {
|
||||||
Box::pin(async move {
|
|
||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
let version = client.version.load();
|
let version = client.version.load();
|
||||||
let is_mob = entity.entity_type.mob || self.get_mob().is_some();
|
let is_mob = entity.entity_type.mob || self.get_mob().is_some();
|
||||||
@ -349,31 +331,30 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
.and_then(|mob| mob.mob_java_spawn_metadata(version));
|
.and_then(|mob| mob.mob_java_spawn_metadata(version));
|
||||||
let spawn_packet = entity.create_spawn_living_packet(metadata.clone());
|
let spawn_packet = entity.create_spawn_living_packet(metadata.clone());
|
||||||
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
||||||
client.enqueue_packet(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
if version >= JavaMinecraftVersion::V_1_15
|
if version >= JavaMinecraftVersion::V_1_15
|
||||||
&& let Some(meta) = metadata
|
&& let Some(meta) = metadata
|
||||||
{
|
{
|
||||||
let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), meta);
|
let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), meta);
|
||||||
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
||||||
client.enqueue_packet(meta_data).await;
|
client.try_enqueue_packet(meta_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let spawn_packet = entity.create_spawn_packet();
|
let spawn_packet = entity.create_spawn_packet();
|
||||||
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
||||||
client.enqueue_packet(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
if let Some(mob) = self.get_mob()
|
if let Some(mob) = self.get_mob()
|
||||||
&& let Some(metadata) = mob.mob_java_spawn_metadata(version)
|
&& let Some(metadata) = mob.mob_java_spawn_metadata(version)
|
||||||
{
|
{
|
||||||
let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), metadata);
|
let meta_packet = CSetEntityMetadata::new(entity.entity_id.into(), metadata);
|
||||||
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
||||||
client.enqueue_packet(meta_data).await;
|
client.try_enqueue_packet(meta_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn damage_with_context(
|
fn damage_with_context(
|
||||||
@ -442,12 +423,12 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
self.get_entity().has_passengers()
|
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()
|
self.get_entity()
|
||||||
.has_passenger(other.get_entity().entity_id)
|
.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);
|
self.get_entity().move_entity(caller, motion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +436,7 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&self, entity: &Arc<dyn EntityBase>) {
|
fn push(&self, entity: &dyn EntityBase) {
|
||||||
let self_entity = self.get_entity();
|
let self_entity = self.get_entity();
|
||||||
let other_entity = entity.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)]
|
#[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 picked_up = false;
|
||||||
let mut pushed = false;
|
let mut pushed = false;
|
||||||
let self_entity = self.get_entity();
|
let self_entity = self.get_entity();
|
||||||
@ -555,10 +536,9 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
&& !other.is_passenger()
|
&& !other.is_passenger()
|
||||||
&& other.is_pushable()
|
&& other.is_pushable()
|
||||||
&& other.get_entity().riding_cooldown.load(Relaxed) == 0
|
&& other.get_entity().riding_cooldown.load(Relaxed) == 0
|
||||||
|
&& let Some(self_arc) = world.get_entity_by_id(self_entity.entity_id)
|
||||||
{
|
{
|
||||||
dyn_self
|
self_entity.add_passenger(self_arc, other.clone());
|
||||||
.get_entity()
|
|
||||||
.add_passenger(dyn_self.clone(), other.clone());
|
|
||||||
picked_up = true;
|
picked_up = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -582,14 +562,14 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
|| !other.get_entity().has_vehicle())
|
|| !other.get_entity().has_vehicle())
|
||||||
&& other.is_pushable()
|
&& other.is_pushable()
|
||||||
{
|
{
|
||||||
dyn_self.push(&other);
|
dyn_self.push(other.as_ref());
|
||||||
pushed = true;
|
pushed = true;
|
||||||
}
|
}
|
||||||
} else if !self.has_passenger(&other)
|
} else if !self.has_passenger(other.as_ref())
|
||||||
&& other.is_pushable()
|
&& other.is_pushable()
|
||||||
&& is_other_minecart
|
&& is_other_minecart
|
||||||
{
|
{
|
||||||
dyn_self.push(&other);
|
dyn_self.push(other.as_ref());
|
||||||
pushed = true;
|
pushed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,8 +578,7 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
let players = world.get_players_at_box(&push_bb);
|
let players = world.get_players_at_box(&push_bb);
|
||||||
for player in players {
|
for player in players {
|
||||||
if player.get_entity().entity_id != self_entity.entity_id && is_rideable_minecart {
|
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.as_ref());
|
||||||
dyn_self.push(&player_base);
|
|
||||||
pushed = true;
|
pushed = true;
|
||||||
// Non-rideable minecarts (hoppers, chests) do not push players in vanilla.
|
// 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);
|
let other_entities = world.get_entities_at_box(&entity_bb);
|
||||||
for other in other_entities {
|
for other in other_entities {
|
||||||
if other.get_entity().entity_id != self_entity.entity_id {
|
if other.get_entity().entity_id != self_entity.entity_id {
|
||||||
dyn_self.push(&other);
|
dyn_self.push(other.as_ref());
|
||||||
pushed = true;
|
pushed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -616,8 +595,7 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
let players = world.get_players_at_box(&entity_bb);
|
let players = world.get_players_at_box(&entity_bb);
|
||||||
for player in players {
|
for player in players {
|
||||||
if player.get_entity().entity_id != self_entity.entity_id {
|
if player.get_entity().entity_id != self_entity.entity_id {
|
||||||
let player_base: Arc<dyn EntityBase> = player.clone();
|
dyn_self.push(player.as_ref());
|
||||||
dyn_self.push(&player_base);
|
|
||||||
pushed = true;
|
pushed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -652,7 +630,7 @@ pub trait EntityBase: Send + Sync + std::any::Any {
|
|||||||
|
|
||||||
fn cast_any(&self) -> &dyn 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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1524,14 +1502,14 @@ impl Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[expect(dead_code)]
|
#[expect(dead_code)]
|
||||||
fn tick_block_underneath(_caller: &Arc<dyn EntityBase>) {
|
fn tick_block_underneath(_caller: &dyn EntityBase) {
|
||||||
// let world = self.world.read();
|
// let world = self.world.read();
|
||||||
|
|
||||||
// let (pos, block, state) = self.get_block_with_y_offset(0.2);
|
// let (pos, block, state) = self.get_block_with_y_offset(0.2);
|
||||||
|
|
||||||
// world
|
// world
|
||||||
// .block_registry
|
// .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
|
// 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() {
|
if !self.is_affected_by_blocks() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1630,10 +1608,7 @@ impl Entity {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let collision_shape = if block == &Block::POWDER_SNOW {
|
let collision_shape = if block == &Block::POWDER_SNOW {
|
||||||
crate::block::blocks::powder_snow::inside_collision_shape_for_entity(
|
crate::block::blocks::powder_snow::inside_collision_shape_for_entity(caller, &pos)
|
||||||
caller.as_ref(),
|
|
||||||
&pos,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
world
|
world
|
||||||
.block_registry
|
.block_registry
|
||||||
@ -1648,7 +1623,7 @@ impl Entity {
|
|||||||
world.block_registry.on_entity_collision(
|
world.block_registry.on_entity_collision(
|
||||||
block,
|
block,
|
||||||
&world,
|
&world,
|
||||||
caller.as_ref(),
|
caller,
|
||||||
&pos,
|
&pos,
|
||||||
state,
|
state,
|
||||||
&server_arc,
|
&server_arc,
|
||||||
@ -1940,7 +1915,7 @@ impl Entity {
|
|||||||
|
|
||||||
// updateWaterState() in yarn
|
// 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 is_pushed = caller.is_pushed_by_fluids();
|
||||||
let mut fluids = BTreeMap::new();
|
let mut fluids = BTreeMap::new();
|
||||||
|
|
||||||
@ -2018,7 +1993,7 @@ impl Entity {
|
|||||||
for (_, fluid) in fluids {
|
for (_, fluid) in fluids {
|
||||||
world
|
world
|
||||||
.block_registry
|
.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 {
|
let lava_speed = if world.dimension == Dimension::THE_NETHER {
|
||||||
@ -2220,7 +2195,7 @@ impl Entity {
|
|||||||
// Move by a delta, adjust for collisions, and send
|
// Move by a delta, adjust for collisions, and send
|
||||||
|
|
||||||
// Does not send movement. That must be done separately
|
// 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() {
|
if caller.get_player().is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2245,7 +2220,7 @@ impl Entity {
|
|||||||
self.velocity.store(Vector3::default());
|
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);
|
self.move_pos(final_move);
|
||||||
|
|
||||||
@ -2255,7 +2230,7 @@ impl Entity {
|
|||||||
|
|
||||||
if let Some(living) = caller.get_living_entity() {
|
if let Some(living) = caller.get_living_entity() {
|
||||||
let on_ground = self.on_ground.load(Ordering::SeqCst);
|
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 {
|
if motion.y != final_move.y {
|
||||||
@ -2263,7 +2238,7 @@ impl Entity {
|
|||||||
let block = self.get_block_with_y_offset(0.2).1;
|
let block = self.get_block_with_y_offset(0.2).1;
|
||||||
world
|
world
|
||||||
.block_registry
|
.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);
|
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 {
|
if self.portal_cooldown.load(Ordering::Relaxed) > 0 {
|
||||||
self.portal_cooldown.fetch_sub(1, Ordering::Relaxed);
|
self.portal_cooldown.fetch_sub(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
@ -2331,15 +2306,10 @@ impl Entity {
|
|||||||
};
|
};
|
||||||
let mut should_remove = false;
|
let mut should_remove = false;
|
||||||
if let Some(portal_processor) = manager_guard.as_mut() {
|
if let Some(portal_processor) = manager_guard.as_mut() {
|
||||||
if portal_processor.process_portal_teleportation(
|
if portal_processor.process_portal_teleportation(&self.world.load(), caller, true) {
|
||||||
&self.world.load(),
|
|
||||||
caller.as_ref(),
|
|
||||||
true,
|
|
||||||
) {
|
|
||||||
self.portal_cooldown
|
self.portal_cooldown
|
||||||
.store(self.default_portal_cooldown(), Ordering::Relaxed);
|
.store(self.default_portal_cooldown(), Ordering::Relaxed);
|
||||||
|
|
||||||
let caller_clone = caller.clone();
|
|
||||||
let world_clone = self.world.load_full();
|
let world_clone = self.world.load_full();
|
||||||
let portal_type = portal_processor.portal_type;
|
let portal_type = portal_processor.portal_type;
|
||||||
let dest_world_opt = portal_processor.destination_world.clone();
|
let dest_world_opt = portal_processor.destination_world.clone();
|
||||||
@ -2348,21 +2318,17 @@ impl Entity {
|
|||||||
let entity_id = self.entity_id;
|
let entity_id = self.entity_id;
|
||||||
let yaw = self.yaw.load();
|
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 || {
|
rayon::spawn(move || {
|
||||||
let dest = portal_type.get_portal_destination(
|
let Some(entity_arc) = world_clone.get_entity_by_id(entity_id) else {
|
||||||
&world_for_dest,
|
return;
|
||||||
|
};
|
||||||
|
let transition = portal_type.get_portal_destination(
|
||||||
|
&world_clone,
|
||||||
dest_world_opt,
|
dest_world_opt,
|
||||||
&caller_for_dest,
|
entity_arc.as_ref(),
|
||||||
entry_pos,
|
entry_pos,
|
||||||
src_portal.as_ref(),
|
src_portal.as_ref(),
|
||||||
);
|
);
|
||||||
let _ = tx.send(dest);
|
|
||||||
});
|
|
||||||
let transition = rx.await.ok().flatten();
|
|
||||||
|
|
||||||
if let Some(transition) = transition {
|
if let Some(transition) = transition {
|
||||||
let dest_world = transition.new_world.clone();
|
let dest_world = transition.new_world.clone();
|
||||||
@ -2371,21 +2337,16 @@ impl Entity {
|
|||||||
let teleport_pos = transition.position;
|
let teleport_pos = transition.position;
|
||||||
|
|
||||||
// Teleport the main entity
|
// Teleport the main entity
|
||||||
caller_clone
|
entity_arc.teleport(teleport_pos, yaw_val, pitch, dest_world.clone());
|
||||||
.teleport(teleport_pos, yaw_val, pitch, dest_world.clone())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Teleport all passengers recursively along with the vehicle
|
// 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);
|
let yaw_delta = yaw_val.map(|y| y - yaw);
|
||||||
Self::teleport_passengers_recursive(
|
Self::teleport_passengers_recursive(
|
||||||
entity.get_entity(),
|
entity_arc.get_entity(),
|
||||||
teleport_pos,
|
teleport_pos,
|
||||||
yaw_delta,
|
yaw_delta,
|
||||||
&dest_world,
|
&dest_world,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if portal_processor.portal_time == 0 {
|
} else if portal_processor.portal_time == 0 {
|
||||||
@ -2398,13 +2359,12 @@ impl Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively teleports all passengers (and their passengers) to the destination
|
/// Recursively teleports all passengers (and their passengers) to the destination
|
||||||
fn teleport_passengers_recursive<'a>(
|
fn teleport_passengers_recursive(
|
||||||
entity: &'a Self,
|
entity: &Self,
|
||||||
position: Vector3<f64>,
|
position: Vector3<f64>,
|
||||||
yaw_delta: Option<f32>,
|
yaw_delta: Option<f32>,
|
||||||
dest_world: &'a Arc<World>,
|
dest_world: &Arc<World>,
|
||||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
|
) {
|
||||||
Box::pin(async move {
|
|
||||||
let passengers = entity
|
let passengers = entity
|
||||||
.passengers
|
.passengers
|
||||||
.lock()
|
.lock()
|
||||||
@ -2425,23 +2385,14 @@ impl Entity {
|
|||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
passenger
|
passenger.teleport(position, passenger_yaw, None, dest_world.clone());
|
||||||
.teleport(position, passenger_yaw, None, dest_world.clone())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Recursively teleport nested passengers
|
// Recursively teleport nested passengers
|
||||||
for nested in nested_passengers {
|
for nested in nested_passengers {
|
||||||
let nested_entity = nested.get_entity();
|
let nested_entity = nested.get_entity();
|
||||||
Self::teleport_passengers_recursive(
|
Self::teleport_passengers_recursive(nested_entity, position, yaw_delta, dest_world);
|
||||||
nested_entity,
|
|
||||||
position,
|
|
||||||
yaw_delta,
|
|
||||||
dest_world,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_use_portal(&self, _portal_delay: u32, portal_world: Arc<World>, pos: BlockPos) {
|
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());
|
World::collect_java_recipients_by_version(java_recipients.into_iter());
|
||||||
|
|
||||||
for (version, recipients) in recipients_by_version {
|
for (version, recipients) in recipients_by_version {
|
||||||
if version < CURRENT_MC_VERSION {
|
if version < JavaMinecraftVersion::V_1_21 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
@ -4060,7 +4011,7 @@ impl Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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.
|
// Recomputed during movement/block-collision handling in the same tick.
|
||||||
let was_in_powder_snow = self.is_in_powder_snow.load(Ordering::Relaxed);
|
let was_in_powder_snow = self.is_in_powder_snow.load(Ordering::Relaxed);
|
||||||
self.was_in_powder_snow
|
self.was_in_powder_snow
|
||||||
@ -4070,7 +4021,7 @@ impl EntityBase for Entity {
|
|||||||
self.update_last_pos();
|
self.update_last_pos();
|
||||||
self.tick_portal(caller);
|
self.tick_portal(caller);
|
||||||
self.update_fluid_state(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);
|
let fire_ticks = self.fire_ticks.load(Ordering::Relaxed);
|
||||||
|
|
||||||
// Check for fire immunity (or if the specific entity is)
|
// Check for fire immunity (or if the specific entity is)
|
||||||
@ -4083,7 +4034,7 @@ impl EntityBase for Entity {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if fire_ticks % 20 == 0 {
|
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);
|
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 {
|
fn get_entity(&self) -> &Entity {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.ageable_ai_step();
|
||||||
|
|
||||||
self.in_state_ticks.fetch_add(1, Ordering::Relaxed);
|
self.in_state_ticks.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|||||||
@ -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) {
|
if self.mob_entity.living_entity.dead.load(Relaxed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -278,7 +278,7 @@ impl Mob for FoxEntity {
|
|||||||
self.set_variant(FoxVariant::from_name(name));
|
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();
|
self.ageable_ai_step();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -172,7 +172,7 @@ impl Mob for FrogEntity {
|
|||||||
self.set_variant(FrogVariant::from_name(name));
|
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();
|
self.ageable_ai_step();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -153,7 +153,7 @@ impl Mob for HappyGhastEntity {
|
|||||||
&self.mob_entity
|
&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();
|
self.ageable_ai_step();
|
||||||
|
|
||||||
let leash_time = self.leash_holder_time.load(Ordering::Relaxed);
|
let leash_time = self.leash_holder_time.load(Ordering::Relaxed);
|
||||||
|
|||||||
@ -133,7 +133,7 @@ impl Mob for IronGolemEntity {
|
|||||||
&self.mob_entity
|
&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);
|
let attack_tick = self.attack_animation_tick.load(Ordering::Relaxed);
|
||||||
if attack_tick > 0 {
|
if attack_tick > 0 {
|
||||||
self.attack_animation_tick.fetch_sub(1, Ordering::Relaxed);
|
self.attack_animation_tick.fetch_sub(1, Ordering::Relaxed);
|
||||||
|
|||||||
@ -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 entity = &self.mob_entity.living_entity.entity;
|
||||||
|
|
||||||
let Ok(passengers) = entity.passengers.try_lock() else {
|
let Ok(passengers) = entity.passengers.try_lock() else {
|
||||||
|
|||||||
@ -357,7 +357,7 @@ impl Mob for SnifferEntity {
|
|||||||
&self.mob_entity
|
&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();
|
self.ageable_ai_step();
|
||||||
let state = self.get_state();
|
let state = self.get_state();
|
||||||
match state {
|
match state {
|
||||||
|
|||||||
@ -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();
|
self.villager_mob_tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -784,7 +784,7 @@ impl Mob for WanderingTraderEntity {
|
|||||||
true
|
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)
|
// Despawn delay handling (vanilla aiStep / maybeDespawn)
|
||||||
if !self.is_trading.load(Ordering::Relaxed) {
|
if !self.is_trading.load(Ordering::Relaxed) {
|
||||||
let delay = self.despawn_delay.load(Ordering::Relaxed);
|
let delay = self.despawn_delay.load(Ordering::Relaxed);
|
||||||
|
|||||||
@ -290,7 +290,6 @@ use crate::command::context::command_source::CommandSource;
|
|||||||
use crate::command::node::dispatcher::CommandDispatcher;
|
use crate::command::node::dispatcher::CommandDispatcher;
|
||||||
use crate::command::{CommandSender, client_suggestions};
|
use crate::command::{CommandSender, client_suggestions};
|
||||||
use crate::data::SaveJSONConfiguration;
|
use crate::data::SaveJSONConfiguration;
|
||||||
use crate::entity::TeleportFuture;
|
|
||||||
use crate::net::{ClientPlatform, GameProfile};
|
use crate::net::{ClientPlatform, GameProfile};
|
||||||
use crate::net::{DisconnectReason, PlayerConfig};
|
use crate::net::{DisconnectReason, PlayerConfig};
|
||||||
use crate::plugin::player::exp_change::PlayerExpChangeEvent;
|
use crate::plugin::player::exp_change::PlayerExpChangeEvent;
|
||||||
@ -918,11 +917,11 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines, clippy::items_after_statements)]
|
#[expect(clippy::too_many_lines, clippy::items_after_statements)]
|
||||||
pub async fn new(
|
pub fn new(
|
||||||
client: Arc<ClientPlatform>,
|
client: Arc<ClientPlatform>,
|
||||||
gameprofile: GameProfile,
|
gameprofile: GameProfile,
|
||||||
config: PlayerConfig,
|
config: PlayerConfig,
|
||||||
world: Arc<World>,
|
world: &Arc<World>,
|
||||||
gamemode: GameMode,
|
gamemode: GameMode,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let inventory_changed = Arc::new(AtomicBool::new(true));
|
let inventory_changed = Arc::new(AtomicBool::new(true));
|
||||||
@ -986,13 +985,9 @@ impl Player {
|
|||||||
let mut abilities = Abilities::default();
|
let mut abilities = Abilities::default();
|
||||||
abilities.set_for_gamemode(gamemode);
|
abilities.set_for_gamemode(gamemode);
|
||||||
|
|
||||||
let properties = gameprofile.properties.load().clone();
|
let properties = gameprofile.properties.load();
|
||||||
let mut bedrock_skin = tokio::task::spawn_blocking(move || {
|
let mut bedrock_skin = Self::fetch_skin(&properties)
|
||||||
Self::fetch_skin(&properties)
|
.unwrap_or_else(pumpkin_protocol::bedrock::client::Skin::steve);
|
||||||
.unwrap_or_else(pumpkin_protocol::bedrock::client::Skin::steve)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|_| pumpkin_protocol::bedrock::client::Skin::steve());
|
|
||||||
|
|
||||||
// Standard_Custom is a shared placeholder. Give fallback skins a stable,
|
// Standard_Custom is a shared placeholder. Give fallback skins a stable,
|
||||||
// per-player identity so Bedrock never sees duplicate skin IDs.
|
// 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
|
// Vanilla `Player#attack` ends the successful-hit branch with
|
||||||
// `causeFoodExhaustion(0.1F)`. Only landed hits exhaust; the miss/no-damage
|
// `causeFoodExhaustion(0.1F)`. Only landed hits exhaust; the miss/no-damage
|
||||||
// case returned early above.
|
// case returned early above.
|
||||||
self.add_exhaustion(0.1).await;
|
self.add_exhaustion(0.1);
|
||||||
|
|
||||||
if config.swing {}
|
if config.swing {}
|
||||||
}
|
}
|
||||||
@ -2456,7 +2451,7 @@ impl Player {
|
|||||||
if player_pos != target_pos {
|
if player_pos != target_pos {
|
||||||
self.living_entity.entity.set_pos(target_pos);
|
self.living_entity.entity.set_pos(target_pos);
|
||||||
if let Some(p) = self.world().get_player_by_uuid(self.gameprofile.id) {
|
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;
|
crate::world::chunker::update_position(&p).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2530,7 +2525,7 @@ impl Player {
|
|||||||
&& !chunk_of_chunks.is_empty()
|
&& !chunk_of_chunks.is_empty()
|
||||||
{
|
{
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
server.runtime.spawn(async move {
|
server.spawn_task(async move {
|
||||||
client.send_chunks(&chunk_of_chunks).await;
|
client.send_chunks(&chunk_of_chunks).await;
|
||||||
});
|
});
|
||||||
if let ClientPlatform::Bedrock(bedrock_client) = self.client.as_ref()
|
if let ClientPlatform::Bedrock(bedrock_client) = self.client.as_ref()
|
||||||
@ -2559,13 +2554,11 @@ impl Player {
|
|||||||
self.sleeping_since.store(Some(sleeping_since + 1));
|
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)
|
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 world_clone = p.world();
|
||||||
let server_clone = world_clone.server.upgrade();
|
let server_clone = world_clone.server.upgrade();
|
||||||
server.runtime.spawn(async move {
|
|
||||||
let pos = *p
|
let pos = *p
|
||||||
.mining_pos
|
.mining_pos
|
||||||
.lock()
|
.lock()
|
||||||
@ -2593,7 +2586,7 @@ impl Player {
|
|||||||
pumpkin_world::world::BlockFlags::SKIP_DROPS
|
pumpkin_world::world::BlockFlags::SKIP_DROPS
|
||||||
| pumpkin_world::world::BlockFlags::NOTIFY_NEIGHBORS
|
| pumpkin_world::world::BlockFlags::NOTIFY_NEIGHBORS
|
||||||
};
|
};
|
||||||
if world.break_block(&pos, Some(p.clone()), flags).is_some() {
|
if world.break_block(&pos, Some(&p), flags).is_some() {
|
||||||
if let Some(server) = server_clone {
|
if let Some(server) = server_clone {
|
||||||
server
|
server
|
||||||
.block_registry
|
.block_registry
|
||||||
@ -2601,29 +2594,24 @@ impl Player {
|
|||||||
}
|
}
|
||||||
p.apply_tool_damage_for_block_break(state);
|
p.apply_tool_damage_for_block_break(state);
|
||||||
if can_harvest {
|
if can_harvest {
|
||||||
p.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
|
p.add_exhaustion(MINE_BLOCK_EXHAUSTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
self.last_attacked_ticks.fetch_add(1, Ordering::Relaxed);
|
self.last_attacked_ticks.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
if let Some(ref p_arc) = player_arc {
|
self.living_entity.tick(self, server);
|
||||||
let caller: Arc<dyn EntityBase> = p_arc.clone();
|
|
||||||
self.living_entity.tick(&caller, server);
|
self.breath_manager.tick(self);
|
||||||
self.breath_manager.tick(p_arc);
|
self.hunger_manager.tick(self);
|
||||||
self.hunger_manager.tick(p_arc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vanilla updates pose in PlayerEntity#tick after super.tick().
|
// Vanilla updates pose in PlayerEntity#tick after super.tick().
|
||||||
self.update_player_pose();
|
self.update_player_pose();
|
||||||
self.check_inventory_advancements();
|
self.check_inventory_advancements();
|
||||||
if let Some(ref p_arc) = player_arc
|
if let Ok(mut adv) = self.advancements.try_lock() {
|
||||||
&& let Ok(mut adv) = self.advancements.try_lock()
|
adv.flush_dirty(self, true);
|
||||||
{
|
|
||||||
adv.flush_dirty(p_arc, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// experience handling
|
// experience handling
|
||||||
@ -2649,10 +2637,8 @@ impl Player {
|
|||||||
let idle_timeout_minutes = server.player_idle_timeout.load(Ordering::Relaxed);
|
let idle_timeout_minutes = server.player_idle_timeout.load(Ordering::Relaxed);
|
||||||
if idle_timeout_minutes > 0 {
|
if idle_timeout_minutes > 0 {
|
||||||
let idle_duration = now.duration_since(self.last_action_time.load());
|
let idle_duration = now.duration_since(self.last_action_time.load());
|
||||||
if idle_duration >= Duration::from_secs(idle_timeout_minutes as u64 * 60)
|
if idle_duration >= Duration::from_secs(idle_timeout_minutes as u64 * 60) {
|
||||||
&& let Some(ref p_arc) = player_arc
|
self.kick(
|
||||||
{
|
|
||||||
p_arc.kick(
|
|
||||||
DisconnectReason::KickedForIdle,
|
DisconnectReason::KickedForIdle,
|
||||||
&TextComponent::translate_cross(
|
&TextComponent::translate_cross(
|
||||||
translation::java::MULTIPLAYER_DISCONNECT_IDLING,
|
translation::java::MULTIPLAYER_DISCONNECT_IDLING,
|
||||||
@ -2714,27 +2700,27 @@ impl Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn jump(&self) {
|
pub fn jump(&self) {
|
||||||
self.stats
|
self.stats
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
.increment_custom(statistics::CustomStatistic::Jump, 1);
|
.increment_custom(statistics::CustomStatistic::Jump, 1);
|
||||||
if self.living_entity.entity.is_sprinting() {
|
if self.living_entity.entity.is_sprinting() {
|
||||||
self.add_exhaustion(0.2).await;
|
self.add_exhaustion(0.2);
|
||||||
} else {
|
} 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...
|
// TODO: Swimming, gliding...
|
||||||
if self.living_entity.entity.on_ground.load(Ordering::Relaxed) {
|
if self.living_entity.entity.on_ground.load(Ordering::Relaxed) {
|
||||||
let delta = (delta_pos.horizontal_length() * 100.0).round() as f32;
|
let delta = (delta_pos.horizontal_length() * 100.0).round() as f32;
|
||||||
if delta > 0.0 {
|
if delta > 0.0 {
|
||||||
if self.living_entity.entity.is_sprinting() {
|
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 {
|
} 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();
|
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);
|
player.get_entity().last_pos.store(position);
|
||||||
|
|
||||||
self.send_abilities_update();
|
self.send_abilities_update();
|
||||||
@ -3658,27 +3644,27 @@ impl Player {
|
|||||||
/// `yaw` and `pitch` are in degrees.
|
/// `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.
|
/// 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.
|
/// 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 is the ultra special magic code used to create the teleport id
|
||||||
// This returns the old value
|
// This returns the old value
|
||||||
// This operation wraps around on overflow.
|
// This operation wraps around on overflow.
|
||||||
let Some(server) = self.world().server.upgrade() else {
|
let Some(server) = self.world().server.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
send_cancellable! {{
|
if let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id) {
|
||||||
server;
|
let mut event = PlayerTeleportEvent {
|
||||||
PlayerTeleportEvent {
|
player: player_arc,
|
||||||
player: self.clone(),
|
|
||||||
from: self.living_entity.entity.pos.load(),
|
from: self.living_entity.entity.pos.load(),
|
||||||
to: position,
|
to: position,
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
};
|
};
|
||||||
|
server.plugin_manager.fire_blocking(&server, &mut event);
|
||||||
|
if event.cancelled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
'after: {
|
let i = self.teleport_id_count.fetch_add(1, Ordering::Relaxed);
|
||||||
let position = event.to;
|
|
||||||
let i = self
|
|
||||||
.teleport_id_count
|
|
||||||
.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let teleport_id = i + 1;
|
let teleport_id = i + 1;
|
||||||
self.living_entity.entity.set_pos(position);
|
self.living_entity.entity.set_pos(position);
|
||||||
let entity = &self.living_entity.entity;
|
let entity = &self.living_entity.entity;
|
||||||
@ -3700,7 +3686,7 @@ impl Player {
|
|||||||
Vec::new(),
|
Vec::new(),
|
||||||
);
|
);
|
||||||
if let Ok(data) = client.serialize_packet(&packet) {
|
if let Ok(data) = client.serialize_packet(&packet) {
|
||||||
client.send_packet_now(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientPlatform::Bedrock(client) => {
|
ClientPlatform::Bedrock(client) => {
|
||||||
@ -3719,18 +3705,14 @@ impl Player {
|
|||||||
VarULong(0),
|
VarULong(0),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
VarULong(
|
VarULong(self.tick_counter.load(Ordering::Relaxed).max(0) as u64),
|
||||||
self.tick_counter.load(Ordering::Relaxed).max(0) as u64,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if let Ok(data) = client.serialize_packet(&packet) {
|
if let Ok(data) = client.serialize_packet(&packet) {
|
||||||
client.send_game_packet(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_interaction_range(&self) -> f64 {
|
pub fn block_interaction_range(&self) -> f64 {
|
||||||
if self.gamemode.load() == GameMode::Creative {
|
if self.gamemode.load() == GameMode::Creative {
|
||||||
@ -3818,7 +3800,7 @@ impl Player {
|
|||||||
health > 0.0 && health < max_health
|
health > 0.0 && health < max_health
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_exhaustion(&self, exhaustion: f32) {
|
pub fn add_exhaustion(&self, exhaustion: f32) {
|
||||||
if self
|
if self
|
||||||
.abilities
|
.abilities
|
||||||
.lock()
|
.lock()
|
||||||
@ -3835,8 +3817,7 @@ impl Player {
|
|||||||
if let Some(server) = self.world().server.upgrade() {
|
if let Some(server) = self.world().server.upgrade() {
|
||||||
server
|
server
|
||||||
.plugin_manager
|
.plugin_manager
|
||||||
.fire(&server, &mut exhaustion_event)
|
.fire_blocking(&server, &mut exhaustion_event);
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
if exhaustion_event.cancelled {
|
if exhaustion_event.cancelled {
|
||||||
return;
|
return;
|
||||||
@ -6188,37 +6169,20 @@ impl EntityBase for Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn teleport(
|
fn teleport(
|
||||||
self: Arc<Self>,
|
&self,
|
||||||
position: Vector3<f64>,
|
position: Vector3<f64>,
|
||||||
yaw: Option<f32>,
|
yaw: Option<f32>,
|
||||||
pitch: Option<f32>,
|
pitch: Option<f32>,
|
||||||
world: Arc<World>,
|
world: Arc<World>,
|
||||||
) -> TeleportFuture {
|
) {
|
||||||
Box::pin(async move {
|
|
||||||
if Arc::ptr_eq(&world, &self.world()) {
|
if Arc::ptr_eq(&world, &self.world()) {
|
||||||
// Same world
|
// Same world
|
||||||
let yaw = yaw.unwrap_or(self.living_entity.entity.yaw.load());
|
let yaw = yaw.unwrap_or_else(|| self.living_entity.entity.yaw.load());
|
||||||
let pitch = pitch.unwrap_or(self.living_entity.entity.pitch.load());
|
let pitch = pitch.unwrap_or_else(|| self.living_entity.entity.pitch.load());
|
||||||
let Some(server) = self.world().server.upgrade() else {
|
self.request_teleport(position, yaw, pitch);
|
||||||
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();
|
let entity = self.get_entity();
|
||||||
self.request_teleport(position, yaw, pitch).await;
|
|
||||||
let chunk_pos = entity.chunk_pos.load();
|
let chunk_pos = entity.chunk_pos.load();
|
||||||
entity
|
entity.world.load().broadcast_to_chunk_except(
|
||||||
.world
|
|
||||||
.load()
|
|
||||||
.broadcast_to_chunk_except(
|
|
||||||
chunk_pos,
|
chunk_pos,
|
||||||
&[self.living_entity.entity.entity_uuid],
|
&[self.living_entity.entity.entity_uuid],
|
||||||
&CEntityPositionSync::new(
|
&CEntityPositionSync::new(
|
||||||
@ -6228,15 +6192,13 @@ impl EntityBase for Player {
|
|||||||
yaw,
|
yaw,
|
||||||
pitch,
|
pitch,
|
||||||
entity.on_ground.load(Ordering::SeqCst),
|
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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
} else {
|
|
||||||
self.teleport_world(world, position, yaw, pitch).await;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_entity(&self) -> &Entity {
|
fn get_entity(&self) -> &Entity {
|
||||||
|
|||||||
@ -19,11 +19,10 @@ use serde::ser::SerializeMap;
|
|||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde_json::to_string_pretty;
|
use serde_json::to_string_pretty;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fs::{create_dir_all, read, write};
|
use std::fs::read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tokio::task::spawn_blocking;
|
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -271,19 +270,17 @@ impl PlayerAdvancement {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let json = to_string_pretty(self).map_err(AdvancementDataError::Json)?;
|
let json = to_string_pretty(self).map_err(AdvancementDataError::Json)?;
|
||||||
let path = self.path.clone();
|
let Some(parent) = self.path.parent() else {
|
||||||
spawn_blocking(move || {
|
|
||||||
let Some(parent) = path.parent() else {
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
if let Err(e) = create_dir_all(parent) {
|
if let Err(e) = tokio::fs::create_dir_all(parent).await {
|
||||||
error!("Failed to create player advancement directory : {e}");
|
error!("Failed to create player advancement directory : {e}");
|
||||||
return Err(AdvancementDataError::Io(e));
|
return Err(AdvancementDataError::Io(e));
|
||||||
}
|
}
|
||||||
write(path, json).map_err(AdvancementDataError::Io)
|
tokio::fs::write(&self.path, json)
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap_or(Ok(()))
|
.map_err(AdvancementDataError::Io)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the player's advancement progress from disk.
|
/// Loads the player's advancement progress from disk.
|
||||||
@ -352,7 +349,7 @@ impl PlayerAdvancement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Flushes any pending advancement state down to the client.
|
/// 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() {
|
if self.is_first_packet || !self.roots_to_update.is_empty() {
|
||||||
let mut progress: HashMap<Identifier, &AdvancementProgress> = HashMap::new();
|
let mut progress: HashMap<Identifier, &AdvancementProgress> = HashMap::new();
|
||||||
let mut added: Vec<&Advancement> = Vec::new();
|
let mut added: Vec<&Advancement> = Vec::new();
|
||||||
@ -368,7 +365,6 @@ impl PlayerAdvancement {
|
|||||||
}
|
}
|
||||||
self.progress_changed.clear();
|
self.progress_changed.clear();
|
||||||
if !progress.is_empty() || !added.is_empty() || !removed.is_empty() {
|
if !progress.is_empty() || !added.is_empty() || !removed.is_empty() {
|
||||||
let player = player.clone();
|
|
||||||
let parsed_progress: Vec<AdvancementProgressData> = progress
|
let parsed_progress: Vec<AdvancementProgressData> = progress
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(key, val)| AdvancementProgressData {
|
.map(|(key, val)| AdvancementProgressData {
|
||||||
|
|||||||
@ -286,7 +286,7 @@ impl EntityBase for ArrowEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(clippy::too_many_lines)]
|
#[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 entity = self.get_entity();
|
||||||
let world = entity.world.load();
|
let world = entity.world.load();
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
use std::sync::RwLock;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
use crate::plugin::player::egg_throw::PlayerEggThrowEvent;
|
use crate::plugin::player::egg_throw::PlayerEggThrowEvent;
|
||||||
use crate::{
|
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);
|
self.thrown.process_tick(caller, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use crate::entity::projectile::ProjectileHit;
|
use crate::entity::projectile::ProjectileHit;
|
||||||
@ -47,7 +46,7 @@ impl EnderPearlEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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);
|
self.thrown.process_tick(caller, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,11 +93,9 @@ impl EntityBase for EnderPearlEntity {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let world_clone = world.clone();
|
|
||||||
let owner_id = self.thrown.owner_id;
|
let owner_id = self.thrown.owner_id;
|
||||||
let teleport_pos = entity.last_pos.load();
|
let teleport_pos = entity.last_pos.load();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
if let (
|
if let (
|
||||||
ProjectileHit::Entity {
|
ProjectileHit::Entity {
|
||||||
entity: hit_entity,
|
entity: hit_entity,
|
||||||
@ -120,37 +117,33 @@ impl EntityBase for EnderPearlEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(owner_id) = owner_id
|
if let Some(owner_id) = owner_id
|
||||||
&& let Some(owner) = world_clone.get_entity_by_id(owner_id)
|
&& let Some(owner) = world.get_entity_by_id(owner_id)
|
||||||
&& owner.get_entity().is_alive()
|
&& owner.get_entity().is_alive()
|
||||||
&& owner.get_living_entity().is_none_or(|living| {
|
&& owner.get_living_entity().is_none_or(|living| {
|
||||||
living.health.load() > 0.0
|
living.health.load() > 0.0 && owner.get_entity().pose.load() != EntityPose::Sleeping
|
||||||
&& owner.get_entity().pose.load() != EntityPose::Sleeping
|
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let should_spawn_endermite = rand::random::<f32>() < ENDERMITE_SPAWN_CHANCE;
|
let should_spawn_endermite = rand::random::<f32>() < ENDERMITE_SPAWN_CHANCE;
|
||||||
if world_clone.should_spawn_monsters() && should_spawn_endermite {
|
if world.should_spawn_monsters() && should_spawn_endermite {
|
||||||
let entity = Entity::new(
|
let entity = Entity::new(
|
||||||
world_clone.clone(),
|
world.clone(),
|
||||||
owner.get_entity().pos.load(),
|
owner.get_entity().pos.load(),
|
||||||
&EntityType::ENDERMITE,
|
&EntityType::ENDERMITE,
|
||||||
);
|
);
|
||||||
let endermite = EndermiteEntity::new(entity);
|
let endermite = EndermiteEntity::new(entity);
|
||||||
world_clone.spawn_entity(endermite);
|
world.spawn_entity(endermite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In vanilla, teleport handles everything including sound
|
// In vanilla, teleport handles everything including sound
|
||||||
owner
|
owner.teleport(
|
||||||
.clone()
|
|
||||||
.teleport(
|
|
||||||
teleport_pos,
|
teleport_pos,
|
||||||
Some(owner.get_entity().yaw.load()),
|
Some(owner.get_entity().yaw.load()),
|
||||||
Some(owner.get_entity().pitch.load()),
|
Some(owner.get_entity().pitch.load()),
|
||||||
world_clone.clone(),
|
world.clone(),
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
|
|
||||||
// Play teleport sound at new position
|
// Play teleport sound at new position
|
||||||
world_clone.play_sound(
|
world.play_sound(
|
||||||
Sound::EntityPlayerTeleport,
|
Sound::EntityPlayerTeleport,
|
||||||
SoundCategory::Players,
|
SoundCategory::Players,
|
||||||
&teleport_pos,
|
&teleport_pos,
|
||||||
@ -163,7 +156,6 @@ impl EntityBase for EnderPearlEntity {
|
|||||||
pumpkin_data::damage::DamageType::ENDER_PEARL,
|
pumpkin_data::damage::DamageType::ENDER_PEARL,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
world.send_entity_status(entity, EntityStatus::Death, Some(ActorEventID::Death));
|
world.send_entity_status(entity, EntityStatus::Death, Some(ActorEventID::Death));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
|
|
||||||
use pumpkin_data::damage::DamageType;
|
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 entity = &self.entity;
|
||||||
let world = entity.world.load();
|
let world = entity.world.load();
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::entity::item::ItemEntity;
|
use crate::entity::item::ItemEntity;
|
||||||
use crate::entity::living::LivingEntity;
|
use crate::entity::living::LivingEntity;
|
||||||
use crate::entity::player::Player;
|
use crate::entity::player::Player;
|
||||||
use crate::{entity::EntityBaseFuture, server::Server};
|
use crate::server::Server;
|
||||||
use core::f64;
|
use core::f64;
|
||||||
use pumpkin_data::damage::DamageType;
|
use pumpkin_data::damage::DamageType;
|
||||||
use pumpkin_data::entity::EntityType;
|
use pumpkin_data::entity::EntityType;
|
||||||
@ -122,7 +122,7 @@ fn lerp(t: f64, start: f64, end: f64) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for EyeOfEnder {
|
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;
|
let entity = &self.entity;
|
||||||
entity.tick(caller, server);
|
entity.tick(caller, server);
|
||||||
|
|
||||||
@ -212,25 +212,17 @@ impl EntityBase for EyeOfEnder {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_item_entity(self: Arc<Self>) -> Option<Arc<ItemEntity>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cast_any(&self) -> &dyn std::any::Any {
|
fn cast_any(&self) -> &dyn std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_java_spawn_packet<'a>(
|
fn send_java_spawn_packet(&self, client: &crate::net::java::JavaClient) {
|
||||||
&'a self,
|
|
||||||
client: &'a crate::net::java::JavaClient,
|
|
||||||
) -> EntityBaseFuture<'a, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let spawn_packet = self.entity.create_spawn_packet();
|
let spawn_packet = self.entity.create_spawn_packet();
|
||||||
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
||||||
client.enqueue_packet(data).await;
|
client.try_enqueue_packet(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.version.load() >= pumpkin_data::packet::CURRENT_MC_VERSION {
|
if client.version.load() >= pumpkin_util::version::JavaMinecraftVersion::V_1_21 {
|
||||||
let metadata = Metadata::new(
|
let metadata = Metadata::new(
|
||||||
pumpkin_data::tracked_data::eye_of_ender::ITEM_STACK,
|
pumpkin_data::tracked_data::eye_of_ender::ITEM_STACK,
|
||||||
ItemStackSerializer::from(
|
ItemStackSerializer::from(
|
||||||
@ -248,10 +240,9 @@ impl EntityBase for EyeOfEnder {
|
|||||||
data.into(),
|
data.into(),
|
||||||
);
|
);
|
||||||
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
||||||
client.enqueue_packet(meta_data).await;
|
client.try_enqueue_packet(meta_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
use std::sync::RwLock;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
use pumpkin_data::item::Item;
|
use pumpkin_data::item::Item;
|
||||||
use pumpkin_data::item_stack::ItemStack;
|
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 entity = self.get_entity();
|
||||||
let mut velocity = entity.velocity.load();
|
let mut velocity = entity.velocity.load();
|
||||||
|
|
||||||
|
|||||||
@ -10,11 +10,7 @@ use pumpkin_util::{
|
|||||||
math::vector3::Vector3,
|
math::vector3::Vector3,
|
||||||
random::{RandomGenerator, RandomImpl, get_seed, xoroshiro128::Xoroshiro},
|
random::{RandomGenerator, RandomImpl, get_seed, xoroshiro128::Xoroshiro},
|
||||||
};
|
};
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
use std::sync::{
|
|
||||||
Arc,
|
|
||||||
atomic::{AtomicU32, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
const GRAVITY: f64 = 0.0;
|
const GRAVITY: f64 = 0.0;
|
||||||
|
|
||||||
@ -95,7 +91,7 @@ impl FireworkRocketEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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);
|
self.entity.process_tick(caller, server);
|
||||||
|
|
||||||
let entity = self.get_entity();
|
let entity = self.get_entity();
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||||
|
|
||||||
use crate::entity::projectile::{ProjectileHit, is_projectile};
|
use crate::entity::projectile::{ProjectileHit, is_projectile};
|
||||||
@ -92,7 +91,7 @@ impl FishingBobberEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
#[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 entity = self.get_entity();
|
||||||
let world = entity.world.load();
|
let world = entity.world.load();
|
||||||
|
|
||||||
@ -180,7 +179,7 @@ impl FishingBobberEntity {
|
|||||||
.expand(0.3, 0.3, 0.3);
|
.expand(0.3, 0.3, 0.3);
|
||||||
|
|
||||||
// Basic block collision to stop bobber
|
// 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() {
|
if !block_cols.is_empty() {
|
||||||
self.in_ground.store(true, Ordering::Relaxed);
|
self.in_ground.store(true, Ordering::Relaxed);
|
||||||
entity.velocity.store(Vector3::new(0.0, 0.0, 0.0));
|
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);
|
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);
|
self.process_tick(caller, server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
use std::sync::RwLock;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
use crate::entity::projectile::splash_potion::extinguish_fire_if_water_potion;
|
use crate::entity::projectile::splash_potion::extinguish_fire_if_water_potion;
|
||||||
use crate::{
|
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);
|
self.thrown.process_tick(caller, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use pumpkin_data::damage::DamageType;
|
use pumpkin_data::damage::DamageType;
|
||||||
@ -57,7 +56,7 @@ impl LlamaSpitEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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) {
|
if self.get_entity().touching_water.load(Ordering::Relaxed) {
|
||||||
self.get_entity().remove();
|
self.get_entity().remove();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -118,7 +118,7 @@ impl ThrownItemEntity {
|
|||||||
|
|
||||||
impl ThrownItemEntity {
|
impl ThrownItemEntity {
|
||||||
/// Process a tick for projectile movement and collisions
|
/// 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 entity = self.get_entity();
|
||||||
let world = entity.world.load();
|
let world = entity.world.load();
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ impl ThrownItemEntity {
|
|||||||
let mut hit = None;
|
let mut hit = None;
|
||||||
|
|
||||||
// Block collisions
|
// 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() {
|
for (idx, bb) in block_cols.iter().enumerate() {
|
||||||
if let Some(t) = calculate_ray_intersection(&start_pos, &delta, bb)
|
if let Some(t) = calculate_ray_intersection(&start_pos, &delta, bb)
|
||||||
&& t < closest_t
|
&& t < closest_t
|
||||||
|
|||||||
@ -318,7 +318,7 @@ impl EntityBase for ShulkerBulletEntity {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
#[allow(clippy::too_many_lines)]
|
#[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) {
|
if self.has_hit.load(Ordering::Relaxed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -37,7 +36,7 @@ impl SmallFireballEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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);
|
self.thrown.process_tick(caller, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use crate::entity::projectile::ProjectileHit;
|
use crate::entity::projectile::ProjectileHit;
|
||||||
@ -42,7 +41,7 @@ impl SnowballEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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);
|
self.thrown.process_tick(caller, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
self.thrown.process_tick(caller, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -152,7 +152,7 @@ impl TridentEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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 entity = self.get_entity();
|
||||||
let world = entity.world.load();
|
let world = entity.world.load();
|
||||||
|
|
||||||
|
|||||||
@ -126,7 +126,7 @@ impl WindChargeEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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);
|
self.thrown_item_entity.process_tick(caller, server);
|
||||||
|
|
||||||
if let Some(cooldown) = self.deflect_cooldown() {
|
if let Some(cooldown) = self.deflect_cooldown() {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use pumpkin_data::{damage::DamageType, effect::StatusEffect, potion::Effect, tracked_data};
|
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);
|
self.thrown.process_tick(caller, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,13 +6,10 @@ use pumpkin_protocol::{codec::var_int::VarInt, java::client::play::Metadata};
|
|||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
use std::{
|
use std::{
|
||||||
f64::consts::TAU,
|
f64::consts::TAU,
|
||||||
sync::{
|
sync::atomic::{
|
||||||
Arc,
|
|
||||||
atomic::{
|
|
||||||
AtomicU32,
|
AtomicU32,
|
||||||
Ordering::{self, Relaxed},
|
Ordering::{self, Relaxed},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TNTEntity {
|
pub struct TNTEntity {
|
||||||
@ -32,7 +29,7 @@ impl TNTEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityBase for 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 entity = &self.entity;
|
||||||
|
|
||||||
let mut velo = entity.velocity.load();
|
let mut velo = entity.velocity.load();
|
||||||
|
|||||||
@ -60,7 +60,7 @@ impl EntityBase for BoatEntity {
|
|||||||
None
|
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();
|
self.vehicle.tick();
|
||||||
|
|
||||||
let underwater = self.ticks_underwater.load();
|
let underwater = self.ticks_underwater.load();
|
||||||
|
|||||||
@ -132,7 +132,7 @@ impl EntityBase for MinecartEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[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();
|
self.vehicle.tick();
|
||||||
if let MinecartKind::Furnace(minecart) = &self.kind {
|
if let MinecartKind::Furnace(minecart) = &self.kind {
|
||||||
minecart.tick(&self.vehicle.entity);
|
minecart.tick(&self.vehicle.entity);
|
||||||
@ -222,13 +222,11 @@ impl EntityBase for MinecartEntity {
|
|||||||
if !p_ids.is_empty() {
|
if !p_ids.is_empty() {
|
||||||
let world = self.vehicle.entity.world.load();
|
let world = self.vehicle.entity.world.load();
|
||||||
let vid = self.vehicle.entity.entity_id;
|
let vid = self.vehicle.entity.entity_id;
|
||||||
tokio::spawn(async move {
|
|
||||||
if let Some(v) = world.get_entity_by_id(vid) {
|
if let Some(v) = world.get_entity_by_id(vid) {
|
||||||
for pid in p_ids {
|
for pid in p_ids {
|
||||||
v.get_entity().remove_passenger(pid).await;
|
v.get_entity().remove_passenger_sync(pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.vehicle.get_hurt_time() == 0 {
|
if self.vehicle.get_hurt_time() == 0 {
|
||||||
@ -503,7 +501,7 @@ impl EntityBase for MinecartEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[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 self_entity = self.get_entity();
|
||||||
let other_entity = entity.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);
|
let to_position = self.vehicle.entity.pos.load().add(&motion);
|
||||||
self.vehicle.entity.move_entity(caller, motion);
|
self.vehicle.entity.move_entity(caller, motion);
|
||||||
let entity_id = self.vehicle.entity.entity_id;
|
let entity_id = self.vehicle.entity.entity_id;
|
||||||
|
|||||||
@ -435,7 +435,7 @@ impl PumpkinServer {
|
|||||||
"The input is not a TTY; falling back to simple logger and ignoring `use_tty` setting"
|
"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
|
.server
|
||||||
.player_data_storage
|
.player_data_storage
|
||||||
.save_all_players(&self.server)
|
.save_all_players(&self.server)
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
error!("Error saving all players during shutdown: {e}");
|
error!("Error saving all players during shutdown: {e}");
|
||||||
}
|
}
|
||||||
@ -586,9 +585,10 @@ impl PumpkinServer {
|
|||||||
}
|
}
|
||||||
player.remove().await;
|
player.remove().await;
|
||||||
server_clone.remove_player(&player).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)
|
.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
|
if let Err(e) = server_clone.advancement_manager
|
||||||
@ -686,10 +686,7 @@ impl PumpkinServer {
|
|||||||
client.await_tasks().await;
|
client.await_tasks().await;
|
||||||
player.remove().await;
|
player.remove().await;
|
||||||
server.remove_player(&player).await;
|
server.remove_player(&player).await;
|
||||||
if let Err(error) = server
|
if let Err(error) = server.player_data_storage.handle_player_leave(&player)
|
||||||
.player_data_storage
|
|
||||||
.handle_player_leave(&player)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
error!("Failed to save player data on disconnect: {error}");
|
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 (tx, mut rx) = tokio::sync::mpsc::channel(1);
|
||||||
let rt = tokio::runtime::Handle::current();
|
let rt = tokio::runtime::Handle::current();
|
||||||
std::thread::spawn(move || {
|
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()));
|
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)
|
while !SHOULD_STOP.load(Ordering::Relaxed)
|
||||||
&& let Some(command) = rx.recv().await
|
&& let Some(command) = rx.recv().await
|
||||||
{
|
{
|
||||||
let mut event = ServerCommandEvent::new(command.clone());
|
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 {
|
if !event.cancelled {
|
||||||
server.command_dispatcher.load().handle_command(
|
server_clone.command_dispatcher.load().handle_command(
|
||||||
&command::CommandSender::Console.into_source(&server),
|
&command::CommandSender::Console.into_source(&server_clone),
|
||||||
command.as_str(),
|
command.as_str(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -343,26 +343,32 @@ impl BedrockClient {
|
|||||||
let cache_enabled = server.advanced_config.networking.bedrock.chunk_caching
|
let cache_enabled = server.advanced_config.networking.bedrock.chunk_caching
|
||||||
&& self.client_cache_supported.load(Ordering::Relaxed);
|
&& self.client_cache_supported.load(Ordering::Relaxed);
|
||||||
|
|
||||||
let mut serialize_tasks = Vec::with_capacity(valid_chunks.len());
|
let world = player.world();
|
||||||
for chunk in valid_chunks {
|
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||||
let block_actors = player.world().bedrock_chunk_block_actors(&chunk);
|
rayon::spawn(move || {
|
||||||
serialize_tasks.push(tokio::task::spawn_blocking(move || {
|
let mut encoded_payloads = Vec::with_capacity(valid_chunks.len());
|
||||||
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();
|
let mut new_blobs = Vec::new();
|
||||||
for task in serialize_tasks {
|
for chunk in valid_chunks {
|
||||||
match task.await {
|
let block_actors = world.bedrock_chunk_block_actors(&chunk);
|
||||||
Ok(Ok((payload, blobs))) => {
|
match CLevelChunk::encode_chunk(
|
||||||
|
&chunk,
|
||||||
|
bedrock_dimension,
|
||||||
|
cache_enabled,
|
||||||
|
&block_actors,
|
||||||
|
) {
|
||||||
|
Ok((payload, blobs)) => {
|
||||||
encoded_payloads.push(payload);
|
encoded_payloads.push(payload);
|
||||||
new_blobs.extend(blobs);
|
new_blobs.extend(blobs);
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => error!("Failed to serialize Bedrock chunk: {:?}", e),
|
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() {
|
if !new_blobs.is_empty() {
|
||||||
let mut cache = self.blob_cache.lock().await;
|
let mut cache = self.blob_cache.lock().await;
|
||||||
@ -528,11 +534,8 @@ impl BedrockClient {
|
|||||||
self.close_token.is_cancelled() || self.session.is_closed()
|
self.close_token.is_cancelled() || self.session.is_closed()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enqueue_spawn_packet(self: &Arc<Self>, entity: Arc<dyn crate::entity::EntityBase>) {
|
pub fn enqueue_spawn_packet(&self, entity: &dyn crate::entity::EntityBase) {
|
||||||
let client = self.clone();
|
entity.send_bedrock_spawn_packet(self);
|
||||||
self.spawn_task(async move {
|
|
||||||
entity.send_bedrock_spawn_packet(&client).await;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_batch(
|
async fn process_batch(
|
||||||
|
|||||||
@ -45,7 +45,7 @@ impl BedrockClient {
|
|||||||
if player.gamemode.load() == GameMode::Creative {
|
if player.gamemode.load() == GameMode::Creative {
|
||||||
let new_state = world.break_block(
|
let new_state = world.break_block(
|
||||||
&location,
|
&location,
|
||||||
Some(player.clone()),
|
Some(player),
|
||||||
BlockFlags::NOTIFY_NEIGHBORS | BlockFlags::SKIP_DROPS,
|
BlockFlags::NOTIFY_NEIGHBORS | BlockFlags::SKIP_DROPS,
|
||||||
);
|
);
|
||||||
if new_state.is_some() {
|
if new_state.is_some() {
|
||||||
@ -59,11 +59,12 @@ impl BedrockClient {
|
|||||||
player.stop_mining();
|
player.stop_mining();
|
||||||
let broken_state = world.get_block_state(&location);
|
let broken_state = world.get_block_state(&location);
|
||||||
let can_harvest = player.can_harvest(broken_state, block);
|
let can_harvest = player.can_harvest(broken_state, block);
|
||||||
let new_state = world.break_block(
|
let flags = if can_harvest {
|
||||||
&location,
|
BlockFlags::NOTIFY_NEIGHBORS
|
||||||
Some(player.clone()),
|
} else {
|
||||||
BlockFlags::NOTIFY_NEIGHBORS,
|
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
|
||||||
);
|
};
|
||||||
|
let new_state = world.break_block(&location, Some(player), flags);
|
||||||
if new_state.is_some() {
|
if new_state.is_some() {
|
||||||
server.block_registry.broken(
|
server.block_registry.broken(
|
||||||
&world,
|
&world,
|
||||||
@ -75,7 +76,7 @@ impl BedrockClient {
|
|||||||
);
|
);
|
||||||
player.apply_tool_damage_for_block_break(broken_state);
|
player.apply_tool_damage_for_block_break(broken_state);
|
||||||
if can_harvest {
|
if can_harvest {
|
||||||
player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
|
player.add_exhaustion(MINE_BLOCK_EXHAUSTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -157,16 +158,13 @@ impl BedrockClient {
|
|||||||
} else {
|
} else {
|
||||||
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
|
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
|
||||||
};
|
};
|
||||||
if world
|
if world.break_block(&location, Some(player), flags).is_some() {
|
||||||
.break_block(&location, Some(player.clone()), flags)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
server
|
server
|
||||||
.block_registry
|
.block_registry
|
||||||
.broken(&world, block, player, &location, server, state);
|
.broken(&world, block, player, &location, server, state);
|
||||||
player.apply_tool_damage_for_block_break(state);
|
player.apply_tool_damage_for_block_break(state);
|
||||||
if can_harvest {
|
if can_harvest {
|
||||||
player.add_exhaustion(MINE_BLOCK_EXHAUSTION).await;
|
player.add_exhaustion(MINE_BLOCK_EXHAUSTION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -145,7 +145,7 @@ impl BedrockClient {
|
|||||||
|
|
||||||
if pos_changed {
|
if pos_changed {
|
||||||
chunker::update_position(player).await;
|
chunker::update_position(player).await;
|
||||||
player.progress_motion(delta).await;
|
player.progress_motion(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,6 @@ impl JavaClient {
|
|||||||
if version.supports_configuration_state() {
|
if version.supports_configuration_state() {
|
||||||
self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()]))
|
self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()]))
|
||||||
.await;
|
.await;
|
||||||
let registry_packets = tokio::task::spawn_blocking(move || {
|
|
||||||
let registry = Registry::get_synced(version);
|
let registry = Registry::get_synced(version);
|
||||||
let mut packets = Vec::new();
|
let mut packets = Vec::new();
|
||||||
let mut sent_dimension_type = false;
|
let mut sent_dimension_type = false;
|
||||||
@ -58,12 +57,8 @@ impl JavaClient {
|
|||||||
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
|
if let Ok(data) = Self::serialize_packet_for_version(&packet, version) {
|
||||||
packets.push(data);
|
packets.push(data);
|
||||||
}
|
}
|
||||||
packets
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
for packet_data in registry_packets {
|
for packet_data in packets {
|
||||||
self.send_packet_now(packet_data).await;
|
self.send_packet_now(packet_data).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -358,20 +358,18 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let version = self.version.load();
|
let version = self.version.load();
|
||||||
// Offload CPU-heavy packet serialization (palette packing, lighting, NBT heightmaps)
|
let (tx, rx) = oneshot::channel();
|
||||||
// off Tokio worker threads onto blocking/Rayon worker threads
|
rayon::spawn(move || {
|
||||||
let serialize_tasks: Vec<_> = valid_chunks
|
let mut serialized = Vec::with_capacity(valid_chunks.len());
|
||||||
.into_iter()
|
for chunk in valid_chunks {
|
||||||
.map(|chunk| {
|
|
||||||
tokio::task::spawn_blocking(move || {
|
|
||||||
let mut buf = Vec::with_capacity(32 * 1024);
|
let mut buf = Vec::with_capacity(32 * 1024);
|
||||||
if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) {
|
if let Err(err) = buf.write_var_int(&VarInt(CChunkData::to_id(version))) {
|
||||||
error!("Failed to write chunk data id: {err:?}");
|
error!("Failed to write chunk data id: {err:?}");
|
||||||
return None;
|
continue;
|
||||||
}
|
}
|
||||||
if let Err(err) = CChunkData(&chunk).write_packet_data(&mut buf, &version) {
|
if let Err(err) = CChunkData(&chunk).write_packet_data(&mut buf, &version) {
|
||||||
error!("Failed to write chunk data: {err:?}");
|
error!("Failed to write chunk data: {err:?}");
|
||||||
return None;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let light_buf = if version >= JavaMinecraftVersion::V_1_14
|
let light_buf = if version >= JavaMinecraftVersion::V_1_14
|
||||||
@ -403,21 +401,23 @@ impl JavaClient {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Some((Bytes::from(buf), light_buf))
|
serialized.push((Bytes::from(buf), light_buf));
|
||||||
})
|
}
|
||||||
})
|
let _ = tx.send(serialized);
|
||||||
.collect();
|
});
|
||||||
|
|
||||||
|
let Ok(serialized) = rx.await else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if version >= JavaMinecraftVersion::V_1_20_2 {
|
if version >= JavaMinecraftVersion::V_1_20_2 {
|
||||||
self.send_packet(&CChunkBatchStart).await;
|
self.send_packet(&CChunkBatchStart).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
for task in serialize_tasks {
|
for (chunk_data, light_data) in serialized {
|
||||||
if let Ok(Some((chunk_data, light_data))) = task.await {
|
self.enqueue_packet(chunk_data).await;
|
||||||
self.send_packet_now_data(chunk_data).await;
|
|
||||||
if let Some(light_data) = light_data {
|
if let Some(light_data) = light_data {
|
||||||
self.send_packet_now_data(light_data).await;
|
self.enqueue_packet(light_data).await;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,7 +78,7 @@ impl JavaClient {
|
|||||||
// Block break & play sound
|
// Block break & play sound
|
||||||
let new_state = world.break_block(
|
let new_state = world.break_block(
|
||||||
&position,
|
&position,
|
||||||
Some(player.clone()),
|
Some(player),
|
||||||
BlockFlags::NOTIFY_NEIGHBORS | BlockFlags::SKIP_DROPS,
|
BlockFlags::NOTIFY_NEIGHBORS | BlockFlags::SKIP_DROPS,
|
||||||
);
|
);
|
||||||
if new_state.is_some() {
|
if new_state.is_some() {
|
||||||
@ -100,11 +100,12 @@ impl JavaClient {
|
|||||||
if speed >= 1.0 {
|
if speed >= 1.0 {
|
||||||
let broken_state = world.get_block_state(&position);
|
let broken_state = world.get_block_state(&position);
|
||||||
let can_harvest = player.can_harvest(broken_state, block);
|
let can_harvest = player.can_harvest(broken_state, block);
|
||||||
let new_state = world.break_block(
|
let flags = if can_harvest {
|
||||||
&position,
|
BlockFlags::NOTIFY_NEIGHBORS
|
||||||
Some(player.clone()),
|
} else {
|
||||||
BlockFlags::NOTIFY_NEIGHBORS,
|
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
|
||||||
);
|
};
|
||||||
|
let new_state = world.break_block(&position, Some(player), flags);
|
||||||
if new_state.is_some() {
|
if new_state.is_some() {
|
||||||
server.block_registry.broken(
|
server.block_registry.broken(
|
||||||
&world,
|
&world,
|
||||||
@ -116,7 +117,7 @@ impl JavaClient {
|
|||||||
);
|
);
|
||||||
player.apply_tool_damage_for_block_break(broken_state);
|
player.apply_tool_damage_for_block_break(broken_state);
|
||||||
if can_harvest {
|
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;
|
let item_id = player.inventory().held_item().item.id;
|
||||||
player.increment_stat(StatisticCategory::Used, item_id as i32, 1);
|
player.increment_stat(StatisticCategory::Used, item_id as i32, 1);
|
||||||
@ -195,7 +196,7 @@ impl JavaClient {
|
|||||||
|
|
||||||
let new_state = world.break_block(
|
let new_state = world.break_block(
|
||||||
&location,
|
&location,
|
||||||
Some(player.clone()),
|
Some(player),
|
||||||
if block_drop {
|
if block_drop {
|
||||||
BlockFlags::NOTIFY_NEIGHBORS
|
BlockFlags::NOTIFY_NEIGHBORS
|
||||||
} else {
|
} else {
|
||||||
@ -209,7 +210,7 @@ impl JavaClient {
|
|||||||
|
|
||||||
player.apply_tool_damage_for_block_break(state);
|
player.apply_tool_damage_for_block_break(state);
|
||||||
if block_drop {
|
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;
|
let item_id = player.inventory().held_item().item.id;
|
||||||
player.increment_stat(StatisticCategory::Used, item_id as i32, 1);
|
player.increment_stat(StatisticCategory::Used, item_id as i32, 1);
|
||||||
|
|||||||
@ -105,7 +105,7 @@ impl JavaClient {
|
|||||||
|
|
||||||
let height_difference = pos.y - last_pos.y;
|
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 {
|
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;
|
let new_on_ground = packet.collision & FLAG_ON_GROUND != 0;
|
||||||
@ -167,7 +167,7 @@ impl JavaClient {
|
|||||||
if delta.length_squared() > 1.0E-5 {
|
if delta.length_squared() > 1.0E-5 {
|
||||||
player.update_last_action_time();
|
player.update_last_action_time();
|
||||||
}
|
}
|
||||||
player.progress_motion(delta).await;
|
player.progress_motion(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
'cancelled: {
|
'cancelled: {
|
||||||
@ -247,7 +247,7 @@ impl JavaClient {
|
|||||||
&& (packet.collision & FLAG_ON_GROUND) != 0
|
&& (packet.collision & FLAG_ON_GROUND) != 0
|
||||||
&& height_difference > 0.0
|
&& height_difference > 0.0
|
||||||
{
|
{
|
||||||
player.jump().await;
|
player.jump();
|
||||||
}
|
}
|
||||||
entity
|
entity
|
||||||
.on_ground
|
.on_ground
|
||||||
@ -324,7 +324,7 @@ impl JavaClient {
|
|||||||
if delta.length_squared() > 1.0E-5 {
|
if delta.length_squared() > 1.0E-5 {
|
||||||
player.update_last_action_time();
|
player.update_last_action_time();
|
||||||
}
|
}
|
||||||
player.progress_motion(delta).await;
|
player.progress_motion(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
'cancelled: {
|
'cancelled: {
|
||||||
|
|||||||
@ -31,9 +31,7 @@ impl JavaClient {
|
|||||||
.send_client_packet(&CSetCamera::new(target_id.into()))
|
.send_client_packet(&CSetCamera::new(target_id.into()))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
player
|
player.request_teleport(target_pos, target_yaw, target_pitch);
|
||||||
.request_teleport(target_pos, target_yaw, target_pitch)
|
|
||||||
.await;
|
|
||||||
} else if let Some(target_player) = server.get_player_by_uuid(packet.target) {
|
} 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_pos = target_player.living_entity.entity.pos.load();
|
||||||
let target_yaw = target_player.living_entity.entity.yaw.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()))
|
.send_client_packet(&CSetCamera::new(target_id.into()))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
player
|
player.request_teleport(target_pos, target_yaw, target_pitch);
|
||||||
.request_teleport(target_pos, target_yaw, target_pitch)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,9 +28,7 @@ impl JavaClient {
|
|||||||
.send_client_packet(&CSetCamera::new(target_id.into()))
|
.send_client_packet(&CSetCamera::new(target_id.into()))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
player
|
player.request_teleport(target_pos, target_yaw, target_pitch);
|
||||||
.request_teleport(target_pos, target_yaw, target_pitch)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -251,31 +251,13 @@ impl ClientPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_enqueue_spawn_packet(&self, entity: &Arc<dyn crate::entity::EntityBase>) {
|
pub fn try_enqueue_spawn_packet(&self, entity: &Arc<dyn crate::entity::EntityBase>) {
|
||||||
match self {
|
self.enqueue_spawn_packet(entity);
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
match self {
|
||||||
Self::Java(java) => entity.send_java_spawn_packet(java).await,
|
Self::Java(java) => entity.send_java_spawn_packet(java),
|
||||||
Self::Bedrock(bedrock) => entity.send_bedrock_spawn_packet(bedrock).await,
|
Self::Bedrock(bedrock) => entity.send_bedrock_spawn_packet(bedrock),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,13 +30,8 @@ impl PermissionCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn calculate_hash(path: &Path) -> tokio::io::Result<String> {
|
pub async fn calculate_hash(path: &Path) -> tokio::io::Result<String> {
|
||||||
let path = path.to_path_buf();
|
let bytes = fs::read(path).await?;
|
||||||
tokio::task::spawn_blocking(move || {
|
|
||||||
let bytes = std::fs::read(path)?;
|
|
||||||
Ok(calculate_hash_for_bytes(&bytes))
|
Ok(calculate_hash_for_bytes(&bytes))
|
||||||
})
|
|
||||||
.await
|
|
||||||
.map_err(std::io::Error::other)?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
@ -178,14 +178,12 @@ impl HostEntity for PluginHostState {
|
|||||||
)
|
)
|
||||||
.map_err(|_| wasmtime::Error::msg("invalid world resource handle"))?;
|
.map_err(|_| wasmtime::Error::msg("invalid world resource handle"))?;
|
||||||
let world = world.provider.clone();
|
let world = world.provider.clone();
|
||||||
entity_base
|
entity_base.teleport(
|
||||||
.teleport(
|
|
||||||
pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2),
|
pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
world,
|
world,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -224,7 +224,9 @@ impl Goal for CustomWasmGoal {
|
|||||||
if let Some(entity_arc) = current_mob_entity(mob) {
|
if let Some(entity_arc) = current_mob_entity(mob) {
|
||||||
let plugin = self.plugin.clone();
|
let plugin = self.plugin.clone();
|
||||||
let goal_id = self.goal_id;
|
let goal_id = self.goal_id;
|
||||||
tokio::spawn(async move {
|
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;
|
let mut store = plugin.store.lock().await;
|
||||||
match plugin.plugin_instance {
|
match plugin.plugin_instance {
|
||||||
PluginInstance::V0_1(ref plugin_inst) => {
|
PluginInstance::V0_1(ref plugin_inst) => {
|
||||||
@ -265,12 +267,15 @@ impl Goal for CustomWasmGoal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn tick(&mut self, mob: &dyn InternalMob) {
|
fn tick(&mut self, mob: &dyn InternalMob) {
|
||||||
if let Some(entity_arc) = current_mob_entity(mob) {
|
if let Some(entity_arc) = current_mob_entity(mob) {
|
||||||
let plugin = self.plugin.clone();
|
let plugin = self.plugin.clone();
|
||||||
let goal_id = self.goal_id;
|
let goal_id = self.goal_id;
|
||||||
tokio::spawn(async move {
|
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;
|
let mut store = plugin.store.lock().await;
|
||||||
match plugin.plugin_instance {
|
match plugin.plugin_instance {
|
||||||
PluginInstance::V0_1(ref plugin_inst) => {
|
PluginInstance::V0_1(ref plugin_inst) => {
|
||||||
@ -311,12 +316,15 @@ impl Goal for CustomWasmGoal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn stop(&mut self, mob: &dyn InternalMob) {
|
fn stop(&mut self, mob: &dyn InternalMob) {
|
||||||
if let Some(entity_arc) = current_mob_entity(mob) {
|
if let Some(entity_arc) = current_mob_entity(mob) {
|
||||||
let plugin = self.plugin.clone();
|
let plugin = self.plugin.clone();
|
||||||
let goal_id = self.goal_id;
|
let goal_id = self.goal_id;
|
||||||
tokio::spawn(async move {
|
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;
|
let mut store = plugin.store.lock().await;
|
||||||
match plugin.plugin_instance {
|
match plugin.plugin_instance {
|
||||||
PluginInstance::V0_1(ref plugin_inst) => {
|
PluginInstance::V0_1(ref plugin_inst) => {
|
||||||
@ -357,6 +365,7 @@ impl Goal for CustomWasmGoal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostMob for PluginHostState {
|
impl HostMob for PluginHostState {
|
||||||
|
|||||||
@ -2190,9 +2190,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
) -> wasmtime::Result<()> {
|
) -> wasmtime::Result<()> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
let world = world_from_resource(self, &world);
|
let world = world_from_resource(self, &world);
|
||||||
player
|
player.teleport(from_wasm_position(position), yaw, pitch, world);
|
||||||
.teleport(from_wasm_position(position), yaw, pitch, world)
|
|
||||||
.await;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -237,7 +237,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
Dimension::End => pumpkin_data::dimension::Dimension::THE_END,
|
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)
|
self.add_world(world)
|
||||||
.map_err(|_| wasmtime::Error::msg("failed to add world resource"))
|
.map_err(|_| wasmtime::Error::msg("failed to add world resource"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -295,8 +295,8 @@ impl PluginManager {
|
|||||||
.map_err(|e| ManagerError::IoError(std::io::Error::other(e)))?;
|
.map_err(|e| ManagerError::IoError(std::io::Error::other(e)))?;
|
||||||
|
|
||||||
let manager = self.clone();
|
let manager = self.clone();
|
||||||
let server = server.clone();
|
let server_clone = Arc::clone(server);
|
||||||
let task = tokio::spawn(async move {
|
let task = server.spawn_task(async move {
|
||||||
// Keep watcher alive by moving it into the task
|
// Keep watcher alive by moving it into the task
|
||||||
let _watcher = watcher;
|
let _watcher = watcher;
|
||||||
|
|
||||||
@ -333,7 +333,9 @@ impl PluginManager {
|
|||||||
// For now, we just try to load it. If it's already loaded,
|
// For now, we just try to load it. If it's already loaded,
|
||||||
// the loader might handle it or we might get a duplicate.
|
// the loader might handle it or we might get a duplicate.
|
||||||
// Most WASM loaders will just create a new instance.
|
// 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);
|
error!("Failed to hot-reload plugin {:?}: {}", path, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -542,7 +544,7 @@ impl PluginManager {
|
|||||||
|
|
||||||
let context = Arc::new(Context::new(
|
let context = Arc::new(Context::new(
|
||||||
metadata.clone(),
|
metadata.clone(),
|
||||||
server,
|
server.clone(),
|
||||||
Arc::clone(&self.handlers),
|
Arc::clone(&self.handlers),
|
||||||
Arc::clone(self),
|
Arc::clone(self),
|
||||||
Arc::clone(&LOGGER_IMPL),
|
Arc::clone(&LOGGER_IMPL),
|
||||||
@ -571,7 +573,7 @@ impl PluginManager {
|
|||||||
let plugin_name = metadata.name.clone();
|
let plugin_name = metadata.name.clone();
|
||||||
let loader_clone = loader.clone();
|
let loader_clone = loader.clone();
|
||||||
|
|
||||||
let task = tokio::spawn(async move {
|
let task = server.spawn_task(async move {
|
||||||
// Initialize the plugin
|
// Initialize the plugin
|
||||||
match instance.on_load(context.clone()).await {
|
match instance.on_load(context.clone()).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
@ -616,9 +618,7 @@ impl PluginManager {
|
|||||||
|
|
||||||
// Try to unload the plugin data
|
// Try to unload the plugin data
|
||||||
if let Some(data) = loader_data {
|
if let Some(data) = loader_data {
|
||||||
tokio::spawn(async move {
|
|
||||||
loader_clone.unload(data).await.ok();
|
loader_clone.unload(data).await.ok();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@ -317,30 +317,24 @@ impl Server {
|
|||||||
|
|
||||||
// Fetch / generate keys in background tasks to avoid blocking startup
|
// Fetch / generate keys in background tasks to avoid blocking startup
|
||||||
let server_clone = server.clone();
|
let server_clone = server.clone();
|
||||||
tokio::spawn(async move {
|
server.spawn_task(async move {
|
||||||
let key_store = tokio::task::spawn_blocking(|| Arc::new(KeyStore::new()))
|
let key_store = Arc::new(KeyStore::new());
|
||||||
.await
|
|
||||||
.unwrap_or_else(|_| Arc::new(KeyStore::new()));
|
|
||||||
let _ = server_clone.key_store.set(key_store);
|
let _ = server_clone.key_store.set(key_store);
|
||||||
});
|
});
|
||||||
|
|
||||||
if server.basic_config.allow_chat_reports {
|
if server.basic_config.allow_chat_reports {
|
||||||
let server_clone = server.clone();
|
let server_clone = server.clone();
|
||||||
tokio::spawn(async move {
|
server.spawn_task(async move {
|
||||||
let auth_config = server_clone
|
let auth_config = server_clone
|
||||||
.advanced_config
|
.advanced_config
|
||||||
.networking
|
.networking
|
||||||
.java
|
.java
|
||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let keys = tokio::task::spawn_blocking(move || {
|
let keys = fetch_mojang_public_keys(&auth_config).unwrap_or_else(|e| {
|
||||||
fetch_mojang_public_keys(&auth_config).unwrap_or_else(|e| {
|
|
||||||
error!("Failed to fetch Mojang keys: {e}");
|
error!("Failed to fetch Mojang keys: {e}");
|
||||||
Vec::new()
|
Vec::new()
|
||||||
})
|
});
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap_or_default();
|
|
||||||
server_clone.mojang_public_keys.store(Arc::new(keys));
|
server_clone.mojang_public_keys.store(Arc::new(keys));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -354,68 +348,47 @@ impl Server {
|
|||||||
.enabled
|
.enabled
|
||||||
{
|
{
|
||||||
let server_clone = server.clone();
|
let server_clone = server.clone();
|
||||||
tokio::spawn(async move {
|
server.spawn_task(async move {
|
||||||
let auth = server_clone
|
let auth = server_clone
|
||||||
.advanced_config
|
.advanced_config
|
||||||
.networking
|
.networking
|
||||||
.bedrock
|
.bedrock
|
||||||
.authentication
|
.authentication
|
||||||
.clone();
|
.clone();
|
||||||
let keys = match tokio::task::spawn_blocking(move || {
|
let keys = match pumpkin_util::jwt::fetch_oidc_jwks(
|
||||||
pumpkin_util::jwt::fetch_oidc_jwks(
|
|
||||||
auth.url.as_deref(),
|
auth.url.as_deref(),
|
||||||
auth.connect_timeout,
|
auth.connect_timeout,
|
||||||
auth.read_timeout,
|
auth.read_timeout,
|
||||||
)
|
) {
|
||||||
})
|
Ok(keys) => keys,
|
||||||
.await
|
Err(error) => {
|
||||||
{
|
|
||||||
Ok(Ok(keys)) => keys,
|
|
||||||
Ok(Err(error)) => {
|
|
||||||
error!("Failed to fetch Bedrock OIDC keys: {error}");
|
error!("Failed to fetch Bedrock OIDC keys: {error}");
|
||||||
(String::new(), pumpkin_util::jwt::Jwks { keys: Vec::new() })
|
(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 _ = server_clone.bedrock_oidc_keys.set(keys);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let world_loader = |dim: Dimension| {
|
let mut worlds_vec = Vec::new();
|
||||||
let path = world_path.clone();
|
for dim in &server.dimensions {
|
||||||
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!(
|
info!(
|
||||||
"Loading {}",
|
"Loading {}",
|
||||||
TextComponent::text(dim.minecraft_name.to_string())
|
TextComponent::text(dim.minecraft_name.to_string())
|
||||||
.color_named(NamedColor::DarkGreen)
|
.color_named(NamedColor::DarkGreen)
|
||||||
.to_pretty_console()
|
.to_pretty_console()
|
||||||
);
|
);
|
||||||
let level = into_level(dim.clone(), &config, path, seed);
|
let config = Arc::new(server.advanced_config.world.clone());
|
||||||
let world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak));
|
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()));
|
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
|
||||||
level.world_portal.store(Arc::new(Some(portal)));
|
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() {
|
|
||||||
worlds_vec.push(world);
|
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 worlds = self.worlds.load();
|
||||||
let world = worlds
|
let world = worlds
|
||||||
@ -487,15 +460,12 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let server = self.clone();
|
let world_path = self.basic_config.get_world_path().join(name);
|
||||||
let name_clone = name.clone();
|
let registry = self.block_registry.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
let l_info = self.level_info.clone();
|
||||||
let world_path = server.basic_config.get_world_path().join(name_clone);
|
let weak = Arc::downgrade(self);
|
||||||
let registry = server.block_registry.clone();
|
let config = Arc::new(self.advanced_config.world.clone());
|
||||||
let l_info = server.level_info.clone();
|
let seed = self.level_info.load().world_gen_settings.seed;
|
||||||
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 level =
|
let level =
|
||||||
pumpkin_world::dimension::into_level(dimension.clone(), &config, world_path, seed);
|
pumpkin_world::dimension::into_level(dimension.clone(), &config, world_path, seed);
|
||||||
@ -503,21 +473,15 @@ impl Server {
|
|||||||
let world = Arc::new(world);
|
let world = Arc::new(world);
|
||||||
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
|
let portal: Arc<dyn WorldPortalExt> = Arc::new(WorldPortal(world.clone()));
|
||||||
level.world_portal.store(Arc::new(Some(portal)));
|
level.world_portal.store(Arc::new(Some(portal)));
|
||||||
server.worlds.rcu(|worlds| {
|
self.worlds.rcu(|worlds| {
|
||||||
let mut new_worlds = (**worlds).clone();
|
let mut new_worlds = (**worlds).clone();
|
||||||
new_worlds.push(world.clone());
|
new_worlds.push(world.clone());
|
||||||
new_worlds
|
new_worlds
|
||||||
});
|
});
|
||||||
let mut event =
|
let mut event =
|
||||||
crate::plugin::api::events::world::world_init::WorldInitEvent::new(world.clone());
|
crate::plugin::api::events::world::world_init::WorldInitEvent::new(world.clone());
|
||||||
server.plugin_manager.fire_blocking(&server, &mut event);
|
self.plugin_manager.fire_blocking(self, &mut event);
|
||||||
world
|
world
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
error!("World creation failed");
|
|
||||||
std::process::exit(1);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn unload_world(&self, name: &str) -> Result<(), String> {
|
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}"));
|
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}");
|
error!("Failed to save player data: {err}");
|
||||||
return Err(format!("Failed to save player data: {err}"));
|
return Err(format!("Failed to save player data: {err}"));
|
||||||
}
|
}
|
||||||
@ -650,8 +614,7 @@ impl Server {
|
|||||||
|
|
||||||
let first_world = self.worlds.load().first().cloned()?;
|
let first_world = self.worlds.load().first().cloned()?;
|
||||||
|
|
||||||
let (world, nbt) =
|
let (world, nbt) = if let Ok(Some(data)) = self.player_data_storage.load_data(&profile.id) {
|
||||||
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_key) = data.get_string("Dimension") {
|
||||||
if let Some(dimension) = Dimension::from_name(dimension_key) {
|
if let Some(dimension) = Dimension::from_name(dimension_key) {
|
||||||
let world = self.get_world_from_dimension(dimension);
|
let world = self.get_world_from_dimension(dimension);
|
||||||
@ -673,10 +636,9 @@ impl Server {
|
|||||||
client,
|
client,
|
||||||
profile,
|
profile,
|
||||||
config.clone().unwrap_or_default(),
|
config.clone().unwrap_or_default(),
|
||||||
world.clone(),
|
&world,
|
||||||
gamemode,
|
gamemode,
|
||||||
)
|
);
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Some(mut nbt_data) = nbt {
|
if let Some(mut nbt_data) = nbt {
|
||||||
player.read_nbt(&mut nbt_data);
|
player.read_nbt(&mut nbt_data);
|
||||||
@ -1059,24 +1021,20 @@ impl Server {
|
|||||||
/// This includes player ticking (network, keep-alives) and flushing world updates to clients.
|
/// This includes player ticking (network, keep-alives) and flushing world updates to clients.
|
||||||
pub fn tick_players_and_network(self: &Arc<Self>) {
|
pub fn tick_players_and_network(self: &Arc<Self>) {
|
||||||
let worlds = self.worlds.load();
|
let worlds = self.worlds.load();
|
||||||
|
let handle = self.runtime.clone();
|
||||||
|
|
||||||
for world in worlds.iter() {
|
for world in worlds.iter() {
|
||||||
world.flush_block_updates();
|
world.flush_block_updates();
|
||||||
world.flush_synced_block_events();
|
world.flush_synced_block_events();
|
||||||
}
|
|
||||||
|
|
||||||
let mut all_players = Vec::new();
|
|
||||||
for world in worlds.iter() {
|
|
||||||
let players = world.players.load();
|
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();
|
||||||
let handle = self.runtime.clone();
|
|
||||||
all_players.par_iter().for_each(|player| {
|
|
||||||
let _guard = handle.enter();
|
|
||||||
player.tick(self);
|
player.tick(self);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Ticks the game logic for all worlds. This is the part that is affected by `/tick freeze`.
|
/// Ticks the game logic for all worlds. This is the part that is affected by `/tick freeze`.
|
||||||
pub fn tick_worlds(self: &Arc<Self>) {
|
pub fn tick_worlds(self: &Arc<Self>) {
|
||||||
|
|||||||
@ -162,7 +162,7 @@ impl TaskScheduler {
|
|||||||
let handler_id = task.handler_id;
|
let handler_id = task.handler_id;
|
||||||
let server_clone = server.clone();
|
let server_clone = server.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
server.spawn_task(async move {
|
||||||
let mut store = plugin.store.lock().await;
|
let mut store = plugin.store.lock().await;
|
||||||
match plugin.plugin_instance {
|
match plugin.plugin_instance {
|
||||||
crate::plugin::loader::wasm::wasm_host::PluginInstance::V0_1(ref 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
Loading…
Reference in New Issue
Block a user