1use crate::*;
27use boxcars;
28use std::collections::HashMap;
29
30pub mod actor_state;
31mod boost_pad_resolution;
32mod replay_keys;
33pub mod view;
34pub use actor_state::*;
35pub(crate) use boost_pad_resolution::*;
36pub(crate) use replay_keys::*;
37pub use view::*;
38
39pub(crate) type PlayerCameraToggleState = (Option<bool>, Option<bool>, Option<bool>);
43
44pub(crate) fn attribute_type_name(attribute: &boxcars::Attribute) -> &'static str {
45 match attribute {
46 boxcars::Attribute::Boolean(_) => "Boolean",
47 boxcars::Attribute::Byte(_) => "Byte",
48 boxcars::Attribute::AppliedDamage(_) => "AppliedDamage",
49 boxcars::Attribute::DamageState(_) => "DamageState",
50 boxcars::Attribute::CamSettings(_) => "CamSettings",
51 boxcars::Attribute::ClubColors(_) => "ClubColors",
52 boxcars::Attribute::Demolish(_) => "Demolish",
53 boxcars::Attribute::DemolishExtended(_) => "DemolishExtended",
54 boxcars::Attribute::DemolishFx(_) => "DemolishFx",
55 boxcars::Attribute::Enum(_) => "Enum",
56 boxcars::Attribute::Explosion(_) => "Explosion",
57 boxcars::Attribute::ExtendedExplosion(_) => "ExtendedExplosion",
58 boxcars::Attribute::FlaggedByte(_, _) => "FlaggedByte",
59 boxcars::Attribute::ActiveActor(_) => "ActiveActor",
60 boxcars::Attribute::Float(_) => "Float",
61 boxcars::Attribute::GameMode(_, _) => "GameMode",
62 boxcars::Attribute::Int(_) => "Int",
63 boxcars::Attribute::Int64(_) => "Int64",
64 boxcars::Attribute::Loadout(_) => "Loadout",
65 boxcars::Attribute::TeamLoadout(_) => "TeamLoadout",
66 boxcars::Attribute::Location(_) => "Location",
67 boxcars::Attribute::MusicStinger(_) => "MusicStinger",
68 boxcars::Attribute::PlayerHistoryKey(_) => "PlayerHistoryKey",
69 boxcars::Attribute::Pickup(_) => "Pickup",
70 boxcars::Attribute::PickupNew(_) => "PickupNew",
71 boxcars::Attribute::QWord(_) => "QWord",
72 boxcars::Attribute::Welded(_) => "Welded",
73 boxcars::Attribute::Title(_, _, _, _, _, _, _, _) => "Title",
74 boxcars::Attribute::TeamPaint(_) => "TeamPaint",
75 boxcars::Attribute::RigidBody(_) => "RigidBody",
76 boxcars::Attribute::String(_) => "String",
77 boxcars::Attribute::UniqueId(_) => "UniqueId",
78 boxcars::Attribute::Reservation(_) => "Reservation",
79 boxcars::Attribute::PartyLeader(_) => "PartyLeader",
80 boxcars::Attribute::PrivateMatch(_) => "PrivateMatch",
81 boxcars::Attribute::LoadoutOnline(_) => "LoadoutOnline",
82 boxcars::Attribute::LoadoutsOnline(_) => "LoadoutsOnline",
83 boxcars::Attribute::StatEvent(_) => "StatEvent",
84 boxcars::Attribute::Rotation(_) => "Rotation",
85 boxcars::Attribute::RepStatTitle(_) => "RepStatTitle",
86 boxcars::Attribute::PickupInfo(_) => "PickupInfo",
87 boxcars::Attribute::Impulse(_) => "Impulse",
88 boxcars::Attribute::ReplicatedBoost(_) => "ReplicatedBoost",
89 boxcars::Attribute::LogoData(_) => "LogoData",
90 }
91}
92
93#[macro_export]
105macro_rules! attribute_match {
106 ($value:expr_2021, $type:path $(,)?) => {{
107 let attribute = $value;
108 if let $type(value) = attribute {
109 Ok(value)
110 } else {
111 SubtrActorError::new_result(SubtrActorErrorVariant::UnexpectedAttributeType {
112 expected_type: stringify!($type),
113 actual_type: attribute_type_name(&attribute),
114 })
115 }
116 }};
117}
118
119#[macro_export]
128macro_rules! get_attribute_errors_expected {
129 ($self:ident, $map:expr_2021, $prop:expr_2021, $type:path) => {
130 $self
131 .get_attribute($map, $prop)
132 .and_then(|found| attribute_match!(found, $type))
133 };
134}
135
136macro_rules! get_attribute_and_updated {
149 ($self:ident, $map:expr_2021, $prop:expr_2021, $type:path) => {
150 $self
151 .get_attribute_and_updated($map, $prop)
152 .and_then(|(found, updated)| attribute_match!(found, $type).map(|v| (v, updated)))
153 };
154}
155
156macro_rules! get_actor_attribute_matching {
165 ($self:ident, $actor:expr_2021, $prop:expr_2021, $type:path) => {
166 $self
167 .get_actor_attribute($actor, $prop)
168 .and_then(|found| attribute_match!(found, $type))
169 };
170}
171
172macro_rules! get_derived_attribute {
181 ($map:expr_2021, $key:expr_2021, $type:path) => {
182 $map.get($key)
183 .ok_or_else(|| {
184 SubtrActorError::new(SubtrActorErrorVariant::DerivedKeyValueNotFound {
185 name: $key.to_string(),
186 })
187 })
188 .and_then(|found| attribute_match!(&found.0, $type))
189 };
190}
191
192fn get_actor_id_from_active_actor<T>(
193 _: T,
194 active_actor: &boxcars::ActiveActor,
195) -> boxcars::ActorId {
196 active_actor.actor
197}
198
199fn use_update_actor<T>(id: boxcars::ActorId, _: T) -> boxcars::ActorId {
200 id
201}
202
203#[derive(Clone, Default)]
204struct CachedObjectIds {
205 player_types: Vec<boxcars::ObjectId>,
206 car_type: Option<boxcars::ObjectId>,
207 boost_type: Option<boxcars::ObjectId>,
208 dodge_type: Option<boxcars::ObjectId>,
209 jump_type: Option<boxcars::ObjectId>,
210 double_jump_type: Option<boxcars::ObjectId>,
211 unique_id: Option<boxcars::ObjectId>,
212 team: Option<boxcars::ObjectId>,
213 bot: Option<boxcars::ObjectId>,
214 client_loadouts: Option<boxcars::ObjectId>,
215 player_replication: Option<boxcars::ObjectId>,
216 vehicle: Option<boxcars::ObjectId>,
217 boost_replicated: Option<boxcars::ObjectId>,
218 boost_amount: Option<boxcars::ObjectId>,
219 component_active: Option<boxcars::ObjectId>,
220 seconds_remaining: Option<boxcars::ObjectId>,
221 replicated_state_name: Option<boxcars::ObjectId>,
222 replicated_game_state_time_remaining: Option<boxcars::ObjectId>,
223 match_type_class: Option<boxcars::ObjectId>,
224 ball_has_been_hit: Option<boxcars::ObjectId>,
225 replicated_game_playlist: Option<boxcars::ObjectId>,
226 ball_hit_team_num: Option<boxcars::ObjectId>,
227 dodges_refreshed_counter: Option<boxcars::ObjectId>,
228 camera_settings_pri: Option<boxcars::ObjectId>,
229 camera_settings_profile: Option<boxcars::ObjectId>,
230}
231
232impl CachedObjectIds {
233 fn from_name_map(name_to_object_id: &HashMap<String, boxcars::ObjectId>) -> Self {
234 let cached = |name| name_to_object_id.get(name).copied();
235 let mut player_types = name_to_object_id
236 .iter()
237 .filter_map(|(name, object_id)| is_player_type_object_name(name).then_some(*object_id))
238 .collect::<Vec<_>>();
239 player_types.sort_by_key(|object_id| object_id.0);
240 Self {
241 player_types,
242 car_type: cached(CAR_TYPE),
243 boost_type: cached(BOOST_TYPE),
244 dodge_type: cached(DODGE_TYPE),
245 jump_type: cached(JUMP_TYPE),
246 double_jump_type: cached(DOUBLE_JUMP_TYPE),
247 unique_id: cached(UNIQUE_ID_KEY),
248 team: cached(TEAM_KEY),
249 bot: cached(BOT_KEY),
250 client_loadouts: cached(CLIENT_LOADOUTS_KEY),
251 player_replication: cached(PLAYER_REPLICATION_KEY),
252 vehicle: cached(VEHICLE_KEY),
253 boost_replicated: cached(BOOST_REPLICATED_KEY),
254 boost_amount: cached(BOOST_AMOUNT_KEY),
255 component_active: cached(COMPONENT_ACTIVE_KEY),
256 seconds_remaining: cached(SECONDS_REMAINING_KEY),
257 replicated_state_name: cached(REPLICATED_STATE_NAME_KEY),
258 replicated_game_state_time_remaining: cached(REPLICATED_GAME_STATE_TIME_REMAINING_KEY),
259 match_type_class: cached(MATCH_TYPE_CLASS_KEY),
260 ball_has_been_hit: cached(BALL_HAS_BEEN_HIT_KEY),
261 replicated_game_playlist: cached(REPLICATED_GAME_PLAYLIST_KEY),
262 ball_hit_team_num: cached(BALL_HIT_TEAM_NUM_KEY),
263 dodges_refreshed_counter: cached(DODGES_REFRESHED_COUNTER_KEY),
264 camera_settings_pri: cached(CAMERA_SETTINGS_PRI_KEY),
265 camera_settings_profile: cached(CAMERA_SETTINGS_PROFILE_KEY),
266 }
267 }
268}
269
270mod bootstrap;
271mod debug;
272mod player_queries;
273mod queries;
274mod updaters;
275
276pub struct ReplayProcessor<'a> {
308 pub replay: &'a boxcars::Replay,
310 spatial_normalization_factor: f32,
311 rigid_body_velocity_normalization_factor: f32,
312 uses_legacy_rigid_body_rotation: bool,
313 cached_object_ids: CachedObjectIds,
314 is_boost_pad_object: Vec<bool>,
315 pub actor_state: ActorStateModeler,
317 pub object_id_to_name: HashMap<boxcars::ObjectId, String>,
319 pub name_to_object_id: HashMap<String, boxcars::ObjectId>,
321 pub ball_actor_id: Option<boxcars::ActorId>,
323 pub game_type_details: ReplayGameTypeDetails,
325 pub team_zero: Vec<PlayerId>,
327 pub team_one: Vec<PlayerId>,
329 pub player_to_actor_id: HashMap<PlayerId, boxcars::ActorId>,
331 pub player_actor_to_loadout: HashMap<boxcars::ActorId, boxcars::TeamLoadout>,
333 pub player_actor_to_camera_settings: HashMap<boxcars::ActorId, PlayerCameraSettings>,
339 camera_settings_actor_to_player_actor: HashMap<boxcars::ActorId, boxcars::ActorId>,
343 player_actor_to_camera_settings_actor: HashMap<boxcars::ActorId, boxcars::ActorId>,
348 camera_settings_actor_to_settings: HashMap<boxcars::ActorId, PlayerCameraSettings>,
350 pub player_to_car: HashMap<boxcars::ActorId, boxcars::ActorId>,
352 pub player_to_team: HashMap<boxcars::ActorId, boxcars::ActorId>,
354 pub car_to_player: HashMap<boxcars::ActorId, boxcars::ActorId>,
356 pub car_to_boost: HashMap<boxcars::ActorId, boxcars::ActorId>,
358 pub car_to_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
360 pub car_to_double_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
362 pub car_to_dodge: HashMap<boxcars::ActorId, boxcars::ActorId>,
364 pub boost_pad_events: Vec<BoostPadEvent>,
366 current_frame_boost_pad_events: Vec<BoostPadEvent>,
367 boost_pad_pickup_sequence_times: HashMap<(String, u8), f32>,
368 boost_pad_resolution: BoostPadResolutionState,
369 pub touch_events: Vec<TouchEvent>,
371 current_frame_touch_events: Vec<TouchEvent>,
372 pub dodge_refreshed_events: Vec<DodgeRefreshedEvent>,
374 current_frame_dodge_refreshed_events: Vec<DodgeRefreshedEvent>,
375 dodge_refreshed_counters: HashMap<PlayerId, i32>,
376 pub player_camera_events: Vec<(PlayerId, PlayerCameraStateChange)>,
380 player_camera_state_last: HashMap<PlayerId, PlayerCameraToggleState>,
383 pub goal_events: Vec<GoalEvent>,
385 current_frame_goal_events: Vec<GoalEvent>,
386 pub player_stat_events: Vec<PlayerStatEvent>,
388 current_frame_player_stat_events: Vec<PlayerStatEvent>,
389 player_stat_counters: HashMap<(PlayerId, PlayerStatEventKind), i32>,
390 pub demolishes: Vec<DemolishInfo>,
392 known_demolishes: Vec<(DemolishAttribute, usize)>,
393 demolish_format: Option<DemolishFormat>,
394 kickoff_phase_active_last_frame: bool,
395}
396
397impl<'a> ReplayProcessor<'a> {
398 const LEGACY_RIGID_BODY_NET_VERSION_CUTOFF: i32 = 5;
399 const LEGACY_RIGID_BODY_ROTATION_NET_VERSION_CUTOFF: i32 = 7;
400 const LEGACY_RIGID_BODY_LOCATION_FACTOR: f32 = 100.0;
401 const LEGACY_RIGID_BODY_VELOCITY_FACTOR: f32 = 10.0;
402
403 fn uses_legacy_rigid_body_vector_scale(net_version: Option<i32>) -> bool {
404 net_version.is_none_or(|version| version < Self::LEGACY_RIGID_BODY_NET_VERSION_CUTOFF)
405 }
406
407 fn uses_legacy_rigid_body_rotation_for_net_version(net_version: Option<i32>) -> bool {
408 net_version
409 .is_none_or(|version| version < Self::LEGACY_RIGID_BODY_ROTATION_NET_VERSION_CUTOFF)
410 }
411
412 fn rigid_body_location_normalization_factor_for_net_version(net_version: Option<i32>) -> f32 {
413 if Self::uses_legacy_rigid_body_vector_scale(net_version) {
414 Self::LEGACY_RIGID_BODY_LOCATION_FACTOR
415 } else {
416 1.0
417 }
418 }
419
420 fn rigid_body_velocity_normalization_factor_for_net_version(net_version: Option<i32>) -> f32 {
421 if Self::uses_legacy_rigid_body_vector_scale(net_version) {
422 Self::LEGACY_RIGID_BODY_VELOCITY_FACTOR
423 } else {
424 1.0
425 }
426 }
427
428 pub fn new(replay: &'a boxcars::Replay) -> SubtrActorResult<Self> {
442 let mut object_id_to_name = HashMap::new();
443 let mut name_to_object_id = HashMap::new();
444 let spatial_normalization_factor =
445 Self::rigid_body_location_normalization_factor_for_net_version(replay.net_version);
446 let rigid_body_velocity_normalization_factor =
447 Self::rigid_body_velocity_normalization_factor_for_net_version(replay.net_version);
448 let uses_legacy_rigid_body_rotation =
449 Self::uses_legacy_rigid_body_rotation_for_net_version(replay.net_version);
450 for (id, name) in replay.objects.iter().enumerate() {
451 let object_id = boxcars::ObjectId(id as i32);
452 object_id_to_name.insert(object_id, name.clone());
453 name_to_object_id.insert(name.clone(), object_id);
454 }
455 let cached_object_ids = CachedObjectIds::from_name_map(&name_to_object_id);
456 let game_type_details = ReplayGameTypeDetails::from_headers(&replay.properties);
457 let mut processor = Self {
458 actor_state: ActorStateModeler::new(),
459 replay,
460 spatial_normalization_factor,
461 rigid_body_velocity_normalization_factor,
462 uses_legacy_rigid_body_rotation,
463 cached_object_ids,
464 is_boost_pad_object: replay
465 .objects
466 .iter()
467 .map(|name| name.contains("VehiclePickup_Boost_TA"))
468 .collect(),
469 object_id_to_name,
470 name_to_object_id,
471 game_type_details,
472 team_zero: Vec::new(),
473 team_one: Vec::new(),
474 ball_actor_id: None,
475 player_to_car: HashMap::new(),
476 player_to_team: HashMap::new(),
477 player_to_actor_id: HashMap::new(),
478 player_actor_to_loadout: HashMap::new(),
479 player_actor_to_camera_settings: HashMap::new(),
480 camera_settings_actor_to_player_actor: HashMap::new(),
481 player_actor_to_camera_settings_actor: HashMap::new(),
482 camera_settings_actor_to_settings: HashMap::new(),
483 car_to_player: HashMap::new(),
484 car_to_boost: HashMap::new(),
485 car_to_jump: HashMap::new(),
486 car_to_double_jump: HashMap::new(),
487 car_to_dodge: HashMap::new(),
488 boost_pad_events: Vec::new(),
489 current_frame_boost_pad_events: Vec::new(),
490 boost_pad_pickup_sequence_times: HashMap::new(),
491 boost_pad_resolution: BoostPadResolutionState::default(),
492 touch_events: Vec::new(),
493 current_frame_touch_events: Vec::new(),
494 dodge_refreshed_events: Vec::new(),
495 current_frame_dodge_refreshed_events: Vec::new(),
496 dodge_refreshed_counters: HashMap::new(),
497 player_camera_events: Vec::new(),
498 player_camera_state_last: HashMap::new(),
499 goal_events: Vec::new(),
500 current_frame_goal_events: Vec::new(),
501 player_stat_events: Vec::new(),
502 current_frame_player_stat_events: Vec::new(),
503 player_stat_counters: HashMap::new(),
504 demolishes: Vec::new(),
505 known_demolishes: Vec::new(),
506 demolish_format: None,
507 kickoff_phase_active_last_frame: false,
508 };
509 processor
510 .set_player_order_from_headers()
511 .or_else(|_| processor.set_player_order_from_frames())?;
512
513 Ok(processor)
514 }
515
516 pub fn spatial_normalization_factor(&self) -> f32 {
518 self.spatial_normalization_factor
519 }
520
521 pub fn rigid_body_velocity_normalization_factor(&self) -> f32 {
523 self.rigid_body_velocity_normalization_factor
524 }
525
526 fn sync_player_order_from_known_mappings(&mut self) {
527 let player_ids: Vec<_> = self.player_to_actor_id.keys().cloned().collect();
528 for player_id in player_ids {
529 let already_ordered =
530 self.team_zero.contains(&player_id) || self.team_one.contains(&player_id);
531 if already_ordered {
532 continue;
533 }
534
535 let Ok(is_team_0) = self.get_player_is_team_0(&player_id) else {
536 continue;
537 };
538 if is_team_0 {
539 self.team_zero.push(player_id);
540 } else {
541 self.team_one.push(player_id);
542 }
543 }
544 }
545
546 pub(crate) fn insert_player_actor_id(
547 &mut self,
548 player_id: PlayerId,
549 actor_id: boxcars::ActorId,
550 ) {
551 let stale_player_ids = self
552 .player_to_actor_id
553 .iter()
554 .filter(|(existing_player_id, existing_actor_id)| {
555 **existing_actor_id == actor_id && **existing_player_id != player_id
556 })
557 .map(|(existing_player_id, _existing_actor_id)| existing_player_id.clone())
558 .collect::<Vec<_>>();
559
560 for stale_player_id in stale_player_ids {
561 self.player_to_actor_id.remove(&stale_player_id);
562 self.team_zero
563 .retain(|ordered_player_id| ordered_player_id != &stale_player_id);
564 self.team_one
565 .retain(|ordered_player_id| ordered_player_id != &stale_player_id);
566 }
567
568 self.player_to_actor_id.insert(player_id, actor_id);
569 }
570
571 fn normalize_vector_by_factor(
572 &self,
573 vector: boxcars::Vector3f,
574 factor: f32,
575 ) -> boxcars::Vector3f {
576 if (factor - 1.0).abs() < f32::EPSILON {
577 vector
578 } else {
579 boxcars::Vector3f {
580 x: vector.x * factor,
581 y: vector.y * factor,
582 z: vector.z * factor,
583 }
584 }
585 }
586
587 fn normalize_vector(&self, vector: boxcars::Vector3f) -> boxcars::Vector3f {
588 self.normalize_vector_by_factor(vector, self.spatial_normalization_factor)
589 }
590
591 fn normalize_rigid_body_velocity(&self, vector: boxcars::Vector3f) -> boxcars::Vector3f {
592 self.normalize_vector_by_factor(vector, self.rigid_body_velocity_normalization_factor)
593 }
594
595 fn normalize_optional_rigid_body_velocity(
596 &self,
597 vector: Option<boxcars::Vector3f>,
598 ) -> Option<boxcars::Vector3f> {
599 vector.map(|value| self.normalize_rigid_body_velocity(value))
600 }
601
602 fn normalize_rigid_body_rotation(&self, rotation: boxcars::Quaternion) -> boxcars::Quaternion {
603 if !self.uses_legacy_rigid_body_rotation {
604 return rotation;
605 }
606
607 let normalized = glam::Quat::from_euler(
611 glam::EulerRot::ZYX,
612 rotation.y * std::f32::consts::PI,
613 rotation.x * std::f32::consts::PI,
614 -rotation.z * std::f32::consts::PI,
615 );
616 boxcars::Quaternion {
617 x: normalized.x,
618 y: normalized.y,
619 z: normalized.z,
620 w: normalized.w,
621 }
622 }
623
624 fn normalize_rigid_body(&self, rigid_body: &boxcars::RigidBody) -> boxcars::RigidBody {
625 if (self.spatial_normalization_factor - 1.0).abs() < f32::EPSILON
626 && (self.rigid_body_velocity_normalization_factor - 1.0).abs() < f32::EPSILON
627 && !self.uses_legacy_rigid_body_rotation
628 {
629 *rigid_body
630 } else {
631 boxcars::RigidBody {
632 sleeping: rigid_body.sleeping,
633 location: self.normalize_vector(rigid_body.location),
634 rotation: self.normalize_rigid_body_rotation(rigid_body.rotation),
635 linear_velocity: self
636 .normalize_optional_rigid_body_velocity(rigid_body.linear_velocity),
637 angular_velocity: self
638 .normalize_optional_rigid_body_velocity(rigid_body.angular_velocity),
639 }
640 }
641 }
642
643 fn required_cached_object_id(
644 &self,
645 object_id: Option<boxcars::ObjectId>,
646 name: &'static str,
647 ) -> SubtrActorResult<boxcars::ObjectId> {
648 object_id
649 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
650 }
651
652 pub(crate) fn get_player_type_actor_ids(&self) -> SubtrActorResult<Vec<boxcars::ActorId>> {
653 if self.cached_object_ids.player_types.is_empty() {
654 return SubtrActorError::new_result(SubtrActorErrorVariant::ObjectIdNotFound {
655 name: PLAYER_TYPE,
656 });
657 }
658
659 Ok(self
660 .cached_object_ids
661 .player_types
662 .iter()
663 .flat_map(|object_id| self.get_actor_ids_by_object_id(object_id).iter().copied())
664 .collect())
665 }
666
667 pub fn process<H: Collector>(&mut self, handler: &mut H) -> SubtrActorResult<()> {
689 let mut target_time = TimeAdvance::NextFrame;
692 for (index, frame) in self
693 .replay
694 .network_frames
695 .as_ref()
696 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoNetworkFrames))?
697 .frames
698 .iter()
699 .enumerate()
700 {
701 self.actor_state.process_frame(frame, index)?;
703 self.update_game_type_details();
704 self.update_mappings(frame)?;
705 self.update_ball_id(frame)?;
706 self.update_boost_amounts(frame, index)?;
707 self.update_boost_pad_events(frame, index)?;
708 self.update_touch_events(frame, index)?;
709 self.update_dodge_refreshed_events(frame, index)?;
710 self.update_player_camera_events(frame, index)?;
711 self.update_goal_events(frame, index)?;
712 self.update_player_stat_events(frame, index)?;
713 self.update_demolishes(frame, index)?;
714
715 let mut current_time = match &target_time {
718 TimeAdvance::Time(t) => *t,
719 TimeAdvance::NextFrame => frame.time,
720 };
721
722 while current_time <= frame.time {
723 target_time = handler.process_frame(self, frame, index, current_time)?;
726 if let TimeAdvance::Time(new_target) = target_time {
732 current_time = new_target;
733 } else {
734 break;
735 }
736 }
737 }
738 handler.finish_replay(self)?;
739 Ok(())
740 }
741
742 pub fn process_all(&mut self, collectors: &mut [&mut dyn Collector]) -> SubtrActorResult<()> {
751 for (index, frame) in self
752 .replay
753 .network_frames
754 .as_ref()
755 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoNetworkFrames))?
756 .frames
757 .iter()
758 .enumerate()
759 {
760 self.actor_state.process_frame(frame, index)?;
761 self.update_game_type_details();
762 self.update_mappings(frame)?;
763 self.update_ball_id(frame)?;
764 self.update_boost_amounts(frame, index)?;
765 self.update_boost_pad_events(frame, index)?;
766 self.update_touch_events(frame, index)?;
767 self.update_dodge_refreshed_events(frame, index)?;
768 self.update_player_camera_events(frame, index)?;
769 self.update_goal_events(frame, index)?;
770 self.update_player_stat_events(frame, index)?;
771 self.update_demolishes(frame, index)?;
772
773 for collector in collectors.iter_mut() {
774 collector.process_frame(self, frame, index, frame.time)?;
775 }
776 }
777 for collector in collectors.iter_mut() {
778 collector.finish_replay(self)?;
779 }
780 Ok(())
781 }
782
783 pub fn reset(&mut self) {
785 self.ball_actor_id = None;
786 self.actor_state = ActorStateModeler::new();
790 self.boost_pad_events = Vec::new();
791 self.current_frame_boost_pad_events = Vec::new();
792 self.boost_pad_pickup_sequence_times = HashMap::new();
793 self.boost_pad_resolution = BoostPadResolutionState::default();
794 self.touch_events = Vec::new();
795 self.current_frame_touch_events = Vec::new();
796 self.dodge_refreshed_events = Vec::new();
797 self.current_frame_dodge_refreshed_events = Vec::new();
798 self.dodge_refreshed_counters = HashMap::new();
799 self.player_camera_events = Vec::new();
800 self.player_camera_state_last = HashMap::new();
801 self.goal_events = Vec::new();
802 self.current_frame_goal_events = Vec::new();
803 self.player_stat_events = Vec::new();
804 self.current_frame_player_stat_events = Vec::new();
805 self.player_stat_counters = HashMap::new();
806 self.demolishes = Vec::new();
807 self.known_demolishes = Vec::new();
808 self.demolish_format = None;
809 self.kickoff_phase_active_last_frame = false;
810 }
811}
812
813#[cfg(test)]
814#[path = "mod_tests.rs"]
815mod tests;