use boxcars;
use serde::Serialize;
use crate::*;
pub use super::replay_data_heuristics::{ReplayDataHeuristicData, ReplayDataSupplementalData};
#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
#[ts(export)]
pub enum BallFrame {
Empty,
Data {
#[ts(as = "crate::ts_bindings::RigidBodyTs")]
rigid_body: boxcars::RigidBody,
},
}
impl BallFrame {
fn new_from_processor(processor: &ReplayProcessor, current_time: f32) -> Self {
if processor.get_ignore_ball_syncing().unwrap_or(false) {
Self::Empty
} else if let Ok(rigid_body) = processor.get_interpolated_ball_rigid_body(current_time, 0.0)
{
Self::new_from_rigid_body(rigid_body)
} else {
Self::Empty
}
}
fn new_from_rigid_body(rigid_body: boxcars::RigidBody) -> Self {
Self::Data { rigid_body }
}
}
#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
#[ts(export)]
pub enum PlayerFrame {
Empty,
Data {
#[ts(as = "crate::ts_bindings::RigidBodyTs")]
rigid_body: boxcars::RigidBody,
boost_amount: f32,
boost_active: bool,
powerslide_active: bool,
jump_active: bool,
double_jump_active: bool,
dodge_active: bool,
player_name: Option<String>,
team: Option<i32>,
is_team_0: Option<bool>,
},
}
impl PlayerFrame {
fn new_from_processor(
processor: &ReplayProcessor,
player_id: &PlayerId,
current_time: f32,
) -> SubtrActorResult<Self> {
let rigid_body =
processor.get_interpolated_player_rigid_body(player_id, current_time, 0.0)?;
if rigid_body.sleeping {
return Ok(PlayerFrame::Empty);
}
let boost_amount = processor.get_player_boost_level(player_id)?;
let boost_active = processor.get_boost_active(player_id).unwrap_or(0) % 2 == 1;
let powerslide_active = processor.get_powerslide_active(player_id).unwrap_or(false);
let jump_active = processor.get_jump_active(player_id).unwrap_or(0) % 2 == 1;
let double_jump_active = processor.get_double_jump_active(player_id).unwrap_or(0) % 2 == 1;
let dodge_active = processor.get_dodge_active(player_id).unwrap_or(0) % 2 == 1;
let player_name = processor.get_player_name(player_id).ok();
let team = processor
.get_player_team_key(player_id)
.ok()
.and_then(|team_key| team_key.parse::<i32>().ok());
let is_team_0 = processor.get_player_is_team_0(player_id).ok();
Ok(Self::from_data(
rigid_body,
boost_amount,
boost_active,
powerslide_active,
jump_active,
double_jump_active,
dodge_active,
player_name,
team,
is_team_0,
))
}
#[allow(clippy::too_many_arguments)]
fn from_data(
rigid_body: boxcars::RigidBody,
boost_amount: f32,
boost_active: bool,
powerslide_active: bool,
jump_active: bool,
double_jump_active: bool,
dodge_active: bool,
player_name: Option<String>,
team: Option<i32>,
is_team_0: Option<bool>,
) -> Self {
if rigid_body.sleeping {
Self::Empty
} else {
Self::Data {
rigid_body,
boost_amount,
boost_active,
powerslide_active,
jump_active,
double_jump_active,
dodge_active,
player_name,
team,
is_team_0,
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct PlayerData {
frames: Vec<PlayerFrame>,
}
impl PlayerData {
fn new() -> Self {
Self { frames: Vec::new() }
}
fn add_frame(&mut self, frame_index: usize, frame: PlayerFrame) {
let empty_frames_to_add = frame_index - self.frames.len();
if empty_frames_to_add > 0 {
for _ in 0..empty_frames_to_add {
self.frames.push(PlayerFrame::Empty)
}
}
self.frames.push(frame)
}
pub fn frames(&self) -> &Vec<PlayerFrame> {
&self.frames
}
pub fn frame_count(&self) -> usize {
self.frames.len()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct BallData {
frames: Vec<BallFrame>,
}
impl BallData {
fn new() -> Self {
Self { frames: Vec::new() }
}
fn add_frame(&mut self, frame_index: usize, frame: BallFrame) {
let empty_frames_to_add = frame_index - self.frames.len();
if empty_frames_to_add > 0 {
for _ in 0..empty_frames_to_add {
self.frames.push(BallFrame::Empty)
}
}
self.frames.push(frame)
}
pub fn frames(&self) -> &Vec<BallFrame> {
&self.frames
}
pub fn frame_count(&self) -> usize {
self.frames.len()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct MetadataFrame {
pub time: f32,
pub seconds_remaining: i32,
pub replicated_game_state_name: i32,
pub replicated_game_state_time_remaining: i32,
}
impl MetadataFrame {
fn new_from_processor(processor: &ReplayProcessor, time: f32) -> SubtrActorResult<Self> {
Ok(Self::new(
time,
processor.get_seconds_remaining()?,
processor.get_replicated_state_name().unwrap_or(0),
processor
.get_replicated_game_state_time_remaining()
.unwrap_or(0),
))
}
fn new(
time: f32,
seconds_remaining: i32,
replicated_game_state_name: i32,
replicated_game_state_time_remaining: i32,
) -> Self {
MetadataFrame {
time,
seconds_remaining,
replicated_game_state_name,
replicated_game_state_time_remaining,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct FrameData {
pub ball_data: BallData,
#[ts(as = "Vec<(crate::ts_bindings::RemoteIdTs, PlayerData)>")]
pub players: Vec<(PlayerId, PlayerData)>,
pub metadata_frames: Vec<MetadataFrame>,
}
#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
#[ts(export)]
pub struct ReplayData {
pub frame_data: FrameData,
pub meta: ReplayMeta,
pub demolish_infos: Vec<DemolishInfo>,
pub boost_pad_events: Vec<BoostPadEvent>,
pub boost_pads: Vec<ResolvedBoostPad>,
pub touch_events: Vec<TouchEvent>,
pub dodge_refreshed_events: Vec<DodgeRefreshedEvent>,
pub player_stat_events: Vec<PlayerStatEvent>,
pub goal_events: Vec<GoalEvent>,
pub heuristic_data: ReplayDataHeuristicData,
}
impl ReplayData {
pub fn as_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
pub fn as_pretty_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
}
impl FrameData {
fn new() -> Self {
FrameData {
ball_data: BallData::new(),
players: Vec::new(),
metadata_frames: Vec::new(),
}
}
pub fn frame_count(&self) -> usize {
self.metadata_frames.len()
}
pub fn duration(&self) -> f32 {
self.metadata_frames.last().map(|f| f.time).unwrap_or(0.0)
}
fn add_frame(
&mut self,
frame_metadata: MetadataFrame,
ball_frame: BallFrame,
player_frames: Vec<(PlayerId, PlayerFrame)>,
) -> SubtrActorResult<()> {
let frame_index = self.metadata_frames.len();
self.metadata_frames.push(frame_metadata);
self.ball_data.add_frame(frame_index, ball_frame);
for (player_id, frame) in player_frames {
self.players
.get_entry(player_id)
.or_insert_with(PlayerData::new)
.add_frame(frame_index, frame)
}
Ok(())
}
}
pub struct ReplayDataCollector {
frame_data: FrameData,
}
impl Default for ReplayDataCollector {
fn default() -> Self {
Self::new()
}
}
impl ReplayDataCollector {
pub fn new() -> Self {
ReplayDataCollector {
frame_data: FrameData::new(),
}
}
pub fn get_frame_data(self) -> FrameData {
self.frame_data
}
pub fn into_replay_data(self, processor: ReplayProcessor<'_>) -> SubtrActorResult<ReplayData> {
self.into_replay_data_with_supplemental_data(
processor,
ReplayDataSupplementalData::default(),
)
}
pub fn into_replay_data_with_supplemental_data(
self,
processor: ReplayProcessor<'_>,
supplemental_data: ReplayDataSupplementalData,
) -> SubtrActorResult<ReplayData> {
let meta = processor.get_replay_meta()?;
Ok(ReplayData {
meta,
demolish_infos: processor.demolishes,
boost_pad_events: processor.boost_pad_events,
boost_pads: supplemental_data.boost_pads,
touch_events: processor.touch_events,
dodge_refreshed_events: processor.dodge_refreshed_events,
player_stat_events: processor.player_stat_events,
goal_events: processor.goal_events,
heuristic_data: supplemental_data.heuristic_data,
frame_data: self.get_frame_data(),
})
}
pub fn get_replay_data(mut self, replay: &boxcars::Replay) -> SubtrActorResult<ReplayData> {
let mut processor = ReplayProcessor::new(replay)?;
let mut flip_reset_tracker = FlipResetTracker::new();
let mut boost_pad_collector = ResolvedBoostPadCollector::new();
processor.process_all(&mut [
&mut self,
&mut flip_reset_tracker,
&mut boost_pad_collector,
])?;
let supplemental_data =
ReplayDataSupplementalData::from_flip_reset_tracker(flip_reset_tracker)
.with_boost_pads(boost_pad_collector.into_resolved_boost_pads());
self.into_replay_data_with_supplemental_data(processor, supplemental_data)
}
fn get_player_frames(
&self,
processor: &ReplayProcessor,
current_time: f32,
) -> SubtrActorResult<Vec<(PlayerId, PlayerFrame)>> {
Ok(processor
.iter_player_ids_in_order()
.map(|player_id| {
(
player_id.clone(),
PlayerFrame::new_from_processor(processor, player_id, current_time)
.unwrap_or(PlayerFrame::Empty),
)
})
.collect())
}
}
impl Collector for ReplayDataCollector {
fn process_frame(
&mut self,
processor: &ReplayProcessor,
_frame: &boxcars::Frame,
_frame_number: usize,
current_time: f32,
) -> SubtrActorResult<TimeAdvance> {
let metadata_frame = MetadataFrame::new_from_processor(processor, current_time)?;
let ball_frame = BallFrame::new_from_processor(processor, current_time);
let player_frames = self.get_player_frames(processor, current_time)?;
self.frame_data
.add_frame(metadata_frame, ball_frame, player_frames)?;
Ok(TimeAdvance::NextFrame)
}
}