subtr-actor 0.5.0

Rocket League replay transformer
Documentation
use super::*;

#[derive(Default)]
pub struct FiftyFiftyStateCalculator {
    active_event: Option<ActiveFiftyFifty>,
    last_resolved_event: Option<FiftyFiftyEvent>,
    kickoff_touch_window_open: bool,
}

impl FiftyFiftyStateCalculator {
    pub fn new() -> Self {
        Self::default()
    }

    fn reset(&mut self) {
        self.active_event = None;
    }

    fn maybe_resolve_active_event(
        &mut self,
        frame: &FrameInfo,
        ball: &BallFrameState,
        possession_state: &PossessionState,
    ) -> Option<FiftyFiftyEvent> {
        let active = self.active_event.as_ref()?;
        let age = (frame.time - active.last_touch_time).max(0.0);
        if age < FIFTY_FIFTY_RESOLUTION_DELAY_SECONDS {
            return None;
        }

        let winning_team_is_team_0 = FiftyFiftyCalculator::winning_team_from_ball(active, ball);
        let possession_team_is_team_0 = possession_state.current_team_is_team_0;
        let should_resolve = winning_team_is_team_0.is_some()
            || possession_team_is_team_0.is_some()
            || age >= FIFTY_FIFTY_MAX_DURATION_SECONDS;
        if !should_resolve {
            return None;
        }

        let active = self.active_event.take()?;
        let event = FiftyFiftyEvent {
            start_time: active.start_time,
            start_frame: active.start_frame,
            resolve_time: frame.time,
            resolve_frame: frame.frame_number,
            is_kickoff: active.is_kickoff,
            team_zero_player: active.team_zero_player,
            team_one_player: active.team_one_player,
            team_zero_position: active.team_zero_position,
            team_one_position: active.team_one_position,
            midpoint: active.midpoint,
            plane_normal: active.plane_normal,
            winning_team_is_team_0,
            possession_team_is_team_0,
        };
        self.last_resolved_event = Some(event.clone());
        Some(event)
    }

    #[allow(clippy::too_many_arguments)]
    pub fn update(
        &mut self,
        frame: &FrameInfo,
        gameplay: &GameplayState,
        ball: &BallFrameState,
        players: &PlayerFrameState,
        touch_state: &TouchState,
        possession_state: &PossessionState,
        live_play_state: &LivePlayState,
    ) -> FiftyFiftyState {
        if FiftyFiftyCalculator::kickoff_phase_active(gameplay) {
            self.kickoff_touch_window_open = true;
        }

        if !live_play_state.is_live_play {
            self.reset();
            return FiftyFiftyState {
                active_event: None,
                resolved_events: Vec::new(),
                last_resolved_event: self.last_resolved_event.clone(),
            };
        }

        let has_touch = !touch_state.touch_events.is_empty();
        let has_contested_touch = touch_state
            .touch_events
            .iter()
            .any(|touch| touch.team_is_team_0)
            && touch_state
                .touch_events
                .iter()
                .any(|touch| !touch.team_is_team_0);

        if let Some(active_event) = self.active_event.as_mut() {
            let age = (frame.time - active_event.last_touch_time).max(0.0);
            if age <= FIFTY_FIFTY_CONTINUATION_TOUCH_WINDOW_SECONDS
                && active_event.contains_team_touch(&touch_state.touch_events)
            {
                active_event.last_touch_time = frame.time;
                active_event.last_touch_frame = frame.frame_number;
            }
        }

        let mut resolved_events = Vec::new();
        if let Some(event) = self.maybe_resolve_active_event(frame, ball, possession_state) {
            resolved_events.push(event);
        }

        if has_contested_touch {
            if self.active_event.is_none() {
                self.active_event = FiftyFiftyCalculator::contested_touch(
                    frame,
                    players,
                    &touch_state.touch_events,
                    self.kickoff_touch_window_open,
                );
            }
        } else if has_touch {
            if let Some(active_event) = self.active_event.as_mut() {
                let age = (frame.time - active_event.last_touch_time).max(0.0);
                if age <= FIFTY_FIFTY_CONTINUATION_TOUCH_WINDOW_SECONDS
                    && active_event.contains_team_touch(&touch_state.touch_events)
                {
                    active_event.last_touch_time = frame.time;
                    active_event.last_touch_frame = frame.frame_number;
                }
            }
        }

        if has_touch {
            self.kickoff_touch_window_open = false;
        }

        FiftyFiftyState {
            active_event: self.active_event.clone(),
            resolved_events,
            last_resolved_event: self.last_resolved_event.clone(),
        }
    }
}