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 let normalized_rigid_body = self.normalize_rigid_body(rigid_body);
215 Ok(apply_velocities_to_rigid_body(
216 &normalized_rigid_body,
217 interpolation_amount,
218 ))
219 }
220
221 pub fn get_interpolated_actor_rigid_body(
223 &self,
224 actor_id: &boxcars::ActorId,
225 time: f32,
226 close_enough: f32,
227 ) -> SubtrActorResult<boxcars::RigidBody> {
228 let (frame_body, frame_index) = self.get_actor_rigid_body(actor_id)?;
229 let frame_time = self.get_frame(*frame_index)?.time;
230 let time_and_frame_difference = time - frame_time;
231
232 if time_and_frame_difference.abs() <= close_enough.abs() {
233 return Ok(self.normalize_rigid_body(frame_body));
234 }
235
236 let search_direction = if time_and_frame_difference > 0.0 {
237 SearchDirection::Forward
238 } else {
239 SearchDirection::Backward
240 };
241
242 let object_id = self.get_object_id_for_key(RIGID_BODY_STATE_KEY)?;
243
244 let (attribute, found_frame) =
245 self.find_update_in_direction(*frame_index, actor_id, object_id, search_direction)?;
246 let found_time = self.get_frame(found_frame)?.time;
247
248 let found_body = attribute_match!(attribute, boxcars::Attribute::RigidBody)?;
249
250 if (found_time - time).abs() <= close_enough {
251 return Ok(self.normalize_rigid_body(&found_body));
252 }
253
254 let (start_body, start_time, end_body, end_time) = match search_direction {
255 SearchDirection::Forward => (frame_body, frame_time, &found_body, found_time),
256 SearchDirection::Backward => (&found_body, found_time, frame_body, frame_time),
257 };
258 let start_body = self.normalize_rigid_body(start_body);
259 let end_body = self.normalize_rigid_body(end_body);
260
261 get_interpolated_rigid_body(&start_body, start_time, &end_body, end_time, time)
262 }
263
264 pub fn get_object_id_for_key(
266 &self,
267 name: &'static str,
268 ) -> SubtrActorResult<&boxcars::ObjectId> {
269 self.name_to_object_id
270 .get(name)
271 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound { name }))
272 }
273
274 pub fn get_actor_ids_by_type(
276 &self,
277 name: &'static str,
278 ) -> SubtrActorResult<&[boxcars::ActorId]> {
279 self.get_object_id_for_key(name)
280 .map(|object_id| self.get_actor_ids_by_object_id(object_id))
281 }
282
283 pub(crate) fn get_actor_ids_by_object_id(
284 &self,
285 object_id: &boxcars::ObjectId,
286 ) -> &[boxcars::ActorId] {
287 self.actor_state
288 .actor_ids_by_type
289 .get(object_id)
290 .map(|v| &v[..])
291 .unwrap_or_else(|| &EMPTY_ACTOR_IDS)
292 }
293
294 pub(crate) fn get_actor_state(
296 &self,
297 actor_id: &boxcars::ActorId,
298 ) -> SubtrActorResult<&ActorState> {
299 self.actor_state.actor_states.get(actor_id).ok_or_else(|| {
300 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
301 actor_id: *actor_id,
302 })
303 })
304 }
305
306 pub(crate) fn get_actor_state_or_recently_deleted(
308 &self,
309 actor_id: &boxcars::ActorId,
310 ) -> SubtrActorResult<&ActorState> {
311 self.actor_state
312 .actor_states
313 .get(actor_id)
314 .or_else(|| self.actor_state.recently_deleted_actor_states.get(actor_id))
315 .ok_or_else(|| {
316 SubtrActorError::new(SubtrActorErrorVariant::NoStateForActorId {
317 actor_id: *actor_id,
318 })
319 })
320 }
321
322 fn get_actor_attribute<'b>(
323 &'b self,
324 actor_id: &boxcars::ActorId,
325 property: &'static str,
326 ) -> SubtrActorResult<&'b boxcars::Attribute> {
327 self.get_attribute(&self.get_actor_state(actor_id)?.attributes, property)
328 }
329
330 pub fn get_attribute<'b>(
332 &'b self,
333 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
334 property: &'static str,
335 ) -> SubtrActorResult<&'b boxcars::Attribute> {
336 self.get_attribute_and_updated(map, property).map(|v| &v.0)
337 }
338
339 pub fn get_attribute_and_updated<'b>(
341 &'b self,
342 map: &'b HashMap<boxcars::ObjectId, (boxcars::Attribute, usize)>,
343 property: &'static str,
344 ) -> SubtrActorResult<&'b (boxcars::Attribute, usize)> {
345 let attribute_object_id = self.get_object_id_for_key(property)?;
346 map.get(attribute_object_id).ok_or_else(|| {
347 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState { property })
348 })
349 }
350
351 pub(crate) fn find_ball_actor(&self) -> Option<boxcars::ActorId> {
353 BALL_TYPES
354 .iter()
355 .filter_map(|ball_type| self.iter_actors_by_type(ball_type))
356 .flatten()
357 .map(|(actor_id, _)| *actor_id)
358 .next()
359 }
360
361 pub fn get_ball_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
363 self.ball_actor_id.ok_or(SubtrActorError::new(
364 SubtrActorErrorVariant::BallActorNotFound,
365 ))
366 }
367
368 pub fn get_metadata_actor_id(&self) -> SubtrActorResult<boxcars::ActorId> {
370 if let Ok(actor_ids) = self.get_actor_ids_by_type(GAME_TYPE) {
371 if let Some(actor_id) = actor_ids.first() {
372 return Ok(*actor_id);
373 }
374 }
375
376 let metadata_object_ids = [
377 self.cached_object_ids.seconds_remaining,
378 self.cached_object_ids.replicated_state_name,
379 self.cached_object_ids.replicated_game_state_time_remaining,
380 self.cached_object_ids.ball_has_been_hit,
381 ];
382
383 self.actor_state
384 .actor_states
385 .iter()
386 .filter_map(|(actor_id, actor_state)| {
387 let metadata_attribute_count = metadata_object_ids
388 .iter()
389 .flatten()
390 .filter(|object_id| actor_state.attributes.contains_key(object_id))
391 .count();
392 (metadata_attribute_count > 0).then_some((
393 metadata_attribute_count,
394 std::cmp::Reverse(*actor_id),
395 *actor_id,
396 ))
397 })
398 .max()
399 .map(|(_, _, actor_id)| actor_id)
400 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))
401 }
402
403 pub fn get_player_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
405 self.player_to_actor_id
406 .get(player_id)
407 .ok_or_else(|| {
408 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
409 name: "ActorId",
410 player_id: player_id.clone(),
411 })
412 })
413 .cloned()
414 }
415
416 pub fn get_car_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
418 self.player_to_car
419 .get(&self.get_player_actor_id(player_id)?)
420 .ok_or_else(|| {
421 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
422 name: "Car",
423 player_id: player_id.clone(),
424 })
425 })
426 .cloned()
427 }
428
429 pub fn get_car_connected_actor_id(
431 &self,
432 player_id: &PlayerId,
433 map: &HashMap<boxcars::ActorId, boxcars::ActorId>,
434 name: &'static str,
435 ) -> SubtrActorResult<boxcars::ActorId> {
436 map.get(&self.get_car_actor_id(player_id)?)
437 .ok_or_else(|| {
438 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
439 name,
440 player_id: player_id.clone(),
441 })
442 })
443 .cloned()
444 }
445
446 pub fn get_boost_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
448 self.get_car_connected_actor_id(player_id, &self.car_to_boost, "Boost")
449 }
450
451 pub fn get_jump_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
453 self.get_car_connected_actor_id(player_id, &self.car_to_jump, "Jump")
454 }
455
456 pub fn get_double_jump_actor_id(
458 &self,
459 player_id: &PlayerId,
460 ) -> SubtrActorResult<boxcars::ActorId> {
461 self.get_car_connected_actor_id(player_id, &self.car_to_double_jump, "Double Jump")
462 }
463
464 pub fn get_dodge_actor_id(&self, player_id: &PlayerId) -> SubtrActorResult<boxcars::ActorId> {
466 self.get_car_connected_actor_id(player_id, &self.car_to_dodge, "Dodge")
467 }
468
469 pub fn get_actor_rigid_body(
471 &self,
472 actor_id: &boxcars::ActorId,
473 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
474 get_attribute_and_updated!(
475 self,
476 &self.get_actor_state(actor_id)?.attributes,
477 RIGID_BODY_STATE_KEY,
478 boxcars::Attribute::RigidBody
479 )
480 }
481
482 pub fn get_actor_rigid_body_or_recently_deleted(
484 &self,
485 actor_id: &boxcars::ActorId,
486 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
487 get_attribute_and_updated!(
488 self,
489 &self
490 .get_actor_state_or_recently_deleted(actor_id)?
491 .attributes,
492 RIGID_BODY_STATE_KEY,
493 boxcars::Attribute::RigidBody
494 )
495 }
496
497 pub fn iter_player_ids_in_order(&self) -> impl Iterator<Item = &PlayerId> {
499 self.team_zero.iter().chain(self.team_one.iter())
500 }
501
502 pub fn current_in_game_team_player_counts(&self) -> [usize; 2] {
504 let mut counts = [0, 0];
505 let Ok(player_actor_ids) = self.get_actor_ids_by_type(PLAYER_TYPE) else {
506 return counts;
507 };
508 let mut seen_players = std::collections::HashSet::new();
509
510 for actor_id in player_actor_ids {
511 let Ok(player_id) = self.get_player_id_from_actor_id(actor_id) else {
512 continue;
513 };
514 if !seen_players.insert(player_id) {
515 continue;
516 }
517
518 let Some(team_actor_id) = self.player_to_team.get(actor_id) else {
519 continue;
520 };
521 let Ok(team_state) = self.get_actor_state(team_actor_id) else {
522 continue;
523 };
524 let Some(team_name) = self.object_id_to_name.get(&team_state.object_id) else {
525 continue;
526 };
527
528 match team_name.chars().last() {
529 Some('0') => counts[0] += 1,
530 Some('1') => counts[1] += 1,
531 _ => {}
532 }
533 }
534
535 counts
536 }
537
538 pub fn player_count(&self) -> usize {
540 self.iter_player_ids_in_order().count()
541 }
542
543 pub fn get_player_names(&self) -> HashMap<PlayerId, String> {
545 self.iter_player_ids_in_order()
546 .filter_map(|player_id| {
547 self.get_player_name(player_id)
548 .ok()
549 .map(|name| (player_id.clone(), name))
550 })
551 .collect()
552 }
553
554 pub(crate) fn iter_actors_by_type_err(
556 &self,
557 name: &'static str,
558 ) -> SubtrActorResult<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
559 Ok(self.iter_actors_by_object_id(self.get_object_id_for_key(name)?))
560 }
561
562 pub fn iter_actors_by_type(
564 &self,
565 name: &'static str,
566 ) -> Option<impl Iterator<Item = (&boxcars::ActorId, &ActorState)>> {
567 self.iter_actors_by_type_err(name).ok()
568 }
569
570 pub fn iter_actors_by_object_id<'b>(
572 &'b self,
573 object_id: &'b boxcars::ObjectId,
574 ) -> impl Iterator<Item = (&'b boxcars::ActorId, &'b ActorState)> + 'b {
575 let actor_ids = self
576 .actor_state
577 .actor_ids_by_type
578 .get(object_id)
579 .map(|v| &v[..])
580 .unwrap_or_else(|| &EMPTY_ACTOR_IDS);
581
582 actor_ids
583 .iter()
584 .map(move |id| (id, self.actor_state.actor_states.get(id).unwrap()))
585 }
586
587 pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
589 let seconds_remaining_object_id =
590 self.cached_object_ids.seconds_remaining.ok_or_else(|| {
591 SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound {
592 name: SECONDS_REMAINING_KEY,
593 })
594 })?;
595 let metadata_actor_id = self.get_metadata_actor_id()?;
596 let metadata_state = self.get_actor_state(&metadata_actor_id)?;
597 metadata_state
598 .attributes
599 .get(&seconds_remaining_object_id)
600 .ok_or_else(|| {
601 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState {
602 property: SECONDS_REMAINING_KEY,
603 })
604 })
605 .and_then(|(attribute, _)| attribute_match!(attribute, boxcars::Attribute::Int))
606 .copied()
607 }
608
609 pub fn get_replicated_state_name(&self) -> SubtrActorResult<i32> {
611 get_actor_attribute_matching!(
612 self,
613 &self.get_metadata_actor_id()?,
614 REPLICATED_STATE_NAME_KEY,
615 boxcars::Attribute::Int
616 )
617 .cloned()
618 }
619
620 pub fn get_replicated_game_state_time_remaining(&self) -> SubtrActorResult<i32> {
622 get_actor_attribute_matching!(
623 self,
624 &self.get_metadata_actor_id()?,
625 REPLICATED_GAME_STATE_TIME_REMAINING_KEY,
626 boxcars::Attribute::Int
627 )
628 .cloned()
629 }
630
631 pub fn get_ball_has_been_hit(&self) -> SubtrActorResult<bool> {
633 get_actor_attribute_matching!(
634 self,
635 &self.get_metadata_actor_id()?,
636 BALL_HAS_BEEN_HIT_KEY,
637 boxcars::Attribute::Boolean
638 )
639 .cloned()
640 }
641
642 pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
644 let actor_id = self.get_ball_actor_id()?;
645 get_actor_attribute_matching!(
646 self,
647 &actor_id,
648 IGNORE_SYNCING_KEY,
649 boxcars::Attribute::Boolean
650 )
651 .cloned()
652 }
653
654 pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
656 self.ball_actor_id
657 .ok_or(SubtrActorError::new(
658 SubtrActorErrorVariant::BallActorNotFound,
659 ))
660 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
661 }
662
663 pub fn get_normalized_ball_rigid_body(&self) -> SubtrActorResult<boxcars::RigidBody> {
665 self.get_ball_rigid_body()
666 .map(|rigid_body| self.normalize_rigid_body(rigid_body))
667 }
668
669 pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
671 Ok(self
672 .get_ball_rigid_body()
673 .map(|rb| !rb.sleeping)
674 .unwrap_or(false))
675 }
676
677 pub fn get_ball_rigid_body_and_updated(
679 &self,
680 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
681 self.ball_actor_id
682 .ok_or(SubtrActorError::new(
683 SubtrActorErrorVariant::BallActorNotFound,
684 ))
685 .and_then(|actor_id| {
686 get_attribute_and_updated!(
687 self,
688 &self.get_actor_state(&actor_id)?.attributes,
689 RIGID_BODY_STATE_KEY,
690 boxcars::Attribute::RigidBody
691 )
692 })
693 }
694
695 pub fn get_velocity_applied_ball_rigid_body(
697 &self,
698 target_time: f32,
699 ) -> SubtrActorResult<boxcars::RigidBody> {
700 let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
701 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
702 }
703
704 pub fn get_interpolated_ball_rigid_body(
706 &self,
707 time: f32,
708 close_enough: f32,
709 ) -> SubtrActorResult<boxcars::RigidBody> {
710 self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
711 }
712
713 pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
715 get_actor_attribute_matching!(
716 self,
717 &self.get_player_actor_id(player_id)?,
718 PLAYER_NAME_KEY,
719 boxcars::Attribute::String
720 )
721 .cloned()
722 }
723
724 fn get_player_int_stat(
725 &self,
726 player_id: &PlayerId,
727 key: &'static str,
728 ) -> SubtrActorResult<i32> {
729 get_actor_attribute_matching!(
730 self,
731 &self.get_player_actor_id(player_id)?,
732 key,
733 boxcars::Attribute::Int
734 )
735 .cloned()
736 }
737
738 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
740 let team_actor_id = self
741 .player_to_team
742 .get(&self.get_player_actor_id(player_id)?)
743 .ok_or_else(|| {
744 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
745 player_id: player_id.clone(),
746 })
747 })?;
748 let state = self.get_actor_state(team_actor_id)?;
749 self.object_id_to_name
750 .get(&state.object_id)
751 .ok_or_else(|| {
752 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
753 player_id: player_id.clone(),
754 })
755 })
756 .cloned()
757 }
758
759 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
761 Ok(self
762 .get_player_team_key(player_id)?
763 .chars()
764 .last()
765 .ok_or_else(|| {
766 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
767 player_id: player_id.clone(),
768 })
769 })?
770 == '0')
771 }
772
773 pub(crate) fn get_team_actor_id_for_side(
775 &self,
776 is_team_0: bool,
777 ) -> SubtrActorResult<boxcars::ActorId> {
778 let player_id = if is_team_0 {
779 self.team_zero.first()
780 } else {
781 self.team_one.first()
782 }
783 .ok_or(SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))?;
784
785 self.player_to_team
786 .get(&self.get_player_actor_id(player_id)?)
787 .copied()
788 .ok_or_else(|| {
789 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
790 name: "Team",
791 player_id: player_id.clone(),
792 })
793 })
794 }
795
796 pub fn get_team_score(&self, is_team_0: bool) -> SubtrActorResult<i32> {
798 let team_actor_id = self.get_team_actor_id_for_side(is_team_0)?;
799 get_actor_attribute_matching!(
800 self,
801 &team_actor_id,
802 TEAM_GAME_SCORE_KEY,
803 boxcars::Attribute::Int
804 )
805 .cloned()
806 }
807
808 pub fn get_team_scores(&self) -> SubtrActorResult<(i32, i32)> {
810 Ok((self.get_team_score(true)?, self.get_team_score(false)?))
811 }
812
813 pub fn get_player_rigid_body(
815 &self,
816 player_id: &PlayerId,
817 ) -> SubtrActorResult<&boxcars::RigidBody> {
818 self.get_car_actor_id(player_id)
819 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
820 }
821
822 pub fn get_normalized_player_rigid_body(
824 &self,
825 player_id: &PlayerId,
826 ) -> SubtrActorResult<boxcars::RigidBody> {
827 self.get_player_rigid_body(player_id)
828 .map(|rigid_body| self.normalize_rigid_body(rigid_body))
829 }
830
831 pub fn get_player_rigid_body_and_updated(
833 &self,
834 player_id: &PlayerId,
835 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
836 self.get_car_actor_id(player_id).and_then(|actor_id| {
837 get_attribute_and_updated!(
838 self,
839 &self.get_actor_state(&actor_id)?.attributes,
840 RIGID_BODY_STATE_KEY,
841 boxcars::Attribute::RigidBody
842 )
843 })
844 }
845
846 pub fn get_player_rigid_body_and_updated_or_recently_deleted(
848 &self,
849 player_id: &PlayerId,
850 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
851 self.get_car_actor_id(player_id)
852 .and_then(|actor_id| self.get_actor_rigid_body_or_recently_deleted(&actor_id))
853 }
854
855 pub fn get_velocity_applied_player_rigid_body(
857 &self,
858 player_id: &PlayerId,
859 target_time: f32,
860 ) -> SubtrActorResult<boxcars::RigidBody> {
861 let (current_rigid_body, frame_index) =
862 self.get_player_rigid_body_and_updated(player_id)?;
863 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
864 }
865
866 pub fn get_interpolated_player_rigid_body(
868 &self,
869 player_id: &PlayerId,
870 time: f32,
871 close_enough: f32,
872 ) -> SubtrActorResult<boxcars::RigidBody> {
873 self.get_car_actor_id(player_id).and_then(|car_actor_id| {
874 self.get_interpolated_actor_rigid_body(&car_actor_id, time, close_enough)
875 })
876 }
877
878 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
880 self.get_boost_actor_id(player_id).and_then(|actor_id| {
881 let boost_state = self.get_actor_state(&actor_id)?;
882 get_derived_attribute!(
883 boost_state.derived_attributes,
884 BOOST_AMOUNT_KEY,
885 boxcars::Attribute::Float
886 )
887 .cloned()
888 })
889 }
890
891 pub fn get_player_last_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
893 self.get_boost_actor_id(player_id).and_then(|actor_id| {
894 let boost_state = self.get_actor_state(&actor_id)?;
895 get_derived_attribute!(
896 boost_state.derived_attributes,
897 LAST_BOOST_AMOUNT_KEY,
898 boxcars::Attribute::Byte
899 )
900 .map(|value| *value as f32)
901 })
902 }
903
904 pub fn get_player_boost_percentage(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
906 self.get_player_boost_level(player_id)
907 .map(boost_amount_to_percent)
908 }
909
910 pub fn get_player_match_assists(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
912 self.get_player_int_stat(player_id, MATCH_ASSISTS_KEY)
913 }
914
915 pub fn get_player_match_goals(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
917 self.get_player_int_stat(player_id, MATCH_GOALS_KEY)
918 }
919
920 pub fn get_player_match_saves(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
922 self.get_player_int_stat(player_id, MATCH_SAVES_KEY)
923 }
924
925 pub fn get_player_match_score(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
927 self.get_player_int_stat(player_id, MATCH_SCORE_KEY)
928 }
929
930 pub fn get_player_match_shots(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
932 self.get_player_int_stat(player_id, MATCH_SHOTS_KEY)
933 }
934
935 pub fn get_ball_hit_team_num(&self) -> SubtrActorResult<u8> {
937 let ball_actor_id = self.get_ball_actor_id()?;
938 get_actor_attribute_matching!(
939 self,
940 &ball_actor_id,
941 BALL_HIT_TEAM_NUM_KEY,
942 boxcars::Attribute::Byte
943 )
944 .cloned()
945 }
946
947 pub fn get_scored_on_team_num(&self) -> SubtrActorResult<u8> {
949 get_actor_attribute_matching!(
950 self,
951 &self.get_metadata_actor_id()?,
952 REPLICATED_SCORED_ON_TEAM_KEY,
953 boxcars::Attribute::Byte
954 )
955 .cloned()
956 }
957
958 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
960 get_actor_attribute_matching!(
961 self,
962 &actor_id,
963 COMPONENT_ACTIVE_KEY,
964 boxcars::Attribute::Byte
965 )
966 .cloned()
967 }
968
969 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
971 self.get_boost_actor_id(player_id)
972 .and_then(|actor_id| self.get_component_active(&actor_id))
973 }
974
975 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
977 self.get_jump_actor_id(player_id)
978 .and_then(|actor_id| self.get_component_active(&actor_id))
979 }
980
981 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
983 self.get_double_jump_actor_id(player_id)
984 .and_then(|actor_id| self.get_component_active(&actor_id))
985 }
986
987 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
989 self.get_dodge_actor_id(player_id)
990 .and_then(|actor_id| self.get_component_active(&actor_id))
991 }
992
993 pub fn get_powerslide_active(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
995 get_actor_attribute_matching!(
996 self,
997 &self.get_car_actor_id(player_id)?,
998 HANDBRAKE_KEY,
999 boxcars::Attribute::Boolean
1000 )
1001 .cloned()
1002 }
1003}