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;
6#[cfg(feature = "serialization")]
7use serde::{Deserialize, Serialize};
8
9use crate::utils::{To01String, get_key_ref, take_and_parse_key, take_key_owned};
10use crate::{
11    VmfBlock, VmfSerializable,
12    errors::{VmfError, VmfResult},
13};
14
15/// Represents the version info of a VMF file.
16#[derive(Debug, Default, Clone, PartialEq)]
17#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
18pub struct VersionInfo {
19    /// The editor version.
20    pub editor_version: i32,
21    /// The editor build number.
22    pub editor_build: i32,
23    /// The map version.
24    pub map_version: i32,
25    /// The format version.
26    pub format_version: i32,
27    /// Whether the VMF is a prefab.
28    pub prefab: bool,
29}
30
31impl TryFrom<VmfBlock> for VersionInfo {
32    type Error = VmfError;
33
34    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
35        let kv = &mut block.key_values;
36        Ok(Self {
37            editor_version: take_and_parse_key::<i32>(kv, "editorversion")?,
38            editor_build: take_and_parse_key::<i32>(kv, "editorbuild")?,
39            map_version: take_and_parse_key::<i32>(kv, "mapversion")?,
40            format_version: take_and_parse_key::<i32>(kv, "formatversion")?,
41            prefab: get_key_ref(kv, "prefab")? == "1",
42        })
43    }
44}
45
46impl From<VersionInfo> for VmfBlock {
47    fn from(val: VersionInfo) -> Self {
48        let mut key_values = IndexMap::new();
49        key_values.insert("editorversion".to_string(), val.editor_version.to_string());
50        key_values.insert("editorbuild".to_string(), val.editor_build.to_string());
51        key_values.insert("mapversion".to_string(), val.map_version.to_string());
52        key_values.insert("formatversion".to_string(), val.format_version.to_string());
53        key_values.insert("prefab".to_string(), val.prefab.to_01_string());
54
55        VmfBlock {
56            name: "versioninfo".to_string(),
57            key_values,
58            blocks: Vec::new(),
59        }
60    }
61}
62
63impl VmfSerializable for VersionInfo {
64    fn to_vmf_string(&self, indent_level: usize) -> String {
65        let indent = "\t".repeat(indent_level);
66        let mut output = String::with_capacity(256);
67
68        output.push_str(&format!("{0}versioninfo\n{0}{{\n", indent));
69        output.push_str(&format!(
70            "{}\t\"editorversion\" \"{}\"\n",
71            indent, self.editor_version
72        ));
73        output.push_str(&format!(
74            "{}\t\"editorbuild\" \"{}\"\n",
75            indent, self.editor_build
76        ));
77        output.push_str(&format!(
78            "{}\t\"mapversion\" \"{}\"\n",
79            indent, self.map_version
80        ));
81        output.push_str(&format!(
82            "{}\t\"formatversion\" \"{}\"\n",
83            indent, self.format_version
84        ));
85        output.push_str(&format!(
86            "{}\t\"prefab\" \"{}\"\n",
87            indent,
88            self.prefab.to_01_string()
89        ));
90
91        output.push_str(&format!("{}}}\n", indent));
92        output
93    }
94}
95
96/// Represents a collection of VisGroups in a VMF file.
97#[derive(Debug, Default, Clone, PartialEq, Deref, DerefMut, IntoIterator)]
98#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
99pub struct VisGroups {
100    /// The list of VisGroups.
101    #[deref]
102    pub groups: Vec<VisGroup>,
103}
104
105/// Recursively finds a VisGroup by its ID within a slice of VisGroups.
106/// Returns None if not found.
107fn find_visgroup_by_id(groups: &[VisGroup], id_to_find: i32) -> Option<&VisGroup> {
108    for group in groups {
109        if group.id == id_to_find {
110            return Some(group);
111        }
112        if let Some(ref children) = group.children {
113            if let Some(found) = find_visgroup_by_id(children, id_to_find) {
114                return Some(found);
115            }
116        }
117    }
118    None
119}
120
121/// Recursively finds a mutable reference to a VisGroup by its ID within a slice of VisGroups.
122/// Returns None if not found.
123fn find_visgroup_by_id_mut(
124    groups: &mut [VisGroup],
125    id_to_find: i32,
126) -> Option<&mut VisGroup> {
127    for group in groups {
128        if group.id == id_to_find {
129            return Some(group);
130        }
131        if let Some(ref mut children) = group.children {
132            if let Some(found) = find_visgroup_by_id_mut(children, id_to_find) {
133                return Some(found);
134            }
135        }
136    }
137    None
138}
139
140/// Recursively finds a VisGroup by its name within a slice of VisGroups.
141/// Returns None if not found.
142fn find_visgroup_by_name<'a>(groups: &'a [VisGroup], name_to_find: &str) -> Option<&'a VisGroup> {
143    for group in groups {
144        if group.name == name_to_find {
145            return Some(group);
146        }
147        if let Some(ref children) = group.children {
148            if let Some(found) = find_visgroup_by_name(children, name_to_find) {
149                return Some(found);
150            }
151        }
152    }
153    None
154}
155
156/// Recursively finds a mutable reference to a VisGroup by its name within a slice of VisGroups.
157/// Returns None if not found.
158fn find_visgroup_by_name_mut<'a>(
159    groups: &'a mut [VisGroup],
160    name_to_find: &str,
161) -> Option<&'a mut VisGroup> {
162    for group in groups {
163        if group.name == name_to_find {
164            return Some(group);
165        }
166        if let Some(ref mut children) = group.children {
167            if let Some(found) = find_visgroup_by_name_mut(children, name_to_find) {
168                return Some(found);
169            }
170        }
171    }
172    None
173}
174
175impl VisGroups {
176    /// Finds a VisGroup by its name recursively within this collection.
177    ///
178    /// # Arguments
179    ///
180    /// * `name` - The exact name of the VisGroup to find.
181    ///
182    /// # Returns
183    ///
184    /// An `Option` containing a reference to the found `VisGroup`, or `None`.
185    pub fn find_by_name(&self, name: &str) -> Option<&VisGroup> {
186        find_visgroup_by_name(&self.groups, name)
187    }
188
189    /// Finds a mutable reference to a VisGroup by its name recursively within this collection.
190    ///
191    /// # Arguments
192    ///
193    /// * `name` - The exact name of the VisGroup to find.
194    ///
195    /// # Returns
196    ///
197    /// An `Option` containing a mutable reference to the found `VisGroup`, or `None`.
198    pub fn find_by_name_mut(&mut self, name: &str) -> Option<&mut VisGroup> {
199        find_visgroup_by_name_mut(&mut self.groups, name)
200    }
201
202    /// Finds a VisGroup by its ID recursively within this collection.
203    ///
204    /// # Arguments
205    ///
206    /// * `id` - The ID of the VisGroup to find.
207    ///
208    /// # Returns
209    ///
210    /// An `Option` containing a reference to the found `VisGroup`, or `None`.
211    pub fn find_by_id(&self, id: i32) -> Option<&VisGroup> {
212        find_visgroup_by_id(&self.groups, id)
213    }
214
215    /// Finds a mutable reference to a VisGroup by its ID recursively within this collection.
216    ///
217    /// # Arguments
218    ///
219    /// * `id` - The ID of the VisGroup to find.
220    ///
221    /// # Returns
222    ///
223    /// An `Option` containing a mutable reference to the found `VisGroup`, or `None`.
224    pub fn find_by_id_mut(&mut self, id: i32) -> Option<&mut VisGroup> {
225        find_visgroup_by_id_mut(&mut self.groups, id)
226    }
227}
228
229impl TryFrom<VmfBlock> for VisGroups {
230    type Error = VmfError;
231
232    fn try_from(block: VmfBlock) -> VmfResult<Self> {
233        let mut groups = Vec::with_capacity(block.blocks.len());
234        for group in block.blocks {
235            groups.push(VisGroup::try_from(group)?);
236        }
237
238        Ok(Self { groups })
239    }
240}
241
242impl From<VisGroups> for VmfBlock {
243    fn from(val: VisGroups) -> Self {
244        let mut visgroups_block = VmfBlock {
245            name: "visgroups".to_string(),
246            key_values: IndexMap::new(),
247            blocks: Vec::with_capacity(val.groups.len()),
248        };
249
250        for group in val.groups {
251            visgroups_block.blocks.push(group.into())
252        }
253
254        visgroups_block
255    }
256}
257
258impl VmfSerializable for VisGroups {
259    fn to_vmf_string(&self, indent_level: usize) -> String {
260        let indent = "\t".repeat(indent_level);
261        let mut output = String::with_capacity(128);
262
263        output.push_str(&format!("{0}visgroups\n{0}{{\n", indent));
264
265        if self.groups.is_empty() {
266            output.push_str(&format!("{}}}\n", indent));
267            return output;
268        }
269
270        for group in &self.groups {
271            output.push_str(&group.to_vmf_string(indent_level));
272        }
273
274        output.push_str(&format!("{}}}\n", indent));
275        output
276    }
277}
278
279/// Represents a VisGroup in a VMF file.
280#[derive(Debug, Default, Clone, PartialEq)]
281#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
282pub struct VisGroup {
283    /// The name of the VisGroup.
284    pub name: String,
285    /// The ID of the VisGroup.
286    pub id: i32,
287    /// The color of the VisGroup in the editor.
288    pub color: String,
289    /// The child VisGroups of this VisGroup, if any.
290    #[cfg_attr(
291        feature = "serialization",
292        serde(default, skip_serializing_if = "Option::is_none")
293    )]
294    pub children: Option<Vec<VisGroup>>,
295}
296
297impl TryFrom<VmfBlock> for VisGroup {
298    type Error = VmfError;
299
300    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
301        let children = if !block.blocks.is_empty() {
302            let mut children_vec = Vec::with_capacity(block.blocks.len());
303            for child_block in block.blocks {
304                children_vec.push(VisGroup::try_from(child_block)?);
305            }
306            Some(children_vec)
307        } else {
308            None
309        };
310
311        let kv = &mut block.key_values;
312        Ok(Self {
313            name: take_key_owned(kv, "name")?,
314            id: take_and_parse_key::<i32>(kv, "visgroupid")?,
315            color: take_key_owned(kv, "color")?,
316            children,
317        })
318    }
319}
320
321impl From<VisGroup> for VmfBlock {
322    fn from(val: VisGroup) -> Self {
323        // Create a block for VisGroup
324        let mut visgroup_block = VmfBlock {
325            name: "visgroup".to_string(),
326            key_values: IndexMap::new(),
327            blocks: Vec::new(),
328        };
329
330        // Adds key-value pairs for VisGroup
331        visgroup_block
332            .key_values
333            .insert("name".to_string(), val.name);
334        visgroup_block
335            .key_values
336            .insert("visgroupid".to_string(), val.id.to_string());
337        visgroup_block
338            .key_values
339            .insert("color".to_string(), val.color);
340
341        // If the `VisGroup` has a child element, adds it as nested block
342        if let Some(children) = val.children {
343            for child in children {
344                visgroup_block.blocks.push(child.into());
345            }
346        }
347
348        visgroup_block
349    }
350}
351
352impl VmfSerializable for VisGroup {
353    fn to_vmf_string(&self, indent_level: usize) -> String {
354        let indent = "\t".repeat(indent_level);
355        let mut output = String::with_capacity(64);
356
357        output.push_str(&format!("{0}\tvisgroup\n\t{0}{{\n", indent));
358        output.push_str(&format!("{}\t\t\"name\" \"{}\"\n", indent, self.name));
359        output.push_str(&format!("{}\t\t\"visgroupid\" \"{}\"\n", indent, self.id));
360        output.push_str(&format!("{}\t\t\"color\" \"{}\"\n", indent, self.color));
361
362        // If there are child elements, adds them
363        if let Some(ref children) = self.children {
364            for child in children {
365                output.push_str(&child.to_vmf_string(indent_level + 1));
366            }
367        }
368
369        output.push_str(&format!("{}\t}}\n", indent));
370        output
371    }
372}
373
374/// Represents the view settings of a VMF file.
375#[derive(Debug, Clone, PartialEq)]
376#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
377pub struct ViewSettings {
378    /// Whether snapping to the grid is enabled.
379    pub snap_to_grid: bool,
380    /// Whether the grid is shown in the editor.
381    pub show_grid: bool,
382    /// Whether the logical grid is shown in the editor.
383    pub show_logical_grid: bool,
384    /// The grid spacing.
385    pub grid_spacing: u16,
386    /// Whether the 3D grid is shown in the editor.
387    pub show_3d_grid: bool,
388}
389
390impl Default for ViewSettings {
391    fn default() -> Self {
392        Self {
393            snap_to_grid: true,
394            show_grid: true,
395            show_logical_grid: false,
396            grid_spacing: 8,
397            show_3d_grid: false,
398        }
399    }
400}
401
402impl TryFrom<VmfBlock> for ViewSettings {
403    type Error = VmfError;
404
405    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
406        let kv = &mut block.key_values;
407
408        Ok(Self {
409            snap_to_grid: get_key_ref(kv, "bSnapToGrid")? == "1",
410            show_grid: get_key_ref(kv, "bShowGrid")? == "1",
411            show_logical_grid: get_key_ref(kv, "bShowLogicalGrid")? == "1",
412            grid_spacing: take_and_parse_key::<u16>(kv, "nGridSpacing").unwrap_or(64),
413            show_3d_grid: get_key_ref(kv, "bShow3DGrid").map_or("0", |v| v) == "1",
414        })
415    }
416}
417
418impl From<ViewSettings> for VmfBlock {
419    fn from(val: ViewSettings) -> Self {
420        let mut key_values = IndexMap::new();
421        key_values.insert("bSnapToGrid".to_string(), val.snap_to_grid.to_01_string());
422        key_values.insert("bShowGrid".to_string(), val.show_grid.to_01_string());
423        key_values.insert(
424            "bShowLogicalGrid".to_string(),
425            val.show_logical_grid.to_01_string(),
426        );
427        key_values.insert("nGridSpacing".to_string(), val.grid_spacing.to_string());
428        key_values.insert("bShow3DGrid".to_string(), val.show_3d_grid.to_01_string());
429
430        VmfBlock {
431            name: "viewsettings".to_string(),
432            key_values,
433            blocks: Vec::new(),
434        }
435    }
436}
437
438impl VmfSerializable for ViewSettings {
439    fn to_vmf_string(&self, indent_level: usize) -> String {
440        let indent = "\t".repeat(indent_level);
441        let mut output = String::with_capacity(64);
442
443        output.push_str(&format!("{0}viewsettings\n{0}{{\n", indent));
444        output.push_str(&format!(
445            "{}\t\"bSnapToGrid\" \"{}\"\n",
446            indent,
447            self.snap_to_grid.to_01_string()
448        ));
449        output.push_str(&format!(
450            "{}\t\"bShowGrid\" \"{}\"\n",
451            indent,
452            self.show_grid.to_01_string()
453        ));
454        output.push_str(&format!(
455            "{}\t\"bShowLogicalGrid\" \"{}\"\n",
456            indent,
457            self.show_logical_grid.to_01_string()
458        ));
459        output.push_str(&format!(
460            "{}\t\"nGridSpacing\" \"{}\"\n",
461            indent, self.grid_spacing
462        ));
463        output.push_str(&format!(
464            "{}\t\"bShow3DGrid\" \"{}\"\n",
465            indent,
466            self.show_3d_grid.to_01_string()
467        ));
468
469        output.push_str(&format!("{}}}\n", indent));
470        output
471    }
472}