Spectate mode
authorhgn <hgodden00@gmail.com>
Sun, 4 May 2025 10:56:05 +0000 (11:56 +0100)
committerhgn <hgodden00@gmail.com>
Sun, 4 May 2025 10:56:05 +0000 (11:56 +0100)
16 files changed:
build.c
src/dbtest.c
src/gameserver.c
src/gameserver_database.c
src/gameserver_requests.c
src/menu.c
src/menu.h
src/network.c
src/network_msg.h
src/player_remote.c
src/player_remote.h
src/replay2.c
src/replay2.h
src/skaterift.c
src/skaterift.h
src/world_load.c

diff --git a/build.c b/build.c
index bdb8aa76c648896b487ab072b3d9e95f2241adb1..6735278a304a11c58b437af8c9da02d236d7feef 100644 (file)
--- a/build.c
+++ b/build.c
@@ -437,6 +437,13 @@ int main( int argc, const char *argv[] )
    _vg_opt_init( argc, argv );
 
    const char *arg;
+
+   if( vg_opt('r', NULL) )
+      vg_test_env.optimization = 3;
+
+   if( vg_long_opt( "no-asan", NULL ) )
+      vg_test_env.debug_asan = 0;
+
    if( vg_long_opt( "release-all", NULL ) )
       s_release_all();
 
@@ -455,12 +462,6 @@ int main( int argc, const char *argv[] )
    if( vg_long_opt( "tools", NULL ) )
       s_compile_tools();
 
-   if( vg_opt('r', NULL) )
-      vg_test_env.optimization = 3;
-
-   if( vg_long_opt( "no-asan", NULL ) )
-      vg_test_env.debug_asan = 0;
-
    if( (arg = vg_long_opt_arg( "strdjb2", NULL )) )
       printf( "vg_strdjb2('%s'): %u\n", arg, vg_strdjb2(arg) );
 
index d0ad269977c7523257d1f11cf4dc2e6dd81b3993..1ae01c0b1c6318da86c7a490ef407e0823502d2d 100644 (file)
@@ -5,6 +5,7 @@
 #include <string.h>
 #include <stddef.h>
 
+//#define DB_PREDICTABLE
 #include "vg/vg_platform.h"
 #include "vg/vg_async2.h"
 #include "vg/vg_log.h"
@@ -52,11 +53,15 @@ int main( int argc, const char *argv[] )
    if( vg_long_opt( "die-safe", "" ) )
       die_in_kinda_safe_place = 1;
 
-   int num_to_add = 2;
+   int num_to_add = 0;
    const char *arg;
    if( (arg = vg_long_opt_arg( "add", "" )) )
       num_to_add = atoi( arg );
 
+   const char *dump_path = NULL;
+   if( (arg = vg_long_opt_arg( "dump", "" )) )
+      dump_path = arg;
+
    if( !_vg_opt_check() )
       exit(0);
 
@@ -91,7 +96,7 @@ int main( int argc, const char *argv[] )
       };
       vg_db_write( &db, entry_addr, &entry, sizeof(entry) );
 
-      if( die_mid_transaction )
+      if( die_mid_transaction && i==(num_to_add-1) )
       {
          vg_error( "Dieing mid transaction\n" );
          exit(1);
@@ -115,6 +120,10 @@ int main( int argc, const char *argv[] )
    }
 #endif
 
+   FILE *fdump = NULL;
+   if( dump_path )
+      fdump = fopen( dump_path, "w" );
+
    vg_db_skipper_iter_start( &db, &ctx );
    u16 item_index;
    while( vg_db_skipper_iter( &db, &ctx, &item_index ) )
@@ -123,8 +132,14 @@ int main( int argc, const char *argv[] )
       struct entry thingy;
       vg_db_read( &db, item_address, &thingy, sizeof(thingy) );
       vg_info( "Steamid: %lx\n", thingy.steamid );
+
+      if( fdump )
+         fprintf( fdump, "%lx\n", thingy.steamid );
    }
 
+   if( fdump )
+      fclose( fdump );
+
    if( die_in_kinda_safe_place )
    {
       vg_error( "Dieing in kinda safe place\n" );
index edc2adebe65663da42a4ed31faf34d35ec7cbd78..169608744adcadbf22344a2668670376db9d413d 100644 (file)
@@ -477,8 +477,6 @@ static void gameserver_update_knowledge_table( int client0, int client1, int cle
    {
       if( gameserver_item_eq(&c0->items[k_netmsg_playeritem_world0], &c1->items[k_netmsg_playeritem_world0]))
          flags |= CLIENT_KNOWLEDGE_SAME_WORLD0;
-      if( gameserver_item_eq(&c0->items[k_netmsg_playeritem_world1], &c1->items[k_netmsg_playeritem_world1]))
-         flags |= CLIENT_KNOWLEDGE_SAME_WORLD1;
    }
 
    _gameserver.client_knowledge_mask[idx] = flags;
@@ -597,7 +595,6 @@ static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg )
                (const char *[]){[k_netmsg_playeritem_board]="board",
                                 [k_netmsg_playeritem_player]="player",
                                 [k_netmsg_playeritem_world0]="world0",
-                                [k_netmsg_playeritem_world1]="world1"
                }[item->type_index], item->uid );
 
       gameserver_update_all_knowledge( client_id, 0 );
