vmf_forge/vmf/
entities.rs

1//! This module provides structures for representing entities in a VMF file.
2
3use crate::{
4    errors::{VmfError, VmfResult},
5    VmfBlock, VmfSerializable,
6};
7use derive_more::{Deref, DerefMut, IntoIterator};
8use indexmap::IndexMap;
9use serde::{Deserialize, Serialize};
10
11use super::common::Editor;
12use super::world::Solid;
13
14/// Represents an entity in a VMF file.
15#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
16pub struct Entity {
17    /// The key-value pairs associated with this entity.
18    pub key_values: IndexMap<String, String>,
19    /// The output connections of this entity.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub connections: Option<Vec<(String, String)>>,
22    /// The solids associated with this entity, if any.
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub solids: Option<Vec<Solid>>,
25    /// The editor data for this entity.
26    pub editor: Editor,
27    /// Indicates if the entity is hidden within the editor.  This field
28    /// is primarily used when parsing a `hidden` block within a VMF file,
29    /// and is not serialized back when writing the VMF.
30    #[serde(default, skip_serializing)]
31    pub is_hidden: bool,
32}
33
34impl Entity {
35    /// Creates a new `Entity` with the specified classname and ID.
36    ///
37    /// # Arguments
38    ///
39    /// * `classname` - The classname of the entity (e.g., "func_detail", "info_player_start").
40    /// * `id` - The unique ID of the entity.
41    ///
42    /// # Example
43    ///
44    /// ```
45    /// use vmf_forge::prelude::*;
46    ///
47    /// let entity = Entity::new("info_player_start", 1);
48    /// assert_eq!(entity.classname(), Some("info_player_start"));
49    /// assert_eq!(entity.id(), 1);
50    /// ```
51    pub fn new(classname: impl Into<String>, id: u64) -> Self {
52        let mut key_values = IndexMap::new();
53        key_values.insert("classname".to_string(), classname.into());
54        key_values.insert("id".to_string(), id.to_string());
55        Entity {
56            key_values,
57            connections: None,
58            solids: None,
59            editor: Editor::default(),
60            is_hidden: false,
61        }
62    }
63
64    /// Sets a key-value pair for the entity.  If the key already exists,
65    /// its value is updated.
66    ///
67    /// # Arguments
68    ///
69    /// * `key` - The key to set.
70    /// * `value` - The value to set for the key.
71    pub fn set(&mut self, key: String, value: String) {
72        self.key_values.insert(key, value);
73    }
74
75    /// Removes a key-value pair from the entity, preserving the order of other keys.
76    ///
77    /// # Arguments
78    ///
79    /// * `key` - The key to remove.
80    ///
81    /// # Returns
82    ///
83    /// An `Option` containing the removed value, if the key was present.
84    pub fn remove_key(&mut self, key: &str) -> Option<String> {
85        self.key_values.shift_remove(key)
86    }
87
88    /// Removes a key-value pair from the entity, potentially changing the order of other keys.
89    /// This is faster than `remove_key` but does not preserve insertion order.
90    ///
91    /// # Arguments
92    ///
93    /// * `key` - The key to remove.
94    ///
95    /// # Returns
96    ///
97    /// An `Option` containing the removed value, if the key was present.
98    pub fn swap_remove_key(&mut self, key: &str) -> Option<String> {
99        self.key_values.swap_remove(key)
100    }
101
102    /// Gets the value associated with the given key.
103    ///
104    /// # Arguments
105    ///
106    /// * `key` - The key to get the value for.
107    ///
108    /// # Returns
109    ///
110    /// An `Option` containing a reference to the value, if the key is present.
111    pub fn get(&self, key: &str) -> Option<&String> {
112        self.key_values.get(key)
113    }
114
115    /// Gets a mutable reference to the value associated with the given key.
116    ///
117    /// # Arguments
118    ///
119    /// * `key` - The key to get the value for.
120    ///
121    /// # Returns
122    ///
123    /// An `Option` containing a mutable reference to the value, if the key is present.
124    pub fn get_mut(&mut self, key: &str) -> Option<&mut String> {
125        self.key_values.get_mut(key)
126    }
127
128    /// Returns the classname of the entity.
129    ///
130    /// # Returns
131    ///
132    /// An `Option` containing the classname, if it exists.
133    pub fn classname(&self) -> Option<&str> {
134        self.key_values.get("classname").map(|s| s.as_str())
135    }
136
137    /// Returns the targetname of the entity.
138    ///
139    /// # Returns
140    ///
141    /// An `Option` containing the targetname, if it exists.
142    pub fn targetname(&self) -> Option<&str> {
143        self.key_values.get("targetname").map(|s| s.as_str())
144    }
145
146    /// Returns the ID of the entity.
147    pub fn id(&self) -> u64 {
148        self.key_values
149            .get("id")
150            .and_then(|s| s.parse::<u64>().ok())
151            .unwrap_or(0)
152    }
153
154    /// Returns the model of the entity.
155    ///
156    /// # Returns
157    ///
158    /// An `Option` containing the model path, if it exists.
159    pub fn model(&self) -> Option<&str> {
160        self.key_values.get("model").map(|s| s.as_str())
161    }
162
163    /// Adds an output connection to the entity.
164    ///
165    /// # Arguments
166    ///
167    /// * `output` - The name of the output on this entity.
168    /// * `target_entity` - The targetname of the entity to connect to.
169    /// * `input` - The name of the input on the target entity.
170    /// * `parms` - The parameters to pass to the input.
171    /// * `delay` - The delay before the input is triggered, in seconds.
172    /// * `fire_limit` - The number of times the output can be fired (-1 for unlimited).
173    ///
174    /// # Example
175    ///
176    /// ```
177    /// use vmf_forge::prelude::*;
178    ///
179    /// let mut entity = Entity::new("logic_relay", 1);
180    /// entity.add_connection("OnTrigger", "my_door", "Open", "", 0.0, -1);
181    /// ```
182    pub fn add_connection(
183        &mut self,
184        output: impl Into<String>,
185        target_entity: impl AsRef<str>,
186        input: impl AsRef<str>,
187        parms: impl AsRef<str>,
188        delay: f32,
189        fire_limit: i32,
190    ) {
191        let input_result = format!(
192            "{}\x1B{}\x1B{}\x1B{}\x1B{}",
193            target_entity.as_ref(),
194            input.as_ref(),
195            parms.as_ref(),
196            delay,
197            fire_limit
198        );
199        if let Some(connections) = &mut self.connections {
200            connections.push((output.into(), input_result));
201        } else {
202            self.connections = Some(vec![(output.into(), input_result)]);
203        }
204    }
205
206    /// Removes all connections from this entity.
207    pub fn clear_connections(&mut self) {
208        self.connections = None;
209    }
210
211    /// Checks if a specific connection exists.
212    ///
213    /// # Arguments
214    /// * `output` The output to check
215    /// * `input` The input to check
216    ///
217    /// # Returns
218    /// * `true` if the connection exists, `false` otherwise.
219    pub fn has_connection(&self, output: &str, input: &str) -> bool {
220        if let Some(connections) = &self.connections {
221            connections.iter().any(|(o, i)| o == output && i == input)
222        } else {
223            false
224        }
225    }
226}
227
228impl TryFrom<VmfBlock> for Entity {
229    type Error = VmfError;
230
231    fn try_from(block: VmfBlock) -> VmfResult<Self> {
232        // Extract key-value pairs from the block
233        let key_values = block.key_values;
234
235        // Searches for nested blocks and extracts the necessary information
236        let mut ent = Self {
237            key_values,
238            ..Default::default()
239        };
240        let mut solids = Vec::new();
241
242        for block in block.blocks {
243            match block.name.as_str() {
244                "editor" => ent.editor = Editor::try_from(block)?,
245                "connections" => ent.connections = process_connections(block.key_values),
246                "solid" => solids.push(Solid::try_from(block)?),
247                "hidden" => {
248                    if let Some(hidden_block) = block.blocks.first() {
249                        solids.push(Solid::try_from(hidden_block.to_owned())?)
250                    }
251                }
252                _ => {
253                    #[cfg(feature = "debug_assert_info")]
254                    debug_assert!(
255                        false,
256                        "Unexpected block name: {}, id: {:?}",
257                        block.name,
258                        ent.key_values.get("id")
259                    );
260                }
261            }
262        }
263
264        if !solids.is_empty() {
265            ent.solids = Some(solids);
266        }
267
268        Ok(ent)
269    }
270}
271
272impl From<Entity> for VmfBlock {
273    fn from(val: Entity) -> Self {
274        let editor = val.editor.into();
275
276        VmfBlock {
277            name: "entity".to_string(),
278            key_values: val.key_values,
279            blocks: vec![editor],
280        }
281    }
282}
283
284impl VmfSerializable for Entity {
285    fn to_vmf_string(&self, indent_level: usize) -> String {
286        let indent = "\t".repeat(indent_level);
287        let mut output = String::with_capacity(256);
288
289        // Writes the main entity block
290        output.push_str(&format!("{0}entity\n{0}{{\n", indent));
291
292        // Adds key_values of the main block
293        for (key, value) in &self.key_values {
294            output.push_str(&format!("{}\t\"{}\" \"{}\"\n", indent, key, value));
295        }
296
297        // Adds connections block
298        if let Some(connections) = &self.connections {
299            output.push_str(&format!("{0}\tconnections\n{0}\t{{\n", indent));
300            for (out, inp) in connections {
301                output.push_str(&format!("{}\t\t\"{}\" \"{}\"\n", indent, out, inp));
302            }
303            output.push_str(&format!("{}\t}}\n", indent));
304        }
305
306        // Solids block
307        if let Some(solids) = &self.solids {
308            for solid in solids {
309                output.push_str(&solid.to_vmf_string(indent_level + 1));
310            }
311        }
312
313        // Editor block
314        output.push_str(&self.editor.to_vmf_string(indent_level + 1));
315
316        output.push_str(&format!("{}}}\n", indent));
317
318        output
319    }
320}
321
322/// Represents a collection of entities in a VMF file.
323#[derive(
324    Debug, Default, Clone, Serialize, Deserialize, PartialEq, Deref, DerefMut, IntoIterator,
325)]
326pub struct Entities {
327    /// The vector of entities.
328    pub vec: Vec<Entity>,
329}
330
331impl Entities {
332    /// Returns an iterator over the entities that have the specified key-value pair.
333    ///
334    /// # Arguments
335    ///
336    /// * `key` - The key to search for.
337    /// * `value` - The value to search for.
338    pub fn find_by_keyvalue<'a>(
339        &'a self,
340        key: &'a str,
341        value: &'a str,
342    ) -> impl Iterator<Item = &'a Entity> + 'a {
343        self.vec
344            .iter()
345            .filter(move |ent| ent.key_values.get(key).is_some_and(|v| v == value))
346    }
347
348    /// Returns an iterator over the entities that have the specified key-value pair, allowing modification.
349    ///
350    /// # Arguments
351    ///
352    /// * `key` - The key to search for.
353    /// * `value` - The value to search for.
354    pub fn find_by_keyvalue_mut<'a>(
355        &'a mut self,
356        key: &'a str,
357        value: &'a str,
358    ) -> impl Iterator<Item = &'a mut Entity> + 'a {
359        self.vec
360            .iter_mut()
361            .filter(move |ent| ent.key_values.get(key).is_some_and(|v| v == value))
362    }
363
364    /// Returns an iterator over the entities with the specified classname.
365    ///
366    /// # Arguments
367    ///
368    /// * `classname` - The classname to search for.
369    pub fn find_by_classname<'a>(
370        &'a self,
371        classname: &'a str,
372    ) -> impl Iterator<Item = &'a Entity> + 'a {
373        self.find_by_keyvalue("classname", classname)
374    }
375
376    /// Returns an iterator over the entities with the specified targetname.
377    ///
378    /// # Arguments
379    ///
380    /// * `name` - The targetname to search for.
381    pub fn find_by_name<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Entity> + 'a {
382        self.find_by_keyvalue("targetname", name)
383    }
384
385    /// Returns an iterator over the entities with the specified classname, allowing modification.
386    ///
387    /// # Arguments
388    ///
389    /// * `classname` - The classname to search for.
390    pub fn find_by_classname_mut<'a>(
391        &'a mut self,
392        classname: &'a str,
393    ) -> impl Iterator<Item = &'a mut Entity> + 'a {
394        self.find_by_keyvalue_mut("classname", classname)
395    }
396
397    /// Returns an iterator over the entities with the specified targetname, allowing modification.
398    ///
399    /// # Arguments
400    ///
401    /// * `name` - The targetname to search for.
402    pub fn find_by_name_mut<'a>(
403        &'a mut self,
404        name: &'a str,
405    ) -> impl Iterator<Item = &'a mut Entity> + 'a {
406        self.find_by_keyvalue_mut("targetname", name)
407    }
408
409    /// Returns an iterator over the entities with the specified model.
410    ///
411    /// # Arguments
412    ///
413    /// * `model` - The model to search for.
414    pub fn find_by_model<'a>(&'a self, model: &'a str) -> impl Iterator<Item = &'a Entity> + 'a {
415        self.find_by_keyvalue("model", model)
416    }
417
418    /// Returns an iterator over the entities with the specified model, allowing modification.
419    ///
420    /// # Arguments
421    ///
422    /// * `model` - The model to search for.
423    pub fn find_by_model_mut<'a>(
424        &'a mut self,
425        model: &'a str,
426    ) -> impl Iterator<Item = &'a mut Entity> + 'a {
427        self.find_by_keyvalue_mut("model", model)
428    }
429
430    /// Removes an entity by its ID.
431    ///
432    /// # Arguments
433    ///
434    /// * `entity_id` - The ID of the entity to remove.
435    ///
436    /// # Returns
437    ///
438    /// An `Option` containing the removed `Entity`, if found. Returns `None`
439    /// if no entity with the given ID exists.
440    pub fn remove_entity(&mut self, entity_id: i32) -> Option<Entity> {
441        if let Some(index) = self
442            .vec
443            .iter()
444            .position(|e| e.key_values.get("id") == Some(&entity_id.to_string()))
445        {
446            Some(self.vec.remove(index))
447        } else {
448            None
449        }
450    }
451
452    /// Removes all entities that have a matching key-value pair.
453    ///
454    /// # Arguments
455    ///
456    /// * `key` - The key to check.
457    /// * `value` - The value to compare against.
458    pub fn remove_by_keyvalue(&mut self, key: &str, value: &str) {
459        self.vec
460            .retain(|ent| ent.key_values.get(key).map(|v| v != value).unwrap_or(true));
461    }
462}
463
464// utils func
465fn process_connections(map: IndexMap<String, String>) -> Option<Vec<(String, String)>> {
466    if map.is_empty() {
467        return None;
468    }
469
470    let result = map
471        .iter()
472        .flat_map(|(key, value)| {
473            value
474                .split('\r')
475                .map(move |part| (key.clone(), part.to_string()))
476        })
477        .collect();
478
479    Some(result)
480}