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}