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: &Vec<(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: &Vec<(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: &Vec<(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
250 .angular_velocity
251 .unwrap_or(boxcars::Vector3f {
252 x: 0.0,
253 y: 0.0,
254 z: 0.0,
255 });
256 let angular_velocity = glam::Vec3::new(rbav.x, rbav.y, rbav.z);
257 let magnitude = angular_velocity.length();
258 let angular_velocity_unit_vector = angular_velocity.normalize_or_zero();
259
260 let mut rotation = glam::Quat::from_xyzw(
261 rigid_body.rotation.x,
262 rigid_body.rotation.y,
263 rigid_body.rotation.z,
264 rigid_body.rotation.w,
265 );
266
267 if angular_velocity_unit_vector.length() != 0.0 {
268 let delta_rotation =
269 glam::Quat::from_axis_angle(angular_velocity_unit_vector, magnitude * time_delta);
270 rotation *= delta_rotation;
271 }
272
273 boxcars::Quaternion {
274 x: rotation.x,
275 y: rotation.y,
276 z: rotation.z,
277 w: rotation.w,
278 }
279}
280
281pub fn get_interpolated_rigid_body(
295 start_body: &boxcars::RigidBody,
296 start_time: f32,
297 end_body: &boxcars::RigidBody,
298 end_time: f32,
299 time: f32,
300) -> SubtrActorResult<boxcars::RigidBody> {
301 if !(start_time <= time && time <= end_time) {
302 return SubtrActorError::new_result(SubtrActorErrorVariant::InterpolationTimeOrderError {
303 start_time,
304 time,
305 end_time,
306 });
307 }
308
309 let duration = end_time - start_time;
310 let interpolation_amount = (time - start_time) / duration;
311 let start_position = util::vec_to_glam(&start_body.location);
312 let end_position = util::vec_to_glam(&end_body.location);
313 let interpolated_location = start_position.lerp(end_position, interpolation_amount);
314 let start_rotation = quat_to_glam(&start_body.rotation);
315 let end_rotation = quat_to_glam(&end_body.rotation);
316 let interpolated_rotation = start_rotation.slerp(end_rotation, interpolation_amount);
317
318 Ok(boxcars::RigidBody {
319 location: glam_to_vec(&interpolated_location),
320 rotation: glam_to_quat(&interpolated_rotation),
321 sleeping: start_body.sleeping,
322 linear_velocity: start_body.linear_velocity,
323 angular_velocity: start_body.angular_velocity,
324 })
325}
326
327#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
329pub enum SearchDirection {
330 Forward,
331 Backward,
332}
333
334pub fn find_in_direction<T, F, R>(
349 items: &[T],
350 current_index: usize,
351 direction: SearchDirection,
352 predicate: F,
353) -> Option<(usize, R)>
354where
355 F: Fn(&T) -> Option<R>,
356{
357 let mut iter: Box<dyn Iterator<Item = (usize, &T)>> = match direction {
358 SearchDirection::Forward => Box::new(
359 items[current_index + 1..]
360 .iter()
361 .enumerate()
362 .map(move |(i, item)| (i + current_index + 1, item)),
363 ),
364 SearchDirection::Backward => Box::new(items[..current_index].iter().enumerate().rev()),
365 };
366
367 iter.find_map(|(i, item)| predicate(item).map(|res| (i, res)))
368}