subtr_actor/
processor.rs

1use crate::*;
2use boxcars;
3use std::collections::HashMap;
4
5/// Attempts to match an attribute value with the given type.
6///
7/// # Arguments
8///
9/// * `$value` - An expression that yields the attribute value.
10/// * `$type` - The expected enum path.
11///
12/// If the attribute matches the specified type, it is returned wrapped in an
13/// [`Ok`] variant of a [`Result`]. If the attribute doesn't match, it results in an
14/// [`Err`] variant with a [`SubtrActorError`], specifying the expected type and
15/// the actual type.
16macro_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
30/// Obtains an attribute from a map and ensures it matches the expected type.
31///
32/// # Arguments
33///
34/// * `$self` - The struct or instance on which the function is invoked.
35/// * `$map` - The data map.
36/// * `$prop` - The attribute key.
37/// * `$type` - The expected enum path.
38macro_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
46/// Obtains an attribute and its updated status from a map and ensures the
47/// attribute matches the expected type.
48///
49/// # Arguments
50///
51/// * `$self` - The struct or instance on which the function is invoked.
52/// * `$map` - The data map.
53/// * `$prop` - The attribute key.
54/// * `$type` - The expected enum path.
55///
56/// It returns a [`Result`] with a tuple of the matched attribute and its updated
57/// status, after invoking [`attribute_match!`] on the found attribute.
58macro_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
66/// Obtains an actor attribute and ensures it matches the expected type.
67///
68/// # Arguments
69///
70/// * `$self` - The struct or instance on which the function is invoked.
71/// * `$actor` - The actor identifier.
72/// * `$prop` - The attribute key.
73/// * `$type` - The expected enum path.
74macro_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
82/// Obtains a derived attribute from a map and ensures it matches the expected
83/// type.
84///
85/// # Arguments
86///
87/// * `$map` - The data map.
88/// * `$key` - The attribute key.
89/// * `$type` - The expected enum path.
90macro_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
113/// The [`ReplayProcessor`] struct is a pivotal component in `subtr-actor`'s
114/// replay parsing pipeline. It is designed to process and traverse an actor
115/// graph of a Rocket League replay, and expose methods for collectors to gather
116/// specific data points as it progresses through the replay.
117///
118/// The processor pushes frames from a replay through an [`ActorStateModeler`],
119/// which models the state all actors in the replay at a given point in time.
120/// The [`ReplayProcessor`] also maintains various mappings to allow efficient
121/// lookup and traversal of the actor graph, thus assisting [`Collector`]
122/// instances in their data accumulation tasks.
123///
124/// The primary method of this struct is [`process`](ReplayProcessor::process),
125/// which takes a collector and processes the replay. As it traverses the
126/// replay, it calls the [`Collector::process_frame`] method of the passed
127/// collector, passing the current frame along with its contextual data. This
128/// allows the collector to extract specific data from each frame as needed.
129///
130/// The [`ReplayProcessor`] also provides a number of helper methods for
131/// navigating the actor graph and extracting information, such as
132/// [`get_ball_rigid_body`](ReplayProcessor::get_ball_rigid_body),
133/// [`get_player_name`](ReplayProcessor::get_player_name),
134/// [`get_player_team_key`](ReplayProcessor::get_player_team_key),
135/// [`get_player_is_team_0`](ReplayProcessor::get_player_is_team_0), and
136/// [`get_player_rigid_body`](ReplayProcessor::get_player_rigid_body).
137///
138/// # See Also
139///
140/// * [`ActorStateModeler`]: A struct used to model the states of multiple
141/// actors at a given point in time.
142/// * [`Collector`]: A trait implemented by objects that wish to collect data as
143/// the `ReplayProcessor` processes a replay.
144pub 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    /// Constructs a new [`ReplayProcessor`] instance with the provided replay.
165    ///
166    /// # Arguments
167    ///
168    /// * `replay` - A reference to the [`boxcars::Replay`] to be processed.
169    ///
170    /// # Returns
171    ///
172    /// Returns a [`SubtrActorResult`] of [`ReplayProcessor`]. In the process of
173    /// initialization, the [`ReplayProcessor`]: - Maps each object id in the
174    /// replay to its corresponding name. - Initializes empty state and
175    /// attribute maps. - Sets the player order from either replay headers or
176    /// frames, if available.
177    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    /// [`Self::process`] takes a [`Collector`] as an argument and iterates over
211    /// each frame in the replay, updating the internal state of the processor
212    /// and other relevant mappings based on the current frame.
213    ///
214    /// After each a frame is processed, [`Collector::process_frame`] of the
215    /// collector is called. The [`TimeAdvance`] return value of this call into
216    /// [`Collector::process_frame`] is used to determine what happens next: in
217    /// the case of [`TimeAdvance::Time`], the notion of current time is
218    /// advanced by the provided amount, and only the timestamp of the frame is
219    /// exceeded, do we process the next frame. This mechanism allows fine
220    /// grained control of frame processing, and the frequency of invocations of
221    /// the [`Collector`]. If time is advanced by less than the delay between
222    /// frames, the collector will be called more than once per frame, and can
223    /// use functions like [`Self::get_interpolated_player_rigid_body`] to get
224    /// values that are interpolated between frames. Its also possible to skip
225    /// over frames by providing time advance values that are sufficiently
226    /// large.
227    ///
228    /// At the end of processing, it checks to make sure that no unknown players
229    /// were encountered during the replay. If any unknown players are found, an
230    /// error is returned.
231    pub fn process<H: Collector>(&mut self, handler: &mut H) -> SubtrActorResult<()> {
232        // Initially, we set target_time to NextFrame to ensure the collector
233        // will process the first frame.
234        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            // Update the internal state of the processor based on the current frame
247            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            // Get the time to process for this frame. If target_time is set to
254            // NextFrame, we use the time of the current frame.
255            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                // Call the handler to process the frame and get the time for
262                // the next frame the handler wants to process
263                target_time = handler.process_frame(&self, frame, index, current_time)?;
264                // If the handler specified a specific time, update current_time
265                // to that time. If the handler specified NextFrame, we break
266                // out of the loop to move on to the next frame in the replay.
267                // This design allows the handler to have control over the frame
268                // rate, including the possibility of skipping frames.
269                if let TimeAdvance::Time(new_target) = target_time {
270                    current_time = new_target;
271                } else {
272                    break;
273                }
274            }
275        }
276        // TODO: This should probably not be mandatory. Also it really only
277        // checks that the set is the same at the end as it was in the
278        // beggining. There could still be issue in the intervening frames that
279        // are not detected.
280        // Make sure that we didn't encounter any players we
281        // did not know about at the beggining of the replay.
282        self.check_player_id_set()
283    }
284
285    /// Reset the state of the [`ReplayProcessor`].
286    pub fn reset(&mut self) {
287        self.player_to_car = HashMap::new();
288        self.player_to_team = HashMap::new();
289        self.player_to_actor_id = HashMap::new();
290        self.car_to_boost = HashMap::new();
291        self.car_to_jump = HashMap::new();
292        self.car_to_double_jump = HashMap::new();
293        self.car_to_dodge = HashMap::new();
294        self.actor_state = ActorStateModeler::new();
295        self.demolishes = Vec::new();
296        self.known_demolishes = Vec::new();
297    }
298
299    fn set_player_order_from_headers(&mut self) -> SubtrActorResult<()> {
300        let _player_stats = self
301            .replay
302            .properties
303            .iter()
304            .find(|(key, _)| key == "PlayerStats")
305            .ok_or_else(|| {
306                SubtrActorError::new(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
307            })?;
308        // XXX: implementation incomplete
309        SubtrActorError::new_result(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
310    }
311
312    /// Processes the replay until it has gathered enough information to map
313    /// players to their actor IDs.
314    ///
315    /// This function is designed to ensure that each player that participated
316    /// in the game is associated with a corresponding actor ID. It runs the
317    /// processing operation for approximately the first 10 seconds of the
318    /// replay (10 * 30 frames), as this time span is generally sufficient to
319    /// identify all players.
320    ///
321    /// Note that this function is particularly necessary because the headers of
322    /// replays sometimes omit some players.
323    ///
324    /// # Errors
325    ///
326    /// If any error other than `FinishProcessingEarly` occurs during the
327    /// processing operation, it is propagated up by this function.
328    pub fn process_long_enough_to_get_actor_ids(&mut self) -> SubtrActorResult<()> {
329        let mut handler = |_p: &ReplayProcessor, _f: &boxcars::Frame, n: usize, _current_time| {
330            // XXX: 10 seconds should be enough to find everyone, right?
331            if n > 10 * 30 {
332                SubtrActorError::new_result(SubtrActorErrorVariant::FinishProcessingEarly)
333            } else {
334                Ok(TimeAdvance::NextFrame)
335            }
336        };
337        let process_result = self.process(&mut handler);
338        if let Some(SubtrActorErrorVariant::FinishProcessingEarly) =
339            process_result.as_ref().err().map(|e| e.variant.clone())
340        {
341            Ok(())
342        } else {
343            process_result
344        }
345    }
346
347    fn set_player_order_from_frames(&mut self) -> SubtrActorResult<()> {
348        self.process_long_enough_to_get_actor_ids()?;
349        let result: Result<HashMap<PlayerId, bool>, _> = self
350            .player_to_actor_id
351            .keys()
352            .map(|player_id| Ok((player_id.clone(), self.get_player_is_team_0(player_id)?)))
353            .collect();
354
355        let player_to_team_0 = result?;
356
357        let (team_zero, team_one): (Vec<_>, Vec<_>) = player_to_team_0
358            .keys()
359            .cloned()
360            // The unwrap here is fine because we know the get will succeed
361            .partition(|player_id| *player_to_team_0.get(player_id).unwrap());
362
363        self.team_zero = team_zero;
364        self.team_one = team_one;
365
366        self.team_zero
367            .sort_by(|a, b| format!("{:?}", a).cmp(&format!("{:?}", b)));
368        self.team_one
369            .sort_by(|a, b| format!("{:?}", a).cmp(&format!("{:?}", b)));
370
371        self.reset();
372        Ok(())
373    }
374
375    pub fn check_player_id_set(&self) -> SubtrActorResult<()> {
376        let known_players =
377            std::collections::HashSet::<_>::from_iter(self.player_to_actor_id.keys());
378        let original_players =
379            std::collections::HashSet::<_>::from_iter(self.iter_player_ids_in_order());
380
381        if original_players != known_players {
382            return SubtrActorError::new_result(SubtrActorErrorVariant::InconsistentPlayerSet {
383                found: known_players.into_iter().cloned().collect(),
384                original: original_players.into_iter().cloned().collect(),
385            });
386        } else {
387            Ok(())
388        }
389    }
390
391    /// Processes the replay enough to get the actor IDs and then retrieves the replay metadata.
392    ///
393    /// This method is a convenience function that combines the functionalities
394    /// of
395    /// [`process_long_enough_to_get_actor_ids`](Self::process_long_enough_to_get_actor_ids)
396    /// and [`get_replay_meta`](Self::get_replay_meta) into a single operation.
397    /// It's meant to be used when you don't necessarily want to process the
398    /// whole replay and need only the replay's metadata.
399    pub fn process_and_get_replay_meta(&mut self) -> SubtrActorResult<ReplayMeta> {
400        if self.player_to_actor_id.is_empty() {
401            self.process_long_enough_to_get_actor_ids()?;
402        }
403        self.get_replay_meta()
404    }
405
406    /// Retrieves the replay metadata.
407    ///
408    /// This function collects information about each player in the replay and
409    /// groups them by team. For each player, it gets the player's name and
410    /// statistics. All this information is then wrapped into a [`ReplayMeta`]
411    /// object along with the properties from the replay.
412    pub fn get_replay_meta(&self) -> SubtrActorResult<ReplayMeta> {
413        let empty_player_stats = Vec::new();
414        let player_stats = if let Some((_, boxcars::HeaderProp::Array(per_player))) = self
415            .replay
416            .properties
417            .iter()
418            .find(|(key, _)| key == "PlayerStats")
419        {
420            per_player
421        } else {
422            &empty_player_stats
423        };
424        let known_count = self.iter_player_ids_in_order().count();
425        if player_stats.len() != known_count {
426            log::warn!(
427                "Replay does not have player stats for all players. encountered {:?} {:?}",
428                known_count,
429                player_stats.len()
430            )
431        }
432        let get_player_info = |player_id| {
433            let name = self.get_player_name(player_id)?;
434            let stats = find_player_stats(player_id, &name, player_stats).ok();
435            Ok(PlayerInfo {
436                name,
437                stats,
438                remote_id: player_id.clone(),
439            })
440        };
441        let team_zero: SubtrActorResult<Vec<PlayerInfo>> =
442            self.team_zero.iter().map(get_player_info).collect();
443        let team_one: SubtrActorResult<Vec<PlayerInfo>> =
444            self.team_one.iter().map(get_player_info).collect();
445        Ok(ReplayMeta {
446            team_zero: team_zero?,
447            team_one: team_one?,
448            all_headers: self.replay.properties.clone(),
449        })
450    }
451
452    /// Searches for the next or previous update for a specified actor and
453    /// object in the replay's network frames.
454    ///
455    /// This method uses the [`find_in_direction`](util::find_in_direction)
456    /// function to search through the network frames of the replay to find the
457    /// next (or previous, depending on the direction provided) attribute update
458    /// for a specified actor and object.
459    ///
460    /// # Arguments
461    ///
462    /// * `current_index` - The index of the network frame from where the search should start.
463    /// * `actor_id` - The ID of the actor for which the update is being searched.
464    /// * `object_id` - The ID of the object associated with the actor for which
465    /// the update is being searched.
466    /// * `direction` - The direction of search, specified as either
467    /// [`SearchDirection::Backward`] or [`SearchDirection::Forward`].
468    ///
469    /// # Returns
470    ///
471    /// If a matching update is found, this function returns a
472    /// [`SubtrActorResult`] tuple containing the found attribute and its index
473    /// in the replay's network frames.
474    ///
475    /// # Errors
476    ///
477    /// If no matching update is found, or if the replay has no network frames,
478    /// this function returns a [`SubtrActorError`]. Specifically, it returns
479    /// `NoUpdateAfterFrame` error variant if no update is found after the
480    /// specified frame, or `NoNetworkFrames` if the replay lacks network
481    /// frames.
482    ///
483    /// [`SearchDirection::Backward`]: enum.SearchDirection.html#variant.Backward
484    /// [`SearchDirection::Forward`]: enum.SearchDirection.html#variant.Forward
485    /// [`SubtrActorResult`]: type.SubtrActorResult.html
486    /// [`SubtrActorError`]: struct.SubtrActorError.html
487    pub fn find_update_in_direction(
488        &self,
489        current_index: usize,
490        actor_id: &boxcars::ActorId,
491        object_id: &boxcars::ObjectId,
492        direction: SearchDirection,
493    ) -> SubtrActorResult<(boxcars::Attribute, usize)> {
494        let frames = self
495            .replay
496            .network_frames
497            .as_ref()
498            .ok_or(SubtrActorError::new(
499                SubtrActorErrorVariant::NoNetworkFrames,
500            ))?;
501
502        let predicate = |frame: &boxcars::Frame| {
503            frame
504                .updated_actors
505                .iter()
506                .find(|update| &update.actor_id == actor_id && &update.object_id == object_id)
507                .map(|update| &update.attribute)
508                .cloned()
509        };
510
511        match util::find_in_direction(&frames.frames, current_index, direction, predicate) {
512            Some((index, attribute)) => Ok((attribute, index)),
513            None => SubtrActorError::new_result(SubtrActorErrorVariant::NoUpdateAfterFrame {
514                actor_id: actor_id.clone(),
515                object_id: object_id.clone(),
516                frame_index: current_index,
517            }),
518        }
519    }
520
521    // Update functions
522
523    /// This method is responsible for updating various mappings that are used
524    /// to track and link different actors in the replay.
525    ///
526    /// The replay data is a stream of [`boxcars::Frame`] objects that contain
527    /// information about the game at a specific point in time. These frames
528    /// contain updates for different actors, and the goal of this method is to
529    /// maintain and update the mappings for these actors as the frames are
530    /// processed.
531    ///
532    /// The method loops over each `updated_actors` field in the
533    /// [`boxcars::Frame`]. For each updated actor, it checks whether the
534    /// actor's object ID matches the object ID of various keys in the actor
535    /// state. If a match is found, the corresponding map is updated with a new
536    /// entry linking the actor ID to the value of the attribute in the replay
537    /// frame.
538    ///
539    /// The mappings updated are:
540    /// - `player_to_actor_id`: maps a player's [`boxcars::UniqueId`] to their actor ID.
541    /// - `player_to_team`: maps a player's actor ID to their team actor ID.
542    /// - `player_to_car`: maps a player's actor ID to their car actor ID.
543    /// - `car_to_boost`: maps a car's actor ID to its associated boost actor ID.
544    /// - `car_to_dodge`: maps a car's actor ID to its associated dodge actor ID.
545    /// - `car_to_jump`: maps a car's actor ID to its associated jump actor ID.
546    /// - `car_to_double_jump`: maps a car's actor ID to its associated double jump actor ID.
547    ///
548    /// The function also handles the deletion of actors. When an actor is
549    /// deleted, the function removes the actor's ID from the `player_to_car`
550    /// mapping.
551    fn update_mappings(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
552        for update in frame.updated_actors.iter() {
553            macro_rules! maintain_link {
554                ($map:expr, $actor_type:expr, $attr:expr, $get_key: expr, $get_value: expr, $type:path) => {{
555                    if &update.object_id == self.get_object_id_for_key(&$attr)? {
556                        if self
557                            .get_actor_ids_by_type($actor_type)?
558                            .iter()
559                            .any(|id| id == &update.actor_id)
560                        {
561                            let value = get_actor_attribute_matching!(
562                                self,
563                                &update.actor_id,
564                                $attr,
565                                $type
566                            )?;
567                            let _key = $get_key(update.actor_id, value);
568                            let _new_value = $get_value(update.actor_id, value);
569                            let _old_value = $map.insert(
570                                $get_key(update.actor_id, value),
571                                $get_value(update.actor_id, value),
572                            );
573                        }
574                    }
575                }};
576            }
577            macro_rules! maintain_actor_link {
578                ($map:expr, $actor_type:expr, $attr:expr) => {
579                    maintain_link!(
580                        $map,
581                        $actor_type,
582                        $attr,
583                        // This is slightly confusing, but in these cases we are
584                        // using the attribute as the key to the current actor.
585                        get_actor_id_from_active_actor,
586                        use_update_actor,
587                        boxcars::Attribute::ActiveActor
588                    )
589                };
590            }
591            macro_rules! maintain_vehicle_key_link {
592                ($map:expr, $actor_type:expr) => {
593                    maintain_actor_link!($map, $actor_type, VEHICLE_KEY)
594                };
595            }
596            maintain_link!(
597                self.player_to_actor_id,
598                PLAYER_TYPE,
599                UNIQUE_ID_KEY,
600                |_, unique_id: &Box<boxcars::UniqueId>| unique_id.remote_id.clone(),
601                use_update_actor,
602                boxcars::Attribute::UniqueId
603            );
604            maintain_link!(
605                self.player_to_team,
606                PLAYER_TYPE,
607                TEAM_KEY,
608                // In this case we are using the update actor as the key.
609                use_update_actor,
610                get_actor_id_from_active_actor,
611                boxcars::Attribute::ActiveActor
612            );
613            maintain_actor_link!(self.player_to_car, CAR_TYPE, PLAYER_REPLICATION_KEY);
614            maintain_vehicle_key_link!(self.car_to_boost, BOOST_TYPE);
615            maintain_vehicle_key_link!(self.car_to_dodge, DODGE_TYPE);
616            maintain_vehicle_key_link!(self.car_to_jump, JUMP_TYPE);
617            maintain_vehicle_key_link!(self.car_to_double_jump, DOUBLE_JUMP_TYPE);
618        }
619
620        for actor_id in frame.deleted_actors.iter() {
621            self.player_to_car.remove(actor_id).map(|car_id| {
622                log::info!("Player actor {:?} deleted, car id: {:?}.", actor_id, car_id)
623            });
624        }
625
626        Ok(())
627    }
628
629    fn update_ball_id(&mut self, frame: &boxcars::Frame) -> SubtrActorResult<()> {
630        // XXX: This assumes there is only ever one ball, which is safe (I think?)
631        if let Some(actor_id) = self.ball_actor_id {
632            if frame.deleted_actors.contains(&actor_id) {
633                self.ball_actor_id = None;
634            }
635        } else {
636            self.ball_actor_id = self.find_ball_actor();
637            if self.ball_actor_id.is_some() {
638                return self.update_ball_id(frame);
639            }
640        }
641        Ok(())
642    }
643
644    /// Updates the boost amounts for all the actors in a given frame.
645    ///
646    /// This function works by iterating over all the actors of a particular
647    /// boost type. For each actor, it retrieves the current boost value. If the
648    /// actor's boost value hasn't been updated, it continues using the derived
649    /// boost value from the last frame. If the actor's boost is active, it
650    /// subtracts from the current boost value according to the frame delta and
651    /// the constant `BOOST_USED_PER_SECOND`.
652    ///
653    /// The updated boost values are then stored in the actor's derived
654    /// attributes.
655    ///
656    /// # Arguments
657    ///
658    /// * `frame` - A reference to the [`Frame`] in which the boost amounts are to be updated.
659    /// * `frame_index` - The index of the frame in the replay.
660    /// [`Frame`]: boxcars::Frame
661    fn update_boost_amounts(
662        &mut self,
663        frame: &boxcars::Frame,
664        frame_index: usize,
665    ) -> SubtrActorResult<()> {
666        let updates: Vec<_> = self
667            .iter_actors_by_type_err(BOOST_TYPE)?
668            .map(|(actor_id, actor_state)| {
669                let (actor_amount_value, last_value, _, derived_value, is_active) =
670                    self.get_current_boost_values(actor_state);
671                let mut current_value = if actor_amount_value == last_value {
672                    // If we don't have an update in the actor, just continue
673                    // using our derived value
674                    derived_value
675                } else {
676                    // If we do have an update in the actor, use that value.
677                    actor_amount_value.into()
678                };
679                if is_active {
680                    current_value -= frame.delta * BOOST_USED_PER_SECOND;
681                }
682                (actor_id.clone(), current_value.max(0.0), actor_amount_value)
683            })
684            .collect();
685
686        for (actor_id, current_value, new_last_value) in updates {
687            let derived_attributes = &mut self
688                .actor_state
689                .actor_states
690                .get_mut(&actor_id)
691                // This actor is known to exist, so unwrap is fine
692                .unwrap()
693                .derived_attributes;
694
695            derived_attributes.insert(
696                LAST_BOOST_AMOUNT_KEY.to_string(),
697                (boxcars::Attribute::Byte(new_last_value), frame_index),
698            );
699            derived_attributes.insert(
700                BOOST_AMOUNT_KEY.to_string(),
701                (boxcars::Attribute::Float(current_value), frame_index),
702            );
703        }
704        Ok(())
705    }
706
707    /// Gets the current boost values for a given actor state.
708    ///
709    /// This function retrieves the current boost amount, whether the boost is active,
710    /// the derived boost amount, and the last known boost amount from the actor's state.
711    /// The derived value is retrieved from the actor's derived attributes, while
712    /// the other values are retrieved directly from the actor's attributes.
713    ///
714    /// # Arguments
715    ///
716    /// * `actor_state` - A reference to the actor's [`ActorState`] from which
717    /// the boost values are to be retrieved.
718    ///
719    /// # Returns
720    ///
721    /// This function returns a tuple consisting of the following:
722    /// * Current boost amount
723    /// * Last known boost amount
724    /// * Boost active value (1 if active, 0 otherwise)
725    /// * Derived boost amount
726    /// * Whether the boost is active (true if active, false otherwise)
727    fn get_current_boost_values(&self, actor_state: &ActorState) -> (u8, u8, u8, f32, bool) {
728        let amount_value = get_attribute_errors_expected!(
729            self,
730            &actor_state.attributes,
731            BOOST_AMOUNT_KEY,
732            boxcars::Attribute::Byte
733        )
734        .cloned()
735        .unwrap_or(0);
736        let active_value = get_attribute_errors_expected!(
737            self,
738            &actor_state.attributes,
739            COMPONENT_ACTIVE_KEY,
740            boxcars::Attribute::Byte
741        )
742        .cloned()
743        .unwrap_or(0);
744        let is_active = active_value % 2 == 1;
745        let derived_value = actor_state
746            .derived_attributes
747            .get(&BOOST_AMOUNT_KEY.to_string())
748            .cloned()
749            .and_then(|v| attribute_match!(v.0, boxcars::Attribute::Float).ok())
750            .unwrap_or(0.0);
751        let last_boost_amount = attribute_match!(
752            actor_state
753                .derived_attributes
754                .get(&LAST_BOOST_AMOUNT_KEY.to_string())
755                .cloned()
756                .map(|v| v.0)
757                .unwrap_or_else(|| boxcars::Attribute::Byte(amount_value)),
758            boxcars::Attribute::Byte
759        )
760        .unwrap_or(0);
761        (
762            amount_value,
763            last_boost_amount,
764            active_value,
765            derived_value,
766            is_active,
767        )
768    }
769
770    fn update_demolishes(&mut self, frame: &boxcars::Frame, index: usize) -> SubtrActorResult<()> {
771        let new_demolishes: Vec<_> = self
772            .get_active_demolish_fx()?
773            .flat_map(|demolish_fx| {
774                if !self.demolish_is_known(&demolish_fx, index) {
775                    Some(demolish_fx.as_ref().clone())
776                } else {
777                    None
778                }
779            })
780            .collect();
781
782        for demolish in new_demolishes {
783            match self.build_demolish_info(&demolish, frame, index) {
784                Ok(demolish_info) => self.demolishes.push(demolish_info),
785                Err(_e) => {
786                    log::warn!("Error building demolish info");
787                }
788            }
789            self.known_demolishes.push((demolish, index))
790        }
791
792        Ok(())
793    }
794
795    fn build_demolish_info(
796        &self,
797        demolish_fx: &boxcars::DemolishFx,
798        frame: &boxcars::Frame,
799        index: usize,
800    ) -> SubtrActorResult<DemolishInfo> {
801        let attacker = self.get_player_id_from_car_id(&demolish_fx.attacker)?;
802        let victim = self.get_player_id_from_car_id(&demolish_fx.victim)?;
803        Ok(DemolishInfo {
804            time: frame.time,
805            seconds_remaining: self.get_seconds_remaining()?,
806            frame: index,
807            attacker,
808            victim,
809            attacker_velocity: demolish_fx.attack_velocity.clone(),
810            victim_velocity: demolish_fx.victim_velocity.clone(),
811        })
812    }
813
814    // ID Mapping functions
815
816    fn get_player_id_from_car_id(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<PlayerId> {
817        self.get_player_id_from_actor_id(&self.get_player_actor_id_from_car_actor_id(actor_id)?)
818    }
819
820    fn get_player_id_from_actor_id(
821        &self,
822        actor_id: &boxcars::ActorId,
823    ) -> SubtrActorResult<PlayerId> {
824        for (player_id, player_actor_id) in self.player_to_actor_id.iter() {
825            if actor_id == player_actor_id {
826                return Ok(player_id.clone());
827            }
828        }
829        return SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
830            actor_id: actor_id.clone(),
831        });
832    }
833
834    fn get_player_actor_id_from_car_actor_id(
835        &self,
836        actor_id: &boxcars::ActorId,
837    ) -> SubtrActorResult<boxcars::ActorId> {
838        for (player_id, car_id) in self.player_to_car.iter() {
839            if actor_id == car_id {
840                return Ok(player_id.clone());
841            }
842        }
843        return SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
844            actor_id: actor_id.clone(),
845        });
846    }
847
848    fn demolish_is_known(&self, demolish_fx: &boxcars::DemolishFx, frame_index: usize) -> bool {
849        self.known_demolishes.iter().any(|(existing, index)| {
850            existing == demolish_fx
851                && frame_index
852                    .checked_sub(*index)
853                    .or_else(|| index.checked_sub(frame_index))
854                    .unwrap()
855                    < MAX_DEMOLISH_KNOWN_FRAMES_PASSED
856        })
857    }
858
859    /// Provides an iterator over the active demolition effects,
860    /// [`boxcars::DemolishFx`], in the current frame.
861    pub fn get_active_demolish_fx(
862        &self,
863    ) -> SubtrActorResult<impl Iterator<Item = &Box<boxcars::DemolishFx>>> {
864        Ok(self
865            .iter_actors_by_type_err(CAR_TYPE)?
866            .flat_map(|(_actor_id, state)| {
867                get_attribute_errors_expected!(
868                    self,
869                    &state.attributes,
870                    DEMOLISH_GOAL_EXPLOSION_KEY,
871                    boxcars::Attribute::DemolishFx
872                )
873                .ok()
874            }))
875    }
876
877    // Interpolation Support functions
878
879    fn get_frame(&self, frame_index: usize) -> SubtrActorResult<&boxcars::Frame> {
880        self.replay
881            .network_frames
882            .as_ref()
883            .ok_or(SubtrActorError::new(
884                SubtrActorErrorVariant::NoNetworkFrames,
885            ))?
886            .frames
887            .get(frame_index)
888            .ok_or(SubtrActorError::new(
889                SubtrActorErrorVariant::FrameIndexOutOfBounds,
890            ))
891    }
892
893    fn velocities_applied_rigid_body(
894        &self,
895        rigid_body: &boxcars::RigidBody,
896        rb_frame_index: usize,
897        target_time: f32,
898    ) -> SubtrActorResult<boxcars::RigidBody> {
899        let rb_frame = self.get_frame(rb_frame_index)?;
900        let interpolation_amount = target_time - rb_frame.time;
901        Ok(apply_velocities_to_rigid_body(
902            rigid_body,
903            interpolation_amount,
904        ))
905    }
906
907    /// This function first retrieves the actor's [`RigidBody`] at the current
908    /// frame. If the time difference between the current frame and the provided
909    /// time is within the `close_enough` threshold, the function returns the
910    /// current frame's [`RigidBody`].
911    ///
912    /// If the [`RigidBody`] at the exact time is not available, the function
913    /// searches in the appropriate direction (either forwards or backwards in
914    /// time) to find another [`RigidBody`] to interpolate from. If the found
915    /// [`RigidBody`]'s time is within the `close_enough` threshold, it is
916    /// returned.
917    ///
918    /// Otherwise, it interpolates between the two [`RigidBody`]s (from the
919    /// current frame and the found frame) to produce a [`RigidBody`] for the
920    /// specified time. This is done using the [`get_interpolated_rigid_body`]
921    /// function from the `util` module.
922    ///
923    /// # Arguments
924    ///
925    /// * `actor_id` - The ID of the actor whose [`RigidBody`] is to be retrieved.
926    /// * `time` - The time at which the actor's [`RigidBody`] is to be retrieved.
927    /// * `close_enough` - The acceptable threshold for time difference when
928    ///   determining if a [`RigidBody`] is close enough to the desired time to not
929    ///   require interpolation.
930    ///
931    /// # Returns
932    ///
933    /// A [`RigidBody`] for the actor at the specified time.
934    ///
935    /// [`RigidBody`]: boxcars::RigidBody
936    /// [`get_interpolated_rigid_body`]: util::get_interpolated_rigid_body
937    pub fn get_interpolated_actor_rigid_body(
938        &self,
939        actor_id: &boxcars::ActorId,
940        time: f32,
941        close_enough: f32,
942    ) -> SubtrActorResult<boxcars::RigidBody> {
943        let (frame_body, frame_index) = self.get_actor_rigid_body(actor_id)?;
944        let frame_time = self.get_frame(*frame_index)?.time;
945        let time_and_frame_difference = time - frame_time;
946
947        if (time_and_frame_difference).abs() <= close_enough.abs() {
948            return Ok(frame_body.clone());
949        }
950
951        let search_direction = if time_and_frame_difference > 0.0 {
952            util::SearchDirection::Forward
953        } else {
954            util::SearchDirection::Backward
955        };
956
957        let object_id = self.get_object_id_for_key(RIGID_BODY_STATE_KEY)?;
958
959        let (attribute, found_frame) =
960            self.find_update_in_direction(*frame_index, &actor_id, object_id, search_direction)?;
961        let found_time = self.get_frame(found_frame)?.time;
962
963        let found_body = attribute_match!(attribute, boxcars::Attribute::RigidBody)?;
964
965        if (found_time - time).abs() <= close_enough {
966            return Ok(found_body.clone());
967        }
968
969        let (start_body, start_time, end_body, end_time) = match search_direction {
970            util::SearchDirection::Forward => (frame_body, frame_time, &found_body, found_time),
971            util::SearchDirection::Backward => (&found_body, found_time, frame_body, frame_time),
972        };
973
974        util::get_interpolated_rigid_body(start_body, start_time, end_body, end_time, time)
975    }
976
977    // Actor functions
978
979    fn get_object_id_for_key(&self, name: &'static str) -> SubtrActorResult<&boxcars::ObjectId> {
980        self.name_to_object_id
981            .get(name)
982            .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
983    }
984
985    fn get_actor_ids_by_type(&self, name: &'static str) -> SubtrActorResult<&[boxcars::ActorId]> {
986        self.get_object_id_for_key(name)
987            .map(|object_id| self.get_actor_ids_by_object_id(object_id))
988    }
989
990    fn get_actor_ids_by_object_id(&self, object_id: &boxcars::ObjectId) -> &[boxcars::ActorId] {
991        self.actor_state
992            .actor_ids_by_type
993            .get(object_id)
994            .map(|v| &v[..])
995            .unwrap_or_else(|| &EMPTY_ACTOR_IDS)
996    }
997
998    fn get_actor_state(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<&ActorState> {
999        self.actor_state.actor_states.get(actor_id).ok_or_else(|| {
1000            SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
1001                actor_id: actor_id.clone(),
1002            })
1003        })
1004    }
1005
1006    fn get_actor_attribute<'b>(
1007        &'b self,
1008        actor_id: &boxcars::ActorId,
1009        property: &'static str,
1010    ) -> SubtrActorResult<&'b boxcars::Attribute> {
1011        self.get_attribute(&self.get_actor_state(actor_id)?.attributes, property)
1012    }
1013
1014    fn get_attribute<'b>(
1015        &'b self,
1016        map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1017        property: &'static str,
1018    ) -> SubtrActorResult<&'b boxcars::Attribute> {
1019        self.get_attribute_and_updated(map, property).map(|v| &v.0)
1020    }
1021
1022    fn get_attribute_and_updated<'b>(
1023        &'b self,
1024        map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1025        property: &'static str,
1026    ) -> SubtrActorResult<&'b (boxcars::Attribute, usize)> {
1027        let attribute_object_id = self.get_object_id_for_key(property)?;
1028        map.get(attribute_object_id).ok_or_else(|| {
1029            SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState { property })
1030        })
1031    }
1032
1033    fn find_ball_actor(&self) -> Option<boxcars::ActorId> {
1034        BALL_TYPES
1035            .iter()
1036            .filter_map(|ball_type| self.iter_actors_by_type(ball_type))
1037            .flat_map(|i| i)
1038            .map(|(actor_id, _)| actor_id.clone())
1039            .next()
1040    }
1041
1042    pub fn get_ball_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
1043        self.ball_actor_id.ok_or(SubtrActorError::new(
1044            SubtrActorErrorVariant::BallActorNotFound,
1045        ))
1046    }
1047
1048    pub fn get_metadata_actor_id(&self) -> SubtrActorResult<&boxcars::ActorId> {
1049        self.get_actor_ids_by_type(GAME_TYPE)?
1050            .iter()
1051            .next()
1052            .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))
1053    }
1054
1055    pub fn get_player_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1056        self.player_to_actor_id
1057            .get(&player_id)
1058            .ok_or_else(|| {
1059                SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1060                    name: "ActorId",
1061                    player_id: player_id.clone(),
1062                })
1063            })
1064            .cloned()
1065    }
1066
1067    pub fn get_car_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1068        self.player_to_car
1069            .get(&self.get_player_actor_id(player_id)?)
1070            .ok_or_else(|| {
1071                SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1072                    name: "Car",
1073                    player_id: player_id.clone(),
1074                })
1075            })
1076            .cloned()
1077    }
1078
1079    pub fn get_car_connected_actor_id(
1080        &self,
1081        player_id: &PlayerId,
1082        map: &HashMap<boxcars::ActorId, boxcars::ActorId>,
1083        name: &'static str,
1084    ) -> SubtrActorResult<boxcars::ActorId> {
1085        map.get(&self.get_car_actor_id(player_id)?)
1086            .ok_or_else(|| {
1087                SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
1088                    name,
1089                    player_id: player_id.clone(),
1090                })
1091            })
1092            .cloned()
1093    }
1094
1095    pub fn get_boost_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1096        self.get_car_connected_actor_id(player_id, &self.car_to_boost, "Boost")
1097    }
1098
1099    pub fn get_jump_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1100        self.get_car_connected_actor_id(player_id, &self.car_to_jump, "Jump")
1101    }
1102
1103    pub fn get_double_jump_actor_id(
1104        &self,
1105        player_id: &PlayerId,
1106    ) -> SubtrActorResult<boxcars::ActorId> {
1107        self.get_car_connected_actor_id(player_id, &self.car_to_double_jump, "Double Jump")
1108    }
1109
1110    pub fn get_dodge_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
1111        self.get_car_connected_actor_id(player_id, &self.car_to_dodge, "Dodge")
1112    }
1113
1114    pub fn get_actor_rigid_body(
1115        &self,
1116        actor_id: &boxcars::ActorId,
1117    ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1118        get_attribute_and_updated!(
1119            self,
1120            &self.get_actor_state(&actor_id)?.attributes,
1121            RIGID_BODY_STATE_KEY,
1122            boxcars::Attribute::RigidBody
1123        )
1124    }
1125
1126    // Actor iteration functions
1127
1128    pub fn iter_player_ids_in_order(&self) -> impl Iterator<Item = &PlayerId> {
1129        self.team_zero.iter().chain(self.team_one.iter())
1130    }
1131
1132    pub fn player_count(&self) -> usize {
1133        self.iter_player_ids_in_order().count()
1134    }
1135
1136    fn iter_actors_by_type_err(
1137        &self,
1138        name: &'static str,
1139    ) -> SubtrActorResult<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1140        Ok(self.iter_actors_by_object_id(self.get_object_id_for_key(name)?))
1141    }
1142
1143    pub fn iter_actors_by_type(
1144        &self,
1145        name: &'static str,
1146    ) -> Option<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
1147        self.iter_actors_by_type_err(name).ok()
1148    }
1149
1150    pub fn iter_actors_by_object_id<'b>(
1151        &'b self,
1152        object_id: &'b boxcars::ObjectId,
1153    ) -> impl Iterator<Item = (&'b boxcars::ActorId, &'b ActorState)> + 'b {
1154        let actor_ids = self
1155            .actor_state
1156            .actor_ids_by_type
1157            .get(object_id)
1158            .map(|v| &v[..])
1159            .unwrap_or_else(|| &EMPTY_ACTOR_IDS);
1160
1161        actor_ids
1162            .iter()
1163            // This unwrap is fine because we know the actor will exist as it is
1164            // in the actor_ids_by_type
1165            .map(move |id| (id, self.actor_state.actor_states.get(id).unwrap()))
1166    }
1167
1168    // Properties
1169
1170    /// Returns the remaining time in seconds in the game as an `i32`.
1171    pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
1172        get_actor_attribute_matching!(
1173            self,
1174            self.get_metadata_actor_id()?,
1175            SECONDS_REMAINING_KEY,
1176            boxcars::Attribute::Int
1177        )
1178        .cloned()
1179    }
1180
1181    /// Returns a boolean indicating whether ball syncing is ignored.
1182    pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
1183        let actor_id = self.get_ball_actor_id()?;
1184        get_actor_attribute_matching!(
1185            self,
1186            &actor_id,
1187            IGNORE_SYNCING_KEY,
1188            boxcars::Attribute::Boolean
1189        )
1190        .cloned()
1191    }
1192
1193    /// Returns a reference to the [`RigidBody`](boxcars::RigidBody) of the ball.
1194    pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
1195        self.ball_actor_id
1196            .ok_or(SubtrActorError::new(
1197                SubtrActorErrorVariant::BallActorNotFound,
1198            ))
1199            .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1200    }
1201
1202    /// Returns a boolean indicating whether the ball's
1203    /// [`RigidBody`](boxcars::RigidBody) exists and is not sleeping.
1204    pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
1205        Ok(self
1206            .get_ball_rigid_body()
1207            .map(|rb| !rb.sleeping)
1208            .unwrap_or(false))
1209    }
1210
1211    /// Returns a reference to the ball's [`RigidBody`](boxcars::RigidBody) and
1212    /// its last updated frame.
1213    pub fn get_ball_rigid_body_and_updated(
1214        &self,
1215    ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1216        self.ball_actor_id
1217            .ok_or(SubtrActorError::new(
1218                SubtrActorErrorVariant::BallActorNotFound,
1219            ))
1220            .and_then(|actor_id| {
1221                get_attribute_and_updated!(
1222                    self,
1223                    &self.get_actor_state(&actor_id)?.attributes,
1224                    RIGID_BODY_STATE_KEY,
1225                    boxcars::Attribute::RigidBody
1226                )
1227            })
1228    }
1229
1230    /// Returns a [`RigidBody`](boxcars::RigidBody) of the ball with applied
1231    /// velocity at the target time.
1232    pub fn get_velocity_applied_ball_rigid_body(
1233        &self,
1234        target_time: f32,
1235    ) -> SubtrActorResult<boxcars::RigidBody> {
1236        let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
1237        self.velocities_applied_rigid_body(&current_rigid_body, *frame_index, target_time)
1238    }
1239
1240    /// Returns an interpolated [`RigidBody`](boxcars::RigidBody) of the ball at
1241    /// a specified time.
1242    pub fn get_interpolated_ball_rigid_body(
1243        &self,
1244        time: f32,
1245        close_enough: f32,
1246    ) -> SubtrActorResult<boxcars::RigidBody> {
1247        self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
1248    }
1249
1250    /// Returns the name of the specified player.
1251    pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1252        get_actor_attribute_matching!(
1253            self,
1254            &self.get_player_actor_id(player_id)?,
1255            PLAYER_NAME_KEY,
1256            boxcars::Attribute::String
1257        )
1258        .cloned()
1259    }
1260
1261    /// Returns the team key for the specified player.
1262    pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
1263        let team_actor_id = self
1264            .player_to_team
1265            .get(&self.get_player_actor_id(player_id)?)
1266            .ok_or_else(|| {
1267                SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1268                    player_id: player_id.clone(),
1269                })
1270            })?;
1271        let state = self.get_actor_state(team_actor_id)?;
1272        self.object_id_to_name
1273            .get(&state.object_id)
1274            .ok_or_else(|| {
1275                SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
1276                    player_id: player_id.clone(),
1277                })
1278            })
1279            .cloned()
1280    }
1281
1282    /// Determines if the player is on team 0.
1283    pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
1284        Ok(self
1285            .get_player_team_key(player_id)?
1286            .chars()
1287            .last()
1288            .ok_or_else(|| {
1289                SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
1290                    player_id: player_id.clone(),
1291                })
1292            })?
1293            == '0')
1294    }
1295
1296    /// Returns a reference to the [`RigidBody`](boxcars::RigidBody) of the player's car.
1297    pub fn get_player_rigid_body(
1298        &self,
1299        player_id: &PlayerId,
1300    ) -> SubtrActorResult<&boxcars::RigidBody> {
1301        self.get_car_actor_id(player_id)
1302            .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
1303    }
1304
1305    /// Returns the most recent update to the [`RigidBody`](boxcars::RigidBody)
1306    /// of the player's car along with the index of the frame in which it was
1307    /// updated.
1308    pub fn get_player_rigid_body_and_updated(
1309        &self,
1310        player_id: &PlayerId,
1311    ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
1312        self.get_car_actor_id(player_id).and_then(|actor_id| {
1313            get_attribute_and_updated!(
1314                self,
1315                &self.get_actor_state(&actor_id)?.attributes,
1316                RIGID_BODY_STATE_KEY,
1317                boxcars::Attribute::RigidBody
1318            )
1319        })
1320    }
1321
1322    pub fn get_velocity_applied_player_rigid_body(
1323        &self,
1324        player_id: &PlayerId,
1325        target_time: f32,
1326    ) -> SubtrActorResult<boxcars::RigidBody> {
1327        let (current_rigid_body, frame_index) =
1328            self.get_player_rigid_body_and_updated(player_id)?;
1329        self.velocities_applied_rigid_body(&current_rigid_body, *frame_index, target_time)
1330    }
1331
1332    pub fn get_interpolated_player_rigid_body(
1333        &self,
1334        player_id: &PlayerId,
1335        time: f32,
1336        close_enough: f32,
1337    ) -> SubtrActorResult<boxcars::RigidBody> {
1338        self.get_interpolated_actor_rigid_body(
1339            &self.get_car_actor_id(player_id).unwrap(),
1340            time,
1341            close_enough,
1342        )
1343    }
1344
1345    pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
1346        self.get_boost_actor_id(player_id).and_then(|actor_id| {
1347            let boost_state = self.get_actor_state(&actor_id)?;
1348            get_derived_attribute!(
1349                boost_state.derived_attributes,
1350                BOOST_AMOUNT_KEY,
1351                boxcars::Attribute::Float
1352            )
1353            .cloned()
1354        })
1355    }
1356
1357    pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
1358        get_actor_attribute_matching!(
1359            self,
1360            &actor_id,
1361            COMPONENT_ACTIVE_KEY,
1362            boxcars::Attribute::Byte
1363        )
1364        .cloned()
1365    }
1366
1367    pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1368        self.get_boost_actor_id(player_id)
1369            .and_then(|actor_id| self.get_component_active(&actor_id))
1370    }
1371
1372    pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1373        self.get_jump_actor_id(player_id)
1374            .and_then(|actor_id| self.get_component_active(&actor_id))
1375    }
1376
1377    pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1378        self.get_double_jump_actor_id(player_id)
1379            .and_then(|actor_id| self.get_component_active(&actor_id))
1380    }
1381
1382    pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
1383        self.get_dodge_actor_id(player_id)
1384            .and_then(|actor_id| self.get_component_active(&actor_id))
1385    }
1386
1387    // Debugging
1388
1389    pub fn map_attribute_keys(
1390        &self,
1391        hash_map: &HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
1392    ) -> HashMap<String, boxcars::Attribute> {
1393        hash_map
1394            .iter()
1395            .map(|(k, (v, _updated))| {
1396                self.object_id_to_name
1397                    .get(k)
1398                    .map(|name| (name.clone(), v.clone()))
1399                    .unwrap()
1400            })
1401            .collect()
1402    }
1403
1404    pub fn all_mappings_string(&self) -> String {
1405        let pairs = [
1406            ("player_to_car", &self.player_to_car),
1407            ("player_to_team", &self.player_to_team),
1408            ("car_to_boost", &self.car_to_boost),
1409            ("car_to_jump", &self.car_to_jump),
1410            ("car_to_double_jump", &self.car_to_double_jump),
1411            ("car_to_dodge", &self.car_to_dodge),
1412        ];
1413        let strings: Vec<_> = pairs
1414            .iter()
1415            .map(|(map_name, map)| format!("{:?}: {:?}", map_name, map))
1416            .collect();
1417        strings.join("\n")
1418    }
1419
1420    pub fn actor_state_string(&self, actor_id: &boxcars::ActorId) -> String {
1421        format!(
1422            "{:?}",
1423            self.get_actor_state(actor_id)
1424                .map(|s| self.map_attribute_keys(&s.attributes))
1425        )
1426    }
1427
1428    pub fn print_actors_by_id<'b>(&self, actor_ids: impl Iterator<Item = &'b boxcars::ActorId>) {
1429        actor_ids.for_each(|actor_id| {
1430            let state = self.get_actor_state(actor_id).unwrap();
1431            println!(
1432                "{:?}\n\n\n",
1433                self.object_id_to_name.get(&state.object_id).unwrap()
1434            );
1435            println!("{:?}", self.map_attribute_keys(&state.attributes))
1436        })
1437    }
1438
1439    pub fn print_actors_of_type(&self, actor_type: &'static str) {
1440        self.iter_actors_by_type(actor_type)
1441            .unwrap()
1442            .for_each(|(_actor_id, state)| {
1443                println!("{:?}", self.map_attribute_keys(&state.attributes));
1444            });
1445    }
1446
1447    pub fn print_actor_types(&self) {
1448        let types: Vec<_> = self
1449            .actor_state
1450            .actor_ids_by_type
1451            .keys()
1452            .filter_map(|id| self.object_id_to_name.get(id))
1453            .collect();
1454        println!("{:?}", types);
1455    }
1456
1457    pub fn print_all_actors(&self) {
1458        self.actor_state
1459            .actor_states
1460            .iter()
1461            .for_each(|(actor_id, _actor_state)| {
1462                println!("{:?}", self.actor_state_string(actor_id))
1463            })
1464    }
1465}