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, PartialEq, Serialize)]
20pub struct DemolishInfo {
21 pub time: f32,
23 pub seconds_remaining: i32,
25 pub frame: usize,
27 pub attacker: PlayerId,
29 pub victim: PlayerId,
31 pub attacker_velocity: boxcars::Vector3f,
33 pub victim_velocity: boxcars::Vector3f,
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize)]
41pub struct ReplayMeta {
42 pub team_zero: Vec<PlayerInfo>,
44 pub team_one: Vec<PlayerInfo>,
46 pub all_headers: Vec<(String, HeaderProp)>,
48}
49
50impl ReplayMeta {
51 pub fn player_count(&self) -> usize {
53 self.team_one.len() + self.team_zero.len()
54 }
55
56 pub fn player_order(&self) -> impl Iterator<Item = &PlayerInfo> {
59 self.team_zero.iter().chain(self.team_one.iter())
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Serialize)]
67pub struct PlayerInfo {
68 pub remote_id: RemoteId,
70 pub stats: Option<std::collections::HashMap<String, HeaderProp>>,
74 pub name: String,
76}
77
78pub fn find_player_stats(
79 player_id: &RemoteId,
80 name: &String,
81 all_player_stats: &Vec<Vec<(String, HeaderProp)>>,
82) -> Result<std::collections::HashMap<String, HeaderProp>, String> {
83 Ok(all_player_stats
84 .iter()
85 .find(|player_stats| matches_stats(player_id, name, player_stats))
86 .ok_or(format!(
87 "Player not found {player_id:?} {all_player_stats:?}"
88 ))?
89 .iter()
90 .cloned()
91 .collect())
92}
93
94fn matches_stats(player_id: &RemoteId, name: &String, props: &Vec<(String, HeaderProp)>) -> bool {
95 if platform_matches(player_id, props) != Ok(true) {
96 return false;
97 }
98 match player_id {
99 RemoteId::Epic(_) => name_matches(name, props),
100 RemoteId::Steam(id) => online_id_matches(*id, props),
101 RemoteId::Xbox(id) => online_id_matches(*id, props),
102 RemoteId::PlayStation(ps4id) => online_id_matches(ps4id.online_id, props),
103 RemoteId::PsyNet(psynet_id) => online_id_matches(psynet_id.online_id, props),
104 RemoteId::Switch(switch_id) => online_id_matches(switch_id.online_id, props),
105 _ => false,
106 }
107}
108
109fn name_matches(name: &String, props: &[(String, HeaderProp)]) -> bool {
110 if let Ok((_, HeaderProp::Str(stat_name))) = get_prop("Name", props) {
111 *name == stat_name
112 } else {
113 false
114 }
115}
116
117fn online_id_matches(id: u64, props: &[(String, HeaderProp)]) -> bool {
118 if let Ok((_, HeaderProp::QWord(props_id))) = get_prop("OnlineID", props) {
119 id == props_id
120 } else {
121 false
122 }
123}
124
125fn platform_matches(
126 player_id: &RemoteId,
127 props: &Vec<(String, HeaderProp)>,
128) -> Result<bool, String> {
129 if let (
130 _,
131 HeaderProp::Byte {
132 kind: _,
133 value: Some(value),
134 },
135 ) = get_prop("Platform", props)?
136 {
137 Ok(match (player_id, value.as_ref()) {
138 (RemoteId::Steam(_), "OnlinePlatform_Steam") => true,
139 (RemoteId::PlayStation(_), "OnlinePlatform_PS4") => true,
140 (RemoteId::Epic(_), "OnlinePlatform_Epic") => true,
141 (RemoteId::PsyNet(_), "OnlinePlatform_PS4") => true,
142 (RemoteId::Xbox(_), "OnlinePlatform_Dingo") => true,
143 (RemoteId::Switch(_), "OnlinePlatform_Switch") => true,
145 _ => false,
147 })
148 } else {
149 fmt_err!("Unexpected platform value {:?}", props)
150 }
151}
152
153fn get_prop(prop: &str, props: &[(String, HeaderProp)]) -> Result<(String, HeaderProp), String> {
154 props
155 .iter()
156 .find(|(attr, _)| attr == prop)
157 .ok_or("Coudn't find name property".to_string())
158 .cloned()
159}
160
161pub(crate) trait VecMapEntry<K: PartialEq, V> {
162 fn get_entry(&mut self, key: K) -> Entry<K, V>;
163}
164
165pub(crate) enum Entry<'a, K: PartialEq, V> {
166 Occupied(OccupiedEntry<'a, K, V>),
167 Vacant(VacantEntry<'a, K, V>),
168}
169
170impl<'a, K: PartialEq, V> Entry<'a, K, V> {
171 pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
172 match self {
173 Entry::Occupied(occupied) => &mut occupied.entry.1,
174 Entry::Vacant(vacant) => {
175 vacant.vec.push((vacant.key, default()));
176 &mut vacant.vec.last_mut().unwrap().1
177 }
178 }
179 }
180}
181
182pub(crate) struct OccupiedEntry<'a, K: PartialEq, V> {
183 entry: &'a mut (K, V),
184}
185
186pub(crate) struct VacantEntry<'a, K: PartialEq, V> {
187 vec: &'a mut Vec<(K, V)>,
188 key: K,
189}
190
191impl<K: PartialEq + Clone, V> VecMapEntry<K, V> for Vec<(K, V)> {
192 fn get_entry(&mut self, key: K) -> Entry<K, V> {
193 match self.iter_mut().position(|(k, _)| k == &key) {
194 Some(index) => Entry::Occupied(OccupiedEntry {
195 entry: &mut self[index],
196 }),
197 None => Entry::Vacant(VacantEntry { vec: self, key }),
198 }
199 }
200}
201
202pub fn vec_to_glam(v: &boxcars::Vector3f) -> glam::f32::Vec3 {
203 glam::f32::Vec3::new(v.x, v.y, v.z)
204}
205
206pub fn glam_to_vec(v: &glam::f32::Vec3) -> boxcars::Vector3f {
207 boxcars::Vector3f {
208 x: v.x,
209 y: v.y,
210 z: v.z,
211 }
212}
213
214pub fn quat_to_glam(q: &boxcars::Quaternion) -> glam::Quat {
215 glam::Quat::from_xyzw(q.x, q.y, q.z, q.w)
216}
217
218pub fn glam_to_quat(rotation: &glam::Quat) -> boxcars::Quaternion {
219 boxcars::Quaternion {
220 x: rotation.x,
221 y: rotation.y,
222 z: rotation.z,
223 w: rotation.w,
224 }
225}
226
227pub fn apply_velocities_to_rigid_body(
228 rigid_body: &boxcars::RigidBody,
229 time_delta: f32,
230) -> boxcars::RigidBody {
231 let mut interpolated = *rigid_body;
232 if time_delta == 0.0 {
233 return interpolated;
234 }
235 let linear_velocity = interpolated.linear_velocity.unwrap_or(boxcars::Vector3f {
236 x: 0.0,
237 y: 0.0,
238 z: 0.0,
239 });
240 let location = vec_to_glam(&rigid_body.location) + (time_delta * vec_to_glam(&linear_velocity));
241 interpolated.location = glam_to_vec(&location);
242 interpolated.rotation = apply_angular_velocity(rigid_body, time_delta);
243 interpolated
244}
245
246fn apply_angular_velocity(rigid_body: &boxcars::RigidBody, time_delta: f32) -> boxcars::Quaternion {
247 let rbav = rigid_body.angular_velocity.unwrap_or(boxcars::Vector3f {
250 x: 0.0,
251 y: 0.0,
252 z: 0.0,
253 });
254 let angular_velocity = glam::Vec3::new(rbav.x, rbav.y, rbav.z);
255 let magnitude = angular_velocity.length();
256 let angular_velocity_unit_vector = angular_velocity.normalize_or_zero();
257
258 let mut rotation = glam::Quat::from_xyzw(
259 rigid_body.rotation.x,
260 rigid_body.rotation.y,
261 rigid_body.rotation.z,
262 rigid_body.rotation.w,
263 );
264
265 if angular_velocity_unit_vector.length() != 0.0 {
266 let delta_rotation =
267 glam::Quat::from_axis_angle(angular_velocity_unit_vector, magnitude * time_delta);
268 rotation *= delta_rotation;
269 }
270
271 boxcars::Quaternion {
272 x: rotation.x,
273 y: rotation.y,
274 z: rotation.z,
275 w: rotation.w,
276 }
277}
278
279pub fn get_interpolated_rigid_body(
293 start_body: &boxcars::RigidBody,
294 start_time: f32,
295 end_body: &boxcars::RigidBody,
296 end_time: f32,
297 time: f32,
298) -> SubtrActorResult<boxcars::RigidBody> {
299 if !(start_time <= time && time <= end_time) {
300 return SubtrActorError::new_result(SubtrActorErrorVariant::InterpolationTimeOrderError {
301 start_time,
302 time,
303 end_time,
304 });
305 }
306
307 let duration = end_time - start_time;
308 let interpolation_amount = (time - start_time) / duration;
309 let start_position = util::vec_to_glam(&start_body.location);
310 let end_position = util::vec_to_glam(&end_body.location);
311 let interpolated_location = start_position.lerp(end_position, interpolation_amount);
312 let start_rotation = quat_to_glam(&start_body.rotation);
313 let end_rotation = quat_to_glam(&end_body.rotation);
314 let interpolated_rotation = start_rotation.slerp(end_rotation, interpolation_amount);
315
316 Ok(boxcars::RigidBody {
317 location: glam_to_vec(&interpolated_location),
318 rotation: glam_to_quat(&interpolated_rotation),
319 sleeping: start_body.sleeping,
320 linear_velocity: start_body.linear_velocity,
321 angular_velocity: start_body.angular_velocity,
322 })
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
327pub enum SearchDirection {
328 Forward,
329 Backward,
330}
331
332pub fn find_in_direction<T, F, R>(
347 items: &[T],
348 current_index: usize,
349 direction: SearchDirection,
350 predicate: F,
351) -> Option<(usize, R)>
352where
353 F: Fn(&T) -> Option<R>,
354{
355 let mut iter: Box<dyn Iterator<Item = (usize, &T)>> = match direction {
356 SearchDirection::Forward => Box::new(
357 items[current_index + 1..]
358 .iter()
359 .enumerate()
360 .map(move |(i, item)| (i + current_index + 1, item)),
361 ),
362 SearchDirection::Backward => Box::new(items[..current_index].iter().enumerate().rev()),
363 };
364
365 iter.find_map(|(i, item)| predicate(item).map(|res| (i, res)))
366}