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}