a bare-bones limbo server in rust (mirror of https://github.com/xoogware/crawlspace)

feat(world): basic block entity parsing from anvil

brwr.dev f22d95a3 b2ea8556

verified
+413 -5
+405
src/world/block_entity.rs
··· 1 + /* 2 + * Copyright (c) 2024 Andrew Brower. 3 + * This file is part of Crawlspace. 4 + * 5 + * Crawlspace is free software: you can redistribute it and/or 6 + * modify it under the terms of the GNU Affero General Public 7 + * License as published by the Free Software Foundation, either 8 + * version 3 of the License, or (at your option) any later version. 9 + * 10 + * Crawlspace is distributed in the hope that it will be useful, 11 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 + * Affero General Public License for more details. 14 + * 15 + * You should have received a copy of the GNU Affero General Public 16 + * License along with Crawlspace. If not, see 17 + * <https://www.gnu.org/licenses/>. 18 + */ 19 + 20 + use std::collections::HashMap; 21 + 22 + use color_eyre::eyre::{bail, Error, Result}; 23 + use fastnbt::Value; 24 + 25 + macro_rules! get_tag { 26 + ($data:expr, $tag_kind:path, $tag_name:literal) => {{ 27 + { 28 + let tag = $data.get($tag_name); 29 + 30 + let Some(tag) = tag else { 31 + bail!("Unable to find tag {}: {:?}", $tag_name, $data); 32 + }; 33 + 34 + match tag { 35 + $tag_kind(v) => v.to_owned(), 36 + e => bail!( 37 + "Improper tag kind found searching for {}: {:?}", 38 + $tag_name, 39 + e 40 + ), 41 + } 42 + } 43 + }}; 44 + } 45 + 46 + macro_rules! get_tag_opt { 47 + ($data:expr, $tag_kind:path, $tag_name:literal) => {{ 48 + let tag = $data.get($tag_name); 49 + 50 + match tag { 51 + Some($tag_kind(v)) => Some(v.to_owned()), 52 + _ => None, 53 + } 54 + }}; 55 + } 56 + 57 + pub struct BlockEntity { 58 + pub id: String, 59 + pub keep_packed: bool, 60 + pub x: i32, 61 + pub y: i32, 62 + pub z: i32, 63 + pub other_tags: Box<dyn BlockEntityTags>, 64 + } 65 + 66 + impl BlockEntity { 67 + pub fn try_parse(value: Value) -> Result<Self> { 68 + let Value::Compound(data) = value else { 69 + bail!( 70 + "try_parse was called with a value that is not a compound: {:?}", 71 + value 72 + ); 73 + }; 74 + 75 + let id = get_tag!(data, Value::String, "id"); 76 + trace!("Parsing {id}"); 77 + 78 + let other_tags: Box<dyn BlockEntityTags> = match id.as_str() { 79 + "minecraft:barrel" => Box::new(BarrelBlockEntityTags::try_parse(&data)?), 80 + "minecraft:brewing_stand" => Box::new(BrewingStandBlockEntityTags::try_parse(&data)?), 81 + "minecraft:campfire" => Box::new(CampfireBlockEntityTags::try_parse(&data)?), 82 + "minecraft:chiseled_bookshelf" => { 83 + Box::new(ChiseledBookshelfBlockEntityTags::try_parse(&data)?) 84 + } 85 + "minecraft:sign" => Box::new(SignBlockEntityTags::try_parse(&data)?), 86 + "minecraft:skull" => Box::new(SkullBlockEntityTags::try_parse(&data)?), 87 + t => unimplemented!("Got block with tag {t}, but it isn't implemented"), 88 + }; 89 + 90 + Ok(Self { 91 + id, 92 + keep_packed: get_tag!(data, Value::String, "keepPacked") == "true", 93 + x: get_tag!(data, Value::Int, "x"), 94 + y: get_tag!(data, Value::Int, "y"), 95 + z: get_tag!(data, Value::Int, "z"), 96 + other_tags, 97 + }) 98 + } 99 + } 100 + 101 + pub struct BlockEntityItem { 102 + pub slot: i8, 103 + pub id: String, 104 + pub count: i32, 105 + } 106 + 107 + pub trait BlockEntityTags { 108 + fn try_parse(from: &HashMap<String, Value>) -> Result<Self> 109 + where 110 + Self: Sized; 111 + } 112 + 113 + pub struct SignBlockEntityTags { 114 + pub is_waxed: bool, 115 + pub front_text: SignText, 116 + pub back_text: SignText, 117 + } 118 + 119 + pub struct SignText { 120 + pub has_glowing_text: bool, 121 + pub color: String, 122 + pub messages: Vec<String>, 123 + } 124 + 125 + fn byte_to_bool(byte: i8) -> bool { 126 + matches!(byte, 0) 127 + } 128 + 129 + impl BlockEntityTags for SignBlockEntityTags { 130 + fn try_parse(from: &HashMap<String, Value>) -> Result<Self> { 131 + let front_text_tag = get_tag!(from, Value::Compound, "front_text"); 132 + let back_text_tag = get_tag!(from, Value::Compound, "back_text"); 133 + 134 + let front_messages = get_tag!(front_text_tag, Value::List, "messages") 135 + .iter() 136 + .map(|v| match v { 137 + Value::String(s) => s.to_owned(), 138 + _ => "Unknown".to_owned(), 139 + }) 140 + .collect(); 141 + 142 + let back_messages = get_tag!(back_text_tag, Value::List, "messages") 143 + .iter() 144 + .map(|v| match v { 145 + Value::String(s) => s.to_owned(), 146 + _ => "Unknown".to_owned(), 147 + }) 148 + .collect(); 149 + 150 + Ok(Self { 151 + is_waxed: byte_to_bool(get_tag!(from, Value::Byte, "is_waxed")), 152 + front_text: SignText { 153 + has_glowing_text: byte_to_bool(get_tag!( 154 + front_text_tag, 155 + Value::Byte, 156 + "has_glowing_text" 157 + )), 158 + color: get_tag!(front_text_tag, Value::String, "color").to_owned(), 159 + messages: front_messages, 160 + }, 161 + back_text: SignText { 162 + has_glowing_text: byte_to_bool(get_tag!( 163 + back_text_tag, 164 + Value::Byte, 165 + "has_glowing_text" 166 + )), 167 + color: get_tag!(back_text_tag, Value::String, "color").to_owned(), 168 + messages: back_messages, 169 + }, 170 + }) 171 + } 172 + } 173 + 174 + pub struct CampfireBlockEntityTags { 175 + cooking_times: Vec<i32>, 176 + cooking_total_times: Vec<i32>, 177 + items: Vec<BlockEntityItem>, 178 + } 179 + 180 + impl BlockEntityTags for CampfireBlockEntityTags { 181 + fn try_parse(from: &HashMap<String, Value>) -> Result<Self> 182 + where 183 + Self: Sized, 184 + { 185 + let cooking_times = get_tag!(from, Value::List, "CookingTimes") 186 + .iter() 187 + .map(|v| match v { 188 + Value::Int(s) => s.to_owned(), 189 + _ => 0, 190 + }) 191 + .collect(); 192 + 193 + let cooking_total_times = get_tag!(from, Value::List, "CookingTotalTimes") 194 + .iter() 195 + .map(|v| match v { 196 + Value::Int(s) => s.to_owned(), 197 + _ => 0, 198 + }) 199 + .collect(); 200 + 201 + let items = { 202 + let item_compounds = get_tag!(from, Value::List, "Items"); 203 + let mut items = Vec::with_capacity(item_compounds.len()); 204 + for c in item_compounds { 205 + match c { 206 + Value::Compound(c) => items.push(BlockEntityItem { 207 + slot: get_tag!(c, Value::Byte, "Slot"), 208 + id: get_tag!(c, Value::String, "id"), 209 + count: get_tag!(c, Value::Int, "count"), 210 + }), 211 + t => bail!("Wrong tag type parsing item: {:?}", t), 212 + } 213 + } 214 + items 215 + }; 216 + 217 + Ok(Self { 218 + cooking_times, 219 + cooking_total_times, 220 + items, 221 + }) 222 + } 223 + } 224 + 225 + pub struct ChiseledBookshelfBlockEntityTags { 226 + items: Vec<BlockEntityItem>, 227 + last_interacted_slot: i32, 228 + } 229 + 230 + impl BlockEntityTags for ChiseledBookshelfBlockEntityTags { 231 + fn try_parse(from: &HashMap<String, Value>) -> Result<Self> 232 + where 233 + Self: Sized, 234 + { 235 + let items = { 236 + let item_compounds = get_tag!(from, Value::List, "Items"); 237 + let mut items = Vec::with_capacity(item_compounds.len()); 238 + for c in item_compounds { 239 + match c { 240 + Value::Compound(c) => items.push(BlockEntityItem { 241 + slot: get_tag!(c, Value::Byte, "Slot"), 242 + id: get_tag!(c, Value::String, "id"), 243 + count: get_tag!(c, Value::Int, "count"), 244 + }), 245 + t => bail!("Wrong tag type parsing item: {:?}", t), 246 + } 247 + } 248 + items 249 + }; 250 + 251 + Ok(Self { 252 + items, 253 + last_interacted_slot: get_tag!(from, Value::Int, "last_interacted_slot"), 254 + }) 255 + } 256 + } 257 + 258 + pub struct BarrelBlockEntityTags { 259 + custom_name: Option<String>, 260 + items: Vec<BlockEntityItem>, 261 + } 262 + 263 + impl BlockEntityTags for BarrelBlockEntityTags { 264 + fn try_parse(from: &HashMap<String, Value>) -> Result<Self> 265 + where 266 + Self: Sized, 267 + { 268 + let items = { 269 + let item_compounds = get_tag!(from, Value::List, "Items"); 270 + let mut items = Vec::with_capacity(item_compounds.len()); 271 + for c in item_compounds { 272 + match c { 273 + Value::Compound(c) => items.push(BlockEntityItem { 274 + slot: get_tag!(c, Value::Byte, "Slot"), 275 + id: get_tag!(c, Value::String, "id"), 276 + count: get_tag!(c, Value::Int, "count"), 277 + }), 278 + t => bail!("Wrong tag type parsing item: {:?}", t), 279 + } 280 + } 281 + items 282 + }; 283 + 284 + Ok(Self { 285 + custom_name: get_tag_opt!(from, Value::String, "CustomName"), 286 + items, 287 + }) 288 + } 289 + } 290 + 291 + pub struct BrewingStandBlockEntityTags { 292 + brew_time: i16, 293 + custom_name: Option<String>, 294 + fuel: i8, 295 + items: Vec<BlockEntityItem>, 296 + lock: String, 297 + } 298 + 299 + impl BlockEntityTags for BrewingStandBlockEntityTags { 300 + fn try_parse(from: &HashMap<String, Value>) -> Result<Self> 301 + where 302 + Self: Sized, 303 + { 304 + let items = { 305 + let item_compounds = get_tag!(from, Value::List, "Items"); 306 + let mut items = Vec::with_capacity(item_compounds.len()); 307 + for c in item_compounds { 308 + match c { 309 + Value::Compound(c) => items.push(BlockEntityItem { 310 + slot: get_tag!(c, Value::Byte, "Slot"), 311 + id: get_tag!(c, Value::String, "id"), 312 + count: get_tag!(c, Value::Int, "count"), 313 + }), 314 + t => bail!("Wrong tag type parsing item: {:?}", t), 315 + } 316 + } 317 + items 318 + }; 319 + 320 + Ok(Self { 321 + brew_time: get_tag!(from, Value::Short, "BrewTime"), 322 + custom_name: get_tag_opt!(from, Value::String, "CustomName"), 323 + fuel: get_tag!(from, Value::Byte, "Fuel"), 324 + items, 325 + lock: get_tag!(from, Value::String, "Lock"), 326 + }) 327 + } 328 + } 329 + 330 + pub struct SkullBlockEntityTags { 331 + pub custom_name: Option<String>, 332 + pub note_block_sound: Option<String>, 333 + pub profile: SkullProfile, 334 + } 335 + 336 + pub enum SkullProfile { 337 + String(String), 338 + Compound { 339 + name: String, 340 + id: Vec<i32>, 341 + properties: Option<Vec<SkullProperty>>, 342 + }, 343 + } 344 + 345 + pub struct SkullProperty { 346 + pub name: String, 347 + pub value: String, 348 + pub signature: Option<String>, 349 + } 350 + 351 + impl BlockEntityTags for SkullBlockEntityTags { 352 + fn try_parse(from: &HashMap<String, Value>) -> Result<Self> 353 + where 354 + Self: Sized, 355 + { 356 + let profile = match get_tag_opt!(from, Value::String, "profile") { 357 + Some(s) => SkullProfile::String(s), 358 + None => match get_tag_opt!(from, Value::Compound, "profile") { 359 + None => bail!("Profile not present for skull"), 360 + Some(c) => { 361 + let id = get_tag!(from, Value::List, "id") 362 + .iter() 363 + .map(|v| match v { 364 + Value::Int(s) => s.to_owned(), 365 + _ => 0, 366 + }) 367 + .collect(); 368 + 369 + let properties = { 370 + match get_tag_opt!(from, Value::List, "properties") { 371 + None => None, 372 + Some(property_compounds) => { 373 + let mut properties = Vec::with_capacity(property_compounds.len()); 374 + for c in property_compounds { 375 + match c { 376 + Value::Compound(s) => properties.push(SkullProperty { 377 + name: get_tag!(s, Value::String, "name"), 378 + value: get_tag!(s, Value::String, "value"), 379 + signature: get_tag_opt!(s, Value::String, "signature"), 380 + }), 381 + t => bail!("Wrong tag type parsing item: {:?}", t), 382 + } 383 + } 384 + 385 + Some(properties) 386 + } 387 + } 388 + }; 389 + 390 + SkullProfile::Compound { 391 + name: get_tag!(c, Value::String, "name"), 392 + id, 393 + properties, 394 + } 395 + } 396 + }, 397 + }; 398 + 399 + Ok(Self { 400 + custom_name: get_tag_opt!(from, Value::String, "custom_name"), 401 + note_block_sound: get_tag_opt!(from, Value::String, "note_block_sound"), 402 + profile, 403 + }) 404 + } 405 + }
+8 -5
src/world/mod.rs
··· 17 17 * <https://www.gnu.org/licenses/>. 18 18 */ 19 19 20 - use std::{ 21 - collections::HashMap, 22 - fs::File, 23 - path::Path, 24 - }; 20 + use std::{collections::HashMap, fs::File, path::Path}; 25 21 22 + use block_entity::BlockEntity; 26 23 use color_eyre::eyre::Result; 27 24 use fastanvil::Region; 28 25 use rayon::prelude::*; 29 26 use serde::Deserialize; 30 27 28 + mod block_entity; 31 29 pub mod blocks; 32 30 33 31 #[derive(Clone, Debug)] ··· 48 46 #[serde(rename = "LastUpdate")] 49 47 pub _last_update: f64, 50 48 pub sections: Vec<Section>, 49 + pub block_entities: Vec<fastnbt::Value>, 51 50 } 52 51 53 52 #[derive(Clone, Debug, Deserialize)] ··· 138 137 139 138 if (-10..10).contains(&parsed.x_pos) && (-10..10).contains(&parsed.z_pos) { 140 139 parsed.sections.sort_by_key(|c| c.y); 140 + 141 + parsed.block_entities.clone().into_iter().for_each(|e| { 142 + let _ = BlockEntity::try_parse(e); 143 + }); 141 144 142 145 debug!( 143 146 "Successfully parsed chunk at {}, {}",