subtr-actor 1.0.0

Rocket League replay transformer
Documentation
use super::*;

fn player_id(id: u64) -> PlayerId {
    boxcars::RemoteId::Steam(id)
}

fn rigid_body(position: glam::Vec3) -> boxcars::RigidBody {
    boxcars::RigidBody {
        sleeping: false,
        location: glam_to_vec(&position),
        rotation: boxcars::Quaternion {
            x: 0.0,
            y: 0.0,
            z: 0.0,
            w: 1.0,
        },
        linear_velocity: Some(glam_to_vec(&glam::Vec3::ZERO)),
        angular_velocity: Some(glam_to_vec(&glam::Vec3::ZERO)),
    }
}

fn frame(frame_number: usize, time: f32) -> FrameInfo {
    FrameInfo {
        frame_number,
        time,
        dt: 0.25,
        seconds_remaining: None,
    }
}

fn ball(y: f32) -> BallFrameState {
    BallFrameState::Present(BallSample {
        rigid_body: rigid_body(glam::Vec3::new(0.0, y, BALL_RADIUS_Z)),
    })
}

fn players(player_one_y: f32, player_two_y: f32) -> PlayerFrameState {
    PlayerFrameState {
        players: vec![
            PlayerSample {
                player_id: player_id(1),
                is_team_0: true,
                hitbox: default_car_hitbox(),
                rigid_body: Some(rigid_body(glam::Vec3::new(0.0, player_one_y, 0.0))),
                boost_amount: None,
                last_boost_amount: None,
                boost_active: false,
                dodge_active: false,
                powerslide_active: false,
                match_goals: None,
                match_assists: None,
                match_saves: None,
                match_shots: None,
                match_score: None,
            },
            PlayerSample {
                player_id: player_id(2),
                is_team_0: false,
                hitbox: default_car_hitbox(),
                rigid_body: Some(rigid_body(glam::Vec3::new(0.0, player_two_y, 0.0))),
                boost_amount: None,
                last_boost_amount: None,
                boost_active: false,
                dodge_active: false,
                powerslide_active: false,
                match_goals: None,
                match_assists: None,
                match_saves: None,
                match_shots: None,
                match_score: None,
            },
        ],
    }
}

fn touch(frame_number: usize, time: f32, player: PlayerId, is_team_0: bool) -> TouchEvent {
    TouchEvent {
        touch_id: None,
        time,
        frame: frame_number,
        team_is_team_0: is_team_0,
        player: Some(player),
        player_position: None,
        closest_approach_distance: None,
        dodge_contact: false,
    }
}

fn touch_state(touches: Vec<TouchEvent>) -> TouchState {
    TouchState {
        touch_events: touches,
        ..TouchState::default()
    }
}

fn update(
    calculator: &mut ControlledPlayCalculator,
    frame_number: usize,
    time: f32,
    ball_y: f32,
    touches: Vec<TouchEvent>,
) {
    calculator
        .update(
            &frame(frame_number, time),
            &ball(ball_y),
            &players(ball_y, 4000.0),
            &touch_state(touches),
            &LivePlayState::active_play(),
        )
        .unwrap();
}

#[test]
fn emits_same_player_controlled_play_with_touch_span_and_close_time() {
    let mut calculator = ControlledPlayCalculator::new();
    update(
        &mut calculator,
        0,
        0.0,
        0.0,
        vec![touch(0, 0.0, player_id(1), true)],
    );
    update(&mut calculator, 1, 0.25, 100.0, vec![]);
    update(&mut calculator, 2, 0.50, 250.0, vec![]);
    update(&mut calculator, 3, 0.75, 500.0, vec![]);
    update(
        &mut calculator,
        4,
        1.00,
        900.0,
        vec![touch(4, 1.00, player_id(1), true)],
    );

    calculator.finish();

    assert_eq!(calculator.events().len(), 1);
    let event = &calculator.events()[0];
    assert_eq!(event.player_id, player_id(1));
    assert_eq!(event.touch_count, 2);
    assert_eq!(event.first_touch_time, 0.0);
    assert_eq!(event.last_touch_time, 1.0);
    assert_eq!(event.duration, 1.0);
    assert_eq!(event.close_duration, 1.0);
    assert_eq!(event.total_advance_distance, 900.0);
}

#[test]
fn rejects_candidate_when_first_to_last_touch_span_is_too_short() {
    let mut calculator = ControlledPlayCalculator::new();
    update(
        &mut calculator,
        0,
        0.0,
        0.0,
        vec![touch(0, 0.0, player_id(1), true)],
    );
    update(&mut calculator, 1, 0.25, 100.0, vec![]);
    update(&mut calculator, 2, 0.50, 250.0, vec![]);
    update(&mut calculator, 3, 0.75, 500.0, vec![]);
    update(
        &mut calculator,
        4,
        0.90,
        900.0,
        vec![touch(4, 0.90, player_id(1), true)],
    );
    update(&mut calculator, 5, 1.25, 900.0, vec![]);

    calculator.finish();

    assert!(calculator.events().is_empty());
}

#[test]
fn other_player_touch_breaks_candidate_before_it_can_validate() {
    let mut calculator = ControlledPlayCalculator::new();
    update(
        &mut calculator,
        0,
        0.0,
        0.0,
        vec![touch(0, 0.0, player_id(1), true)],
    );
    update(
        &mut calculator,
        1,
        0.50,
        100.0,
        vec![touch(1, 0.50, player_id(2), false)],
    );
    update(
        &mut calculator,
        2,
        1.25,
        200.0,
        vec![touch(2, 1.25, player_id(1), true)],
    );

    calculator.finish();

    assert!(calculator.events().is_empty());
}