fix: bedrock crafting
This commit is contained in:
parent
efa97ad174
commit
7ff990c468
@ -22,6 +22,7 @@ pub enum Action {
|
|||||||
AbortBreak = 1,
|
AbortBreak = 1,
|
||||||
StopBreak = 2,
|
StopBreak = 2,
|
||||||
GetUpdatedBlock = 3,
|
GetUpdatedBlock = 3,
|
||||||
|
/// Seems to be not used, or atleast not send by client
|
||||||
DropItem = 4,
|
DropItem = 4,
|
||||||
StartSleeping = 5,
|
StartSleeping = 5,
|
||||||
StopSleeping = 6,
|
StopSleeping = 6,
|
||||||
|
|||||||
@ -12,8 +12,6 @@ use pumpkin_data::{Block, chunk::ChunkStatus, fluid::Fluid};
|
|||||||
use pumpkin_nbt::{compound::NbtCompound, nbt_long_array};
|
use pumpkin_nbt::{compound::NbtCompound, nbt_long_array};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{debug, trace};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chunk::{
|
chunk::{
|
||||||
@ -289,43 +287,11 @@ impl ChunkEntityData {
|
|||||||
chunk_entity_data.position[1],
|
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 {
|
Ok(Self {
|
||||||
x: position.x,
|
x: position.x,
|
||||||
z: position.y,
|
z: position.y,
|
||||||
data: Mutex::new(map),
|
data: Mutex::new(chunk_entity_data.entities),
|
||||||
dirty: AtomicBool::new(false),
|
dirty: AtomicBool::new(false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -334,7 +300,7 @@ impl ChunkEntityData {
|
|||||||
let nbt = EntityNbt {
|
let nbt = EntityNbt {
|
||||||
data_version: WORLD_DATA_VERSION,
|
data_version: WORLD_DATA_VERSION,
|
||||||
position: [self.x, self.z],
|
position: [self.x, self.z],
|
||||||
entities: self.data.lock().await.values().cloned().collect(),
|
entities: self.data.lock().await.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|||||||
@ -89,7 +89,7 @@ pub struct ChunkEntityData {
|
|||||||
pub x: i32,
|
pub x: i32,
|
||||||
/// Chunk Z
|
/// Chunk Z
|
||||||
pub z: i32,
|
pub z: i32,
|
||||||
pub data: Mutex<FxHashMap<uuid::Uuid, NbtCompound>>,
|
pub data: Mutex<Vec<NbtCompound>>,
|
||||||
|
|
||||||
pub dirty: AtomicBool,
|
pub dirty: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ pub struct Node {
|
|||||||
pub stage: StagedChunkEnum,
|
pub stage: StagedChunkEnum,
|
||||||
pub in_degree: u32,
|
pub in_degree: u32,
|
||||||
pub in_queue: bool,
|
pub in_queue: bool,
|
||||||
|
pub in_flight: bool,
|
||||||
pub edge: EdgeKey,
|
pub edge: EdgeKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ impl Node {
|
|||||||
stage,
|
stage,
|
||||||
in_degree: 0,
|
in_degree: 0,
|
||||||
in_queue: false,
|
in_queue: false,
|
||||||
|
in_flight: false,
|
||||||
edge: EdgeKey::null(),
|
edge: EdgeKey::null(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,6 +77,7 @@ pub struct GenerationSchedule {
|
|||||||
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
||||||
listener: Arc<ChunkListener>,
|
listener: Arc<ChunkListener>,
|
||||||
lighting_config: LightingEngineConfig,
|
lighting_config: LightingEngineConfig,
|
||||||
|
last_unload: std::time::Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerationSchedule {
|
impl GenerationSchedule {
|
||||||
@ -169,6 +170,7 @@ impl GenerationSchedule {
|
|||||||
listener,
|
listener,
|
||||||
chunk_map: Default::default(),
|
chunk_map: Default::default(),
|
||||||
lighting_config,
|
lighting_config,
|
||||||
|
last_unload: std::time::Instant::now(),
|
||||||
};
|
};
|
||||||
scheduler.work(level_sched);
|
scheduler.work(level_sched);
|
||||||
})
|
})
|
||||||
@ -443,9 +445,12 @@ impl GenerationSchedule {
|
|||||||
{
|
{
|
||||||
let task = &mut holder.tasks[i];
|
let task = &mut holder.tasks[i];
|
||||||
if !task.is_null() {
|
if !task.is_null() {
|
||||||
self.waiting_for_chunks.remove(task);
|
let is_in_flight = self.graph.nodes.get(*task).is_some_and(|n| n.in_flight);
|
||||||
self.drop_node(*task);
|
if !is_in_flight {
|
||||||
*task = NodeKey::null();
|
self.waiting_for_chunks.remove(task);
|
||||||
|
self.drop_node(*task);
|
||||||
|
*task = NodeKey::null();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if new_stage == StagedChunkEnum::None
|
if new_stage == StagedChunkEnum::None
|
||||||
@ -601,20 +606,13 @@ impl GenerationSchedule {
|
|||||||
self.public_chunk_map.remove(&pos);
|
self.public_chunk_map.remove(&pos);
|
||||||
holder.public = false;
|
holder.public = false;
|
||||||
}
|
}
|
||||||
let sc = Arc::strong_count(&chunk);
|
|
||||||
if sc == 1 {
|
// Forcefully drop chunks when unloaded to prevent memory leaks
|
||||||
if chunk.is_dirty() {
|
// from dangling strong references
|
||||||
chunks.push((pos, Chunk::Level(chunk)));
|
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));
|
|
||||||
}
|
}
|
||||||
|
self.chunk_map.remove(&pos);
|
||||||
}
|
}
|
||||||
Chunk::Proto(chunk) => {
|
Chunk::Proto(chunk) => {
|
||||||
debug_assert!(!holder.public);
|
debug_assert!(!holder.public);
|
||||||
@ -646,8 +644,8 @@ impl GenerationSchedule {
|
|||||||
let mut chunks = Vec::with_capacity(self.chunk_map.len());
|
let mut chunks = Vec::with_capacity(self.chunk_map.len());
|
||||||
|
|
||||||
for (pos, holder) in &mut self.chunk_map {
|
for (pos, holder) in &mut self.chunk_map {
|
||||||
if let Some(chunk) = holder.chunk.take() {
|
if let Some(chunk) = &holder.chunk {
|
||||||
let should_save = match &chunk {
|
let should_save = match chunk {
|
||||||
Chunk::Level(sync_chunk) => sync_chunk.is_dirty(),
|
Chunk::Level(sync_chunk) => sync_chunk.is_dirty(),
|
||||||
Chunk::Proto(proto) => {
|
Chunk::Proto(proto) => {
|
||||||
save_proto_chunk
|
save_proto_chunk
|
||||||
@ -660,9 +658,11 @@ impl GenerationSchedule {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if should_save {
|
if should_save {
|
||||||
chunks.push((*pos, chunk));
|
let chunk_to_save = match chunk {
|
||||||
} else {
|
Chunk::Level(sync_chunk) => Chunk::Level(sync_chunk.clone()),
|
||||||
holder.chunk = Some(chunk);
|
Chunk::Proto(_) => holder.chunk.take().unwrap(),
|
||||||
|
};
|
||||||
|
chunks.push((*pos, chunk_to_save));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -803,13 +803,14 @@ impl GenerationSchedule {
|
|||||||
|
|
||||||
if was_public {
|
if was_public {
|
||||||
self.apply_lighting_override(&chunk);
|
self.apply_lighting_override(&chunk);
|
||||||
holder.chunk = Some(Chunk::Level(chunk.clone()));
|
let public_chunk = chunk.clone();
|
||||||
self.public_chunk_map.insert(new_pos, chunk.clone());
|
self.public_chunk_map.insert(new_pos, public_chunk);
|
||||||
info!(
|
info!(
|
||||||
"Notifying players: regenerated chunk at {:?} (was already public)",
|
"Notifying players: regenerated chunk at {:?} (was already public)",
|
||||||
new_pos
|
new_pos
|
||||||
);
|
);
|
||||||
self.listener.process_new_chunk(new_pos, &chunk);
|
self.listener.process_new_chunk(new_pos, &chunk);
|
||||||
|
holder.chunk = Some(Chunk::Level(chunk));
|
||||||
} else {
|
} else {
|
||||||
self.apply_lighting_override(&chunk);
|
self.apply_lighting_override(&chunk);
|
||||||
let public_chunk = chunk.clone();
|
let public_chunk = chunk.clone();
|
||||||
@ -1009,9 +1010,13 @@ impl GenerationSchedule {
|
|||||||
// 1. Get latest world state (player moves, etc)
|
// 1. Get latest world state (player moves, etc)
|
||||||
self.resort_work(self.send_level.get());
|
self.resort_work(self.send_level.get());
|
||||||
|
|
||||||
// Process unload queue continuously if there are chunks to unload
|
// Process unload queue periodically (every 1 second) to batch writes together
|
||||||
if !self.unload_chunks.is_empty() {
|
// 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.process_unload_queue();
|
||||||
|
self.last_unload = std::time::Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Process all pending chunk results from workers
|
// 2. Process all pending chunk results from workers
|
||||||
@ -1056,6 +1061,7 @@ impl GenerationSchedule {
|
|||||||
node.in_queue = false;
|
node.in_queue = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
node.in_flight = true;
|
||||||
let node = node.clone();
|
let node = node.clone();
|
||||||
if node.stage == StagedChunkEnum::Empty {
|
if node.stage == StagedChunkEnum::Empty {
|
||||||
self.running_task_count += 1;
|
self.running_task_count += 1;
|
||||||
@ -1114,6 +1120,7 @@ impl GenerationSchedule {
|
|||||||
if !all_ready {
|
if !all_ready {
|
||||||
if let Some(n) = self.graph.nodes.get_mut(task.1) {
|
if let Some(n) = self.graph.nodes.get_mut(task.1) {
|
||||||
n.in_queue = false;
|
n.in_queue = false;
|
||||||
|
n.in_flight = false;
|
||||||
}
|
}
|
||||||
self.waiting_for_chunks.insert(task.1);
|
self.waiting_for_chunks.insert(task.1);
|
||||||
// Close the TOCTOU window: the chunk we're waiting for may
|
// 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 (t_send, mut t_recv) = tokio::sync::mpsc::channel(1000);
|
||||||
|
|
||||||
|
let batch_len = batch.len();
|
||||||
let level_clone = level.clone();
|
let level_clone = level.clone();
|
||||||
let batch_clone = batch.clone();
|
|
||||||
|
|
||||||
let fetch_task = tokio::spawn(async move {
|
let fetch_task = tokio::spawn(async move {
|
||||||
level_clone
|
level_clone
|
||||||
.chunk_saver
|
.chunk_saver
|
||||||
.fetch_chunks(&level_clone.level_folder, &batch_clone, t_send)
|
.fetch_chunks(&level_clone.level_folder, &batch, t_send)
|
||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
|
|
||||||
for _ in 0..batch.len() {
|
for _ in 0..batch_len {
|
||||||
let data = match t_recv.recv().await {
|
let data = match t_recv.recv().await {
|
||||||
Some(res) => res,
|
Some(res) => res,
|
||||||
None => break,
|
None => break,
|
||||||
|
|||||||
@ -24,7 +24,7 @@ use pumpkin_data::dimension::Dimension;
|
|||||||
use pumpkin_data::{Block, block_properties::has_random_ticks, fluid::Fluid};
|
use pumpkin_data::{Block, block_properties::has_random_ticks, fluid::Fluid};
|
||||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||||
use pumpkin_util::world_seed::Seed;
|
use pumpkin_util::world_seed::Seed;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::FxHashSet;
|
||||||
use std::sync::{Arc, Mutex, Weak};
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{
|
use std::{
|
||||||
@ -119,8 +119,10 @@ pub struct RandomTickSample {
|
|||||||
|
|
||||||
pub struct LevelFolder {
|
pub struct LevelFolder {
|
||||||
pub root_folder: PathBuf,
|
pub root_folder: PathBuf,
|
||||||
|
pub dim_folder: PathBuf,
|
||||||
pub region_folder: PathBuf,
|
pub region_folder: PathBuf,
|
||||||
pub entities_folder: PathBuf,
|
pub entities_folder: PathBuf,
|
||||||
|
pub poi_folder: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Level {
|
impl Level {
|
||||||
@ -132,16 +134,26 @@ impl Level {
|
|||||||
dimension: Dimension,
|
dimension: Dimension,
|
||||||
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
gen_pool: Option<Arc<rayon::ThreadPool>>,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
let region_folder = root_folder.join("region");
|
let (namespace, name) = match dimension.minecraft_name.split_once(':') {
|
||||||
let entities_folder = root_folder.join("entities");
|
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(®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(&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 {
|
let level_folder = Arc::new(LevelFolder {
|
||||||
root_folder,
|
root_folder,
|
||||||
|
dim_folder,
|
||||||
region_folder,
|
region_folder,
|
||||||
entities_folder,
|
entities_folder,
|
||||||
|
poi_folder,
|
||||||
});
|
});
|
||||||
|
|
||||||
let seed = Seed(seed as u64);
|
let seed = Seed(seed as u64);
|
||||||
@ -225,7 +237,7 @@ impl Level {
|
|||||||
let arc_chunk = Arc::new(ChunkEntityData {
|
let arc_chunk = Arc::new(ChunkEntityData {
|
||||||
x: pos.x,
|
x: pos.x,
|
||||||
z: pos.y,
|
z: pos.y,
|
||||||
data: tokio::sync::Mutex::new(FxHashMap::default()),
|
data: tokio::sync::Mutex::new(Vec::new()),
|
||||||
dirty: AtomicBool::new(false),
|
dirty: AtomicBool::new(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -246,7 +258,7 @@ impl Level {
|
|||||||
let arc_chunk = Arc::new(ChunkEntityData {
|
let arc_chunk = Arc::new(ChunkEntityData {
|
||||||
x: pos.x,
|
x: pos.x,
|
||||||
z: pos.y,
|
z: pos.y,
|
||||||
data: tokio::sync::Mutex::new(FxHashMap::default()),
|
data: tokio::sync::Mutex::new(Vec::new()),
|
||||||
dirty: AtomicBool::new(false),
|
dirty: AtomicBool::new(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -322,8 +334,9 @@ impl Level {
|
|||||||
self.tasks.wait().await;
|
self.tasks.wait().await;
|
||||||
self.chunk_system_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;
|
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;
|
self.entity_saver.block_and_await_ongoing_tasks().await;
|
||||||
|
|
||||||
// save all chunks currently in memory
|
// save all chunks currently in memory
|
||||||
@ -521,9 +534,7 @@ impl Level {
|
|||||||
.map(|entry| *entry.key())
|
.map(|entry| *entry.key())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !entity_chunks_to_remove.is_empty() {
|
// We do not clean them here because we want the caller to save any active entities in them first.
|
||||||
self.clean_entity_chunks(&entity_chunks_to_remove);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the difference is too big, we can shrink the loaded chunks
|
// if the difference is too big, we can shrink the loaded chunks
|
||||||
// (1024 chunks is the equivalent to a 32x32 chunks area)
|
// (1024 chunks is the equivalent to a 32x32 chunks area)
|
||||||
|
|||||||
@ -376,9 +376,9 @@ pub struct PoiStorage {
|
|||||||
|
|
||||||
impl PoiStorage {
|
impl PoiStorage {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(world_folder: &Path) -> Self {
|
pub fn new(poi_folder: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
folder: world_folder.join("poi"),
|
folder: poi_folder,
|
||||||
regions: HashMap::new(),
|
regions: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,7 +532,7 @@ mod tests {
|
|||||||
let dir = std::env::temp_dir().join("pumpkin_poi_mca_test");
|
let dir = std::env::temp_dir().join("pumpkin_poi_mca_test");
|
||||||
let _ = std::fs::remove_dir_all(&dir);
|
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(100, 64, 100)));
|
||||||
storage.add_portal(BlockPos(Vector3::new(110, 64, 100)));
|
storage.add_portal(BlockPos(Vector3::new(110, 64, 100)));
|
||||||
@ -552,7 +552,7 @@ mod tests {
|
|||||||
assert!(mca_path.exists());
|
assert!(mca_path.exists());
|
||||||
|
|
||||||
// Reload and verify
|
// Reload and verify
|
||||||
let mut storage2 = PoiStorage::new(&dir);
|
let mut storage2 = PoiStorage::new(dir.join("poi"));
|
||||||
let results2 = storage2.get_in_square(
|
let results2 = storage2.get_in_square(
|
||||||
BlockPos(Vector3::new(105, 64, 100)),
|
BlockPos(Vector3::new(105, 64, 100)),
|
||||||
16,
|
16,
|
||||||
|
|||||||
@ -3300,6 +3300,13 @@ impl NBTStorage for Entity {
|
|||||||
if self.has_visual_fire.load(Relaxed) {
|
if self.has_visual_fire.load(Relaxed) {
|
||||||
nbt.put_bool("HasVisualFire", true);
|
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...
|
// todo more...
|
||||||
})
|
})
|
||||||
@ -3340,6 +3347,15 @@ impl NBTStorage for Entity {
|
|||||||
.store(nbt.get_int("PortalCooldown").unwrap_or(0) as u32, Relaxed);
|
.store(nbt.get_int("PortalCooldown").unwrap_or(0) as u32, Relaxed);
|
||||||
self.has_visual_fire
|
self.has_visual_fire
|
||||||
.store(nbt.get_bool("HasVisualFire").unwrap_or(false), Relaxed);
|
.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...
|
// todo more...
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -886,11 +886,15 @@ impl Player {
|
|||||||
let chunks_to_clean = level.mark_chunks_as_not_watched(&radial_chunks).await;
|
let chunks_to_clean = level.mark_chunks_as_not_watched(&radial_chunks).await;
|
||||||
// Remove chunks with no watchers from the cache
|
// Remove chunks with no watchers from the cache
|
||||||
if !chunks_to_clean.is_empty() {
|
if !chunks_to_clean.is_empty() {
|
||||||
|
world.remove_entities_in_chunks(&chunks_to_clean).await;
|
||||||
level.clean_entity_chunks(&chunks_to_clean);
|
level.clean_entity_chunks(&chunks_to_clean);
|
||||||
world.remove_entities_in_chunks(&chunks_to_clean);
|
|
||||||
}
|
}
|
||||||
// Remove left over entries from all possiblily loaded chunks
|
// 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!(
|
debug!(
|
||||||
"Removed player id {} from world {} ({} chunks remain cached)",
|
"Removed player id {} from world {} ({} chunks remain cached)",
|
||||||
@ -3093,6 +3097,7 @@ impl Player {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
screen_handler.set_received_stack(slot_index, updated_stack);
|
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,
|
packet: &'a CSetContainerContent,
|
||||||
) -> PlayerFuture<'a, ()> {
|
) -> PlayerFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
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, ()> {
|
fn enqueue_slot_packet<'a>(&'a self, packet: &'a CSetContainerSlot) -> PlayerFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
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, ()> {
|
fn enqueue_cursor_packet<'a>(&'a self, packet: &'a CSetCursorItem) -> PlayerFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
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,
|
packet: &'a CSetPlayerInventory,
|
||||||
) -> PlayerFuture<'a, ()> {
|
) -> PlayerFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
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>,
|
player: &Arc<Player>,
|
||||||
packet: SInventoryTransaction,
|
packet: SInventoryTransaction,
|
||||||
) {
|
) {
|
||||||
|
tracing::info!("handle_inventory_action: packet={:?}", packet);
|
||||||
let mut inventory_updated = false;
|
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 {
|
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) =
|
if let Some(screen_slot) =
|
||||||
map_bedrock_slot_to_screen_handler(window_id, action.inventory_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;
|
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) {
|
pub async fn handle_interaction(&self, player: &Arc<Player>, packet: SInteraction) {
|
||||||
@ -868,6 +973,9 @@ impl BedrockClient {
|
|||||||
player.mining.store(false, Ordering::Relaxed);
|
player.mining.store(false, Ordering::Relaxed);
|
||||||
world.set_block_breaking(entity, location, -1).await;
|
world.set_block_breaking(entity, location, -1).await;
|
||||||
}
|
}
|
||||||
|
PlayerAction::DropItem => {
|
||||||
|
player.drop_held_item(false).await;
|
||||||
|
}
|
||||||
// TODO
|
// TODO
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -953,6 +1061,7 @@ impl BedrockClient {
|
|||||||
let mut result = 0u8; // 0 = Success, 1 = Error
|
let mut result = 0u8; // 0 = Success, 1 = Error
|
||||||
|
|
||||||
for action in request.actions {
|
for action in request.actions {
|
||||||
|
tracing::info!("Processing ItemStackRequestAction: {:?}", action);
|
||||||
match action {
|
match action {
|
||||||
ItemStackRequestAction::CraftCreative {
|
ItemStackRequestAction::CraftCreative {
|
||||||
creative_item_id,
|
creative_item_id,
|
||||||
@ -1041,6 +1150,29 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source_stack.decrement(count);
|
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() {
|
let source_stack = if source_stack.is_empty() {
|
||||||
ItemStack::EMPTY.clone()
|
ItemStack::EMPTY.clone()
|
||||||
} else {
|
} else {
|
||||||
@ -1554,11 +1686,23 @@ fn map_bedrock_container_slot(
|
|||||||
ContainerName::Cursor => None,
|
ContainerName::Cursor => None,
|
||||||
ContainerName::CraftingInput => {
|
ContainerName::CraftingInput => {
|
||||||
if is_player_screen {
|
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()
|
} else if screen_handler.window_type()
|
||||||
== Some(pumpkin_data::screen::WindowType::Crafting)
|
== 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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -1800,6 +1944,11 @@ async fn get_slot_stack(
|
|||||||
}
|
}
|
||||||
if slot_info.container_name.container_name == ContainerName::Cursor {
|
if slot_info.container_name.container_name == ContainerName::Cursor {
|
||||||
let cursor_lock = screen_handler.get_behaviour().cursor_stack.lock().await;
|
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();
|
return cursor_lock.clone();
|
||||||
}
|
}
|
||||||
if let Some(screen_slot) = map_bedrock_container_slot(
|
if let Some(screen_slot) = map_bedrock_container_slot(
|
||||||
|
|||||||
@ -31,7 +31,6 @@ use crate::{
|
|||||||
server::{RecipeManager, Server},
|
server::{RecipeManager, Server},
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use pumpkin_world::level::SyncChunk;
|
|
||||||
|
|
||||||
pub struct WasmResource<T> {
|
pub struct WasmResource<T> {
|
||||||
pub provider: T,
|
pub provider: T,
|
||||||
@ -44,7 +43,7 @@ pub type JavaPlayerResource = WasmResource<Arc<Player>>;
|
|||||||
pub type BedrockPlayerResource = WasmResource<Arc<Player>>;
|
pub type BedrockPlayerResource = WasmResource<Arc<Player>>;
|
||||||
pub type EntityResource = WasmResource<Arc<dyn EntityBase>>;
|
pub type EntityResource = WasmResource<Arc<dyn EntityBase>>;
|
||||||
pub type WorldResource = WasmResource<Arc<World>>;
|
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 WorldBorderResource = WasmResource<Arc<World>>;
|
||||||
pub type ScoreboardResource = WasmResource<Arc<World>>;
|
pub type ScoreboardResource = WasmResource<Arc<World>>;
|
||||||
pub type GuiResource = WasmResource<Arc<Mutex<PluginGui>>>;
|
pub type GuiResource = WasmResource<Arc<Mutex<PluginGui>>>;
|
||||||
@ -157,7 +156,7 @@ impl PluginHostState {
|
|||||||
pub fn add_chunk<T>(
|
pub fn add_chunk<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
world: Arc<World>,
|
world: Arc<World>,
|
||||||
chunk: SyncChunk,
|
chunk: Weak<pumpkin_world::chunk::ChunkData>,
|
||||||
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
|
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
|
||||||
let resource = self.resource_table.push(ChunkResource {
|
let resource = self.resource_table.push(ChunkResource {
|
||||||
provider: (world, chunk),
|
provider: (world, chunk),
|
||||||
|
|||||||
@ -229,7 +229,7 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
|||||||
.get(&pos)
|
.get(&pos)
|
||||||
.map(|c| c.value().clone());
|
.map(|c| c.value().clone());
|
||||||
if let Some(chunk) = chunk {
|
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))
|
Ok(Some(res))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
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> {
|
async fn get_x(&mut self, chunk: Resource<WitChunk>) -> wasmtime::Result<i32> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (_, chunk_data) = &chunk_res.provider;
|
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)
|
Ok(chunk_data.x)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_z(&mut self, chunk: Resource<WitChunk>) -> wasmtime::Result<i32> {
|
async fn get_z(&mut self, chunk: Resource<WitChunk>) -> wasmtime::Result<i32> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (_, chunk_data) = &chunk_res.provider;
|
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)
|
Ok(chunk_data.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -712,6 +718,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
|||||||
) -> wasmtime::Result<u16> {
|
) -> wasmtime::Result<u16> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (_, chunk_data) = &chunk_res.provider;
|
let (_, chunk_data) = &chunk_res.provider;
|
||||||
|
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||||
|
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||||
|
};
|
||||||
Ok(chunk_data
|
Ok(chunk_data
|
||||||
.section
|
.section
|
||||||
.get_block_absolute_y(pos.x as usize, pos.y, pos.z as usize)
|
.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> {
|
) -> wasmtime::Result<WitBlockState> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (_, chunk_data) = &chunk_res.provider;
|
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
|
let id = chunk_data
|
||||||
.section
|
.section
|
||||||
.get_block_absolute_y(pos.x as usize, pos.y, pos.z as usize)
|
.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<()> {
|
) -> wasmtime::Result<()> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (world, chunk_data) = &chunk_res.provider;
|
let (world, chunk_data) = &chunk_res.provider;
|
||||||
|
let Some(chunk_data) = chunk_data.upgrade() else {
|
||||||
|
return Err(wasmtime::Error::msg("Chunk unloaded"));
|
||||||
|
};
|
||||||
|
|
||||||
let replaced =
|
let replaced =
|
||||||
chunk_data.set_block_absolute_y(pos.x as usize, pos.y, pos.z as usize, state);
|
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> {
|
) -> wasmtime::Result<pumpkin::plugin::biomes::Biome> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (_, chunk_data) = &chunk_res.provider;
|
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
|
let id = chunk_data
|
||||||
.section
|
.section
|
||||||
.get_rough_biome_absolute_y(pos.x as usize, pos.y, pos.z as usize)
|
.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>> {
|
) -> wasmtime::Result<Option<BlockEntityType>> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (world, chunk_data) = &chunk_res.provider;
|
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 =
|
let absolute_pos =
|
||||||
BlockPos::new(chunk_data.x * 16 + pos.x, pos.y, chunk_data.z * 16 + pos.z);
|
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);
|
let block_entity = world.get_block_entity(&absolute_pos);
|
||||||
@ -836,6 +857,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
|||||||
) -> wasmtime::Result<i32> {
|
) -> wasmtime::Result<i32> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (_, chunk_data) = &chunk_res.provider;
|
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(
|
Ok(chunk_data.heightmap.lock().unwrap().get(
|
||||||
ChunkHeightmapType::WorldSurface,
|
ChunkHeightmapType::WorldSurface,
|
||||||
x,
|
x,
|
||||||
@ -851,6 +875,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
|||||||
) -> wasmtime::Result<u8> {
|
) -> wasmtime::Result<u8> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (_, chunk_data) = &chunk_res.provider;
|
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;
|
let section_index = (pos.y - chunk_data.section.min_y) as usize / 16;
|
||||||
Ok(chunk_data
|
Ok(chunk_data
|
||||||
.light_engine
|
.light_engine
|
||||||
@ -870,6 +897,9 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
|||||||
) -> wasmtime::Result<u8> {
|
) -> wasmtime::Result<u8> {
|
||||||
let chunk_res = self.get_chunk_res(&chunk)?;
|
let chunk_res = self.get_chunk_res(&chunk)?;
|
||||||
let (_, chunk_data) = &chunk_res.provider;
|
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;
|
let section_index = (pos.y - chunk_data.section.min_y) as usize / 16;
|
||||||
Ok(chunk_data
|
Ok(chunk_data
|
||||||
.light_engine
|
.light_engine
|
||||||
|
|||||||
@ -196,12 +196,13 @@ impl Server {
|
|||||||
let defaultgamemode = Mutex::new(DefaultGamemode {
|
let defaultgamemode = Mutex::new(DefaultGamemode {
|
||||||
gamemode: basic_config.default_gamemode,
|
gamemode: basic_config.default_gamemode,
|
||||||
});
|
});
|
||||||
|
let players_dir = world_path.join("players");
|
||||||
let player_data_storage = ServerPlayerData::new(
|
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),
|
Duration::from_secs(advanced_config.player_data.save_player_cron_interval),
|
||||||
advanced_config.player_data.save_player_data,
|
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 white_list = AtomicBool::new(basic_config.white_list);
|
||||||
|
|
||||||
let tick_rate_manager = Arc::new(ServerTickRateManager::new(basic_config.tps));
|
let tick_rate_manager = Arc::new(ServerTickRateManager::new(basic_config.tps));
|
||||||
|
|||||||
@ -114,8 +114,8 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
if !chunks_to_clean.is_empty() {
|
if !chunks_to_clean.is_empty() {
|
||||||
|
world.remove_entities_in_chunks(&chunks_to_clean).await;
|
||||||
world.level.clean_entity_chunks(&chunks_to_clean);
|
world.level.clean_entity_chunks(&chunks_to_clean);
|
||||||
world.remove_entities_in_chunks(&chunks_to_clean);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !loading_chunks.is_empty() {
|
if !loading_chunks.is_empty() {
|
||||||
|
|||||||
@ -285,7 +285,7 @@ impl World {
|
|||||||
let generation_settings = GenerationSettings::from_dimension(&dimension);
|
let generation_settings = GenerationSettings::from_dimension(&dimension);
|
||||||
|
|
||||||
// Load portal POI from disk (PoiStorage::new automatically loads from disk if files exist)
|
// 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)
|
let dragon_fight = (dimension.minecraft_name == Dimension::THE_END.minecraft_name)
|
||||||
.then(|| Mutex::new(dragon_fight::DragonFight::new()));
|
.then(|| Mutex::new(dragon_fight::DragonFight::new()));
|
||||||
Self {
|
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
|
// 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
|
// Otherwise we just update the nbt data
|
||||||
let base_entity = entity.get_entity();
|
let base_entity = entity.get_entity();
|
||||||
let uuid = base_entity.entity_uuid;
|
|
||||||
let current_chunk_coordinate = base_entity.block_pos.load().chunk_position();
|
let current_chunk_coordinate = base_entity.block_pos.load().chunk_position();
|
||||||
let mut nbt = NbtCompound::new();
|
let mut nbt = NbtCompound::new();
|
||||||
entity.write_nbt(&mut nbt).await;
|
entity.write_nbt(&mut nbt).await;
|
||||||
@ -388,37 +387,21 @@ impl World {
|
|||||||
chunk.mark_dirty(true);
|
chunk.mark_dirty(true);
|
||||||
let mut data = chunk.data.lock().await;
|
let mut data = chunk.data.lock().await;
|
||||||
if old_chunk == current_chunk_coordinate {
|
if old_chunk == current_chunk_coordinate {
|
||||||
data.insert(uuid, nbt);
|
data.push(nbt);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The chunk has changed, lets remove the entity from the old chunk
|
// 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
|
// We did not continue, so lets save data in a new chunk
|
||||||
let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await;
|
let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await;
|
||||||
let mut data = chunk.data.lock().await;
|
let mut data = chunk.data.lock().await;
|
||||||
data.insert(uuid, nbt);
|
data.push(nbt);
|
||||||
chunk.mark_dirty(true);
|
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.
|
/// Sends an entity status update to all players tracking the specified entity.
|
||||||
pub fn send_entity_status(&self, entity: &Entity, status: EntityStatus) {
|
pub fn send_entity_status(&self, entity: &Entity, status: EntityStatus) {
|
||||||
let chunk_pos = entity.chunk_pos.load();
|
let chunk_pos = entity.chunk_pos.load();
|
||||||
@ -1134,7 +1117,8 @@ impl World {
|
|||||||
self.level.should_unload.store(true, Relaxed);
|
self.level.should_unload.store(true, Relaxed);
|
||||||
let cleaned_chunks = self.level.clean_memory();
|
let cleaned_chunks = self.level.clean_memory();
|
||||||
if !cleaned_chunks.is_empty() {
|
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 autosave is configured and this tick will trigger an autosave, don't double notify
|
||||||
if self.level.autosave_ticks == 0 {
|
if self.level.autosave_ticks == 0 {
|
||||||
@ -3526,6 +3510,7 @@ impl World {
|
|||||||
let mut entity_receiver = self.level.receive_entity_chunks(chunks);
|
let mut entity_receiver = self.level.receive_entity_chunks(chunks);
|
||||||
let level = self.level.clone();
|
let level = self.level.clone();
|
||||||
let world = self.clone();
|
let world = self.clone();
|
||||||
|
|
||||||
player.clone().spawn_task(async move {
|
player.clone().spawn_task(async move {
|
||||||
'main: loop {
|
'main: loop {
|
||||||
let recv_result = tokio::select! {
|
let recv_result = tokio::select! {
|
||||||
@ -3553,62 +3538,46 @@ impl World {
|
|||||||
"Received chunk {:?}, but it is no longer watched... cleaning",
|
"Received chunk {:?}, but it is no longer watched... cleaning",
|
||||||
&position
|
&position
|
||||||
);
|
);
|
||||||
let mut ids_to_remove = Vec::new();
|
|
||||||
|
|
||||||
for (uuid, entity_nbt) in chunk.data.lock().await.iter() {
|
if first_load {
|
||||||
let Some(id) = entity_nbt.get_string("id") else {
|
for entity_nbt in chunk.data.lock().await.iter() {
|
||||||
warn!("Entity has no ID");
|
let Some(id) = entity_nbt.get_string("id") else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(entity_type) =
|
let Some(entity_type) =
|
||||||
EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(id))
|
EntityType::from_name(id.strip_prefix("minecraft:").unwrap_or(id))
|
||||||
else {
|
else {
|
||||||
warn!("Entity has no valid Entity Type {id}");
|
continue;
|
||||||
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();
|
|
||||||
|
|
||||||
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();
|
let mut nbt = NbtCompound::new();
|
||||||
entity.write_nbt(&mut nbt).await;
|
entity.write_nbt(&mut nbt).await;
|
||||||
|
|
||||||
if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load()
|
if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load()
|
||||||
{
|
{
|
||||||
let old_chunk = old_chunk.to_vec2_i32();
|
let old_chunk = old_chunk.to_vec2_i32();
|
||||||
let chunk = world.level.get_entity_chunk(old_chunk).await;
|
let chunk = world.level.get_entity_chunk(old_chunk).await;
|
||||||
chunk.mark_dirty(true);
|
chunk.mark_dirty(true);
|
||||||
let base_entity = entity.get_entity();
|
|
||||||
let current_chunk_coordinate =
|
let current_chunk_coordinate =
|
||||||
base_entity.block_pos.load().chunk_position();
|
base_entity.block_pos.load().chunk_position();
|
||||||
|
|
||||||
let mut data = chunk.data.lock().await;
|
let mut data = chunk.data.lock().await;
|
||||||
if old_chunk == current_chunk_coordinate {
|
if old_chunk == current_chunk_coordinate {
|
||||||
data.insert(*uuid, nbt);
|
data.push(nbt);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The chunk has changed, lets remove the entity from the old chunk
|
// The chunk has changed, lets remove the entity from the old chunk
|
||||||
data.remove(uuid);
|
data.clear();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3617,7 +3586,7 @@ impl World {
|
|||||||
|
|
||||||
// Add all new Entities to the world
|
// Add all new Entities to the world
|
||||||
let mut entities_to_add: Vec<Arc<dyn EntityBase>> = Vec::new();
|
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 {
|
let Some(id) = entity_nbt.get_string("id") else {
|
||||||
debug!("Entity has no ID");
|
debug!("Entity has no ID");
|
||||||
continue;
|
continue;
|
||||||
@ -3629,29 +3598,17 @@ impl World {
|
|||||||
continue;
|
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
|
// 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.read_nbt_non_mut(entity_nbt).await;
|
||||||
|
|
||||||
|
entity.init_data_tracker().await;
|
||||||
|
|
||||||
let base_entity = entity.get_entity();
|
let base_entity = entity.get_entity();
|
||||||
|
|
||||||
// Clear velocity so the client does not replay the drop animation.
|
// Clear velocity so the client does not replay the drop animation.
|
||||||
@ -3663,12 +3620,12 @@ impl World {
|
|||||||
.client
|
.client
|
||||||
.enqueue_packet(&base_entity.create_spawn_packet())
|
.enqueue_packet(&base_entity.create_spawn_packet())
|
||||||
.await;
|
.await;
|
||||||
entity.init_data_tracker().await;
|
|
||||||
|
|
||||||
if first_load {
|
if first_load {
|
||||||
entities_to_add.push(entity);
|
entities_to_add.push(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if first_load && !entities_to_add.is_empty() {
|
if first_load && !entities_to_add.is_empty() {
|
||||||
world.entities.rcu(|current_entities| {
|
world.entities.rcu(|current_entities| {
|
||||||
let mut new_entities = (**current_entities).clone();
|
let mut new_entities = (**current_entities).clone();
|
||||||
@ -4152,7 +4109,7 @@ impl World {
|
|||||||
self.spawn_state.load().add_entity(self, entity.as_ref());
|
self.spawn_state.load().add_entity(self, entity.as_ref());
|
||||||
|
|
||||||
let chunk = self.level.get_entity_chunk(chunk_coordinate).await;
|
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);
|
chunk.mark_dirty(true);
|
||||||
|
|
||||||
self.entities.rcu(|current_entities| {
|
self.entities.rcu(|current_entities| {
|
||||||
@ -4162,6 +4119,7 @@ impl World {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_async)]
|
||||||
pub async fn remove_entity(&self, entity: &dyn EntityBase) {
|
pub async fn remove_entity(&self, entity: &dyn EntityBase) {
|
||||||
let base_entity = entity.get_entity();
|
let base_entity = entity.get_entity();
|
||||||
self.spawn_state.load().remove_entity(self, entity);
|
self.spawn_state.load().remove_entity(self, entity);
|
||||||
@ -4177,11 +4135,9 @@ impl World {
|
|||||||
&CRemoveEntities::new(&[base_entity.entity_id.into()]),
|
&CRemoveEntities::new(&[base_entity.entity_id.into()]),
|
||||||
&CRemoveActor::new(VarLong(base_entity.entity_id as i64)),
|
&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 chunks_set: FxHashSet<_> = chunks.iter().copied().collect();
|
||||||
let mut entities_to_remove = Vec::new();
|
let mut entities_to_remove = Vec::new();
|
||||||
|
|
||||||
@ -4201,9 +4157,8 @@ impl World {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for entity in entities_to_remove {
|
for entity in entities_to_remove {
|
||||||
|
self.save_entity(&entity).await;
|
||||||
self.spawn_state.load().remove_entity(self, entity.as_ref());
|
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
|
self.block_entities
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user