1use crate::*;
2use boxcars;
3use std::collections::HashMap;
4
5pub mod actor_state;
6mod boost_pad_resolution;
7mod replay_keys;
8pub mod view;
9pub use actor_state::*;
10pub(crate) use boost_pad_resolution::*;
11pub(crate) use replay_keys::*;
12pub use view::*;
13
14pub(crate) fn attribute_type_name(attribute: &boxcars::Attribute) -> &'static str {
15 match attribute {
16 boxcars::Attribute::Boolean(_) => "Boolean",
17 boxcars::Attribute::Byte(_) => "Byte",
18 boxcars::Attribute::AppliedDamage(_) => "AppliedDamage",
19 boxcars::Attribute::DamageState(_) => "DamageState",
20 boxcars::Attribute::CamSettings(_) => "CamSettings",
21 boxcars::Attribute::ClubColors(_) => "ClubColors",
22 boxcars::Attribute::Demolish(_) => "Demolish",
23 boxcars::Attribute::DemolishExtended(_) => "DemolishExtended",
24 boxcars::Attribute::DemolishFx(_) => "DemolishFx",
25 boxcars::Attribute::Enum(_) => "Enum",
26 boxcars::Attribute::Explosion(_) => "Explosion",
27 boxcars::Attribute::ExtendedExplosion(_) => "ExtendedExplosion",
28 boxcars::Attribute::FlaggedByte(_, _) => "FlaggedByte",
29 boxcars::Attribute::ActiveActor(_) => "ActiveActor",
30 boxcars::Attribute::Float(_) => "Float",
31 boxcars::Attribute::GameMode(_, _) => "GameMode",
32 boxcars::Attribute::Int(_) => "Int",
33 boxcars::Attribute::Int64(_) => "Int64",
34 boxcars::Attribute::Loadout(_) => "Loadout",
35 boxcars::Attribute::TeamLoadout(_) => "TeamLoadout",
36 boxcars::Attribute::Location(_) => "Location",
37 boxcars::Attribute::MusicStinger(_) => "MusicStinger",
38 boxcars::Attribute::PlayerHistoryKey(_) => "PlayerHistoryKey",
39 boxcars::Attribute::Pickup(_) => "Pickup",
40 boxcars::Attribute::PickupNew(_) => "PickupNew",
41 boxcars::Attribute::QWord(_) => "QWord",
42 boxcars::Attribute::Welded(_) => "Welded",
43 boxcars::Attribute::Title(_, _, _, _, _, _, _, _) => "Title",
44 boxcars::Attribute::TeamPaint(_) => "TeamPaint",
45 boxcars::Attribute::RigidBody(_) => "RigidBody",
46 boxcars::Attribute::String(_) => "String",
47 boxcars::Attribute::UniqueId(_) => "UniqueId",
48 boxcars::Attribute::Reservation(_) => "Reservation",
49 boxcars::Attribute::PartyLeader(_) => "PartyLeader",
50 boxcars::Attribute::PrivateMatch(_) => "PrivateMatch",
51 boxcars::Attribute::LoadoutOnline(_) => "LoadoutOnline",
52 boxcars::Attribute::LoadoutsOnline(_) => "LoadoutsOnline",
53 boxcars::Attribute::StatEvent(_) => "StatEvent",
54 boxcars::Attribute::Rotation(_) => "Rotation",
55 boxcars::Attribute::RepStatTitle(_) => "RepStatTitle",
56 boxcars::Attribute::PickupInfo(_) => "PickupInfo",
57 boxcars::Attribute::Impulse(_) => "Impulse",
58 boxcars::Attribute::ReplicatedBoost(_) => "ReplicatedBoost",
59 boxcars::Attribute::LogoData(_) => "LogoData",
60 }
61}
62
63#[macro_export]
75macro_rules! attribute_match {
76 ($value:expr_2021, $type:path $(,)?) => {{
77 let attribute = $value;
78 if let $type(value) = attribute {
79 Ok(value)
80 } else {
81 SubtrActorError::new_result(SubtrActorErrorVariant::UnexpectedAttributeType {
82 expected_type: stringify!($type),
83 actual_type: attribute_type_name(&attribute),
84 })
85 }
86 }};
87}
88
89#[macro_export]
98macro_rules! get_attribute_errors_expected {
99 ($self:ident, $map:expr_2021, $prop:expr_2021, $type:path) => {
100 $self
101 .get_attribute($map, $prop)
102 .and_then(|found| attribute_match!(found, $type))
103 };
104}
105
106macro_rules! get_attribute_and_updated {
119 ($self:ident, $map:expr_2021, $prop:expr_2021, $type:path) => {
120 $self
121 .get_attribute_and_updated($map, $prop)
122 .and_then(|(found, updated)| attribute_match!(found, $type).map(|v| (v, updated)))
123 };
124}
125
126macro_rules! get_actor_attribute_matching {
135 ($self:ident, $actor:expr_2021, $prop:expr_2021, $type:path) => {
136 $self
137 .get_actor_attribute($actor, $prop)
138 .and_then(|found| attribute_match!(found, $type))
139 };
140}
141
142macro_rules! get_derived_attribute {
151 ($map:expr_2021, $key:expr_2021, $type:path) => {
152 $map.get($key)
153 .ok_or_else(|| {
154 SubtrActorError::new(SubtrActorErrorVariant::DerivedKeyValueNotFound {
155 name: $key.to_string(),
156 })
157 })
158 .and_then(|found| attribute_match!(&found.0, $type))
159 };
160}
161
162fn get_actor_id_from_active_actor<T>(
163 _: T,
164 active_actor: &boxcars::ActiveActor,
165) -> boxcars::ActorId {
166 active_actor.actor
167}
168
169fn use_update_actor<T>(id: boxcars::ActorId, _: T) -> boxcars::ActorId {
170 id
171}
172
173#[derive(Clone, Copy, Default)]
174struct CachedObjectIds {
175 player_type: Option<boxcars::ObjectId>,
176 car_type: Option<boxcars::ObjectId>,
177 boost_type: Option<boxcars::ObjectId>,
178 dodge_type: Option<boxcars::ObjectId>,
179 jump_type: Option<boxcars::ObjectId>,
180 double_jump_type: Option<boxcars::ObjectId>,
181 unique_id: Option<boxcars::ObjectId>,
182 team: Option<boxcars::ObjectId>,
183 bot: Option<boxcars::ObjectId>,
184 client_loadouts: Option<boxcars::ObjectId>,
185 player_replication: Option<boxcars::ObjectId>,
186 vehicle: Option<boxcars::ObjectId>,
187 boost_replicated: Option<boxcars::ObjectId>,
188 boost_amount: Option<boxcars::ObjectId>,
189 component_active: Option<boxcars::ObjectId>,
190 seconds_remaining: Option<boxcars::ObjectId>,
191 replicated_state_name: Option<boxcars::ObjectId>,
192 replicated_game_state_time_remaining: Option<boxcars::ObjectId>,
193 match_type_class: Option<boxcars::ObjectId>,
194 ball_has_been_hit: Option<boxcars::ObjectId>,
195 replicated_game_playlist: Option<boxcars::ObjectId>,
196 ball_hit_team_num: Option<boxcars::ObjectId>,
197 dodges_refreshed_counter: Option<boxcars::ObjectId>,
198 camera_settings_pri: Option<boxcars::ObjectId>,
199 camera_settings_profile: Option<boxcars::ObjectId>,
200}
201
202impl CachedObjectIds {
203 fn from_name_map(name_to_object_id: &HashMap<String, boxcars::ObjectId>) -> Self {
204 let cached = |name| name_to_object_id.get(name).copied();
205 Self {
206 player_type: cached(PLAYER_TYPE),
207 car_type: cached(CAR_TYPE),
208 boost_type: cached(BOOST_TYPE),
209 dodge_type: cached(DODGE_TYPE),
210 jump_type: cached(JUMP_TYPE),
211 double_jump_type: cached(DOUBLE_JUMP_TYPE),
212 unique_id: cached(UNIQUE_ID_KEY),
213 team: cached(TEAM_KEY),
214 bot: cached(BOT_KEY),
215 client_loadouts: cached(CLIENT_LOADOUTS_KEY),
216 player_replication: cached(PLAYER_REPLICATION_KEY),
217 vehicle: cached(VEHICLE_KEY),
218 boost_replicated: cached(BOOST_REPLICATED_KEY),
219 boost_amount: cached(BOOST_AMOUNT_KEY),
220 component_active: cached(COMPONENT_ACTIVE_KEY),
221 seconds_remaining: cached(SECONDS_REMAINING_KEY),
222 replicated_state_name: cached(REPLICATED_STATE_NAME_KEY),
223 replicated_game_state_time_remaining: cached(REPLICATED_GAME_STATE_TIME_REMAINING_KEY),
224 match_type_class: cached(MATCH_TYPE_CLASS_KEY),
225 ball_has_been_hit: cached(BALL_HAS_BEEN_HIT_KEY),
226 replicated_game_playlist: cached(REPLICATED_GAME_PLAYLIST_KEY),
227 ball_hit_team_num: cached(BALL_HIT_TEAM_NUM_KEY),
228 dodges_refreshed_counter: cached(DODGES_REFRESHED_COUNTER_KEY),
229 camera_settings_pri: cached(CAMERA_SETTINGS_PRI_KEY),
230 camera_settings_profile: cached(CAMERA_SETTINGS_PROFILE_KEY),
231 }
232 }
233}
234
235mod bootstrap;
236mod debug;
237mod player_queries;
238mod queries;
239mod updaters;
240
241pub struct ReplayProcessor<'a> {
273 pub replay: &'a boxcars::Replay,
275 spatial_normalization_factor: f32,
276 rigid_body_velocity_normalization_factor: f32,
277 uses_legacy_rigid_body_rotation: bool,
278 cached_object_ids: CachedObjectIds,
279 is_boost_pad_object: Vec<bool>,
280 pub actor_state: ActorStateModeler,
282 pub object_id_to_name: HashMap<boxcars::ObjectId, String>,
284 pub name_to_object_id: HashMap<String, boxcars::ObjectId>,
286 pub ball_actor_id: Option<boxcars::ActorId>,
288 pub game_type_details: ReplayGameTypeDetails,
290 pub team_zero: Vec<PlayerId>,
292 pub team_one: Vec<PlayerId>,
294 pub player_to_actor_id: HashMap<PlayerId, boxcars::ActorId>,
296 pub player_actor_to_loadout: HashMap<boxcars::ActorId, boxcars::TeamLoadout>,
298 pub player_actor_to_camera_settings: HashMap<boxcars::ActorId, PlayerCameraSettings>,
304 camera_settings_actor_to_player_actor: HashMap<boxcars::ActorId, boxcars::ActorId>,
308 camera_settings_actor_to_settings: HashMap<boxcars::ActorId, PlayerCameraSettings>,
310 pub player_to_car: HashMap<boxcars::ActorId, boxcars::ActorId>,
312 pub player_to_team: HashMap<boxcars::ActorId, boxcars::ActorId>,
314 pub car_to_player: HashMap<boxcars::ActorId, boxcars::ActorId>,
316 pub car_to_boost: HashMap<boxcars::ActorId, boxcars::ActorId>,
318 pub car_to_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
320 pub car_to_double_jump: HashMap<boxcars::ActorId, boxcars::ActorId>,
322 pub car_to_dodge: HashMap<boxcars::ActorId, boxcars::ActorId>,
324 pub boost_pad_events: Vec<BoostPadEvent>,
326 current_frame_boost_pad_events: Vec<BoostPadEvent>,
327 boost_pad_pickup_sequence_times: HashMap<(String, u8), f32>,
328 boost_pad_resolution: BoostPadResolutionState,
329 pub touch_events: Vec<TouchEvent>,
331 current_frame_touch_events: Vec<TouchEvent>,
332 pub dodge_refreshed_events: Vec<DodgeRefreshedEvent>,
334 current_frame_dodge_refreshed_events: Vec<DodgeRefreshedEvent>,
335 dodge_refreshed_counters: HashMap<PlayerId, i32>,
336 pub goal_events: Vec<GoalEvent>,
338 current_frame_goal_events: Vec<GoalEvent>,
339 pub player_stat_events: Vec<PlayerStatEvent>,
341 current_frame_player_stat_events: Vec<PlayerStatEvent>,
342 player_stat_counters: HashMap<(PlayerId, PlayerStatEventKind), i32>,
343 pub demolishes: Vec<DemolishInfo>,
345 known_demolishes: Vec<(DemolishAttribute, usize)>,
346 demolish_format: Option<DemolishFormat>,
347 kickoff_phase_active_last_frame: bool,
348}
349
350impl<'a> ReplayProcessor<'a> {
351 const LEGACY_RIGID_BODY_NET_VERSION_CUTOFF: i32 = 5;
352 const LEGACY_RIGID_BODY_ROTATION_NET_VERSION_CUTOFF: i32 = 7;
353 const LEGACY_RIGID_BODY_LOCATION_FACTOR: f32 = 100.0;
354 const LEGACY_RIGID_BODY_VELOCITY_FACTOR: f32 = 10.0;
355
356 fn uses_legacy_rigid_body_vector_scale(net_version: Option<i32>) -> bool {
357 net_version.is_none_or(|version| version < Self::LEGACY_RIGID_BODY_NET_VERSION_CUTOFF)
358 }
359
360 fn uses_legacy_rigid_body_rotation_for_net_version(net_version: Option<i32>) -> bool {
361 net_version
362 .is_none_or(|version| version < Self::LEGACY_RIGID_BODY_ROTATION_NET_VERSION_CUTOFF)
363 }
364
365 fn rigid_body_location_normalization_factor_for_net_version(net_version: Option<i32>) -> f32 {
366 if Self::uses_legacy_rigid_body_vector_scale(net_version) {
367 Self::LEGACY_RIGID_BODY_LOCATION_FACTOR
368 } else {
369 1.0
370 }
371 }
372
373 fn rigid_body_velocity_normalization_factor_for_net_version(net_version: Option<i32>) -> f32 {
374 if Self::uses_legacy_rigid_body_vector_scale(net_version) {
375 Self::LEGACY_RIGID_BODY_VELOCITY_FACTOR
376 } else {
377 1.0
378 }
379 }
380
381 pub fn new(replay: &'a boxcars::Replay) -> SubtrActorResult<Self> {
395 let mut object_id_to_name = HashMap::new();
396 let mut name_to_object_id = HashMap::new();
397 let spatial_normalization_factor =
398 Self::rigid_body_location_normalization_factor_for_net_version(replay.net_version);
399 let rigid_body_velocity_normalization_factor =
400 Self::rigid_body_velocity_normalization_factor_for_net_version(replay.net_version);
401 let uses_legacy_rigid_body_rotation =
402 Self::uses_legacy_rigid_body_rotation_for_net_version(replay.net_version);
403 for (id, name) in replay.objects.iter().enumerate() {
404 let object_id = boxcars::ObjectId(id as i32);
405 object_id_to_name.insert(object_id, name.clone());
406 name_to_object_id.insert(name.clone(), object_id);
407 }
408 let cached_object_ids = CachedObjectIds::from_name_map(&name_to_object_id);
409 let game_type_details = ReplayGameTypeDetails::from_headers(&replay.properties);
410 let mut processor = Self {
411 actor_state: ActorStateModeler::new(),
412 replay,
413 spatial_normalization_factor,
414 rigid_body_velocity_normalization_factor,
415 uses_legacy_rigid_body_rotation,
416 cached_object_ids,
417 is_boost_pad_object: replay
418 .objects
419 .iter()
420 .map(|name| name.contains("VehiclePickup_Boost_TA"))
421 .collect(),
422 object_id_to_name,
423 name_to_object_id,
424 game_type_details,
425 team_zero: Vec::new(),
426 team_one: Vec::new(),
427 ball_actor_id: None,
428 player_to_car: HashMap::new(),
429 player_to_team: HashMap::new(),
430 player_to_actor_id: HashMap::new(),
431 player_actor_to_loadout: HashMap::new(),
432 player_actor_to_camera_settings: HashMap::new(),
433 camera_settings_actor_to_player_actor: HashMap::new(),
434 camera_settings_actor_to_settings: HashMap::new(),
435 car_to_player: HashMap::new(),
436 car_to_boost: HashMap::new(),
437 car_to_jump: HashMap::new(),
438 car_to_double_jump: HashMap::new(),
439 car_to_dodge: HashMap::new(),
440 boost_pad_events: Vec::new(),
441 current_frame_boost_pad_events: Vec::new(),
442 boost_pad_pickup_sequence_times: HashMap::new(),
443 boost_pad_resolution: BoostPadResolutionState::default(),
444 touch_events: Vec::new(),
445 current_frame_touch_events: Vec::new(),
446 dodge_refreshed_events: Vec::new(),
447 current_frame_dodge_refreshed_events: Vec::new(),
448 dodge_refreshed_counters: HashMap::new(),
449 goal_events: Vec::new(),
450 current_frame_goal_events: Vec::new(),
451 player_stat_events: Vec::new(),
452 current_frame_player_stat_events: Vec::new(),
453 player_stat_counters: HashMap::new(),
454 demolishes: Vec::new(),
455 known_demolishes: Vec::new(),
456 demolish_format: None,
457 kickoff_phase_active_last_frame: false,
458 };
459 processor
460 .set_player_order_from_headers()
461 .or_else(|_| processor.set_player_order_from_frames())?;
462
463 Ok(processor)
464 }
465
466 pub fn spatial_normalization_factor(&self) -> f32 {
468 self.spatial_normalization_factor
469 }
470
471 pub fn rigid_body_velocity_normalization_factor(&self) -> f32 {
473 self.rigid_body_velocity_normalization_factor
474 }
475
476 fn sync_player_order_from_known_mappings(&mut self) {
477 let player_ids: Vec<_> = self.player_to_actor_id.keys().cloned().collect();
478 for player_id in player_ids {
479 let already_ordered =
480 self.team_zero.contains(&player_id) || self.team_one.contains(&player_id);
481 if already_ordered {
482 continue;
483 }
484
485 let Ok(is_team_0) = self.get_player_is_team_0(&player_id) else {
486 continue;
487 };
488 if is_team_0 {
489 self.team_zero.push(player_id);
490 } else {
491 self.team_one.push(player_id);
492 }
493 }
494 }
495
496 pub(crate) fn insert_player_actor_id(
497 &mut self,
498 player_id: PlayerId,
499 actor_id: boxcars::ActorId,
500 ) {
501 let stale_player_ids = self
502 .player_to_actor_id
503 .iter()
504 .filter(|(existing_player_id, existing_actor_id)| {
505 **existing_actor_id == actor_id && **existing_player_id != player_id
506 })
507 .map(|(existing_player_id, _existing_actor_id)| existing_player_id.clone())
508 .collect::<Vec<_>>();
509
510 for stale_player_id in stale_player_ids {
511 self.player_to_actor_id.remove(&stale_player_id);
512 self.team_zero
513 .retain(|ordered_player_id| ordered_player_id != &stale_player_id);
514 self.team_one
515 .retain(|ordered_player_id| ordered_player_id != &stale_player_id);
516 }
517
518 self.player_to_actor_id.insert(player_id, actor_id);
519 }
520
521 fn normalize_vector_by_factor(
522 &self,
523 vector: boxcars::Vector3f,
524 factor: f32,
525 ) -> boxcars::Vector3f {
526 if (factor - 1.0).abs() < f32::EPSILON {
527 vector
528 } else {
529 boxcars::Vector3f {
530 x: vector.x * factor,
531 y: vector.y * factor,
532 z: vector.z * factor,
533 }
534 }
535 }
536
537 fn normalize_vector(&self, vector: boxcars::Vector3f) -> boxcars::Vector3f {
538 self.normalize_vector_by_factor(vector, self.spatial_normalization_factor)
539 }
540
541 fn normalize_rigid_body_velocity(&self, vector: boxcars::Vector3f) -> boxcars::Vector3f {
542 self.normalize_vector_by_factor(vector, self.rigid_body_velocity_normalization_factor)
543 }
544
545 fn normalize_optional_rigid_body_velocity(
546 &self,
547 vector: Option<boxcars::Vector3f>,
548 ) -> Option<boxcars::Vector3f> {
549 vector.map(|value| self.normalize_rigid_body_velocity(value))
550 }
551
552 fn normalize_rigid_body_rotation(&self, rotation: boxcars::Quaternion) -> boxcars::Quaternion {
553 if !self.uses_legacy_rigid_body_rotation {
554 return rotation;
555 }
556
557 let normalized = glam::Quat::from_euler(
561 glam::EulerRot::ZYX,
562 rotation.y * std::f32::consts::PI,
563 rotation.x * std::f32::consts::PI,
564 -rotation.z * std::f32::consts::PI,
565 );
566 boxcars::Quaternion {
567 x: normalized.x,
568 y: normalized.y,
569 z: normalized.z,
570 w: normalized.w,
571 }
572 }
573
574 fn normalize_rigid_body(&self, rigid_body: &boxcars::RigidBody) -> boxcars::RigidBody {
575 if (self.spatial_normalization_factor - 1.0).abs() < f32::EPSILON
576 && (self.rigid_body_velocity_normalization_factor - 1.0).abs() < f32::EPSILON
577 && !self.uses_legacy_rigid_body_rotation
578 {
579 *rigid_body
580 } else {
581 boxcars::RigidBody {
582 sleeping: rigid_body.sleeping,
583 location: self.normalize_vector(rigid_body.location),
584 rotation: self.normalize_rigid_body_rotation(rigid_body.rotation),
585 linear_velocity: self
586 .normalize_optional_rigid_body_velocity(rigid_body.linear_velocity),
587 angular_velocity: self
588 .normalize_optional_rigid_body_velocity(rigid_body.angular_velocity),
589 }
590 }
591 }
592
593 fn required_cached_object_id(
594 &self,
595 object_id: Option<boxcars::ObjectId>,
596 name: &'static str,
597 ) -> SubtrActorResult<boxcars::ObjectId> {
598 object_id
599 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
600 }
601
602 pub fn process<H: Collector>(&mut self, handler: &mut H) -> SubtrActorResult<()> {
624 let mut target_time = TimeAdvance::NextFrame;
627 for (index, frame) in self
628 .replay
629 .network_frames
630 .as_ref()
631 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoNetworkFrames))?
632 .frames
633 .iter()
634 .enumerate()
635 {
636 self.actor_state.process_frame(frame, index)?;
638 self.update_game_type_details();
639 self.update_mappings(frame)?;
640 self.update_ball_id(frame)?;
641 self.update_boost_amounts(frame, index)?;
642 self.update_boost_pad_events(frame, index)?;
643 self.update_touch_events(frame, index)?;
644 self.update_dodge_refreshed_events(frame, index)?;
645 self.update_goal_events(frame, index)?;
646 self.update_player_stat_events(frame, index)?;
647 self.update_demolishes(frame, index)?;
648
649 let mut current_time = match &target_time {
652 TimeAdvance::Time(t) => *t,
653 TimeAdvance::NextFrame => frame.time,
654 };
655
656 while current_time <= frame.time {
657 target_time = handler.process_frame(self, frame, index, current_time)?;
660 if let TimeAdvance::Time(new_target) = target_time {
666 current_time = new_target;
667 } else {
668 break;
669 }
670 }
671 }
672 handler.finish_replay(self)?;
673 Ok(())
674 }
675
676 pub fn process_all(&mut self, collectors: &mut [&mut dyn Collector]) -> SubtrActorResult<()> {
685 for (index, frame) in self
686 .replay
687 .network_frames
688 .as_ref()
689 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoNetworkFrames))?
690 .frames
691 .iter()
692 .enumerate()
693 {
694 self.actor_state.process_frame(frame, index)?;
695 self.update_game_type_details();
696 self.update_mappings(frame)?;
697 self.update_ball_id(frame)?;
698 self.update_boost_amounts(frame, index)?;
699 self.update_boost_pad_events(frame, index)?;
700 self.update_touch_events(frame, index)?;
701 self.update_dodge_refreshed_events(frame, index)?;
702 self.update_goal_events(frame, index)?;
703 self.update_player_stat_events(frame, index)?;
704 self.update_demolishes(frame, index)?;
705
706 for collector in collectors.iter_mut() {
707 collector.process_frame(self, frame, index, frame.time)?;
708 }
709 }
710 for collector in collectors.iter_mut() {
711 collector.finish_replay(self)?;
712 }
713 Ok(())
714 }
715
716 pub fn reset(&mut self) {
718 self.ball_actor_id = None;
719 self.actor_state = ActorStateModeler::new();
723 self.boost_pad_events = Vec::new();
724 self.current_frame_boost_pad_events = Vec::new();
725 self.boost_pad_pickup_sequence_times = HashMap::new();
726 self.boost_pad_resolution = BoostPadResolutionState::default();
727 self.touch_events = Vec::new();
728 self.current_frame_touch_events = Vec::new();
729 self.dodge_refreshed_events = Vec::new();
730 self.current_frame_dodge_refreshed_events = Vec::new();
731 self.dodge_refreshed_counters = HashMap::new();
732 self.goal_events = Vec::new();
733 self.current_frame_goal_events = Vec::new();
734 self.player_stat_events = Vec::new();
735 self.current_frame_player_stat_events = Vec::new();
736 self.player_stat_counters = HashMap::new();
737 self.demolishes = Vec::new();
738 self.known_demolishes = Vec::new();
739 self.demolish_format = None;
740 self.kickoff_phase_active_last_frame = false;
741 }
742}
743
744#[cfg(test)]
745#[path = "mod_tests.rs"]
746mod tests;