Structures: begin adding Nether Fortress

This commit is contained in:
Alexander Medvedev 2025-08-18 17:59:14 +02:00
parent 398ba39233
commit 47254b14ba
30 changed files with 418 additions and 192 deletions

View File

@ -112,34 +112,27 @@ impl<V: Hash + Eq + Copy + Default, const DIM: usize> PalettedContainer<V, DIM>
debug_assert!(bits_per_entry >= encompassing_bits(data.counts.len()));
debug_assert!(bits_per_entry <= 15);
let palette: Box<[V]> = data.palette.iter().copied().collect();
let key_to_index_map: HashMap<V, usize> = palette
.iter()
.enumerate()
.map(|(index, key)| (*key, index))
.collect();
// Don't use HashMap's here, because its slow
let blocks_per_i64 = 64 / bits_per_entry;
let packed_indices = data
let packed_indices: Box<[i64]> = data
.cube
.as_flattened()
.as_flattened()
.chunks(blocks_per_i64 as usize)
.map(|chunk| {
chunk.iter().enumerate().fold(0, |acc, (index, key)| {
let key_index = key_to_index_map.get(key).unwrap();
debug_assert!((1 << bits_per_entry) > *key_index);
let key_index = data.palette.iter().position(|&x| x == *key).unwrap();
debug_assert!((1 << bits_per_entry) > key_index);
let packed_offset_index =
(*key_index as u64) << (bits_per_entry as u64 * index as u64);
(key_index as u64) << (bits_per_entry as u64 * index as u64);
acc | packed_offset_index as i64
})
})
.collect();
(palette, packed_indices)
(data.palette.clone().into_boxed_slice(), packed_indices)
}
}
}

View File

