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}