vmf_forge/vmf/
world.rs

1//! This module provides structures for representing the world block in a VMF file, which contains world geometry, hidden entities, and groups.
2
3use indexmap::IndexMap;
4#[cfg(feature = "serialization")]
5use serde::{Deserialize, Serialize};
6
7use super::common::Editor;
8use crate::utils::{To01String, get_key_ref, take_and_parse_key, take_key_owned};
9use crate::{
10    VmfBlock, VmfSerializable,
11    errors::{VmfError, VmfResult},
12};
13use std::mem;
14
15/// Represents the world block in a VMF file.
16#[derive(Debug, Default, Clone, PartialEq)]
17#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
18pub struct World {
19    /// The key-value pairs associated with the world.
20    pub key_values: IndexMap<String, String>,
21    /// The list of solids that make up the world geometry.
22    pub solids: Vec<Solid>,
23    /// The list of hidden solids in the world.
24    pub hidden: Vec<Solid>,
25    /// The groups present in the world, if any.
26    #[cfg_attr(
27        feature = "serialization",
28        serde(default, skip_serializing_if = "Option::is_none")
29    )]
30    pub group: Option<Group>,
31}
32
33impl TryFrom<VmfBlock> for World {
34    type Error = VmfError;
35
36    fn try_from(block: VmfBlock) -> VmfResult<Self> {
37        let estimated_solids = block.blocks.len().saturating_sub(1);
38        let mut world = World {
39            key_values: block.key_values,
40            solids: Vec::with_capacity(estimated_solids),
41            hidden: Vec::with_capacity(16),
42            group: None,
43        };
44
45        for mut inner_block in block.blocks {
46            match inner_block.name.as_str() {
47                "solid" => world.solids.push(Solid::try_from(inner_block)?),
48                "group" => world.group = Group::try_from(inner_block).ok(),
49                "hidden" => {
50                    if !inner_block.blocks.is_empty() {
51                        // Take ownership of the first block instead of cloning
52                        let hidden_block = mem::take(&mut inner_block.blocks[0]);
53                        world.hidden.push(Solid::try_from(hidden_block)?);
54                    }
55                }
56                _ => {
57                    // The `world` block does not support other types of blocks (except `hidden`, `group` and `solid`)
58                    #[cfg(feature = "debug_assert_info")]
59                    debug_assert!(false, "Unexpected block name: {}", inner_block.name);
60                }
61            };
62        }
63
64        Ok(world)
65    }
66}
67
68impl From<World> for VmfBlock {
69    fn from(val: World) -> Self {
70        let mut blocks = Vec::new();
71
72        // Add solids
73        for solid in val.solids {
74            blocks.push(solid.into());
75        }
76
77        // Add hidden solids
78        for hidden_solid in val.hidden {
79            blocks.push(VmfBlock {
80                name: "hidden".to_string(),
81                key_values: IndexMap::new(),
82                blocks: vec![hidden_solid.into()],
83            });
84        }
85
86        // Add groups
87        if let Some(group) = val.group {
88            blocks.push(group.into());
89        }
90
91        VmfBlock {
92            name: "world".to_string(),
93            key_values: val.key_values,
94            blocks,
95        }
96    }
97}
98
99impl VmfSerializable for World {
100    fn to_vmf_string(&self, indent_level: usize) -> String {
101        let indent = "\t".repeat(indent_level);
102        let mut output = String::with_capacity(2048);
103
104        output.push_str(&format!("{0}world\n{0}{{\n", indent));
105
106        // Adds key_values of the main block
107        for (key, value) in &self.key_values {
108            output.push_str(&format!("{}\t\"{}\" \"{}\"\n", indent, key, value));
109        }
110
111        // Solids Block
112        if !self.solids.is_empty() {
113            for solid in &self.solids {
114                output.push_str(&solid.to_vmf_string(indent_level + 1));
115            }
116        }
117
118        // Hidden Solids Block
119        if !self.hidden.is_empty() {
120            output.push_str(&format!("{0}\tHidden\n{0}\t{{\n", indent));
121            for solid in &self.hidden {
122                output.push_str(&solid.to_vmf_string(indent_level + 2));
123            }
124            output.push_str(&format!("{}\t}}\n", indent));
125        }
126
127        // Group Block
128        if let Some(group) = &self.group {
129            output.push_str(&group.to_vmf_string(indent_level + 1));
130        }
131
132        output.push_str(&format!("{}}}\n", indent));
133        output
134    }
135}
136
137/// Represents a solid object in the VMF world.
138#[derive(Debug, Default, Clone, PartialEq)]
139#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
140pub struct Solid {
141    /// The unique ID of the solid.
142    pub id: u64,
143    /// The sides of the solid.
144    pub sides: Vec<Side>,
145    /// The editor data for the solid.
146    pub editor: Editor,
147}
148
149impl TryFrom<VmfBlock> for Solid {
150    type Error = VmfError;
151
152    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
153        let mut solid = Solid {
154            id: take_and_parse_key::<u64>(&mut block.key_values, "id")?,
155            sides: Vec::with_capacity(block.blocks.len()),
156            ..Default::default()
157        };
158
159        for inner_block in block.blocks {
160            match inner_block.name.as_str() {
161                "side" => solid.sides.push(Side::try_from(inner_block)?),
162                "editor" => solid.editor = Editor::try_from(inner_block)?,
163                _ => {
164                    #[cfg(feature = "debug_assert_info")]
165                    debug_assert!(false, "Unexpected block name: {}", inner_block.name);
166                }
167            }
168        }
169
170        Ok(solid)
171    }
172}
173
174impl From<Solid> for VmfBlock {
175    fn from(val: Solid) -> Self {
176        let mut blocks = Vec::new();
177
178        // Adds sides
179        for side in val.sides {
180            blocks.push(side.into());
181        }
182
183        // Adds editor
184        blocks.push(val.editor.into());
185
186        VmfBlock {
187            name: "solid".to_string(),
188            key_values: {
189                let mut key_values = IndexMap::new();
190                key_values.insert("id".to_string(), val.id.to_string());
191                key_values
192            },
193            blocks,
194        }
195    }
196}
197
198impl VmfSerializable for Solid {
199    fn to_vmf_string(&self, indent_level: usize) -> String {
200        let indent = "\t".repeat(indent_level);
201        let mut output = String::with_capacity(256);
202
203        // Start of solid block
204        output.push_str(&format!("{0}solid\n{0}{{\n", indent));
205        output.push_str(&format!("{}\t\"id\" \"{}\"\n", indent, self.id));
206
207        // Sides
208        for side in &self.sides {
209            output.push_str(&side.to_vmf_string(indent_level + 1));
210        }
211
212        // Editor block
213        output.push_str(&self.editor.to_vmf_string(indent_level + 1));
214
215        output.push_str(&format!("{}}}\n", indent));
216
217        output
218    }
219}
220
221/// Represents a side of a solid object in the VMF world.
222#[derive(Debug, Default, Clone, PartialEq)]
223#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
224pub struct Side {
225    /// The unique ID of the side.
226    pub id: u32,
227    /// The plane equation of the side.
228    pub plane: String,
229    /// The material used on the side.
230    pub material: String,
231    /// The U axis of the texture coordinates.
232    pub u_axis: String,
233    /// The V axis of the texture coordinates.
234    pub v_axis: String,
235    /// The rotation of the texture.
236    #[cfg_attr(
237        feature = "serialization",
238        serde(default, skip_serializing_if = "Option::is_none")
239    )]
240    pub rotation: Option<f32>,
241    /// The scale of the lightmap.
242    pub lightmap_scale: u16,
243    /// The smoothing groups that this side belongs to.
244    pub smoothing_groups: i32,
245    /// flags
246    #[cfg_attr(
247        feature = "serialization",
248        serde(default, skip_serializing_if = "Option::is_none")
249    )]
250    pub flags: Option<u32>,
251    /// The displacement info of the side, if any.
252    #[cfg_attr(
253        feature = "serialization",
254        serde(default, skip_serializing_if = "Option::is_none")
255    )]
256    pub dispinfo: Option<DispInfo>,
257}
258
259impl TryFrom<VmfBlock> for Side {
260    type Error = VmfError;
261
262    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
263        let kv = &mut block.key_values;
264        // Use iter_mut().find() to get a mutable reference without consuming the vector yet
265        // We'll use mem::take later if found.
266        let dispinfo_block = block.blocks.iter_mut().find(|b| b.name == "dispinfo");
267
268        // Take ownership of required String fields
269        let plane = take_key_owned(kv, "plane")?;
270        let material = take_key_owned(kv, "material")?;
271        let u_axis = take_key_owned(kv, "uaxis")?;
272        let v_axis = take_key_owned(kv, "vaxis")?;
273
274        // Parse required numeric fields, taking ownership
275        let id = take_and_parse_key::<u32>(kv, "id")?;
276        let lightmap_scale = take_and_parse_key::<u16>(kv, "lightmapscale").unwrap_or(16);
277        let smoothing_groups = take_and_parse_key::<i32>(kv, "smoothing_groups").unwrap_or(0);
278
279        // Parse optional numeric fields using .ok()
280        // This will consume the key if present and parseable, otherwise yield None
281        let rotation = take_and_parse_key::<f32>(kv, "rotation").ok();
282        let flags = take_and_parse_key::<u32>(kv, "flags").ok();
283
284        let dispinfo = match dispinfo_block {
285            Some(block) => Some(DispInfo::try_from(mem::take(block))?),
286            None => None,
287        };
288
289        Ok(Side {
290            id,
291            plane,
292            material,
293            u_axis,
294            v_axis,
295            rotation,
296            lightmap_scale,
297            smoothing_groups,
298            flags,
299            dispinfo,
300        })
301    }
302}
303
304impl From<Side> for VmfBlock {
305    fn from(val: Side) -> Self {
306        let mut key_values = IndexMap::new();
307        key_values.insert("id".to_string(), val.id.to_string());
308        key_values.insert("plane".to_string(), val.plane);
309        key_values.insert("material".to_string(), val.material);
310        key_values.insert("uaxis".to_string(), val.u_axis);
311        key_values.insert("vaxis".to_string(), val.v_axis);
312        key_values.insert("lightmapscale".to_string(), val.lightmap_scale.to_string());
313        key_values.insert(
314            "smoothing_groups".to_string(),
315            val.smoothing_groups.to_string(),
316        );
317
318        if let Some(rotation) = val.rotation {
319            key_values.insert("rotation".to_string(), rotation.to_string());
320        }
321        if let Some(flags) = val.flags {
322            key_values.insert("flags".to_string(), flags.to_string());
323        }
324
325        let mut blocks = Vec::new();
326        if let Some(dispinfo) = val.dispinfo {
327            blocks.push(dispinfo.into());
328        }
329
330        VmfBlock {
331            name: "side".to_string(),
332            key_values,
333            blocks,
334        }
335    }
336}
337
338impl VmfSerializable for Side {
339    fn to_vmf_string(&self, indent_level: usize) -> String {
340        let indent = "\t".repeat(indent_level);
341        let mut output = String::with_capacity(256);
342
343        // Start of Side block
344        output.push_str(&format!("{0}side\n{0}{{\n", indent));
345
346        // Writes all key-value pairs with appropriate indentation
347        output.push_str(&format!("{}\t\"id\" \"{}\"\n", indent, self.id));
348        output.push_str(&format!("{}\t\"plane\" \"{}\"\n", indent, self.plane));
349        output.push_str(&format!("{}\t\"material\" \"{}\"\n", indent, self.material));
350        output.push_str(&format!("{}\t\"uaxis\" \"{}\"\n", indent, self.u_axis));
351        output.push_str(&format!("{}\t\"vaxis\" \"{}\"\n", indent, self.v_axis));
352
353        if let Some(rotation) = self.rotation {
354            output.push_str(&format!("{}\t\"rotation\" \"{}\"\n", indent, rotation));
355        }
356
357        output.push_str(&format!(
358            "{}\t\"lightmapscale\" \"{}\"\n",
359            indent, self.lightmap_scale
360        ));
361        output.push_str(&format!(
362            "{}\t\"smoothing_groups\" \"{}\"\n",
363            indent, self.smoothing_groups
364        ));
365
366        // Adds the flag if it exists
367        if let Some(flags) = self.flags {
368            output.push_str(&format!("{}\t\"flags\" \"{}\"\n", indent, flags));
369        }
370
371        if let Some(dispinfo) = &self.dispinfo {
372            output.push_str(&dispinfo.to_vmf_string(indent_level + 1));
373        }
374
375        // End of Side block
376        output.push_str(&format!("{0}}}\n", indent));
377
378        output
379    }
380}
381
382/// Finds a block with the specified name in a vector of `VmfBlock`s,
383/// removes it from the vector, and returns ownership.
384/// Uses swap_remove for O(1) removal, but changes the order of remaining blocks.
385///
386/// # Arguments
387///
388/// * `blocks` - A mutable reference to a vector of `VmfBlock`s to search and modify.
389/// * `name` - The name of the block to search for.
390///
391/// # Returns
392///
393/// A `Result` containing the owned `VmfBlock` with the specified name,
394/// or a `VmfError` if no such block is found.
395#[inline(always)]
396fn take_block(blocks: &mut Vec<VmfBlock>, name: &str) -> VmfResult<VmfBlock> {
397    let index = blocks.iter().position(|b| b.name == name);
398    match index {
399        // Some(idx) => Ok(mem::take(&mut blocks[idx])),
400        Some(idx) => Ok(blocks.swap_remove(idx)),
401        None => Err(VmfError::InvalidFormat(format!(
402            "Missing {} block in dispinfo",
403            name
404        ))),
405    }
406}
407
408/// Represents the displacement information for a side.
409#[derive(Debug, Default, Clone, PartialEq)]
410#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
411pub struct DispInfo {
412    /// The power of the displacement map (2, 3, or 4).
413    pub power: u8,
414    /// The starting position of the displacement.
415    pub start_position: String,
416    /// Flags for the displacement.
417    #[cfg_attr(
418        feature = "serialization",
419        serde(default, skip_serializing_if = "Option::is_none")
420    )]
421    pub flags: Option<u32>,
422    /// The elevation of the displacement.
423    pub elevation: f32,
424    /// Whether the displacement is subdivided.
425    pub subdiv: bool,
426    /// The normals for each vertex in the displacement.
427    pub normals: DispRows,
428    /// The distances for each vertex in the displacement.
429    pub distances: DispRows,
430    /// The offsets for each vertex in the displacement.
431    pub offsets: DispRows,
432    /// The offset normals for each vertex in the displacement.
433    pub offset_normals: DispRows,
434    /// The alpha values for each vertex in the displacement.
435    pub alphas: DispRows,
436    /// The triangle tags for the displacement.
437    pub triangle_tags: DispRows,
438    /// The allowed vertices for the displacement.
439    pub allowed_verts: IndexMap<String, Vec<i32>>,
440}
441
442impl TryFrom<VmfBlock> for DispInfo {
443    type Error = VmfError;
444
445    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
446        // Extract required child blocks first, consuming them from block.blocks
447        let normals_block = take_block(&mut block.blocks, "normals")?;
448        let distances_block = take_block(&mut block.blocks, "distances")?;
449        let alphas_block = take_block(&mut block.blocks, "alphas")?;
450        let triangle_tags_block = take_block(&mut block.blocks, "triangle_tags")?;
451        let allowed_verts_block = take_block(&mut block.blocks, "allowed_verts")?;
452
453        // These blocks may not be present in the decompiled vmf. Why?
454        let offsets = block
455            .blocks
456            .iter_mut()
457            .find(|b| b.name == "offsets")
458            .map_or_else(
459                || Ok(DispRows::default()),
460                |b| DispRows::try_from(mem::take(b)),
461            )?;
462
463        let offset_normals = block
464            .blocks
465            .iter_mut()
466            .find(|b| b.name == "offset_normals")
467            .map_or_else(
468                || Ok(DispRows::default()),
469                |b| DispRows::try_from(mem::take(b)),
470            )?;
471
472        // Extract key-values from the parent dispinfo block
473        let kv = &mut block.key_values;
474        let power = take_and_parse_key::<u8>(kv, "power")?;
475        let start_position = take_key_owned(kv, "startposition")?;
476        let flags = take_and_parse_key::<u32>(kv, "flags").ok();
477        let elevation = take_and_parse_key::<f32>(kv, "elevation")?;
478        let subdiv = get_key_ref(kv, "subdiv")? == "1";
479
480        // Convert extracted blocks
481        let normals = DispRows::try_from(normals_block)?;
482        let distances = DispRows::try_from(distances_block)?;
483        let alphas = DispRows::try_from(alphas_block)?;
484        let triangle_tags = DispRows::try_from(triangle_tags_block)?;
485        let allowed_verts = DispInfo::parse_allowed_verts(allowed_verts_block)?;
486
487        Ok(DispInfo {
488            power,
489            start_position,
490            flags,
491            elevation,
492            subdiv,
493            normals,
494            distances,
495            offsets,
496            offset_normals,
497            alphas,
498            triangle_tags,
499            allowed_verts,
500        })
501    }
502}
503
504impl From<DispInfo> for VmfBlock {
505    fn from(val: DispInfo) -> Self {
506        let blocks = vec![
507            val.normals.into_vmf_block("normals"),
508            val.distances.into_vmf_block("distances"),
509            val.offsets.into_vmf_block("offsets"),
510            val.offset_normals.into_vmf_block("offset_normals"),
511            val.alphas.into_vmf_block("alphas"),
512            val.triangle_tags.into_vmf_block("triangle_tags"),
513            DispInfo::allowed_verts_into_vmf_block(val.allowed_verts),
514        ];
515
516        let mut key_values = IndexMap::new();
517        key_values.insert("power".to_string(), val.power.to_string());
518        key_values.insert("startposition".to_string(), val.start_position);
519        key_values.insert("elevation".to_string(), val.elevation.to_string());
520        key_values.insert("subdiv".to_string(), val.subdiv.to_01_string());
521
522        if let Some(flags) = val.flags {
523            key_values.insert("flags".to_string(), flags.to_string());
524        }
525
526        VmfBlock {
527            name: "dispinfo".to_string(),
528            key_values,
529            blocks,
530        }
531    }
532}
533
534impl VmfSerializable for DispInfo {
535    fn to_vmf_string(&self, indent_level: usize) -> String {
536        let indent = "\t".repeat(indent_level);
537        let mut output = String::with_capacity(256);
538
539        output.push_str(&format!("{}dispinfo\n", indent));
540        output.push_str(&format!("{}{{\n", indent));
541        output.push_str(&format!("{}\t\"power\" \"{}\"\n", indent, self.power));
542        output.push_str(&format!(
543            "{}\t\"startposition\" \"{}\"\n",
544            indent, self.start_position
545        ));
546
547        // Adds the flag if it exists
548        if let Some(flags) = self.flags {
549            output.push_str(&format!("{}\t\"flags\" \"{}\"\n", indent, flags));
550        }
551
552        output.push_str(&format!(
553            "{}\t\"elevation\" \"{}\"\n",
554            indent, self.elevation
555        ));
556        output.push_str(&format!(
557            "{}\t\"subdiv\" \"{}\"\n",
558            indent,
559            self.subdiv.to_01_string()
560        ));
561        output.push_str(&self.normals.to_vmf_string(indent_level + 1, "normals"));
562        output.push_str(&self.distances.to_vmf_string(indent_level + 1, "distances"));
563        output.push_str(&self.offsets.to_vmf_string(indent_level + 1, "offsets"));
564        output.push_str(
565            &self
566                .offset_normals
567                .to_vmf_string(indent_level + 1, "offset_normals"),
568        );
569        output.push_str(&self.alphas.to_vmf_string(indent_level + 1, "alphas"));
570        output.push_str(
571            &self
572                .triangle_tags
573                .to_vmf_string(indent_level + 1, "triangle_tags"),
574        );
575        output.push_str(&Self::allowed_verts_to_vmf_string(
576            &self.allowed_verts,
577            indent_level + 1,
578        ));
579        output.push_str(&format!("{}}}\n", indent));
580
581        output
582    }
583}
584
585impl DispInfo {
586    /// Parses the allowed vertices from a `VmfBlock`.
587    ///
588    /// # Arguments
589    ///
590    /// * `block` - The `VmfBlock` containing the allowed vertices data.
591    ///
592    /// # Returns
593    ///
594    /// A `Result` containing an `IndexMap` of allowed vertices, or a `VmfError` if parsing fails.
595    fn parse_allowed_verts(block: VmfBlock) -> VmfResult<IndexMap<String, Vec<i32>>> {
596        let mut allowed_verts = IndexMap::new();
597        for (key, value) in block.key_values.into_iter() {
598            let verts: VmfResult<Vec<i32>> = value
599                .split_whitespace()
600                .map(|s| {
601                    s.parse::<i32>().map_err(|e| VmfError::ParseInt {
602                        source: e,
603                        key: s.to_string(),
604                    })
605                })
606                .collect();
607            allowed_verts.insert(key, verts?);
608        }
609        Ok(allowed_verts)
610    }
611
612    /// Converts the allowed vertices data into a `VmfBlock`.
613    ///
614    /// # Arguments
615    ///
616    /// * `allowed_verts` - The `IndexMap` containing the allowed vertices data.
617    ///
618    /// # Returns
619    ///
620    /// A `VmfBlock` representing the allowed vertices data.
621    fn allowed_verts_into_vmf_block(allowed_verts: IndexMap<String, Vec<i32>>) -> VmfBlock {
622        let mut key_values = IndexMap::new();
623        for (key, values) in allowed_verts {
624            key_values.insert(
625                key,
626                values
627                    .iter()
628                    .map(|v| v.to_string())
629                    .collect::<Vec<String>>()
630                    .join(" "),
631            );
632        }
633
634        VmfBlock {
635            name: "allowed_verts".to_string(),
636            key_values,
637            blocks: Vec::new(),
638        }
639    }
640
641    /// Converts the allowed vertices data into a string representation.
642    ///
643    /// # Arguments
644    ///
645    /// * `allowed_verts` - A reference to an `IndexMap` containing the allowed vertices data.
646    /// * `indent_level` - The indentation level for formatting.
647    ///
648    /// # Returns
649    ///
650    /// A string representation of the allowed vertices data.
651    fn allowed_verts_to_vmf_string(
652        allowed_verts: &IndexMap<String, Vec<i32>>,
653        indent_level: usize,
654    ) -> String {
655        let indent = "\t".repeat(indent_level);
656        let mut output = String::new();
657
658        output.push_str(&format!("{}allowed_verts\n", indent));
659        output.push_str(&format!("{}{{\n", indent));
660        for (key, values) in allowed_verts {
661            output.push_str(&format!(
662                "{}\t\"{}\" \"{}\"\n",
663                indent,
664                key,
665                values
666                    .iter()
667                    .map(|v| v.to_string())
668                    .collect::<Vec<String>>()
669                    .join(" ")
670            ));
671        }
672        output.push_str(&format!("{}}}\n", indent));
673
674        output
675    }
676}
677
678/// Represents rows of data for displacement information, such as normals, distances, offsets, etc.
679#[derive(Debug, Default, Clone, PartialEq, Eq)]
680#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
681pub struct DispRows {
682    /// The rows of data, each represented as a string.
683    pub rows: Vec<String>,
684}
685
686impl TryFrom<VmfBlock> for DispRows {
687    type Error = VmfError;
688
689    fn try_from(block: VmfBlock) -> VmfResult<Self> {
690        let mut rows = Vec::with_capacity(block.key_values.len());
691        for (key, value) in block.key_values {
692            if let Some(stripped_idx) = key.strip_prefix("row") {
693                let index = stripped_idx
694                    .parse::<usize>()
695                    .map_err(|e| VmfError::ParseInt {
696                        source: e,
697                        key: key.to_string(),
698                    })?;
699                if index >= rows.len() {
700                    rows.resize(index + 1, String::new());
701                }
702                rows[index] = value;
703            }
704        }
705        Ok(DispRows { rows })
706    }
707}
708
709impl DispRows {
710    /// Converts the `DispRows` data into a `VmfBlock` with the specified name.
711    ///
712    /// # Arguments
713    ///
714    /// * `self` - The `DispRows` instance to convert.
715    /// * `name` - The name of the block.
716    ///
717    /// # Returns
718    ///
719    /// A `VmfBlock` representing the `DispRows` data.
720    fn into_vmf_block(self, name: &str) -> VmfBlock {
721        let mut key_values = IndexMap::new();
722        for (i, row) in self.rows.into_iter().enumerate() {
723            key_values.insert(format!("row{}", i), row);
724        }
725
726        VmfBlock {
727            name: name.to_string(),
728            key_values,
729            blocks: Vec::new(),
730        }
731    }
732
733    /// Converts the `DispRows` data into a string representation with the specified name and indentation level.
734    ///
735    /// # Arguments
736    ///
737    /// * `self` - A reference to the `DispRows` instance.
738    /// * `indent_level` - The indentation level for formatting.
739    /// * `name` - The name of the block.
740    ///
741    /// # Returns
742    ///
743    /// A string representation of the `DispRows` data.
744    fn to_vmf_string(&self, indent_level: usize, name: &str) -> String {
745        let indent = "\t".repeat(indent_level);
746        let mut output = String::with_capacity(32);
747
748        output.push_str(&format!("{}{}\n", indent, name));
749        output.push_str(&format!("{}{{\n", indent));
750        for (i, row) in self.rows.iter().enumerate() {
751            output.push_str(&format!("{}\t\"row{}\" \"{}\"\n", indent, i, row));
752        }
753        output.push_str(&format!("{}}}\n", indent));
754
755        output
756    }
757}
758
759/// Represents a group in the VMF world.
760#[derive(Debug, Default, Clone, PartialEq, Eq)]
761#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
762pub struct Group {
763    /// The unique ID of the group.
764    pub id: u32,
765    /// The editor data for the group.
766    pub editor: Editor,
767}
768
769impl TryFrom<VmfBlock> for Group {
770    type Error = VmfError;
771
772    fn try_from(mut block: VmfBlock) -> VmfResult<Self> {
773        let mut editor = None;
774        for inner_block in block.blocks {
775            if inner_block.name.eq_ignore_ascii_case("editor") {
776                editor = Some(Editor::try_from(inner_block)?);
777            }
778        }
779
780        Ok(Self {
781            id: take_and_parse_key::<u32>(&mut block.key_values, "id")?,
782            editor: editor.unwrap_or_default(),
783        })
784    }
785}
786
787impl From<Group> for VmfBlock {
788    fn from(val: Group) -> Self {
789        let mut blocks = Vec::with_capacity(2);
790
791        // Adds Editor block
792        blocks.push(val.editor.into());
793
794        VmfBlock {
795            name: "group".to_string(),
796            key_values: {
797                let mut key_values = IndexMap::new();
798                key_values.insert("id".to_string(), val.id.to_string());
799                key_values
800            },
801            blocks,
802        }
803    }
804}
805
806impl VmfSerializable for Group {
807    fn to_vmf_string(&self, indent_level: usize) -> String {
808        let indent = "\t".repeat(indent_level);
809        let mut output = String::with_capacity(64);
810
811        // Writes the main entity block
812        output.push_str(&format!("{0}group\n{0}{{\n", indent));
813        output.push_str(&format!("{}\t\"id\" \"{}\"\n", indent, self.id));
814
815        // Editor block
816        output.push_str(&self.editor.to_vmf_string(indent_level + 1));
817
818        output.push_str(&format!("{}}}\n", indent));
819
820        output
821    }
822}