setting up revised replay system
authorhgn <hgodden00@gmail.com>
Tue, 8 Apr 2025 14:38:10 +0000 (15:38 +0100)
committerhgn <hgodden00@gmail.com>
Tue, 8 Apr 2025 14:38:10 +0000 (15:38 +0100)
24 files changed:
src/ent_route.c
src/ent_route.h
src/gameserver.c
src/gameserver_database.c
src/gameserver_database.h
src/gameserver_replay.c
src/gameserver_replay.h
src/gameserver_requests.c
src/gameserver_requests.h
src/network.c
src/network_msg.h
src/network_requests.c
src/player.c
src/player_remote.c
src/player_replay.c
src/player_replay.h
src/replay2.c [new file with mode: 0644]
src/replay2.h [new file with mode: 0644]
src/serialized_replay.h [new file with mode: 0644]
src/skaterift.c
src/world_render.c
src/world_routes.h
src/world_sfd.c
src/world_sfd.h

index 8412d61ff4b28dc41765d5e30cef8fa4815600e6..ad19724ed1a0e9a0b11270040bc757b0f57f9586 100644 (file)
@@ -1,8 +1,9 @@
 #include "ent_route.h"
 #include "input.h"
 #include "gui.h"
+#include "network_requests.h"
 
-struct global_ent_route _ent_route;
+struct _ent_route _ent_route;
 
 entity_call_result ent_route_call( world_instance *world, ent_call *call )
 {
@@ -16,16 +17,17 @@ entity_call_result ent_route_call( world_instance *world, ent_call *call )
          gui_helper_reset( k_gui_helper_mode_clear );
          vg_str text;
 
+#if 0
          if( (_ent_route.helper_weekly = gui_new_helper( input_button_list[k_srbind_mleft], &text )))
             vg_strcat( &text, "Weekly" );
-
          if( (_ent_route.helper_alltime = gui_new_helper( input_button_list[k_srbind_mright], &text )))
             vg_strcat( &text, "All time" );
+#endif
 
          if( gui_new_helper( input_button_list[k_srbind_mback], &text ) )
             vg_strcat( &text, "Exit" );
 
-         _ent_route.looking_at_board = route;
+         _ent_route.viewing_route_id = call->id;
          localplayer.immobile = 1;
       }
 
@@ -41,7 +43,7 @@ void ent_route_preupdate(void)
       return;
 
    world_instance *world = &_world.main;
-   ent_route *route = _ent_route.looking_at_board;
+   ent_route *route = af_arritm( &world->ent_route, mdl_entity_id_id( _ent_route.viewing_route_id ) );
 
    u32 cam_id = 0;
    if( __builtin_expect( world->meta.version >= 103, 1 ) )
@@ -54,7 +56,104 @@ void ent_route_preupdate(void)
       world_set_entity_driven_camera( &temp );
    }
 
-   if( button_down( k_srbind_mleft ) )
+   bool select_move = 0;
+
+   /* TODO: Visual buttons like the board shops */
+   if( button_down( k_srbind_mup ) )
+   {
+      if( _ent_route.run_selection > 0 )
+      {
+         _ent_route.run_selection --;
+         select_move = 1;
+      }
+   }
+   if( button_down( k_srbind_mdown ) )
+   {
+      if( _ent_route.run_selection < 10 )
+      {
+         _ent_route.run_selection ++;
+         select_move = 1;
+      }
+   }
+
+   if( select_move )
+   {
+      sfd_encode( (v2i){0,_ent_route.run_selection    }, "  ", k_world_sfd_left );
+      sfd_encode( (v2i){0,_ent_route.run_selection + 1}, "AA", k_world_sfd_left );
+      sfd_encode( (v2i){0,_ent_route.run_selection + 2}, "  ", k_world_sfd_left );
+   }
+
+   if( button_down( k_srbind_maccept ) )
+   {
+      if( _remote_replay.state != k_remote_replay_state_none )
+         goto E0;
+
+      struct leaderboard_cache *board = &world->leaderboard_cache[ mdl_entity_id_id( _ent_route.viewing_route_id ) ];
+      vg_msg body;
+      vg_msg_init( &body, board->data, board->data_len );
+
+      vg_info( "Looking for the %u run\n", _ent_route.run_selection );
+
+      u32 l = 0;
+
+      if( vg_msg_seekframe( &body, "rows" ) )
+      {
+         while( vg_msg_seekframe( &body, NULL ) )
+         {
+            const char *username = vg_msg_getkvstr( &body, "username" );
+            vg_info( "%u: %s\n", l, username );
+
+            if( l == _ent_route.run_selection )
+            {
+               u32 centiseconds;
+               vg_msg_getkvintg( &body, "time", k_vg_msg_u32, &centiseconds, NULL );
+
+               u32 lastmin;
+               vg_msg_getkvintg( &body, "lastmin", k_vg_msg_u32, &lastmin, NULL );
+
+               u64 steamid; 
+               vg_msg_getkvintg( &body, "steamid", k_vg_msg_u64, &steamid, NULL );
+
+               // TODO: put this in a header
+               u32 minutes_span = (centiseconds+(200*60)) / (100*60);
+
+               vg_queue_clear( &_remote_replay.replay.buffer );
+               _remote_replay.min_frame_t = 0.0;
+               _remote_replay.total_chunks = minutes_span;
+               _remote_replay.chunks_downloaded = 0;
+               _remote_replay.steamid = steamid;
+               _remote_replay.state = k_remote_replay_state_downloading;
+               
+               for( u32 i=0; i<minutes_span; i ++ )
+               {
+                  struct remote_replay_chunk *chunk = &_remote_replay.chunks[i];
+                  chunk->minute = lastmin - minutes_span + 1 + i;
+                  chunk->state = k_chunk_state_none;
+
+                  // TODO: cache wrapper
+                  //         
+                  //       reset_replay_cache( span, callback_when_complete, callback_when_fail )
+                  //       get_cache_remote_replay( ... ) x3
+                  //
+                  //network_download_replay( steamid, want_minute );
+               }
+
+               // TODO: UI overlay for downlaoding
+               sfd_encode( (v2i){0,6}, "Downloading ...", k_world_sfd_center );
+
+               break;
+            }
+            else
+            {
+               l ++;
+               vg_msg_skip_frame( &body );
+            }
+         }
+      }
+   }
+
+#if 0
+if( button_down( k_srbind_mleft ) )
    {
       world_sfd.view_weekly = 1;
       world_sfd_compile_active_scores();
@@ -65,9 +164,11 @@ void ent_route_preupdate(void)
       world_sfd.view_weekly = 0;
       world_sfd_compile_active_scores();
    }
-
    _ent_route.helper_alltime->greyed = !world_sfd.view_weekly;
    _ent_route.helper_weekly->greyed = world_sfd.view_weekly;
+#endif
+
+E0:
 
    if( button_down( k_srbind_mback ) )
    {
index b687c3ffe34174e78e36d28d5dba4b140f0458c1..7b5af1065ae38185b92080f99adc136538b5e94f 100644 (file)
@@ -1,12 +1,14 @@
 #pragma once
 #include "entity.h"
 
-struct global_ent_route
+struct _ent_route
 {
-   struct gui_helper *helper_weekly, *helper_alltime;
-   ent_route *looking_at_board;
+   //struct gui_helper *helper_weekly, *helper_alltime;
+
+   u32 viewing_route_id;
+   u32 run_selection;
 }
-extern global_ent_route;
+extern _ent_route;
 
 entity_call_result ent_route_call( world_instance *world, ent_call *call );
 void ent_route_preupdate(void);
index 008c8fcc9fba96790b4772cb8c1c8f1068f222d4..307e2b651df0ce7c2deb91b3ce5eda4fbc5ecd3d 100644 (file)
@@ -3,6 +3,7 @@
  */
 
 #define _DEFAULT_SOURCE
+#define VG_SERVER
 #include <signal.h>
 #include <unistd.h>
 #include <time.h>
@@ -183,6 +184,7 @@ static void remove_client( int index )
    memset( client, 0, sizeof(struct gameserver_client) );
    gameserver_update_all_knowledge( index, 1 );
    _gs_requests_client_disconnect( index );
+   _gs_replay_client_disconnect( index );
 }
 
 /*
@@ -614,13 +616,17 @@ static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg )
       /* WARNING FIXME WARNING FIXME ------------------------------------------- */
       if( !strcmp( prop->msg, "save" ) )
       {
-         _gs_write_replay_to_disk( client_id, 1.0*60.0, "/tmp/server-replay.replay" );
+         //_gs_write_replay_to_disk( client_id, 1.0*60.0, "/tmp/server-replay.replay" );
       }
       else if( !strcmp( prop->msg, "transfer" ) )
       {
          //_gs_start_transfer( client_id, "/tmp/server-replay.replay" );
          //_gs_test_replay( client_id, "/tmp/server-replay.replay" );
       }
+      else if( !strcmp( prop->msg, "$savemin" ) )
+      {
+         _gs_replay_request_save( client_id, client->steamid, _gs_replay.current_minute, 2, 0 );
+      }
 
       gameserver_send_to_all( client_id, prop, sizeof(netmsg_chat)+l+1, k_nSteamNetworkingSend_Reliable );
    }
@@ -654,191 +660,6 @@ u32 gameserver_get_current_week(void)
    return time(NULL) / (7*24*60*60);
 }
 
