mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-11 07:11:30 +11:00
Add block entities (#32)
This PR aims to add block entities. Fixes #5 --------- Co-authored-by: Ryan Johnson <ryanj00a@gmail.com>
This commit is contained in:
parent
bcd686990d
commit
1ceafe0ce0
|
@ -28,7 +28,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -25..25 {
|
for z in -25..25 {
|
||||||
for x in -25..25 {
|
for x in -25..25 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,13 @@ tokio = { version = "1.25.0", features = ["full"] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
url = { version = "2.2.2", features = ["serde"] }
|
url = { version = "2.2.2", features = ["serde"] }
|
||||||
uuid = { version = "1.1.2", features = ["serde"] }
|
uuid = { version = "1.1.2", features = ["serde"] }
|
||||||
valence_nbt = { version = "0.5.0", path = "../valence_nbt" }
|
valence_nbt = { version = "0.5.0", path = "../valence_nbt", features = [
|
||||||
valence_protocol = { version = "0.1.0", path = "../valence_protocol", features = ["encryption", "compression"] }
|
"uuid",
|
||||||
|
] }
|
||||||
|
valence_protocol = { version = "0.1.0", path = "../valence_protocol", features = [
|
||||||
|
"encryption",
|
||||||
|
"compression",
|
||||||
|
] }
|
||||||
|
|
||||||
[dependencies.reqwest]
|
[dependencies.reqwest]
|
||||||
version = "0.11.12"
|
version = "0.11.12"
|
||||||
|
|
|
@ -60,7 +60,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -50..50 {
|
for z in -50..50 {
|
||||||
for x in -50..50 {
|
for x in -50..50 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
126
crates/valence/examples/block_entities.rs
Normal file
126
crates/valence/examples/block_entities.rs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
use valence::client::despawn_disconnected_clients;
|
||||||
|
use valence::client::event::{default_event_handler, ChatMessage, UseItemOnBlock};
|
||||||
|
use valence::prelude::*;
|
||||||
|
use valence_nbt::{compound, List};
|
||||||
|
use valence_protocol::types::Hand;
|
||||||
|
|
||||||
|
const FLOOR_Y: i32 = 64;
|
||||||
|
const SIGN_POS: [i32; 3] = [3, FLOOR_Y + 1, 2];
|
||||||
|
const SKULL_POS: BlockPos = BlockPos::new(3, FLOOR_Y + 1, 3);
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
tracing_subscriber::fmt().init();
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.add_plugin(ServerPlugin::new(()))
|
||||||
|
.add_system_to_stage(EventLoop, default_event_handler)
|
||||||
|
.add_system_to_stage(EventLoop, event_handler)
|
||||||
|
.add_system_set(PlayerList::default_system_set())
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(init_clients)
|
||||||
|
.add_system(despawn_disconnected_clients)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(world: &mut World) {
|
||||||
|
let mut instance = world
|
||||||
|
.resource::<Server>()
|
||||||
|
.new_instance(DimensionId::default());
|
||||||
|
|
||||||
|
for z in -5..5 {
|
||||||
|
for x in -5..5 {
|
||||||
|
instance.insert_chunk([x, z], Chunk::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for z in 0..16 {
|
||||||
|
for x in 0..8 {
|
||||||
|
instance.set_block([x, FLOOR_Y, z], BlockState::WHITE_CONCRETE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.set_block(
|
||||||
|
[3, FLOOR_Y + 1, 1],
|
||||||
|
BlockState::CHEST.set(PropName::Facing, PropValue::West),
|
||||||
|
);
|
||||||
|
instance.set_block(
|
||||||
|
SIGN_POS,
|
||||||
|
Block::with_nbt(
|
||||||
|
BlockState::OAK_SIGN.set(PropName::Rotation, PropValue::_4),
|
||||||
|
compound! {
|
||||||
|
"Text1" => "Type in chat:".color(Color::RED),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
instance.set_block(
|
||||||
|
SKULL_POS,
|
||||||
|
BlockState::PLAYER_HEAD.set(PropName::Rotation, PropValue::_12),
|
||||||
|
);
|
||||||
|
|
||||||
|
world.spawn(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_clients(
|
||||||
|
mut clients: Query<&mut Client, Added<Client>>,
|
||||||
|
instances: Query<Entity, With<Instance>>,
|
||||||
|
) {
|
||||||
|
for mut client in &mut clients {
|
||||||
|
client.set_position([1.5, FLOOR_Y as f64 + 1.0, 1.5]);
|
||||||
|
client.set_yaw(-90.0);
|
||||||
|
client.set_instance(instances.single());
|
||||||
|
client.set_game_mode(GameMode::Creative);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_handler(
|
||||||
|
clients: Query<&Client>,
|
||||||
|
mut messages: EventReader<ChatMessage>,
|
||||||
|
mut block_interacts: EventReader<UseItemOnBlock>,
|
||||||
|
mut instances: Query<&mut Instance>,
|
||||||
|
) {
|
||||||
|
let mut instance = instances.single_mut();
|
||||||
|
for ChatMessage {
|
||||||
|
client, message, ..
|
||||||
|
} in messages.iter()
|
||||||
|
{
|
||||||
|
let Ok(client) = clients.get(*client) else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sign = instance.block_mut(SIGN_POS).unwrap();
|
||||||
|
let nbt = sign.nbt_mut().unwrap();
|
||||||
|
nbt.insert("Text2", message.to_string().color(Color::DARK_GREEN));
|
||||||
|
nbt.insert("Text3", format!("~{}", client.username()).italic());
|
||||||
|
}
|
||||||
|
|
||||||
|
for UseItemOnBlock {
|
||||||
|
client,
|
||||||
|
position,
|
||||||
|
hand,
|
||||||
|
..
|
||||||
|
} in block_interacts.iter()
|
||||||
|
{
|
||||||
|
if *hand == Hand::Main && *position == SKULL_POS {
|
||||||
|
let Ok(client) = clients.get(*client) else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(textures) = client.properties().iter().find(|prop| prop.name == "textures") else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut skull = instance.block_mut(SKULL_POS).unwrap();
|
||||||
|
let nbt = skull.nbt_mut().unwrap();
|
||||||
|
*nbt = compound! {
|
||||||
|
"SkullOwner" => compound! {
|
||||||
|
"Id" => client.uuid(),
|
||||||
|
"Properties" => compound! {
|
||||||
|
"textures" => List::Compound(vec![compound! {
|
||||||
|
"Value" => textures.value.clone(),
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -25..25 {
|
for z in -25..25 {
|
||||||
for x in -25..25 {
|
for x in -25..25 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ fn digging_creative_mode(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if client.game_mode() == GameMode::Creative {
|
if client.game_mode() == GameMode::Creative {
|
||||||
instance.set_block_state(event.position, BlockState::AIR);
|
instance.set_block(event.position, BlockState::AIR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ fn digging_survival_mode(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if client.game_mode() == GameMode::Survival {
|
if client.game_mode() == GameMode::Survival {
|
||||||
instance.set_block_state(event.position, BlockState::AIR);
|
instance.set_block(event.position, BlockState::AIR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,6 @@ fn place_blocks(
|
||||||
inventory.replace_slot(slot_id, slot);
|
inventory.replace_slot(slot_id, slot);
|
||||||
}
|
}
|
||||||
let real_pos = event.position.get_in_direction(event.face);
|
let real_pos = event.position.get_in_direction(event.face);
|
||||||
instance.set_block_state(real_pos, block_kind.to_state());
|
instance.set_block(real_pos, block_kind.to_state());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,11 +34,11 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -25..25 {
|
for z in -25..25 {
|
||||||
for x in -25..25 {
|
for x in -25..25 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
instance.set_block_state(CHEST_POS, BlockState::CHEST);
|
instance.set_block(CHEST_POS, BlockState::CHEST);
|
||||||
instance.set_block_state(
|
instance.set_block(
|
||||||
[CHEST_POS[0], CHEST_POS[1] - 1, CHEST_POS[2]],
|
[CHEST_POS[0], CHEST_POS[1] - 1, CHEST_POS[2]],
|
||||||
BlockState::STONE,
|
BlockState::STONE,
|
||||||
);
|
);
|
||||||
|
|
|
@ -58,7 +58,7 @@ fn setup(world: &mut World) {
|
||||||
};
|
};
|
||||||
|
|
||||||
for y in 0..SPAWN_Y {
|
for y in 0..SPAWN_Y {
|
||||||
instance.set_block_state([x, y, z], block);
|
instance.set_block([x, y, z], block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in BOARD_MIN_Z..=BOARD_MAX_Z {
|
for z in BOARD_MIN_Z..=BOARD_MAX_Z {
|
||||||
for x in BOARD_MIN_X..=BOARD_MAX_X {
|
for x in BOARD_MIN_X..=BOARD_MAX_X {
|
||||||
instance.set_block_state([x, BOARD_Y, z], BlockState::DIRT);
|
instance.set_block([x, BOARD_Y, z], BlockState::DIRT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +173,7 @@ fn update_board(
|
||||||
BlockState::DIRT
|
BlockState::DIRT
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.set_block_state([x, BOARD_Y, z], block);
|
instance.set_block([x, BOARD_Y, z], block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ fn setup(world: &mut World) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.set_block_state(SPAWN_POS, BlockState::BEDROCK);
|
instance.set_block(SPAWN_POS, BlockState::BEDROCK);
|
||||||
|
|
||||||
let instance_id = world.spawn(instance).id();
|
let instance_id = world.spawn(instance).id();
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -25..25 {
|
for z in -25..25 {
|
||||||
for x in -25..25 {
|
for x in -25..25 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], block);
|
instance.set_block([x, SPAWN_Y, z], block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -25..25 {
|
for z in -25..25 {
|
||||||
for x in -25..25 {
|
for x in -25..25 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,11 +189,11 @@ fn reset(client: &mut Client, state: &mut GameState, instance: &mut Instance) {
|
||||||
state.combo = 0;
|
state.combo = 0;
|
||||||
|
|
||||||
for block in &state.blocks {
|
for block in &state.blocks {
|
||||||
instance.set_block_state(*block, BlockState::AIR);
|
instance.set_block(*block, BlockState::AIR);
|
||||||
}
|
}
|
||||||
state.blocks.clear();
|
state.blocks.clear();
|
||||||
state.blocks.push_back(START_POS);
|
state.blocks.push_back(START_POS);
|
||||||
instance.set_block_state(START_POS, BlockState::STONE);
|
instance.set_block(START_POS, BlockState::STONE);
|
||||||
|
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
generate_next_block(state, instance, false);
|
generate_next_block(state, instance, false);
|
||||||
|
@ -212,7 +212,7 @@ fn reset(client: &mut Client, state: &mut GameState, instance: &mut Instance) {
|
||||||
fn generate_next_block(state: &mut GameState, instance: &mut Instance, in_game: bool) {
|
fn generate_next_block(state: &mut GameState, instance: &mut Instance, in_game: bool) {
|
||||||
if in_game {
|
if in_game {
|
||||||
let removed_block = state.blocks.pop_front().unwrap();
|
let removed_block = state.blocks.pop_front().unwrap();
|
||||||
instance.set_block_state(removed_block, BlockState::AIR);
|
instance.set_block(removed_block, BlockState::AIR);
|
||||||
|
|
||||||
state.score += 1
|
state.score += 1
|
||||||
}
|
}
|
||||||
|
@ -228,7 +228,7 @@ fn generate_next_block(state: &mut GameState, instance: &mut Instance, in_game:
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
instance.set_block_state(block_pos, *BLOCK_TYPES.choose(&mut rng).unwrap());
|
instance.set_block(block_pos, *BLOCK_TYPES.choose(&mut rng).unwrap());
|
||||||
state.blocks.push_back(block_pos);
|
state.blocks.push_back(block_pos);
|
||||||
|
|
||||||
// Combo System
|
// Combo System
|
||||||
|
|
|
@ -49,7 +49,7 @@ fn setup(world: &mut World) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.set_block_state([0, SPAWN_Y, 0], BlockState::BEDROCK);
|
instance.set_block([0, SPAWN_Y, 0], BlockState::BEDROCK);
|
||||||
|
|
||||||
world.spawn(instance);
|
world.spawn(instance);
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -25..25 {
|
for z in -25..25 {
|
||||||
for x in -25..25 {
|
for x in -25..25 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], BlockState::LIGHT_GRAY_WOOL);
|
instance.set_block([x, SPAWN_Y, z], BlockState::LIGHT_GRAY_WOOL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -25..25 {
|
for z in -25..25 {
|
||||||
for x in -25..25 {
|
for x in -25..25 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ fn setup(world: &mut World) {
|
||||||
|
|
||||||
for z in -25..25 {
|
for z in -25..25 {
|
||||||
for x in -25..25 {
|
for x in -25..25 {
|
||||||
instance.set_block_state([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ pub use chunk_entry::*;
|
||||||
use glam::{DVec3, Vec3};
|
use glam::{DVec3, Vec3};
|
||||||
use num::integer::div_ceil;
|
use num::integer::div_ceil;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use valence_protocol::block::BlockState;
|
|
||||||
use valence_protocol::packets::s2c::particle::{Particle, ParticleS2c};
|
use valence_protocol::packets::s2c::particle::{Particle, ParticleS2c};
|
||||||
use valence_protocol::packets::s2c::play::{SetActionBarText, SoundEffect};
|
use valence_protocol::packets::s2c::play::{SetActionBarText, SoundEffect};
|
||||||
use valence_protocol::types::SoundCategory;
|
use valence_protocol::types::SoundCategory;
|
||||||
|
@ -15,7 +14,7 @@ use valence_protocol::{BlockPos, EncodePacket, LengthPrefixedArray, Sound, Text}
|
||||||
|
|
||||||
use crate::dimension::DimensionId;
|
use crate::dimension::DimensionId;
|
||||||
use crate::entity::McEntity;
|
use crate::entity::McEntity;
|
||||||
pub use crate::instance::chunk::Chunk;
|
pub use crate::instance::chunk::{Block, BlockMut, BlockRef, Chunk};
|
||||||
use crate::packet::{PacketWriter, WritePacket};
|
use crate::packet::{PacketWriter, WritePacket};
|
||||||
use crate::server::{Server, SharedServer};
|
use crate::server::{Server, SharedServer};
|
||||||
use crate::view::ChunkPos;
|
use crate::view::ChunkPos;
|
||||||
|
@ -218,59 +217,90 @@ impl Instance {
|
||||||
self.packet_buf.shrink_to_fit();
|
self.packet_buf.shrink_to_fit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the block state at an absolute block position in world space. Only
|
/// Gets a reference to the block at an absolute block position in world
|
||||||
/// works for blocks in loaded chunks.
|
/// space. Only works for blocks in loaded chunks.
|
||||||
///
|
///
|
||||||
/// If the position is not inside of a chunk, then [`BlockState::AIR`] is
|
/// If the position is not inside of a chunk, then [`Option::None`] is
|
||||||
/// returned.
|
/// returned.
|
||||||
pub fn block_state(&self, pos: impl Into<BlockPos>) -> BlockState {
|
pub fn block(&self, pos: impl Into<BlockPos>) -> Option<BlockRef> {
|
||||||
let pos = pos.into();
|
let pos = pos.into();
|
||||||
|
|
||||||
let Some(y) = pos.y.checked_sub(self.info.min_y).and_then(|y| y.try_into().ok()) else {
|
let Some(y) = pos.y.checked_sub(self.info.min_y).and_then(|y| y.try_into().ok()) else {
|
||||||
return BlockState::AIR;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if y >= self.info.section_count * 16 {
|
if y >= self.info.section_count * 16 {
|
||||||
return BlockState::AIR;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(chunk) = self.chunk(ChunkPos::from_block_pos(pos)) else {
|
let Some(chunk) = self.chunk(ChunkPos::from_block_pos(pos)) else {
|
||||||
return BlockState::AIR;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
chunk.block_state(
|
Some(chunk.block(
|
||||||
pos.x.rem_euclid(16) as usize,
|
pos.x.rem_euclid(16) as usize,
|
||||||
y,
|
y,
|
||||||
pos.z.rem_euclid(16) as usize,
|
pos.z.rem_euclid(16) as usize,
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the block state at an absolute block position in world space. The
|
/// Gets a mutable reference to the block at an absolute block position in
|
||||||
/// previous block state at the position is returned.
|
/// world space. Only works for blocks in loaded chunks.
|
||||||
///
|
///
|
||||||
/// If the position is not within a loaded chunk or otherwise out of bounds,
|
/// If the position is not inside of a chunk, then [`Option::None`] is
|
||||||
/// then [`BlockState::AIR`] is returned with no effect.
|
/// returned.
|
||||||
pub fn set_block_state(&mut self, pos: impl Into<BlockPos>, block: BlockState) -> BlockState {
|
pub fn block_mut(&mut self, pos: impl Into<BlockPos>) -> Option<BlockMut> {
|
||||||
let pos = pos.into();
|
let pos = pos.into();
|
||||||
|
|
||||||
let Some(y) = pos.y.checked_sub(self.info.min_y).and_then(|y| y.try_into().ok()) else {
|
let Some(y) = pos.y.checked_sub(self.info.min_y).and_then(|y| y.try_into().ok()) else {
|
||||||
return BlockState::AIR;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if y >= self.info.section_count * 16 {
|
if y >= self.info.section_count * 16 {
|
||||||
return BlockState::AIR;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(chunk) = self.chunk_mut(ChunkPos::from_block_pos(pos)) else {
|
let Some(chunk) = self.chunk_mut(ChunkPos::from_block_pos(pos)) else {
|
||||||
return BlockState::AIR;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
chunk.set_block_state(
|
Some(chunk.block_mut(
|
||||||
|
pos.x.rem_euclid(16) as usize,
|
||||||
|
y,
|
||||||
|
pos.z.rem_euclid(16) as usize,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the block at an absolute block position in world space. The
|
||||||
|
/// previous block at the position is returned.
|
||||||
|
///
|
||||||
|
/// If the position is not within a loaded chunk or otherwise out of bounds,
|
||||||
|
/// then [`Option::None`] is returned with no effect.
|
||||||
|
pub fn set_block(
|
||||||
|
&mut self,
|
||||||
|
pos: impl Into<BlockPos>,
|
||||||
|
block: impl Into<Block>,
|
||||||
|
) -> Option<Block> {
|
||||||
|
let pos = pos.into();
|
||||||
|
|
||||||
|
let Some(y) = pos.y.checked_sub(self.info.min_y).and_then(|y| y.try_into().ok()) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if y >= self.info.section_count * 16 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(chunk) = self.chunk_mut(ChunkPos::from_block_pos(pos)) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(chunk.set_block(
|
||||||
pos.x.rem_euclid(16) as usize,
|
pos.x.rem_euclid(16) as usize,
|
||||||
y,
|
y,
|
||||||
pos.z.rem_euclid(16) as usize,
|
pos.z.rem_euclid(16) as usize,
|
||||||
block,
|
block,
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a packet into the global packet buffer of this instance. All
|
/// Writes a packet into the global packet buffer of this instance. All
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::btree_map::Entry;
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
// Using nonstandard mutex to avoid poisoning API.
|
// Using nonstandard mutex to avoid poisoning API.
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use valence_nbt::compound;
|
use valence_nbt::{compound, Compound};
|
||||||
use valence_protocol::block::BlockState;
|
use valence_protocol::block::{BlockEntity, BlockState};
|
||||||
use valence_protocol::packets::s2c::play::{
|
use valence_protocol::packets::s2c::play::{
|
||||||
BlockUpdate, ChunkDataAndUpdateLightEncode, UpdateSectionBlocksEncode,
|
BlockEntityData, BlockUpdate, ChunkDataAndUpdateLightEncode, UpdateSectionBlocksEncode,
|
||||||
};
|
};
|
||||||
|
use valence_protocol::types::ChunkDataBlockEntity;
|
||||||
use valence_protocol::{BlockPos, Encode, VarInt, VarLong};
|
use valence_protocol::{BlockPos, Encode, VarInt, VarLong};
|
||||||
|
|
||||||
use crate::biome::BiomeId;
|
use crate::biome::BiomeId;
|
||||||
|
@ -32,6 +36,9 @@ pub struct Chunk<const LOADED: bool = false> {
|
||||||
/// Tracks if any clients are in view of this (loaded) chunk. Useful for
|
/// Tracks if any clients are in view of this (loaded) chunk. Useful for
|
||||||
/// knowing when a chunk should be unloaded.
|
/// knowing when a chunk should be unloaded.
|
||||||
viewed: AtomicBool,
|
viewed: AtomicBool,
|
||||||
|
/// Block entities in this chunk
|
||||||
|
block_entities: BTreeMap<u32, BlockEntity>,
|
||||||
|
modified_block_entities: BTreeSet<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
|
@ -46,6 +53,124 @@ struct Section {
|
||||||
section_updates: Vec<VarLong>,
|
section_updates: Vec<VarLong>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a block with an optional block entity
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct Block {
|
||||||
|
state: BlockState,
|
||||||
|
/// Nbt of the block entity
|
||||||
|
nbt: Option<Compound>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Block {
|
||||||
|
pub const AIR: Self = Self {
|
||||||
|
state: BlockState::AIR,
|
||||||
|
nbt: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn new(state: BlockState) -> Self {
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
nbt: state.block_entity_kind().map(|_| Compound::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_nbt(state: BlockState, nbt: Compound) -> Self {
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
nbt: state.block_entity_kind().map(|_| nbt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn state(&self) -> BlockState {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockState> for Block {
|
||||||
|
fn from(value: BlockState) -> Self {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockRef<'_>> for Block {
|
||||||
|
fn from(BlockRef { state, nbt }: BlockRef<'_>) -> Self {
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
nbt: nbt.cloned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BlockMut<'_>> for Block {
|
||||||
|
fn from(value: BlockMut<'_>) -> Self {
|
||||||
|
Self {
|
||||||
|
state: value.state,
|
||||||
|
nbt: value.nbt().cloned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Block> for Block {
|
||||||
|
fn from(value: &Block) -> Self {
|
||||||
|
value.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&mut Block> for Block {
|
||||||
|
fn from(value: &mut Block) -> Self {
|
||||||
|
value.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immutable reference to a block in a chunk
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct BlockRef<'a> {
|
||||||
|
state: BlockState,
|
||||||
|
nbt: Option<&'a Compound>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BlockRef<'a> {
|
||||||
|
pub const fn state(&self) -> BlockState {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn nbt(&self) -> Option<&'a Compound> {
|
||||||
|
self.nbt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable reference to a block in a chunk
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BlockMut<'a> {
|
||||||
|
state: BlockState,
|
||||||
|
/// Entry into the block entity map.
|
||||||
|
entry: Entry<'a, u32, BlockEntity>,
|
||||||
|
modified: &'a mut BTreeSet<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BlockMut<'a> {
|
||||||
|
pub const fn state(&self) -> BlockState {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nbt(&self) -> Option<&Compound> {
|
||||||
|
match &self.entry {
|
||||||
|
Entry::Occupied(entry) => Some(&entry.get().nbt),
|
||||||
|
Entry::Vacant(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nbt_mut(&mut self) -> Option<&mut Compound> {
|
||||||
|
match &mut self.entry {
|
||||||
|
Entry::Occupied(entry) => {
|
||||||
|
self.modified.insert(*entry.key());
|
||||||
|
Some(&mut entry.get_mut().nbt)
|
||||||
|
}
|
||||||
|
Entry::Vacant(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16;
|
const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16;
|
||||||
const SECTION_BIOME_COUNT: usize = 4 * 4 * 4;
|
const SECTION_BIOME_COUNT: usize = 4 * 4 * 4;
|
||||||
|
|
||||||
|
@ -59,6 +184,8 @@ impl Chunk<false> {
|
||||||
cached_init_packets: Mutex::new(vec![]),
|
cached_init_packets: Mutex::new(vec![]),
|
||||||
refresh: true,
|
refresh: true,
|
||||||
viewed: AtomicBool::new(false),
|
viewed: AtomicBool::new(false),
|
||||||
|
block_entities: BTreeMap::new(),
|
||||||
|
modified_block_entities: BTreeSet::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
chunk.resize(section_count);
|
chunk.resize(section_count);
|
||||||
|
@ -84,12 +211,15 @@ impl Chunk<false> {
|
||||||
|
|
||||||
pub(super) fn into_loaded(self) -> Chunk<true> {
|
pub(super) fn into_loaded(self) -> Chunk<true> {
|
||||||
debug_assert!(self.refresh);
|
debug_assert!(self.refresh);
|
||||||
|
debug_assert!(self.modified_block_entities.is_empty());
|
||||||
|
|
||||||
Chunk {
|
Chunk {
|
||||||
sections: self.sections,
|
sections: self.sections,
|
||||||
cached_init_packets: self.cached_init_packets,
|
cached_init_packets: self.cached_init_packets,
|
||||||
refresh: true,
|
refresh: true,
|
||||||
viewed: AtomicBool::new(false),
|
viewed: AtomicBool::new(false),
|
||||||
|
block_entities: self.block_entities,
|
||||||
|
modified_block_entities: self.modified_block_entities,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,6 +237,8 @@ impl Clone for Chunk {
|
||||||
cached_init_packets: Mutex::new(vec![]),
|
cached_init_packets: Mutex::new(vec![]),
|
||||||
refresh: true,
|
refresh: true,
|
||||||
viewed: AtomicBool::new(false),
|
viewed: AtomicBool::new(false),
|
||||||
|
block_entities: self.block_entities.clone(),
|
||||||
|
modified_block_entities: BTreeSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,6 +264,8 @@ impl Chunk<true> {
|
||||||
cached_init_packets: Mutex::new(vec![]),
|
cached_init_packets: Mutex::new(vec![]),
|
||||||
refresh: true,
|
refresh: true,
|
||||||
viewed: AtomicBool::new(false),
|
viewed: AtomicBool::new(false),
|
||||||
|
block_entities: self.block_entities.clone(),
|
||||||
|
modified_block_entities: BTreeSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,12 +295,15 @@ impl Chunk<true> {
|
||||||
for sect in &mut self.sections {
|
for sect in &mut self.sections {
|
||||||
sect.section_updates.clear();
|
sect.section_updates.clear();
|
||||||
}
|
}
|
||||||
|
self.modified_block_entities.clear();
|
||||||
|
|
||||||
Chunk {
|
Chunk {
|
||||||
sections: self.sections,
|
sections: self.sections,
|
||||||
cached_init_packets: self.cached_init_packets,
|
cached_init_packets: self.cached_init_packets,
|
||||||
refresh: true,
|
refresh: true,
|
||||||
viewed: AtomicBool::new(false),
|
viewed: AtomicBool::new(false),
|
||||||
|
block_entities: self.block_entities,
|
||||||
|
modified_block_entities: self.modified_block_entities,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,6 +345,24 @@ impl Chunk<true> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for idx in &self.modified_block_entities {
|
||||||
|
let Some(block_entity) = self.block_entities.get(idx) else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let x = idx % 16;
|
||||||
|
let z = (idx / 16) % 16;
|
||||||
|
let y = idx / 16 / 16;
|
||||||
|
|
||||||
|
let global_x = pos.x * 16 + x as i32;
|
||||||
|
let global_y = info.min_y + y as i32;
|
||||||
|
let global_z = pos.z * 16 + z as i32;
|
||||||
|
|
||||||
|
writer.write_packet(&BlockEntityData {
|
||||||
|
position: BlockPos::new(global_x, global_y, global_z),
|
||||||
|
kind: block_entity.kind,
|
||||||
|
data: Cow::Borrowed(&block_entity.nbt),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,6 +412,23 @@ impl Chunk<true> {
|
||||||
&mut compression_scratch,
|
&mut compression_scratch,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let block_entities: Vec<_> = self
|
||||||
|
.block_entities
|
||||||
|
.iter()
|
||||||
|
.map(|(idx, block_entity)| {
|
||||||
|
let x = idx % 16;
|
||||||
|
let z = idx / 16 % 16;
|
||||||
|
let y = (idx / 16 / 16) as i16 + info.min_y as i16;
|
||||||
|
|
||||||
|
ChunkDataBlockEntity {
|
||||||
|
packed_xz: ((x << 4) | z) as i8,
|
||||||
|
y,
|
||||||
|
kind: block_entity.kind,
|
||||||
|
data: Cow::Borrowed(&block_entity.nbt),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
writer.write_packet(&ChunkDataAndUpdateLightEncode {
|
writer.write_packet(&ChunkDataAndUpdateLightEncode {
|
||||||
chunk_x: pos.x,
|
chunk_x: pos.x,
|
||||||
chunk_z: pos.z,
|
chunk_z: pos.z,
|
||||||
|
@ -264,7 +436,7 @@ impl Chunk<true> {
|
||||||
// TODO: MOTION_BLOCKING heightmap
|
// TODO: MOTION_BLOCKING heightmap
|
||||||
},
|
},
|
||||||
blocks_and_biomes: scratch,
|
blocks_and_biomes: scratch,
|
||||||
block_entities: &[],
|
block_entities: &block_entities,
|
||||||
trust_edges: true,
|
trust_edges: true,
|
||||||
sky_light_mask: &info.filler_sky_light_mask,
|
sky_light_mask: &info.filler_sky_light_mask,
|
||||||
block_light_mask: &[],
|
block_light_mask: &[],
|
||||||
|
@ -284,6 +456,7 @@ impl Chunk<true> {
|
||||||
for sect in &mut self.sections {
|
for sect in &mut self.sections {
|
||||||
sect.section_updates.clear();
|
sect.section_updates.clear();
|
||||||
}
|
}
|
||||||
|
self.modified_block_entities.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +490,7 @@ impl<const LOADED: bool> Chunk<LOADED> {
|
||||||
|
|
||||||
/// Sets the block state at the provided offsets in the chunk. The previous
|
/// Sets the block state at the provided offsets in the chunk. The previous
|
||||||
/// block state at the position is returned.
|
/// block state at the position is returned.
|
||||||
|
/// Also, the corresponding block entity is placed.
|
||||||
///
|
///
|
||||||
/// **Note**: The arguments to this function are offsets from the minimum
|
/// **Note**: The arguments to this function are offsets from the minimum
|
||||||
/// corner of the chunk in _chunk space_ rather than _world space_.
|
/// corner of the chunk in _chunk space_ rather than _world space_.
|
||||||
|
@ -359,6 +533,23 @@ impl<const LOADED: bool> Chunk<LOADED> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let idx = (x + z * 16 + y * 16 * 16) as _;
|
||||||
|
match block.block_entity_kind() {
|
||||||
|
Some(kind) => {
|
||||||
|
let block_entity = BlockEntity {
|
||||||
|
kind,
|
||||||
|
nbt: compound! {},
|
||||||
|
};
|
||||||
|
self.block_entities.insert(idx, block_entity);
|
||||||
|
if LOADED && !self.refresh {
|
||||||
|
self.modified_block_entities.insert(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.block_entities.remove(&idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
old_block
|
old_block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,6 +615,236 @@ impl<const LOADED: bool> Chunk<LOADED> {
|
||||||
}
|
}
|
||||||
|
|
||||||
sect.block_states.fill(block);
|
sect.block_states.fill(block);
|
||||||
|
|
||||||
|
for z in 0..16 {
|
||||||
|
for x in 0..16 {
|
||||||
|
for y in 0..16 {
|
||||||
|
let y = sect_y * 16 + y;
|
||||||
|
let idx = (x + z * 16 + y * 16 * 16) as _;
|
||||||
|
match block.block_entity_kind() {
|
||||||
|
Some(kind) => {
|
||||||
|
let block_entity = BlockEntity {
|
||||||
|
kind,
|
||||||
|
nbt: compound! {},
|
||||||
|
};
|
||||||
|
self.block_entities.insert(idx, block_entity);
|
||||||
|
if LOADED && !self.refresh {
|
||||||
|
self.modified_block_entities.insert(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.block_entities.remove(&idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the block entity at the provided offsets in the
|
||||||
|
/// chunk.
|
||||||
|
///
|
||||||
|
/// **Note**: The arguments to this function are offsets from the minimum
|
||||||
|
/// corner of the chunk in _chunk space_ rather than _world space_.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk. `x` and `z`
|
||||||
|
/// must be less than 16 while `y` must be less than `section_count() * 16`.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn block_entity(&self, x: usize, y: usize, z: usize) -> Option<&BlockEntity> {
|
||||||
|
assert!(
|
||||||
|
x < 16 && y < self.section_count() * 16 && z < 16,
|
||||||
|
"chunk block offsets of ({x}, {y}, {z}) are out of bounds"
|
||||||
|
);
|
||||||
|
let idx = (x + z * 16 + y * 16 * 16) as _;
|
||||||
|
|
||||||
|
self.block_entities.get(&idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the block entity at the provided offsets in the chunk.
|
||||||
|
/// Returns the block entity that was there before.
|
||||||
|
///
|
||||||
|
/// **Note**: The arguments to this function are offsets from the minimum
|
||||||
|
/// corner of the chunk in _chunk space_ rather than _world space_.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk. `x` and `z`
|
||||||
|
/// must be less than 16 while `y` must be less than `section_count() * 16`.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn set_block_entity(
|
||||||
|
&mut self,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
z: usize,
|
||||||
|
block_entity: BlockEntity,
|
||||||
|
) -> Option<BlockEntity> {
|
||||||
|
assert!(
|
||||||
|
x < 16 && y < self.section_count() * 16 && z < 16,
|
||||||
|
"chunk block offsets of ({x}, {y}, {z}) are out of bounds"
|
||||||
|
);
|
||||||
|
let idx = (x + z * 16 + y * 16 * 16) as _;
|
||||||
|
let old = self.block_entities.insert(idx, block_entity);
|
||||||
|
if LOADED {
|
||||||
|
self.modified_block_entities.insert(idx);
|
||||||
|
self.cached_init_packets.get_mut().clear();
|
||||||
|
}
|
||||||
|
old
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Edits the block entity at the provided offsets in the chunk.
|
||||||
|
/// Does nothing if there is no block entity at the provided offsets.
|
||||||
|
///
|
||||||
|
/// **Note**: The arguments to this function are offsets from the minimum
|
||||||
|
/// corner of the chunk in _chunk space_ rather than _world space_.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk. `x` and `z`
|
||||||
|
/// must be less than 16 while `y` must be less than `section_count() * 16`.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn edit_block_entity(
|
||||||
|
&mut self,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
z: usize,
|
||||||
|
f: impl FnOnce(&mut BlockEntity),
|
||||||
|
) {
|
||||||
|
assert!(
|
||||||
|
x < 16 && y < self.section_count() * 16 && z < 16,
|
||||||
|
"chunk block offsets of ({x}, {y}, {z}) are out of bounds"
|
||||||
|
);
|
||||||
|
let idx = (x + z * 16 + y * 16 * 16) as _;
|
||||||
|
self.block_entities.entry(idx).and_modify(f);
|
||||||
|
if LOADED {
|
||||||
|
self.modified_block_entities.insert(idx);
|
||||||
|
self.cached_init_packets.get_mut().clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the block at the provided offsets in the chunk. The previous
|
||||||
|
/// block at the position is returned.
|
||||||
|
///
|
||||||
|
/// **Note**: The arguments to this function are offsets from the minimum
|
||||||
|
/// corner of the chunk in _chunk space_ rather than _world space_.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk. `x` and `z`
|
||||||
|
/// must be less than 16 while `y` must be less than `section_count() * 16`.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn set_block(&mut self, x: usize, y: usize, z: usize, block: impl Into<Block>) -> Block {
|
||||||
|
assert!(
|
||||||
|
x < 16 && y < self.section_count() * 16 && z < 16,
|
||||||
|
"chunk block offsets of ({x}, {y}, {z}) are out of bounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
let Block { state, nbt } = block.into();
|
||||||
|
let old_state = {
|
||||||
|
let sect_y = y / 16;
|
||||||
|
let sect = &mut self.sections[sect_y];
|
||||||
|
let idx = x + z * 16 + y % 16 * 16 * 16;
|
||||||
|
|
||||||
|
let old_state = sect.block_states.set(idx, state);
|
||||||
|
|
||||||
|
if state != old_state {
|
||||||
|
// Update non-air count.
|
||||||
|
match (state.is_air(), old_state.is_air()) {
|
||||||
|
(true, false) => sect.non_air_count -= 1,
|
||||||
|
(false, true) => sect.non_air_count += 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if LOADED && !self.refresh {
|
||||||
|
let compact =
|
||||||
|
(state.to_raw() as i64) << 12 | (x << 8 | z << 4 | (y % 16)) as i64;
|
||||||
|
sect.section_updates.push(VarLong(compact));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
old_state
|
||||||
|
};
|
||||||
|
|
||||||
|
let idx = (x + z * 16 + y * 16 * 16) as _;
|
||||||
|
let old_block_entity = match nbt.and_then(|nbt| {
|
||||||
|
state
|
||||||
|
.block_entity_kind()
|
||||||
|
.map(|kind| BlockEntity { kind, nbt })
|
||||||
|
}) {
|
||||||
|
Some(block_entity) => self.block_entities.insert(idx, block_entity),
|
||||||
|
None => self.block_entities.remove(&idx),
|
||||||
|
};
|
||||||
|
if LOADED && !self.refresh {
|
||||||
|
self.modified_block_entities.insert(idx);
|
||||||
|
self.cached_init_packets.get_mut().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Block {
|
||||||
|
state: old_state,
|
||||||
|
nbt: old_block_entity.map(|block_entity| block_entity.nbt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the block at the provided offsets in the chunk.
|
||||||
|
///
|
||||||
|
/// **Note**: The arguments to this function are offsets from the minimum
|
||||||
|
/// corner of the chunk in _chunk space_ rather than _world space_.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk. `x` and `z`
|
||||||
|
/// must be less than 16 while `y` must be less than `section_count() * 16`.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn block(&self, x: usize, y: usize, z: usize) -> BlockRef {
|
||||||
|
assert!(
|
||||||
|
x < 16 && y < self.section_count() * 16 && z < 16,
|
||||||
|
"chunk block offsets of ({x}, {y}, {z}) are out of bounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
let state = self.sections[y / 16]
|
||||||
|
.block_states
|
||||||
|
.get(x + z * 16 + y % 16 * 16 * 16);
|
||||||
|
|
||||||
|
let idx = (x + z * 16 + y * 16 * 16) as _;
|
||||||
|
|
||||||
|
let nbt = self
|
||||||
|
.block_entities
|
||||||
|
.get(&idx)
|
||||||
|
.map(|block_entity| &block_entity.nbt);
|
||||||
|
|
||||||
|
BlockRef { state, nbt }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the block at the provided offsets in the
|
||||||
|
/// chunk.
|
||||||
|
///
|
||||||
|
/// **Note**: The arguments to this function are offsets from the minimum
|
||||||
|
/// corner of the chunk in _chunk space_ rather than _world space_.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the offsets are outside the bounds of the chunk. `x` and `z`
|
||||||
|
/// must be less than 16 while `y` must be less than `section_count() * 16`.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn block_mut(&mut self, x: usize, y: usize, z: usize) -> BlockMut {
|
||||||
|
assert!(
|
||||||
|
x < 16 && y < self.section_count() * 16 && z < 16,
|
||||||
|
"chunk block offsets of ({x}, {y}, {z}) are out of bounds"
|
||||||
|
);
|
||||||
|
|
||||||
|
let state = self.sections[y / 16]
|
||||||
|
.block_states
|
||||||
|
.get(x + z * 16 + y % 16 * 16 * 16);
|
||||||
|
|
||||||
|
let idx = (x + z * 16 + y * 16 * 16) as _;
|
||||||
|
|
||||||
|
let entry = self.block_entities.entry(idx);
|
||||||
|
|
||||||
|
BlockMut {
|
||||||
|
state,
|
||||||
|
entry,
|
||||||
|
modified: &mut self.modified_block_entities,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the biome at the provided biome offsets in the chunk.
|
/// Gets the biome at the provided biome offsets in the chunk.
|
||||||
|
@ -522,6 +943,8 @@ impl<const LOADED: bool> Chunk<LOADED> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use valence_protocol::block::BlockEntityKind;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::protocol::block::BlockState;
|
use crate::protocol::block::BlockState;
|
||||||
|
|
||||||
|
@ -565,4 +988,37 @@ mod tests {
|
||||||
chunk.fill_block_states(0, BlockState::AIR);
|
chunk.fill_block_states(0, BlockState::AIR);
|
||||||
check(&chunk, 6);
|
check(&chunk, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_entity_changes() {
|
||||||
|
let mut chunk = Chunk::new(5).into_loaded();
|
||||||
|
chunk.refresh = false;
|
||||||
|
|
||||||
|
assert!(chunk.block_entity(0, 0, 0).is_none());
|
||||||
|
chunk.set_block_state(0, 0, 0, BlockState::CHEST);
|
||||||
|
assert_eq!(
|
||||||
|
chunk.block_entity(0, 0, 0),
|
||||||
|
Some(&BlockEntity {
|
||||||
|
kind: BlockEntityKind::Chest,
|
||||||
|
nbt: compound! {}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
chunk.set_block_state(0, 0, 0, BlockState::STONE);
|
||||||
|
assert!(chunk.block_entity(0, 0, 0).is_none());
|
||||||
|
|
||||||
|
chunk.fill_block_states(2, BlockState::CHEST);
|
||||||
|
for x in 0..16 {
|
||||||
|
for z in 0..16 {
|
||||||
|
for y in 32..47 {
|
||||||
|
assert_eq!(
|
||||||
|
chunk.block_entity(x, y, z),
|
||||||
|
Some(&BlockEntity {
|
||||||
|
kind: BlockEntityKind::Chest,
|
||||||
|
nbt: compound! {}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub mod prelude {
|
||||||
EntityAnimation, EntityKind, EntityStatus, McEntity, McEntityManager, TrackedData,
|
EntityAnimation, EntityKind, EntityStatus, McEntity, McEntityManager, TrackedData,
|
||||||
};
|
};
|
||||||
pub use glam::DVec3;
|
pub use glam::DVec3;
|
||||||
pub use instance::{Chunk, Instance};
|
pub use instance::{Block, BlockMut, BlockRef, Chunk, Instance};
|
||||||
pub use inventory::{Inventory, InventoryKind, OpenInventory};
|
pub use inventory::{Inventory, InventoryKind, OpenInventory};
|
||||||
pub use player_list::{PlayerList, PlayerListEntry};
|
pub use player_list::{PlayerList, PlayerListEntry};
|
||||||
pub use protocol::block::{BlockState, PropName, PropValue};
|
pub use protocol::block::{BlockState, PropName, PropValue};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use num_integer::div_ceil;
|
use num_integer::{div_ceil, Integer};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use valence::biome::BiomeId;
|
use valence::biome::BiomeId;
|
||||||
use valence::instance::Chunk;
|
use valence::instance::Chunk;
|
||||||
use valence::protocol::block::{BlockKind, PropName, PropValue};
|
use valence::protocol::block::{BlockEntity, BlockEntityKind, BlockKind, PropName, PropValue};
|
||||||
use valence::protocol::Ident;
|
use valence::protocol::Ident;
|
||||||
use valence_nbt::{Compound, List, Value};
|
use valence_nbt::{Compound, List, Value};
|
||||||
|
|
||||||
|
@ -51,6 +51,14 @@ pub enum ToValenceError {
|
||||||
BadBiomeLongCount,
|
BadBiomeLongCount,
|
||||||
#[error("invalid biome palette index")]
|
#[error("invalid biome palette index")]
|
||||||
BadBiomePaletteIndex,
|
BadBiomePaletteIndex,
|
||||||
|
#[error("missing block entities")]
|
||||||
|
MissingBlockEntity,
|
||||||
|
#[error("missing block entity ident")]
|
||||||
|
MissingBlockEntityIdent,
|
||||||
|
#[error("invalid block entity ident of \"{0}\"")]
|
||||||
|
UnknownBlockEntityIdent(String),
|
||||||
|
#[error("invalid block entity position")]
|
||||||
|
InvalidBlockEntityPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes an Anvil chunk in NBT form and writes its data to a Valence [`Chunk`].
|
/// Takes an Anvil chunk in NBT form and writes its data to a Valence [`Chunk`].
|
||||||
|
@ -254,6 +262,48 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(Value::List(block_entities)) = nbt.get("block_entities") else {
|
||||||
|
return Err(ToValenceError::MissingBlockEntity);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let List::Compound(block_entities) = block_entities {
|
||||||
|
for comp in block_entities {
|
||||||
|
let Some(Value::String(ident)) = comp.get("id") else {
|
||||||
|
return Err(ToValenceError::MissingBlockEntityIdent);
|
||||||
|
};
|
||||||
|
let Ok(ident) = Ident::new(&ident[..]) else {
|
||||||
|
return Err(ToValenceError::UnknownBlockEntityIdent(ident.clone()));
|
||||||
|
};
|
||||||
|
let Some(kind) = BlockEntityKind::from_ident(ident) else {
|
||||||
|
return Err(ToValenceError::UnknownBlockEntityIdent(ident.as_str().to_string()));
|
||||||
|
};
|
||||||
|
let block_entity = BlockEntity {
|
||||||
|
kind,
|
||||||
|
nbt: comp.clone(),
|
||||||
|
};
|
||||||
|
let Some(Value::Int(x)) = comp.get("x") else {
|
||||||
|
return Err(ToValenceError::InvalidBlockEntityPosition);
|
||||||
|
};
|
||||||
|
let Ok(x) = usize::try_from(x.mod_floor(&16)) else {
|
||||||
|
return Err(ToValenceError::InvalidBlockEntityPosition);
|
||||||
|
};
|
||||||
|
let Some(Value::Int(y)) = comp.get("y") else {
|
||||||
|
return Err(ToValenceError::InvalidBlockEntityPosition);
|
||||||
|
};
|
||||||
|
let Ok(y) = usize::try_from(y + sect_offset * 16) else {
|
||||||
|
return Err(ToValenceError::InvalidBlockEntityPosition);
|
||||||
|
};
|
||||||
|
let Some(Value::Int(z)) = comp.get("z") else {
|
||||||
|
return Err(ToValenceError::InvalidBlockEntityPosition);
|
||||||
|
};
|
||||||
|
let Ok(z) = usize::try_from(z.mod_floor(&16)) else {
|
||||||
|
return Err(ToValenceError::InvalidBlockEntityPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
chunk.set_block_entity(x, y, z, block_entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ edition = "2021"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.4.3"
|
||||||
cesu8 = "1.1.0"
|
cesu8 = "1.1.0"
|
||||||
indexmap = { version = "1.9.1", optional = true }
|
indexmap = { version = "1.9.1", optional = true }
|
||||||
|
uuid = { version = "1.1.2", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# When enabled, the order of fields in compounds are preserved.
|
# When enabled, the order of fields in compounds are preserved.
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::tag::Tag;
|
use crate::tag::Tag;
|
||||||
use crate::Compound;
|
use crate::Compound;
|
||||||
|
|
||||||
|
@ -254,6 +257,20 @@ impl From<Vec<i64>> for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "uuid")]
|
||||||
|
impl From<Uuid> for Value {
|
||||||
|
fn from(value: Uuid) -> Self {
|
||||||
|
let (most, least) = value.as_u64_pair();
|
||||||
|
|
||||||
|
let first = (most >> 32) as i32;
|
||||||
|
let second = most as i32;
|
||||||
|
let third = (least >> 32) as i32;
|
||||||
|
let fourth = least as i32;
|
||||||
|
|
||||||
|
Value::IntArray(vec![first, second, third, fourth])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Vec<i8>> for List {
|
impl From<Vec<i8>> for List {
|
||||||
fn from(v: Vec<i8>) -> Self {
|
fn from(v: Vec<i8>) -> Self {
|
||||||
List::Byte(v)
|
List::Byte(v)
|
||||||
|
|
|
@ -11,6 +11,7 @@ use crate::ident;
|
||||||
struct TopLevel {
|
struct TopLevel {
|
||||||
blocks: Vec<Block>,
|
blocks: Vec<Block>,
|
||||||
shapes: Vec<Shape>,
|
shapes: Vec<Shape>,
|
||||||
|
block_entity_types: Vec<BlockEntityKind>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
@ -34,6 +35,13 @@ impl Block {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
struct BlockEntityKind {
|
||||||
|
id: u32,
|
||||||
|
ident: String,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
struct Property {
|
struct Property {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -47,6 +55,7 @@ struct State {
|
||||||
opaque: bool,
|
opaque: bool,
|
||||||
replaceable: bool,
|
replaceable: bool,
|
||||||
collision_shapes: Vec<u16>,
|
collision_shapes: Vec<u16>,
|
||||||
|
block_entity_type: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
@ -60,8 +69,11 @@ struct Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build() -> anyhow::Result<TokenStream> {
|
pub fn build() -> anyhow::Result<TokenStream> {
|
||||||
let TopLevel { blocks, shapes } =
|
let TopLevel {
|
||||||
serde_json::from_str(include_str!("../../../extracted/blocks.json"))?;
|
blocks,
|
||||||
|
shapes,
|
||||||
|
block_entity_types,
|
||||||
|
} = serde_json::from_str(include_str!("../../../extracted/blocks.json"))?;
|
||||||
|
|
||||||
let max_state_id = blocks.iter().map(|b| b.max_state_id()).max().unwrap();
|
let max_state_id = blocks.iter().map(|b| b.max_state_id()).max().unwrap();
|
||||||
|
|
||||||
|
@ -285,6 +297,19 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
||||||
})
|
})
|
||||||
.collect::<TokenStream>();
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let state_to_block_entity_type_arms = blocks
|
||||||
|
.iter()
|
||||||
|
.flat_map(|b| {
|
||||||
|
b.states.iter().filter_map(|s| {
|
||||||
|
let id = s.id;
|
||||||
|
let block_entity_type = s.block_entity_type?;
|
||||||
|
Some(quote! {
|
||||||
|
#id => Some(#block_entity_type),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
let kind_to_state_arms = blocks
|
let kind_to_state_arms = blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|b| {
|
.map(|b| {
|
||||||
|
@ -373,6 +398,69 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
||||||
})
|
})
|
||||||
.collect::<TokenStream>();
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let block_entity_kind_variants = block_entity_types
|
||||||
|
.iter()
|
||||||
|
.map(|block_entity| {
|
||||||
|
let name = ident(block_entity.name.to_pascal_case());
|
||||||
|
let doc = format!(
|
||||||
|
"The block entity type `{}` (ID {}).",
|
||||||
|
block_entity.name, block_entity.id
|
||||||
|
);
|
||||||
|
quote! {
|
||||||
|
#[doc = #doc]
|
||||||
|
#name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let block_entity_kind_from_id_arms = block_entity_types
|
||||||
|
.iter()
|
||||||
|
.map(|block_entity| {
|
||||||
|
let id = block_entity.id;
|
||||||
|
let name = ident(block_entity.name.to_pascal_case());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#id => Some(Self::#name),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let block_entity_kind_to_id_arms = block_entity_types
|
||||||
|
.iter()
|
||||||
|
.map(|block_entity| {
|
||||||
|
let id = block_entity.id;
|
||||||
|
let name = ident(block_entity.name.to_pascal_case());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
Self::#name => #id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let block_entity_kind_from_ident_arms = block_entity_types
|
||||||
|
.iter()
|
||||||
|
.map(|block_entity| {
|
||||||
|
let name = ident(block_entity.name.to_pascal_case());
|
||||||
|
let ident = &block_entity.ident;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#ident => Some(Self::#name),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
|
let block_entity_kind_to_ident_arms = block_entity_types
|
||||||
|
.iter()
|
||||||
|
.map(|block_entity| {
|
||||||
|
let name = ident(block_entity.name.to_pascal_case());
|
||||||
|
let ident = &block_entity.ident;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
Self::#name => ident_str!(#ident),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
let block_kind_count = blocks.len();
|
let block_kind_count = blocks.len();
|
||||||
|
|
||||||
let prop_names = blocks
|
let prop_names = blocks
|
||||||
|
@ -578,6 +666,18 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn block_entity_kind(self) -> Option<BlockEntityKind> {
|
||||||
|
let kind = match self.0 {
|
||||||
|
#state_to_block_entity_type_arms
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
match kind {
|
||||||
|
Some(id) => BlockEntityKind::from_id(id),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#default_block_states
|
#default_block_states
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -786,5 +886,51 @@ pub fn build() -> anyhow::Result<TokenStream> {
|
||||||
Self::from_bool(b)
|
Self::from_bool(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub enum BlockEntityKind {
|
||||||
|
#block_entity_kind_variants
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockEntityKind {
|
||||||
|
pub const fn from_id(num: u32) -> Option<Self> {
|
||||||
|
match num {
|
||||||
|
#block_entity_kind_from_id_arms
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn id(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
#block_entity_kind_to_id_arms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_ident(ident: Ident<&str>) -> Option<Self> {
|
||||||
|
match ident.as_str() {
|
||||||
|
#block_entity_kind_from_ident_arms
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ident(self) -> Ident<&'static str> {
|
||||||
|
match self {
|
||||||
|
#block_entity_kind_to_ident_arms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encode for BlockEntityKind {
|
||||||
|
fn encode(&self, w: impl Write) -> Result<()> {
|
||||||
|
VarInt(self.id() as i32).encode(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Decode<'a> for BlockEntityKind {
|
||||||
|
fn decode(r: &mut &'a [u8]) -> Result<Self> {
|
||||||
|
let id = VarInt::decode(r)?;
|
||||||
|
Self::from_id(id.0 as u32).with_context(|| format!("id {}", id.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ use std::io::Write;
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use valence_nbt::Compound;
|
||||||
|
use valence_protocol_macros::ident_str;
|
||||||
|
|
||||||
use crate::{Decode, Encode, ItemKind, Result, VarInt};
|
use crate::{Decode, Encode, Ident, ItemKind, Result, VarInt};
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/block.rs"));
|
include!(concat!(env!("OUT_DIR"), "/block.rs"));
|
||||||
|
|
||||||
|
@ -79,6 +81,18 @@ impl Decode<'_> for BlockKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct BlockEntity {
|
||||||
|
pub kind: BlockEntityKind,
|
||||||
|
pub nbt: Compound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockEntity {
|
||||||
|
pub const fn new(kind: BlockEntityKind, nbt: Compound) -> Self {
|
||||||
|
Self { kind, nbt }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
pub enum BlockFace {
|
pub enum BlockFace {
|
||||||
/// -Y
|
/// -Y
|
||||||
|
|
|
@ -40,6 +40,18 @@ pub struct Ident<S> {
|
||||||
path_start: usize,
|
path_start: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> Ident<S> {
|
||||||
|
/// Returns an Ident with the given fields
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This function does not check for the validity of the Ident.
|
||||||
|
/// For a safe version use [`Ident::new`]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub const fn new_unchecked(string: S, path_start: usize) -> Self {
|
||||||
|
Self { string, path_start }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S: AsRef<str>> Ident<S> {
|
impl<S: AsRef<str>> Ident<S> {
|
||||||
pub fn new(string: S) -> Result<Self, IdentError<S>> {
|
pub fn new(string: S) -> Result<Self, IdentError<S>> {
|
||||||
let check_namespace = |s: &str| {
|
let check_namespace = |s: &str| {
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::borrow::Cow;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use valence_nbt::Compound;
|
use valence_nbt::Compound;
|
||||||
|
|
||||||
|
use crate::block::BlockEntityKind;
|
||||||
use crate::block_pos::BlockPos;
|
use crate::block_pos::BlockPos;
|
||||||
use crate::byte_angle::ByteAngle;
|
use crate::byte_angle::ByteAngle;
|
||||||
use crate::ident::Ident;
|
use crate::ident::Ident;
|
||||||
|
@ -189,11 +190,10 @@ pub mod play {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
|
#[derive(Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
|
||||||
#[packet_id = 0x07]
|
#[packet_id = 0x07]
|
||||||
pub struct BlockEntityData {
|
pub struct BlockEntityData<'a> {
|
||||||
pub position: BlockPos,
|
pub position: BlockPos,
|
||||||
// TODO: BlockEntityKind enum?
|
pub kind: BlockEntityKind,
|
||||||
pub kind: VarInt,
|
pub data: Cow<'a, Compound>,
|
||||||
pub data: Compound,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
|
#[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)]
|
||||||
|
@ -404,7 +404,7 @@ pub mod play {
|
||||||
pub chunk_z: i32,
|
pub chunk_z: i32,
|
||||||
pub heightmaps: Compound,
|
pub heightmaps: Compound,
|
||||||
pub blocks_and_biomes: &'a [u8],
|
pub blocks_and_biomes: &'a [u8],
|
||||||
pub block_entities: Vec<ChunkDataBlockEntity>,
|
pub block_entities: Vec<ChunkDataBlockEntity<'a>>,
|
||||||
pub trust_edges: bool,
|
pub trust_edges: bool,
|
||||||
pub sky_light_mask: Vec<u64>,
|
pub sky_light_mask: Vec<u64>,
|
||||||
pub block_light_mask: Vec<u64>,
|
pub block_light_mask: Vec<u64>,
|
||||||
|
@ -421,7 +421,7 @@ pub mod play {
|
||||||
pub chunk_z: i32,
|
pub chunk_z: i32,
|
||||||
pub heightmaps: &'a Compound,
|
pub heightmaps: &'a Compound,
|
||||||
pub blocks_and_biomes: &'a [u8],
|
pub blocks_and_biomes: &'a [u8],
|
||||||
pub block_entities: &'a [ChunkDataBlockEntity],
|
pub block_entities: &'a [ChunkDataBlockEntity<'a>],
|
||||||
pub trust_edges: bool,
|
pub trust_edges: bool,
|
||||||
pub sky_light_mask: &'a [u64],
|
pub sky_light_mask: &'a [u64],
|
||||||
pub block_light_mask: &'a [u64],
|
pub block_light_mask: &'a [u64],
|
||||||
|
@ -981,7 +981,7 @@ pub mod play {
|
||||||
AwardStatistics,
|
AwardStatistics,
|
||||||
AcknowledgeBlockChange,
|
AcknowledgeBlockChange,
|
||||||
SetBlockDestroyStage,
|
SetBlockDestroyStage,
|
||||||
BlockEntityData,
|
BlockEntityData<'a>,
|
||||||
BlockAction,
|
BlockAction,
|
||||||
BlockUpdate,
|
BlockUpdate,
|
||||||
BossBar,
|
BossBar,
|
||||||
|
|
|
@ -8,6 +8,7 @@ use anyhow::Context;
|
||||||
use serde::de::Visitor;
|
use serde::de::Visitor;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use valence_nbt::Value;
|
||||||
|
|
||||||
use crate::{Decode, Encode, Ident, Result};
|
use crate::{Decode, Encode, Ident, Result};
|
||||||
|
|
||||||
|
@ -829,6 +830,15 @@ impl<'a> From<&'a Text> for Cow<'a, Text> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Text> for Value {
|
||||||
|
fn from(value: Text) -> Self {
|
||||||
|
Value::String(
|
||||||
|
serde_json::to_string(&value)
|
||||||
|
.unwrap_or_else(|err| panic!("failed to jsonify text {value:?}\n{err}")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Text {
|
impl fmt::Debug for Text {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.write_string(f)
|
self.write_string(f)
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
//! Miscellaneous type definitions used in packets.
|
//! Miscellaneous type definitions used in packets.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use bitfield_struct::bitfield;
|
use bitfield_struct::bitfield;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use valence_nbt::Compound;
|
use valence_nbt::Compound;
|
||||||
|
|
||||||
|
use crate::block::BlockEntityKind;
|
||||||
use crate::{BlockPos, Decode, Encode, Ident, ItemStack, Text, VarInt};
|
use crate::{BlockPos, Decode, Encode, Ident, ItemStack, Text, VarInt};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||||
|
@ -238,12 +241,11 @@ pub enum GameEventKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
|
||||||
pub struct ChunkDataBlockEntity {
|
pub struct ChunkDataBlockEntity<'a> {
|
||||||
pub packed_xz: i8,
|
pub packed_xz: i8,
|
||||||
pub y: i16,
|
pub y: i16,
|
||||||
// TODO: block entity kind?
|
pub kind: BlockEntityKind,
|
||||||
pub kind: VarInt,
|
pub data: Cow<'a, Compound>,
|
||||||
pub data: Compound,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode)]
|
||||||
|
|
41
crates/valence_protocol_macros/src/ident_str.rs
Normal file
41
crates/valence_protocol_macros/src/ident_str.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse2, LitStr, Result};
|
||||||
|
|
||||||
|
fn check_namespace(s: &str) -> bool {
|
||||||
|
!s.is_empty()
|
||||||
|
&& s.chars()
|
||||||
|
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-'))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_path(s: &str) -> bool {
|
||||||
|
!s.is_empty()
|
||||||
|
&& s.chars()
|
||||||
|
.all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/'))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ident_str(item: TokenStream) -> Result<TokenStream> {
|
||||||
|
let ident_lit: LitStr = parse2(item)?;
|
||||||
|
let mut ident = &ident_lit.value()[..];
|
||||||
|
|
||||||
|
let path_start = match ident.split_once(':') {
|
||||||
|
Some(("minecraft", path)) if check_path(path) => {
|
||||||
|
ident = path;
|
||||||
|
0
|
||||||
|
}
|
||||||
|
Some((namespace, path)) if check_namespace(namespace) && check_path(path) => {
|
||||||
|
namespace.len() + 1
|
||||||
|
}
|
||||||
|
None if check_path(ident) => 0,
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
ident_lit.span(),
|
||||||
|
"string cannot be parsed as ident",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
::valence_protocol::ident::Ident::new_unchecked(#ident, #path_start)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
//! This crate provides derive macros for [`Encode`], [`Decode`],
|
//! This crate provides derive macros for [`Encode`], [`Decode`],
|
||||||
//! [`EncodePacket`], and [`DecodePacket`].
|
//! [`EncodePacket`], and [`DecodePacket`].
|
||||||
|
//! It also provides the procedural macro [`ident_str!`]
|
||||||
//!
|
//!
|
||||||
//! See `valence_protocol`'s documentation for more information.
|
//! See `valence_protocol`'s documentation for more information.
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ use syn::{
|
||||||
|
|
||||||
mod decode;
|
mod decode;
|
||||||
mod encode;
|
mod encode;
|
||||||
|
mod ident_str;
|
||||||
|
|
||||||
#[proc_macro_derive(Encode, attributes(tag))]
|
#[proc_macro_derive(Encode, attributes(tag))]
|
||||||
pub fn derive_encode(item: StdTokenStream) -> StdTokenStream {
|
pub fn derive_encode(item: StdTokenStream) -> StdTokenStream {
|
||||||
|
@ -46,6 +48,14 @@ pub fn derive_decode_packet(item: StdTokenStream) -> StdTokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn ident_str(item: StdTokenStream) -> StdTokenStream {
|
||||||
|
match ident_str::ident_str(item.into()) {
|
||||||
|
Ok(tokens) => tokens.into(),
|
||||||
|
Err(e) => e.into_compile_error().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn find_packet_id_attr(attrs: &[Attribute]) -> Result<Option<LitInt>> {
|
fn find_packet_id_attr(attrs: &[Attribute]) -> Result<Option<LitInt>> {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
if let Meta::NameValue(nv) = attr.parse_meta()? {
|
if let Meta::NameValue(nv) = attr.parse_meta()? {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -76,6 +76,12 @@ public class Blocks implements Main.Extractor {
|
||||||
|
|
||||||
stateJson.add("collision_shapes", collisionShapeIdxsJson);
|
stateJson.add("collision_shapes", collisionShapeIdxsJson);
|
||||||
|
|
||||||
|
for (var blockEntity : Registries.BLOCK_ENTITY_TYPE) {
|
||||||
|
if (blockEntity.supports(state)) {
|
||||||
|
stateJson.addProperty("block_entity_type", Registries.BLOCK_ENTITY_TYPE.getRawId(blockEntity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
statesJson.add(stateJson);
|
statesJson.add(stateJson);
|
||||||
}
|
}
|
||||||
blockJson.add("states", statesJson);
|
blockJson.add("states", statesJson);
|
||||||
|
@ -83,6 +89,16 @@ public class Blocks implements Main.Extractor {
|
||||||
blocksJson.add(blockJson);
|
blocksJson.add(blockJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var blockEntitiesJson = new JsonArray();
|
||||||
|
for (var blockEntity : Registries.BLOCK_ENTITY_TYPE) {
|
||||||
|
var blockEntityJson = new JsonObject();
|
||||||
|
blockEntityJson.addProperty("id", Registries.BLOCK_ENTITY_TYPE.getRawId(blockEntity));
|
||||||
|
blockEntityJson.addProperty("ident", Registries.BLOCK_ENTITY_TYPE.getId(blockEntity).toString());
|
||||||
|
blockEntityJson.addProperty("name", Registries.BLOCK_ENTITY_TYPE.getId(blockEntity).getPath());
|
||||||
|
|
||||||
|
blockEntitiesJson.add(blockEntityJson);
|
||||||
|
}
|
||||||
|
|
||||||
var shapesJson = new JsonArray();
|
var shapesJson = new JsonArray();
|
||||||
for (var shape : shapes.keySet()) {
|
for (var shape : shapes.keySet()) {
|
||||||
var shapeJson = new JsonObject();
|
var shapeJson = new JsonObject();
|
||||||
|
@ -95,6 +111,7 @@ public class Blocks implements Main.Extractor {
|
||||||
shapesJson.add(shapeJson);
|
shapesJson.add(shapeJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
topLevelJson.add("block_entity_types", blockEntitiesJson);
|
||||||
topLevelJson.add("shapes", shapesJson);
|
topLevelJson.add("shapes", shapesJson);
|
||||||
topLevelJson.add("blocks", blocksJson);
|
topLevelJson.add("blocks", blocksJson);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue