Skip to main content

source2_demo/string_table/
container.rs

1use crate::error::StringTableError;
2use crate::string_table::*;
3use crate::HashMap;
4
5/// Container managing all string tables in a replay.
6///
7/// String tables store game data in key-value pairs organized by table name.
8///
9/// # Examples
10///
11/// ## Iterating all tables
12///
13/// ```no_run
14/// use source2_demo::prelude::*;
15///
16/// # fn example(ctx: &Context) {
17/// for table in ctx.string_tables().iter() {
18///     println!("Table: {} ({} rows)", table.name(), table.iter().count());
19/// }
20/// # }
21/// ```
22///
23/// ## Accessing a specific table
24///
25/// ```no_run
26/// use source2_demo::prelude::*;
27///
28/// # fn example(ctx: &Context) -> anyhow::Result<()> {
29/// // Get by table name
30/// let modifiers = ctx.string_tables().get_by_name("ActiveModifiers")?;
31/// println!("Active modifiers: {}", modifiers.iter().count());
32///
33/// // Get by table ID
34/// let table = ctx.string_tables().get_by_id(0)?;
35/// println!("Table at index 0: {}", table.name());
36/// # Ok(())
37/// # }
38/// ```
39///
40/// ## Extracting player data from userinfo
41///
42/// ```no_run
43/// use source2_demo::prelude::*;
44/// use source2_demo::proto::CMsgPlayerInfo;
45///
46/// # fn example(ctx: &Context) -> anyhow::Result<()> {
47/// let userinfo = ctx.string_tables().get_by_name("userinfo")?;
48///
49/// // Read player info for slot 0
50/// let player_row = userinfo.get_row_by_index(0)?;
51/// if let Some(data) = player_row.value() {
52///     let player_info = CMsgPlayerInfo::decode(data)?;
53///     println!("Player: {}", player_info.name());
54/// }
55/// # Ok(())
56/// # }
57/// ```
58#[derive(Default, Clone)]
59pub struct StringTables {
60    pub(crate) tables: Vec<StringTable>,
61    pub(crate) name_to_table: HashMap<String, usize>,
62}
63
64impl StringTables {
65    /// Returns an iterator over all string tables.
66    ///
67    /// Useful for discovering available tables or performing operations
68    /// on all tables regardless of their names.
69    pub fn iter(&self) -> impl Iterator<Item = &StringTable> {
70        self.tables.iter()
71    }
72
73    /// Gets a string table by its numeric ID/index.
74    ///
75    /// # Arguments
76    ///
77    /// * `id` - The numeric index of the table
78    ///
79    /// # Errors
80    ///
81    /// Returns [`StringTableError::TableNotFoundById`] if no table exists at the given ID.
82    ///
83    /// # Examples
84    ///
85    /// ```no_run
86    /// use source2_demo::prelude::*;
87    ///
88    /// # fn example(ctx: &Context) -> anyhow::Result<()> {
89    /// let table = ctx.string_tables().get_by_id(5)?;
90    /// println!("Table: {}", table.name());
91    /// # Ok(())
92    /// # }
93    /// ```
94    pub fn get_by_id(&self, id: usize) -> Result<&StringTable, StringTableError> {
95        self.tables
96            .get(id)
97            .ok_or(StringTableError::TableNotFoundById(id as i32))
98    }
99
100    /// Gets a string table by its name.
101    ///
102    /// This is the most common way to access string tables since you typically
103    /// know which table (e.g., "userinfo", "ActiveModifiers") you need.
104    ///
105    /// # Arguments
106    ///
107    /// * `name` - The name of the table (case-sensitive)
108    ///
109    /// # Errors
110    ///
111    /// Returns [`StringTableError::TableNotFoundByName`] if no table with the given
112    /// name exists.
113    ///
114    /// # Examples
115    ///
116    /// ```no_run
117    /// use source2_demo::prelude::*;
118    ///
119    /// # fn example(ctx: &Context) -> anyhow::Result<()> {
120    /// // Get the userinfo table (contains player info)
121    /// let userinfo = ctx.string_tables().get_by_name("userinfo")?;
122    ///
123    /// // Get the active modifiers table
124    /// let modifiers = ctx.string_tables().get_by_name("ActiveModifiers")?;
125    /// # Ok(())
126    /// # }
127    /// ```
128    pub fn get_by_name(&self, name: &str) -> Result<&StringTable, StringTableError> {
129        self.name_to_table
130            .get(name)
131            .ok_or_else(|| StringTableError::TableNotFoundByName(name.to_string()))
132            .map(|&idx| &self.tables[idx])
133    }
134
135    pub(crate) fn get_by_name_mut(
136        &mut self,
137        name: &str,
138    ) -> Result<&mut StringTable, StringTableError> {
139        self.name_to_table
140            .get(name)
141            .ok_or_else(|| StringTableError::TableNotFoundByName(name.to_string()))
142            .map(|&idx| self.tables.get_mut(idx).unwrap())
143    }
144}