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_car_hitbox(&self, player_id: &PlayerId) -> CarHitbox {
184 car_hitbox_for_body_id_or_name(
185 self.get_player_loadout_body_id(player_id),
186 self.get_player_loadout_body_name(player_id).as_deref(),
187 )
188 .unwrap_or_else(default_car_hitbox)
189 }
190
191 fn get_player_int_stat(
192 &self,
193 player_id: &PlayerId,
194 key: &'static str,
195 ) -> SubtrActorResult<i32> {
196 get_actor_attribute_matching!(
197 self,
198 &self.get_player_actor_id(player_id)?,
199 key,
200 boxcars::Attribute::Int
201 )
202 .cloned()
203 }
204
205 pub fn get_player_team_key(&self, player_id: &PlayerId) -> SubtrActorResult<String> {
207 let team_actor_id = self
208 .player_to_team
209 .get(&self.get_player_actor_id(player_id)?)
210 .ok_or_else(|| {
211 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
212 player_id: player_id.clone(),
213 })
214 })?;
215 let state = self.get_actor_state(team_actor_id)?;
216 self.object_id_to_name
217 .get(&state.object_id)
218 .ok_or_else(|| {
219 SubtrActorError::new(SubtrActorErrorVariant::UnknownPlayerTeam {
220 player_id: player_id.clone(),
221 })
222 })
223 .cloned()
224 }
225
226 pub fn get_player_is_team_0(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
228 Ok(self
229 .get_player_team_key(player_id)?
230 .chars()
231 .last()
232 .ok_or_else(|| {
233 SubtrActorError::new(SubtrActorErrorVariant::EmptyTeamName {
234 player_id: player_id.clone(),
235 })
236 })?
237 == '0')
238 }
239
240 pub(crate) fn get_team_actor_id_for_side(
242 &self,
243 is_team_0: bool,
244 ) -> SubtrActorResult<boxcars::ActorId> {
245 let player_id = if is_team_0 {
246 self.team_zero.first()
247 } else {
248 self.team_one.first()
249 }
250 .ok_or_else(|| SubtrActorError::new(SubtrActorErrorVariant::NoGameActor))?;
251
252 self.player_to_team
253 .get(&self.get_player_actor_id(player_id)?)
254 .copied()
255 .ok_or_else(|| {
256 SubtrActorError::new(SubtrActorErrorVariant::ActorNotFound {
257 name: "Team",
258 player_id: player_id.clone(),
259 })
260 })
261 }
262
263 pub fn get_team_score(&self, is_team_0: bool) -> SubtrActorResult<i32> {
265 let team_actor_id = self.get_team_actor_id_for_side(is_team_0)?;
266 get_actor_attribute_matching!(
267 self,
268 &team_actor_id,
269 TEAM_GAME_SCORE_KEY,
270 boxcars::Attribute::Int
271 )
272 .cloned()
273 }
274
275 pub fn get_team_scores(&self) -> SubtrActorResult<(i32, i32)> {
277 Ok((self.get_team_score(true)?, self.get_team_score(false)?))
278 }
279
280 pub fn get_player_rigid_body(
282 &self,
283 player_id: &PlayerId,
284 ) -> SubtrActorResult<&boxcars::RigidBody> {
285 self.get_car_actor_id(player_id)
286 .and_then(|actor_id| self.get_actor_rigid_body(&actor_id).map(|v| v.0))
287 }
288
289 pub fn get_normalized_player_rigid_body(
291 &self,
292 player_id: &PlayerId,
293 ) -> SubtrActorResult<boxcars::RigidBody> {
294 self.get_player_rigid_body(player_id)
295 .map(|rigid_body| self.normalize_rigid_body(rigid_body))
296 }
297
298 pub(crate) fn get_normalized_player_position(
300 &self,
301 player_id: &PlayerId,
302 ) -> Option<boxcars::Vector3f> {
303 self.get_normalized_player_rigid_body(player_id)
304 .ok()
305 .map(|rigid_body| rigid_body.location)
306 }
307
308 pub fn get_player_rigid_body_and_updated(
310 &self,
311 player_id: &PlayerId,
312 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
313 self.get_car_actor_id(player_id).and_then(|actor_id| {
314 get_attribute_and_updated!(
315 self,
316 &self.get_actor_state(&actor_id)?.attributes,
317 RIGID_BODY_STATE_KEY,
318 boxcars::Attribute::RigidBody
319 )
320 })
321 }
322
323 pub fn get_player_rigid_body_and_updated_or_recently_deleted(
325 &self,
326 player_id: &PlayerId,
327 ) -> SubtrActorResult<(&boxcars::RigidBody, &usize)> {
328 self.get_car_actor_id(player_id)
329 .and_then(|actor_id| self.get_actor_rigid_body_or_recently_deleted(&actor_id))
330 }
331
332 pub fn get_velocity_applied_player_rigid_body(
334 &self,
335 player_id: &PlayerId,
336 target_time: f32,
337 ) -> SubtrActorResult<boxcars::RigidBody> {
338 let (current_rigid_body, frame_index) =
339 self.get_player_rigid_body_and_updated(player_id)?;
340 self.velocities_applied_rigid_body(current_rigid_body, *frame_index, target_time)
341 }
342
343 pub fn get_interpolated_player_rigid_body(
345 &self,
346 player_id: &PlayerId,
347 time: f32,
348 close_enough: f32,
349 ) -> SubtrActorResult<boxcars::RigidBody> {
350 self.get_car_actor_id(player_id).and_then(|car_actor_id| {
351 self.get_interpolated_actor_rigid_body(&car_actor_id, time, close_enough)
352 })
353 }
354
355 pub fn get_player_boost_level(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
357 self.get_boost_actor_id(player_id).and_then(|actor_id| {
358 let boost_state = self.get_actor_state(&actor_id)?;
359 get_derived_attribute!(
360 boost_state.derived_attributes,
361 BOOST_AMOUNT_KEY,
362 boxcars::Attribute::Float
363 )
364 .cloned()
365 })
366 }
367
368 pub fn get_player_last_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 LAST_BOOST_AMOUNT_KEY,
375 boxcars::Attribute::Byte
376 )
377 .map(|value| *value as f32)
378 })
379 }
380
381 pub fn get_player_boost_percentage(&self, player_id: &PlayerId) -> SubtrActorResult<f32> {
383 self.get_player_boost_level(player_id)
384 .map(boost_amount_to_percent)
385 }
386
387 pub fn get_player_match_assists(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
389 self.get_player_int_stat(player_id, MATCH_ASSISTS_KEY)
390 }
391
392 pub fn get_player_match_goals(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
394 self.get_player_int_stat(player_id, MATCH_GOALS_KEY)
395 }
396
397 pub fn get_player_match_saves(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
399 self.get_player_int_stat(player_id, MATCH_SAVES_KEY)
400 }
401
402 pub fn get_player_match_score(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
404 self.get_player_int_stat(player_id, MATCH_SCORE_KEY)
405 }
406
407 pub fn get_player_match_shots(&self, player_id: &PlayerId) -> SubtrActorResult<i32> {
409 self.get_player_int_stat(player_id, MATCH_SHOTS_KEY)
410 }
411
412 pub fn get_ball_hit_team_num(&self) -> SubtrActorResult<u8> {
414 let ball_actor_id = self.get_ball_actor_id()?;
415 get_actor_attribute_matching!(
416 self,
417 &ball_actor_id,
418 BALL_HIT_TEAM_NUM_KEY,
419 boxcars::Attribute::Byte
420 )
421 .cloned()
422 }
423
424 pub fn get_scored_on_team_num(&self) -> SubtrActorResult<u8> {
426 get_actor_attribute_matching!(
427 self,
428 &self.get_metadata_actor_id()?,
429 REPLICATED_SCORED_ON_TEAM_KEY,
430 boxcars::Attribute::Byte
431 )
432 .cloned()
433 }
434
435 pub fn get_component_active(&self, actor_id: &boxcars::ActorId) -> SubtrActorResult<u8> {
437 get_actor_attribute_matching!(
438 self,
439 &actor_id,
440 COMPONENT_ACTIVE_KEY,
441 boxcars::Attribute::Byte
442 )
443 .cloned()
444 }
445
446 pub fn get_boost_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
448 self.get_boost_actor_id(player_id)
449 .and_then(|actor_id| self.get_component_active(&actor_id))
450 }
451
452 pub fn get_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
454 self.get_jump_actor_id(player_id)
455 .and_then(|actor_id| self.get_component_active(&actor_id))
456 }
457
458 pub fn get_double_jump_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
460 self.get_double_jump_actor_id(player_id)
461 .and_then(|actor_id| self.get_component_active(&actor_id))
462 }
463
464 pub fn get_dodge_active(&self, player_id: &PlayerId) -> SubtrActorResult<u8> {
466 self.get_dodge_actor_id(player_id)
467 .and_then(|actor_id| self.get_component_active(&actor_id))
468 }
469
470 pub fn get_powerslide_active(&self, player_id: &PlayerId) -> SubtrActorResult<bool> {
472 get_actor_attribute_matching!(
473 self,
474 &self.get_car_actor_id(player_id)?,
475 HANDBRAKE_KEY,
476 boxcars::Attribute::Boolean
477 )
478 .cloned()
479 }
480}