index b12130c1a02a729a3fd178a62eea0b35cef6e361..a9f62820ddef1d52ae094ab5174ae72c5c81318f 100644 (file)
@@ -31,7 +31,7 @@ static void *database_worker_thread(void *_)
 {
    vg_info( "Initializing database\n" );
    vg_db *db = &_gs_db.db;
-   vg_db_open( db, "skaterift.db" );
+   vg_db_open( db, "skaterift.db", "skaterift.db.wal" );
    
    _gs_db.users_tree = db->userdata_address + offsetof(struct skaterift_database, users_tree);
    _gs_db.leaderboards_table = db->userdata_address + offsetof(struct skaterift_database, leaderboards_table);
@@ -125,6 +125,7 @@ u64 db_leaderboard_address( const char *uid )
 
       u16 table_id = vg_db_dumb_table_count( &_gs_db.db, index_table_address ) -1;
       vg_db_skipper_placement( &_gs_db.db, &uid_ctx, table_id, &lc );
+      vg_db_checkpoint( &_gs_db.db );
       return new_address;
    }
    else vg_fatal_error( "Out of tables!\n" );
@@ -206,6 +207,7 @@ bool db_writeusertime( char uid[DB_TABLE_UID_MAX], u64 steamid, u32 centiseconds
    entry.centiseconds = centiseconds;
    entry.none0 = 0;
    vg_db_write( &_gs_db.db, entry_address, &entry, sizeof(entry) );
+   vg_db_checkpoint( &_gs_db.db );
    return 1;
 }
 
@@ -253,6 +255,7 @@ static void task_set_username( vg_async_task *task )
    memset( profile.name, 0, sizeof(profile.name) );
    vg_strncpy( info->name, profile.name, sizeof(profile.name), k_strncpy_always_add_null );
    vg_db_write( &_gs_db.db, user_address, &profile, sizeof(profile) );
+   vg_db_checkpoint( &_gs_db.db );
 }
 
 void db_action_set_username( u64 steamid, const char *username )
index c8ee8f257aa1f8f2f1ff0f1b0a995bc4537883db..50bf9ee7ad902ee6c09d59386b22bf93d01a750b 100644 (file)
@@ -281,6 +281,7 @@ static void task_request_run( vg_async_task *task )
                   char cc[4];
                   vg_strncpy( set_cc, cc, 4, k_strncpy_always_add_null );
                   vg_db_write( &_gs_db.db, user_address+offsetof(struct skaterift_profile, cc), cc, 4 );
+                  vg_db_checkpoint( &_gs_db.db );
                }
             }
 
index 6c3dff89eb3a2b3598ecffad74bdb159ffc8c44f..45b81ef6504ac8083acd0be880eced4989c4a02b 100644 (file)
@@ -738,6 +738,7 @@ void menu_gui( ui_context *ctx )
 
       goto menu_draw;
    }
+/* PAGE impromptu */
    else if( menu.page == k_menu_page_impromptu_guide )
    {
       ui_rect panel = { 0,0, 600, 400+240 },
@@ -784,6 +785,7 @@ void menu_gui( ui_context *ctx )
 
       goto menu_draw;
    }
+/* PAGE quick */
    else if( menu.page == k_menu_page_quick )
    {
       ui_px pad = 64;
@@ -862,15 +864,90 @@ void menu_gui( ui_context *ctx )
 
       if( menu_button( ctx, panel, R == 2, allow, "Spectate" ) )
       {
+         u32 count = 0;
+         for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ )
+         {
+            struct network_player *player = &netplayers.list[i];
+            bool able_to_spec = 0;
+            if( player->active && player->same_world )
+               able_to_spec = 1;
+            player->flag_spectate = able_to_spec;
+            if( able_to_spec )
+               count ++;
+         }
+         netplayers.spectate_count = count;
+         menu_open( k_menu_page_spectate );
+      }
+
+      if( button_down( k_srbind_mquick ) || button_down( k_srbind_mback ) )
+      {
+         vg_audio_lock();
+         vg_audio_oneshot( &audio_ui[3], 1.0f, 0.0f, 0, 0 );
+         vg_audio_unlock();
          menu_close();
       }
 