@ -737,6 +737,7 @@ impl<'a> ProtoChunk<'a> {
Xoroshiro::get_population_seed(self.random_config.seed, block_pos.0.x, block_pos.0.z);
for (_structure, (pos, stype)) in self.structure_starts.clone() {
dbg!("generating structure");
stype.generate(pos.clone(), self);
}

View File

@ -1,6 +1,7 @@
use pumpkin_util::math::position::BlockPos;
use pumpkin_data::BlockState;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use crate::ProtoChunk;
use crate::{ProtoChunk, generation::height_limit::HeightLimitView};
pub mod buried_treasure;
pub mod nether_fortress;
@ -11,6 +12,58 @@ pub trait StructureGenerator {
fn generate(&self, position: BlockPos, chunk: &mut crate::ProtoChunk);
}
#[expect(clippy::too_many_arguments)]
pub fn fill_with_outline(
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
outline: &BlockState,
inside: &BlockState,
chunk: &mut crate::ProtoChunk,
) {
for y in min_y..=max_y {
for x in min_x..=max_x {
for z in min_z..=max_z {
if y == min_y || y == max_y || x == min_x || x == max_x || z == min_z || z == max_z
{
chunk.set_block_state(&Vector3::new(x, y, z), outline);
} else {
chunk.set_block_state(&Vector3::new(x, y, z), inside);
}
}
}
}
}
#[expect(clippy::too_many_arguments)]
pub fn fill(
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
state: &BlockState,
chunk: &mut crate::ProtoChunk,
) {
for y in min_y..=max_y {
for x in min_x..=max_x {
for z in min_z..=max_z {
chunk.set_block_state(&Vector3::new(x, y, z), state);
}
}
}
}
pub fn fill_downwards(x: i32, y: i32, z: i32, state: &BlockState, chunk: &mut crate::ProtoChunk) {
for y in y..chunk.bottom_y() as i32 {
chunk.set_block_state(&Vector3::new(x, y, z), state);
}
}
#[derive(Clone)]
pub struct StructurePosition {
pub position: BlockPos,

View File

@ -1,11 +1,14 @@
use pumpkin_util::math::position::BlockPos;
use pumpkin_data::Block;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use serde::Deserialize;
use crate::{
ProtoChunk,
generation::{
positions::chunk_pos,
structure::structures::{StructureGenerator, StructurePiecesCollector, StructurePosition},
structure::structures::{
StructureGenerator, StructurePiecesCollector, StructurePosition, fill_with_outline,
},
},
};
@ -26,5 +29,119 @@ impl StructureGenerator for NetherFortressGenerator {
generator,
}
}
fn generate(&self, _position: BlockPos, _chunk: &mut crate::ProtoChunk) {}
fn generate(&self, position: BlockPos, chunk: &mut crate::ProtoChunk) {
BridgePlatform::generate(&BridgePlatform, position, chunk);
}
}
pub struct BridgePlatform;
impl BridgePlatform {
fn generate(&self, _position: BlockPos, chunk: &mut crate::ProtoChunk) {
super::fill(0, 2, 0, 6, 7, 7, Block::AIR.default_state, chunk);
super::fill(1, 0, 0, 5, 1, 7, Block::NETHER_BRICKS.default_state, chunk);
super::fill(1, 2, 1, 5, 2, 7, Block::NETHER_BRICKS.default_state, chunk);
super::fill(1, 3, 2, 5, 3, 7, Block::NETHER_BRICKS.default_state, chunk);
super::fill(1, 4, 3, 5, 4, 7, Block::NETHER_BRICKS.default_state, chunk);
super::fill(1, 2, 0, 1, 4, 2, Block::NETHER_BRICKS.default_state, chunk);
super::fill(5, 2, 0, 5, 4, 2, Block::NETHER_BRICKS.default_state, chunk);
super::fill(1, 5, 2, 1, 5, 3, Block::NETHER_BRICKS.default_state, chunk);
super::fill(5, 5, 2, 5, 5, 3, Block::NETHER_BRICKS.default_state, chunk);
super::fill(0, 5, 3, 0, 5, 8, Block::NETHER_BRICKS.default_state, chunk);
super::fill(6, 5, 3, 6, 5, 8, Block::NETHER_BRICKS.default_state, chunk);
super::fill(1, 5, 8, 5, 5, 8, Block::NETHER_BRICKS.default_state, chunk);
// TODO
let block_state = Block::NETHER_BRICK_FENCE.default_state;
let block_state2 = Block::NETHER_BRICK_FENCE.default_state;
chunk.set_block_state(
&Vector3::new(1, 6, 3),
Block::NETHER_BRICK_FENCE.default_state,
);
chunk.set_block_state(
&Vector3::new(5, 6, 3),
Block::NETHER_BRICK_FENCE.default_state,
);
chunk.set_block_state(
&Vector3::new(0, 6, 3),
Block::NETHER_BRICK_FENCE.default_state,
);
chunk.set_block_state(
&Vector3::new(6, 6, 3),
Block::NETHER_BRICK_FENCE.default_state,
);
fill_with_outline(0, 6, 4, 0, 6, 7, block_state2, block_state2, chunk);
fill_with_outline(6, 6, 4, 6, 6, 7, block_state2, block_state2, chunk);
chunk.set_block_state(
&Vector3::new(0, 6, 8),
Block::NETHER_BRICK_FENCE.default_state,
);
chunk.set_block_state(
&Vector3::new(6, 6, 8),
Block::NETHER_BRICK_FENCE.default_state,
);
fill_with_outline(1, 6, 8, 5, 6, 8, block_state, block_state, chunk);
chunk.set_block_state(
&Vector3::new(1, 7, 8),
Block::NETHER_BRICK_FENCE.default_state,
);
fill_with_outline(2, 7, 8, 4, 7, 8, block_state, block_state, chunk);
chunk.set_block_state(
&Vector3::new(5, 7, 8),
Block::NETHER_BRICK_FENCE.default_state,
);
chunk.set_block_state(
&Vector3::new(2, 8, 8),
Block::NETHER_BRICK_FENCE.default_state,
);
chunk.set_block_state(&Vector3::new(3, 8, 8), block_state);
chunk.set_block_state(
&Vector3::new(4, 8, 8),
Block::NETHER_BRICK_FENCE.default_state,
);
// Blaze Spawner
chunk.set_block_state(
&Vector3::new(3, 5, 5), // ?
Block::SPAWNER.default_state,
);
for x in 0..6 {
for z in 0..6 {
super::fill_downwards(x, -1, z, Block::NETHER_BRICKS.default_state, chunk);
}
}
}
}
pub struct SmallCorridor;
impl SmallCorridor {
fn generate(&self, _position: BlockPos, chunk: &mut crate::ProtoChunk) {
super::fill(0, 0, 0, 4, 1, 4, Block::NETHER_BRICKS.default_state, chunk);
super::fill(0, 2, 0, 4, 5, 4, Block::AIR.default_state, chunk);
// TODO
let block_state = Block::NETHER_BRICK_FENCE.default_state;
super::fill(0, 2, 0, 0, 5, 4, Block::NETHER_BRICKS.default_state, chunk);
super::fill(4, 2, 0, 4, 5, 4, Block::NETHER_BRICKS.default_state, chunk);
super::fill(0, 3, 1, 0, 4, 1, block_state, chunk);
super::fill(0, 3, 3, 0, 4, 3, block_state, chunk);
super::fill(4, 3, 1, 4, 4, 1, block_state, chunk);
super::fill(4, 3, 3, 4, 4, 3, block_state, chunk);
super::fill(0, 6, 0, 4, 6, 4, Block::NETHER_BRICKS.default_state, chunk);
for i in 0..=4 {
for j in 0..=4 {
super::fill_downwards(i, -1, j, Block::NETHER_BRICKS.default_state, chunk);
}
}
}
}

View File

