a mess but stable
[carveJwlIkooP6JGAAIwe30JlM.git] / player_replay.c
1 #include "skaterift.h"
2 #include "player.h"
3 #include "player_replay.h"
4 #include "input.h"
5 #include "gui.h"
6 #include "freecam.h"
7
8 #include "player_walk.h"
9 #include "player_skate.h"
10 #include "player_dead.h"
11 #include "player_glide.h"
12
13 struct replay_globals player_replay =
14 {
15 .active_keyframe = -1,
16 .show_ui = 1,
17 .editor_mode = 0
18 };
19
20 void replay_clear( replay_buffer *replay )
21 {
22 replay->head = NULL;
23 replay->tail = NULL;
24 replay->cursor_frame = NULL;
25 replay->statehead = NULL;
26 replay->cursor = -99999.9;
27 }
28
29 void *replay_frame_data( replay_frame *frame, enum replay_framedata type )
30 {
31 if( frame->data_table[type][1] == 0 )
32 return NULL;
33
34 void *baseptr = frame;
35 return baseptr + frame->data_table[type][0];
36 }
37
38 static u16 replay_frame_calculate_data_offsets(
39 u16 data_table[k_replay_framedata_rows][2] ){
40
41 u32 total = vg_align8( sizeof(replay_frame) );
42 for( u32 i=0; i<k_replay_framedata_rows; i++ ){
43 data_table[i][0] = total;
44 total += vg_align8(data_table[i][1]);
45
46 if( total > 0xffff )
47 vg_fatal_error( "Exceeded frame storage capacity\n" );
48 }
49 return total;
50 }
51
52 static void replay_tailpop( replay_buffer *replay ){
53 if( replay->cursor_frame == replay->tail )
54 replay->cursor_frame = NULL;
55 if( replay->statehead == replay->tail )
56 replay->statehead = NULL;
57
58 replay->tail = replay->tail->r;
59
60 if( replay->tail )
61 replay->tail->l = NULL;
62 else
63 replay->head = NULL;
64 }
65
66 static replay_frame *replay_newframe( replay_buffer *replay,
67 u16 animator_size,
68 u16 gamestate_size,
69 u16 sfx_count,
70 bool save_glider ){
71 u16 data_table[ k_replay_framedata_rows ][2];
72 data_table[ k_replay_framedata_animator ][1] = animator_size;
73 data_table[ k_replay_framedata_gamestate ][1] = gamestate_size;
74 data_table[ k_replay_framedata_sfx ][1] = sfx_count*sizeof(struct net_sfx);
75 data_table[ k_replay_framedata_internal_gamestate ][1] = 0;
76 if( gamestate_size )
77 {
78 data_table[ k_replay_framedata_internal_gamestate ][1] =
79 sizeof( replay_gamestate );
80 }
81
82 data_table[ k_replay_framedata_glider ][1] = 0;
83 if( save_glider )
84 {
85 data_table[ k_replay_framedata_glider ][1] =
86 sizeof(struct replay_glider_data);
87 }
88
89 u32 nextsize = replay_frame_calculate_data_offsets( data_table );
90
91 replay_frame *frame = NULL;
92 if( replay->head )
93 {
94 u32 headsize = replay->head->total_size,
95 nextpos = ((void *)replay->head - replay->data) + headsize;
96
97 if( nextpos + nextsize > replay->size )
98 {
99 nextpos = 0;
100
101 /* maintain contiguity */
102 while( replay->tail )
103 {
104 if( (void *)replay->tail - replay->data )
105 replay_tailpop( replay );
106 else break;
107 }
108 }
109
110 check_again:;
111 u32 tailpos = (void *)replay->tail - replay->data;
112
113 if( tailpos >= nextpos )
114 {
115 if( nextpos + nextsize > tailpos )
116 {
117 replay_tailpop( replay );
118
119 if( replay->tail )
120 goto check_again;
121 }
122 }
123
124 frame = replay->data + nextpos;
125
126 if( replay->head )
127 replay->head->r = frame;
128 }
129 else
130 frame = replay->data;
131
132 for( u32 i=0; i<k_replay_framedata_rows; i++ )
133 {
134 frame->data_table[i][0] = data_table[i][0];
135 frame->data_table[i][1] = data_table[i][1];
136 }
137
138 frame->total_size = nextsize;
139 frame->l = replay->head;
140 frame->r = NULL;
141 replay->head = frame;
142 if( !replay->tail ) replay->tail = frame;
143 if( gamestate_size ) replay->statehead = frame;
144
145 return frame;
146 }
147
148 static void replay_emit_frame_sounds( replay_frame *frame ){
149 void *baseptr = frame;
150 u16 *inf = frame->data_table[k_replay_framedata_sfx];
151 struct net_sfx *buffer = baseptr + inf[0];
152 u32 count = inf[1] / sizeof(struct net_sfx);
153
154 for( u32 i=0; i<count; i ++ ){
155 net_sfx_play( buffer + i );
156 }
157 }
158
159 int replay_seek( replay_buffer *replay, f64 t )
160 {
161 if( !replay->head ) return 0;
162
163 if( t < replay->tail->time ) t = replay->tail->time;
164 if( t > replay->head->time ) t = replay->head->time;
165
166 if( !replay->cursor_frame ) {
167 replay->cursor = replay->head->time;
168 replay->cursor_frame = replay->head;
169
170 if( fabs(replay->head->time-t) > fabs(replay->tail->time-t) ){
171 replay->cursor = replay->tail->time;
172 replay->cursor_frame = replay->tail;
173 }
174 }
175
176 f64 dir = t - replay->cursor;
177 if( dir == 0.0 ) return 0;
178 dir = vg_signf( dir );
179
180
181 u32 i=4096;
182 while( i --> 0 ){
183 if( dir < 0.0 ){
184 if( t > replay->cursor_frame->time ) {
185 replay->cursor = t;
186 return 1;
187 }
188 }
189
190 replay_frame *next;
191 if( dir > 0.0 ) next = replay->cursor_frame->r;
192 else next = replay->cursor_frame->l;
193
194 if( !next ) break;
195
196 if( dir > 0.0 ){
197 if( t < next->time ){
198 replay->cursor = t;
199 return 1;
200 }
201 }
202
203 replay_emit_frame_sounds( next );
204
205 replay->cursor_frame = next;
206 replay->cursor = next->time;
207
208 if( !i ) return 1;
209 }
210
211 replay->cursor = t;
212 return 0;
213 }
214
215 replay_frame *replay_find_recent_stateframe( replay_buffer *replay )
216 {
217 replay_frame *frame = replay->cursor_frame;
218 u32 i=4096;
219 while( i --> 0 ){
220 if( !frame ) return frame;
221 if( frame->data_table[ k_replay_framedata_gamestate ][1] ) return frame;
222 frame = frame->l;
223 }
224
225 return NULL;
226 }
227
228 f32 replay_subframe_time( replay_buffer *replay )
229 {
230 replay_frame *frame = replay->cursor_frame;
231 if( !frame ) return 0.0f;
232 replay_frame *next = frame->r;
233 if( next )
234 {
235 f64 l = next->time - frame->time,
236 t = (l <= (1.0/128.0))? 0.0: (replay->cursor - frame->time) / l;
237 return vg_clampf( t, 0.0f, 1.0f );
238 }
239 else
240 return 0.0f;
241 }
242
243 void replay_get_frame_camera( replay_frame *frame, vg_camera *cam )
244 {
245 cam->fov = frame->cam.fov;
246 v3_copy( frame->cam.pos, cam->pos );
247 v3_copy( frame->cam.angles, cam->angles );
248 }
249
250 void replay_get_camera( replay_buffer *replay, vg_camera *cam )
251 {
252 cam->nearz = 0.1f;
253 cam->farz = 100.0f;
254 if( replay->cursor_frame )
255 {
256 replay_frame *next = replay->cursor_frame->r;
257
258 if( next )
259 {
260 vg_camera temp;
261
262 replay_get_frame_camera( replay->cursor_frame, cam );
263 replay_get_frame_camera( next, &temp );
264 vg_camera_lerp( cam, &temp, replay_subframe_time( replay ), cam );
265 }
266 else
267 {
268 replay_get_frame_camera( replay->cursor_frame, cam );
269 }
270 }
271 else
272 {
273 v3_zero( cam->pos );
274 v3_zero( cam->angles );
275 cam->fov = 90.0f;
276 }
277 }
278
279 void skaterift_get_replay_cam( vg_camera *cam )
280 {
281 replay_buffer *replay = &player_replay.local;
282
283 if( player_replay.active_keyframe != -1 )
284 {
285 replay_keyframe *kf =
286 &player_replay.keyframes[player_replay.active_keyframe];
287
288 v3_copy( kf->cam.pos, cam->pos );
289 v3_copy( kf->cam.angles, cam->angles );
290 cam->fov = kf->cam.fov;
291 return;
292 }
293
294 if( player_replay.keyframe_count >= 2 )
295 {
296 for( u32 i=0; i<player_replay.keyframe_count-1; i ++ )
297 {
298 replay_keyframe *kf = &player_replay.keyframes[i];
299
300 if( (kf[0].time<=replay->cursor) && (kf[1].time>replay->cursor) )
301 {
302 f64 l = kf[1].time - kf[0].time,
303 t = (l <= (1.0/128.0))? 0.0: (replay->cursor-kf[0].time) / l;
304
305 if( player_replay.keyframe_count >= 3 )
306 {
307 f32 m_start = 0.5f, m_end = 0.5f;
308
309 if( i > 0 )
310 {
311 if( (t < 0.5f) || (i==player_replay.keyframe_count-2) )
312 {
313 kf --;
314 }
315 }
316
317 u32 last = player_replay.keyframe_count-1;
318 if( kf+0 == player_replay.keyframes ) m_start = 1.0f;
319 if( kf+2 == player_replay.keyframes+last ) m_end = 1.0f;
320
321 f32 ts = vg_lerpf( kf[0].time, kf[1].time, 1.0f-m_start ),
322 te = vg_lerpf( kf[1].time, kf[2].time, m_end );
323
324 l = te-ts;
325 t = (replay->cursor-ts)/l;
326
327 /*
328 * Adjust t, so that its derivative matches at the endpoints.
329 * Since t needs to go from 0 to 1, it will naturally change at
330 * different rates between keyframes. So this smooths it out.
331 *
332 * Newton method, going through standard direct quadratic eq has
333 * precision / other problems. Also we only care about 0>t>1.
334 */
335 f32 b = (kf[1].time-ts)/l,
336 x0 = 1.0-t;
337 for( u32 i=0; i<4; i ++ )
338 {
339 f32 ix0 = 1.0f-x0,
340 fx_x0 = 2.0f*b*x0*ix0 + ix0*ix0 - t,
341 fxd_x0 = 2.0f*(-2.0f*b*x0 + b + x0 - 1.0f);
342 x0 = x0 - (fx_x0/fxd_x0);
343 }
344 t = 1.0-x0;
345
346 f32 t0 = t*m_start+(1.0f-m_start),
347 t1 = t*m_end;
348
349 v3f ps, pe, as, ae;
350 f32 fs, fe;
351
352 /* first order */
353 v3_lerp( kf[0].cam.pos, kf[1].cam.pos, t0, ps );
354 vg_camera_lerp_angles( kf[0].cam.angles, kf[1].cam.angles,
355 t0, as );
356 fs = vg_lerpf( kf[0].cam.fov, kf[1].cam.fov, t0 );
357
358 v3_lerp( kf[1].cam.pos, kf[2].cam.pos, t1, pe );
359 vg_camera_lerp_angles( kf[1].cam.angles, kf[2].cam.angles,
360 t1, ae );
361 fe = vg_lerpf( kf[1].cam.fov, kf[2].cam.fov, t1 );
362
363 /* second order */
364 v3_lerp( ps, pe, t, cam->pos );
365 vg_camera_lerp_angles( as, ae, t, cam->angles );
366 cam->fov = vg_lerpf( fs, fe, t );
367 }
368 else
369 {
370 v3_lerp( kf[0].cam.pos, kf[1].cam.pos, t, cam->pos );
371 vg_camera_lerp_angles( kf[0].cam.angles, kf[1].cam.angles,
372 t, cam->angles );
373 cam->fov = vg_lerpf( kf[0].cam.fov, kf[1].cam.fov, t );
374 }
375 return;
376 }
377 }
378 }
379
380 replay_get_camera( replay, cam );
381 }
382
383 struct replay_rb
384 {
385 v3f co, v, w;
386 v4f q;
387 };
388
389 void skaterift_record_frame( replay_buffer *replay, int force_gamestate )
390 {
391 f64 delta = 9999999.9,
392 statedelta = 9999999.9;
393
394 if( replay->head )
395 delta = vg.time - replay->head->time;
396
397 if( replay->statehead )
398 statedelta = vg.time - replay->statehead->time;
399
400 const f64 k_replay_rate = 1.0/30.0,
401 k_gamestate_rate = 0.5;
402
403 int save_frame = 0,
404 save_state = 0,
405 save_glider = 0;
406
407 if( force_gamestate ) save_state = 1;
408 if( statedelta > k_gamestate_rate ) save_state = 1;
409 if( delta > k_replay_rate ) save_frame = 1;
410 if( save_state ) save_frame = 1;
411
412 if( localplayer.have_glider || localplayer.glider_orphan ||
413 localplayer.subsystem == k_player_subsystem_glide ){
414 save_glider = 1;
415 }
416
417 if( !save_frame ) return;
418
419 u16 gamestate_size = 0;
420 if( save_state ){
421 /* TODO: have as part of system struct */
422 gamestate_size = (u32 []){
423 [k_player_subsystem_walk ] = sizeof(struct player_walk_state),
424 [k_player_subsystem_drive] = 0,
425 [k_player_subsystem_skate] = sizeof(struct player_skate_state),
426 [k_player_subsystem_dead ] = localplayer.ragdoll.part_count *
427 sizeof(struct replay_rb),
428 [k_player_subsystem_glide] = sizeof(struct replay_rb),
429 }[ localplayer.subsystem ];
430 }
431
432 u16 animator_size = player_subsystems[localplayer.subsystem]->animator_size;
433
434 replay_frame *frame = replay_newframe( replay,
435 animator_size, gamestate_size,
436 localplayer.local_sfx_buffer_count,
437 save_glider );
438 frame->system = localplayer.subsystem;
439
440 if( save_state ){
441 replay_gamestate *gs =
442 replay_frame_data( frame, k_replay_framedata_internal_gamestate );
443
444 gs->current_run_version = world_static.current_run_version;
445 gs->drowned = localplayer.drowned;
446
447 /* permanent block */
448 memcpy( &gs->rb, &localplayer.rb, sizeof(rigidbody) );
449 memcpy( &gs->glider_rb, &player_glide.rb, sizeof(rigidbody) );
450 memcpy( &gs->cam_control, &localplayer.cam_control,
451 sizeof(struct player_cam_controller) );
452 v3_copy( localplayer.angles, gs->angles );
453
454 void *dst = replay_frame_data( frame, k_replay_framedata_gamestate );
455
456 /* subsytem/dynamic block */
457 if( localplayer.subsystem == k_player_subsystem_walk )
458 memcpy( dst, &player_walk.state, gamestate_size );
459 else if( localplayer.subsystem == k_player_subsystem_skate )
460 memcpy( dst, &player_skate.state, gamestate_size );
461 else if( localplayer.subsystem == k_player_subsystem_dead ){
462 struct replay_rb *arr = dst;
463 for( u32 i=0; i<localplayer.ragdoll.part_count; i ++ ){
464 rigidbody *rb = &localplayer.ragdoll.parts[i].rb;
465 v3_copy( rb->co, arr[i].co );
466 v3_copy( rb->w, arr[i].w );
467 v3_copy( rb->v, arr[i].v );
468 v4_copy( rb->q, arr[i].q );
469 }
470 }
471 else if( localplayer.subsystem == k_player_subsystem_glide ){
472 struct replay_rb *arr = dst;
473 rigidbody *rb = &player_glide.rb;
474 v3_copy( rb->co, arr[0].co );
475 v3_copy( rb->w, arr[0].w );
476 v3_copy( rb->v, arr[0].v );
477 v4_copy( rb->q, arr[0].q );
478 }
479 }
480
481 if( save_glider ){
482 struct replay_glider_data *inf =
483 replay_frame_data( frame, k_replay_framedata_glider );
484
485 inf->have_glider = localplayer.have_glider;
486 inf->glider_orphan = localplayer.glider_orphan;
487 inf->t = player_glide.t;
488 v3_copy( player_glide.rb.co, inf->co );
489 v4_copy( player_glide.rb.q, inf->q );
490 }
491
492 replay->cursor = vg.time;
493 replay->cursor_frame = frame;
494 frame->time = vg.time;
495
496 /* camera */
497 v3_copy( localplayer.cam.pos, frame->cam.pos );
498 if( localplayer.gate_waiting ){
499 m4x3_mulv( localplayer.gate_waiting->transport,
500 frame->cam.pos, frame->cam.pos );
501
502 v3f v0;
503 v3_angles_vector( localplayer.cam.angles, v0 );
504 m3x3_mulv( localplayer.gate_waiting->transport, v0, v0 );
505 v3_angles( v0, frame->cam.angles );
506 }
507 else
508 v3_copy( localplayer.cam.angles, frame->cam.angles );
509
510 frame->cam.fov = localplayer.cam.fov;
511
512 /* animator */
513 void *dst = replay_frame_data( frame, k_replay_framedata_animator ),
514 *src = player_subsystems[localplayer.subsystem]->animator_data;
515 memcpy( dst, src, animator_size );
516
517 /* sound effects */
518 memcpy( replay_frame_data( frame, k_replay_framedata_sfx ),
519 localplayer.local_sfx_buffer,
520 sizeof(struct net_sfx)*localplayer.local_sfx_buffer_count );
521
522 localplayer.local_sfx_buffer_count = 0;
523 }
524
525 static void skaterift_restore_frame( replay_frame *frame )
526 {
527 replay_gamestate *gs =
528 replay_frame_data( frame, k_replay_framedata_internal_gamestate );
529 void *src = replay_frame_data( frame, k_replay_framedata_gamestate );
530 u16 src_size = frame->data_table[ k_replay_framedata_gamestate ][1];
531 world_static.current_run_version = gs->current_run_version;
532 localplayer.drowned = gs->drowned;
533
534 if(frame->system == k_player_subsystem_walk ){
535 memcpy( &player_walk.state, src, src_size );
536 }
537 else if( frame->system == k_player_subsystem_skate ){
538 memcpy( &player_skate.state, src, src_size );
539 }
540 else if( frame->system == k_player_subsystem_dead ){
541 player__dead_transition(0);
542 struct replay_rb *arr = src;
543
544 for( u32 i=0; i<localplayer.ragdoll.part_count; i ++ ){
545 struct ragdoll_part *part = &localplayer.ragdoll.parts[i];
546 rigidbody *rb = &part->rb;
547
548 v3_copy( arr[i].co, rb->co );
549 v3_copy( arr[i].w, rb->w );
550 v3_copy( arr[i].v, rb->v );
551 v4_copy( arr[i].q, rb->q );
552
553 v3_copy( arr[i].co, part->prev_co );
554 v4_copy( arr[i].q, part->prev_q );
555 rb_update_matrices( rb );
556 }
557 }
558 else if( frame->system == k_player_subsystem_glide ){
559 struct replay_rb *arr = src;
560 rigidbody *rb = &player_glide.rb;
561 v3_copy( arr[0].co, rb->co );
562 v3_copy( arr[0].w, rb->w );
563 v3_copy( arr[0].v, rb->v );
564 v4_copy( arr[0].q, rb->q );
565 rb_update_matrices( rb );
566 }
567
568 localplayer.subsystem = frame->system;
569
570 /* restore the seperated glider data if we have it */
571 if( frame->data_table[ k_replay_framedata_glider ][1] ){
572 struct replay_glider_data *inf =
573 replay_frame_data( frame, k_replay_framedata_glider );
574
575 localplayer.have_glider = inf->have_glider;
576 localplayer.glider_orphan = inf->glider_orphan;
577 player_glide.t = inf->t;
578 }
579 else {
580 localplayer.have_glider = 0;
581 localplayer.glider_orphan = 0;
582 player_glide.t = 0.0f;
583 }
584
585 memcpy( &localplayer.rb, &gs->rb, sizeof(rigidbody) );
586 memcpy( &player_glide.rb, &gs->glider_rb, sizeof(rigidbody) );
587 v3_copy( gs->angles, localplayer.angles );
588
589 v3_copy( frame->cam.pos, localplayer.cam.pos );
590 v3_copy( frame->cam.angles, localplayer.cam.angles );
591 localplayer.cam.fov = frame->cam.fov;
592
593 memcpy( &localplayer.cam_control, &gs->cam_control,
594 sizeof(struct player_cam_controller) );
595
596 /* chop end off replay */
597 frame->r = NULL;
598 player_replay.local.statehead = frame;
599 player_replay.local.head = frame;
600 player_replay.local.cursor_frame = frame;
601 player_replay.local.cursor = frame->time;
602 player_replay.replay_control = k_replay_control_scrub;
603 skaterift.activity = k_skaterift_default;
604 vg.time = frame->time;
605 }
606
607 static void skaterift_replay_resume(void){
608 replay_frame *prev = replay_find_recent_stateframe(&player_replay.local);
609
610 if( prev ){
611 player_replay.replay_control = k_replay_control_resume;
612 player_replay.resume_target = prev;
613 player_replay.resume_begin = player_replay.local.cursor;
614 player_replay.resume_transition = 0.0f;
615 }
616
617 gui_helper_clear();
618 }
619
620 static void skaterift_replay_update_helpers(void);
621
622 void skaterift_replay_pre_update(void)
623 {
624 if( skaterift.activity != k_skaterift_replay ) return;
625
626 bool input = player_replay.editor_mode^0x1;
627
628 if( player_replay.replay_control == k_replay_control_resume )
629 {
630 if( player_replay.local.cursor_frame == player_replay.resume_target ||
631 player_replay.local.cursor_frame == NULL )
632 {
633 skaterift_restore_frame( player_replay.resume_target );
634 }
635 else
636 {
637 vg_slewf( &player_replay.resume_transition, 1.0f,
638 vg.time_frame_delta * (1.0f/1.0f) );
639
640 if( player_replay.resume_transition >= 1.0f )
641 skaterift_restore_frame( player_replay.resume_target );
642 else {
643 f64 target = vg_lerp( player_replay.resume_begin,
644 player_replay.resume_target->time,
645 vg_smoothstepf( player_replay.resume_transition ) );
646 if( replay_seek( &player_replay.local, target ) )
647 player_replay.track_velocity = 1.0f;
648 else
649 player_replay.track_velocity = 0.0f;
650 }
651 }
652 }
653 else
654 {
655 if( input && button_down( k_srbind_replay_play ) )
656 player_replay.replay_control = k_replay_control_play;
657 if( input && button_down( k_srbind_replay_freecam ) )
658 {
659 player_replay.use_freecam ^= 0x1;
660
661 if( player_replay.use_freecam )
662 {
663 replay_get_camera( &player_replay.local,
664 &player_replay.replay_freecam );
665 }
666 skaterift_replay_update_helpers();
667 }
668
669 f32 target_speed = 0.0f;
670 if( input )
671 target_speed = axis_state( k_sraxis_replay_h ) * 5.0;
672
673 if( input && button_press( k_srbind_reset ) )
674 target_speed += -2.0;
675
676 if( fabsf(target_speed) > 0.01f )
677 player_replay.replay_control = k_replay_control_scrub;
678
679 if( player_replay.replay_control == k_replay_control_play )
680 target_speed = 1.0;
681
682 vg_slewf( &player_replay.track_velocity, target_speed,
683 18.0f*vg.time_frame_delta );
684
685 if( fabsf( player_replay.track_velocity ) > 0.0001f )
686 {
687 f64 target = player_replay.local.cursor;
688 target += player_replay.track_velocity * vg.time_frame_delta;
689
690 if( !replay_seek( &player_replay.local, target ) )
691 player_replay.track_velocity = 0.0f;
692 }
693
694 if( input && button_down( k_srbind_mback ) )
695 {
696 if( player_replay.local.statehead )
697 skaterift_restore_frame( player_replay.local.statehead );
698 else
699 skaterift.activity = k_skaterift_default;
700 srinput.state = k_input_state_resume;
701 gui_helper_clear();
702 }
703
704 if( input )
705 {
706 if( player_replay.use_freecam )
707 {
708 freecam_preupdate();
709 }
710 else
711 {
712 if( button_down( k_srbind_replay_resume ) )
713 {
714 skaterift_replay_resume();
715 }
716 }
717 }
718 }
719 }
720
721 static void skaterift_replay_update_helpers(void)
722 {
723 player_replay.helper_resume->greyed = player_replay.use_freecam;
724
725 vg_str freecam_text;
726 vg_strnull( &freecam_text, player_replay.helper_freecam->text,
727 GUI_HELPER_TEXT_LENGTH );
728 vg_strcat( &freecam_text,
729 player_replay.use_freecam? "Exit freecam": "Freecam" );
730 }
731
732 static void replay_show_helpers(void)
733 {
734 gui_helper_clear();
735 vg_str text;
736
737 if( gui_new_helper( input_axis_list[k_sraxis_replay_h], &text ) )
738 vg_strcat( &text, "Scrub" );
739
740 if( (player_replay.helper_resume = gui_new_helper(
741 input_button_list[k_srbind_replay_resume], &text )) )
742 vg_strcat( &text, "Resume" );
743
744 if( gui_new_helper( input_button_list[k_srbind_replay_play], &text ))
745 vg_strcat( &text, "Playback" );
746
747 player_replay.helper_freecam = gui_new_helper(
748 input_button_list[k_srbind_replay_freecam], &text );
749
750 skaterift_replay_update_helpers();
751 }
752
753 void skaterift_replay_post_render(void)
754 {
755 #ifndef SR_ALLOW_REWIND_HUB
756 if( world_static.active_instance != k_world_purpose_client )
757 return;
758 #endif
759
760 /* capture the current resume frame at the very last point */
761 if( button_down( k_srbind_reset ) )
762 {
763 if( skaterift.activity == k_skaterift_default )
764 {
765 localplayer.rewinded_since_last_gate = 1;
766 skaterift.activity = k_skaterift_replay;
767 skaterift_record_frame( &player_replay.local, 1 );
768 if( player_replay.local.head )
769 {
770 player_replay.local.cursor = player_replay.local.head->time;
771 player_replay.local.cursor_frame = player_replay.local.head;
772 }
773 player_replay.replay_control = k_replay_control_scrub;
774 replay_show_helpers();
775 }
776 }
777 }
778
779 void skaterift_replay_init(void)
780 {
781 u32 bytes = 1024*1024*10;
782 player_replay.local.data = vg_linear_alloc( vg_mem.rtmemory, bytes );
783 player_replay.local.size = bytes;
784 replay_clear( &player_replay.local );
785 }
786
787 void skaterift_replay_debug_info( ui_context *ctx )
788 {
789 player__debugtext( ctx, 2, "replay info" );
790 replay_buffer *replay = &player_replay.local;
791
792 u32 head = 0,
793 tail = 0;
794 if( replay->tail ) tail = (void *)replay->tail - replay->data;
795 if( replay->head ) head = (void *)replay->head - replay->data;
796
797 player__debugtext( ctx, 1, "head @%u | tail @%u\n", head, tail );
798
799 if( replay->statehead )
800 {
801 for( u32 i=0; i<k_replay_framedata_rows; i++ )
802 {
803 player__debugtext( ctx, 1, "[%u]: [%hu, %hu]\n", i,
804 replay->statehead->data_table[i][0],
805 replay->statehead->data_table[i][1] );
806 }
807 u32 state = (void *)replay->statehead - replay->data;
808 player__debugtext( ctx, 1, "gs @%u\n", state );
809 player__debugtext( ctx, 1, "gamestate_size: %hu\n",
810 replay->statehead->data_table[k_replay_framedata_gamestate][1] );
811 }
812 else
813 player__debugtext( ctx, 1, "gs @NULL\n" );
814
815 f64 start = replay->cursor,
816 end = replay->cursor;
817 if( replay->tail ) start = replay->tail->time;
818 if( replay->head ) end = replay->head->time;
819
820 f64 cur = replay->cursor - start,
821 len = end - start;
822
823 player__debugtext( ctx, 1, "cursor: %.2fs / %.2fs\n", cur, len );
824 }
825
826 static int _keyframe_cmp( const void *p1, const void *p2 )
827 {
828 const replay_keyframe *kf1 = p1, *kf2 = p2;
829 return kf1->time > kf2->time;
830 }
831
832 static void replay_keyframe_sort(void)
833 {
834 qsort( player_replay.keyframes, player_replay.keyframe_count,
835 sizeof(replay_keyframe), _keyframe_cmp );
836 }
837
838 static void replay_fly_edit_keyframe( ui_context *ctx, replay_keyframe *kf )
839 {
840 if( ui_click_down( ctx, UI_MOUSE_LEFT ) )
841 {
842 /* init freecam */
843 v3_copy( kf->cam.pos, player_replay.replay_freecam.pos );
844 v3_copy( kf->cam.angles, player_replay.replay_freecam.angles );
845 v3_zero( player_replay.freecam_v );
846 v3_zero( player_replay.freecam_w );
847 player_replay.replay_freecam.fov = kf->cam.fov;
848 }
849
850 /* move freecam */
851 ui_capture_mouse( ctx, 0 );
852 freecam_preupdate();
853
854 if( vg_getkey(SDLK_q) )
855 player_replay.freecam_v[1] -= vg.time_frame_delta*6.0f*20.0f;
856 if( vg_getkey(SDLK_e) )
857 player_replay.freecam_v[1] += vg.time_frame_delta*6.0f*20.0f;
858
859 v3_copy( player_replay.replay_freecam.pos, g_render.cam.pos );
860 v3_copy( player_replay.replay_freecam.angles, g_render.cam.angles);
861 g_render.cam.fov = player_replay.replay_freecam.fov;
862
863 v3_copy( g_render.cam.pos, kf->cam.pos );
864 v3_copy( g_render.cam.angles, kf->cam.angles );
865 kf->cam.fov = g_render.cam.fov;
866 }
867
868 void skaterift_replay_imgui( ui_context *ctx )
869 {
870 if( skaterift.activity != k_skaterift_replay ) return;
871
872 /* extra keys for entering editor */
873 static u8 f1_key = 0;
874 u8 f1_now = vg_getkey(SDLK_F1);
875 if( f1_now && !f1_key && player_replay.show_ui )
876 {
877 player_replay.editor_mode ^= 0x1;
878
879 if( player_replay.editor_mode )
880 gui_helper_clear();
881 else
882 replay_show_helpers();
883 }
884 f1_key = f1_now;
885
886 static u8 f2_key = 0;
887 u8 f2_now = vg_getkey(SDLK_F2);
888 if( f2_now && !f2_key )
889 {
890 player_replay.show_ui ^= 0x1;
891 }
892 f2_key = f2_now;
893
894 if( player_replay.editor_mode )
895 {
896 static u8 space_key = 0;
897 u8 space_now = vg_getkey(SDLK_SPACE);
898 if( space_now & !space_key )
899 {
900 player_replay.replay_control ^= k_replay_control_play;
901 }
902 space_key = space_now;
903 }
904
905 if( !player_replay.show_ui ) return;
906
907 if( player_replay.editor_mode )
908 {
909 u32 colour = ui_opacity( ui_colour(ctx,k_ui_fg), 0.3333f );
910 ui_rect cx = { vg.window_x/2, 0, 1, vg.window_y },
911 cy = { 0, vg.window_y/2, vg.window_x, 1 };
912 ui_fill( ctx, cx, colour );
913 ui_fill( ctx, cy, colour );
914 }
915
916 replay_buffer *replay = &player_replay.local;
917 f64 start = replay->cursor,
918 end = replay->cursor;
919 if( replay->tail ) start = replay->tail->time;
920 if( replay->head ) end = replay->head->time;
921 f64 len = end - start,
922 cur = (replay->cursor - start) / len;
923
924 char buffer[ 128 ];
925
926 /* mainbar */
927 ui_px height = 32,
928 cwidth = 2;
929 ui_rect timeline = { 0, 0, vg.window_x, height };
930 ui_fill( ctx, timeline, ui_colour( ctx, k_ui_bg ) );
931
932 /* cursor frame block */
933 if( replay->cursor_frame )
934 {
935 if( replay->cursor_frame->r )
936 {
937 f64 l = (replay->cursor_frame->r->time-replay->cursor_frame->time)/len,
938 s = (replay->cursor_frame->time - start) / len;
939 ui_rect box = { s*(f64)vg.window_x, 0,
940 VG_MAX(4,(ui_px)(l*vg.window_x)), timeline[3]+2 };
941 ui_fill( ctx, box, ui_colour( ctx, k_ui_bg+4 ) );
942 }
943 }
944
945 /* cursor */
946 ui_rect cusor = { cur * (f64)vg.window_x - (cwidth/2), 0,
947 cwidth, (player_replay.editor_mode? 0: 16) + timeline[3] };
948 ui_fill( ctx, cusor, ui_colour( ctx, k_ui_bg+7 ) );
949
950 /* latest state marker */
951 if( replay->statehead )
952 {
953 f64 t = (replay->statehead->time - start) / len;
954 ui_rect tag = { t*(f64)vg.window_x, 0, 2, timeline[3]+8 };
955 ui_fill( ctx, tag, ui_colour( ctx, k_ui_green+k_ui_brighter ) );
956 }
957
958 /* previous state marker */
959 replay_frame *prev = replay_find_recent_stateframe( replay );
960 if( prev )
961 {
962 f64 t = (prev->time - start) / len;
963 ui_rect tag = { t*(f64)vg.window_x, 0, 2, timeline[3]+8 };
964 ui_fill( ctx, tag, ui_colour( ctx, k_ui_yellow+k_ui_brighter ) );
965 }
966
967 snprintf( buffer, 128, "-%.2fs (F1: Edit replay)", (end-replay->cursor) );
968 ui_text( ctx, timeline, buffer, 1, k_ui_align_middle_left, 0 );
969 ui_text( ctx, timeline, "0s", 1, k_ui_align_middle_right, 0 );
970
971 if( !player_replay.editor_mode ) return;
972 ui_capture_mouse( ctx, 1 );
973
974 ui_rect panel = { 0, timeline[3] + 20, 200, 400 };
975 ui_fill( ctx, panel, ui_opacity( ui_colour( ctx, k_ui_bg ), 0.5f ) );
976 ui_rect_pad( panel, (ui_px[2]){4,4} );
977
978 if( ui_button( ctx, panel,
979 (player_replay.replay_control == k_replay_control_play)?
980 "Pause (space)": "Play (space)" ) == k_ui_button_click )
981 {
982 player_replay.replay_control ^= k_replay_control_play;
983 }
984
985 /* script bar */
986 ui_rect script = { 0, height + 2, vg.window_x, 16 };
987 ui_fill( ctx, script, ui_colour( ctx, k_ui_bg ) );
988 f64 mouse_t = start + ((f64)ctx->mouse[0] / (f64)vg.window_x)*len;
989
990 /* keyframe draw and select */
991 bool absorb_by_keyframe = 0;
992 ui_px lx = 0;
993 for( u32 i=0; i<player_replay.keyframe_count; i ++ )
994 {
995 replay_keyframe *kf = &player_replay.keyframes[i];
996 f64 t = (kf->time-start)/len;
997
998 ui_px x = t*(f64)vg.window_x-8;
999
1000 /* draw connections between keyframes */
1001 if( i )
1002 {
1003 ui_rect con = { lx, script[1]+7, x-lx, 1 };
1004 ui_fill( ctx, con, ui_colour( ctx, k_ui_blue ) );
1005 vg_line( kf->cam.pos, player_replay.keyframes[i-1].cam.pos, VG__BLUE );
1006 }
1007
1008 /* keyframe selection */
1009 ui_rect tag = { x, script[1], 16, 16 };
1010
1011 if( ui_inside_rect( tag, ctx->mouse ) )
1012 {
1013 absorb_by_keyframe = 1;
1014
1015 if( ui_click_down( ctx, UI_MOUSE_LEFT ) )
1016 {
1017 if( player_replay.active_keyframe != i )
1018 {
1019 player_replay.active_keyframe = i;
1020 replay_seek( &player_replay.local, kf->time );
1021 }
1022 }
1023 else
1024 {
1025 ui_outline( ctx, tag, 1, ui_colour(ctx, k_ui_fg), 0 );
1026 }
1027 }
1028
1029 /* edit controls */
1030 u32 drag_colour = ui_opacity( ui_colour(ctx, k_ui_bg+2), 0.5f );
1031 if( i == player_replay.active_keyframe )
1032 {
1033 ui_outline( ctx, tag, 2, ui_colour(ctx, k_ui_fg), 0 );
1034
1035 ui_rect tray = { tag[0]+8-32, tag[1]+16+2, 64, 16 };
1036 ui_rect dragbar = { tray[0]+16, tray[1], 32, 16 };
1037
1038 bool pos_correct = 0;
1039
1040 if( ui_inside_rect( dragbar, ctx->mouse_click ) )
1041 {
1042 if( ui_clicking( ctx, UI_MOUSE_LEFT ) )
1043 {
1044 drag_colour = ui_opacity( ui_colour(ctx,k_ui_fg), 0.5f );
1045 pos_correct = 1;
1046 replay_seek( &player_replay.local, mouse_t );
1047 }
1048 else if( ui_click_up( ctx, UI_MOUSE_LEFT ) )
1049 {
1050 pos_correct = 1;
1051 kf->time = mouse_t;
1052 replay_keyframe_sort();
1053
1054 for( u32 j=0; j<player_replay.keyframe_count; j ++ )
1055 {
1056 if( player_replay.keyframes[j].time == mouse_t )
1057 {
1058 player_replay.active_keyframe = j;
1059 break;
1060 }
1061 }
1062 }
1063
1064 if( pos_correct )
1065 {
1066 tag[0] = ctx->mouse[0]-8;
1067 tray[0] = tag[0]+8-32;
1068 dragbar[0] = tray[0]+16;
1069 }
1070 }
1071
1072 if( ui_inside_rect( dragbar, ctx->mouse ) )
1073 {
1074 ctx->cursor = k_ui_cursor_hand;
1075 }
1076
1077 if( !pos_correct )
1078 {
1079 ui_fill( ctx, tray,
1080 ui_opacity( ui_colour( ctx, k_ui_bg+2 ), 0.5f ) );
1081 }
1082
1083 ui_fill( ctx, dragbar, drag_colour );
1084 ui_text( ctx, dragbar, ":::", 1, k_ui_align_middle_center, 0 );
1085
1086 if( !pos_correct )
1087 {
1088 ui_rect btn = { tray[0], tray[1], 16, 16 };
1089 if( ui_button_text( ctx, btn, "X", 1 ) == k_ui_button_click )
1090 {
1091 for( u32 j=i; j<player_replay.keyframe_count-1; j ++ )
1092 player_replay.keyframes[j] = player_replay.keyframes[j+1];
1093
1094 player_replay.keyframe_count --;
1095 player_replay.active_keyframe = -1;
1096 }
1097
1098 ui_rect btn1 = { tray[0]+48, tray[1], 16, 16 };
1099
1100 enum ui_button_state mask_using =
1101 k_ui_button_holding_inside |
1102 k_ui_button_holding_outside |
1103 k_ui_button_click;
1104
1105 if( ui_button_text( ctx, btn1, "E", 1 ) & mask_using )
1106 {
1107 replay_fly_edit_keyframe( ctx, kf );
1108 vg_ui_set_mouse_pos( btn1[0]+8, btn1[1]+8 );
1109 }
1110 }
1111 }
1112
1113 ui_fill( ctx, tag, ui_colour( ctx, k_ui_blue ) );
1114 lx = x;
1115 }
1116
1117 /* adding keyframes */
1118 if( ui_inside_rect( script, ctx->mouse ) )
1119 {
1120 ctx->cursor = k_ui_cursor_hand;
1121
1122 ui_rect cursor = { ctx->mouse[0], script[1], 4, 16 };
1123 ui_fill( ctx, cursor, ui_colour( ctx, k_ui_fg ) );
1124
1125 if( !absorb_by_keyframe && ui_click_down( ctx, UI_MOUSE_LEFT ) )
1126 {
1127 u32 max = vg_list_size( player_replay.keyframes );
1128 if( player_replay.keyframe_count == max )
1129 {
1130 ui_start_modal( ctx, "Maximum keyframes reached", UI_MODAL_BAD );
1131 }
1132 else
1133 {
1134 replay_keyframe *kf =
1135 &player_replay.keyframes[player_replay.keyframe_count++];
1136
1137 kf->time = mouse_t;
1138 v3_copy( g_render.cam.pos, kf->cam.pos );
1139 v3_copy( g_render.cam.angles, kf->cam.angles );
1140 kf->cam.fov = g_render.cam.fov;
1141
1142 replay_keyframe_sort();
1143 }
1144 }
1145 }
1146
1147 /* timeline scrub */
1148 bool start_in_timeline =
1149 ui_clicking(ctx, UI_MOUSE_LEFT) &&
1150 ui_inside_rect(timeline, ctx->mouse_click);
1151 if( (ui_inside_rect( timeline, ctx->mouse )) || start_in_timeline )
1152 {
1153 ui_rect cursor = { ctx->mouse[0], timeline[1], 4, timeline[3] };
1154 ui_fill( ctx, cursor, ui_colour( ctx, k_ui_fg ) );
1155 ctx->cursor = k_ui_cursor_ibeam;
1156
1157 if( ui_clicking( ctx, UI_MOUSE_LEFT ) && start_in_timeline )
1158 {
1159 replay_seek( &player_replay.local, mouse_t );
1160 player_replay.active_keyframe = -1;
1161 }
1162 }
1163
1164 if( ui_button( ctx, panel, "Clear keyframes" ) == k_ui_button_click )
1165 {
1166 player_replay.keyframe_count = 0;
1167 }
1168
1169 if( (ui_button( ctx, panel, "Hide UI (F2)" ) == k_ui_button_click) )
1170 {
1171 player_replay.show_ui ^= 0x1;
1172 }
1173
1174 if( player_replay.active_keyframe != -1 )
1175 {
1176 replay_keyframe *kf =
1177 &player_replay.keyframes[ player_replay.active_keyframe ];
1178
1179 enum ui_button_state mask_using =
1180 k_ui_button_holding_inside |
1181 k_ui_button_holding_outside |
1182 k_ui_button_click;
1183
1184 if( ui_button( ctx, panel, "Edit cam" ) & mask_using )
1185 {
1186 replay_fly_edit_keyframe( ctx, kf );
1187 }
1188 }
1189
1190 ui_info( ctx, panel, "World settings" );
1191 f32 new_time = world_current_instance()->time;
1192 if( ui_slider( ctx, panel, "Time of day", 0, 1, &new_time ) )
1193 {
1194 world_current_instance()->time = new_time;
1195 }
1196
1197 ui_info( ctx, panel, "" );
1198 if( ui_button( ctx, panel, "Exit editor (F1)" ) == k_ui_button_click )
1199 {
1200 player_replay.editor_mode = 0;
1201 replay_show_helpers();
1202 }
1203
1204 /* TODO: Add Q/E scrub here too.
1205 * Add replay trimming
1206 */
1207 }