Skip to content

Commit

Permalink
Implement Sponge v3 schematic loading
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
StackDoubleFlow committed Aug 19, 2024
1 parent ff473fa commit 0364b17
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 25 deletions.
15 changes: 7 additions & 8 deletions crates/blocks/src/block_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,26 +151,25 @@ impl BlockEntity {
})
}

pub fn from_nbt(nbt: &HashMap<String, nbt::Value>) -> Option<BlockEntity> {
pub fn from_nbt(id: &str, nbt: &HashMap<String, nbt::Value>) -> Option<BlockEntity> {
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 {
Expand Down
19 changes: 15 additions & 4 deletions crates/core/src/interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -279,6 +280,18 @@ pub fn get_state_for_placement(
}
}

fn read_block_entity_tag(nbt: &nbt::Blob, block_id: &str) -> Option<BlockEntity> {
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,
Expand All @@ -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);
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/plot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
92 changes: 80 additions & 12 deletions crates/core/src/plot/worldedit/schematic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,49 @@ fn parse_block(str: &str) -> Option<Block> {
}

pub fn load_schematic(file_name: &str) -> Result<WorldEditClipboard> {
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<String, nbt::Value>,
version: i32,
size_x: u32,
size_y: u32,
size_z: u32,
) -> Result<(PalettedBitBuffer, FxHashMap<BlockPos, BlockEntity>)> {
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<u32, u32> = 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<u8> = nbt_as!(&nbt["BlockData"], Value::ByteArray)

let data_name = match version {
2 => "BlockData",
3 => "Data",
_ => unreachable!(),
};
let blocks: Vec<u8> = 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) {
Expand Down Expand Up @@ -96,10 +117,57 @@ pub fn load_schematic(file_name: &str) -> Result<WorldEditClipboard> {
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<String, nbt::Value>,
version: i32,
) -> Result<WorldEditClipboard> {
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,
Expand All @@ -108,7 +176,7 @@ pub fn load_schematic(file_name: &str) -> Result<WorldEditClipboard> {
offset_y,
offset_z,
data,
block_entities: parsed_block_entities,
block_entities,
})
}

Expand Down

0 comments on commit 0364b17

Please sign in to comment.