#include "vg/vg_store.h"
#include "vg/vg_stdint.h"
+#include "world_info.h"
/*
* Designed to be used across client and server,
typedef struct highscore_record highscore_record;
typedef struct highscore_track_table highscore_track_table;
typedef struct highscore_database highscore_database;
+typedef struct highscore_playerinfo highscore_playerinfo;
#pragma pack(push,1)
+
+struct highscore_playerinfo
+{
+ char nickname[10];
+ u64 playerid;
+
+ union
+ {
+ aatree_pool_node aapn;
+ aatree_node aa_playerid;
+ };
+};
+
struct highscore_record
{
u16 trackid, points, time, reserved0;
u64 playerid;
u32 datetime;
-
- u32 reserved[7];
+ u32 reserved1;
union
{
}
aa;
- struct
- {
- /* TODO pool allocator */
- u32 next, prev;
- }
- pool;
+ aatree_pool_node pool;
};
};
{
highscore_track_table tracks[ 128 ];
- aatree_ptr pool_head;
- u32 reserved[63];
+ aatree_ptr pool_head, playerinfo_head;
+ u32 entry_capacity,
+ playerinfo_capacity, playerinfo_root;
+
+ u32 reserved[59];
};
#pragma pack(pop)
aainfo_points,
aainfo_time,
aainfo_playerid,
- aainfo_datetime;
+ aainfo_datetime,
+ aainfo_playerinfo_playerid,
+ aainfo_playerinfo;
- void *data;
+ void *data, *playerinfo_data;
}
highscore_system;
return pa->playerid < pb->playerid? -1: 1;
}
-static int highscores_init( u32 pool_size )
+static int highscore_cmp_playerinfo_playerid( void *a, void *b )
{
- struct highscore_system *sys = &highscore_system;
+ highscore_playerinfo *pa = a, *pb = b;
+ if( pa->playerid == pb->playerid ) return 0;
+ return pa->playerid < pb->playerid? -1: 1;
+}
- size_t requested_mem = pool_size * sizeof(highscore_record);
- sys->data = malloc( requested_mem );
+static void *highscore_malloc( u32 count, u32 size )
+{
+ size_t requested_mem = size * count;
+ void *data = vg_alloc( requested_mem );
requested_mem /= 1024;
requested_mem /= 1024;
- if( !highscore_system.data )
+ if( !data )
{
- vg_error( "Could not allocated %dmb of memory for database\n",
- requested_mem );
- return 0;
+ vg_error( "Could not allocated %dmb of memory\n", requested_mem );
+ return NULL;
}
else
- vg_success( "Allocated %dmb for database\n", requested_mem );
+ vg_success( "Allocated %dmb for %u records\n", requested_mem, count );
+
+ return data;
+}
+
+static void highscores_free(void)
+{
+ vg_free( highscore_system.data );
+ vg_free( highscore_system.playerinfo_data );
+}
+
+static int highscores_init( u32 pool_size, u32 playerinfo_pool_size )
+{
+ struct highscore_system *sys = &highscore_system;
+
+ sys->data = highscore_malloc( pool_size, sizeof(highscore_record) );
+ if( !sys->data ) return 0;
+
+ sys->playerinfo_data =
+ highscore_malloc( playerinfo_pool_size, sizeof(highscore_playerinfo));
+ if( !sys->playerinfo_data )
+ {
+ vg_free( sys->data );
+ return 0;
+ }
/* This is ugly.. too bad! */
sys->aainfo.base = highscore_system.data;
sys->aainfo_playerid.offset = offsetof(highscore_record,aa.playerid);
sys->aainfo_playerid.p_cmp = highscore_cmp_playerid;
+ sys->aainfo_playerinfo_playerid.base = highscore_system.playerinfo_data;
+ sys->aainfo_playerinfo_playerid.stride = sizeof(highscore_playerinfo);
+ sys->aainfo_playerinfo_playerid.offset =
+ offsetof(highscore_playerinfo,aa_playerid);
+ sys->aainfo_playerinfo_playerid.p_cmp = highscore_cmp_playerinfo_playerid;
+
+ sys->aainfo_playerinfo.base = highscore_system.playerinfo_data;
+ sys->aainfo_playerinfo.stride = sizeof(highscore_playerinfo);
+ sys->aainfo_playerinfo.offset = offsetof(highscore_playerinfo,aapn);
+ sys->aainfo_playerinfo.p_cmp = NULL;
- /* TODO: Load from disk if avalible */
- if( 0 )
+ FILE *fp = fopen( ".aadb", "rb" );
+ if( fp )
{
+ vg_info( "Loading existing database\n" );
+
+ u64 count = fread( &sys->dbheader, sizeof(highscore_database), 1, fp );
+
+ if( count != 1 )
+ {
+ vg_error( "Unexpected EOF reading database header\n" );
+
+ highscores_free();
+ return 0;
+ }
+
+ count = fread( sys->data, sizeof(highscore_record), pool_size, fp );
+ if( count != pool_size )
+ {
+ vg_error( "Unexpected EOF reading database contents;"
+ " %lu records of %u were read\n", count, pool_size );
+
+ highscores_free();
+ return 0;
+ }
+ count = fread( sys->playerinfo_data, sizeof(highscore_playerinfo),
+ playerinfo_pool_size, fp );
+ if( count != playerinfo_pool_size )
+ {
+ vg_error( "Unexpected EOF reading playerinfo contents;"
+ " %lu records of %u were read\n", count,
+ playerinfo_pool_size );
+
+ highscores_free();
+ return 0;
+ }
+
+ fclose( fp );
}
else
{
+ vg_low( "No existing database found (.aadb)\n" );
vg_info( "Initializing database nodes\n" );
+ memset( &sys->dbheader, 0, sizeof(highscore_database) );
+
sys->dbheader.pool_head = aatree_init_pool( &sys->aainfo, pool_size );
+ sys->dbheader.entry_capacity = pool_size;
for( int i=0; i<vg_list_size(sys->dbheader.tracks); i++ )
{
table->root_time = AATREE_PTR_NIL;
table->root_datetime = AATREE_PTR_NIL;
}
+
+ /* Initialize secondary db */
+ sys->dbheader.playerinfo_head = aatree_init_pool(
+ &sys->aainfo_playerinfo,
+ playerinfo_pool_size );
+ sys->dbheader.playerinfo_capacity = playerinfo_pool_size;
+ sys->dbheader.playerinfo_root = AATREE_PTR_NIL;
}
return 1;
}
-static void highscores_free(void)
+static int highscores_serialize_all(void)
{
- free( highscore_system.data );
+ struct highscore_system *sys = &highscore_system;
+ vg_info( "Serializing database\n" );
+
+ FILE *fp = fopen( ".aadb", "wb" );
+
+ if( !fp )
+ {
+ vg_error( "Could not open .aadb\n" );
+ return 0;
+ }
+
+ fwrite( &sys->dbheader, sizeof(highscore_database), 1, fp );
+ fwrite( sys->data, sizeof(highscore_record),
+ sys->dbheader.entry_capacity, fp );
+ fwrite( sys->playerinfo_data, sizeof(highscore_playerinfo),
+ sys->dbheader.playerinfo_capacity, fp );
+
+ fclose( fp );
+ return 1;
+}
+
+static highscore_record *highscore_find_user_record( u64 playerid, u32 trackid )
+{
+ struct highscore_system *sys = &highscore_system;
+
+ highscore_track_table *table = &sys->dbheader.tracks[trackid];
+ highscore_record temp;
+ temp.playerid = playerid;
+
+ aatree_ptr find =
+ aatree_find( &sys->aainfo_playerid, table->root_playerid, &temp );
+
+ if( find == AATREE_PTR_NIL )
+ return NULL;
+
+ return aatree_get_data( &sys->aainfo_playerid, find );
}
static aatree_ptr highscores_push_record( highscore_record *record )
{
struct highscore_system *sys = &highscore_system;
- /* TODO: Verify steam ID */
- vg_log( "Inserting record into database for track %hu\n",record->trackid );
+ vg_low( "Inserting record into database for track %hu\n",record->trackid );
if( record->trackid >= vg_list_size(sys->dbheader.tracks) )
{
if( existing != AATREE_PTR_NIL )
{
- vg_log( "Freeing existing record for player %lu\n", record->playerid );
+ highscore_record *crecord = aatree_get_data( &sys->aainfo_playerid,
+ existing );
+
+ if( crecord->time < record->time ||
+ (crecord->time == record->time && crecord->points > record->points))
+ {
+ vg_low( "Not overwriting better score\n" );
+ return existing;
+ }
+
+ vg_low( "Freeing existing record for player %lu\n", record->playerid );
table->root_playerid = aatree_del( &sys->aainfo_playerid, existing );
table->root_datetime = aatree_del( &sys->aainfo_datetime, existing );
table->root_points = aatree_del( &sys->aainfo_points, existing );
aatree_pool_alloc( &sys->aainfo, &sys->dbheader.pool_head );
if( index == AATREE_PTR_NIL )
+ {
+ vg_error( "Database records are over capacity!\n" );
return index;
+ }
highscore_record *dst = aatree_get_data( &sys->aainfo, index );
memset( dst, 0, sizeof(highscore_record) );
return index;
}
-static void _highscore_showtime( void *data )
+static aatree_ptr highscore_set_user_nickname( u64 steamid, char nick[10] )
{
- highscore_record *record = data;
- printf( "%hu", record->time );
+ char name[11];
+ for( int i=0; i<10; i++ )
+ name[i] = nick[i];
+ name[10] = '\0';
+
+ vg_low( "Updating %lu's nickname -> %s\n", steamid, name );
+
+ struct highscore_system *sys = &highscore_system;
+
+ highscore_playerinfo temp;
+ temp.playerid = steamid;
+
+ aatree_ptr record = aatree_find( &sys->aainfo_playerinfo_playerid,
+ sys->dbheader.playerinfo_root,
+ &temp );
+ highscore_playerinfo *info;
+
+ if( record != AATREE_PTR_NIL )
+ {
+ info = aatree_get_data( &sys->aainfo_playerinfo, record );
+ }
+ else
+ {
+ record = aatree_pool_alloc( &sys->aainfo_playerinfo,
+ &sys->dbheader.playerinfo_head );
+
+ if( record == AATREE_PTR_NIL )
+ {
+ vg_error( "Player info database is over capacity!\n" );
+ return AATREE_PTR_NIL;
+ }
+
+ info = aatree_get_data( &sys->aainfo_playerinfo, record );
+ memset( info, 0, sizeof(highscore_playerinfo) );
+
+ info->playerid = steamid;
+ sys->dbheader.playerinfo_root = aatree_insert(
+ &sys->aainfo_playerinfo_playerid,
+ sys->dbheader.playerinfo_root,
+ record );
+ }
+
+ for( int i=0; i<10; i++ )
+ info->nickname[i] = nick[i];
+
+ return AATREE_PTR_NIL;
+}
+
+/* Get the length of a string, bounded by '\0' or len, whichever is first */
+static int highscore_strlen( const char *str, int len )
+{
+ int str_length;
+ for( str_length=0; str_length<len; str_length++ )
+ if( !str[str_length] )
+ return str_length;
+
+ return str_length;
}
-static void highscores_print_track( u32 trackid, u32 count )
+/* Print the string(max length:len) centered into buf (has width:width) */
+static void highscore_strc( char *buf, const char *str, int len, int width )
{
+ int str_length = highscore_strlen( str, len ),
+ offs = (width-str_length)/2;
+
+ for( int i=0; i<str_length; i++ )
+ {
+ int j=i+offs;
+
+ if( j >= width )
+ return;
+
+ buf[j] = str[i];
+ }
+}
+
+/* Print the string(max length:len) left aligned into buf */
+static void highscore_strl( char *buf, const char *str, int len )
+{
+ for( int i=0; i<len; i++ )
+ {
+ if( !str[i] )
+ return;
+
+ buf[i] = str[i];
+ }
+}
+
+/* Print the string (max length:len) right aligned into buf (has width:width) */
+static void highscore_strr( char *buf, const char *str, int len, int width )
+{
+ int str_length = highscore_strlen( str, len );
+
+ for( int i=0; i<len; i++ )
+ {
+ if( !str[i] )
+ return;
+
+ buf[width-str_length+i] = str[i];
+ }
+}
+
+/* Print integer (padded with: alt), right aligned into buf(width: len) */
+static void highscore_intr( char *buf, int value, int len, char alt )
+{
+ int i=0;
+ while(value)
+ {
+ if( i>=len )
+ return;
+
+ buf[ len-1 - (i ++) ] = '0' + (value % 10);
+ value /= 10;
+ }
+
+ for( ;i<len; i ++ )
+ buf[ len-1 - i ] = alt;
+}
+
+/* Print integer into buffer with max length len */
+static void highscore_intl( char *buf, int value, int len )
+{
+ char temp[32];
+
+ int i=0;
+ while(value)
+ {
+ if( i>=len )
+ break;
+
+ temp[ i ++ ] = '0' + (value % 10);
+ value /= 10;
+ }
+
+ if( i>len )
+ i = len;
+
+ for( int j=0; j<i; j ++ )
+ {
+ buf[j] = temp[ i-1-j ];
+ }
+}
+
+/* Clear buffer with length using clr character */
+static void highscore_clear( char *buf, char clr, int length )
+{
+ for( int i=0; i<length; i++ )
+ buf[i] = clr;
+}
+
+/*
+ Megapark Green
+--------------------------
+ #| Player | Time | Pts
+ 1|aaaabbbbcc 5:23.32 30000
+ 2| jef 0:20.34 10000
+ 3|aaabbbcccl 2:30.45 20000
+ 4|
+ 5|
+ 6|
+ 7|
+ 8|
+ 9|
+10|
+*/
+
+/* Generate a highscores board in text form, the width is always 27. Buffer
+ * must be (count+3)*27 in size. */
+static void highscores_board_generate( char *buf, u32 id, u32 count )
+{
+ int w=27;
+ highscore_clear( buf, ' ', (count+3)*w );
+
+ struct track_info *inf = &track_infos[id];
struct highscore_system *sys = &highscore_system;
- highscore_track_table *table = &sys->dbheader.tracks[ trackid ];
+ highscore_track_table *table = &sys->dbheader.tracks[ id ];
aatree_ptr it = aatree_kth( &sys->aainfo_time, table->root_time, 0 );
- vg_info( "Highscores, top %u records for track %u\n", count, trackid );
- vg_info( "==============================================\n" );
+ highscore_strc ( buf+w*0, inf->name, w,w );
+ highscore_clear( buf+w*1, '-', w );
+ highscore_strl ( buf+w*2, " #| Player | Time | Pts", 27 );
- int i=0;
- while( it != AATREE_PTR_NIL && i < 10 )
+ for( int i=0; i<count; i++ )
{
+ char *line = buf+w*(3+i);
+ highscore_intr( line, i+1, 2, ' ' );
+ line[2] = '|';
+
+ if( it == AATREE_PTR_NIL )
+ continue;
+
highscore_record *record = aatree_get_data( &sys->aainfo_time, it );
- vg_info( " [%d]: player(%lu), time: %hu, score: %hu, track:%hu\n",
- i+1, record->playerid, record->time, record->points,
- record->trackid );
+ highscore_playerinfo temp;
+ temp.playerid = record->playerid;
+
+ aatree_ptr info_ptr = aatree_find( &sys->aainfo_playerinfo_playerid,
+ sys->dbheader.playerinfo_root,
+ &temp );
+
+ /* Player name */
+ if( info_ptr == AATREE_PTR_NIL )
+ highscore_strl( line+3, "unknown", 10 );
+ else
+ {
+ highscore_playerinfo *inf = aatree_get_data(
+ &sys->aainfo_playerinfo_playerid, info_ptr );
- i++;
+ highscore_strl( line+3, inf->nickname, 10 );
+ }
+
+ u16 centiseconds = record->time,
+ seconds = centiseconds / 100,
+ minutes = seconds / 60;
+
+ centiseconds %= 100;
+ seconds %= 60;
+ minutes %= 60;
+
+ if( minutes > 9 ) minutes = 9;
+
+ /* Timer */
+ highscore_intr( line+14, minutes, 1, '0' );
+ line[15] = ':';
+ highscore_intr( line+16, seconds, 2, '0' );
+ line[18] = '.';
+ highscore_intr( line+19, centiseconds, 2, '0' );
+
+ /* Score */
+ highscore_intl( line+22, record->points, 5 );
it = aatree_next( &sys->aainfo_time, it );
}
+}
+
+/* Print string out to file using newlines. Count is number of records
+ * ( this requires a buffer of (count+3)*27 size */
+static void highscores_board_printf( FILE *fp, const char *buf, u32 count )
+{
+ int w=27;
- vg_info( "==============================================\n" );
+ for( int i=0; i<count+3; i++ )
+ fprintf( fp, "%.27s\n", &buf[i*w] );
}
#endif /* HIGHSCORES_H */