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