vintage_schematics/formats/
internal.rs1use serde::{Deserialize, Serialize};
5
6use crate::{
7 Map,
8 convert::{
9 block::{VS_FLOWER_POT_CODE, convert_block},
10 entities::Sign,
11 },
12 entity::{ItemClass, ItemStack, Value},
13 formats::{
14 Settings,
15 common::{MinecraftBlockEntity, Xyz},
16 },
17};
18
19pub type PropertyMap = Map<String, String>;
21
22pub trait PropertyMapExt {
23 fn get_facing_or_north(&self) -> &str;
24
25 fn get_u8(&self, key: &str) -> Option<u8>;
26
27 fn is_true(&self, key: &str) -> bool;
28}
29
30impl PropertyMapExt for PropertyMap {
31 fn get_facing_or_north(&self) -> &str { self.get("facing").map_or("north", String::as_str) }
32
33 fn get_u8(&self, key: &str) -> Option<u8> { self.get(key).and_then(|v| v.parse().ok()) }
34
35 fn is_true(&self, key: &str) -> bool { self.get(key).is_some_and(|v| v == "true") }
36}
37
38pub type EntityMap = Map<String, Value>;
40
41#[derive(Debug, Clone, Serialize)]
45pub struct Internal {
46 pub block_codes: BlockCodes,
48
49 pub blocks: Vec<Block>,
51
52 pub tile_entities: Vec<MinecraftBlockEntity>,
54
55 pub size: Xyz,
57}
58
59#[derive(Debug, Clone, Serialize)]
61pub enum BlockCodes {
62 VintageStory {
63 codes: Vec<VintageStoryBlockCode>,
64 properties: Vec<EntityMap>,
65 },
66 Minecraft(Vec<MinecraftBlockCode>),
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
70#[serde(rename_all = "PascalCase")]
71pub struct MinecraftBlockCode {
72 pub name: String,
73 #[serde(default)]
74 pub properties: PropertyMap,
75}
76
77#[derive(Debug, Clone, Serialize)]
78pub struct VintageStoryBlockCode {
79 pub name: String,
83
84 pub properties: Vec<usize>,
86}
87
88#[derive(Debug, Clone, Serialize)]
89pub struct Block {
90 pub id: usize,
91 pub position: Xyz,
92}
93
94impl Internal {
95 pub fn convert_to_vintage_story(&mut self, settings: &Settings) {
97 let (mut new_blocks, mut new_properties) = if let BlockCodes::Minecraft(block_codes) = &self.block_codes {
98 let mut new_blocks = Vec::with_capacity(block_codes.len());
99 let mut new_properties = Vec::new();
100 let mut extra_blocks = Vec::new();
101
102 for block in block_codes {
103 let (code, properties) = match convert_block(block, settings) {
104 Some(result) => result,
105 None if settings.replace_missing => (String::from("game:leaves-placed-birch"), None),
106 None => (String::from("game:air"), None), };
108
109 #[allow(clippy::option_if_let_else)]
110 let properties = if let Some(mut properties) = properties {
111 let existing = new_properties.iter().position(|p| *p == properties);
113
114 Some(
115 if let Some(existing) = existing
116 && !properties.contains_key("unique!")
117 {
118 existing
120 } else {
121 if code == VS_FLOWER_POT_CODE
122 && let Some(Value::String(flower)) = properties.remove("flower")
123 {
124 Self::convert_potted_flower(block_codes, &new_blocks, &mut extra_blocks, &mut properties, flower);
125 }
126 new_properties.push(properties);
128 new_properties.len() - 1
129 },
130 )
131 } else {
132 None
133 };
134
135 let properties = properties.map(|p| vec![p]).unwrap_or_default();
136 let block = VintageStoryBlockCode { name: code, properties };
137 new_blocks.push(block);
138 }
139
140 new_blocks.extend(extra_blocks.into_iter().map(|name| VintageStoryBlockCode {
141 name,
142 properties: Vec::new(),
143 }));
144 (new_blocks, new_properties)
145 } else {
146 return;
148 };
149
150 self.convert_tile_entities(&mut new_blocks, &mut new_properties);
151
152 self.block_codes = BlockCodes::VintageStory {
153 codes: new_blocks,
154 properties: new_properties,
155 };
156 }
157
158 fn convert_potted_flower(
159 block_codes: &[MinecraftBlockCode],
160 new_blocks: &[VintageStoryBlockCode],
161 extra_blocks: &mut Vec<String>,
162 properties: &mut EntityMap,
163 flower: String,
164 ) {
165 #[allow(clippy::option_if_let_else)]
171 let flower_id = if let Some(id) = new_blocks.iter().position(|b: &VintageStoryBlockCode| b.name == flower) {
172 id
173 } else if let Some(id) = extra_blocks.iter().position(|e| e == &flower) {
174 block_codes.len() + id
179 } else {
180 extra_blocks.push(flower);
183 block_codes.len() + extra_blocks.len() - 1
184 };
185
186 let item_stack = ItemStack {
188 class: ItemClass::Block,
189 id: i32::try_from(flower_id).unwrap_or_default(),
190 stack_size: 1,
191 stack_attributes: Map::new(),
192 };
193
194 properties.insert(String::from("meshAngle"), Value::Float(0.0));
195 properties.insert(
196 String::from("inventory"),
197 Value::Tree(Map::from([
198 (String::from("qslots"), Value::Integer(1)),
199 (
200 String::from("slots"),
201 Value::Tree(Map::from([(String::from("0"), Value::ItemStack(Some(item_stack)))])),
202 ),
203 ])),
204 );
205 }
206
207 fn convert_tile_entities(&self, new_blocks: &mut [VintageStoryBlockCode], new_properties: &mut Vec<EntityMap>) {
208 for tile_entity in &self.tile_entities {
210 if let Ok(sign) = Sign::try_from(tile_entity)
212 && let Some(block) = self.blocks.iter().find(|block| block.position == tile_entity.position())
213 && let Some(block) = new_blocks.get_mut(block.id)
214 && let Some(properties) = block.properties.first()
215 && let Some(properties) = new_properties.get_mut(*properties)
216 {
217 let (r, g, b) = sign.colour();
218 let colour = i32::from_be_bytes([0xFF, r, g, b]);
222
223 let properties = if properties.contains_key("unique!") {
224 properties
225 } else {
226 let properties = properties.clone();
229 block.properties.push(new_properties.len());
230 new_properties.push(properties);
231 let Some(properties) = new_properties.last_mut() else {
232 continue; };
234 properties
235 };
236
237 properties.extend([
238 (String::from("color"), Value::Integer(colour)),
239 (String::from("fontSize"), Value::Float(16.0)),
240 (String::from("text"), Value::String(sign.text())),
241 (String::from("posx"), Value::Integer(tile_entity.x)),
244 (String::from("posy"), Value::Integer(tile_entity.y)),
245 (String::from("posz"), Value::Integer(tile_entity.z)),
246 ]);
247 properties.remove("unique!");
248 }
249 }
250 }
251}