mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix(bedrock): make nether/end work
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -3257,7 +3257,7 @@ dependencies = [
|
||||
name = "pumpkin"
|
||||
version = "0.1.0-dev+26.2-26.40"
|
||||
dependencies = [
|
||||
"aes 0.9.1",
|
||||
"aes 0.9.2",
|
||||
"arc-swap",
|
||||
"axum",
|
||||
"base64 0.23.1",
|
||||
@@ -3454,6 +3454,7 @@ dependencies = [
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"uuid",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -82,6 +82,8 @@ pub struct BedrockConfig {
|
||||
pub authentication: BedrockAuthenticationConfig,
|
||||
/// Bedrock `NetherNet` transport settings.
|
||||
pub nethernet: NetherNetConfig,
|
||||
/// Whether Bedrock client chunk blob caching is enabled.
|
||||
pub chunk_caching: bool,
|
||||
}
|
||||
|
||||
impl Default for BedrockConfig {
|
||||
@@ -98,6 +100,7 @@ impl Default for BedrockConfig {
|
||||
motd: "A blazingly fast Pumpkin server!".to_string(),
|
||||
authentication: BedrockAuthenticationConfig::default(),
|
||||
nethernet: NetherNetConfig::default(),
|
||||
chunk_caching: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ async-compression = { workspace = true, features = ["tokio", "zlib", "deflate"]
|
||||
flate2.workspace = true
|
||||
|
||||
bitflags.workspace = true
|
||||
xxhash-rust.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
# Validate correctness
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use std::io::{Error, Write};
|
||||
|
||||
use pumpkin_macros::packet;
|
||||
|
||||
use crate::{codec::var_uint::VarUInt, serial::PacketWrite};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CacheBlob {
|
||||
pub hash: u64,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
#[packet(136)]
|
||||
pub struct CClientCacheMissResponse<'a> {
|
||||
pub blobs: &'a [CacheBlob],
|
||||
}
|
||||
|
||||
impl PacketWrite for CClientCacheMissResponse<'_> {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
VarUInt(self.blobs.len() as u32).write(writer)?;
|
||||
for blob in self.blobs {
|
||||
writer.write_all(&blob.hash.to_le_bytes())?;
|
||||
VarUInt(blob.payload.len() as u32).write(writer)?;
|
||||
writer.write_all(&blob.payload)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::io::{Error, Write};
|
||||
use xxhash_rust::xxh64::xxh64;
|
||||
|
||||
use pumpkin_macros::packet;
|
||||
use pumpkin_world::chunk::{ChunkData, palette::NetworkPalette};
|
||||
@@ -7,6 +8,7 @@ use crate::{
|
||||
codec::{var_int::VarInt, var_uint::VarUInt},
|
||||
serial::PacketWrite,
|
||||
};
|
||||
|
||||
const VERSION: u8 = 9;
|
||||
|
||||
#[packet(58)]
|
||||
@@ -20,94 +22,137 @@ pub struct CLevelChunk<'a> {
|
||||
pub chunk: &'a ChunkData,
|
||||
}
|
||||
|
||||
impl PacketWrite for CLevelChunk<'_> {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
VarInt(self.chunk.x).write(writer)?;
|
||||
VarInt(self.chunk.z).write(writer)?;
|
||||
pub type ChunkBlob = (u64, Vec<u8>);
|
||||
pub type EncodedChunk = (Vec<u8>, Vec<ChunkBlob>);
|
||||
|
||||
VarInt(self.dimension).write(writer)?;
|
||||
let sub_chunk_count = self.chunk.section.count as u32;
|
||||
debug_assert_eq!(sub_chunk_count, 24);
|
||||
VarUInt(sub_chunk_count).write(writer)?;
|
||||
impl CLevelChunk<'_> {
|
||||
pub fn encode_chunk(
|
||||
chunk: &ChunkData,
|
||||
dimension: i32,
|
||||
cache_enabled: bool,
|
||||
) -> Result<EncodedChunk, Error> {
|
||||
let mut writer = Vec::new();
|
||||
|
||||
VarInt(chunk.x).write(&mut writer)?;
|
||||
VarInt(chunk.z).write(&mut writer)?;
|
||||
|
||||
VarInt(dimension).write(&mut writer)?;
|
||||
let sub_chunk_count = chunk.section.count as u32;
|
||||
VarUInt(sub_chunk_count).write(&mut writer)?;
|
||||
// Optional sub-chunk request limit. Pumpkin sends complete chunks.
|
||||
false.write(writer)?;
|
||||
self.cache_enabled.write(writer)?;
|
||||
// Blob IDs are present in 26.40 even when client caching is disabled.
|
||||
VarUInt(0).write(writer)?;
|
||||
false.write(&mut writer)?;
|
||||
cache_enabled.write(&mut writer)?;
|
||||
|
||||
let mut chunk_data = Vec::new();
|
||||
let data_write = &mut chunk_data;
|
||||
let mut blobs = Vec::new();
|
||||
|
||||
let block_sections = self
|
||||
.chunk
|
||||
let block_sections = chunk
|
||||
.section
|
||||
.block_sections
|
||||
.read()
|
||||
.map_err(|_| Error::other("block_sections read lock poisoned"))?;
|
||||
let min_y_section = (self.chunk.section.min_y >> 4) as i8;
|
||||
let min_y_section = (chunk.section.min_y >> 4) as i8;
|
||||
|
||||
let mut subchunk_bytes_list = Vec::with_capacity(block_sections.len());
|
||||
|
||||
for (i, block_palette) in block_sections.iter().enumerate() {
|
||||
let mut subchunk_buf = Vec::new();
|
||||
// Version 9: [version:byte][num_storages:byte][sub_chunk_index:byte]
|
||||
let y = (i as i8) + min_y_section;
|
||||
let num_storages = 1;
|
||||
data_write.write_all(&[VERSION, num_storages, y as u8])?;
|
||||
subchunk_buf.write_all(&[VERSION, num_storages, y as u8])?;
|
||||
|
||||
let network_repr = block_palette.convert_be_network();
|
||||
|
||||
(network_repr.bits_per_entry << 1 | 1).write(data_write)?;
|
||||
(network_repr.bits_per_entry << 1 | 1).write(&mut subchunk_buf)?;
|
||||
|
||||
for data in network_repr.packed_data {
|
||||
data.write(data_write)?;
|
||||
data.write(&mut subchunk_buf)?;
|
||||
}
|
||||
|
||||
match network_repr.palette {
|
||||
NetworkPalette::Single(id) => {
|
||||
VarInt(i32::from(id)).write(data_write)?;
|
||||
VarInt(i32::from(id)).write(&mut subchunk_buf)?;
|
||||
}
|
||||
NetworkPalette::Indirect(palette) => {
|
||||
VarInt(palette.len() as i32).write(data_write)?;
|
||||
VarInt(palette.len() as i32).write(&mut subchunk_buf)?;
|
||||
for id in palette {
|
||||
VarInt(i32::from(id)).write(data_write)?;
|
||||
VarInt(i32::from(id)).write(&mut subchunk_buf)?;
|
||||
}
|
||||
}
|
||||
NetworkPalette::Direct => (),
|
||||
}
|
||||
|
||||
subchunk_bytes_list.push(subchunk_buf);
|
||||
}
|
||||
|
||||
let biome_sections = self
|
||||
.chunk
|
||||
let biome_sections = chunk
|
||||
.section
|
||||
.biome_sections
|
||||
.read()
|
||||
.map_err(|_| Error::other("biome_sections read lock poisoned"))?;
|
||||
|
||||
let mut biome_buf = Vec::new();
|
||||
for biome_palette in biome_sections.iter() {
|
||||
let network_repr = biome_palette.convert_be_network();
|
||||
|
||||
(network_repr.bits_per_entry << 1 | 1).write(data_write)?;
|
||||
(network_repr.bits_per_entry << 1 | 1).write(&mut biome_buf)?;
|
||||
|
||||
for data in network_repr.packed_data {
|
||||
data.write(data_write)?;
|
||||
data.write(&mut biome_buf)?;
|
||||
}
|
||||
|
||||
match network_repr.palette {
|
||||
NetworkPalette::Single(id) => {
|
||||
VarInt(i32::from(id)).write(data_write)?;
|
||||
VarInt(i32::from(id)).write(&mut biome_buf)?;
|
||||
}
|
||||
NetworkPalette::Indirect(palette) => {
|
||||
VarInt(palette.len() as i32).write(data_write)?;
|
||||
VarInt(palette.len() as i32).write(&mut biome_buf)?;
|
||||
for id in palette {
|
||||
VarInt(i32::from(id)).write(data_write)?;
|
||||
VarInt(i32::from(id)).write(&mut biome_buf)?;
|
||||
}
|
||||
}
|
||||
NetworkPalette::Direct => (),
|
||||
}
|
||||
}
|
||||
|
||||
data_write.write_all(&[0])?;
|
||||
if cache_enabled {
|
||||
for subchunk_buf in subchunk_bytes_list {
|
||||
let hash = xxh64(&subchunk_buf, 0);
|
||||
blobs.push((hash, subchunk_buf));
|
||||
}
|
||||
let biome_hash = xxh64(&biome_buf, 0);
|
||||
blobs.push((biome_hash, biome_buf));
|
||||
|
||||
VarUInt(chunk_data.len() as u32).write(writer)?;
|
||||
writer.write_all(&chunk_data)
|
||||
VarUInt(blobs.len() as u32).write(&mut writer)?;
|
||||
for (hash, _) in &blobs {
|
||||
writer.write_all(&hash.to_le_bytes())?;
|
||||
}
|
||||
|
||||
// Chunk data payload when cache_enabled: only border block count byte (0).
|
||||
VarUInt(1).write(&mut writer)?;
|
||||
writer.write_all(&[0])?;
|
||||
} else {
|
||||
VarUInt(0).write(&mut writer)?;
|
||||
|
||||
let mut chunk_data = Vec::new();
|
||||
for subchunk_buf in subchunk_bytes_list {
|
||||
chunk_data.write_all(&subchunk_buf)?;
|
||||
}
|
||||
chunk_data.write_all(&biome_buf)?;
|
||||
chunk_data.write_all(&[0])?;
|
||||
|
||||
VarUInt(chunk_data.len() as u32).write(&mut writer)?;
|
||||
writer.write_all(&chunk_data)?;
|
||||
}
|
||||
|
||||
Ok((writer, blobs))
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketWrite for CLevelChunk<'_> {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
let (encoded, _) = Self::encode_chunk(self.chunk, self.dimension, self.cache_enabled)?;
|
||||
writer.write_all(&encoded)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod available_commands;
|
||||
pub mod boss_event;
|
||||
pub mod change_dimension;
|
||||
pub mod chunk_radius_update;
|
||||
pub mod client_cache_miss_response;
|
||||
pub mod common;
|
||||
pub mod container_open;
|
||||
pub mod correct_player_move;
|
||||
@@ -57,6 +58,7 @@ pub use available_commands::*;
|
||||
pub use boss_event::*;
|
||||
pub use change_dimension::*;
|
||||
pub use chunk_radius_update::*;
|
||||
pub use client_cache_miss_response::*;
|
||||
pub use common::*;
|
||||
pub use container_open::*;
|
||||
pub use correct_player_move::*;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
use std::io::{Error, Read};
|
||||
|
||||
use pumpkin_macros::packet;
|
||||
|
||||
use crate::{codec::var_uint::VarUInt, serial::PacketRead};
|
||||
|
||||
#[packet(135)]
|
||||
pub struct SClientCacheBlobStatus {
|
||||
pub miss_hashes: Vec<u64>,
|
||||
pub hit_hashes: Vec<u64>,
|
||||
}
|
||||
|
||||
impl PacketRead for SClientCacheBlobStatus {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
let miss_count = VarUInt::read(reader)?.0 as usize;
|
||||
let mut miss_hashes = Vec::with_capacity(miss_count);
|
||||
for _ in 0..miss_count {
|
||||
let mut bytes = [0u8; 8];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
miss_hashes.push(u64::from_le_bytes(bytes));
|
||||
}
|
||||
|
||||
let hit_count = VarUInt::read(reader)?.0 as usize;
|
||||
let mut hit_hashes = Vec::with_capacity(hit_count);
|
||||
for _ in 0..hit_count {
|
||||
let mut bytes = [0u8; 8];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
hit_hashes.push(u64::from_le_bytes(bytes));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
miss_hashes,
|
||||
hit_hashes,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod actor_event;
|
||||
pub mod animate;
|
||||
pub mod block_pick_request;
|
||||
pub mod client_cache_blob_status;
|
||||
pub mod client_cache_status;
|
||||
pub mod command_request;
|
||||
pub mod container_close;
|
||||
@@ -28,6 +29,7 @@ pub mod text;
|
||||
pub use actor_event::*;
|
||||
pub use animate::*;
|
||||
pub use block_pick_request::*;
|
||||
pub use client_cache_blob_status::*;
|
||||
pub use client_cache_status::*;
|
||||
pub use command_request::*;
|
||||
pub use container_close::*;
|
||||
|
||||
@@ -81,7 +81,7 @@ impl BlockBehaviour for NetherPortalBlock {
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
tracing::debug!(
|
||||
"Nether portal collision at {:?}, targeting world {:?}",
|
||||
args.position,
|
||||
target_world.dimension.minecraft_name
|
||||
|
||||
@@ -2180,6 +2180,7 @@ impl Player {
|
||||
.enqueue_packet(&CPlayStatus::PlayerSpawn)
|
||||
.await;
|
||||
self.bedrock_spawned.store(true, Ordering::Relaxed);
|
||||
self.set_client_loaded(true);
|
||||
}
|
||||
}
|
||||
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
||||
@@ -2839,6 +2840,7 @@ impl Player {
|
||||
}
|
||||
|
||||
/// Teleports the player to a different world or dimension with an optional position, yaw, and pitch.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub async fn teleport_world(
|
||||
self: &Arc<Self>,
|
||||
new_world: Arc<World>,
|
||||
@@ -2938,6 +2940,7 @@ impl Player {
|
||||
false,
|
||||
);
|
||||
bedrock.enqueue_packet(&change_dim_packet).await;
|
||||
self.bedrock_spawned.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ use pumpkin_data::advancement_data::{
|
||||
AdvancementNode, AdvancementProgressData, AdvancementRequirement, AdvancementReward, Criteria,
|
||||
};
|
||||
use pumpkin_data::{ADVANCEMENT_TREE, Advancement, translation};
|
||||
use pumpkin_protocol::java::client::play::{CSelectAdvancementsTab, CUpdateAdvancements};
|
||||
use pumpkin_protocol::bedrock::server::text::SText;
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
CSelectAdvancementsTab, CSystemChatMessage, CUpdateAdvancements,
|
||||
};
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use serde::ser::SerializeMap;
|
||||
@@ -441,15 +444,25 @@ impl PlayerAdvancement {
|
||||
.show_advancement_messages
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
let component = TextComponent::translate_cross(
|
||||
let player_name = player.get_display_name().await;
|
||||
let je_component = TextComponent::translate(
|
||||
display.frame_type.get_translation(),
|
||||
translation::bedrock::CHAT_TYPE_ACHIEVEMENT,
|
||||
[player.get_display_name().await, advancement.name()],
|
||||
[player_name.clone(), advancement.name()],
|
||||
);
|
||||
let je_packet = CSystemChatMessage::new(&je_component, false);
|
||||
|
||||
let be_packet = SText::translation(
|
||||
translation::bedrock::CHAT_TYPE_ACHIEVEMENT.to_string(),
|
||||
vec![
|
||||
player_name.0.to_bedrock_string(),
|
||||
display.get_title().0.to_bedrock_string(),
|
||||
],
|
||||
);
|
||||
|
||||
player
|
||||
.world()
|
||||
.broadcast_system_message(&component, false)
|
||||
.await; //send translate component for the event
|
||||
.broadcast_editioned(&je_packet, &be_packet)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU32},
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -20,11 +20,16 @@ use pumpkin_protocol::{
|
||||
BClientPacket, PacketDecodeError, RawPacket,
|
||||
bedrock::{
|
||||
BEDROCK_GAME_PACKET, SubClient,
|
||||
client::{disconnect_player::CDisconnectPlayer, level_chunk::CLevelChunk},
|
||||
client::{
|
||||
client_cache_miss_response::{CClientCacheMissResponse, CacheBlob},
|
||||
disconnect_player::CDisconnectPlayer,
|
||||
level_chunk::CLevelChunk,
|
||||
},
|
||||
packet_decoder::BedrockBatchDecoder,
|
||||
packet_encoder::BedrockBatchEncoder,
|
||||
server::{
|
||||
animate::SAnimate, block_pick_request::SBlockPickRequest,
|
||||
client_cache_blob_status::SClientCacheBlobStatus,
|
||||
client_cache_status::SClientCacheStatus, command_request::SCommandRequest,
|
||||
container_close::SContainerClose, emote::SEmote, emote_list::SEmoteList,
|
||||
interaction::SInteraction, inventory_transaction::SInventoryTransaction,
|
||||
@@ -111,6 +116,8 @@ pub struct BedrockClient {
|
||||
/// The next form ID to use for custom forms.
|
||||
pub next_form_id: AtomicU32,
|
||||
pub inventory_opened: AtomicBool,
|
||||
pub client_cache_supported: AtomicBool,
|
||||
pub blob_cache: 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>>,
|
||||
@@ -146,6 +153,8 @@ impl BedrockClient {
|
||||
outgoing_packet_priority_recv: Mutex::new(Some(priority_recv)),
|
||||
next_form_id: AtomicU32::new(0),
|
||||
inventory_opened: AtomicBool::new(false),
|
||||
client_cache_supported: AtomicBool::new(false),
|
||||
blob_cache: Mutex::new(HashMap::new()),
|
||||
close_token: CancellationToken::new(),
|
||||
last_seen: Arc::new(AtomicCell::new(std::time::Instant::now())),
|
||||
incoming_game_packet_send: incoming_send,
|
||||
@@ -288,30 +297,45 @@ impl BedrockClient {
|
||||
return;
|
||||
}
|
||||
|
||||
let bedrock_dimension =
|
||||
if player.world().dimension == pumpkin_data::dimension::Dimension::THE_NETHER {
|
||||
1
|
||||
} else if player.world().dimension == pumpkin_data::dimension::Dimension::THE_END {
|
||||
2
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let cache_enabled = server.advanced_config.networking.bedrock.chunk_caching
|
||||
&& self.client_cache_supported.load(Ordering::Relaxed);
|
||||
|
||||
let mut serialize_tasks = Vec::with_capacity(valid_chunks.len());
|
||||
for chunk in valid_chunks {
|
||||
serialize_tasks.push(tokio::task::spawn_blocking(move || {
|
||||
let mut packet_payload = Vec::new();
|
||||
let packet = CLevelChunk {
|
||||
dimension: 0,
|
||||
cache_enabled: false,
|
||||
chunk: &chunk,
|
||||
};
|
||||
packet
|
||||
.write_packet(&mut packet_payload)
|
||||
.map(|()| packet_payload)
|
||||
CLevelChunk::encode_chunk(&chunk, bedrock_dimension, cache_enabled)
|
||||
}));
|
||||
}
|
||||
|
||||
let mut encoded_payloads = Vec::with_capacity(serialize_tasks.len());
|
||||
let mut new_blobs = Vec::new();
|
||||
for task in serialize_tasks {
|
||||
match task.await {
|
||||
Ok(Ok(payload)) => encoded_payloads.push(payload),
|
||||
Ok(Ok((payload, blobs))) => {
|
||||
encoded_payloads.push(payload);
|
||||
new_blobs.extend(blobs);
|
||||
}
|
||||
Ok(Err(e)) => error!("Failed to serialize Bedrock chunk: {:?}", e),
|
||||
Err(e) => error!("Join error in Bedrock chunk serialization: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
if !new_blobs.is_empty() {
|
||||
let mut cache = self.blob_cache.lock().await;
|
||||
for (hash, payload) in new_blobs {
|
||||
cache.insert(hash, payload);
|
||||
}
|
||||
}
|
||||
|
||||
let mut packets_to_enqueue = Vec::with_capacity(encoded_payloads.len());
|
||||
{
|
||||
let encoder = self.network_writer.read().await;
|
||||
@@ -635,7 +659,13 @@ impl BedrockClient {
|
||||
let reader = &mut &payload[..];
|
||||
match packet.id {
|
||||
SClientCacheStatus::PACKET_ID => {
|
||||
// TODO
|
||||
let packet = SClientCacheStatus::read(reader)?;
|
||||
self.client_cache_supported
|
||||
.store(packet.cache_supported, Ordering::Relaxed);
|
||||
}
|
||||
SClientCacheBlobStatus::PACKET_ID => {
|
||||
self.handle_client_cache_blob_status(SClientCacheBlobStatus::read(reader)?)
|
||||
.await;
|
||||
}
|
||||
SResourcePackResponse::PACKET_ID => {
|
||||
self.handle_resource_pack_response(SResourcePackResponse::read(reader)?, server)
|
||||
@@ -736,6 +766,30 @@ impl BedrockClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_client_cache_blob_status(&self, packet: SClientCacheBlobStatus) {
|
||||
if packet.miss_hashes.is_empty() {
|
||||
return;
|
||||
}
|
||||
let cache = self.blob_cache.lock().await;
|
||||
let mut missing_blobs = Vec::with_capacity(packet.miss_hashes.len());
|
||||
for hash in packet.miss_hashes {
|
||||
if let Some(payload) = cache.get(&hash) {
|
||||
missing_blobs.push(CacheBlob {
|
||||
hash,
|
||||
payload: payload.clone(),
|
||||
});
|
||||
} else {
|
||||
warn!("Client requested missing blob {hash:#x} not found in server cache");
|
||||
}
|
||||
}
|
||||
if !missing_blobs.is_empty() {
|
||||
self.send_game_packet(&CClientCacheMissResponse {
|
||||
blobs: &missing_blobs,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn await_close_interrupt(&self) {
|
||||
self.close_token.cancelled().await;
|
||||
}
|
||||
|
||||
@@ -72,35 +72,15 @@ use tracing::{debug, info};
|
||||
|
||||
const MIN_PREDICTED_BREAK_PROGRESS: f32 = 0.65;
|
||||
|
||||
fn descriptor_to_stack(desc: &NetworkItemDescriptor, is_creative: bool) -> ItemStack {
|
||||
fn descriptor_to_stack(desc: &NetworkItemDescriptor) -> ItemStack {
|
||||
if desc.id.0 == 0 || desc.stack_size == 0 {
|
||||
ItemStack::EMPTY.clone()
|
||||
} else {
|
||||
let mut mapped_item = None;
|
||||
|
||||
if is_creative {
|
||||
let index = (desc.id.0.saturating_sub(1)) as usize;
|
||||
if index < pumpkin_data::bedrock_creative::CREATIVE_ENTRIES.len() {
|
||||
let entry = pumpkin_data::bedrock_creative::CREATIVE_ENTRIES[index];
|
||||
if let Some(mapping) = pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock(
|
||||
entry.item_id,
|
||||
entry.item_aux_value,
|
||||
) {
|
||||
mapped_item = Some(mapping.java_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mapped_item.is_none()
|
||||
&& let Some(mapping) = pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock(
|
||||
pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock(
|
||||
desc.id.0 as i16,
|
||||
desc.aux_value.0,
|
||||
)
|
||||
{
|
||||
mapped_item = Some(mapping.java_item);
|
||||
}
|
||||
|
||||
mapped_item.map_or_else(
|
||||
.map_or_else(
|
||||
|| {
|
||||
tracing::warn!(
|
||||
"Failed to map bedrock item id {} and data {} to Java item",
|
||||
@@ -109,7 +89,7 @@ fn descriptor_to_stack(desc: &NetworkItemDescriptor, is_creative: bool) -> ItemS
|
||||
);
|
||||
ItemStack::EMPTY.clone()
|
||||
},
|
||||
|item| ItemStack::new(desc.stack_size as u8, item),
|
||||
|mapping| ItemStack::new(desc.stack_size as u8, mapping.java_item),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -506,7 +486,7 @@ impl BedrockClient {
|
||||
player: &Arc<Player>,
|
||||
packet: SInventoryTransaction,
|
||||
) {
|
||||
tracing::info!("handle_inventory_action: packet={:?}", packet);
|
||||
tracing::debug!("handle_inventory_action: packet={:?}", packet);
|
||||
let mut inventory_updated = false;
|
||||
let mut updates = Vec::new();
|
||||
let result = 0u8;
|
||||
@@ -558,13 +538,12 @@ impl BedrockClient {
|
||||
player_screen_handler.send_content_updates().await;
|
||||
}
|
||||
|
||||
let is_creative = player.gamemode.load() == GameMode::Creative;
|
||||
for action in &packet.actions {
|
||||
use pumpkin_protocol::bedrock::server::inventory_transaction::InventoryActionSource;
|
||||
let source_type = InventoryActionSource::from(action.source_type);
|
||||
if source_type == InventoryActionSource::World {
|
||||
let old_stack = descriptor_to_stack(&action.old_item, is_creative);
|
||||
let new_stack = descriptor_to_stack(&action.new_item, is_creative);
|
||||
let old_stack = descriptor_to_stack(&action.old_item);
|
||||
let new_stack = descriptor_to_stack(&action.new_item);
|
||||
if old_stack.is_empty() && !new_stack.is_empty() {
|
||||
player.drop_item(new_stack).await;
|
||||
}
|
||||
@@ -572,7 +551,7 @@ impl BedrockClient {
|
||||
if let Some(screen_slot) =
|
||||
map_bedrock_slot_to_screen_handler(window_id, action.inventory_slot)
|
||||
{
|
||||
let item_stack = descriptor_to_stack(&action.new_item, is_creative);
|
||||
let item_stack = descriptor_to_stack(&action.new_item);
|
||||
|
||||
let mut player_screen_handler = player.player_screen_handler.lock().await;
|
||||
|
||||
@@ -666,8 +645,7 @@ impl BedrockClient {
|
||||
|
||||
if data.action_type.0 == 0 {
|
||||
// Click block
|
||||
let is_creative = player.gamemode.load() == GameMode::Creative;
|
||||
let client_stack = descriptor_to_stack(&data.item_in_hand, is_creative);
|
||||
let client_stack = descriptor_to_stack(&data.item_in_hand);
|
||||
|
||||
let mut held_item = player.inventory().held_item().await;
|
||||
if !client_stack.is_empty() {
|
||||
@@ -763,8 +741,7 @@ impl BedrockClient {
|
||||
}
|
||||
} else if data.action_type.0 == 1 {
|
||||
// Click air / Use item
|
||||
let is_creative = player.gamemode.load() == GameMode::Creative;
|
||||
let client_stack = descriptor_to_stack(&data.item_in_hand, is_creative);
|
||||
let client_stack = descriptor_to_stack(&data.item_in_hand);
|
||||
|
||||
let mut held = player.inventory.held_item().await;
|
||||
if !client_stack.is_empty()
|
||||
@@ -1363,7 +1340,6 @@ impl BedrockClient {
|
||||
let mut result = 0u8; // 0 = Success, 1 = Error
|
||||
|
||||
for action in request.actions {
|
||||
tracing::info!("Processing ItemStackRequestAction: {:?}", action);
|
||||
match action {
|
||||
ItemStackRequestAction::CraftCreative {
|
||||
creative_item_id,
|
||||
|
||||
Reference in New Issue
Block a user