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