vmf_forge/
lib.rs

1//! A library for parsing and manipulating Valve Map Format (VMF) files.
2//!
3//! This library provides functionality to parse VMF files used in Source Engine games
4//! into Rust data structures, modify the data, and serialize it back into a VMF file.
5//!
6//! # Example
7//!
8//! ```
9//! use vmf_forge::prelude::*;
10//! use std::fs::File;
11//!
12//! fn main() -> Result<(), VmfError> {
13//!     let mut file = File::open("vmf_examples/your_map.vmf")?;
14//!     let vmf_file = VmfFile::parse_file(&mut file)?;
15//!
16//!     println!("Map Version: {}", vmf_file.versioninfo.map_version);
17//!
18//!     Ok(())
19//! }
20//! ```
21
22use indexmap::IndexMap;
23use serde::{Deserialize, Serialize};
24use std::fs::File;
25use std::io::{Read, Write};
26use std::path::Path;
27use std::str::FromStr;
28
29pub mod parser;
30pub(crate) mod utils;
31pub mod vmf;
32
33pub mod errors;
34pub mod prelude;
35
36pub use errors::{VmfError, VmfResult};
37pub use vmf::entities::{Entities, Entity};
38pub use vmf::metadata::{VersionInfo, ViewSettings, VisGroups};
39pub use vmf::regions::{Cameras, Cordons};
40pub use vmf::world::World;
41
42/// A trait for types that can be serialized into a VMF string representation.
43pub trait VmfSerializable {
44    /// Serializes the object into a VMF string.
45    ///
46    /// # Arguments
47    ///
48    /// * `indent_level` - The indentation level to use for formatting.
49    ///
50    /// # Returns
51    ///
52    /// A string representation of the object in VMF format.
53    fn to_vmf_string(&self, indent_level: usize) -> String;
54}
55
56/// Represents a parsed VMF file.
57#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
58pub struct VmfFile {
59    /// The path to the VMF file, if known.
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub path: Option<String>,
62    /// The version info of the VMF file.
63    pub versioninfo: VersionInfo,
64    /// The visgroups in the VMF file.
65    pub visgroups: VisGroups,
66    /// The view settings in the VMF file.
67    pub viewsettings: ViewSettings,
68    /// The world data in the VMF file.
69    pub world: World,
70    /// The entities in the VMF file.
71    pub entities: Entities,
72    /// The hidden entities in the VMF file.
73    pub hiddens: Entities,
74    /// The camera data in the VMF file.
75    pub cameras: Cameras,
76    /// The cordon data in the VMF file.
77    pub cordons: Cordons,
78}
79
80impl VmfFile {
81    /// Parses a VMF file from a string.
82    ///
83    /// # Arguments
84    ///
85    /// * `content` - The string content of the VMF file.
86    ///
87    /// # Returns
88    ///
89    /// A `VmfResult` containing the parsed `VmfFile` or a `VmfError` if parsing fails.
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use vmf_forge::VmfFile;
95    ///
96    /// let vmf_content = r#"
97    /// versioninfo
98    /// {
99    ///     "editorversion" "400"
100    ///     "editorbuild" "8000"
101    ///     "mapversion" "1"
102    ///     "formatversion" "100"
103    ///     "prefab" "0"
104    /// }
105    /// "#;
106    ///
107    /// let vmf_file = VmfFile::parse(vmf_content);
108    /// assert!(vmf_file.is_ok());
109    /// ```
110    pub fn parse(content: &str) -> VmfResult<Self> {
111        parser::parse_vmf(content)
112    }
113
114    /// Parses a VMF file from a `File`.
115    ///
116    /// # Arguments
117    ///
118    /// * `file` - The `File` to read from.
119    ///
120    /// # Returns
121    ///
122    /// A `VmfResult` containing the parsed `VmfFile` or a `VmfError` if parsing fails.
123    ///
124    /// # Examples
125    ///
126    /// ```no_run
127    /// use vmf_forge::VmfFile;
128    /// use std::fs::File;
129    ///
130    /// let mut file = File::open("your_map.vmf").unwrap();
131    /// let vmf_file = VmfFile::parse_file(&mut file);
132    /// assert!(vmf_file.is_ok());
133    /// ```
134    pub fn parse_file(file: &mut impl Read) -> VmfResult<Self> {
135        let mut content = Vec::new();
136        file.read_to_end(&mut content)?;
137        let content = String::from_utf8_lossy(&content);
138
139        VmfFile::parse(&content)
140    }
141
142    /// Opens and parses a VMF file from a file path.
143    ///
144    /// # Arguments
145    ///
146    /// * `path` - The path to the VMF file.
147    ///
148    /// # Returns
149    ///
150    /// A `VmfResult` containing the parsed `VmfFile` or a `VmfError` if an error occurs.
151    ///
152    /// # Examples
153    ///
154    /// ```no_run
155    /// use vmf_forge::VmfFile;
156    ///
157    /// let vmf_file = VmfFile::open("your_map.vmf");
158    /// assert!(vmf_file.is_ok());
159    /// ```
160    pub fn open(path: impl AsRef<Path>) -> VmfResult<Self> {
161        let path_str = path.as_ref().to_string_lossy().to_string();
162        let mut file = File::open(path)?;
163        let mut content = Vec::new();
164        file.read_to_end(&mut content)?;
165        let content = String::from_utf8_lossy(&content);
166
167        let mut vmf_file = VmfFile::parse(&content)?;
168        vmf_file.path = Some(path_str);
169        Ok(vmf_file)
170    }
171
172    /// Saves the `VmfFile` to a file at the specified path.
173    ///
174    /// # Arguments
175    ///
176    /// * `path` - The path to save the VMF file to.
177    ///
178    /// # Returns
179    ///
180    /// A `VmfResult` indicating success or a `VmfError` if an error occurs.
181    ///
182    /// # Examples
183    ///
184    /// ```no_run
185    /// use vmf_forge::VmfFile;
186    ///
187    /// let vmf_file = VmfFile::open("your_map.vmf").unwrap();
188    /// let result = vmf_file.save("new_map.vmf");
189    /// assert!(result.is_ok());
190    /// ```
191    pub fn save(&self, path: impl AsRef<Path>) -> VmfResult<()> {
192        let mut file = File::create(path)?;
193        file.write_all(self.to_vmf_string().as_bytes())?;
194        Ok(())
195    }
196
197    /// Merges the contents of another `VmfFile` into this one.
198    ///
199    /// This method combines the `visgroups`, `world` solids (both visible and hidden),
200    /// `entities`, `hiddens`, and `cordons` from the `other` `VmfFile` into the
201    /// current `VmfFile`.  `versioninfo`, `viewsettings`, and `cameras` are
202    /// *not* merged; the original values in `self` are retained.
203    ///
204    /// This method is experimental and its behavior may change in future versions.
205    /// It does not handle potential ID conflicts between the two VMF files.
206    ///
207    /// # Arguments
208    ///
209    /// * `other` - The `VmfFile` to merge into this one.
210    ///
211    /// # Example
212    ///
213    /// ```no_run
214    /// use vmf_forge::prelude::*;
215    ///
216    /// let mut vmf1 = VmfFile::open("map1.vmf").unwrap();
217    /// let vmf2 = VmfFile::open("map2.vmf").unwrap();
218    ///
219    /// vmf1.merge(vmf2);
220    ///
221    /// // vmf1 now contains the combined contents of both files.
222    /// ```
223    pub fn merge(&mut self, other: VmfFile) {
224        self.visgroups.groups.extend(other.visgroups.groups);
225        self.world.solids.extend(other.world.solids);
226        self.world.hidden.extend(other.world.hidden);
227
228        self.entities.extend(other.entities);
229        self.hiddens.extend(other.hiddens);
230
231        self.cordons.extend(other.cordons.cordons);
232    }
233
234    /// Converts the `VmfFile` to a string in VMF format.
235    ///
236    /// # Returns
237    ///
238    /// A string representing the `VmfFile` in VMF format.
239    pub fn to_vmf_string(&self) -> String {
240        let mut output = String::new();
241
242        // metadatas
243        output.push_str(&self.versioninfo.to_vmf_string(0));
244        output.push_str(&self.visgroups.to_vmf_string(0));
245        output.push_str(&self.viewsettings.to_vmf_string(0));
246        output.push_str(&self.world.to_vmf_string(0));
247
248        // entities
249        for entity in &*self.entities {
250            output.push_str(&entity.to_vmf_string(0));
251        }
252
253        for entity in &*self.hiddens {
254            output.push_str("hidden\n{\n");
255            output.push_str(&entity.to_vmf_string(1));
256            output.push_str("}\n");
257        }
258
259        // regions
260        output.push_str(&self.cameras.to_vmf_string(0));
261        output.push_str(&self.cordons.to_vmf_string(0));
262
263        output
264    }
265}
266
267impl FromStr for VmfFile {
268    type Err = VmfError;
269
270    fn from_str(s: &str) -> Result<Self, Self::Err> {
271        VmfFile::parse(s)
272    }
273}
274
275/// Represents a block in a VMF file, which can contain key-value pairs and other blocks.
276#[derive(Debug, Default, Clone)]
277pub struct VmfBlock {
278    /// The name of the block.
279    pub name: String,
280    /// The key-value pairs in the block.
281    pub key_values: IndexMap<String, String>,
282    /// The child blocks contained within this block.
283    pub blocks: Vec<VmfBlock>,
284}
285
286impl VmfBlock {
287    /// Serializes the `VmfBlock` into a string with the specified indentation level.
288    ///
289    /// # Arguments
290    ///
291    /// * `indent_level` - The indentation level to use for formatting.
292    ///
293    /// # Returns
294    ///
295    /// A string representation of the `VmfBlock` in VMF format.
296    pub fn serialize(&self, indent_level: usize) -> String {
297        let indent = "\t".repeat(indent_level);
298        let mut output = String::new();
299
300        // Opens the block with its name
301        output.push_str(&format!("{}{}\n", indent, self.name));
302        output.push_str(&format!("{}{{\n", indent));
303
304        // Adds all key-value pairs with the required indent
305        for (key, value) in &self.key_values {
306            output.push_str(&format!("{}\t\"{}\" \"{}\"\n", indent, key, value));
307        }
308
309        // Adds nested blocks with an increased indentation level
310        for block in &self.blocks {
311            output.push_str(&block.serialize(indent_level + 1));
312        }
313
314        // Closes the block
315        output.push_str(&format!("{}}}\n", indent));
316
317        output
318    }
319}