chore: more fixes

This commit is contained in:
Alexander Medvedev 2026-08-28 13:31:28 +02:00
parent 37437ee0a0
commit 6eb02f65b9
166 changed files with 1681 additions and 1520 deletions

29
Cargo.lock generated
View File

@ -1668,12 +1668,13 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "flate2"
version = "1.1.9"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
dependencies = [
"crc32fast",
"miniz_oxide",
"miniz_oxide 0.9.1",
"zlib-rs",
]
[[package]]
@ -2591,6 +2592,16 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "miniz_oxide"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "mio"
version = "1.2.2"
@ -3082,7 +3093,7 @@ dependencies = [
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
"miniz_oxide 0.8.9",
]
[[package]]
@ -5172,9 +5183,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.25.0"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc"
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
dependencies = [
"getrandom 0.4.3",
"js-sys",
@ -6342,6 +6353,12 @@ dependencies = [
"syn 3.0.4",
]
[[package]]
name = "zlib-rs"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
[[package]]
name = "zmij"
version = "1.0.23"

View File

@ -135,7 +135,7 @@ futures = { version = "0.3", default-features = false, features = ["std"] }
rayon = { version = "1.12", default-features = false }
crossbeam = { version = "0.8", default-features = false, features = ["std"] }
uuid = { version = "1.25", default-features = false, features = ["serde", "v3", "v4", "std"] }
uuid = { version = "1.26", default-features = false, features = ["serde", "v3", "v4", "std"] }
serde = { version = "1.0", default-features = false, features = ["derive", "std"] }
serde_repr = { version = "0.1", default-features = false }
serde_json = { version = "1.0", default-features = false, features = ["std"] }
@ -156,13 +156,13 @@ crossbeam-utils = { version = "0.8.22", default-features = false, features = ["s
crypto-bigint = { version = "0.7.5", default-features = false }
dashmap = { version = "6.2", default-features = false }
ecdsa = { version = "0.17.0", default-features = false, features = ["std"] }
flate2 = { version = "1.1.9", default-features = false, features = ["rust_backend"] }
flate2 = { version = "1.1.10", default-features = false, features = ["rust_backend"] }
heck = { version = "0.5", default-features = false }
hmac = { version = "=0.13.0", default-features = false }
indexmap = { version = "2.14", default-features = false, features = ["std"] }
itertools = { version = "0.15.0", default-features = false, features = ["use_std"] }
libloading = { version = "0.9", default-features = false, features = ["std"] }
lru = { version = "0.18.2", default-features = false }
lru = { version = "0.18.3", default-features = false }
lz4-java-wrc = { version = "0.2.0", default-features = false, features = ["lz4_flex"] }
md5 = { version = "0.8", default-features = false }
num-bigint = { version = "0.5", default-features = false, features = ["std"] }

View File

@ -114,7 +114,7 @@ impl EnchantingTableScreenHandler {
let mut random = LegacyRand::from_seed(self.enchantment_seed as u64);
for i in 0..3 {
let level = self.calculate_level_requirement(&mut random, i, enchantability);
let level = self.calculate_level_requirement(&mut random, i);
self.level_requirements[i] = level;
}
@ -124,7 +124,6 @@ impl EnchantingTableScreenHandler {
let enchantments = Self::get_enchantment_list(
&mut random,
&item,
i,
self.level_requirements[i],
);
if enchantments.is_empty() {
@ -161,12 +160,7 @@ impl EnchantingTableScreenHandler {
self.send_property_updates();
}
fn calculate_level_requirement(
&self,
random: &mut LegacyRand,
slot: usize,
_enchantability: i32,
) -> i32 {
fn calculate_level_requirement(&self, random: &mut LegacyRand, slot: usize) -> i32 {
let b = self.bookshelf_count;
let level = random.next_bounded_i32(8) + 1 + (b >> 1) + random.next_bounded_i32(b + 1);
@ -185,7 +179,6 @@ impl EnchantingTableScreenHandler {
fn get_enchantment_list(
random: &mut LegacyRand,
item: &ItemStack,
_slot: usize,
level: i32,
) -> Vec<(&'static Enchantment, i32)> {
let enchantability = item
@ -363,8 +356,7 @@ impl ScreenHandler for EnchantingTableScreenHandler {
}
let mut random = self.create_enchantment_random(id as usize);
let mut enchantments =
Self::get_enchantment_list(&mut random, &item_stack, id as usize, level_req);
let mut enchantments = Self::get_enchantment_list(&mut random, &item_stack, level_req);
if enchantments.is_empty() {
return false;

View File

@ -561,7 +561,7 @@ pub trait ScreenHandler: Send + Sync {
.previous_cursor_stack
.set_received_stack(cursor_stack.clone());
if let Some(sync_handler) = behaviour.sync_handler.as_ref() {
sync_handler.update_cursor_stack(behaviour, &cursor_stack);
sync_handler.update_cursor_stack(&cursor_stack);
}
}
}

View File

@ -162,9 +162,8 @@ impl SyncHandler {
/// Sent when the player's held (cursor) item changes.
///
/// # Arguments
/// - `screen_handler` - The screen handler
/// - `stack` - The new cursor item
pub fn update_cursor_stack(&self, _screen_handler: &ScreenHandlerBehaviour, stack: &ItemStack) {
pub fn update_cursor_stack(&self, stack: &ItemStack) {
if let Some(player) = self
.player
.lock()

View File

@ -184,6 +184,88 @@ pub fn send_cancellable(input: TokenStream) -> TokenStream {
expanded.into()
}
#[proc_macro]
pub fn send_cancellable_blocking(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as SynBlock);
let mut stmts_iter = input.stmts.into_iter();
let Some(Stmt::Expr(server_stmt, _)) = stmts_iter.next() else {
abort_call_site!("expected server expression as first statement")
};
let Some(Stmt::Expr(event_stmt, _)) = stmts_iter.next() else {
abort_call_site!("expected event expression as second statement")
};
let event_expr = if let Expr::Reference(syn::ExprReference {
expr,
mutability: Some(_),
..
}) = event_stmt
{
*expr
} else {
event_stmt
};
let mut after_block = None;
let mut cancelled_block = None;
for stmt in stmts_iter {
if let Stmt::Expr(Expr::Block(b), _) = stmt
&& let Some(ref label) = b.label
{
if label.name.ident == "after" {
after_block = Some(b.block);
} else if label.name.ident == "cancelled" {
cancelled_block = Some(b.block);
}
}
}
let execution = match (after_block, cancelled_block) {
(Some(after), Some(cancelled)) => quote! {
if !is_cancelled {
#after
} else {
#cancelled
}
},
(Some(after), None) => quote! {
if !is_cancelled {
#after
}
},
(None, Some(cancelled)) => quote! {
if is_cancelled {
#cancelled
}
},
(None, None) => quote! {},
};
let expanded = quote! {
{
let mut event = #event_expr;
let server_ref: &std::sync::Arc<crate::server::Server> = {
use std::borrow::Borrow;
(#server_stmt).borrow()
};
server_ref.plugin_manager.fire_blocking(server_ref, &mut event);
let is_cancelled = {
use crate::plugin::Cancellable;
event.cancelled()
};
#execution
}
};
expanded.into()
}
/// Attaches a fixed packet ID to a struct implementing `Packet`.
///
/// # Arguments

View File

@ -293,19 +293,6 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossfire"
version = "3.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111ce8f7abfbac38b4bc4f32a3a2dda1a8034b873c90c7899f302cc9dbbc05ec"
dependencies = [
"crossbeam-utils",
"futures-core",
"parking_lot",
"pointers",
"smallvec",
]
[[package]]
name = "crypto-bigint"
version = "0.7.5"
@ -521,7 +508,6 @@ checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
@ -544,17 +530,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.32"
@ -1033,12 +1008,6 @@ dependencies = [
"spki",
]
[[package]]
name = "pointers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dcdc93847ad24990939cce6e1804361e903efcb5f99daa5abd87943a9d6d7ba"
[[package]]
name = "prettyplease"
version = "0.2.37"
@ -1229,7 +1198,6 @@ dependencies = [
"bitflags",
"bytes",
"crossbeam",
"crossfire",
"dashmap",
"flate2",
"futures",

View File

@ -3,10 +3,10 @@ use libfuzzer_sys::fuzz_target;
use pumpkin_protocol::bedrock::packet_decoder::BedrockBatchDecoder;
use pumpkin_protocol::bedrock::server::{
SAnimate, SBlockPickRequest, SClientCacheBlobStatus, SClientCacheStatus, SCommandRequest,
SContainerClose, SEmote, SEmoteList, SInteraction, SInventoryTransaction, SItemStackRequest,
SContainerClose, SEmote, SEmoteList, SInteract, SInventoryTransaction, SItemStackRequest,
SLoadingScreen, SLogin, SMobEquipment, SModalFormResponse, SPacketViolationWarning,
SPlayerAction, SPlayerAuthInput, SPlayerHotbar, SRequestAbility, SRequestChunkRadius,
SRequestNetworkSettings, SResourcePackResponse, SRespawn, SSetLocalPlayerAsInitialized,
SRequestNetworkSettings, SResourcePackClientResponse, SRespawn, SSetLocalPlayerAsInitialized,
SSetPlayerInventoryOptions, SText,
};
use pumpkin_protocol::serial::{PacketRead, PacketReadSlice};
@ -46,7 +46,7 @@ fn fuzz_serverbound_packets(payload: &[u8]) {
SContainerClose,
SEmote,
SEmoteList,
SInteraction,
SInteract,
SInventoryTransaction,
SItemStackRequest,
SLoadingScreen,
@ -60,7 +60,7 @@ fn fuzz_serverbound_packets(payload: &[u8]) {
SRequestAbility,
SRequestChunkRadius,
SRequestNetworkSettings,
SResourcePackResponse,
SResourcePackClientResponse,
SRespawn,
SSetLocalPlayerAsInitialized,
SSetPlayerInventoryOptions,

View File

@ -23,7 +23,7 @@ fuzz_target!(|data: &[u8]| {
2 => SubClient::SubClient1,
_ => SubClient::SubClietn2,
};
let use_compression = data[6] % 2 == 0;
let use_compression = data[6].is_multiple_of(2);
let packet_payload = &data[7..];
let mut encoder = BedrockBatchEncoder::new();

View File

@ -20,8 +20,8 @@ fuzz_target!(|data: &[u8]| {
let compression_threshold = data[0] as usize;
let compression_level = (data[1] % 10) as u32;
let encryption_key: [u8; 16] = data[2..18].try_into().unwrap();
let use_compression = data[18] % 2 == 0;
let use_encryption = data[19] % 2 == 0;
let use_compression = data[18].is_multiple_of(2);
let use_encryption = data[19].is_multiple_of(2);
let packet_data = &data[20..];
let rt = Runtime::new().unwrap();

View File

@ -27,7 +27,7 @@ impl Pool {
let mut index = random.next_bounded_i32(total_weight);
if total_weight < 64 {
return Some(FlattenedContent::get(index, distribution, total_weight));
return Some(FlattenedContent::get(index, distribution));
}
// WrappedContent
@ -60,11 +60,10 @@ impl FlattenedContent {
/// # Arguments
/// * `index` – The target index to select.
/// * `entries` – The weighted entries to flatten.
/// * `total_weight` – The total weight of all entries.
///
/// # Returns
/// The element corresponding to the given index.
pub fn get<E>(index: i32, entries: &[Weighted<E>], _total_weight: i32) -> &E {
pub fn get<E>(index: i32, entries: &[Weighted<E>]) -> &E {
let mut cur_index = 0;
for entry in entries {

View File

@ -1,57 +1,30 @@
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use criterion::{Criterion, criterion_group, criterion_main};
use pumpkin_data::{
chunk_gen_settings::GenerationSettings, dimension::Dimension,
noise_router::OVERWORLD_BASE_NOISE_ROUTER,
};
use pumpkin_world::{
GlobalRandomConfig, ProtoNoiseRouters, bench_create_and_populate_biome,
bench_create_and_populate_noise, bench_create_and_populate_noise_with_surface,
generation::proto_chunk::TerrainCache,
GlobalRandomConfig, bench_create_and_populate_biome, bench_create_and_populate_noise,
bench_create_and_populate_noise_with_surface,
};
fn bench_terrain_gen(c: &mut Criterion) {
let seed = 0;
let random_config = GlobalRandomConfig::new(seed, false);
let base_router = ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &random_config);
let surface_config = GenerationSettings::from_dimension(&Dimension::OVERWORLD);
let terrain_cache = TerrainCache::from_random(&random_config);
let default_state = surface_config.default_block;
c.bench_function("overworld biome", |b| {
b.iter(|| {
bench_create_and_populate_biome(
&base_router,
&random_config,
surface_config,
&terrain_cache,
default_state,
);
bench_create_and_populate_biome(&random_config);
});
});
c.bench_function("overworld noise", |b| {
b.iter(|| {
bench_create_and_populate_noise(
&base_router,
&random_config,
surface_config,
&terrain_cache,
default_state,
);
bench_create_and_populate_noise(&random_config);
});
});
c.bench_function("overworld surface", |b| {
b.iter(|| {
bench_create_and_populate_noise_with_surface(
&base_router,
&random_config,
surface_config,
&terrain_cache,
default_state,
);
bench_create_and_populate_noise_with_surface(&random_config);
});
});
}

View File

@ -102,15 +102,12 @@ fn setup_cache(
}
fn bench_full_chunk_generation(c: &mut Criterion) {
let dimension = Dimension::OVERWORLD;
let world_gen = make_world_gen();
let block_registry = Arc::new(BlockRegistry);
c.bench_function("full_chunk_generation", |b| {
b.iter(|| {
black_box(generate_single_chunk(
&dimension,
0,
&world_gen,
block_registry.as_ref(),
black_box(0),

View File

@ -96,8 +96,6 @@ fn bench_concurrent_chunk_generation(c: &mut Criterion) {
let br = br.clone();
s.spawn(move |_| {
black_box(generate_single_chunk(
&Dimension::OVERWORLD,
0,
&wg,
br.as_ref(),
cx,

View File

@ -54,22 +54,8 @@ impl WorldPortalExt for BlockRegistry {
fn bench_chunk_deserialization(c: &mut Criterion) {
let dimension = Dimension::OVERWORLD;
let world_gen = get_world_gen(
Seed(42),
dimension.clone(),
false,
Vec::new(),
String::new(),
);
let chunk = generate_single_chunk(
&dimension,
0,
&world_gen,
&BlockRegistry,
0,
0,
StagedChunkEnum::Full,
);
let world_gen = get_world_gen(Seed(42), dimension, false, Vec::new(), String::new());
let chunk = generate_single_chunk(&world_gen, &BlockRegistry, 0, 0, StagedChunkEnum::Full);
let Chunk::Level(chunk) = chunk else {
panic!("full generation must return a level chunk");
};

View File

@ -777,24 +777,37 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for AnvilChunkFile<
chunks: Vec<Vector2<i32>>,
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) {
// Don't par iter here so we can prevent backpressure with the await in the async
// runtime
for chunk in chunks {
let index = Self::get_chunk_index(chunk.x, chunk.y);
let is_ok = match &self.chunks_data[index] {
None => stream.send(LoadedData::Missing(chunk)).await.is_ok(),
Some(chunk_metadata) => {
let result = match chunk_metadata.serialized_data.to_chunk(chunk) {
Ok(chunk_res) => LoadedData::Loaded(chunk_res),
Err(err) => LoadedData::Error((chunk, err)),
};
let chunk_items: Vec<(Vector2<i32>, Option<AnvilChunkData>)> = chunks
.into_iter()
.map(|chunk| {
let index = Self::get_chunk_index(chunk.x, chunk.y);
let data = self.chunks_data[index]
.as_ref()
.map(|chunk_metadata| chunk_metadata.serialized_data.clone());
(chunk, data)
})
.collect();
stream.send(result).await.is_ok()
}
};
let (tx, mut rx) = tokio::sync::mpsc::channel(chunk_items.len().max(1));
if !is_ok {
// Stream is closed. Stop unneeded work and IO
rayon::spawn(move || {
use rayon::prelude::*;
chunk_items
.into_par_iter()
.for_each(|(chunk, serialized_data)| {
let result = serialized_data.map_or_else(
|| LoadedData::Missing(chunk),
|data| match data.to_chunk(chunk) {
Ok(chunk_res) => LoadedData::Loaded(chunk_res),
Err(err) => LoadedData::Error((chunk, err)),
},
);
let _ = tx.blocking_send(result);
});
});
while let Some(item) = rx.recv().await {
if stream.send(item).await.is_err() {
return;
}
}

View File

@ -586,22 +586,33 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for LinearV2File<S>
chunks: Vec<Vector2<i32>>,
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) {
for chunk in chunks {
let index = Self::get_chunk_index(chunk.x, chunk.y);
let chunk_items: Vec<(Vector2<i32>, Option<Bytes>)> = chunks
.into_iter()
.map(|chunk| {
let index = Self::get_chunk_index(chunk.x, chunk.y);
let data = self.chunks_data[index].clone();
(chunk, data)
})
.collect();
let is_ok = match &self.chunks_data[index] {
None => stream.send(LoadedData::Missing(chunk)).await.is_ok(),
Some(data) => {
let result = match S::from_bytes(data, chunk) {
let (tx, mut rx) = tokio::sync::mpsc::channel(chunk_items.len().max(1));
rayon::spawn(move || {
use rayon::prelude::*;
chunk_items.into_par_iter().for_each(|(chunk, data)| {
let result = data.map_or_else(
|| LoadedData::Missing(chunk),
|data| match S::from_bytes(&data, chunk) {
Ok(c) => LoadedData::Loaded(c),
Err(err) => LoadedData::Error((chunk, err)),
};
stream.send(result).await.is_ok()
}
};
},
);
let _ = tx.blocking_send(result);
});
});
if !is_ok {
// Receiver dropped — stop early to avoid unnecessary work.
while let Some(item) = rx.recv().await {
if stream.send(item).await.is_err() {
return;
}
}

View File

@ -122,33 +122,49 @@ where
chunks: Vec<Vector2<i32>>,
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
) {
for pos in chunks {
let rel_x = pos.x.rem_euclid(32);
let rel_z = pos.y.rem_euclid(32);
let index = (rel_x + rel_z * 32) as usize;
let chunk_items: Vec<(Vector2<i32>, Option<Vec<u8>>)> = chunks
.into_iter()
.map(|pos| {
let rel_x = pos.x.rem_euclid(32);
let rel_z = pos.y.rem_euclid(32);
let index = (rel_x + rel_z * 32) as usize;
let data = self.data.chunks.get(&index.to_string()).cloned();
(pos, data)
})
.collect();
if let Some(chunk_bytes) = self.data.chunks.get(&index.to_string()) {
let res = (|| {
let mut decoder = StreamingDecoder::new(&chunk_bytes[..]).map_err(|e| {
ChunkReadingError::IoError(std::io::Error::other(e.to_string()))
})?;
let mut decompressed = Vec::new();
std::io::Read::read_to_end(&mut decoder, &mut decompressed)
.map_err(ChunkReadingError::IoError)?;
let bytes = Bytes::from(decompressed);
D::from_bytes(&bytes, pos)
})();
let (tx, mut rx) = tokio::sync::mpsc::channel(chunk_items.len().max(1));
let data_res = match res {
Ok(data) => LoadedData::Loaded(data),
Err(e) => LoadedData::Error((pos, e)),
};
rayon::spawn(move || {
use rayon::prelude::*;
chunk_items.into_par_iter().for_each(|(pos, chunk_bytes)| {
let data_res = chunk_bytes.map_or_else(
|| LoadedData::Missing(pos),
|chunk_bytes| {
let res = (|| {
let mut decoder =
StreamingDecoder::new(&chunk_bytes[..]).map_err(|e| {
ChunkReadingError::IoError(std::io::Error::other(e.to_string()))
})?;
let mut decompressed = Vec::new();
std::io::Read::read_to_end(&mut decoder, &mut decompressed)
.map_err(ChunkReadingError::IoError)?;
let bytes = Bytes::from(decompressed);
D::from_bytes(&bytes, pos)
})();
match res {
Ok(data) => LoadedData::Loaded(data),
Err(e) => LoadedData::Error((pos, e)),
}
},
);
let _ = tx.blocking_send(data_res);
});
});
if stream.send(data_res).await.is_err() {
return;
}
} else {
let _ = stream.send(LoadedData::Missing(pos)).await;
while let Some(item) = rx.recv().await {
if stream.send(item).await.is_err() {
return;
}
}
}

View File

@ -1,5 +1,3 @@
use pumpkin_data::dimension::Dimension;
use crate::ProtoChunk;
use crate::generation::generator::WorldGenerator;
use crate::world::WorldPortalExt;
@ -8,8 +6,6 @@ use pumpkin_config::lighting::LightingEngineConfig;
use super::{Cache, Chunk, StagedChunkEnum};
pub fn generate_single_chunk(
dimension: &Dimension,
biome_mixer_seed: i64,
generator: &WorldGenerator,
block_registry: &dyn WorldPortalExt,
chunk_x: i32,
@ -17,8 +13,6 @@ pub fn generate_single_chunk(
target_stage: StagedChunkEnum,
) -> Chunk {
generate_single_chunk_with_radius(
dimension,
biome_mixer_seed,
generator,
block_registry,
chunk_x,
@ -28,10 +22,7 @@ pub fn generate_single_chunk(
)
}
#[expect(clippy::too_many_arguments)]
pub fn generate_single_chunk_with_radius(
_dimension: &Dimension,
_biome_mixer_seed: i64,
generator: &WorldGenerator,
block_registry: &dyn WorldPortalExt,
chunk_x: i32,
@ -98,7 +89,6 @@ pub fn generate_single_chunk_with_radius(
#[cfg(test)]
mod tests {
use crate::biome::hash_seed;
use crate::chunk::ChunkHeightmapType;
use crate::chunk_system::Chunk;
use crate::chunk_system::{
@ -167,11 +157,8 @@ mod tests {
let block_registry = Arc::new(BlockRegistry);
let world_gen =
get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
let biome_mixer_seed = hash_seed(world_gen.seed());
let chunk = generate_single_chunk(
&dimension,
biome_mixer_seed,
&world_gen,
block_registry.as_ref(),
0,
@ -201,12 +188,9 @@ mod tests {
let dimension = Dimension::OVERWORLD;
let seed = Seed(42);
let block_registry = Arc::new(BlockRegistry);
let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
let biome_mixer_seed = hash_seed(world_gen.seed());
let world_gen = get_world_gen(seed, dimension, false, Vec::new(), String::new());
let chunk = generate_single_chunk(
&dimension,
biome_mixer_seed,
&world_gen,
block_registry.as_ref(),
0,
@ -239,12 +223,9 @@ mod tests {
let dimension = Dimension::OVERWORLD;
let seed = Seed(1_782_124_772_053_846_960);
let block_registry = Arc::new(BlockRegistry);
let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
let biome_mixer_seed = hash_seed(world_gen.seed());
let world_gen = get_world_gen(seed, dimension, false, Vec::new(), String::new());
let chunk = generate_single_chunk(
&dimension,
biome_mixer_seed,
&world_gen,
block_registry.as_ref(),
31,
@ -292,12 +273,9 @@ mod tests {
let dimension = Dimension::OVERWORLD;
let seed = Seed(1_782_124_772_053_846_960);
let block_registry = Arc::new(BlockRegistry);
let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
let biome_mixer_seed = hash_seed(world_gen.seed());
let world_gen = get_world_gen(seed, dimension, false, Vec::new(), String::new());
let chunk = generate_single_chunk_with_radius(
&dimension,
biome_mixer_seed,
&world_gen,
block_registry.as_ref(),
73,
@ -345,11 +323,8 @@ mod tests {
let dimension = Dimension::THE_END;
let seed = Seed(12_345);
let block_registry = Arc::new(BlockRegistry);
let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
let biome_mixer_seed = hash_seed(world_gen.seed());
let world_gen = get_world_gen(seed, dimension, false, Vec::new(), String::new());
let chunk = generate_single_chunk_with_radius(
&dimension,
biome_mixer_seed,
&world_gen,
block_registry.as_ref(),
-306,
@ -398,12 +373,9 @@ mod tests {
let dimension = Dimension::OVERWORLD;
let seed = Seed(1_782_124_772_053_846_960);
let block_registry = Arc::new(BlockRegistry);
let world_gen = get_world_gen(seed, dimension.clone(), false, Vec::new(), String::new());
let biome_mixer_seed = hash_seed(world_gen.seed());
let world_gen = get_world_gen(seed, dimension, false, Vec::new(), String::new());
let chunk = generate_single_chunk(
&dimension,
biome_mixer_seed,
&world_gen,
block_registry.as_ref(),
73,
@ -420,8 +392,6 @@ mod tests {
}
let cage_chunk = generate_single_chunk(
&dimension,
biome_mixer_seed,
&world_gen,
block_registry.as_ref(),
73,

View File

@ -12,7 +12,6 @@ use crate::chunk::io::Dirtiable;
use crate::level::{Level, SyncChunk};
use dashmap::DashMap;
use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::chunk_gen_settings::GenerationSettings;
use pumpkin_util::math::vector2::Vector2;
use slotmap::Key;
use std::cmp::{Ordering, max};
@ -76,6 +75,7 @@ pub struct GenerationSchedule {
listener: Arc<ChunkListener>,
lighting_config: LightingEngineConfig,
last_unload: std::time::Instant,
generation_pool: Arc<rayon::ThreadPool>,
}
impl GenerationSchedule {
@ -113,8 +113,16 @@ impl GenerationSchedule {
io_lock.clone(),
));
let max_in_flight =
(thread::available_parallelism().map_or(1, std::num::NonZero::get) * 4) as u16;
let cpus = thread::available_parallelism().map_or(1, std::num::NonZero::get);
let gen_threads = (cpus / 2).clamp(2, 16);
let generation_pool = Arc::new(
rayon::ThreadPoolBuilder::new()
.num_threads(gen_threads)
.thread_name(|i| format!("ChunkGen-{i}"))
.build()
.expect("Failed to build Chunk Generation ThreadPool"),
);
let max_in_flight = (gen_threads * 2) as u16;
let level_sched = level;
let lighting_config = level_sched.lighting_config;
@ -142,6 +150,7 @@ impl GenerationSchedule {
chunk_map: HashMap::default(),
lighting_config,
last_unload: std::time::Instant::now(),
generation_pool,
};
scheduler.work(&level_sched);
})
@ -752,8 +761,10 @@ impl GenerationSchedule {
chunks.push((pos, Chunk::Level(chunk)));
}
}
Chunk::Proto(chunk) => {
chunks.push((pos, Chunk::Proto(chunk)));
Chunk::Proto(_) => {
// ProtoChunks are in-memory intermediate generation stages
// (e.g. temporary border dependencies). Do not convert and save
// incomplete chunks to disk during runtime unloads.
}
}
}
@ -1416,12 +1427,10 @@ impl GenerationSchedule {
let stage = node.stage;
let send_chunk = self.send_chunk.clone();
let level = level.clone();
let settings =
GenerationSettings::from_dimension(level.world_gen.load().dimension());
rayon::spawn(move || {
self.generation_pool.spawn(move || {
let result = crate::chunk_system::worker_logic::run_generation(
pos, cache, stage, &level, settings,
pos, cache, stage, &level,
);
let _ = send_chunk.send((pos, result));
});

View File

@ -8,7 +8,6 @@ use crate::chunk::io::{FileIO, LoadedData};
use crate::level::Level;
use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::chunk::ChunkStatus;
use pumpkin_data::chunk_gen_settings::GenerationSettings;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use std::sync::atomic::Ordering::Relaxed;
@ -55,19 +54,11 @@ fn needs_relighting(chunk: &crate::chunk::ChunkData, config: LightingEngineConfi
!has_complex_light
}
async fn load_proto_chunk(chunk: &Arc<crate::chunk::ChunkData>, level: &Level) -> ProtoChunk {
let (tx, rx) = tokio::sync::oneshot::channel();
let world_gen = level.world_gen.load();
let chunk_clone = chunk.clone();
rayon::spawn(move || {
let p = ProtoChunk::from_chunk_data(&chunk_clone, &world_gen);
let _ = tx.send(p);
});
rx.await
.unwrap_or_else(|_| ProtoChunk::from_chunk_data(chunk, &level.world_gen.load()))
fn load_proto_chunk(chunk: &crate::chunk::ChunkData, level: &Level) -> ProtoChunk {
ProtoChunk::from_chunk_data(chunk, &level.world_gen.load())
}
async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level) -> Chunk {
fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level) -> Chunk {
let pos = ChunkPos::new(chunk.x, chunk.z);
if chunk.status == ChunkStatus::Full {
let needs_relight = needs_relighting(&chunk, level.lighting_config);
@ -76,7 +67,7 @@ async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level
"Chunk {pos:?} has uniform lighting, downgrading to Features stage for relighting"
);
let mut proto = load_proto_chunk(&chunk, level).await;
let mut proto = load_proto_chunk(&chunk, level);
// Clear all lighting data
let section_count = proto.light.sky_light.len();
@ -92,7 +83,7 @@ async fn process_loaded_chunk(chunk: Arc<crate::chunk::ChunkData>, level: &Level
Chunk::Level(chunk)
}
} else {
let proto = load_proto_chunk(&chunk, level).await;
let proto = load_proto_chunk(&chunk, level);
Chunk::Proto(Box::new(proto))
}
}
@ -150,7 +141,7 @@ pub async fn io_read_work(
match data {
Loaded(chunk) => {
let pos = ChunkPos::new(chunk.x, chunk.z);
let processed = process_loaded_chunk(chunk, &level).await;
let processed = process_loaded_chunk(chunk, &level);
if send.send((pos, RecvChunk::IO(processed))).is_err() {
break;
}
@ -212,31 +203,31 @@ pub async fn io_write_work(
error!("Failed to save chunks: {:?}", e);
}
for i in positions {
{
let mut data = lock
.0
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match data.entry(i) {
Entry::Occupied(mut entry) => {
let rc = entry.get_mut();
if *rc == 1 {
entry.remove();
drop(data);
lock.1.notify_waiters();
} else {
*rc -= 1;
for i in positions {
match data.entry(i) {
Entry::Occupied(mut entry) => {
let rc = entry.get_mut();
if *rc <= 1 {
entry.remove();
} else {
*rc -= 1;
}
}
Entry::Vacant(_) => {
warn!(
"io_write: attempted to release missing lock entry for {:?}",
i
);
}
}
Entry::Vacant(_) => {
warn!(
"io_write: attempted to release missing lock entry for {:?}",
i
);
// continue without panicking to avoid crashing on shutdown races
}
}
}
lock.1.notify_waiters();
}
}
@ -245,7 +236,6 @@ pub fn run_generation(
mut cache: Cache,
stage: StagedChunkEnum,
level: &Level,
_settings: &GenerationSettings,
) -> RecvChunk {
let portal = level.world_portal.load_full();
let Some(portal_ref) = portal.as_deref() else {

View File

@ -15,7 +15,7 @@ pub struct BlendingData {
impl BlendingData {
#[must_use]
pub fn get_height(&self, cell_x: i32, _cell_y: i32, cell_z: i32) -> f64 {
pub fn get_height(&self, cell_x: i32, cell_z: i32) -> f64 {
if !(0..16).contains(&cell_x) || !(0..16).contains(&cell_z) {
return f64::MAX;
}
@ -81,7 +81,7 @@ impl BlendingData {
}
}
pub fn iterate_biomes<F>(&self, quart_x: i32, _quart_y: i32, quart_z: i32, mut consumer: F)
pub fn iterate_biomes<F>(&self, quart_x: i32, quart_z: i32, mut consumer: F)
where
F: FnMut(i32, i32, &'static Biome),
{

View File

@ -90,12 +90,8 @@ impl Blender {
let cell_x = biome_coords::from_block(block_x);
let cell_z = biome_coords::from_block(block_z);
let fixed_height = self.get_blending_data_value(
cell_x,
0,
cell_z,
blending_data::BlendingData::get_height,
);
let fixed_height =
self.get_blending_data_value(cell_x, 0, cell_z, |data, x, _, z| data.get_height(x, z));
if fixed_height != f64::MAX {
return BlendingOutput {
@ -275,7 +271,6 @@ impl Blender {
pub fn blend_biome(
&self,
quart_x: i32,
quart_y: i32,
quart_z: i32,
shift_noise: &DoublePerlinNoiseSampler,
) -> Option<&'static Biome> {
@ -288,7 +283,6 @@ impl Blender {
blending_data.iterate_biomes(
biome_coords::from_chunk(chunk_x),
quart_y,
biome_coords::from_chunk(chunk_z),
|test_cell_x, test_cell_z, biome| {
let dx = (quart_x - test_cell_x) as f64;
@ -324,7 +318,7 @@ pub struct BlenderBiomeSupplier<'a> {
impl BiomeSupplier for BlenderBiomeSupplier<'_> {
fn biome(&self, x: i32, y: i32, z: i32, sampler: &mut MultiNoiseSampler<'_>) -> &'static Biome {
self.blender
.blend_biome(x, y, z, &self.shift_noise)
.blend_biome(x, z, &self.shift_noise)
.unwrap_or_else(|| self.base.biome(x, y, z, sampler))
}
}

View File

@ -228,15 +228,9 @@ impl ConfiguredFeature {
random,
pos,
),
Self::CoralClaw(_feature) => CoralClawFeature::generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
),
Self::CoralClaw(_feature) => {
CoralClawFeature::generate(chunk, block_registry, random, pos)
}
Self::EndPlatform(_feature) => EndPlatformFeature::generate(
chunk,
block_registry,
@ -299,15 +293,7 @@ impl ConfiguredFeature {
random,
pos,
),
Self::Tree(feature) => feature.generate(
block_registry,
chunk,
min_y,
height,
feature_name,
random,
pos,
),
Self::Tree(feature) => feature.generate(block_registry, chunk, random, pos),
Self::RandomSelector(feature) => feature.generate(
chunk,
block_registry,
@ -436,7 +422,7 @@ impl ConfiguredFeature {
Self::DeltaFeature(_feature) => {
DeltaFeatureFeature::generate(chunk, min_y, height, feature_name, random, pos)
}
Self::DripstoneCluster(feature) => feature.generate(chunk, random, pos),
Self::DripstoneCluster(feature) => feature.generate(chunk, pos),
Self::LargeDripstone(feature) => feature.generate(chunk, random, pos),
Self::EndGateway(_feature) => {
EndGatewayFeature::generate(chunk, min_y, height, feature_name, random, pos)

View File

@ -10,13 +10,9 @@ use super::CoralFeature;
pub struct CoralClawFeature;
impl CoralClawFeature {
#[allow(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>(
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {

View File

@ -1,5 +1,5 @@
use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use pumpkin_util::math::position::BlockPos;
use crate::generation::proto_chunk::GenerationCache;
@ -7,12 +7,7 @@ pub struct DripstoneClusterFeature;
impl DripstoneClusterFeature {
#[allow(clippy::unused_self)]
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
pub fn generate<T: GenerationCache>(&self, chunk: &mut T, pos: BlockPos) -> bool {
let mut generated = false;
let radius = 3;

View File

@ -56,7 +56,7 @@ impl AcaciaFoliagePlacer {
foliage_positions
}
pub const fn get_random_height(_random: &mut RandomGenerator) -> i32 {
pub const fn get_random_height() -> i32 {
0
}
}

View File

@ -42,7 +42,7 @@ impl BlobFoliagePlacer {
foliage_positions
}
pub const fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 {
pub const fn get_random_height(&self) -> i32 {
self.height
}
}

View File

@ -42,7 +42,7 @@ impl BushFoliagePlacer {
foliage_positions
}
pub const fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 {
pub const fn get_random_height(&self) -> i32 {
self.height
}
}

View File

@ -88,7 +88,7 @@ impl DarkOakFoliagePlacer {
foliage_positions
}
pub const fn get_random_height(_random: &mut RandomGenerator) -> i32 {
pub const fn get_random_height() -> i32 {
4
}
}

View File

@ -41,7 +41,7 @@ impl LargeOakFoliagePlacer {
foliage_positions
}
pub const fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 {
pub const fn get_random_height(&self) -> i32 {
self.height
}
}

View File

@ -46,11 +46,7 @@ impl JungleFoliagePlacer {
}
foliage_positions
}
pub const fn get_random_height(
&self,
_random: &mut RandomGenerator,
_trunk_height: i32,
) -> i32 {
pub const fn get_random_height(&self) -> i32 {
self.height
}
}

View File

@ -52,7 +52,7 @@ impl MegaPineFoliagePlacer {
}
foliage_positions
}
pub fn get_random_height(&self, random: &mut RandomGenerator, _trunk_height: i32) -> i32 {
pub fn get_random_height(&self, random: &mut RandomGenerator) -> i32 {
self.crown_height.get(random)
}
}

View File

@ -380,18 +380,16 @@ impl FoliageType {
pub fn get_random_height(&self, random: &mut RandomGenerator, trunk_height: i32) -> i32 {
match self {
Self::Blob(blob) => blob.get_random_height(random),
Self::Blob(blob) => blob.get_random_height(),
Self::Spruce(spruce) => spruce.get_random_height(random, trunk_height),
Self::Pine(pine) => pine.get_random_height(random, trunk_height),
Self::Acacia(_acacia) => AcaciaFoliagePlacer::get_random_height(random),
Self::Bush(bush) => bush.get_random_height(random),
Self::Fancy(fancy) => fancy.get_random_height(random),
Self::Jungle(jungle) => jungle.get_random_height(random, trunk_height),
Self::MegaPine(mega_pine) => mega_pine.get_random_height(random, trunk_height),
Self::DarkOak(_dark_oak) => DarkOakFoliagePlacer::get_random_height(random),
Self::RandomSpread(random_spread) => {
random_spread.get_random_height(random, trunk_height)
}
Self::Pine(pine) => pine.get_random_height(random),
Self::Acacia(_acacia) => AcaciaFoliagePlacer::get_random_height(),
Self::Bush(bush) => bush.get_random_height(),
Self::Fancy(fancy) => fancy.get_random_height(),
Self::Jungle(jungle) => jungle.get_random_height(),
Self::MegaPine(mega_pine) => mega_pine.get_random_height(random),
Self::DarkOak(_dark_oak) => DarkOakFoliagePlacer::get_random_height(),
Self::RandomSpread(random_spread) => random_spread.get_random_height(random),
Self::Cherry(cherry) => cherry.get_random_height(random),
}
}

View File

@ -55,7 +55,7 @@ impl PineFoliagePlacer {
placer.radius.get(random) + random.next_bounded_i32((base_height + 1).max(1))
}
pub fn get_random_height(&self, random: &mut RandomGenerator, _trunk_height: i32) -> i32 {
pub fn get_random_height(&self, random: &mut RandomGenerator) -> i32 {
self.height.get(random)
}
}

View File

@ -38,8 +38,7 @@ impl RandomSpreadFoliagePlacer {
}
foliage_positions
}
// TODO: getRandomRadius
pub fn get_random_height(&self, random: &mut RandomGenerator, _trunk_height: i32) -> i32 {
pub fn get_random_height(&self, random: &mut RandomGenerator) -> i32 {
self.foliage_height.get(random)
}
}

View File

@ -37,26 +37,15 @@ pub struct TreeNode {
}
impl TreeFeature {
#[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>(
&self,
block_registry: &dyn WorldPortalExt,
chunk: &mut T,
min_y: i8,
height: u16,
feature_name: pumpkin_data::placed_feature::PlacedFeature, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let (log_positions, root_positions, foliage_positions) = self.generate_main(
block_registry,
chunk,
min_y,
height,
feature_name,
random,
pos,
);
let (log_positions, root_positions, foliage_positions) =
self.generate_main(block_registry, chunk, random, pos);
if log_positions.is_empty() && foliage_positions.is_empty() {
return false;
@ -205,14 +194,10 @@ impl TreeFeature {
state.is_air() || id.has_tag(tag::Block::MINECRAFT_REPLACEABLE_BY_TREES)
}
#[expect(clippy::too_many_arguments)]
fn generate_main<T: GenerationCache>(
&self,
block_registry: &dyn WorldPortalExt,
chunk: &mut T,
_min_y: i8,
_height: u16,
_feature_name: pumpkin_data::placed_feature::PlacedFeature, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> (Vec<BlockPos>, Vec<BlockPos>, Vec<BlockPos>) {

View File

@ -1,9 +1,7 @@
#![allow(clippy::unreachable)]
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used, clippy::panic))]
use pumpkin_data::{
Block, BlockState, chunk_gen_settings::GenerationSettings, dimension::Dimension,
};
use pumpkin_data::{Block, dimension::Dimension};
use pumpkin_util::math::vector2::Vector2;
pub mod biome;
@ -52,16 +50,9 @@ use crate::generation::{
biome_coords,
noise::{CHUNK_DIM, ChunkNoiseGenerator, aquifer_sampler::FluidLevel},
positions::chunk_pos,
proto_chunk::TerrainCache,
};
pub fn bench_create_and_populate_noise(
_base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig,
_settings: &GenerationSettings,
_terrain_cache: &TerrainCache,
_default_block: &'static BlockState,
) {
pub fn bench_create_and_populate_noise(random_config: &GlobalRandomConfig) {
use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use crate::generation::noise::router::surface_height_sampler::{
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
@ -137,13 +128,7 @@ pub fn bench_create_and_populate_noise(
);
}
pub fn bench_create_and_populate_biome(
_base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig,
_settings: &GenerationSettings,
_terrain_cache: &TerrainCache,
_default_block: &'static BlockState,
) {
pub fn bench_create_and_populate_biome(random_config: &GlobalRandomConfig) {
use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use crate::generation::noise::router::multi_noise_sampler::{
MultiNoiseSampler, MultiNoiseSamplerBuilderOptions,
@ -179,13 +164,7 @@ pub fn bench_create_and_populate_biome(
chunk.populate_biomes(generator, &mut multi_noise_sampler);
}
pub fn bench_create_and_populate_noise_with_surface(
_base_router: &ProtoNoiseRouters,
random_config: &GlobalRandomConfig,
_settings: &GenerationSettings,
_terrain_cache: &TerrainCache,
_default_block: &'static BlockState,
) {
pub fn bench_create_and_populate_noise_with_surface(random_config: &GlobalRandomConfig) {
use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator};
use crate::generation::noise::router::{
multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions},

View File

@ -28,7 +28,7 @@ impl BlockBehaviour for EndPortalBlock {
);
args.entity
.get_entity()
.try_use_portal(0, target_world, *args.position);
.try_use_portal(target_world, *args.position);
}
fn placed(&self, args: PlacedArgs<'_>) {

View File

@ -115,10 +115,12 @@ impl FireBlock {
total_burn_chance
}
const fn is_near_rain(_world: &World, _pos: &BlockPos) -> bool {
// TODO: Implement proper rain checking when weather is implemented
// For now, return false to allow fire to work
false
fn is_near_rain(world: &World, pos: &BlockPos) -> bool {
world.is_raining_at(pos)
|| world.is_raining_at(&pos.west())
|| world.is_raining_at(&pos.east())
|| world.is_raining_at(&pos.north())
|| world.is_raining_at(&pos.south())
}
// Get burn odds for a block, used in try_spreading_fire

View File

@ -139,11 +139,9 @@ impl BlockBehaviour for NetherPortalBlock {
args.position,
target_world.dimension.minecraft_name
);
let portal_delay = Self::get_portal_time(args.world, args.entity);
args.entity
.get_entity()
.try_use_portal(portal_delay, target_world, *args.position);
.try_use_portal(target_world, *args.position);
}
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {

View File

@ -52,12 +52,7 @@ impl MushroomPlantBlock {
is_dark_enough && Self::may_place_on(block_accessor.get_block_state(&below_pos))
}
pub fn grow_mushroom(
world: &Arc<World>,
pos: &BlockPos,
block: &Block,
_state_id: BlockStateId,
) -> bool {
pub fn grow_mushroom(world: &Arc<World>, pos: &BlockPos, block: &Block) -> bool {
let species = if block == &Block::BROWN_MUSHROOM {
TreeType::BrownMushroom
} else if block == &Block::RED_MUSHROOM {
@ -291,7 +286,7 @@ impl BlockBehaviour for MushroomPlantBlock {
}
fn perform_bonemeal(&self, args: BonemealArgs<'_>) {
Self::grow_mushroom(args.world, args.position, args.block, args.state_id);
Self::grow_mushroom(args.world, args.position, args.block);
}
}

View File

@ -71,7 +71,6 @@ pub fn get_updated_state(
mut props: VineLikeProperties,
world: &dyn BlockAccessor,
pos: &BlockPos,
_block: &Block,
) -> VineLikeProperties {
let above_pos = pos.up();
if props.up {
@ -324,7 +323,6 @@ impl BlockBehaviour for VineBlock {
VineLikeProperties::from_state_id(args.state_id, args.block),
args.world,
args.position,
args.block,
);
if count_faces(&updated_props) == 0 {
Block::AIR.default_state.id

View File

@ -88,7 +88,7 @@ impl FlowingLava {
world.set_block_state(pos, fire_state_id, BlockFlags::NOTIFY_ALL);
}
fn receive_neighbor_fluids(world: &Arc<World>, _fluid: &Fluid, block_pos: &BlockPos) -> bool {
fn receive_neighbor_fluids(world: &Arc<World>, block_pos: &BlockPos) -> bool {
// Logic to determine if we should replace the fluid with any of (cobble, obsidian, stone, etc.)
let below_is_soul_soil = world
.get_block(&block_pos.offset(BlockDirection::Down.to_offset()))
@ -152,7 +152,7 @@ impl FluidBehaviour for FlowingLava {
old_state_id: BlockStateId,
_notify: bool,
) {
if old_state_id != state_id && Self::receive_neighbor_fluids(world, fluid, block_pos) {
if old_state_id != state_id && Self::receive_neighbor_fluids(world, block_pos) {
let flow_speed = self.get_flow_speed(world);
world.schedule_fluid_tick(fluid, *block_pos, flow_speed, TickPriority::Normal);
}
@ -169,7 +169,7 @@ impl FluidBehaviour for FlowingLava {
block_pos: &BlockPos,
_notify: bool,
) {
if Self::receive_neighbor_fluids(world, fluid, block_pos) {
if Self::receive_neighbor_fluids(world, block_pos) {
let flow_speed = self.get_flow_speed(world);
world.schedule_fluid_tick(fluid, *block_pos, flow_speed, TickPriority::Normal);
}

View File

@ -541,7 +541,7 @@ impl BlockRegistry {
}
#[expect(clippy::too_many_lines)]
pub async fn place_block(
pub fn place_block(
&self,
player: &Arc<Player>,
placed_block: &'static Block,
@ -697,11 +697,7 @@ impl BlockRegistry {
};
server
.plugin_manager
.fire::<crate::plugin::block::block_can_build::BlockCanBuildEvent>(
server,
&mut can_build_event,
)
.await;
.fire_blocking(server, &mut can_build_event);
if can_build_event.cancelled || !can_build_event.buildable {
return Ok(None);
}
@ -713,10 +709,7 @@ impl BlockRegistry {
final_block_pos,
true,
);
server
.plugin_manager
.fire::<crate::plugin::block::block_place::BlockPlaceEvent>(server, &mut event)
.await;
server.plugin_manager.fire_blocking(server, &mut event);
if event.cancelled {
return Ok(None);
}
@ -1204,13 +1197,7 @@ impl BlockRegistry {
state_id
}
pub fn update_neighbors(
&self,
world: &Arc<World>,
position: &BlockPos,
_block: &Block,
flags: BlockFlags,
) {
pub fn update_neighbors(&self, world: &Arc<World>, position: &BlockPos, flags: BlockFlags) {
for direction in BlockDirection::abstract_block_update_order() {
let pos = position.offset(direction.to_offset());

View File

@ -1,6 +1,6 @@
use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::tree::Command;
use pumpkin_config::{BasicConfiguration, CommandsConfig};
use pumpkin_config::CommandsConfig;
use pumpkin_util::{
PermissionLvl,
permission::{Permission, PermissionDefault, PermissionManager, PermissionRegistry},
@ -94,7 +94,6 @@ mod worldborder;
#[must_use]
pub fn default_dispatcher(
permission_manager: &PermissionManager,
_basic_config: &BasicConfiguration,
commands_config: &CommandsConfig,
) -> CommandDispatcher {
let mut dispatcher = crate::command::dispatcher::CommandDispatcher::default();
@ -666,7 +665,7 @@ fn register_level_3_permissions(registry: &PermissionRegistry) {
#[cfg(test)]
mod override_tests {
use pumpkin_config::{BasicConfiguration, CommandOverride, CommandsConfig};
use pumpkin_config::{CommandOverride, CommandsConfig};
use pumpkin_util::PermissionLvl;
use pumpkin_util::permission::{PermissionDefault, PermissionManager};
@ -694,14 +693,13 @@ mod override_tests {
#[test]
fn disabling_a_command_removes_and_hides_it() {
let basic = BasicConfiguration::default();
let mut commands = CommandsConfig::default();
// `gamemode` lives on the legacy dispatcher; disabling it should remove
// it there and flag it on the wrapper.
disabled(&mut commands, "gamemode");
let manager = PermissionManager::new();
let dispatcher = default_dispatcher(&manager, &basic, &commands);
let dispatcher = default_dispatcher(&manager, &commands);
assert!(dispatcher.is_disabled("gamemode"));
assert!(
@ -716,14 +714,13 @@ mod override_tests {
#[test]
fn disabling_an_alias_turns_off_the_whole_command() {
let basic = BasicConfiguration::default();
let mut commands = CommandsConfig::default();
// `tp` is an alias of `teleport`; disabling it should take the whole
// command down, including the primary name.
disabled(&mut commands, "tp");
let manager = PermissionManager::new();
let dispatcher = default_dispatcher(&manager, &basic, &commands);
let dispatcher = default_dispatcher(&manager, &commands);
assert!(dispatcher.is_disabled("tp"));
assert!(dispatcher.is_disabled("teleport"));
@ -733,13 +730,12 @@ mod override_tests {
#[test]
fn disabling_a_node_command_also_disables_its_aliases() {
let basic = BasicConfiguration::default();
let mut commands = CommandsConfig::default();
// `help` is a node-based command with the aliases `h` and `?`.
disabled(&mut commands, "help");
let manager = PermissionManager::new();
let dispatcher = default_dispatcher(&manager, &basic, &commands);
let dispatcher = default_dispatcher(&manager, &commands);
assert!(dispatcher.is_disabled("help"));
assert!(dispatcher.is_disabled("h"));
@ -748,14 +744,13 @@ mod override_tests {
#[test]
fn override_for_unknown_command_is_ignored() {
let basic = BasicConfiguration::default();
let mut commands = CommandsConfig::default();
// A command name that does not exist (usually a typo in the config)
// should be ignored, not silently swallow a real command or panic.
disabled(&mut commands, "notacommand");
let manager = PermissionManager::new();
let dispatcher = default_dispatcher(&manager, &basic, &commands);
let dispatcher = default_dispatcher(&manager, &commands);
assert!(!dispatcher.is_disabled("notacommand"));
assert!(dispatcher.fallback_dispatcher.get_tree("gamemode").is_ok());
@ -763,25 +758,23 @@ mod override_tests {
#[test]
fn override_is_case_insensitive() {
let basic = BasicConfiguration::default();
let mut commands = CommandsConfig::default();
disabled(&mut commands, "GameMode");
let manager = PermissionManager::new();
let dispatcher = default_dispatcher(&manager, &basic, &commands);
let dispatcher = default_dispatcher(&manager, &commands);
assert!(dispatcher.is_disabled("gamemode"));
}
#[test]
fn permission_level_override_rewrites_the_registry_default() {
let basic = BasicConfiguration::default();
let mut commands = CommandsConfig::default();
// `gamemode` is normally level 2; bump it to owner-only.
permission(&mut commands, "gamemode", PermissionLvl::Four);
let manager = PermissionManager::new();
let _dispatcher = default_dispatcher(&manager, &basic, &commands);
let _dispatcher = default_dispatcher(&manager, &commands);
let permission = manager
.get_permission("minecraft:command.gamemode")
@ -794,7 +787,6 @@ mod override_tests {
#[test]
fn permission_override_resolves_node_command_by_convention() {
let basic = BasicConfiguration::default();
let mut commands = CommandsConfig::default();
// `kill` is a node-based command whose permission node is not recorded in
// the legacy dispatcher, so the override must fall back to the
@ -802,7 +794,7 @@ mod override_tests {
permission(&mut commands, "kill", PermissionLvl::Four);
let manager = PermissionManager::new();
let _dispatcher = default_dispatcher(&manager, &basic, &commands);
let _dispatcher = default_dispatcher(&manager, &commands);
let permission = manager
.get_permission("minecraft:command.kill")
@ -815,12 +807,11 @@ mod override_tests {
#[test]
fn permission_level_zero_allows_everyone() {
let basic = BasicConfiguration::default();
let mut commands = CommandsConfig::default();
permission(&mut commands, "gamemode", PermissionLvl::Zero);
let manager = PermissionManager::new();
let _dispatcher = default_dispatcher(&manager, &basic, &commands);
let _dispatcher = default_dispatcher(&manager, &commands);
let permission = manager
.get_permission("minecraft:command.gamemode")

View File

@ -60,7 +60,7 @@ impl CommandExecutor for StartExecutor {
} else {
1
};
let raid_id = raids.create_or_extend_raid(&player, pos, &world);
let raid_id = raids.create_or_extend_raid(pos, &world);
if let Some(id) = raid_id
&& let Some(raid) = raids.get_mut(id)
{

View File

@ -793,7 +793,6 @@ impl CommandDispatcher {
#[cfg(test)]
mod test {
use pumpkin_config::BasicConfiguration;
use pumpkin_data::translation;
use pumpkin_util::permission::PermissionManager;
use pumpkin_util::text::TextContent;
@ -817,22 +816,18 @@ mod test {
#[test]
fn dynamic_command() {
let config = BasicConfiguration::default();
let commands_config = pumpkin_config::CommandsConfig::default();
let manager = PermissionManager::new();
let mut dispatcher =
default_dispatcher(&manager, &config, &commands_config).fallback_dispatcher;
let mut dispatcher = default_dispatcher(&manager, &commands_config).fallback_dispatcher;
let tree = CommandTree::new(["test"], "test_desc");
dispatcher.register(tree, "minecraft:test");
}
#[test]
fn pumpkin_command_aliases() {
let config = BasicConfiguration::default();
let commands_config = pumpkin_config::CommandsConfig::default();
let manager = PermissionManager::new();
let dispatcher =
default_dispatcher(&manager, &config, &commands_config).fallback_dispatcher;
let dispatcher = default_dispatcher(&manager, &commands_config).fallback_dispatcher;
let pumpkin_tree = dispatcher.get_tree("pumpkin").unwrap();
let version_tree = dispatcher.get_tree("version").unwrap();

View File

@ -117,7 +117,7 @@ impl FollowOwnerGoal {
),
None,
None,
world.clone(),
&world,
);
let mut navigator = mob

View File

@ -22,6 +22,7 @@ pub mod look_around;
pub mod look_at_entity;
pub mod melee_attack;
pub mod move_to_target_pos;
pub mod move_towards_target;
pub mod offer_flower;
pub mod open_door;
pub mod owner_hurt_by_target;
@ -89,7 +90,7 @@ pub trait Goal: Send + Sync {
}
}
#[derive(Clone, Copy, Default)]
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
// We actually only use the first 4 bits ;)
pub struct Controls(u8);

View File

@ -8,10 +8,11 @@ use pumpkin_util::math::vector3::Vector3;
use rand::RngExt;
use std::sync::Arc;
const MIN_WAITING_TIME: i32 = 1200;
const MAX_TRYING_TIME: i32 = 1200;
const MIN_INTERVAL: i32 = 200;
const GIVE_UP_TICKS: i32 = 1200;
const STAY_TICKS: i32 = 1200;
const INTERVAL_TICKS: i32 = 200;
/// Mirrors vanilla Minecraft's `net.minecraft.world.entity.ai.goal.MoveToBlockGoal`.
pub struct MoveToTargetPosGoal<M: MoveToTargetPos> {
goal_control: Controls,
pub move_to_target_pos: ParentHandle<M>,
@ -55,7 +56,7 @@ impl<M: MoveToTargetPos> MoveToTargetPosGoal<M> {
}
pub fn get_interval(mob: &dyn Mob) -> i32 {
to_goal_ticks(MIN_INTERVAL + mob.get_random().random_range(0..MIN_INTERVAL))
to_goal_ticks(INTERVAL_TICKS + mob.get_random().random_range(0..INTERVAL_TICKS))
}
pub fn find_target_pos(&mut self, mob: &dyn Mob) -> bool {
@ -104,16 +105,38 @@ impl<M: MoveToTargetPos> MoveToTargetPosGoal<M> {
false
}
fn get_target_pos(&self) -> BlockPos {
#[must_use]
pub fn get_target_pos(&self) -> BlockPos {
self.target_pos.up()
}
const fn should_reset_path(&self) -> bool {
#[must_use]
pub const fn should_reset_path(&self) -> bool {
self.trying_time % 40 == 0
}
fn start_moving_to_target(_mob: &dyn Mob) {
// TODO: implement when navigation is implemented
#[must_use]
pub const fn is_reached_target(&self) -> bool {
self.reached
}
pub fn move_mob_to_block(&self, mob: &dyn Mob) {
let target = self.get_target_pos();
let mut navigator = mob
.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
navigator.set_progress(NavigatorGoal {
current_progress: mob.get_entity().pos.load(),
destination: Vector3::new(
target.0.x as f64 + 0.5,
target.0.y as f64,
target.0.z as f64 + 0.5,
),
speed: self.speed,
});
}
}
@ -141,29 +164,34 @@ impl<M: MoveToTargetPos> Goal for MoveToTargetPosGoal<M> {
let can_target = self
.move_to_target_pos
.get()
.is_some_and(|x| x.is_target_pos(world, self.target_pos));
.is_some_and(|move_to_target_pos| {
move_to_target_pos.is_target_pos(world, self.target_pos)
});
self.trying_time >= -self.safe_waiting_time
&& self.trying_time <= MAX_TRYING_TIME
&& self.trying_time <= GIVE_UP_TICKS
&& can_target
}
fn start(&mut self, mob: &dyn Mob) {
Self::start_moving_to_target(mob);
self.move_mob_to_block(mob);
self.trying_time = 0;
let random = mob.get_random().random_range(0..MIN_WAITING_TIME);
self.safe_waiting_time =
mob.get_random().random_range(random..MIN_WAITING_TIME) + MIN_WAITING_TIME;
let bound = mob.get_random().random_range(0..STAY_TICKS) + STAY_TICKS;
self.safe_waiting_time = mob.get_random().random_range(0..bound) + STAY_TICKS;
}
fn tick(&mut self, mob: &dyn Mob) {
let block_pos = self.get_target_pos();
let block_pos: Vector3<f64> = block_pos.to_f64();
let target = self.get_target_pos();
let target_center = Vector3::new(
target.0.x as f64 + 0.5,
target.0.y as f64 + 0.5,
target.0.z as f64 + 0.5,
);
let Some(move_to_target_pos) = self.move_to_target_pos.get() else {
return;
};
let desired_distance = move_to_target_pos.get_desired_distance_to_target();
if block_pos.squared_distance_to_vec(&mob.get_entity().pos.load())
if target_center.squared_distance_to_vec(&mob.get_entity().pos.load())
< desired_distance * desired_distance
{
self.reached = true;
@ -180,7 +208,11 @@ impl<M: MoveToTargetPos> Goal for MoveToTargetPosGoal<M> {
navigator.set_progress(NavigatorGoal {
current_progress: mob.get_entity().pos.load(),
destination: Vector3::new(block_pos.x + 0.5, block_pos.y, block_pos.z + 0.5),
destination: Vector3::new(
target.0.x as f64 + 0.5,
target.0.y as f64,
target.0.z as f64 + 0.5,
),
speed: self.speed,
});
}

View File

@ -0,0 +1,208 @@
use std::sync::Arc;
use super::{Controls, Goal};
use crate::entity::{EntityBase, ai::pathfinder::NavigatorGoal, mob::Mob};
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use rand::RngExt;
const HORIZONTAL_RANGE: f64 = 16.0;
const VERTICAL_RANGE: i32 = 7;
const TARGET_ATTEMPTS: usize = 10;
/// Mirrors vanilla Minecraft's `MoveTowardsTargetGoal`.
///
/// Moves the mob towards its current attack target within a specified maximum distance.
pub struct MoveTowardsTargetGoal {
goal_control: Controls,
speed: f64,
within: f32,
target: Option<Arc<dyn EntityBase>>,
wanted_pos: Option<Vector3<f64>>,
}
impl MoveTowardsTargetGoal {
#[must_use]
pub const fn new(speed: f64, within: f32) -> Self {
Self {
goal_control: Controls::MOVE,
speed,
within,
target: None,
wanted_pos: None,
}
}
/// Mirrors vanilla's `DefaultRandomPos.getPosTowards(mob, 16, 7, target.position(), Math.PI / 2)`.
fn find_pos_towards(mob: &dyn Mob, target_pos: &Vector3<f64>) -> Option<Vector3<f64>> {
let entity = &mob.get_mob_entity().living_entity.entity;
let mob_pos = entity.pos.load();
let world = entity.world.load();
let dir_x = target_pos.x - mob_pos.x;
let dir_z = target_pos.z - mob_pos.z;
let mut rng = mob.get_random();
let (dir_x, dir_z) = if dir_x == 0.0 && dir_z == 0.0 {
(rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0))
} else {
(dir_x, dir_z)
};
let base_angle = dir_z.atan2(dir_x) - std::f64::consts::FRAC_PI_2;
for _ in 0..TARGET_ATTEMPTS {
let angle =
base_angle + (2.0 * rng.random_range(0.0..1.0) - 1.0) * std::f64::consts::FRAC_PI_2;
let t = rng.random_range(0.0..1.0f64).sqrt();
let dist = t * HORIZONTAL_RANGE * std::f64::consts::SQRT_2;
let dx = -dist * angle.sin();
let dz = dist * angle.cos();
if dx.abs() > HORIZONTAL_RANGE || dz.abs() > HORIZONTAL_RANGE {
continue;
}
let dy = rng.random_range(-VERTICAL_RANGE..=VERTICAL_RANGE);
let candidate = BlockPos::new(
(mob_pos.x + dx) as i32,
(mob_pos.y + dy as f64) as i32,
(mob_pos.z + dz) as i32,
);
let block_at = world.get_block_state(&candidate);
let block_below = world.get_block_state(&BlockPos::new(
candidate.0.x,
candidate.0.y - 1,
candidate.0.z,
));
if block_at.is_solid() || !block_below.is_solid() {
continue;
}
return Some(Vector3::new(
candidate.0.x as f64 + 0.5,
candidate.0.y as f64,
candidate.0.z as f64 + 0.5,
));
}
None
}
}
impl Goal for MoveTowardsTargetGoal {
fn can_start(&mut self, mob: &dyn Mob) -> bool {
let target = mob.get_mob_entity().get_target();
let Some(target) = target else {
self.target = None;
return false;
};
if !target.get_entity().is_alive() {
self.target = None;
return false;
}
let mob_pos = mob.get_entity().pos.load();
let target_pos = target.get_entity().pos.load();
let dist_sq = mob_pos.squared_distance_to_vec(&target_pos);
let within_sq = f64::from(self.within) * f64::from(self.within);
if dist_sq > within_sq {
self.target = None;
return false;
}
let pos = Self::find_pos_towards(mob, &target_pos);
let Some(pos) = pos else {
self.target = None;
return false;
};
self.wanted_pos = Some(pos);
self.target = Some(target);
true
}
fn should_continue(&self, mob: &dyn Mob) -> bool {
let Some(target) = &self.target else {
return false;
};
if !target.get_entity().is_alive() {
return false;
}
let navigator = mob
.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if navigator.is_idle() {
return false;
}
let mob_pos = mob.get_entity().pos.load();
let target_pos = target.get_entity().pos.load();
let dist_sq = mob_pos.squared_distance_to_vec(&target_pos);
let within_sq = f64::from(self.within) * f64::from(self.within);
dist_sq < within_sq
}
fn start(&mut self, mob: &dyn Mob) {
if let Some(wanted_pos) = self.wanted_pos {
let mob_pos = mob.get_entity().pos.load();
let mut navigator = mob
.get_mob_entity()
.navigator
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
navigator.set_progress(NavigatorGoal::new(mob_pos, wanted_pos, self.speed));
}
}
fn stop(&mut self, _mob: &dyn Mob) {
self.target = None;
self.wanted_pos = None;
}
fn controls(&self) -> Controls {
self.goal_control
}
}
#[cfg(test)]
#[allow(clippy::unimplemented)]
mod tests {
use super::MoveTowardsTargetGoal;
use crate::entity::ai::goal::{Controls, Goal};
#[test]
fn initial_controls_and_state() {
let mut goal = MoveTowardsTargetGoal::new(1.0, 16.0);
assert_eq!(goal.controls(), Controls::MOVE);
assert!(goal.target.is_none());
assert!(goal.wanted_pos.is_none());
goal.wanted_pos = Some(pumpkin_util::math::vector3::Vector3::new(1.0, 2.0, 3.0));
assert!(goal.wanted_pos.is_some());
goal.stop(&MockMob);
assert!(goal.target.is_none());
assert!(goal.wanted_pos.is_none());
}
struct MockMob;
impl crate::entity::mob::Mob for MockMob {
fn get_mob_entity(&self) -> &crate::entity::mob::MobEntity {
unimplemented!()
}
fn mob_write_nbt(&self, _nbt: &mut pumpkin_nbt::compound::NbtCompound) {
unimplemented!()
}
fn mob_read_nbt(&self, _nbt: &pumpkin_nbt::compound::NbtCompound) {
unimplemented!()
}
}
}

View File

@ -47,7 +47,7 @@ impl TrackTargetGoal {
self
}
fn can_navigate_to_entity(&self, mob: &dyn Mob, _target: &LivingEntity) -> bool {
fn can_navigate_to_entity(&self, mob: &dyn Mob) -> bool {
let cooldown = to_goal_ticks(10 + mob.get_random().random_range(0..5));
self.check_can_navigate_cooldown
.store(cooldown, Ordering::Relaxed);
@ -95,7 +95,7 @@ impl TrackTargetGoal {
}
if self.can_navigate_flag.load(Ordering::Relaxed) == UNSET {
let can_reach = self.can_navigate_to_entity(mob, target);
let can_reach = self.can_navigate_to_entity(mob);
self.can_navigate_flag.store(
if can_reach { CAN_TRACK } else { CANNOT_TRACK },
Ordering::Relaxed,

View File

@ -94,8 +94,6 @@ impl WalkNodeEvaluator {
pos: Vector3<i32>,
max_y_step: i32,
last_feet_y: f64,
facing: (i32, i32),
current_path_type: PathType,
) -> Option<Node> {
let feet_y = self.get_floor_level(pos);
if feet_y - last_feet_y > self.get_mob_jump_height() {
@ -124,13 +122,7 @@ impl WalkNodeEvaluator {
&& path_type != PathType::Trapdoor
&& path_type != PathType::PowderSnow
{
let jump_node = self.get_jump_on_top_node(
pos,
max_y_step,
last_feet_y,
facing,
current_path_type,
);
let jump_node = self.get_jump_on_top_node(pos, max_y_step, last_feet_y);
if jump_node.is_some() {
node = jump_node;
}
@ -157,8 +149,6 @@ impl WalkNodeEvaluator {
pos: Vector3<i32>,
max_y_step: i32,
last_feet_y: f64,
_facing: (i32, i32),
_current_path_type: PathType,
) -> Option<Node> {
for dy in 1..=max_y_step {
let step_pos = Vector3::new(pos.x, pos.y + dy, pos.z);
@ -419,13 +409,7 @@ impl NodeEvaluator for WalkNodeEvaluator {
for (i, &(dx, dz)) in DIRECTIONS.iter().enumerate() {
let neighbor_pos = current.pos.0.add_raw(dx, 0, dz);
let neighbor_opt = self.find_accepted_node(
neighbor_pos,
max_y_step,
floor_level,
(dx, dz),
current.path_type,
);
let neighbor_opt = self.find_accepted_node(neighbor_pos, max_y_step, floor_level);
if let Some(neighbor) = neighbor_opt {
self.reusable_neighbors[i] = Some(neighbor);
@ -452,13 +436,7 @@ impl NodeEvaluator for WalkNodeEvaluator {
) {
let diagonal_pos = current.pos.0.add_raw(dx, 0, dz);
let diagonal_opt = self.find_accepted_node(
diagonal_pos,
max_y_step,
floor_level,
(dx, dz),
current.path_type,
);
let diagonal_opt = self.find_accepted_node(diagonal_pos, max_y_step, floor_level);
if let Some(diagonal) = diagonal_opt
&& Self::is_diagonal_node_valid(Some(&diagonal))

View File

@ -434,7 +434,6 @@ impl EnderDragonEntity {
target: Vector3<f64>,
fly_speed: f32,
turn_speed: f32,
_y_scale: f64,
) {
let xdd = target.x - pos.x;
let mut ydd = target.y - pos.y;
@ -818,13 +817,7 @@ impl EnderDragonEntity {
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(target) = target_location {
let pos = self.mob_entity.living_entity.entity.pos.load();
self.steer_toward(
pos,
target,
phase.get_fly_speed(),
phase.get_turn_speed(),
0.5,
);
self.steer_toward(pos, target, phase.get_fly_speed(), phase.get_turn_speed());
}
self.mob_entity.living_entity.entity.send_pos_rot();

View File

@ -102,7 +102,7 @@ pub fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vecto
);
}
pub async fn player_attack_sound(pos: &Vector3<f64>, world: &World, attack_type: AttackType) {
pub fn player_attack_sound(pos: &Vector3<f64>, world: &World, attack_type: AttackType) {
match attack_type {
AttackType::Knockback => {
world.play_sound(

View File

@ -21,7 +21,7 @@ impl MobEffect for RaidOmenMobEffect {
.raids
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
raids.create_or_extend_raid(player, raid_pos, &world);
raids.create_or_extend_raid(raid_pos, &world);
player.clear_raid_omen_position();
}
}

View File

@ -90,7 +90,7 @@ impl EntityBase for ExperienceOrbEntity {
entity.move_entity(caller, velo);
entity.tick_block_collisions(caller, server);
entity.tick_block_collisions(caller);
let age = self.orb_age.fetch_add(1, Ordering::Relaxed);
if age >= 6000 {

View File

@ -46,7 +46,7 @@ impl FallingEntity {
}
impl EntityBase for FallingEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
let entity = &self.entity;
let mut velo = entity.velocity.load();
velo.y -= self.get_gravity();
@ -54,7 +54,7 @@ impl EntityBase for FallingEntity {
entity.velocity.store(velo);
entity.move_entity(caller, velo);
entity.tick_block_collisions(caller, server);
entity.tick_block_collisions(caller);
if entity.on_ground.load(Ordering::Relaxed) {
entity.velocity.store(velo.multiply(0.7, -0.5, 0.7));
entity.world.load().set_block_state(

View File

@ -341,16 +341,11 @@ impl ItemEntity {
Some(tick_move)
}
fn move_and_apply_friction(
&self,
caller: &dyn EntityBase,
server: &Server,
move_velo: Vector3<f64>,
) {
fn move_and_apply_friction(&self, caller: &dyn EntityBase, move_velo: Vector3<f64>) {
let entity = &self.entity;
entity.move_entity(caller, move_velo);
entity.tick_block_collisions(caller, server);
entity.tick_block_collisions(caller);
let mut friction = 0.98;
let on_ground = entity.on_ground.load(Ordering::SeqCst);
@ -443,7 +438,7 @@ impl ItemEntity {
}
impl EntityBase for ItemEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
let entity = &self.entity;
self.decrement_pickup_delay();
@ -461,7 +456,7 @@ impl EntityBase for ItemEntity {
};
if tick_move {
self.move_and_apply_friction(caller, server, move_velo);
self.move_and_apply_friction(caller, move_velo);
}
if self.process_age_and_merge() {

View File

@ -986,7 +986,7 @@ impl LivingEntity {
world.broadcast_editioned(&je_packet, &be_packet);
}
fn tick_movement(&self, server: &Server, caller: &dyn EntityBase) {
fn tick_movement(&self, caller: &dyn EntityBase) {
if self.jumping_cooldown.load(Relaxed) != 0 {
self.jumping_cooldown.fetch_sub(1, Relaxed);
}
@ -1060,7 +1060,7 @@ impl LivingEntity {
self.travel_in_air(caller);
}
let suffocating = self.entity.tick_block_collisions(caller, server);
let suffocating = self.entity.tick_block_collisions(caller);
if suffocating {
caller.damage(caller, 1.0, DamageType::IN_WALL);
@ -2644,7 +2644,7 @@ impl EntityBase for LivingEntity {
let is_alive = !self.dead.load(Relaxed) && self.health.load() > 0.0;
let in_death_animation = self.health.load() <= 0.0 && self.death_time.load(Relaxed) < 20;
if is_alive || (in_death_animation && self.entity.entity_type != &EntityType::PLAYER) {
self.tick_movement(server, caller);
self.tick_movement(caller);
// Vanilla-like order: freeze logic runs after movement/collisions.
self.entity.tick_frozen(caller);
}

View File

@ -868,7 +868,7 @@ fn select_vanilla_armor(difficulty: &RegionalDifficulty) -> Vec<(EquipmentSlot,
first = false;
pieces.push((
slot.clone(),
create_equipment_item(tier[i], difficulty),
create_equipment_item(tier[i]),
DEFAULT_EQUIPMENT_DROP_CHANCE,
));
}
@ -878,7 +878,7 @@ fn select_vanilla_armor(difficulty: &RegionalDifficulty) -> Vec<(EquipmentSlot,
/// Creates a fresh, full-durability `ItemStack` for mob equipment.
/// Vanilla mobs always spawn with equipment at full durability.
#[must_use]
fn create_equipment_item(item: &'static Item, _difficulty: &RegionalDifficulty) -> ItemStack {
fn create_equipment_item(item: &'static Item) -> ItemStack {
ItemStack::new(1, item)
}
@ -897,7 +897,7 @@ fn equip_mob_from_def(
// ── Weapon ──
match def.weapon {
WeaponConfig::Always(item) => {
let mut stack = create_equipment_item(item, difficulty);
let mut stack = create_equipment_item(item);
if def.enchanted && difficulty.should_happen(WEAPON_ENCHANT_CHANCE) {
apply_vanilla_enchantments(
&mut stack,
@ -913,7 +913,7 @@ fn equip_mob_from_def(
}
WeaponConfig::AlwaysWeighted(items) => {
let item = weighted_select_item(items);
let mut stack = create_equipment_item(item, difficulty);
let mut stack = create_equipment_item(item);
if def.enchanted && difficulty.should_happen(WEAPON_ENCHANT_CHANCE) {
apply_vanilla_enchantments(
&mut stack,
@ -939,7 +939,7 @@ fn equip_mob_from_def(
};
if rand::random::<f32>() < chance {
let item = weighted_select_item(items);
let mut stack = create_equipment_item(item, difficulty);
let mut stack = create_equipment_item(item);
if def.enchanted && difficulty.should_happen(WEAPON_ENCHANT_CHANCE) {
apply_vanilla_enchantments(
&mut stack,
@ -977,7 +977,7 @@ fn equip_mob_from_def(
ArmorConfig::CustomPerSlot(entries) => {
for entry in entries {
if rand::random::<f32>() < entry.chance {
let mut stack = create_equipment_item(entry.item, difficulty);
let mut stack = create_equipment_item(entry.item);
if def.enchanted && difficulty.should_happen(ARMOR_ENCHANT_CHANCE) {
apply_vanilla_enchantments(
&mut stack,

View File

@ -325,7 +325,7 @@ impl ShulkerEntity {
false
}
pub fn on_shulker_damage(&self, _damage_type: DamageType) {
pub fn on_shulker_damage(&self) {
let living = &self.mob_entity.living_entity;
let health = living.health.load();
let max = living.get_max_health();
@ -386,8 +386,8 @@ impl Mob for ShulkerEntity {
}
}
fn on_damage(&self, damage_type: DamageType, _source: Option<&dyn EntityBase>) {
self.on_shulker_damage(damage_type);
fn on_damage(&self, _damage_type: DamageType, _source: Option<&dyn EntityBase>) {
self.on_shulker_damage();
}
/// When closed, block arrows entirely.

View File

@ -216,7 +216,7 @@ pub trait EntityBase: Send + Sync + std::any::Any {
pitch: Option<f32>,
world: Arc<World>,
) {
self.get_entity().teleport(position, yaw, pitch, world);
self.get_entity().teleport(position, yaw, pitch, &world);
}
fn is_pushed_by_fluids(&self) -> bool {
@ -1502,7 +1502,7 @@ impl Entity {
}
#[expect(dead_code)]
fn tick_block_underneath(_caller: &dyn EntityBase) {
const fn tick_block_underneath() {
// let world = self.world.read();
// let (pos, block, state) = self.get_block_with_y_offset(0.2);
@ -1565,7 +1565,7 @@ impl Entity {
*/
}
pub fn tick_block_collisions(&self, caller: &dyn EntityBase, _server: &Server) -> bool {
pub fn tick_block_collisions(&self, caller: &dyn EntityBase) -> bool {
if !self.is_affected_by_blocks() {
return false;
}
@ -2313,7 +2313,6 @@ impl Entity {
let world_clone = self.world.load_full();
let portal_type = portal_processor.portal_type;
let dest_world_opt = portal_processor.destination_world.clone();
let entry_pos = portal_processor.entry_position;
let src_portal = portal_processor.source_portal.clone();
let entity_id = self.entity_id;
let yaw = self.yaw.load();
@ -2326,7 +2325,6 @@ impl Entity {
&world_clone,
dest_world_opt,
entity_arc.as_ref(),
entry_pos,
src_portal.as_ref(),
);
@ -2395,7 +2393,7 @@ impl Entity {
}
}
pub fn try_use_portal(&self, _portal_delay: u32, portal_world: Arc<World>, pos: BlockPos) {
pub fn try_use_portal(&self, portal_world: Arc<World>, pos: BlockPos) {
let mut portal_event =
crate::plugin::api::events::entity::entity_portal::EntityPortalEvent::new(
self.entity_id,
@ -3109,7 +3107,7 @@ impl Entity {
position: Vector3<f64>,
yaw: Option<f32>,
pitch: Option<f32>,
_world: Arc<World>,
world: &World,
) {
// Update server-side position and bounding box
self.set_pos(position);
@ -3132,7 +3130,7 @@ impl Entity {
.store((pitch * 256.0 / 360.0).rem_euclid(256.0) as u8, Relaxed);
}
let chunk_pos = self.chunk_pos.load();
self.world.load().broadcast_to_chunk(
world.broadcast_to_chunk(
chunk_pos,
&CEntityPositionSync::new(
self.entity_id.into(),
@ -3429,16 +3427,16 @@ impl Entity {
self.remove_passenger_on_disconnect(passenger_id);
}
pub async fn remove_passenger(&self, passenger_id: i32) {
self.remove_passenger_internal(passenger_id, true).await;
pub fn remove_passenger(&self, passenger_id: i32) {
self.remove_passenger_internal(passenger_id, true);
}
pub async fn remove_passenger_before_teleport(&self, passenger_id: i32) {
self.remove_passenger_internal(passenger_id, false).await;
pub fn remove_passenger_before_teleport(&self, passenger_id: i32) {
self.remove_passenger_internal(passenger_id, false);
}
#[allow(clippy::too_many_lines)]
async fn remove_passenger_internal(&self, passenger_id: i32, reposition: bool) {
fn remove_passenger_internal(&self, passenger_id: i32, reposition: bool) {
let mut dismount_event =
crate::plugin::api::events::entity::entity_dismount::EntityDismountEvent::new(
passenger_id,
@ -3452,9 +3450,10 @@ impl Entity {
if let Some(server) = self.world.load().server.upgrade() {
server
.plugin_manager
.fire(&server, &mut dismount_event)
.await;
server.plugin_manager.fire(&server, &mut vehicle_exit).await;
.fire_blocking(&server, &mut dismount_event);
server
.plugin_manager
.fire_blocking(&server, &mut vehicle_exit);
}
if dismount_event.cancelled || vehicle_exit.cancelled {
return;
@ -3521,13 +3520,7 @@ impl Entity {
let world = self.world.load();
let passengers_packet = CSetPassengers::new(VarInt(self.entity_id), &passenger_ids);
if let Some(player) = passenger.get_player() {
if reposition {
player.send_client_packet(&passengers_packet).await;
} else if let ClientPlatform::Java(client) = player.client.as_ref()
&& let Ok(data) = client.serialize_packet(&passengers_packet)
{
client.send_packet_now(data).await;
}
player.try_send_client_packet(&passengers_packet);
world.broadcast_to_chunk_except(
chunk_pos,
&[player.get_entity().entity_uuid],
@ -3729,22 +3722,20 @@ impl Entity {
// the same packet queue as CSetPassengers, preserving send order.
// Vanilla uses DELTA | ROT flags: position absolute, delta/rotation relative.
// With rotation relative and yaw/pitch=0, the client preserves its current look.
player
.send_client_packet(&CPlayerPosition::new(
id.into(),
dismount_pos,
Vector3::new(0.0, 0.0, 0.0),
0.0,
0.0,
vec![
PositionFlag::DeltaX,
PositionFlag::DeltaY,
PositionFlag::DeltaZ,
PositionFlag::YRot,
PositionFlag::XRot,
],
))
.await;
player.try_send_client_packet(&CPlayerPosition::new(
id.into(),
dismount_pos,
Vector3::new(0.0, 0.0, 0.0),
0.0,
0.0,
vec![
PositionFlag::DeltaX,
PositionFlag::DeltaY,
PositionFlag::DeltaZ,
PositionFlag::YRot,
PositionFlag::XRot,
],
));
}
// Vanilla: setSneaking(false) after dismount via sneak input

View File

@ -16,7 +16,8 @@ use crate::entity::{
ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal,
offer_flower::OfferFlowerGoal, revenge::RevengeGoal, wander_around::WanderAroundGoal,
move_towards_target::MoveTowardsTargetGoal, offer_flower::OfferFlowerGoal,
revenge::RevengeGoal, wander_around::WanderAroundGoal,
},
mob::{Mob, MobEntity},
player::Player,
@ -60,17 +61,18 @@ impl IronGolemEntity {
.unwrap_or_else(std::sync::PoisonError::into_inner);
goal_selector.add_goal(1, Box::new(MeleeAttackGoal::new(1.0, true)));
goal_selector.add_goal(2, Box::new(MoveTowardsTargetGoal::new(0.9, 32.0)));
goal_selector.add_goal(4, Box::new(WanderAroundGoal::new(0.6)));
goal_selector.add_goal(5, Box::new(OfferFlowerGoal::new()));
goal_selector.add_goal(6, Box::new(WanderAroundGoal::new(0.6)));
goal_selector.add_goal(
7,
LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 6.0),
);
goal_selector.add_goal(8, Box::new(RandomLookAroundGoal::default()));
target_selector.add_goal(1, Box::new(RevengeGoal::new(true)));
target_selector.add_goal(2, Box::new(RevengeGoal::new(true)));
target_selector.add_goal(
2,
3,
ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::PLAYER, false),
);
target_selector.add_goal(

View File

@ -16,9 +16,11 @@ use advancement::PlayerAdvancement;
use arc_swap::ArcSwap;
use crossbeam::atomic::AtomicCell;
use crossbeam::channel::Receiver;
use crossbeam::queue::SegQueue;
use pumpkin_data::dimension::Dimension;
use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
use pumpkin_inventory::player::ender_chest_inventory::EnderChestInventory;
use pumpkin_protocol::RawPacket;
use pumpkin_protocol::bedrock::client::play_status::CPlayStatus;
use pumpkin_protocol::bedrock::client::set_time::CSetTime;
use pumpkin_protocol::bedrock::client::update_abilities::{Ability, CUpdateAbilities};
@ -80,8 +82,8 @@ impl JavaPlayer<'_> {
self.send_packet(&packet).await;
}
pub async fn send_stats(&self) {
self.0.send_stats().await;
pub fn send_stats(&self) {
self.0.send_stats();
}
pub fn set_scoreboard(&self, scoreboard: Option<Scoreboard>) {
@ -258,15 +260,15 @@ use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::codec::var_long::VarLong;
use pumpkin_protocol::codec::var_ulong::VarULong;
use pumpkin_protocol::java::client::play::{
Animation, CActionBar, CAwardStats, CChangeDifficulty, CCloseContainer, CCombatDeath,
CCustomPayload, CDisguisedChatMessage, CEntityAnimation, CEntityPositionSync, CGameEvent,
CItemCooldown, CMapItemData, COpenScreen, CParticle, CPlayerAbilities, CPlayerInfoUpdate,
CPlayerPosition, CPlayerSpawnPosition, CRespawn, CSetCamera, CSetContainerContent,
CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetExperience, CSetHealth,
CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle, CSystemChatMessage,
CTabList, CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent,
MapIcon, MapPatch, Metadata, PlayerAction, PlayerInfoFlags, PlayerSpawnData, PreviousMessage,
Statistic,
Animation, CAcknowledgeBlockChange, CActionBar, CAwardStats, CChangeDifficulty,
CCloseContainer, CCombatDeath, CCustomPayload, CDisguisedChatMessage, CEntityAnimation,
CEntityPositionSync, CGameEvent, CItemCooldown, CMapItemData, COpenScreen, CParticle,
CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn,
CSetCamera, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
CSetExperience, CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound,
CSubtitle, CSystemChatMessage, CTabList, CTitleAnimation, CTitleText, CUnloadChunk,
CUpdateMobEffect, CUpdateTime, GameEvent, MapIcon, MapPatch, Metadata, PlayerAction,
PlayerInfoFlags, PlayerSpawnData, PreviousMessage, Statistic,
};
use pumpkin_protocol::java::server::play::{
SClickSlot, SContainerButtonClick, SRenameItem, SlotActionType,
@ -827,6 +829,8 @@ pub struct Player {
pub seen_credits: AtomicBool,
pub score: AtomicI32,
pub spawn_extra_particles_on_fall: AtomicBool,
/// Inbound packets waiting to be processed during player tick.
pub inbound_packets: SegQueue<RawPacket>,
}
use base64::prelude::*;
@ -1116,6 +1120,7 @@ impl Player {
seen_credits: AtomicBool::new(false),
score: AtomicI32::new(0),
spawn_extra_particles_on_fall: AtomicBool::new(false),
inbound_packets: SegQueue::new(),
}
}
@ -1123,13 +1128,12 @@ impl Player {
///
/// Note: Tab list header and footer formatting is a Java Edition-specific protocol feature
/// and is safely ignored on Bedrock Edition clients.
pub async fn set_tab_list(&self, tab_list: impl Into<crate::plugin::api::tab_list::TabList>) {
pub fn set_tab_list(&self, tab_list: impl Into<crate::plugin::api::tab_list::TabList>) {
let list = tab_list.into();
self.set_tab_list_header_footer(list.header, list.footer)
.await;
self.set_tab_list_header_footer(&list.header, &list.footer);
}
pub async fn set_tab_list_header_footer(&self, header: TextComponent, footer: TextComponent) {
pub fn set_tab_list_header_footer(&self, header: &TextComponent, footer: &TextComponent) {
*self
.tab_list_header
.lock()
@ -1138,8 +1142,7 @@ impl Player {
.tab_list_footer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = footer.clone();
self.send_client_packet(&CTabList::new(&header, &footer))
.await;
self.try_send_client_packet(&CTabList::new(header, footer));
}
pub fn start_cooldown(&self, group: String, duration: i32) {
@ -1394,7 +1397,7 @@ impl Player {
}
#[expect(clippy::too_many_lines)]
pub async fn attack(&self, victim: Arc<dyn EntityBase>) {
pub fn attack(&self, victim: &Arc<dyn EntityBase>) {
let world = self.world();
let Some(server) = world.server.upgrade() else {
return;
@ -1522,7 +1525,7 @@ impl Player {
}
if !victim.damage_with_context(
&*victim,
victim.as_ref(),
damage as f32,
if is_mace_smash {
DamageType::MACE_SMASH
@ -1567,7 +1570,7 @@ impl Player {
);
}
player_attack_sound(&pos, &world, attack_type).await;
player_attack_sound(&pos, &world, attack_type);
self.living_entity.last_attacking_id.store(
victim_entity.entity_id,
@ -1947,9 +1950,7 @@ impl Player {
// Try positions around the bed based on facing direction
// Vanilla tries multiple offset patterns; we use a simplified version
if let Some(spawn_pos) =
Self::find_bed_spawn_position(&world, pos, facing, respawn_point.yaw)
{
if let Some(spawn_pos) = Self::find_bed_spawn_position(&world, pos, facing) {
return Some(CalculatedRespawnPoint {
position: spawn_pos,
yaw: respawn_point.yaw,
@ -2002,7 +2003,6 @@ impl Player {
world: &Arc<crate::world::World>,
bed_pos: &BlockPos,
facing: HorizontalFacing,
_spawn_angle: f32,
) -> Option<Vector3<f64>> {
// Get offsets based on bed facing direction (vanilla-like order)
let offsets = Self::get_bed_spawn_offsets(facing);
@ -2427,8 +2427,77 @@ impl Player {
self.try_send_client_packet(&packet);
}
pub fn process_inbound_packets(&self) {
const MAX_PACKETS_PER_TICK: usize = 64;
let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id) else {
return;
};
let Some(server_arc) = self.world().server.upgrade() else {
return;
};
let mut count = 0;
while let Some(packet) = self.inbound_packets.pop() {
if self.client.closed() {
break;
}
match self.client.as_ref() {
ClientPlatform::Java(client) => {
if let Err(e) = client.handle_play_packet(&player_arc, &server_arc, &packet) {
if e.is_kick() {
if let Some(kick_reason) = e.client_kick_reason() {
client.try_kick(&TextComponent::text(kick_reason));
} else {
client.try_kick(&TextComponent::text(format!(
"Error while handling incoming packet {e}"
)));
}
}
tracing::error!(
"Failed to handle play packet id {} (payload {} bytes): {}",
packet.id,
packet.payload.len(),
e
);
}
let seq = client.packet_sequence.swap(-1, Ordering::Relaxed);
if seq != -1 {
client.try_send_packet(&CAcknowledgeBlockChange::new(seq.into()));
}
}
ClientPlatform::Bedrock(client) => {
let mut event = crate::plugin::server::packet::PacketReceivedEvent::new(
player_arc.clone(),
packet.id,
packet.payload.clone(),
);
server_arc
.plugin_manager
.fire_blocking(&server_arc, &mut event);
if !event.cancelled
&& let Err(err) =
client.handle_play_packet(&player_arc, &server_arc, &packet)
{
tracing::error!("Failed to handle Bedrock play packet: {err}");
}
}
}
count += 1;
if count >= MAX_PACKETS_PER_TICK {
break;
}
}
}
#[expect(clippy::too_many_lines)]
pub fn tick<'a>(&'a self, server: &'a Server) {
self.process_inbound_packets();
if self.is_spectator() {
self.living_entity
.entity
@ -2451,9 +2520,7 @@ impl Player {
if player_pos != target_pos {
self.living_entity.entity.set_pos(target_pos);
if let Some(p) = self.world().get_player_by_uuid(self.gameprofile.id) {
server.spawn_task(async move {
crate::world::chunker::update_position(&p).await;
});
crate::world::chunker::update_position(&p);
}
}
} else {
@ -2525,7 +2592,7 @@ impl Player {
&& !chunk_of_chunks.is_empty()
{
let client = self.client.clone();
server.spawn_task(async move {
self.spawn_task(async move {
client.send_chunks(&chunk_of_chunks).await;
});
if let ClientPlatform::Bedrock(bedrock_client) = self.client.as_ref()
@ -2952,7 +3019,7 @@ impl Player {
}
}
pub async fn send_stats(&self) {
pub fn send_stats(&self) {
if let ClientPlatform::Java(java) = self.client.as_ref() {
let packet_stats: Vec<Statistic> = {
let stats_guard = self
@ -2974,7 +3041,7 @@ impl Player {
stats: &packet_stats,
};
if let Ok(data) = java.serialize_packet(&packet) {
java.enqueue_packet(data).await;
java.try_enqueue_packet(data);
}
}
}
@ -3343,11 +3410,10 @@ impl Player {
sb.get_entity_team(&self.gameprofile.name).cloned()
}
pub async fn set_compass_target(&self, pos: pumpkin_util::math::position::BlockPos) {
pub fn set_compass_target(&self, pos: pumpkin_util::math::position::BlockPos) {
use pumpkin_protocol::java::client::play::CPlayerSpawnPosition;
self.compass_target.store(Some(pos));
self.send_client_packet(&CPlayerSpawnPosition::new(pos, 0.0, 0.0, String::new()))
.await;
self.try_send_client_packet(&CPlayerSpawnPosition::new(pos, 0.0, 0.0, String::new()));
}
pub fn get_compass_target(&self) -> Option<pumpkin_util::math::position::BlockPos> {
@ -3636,7 +3702,7 @@ impl Player {
self.send_health();
new_world.send_world_info(&player, position, yaw, pitch).await;
new_world.send_world_info(&player, position, yaw, pitch);
}
}}
}
@ -4025,14 +4091,16 @@ impl Player {
self.hunger_manager.level.load()
}
pub async fn set_food_level(&self, food_level: u8) {
pub fn set_food_level(&self, food_level: u8) {
let mut food_event =
crate::plugin::api::events::entity::food_level_change::FoodLevelChangeEvent::new(
self.living_entity.entity.entity_id,
food_level,
);
if let Some(server) = self.world().server.upgrade() {
server.plugin_manager.fire(&server, &mut food_event).await;
server
.plugin_manager
.fire_blocking(&server, &mut food_event);
}
if food_event.cancelled {
return;
@ -4518,7 +4586,7 @@ impl Player {
self.world().spawn_entity(item_entity);
}
pub async fn drop_held_item(&self, drop_stack: bool) {
pub fn drop_held_item(&self, drop_stack: bool) {
let mut item_stack = self.inventory().held_item();
if item_stack.is_empty() {
@ -4537,7 +4605,7 @@ impl Player {
dropped_stack.item.registry_key.to_string(),
dropped_stack.item_count,
);
server.plugin_manager.fire(&server, &mut event).await;
server.plugin_manager.fire_blocking(&server, &mut event);
if event.cancelled {
return;
}
@ -4564,12 +4632,12 @@ impl Player {
}
}
pub async fn swap_item(&self) {
pub fn swap_item(&self) {
if let Some(server) = self.world().server.upgrade()
&& let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
{
let mut event = crate::plugin::api::events::player::player_swap_hands::PlayerSwapHandItemsEvent::new(player_arc);
server.plugin_manager.fire(&server, &mut event).await;
server.plugin_manager.fire_blocking(&server, &mut event);
if event.cancelled {
return;
}
@ -5096,7 +5164,7 @@ impl Player {
screen_handler.update_sync_handler(self.screen_handler_sync_handler.clone());
}
pub async fn on_rename_item(self: &Arc<Self>, packet: SRenameItem<'_>) {
pub fn on_rename_item(self: &Arc<Self>, packet: &SRenameItem<'_>) {
self.update_last_action_time();
let mut prepare_event =
@ -5108,8 +5176,7 @@ impl Player {
if let Some(server) = self.world().server.upgrade() {
server
.plugin_manager
.fire(&server, &mut prepare_event)
.await;
.fire_blocking(&server, &mut prepare_event);
}
let screen_handler_arc = self
@ -5284,7 +5351,7 @@ impl Player {
}
#[allow(clippy::too_many_lines)]
pub async fn on_slot_click(self: &Arc<Self>, packet: SClickSlot, server: &Arc<Server>) {
pub fn on_slot_click(self: &Arc<Self>, packet: SClickSlot, server: &Arc<Server>) {
self.update_last_action_time();
let (
@ -5433,7 +5500,7 @@ impl Player {
SlotActionType::PickupAll => ClickType::DoubleClick,
};
send_cancellable! {{
send_cancellable_blocking! {{
server;
InventoryClickEvent::new(
self,
@ -5459,8 +5526,7 @@ impl Player {
if let Some(server) = self.world().server.upgrade() {
server
.plugin_manager
.fire(&server, &mut interact_event)
.await;
.fire_blocking(&server, &mut interact_event);
}
if interact_event.cancelled {
cancel_screen();
@ -5482,8 +5548,12 @@ impl Player {
stack.item.registry_key.to_string(),
);
if let Some(server) = self.world().server.upgrade() {
server.plugin_manager.fire(&server, &mut craft_event).await;
server.plugin_manager.fire(&server, &mut prep_craft).await;
server
.plugin_manager
.fire_blocking(&server, &mut craft_event);
server
.plugin_manager
.fire_blocking(&server, &mut prep_craft);
}
if craft_event.cancelled || prep_craft.cancelled {
cancel_screen();
@ -5507,8 +5577,12 @@ impl Player {
Some(stack.item.registry_key.to_string()),
);
if let Some(server) = self.world().server.upgrade() {
server.plugin_manager.fire(&server, &mut smith_event).await;
server.plugin_manager.fire(&server, &mut prep_smith).await;
server
.plugin_manager
.fire_blocking(&server, &mut smith_event);
server
.plugin_manager
.fire_blocking(&server, &mut prep_smith);
}
if smith_event.cancelled {
cancel_screen();
@ -5534,8 +5608,7 @@ impl Player {
if let Some(server) = self.world().server.upgrade() {
server
.plugin_manager
.fire(&server, &mut extract_event)
.await;
.fire_blocking(&server, &mut extract_event);
}
}
@ -5550,8 +5623,7 @@ impl Player {
if let Some(server) = self.world().server.upgrade() {
server
.plugin_manager
.fire(&server, &mut prep_grindstone)
.await;
.fire_blocking(&server, &mut prep_grindstone);
}
}
@ -5562,7 +5634,9 @@ impl Player {
if stack.is_empty() { None } else { Some(stack.item.registry_key.to_string()) },
);
if let Some(server) = self.world().server.upgrade() {
server.plugin_manager.fire(&server, &mut prep_result).await;
server
.plugin_manager
.fire_blocking(&server, &mut prep_result);
}
}
@ -5572,7 +5646,9 @@ impl Player {
self.clone(),
);
if let Some(server) = self.world().server.upgrade() {
server.plugin_manager.fire(&server, &mut drag_event).await;
server
.plugin_manager
.fire_blocking(&server, &mut drag_event);
}
if drag_event.cancelled {
cancel_screen();

View File

@ -80,8 +80,8 @@ impl EntityBase for EggEntity {
);
}
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -46,8 +46,8 @@ impl EnderPearlEntity {
}
impl EntityBase for EnderPearlEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -13,7 +13,6 @@ use crate::{
entity::{
Entity, EntityBase,
projectile::{ProjectileHit, ThrownItemEntity},
projectile_deflection::ProjectileDeflectionType,
},
server::Server,
};
@ -145,7 +144,7 @@ impl FireballEntity {
self.explosion_power.store(power, Ordering::Relaxed);
}
pub fn on_deflection(&self, _deflection: &ProjectileDeflectionType, by_attack: bool) {
pub fn on_deflection(&self, by_attack: bool) {
if by_attack {
self.set_acceleration_power(INITIAL_ACCELERATION_POWER);
} else {
@ -206,7 +205,7 @@ impl EntityBase for FireballEntity {
);
}
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
let entity = self.get_entity();
let mut velocity = entity.velocity.load();
@ -227,7 +226,7 @@ impl EntityBase for FireballEntity {
entity.velocity.store(velocity);
}
self.thrown.process_tick(caller, server);
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -91,8 +91,8 @@ impl FireworkRocketEntity {
}
impl EntityBase for FireworkRocketEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.entity.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.entity.process_tick(caller);
let entity = self.get_entity();
let world = entity.world.load();

View File

@ -91,7 +91,7 @@ impl FishingBobberEntity {
}
#[expect(clippy::too_many_lines)]
pub fn process_tick(&self, caller: &dyn EntityBase, _server: &Server) {
pub fn process_tick(&self, caller: &dyn EntityBase) {
let entity = self.get_entity();
let world = entity.world.load();
@ -233,7 +233,7 @@ impl EntityBase for FishingBobberEntity {
self.has_hit.store(true, Ordering::Relaxed);
}
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.process_tick(caller);
}
}

View File

@ -83,8 +83,8 @@ impl EntityBase for LingeringPotionEntity {
);
}
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -56,12 +56,12 @@ impl LlamaSpitEntity {
}
impl EntityBase for LlamaSpitEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
if self.get_entity().touching_water.load(Ordering::Relaxed) {
self.get_entity().remove();
return;
}
self.thrown.process_tick(caller, server);
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -1,5 +1,4 @@
use super::{Entity, EntityBase, living::LivingEntity};
use crate::server::Server;
use pumpkin_data::BlockDirection;
use pumpkin_data::entity::EntityType;
use pumpkin_protocol::java::client::play::CEntityVelocity;
@ -68,15 +67,7 @@ impl ThrownItemEntity {
}
}
pub fn set_velocity_from(
&self,
_shooter: &Entity,
pitch: f32,
yaw: f32,
roll: f32,
speed: f32,
divergence: f32,
) {
pub fn set_velocity_from(&self, pitch: f32, yaw: f32, roll: f32, speed: f32, divergence: f32) {
let yaw_rad = yaw.to_radians();
let pitch_rad = pitch.to_radians();
let roll_rad = (pitch + roll).to_radians();
@ -118,7 +109,7 @@ impl ThrownItemEntity {
impl ThrownItemEntity {
/// Process a tick for projectile movement and collisions
pub fn process_tick(&self, caller: &dyn EntityBase, _server: &Server) {
pub fn process_tick(&self, caller: &dyn EntityBase) {
let entity = self.get_entity();
let world = entity.world.load();

View File

@ -36,8 +36,8 @@ impl SmallFireballEntity {
}
impl EntityBase for SmallFireballEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -41,8 +41,8 @@ impl SnowballEntity {
}
impl EntityBase for SnowballEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -118,8 +118,8 @@ impl EntityBase for SplashPotionEntity {
);
}
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -105,7 +105,6 @@ impl WindChargeEntity {
&mut self,
deflection: &ProjectileDeflectionType,
deflector: Option<&dyn EntityBase>,
_from_attack: bool,
) -> bool {
if let Some(cooldown) = self.deflect_cooldown()
&& cooldown.load(Ordering::Relaxed) > 0
@ -126,8 +125,8 @@ impl WindChargeEntity {
}
impl EntityBase for WindChargeEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown_item_entity.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.thrown_item_entity.process_tick(caller);
if let Some(cooldown) = self.deflect_cooldown() {
let cooldown_ticks = cooldown.load(Ordering::Relaxed);

View File

@ -101,8 +101,8 @@ impl EntityBase for WitherSkullEntity {
);
}
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
self.thrown.process_tick(caller, server);
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
self.thrown.process_tick(caller);
}
fn get_entity(&self) -> &Entity {

View File

@ -29,14 +29,14 @@ impl TNTEntity {
}
impl EntityBase for TNTEntity {
fn tick(&self, caller: &dyn EntityBase, server: &Server) {
fn tick(&self, caller: &dyn EntityBase, _server: &Server) {
let entity = &self.entity;
let mut velo = entity.velocity.load();
velo.y -= self.get_gravity();
entity.move_entity(caller, velo);
entity.tick_block_collisions(caller, server);
entity.tick_block_collisions(caller);
// Read back what actually happened instead of reusing the pre-move
// value: `move_entity` clamps on collision, and an explosion may have

View File

@ -40,8 +40,7 @@ impl ItemBehaviour for EggItem {
egg.set_item_stack(item_stack);
let (yaw, pitch) = player.rotation();
egg.thrown
.set_velocity_from(player.get_entity(), pitch, yaw, 0.0, POWER, 1.0);
egg.thrown.set_velocity_from(pitch, yaw, 0.0, POWER, 1.0);
world.spawn_entity(Arc::new(egg));
// Consume item

View File

@ -41,7 +41,7 @@ impl ItemBehaviour for EnderPearlItem {
let (yaw, pitch) = player.rotation();
pearl
.thrown
.set_velocity_from(player.get_entity(), pitch, yaw, ROLL, POWER, DIVERGENCE);
.set_velocity_from(pitch, yaw, ROLL, POWER, DIVERGENCE);
world.spawn_entity(Arc::new(pearl));
// Consume item

View File

@ -75,9 +75,7 @@ impl ItemBehaviour for SplashPotionItem {
splash.set_item_stack(stack);
let (yaw, pitch) = player.rotation();
splash
.thrown
.set_velocity_from(player.get_entity(), pitch, yaw, 0.0, POWER, 1.0);
splash.thrown.set_velocity_from(pitch, yaw, 0.0, POWER, 1.0);
world.spawn_entity(Arc::new(splash));
@ -129,8 +127,7 @@ impl ItemBehaviour for LingeringPotionItem {
ling.set_item_stack(stack);
let (yaw, pitch) = player.rotation();
ling.thrown
.set_velocity_from(player.get_entity(), pitch, yaw, 0.0, POWER, 1.0);
ling.thrown.set_velocity_from(pitch, yaw, 0.0, POWER, 1.0);
world.spawn_entity(Arc::new(ling));

View File

@ -33,7 +33,7 @@ impl ItemBehaviour for SnowBallItem {
let (yaw, pitch) = player.rotation();
snowball
.thrown
.set_velocity_from(player.get_entity(), pitch, yaw, 0.0, POWER, 1.0);
.set_velocity_from(pitch, yaw, 0.0, POWER, 1.0);
world.spawn_entity(Arc::new(snowball));
// Consume item

View File

@ -37,7 +37,7 @@ impl ItemBehaviour for WindChargeItem {
let wind_charge = ThrownItemEntity::new(entity, player.get_entity(), WIND_CHARGE_GRAVITY);
let (yaw, pitch) = player.rotation();
wind_charge.set_velocity_from(player.get_entity(), pitch, yaw, 0.0, POWER, 1.0);
wind_charge.set_velocity_from(pitch, yaw, 0.0, POWER, 1.0);
// TODO: player.incrementStat(Stats.USED)
// TODO: Implement that the projectile will explode on impact

View File

@ -565,26 +565,25 @@ impl PumpkinServer {
java_client.start_outgoing_packet_task();
if let Some((player, world)) = server_clone
.add_player(Arc::new(ClientPlatform::Java(java_client)), profile, Some(config))
.await
{
.add_player(Arc::new(ClientPlatform::Java(java_client)), profile, Some(config))
{
if let ClientPlatform::Java(client) = player.client.as_ref() {
client.set_player(player.clone());
}
world
.spawn_java_player(&server_clone.basic_config, &player, &server_clone)
.await;
if let ClientPlatform::Java(client) = player.client.as_ref() {
client.set_player(player.clone());
}
world
.spawn_java_player(&server_clone.basic_config, &player, &server_clone)
.await;
if let ClientPlatform::Java(client) = player.client.as_ref() {
client.progress_player_packets(&player, &server_clone).await;
if let ClientPlatform::Java(client) = player.client.as_ref() {
client.progress_player_packets(&player, &server_clone).await;
// Close when done
client.close();
client.await_tasks().await;
}
player.remove().await;
server_clone.remove_player(&player).await;
// Close when done
client.close();
client.await_tasks().await;
}
player.remove().await;
server_clone.remove_player(&player);
if let Err(e) = server_clone
.player_data_storage
.handle_player_leave(&player)
@ -672,20 +671,17 @@ impl PumpkinServer {
client.await_tasks().await;
}
PacketHandlerResult::ReadyToPlay(profile, config) => {
if let Some((player, _world)) = server
.add_player(
Arc::new(ClientPlatform::Bedrock(client.clone())),
profile,
Some(config),
)
.await
{
if let Some((player, _world)) = server.add_player(
Arc::new(ClientPlatform::Bedrock(client.clone())),
profile,
Some(config),
) {
client.set_player(player.clone());
client.progress_player_packets(&player, &server).await;
client.progress_player_packets(&player).await;
client.close().await;
client.await_tasks().await;
player.remove().await;
server.remove_player(&player).await;
server.remove_player(&player);
if let Err(error) = server.player_data_storage.handle_player_leave(&player)
{
error!("Failed to save player data on disconnect: {error}");

View File

@ -21,7 +21,7 @@ impl BedrockClient {
let player_data = if server.advanced_config.networking.bedrock.online_mode {
match auth_payload.authentication_type {
AuthenticationType::Full => {
verify_oidc_token_path(server, &auth_payload.token, false)?
verify_oidc_token_path(server, &auth_payload.token, false).await?
}
AuthenticationType::SelfSigned => {
if server
@ -34,7 +34,7 @@ impl BedrockClient {
return Err(LoginError::SelfSignedNotAllowed);
}
verify_oidc_token_path(server, &auth_payload.token, true)?
verify_oidc_token_path(server, &auth_payload.token, true).await?
}
AuthenticationType::Guest => {
return Err(LoginError::GuestUnimplemented);

View File

@ -60,27 +60,40 @@ struct AuthPayload {
token: String,
}
/// Verifies OIDC tokens for Bedrock 1.26.10+ clients.
fn verify_oidc_token_path(
/// Verifies OIDC tokens for Bedrock 1.26.10+ clients on Rayon thread pool.
async fn verify_oidc_token_path(
server: &Server,
token: &str,
self_signed: bool,
) -> Result<pumpkin_util::jwt::PlayerClaims, LoginError> {
let token = token.to_string();
let (tx, rx) = tokio::sync::oneshot::channel();
if self_signed {
pumpkin_util::jwt::verify_oidc_token_self_signed(token)
.map_err(LoginError::ChainValidationFailed)
rayon::spawn(move || {
let res = pumpkin_util::jwt::verify_oidc_token_self_signed(&token)
.map_err(LoginError::ChainValidationFailed);
let _ = tx.send(res);
});
} else {
let (issuer, jwks) =
server
.bedrock_oidc_keys
.get()
.cloned()
.ok_or(LoginError::ChainValidationFailed(
AuthError::PublicKeyBuild("OIDC keys not initialized".into()),
))?;
pumpkin_util::jwt::verify_oidc_token(token, issuer, jwks)
.map_err(LoginError::ChainValidationFailed)
rayon::spawn(move || {
let res = pumpkin_util::jwt::verify_oidc_token(&token, &issuer, &jwks)
.map_err(LoginError::ChainValidationFailed);
let _ = tx.send(res);
});
}
rx.await.map_err(|_| {
LoginError::ChainValidationFailed(AuthError::PublicKeyBuild("Task cancelled".into()))
})?
}
#[allow(clippy::module_inception)]

View File

@ -117,7 +117,7 @@ pub struct BedrockClient {
pub next_form_id: AtomicU32,
pub inventory_opened: AtomicBool,
pub client_cache_supported: AtomicBool,
pub blob_cache: Mutex<HashMap<u64, Vec<u8>>>,
pub blob_cache: std::sync::Mutex<HashMap<u64, Vec<u8>>>,
/// An notifier that is triggered when this client is closed.
close_token: CancellationToken,
last_seen: Arc<AtomicCell<std::time::Instant>>,
@ -157,7 +157,7 @@ impl BedrockClient {
next_form_id: AtomicU32::new(0),
inventory_opened: AtomicBool::new(false),
client_cache_supported: AtomicBool::new(false),
blob_cache: Mutex::new(HashMap::new()),
blob_cache: std::sync::Mutex::new(HashMap::new()),
close_token: CancellationToken::new(),
last_seen: Arc::new(AtomicCell::new(std::time::Instant::now())),
incoming_game_packet_send: incoming_send,
@ -371,7 +371,10 @@ impl BedrockClient {
};
if !new_blobs.is_empty() {
let mut cache = self.blob_cache.lock().await;
let mut cache = self
.blob_cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for (hash, payload) in new_blobs {
cache.insert(hash, payload);
}
@ -501,6 +504,13 @@ impl BedrockClient {
}
}
pub fn try_enqueue_client_packet<P: BClientPacket>(&self, packet: &P) {
match self.serialize_packet(packet) {
Ok(data) => self.try_enqueue_packet(data),
Err(err) => error!("Failed to serialize Bedrock packet: {err}"),
}
}
pub async fn send_game_packet(&self, packet_data: Bytes) {
let (tx, rx) = oneshot::channel();
if let Err(err) = self
@ -577,17 +587,13 @@ impl BedrockClient {
return Err(Error::other("Packet rate limit exceeded"));
}
self.handle_game_packet(server, game_packet).await?;
self.handle_game_packet(game_packet).await?;
}
Ok(())
}
async fn handle_game_packet(
&self,
_server: &Arc<Server>,
packet: RawPacket,
) -> Result<(), Error> {
async fn handle_game_packet(&self, packet: RawPacket) -> Result<(), Error> {
if let Err(err) = self.incoming_game_packet_send.send(packet).await {
debug!("Failed to send game packet to session task: {err}");
}
@ -642,34 +648,18 @@ impl BedrockClient {
PacketHandlerResult::Stop
}
pub async fn progress_player_packets(
self: &Arc<Self>,
player: &Arc<Player>,
server: &Arc<Server>,
) {
pub async fn progress_player_packets(self: &Arc<Self>, player: &Arc<Player>) {
while let Some(packet) = self.get_packet().await {
let mut event = crate::plugin::server::packet::PacketReceivedEvent::new(
player.clone(),
packet.id,
packet.payload.clone(),
);
server.plugin_manager.fire(server, &mut event).await;
if event.cancelled {
continue;
}
if let Err(err) = self.handle_play_packet(player, server, packet).await {
error!("Failed to handle Bedrock play packet: {err}");
}
player.inbound_packets.push(packet);
}
}
#[allow(clippy::too_many_lines)]
pub async fn handle_play_packet(
&self,
pub fn handle_play_packet(
self: &Arc<Self>,
player: &Arc<Player>,
server: &Arc<Server>,
packet: RawPacket,
packet: &RawPacket,
) -> Result<(), Error> {
let payload = &packet.payload[..];
let reader = &mut &payload[..];
@ -680,42 +670,58 @@ impl BedrockClient {
.store(packet.is_cache_supported, Ordering::Relaxed);
}
SClientCacheBlobStatus::PACKET_ID => {
self.handle_client_cache_blob_status(SClientCacheBlobStatus::read(reader)?)
.await;
let packet = SClientCacheBlobStatus::read(reader)?;
self.handle_client_cache_blob_status(packet);
}
SResourcePackClientResponse::PACKET_ID => {
self.handle_resource_pack_response(SResourcePackClientResponse::read(reader)?, server)
.await;
let packet = SResourcePackClientResponse::read(reader)?;
let client = self.clone();
let server_c = server.clone();
server.spawn_task(async move {
client.handle_resource_pack_response(packet, &server_c).await;
});
}
SPlayerAuthInput::PACKET_ID => {
self.handle_player_auth_input(player, SPlayerAuthInput::read(reader)?, server)
.await;
let packet = SPlayerAuthInput::read(reader)?;
self.handle_player_auth_input(player, packet, server);
}
SRequestChunkRadius::PACKET_ID => {
self.handle_request_chunk_radius(player, SRequestChunkRadius::read(reader)?)
.await;
let packet = SRequestChunkRadius::read(reader)?;
self.handle_request_chunk_radius(player, &packet);
}
SInventoryTransaction::PACKET_ID => {
self.handle_inventory_action(player, SInventoryTransaction::read(reader)?).await;
let packet = SInventoryTransaction::read(reader)?;
self.handle_inventory_action(player, packet);
}
pumpkin_protocol::bedrock::server::item_stack_request::SItemStackRequest::PACKET_ID => {
self.handle_item_stack_request(player, pumpkin_protocol::bedrock::server::item_stack_request::SItemStackRequest::read(reader)?).await;
let packet = pumpkin_protocol::bedrock::server::item_stack_request::SItemStackRequest::read(reader)?;
self.handle_item_stack_request(player, packet);
}
SInteract::PACKET_ID => {
self.handle_interaction(player, SInteract::read(reader)?, server)
.await;
let packet = SInteract::read(reader)?;
self.handle_interaction(&packet);
}
SContainerClose::PACKET_ID => {
self.handle_container_close(player, SContainerClose::read(reader)?)
.await;
let packet = SContainerClose::read(reader)?;
self.handle_container_close(player, &packet);
}
SText::PACKET_ID => {
self.handle_chat_message(server, player, SText::read_slice(reader)?)
.await;
let text = SText::read(reader)?;
let client = self.clone();
let player_c = player.clone();
let server_c = server.clone();
player.spawn_task(async move {
client.handle_chat_message(&server_c, &player_c, text).await;
});
}
SCommandRequest::PACKET_ID => {
self.handle_chat_command(player, server, SCommandRequest::read_slice(reader)?)
.await;
let req = SCommandRequest::read(reader)?;
let client = self.clone();
let player_c = player.clone();
let server_c = server.clone();
player.spawn_task(async move {
client.handle_chat_command(&player_c, &server_c, req).await;
});
}
SSetLocalPlayerAsInitialized::PACKET_ID => {
self.handle_set_local_player_as_initialized(
@ -728,47 +734,44 @@ impl BedrockClient {
// Ignore for now
}
SPlayerAction::PACKET_ID => {
self.handle_player_action(player, server, SPlayerAction::read(reader)?)
.await;
let packet = SPlayerAction::read(reader)?;
self.handle_player_action(player, server, packet);
}
SRespawn::PACKET_ID => {
self.handle_respawn(player, SRespawn::read(reader)?).await;
let packet = SRespawn::read(reader)?;
self.handle_respawn(player, &packet);
}
SAnimate::PACKET_ID => {
self.handle_animate(player, server, &SAnimate::read(reader)?);
self.handle_animate(player, &SAnimate::read(reader)?);
}
SActorEvent::PACKET_ID => {
self.handle_actor_event(player, &SActorEvent::read(reader)?);
}
SEmote::PACKET_ID => {
self.handle_emote(player, server, SEmote::read_slice(reader)?);
self.handle_emote(player, SEmote::read_slice(reader)?);
}
SEmoteList::PACKET_ID => {
self.handle_emote_list(player, server, &SEmoteList::read(reader)?);
self.handle_emote_list(player, &SEmoteList::read(reader)?);
}
pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::PACKET_ID => {
self.handle_modal_form_response(
player,
server,
pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::read_slice(
reader,
)?,
)
.await;
let form_resp = pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::read(
reader,
)?;
self.handle_modal_form_response(player, server, form_resp);
}
SLoadingScreen::PACKET_ID => {
// Ignore for now
}
SBlockPickRequest::PACKET_ID => {
self.handle_block_pick_request(player, SBlockPickRequest::read(reader)?)
.await;
let packet = SBlockPickRequest::read(reader)?;
self.handle_block_pick_request(player, &packet);
}
SRequestAbility::PACKET_ID => {
self.handle_request_ability(player, &SRequestAbility::read(reader)?);
}
SMobEquipment::PACKET_ID => {
self.handle_mob_equipment(server, player, SMobEquipment::read(reader)?)
.await;
let packet = SMobEquipment::read(reader)?;
self.handle_mob_equipment(server, player, &packet);
}
SPacketViolationWarning::PACKET_ID => {
let warning = SPacketViolationWarning::read(reader)?;
@ -787,12 +790,15 @@ impl BedrockClient {
Ok(())
}
pub async fn handle_client_cache_blob_status(&self, packet: SClientCacheBlobStatus) {
pub fn handle_client_cache_blob_status(&self, packet: SClientCacheBlobStatus) {
if packet.miss_hashes.is_empty() {
return;
}
let missing_blobs = {
let cache = self.blob_cache.lock().await;
let cache = self
.blob_cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut missing_blobs = Vec::with_capacity(packet.miss_hashes.len());
for hash in packet.miss_hashes {
if let Some(payload) = cache.get(&hash) {
@ -807,8 +813,7 @@ impl BedrockClient {
missing_blobs
};
if !missing_blobs.is_empty() {
self.send_packet(&CClientCacheMissResponse { missing_blobs })
.await;
self.try_enqueue_client_packet(&CClientCacheMissResponse { missing_blobs });
}
}

View File

@ -256,6 +256,7 @@ async fn negotiate_direct(
Box::pin(negotiate_inner(state, address, offer, None, advertised_ip)).await
}
#[expect(clippy::too_many_lines)]
async fn negotiate_inner(
state: &EndpointState,
address: SocketAddr,
@ -266,11 +267,17 @@ async fn negotiate_inner(
let signaling = if candidates.is_some() { "LAN" } else { "HTTP" };
let direct_ip = candidates.is_none();
trace!(%address, signaling, "Starting NetherNet negotiation");
let (offer, client_public_key) = authenticate_client_offer(
offer,
state.require_client_identity,
state.oidc_verifier.as_ref().and_then(|c| c.get()),
)?;
let (offer, client_public_key) = {
let offer = offer.to_string();
let require_identity = state.require_client_identity;
let oidc_verifier = state.oidc_verifier.as_ref().and_then(|c| c.get()).cloned();
let (tx, rx) = tokio::sync::oneshot::channel();
rayon::spawn(move || {
let res = authenticate_client_offer(&offer, require_identity, oidc_verifier.as_ref());
let _ = tx.send(res);
});
rx.await.map_err(|_| "Task cancelled".to_string())??
};
trace!(
%address,
signaling,
@ -357,7 +364,17 @@ async fn negotiate_inner(
"Gathered NetherNet ICE candidates"
);
trace!(%address, signaling, "Completed NetherNet negotiation");
Ok((add_server_identity(&answer, &state.identity_key)?, session))
let answer = {
let identity_key = state.identity_key.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
rayon::spawn(move || {
let res = add_server_identity(&answer, &identity_key);
let _ = tx.send(res);
});
rx.await.map_err(|_| "Task cancelled".to_string())??
};
Ok((answer, session))
}
async fn build_peer(

View File

@ -103,7 +103,7 @@ impl NetherNetDiscovery {
let players = server
.get_status()
.lock()
.await
.unwrap_or_else(std::sync::PoisonError::into_inner)
.status_response
.players
.as_ref()

View File

@ -2,7 +2,7 @@
use super::*;
impl BedrockClient {
pub fn handle_animate(&self, player: &Arc<Player>, _server: &Server, packet: &SAnimate) {
pub fn handle_animate(&self, player: &Arc<Player>, packet: &SAnimate) {
if !player.has_client_loaded() {
return;
}

View File

@ -3,7 +3,7 @@ use super::*;
impl BedrockClient {
#[allow(clippy::too_many_lines)]
pub async fn handle_block_pick_request(&self, player: &Arc<Player>, packet: SBlockPickRequest) {
pub fn handle_block_pick_request(&self, player: &Arc<Player>, packet: &SBlockPickRequest) {
if !player.can_interact_with_block_at(&packet.position, 1.0) {
return;
}
@ -60,17 +60,14 @@ impl BedrockClient {
player.inventory.set_selected_slot(target_hotbar_slot as u8);
// Send hotbar updates
player
.client
.enqueue_packet_editioned(
&CSetSelectedSlot::new(player.inventory.get_selected_slot() as i8),
&CPlayerHotbar {
selected_slot: VarUInt(player.inventory.get_selected_slot() as u32),
container_id: 0,
should_select_slot: true,
},
)
.await;
player.client.try_enqueue_packet_editioned(
&CSetSelectedSlot::new(player.inventory.get_selected_slot() as i8),
&CPlayerHotbar {
selected_slot: VarUInt(player.inventory.get_selected_slot() as u32),
container_id: 0,
should_select_slot: true,
},
);
// Send screen handler / Java inventory updates
player
@ -93,7 +90,7 @@ impl BedrockClient {
.iter()
.map(NetworkItemStackDescriptor::from)
.collect();
self.enqueue_client_packet(&CInventoryContent {
self.try_enqueue_client_packet(&CInventoryContent {
container_id: VarUInt(0),
slots,
full_container_name: FullContainerName {
@ -101,7 +98,6 @@ impl BedrockClient {
dynamic_id: None,
},
storage_item: NetworkItemStackDescriptor::default(),
})
.await;
});
}
}

View File

@ -2,21 +2,20 @@
use super::*;
impl BedrockClient {
pub async fn handle_container_close(&self, player: &Arc<Player>, packet: SContainerClose) {
pub fn handle_container_close(&self, player: &Arc<Player>, packet: &SContainerClose) {
if packet.container_id == 0 || packet.container_id == 0xff {
self.inventory_opened.store(false, Ordering::Relaxed);
}
player.on_handled_screen_closed();
self.enqueue_client_packet(&SContainerClose {
self.try_enqueue_client_packet(&SContainerClose {
container_id: packet.container_id,
container_type: packet.container_type,
server_initiated_close: false,
})
.await;
});
// Sync the cursor (make it empty) to Bedrock client
self.enqueue_client_packet(&CInventoryContent {
self.try_enqueue_client_packet(&CInventoryContent {
container_id: VarUInt(59), // Cursor container ID
slots: vec![NetworkItemStackDescriptor::default()],
full_container_name: FullContainerName {
@ -24,8 +23,7 @@ impl BedrockClient {
dynamic_id: None,
},
storage_item: NetworkItemStackDescriptor::default(),
})
.await;
});
// Sync the inventory content to Bedrock client
let slots = player
@ -36,7 +34,7 @@ impl BedrockClient {
.iter()
.map(NetworkItemStackDescriptor::from)
.collect();
self.enqueue_client_packet(&CInventoryContent {
self.try_enqueue_client_packet(&CInventoryContent {
container_id: VarUInt(0), // player inventory
slots,
full_container_name: FullContainerName {
@ -44,7 +42,6 @@ impl BedrockClient {
dynamic_id: None,
},
storage_item: NetworkItemStackDescriptor::default(),
})
.await;
});
}
}

View File

@ -2,7 +2,7 @@
use super::*;
impl BedrockClient {
pub fn handle_emote(&self, player: &Arc<Player>, _server: &Server, packet: SEmote<'_>) {
pub fn handle_emote(&self, player: &Arc<Player>, packet: SEmote<'_>) {
if !player.has_client_loaded() {
return;
}

View File

@ -2,7 +2,7 @@
use super::*;
impl BedrockClient {
pub fn handle_emote_list(&self, player: &Arc<Player>, _server: &Server, packet: &SEmoteList) {
pub fn handle_emote_list(&self, player: &Arc<Player>, packet: &SEmoteList) {
tracing::info!(
"handle_emote_list: player={} packet={:?}",
player.gameprofile.name,

View File

@ -2,24 +2,18 @@
use super::*;
impl BedrockClient {
pub async fn handle_interaction(
&self,
_player: &Arc<Player>,
packet: SInteract,
_server: &Arc<Server>,
) {
pub fn handle_interaction(&self, packet: &SInteract) {
if matches!(packet.action, Action::OpenInventory) {
if self.inventory_opened.load(Ordering::Relaxed) {
return;
}
self.inventory_opened.store(true, Ordering::Relaxed);
self.enqueue_client_packet(&CContainerOpen {
self.try_enqueue_client_packet(&CContainerOpen {
container_id: 0,
container_type: 0xff,
position: BlockPos::ZERO,
target_entity_id: VarLong(-1),
})
.await;
});
}
}
}

View File

@ -3,11 +3,7 @@ use super::*;
impl BedrockClient {
#[allow(clippy::too_many_lines, clippy::collapsible_if, clippy::unreachable)]
pub async fn handle_inventory_action(
&self,
player: &Arc<Player>,
packet: SInventoryTransaction,
) {
pub fn handle_inventory_action(&self, player: &Arc<Player>, packet: SInventoryTransaction) {
tracing::debug!("handle_inventory_action: packet={:?}", packet);
let mut inventory_updated = false;
let mut updates = Vec::new();
@ -129,7 +125,7 @@ impl BedrockClient {
.iter()
.map(NetworkItemStackDescriptor::from)
.collect();
self.enqueue_client_packet(&CInventoryContent {
self.try_enqueue_client_packet(&CInventoryContent {
container_id: VarUInt(0),
slots,
full_container_name: FullContainerName {
@ -137,8 +133,7 @@ impl BedrockClient {
dynamic_id: None,
},
storage_item: NetworkItemStackDescriptor::default(),
})
.await;
});
}
match packet.transaction_data {
@ -173,10 +168,10 @@ impl BedrockClient {
let client_stack = descriptor_to_stack(&data.item_in_hand);
let mut held_item = player.inventory().held_item();
if !client_stack.is_empty() {
if held_item.is_empty() || held_item.item.id != client_stack.item.id {
held_item = client_stack.clone();
}
if !client_stack.is_empty()
&& (held_item.is_empty() || held_item.item.id != client_stack.item.id)
{
held_item = client_stack;
}
let result = server.block_registry.use_with_item(
@ -236,18 +231,14 @@ impl BedrockClient {
sequence: VarInt(0),
};
if let Ok(Some(_)) = server
.block_registry
.place_block(
player,
placed_block,
&server,
&dummy_use_item_on,
data.block_position,
face,
)
.await
{
if let Ok(Some(_)) = server.block_registry.place_block(
player,
placed_block,
&server,
&dummy_use_item_on,
data.block_position,
face,
) {
if player.gamemode.load() != GameMode::Creative {
stack.decrement(1);
}
@ -263,7 +254,7 @@ impl BedrockClient {
if !client_stack.is_empty()
&& (held.is_empty() || held.item.id != client_stack.item.id)
{
held = client_stack.clone();
held = client_stack;
player.inventory.set_held_item(held.clone());
}
@ -352,7 +343,7 @@ impl BedrockClient {
}
}
send_cancellable! {{
send_cancellable_blocking! {{
&server;
event;
'after: {
@ -385,7 +376,7 @@ impl BedrockClient {
1 => {
let world = player.world();
if let Some(target) = world.get_entity_by_id(target_runtime_id) {
player.attack(target).await;
player.attack(&target);
}
}
_ => {
@ -450,14 +441,13 @@ impl BedrockClient {
}
}
self.enqueue_client_packet(&CItemStackResponse {
self.try_enqueue_client_packet(&CItemStackResponse {
responses: vec![ItemStackResponseInfo {
result,
client_request_id: packet.legacy_request_id,
containers: container_infos,
}],
})
.await;
});
}
}
}

View File

@ -3,7 +3,7 @@ use super::*;
impl BedrockClient {
#[allow(clippy::too_many_lines)]
pub async fn handle_item_stack_request(
pub fn handle_item_stack_request(
&self,
player: &Arc<Player>,
packet: pumpkin_protocol::bedrock::server::item_stack_request::SItemStackRequest,
@ -520,8 +520,7 @@ impl BedrockClient {
};
// Send Bedrock specific responses and updates
self.enqueue_client_packet(&CItemStackResponse { responses })
.await;
self.try_enqueue_client_packet(&CItemStackResponse { responses });
if inventory_updated {
let slots = player
@ -532,7 +531,7 @@ impl BedrockClient {
.iter()
.map(NetworkItemStackDescriptor::from)
.collect();
self.enqueue_client_packet(&CInventoryContent {
self.try_enqueue_client_packet(&CInventoryContent {
container_id: VarUInt(0),
slots,
full_container_name: FullContainerName {
@ -540,8 +539,7 @@ impl BedrockClient {
dynamic_id: None,
},
storage_item: NetworkItemStackDescriptor::default(),
})
.await;
});
}
}
}

View File

@ -2,11 +2,11 @@
use super::*;
impl BedrockClient {
pub async fn handle_mob_equipment(
pub fn handle_mob_equipment(
&self,
_server: &Arc<Server>,
server: &Arc<Server>,
player: &Arc<Player>,
packet: SMobEquipment,
packet: &SMobEquipment,
) {
player.update_last_action_time();
let slot = packet.selected_slot;
@ -14,23 +14,15 @@ impl BedrockClient {
return;
}
let previous_slot = player.inventory.get_selected_slot();
let server = player.world().server.upgrade();
if let Some(server) = server {
let mut event = PlayerItemHeldEvent::new(player.clone(), previous_slot, slot);
server.plugin_manager.fire(&server, &mut event).await;
let is_cancelled = {
use crate::plugin::Cancellable;
event.cancelled()
};
if is_cancelled {
self.enqueue_client_packet(&CPlayerHotbar {
selected_slot: VarUInt(previous_slot as u32),
container_id: 0,
should_select_slot: true,
})
.await;
return;
}
let mut event = PlayerItemHeldEvent::new(player.clone(), previous_slot, slot);
server.plugin_manager.fire_blocking(server, &mut event);
if event.cancelled {
self.try_enqueue_client_packet(&CPlayerHotbar {
selected_slot: VarUInt(previous_slot as u32),
container_id: 0,
should_select_slot: true,
});
return;
}
let inv = player.inventory();

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