chore: more fixes
This commit is contained in:
parent
37437ee0a0
commit
6eb02f65b9
29
Cargo.lock
generated
29
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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"] }
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
32
crates/pumpkin-protocol/fuzz/Cargo.lock
generated
32
crates/pumpkin-protocol/fuzz/Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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");
|
||||
};
|
||||
|
||||
@ -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 chunk_items: Vec<(Vector2<i32>, Option<AnvilChunkData>)> = chunks
|
||||
.into_iter()
|
||||
.map(|chunk| {
|
||||
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) {
|
||||
let data = self.chunks_data[index]
|
||||
.as_ref()
|
||||
.map(|chunk_metadata| chunk_metadata.serialized_data.clone());
|
||||
(chunk, data)
|
||||
})
|
||||
.collect();
|
||||
|
||||
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, 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);
|
||||
});
|
||||
});
|
||||
|
||||
stream.send(result).await.is_ok()
|
||||
}
|
||||
};
|
||||
|
||||
if !is_ok {
|
||||
// Stream is closed. Stop unneeded work and IO
|
||||
while let Some(item) = rx.recv().await {
|
||||
if stream.send(item).await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,14 +122,28 @@ where
|
||||
chunks: Vec<Vector2<i32>>,
|
||||
stream: tokio::sync::mpsc::Sender<LoadedData<Self::Data, ChunkReadingError>>,
|
||||
) {
|
||||
for pos in chunks {
|
||||
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 (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(|(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| {
|
||||
let mut decoder =
|
||||
StreamingDecoder::new(&chunk_bytes[..]).map_err(|e| {
|
||||
ChunkReadingError::IoError(std::io::Error::other(e.to_string()))
|
||||
})?;
|
||||
let mut decompressed = Vec::new();
|
||||
@ -138,17 +152,19 @@ where
|
||||
let bytes = Bytes::from(decompressed);
|
||||
D::from_bytes(&bytes, pos)
|
||||
})();
|
||||
|
||||
let data_res = match res {
|
||||
match res {
|
||||
Ok(data) => LoadedData::Loaded(data),
|
||||
Err(e) => LoadedData::Error((pos, e)),
|
||||
};
|
||||
|
||||
if stream.send(data_res).await.is_err() {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let _ = stream.send(LoadedData::Missing(pos)).await;
|
||||
},
|
||||
);
|
||||
let _ = tx.blocking_send(data_res);
|
||||
});
|
||||
});
|
||||
|
||||
while let Some(item) = rx.recv().await {
|
||||
if stream.send(item).await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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));
|
||||
});
|
||||
|
||||
@ -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,18 +203,17 @@ 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);
|
||||
for i in positions {
|
||||
match data.entry(i) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let rc = entry.get_mut();
|
||||
if *rc == 1 {
|
||||
if *rc <= 1 {
|
||||
entry.remove();
|
||||
drop(data);
|
||||
lock.1.notify_waiters();
|
||||
} else {
|
||||
*rc -= 1;
|
||||
}
|
||||
@ -233,11 +223,12 @@ pub async fn io_write_work(
|
||||
"io_write: attempted to release missing lock entry for {:?}",
|
||||
i
|
||||
);
|
||||
// continue without panicking to avoid crashing on shutdown races
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lock.1.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_generation(
|
||||
@ -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 {
|
||||
|
||||
@ -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),
|
||||
{
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>) {
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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<'_>) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<'_>) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -117,7 +117,7 @@ impl FollowOwnerGoal {
|
||||
),
|
||||
None,
|
||||
None,
|
||||
world.clone(),
|
||||
&world,
|
||||
);
|
||||
|
||||
let mut navigator = mob
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
208
crates/pumpkin/src/entity/ai/goal/move_towards_target.rs
Normal file
208
crates/pumpkin/src/entity/ai/goal/move_towards_target.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,8 +3722,7 @@ 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(
|
||||
player.try_send_client_packet(&CPlayerPosition::new(
|
||||
id.into(),
|
||||
dismount_pos,
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
@ -3743,8 +3735,7 @@ impl Entity {
|
||||
PositionFlag::YRot,
|
||||
PositionFlag::XRot,
|
||||
],
|
||||
))
|
||||
.await;
|
||||
));
|
||||
}
|
||||
|
||||
// Vanilla: setSneaking(false) after dismount via sneak input
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -566,7 +566,6 @@ impl PumpkinServer {
|
||||
|
||||
if let Some((player, world)) = server_clone
|
||||
.add_player(Arc::new(ClientPlatform::Java(java_client)), profile, Some(config))
|
||||
.await
|
||||
{
|
||||
|
||||
if let ClientPlatform::Java(client) = player.client.as_ref() {
|
||||
@ -584,7 +583,7 @@ impl PumpkinServer {
|
||||
client.await_tasks().await;
|
||||
}
|
||||
player.remove().await;
|
||||
server_clone.remove_player(&player).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(
|
||||
if let Some((player, _world)) = server.add_player(
|
||||
Arc::new(ClientPlatform::Bedrock(client.clone())),
|
||||
profile,
|
||||
Some(config),
|
||||
)
|
||||
.await
|
||||
{
|
||||
) {
|
||||
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}");
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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(
|
||||
let form_resp = pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::read(
|
||||
reader,
|
||||
)?,
|
||||
)
|
||||
.await;
|
||||
)?;
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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(
|
||||
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,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
);
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
if let Ok(Some(_)) = server.block_registry.place_block(
|
||||
player,
|
||||
placed_block,
|
||||
&server,
|
||||
&dummy_use_item_on,
|
||||
data.block_position,
|
||||
face,
|
||||
)
|
||||
.await
|
||||
{
|
||||
) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,24 +14,16 @@ 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 {
|
||||
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,
|
||||
})
|
||||
.await;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let inv = player.inventory();
|
||||
inv.set_selected_slot(slot);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user