From cb8ca6fcee7c5a50d7890c306d4bbf8b79cb109b Mon Sep 17 00:00:00 2001 From: hgn Date: Sun, 4 May 2025 11:56:05 +0100 Subject: [PATCH] Spectate mode --- build.c | 13 +-- src/dbtest.c | 19 +++- src/gameserver.c | 3 - src/gameserver_database.c | 5 +- src/gameserver_requests.c | 1 + src/menu.c | 77 ++++++++++++++ src/menu.h | 6 +- src/network.c | 4 +- src/network_msg.h | 1 - src/player_remote.c | 215 +++++++++++++++++++++++++------------- src/player_remote.h | 15 ++- src/replay2.c | 47 +-------- src/replay2.h | 1 - src/skaterift.c | 19 ++++ src/skaterift.h | 1 + src/world_load.c | 2 + 16 files changed, 291 insertions(+), 138 deletions(-) diff --git a/build.c b/build.c index bdb8aa7..6735278 100644 --- 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) ); diff --git a/src/dbtest.c b/src/dbtest.c index d0ad269..1ae01c0 100644 --- a/src/dbtest.c +++ b/src/dbtest.c @@ -5,6 +5,7 @@ #include #include +//#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" ); diff --git a/src/gameserver.c b/src/gameserver.c index edc2ade..1696087 100644 --- a/src/gameserver.c +++ b/src/gameserver.c @@ -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 ); diff --git a/src/gameserver_database.c b/src/gameserver_database.c index b12130c..a9f6282 100644 --- a/src/gameserver_database.c +++ b/src/gameserver_database.c @@ -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 ) diff --git a/src/gameserver_requests.c b/src/gameserver_requests.c index c8ee8f2..50bf9ee 100644 --- a/src/gameserver_requests.c +++ b/src/gameserver_requests.c @@ -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 ); } } diff --git a/src/menu.c b/src/menu.c index 6c3dff8..45b81ef 100644 --- a/src/menu.c +++ b/src/menu.c @@ -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; iactive && 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; iflag_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; diff --git a/src/menu.h b/src/menu.h index 209a835..8843bf9 100644 --- a/src/menu.h +++ b/src/menu.h @@ -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; diff --git a/src/network.c b/src/network.c index 2c5b81a..e251b9a 100644 --- a/src/network.c +++ b/src/network.c @@ -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; iplayermodel_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, ¤t_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; iisblocked = 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; iframes); i ++ ){ + for( u32 i=0; iframes); 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; isubsystem < 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 ); +} diff --git a/src/player_remote.h b/src/player_remote.h index ecbc033..0d6a7de 100644 --- a/src/player_remote.h +++ b/src/player_remote.h @@ -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 ); diff --git a/src/replay2.c b/src/replay2.c index 8b67730..1c677ce 100644 --- a/src/replay2.c +++ b/src/replay2.c @@ -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( ¤t_frame->net.frame, s0, &_remote_replay.interp0, _remote_replay.cam0, NULL, NULL ); + decode_playerframe( ¤t_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 diff --git a/src/replay2.h b/src/replay2.h index cbf52fe..103420a 100644 --- a/src/replay2.h +++ b/src/replay2.h @@ -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; } diff --git a/src/skaterift.c b/src/skaterift.c index 9ef18ef..7721707 100644 --- a/src/skaterift.c +++ b/src/skaterift.c @@ -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; diff --git a/src/skaterift.h b/src/skaterift.h index f54c994..292a895 100644 --- a/src/skaterift.h +++ b/src/skaterift.h @@ -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 } diff --git a/src/world_load.c b/src/world_load.c index 3ae65de..c590c14 100644 --- a/src/world_load.c +++ b/src/world_load.c @@ -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 -- 2.25.1