142 lines
3.8 KiB
Rust
142 lines
3.8 KiB
Rust
use std::io::Read;
|
|
|
|
use anyhow::Context;
|
|
use swarmbot_interfaces::types::BlockState;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::blocks::Vec3;
|
|
|
|
/// The `WorldEdit` schematic format
|
|
/// <https://minecraft.fandom.com/wiki/Schematic_file_format>
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
#[serde(rename_all = "PascalCase")]
|
|
pub struct Schematic {
|
|
pub width: i16,
|
|
pub height: i16,
|
|
pub length: i16,
|
|
materials: String,
|
|
blocks: Vec<i8>,
|
|
add_blocks: Option<Vec<i8>>,
|
|
data: Vec<i8>,
|
|
w_e_origin_x: Option<i32>,
|
|
w_e_origin_y: Option<i32>,
|
|
w_e_origin_z: Option<i32>,
|
|
w_e_offset_x: Option<i32>,
|
|
w_e_offset_y: Option<i32>,
|
|
w_e_offset_z: Option<i32>,
|
|
}
|
|
|
|
impl Schematic {
|
|
#[allow(unused)]
|
|
pub const fn volume(&self) -> u64 {
|
|
let v = (self.width as i64) * (self.height as i64) * (self.length as i64);
|
|
v.abs_diff(0)
|
|
}
|
|
|
|
#[allow(unused)]
|
|
pub fn load(reader: &mut impl Read) -> anyhow::Result<Self> {
|
|
let res: Result<Self, _> =
|
|
nbt::from_gzip_reader(reader).context("could not load schematic");
|
|
|
|
res
|
|
}
|
|
|
|
#[allow(unused)]
|
|
pub fn is_valid(&self) -> bool {
|
|
self.volume() == self.blocks.len() as u64
|
|
}
|
|
|
|
#[allow(unused)]
|
|
pub fn origin(&self) -> Option<Vec3> {
|
|
match (self.w_e_origin_x, self.w_e_origin_y, self.w_e_origin_z) {
|
|
(Some(x), Some(y), Some(z)) => Some(Vec3::new(x, y, z)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[allow(unused)]
|
|
pub fn offset(&self) -> Option<Vec3> {
|
|
match (self.w_e_offset_x, self.w_e_offset_y, self.w_e_offset_z) {
|
|
(Some(x), Some(y), Some(z)) => Some(Vec3::new(x, y, z)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[allow(unused, clippy::unwrap_used)]
|
|
pub fn width(&self) -> u64 {
|
|
u64::try_from(self.width).unwrap()
|
|
}
|
|
|
|
#[allow(unused, clippy::unwrap_used)]
|
|
pub fn height(&self) -> u64 {
|
|
u64::try_from(self.width).unwrap()
|
|
}
|
|
|
|
#[allow(unused, clippy::unwrap_used)]
|
|
pub fn length(&self) -> u64 {
|
|
u64::try_from(self.length).unwrap()
|
|
}
|
|
|
|
#[allow(unused, clippy::unwrap_used, clippy::indexing_slicing)]
|
|
pub fn blocks(&self) -> impl Iterator<Item = (Vec3, BlockState)> + '_ {
|
|
let origin = self.origin().unwrap_or_default();
|
|
|
|
(0..self.volume()).map(move |idx| {
|
|
let x = idx % self.width();
|
|
|
|
let leftover = idx / self.width();
|
|
let z = leftover % self.length();
|
|
|
|
let y = leftover / self.length();
|
|
|
|
let location = Vec3::new(x as i32, y as i32, z as i32) + origin;
|
|
|
|
let id = self.blocks[idx as usize].abs_diff(0);
|
|
let data = self.data[idx as usize].abs_diff(0);
|
|
let state = BlockState::from(u32::from(id), u16::from(data));
|
|
|
|
(location, state)
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::{collections::HashMap, fs::OpenOptions};
|
|
|
|
use more_asserts::*;
|
|
|
|
use crate::blocks::Vec3;
|
|
|
|
use super::Schematic;
|
|
|
|
#[test]
|
|
fn test_load() {
|
|
let mut reader = OpenOptions::new()
|
|
.read(true)
|
|
.open("test-data/parkour.schematic")
|
|
.unwrap();
|
|
|
|
let schematic = Schematic::load(&mut reader).unwrap();
|
|
|
|
assert!(schematic.is_valid());
|
|
|
|
let origin = schematic.origin().unwrap_or_default();
|
|
|
|
let mut map = HashMap::new();
|
|
for (loc, state) in schematic.blocks() {
|
|
assert_ge!(loc.x, origin.x);
|
|
assert_lt!(loc.x, origin.x + schematic.width as i32);
|
|
|
|
assert_ge!(loc.y, origin.y);
|
|
assert_lt!(loc.y, origin.y + schematic.height as i32);
|
|
|
|
assert_ge!(loc.z, origin.z);
|
|
assert_lt!(loc.z, origin.z + schematic.length as i32);
|
|
map.insert(loc, state);
|
|
}
|
|
|
|
let stained_glass = map[&Vec3::new(-162, 81, -357)];
|
|
assert_eq!(stained_glass.id(), 95);
|
|
}
|
|
}
|