a more comprehensive workshop system
[carveJwlIkooP6JGAAIwe30JlM.git] / ent_skateshop.c
1 #ifndef ENT_SKATESHOP_C
2 #define ENT_SKATESHOP_C
3
4 #define VG_GAME
5 #include "vg/vg.h"
6 #include "vg/vg_steam_ugc.h"
7 #include "vg/vg_msg.h"
8 #include "ent_skateshop.h"
9 #include "world.h"
10 #include "player.h"
11 #include "gui.h"
12 #include "menu.h"
13 #include "pointcloud.h"
14 #include "highscores.h"
15 #include "steam.h"
16
17 /*
18 * Checks string equality but does a hash check first
19 */
20 static inline int const_str_eq( u32 hash, const char *str, const char *cmp )
21 {
22 if( hash == vg_strdjb2(cmp) )
23 if( !strcmp( str, cmp ) )
24 return 1;
25 return 0;
26 }
27
28 /*
29 * Get an existing cache instance, allocate a new one to be loaded, or NULL if
30 * there is no space
31 */
32 VG_STATIC struct cache_board *skateshop_cache_fetch( u32 registry_index )
33 {
34 struct registry_board *reg = NULL;
35
36 if( registry_index < global_skateshop.registry_count ){
37 reg = &global_skateshop.registry[ registry_index ];
38
39 if( reg->cache_ptr ){
40 return reg->cache_ptr;
41 }
42 }
43
44 /* lru eviction. should be a linked list maybe... */
45 double min_time = 1e300;
46 struct cache_board *min_board = NULL;
47
48 SDL_AtomicLock( &global_skateshop.sl_cache_access );
49 for( u32 i=0; i<SKATESHOP_BOARD_CACHE_MAX; i++ ){
50 struct cache_board *cache_ptr = &global_skateshop.cache[i];
51
52 if( cache_ptr->state == k_cache_board_state_load_request ) continue;
53 if( cache_ptr->ref_count ) continue;
54
55 if( cache_ptr->last_use_time < min_time ){
56 min_time = cache_ptr->last_use_time;
57 min_board = cache_ptr;
58 }
59 }
60
61 if( min_board ){
62 if( min_board->state == k_cache_board_state_loaded ){
63 struct registry_board *other =
64 &global_skateshop.registry[ min_board->registry_id ];
65
66 vg_info( "Deallocating board: '%s'\n", min_board, other->foldername );
67
68 player_board_unload( &min_board->board );
69 other->cache_ptr = NULL;
70 }
71
72 if( reg ){
73 vg_info( "Allocating board (reg:%u) '%s'\n",
74 registry_index, reg->foldername );
75 }
76 else{
77 vg_info( "Pre-allocating board (reg:%u) 'null'\n", registry_index );
78 }
79
80 min_board->registry_id = registry_index;
81 min_board->last_use_time = vg.time;
82 min_board->ref_count = 0;
83 min_board->state = k_cache_board_state_load_request;
84 }
85 else{
86 vg_error( "No free boards to load registry!\n" );
87 }
88
89 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
90 return min_board;
91 }
92
93 VG_STATIC void skateshop_update_viewpage(void)
94 {
95 u32 page = global_skateshop.selected_registry_id/SKATESHOP_VIEW_SLOT_MAX;
96
97 for( u32 i=0; i<SKATESHOP_VIEW_SLOT_MAX; i++ ){
98 struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
99 u32 request_id = page*SKATESHOP_VIEW_SLOT_MAX + i;
100
101 if( slot->cache_ptr ) unwatch_cache_board( slot->cache_ptr );
102
103 slot->cache_ptr = skateshop_cache_fetch( request_id );
104 if( slot->cache_ptr ) watch_cache_board( slot->cache_ptr );
105 }
106 }
107
108 /*
109 * op/subroutine: k_workshop_op_item_load
110 * -----------------------------------------------------------------------------
111 */
112
113 /*
114 * Reciever for board completion; only promotes the status in the main thread
115 */
116 VG_STATIC void skateshop_async_board_loaded( void *payload, u32 size )
117 {
118 SDL_AtomicLock( &global_skateshop.sl_cache_access );
119 struct cache_board *cache_ptr = payload;
120 cache_ptr->last_use_time = vg.time;
121 cache_ptr->state = k_cache_board_state_loaded;
122
123 struct registry_board *reg =
124 &global_skateshop.registry[ cache_ptr->registry_id ];
125 reg->cache_ptr = cache_ptr;
126 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
127
128 vg_success( "Async board loaded (%s)\n", reg->foldername );
129 }
130
131 /*
132 * Thread(or subroutine of thread), for checking view slots that weve installed.
133 * Load the model if a view slot wants it
134 */
135 VG_STATIC void workshop_visibile_load_loop_thread( void *_args )
136 {
137 char path_buf[4096];
138 vg_str folder;
139
140 for( u32 i=0; i<SKATESHOP_BOARD_CACHE_MAX; i++ ){
141 struct cache_board *cache_ptr = &global_skateshop.cache[i];
142
143 SDL_AtomicLock( &global_skateshop.sl_cache_access );
144 if( cache_ptr->state == k_cache_board_state_load_request ){
145 if( cache_ptr->registry_id >= global_skateshop.registry_count ){
146 /* should maybe have a different value for this case */
147 cache_ptr->state = k_cache_board_state_none;
148 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
149 continue;
150 }
151
152 /* continue with the request */
153 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
154
155 struct registry_board *reg =
156 &global_skateshop.registry[ cache_ptr->registry_id ];
157
158 if( reg->workshop_id ){
159 vg_async_item *call =
160 vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
161
162 struct async_workshop_filepath_info *info = call->payload;
163 info->buf = path_buf;
164 info->id = reg->workshop_id;
165 info->len = vg_list_size(path_buf);
166 vg_async_dispatch( call, async_workshop_get_filepath );
167 vg_async_stall(); /* too bad! */
168
169 if( path_buf[0] == '\0' ){
170 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64 ")\n",
171 reg->workshop_id );
172
173 goto file_is_broken;
174 }
175
176 folder.buffer = path_buf;
177 folder.i = strlen(path_buf);
178 folder.len = 4096;
179 }
180 else{
181 vg_strnull( &folder, path_buf, 4096 );
182 vg_strcat( &folder, "boards/" );
183 vg_strcat( &folder, reg->foldername );
184 }
185
186 vg_str meta_path = folder;
187 vg_strcat( &meta_path, "/addon.inf" );
188
189 if( !vg_strgood( &meta_path ) ) {
190 vg_error( "Metadata path too long\n" );
191 goto file_is_broken;
192 }
193
194 u8 meta[512];
195 FILE *fp = fopen( meta_path.buffer, "rb" );
196
197 if( !fp ) goto file_is_broken;
198
199 u32 l = fread( meta, 1, 512, fp );
200 if( l != 512 ){
201 if( !feof(fp) ){
202 fclose(fp);
203 vg_error( "unknown error codition" );
204 goto file_is_broken;
205 }
206 }
207 fclose(fp);
208
209 /* load content files
210 * --------------------------------- */
211
212 vg_str content_path = folder;
213
214 vg_msg msg;
215 vg_msg_init( &msg, meta, l );
216 vg_msg_cmd cmd;
217 while( vg_msg_next( &msg, &cmd ) ){
218 if( (msg.depth == 0) && (cmd.code == k_vg_msg_code_kvstring) ){
219 if( VG_STRDJB2_EQ( "content", cmd.key, cmd.key_djb2 ) ){
220 vg_strcat( &content_path, "/" );
221 vg_strcat( &content_path, cmd.value._buf );
222 break;
223 }
224 }
225 }
226 if( !vg_strgood( &content_path ) ) {
227 vg_error( "Metadata path too long\n" );
228 goto file_is_broken;
229 }
230
231 player_board_load( &cache_ptr->board, content_path.buffer );
232 vg_async_call( skateshop_async_board_loaded, cache_ptr, 0 );
233 continue;
234
235 file_is_broken:;
236 SDL_AtomicLock( &global_skateshop.sl_cache_access );
237 cache_ptr->state = k_cache_board_state_none;
238 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
239 }
240 else
241 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
242 }
243 vg_async_call( workshop_async_any_complete, NULL, 0 );
244 }
245
246 /*
247 * op: k_workshop_op_item_scan
248 * -----------------------------------------------------------------------------
249 */
250
251 /*
252 * Reciever for scan completion. copies the registry_count back into t0
253 */
254 VG_STATIC void workshop_async_reg_update( void *data, u32 size )
255 {
256 vg_info( "Registry update notify\n" );
257 global_skateshop.registry_count = global_skateshop.t1_registry_count;
258 }
259
260 VG_STATIC void workshop_steam_scan(void)
261 {
262 /*
263 * Steam workshop scan
264 */
265 vg_info( "Scanning steam workshop for boards\n" );
266 PublishedFileId_t workshop_ids[ SKATESHOP_REGISTRY_MAX ];
267 u32 workshop_count = SKATESHOP_REGISTRY_MAX;
268
269 vg_async_item *call = vg_async_alloc(
270 sizeof(struct async_workshop_installed_files_info));
271 struct async_workshop_installed_files_info *info = call->payload;
272 info->buffer = workshop_ids;
273 info->len = &workshop_count;
274 vg_async_dispatch( call, async_workshop_get_installed_files );
275 vg_async_stall();
276
277 for( u32 j=0; j<workshop_count; j++ ){
278 /* check for existance in both our caches
279 * ----------------------------------------------------------*/
280 PublishedFileId_t id = workshop_ids[j];
281 for( u32 i=0; i<global_skateshop.t1_registry_count; i++ ){
282 struct registry_board *reg = &global_skateshop.registry[i];
283
284 if( reg->workshop_id == id ){
285 reg->state = k_registry_board_state_indexed;
286 goto next_file_workshop;
287 }
288 }
289 for( u32 i=0; i<global_skateshop.t1_world_registry_count; i++ ){
290 struct registry_world *reg = &global_skateshop.world_registry[i];
291
292 if( reg->workshop_id == id ){
293 reg->state = k_registry_board_state_indexed;
294 goto next_file_workshop;
295 }
296 }
297
298 /* new one, lets find out what type it is
299 * ---------------------------------------------------------------*/
300 vg_info( "new listing from the steam workshop!: "PRINTF_U64"\n", id );
301 vg_async_item *call1 =
302 vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
303
304 char path[ 4096 ];
305
306 struct async_workshop_filepath_info *info = call1->payload;
307 info->buf = path;
308 info->id = id;
309 info->len = vg_list_size(path);
310 vg_async_dispatch( call1, async_workshop_get_filepath );
311 vg_async_stall(); /* too bad! */
312
313 vg_info( "%s\n", path );
314 vg_str folder = {.buffer = path, .i=strlen(path), .len=4096};
315 vg_str meta_path = folder;
316 vg_strcat( &meta_path, "/addon.inf" );
317 if( !vg_strgood( &meta_path ) ){
318 vg_error( "The metadata path is too long\n" );
319 goto next_file_workshop;
320 }
321
322 u8 meta[512];
323 FILE *fp = fopen( meta_path.buffer, "rb" );
324 if( !fp ){
325 vg_error( "Could not open the '%s'\n", meta_path.buffer );
326 goto next_file_workshop;
327 }
328
329 u32 l = fread( meta, 1, 512, fp );
330 if( l != 512 ){
331 if( !feof(fp) ){
332 fclose(fp);
333 vg_error( "unknown error codition" );
334 goto next_file_workshop;
335 }
336 }
337 fclose(fp);
338
339 enum workshop_file_type type = k_workshop_file_type_none;
340 vg_msg msg;
341 vg_msg_init( &msg, meta, l );
342
343 vg_msg_cmd cmd;
344 while( vg_msg_next( &msg, &cmd ) ){
345 if( (msg.depth == 1) && (cmd.code == k_vg_msg_code_frame) ){
346 if( VG_STRDJB2_EQ( "workshop", cmd.key, cmd.key_djb2 ) ){
347 u32 depth = msg.depth;
348 while( (msg.depth == depth) && vg_msg_next( &msg, &cmd ) ){
349 if( cmd.code & k_vg_msg_code_unsigned ){
350 if( VG_STRDJB2_EQ( "type", cmd.key, cmd.key_djb2 ) ){
351 type = cmd.value._u32;
352 }
353 }
354 }
355 }
356 }
357 }
358
359 if( type == k_workshop_file_type_none ){
360 vg_error( "Cannot determine addon type\n" );
361 goto next_file_workshop;
362 }
363
364 /* now that we have the type
365 * ------------------------------------------------------------------*/
366
367 if( type == k_workshop_file_type_board ){
368 if( global_skateshop.t1_registry_count == SKATESHOP_REGISTRY_MAX ){
369 vg_error( "You have too many boards installed!\n" );
370 goto next_file_workshop;
371 }
372
373 struct registry_board *reg = &global_skateshop.registry[
374 global_skateshop.t1_registry_count ++ ];
375
376 reg->cache_ptr = NULL;
377 snprintf( reg->foldername, 64, PRINTF_U64, id );
378 reg->foldername_hash = vg_strdjb2( reg->foldername );
379 reg->workshop_id = id;
380 reg->state = k_registry_board_state_indexed;
381 }
382 else if( type == k_workshop_file_type_world ){
383 if( global_skateshop.t1_world_registry_count == SKATESHOP_WORLDS_MAX ){
384 vg_error( "You have too many worlds installed!\n" );
385 goto next_file_workshop;
386 }
387
388 struct registry_world *reg = &global_skateshop.world_registry[
389 global_skateshop.t1_world_registry_count ++ ];
390
391 snprintf( reg->foldername, 64, PRINTF_U64, id );
392 reg->foldername_hash = vg_strdjb2( reg->foldername );
393 reg->type = k_world_load_type_workshop;
394 reg->workshop_id = id;
395 reg->state = k_registry_board_state_indexed;
396 }
397
398 next_file_workshop:;
399 }
400 }
401
402 /*
403 * Async thread which scans local files for boards, as well as scheduling
404 * synchronous calls to the workshop
405 */
406 VG_STATIC void workshop_scan_thread( void *_args )
407 {
408 vg_linear_clear( vg_mem.scratch );
409
410 for( u32 i=0; i<global_skateshop.t1_registry_count; i++ ){
411 struct registry_board *reg = &global_skateshop.registry[i];
412 reg->state = k_registry_board_state_indexed_absent;
413 }
414
415 #if 0
416 /*
417 * Local disk scan
418 */
419 vg_info( "Scanning models/boards/*.mdl\n" );
420 tinydir_dir dir;
421 tinydir_open( &dir, "models/boards" );
422
423 while( dir.has_next ){
424 tinydir_file file;
425 tinydir_readfile( &dir, &file );
426
427 if( file.is_reg ){
428 u32 hash = vg_strdjb2( file.name );
429
430 for( u32 i=0; i<global_skateshop.t1_registry_count; i++ ){
431 struct registry_board *reg = &global_skateshop.registry[i];
432
433 if( const_str_eq( hash, file.name, reg->filename ) ){
434 reg->state = k_registry_board_state_indexed;
435 goto next_file;
436 }
437 }
438
439 if( global_skateshop.t1_registry_count == SKATESHOP_REGISTRY_MAX ){
440 vg_error( "You have too many boards installed!\n" );
441 break;
442 }
443
444 vg_info( "new listing!: %s\n", file.name );
445
446 struct registry_board *reg =
447 &global_skateshop.registry[global_skateshop.t1_registry_count ++];
448
449 reg->cache_ptr = NULL;
450 vg_strncpy( file.name, reg->filename, 64, k_strncpy_always_add_null );
451 #if 0
452 vg_strncpy( file.name, reg->workshop.title,
453 64, k_strncpy_always_add_null );
454 #endif
455 reg->filename_hash = hash;
456 reg->workshop_id = 0;
457 reg->state = k_registry_board_state_indexed;
458
459 #if 0
460 reg->workshop.author = 0;
461 strcpy( reg->workshop.author_name, "custom" );
462 #endif
463 }
464
465 next_file: tinydir_next( &dir );
466 }
467
468 tinydir_close(&dir);
469 #endif
470
471 if( steam_ready ) workshop_steam_scan();
472
473 vg_async_call( workshop_async_reg_update, NULL, 0 );
474 vg_async_stall();
475 workshop_visibile_load_loop_thread(NULL);
476 }
477
478 /*
479 * Asynchronous scan of local disk for items and add them to the registry
480 */
481 VG_STATIC void workshop_op_item_scan(void)
482 {
483 skaterift_begin_op( k_workshop_op_item_scan );
484 vg_loader_start( workshop_scan_thread, NULL );
485 }
486
487 /*
488 * op: k_async_op_world_scan
489 * -----------------------------------------------------------------------------
490 */
491
492 /*
493 * Reciever for scan completion. copies the registry_count back into t0
494 */
495 VG_STATIC void workshop_async_world_reg_update( void *data, u32 size )
496 {
497 vg_info( "World registry update notify\n" );
498 global_skateshop.world_registry_count =
499 global_skateshop.t1_world_registry_count;
500 skaterift_end_op();
501 }
502
503 /*
504 * Add a local world folder to the registry, it will verify existing ones are
505 * still there too.
506 */
507 VG_STATIC void world_scan_register_local( const char *folder_name )
508 {
509 u32 hash = vg_strdjb2( folder_name );
510 for( u32 i=0; i<global_skateshop.t1_world_registry_count; i++ ){
511 struct registry_world *reg =
512 &global_skateshop.world_registry[i];
513
514 if( const_str_eq( hash, folder_name, reg->foldername ) ){
515 reg->state = k_registry_board_state_indexed;
516 return;
517 }
518 }
519
520 if( global_skateshop.t1_world_registry_count == SKATESHOP_WORLDS_MAX ){
521 vg_error( "You have too many worlds installed!\n" );
522 return;
523 }
524
525 vg_info( "new listing!: %s\n", folder_name );
526
527 struct registry_world *reg = &global_skateshop.world_registry[
528 global_skateshop.t1_world_registry_count ++ ];
529
530 vg_strncpy( folder_name, reg->foldername, 64, k_strncpy_overflow_fatal );
531 reg->foldername_hash = hash;
532 reg->state = k_registry_board_state_indexed;
533 //reg->meta_present = 0;
534 reg->type = k_world_load_type_local;
535 }
536
537 /*
538 * Async thread which scans local files for boards, as well as scheduling
539 * synchronous calls to the workshop
540 */
541 VG_STATIC void world_scan_thread( void *_args )
542 {
543 vg_linear_clear( vg_mem.scratch );
544
545 for( u32 i=0; i<global_skateshop.t1_world_registry_count; i++ ){
546 struct registry_world *reg = &global_skateshop.world_registry[i];
547 reg->state = k_registry_board_state_indexed_absent;
548 }
549
550 /*
551 * Local disk scan
552 */
553 vg_info( "Scanning maps/*.mdl\n" );
554
555 char path_buf[4096];
556 vg_str path;
557 vg_strnull( &path, path_buf, 4096 );
558 vg_strcat( &path, "maps/" );
559
560 DIR *dir = opendir( path.buffer );
561 if( !dir ){
562 vg_error( "opendir('maps') failed\n" );
563 vg_async_call( workshop_async_any_complete, NULL, 0 );
564 return;
565 }
566
567 struct dirent *entry;
568 while( (entry = readdir(dir)) ){
569 if( entry->d_type == DT_DIR ){
570 if( entry->d_name[0] == '.' ) continue;
571
572 vg_str folder = path;
573 char *folder_name = folder.buffer+folder.i;
574
575 if( strlen( entry->d_name ) >
576 vg_list_size(global_skateshop.world_registry[0].foldername)){
577 vg_warn( "Map folder too long: %s\n", entry->d_name );
578 continue;
579 }
580
581 vg_strcat( &folder, entry->d_name );
582 if( !vg_strgood( &folder ) ) break;
583
584 DIR *subdir = opendir( folder.buffer );
585 while( (entry = readdir(subdir)) ){
586 if( entry->d_type == DT_REG ){
587 if( entry->d_name[0] == '.' ) continue;
588
589 vg_str file = folder;
590 vg_strcat( &file, "/" );
591 vg_strcat( &file, entry->d_name );
592 if( !vg_strgood( &file ) ) continue;
593
594 char *ext = vg_strch( &file, '.' );
595 if( !ext ) continue;
596 if( strcmp(ext,".mdl") ) continue;
597
598 vg_strcat( &folder, "" );
599 world_scan_register_local( folder_name );
600 }
601 }
602 closedir(subdir);
603 }
604 }
605 closedir(dir);
606
607 vg_async_call( workshop_async_world_reg_update, NULL, 0 );
608
609 #if 0
610 tinydir_close(&dir);
611
612 if( steam_ready ) workshop_steam_scan();
613
614 vg_async_call( workshop_async_reg_update, NULL, 0 );
615 vg_async_stall();
616 workshop_visibile_load_loop_thread(NULL);
617 #endif
618 }
619
620 /*
621 * Asynchronous scan of local disk for worlds
622 */
623 VG_STATIC void skateshop_op_world_scan(void)
624 {
625 skaterift_begin_op( k_async_op_world_scan );
626 vg_loader_start( world_scan_thread, NULL );
627 }
628
629 /*
630 * Regular stuff
631 * -----------------------------------------------------------------------------
632 */
633
634 /* we can only keep using a viewslot pointer for multiple frames if we watch it
635 * using this function */
636 VG_STATIC void watch_cache_board( struct cache_board *ptr )
637 {
638 if( ptr->ref_count >= 32 ){
639 vg_fatal_error( "dynamic board watch missmatch (limit is 32)\n" );
640 }
641
642 ptr->last_use_time = vg.time;
643 ptr->ref_count ++;
644 }
645
646 /* after this is called, the calling code only has access to the pointer for the
647 * duration of the rest of the frame */
648 VG_STATIC void unwatch_cache_board( struct cache_board *ptr )
649 {
650 if( ptr->ref_count == 0 ){
651 vg_fatal_error( "dynamic board unwatch missmatch (no watchers)\n" );
652 }
653
654 ptr->ref_count --;
655 }
656
657 /*
658 * Callback handler for persona state changes,
659 * it sets the author names on the registries
660 */
661 VG_STATIC void callback_persona_statechange( CallbackMsg_t *msg )
662 {
663 PersonaStateChange_t *info = (PersonaStateChange_t *)msg->m_pubParam;
664 ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
665
666 #if 0
667 if( info->m_nChangeFlags & k_EPersonaChangeName ){
668 for( u32 i=0; i<global_skateshop.registry_count; i++ ){
669 struct registry_board *reg = &global_skateshop.registry[i];
670 if( reg->workshop.author == info->m_ulSteamID ){
671 const char *name = SteamAPI_ISteamFriends_GetFriendPersonaName(
672 hSteamFriends, info->m_ulSteamID );
673 str_utf8_collapse( name, reg->workshop.author_name, 32 );
674 }
675 }
676 }
677 #endif
678 }
679
680 /*
681 * VG event init
682 */
683 VG_STATIC void skateshop_init(void)
684 {
685 u32 reg_size = sizeof(struct registry_board)*SKATESHOP_REGISTRY_MAX,
686 wreg_size = sizeof(struct registry_world)*SKATESHOP_WORLDS_MAX,
687 cache_size = sizeof(struct cache_board)*SKATESHOP_BOARD_CACHE_MAX;
688
689 global_skateshop.registry = vg_linear_alloc( vg_mem.rtmemory, reg_size );
690 global_skateshop.world_registry =
691 vg_linear_alloc( vg_mem.rtmemory, wreg_size );
692 global_skateshop.cache = vg_linear_alloc( vg_mem.rtmemory, cache_size );
693
694 memset( global_skateshop.cache, 0, cache_size );
695
696 for( u32 i=0; i<SKATESHOP_BOARD_CACHE_MAX; i++ ){
697 struct cache_board *board = &global_skateshop.cache[i];
698 board->state = k_cache_board_state_none;
699 board->registry_id = 0xffffffff;
700 board->last_use_time = -99999.9;
701 board->ref_count = 0;
702 }
703
704 if( steam_ready ){
705 steam_register_callback( k_iPersonaStateChange,
706 callback_persona_statechange );
707 }
708 }
709
710 VG_STATIC struct cache_board *skateshop_selected_cache_if_loaded(void)
711 {
712 if( global_skateshop.registry_count > 0 ){
713 u32 reg_id = global_skateshop.selected_registry_id;
714 struct registry_board *reg = &global_skateshop.registry[ reg_id ];
715
716 SDL_AtomicLock( &global_skateshop.sl_cache_access );
717 if( reg->cache_ptr &&
718 (reg->cache_ptr->state == k_cache_board_state_loaded ) )
719 {
720 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
721 return reg->cache_ptr;
722 }
723 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
724 }
725
726 return NULL;
727 }
728
729 VG_STATIC void pointcloud_async_end(void *_, u32 __)
730 {
731 pointcloud_animate( k_pointcloud_anim_opening );
732 skaterift_end_op();
733 }
734
735 VG_STATIC void pointcloud_clear_async(void *_, u32 __)
736 {
737 pointcloud.count = 0;
738 pointcloud_animate( k_pointcloud_anim_opening );
739 skaterift_end_op();
740 }
741
742 VG_STATIC void skateshop_preview_loader_thread( void *_data )
743 {
744 struct registry_world *reg = _data;
745
746 if( reg->type == k_world_load_type_local ){
747 char path_buf[4096];
748 vg_str path;
749 vg_strnull( &path, path_buf, 4096 );
750 vg_strcat( &path, "maps/" );
751 vg_strcat( &path, reg->foldername );
752 vg_strcat( &path, "/preview.bin" );
753
754 vg_linear_clear(vg_mem.scratch);
755 u32 size;
756
757 void *data = vg_file_read( vg_mem.scratch, path_buf, &size );
758 if( data ){
759 if( size < sizeof(pointcloud_buffer) ){
760 vg_async_call( pointcloud_clear_async, NULL, 0 );
761 return;
762 }
763
764 vg_async_item *call = vg_async_alloc(size);
765 pointcloud_buffer *pcbuf = call->payload;
766 memcpy( pcbuf, data, size );
767
768 u32 point_count = (size-sizeof(pointcloud_buffer)) /
769 sizeof(struct pointcloud_vert);
770 pcbuf->max = point_count;
771 pcbuf->count = point_count;
772 pcbuf->op = k_pointcloud_op_clear;
773
774 vg_async_dispatch( call, async_pointcloud_sub );
775 vg_async_call( pointcloud_async_end, NULL, 0 );
776 }
777 else{
778 vg_async_call( pointcloud_clear_async, NULL, 0 );
779 }
780 }
781 else{
782 vg_async_call( pointcloud_clear_async, NULL, 0 );
783 }
784 }
785
786 VG_STATIC void skateshop_load_world_preview( struct registry_world *reg )
787 {
788 skaterift_begin_op( k_async_op_world_load_preview );
789 vg_loader_start( skateshop_preview_loader_thread, reg );
790 }
791
792 /*
793 * VG event preupdate
794 */
795 void temp_update_playermodel(void);
796 VG_STATIC void global_skateshop_preupdate(void)
797 {
798 float rate = vg_minf( 1.0f, vg.time_frame_delta * 2.0f );
799 global_skateshop.factive = vg_lerpf( global_skateshop.factive,
800 global_skateshop.active, rate );
801
802 if( !global_skateshop.active ) return;
803
804 world_instance *world = world_current_instance();
805 ent_skateshop *shop = global_skateshop.ptr_ent;
806
807 /* camera positioning */
808 ent_camera *ref = mdl_arritm( &world->ent_camera,
809 mdl_entity_id_id(shop->id_camera) );
810
811 v3f dir = {0.0f,-1.0f,0.0f};
812 mdl_transform_vector( &ref->transform, dir, dir );
813 m3x3_mulv( localplayer.invbasis, dir, dir );
814 player_vector_angles( localplayer.cam_override_angles, dir, 1.0f, 0.0f );
815
816 v3f lookat;
817 if( shop->type == k_skateshop_type_boardshop ||
818 shop->type == k_skateshop_type_worldshop ){
819 ent_marker *display = mdl_arritm( &world->ent_marker,
820 mdl_entity_id_id(shop->boards.id_display) );
821
822 v3_sub( display->transform.co, localplayer.rb.co, lookat );
823
824 }
825 else if( shop->type == k_skateshop_type_charshop ){
826 v3_sub( ref->transform.co, localplayer.rb.co, lookat );
827 }
828 else{
829 vg_fatal_error( "Unknown store (%u)\n", shop->type );
830 }
831
832 q_axis_angle( localplayer.rb.q, (v3f){0.0f,1.0f,0.0f},
833 atan2f(lookat[0],lookat[2]) );
834
835 v3_copy( ref->transform.co, localplayer.cam_override_pos );
836 localplayer.cam_override_fov = ref->fov;
837 localplayer.cam_override_strength = global_skateshop.factive;
838
839 /* input */
840 if( shop->type == k_skateshop_type_boardshop ){
841 gui_helper_action( axis_display_string( k_sraxis_mbrowse_h ), "browse" );
842 gui_helper_action( button_display_string( k_srbind_mback ), "exit" );
843
844 struct cache_board *selected_cache = skateshop_selected_cache_if_loaded();
845
846 if( selected_cache ){
847 gui_helper_action( button_display_string( k_srbind_maccept ), "pick" );
848 }
849
850 /*
851 * Controls
852 * ----------------------
853 */
854
855 if( button_down( k_srbind_mleft ) ){
856 if( global_skateshop.selected_registry_id > 0 ){
857 global_skateshop.selected_registry_id --;
858 }
859 }
860
861 if( button_down( k_srbind_mright ) ){
862 if( global_skateshop.selected_registry_id+1 <
863 global_skateshop.registry_count )
864 {
865 global_skateshop.selected_registry_id ++;
866 }
867 }
868
869 if( selected_cache && button_down( k_srbind_maccept ) ){
870 vg_info( "chose board from skateshop (%u)\n",
871 global_skateshop.selected_registry_id );
872
873 if( localplayer.board_view_slot ){
874 unwatch_cache_board( localplayer.board_view_slot );
875 }
876
877 localplayer.board_view_slot = selected_cache;
878 watch_cache_board( localplayer.board_view_slot );
879
880 global_skateshop_exit();
881 return;
882 }
883 }
884 else if( shop->type == k_skateshop_type_charshop ){
885 gui_helper_action( axis_display_string( k_sraxis_mbrowse_h ), "browse" );
886 gui_helper_action( button_display_string( k_srbind_mback ), "exit" );
887 gui_helper_action( button_display_string( k_srbind_maccept ), "pick" );
888
889 if( button_down( k_srbind_mleft ) ){
890 if( cl_playermdl_id > 0 ){
891 cl_playermdl_id --;
892 }
893 else{
894 cl_playermdl_id = 2; /* HACK */
895 }
896 temp_update_playermodel(); /* HACK */
897 }
898
899 if( button_down( k_srbind_mright ) ){
900 if( cl_playermdl_id+1 < 3 ){
901 cl_playermdl_id ++;
902 }
903 else{
904 cl_playermdl_id = 0; /* HACK */
905 }
906 temp_update_playermodel(); /* HACK */
907 /*lol*/
908 }
909
910 if( button_down( k_srbind_maccept ) ){
911 global_skateshop_exit();
912 }
913 }
914 else if( shop->type == k_skateshop_type_worldshop ){
915 int browseable = 0,
916 loadable = 0;
917
918 if( global_skateshop.world_registry_count &&
919 ((skaterift.async_op == k_async_op_none)||
920 (skaterift.async_op == k_async_op_world_load_preview))){
921 gui_helper_action( axis_display_string(k_sraxis_mbrowse_h), "browse" );
922 browseable = 1;
923 }
924
925 if( skaterift.async_op == k_async_op_none ){
926 gui_helper_action( button_display_string(k_srbind_maccept), "load" );
927 loadable = 1;
928 }
929
930 int change = 0;
931
932 if( browseable ){
933 if( button_down( k_srbind_mleft ) ){
934 if( global_skateshop.selected_world_id > 0 )
935 {
936 global_skateshop.selected_world_id --;
937 change = 1;
938 }
939 }
940
941 if( button_down( k_srbind_mright ) ){
942 if( global_skateshop.selected_world_id+1 <
943 global_skateshop.world_registry_count )
944 {
945 global_skateshop.selected_world_id ++;
946 change = 1;
947 }
948 }
949 }
950
951 if( change && pointcloud_idle() ){
952 pointcloud_animate( k_pointcloud_anim_hiding );
953 }
954
955 if( skaterift.async_op == k_async_op_none ){
956 struct registry_world *rw = &global_skateshop.world_registry[
957 global_skateshop.selected_world_id ];
958
959 /* automatically load in clouds */
960 if( loadable && button_down( k_srbind_maccept ) ){
961 vg_info( "Select world (%u)\n",
962 global_skateshop.selected_world_id );
963 skaterift_change_world( rw->foldername );
964 return;
965 }
966 else{
967 if( pointcloud.anim == k_pointcloud_anim_idle_closed ){
968 if( global_skateshop.pointcloud_world_id !=
969 global_skateshop.selected_world_id )
970 {
971 global_skateshop.pointcloud_world_id =
972 global_skateshop.selected_world_id;
973 skateshop_load_world_preview( rw );
974 }
975 else{
976 pointcloud_animate( k_pointcloud_anim_opening );
977 }
978 }
979 else if( pointcloud.anim == k_pointcloud_anim_idle_open ){
980 if( global_skateshop.pointcloud_world_id !=
981 global_skateshop.selected_world_id )
982 {
983 pointcloud_animate( k_pointcloud_anim_hiding );
984 }
985 }
986 }
987 }
988 }
989 else{
990 vg_fatal_error( "Unknown store (%u)\n", shop->type );
991 }
992
993 if( button_down( k_srbind_mback ) ){
994 global_skateshop_exit();
995 return;
996 }
997 }
998
999 VG_STATIC void skateshop_render_boardshop(void)
1000 {
1001 world_instance *world = world_current_instance();
1002 ent_skateshop *shop = global_skateshop.ptr_ent;
1003
1004 u32 slot_count = vg_list_size(global_skateshop.shop_view_slots);
1005
1006 ent_marker *mark_rack = mdl_arritm( &world->ent_marker,
1007 mdl_entity_id_id(shop->boards.id_rack)),
1008 *mark_display = mdl_arritm( &world->ent_marker,
1009 mdl_entity_id_id(shop->boards.id_display));
1010
1011 int visibility[ SKATESHOP_VIEW_SLOT_MAX ];
1012 SDL_AtomicLock( &global_skateshop.sl_cache_access );
1013 for( u32 i=0; i<SKATESHOP_VIEW_SLOT_MAX; i++ ){
1014 struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
1015
1016 visibility[i] = 1;
1017
1018 if( slot->cache_ptr == NULL ) visibility[i] = 0;
1019 else if( slot->cache_ptr->state != k_cache_board_state_loaded )
1020 visibility[i] = 0;
1021 }
1022 SDL_AtomicUnlock( &global_skateshop.sl_cache_access );
1023
1024 /* Render loaded boards in the view slots */
1025 for( u32 i=0; i<slot_count; i++ ){
1026 struct shop_view_slot *slot = &global_skateshop.shop_view_slots[i];
1027 float selected = 0.0f;
1028
1029 if( !visibility[i] ) goto fade_out;
1030
1031 mdl_transform xform;
1032 transform_identity( &xform );
1033
1034 xform.co[0] = -((float)i - ((float)slot_count)*0.5f)*0.45f;
1035 mdl_transform_mul( &mark_rack->transform, &xform, &xform );
1036
1037 if( slot->cache_ptr->registry_id ==
1038 global_skateshop.selected_registry_id ){
1039 selected = 1.0f;
1040 }
1041
1042 float t = slot->view_blend;
1043 v3_lerp( xform.co, mark_display->transform.co, t, xform.co );
1044 q_nlerp( xform.q, mark_display->transform.q, t, xform.q );
1045 v3_lerp( xform.s, mark_display->transform.s, t, xform.s );
1046
1047 m4x3f mmdl;
1048 mdl_transform_m4x3( &xform, mmdl );
1049 render_board( &main_camera, world, &slot->cache_ptr->board, mmdl,
1050 k_board_shader_entity );
1051
1052 fade_out:;
1053 float rate = 5.0f*vg.time_delta;
1054 slot->view_blend = vg_lerpf( slot->view_blend, selected, rate );
1055 }
1056
1057 ent_marker *mark_info = mdl_arritm( &world->ent_marker,
1058 mdl_entity_id_id(shop->boards.id_info));
1059 m4x3f mtext, mrack;
1060 mdl_transform_m4x3( &mark_info->transform, mtext );
1061 mdl_transform_m4x3( &mark_rack->transform, mrack );
1062
1063 #if 0
1064 const char *text_title = "Fish - Title";
1065 const char *text_author = "by Shaniqua";
1066 #endif
1067
1068 m4x3f mlocal, mmdl;
1069 m4x3_identity( mlocal );
1070
1071 float scale = 0.2f,
1072 thickness = 0.03f;
1073
1074 font3d_bind( &gui.font, &main_camera );
1075 shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
1076
1077 /* Selection counter
1078 * ------------------------------------------------------------------ */
1079 m3x3_zero( mlocal );
1080 v3_zero( mlocal[3] );
1081 mlocal[0][0] = -scale*2.0f;
1082 mlocal[1][2] = -scale*2.0f;
1083 mlocal[2][1] = -thickness;
1084 mlocal[3][2] = -0.7f;
1085 m4x3_mul( mrack, mlocal, mmdl );
1086
1087 if( global_skateshop.registry_count == 0 ){
1088 font3d_simple_draw( &gui.font, 0,
1089 "Nothing installed", &main_camera, mmdl );
1090 }
1091 else{
1092 char buf[16];
1093 int i=0;
1094 i+=highscore_intl( buf+i, global_skateshop.selected_registry_id+1, 3 );
1095 buf[i++] = '/';
1096 i+=highscore_intl( buf+i, global_skateshop.registry_count, 3 );
1097 buf[i++] = '\0';
1098
1099 font3d_simple_draw( &gui.font, 0, buf, &main_camera, mmdl );
1100 }
1101
1102 struct cache_board *cache_ptr = skateshop_selected_cache_if_loaded();
1103 if( !cache_ptr ) return;
1104
1105 struct registry_board *reg =
1106 &global_skateshop.registry[cache_ptr->registry_id];
1107
1108 #if 0
1109 struct workshop_file_info *info = &reg->workshop;
1110
1111 /* Skin title
1112 * ----------------------------------------------------------------- */
1113 m3x3_zero( mlocal );
1114 m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
1115 mlocal[3][0] = -font3d_string_width( &gui.font, 0, info->title );
1116 mlocal[3][0] *= scale*0.5f;
1117 mlocal[3][1] = 0.1f;
1118 mlocal[3][2] = 0.0f;
1119 m4x3_mul( mtext, mlocal, mmdl );
1120 font3d_simple_draw( &gui.font, 0, info->title, &main_camera, mmdl );
1121
1122 /* Author name
1123 * ----------------------------------------------------------------- */
1124 scale *= 0.4f;
1125 m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
1126 mlocal[3][0] = -font3d_string_width( &gui.font, 0, info->author_name );
1127 mlocal[3][0] *= scale*0.5f;
1128 mlocal[3][1] = 0.0f;
1129 mlocal[3][2] = 0.0f;
1130 m4x3_mul( mtext, mlocal, mmdl );
1131 font3d_simple_draw( &gui.font, 0, info->author_name, &main_camera, mmdl );
1132 #endif
1133 }
1134
1135 VG_STATIC void skateshop_render_charshop(void)
1136 {
1137 }
1138
1139 VG_STATIC void skateshop_render_worldshop(void)
1140 {
1141 world_instance *world = world_current_instance();
1142
1143 ent_skateshop *shop = global_skateshop.ptr_ent;
1144 ent_marker *mark_display = mdl_arritm( &world->ent_marker,
1145 mdl_entity_id_id(shop->worlds.id_display)),
1146 *mark_info = mdl_arritm( &world->ent_marker,
1147 mdl_entity_id_id(shop->boards.id_info));
1148
1149 /* Text */
1150 char buftext[128], bufsubtext[128];
1151 vg_str info, subtext;
1152 vg_strnull( &info, buftext, 128 );
1153 vg_strnull( &subtext, bufsubtext, 128 );
1154
1155 if( global_skateshop.world_registry_count ){
1156 struct registry_world *rw = &global_skateshop.world_registry[
1157 global_skateshop.selected_world_id ];
1158
1159 info.i+=highscore_intl( info.buffer+info.i,
1160 global_skateshop.selected_world_id+1, 3 );
1161 info.buffer[info.i++] = '/';
1162 info.i+=highscore_intl( info.buffer+info.i,
1163 global_skateshop.world_registry_count, 3 );
1164 info.buffer[info.i++] = ' ';
1165 info.buffer[info.i] = '\0';
1166
1167 vg_strcat( &info, rw->foldername );
1168 if( skaterift.async_op == k_async_op_world_loading ||
1169 skaterift.async_op == k_async_op_world_preloading ){
1170 vg_strcat( &subtext, "Loading..." );
1171 }
1172 else{
1173 vg_strcat( &subtext, "No information" );
1174 }
1175 }
1176 else{
1177 vg_strcat( &info, "No worlds installed" );
1178 }
1179
1180
1181 m4x3f mtext,mlocal,mtextmdl;
1182 mdl_transform_m4x3( &mark_info->transform, mtext );
1183
1184 font3d_bind( &gui.font, &main_camera );
1185 shader_model_font_uColour( (v4f){1.0f,1.0f,1.0f,1.0f} );
1186
1187 float scale = 0.2f, thickness = 0.015f, scale1 = 0.08f;
1188 m3x3_zero( mlocal );
1189 m3x3_setdiagonalv3( mlocal, (v3f){ scale, scale, thickness } );
1190 mlocal[3][0] = -font3d_string_width( &gui.font, 0, buftext );
1191 mlocal[3][0] *= scale*0.5f;
1192 mlocal[3][1] = 0.1f;
1193 mlocal[3][2] = 0.0f;
1194 m4x3_mul( mtext, mlocal, mtextmdl );
1195 font3d_simple_draw( &gui.font, 0, buftext, &main_camera, mtextmdl );
1196
1197 m3x3_setdiagonalv3( mlocal, (v3f){ scale1, scale1, thickness } );
1198 mlocal[3][0] = -font3d_string_width( &gui.font, 0, bufsubtext );
1199 mlocal[3][0] *= scale1*0.5f;
1200 mlocal[3][1] = -scale1*0.3f;
1201 m4x3_mul( mtext, mlocal, mtextmdl );
1202 font3d_simple_draw( &gui.font, 0, bufsubtext, &main_camera, mtextmdl );
1203
1204 /* pointcloud */
1205 m4x3f mmdl;
1206 mdl_transform_m4x3( &mark_display->transform, mmdl );
1207 m4x3_rotate_y( mmdl, vg.time * 0.2 );
1208
1209 glEnable(GL_BLEND);
1210 glBlendFunc(GL_ONE, GL_ONE);
1211 glDisable(GL_DEPTH_TEST);
1212 pointcloud_render( world, &main_camera, mmdl );
1213 glDisable(GL_BLEND);
1214 glEnable(GL_DEPTH_TEST);
1215 }
1216
1217 /*
1218 * World: render event
1219 */
1220 VG_STATIC void skateshop_render(void)
1221 {
1222 if( !global_skateshop.active ) return;
1223
1224 ent_skateshop *shop = global_skateshop.ptr_ent;
1225
1226 if( shop->type == k_skateshop_type_boardshop ){
1227 skateshop_render_boardshop();
1228 }
1229 else if( shop->type == k_skateshop_type_charshop ){
1230 skateshop_render_charshop();
1231 }
1232 else if( shop->type == k_skateshop_type_worldshop ){
1233 skateshop_render_worldshop();
1234 }
1235 else{
1236 vg_fatal_error( "Unknown store (%u)\n", shop->type );
1237 }
1238 }
1239
1240 /*
1241 * Entity logic: entrance event
1242 */
1243 VG_STATIC void ent_skateshop_call( world_instance *world, ent_call *call )
1244 {
1245 u32 index = mdl_entity_id_id( call->id );
1246 ent_skateshop *shop = mdl_arritm( &world->ent_skateshop, index );
1247 vg_info( "skateshop_call\n" );
1248
1249 if( menu.active ) return;
1250 if( skaterift.async_op != k_async_op_none ) return;
1251
1252 if( call->function == k_ent_function_trigger ){
1253 if( localplayer.subsystem != k_player_subsystem_walk ){
1254 return;
1255 }
1256
1257 vg_info( "Entering skateshop\n" );
1258
1259 localplayer.immobile = 1;
1260 menu.disable_open = 1;
1261 global_skateshop.active = 1;
1262
1263 v3_zero( localplayer.rb.v );
1264 v3_zero( localplayer.rb.w );
1265 localplayer._walk.move_speed = 0.0f;
1266 global_skateshop.ptr_ent = shop;
1267
1268 if( shop->type == k_skateshop_type_boardshop ){
1269 skateshop_update_viewpage();
1270 workshop_op_item_scan();
1271 }
1272 else if( shop->type == k_skateshop_type_worldshop ){
1273 pointcloud_animate( k_pointcloud_anim_opening );
1274 skateshop_op_world_scan();
1275 }
1276 }
1277 }
1278
1279 /*
1280 * Entity logic: exit event
1281 */
1282 VG_STATIC void global_skateshop_exit(void)
1283 {
1284 vg_info( "exit skateshop\n" );
1285 localplayer.immobile = 0;
1286 global_skateshop.active = 0;
1287 menu.disable_open = 0;
1288 srinput.ignore_input_frames = 2;
1289 }
1290
1291 #endif /* ENT_SKATESHOP_C */