+      goto menu_draw;
+   }
+/* PAGE spectate */
+   else if( menu.page == k_menu_page_spectate )
+   {
+      ui_px pad = 64;
+      ui_rect panel = { 24, pad, 300, vg.window_y-(pad*2) };
+      ui_fill( ctx, panel, ui_opacity( GUI_COL_DARK, 0.35f ) );
+      ui_outline( ctx, panel, 1, GUI_COL_NORM, 0 );
+      ui_rect_pad( panel, (ui_px[]){8,8} );
+
+      ui_rect title;
+      ctx->font = &vgf_default_title;
+      ui_split( panel, k_ui_axis_h, 28*2, 0, title, panel );
+      ui_text( ctx, title, "Spectate", 1, k_ui_align_middle_center, 0 );
+
+      ctx->font = &vgf_default_large;
+
+      if( !network_connected() )
+      {
+         ui_text( ctx, panel, "Offline", 1, k_ui_align_middle_center, 0 );
+         goto menu_draw;
+      }
+
+      i32 R = menu_nav( &menu.spectate_row, mv, netplayers.spectate_count );
+
+      u32 count = 0;
+      for( u32 i=0; i<NETWORK_MAX_PLAYERS; i ++ )
+      {
+         struct network_player *player = &netplayers.list[i];
+         if( player->flag_spectate )
+         {
+            bool clickable = 0;
+            if( player->active && player->same_world )
+               clickable = 1;
+
+            if( menu_button( ctx, panel, R == count, clickable, player->username ) )
+            {
+               vg_info( "Starting spectate for: %s\n", player->username );
+               skaterift.activity = k_skaterift_spectate;
+               netplayers.spectate_index = i;
+               localplayer.immobile = 1;
+               vg_camera_copy( &localplayer.cam, &netplayers.spectate_camera );
+
+               gui_helper_reset( k_gui_helper_mode_black_bars );
+               vg_str text;
+               if( gui_new_helper( input_button_list[k_srbind_mback], &text ))
+                  vg_strcat( &text, "Stop spectating" );
+            }
+
+            count ++;
+         }
+      }
+
       if( button_down( k_srbind_mquick ) || button_down( k_srbind_mback ) )
       {
          vg_audio_lock();
          vg_audio_oneshot( &audio_ui[3], 1.0f, 0.0f, 0, 0 );
          vg_audio_unlock();
          menu_close();
+         menu_open( k_menu_page_quick );
       }
 
       goto menu_draw;
index 209a83598f0d09361ec7eff6ae0b9eb2bdeb5cf2..8843bf9141ab88d1299cd6235714afe83e950546 100644 (file)
@@ -16,7 +16,8 @@ enum menu_page
    k_menu_page_credits,
    k_menu_page_help,
    k_menu_page_impromptu_guide,
-   k_menu_page_quick
+   k_menu_page_quick,
+   k_menu_page_spectate,
 };
 
 enum menu_main_subpage
@@ -41,7 +42,8 @@ struct global_menu
        guide_sel,
        prem_row,
        prof_row,
-       quick_row;
+       quick_row,
+       spectate_row;
    f32 mouse_dist;  /* used for waking up mouse */
 
    f32 repeater;
index 2c5b81a5cf59b881fc26bcfd0c77d5df019ef750..e251b9a93516c456c3aa1bb51fd3286a149c132e 100644 (file)
@@ -147,7 +147,7 @@ void network_send_item( enum netmsg_playeritem_type type )
    item->type_index = type;
    item->client = 0;
 
-   if( (type == k_netmsg_playeritem_world0) || (type == k_netmsg_playeritem_world1) )
+   if( type == k_netmsg_playeritem_world0 )
    {
       addon_uid( _world.main.addon_id, item->uid );
    }
@@ -399,9 +399,7 @@ static void network_sign_on_complete(void)
    /* send our init info */
    network_send_username();
    for( u32 i=0; i<k_netmsg_playeritem_max; i ++ )
-   {
       network_send_item(i);
-   }
    network_send_region();
 }
 
index 1f2d64531b30de13049bd7d8a6d0254e90a69779..98161bbc9ee591ccda468d8d3d3305bf1f9f1139 100644 (file)
@@ -94,7 +94,6 @@ enum netmsg_playeritem_type {
    k_netmsg_playeritem_board = 0,
    k_netmsg_playeritem_player,
    k_netmsg_playeritem_world0,
-   k_netmsg_playeritem_world1,
    k_netmsg_playeritem_max
 };
 
index f798125bf97fafaf2ac1c11152e285059970a202..08f2df08e82792e5e7b6da024f9973e815157976 100644 (file)
@@ -15,14 +15,26 @@ struct global_netplayers netplayers;
 
 static i32 k_show_own_name = 0;
 
-static void player_remote_clear( struct network_player *player )
+static void player_remote_clear( u32 player_index )
 {
+   struct network_player *player = &netplayers.list[ player_index ];
    addon_cache_unwatch( k_addon_type_player, player->playermodel_view_slot );
    addon_cache_unwatch( k_addon_type_board, player->board_view_slot );
 
+   bool wanted_to_spec_maybe = player->flag_spectate;
+
    memset( player, 0, sizeof(*player) );
-   strcpy( player->username, "unknown" );
+   strcpy( player->username, "disconnected" );
    player->subsystem = k_player_subsystem_invalid;
+   player->flag_spectate = wanted_to_spec_maybe;
+
+   if( skaterift.activity == k_skaterift_spectate )
+   {
+      if( player_index == netplayers.spectate_index )
+      {
+         skaterift.activity = k_skaterift_default;
+      }
+   }
 }
 
 static void relink_remote_player_worlds( u32 client_id )
@@ -35,6 +47,8 @@ static void relink_remote_player_worlds( u32 client_id )
    addon_reg *current_world_reg = addon_details( _world.main.addon_id );
    if( addon_alias_eq( &q, &current_world_reg->alias ) )
       player->same_world = 1;
+   else
+      player->same_world = 0;
 }
 
 /* 
@@ -45,6 +59,9 @@ static void relink_remote_player_worlds( u32 client_id )
  */
 void relink_all_remote_player_worlds(void)
 {
+   if( !network_connected() )
+      return; 
+
    for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i++ )
    {
       struct network_player *player = &netplayers.list[i];
@@ -60,7 +77,7 @@ void player_remote_update_friendflags( struct network_player *remote )
    remote->isblocked = SteamAPI_ISteamFriends_HasFriend( hSteamFriends, remote->steamid, k_EFriendFlagBlocked );
 }
 
