add zig cc option to build.c
[carveJwlIkooP6JGAAIwe30JlM.git] / highscores.c
1 #ifndef HIGHSCORES_C
2 #define HIGHSCORES_C
3
4 #include "highscores.h"
5
6 static int highscore_cmp_points( void *a, void *b )
7 {
8 highscore_record *pa = a, *pb = b;
9 return (int)pa->points - (int)pb->points;
10 }
11
12 static int highscore_cmp_datetime( void *a, void *b )
13 {
14 highscore_record *pa = a, *pb = b;
15
16 if( pa->datetime == pb->datetime ) return 0;
17 return pa->datetime < pb->datetime? 1: -1;
18 }
19
20 static int highscore_cmp_time( void *a, void *b )
21 {
22 highscore_record *pa = a, *pb = b;
23 return (int)pb->time - (int)pa->time;
24 }
25
26 static int highscore_cmp_playerid( void *a, void *b )
27 {
28 highscore_record *pa = a, *pb = b;
29 if( pa->playerid == pb->playerid ) return 0;
30 return pa->playerid < pb->playerid? -1: 1;
31 }
32
33 static int highscore_cmp_playerinfo_playerid( void *a, void *b )
34 {
35 highscore_playerinfo *pa = a, *pb = b;
36 if( pa->playerid == pb->playerid ) return 0;
37 return pa->playerid < pb->playerid? -1: 1;
38 }
39
40 static void highscores_create_db(void)
41 {
42 struct highscore_system *sys = &highscore_system;
43
44 vg_info( "Initializing database nodes\n" );
45 memset( &sys->dbheader, 0, sizeof(highscore_database) );
46
47 sys->dbheader.pool_head = aatree_init_pool( &sys->aainfo, sys->pool_size );
48 sys->dbheader.entry_capacity = sys->pool_size;
49
50 for( int i=0; i<vg_list_size(sys->dbheader.tracks); i++ )
51 {
52 highscore_track_table *table = &sys->dbheader.tracks[i];
53 table->root_points = AATREE_PTR_NIL;
54 table->root_playerid = AATREE_PTR_NIL;
55 table->root_time = AATREE_PTR_NIL;
56 table->root_datetime = AATREE_PTR_NIL;
57 }
58
59 /* Initialize secondary db */
60 sys->dbheader.playerinfo_head = aatree_init_pool(
61 &sys->aainfo_playerinfo,
62 sys->playerinfo_pool_size );
63 sys->dbheader.playerinfo_capacity = sys->playerinfo_pool_size;
64 sys->dbheader.playerinfo_root = AATREE_PTR_NIL;
65 }
66
67 static int highscores_read( const char *path ){
68 struct highscore_system *sys = &highscore_system;
69
70 if( path == NULL )
71 path = ".aadb";
72
73 FILE *fp = fopen( path, "rb" );
74 if( fp ){
75 vg_info( "Loading existing database\n" );
76
77 u64 count = fread( &sys->dbheader, sizeof(highscore_database), 1, fp );
78
79 if( count != 1 )
80 {
81 vg_error( "Unexpected EOF reading database header\n" );
82 return 0;
83 }
84
85 count = fread( sys->data, sizeof(highscore_record),
86 sys->pool_size, fp );
87
88 if( count != sys->pool_size )
89 {
90 vg_error( "Unexpected EOF reading database contents;"
91 " %lu records of %u were read\n", count, sys->pool_size );
92 return 0;
93 }
94
95 count = fread( sys->playerinfo_data, sizeof(highscore_playerinfo),
96 sys->playerinfo_pool_size, fp );
97
98 if( count != sys->playerinfo_pool_size )
99 {
100 vg_error( "Unexpected EOF reading playerinfo contents;"
101 " %lu records of %u were read\n", count,
102 sys->playerinfo_pool_size );
103 return 0;
104 }
105
106 fclose( fp );
107 return 1;
108 }
109 else
110 {
111 vg_low( "No existing database found (%s)\n", path );
112 return 0;
113 }
114 }
115
116 static void highscores_init( u32 pool_size, u32 playerinfo_pool_size )
117 {
118 struct highscore_system *sys = &highscore_system;
119
120 sys->data = vg_linear_alloc( vg_mem.rtmemory,
121 pool_size*sizeof(highscore_record) );
122
123 sys->playerinfo_data =
124 vg_linear_alloc( vg_mem.rtmemory,
125 playerinfo_pool_size * sizeof(highscore_playerinfo) );
126
127 memset( sys->data, 0, pool_size*sizeof(highscore_record) );
128 memset( sys->playerinfo_data, 0,
129 playerinfo_pool_size*sizeof(highscore_playerinfo) );
130
131
132 /* This is ugly.. too bad! */
133 sys->aainfo.base = highscore_system.data;
134 sys->aainfo.stride = sizeof(highscore_record);
135 sys->aainfo.offset = offsetof(highscore_record,pool);
136 sys->aainfo.p_cmp = NULL;
137
138 sys->aainfo_datetime.base = highscore_system.data;
139 sys->aainfo_datetime.stride = sizeof(highscore_record);
140 sys->aainfo_datetime.offset = offsetof(highscore_record,aa.datetime);
141 sys->aainfo_datetime.p_cmp = highscore_cmp_datetime;
142
143 sys->aainfo_points.base = highscore_system.data;
144 sys->aainfo_points.stride = sizeof(highscore_record);
145 sys->aainfo_points.offset = offsetof(highscore_record,aa.points);
146 sys->aainfo_points.p_cmp = highscore_cmp_points;
147
148 sys->aainfo_time.base = highscore_system.data;
149 sys->aainfo_time.stride = sizeof(highscore_record);
150 sys->aainfo_time.offset = offsetof(highscore_record,aa.time);
151 sys->aainfo_time.p_cmp = highscore_cmp_time;
152
153 sys->aainfo_playerid.base = highscore_system.data;
154 sys->aainfo_playerid.stride = sizeof(highscore_record);
155 sys->aainfo_playerid.offset = offsetof(highscore_record,aa.playerid);
156 sys->aainfo_playerid.p_cmp = highscore_cmp_playerid;
157
158 sys->aainfo_playerinfo_playerid.base = highscore_system.playerinfo_data;
159 sys->aainfo_playerinfo_playerid.stride = sizeof(highscore_playerinfo);
160 sys->aainfo_playerinfo_playerid.offset =
161 offsetof(highscore_playerinfo,aa_playerid);
162 sys->aainfo_playerinfo_playerid.p_cmp = highscore_cmp_playerinfo_playerid;
163
164 /* TODO: Is this even useable? */
165 sys->aainfo_playerinfo.base = highscore_system.playerinfo_data;
166 sys->aainfo_playerinfo.stride = sizeof(highscore_playerinfo);
167 sys->aainfo_playerinfo.offset = offsetof(highscore_playerinfo,aapn);
168 sys->aainfo_playerinfo.p_cmp = NULL;
169
170 sys->playerinfo_pool_size = playerinfo_pool_size;
171 sys->pool_size = pool_size;
172 }
173
174 static int highscores_serialize_all(void)
175 {
176 struct highscore_system *sys = &highscore_system;
177 vg_info( "Serializing database\n" );
178
179 FILE *fp = fopen( ".aadb", "wb" );
180
181 if( !fp )
182 {
183 vg_error( "Could not open .aadb\n" );
184 return 0;
185 }
186
187 fwrite( &sys->dbheader, sizeof(highscore_database), 1, fp );
188 fwrite( sys->data, sizeof(highscore_record),
189 sys->dbheader.entry_capacity, fp );
190 fwrite( sys->playerinfo_data, sizeof(highscore_playerinfo),
191 sys->dbheader.playerinfo_capacity, fp );
192
193 fclose( fp );
194 return 1;
195 }
196
197 static highscore_record *highscore_find_user_record( u64 playerid,
198 u32 trackid )
199 {
200 struct highscore_system *sys = &highscore_system;
201
202 highscore_track_table *table = &sys->dbheader.tracks[trackid];
203 highscore_record temp;
204 temp.playerid = playerid;
205
206 aatree_ptr find =
207 aatree_find( &sys->aainfo_playerid, table->root_playerid, &temp );
208
209 if( find == AATREE_PTR_NIL )
210 return NULL;
211
212 return aatree_get_data( &sys->aainfo_playerid, find );
213 }
214
215 static aatree_ptr highscores_push_record( highscore_record *record )
216 {
217 struct highscore_system *sys = &highscore_system;
218
219 vg_low( "Inserting record into database for track %hu\n",record->trackid );
220
221 if( record->trackid >= vg_list_size(sys->dbheader.tracks) ){
222 vg_error( "TrackID out of range (%hu>=%d)\n", record->trackid,
223 vg_list_size(sys->dbheader.tracks) );
224
225 return AATREE_PTR_NIL;
226 }
227
228 /* Search for existing record on this track */
229 highscore_track_table *table = &sys->dbheader.tracks[record->trackid];
230 aatree_ptr existing = aatree_find( &sys->aainfo_playerid,
231 table->root_playerid,
232 record );
233
234 if( existing != AATREE_PTR_NIL ){
235 highscore_record *crecord = aatree_get_data( &sys->aainfo_playerid,
236 existing );
237
238 if( crecord->time < record->time ||
239 (crecord->time == record->time && crecord->points > record->points))
240 {
241 vg_low( "Not overwriting better score\n" );
242 return existing;
243 }
244
245 vg_low( "Freeing existing record for player %lu\n", record->playerid );
246 table->root_playerid = aatree_del( &sys->aainfo_playerid, existing );
247 table->root_datetime = aatree_del( &sys->aainfo_datetime, existing );
248 table->root_points = aatree_del( &sys->aainfo_points, existing );
249 table->root_time = aatree_del( &sys->aainfo_time, existing );
250
251 aatree_pool_free( &sys->aainfo, existing, &sys->dbheader.pool_head );
252 }
253
254 aatree_ptr index =
255 aatree_pool_alloc( &sys->aainfo, &sys->dbheader.pool_head );
256
257 if( index == AATREE_PTR_NIL )
258 {
259 vg_error( "Database records are over capacity!\n" );
260 return index;
261 }
262
263 highscore_record *dst = aatree_get_data( &sys->aainfo, index );
264 memset( dst, 0, sizeof(highscore_record) );
265
266 dst->trackid = record->trackid;
267 dst->datetime = record->datetime;
268 dst->playerid = record->playerid;
269 dst->points = record->points;
270 dst->time = record->time;
271
272 table->root_time =
273 aatree_insert( &sys->aainfo_time, table->root_time, index );
274 table->root_datetime =
275 aatree_insert( &sys->aainfo_datetime, table->root_datetime, index );
276 table->root_playerid =
277 aatree_insert( &sys->aainfo_playerid, table->root_playerid, index );
278 table->root_points =
279 aatree_insert( &sys->aainfo_points, table->root_points, index );
280
281 return index;
282 }
283
284 static aatree_ptr highscore_set_user_nickname( u64 steamid, char nick[16] )
285 {
286 char name[17];
287 for( int i=0; i<16; i++ )
288 name[i] = nick[i];
289 name[16] = '\0';
290
291 vg_low( "Updating %lu's nickname -> %s\n", steamid, name );
292
293 struct highscore_system *sys = &highscore_system;
294
295 highscore_playerinfo temp;
296 temp.playerid = steamid;
297
298 aatree_ptr record = aatree_find( &sys->aainfo_playerinfo_playerid,
299 sys->dbheader.playerinfo_root,
300 &temp );
301 highscore_playerinfo *info;
302
303 if( record != AATREE_PTR_NIL )
304 {
305 info = aatree_get_data( &sys->aainfo_playerinfo, record );
306 }
307 else
308 {
309 record = aatree_pool_alloc( &sys->aainfo_playerinfo,
310 &sys->dbheader.playerinfo_head );
311
312 if( record == AATREE_PTR_NIL )
313 {
314 vg_error( "Player info database is over capacity!\n" );
315 return AATREE_PTR_NIL;
316 }
317
318 info = aatree_get_data( &sys->aainfo_playerinfo, record );
319 memset( info, 0, sizeof(highscore_playerinfo) );
320
321 info->playerid = steamid;
322 sys->dbheader.playerinfo_root = aatree_insert(
323 &sys->aainfo_playerinfo_playerid,
324 sys->dbheader.playerinfo_root, record );
325 }
326
327 for( int i=0; i<16; i++ )
328 info->nickname[i] = nick[i];
329
330 return AATREE_PTR_NIL;
331 }
332
333 /* Get the length of a string, bounded by '\0' or len, whichever is first */
334 static int highscore_strlen( const char *str, int len )
335 {
336 int str_length;
337 for( str_length=0; str_length<len; str_length++ )
338 if( !str[str_length] )
339 return str_length;
340
341 return str_length;
342 }
343
344 /* Print the string(max length:len) centered into buf (has width:width) */
345 static void highscore_strc( char *buf, const char *str, int len, int width )
346 {
347 int str_length = highscore_strlen( str, len ),
348 offs = (width-str_length)/2;
349
350 for( int i=0; i<str_length; i++ )
351 {
352 int j=i+offs;
353
354 if( j >= width )
355 return;
356
357 buf[j] = str[i];
358 }
359 }
360
361 /* Print the string(max length:len) left aligned into buf */
362 static void highscore_strl( char *buf, const char *str, int len )
363 {
364 for( int i=0; i<len; i++ )
365 {
366 if( !str[i] )
367 return;
368
369 buf[i] = str[i];
370 }
371 }
372
373 /* Print the string (max length:len) right aligned into buf (has width:width) */
374 static void highscore_strr( char *buf, const char *str, int len, int width )
375 {
376 int str_length = highscore_strlen( str, len );
377
378 for( int i=0; i<len; i++ )
379 {
380 if( !str[i] )
381 return;
382
383 buf[width-str_length+i] = str[i];
384 }
385 }
386
387 /* Print integer (padded with: alt), right aligned into buf(width: len)
388 * returns number of digits (not including alt), that were written to buf */
389 static int highscore_intr( char *buf, int value, int len, char alt )
390 {
391 int i=0;
392 while(value){
393 if( i>=len )
394 return i;
395
396 buf[ len-1 - (i ++) ] = '0' + (value % 10);
397 value /= 10;
398 }
399
400 for( ;i<len; i ++ )
401 buf[ len-1 - i ] = alt;
402
403 return i;
404 }
405
406 /* Print integer into buffer with max length len
407 * retuns the number of digits written to buf */
408 static int highscore_intl( char *buf, int value, int len ){
409 if( value ){
410 char temp[32];
411 int i=0;
412 while(value){
413 if( i>=len )
414 break;
415
416 temp[ i ++ ] = '0' + (value % 10);
417 value /= 10;
418 }
419
420 if( i>len )
421 i = len;
422
423 for( int j=0; j<i; j ++ )
424 buf[j] = temp[ i-1-j ];
425
426 return i;
427 }
428 else{
429 buf[ 0 ] = '0';
430 return 1;
431 }
432 }
433
434 /* Clear buffer with length using clr character */
435 static void highscore_clear( char *buf, char clr, int length )
436 {
437 for( int i=0; i<length; i++ )
438 buf[i] = clr;
439 }
440
441 /*
442 Megapark Green
443 --------------------------
444 #| Player | Time | Pts
445 1|aaaabbbbcc 5:23.32 30000
446 2| jef 0:20.34 10000
447 3|aaabbbcccl 2:30.45 20000
448 4|
449 5|
450 6|
451 7|
452 8|
453 9|
454 10|
455 */
456
457 /* Generate a highscores board in text form, the width is always 27. Buffer
458 * must be (count+3)*27 in size. */
459 static void highscores_board_generate( char *buf, u32 id, u32 count )
460 {
461 int w=27;
462 highscore_clear( buf, ' ', (count+3)*w );
463
464 struct track_info *inf = &track_infos[id];
465 struct highscore_system *sys = &highscore_system;
466
467 highscore_track_table *table = &sys->dbheader.tracks[ id ];
468 aatree_ptr it = aatree_kth( &sys->aainfo_time, table->root_time, 0 );
469
470 highscore_strc ( buf+w*0, inf->name, w,w );
471 highscore_clear( buf+w*1, '-', w );
472 highscore_strl ( buf+w*2, " #| Player | Time ", 27 );
473
474 for( int i=0; i<count; i++ )
475 {
476 char *line = buf+w*(3+i);
477 highscore_intr( line, i+1, 2, ' ' );
478 line[2] = '|';
479
480 if( it == AATREE_PTR_NIL )
481 continue;
482
483 highscore_record *record = aatree_get_data( &sys->aainfo_time, it );
484 highscore_playerinfo temp;
485 temp.playerid = record->playerid;
486
487 aatree_ptr info_ptr = aatree_find( &sys->aainfo_playerinfo_playerid,
488 sys->dbheader.playerinfo_root,
489 &temp );
490
491 /* Player name */
492 if( info_ptr == AATREE_PTR_NIL )
493 highscore_strl( line+3, "unknown", 16 );
494 else
495 {
496 highscore_playerinfo *inf = aatree_get_data(
497 &sys->aainfo_playerinfo_playerid, info_ptr );
498
499 highscore_strl( line+3, inf->nickname, 16 );
500
501 /* yep, this is fucking awesome! */
502 if( inf->playerid == 0x8297744501001001 ||
503 inf->playerid == 0x1ec4620101001001 ||
504 inf->playerid == 0x0110000145749782 ||
505 inf->playerid == 0x011000010162c41e )
506 {
507 i --;
508 /* FIXME: Clear line, or yknow, do it properly */
509 }
510 }
511
512 u16 centiseconds = record->time,
513 seconds = centiseconds / 100,
514 minutes = seconds / 60;
515
516 centiseconds %= 100;
517 seconds %= 60;
518 minutes %= 60;
519
520 if( minutes > 9 ) minutes = 9;
521
522 /* Timer */
523 highscore_intr( line+20, minutes, 1, '0' );
524 line[21] = ':';
525 highscore_intr( line+22, seconds, 2, '0' );
526 line[24] = '.';
527 highscore_intr( line+25, centiseconds, 2, '0' );
528
529 #if 0
530 /* Score */
531 highscore_intl( line+22, record->points, 5 );
532 #endif
533 it = aatree_next( &sys->aainfo_time, it );
534 }
535 }
536
537 /* Print string out to file using newlines. Count is number of records
538 * ( this requires a buffer of (count+3)*27 size */
539 static void highscores_board_printf( FILE *fp, const char *buf, u32 count )
540 {
541 int w=27;
542
543 for( int i=0; i<count+3; i++ )
544 fprintf( fp, "%.27s\n", &buf[i*w] );
545 }
546
547 #endif /* HIGHSCORES_C */