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