From: hgn Date: Sat, 9 Mar 2024 20:32:26 +0000 (+0000) Subject: basic features of replay editor X-Git-Url: https://harrygodden.com/git/?a=commitdiff_plain;h=c137bd8a21c36e63f564801ceb800b47f1ec3c7b;hp=670daa775dec0954adb27354c3e37c66fc6567dd;p=carveJwlIkooP6JGAAIwe30JlM.git basic features of replay editor --- diff --git a/addon.c b/addon.c index 1b9f622..72083e3 100644 --- a/addon.c +++ b/addon.c @@ -787,7 +787,6 @@ static void T1_addon_cache_load_loop(void *_) } } -/* TODO: migrate to addon.c */ void addon_system_pre_update(void) { if( !vg_loader_availible() ) return; diff --git a/freecam.c b/freecam.c index 45713c6..d961ed6 100644 --- a/freecam.c +++ b/freecam.c @@ -42,15 +42,3 @@ void freecam_preupdate(void) v3_muladds( cam->pos, player_replay.freecam_v, vg.time_frame_delta, cam->pos ); } - -int freecam_cmd( int argc, const char *argv[] ) -{ - if( argc ){ - player_replay.freecam = atoi(argv[0]); - - if( player_replay.freecam ){ - vg_camera_copy( &skaterift.cam, &player_replay.replay_freecam ); - } - } - return 0; -} diff --git a/player_replay.c b/player_replay.c index 887c6f1..174851e 100644 --- a/player_replay.c +++ b/player_replay.c @@ -10,7 +10,10 @@ #include "player_dead.h" #include "player_glide.h" -struct replay_globals player_replay; +struct replay_globals player_replay = +{ + .active_keyframe = -1 +}; void replay_clear( replay_buffer *replay ) { @@ -68,13 +71,15 @@ static replay_frame *replay_newframe( replay_buffer *replay, data_table[ k_replay_framedata_gamestate ][1] = gamestate_size; data_table[ k_replay_framedata_sfx ][1] = sfx_count*sizeof(struct net_sfx); data_table[ k_replay_framedata_internal_gamestate ][1] = 0; - if( gamestate_size ){ + if( gamestate_size ) + { data_table[ k_replay_framedata_internal_gamestate ][1] = sizeof( replay_gamestate ); } data_table[ k_replay_framedata_glider ][1] = 0; - if( save_glider ){ + if( save_glider ) + { data_table[ k_replay_framedata_glider ][1] = sizeof(struct replay_glider_data); } @@ -82,15 +87,18 @@ static replay_frame *replay_newframe( replay_buffer *replay, u32 nextsize = replay_frame_calculate_data_offsets( data_table ); replay_frame *frame = NULL; - if( replay->head ){ + if( replay->head ) + { u32 headsize = replay->head->total_size, nextpos = ((void *)replay->head - replay->data) + headsize; - if( nextpos + nextsize > replay->size ){ + if( nextpos + nextsize > replay->size ) + { nextpos = 0; /* maintain contiguity */ - while( replay->tail ){ + while( replay->tail ) + { if( (void *)replay->tail - replay->data ) replay_tailpop( replay ); else break; @@ -100,8 +108,10 @@ static replay_frame *replay_newframe( replay_buffer *replay, check_again:; u32 tailpos = (void *)replay->tail - replay->data; - if( tailpos >= nextpos ){ - if( nextpos + nextsize > tailpos ){ + if( tailpos >= nextpos ) + { + if( nextpos + nextsize > tailpos ) + { replay_tailpop( replay ); if( replay->tail ) @@ -117,7 +127,8 @@ check_again:; else frame = replay->data; - for( u32 i=0; idata_table[i][0] = data_table[i][0]; frame->data_table[i][1] = data_table[i][1]; } @@ -217,7 +228,8 @@ f32 replay_subframe_time( replay_buffer *replay ) replay_frame *frame = replay->cursor_frame; if( !frame ) return 0.0f; replay_frame *next = frame->r; - if( next ){ + if( next ) + { f64 l = next->time - frame->time, t = (l <= (1.0/128.0))? 0.0: (replay->cursor - frame->time) / l; return vg_clampf( t, 0.0f, 1.0f ); @@ -228,37 +240,127 @@ f32 replay_subframe_time( replay_buffer *replay ) void replay_get_frame_camera( replay_frame *frame, vg_camera *cam ) { - cam->fov = frame->cam_fov; - v3_copy( frame->cam_pos, cam->pos ); - v3_copy( frame->cam_angles, cam->angles ); + cam->fov = frame->cam.fov; + v3_copy( frame->cam.pos, cam->pos ); + v3_copy( frame->cam.angles, cam->angles ); } void replay_get_camera( replay_buffer *replay, vg_camera *cam ) { cam->nearz = 0.1f; cam->farz = 100.0f; - if( replay->cursor_frame ){ + if( replay->cursor_frame ) + { replay_frame *next = replay->cursor_frame->r; - if( next ){ + if( next ) + { vg_camera temp; replay_get_frame_camera( replay->cursor_frame, cam ); replay_get_frame_camera( next, &temp ); vg_camera_lerp( cam, &temp, replay_subframe_time( replay ), cam ); } - else { + else + { replay_get_frame_camera( replay->cursor_frame, cam ); } } - else { + else + { v3_zero( cam->pos ); v3_zero( cam->angles ); cam->fov = 90.0f; } } -struct replay_rb{ +void skaterift_get_replay_cam( vg_camera *cam ) +{ + replay_buffer *replay = &player_replay.local; + + if( player_replay.active_keyframe != -1 ) + { + replay_keyframe *kf = + &player_replay.keyframes[player_replay.active_keyframe]; + + v3_copy( kf->cam.pos, cam->pos ); + v3_copy( kf->cam.angles, cam->angles ); + cam->fov = kf->cam.fov; + return; + } + + if( player_replay.keyframe_count >= 2 ) + { + for( u32 i=0; icursor) && (kf[1].time>replay->cursor) ) + { + f64 l = kf[1].time - kf[0].time, + t = (l <= (1.0/128.0))? 0.0: (replay->cursor-kf[0].time) / l; + + if( player_replay.keyframe_count >= 3 ) + { + f32 m_start = 0.5f, m_end = 0.5f; + + if( i > 0 ) + { + if( (t < 0.5f) || (i==player_replay.keyframe_count-2) ) + { + kf --; + } + } + + u32 last = player_replay.keyframe_count-1; + if( kf+0 == player_replay.keyframes ) m_start = 1.0f; + if( kf+2 == player_replay.keyframes+last ) m_end = 1.0f; + + f32 ts = vg_lerpf( kf[0].time, kf[1].time, 1.0f-m_start ), + te = vg_lerpf( kf[1].time, kf[2].time, m_end ); + + l = te-ts; + t = (replay->cursor-ts)/l; + + f32 t0 = t*m_start+(1.0f-m_start), + t1 = t*m_end; + + v3f ps, pe, as, ae; + f32 fs, fe; + + /* first order */ + v3_lerp( kf[0].cam.pos, kf[1].cam.pos, t0, ps ); + vg_camera_lerp_angles( kf[0].cam.angles, kf[1].cam.angles, + t0, as ); + fs = vg_lerpf( kf[0].cam.fov, kf[1].cam.fov, t0 ); + + v3_lerp( kf[1].cam.pos, kf[2].cam.pos, t1, pe ); + vg_camera_lerp_angles( kf[1].cam.angles, kf[2].cam.angles, + t1, ae ); + fe = vg_lerpf( kf[1].cam.fov, kf[2].cam.fov, t1 ); + + /* second order */ + v3_lerp( ps, pe, t, cam->pos ); + vg_camera_lerp_angles( as, ae, t, cam->angles ); + cam->fov = vg_lerpf( fs, fe, t ); + } + else + { + v3_lerp( kf[0].cam.pos, kf[1].cam.pos, t, cam->pos ); + vg_camera_lerp_angles( kf[0].cam.angles, kf[1].cam.angles, + t, cam->angles ); + cam->fov = vg_lerpf( kf[0].cam.fov, kf[1].cam.fov, t ); + } + return; + } + } + } + + replay_get_camera( replay, cam ); +} + +struct replay_rb +{ v3f co, v, w; v4f q; }; @@ -370,20 +472,20 @@ void skaterift_record_frame( replay_buffer *replay, int force_gamestate ) frame->time = vg.time; /* camera */ - v3_copy( localplayer.cam.pos, frame->cam_pos ); + v3_copy( localplayer.cam.pos, frame->cam.pos ); if( localplayer.gate_waiting ){ m4x3_mulv( localplayer.gate_waiting->transport, - frame->cam_pos, frame->cam_pos ); + frame->cam.pos, frame->cam.pos ); v3f v0; v3_angles_vector( localplayer.cam.angles, v0 ); m3x3_mulv( localplayer.gate_waiting->transport, v0, v0 ); - v3_angles( v0, frame->cam_angles ); + v3_angles( v0, frame->cam.angles ); } else - v3_copy( localplayer.cam.angles, frame->cam_angles ); + v3_copy( localplayer.cam.angles, frame->cam.angles ); - frame->cam_fov = localplayer.cam.fov; + frame->cam.fov = localplayer.cam.fov; /* animator */ void *dst = replay_frame_data( frame, k_replay_framedata_animator ), @@ -461,9 +563,9 @@ void skaterift_restore_frame( replay_frame *frame ){ memcpy( &player_glide.rb, &gs->glider_rb, sizeof(rigidbody) ); v3_copy( gs->angles, localplayer.angles ); - v3_copy( frame->cam_pos, localplayer.cam.pos ); - v3_copy( frame->cam_angles, localplayer.cam.angles ); - localplayer.cam.fov = frame->cam_fov; + v3_copy( frame->cam.pos, localplayer.cam.pos ); + v3_copy( frame->cam.angles, localplayer.cam.angles ); + localplayer.cam.fov = frame->cam.fov; memcpy( &localplayer.cam_control, &gs->cam_control, sizeof(struct player_cam_controller) ); @@ -526,12 +628,15 @@ void skaterift_replay_pre_update(void) else { if( button_down( k_srbind_replay_play ) ) player_replay.replay_control = k_replay_control_play; - if( button_down( k_srbind_replay_freecam ) ){ - player_replay.freecam = player_replay.freecam ^ 0x1; + if( button_down( k_srbind_replay_freecam ) ) + { + player_replay.use_freecam ^= 0x1; - if( player_replay.freecam ) + if( player_replay.use_freecam ) + { replay_get_camera( &player_replay.local, &player_replay.replay_freecam ); + } skaterift_replay_update_helpers(); } @@ -567,24 +672,29 @@ void skaterift_replay_pre_update(void) gui_helper_clear(); } - if( player_replay.freecam ){ - //freecam_preupdate(); + if( player_replay.use_freecam ) + { + freecam_preupdate(); } - else { - if( button_down( k_srbind_replay_resume ) ){ + else + { + if( button_down( k_srbind_replay_resume ) ) + { skaterift_replay_resume(); } } } } -static void skaterift_replay_update_helpers(void){ - player_replay.helper_resume->greyed = player_replay.freecam; +static void skaterift_replay_update_helpers(void) +{ + player_replay.helper_resume->greyed = player_replay.use_freecam; vg_str freecam_text; vg_strnull( &freecam_text, player_replay.helper_freecam->text, GUI_HELPER_TEXT_LENGTH ); - vg_strcat( &freecam_text, player_replay.freecam? "exit freecam": "freecam" ); + vg_strcat( &freecam_text, + player_replay.use_freecam? "exit freecam": "freecam" ); } void skaterift_replay_post_render(void) @@ -630,6 +740,17 @@ void skaterift_replay_post_render(void) } } +void skaterift_replay_init(void) +{ + u32 bytes = 1024*1024*10; + player_replay.local.data = vg_linear_alloc( vg_mem.rtmemory, bytes ); + player_replay.local.size = bytes; + replay_clear( &player_replay.local ); + + vg_console_reg_var( "replay_editor", &player_replay.editor_mode, + k_var_dtype_i32, VG_VAR_PERSISTENT ); +} + void skaterift_replay_debug_info(void) { player__debugtext( 2, "replay info" ); @@ -667,6 +788,23 @@ void skaterift_replay_debug_info(void) player__debugtext( 1, "cursor: %.2fs / %.2fs\n", cur, len ); } +static int _keyframe_cmp( const void *p1, const void *p2 ) +{ + const replay_keyframe *kf1 = p1, *kf2 = p2; + return kf1->time > kf2->time; +} + +static void replay_keyframe_sort(void) +{ + qsort( player_replay.keyframes, player_replay.keyframe_count, + sizeof(replay_keyframe), _keyframe_cmp ); +} + +static void replay_editor_imgui(void) +{ + +} + void skaterift_replay_imgui(void) { if( skaterift.activity != k_skaterift_replay ) return; @@ -684,37 +822,41 @@ void skaterift_replay_imgui(void) /* mainbar */ ui_px height = 32, cwidth = 2; - ui_rect bar = { 0, 0, vg.window_x, height }; - ui_fill( bar, ui_colour( k_ui_bg ) ); + ui_rect timeline = { 0, 0, vg.window_x, height }; + ui_fill( timeline, ui_colour( k_ui_bg ) ); /* cursor frame block */ - if( replay->cursor_frame ){ - if( replay->cursor_frame->r ){ + if( replay->cursor_frame ) + { + if( replay->cursor_frame->r ) + { f64 l = (replay->cursor_frame->r->time-replay->cursor_frame->time)/len, s = (replay->cursor_frame->time - start) / len; ui_rect box = { s*(f64)vg.window_x, 0, - VG_MAX(4,(ui_px)(l*vg.window_x)), bar[3]+2 }; + VG_MAX(4,(ui_px)(l*vg.window_x)), timeline[3]+2 }; ui_fill( box, ui_colour( k_ui_bg+4 ) ); } } /* cursor */ ui_rect cusor = { cur * (f64)vg.window_x - (cwidth/2), 0, - cwidth, bar[3] }; + cwidth, (player_replay.editor_mode? 0: 16) + timeline[3] }; ui_fill( cusor, ui_colour( k_ui_bg+7 ) ); /* latest state marker */ - if( replay->statehead ){ + if( replay->statehead ) + { f64 t = (replay->statehead->time - start) / len; - ui_rect tag = { t*(f64)vg.window_x, 0, 2, bar[3]+8 }; + ui_rect tag = { t*(f64)vg.window_x, 0, 2, timeline[3]+8 }; ui_fill( tag, ui_colour( k_ui_green+k_ui_brighter ) ); } /* previous state marker */ replay_frame *prev = replay_find_recent_stateframe( replay ); - if( prev ){ + if( prev ) + { f64 t = (prev->time - start) / len; - ui_rect tag = { t*(f64)vg.window_x, 0, 2, bar[3]+8 }; + ui_rect tag = { t*(f64)vg.window_x, 0, 2, timeline[3]+8 }; ui_fill( tag, ui_colour( k_ui_yellow+k_ui_brighter ) ); } @@ -724,6 +866,182 @@ void skaterift_replay_imgui(void) ui_text( cusor, buffer, 1, k_ui_align_middle_left, 0 ); snprintf( buffer, 128, "-%.2fs\n", len ); - ui_text( bar, buffer, 1, k_ui_align_middle_left, 0 ); - ui_text( bar, "0s", 1, k_ui_align_middle_right, 0 ); + ui_text( timeline, buffer, 1, k_ui_align_middle_left, 0 ); + ui_text( timeline, "0s", 1, k_ui_align_middle_right, 0 ); + + if( player_replay.editor_mode ) + { + gui_helper_clear(); + vg_ui.wants_mouse = 1; + + ui_rect panel = { 0, timeline[3] + 20, 200, 400 }; + ui_fill( panel, ui_opacity( ui_colour(k_ui_bg), 0.5f ) ); + ui_rect_pad( panel, (ui_px[2]){4,4} ); + + if( ui_button( panel, + (player_replay.replay_control == k_replay_control_play)? + "Pause": "Play" ) == k_ui_button_click ) + { + player_replay.replay_control ^= k_replay_control_play; + } + + + + + /* script bar */ + ui_rect script = { 0, height + 2, vg.window_x, 16 }; + ui_fill( script, ui_colour( k_ui_bg ) ); + + f64 mouse_t = start + ((f64)vg_ui.mouse[0] / (f64)vg.window_x)*len; + + /* keyframe draw and select */ + bool absorb_by_keyframe = 0; + ui_px lx = 0; + for( u32 i=0; itime-start)/len; + + ui_px x = t*(f64)vg.window_x-8; + + if( i ) + { + ui_rect con = { lx, script[1]+7, x-lx, 1 }; + ui_fill( con, ui_colour(k_ui_blue) ); + + vg_line( kf->cam.pos, + player_replay.keyframes[i-1].cam.pos, VG__BLUE ); + } + + ui_rect tag = { x, script[1], 16, 16 }; + + if( ui_inside_rect( tag, vg_ui.mouse ) ) + { + absorb_by_keyframe = 1; + + if( ui_click_down( UI_MOUSE_LEFT ) ) + { + if( player_replay.active_keyframe != i ) + { + player_replay.drag_wait = 1; + player_replay.active_keyframe = i; + replay_seek( &player_replay.local, kf->time ); + } + } + else + { + ui_outline( tag, 1, ui_colour(k_ui_fg), 0 ); + } + } + + if( i == player_replay.active_keyframe ) + { + ui_outline( tag, 2, ui_colour(k_ui_fg), 0 ); + + if( ui_inside_rect( tag, vg_ui.mouse_click ) + && !player_replay.drag_wait ) + { + if( ui_clicking( UI_MOUSE_LEFT ) ) + { + absorb_by_keyframe = 1; + tag[0] = vg_ui.mouse[0]-8; + replay_seek( &player_replay.local, mouse_t ); + } + else if( ui_click_up( UI_MOUSE_LEFT ) ) + { + kf->time = mouse_t; + replay_keyframe_sort(); + } + } + } + + ui_fill( tag, ui_colour(k_ui_blue) ); + lx = x; + } + + if( player_replay.drag_wait && !ui_clicking(UI_MOUSE_LEFT) ) + player_replay.drag_wait = 0; + + /* adding keyframes */ + if( ui_inside_rect( script, vg_ui.mouse ) ) + { + vg_ui.cursor = k_ui_cursor_hand; + + ui_rect cursor = { vg_ui.mouse[0], script[1], 4, 16 }; + ui_fill( cursor, ui_colour( k_ui_fg ) ); + + if( !absorb_by_keyframe && ui_click_down( UI_MOUSE_LEFT ) ) + { + u32 max = vg_list_size( player_replay.keyframes ); + if( player_replay.keyframe_count == max ) + { + ui_start_modal( "Maximum keyframes reached", UI_MODAL_BAD ); + } + else + { + replay_keyframe *kf = + &player_replay.keyframes[player_replay.keyframe_count++]; + + kf->time = mouse_t; + v3_copy( skaterift.cam.pos, kf->cam.pos ); + v3_copy( skaterift.cam.angles, kf->cam.angles ); + kf->cam.fov = skaterift.cam.fov; + + replay_keyframe_sort(); + } + } + } + + + /* timeline scrub */ + bool start_in_timeline = + ui_clicking(UI_MOUSE_LEFT) && + ui_inside_rect(timeline, vg_ui.mouse_click); + if( (ui_inside_rect( timeline, vg_ui.mouse )) || start_in_timeline ) + { + ui_rect cursor = { vg_ui.mouse[0], timeline[1], 4, timeline[3] }; + ui_fill( cursor, ui_colour( k_ui_fg ) ); + vg_ui.cursor = k_ui_cursor_ibeam; + + if( ui_clicking( UI_MOUSE_LEFT ) && start_in_timeline ) + { + replay_seek( &player_replay.local, mouse_t ); + player_replay.active_keyframe = -1; + } + } + + if( player_replay.active_keyframe != -1 ) + { + replay_keyframe *kf = + &player_replay.keyframes[ player_replay.active_keyframe ]; + + enum ui_button_state mask_using = + k_ui_button_holding_inside | + k_ui_button_holding_outside | + k_ui_button_click; + + if( ui_button( panel, "Edit cam" ) & mask_using ) + { + if( ui_click_down( UI_MOUSE_LEFT ) ) + { + /* init freecam */ + v3_copy( kf->cam.pos, player_replay.replay_freecam.pos ); + v3_copy( kf->cam.angles, player_replay.replay_freecam.angles ); + player_replay.replay_freecam.fov = kf->cam.fov; + } + + /* move freecam */ + vg_ui.wants_mouse = 0; + freecam_preupdate(); + + v3_copy( player_replay.replay_freecam.pos, skaterift.cam.pos ); + v3_copy( player_replay.replay_freecam.angles, skaterift.cam.angles); + skaterift.cam.fov = player_replay.replay_freecam.fov; + + v3_copy( skaterift.cam.pos, kf->cam.pos ); + v3_copy( skaterift.cam.angles, kf->cam.angles ); + kf->cam.fov = skaterift.cam.fov; + } + } + } } diff --git a/player_replay.h b/player_replay.h index b7f4a57..db69484 100644 --- a/player_replay.h +++ b/player_replay.h @@ -4,6 +4,8 @@ typedef struct replay_buffer replay_buffer; typedef struct replay_frame replay_frame; +typedef struct replay_keyframe replay_keyframe; + typedef struct replay_gamestate replay_gamestate; typedef struct replay_sfx replay_sfx; @@ -25,11 +27,17 @@ enum replay_framedata{ k_replay_framedata_rows }; -struct replay_frame { - v3f cam_pos, cam_angles; - f32 cam_fov; +struct replay_cam +{ + v3f pos, angles; + f32 fov; +}; +struct replay_frame +{ + struct replay_cam cam; f64 time; + replay_frame *l, *r; enum player_subsystem system; @@ -37,7 +45,15 @@ struct replay_frame { u16 data_table[k_replay_framedata_rows][2]; }; -struct replay_gamestate { +/* player-defined replay frames */ +struct replay_keyframe +{ + struct replay_cam cam; + f64 time; +}; + +struct replay_gamestate +{ rigidbody rb, glider_rb; /* TODO: these don't need to be saved with their full matrices */ v3f angles; @@ -46,7 +62,8 @@ struct replay_gamestate { }; /* we save this per-anim-frame. if there glider is existing in any state */ -struct replay_glider_data { +struct replay_glider_data +{ bool have_glider, glider_orphan; f32 t; v3f co; @@ -65,17 +82,26 @@ struct replay_globals f32 resume_transition; enum replay_control { - k_replay_control_scrub, - k_replay_control_play, - k_replay_control_resume + k_replay_control_scrub = 0x00, + k_replay_control_play = 0x01, + k_replay_control_resume= 0x02 } replay_control; f32 track_velocity; struct gui_helper *helper_resume, *helper_freecam; vg_camera replay_freecam; - i32 freecam; + + bool use_freecam; v3f freecam_v, freecam_w; + + i32 editor_mode; + + replay_keyframe keyframes[32]; + u32 keyframe_count; + i32 active_keyframe; + + bool drag_wait; } extern player_replay; @@ -95,3 +121,5 @@ void skaterift_replay_debug_info(void); void skaterift_record_frame( replay_buffer *replay, int force_gamestate ); void skaterift_replay_post_render(void); +void skaterift_replay_init(void); +void skaterift_get_replay_cam( vg_camera *cam ); diff --git a/skaterift.c b/skaterift.c index c9f9371..4f86538 100644 --- a/skaterift.c +++ b/skaterift.c @@ -188,12 +188,8 @@ static void skaterift_load_world_content(void){ skaterift_world_load_thread( &args ); } -static void skaterift_load_player_content(void){ - u32 bytes = 1024*1024*10; - player_replay.local.data = vg_linear_alloc( vg_mem.rtmemory, bytes ); - player_replay.local.size = bytes; - replay_clear( &player_replay.local ); - +static void skaterift_load_player_content(void) +{ particle_alloc( &particles_grind, 300 ); particle_alloc( &particles_env, 200 ); @@ -213,7 +209,6 @@ void vg_load(void) vg_audio.always_keep_compressed = 1; vg_console_reg_cmd( "load_world", skaterift_load_world_command, NULL ); - vg_console_reg_cmd( "fc", freecam_cmd, NULL ); vg_console_reg_var( "immobile", &localplayer.immobile, k_var_dtype_i32, 0 ); vg_loader_step( render_init, NULL ); @@ -231,6 +226,7 @@ void vg_load(void) vg_loader_step( workshop_init, NULL ); vg_loader_step( skateshop_init, NULL ); vg_loader_step( ent_tornado_init, NULL ); + vg_loader_step( skaterift_replay_init, NULL ); vg_loader_step( skaterift_load_player_content, NULL ); /* --------------------- */ @@ -548,19 +544,23 @@ static void render_scene(void){ render_world( holdout_world, &global_miniworld.cam, 1, 0, 1, 1 ); } -static void skaterift_composite_maincamera(void){ +static void skaterift_composite_maincamera(void) +{ vg_camera_lerp( &localplayer.cam, &world_static.focus_cam, vg_smoothstepf(world_static.focus_strength), &skaterift.cam ); - if( player_replay.freecam ){ - freecam_preupdate(); - v3_copy( player_replay.replay_freecam.pos, skaterift.cam.pos ); - v3_copy( player_replay.replay_freecam.angles, skaterift.cam.angles ); - skaterift.cam.fov = player_replay.replay_freecam.fov; - } - else { - if( skaterift.activity == k_skaterift_replay ){ - replay_get_camera( &player_replay.local, &skaterift.cam ); + if( skaterift.activity == k_skaterift_replay ) + { + if( player_replay.use_freecam ) + { + freecam_preupdate(); + v3_copy( player_replay.replay_freecam.pos, skaterift.cam.pos ); + v3_copy( player_replay.replay_freecam.angles, skaterift.cam.angles ); + skaterift.cam.fov = player_replay.replay_freecam.fov; + } + else + { + skaterift_get_replay_cam( &skaterift.cam ); } }