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}