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<()> {
603 for update in frame.updated_actors.iter() {
604 macro_rules! maintain_link {
605 ($map:expr, $actor_type:expr, $attr:expr, $get_key:expr, $get_value:expr, $type:path $(, skip_value $skip:expr)?) => {{
606 if &update.object_id == self.get_object_id_for_key(&$attr)? {
607 if self
608 .get_actor_ids_by_type($actor_type)?
609 .iter()
610 .any(|id| id == &update.actor_id)
611 {
612 let value = get_actor_attribute_matching!(
613 self,
614 &update.actor_id,
615 $attr,
616 $type
617 )?;
618 let _key = $get_key(update.actor_id, value);
619 let _new_value = $get_value(update.actor_id, value);
620 if true $(&& _new_value != $skip)? {
621 let _ = $map.insert(_key, _new_value);
622 }
623 }
624 }
625 }};
626 }
627 macro_rules! maintain_actor_link {
628 ($map:expr, $actor_type:expr, $attr:expr $(, skip_value $skip:expr)?) => {
629 maintain_link!(
630 $map,
631 $actor_type,
632 $attr,
633 get_actor_id_from_active_actor,
636 use_update_actor,
637 boxcars::Attribute::ActiveActor
638 $(, skip_value $skip)?
639 )
640 };
641 }
642 macro_rules! maintain_vehicle_key_link {
643 ($map:expr, $actor_type:expr) => {
644 maintain_actor_link!($map, $actor_type, VEHICLE_KEY)
645 };
646 }
647 maintain_link!(
648 self.player_to_actor_id,
649 PLAYER_TYPE,
650 UNIQUE_ID_KEY,
651 |_, unique_id: &boxcars::UniqueId| unique_id.remote_id.clone(),
652 use_update_actor,
653 boxcars::Attribute::UniqueId
654 );
655 maintain_link!(
656 self.player_to_team,
657 PLAYER_TYPE,
658 TEAM_KEY,
659 use_update_actor,
661 get_actor_id_from_active_actor,
662 boxcars::Attribute::ActiveActor
663 );
664 maintain_actor_link!(self.player_to_car, CAR_TYPE, PLAYER_REPLICATION_KEY);
665 maintain_link!(
669 self.car_to_player,
670 CAR_TYPE,
671 PLAYER_REPLICATION_KEY,
672 use_update_actor,
673 get_actor_id_from_active_actor,
674 boxcars::Attribute::ActiveActor,
675 skip_value boxcars::ActorId(-1)
676 );
677 maintain_vehicle_key_link!(self.car_to_boost, BOOST_TYPE);
678 maintain_vehicle_key_link!(self.car_to_dodge, DODGE_TYPE);
679 maintain_vehicle_key_link!(self.car_to_jump, JUMP_TYPE);
680 maintain_vehicle_key_link!(self.car_to_double_jump, DOUBLE_JUMP_TYPE);
681 }
682
683 for actor_id in frame.deleted_actors.iter() {
684 if let Some(car_id) = self.player_to_car.remove(actor_id) {
685 log::info!("Player actor {actor_id:?} deleted, car id: {car_id:?}.");
686 }
687 }
688
689 Ok(())
690 }
691
692 fn update_ball_id(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
693 if let Some(actor_id) = self.ball_actor_id {
695 if frame.deleted_actors.contains(&actor_id) {
696 self.ball_actor_id = None;
697 }
698 } else {
699 self.ball_actor_id = self.find_ball_actor();
700 if self.ball_actor_id.is_some() {
701 return self.update_ball_id(frame);
702 }
703 }
704 Ok(())
705 }
706
707 fn update_boost_amounts(
725 &mut self,
726 frame: &boxcars::Frame,
727 frame_index: usize,
728 ) -> SubtrActorResult<()> {
729 let updates: Vec<_> = self
730 .iter_actors_by_type_err(BOOST_TYPE)?
731 .map(|(actor_id, actor_state)| {
732 let (actor_amount_value, last_value, _, derived_value, is_active) =
733 self.get_current_boost_values(actor_state);
734 let mut current_value = if actor_amount_value == last_value {
735 derived_value
738 } else {
739 actor_amount_value.into()
741 };
742 if is_active {
743 current_value -= frame.delta * BOOST_USED_RAW_UNITS_PER_SECOND;
744 }
745 (*actor_id, current_value.max(0.0), actor_amount_value)
746 })
747 .collect();
748
749 for (actor_id, current_value, new_last_value) in updates {
750 let derived_attributes = &mut self
751 .actor_state
752 .actor_states
753 .get_mut(&actor_id)
754 .unwrap()
756 .derived_attributes;
757
758 derived_attributes.insert(
759 LAST_BOOST_AMOUNT_KEY.to_string(),
760 (boxcars::Attribute::Byte(new_last_value), frame_index),
761 );
762 derived_attributes.insert(
763 BOOST_AMOUNT_KEY.to_string(),
764 (boxcars::Attribute::Float(current_value), frame_index),
765 );
766 }
767 Ok(())
768 }
769
770 fn get_current_boost_values(&self, actor_state: &ActorState) -> (u8, u8, u8, f32, bool) {
791 let amount_value = if let Ok(boxcars::Attribute::ReplicatedBoost(replicated_boost)) =
793 self.get_attribute(&actor_state.attributes, BOOST_REPLICATED_KEY)
794 {
795 replicated_boost.boost_amount
796 } else {
797 get_attribute_errors_expected!(
799 self,
800 &actor_state.attributes,
801 BOOST_AMOUNT_KEY,
802 boxcars::Attribute::Byte
803 )
804 .cloned()
805 .unwrap_or(0)
806 };
807 let active_value = get_attribute_errors_expected!(
808 self,
809 &actor_state.attributes,
810 COMPONENT_ACTIVE_KEY,
811 boxcars::Attribute::Byte
812 )
813 .cloned()
814 .unwrap_or(0);
815 let is_active = active_value % 2 == 1;
816 let derived_value = actor_state
817 .derived_attributes
818 .get(BOOST_AMOUNT_KEY)
819 .cloned()
820 .and_then(|v| attribute_match!(v.0, boxcars::Attribute::Float).ok())
821 .unwrap_or(0.0);
822 let last_boost_amount = attribute_match!(
823 actor_state
824 .derived_attributes
825 .get(LAST_BOOST_AMOUNT_KEY)
826 .cloned()
827 .map(|v| v.0)
828 .unwrap_or_else(|| boxcars::Attribute::Byte(amount_value)),
829 boxcars::Attribute::Byte
830 )
831 .unwrap_or(0);
832 (
833 amount_value,
834 last_boost_amount,
835 active_value,
836 derived_value,
837 is_active,
838 )
839 }
840
841 fn update_demolishes(
856 &mut self,
857 frame: &boxcars::Frame,
858 frame_index: usize,
859 ) -> SubtrActorResult<()> {
860 if self.demolish_format.is_none() {
861 self.demolish_format = self.detect_demolish_format();
862 }
863
864 let new_demolishes: Vec<_> = self.get_active_demos()?.collect();
865
866 for demolish in new_demolishes {
867 self.try_push_demolish(&demolish, frame, frame_index);
868 }
869
870 for update in &frame.updated_actors {
871 let demolish = match &update.attribute {
872 boxcars::Attribute::DemolishExtended(d) => {
873 self.demolish_format = Some(DemolishFormat::Extended);
874 Some(DemolishAttribute::Extended(**d))
875 }
876 boxcars::Attribute::DemolishFx(d) => {
877 self.demolish_format = Some(DemolishFormat::Fx);
878 Some(DemolishAttribute::Fx(**d))
879 }
880 _ => None,
881 };
882 if let Some(demolish) = demolish {
883 self.try_push_demolish(&demolish, frame, frame_index);
884 }
885 }
886
887 Ok(())
888 }
889
890 fn try_push_demolish(
891 &mut self,
892 demolish: &DemolishAttribute,
893 frame: &boxcars::Frame,
894 frame_index: usize,
895 ) {
896 if self.demolish_is_known(demolish, frame_index) {
897 return;
898 }
899 self.known_demolishes.push((demolish.clone(), frame_index));
900 if let Ok(info) = self.build_demolish_info(demolish, frame, frame_index) {
901 self.demolishes.push(info);
902 } else {
903 log::warn!(
904 "Error building demolish info: attacker_car={:?}, victim_car={:?}",
905 demolish.attacker_actor_id(),
906 demolish.victim_actor_id(),
907 );
908 }
909 }
910
911 fn build_demolish_info(
912 &self,
913 demo: &DemolishAttribute,
914 frame: &boxcars::Frame,
915 frame_index: usize,
916 ) -> SubtrActorResult<DemolishInfo> {
917 let attacker = self.get_player_id_from_car_id(&demo.attacker_actor_id())?;
918 let victim = self.get_player_id_from_car_id(&demo.victim_actor_id())?;
919 let (current_rigid_body, _) =
920 self.get_player_rigid_body_and_updated_or_recently_deleted(&victim)?;
921 Ok(DemolishInfo {
922 time: frame.time,
923 seconds_remaining: self.get_seconds_remaining()?,
924 frame: frame_index,
925 attacker,
926 victim,
927 attacker_velocity: demo.attacker_velocity(),
928 victim_velocity: demo.victim_velocity(),
929 victim_location: current_rigid_body.location,
930 })
931 }
932
933 pub fn get_player_id_from_car_id(
936 &self,
937 actor_id: &boxcars::ActorId,
938 ) -> SubtrActorResult<PlayerId> {
939 self.get_player_id_from_actor_id(&self.get_player_actor_id_from_car_actor_id(actor_id)?)
940 }
941
942 fn get_player_id_from_actor_id(
943 &self,
944 actor_id: &boxcars::ActorId,
945 ) -> SubtrActorResult<PlayerId> {
946 for (player_id, player_actor_id) in self.player_to_actor_id.iter() {
947 if actor_id == player_actor_id {
948 return Ok(player_id.clone());
949 }
950 }
951 SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
952 actor_id: *actor_id,
953 })
954 }
955
956 fn get_player_actor_id_from_car_actor_id(
957 &self,
958 actor_id: &boxcars::ActorId,
959 ) -> SubtrActorResult<boxcars::ActorId> {
960 self.car_to_player.get(actor_id).copied().ok_or_else(|| {
961 SubtrActorError::new(SubtrActorErrorVariant::NoMatchingPlayerId {
962 actor_id: *actor_id,
963 })
964 })
965 }
966
967 fn demolish_is_known(&self, demo: &DemolishAttribute, frame_index: usize) -> bool {
968 self.known_demolishes
969 .iter()
970 .any(|(existing, existing_frame_index)| {
971 existing == demo
972 && frame_index
973 .checked_sub(*existing_frame_index)
974 .or_else(|| existing_frame_index.checked_sub(frame_index))
975 .unwrap()
976 < MAX_DEMOLISH_KNOWN_FRAMES_PASSED
977 })
978 }
979
980 pub fn get_demolish_format(&self) -> Option<DemolishFormat> {
982 self.demolish_format
983 }
984
985 pub fn detect_demolish_format(&self) -> Option<DemolishFormat> {
987 let actors = self.iter_actors_by_type_err(CAR_TYPE).ok()?;
988 for (_actor_id, state) in actors {
989 if get_attribute_errors_expected!(
990 self,
991 &state.attributes,
992 DEMOLISH_EXTENDED_KEY,
993 boxcars::Attribute::DemolishExtended
994 )
995 .is_ok()
996 {
997 return Some(DemolishFormat::Extended);
998 }
999 if get_attribute_errors_expected!(
1000 self,
1001 &state.attributes,
1002 DEMOLISH_GOAL_EXPLOSION_KEY,
1003 boxcars::Attribute::DemolishFx
1004 )
1005 .is_ok()
1006 {
1007 return Some(DemolishFormat::Fx);
1008 }
1009 }
1010 None
1011 }
1012
1013 pub fn get_active_demos(
1017 &self,
1018 ) -> SubtrActorResult<impl Iterator<Item = DemolishAttribute> + '_> {
1019 let format = self.demolish_format;
1020 let actors: Vec<_> = self.iter_actors_by_type_err(CAR_TYPE)?.collect();
1021 Ok(actors
1022 .into_iter()
1023 .filter_map(move |(_actor_id, state)| match format {
1024 Some(DemolishFormat::Extended) => get_attribute_errors_expected!(
1025 self,
1026 &state.attributes,
1027 DEMOLISH_EXTENDED_KEY,
1028 boxcars::Attribute::DemolishExtended
1029 )
1030 .ok()
1031 .map(|demo| DemolishAttribute::Extended(**demo)),
1032 Some(DemolishFormat::Fx) => get_attribute_errors_expected!(
1033 self,
1034 &state.attributes,
1035 DEMOLISH_GOAL_EXPLOSION_KEY,
1036 boxcars::Attribute::DemolishFx
1037 )
1038 .ok()
1039 .map(|demo| DemolishAttribute::Fx(**demo)),
1040 None => None,
1041 }))
1042 }
1043
1044 fn get_frame(&self, frame_index: usize) -> SubtrActorResult<&boxcars::Frame> {
1047 self.replay
1048 .network_frames
1049 .as_ref()
1050 .ok_or(SubtrActorError::new(
1051 SubtrActorErrorVariant::NoNetworkFrames,
1052 ))?
1053 .frames
1054 .get(frame_index)
1055 .ok_or(SubtrActorError::new(
1056 SubtrActorErrorVariant::FrameIndexOutOfBounds,
1057 ))
1058 }
1059
1060 fn velocities_applied_rigid_body(
1061 &self,
1062 rigid_body: &boxcars::RigidBody,
1063 rb_frame_index: usize,
1064 target_time: f32,
1065 ) -> SubtrActorResult<boxcars::RigidBody> {
1066 let rb_frame = self.get_frame(rb_frame_index)?;
1067 let interpolation_amount = target_time - rb_frame.time;
1068 Ok(apply_velocities_to_rigid_body(
1069 rigid_body,
1070 interpolation_amount,
1071 ))
1072 }
1073
1074 pub fn get_interpolated_actor_rigid_body(
1105 &self,
1106 actor_id: &boxcars::ActorId,
1107 time: f32,
1108 close_enough: f32,
1109 ) -> SubtrActorResult<boxcars::RigidBody> {
1110 let (frame_body, frame_index) = self.get_actor_rigid_body(actor_id)?;
1111 let frame_time = self.get_frame(*frame_index)?.time;
1112 let time_and_frame_difference = time - frame_time;
1113
1114 if (time_and_frame_difference).abs() <= close_enough.abs() {
1115 return Ok(*frame_body);
1116 }
1117
1118 let search_direction = if time_and_frame_difference > 0.0 {
1119 util::SearchDirection::Forward
1120 } else {
1121 util::SearchDirection::Backward
1122 };
1123
1124 let object_id = self.get_object_id_for_key(RIGID_BODY_STATE_KEY)?;
1125
1126 let (attribute, found_frame) =
1127 self.find_update_in_direction(*frame_index, actor_id, object_id, search_direction)?;
1128 let found_time = self.get_frame(found_frame)?.time;
1129
1130 let found_body = attribute_match!(attribute, boxcars::Attribute::RigidBody)?;
1131
1132 if (found_time - time).abs() <= close_enough {
1133 return Ok(found_body);
1134 }
1135
1136 let (start_body, start_time, end_body, end_time) = match search_direction {
1137 util::SearchDirection::Forward => (frame_body, frame_time, &found_body, found_time),
1138 util::SearchDirection::Backward => (&found_body, found_time, frame_body, frame_time),
1139 };
1140
1141 util::get_interpolated_rigid_body(start_body, start_time, end_body, end_time, time)
1142 }
1143
1144 pub fn get_object_id_for_key(
1147 &self,
1148 name: &'static str,
1149 ) -> SubtrActorResult<&boxcars::ObjectId> {
1150 self.name_to_object_id
1151 .get(name)
1152 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
1153 }
1154
1155 pub fn get_actor_ids_by_type(
1156 &self,
1157 name: &'static str,
1158 ) -> SubtrActorResult<&[boxcars::ActorId]> {
1159 self.get_object_id_for_key(name)
1160 .map(|object_id| self.get_actor_ids_by_object_id(object_id))
1161 }
1162
1163 fn get_actor_ids_by_object_id(&self, object_id: &boxcars::ObjectId) -> &[boxcars::ActorId] {
1164 self.actor_state
1165 .actor_ids_by_type
1166 .get(object_id)
1167 .map(|v| &v[..])
1168 .unwrap_or_else(|| &EMPTY_ACTOR_IDS)
1169 }
1170
1171 fn get_actor_state(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<&ActorState> {
1172 self.actor_state.actor_states.get(actor_id).ok_or_else(|| {
1173 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
1174 actor_id: *actor_id,
1175 })
1176 })
1177 }
1178
1179 fn get_actor_state_or_recently_deleted(
1180 &self,
1181 actor_id: &boxcars::ActorId,
1182 ) -> SubtrActorResult<&ActorState> {
1183 self.actor_state
1184 .actor_states
1185 .get(actor_id)
1186 .or_else(|| self.actor_state.recently_deleted_actor_states.get(actor_id))
1187 .ok_or_else(|| {
1188 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
1189 actor_id: *actor_id,
1190 })
1191 })
1192 }
1193
1194 fn get_actor_attribute<'b>(
1195 &'b self,
1196 actor_id: &boxcars::ActorId,
1197 property: &'static str,
1198 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1199 self.get_attribute(&self.get_actor_state(actor_id)?.attributes, property)
1200 }
1201
1202 pub fn get_attribute<'b>(
1203 &'b self,
1204 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1205 property: &'static str,
1206 ) -> SubtrActorResult<&'b boxcars::Attribute> {
1207 self.get_attribute_and_updated(map, property).map(|v| &v.0)
1208 }
1209
1210 pub fn get_attribute_and_updated<'b>(
1211 &'b self,
1212 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1213 property: &'static str,
1214 ) -> SubtrActorResult<&'b (boxcars::Attribute, usize)> {
1215 let attribute_object_id = self.get_object_id_for_key(property)?;
1216 map.get(attribute_object_id).ok_or_else(|| {
1217 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState { property })
1218 })
1219 }
1220
1221 fn find_ball_actor(&self) -> Option<boxcars::ActorId> {
1222 BALL_TYPES
1223 .iter()
1224 .filter_map(|ball_type| self.iter_actors_by_type(ball_type))
1225 .flatten()
1226 .map(|(actor_id, _)| *actor_id)
1227 .next()
1228 }
1229
1230 pub fn get_ball_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
1231 self.ball_actor_id.ok_or(SubtrActorError::new(
1232 SubtrActorErrorVariant::BallActorNotFound,
1233 ))
1234 }
1235
1236 pub fn get_metadata_actor_id(&self) -> SubtrActorResult<&boxcars::ActorId> {
1237 self.get_actor_ids_by_type(GAME_TYPE)?
1238 .iter()
1239 .next()
1240 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))
1241 }
1242
1243 pub fn get_player_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1244 self.player_to_actor_id
1245 .get(player_id)
1246 .ok_or_else(|| {
1247 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1248 name: "ActorId",
1249 player_id: player_id.clone(),
1250 })
1251 })
1252 .cloned()
1253 }
1254
1255 pub fn get_car_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1256 self.player_to_car
1257 .get(&self.get_player_actor_id(player_id)?)
1258 .ok_or_else(|| {
1259 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1260 name: "Car",
1261 player_id: player_id.clone(),
1262 })
1263 })
1264 .cloned()
1265 }
1266
1267 pub fn get_car_connected_actor_id(
1268 &self,
1269 player_id: &PlayerId,
1270 map: &HashMap<boxcars::ActorId, boxcars::ActorId>,
1271 name: &'static str,
1272 ) -> SubtrActorResult<boxcars::ActorId> {
1273 map.get(&self.get_car_actor_id(player_id)?)
1274 .ok_or_else(|| {
1275 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1276 name,
1277 player_id: player_id.clone(),
1278 })
1279 })
1280 .cloned()
1281 }
1282
1283 pub fn get_boost_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1284 self.get_car_connected_actor_id(player_id, &self.car_to_boost, "Boost")
1285 }
1286
1287 pub fn get_jump_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1288 self.get_car_connected_actor_id(player_id, &self.car_to_jump, "Jump")
1289 }
1290
1291 pub fn get_double_jump_actor_id(
1292 &self,
1293 player_id: &PlayerId,
1294 ) -> SubtrActorResult<boxcars::ActorId> {
1295 self.get_car_connected_actor_id(player_id, &self.car_to_double_jump, "Double Jump")
1296 }
1297
1298 pub fn get_dodge_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1299 self.get_car_connected_actor_id(player_id, &self.car_to_dodge, "Dodge")
1300 }
1301
1302 pub fn get_actor_rigid_body(
1303 &self,
1304 actor_id: &boxcars::ActorId,
1305 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1306 get_attribute_and_updated!(
1307 self,
1308 &self.get_actor_state(actor_id)?.attributes,
1309 RIGID_BODY_STATE_KEY,
1310 boxcars::Attribute::RigidBody
1311 )
1312 }
1313
1314 pub fn get_actor_rigid_body_or_recently_deleted(
1315 &self,
1316 actor_id: &boxcars::ActorId,
1317 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1318 get_attribute_and_updated!(
1319 self,
1320 &self
1321 .get_actor_state_or_recently_deleted(actor_id)?
1322 .attributes,
1323 RIGID_BODY_STATE_KEY,
1324 boxcars::Attribute::RigidBody
1325 )
1326 }
1327
1328 pub fn iter_player_ids_in_order(&self) -> impl Iterator<Item = &PlayerId> {
1331 self.team_zero.iter().chain(self.team_one.iter())
1332 }
1333
1334 pub fn player_count(&self) -> usize {
1335 self.iter_player_ids_in_order().count()
1336 }
1337
1338 pub fn get_player_names(&self) -> HashMap<PlayerId, String> {
1340 self.iter_player_ids_in_order()
1341 .filter_map(|player_id| {
1342 self.get_player_name(player_id)
1343 .ok()
1344 .map(|name| (player_id.clone(), name))
1345 })
1346 .collect()
1347 }
1348
1349 fn iter_actors_by_type_err(
1350 &self,
1351 name: &'static str,
1352 ) -> SubtrActorResult<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1353 Ok(self.iter_actors_by_object_id(self.get_object_id_for_key(name)?))
1354 }
1355
1356 pub fn iter_actors_by_type(
1357 &self,
1358 name: &'static str,
1359 ) -> Option<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1360 self.iter_actors_by_type_err(name).ok()
1361 }
1362
1363 pub fn iter_actors_by_object_id<'b>(
1364 &'b self,
1365 object_id: &'b boxcars::ObjectId,
1366 ) -> impl Iterator<Item = (&'b boxcars::ActorId, &'b ActorState)> + 'b {
1367 let actor_ids = self
1368 .actor_state
1369 .actor_ids_by_type
1370 .get(object_id)
1371 .map(|v| &v[..])
1372 .unwrap_or_else(|| &EMPTY_ACTOR_IDS);
1373
1374 actor_ids
1375 .iter()
1376 .map(move |id| (id, self.actor_state.actor_states.get(id).unwrap()))
1379 }
1380
1381 pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
1385 get_actor_attribute_matching!(
1386 self,
1387 self.get_metadata_actor_id()?,
1388 SECONDS_REMAINING_KEY,
1389 boxcars::Attribute::Int
1390 )
1391 .cloned()
1392 }
1393
1394 pub fn get_replicated_state_name(&self) -> SubtrActorResult<i32> {
1401 get_actor_attribute_matching!(
1402 self,
1403 self.get_metadata_actor_id()?,
1404 REPLICATED_STATE_NAME_KEY,
1405 boxcars::Attribute::Int
1406 )
1407 .cloned()
1408 }
1409
1410 pub fn get_replicated_game_state_time_remaining(&self) -> SubtrActorResult<i32> {
1419 get_actor_attribute_matching!(
1420 self,
1421 self.get_metadata_actor_id()?,
1422 REPLICATED_GAME_STATE_TIME_REMAINING_KEY,
1423 boxcars::Attribute::Int
1424 )
1425 .cloned()
1426 }
1427
1428 pub fn get_ball_has_been_hit(&self) -> SubtrActorResult<bool> {
1433 get_actor_attribute_matching!(
1434 self,
1435 self.get_metadata_actor_id()?,
1436 BALL_HAS_BEEN_HIT_KEY,
1437 boxcars::Attribute::Boolean
1438 )
1439 .cloned()
1440 }
1441
1442 pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
1444 let actor_id = self.get_ball_actor_id()?;
1445 get_actor_attribute_matching!(
1446 self,
1447 &actor_id,
1448 IGNORE_SYNCING_KEY,
1449 boxcars::Attribute::Boolean
1450 )
1451 .cloned()
1452 }
1453
1454 pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
1456 self.ball_actor_id
1457 .ok_or(SubtrActorError::new(
1458 SubtrActorErrorVariant::BallActorNotFound,
1459 ))
1460 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1461 }
1462
1463 pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
1466 Ok(self
1467 .get_ball_rigid_body()
1468 .map(|rb| !rb.sleeping)
1469 .unwrap_or(false))
1470 }
1471
1472 pub fn get_ball_rigid_body_and_updated(
1475 &self,
1476 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1477 self.ball_actor_id
1478 .ok_or(SubtrActorError::new(
1479 SubtrActorErrorVariant::BallActorNotFound,
1480 ))
1481 .and_then(|actor_id| {
1482 get_attribute_and_updated!(
1483 self,
1484 &self.get_actor_state(&actor_id)?.attributes,
1485 RIGID_BODY_STATE_KEY,
1486 boxcars::Attribute::RigidBody
1487 )
1488 })
1489 }
1490
1491 pub fn get_velocity_applied_ball_rigid_body(
1494 &self,
1495 target_time: f32,
1496 ) -> SubtrActorResult<boxcars::RigidBody> {
1497 let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
1498 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
1499 }
1500
1501 pub fn get_interpolated_ball_rigid_body(
1504 &self,
1505 time: f32,
1506 close_enough: f32,
1507 ) -> SubtrActorResult<boxcars::RigidBody> {
1508 self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
1509 }
1510
1511 pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1513 get_actor_attribute_matching!(
1514 self,
1515 &self.get_player_actor_id(player_id)?,
1516 PLAYER_NAME_KEY,
1517 boxcars::Attribute::String
1518 )
1519 .cloned()
1520 }
1521
1522 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1524 let team_actor_id = self
1525 .player_to_team
1526 .get(&self.get_player_actor_id(player_id)?)
1527 .ok_or_else(|| {
1528 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1529 player_id: player_id.clone(),
1530 })
1531 })?;
1532 let state = self.get_actor_state(team_actor_id)?;
1533 self.object_id_to_name
1534 .get(&state.object_id)
1535 .ok_or_else(|| {
1536 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1537 player_id: player_id.clone(),
1538 })
1539 })
1540 .cloned()
1541 }
1542
1543 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
1545 Ok(self
1546 .get_player_team_key(player_id)?
1547 .chars()
1548 .last()
1549 .ok_or_else(|| {
1550 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
1551 player_id: player_id.clone(),
1552 })
1553 })?
1554 == '0')
1555 }
1556
1557 pub fn get_player_rigid_body(
1559 &self,
1560 player_id: &PlayerId,
1561 ) -> SubtrActorResult<&boxcars::RigidBody> {
1562 self.get_car_actor_id(player_id)
1563 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1564 }
1565
1566 pub fn get_player_rigid_body_and_updated(
1570 &self,
1571 player_id: &PlayerId,
1572 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1573 self.get_car_actor_id(player_id).and_then(|actor_id| {
1574 get_attribute_and_updated!(
1575 self,
1576 &self.get_actor_state(&actor_id)?.attributes,
1577 RIGID_BODY_STATE_KEY,
1578 boxcars::Attribute::RigidBody
1579 )
1580 })
1581 }
1582
1583 pub fn get_player_rigid_body_and_updated_or_recently_deleted(
1586 &self,
1587 player_id: &PlayerId,
1588 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1589 self.get_car_actor_id(player_id)
1590 .and_then(|actor_id| self.get_actor_rigid_body_or_recently_deleted(&actor_id))
1591 }
1592
1593 pub fn get_velocity_applied_player_rigid_body(
1594 &self,
1595 player_id: &PlayerId,
1596 target_time: f32,
1597 ) -> SubtrActorResult<boxcars::RigidBody> {
1598 let (current_rigid_body, frame_index) =
1599 self.get_player_rigid_body_and_updated(player_id)?;
1600 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
1601 }
1602
1603 pub fn get_interpolated_player_rigid_body(
1604 &self,
1605 player_id: &PlayerId,
1606 time: f32,
1607 close_enough: f32,
1608 ) -> SubtrActorResult<boxcars::RigidBody> {
1609 self.get_interpolated_actor_rigid_body(
1610 &self.get_car_actor_id(player_id).unwrap(),
1611 time,
1612 close_enough,
1613 )
1614 }
1615
1616 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
1621 self.get_boost_actor_id(player_id).and_then(|actor_id| {
1622 let boost_state = self.get_actor_state(&actor_id)?;
1623 get_derived_attribute!(
1624 boost_state.derived_attributes,
1625 BOOST_AMOUNT_KEY,
1626 boxcars::Attribute::Float
1627 )
1628 .cloned()
1629 })
1630 }
1631
1632 pub fn get_player_boost_percentage(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
1634 self.get_player_boost_level(player_id)
1635 .map(boost_amount_to_percent)
1636 }
1637
1638 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
1639 get_actor_attribute_matching!(
1640 self,
1641 &actor_id,
1642 COMPONENT_ACTIVE_KEY,
1643 boxcars::Attribute::Byte
1644 )
1645 .cloned()
1646 }
1647
1648 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1649 self.get_boost_actor_id(player_id)
1650 .and_then(|actor_id| self.get_component_active(&actor_id))
1651 }
1652
1653 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1654 self.get_jump_actor_id(player_id)
1655 .and_then(|actor_id| self.get_component_active(&actor_id))
1656 }
1657
1658 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1659 self.get_double_jump_actor_id(player_id)
1660 .and_then(|actor_id| self.get_component_active(&actor_id))
1661 }
1662
1663 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1664 self.get_dodge_actor_id(player_id)
1665 .and_then(|actor_id| self.get_component_active(&actor_id))
1666 }
1667
1668 pub fn map_attribute_keys(
1671 &self,
1672 hash_map: &HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1673 ) -> HashMap<String, boxcars::Attribute> {
1674 hash_map
1675 .iter()
1676 .map(|(k, (v, _updated))| {
1677 self.object_id_to_name
1678 .get(k)
1679 .map(|name| (name.clone(), v.clone()))
1680 .unwrap()
1681 })
1682 .collect()
1683 }
1684
1685 pub fn all_mappings_string(&self) -> String {
1686 let pairs = [
1687 ("player_to_car", &self.player_to_car),
1688 ("player_to_team", &self.player_to_team),
1689 ("car_to_player", &self.car_to_player),
1690 ("car_to_boost", &self.car_to_boost),
1691 ("car_to_jump", &self.car_to_jump),
1692 ("car_to_double_jump", &self.car_to_double_jump),
1693 ("car_to_dodge", &self.car_to_dodge),
1694 ];
1695 let mut strings: Vec<_> = pairs
1696 .iter()
1697 .map(|(map_name, map)| format!("{map_name:?}: {map:?}"))
1698 .collect();
1699 strings.push(format!("name_to_object_id: {:?}", &self.name_to_object_id));
1700 strings.join("\n")
1701 }
1702
1703 pub fn actor_state_string(&self, actor_id: &boxcars::ActorId) -> String {
1704 if let Ok(actor_state) = self.get_actor_state(actor_id) {
1705 format!("{:?}", self.map_attribute_keys(&actor_state.attributes))
1706 } else {
1707 String::from("error")
1708 }
1709 }
1710
1711 pub fn print_actors_by_id<'b>(&self, actor_ids: impl Iterator<Item = &'b boxcars::ActorId>) {
1712 actor_ids.for_each(|actor_id| {
1713 let state = self.get_actor_state(actor_id).unwrap();
1714 println!(
1715 "{:?}\n\n\n",
1716 self.object_id_to_name.get(&state.object_id).unwrap()
1717 );
1718 println!("{:?}", self.map_attribute_keys(&state.attributes))
1719 })
1720 }
1721
1722 pub fn print_actors_of_type(&self, actor_type: &'static str) {
1723 self.iter_actors_by_type(actor_type)
1724 .unwrap()
1725 .for_each(|(_actor_id, state)| {
1726 log::debug!("{:?}", self.map_attribute_keys(&state.attributes));
1727 });
1728 }
1729
1730 pub fn print_actor_types(&self) {
1731 let types: Vec<_> = self
1732 .actor_state
1733 .actor_ids_by_type
1734 .keys()
1735 .filter_map(|id| self.object_id_to_name.get(id))
1736 .collect();
1737 log::debug!("{types:?}");
1738 }
1739
1740 pub fn print_all_actors(&self) {
1741 self.actor_state
1742 .actor_states
1743 .iter()
1744 .for_each(|(actor_id, actor_state)| {
1745 log::debug!(
1746 "{}: {:?}",
1747 self.object_id_to_name
1748 .get(&actor_state.object_id)
1749 .unwrap_or(&String::from("unknown")),
1750 self.actor_state_string(actor_id)
1751 )
1752 });
1753 }
1754}