1use crate::*;
2use boxcars;
3use std::collections::HashMap;
4
5macro_rules! attribute_match {
17 ($value:expr, $type:path $(,)?) => {{
18 let attribute = $value;
19 if let $type(value) = attribute {
20 Ok(value)
21 } else {
22 SubtrActorError::new_result(SubtrActorErrorVariant::UnexpectedAttributeType {
23 expected_type: stringify!(path).to_string(),
24 actual_type: attribute_to_tag(&attribute).to_string(),
25 })
26 }
27 }};
28}
29
30macro_rules! get_attribute_errors_expected {
39 ($self:ident, $map:expr, $prop:expr, $type:path) => {
40 $self
41 .get_attribute($map, $prop)
42 .and_then(|found| attribute_match!(found, $type))
43 };
44}
45
46macro_rules! get_attribute_and_updated {
59 ($self:ident, $map:expr, $prop:expr, $type:path) => {
60 $self
61 .get_attribute_and_updated($map, $prop)
62 .and_then(|(found, updated)| attribute_match!(found, $type).map(|v| (v, updated)))
63 };
64}
65
66macro_rules! get_actor_attribute_matching {
75 ($self:ident, $actor:expr, $prop:expr, $type:path) => {
76 $self
77 .get_actor_attribute($actor, $prop)
78 .and_then(|found| attribute_match!(found, $type))
79 };
80}
81
82macro_rules! get_derived_attribute {
91 ($map:expr, $key:expr, $type:path) => {
92 $map.get($key)
93 .ok_or_else(|| {
94 SubtrActorError::new(SubtrActorErrorVariant::DerivedKeyValueNotFound {
95 name: $key.to_string(),
96 })
97 })
98 .and_then(|found| attribute_match!(&found.0, $type))
99 };
100}
101
102fn get_actor_id_from_active_actor<T>(
103 _: T,
104 active_actor: &boxcars::ActiveActor,
105) -> boxcars::ActorId {
106 active_actor.actor
107}
108
109fn use_update_actor<T>(id: boxcars::ActorId, _: T) -> boxcars::ActorId {
110 id
111}
112
113pub struct ReplayProcessor<'a> {
145 pub replay: &'a boxcars::Replay,
146 pub actor_state: ActorStateModeler,
147 pub object_id_to_name: HashMap<boxcars::ObjectId, String>,
148 pub name_to_object_id: HashMap<String, boxcars::ObjectId>,
149 pub ball_actor_id: Option<boxcars::ActorId>,
150 pub team_zero: Vec<PlayerId>,
151 pub team_one: Vec<PlayerId>,
152 pub player_to_actor_id: HashMap<PlayerId, boxcars::ActorId>,
153 pub player_to_car: HashMap<boxcars::ActorId, boxcars::ActorId>,
154 pub player_to_team: HashMap<boxcars::ActorId, boxcars::ActorId>,
155 pub car_to_boost: HashMap<boxcars::ActorId, boxcars::ActorId>,
156 pub car_to_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
157 pub car_to_double_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
158 pub car_to_dodge: HashMap<boxcars::ActorId, boxcars::ActorId>,
159 pub demolishes: Vec<DemolishInfo>,
160 known_demolishes: Vec<(boxcars::DemolishFx, usize)>,
161}
162
163impl<'a> ReplayProcessor<'a> {
164 pub fn new(replay: &'a boxcars::Replay) -> SubtrActorResult<Self> {
178 let mut object_id_to_name = HashMap::new();
179 let mut name_to_object_id = HashMap::new();
180 for (id, name) in replay.objects.iter().enumerate() {
181 let object_id = boxcars::ObjectId(id as i32);
182 object_id_to_name.insert(object_id, name.clone());
183 name_to_object_id.insert(name.clone(), object_id);
184 }
185 let mut processor = Self {
186 actor_state: ActorStateModeler::new(),
187 replay,
188 object_id_to_name,
189 name_to_object_id,
190 team_zero: Vec::new(),
191 team_one: Vec::new(),
192 ball_actor_id: None,
193 player_to_car: HashMap::new(),
194 player_to_team: HashMap::new(),
195 player_to_actor_id: HashMap::new(),
196 car_to_boost: HashMap::new(),
197 car_to_jump: HashMap::new(),
198 car_to_double_jump: HashMap::new(),
199 car_to_dodge: HashMap::new(),
200 demolishes: Vec::new(),
201 known_demolishes: Vec::new(),
202 };
203 processor
204 .set_player_order_from_headers()
205 .or_else(|_| processor.set_player_order_from_frames())?;
206
207 Ok(processor)
208 }
209
210 pub fn process<H: Collector>(&mut self, handler: &mut H) -> SubtrActorResult<()> {
232 let mut target_time = TimeAdvance::NextFrame;
235 for (index, frame) in self
236 .replay
237 .network_frames
238 .as_ref()
239 .ok_or(SubtrActorError::new(
240 SubtrActorErrorVariant::NoNetworkFrames,
241 ))?
242 .frames
243 .iter()
244 .enumerate()
245 {
246 self.actor_state.process_frame(frame, index)?;
248 self.update_mappings(frame)?;
249 self.update_ball_id(frame)?;
250 self.update_boost_amounts(frame, index)?;
251 self.update_demolishes(frame, index)?;
252
253 let mut current_time = match &target_time {
256 TimeAdvance::Time(t) => *t,
257 TimeAdvance::NextFrame => frame.time,
258 };
259
260 while current_time <= frame.time {
261 target_time = handler.process_frame(&self, frame, index, current_time)?;
264 if let TimeAdvance::Time(new_target) = target_time {
270 current_time = new_target;
271 } else {
272 break;
273 }
274 }
275 }
276 self.check_player_id_set()
283 }
284
285 pub fn reset(&mut self) {
287 self.player_to_car = HashMap::new();
288 self.player_to_team = HashMap::new();
289 self.player_to_actor_id = HashMap::new();
290 self.car_to_boost = HashMap::new();
291 self.car_to_jump = HashMap::new();
292 self.car_to_double_jump = HashMap::new();
293 self.car_to_dodge = HashMap::new();
294 self.actor_state = ActorStateModeler::new();
295 self.demolishes = Vec::new();
296 self.known_demolishes = Vec::new();
297 }
298
299 fn set_player_order_from_headers(&mut self) -> SubtrActorResult<()> {
300 let _player_stats = self
301 .replay
302 .properties
303 .iter()
304 .find(|(key, _)| key == "PlayerStats")
305 .ok_or_else(|| {
306 SubtrActorError::new(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
307 })?;
308 SubtrActorError::new_result(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
310 }
311
312 pub fn process_long_enough_to_get_actor_ids(&mut self) -> SubtrActorResult<()> {
329 let mut handler = |_p: &ReplayProcessor, _f: &boxcars::Frame, n: usize, _current_time| {
330 if n > 10 * 30 {
332 SubtrActorError::new_result(SubtrActorErrorVariant::FinishProcessingEarly)
333 } else {
334 Ok(TimeAdvance::NextFrame)
335 }
336 };
337 let process_result = self.process(&mut handler);
338 if let Some(SubtrActorErrorVariant::FinishProcessingEarly) =
339 process_result.as_ref().err().map(|e| e.variant.clone())
340 {
341 Ok(())
342 } else {
343 process_result
344 }
345 }
346
347 fn set_player_order_from_frames(&mut self) -> SubtrActorResult<()> {
348 self.process_long_enough_to_get_actor_ids()?;
349 let result: Result<HashMap<PlayerId, bool>, _> = self
350 .player_to_actor_id
351 .keys()
352 .map(|player_id| Ok((player_id.clone(), self.get_player_is_team_0(player_id)?)))
353 .collect();
354
355 let player_to_team_0 = result?;
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 pub fn process_and_get_replay_meta(&mut self) -> SubtrActorResult<ReplayMeta> {
400 if self.player_to_actor_id.is_empty() {
401 self.process_long_enough_to_get_actor_ids()?;
402 }
403 self.get_replay_meta()
404 }
405
406 pub fn get_replay_meta(&self) -> SubtrActorResult<ReplayMeta> {
413 let empty_player_stats = Vec::new();
414 let player_stats = if let Some((_, boxcars::HeaderProp::Array(per_player))) = self
415 .replay
416 .properties
417 .iter()
418 .find(|(key, _)| key == "PlayerStats")
419 {
420 per_player
421 } else {
422 &empty_player_stats
423 };
424 let known_count = self.iter_player_ids_in_order().count();
425 if player_stats.len() != known_count {
426 log::warn!(
427 "Replay does not have player stats for all players. encountered {:?} {:?}",
428 known_count,
429 player_stats.len()
430 )
431 }
432 let get_player_info = |player_id| {
433 let name = self.get_player_name(player_id)?;
434 let stats = find_player_stats(player_id, &name, player_stats).ok();
435 Ok(PlayerInfo {
436 name,
437 stats,
438 remote_id: player_id.clone(),
439 })
440 };
441 let team_zero: SubtrActorResult<Vec<PlayerInfo>> =
442 self.team_zero.iter().map(get_player_info).collect();
443 let team_one: SubtrActorResult<Vec<PlayerInfo>> =
444 self.team_one.iter().map(get_player_info).collect();
445 Ok(ReplayMeta {
446 team_zero: team_zero?,
447 team_one: team_one?,
448 all_headers: self.replay.properties.clone(),
449 })
450 }
451
452 pub fn find_update_in_direction(
488 &self,
489 current_index: usize,
490 actor_id: &boxcars::ActorId,
491 object_id: &boxcars::ObjectId,
492 direction: SearchDirection,
493 ) -> SubtrActorResult<(boxcars::Attribute, usize)> {
494 let frames = self
495 .replay
496 .network_frames
497 .as_ref()
498 .ok_or(SubtrActorError::new(
499 SubtrActorErrorVariant::NoNetworkFrames,
500 ))?;
501
502 let predicate = |frame: &boxcars::Frame| {
503 frame
504 .updated_actors
505 .iter()
506 .find(|update| &update.actor_id == actor_id && &update.object_id == object_id)
507 .map(|update| &update.attribute)
508 .cloned()
509 };
510
511 match util::find_in_direction(&frames.frames, current_index, direction, predicate) {
512 Some((index, attribute)) => Ok((attribute, index)),
513 None => SubtrActorError::new_result(SubtrActorErrorVariant::NoUpdateAfterFrame {
514 actor_id: actor_id.clone(),
515 object_id: object_id.clone(),
516 frame_index: current_index,
517 }),
518 }
519 }
520
521 fn update_mappings(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
552 for update in frame.updated_actors.iter() {
553 macro_rules! maintain_link {
554 ($map:expr, $actor_type:expr, $attr:expr, $get_key: expr, $get_value: expr, $type:path) => {{
555 if &update.object_id == self.get_object_id_for_key(&$attr)? {
556 if self
557 .get_actor_ids_by_type($actor_type)?
558 .iter()
559 .any(|id| id == &update.actor_id)
560 {
561 let value = get_actor_attribute_matching!(
562 self,
563 &update.actor_id,
564 $attr,
565 $type
566 )?;
567 let _key = $get_key(update.actor_id, value);
568 let _new_value = $get_value(update.actor_id, value);
569 let _old_value = $map.insert(
570 $get_key(update.actor_id, value),
571 $get_value(update.actor_id, value),
572 );
573 }
574 }
575 }};
576 }
577 macro_rules! maintain_actor_link {
578 ($map:expr, $actor_type:expr, $attr:expr) => {
579 maintain_link!(
580 $map,
581 $actor_type,
582 $attr,
583 get_actor_id_from_active_actor,
586 use_update_actor,
587 boxcars::Attribute::ActiveActor
588 )
589 };
590 }
591 macro_rules! maintain_vehicle_key_link {
592 ($map:expr, $actor_type:expr) => {
593 maintain_actor_link!($map, $actor_type, VEHICLE_KEY)
594 };
595 }
596 maintain_link!(
597 self.player_to_actor_id,
598 PLAYER_TYPE,
599 UNIQUE_ID_KEY,
600 |_, unique_id: &Box<boxcars::UniqueId>| unique_id.remote_id.clone(),
601 use_update_actor,
602 boxcars::Attribute::UniqueId
603 );
604 maintain_link!(
605 self.player_to_team,
606 PLAYER_TYPE,
607 TEAM_KEY,
608 use_update_actor,
610 get_actor_id_from_active_actor,
611 boxcars::Attribute::ActiveActor
612 );
613 maintain_actor_link!(self.player_to_car, CAR_TYPE, PLAYER_REPLICATION_KEY);
614 maintain_vehicle_key_link!(self.car_to_boost, BOOST_TYPE);
615 maintain_vehicle_key_link!(self.car_to_dodge, DODGE_TYPE);
616 maintain_vehicle_key_link!(self.car_to_jump, JUMP_TYPE);
617 maintain_vehicle_key_link!(self.car_to_double_jump, DOUBLE_JUMP_TYPE);
618 }
619
620 for actor_id in frame.deleted_actors.iter() {
621 self.player_to_car.remove(actor_id).map(|car_id| {
622 log::info!("Player actor {:?} deleted, car id: {:?}.", actor_id, car_id)
623 });
624 }
625
626 Ok(())
627 }
628
629 fn update_ball_id(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
630 if let Some(actor_id) = self.ball_actor_id {
632 if frame.deleted_actors.contains(&actor_id) {
633 self.ball_actor_id = None;
634 }
635 } else {
636 self.ball_actor_id = self.find_ball_actor();
637 if self.ball_actor_id.is_some() {
638 return self.update_ball_id(frame);
639 }
640 }
641 Ok(())
642 }
643
644 fn update_boost_amounts(
662 &mut self,
663 frame: &boxcars::Frame,
664 frame_index: usize,
665 ) -> SubtrActorResult<()> {
666 let updates: Vec<_> = self
667 .iter_actors_by_type_err(BOOST_TYPE)?
668 .map(|(actor_id, actor_state)| {
669 let (actor_amount_value, last_value, _, derived_value, is_active) =
670 self.get_current_boost_values(actor_state);
671 let mut current_value = if actor_amount_value == last_value {
672 derived_value
675 } else {
676 actor_amount_value.into()
678 };
679 if is_active {
680 current_value -= frame.delta * BOOST_USED_PER_SECOND;
681 }
682 (actor_id.clone(), current_value.max(0.0), actor_amount_value)
683 })
684 .collect();
685
686 for (actor_id, current_value, new_last_value) in updates {
687 let derived_attributes = &mut self
688 .actor_state
689 .actor_states
690 .get_mut(&actor_id)
691 .unwrap()
693 .derived_attributes;
694
695 derived_attributes.insert(
696 LAST_BOOST_AMOUNT_KEY.to_string(),
697 (boxcars::Attribute::Byte(new_last_value), frame_index),
698 );
699 derived_attributes.insert(
700 BOOST_AMOUNT_KEY.to_string(),
701 (boxcars::Attribute::Float(current_value), frame_index),
702 );
703 }
704 Ok(())
705 }
706
707 fn get_current_boost_values(&self, actor_state: &ActorState) -> (u8, u8, u8, f32, bool) {
728 let amount_value = get_attribute_errors_expected!(
729 self,
730 &actor_state.attributes,
731 BOOST_AMOUNT_KEY,
732 boxcars::Attribute::Byte
733 )
734 .cloned()
735 .unwrap_or(0);
736 let active_value = get_attribute_errors_expected!(
737 self,
738 &actor_state.attributes,
739 COMPONENT_ACTIVE_KEY,
740 boxcars::Attribute::Byte
741 )
742 .cloned()
743 .unwrap_or(0);
744 let is_active = active_value % 2 == 1;
745 let derived_value = actor_state
746 .derived_attributes
747 .get(&BOOST_AMOUNT_KEY.to_string())
748 .cloned()
749 .and_then(|v| attribute_match!(v.0, boxcars::Attribute::Float).ok())
750 .unwrap_or(0.0);
751 let last_boost_amount = attribute_match!(
752 actor_state
753 .derived_attributes
754 .get(&LAST_BOOST_AMOUNT_KEY.to_string())
755 .cloned()
756 .map(|v| v.0)
757 .unwrap_or_else(|| boxcars::Attribute::Byte(amount_value)),
758 boxcars::Attribute::Byte
759 )
760 .unwrap_or(0);
761 (
762 amount_value,
763 last_boost_amount,
764 active_value,
765 derived_value,
766 is_active,
767 )
768 }
769
770 fn update_demolishes(&mut self, frame: &boxcars::Frame, index: usize) -> SubtrActorResult<()> {
771 let new_demolishes: Vec<_> = self
772 .get_active_demolish_fx()?
773 .flat_map(|demolish_fx| {
774 if !self.demolish_is_known(&demolish_fx, index) {
775 Some(demolish_fx.as_ref().clone())
776 } else {
777 None
778 }
779 })
780 .collect();
781
782 for demolish in new_demolishes {
783 match self.build_demolish_info(&demolish, frame, index) {
784 Ok(demolish_info) => self.demolishes.push(demolish_info),
785 Err(_e) => {
786 log::warn!("Error building demolish info");
787 }
788 }
789 self.known_demolishes.push((demolish, index))
790 }
791
792 Ok(())
793 }
794
795 fn build_demolish_info(
796 &self,
797 demolish_fx: &boxcars::DemolishFx,
798 frame: &boxcars::Frame,
799 index: usize,
800 ) -> SubtrActorResult<DemolishInfo> {
801 let attacker = self.get_player_id_from_car_id(&demolish_fx.attacker)?;
802 let victim = self.get_player_id_from_car_id(&demolish_fx.victim)?;
803 Ok(DemolishInfo {
804 time: frame.time,
805 seconds_remaining: self.get_seconds_remaining()?,
806 frame: index,
807 attacker,
808 victim,
809 attacker_velocity: demolish_fx.attack_velocity.clone(),
810 victim_velocity: demolish_fx.victim_velocity.clone(),
811 })
812 }
813
814 fn get_player_id_from_car_id(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<PlayerId> {
817 self.get_player_id_from_actor_id(&self.get_player_actor_id_from_car_actor_id(actor_id)?)
818 }
819
820 fn get_player_id_from_actor_id(
821 &self,
822 actor_id: &boxcars::ActorId,
823 ) -> SubtrActorResult<PlayerId> {
824 for (player_id, player_actor_id) in self.player_to_actor_id.iter() {
825 if actor_id == player_actor_id {
826 return Ok(player_id.clone());
827 }
828 }
829 return SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
830 actor_id: actor_id.clone(),
831 });
832 }
833
834 fn get_player_actor_id_from_car_actor_id(
835 &self,
836 actor_id: &boxcars::ActorId,
837 ) -> SubtrActorResult<boxcars::ActorId> {
838 for (player_id, car_id) in self.player_to_car.iter() {
839 if actor_id == car_id {
840 return Ok(player_id.clone());
841 }
842 }
843 return SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
844 actor_id: actor_id.clone(),
845 });
846 }
847
848 fn demolish_is_known(&self, demolish_fx: &boxcars::DemolishFx, frame_index: usize) -> bool {
849 self.known_demolishes.iter().any(|(existing, index)| {
850 existing == demolish_fx
851 && frame_index
852 .checked_sub(*index)
853 .or_else(|| index.checked_sub(frame_index))
854 .unwrap()
855 < MAX_DEMOLISH_KNOWN_FRAMES_PASSED
856 })
857 }
858
859 pub fn get_active_demolish_fx(
862 &self,
863 ) -> SubtrActorResult<impl Iterator<Item = &Box<boxcars::DemolishFx>>> {
864 Ok(self
865 .iter_actors_by_type_err(CAR_TYPE)?
866 .flat_map(|(_actor_id, state)| {
867 get_attribute_errors_expected!(
868 self,
869 &state.attributes,
870 DEMOLISH_GOAL_EXPLOSION_KEY,
871 boxcars::Attribute::DemolishFx
872 )
873 .ok()
874 }))
875 }
876
877 fn get_frame(&self, frame_index: usize) -> SubtrActorResult<&boxcars::Frame> {
880 self.replay
881 .network_frames
882 .as_ref()
883 .ok_or(SubtrActorError::new(
884 SubtrActorErrorVariant::NoNetworkFrames,
885 ))?
886 .frames
887 .get(frame_index)
888 .ok_or(SubtrActorError::new(
889 SubtrActorErrorVariant::FrameIndexOutOfBounds,
890 ))
891 }
892
893 fn velocities_applied_rigid_body(
894 &self,
895 rigid_body: &boxcars::RigidBody,
896 rb_frame_index: usize,
897 target_time: f32,
898 ) -> SubtrActorResult<boxcars::RigidBody> {
899 let rb_frame = self.get_frame(rb_frame_index)?;
900 let interpolation_amount = target_time - rb_frame.time;
901 Ok(apply_velocities_to_rigid_body(
902 rigid_body,
903 interpolation_amount,
904 ))
905 }
906
907 pub fn get_interpolated_actor_rigid_body(
938 &self,
939 actor_id: &boxcars::ActorId,
940 time: f32,
941 close_enough: f32,
942 ) -> SubtrActorResult<boxcars::RigidBody> {
943 let (frame_body, frame_index) = self.get_actor_rigid_body(actor_id)?;
944 let frame_time = self.get_frame(*frame_index)?.time;
945 let time_and_frame_difference = time - frame_time;
946
947 if (time_and_frame_difference).abs() <= close_enough.abs() {
948 return Ok(frame_body.clone());
949 }
950
951 let search_direction = if time_and_frame_difference > 0.0 {
952 util::SearchDirection::Forward
953 } else {
954 util::SearchDirection::Backward
955 };
956
957 let object_id = self.get_object_id_for_key(RIGID_BODY_STATE_KEY)?;
958
959 let (attribute, found_frame) =
960 self.find_update_in_direction(*frame_index, &actor_id, object_id, search_direction)?;
961 let found_time = self.get_frame(found_frame)?.time;
962
963 let found_body = attribute_match!(attribute, boxcars::Attribute::RigidBody)?;
964
965 if (found_time - time).abs() <= close_enough {
966 return Ok(found_body.clone());
967 }
968
969 let (start_body, start_time, end_body, end_time) = match search_direction {
970 util::SearchDirection::Forward => (frame_body, frame_time, &found_body, found_time),
971 util::SearchDirection::Backward => (&found_body, found_time, frame_body, frame_time),
972 };
973
974 util::get_interpolated_rigid_body(start_body, start_time, end_body, end_time, time)
975 }
976
977 fn get_object_id_for_key(&self, name: &'static str) -> SubtrActorResult<&boxcars::ObjectId> {
980 self.name_to_object_id
981 .get(name)
982 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
983 }
984
985 fn get_actor_ids_by_type(&self, name: &'static str) -> SubtrActorResult<&[boxcars::ActorId]> {
986 self.get_object_id_for_key(name)
987 .map(|object_id| self.get_actor_ids_by_object_id(object_id))
988 }
989
990 fn get_actor_ids_by_object_id(&self, object_id: &boxcars::ObjectId) -> &[boxcars::ActorId] {
991 self.actor_state
992 .actor_ids_by_type
993 .get(object_id)
994 .map(|v| &v[..])
995 .unwrap_or_else(|| &EMPTY_ACTOR_IDS)
996 }
997
998 fn get_actor_state(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<&ActorState> {
999 self.actor_state.actor_states.get(actor_id).ok_or_else(|| {
1000 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
1001 actor_id: actor_id.clone(),
1002 })
1003 })
1004 }
1005
1006 fn get_actor_attribute<'b>(
1007 &'b self,
1008 actor_id: &boxcars::ActorId,
1009 property: &'static str,
1010 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1011 self.get_attribute(&self.get_actor_state(actor_id)?.attributes, property)
1012 }
1013
1014 fn get_attribute<'b>(
1015 &'b self,
1016 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1017 property: &'static str,
1018 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1019 self.get_attribute_and_updated(map, property).map(|v| &v.0)
1020 }
1021
1022 fn get_attribute_and_updated<'b>(
1023 &'b self,
1024 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1025 property: &'static str,
1026 ) -> SubtrActorResult<&'b (boxcars::Attribute, usize)> {
1027 let attribute_object_id = self.get_object_id_for_key(property)?;
1028 map.get(attribute_object_id).ok_or_else(|| {
1029 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState { property })
1030 })
1031 }
1032
1033 fn find_ball_actor(&self) -> Option<boxcars::ActorId> {
1034 BALL_TYPES
1035 .iter()
1036 .filter_map(|ball_type| self.iter_actors_by_type(ball_type))
1037 .flat_map(|i| i)
1038 .map(|(actor_id, _)| actor_id.clone())
1039 .next()
1040 }
1041
1042 pub fn get_ball_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
1043 self.ball_actor_id.ok_or(SubtrActorError::new(
1044 SubtrActorErrorVariant::BallActorNotFound,
1045 ))
1046 }
1047
1048 pub fn get_metadata_actor_id(&self) -> SubtrActorResult<&boxcars::ActorId> {
1049 self.get_actor_ids_by_type(GAME_TYPE)?
1050 .iter()
1051 .next()
1052 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))
1053 }
1054
1055 pub fn get_player_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1056 self.player_to_actor_id
1057 .get(&player_id)
1058 .ok_or_else(|| {
1059 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1060 name: "ActorId",
1061 player_id: player_id.clone(),
1062 })
1063 })
1064 .cloned()
1065 }
1066
1067 pub fn get_car_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1068 self.player_to_car
1069 .get(&self.get_player_actor_id(player_id)?)
1070 .ok_or_else(|| {
1071 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1072 name: "Car",
1073 player_id: player_id.clone(),
1074 })
1075 })
1076 .cloned()
1077 }
1078
1079 pub fn get_car_connected_actor_id(
1080 &self,
1081 player_id: &PlayerId,
1082 map: &HashMap<boxcars::ActorId, boxcars::ActorId>,
1083 name: &'static str,
1084 ) -> SubtrActorResult<boxcars::ActorId> {
1085 map.get(&self.get_car_actor_id(player_id)?)
1086 .ok_or_else(|| {
1087 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1088 name,
1089 player_id: player_id.clone(),
1090 })
1091 })
1092 .cloned()
1093 }
1094
1095 pub fn get_boost_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1096 self.get_car_connected_actor_id(player_id, &self.car_to_boost, "Boost")
1097 }
1098
1099 pub fn get_jump_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1100 self.get_car_connected_actor_id(player_id, &self.car_to_jump, "Jump")
1101 }
1102
1103 pub fn get_double_jump_actor_id(
1104 &self,
1105 player_id: &PlayerId,
1106 ) -> SubtrActorResult<boxcars::ActorId> {
1107 self.get_car_connected_actor_id(player_id, &self.car_to_double_jump, "Double Jump")
1108 }
1109
1110 pub fn get_dodge_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1111 self.get_car_connected_actor_id(player_id, &self.car_to_dodge, "Dodge")
1112 }
1113
1114 pub fn get_actor_rigid_body(
1115 &self,
1116 actor_id: &boxcars::ActorId,
1117 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1118 get_attribute_and_updated!(
1119 self,
1120 &self.get_actor_state(&actor_id)?.attributes,
1121 RIGID_BODY_STATE_KEY,
1122 boxcars::Attribute::RigidBody
1123 )
1124 }
1125
1126 pub fn iter_player_ids_in_order(&self) -> impl Iterator<Item = &PlayerId> {
1129 self.team_zero.iter().chain(self.team_one.iter())
1130 }
1131
1132 pub fn player_count(&self) -> usize {
1133 self.iter_player_ids_in_order().count()
1134 }
1135
1136 fn iter_actors_by_type_err(
1137 &self,
1138 name: &'static str,
1139 ) -> SubtrActorResult<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1140 Ok(self.iter_actors_by_object_id(self.get_object_id_for_key(name)?))
1141 }
1142
1143 pub fn iter_actors_by_type(
1144 &self,
1145 name: &'static str,
1146 ) -> Option<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1147 self.iter_actors_by_type_err(name).ok()
1148 }
1149
1150 pub fn iter_actors_by_object_id<'b>(
1151 &'b self,
1152 object_id: &'b boxcars::ObjectId,
1153 ) -> impl Iterator<Item = (&'b boxcars::ActorId, &'b ActorState)> + 'b {
1154 let actor_ids = self
1155 .actor_state
1156 .actor_ids_by_type
1157 .get(object_id)
1158 .map(|v| &v[..])
1159 .unwrap_or_else(|| &EMPTY_ACTOR_IDS);
1160
1161 actor_ids
1162 .iter()
1163 .map(move |id| (id, self.actor_state.actor_states.get(id).unwrap()))
1166 }
1167
1168 pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
1172 get_actor_attribute_matching!(
1173 self,
1174 self.get_metadata_actor_id()?,
1175 SECONDS_REMAINING_KEY,
1176 boxcars::Attribute::Int
1177 )
1178 .cloned()
1179 }
1180
1181 pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
1183 let actor_id = self.get_ball_actor_id()?;
1184 get_actor_attribute_matching!(
1185 self,
1186 &actor_id,
1187 IGNORE_SYNCING_KEY,
1188 boxcars::Attribute::Boolean
1189 )
1190 .cloned()
1191 }
1192
1193 pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
1195 self.ball_actor_id
1196 .ok_or(SubtrActorError::new(
1197 SubtrActorErrorVariant::BallActorNotFound,
1198 ))
1199 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1200 }
1201
1202 pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
1205 Ok(self
1206 .get_ball_rigid_body()
1207 .map(|rb| !rb.sleeping)
1208 .unwrap_or(false))
1209 }
1210
1211 pub fn get_ball_rigid_body_and_updated(
1214 &self,
1215 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1216 self.ball_actor_id
1217 .ok_or(SubtrActorError::new(
1218 SubtrActorErrorVariant::BallActorNotFound,
1219 ))
1220 .and_then(|actor_id| {
1221 get_attribute_and_updated!(
1222 self,
1223 &self.get_actor_state(&actor_id)?.attributes,
1224 RIGID_BODY_STATE_KEY,
1225 boxcars::Attribute::RigidBody
1226 )
1227 })
1228 }
1229
1230 pub fn get_velocity_applied_ball_rigid_body(
1233 &self,
1234 target_time: f32,
1235 ) -> SubtrActorResult<boxcars::RigidBody> {
1236 let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
1237 self.velocities_applied_rigid_body(¤t_rigid_body, *frame_index, target_time)
1238 }
1239
1240 pub fn get_interpolated_ball_rigid_body(
1243 &self,
1244 time: f32,
1245 close_enough: f32,
1246 ) -> SubtrActorResult<boxcars::RigidBody> {
1247 self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
1248 }
1249
1250 pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1252 get_actor_attribute_matching!(
1253 self,
1254 &self.get_player_actor_id(player_id)?,
1255 PLAYER_NAME_KEY,
1256 boxcars::Attribute::String
1257 )
1258 .cloned()
1259 }
1260
1261 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1263 let team_actor_id = self
1264 .player_to_team
1265 .get(&self.get_player_actor_id(player_id)?)
1266 .ok_or_else(|| {
1267 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1268 player_id: player_id.clone(),
1269 })
1270 })?;
1271 let state = self.get_actor_state(team_actor_id)?;
1272 self.object_id_to_name
1273 .get(&state.object_id)
1274 .ok_or_else(|| {
1275 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1276 player_id: player_id.clone(),
1277 })
1278 })
1279 .cloned()
1280 }
1281
1282 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
1284 Ok(self
1285 .get_player_team_key(player_id)?
1286 .chars()
1287 .last()
1288 .ok_or_else(|| {
1289 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
1290 player_id: player_id.clone(),
1291 })
1292 })?
1293 == '0')
1294 }
1295
1296 pub fn get_player_rigid_body(
1298 &self,
1299 player_id: &PlayerId,
1300 ) -> SubtrActorResult<&boxcars::RigidBody> {
1301 self.get_car_actor_id(player_id)
1302 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1303 }
1304
1305 pub fn get_player_rigid_body_and_updated(
1309 &self,
1310 player_id: &PlayerId,
1311 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1312 self.get_car_actor_id(player_id).and_then(|actor_id| {
1313 get_attribute_and_updated!(
1314 self,
1315 &self.get_actor_state(&actor_id)?.attributes,
1316 RIGID_BODY_STATE_KEY,
1317 boxcars::Attribute::RigidBody
1318 )
1319 })
1320 }
1321
1322 pub fn get_velocity_applied_player_rigid_body(
1323 &self,
1324 player_id: &PlayerId,
1325 target_time: f32,
1326 ) -> SubtrActorResult<boxcars::RigidBody> {
1327 let (current_rigid_body, frame_index) =
1328 self.get_player_rigid_body_and_updated(player_id)?;
1329 self.velocities_applied_rigid_body(¤t_rigid_body, *frame_index, target_time)
1330 }
1331
1332 pub fn get_interpolated_player_rigid_body(
1333 &self,
1334 player_id: &PlayerId,
1335 time: f32,
1336 close_enough: f32,
1337 ) -> SubtrActorResult<boxcars::RigidBody> {
1338 self.get_interpolated_actor_rigid_body(
1339 &self.get_car_actor_id(player_id).unwrap(),
1340 time,
1341 close_enough,
1342 )
1343 }
1344
1345 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
1346 self.get_boost_actor_id(player_id).and_then(|actor_id| {
1347 let boost_state = self.get_actor_state(&actor_id)?;
1348 get_derived_attribute!(
1349 boost_state.derived_attributes,
1350 BOOST_AMOUNT_KEY,
1351 boxcars::Attribute::Float
1352 )
1353 .cloned()
1354 })
1355 }
1356
1357 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
1358 get_actor_attribute_matching!(
1359 self,
1360 &actor_id,
1361 COMPONENT_ACTIVE_KEY,
1362 boxcars::Attribute::Byte
1363 )
1364 .cloned()
1365 }
1366
1367 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1368 self.get_boost_actor_id(player_id)
1369 .and_then(|actor_id| self.get_component_active(&actor_id))
1370 }
1371
1372 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1373 self.get_jump_actor_id(player_id)
1374 .and_then(|actor_id| self.get_component_active(&actor_id))
1375 }
1376
1377 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1378 self.get_double_jump_actor_id(player_id)
1379 .and_then(|actor_id| self.get_component_active(&actor_id))
1380 }
1381
1382 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1383 self.get_dodge_actor_id(player_id)
1384 .and_then(|actor_id| self.get_component_active(&actor_id))
1385 }
1386
1387 pub fn map_attribute_keys(
1390 &self,
1391 hash_map: &HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1392 ) -> HashMap<String, boxcars::Attribute> {
1393 hash_map
1394 .iter()
1395 .map(|(k, (v, _updated))| {
1396 self.object_id_to_name
1397 .get(k)
1398 .map(|name| (name.clone(), v.clone()))
1399 .unwrap()
1400 })
1401 .collect()
1402 }
1403
1404 pub fn all_mappings_string(&self) -> String {
1405 let pairs = [
1406 ("player_to_car", &self.player_to_car),
1407 ("player_to_team", &self.player_to_team),
1408 ("car_to_boost", &self.car_to_boost),
1409 ("car_to_jump", &self.car_to_jump),
1410 ("car_to_double_jump", &self.car_to_double_jump),
1411 ("car_to_dodge", &self.car_to_dodge),
1412 ];
1413 let strings: Vec<_> = pairs
1414 .iter()
1415 .map(|(map_name, map)| format!("{:?}: {:?}", map_name, map))
1416 .collect();
1417 strings.join("\n")
1418 }
1419
1420 pub fn actor_state_string(&self, actor_id: &boxcars::ActorId) -> String {
1421 format!(
1422 "{:?}",
1423 self.get_actor_state(actor_id)
1424 .map(|s| self.map_attribute_keys(&s.attributes))
1425 )
1426 }
1427
1428 pub fn print_actors_by_id<'b>(&self, actor_ids: impl Iterator<Item = &'b boxcars::ActorId>) {
1429 actor_ids.for_each(|actor_id| {
1430 let state = self.get_actor_state(actor_id).unwrap();
1431 println!(
1432 "{:?}\n\n\n",
1433 self.object_id_to_name.get(&state.object_id).unwrap()
1434 );
1435 println!("{:?}", self.map_attribute_keys(&state.attributes))
1436 })
1437 }
1438
1439 pub fn print_actors_of_type(&self, actor_type: &'static str) {
1440 self.iter_actors_by_type(actor_type)
1441 .unwrap()
1442 .for_each(|(_actor_id, state)| {
1443 println!("{:?}", self.map_attribute_keys(&state.attributes));
1444 });
1445 }
1446
1447 pub fn print_actor_types(&self) {
1448 let types: Vec<_> = self
1449 .actor_state
1450 .actor_ids_by_type
1451 .keys()
1452 .filter_map(|id| self.object_id_to_name.get(id))
1453 .collect();
1454 println!("{:?}", types);
1455 }
1456
1457 pub fn print_all_actors(&self) {
1458 self.actor_state
1459 .actor_states
1460 .iter()
1461 .for_each(|(actor_id, _actor_state)| {
1462 println!("{:?}", self.actor_state_string(actor_id))
1463 })
1464 }
1465}