1use crate::*;
2use ::ndarray;
3use boxcars;
4pub use derive_new;
5pub use paste;
6use serde::Serialize;
7use std::sync::Arc;
8
9#[derive(Debug, Clone, PartialEq, Serialize)]
20pub struct NDArrayColumnHeaders {
21 pub global_headers: Vec<String>,
22 pub player_headers: Vec<String>,
23}
24
25impl NDArrayColumnHeaders {
26 pub fn new(global_headers: Vec<String>, player_headers: Vec<String>) -> Self {
27 Self {
28 global_headers,
29 player_headers,
30 }
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize)]
43pub struct ReplayMetaWithHeaders {
44 pub replay_meta: ReplayMeta,
45 pub column_headers: NDArrayColumnHeaders,
46}
47
48impl ReplayMetaWithHeaders {
49 pub fn headers_vec(&self) -> Vec<String> {
50 self.headers_vec_from(|_, _info, index| format!("Player {index} - "))
51 }
52
53 pub fn headers_vec_from<F>(&self, player_prefix_getter: F) -> Vec<String>
54 where
55 F: Fn(&Self, &PlayerInfo, usize) -> String,
56 {
57 self.column_headers
58 .global_headers
59 .iter()
60 .cloned()
61 .chain(self.replay_meta.player_order().enumerate().flat_map(
62 move |(player_index, info)| {
63 let player_prefix = player_prefix_getter(self, info, player_index);
64 self.column_headers
65 .player_headers
66 .iter()
67 .map(move |header| format!("{player_prefix}{header}"))
68 },
69 ))
70 .collect()
71 }
72}
73
74pub struct NDArrayCollector<F> {
85 feature_adders: FeatureAdders<F>,
86 player_feature_adders: PlayerFeatureAdders<F>,
87 data: Vec<F>,
88 replay_meta: Option<ReplayMeta>,
89 frames_added: usize,
90}
91
92impl<F> NDArrayCollector<F> {
93 pub fn new(
111 feature_adders: FeatureAdders<F>,
112 player_feature_adders: PlayerFeatureAdders<F>,
113 ) -> Self {
114 Self {
115 feature_adders,
116 player_feature_adders,
117 data: Vec::new(),
118 replay_meta: None,
119 frames_added: 0,
120 }
121 }
122
123 pub fn get_column_headers(&self) -> NDArrayColumnHeaders {
131 let global_headers = self
132 .feature_adders
133 .iter()
134 .flat_map(move |fa| {
135 fa.get_column_headers()
136 .iter()
137 .map(move |column_name| column_name.to_string())
138 })
139 .collect();
140 let player_headers = self
141 .player_feature_adders
142 .iter()
143 .flat_map(move |pfa| {
144 pfa.get_column_headers()
145 .iter()
146 .map(move |base_name| base_name.to_string())
147 })
148 .collect();
149 NDArrayColumnHeaders::new(global_headers, player_headers)
150 }
151
152 pub fn get_ndarray(self) -> SubtrActorResult<ndarray::Array2<F>> {
163 self.get_meta_and_ndarray().map(|a| a.1)
164 }
165
166 pub fn get_meta_and_ndarray(
176 self,
177 ) -> SubtrActorResult<(ReplayMetaWithHeaders, ndarray::Array2<F>)> {
178 let features_per_row = self.try_get_frame_feature_count()?;
179 let expected_length = features_per_row * self.frames_added;
180 assert!(self.data.len() == expected_length);
181 let column_headers = self.get_column_headers();
182 Ok((
183 ReplayMetaWithHeaders {
184 replay_meta: self.replay_meta.ok_or(SubtrActorError::new(
185 SubtrActorErrorVariant::CouldNotBuildReplayMeta,
186 ))?,
187 column_headers,
188 },
189 ndarray::Array2::from_shape_vec((self.frames_added, features_per_row), self.data)
190 .map_err(SubtrActorErrorVariant::NDArrayShapeError)
191 .map_err(SubtrActorError::new)?,
192 ))
193 }
194
195 pub fn process_and_get_meta_and_headers(
211 &mut self,
212 replay: &boxcars::Replay,
213 ) -> SubtrActorResult<ReplayMetaWithHeaders> {
214 let mut processor = ReplayProcessor::new(replay)?;
215 processor.process_long_enough_to_get_actor_ids()?;
216 self.maybe_set_replay_meta(&processor)?;
217 Ok(ReplayMetaWithHeaders {
218 replay_meta: self
219 .replay_meta
220 .as_ref()
221 .ok_or(SubtrActorError::new(
222 SubtrActorErrorVariant::CouldNotBuildReplayMeta,
223 ))?
224 .clone(),
225 column_headers: self.get_column_headers(),
226 })
227 }
228
229 fn try_get_frame_feature_count(&self) -> SubtrActorResult<usize> {
230 let player_count = self
231 .replay_meta
232 .as_ref()
233 .ok_or(SubtrActorError::new(
234 SubtrActorErrorVariant::CouldNotBuildReplayMeta,
235 ))?
236 .player_count();
237 let global_feature_count: usize = self
238 .feature_adders
239 .iter()
240 .map(|fa| fa.features_added())
241 .sum();
242 let player_feature_count: usize = self
243 .player_feature_adders
244 .iter() .map(|pfa| pfa.features_added() * player_count)
246 .sum();
247 Ok(global_feature_count + player_feature_count)
248 }
249
250 fn maybe_set_replay_meta(&mut self, processor: &ReplayProcessor) -> SubtrActorResult<()> {
251 if self.replay_meta.is_none() {
252 self.replay_meta = Some(processor.get_replay_meta()?);
253 }
254 Ok(())
255 }
256}
257
258impl<F> Collector for NDArrayCollector<F> {
259 fn process_frame(
260 &mut self,
261 processor: &ReplayProcessor,
262 frame: &boxcars::Frame,
263 frame_number: usize,
264 current_time: f32,
265 ) -> SubtrActorResult<collector::TimeAdvance> {
266 self.maybe_set_replay_meta(processor)?;
267
268 for feature_adder in self.feature_adders.iter() {
269 feature_adder.add_features(
270 processor,
271 frame,
272 frame_number,
273 current_time,
274 &mut self.data,
275 )?;
276 }
277
278 for player_id in processor.iter_player_ids_in_order() {
279 for player_feature_adder in self.player_feature_adders.iter() {
280 player_feature_adder.add_features(
281 player_id,
282 processor,
283 frame,
284 frame_number,
285 current_time,
286 &mut self.data,
287 )?;
288 }
289 }
290
291 self.frames_added += 1;
292
293 Ok(collector::TimeAdvance::NextFrame)
294 }
295}
296
297fn global_feature_adder_from_name<F>(
298 name: &str,
299) -> Option<Arc<dyn FeatureAdder<F> + Send + Sync + 'static>>
300where
301 F: TryFrom<f32> + Send + Sync + 'static,
302 <F as TryFrom<f32>>::Error: std::fmt::Debug,
303{
304 match name {
305 "BallRigidBody" => Some(BallRigidBody::<F>::arc_new()),
306 "BallRigidBodyNoVelocities" => Some(BallRigidBodyNoVelocities::<F>::arc_new()),
307 "BallRigidBodyQuaternions" => Some(BallRigidBodyQuaternions::<F>::arc_new()),
308 "VelocityAddedBallRigidBodyNoVelocities" => {
309 Some(VelocityAddedBallRigidBodyNoVelocities::<F>::arc_new())
310 }
311 "InterpolatedBallRigidBodyNoVelocities" => {
312 Some(InterpolatedBallRigidBodyNoVelocities::<F>::arc_new(0.0))
313 }
314 "SecondsRemaining" => Some(SecondsRemaining::<F>::arc_new()),
315 "CurrentTime" => Some(CurrentTime::<F>::arc_new()),
316 "FrameTime" => Some(FrameTime::<F>::arc_new()),
317 "ReplicatedStateName" => Some(ReplicatedStateName::<F>::arc_new()),
318 "ReplicatedGameStateTimeRemaining" => {
319 Some(ReplicatedGameStateTimeRemaining::<F>::arc_new())
320 }
321 "BallHasBeenHit" => Some(BallHasBeenHit::<F>::arc_new()),
322 _ => None,
323 }
324}
325
326fn player_feature_adder_from_name<F>(
327 name: &str,
328) -> Option<Arc<dyn PlayerFeatureAdder<F> + Send + Sync + 'static>>
329where
330 F: TryFrom<f32> + Send + Sync + 'static,
331 <F as TryFrom<f32>>::Error: std::fmt::Debug,
332{
333 match name {
334 "PlayerRigidBody" => Some(PlayerRigidBody::<F>::arc_new()),
335 "PlayerRigidBodyNoVelocities" => Some(PlayerRigidBodyNoVelocities::<F>::arc_new()),
336 "PlayerRigidBodyQuaternions" => Some(PlayerRigidBodyQuaternions::<F>::arc_new()),
337 "VelocityAddedPlayerRigidBodyNoVelocities" => {
338 Some(VelocityAddedPlayerRigidBodyNoVelocities::<F>::arc_new())
339 }
340 "InterpolatedPlayerRigidBodyNoVelocities" => {
341 Some(InterpolatedPlayerRigidBodyNoVelocities::<F>::arc_new(0.003))
342 }
343 "PlayerBoost" => Some(PlayerBoost::<F>::arc_new()),
344 "PlayerJump" => Some(PlayerJump::<F>::arc_new()),
345 "PlayerAnyJump" => Some(PlayerAnyJump::<F>::arc_new()),
346 "PlayerDemolishedBy" => Some(PlayerDemolishedBy::<F>::arc_new()),
347 _ => None,
348 }
349}
350
351impl<F> NDArrayCollector<F>
352where
353 F: TryFrom<f32> + Send + Sync + 'static,
354 <F as TryFrom<f32>>::Error: std::fmt::Debug,
355{
356 pub fn from_strings_typed(fa_names: &[&str], pfa_names: &[&str]) -> SubtrActorResult<Self> {
359 let feature_adders: Vec<Arc<dyn FeatureAdder<F> + Send + Sync>> = fa_names
360 .iter()
361 .map(|name| {
362 global_feature_adder_from_name(name).ok_or_else(|| {
363 SubtrActorError::new(SubtrActorErrorVariant::UnknownFeatureAdderName(
364 name.to_string(),
365 ))
366 })
367 })
368 .collect::<SubtrActorResult<Vec<_>>>()?;
369 let player_feature_adders: Vec<Arc<dyn PlayerFeatureAdder<F> + Send + Sync>> = pfa_names
370 .iter()
371 .map(|name| {
372 player_feature_adder_from_name(name).ok_or_else(|| {
373 SubtrActorError::new(SubtrActorErrorVariant::UnknownFeatureAdderName(
374 name.to_string(),
375 ))
376 })
377 })
378 .collect::<SubtrActorResult<Vec<_>>>()?;
379 Ok(Self::new(feature_adders, player_feature_adders))
380 }
381}
382
383impl NDArrayCollector<f32> {
384 pub fn from_strings(fa_names: &[&str], pfa_names: &[&str]) -> SubtrActorResult<Self> {
387 Self::from_strings_typed(fa_names, pfa_names)
388 }
389}
390
391impl<F: TryFrom<f32> + Send + Sync + 'static> Default for NDArrayCollector<F>
392where
393 <F as TryFrom<f32>>::Error: std::fmt::Debug,
394{
395 fn default() -> Self {
396 NDArrayCollector::new(
397 vec![BallRigidBody::arc_new()],
398 vec![
399 PlayerRigidBody::arc_new(),
400 PlayerBoost::arc_new(),
401 PlayerAnyJump::arc_new(),
402 ],
403 )
404 }
405}
406
407pub trait FeatureAdder<F> {
414 fn features_added(&self) -> usize {
415 self.get_column_headers().len()
416 }
417
418 fn get_column_headers(&self) -> &[&str];
419
420 fn add_features(
421 &self,
422 processor: &ReplayProcessor,
423 frame: &boxcars::Frame,
424 frame_count: usize,
425 current_time: f32,
426 vector: &mut Vec<F>,
427 ) -> SubtrActorResult<()>;
428}
429
430pub type FeatureAdders<F> = Vec<Arc<dyn FeatureAdder<F> + Send + Sync>>;
431
432pub trait LengthCheckedFeatureAdder<F, const N: usize> {
438 fn get_column_headers_array(&self) -> &[&str; N];
439
440 fn get_features(
441 &self,
442 processor: &ReplayProcessor,
443 frame: &boxcars::Frame,
444 frame_count: usize,
445 current_time: f32,
446 ) -> SubtrActorResult<[F; N]>;
447}
448
449#[macro_export]
458macro_rules! impl_feature_adder {
459 ($struct_name:ident) => {
460 impl<F: TryFrom<f32>> FeatureAdder<F> for $struct_name<F>
461 where
462 <F as TryFrom<f32>>::Error: std::fmt::Debug,
463 {
464 fn add_features(
465 &self,
466 processor: &ReplayProcessor,
467 frame: &boxcars::Frame,
468 frame_count: usize,
469 current_time: f32,
470 vector: &mut Vec<F>,
471 ) -> SubtrActorResult<()> {
472 Ok(
473 vector.extend(self.get_features(
474 processor,
475 frame,
476 frame_count,
477 current_time,
478 )?),
479 )
480 }
481
482 fn get_column_headers(&self) -> &[&str] {
483 self.get_column_headers_array()
484 }
485 }
486 };
487}
488
489pub trait PlayerFeatureAdder<F> {
497 fn features_added(&self) -> usize {
498 self.get_column_headers().len()
499 }
500
501 fn get_column_headers(&self) -> &[&str];
502
503 fn add_features(
504 &self,
505 player_id: &PlayerId,
506 processor: &ReplayProcessor,
507 frame: &boxcars::Frame,
508 frame_count: usize,
509 current_time: f32,
510 vector: &mut Vec<F>,
511 ) -> SubtrActorResult<()>;
512}
513
514pub type PlayerFeatureAdders<F> = Vec<Arc<dyn PlayerFeatureAdder<F> + Send + Sync>>;
515
516pub trait LengthCheckedPlayerFeatureAdder<F, const N: usize> {
522 fn get_column_headers_array(&self) -> &[&str; N];
523
524 fn get_features(
525 &self,
526 player_id: &PlayerId,
527 processor: &ReplayProcessor,
528 frame: &boxcars::Frame,
529 frame_count: usize,
530 current_time: f32,
531 ) -> SubtrActorResult<[F; N]>;
532}
533
534#[macro_export]
544macro_rules! impl_player_feature_adder {
545 ($struct_name:ident) => {
546 impl<F: TryFrom<f32>> PlayerFeatureAdder<F> for $struct_name<F>
547 where
548 <F as TryFrom<f32>>::Error: std::fmt::Debug,
549 {
550 fn add_features(
551 &self,
552 player_id: &PlayerId,
553 processor: &ReplayProcessor,
554 frame: &boxcars::Frame,
555 frame_count: usize,
556 current_time: f32,
557 vector: &mut Vec<F>,
558 ) -> SubtrActorResult<()> {
559 Ok(vector.extend(self.get_features(
560 player_id,
561 processor,
562 frame,
563 frame_count,
564 current_time,
565 )?))
566 }
567
568 fn get_column_headers(&self) -> &[&str] {
569 self.get_column_headers_array()
570 }
571 }
572 };
573}
574
575impl<G, F, const N: usize> FeatureAdder<F> for (G, &[&str; N])
576where
577 G: Fn(&ReplayProcessor, &boxcars::Frame, usize, f32) -> SubtrActorResult<[F; N]>,
578{
579 fn add_features(
580 &self,
581 processor: &ReplayProcessor,
582 frame: &boxcars::Frame,
583 frame_count: usize,
584 current_time: f32,
585 vector: &mut Vec<F>,
586 ) -> SubtrActorResult<()> {
587 vector.extend(self.0(processor, frame, frame_count, current_time)?);
588 Ok(())
589 }
590
591 fn get_column_headers(&self) -> &[&str] {
592 self.1.as_slice()
593 }
594}
595
596impl<G, F, const N: usize> PlayerFeatureAdder<F> for (G, &[&str; N])
597where
598 G: Fn(&PlayerId, &ReplayProcessor, &boxcars::Frame, usize, f32) -> SubtrActorResult<[F; N]>,
599{
600 fn add_features(
601 &self,
602 player_id: &PlayerId,
603 processor: &ReplayProcessor,
604 frame: &boxcars::Frame,
605 frame_count: usize,
606 current_time: f32,
607 vector: &mut Vec<F>,
608 ) -> SubtrActorResult<()> {
609 vector.extend(self.0(
610 player_id,
611 processor,
612 frame,
613 frame_count,
614 current_time,
615 )?);
616 Ok(())
617 }
618
619 fn get_column_headers(&self) -> &[&str] {
620 self.1.as_slice()
621 }
622}
623
624#[macro_export]
659macro_rules! build_global_feature_adder {
660 ($struct_name:ident, $prop_getter:expr, $( $column_names:expr ),* $(,)?) => {
661
662 #[derive(derive_new::new)]
663 pub struct $struct_name<F> {
664 _zero: std::marker::PhantomData<F>,
665 }
666
667 impl<F: Sync + Send + TryFrom<f32> + 'static> $struct_name<F> where
668 <F as TryFrom<f32>>::Error: std::fmt::Debug,
669 {
670 pub fn arc_new() -> std::sync::Arc<dyn FeatureAdder<F> + Send + Sync + 'static> {
671 std::sync::Arc::new(Self::new())
672 }
673 }
674
675 global_feature_adder!(
676 $struct_name,
677 $prop_getter,
678 $( $column_names ),*
679 );
680 }
681}
682
683#[macro_export]
695macro_rules! global_feature_adder {
696 ($struct_name:ident, $prop_getter:expr, $( $column_names:expr ),* $(,)?) => {
697 macro_rules! _global_feature_adder {
698 ($count:ident) => {
699 impl<F: TryFrom<f32>> LengthCheckedFeatureAdder<F, $count> for $struct_name<F>
700 where
701 <F as TryFrom<f32>>::Error: std::fmt::Debug,
702 {
703 fn get_column_headers_array(&self) -> &[&str; $count] {
704 &[$( $column_names ),*]
705 }
706
707 fn get_features(
708 &self,
709 processor: &ReplayProcessor,
710 frame: &boxcars::Frame,
711 frame_count: usize,
712 current_time: f32,
713 ) -> SubtrActorResult<[F; $count]> {
714 $prop_getter(self, processor, frame, frame_count, current_time)
715 }
716 }
717
718 impl_feature_adder!($struct_name);
719 };
720 }
721 paste::paste! {
722 const [<$struct_name:snake:upper _LENGTH>]: usize = [$($column_names),*].len();
723 _global_feature_adder!([<$struct_name:snake:upper _LENGTH>]);
724 }
725 }
726}
727
728#[macro_export]
787macro_rules! build_player_feature_adder {
788 ($struct_name:ident, $prop_getter:expr, $( $column_names:expr ),* $(,)?) => {
789 #[derive(derive_new::new)]
790 pub struct $struct_name<F> {
791 _zero: std::marker::PhantomData<F>,
792 }
793
794 impl<F: Sync + Send + TryFrom<f32> + 'static> $struct_name<F> where
795 <F as TryFrom<f32>>::Error: std::fmt::Debug,
796 {
797 pub fn arc_new() -> std::sync::Arc<dyn PlayerFeatureAdder<F> + Send + Sync + 'static> {
798 std::sync::Arc::new(Self::new())
799 }
800 }
801
802 player_feature_adder!(
803 $struct_name,
804 $prop_getter,
805 $( $column_names ),*
806 );
807 }
808}
809
810#[macro_export]
823macro_rules! player_feature_adder {
824 ($struct_name:ident, $prop_getter:expr, $( $column_names:expr ),* $(,)?) => {
825 macro_rules! _player_feature_adder {
826 ($count:ident) => {
827 impl<F: TryFrom<f32>> LengthCheckedPlayerFeatureAdder<F, $count> for $struct_name<F>
828 where
829 <F as TryFrom<f32>>::Error: std::fmt::Debug,
830 {
831 fn get_column_headers_array(&self) -> &[&str; $count] {
832 &[$( $column_names ),*]
833 }
834
835 fn get_features(
836 &self,
837 player_id: &PlayerId,
838 processor: &ReplayProcessor,
839 frame: &boxcars::Frame,
840 frame_count: usize,
841 current_time: f32,
842 ) -> SubtrActorResult<[F; $count]> {
843 $prop_getter(self, player_id, processor, frame, frame_count, current_time)
844 }
845 }
846
847 impl_player_feature_adder!($struct_name);
848 };
849 }
850 paste::paste! {
851 const [<$struct_name:snake:upper _LENGTH>]: usize = [$($column_names),*].len();
852 _player_feature_adder!([<$struct_name:snake:upper _LENGTH>]);
853 }
854 }
855}
856
857pub fn convert_float_conversion_error<T>(_: T) -> SubtrActorError {
860 SubtrActorError::new(SubtrActorErrorVariant::FloatConversionError)
861}
862
863#[macro_export]
874macro_rules! convert_all {
875 ($err:expr, $( $item:expr ),* $(,)?) => {{
876 Ok([
877 $( $item.try_into().map_err($err)? ),*
878 ])
879 }};
880}
881
882#[macro_export]
904macro_rules! convert_all_floats {
905 ($( $item:expr ),* $(,)?) => {{
906 convert_all!(convert_float_conversion_error, $( $item ),*)
907 }};
908}
909
910fn or_zero_boxcars_3f() -> boxcars::Vector3f {
911 boxcars::Vector3f {
912 x: 0.0,
913 y: 0.0,
914 z: 0.0,
915 }
916}
917
918type RigidBodyArrayResult<F> = SubtrActorResult<[F; 12]>;
919
920pub fn get_rigid_body_properties<F: TryFrom<f32>>(
930 rigid_body: &boxcars::RigidBody,
931) -> RigidBodyArrayResult<F>
932where
933 <F as TryFrom<f32>>::Error: std::fmt::Debug,
934{
935 let linear_velocity = rigid_body
936 .linear_velocity
937 .unwrap_or_else(or_zero_boxcars_3f);
938 let angular_velocity = rigid_body
939 .angular_velocity
940 .unwrap_or_else(or_zero_boxcars_3f);
941 let rotation = rigid_body.rotation;
942 let location = rigid_body.location;
943 let (rx, ry, rz) =
944 glam::quat(rotation.x, rotation.y, rotation.z, rotation.w).to_euler(glam::EulerRot::XYZ);
945 convert_all_floats!(
946 location.x,
947 location.y,
948 location.z,
949 rx,
950 ry,
951 rz,
952 linear_velocity.x,
953 linear_velocity.y,
954 linear_velocity.z,
955 angular_velocity.x,
956 angular_velocity.y,
957 angular_velocity.z,
958 )
959}
960
961pub fn get_rigid_body_properties_no_velocities<F: TryFrom<f32>>(
970 rigid_body: &boxcars::RigidBody,
971) -> SubtrActorResult<[F; 7]>
972where
973 <F as TryFrom<f32>>::Error: std::fmt::Debug,
974{
975 let rotation = rigid_body.rotation;
976 let location = rigid_body.location;
977 convert_all_floats!(
978 location.x, location.y, location.z, rotation.x, rotation.y, rotation.z, rotation.w
979 )
980}
981
982fn default_rb_state<F: TryFrom<f32>>() -> RigidBodyArrayResult<F>
983where
984 <F as TryFrom<f32>>::Error: std::fmt::Debug,
985{
986 convert_all!(
987 convert_float_conversion_error,
988 0.0,
992 0.0,
993 0.0,
994 0.0,
995 0.0,
996 0.0,
997 0.0,
998 0.0,
999 0.0,
1000 0.0,
1001 0.0,
1002 0.0,
1003 )
1004}
1005
1006fn default_rb_state_no_velocities<F: TryFrom<f32>>() -> SubtrActorResult<[F; 7]>
1007where
1008 <F as TryFrom<f32>>::Error: std::fmt::Debug,
1009{
1010 convert_all_floats!(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,)
1011}
1012
1013build_global_feature_adder!(
1014 SecondsRemaining,
1015 |_, processor: &ReplayProcessor, _frame, _index, _current_time| {
1016 convert_all_floats!(processor.get_seconds_remaining().unwrap_or(0) as f32)
1017 },
1018 "seconds remaining"
1019);
1020
1021build_global_feature_adder!(
1022 CurrentTime,
1023 |_, _processor, _frame, _index, current_time: f32| { convert_all_floats!(current_time) },
1024 "current time"
1025);
1026
1027build_global_feature_adder!(
1029 ReplicatedStateName,
1030 |_, processor: &ReplayProcessor, _frame, _index, _current_time| {
1031 convert_all_floats!(processor.get_replicated_state_name().unwrap_or(0) as f32)
1032 },
1033 "game state"
1034);
1035
1036build_global_feature_adder!(
1038 ReplicatedGameStateTimeRemaining,
1039 |_, processor: &ReplayProcessor, _frame, _index, _current_time| {
1040 convert_all_floats!(processor
1041 .get_replicated_game_state_time_remaining()
1042 .unwrap_or(0) as f32)
1043 },
1044 "kickoff countdown"
1045);
1046
1047build_global_feature_adder!(
1049 BallHasBeenHit,
1050 |_, processor: &ReplayProcessor, _frame, _index, _current_time| {
1051 convert_all_floats!(if processor.get_ball_has_been_hit().unwrap_or(false) {
1052 1.0
1053 } else {
1054 0.0
1055 })
1056 },
1057 "ball has been hit"
1058);
1059
1060build_global_feature_adder!(
1061 FrameTime,
1062 |_, _processor, frame: &boxcars::Frame, _index, _current_time| {
1063 convert_all_floats!(frame.time)
1064 },
1065 "frame time"
1066);
1067
1068build_global_feature_adder!(
1069 BallRigidBody,
1070 |_, processor: &ReplayProcessor, _frame, _index, _current_time| {
1071 processor
1072 .get_ball_rigid_body()
1073 .and_then(|rb| get_rigid_body_properties(rb))
1074 .or_else(|_| default_rb_state())
1075 },
1076 "Ball - position x",
1077 "Ball - position y",
1078 "Ball - position z",
1079 "Ball - rotation x",
1080 "Ball - rotation y",
1081 "Ball - rotation z",
1082 "Ball - linear velocity x",
1083 "Ball - linear velocity y",
1084 "Ball - linear velocity z",
1085 "Ball - angular velocity x",
1086 "Ball - angular velocity y",
1087 "Ball - angular velocity z",
1088);
1089
1090build_global_feature_adder!(
1091 BallRigidBodyNoVelocities,
1092 |_, processor: &ReplayProcessor, _frame, _index, _current_time| {
1093 processor
1094 .get_ball_rigid_body()
1095 .and_then(|rb| get_rigid_body_properties_no_velocities(rb))
1096 .or_else(|_| default_rb_state_no_velocities())
1097 },
1098 "Ball - position x",
1099 "Ball - position y",
1100 "Ball - position z",
1101 "Ball - rotation x",
1102 "Ball - rotation y",
1103 "Ball - rotation z",
1104 "Ball - rotation w",
1105);
1106
1107build_global_feature_adder!(
1110 VelocityAddedBallRigidBodyNoVelocities,
1111 |_, processor: &ReplayProcessor, _frame, _index, current_time: f32| {
1112 processor
1113 .get_velocity_applied_ball_rigid_body(current_time)
1114 .and_then(|rb| get_rigid_body_properties_no_velocities(&rb))
1115 .or_else(|_| default_rb_state_no_velocities())
1116 },
1117 "Ball - position x",
1118 "Ball - position y",
1119 "Ball - position z",
1120 "Ball - rotation x",
1121 "Ball - rotation y",
1122 "Ball - rotation z",
1123 "Ball - rotation w",
1124);
1125
1126#[derive(derive_new::new)]
1127pub struct InterpolatedBallRigidBodyNoVelocities<F> {
1128 close_enough_to_frame_time: f32,
1129 _zero: std::marker::PhantomData<F>,
1130}
1131
1132impl<F> InterpolatedBallRigidBodyNoVelocities<F> {
1133 pub fn arc_new(close_enough_to_frame_time: f32) -> Arc<Self> {
1134 Arc::new(Self::new(close_enough_to_frame_time))
1135 }
1136}
1137
1138global_feature_adder!(
1139 InterpolatedBallRigidBodyNoVelocities,
1140 |s: &InterpolatedBallRigidBodyNoVelocities<F>,
1141 processor: &ReplayProcessor,
1142 _frame: &boxcars::Frame,
1143 _index,
1144 current_time: f32| {
1145 processor
1146 .get_interpolated_ball_rigid_body(current_time, s.close_enough_to_frame_time)
1147 .map(|v| get_rigid_body_properties_no_velocities(&v))
1148 .unwrap_or_else(|_| default_rb_state_no_velocities())
1149 },
1150 "Ball - position x",
1151 "Ball - position y",
1152 "Ball - position z",
1153 "Ball - rotation x",
1154 "Ball - rotation y",
1155 "Ball - rotation z",
1156 "Ball - rotation w",
1157);
1158
1159build_player_feature_adder!(
1160 PlayerRigidBody,
1161 |_, player_id: &PlayerId, processor: &ReplayProcessor, _frame, _index, _current_time: f32| {
1162 if let Ok(rb) = processor.get_player_rigid_body(player_id) {
1163 get_rigid_body_properties(rb)
1164 } else {
1165 default_rb_state()
1166 }
1167 },
1168 "position x",
1169 "position y",
1170 "position z",
1171 "rotation x",
1172 "rotation y",
1173 "rotation z",
1174 "linear velocity x",
1175 "linear velocity y",
1176 "linear velocity z",
1177 "angular velocity x",
1178 "angular velocity y",
1179 "angular velocity z",
1180);
1181
1182build_player_feature_adder!(
1183 PlayerRigidBodyNoVelocities,
1184 |_, player_id: &PlayerId, processor: &ReplayProcessor, _frame, _index, _current_time: f32| {
1185 if let Ok(rb) = processor.get_player_rigid_body(player_id) {
1186 get_rigid_body_properties_no_velocities(rb)
1187 } else {
1188 default_rb_state_no_velocities()
1189 }
1190 },
1191 "position x",
1192 "position y",
1193 "position z",
1194 "rotation x",
1195 "rotation y",
1196 "rotation z",
1197 "rotation w"
1198);
1199
1200build_player_feature_adder!(
1203 VelocityAddedPlayerRigidBodyNoVelocities,
1204 |_, player_id: &PlayerId, processor: &ReplayProcessor, _frame, _index, current_time: f32| {
1205 if let Ok(rb) = processor.get_velocity_applied_player_rigid_body(player_id, current_time) {
1206 get_rigid_body_properties_no_velocities(&rb)
1207 } else {
1208 default_rb_state_no_velocities()
1209 }
1210 },
1211 "position x",
1212 "position y",
1213 "position z",
1214 "rotation x",
1215 "rotation y",
1216 "rotation z",
1217 "rotation w"
1218);
1219
1220#[derive(derive_new::new)]
1221pub struct InterpolatedPlayerRigidBodyNoVelocities<F> {
1222 close_enough_to_frame_time: f32,
1223 _zero: std::marker::PhantomData<F>,
1224}
1225
1226impl<F> InterpolatedPlayerRigidBodyNoVelocities<F> {
1227 pub fn arc_new(close_enough_to_frame_time: f32) -> Arc<Self> {
1228 Arc::new(Self::new(close_enough_to_frame_time))
1229 }
1230}
1231
1232player_feature_adder!(
1233 InterpolatedPlayerRigidBodyNoVelocities,
1234 |s: &InterpolatedPlayerRigidBodyNoVelocities<F>,
1235 player_id: &PlayerId,
1236 processor: &ReplayProcessor,
1237 _frame: &boxcars::Frame,
1238 _index,
1239 current_time: f32| {
1240 processor
1241 .get_interpolated_player_rigid_body(
1242 player_id,
1243 current_time,
1244 s.close_enough_to_frame_time,
1245 )
1246 .map(|v| get_rigid_body_properties_no_velocities(&v))
1247 .unwrap_or_else(|_| default_rb_state_no_velocities())
1248 },
1249 "i position x",
1250 "i position y",
1251 "i position z",
1252 "i rotation x",
1253 "i rotation y",
1254 "i rotation z",
1255 "i rotation w"
1256);
1257
1258build_player_feature_adder!(
1259 PlayerBoost,
1260 |_, player_id: &PlayerId, processor: &ReplayProcessor, _frame, _index, _current_time: f32| {
1261 convert_all_floats!(processor.get_player_boost_level(player_id).unwrap_or(0.0))
1262 },
1263 "boost level (raw replay units)"
1264);
1265
1266fn u8_get_f32(v: u8) -> SubtrActorResult<f32> {
1267 Ok(v.into())
1268}
1269
1270build_player_feature_adder!(
1271 PlayerJump,
1272 |_,
1273 player_id: &PlayerId,
1274 processor: &ReplayProcessor,
1275 _frame,
1276 _frame_number,
1277 _current_time: f32| {
1278 convert_all_floats!(
1279 processor
1280 .get_dodge_active(player_id)
1281 .and_then(u8_get_f32)
1282 .unwrap_or(0.0),
1283 processor
1284 .get_jump_active(player_id)
1285 .and_then(u8_get_f32)
1286 .unwrap_or(0.0),
1287 processor
1288 .get_double_jump_active(player_id)
1289 .and_then(u8_get_f32)
1290 .unwrap_or(0.0),
1291 )
1292 },
1293 "dodge active",
1294 "jump active",
1295 "double jump active"
1296);
1297
1298build_player_feature_adder!(
1299 PlayerAnyJump,
1300 |_,
1301 player_id: &PlayerId,
1302 processor: &ReplayProcessor,
1303 _frame,
1304 _frame_number,
1305 _current_time: f32| {
1306 let dodge_is_active = processor.get_dodge_active(player_id).unwrap_or(0) % 2;
1307 let jump_is_active = processor.get_jump_active(player_id).unwrap_or(0) % 2;
1308 let double_jump_is_active = processor.get_double_jump_active(player_id).unwrap_or(0) % 2;
1309 let value: f32 = [dodge_is_active, jump_is_active, double_jump_is_active]
1310 .into_iter()
1311 .enumerate()
1312 .map(|(index, is_active)| (1 << index) * is_active)
1313 .sum::<u8>() as f32;
1314 convert_all_floats!(value)
1315 },
1316 "any_jump_active"
1317);
1318
1319const DEMOLISH_APPEARANCE_FRAME_COUNT: usize = 30;
1320
1321build_player_feature_adder!(
1322 PlayerDemolishedBy,
1323 |_,
1324 player_id: &PlayerId,
1325 processor: &ReplayProcessor,
1326 _frame,
1327 frame_number,
1328 _current_time: f32| {
1329 let demolisher_index = processor
1330 .demolishes
1331 .iter()
1332 .find(|demolish_info| {
1333 &demolish_info.victim == player_id
1334 && frame_number - demolish_info.frame < DEMOLISH_APPEARANCE_FRAME_COUNT
1335 })
1336 .map(|demolish_info| {
1337 processor
1338 .iter_player_ids_in_order()
1339 .position(|player_id| player_id == &demolish_info.attacker)
1340 .unwrap_or_else(|| processor.iter_player_ids_in_order().count())
1341 })
1342 .and_then(|v| i32::try_from(v).ok())
1343 .unwrap_or(-1);
1344 convert_all_floats!(demolisher_index as f32)
1345 },
1346 "player demolished by"
1347);
1348
1349build_player_feature_adder!(
1350 PlayerRigidBodyQuaternions,
1351 |_, player_id: &PlayerId, processor: &ReplayProcessor, _frame, _index, _current_time: f32| {
1352 if let Ok(rb) = processor.get_player_rigid_body(player_id) {
1353 let rotation = rb.rotation;
1354 let location = rb.location;
1355 convert_all_floats!(
1356 location.x, location.y, location.z, rotation.x, rotation.y, rotation.z, rotation.w
1357 )
1358 } else {
1359 convert_all_floats!(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)
1360 }
1361 },
1362 "position x",
1363 "position y",
1364 "position z",
1365 "quaternion x",
1366 "quaternion y",
1367 "quaternion z",
1368 "quaternion w"
1369);
1370
1371build_global_feature_adder!(
1372 BallRigidBodyQuaternions,
1373 |_, processor: &ReplayProcessor, _frame, _index, _current_time| {
1374 match processor.get_ball_rigid_body() {
1375 Ok(rb) => {
1376 let rotation = rb.rotation;
1377 let location = rb.location;
1378 convert_all_floats!(
1379 location.x, location.y, location.z, rotation.x, rotation.y, rotation.z,
1380 rotation.w
1381 )
1382 }
1383 Err(_) => convert_all_floats!(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0),
1384 }
1385 },
1386 "Ball - position x",
1387 "Ball - position y",
1388 "Ball - position z",
1389 "Ball - quaternion x",
1390 "Ball - quaternion y",
1391 "Ball - quaternion z",
1392 "Ball - quaternion w"
1393);