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(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
82 /// the given ID.
83 ///
84 /// # Examples
85 ///
86 /// ```no_run
87 /// use source2_demo::prelude::*;
88 ///
89 /// # fn example(ctx: &Context) -> anyhow::Result<()> {
90 /// let table = ctx.string_tables().get_by_id(5)?;
91 /// println!("Table: {}", table.name());
92 /// # Ok(())
93 /// # }
94 /// ```
95 pub fn get_by_id(&self, id: usize) -> Result<&StringTable, StringTableError> {
96 self.tables
97 .get(id)
98 .ok_or(StringTableError::TableNotFoundById(id as i32))
99 }
100
101 /// Gets a string table by its name.
102 ///
103 /// This is the most common way to access string tables since you typically
104 /// know which table (e.g., "userinfo", "ActiveModifiers") you need.
105 ///
106 /// # Arguments
107 ///
108 /// * `name` - The name of the table (case-sensitive)
109 ///
110 /// # Errors
111 ///
112 /// Returns [`StringTableError::TableNotFoundByName`] if no table with the
113 /// given name exists.
114 ///
115 /// # Examples
116 ///
117 /// ```no_run
118 /// use source2_demo::prelude::*;
119 ///
120 /// # fn example(ctx: &Context) -> anyhow::Result<()> {
121 /// // Get the userinfo table (contains player info)
122 /// let userinfo = ctx.string_tables().get_by_name("userinfo")?;
123 ///
124 /// // Get the active modifiers table
125 /// let modifiers = ctx.string_tables().get_by_name("ActiveModifiers")?;
126 /// # Ok(())
127 /// # }
128 /// ```
129 pub fn get_by_name(&self, name: &str) -> Result<&StringTable, StringTableError> {
130 self.name_to_table
131 .get(name)
132 .ok_or_else(|| StringTableError::TableNotFoundByName(name.to_string()))
133 .map(|&idx| &self.tables[idx])
134 }
135
136 pub(crate) fn get_by_name_mut(
137 &mut self,
138 name: &str,
139 ) -> Result<&mut StringTable, StringTableError> {
140 self.name_to_table
141 .get(name)
142 .ok_or_else(|| StringTableError::TableNotFoundByName(name.to_string()))
143 .map(|&idx| self.tables.get_mut(idx).unwrap())
144 }
145}