source2_demo/parser/observer.rs
1use crate::parser::Context;
2use crate::proto::*;
3use crate::{Entity, EntityEvents, GameEvent, StringTable};
4
5#[cfg(feature = "dota")]
6use crate::event::CombatLogEntry;
7
8/// Result type for observers ([`anyhow::Result`])
9pub type ObserverResult = anyhow::Result<()>;
10
11
12bitflags::bitflags! {
13 /// Bitflags for declaring observer interests.
14 ///
15 /// Use these flags in the [`Observer::interests`] method to specify which
16 /// events your observer wants to receive. This allows the parser to skip
17 /// unnecessary processing for events no observer cares about.
18 ///
19 /// # Examples
20 ///
21 /// ```
22 /// use source2_demo::prelude::*;
23 ///
24 /// #[derive(Default)]
25 /// struct EntityTracker;
26 ///
27 /// impl Observer for EntityTracker {
28 /// fn interests(&self) -> Interests {
29 /// // Track entities and receive tick events
30 /// Interests::ENABLE_ENTITY | Interests::TRACK_ENTITY | Interests::TICK_START
31 /// }
32 ///
33 /// fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
34 /// // Handle entity updates
35 /// Ok(())
36 /// }
37 ///
38 /// fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
39 /// // Handle tick start
40 /// Ok(())
41 /// }
42 /// }
43 /// ```
44 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
45 pub struct Interests: u64 {
46 /// Interest in demo commands (EDemoCommands)
47 const DEMO = 1 << 0;
48 /// Interest in net messages (NetMessages)
49 const NET = 1 << 1;
50 /// Interest in SVC messages (SvcMessages)
51 const SVC = 1 << 2;
52
53 /// Interest in base user messages (EBaseUserMessages)
54 const BASE_UM = 1 << 3;
55 /// Interest in base game events (EBaseGameEvents) and game events
56 const BASE_GE = 1 << 4;
57
58 /// Interest in tick start events
59 const TICK_START = 1 << 5;
60 /// Interest in tick end events
61 const TICK_END = 1 << 6;
62
63 /// Enable entity tracking (required for entity callbacks)
64 const ENABLE_ENTITY = 1 << 7;
65 /// Interest in entity create/update/delete events
66 const TRACK_ENTITY = 1 << 8;
67 /// Enable string table tracking
68 const ENABLE_STRINGTAB = 1 << 9;
69 /// Interest in string table update events
70 const TRACK_STRINGTAB = 1 << 10;
71
72 /// Interest in replay end event
73 const STOP = 1 << 11;
74
75 #[cfg(feature = "dota")]
76 /// Interest in Dota 2 user messages (EDotaUserMessages)
77 const DOTA_UM = 1 << 12;
78
79 #[cfg(feature = "dota")]
80 /// Interest in combat log entries (Dota 2 only)
81 const COMBAT_LOG = 1 << 13;
82
83
84 #[cfg(feature = "deadlock")]
85 /// Interest in Citadel/Deadlock user messages (CitadelUserMessageIds)
86 const CITA_UM = 1 << 14;
87
88 #[cfg(feature = "deadlock")]
89 /// Interest in Citadel/Deadlock game events (ECitadelGameEvents)
90 const CITA_GE = 1 << 15;
91
92 #[cfg(feature = "cs2")]
93 /// Interest in CS2 user messages (ECstrike15UserMessages)
94 const CS2_UM = 1 << 16;
95
96 #[cfg(feature = "cs2")]
97 /// Interest in CS2 game events (ECsgoGameEvents)
98 const CS2_GE = 1 << 17;
99 }
100}
101
102
103/// Observer trait for handling demo file events.
104///
105/// Implement this trait to receive callbacks as the parser processes the demo file.
106/// You can either implement it manually or use the `#[observer]` attribute macro
107/// for a more convenient approach.
108///
109/// # Interest-based Filtering
110///
111/// The [`interests`](Observer::interests) method allows you to declare which events
112/// your observer wants to receive. This is crucial for performance as it allows the
113/// parser to skip processing events that no observer cares about.
114///
115/// # Examples
116///
117/// ## Using the `#[observer]` macro (recommended)
118///
119/// ```no_run
120/// use source2_demo::prelude::*;
121/// use source2_demo_protobufs::CDotaUserMsgChatMessage;
122///
123/// #[derive(Default)]
124/// struct GameStats {
125/// combat_logs: u32,
126/// messages: u32,
127/// }
128///
129/// #[observer]
130/// impl GameStats {
131/// #[on_message]
132/// fn on_chat_msg(&mut self, ctx: &Context, msg: CDotaUserMsgChatMessage) -> ObserverResult {
133/// self.messages += 1;
134/// Ok(())
135/// }
136///
137/// #[on_combat_log]
138/// fn on_combat_log(&mut self, ctx: &Context, entry: &CombatLogEntry) -> ObserverResult {
139/// self.combat_logs += 1;
140/// Ok(())
141/// }
142/// }
143/// ```
144///
145/// ## Manual implementation
146///
147/// ```no_run
148/// use source2_demo::prelude::*;
149///
150/// #[derive(Default)]
151/// struct EntityCounter {
152/// count: usize,
153/// }
154///
155/// impl Observer for EntityCounter {
156/// fn interests(&self) -> Interests {
157/// Interests::ENABLE_ENTITY | Interests::TRACK_ENTITY
158/// }
159///
160/// fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
161/// if event == EntityEvents::Created {
162/// self.count += 1;
163/// }
164/// Ok(())
165/// }
166/// }
167/// ```
168///
169/// ## Combining multiple interests
170///
171/// ```no_run
172/// use source2_demo::prelude::*;
173///
174/// #[derive(Default)]
175/// struct MultiObserver;
176///
177/// impl Observer for MultiObserver {
178/// fn interests(&self) -> Interests {
179/// Interests::TICK_START
180/// | Interests::TICK_END
181/// | Interests::ENABLE_ENTITY
182/// | Interests::TRACK_ENTITY
183/// }
184///
185/// fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
186/// println!("Tick {}", ctx.tick());
187/// Ok(())
188/// }
189///
190/// fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
191/// // Process entities
192/// Ok(())
193/// }
194/// }
195/// ```
196#[allow(unused_variables)]
197pub trait Observer {
198 /// Declares which events this observer is interested in.
199 ///
200 /// Return an empty [`Interests`] to receive no events, or combine flags
201 /// using the `|` operator. This method is called once when the observer
202 /// is registered.
203 ///
204 /// # Default
205 ///
206 /// Returns [`Interests::empty()`] by default (no events).
207 fn interests(&self) -> Interests {
208 Interests::empty()
209 }
210
211 /// Called when a demo command is received.
212 ///
213 /// Requires [`Interests::DEMO`] to be set.
214 #[cold]
215 #[inline(never)]
216 fn on_demo_command(
217 &mut self,
218 ctx: &Context,
219 msg_type: EDemoCommands,
220 msg: &[u8],
221 ) -> ObserverResult {
222 Ok(())
223 }
224
225 /// Called when a net message is received.
226 ///
227 /// Requires [`Interests::NET`] to be set.
228 #[cold]
229 #[inline(never)]
230 fn on_net_message(
231 &mut self,
232 ctx: &Context,
233 msg_type: NetMessages,
234 msg: &[u8],
235 ) -> ObserverResult {
236 Ok(())
237 }
238
239 /// Called when an SVC (server-to-client) message is received.
240 ///
241 /// Requires [`Interests::SVC`] to be set.
242 #[cold]
243 #[inline(never)]
244 fn on_svc_message(
245 &mut self,
246 ctx: &Context,
247 msg_type: SvcMessages,
248 msg: &[u8],
249 ) -> ObserverResult {
250 Ok(())
251 }
252
253 /// Called when a base user message is received.
254 ///
255 /// Requires [`Interests::BASE_UM`] to be set.
256 #[cold]
257 #[inline(never)]
258 fn on_base_user_message(
259 &mut self,
260 ctx: &Context,
261 msg_type: EBaseUserMessages,
262 msg: &[u8],
263 ) -> ObserverResult {
264 Ok(())
265 }
266
267 /// Called when a base game event is received.
268 ///
269 /// Requires [`Interests::BASE_GE`] to be set.
270 #[cold]
271 #[inline(never)]
272 fn on_base_game_event(
273 &mut self,
274 ctx: &Context,
275 msg_type: EBaseGameEvents,
276 msg: &[u8],
277 ) -> ObserverResult {
278 Ok(())
279 }
280
281 /// Called at the start of each tick.
282 ///
283 /// Requires [`Interests::TICK_START`] to be set.
284 #[cold]
285 #[inline(never)]
286 fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
287 Ok(())
288 }
289
290 /// Called at the end of each tick.
291 ///
292 /// Requires [`Interests::TICK_END`] to be set.
293 #[cold]
294 #[inline(never)]
295 fn on_tick_end(&mut self, ctx: &Context) -> ObserverResult {
296 Ok(())
297 }
298
299 /// Called when an entity is created, updated, or deleted.
300 ///
301 /// Requires [`Interests::TRACK_ENTITY`] and [`Interests::ENABLE_ENTITY`] to be set.
302 #[cold]
303 #[inline(never)]
304 fn on_entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
305 Ok(())
306 }
307
308 /// Called when a game event occurs.
309 ///
310 /// Requires [`Interests::BASE_GE`] to be set.
311 #[cold]
312 #[inline(never)]
313 fn on_game_event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
314 Ok(())
315 }
316
317 /// Called when a string table is updated.
318 ///
319 /// Requires [`Interests::TRACK_STRINGTAB`] and [`Interests::ENABLE_STRINGTAB`] to be set.
320 #[cold]
321 #[inline(never)]
322 fn on_string_table(
323 &mut self,
324 ctx: &Context,
325 st: &StringTable,
326 modified: &[i32],
327 ) -> ObserverResult {
328 Ok(())
329 }
330
331 /// Called when the replay ends.
332 ///
333 /// Requires [`Interests::STOP`] to be set.
334 /// This is the last callback before parsing completes.
335 ///
336 /// # Arguments
337 ///
338 /// * `ctx` - Current replay context
339 #[cold]
340 #[inline(never)]
341 fn on_stop(&mut self, ctx: &Context) -> ObserverResult {
342 Ok(())
343 }
344
345 /// Called when a combat log entry is received (Dota 2 only).
346 ///
347 /// Combat log entries describe in-game events like damage, healing, kills, etc.
348 /// Only available with the `dota` feature enabled.
349 ///
350 /// Requires [`Interests::COMBAT_LOG`] to be set.
351 #[cold]
352 #[inline(never)]
353 #[cfg(feature = "dota")]
354 fn on_combat_log(&mut self, ctx: &Context, cle: &CombatLogEntry) -> ObserverResult {
355 Ok(())
356 }
357
358 /// Called when a Dota 2 user message is received.
359 ///
360 /// Dota 2 specific user messages. Only available with the `dota` feature enabled.
361 ///
362 /// Requires [`Interests::DOTA_UM`] to be set.
363 #[cold]
364 #[inline(never)]
365 #[cfg(feature = "dota")]
366 fn on_dota_user_message(
367 &mut self,
368 ctx: &Context,
369 msg_type: EDotaUserMessages,
370 msg: &[u8],
371 ) -> ObserverResult {
372 Ok(())
373 }
374
375 /// Called when a Citadel/Deadlock game event is received.
376 ///
377 /// Deadlock specific game events. Only available with the `deadlock` feature enabled.
378 ///
379 /// Requires [`Interests::CITA_GE`] to be set.
380 #[cold]
381 #[inline(never)]
382 #[cfg(feature = "deadlock")]
383 fn on_citadel_game_event(
384 &mut self,
385 ctx: &Context,
386 msg_type: ECitadelGameEvents,
387 msg: &[u8],
388 ) -> ObserverResult {
389 Ok(())
390 }
391
392 /// Called when a Citadel/Deadlock user message is received.
393 ///
394 /// Deadlock specific user messages. Only available with the `deadlock` feature enabled.
395 ///
396 /// Requires [`Interests::CITA_UM`] to be set.
397 #[cold]
398 #[inline(never)]
399 #[cfg(feature = "deadlock")]
400 fn on_citadel_user_message(
401 &mut self,
402 ctx: &Context,
403 msg_type: CitadelUserMessageIds,
404 msg: &[u8],
405 ) -> ObserverResult {
406 Ok(())
407 }
408
409 /// Called when a Counter-Strike 2 user message is received.
410 ///
411 /// CS2 specific user messages. Only available with the `cs2` feature enabled.
412 ///
413 /// Requires [`Interests::CS2_UM`] to be set.
414 #[cold]
415 #[inline(never)]
416 #[cfg(feature = "cs2")]
417 fn on_cs2_user_message(
418 &mut self,
419 ctx: &Context,
420 msg_type: ECstrike15UserMessages,
421 msg: &[u8],
422 ) -> ObserverResult {
423 Ok(())
424 }
425
426 /// Called when a Counter-Strike 2 game event is received.
427 ///
428 /// CS2 specific game events. Only available with the `cs2` feature enabled.
429 ///
430 /// Requires [`Interests::CS2_GE`] to be set.
431 #[cold]
432 #[inline(never)]
433 #[cfg(feature = "cs2")]
434 fn on_cs2_game_event(
435 &mut self,
436 ctx: &Context,
437 msg_type: ECsgoGameEvents,
438 msg: &[u8],
439 ) -> ObserverResult {
440 Ok(())
441 }
442}