2022-07-15 16:18:20 +10:00
|
|
|
//! Chunks and related types.
|
|
|
|
|
2022-04-15 07:55:45 +10:00
|
|
|
// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil
|
|
|
|
|
2022-07-04 10:32:05 +10:00
|
|
|
use std::collections::hash_map::Entry;
|
2022-07-05 11:22:29 +10:00
|
|
|
use std::collections::HashMap;
|
2022-04-15 07:55:45 +10:00
|
|
|
use std::io::Write;
|
2022-04-29 17:48:41 +10:00
|
|
|
use std::iter::FusedIterator;
|
2022-04-15 07:55:45 +10:00
|
|
|
|
|
|
|
use bitvec::vec::BitVec;
|
|
|
|
use num::Integer;
|
2022-05-16 19:36:14 +10:00
|
|
|
use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-07-07 11:27:59 +10:00
|
|
|
use crate::biome::BiomeId;
|
2022-05-01 12:05:38 +10:00
|
|
|
use crate::block::BlockState;
|
2022-07-07 11:27:59 +10:00
|
|
|
use crate::block_pos::BlockPos;
|
|
|
|
pub use crate::chunk_pos::ChunkPos;
|
2022-07-16 13:40:39 +10:00
|
|
|
use crate::config::Config;
|
2022-07-07 11:27:59 +10:00
|
|
|
use crate::dimension::DimensionId;
|
2022-07-11 22:08:02 +10:00
|
|
|
use crate::protocol_inner::packets::play::s2c::{
|
2022-07-29 21:49:08 +10:00
|
|
|
BlockUpdate, ChunkDataHeightmaps, ChunkData, S2cPlayPacket, ChunkSectionUpdate,
|
2022-04-15 07:55:45 +10:00
|
|
|
};
|
2022-07-11 22:08:02 +10:00
|
|
|
use crate::protocol_inner::{Encode, Nbt, VarInt, VarLong};
|
2022-07-07 11:27:59 +10:00
|
|
|
use crate::server::SharedServer;
|
|
|
|
use crate::Ticks;
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// A container for all [`Chunks`]s in a [`World`](crate::world::World).
|
2022-07-16 13:40:39 +10:00
|
|
|
pub struct Chunks<C: Config> {
|
|
|
|
chunks: HashMap<ChunkPos, Chunk<C>>,
|
|
|
|
server: SharedServer<C>,
|
2022-06-23 01:06:54 +10:00
|
|
|
dimension: DimensionId,
|
2022-04-29 17:48:41 +10:00
|
|
|
}
|
|
|
|
|
2022-07-16 13:40:39 +10:00
|
|
|
impl<C: Config> Chunks<C> {
|
|
|
|
pub(crate) fn new(server: SharedServer<C>, dimension: DimensionId) -> Self {
|
2022-05-16 19:36:14 +10:00
|
|
|
Self {
|
|
|
|
chunks: HashMap::new(),
|
|
|
|
server,
|
2022-06-23 01:06:54 +10:00
|
|
|
dimension,
|
2022-05-16 19:36:14 +10:00
|
|
|
}
|
2022-04-29 17:48:41 +10:00
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Creates an empty chunk at the provided position and returns a mutable
|
|
|
|
/// refernce to it.
|
|
|
|
///
|
|
|
|
/// If a chunk at the position already exists, then the old chunk
|
|
|
|
/// is overwritten.
|
|
|
|
///
|
|
|
|
/// **Note**: For the vanilla Minecraft client to see a chunk, all chunks
|
|
|
|
/// adjacent to it must also be loaded. It is also important that clients
|
|
|
|
/// are not spawned within unloaded chunks via
|
|
|
|
/// [`spawn`](crate::client::Client::spawn).
|
2022-07-28 12:21:11 +10:00
|
|
|
pub fn create(&mut self, pos: impl Into<ChunkPos>, data: C::ChunkState) -> &mut Chunk<C> {
|
2022-06-30 04:09:00 +10:00
|
|
|
let section_count = (self.server.dimension(self.dimension).height / 16) as u32;
|
2022-07-16 13:40:39 +10:00
|
|
|
let chunk = Chunk::new(section_count, self.server.current_tick(), data);
|
2022-07-04 10:32:05 +10:00
|
|
|
|
|
|
|
match self.chunks.entry(pos.into()) {
|
|
|
|
Entry::Occupied(mut oe) => {
|
|
|
|
oe.insert(chunk);
|
|
|
|
oe.into_mut()
|
2022-07-05 11:22:29 +10:00
|
|
|
}
|
2022-07-04 10:32:05 +10:00
|
|
|
Entry::Vacant(ve) => ve.insert(chunk),
|
|
|
|
}
|
2022-06-30 04:09:00 +10:00
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Removes a chunk at the provided position.
|
|
|
|
///
|
|
|
|
/// If a chunk exists at the position, then it is deleted and `true` is
|
|
|
|
/// returned. Otherwise, `false` is returned.
|
|
|
|
pub fn delete(&mut self, pos: impl Into<ChunkPos>) -> bool {
|
|
|
|
self.chunks.remove(&pos.into()).is_some()
|
2022-06-30 04:09:00 +10:00
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Returns the number of loaded chunks.
|
2022-04-29 17:48:41 +10:00
|
|
|
pub fn count(&self) -> usize {
|
2022-05-16 19:36:14 +10:00
|
|
|
self.chunks.len()
|
2022-04-29 17:48:41 +10:00
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Gets a shared reference to the chunk at the provided position.
|
|
|
|
///
|
|
|
|
/// If there is no chunk at the position, then `None` is returned.
|
2022-07-16 13:40:39 +10:00
|
|
|
pub fn get(&self, pos: impl Into<ChunkPos>) -> Option<&Chunk<C>> {
|
2022-06-23 01:06:54 +10:00
|
|
|
self.chunks.get(&pos.into())
|
2022-04-29 17:48:41 +10:00
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Gets an exclusive reference to the chunk at the provided position.
|
|
|
|
///
|
|
|
|
/// If there is no chunk at the position, then `None` is returned.
|
2022-07-16 13:40:39 +10:00
|
|
|
pub fn get_mut(&mut self, pos: impl Into<ChunkPos>) -> Option<&mut Chunk<C>> {
|
2022-06-30 04:09:00 +10:00
|
|
|
self.chunks.get_mut(&pos.into())
|
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Deletes all chunks.
|
2022-05-16 19:36:14 +10:00
|
|
|
pub fn clear(&mut self) {
|
|
|
|
self.chunks.clear();
|
2022-04-29 17:48:41 +10:00
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Returns an immutable iterator over all chunks in the world in an
|
|
|
|
/// unspecified order.
|
2022-07-16 13:40:39 +10:00
|
|
|
pub fn iter(&self) -> impl FusedIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
|
2022-05-16 19:36:14 +10:00
|
|
|
self.chunks.iter().map(|(&pos, chunk)| (pos, chunk))
|
2022-04-29 17:48:41 +10:00
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Returns a mutable iterator over all chunks in the world in an
|
|
|
|
/// unspecified order.
|
2022-07-16 13:40:39 +10:00
|
|
|
pub fn iter_mut(&mut self) -> impl FusedIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
|
2022-06-30 04:09:00 +10:00
|
|
|
self.chunks.iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Returns a parallel immutable iterator over all chunks in the world in an
|
|
|
|
/// unspecified order.
|
2022-07-16 13:40:39 +10:00
|
|
|
pub fn par_iter(&self) -> impl ParallelIterator<Item = (ChunkPos, &Chunk<C>)> + Clone + '_ {
|
2022-05-16 19:36:14 +10:00
|
|
|
self.chunks.par_iter().map(|(&pos, chunk)| (pos, chunk))
|
2022-04-29 17:48:41 +10:00
|
|
|
}
|
2022-06-23 01:06:54 +10:00
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Returns a parallel mutable iterator over all chunks in the world in an
|
|
|
|
/// unspecified order.
|
2022-07-16 13:40:39 +10:00
|
|
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = (ChunkPos, &mut Chunk<C>)> + '_ {
|
2022-06-30 04:09:00 +10:00
|
|
|
self.chunks.par_iter_mut().map(|(&pos, chunk)| (pos, chunk))
|
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Gets the block state at a position.
|
|
|
|
///
|
|
|
|
/// If the position is not inside of a chunk, then `None` is returned.
|
|
|
|
///
|
|
|
|
/// Note: if you need to get a large number of blocks, it may be more
|
|
|
|
/// efficient to read from the chunks directly with
|
|
|
|
/// [`Chunk::get_block_state`].
|
2022-06-23 01:06:54 +10:00
|
|
|
pub fn get_block_state(&self, pos: impl Into<BlockPos>) -> Option<BlockState> {
|
|
|
|
let pos = pos.into();
|
2022-06-25 09:11:15 +10:00
|
|
|
let chunk_pos = ChunkPos::from(pos);
|
2022-06-23 01:06:54 +10:00
|
|
|
|
|
|
|
let chunk = self.get(chunk_pos)?;
|
|
|
|
|
|
|
|
let min_y = self.server.dimension(self.dimension).min_y;
|
|
|
|
|
|
|
|
let y = pos.y.checked_sub(min_y)?.try_into().ok()?;
|
|
|
|
|
|
|
|
if y < chunk.height() {
|
|
|
|
Some(chunk.get_block_state(
|
|
|
|
pos.x.rem_euclid(16) as usize,
|
|
|
|
y,
|
|
|
|
pos.z.rem_euclid(16) as usize,
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// Sets the block state at a position.
|
|
|
|
///
|
|
|
|
/// If the position is inside of a chunk, then `true` is returned.
|
|
|
|
/// Otherwise, `false` is returned.
|
|
|
|
///
|
|
|
|
/// Note: if you need to set a large number of blocks, it may be more
|
|
|
|
/// efficient write to the chunks directly with
|
|
|
|
/// [`Chunk::set_block_state`].
|
2022-06-23 01:06:54 +10:00
|
|
|
pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> bool {
|
|
|
|
let pos = pos.into();
|
2022-06-25 09:11:15 +10:00
|
|
|
let chunk_pos = ChunkPos::from(pos);
|
2022-06-23 01:06:54 +10:00
|
|
|
|
2022-06-30 04:09:00 +10:00
|
|
|
if let Some(chunk) = self.chunks.get_mut(&chunk_pos) {
|
|
|
|
let min_y = self.server.dimension(self.dimension).min_y;
|
2022-06-23 01:06:54 +10:00
|
|
|
|
|
|
|
if let Some(y) = pos.y.checked_sub(min_y).and_then(|y| y.try_into().ok()) {
|
|
|
|
if y < chunk.height() {
|
2022-06-30 04:09:00 +10:00
|
|
|
chunk.set_block_state(
|
2022-06-23 01:06:54 +10:00
|
|
|
pos.x.rem_euclid(16) as usize,
|
|
|
|
y,
|
|
|
|
pos.z.rem_euclid(16) as usize,
|
|
|
|
block,
|
|
|
|
);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
2022-04-29 17:48:41 +10:00
|
|
|
}
|
|
|
|
|
2022-07-11 22:08:02 +10:00
|
|
|
/// A chunk is a 16x16-block segment of a world with a height determined by the
|
|
|
|
/// [`Dimension`](crate::dimension::Dimension) of the world.
|
|
|
|
///
|
|
|
|
/// In addition to blocks, chunks also contain [biomes](crate::biome::Biome).
|
|
|
|
/// Every 4x4x4 segment of blocks in a chunk corresponds to a biome.
|
2022-07-16 13:40:39 +10:00
|
|
|
pub struct Chunk<C: Config> {
|
2022-07-28 12:21:11 +10:00
|
|
|
/// Custom state.
|
|
|
|
pub state: C::ChunkState,
|
2022-04-15 07:55:45 +10:00
|
|
|
sections: Box<[ChunkSection]>,
|
|
|
|
// TODO block_entities: HashMap<u32, BlockEntity>,
|
2022-05-16 19:36:14 +10:00
|
|
|
/// The MOTION_BLOCKING heightmap
|
2022-04-15 07:55:45 +10:00
|
|
|
heightmap: Vec<i64>,
|
2022-05-16 19:36:14 +10:00
|
|
|
created_tick: Ticks,
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-07-16 13:40:39 +10:00
|
|
|
impl<C: Config> Chunk<C> {
|
2022-07-28 12:21:11 +10:00
|
|
|
pub(crate) fn new(section_count: u32, current_tick: Ticks, data: C::ChunkState) -> Self {
|
2022-04-15 07:55:45 +10:00
|
|
|
let sect = ChunkSection {
|
2022-06-23 01:06:54 +10:00
|
|
|
blocks: [BlockState::AIR.to_raw(); 4096],
|
|
|
|
modified_count: 1, // Must be >0 so the chunk is initialized.
|
2022-05-01 12:05:38 +10:00
|
|
|
biomes: [BiomeId::default(); 64],
|
2022-04-15 07:55:45 +10:00
|
|
|
compact_data: Vec::new(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut chunk = Self {
|
2022-07-28 12:21:11 +10:00
|
|
|
state: data,
|
2022-05-16 19:36:14 +10:00
|
|
|
sections: vec![sect; section_count as usize].into(),
|
2022-04-15 07:55:45 +10:00
|
|
|
heightmap: Vec::new(),
|
2022-05-16 19:36:14 +10:00
|
|
|
created_tick: current_tick,
|
2022-04-15 07:55:45 +10:00
|
|
|
};
|
|
|
|
|
2022-06-30 04:09:00 +10:00
|
|
|
chunk.apply_modifications();
|
2022-04-15 07:55:45 +10:00
|
|
|
chunk
|
|
|
|
}
|
|
|
|
|
2022-05-16 19:36:14 +10:00
|
|
|
pub fn created_tick(&self) -> Ticks {
|
|
|
|
self.created_tick
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn height(&self) -> usize {
|
|
|
|
self.sections.len() * 16
|
|
|
|
}
|
|
|
|
|
2022-05-01 12:05:38 +10:00
|
|
|
pub fn get_block_state(&self, x: usize, y: usize, z: usize) -> BlockState {
|
2022-04-15 07:55:45 +10:00
|
|
|
if x < 16 && y < self.height() && z < 16 {
|
2022-06-23 01:06:54 +10:00
|
|
|
BlockState::from_raw_unchecked(
|
|
|
|
self.sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] & BLOCK_STATE_MASK,
|
|
|
|
)
|
2022-04-15 07:55:45 +10:00
|
|
|
} else {
|
2022-05-01 12:05:38 +10:00
|
|
|
BlockState::AIR
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-30 04:09:00 +10:00
|
|
|
pub fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: BlockState) {
|
|
|
|
assert!(
|
|
|
|
x < 16 && y < self.height() && z < 16,
|
|
|
|
"the chunk block coordinates must be within bounds"
|
|
|
|
);
|
|
|
|
|
|
|
|
let sect = &mut self.sections[y / 16];
|
|
|
|
let idx = x + z * 16 + y % 16 * 16 * 16;
|
|
|
|
|
|
|
|
if block.to_raw() != sect.blocks[idx] & BLOCK_STATE_MASK {
|
|
|
|
if sect.blocks[idx] & !BLOCK_STATE_MASK == 0 {
|
|
|
|
sect.modified_count += 1;
|
|
|
|
}
|
|
|
|
sect.blocks[idx] = block.to_raw() | !BLOCK_STATE_MASK;
|
|
|
|
|
|
|
|
// TODO: if the block type was modified and the old block type
|
|
|
|
// could be a block entity, then the block entity at this
|
|
|
|
// position must be cleared.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 07:55:45 +10:00
|
|
|
pub fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId {
|
|
|
|
if x < 4 && y < self.height() / 4 && z < 4 {
|
2022-05-01 12:05:38 +10:00
|
|
|
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4]
|
2022-04-15 07:55:45 +10:00
|
|
|
} else {
|
|
|
|
BiomeId::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-30 04:09:00 +10:00
|
|
|
pub fn set_biome(&mut self, x: usize, y: usize, z: usize, b: BiomeId) {
|
|
|
|
assert!(
|
|
|
|
x < 4 && y < self.height() / 4 && z < 4,
|
|
|
|
"the chunk biome coordinates must be within bounds"
|
|
|
|
);
|
|
|
|
|
|
|
|
self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = b;
|
|
|
|
}
|
|
|
|
|
2022-05-16 19:36:14 +10:00
|
|
|
/// Gets the chunk data packet for this chunk with the given position. This
|
|
|
|
/// does not include unapplied changes.
|
2022-07-29 21:49:08 +10:00
|
|
|
pub(crate) fn chunk_data_packet(&self, pos: ChunkPos) -> ChunkData {
|
2022-04-15 07:55:45 +10:00
|
|
|
let mut blocks_and_biomes = Vec::new();
|
|
|
|
|
2022-05-16 19:36:14 +10:00
|
|
|
for sect in self.sections.iter() {
|
|
|
|
blocks_and_biomes.extend_from_slice(§.compact_data);
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-07-29 21:49:08 +10:00
|
|
|
ChunkData {
|
2022-04-15 07:55:45 +10:00
|
|
|
chunk_x: pos.x,
|
|
|
|
chunk_z: pos.z,
|
2022-07-29 21:49:08 +10:00
|
|
|
heightmaps: Nbt(ChunkDataHeightmaps {
|
2022-05-16 19:36:14 +10:00
|
|
|
motion_blocking: self.heightmap.clone(),
|
|
|
|
}),
|
2022-04-15 07:55:45 +10:00
|
|
|
blocks_and_biomes,
|
|
|
|
block_entities: Vec::new(), // TODO
|
|
|
|
trust_edges: true,
|
2022-04-30 22:06:20 +10:00
|
|
|
// sky_light_mask: bitvec![u64, _; 1; section_count + 2],
|
|
|
|
sky_light_mask: BitVec::new(),
|
2022-04-15 07:55:45 +10:00
|
|
|
block_light_mask: BitVec::new(),
|
|
|
|
empty_sky_light_mask: BitVec::new(),
|
|
|
|
empty_block_light_mask: BitVec::new(),
|
2022-04-30 22:06:20 +10:00
|
|
|
// sky_light_arrays: vec![[0xff; 2048]; section_count + 2],
|
|
|
|
sky_light_arrays: Vec::new(),
|
2022-04-15 07:55:45 +10:00
|
|
|
block_light_arrays: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-23 01:06:54 +10:00
|
|
|
/// Returns unapplied changes to this chunk as block change packets through
|
|
|
|
/// the provided closure.
|
|
|
|
pub(crate) fn block_change_packets(
|
|
|
|
&self,
|
|
|
|
pos: ChunkPos,
|
|
|
|
min_y: i32,
|
|
|
|
mut packet: impl FnMut(BlockChangePacket),
|
|
|
|
) {
|
|
|
|
for (sect_y, sect) in self.sections.iter().enumerate() {
|
|
|
|
if sect.modified_count == 1 {
|
|
|
|
let (idx, &block) = sect
|
|
|
|
.blocks
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.find(|&(_, &b)| b & !BLOCK_STATE_MASK != 0)
|
|
|
|
.expect("invalid modified count");
|
|
|
|
|
|
|
|
let global_x = pos.x * 16 + (idx % 16) as i32;
|
|
|
|
let global_y = sect_y as i32 * 16 + (idx / (16 * 16)) as i32 + min_y;
|
|
|
|
let global_z = pos.z * 16 + (idx / 16 % 16) as i32;
|
|
|
|
|
2022-07-01 07:18:29 +10:00
|
|
|
packet(BlockChangePacket::Single(BlockUpdate {
|
2022-06-23 01:06:54 +10:00
|
|
|
location: BlockPos::new(global_x, global_y, global_z),
|
|
|
|
block_id: VarInt((block & BLOCK_STATE_MASK).into()),
|
|
|
|
}));
|
|
|
|
} else if sect.modified_count > 1 {
|
|
|
|
let mut blocks = Vec::new();
|
|
|
|
for y in 0..16 {
|
|
|
|
for z in 0..16 {
|
|
|
|
for x in 0..16 {
|
|
|
|
let block =
|
|
|
|
sect.blocks[x as usize + z as usize * 16 + y as usize * 16 * 16];
|
|
|
|
|
|
|
|
if block & !BLOCK_STATE_MASK != 0 {
|
|
|
|
blocks.push(VarLong(
|
|
|
|
((block & BLOCK_STATE_MASK) as i64) << 12
|
|
|
|
| (x << 8 | z << 4 | y),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let chunk_section_position = (pos.x as i64) << 42
|
|
|
|
| (pos.z as i64 & 0x3fffff) << 20
|
2022-06-25 09:11:15 +10:00
|
|
|
| (sect_y as i64 + min_y.div_euclid(16) as i64) & 0xfffff;
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-07-29 21:49:08 +10:00
|
|
|
packet(BlockChangePacket::Multi(ChunkSectionUpdate {
|
2022-06-23 01:06:54 +10:00
|
|
|
chunk_section_position,
|
|
|
|
invert_trust_edges: false,
|
|
|
|
blocks,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn apply_modifications(&mut self) {
|
2022-06-23 01:06:54 +10:00
|
|
|
let mut any_modified = false;
|
|
|
|
|
2022-06-30 04:09:00 +10:00
|
|
|
for sect in self.sections.iter_mut() {
|
2022-06-23 01:06:54 +10:00
|
|
|
if sect.modified_count > 0 {
|
|
|
|
sect.modified_count = 0;
|
|
|
|
any_modified = true;
|
|
|
|
|
|
|
|
sect.compact_data.clear();
|
|
|
|
|
|
|
|
let mut non_air_block_count: i16 = 0;
|
|
|
|
|
|
|
|
for b in &mut sect.blocks {
|
|
|
|
*b &= BLOCK_STATE_MASK;
|
|
|
|
if !BlockState::from_raw_unchecked(*b).is_air() {
|
|
|
|
non_air_block_count += 1;
|
|
|
|
}
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
2022-06-23 01:06:54 +10:00
|
|
|
|
|
|
|
non_air_block_count.encode(&mut sect.compact_data).unwrap();
|
|
|
|
|
|
|
|
encode_paletted_container(
|
|
|
|
sect.blocks.iter().cloned(),
|
|
|
|
4,
|
|
|
|
9,
|
|
|
|
log2_ceil((BlockState::max_raw() + 1) as usize),
|
|
|
|
&mut sect.compact_data,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// TODO: The direct bits per idx changes depending on the number of biomes in
|
|
|
|
// the biome registry.
|
|
|
|
encode_paletted_container(
|
|
|
|
sect.biomes.iter().map(|b| b.0),
|
|
|
|
0,
|
|
|
|
4,
|
|
|
|
6,
|
|
|
|
&mut sect.compact_data,
|
|
|
|
)
|
|
|
|
.unwrap();
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
2022-06-23 01:06:54 +10:00
|
|
|
}
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-06-23 01:06:54 +10:00
|
|
|
if any_modified {
|
2022-06-30 04:09:00 +10:00
|
|
|
build_heightmap(&self.sections, &mut self.heightmap);
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub(crate) enum BlockChangePacket {
|
2022-07-01 07:18:29 +10:00
|
|
|
Single(BlockUpdate),
|
2022-07-29 21:49:08 +10:00
|
|
|
Multi(ChunkSectionUpdate),
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
|
2022-06-10 13:26:21 +10:00
|
|
|
impl From<BlockChangePacket> for S2cPlayPacket {
|
2022-04-15 07:55:45 +10:00
|
|
|
fn from(p: BlockChangePacket) -> Self {
|
|
|
|
match p {
|
|
|
|
BlockChangePacket::Single(p) => p.into(),
|
|
|
|
BlockChangePacket::Multi(p) => p.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A 16x16x16 section of blocks, biomes, and light in a chunk.
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct ChunkSection {
|
2022-06-23 01:06:54 +10:00
|
|
|
/// The block states in this section, stored in x, z, y order.
|
|
|
|
/// The most significant bit is used to indicate if this block has been
|
|
|
|
/// modified.
|
|
|
|
blocks: [u16; 4096],
|
|
|
|
/// The number of modified blocks
|
|
|
|
modified_count: u16,
|
2022-05-01 12:05:38 +10:00
|
|
|
biomes: [BiomeId; 64],
|
2022-04-15 07:55:45 +10:00
|
|
|
compact_data: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
2022-06-23 01:06:54 +10:00
|
|
|
const BLOCK_STATE_MASK: u16 = 0x7fff;
|
|
|
|
|
|
|
|
const _: () = assert!(
|
|
|
|
BlockState::max_raw() <= BLOCK_STATE_MASK,
|
|
|
|
"There is not enough space in the block state type to store the modified bit. A bit array \
|
|
|
|
separate from the block state array should be created to keep track of modified blocks in \
|
|
|
|
the chunk section."
|
|
|
|
);
|
|
|
|
|
2022-04-15 07:55:45 +10:00
|
|
|
/// Builds the MOTION_BLOCKING heightmap.
|
|
|
|
fn build_heightmap(sections: &[ChunkSection], heightmap: &mut Vec<i64>) {
|
|
|
|
let height = sections.len() * 16;
|
|
|
|
let bits_per_val = log2_ceil(height);
|
|
|
|
let vals_per_u64 = 64 / bits_per_val;
|
2022-04-29 17:48:41 +10:00
|
|
|
let u64_count = Integer::div_ceil(&256, &vals_per_u64);
|
2022-04-15 07:55:45 +10:00
|
|
|
|
|
|
|
heightmap.clear();
|
2022-04-29 17:48:41 +10:00
|
|
|
heightmap.resize(u64_count, 0);
|
2022-04-15 07:55:45 +10:00
|
|
|
|
|
|
|
for x in 0..16 {
|
|
|
|
for z in 0..16 {
|
|
|
|
for y in (0..height).rev() {
|
2022-06-23 01:06:54 +10:00
|
|
|
let block = BlockState::from_raw_unchecked(
|
|
|
|
sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] & BLOCK_STATE_MASK,
|
|
|
|
);
|
|
|
|
|
2022-04-15 07:55:45 +10:00
|
|
|
// TODO: is_solid || is_fluid heuristic for motion blocking.
|
2022-05-01 12:05:38 +10:00
|
|
|
if !block.is_air() {
|
2022-04-15 07:55:45 +10:00
|
|
|
let column_height = y as u64;
|
|
|
|
|
|
|
|
let i = x * 16 + z; // TODO: X or Z major?
|
|
|
|
heightmap[i / vals_per_u64] |=
|
|
|
|
(column_height << (i % vals_per_u64 * bits_per_val)) as i64;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn encode_paletted_container(
|
2022-05-17 04:53:21 +10:00
|
|
|
mut entries: impl ExactSizeIterator<Item = u16> + Clone,
|
2022-04-15 07:55:45 +10:00
|
|
|
min_bits_per_idx: usize,
|
|
|
|
direct_threshold: usize,
|
|
|
|
direct_bits_per_idx: usize,
|
|
|
|
w: &mut impl Write,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
let mut palette = Vec::new();
|
|
|
|
|
2022-05-01 12:05:38 +10:00
|
|
|
for entry in entries.clone() {
|
2022-04-15 07:55:45 +10:00
|
|
|
if !palette.contains(&entry) {
|
|
|
|
palette.push(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let bits_per_idx = log2_ceil(palette.len());
|
|
|
|
|
|
|
|
(bits_per_idx as u8).encode(w)?;
|
|
|
|
|
|
|
|
if bits_per_idx == 0 {
|
|
|
|
// Single value case
|
|
|
|
debug_assert_eq!(palette.len(), 1);
|
|
|
|
VarInt(palette[0] as i32).encode(w)?;
|
|
|
|
VarInt(0).encode(w)?; // data array length
|
|
|
|
} else if bits_per_idx >= direct_threshold {
|
|
|
|
// Direct case
|
|
|
|
// Skip the palette
|
|
|
|
let idxs_per_u64 = 64 / direct_bits_per_idx;
|
2022-04-29 17:48:41 +10:00
|
|
|
let u64_count = Integer::div_ceil(&entries.len(), &idxs_per_u64);
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
VarInt(u64_count as i32).encode(w)?;
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-05-17 04:53:21 +10:00
|
|
|
for _ in 0..idxs_per_u64 {
|
2022-04-15 07:55:45 +10:00
|
|
|
let mut val = 0u64;
|
|
|
|
for i in 0..idxs_per_u64 {
|
2022-05-17 04:53:21 +10:00
|
|
|
if let Some(entry) = entries.next() {
|
|
|
|
val |= (entry as u64) << (i * direct_bits_per_idx);
|
|
|
|
}
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
val.encode(w)?;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Indirect case
|
|
|
|
VarInt(palette.len() as i32).encode(w)?;
|
|
|
|
for &val in &palette {
|
|
|
|
VarInt(val as i32).encode(w)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let bits_per_idx = bits_per_idx.max(min_bits_per_idx);
|
|
|
|
let idxs_per_u64 = 64 / bits_per_idx;
|
2022-04-29 17:48:41 +10:00
|
|
|
let u64_count = Integer::div_ceil(&entries.len(), &idxs_per_u64);
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-04-29 17:48:41 +10:00
|
|
|
VarInt(u64_count as i32).encode(w)?;
|
2022-04-15 07:55:45 +10:00
|
|
|
|
2022-05-17 04:53:21 +10:00
|
|
|
for _ in 0..u64_count {
|
2022-04-15 07:55:45 +10:00
|
|
|
let mut val = 0u64;
|
|
|
|
for i in 0..idxs_per_u64 {
|
2022-05-17 04:53:21 +10:00
|
|
|
if let Some(entry) = entries.next() {
|
|
|
|
let palette_idx = palette
|
|
|
|
.iter()
|
|
|
|
.position(|&e| e == entry)
|
2022-05-17 19:58:43 +10:00
|
|
|
.expect("entry should be in the palette")
|
|
|
|
as u64;
|
|
|
|
|
2022-05-17 04:53:21 +10:00
|
|
|
val |= palette_idx << (i * bits_per_idx);
|
2022-05-17 19:58:43 +10:00
|
|
|
}
|
2022-04-15 07:55:45 +10:00
|
|
|
}
|
|
|
|
val.encode(w)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Calculates the log base 2 rounded up.
|
|
|
|
fn log2_ceil(n: usize) -> usize {
|
|
|
|
n.next_power_of_two().trailing_zeros() as usize
|
|
|
|
}
|