1use super::*;
2
3impl<'a> ReplayProcessor<'a> {
4 pub fn get_seconds_remaining(&self) -> SubtrActorResult<i32> {
6 let seconds_remaining_object_id =
7 self.cached_object_ids.seconds_remaining.ok_or_else(|| {
8 SubtrActorError::new(SubtrActorErrorVariant::ObjectIdNotFound {
9 name: SECONDS_REMAINING_KEY,
10 })
11 })?;
12 let metadata_actor_id = self.get_metadata_actor_id()?;
13 let metadata_state = self.get_actor_state(&metadata_actor_id)?;
14 metadata_state
15 .attributes
16 .get(&seconds_remaining_object_id)
17 .ok_or_else(|| {
18 SubtrActorError::new(SubtrActorErrorVariant::PropertyNotFoundInState {
19 property: SECONDS_REMAINING_KEY,
20 })
21 })
22 .and_then(|(attribute, _)| attribute_match!(attribute, boxcars::Attribute::Int))
23 .copied()
24 }
25
26 pub fn get_replicated_state_name(&self) -> SubtrActorResult<i32> {
28 get_actor_attribute_matching!(
29 self,
30 &self.get_metadata_actor_id()?,
31 REPLICATED_STATE_NAME_KEY,
32 boxcars::Attribute::Int
33 )
34 .cloned()
35 }
36
37 pub fn get_replicated_game_state_time_remaining(&self) -> SubtrActorResult<i32> {
39 get_actor_attribute_matching!(
40 self,
41 &self.get_metadata_actor_id()?,
42 REPLICATED_GAME_STATE_TIME_REMAINING_KEY,
43 boxcars::Attribute::Int
44 )
45 .cloned()
46 }
47
48 pub fn get_ball_has_been_hit(&self) -> SubtrActorResult<bool> {
50 get_actor_attribute_matching!(
51 self,
52 &self.get_metadata_actor_id()?,
53 BALL_HAS_BEEN_HIT_KEY,
54 boxcars::Attribute::Boolean
55 )
56 .cloned()
57 }
58
59 pub fn get_ignore_ball_syncing(&self) -> SubtrActorResult<bool> {
61 let actor_id = self.get_ball_actor_id()?;
62 get_actor_attribute_matching!(
63 self,
64 &actor_id,
65 IGNORE_SYNCING_KEY,
66 boxcars::Attribute::Boolean
67 )
68 .cloned()
69 }
70
71 pub fn get_ball_rigid_body(&self) -> SubtrActorResult<&boxcars::RigidBody> {
73 self.ball_actor_id
74 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::BallActorNotFound))
75 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
76 }
77
78 pub fn get_normalized_ball_rigid_body(&self) -> SubtrActorResult<boxcars::RigidBody> {
80 self.get_ball_rigid_body()
81 .map(|rigid_body| self.normalize_rigid_body(rigid_body))
82 }
83
84 pub fn ball_rigid_body_exists(&self) -> SubtrActorResult<bool> {
86 Ok(self
87 .get_ball_rigid_body()
88 .map(|rb| !rb.sleeping)
89 .unwrap_or(false))
90 }
91
92 pub fn get_ball_rigid_body_and_updated(
94 &self,
95 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
96 self.ball_actor_id
97 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::BallActorNotFound))
98 .and_then(|actor_id| {
99 get_attribute_and_updated!(
100 self,
101 &self.get_actor_state(&actor_id)?.attributes,
102 RIGID_BODY_STATE_KEY,
103 boxcars::Attribute::RigidBody
104 )
105 })
106 }
107
108 pub fn get_velocity_applied_ball_rigid_body(
110 &self,
111 target_time: f32,
112 ) -> SubtrActorResult<boxcars::RigidBody> {
113 let (current_rigid_body, frame_index) = self.get_ball_rigid_body_and_updated()?;
114 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
115 }
116
117 pub fn get_interpolated_ball_rigid_body(
119 &self,
120 time: f32,
121 close_enough: f32,
122 ) -> SubtrActorResult<boxcars::RigidBody> {
123 self.get_interpolated_actor_rigid_body(&self.get_ball_actor_id()?, time, close_enough)
124 }
125
126 pub fn get_player_name(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
128 get_actor_attribute_matching!(
129 self,
130 &self.get_player_actor_id(player_id)?,
131 PLAYER_NAME_KEY,
132 boxcars::Attribute::String
133 )
134 .cloned()
135 }
136
137 fn player_stats_headers(&self) -> Option<&Vec<Vec<(String, boxcars::HeaderProp)>>> {
138 self.replay
139 .properties
140 .iter()
141 .find_map(|(key, value)| match (key.as_str(), value) {
142 ("PlayerStats", boxcars::HeaderProp::Array(player_stats)) => Some(player_stats),
143 _ => None,
144 })
145 }
146
147 fn player_header_stats(
148 &self,
149 player_id: &PlayerId,
150 ) -> Option<std::collections::HashMap<String, boxcars::HeaderProp>> {
151 let player_stats = self.player_stats_headers()?;
152 let fallback_name = String::new();
153 self.get_player_name(player_id)
154 .ok()
155 .and_then(|name| {
156 crate::replay_meta::find_player_stats(player_id, &name, player_stats).ok()
157 })
158 .or_else(|| {
159 crate::replay_meta::find_player_stats(player_id, &fallback_name, player_stats).ok()
160 })
161 }
162
163 pub(crate) fn get_player_loadout_body_name(&self, player_id: &PlayerId) -> Option<String> {
164 self.player_header_stats(player_id)?
165 .get("LoadoutBody")
166 .and_then(|property| match property {
167 boxcars::HeaderProp::Str(body_name) => Some(body_name.clone()),
168 _ => None,
169 })
170 }
171
172 pub(crate) fn get_player_loadout_body_id(&self, player_id: &PlayerId) -> Option<u32> {
173 let player_actor_id = self.get_player_actor_id(player_id).ok()?;
174 let loadout = self.player_actor_to_loadout.get(&player_actor_id)?;
175 match self.get_player_is_team_0(player_id).ok() {
176 Some(true) => Some(loadout.blue.body),
177 Some(false) => Some(loadout.orange.body),
178 None if loadout.blue.body == loadout.orange.body => Some(loadout.blue.body),
179 None => Some(loadout.blue.body),
180 }
181 }
182
183 pub(crate) fn get_player_camera_settings(
187 &self,
188 player_id: &PlayerId,
189 ) -> Option<PlayerCameraSettings> {
190 let player_actor_id = self.get_player_actor_id(player_id).ok()?;
191 self.player_actor_to_camera_settings
192 .get(&player_actor_id)
193 .copied()
194 }
195
196 pub(crate) fn get_player_car_hitbox(&self, player_id: &PlayerId) -> CarHitbox {
197 car_hitbox_for_body_id_or_name(
198 self.get_player_loadout_body_id(player_id),
199 self.get_player_loadout_body_name(player_id).as_deref(),
200 )
201 .unwrap_or_else(default_car_hitbox)
202 }
203
204 fn get_player_int_stat(
205 &self,
206 player_id: &PlayerId,
207 key: &'static str,
208 ) -> SubtrActorResult<i32> {
209 get_actor_attribute_matching!(
210 self,
211 &self.get_player_actor_id(player_id)?,
212 key,
213 boxcars::Attribute::Int
214 )
215 .cloned()
216 }
217
218 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
220 let team_actor_id = self
221 .player_to_team
222 .get(&self.get_player_actor_id(player_id)?)
223 .ok_or_else(|| {
224 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
225 player_id: player_id.clone(),
226 })
227 })?;
228 let state = self.get_actor_state(team_actor_id)?;
229 self.object_id_to_name
230 .get(&state.object_id)
231 .ok_or_else(|| {
232 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
233 player_id: player_id.clone(),
234 })
235 })
236 .cloned()
237 }
238
239 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
241 Ok(self
242 .get_player_team_key(player_id)?
243 .chars()
244 .last()
245 .ok_or_else(|| {
246 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
247 player_id: player_id.clone(),
248 })
249 })?
250 == '0')
251 }
252
253 pub(crate) fn get_team_actor_id_for_side(
255 &self,
256 is_team_0: bool,
257 ) -> SubtrActorResult<boxcars::ActorId> {
258 let player_id = if is_team_0 {
259 self.team_zero.first()
260 } else {
261 self.team_one.first()
262 }
263 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))?;
264
265 self.player_to_team
266 .get(&self.get_player_actor_id(player_id)?)
267 .copied()
268 .ok_or_else(|| {
269 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
270 name: "Team",
271 player_id: player_id.clone(),
272 })
273 })
274 }
275
276 pub fn get_team_score(&self, is_team_0: bool) -> SubtrActorResult<i32> {
278 let team_actor_id = self.get_team_actor_id_for_side(is_team_0)?;
279 get_actor_attribute_matching!(
280 self,
281 &team_actor_id,
282 TEAM_GAME_SCORE_KEY,
283 boxcars::Attribute::Int
284 )
285 .cloned()
286 }
287
288 pub fn get_team_scores(&self) -> SubtrActorResult<(i32, i32)> {
290 Ok((self.get_team_score(true)?, self.get_team_score(false)?))
291 }
292
293 pub fn get_player_rigid_body(
295 &self,
296 player_id: &PlayerId,
297 ) -> SubtrActorResult<&boxcars::RigidBody> {
298 self.get_car_actor_id(player_id)
299 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
300 }
301
302 pub fn get_normalized_player_rigid_body(
304 &self,
305 player_id: &PlayerId,
306 ) -> SubtrActorResult<boxcars::RigidBody> {
307 self.get_player_rigid_body(player_id)
308 .map(|rigid_body| self.normalize_rigid_body(rigid_body))
309 }
310
311 pub(crate) fn get_normalized_player_position(
313 &self,
314 player_id: &PlayerId,
315 ) -> Option<boxcars::Vector3f> {
316 self.get_normalized_player_rigid_body(player_id)
317 .ok()
318 .map(|rigid_body| rigid_body.location)
319 }
320
321 pub fn get_player_rigid_body_and_updated(
323 &self,
324 player_id: &PlayerId,
325 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
326 self.get_car_actor_id(player_id).and_then(|actor_id| {
327 get_attribute_and_updated!(
328 self,
329 &self.get_actor_state(&actor_id)?.attributes,
330 RIGID_BODY_STATE_KEY,
331 boxcars::Attribute::RigidBody
332 )
333 })
334 }
335
336 pub fn get_player_rigid_body_and_updated_or_recently_deleted(
338 &self,
339 player_id: &PlayerId,
340 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
341 self.get_car_actor_id(player_id)
342 .and_then(|actor_id| self.get_actor_rigid_body_or_recently_deleted(&actor_id))
343 }
344
345 pub fn get_velocity_applied_player_rigid_body(
347 &self,
348 player_id: &PlayerId,
349 target_time: f32,
350 ) -> SubtrActorResult<boxcars::RigidBody> {
351 let (current_rigid_body, frame_index) =
352 self.get_player_rigid_body_and_updated(player_id)?;
353 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
354 }
355
356 pub fn get_interpolated_player_rigid_body(
358 &self,
359 player_id: &PlayerId,
360 time: f32,
361 close_enough: f32,
362 ) -> SubtrActorResult<boxcars::RigidBody> {
363 self.get_car_actor_id(player_id).and_then(|car_actor_id| {
364 self.get_interpolated_actor_rigid_body(&car_actor_id, time, close_enough)
365 })
366 }
367
368 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
370 self.get_boost_actor_id(player_id).and_then(|actor_id| {
371 let boost_state = self.get_actor_state(&actor_id)?;
372 get_derived_attribute!(
373 boost_state.derived_attributes,
374 BOOST_AMOUNT_KEY,
375 boxcars::Attribute::Float
376 )
377 .cloned()
378 })
379 }
380
381 pub fn get_player_last_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
383 self.get_boost_actor_id(player_id).and_then(|actor_id| {
384 let boost_state = self.get_actor_state(&actor_id)?;
385 get_derived_attribute!(
386 boost_state.derived_attributes,
387 LAST_BOOST_AMOUNT_KEY,
388 boxcars::Attribute::Byte
389 )
390 .map(|value| *value as f32)
391 })
392 }
393
394 pub fn get_player_boost_percentage(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
396 self.get_player_boost_level(player_id)
397 .map(boost_amount_to_percent)
398 }
399
400 pub fn get_player_match_assists(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
402 self.get_player_int_stat(player_id, MATCH_ASSISTS_KEY)
403 }
404
405 pub fn get_player_match_goals(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
407 self.get_player_int_stat(player_id, MATCH_GOALS_KEY)
408 }
409
410 pub fn get_player_match_saves(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
412 self.get_player_int_stat(player_id, MATCH_SAVES_KEY)
413 }
414
415 pub fn get_player_match_score(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
417 self.get_player_int_stat(player_id, MATCH_SCORE_KEY)
418 }
419
420 pub fn get_player_match_shots(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
422 self.get_player_int_stat(player_id, MATCH_SHOTS_KEY)
423 }
424
425 pub fn get_ball_hit_team_num(&self) -> SubtrActorResult<u8> {
427 let ball_actor_id = self.get_ball_actor_id()?;
428 get_actor_attribute_matching!(
429 self,
430 &ball_actor_id,
431 BALL_HIT_TEAM_NUM_KEY,
432 boxcars::Attribute::Byte
433 )
434 .cloned()
435 }
436
437 pub fn get_scored_on_team_num(&self) -> SubtrActorResult<u8> {
439 get_actor_attribute_matching!(
440 self,
441 &self.get_metadata_actor_id()?,
442 REPLICATED_SCORED_ON_TEAM_KEY,
443 boxcars::Attribute::Byte
444 )
445 .cloned()
446 }
447
448 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
450 get_actor_attribute_matching!(
451 self,
452 &actor_id,
453 COMPONENT_ACTIVE_KEY,
454 boxcars::Attribute::Byte
455 )
456 .cloned()
457 }
458
459 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
461 self.get_boost_actor_id(player_id)
462 .and_then(|actor_id| self.get_component_active(&actor_id))
463 }
464
465 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
467 self.get_jump_actor_id(player_id)
468 .and_then(|actor_id| self.get_component_active(&actor_id))
469 }
470
471 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
473 self.get_double_jump_actor_id(player_id)
474 .and_then(|actor_id| self.get_component_active(&actor_id))
475 }
476
477 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
479 self.get_dodge_actor_id(player_id)
480 .and_then(|actor_id| self.get_component_active(&actor_id))
481 }
482
483 pub fn get_powerslide_active(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
485 get_actor_attribute_matching!(
486 self,
487 &self.get_car_actor_id(player_id)?,
488 HANDBRAKE_KEY,
489 boxcars::Attribute::Boolean
490 )
491 .cloned()
492 }
493}