subtr-actor 0.6.0

Rocket League replay transformer
Documentation
use super::*;
use crate::stats::calculators::*;
use crate::*;

#[derive(Debug, Clone, Default)]
pub struct StatsTimelineFrameState {
    pub frame: Option<ReplayStatsFrame>,
}

pub struct StatsTimelineFrameNode {
    replay_meta: Option<ReplayMeta>,
    state: StatsTimelineFrameState,
}

impl StatsTimelineFrameNode {
    pub fn new() -> Self {
        Self {
            replay_meta: None,
            state: StatsTimelineFrameState::default(),
        }
    }

    fn replay_meta(&self) -> SubtrActorResult<&ReplayMeta> {
        self.replay_meta.as_ref().ok_or_else(|| {
            SubtrActorError::new(SubtrActorErrorVariant::CallbackError(
                "missing ReplayMeta state while building timeline frame".to_owned(),
            ))
        })
    }

    fn is_team_zero_player(replay_meta: &ReplayMeta, player: &PlayerInfo) -> bool {
        replay_meta
            .team_zero
            .iter()
            .any(|team_player| team_player.remote_id == player.remote_id)
    }

    fn team_snapshot(
        &self,
        ctx: &AnalysisStateContext<'_>,
        is_team_zero: bool,
    ) -> SubtrActorResult<TeamStatsSnapshot> {
        let fifty_fifty = ctx.get::<FiftyFiftyCalculator>()?;
        let possession = ctx.get::<PossessionCalculator>()?;
        let pressure = ctx.get::<PressureCalculator>()?;
        let rush = ctx.get::<RushCalculator>()?;
        let match_stats = ctx.get::<MatchStatsCalculator>()?;
        let backboard = ctx.get::<BackboardCalculator>()?;
        let double_tap = ctx.get::<DoubleTapCalculator>()?;
        let ball_carry = ctx.get::<BallCarryCalculator>()?;
        let boost = ctx.get::<BoostCalculator>()?;
        let movement = ctx.get::<MovementCalculator>()?;
        let powerslide = ctx.get::<PowerslideCalculator>()?;
        let demo = ctx.get::<DemoCalculator>()?;
        Ok(TeamStatsSnapshot {
            fifty_fifty: fifty_fifty.stats().for_team(is_team_zero),
            possession: possession.stats().for_team(is_team_zero),
            pressure: pressure.stats().for_team(is_team_zero),
            rush: rush.stats().for_team(is_team_zero),
            core: if is_team_zero {
                match_stats.team_zero_stats()
            } else {
                match_stats.team_one_stats()
            },
            backboard: if is_team_zero {
                backboard.team_zero_stats().clone()
            } else {
                backboard.team_one_stats().clone()
            },
            double_tap: if is_team_zero {
                double_tap.team_zero_stats().clone()
            } else {
                double_tap.team_one_stats().clone()
            },
            ball_carry: if is_team_zero {
                ball_carry.team_zero_stats().clone()
            } else {
                ball_carry.team_one_stats().clone()
            },
            boost: if is_team_zero {
                boost.team_zero_stats().clone()
            } else {
                boost.team_one_stats().clone()
            },
            movement: if is_team_zero {
                movement.team_zero_stats().clone()
            } else {
                movement.team_one_stats().clone()
            },
            powerslide: if is_team_zero {
                powerslide.team_zero_stats().clone()
            } else {
                powerslide.team_one_stats().clone()
            },
            demo: if is_team_zero {
                demo.team_zero_stats().clone()
            } else {
                demo.team_one_stats().clone()
            },
        })
    }

    fn player_snapshot(
        &self,
        ctx: &AnalysisStateContext<'_>,
        replay_meta: &ReplayMeta,
        player: &PlayerInfo,
    ) -> SubtrActorResult<PlayerStatsSnapshot> {
        let player_id = &player.remote_id;
        Ok(PlayerStatsSnapshot {
            player_id: player.remote_id.clone(),
            name: player.name.clone(),
            is_team_0: Self::is_team_zero_player(replay_meta, player),
            core: ctx
                .get::<MatchStatsCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            backboard: ctx
                .get::<BackboardCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            ceiling_shot: ctx
                .get::<CeilingShotCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            double_tap: ctx
                .get::<DoubleTapCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            fifty_fifty: ctx
                .get::<FiftyFiftyCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            speed_flip: ctx
                .get::<SpeedFlipCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            touch: ctx
                .get::<TouchCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default()
                .with_complete_labeled_touch_counts(),
            musty_flick: ctx
                .get::<MustyFlickCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            dodge_reset: ctx
                .get::<DodgeResetCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            ball_carry: ctx
                .get::<BallCarryCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            boost: ctx
                .get::<BoostCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            movement: ctx
                .get::<MovementCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default()
                .with_complete_labeled_tracked_time(),
            positioning: ctx
                .get::<PositioningCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            powerslide: ctx
                .get::<PowerslideCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
            demo: ctx
                .get::<DemoCalculator>()?
                .player_stats()
                .get(player_id)
                .cloned()
                .unwrap_or_default(),
        })
    }
}

impl Default for StatsTimelineFrameNode {
    fn default() -> Self {
        Self::new()
    }
}

impl AnalysisNode for StatsTimelineFrameNode {
    type State = StatsTimelineFrameState;

    fn name(&self) -> &'static str {
        "stats_timeline_frame"
    }

    fn on_replay_meta(&mut self, meta: &ReplayMeta) -> SubtrActorResult<()> {
        self.replay_meta = Some(meta.clone());
        Ok(())
    }

    fn dependencies(&self) -> NodeDependencies {
        vec![
            frame_info_dependency(),
            gameplay_state_dependency(),
            live_play_dependency(),
            match_stats_dependency(),
            backboard_dependency(),
            ceiling_shot_dependency(),
            double_tap_dependency(),
            fifty_fifty_dependency(),
            possession_dependency(),
            pressure_dependency(),
            rush_dependency(),
            touch_dependency(),
            speed_flip_dependency(),
            musty_flick_dependency(),
            dodge_reset_dependency(),
            ball_carry_dependency(),
            boost_dependency(),
            movement_dependency(),
            positioning_dependency(),
            powerslide_dependency(),
            demo_dependency(),
        ]
    }

    fn evaluate(&mut self, ctx: &AnalysisStateContext<'_>) -> SubtrActorResult<()> {
        let replay_meta = self.replay_meta()?;
        let frame = ctx.get::<FrameInfo>()?;
        let gameplay = ctx.get::<GameplayState>()?;
        let live_play_state = ctx.get::<LivePlayState>()?;
        self.state.frame = Some(ReplayStatsFrame {
            frame_number: frame.frame_number,
            time: frame.time,
            dt: frame.dt,
            seconds_remaining: frame.seconds_remaining,
            game_state: gameplay.game_state,
            gameplay_phase: live_play_state.gameplay_phase,
            is_live_play: live_play_state.is_live_play,
            team_zero: self.team_snapshot(ctx, true)?,
            team_one: self.team_snapshot(ctx, false)?,
            players: replay_meta
                .player_order()
                .map(|player| self.player_snapshot(ctx, replay_meta, player))
                .collect::<SubtrActorResult<Vec<_>>>()?,
        });
        Ok(())
    }

    fn state(&self) -> &Self::State {
        &self.state
    }
}

pub(crate) fn boxed_default() -> Box<dyn AnalysisNodeDyn> {
    Box::new(StatsTimelineFrameNode::new())
}