-
-#if 0
-static void gameserver_request_respond( enum request_status status, netmsg_request *res, vg_msg *body,
-                                        SteamNetworkingMessage_t *msg )
-{
-   int client_id = gameserver_conid( msg->m_conn );
-   u32 len = 0;
-   if( body )
-   {
-      len = body->cur.co;
-      vg_low( "[%d#%d] Response: %d\n", client_id, (i32)res->id, status );
-      vg_msg_print( body, len );
-   }
-
-   res->status = status;
-
-   SteamAPI_ISteamNetworkingSockets_SendMessageToConnection( hSteamNetworkingSockets, msg->m_conn,
-                                                             res, sizeof(netmsg_request) + len,
-                                                             k_nSteamNetworkingSend_Reliable, NULL );
-
-   SteamAPI_SteamNetworkingMessage_t_Release( msg );
-}
-
-struct user_request_thread_data {
-   SteamNetworkingMessage_t *msg;
-};
-
-static enum request_status gameserver_cat_table( 
-      vg_msg *msg, 
-      const char *mod, const char *route, u32 week, const char *alias )
-{
-   char table_name[ DB_TABLE_UID_MAX ];
-   if( !db_get_highscore_table_name( mod, route, week, table_name ) )
-      return k_request_status_out_of_memory;
-
-   char buf[512];
-   vg_str q;
-   vg_strnull( &q, buf, 512 );
-   vg_strcat( &q, "SELECT * FROM \"" );
-   vg_strcat( &q, table_name );
-   vg_strcat( &q, "\" ORDER BY time ASC LIMIT 10;" );
-   if( !vg_strgood(&q) )
-      return k_request_status_out_of_memory;
-
-   sqlite3_stmt *stmt = db_stmt( q.buffer );
-   if( !stmt )
-      return k_request_status_database_error;
-
-   vg_msg_frame( msg, alias );
-   for( u32 i=0; i<10; i ++ ){
-      int fc = sqlite3_step( stmt );
-
-      if( fc == SQLITE_ROW ){
-         i32 time = sqlite3_column_int( stmt, 1 );
-         i64 steamid_i64 = sqlite3_column_int64( stmt, 0 );
-         u64 steamid = *((u64 *)&steamid_i64);
-
-         if( steamid == k_steamid_max )
-            continue;
-
-         vg_msg_frame( msg, "" );
-         vg_msg_wkvnum( msg, "time", k_vg_msg_u32, 1, &time );
-         vg_msg_wkvnum( msg, "steamid", k_vg_msg_u64, 1, &steamid );
-
-         char username[32];
-         if( db_getuserinfo( steamid, username, sizeof(username), NULL ) )
-            vg_msg_wkvstr( msg, "username", username );
-         vg_msg_end_frame( msg );
-      }
-      else if( fc == SQLITE_DONE ){
-         break;
-      }
-      else {
-         log_sqlite3( fc );
-         break;
-      }
-   }
-
-   sqlite3_finalize( stmt );
-   vg_msg_end_frame( msg );
-   return k_request_status_ok;
-}
-
-static void gameserver_process_user_request( db_request *db_req )
-{
-   struct user_request_thread_data *inf = (void *)db_req->data;
-   SteamNetworkingMessage_t *msg = inf->msg;
-
-   int client_id = gameserver_conid( msg->m_conn );
-   if( client_id == -1 )
-   {
-      SteamAPI_SteamNetworkingMessage_t_Release( msg );
-      return;
-   }
-
-   struct gameserver_client *client = &_gameserver.clients[ client_id ];
-
-   netmsg_request *req = (netmsg_request *)msg->m_pData;
-   vg_msg data;
-   vg_msg_init( &data, req->q, msg->m_cbSize - sizeof(netmsg_request) );
-
-   /* create response packet */
-   netmsg_request *res = alloca( sizeof(netmsg_request) + NETWORK_REQUEST_MAX );
-   res->inetmsg_id = k_inetmsg_response;
-   res->id = req->id;
-   vg_msg body;
-   vg_msg_init( &body, res->q, NETWORK_REQUEST_MAX );
-
-   const char *endpoint = vg_msg_getkvstr( &data, "endpoint" );
-
-   if( !endpoint )
-   {
-      gameserver_request_respond( k_request_status_invalid_endpoint, res, NULL, msg );
-      return;
-   }
-
-   if( !strcmp( endpoint, "scoreboard" ) )
-   {
-      const char *mod = vg_msg_getkvstr( &data, "mod" );
-      const char *route = vg_msg_getkvstr( &data, "route" );
-      u32 week;
-      vg_msg_getkvintg( &data, "week", k_vg_msg_u32, &week, NULL );
-      
-      if( week == NETWORK_LEADERBOARD_CURRENT_WEEK )
-      {
-         gameserver_cat_table( &body, mod, route, gameserver_get_current_week(), "rows_weekly" );
-      }
-      else if( week == NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK )
-      {
-         gameserver_cat_table( &body, mod, route, 0, "rows" );
-         gameserver_cat_table( &body, mod, route, gameserver_get_current_week(), "rows_weekly" );
-      }
-      else 
-         gameserver_cat_table( &body, mod, route, week, "rows" );
-
-      if( body.error != k_vg_msg_error_OK )
-      {
-         gameserver_request_respond( k_request_status_out_of_memory, res, NULL, msg );
-         return;
-      }
-
-      gameserver_request_respond( k_request_status_ok, res, &body, msg );
-   }
-   else if( !strcmp( endpoint, "setlap" ) )
-   {
-      if( client->steamid == k_steamid_max )
-      {
-         gameserver_request_respond( k_request_status_unauthorized, res, NULL, msg );
-         return;
-      }
-
-      const char *mod = vg_msg_getkvstr( &data, "mod" );
-      const char *route = vg_msg_getkvstr( &data, "route" );
-      
-      char weekly_table[ DB_TABLE_UID_MAX ],
-           alltime_table[ DB_TABLE_UID_MAX ];
-
-      u32 week = gameserver_get_current_week();
-
-      if( !db_get_highscore_table_name( mod, route, 0, alltime_table ) ||
-          !db_get_highscore_table_name( mod, route, week, weekly_table ) )
-      {
-         gameserver_request_respond( k_request_status_out_of_memory, res, NULL, msg );
-         return;
-      }
-
-      i32 centiseconds;
-      vg_msg_getkvintg( &data, "time", k_vg_msg_i32, &centiseconds, NULL );
-      if( centiseconds < 5*100 )
-      {
-         gameserver_request_respond( k_request_status_client_error, res, NULL, msg );
-         return;
-      }
-
-      db_writeusertime( alltime_table, client->steamid, centiseconds, 1 );
-      db_writeusertime( weekly_table, client->steamid, centiseconds, 1 );
-      gameserver_request_respond( k_request_status_ok, res, NULL, msg );
-   }
-   else
-   {
-      gameserver_request_respond( k_request_status_invalid_endpoint, res, NULL, msg );
-   }
-}
-#endif
-
 static void gameserver_rx_300_400( SteamNetworkingMessage_t *msg )
 {
    netmsg_blank *tmp = msg->m_pData;
@@ -922,7 +743,6 @@ u64 seconds_to_server_ticks( f64 s )
 int main( int argc, char *argv[] )
 {
    _gameserver.thread = pthread_self();
-
    vg_log_init();
 
    signal( SIGINT, inthandler );
index f7503cc9355caf0a096662baebbae6e28aab854c..01acb5f416d961d2efcc510675a36c8420ea4acd 100644 (file)
@@ -34,7 +34,7 @@ sqlite3_stmt *db_stmt( const char *code )
 {
    THREAD_1;
 #ifdef DB_LOG_SQL_STATEMENTS
-   vg_low( code );
+   vg_low( "%s\n", code );
 #endif
 
    sqlite3_stmt *stmt;
@@ -196,7 +196,7 @@ i32 db_readusertime( char table[DB_TABLE_UID_MAX], u64 steamid )
       return 0;
 }
 
-bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, bool only_if_faster )
+bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, bool only_if_faster, i32 last_minute )
 {
    THREAD_1;
 
@@ -207,17 +207,12 @@ bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, boo
    vg_strnull( &q, buf, 512 );
    vg_strcat( &q, "CREATE TABLE IF NOT EXISTS \n \"" );
    vg_strcat( &q, table );
-   vg_strcat( &q, "\"\n (steamid BIGINT UNIQUE, time INT);" );
+   vg_strcat( &q, "\"\n (steamid BIGINT UNIQUE, time INT, lastmin INT);" );
    if( !vg_strgood(&q) ) 
       return 0;
-
-   vg_str str;
    sqlite3_stmt *create_table = db_stmt( q.buffer );
-
    if( create_table )
    {
-      db_sqlite3_bind_sz( create_table, 1, table );
-
       int fc = sqlite3_step( create_table );
       sqlite3_finalize( create_table );
       if( fc != SQLITE_DONE )
@@ -226,6 +221,22 @@ bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, boo
    else 
       return 0;
 
+   /* auto smash append lastmin column */
+#if 0
+   vg_strnull( &q, buf, 512 );
+   vg_strcat( &q, "ALTER TABLE \"" );
+   vg_strcat( &q, table );
+   vg_strcat( &q, "\" ADD COLUMN lastmin INT default 0;" );
+   if( !vg_strgood(&q) ) 
+      return 0;
+   sqlite3_stmt *s2 = db_stmt( q.buffer );
+   if( s2 )
+   {
+      sqlite3_step( s2 );
+      sqlite3_finalize( s2 );
+   }
+#endif
+
    if( only_if_faster )
    {
       i32 current = db_readusertime( table, steamid );
@@ -238,7 +249,7 @@ bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, boo
    vg_strnull( &q, buf, 512 );
    vg_strcat( &q, "REPLACE INTO \"" );
    vg_strcat( &q, table );
-   vg_strcat( &q, "\"(steamid,time)\n VALUES (?,?);" );
+   vg_strcat( &q, "\"(steamid,time,lastmin)\n VALUES (?,?,?);" );
    if( !vg_strgood(&q) ) 
       return 0;
 
@@ -248,6 +259,7 @@ bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, boo
    {
       sqlite3_bind_int64( stmt, 1, *((i64 *)&steamid) );
       sqlite3_bind_int( stmt, 2, score );
+      sqlite3_bind_int( stmt, 3, last_minute );
 
       int fc = sqlite3_step( stmt );
       sqlite3_finalize( stmt );
@@ -363,13 +375,7 @@ void db_action_set_username( u64 steamid, const char *username )
    vg_async_task_dispatch( &_gs_db.tasks, task );
 }
 
-
-
-
-
-enum request_status gameserver_cat_table( 
-      vg_msg *msg, 
-      const char *mod, const char *route, u32 week, const char *alias )
+enum request_status gameserver_cat_table( vg_msg *msg, const char *mod, const char *route, u32 week, const char *alias )
 {
    char table_name[ DB_TABLE_UID_MAX ];
    if( !db_get_highscore_table_name( mod, route, week, table_name ) )
@@ -389,11 +395,14 @@ enum request_status gameserver_cat_table(
       return k_request_status_database_error;
 
    vg_msg_frame( msg, alias );
-   for( u32 i=0; i<10; i ++ ){
+   for( u32 i=0; i<10; i ++ )
+   {
       int fc = sqlite3_step( stmt );
 
-      if( fc == SQLITE_ROW ){
+      if( fc == SQLITE_ROW )
+      {
          i32 time = sqlite3_column_int( stmt, 1 );
+         i32 last_minute = sqlite3_column_int( stmt, 2 );
          i64 steamid_i64 = sqlite3_column_int64( stmt, 0 );
          u64 steamid = *((u64 *)&steamid_i64);
 
@@ -403,16 +412,19 @@ enum request_status gameserver_cat_table(
          vg_msg_frame( msg, "" );
          vg_msg_wkvnum( msg, "time", k_vg_msg_u32, 1, &time );
          vg_msg_wkvnum( msg, "steamid", k_vg_msg_u64, 1, &steamid );
+         vg_msg_wkvnum( msg, "lastmin", k_vg_msg_u32, 1, &last_minute );
 
          char username[32];
          if( db_getuserinfo( steamid, username, sizeof(username), NULL ) )
             vg_msg_wkvstr( msg, "username", username );
          vg_msg_end_frame( msg );
       }
-      else if( fc == SQLITE_DONE ){
+      else if( fc == SQLITE_DONE )
+      {
          break;
       }
-      else {
+      else 
+      {
          log_sqlite3( fc );
          break;
       }
@@ -422,5 +434,3 @@ enum request_status gameserver_cat_table(
    vg_msg_end_frame( msg );
    return k_request_status_ok;
 }
-
-
index 0b1a7036dcedc650e4c4d0f261873e8d6942aa4a..091c6e5cc0915f7dcaeacf5a18895a31354bda24 100644 (file)
@@ -30,6 +30,9 @@ struct _gs_db
 extern _gs_db;
 
 void db_action_set_username( u64 steamid, const char *username );
+bool db_get_highscore_table_name( const char *mod_uid, const char *run_uid, u32 week, char table_name[DB_TABLE_UID_MAX] );
+i32 db_readusertime( char table[DB_TABLE_UID_MAX], u64 steamid );
+bool db_writeusertime( char table[DB_TABLE_UID_MAX], u64 steamid, i32 score, bool only_if_faster, i32 last_minute );
 
 #if 0
 /*
index 2b1b2b86c85243cde0f94e7c18c0db23395f956e..222e16ab7b6bb294d32110d1aa06b6028efa7a54 100644 (file)
@@ -3,20 +3,16 @@
 
 struct _gs_replay _gs_replay;
 
-void _gs_replay_server_tick(void)
+u32 _gs_replay_get_minute(void)
 {
+   return time(NULL) / 60;
 }
 
-struct serialized_replay_header
-{
-   u32 total_frames,
-       head_offset;
-};
-
 struct serialized_replay
 {
-   char path[ 1024 ];
-   struct serialized_replay_header header;
+   u64 steamid;
+   u32 minute;
+
    u32 buffer_size;
    u8 buffer[];
 };
@@ -26,62 +22,197 @@ static void task_write_replay( vg_async_task *task )
    THREAD_1;
    struct serialized_replay *info = (void *)task->data;
 
-   FILE *fp = fopen( info->path, "w" );
+   vg_make_directory( "replaydata" );
+
+   char path[1024];
+   snprintf( path, sizeof(path), "replaydata/%lx@%x", info->steamid, info->minute );
+
+   FILE *fp = fopen( path, "w" );
    if( !fp )
    {
       vg_error( "Failed to open '%s' for writing\n", fp );
       return;
    }
 
-   fwrite( &info->header, sizeof(info->header), 1, fp );
    fwrite( &info->buffer, info->buffer_size, 1, fp );
    fclose( fp );
 
-   vg_success( "Saved replay: %s\n", info->path );
+   vg_success( "Saved replay: %s\n", path );
 }
 
-void _gs_write_replay_to_disk( u8 client_id, f64 server_duration, const char *path )
+void _gs_replay_run_save_requests( u32 client_id )
 {
-   u64 min_ticks = _gameserver.ticks - seconds_to_server_ticks( server_duration );
-
    gs_replay *replay = &_gs_replay.replays[ client_id ];
-   if( replay->ring_buffer.allocation_count == 0 )
+   for( u32 j=0; j<REPLAY_MINUTE_COUNT; j ++ )
    {
-      vg_error( "Replay frame count is 0, nothing to save..\n" );
-      return;
+      u32 minute = _gs_replay.current_minute - j,
+          minute_index = minute % REPLAY_MINUTE_COUNT;
+
+      u32 flags = replay->minute_flags[ minute_index ];
+      if( flags & k_replay_minute_save_request )
+      {
+         if( !(flags & k_replay_minute_saved) )
+         {
+            /* do the saving */
+            vg_info( "Saving replay minute %u#%u\n", client_id, minute );
+
+            if( !(flags & k_replay_minute_has_data) )
+               vg_warn( "This minute doesn't have any data!\n" );
+
+            if( flags & k_replay_minute_data_deleting )
+               vg_warn( "This minute is being recouped / deleted!\n" );
+
+            /* Rewind to first frame of minute */
+            u32 frame_id = replay->ring_buffer.head_offset;
+            u32 buffer_size = 0, total_frames = 0, start_id = 0xffffffff, end_id = 0xffffffff;
+            while(1)
+            {
+               gs_replay_frame *frame = vg_queue_data( &replay->ring_buffer, frame_id );
+
+               if( frame->minute == minute )
+               {
+                  buffer_size += vg_queue_item_size( &replay->ring_buffer, frame_id );
+                  total_frames ++;
+                  start_id = frame_id;
+
+                  if( end_id == 0xffffffff )
+                     end_id = frame_id;
+               }
+               else if( frame->minute < minute )
+                  break;
+
+               if( !vg_queue_previous( &replay->ring_buffer, frame_id, &frame_id ) )
+                  break;
+            }
+
+            if( (start_id == 0xffffffff) || (end_id == 0xffffffff) )
+            {
+               vg_error( "Rewinding to minute start failed\n" );
+               return;
+            }
+
+            u32 task_size = sizeof( struct serialized_replay ) + buffer_size;
+            vg_async_task *task = vg_allocate_async_task( &_gs_db.tasks, task_size, 1 );
+            task->handler = task_write_replay;
+
+            struct serialized_replay *replay_inf = (void *)task->data;
+            replay_inf->steamid = _gameserver.clients[ client_id ].steamid;
+            replay_inf->minute = minute;
+
+            u32 output_size = 0;
+            frame_id = start_id;
+            for( u32 i=0; i<total_frames; i ++ )
+            {
+               gs_replay_frame *frame = vg_queue_data( &replay->ring_buffer, frame_id );
+
+               serialized_netmsg *snm = ((void *)replay_inf->buffer) + output_size;
+               snm->msg_size = frame->frame_size;
+               snm->unused0 = 0;
+               snm->unused1 = 0;
+               memcpy( snm->msg_data, &frame->network_frame, frame->frame_size );
+               output_size += sizeof(serialized_netmsg) + frame->frame_size;
+
+               if( !vg_queue_next( &replay->ring_buffer, frame_id, &frame_id ) )
+                  break;
+            }
+
+            replay_inf->buffer_size = output_size;
+            vg_async_task_dispatch( &_gs_db.tasks, task );
+
+            flags |= (u8)k_replay_minute_saved;
+         }
+
+         flags &= ~(u8)k_replay_minute_save_request;
+      }
+      replay->minute_flags[ minute_index ] = flags;
    }
-   
-   /* FIXME: Should we do this on another thread, or write to a buffer then send that for a save thread */
+}
+
+void _gs_replay_client_disconnect( u32 client_id )
+{
+   _gs_replay_run_save_requests( client_id );
+
+   gs_replay *dest_replay = &_gs_replay.replays[ client_id ];
+   for( u32 i=0; i<REPLAY_MINUTE_COUNT; i ++ )
+      dest_replay->minute_flags[i] = 0x00;
 
-   u32 frame_id = replay->ring_buffer.head_offset;
-   u32 buffer_size = 0, total_frames = 0;
-   while(1)
+   vg_queue_clear( &dest_replay->ring_buffer );
+}
+
+struct request_save_info
+{
+   u32 client_id;
+   u64 steam_id;
+   u32 start_minute, length;
+};
+
+void _gs_replay_request_save( u32 client_id, u64 steamid, u32 start_minute, u32 length, bool async );
+static void _gs_replay_async_request_save( vg_async_task *task )
+{
+   struct request_save_info *info = (void *)task->data;
+   _gs_replay_request_save( info->client_id, info->steam_id, info->start_minute, info->length, 0 );
+}
+
+void _gs_replay_request_save( u32 client_id, u64 steamid, u32 start_minute, u32 length, bool async )
+{
+   if( async )
    {
-      gs_replay_frame *frame = vg_queue_data( &replay->ring_buffer, frame_id );
-      buffer_size += vg_queue_item_size( &replay->ring_buffer, frame_id );
-      total_frames ++;
+      THREAD_1;
+      /* C0: In the future, this is like just storing all the call parameters to a struct, and then recalling the 
+       *     function later. Really, we could auto-generate such patterns.
+       *
+       *     void( u32 id )
+       *     {
+       *        #c0_recall( thread0 );
+       *
+       *        .. from here on out we are thread0
+       *     }
+       */
+      vg_async_task *task = vg_allocate_async_task( &_gameserver.tasks, sizeof(struct request_save_info), 1 );
+      struct request_save_info *info = (void *)task->data;
+      info->client_id = client_id;
+      info->steam_id = steamid;
+      info->start_minute = start_minute;
+      info->length = length;
+      task->handler = _gs_replay_async_request_save;
+      vg_async_task_dispatch( &_gameserver.tasks, task );
+      return;
+   }
+
+   THREAD_0;
 
-      if( frame->tick_recorded < min_ticks )
-         break;
+   if( _gameserver.clients[ client_id ].steamid != steamid )
+   {
+      /* Probably very rare */
+      vg_error( "Tried to request save for a client, but the steamid doesnt match from when it was called.\n" );
+      return;
+   }
 
-      if( !vg_queue_previous( &replay->ring_buffer, frame_id, &frame_id ) )
-         break;
+   gs_replay *replay = &_gs_replay.replays[ client_id ];
+   for( u32 i=0; i<length; i ++ )
+   {
+      u32 minute_index = (start_minute - i) % REPLAY_MINUTE_COUNT;
+      replay->minute_flags[ minute_index ] |= k_replay_minute_save_request;
    }
+}
 
-   u32 task_size = sizeof( struct serialized_replay ) + buffer_size;
-   vg_async_task *task = vg_allocate_async_task( &_gs_db.tasks, task_size, 1 );
-   struct serialized_replay *replay_inf = (void *)task->data;
-   vg_queue_memcpy( &replay->ring_buffer, replay_inf->buffer, frame_id, buffer_size );
-   task->handler = task_write_replay;
-   replay_inf->buffer_size = buffer_size;
-   replay_inf->header.total_frames = total_frames;
-   replay_inf->header.head_offset = buffer_size - vg_queue_item_size( &replay->ring_buffer, replay->ring_buffer.head_offset );
-   vg_strncpy( path, replay_inf->path, sizeof(replay_inf->path), k_strncpy_always_add_null );
+void _gs_replay_server_tick(void)
+{
+   u32 new_minute = _gs_replay_get_minute();
 
-   vg_queue_item *first = (void*)replay_inf->buffer;
-   first->prev_size = 0;
+   if( new_minute > _gs_replay.current_minute )
+   {
+      for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ )
+      {
+         _gs_replay_run_save_requests(i);
+         gs_replay *replay = &_gs_replay.replays[ i ];
+         replay->minute_flags[ new_minute % REPLAY_MINUTE_COUNT ] = 0x00;
+      }
 
-   vg_async_task_dispatch( &_gs_db.tasks, task );
+      _gs_replay.current_minute = new_minute;
+
+      vg_info( "The new minute is: %u\n", new_minute );
+   }
 }
 
 void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_size )
@@ -110,6 +241,17 @@ void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_s
          if( free_frame->tick_recorded >= min_ticks )
             break;
 
+         /* check if the minute this frame exists in is under a save request. */
+         if( free_frame->minute > (_gs_replay.current_minute-REPLAY_MINUTE_COUNT) )
+         {
+            u32 minute_index = free_frame->minute % REPLAY_MINUTE_COUNT;
+            u32 minute_flags = dest_replay->minute_flags[ minute_index ];
+            if( minute_flags & k_replay_minute_save_request )
+               break;
+            else
+               dest_replay->minute_flags[ minute_index ] |= k_replay_minute_data_deleting;
+         }
+
          vg_queue_pop( ring );
       }
 
@@ -146,4 +288,15 @@ void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_s
    dest_frame->tick_recorded = _gameserver.ticks;
    dest_frame->frame_size = frame_size;
    dest_frame->_ = 0;
+   dest_frame->minute = _gs_replay.current_minute;
+   dest_replay->minute_flags[ _gs_replay.current_minute % REPLAY_MINUTE_COUNT ] |= k_replay_minute_has_data; 
+}
+
+#if 0
+
+u64 _gs_write_replay_to_disk( u8 client_id, f64 server_duration )
+{
+   THREAD_0;
+
 }
+#endif
index 97b14e8472a8114981053bf928d04a2789b8fdef..327c25c5d144e36deb590cc89017c461fc9706be 100644 (file)
@@ -2,6 +2,7 @@
 #include "network_msg.h"
 
 #define REPLAY_SIZE_LIMIT 1024*1024*2
+#define REPLAY_MINUTE_COUNT 8
 
 typedef struct gs_replay_frame gs_replay_frame;
 typedef struct gs_replay gs_replay;
@@ -9,19 +10,31 @@ typedef struct gs_replay gs_replay;
 struct gs_replay_frame
 {
    u64 tick_recorded;
+   u32 minute;
    u16 frame_size;
    u16 _;
    struct netmsg_playerframe network_frame;
 };
 
+enum replay_minute_flag
+{
+   k_replay_minute_has_data     = 0x1,
+   k_replay_minute_data_deleting= 0x2,
+   k_replay_minute_save_request = 0x4,
+   k_replay_minute_saved        = 0x8
+};
+
 struct _gs_replay
 {
    struct gs_replay
    {
       vg_queue ring_buffer;
+      u8 minute_flags[ 8 ];
    }
    replays[ NETWORK_MAX_PLAYERS ];
 
+   u32 current_minute;
+
    bool print_info;
    u64 print_ticker;
 }
@@ -32,3 +45,7 @@ void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_s
 void _gs_write_replay_to_disk( u8 client_id, f64 server_duration, const char *path );
 void _gs_test_replay( u8 client_id, const char *path );
 void _gs_transfer_tick(void);
+
+u32 _gs_replay_get_minute(void);
+void _gs_replay_client_disconnect( u32 client_id );
+void _gs_replay_request_save( u32 client_id, u64 steamid, u32 start_minute, u32 length, bool async );
index 19abfd5193198e604eae15d101ab1f0a618e18ff..93b14149357b680a79eb296ce05acd0c4056dd53 100644 (file)
@@ -225,10 +225,10 @@ static void task_request_processing_complete( vg_async_task *task )
    /* OK or client error */
    if( req->client_request_id )
    {
-      if( req->status < k_request_status_ok )
-         req->state = k_request_state_server_error;
-      else
+      if( req->status == k_request_status_ok )
          req->state = k_request_state_transfer_start;
+      else
+         req->state = k_request_state_server_error;
    }
    else
    { 
@@ -268,7 +268,9 @@ static void task_request_run( vg_async_task *task )
          const char *route = vg_msg_getkvstr( &client_msg, "route" );
          u32 week;
          vg_msg_getkvintg( &client_msg, "week", k_vg_msg_u32, &week, NULL );
+         gameserver_cat_table( &body_msg, mod, route, 0, "rows" );
          
+#if 0
          if( week == NETWORK_LEADERBOARD_CURRENT_WEEK )
          {
             gameserver_cat_table( &body_msg, mod, route, gameserver_get_current_week(), "rows_weekly" );
@@ -280,44 +282,84 @@ static void task_request_run( vg_async_task *task )
          }
          else 
             gameserver_cat_table( &body_msg, mod, route, week, "rows" );
+#endif
+      }
+      else if( !strcmp( endpoint, "replay" ) )
+      {
+         u64 steamid;
+         vg_msg_getkvintg( &client_msg, "steamid", k_vg_msg_u64, &steamid, NULL );
+         if( steamid == 0 )
+            steamid = req->user_steamid;
+
+         u32 minute;
+         vg_msg_getkvintg( &client_msg, "minute", k_vg_msg_u32, &minute, NULL );
+         if( minute == 0 )
+            minute = _gs_replay_get_minute()-1;
+
+         char path[1024];
+         snprintf( path, sizeof(path), "replaydata/%lx@%x", steamid, minute );
+
+         FILE *fp = fopen( path, "rb" );
+         if( !fp )
+            req->status = k_request_status_not_found;
+         else
+         {
+            u64 l = fread( req->data_buffer, 1, GS_TRANSFER_MAX_SIZE, fp );
+            if( feof( fp ) )
+               req->data_buffer_send_size = l;
+            else
+            {
+               if( ferror( fp ) )
+                  req->status = k_request_status_database_error;
+               else
+                  req->status = k_request_status_out_of_memory;
+            }
+            fclose( fp );
+         }
+         goto E0;
       }
-#if 0
       else if( !strcmp( endpoint, "setlap" ) )
       {
-         if( client->steamid == k_steamid_max )
+         if( req->user_steamid == k_steamid_max )
          {
-            gameserver_request_respond( k_request_status_unauthorized, res, NULL, msg );
-            return;
+            req->status = k_request_status_unauthorized;
+            goto E0;
          }
 
-         const char *mod = vg_msg_getkvstr( &data, "mod" );
-         const char *route = vg_msg_getkvstr( &data, "route" );
+         const char *mod = vg_msg_getkvstr( &client_msg, "mod" );
+         const char *route = vg_msg_getkvstr( &client_msg, "route" );
          
-         char weekly_table[ DB_TABLE_UID_MAX ],
-              alltime_table[ DB_TABLE_UID_MAX ];
-
-         u32 week = gameserver_get_current_week();
+         //char weekly_table[ DB_TABLE_UID_MAX ],
+         char table[ DB_TABLE_UID_MAX ];
 
-         if( !db_get_highscore_table_name( mod, route, 0, alltime_table ) ||
-             !db_get_highscore_table_name( mod, route, week, weekly_table ) )
+         if( !db_get_highscore_table_name( mod, route, 0, table ) )
          {
-            gameserver_request_respond( k_request_status_out_of_memory, res, NULL, msg );
-            return;
+            req->status = k_request_status_out_of_memory;
+            goto E0;
          }
 
          i32 centiseconds;
-         vg_msg_getkvintg( &data, "time", k_vg_msg_i32, &centiseconds, NULL );
+         vg_msg_getkvintg( &client_msg, "time", k_vg_msg_i32, &centiseconds, NULL );
          if( centiseconds < 5*100 )
          {
-            gameserver_request_respond( k_request_status_client_error, res, NULL, msg );
-            return;
+            req->status = k_request_status_client_error;
+            goto E0;
          }
 
-         db_writeusertime( alltime_table, client->steamid, centiseconds, 1 );
-         db_writeusertime( weekly_table, client->steamid, centiseconds, 1 );
-         gameserver_request_respond( k_request_status_ok, res, NULL, msg );
+         u32 last_minute = 0;
+         i32 current_time = db_readusertime( table, req->user_steamid );
+         if( (current_time == 0) || (centiseconds < current_time) )
+         {
+            u32 current_minute = _gs_replay_get_minute(),
+                minutes_span = (centiseconds+(200*60)) / (100*60);
+            _gs_replay_request_save( req->client_id, req->user_steamid, current_minute, minutes_span, 1 );
+
+            last_minute = current_minute;
+         }
+
+         db_writeusertime( table, req->user_steamid, centiseconds, 1, last_minute );
+         //db_writeusertime( weekly_table, req->user_steamid, centiseconds, 1, last_minute );
       }
-#endif
       else
       {
          req->status = k_request_status_invalid_endpoint;
@@ -332,6 +374,7 @@ static void task_request_run( vg_async_task *task )
          req->data_buffer_send_size = body_msg.cur.co;
    }
 
+E0:;
    vg_async_task *return_task = vg_allocate_async_task( &_gameserver.tasks, sizeof(struct task_request_run_info), 1 );
    memcpy( return_task->data, info, sizeof(struct task_request_run_info) );
    return_task->handler = task_request_processing_complete;
@@ -368,6 +411,7 @@ void _gs_handle_request_message( u32 client_id, SteamNetworkingMessage_t *msg )
          req->state = k_request_state_none;
          req->message = msg;
          req->user_uid = client->session_uid;
+         req->user_steamid = client->steamid;
          req->client_id = client_id;
          req->client_request_id = client_packet->id;
          req->status = k_request_status_ok;
index bf23f9a0fef5ed02d651504716a05ceadd140878..24d4764999a679c04b337ba7d312f1e10be024ec 100644 (file)
@@ -29,6 +29,7 @@ struct gs_request
 
    SteamNetworkingMessage_t *message;
 
+   u64 user_steamid;
    u64 user_uid;
    u32 client_id;
    u8  client_request_id;
index bce62e8e2c28b1c522ed1b10b4bddd73a76344f2..4c3978daa36fced35cf7d35b723066f18ed91019 100644 (file)
@@ -298,11 +298,12 @@ static void on_server_connect_status( CallbackMsg_t *msg )
          string_ESteamNetworkingConnectionState(info->m_eOldState),
          string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
 
-   if( info->m_hConn == network_client.remote ){
+   if( info->m_hConn == network_client.remote )
+   {
       network_client.state = info->m_info.m_eState;
 
-      if( info->m_info.m_eState == 
-            k_ESteamNetworkingConnectionState_Connected ){
+      if( info->m_info.m_eState == k_ESteamNetworkingConnectionState_Connected )
+      {
          vg_success("  Connected to remote server.. authenticating\n");
 
          /* send version info to server */
@@ -326,21 +327,21 @@ static void on_server_connect_status( CallbackMsg_t *msg )
                hSteamNetworkingSockets, network_client.remote, auth, size,
                k_nSteamNetworkingSend_Reliable, NULL );
       }
-      else if( info->m_info.m_eState == 
-            k_ESteamNetworkingConnectionState_ClosedByPeer ){
-
-         if( info->m_info.m_eEndReason == 
-               k_ESteamNetConnectionEnd_Misc_InternalError ){
+      else if( info->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer )
+      {
+         if( info->m_info.m_eEndReason == k_ESteamNetConnectionEnd_Misc_InternalError )
+         {
             network_client.retries = 40;
          }
          network_disconnect();
       }
-      else if( info->m_info.m_eState == 
-            k_ESteamNetworkingConnectionState_ProblemDetectedLocally ){
+      else if( info->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally )
+      {
          network_disconnect();
       }
    }
-   else{
+   else
+   {
       //vg_warn( "  Recieved signal from unknown connection\n" );
    }
 
index c156a2dec4a0cb13db352928d4576a15d8844128..29669c41efd353befb3028fb39c9ffedad9411ec 100644 (file)
@@ -165,5 +165,15 @@ enum request_status {
   k_request_status_transfer_continue = 203
 };
 
+typedef struct serialized_netmsg serialized_netmsg;
+struct serialized_netmsg
+{
+   u16 msg_size;
+   u16 unused0;
+   u32 unused1;
+
+   u8  msg_data[];
+};
+
 #pragma pack(pop)
 #endif /* NETWORK_MSG_H */
index 33389915d947c625125e659909291a67e4bab982..924a93b8a898e7c9c0dca4a7265614ad57325201 100644 (file)
@@ -163,6 +163,32 @@ void network_publish_laptime( const char *mod_uid, const char *route_uid, f64 la
    network_send_request( packet, &data, NULL, 0 );
 }
 
+#if 0
+static void replay_download_callback( void *data, u32 data_size, u64 userdata, enum request_status status )
+{
+   if( status == k_request_status_ok )
+      vg_info( "%u bytes downloaded OK\n", data_size );
+   else
+      vg_warn( "Womp womp\n" );
+}
+
+void network_download_replay( u64 steamid, u32 minute )
+{
+   if( !network_connected() )
+      return;
+
+   netmsg_request *packet = alloca( sizeof(netmsg_request) + 512 );
+   packet->inetmsg_id = k_inetmsg_request;
+
+   vg_msg data;
+   vg_msg_init( &data, packet->buffer, 512 );
+   vg_msg_wkvstr( &data, "endpoint", "replay" );
+   vg_msg_wkvnum( &data, "minute", k_vg_msg_u32, 1, &minute );
+   vg_msg_wkvnum( &data, "steamid", k_vg_msg_u64, 1, &steamid );
+   network_send_request( packet, &data, replay_download_callback, 0 );
+}
+#endif
+
 static void _delete_request( net_request *request )
 {
    vg_pool_unwatch( &_net_requests.request_pool, vg_pool_id( &_net_requests.request_pool, request ) );
index e1789a37d5c25fb64f63d217a7545ff5cc070fd6..2b184585f9dc67eeea3ed1a61ef6e406eca71cac 100644 (file)
@@ -401,9 +401,11 @@ void net_sfx_exchange( bitpack_ctx *ctx, struct net_sfx *sfx )
 
 void net_sfx_play( struct net_sfx *sfx )
 {
-   if( sfx->system < k_player_subsystem_max ){
+   if( sfx->system < k_player_subsystem_max )
+   {
       struct player_subsystem_interface *sys = player_subsystems[sfx->system];
-      if( sys->sfx_oneshot ){
+      if( sys->sfx_oneshot )
+      {
          sys->sfx_oneshot( sfx->id, sfx->location, sfx->volume );
       }
    }
index 12db20a3c4f44a6e24fa885b8f305a1613db4026..4e0783983130fb22f2e6e3f8e137093fc507e517 100644 (file)
@@ -172,6 +172,10 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
          .buffer_len = datasize,
          .bytes = 0,
       };
+
+      /* camera */
+      v2f _null_v2f = {0,0};
+      bitpack_qv2f( &ctx, 8, 0.0f, VG_TAUf, _null_v2f );
       
       /* animation 
        * -------------------------------------------------------------*/
@@ -180,8 +184,7 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
       dest->boundary_hash = frame->boundary_hash;
 
       struct network_player *player = &netplayers.list[ frame->client ];
-      struct player_subsystem_interface *sys = 
-         player_subsystems[ frame->subsystem ];
+      struct player_subsystem_interface *sys = player_subsystems[ frame->subsystem ];
 
       memset( &dest->data, 0, sys->animator_size );
       if( sys->network_animator_exchange )
@@ -192,23 +195,24 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
       /* sfx
        * -------------------------------------------------------------*/
       
-      for( u32 i=0; i<frame->sound_effects; i ++ ){
+      for( u32 i=0; i<frame->sound_effects; i ++ )
+      {
          struct net_sfx sfx;
          net_sfx_exchange( &ctx, &sfx );
 
-         f64 t = (frame->timestamp - NETWORK_FRAMERATE) + 
-                 (sfx.subframe*NETWORK_FRAMERATE);
-
+         f64 t = (frame->timestamp - NETWORK_FRAMERATE) + (sfx.subframe*NETWORK_FRAMERATE);
          f32 remaining = t - ib->t;
 
          if( remaining <= 0.0f )
             net_sfx_play( &sfx );
-         else{
+         else
+         {
             struct net_sfx *dst = NULL;
-
-            for( u32 j=0; j<NETWORK_SFX_QUEUE_LENGTH; j ++ ){
+            for( u32 j=0; j<NETWORK_SFX_QUEUE_LENGTH; j ++ )
+            {
                struct net_sfx *sj = &netplayers.sfx_queue[j];
-               if( sj->system == k_player_subsystem_invalid ){
+               if( sj->system == k_player_subsystem_invalid )
+               {
                   dst = sj;
                   break;
                }
@@ -226,8 +230,8 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
        * -------------------------------------------------------------*/
 
       memset( &dest->data_glider, 0, sizeof(struct remote_glider_animator) );
-      if( dest->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|
-                         NETMSG_PLAYERFRAME_GLIDER_ORPHAN) ){
+      if( dest->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|NETMSG_PLAYERFRAME_GLIDER_ORPHAN) )
+      {
          player_glide_remote_animator_exchange( &ctx, &dest->data_glider );
       }
 
@@ -1112,10 +1116,8 @@ void remote_players_init(void)
 {
    vg_console_reg_cmd( "network_info", cmd_network_info, NULL );
    vg_console_reg_cmd( "add_test_players", remote_players_randomize, NULL );
-   vg_console_reg_var( "k_show_own_name", &k_show_own_name, 
-                       k_var_dtype_i32, 0 );
-   for( u32 i=0; i<NETWORK_SFX_QUEUE_LENGTH; i ++ ){
+   vg_console_reg_var( "k_show_own_name", &k_show_own_name, k_var_dtype_i32, 0 );
+   for( u32 i=0; i<NETWORK_SFX_QUEUE_LENGTH; i ++ )
       netplayers.sfx_queue[i].system = k_player_subsystem_invalid;
-   }
 }
 
index 00946805ddccb0904cea0752c291e76af2fd1c82..4c1427ccca5b75ea345d7be3f8dc50d95495ba00 100644 (file)
@@ -61,11 +61,9 @@ static void replay_tailpop( replay_buffer *replay ){
       replay->head = NULL;
 }
 
-static replay_frame *replay_newframe( replay_buffer *replay, 
-                                         u16 animator_size,
-                                         u16 gamestate_size,
-                                         u16 sfx_count,
-                                         bool save_glider ){
+static replay_frame *replay_newframe( replay_buffer *replay, u16 animator_size, u16 gamestate_size, u16 sfx_count,
+                                                             bool save_glider )
+{
    u16 data_table[ k_replay_framedata_rows ][2];
    data_table[ k_replay_framedata_animator ][1]  = animator_size;
    data_table[ k_replay_framedata_gamestate ][1] = gamestate_size;
@@ -143,7 +141,8 @@ check_again:;
    return frame;
 }
 
-static void replay_emit_frame_sounds( replay_frame *frame ){
+static void replay_emit_frame_sounds( replay_frame *frame )
+{
    void *baseptr = frame;
    u16 *inf = frame->data_table[k_replay_framedata_sfx];
    struct net_sfx *buffer = baseptr + inf[0];
@@ -407,15 +406,14 @@ void skaterift_record_frame( replay_buffer *replay, int force_gamestate )
    if( delta > k_replay_rate ) save_frame = 1;
    if( save_state ) save_frame = 1;
 
-   if( localplayer.have_glider || localplayer.glider_orphan ||
-       localplayer.subsystem == k_player_subsystem_glide ){
+   if( localplayer.have_glider || localplayer.glider_orphan || localplayer.subsystem == k_player_subsystem_glide )
       save_glider = 1;
-   }
 
    if( !save_frame ) return;
 
    u16 gamestate_size = 0;
-   if( save_state ){
+   if( save_state )
+   {
       /* TODO: have as part of system struct */
       gamestate_size = (u32 []){ 
          [k_player_subsystem_walk ] = sizeof(struct player_walk_state),
@@ -429,15 +427,14 @@ void skaterift_record_frame( replay_buffer *replay, int force_gamestate )
 
    u16 animator_size = player_subsystems[localplayer.subsystem]->animator_size;
    
-   replay_frame *frame = replay_newframe( replay,
-                                          animator_size, gamestate_size, 
+   replay_frame *frame = replay_newframe( replay, animator_size, gamestate_size, 
                                           localplayer.local_sfx_buffer_count,
                                           save_glider );
    frame->system = localplayer.subsystem;
 
-   if( save_state ){
-      replay_gamestate *gs = 
-         replay_frame_data( frame, k_replay_framedata_internal_gamestate );
+   if( save_state )
+   {
+      replay_gamestate *gs = replay_frame_data( frame, k_replay_framedata_internal_gamestate );
 
       gs->current_run_version = _world.current_run_version;
       gs->drowned = localplayer.drowned;
@@ -476,10 +473,9 @@ void skaterift_record_frame( replay_buffer *replay, int force_gamestate )
       }
    }
 
-   if( save_glider ){
-      struct replay_glider_data *inf = 
-         replay_frame_data( frame, k_replay_framedata_glider );
-
+   if( save_glider )
+   {
+      struct replay_glider_data *inf = replay_frame_data( frame, k_replay_framedata_glider );
       inf->have_glider   = localplayer.have_glider;
       inf->glider_orphan = localplayer.glider_orphan;
       inf->t             = player_glide.t;
@@ -615,6 +611,7 @@ static void skaterift_replay_resume(void)
    }
 }
 
+static void _remote_replay_pre_update(void);
 void skaterift_replay_pre_update(void)
 {
    if( skaterift.activity != k_skaterift_replay ) 
@@ -718,6 +715,9 @@ void skaterift_replay_pre_update(void)
 
 void skaterift_open_replay(void)
 {
+   vg_error( "stub!!!\n" );
+   return;
+
    skaterift.activity = k_skaterift_replay;
    skaterift_record_frame( &player_replay.local, 1 );
    if( player_replay.local.head )
@@ -732,9 +732,9 @@ void skaterift_open_replay(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;
+   u32 MB = 1024*1024;
+   player_replay.local.data = vg_linear_alloc( vg_mem.rtmemory, 6*MB );
+   player_replay.local.size = 6*MB;
    replay_clear( &player_replay.local );
 }
 
index c1ebf996f07582ab772009828f16e79680bd23f3..d1f190daf23edd945a82ae0ba30f78e0e9ffd35f 100644 (file)
@@ -78,6 +78,7 @@ struct replay_sfx {
 struct replay_globals 
 {
    replay_buffer local;
+
    replay_frame *resume_target;
    f64 resume_begin;
    f32 resume_transition;
@@ -92,13 +93,11 @@ struct replay_globals
    struct gui_helper *helper_resume, *helper_freecam;
 
    vg_camera replay_freecam;
-
    bool use_freecam;
    bool hide_ui;
    v3f freecam_v, freecam_w;
 
    i32 editor_mode;
-
    replay_keyframe keyframes[32];
    u32 keyframe_count;
    i32 active_keyframe;
diff --git a/src/replay2.c b/src/replay2.c
new file mode 100644 (file)
index 0000000..be53cc8
--- /dev/null
@@ -0,0 +1,433 @@
+#include "replay2.h"
+
+struct _remote_replay _remote_replay;
+struct _replay_player _replay_player;
+
+void _replay2_init(void)
+{
+   u32 MB = 1024*1024,
+       size = 4*MB;
+
+   _remote_replay.replay.buffer.buffer = vg_linear_alloc( vg_mem.rtmemory, size );
+   _remote_replay.replay.buffer.size = size;
+}
+
+void _replay2_open_player( replay2 *replay, bool end )
+{
+   if( replay == NULL )
+   {
+      vg_error( "Cannot open a NULL replay\n" );
+      return;
+   }
+   if( replay->buffer.allocation_count < 2 )
+   {
+      vg_error( "Not enough frames in that replay\n" );
+      return;
+   }
+
+   skaterift.activity = k_skaterift_replay;
+   _replay_player.replay = replay;
+
+   if( end ) 
+      _replay_player.cursor_frame_offset = replay->buffer.head_offset;
+   else
+      _replay_player.cursor_frame_offset = replay->buffer.tail_offset;
+
+   replay2_frame *frame = vg_queue_data( &replay->buffer, _replay_player.cursor_frame_offset );
+   _replay_player.cursor = frame->time;
+}
+
+/* remote replay downloader 
+ * ------------------------------------------------------------------ */
+
+static void replay_download_callback( void *data, u32 data_size, u64 userdata, enum request_status status )
+{
+   bool is_from_network = userdata;
+   replay2 *replay = &_remote_replay.replay;
+
+   struct remote_replay_chunk *chunk = &_remote_replay.chunks[ _remote_replay.chunks_downloaded ];
+   if( status == k_request_status_ok )
+   {
+      if( is_from_network )
+      {
+         char path[1024];
+         snprintf( path, sizeof(path), "replaydata/%lx@%x", _remote_replay.steamid, chunk->minute );
+
+         FILE *fp = fopen( path, "wb" );
+         if( fp )
+         {
+            fwrite( data, data_size, 1, fp );
+            fclose( fp );
+            vg_low( "Cached %s to disk\n", path );
+         }
+         else
+         {
+            vg_error( "Can't open '%s' for writing\n", path );
+         }
+      }
+
+      vg_info( "Processing %u bytes of network frames\n", data_size );
+
+      u32 offset = 0;
+      while(1)
+      {
+         serialized_netmsg *snm = data + offset;
+         if( snm->msg_size < sizeof(netmsg_blank) )
+         {
+            vg_error( "Message size too small\n" );
+            goto E0;
+         }
+
+         if( offset + sizeof(serialized_netmsg) + snm->msg_size > data_size )
+         {
+            vg_error( "Message size too big (doesn't fit buffer)\n" );
+            goto E0;
+         }
+
+         netmsg_blank *tmp = (void *)snm->msg_data;
+         if( tmp->inetmsg_id == k_inetmsg_playerframe )
+         {
+            netmsg_playerframe *playerframe = (void *)snm->msg_data;
+
+            if( playerframe->timestamp <= _remote_replay.min_frame_t )
+            {
+               vg_warn( "Dropping packet with lesser timestamp (%f). (FIXME)\n", playerframe->timestamp );
+               goto E1;
+            }
+            _remote_replay.min_frame_t = playerframe->timestamp;
+
+            replay2_frame *dst_frame = vg_queue_alloc( &replay->buffer, sizeof(replay2_frame) + snm->msg_size, NULL );
+            if( !dst_frame )
+            {
+               vg_error( "Out of mem adding frame!\n" );
+               goto E0;
+            }
+            dst_frame->net_frame_size = snm->msg_size;
+            dst_frame->time = playerframe->timestamp;
+            memcpy( &dst_frame->net_frame, playerframe, snm->msg_size );
+         }
+
+E1:      offset += sizeof(serialized_netmsg) + snm->msg_size;
+         if( offset == data_size )
+            break;
+      }
+
+      chunk->state = k_chunk_state_processed;
+      _remote_replay.chunks_downloaded ++;
+
+      if( _remote_replay.chunks_downloaded == _remote_replay.total_chunks )
+      {
+         _remote_replay.state = k_remote_replay_state_ready;
+         vg_success( "Winner! (%u total frames)\n", replay->buffer.allocation_count );
+
+         _replay2_open_player( replay, 0 );
+      }
+
+      return;
+   }
+
+E0:chunk->state = k_chunk_state_broken;
+   _remote_replay.state = k_remote_replay_state_failed;
+}
+
+struct async_cache_check_result
+{
+   bool found_in_cache;
+   bool broken;
+   u32 data_length;
+   u8 data[];
+};
+
+static void async_cache_check_result( void *payload, u32 size )
+{
+   struct async_cache_check_result *result = payload;
+   struct remote_replay_chunk *chunk = &_remote_replay.chunks[ _remote_replay.chunks_downloaded ];
+
+   if( result->found_in_cache )
+   {
+      if( result->broken )
+         replay_download_callback( NULL, 0, 0, k_request_status_not_found );
+      else
+         replay_download_callback( result->data, result->data_length, 0, k_request_status_ok );
+   }
+   else
+   {
+      if( !network_connected() )
+         return;
+
+      chunk->state = k_chunk_state_downloading;
+      vg_make_directory( "replaydata" );
+
+      netmsg_request *packet = alloca( sizeof(netmsg_request) + 512 );
+      packet->inetmsg_id = k_inetmsg_request;
+
+      vg_msg data;
+      vg_msg_init( &data, packet->buffer, 512 );
+      vg_msg_wkvstr( &data, "endpoint", "replay" );
+      vg_msg_wkvnum( &data, "minute", k_vg_msg_u32, 1, &chunk->minute );
+      vg_msg_wkvnum( &data, "steamid", k_vg_msg_u64, 1, &_remote_replay.steamid );
+      network_send_request( packet, &data, replay_download_callback, 1 );
+   }
+}
+
+static void _remote_replay_cache_check( void *_ )
+{
+   struct remote_replay_chunk *chunk = &_remote_replay.chunks[ _remote_replay.chunks_downloaded ];
+
+   char path[1024];
+   snprintf( path, sizeof(path), "replaydata/%lx@%x", _remote_replay.steamid, chunk->minute );
+
+   vg_async_item *item = vg_async_alloc( sizeof(struct async_cache_check_result) + 20*1024*1024 );
+   struct async_cache_check_result *result = item->payload;
+   result->broken = 0;
+
+   FILE *fp = fopen( path, "rb" );
+   if( fp )
+   {
+      result->found_in_cache = 1;
+      result->data_length = fread( result->data, 1, 20*1024*1024, fp );
+
+      if( !feof( fp ) )
+         result->broken = 1;
+
+      fclose( fp );
+   }
+   else
+   {
+      result->found_in_cache = 0;
+      result->data_length = 0;
+   }
+
+   vg_async_dispatch( item, async_cache_check_result );
+}
+
+static void _remote_replay_pre_update(void)
+{
+   if( _remote_replay.state == k_remote_replay_state_none )
+      return;
+
+   if( _remote_replay.state == k_remote_replay_state_ready )
+      return;
+
+   if( _remote_replay.state == k_remote_replay_state_failed )
+      return;
+
+   struct remote_replay_chunk *chunk = &_remote_replay.chunks[ _remote_replay.chunks_downloaded ];
+   if( chunk->state == k_chunk_state_none )
+   {
+      if( vg_loader_availible() )
+      {
+         chunk->state = k_chunk_state_cache_check;
+         vg_loader_start( _remote_replay_cache_check, NULL );
+      }
+   }
+}
+
+void _replay2_pre_update(void)
+{
+   _remote_replay_pre_update();
+}
+
+void _replay2_seek( f64 t, bool play_sounds )
+{
+   replay2 *replay = _replay_player.replay;
+   VG_ASSERT( replay );
+
+   replay2_frame *frame_start = vg_queue_data( &replay->buffer, replay->buffer.tail_offset ),
+                 *frame_end   = vg_queue_data( &replay->buffer, replay->buffer.head_offset );
+
+   if( t < frame_start->time ) t = frame_start->time;
+   if( t > frame_end->time ) t = frame_end->time;
+
+   f64 dir = t - _replay_player.cursor;
+   if( dir == 0.0 ) 
+      return;
+   dir = vg_signf( dir );
+   
+   u32 current_offset = _replay_player.cursor_frame_offset;
+   replay2_frame *current_frame = vg_queue_data( &replay->buffer, current_offset );
+   for( u32 i=0; i<4096; i ++ )
+   {
+      if( dir < 0.0 )
+      {
+         if( t > current_frame->time ) 
+         {
+            break;
+         }
+      }
+
+      bool next = 0;
+      if( dir > 0.0 ) 
+         next = vg_queue_next(     &replay->buffer, current_offset, &current_offset );
+      else 
+         next = vg_queue_previous( &replay->buffer, current_offset, &current_offset );
+
+      if( !next )
+         break;
+
+      current_frame = vg_queue_data( &replay->buffer, current_offset );
+      if( dir > 0.0 )
+      {
+         if( t < current_frame->time )
+         {
+            break;
+         }
+      }
+
+      if( play_sounds )
+      {
+         //replay_emit_frame_sounds( next );
+      }
+   }
+
+   _replay_player.cursor_frame_offset = current_offset;
+   _replay_player.cursor = current_frame->time;
+}
+
+void _replay2_imgui( ui_context *ctx )
+{
+   if( skaterift.activity != k_skaterift_replay ) 
+      return;
+
+   if( _replay_player.hide_ui ) 
+      return;
+
+   if( vg_input.display_input_method != k_input_method_controller )
+   {
+      ui_capture_mouse( ctx, 1 );
+   }
+
+   replay2 *replay = _replay_player.replay;
+   VG_ASSERT( replay );
+
+   f64 start = _replay_player.cursor,
+       end   = start;
+   replay2_frame *frame_start = vg_queue_data( &replay->buffer, replay->buffer.tail_offset ),
+                 *frame_end   = vg_queue_data( &replay->buffer, replay->buffer.head_offset );
+   start = frame_start->time;
+   end = frame_end->time;
+   f64 len = end - start,
+       cur = (_replay_player.cursor - start) / len;
+
+   /* mainbar */
+   ui_rect timeline = { 8, vg.window_y-(32+8), vg.window_x-16, 32 };
+   ui_rect start_box;
+   ui_split( timeline, k_ui_axis_v, 32, 8, start_box, timeline );
+
+   const char *start_text = (_replay_player.replay_control == k_replay_control_play)? "||": ">";
+   if( menu_button_rect( ctx, start_box, 0, 1, start_text ) )
+   {
+      _replay_player.replay_control ^= k_replay_control_play;
+   }
+
+   ui_fill( ctx, timeline, ui_colour( ctx, k_ui_bg ) );
+
+   /* cursor frame block */
+   replay2_frame *frame_cursor = vg_queue_data( &replay->buffer, _replay_player.cursor_frame_offset );
+   u32 next_frame_offset;
+   if( vg_queue_next( &replay->buffer, _replay_player.cursor_frame_offset, &next_frame_offset ) )
+   {
+      replay2_frame *next_frame = vg_queue_data( &replay->buffer, next_frame_offset );
+      f64 l = (next_frame->time - frame_cursor->time )/len,
+          s = (frame_cursor->time - start) / len;
+      ui_rect box = { timeline[0] + s*(f64)timeline[2], timeline[1], 
+                      VG_MAX(4,(ui_px)(l*timeline[2])), timeline[3]+2 };
+      ui_fill( ctx, box, ui_colour( ctx, k_ui_bg+4 ) );
+   }
+
+   /* cursor */
+   ui_rect cusor = { timeline[0] + cur*(f64)timeline[2] - 1, timeline[1], 2, timeline[3] };
+   ui_fill( ctx, cusor, ui_colour( ctx, k_ui_bg+7 ) );
+
+   /* latest state marker */
+#if 0
+   if( skaterift.allow_replay_resume )
+   {
+      if( replay->statehead )
+      {
+         f64 t = (replay->statehead->time - start) / len;
+         ui_rect tag = { timeline[0] + t*(f64)timeline[2], timeline[1], 
+                         2, timeline[3]+8 };
+         ui_fill( ctx, tag, ui_colour( ctx, k_ui_green+k_ui_brighter ) );
+      }
+
+      /* previous state marker */
+      replay_frame *prev = replay_find_recent_stateframe( replay );
+      if( prev )
+      {
+         f64 t = (prev->time - start) / len;
+         ui_rect tag = { timeline[0] + t*(f64)timeline[2], timeline[1], 
+                         2, timeline[3]+8 };
+         ui_fill( ctx, tag, ui_colour( ctx, k_ui_yellow+k_ui_brighter ) );
+      }
+   }
+#endif
+
+   char buffer[ 128 ];
+   snprintf( buffer, 128, "-%.2fs", (end-_replay_player.cursor) );
+   ui_text( ctx, timeline, buffer, 1, k_ui_align_middle_left, 0 );
+   ui_text( ctx, timeline, "0s", 1, k_ui_align_middle_right, 0 );
+
+   /* helpers */
+   ctx->font = &vgf_default_large;
+
+   ui_rect helper_list_l = { 10, timeline[1] - (ctx->font->sy+8), vg.window_x/2, ctx->font->sy };
+   char buf[256];
+   vg_str str;
+   vg_strnull( &str, buf, sizeof(buf) );
+   vg_input_string( &str, input_axis_list[k_sraxis_replay_h], 1 );
+   vg_strcat( &str, "\x07 Scrub" );
+   ui_text( ctx, helper_list_l, buf, 1, k_ui_align_left, 0 );
+   helper_list_l[1] -= helper_list_l[3]+2;
+
+   vg_strnull( &str, buf, sizeof(buf) );
+   vg_input_string( &str, input_button_list[k_srbind_replay_play], 1 );
+   vg_strcat( &str, (_replay_player.replay_control == k_replay_control_play)? "\x07 Pause": "\x07 Play" );
+   ui_text( ctx, helper_list_l, buf, 1, k_ui_align_left, 0 );
+   helper_list_l[1] -= helper_list_l[3]+2;
+
+   vg_strnull( &str, buf, sizeof(buf) );
+   vg_input_string( &str, input_button_list[k_srbind_replay_freecam], 1 );
+   vg_strcat( &str, "\x07 Freecam" );
+   ui_text( ctx, helper_list_l, buf, 1, k_ui_align_left, 0 );
+   helper_list_l[1] -= helper_list_l[3]+2;
+
+   vg_strnull( &str, buf, sizeof(buf) );
+   vg_input_string( &str, input_button_list[k_srbind_replay_hide_ui], 1 );
+   vg_strcat( &str, "\x07 Hide UI" );
+   ui_text( ctx, helper_list_l, buf, 1, k_ui_align_left, 0 );
+   helper_list_l[1] -= helper_list_l[3]+2;
+
+   ui_rect helper_list_r = { vg.window_x/2, timeline[1] - (ctx->font->sy+8), vg.window_x/2-10, ctx->font->sy };
+   vg_strnull( &str, buf, sizeof(buf) );
+   vg_input_string( &str, input_button_list[k_srbind_mback], 1 );
+   vg_strcat( &str, "\x07 Exit Replay" );
+   ui_text( ctx, helper_list_r, buf, 1, k_ui_align_right, 0 );
+   helper_list_l[1] -= helper_list_r[3]+2;
+
+   if( _replay_player.use_freecam )
+   {
+      ui_rect box = { vg.window_x/2 - 200, 40, 400, ctx->font->sy };
+      ui_text( ctx, box, KYEL "\x06\x02--- Freecam Enabled ---", 1, k_ui_align_center, 0 );
+   }
+
+   ctx->font = &vgf_default_small;
+
+   /* timeline scrub */
+   bool start_in_timeline = 
+      ui_clicking(ctx, UI_MOUSE_LEFT) && 
+      ui_inside_rect(timeline, ctx->mouse_click);
+
+   if( (ui_inside_rect( timeline, ctx->mouse )) || start_in_timeline )
+   {
+      ui_rect cursor = { ctx->mouse[0], timeline[1], 4, timeline[3] };
+      ui_fill( ctx, cursor, ui_colour( ctx, k_ui_fg ) );
+      ctx->cursor = k_ui_cursor_ibeam;
+
+      if( ui_clicking( ctx, UI_MOUSE_LEFT ) && start_in_timeline )
+      {
+         f64 mouse_t = start + ((f64)(ctx->mouse[0]-timeline[0]) / (f64)timeline[2])*len;
+         _replay2_seek( mouse_t, 1 );
+      }
+   }
+}
diff --git a/src/replay2.h b/src/replay2.h
new file mode 100644 (file)
index 0000000..fe4c637
--- /dev/null
@@ -0,0 +1,81 @@
+#pragma once
+#include "vg/vg_mem_queue.h"
+
+typedef struct replay2 replay2;
+struct replay2
+{
+   enum replay_type
+   {
+      k_replay_type_local,
+      k_replay_type_network
+   }
+   type;
+
+   vg_queue buffer;
+};
+
+typedef struct replay2_frame replay2_frame;
+struct replay2_frame
+{
+   f64 time; // duplicated from net_frame
+   u32 net_frame_size;
+   netmsg_playerframe net_frame;
+};
+
+struct _remote_replay
+{
+   u32 total_chunks,
+       chunks_downloaded;
+
+   u64 steamid;
+   struct remote_replay_chunk
+   {
+      u32 minute;
+      enum chunk_state
+      {
+         k_chunk_state_none,
+         k_chunk_state_cache_check,
+         k_chunk_state_downloading,
+         k_chunk_state_processed,
+         k_chunk_state_broken
+      }
+      state;
+   }
+   chunks[8];
+   f64 min_frame_t;
+
+   enum remote_replay_state
+   {
+      k_remote_replay_state_none,
+      k_remote_replay_state_downloading,
+      k_remote_replay_state_ready,
+      k_remote_replay_state_failed
+   }
+   state;
+
+   replay2 replay;
+}
+extern _remote_replay;
+
+struct _replay_player
+{
+   replay2 *replay;
+
+   /* TODO: Modifiers / keyframes lane */
+   
+   u32 cursor_frame_offset;
+   f64 cursor;
+   enum replay_control replay_control;
+   f32 track_velocity;
+
+   vg_camera replay_freecam;
+   bool use_freecam;
+   bool hide_ui;
+   v3f freecam_v, freecam_w;
+}
+extern _replay_player;
+
+void _replay2_init(void);
+void _replay2_pre_update(void);
+void _replay2_imgui( ui_context *ctx );
+void _replay2_open_player( replay2 *replay, bool end );
diff --git a/src/serialized_replay.h b/src/serialized_replay.h
new file mode 100644 (file)
index 0000000..c5e76c6
--- /dev/null
@@ -0,0 +1,2 @@
+#pragma once
+#include "network_msg.h"
index 0c6b89d9fdb0e0993158865c720adb15b10b00c9..e7762eef2f7246ee338d59394cf1cd34fa23f15f 100644 (file)
@@ -54,6 +54,7 @@
 #include "ent_script.h"
 #include "board_maker.h"
 #include "compass.h"
+#include "replay2.h"
 
 struct skaterift_globals skaterift = 
 { 
@@ -106,6 +107,7 @@ void game_load(void)
    vg_loader_step( world_map_init, NULL );
    vg_loader_step( ent_tornado_init, NULL );
    vg_loader_step( skaterift_replay_init, NULL );
+   vg_loader_step( _replay2_init, NULL );
    vg_loader_step( skaterift_load_player_content, NULL );
 
    vg_loader_set_user_information( "Compiling shaders" );
@@ -199,6 +201,7 @@ void vg_pre_update(void)
    }
 
    skaterift_replay_pre_update();
+   _replay2_pre_update();
    remote_sfx_pre_update();
 
    v3f listen_co;
@@ -380,10 +383,10 @@ static void render_main_game(void)
    {
       player__animate_from_replay( &player_replay.local );
    }
-   else{
+   else
+   {
       player__animate();
-      skaterift_record_frame( &player_replay.local,
-                              localplayer.deferred_frame_record );
+      skaterift_record_frame( &player_replay.local, localplayer.deferred_frame_record );
       localplayer.deferred_frame_record = 0;
    }
 
@@ -533,7 +536,8 @@ void vg_gui( ui_context *ctx )
    world_instance *world = &_world.main;
 
    world_routes_imgui( ctx, world );
-   skaterift_replay_imgui( ctx );
+   //skaterift_replay_imgui( ctx );
+   _replay2_imgui( ctx );
    workshop_form_gui( ctx );
    world_gui( ctx, world );
 
@@ -615,6 +619,7 @@ void vg_framebuffer_resize( int w, int h )
 #include "skaterift_script.c"
 #include "board_maker.c"
 #include "compass.c"
+#include "replay2.c"
 
 //TODO
 //#include "vg/submodules/hashmap.c/hashmap.c"
index 0eb8101e47a87975fc0abb7d22b45b7f09575a66..46accf1cd2e72239ba78aaced1b8b71764d33cd6 100644 (file)
@@ -1423,7 +1423,7 @@ void render_world_cubemaps( world_instance *world )
 
 void render_world_portal_cubemaps(void)
 {
-   if( vg_loader_availible() )
+   if( vg_loader_availible() ) // HACK? 
    {
       qoi_desc desc = 
       {
index ae1ade0b284fe05493e185175e3ee1c93b35e21b..09e183b32b52a12ff97bba6ed5535efdba450bd7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ * Copyright (C) 2021-2025 Mt.ZERO Software, Harry Godden - All Rights Reserved
  */
 
 #pragma once
@@ -9,10 +9,8 @@
 #include "network_msg.h"
 
 void world_routes_init(void);
-void world_routes_fracture( world_instance *world, ent_gate *gate,
-                               v3f imp_co, v3f imp_v );
-void world_routes_activate_entry_gate( world_instance *world, 
-                                          ent_gate *rg );
+void world_routes_fracture( world_instance *world, ent_gate *gate, v3f imp_co, v3f imp_v );
+void world_routes_activate_entry_gate( world_instance *world, ent_gate *rg );
 void render_world_routes( world_instance *world, 
                           world_instance *host_world,
                           m4x3f mmdl, vg_camera *cam, 
index d41037f4f84103fd7b17594c1ac4d20d7ececb8b..ecc34181bd8b2cbdf4a3f0b5aae4d2de1fc5f46d 100644 (file)
@@ -57,6 +57,9 @@ void sfd_encode( v2i co, const char *str, enum world_sfd_align align )
    i32 row_h = world_sfd.h -1 -co[1];
    i32 offset_x = 0;
 
+   if( row_h < 0 ) 
+      return;
+
    i32 w = VG_MIN( strlen(str), world_sfd.w );
    if( align == k_world_sfd_center )
       offset_x = (world_sfd.w - w) / 2;
@@ -65,7 +68,8 @@ void sfd_encode( v2i co, const char *str, enum world_sfd_align align )
    else
       offset_x = co[0];
 
-   for( i32 i=0; i<world_sfd.w; i++ ){
+   for( i32 i=0; i<world_sfd.w; i++ )
+   {
       i32 u = i + offset_x,
           idx = (world_sfd.w*row_h + u) * 2;
 
@@ -79,29 +83,45 @@ void sfd_encode( v2i co, const char *str, enum world_sfd_align align )
    }
 }
 
-void world_sfd_compile_scores( struct leaderboard_cache *board, const char *title )
+void world_sfd_compile_active_scores(void)
 {
+   world_instance *world = &_world.main;
+   
+   struct leaderboard_cache *board = NULL;
+   const char *title = "Out of range";
+
+   if( world_sfd.active_route_board < af_arrcount( &world->ent_route ) )
+   {
+      board = &world->leaderboard_cache[ world_sfd.active_route_board ];
+      ent_route *route = af_arritm( &world->ent_route, world_sfd.active_route_board );
+      title = af_str( &world->meta.af, route->pstr_name );
+   }
+
    for( u32 i=0; i<13; i++ )
       sfd_clear(i);
 
    sfd_encode( (v2i){0,0}, title, k_world_sfd_left );
 
-   if( !board ){
+   if( !board )
+   {
       sfd_encode( (v2i){-1,4}, "Error out of range", k_world_sfd_center );
       return;
    }
 
-   if( !network_connected() ){
+   if( !network_connected() )
+   {
       sfd_encode( (v2i){-1,0}, "Offline", k_world_sfd_right );
       return;
    }
 
-   if( board->status == k_request_status_not_found ){
+   if( board->status == k_request_status_not_found )
+   {
       sfd_encode( (v2i){-1,4}, "No records", k_world_sfd_center );
       return;
    }
 
-   if( board->status != k_request_status_ok ){
+   if( board->status != k_request_status_ok )
+   {
       char buf[32];
       vg_str s;
       vg_strnull( &s, buf, 32 );
@@ -116,17 +136,21 @@ void world_sfd_compile_scores( struct leaderboard_cache *board, const char *titl
 
    const char *alias = "rows";
 
+#if 0
    if( world_sfd.view_weekly ){
       alias = "rows_weekly";
       sfd_encode( (v2i){-1,0}, "Weekly", k_world_sfd_right );
    }
    else {
-      sfd_encode( (v2i){-1,0}, "All-Time", k_world_sfd_right );
    }
+#endif
+   sfd_encode( (v2i){-1,0}, "All-Time", k_world_sfd_right );
 
    u32 l = 1;
-   if( vg_msg_seekframe( &body, alias ) ){
-      while( vg_msg_seekframe( &body, NULL ) ){
+   if( vg_msg_seekframe( &body, alias ) )
+   {
+      while( vg_msg_seekframe( &body, NULL ) )
+      {
          /* name */
          const char *username = vg_msg_getkvstr( &body, "username" );
 
@@ -147,7 +171,7 @@ void world_sfd_compile_scores( struct leaderboard_cache *board, const char *titl
          vg_strnull( &str, buf, 100 );
          
          u32 centiseconds;
-         vg_msg_getkvintg( &body, "time", k_vg_msg_i32, &centiseconds, NULL );
+         vg_msg_getkvintg( &body, "time", k_vg_msg_u32, &centiseconds, NULL );
 
          i32 seconds      = centiseconds / 100,
              minutes      = seconds / 60;
@@ -167,39 +191,26 @@ void world_sfd_compile_scores( struct leaderboard_cache *board, const char *titl
          vg_msg_skip_frame( &body );
       }
    }
-   else {
+   else 
+   {
       sfd_encode( (v2i){-1,4}, "No records", k_world_sfd_center );
    }
 }
 
-void world_sfd_compile_active_scores(void)
-{
-   world_instance *world = &_world.main;
-   
-   struct leaderboard_cache *board = NULL;
-   const char *name = "Out of range";
-
-   if( world_sfd.active_route_board < af_arrcount( &world->ent_route ) ){
-      board = &world->leaderboard_cache[ world_sfd.active_route_board ];
-      ent_route *route = af_arritm( &world->ent_route, 
-                                     world_sfd.active_route_board );
-      name = af_str( &world->meta.af, route->pstr_name );
-   }
-         
-   world_sfd_compile_scores( board, name );
-}
-
 void world_sfd_update( world_instance *world, v3f pos )
 {
-   if( af_arrcount( &world->ent_route ) ){
+   if( af_arrcount( &world->ent_route ) )
+   {
       u32 closest = 0;
       float min_dist = INFINITY;
 
-      for( u32 i=0; i<af_arrcount( &world->ent_route ); i++ ){
+      for( u32 i=0; i<af_arrcount( &world->ent_route ); i++ )
+      {
          ent_route *route = af_arritm( &world->ent_route, i );
          float dist = v3_dist2( route->board_transform[3], pos );
 
-         if( dist < min_dist ){
+         if( dist < min_dist )
+         {
             min_dist = dist;
             closest = i;
          }
@@ -228,14 +239,14 @@ void world_sfd_update( world_instance *world, v3f pos )
       }
 
       /* compile board text if we changed. */
-      if( world_sfd.active_route_board != closest ){
+      if( world_sfd.active_route_board != closest )
          world_sfd_compile_active_scores();
-      }
 
       world_sfd.active_route_board = closest;
    }
 
-   for( int i=0; i<world_sfd.w*world_sfd.h; i++ ){
+   for( int i=0; i<world_sfd.w*world_sfd.h; i++ )
+   {
       float *target = &world_sfd.buffer[i*2+0],
             *cur =    &world_sfd.buffer[i*2+1];
       
index e79fbbca30430fd5c190f5397b15546da1f12a48..b755647b1711a1e87a5d677c2318550bb3844330 100644 (file)
@@ -6,7 +6,8 @@
 #include "world_routes.h"
 #include "scene.h"
 
-struct world_sfd{
+struct world_sfd
+{
    GLuint tex_scoretex;
 
    glmesh mesh_base, mesh_display;
@@ -15,10 +16,13 @@ struct world_sfd{
    u32 active_route_board;
    scene_context scene;
 
-   u32 view_weekly;
+   //u32 view_weekly;
+   u32 run_selecteion;
 
    u32 w, h;
    float *buffer;
+
+
 }
 extern world_sfd;
 void world_sfd_init(void);
@@ -32,6 +36,4 @@ enum world_sfd_align {
 void sfd_encode( v2i co, const char *str, enum world_sfd_align align );
 void world_sfd_update( world_instance *world, v3f pos );
 void sfd_render( world_instance *world, vg_camera *cam, m4x3f transform );
-void world_sfd_compile_scores( struct leaderboard_cache *leaderboard,
-                               const char *title );
 void world_sfd_compile_active_scores(void);