1use crate::*;
2use boxcars;
3use std::collections::HashMap;
4
5
6macro_rules! attribute_match {
18 ($value:expr, $type:path $(,)?) => {{
19 let attribute = $value;
20 if let $type(value) = attribute {
21 Ok(value)
22 } else {
23 SubtrActorError::new_result(SubtrActorErrorVariant::UnexpectedAttributeType {
24 expected_type: stringify!(path).to_string(),
25 actual_type: attribute_to_tag(&attribute).to_string(),
26 })
27 }
28 }};
29}
30
31macro_rules! get_attribute_errors_expected {
40 ($self:ident, $map:expr, $prop:expr, $type:path) => {
41 $self
42 .get_attribute($map, $prop)
43 .and_then(|found| attribute_match!(found, $type))
44 };
45}
46
47macro_rules! get_attribute_and_updated {
60 ($self:ident, $map:expr, $prop:expr, $type:path) => {
61 $self
62 .get_attribute_and_updated($map, $prop)
63 .and_then(|(found, updated)| attribute_match!(found, $type).map(|v| (v, updated)))
64 };
65}
66
67macro_rules! get_actor_attribute_matching {
76 ($self:ident, $actor:expr, $prop:expr, $type:path) => {
77 $self
78 .get_actor_attribute($actor, $prop)
79 .and_then(|found| attribute_match!(found, $type))
80 };
81}
82
83macro_rules! get_derived_attribute {
92 ($map:expr, $key:expr, $type:path) => {
93 $map.get($key)
94 .ok_or_else(|| {
95 SubtrActorError::new(SubtrActorErrorVariant::DerivedKeyValueNotFound {
96 name: $key.to_string(),
97 })
98 })
99 .and_then(|found| attribute_match!(&found.0, $type))
100 };
101}
102
103fn get_actor_id_from_active_actor<T>(
104 _: T,
105 active_actor: &boxcars::ActiveActor,
106) -> boxcars::ActorId {
107 active_actor.actor
108}
109
110fn use_update_actor<T>(id: boxcars::ActorId, _: T) -> boxcars::ActorId {
111 id
112}
113
114pub struct ReplayProcessor<'a> {
146 pub replay: &'a boxcars::Replay,
147 pub actor_state: ActorStateModeler,
148 pub object_id_to_name: HashMap<boxcars::ObjectId, String>,
149 pub name_to_object_id: HashMap<String, boxcars::ObjectId>,
150 pub ball_actor_id: Option<boxcars::ActorId>,
151 pub team_zero: Vec<PlayerId>,
152 pub team_one: Vec<PlayerId>,
153 pub player_to_actor_id: HashMap<PlayerId, boxcars::ActorId>,
154 pub player_to_car: HashMap<boxcars::ActorId, boxcars::ActorId>,
155 pub player_to_team: HashMap<boxcars::ActorId, boxcars::ActorId>,
156 pub car_to_boost: HashMap<boxcars::ActorId, boxcars::ActorId>,
157 pub car_to_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
158 pub car_to_double_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
159 pub car_to_dodge: HashMap<boxcars::ActorId, boxcars::ActorId>,
160 pub demolishes: Vec<DemolishInfo>,
161 pub shots: Vec<ShotMetadata>,
162 known_demolishes: Vec<(boxcars::DemolishFx, usize)>,
163}
164
165impl<'a> ReplayProcessor<'a> {
166 pub fn new(replay: &'a boxcars::Replay) -> SubtrActorResult<Self> {
180 let mut object_id_to_name = HashMap::new();
181 let mut name_to_object_id = HashMap::new();
182 for (id, name) in replay.objects.iter().enumerate() {
183 let object_id = boxcars::ObjectId(id as i32);
184 object_id_to_name.insert(object_id, name.clone());
185 name_to_object_id.insert(name.clone(), object_id);
186 }
187 let mut processor = Self {
188 actor_state: ActorStateModeler::new(),
189 replay,
190 object_id_to_name,
191 name_to_object_id,
192 team_zero: Vec::new(),
193 team_one: Vec::new(),
194 ball_actor_id: None,
195 player_to_car: HashMap::new(),
196 player_to_team: HashMap::new(),
197 player_to_actor_id: HashMap::new(),
198 car_to_boost: HashMap::new(),
199 car_to_jump: HashMap::new(),
200 car_to_double_jump: HashMap::new(),
201 car_to_dodge: HashMap::new(),
202 demolishes: Vec::new(),
203 known_demolishes: Vec::new(),
204 shots: Vec::new(),
205 };
206 processor
207 .set_player_order_from_headers()
208 .or_else(|_| processor.set_player_order_from_frames())?;
209
210 Ok(processor)
211 }
212
213 pub fn process<H: Collector>(&mut self, handler: &mut H) -> SubtrActorResult<()> {
235 let mut target_time = TimeAdvance::NextFrame;
238 for (index, frame) in self
239 .replay
240 .network_frames
241 .as_ref()
242 .ok_or(SubtrActorError::new(
243 SubtrActorErrorVariant::NoNetworkFrames,
244 ))?
245 .frames
246 .iter()
247 .enumerate()
248 {
249 self.actor_state.process_frame(frame, index)?;
251 self.update_mappings(frame)?;
252 self.update_ball_id(frame)?;
253 self.update_boost_amounts(frame, index)?;
254 self.update_demolishes(frame, index)?;
255 let mut current_time = match &target_time {
260 TimeAdvance::Time(t) => *t,
261 TimeAdvance::NextFrame => frame.time,
262 };
263
264 while current_time <= frame.time {
265 target_time = handler.process_frame(&self, frame, index, current_time)?;
268 if let TimeAdvance::Time(new_target) = target_time {
274 current_time = new_target;
275 } else {
276 break;
277 }
278 }
279 }
280 Ok(())
281 }
282
283 pub fn reset(&mut self) {
285 self.player_to_car = HashMap::new();
286 self.player_to_team = HashMap::new();
287 self.player_to_actor_id = HashMap::new();
288 self.car_to_boost = HashMap::new();
289 self.car_to_jump = HashMap::new();
290 self.car_to_double_jump = HashMap::new();
291 self.car_to_dodge = HashMap::new();
292 self.actor_state = ActorStateModeler::new();
293 self.demolishes = Vec::new();
294 self.known_demolishes = Vec::new();
295 }
296
297 fn set_player_order_from_headers(&mut self) -> SubtrActorResult<()> {
298 let _player_stats = self
299 .replay
300 .properties
301 .iter()
302 .find(|(key, _)| key == "PlayerStats")
303 .ok_or_else(|| {
304 SubtrActorError::new(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
305 })?;
306 SubtrActorError::new_result(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
308 }
309
310 pub fn process_long_enough_to_get_actor_ids(&mut self) -> SubtrActorResult<()> {
327 let mut handler = |_p: &ReplayProcessor, _f: &boxcars::Frame, n: usize, _current_time| {
328 if n > 10 * 30 {
330 SubtrActorError::new_result(SubtrActorErrorVariant::FinishProcessingEarly)
331 } else {
332 Ok(TimeAdvance::NextFrame)
333 }
334 };
335 let process_result = self.process(&mut handler);
336 if let Some(SubtrActorErrorVariant::FinishProcessingEarly) =
337 process_result.as_ref().err().map(|e| e.variant.clone())
338 {
339 Ok(())
340 } else {
341 process_result
342 }
343 }
344
345 fn set_player_order_from_frames(&mut self) -> SubtrActorResult<()> {
346 self.process_long_enough_to_get_actor_ids()?;
347 let player_to_team_0: HashMap<PlayerId, bool> = self
348 .player_to_actor_id
349 .keys()
350 .filter_map(|player_id| {
351 self.get_player_is_team_0(player_id)
352 .ok()
353 .map(|is_team_0| (player_id.clone(), is_team_0))
354 })
355 .collect();
356
357 let (team_zero, team_one): (Vec<_>, Vec<_>) = player_to_team_0
358 .keys()
359 .cloned()
360 .partition(|player_id| *player_to_team_0.get(player_id).unwrap());
362
363 self.team_zero = team_zero;
364 self.team_one = team_one;
365
366 self.team_zero
367 .sort_by(|a, b| format!("{:?}", a).cmp(&format!("{:?}", b)));
368 self.team_one
369 .sort_by(|a, b| format!("{:?}", a).cmp(&format!("{:?}", b)));
370
371 self.reset();
372 Ok(())
373 }
374
375 pub fn check_player_id_set(&self) -> SubtrActorResult<()> {
376 let known_players =
377 std::collections::HashSet::<_>::from_iter(self.player_to_actor_id.keys());
378 let original_players =
379 std::collections::HashSet::<_>::from_iter(self.iter_player_ids_in_order());
380
381 if original_players != known_players {
382 return SubtrActorError::new_result(SubtrActorErrorVariant::InconsistentPlayerSet {
383 found: known_players.into_iter().cloned().collect(),
384 original: original_players.into_iter().cloned().collect(),
385 });
386 } else {
387 Ok(())
388 }
389 }
390
391 fn build_shots_info(
461 &self,
462 demolish_fx: &boxcars::DemolishFx,
463 frame: &boxcars::Frame,
464 index: usize,
465 ) -> SubtrActorResult<DemolishInfo> {
466 let attacker = self.get_player_id_from_car_id(&demolish_fx.attacker)?;
467 let victim = self.get_player_id_from_car_id(&demolish_fx.victim)?;
468 Ok(DemolishInfo {
469 time: frame.time,
470 seconds_remaining: self.get_seconds_remaining()?,
471 frame: index,
472 attacker,
473 victim,
474 attacker_velocity: demolish_fx.attack_velocity.clone(),
475 victim_velocity: demolish_fx.victim_velocity.clone(),
476 })
477 }
478
479 pub fn process_and_get_replay_meta(&mut self) -> SubtrActorResult<ReplayMeta> {
488 if self.player_to_actor_id.is_empty() {
489 self.process_long_enough_to_get_actor_ids()?;
490 }
491 self.get_replay_meta()
492 }
493
494 pub fn get_replay_meta(&self) -> SubtrActorResult<ReplayMeta> {
501 let empty_player_stats = Vec::new();
502 let player_stats = if let Some((_, boxcars::HeaderProp::Array(per_player))) = self
503 .replay
504 .properties
505 .iter()
506 .find(|(key, _)| key == "PlayerStats")
507 {
508 per_player
509 } else {
510 &empty_player_stats
511 };
512 let known_count = self.iter_player_ids_in_order().count();
513 if player_stats.len() != known_count {
514 log::warn!(
515 "Replay does not have player stats for all players. encountered {:?} {:?}",
516 known_count,
517 player_stats.len()
518 )
519 }
520 let get_player_info = |player_id| {
521 let name = self.get_player_name(player_id)?;
522 let stats = find_player_stats(player_id, &name, player_stats).ok();
523 Ok(PlayerInfo {
524 name,
525 stats,
526 remote_id: player_id.clone(),
527 })
528 };
529 let team_zero: SubtrActorResult<Vec<PlayerInfo>> =
530 self.team_zero.iter().map(get_player_info).collect();
531 let team_one: SubtrActorResult<Vec<PlayerInfo>> =
532 self.team_one.iter().map(get_player_info).collect();
533 Ok(ReplayMeta {
534 shots: self.shots.clone(),
535 team_zero: team_zero?,
536 team_one: team_one?,
537 all_headers: self.replay.properties.clone(),
538 })
539 }
540
541 pub fn find_update_in_direction(
577 &self,
578 current_index: usize,
579 actor_id: &boxcars::ActorId,
580 object_id: &boxcars::ObjectId,
581 direction: SearchDirection,
582 ) -> SubtrActorResult<(boxcars::Attribute, usize)> {
583 let frames = self
584 .replay
585 .network_frames
586 .as_ref()
587 .ok_or(SubtrActorError::new(
588 SubtrActorErrorVariant::NoNetworkFrames,
589 ))?;
590
591 let predicate = |frame: &boxcars::Frame| {
592 frame
593 .updated_actors
594 .iter()
595 .find(|update| &update.actor_id == actor_id && &update.object_id == object_id)
596 .map(|update| &update.attribute)
597 .cloned()
598 };
599
600 match util::find_in_direction(&frames.frames, current_index, direction, predicate) {
601 Some((index, attribute)) => Ok((attribute, index)),
602 None => SubtrActorError::new_result(SubtrActorErrorVariant::NoUpdateAfterFrame {
603 actor_id: actor_id.clone(),
604 object_id: object_id.clone(),
605 frame_index: current_index,
606 }),
607 }
608 }
609
610 fn update_mappings(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
641 for update in frame.updated_actors.iter() {
642 macro_rules! maintain_link {
643 ($map:expr, $actor_type:expr, $attr:expr, $get_key: expr, $get_value: expr, $type:path) => {{
644 if &update.object_id == self.get_object_id_for_key(&$attr)? {
645 if self
646 .get_actor_ids_by_type($actor_type)?
647 .iter()
648 .any(|id| id == &update.actor_id)
649 {
650 let value = get_actor_attribute_matching!(
651 self,
652 &update.actor_id,
653 $attr,
654 $type
655 )?;
656 let _key = $get_key(update.actor_id, value);
657 let _new_value = $get_value(update.actor_id, value);
658 let _old_value = $map.insert(
659 $get_key(update.actor_id, value),
660 $get_value(update.actor_id, value),
661 );
662 }
663 }
664 }};
665 }
666 macro_rules! maintain_actor_link {
667 ($map:expr, $actor_type:expr, $attr:expr) => {
668 maintain_link!(
669 $map,
670 $actor_type,
671 $attr,
672 get_actor_id_from_active_actor,
675 use_update_actor,
676 boxcars::Attribute::ActiveActor
677 )
678 };
679 }
680 macro_rules! maintain_vehicle_key_link {
681 ($map:expr, $actor_type:expr) => {
682 maintain_actor_link!($map, $actor_type, VEHICLE_KEY)
683 };
684 }
685 maintain_link!(
686 self.player_to_actor_id,
687 PLAYER_TYPE,
688 UNIQUE_ID_KEY,
689 |_, unique_id: &Box<boxcars::UniqueId>| unique_id.remote_id.clone(),
690 use_update_actor,
691 boxcars::Attribute::UniqueId
692 );
693 maintain_link!(
694 self.player_to_team,
695 PLAYER_TYPE,
696 TEAM_KEY,
697 use_update_actor,
699 get_actor_id_from_active_actor,
700 boxcars::Attribute::ActiveActor
701 );
702 maintain_actor_link!(self.player_to_car, CAR_TYPE, PLAYER_REPLICATION_KEY);
703 maintain_vehicle_key_link!(self.car_to_boost, BOOST_TYPE);
704 maintain_vehicle_key_link!(self.car_to_dodge, DODGE_TYPE);
705 maintain_vehicle_key_link!(self.car_to_jump, JUMP_TYPE);
706 maintain_vehicle_key_link!(self.car_to_double_jump, DOUBLE_JUMP_TYPE);
707 }
708
709 for actor_id in frame.deleted_actors.iter() {
710 self.player_to_car.remove(actor_id).map(|car_id| {
711 log::info!("Player actor {:?} deleted, car id: {:?}.", actor_id, car_id)
712 });
713 }
714
715 Ok(())
716 }
717
718 fn update_ball_id(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
719 if let Some(actor_id) = self.ball_actor_id {
721 if frame.deleted_actors.contains(&actor_id) {
722 self.ball_actor_id = None;
723 }
724 } else {
725 self.ball_actor_id = self.find_ball_actor();
726 if self.ball_actor_id.is_some() {
727 return self.update_ball_id(frame);
728 }
729 }
730 Ok(())
731 }
732
733 fn update_boost_amounts(
751 &mut self,
752 frame: &boxcars::Frame,
753 frame_index: usize,
754 ) -> SubtrActorResult<()> {
755 let updates: Vec<_> = self
756 .iter_actors_by_type_err(BOOST_TYPE)?
757 .map(|(actor_id, actor_state)| {
758 let (actor_amount_value, last_value, _, derived_value, is_active) =
759 self.get_current_boost_values(actor_state);
760 let mut current_value = if actor_amount_value == last_value {
761 derived_value
764 } else {
765 actor_amount_value.into()
767 };
768 if is_active {
769 current_value -= frame.delta * BOOST_USED_PER_SECOND;
770 }
771 (actor_id.clone(), current_value.max(0.0), actor_amount_value)
772 })
773 .collect();
774
775 for (actor_id, current_value, new_last_value) in updates {
776 let derived_attributes = &mut self
777 .actor_state
778 .actor_states
779 .get_mut(&actor_id)
780 .unwrap()
782 .derived_attributes;
783
784 derived_attributes.insert(
785 LAST_BOOST_AMOUNT_KEY.to_string(),
786 (boxcars::Attribute::Byte(new_last_value), frame_index),
787 );
788 derived_attributes.insert(
789 BOOST_AMOUNT_KEY.to_string(),
790 (boxcars::Attribute::Float(current_value), frame_index),
791 );
792 }
793 Ok(())
794 }
795
796 fn get_current_boost_values(&self, actor_state: &ActorState) -> (u8, u8, u8, f32, bool) {
817 let amount_value = get_attribute_errors_expected!(
818 self,
819 &actor_state.attributes,
820 BOOST_AMOUNT_KEY,
821 boxcars::Attribute::Byte
822 )
823 .cloned()
824 .unwrap_or(0);
825 let active_value = get_attribute_errors_expected!(
826 self,
827 &actor_state.attributes,
828 COMPONENT_ACTIVE_KEY,
829 boxcars::Attribute::Byte
830 )
831 .cloned()
832 .unwrap_or(0);
833 let is_active = active_value % 2 == 1;
834 let derived_value = actor_state
835 .derived_attributes
836 .get(&BOOST_AMOUNT_KEY.to_string())
837 .cloned()
838 .and_then(|v| attribute_match!(v.0, boxcars::Attribute::Float).ok())
839 .unwrap_or(0.0);
840 let last_boost_amount = attribute_match!(
841 actor_state
842 .derived_attributes
843 .get(&LAST_BOOST_AMOUNT_KEY.to_string())
844 .cloned()
845 .map(|v| v.0)
846 .unwrap_or_else(|| boxcars::Attribute::Byte(amount_value)),
847 boxcars::Attribute::Byte
848 )
849 .unwrap_or(0);
850 (
851 amount_value,
852 last_boost_amount,
853 active_value,
854 derived_value,
855 is_active,
856 )
857 }
858
859 fn update_demolishes(&mut self, frame: &boxcars::Frame, index: usize) -> SubtrActorResult<()> {
860 let new_demolishes: Vec<_> = self
861 .get_active_demolish_fx()?
862 .flat_map(|demolish_fx| {
863 if !self.demolish_is_known(&demolish_fx, index) {
864 Some(demolish_fx.as_ref().clone())
865 } else {
866 None
867 }
868 })
869 .collect();
870
871 for demolish in new_demolishes {
872 match self.build_demolish_info(&demolish, frame, index) {
873 Ok(demolish_info) => self.demolishes.push(demolish_info),
874 Err(_e) => {
875 log::warn!("Error building demolish info");
876 }
877 }
878 self.known_demolishes.push((demolish, index))
879 }
880
881 Ok(())
882 }
883
884 fn build_demolish_info(
885 &self,
886 demolish_fx: &boxcars::DemolishFx,
887 frame: &boxcars::Frame,
888 index: usize,
889 ) -> SubtrActorResult<DemolishInfo> {
890 let attacker = self.get_player_id_from_car_id(&demolish_fx.attacker)?;
891 let victim = self.get_player_id_from_car_id(&demolish_fx.victim)?;
892 Ok(DemolishInfo {
893 time: frame.time,
894 seconds_remaining: self.get_seconds_remaining()?,
895 frame: index,
896 attacker,
897 victim,
898 attacker_velocity: demolish_fx.attack_velocity.clone(),
899 victim_velocity: demolish_fx.victim_velocity.clone(),
900 })
901 }
902
903 fn get_player_id_from_car_id(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<PlayerId> {
906 self.get_player_id_from_actor_id(&self.get_player_actor_id_from_car_actor_id(actor_id)?)
907 }
908
909 fn get_player_id_from_actor_id(
910 &self,
911 actor_id: &boxcars::ActorId,
912 ) -> SubtrActorResult<PlayerId> {
913 for (player_id, player_actor_id) in self.player_to_actor_id.iter() {
914 if actor_id == player_actor_id {
915 return Ok(player_id.clone());
916 }
917 }
918 return SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
919 actor_id: actor_id.clone(),
920 });
921 }
922
923 fn get_player_actor_id_from_car_actor_id(
924 &self,
925 actor_id: &boxcars::ActorId,
926 ) -> SubtrActorResult<boxcars::ActorId> {
927 for (player_id, car_id) in self.player_to_car.iter() {
928 if actor_id == car_id {
929 return Ok(player_id.clone());
930 }
931 }
932 return SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
933 actor_id: actor_id.clone(),
934 });
935 }
936
937 fn demolish_is_known(&self, demolish_fx: &boxcars::DemolishFx, frame_index: usize) -> bool {
938 self.known_demolishes.iter().any(|(existing, index)| {
939 existing == demolish_fx
940 && frame_index
941 .checked_sub(*index)
942 .or_else(|| index.checked_sub(frame_index))
943 .unwrap()
944 < MAX_DEMOLISH_KNOWN_FRAMES_PASSED
945 })
946 }
947
948 pub fn get_active_demolish_fx(
951 &self,
952 ) -> SubtrActorResult<impl Iterator<Item = &Box<boxcars::DemolishFx>>> {
953 Ok(self
954 .iter_actors_by_type_err(CAR_TYPE)?
955 .flat_map(|(_actor_id, state)| {
956 get_attribute_errors_expected!(
957 self,
958 &state.attributes,
959 DEMOLISH_GOAL_EXPLOSION_KEY,
960 boxcars::Attribute::DemolishFx
961 )
962 .ok()
963 }))
964 }
965
966 fn get_frame(&self, frame_index: usize) -> SubtrActorResult<&boxcars::Frame> {
969 self.replay
970 .network_frames
971 .as_ref()
972 .ok_or(SubtrActorError::new(
973 SubtrActorErrorVariant::NoNetworkFrames,
974 ))?
975 .frames
976 .get(frame_index)
977 .ok_or(SubtrActorError::new(
978 SubtrActorErrorVariant::FrameIndexOutOfBounds,
979 ))
980 }
981
982 fn velocities_applied_rigid_body(
983 &self,
984 rigid_body: &boxcars::RigidBody,
985 rb_frame_index: usize,
986 target_time: f32,
987 ) -> SubtrActorResult<boxcars::RigidBody> {
988 let rb_frame = self.get_frame(rb_frame_index)?;
989 let interpolation_amount = target_time - rb_frame.time;
990 Ok(apply_velocities_to_rigid_body(
991 rigid_body,
992 interpolation_amount,
993 ))
994 }
995
996 pub fn get_interpolated_actor_rigid_body(
1027 &self,
1028 actor_id: &boxcars::ActorId,
1029 time: f32,
1030 close_enough: f32,
1031 ) -> SubtrActorResult<boxcars::RigidBody> {
1032 let (frame_body, frame_index) = self.get_actor_rigid_body(actor_id)?;
1033 let frame_time = self.get_frame(*frame_index)?.time;
1034 let time_and_frame_difference = time - frame_time;
1035
1036 if (time_and_frame_difference).abs() <= close_enough.abs() {
1037 return Ok(frame_body.clone());
1038 }
1039
1040 let search_direction = if time_and_frame_difference > 0.0 {
1041 util::SearchDirection::Forward
1042 } else {
1043 util::SearchDirection::Backward
1044 };
1045
1046 let object_id = self.get_object_id_for_key(RIGID_BODY_STATE_KEY)?;
1047
1048 let (attribute, found_frame) =
1049 self.find_update_in_direction(*frame_index, &actor_id, object_id, search_direction)?;
1050 let found_time = self.get_frame(found_frame)?.time;
1051
1052 let found_body = attribute_match!(attribute, boxcars::Attribute::RigidBody)?;
1053
1054 if (found_time - time).abs() <= close_enough {
1055 return Ok(found_body.clone());
1056 }
1057
1058 let (start_body, start_time, end_body, end_time) = match search_direction {
1059 util::SearchDirection::Forward => (frame_body, frame_time, &found_body, found_time),
1060 util::SearchDirection::Backward => (&found_body, found_time, frame_body, frame_time),
1061 };
1062
1063 util::get_interpolated_rigid_body(start_body, start_time, end_body, end_time, time)
1064 }
1065
1066 fn get_object_id_for_key(&self, name: &'static str) -> SubtrActorResult<&boxcars::ObjectId> {
1069 self.name_to_object_id
1070 .get(name)
1071 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
1072 }
1073
1074 fn get_actor_ids_by_type(&self, name: &'static str) -> SubtrActorResult<&[boxcars::ActorId]> {
1075 self.get_object_id_for_key(name)
1076 .map(|object_id| self.get_actor_ids_by_object_id(object_id))
1077 }
1078
1079 fn get_actor_ids_by_object_id(&self, object_id: &boxcars::ObjectId) -> &[boxcars::ActorId] {
1080 self.actor_state
1081 .actor_ids_by_type
1082 .get(object_id)
1083 .map(|v| &v[..])
1084 .unwrap_or_else(|| &EMPTY_ACTOR_IDS)
1085 }
1086
1087 fn get_actor_state(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<&ActorState> {
1088 self.actor_state.actor_states.get(actor_id).ok_or_else(|| {
1089 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
1090 actor_id: actor_id.clone(),
1091 })
1092 })
1093 }
1094
1095 fn get_actor_attribute<'b>(
1096 &'b self,
1097 actor_id: &boxcars::ActorId,
1098 property: &'static str,
1099 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1100 self.get_attribute(&self.get_actor_state(actor_id)?.attributes, property)
1101 }
1102
1103 fn get_attribute<'b>(
1104 &'b self,
1105 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1106 property: &'static str,
1107 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1108 self.get_attribute_and_updated(map, property).map(|v| &v.0)
1109 }
1110
1111 fn get_attribute_and_updated<'b>(
1112 &'b self,
1113 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1114 property: &'static str,
1115 ) -> SubtrActorResult<&'b (boxcars::Attribute, usize)> {
1116 let attribute_object_id = self.get_object_id_for_key(property)?;
1117 map.get(attribute_object_id).ok_or_else(|| {
1118 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState { property })
1119 })
1120 }
1121
1122 fn find_ball_actor(&self) -> Option<boxcars::ActorId> {
1123 BALL_TYPES
1124 .iter()
1125 .filter_map(|ball_type| self.iter_actors_by_type(ball_type))
1126 .flat_map(|i| i)
1127 .map(|(actor_id, _)| actor_id.clone())
1128 .next()
1129 }
1130
1131 pub fn get_ball_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
1132 self.ball_actor_id.ok_or(SubtrActorError::new(
1133 SubtrActorErrorVariant::BallActorNotFound,
1134 ))
1135 }
1136
1137 pub fn get_metadata_actor_id(&self) -> SubtrActorResult<&boxcars::ActorId> {
1138 self.get_actor_ids_by_type(GAME_TYPE)?
1139 .iter()
1140 .next()
1141 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))
1142 }
1143
1144 pub fn get_player_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1145 self.player_to_actor_id
1146 .get(&player_id)
1147 .ok_or_else(|| {
1148 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1149 name: "ActorId",
1150 player_id: player_id.clone(),
1151 })
1152 })
1153 .cloned()
1154 }
1155
1156 pub fn get_car_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1157 self.player_to_car
1158 .get(&self.get_player_actor_id(player_id)?)
1159 .ok_or_else(|| {
1160 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1161 name: "Car",
1162 player_id: player_id.clone(),
1163 })
1164 })
1165 .cloned()
1166 }
1167
1168 pub fn get_car_connected_actor_id(
1169 &self,
1170 player_id: &PlayerId,
1171 map: &HashMap<boxcars::ActorId, boxcars::ActorId>,
1172 name: &'static str,
1173 ) -> SubtrActorResult<boxcars::ActorId> {
1174 map.get(&self.get_car_actor_id(player_id)?)
1175 .ok_or_else(|| {
1176 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1177 name,
1178 player_id: player_id.clone(),
1179 })
1180 })
1181 .cloned()
1182 }
1183
1184 pub fn get_boost_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1185 self.get_car_connected_actor_id(player_id, &self.car_to_boost, "Boost")
1186 }
1187
1188 pub fn get_jump_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1189 self.get_car_connected_actor_id(player_id, &self.car_to_jump, "Jump")
1190 }
1191
1192 pub fn get_double_jump_actor_id(
1193 &self,
1194 player_id: &PlayerId,
1195 ) -> SubtrActorResult<boxcars::ActorId> {
1196 self.get_car_connected_actor_id(player_id, &self.car_to_double_jump, "Double Jump")
1197 }
1198
1199 pub fn get_dodge_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1200 self.get_car_connected_actor_id(player_id, &self.car_to_dodge, "Dodge")
1201 }
1202
1203 pub fn get_actor_rigid_body(
1204 &self,
1205 actor_id: &boxcars::ActorId,
1206 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1207 get_attribute_and_updated!(
1208 self,
1209 &self.get_actor_state(&actor_id)?.attributes,
1210 RIGID_BODY_STATE_KEY,
1211 boxcars::Attribute::RigidBody
1212 )
1213 }
1214
1215 pub fn iter_player_ids_in_order(&self) -> impl Iterator<Item = &PlayerId> {
1218 self.team_zero.iter().chain(self.team_one.iter())
1219 }
1220
1221 pub fn player_count(&self) -> usize {
1222 self.iter_player_ids_in_order().count()
1223 }
1224
1225 fn iter_actors_by_type_err(
1226 &self,
1227 name: &'static str,
1228 ) -> SubtrActorResult<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1229 Ok(self.iter_actors_by_object_id(self.get_object_id_for_key(name)?))
1230 }
1231
1232 pub fn iter_actors_by_type(
1233 &self,
1234 name: &'static str,
1235 ) -> Option<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1236 self.iter_actors_by_type_err(name).ok()
1237 }
1238
1239 pub fn iter_actors_by_object_id<'b>(
1240 &'b self,
1241 object_id: &'b boxcars::ObjectId,
1242 ) -> impl Iterator<Item = (&'b boxcars::ActorId, &'b ActorState)> + 'b {
1243 let actor_ids = self
1244 .actor_state
1245 .actor_ids_by_type
1246 .get(object_id)
1247 .map(|v| &v[..])
1248 .unwrap_or_else(|| &EMPTY_ACTOR_IDS);
1249
1250 actor_ids
1251 .iter()
1252 .map(move |id| (id, self.actor_state.actor_states.get(id).unwrap()))
1255 }
1256
1257 pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
1261 get_actor_attribute_matching!(
1262 self,
1263 self.get_metadata_actor_id()?,
1264 SECONDS_REMAINING_KEY,
1265 boxcars::Attribute::Int
1266 )
1267 .cloned()
1268 }
1269
1270 pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
1272 let actor_id = self.get_ball_actor_id()?;
1273 get_actor_attribute_matching!(
1274 self,
1275 &actor_id,
1276 IGNORE_SYNCING_KEY,
1277 boxcars::Attribute::Boolean
1278 )
1279 .cloned()
1280 }
1281
1282 pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
1284 self.ball_actor_id
1285 .ok_or(SubtrActorError::new(
1286 SubtrActorErrorVariant::BallActorNotFound,
1287 ))
1288 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1289 }
1290
1291 pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
1294 Ok(self
1295 .get_ball_rigid_body()
1296 .map(|rb| !rb.sleeping)
1297 .unwrap_or(false))
1298 }
1299
1300 pub fn get_ball_rigid_body_and_updated(
1303 &self,
1304 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1305 self.ball_actor_id
1306 .ok_or(SubtrActorError::new(
1307 SubtrActorErrorVariant::BallActorNotFound,
1308 ))
1309 .and_then(|actor_id| {
1310 get_attribute_and_updated!(
1311 self,
1312 &self.get_actor_state(&actor_id)?.attributes,
1313 RIGID_BODY_STATE_KEY,
1314 boxcars::Attribute::RigidBody
1315 )
1316 })
1317 }
1318
1319 pub fn get_velocity_applied_ball_rigid_body(
1322 &self,
1323 target_time: f32,
1324 ) -> SubtrActorResult<boxcars::RigidBody> {
1325 let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
1326 self.velocities_applied_rigid_body(¤t_rigid_body, *frame_index, target_time)
1327 }
1328
1329 pub fn get_interpolated_ball_rigid_body(
1332 &self,
1333 time: f32,
1334 close_enough: f32,
1335 ) -> SubtrActorResult<boxcars::RigidBody> {
1336 self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
1337 }
1338
1339 pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1341 get_actor_attribute_matching!(
1342 self,
1343 &self.get_player_actor_id(player_id)?,
1344 PLAYER_NAME_KEY,
1345 boxcars::Attribute::String
1346 )
1347 .cloned()
1348 }
1349
1350 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1352 let team_actor_id = self
1353 .player_to_team
1354 .get(&self.get_player_actor_id(player_id)?)
1355 .ok_or_else(|| {
1356 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1357 player_id: player_id.clone(),
1358 })
1359 })?;
1360 let state = self.get_actor_state(team_actor_id)?;
1361 self.object_id_to_name
1362 .get(&state.object_id)
1363 .ok_or_else(|| {
1364 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1365 player_id: player_id.clone(),
1366 })
1367 })
1368 .cloned()
1369 }
1370
1371 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
1373 Ok(self
1374 .get_player_team_key(player_id)?
1375 .chars()
1376 .last()
1377 .ok_or_else(|| {
1378 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
1379 player_id: player_id.clone(),
1380 })
1381 })?
1382 == '0')
1383 }
1384
1385 pub fn get_player_rigid_body(
1387 &self,
1388 player_id: &PlayerId,
1389 ) -> SubtrActorResult<&boxcars::RigidBody> {
1390 self.get_car_actor_id(player_id)
1391 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1392 }
1393
1394 pub fn get_player_rigid_body_and_updated(
1398 &self,
1399 player_id: &PlayerId,
1400 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1401 self.get_car_actor_id(player_id).and_then(|actor_id| {
1402 get_attribute_and_updated!(
1403 self,
1404 &self.get_actor_state(&actor_id)?.attributes,
1405 RIGID_BODY_STATE_KEY,
1406 boxcars::Attribute::RigidBody
1407 )
1408 })
1409 }
1410
1411 pub fn get_velocity_applied_player_rigid_body(
1412 &self,
1413 player_id: &PlayerId,
1414 target_time: f32,
1415 ) -> SubtrActorResult<boxcars::RigidBody> {
1416 let (current_rigid_body, frame_index) =
1417 self.get_player_rigid_body_and_updated(player_id)?;
1418 self.velocities_applied_rigid_body(¤t_rigid_body, *frame_index, target_time)
1419 }
1420
1421 pub fn get_interpolated_player_rigid_body(
1422 &self,
1423 player_id: &PlayerId,
1424 time: f32,
1425 close_enough: f32,
1426 ) -> SubtrActorResult<boxcars::RigidBody> {
1427 self.get_interpolated_actor_rigid_body(
1428 &self.get_car_actor_id(player_id).unwrap(),
1429 time,
1430 close_enough,
1431 )
1432 }
1433
1434 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
1435 self.get_boost_actor_id(player_id).and_then(|actor_id| {
1436 let boost_state = self.get_actor_state(&actor_id)?;
1437 get_derived_attribute!(
1438 boost_state.derived_attributes,
1439 BOOST_AMOUNT_KEY,
1440 boxcars::Attribute::Float
1441 )
1442 .cloned()
1443 })
1444 }
1445
1446 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
1447 get_actor_attribute_matching!(
1448 self,
1449 &actor_id,
1450 COMPONENT_ACTIVE_KEY,
1451 boxcars::Attribute::Byte
1452 )
1453 .cloned()
1454 }
1455
1456 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1457 self.get_boost_actor_id(player_id)
1458 .and_then(|actor_id| self.get_component_active(&actor_id))
1459 }
1460
1461 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1462 self.get_jump_actor_id(player_id)
1463 .and_then(|actor_id| self.get_component_active(&actor_id))
1464 }
1465
1466 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1467 self.get_double_jump_actor_id(player_id)
1468 .and_then(|actor_id| self.get_component_active(&actor_id))
1469 }
1470
1471 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1472 self.get_dodge_actor_id(player_id)
1473 .and_then(|actor_id| self.get_component_active(&actor_id))
1474 }
1475
1476 pub fn map_attribute_keys(
1479 &self,
1480 hash_map: &HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1481 ) -> HashMap<String, boxcars::Attribute> {
1482 hash_map
1483 .iter()
1484 .map(|(k, (v, _updated))| {
1485 self.object_id_to_name
1486 .get(k)
1487 .map(|name| (name.clone(), v.clone()))
1488 .unwrap()
1489 })
1490 .collect()
1491 }
1492
1493 pub fn all_mappings_string(&self) -> String {
1494 let pairs = [
1495 ("player_to_car", &self.player_to_car),
1496 ("player_to_team", &self.player_to_team),
1497 ("car_to_boost", &self.car_to_boost),
1498 ("car_to_jump", &self.car_to_jump),
1499 ("car_to_double_jump", &self.car_to_double_jump),
1500 ("car_to_dodge", &self.car_to_dodge),
1501 ];
1502 let strings: Vec<_> = pairs
1503 .iter()
1504 .map(|(map_name, map)| format!("{:?}: {:?}", map_name, map))
1505 .collect();
1506 strings.join("\n")
1507 }
1508
1509 pub fn actor_state_string(&self, actor_id: &boxcars::ActorId) -> String {
1510 format!(
1511 "{:?}",
1512 self.get_actor_state(actor_id)
1513 .map(|s| self.map_attribute_keys(&s.attributes))
1514 )
1515 }
1516
1517 pub fn print_actors_by_id<'b>(&self, actor_ids: impl Iterator<Item = &'b boxcars::ActorId>) {
1518 actor_ids.for_each(|actor_id| {
1519 let state = self.get_actor_state(actor_id).unwrap();
1520 println!(
1521 "{:?}\n\n\n",
1522 self.object_id_to_name.get(&state.object_id).unwrap()
1523 );
1524 println!("{:?}", self.map_attribute_keys(&state.attributes))
1525 })
1526 }
1527
1528 pub fn print_actors_of_type(&self, actor_type: &'static str) {
1529 self.iter_actors_by_type(actor_type)
1530 .unwrap()
1531 .for_each(|(_actor_id, state)| {
1532 println!("{:?}", self.map_attribute_keys(&state.attributes));
1533 });
1534 }
1535
1536 pub fn print_actor_types(&self) {
1537 let types: Vec<_> = self
1538 .actor_state
1539 .actor_ids_by_type
1540 .keys()
1541 .filter_map(|id| self.object_id_to_name.get(id))
1542 .collect();
1543 println!("{:?}", types);
1544 }
1545
1546 pub fn print_all_actors(&self) {
1547 self.actor_state
1548 .actor_states
1549 .iter()
1550 .for_each(|(actor_id, _actor_state)| {
1551 println!("{:?}", self.actor_state_string(actor_id))
1552 })
1553 }
1554}