From 0364b1763a43de08a6cce09bd4406551ddccb1f8 Mon Sep 17 00:00:00 2001 From: StackDoubleFlow Date: Mon, 19 Aug 2024 15:52:46 -0400 Subject: [PATCH] Implement Sponge v3 schematic loading This also makes reading block entity nbt tags from items more flexible (i.e. you don't need the Id field or minecraft: prefix anymore) --- crates/blocks/src/block_entities.rs | 15 ++-- crates/core/src/interaction.rs | 19 ++++- crates/core/src/plot/mod.rs | 2 +- crates/core/src/plot/worldedit/schematic.rs | 92 ++++++++++++++++++--- 4 files changed, 103 insertions(+), 25 deletions(-) diff --git a/crates/blocks/src/block_entities.rs b/crates/blocks/src/block_entities.rs index a6110943..2d17589d 100644 --- a/crates/blocks/src/block_entities.rs +++ b/crates/blocks/src/block_entities.rs @@ -151,26 +151,25 @@ impl BlockEntity { }) } - pub fn from_nbt(nbt: &HashMap) -> Option { + pub fn from_nbt(id: &str, nbt: &HashMap) -> Option { use nbt::Value; - let id = nbt_unwrap_val!(&nbt.get("Id").or_else(|| nbt.get("id"))?, Value::String); - match id.as_ref() { - "minecraft:comparator" => Some(BlockEntity::Comparator { + match id.trim_start_matches("minecraft:") { + "comparator" => Some(BlockEntity::Comparator { output_strength: *nbt_unwrap_val!(&nbt["OutputSignal"], Value::Int) as u8, }), - "minecraft:furnace" => BlockEntity::load_container( + "furnace" => BlockEntity::load_container( nbt_unwrap_val!(&nbt["Items"], Value::List), ContainerType::Furnace, ), - "minecraft:barrel" => BlockEntity::load_container( + "barrel" => BlockEntity::load_container( nbt_unwrap_val!(&nbt["Items"], Value::List), ContainerType::Barrel, ), - "minecraft:hopper" => BlockEntity::load_container( + "hopper" => BlockEntity::load_container( nbt_unwrap_val!(&nbt["Items"], Value::List), ContainerType::Hopper, ), - "minecraft:sign" => { + "sign" => { let sign = if nbt.contains_key("Text1") { // This is the pre-1.20 encoding SignBlockEntity { diff --git a/crates/core/src/interaction.rs b/crates/core/src/interaction.rs index 1623a653..a14c5223 100644 --- a/crates/core/src/interaction.rs +++ b/crates/core/src/interaction.rs @@ -8,6 +8,7 @@ use mchprs_blocks::items::{Item, ItemStack}; use mchprs_blocks::{BlockFace, BlockPos, SignType}; use mchprs_network::packets::clientbound::{COpenSignEditor, ClientBoundPacket}; use mchprs_redstone::{self as redstone, noteblock}; +use mchprs_utils::nbt_unwrap_val; use mchprs_world::{TickPriority, World}; pub fn on_use( @@ -279,6 +280,18 @@ pub fn get_state_for_placement( } } +fn read_block_entity_tag(nbt: &nbt::Blob, block_id: &str) -> Option { + if let nbt::Value::Compound(compound) = &nbt["BlockEntityTag"] { + let id = match nbt.get("Id").or_else(|| nbt.get("id")) { + Some(id) => nbt_unwrap_val!(id, nbt::Value::String), + None => block_id, + }; + return BlockEntity::from_nbt(id, compound); + } + + None +} + pub fn place_in_world( block: Block, world: &mut impl World, @@ -287,10 +300,8 @@ pub fn place_in_world( ) { if block.has_block_entity() { if let Some(nbt) = nbt { - if let nbt::Value::Compound(compound) = &nbt["BlockEntityTag"] { - if let Some(block_entity) = BlockEntity::from_nbt(compound) { - world.set_block_entity(pos, block_entity); - } + if let Some(block_entity) = read_block_entity_tag(nbt, block.get_name()) { + world.set_block_entity(pos, block_entity); } }; } diff --git a/crates/core/src/plot/mod.rs b/crates/core/src/plot/mod.rs index b6857b3b..b86d0720 100644 --- a/crates/core/src/plot/mod.rs +++ b/crates/core/src/plot/mod.rs @@ -7,11 +7,11 @@ mod scoreboard; pub mod worldedit; use crate::config::CONFIG; +use crate::interaction; use crate::interaction::UseOnBlockContext; use crate::player::{EntityId, Gamemode, PacketSender, Player, PlayerPos}; use crate::server::{BroadcastMessage, Message, PrivMessage}; use crate::utils::HyphenatedUUID; -use crate::interaction; use anyhow::Error; use bus::BusReader; use mchprs_blocks::block_entities::BlockEntity; diff --git a/crates/core/src/plot/worldedit/schematic.rs b/crates/core/src/plot/worldedit/schematic.rs index d7fb5030..e66cc75a 100644 --- a/crates/core/src/plot/worldedit/schematic.rs +++ b/crates/core/src/plot/worldedit/schematic.rs @@ -44,28 +44,49 @@ fn parse_block(str: &str) -> Option { } pub fn load_schematic(file_name: &str) -> Result { - use nbt::Value; - let mut file = File::open("./schems/".to_owned() + file_name)?; let nbt = nbt::Blob::from_gzip_reader(&mut file)?; - let size_x = nbt_as!(nbt["Width"], Value::Short) as u32; - let size_z = nbt_as!(nbt["Length"], Value::Short) as u32; - let size_y = nbt_as!(nbt["Height"], Value::Short) as u32; + + let root = if nbt.content.contains_key("Schematic") { + nbt_as!(&nbt["Schematic"], nbt::Value::Compound) + } else { + &nbt.content + }; + + let version = nbt_as!(root["Version"], nbt::Value::Int); + match version { + 2 | 3 => load_schematic_sponge(root, version), + _ => bail!("unknown schematic version: {}", version), + } +} + +fn read_block_container( + nbt: &nbt::Map, + version: i32, + size_x: u32, + size_y: u32, + size_z: u32, +) -> Result<(PalettedBitBuffer, FxHashMap)> { + use nbt::Value; + let nbt_palette = nbt_as!(&nbt["Palette"], Value::Compound); - let metadata = nbt_as!(&nbt["Metadata"], Value::Compound); - let offset_x = -nbt_as!(metadata["WEOffsetX"], Value::Int); - let offset_y = -nbt_as!(metadata["WEOffsetY"], Value::Int); - let offset_z = -nbt_as!(metadata["WEOffsetZ"], Value::Int); let mut palette: FxHashMap = FxHashMap::default(); for (k, v) in nbt_palette { let id = *nbt_as!(v, Value::Int) as u32; let block = parse_block(k).with_context(|| format!("error parsing block: {}", k))?; palette.insert(id, block.get_id()); } - let blocks: Vec = nbt_as!(&nbt["BlockData"], Value::ByteArray) + + let data_name = match version { + 2 => "BlockData", + 3 => "Data", + _ => unreachable!(), + }; + let blocks: Vec = nbt_as!(&nbt[data_name], Value::ByteArray) .iter() .map(|b| *b as u8) .collect(); + let mut data = PalettedBitBuffer::new((size_x * size_y * size_z) as usize, 9); let mut i = 0; for y_offset in (0..size_y).map(|y| y * size_z * size_x) { @@ -96,10 +117,57 @@ pub fn load_schematic(file_name: &str) -> Result { y: pos_array[1], z: pos_array[2], }; - if let Some(parsed) = BlockEntity::from_nbt(val) { + let id = nbt_as!(&val.get("Id").unwrap_or_else(|| &nbt["id"]), Value::String); + let data = match version { + 2 => val, + 3 => nbt_as!(&val["Data"], Value::Compound), + _ => unreachable!(), + }; + if let Some(parsed) = BlockEntity::from_nbt(id, data) { parsed_block_entities.insert(pos, parsed); } } + + Ok((data, parsed_block_entities)) +} + +fn load_schematic_sponge( + nbt: &nbt::Map, + version: i32, +) -> Result { + use nbt::Value; + + let size_x = nbt_as!(nbt["Width"], Value::Short) as u32; + let size_z = nbt_as!(nbt["Length"], Value::Short) as u32; + let size_y = nbt_as!(nbt["Height"], Value::Short) as u32; + + let (offset_x, offset_y, offset_z) = match version { + 2 => { + let metadata = nbt_as!(&nbt["Metadata"], Value::Compound); + ( + -nbt_as!(metadata["WEOffsetX"], Value::Int), + -nbt_as!(metadata["WEOffsetY"], Value::Int), + -nbt_as!(metadata["WEOffsetZ"], Value::Int), + ) + } + 3 => { + let offset_array = nbt_as!(&nbt["Offset"], Value::IntArray); + (-offset_array[0], -offset_array[1], -offset_array[2]) + } + _ => unreachable!(), + }; + + let (data, block_entities) = read_block_container( + match version { + 2 => nbt, + 3 => nbt_as!(&nbt["Blocks"], Value::Compound), + _ => unreachable!(), + }, + version, + size_x, + size_y, + size_z, + )?; Ok(WorldEditClipboard { size_x, size_y, @@ -108,7 +176,7 @@ pub fn load_schematic(file_name: &str) -> Result { offset_y, offset_z, data, - block_entities: parsed_block_entities, + block_entities, }) }