source2_demo_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod protobuf_map;
4
5use crate::protobuf_map::get_enum_from_struct;
6use proc_macro::{TokenStream};
7use quote::{quote, ToTokens};
8use syn::{parse_macro_input, parse::Parser, punctuated::Punctuated, Ident, ItemImpl, Token, Type, FnArg};
9
10#[allow(unused_mut)]
11#[proc_macro_attribute]
12pub fn observer(attr: TokenStream, item: TokenStream) -> TokenStream {
13    let mut mode_all = false;
14
15    if !attr.is_empty() {
16        let parser = Punctuated::<Ident, Token![,]>::parse_terminated;
17        if let Ok(idents) = parser.parse(attr.clone()) {
18            for id in idents {
19                if id == "manual" { mode_all = false; }
20                if id == "all" { mode_all = true; }
21            }
22        }
23    }
24
25    let input = parse_macro_input!(item as ItemImpl);
26    let struct_name = &input.self_ty;
27
28    let mut interests = quote!(::source2_demo::Interests::empty());
29    macro_rules! add_flag {
30        ($flag:ident) => {
31            interests = quote!(#interests | ::source2_demo::Interests::$flag);
32        };
33    }
34
35    for a in &input.attrs {
36        if a.path().is_ident("uses_entities") { add_flag!(ENABLE_ENTITY); }
37        if a.path().is_ident("uses_string_tables") { add_flag!(ENABLE_STRINGTAB); }
38        if a.path().is_ident("uses_game_events") { add_flag!(BASE_GE); }
39        #[cfg(feature = "dota")]
40        if a.path().is_ident("uses_combat_log") {
41            add_flag!(ENABLE_STRINGTAB);
42            add_flag!(COMBAT_LOG);
43        }
44    }
45
46    let mut has_demo = false;
47    let mut has_net = false;
48    let mut has_svc = false;
49    let mut has_base_um = false;
50    let mut has_base_ge = false;
51    let mut has_tick_start = false;
52    let mut has_tick_end = false;
53    let mut has_entity = false;
54    let mut has_entity_track = false;
55    let mut has_string_table = false;
56    let mut has_string_table_track = false;
57    let mut has_stop = false;
58
59    #[cfg(feature = "dota")]
60    let mut has_dota_um = false;
61    #[cfg(feature = "dota")]
62    let mut has_combat_log = false;
63
64    #[cfg(feature = "citadel")]
65    let mut has_cita_um = false;
66    #[cfg(feature = "citadel")]
67    let mut has_cita_ge = false;
68
69    #[cfg(feature = "cs2")]
70    let mut has_cs2_um = false;
71    #[cfg(feature = "cs2")]
72    let mut has_cs2_ge = false;
73
74    #[cfg(feature = "dota")]
75    let mut on_combat_log_body = quote!();
76    #[cfg(feature = "dota")]
77    let mut on_dota_user_message_body = quote!();
78
79    #[cfg(feature = "citadel")]
80    let mut on_citadel_user_message_body = quote!();
81    #[cfg(feature = "citadel")]
82    let mut on_citadel_game_event_body = quote!();
83
84    #[cfg(feature = "cs2")]
85    let mut on_cs2_user_message_body = quote!();
86    #[cfg(feature = "cs2")]
87    let mut on_cs2_game_event_body = quote!();
88
89    let mut on_svc_message_body = quote!();
90    let mut on_net_message_body = quote!();
91    let mut on_base_game_event_body = quote!();
92    let mut on_demo_command_body = quote!();
93    let mut on_base_user_message_body = quote!();
94    let mut on_tick_start_body = quote!();
95    let mut on_tick_end_body = quote!();
96    let mut on_entity_body = quote!();
97    let mut on_game_event_body = quote!();
98    let mut on_string_table_body = quote!();
99    let mut on_stop_body = quote!();
100
101    for item in &input.items {
102        if let syn::ImplItem::Fn(method) = item {
103            for attr in &method.attrs {
104                for a in &method.attrs {
105                    if a.path().is_ident("uses_entities") { add_flag!(ENABLE_ENTITY); }
106                    if a.path().is_ident("uses_string_tables") { add_flag!(ENABLE_STRINGTAB); }
107                    if a.path().is_ident("uses_game_events") { add_flag!(BASE_GE); }
108                    #[cfg(feature = "dota")]
109                    if a.path().is_ident("uses_combat_log") { add_flag!(DOTA_UM); }
110                }
111
112                let method_name = method.sig.ident.clone();
113                let mut args = vec![];
114
115                let (arg_type, _) = get_arg_type(method, 1);
116                if arg_type.to_token_stream().to_string() == "Context" {
117                    args.push(quote! { ctx })
118                }
119
120                if let Some(ident) = attr.path().get_ident() {
121                    match ident.to_string().as_str() {
122                        "on_tick_start" => {
123                            has_tick_start = true;
124                            on_tick_start_body.extend(quote! {
125                            self.#method_name(#(#args),*)?;
126                        })
127                        }
128                        "on_tick_end" => {
129                            has_tick_end = true;
130                            on_tick_end_body.extend(quote! {
131                            self.#method_name(#(#args),*)?;
132                        })
133                        }
134                        "on_stop" => {
135                            has_stop = true;
136                            on_stop_body.extend(quote! {
137                                self.#method_name(#(#args),*)?;
138                            });
139                        }
140                        #[cfg(feature = "dota")]
141                        "on_combat_log" => {
142                            args.push(quote! { cle });
143
144                            on_combat_log_body.extend(quote! {
145                                self.#method_name(#(#args),*)?;
146                            })
147                        }
148                        "on_entity" => {
149                            has_entity_track = true;
150                            let (arg_type, is_ref) = get_arg_type(method, args.len() + 1);
151
152                            if arg_type.to_token_stream().to_string() == "EntityEvents" {
153                                if is_ref {
154                                    args.push(quote! { &event });
155                                } else {
156                                    args.push(quote! { event });
157                                }
158                            }
159
160                            args.push(quote! { entity });
161
162                            on_entity_body.extend(if let Ok(entity_class) = attr.parse_args::<syn::LitStr>() {
163                                quote! {
164                                    if entity.class().name() == #entity_class {
165                                        self.#method_name(#(#args),*)?;
166                                    }
167                                }
168                            } else {
169                                quote! {
170                                    self.#method_name(#(#args),*)?;
171                                }
172                            });
173                        }
174                        "on_game_event" => {
175                            args.push(quote! { ge });
176                            on_game_event_body.extend(if let Ok(event_name) = attr.parse_args::<syn::LitStr>() {
177                                quote! {
178                                    if ge.name() == #event_name {
179                                        self.#method_name(#(#args),*)?;
180                                    }
181                                }
182                            } else {
183                                quote! {
184                                    self.#method_name(#(#args),*)?;
185                                }
186                            });
187                        }
188                        "on_string_table" => {
189                            has_string_table_track = true;
190                            args.push(quote! { table });
191                            args.push(quote! { modified });
192                            on_string_table_body.extend(if let Ok(table_name) = attr.parse_args::<syn::LitStr>() {
193                                quote! {
194                                    if table.name() == #table_name {
195                                        self.#method_name(#(#args),*)?;
196                                    }
197                                }
198                            } else {
199                                quote! {
200                                    self.#method_name(#(#args),*)?;
201                                }
202                            });
203                        }
204                        "on_message" => {
205                            let (arg_type, is_ref) = get_arg_type(method, args.len() + 1);
206                            let enum_type = get_enum_from_struct(arg_type.to_token_stream().to_string().as_str());
207                            let type_string = enum_type.to_token_stream().to_string();
208                            let root = type_string.split("::").collect::<Vec<_>>()[0].trim();
209
210                            args.push(if is_ref {
211                                quote! { &message }
212                            } else {
213                                quote! { message }
214                            });
215
216                            macro_rules! extend {
217                                ($body: ident) => {
218                                    $body.extend(quote! {
219                                        if msg_type == #enum_type {
220                                            if let Ok(message) = #arg_type::decode(msg) {
221                                                self.#method_name(#(#args),*)?;
222                                            }
223                                        }
224                                    })
225                                };
226                            }
227
228                            match root {
229                                "EDemoCommands" => has_demo = true,
230                                "EBaseUserMessages" => has_base_um = true,
231                                "EBaseGameEvents" => has_base_ge = true,
232                                "SvcMessages" => has_svc = true,
233                                "NetMessages" => has_net = true,
234                                #[cfg(feature = "dota")]
235                                "EDotaUserMessages" => has_dota_um = true,
236                                #[cfg(feature = "citadel")]
237                                "CitadelUserMessageIds" => has_cita_um = true,
238                                #[cfg(feature = "citadel")]
239                                "ECitadelGameEvents" => has_cita_ge = true,
240                                #[cfg(feature = "cs2")]
241                                "ECstrike15UserMessages" => has_cs2_um = true,
242                                #[cfg(feature = "cs2")]
243                                "ECsgoGameEvents" => has_cs2_ge = true,
244                                _ => {}
245                            }
246
247
248                            match root {
249                                "EDemoCommands" => extend!(on_demo_command_body),
250                                "EBaseUserMessages" => extend!(on_base_user_message_body),
251                                "EBaseGameEvents" => extend!(on_base_game_event_body),
252                                "SvcMessages" => extend!(on_svc_message_body),
253                                "NetMessages" => extend!(on_net_message_body),
254
255                                #[cfg(feature = "dota")]
256                                "EDotaUserMessages" => extend!(on_dota_user_message_body),
257                                #[cfg(feature = "citadel")]
258                                "CitadelUserMessageIds" => extend!(on_citadel_user_message_body),
259                                #[cfg(feature = "citadel")]
260                                "ECitadelGameEvents" => extend!(on_citadel_game_event_body),
261                                #[cfg(feature = "cs2")]
262                                "ECstrike15UserMessages" => extend!(on_cs2_user_message_body),
263                                #[cfg(feature = "cs2")]
264                                "ECsgoGameEvents" => extend!(on_cs2_game_event_body),
265
266                                x => unreachable!("{}", x),
267                            }
268                        }
269                        _ => {}
270                    }
271                }
272            }
273        }
274    }
275
276    let mut obs_body = quote! {
277        fn on_base_user_message(
278            &mut self,
279            ctx: &Context,
280            msg_type: EBaseUserMessages,
281            msg: &[u8],
282        ) -> ObserverResult {
283            #on_base_user_message_body
284            Ok(())
285        }
286
287        fn on_svc_message(
288            &mut self,
289            ctx: &Context,
290            msg_type: SvcMessages,
291            msg: &[u8],
292        ) -> ObserverResult {
293            #on_svc_message_body
294            Ok(())
295        }
296
297        fn on_net_message(
298            &mut self,
299            ctx: &Context,
300            msg_type: NetMessages,
301            msg: &[u8],
302        ) -> ObserverResult {
303            #on_net_message_body
304            Ok(())
305        }
306
307        fn on_base_game_event(
308            &mut self,
309            ctx: &Context,
310            msg_type: EBaseGameEvents,
311            msg: &[u8],
312        ) -> ObserverResult {
313            #on_base_game_event_body
314            Ok(())
315        }
316
317        fn on_demo_command(
318            &mut self,
319            ctx: &Context,
320            msg_type: EDemoCommands,
321            msg: &[u8],
322        ) -> ObserverResult {
323            #on_demo_command_body
324            Ok(())
325        }
326
327        fn on_tick_start(
328            &mut self,
329            ctx: &Context,
330        ) -> ObserverResult {
331            #on_tick_start_body
332            Ok(())
333        }
334
335        fn on_tick_end(
336            &mut self,
337            ctx: &Context,
338        ) -> ObserverResult {
339            #on_tick_end_body
340            Ok(())
341        }
342
343        fn on_entity(
344            &mut self,
345            ctx: &Context,
346            event: EntityEvents,
347            entity: &Entity,
348        ) -> ObserverResult {
349            #on_entity_body
350            Ok(())
351        }
352
353        fn on_game_event(
354            &mut self,
355            ctx: &Context,
356            ge: &GameEvent
357        ) -> ObserverResult {
358            #on_game_event_body
359            Ok(())
360        }
361
362        fn on_string_table(
363            &mut self,
364            ctx: &Context,
365            table: &StringTable,
366            modified: &[i32]
367        ) -> ObserverResult {
368            #on_string_table_body
369            Ok(())
370        }
371
372        fn on_stop(
373            &mut self,
374            ctx: &Context,
375        ) -> ObserverResult {
376            #on_stop_body
377            Ok(())
378        }
379    };
380
381    #[cfg(feature = "dota")]
382    obs_body.extend(quote! {
383        fn on_combat_log(
384            &mut self,
385            ctx: &Context,
386            cle: &CombatLogEntry
387        ) -> ObserverResult {
388            #on_combat_log_body
389            Ok(())
390        }
391
392        fn on_dota_user_message(
393            &mut self,
394            ctx: &Context,
395            msg_type: EDotaUserMessages,
396            msg: &[u8],
397        ) -> ObserverResult {
398            #on_dota_user_message_body
399            Ok(())
400        }
401    });
402
403    #[cfg(feature = "citadel")]
404    obs_body.extend(quote! {
405        fn on_citadel_user_message(
406            &mut self,
407            ctx: &Context,
408            msg_type: CitadelUserMessageIds,
409            msg: &[u8],
410        ) -> ObserverResult {
411            #on_citadel_user_message_body
412            Ok(())
413        }
414
415        fn on_citadel_game_event(
416            &mut self,
417            ctx: &Context,
418            msg_type: ECitadelGameEvents,
419            msg: &[u8],
420        ) -> ObserverResult {
421            #on_citadel_game_event_body
422            Ok(())
423        }
424    });
425
426    #[cfg(feature = "cs2")]
427    obs_body.extend(quote! {
428        fn on_cs2_user_message(
429            &mut self,
430            ctx: &Context,
431            msg_type: ECstrike15UserMessages,
432            msg: &[u8],
433        ) -> ObserverResult {
434            #on_cs2_user_message_body
435            Ok(())
436        }
437
438        fn on_cs2_game_event(
439            &mut self,
440            ctx: &Context,
441            msg_type: ECsgoGameEvents,
442            msg: &[u8],
443        ) -> ObserverResult {
444            #on_cs2_game_event_body
445            Ok(())
446        }
447    });
448
449    macro_rules! add_if { ($cond:expr, $flag:ident) => {
450        if $cond { interests = quote!(#interests | ::source2_demo::Interests::$flag); }
451    }}
452
453    if mode_all {
454        interests = quote!(::source2_demo::Interests::all());
455    }
456
457    add_if!(has_demo,       DEMO);
458    add_if!(has_net,        NET);
459    add_if!(has_svc,        SVC);
460    add_if!(has_base_um,    BASE_UM);
461    add_if!(has_base_ge,    BASE_GE);
462    add_if!(has_tick_start, TICK_START);
463    add_if!(has_tick_end,   TICK_END);
464    add_if!(has_entity,     ENABLE_ENTITY);
465    add_if!(has_entity_track,     TRACK_ENTITY);
466    add_if!(has_string_table,  ENABLE_STRINGTAB);
467    add_if!(has_string_table_track,  TRACK_STRINGTAB);
468    add_if!(has_stop,       STOP);
469
470    #[cfg(feature = "dota")]
471    add_if!(has_dota_um,    DOTA_UM);
472    #[cfg(feature = "dota")]
473    add_if!(has_combat_log, COMBAT_LOG);
474
475    #[cfg(feature = "citadel")]
476    add_if!(has_cita_um,    CITA_UM);
477    #[cfg(feature = "citadel")]
478    add_if!(has_cita_ge,    CITA_GE);
479
480    #[cfg(feature = "cs2")]
481    add_if!(has_cs2_um,     CS2_UM);
482    #[cfg(feature = "cs2")]
483    add_if!(has_cs2_ge,     CS2_GE);
484
485    let ret = quote! {
486        impl Observer for #struct_name {
487            fn interests(&self) -> ::source2_demo::Interests { #interests }
488            #obs_body
489        }
490        #input
491    };
492
493    TokenStream::from(ret)
494}
495
496fn get_arg_type(method: &syn::ImplItemFn, n: usize) -> (Type, bool) {
497    if let Some(FnArg::Typed(pat_type)) = method.sig.inputs.iter().nth(n) {
498        if let Type::Reference(x) = pat_type.ty.as_ref() {
499            (*x.elem.clone(), true)
500        } else {
501            (*pat_type.ty.clone(), false)
502        }
503    } else {
504        panic!("Expected argument")
505    }
506}
507
508/// A method wrapped with `#[on_message]` macro is called whenever a specified protobuf message appears in replay.
509///
510/// # Examples
511///
512/// ```no_compile
513/// #[on_message]
514/// fn message(&mut self, ctx: &Context, message: &CCitadelUserMsgChatMsg) -> ObserverResult {
515///     Ok(())
516/// }
517/// ```
518///
519/// ```no_compile
520/// #[on_message]
521/// fn message(&mut self, message: CCitadelUserMsgChatMsg) -> ObserverResult {
522///     Ok(())
523/// }
524/// ```
525#[proc_macro_attribute]
526pub fn on_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
527    item
528}
529
530/// A method wrapped with `#[on_tick_start]` macro is called at the start of each tick.
531///
532/// # Examples
533///
534/// ```no_compile
535/// #[on_tick_start]
536/// fn tick_start(&mut self, ctx: &Context) -> ObserverResult {
537///    Ok(())
538/// }
539/// ```
540///
541/// ```no_compile
542/// #[on_tick_start]
543/// fn tick_start(&mut self) -> ObserverResult {
544///    Ok(())
545/// }
546/// ```
547#[proc_macro_attribute]
548pub fn on_tick_start(_attr: TokenStream, item: TokenStream) -> TokenStream {
549    item
550}
551
552/// A method wrapped with `#[on_tick_end]` macro is called at the end of each tick.
553///
554/// # Examples
555///
556/// ```no_compile
557/// #[on_tick_end]
558/// fn tick_end(&mut self, ctx: &Context) -> ObserverResult {
559///    Ok(())
560/// }
561/// ```
562///
563/// ```no_compile
564/// #[on_tick_end]
565/// fn tick_end(&mut self) -> ObserverResult {
566///    Ok(())
567/// }
568/// ```
569#[proc_macro_attribute]
570pub fn on_tick_end(_attr: TokenStream, item: TokenStream) -> TokenStream {
571    item
572}
573
574/// A method wrapped with `#[on_entity]` macro is called whenever an entity is created, updated or deleted.
575///
576/// # Examples
577///
578/// ```no_compile
579/// #[on_entity]
580/// fn entity(&mut self, ctx: &Context, event: EntityEvents, entity: &Entity) -> ObserverResult {
581///    Ok(())
582/// }
583/// ```
584///
585/// ```no_compile
586/// #[on_entity("CCitadelPlayerPawn")] // Will be called for entities with class "CCitadelPlayerPawn"
587/// fn entity(&mut self, ctx: &Context, entity: &Entity) -> ObserverResult {
588///    Ok(())
589/// }
590/// ```
591///
592/// ```no_compile
593/// #[on_entity]
594/// fn entity(&mut self, entity: &Entity) -> ObserverResult {
595///    Ok(())
596/// }
597/// ```
598#[proc_macro_attribute]
599pub fn on_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
600    item
601}
602
603/// A method wrapped with `#[on_game_event]` macro is called whenever CSvcMsgGameEvent appears in replay.
604///
605/// # Examples
606///
607/// ```no_compile
608/// #[on_game_event] // Will be called for all game events
609/// fn event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
610///    Ok(())
611/// }
612/// ```
613///
614/// ```no_compile
615/// #[on_game_event("player_death")] // Will be called for "player_death" event only
616/// fn event(&mut self, ctx: &Context, ge: &GameEvent) -> ObserverResult {
617///    Ok(())
618/// }
619/// ```
620///
621/// ```no_compile
622/// #[on_game_event]
623/// fn event(&mut self, ge: &GameEvent) -> ObserverResult {
624///    Ok(())
625/// }
626#[proc_macro_attribute]
627pub fn on_game_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
628    item
629}
630
631/// A method wrapped with `#[on_string_table]` macro is called when string table is updated.
632///
633/// # Examples
634///
635/// ```no_compile
636/// #[on_string_table] // Will be called when any string table is updated
637/// fn string_table(&mut self, ctx: &Context, table: &StringTable, modified: &[i32]) -> ObserverResult {
638///    Ok(())
639/// }
640/// ```
641///
642/// ```no_compile
643/// #[on_string_table("EntityNames")] // Will be called when "EntityNames" table is updated
644/// fn string_table(&mut self, ctx: &Context, table: &StringTable, modified: &[i32]) -> ObserverResult {
645///    Ok(())
646/// }
647/// ```
648///
649/// ```no_compile
650/// #[on_string_table]
651/// fn string_table(&mut self, table: &StringTable, modified: &[i32]) -> ObserverResult {
652///    Ok(())
653/// }
654/// ```
655#[proc_macro_attribute]
656pub fn on_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
657    item
658}
659
660/// A method wrapped with `#[on_stop]` macro is called when CDemoStop appears in replay.
661///
662/// # Examples
663///
664/// ```no_compile
665/// #[on_stop]
666/// fn stop(&mut self, ctx: &Context) -> ObserverResult {
667///    Ok(())
668/// }
669/// ```
670///
671/// ```no_compile
672/// #[on_stop]
673/// fn stop(&mut self) -> ObserverResult {
674///    Ok(())
675/// }
676/// ```
677#[proc_macro_attribute]
678pub fn on_stop(_attr: TokenStream, item: TokenStream) -> TokenStream {
679    item
680}
681
682/// A method wrapped with `#[on_combat_log]` macro is called whenever CMsgDotaCombatLogEntry appears in replay.
683///
684/// # Examples
685///
686/// ```no_compile
687/// #[on_combat_log]
688/// fn combat_log(&mut self, ctx: &Context, cle: &CombatLogEntry) -> ObserverResult {
689///    Ok(())
690/// }
691///
692/// ```no_compile
693/// #[on_combat_log]
694/// fn combat_log(&mut self, cle: &CombatLogEntry) -> ObserverResult {
695///    Ok(())
696/// }
697#[cfg(feature = "dota")]
698#[proc_macro_attribute]
699pub fn on_combat_log(_attr: TokenStream, item: TokenStream) -> TokenStream {
700    item
701}
702
703#[proc_macro_attribute]
704pub fn uses_entities(_attr: TokenStream, item: TokenStream) -> TokenStream { item }
705
706#[proc_macro_attribute]
707pub fn uses_string_tables(_attr: TokenStream, item: TokenStream) -> TokenStream { item }
708
709#[proc_macro_attribute]
710pub fn uses_game_events(_attr: TokenStream, item: TokenStream) -> TokenStream { item }
711
712#[cfg(feature = "dota")]
713#[proc_macro_attribute]
714pub fn uses_combat_log(_attr: TokenStream, item: TokenStream) -> TokenStream { item }