1 #ifndef ENT_SKATESHOP_C
2 #define ENT_SKATESHOP_C
6 #include "vg/vg_steam_ugc.h"
8 #include "ent_skateshop.h"
13 #include "pointcloud.h"
14 #include "highscores.h"
18 * Checks string equality but does a hash check first
20 static inline int const_str_eq( u32 hash
, const char *str
, const char *cmp
)
22 if( hash
== vg_strdjb2(cmp
) )
23 if( !strcmp( str
, cmp
) )
29 * Get an existing cache instance, allocate a new one to be loaded, or NULL if
32 VG_STATIC
struct cache_board
*skateshop_cache_fetch( u32 registry_index
)
34 struct registry_board
*reg
= NULL
;
36 if( registry_index
< global_skateshop
.registry_count
){
37 reg
= &global_skateshop
.registry
[ registry_index
];
40 return reg
->cache_ptr
;
44 /* lru eviction. should be a linked list maybe... */
45 double min_time
= 1e300
;
46 struct cache_board
*min_board
= NULL
;
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
];
52 if( cache_ptr
->state
== k_cache_board_state_load_request
) continue;
53 if( cache_ptr
->ref_count
) continue;
55 if( cache_ptr
->last_use_time
< min_time
){
56 min_time
= cache_ptr
->last_use_time
;
57 min_board
= cache_ptr
;
62 if( min_board
->state
== k_cache_board_state_loaded
){
63 struct registry_board
*other
=
64 &global_skateshop
.registry
[ min_board
->registry_id
];
66 vg_info( "Deallocating board: '%s'\n", min_board
, other
->foldername
);
68 player_board_unload( &min_board
->board
);
69 other
->cache_ptr
= NULL
;
73 vg_info( "Allocating board (reg:%u) '%s'\n",
74 registry_index
, reg
->foldername
);
77 vg_info( "Pre-allocating board (reg:%u) 'null'\n", registry_index
);
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
;
86 vg_error( "No free boards to load registry!\n" );
89 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
93 VG_STATIC
void skateshop_update_viewpage(void)
95 u32 page
= global_skateshop
.selected_registry_id
/SKATESHOP_VIEW_SLOT_MAX
;
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
;
101 if( slot
->cache_ptr
) unwatch_cache_board( slot
->cache_ptr
);
103 slot
->cache_ptr
= skateshop_cache_fetch( request_id
);
104 if( slot
->cache_ptr
) watch_cache_board( slot
->cache_ptr
);
109 * op/subroutine: k_workshop_op_item_load
110 * -----------------------------------------------------------------------------
114 * Reciever for board completion; only promotes the status in the main thread
116 VG_STATIC
void skateshop_async_board_loaded( void *payload
, u32 size
)
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
;
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
);
128 vg_success( "Async board loaded (%s)\n", reg
->foldername
);
132 * Thread(or subroutine of thread), for checking view slots that weve installed.
133 * Load the model if a view slot wants it
135 VG_STATIC
void workshop_visibile_load_loop_thread( void *_args
)
140 for( u32 i
=0; i
<SKATESHOP_BOARD_CACHE_MAX
; i
++ ){
141 struct cache_board
*cache_ptr
= &global_skateshop
.cache
[i
];
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
);
152 /* continue with the request */
153 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
155 struct registry_board
*reg
=
156 &global_skateshop
.registry
[ cache_ptr
->registry_id
];
158 if( reg
->workshop_id
){
159 vg_async_item
*call
=
160 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
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! */
169 if( path_buf
[0] == '\0' ){
170 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64
")\n",
176 folder
.buffer
= path_buf
;
177 folder
.i
= strlen(path_buf
);
181 vg_strnull( &folder
, path_buf
, 4096 );
182 vg_strcat( &folder
, "boards/" );
183 vg_strcat( &folder
, reg
->foldername
);
186 vg_str meta_path
= folder
;
187 vg_strcat( &meta_path
, "/addon.inf" );
189 if( !vg_strgood( &meta_path
) ) {
190 vg_error( "Metadata path too long\n" );
195 FILE *fp
= fopen( meta_path
.buffer
, "rb" );
197 if( !fp
) goto file_is_broken
;
199 u32 l
= fread( meta
, 1, 512, fp
);
203 vg_error( "unknown error codition" );
209 /* load content files
210 * --------------------------------- */
212 vg_str content_path
= folder
;
215 vg_msg_init( &msg
, meta
, l
);
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
);
226 if( !vg_strgood( &content_path
) ) {
227 vg_error( "Metadata path too long\n" );
231 player_board_load( &cache_ptr
->board
, content_path
.buffer
);
232 vg_async_call( skateshop_async_board_loaded
, cache_ptr
, 0 );
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
);
241 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
243 vg_async_call( workshop_async_any_complete
, NULL
, 0 );
247 * op: k_workshop_op_item_scan
248 * -----------------------------------------------------------------------------
252 * Reciever for scan completion. copies the registry_count back into t0
254 VG_STATIC
void workshop_async_reg_update( void *data
, u32 size
)
256 vg_info( "Registry update notify\n" );
257 global_skateshop
.registry_count
= global_skateshop
.t1_registry_count
;
260 VG_STATIC
void workshop_steam_scan(void)
263 * Steam workshop scan
265 vg_info( "Scanning steam workshop for boards\n" );
266 PublishedFileId_t workshop_ids
[ SKATESHOP_REGISTRY_MAX
];
267 u32 workshop_count
= SKATESHOP_REGISTRY_MAX
;
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
);
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
];
284 if( reg
->workshop_id
== id
){
285 reg
->state
= k_registry_board_state_indexed
;
286 goto next_file_workshop
;
289 for( u32 i
=0; i
<global_skateshop
.t1_world_registry_count
; i
++ ){
290 struct registry_world
*reg
= &global_skateshop
.world_registry
[i
];
292 if( reg
->workshop_id
== id
){
293 reg
->state
= k_registry_board_state_indexed
;
294 goto next_file_workshop
;
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
) );
306 struct async_workshop_filepath_info
*info
= call1
->payload
;
309 info
->len
= vg_list_size(path
);
310 vg_async_dispatch( call1
, async_workshop_get_filepath
);
311 vg_async_stall(); /* too bad! */
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
;
323 FILE *fp
= fopen( meta_path
.buffer
, "rb" );
325 vg_error( "Could not open the '%s'\n", meta_path
.buffer
);
326 goto next_file_workshop
;
329 u32 l
= fread( meta
, 1, 512, fp
);
333 vg_error( "unknown error codition" );
334 goto next_file_workshop
;
339 enum workshop_file_type type
= k_workshop_file_type_none
;
341 vg_msg_init( &msg
, meta
, l
);
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
;
359 if( type
== k_workshop_file_type_none
){
360 vg_error( "Cannot determine addon type\n" );
361 goto next_file_workshop
;
364 /* now that we have the type
365 * ------------------------------------------------------------------*/
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
;
373 struct registry_board
*reg
= &global_skateshop
.registry
[
374 global_skateshop
.t1_registry_count
++ ];
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
;
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
;
388 struct registry_world
*reg
= &global_skateshop
.world_registry
[
389 global_skateshop
.t1_world_registry_count
++ ];
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
;
403 * Async thread which scans local files for boards, as well as scheduling
404 * synchronous calls to the workshop
406 VG_STATIC
void workshop_scan_thread( void *_args
)
408 vg_linear_clear( vg_mem
.scratch
);
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
;
419 vg_info( "Scanning models/boards/*.mdl\n" );
421 tinydir_open( &dir
, "models/boards" );
423 while( dir
.has_next
){
425 tinydir_readfile( &dir
, &file
);
428 u32 hash
= vg_strdjb2( file
.name
);
430 for( u32 i
=0; i
<global_skateshop
.t1_registry_count
; i
++ ){
431 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
433 if( const_str_eq( hash
, file
.name
, reg
->filename
) ){
434 reg
->state
= k_registry_board_state_indexed
;
439 if( global_skateshop
.t1_registry_count
== SKATESHOP_REGISTRY_MAX
){
440 vg_error( "You have too many boards installed!\n" );
444 vg_info( "new listing!: %s\n", file
.name
);
446 struct registry_board
*reg
=
447 &global_skateshop
.registry
[global_skateshop
.t1_registry_count
++];
449 reg
->cache_ptr
= NULL
;
450 vg_strncpy( file
.name
, reg
->filename
, 64, k_strncpy_always_add_null
);
452 vg_strncpy( file
.name
, reg
->workshop
.title
,
453 64, k_strncpy_always_add_null
);
455 reg
->filename_hash
= hash
;
456 reg
->workshop_id
= 0;
457 reg
->state
= k_registry_board_state_indexed
;
460 reg
->workshop
.author
= 0;
461 strcpy( reg
->workshop
.author_name
, "custom" );
465 next_file
: tinydir_next( &dir
);
471 if( steam_ready
) workshop_steam_scan();
473 vg_async_call( workshop_async_reg_update
, NULL
, 0 );
475 workshop_visibile_load_loop_thread(NULL
);
479 * Asynchronous scan of local disk for items and add them to the registry
481 VG_STATIC
void workshop_op_item_scan(void)
483 skaterift_begin_op( k_workshop_op_item_scan
);
484 vg_loader_start( workshop_scan_thread
, NULL
);
488 * op: k_async_op_world_scan
489 * -----------------------------------------------------------------------------
493 * Reciever for scan completion. copies the registry_count back into t0
495 VG_STATIC
void workshop_async_world_reg_update( void *data
, u32 size
)
497 vg_info( "World registry update notify\n" );
498 global_skateshop
.world_registry_count
=
499 global_skateshop
.t1_world_registry_count
;
504 * Add a local world folder to the registry, it will verify existing ones are
507 VG_STATIC
void world_scan_register_local( const char *folder_name
)
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
];
514 if( const_str_eq( hash
, folder_name
, reg
->foldername
) ){
515 reg
->state
= k_registry_board_state_indexed
;
520 if( global_skateshop
.t1_world_registry_count
== SKATESHOP_WORLDS_MAX
){
521 vg_error( "You have too many worlds installed!\n" );
525 vg_info( "new listing!: %s\n", folder_name
);
527 struct registry_world
*reg
= &global_skateshop
.world_registry
[
528 global_skateshop
.t1_world_registry_count
++ ];
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
;
538 * Async thread which scans local files for boards, as well as scheduling
539 * synchronous calls to the workshop
541 VG_STATIC
void world_scan_thread( void *_args
)
543 vg_linear_clear( vg_mem
.scratch
);
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
;
553 vg_info( "Scanning maps/*.mdl\n" );
557 vg_strnull( &path
, path_buf
, 4096 );
558 vg_strcat( &path
, "maps/" );
560 DIR *dir
= opendir( path
.buffer
);
562 vg_error( "opendir('maps') failed\n" );
563 vg_async_call( workshop_async_any_complete
, NULL
, 0 );
567 struct dirent
*entry
;
568 while( (entry
= readdir(dir
)) ){
569 if( entry
->d_type
== DT_DIR
){
570 if( entry
->d_name
[0] == '.' ) continue;
572 vg_str folder
= path
;
573 char *folder_name
= folder
.buffer
+folder
.i
;
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
);
581 vg_strcat( &folder
, entry
->d_name
);
582 if( !vg_strgood( &folder
) ) break;
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;
589 vg_str file
= folder
;
590 vg_strcat( &file
, "/" );
591 vg_strcat( &file
, entry
->d_name
);
592 if( !vg_strgood( &file
) ) continue;
594 char *ext
= vg_strch( &file
, '.' );
596 if( strcmp(ext
,".mdl") ) continue;
598 vg_strcat( &folder
, "" );
599 world_scan_register_local( folder_name
);
607 vg_async_call( workshop_async_world_reg_update
, NULL
, 0 );
612 if( steam_ready
) workshop_steam_scan();
614 vg_async_call( workshop_async_reg_update
, NULL
, 0 );
616 workshop_visibile_load_loop_thread(NULL
);
621 * Asynchronous scan of local disk for worlds
623 VG_STATIC
void skateshop_op_world_scan(void)
625 skaterift_begin_op( k_async_op_world_scan
);
626 vg_loader_start( world_scan_thread
, NULL
);
631 * -----------------------------------------------------------------------------
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
)
638 if( ptr
->ref_count
>= 32 ){
639 vg_fatal_error( "dynamic board watch missmatch (limit is 32)\n" );
642 ptr
->last_use_time
= vg
.time
;
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
)
650 if( ptr
->ref_count
== 0 ){
651 vg_fatal_error( "dynamic board unwatch missmatch (no watchers)\n" );
658 * Callback handler for persona state changes,
659 * it sets the author names on the registries
661 VG_STATIC
void callback_persona_statechange( CallbackMsg_t
*msg
)
663 PersonaStateChange_t
*info
= (PersonaStateChange_t
*)msg
->m_pubParam
;
664 ISteamFriends
*hSteamFriends
= SteamAPI_SteamFriends();
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 );
683 VG_STATIC
void skateshop_init(void)
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
;
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
);
694 memset( global_skateshop
.cache
, 0, cache_size
);
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;
705 steam_register_callback( k_iPersonaStateChange
,
706 callback_persona_statechange
);
710 VG_STATIC
struct cache_board
*skateshop_selected_cache_if_loaded(void)
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
];
716 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
717 if( reg
->cache_ptr
&&
718 (reg
->cache_ptr
->state
== k_cache_board_state_loaded
) )
720 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
721 return reg
->cache_ptr
;
723 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
729 VG_STATIC
void pointcloud_async_end(void *_
, u32 __
)
731 pointcloud_animate( k_pointcloud_anim_opening
);
735 VG_STATIC
void pointcloud_clear_async(void *_
, u32 __
)
737 pointcloud
.count
= 0;
738 pointcloud_animate( k_pointcloud_anim_opening
);
742 VG_STATIC
void skateshop_preview_loader_thread( void *_data
)
744 struct registry_world
*reg
= _data
;
746 if( reg
->type
== k_world_load_type_local
){
749 vg_strnull( &path
, path_buf
, 4096 );
750 vg_strcat( &path
, "maps/" );
751 vg_strcat( &path
, reg
->foldername
);
752 vg_strcat( &path
, "/preview.bin" );
754 vg_linear_clear(vg_mem
.scratch
);
757 void *data
= vg_file_read( vg_mem
.scratch
, path_buf
, &size
);
759 if( size
< sizeof(pointcloud_buffer
) ){
760 vg_async_call( pointcloud_clear_async
, NULL
, 0 );
764 vg_async_item
*call
= vg_async_alloc(size
);
765 pointcloud_buffer
*pcbuf
= call
->payload
;
766 memcpy( pcbuf
, data
, size
);
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
;
774 vg_async_dispatch( call
, async_pointcloud_sub
);
775 vg_async_call( pointcloud_async_end
, NULL
, 0 );
778 vg_async_call( pointcloud_clear_async
, NULL
, 0 );
782 vg_async_call( pointcloud_clear_async
, NULL
, 0 );
786 VG_STATIC
void skateshop_load_world_preview( struct registry_world
*reg
)
788 skaterift_begin_op( k_async_op_world_load_preview
);
789 vg_loader_start( skateshop_preview_loader_thread
, reg
);
795 void temp_update_playermodel(void);
796 VG_STATIC
void global_skateshop_preupdate(void)
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
);
802 if( !global_skateshop
.active
) return;
804 world_instance
*world
= world_current_instance();
805 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
807 /* camera positioning */
808 ent_camera
*ref
= mdl_arritm( &world
->ent_camera
,
809 mdl_entity_id_id(shop
->id_camera
) );
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
);
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
) );
822 v3_sub( display
->transform
.co
, localplayer
.rb
.co
, lookat
);
825 else if( shop
->type
== k_skateshop_type_charshop
){
826 v3_sub( ref
->transform
.co
, localplayer
.rb
.co
, lookat
);
829 vg_fatal_error( "Unknown store (%u)\n", shop
->type
);
832 q_axis_angle( localplayer
.rb
.q
, (v3f
){0.0f
,1.0f
,0.0f
},
833 atan2f(lookat
[0],lookat
[2]) );
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
;
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" );
844 struct cache_board
*selected_cache
= skateshop_selected_cache_if_loaded();
846 if( selected_cache
){
847 gui_helper_action( button_display_string( k_srbind_maccept
), "pick" );
852 * ----------------------
855 if( button_down( k_srbind_mleft
) ){
856 if( global_skateshop
.selected_registry_id
> 0 ){
857 global_skateshop
.selected_registry_id
--;
861 if( button_down( k_srbind_mright
) ){
862 if( global_skateshop
.selected_registry_id
+1 <
863 global_skateshop
.registry_count
)
865 global_skateshop
.selected_registry_id
++;
869 if( selected_cache
&& button_down( k_srbind_maccept
) ){
870 vg_info( "chose board from skateshop (%u)\n",
871 global_skateshop
.selected_registry_id
);
873 if( localplayer
.board_view_slot
){
874 unwatch_cache_board( localplayer
.board_view_slot
);
877 localplayer
.board_view_slot
= selected_cache
;
878 watch_cache_board( localplayer
.board_view_slot
);
880 global_skateshop_exit();
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" );
889 if( button_down( k_srbind_mleft
) ){
890 if( cl_playermdl_id
> 0 ){
894 cl_playermdl_id
= 2; /* HACK */
896 temp_update_playermodel(); /* HACK */
899 if( button_down( k_srbind_mright
) ){
900 if( cl_playermdl_id
+1 < 3 ){
904 cl_playermdl_id
= 0; /* HACK */
906 temp_update_playermodel(); /* HACK */
910 if( button_down( k_srbind_maccept
) ){
911 global_skateshop_exit();
914 else if( shop
->type
== k_skateshop_type_worldshop
){
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" );
925 if( skaterift
.async_op
== k_async_op_none
){
926 gui_helper_action( button_display_string(k_srbind_maccept
), "load" );
933 if( button_down( k_srbind_mleft
) ){
934 if( global_skateshop
.selected_world_id
> 0 )
936 global_skateshop
.selected_world_id
--;
941 if( button_down( k_srbind_mright
) ){
942 if( global_skateshop
.selected_world_id
+1 <
943 global_skateshop
.world_registry_count
)
945 global_skateshop
.selected_world_id
++;
951 if( change
&& pointcloud_idle() ){
952 pointcloud_animate( k_pointcloud_anim_hiding
);
955 if( skaterift
.async_op
== k_async_op_none
){
956 struct registry_world
*rw
= &global_skateshop
.world_registry
[
957 global_skateshop
.selected_world_id
];
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
);
967 if( pointcloud
.anim
== k_pointcloud_anim_idle_closed
){
968 if( global_skateshop
.pointcloud_world_id
!=
969 global_skateshop
.selected_world_id
)
971 global_skateshop
.pointcloud_world_id
=
972 global_skateshop
.selected_world_id
;
973 skateshop_load_world_preview( rw
);
976 pointcloud_animate( k_pointcloud_anim_opening
);
979 else if( pointcloud
.anim
== k_pointcloud_anim_idle_open
){
980 if( global_skateshop
.pointcloud_world_id
!=
981 global_skateshop
.selected_world_id
)
983 pointcloud_animate( k_pointcloud_anim_hiding
);
990 vg_fatal_error( "Unknown store (%u)\n", shop
->type
);
993 if( button_down( k_srbind_mback
) ){
994 global_skateshop_exit();
999 VG_STATIC
void skateshop_render_boardshop(void)
1001 world_instance
*world
= world_current_instance();
1002 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
1004 u32 slot_count
= vg_list_size(global_skateshop
.shop_view_slots
);
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
));
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
];
1018 if( slot
->cache_ptr
== NULL
) visibility
[i
] = 0;
1019 else if( slot
->cache_ptr
->state
!= k_cache_board_state_loaded
)
1022 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
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
;
1029 if( !visibility
[i
] ) goto fade_out
;
1031 mdl_transform xform
;
1032 transform_identity( &xform
);
1034 xform
.co
[0] = -((float)i
- ((float)slot_count
)*0.5f
)*0.45f
;
1035 mdl_transform_mul( &mark_rack
->transform
, &xform
, &xform
);
1037 if( slot
->cache_ptr
->registry_id
==
1038 global_skateshop
.selected_registry_id
){
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
);
1048 mdl_transform_m4x3( &xform
, mmdl
);
1049 render_board( &main_camera
, world
, &slot
->cache_ptr
->board
, mmdl
,
1050 k_board_shader_entity
);
1053 float rate
= 5.0f
*vg
.time_delta
;
1054 slot
->view_blend
= vg_lerpf( slot
->view_blend
, selected
, rate
);
1057 ent_marker
*mark_info
= mdl_arritm( &world
->ent_marker
,
1058 mdl_entity_id_id(shop
->boards
.id_info
));
1060 mdl_transform_m4x3( &mark_info
->transform
, mtext
);
1061 mdl_transform_m4x3( &mark_rack
->transform
, mrack
);
1064 const char *text_title
= "Fish - Title";
1065 const char *text_author
= "by Shaniqua";
1069 m4x3_identity( mlocal
);
1074 font3d_bind( &gui
.font
, &main_camera
);
1075 shader_model_font_uColour( (v4f
){1.0f
,1.0f
,1.0f
,1.0f
} );
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
);
1087 if( global_skateshop
.registry_count
== 0 ){
1088 font3d_simple_draw( &gui
.font
, 0,
1089 "Nothing installed", &main_camera
, mmdl
);
1094 i
+=highscore_intl( buf
+i
, global_skateshop
.selected_registry_id
+1, 3 );
1096 i
+=highscore_intl( buf
+i
, global_skateshop
.registry_count
, 3 );
1099 font3d_simple_draw( &gui
.font
, 0, buf
, &main_camera
, mmdl
);
1102 struct cache_board
*cache_ptr
= skateshop_selected_cache_if_loaded();
1103 if( !cache_ptr
) return;
1105 struct registry_board
*reg
=
1106 &global_skateshop
.registry
[cache_ptr
->registry_id
];
1109 struct workshop_file_info
*info
= ®
->workshop
;
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
);
1123 * ----------------------------------------------------------------- */
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
);
1135 VG_STATIC
void skateshop_render_charshop(void)
1139 VG_STATIC
void skateshop_render_worldshop(void)
1141 world_instance
*world
= world_current_instance();
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
));
1150 char buftext
[128], bufsubtext
[128];
1151 vg_str info
, subtext
;
1152 vg_strnull( &info
, buftext
, 128 );
1153 vg_strnull( &subtext
, bufsubtext
, 128 );
1155 if( global_skateshop
.world_registry_count
){
1156 struct registry_world
*rw
= &global_skateshop
.world_registry
[
1157 global_skateshop
.selected_world_id
];
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';
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..." );
1173 vg_strcat( &subtext
, "No information" );
1177 vg_strcat( &info
, "No worlds installed" );
1181 m4x3f mtext
,mlocal
,mtextmdl
;
1182 mdl_transform_m4x3( &mark_info
->transform
, mtext
);
1184 font3d_bind( &gui
.font
, &main_camera
);
1185 shader_model_font_uColour( (v4f
){1.0f
,1.0f
,1.0f
,1.0f
} );
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
);
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
);
1206 mdl_transform_m4x3( &mark_display
->transform
, mmdl
);
1207 m4x3_rotate_y( mmdl
, vg
.time
* 0.2 );
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
);
1218 * World: render event
1220 VG_STATIC
void skateshop_render(void)
1222 if( !global_skateshop
.active
) return;
1224 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
1226 if( shop
->type
== k_skateshop_type_boardshop
){
1227 skateshop_render_boardshop();
1229 else if( shop
->type
== k_skateshop_type_charshop
){
1230 skateshop_render_charshop();
1232 else if( shop
->type
== k_skateshop_type_worldshop
){
1233 skateshop_render_worldshop();
1236 vg_fatal_error( "Unknown store (%u)\n", shop
->type
);
1241 * Entity logic: entrance event
1243 VG_STATIC
void ent_skateshop_call( world_instance
*world
, ent_call
*call
)
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" );
1249 if( menu
.active
) return;
1250 if( skaterift
.async_op
!= k_async_op_none
) return;
1252 if( call
->function
== k_ent_function_trigger
){
1253 if( localplayer
.subsystem
!= k_player_subsystem_walk
){
1257 vg_info( "Entering skateshop\n" );
1259 localplayer
.immobile
= 1;
1260 menu
.disable_open
= 1;
1261 global_skateshop
.active
= 1;
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
;
1268 if( shop
->type
== k_skateshop_type_boardshop
){
1269 skateshop_update_viewpage();
1270 workshop_op_item_scan();
1272 else if( shop
->type
== k_skateshop_type_worldshop
){
1273 pointcloud_animate( k_pointcloud_anim_opening
);
1274 skateshop_op_world_scan();
1280 * Entity logic: exit event
1282 VG_STATIC
void global_skateshop_exit(void)
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;
1291 #endif /* ENT_SKATESHOP_C */