1use crate::*;
2use boxcars;
3use std::collections::HashMap;
4
5#[macro_export]
17macro_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
31#[macro_export]
40macro_rules! get_attribute_errors_expected {
41 ($self:ident, $map:expr, $prop:expr, $type:path) => {
42 $self
43 .get_attribute($map, $prop)
44 .and_then(|found| attribute_match!(found, $type))
45 };
46}
47
48macro_rules! get_attribute_and_updated {
61 ($self:ident, $map:expr, $prop:expr, $type:path) => {
62 $self
63 .get_attribute_and_updated($map, $prop)
64 .and_then(|(found, updated)| attribute_match!(found, $type).map(|v| (v, updated)))
65 };
66}
67
68macro_rules! get_actor_attribute_matching {
77 ($self:ident, $actor:expr, $prop:expr, $type:path) => {
78 $self
79 .get_actor_attribute($actor, $prop)
80 .and_then(|found| attribute_match!(found, $type))
81 };
82}
83
84macro_rules! get_derived_attribute {
93 ($map:expr, $key:expr, $type:path) => {
94 $map.get($key)
95 .ok_or_else(|| {
96 SubtrActorError::new(SubtrActorErrorVariant::DerivedKeyValueNotFound {
97 name: $key.to_string(),
98 })
99 })
100 .and_then(|found| attribute_match!(&found.0, $type))
101 };
102}
103
104fn get_actor_id_from_active_actor<T>(
105 _: T,
106 active_actor: &boxcars::ActiveActor,
107) -> boxcars::ActorId {
108 active_actor.actor
109}
110
111fn use_update_actor<T>(id: boxcars::ActorId, _: T) -> boxcars::ActorId {
112 id
113}
114
115pub struct ReplayProcessor<'a> {
147 pub replay: &'a boxcars::Replay,
148 pub actor_state: ActorStateModeler,
149 pub object_id_to_name: HashMap<boxcars::ObjectId, String>,
150 pub name_to_object_id: HashMap<String, boxcars::ObjectId>,
151 pub ball_actor_id: Option<boxcars::ActorId>,
152 pub team_zero: Vec<PlayerId>,
153 pub team_one: Vec<PlayerId>,
154 pub player_to_actor_id: HashMap<PlayerId, boxcars::ActorId>,
155 pub player_to_car: HashMap<boxcars::ActorId, boxcars::ActorId>,
156 pub player_to_team: HashMap<boxcars::ActorId, boxcars::ActorId>,
157 pub car_to_player: HashMap<boxcars::ActorId, boxcars::ActorId>,
158 pub car_to_boost: HashMap<boxcars::ActorId, boxcars::ActorId>,
159 pub car_to_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
160 pub car_to_double_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
161 pub car_to_dodge: HashMap<boxcars::ActorId, boxcars::ActorId>,
162 pub demolishes: Vec<DemolishInfo>,
163 known_demolishes: Vec<(DemolishAttribute, usize)>,
164 demolish_format: Option<DemolishFormat>,
165}
166
167impl<'a> ReplayProcessor<'a> {
168 pub fn new(replay: &'a boxcars::Replay) -> SubtrActorResult<Self> {
182 let mut object_id_to_name = HashMap::new();
183 let mut name_to_object_id = HashMap::new();
184 for (id, name) in replay.objects.iter().enumerate() {
185 let object_id = boxcars::ObjectId(id as i32);
186 object_id_to_name.insert(object_id, name.clone());
187 name_to_object_id.insert(name.clone(), object_id);
188 }
189 let mut processor = Self {
190 actor_state: ActorStateModeler::new(),
191 replay,
192 object_id_to_name,
193 name_to_object_id,
194 team_zero: Vec::new(),
195 team_one: Vec::new(),
196 ball_actor_id: None,
197 player_to_car: HashMap::new(),
198 player_to_team: HashMap::new(),
199 player_to_actor_id: HashMap::new(),
200 car_to_player: HashMap::new(),
201 car_to_boost: HashMap::new(),
202 car_to_jump: HashMap::new(),
203 car_to_double_jump: HashMap::new(),
204 car_to_dodge: HashMap::new(),
205 demolishes: Vec::new(),
206 known_demolishes: Vec::new(),
207 demolish_format: None,
208 };
209 processor
210 .set_player_order_from_headers()
211 .or_else(|_| processor.set_player_order_from_frames())?;
212
213 Ok(processor)
214 }
215
216 pub fn process<H: Collector>(&mut self, handler: &mut H) -> SubtrActorResult<()> {
238 let mut target_time = TimeAdvance::NextFrame;
241 for (index, frame) in self
242 .replay
243 .network_frames
244 .as_ref()
245 .ok_or(SubtrActorError::new(
246 SubtrActorErrorVariant::NoNetworkFrames,
247 ))?
248 .frames
249 .iter()
250 .enumerate()
251 {
252 self.actor_state.process_frame(frame, index)?;
254 self.update_mappings(frame)?;
255 self.update_ball_id(frame)?;
256 self.update_boost_amounts(frame, index)?;
257 self.update_demolishes(frame, index)?;
258
259 let mut current_time = match &target_time {
262 TimeAdvance::Time(t) => *t,
263 TimeAdvance::NextFrame => frame.time,
264 };
265
266 while current_time <= frame.time {
267 target_time = handler.process_frame(self, frame, index, current_time)?;
270 if let TimeAdvance::Time(new_target) = target_time {
276 current_time = new_target;
277 } else {
278 break;
279 }
280 }
281 }
282 Ok(())
283 }
284
285 pub fn process_all(&mut self, collectors: &mut [&mut dyn Collector]) -> SubtrActorResult<()> {
294 for (index, frame) in self
295 .replay
296 .network_frames
297 .as_ref()
298 .ok_or(SubtrActorError::new(
299 SubtrActorErrorVariant::NoNetworkFrames,
300 ))?
301 .frames
302 .iter()
303 .enumerate()
304 {
305 self.actor_state.process_frame(frame, index)?;
306 self.update_mappings(frame)?;
307 self.update_ball_id(frame)?;
308 self.update_boost_amounts(frame, index)?;
309 self.update_demolishes(frame, index)?;
310
311 for collector in collectors.iter_mut() {
312 collector.process_frame(self, frame, index, frame.time)?;
313 }
314 }
315 Ok(())
316 }
317
318 pub fn reset(&mut self) {
320 self.player_to_car = HashMap::new();
321 self.player_to_team = HashMap::new();
322 self.player_to_actor_id = HashMap::new();
323 self.car_to_player = HashMap::new();
324 self.car_to_boost = HashMap::new();
325 self.car_to_jump = HashMap::new();
326 self.car_to_double_jump = HashMap::new();
327 self.car_to_dodge = HashMap::new();
328 self.actor_state = ActorStateModeler::new();
329 self.demolishes = Vec::new();
330 self.known_demolishes = Vec::new();
331 self.demolish_format = None;
332 }
333
334 fn set_player_order_from_headers(&mut self) -> SubtrActorResult<()> {
335 let _player_stats = self
336 .replay
337 .properties
338 .iter()
339 .find(|(key, _)| key == "PlayerStats")
340 .ok_or_else(|| {
341 SubtrActorError::new(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
342 })?;
343 SubtrActorError::new_result(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
345 }
346
347 pub fn process_long_enough_to_get_actor_ids(&mut self) -> SubtrActorResult<()> {
364 let mut handler = |_p: &ReplayProcessor, _f: &boxcars::Frame, n: usize, _current_time| {
365 if n > 10 * 30 {
367 SubtrActorError::new_result(SubtrActorErrorVariant::FinishProcessingEarly)
368 } else {
369 Ok(TimeAdvance::NextFrame)
370 }
371 };
372 let process_result = self.process(&mut handler);
373 if let Some(SubtrActorErrorVariant::FinishProcessingEarly) =
374 process_result.as_ref().err().map(|e| e.variant.clone())
375 {
376 Ok(())
377 } else {
378 process_result
379 }
380 }
381
382 fn set_player_order_from_frames(&mut self) -> SubtrActorResult<()> {
383 self.process_long_enough_to_get_actor_ids()?;
384 let player_to_team_0: HashMap<PlayerId, bool> = self
385 .player_to_actor_id
386 .keys()
387 .filter_map(|player_id| {
388 self.get_player_is_team_0(player_id)
389 .ok()
390 .map(|is_team_0| (player_id.clone(), is_team_0))
391 })
392 .collect();
393
394 let (team_zero, team_one): (Vec<_>, Vec<_>) = player_to_team_0
395 .keys()
396 .cloned()
397 .partition(|player_id| *player_to_team_0.get(player_id).unwrap());
399
400 self.team_zero = team_zero;
401 self.team_one = team_one;
402
403 self.team_zero
404 .sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
405 self.team_one
406 .sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
407
408 self.reset();
409 Ok(())
410 }
411
412 pub fn check_player_id_set(&self) -> SubtrActorResult<()> {
413 let known_players =
414 std::collections::HashSet::<_>::from_iter(self.player_to_actor_id.keys());
415 let original_players =
416 std::collections::HashSet::<_>::from_iter(self.iter_player_ids_in_order());
417
418 if original_players != known_players {
419 SubtrActorError::new_result(SubtrActorErrorVariant::InconsistentPlayerSet {
420 found: known_players.into_iter().cloned().collect(),
421 original: original_players.into_iter().cloned().collect(),
422 })
423 } else {
424 Ok(())
425 }
426 }
427
428 pub fn process_and_get_replay_meta(&mut self) -> SubtrActorResult<ReplayMeta> {
437 if self.player_to_actor_id.is_empty() {
438 self.process_long_enough_to_get_actor_ids()?;
439 }
440 self.get_replay_meta()
441 }
442
443 pub fn get_replay_meta(&self) -> SubtrActorResult<ReplayMeta> {
450 let empty_player_stats = Vec::new();
451 let player_stats = if let Some((_, boxcars::HeaderProp::Array(per_player))) = self
452 .replay
453 .properties
454 .iter()
455 .find(|(key, _)| key == "PlayerStats")
456 {
457 per_player
458 } else {
459 &empty_player_stats
460 };
461 let known_count = self.iter_player_ids_in_order().count();
462 if player_stats.len() != known_count {
463 log::warn!(
464 "Replay does not have player stats for all players. encountered {:?} {:?}",
465 known_count,
466 player_stats.len()
467 )
468 }
469 let get_player_info = |player_id| {
470 let name = self.get_player_name(player_id)?;
471 let stats = find_player_stats(player_id, &name, player_stats).ok();
472 Ok(PlayerInfo {
473 name,
474 stats,
475 remote_id: player_id.clone(),
476 })
477 };
478 let team_zero: SubtrActorResult<Vec<PlayerInfo>> =
479 self.team_zero.iter().map(get_player_info).collect();
480 let team_one: SubtrActorResult<Vec<PlayerInfo>> =
481 self.team_one.iter().map(get_player_info).collect();
482 Ok(ReplayMeta {
483 team_zero: team_zero?,
484 team_one: team_one?,
485 all_headers: self.replay.properties.clone(),
486 })
487 }
488
489 pub fn find_update_in_direction(
525 &self,
526 current_index: usize,
527 actor_id: &boxcars::ActorId,
528 object_id: &boxcars::ObjectId,
529 direction: SearchDirection,
530 ) -> SubtrActorResult<(boxcars::Attribute, usize)> {
531 let frames = self
532 .replay
533 .network_frames
534 .as_ref()
535 .ok_or(SubtrActorError::new(
536 SubtrActorErrorVariant::NoNetworkFrames,
537 ))?;
538
539 let predicate = |frame: &boxcars::Frame| {
540 frame
541 .updated_actors
542 .iter()
543 .find(|update| &update.actor_id == actor_id && &update.object_id == object_id)
544 .map(|update| &update.attribute)
545 .cloned()
546 };
547
548 match util::find_in_direction(&frames.frames, current_index, direction, predicate) {
549 Some((index, attribute)) => Ok((attribute, index)),
550 None => SubtrActorError::new_result(SubtrActorErrorVariant::NoUpdateAfterFrame {
551 actor_id: *actor_id,
552 object_id: *object_id,
553 frame_index: current_index,
554 }),
555 }
556 }
557
558 fn update_mappings(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
590 for update in frame.updated_actors.iter() {
591 macro_rules! maintain_link {
592 ($map:expr, $actor_type:expr, $attr:expr, $get_key: expr, $get_value: expr, $type:path) => {{
593 if &update.object_id == self.get_object_id_for_key(&$attr)? {
594 if self
595 .get_actor_ids_by_type($actor_type)?
596 .iter()
597 .any(|id| id == &update.actor_id)
598 {
599 let value = get_actor_attribute_matching!(
600 self,
601 &update.actor_id,
602 $attr,
603 $type
604 )?;
605 let _key = $get_key(update.actor_id, value);
606 let _new_value = $get_value(update.actor_id, value);
607 let _old_value = $map.insert(
608 $get_key(update.actor_id, value),
609 $get_value(update.actor_id, value),
610 );
611 }
612 }
613 }};
614 }
615 macro_rules! maintain_actor_link {
616 ($map:expr, $actor_type:expr, $attr:expr) => {
617 maintain_link!(
618 $map,
619 $actor_type,
620 $attr,
621 get_actor_id_from_active_actor,
624 use_update_actor,
625 boxcars::Attribute::ActiveActor
626 )
627 };
628 }
629 macro_rules! maintain_vehicle_key_link {
630 ($map:expr, $actor_type:expr) => {
631 maintain_actor_link!($map, $actor_type, VEHICLE_KEY)
632 };
633 }
634 maintain_link!(
635 self.player_to_actor_id,
636 PLAYER_TYPE,
637 UNIQUE_ID_KEY,
638 |_, unique_id: &boxcars::UniqueId| unique_id.remote_id.clone(),
639 use_update_actor,
640 boxcars::Attribute::UniqueId
641 );
642 maintain_link!(
643 self.player_to_team,
644 PLAYER_TYPE,
645 TEAM_KEY,
646 use_update_actor,
648 get_actor_id_from_active_actor,
649 boxcars::Attribute::ActiveActor
650 );
651 maintain_actor_link!(self.player_to_car, CAR_TYPE, PLAYER_REPLICATION_KEY);
652 maintain_link!(
655 self.car_to_player,
656 CAR_TYPE,
657 PLAYER_REPLICATION_KEY,
658 use_update_actor,
659 get_actor_id_from_active_actor,
660 boxcars::Attribute::ActiveActor
661 );
662 maintain_vehicle_key_link!(self.car_to_boost, BOOST_TYPE);
663 maintain_vehicle_key_link!(self.car_to_dodge, DODGE_TYPE);
664 maintain_vehicle_key_link!(self.car_to_jump, JUMP_TYPE);
665 maintain_vehicle_key_link!(self.car_to_double_jump, DOUBLE_JUMP_TYPE);
666 }
667
668 for actor_id in frame.deleted_actors.iter() {
669 if let Some(car_id) = self.player_to_car.remove(actor_id) {
670 log::info!("Player actor {actor_id:?} deleted, car id: {car_id:?}.");
671 }
672 }
673
674 Ok(())
675 }
676
677 fn update_ball_id(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
678 if let Some(actor_id) = self.ball_actor_id {
680 if frame.deleted_actors.contains(&actor_id) {
681 self.ball_actor_id = None;
682 }
683 } else {
684 self.ball_actor_id = self.find_ball_actor();
685 if self.ball_actor_id.is_some() {
686 return self.update_ball_id(frame);
687 }
688 }
689 Ok(())
690 }
691
692 fn update_boost_amounts(
710 &mut self,
711 frame: &boxcars::Frame,
712 frame_index: usize,
713 ) -> SubtrActorResult<()> {
714 let updates: Vec<_> = self
715 .iter_actors_by_type_err(BOOST_TYPE)?
716 .map(|(actor_id, actor_state)| {
717 let (actor_amount_value, last_value, _, derived_value, is_active) =
718 self.get_current_boost_values(actor_state);
719 let mut current_value = if actor_amount_value == last_value {
720 derived_value
723 } else {
724 actor_amount_value.into()
726 };
727 if is_active {
728 current_value -= frame.delta * BOOST_USED_PER_SECOND;
729 }
730 (*actor_id, current_value.max(0.0), actor_amount_value)
731 })
732 .collect();
733
734 for (actor_id, current_value, new_last_value) in updates {
735 let derived_attributes = &mut self
736 .actor_state
737 .actor_states
738 .get_mut(&actor_id)
739 .unwrap()
741 .derived_attributes;
742
743 derived_attributes.insert(
744 LAST_BOOST_AMOUNT_KEY.to_string(),
745 (boxcars::Attribute::Byte(new_last_value), frame_index),
746 );
747 derived_attributes.insert(
748 BOOST_AMOUNT_KEY.to_string(),
749 (boxcars::Attribute::Float(current_value), frame_index),
750 );
751 }
752 Ok(())
753 }
754
755 fn get_current_boost_values(&self, actor_state: &ActorState) -> (u8, u8, u8, f32, bool) {
776 let amount_value = if let Ok(boxcars::Attribute::ReplicatedBoost(replicated_boost)) =
778 self.get_attribute(&actor_state.attributes, BOOST_REPLICATED_KEY)
779 {
780 replicated_boost.boost_amount
781 } else {
782 get_attribute_errors_expected!(
784 self,
785 &actor_state.attributes,
786 BOOST_AMOUNT_KEY,
787 boxcars::Attribute::Byte
788 )
789 .cloned()
790 .unwrap_or(0)
791 };
792 let active_value = get_attribute_errors_expected!(
793 self,
794 &actor_state.attributes,
795 COMPONENT_ACTIVE_KEY,
796 boxcars::Attribute::Byte
797 )
798 .cloned()
799 .unwrap_or(0);
800 let is_active = active_value % 2 == 1;
801 let derived_value = actor_state
802 .derived_attributes
803 .get(BOOST_AMOUNT_KEY)
804 .cloned()
805 .and_then(|v| attribute_match!(v.0, boxcars::Attribute::Float).ok())
806 .unwrap_or(0.0);
807 let last_boost_amount = attribute_match!(
808 actor_state
809 .derived_attributes
810 .get(LAST_BOOST_AMOUNT_KEY)
811 .cloned()
812 .map(|v| v.0)
813 .unwrap_or_else(|| boxcars::Attribute::Byte(amount_value)),
814 boxcars::Attribute::Byte
815 )
816 .unwrap_or(0);
817 (
818 amount_value,
819 last_boost_amount,
820 active_value,
821 derived_value,
822 is_active,
823 )
824 }
825
826 fn update_demolishes(
827 &mut self,
828 frame: &boxcars::Frame,
829 frame_index: usize,
830 ) -> SubtrActorResult<()> {
831 if self.demolish_format.is_none() {
832 self.demolish_format = self.detect_demolish_format();
833 }
834
835 let new_demolishes: Vec<_> = self.get_active_demos()?.collect();
836
837 for demolish in new_demolishes {
838 if self.demolish_is_known(&demolish, frame_index) {
839 continue;
840 }
841 self.known_demolishes.push((demolish.clone(), frame_index));
842 match self.build_demolish_info(&demolish, frame, frame_index) {
843 Ok(demolish_info) => self.demolishes.push(demolish_info),
844 Err(_e) => {
845 log::warn!(
846 "Error building demolish info: {}; \
847 attacker_car={}, victim_car={}, attacker={}, victim={}",
848 _e.variant,
849 demolish.attacker_actor_id(),
850 demolish.victim_actor_id(),
851 self.car_to_player
852 .get(&demolish.attacker_actor_id())
853 .unwrap_or(&boxcars::ActorId(-1)),
854 self.car_to_player
855 .get(&demolish.victim_actor_id())
856 .unwrap_or(&boxcars::ActorId(-1)),
857 );
858 }
859 }
860 }
861
862 Ok(())
863 }
864
865 fn build_demolish_info(
866 &self,
867 demo: &DemolishAttribute,
868 frame: &boxcars::Frame,
869 frame_index: usize,
870 ) -> SubtrActorResult<DemolishInfo> {
871 let attacker = self.get_player_id_from_car_id(&demo.attacker_actor_id())?;
872 let victim = self.get_player_id_from_car_id(&demo.victim_actor_id())?;
873 let (current_rigid_body, _) = self.get_player_rigid_body_and_updated(&victim)?;
874 Ok(DemolishInfo {
875 time: frame.time,
876 seconds_remaining: self.get_seconds_remaining()?,
877 frame: frame_index,
878 attacker,
879 victim,
880 attacker_velocity: demo.attacker_velocity(),
881 victim_velocity: demo.victim_velocity(),
882 victim_location: current_rigid_body.location,
883 })
884 }
885
886 pub fn get_player_id_from_car_id(
889 &self,
890 actor_id: &boxcars::ActorId,
891 ) -> SubtrActorResult<PlayerId> {
892 self.get_player_id_from_actor_id(&self.get_player_actor_id_from_car_actor_id(actor_id)?)
893 }
894
895 fn get_player_id_from_actor_id(
896 &self,
897 actor_id: &boxcars::ActorId,
898 ) -> SubtrActorResult<PlayerId> {
899 for (player_id, player_actor_id) in self.player_to_actor_id.iter() {
900 if actor_id == player_actor_id {
901 return Ok(player_id.clone());
902 }
903 }
904 SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
905 actor_id: *actor_id,
906 })
907 }
908
909 fn get_player_actor_id_from_car_actor_id(
910 &self,
911 actor_id: &boxcars::ActorId,
912 ) -> SubtrActorResult<boxcars::ActorId> {
913 self.car_to_player.get(actor_id).copied().ok_or_else(|| {
914 SubtrActorError::new(SubtrActorErrorVariant::NoMatchingPlayerId {
915 actor_id: *actor_id,
916 })
917 })
918 }
919
920 fn demolish_is_known(&self, demo: &DemolishAttribute, frame_index: usize) -> bool {
921 self.known_demolishes
922 .iter()
923 .any(|(existing, existing_frame_index)| {
924 existing == demo
925 && frame_index
926 .checked_sub(*existing_frame_index)
927 .or_else(|| existing_frame_index.checked_sub(frame_index))
928 .unwrap()
929 < MAX_DEMOLISH_KNOWN_FRAMES_PASSED
930 })
931 }
932
933 pub fn get_demolish_format(&self) -> Option<DemolishFormat> {
935 self.demolish_format
936 }
937
938 pub fn detect_demolish_format(&self) -> Option<DemolishFormat> {
940 let actors = self.iter_actors_by_type_err(CAR_TYPE).ok()?;
941 for (_actor_id, state) in actors {
942 if get_attribute_errors_expected!(
943 self,
944 &state.attributes,
945 DEMOLISH_EXTENDED_KEY,
946 boxcars::Attribute::DemolishExtended
947 )
948 .is_ok()
949 {
950 return Some(DemolishFormat::Extended);
951 }
952 if get_attribute_errors_expected!(
953 self,
954 &state.attributes,
955 DEMOLISH_GOAL_EXPLOSION_KEY,
956 boxcars::Attribute::DemolishFx
957 )
958 .is_ok()
959 {
960 return Some(DemolishFormat::Fx);
961 }
962 }
963 None
964 }
965
966 pub fn get_active_demos(
970 &self,
971 ) -> SubtrActorResult<impl Iterator<Item = DemolishAttribute> + '_> {
972 let format = self.demolish_format;
973 let actors: Vec<_> = self.iter_actors_by_type_err(CAR_TYPE)?.collect();
974 Ok(actors
975 .into_iter()
976 .filter_map(move |(_actor_id, state)| match format {
977 Some(DemolishFormat::Extended) => get_attribute_errors_expected!(
978 self,
979 &state.attributes,
980 DEMOLISH_EXTENDED_KEY,
981 boxcars::Attribute::DemolishExtended
982 )
983 .ok()
984 .map(|demo| DemolishAttribute::Extended(**demo)),
985 Some(DemolishFormat::Fx) => get_attribute_errors_expected!(
986 self,
987 &state.attributes,
988 DEMOLISH_GOAL_EXPLOSION_KEY,
989 boxcars::Attribute::DemolishFx
990 )
991 .ok()
992 .map(|demo| DemolishAttribute::Fx(**demo)),
993 None => None,
994 }))
995 }
996
997 fn get_frame(&self, frame_index: usize) -> SubtrActorResult<&boxcars::Frame> {
1000 self.replay
1001 .network_frames
1002 .as_ref()
1003 .ok_or(SubtrActorError::new(
1004 SubtrActorErrorVariant::NoNetworkFrames,
1005 ))?
1006 .frames
1007 .get(frame_index)
1008 .ok_or(SubtrActorError::new(
1009 SubtrActorErrorVariant::FrameIndexOutOfBounds,
1010 ))
1011 }
1012
1013 fn velocities_applied_rigid_body(
1014 &self,
1015 rigid_body: &boxcars::RigidBody,
1016 rb_frame_index: usize,
1017 target_time: f32,
1018 ) -> SubtrActorResult<boxcars::RigidBody> {
1019 let rb_frame = self.get_frame(rb_frame_index)?;
1020 let interpolation_amount = target_time - rb_frame.time;
1021 Ok(apply_velocities_to_rigid_body(
1022 rigid_body,
1023 interpolation_amount,
1024 ))
1025 }
1026
1027 pub fn get_interpolated_actor_rigid_body(
1058 &self,
1059 actor_id: &boxcars::ActorId,
1060 time: f32,
1061 close_enough: f32,
1062 ) -> SubtrActorResult<boxcars::RigidBody> {
1063 let (frame_body, frame_index) = self.get_actor_rigid_body(actor_id)?;
1064 let frame_time = self.get_frame(*frame_index)?.time;
1065 let time_and_frame_difference = time - frame_time;
1066
1067 if (time_and_frame_difference).abs() <= close_enough.abs() {
1068 return Ok(*frame_body);
1069 }
1070
1071 let search_direction = if time_and_frame_difference > 0.0 {
1072 util::SearchDirection::Forward
1073 } else {
1074 util::SearchDirection::Backward
1075 };
1076
1077 let object_id = self.get_object_id_for_key(RIGID_BODY_STATE_KEY)?;
1078
1079 let (attribute, found_frame) =
1080 self.find_update_in_direction(*frame_index, actor_id, object_id, search_direction)?;
1081 let found_time = self.get_frame(found_frame)?.time;
1082
1083 let found_body = attribute_match!(attribute, boxcars::Attribute::RigidBody)?;
1084
1085 if (found_time - time).abs() <= close_enough {
1086 return Ok(found_body);
1087 }
1088
1089 let (start_body, start_time, end_body, end_time) = match search_direction {
1090 util::SearchDirection::Forward => (frame_body, frame_time, &found_body, found_time),
1091 util::SearchDirection::Backward => (&found_body, found_time, frame_body, frame_time),
1092 };
1093
1094 util::get_interpolated_rigid_body(start_body, start_time, end_body, end_time, time)
1095 }
1096
1097 pub fn get_object_id_for_key(
1100 &self,
1101 name: &'static str,
1102 ) -> SubtrActorResult<&boxcars::ObjectId> {
1103 self.name_to_object_id
1104 .get(name)
1105 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
1106 }
1107
1108 pub fn get_actor_ids_by_type(
1109 &self,
1110 name: &'static str,
1111 ) -> SubtrActorResult<&[boxcars::ActorId]> {
1112 self.get_object_id_for_key(name)
1113 .map(|object_id| self.get_actor_ids_by_object_id(object_id))
1114 }
1115
1116 fn get_actor_ids_by_object_id(&self, object_id: &boxcars::ObjectId) -> &[boxcars::ActorId] {
1117 self.actor_state
1118 .actor_ids_by_type
1119 .get(object_id)
1120 .map(|v| &v[..])
1121 .unwrap_or_else(|| &EMPTY_ACTOR_IDS)
1122 }
1123
1124 fn get_actor_state(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<&ActorState> {
1125 self.actor_state.actor_states.get(actor_id).ok_or_else(|| {
1126 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
1127 actor_id: *actor_id,
1128 })
1129 })
1130 }
1131
1132 fn get_actor_attribute<'b>(
1133 &'b self,
1134 actor_id: &boxcars::ActorId,
1135 property: &'static str,
1136 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1137 self.get_attribute(&self.get_actor_state(actor_id)?.attributes, property)
1138 }
1139
1140 pub fn get_attribute<'b>(
1141 &'b self,
1142 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1143 property: &'static str,
1144 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1145 self.get_attribute_and_updated(map, property).map(|v| &v.0)
1146 }
1147
1148 pub fn get_attribute_and_updated<'b>(
1149 &'b self,
1150 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1151 property: &'static str,
1152 ) -> SubtrActorResult<&'b (boxcars::Attribute, usize)> {
1153 let attribute_object_id = self.get_object_id_for_key(property)?;
1154 map.get(attribute_object_id).ok_or_else(|| {
1155 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState { property })
1156 })
1157 }
1158
1159 fn find_ball_actor(&self) -> Option<boxcars::ActorId> {
1160 BALL_TYPES
1161 .iter()
1162 .filter_map(|ball_type| self.iter_actors_by_type(ball_type))
1163 .flatten()
1164 .map(|(actor_id, _)| *actor_id)
1165 .next()
1166 }
1167
1168 pub fn get_ball_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
1169 self.ball_actor_id.ok_or(SubtrActorError::new(
1170 SubtrActorErrorVariant::BallActorNotFound,
1171 ))
1172 }
1173
1174 pub fn get_metadata_actor_id(&self) -> SubtrActorResult<&boxcars::ActorId> {
1175 self.get_actor_ids_by_type(GAME_TYPE)?
1176 .iter()
1177 .next()
1178 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))
1179 }
1180
1181 pub fn get_player_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1182 self.player_to_actor_id
1183 .get(player_id)
1184 .ok_or_else(|| {
1185 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1186 name: "ActorId",
1187 player_id: player_id.clone(),
1188 })
1189 })
1190 .cloned()
1191 }
1192
1193 pub fn get_car_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1194 self.player_to_car
1195 .get(&self.get_player_actor_id(player_id)?)
1196 .ok_or_else(|| {
1197 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1198 name: "Car",
1199 player_id: player_id.clone(),
1200 })
1201 })
1202 .cloned()
1203 }
1204
1205 pub fn get_car_connected_actor_id(
1206 &self,
1207 player_id: &PlayerId,
1208 map: &HashMap<boxcars::ActorId, boxcars::ActorId>,
1209 name: &'static str,
1210 ) -> SubtrActorResult<boxcars::ActorId> {
1211 map.get(&self.get_car_actor_id(player_id)?)
1212 .ok_or_else(|| {
1213 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1214 name,
1215 player_id: player_id.clone(),
1216 })
1217 })
1218 .cloned()
1219 }
1220
1221 pub fn get_boost_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1222 self.get_car_connected_actor_id(player_id, &self.car_to_boost, "Boost")
1223 }
1224
1225 pub fn get_jump_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1226 self.get_car_connected_actor_id(player_id, &self.car_to_jump, "Jump")
1227 }
1228
1229 pub fn get_double_jump_actor_id(
1230 &self,
1231 player_id: &PlayerId,
1232 ) -> SubtrActorResult<boxcars::ActorId> {
1233 self.get_car_connected_actor_id(player_id, &self.car_to_double_jump, "Double Jump")
1234 }
1235
1236 pub fn get_dodge_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1237 self.get_car_connected_actor_id(player_id, &self.car_to_dodge, "Dodge")
1238 }
1239
1240 pub fn get_actor_rigid_body(
1241 &self,
1242 actor_id: &boxcars::ActorId,
1243 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1244 get_attribute_and_updated!(
1245 self,
1246 &self.get_actor_state(actor_id)?.attributes,
1247 RIGID_BODY_STATE_KEY,
1248 boxcars::Attribute::RigidBody
1249 )
1250 }
1251
1252 pub fn iter_player_ids_in_order(&self) -> impl Iterator<Item = &PlayerId> {
1255 self.team_zero.iter().chain(self.team_one.iter())
1256 }
1257
1258 pub fn player_count(&self) -> usize {
1259 self.iter_player_ids_in_order().count()
1260 }
1261
1262 pub fn get_player_names(&self) -> HashMap<PlayerId, String> {
1264 self.iter_player_ids_in_order()
1265 .filter_map(|player_id| {
1266 self.get_player_name(player_id)
1267 .ok()
1268 .map(|name| (player_id.clone(), name))
1269 })
1270 .collect()
1271 }
1272
1273 fn iter_actors_by_type_err(
1274 &self,
1275 name: &'static str,
1276 ) -> SubtrActorResult<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1277 Ok(self.iter_actors_by_object_id(self.get_object_id_for_key(name)?))
1278 }
1279
1280 pub fn iter_actors_by_type(
1281 &self,
1282 name: &'static str,
1283 ) -> Option<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1284 self.iter_actors_by_type_err(name).ok()
1285 }
1286
1287 pub fn iter_actors_by_object_id<'b>(
1288 &'b self,
1289 object_id: &'b boxcars::ObjectId,
1290 ) -> impl Iterator<Item = (&'b boxcars::ActorId, &'b ActorState)> + 'b {
1291 let actor_ids = self
1292 .actor_state
1293 .actor_ids_by_type
1294 .get(object_id)
1295 .map(|v| &v[..])
1296 .unwrap_or_else(|| &EMPTY_ACTOR_IDS);
1297
1298 actor_ids
1299 .iter()
1300 .map(move |id| (id, self.actor_state.actor_states.get(id).unwrap()))
1303 }
1304
1305 pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
1309 get_actor_attribute_matching!(
1310 self,
1311 self.get_metadata_actor_id()?,
1312 SECONDS_REMAINING_KEY,
1313 boxcars::Attribute::Int
1314 )
1315 .cloned()
1316 }
1317
1318 pub fn get_replicated_state_name(&self) -> SubtrActorResult<i32> {
1325 get_actor_attribute_matching!(
1326 self,
1327 self.get_metadata_actor_id()?,
1328 REPLICATED_STATE_NAME_KEY,
1329 boxcars::Attribute::Int
1330 )
1331 .cloned()
1332 }
1333
1334 pub fn get_replicated_game_state_time_remaining(&self) -> SubtrActorResult<i32> {
1343 get_actor_attribute_matching!(
1344 self,
1345 self.get_metadata_actor_id()?,
1346 REPLICATED_GAME_STATE_TIME_REMAINING_KEY,
1347 boxcars::Attribute::Int
1348 )
1349 .cloned()
1350 }
1351
1352 pub fn get_ball_has_been_hit(&self) -> SubtrActorResult<bool> {
1357 get_actor_attribute_matching!(
1358 self,
1359 self.get_metadata_actor_id()?,
1360 BALL_HAS_BEEN_HIT_KEY,
1361 boxcars::Attribute::Boolean
1362 )
1363 .cloned()
1364 }
1365
1366 pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
1368 let actor_id = self.get_ball_actor_id()?;
1369 get_actor_attribute_matching!(
1370 self,
1371 &actor_id,
1372 IGNORE_SYNCING_KEY,
1373 boxcars::Attribute::Boolean
1374 )
1375 .cloned()
1376 }
1377
1378 pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
1380 self.ball_actor_id
1381 .ok_or(SubtrActorError::new(
1382 SubtrActorErrorVariant::BallActorNotFound,
1383 ))
1384 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1385 }
1386
1387 pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
1390 Ok(self
1391 .get_ball_rigid_body()
1392 .map(|rb| !rb.sleeping)
1393 .unwrap_or(false))
1394 }
1395
1396 pub fn get_ball_rigid_body_and_updated(
1399 &self,
1400 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1401 self.ball_actor_id
1402 .ok_or(SubtrActorError::new(
1403 SubtrActorErrorVariant::BallActorNotFound,
1404 ))
1405 .and_then(|actor_id| {
1406 get_attribute_and_updated!(
1407 self,
1408 &self.get_actor_state(&actor_id)?.attributes,
1409 RIGID_BODY_STATE_KEY,
1410 boxcars::Attribute::RigidBody
1411 )
1412 })
1413 }
1414
1415 pub fn get_velocity_applied_ball_rigid_body(
1418 &self,
1419 target_time: f32,
1420 ) -> SubtrActorResult<boxcars::RigidBody> {
1421 let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
1422 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
1423 }
1424
1425 pub fn get_interpolated_ball_rigid_body(
1428 &self,
1429 time: f32,
1430 close_enough: f32,
1431 ) -> SubtrActorResult<boxcars::RigidBody> {
1432 self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
1433 }
1434
1435 pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1437 get_actor_attribute_matching!(
1438 self,
1439 &self.get_player_actor_id(player_id)?,
1440 PLAYER_NAME_KEY,
1441 boxcars::Attribute::String
1442 )
1443 .cloned()
1444 }
1445
1446 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1448 let team_actor_id = self
1449 .player_to_team
1450 .get(&self.get_player_actor_id(player_id)?)
1451 .ok_or_else(|| {
1452 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1453 player_id: player_id.clone(),
1454 })
1455 })?;
1456 let state = self.get_actor_state(team_actor_id)?;
1457 self.object_id_to_name
1458 .get(&state.object_id)
1459 .ok_or_else(|| {
1460 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1461 player_id: player_id.clone(),
1462 })
1463 })
1464 .cloned()
1465 }
1466
1467 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
1469 Ok(self
1470 .get_player_team_key(player_id)?
1471 .chars()
1472 .last()
1473 .ok_or_else(|| {
1474 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
1475 player_id: player_id.clone(),
1476 })
1477 })?
1478 == '0')
1479 }
1480
1481 pub fn get_player_rigid_body(
1483 &self,
1484 player_id: &PlayerId,
1485 ) -> SubtrActorResult<&boxcars::RigidBody> {
1486 self.get_car_actor_id(player_id)
1487 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1488 }
1489
1490 pub fn get_player_rigid_body_and_updated(
1494 &self,
1495 player_id: &PlayerId,
1496 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1497 self.get_car_actor_id(player_id).and_then(|actor_id| {
1498 get_attribute_and_updated!(
1499 self,
1500 &self.get_actor_state(&actor_id)?.attributes,
1501 RIGID_BODY_STATE_KEY,
1502 boxcars::Attribute::RigidBody
1503 )
1504 })
1505 }
1506
1507 pub fn get_velocity_applied_player_rigid_body(
1508 &self,
1509 player_id: &PlayerId,
1510 target_time: f32,
1511 ) -> SubtrActorResult<boxcars::RigidBody> {
1512 let (current_rigid_body, frame_index) =
1513 self.get_player_rigid_body_and_updated(player_id)?;
1514 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
1515 }
1516
1517 pub fn get_interpolated_player_rigid_body(
1518 &self,
1519 player_id: &PlayerId,
1520 time: f32,
1521 close_enough: f32,
1522 ) -> SubtrActorResult<boxcars::RigidBody> {
1523 self.get_interpolated_actor_rigid_body(
1524 &self.get_car_actor_id(player_id).unwrap(),
1525 time,
1526 close_enough,
1527 )
1528 }
1529
1530 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
1531 self.get_boost_actor_id(player_id).and_then(|actor_id| {
1532 let boost_state = self.get_actor_state(&actor_id)?;
1533 get_derived_attribute!(
1534 boost_state.derived_attributes,
1535 BOOST_AMOUNT_KEY,
1536 boxcars::Attribute::Float
1537 )
1538 .cloned()
1539 })
1540 }
1541
1542 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
1543 get_actor_attribute_matching!(
1544 self,
1545 &actor_id,
1546 COMPONENT_ACTIVE_KEY,
1547 boxcars::Attribute::Byte
1548 )
1549 .cloned()
1550 }
1551
1552 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1553 self.get_boost_actor_id(player_id)
1554 .and_then(|actor_id| self.get_component_active(&actor_id))
1555 }
1556
1557 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1558 self.get_jump_actor_id(player_id)
1559 .and_then(|actor_id| self.get_component_active(&actor_id))
1560 }
1561
1562 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1563 self.get_double_jump_actor_id(player_id)
1564 .and_then(|actor_id| self.get_component_active(&actor_id))
1565 }
1566
1567 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1568 self.get_dodge_actor_id(player_id)
1569 .and_then(|actor_id| self.get_component_active(&actor_id))
1570 }
1571
1572 pub fn map_attribute_keys(
1575 &self,
1576 hash_map: &HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1577 ) -> HashMap<String, boxcars::Attribute> {
1578 hash_map
1579 .iter()
1580 .map(|(k, (v, _updated))| {
1581 self.object_id_to_name
1582 .get(k)
1583 .map(|name| (name.clone(), v.clone()))
1584 .unwrap()
1585 })
1586 .collect()
1587 }
1588
1589 pub fn all_mappings_string(&self) -> String {
1590 let pairs = [
1591 ("player_to_car", &self.player_to_car),
1592 ("player_to_team", &self.player_to_team),
1593 ("car_to_player", &self.car_to_player),
1594 ("car_to_boost", &self.car_to_boost),
1595 ("car_to_jump", &self.car_to_jump),
1596 ("car_to_double_jump", &self.car_to_double_jump),
1597 ("car_to_dodge", &self.car_to_dodge),
1598 ];
1599 let mut strings: Vec<_> = pairs
1600 .iter()
1601 .map(|(map_name, map)| format!("{map_name:?}: {map:?}"))
1602 .collect();
1603 strings.push(format!("name_to_object_id: {:?}", &self.name_to_object_id));
1604 strings.join("\n")
1605 }
1606
1607 pub fn actor_state_string(&self, actor_id: &boxcars::ActorId) -> String {
1608 if let Ok(actor_state) = self.get_actor_state(actor_id) {
1609 format!("{:?}", self.map_attribute_keys(&actor_state.attributes))
1610 } else {
1611 String::from("error")
1612 }
1613 }
1614
1615 pub fn print_actors_by_id<'b>(&self, actor_ids: impl Iterator<Item = &'b boxcars::ActorId>) {
1616 actor_ids.for_each(|actor_id| {
1617 let state = self.get_actor_state(actor_id).unwrap();
1618 println!(
1619 "{:?}\n\n\n",
1620 self.object_id_to_name.get(&state.object_id).unwrap()
1621 );
1622 println!("{:?}", self.map_attribute_keys(&state.attributes))
1623 })
1624 }
1625
1626 pub fn print_actors_of_type(&self, actor_type: &'static str) {
1627 self.iter_actors_by_type(actor_type)
1628 .unwrap()
1629 .for_each(|(_actor_id, state)| {
1630 log::debug!("{:?}", self.map_attribute_keys(&state.attributes));
1631 });
1632 }
1633
1634 pub fn print_actor_types(&self) {
1635 let types: Vec<_> = self
1636 .actor_state
1637 .actor_ids_by_type
1638 .keys()
1639 .filter_map(|id| self.object_id_to_name.get(id))
1640 .collect();
1641 log::debug!("{types:?}");
1642 }
1643
1644 pub fn print_all_actors(&self) {
1645 self.actor_state
1646 .actor_states
1647 .iter()
1648 .for_each(|(actor_id, actor_state)| {
1649 log::debug!(
1650 "{}: {:?}",
1651 self.object_id_to_name
1652 .get(&actor_state.object_id)
1653 .unwrap_or(&String::from("unknown")),
1654 self.actor_state_string(actor_id)
1655 )
1656 });
1657 }
1658}