vmf_forge/vmf/
metadata.rs

1//! This module provides structures for representing metadata blocks in a VMF file, such as version info, visgroups, and view settings.
2
3use derive_more::{Deref, DerefMut, IntoIterator};
4
5use indexmap::IndexMap;
6use serde::{Deserialize, Serialize};
7
8use crate::utils::{get_key, parse_hs_key, To01String};
9use crate::{
10    errors::{VmfError, VmfResult},
11    VmfBlock, VmfSerializable,
12};
13
14/// Represents the version info of a VMF file.
15#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
16pub struct VersionInfo {
17    /// The editor version.
18    pub editor_version: i32,
19    /// The editor build number.
20    pub editor_build: i32,
21    /// The map version.
22    pub map_version: i32,
23    /// The format version.
24    pub format_version: i32,
25    /// Whether the VMF is a prefab.
26    pub prefab: bool,
27}
28
29impl TryFrom<VmfBlock> for VersionInfo {
30    type Error = VmfError;
31
32    fn try_from(block: VmfBlock) -> VmfResult<Self> {
33        let kv = &block.key_values;
34        Ok(Self {
35            editor_version: parse_hs_key!(kv, "editorversion", i32)?,
36            editor_build: parse_hs_key!(kv, "editorbuild", i32)?,
37            map_version: parse_hs_key!(kv, "mapversion", i32)?,
38            format_version: parse_hs_key!(kv, "formatversion", i32)?,
39            prefab: get_key!(kv, "prefab")? == "1",
40        })
41    }
42}
43
44impl From<VersionInfo> for VmfBlock {
45    fn from(val: VersionInfo) -> Self {
46        let mut key_values = IndexMap::new();
47        key_values.insert("editorversion".to_string(), val.editor_version.to_string());
48        key_values.insert("editorbuild".to_string(), val.editor_build.to_string());
49        key_values.insert("mapversion".to_string(), val.map_version.to_string());
50        key_values.insert("formatversion".to_string(), val.format_version.to_string());
51        key_values.insert("prefab".to_string(), val.prefab.to_01_string());
52
53        VmfBlock {
54            name: "versioninfo".to_string(),
55            key_values,
56            blocks: Vec::new(),
57        }
58    }
59}
60
61impl VmfSerializable for VersionInfo {
62    fn to_vmf_string(&self, indent_level: usize) -> String {
63        let indent = "\t".repeat(indent_level);
64        let mut output = String::with_capacity(256);
65
66        output.push_str(&format!("{0}versioninfo\n{0}{{\n", indent));
67        output.push_str(&format!(
68            "{}\t\"editorversion\" \"{}\"\n",
69            indent, self.editor_version
70        ));
71        output.push_str(&format!(
72            "{}\t\"editorbuild\" \"{}\"\n",
73            indent, self.editor_build
74        ));
75        output.push_str(&format!(
76            "{}\t\"mapversion\" \"{}\"\n",
77            indent, self.map_version
78        ));
79        output.push_str(&format!(
80            "{}\t\"formatversion\" \"{}\"\n",
81            indent, self.format_version
82        ));
83        output.push_str(&format!(
84            "{}\t\"prefab\" \"{}\"\n",
85            indent,
86            self.prefab.to_01_string()
87        ));
88
89        output.push_str(&format!("{}}}\n", indent));
90        output
91    }
92}
93
94/// Represents a collection of VisGroups in a VMF file.
95#[derive(
96    Debug, Default, Clone, Serialize, Deserialize, PartialEq, Deref, DerefMut, IntoIterator,
97)]
98pub struct VisGroups {
99    /// The list of VisGroups.
100    #[deref]
101    pub groups: Vec<VisGroup>,
102}
103
104impl TryFrom<VmfBlock> for VisGroups {
105    type Error = VmfError;
106
107    fn try_from(block: VmfBlock) -> VmfResult<Self> {
108        let mut groups = Vec::with_capacity(12);
109        for group in block.blocks {
110            groups.push(VisGroup::try_from(group)?);
111        }
112
113        Ok(Self { groups })
114    }
115}
116
117impl From<VisGroups> for VmfBlock {
118    fn from(val: VisGroups) -> Self {
119        let mut visgroups_block = VmfBlock {
120            name: "visgroups".to_string(),
121            key_values: IndexMap::new(),
122            blocks: Vec::with_capacity(val.groups.len()),
123        };
124
125        for group in val.groups {
126            visgroups_block.blocks.push(group.into())
127        }
128
129        visgroups_block
130    }
131}
132
133impl VmfSerializable for VisGroups {
134    fn to_vmf_string(&self, indent_level: usize) -> String {
135        let indent = "\t".repeat(indent_level);
136        let mut output = String::with_capacity(128);
137
138        output.push_str(&format!("{0}visgroups\n{0}{{\n", indent));
139
140        if self.groups.is_empty() {
141            output.push_str(&format!("{}}}\n", indent));
142            return output;
143        }
144
145        for group in &self.groups {
146            output.push_str(&group.to_vmf_string(indent_level));
147        }
148
149        output.push_str(&format!("{}}}\n", indent));
150        output
151    }
152}
153
154/// Represents a VisGroup in a VMF file.
155#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
156pub struct VisGroup {
157    /// The name of the VisGroup.
158    pub name: String,
159    /// The ID of the VisGroup.
160    pub id: i32,
161    /// The color of the VisGroup in the editor.
162    pub color: String,
163    /// The child VisGroups of this VisGroup, if any.
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub children: Option<Vec<VisGroup>>,
166}
167
168impl TryFrom<VmfBlock> for VisGroup {
169    type Error = VmfError;
170
171    fn try_from(block: VmfBlock) -> VmfResult<Self> {
172        let children = if block.blocks.is_empty() {
173            None
174        } else {
175            Some(
176                block
177                    .blocks
178                    .into_iter()
179                    .map(VisGroup::try_from)
180                    .collect::<VmfResult<Vec<_>>>()?,
181            )
182        };
183
184        Ok(Self {
185            name: get_key!(block.key_values, "name")?.to_owned(),
186            id: parse_hs_key!(block.key_values, "visgroupid", i32)?,
187            color: get_key!(block.key_values, "color")?.to_owned(),
188            children,
189        })
190    }
191}
192
193impl From<VisGroup> for VmfBlock {
194    fn from(val: VisGroup) -> Self {
195        // Create a block for VisGroup
196        let mut visgroup_block = VmfBlock {
197            name: "visgroup".to_string(),
198            key_values: IndexMap::new(),
199            blocks: Vec::new(),
200        };
201
202        // Adds key-value pairs for VisGroup
203        visgroup_block
204            .key_values
205            .insert("name".to_string(), val.name);
206        visgroup_block
207            .key_values
208            .insert("visgroupid".to_string(), val.id.to_string());
209        visgroup_block
210            .key_values
211            .insert("color".to_string(), val.color);
212
213        // If the `VisGroup` has a child element, adds it as nested block
214        if let Some(children) = val.children {
215            for child in children {
216                visgroup_block.blocks.push(child.into());
217            }
218        }
219
220        visgroup_block
221    }
222}
223
224impl VmfSerializable for VisGroup {
225    fn to_vmf_string(&self, indent_level: usize) -> String {
226        let indent = "\t".repeat(indent_level);
227        let mut output = String::with_capacity(64);
228
229        output.push_str(&format!("{0}\tvisgroup\n\t{0}{{\n", indent));
230        output.push_str(&format!("{}\t\t\"name\" \"{}\"\n", indent, self.name));
231        output.push_str(&format!("{}\t\t\"visgroupid\" \"{}\"\n", indent, self.id));
232        output.push_str(&format!("{}\t\t\"color\" \"{}\"\n", indent, self.color));
233
234        // If there are child elements, adds them
235        if let Some(ref children) = self.children {
236            for child in children {
237                output.push_str(&child.to_vmf_string(indent_level + 1));
238            }
239        }
240
241        output.push_str(&format!("{}\t}}\n", indent));
242        output
243    }
244}
245
246/// Represents the view settings of a VMF file.
247#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
248pub struct ViewSettings {
249    /// Whether snapping to the grid is enabled.
250    pub snap_to_grid: bool,
251    /// Whether the grid is shown in the editor.
252    pub show_grid: bool,
253    /// Whether the logical grid is shown in the editor.
254    pub show_logical_grid: bool,
255    /// The grid spacing.
256    pub grid_spacing: u16,
257    /// Whether the 3D grid is shown in the editor.
258    pub show_3d_grid: bool,
259}
260
261impl Default for ViewSettings {
262    fn default() -> Self {
263        Self {
264            snap_to_grid: true,
265            show_grid: true,
266            show_logical_grid: false,
267            grid_spacing: 8,
268            show_3d_grid: false,
269        }
270    }
271}
272
273impl TryFrom<VmfBlock> for ViewSettings {
274    type Error = VmfError;
275
276    fn try_from(block: VmfBlock) -> VmfResult<Self> {
277        Ok(Self {
278            snap_to_grid: get_key!(&block.key_values, "bSnapToGrid")? == "1",
279            show_grid: get_key!(&block.key_values, "bShowGrid")? == "1",
280            show_logical_grid: get_key!(&block.key_values, "bShowLogicalGrid")? == "1",
281            grid_spacing: get_key!(&block.key_values, "nGridSpacing")?
282                .parse()
283                .unwrap_or(64),
284            show_3d_grid: get_key!(&block.key_values, "bShow3DGrid", "0".into()) == "1",
285        })
286    }
287}
288
289impl From<ViewSettings> for VmfBlock {
290    fn from(val: ViewSettings) -> Self {
291        let mut key_values = IndexMap::new();
292        key_values.insert("bSnapToGrid".to_string(), val.snap_to_grid.to_01_string());
293        key_values.insert("bShowGrid".to_string(), val.show_grid.to_01_string());
294        key_values.insert(
295            "bShowLogicalGrid".to_string(),
296            val.show_logical_grid.to_01_string(),
297        );
298        key_values.insert("nGridSpacing".to_string(), val.grid_spacing.to_string());
299        key_values.insert("bShow3DGrid".to_string(), val.show_3d_grid.to_01_string());
300
301        VmfBlock {
302            name: "viewsettings".to_string(),
303            key_values,
304            blocks: Vec::new(),
305        }
306    }
307}
308
309impl VmfSerializable for ViewSettings {
310    fn to_vmf_string(&self, indent_level: usize) -> String {
311        let indent = "\t".repeat(indent_level);
312        let mut output = String::with_capacity(64);
313
314        output.push_str(&format!("{0}viewsettings\n{0}{{\n", indent));
315        output.push_str(&format!(
316            "{}\t\"bSnapToGrid\" \"{}\"\n",
317            indent,
318            self.snap_to_grid.to_01_string()
319        ));
320        output.push_str(&format!(
321            "{}\t\"bShowGrid\" \"{}\"\n",
322            indent,
323            self.show_grid.to_01_string()
324        ));
325        output.push_str(&format!(
326            "{}\t\"bShowLogicalGrid\" \"{}\"\n",
327            indent,
328            self.show_logical_grid.to_01_string()
329        ));
330        output.push_str(&format!(
331            "{}\t\"nGridSpacing\" \"{}\"\n",
332            indent, self.grid_spacing
333        ));
334        output.push_str(&format!(
335            "{}\t\"bShow3DGrid\" \"{}\"\n",
336            indent,
337            self.show_3d_grid.to_01_string()
338        ));
339
340        output.push_str(&format!("{}}}\n", indent));
341        output
342    }
343}