1use boxcars::{HeaderProp, RemoteId};
2use serde::Serialize;
3
4use crate::*;
5
6macro_rules! fmt_err {
7 ($( $item:expr ),* $(,)?) => {
8 Err(format!($( $item ),*))
9 };
10}
11
12pub type PlayerId = boxcars::RemoteId;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum DemolishFormat {
17 Fx,
19 Extended,
21}
22
23#[derive(Debug, Clone, PartialEq)]
29pub enum DemolishAttribute {
30 Fx(boxcars::DemolishFx),
31 Extended(boxcars::DemolishExtended),
32}
33
34impl DemolishAttribute {
35 pub fn attacker_actor_id(&self) -> boxcars::ActorId {
36 match self {
37 DemolishAttribute::Fx(fx) => fx.attacker,
38 DemolishAttribute::Extended(ext) => ext.attacker.actor,
39 }
40 }
41
42 pub fn victim_actor_id(&self) -> boxcars::ActorId {
43 match self {
44 DemolishAttribute::Fx(fx) => fx.victim,
45 DemolishAttribute::Extended(ext) => ext.victim.actor,
46 }
47 }
48
49 pub fn attacker_velocity(&self) -> boxcars::Vector3f {
50 match self {
51 DemolishAttribute::Fx(fx) => fx.attack_velocity,
52 DemolishAttribute::Extended(ext) => ext.attacker_velocity,
53 }
54 }
55
56 pub fn victim_velocity(&self) -> boxcars::Vector3f {
57 match self {
58 DemolishAttribute::Fx(fx) => fx.victim_velocity,
59 DemolishAttribute::Extended(ext) => ext.victim_velocity,
60 }
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize)]
70pub struct DemolishInfo {
71 pub time: f32,
73 pub seconds_remaining: i32,
75 pub frame: usize,
77 pub attacker: PlayerId,
79 pub victim: PlayerId,
81 pub attacker_velocity: boxcars::Vector3f,
83 pub victim_velocity: boxcars::Vector3f,
85 pub victim_location: boxcars::Vector3f,
87}
88
89#[derive(Debug, Clone, PartialEq, Serialize)]
93pub struct ReplayMeta {
94 pub team_zero: Vec<PlayerInfo>,
96 pub team_one: Vec<PlayerInfo>,
98 pub all_headers: Vec<(String, HeaderProp)>,
100}
101
102impl ReplayMeta {
103 pub fn player_count(&self) -> usize {
105 self.team_one.len() + self.team_zero.len()
106 }
107
108 pub fn player_order(&self) -> impl Iterator<Item = &PlayerInfo> {
111 self.team_zero.iter().chain(self.team_one.iter())
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Serialize)]
119pub struct PlayerInfo {
120 pub remote_id: RemoteId,
122 pub stats: Option<std::collections::HashMap<String, HeaderProp>>,
126 pub name: String,
128}
129
130pub fn find_player_stats(
131 player_id: &RemoteId,
132 name: &String,
133 all_player_stats: &Vec<Vec<(String, HeaderProp)>>,
134) -> Result<std::collections::HashMap<String, HeaderProp>, String> {
135 Ok(all_player_stats
136 .iter()
137 .find(|player_stats| matches_stats(player_id, name, player_stats))
138 .ok_or(format!(
139 "Player not found {player_id:?} {all_player_stats:?}"
140 ))?
141 .iter()
142 .cloned()
143 .collect())
144}
145
146fn matches_stats(player_id: &RemoteId, name: &String, props: &Vec<(String, HeaderProp)>) -> bool {
147 if platform_matches(player_id, props) != Ok(true) {
148 return false;
149 }
150 match player_id {
151 RemoteId::Epic(_) => name_matches(name, props),
152 RemoteId::Steam(id) => online_id_matches(*id, props),
153 RemoteId::Xbox(id) => online_id_matches(*id, props),
154 RemoteId::PlayStation(ps4id) => online_id_matches(ps4id.online_id, props),
155 RemoteId::PsyNet(psynet_id) => online_id_matches(psynet_id.online_id, props),
156 RemoteId::Switch(switch_id) => online_id_matches(switch_id.online_id, props),
157 _ => false,
158 }
159}
160
161fn name_matches(name: &String, props: &[(String, HeaderProp)]) -> bool {
162 if let Ok((_, HeaderProp::Str(stat_name))) = get_prop("Name", props) {
163 *name == stat_name
164 } else {
165 false
166 }
167}
168
169fn online_id_matches(id: u64, props: &[(String, HeaderProp)]) -> bool {
170 if let Ok((_, HeaderProp::QWord(props_id))) = get_prop("OnlineID", props) {
171 id == props_id
172 } else {
173 false
174 }
175}
176
177fn platform_matches(
178 player_id: &RemoteId,
179 props: &Vec<(String, HeaderProp)>,
180) -> Result<bool, String> {
181 if let (
182 _,
183 HeaderProp::Byte {
184 kind: _,
185 value: Some(value),
186 },
187 ) = get_prop("Platform", props)?
188 {
189 Ok(match (player_id, value.as_ref()) {
190 (RemoteId::Steam(_), "OnlinePlatform_Steam") => true,
191 (RemoteId::PlayStation(_), "OnlinePlatform_PS4") => true,
192 (RemoteId::Epic(_), "OnlinePlatform_Epic") => true,
193 (RemoteId::PsyNet(_), "OnlinePlatform_PS4") => true,
194 (RemoteId::Xbox(_), "OnlinePlatform_Dingo") => true,
195 (RemoteId::Switch(_), "OnlinePlatform_Switch") => true,
197 _ => false,
199 })
200 } else {
201 fmt_err!("Unexpected platform value {:?}", props)
202 }
203}
204
205fn get_prop(prop: &str, props: &[(String, HeaderProp)]) -> Result<(String, HeaderProp), String> {
206 props
207 .iter()
208 .find(|(attr, _)| attr == prop)
209 .ok_or("Coudn't find name property".to_string())
210 .cloned()
211}
212
213pub(crate) trait VecMapEntry<K: PartialEq, V> {
214 fn get_entry(&mut self, key: K) -> Entry<'_, K, V>;
215}
216
217pub(crate) enum Entry<'a, K: PartialEq, V> {
218 Occupied(OccupiedEntry<'a, K, V>),
219 Vacant(VacantEntry<'a, K, V>),
220}
221
222impl<'a, K: PartialEq, V> Entry<'a, K, V> {
223 pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
224 match self {
225 Entry::Occupied(occupied) => &mut occupied.entry.1,
226 Entry::Vacant(vacant) => {
227 vacant.vec.push((vacant.key, default()));
228 &mut vacant.vec.last_mut().unwrap().1
229 }
230 }
231 }
232}
233
234pub(crate) struct OccupiedEntry<'a, K: PartialEq, V> {
235 entry: &'a mut (K, V),
236}
237
238pub(crate) struct VacantEntry<'a, K: PartialEq, V> {
239 vec: &'a mut Vec<(K, V)>,
240 key: K,
241}
242
243impl<K: PartialEq + Clone, V> VecMapEntry<K, V> for Vec<(K, V)> {
244 fn get_entry(&mut self, key: K) -> Entry<'_, K, V> {
245 match self.iter_mut().position(|(k, _)| k == &key) {
246 Some(index) => Entry::Occupied(OccupiedEntry {
247 entry: &mut self[index],
248 }),
249 None => Entry::Vacant(VacantEntry { vec: self, key }),
250 }
251 }
252}
253
254pub fn vec_to_glam(v: &boxcars::Vector3f) -> glam::f32::Vec3 {
255 glam::f32::Vec3::new(v.x, v.y, v.z)
256}
257
258pub fn glam_to_vec(v: &glam::f32::Vec3) -> boxcars::Vector3f {
259 boxcars::Vector3f {
260 x: v.x,
261 y: v.y,
262 z: v.z,
263 }
264}
265
266pub fn quat_to_glam(q: &boxcars::Quaternion) -> glam::Quat {
267 glam::Quat::from_xyzw(q.x, q.y, q.z, q.w)
268}
269
270pub fn glam_to_quat(rotation: &glam::Quat) -> boxcars::Quaternion {
271 boxcars::Quaternion {
272 x: rotation.x,
273 y: rotation.y,
274 z: rotation.z,
275 w: rotation.w,
276 }
277}
278
279pub fn apply_velocities_to_rigid_body(
280 rigid_body: &boxcars::RigidBody,
281 time_delta: f32,
282) -> boxcars::RigidBody {
283 let mut interpolated = *rigid_body;
284 if time_delta == 0.0 {
285 return interpolated;
286 }
287 let linear_velocity = interpolated.linear_velocity.unwrap_or(boxcars::Vector3f {
288 x: 0.0,
289 y: 0.0,
290 z: 0.0,
291 });
292 let location = vec_to_glam(&rigid_body.location) + (time_delta * vec_to_glam(&linear_velocity));
293 interpolated.location = glam_to_vec(&location);
294 interpolated.rotation = apply_angular_velocity(rigid_body, time_delta);
295 interpolated
296}
297
298fn apply_angular_velocity(rigid_body: &boxcars::RigidBody, time_delta: f32) -> boxcars::Quaternion {
299 let rbav = rigid_body.angular_velocity.unwrap_or(boxcars::Vector3f {
302 x: 0.0,
303 y: 0.0,
304 z: 0.0,
305 });
306 let angular_velocity = glam::Vec3::new(rbav.x, rbav.y, rbav.z);
307 let magnitude = angular_velocity.length();
308 let angular_velocity_unit_vector = angular_velocity.normalize_or_zero();
309
310 let mut rotation = glam::Quat::from_xyzw(
311 rigid_body.rotation.x,
312 rigid_body.rotation.y,
313 rigid_body.rotation.z,
314 rigid_body.rotation.w,
315 );
316
317 if angular_velocity_unit_vector.length() != 0.0 {
318 let delta_rotation =
319 glam::Quat::from_axis_angle(angular_velocity_unit_vector, magnitude * time_delta);
320 rotation *= delta_rotation;
321 }
322
323 boxcars::Quaternion {
324 x: rotation.x,
325 y: rotation.y,
326 z: rotation.z,
327 w: rotation.w,
328 }
329}
330
331pub fn get_interpolated_rigid_body(
345 start_body: &boxcars::RigidBody,
346 start_time: f32,
347 end_body: &boxcars::RigidBody,
348 end_time: f32,
349 time: f32,
350) -> SubtrActorResult<boxcars::RigidBody> {
351 if !(start_time <= time && time <= end_time) {
352 return SubtrActorError::new_result(SubtrActorErrorVariant::InterpolationTimeOrderError {
353 start_time,
354 time,
355 end_time,
356 });
357 }
358
359 let duration = end_time - start_time;
360 let interpolation_amount = (time - start_time) / duration;
361 let start_position = util::vec_to_glam(&start_body.location);
362 let end_position = util::vec_to_glam(&end_body.location);
363 let interpolated_location = start_position.lerp(end_position, interpolation_amount);
364 let start_rotation = quat_to_glam(&start_body.rotation);
365 let end_rotation = quat_to_glam(&end_body.rotation);
366 let interpolated_rotation = start_rotation.slerp(end_rotation, interpolation_amount);
367
368 Ok(boxcars::RigidBody {
369 location: glam_to_vec(&interpolated_location),
370 rotation: glam_to_quat(&interpolated_rotation),
371 sleeping: start_body.sleeping,
372 linear_velocity: start_body.linear_velocity,
373 angular_velocity: start_body.angular_velocity,
374 })
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
379pub enum SearchDirection {
380 Forward,
381 Backward,
382}
383
384pub fn find_in_direction<T, F, R>(
399 items: &[T],
400 current_index: usize,
401 direction: SearchDirection,
402 predicate: F,
403) -> Option<(usize, R)>
404where
405 F: Fn(&T) -> Option<R>,
406{
407 let mut iter: Box<dyn Iterator<Item = (usize, &T)>> = match direction {
408 SearchDirection::Forward => Box::new(
409 items[current_index + 1..]
410 .iter()
411 .enumerate()
412 .map(move |(i, item)| (i + current_index + 1, item)),
413 ),
414 SearchDirection::Backward => Box::new(items[..current_index].iter().enumerate().rev()),
415 };
416
417 iter.find_map(|(i, item)| predicate(item).map(|res| (i, res)))
418}