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#[proc_macro_attribute]
526pub fn on_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
527 item
528}
529
530#[proc_macro_attribute]
548pub fn on_tick_start(_attr: TokenStream, item: TokenStream) -> TokenStream {
549 item
550}
551
552#[proc_macro_attribute]
570pub fn on_tick_end(_attr: TokenStream, item: TokenStream) -> TokenStream {
571 item
572}
573
574#[proc_macro_attribute]
599pub fn on_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
600 item
601}
602
603#[proc_macro_attribute]
627pub fn on_game_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
628 item
629}
630
631#[proc_macro_attribute]
656pub fn on_string_table(_attr: TokenStream, item: TokenStream) -> TokenStream {
657 item
658}
659
660#[proc_macro_attribute]
678pub fn on_stop(_attr: TokenStream, item: TokenStream) -> TokenStream {
679 item
680}
681
682#[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 }