1use std::collections::HashMap;
2
3use boxcars::{HeaderProp, RemoteId};
4use serde::Serialize;
5
6use crate::*;
7
8macro_rules! fmt_err {
9 ($( $item:expr ),* $(,)?) => {
10 Err(format!($( $item ),*))
11 };
12}
13
14pub type PlayerId = boxcars::RemoteId;
15
16#[derive(Debug, Clone, PartialEq, Serialize)]
22pub struct DemolishInfo {
23 pub time: f32,
25 pub seconds_remaining: i32,
27 pub frame: usize,
29 pub attacker: PlayerId,
31 pub victim: PlayerId,
33 pub attacker_velocity: boxcars::Vector3f,
35 pub victim_velocity: boxcars::Vector3f,
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize)]
43pub struct ReplayMeta {
44 pub shots: Vec<ShotMetadata>,
45 pub team_zero: Vec<PlayerInfo>,
47 pub team_one: Vec<PlayerInfo>,
49 pub all_headers: Vec<(String, HeaderProp)>,
51}
52
53impl ReplayMeta {
54 pub fn player_count(&self) -> usize {
56 self.team_one.len() + self.team_zero.len()
57 }
58
59 pub fn player_order(&self) -> impl Iterator<Item = &PlayerInfo> {
62 self.team_zero.iter().chain(self.team_one.iter())
63 }
64}
65
66
67#[derive(Debug, Clone, PartialEq, Serialize)]
68pub struct ShotMetadata {
69 pub shooter: String,
70 pub frame: usize,
71 pub ball_position: (f32, f32, f32),
72 pub ball_linear_velocity: (f32, f32, f32),
73 pub ball_angular_velocity: (f32, f32, f32),
74 pub player_positions: HashMap<String, (f32, f32, f32)>,
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize)]
81pub struct PlayerInfo {
82 pub remote_id: RemoteId,
84 pub stats: Option<std::collections::HashMap<String, HeaderProp>>,
88 pub name: String,
90}
91
92pub fn find_player_stats(
93 player_id: &RemoteId,
94 name: &String,
95 all_player_stats: &Vec<Vec<(String, HeaderProp)>>,
96) -> Result<std::collections::HashMap<String, HeaderProp>, String> {
97 Ok(all_player_stats
98 .iter()
99 .find(|player_stats| matches_stats(player_id, name, player_stats))
100 .ok_or(format!(
101 "Player not found {:?} {:?}",
102 player_id, all_player_stats
103 ))?
104 .iter()
105 .cloned()
106 .collect())
107}
108
109fn matches_stats(player_id: &RemoteId, name: &String, props: &Vec<(String, HeaderProp)>) -> bool {
110 if platform_matches(player_id, props) != Ok(true) {
111 return false;
112 }
113 match player_id {
114 RemoteId::Epic(_) => name_matches(name, props),
115 RemoteId::Steam(id) => online_id_matches(*id, props),
116 RemoteId::Xbox(id) => online_id_matches(*id, props),
117 RemoteId::PlayStation(ps4id) => online_id_matches(ps4id.online_id, props),
118 RemoteId::PsyNet(psynet_id) => online_id_matches(psynet_id.online_id, props),
119 RemoteId::Switch(switch_id) => online_id_matches(switch_id.online_id, props),
120 _ => false,
121 }
122}
123
124fn name_matches(name: &String, props: &Vec<(String, HeaderProp)>) -> bool {
125 if let Ok((_, HeaderProp::Str(stat_name))) = get_prop("Name", props) {
126 *name == stat_name
127 } else {
128 false
129 }
130}
131
132fn online_id_matches(id: u64, props: &Vec<(String, HeaderProp)>) -> bool {
133 if let Ok((_, HeaderProp::QWord(props_id))) = get_prop("OnlineID", props) {
134 id == props_id
135 } else {
136 false
137 }
138}
139
140fn platform_matches(
141 player_id: &RemoteId,
142 props: &Vec<(String, HeaderProp)>,
143) -> Result<bool, String> {
144 if let (
145 _,
146 HeaderProp::Byte {
147 kind: _,
148 value: Some(value),
149 },
150 ) = get_prop("Platform", props)?
151 {
152 Ok(match (player_id, value.as_ref()) {
153 (RemoteId::Steam(_), "OnlinePlatform_Steam") => true,
154 (RemoteId::PlayStation(_), "OnlinePlatform_PS4") => true,
155 (RemoteId::Epic(_), "OnlinePlatform_Epic") => true,
156 (RemoteId::PsyNet(_), "OnlinePlatform_PS4") => true,
157 (RemoteId::Xbox(_), "OnlinePlatform_Dingo") => true,
158 (RemoteId::Switch(_), "OnlinePlatform_Switch") => true,
160 _ => false,
162 })
163 } else {
164 fmt_err!("Unexpected platform value {:?}", props)
165 }
166}
167
168fn get_prop(prop: &str, props: &Vec<(String, HeaderProp)>) -> Result<(String, HeaderProp), String> {
169 props
170 .iter()
171 .find(|(attr, _)| attr == prop)
172 .ok_or("Coudn't find name property".to_string())
173 .cloned()
174}
175
176pub(crate) trait VecMapEntry<K: PartialEq, V> {
177 fn get_entry(&mut self, key: K) -> Entry<K, V>;
178}
179
180pub(crate) enum Entry<'a, K: PartialEq, V> {
181 Occupied(OccupiedEntry<'a, K, V>),
182 Vacant(VacantEntry<'a, K, V>),
183}
184
185impl<'a, K: PartialEq, V> Entry<'a, K, V> {
186 pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
187 match self {
188 Entry::Occupied(occupied) => &mut occupied.entry.1,
189 Entry::Vacant(vacant) => {
190 vacant.vec.push((vacant.key, default()));
191 &mut vacant.vec.last_mut().unwrap().1
192 }
193 }
194 }
195}
196
197pub(crate) struct OccupiedEntry<'a, K: PartialEq, V> {
198 entry: &'a mut (K, V),
199}
200
201pub(crate) struct VacantEntry<'a, K: PartialEq, V> {
202 vec: &'a mut Vec<(K, V)>,
203 key: K,
204}
205
206impl<K: PartialEq + Clone, V> VecMapEntry<K, V> for Vec<(K, V)> {
207 fn get_entry(&mut self, key: K) -> Entry<K, V> {
208 match self.iter_mut().position(|(k, _)| k == &key) {
209 Some(index) => Entry::Occupied(OccupiedEntry {
210 entry: &mut self[index],
211 }),
212 None => Entry::Vacant(VacantEntry { vec: self, key }),
213 }
214 }
215}
216
217pub fn vec_to_glam(v: &boxcars::Vector3f) -> glam::f32::Vec3 {
218 glam::f32::Vec3::new(v.x, v.y, v.z)
219}
220
221pub fn glam_to_vec(v: &glam::f32::Vec3) -> boxcars::Vector3f {
222 boxcars::Vector3f {
223 x: v.x,
224 y: v.y,
225 z: v.z,
226 }
227}
228
229pub fn quat_to_glam(q: &boxcars::Quaternion) -> glam::Quat {
230 glam::Quat::from_xyzw(q.x, q.y, q.z, q.w)
231}
232
233pub fn glam_to_quat(rotation: &glam::Quat) -> boxcars::Quaternion {
234 boxcars::Quaternion {
235 x: rotation.x,
236 y: rotation.y,
237 z: rotation.z,
238 w: rotation.w,
239 }
240}
241
242pub fn apply_velocities_to_rigid_body(
243 rigid_body: &boxcars::RigidBody,
244 time_delta: f32,
245) -> boxcars::RigidBody {
246 let mut interpolated = rigid_body.clone();
247 if time_delta == 0.0 {
248 return interpolated;
249 }
250 let linear_velocity = interpolated.linear_velocity.unwrap_or(boxcars::Vector3f {
251 x: 0.0,
252 y: 0.0,
253 z: 0.0,
254 });
255 let location = vec_to_glam(&rigid_body.location) + (time_delta * vec_to_glam(&linear_velocity));
256 interpolated.location = glam_to_vec(&location);
257 interpolated.rotation = apply_angular_velocity(rigid_body, time_delta);
258 interpolated
259}
260
261fn apply_angular_velocity(rigid_body: &boxcars::RigidBody, time_delta: f32) -> boxcars::Quaternion {
262 let rbav = rigid_body
265 .angular_velocity
266 .unwrap_or_else(|| boxcars::Vector3f {
267 x: 0.0,
268 y: 0.0,
269 z: 0.0,
270 });
271 let angular_velocity = glam::Vec3::new(rbav.x, rbav.y, rbav.z);
272 let magnitude = angular_velocity.length();
273 let angular_velocity_unit_vector = angular_velocity.normalize_or_zero();
274
275 let mut rotation = glam::Quat::from_xyzw(
276 rigid_body.rotation.x,
277 rigid_body.rotation.y,
278 rigid_body.rotation.z,
279 rigid_body.rotation.w,
280 );
281
282 if angular_velocity_unit_vector.length() != 0.0 {
283 let delta_rotation =
284 glam::Quat::from_axis_angle(angular_velocity_unit_vector, magnitude * time_delta);
285 rotation *= delta_rotation;
286 }
287
288 boxcars::Quaternion {
289 x: rotation.x,
290 y: rotation.y,
291 z: rotation.z,
292 w: rotation.w,
293 }
294}
295
296pub fn get_interpolated_rigid_body(
310 start_body: &boxcars::RigidBody,
311 start_time: f32,
312 end_body: &boxcars::RigidBody,
313 end_time: f32,
314 time: f32,
315) -> SubtrActorResult<boxcars::RigidBody> {
316 if !(start_time <= time && time <= end_time) {
317 return SubtrActorError::new_result(SubtrActorErrorVariant::InterpolationTimeOrderError {
318 start_time,
319 time,
320 end_time,
321 });
322 }
323
324 let duration = end_time - start_time;
325 let interpolation_amount = (time - start_time) / duration;
326 let start_position = util::vec_to_glam(&start_body.location);
327 let end_position = util::vec_to_glam(&end_body.location);
328 let interpolated_location = start_position.lerp(end_position, interpolation_amount);
329 let start_rotation = quat_to_glam(&start_body.rotation);
330 let end_rotation = quat_to_glam(&end_body.rotation);
331 let interpolated_rotation = start_rotation.slerp(end_rotation, interpolation_amount);
332
333 Ok(boxcars::RigidBody {
334 location: glam_to_vec(&interpolated_location),
335 rotation: glam_to_quat(&interpolated_rotation),
336 sleeping: start_body.sleeping,
337 linear_velocity: start_body.linear_velocity,
338 angular_velocity: start_body.angular_velocity,
339 })
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
344pub enum SearchDirection {
345 Forward,
346 Backward,
347}
348
349pub fn find_in_direction<T, F, R>(
364 items: &[T],
365 current_index: usize,
366 direction: SearchDirection,
367 predicate: F,
368) -> Option<(usize, R)>
369where
370 F: Fn(&T) -> Option<R>,
371{
372 let mut iter: Box<dyn Iterator<Item = (usize, &T)>> = match direction {
373 SearchDirection::Forward => Box::new(
374 items[current_index + 1..]
375 .iter()
376 .enumerate()
377 .map(move |(i, item)| (i + current_index + 1, item)),
378 ),
379 SearchDirection::Backward => Box::new(items[..current_index].iter().enumerate().rev()),
380 };
381
382 iter.find_map(|(i, item)| predicate(item).map(|res| (i, res)))
383}