mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix: bedrock crafting
This commit is contained in:
@@ -22,6 +22,7 @@ pub enum Action {
|
||||
AbortBreak = 1,
|
||||
StopBreak = 2,
|
||||
GetUpdatedBlock = 3,
|
||||
/// Seems to be not used, or atleast not send by client
|
||||
DropItem = 4,
|
||||
StartSleeping = 5,
|
||||
StopSleeping = 6,
|
||||
|
||||
@@ -12,8 +12,6 @@ use pumpkin_data::{Block, chunk::ChunkStatus, fluid::Fluid};
|
||||
use pumpkin_nbt::{compound::NbtCompound, nbt_long_array};
|
||||
use rustc_hash::FxHashMap;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
chunk::{
|
||||
@@ -289,43 +287,11 @@ impl ChunkEntityData {
|
||||
chunk_entity_data.position[1],
|
||||
)));
|
||||
}
|
||||
let mut map = FxHashMap::default();
|
||||
for entity_nbt in chunk_entity_data.entities {
|
||||
if entity_nbt.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let uuid = if let Some(uuid) = entity_nbt.get_int_array("UUID") {
|
||||
if uuid.len() != 4 {
|
||||
debug!(
|
||||
"Entity in chunk {},{} has invalid UUID array length {}: {:?}",
|
||||
position.x,
|
||||
position.y,
|
||||
uuid.len(),
|
||||
entity_nbt
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Uuid::from_u128(
|
||||
(uuid[0] as u128) << 96
|
||||
| (uuid[1] as u128) << 64
|
||||
| (uuid[2] as u128) << 32
|
||||
| (uuid[3] as u128),
|
||||
)
|
||||
} else {
|
||||
trace!(
|
||||
"Entity in chunk {},{} is missing UUID: {:?}",
|
||||
position.x, position.y, entity_nbt
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
map.insert(uuid, entity_nbt);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
x: position.x,
|
||||
z: position.y,
|
||||
data: Mutex::new(map),
|
||||
data: Mutex::new(chunk_entity_data.entities),
|
||||
dirty: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
@@ -334,7 +300,7 @@ impl ChunkEntityData {
|
||||
let nbt = EntityNbt {
|
||||
data_version: WORLD_DATA_VERSION,
|
||||
position: [self.x, self.z],
|
||||
entities: self.data.lock().await.values().cloned().collect(),
|
||||
entities: self.data.lock().await.clone(),
|
||||
};
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
@@ -89,7 +89,7 @@ pub struct ChunkEntityData {
|
||||
pub x: i32,
|
||||
/// Chunk Z
|
||||
pub z: i32,
|
||||
pub data: Mutex<FxHashMap<uuid::Uuid, NbtCompound>>,
|
||||
pub data: Mutex<Vec<NbtCompound>>,
|
||||
|
||||
pub dirty: AtomicBool,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ pub struct Node {
|
||||
pub stage: StagedChunkEnum,
|
||||
pub in_degree: u32,
|
||||
pub in_queue: bool,
|
||||
pub in_flight: bool,
|
||||
pub edge: EdgeKey,
|
||||
}
|
||||
|
||||
@@ -19,6 +20,7 @@ impl Node {
|
||||
stage,
|
||||
in_degree: 0,
|
||||
in_queue: false,
|
||||
in_flight: false,
|
||||
edge: EdgeKey::null(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ pub struct GenerationSchedule {
|
||||
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
||||
listener: Arc<ChunkListener>,
|
||||
lighting_config: LightingEngineConfig,
|
||||
last_unload: std::time::Instant,
|
||||
}
|
||||
|
||||
impl GenerationSchedule {
|
||||
@@ -169,6 +170,7 @@ impl GenerationSchedule {
|
||||
listener,
|
||||
chunk_map: Default::default(),
|
||||
lighting_config,
|
||||
last_unload: std::time::Instant::now(),
|
||||
};
|
||||
scheduler.work(level_sched);
|
||||
})
|
||||
@@ -443,9 +445,12 @@ impl GenerationSchedule {
|
||||
{
|
||||
let task = &mut holder.tasks[i];
|
||||
if !task.is_null() {
|
||||
self.waiting_for_chunks.remove(task);
|
||||
self.drop_node(*task);
|
||||
*task = NodeKey::null();
|
||||
let is_in_flight = self.graph.nodes.get(*task).is_some_and(|n| n.in_flight);
|
||||
if !is_in_flight {
|
||||
self.waiting_for_chunks.remove(task);
|
||||
self.drop_node(*task);
|
||||
*task = NodeKey::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
if new_stage == StagedChunkEnum::None
|
||||
@@ -601,20 +606,13 @@ impl GenerationSchedule {
|
||||
self.public_chunk_map.remove(&pos);
|
||||
holder.public = false;
|
||||
}
|
||||
let sc = Arc::strong_count(&chunk);
|
||||
if sc == 1 {
|
||||
if chunk.is_dirty() {
|
||||
chunks.push((pos, Chunk::Level(chunk)));
|
||||
}
|
||||
self.chunk_map.remove(&pos);
|
||||
} else {
|
||||
warn!(
|
||||
"unload_chunk: chunk {pos:?} still has {} strong refs; cannot unload. holder.public={}",
|
||||
sc, holder.public
|
||||
);
|
||||
self.unload_chunks.insert(pos);
|
||||
holder.chunk = Some(Chunk::Level(chunk));
|
||||
|
||||
// Forcefully drop chunks when unloaded to prevent memory leaks
|
||||
// from dangling strong references
|
||||
if chunk.is_dirty() {
|
||||
chunks.push((pos, Chunk::Level(chunk)));
|
||||
}
|
||||
self.chunk_map.remove(&pos);
|
||||
}
|
||||
Chunk::Proto(chunk) => {
|
||||
debug_assert!(!holder.public);
|
||||
@@ -646,8 +644,8 @@ impl GenerationSchedule {
|
||||
let mut chunks = Vec::with_capacity(self.chunk_map.len());
|
||||
|
||||
for (pos, holder) in &mut self.chunk_map {
|
||||
if let Some(chunk) = holder.chunk.take() {
|
||||
let should_save = match &chunk {
|
||||
if let Some(chunk) = &holder.chunk {
|
||||
let should_save = match chunk {
|
||||
Chunk::Level(sync_chunk) => sync_chunk.is_dirty(),
|
||||
Chunk::Proto(proto) => {
|
||||
save_proto_chunk
|
||||
@@ -660,9 +658,11 @@ impl GenerationSchedule {
|
||||
};
|
||||
|
||||
if should_save {
|
||||
chunks.push((*pos, chunk));
|
||||
} else {
|
||||
holder.chunk = Some(chunk);
|
||||
let chunk_to_save = match chunk {
|
||||
Chunk::Level(sync_chunk) => Chunk::Level(sync_chunk.clone()),
|
||||
Chunk::Proto(_) => holder.chunk.take().unwrap(),
|
||||
};
|
||||
chunks.push((*pos, chunk_to_save));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -803,13 +803,14 @@ impl GenerationSchedule {
|
||||
|
||||
if was_public {
|
||||
self.apply_lighting_override(&chunk);
|
||||
holder.chunk = Some(Chunk::Level(chunk.clone()));
|
||||
self.public_chunk_map.insert(new_pos, chunk.clone());
|
||||
let public_chunk = chunk.clone();
|
||||
self.public_chunk_map.insert(new_pos, public_chunk);
|
||||
info!(
|
||||
"Notifying players: regenerated chunk at {:?} (was already public)",
|
||||
new_pos
|
||||
);
|
||||
self.listener.process_new_chunk(new_pos, &chunk);
|
||||
holder.chunk = Some(Chunk::Level(chunk));
|
||||
} else {
|
||||
self.apply_lighting_override(&chunk);
|
||||
let public_chunk = chunk.clone();
|
||||
@@ -1009,9 +1010,13 @@ impl GenerationSchedule {
|
||||
// 1. Get latest world state (player moves, etc)
|
||||
self.resort_work(self.send_level.get());
|
||||
|
||||
// Process unload queue continuously if there are chunks to unload
|
||||
if !self.unload_chunks.is_empty() {
|
||||
// Process unload queue periodically (every 1 second) to batch writes together
|
||||
// and act as a brief memory cache if a player walks back into the chunk.
|
||||
if !self.unload_chunks.is_empty()
|
||||
&& self.last_unload.elapsed() >= std::time::Duration::from_secs(1)
|
||||
{
|
||||
self.process_unload_queue();
|
||||
self.last_unload = std::time::Instant::now();
|
||||
}
|
||||
|
||||
// 2. Process all pending chunk results from workers
|
||||
@@ -1056,6 +1061,7 @@ impl GenerationSchedule {
|
||||
node.in_queue = false;
|
||||
continue;
|
||||
}
|
||||
node.in_flight = true;
|
||||
let node = node.clone();
|
||||
if node.stage == StagedChunkEnum::Empty {
|
||||
self.running_task_count += 1;
|
||||
@@ -1114,6 +1120,7 @@ impl GenerationSchedule {
|
||||
if !all_ready {
|
||||
if let Some(n) = self.graph.nodes.get_mut(task.1) {
|
||||
n.in_queue = false;
|
||||
n.in_flight = false;
|
||||
}
|
||||
self.waiting_for_chunks.insert(task.1);
|
||||
// Close the TOCTOU window: the chunk we're waiting for may
|
||||
|
||||
@@ -76,17 +76,17 @@ pub async fn io_read_work(
|
||||
|
||||
let (t_send, mut t_recv) = tokio::sync::mpsc::channel(1000);
|
||||
|
||||
let batch_len = batch.len();
|
||||
let level_clone = level.clone();
|
||||
let batch_clone = batch.clone();
|
||||
|
||||
let fetch_task = tokio::spawn(async move {
|
||||
level_clone
|
||||
.chunk_saver
|
||||
.fetch_chunks(&level_clone.level_folder, &batch_clone, t_send)
|
||||
.fetch_chunks(&level_clone.level_folder, &batch, t_send)
|
||||
.await;
|
||||
});
|
||||
|
||||
for _ in 0..batch.len() {
|
||||
for _ in 0..batch_len {
|
||||
let data = match t_recv.recv().await {
|
||||
Some(res) => res,
|
||||
None => break,
|
||||
|
||||
@@ -24,7 +24,7 @@ use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::{Block, block_properties::has_random_ticks, fluid::Fluid};
|
||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||
use pumpkin_util::world_seed::Seed;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
@@ -119,8 +119,10 @@ pub struct RandomTickSample {
|
||||
|
||||
pub struct LevelFolder {
|
||||
pub root_folder: PathBuf,
|
||||
pub dim_folder: PathBuf,
|
||||
pub region_folder: PathBuf,
|
||||
pub entities_folder: PathBuf,
|
||||
pub poi_folder: PathBuf,
|
||||
}
|
||||
|
||||
impl Level {
|
||||
@@ -132,16 +134,26 @@ impl Level {
|
||||
dimension: Dimension,
|
||||
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
||||
) -> Arc<Self> {
|
||||
let region_folder = root_folder.join("region");
|
||||
let entities_folder = root_folder.join("entities");
|
||||
let (namespace, name) = match dimension.minecraft_name.split_once(':') {
|
||||
Some((ns, n)) => (ns, n),
|
||||
None => ("minecraft", dimension.minecraft_name),
|
||||
};
|
||||
let dim_folder = root_folder.join("dimensions").join(namespace).join(name);
|
||||
|
||||
let region_folder = dim_folder.join("region");
|
||||
let entities_folder = dim_folder.join("entities");
|
||||
let poi_folder = dim_folder.join("poi");
|
||||
|
||||
std::fs::create_dir_all(®ion_folder).expect("Failed to create Region folder");
|
||||
std::fs::create_dir_all(&entities_folder).expect("Failed to create Entities folder");
|
||||
std::fs::create_dir_all(&poi_folder).expect("Failed to create POI folder");
|
||||
|
||||
let level_folder = Arc::new(LevelFolder {
|
||||
root_folder,
|
||||
dim_folder,
|
||||
region_folder,
|
||||
entities_folder,
|
||||
poi_folder,
|
||||
});
|
||||
|
||||
let seed = Seed(seed as u64);
|
||||
@@ -225,7 +237,7 @@ impl Level {
|
||||
let arc_chunk = Arc::new(ChunkEntityData {
|
||||
x: pos.x,
|
||||
z: pos.y,
|
||||
data: tokio::sync::Mutex::new(FxHashMap::default()),
|
||||
data: tokio::sync::Mutex::new(Vec::new()),
|
||||
dirty: AtomicBool::new(false),
|
||||
});
|
||||
|
||||
@@ -246,7 +258,7 @@ impl Level {
|
||||
let arc_chunk = Arc::new(ChunkEntityData {
|
||||
x: pos.x,
|
||||
z: pos.y,
|
||||
data: tokio::sync::Mutex::new(FxHashMap::default()),
|
||||
data: tokio::sync::Mutex::new(Vec::new()),
|
||||
dirty: AtomicBool::new(false),
|
||||
});
|
||||
|
||||
@@ -322,8 +334,9 @@ impl Level {
|
||||
self.tasks.wait().await;
|
||||
self.chunk_system_tasks.wait().await;
|
||||
|
||||
info!("Flushing data to disk for {}...", world_id);
|
||||
info!("Flushing chunk data to disk for {}...", world_id);
|
||||
self.chunk_saver.block_and_await_ongoing_tasks().await;
|
||||
info!("Flushing entity data to disk for {}...", world_id);
|
||||
self.entity_saver.block_and_await_ongoing_tasks().await;
|
||||
|
||||
// save all chunks currently in memory
|
||||
@@ -521,9 +534,7 @@ impl Level {
|
||||
.map(|entry| *entry.key())
|
||||
.collect();
|
||||
|
||||
if !entity_chunks_to_remove.is_empty() {
|
||||
self.clean_entity_chunks(&entity_chunks_to_remove);
|
||||
}
|
||||
// We do not clean them here because we want the caller to save any active entities in them first.
|
||||
|
||||
// if the difference is too big, we can shrink the loaded chunks
|
||||
// (1024 chunks is the equivalent to a 32x32 chunks area)
|
||||
|
||||
@@ -376,9 +376,9 @@ pub struct PoiStorage {
|
||||
|
||||
impl PoiStorage {
|
||||
#[must_use]
|
||||
pub fn new(world_folder: &Path) -> Self {
|
||||
pub fn new(poi_folder: PathBuf) -> Self {
|
||||
Self {
|
||||
folder: world_folder.join("poi"),
|
||||
folder: poi_folder,
|
||||
regions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -532,7 +532,7 @@ mod tests {
|
||||
let dir = std::env::temp_dir().join("pumpkin_poi_mca_test");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
|
||||
let mut storage = PoiStorage::new(&dir);
|
||||
let mut storage = PoiStorage::new(dir.join("poi"));
|
||||
|
||||
storage.add_portal(BlockPos(Vector3::new(100, 64, 100)));
|
||||
storage.add_portal(BlockPos(Vector3::new(110, 64, 100)));
|
||||
@@ -552,7 +552,7 @@ mod tests {
|
||||
assert!(mca_path.exists());
|
||||
|
||||
// Reload and verify
|
||||
let mut storage2 = PoiStorage::new(&dir);
|
||||
let mut storage2 = PoiStorage::new(dir.join("poi"));
|
||||
let results2 = storage2.get_in_square(
|
||||
BlockPos(Vector3::new(105, 64, 100)),
|
||||
16,
|
||||
|
||||
@@ -3300,6 +3300,13 @@ impl NBTStorage for Entity {
|
||||
if self.has_visual_fire.load(Relaxed) {
|
||||
nbt.put_bool("HasVisualFire", true);
|
||||
}
|
||||
nbt.put_int("TicksFrozen", self.frozen_ticks.load(Relaxed));
|
||||
if let Some(custom_name) = &**self.custom_name.load()
|
||||
&& let Ok(name_json) = pumpkin_util::serde_json::to_string(custom_name)
|
||||
{
|
||||
nbt.put_string("CustomName", name_json);
|
||||
}
|
||||
nbt.put_bool("CustomNameVisible", self.custom_name_visible.load(Relaxed));
|
||||
|
||||
// todo more...
|
||||
})
|
||||
@@ -3340,6 +3347,15 @@ impl NBTStorage for Entity {
|
||||
.store(nbt.get_int("PortalCooldown").unwrap_or(0) as u32, Relaxed);
|
||||
self.has_visual_fire
|
||||
.store(nbt.get_bool("HasVisualFire").unwrap_or(false), Relaxed);
|
||||
self.frozen_ticks
|
||||
.store(nbt.get_int("TicksFrozen").unwrap_or(0), Relaxed);
|
||||
if let Some(name_json) = nbt.get_string("CustomName")
|
||||
&& let Ok(component) = pumpkin_util::serde_json::from_str(name_json)
|
||||
{
|
||||
self.custom_name.store(Arc::new(Some(component)));
|
||||
}
|
||||
self.custom_name_visible
|
||||
.store(nbt.get_bool("CustomNameVisible").unwrap_or(false), Relaxed);
|
||||
// todo more...
|
||||
})
|
||||
}
|
||||
|
||||
@@ -886,11 +886,15 @@ impl Player {
|
||||
let chunks_to_clean = level.mark_chunks_as_not_watched(&radial_chunks).await;
|
||||
// Remove chunks with no watchers from the cache
|
||||
if !chunks_to_clean.is_empty() {
|
||||
world.remove_entities_in_chunks(&chunks_to_clean).await;
|
||||
level.clean_entity_chunks(&chunks_to_clean);
|
||||
world.remove_entities_in_chunks(&chunks_to_clean);
|
||||
}
|
||||
// Remove left over entries from all possiblily loaded chunks
|
||||
level.clean_memory();
|
||||
let cleaned_chunks = level.clean_memory();
|
||||
if !cleaned_chunks.is_empty() {
|
||||
world.remove_entities_in_chunks(&cleaned_chunks).await;
|
||||
level.clean_entity_chunks(&cleaned_chunks);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Removed player id {} from world {} ({} chunks remain cached)",
|
||||
@@ -3093,6 +3097,7 @@ impl Player {
|
||||
.await
|
||||
{
|
||||
screen_handler.set_received_stack(slot_index, updated_stack);
|
||||
screen_handler.send_content_updates().await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4869,19 +4874,170 @@ impl InventoryPlayer for Player {
|
||||
packet: &'a CSetContainerContent,
|
||||
) -> PlayerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.client.enqueue_packet(packet).await;
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
java.enqueue_packet(packet).await;
|
||||
}
|
||||
ClientPlatform::Bedrock(bedrock) => {
|
||||
use pumpkin_protocol::bedrock::{
|
||||
client::inventory_content::CInventoryContent,
|
||||
network_item::{
|
||||
ContainerName, FullContainerName, NetworkItemStackDescriptor,
|
||||
},
|
||||
};
|
||||
use pumpkin_protocol::codec::var_uint::VarUInt;
|
||||
|
||||
let window_id = packet.window_id.0 as u32;
|
||||
let slots: Vec<NetworkItemStackDescriptor> = packet
|
||||
.slot_data
|
||||
.iter()
|
||||
.map(|s| NetworkItemStackDescriptor::from(&*s.0))
|
||||
.collect();
|
||||
|
||||
if window_id == 0 {
|
||||
let bedrock_packet = CInventoryContent {
|
||||
container_id: VarUInt(0),
|
||||
slots,
|
||||
full_container_name: FullContainerName {
|
||||
container_name: ContainerName::Inventory,
|
||||
dynamic_id: None,
|
||||
},
|
||||
storage_item: NetworkItemStackDescriptor::default(),
|
||||
};
|
||||
bedrock.enqueue_packet(&bedrock_packet).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn enqueue_slot_packet<'a>(&'a self, packet: &'a CSetContainerSlot) -> PlayerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.client.enqueue_packet(packet).await;
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
java.enqueue_packet(packet).await;
|
||||
}
|
||||
ClientPlatform::Bedrock(bedrock) => {
|
||||
use pumpkin_protocol::bedrock::{
|
||||
client::inventory_content::CInventoryContent,
|
||||
client::inventory_slot::CInventorySlot,
|
||||
network_item::{
|
||||
ContainerName, FullContainerName, NetworkItemStackDescriptor,
|
||||
},
|
||||
};
|
||||
use pumpkin_protocol::codec::var_uint::VarUInt;
|
||||
|
||||
let window_id = packet.window_id;
|
||||
tracing::info!(
|
||||
"enqueue_slot_packet: window_id={}, slot={}",
|
||||
window_id,
|
||||
packet.slot
|
||||
);
|
||||
|
||||
if window_id == 0 {
|
||||
tracing::info!(
|
||||
"enqueue_slot_packet: window_id is 0, sending CInventoryContent to Bedrock client"
|
||||
);
|
||||
let mut slots = Vec::with_capacity(36);
|
||||
let main_inventory = &self.inventory().main_inventory;
|
||||
for s in main_inventory {
|
||||
let stack = s.lock().await;
|
||||
slots.push(NetworkItemStackDescriptor::from(&*stack));
|
||||
}
|
||||
|
||||
let bedrock_packet = CInventoryContent {
|
||||
container_id: VarUInt(0),
|
||||
slots,
|
||||
full_container_name: FullContainerName {
|
||||
container_name: ContainerName::Inventory,
|
||||
dynamic_id: None,
|
||||
},
|
||||
storage_item: NetworkItemStackDescriptor::default(),
|
||||
};
|
||||
bedrock.enqueue_packet(&bedrock_packet).await;
|
||||
} else {
|
||||
let slot_idx = packet.slot as usize;
|
||||
let item_desc = NetworkItemStackDescriptor::from(&*packet.slot_data.0);
|
||||
|
||||
// Container screen
|
||||
let current_handler = self.current_screen_handler.lock().await.clone();
|
||||
let handler = current_handler.lock().await;
|
||||
let window_type = handler.window_type();
|
||||
let total_slots = handler.get_behaviour().slots.len();
|
||||
let bedrock_info = if total_slots >= 36 {
|
||||
let container_slots = total_slots - 36;
|
||||
if slot_idx < container_slots {
|
||||
if window_type == Some(WindowType::Crafting) {
|
||||
if slot_idx == 0 {
|
||||
Some((ContainerName::CreatedOutput, 0))
|
||||
} else {
|
||||
Some((
|
||||
ContainerName::CraftingInput,
|
||||
(32 + slot_idx - 1) as u8,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Some((ContainerName::LevelEntity, slot_idx as u8))
|
||||
}
|
||||
} else {
|
||||
let inv_slot = slot_idx - container_slots;
|
||||
if inv_slot < 27 {
|
||||
Some((ContainerName::Inventory, (inv_slot + 9) as u8))
|
||||
} else {
|
||||
Some((ContainerName::Inventory, (inv_slot - 27) as u8))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((container_name, slot_id)) = bedrock_info {
|
||||
let bedrock_packet = CInventorySlot {
|
||||
window_id: VarUInt(window_id as u32),
|
||||
inventory_slot: VarUInt(slot_id as u32),
|
||||
container_name: Some(FullContainerName {
|
||||
container_name,
|
||||
dynamic_id: None,
|
||||
}),
|
||||
storage: None,
|
||||
item: item_desc,
|
||||
};
|
||||
bedrock.enqueue_packet(&bedrock_packet).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn enqueue_cursor_packet<'a>(&'a self, packet: &'a CSetCursorItem) -> PlayerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.client.enqueue_packet(packet).await;
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
java.enqueue_packet(packet).await;
|
||||
}
|
||||
ClientPlatform::Bedrock(bedrock) => {
|
||||
use pumpkin_protocol::bedrock::{
|
||||
client::inventory_content::CInventoryContent,
|
||||
network_item::{
|
||||
ContainerName, FullContainerName, NetworkItemStackDescriptor,
|
||||
},
|
||||
};
|
||||
use pumpkin_protocol::codec::var_uint::VarUInt;
|
||||
|
||||
let item_desc = NetworkItemStackDescriptor::from(&*packet.stack.0);
|
||||
let bedrock_packet = CInventoryContent {
|
||||
container_id: VarUInt(59),
|
||||
slots: vec![item_desc],
|
||||
full_container_name: FullContainerName {
|
||||
container_name: ContainerName::Cursor,
|
||||
dynamic_id: None,
|
||||
},
|
||||
storage_item: NetworkItemStackDescriptor::default(),
|
||||
};
|
||||
bedrock.enqueue_packet(&bedrock_packet).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4899,7 +5055,42 @@ impl InventoryPlayer for Player {
|
||||
packet: &'a CSetPlayerInventory,
|
||||
) -> PlayerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.client.enqueue_packet(packet).await;
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
java.enqueue_packet(packet).await;
|
||||
}
|
||||
ClientPlatform::Bedrock(bedrock) => {
|
||||
use pumpkin_protocol::bedrock::{
|
||||
client::inventory_content::CInventoryContent,
|
||||
network_item::{
|
||||
ContainerName, FullContainerName, NetworkItemStackDescriptor,
|
||||
},
|
||||
};
|
||||
use pumpkin_protocol::codec::var_uint::VarUInt;
|
||||
|
||||
tracing::info!(
|
||||
"enqueue_slot_set_packet: slot={}, sending CInventoryContent to Bedrock client",
|
||||
packet.slot.0
|
||||
);
|
||||
let mut slots = Vec::with_capacity(36);
|
||||
let main_inventory = &self.inventory().main_inventory;
|
||||
for s in main_inventory {
|
||||
let stack = s.lock().await;
|
||||
slots.push(NetworkItemStackDescriptor::from(&*stack));
|
||||
}
|
||||
|
||||
let bedrock_packet = CInventoryContent {
|
||||
container_id: VarUInt(0),
|
||||
slots,
|
||||
full_container_name: FullContainerName {
|
||||
container_name: ContainerName::Inventory,
|
||||
dynamic_id: None,
|
||||
},
|
||||
storage_item: NetworkItemStackDescriptor::default(),
|
||||
};
|
||||
bedrock.enqueue_packet(&bedrock_packet).await;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -451,10 +451,69 @@ impl BedrockClient {
|
||||
player: &Arc<Player>,
|
||||
packet: SInventoryTransaction,
|
||||
) {
|
||||
tracing::info!("handle_inventory_action: packet={:?}", packet);
|
||||
let mut inventory_updated = false;
|
||||
let mut updates = Vec::new();
|
||||
let result = 0u8;
|
||||
|
||||
if packet.actions.is_empty() && packet.legacy_request_id.0 != 0 {
|
||||
let mut player_screen_handler = player.player_screen_handler.lock().await;
|
||||
for legacy_slot in &packet.legacy_set_item_slots {
|
||||
let mapped_window_id = match legacy_slot.container_id {
|
||||
28 | 29 => 0, // HotBar or Inventory
|
||||
6 | 120 => 120, // Armor
|
||||
34 | 119 => 119, // Offhand
|
||||
other => other as i32,
|
||||
};
|
||||
for &slot_id in &legacy_slot.slots {
|
||||
if let Some(screen_slot) =
|
||||
map_bedrock_slot_to_screen_handler(mapped_window_id, slot_id as u32)
|
||||
{
|
||||
let current_stack = player_screen_handler
|
||||
.get_slot(screen_slot)
|
||||
.get_cloned_stack()
|
||||
.await;
|
||||
if !current_stack.is_empty() {
|
||||
player.drop_item(current_stack.clone()).await;
|
||||
|
||||
player_screen_handler
|
||||
.get_slot(screen_slot)
|
||||
.set_stack(ItemStack::EMPTY.clone())
|
||||
.await;
|
||||
player_screen_handler
|
||||
.set_received_stack(screen_slot, ItemStack::EMPTY.clone());
|
||||
|
||||
record_update(
|
||||
&mut updates,
|
||||
FullContainerName {
|
||||
container_name: match legacy_slot.container_id {
|
||||
28 => ContainerName::HotBar,
|
||||
_ => ContainerName::Inventory,
|
||||
},
|
||||
dynamic_id: None,
|
||||
},
|
||||
slot_id,
|
||||
0,
|
||||
VarInt(0),
|
||||
);
|
||||
inventory_updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
player_screen_handler.send_content_updates().await;
|
||||
}
|
||||
|
||||
for action in &packet.actions {
|
||||
if let Some(window_id) = action.window_id {
|
||||
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);
|
||||
let new_stack = descriptor_to_stack(&action.new_item);
|
||||
if old_stack.is_empty() && !new_stack.is_empty() {
|
||||
player.drop_item(new_stack).await;
|
||||
}
|
||||
} else if let Some(window_id) = action.window_id {
|
||||
if let Some(screen_slot) =
|
||||
map_bedrock_slot_to_screen_handler(window_id, action.inventory_slot)
|
||||
{
|
||||
@@ -635,6 +694,52 @@ impl BedrockClient {
|
||||
player.living_entity.clear_active_hand().await;
|
||||
}
|
||||
}
|
||||
|
||||
if packet.legacy_request_id.0 != 0 {
|
||||
use pumpkin_protocol::bedrock::client::item_stack_response::{
|
||||
CItemStackResponse, ItemStackResponse, ItemStackResponseContainerInfo,
|
||||
ItemStackResponseSlotInfo,
|
||||
};
|
||||
|
||||
let mut container_infos = Vec::new();
|
||||
if result == 0 {
|
||||
for update in updates {
|
||||
let container_info = container_infos.iter_mut().find(
|
||||
|info: &&mut ItemStackResponseContainerInfo| {
|
||||
info.container_name == update.container_name
|
||||
},
|
||||
);
|
||||
|
||||
let slot_info = ItemStackResponseSlotInfo {
|
||||
slot: update.slot_id,
|
||||
hotbar_slot: update.slot_id,
|
||||
count: update.count,
|
||||
item_stack_id: update.stack_id,
|
||||
custom_name: String::new(),
|
||||
filtered_custom_name: String::new(),
|
||||
durability_correction: VarInt(0),
|
||||
};
|
||||
|
||||
if let Some(info) = container_info {
|
||||
info.slots.push(slot_info);
|
||||
} else {
|
||||
container_infos.push(ItemStackResponseContainerInfo {
|
||||
container_name: update.container_name.clone(),
|
||||
slots: vec![slot_info],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.enqueue_packet(&CItemStackResponse {
|
||||
responses: vec![ItemStackResponse {
|
||||
result,
|
||||
request_id: packet.legacy_request_id,
|
||||
container_infos,
|
||||
}],
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_interaction(&self, player: &Arc<Player>, packet: SInteraction) {
|
||||
@@ -868,6 +973,9 @@ impl BedrockClient {
|
||||
player.mining.store(false, Ordering::Relaxed);
|
||||
world.set_block_breaking(entity, location, -1).await;
|
||||
}
|
||||
PlayerAction::DropItem => {
|
||||
player.drop_held_item(false).await;
|
||||
}
|
||||
// TODO
|
||||
_ => {}
|
||||
}
|
||||
@@ -953,6 +1061,7 @@ 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,
|
||||
@@ -1041,6 +1150,29 @@ impl BedrockClient {
|
||||
}
|
||||
|
||||
source_stack.decrement(count);
|
||||
if source.container_name.container_name == ContainerName::CreatedOutput
|
||||
{
|
||||
if let Some(ref mut stack) = created_item {
|
||||
stack.decrement(count);
|
||||
if stack.is_empty() {
|
||||
created_item = None;
|
||||
}
|
||||
}
|
||||
} else if source.container_name.container_name == ContainerName::Cursor
|
||||
{
|
||||
let cursor_is_empty = screen_handler
|
||||
.get_behaviour()
|
||||
.cursor_stack
|
||||
.lock()
|
||||
.await
|
||||
.is_empty();
|
||||
if cursor_is_empty && let Some(ref mut stack) = created_item {
|
||||
stack.decrement(count);
|
||||
if stack.is_empty() {
|
||||
created_item = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
let source_stack = if source_stack.is_empty() {
|
||||
ItemStack::EMPTY.clone()
|
||||
} else {
|
||||
@@ -1554,11 +1686,23 @@ fn map_bedrock_container_slot(
|
||||
ContainerName::Cursor => None,
|
||||
ContainerName::CraftingInput => {
|
||||
if is_player_screen {
|
||||
(slot_id < 4).then(|| 1 + slot_id as usize)
|
||||
if slot_id < 4 {
|
||||
Some(1 + slot_id as usize)
|
||||
} else if (28..32).contains(&slot_id) {
|
||||
Some(1 + (slot_id - 28) as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if screen_handler.window_type()
|
||||
== Some(pumpkin_data::screen::WindowType::Crafting)
|
||||
{
|
||||
(slot_id < 9).then(|| 1 + slot_id as usize)
|
||||
if slot_id < 9 {
|
||||
Some(1 + slot_id as usize)
|
||||
} else if (32..41).contains(&slot_id) {
|
||||
Some(1 + (slot_id - 32) as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -1800,6 +1944,11 @@ async fn get_slot_stack(
|
||||
}
|
||||
if slot_info.container_name.container_name == ContainerName::Cursor {
|
||||
let cursor_lock = screen_handler.get_behaviour().cursor_stack.lock().await;
|
||||
if cursor_lock.is_empty()
|
||||
&& let Some(stack) = created_item
|
||||
{
|
||||
return stack.clone();
|
||||
}
|
||||
return cursor_lock.clone();
|
||||
}
|
||||
if let Some(screen_slot) = map_bedrock_container_slot(
|
||||
|
||||
@@ -31,7 +31,6 @@ use crate::{
|
||||
server::{RecipeManager, Server},
|
||||
world::World,
|
||||
};
|
||||
use pumpkin_world::level::SyncChunk;
|
||||
|
||||
pub struct WasmResource<T> {
|
||||
pub provider: T,
|
||||
@@ -44,7 +43,7 @@ pub type JavaPlayerResource = WasmResource<Arc<Player>>;
|
||||
pub type BedrockPlayerResource = WasmResource<Arc<Player>>;
|
||||
pub type EntityResource = WasmResource<Arc<dyn EntityBase>>;
|
||||
pub type WorldResource = WasmResource<Arc<World>>;
|
||||
pub type ChunkResource = WasmResource<(Arc<World>, SyncChunk)>;
|
||||
pub type ChunkResource = WasmResource<(Arc<World>, Weak<pumpkin_world::chunk::ChunkData>)>;
|
||||
pub type WorldBorderResource = WasmResource<Arc<World>>;
|
||||
pub type ScoreboardResource = WasmResource<Arc<World>>;
|
||||
pub type GuiResource = WasmResource<Arc<Mutex<PluginGui>>>;
|
||||
@@ -157,7 +156,7 @@ impl PluginHostState {
|
||||
pub fn add_chunk<T>(
|
||||
&mut self,
|
||||
world: Arc<World>,
|
||||
chunk: SyncChunk,
|
||||
chunk: Weak<pumpkin_world::chunk::ChunkData>,
|
||||
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
|
||||
let resource = self.resource_table.push(ChunkResource {
|
||||
provider: (world, chunk),
|
||||
|
||||
@@ -229,7 +229,7 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
||||
.get(&pos)
|
||||
.map(|c| c.value().clone());
|
||||
if let Some(chunk) = chunk {
|
||||
let res = self.add_chunk(world_provider, chunk)?;
|
||||
let res = self.add_chunk(world_provider, std::sync::Arc::downgrade(&chunk))?;
|
||||
Ok(Some(res))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -696,12 +696,18 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
async fn get_x(&mut self, chunk: Resource<WitChunk>) -> wasmtime::Result<i32> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
Ok(chunk_data.x)
|
||||
}
|
||||
|
||||
async fn get_z(&mut self, chunk: Resource<WitChunk>) -> wasmtime::Result<i32> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
Ok(chunk_data.z)
|
||||
}
|
||||
|
||||
@@ -712,6 +718,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
) -> wasmtime::Result<u16> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
Ok(chunk_data
|
||||
.section
|
||||
.get_block_absolute_y(pos.x as usize, pos.y, pos.z as usize)
|
||||
@@ -725,6 +734,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
) -> wasmtime::Result<WitBlockState> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
let id = chunk_data
|
||||
.section
|
||||
.get_block_absolute_y(pos.x as usize, pos.y, pos.z as usize)
|
||||
@@ -783,6 +795,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
) -> wasmtime::Result<()> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (world, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
|
||||
let replaced =
|
||||
chunk_data.set_block_absolute_y(pos.x as usize, pos.y, pos.z as usize, state);
|
||||
@@ -804,6 +819,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
) -> wasmtime::Result<pumpkin::plugin::biomes::Biome> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
let id = chunk_data
|
||||
.section
|
||||
.get_rough_biome_absolute_y(pos.x as usize, pos.y, pos.z as usize)
|
||||
@@ -821,6 +839,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
) -> wasmtime::Result<Option<BlockEntityType>> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (world, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
let absolute_pos =
|
||||
BlockPos::new(chunk_data.x * 16 + pos.x, pos.y, chunk_data.z * 16 + pos.z);
|
||||
let block_entity = world.get_block_entity(&absolute_pos);
|
||||
@@ -836,6 +857,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
) -> wasmtime::Result<i32> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
Ok(chunk_data.heightmap.lock().unwrap().get(
|
||||
ChunkHeightmapType::WorldSurface,
|
||||
x,
|
||||
@@ -851,6 +875,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
) -> wasmtime::Result<u8> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
let section_index = (pos.y - chunk_data.section.min_y) as usize / 16;
|
||||
Ok(chunk_data
|
||||
.light_engine
|
||||
@@ -870,6 +897,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
||||
) -> wasmtime::Result<u8> {
|
||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||
let (_, chunk_data) = &chunk_res.provider;
|
||||
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||
};
|
||||
let section_index = (pos.y - chunk_data.section.min_y) as usize / 16;
|
||||
Ok(chunk_data
|
||||
.light_engine
|
||||
|
||||
@@ -196,12 +196,13 @@ impl Server {
|
||||
let defaultgamemode = Mutex::new(DefaultGamemode {
|
||||
gamemode: basic_config.default_gamemode,
|
||||
});
|
||||
let players_dir = world_path.join("players");
|
||||
let player_data_storage = ServerPlayerData::new(
|
||||
world_path.join("playerdata"),
|
||||
players_dir.join("data"),
|
||||
Duration::from_secs(advanced_config.player_data.save_player_cron_interval),
|
||||
advanced_config.player_data.save_player_data,
|
||||
);
|
||||
let advancement_manager = Arc::new(AdvancementManager::new(world_path.clone(), true));
|
||||
let advancement_manager = Arc::new(AdvancementManager::new(players_dir.clone(), true));
|
||||
let white_list = AtomicBool::new(basic_config.white_list);
|
||||
|
||||
let tick_rate_manager = Arc::new(ServerTickRateManager::new(basic_config.tps));
|
||||
|
||||
@@ -114,8 +114,8 @@ pub async fn update_position(player: &Arc<Player>) {
|
||||
.await;
|
||||
|
||||
if !chunks_to_clean.is_empty() {
|
||||
world.remove_entities_in_chunks(&chunks_to_clean).await;
|
||||
world.level.clean_entity_chunks(&chunks_to_clean);
|
||||
world.remove_entities_in_chunks(&chunks_to_clean);
|
||||
}
|
||||
|
||||
if !loading_chunks.is_empty() {
|
||||
|
||||
@@ -285,7 +285,7 @@ impl World {
|
||||
let generation_settings = GenerationSettings::from_dimension(&dimension);
|
||||
|
||||
// Load portal POI from disk (PoiStorage::new automatically loads from disk if files exist)
|
||||
let portal_poi = portal::PortalPoiStorage::new(&level.level_folder.root_folder);
|
||||
let portal_poi = portal::PortalPoiStorage::new(level.level_folder.poi_folder.clone());
|
||||
let dragon_fight = (dimension.minecraft_name == Dimension::THE_END.minecraft_name)
|
||||
.then(|| Mutex::new(dragon_fight::DragonFight::new()));
|
||||
Self {
|
||||
@@ -377,7 +377,6 @@ impl World {
|
||||
// First lets see if the entity was saved on an other chunk, and if the current chunk does not match we remove it
|
||||
// Otherwise we just update the nbt data
|
||||
let base_entity = entity.get_entity();
|
||||
let uuid = base_entity.entity_uuid;
|
||||
let current_chunk_coordinate = base_entity.block_pos.load().chunk_position();
|
||||
let mut nbt = NbtCompound::new();
|
||||
entity.write_nbt(&mut nbt).await;
|
||||
@@ -388,37 +387,21 @@ impl World {
|
||||
chunk.mark_dirty(true);
|
||||
let mut data = chunk.data.lock().await;
|
||||
if old_chunk == current_chunk_coordinate {
|
||||
data.insert(uuid, nbt);
|
||||
data.push(nbt);
|
||||
return;
|
||||
}
|
||||
|
||||
// The chunk has changed, lets remove the entity from the old chunk
|
||||
data.remove(&uuid);
|
||||
// TODO?
|
||||
data.clear();
|
||||
}
|
||||
// We did not continue, so lets save data in a new chunk
|
||||
let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await;
|
||||
let mut data = chunk.data.lock().await;
|
||||
data.insert(uuid, nbt);
|
||||
data.push(nbt);
|
||||
chunk.mark_dirty(true);
|
||||
}
|
||||
|
||||
async fn remove_entity_data(&self, entity: &Entity) {
|
||||
let current_chunk_coordinate = entity.block_pos.load().chunk_position();
|
||||
if let Some(old_chunk) = entity.first_loaded_chunk_position.load() {
|
||||
let old_chunk = old_chunk.to_vec2_i32();
|
||||
let chunk = self.level.get_entity_chunk(old_chunk).await;
|
||||
chunk.mark_dirty(true);
|
||||
if old_chunk == current_chunk_coordinate {
|
||||
chunk.data.lock().await.remove(&entity.entity_uuid);
|
||||
} else {
|
||||
let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await;
|
||||
// The chunk has changed, lets remove the entity from the old chunk
|
||||
chunk.data.lock().await.remove(&entity.entity_uuid);
|
||||
chunk.mark_dirty(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends an entity status update to all players tracking the specified entity.
|
||||
pub fn send_entity_status(&self, entity: &Entity, status: EntityStatus) {
|
||||
let chunk_pos = entity.chunk_pos.load();
|
||||
@@ -1134,7 +1117,8 @@ impl World {
|
||||
self.level.should_unload.store(true, Relaxed);
|
||||
let cleaned_chunks = self.level.clean_memory();
|
||||
if !cleaned_chunks.is_empty() {
|
||||
self.remove_entities_in_chunks(&cleaned_chunks);
|
||||
self.remove_entities_in_chunks(&cleaned_chunks).await;
|
||||
self.level.clean_entity_chunks(&cleaned_chunks);
|
||||
}
|
||||
// If autosave is configured and this tick will trigger an autosave, don't double notify
|
||||
if self.level.autosave_ticks == 0 {
|
||||
@@ -3526,6 +3510,7 @@ impl World {
|
||||
let mut entity_receiver = self.level.receive_entity_chunks(chunks);
|
||||
let level = self.level.clone();
|
||||
let world = self.clone();
|
||||
|
||||
player.clone().spawn_task(async move {
|
||||
'main: loop {
|
||||
let recv_result = tokio::select! {
|
||||
@@ -3553,62 +3538,46 @@ impl World {
|
||||
"Received chunk {:?}, but it is no longer watched... cleaning",
|
||||
&position
|
||||
);
|
||||
let mut ids_to_remove = Vec::new();
|
||||
|
||||
for (uuid, entity_nbt) in chunk.data.lock().await.iter() {
|
||||
let Some(id) = entity_nbt.get_string("id") else {
|
||||
warn!("Entity has no ID");
|
||||
continue;
|
||||
};
|
||||
let Some(entity_type) =
|
||||
EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(id))
|
||||
else {
|
||||
warn!("Entity has no valid Entity Type {id}");
|
||||
continue;
|
||||
};
|
||||
// Pos is zero since it will read from nbt
|
||||
let entity =
|
||||
from_type(entity_type, Vector3::new(0.0, 0.0, 0.0), &world, *uuid);
|
||||
entity.read_nbt_non_mut(entity_nbt).await;
|
||||
let base_entity = entity.get_entity();
|
||||
if first_load {
|
||||
for entity_nbt in chunk.data.lock().await.iter() {
|
||||
let Some(id) = entity_nbt.get_string("id") else {
|
||||
continue;
|
||||
};
|
||||
let Some(entity_type) =
|
||||
EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(id))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
ids_to_remove.push(VarInt(base_entity.entity_id));
|
||||
let entity = from_type(
|
||||
entity_type,
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
&world,
|
||||
Uuid::new_v4(),
|
||||
);
|
||||
entity.read_nbt_non_mut(entity_nbt).await;
|
||||
let base_entity = entity.get_entity();
|
||||
|
||||
if first_load {
|
||||
let mut nbt = NbtCompound::new();
|
||||
entity.write_nbt(&mut nbt).await;
|
||||
|
||||
if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load()
|
||||
{
|
||||
let old_chunk = old_chunk.to_vec2_i32();
|
||||
let chunk = world.level.get_entity_chunk(old_chunk).await;
|
||||
chunk.mark_dirty(true);
|
||||
let base_entity = entity.get_entity();
|
||||
let current_chunk_coordinate =
|
||||
base_entity.block_pos.load().chunk_position();
|
||||
|
||||
let mut data = chunk.data.lock().await;
|
||||
if old_chunk == current_chunk_coordinate {
|
||||
data.insert(*uuid, nbt);
|
||||
data.push(nbt);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The chunk has changed, lets remove the entity from the old chunk
|
||||
data.remove(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ids_to_remove.is_empty() {
|
||||
match player.client.as_ref() {
|
||||
crate::net::ClientPlatform::Java(java) => {
|
||||
java.enqueue_packet(&CRemoveEntities::new(&ids_to_remove))
|
||||
.await;
|
||||
}
|
||||
crate::net::ClientPlatform::Bedrock(bedrock) => {
|
||||
for id in &ids_to_remove {
|
||||
bedrock
|
||||
.enqueue_packet(&CRemoveActor::new(VarLong(id.0 as i64)))
|
||||
.await;
|
||||
}
|
||||
data.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3617,7 +3586,7 @@ impl World {
|
||||
|
||||
// Add all new Entities to the world
|
||||
let mut entities_to_add: Vec<Arc<dyn EntityBase>> = Vec::new();
|
||||
for (uuid, entity_nbt) in chunk.data.lock().await.iter() {
|
||||
for entity_nbt in chunk.data.lock().await.iter() {
|
||||
let Some(id) = entity_nbt.get_string("id") else {
|
||||
debug!("Entity has no ID");
|
||||
continue;
|
||||
@@ -3629,29 +3598,17 @@ impl World {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Check if this entity already exists in the world (e.g. another player
|
||||
// is still online and tracking it). If so, just send the spawn packet
|
||||
// for the existing entity to the reconnecting player instead of
|
||||
// creating a duplicate with a different entity_id.
|
||||
let existing = world
|
||||
.entities
|
||||
.load()
|
||||
.iter()
|
||||
.find(|e| e.get_entity().entity_uuid == *uuid)
|
||||
.cloned();
|
||||
if let Some(existing_entity) = existing {
|
||||
let base_entity = existing_entity.get_entity();
|
||||
player
|
||||
.client
|
||||
.enqueue_packet(&base_entity.create_spawn_packet())
|
||||
.await;
|
||||
existing_entity.init_data_tracker().await;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pos is zero since it will read from nbt
|
||||
let entity = from_type(entity_type, Vector3::new(0.0, 0.0, 0.0), &world, *uuid);
|
||||
let entity = from_type(
|
||||
entity_type,
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
&world,
|
||||
Uuid::new_v4(),
|
||||
);
|
||||
entity.read_nbt_non_mut(entity_nbt).await;
|
||||
|
||||
entity.init_data_tracker().await;
|
||||
|
||||
let base_entity = entity.get_entity();
|
||||
|
||||
// Clear velocity so the client does not replay the drop animation.
|
||||
@@ -3663,12 +3620,12 @@ impl World {
|
||||
.client
|
||||
.enqueue_packet(&base_entity.create_spawn_packet())
|
||||
.await;
|
||||
entity.init_data_tracker().await;
|
||||
|
||||
if first_load {
|
||||
entities_to_add.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if first_load && !entities_to_add.is_empty() {
|
||||
world.entities.rcu(|current_entities| {
|
||||
let mut new_entities = (**current_entities).clone();
|
||||
@@ -4152,7 +4109,7 @@ impl World {
|
||||
self.spawn_state.load().add_entity(self, entity.as_ref());
|
||||
|
||||
let chunk = self.level.get_entity_chunk(chunk_coordinate).await;
|
||||
chunk.data.lock().await.insert(base_entity.entity_uuid, nbt);
|
||||
chunk.data.lock().await.push(nbt);
|
||||
chunk.mark_dirty(true);
|
||||
|
||||
self.entities.rcu(|current_entities| {
|
||||
@@ -4162,6 +4119,7 @@ impl World {
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn remove_entity(&self, entity: &dyn EntityBase) {
|
||||
let base_entity = entity.get_entity();
|
||||
self.spawn_state.load().remove_entity(self, entity);
|
||||
@@ -4177,11 +4135,9 @@ impl World {
|
||||
&CRemoveEntities::new(&[base_entity.entity_id.into()]),
|
||||
&CRemoveActor::new(VarLong(base_entity.entity_id as i64)),
|
||||
);
|
||||
|
||||
self.remove_entity_data(base_entity).await;
|
||||
}
|
||||
|
||||
pub fn remove_entities_in_chunks(&self, chunks: &[Vector2<i32>]) {
|
||||
pub async fn remove_entities_in_chunks(&self, chunks: &[Vector2<i32>]) {
|
||||
let chunks_set: FxHashSet<_> = chunks.iter().copied().collect();
|
||||
let mut entities_to_remove = Vec::new();
|
||||
|
||||
@@ -4201,9 +4157,8 @@ impl World {
|
||||
});
|
||||
|
||||
for entity in entities_to_remove {
|
||||
self.save_entity(&entity).await;
|
||||
self.spawn_state.load().remove_entity(self, entity.as_ref());
|
||||
// Important: We do NOT call remove_entity_data here because we want the entities
|
||||
// to persist in the chunk data on disk. We only remove them from the active world (RAM).
|
||||
}
|
||||
|
||||
self.block_entities
|
||||
|
||||
Reference in New Issue
Block a user