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 and `#[should_track_entity]` filters which entities retain state.
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/// Filters which entities retain decoded field state while rewriting.
858///
859/// Return `false` to skip state decoding when the entity also does not enter
860/// the rewrite path. The default is `true`, preserving complete entity state.
861///
862/// The handler can receive `ctx: &Context`, `event: EntityEvents`, and
863/// `entity: &Entity` in any order. Return `bool`.
864#[proc_macro_attribute]
865pub fn should_track_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
866    item
867}
868
869/// Marks a method as a tick-start handler.
870///
871/// This handler is called at the beginning of each game tick. Use it for
872/// per-tick logic like updating game state, tracking changes, or generating output.
873///
874/// # Parameters
875///
876/// The handler can receive:
877/// - `ctx: &Context` (optional) - Current replay state including tick number
878///
879/// Return `ObserverResult`.
880///
881/// # When It's Called
882///
883/// Called right after the tick number is incremented in the Context, but before
884/// any other tick-specific events are processed.
885///
886/// # Examples
887///
888/// ## Track entity state every tick
889///
890/// ```no_run
891/// # use source2_demo::prelude::*;
892/// # struct MyObs;
893/// # impl MyObs {
894/// #[on_tick_start]
895/// fn on_tick_start(&mut self, ctx: &Context) -> ObserverResult {
896///     if ctx.tick() % 30 == 0 {  // Every second (30 ticks/sec)
897///         println!("Ticks processed: {}", ctx.tick());
898///     }
899///     Ok(())
900/// }
901/// # }
902/// ```
903///
904/// ## Without context
905///
906/// ```no_run
907/// # use source2_demo::prelude::*;
908/// # struct MyObs;
909/// # impl MyObs {
910/// #[on_tick_start]
911/// fn on_tick_start(&mut self) -> ObserverResult {
912///     // Do something without needing context
913///     Ok(())
914/// }
915/// # }
916/// ```
917#[proc_macro_attribute]
918pub fn on_tick_start(_attr: TokenStream, item: TokenStream) -> TokenStream {
919    item
920}
921
922/// Marks a method as a tick-end handler.
923///
924/// This handler is called at the end of each game tick. Use it to finalize
925/// per-tick calculations, output results, or reset temporary state.
926///
927/// # Parameters
928///
929/// The handler can receive:
930/// - `ctx: &Context` (optional) - Current replay state
931///
932/// Return `ObserverResult`.
933///
934/// # When It's Called
935///
936/// Called after all events for the current tick have been processed.
937///
938/// # Examples
939///
940/// ## Flush buffered data at end of tick
941///
942/// ```no_run
943/// # use source2_demo::prelude::*;
944/// # struct MyObs {
945/// #     buffer: Vec<String>,
946/// # }
947/// # impl MyObs {
948/// #[on_tick_end]
949/// fn on_tick_end(&mut self, ctx: &Context) -> ObserverResult {
950///     // Output buffered data
951///     for item in self.buffer.drain(..) {
952///         println!("[{}] {}", ctx.tick(), item);
953///     }
954///     Ok(())
955/// }
956/// # }
957/// ```
958#[proc_macro_attribute]
959pub fn on_tick_end(_attr: TokenStream, item: TokenStream) -> TokenStream {
960    item
961}
962
963/// Marks a method as an entity event handler.
964///
965/// This handler is called when entities are created, updated, or deleted.
966/// Optionally filter to specific entity class names.
967///
968/// # Parameters
969///
970/// The handler can receive:
971/// - `ctx: &Context` (optional) - Current replay state
972/// - `event: EntityEvents` or `event: &EntityEvents` (optional) - Type of entity event
973/// - `entity: &Entity` (optional) - The entity that changed
974///
975/// Parameters can appear in any order. Return `ObserverResult`.
976///
977/// # Filtering
978///
979/// Pass a class name to only handle entities of that type:
980/// ```no_run
981/// # use source2_demo::prelude::*;
982/// # struct MyObs;
983/// # impl MyObs {
984/// #[on_entity("CDOTA_Unit_Hero_Axe")]
985/// fn on_axe(&mut self, entity: &Entity) -> ObserverResult {
986///     // Only called for Axe hero entity
987///     Ok(())
988/// }
989/// # }
990/// ```
991///
992/// # Examples
993///
994/// ## Track all entity changes
995///
996/// ```no_run
997/// # use source2_demo::prelude::*;
998/// # struct MyObs {
999/// #     created: usize,
1000/// # }
1001/// # impl MyObs {
1002/// #[on_entity]
1003/// fn on_entity(&mut self, event: EntityEvents, entity: &Entity) -> ObserverResult {
1004///     if event == EntityEvents::Created {
1005///         self.created += 1;
1006///     }
1007///     Ok(())
1008/// }
1009/// # }
1010/// ```
1011///
1012/// ## Track specific entity type
1013///
1014/// ```no_run
1015/// # use source2_demo::prelude::*;
1016/// # struct MyObs;
1017/// # impl MyObs {
1018/// #[on_entity("CDOTA_PlayerResource")]
1019/// fn on_player_resource(&mut self, ctx: &Context, entity: &Entity) -> ObserverResult {
1020///     // Only called when player resource entity updates
1021///     Ok(())
1022/// }
1023/// # }
1024/// ```
1025#[proc_macro_attribute]
1026pub fn on_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
1027    item
1028}
1029
1030/// Marks a method as a game event handler.
1031///
1032/// This handler is called when game events occur (kills, deaths, item purchases, etc.).
1033/// Optionally filter to specific event names.
1034///
1035/// # Parameters
1036///
1037/// The handler can receive:
1038/// - `ctx: &Context` (optional) - Current replay state
1039/// - `ge: &GameEvent` (optional) - The game event
1040///
1041/// Parameters can appear in any order. Return `ObserverResult`.
1042///
1043/// # Filtering
1044///
1045/// Pass an event name to only handle that event type:
1046/// ```no_run
1047/// # use source2_demo::prelude::*;
1048/// # struct MyObs;
1049/// # impl MyObs {
1050/// #[on_game_event("player_death")]
1051/// fn on_death(&mut self, ge: &GameEvent) -> ObserverResult {
1052///     // Only called for player_death events
1053///     Ok(())
1054/// }
1055/// # }
1056/// ```
1057///
1058/// # Examples
1059///
1060/// ## Handle all game events
1061///
1062/// ```no_run
1063/// # use source2_demo::prelude::*;
1064/// # struct MyObs;
1065/// # impl MyObs {
1066/// #[on_game_event]
1067/// fn on_event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
1068///     println!("[{}] Event: {}", ctx.tick(), ge.name());
1069///     Ok(())
1070/// }
1071/// # }
1072/// ```
1073///
1074/// ## Handle specific event type
1075///
1076/// ```no_run
1077/// # use source2_demo::prelude::*;
1078/// # struct MyObs {
1079/// #     kill_count: u32,
1080/// # }
1081/// # impl MyObs {
1082/// #[on_game_event("dota_player_kill")]
1083/// fn on_kill(&mut self, ge: &GameEvent) -> ObserverResult {
1084///     self.kill_count += 1;
1085///     Ok(())
1086/// }
1087/// # }
1088/// ```
1089#[proc_macro_attribute]
1090pub fn on_game_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
1091    item
1092}
1093
1094/// Marks a method as a string table update handler.
1095///
1096/// This handler is called when string tables are updated. String tables contain
1097/// game data like player names, modifiers, effects, etc.
1098/// Optionally filter to specific table names.
1099///
1100/// # Parameters
1101///
1102/// The handler can receive:
1103/// - `ctx: &Context` (optional) - Current replay state
1104/// - `table: &StringTable` (optional) - The updated string table
1105/// - `modified: &[i32]` (optional) - Indices of rows that were modified
1106///
1107/// Parameters can appear in any order. Return `ObserverResult`.
1108///
1109/// # Filtering
1110///
1111/// Pass a table name to only handle that table:
1112/// ```no_run
1113/// # use source2_demo::prelude::*;
1114/// # struct MyObs;
1115/// # impl MyObs {
1116/// #[on_string_table("userinfo")]
1117/// fn on_userinfo(&mut self, table: &StringTable, modified: &[i32]) -> ObserverResult {
1118///     // Only called when userinfo table updates
1119///     Ok(())
1120/// }
1121/// # }
1122/// ```
1123///
1124/// # Examples
1125///
1126/// ## Track all string table updates
1127///
1128/// ```no_run
1129/// # use source2_demo::prelude::*;
1130/// # struct MyObs;
1131/// # impl MyObs {
1132/// #[on_string_table]
1133/// fn on_table_update(&mut self, ctx: &Context, table: &StringTable, modified: &[i32]) -> ObserverResult {
1134///     println!("[{}] Table {} updated: {} rows", ctx.tick(), table.name(), modified.len());
1135///     Ok(())
1136/// }
1137/// # }
1138/// ```
1139///
1140/// ## Monitor specific table
1141///
1142/// ```no_run
1143/// # use source2_demo::prelude::*;
1144/// # struct MyObs;
1145/// # impl MyObs {
1146/// #[on_string_table("ActiveModifiers")]
1147/// fn on_modifiers(&mut self, table: &StringTable, modified: &[i32]) -> ObserverResult {
1148///     println!("Active modifiers changed: {} rows", modified.len());
1149///     Ok(())
1150/// }
1151/// # }
1152/// ```
1153#[proc_macro_attribute]
1154pub fn on_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
1155    item
1156}
1157
1158/// Marks a method as a replay stop handler.
1159///
1160/// This handler is called when the replay ends (CDemoStop message).
1161/// Use it to finalize results, output statistics, or clean up resources.
1162///
1163/// # Parameters
1164///
1165/// The handler can receive:
1166/// - `ctx: &Context` (optional) - Final replay state
1167///
1168/// Return `ObserverResult`.
1169///
1170/// # Examples
1171///
1172/// ## Output final statistics
1173///
1174/// ```no_run
1175/// # use source2_demo::prelude::*;
1176/// # struct MyObs {
1177/// #     ticks: u32,
1178/// # }
1179/// # impl MyObs {
1180/// #[on_stop]
1181/// fn on_stop(&mut self, ctx: &Context) -> ObserverResult {
1182///     println!("Replay ended at tick {}", ctx.tick());
1183///     println!("Total ticks processed: {}", self.ticks);
1184///     Ok(())
1185/// }
1186/// # }
1187/// ```
1188///
1189/// ## Without context
1190///
1191/// ```no_run
1192/// # use source2_demo::prelude::*;
1193/// # struct MyObs;
1194/// # impl MyObs {
1195/// #[on_stop]
1196/// fn on_stop(&mut self) -> ObserverResult {
1197///     println!("Replay complete");
1198///     Ok(())
1199/// }
1200/// # }
1201/// ```
1202#[proc_macro_attribute]
1203pub fn on_stop(_attr: TokenStream, item: TokenStream) -> TokenStream {
1204    item
1205}
1206
1207/// Marks a method as a combat log handler (Dota 2 only).
1208///
1209/// This handler is called whenever a combat log entry is generated.
1210/// Combat log entries include damage, healing, kills, abilities, items, etc.
1211///
1212/// # Parameters
1213///
1214/// The handler can receive:
1215/// - `ctx: &Context` (optional) - Current replay state
1216/// - `cle: &CombatLogEntry` (optional) - The combat log entry
1217///
1218/// Parameters can appear in any order. Return `ObserverResult`.
1219///
1220/// # Requires Feature
1221///
1222/// Only available when the `dota` feature is enabled.
1223///
1224/// # Examples
1225///
1226/// ## Track damage in real-time
1227///
1228/// ```no_run
1229/// # use source2_demo::prelude::*;
1230/// # use source2_demo::proto::DotaCombatlogTypes;
1231/// # struct MyObs {
1232/// #     total_damage: u32,
1233/// # }
1234/// # impl MyObs {
1235/// #[on_combat_log]
1236/// fn on_damage(&mut self, ctx: &Context, cle: &CombatLogEntry) -> ObserverResult {
1237///     if cle.r#type() == DotaCombatlogTypes::DotaCombatlogDamage {
1238///         if let Ok(damage) = cle.value() {
1239///             self.total_damage += damage;
1240///         }
1241///     }
1242///     Ok(())
1243/// }
1244/// # }
1245/// ```
1246///
1247/// ## Track kills
1248///
1249/// ```no_run
1250/// # use source2_demo::prelude::*;
1251/// # use source2_demo::proto::DotaCombatlogTypes;
1252/// # struct MyObs {
1253/// #     kills: u32,
1254/// # }
1255/// # impl MyObs {
1256/// #[on_combat_log]
1257/// fn on_kill(&mut self, cle: &CombatLogEntry) -> ObserverResult {
1258///     if cle.r#type() == DotaCombatlogTypes::DotaCombatlogDeath {
1259///         self.kills += 1;
1260///     }
1261///     Ok(())
1262/// }
1263/// # }
1264/// ```
1265#[cfg(feature = "dota")]
1266#[proc_macro_attribute]
1267pub fn on_combat_log(_attr: TokenStream, item: TokenStream) -> TokenStream {
1268    item
1269}
1270
1271/// Marks the impl block or individual method to enable all tracking.
1272///
1273/// This is equivalent to `#[observer(all)]`.
1274///
1275/// # Examples
1276///
1277/// ```no_run
1278/// # use source2_demo::prelude::*;
1279/// #[observer]
1280/// #[uses_all]
1281/// impl MyObs {
1282/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1283/// }
1284/// ```
1285#[proc_macro_attribute]
1286pub fn uses_all(_attr: TokenStream, item: TokenStream) -> TokenStream {
1287    item
1288}
1289
1290/// Marks the impl block to enable entity tracking.
1291///
1292/// When applied to an impl block or individual method, automatically enables
1293/// the `ENTITY_STATE` interest flag so entities are tracked during parsing.
1294///
1295/// # Examples
1296///
1297/// ```no_run
1298/// # use source2_demo::prelude::*;
1299/// #[observer]
1300/// #[uses_entities]
1301/// impl MyObs {
1302///     // Now you can use #[on_entity] handlers
1303/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1304/// }
1305/// ```
1306#[proc_macro_attribute]
1307pub fn uses_entities(_attr: TokenStream, item: TokenStream) -> TokenStream {
1308    item
1309}
1310
1311/// Marks the impl block to enable string table tracking.
1312///
1313/// When applied to an impl block or individual method, automatically enables
1314/// the `STRING_TABLE_STATE` interest flag so string tables are tracked during parsing.
1315///
1316/// # Examples
1317///
1318/// ```no_run
1319/// # use source2_demo::prelude::*;
1320/// #[observer]
1321/// #[uses_string_tables]
1322/// impl MyObs {
1323///     // Now you can use #[on_string_table] handlers
1324/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1325/// }
1326/// ```
1327#[proc_macro_attribute]
1328pub fn uses_string_tables(_attr: TokenStream, item: TokenStream) -> TokenStream {
1329    item
1330}
1331
1332/// Marks the impl block to enable game event tracking.
1333///
1334/// When applied to an impl block or individual method, automatically enables
1335/// the `BASE_GAME_EVENT` interest flag so game events are tracked during parsing.
1336///
1337/// # Examples
1338///
1339/// ```no_run
1340/// # use source2_demo::prelude::*;
1341/// #[observer]
1342/// #[uses_game_events]
1343/// impl MyObs {
1344///     // Now you can use #[on_game_event] handlers
1345/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1346/// }
1347/// ```
1348#[proc_macro_attribute]
1349pub fn uses_game_events(_attr: TokenStream, item: TokenStream) -> TokenStream {
1350    item
1351}
1352
1353/// Marks the impl block to enable combat log tracking (Dota 2 only).
1354///
1355/// When applied to an impl block or individual method, automatically enables
1356/// the `COMBAT_LOG_ENTRIES` and `STRING_TABLE_STATE` interest flags for combat
1357/// log parsing.
1358///
1359/// # Requires Feature
1360///
1361/// Only available when the `dota` feature is enabled.
1362///
1363/// # Examples
1364///
1365/// ```no_run
1366/// # use source2_demo::prelude::*;
1367/// #[observer]
1368/// #[uses_combat_log]
1369/// impl MyObs {
1370///     // Now you can use #[on_combat_log] handlers
1371/// # fn dummy(&mut self) -> ObserverResult { Ok(()) }
1372/// }
1373/// ```
1374#[cfg(feature = "dota")]
1375#[proc_macro_attribute]
1376pub fn uses_combat_log(_attr: TokenStream, item: TokenStream) -> TokenStream {
1377    item
1378}