use super::*;
impl<'a> ReplayProcessor<'a> {
pub(crate) fn set_player_order_from_headers(&mut self) -> SubtrActorResult<()> {
let _player_stats = self
.replay
.properties
.iter()
.find(|(key, _)| key == "PlayerStats")
.ok_or_else(|| {
SubtrActorError::new(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
})?;
SubtrActorError::new_result(SubtrActorErrorVariant::PlayerStatsHeaderNotFound)
}
pub fn process_long_enough_to_get_actor_ids(&mut self) -> SubtrActorResult<()> {
let mut handler = |_p: &dyn ProcessorView, _f: &boxcars::Frame, n: usize, _current_time| {
if n > 10 * 30 {
SubtrActorError::new_result(SubtrActorErrorVariant::FinishProcessingEarly)
} else {
Ok(TimeAdvance::NextFrame)
}
};
let process_result = self.process(&mut handler);
if let Some(SubtrActorErrorVariant::FinishProcessingEarly) =
process_result.as_ref().err().map(|e| e.variant.clone())
{
Ok(())
} else {
process_result
}
}
pub(crate) fn set_player_order_from_frames(&mut self) -> SubtrActorResult<()> {
self.process_long_enough_to_get_actor_ids()?;
let player_to_team_0: HashMap<PlayerId, bool> = self
.player_to_actor_id
.keys()
.filter_map(|player_id| {
self.get_player_is_team_0(player_id)
.ok()
.map(|is_team_0| (player_id.clone(), is_team_0))
})
.collect();
let (team_zero, team_one): (Vec<_>, Vec<_>) = player_to_team_0
.keys()
.cloned()
.partition(|player_id| *player_to_team_0.get(player_id).unwrap());
self.team_zero = team_zero;
self.team_one = team_one;
self.team_zero
.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
self.team_one
.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
self.reset();
Ok(())
}
pub fn check_player_id_set(&self) -> SubtrActorResult<()> {
let known_players =
std::collections::HashSet::<_>::from_iter(self.player_to_actor_id.keys());
let original_players =
std::collections::HashSet::<_>::from_iter(self.iter_player_ids_in_order());
if original_players != known_players {
SubtrActorError::new_result(SubtrActorErrorVariant::InconsistentPlayerSet {
found: known_players.into_iter().cloned().collect(),
original: original_players.into_iter().cloned().collect(),
})
} else {
Ok(())
}
}
pub fn process_and_get_replay_meta(&mut self) -> SubtrActorResult<ReplayMeta> {
if self.player_to_actor_id.is_empty() {
self.process_long_enough_to_get_actor_ids()?;
}
self.get_replay_meta()
}
pub fn get_replay_meta(&self) -> SubtrActorResult<ReplayMeta> {
let empty_player_stats = Vec::new();
let player_stats = if let Some((_, boxcars::HeaderProp::Array(per_player))) = self
.replay
.properties
.iter()
.find(|(key, _)| key == "PlayerStats")
{
per_player
} else {
&empty_player_stats
};
let known_count = self.iter_player_ids_in_order().count();
if player_stats.len() != known_count {
log::warn!(
"Replay does not have player stats for all players. encountered {:?} {:?}",
known_count,
player_stats.len()
)
}
let get_player_info = |player_id| {
let fallback_name = String::new();
let stats = self
.get_player_name(player_id)
.ok()
.and_then(|name| find_player_stats(player_id, &name, player_stats).ok())
.or_else(|| find_player_stats(player_id, &fallback_name, player_stats).ok());
let name = self
.get_player_name(player_id)
.ok()
.or_else(|| {
stats.as_ref().and_then(|stats| {
stats.get("Name").and_then(|prop| match prop {
boxcars::HeaderProp::Str(name) => Some(name.clone()),
_ => None,
})
})
})
.or_else(|| remote_id_display_name(player_id))
.unwrap_or_else(|| format!("{player_id:?}"));
let car_body_id = self.get_player_loadout_body_id(player_id);
let car_body_name = self.get_player_loadout_body_name(player_id);
let car_hitbox_family =
hitbox_family_for_body_id_or_name(car_body_id, car_body_name.as_deref())
.map(|family| format!("{family:?}"));
Ok(PlayerInfo {
name,
stats,
remote_id: player_id.clone(),
car_body_id,
car_body_name,
car_hitbox_family,
})
};
let team_zero: SubtrActorResult<Vec<PlayerInfo>> =
self.team_zero.iter().map(get_player_info).collect();
let team_one: SubtrActorResult<Vec<PlayerInfo>> =
self.team_one.iter().map(get_player_info).collect();
Ok(ReplayMeta {
team_zero: team_zero?,
team_one: team_one?,
game_type: self.get_replay_game_type_details(),
season: season_from_headers(&self.replay.properties),
all_headers: self.replay.properties.clone(),
})
}
}
fn remote_id_display_name(player_id: &PlayerId) -> Option<String> {
Some(match player_id {
boxcars::RemoteId::PlayStation(id) if !id.name.is_empty() => id.name.clone(),
boxcars::RemoteId::PlayStation(id) => id.online_id.to_string(),
boxcars::RemoteId::PsyNet(id) => id.online_id.to_string(),
boxcars::RemoteId::SplitScreen(id) => id.to_string(),
boxcars::RemoteId::Steam(id) => id.to_string(),
boxcars::RemoteId::Switch(id) => id.online_id.to_string(),
boxcars::RemoteId::Xbox(id) => id.to_string(),
boxcars::RemoteId::QQ(id) => id.to_string(),
boxcars::RemoteId::Epic(id) => id.clone(),
})
}