1use super::*;
2
3impl<'a> ReplayProcessor<'a> {
4 pub fn find_update_in_direction(
6 &self,
7 current_index: usize,
8 actor_id: &boxcars::ActorId,
9 object_id: &boxcars::ObjectId,
10 direction: SearchDirection,
11 ) -> SubtrActorResult<(boxcars::Attribute, usize)> {
12 let frames = self
13 .replay
14 .network_frames
15 .as_ref()
16 .ok_or(SubtrActorError::new(
17 SubtrActorErrorVariant::NoNetworkFrames,
18 ))?;
19 match direction {
20 SearchDirection::Forward => {
21 for index in (current_index + 1)..frames.frames.len() {
22 if let Some(attribute) = frames.frames[index]
23 .updated_actors
24 .iter()
25 .find(|update| {
26 &update.actor_id == actor_id && &update.object_id == object_id
27 })
28 .map(|update| update.attribute.clone())
29 {
30 return Ok((attribute, index));
31 }
32 }
33 }
34 SearchDirection::Backward => {
35 for index in (0..current_index).rev() {
36 if let Some(attribute) = frames.frames[index]
37 .updated_actors
38 .iter()
39 .find(|update| {
40 &update.actor_id == actor_id && &update.object_id == object_id
41 })
42 .map(|update| update.attribute.clone())
43 {
44 return Ok((attribute, index));
45 }
46 }
47 }
48 }
49
50 SubtrActorError::new_result(SubtrActorErrorVariant::NoUpdateAfterFrame {
51 actor_id: *actor_id,
52 object_id: *object_id,
53 frame_index: current_index,
54 })
55 }
56
57 pub fn get_player_id_from_car_id(
59 &self,
60 actor_id: &boxcars::ActorId,
61 ) -> SubtrActorResult<PlayerId> {
62 self.get_player_id_from_actor_id(&self.get_player_actor_id_from_car_actor_id(actor_id)?)
63 }
64
65 pub(crate) fn get_player_id_from_actor_id(
67 &self,
68 actor_id: &boxcars::ActorId,
69 ) -> SubtrActorResult<PlayerId> {
70 for (player_id, player_actor_id) in self.player_to_actor_id.iter() {
71 if actor_id == player_actor_id {
72 return Ok(player_id.clone());
73 }
74 }
75 SubtrActorError::new_result(SubtrActorErrorVariant::NoMatchingPlayerId {
76 actor_id: *actor_id,
77 })
78 }
79
80 fn get_player_actor_id_from_car_actor_id(
81 &self,
82 actor_id: &boxcars::ActorId,
83 ) -> SubtrActorResult<boxcars::ActorId> {
84 self.car_to_player.get(actor_id).copied().ok_or_else(|| {
85 SubtrActorError::new(SubtrActorErrorVariant::NoMatchingPlayerId {
86 actor_id: *actor_id,
87 })
88 })
89 }
90
91 pub(crate) fn demolish_is_known(&self, demo: &DemolishAttribute, frame_index: usize) -> bool {
93 self.known_demolishes
94 .iter()
95 .any(|(existing, existing_frame_index)| {
96 existing == demo
97 && frame_index
98 .checked_sub(*existing_frame_index)
99 .or_else(|| existing_frame_index.checked_sub(frame_index))
100 .unwrap()
101 < MAX_DEMOLISH_KNOWN_FRAMES_PASSED
102 })
103 }
104
105 pub fn get_demolish_format(&self) -> Option<DemolishFormat> {
107 self.demolish_format
108 }
109
110 pub fn current_frame_boost_pad_events(&self) -> &[BoostPadEvent] {
112 &self.current_frame_boost_pad_events
113 }
114
115 pub fn current_frame_touch_events(&self) -> &[TouchEvent] {
117 &self.current_frame_touch_events
118 }
119
120 pub fn current_frame_dodge_refreshed_events(&self) -> &[DodgeRefreshedEvent] {
122 &self.current_frame_dodge_refreshed_events
123 }
124
125 pub fn current_frame_goal_events(&self) -> &[GoalEvent] {
127 &self.current_frame_goal_events
128 }
129
130 pub fn current_frame_player_stat_events(&self) -> &[PlayerStatEvent] {
132 &self.current_frame_player_stat_events
133 }
134
135 pub fn detect_demolish_format(&self) -> Option<DemolishFormat> {
137 let actors = self.iter_actors_by_type_err(CAR_TYPE).ok()?;
138 for (_actor_id, state) in actors {
139 if get_attribute_errors_expected!(
140 self,
141 &state.attributes,
142 DEMOLISH_EXTENDED_KEY,
143 boxcars::Attribute::DemolishExtended
144 )
145 .is_ok()
146 {
147 return Some(DemolishFormat::Extended);
148 }
149 if get_attribute_errors_expected!(
150 self,
151 &state.attributes,
152 DEMOLISH_GOAL_EXPLOSION_KEY,
153 boxcars::Attribute::DemolishFx
154 )
155 .is_ok()
156 {
157 return Some(DemolishFormat::Fx);
158 }
159 }
160 None
161 }
162
163 pub fn get_active_demos(
165 &self,
166 ) -> SubtrActorResult<impl Iterator<Item = DemolishAttribute> + '_> {
167 let format = self.demolish_format;
168 let actors: Vec<_> = self.iter_actors_by_type_err(CAR_TYPE)?.collect();
169 Ok(actors
170 .into_iter()
171 .filter_map(move |(_actor_id, state)| match format {
172 Some(DemolishFormat::Extended) => get_attribute_errors_expected!(
173 self,
174 &state.attributes,
175 DEMOLISH_EXTENDED_KEY,
176 boxcars::Attribute::DemolishExtended
177 )
178 .ok()
179 .map(|demo| DemolishAttribute::Extended(**demo)),
180 Some(DemolishFormat::Fx) => get_attribute_errors_expected!(
181 self,
182 &state.attributes,
183 DEMOLISH_GOAL_EXPLOSION_KEY,
184 boxcars::Attribute::DemolishFx
185 )
186 .ok()
187 .map(|demo| DemolishAttribute::Fx(**demo)),
188 None => None,
189 }))
190 }
191
192 fn get_frame(&self, frame_index: usize) -> SubtrActorResult<&boxcars::Frame> {
193 self.replay
194 .network_frames
195 .as_ref()
196 .ok_or(SubtrActorError::new(
197 SubtrActorErrorVariant::NoNetworkFrames,
198 ))?
199 .frames
200 .get(frame_index)
201 .ok_or(SubtrActorError::new(
202 SubtrActorErrorVariant::FrameIndexOutOfBounds,
203 ))
204 }
205
206 fn velocities_applied_rigid_body(
207 &self,
208 rigid_body: &boxcars::RigidBody,
209 rb_frame_index: usize,
210 target_time: f32,
211 ) -> SubtrActorResult<boxcars::RigidBody> {
212 let rb_frame = self.get_frame(rb_frame_index)?;
213 let interpolation_amount = target_time - rb_frame.time;
214 Ok(self.normalize_rigid_body(&apply_velocities_to_rigid_body(
215 rigid_body,
216 interpolation_amount,
217 )))
218 }
219
220 pub fn get_interpolated_actor_rigid_body(
222 &self,
223 actor_id: &boxcars::ActorId,
224 time: f32,
225 close_enough: f32,
226 ) -> SubtrActorResult<boxcars::RigidBody> {
227 let (frame_body, frame_index) = self.get_actor_rigid_body(actor_id)?;
228 let frame_time = self.get_frame(*frame_index)?.time;
229 let time_and_frame_difference = time - frame_time;
230
231 if time_and_frame_difference.abs() <= close_enough.abs() {
232 return Ok(self.normalize_rigid_body(frame_body));
233 }
234
235 let search_direction = if time_and_frame_difference > 0.0 {
236 util::SearchDirection::Forward
237 } else {
238 util::SearchDirection::Backward
239 };
240
241 let object_id = self.get_object_id_for_key(RIGID_BODY_STATE_KEY)?;
242
243 let (attribute, found_frame) =
244 self.find_update_in_direction(*frame_index, actor_id, object_id, search_direction)?;
245 let found_time = self.get_frame(found_frame)?.time;
246
247 let found_body = attribute_match!(attribute, boxcars::Attribute::RigidBody)?;
248
249 if (found_time - time).abs() <= close_enough {
250 return Ok(self.normalize_rigid_body(&found_body));
251 }
252
253 let (start_body, start_time, end_body, end_time) = match search_direction {
254 util::SearchDirection::Forward => (frame_body, frame_time, &found_body, found_time),
255 util::SearchDirection::Backward => (&found_body, found_time, frame_body, frame_time),
256 };
257
258 util::get_interpolated_rigid_body(start_body, start_time, end_body, end_time, time)
259 .map(|rigid_body| self.normalize_rigid_body(&rigid_body))
260 }
261
262 pub fn get_object_id_for_key(
264 &self,
265 name: &'static str,
266 ) -> SubtrActorResult<&boxcars::ObjectId> {
267 self.name_to_object_id
268 .get(name)
269 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
270 }
271
272 pub fn get_actor_ids_by_type(
274 &self,
275 name: &'static str,
276 ) -> SubtrActorResult<&[boxcars::ActorId]> {
277 self.get_object_id_for_key(name)
278 .map(|object_id| self.get_actor_ids_by_object_id(object_id))
279 }
280
281 fn get_actor_ids_by_object_id(&self, object_id: &boxcars::ObjectId) -> &[boxcars::ActorId] {
282 self.actor_state
283 .actor_ids_by_type
284 .get(object_id)
285 .map(|v| &v[..])
286 .unwrap_or_else(|| &EMPTY_ACTOR_IDS)
287 }
288
289 pub(crate) fn get_actor_state(
291 &self,
292 actor_id: &boxcars::ActorId,
293 ) -> SubtrActorResult<&ActorState> {
294 self.actor_state.actor_states.get(actor_id).ok_or_else(|| {
295 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
296 actor_id: *actor_id,
297 })
298 })
299 }
300
301 pub(crate) fn get_actor_state_or_recently_deleted(
303 &self,
304 actor_id: &boxcars::ActorId,
305 ) -> SubtrActorResult<&ActorState> {
306 self.actor_state
307 .actor_states
308 .get(actor_id)
309 .or_else(|| self.actor_state.recently_deleted_actor_states.get(actor_id))
310 .ok_or_else(|| {
311 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
312 actor_id: *actor_id,
313 })
314 })
315 }
316
317 fn get_actor_attribute<'b>(
318 &'b self,
319 actor_id: &boxcars::ActorId,
320 property: &'static str,
321 ) -> SubtrActorResult<&'b boxcars::Attribute> {
322 self.get_attribute(&self.get_actor_state(actor_id)?.attributes, property)
323 }
324
325 pub fn get_attribute<'b>(
327 &'b self,
328 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
329 property: &'static str,
330 ) -> SubtrActorResult<&'b boxcars::Attribute> {
331 self.get_attribute_and_updated(map, property).map(|v| &v.0)
332 }
333
334 pub fn get_attribute_and_updated<'b>(
336 &'b self,
337 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
338 property: &'static str,
339 ) -> SubtrActorResult<&'b (boxcars::Attribute, usize)> {
340 let attribute_object_id = self.get_object_id_for_key(property)?;
341 map.get(attribute_object_id).ok_or_else(|| {
342 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState { property })
343 })
344 }
345
346 pub(crate) fn find_ball_actor(&self) -> Option<boxcars::ActorId> {
348 BALL_TYPES
349 .iter()
350 .filter_map(|ball_type| self.iter_actors_by_type(ball_type))
351 .flatten()
352 .map(|(actor_id, _)| *actor_id)
353 .next()
354 }
355
356 pub fn get_ball_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
358 self.ball_actor_id.ok_or(SubtrActorError::new(
359 SubtrActorErrorVariant::BallActorNotFound,
360 ))
361 }
362
363 pub fn get_metadata_actor_id(&self) -> SubtrActorResult<&boxcars::ActorId> {
365 self.get_actor_ids_by_type(GAME_TYPE)?
366 .iter()
367 .next()
368 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))
369 }
370
371 pub fn get_player_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
373 self.player_to_actor_id
374 .get(player_id)
375 .ok_or_else(|| {
376 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
377 name: "ActorId",
378 player_id: player_id.clone(),
379 })
380 })
381 .cloned()
382 }
383
384 pub fn get_car_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
386 self.player_to_car
387 .get(&self.get_player_actor_id(player_id)?)
388 .ok_or_else(|| {
389 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
390 name: "Car",
391 player_id: player_id.clone(),
392 })
393 })
394 .cloned()
395 }
396
397 pub fn get_car_connected_actor_id(
399 &self,
400 player_id: &PlayerId,
401 map: &HashMap<boxcars::ActorId, boxcars::ActorId>,
402 name: &'static str,
403 ) -> SubtrActorResult<boxcars::ActorId> {
404 map.get(&self.get_car_actor_id(player_id)?)
405 .ok_or_else(|| {
406 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
407 name,
408 player_id: player_id.clone(),
409 })
410 })
411 .cloned()
412 }
413
414 pub fn get_boost_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
416 self.get_car_connected_actor_id(player_id, &self.car_to_boost, "Boost")
417 }
418
419 pub fn get_jump_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
421 self.get_car_connected_actor_id(player_id, &self.car_to_jump, "Jump")
422 }
423
424 pub fn get_double_jump_actor_id(
426 &self,
427 player_id: &PlayerId,
428 ) -> SubtrActorResult<boxcars::ActorId> {
429 self.get_car_connected_actor_id(player_id, &self.car_to_double_jump, "Double Jump")
430 }
431
432 pub fn get_dodge_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
434 self.get_car_connected_actor_id(player_id, &self.car_to_dodge, "Dodge")
435 }
436
437 pub fn get_actor_rigid_body(
439 &self,
440 actor_id: &boxcars::ActorId,
441 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
442 get_attribute_and_updated!(
443 self,
444 &self.get_actor_state(actor_id)?.attributes,
445 RIGID_BODY_STATE_KEY,
446 boxcars::Attribute::RigidBody
447 )
448 }
449
450 pub fn get_actor_rigid_body_or_recently_deleted(
452 &self,
453 actor_id: &boxcars::ActorId,
454 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
455 get_attribute_and_updated!(
456 self,
457 &self
458 .get_actor_state_or_recently_deleted(actor_id)?
459 .attributes,
460 RIGID_BODY_STATE_KEY,
461 boxcars::Attribute::RigidBody
462 )
463 }
464
465 pub fn iter_player_ids_in_order(&self) -> impl Iterator<Item = &PlayerId> {
467 self.team_zero.iter().chain(self.team_one.iter())
468 }
469
470 pub fn current_in_game_team_player_counts(&self) -> [usize; 2] {
472 let mut counts = [0, 0];
473 let Ok(player_actor_ids) = self.get_actor_ids_by_type(PLAYER_TYPE) else {
474 return counts;
475 };
476 let mut seen_players = std::collections::HashSet::new();
477
478 for actor_id in player_actor_ids {
479 let Ok(player_id) = self.get_player_id_from_actor_id(actor_id) else {
480 continue;
481 };
482 if !seen_players.insert(player_id) {
483 continue;
484 }
485
486 let Some(team_actor_id) = self.player_to_team.get(actor_id) else {
487 continue;
488 };
489 let Ok(team_state) = self.get_actor_state(team_actor_id) else {
490 continue;
491 };
492 let Some(team_name) = self.object_id_to_name.get(&team_state.object_id) else {
493 continue;
494 };
495
496 match team_name.chars().last() {
497 Some('0') => counts[0] += 1,
498 Some('1') => counts[1] += 1,
499 _ => {}
500 }
501 }
502
503 counts
504 }
505
506 pub fn player_count(&self) -> usize {
508 self.iter_player_ids_in_order().count()
509 }
510
511 pub fn get_player_names(&self) -> HashMap<PlayerId, String> {
513 self.iter_player_ids_in_order()
514 .filter_map(|player_id| {
515 self.get_player_name(player_id)
516 .ok()
517 .map(|name| (player_id.clone(), name))
518 })
519 .collect()
520 }
521
522 pub(crate) fn iter_actors_by_type_err(
524 &self,
525 name: &'static str,
526 ) -> SubtrActorResult<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
527 Ok(self.iter_actors_by_object_id(self.get_object_id_for_key(name)?))
528 }
529
530 pub fn iter_actors_by_type(
532 &self,
533 name: &'static str,
534 ) -> Option<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
535 self.iter_actors_by_type_err(name).ok()
536 }
537
538 pub fn iter_actors_by_object_id<'b>(
540 &'b self,
541 object_id: &'b boxcars::ObjectId,
542 ) -> impl Iterator<Item = (&'b boxcars::ActorId, &'b ActorState)> + 'b {
543 let actor_ids = self
544 .actor_state
545 .actor_ids_by_type
546 .get(object_id)
547 .map(|v| &v[..])
548 .unwrap_or_else(|| &EMPTY_ACTOR_IDS);
549
550 actor_ids
551 .iter()
552 .map(move |id| (id, self.actor_state.actor_states.get(id).unwrap()))
553 }
554
555 pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
557 get_actor_attribute_matching!(
558 self,
559 self.get_metadata_actor_id()?,
560 SECONDS_REMAINING_KEY,
561 boxcars::Attribute::Int
562 )
563 .cloned()
564 }
565
566 pub fn get_replicated_state_name(&self) -> SubtrActorResult<i32> {
568 get_actor_attribute_matching!(
569 self,
570 self.get_metadata_actor_id()?,
571 REPLICATED_STATE_NAME_KEY,
572 boxcars::Attribute::Int
573 )
574 .cloned()
575 }
576
577 pub fn get_replicated_game_state_time_remaining(&self) -> SubtrActorResult<i32> {
579 get_actor_attribute_matching!(
580 self,
581 self.get_metadata_actor_id()?,
582 REPLICATED_GAME_STATE_TIME_REMAINING_KEY,
583 boxcars::Attribute::Int
584 )
585 .cloned()
586 }
587
588 pub fn get_ball_has_been_hit(&self) -> SubtrActorResult<bool> {
590 get_actor_attribute_matching!(
591 self,
592 self.get_metadata_actor_id()?,
593 BALL_HAS_BEEN_HIT_KEY,
594 boxcars::Attribute::Boolean
595 )
596 .cloned()
597 }
598
599 pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
601 let actor_id = self.get_ball_actor_id()?;
602 get_actor_attribute_matching!(
603 self,
604 &actor_id,
605 IGNORE_SYNCING_KEY,
606 boxcars::Attribute::Boolean
607 )
608 .cloned()
609 }
610
611 pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
613 self.ball_actor_id
614 .ok_or(SubtrActorError::new(
615 SubtrActorErrorVariant::BallActorNotFound,
616 ))
617 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
618 }
619
620 pub fn get_normalized_ball_rigid_body(&self) -> SubtrActorResult<boxcars::RigidBody> {
622 self.get_ball_rigid_body()
623 .map(|rigid_body| self.normalize_rigid_body(rigid_body))
624 }
625
626 pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
628 Ok(self
629 .get_ball_rigid_body()
630 .map(|rb| !rb.sleeping)
631 .unwrap_or(false))
632 }
633
634 pub fn get_ball_rigid_body_and_updated(
636 &self,
637 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
638 self.ball_actor_id
639 .ok_or(SubtrActorError::new(
640 SubtrActorErrorVariant::BallActorNotFound,
641 ))
642 .and_then(|actor_id| {
643 get_attribute_and_updated!(
644 self,
645 &self.get_actor_state(&actor_id)?.attributes,
646 RIGID_BODY_STATE_KEY,
647 boxcars::Attribute::RigidBody
648 )
649 })
650 }
651
652 pub fn get_velocity_applied_ball_rigid_body(
654 &self,
655 target_time: f32,
656 ) -> SubtrActorResult<boxcars::RigidBody> {
657 let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
658 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
659 }
660
661 pub fn get_interpolated_ball_rigid_body(
663 &self,
664 time: f32,
665 close_enough: f32,
666 ) -> SubtrActorResult<boxcars::RigidBody> {
667 self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
668 }
669
670 pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
672 get_actor_attribute_matching!(
673 self,
674 &self.get_player_actor_id(player_id)?,
675 PLAYER_NAME_KEY,
676 boxcars::Attribute::String
677 )
678 .cloned()
679 }
680
681 fn get_player_int_stat(
682 &self,
683 player_id: &PlayerId,
684 key: &'static str,
685 ) -> SubtrActorResult<i32> {
686 get_actor_attribute_matching!(
687 self,
688 &self.get_player_actor_id(player_id)?,
689 key,
690 boxcars::Attribute::Int
691 )
692 .cloned()
693 }
694
695 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
697 let team_actor_id = self
698 .player_to_team
699 .get(&self.get_player_actor_id(player_id)?)
700 .ok_or_else(|| {
701 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
702 player_id: player_id.clone(),
703 })
704 })?;
705 let state = self.get_actor_state(team_actor_id)?;
706 self.object_id_to_name
707 .get(&state.object_id)
708 .ok_or_else(|| {
709 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
710 player_id: player_id.clone(),
711 })
712 })
713 .cloned()
714 }
715
716 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
718 Ok(self
719 .get_player_team_key(player_id)?
720 .chars()
721 .last()
722 .ok_or_else(|| {
723 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
724 player_id: player_id.clone(),
725 })
726 })?
727 == '0')
728 }
729
730 pub(crate) fn get_team_actor_id_for_side(
732 &self,
733 is_team_0: bool,
734 ) -> SubtrActorResult<boxcars::ActorId> {
735 let player_id = if is_team_0 {
736 self.team_zero.first()
737 } else {
738 self.team_one.first()
739 }
740 .ok_or(SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))?;
741
742 self.player_to_team
743 .get(&self.get_player_actor_id(player_id)?)
744 .copied()
745 .ok_or_else(|| {
746 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
747 name: "Team",
748 player_id: player_id.clone(),
749 })
750 })
751 }
752
753 pub fn get_team_score(&self, is_team_0: bool) -> SubtrActorResult<i32> {
755 let team_actor_id = self.get_team_actor_id_for_side(is_team_0)?;
756 get_actor_attribute_matching!(
757 self,
758 &team_actor_id,
759 TEAM_GAME_SCORE_KEY,
760 boxcars::Attribute::Int
761 )
762 .cloned()
763 }
764
765 pub fn get_team_scores(&self) -> SubtrActorResult<(i32, i32)> {
767 Ok((self.get_team_score(true)?, self.get_team_score(false)?))
768 }
769
770 pub fn get_player_rigid_body(
772 &self,
773 player_id: &PlayerId,
774 ) -> SubtrActorResult<&boxcars::RigidBody> {
775 self.get_car_actor_id(player_id)
776 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
777 }
778
779 pub fn get_normalized_player_rigid_body(
781 &self,
782 player_id: &PlayerId,
783 ) -> SubtrActorResult<boxcars::RigidBody> {
784 self.get_player_rigid_body(player_id)
785 .map(|rigid_body| self.normalize_rigid_body(rigid_body))
786 }
787
788 pub fn get_player_rigid_body_and_updated(
790 &self,
791 player_id: &PlayerId,
792 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
793 self.get_car_actor_id(player_id).and_then(|actor_id| {
794 get_attribute_and_updated!(
795 self,
796 &self.get_actor_state(&actor_id)?.attributes,
797 RIGID_BODY_STATE_KEY,
798 boxcars::Attribute::RigidBody
799 )
800 })
801 }
802
803 pub fn get_player_rigid_body_and_updated_or_recently_deleted(
805 &self,
806 player_id: &PlayerId,
807 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
808 self.get_car_actor_id(player_id)
809 .and_then(|actor_id| self.get_actor_rigid_body_or_recently_deleted(&actor_id))
810 }
811
812 pub fn get_velocity_applied_player_rigid_body(
814 &self,
815 player_id: &PlayerId,
816 target_time: f32,
817 ) -> SubtrActorResult<boxcars::RigidBody> {
818 let (current_rigid_body, frame_index) =
819 self.get_player_rigid_body_and_updated(player_id)?;
820 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
821 }
822
823 pub fn get_interpolated_player_rigid_body(
825 &self,
826 player_id: &PlayerId,
827 time: f32,
828 close_enough: f32,
829 ) -> SubtrActorResult<boxcars::RigidBody> {
830 self.get_car_actor_id(player_id).and_then(|car_actor_id| {
831 self.get_interpolated_actor_rigid_body(&car_actor_id, time, close_enough)
832 })
833 }
834
835 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
837 self.get_boost_actor_id(player_id).and_then(|actor_id| {
838 let boost_state = self.get_actor_state(&actor_id)?;
839 get_derived_attribute!(
840 boost_state.derived_attributes,
841 BOOST_AMOUNT_KEY,
842 boxcars::Attribute::Float
843 )
844 .cloned()
845 })
846 }
847
848 pub fn get_player_last_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
850 self.get_boost_actor_id(player_id).and_then(|actor_id| {
851 let boost_state = self.get_actor_state(&actor_id)?;
852 get_derived_attribute!(
853 boost_state.derived_attributes,
854 LAST_BOOST_AMOUNT_KEY,
855 boxcars::Attribute::Byte
856 )
857 .map(|value| *value as f32)
858 })
859 }
860
861 pub fn get_player_boost_percentage(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
863 self.get_player_boost_level(player_id)
864 .map(boost_amount_to_percent)
865 }
866
867 pub fn get_player_match_assists(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
869 self.get_player_int_stat(player_id, MATCH_ASSISTS_KEY)
870 }
871
872 pub fn get_player_match_goals(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
874 self.get_player_int_stat(player_id, MATCH_GOALS_KEY)
875 }
876
877 pub fn get_player_match_saves(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
879 self.get_player_int_stat(player_id, MATCH_SAVES_KEY)
880 }
881
882 pub fn get_player_match_score(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
884 self.get_player_int_stat(player_id, MATCH_SCORE_KEY)
885 }
886
887 pub fn get_player_match_shots(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
889 self.get_player_int_stat(player_id, MATCH_SHOTS_KEY)
890 }
891
892 pub fn get_ball_hit_team_num(&self) -> SubtrActorResult<u8> {
894 let ball_actor_id = self.get_ball_actor_id()?;
895 get_actor_attribute_matching!(
896 self,
897 &ball_actor_id,
898 BALL_HIT_TEAM_NUM_KEY,
899 boxcars::Attribute::Byte
900 )
901 .cloned()
902 }
903
904 pub fn get_scored_on_team_num(&self) -> SubtrActorResult<u8> {
906 get_actor_attribute_matching!(
907 self,
908 self.get_metadata_actor_id()?,
909 REPLICATED_SCORED_ON_TEAM_KEY,
910 boxcars::Attribute::Byte
911 )
912 .cloned()
913 }
914
915 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
917 get_actor_attribute_matching!(
918 self,
919 &actor_id,
920 COMPONENT_ACTIVE_KEY,
921 boxcars::Attribute::Byte
922 )
923 .cloned()
924 }
925
926 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
928 self.get_boost_actor_id(player_id)
929 .and_then(|actor_id| self.get_component_active(&actor_id))
930 }
931
932 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
934 self.get_jump_actor_id(player_id)
935 .and_then(|actor_id| self.get_component_active(&actor_id))
936 }
937
938 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
940 self.get_double_jump_actor_id(player_id)
941 .and_then(|actor_id| self.get_component_active(&actor_id))
942 }
943
944 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
946 self.get_dodge_actor_id(player_id)
947 .and_then(|actor_id| self.get_component_active(&actor_id))
948 }
949
950 pub fn get_powerslide_active(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
952 get_actor_attribute_matching!(
953 self,
954 &self.get_car_actor_id(player_id)?,
955 HANDBRAKE_KEY,
956 boxcars::Attribute::Boolean
957 )
958 .cloned()
959 }
960}