@ -103,7 +103,8 @@ impl CandleCakeBlock {
#[async_trait]
impl BlockBehaviour for CandleCakeBlock {
async fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
match args.item_stack.lock().await.item.id {
let item_id = args.item_stack.lock().await.item.id;
match item_id {
id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => {
BlockActionResult::Pass
} // Item::FIRE_CHARGE | Item::FLINT_AND_STEEL

View File

@ -36,7 +36,7 @@ struct LocationExecutor;
struct EntityExecutor(bool);
async fn send_damage_result(
sender: &mut CommandSender,
sender: &CommandSender,
success: bool,
amount: f32,
target_name: TextComponent,

View File

@ -176,7 +176,7 @@ pub fn snbt_colorful_display(tag: &NbtTag, depth: usize) -> Result<TextComponent
NbtTag::IntArray(value) => {
let int_array_format = TextComponent::text("I").color_named(NamedColor::Red);
let mut content = TextComponent::text("[")
.add_child(int_array_format.clone())
.add_child(int_array_format)
.add_child(TextComponent::text("; "));
for (index, int) in value.iter().take(128).enumerate() {

View File

@ -45,7 +45,7 @@ struct Executor {
}
impl Executor {
async fn handle_query(&self, sender: &mut CommandSender, target: &Player, exp_type: ExpType) {
async fn handle_query(&self, sender: &CommandSender, target: &Player, exp_type: ExpType) {
match exp_type {
ExpType::Levels => {
let level = target.experience_level.load(Ordering::Relaxed);

View File

@ -93,7 +93,7 @@ impl CommandExecutor for AngleWorldSpawnExecutor {
}
async fn setworldspawn(
sender: &mut CommandSender,
sender: &CommandSender,
server: &Server,
block_pos: BlockPos,
yaw: f32,

View File

@ -56,7 +56,7 @@ struct TickExecutor(SubCommand);
impl TickExecutor {
async fn handle_query(
&self,
sender: &mut CommandSender,
sender: &CommandSender,
server: &crate::server::Server,
manager: &crate::server::tick_rate_manager::ServerTickRateManager,
) -> Result<(), CommandError> {
@ -102,7 +102,7 @@ impl TickExecutor {
}
async fn handle_non_sprinting_status(
&self,
sender: &mut CommandSender,
sender: &CommandSender,
manager: &crate::server::tick_rate_manager::ServerTickRateManager,
avg_tick_nanos: i64,
) {
@ -121,7 +121,7 @@ impl TickExecutor {
}
}
async fn send_percentiles(&self, sender: &mut CommandSender, server: &crate::server::Server) {
async fn send_percentiles(&self, sender: &CommandSender, server: &crate::server::Server) {
let tick_count = server.tick_count.load(Ordering::Relaxed);
let sample_size = (tick_count as usize).min(100);
@ -149,7 +149,7 @@ impl TickExecutor {
}
async fn handle_step_command(
&self,
sender: &mut CommandSender,
sender: &CommandSender,
server: &crate::server::Server,
manager: &crate::server::tick_rate_manager::ServerTickRateManager,
ticks: i32,
@ -173,7 +173,7 @@ impl TickExecutor {
}
async fn handle_sprint_command(
&self,
sender: &mut CommandSender,
sender: &CommandSender,
server: &crate::server::Server,
manager: &crate::server::tick_rate_manager::ServerTickRateManager,
ticks: i32,

View File

@ -83,7 +83,7 @@ impl CommandDispatcher {
/// - do not query suggestions for the same consumer multiple times just because they are on different paths through the tree
pub(crate) async fn find_suggestions<'a>(
&'a self,
src: &mut CommandSender,
src: &CommandSender,
server: &'a Server,
cmd: &'a str,
) -> Vec<CommandSuggestion> {
@ -357,7 +357,7 @@ impl CommandDispatcher {
}
async fn try_find_suggestions_on_path<'a>(
src: &mut CommandSender,
src: &CommandSender,
server: &'a Server,
path: &[usize],
tree: &'a CommandTree,

View File

@ -83,7 +83,7 @@ mod format {
date: &OffsetDateTime,
serializer: S,
) -> Result<S::Ok, S::Error> {
let s = date.format(DATE_FORMAT).unwrap().to_string();
let s = date.format(DATE_FORMAT).unwrap();
serializer.serialize_str(&s)
}
@ -107,7 +107,7 @@ mod format {
serializer: S,
) -> Result<S::Ok, S::Error> {
if let Some(date) = date {
let s = date.format(DATE_FORMAT).unwrap().to_string();
let s = date.format(DATE_FORMAT).unwrap();
serializer.serialize_str(&s)
} else {
serializer.serialize_str("forever")

View File

@ -295,7 +295,7 @@ mod test {
let path = temp_dir.path().to_path_buf();
let uuid = Uuid::new_v4();
let mut storage = PlayerDataStorage::new(path.clone());
let mut storage = PlayerDataStorage::new(path);
storage.set_save_enabled(true);
// Create and save player data

View File

@ -164,14 +164,15 @@ impl LivingEntity {
}
pub async fn add_effect(&self, effect: Effect) {
let mut effects = self.active_effects.lock().await;
effects.insert(effect.effect_type, effect);
self.active_effects
.lock()
.await
.insert(effect.effect_type, effect);
// TODO broadcast metadata
}
pub async fn remove_effect(&self, effect_type: &'static StatusEffect) {
let mut effects = self.active_effects.lock().await;
effects.remove(&effect_type);
self.active_effects.lock().await.remove(&effect_type);
self.entity
.world
.read()

View File

@ -757,8 +757,6 @@ impl Entity {
let max = aabb.max_block_pos();
let world = self.world.read().await;
let mut eye_level_box = aabb;
let eye_height = f64::from(self.standing_eye_height);
@ -767,6 +765,7 @@ impl Entity {
eye_level_box.max.y = eye_level_box.min.y;
let world = self.world.read().await;
for x in min.0.x..=max.0.x {
for y in min.0.y..=max.0.y {
for z in min.0.z..=max.0.z {
@ -1126,9 +1125,12 @@ impl Entity {
#[allow(clippy::float_cmp)]
async fn get_velocity_multiplier(&self) -> f32 {
let world = self.world.read().await;
let block = world.get_block(&self.block_pos.load()).await;
let block = self
.world
.read()
.await
.get_block(&self.block_pos.load())
.await;
let multiplier = block.velocity_multiplier;
@ -1143,9 +1145,10 @@ impl Entity {
#[allow(clippy::float_cmp)]
async fn get_jump_velocity_multiplier(&self) -> f32 {
let world = self.world.read().await;
let f = world
let f = self
.world
.read()
.await
.get_block(&self.block_pos.load())
.await
.jump_velocity_multiplier;
@ -1302,6 +1305,7 @@ impl Entity {
portal_manager.portal_world.clone(),
)
.await;
drop(portal_manager);
} else if portal_manager.ticks_in_portal == 0 {
should_remove = true;
}

View File

@ -1075,8 +1075,7 @@ impl Player {
pitch: Option<f32>,
) {
let current_world = self.living_entity.entity.world.read().await.clone();
let info = &new_world.level_info.read().await;
let yaw = yaw.unwrap_or(info.spawn_angle);
let yaw = yaw.unwrap_or(new_world.level_info.read().await.spawn_angle);
let pitch = pitch.unwrap_or(10.0);
send_cancellable! {{
@ -1212,8 +1211,7 @@ impl Player {
}
pub async fn add_exhaustion(&self, exhaustion: f32) {
let abilities = self.abilities.lock().await;
if abilities.invulnerable {
if self.abilities.lock().await.invulnerable {
return;
}
self.hunger_manager.add_exhaustion(exhaustion);
@ -1456,7 +1454,7 @@ impl Player {
pub async fn drop_held_item(&self, drop_stack: bool) {
// should be locked first otherwise cause deadlock in tick() (this thread lock stack, that thread lock screen_handler)
let screen_binding = self.current_screen_handler.lock().await;
let binding = self.inventory.held_item();
let mut item_stack = binding.lock().await;
@ -1467,6 +1465,7 @@ impl Player {
item_stack.decrement(drop_amount);
let selected_slot = self.inventory.get_selected_slot();
let inv: Arc<dyn Inventory> = self.inventory.clone();
let screen_binding = self.current_screen_handler.lock().await;
let mut screen_handler = screen_binding.lock().await;
let slot_index = screen_handler
.get_slot_index(&inv, selected_slot as usize)
@ -1838,6 +1837,7 @@ impl Player {
screen_handler.update_to_client().await;
} else {
screen_handler.send_content_updates().await;
drop(screen_handler);
}
}
@ -1963,18 +1963,19 @@ impl NBTStorage for PlayerInventory {
let mut item_compound = NbtCompound::new();
item_compound.put_byte("Slot", i as i8);
stack.write_item_stack(&mut item_compound);
drop(stack);
vec.push(NbtTag::Compound(item_compound));
}
}
let mut equipment_compound = NbtCompound::new();
for slot in self.equipment_slots.values() {
let equipment_binding = self.entity_equipment.lock().await;
let stack_binding = equipment_binding.get(slot);
let stack_binding = self.entity_equipment.lock().await.get(slot);
let stack = stack_binding.lock().await;
if !stack.is_empty() {
let mut item_compound = NbtCompound::new();
stack.write_item_stack(&mut item_compound);
drop(stack);
match slot {
EquipmentSlot::OffHand(_) => {
equipment_compound.put_component("offhand", item_compound);
@ -2067,8 +2068,12 @@ impl EntityBase for Player {
if self.abilities.lock().await.invulnerable && damage_type != DamageType::GENERIC_KILL {
return false;
}
let world = self.living_entity.entity.world.read().await;
let dyn_self = world
let dyn_self = self
.living_entity
.entity
.world
.read()
.await
.get_entity_by_id(self.living_entity.entity.entity_id)
.await
.expect("Entity not found in world");
@ -2380,14 +2385,14 @@ impl LastSeen {
pub async fn indexed_for(&self, recipient: &Arc<Player>) -> Box<[PreviousMessage]> {
let mut indexed = Vec::new();
for signature in &self.0 {
if let Some(index) = recipient
let index = recipient
.signature_cache
.lock()
.await
.full_cache
.iter()
.position(|s| s == signature)
{
.position(|s| s == signature);
if let Some(index) = index {
indexed.push(PreviousMessage {
// Send ID reference to recipient's cache (index + 1 because 0 is reserved for full signature)
id: VarInt(1 + index as i32),

View File

@ -190,6 +190,9 @@ impl PumpkinServer {
}
if rcon.enabled {
log::warn!(
"RCON is enabled, but it's highly insecure as it transmits passwords and commands in plain text. This makes it vulnerable to interception and exploitation by anyone on the network"
);
let rcon_server = server.clone();
server.spawn_task(async move {
RCONServer::run(&rcon, rcon_server).await.unwrap();

View File

@ -15,6 +15,12 @@
#![deny(clippy::branches_sharing_code)]
#![deny(clippy::equatable_if_let)]
#![deny(clippy::option_if_let_else)]
#![deny(clippy::needless_pass_by_ref_mut)]
#![deny(clippy::needless_collect)]
#![deny(clippy::redundant_clone)]
#![deny(clippy::branches_sharing_code)]
#![deny(clippy::set_contains_or_insert)]
#![deny(clippy::significant_drop_in_scrutinee)]
// use log crate
#![deny(clippy::print_stdout)]
#![deny(clippy::print_stderr)]

View File

@ -387,7 +387,7 @@ impl BedrockClient {
self.tasks.wait().await;
self.be_clients.lock().await.remove(&self.address);
if let Some(player) = self.player.lock().await.clone() {
if let Some(player) = self.player.lock().await.as_ref() {
player.remove().await;
}
}

View File

@ -106,7 +106,6 @@ impl BedrockClient {
);
let entity = &player.living_entity.entity;
let world = &entity.world.read().await;
if BASIC_CONFIG.allow_chat_reports {
//TODO Alex help, what is this?
//world.broadcast_secure_player_chat(player, &message, decorated_message).await;
@ -120,7 +119,7 @@ impl BedrockClient {
message, gameprofile.name.clone()
);
world.broadcast_editioned(&je_packet, &be_packet).await;
entity.world.read().await.broadcast_editioned(&je_packet, &be_packet).await;
}
}
}}

View File

@ -327,6 +327,7 @@ impl JavaClient {
self.finish_login(&profile).await;
*self.gameprofile.lock().await = Some(profile);
*address = new_address;
drop(address);
}
Err(error) => self.kick(TextComponent::text(error.to_string())).await,
}

View File

@ -342,8 +342,7 @@ impl JavaClient {
}
ConnectionState::Config => self.handle_config_packet(server, packet).await,
ConnectionState::Play => {
let player = self.player.lock().await;
if let Some(player) = player.as_ref() {
if let Some(player) = self.player.lock().await.as_ref() {
match self.handle_play_packet(player, server, packet).await {
Ok(()) => {}
Err(e) => {

View File

@ -183,6 +183,7 @@ impl JavaClient {
player.living_entity.entity.set_pos(*position);
*awaiting_teleport = None;
drop(awaiting_teleport);
} else {
self.kick(TextComponent::text("Wrong teleport id")).await;
}
@ -542,8 +543,7 @@ impl JavaClient {
// Some commands can take a long time to execute. If they do, they block packet processing for the player.
// That's why we will spawn a task instead.
server.spawn_task(async move {
let dispatcher = server_clone.command_dispatcher.read().await;
dispatcher
server_clone.command_dispatcher.read().await
.handle_command(
&mut CommandSender::Player(player_clone),
&server_clone,
@ -888,10 +888,7 @@ impl JavaClient {
}
// Update the chat session fields
let mut chat_session = player.chat_session.lock().await; // Await the lock
// Update the chat session fields
*chat_session = ChatSession::new(
*player.chat_session.lock().await = ChatSession::new(
session.session_id,
session.expires_at,
session.public_key.clone(),
@ -1246,8 +1243,10 @@ impl JavaClient {
}
player.mining.store(false, Ordering::Relaxed);
let entity = &player.living_entity.entity;
let world = &entity.world.read().await;
world
entity
.world
.read()
.await
.set_block_breaking(entity, player_action.position, -1)
.await;
self.update_sequence(player, player_action.sequence.0);
@ -1272,14 +1271,14 @@ impl JavaClient {
world.set_block_breaking(entity, location, -1).await;
let (block, state) = world.get_block_and_state(&location).await;
let drop = player.gamemode.load() != GameMode::Creative
let block_drop = player.gamemode.load() != GameMode::Creative
&& player.can_harvest(state, block).await;
world
.break_block(
&location,
Some(player.clone()),
if drop {
if block_drop {
BlockFlags::NOTIFY_NEIGHBORS
} else {
BlockFlags::SKIP_DROPS | BlockFlags::NOTIFY_NEIGHBORS
@ -1448,14 +1447,16 @@ impl JavaClient {
.await;
// Check if the item is a block, because not every item can be placed :D
if let Some(block) = Block::from_item_id(item.lock().await.item.id) {
let item_id = item.lock().await.item.id;
if let Some(block) = Block::from_item_id(item_id) {
should_try_decrement = self
.run_is_block_place(player, block, server, use_item_on, position, face)
.await?;
}
// Check if the item is a spawn egg
if let Some(entity) = entity_from_egg(item.lock().await.item.id) {
let item_id = item.lock().await.item.id;
if let Some(entity) = entity_from_egg(item_id) {
self.spawn_entity_from_egg(player, entity, position, face)
.await;
should_try_decrement = true;
@ -1655,6 +1656,7 @@ impl JavaClient {
.await;
player_screen_handler.set_received_stack(packet.slot as usize, item_stack);
player_screen_handler.send_content_updates().await;
drop(player_screen_handler);
} else if is_negative && is_legal {
// Item drop
player.drop_item(item_stack).await;
@ -1663,8 +1665,11 @@ impl JavaClient {
}
pub async fn handle_chunk_batch(&self, player: &Player, packet: SChunkBatch) {
let mut chunk_manager = player.chunk_manager.lock().await;
chunk_manager.handle_acknowledge(packet.chunks_per_tick);
player
.chunk_manager
.lock()
.await
.handle_acknowledge(packet.chunks_per_tick);
log::trace!(
"Client requested {} chunks per tick",
packet.chunks_per_tick
@ -1686,7 +1691,7 @@ impl JavaClient {
packet: SCommandSuggestion,
server: &Arc<Server>,
) {
let mut src = CommandSender::Player(player.clone());
let src = CommandSender::Player(player.clone());
let Some(cmd) = &packet.command.get(1..) else {
return;
};
@ -1696,8 +1701,12 @@ impl JavaClient {
return;
};
let dispatcher = server.command_dispatcher.read().await;
let suggestions = dispatcher.find_suggestions(&mut src, server, cmd).await;
let suggestions = server
.command_dispatcher
.read()
.await
.find_suggestions(&src, server, cmd)
.await;
let response = CCommandSuggestions::new(
packet.id,
@ -1762,7 +1771,6 @@ impl JavaClient {
face: BlockDirection,
) -> Result<bool, BlockPlacingError> {
let entity = &player.living_entity.entity;
let world = &entity.world.read().await;
// Check if the block is under the world
if location.0.y + face.to_offset().y < i32::from(Self::WORLD_LOWEST_Y) {
@ -1792,6 +1800,8 @@ impl JavaClient {
}
let clicked_block_pos = BlockPos(location.0);
let world = &entity.world.read().await;
let (clicked_block, clicked_block_state) =
world.get_block_and_state(&clicked_block_pos).await;

View File

@ -194,7 +194,7 @@ pub async fn can_not_join(
Some(expires) => text.add_child(TextComponent::translate(
"multiplayer.disconnect.banned.expiration",
[TextComponent::text(
expires.format(FORMAT_DESCRIPTION).unwrap().to_string(),
expires.format(FORMAT_DESCRIPTION).unwrap(),
)],
)),
None => text,
@ -214,8 +214,7 @@ pub async fn can_not_join(
}
}
let mut banned_ips = BANNED_IP_LIST.write().await;
if let Some(entry) = banned_ips.get_entry(&address.ip()) {
if let Some(entry) = BANNED_IP_LIST.write().await.get_entry(&address.ip()) {
let text = TextComponent::translate(
"multiplayer.disconnect.banned_ip.reason",
[TextComponent::text(entry.reason.clone())],
@ -224,7 +223,7 @@ pub async fn can_not_join(
Some(expires) => text.add_child(TextComponent::translate(
"multiplayer.disconnect.banned_ip.expiration",
[TextComponent::text(
expires.format(FORMAT_DESCRIPTION).unwrap().to_string(),
expires.format(FORMAT_DESCRIPTION).unwrap(),
)],
)),
None => text,

View File

@ -126,8 +126,10 @@ impl RCONClient {
let output_clone = output.clone();
let packet_body = packet.get_body().to_owned();
tokio::spawn(async move {
let dispatcher = server_clone.command_dispatcher.read().await;
dispatcher
server_clone
.command_dispatcher
.read()
.await
.handle_command(
&mut crate::command::CommandSender::Rcon(output_clone),
&server_clone,

View File

@ -187,9 +187,8 @@ impl Context {
));
}
let manager = self.permission_manager.read().await;
let mut registry = manager.registry.write().await;
registry.register_permission(permission)
let registry = &self.permission_manager.read().await.registry;
registry.write().await.register_permission(permission)
}
/// Check if a player has a permission

View File

@ -302,16 +302,13 @@ impl PluginManager {
}
/// Start loading a plugin asynchronously
#[allow(clippy::too_many_lines)]
async fn start_loading_plugin(
&self,
path: &Path,
) -> Result<tokio::task::JoinHandle<()>, ManagerError> {
let loaders = self.loaders.read().await;
for loader in loaders.iter() {
for loader in self.loaders.read().await.iter() {
if loader.can_load(path) {
let server = self.server.read().await;
let self_ref = self.self_ref.read().await;
let (mut instance, metadata, loader_data) = loader.load(path).await?;
// Mark plugin as loading
@ -320,15 +317,25 @@ impl PluginManager {
.await
.insert(metadata.name.to_string(), PluginState::Loading);
let self_ref = self_ref
.as_ref()
let self_ref = self
.self_ref
.read()
.await
.clone()
.ok_or(ManagerError::ServerNotInitialized)?;
let context = Arc::new(Context::new(
metadata.clone(),
Arc::clone(server.as_ref().ok_or(ManagerError::ServerNotInitialized)?),
Arc::clone(
&self
.server
.read()
.await
.clone()
.ok_or(ManagerError::ServerNotInitialized)?,
),
Arc::clone(&self.handlers),
Arc::clone(self_ref),
Arc::clone(&self_ref),
Arc::clone(&PERMISSION_MANAGER),
));
@ -352,7 +359,7 @@ impl PluginManager {
self.unloaded_files.write().await.remove(path);
// Spawn async task for plugin initialization
let self_ref_clone = Arc::clone(self_ref);
let self_ref_clone = Arc::clone(&self_ref);
let state_notify = Arc::clone(&self.state_notify);
let plugin_name = metadata.name.to_string();
let loader_clone = loader.clone();
@ -447,7 +454,7 @@ impl PluginManager {
PluginState::Loaded => return Ok(()),
PluginState::Failed(error) => {
return Err(ManagerError::LoaderError(
LoaderError::InitializationFailed(error.clone()),
LoaderError::InitializationFailed(error),
));
}
PluginState::Loading => {

View File

@ -213,10 +213,10 @@ impl Server {
log::info!("Loading End: {seed}");
let end = World::load(
Dimension::End.into_level(world_path.clone(), block_registry.clone(), seed),
level_info.clone(),
level_info,
VanillaDimensionType::TheEnd,
block_registry.clone(),
weak.clone(),
block_registry,
weak,
);
*server
.worlds
@ -239,13 +239,14 @@ impl Server {
pub async fn get_world_from_dimension(&self, dimension: VanillaDimensionType) -> Arc<World> {
// TODO: this is really bad
let world_guard = self.worlds.read().await;
let world = match dimension {
match dimension {
VanillaDimensionType::Overworld => world_guard.first(),
VanillaDimensionType::OverworldCaves => todo!(),
VanillaDimensionType::TheEnd => world_guard.get(2),
VanillaDimensionType::TheNether => world_guard.get(1),
};
world.cloned().unwrap()
}
.cloned()
.unwrap()
}
#[allow(clippy::if_then_some_else_none)]
@ -291,27 +292,36 @@ impl Server {
(world, Some(data))
} else {
log::warn!("Invalid dimension key in player data: {dimension_key}");
let default_world_guard = self.worlds.read().await;
let default_world = default_world_guard
let default_world = self
.worlds
.read()
.await
.first()
.expect("Default world should exist");
(default_world.clone(), Some(data))
.expect("Default world should exist")
.clone();
(default_world, Some(data))
}
} else {
// Player data exists but doesn't have a "Dimension" key.
let default_world_guard = self.worlds.read().await;
let default_world = default_world_guard
let default_world = self
.worlds
.read()
.await
.first()
.expect("Default world should exist");
(default_world.clone(), Some(data))
.expect("Default world should exist")
.clone();
(default_world, Some(data))
}
} else {
// No player data found or an error occurred, default to the Overworld.
let default_world_guard = self.worlds.read().await;
let default_world = default_world_guard
let default_world = self
.worlds
.read()
.await
.first()
.expect("Default world should exist");
(default_world.clone(), None)
.expect("Default world should exist")
.clone();
(default_world, None)
};
let mut player = Player::new(
@ -457,6 +467,7 @@ impl Server {
level_info.difficulty_locked,
))
.await;
drop(level_info);
}
}
@ -708,15 +719,7 @@ impl Server {
EntitySelectorType::AllEntities => {
let mut entities = Vec::new();
for world in self.worlds.read().await.iter() {
entities.extend(
world
.entities
.read()
.await
.values()
.cloned()
.collect::<Vec<_>>(),
);
entities.extend(world.entities.read().await.values().cloned());
entities.extend(
world
.players
@ -724,8 +727,7 @@ impl Server {
.await
.values()
.cloned()
.map(|p| p as Arc<dyn EntityBase>)
.collect::<Vec<_>>(),
.map(|p| p as Arc<dyn EntityBase>),
);
}
entities.into_iter()

View File

@ -283,6 +283,7 @@ impl World {
chunk.mark_dirty(true);
if old_chunk == current_chunk_coordinate {
chunk.data.remove(&entity.entity_uuid);
drop(chunk);
} else {
let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await;
let mut chunk = chunk.write().await;
@ -433,10 +434,13 @@ impl World {
.await
.add_seen_signature(&chat_message.signature.clone().unwrap()); // Unwrap is safe because we check for None in validate_chat_message
let recipient_signature_cache = &mut recipient.signature_cache.lock().await;
if recipient.gameprofile.id != sender.gameprofile.id {
// Sender may update recipient on signatures recipient hasn't seen
recipient_signature_cache.cache_signatures(sender_last_seen.as_ref());
recipient
.signature_cache
.lock()
.await
.cache_signatures(sender_last_seen.as_ref());
}
recipient.chat_session.lock().await.messages_received += 1;
}
@ -587,9 +591,11 @@ impl World {
if weather.weather_cycle_enabled && (weather.raining || weather.thundering) {
weather.reset_weather_cycle(self).await;
drop(weather);
}
} else if level_time.world_age % 20 == 0 {
level_time.send_time(self).await;
drop(level_time);
}
}
@ -1246,6 +1252,8 @@ impl World {
scenario_id: String::new(),
owner_id: String::new(),
};
drop(level_info);
drop(weather);
let client = player.client.bedrock();
client
@ -1436,9 +1444,7 @@ impl World {
f64::from(info.spawn_z) + 0.5,
);
let yaw = info.spawn_angle;
let pitch = 0.0;
(position, yaw, pitch)
(position, yaw, 0.0) // Pitch
};
let velocity = player.living_entity.entity.velocity.load();
@ -1470,11 +1476,9 @@ impl World {
}],
))
.await;
let current_players = self.players.read().await;
// Here, we send all the infos of players who already joined.
{
let current_players = self.players.read().await;
let mut current_player_data = Vec::new();
for (_, player) in current_players
@ -1499,6 +1503,7 @@ impl World {
signature: chat_session.signature.clone(),
})));
}
drop(chat_session);
current_player_data.push((&player.gameprofile.id, player_actions));
}
@ -1544,7 +1549,7 @@ impl World {
// Spawn players for our client.
let id = player.gameprofile.id;
for (_, existing_player) in self.players.read().await.iter().filter(|c| c.0 != &id) {
for (_, existing_player) in current_players.iter().filter(|c| c.0 != &id) {
let entity = &existing_player.living_entity.entity;
let pos = entity.pos.load();
let gameprofile = &existing_player.gameprofile;
@ -1563,34 +1568,37 @@ impl World {
entity.velocity.load(),
))
.await;
let config = existing_player.config.read().await;
let mut buf = Vec::new();
for meta in [
Metadata::new(
DATA_PLAYER_MODE_CUSTOMISATION,
MetaDataType::Byte,
config.skin_parts,
),
Metadata::new(
DATA_PLAYER_MAIN_HAND,
MetaDataType::Byte,
config.main_hand as u8,
),
] {
let mut serializer_buf = Vec::new();
{
let config = existing_player.config.read().await;
let mut buf = Vec::new();
for meta in [
Metadata::new(
DATA_PLAYER_MODE_CUSTOMISATION,
MetaDataType::Byte,
config.skin_parts,
),
Metadata::new(
DATA_PLAYER_MAIN_HAND,
MetaDataType::Byte,
config.main_hand as u8,
),
] {
let mut serializer_buf = Vec::new();
let mut serializer = Serializer::new(&mut serializer_buf);
meta.serialize(&mut serializer).unwrap();
buf.extend(serializer_buf);
let mut serializer = Serializer::new(&mut serializer_buf);
meta.serialize(&mut serializer).unwrap();
buf.extend(serializer_buf);
}
drop(config);
// END
buf.put_u8(255);
client
.enqueue_packet(&CSetEntityMetadata::new(
existing_player.get_entity().entity_id.into(),
buf.into(),
))
.await;
}
// END
buf.put_u8(255);
client
.enqueue_packet(&CSetEntityMetadata::new(
existing_player.get_entity().entity_id.into(),
buf.into(),
))
.await;
}
player.send_client_information().await;
@ -1622,6 +1630,7 @@ impl World {
// Calculate rain and thunder levels directly from public fields
let rain_level = weather.rain_level.clamp(0.0, 1.0);
let thunder_level = weather.thunder_level.clamp(0.0, 1.0);
drop(weather);
client
.enqueue_packet(&CGameEvent::new(GameEvent::RainLevelChange, rain_level))
@ -1658,8 +1667,7 @@ impl World {
));
for (slot, item_arc_mutex) in &from.inventory.entity_equipment.lock().await.equipment {
let item_guard = item_arc_mutex.lock().await;
let item_stack = item_guard.clone();
let item_stack = item_arc_mutex.lock().await.clone();
equipment_list.push((slot.discriminant(), item_stack));
}
@ -1829,6 +1837,7 @@ impl World {
.is_some_and(|since| since >= 100)
})
.count();
drop(players);
// TODO: sleep ratio
sleeping_player_count == player_count
@ -1943,8 +1952,8 @@ impl World {
});
let mut entity_receiver = self.level.receive_entity_chunks(chunks);
let level = self.level.clone();
let player = player1.clone();
let world = world1.clone();
let player = player1;
let world = world1;
player.clone().spawn_task(async move {
'main: loop {
let recv_result = tokio::select! {
@ -1971,9 +1980,8 @@ impl World {
);
let mut ids = Vec::new();
// Remove all the entities from the world
let entity_chunk = chunk.read().await;
let mut entities = world.entities.write().await;
for (uuid, entity_nbt) in &entity_chunk.data {
for (uuid, entity_nbt) in &chunk.read().await.data {
let Some(id) = entity_nbt.get_string("id") else {
log::warn!("Entity has no ID");
continue;
@ -2273,9 +2281,7 @@ impl World {
let event = PLUGIN_MANAGER.fire(event).await;
if !event.cancelled {
let current_players = current_players.clone();
let players = current_players.read().await;
for player in players.values() {
for player in current_players.read().await.values() {
player.send_system_message(&event.join_message).await;
}
log::info!("{}", event.join_message.to_pretty_console());
@ -2330,6 +2336,7 @@ impl World {
for player in players.values() {
player.send_system_message(&event.leave_message).await;
}
drop(players);
log::info!("{}", event.leave_message.to_pretty_console());
}
}
@ -2352,6 +2359,7 @@ impl World {
entity.write_nbt(&mut nbt).await;
chunk.data.insert(base_entity.entity_uuid, nbt);
chunk.mark_dirty(true);
drop(chunk);
let mut current_entities = self.entities.write().await;
current_entities.insert(base_entity.entity_uuid, entity);

View File

@ -9,6 +9,7 @@ use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::WorldgenBiome::MINECRAFT_REDUCE_WATER_AMBIENT_SPAWNS;
use pumpkin_data::{Block, BlockDirection, BlockState};
use pumpkin_util::GameMode;
use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions};
use pumpkin_util::math::get_section_cord;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2;
@ -505,14 +506,34 @@ pub async fn is_valid_spawn_position_for_type(
distance: f64,
) -> bool {
// TODO !SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random)
// TODO level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5))
!(category == &MobCategory::MISC
|| (!entity_type.can_spawn_far_from_player
&& distance
> f64::from(entity_type.category.despawn_distance)
* f64::from(entity_type.category.despawn_distance))
|| !entity_type.summonable
|| !is_spawn_position_ok(world, block_pos, entity_type).await)
if category == &MobCategory::MISC {
return false;
}
if !entity_type.can_spawn_far_from_player
&& distance
> f64::from(entity_type.category.despawn_distance)
* f64::from(entity_type.category.despawn_distance)
{
return false;
}
if !entity_type.summonable {
return false;
}
if !is_spawn_position_ok(world, block_pos, entity_type).await {
return false;
}
// TODO: we should use getSpawnBox, but this is only modified for slimes and magma slimes
world
.is_space_empty(BoundingBox::new_from_pos(
f64::from(block_pos.0.x) + 0.5,
f64::from(block_pos.0.y),
f64::from(block_pos.0.z) + 0.5,
&EntityDimensions {
width: entity_type.dimension[0],
height: entity_type.dimension[1],
},
))
.await
}
pub async fn is_spawn_position_ok(
@ -536,6 +557,7 @@ pub async fn is_spawn_position_ok(
let down = world.get_block_state(&block_pos.down()).await;
let up = world.get_block_state(&block_pos.up()).await;
let cur = world.get_block_state(block_pos).await;
// TODO: blockState.allowsSpawning
if down.is_side_solid(BlockDirection::Up) {
is_valid_empty_spawn_block(cur) && is_valid_empty_spawn_block(up)
} else {
@ -548,16 +570,10 @@ pub async fn is_spawn_position_ok(
#[must_use]
pub fn is_valid_empty_spawn_block(state: &'static BlockState) -> bool {
if state.is_solid() {
false
} else if false {
// TODO isSignalSource
false
} else if state.is_liquid() {
false
} else {
// TODO !entityType.isBlockDangerous(blockState);
!Block::from_state_id(state.id)
.is_tagged_with_by_tag(&MINECRAFT_PREVENT_MOB_SPAWNING_INSIDE)
// TODO: emitsRedstonePower
if state.is_full_cube() || state.is_liquid() {
return false;
}
// TODO !entityType.isBlockDangerous(blockState);
!Block::from_state_id(state.id).is_tagged_with_by_tag(&MINECRAFT_PREVENT_MOB_SPAWNING_INSIDE)
}