Skip to main content

source2_demo/entity/
container.rs

1use crate::error::EntityError;
2use crate::Entity;
3
4/// Container for all entities in the replay.
5///
6/// Entities represent all game objects (heroes, NPCs, items, wards, etc.).
7/// This container provides multiple ways to access them:
8/// - By index (direct position in entity array)
9/// - By handle (combined serial + index)
10/// - By class ID (entity type)
11/// - By class name (e.g., "CDOTA_Unit_Hero_Axe")
12///
13/// # Entity Access Patterns
14///
15/// Different scenarios require different lookup methods:
16/// - **Known class name**: Use [`get_by_class_name`](Entities::get_by_class_name)
17/// - **Iterating all**: Use [`iter`](Entities::iter)
18/// - **Known index**: Use [`get_by_index`](Entities::get_by_index)
19/// - **Finding by type**: Use iterator with filter
20///
21/// # Examples
22///
23/// ## Get the player resource
24///
25/// ```no_run
26/// use source2_demo::prelude::*;
27///
28/// # fn example(ctx: &Context) -> anyhow::Result<()> {
29/// let player_resource = ctx.entities().get_by_class_name("CDOTA_PlayerResource")?;
30/// # Ok(())
31/// # }
32/// ```
33///
34/// ## Find all heroes on a specific team
35///
36/// ```no_run
37/// use source2_demo::prelude::*;
38///
39/// # fn example(ctx: &Context) -> anyhow::Result<()> {
40/// let radiant_heroes: Vec<&Entity> = ctx.entities()
41///     .iter()
42///     .filter(|e| {
43///         e.class().name().starts_with("CDOTA_Unit_Hero_")
44///             && try_property!(e, u32, "m_iTeamNum") == Some(2) // 2 = Radiant
45///     })
46///     .collect();
47/// # Ok(())
48/// # }
49/// ```
50///
51/// ## Get entity by index
52///
53/// ```no_run
54/// use source2_demo::prelude::*;
55///
56/// # fn example(ctx: &Context) -> anyhow::Result<()> {
57/// let entity = ctx.entities().get_by_index(256)?;
58/// println!("Entity type: {}", entity.class().name());
59/// # Ok(())
60/// # }
61/// ```
62pub struct Entities {
63    pub(crate) entities_vec: Vec<Entity>,
64}
65
66impl Default for Entities {
67    fn default() -> Self {
68        Entities {
69            entities_vec: vec![Entity::default(); 8192],
70        }
71    }
72}
73
74impl Entities {
75    /// Returns an iterator over all active entities.
76    ///
77    /// Iterates only entities that are currently alive/active in the replay.
78    /// Deleted entities are automatically skipped.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use source2_demo::prelude::*;
84    /// use source2_demo::proto::*;
85    ///
86    /// #[derive(Default)]
87    /// struct MyObs;
88    ///
89    /// impl Observer for MyObs {
90    ///     fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
91    ///         let dire_heroes = ctx
92    ///             .entities()
93    ///             .iter()
94    ///             .filter(|&e| {
95    ///                 e.class().name().starts_with("CDOTA_Hero_Unit")
96    ///                     && try_property!(e, u32, "m_iTeamNum") == Some(3)
97    ///                     && try_property!(e, u32, "m_hReplicatingOtherHeroModel") == Some(u32::MAX)
98    ///             })
99    ///             .collect::<Vec<_>>();
100    ///         Ok(())
101    ///     }
102    /// }
103    /// ```
104    pub fn iter(&self) -> impl Iterator<Item = &Entity> {
105        self.entities_vec.iter().filter(|e| e.index != u32::MAX)
106    }
107
108    /// Gets an entity by its index in the entity array.
109    ///
110    /// Entity indices are in the range 0-8191. This is the fastest way to
111    /// access an entity if you already know its index.
112    ///
113    /// # Arguments
114    ///
115    /// * `index` - The entity index (0-8191)
116    ///
117    /// # Errors
118    ///
119    /// Returns [`EntityError::IndexNotFound`] if no entity exists at the given index
120    /// or if the entity at that index has been deleted.
121    ///
122    /// # Examples
123    ///
124    /// ```no_run
125    /// use source2_demo::prelude::*;
126    ///
127    /// # fn example(ctx: &Context) -> anyhow::Result<()> {
128    /// let entity = ctx.entities().get_by_index(0)?;
129    /// println!("First entity type: {}", entity.class().name());
130    /// # Ok(())
131    /// # }
132    /// ```
133    pub fn get_by_index(&self, index: usize) -> Result<&Entity, EntityError> {
134        if let Some(e) = self.entities_vec.get(index) {
135            if e.index != u32::MAX {
136                return Ok(e);
137            }
138        }
139        Err(EntityError::IndexNotFound(index))
140    }
141
142    /// Gets an entity by its handle.
143    ///
144    /// A handle combines the serial number and index into a single identifier.
145    /// This is useful when you have a handle reference from entity properties.
146    ///
147    /// # Arguments
148    ///
149    /// * `handle` - The entity handle (serial << 14 | index)
150    ///
151    /// # Errors
152    ///
153    /// Returns [`EntityError::HandleNotFound`] if no valid entity exists
154    /// for the given handle.
155    ///
156    /// # Examples
157    ///
158    /// ```no_run
159    /// use source2_demo::prelude::*;
160    ///
161    /// # fn example(ctx: &Context) -> anyhow::Result<()> {
162    /// let handle = 123; // Example handle from entity property
163    /// let entity = ctx.entities().get_by_handle(handle as usize)?;
164    /// # Ok(())
165    /// # }
166    /// ```
167    pub fn get_by_handle(&self, handle: usize) -> Result<&Entity, EntityError> {
168        self.get_by_index(handle & 0x3fff)
169            .map_err(|_| EntityError::HandleNotFound(handle))
170    }
171
172    /// Gets the first entity with the specified class ID.
173    ///
174    /// Typically only useful if you know there's only one entity of that class,
175    /// or if you only need the first one. For finding multiple entities of a type,
176    /// use [`iter`](Entities::iter) with a filter.
177    ///
178    /// # Arguments
179    ///
180    /// * `id` - The class ID to search for
181    ///
182    /// # Errors
183    ///
184    /// Returns [`EntityError::ClassIdNotFound`] if no entity with the given class ID exists.
185    ///
186    /// # Examples
187    ///
188    /// ```no_run
189    /// use source2_demo::prelude::*;
190    ///
191    /// # fn example(ctx: &Context) -> anyhow::Result<()> {
192    /// // Find entity by class ID
193    /// let entity = ctx.entities().get_by_class_id(42)?;
194    /// # Ok(())
195    /// # }
196    /// ```
197    pub fn get_by_class_id(&self, id: i32) -> Result<&Entity, EntityError> {
198        self.iter()
199            .find(|&entity| entity.class().id() == id)
200            .ok_or(EntityError::ClassIdNotFound(id))
201    }
202
203    /// Gets the first entity with the specified class name.
204    ///
205    /// This is useful for finding unique entities like "CDOTA_PlayerResource"
206    /// or specific entity types. For finding multiple entities of a class type,
207    /// use [`iter`](Entities::iter) with a filter.
208    ///
209    /// # Arguments
210    ///
211    /// * `name` - The class name to search for (e.g., "CDOTA_PlayerResource")
212    ///
213    /// # Errors
214    ///
215    /// Returns [`EntityError::ClassNameNotFound`] if no entity with the given
216    /// class name exists.
217    ///
218    /// # Examples
219    ///
220    /// ```no_run
221    /// use source2_demo::prelude::*;
222    ///
223    /// # fn example(ctx: &Context) -> anyhow::Result<()> {
224    /// // Find the player resource entity
225    /// let player_resource = ctx.entities().get_by_class_name("CDOTA_PlayerResource")?;
226    ///
227    /// // Now you can get player info from this entity
228    /// let player_name: String = property!(player_resource, "m_vecPlayerData.{:04}.m_iszPlayerName", 0);
229    /// # Ok(())
230    /// # }
231    /// ```
232    pub fn get_by_class_name(&self, name: &str) -> Result<&Entity, EntityError> {
233        self.iter()
234            .find(|&entity| entity.class().name() == name)
235            .ok_or(EntityError::ClassNameNotFound(name.to_string()))
236    }
237}