vmf_forge/vmf/
entities.rs

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