#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 )
{
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;
}
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 ) )
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, ¢iseconds, 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();
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 ) )
{
#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);
*/
#define _DEFAULT_SOURCE
+#define VG_SERVER
#include <signal.h>
#include <unistd.h>
#include <time.h>
memset( client, 0, sizeof(struct gameserver_client) );
gameserver_update_all_knowledge( index, 1 );
_gs_requests_client_disconnect( index );
+ _gs_replay_client_disconnect( index );
}
/*
/* 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 );
}
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, ¢iseconds, 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;
int main( int argc, char *argv[] )
{
_gameserver.thread = pthread_self();
-
vg_log_init();
signal( SIGINT, inthandler );
{
THREAD_1;
#ifdef DB_LOG_SQL_STATEMENTS
- vg_low( code );
+ vg_low( "%s\n", code );
#endif
sqlite3_stmt *stmt;
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;
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 )
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 );
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;
{
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 );
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 ) )
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);
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;
}
vg_msg_end_frame( msg );
return k_request_status_ok;
}
-
-
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
/*
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[];
};
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 )
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 );
}
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
#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;
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;
}
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 );
/* 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
{
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" );
}
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, ¢iseconds, NULL );
+ vg_msg_getkvintg( &client_msg, "time", k_vg_msg_i32, ¢iseconds, 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;
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;
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;
SteamNetworkingMessage_t *message;
+ u64 user_steamid;
u64 user_uid;
u32 client_id;
u8 client_request_id;
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 */
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" );
}
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 */
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 ) );
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 );
}
}
.buffer_len = datasize,
.bytes = 0,
};
+
+ /* camera */
+ v2f _null_v2f = {0,0};
+ bitpack_qv2f( &ctx, 8, 0.0f, VG_TAUf, _null_v2f );
/* animation
* -------------------------------------------------------------*/
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 )
/* 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;
}
* -------------------------------------------------------------*/
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 );
}
{
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;
- }
}
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;
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];
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),
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;
}
}
- 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;
}
}
+static void _remote_replay_pre_update(void);
void skaterift_replay_pre_update(void)
{
if( skaterift.activity != k_skaterift_replay )
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 )
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 );
}
struct replay_globals
{
replay_buffer local;
+
replay_frame *resume_target;
f64 resume_begin;
f32 resume_transition;
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;
--- /dev/null
+#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, ¤t_offset );
+ else
+ next = vg_queue_previous( &replay->buffer, current_offset, ¤t_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 );
+ }
+ }
+}
--- /dev/null
+#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 );
--- /dev/null
+#pragma once
+#include "network_msg.h"
#include "ent_script.h"
#include "board_maker.h"
#include "compass.h"
+#include "replay2.h"
struct skaterift_globals skaterift =
{
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" );
}
skaterift_replay_pre_update();
+ _replay2_pre_update();
remote_sfx_pre_update();
v3f listen_co;
{
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;
}
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 );
#include "skaterift_script.c"
#include "board_maker.c"
#include "compass.c"
+#include "replay2.c"
//TODO
//#include "vg/submodules/hashmap.c/hashmap.c"
void render_world_portal_cubemaps(void)
{
- if( vg_loader_availible() )
+ if( vg_loader_availible() ) // HACK?
{
qoi_desc desc =
{
/*
- * 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
#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,
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;
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;
}
}
-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 );
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" );
vg_strnull( &str, buf, 100 );
u32 centiseconds;
- vg_msg_getkvintg( &body, "time", k_vg_msg_i32, ¢iseconds, NULL );
+ vg_msg_getkvintg( &body, "time", k_vg_msg_u32, ¢iseconds, NULL );
i32 seconds = centiseconds / 100,
minutes = seconds / 60;
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;
}
}
/* 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];
#include "world_routes.h"
#include "scene.h"
-struct world_sfd{
+struct world_sfd
+{
GLuint tex_scoretex;
glmesh mesh_base, mesh_display;
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);
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);