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 Ok(())
277 }
278
279 pub fn reset(&mut self) {
281 self.player_to_car = HashMap::new();
282 self.player_to_team = HashMap::new();
283 self.player_to_actor_id = HashMap::new();
284 self.car_to_boost = HashMap::new();
285 self.car_to_jump = HashMap::new();
286 self.car_to_double_jump = HashMap::new();
287 self.car_to_dodge = HashMap::new();
288 self.actor_state = ActorStateModeler::new();
289 self.demolishes = Vec::new();
290 self.known_demolishes = Vec::new();
291 }
292
293 fn set_player_order_from_headers(&mut self) -> SubtrActorResult<()> {
294 let _player_stats = self
295 .replay
296 .properties
297 .iter()
298 .find(|(key, _)| key == "PlayerStats")
299 .ok_or_else(|| {
300 SubtrActorError::new(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
301 })?;
302 SubtrActorError::new_result(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
304 }
305
306 pub fn process_long_enough_to_get_actor_ids(&mut self) -> SubtrActorResult<()> {
323 let mut handler = |_p: &ReplayProcessor, _f: &boxcars::Frame, n: usize, _current_time| {
324 if n > 10 * 30 {
326 SubtrActorError::new_result(SubtrActorErrorVariant::FinishProcessingEarly)
327 } else {
328 Ok(TimeAdvance::NextFrame)
329 }
330 };
331 let process_result = self.process(&mut handler);
332 if let Some(SubtrActorErrorVariant::FinishProcessingEarly) =
333 process_result.as_ref().err().map(|e| e.variant.clone())
334 {
335 Ok(())
336 } else {
337 process_result
338 }
339 }
340
341 fn set_player_order_from_frames(&mut self) -> SubtrActorResult<()> {
342 self.process_long_enough_to_get_actor_ids()?;
343 let player_to_team_0: HashMap<PlayerId, bool> = self
344 .player_to_actor_id
345 .keys()
346 .filter_map(|player_id| {
347 self.get_player_is_team_0(player_id)
348 .ok()
349 .map(|is_team_0| (player_id.clone(), is_team_0))
350 })
351 .collect();
352
353 let (team_zero, team_one): (Vec<_>, Vec<_>) = player_to_team_0
354 .keys()
355 .cloned()
356 .partition(|player_id| *player_to_team_0.get(player_id).unwrap());
358
359 self.team_zero = team_zero;
360 self.team_one = team_one;
361
362 self.team_zero
363 .sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
364 self.team_one
365 .sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
366
367 self.reset();
368 Ok(())
369 }
370
371 pub fn check_player_id_set(&self) -> SubtrActorResult<()> {
372 let known_players =
373 std::collections::HashSet::<_>::from_iter(self.player_to_actor_id.keys());
374 let original_players =
375 std::collections::HashSet::<_>::from_iter(self.iter_player_ids_in_order());
376
377 if original_players != known_players {
378 SubtrActorError::new_result(SubtrActorErrorVariant::InconsistentPlayerSet {
379 found: known_players.into_iter().cloned().collect(),
380 original: original_players.into_iter().cloned().collect(),
381 })
382 } else {
383 Ok(())
384 }
385 }
386
387 pub fn process_and_get_replay_meta(&mut self) -> SubtrActorResult<ReplayMeta> {
396 if self.player_to_actor_id.is_empty() {
397 self.process_long_enough_to_get_actor_ids()?;
398 }
399 self.get_replay_meta()
400 }
401
402 pub fn get_replay_meta(&self) -> SubtrActorResult<ReplayMeta> {
409 let empty_player_stats = Vec::new();
410 let player_stats = if let Some((_, boxcars::HeaderProp::Array(per_player))) = self
411 .replay
412 .properties
413 .iter()
414 .find(|(key, _)| key == "PlayerStats")
415 {
416 per_player
417 } else {
418 &empty_player_stats
419 };
420 let known_count = self.iter_player_ids_in_order().count();
421 if player_stats.len() != known_count {
422 log::warn!(
423 "Replay does not have player stats for all players. encountered {:?} {:?}",
424 known_count,
425 player_stats.len()
426 )
427 }
428 let get_player_info = |player_id| {
429 let name = self.get_player_name(player_id)?;
430 let stats = find_player_stats(player_id, &name, player_stats).ok();
431 Ok(PlayerInfo {
432 name,
433 stats,
434 remote_id: player_id.clone(),
435 })
436 };
437 let team_zero: SubtrActorResult<Vec<PlayerInfo>> =
438 self.team_zero.iter().map(get_player_info).collect();
439 let team_one: SubtrActorResult<Vec<PlayerInfo>> =
440 self.team_one.iter().map(get_player_info).collect();
441 Ok(ReplayMeta {
442 team_zero: team_zero?,
443 team_one: team_one?,
444 all_headers: self.replay.properties.clone(),
445 })
446 }
447
448 pub fn find_update_in_direction(
484 &self,
485 current_index: usize,
486 actor_id: &boxcars::ActorId,
487 object_id: &boxcars::ObjectId,
488 direction: SearchDirection,
489 ) -> SubtrActorResult<(boxcars::Attribute, usize)> {
490 let frames = self
491 .replay
492 .network_frames
493 .as_ref()
494 .ok_or(SubtrActorError::new(
495 SubtrActorErrorVariant::NoNetworkFrames,
496 ))?;
497
498 let predicate = |frame: &boxcars::Frame| {
499 frame
500 .updated_actors
501 .iter()
502 .find(|update| &update.actor_id == actor_id && &update.object_id == object_id)
503 .map(|update| &update.attribute)
504 .cloned()
505 };
506
507 match util::find_in_direction(&frames.frames, current_index, direction, predicate) {
508 Some((index, attribute)) => Ok((attribute, index)),
509 None => SubtrActorError::new_result(SubtrActorErrorVariant::NoUpdateAfterFrame {
510 actor_id: *actor_id,
511 object_id: *object_id,
512 frame_index: current_index,
513 }),
514 }
515 }
516
517 fn update_mappings(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
548 for update in frame.updated_actors.iter() {
549 macro_rules! maintain_link {
550 ($map:expr, $actor_type:expr, $attr:expr, $get_key: expr, $get_value: expr, $type:path) => {{
551 if &update.object_id == self.get_object_id_for_key(&$attr)? {
552 if self
553 .get_actor_ids_by_type($actor_type)?
554 .iter()
555 .any(|id| id == &update.actor_id)
556 {
557 let value = get_actor_attribute_matching!(
558 self,
559 &update.actor_id,
560 $attr,
561 $type
562 )?;
563 let _key = $get_key(update.actor_id, value);
564 let _new_value = $get_value(update.actor_id, value);
565 let _old_value = $map.insert(
566 $get_key(update.actor_id, value),
567 $get_value(update.actor_id, value),
568 );
569 }
570 }
571 }};
572 }
573 macro_rules! maintain_actor_link {
574 ($map:expr, $actor_type:expr, $attr:expr) => {
575 maintain_link!(
576 $map,
577 $actor_type,
578 $attr,
579 get_actor_id_from_active_actor,
582 use_update_actor,
583 boxcars::Attribute::ActiveActor
584 )
585 };
586 }
587 macro_rules! maintain_vehicle_key_link {
588 ($map:expr, $actor_type:expr) => {
589 maintain_actor_link!($map, $actor_type, VEHICLE_KEY)
590 };
591 }
592 maintain_link!(
593 self.player_to_actor_id,
594 PLAYER_TYPE,
595 UNIQUE_ID_KEY,
596 |_, unique_id: &boxcars::UniqueId| unique_id.remote_id.clone(),
597 use_update_actor,
598 boxcars::Attribute::UniqueId
599 );
600 maintain_link!(
601 self.player_to_team,
602 PLAYER_TYPE,
603 TEAM_KEY,
604 use_update_actor,
606 get_actor_id_from_active_actor,
607 boxcars::Attribute::ActiveActor
608 );
609 maintain_actor_link!(self.player_to_car, CAR_TYPE, PLAYER_REPLICATION_KEY);
610 maintain_vehicle_key_link!(self.car_to_boost, BOOST_TYPE);
611 maintain_vehicle_key_link!(self.car_to_dodge, DODGE_TYPE);
612 maintain_vehicle_key_link!(self.car_to_jump, JUMP_TYPE);
613 maintain_vehicle_key_link!(self.car_to_double_jump, DOUBLE_JUMP_TYPE);
614 }
615
616 for actor_id in frame.deleted_actors.iter() {
617 if let Some(car_id) = self.player_to_car.remove(actor_id) {
618 log::info!("Player actor {actor_id:?} deleted, car id: {car_id:?}.");
619 }
620 }
621
622 Ok(())
623 }
624
625 fn update_ball_id(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
626 if let Some(actor_id) = self.ball_actor_id {
628 if frame.deleted_actors.contains(&actor_id) {
629 self.ball_actor_id = None;
630 }
631 } else {
632 self.ball_actor_id = self.find_ball_actor();
633 if self.ball_actor_id.is_some() {
634 return self.update_ball_id(frame);
635 }
636 }
637 Ok(())
638 }
639
640 fn update_boost_amounts(
658 &mut self,
659 frame: &boxcars::Frame,
660 frame_index: usize,
661 ) -> SubtrActorResult<()> {
662 let updates: Vec<_> = self
663 .iter_actors_by_type_err(BOOST_TYPE)?
664 .map(|(actor_id, actor_state)| {
665 let (actor_amount_value, last_value, _, derived_value, is_active) =
666 self.get_current_boost_values(actor_state);
667 let mut current_value = if actor_amount_value == last_value {
668 derived_value
671 } else {
672 actor_amount_value.into()
674 };
675 if is_active {
676 current_value -= frame.delta * BOOST_USED_PER_SECOND;
677 }
678 (*actor_id, current_value.max(0.0), actor_amount_value)
679 })
680 .collect();
681
682 for (actor_id, current_value, new_last_value) in updates {
683 let derived_attributes = &mut self
684 .actor_state
685 .actor_states
686 .get_mut(&actor_id)
687 .unwrap()
689 .derived_attributes;
690
691 derived_attributes.insert(
692 LAST_BOOST_AMOUNT_KEY.to_string(),
693 (boxcars::Attribute::Byte(new_last_value), frame_index),
694 );
695 derived_attributes.insert(
696 BOOST_AMOUNT_KEY.to_string(),
697 (boxcars::Attribute::Float(current_value), frame_index),
698 );
699 }
700 Ok(())
701 }
702
703 fn get_current_boost_values(&self, actor_state: &ActorState) -> (u8, u8, u8, f32, bool) {
724 let amount_value = if let Ok(boxcars::Attribute::ReplicatedBoost(replicated_boost)) =
726 self.get_attribute(&actor_state.attributes, BOOST_REPLICATED_KEY)
727 {
728 replicated_boost.boost_amount
729 } else {
730 get_attribute_errors_expected!(
732 self,
733 &actor_state.attributes,
734 BOOST_AMOUNT_KEY,
735 boxcars::Attribute::Byte
736 )
737 .cloned()
738 .unwrap_or(0)
739 };
740 let active_value = get_attribute_errors_expected!(
741 self,
742 &actor_state.attributes,
743 COMPONENT_ACTIVE_KEY,
744 boxcars::Attribute::Byte
745 )
746 .cloned()
747 .unwrap_or(0);
748 let is_active = active_value % 2 == 1;
749 let derived_value = actor_state
750 .derived_attributes
751 .get(BOOST_AMOUNT_KEY)
752 .cloned()
753 .and_then(|v| attribute_match!(v.0, boxcars::Attribute::Float).ok())
754 .unwrap_or(0.0);
755 let last_boost_amount = attribute_match!(
756 actor_state
757 .derived_attributes
758 .get(LAST_BOOST_AMOUNT_KEY)
759 .cloned()
760 .map(|v| v.0)
761 .unwrap_or_else(|| boxcars::Attribute::Byte(amount_value)),
762 boxcars::Attribute::Byte
763 )
764 .unwrap_or(0);
765 (
766 amount_value,
767 last_boost_amount,
768 active_value,
769 derived_value,
770 is_active,
771 )
772 }
773
774 fn update_demolishes(&mut self, frame: &boxcars::Frame, index: usize) -> SubtrActorResult<()> {
775 let new_demolishes: Vec<_> = self
776 .get_active_demolish_fx()?
777 .flat_map(|demolish_fx| {
778 if !self.demolish_is_known(demolish_fx, index) {
779 Some(*demolish_fx.as_ref())
780 } else {
781 None
782 }
783 })
784 .collect();
785
786 for demolish in new_demolishes {
787 match self.build_demolish_info(&demolish, frame, index) {
788 Ok(demolish_info) => self.demolishes.push(demolish_info),
789 Err(_e) => {
790 log::warn!("Error building demolish info");
791 }
792 }
793 self.known_demolishes.push((demolish, index))
794 }
795
796 Ok(())
797 }
798
799 fn build_demolish_info(
800 &self,
801 demolish_fx: &boxcars::DemolishFx,
802 frame: &boxcars::Frame,
803 index: usize,
804 ) -> SubtrActorResult<DemolishInfo> {
805 let attacker = self.get_player_id_from_car_id(&demolish_fx.attacker)?;
806 let victim = self.get_player_id_from_car_id(&demolish_fx.victim)?;
807 Ok(DemolishInfo {
808 time: frame.time,
809 seconds_remaining: self.get_seconds_remaining()?,
810 frame: index,
811 attacker,
812 victim,
813 attacker_velocity: demolish_fx.attack_velocity,
814 victim_velocity: demolish_fx.victim_velocity,
815 })
816 }
817
818 fn get_player_id_from_car_id(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<PlayerId> {
821 self.get_player_id_from_actor_id(&self.get_player_actor_id_from_car_actor_id(actor_id)?)
822 }
823
824 fn get_player_id_from_actor_id(
825 &self,
826 actor_id: &boxcars::ActorId,
827 ) -> SubtrActorResult<PlayerId> {
828 for (player_id, player_actor_id) in self.player_to_actor_id.iter() {
829 if actor_id == player_actor_id {
830 return Ok(player_id.clone());
831 }
832 }
833 SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
834 actor_id: *actor_id,
835 })
836 }
837
838 fn get_player_actor_id_from_car_actor_id(
839 &self,
840 actor_id: &boxcars::ActorId,
841 ) -> SubtrActorResult<boxcars::ActorId> {
842 for (player_id, car_id) in self.player_to_car.iter() {
843 if actor_id == car_id {
844 return Ok(*player_id);
845 }
846 }
847 SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
848 actor_id: *actor_id,
849 })
850 }
851
852 fn demolish_is_known(&self, demolish_fx: &boxcars::DemolishFx, frame_index: usize) -> bool {
853 self.known_demolishes.iter().any(|(existing, index)| {
854 existing == demolish_fx
855 && frame_index
856 .checked_sub(*index)
857 .or_else(|| index.checked_sub(frame_index))
858 .unwrap()
859 < MAX_DEMOLISH_KNOWN_FRAMES_PASSED
860 })
861 }
862
863 pub fn get_active_demolish_fx(
866 &self,
867 ) -> SubtrActorResult<impl Iterator<Item = &Box<boxcars::DemolishFx>>> {
868 Ok(self
869 .iter_actors_by_type_err(CAR_TYPE)?
870 .flat_map(|(_actor_id, state)| {
871 get_attribute_errors_expected!(
872 self,
873 &state.attributes,
874 DEMOLISH_GOAL_EXPLOSION_KEY,
875 boxcars::Attribute::DemolishFx
876 )
877 .ok()
878 }))
879 }
880
881 fn get_frame(&self, frame_index: usize) -> SubtrActorResult<&boxcars::Frame> {
884 self.replay
885 .network_frames
886 .as_ref()
887 .ok_or(SubtrActorError::new(
888 SubtrActorErrorVariant::NoNetworkFrames,
889 ))?
890 .frames
891 .get(frame_index)
892 .ok_or(SubtrActorError::new(
893 SubtrActorErrorVariant::FrameIndexOutOfBounds,
894 ))
895 }
896
897 fn velocities_applied_rigid_body(
898 &self,
899 rigid_body: &boxcars::RigidBody,
900 rb_frame_index: usize,
901 target_time: f32,
902 ) -> SubtrActorResult<boxcars::RigidBody> {
903 let rb_frame = self.get_frame(rb_frame_index)?;
904 let interpolation_amount = target_time - rb_frame.time;
905 Ok(apply_velocities_to_rigid_body(
906 rigid_body,
907 interpolation_amount,
908 ))
909 }
910
911 pub fn get_interpolated_actor_rigid_body(
942 &self,
943 actor_id: &boxcars::ActorId,
944 time: f32,
945 close_enough: f32,
946 ) -> SubtrActorResult<boxcars::RigidBody> {
947 let (frame_body, frame_index) = self.get_actor_rigid_body(actor_id)?;
948 let frame_time = self.get_frame(*frame_index)?.time;
949 let time_and_frame_difference = time - frame_time;
950
951 if (time_and_frame_difference).abs() <= close_enough.abs() {
952 return Ok(*frame_body);
953 }
954
955 let search_direction = if time_and_frame_difference > 0.0 {
956 util::SearchDirection::Forward
957 } else {
958 util::SearchDirection::Backward
959 };
960
961 let object_id = self.get_object_id_for_key(RIGID_BODY_STATE_KEY)?;
962
963 let (attribute, found_frame) =
964 self.find_update_in_direction(*frame_index, actor_id, object_id, search_direction)?;
965 let found_time = self.get_frame(found_frame)?.time;
966
967 let found_body = attribute_match!(attribute, boxcars::Attribute::RigidBody)?;
968
969 if (found_time - time).abs() <= close_enough {
970 return Ok(found_body);
971 }
972
973 let (start_body, start_time, end_body, end_time) = match search_direction {
974 util::SearchDirection::Forward => (frame_body, frame_time, &found_body, found_time),
975 util::SearchDirection::Backward => (&found_body, found_time, frame_body, frame_time),
976 };
977
978 util::get_interpolated_rigid_body(start_body, start_time, end_body, end_time, time)
979 }
980
981 fn get_object_id_for_key(&self, name: &'static str) -> SubtrActorResult<&boxcars::ObjectId> {
984 self.name_to_object_id
985 .get(name)
986 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
987 }
988
989 fn get_actor_ids_by_type(&self, name: &'static str) -> SubtrActorResult<&[boxcars::ActorId]> {
990 self.get_object_id_for_key(name)
991 .map(|object_id| self.get_actor_ids_by_object_id(object_id))
992 }
993
994 fn get_actor_ids_by_object_id(&self, object_id: &boxcars::ObjectId) -> &[boxcars::ActorId] {
995 self.actor_state
996 .actor_ids_by_type
997 .get(object_id)
998 .map(|v| &v[..])
999 .unwrap_or_else(|| &EMPTY_ACTOR_IDS)
1000 }
1001
1002 fn get_actor_state(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<&ActorState> {
1003 self.actor_state.actor_states.get(actor_id).ok_or_else(|| {
1004 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
1005 actor_id: *actor_id,
1006 })
1007 })
1008 }
1009
1010 fn get_actor_attribute<'b>(
1011 &'b self,
1012 actor_id: &boxcars::ActorId,
1013 property: &'static str,
1014 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1015 self.get_attribute(&self.get_actor_state(actor_id)?.attributes, property)
1016 }
1017
1018 fn get_attribute<'b>(
1019 &'b self,
1020 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1021 property: &'static str,
1022 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1023 self.get_attribute_and_updated(map, property).map(|v| &v.0)
1024 }
1025
1026 fn get_attribute_and_updated<'b>(
1027 &'b self,
1028 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1029 property: &'static str,
1030 ) -> SubtrActorResult<&'b (boxcars::Attribute, usize)> {
1031 let attribute_object_id = self.get_object_id_for_key(property)?;
1032 map.get(attribute_object_id).ok_or_else(|| {
1033 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState { property })
1034 })
1035 }
1036
1037 fn find_ball_actor(&self) -> Option<boxcars::ActorId> {
1038 BALL_TYPES
1039 .iter()
1040 .filter_map(|ball_type| self.iter_actors_by_type(ball_type))
1041 .flatten()
1042 .map(|(actor_id, _)| *actor_id)
1043 .next()
1044 }
1045
1046 pub fn get_ball_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
1047 self.ball_actor_id.ok_or(SubtrActorError::new(
1048 SubtrActorErrorVariant::BallActorNotFound,
1049 ))
1050 }
1051
1052 pub fn get_metadata_actor_id(&self) -> SubtrActorResult<&boxcars::ActorId> {
1053 self.get_actor_ids_by_type(GAME_TYPE)?
1054 .iter()
1055 .next()
1056 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))
1057 }
1058
1059 pub fn get_player_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1060 self.player_to_actor_id
1061 .get(player_id)
1062 .ok_or_else(|| {
1063 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1064 name: "ActorId",
1065 player_id: player_id.clone(),
1066 })
1067 })
1068 .cloned()
1069 }
1070
1071 pub fn get_car_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1072 self.player_to_car
1073 .get(&self.get_player_actor_id(player_id)?)
1074 .ok_or_else(|| {
1075 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1076 name: "Car",
1077 player_id: player_id.clone(),
1078 })
1079 })
1080 .cloned()
1081 }
1082
1083 pub fn get_car_connected_actor_id(
1084 &self,
1085 player_id: &PlayerId,
1086 map: &HashMap<boxcars::ActorId, boxcars::ActorId>,
1087 name: &'static str,
1088 ) -> SubtrActorResult<boxcars::ActorId> {
1089 map.get(&self.get_car_actor_id(player_id)?)
1090 .ok_or_else(|| {
1091 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1092 name,
1093 player_id: player_id.clone(),
1094 })
1095 })
1096 .cloned()
1097 }
1098
1099 pub fn get_boost_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1100 self.get_car_connected_actor_id(player_id, &self.car_to_boost, "Boost")
1101 }
1102
1103 pub fn get_jump_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1104 self.get_car_connected_actor_id(player_id, &self.car_to_jump, "Jump")
1105 }
1106
1107 pub fn get_double_jump_actor_id(
1108 &self,
1109 player_id: &PlayerId,
1110 ) -> SubtrActorResult<boxcars::ActorId> {
1111 self.get_car_connected_actor_id(player_id, &self.car_to_double_jump, "Double Jump")
1112 }
1113
1114 pub fn get_dodge_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1115 self.get_car_connected_actor_id(player_id, &self.car_to_dodge, "Dodge")
1116 }
1117
1118 pub fn get_actor_rigid_body(
1119 &self,
1120 actor_id: &boxcars::ActorId,
1121 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1122 get_attribute_and_updated!(
1123 self,
1124 &self.get_actor_state(actor_id)?.attributes,
1125 RIGID_BODY_STATE_KEY,
1126 boxcars::Attribute::RigidBody
1127 )
1128 }
1129
1130 pub fn iter_player_ids_in_order(&self) -> impl Iterator<Item = &PlayerId> {
1133 self.team_zero.iter().chain(self.team_one.iter())
1134 }
1135
1136 pub fn player_count(&self) -> usize {
1137 self.iter_player_ids_in_order().count()
1138 }
1139
1140 fn iter_actors_by_type_err(
1141 &self,
1142 name: &'static str,
1143 ) -> SubtrActorResult<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1144 Ok(self.iter_actors_by_object_id(self.get_object_id_for_key(name)?))
1145 }
1146
1147 pub fn iter_actors_by_type(
1148 &self,
1149 name: &'static str,
1150 ) -> Option<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1151 self.iter_actors_by_type_err(name).ok()
1152 }
1153
1154 pub fn iter_actors_by_object_id<'b>(
1155 &'b self,
1156 object_id: &'b boxcars::ObjectId,
1157 ) -> impl Iterator<Item = (&'b boxcars::ActorId, &'b ActorState)> + 'b {
1158 let actor_ids = self
1159 .actor_state
1160 .actor_ids_by_type
1161 .get(object_id)
1162 .map(|v| &v[..])
1163 .unwrap_or_else(|| &EMPTY_ACTOR_IDS);
1164
1165 actor_ids
1166 .iter()
1167 .map(move |id| (id, self.actor_state.actor_states.get(id).unwrap()))
1170 }
1171
1172 pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
1176 get_actor_attribute_matching!(
1177 self,
1178 self.get_metadata_actor_id()?,
1179 SECONDS_REMAINING_KEY,
1180 boxcars::Attribute::Int
1181 )
1182 .cloned()
1183 }
1184
1185 pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
1187 let actor_id = self.get_ball_actor_id()?;
1188 get_actor_attribute_matching!(
1189 self,
1190 &actor_id,
1191 IGNORE_SYNCING_KEY,
1192 boxcars::Attribute::Boolean
1193 )
1194 .cloned()
1195 }
1196
1197 pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
1199 self.ball_actor_id
1200 .ok_or(SubtrActorError::new(
1201 SubtrActorErrorVariant::BallActorNotFound,
1202 ))
1203 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1204 }
1205
1206 pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
1209 Ok(self
1210 .get_ball_rigid_body()
1211 .map(|rb| !rb.sleeping)
1212 .unwrap_or(false))
1213 }
1214
1215 pub fn get_ball_rigid_body_and_updated(
1218 &self,
1219 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1220 self.ball_actor_id
1221 .ok_or(SubtrActorError::new(
1222 SubtrActorErrorVariant::BallActorNotFound,
1223 ))
1224 .and_then(|actor_id| {
1225 get_attribute_and_updated!(
1226 self,
1227 &self.get_actor_state(&actor_id)?.attributes,
1228 RIGID_BODY_STATE_KEY,
1229 boxcars::Attribute::RigidBody
1230 )
1231 })
1232 }
1233
1234 pub fn get_velocity_applied_ball_rigid_body(
1237 &self,
1238 target_time: f32,
1239 ) -> SubtrActorResult<boxcars::RigidBody> {
1240 let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
1241 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
1242 }
1243
1244 pub fn get_interpolated_ball_rigid_body(
1247 &self,
1248 time: f32,
1249 close_enough: f32,
1250 ) -> SubtrActorResult<boxcars::RigidBody> {
1251 self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
1252 }
1253
1254 pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1256 get_actor_attribute_matching!(
1257 self,
1258 &self.get_player_actor_id(player_id)?,
1259 PLAYER_NAME_KEY,
1260 boxcars::Attribute::String
1261 )
1262 .cloned()
1263 }
1264
1265 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1267 let team_actor_id = self
1268 .player_to_team
1269 .get(&self.get_player_actor_id(player_id)?)
1270 .ok_or_else(|| {
1271 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1272 player_id: player_id.clone(),
1273 })
1274 })?;
1275 let state = self.get_actor_state(team_actor_id)?;
1276 self.object_id_to_name
1277 .get(&state.object_id)
1278 .ok_or_else(|| {
1279 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1280 player_id: player_id.clone(),
1281 })
1282 })
1283 .cloned()
1284 }
1285
1286 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
1288 Ok(self
1289 .get_player_team_key(player_id)?
1290 .chars()
1291 .last()
1292 .ok_or_else(|| {
1293 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
1294 player_id: player_id.clone(),
1295 })
1296 })?
1297 == '0')
1298 }
1299
1300 pub fn get_player_rigid_body(
1302 &self,
1303 player_id: &PlayerId,
1304 ) -> SubtrActorResult<&boxcars::RigidBody> {
1305 self.get_car_actor_id(player_id)
1306 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1307 }
1308
1309 pub fn get_player_rigid_body_and_updated(
1313 &self,
1314 player_id: &PlayerId,
1315 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1316 self.get_car_actor_id(player_id).and_then(|actor_id| {
1317 get_attribute_and_updated!(
1318 self,
1319 &self.get_actor_state(&actor_id)?.attributes,
1320 RIGID_BODY_STATE_KEY,
1321 boxcars::Attribute::RigidBody
1322 )
1323 })
1324 }
1325
1326 pub fn get_velocity_applied_player_rigid_body(
1327 &self,
1328 player_id: &PlayerId,
1329 target_time: f32,
1330 ) -> SubtrActorResult<boxcars::RigidBody> {
1331 let (current_rigid_body, frame_index) =
1332 self.get_player_rigid_body_and_updated(player_id)?;
1333 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
1334 }
1335
1336 pub fn get_interpolated_player_rigid_body(
1337 &self,
1338 player_id: &PlayerId,
1339 time: f32,
1340 close_enough: f32,
1341 ) -> SubtrActorResult<boxcars::RigidBody> {
1342 self.get_interpolated_actor_rigid_body(
1343 &self.get_car_actor_id(player_id).unwrap(),
1344 time,
1345 close_enough,
1346 )
1347 }
1348
1349 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
1350 self.get_boost_actor_id(player_id).and_then(|actor_id| {
1351 let boost_state = self.get_actor_state(&actor_id)?;
1352 get_derived_attribute!(
1353 boost_state.derived_attributes,
1354 BOOST_AMOUNT_KEY,
1355 boxcars::Attribute::Float
1356 )
1357 .cloned()
1358 })
1359 }
1360
1361 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
1362 get_actor_attribute_matching!(
1363 self,
1364 &actor_id,
1365 COMPONENT_ACTIVE_KEY,
1366 boxcars::Attribute::Byte
1367 )
1368 .cloned()
1369 }
1370
1371 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1372 self.get_boost_actor_id(player_id)
1373 .and_then(|actor_id| self.get_component_active(&actor_id))
1374 }
1375
1376 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1377 self.get_jump_actor_id(player_id)
1378 .and_then(|actor_id| self.get_component_active(&actor_id))
1379 }
1380
1381 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1382 self.get_double_jump_actor_id(player_id)
1383 .and_then(|actor_id| self.get_component_active(&actor_id))
1384 }
1385
1386 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1387 self.get_dodge_actor_id(player_id)
1388 .and_then(|actor_id| self.get_component_active(&actor_id))
1389 }
1390
1391 pub fn map_attribute_keys(
1394 &self,
1395 hash_map: &HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1396 ) -> HashMap<String, boxcars::Attribute> {
1397 hash_map
1398 .iter()
1399 .map(|(k, (v, _updated))| {
1400 self.object_id_to_name
1401 .get(k)
1402 .map(|name| (name.clone(), v.clone()))
1403 .unwrap()
1404 })
1405 .collect()
1406 }
1407
1408 pub fn all_mappings_string(&self) -> String {
1409 let pairs = [
1410 ("player_to_car", &self.player_to_car),
1411 ("player_to_team", &self.player_to_team),
1412 ("car_to_boost", &self.car_to_boost),
1413 ("car_to_jump", &self.car_to_jump),
1414 ("car_to_double_jump", &self.car_to_double_jump),
1415 ("car_to_dodge", &self.car_to_dodge),
1416 ];
1417 let strings: Vec<_> = pairs
1418 .iter()
1419 .map(|(map_name, map)| format!("{map_name:?}: {map:?}"))
1420 .collect();
1421 strings.join("\n")
1422 }
1423
1424 pub fn actor_state_string(&self, actor_id: &boxcars::ActorId) -> String {
1425 format!(
1426 "{:?}",
1427 self.get_actor_state(actor_id)
1428 .map(|s| self.map_attribute_keys(&s.attributes))
1429 )
1430 }
1431
1432 pub fn print_actors_by_id<'b>(&self, actor_ids: impl Iterator<Item = &'b boxcars::ActorId>) {
1433 actor_ids.for_each(|actor_id| {
1434 let state = self.get_actor_state(actor_id).unwrap();
1435 println!(
1436 "{:?}\n\n\n",
1437 self.object_id_to_name.get(&state.object_id).unwrap()
1438 );
1439 println!("{:?}", self.map_attribute_keys(&state.attributes))
1440 })
1441 }
1442
1443 pub fn print_actors_of_type(&self, actor_type: &'static str) {
1444 self.iter_actors_by_type(actor_type)
1445 .unwrap()
1446 .for_each(|(_actor_id, state)| {
1447 println!("{:?}", self.map_attribute_keys(&state.attributes));
1448 });
1449 }
1450
1451 pub fn print_actor_types(&self) {
1452 let types: Vec<_> = self
1453 .actor_state
1454 .actor_ids_by_type
1455 .keys()
1456 .filter_map(|id| self.object_id_to_name.get(id))
1457 .collect();
1458 println!("{types:?}");
1459 }
1460
1461 pub fn print_all_actors(&self) {
1462 self.actor_state
1463 .actor_states
1464 .iter()
1465 .for_each(|(actor_id, _actor_state)| {
1466 println!("{:?}", self.actor_state_string(actor_id))
1467 })
1468 }
1469}