Skip to main content

source2_demo_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2//!
3//! # Procedural Macros for Source 2 Replay Parser
4//!
5//! This crate provides procedural macros that simplify implementing the `Observer`
6//! and `DemoRewriter` traits.
7//!
8//! ## Overview
9//!
10//! The macros automate much of the boilerplate required for handling replay events:
11//! - Automatically implement the Observer trait
12//! - Generate protobuf message decoding
13//! - Set up interest flags based on used methods
14//! - Handle optional `Context` parameters
15//!
16//! Handler attributes may be imported and used as single-segment attributes,
17//! such as `#[on_message]`, or written with a path, such as
18//! `#[source2_demo_macros::on_message]`.
19//!
20//! ## Main Macros
21//!
22//! ### `#[observer]` - Main implementation macro
23//!
24//! Automatically implements the Observer trait with all necessary methods.
25//! Takes an impl block with event handlers and generates the full implementation.
26//!
27//! ```no_run
28//! # use source2_demo::prelude::*;
29//! #[derive(Default)]
30//! struct MyObserver;
31//!
32//! #[observer]
33//! impl MyObserver {
34//!     #[on_tick_start]
35//!     fn on_tick(&mut self, ctx: &Context) -> ObserverResult {
36//!         println!("Tick: {}", ctx.tick());
37//!         Ok(())
38//!     }
39//! }
40//! ```
41//!
42//! ### `#[rewriter]` - Demo writer implementation macro
43//!
44//! Automatically implements the `DemoRewriter` trait for replay rewriting.
45//! Use it when you want to drop, replace, or mutate demo messages while the
46//! writer emits a new replay. Packet message handlers can receive either the raw
47//! packet payload or a decoded protobuf message. The protobuf form infers the
48//! packet id from the message type, just like `#[on_message]`.
49//!
50//! #### Drop a decoded packet message
51//!
52//! ```no_run
53//! # use source2_demo::prelude::*;
54//! # use source2_demo::proto::CDotaUserMsgChatMessage;
55//! # use source2_demo::writer::*;
56//! struct RemoveChat;
57//!
58//! #[rewriter]
59//! impl RemoveChat {
60//!     #[rewrite_packet_message]
61//!     fn remove_chat(
62//!         &mut self,
63//!         _message: CDotaUserMsgChatMessage,
64//!     ) -> Result<MessageRewrite, ParserError> {
65//!         Ok(MessageRewrite::Drop)
66//!     }
67//! }
68//! ```
69//!
70//! #### Mutate and re-encode a decoded packet message
71//!
72//! ```no_run
73//! # use source2_demo::prelude::*;
74//! # use source2_demo::proto::CDotaUserMsgChatMessage;
75//! # use source2_demo::writer::*;
76//! struct RedactChat;
77//!
78//! #[rewriter]
79//! impl RedactChat {
80//!     #[rewrite_packet_message]
81//!     fn redact_chat(
82//!         &mut self,
83//!         message: &mut CDotaUserMsgChatMessage,
84//!     ) -> Result<MessageRewrite, ParserError> {
85//!         message.message_text = Some("[redacted]".to_string());
86//!         Ok(MessageRewrite::Rewrite)
87//!     }
88//! }
89//! ```
90//!
91//! #### Rewrite a string table entry
92//!
93//! ```no_run
94//! # use source2_demo::prelude::*;
95//! # use source2_demo::proto::CMsgPlayerInfo;
96//! # use source2_demo::writer::*;
97//! struct AnonymizeUserInfo;
98//!
99//! #[rewriter]
100//! impl AnonymizeUserInfo {
101//!     #[rewrite_string_table_entry]
102//!     fn rewrite_userinfo(
103//!         &mut self,
104//!         table_name: &str,
105//!         entry: &mut StringTableEntryUpdate,
106//!     ) -> Result<(), ParserError> {
107//!         if table_name == "userinfo" {
108//!             if let Some(value) = entry.value_mut() {
109//!                 let mut player = CMsgPlayerInfo::decode(value.as_slice())?;
110//!                 player.name = Some("Anonymous".to_string());
111//!                 *value = player.encode_to_vec();
112//!             }
113//!         }
114//!         Ok(())
115//!     }
116//! }
117//! ```
118//!
119//! #### Rewrite decoded entity fields
120//!
121//! ```no_run
122//! # use source2_demo::prelude::*;
123//! # use source2_demo::writer::*;
124//! struct RemoveSteamIds;
125//!
126//! #[rewriter]
127//! impl RemoveSteamIds {
128//!     #[rewrite_field(class = "CDOTA_PlayerResource", field = ends_with("m_iPlayerSteamID"))]
129//!     fn remove_steam_id(&mut self, _value: u64) -> u64 {
130//!         0
131//!     }
132//! }
133//! ```
134//!
135//! ### Event Handler Macros
136//!
137//! These attributes mark methods as event handlers within an #[observer] impl:
138//!
139//! - `#[on_message]` - Handles protobuf messages
140//! - `#[on_tick_start]` - Called at tick start
141//! - `#[on_tick_end]` - Called at tick end
142//! - `#[on_entity]` - Called for entity changes
143//! - `#[on_game_event]` - Called for game events
144//! - `#[on_string_table]` - Called for string table updates
145//! - `#[on_stop]` - Called when replay ends
146//! - `#[on_combat_log]` - Called for combat log entries (Dota 2 only)
147//!
148//! ### Trait Markers
149//!
150//! These mark impl blocks with which data types to track:
151//!
152//! - `#[uses_all]` - Track all supported data
153//! - `#[uses_entities]` - Track entities
154//! - `#[uses_string_tables]` - Track string tables
155//! - `#[uses_game_events]` - Track game events
156//! - `#[uses_combat_log]` - Track combat log (Dota 2 only)
157//!
158//! ## How the Macros Work
159//!
160//! The `#[observer]` macro:
161//!
162//! 1. Scans all methods for event handler attributes
163//! 2. Collects parameter types to generate interest flags
164//! 3. Creates decoding logic for protobuf messages
165//! 4. Generates the full `Observer` trait implementation
166//! 5. Handles optional parameters (Context is optional)
167//!
168//! ## Complete Example
169//!
170//! ```no_run
171//! use source2_demo::prelude::*;
172//!
173//! #[derive(Default)]
174//! struct GameAnalyzer {
175//!     tick_count: u32,
176//!     entity_count: u32,
177//! }
178//!
179//! #[observer]
180//! #[uses_entities]
181//! impl GameAnalyzer {
182//!     #[on_tick_start]
183//!     fn on_tick(&mut self, ctx: &Context) -> ObserverResult {
184//!         self.tick_count += 1;
185//!         Ok(())
186//!     }
187//!
188//!     #[on_entity]
189//!     fn on_entity_create(&mut self, event: EntityEvents, entity: &Entity) -> ObserverResult {
190//!         if event == EntityEvents::Created {
191//!             self.entity_count += 1;
192//!         }
193//!         Ok(())
194//!     }
195//!
196//!     #[on_message]
197//!     fn on_chat(&mut self, msg: CDotaUserMsgChatMessage) -> ObserverResult {
198//!         println!("Chat: {}", msg.message_text());
199//!         Ok(())
200//!     }
201//! }
202//! ```
203//!
204//! ## Key Features
205//!
206//! - **Automatic Interest Management**: Macro automatically determines which interests
207//!   to set based on which handler methods are defined
208//! - **Optional Context**: Pass `ctx: &Context` to any handler or omit it
209//! - **Automatic Message Decoding**: `#[on_message]` handlers automatically decode
210//!   protobuf messages based on parameter type
211//! - **Filtering Attributes**: Filter events with string arguments:
212//!   - `#[on_entity("CDOTA_Unit_Hero_Axe")]` - Only heroes named Axe
213//!   - `#[on_game_event("player_death")]` - Only death events
214//!   - `#[on_string_table("userinfo")]` - Only userinfo table updates
215//! - **Game-Specific Messages**: Use `#[on_message]` with Dota 2, Deadlock, or
216//!   CS2 protobuf message types
217
218mod observer_impl;
219mod protobuf_map;
220mod rewriter_impl;
221mod type_utils;
222
223use proc_macro::TokenStream;
224
225#[allow(unused_mut)]
226#[proc_macro_attribute]
227/// Implements the Observer trait for your struct.
228///
229/// This is the main macro that ties everything together. Apply it to an `impl` block
230/// that contains event handler methods marked with `#[on_*]` attributes.
231///
232/// # What It Does
233///
234/// - Automatically implements the Observer trait
235/// - Generates code to call all handler methods at appropriate times
236/// - Sets up interest flags based on which handlers are defined
237/// - Decodes protobuf messages and passes them to handlers
238/// - Filters events based on optional string arguments
239///
240/// # Modes
241///
242/// Use `#[observer]` to infer interests from handlers and `#[uses_*]` markers.
243/// Use `#[observer(all)]` or `#[uses_all]` to enable every interest.
244///
245/// # Handler Attributes
246///
247/// Use these attributes inside the impl block to mark event handlers:
248///
249/// - `#[on_tick_start]` - Called at the start of each tick
250/// - `#[on_tick_end]` - Called at the end of each tick
251/// - `#[on_entity]` - Called when entities change
252/// - `#[on_entity("ClassName")]` - Only for specific entity classes
253/// - `#[on_message]` - Called for protobuf messages (type inferred from param)
254/// - `#[on_game_event]` - Called for all game events
255/// - `#[on_game_event("event_name")]` - Only for specific events
256/// - `#[on_string_table]` - Called when string tables update
257/// - `#[on_string_table("table_name")]` - Only for specific tables
258/// - `#[on_stop]` - Called when replay ends
259/// - `#[on_combat_log]` - Called for combat log entries (Dota 2 only)
260///
261/// # Trait Attributes
262///
263/// Apply these to the `impl` block or individual methods to enable tracking:
264///
265/// - `#[uses_all]` - Enable all tracking
266/// - `#[uses_entities]` - Enable entity tracking
267/// - `#[uses_string_tables]` - Enable string table tracking
268/// - `#[uses_game_events]` - Enable game event tracking
269/// - `#[uses_combat_log]` - Enable combat log tracking (Dota 2 only)
270///
271/// # Parameter Guidelines
272///
273/// Handlers can receive these parameters. Most are optional; `#[on_message]`
274/// requires either one protobuf message or a supported message enum plus
275/// `&[u8]` payload.
276/// - `ctx: &Context` - Current replay state (always optional)
277/// - Specific parameters depending on the handler:
278///   - `event: EntityEvents` or `event: &EntityEvents` (on_entity)
279///   - `entity: &Entity` (on_entity)
280///   - `ge: &GameEvent` (on_game_event)
281///   - `table: &StringTable` (on_string_table)
282///   - `modified: &[i32]` (on_string_table)
283///   - `cle: &CombatLogEntry` (on_combat_log)
284///   - A protobuf message, or a supported message enum plus `&[u8]` payload
285///     (on_message)
286///
287/// Handler parameters can appear in any order. Handlers return
288/// `ObserverResult`.
289///
290/// # Examples
291///
292/// ## Basic observer
293///
294/// ```no_run
295/// # use source2_demo::prelude::*;
296/// #[derive(Default)]
297/// struct BasicObserver;
298///
299/// #[observer]
300/// impl BasicObserver {
301///     #[on_tick_start]
302///     fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
303///         println!("Tick: {}", ctx.tick());
304///         Ok(())
305///     }
306/// }
307/// ```
308///
309/// ## With entity tracking
310///
311/// ```no_run
312/// # use source2_demo::prelude::*;
313/// #[derive(Default)]
314/// struct EntityTracker;
315///
316/// #[observer]
317/// #[uses_entities]
318/// impl EntityTracker {
319///     #[on_entity]
320///     fn on_hero_created(&mut self, event: EntityEvents, entity: &Entity) -> ObserverResult {
321///         if event == EntityEvents::Created && entity.class().name().starts_with("CDOTA_Unit_Hero_") {
322///             println!("Hero created: {}", entity.class().name());
323///         }
324///         Ok(())
325///     }
326/// }
327/// ```
328///
329/// ## With multiple handlers
330///
331/// ```no_run
332/// # use source2_demo::prelude::*;
333/// #[derive(Default)]
334/// struct ComplexObserver {
335///     ticks: u32,
336///     messages: u32,
337/// }
338///
339/// #[observer]
340/// impl ComplexObserver {
341///     #[on_tick_start]
342///     fn on_tick(&mut self, ctx: &Context) -> ObserverResult {
343///         self.ticks += 1;
344///         Ok(())
345///     }
346///
347///     #[on_message]
348///     fn on_chat(&mut self, msg: CDotaUserMsgChatMessage) -> ObserverResult {
349///         self.messages += 1;
350///         println!("Message: {}", msg.message_text());
351///         Ok(())
352///     }
353///
354///     #[on_stop]
355///     fn on_replay_end(&mut self) -> ObserverResult {
356///         println!("Total ticks: {}, messages: {}", self.ticks, self.messages);
357///         Ok(())
358///     }
359/// }
360/// ```
361///
362/// ## With game event filtering
363///
364/// ```no_run
365/// # use source2_demo::prelude::*;
366/// #[derive(Default)]
367/// struct DeathTracker {
368///     deaths: u32,
369/// }
370///
371/// #[observer]
372/// impl DeathTracker {
373///     #[on_game_event("player_death")]
374///     fn on_death(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
375///         self.deaths += 1;
376///         Ok(())
377///     }
378/// }
379/// ```
380///
381/// # Interest Flags
382///
383/// The macro automatically determines which interest flags to set based on which
384/// handlers are defined. For example:
385/// - If you have `#[on_tick_start]`, `Interests::TICK_START` is added
386/// - If you have `#[on_entity]`, both `Interests::ENTITY_STATE` and
387///   `Interests::ENTITY_EVENTS` are added
388/// - If you have `#[on_message]` handlers, appropriate message interest flags are added
389///
390/// You can also use trait attributes like `#[uses_entities]` to manually ensure
391/// certain interests are set.
392pub fn observer(attr: TokenStream, item: TokenStream) -> TokenStream {
393    observer_impl::expand_observer(attr, item)
394}
395
396/// Implements the `DemoRewriter` trait for your struct.
397///
398/// Apply this to an inherent `impl` block and mark methods with writer callback
399/// attributes. The macro builds the `RewriteInterests` mask from those methods
400/// so the writer only decodes the parts of the replay your rewrite needs.
401///
402/// # Callback Types
403///
404/// - `#[rewrite_demo_message]` sees outer `EDemoCommands` payloads before
405///   packet-level decoding.
406/// - `#[rewrite_packet_message]` sees messages inside demo packets. It can take
407///   raw `(msg_type, payload)` arguments or a decoded protobuf message.
408/// - `#[rewrite_packet_messages]` can mutate the final decoded packet message
409///   list after individual packet-message callbacks run.
410/// - `#[rewrite_demo_string_tables]`, `#[rewrite_string_table_entry]`,
411///   `#[rewrite_svc_create_string_table]`, and
412///   `#[rewrite_svc_update_string_table]` target string-table data at different
413///   levels of decoding.
414/// - `#[rewrite_field]` and `#[replace_entity_field]` replace decoded entity
415///   field values, while `#[should_rewrite_entity]` filters which entities enter
416///   that path.
417///
418/// # Return Values
419///
420/// Message callbacks return `Result<MessageRewrite, ParserError>`.
421/// `Keep` leaves the current payload alone, `Drop` removes it, `Replace(bytes)`
422/// writes explicit bytes, and `Rewrite` re-encodes a mutable decoded protobuf
423/// argument when that callback supports it. List, entry, and filter callbacks
424/// use the return type of the matching `DemoRewriter` method.
425///
426/// # Examples
427///
428/// ## Drop a decoded packet message
429///
430/// ```no_run
431/// # use source2_demo::prelude::*;
432/// # use source2_demo::proto::CDotaUserMsgChatMessage;
433/// # use source2_demo::writer::*;
434/// struct RemoveChat;
435///
436/// #[rewriter]
437/// impl RemoveChat {
438///     #[rewrite_packet_message]
439///     fn remove_chat(
440///         &mut self,
441///         _msg: CDotaUserMsgChatMessage,
442///     ) -> Result<MessageRewrite, ParserError> {
443///         Ok(MessageRewrite::Drop)
444///     }
445/// }
446/// ```
447///
448/// ## Mutate and re-encode a decoded packet message
449///
450/// ```no_run
451/// # use source2_demo::prelude::*;
452/// # use source2_demo::proto::CDotaUserMsgChatMessage;
453/// # use source2_demo::writer::*;
454/// struct RedactChat;
455///
456/// #[rewriter]
457/// impl RedactChat {
458///     #[rewrite_packet_message]
459///     fn redact_chat(
460///         &mut self,
461///         message: &mut CDotaUserMsgChatMessage,
462///     ) -> Result<MessageRewrite, ParserError> {
463///         message.message_text = Some("[redacted]".to_string());
464///         Ok(MessageRewrite::Rewrite)
465///     }
466/// }
467/// ```
468///
469/// ## Rewrite a string table entry
470///
471/// ```no_run
472/// # use source2_demo::prelude::*;
473/// # use source2_demo::proto::CMsgPlayerInfo;
474/// # use source2_demo::writer::*;
475/// struct AnonymizeUserInfo;
476///
477/// #[rewriter]
478/// impl AnonymizeUserInfo {
479///     #[rewrite_string_table_entry]
480///     fn rewrite_userinfo(
481///         &mut self,
482///         table_name: &str,
483///         entry: &mut StringTableEntryUpdate,
484///     ) -> Result<(), ParserError> {
485///         if table_name == "userinfo" {
486///             if let Some(value) = entry.value_mut() {
487///                 let mut player = CMsgPlayerInfo::decode(value.as_slice())?;
488///                 player.name = Some("Anonymous".to_string());
489///                 *value = player.encode_to_vec();
490///             }
491///         }
492///         Ok(())
493///     }
494/// }
495/// ```
496///
497/// ## Rewrite decoded entity fields
498///
499/// ```no_run
500/// # use source2_demo::prelude::*;
501/// # use source2_demo::writer::*;
502/// struct RemoveSteamIds;
503///
504/// #[rewriter]
505/// impl RemoveSteamIds {
506///     #[rewrite_field(class = "CDOTA_PlayerResource", field = ends_with("m_iPlayerSteamID"))]
507///     fn remove_steam_id(&mut self, _value: u64) -> u64 {
508///         0
509///     }
510/// }
511/// ```
512#[proc_macro_attribute]
513pub fn rewriter(_attr: TokenStream, item: TokenStream) -> TokenStream {
514    rewriter_impl::expand_rewriter(item)
515}
516
517/// Marks a method as a protobuf message handler.
518///
519/// Use this to handle specific protobuf message types. The message type is inferred
520/// from the method's parameter type, which should be a mapped protobuf message
521/// struct.
522///
523/// The method will automatically decode binary message data and call your handler
524/// with the decoded message object.
525///
526/// # Parameters
527///
528/// A decoded handler can receive:
529///
530/// - `ctx: &Context` (optional)
531/// - One protobuf message by value or shared reference
532///
533/// A raw handler can instead receive:
534///
535/// - `ctx: &Context` (optional)
536/// - One supported message enum by value or shared reference
537/// - `payload: &[u8]`
538///
539/// Parameters can appear in any order.
540/// Return `ObserverResult`.
541///
542/// # Supported Message Types
543///
544/// - `CDotaUserMsgChatMessage` and other Dota 2 messages
545/// - `CCitadelUserMsgChatMsg` and other Deadlock messages
546/// - `CCsUsrMsgVguiMenu` and other CS2 messages
547/// - Other mapped protobuf message types
548///
549/// # Examples
550///
551/// ## Handle Dota 2 chat messages
552///
553/// ```no_run
554/// # use source2_demo::prelude::*;
555/// # struct MyObs;
556/// # impl MyObs {
557/// #[on_message]
558/// fn on_chat(&mut self, ctx: &Context, msg: CDotaUserMsgChatMessage) -> ObserverResult {
559///     println!("[{}] {}", ctx.tick(), msg.message_text());
560///     Ok(())
561/// }
562/// # }
563/// ```
564///
565/// ## Handle without context
566///
567/// ```no_run
568/// # use source2_demo::prelude::*;
569/// # struct MyObs;
570/// # impl MyObs {
571/// #[on_message]
572/// fn on_chat(&mut self, msg: CDotaUserMsgChatMessage) -> ObserverResult {
573///     println!("Message: {}", msg.message_text());
574///     Ok(())
575/// }
576/// # }
577/// ```
578///
579/// ## Handle by reference
580///
581/// ```no_run
582/// # use source2_demo::prelude::*;
583/// # struct MyObs;
584/// # impl MyObs {
585/// #[on_message]
586/// fn on_chat(&mut self, msg: &CDotaUserMsgChatMessage) -> ObserverResult {
587///     println!("Message: {}", msg.message_text());
588///     Ok(())
589/// }
590/// # }
591/// ```
592///
593/// ## Handle raw messages
594///
595/// ```no_run
596/// # use source2_demo::prelude::*;
597/// # struct MyObs;
598/// # impl MyObs {
599/// #[on_message]
600/// fn on_raw(&mut self, msg_type: SvcMessages, payload: &[u8]) -> ObserverResult {
601///     println!("{msg_type:?}: {} bytes", payload.len());
602///     Ok(())
603/// }
604/// # }
605/// ```
606#[proc_macro_attribute]
607pub fn on_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
608    item
609}
610
611/// Rewrites an outer demo command payload.
612///
613/// Use this for callbacks that operate before packet-level decoding, such as
614/// dropping or replacing whole `EDemoCommands` messages. Raw handlers can
615/// receive `ctx: &Context`, `tick: u32`, `msg_type: EDemoCommands`, and
616/// `payload: &[u8]`, in any supported callback-argument order.
617///
618/// Return `Result<MessageRewrite, ParserError>`.
619#[proc_macro_attribute]
620pub fn rewrite_demo_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
621    item
622}
623
624/// Rewrites an individual message inside a demo packet.
625///
626/// A raw handler can receive packet metadata and payload bytes. A typed handler
627/// can receive a protobuf message; the macro infers the packet message id from
628/// that protobuf type. Mutable protobuf arguments may return
629/// `MessageRewrite::Rewrite` to re-encode the modified message.
630///
631/// # Parameters
632///
633/// A raw handler can receive `ctx: &Context`, `tick: u32`, `msg_type: i32`,
634/// and `payload: &[u8]`. A typed handler can receive `ctx: &Context`,
635/// `tick: u32`, and one protobuf message by value, shared reference, or mutable
636/// reference. Parameters can appear in any order.
637///
638/// Return `Result<MessageRewrite, ParserError>`.
639///
640/// # Examples
641///
642/// ## Drop a decoded protobuf message
643///
644/// ```no_run
645/// # use source2_demo::prelude::*;
646/// # use source2_demo::proto::CDotaUserMsgChatMessage;
647/// # use source2_demo::writer::*;
648/// # struct MyRewriter;
649/// # impl MyRewriter {
650/// #[rewrite_packet_message]
651/// fn remove_chat(&mut self, _msg: CDotaUserMsgChatMessage) -> Result<MessageRewrite, ParserError> {
652///     Ok(MessageRewrite::Drop)
653/// }
654/// # }
655/// ```
656///
657/// ## Mutate a decoded protobuf message
658///
659/// ```no_run
660/// # use source2_demo::prelude::*;
661/// # use source2_demo::proto::CDotaUserMsgChatMessage;
662/// # use source2_demo::writer::*;
663/// # struct MyRewriter;
664/// # impl MyRewriter {
665/// #[rewrite_packet_message]
666/// fn redact_chat(
667///     &mut self,
668///     msg: &mut CDotaUserMsgChatMessage,
669/// ) -> Result<MessageRewrite, ParserError> {
670///     msg.message_text = Some("[redacted]".to_string());
671///     Ok(MessageRewrite::Rewrite)
672/// }
673/// # }
674/// ```
675#[proc_macro_attribute]
676pub fn rewrite_packet_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
677    item
678}
679
680/// Mutates the decoded packet message list after per-message rewrites.
681///
682/// Use this when the rewrite needs packet-wide context, such as appending a new
683/// `PacketMessage`, removing several messages together, or reordering messages
684/// within one packet.
685///
686/// The handler can receive `ctx: &Context`, `tick: u32`, and
687/// `messages: &mut Vec<PacketMessage>` in any order. Return
688/// `Result<(), ParserError>`.
689#[proc_macro_attribute]
690pub fn rewrite_packet_messages(_attr: TokenStream, item: TokenStream) -> TokenStream {
691    item
692}
693
694/// Rewrites a decoded `CDemoStringTables` outer demo message.
695///
696/// This callback sees the full demo string-table message. Return
697/// `MessageRewrite::Rewrite` after mutating the decoded message, or
698/// `MessageRewrite::Replace(bytes)` to provide encoded bytes directly.
699///
700/// The handler can receive `ctx: &Context`, `tick: u32`, and
701/// `message: &mut CDemoStringTables` in any order. Return
702/// `Result<MessageRewrite, ParserError>`.
703#[proc_macro_attribute]
704pub fn rewrite_demo_string_tables(_attr: TokenStream, item: TokenStream) -> TokenStream {
705    item
706}
707
708/// Rewrites one decoded string table entry update.
709///
710/// Use this for targeted string-table edits such as rewriting `userinfo` rows.
711/// The handler can receive `ctx: &Context`, `tick: u32`, `table_name: &str`,
712/// and `entry: &mut StringTableEntryUpdate` in any order. Return
713/// `Result<(), ParserError>`.
714///
715/// # Examples
716///
717/// ```no_run
718/// # use source2_demo::prelude::*;
719/// # use source2_demo::proto::CMsgPlayerInfo;
720/// # use source2_demo::writer::*;
721/// # struct MyRewriter;
722/// # impl MyRewriter {
723/// #[rewrite_string_table_entry]
724/// fn anonymize_userinfo(
725///     &mut self,
726///     table_name: &str,
727///     entry: &mut StringTableEntryUpdate,
728/// ) -> Result<(), ParserError> {
729///     if table_name == "userinfo" {
730///         if let Some(value) = entry.value_mut() {
731///             let mut player = CMsgPlayerInfo::decode(value.as_slice())?;
732///             player.name = Some("Anonymous".to_string());
733///             *value = player.encode_to_vec();
734///         }
735///     }
736///     Ok(())
737/// }
738/// # }
739/// ```
740#[proc_macro_attribute]
741pub fn rewrite_string_table_entry(_attr: TokenStream, item: TokenStream) -> TokenStream {
742    item
743}
744
745/// Rewrites a decoded `svc_CreateStringTable` packet message.
746///
747/// Return `MessageRewrite::Rewrite` after mutating the decoded
748/// `CSvcMsgCreateStringTable`, or return `Replace` / `Drop` for explicit output
749/// control.
750///
751/// The handler can receive `ctx: &Context`, `tick: u32`, and
752/// `message: &mut CSvcMsgCreateStringTable` in any order. Return
753/// `Result<MessageRewrite, ParserError>`.
754#[proc_macro_attribute]
755pub fn rewrite_svc_create_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
756    item
757}
758
759/// Rewrites a decoded `svc_UpdateStringTable` packet message.
760///
761/// This is useful for incremental string-table updates that arrive inside
762/// packet data. Return `MessageRewrite::Rewrite` after mutating the decoded
763/// `CSvcMsgUpdateStringTable`.
764///
765/// The handler can receive `ctx: &Context`, `tick: u32`, and
766/// `message: &mut CSvcMsgUpdateStringTable` in any order. Return
767/// `Result<MessageRewrite, ParserError>`.
768#[proc_macro_attribute]
769pub fn rewrite_svc_update_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
770    item
771}
772
773/// Replaces decoded entity field values with custom logic.
774///
775/// This low-level form receives the current field as `&FieldValue` and returns
776/// `Option<FieldValue>`. Return `Some` to replace the value; return `None` to
777/// leave it unchanged or let later generated checks continue.
778///
779/// The handler can receive `ctx: &Context`, `event: EntityEvents`,
780/// `entity: &Entity`, `field_name: &str`, and `value: &FieldValue` in any
781/// order.
782#[proc_macro_attribute]
783pub fn replace_entity_field(_attr: TokenStream, item: TokenStream) -> TokenStream {
784    item
785}
786
787/// Replaces decoded entity field values with class and field filters.
788///
789/// # Attribute Arguments
790///
791/// - `class = ...` is required.
792/// - `field = ...` is optional.
793///
794/// Both filters accept an exact string literal or the `exact`, `starts_with`,
795/// `ends_with`, `contains`, `any`, `all`, and `not` predicates.
796///
797/// Field-specific handlers may use typed values such as `u64`, `bool`, or
798/// `String`. Class-only handlers must use `FieldValue` or `&FieldValue`
799/// because there is no field predicate to determine one concrete value type.
800///
801/// # Handler Parameters
802///
803/// A handler must receive one field value parameter. Field-specific handlers
804/// support `&str`, `String`, `&String`, `bool`, `f32`, `i8`, `i16`, `i32`,
805/// `i64`, `u8`, `u16`, `u32`, `u64`, `[f32; 2]`, `[f32; 3]`, `[f32; 4]`,
806/// `FieldValue`, and `&FieldValue`. Class-only handlers must use `FieldValue`
807/// or `&FieldValue`.
808///
809/// A handler can also receive any of these optional parameters:
810///
811/// - `ctx: &Context`
812/// - `event: EntityEvents`
813/// - `entity: &Entity`
814/// - `field_name: &str`
815///
816/// Parameters can appear in any order except that `field_name: &str` must come
817/// before the field value parameter. This matters because `&str` can itself be
818/// the field value type.
819///
820/// # Return Values
821///
822/// Return a replacement value directly to always rewrite the field. Return
823/// `Option<T>` to rewrite with `Some(value)` or leave the field unchanged with
824/// `None`. The replacement must convert into `FieldValue`.
825///
826/// # Examples
827///
828/// ```no_run
829/// # use source2_demo::prelude::*;
830/// # use source2_demo::writer::*;
831/// # struct MyRewriter;
832/// # impl MyRewriter {
833/// #[rewrite_field(class = "CDOTA_PlayerResource", field = ends_with("m_iPlayerSteamID"))]
834/// fn remove_steam_id(&mut self, _value: u64) -> u64 {
835///     0
836/// }
837/// # }
838/// ```
839#[proc_macro_attribute]
840pub fn rewrite_field(_attr: TokenStream, item: TokenStream) -> TokenStream {
841    item
842}
843
844/// Filters which entities enter the entity field rewrite path.
845///
846/// Return `false` to skip all entity-field replacement callbacks for that
847/// entity. Use this to keep broad `#[rewrite_field]` handlers from forcing the
848/// writer to decode and re-encode unrelated entity classes.
849///
850/// The handler can receive `ctx: &Context`, `event: EntityEvents`, and
851/// `entity: &Entity` in any order. Return `bool`.
852#[proc_macro_attribute]
853pub fn should_rewrite_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
854    item
855}
856
857/// Marks a method as a tick-start handler.
858///
859/// This handler is called at the beginning of each game tick. Use it for
860/// per-tick logic like updating game state, tracking changes, or generating output.
861///
862/// # Parameters
863///
864/// The handler can receive:
865/// - `ctx: &Context` (optional) - Current replay state including tick number
866///
867/// Return `ObserverResult`.
868///
869/// # When It's Called
870///
871/// Called right after the tick number is incremented in the Context, but before
872/// any other tick-specific events are processed.
873///
874/// # Examples
875///
876/// ## Track entity state every tick
877///
878/// ```no_run
879/// # use source2_demo::prelude::*;
880/// # struct MyObs;
881/// # impl MyObs {
882/// #[on_tick_start]
883/// fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
884///     if ctx.tick() % 30 == 0 {  // Every second (30 ticks/sec)
885///         println!("Ticks processed: {}", ctx.tick());
886///     }
887///     Ok(())
888/// }
889/// # }
890/// ```
891///
892/// ## Without context
893///
894/// ```no_run
895/// # use source2_demo::prelude::*;
896/// # struct MyObs;
897/// # impl MyObs {
898/// #[on_tick_start]
899/// fn on_tick_start(&mut self) -> ObserverResult {
900///     // Do something without needing context
901///     Ok(())
902/// }
903/// # }
904/// ```
905#[proc_macro_attribute]
906pub fn on_tick_start(_attr: TokenStream, item: TokenStream) -> TokenStream {
907    item
908}
909
910/// Marks a method as a tick-end handler.
911///
912/// This handler is called at the end of each game tick. Use it to finalize
913/// per-tick calculations, output results, or reset temporary state.
914///
915/// # Parameters
916///
917/// The handler can receive:
918/// - `ctx: &Context` (optional) - Current replay state
919///
920/// Return `ObserverResult`.
921///
922/// # When It's Called
923///
924/// Called after all events for the current tick have been processed.
925///
926/// # Examples
927///
928/// ## Flush buffered data at end of tick
929///
930/// ```no_run
931/// # use source2_demo::prelude::*;
932/// # struct MyObs {
933/// #     buffer: Vec<String>,
934/// # }
935/// # impl MyObs {
936/// #[on_tick_end]
937/// fn on_tick_end(&mut self, ctx: &Context) -> ObserverResult {
938///     // Output buffered data
939///     for item in self.buffer.drain(..) {
940///         println!("[{}] {}", ctx.tick(), item);
941///     }
942///     Ok(())
943/// }
944/// # }
945/// ```
946#[proc_macro_attribute]
947pub fn on_tick_end(_attr: TokenStream, item: TokenStream) -> TokenStream {
948    item
949}
950
951/// Marks a method as an entity event handler.
952///
953/// This handler is called when entities are created, updated, or deleted.
954/// Optionally filter to specific entity class names.
955///
956/// # Parameters
957///
958/// The handler can receive:
959/// - `ctx: &Context` (optional) - Current replay state
960/// - `event: EntityEvents` or `event: &EntityEvents` (optional) - Type of entity event
961/// - `entity: &Entity` (optional) - The entity that changed
962///
963/// Parameters can appear in any order. Return `ObserverResult`.
964///
965/// # Filtering
966///
967/// Pass a class name to only handle entities of that type:
968/// ```no_run
969/// # use source2_demo::prelude::*;
970/// # struct MyObs;
971/// # impl MyObs {
972/// #[on_entity("CDOTA_Unit_Hero_Axe")]
973/// fn on_axe(&mut self, entity: &Entity) -> ObserverResult {
974///     // Only called for Axe hero entity
975///     Ok(())
976/// }
977/// # }
978/// ```
979///
980/// # Examples
981///
982/// ## Track all entity changes
983///
984/// ```no_run
985/// # use source2_demo::prelude::*;
986/// # struct MyObs {
987/// #     created: usize,
988/// # }
989/// # impl MyObs {
990/// #[on_entity]
991/// fn on_entity(&mut self, event: EntityEvents, entity: &Entity) -> ObserverResult {
992///     if event == EntityEvents::Created {
993///         self.created += 1;
994///     }
995///     Ok(())
996/// }
997/// # }
998/// ```
999///
1000/// ## Track specific entity type
1001///
1002/// ```no_run
1003/// # use source2_demo::prelude::*;
1004/// # struct MyObs;
1005/// # impl MyObs {
1006/// #[on_entity("CDOTA_PlayerResource")]
1007/// fn on_player_resource(&mut self, ctx: &Context, entity: &Entity) -> ObserverResult {
1008///     // Only called when player resource entity updates
1009///     Ok(())
1010/// }
1011/// # }
1012/// ```
1013#[proc_macro_attribute]
1014pub fn on_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
1015    item
1016}
1017
1018/// Marks a method as a game event handler.
1019///
1020/// This handler is called when game events occur (kills, deaths, item purchases, etc.).
1021/// Optionally filter to specific event names.
1022///
1023/// # Parameters
1024///
1025/// The handler can receive:
1026/// - `ctx: &Context` (optional) - Current replay state
1027/// - `ge: &GameEvent` (optional) - The game event
1028///
1029/// Parameters can appear in any order. Return `ObserverResult`.
1030///
1031/// # Filtering
1032///
1033/// Pass an event name to only handle that event type:
1034/// ```no_run
1035/// # use source2_demo::prelude::*;
1036/// # struct MyObs;
1037/// # impl MyObs {
1038/// #[on_game_event("player_death")]
1039/// fn on_death(&mut self, ge: &GameEvent) -> ObserverResult {
1040///     // Only called for player_death events
1041///     Ok(())
1042/// }
1043/// # }
1044/// ```
1045///
1046/// # Examples
1047///
1048/// ## Handle all game events
1049///
1050/// ```no_run
1051/// # use source2_demo::prelude::*;
1052/// # struct MyObs;
1053/// # impl MyObs {
1054/// #[on_game_event]
1055/// fn on_event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
1056///     println!("[{}] Event: {}", ctx.tick(), ge.name());
1057///     Ok(())
1058/// }
1059/// # }
1060/// ```
1061///
1062/// ## Handle specific event type
1063///
1064/// ```no_run
1065/// # use source2_demo::prelude::*;
1066/// # struct MyObs {
1067/// #     kill_count: u32,
1068/// # }
1069/// # impl MyObs {
1070/// #[on_game_event("dota_player_kill")]
1071/// fn on_kill(&mut self, ge: &GameEvent) -> ObserverResult {
1072///     self.kill_count += 1;
1073///     Ok(())
1074/// }
1075/// # }
1076/// ```
1077#[proc_macro_attribute]
1078pub fn on_game_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
1079    item
1080}
1081
1082/// Marks a method as a string table update handler.
1083///
1084/// This handler is called when string tables are updated. String tables contain
1085/// game data like player names, modifiers, effects, etc.
1086/// Optionally filter to specific table names.
1087///
1088/// # Parameters
1089///
1090/// The handler can receive:
1091/// - `ctx: &Context` (optional) - Current replay state
1092/// - `table: &StringTable` (optional) - The updated string table
1093/// - `modified: &[i32]` (optional) - Indices of rows that were modified
1094///
1095/// Parameters can appear in any order. Return `ObserverResult`.
1096///
1097/// # Filtering
1098///
1099/// Pass a table name to only handle that table:
1100/// ```no_run
1101/// # use source2_demo::prelude::*;
1102/// # struct MyObs;
1103/// # impl MyObs {
1104/// #[on_string_table("userinfo")]
1105/// fn on_userinfo(&mut self, table: &StringTable, modified: &[i32]) -> ObserverResult {
1106///     // Only called when userinfo table updates
1107///     Ok(())
1108/// }
1109/// # }
1110/// ```
1111///
1112/// # Examples
1113///
1114/// ## Track all string table updates
1115///
1116/// ```no_run
1117/// # use source2_demo::prelude::*;
1118/// # struct MyObs;
1119/// # impl MyObs {
1120/// #[on_string_table]
1121/// fn on_table_update(&mut self, ctx: &Context, table: &StringTable, modified: &[i32]) -> ObserverResult {
1122///     println!("[{}] Table {} updated: {} rows", ctx.tick(), table.name(), modified.len());
1123///     Ok(())
1124/// }
1125/// # }
1126/// ```
1127///
1128/// ## Monitor specific table
1129///
1130/// ```no_run
1131/// # use source2_demo::prelude::*;
1132/// # struct MyObs;
1133/// # impl MyObs {
1134/// #[on_string_table("ActiveModifiers")]
1135/// fn on_modifiers(&mut self, table: &StringTable, modified: &[i32]) -> ObserverResult {
1136///     println!("Active modifiers changed: {} rows", modified.len());
1137///     Ok(())
1138/// }
1139/// # }
1140/// ```
1141#[proc_macro_attribute]
1142pub fn on_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
1143    item
1144}
1145
1146/// Marks a method as a replay stop handler.
1147///
1148/// This handler is called when the replay ends (CDemoStop message).
1149/// Use it to finalize results, output statistics, or clean up resources.
1150///
1151/// # Parameters
1152///
1153/// The handler can receive:
1154/// - `ctx: &Context` (optional) - Final replay state
1155///
1156/// Return `ObserverResult`.
1157///
1158/// # Examples
1159///
1160/// ## Output final statistics
1161///
1162/// ```no_run
1163/// # use source2_demo::prelude::*;
1164/// # struct MyObs {
1165/// #     ticks: u32,
1166/// # }
1167/// # impl MyObs {
1168/// #[on_stop]
1169/// fn on_stop(&mut self, ctx: &Context) -> ObserverResult {
1170///     println!("Replay ended at tick {}", ctx.tick());
1171///     println!("Total ticks processed: {}", self.ticks);
1172///     Ok(())
1173/// }
1174/// # }
1175/// ```
1176///
1177/// ## Without context
1178///
1179/// ```no_run
1180/// # use source2_demo::prelude::*;
1181/// # struct MyObs;
1182/// # impl MyObs {
1183/// #[on_stop]
1184/// fn on_stop(&mut self) -> ObserverResult {
1185///     println!("Replay complete");
1186///     Ok(())
1187/// }
1188/// # }
1189/// ```
1190#[proc_macro_attribute]
1191pub fn on_stop(_attr: TokenStream, item: TokenStream) -> TokenStream {
1192    item
1193}
1194
1195/// Marks a method as a combat log handler (Dota 2 only).
1196///
1197/// This handler is called whenever a combat log entry is generated.
1198/// Combat log entries include damage, healing, kills, abilities, items, etc.
1199///
1200/// # Parameters
1201///
1202/// The handler can receive:
1203/// - `ctx: &Context` (optional) - Current replay state
1204/// - `cle: &CombatLogEntry` (optional) - The combat log entry
1205///
1206/// Parameters can appear in any order. Return `ObserverResult`.
1207///
1208/// # Requires Feature
1209///
1210/// Only available when the `dota` feature is enabled.
1211///
1212/// # Examples
1213///
1214/// ## Track damage in real-time
1215///
1216/// ```no_run
1217/// # use source2_demo::prelude::*;
1218/// # use source2_demo::proto::DotaCombatlogTypes;
1219/// # struct MyObs {
1220/// #     total_damage: u32,
1221/// # }
1222/// # impl MyObs {
1223/// #[on_combat_log]
1224/// fn on_damage(&mut self, ctx: &Context, cle: &CombatLogEntry) -> ObserverResult {
1225///     if cle.r#type() == DotaCombatlogTypes::DotaCombatlogDamage {
1226///         if let Ok(damage) = cle.value() {
1227///             self.total_damage += damage;
1228///         }
1229///     }
1230///     Ok(())
1231/// }
1232/// # }
1233/// ```
1234///
1235/// ## Track kills
1236///
1237/// ```no_run
1238/// # use source2_demo::prelude::*;
1239/// # use source2_demo::proto::DotaCombatlogTypes;
1240/// # struct MyObs {
1241/// #     kills: u32,
1242/// # }
1243/// # impl MyObs {
1244/// #[on_combat_log]
1245/// fn on_kill(&mut self, cle: &CombatLogEntry) -> ObserverResult {
1246///     if cle.r#type() == DotaCombatlogTypes::DotaCombatlogDeath {
1247///         self.kills += 1;
1248///     }
1249///     Ok(())
1250/// }
1251/// # }
1252/// ```
1253#[cfg(feature = "dota")]
1254#[proc_macro_attribute]
1255pub fn on_combat_log(_attr: TokenStream, item: TokenStream) -> TokenStream {
1256    item
1257}
1258
1259/// Marks the impl block or individual method to enable all tracking.
1260///
1261/// This is equivalent to `#[observer(all)]`.
1262///
1263/// # Examples
1264///
1265/// ```no_run
1266/// # use source2_demo::prelude::*;
1267/// #[observer]
1268/// #[uses_all]
1269/// impl MyObs {
1270/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1271/// }
1272/// ```
1273#[proc_macro_attribute]
1274pub fn uses_all(_attr: TokenStream, item: TokenStream) -> TokenStream {
1275    item
1276}
1277
1278/// Marks the impl block to enable entity tracking.
1279///
1280/// When applied to an impl block or individual method, automatically enables
1281/// the `ENTITY_STATE` interest flag so entities are tracked during parsing.
1282///
1283/// # Examples
1284///
1285/// ```no_run
1286/// # use source2_demo::prelude::*;
1287/// #[observer]
1288/// #[uses_entities]
1289/// impl MyObs {
1290///     // Now you can use #[on_entity] handlers
1291/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1292/// }
1293/// ```
1294#[proc_macro_attribute]
1295pub fn uses_entities(_attr: TokenStream, item: TokenStream) -> TokenStream {
1296    item
1297}
1298
1299/// Marks the impl block to enable string table tracking.
1300///
1301/// When applied to an impl block or individual method, automatically enables
1302/// the `STRING_TABLE_STATE` interest flag so string tables are tracked during parsing.
1303///
1304/// # Examples
1305///
1306/// ```no_run
1307/// # use source2_demo::prelude::*;
1308/// #[observer]
1309/// #[uses_string_tables]
1310/// impl MyObs {
1311///     // Now you can use #[on_string_table] handlers
1312/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1313/// }
1314/// ```
1315#[proc_macro_attribute]
1316pub fn uses_string_tables(_attr: TokenStream, item: TokenStream) -> TokenStream {
1317    item
1318}
1319
1320/// Marks the impl block to enable game event tracking.
1321///
1322/// When applied to an impl block or individual method, automatically enables
1323/// the `BASE_GAME_EVENT` interest flag so game events are tracked during parsing.
1324///
1325/// # Examples
1326///
1327/// ```no_run
1328/// # use source2_demo::prelude::*;
1329/// #[observer]
1330/// #[uses_game_events]
1331/// impl MyObs {
1332///     // Now you can use #[on_game_event] handlers
1333/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1334/// }
1335/// ```
1336#[proc_macro_attribute]
1337pub fn uses_game_events(_attr: TokenStream, item: TokenStream) -> TokenStream {
1338    item
1339}
1340
1341/// Marks the impl block to enable combat log tracking (Dota 2 only).
1342///
1343/// When applied to an impl block or individual method, automatically enables
1344/// the `COMBAT_LOG_ENTRIES` and `STRING_TABLE_STATE` interest flags for combat
1345/// log parsing.
1346///
1347/// # Requires Feature
1348///
1349/// Only available when the `dota` feature is enabled.
1350///
1351/// # Examples
1352///
1353/// ```no_run
1354/// # use source2_demo::prelude::*;
1355/// #[observer]
1356/// #[uses_combat_log]
1357/// impl MyObs {
1358///     // Now you can use #[on_combat_log] handlers
1359/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1360/// }
1361/// ```
1362#[cfg(feature = "dota")]
1363#[proc_macro_attribute]
1364pub fn uses_combat_log(_attr: TokenStream, item: TokenStream) -> TokenStream {
1365    item
1366}