-void decode_playerframe( netmsg_playerframe *frame, u32 data_length, struct interp_frame *dest, v2f out_angles, 
+void decode_playerframe( netmsg_playerframe *frame, u32 data_length, struct interp_frame *dest,
                          struct net_sfx *sfx_buffer, u32 *inout_sfx_buffer_len )
 {
    dest->active = 1;
@@ -76,10 +93,9 @@ void decode_playerframe( netmsg_playerframe *frame, u32 data_length, struct inte
    };
 
    /* camera */
-   v2f angles = {0,0};
-   bitpack_qv2f( &ctx, 8, 0.0f, 1.0f, angles );
-   if( out_angles )
-      v2_muls( angles, VG_TAUf, out_angles );
+   dest->angles[2] = 0.0f;
+   bitpack_qv2f( &ctx, 8, 0.0f, 1.0f, dest->angles );
+   v2_muls( dest->angles, VG_TAUf, dest->angles );
    
    /* animation 
     * -------------------------------------------------------------*/
@@ -133,13 +149,16 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
 {
    netmsg_blank *tmp = msg->m_pData;
 
-   if( tmp->inetmsg_id == k_inetmsg_playerjoin ){
+   if( tmp->inetmsg_id == k_inetmsg_playerjoin )
+   {
       netmsg_playerjoin *playerjoin = msg->m_pData;
-      if( !packet_minsize( msg, sizeof(*playerjoin) )) return;
+      if( !packet_minsize( msg, sizeof(*playerjoin) )) 
+         return;
 
-      if( playerjoin->index < VG_ARRAY_LEN(netplayers.list) ){
+      if( playerjoin->index < VG_ARRAY_LEN(netplayers.list) )
+      {
          struct network_player *player = &netplayers.list[ playerjoin->index ];
-         player_remote_clear( player );
+         player_remote_clear( playerjoin->index );
          player->active = 1;
          player->steamid = playerjoin->steamid;
          player_remote_update_friendflags( player );
@@ -150,47 +169,43 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
 
          struct interp_buffer *buf = &netplayers.interp_data[playerjoin->index];
          buf->t = -99999999.9;
-         for( u32 i=0; i<VG_ARRAY_LEN(buf->frames); i ++ ){
+         for( u32 i=0; i<VG_ARRAY_LEN(buf->frames); i ++ )
             buf->frames[i].active = 0;
-         }
-
-         vg_info( "#%u joined friend: %d, blocked: %d\n", 
-                  playerjoin->index, player->isfriend, player->isblocked );
+         vg_info( "#%u joined friend: %d, blocked: %d\n", playerjoin->index, player->isfriend, player->isblocked );
       }
-      else {
+      else
          vg_error( "inetmsg_playerjoin: player index out of range\n" );
-      }
    }
-   else if( tmp->inetmsg_id == k_inetmsg_playerleave ){
+   else if( tmp->inetmsg_id == k_inetmsg_playerleave )
+   {
       netmsg_playerleave *playerleave = msg->m_pData;
-      if( !packet_minsize( msg, sizeof(*playerleave) )) return;
+      if( !packet_minsize( msg, sizeof(*playerleave) )) 
+         return;
       
-      if( playerleave->index < VG_ARRAY_LEN(netplayers.list) ){
+      if( playerleave->index < VG_ARRAY_LEN(netplayers.list) )
+      {
          struct network_player *player = &netplayers.list[ playerleave->index ];
-         player_remote_clear( player );
+         player_remote_clear( playerleave->index );
          player->active = 0;
          vg_info( "player leave (%d)\n", playerleave->index );
       }
-      else {
+      else 
          vg_error( "inetmsg_playerleave: player index out of range\n" );
-      }
    }
-   else if( tmp->inetmsg_id == k_inetmsg_playerusername ){
+   else if( tmp->inetmsg_id == k_inetmsg_playerusername )
+   {
       netmsg_playerusername *update = msg->m_pData;
-      if( !packet_minsize( msg, sizeof(*update) )) return;
+      if( !packet_minsize( msg, sizeof(*update) )) 
+         return;
 
-      if( update->index < VG_ARRAY_LEN(netplayers.list) ){
+      if( update->index < VG_ARRAY_LEN(netplayers.list) )
+      {
          struct network_player *player = &netplayers.list[ update->index ];
-
-         network_msgstring( update->name, msg->m_cbSize, sizeof(*update),
-                            player->username, sizeof(player->username) );
-
-         vg_info( "#%u changed username to: %s\n", 
-                  update->index, player->username );
+         network_msgstring( update->name, msg->m_cbSize, sizeof(*update), player->username, sizeof(player->username) );
+         vg_info( "#%u changed username to: %s\n", update->index, player->username );
       }
-      else {
+      else
          vg_error( "inetmsg_playerleave: player index out of range\n" );
-      }
    }
    else if( tmp->inetmsg_id == k_inetmsg_playerframe )
    {
@@ -236,6 +251,7 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
          }
       }
 
+      // FIXME: Duplicated code?
       dest->active = 1;
       dest->subsystem = frame->subsystem;
       dest->flags = frame->flags;
@@ -248,8 +264,9 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
       };
 
       /* camera */
-      v2f _null_v2f = {0,0};
-      bitpack_qv2f( &ctx, 8, 0.0f, 1.0f, _null_v2f );
+      dest->angles[2] = 0.0f;
+      bitpack_qv2f( &ctx, 8, 0.0f, 1.0f, dest->angles );
+      v2_muls( dest->angles, VG_TAUf, dest->angles );
       
       /* animation 
        * -------------------------------------------------------------*/
@@ -305,9 +322,7 @@ 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) )
-      {
          player_glide_remote_animator_exchange( &ctx, &dest->data_glider );
-      }
 
       // FIXME: these frames might be out of order! Not good to set this
       player->subsystem = frame->subsystem;
@@ -336,7 +351,6 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
                (const char *[]){[k_netmsg_playeritem_board]="board",
                                 [k_netmsg_playeritem_player]="player",
                                 [k_netmsg_playeritem_world0]="world0",
-                                [k_netmsg_playeritem_world1]="world1"
                }[item->type_index], item->uid );
 
       struct network_player *player = &netplayers.list[ item->client ];
@@ -353,8 +367,7 @@ void player_remote_rx_200_300( SteamNetworkingMessage_t *msg )
          addon_cache_unwatch( k_addon_type_player, player->playermodel_view_slot );
          player->playermodel_view_slot = addon_cache_create_viewer_from_uid( k_addon_type_player, uid );
       }
-      else if( (item->type_index == k_netmsg_playeritem_world0) ||
-               (item->type_index == k_netmsg_playeritem_world1) )
+      else if( item->type_index == k_netmsg_playeritem_world0 )
       {
          relink_remote_player_worlds( item->client );
       }
@@ -470,29 +483,29 @@ static void remote_player_effect( struct network_player *player,
 /*
  * write the remote players final_mtx 
  */
-void pose_remote_player( f64 pose_time, struct interp_frame *f0, struct interp_frame *f1, struct player_board *board,
+void pose_remote_player( f64 pose_time, struct interp_frame *i0, struct interp_frame *i1, struct player_board *board,
                          
                          m4x3f *out_final_mtx, v3f *out_glider_mtx,
                          struct player_board_pose *out_board_pose, 
                          struct player_effects_data *out_effects, 
-                         bool *out_render_glider )
+                         bool *out_render_glider, vg_camera *out_camera )
 {
    struct skeleton *sk = &localplayer.skeleton;
-   struct player_subsystem_interface *sys0 = player_subsystems[f0->subsystem],
+   struct player_subsystem_interface *sys0 = player_subsystems[i0->subsystem],
                                      *sys1 = NULL;
 
    player_pose pose0, pose1, posed;
-   sys0->pose( &f0->data, &pose0 );
+   sys0->pose( &i0->data, &pose0 );
    f32 t = 0.0f;
-   if( f1 )
+   if( i1 )
    {
-      t = (pose_time - f0->timestamp) / (f1->timestamp - f0->timestamp);
+      t = (pose_time - i0->timestamp) / (i1->timestamp - i0->timestamp);
       t = vg_clampf( t, 0.0f, 1.0f );
 
-      sys1 = player_subsystems[f1->subsystem];
-      sys1->pose( &f1->data, &pose1 );
+      sys1 = player_subsystems[i1->subsystem];
+      sys1->pose( &i1->data, &pose1 );
 
-      u16 bounds = f0->boundary_hash^f1->boundary_hash;
+      u16 bounds = i0->boundary_hash^i1->boundary_hash;
       if( bounds & NETMSG_BOUNDARY_BIT )
          t = 1.0f;
       if( bounds & NETMSG_GATE_BOUNDARY_BIT )
@@ -509,12 +522,12 @@ void pose_remote_player( f64 pose_time, struct interp_frame *f0, struct interp_f
       if( t < 0.5f )
       {
          if( sys0->effects ) 
-            sys0->effects( &f0->data, out_final_mtx, board, out_effects );
+            sys0->effects( &i0->data, out_final_mtx, board, out_effects );
       }
       else
       {
          if( sys1->effects ) 
-            sys1->effects( &f1->data, out_final_mtx, board, out_effects );
+            sys1->effects( &i1->data, out_final_mtx, board, out_effects );
       }
       memcpy( out_board_pose, &posed.board, sizeof(struct player_board_pose) );
    }
@@ -523,28 +536,28 @@ void pose_remote_player( f64 pose_time, struct interp_frame *f0, struct interp_f
       effect_blink_apply( &out_effects->blink, &pose0, vg.time_delta );
       apply_full_skeleton_pose( sk, &pose0, out_final_mtx );
       if( sys0->effects ) 
-         sys0->effects( &f0->data, out_final_mtx, board, out_effects );
+         sys0->effects( &i0->data, out_final_mtx, board, out_effects );
       memcpy( out_board_pose, &pose0.board, sizeof(struct player_board_pose) );
    }
 
-   if( f0->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|NETMSG_PLAYERFRAME_GLIDER_ORPHAN) )
+   if( i0->flags & (NETMSG_PLAYERFRAME_HAVE_GLIDER|NETMSG_PLAYERFRAME_GLIDER_ORPHAN) )
    {
       *out_render_glider = 1;
       v3f co;
       v4f q;
       f32 s;
 
-      if( f1 )
+      if( i1 )
       {
-         v3_lerp( f0->data_glider.root_co,f1->data_glider.root_co, t, co );
-         q_nlerp( f0->data_glider.root_q, f1->data_glider.root_q,  t, q );
-         s = vg_lerpf( f0->data_glider.s, f1->data_glider.s, t );
+         v3_lerp( i0->data_glider.root_co,i1->data_glider.root_co, t, co );
+         q_nlerp( i0->data_glider.root_q, i1->data_glider.root_q,  t, q );
+         s = vg_lerpf( i0->data_glider.s, i1->data_glider.s, t );
       }
       else 
       {
-         v3_copy( f0->data_glider.root_co, co );
-         v4_copy( f0->data_glider.root_q, q );
-         s = f0->data_glider.s;
+         v3_copy( i0->data_glider.root_co, co );
+         v4_copy( i0->data_glider.root_q, q );
+         s = i0->data_glider.s;
       }
 
       q_m3x3( q, out_glider_mtx );
@@ -553,6 +566,42 @@ void pose_remote_player( f64 pose_time, struct interp_frame *f0, struct interp_f
    }
    else
       *out_render_glider = 0;
+
+   if( out_camera )
+   {
+      struct player_cam_controller cc = { .camera_mode = k_cam_thirdperson, .camera_type_blend = 0 };
+      v3f angles;
+      compute_cam_controller_offsets( i0->subsystem, &cc );
+
+      if( i1 ) vg_camera_lerp_angles( i0->angles, i1->angles, t, angles );
+      else     v3_copy( i0->angles, angles );
+
+      /* position */
+      v3f fpv_pos, fpv_offset;
+      m4x3_mulv( out_final_mtx[ localplayer.id_head-1 ], cc.fpv_viewpoint, fpv_pos );
+      m3x3_mulv( out_final_mtx[0], cc.fpv_offset, fpv_offset ); // NOTE: [0] could be wrong (was rb.to_world)
+      v3_add( fpv_offset, fpv_pos, fpv_pos );
+
+      /* origin */
+      v3f tpv_origin, tpv_offset, tpv_pos;
+      m4x3_mulv( out_final_mtx[0], cc.tpv_offset, tpv_origin );
+
+      /* offset */
+      v3f camera_follow_dir = 
+         { -sinf( angles[0] ) * cosf( angles[1] ),
+            sinf( angles[1] ),
+            cosf( angles[0] ) * cosf( angles[1] ) };
+      v3_muls( camera_follow_dir, 1.8f, tpv_offset );
+      //v3_muladds( tpv_offset, cc.cam_velocity_smooth, -0.025f, tpv_offset );
+
+      v3_add( tpv_origin, tpv_offset, tpv_pos );
+      v3_lerp( tpv_pos, fpv_pos, cc.camera_type_blend, out_camera->pos );
+      v3_copy( angles, out_camera->angles );
+
+      f32 fov_skate = vg_lerpf( 97.0f, 135.0f, k_fov ),
+          fov_walk  = vg_lerpf( 90.0f, 110.0f, k_fov );
+      out_camera->fov = vg_lerpf( fov_walk, fov_skate, cc.camera_type_blend );
+   }
 }
 
 /* 
@@ -598,6 +647,13 @@ void animate_remote_player( u32 index )
       }
    }
 
+   vg_camera *out_camera = NULL;
+   if( skaterift.activity == k_skaterift_spectate )
+   {
+      if( index == netplayers.spectate_index )
+         out_camera = &netplayers.spectate_camera;
+   }
+
    struct network_player *player = &netplayers.list[ index ];
    struct skeleton *sk = &localplayer.skeleton;
    m4x3f *final_mtx = &netplayers.final_mtx[ sk->bone_count*index ];
@@ -609,7 +665,8 @@ void animate_remote_player( u32 index )
    
    if( minframe && maxframe )
    {
-      pose_remote_player( buf->t, minframe, maxframe, board, final_mtx, glider_mtx, board_pose, effects, glider_flag );
+      pose_remote_player( buf->t, minframe, maxframe, board, final_mtx, glider_mtx, board_pose, effects, glider_flag,
+                           out_camera );
       buf->t += vg.time_frame_delta;
    }
    else 
@@ -617,7 +674,8 @@ void animate_remote_player( u32 index )
       buf->t = abs_max_time - 0.25;
 
       if( abs_max_frame )
-         pose_remote_player( buf->t, abs_max_frame, NULL, board, final_mtx, glider_mtx, board_pose, effects, glider_flag );
+         pose_remote_player( buf->t, abs_max_frame, NULL, board, final_mtx, glider_mtx, board_pose, effects, 
+                              glider_flag, out_camera );
       else 
          return;
    }
@@ -1062,8 +1120,8 @@ void remote_players_chat_imgui( ui_context *ctx )
       }
       else 
       {
-         if( (skaterift.activity == k_skaterift_default) && 
-               button_down( k_srbind_chat ) ){
+         if( (skaterift.activity == k_skaterift_default) && button_down( k_srbind_chat ) )
+         {
             netplayers.chatting = 1;
             netplayers.chat_buffer[0] = '\0';
             srinput.state = k_input_state_pause;
@@ -1150,8 +1208,7 @@ static void cb_network_view( ui_context *ctx, ui_rect rect,
    {
       ui_info( ctx, rect, "#-1: localplayer" );
       
-      snprintf( buf, 512, "U%.3f/D%.3fkbs", 
-                netplayers.up_kbs, netplayers.down_kbs );
+      snprintf( buf, 512, "U%.3f/D%.3fkbs", netplayers.up_kbs, netplayers.down_kbs );
       ui_info( ctx, rect, buf );
 
       for( u32 i=0; i<VG_ARRAY_LEN(netplayers.list); i++ )
@@ -1162,11 +1219,9 @@ static void cb_network_view( ui_context *ctx, ui_rect rect,
             const char *sysname = "invalid";
 
             if( player->subsystem < k_player_subsystem_max )
-            {
                sysname = player_subsystems[ player->subsystem ]->name;
-            }
-            snprintf( buf, 512, "#%u: %s [%s] D%.1fkbs", 
-                      i, player->username, sysname, player->down_kbs );
+
+            snprintf( buf, 512, "#%u: %s [%s] D%.1fkbs", i, player->username, sysname, player->down_kbs );
             ui_info( ctx, rect, buf );
          }
       }
@@ -1197,3 +1252,19 @@ void remote_players_init(void)
       netplayers.sfx_queue[i].system = k_player_subsystem_invalid;
 }
 
+void _network_get_spectate_cam( vg_camera *cam )
+{
+   if( !network_connected() )
+      return;
+
+   struct network_player *player = &netplayers.list[ netplayers.spectate_index ];
+   if( !player->active || !player->same_world )
+   {
+      gui_helper_reset( k_gui_helper_mode_clear );
+      localplayer.immobile = 0;
+      skaterift.activity = k_skaterift_default;
+      return;
+   }
+
+   vg_camera_copy( &netplayers.spectate_camera, cam );
+}
index ecbc03346bfaad89c2ca3f557cb615cd92eddd9b..0d6a7def2c9afd1f9f413fbcba53d7345f7f6ad2 100644 (file)
@@ -23,6 +23,8 @@ struct global_netplayers
       u16 board_view_slot, playermodel_view_slot;
       enum player_subsystem subsystem;
 
+      bool flag_spectate;
+
       bool same_world;
       u32 location_pstr;
 
@@ -44,16 +46,21 @@ struct global_netplayers
       bool render_glider;
    }
    list[ NETWORK_MAX_PLAYERS ];
+   u32 spectate_count;
+   u32 spectate_index;
+   vg_camera spectate_camera;
 
    struct interp_buffer {
       /* collect the most recent 6 frames of animation data */
-      struct interp_frame {
+      struct interp_frame 
+      {
          int active;
          f64 timestamp;
          enum player_subsystem subsystem;
 
          u8 flags;
          u16 boundary_hash;
+         v3f angles;
 
          union interp_animdata {
             /* these aren't accessed directly, just used to take the 
@@ -68,7 +75,6 @@ struct global_netplayers
          struct remote_glider_animator data_glider;
       }
       frames[ NETWORK_BUFFERFRAMES ];
-
       f64 t;
    }
    interp_data[ NETWORK_MAX_PLAYERS ];
@@ -107,7 +113,8 @@ void pose_remote_player( f64 pose_time, struct interp_frame *f0, struct interp_f
                          m4x3f *out_final_mtx, v3f *out_glider_mtx,
                          struct player_board_pose *out_board_pose, 
                          struct player_effects_data *out_effects, 
-                         bool *out_render_glider );
+                         bool *out_render_glider, vg_camera *out_camera );
 
-void decode_playerframe( netmsg_playerframe *frame, u32 data_length, struct interp_frame *dest, v2f out_angles, 
+void decode_playerframe( netmsg_playerframe *frame, u32 data_length, struct interp_frame *dest,
                          struct net_sfx *sfx_buffer, u32 *inout_sfx_buffer_len );
+void _network_get_spectate_cam( vg_camera *cam );
index 8b677303c6d94d4d5f24bdc801b60b340cbaa7f7..1c677cecc0eec85d78988b9c81d029f829f30431 100644 (file)
@@ -475,46 +475,8 @@ void _replay2_pre_update(void)
                              *i1 = &_remote_replay.interp1;
          struct player_cam_controller cc = { .camera_mode = k_cam_thirdperson, .camera_type_blend = 0 };
          compute_cam_controller_offsets( i0->subsystem, &cc );
-         pose_remote_player( frame_time, i0, i1, board, final_mtx, glider_mtx, board_pose, effects, glider_flag );
-
-         f32 t = (frame_time - i0->timestamp) / (i1->timestamp - i0->timestamp);
-             t = vg_clampf( t, 0.0f, 1.0f );
-         u16 bounds = i0->boundary_hash^i1->boundary_hash;
-         if( bounds & NETMSG_BOUNDARY_BIT )
-            t = 1.0f;
-         if( bounds & NETMSG_GATE_BOUNDARY_BIT )
-            t = 1.0f;
-
-         v3f angles;
-         angles[0] = vg_alerpf( _remote_replay.cam0[0], _remote_replay.cam1[0], t );
-         angles[1] = vg_lerpf(  _remote_replay.cam0[1], _remote_replay.cam1[1], t );
-         angles[2] = 0.0f;
-
-         /* position */
-         v3f fpv_pos, fpv_offset;
-         m4x3_mulv( _replay2.final_mtx[ localplayer.id_head-1 ], cc.fpv_viewpoint, fpv_pos );
-         m3x3_mulv( _replay2.final_mtx[0], cc.fpv_offset, fpv_offset ); // NOTE: [0] could be wrong (was rb.to_world)
-         v3_add( fpv_offset, fpv_pos, fpv_pos );
-
-         /* origin */
-         v3f tpv_origin, tpv_offset, tpv_pos;
-         m4x3_mulv( _replay2.final_mtx[0], cc.tpv_offset, tpv_origin );
-
-         /* offset */
-         v3f camera_follow_dir = 
-            { -sinf( angles[0] ) * cosf( angles[1] ),
-               sinf( angles[1] ),
-               cosf( angles[0] ) * cosf( angles[1] ) };
-         v3_muls( camera_follow_dir, 1.8f, tpv_offset );
-         //v3_muladds( tpv_offset, cc.cam_velocity_smooth, -0.025f, tpv_offset );
-
-         v3_add( tpv_origin, tpv_offset, tpv_pos );
-         v3_lerp( tpv_pos, fpv_pos, cc.camera_type_blend, _replay2.playback_cam.pos );
-         v2_copy( angles, _replay2.playback_cam.angles );
-
-         f32 fov_skate = vg_lerpf( 97.0f, 135.0f, k_fov ),
-             fov_walk  = vg_lerpf( 90.0f, 110.0f, k_fov );
-         _replay2.playback_cam.fov = vg_lerpf( fov_walk, fov_skate, cc.camera_type_blend );
+         pose_remote_player( frame_time, i0, i1, board, final_mtx, glider_mtx, board_pose, effects, glider_flag,
+                              &_replay2.playback_cam );
       }
       else
       {
@@ -674,15 +636,14 @@ void _replay2_decode(void)
    if( _replay2.type == k_replay_type_network )
    {
       u32 s0 = current_frame->net.frame_size - sizeof(netmsg_playerframe);
-      decode_playerframe( &current_frame->net.frame, s0, &_remote_replay.interp0, _remote_replay.cam0, NULL, NULL );
+      decode_playerframe( &current_frame->net.frame, s0, &_remote_replay.interp0, NULL, NULL );
 
       if( next_frame )
       {
          u32 len = VG_ARRAY_LEN( _replay2.sfx_queue );
          replay2_frame *next_frame = vg_queue_data( buffer, next_offset );
          u32 s1 = next_frame->net.frame_size - sizeof(netmsg_playerframe);
-         decode_playerframe( &next_frame->net.frame, s1, &_remote_replay.interp1, _remote_replay.cam1, 
-                             _replay2.sfx_queue, &len );
+         decode_playerframe( &next_frame->net.frame, s1, &_remote_replay.interp1, _replay2.sfx_queue, &len );
          _replay2.sfx_queue_length = len;
       }
       else
index cbf52fe69e614341079a0c1f1133a528a5b5eb61..103420a7e20d4905c06e73c53bcc171ec5d89e68 100644 (file)
@@ -73,7 +73,6 @@ struct _remote_replay
    i64 last_second;
    f64 end_offset, start_offset; /* from the download */
 
-   v2f cam0, cam1;
    struct interp_frame interp0, interp1;
    vg_queue buffer;
 }
index 9ef18ef8ad408e647c52cded4b961f1f194093be..7721707a6deba28cd06759744416dc9993163627 100644 (file)
@@ -225,6 +225,20 @@ void vg_pre_update(void)
    if( skaterift.activity & k_skaterift_replay )
       target = 0;
 
+   if( skaterift.activity == k_skaterift_spectate )
+   {
+      if( button_down( k_srbind_mback ) )
+      {
+         vg_audio_lock();
+         vg_audio_oneshot( &audio_ui[3], 1.0f, 0.0f, 0, 0 );
+         vg_audio_unlock();
+         menu_close();
+         menu_open( k_menu_page_quick );
+         gui_helper_reset( k_gui_helper_mode_clear );
+         localplayer.immobile = 0;
+      }
+   }
+
    world_update( &_world.main, localplayer.rb.co );
    _board_maker_pre_update();
 
@@ -357,6 +371,11 @@ static void skaterift_composite_maincamera(void)
       _replay2_get_camera( &g_render.cam );
    }
 
+   if( skaterift.activity == k_skaterift_spectate )
+   {
+      _network_get_spectate_cam( &g_render.cam );
+   }
+
    g_render.cam.nearz = 0.1f;
    g_render.cam.farz  = 2100.0f;
 
index f54c994a5a525efcdd5c95e5b3caeb1c33c9ea22..292a8950b5d544a589d2aebd51d69dec421f1afe 100644 (file)
@@ -21,6 +21,7 @@ struct skaterift_globals
    enum skaterift_activity {
       k_skaterift_default    = 0x00,  /* regular playing */
       k_skaterift_replay     = 0x01,
+      k_skaterift_spectate   = 0x02,
       k_skaterift_menu       = 0x04,
       k_skaterift_activity_max = 0x8
    }
index 3ae65de703a3682fadbf8b1b70f02818e0d55004..c590c14aba1d809f36b40ca2292546a2fa9d418b 100644 (file)
@@ -378,6 +378,7 @@ void async_worldsave_go( vg_async_task *task )
    _world.load_addon = 0;
    _vg_tower_set_flag( skaterift.sig_world, 1 );
    menu_on_world_change( _world.main.addon_id );
+   relink_all_remote_player_worlds();
 
    vg_audio_lock();
    vg_audio_oneshot( &audio_ui[2], 1.0f, 0.0f, 0, 0 );
@@ -484,6 +485,7 @@ void skaterift_load_world_start( addon_id addon_id, bool preview )
       _world.loader_state = k_world_loader_saving_current;
       _world.event = k_world_event_none;
       player__clear_world_dependent_variables();
+      relink_all_remote_player_worlds();
       vg_loader_set_user_information( "Saving current world" );
    }
    else