1 #ifndef ENT_SKATESHOP_C
2 #define ENT_SKATESHOP_C
6 #include "vg/vg_steam_ugc.h"
7 #include "ent_skateshop.h"
12 #include "pointcloud.h"
15 * Checks string equality but does a hash check first
17 static inline int const_str_eq( u32 hash
, const char *str
, const char *cmp
)
19 if( hash
== vg_strdjb2(cmp
) )
20 if( !strcmp( str
, cmp
) )
26 * Get an existing cache instance, allocate a new one to be loaded, or NULL if
29 VG_STATIC
struct cache_board
*skateshop_cache_fetch( u32 registry_index
)
31 struct registry_board
*reg
= NULL
;
33 if( registry_index
< global_skateshop
.registry_count
){
34 reg
= &global_skateshop
.registry
[ registry_index
];
37 return reg
->cache_ptr
;
41 /* lru eviction. should be a linked list maybe... */
42 double min_time
= 1e300
;
43 struct cache_board
*min_board
= NULL
;
45 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
46 for( u32 i
=0; i
<SKATESHOP_BOARD_CACHE_MAX
; i
++ ){
47 struct cache_board
*cache_ptr
= &global_skateshop
.cache
[i
];
49 if( cache_ptr
->state
== k_cache_board_state_load_request
) continue;
50 if( cache_ptr
->ref_count
) continue;
52 if( cache_ptr
->last_use_time
< min_time
){
53 min_time
= cache_ptr
->last_use_time
;
54 min_board
= cache_ptr
;
59 if( min_board
->state
== k_cache_board_state_loaded
){
60 struct registry_board
*other
=
61 &global_skateshop
.registry
[ min_board
->registry_id
];
63 vg_info( "Deallocating board: '%s'\n", min_board
, other
->filename
);
65 player_board_unload( &min_board
->board
);
66 other
->cache_ptr
= NULL
;
70 vg_info( "Allocating board (reg:%u) '%s'\n",
71 registry_index
, reg
->filename
);
74 vg_info( "Pre-allocating board (reg:%u) 'null'\n", registry_index
);
77 min_board
->registry_id
= registry_index
;
78 min_board
->last_use_time
= vg
.time
;
79 min_board
->ref_count
= 0;
80 min_board
->state
= k_cache_board_state_load_request
;
83 vg_error( "No free boards to load registry!\n" );
86 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
90 VG_STATIC
void skateshop_update_viewpage(void)
92 u32 page
= global_skateshop
.selected_registry_id
/SKATESHOP_VIEW_SLOT_MAX
;
94 for( u32 i
=0; i
<SKATESHOP_VIEW_SLOT_MAX
; i
++ ){
95 struct shop_view_slot
*slot
= &global_skateshop
.shop_view_slots
[i
];
96 u32 request_id
= page
*SKATESHOP_VIEW_SLOT_MAX
+ i
;
98 if( slot
->cache_ptr
) unwatch_cache_board( slot
->cache_ptr
);
100 slot
->cache_ptr
= skateshop_cache_fetch( request_id
);
101 if( slot
->cache_ptr
) watch_cache_board( slot
->cache_ptr
);
105 /* generic reciever */
106 VG_STATIC
void workshop_async_any_complete( void *data
, u32 size
)
112 * op/subroutine: k_workshop_op_item_load
113 * -----------------------------------------------------------------------------
117 * Reciever for board completion; only promotes the status in the main thread
119 VG_STATIC
void skateshop_async_board_loaded( void *payload
, u32 size
)
121 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
122 struct cache_board
*cache_ptr
= payload
;
123 cache_ptr
->last_use_time
= vg
.time
;
124 cache_ptr
->state
= k_cache_board_state_loaded
;
126 struct registry_board
*reg
=
127 &global_skateshop
.registry
[ cache_ptr
->registry_id
];
128 reg
->cache_ptr
= cache_ptr
;
129 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
131 vg_success( "Async board loaded (%s)\n", reg
->filename
);
135 * Thread(or subroutine of thread), for checking view slots that weve installed.
136 * Load the model if a view slot wants it
138 VG_STATIC
void workshop_visibile_load_loop_thread( void *_args
)
141 for( u32 i
=0; i
<SKATESHOP_BOARD_CACHE_MAX
; i
++ ){
142 struct cache_board
*cache_ptr
= &global_skateshop
.cache
[i
];
144 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
145 if( cache_ptr
->state
== k_cache_board_state_load_request
){
146 if( cache_ptr
->registry_id
>= global_skateshop
.registry_count
){
147 /* should maybe have a different value for this case */
148 cache_ptr
->state
= k_cache_board_state_none
;
149 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
153 /* continue with the request */
154 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
156 struct registry_board
*reg
=
157 &global_skateshop
.registry
[ cache_ptr
->registry_id
];
159 if( reg
->workshop_id
){
160 vg_async_item
*call
=
161 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
163 struct async_workshop_filepath_info
*info
= call
->payload
;
165 info
->id
= reg
->workshop_id
;
166 info
->len
= vg_list_size(path
) - strlen("/board.mdl")-1;
167 vg_async_dispatch( call
, async_workshop_get_filepath
);
168 vg_async_stall(); /* too bad! */
170 if( path
[0] == '\0' ){
171 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
172 cache_ptr
->state
= k_cache_board_state_none
;
173 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
175 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64
")\n",
180 strcat( path
, "/board.mdl" );
184 snprintf( path
, 256, "models/boards/%s", reg
->filename
);
187 player_board_load( &cache_ptr
->board
, path
);
188 vg_async_call( skateshop_async_board_loaded
, cache_ptr
, 0 );
191 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
193 vg_async_call( workshop_async_any_complete
, NULL
, 0 );
197 * op: k_workshop_op_item_scan
198 * -----------------------------------------------------------------------------
202 * Reciever for scan completion. copies the registry_count back into t0
204 VG_STATIC
void workshop_async_reg_update( void *data
, u32 size
)
206 vg_info( "Registry update notify\n" );
207 global_skateshop
.registry_count
= global_skateshop
.t1_registry_count
;
210 VG_STATIC
void workshop_steam_scan(void)
213 * Steam workshop scan
215 vg_info( "Scanning steam workshop for boards\n" );
216 PublishedFileId_t workshop_ids
[ SKATESHOP_REGISTRY_MAX
];
217 u32 workshop_count
= SKATESHOP_REGISTRY_MAX
;
219 vg_async_item
*call
= vg_async_alloc(
220 sizeof(struct async_workshop_installed_files_info
));
221 struct async_workshop_installed_files_info
*info
= call
->payload
;
222 info
->buffer
= workshop_ids
;
223 info
->len
= &workshop_count
;
224 vg_async_dispatch( call
, async_workshop_get_installed_files
);
227 for( u32 j
=0; j
<workshop_count
; j
++ ){
228 PublishedFileId_t id
= workshop_ids
[j
];
230 for( u32 i
=0; i
<global_skateshop
.t1_registry_count
; i
++ ){
231 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
233 if( reg
->workshop_id
== id
){
234 reg
->state
= k_registry_board_state_indexed
;
235 goto next_file_workshop
;
239 if( global_skateshop
.t1_registry_count
== SKATESHOP_REGISTRY_MAX
){
240 vg_error( "You have too many boards installed!\n" );
244 vg_info( "new listing from the steam workshop!: "PRINTF_U64
"\n", id
);
246 struct registry_board
*reg
= &global_skateshop
.registry
[
247 global_skateshop
.t1_registry_count
++ ];
249 reg
->cache_ptr
= NULL
;
250 snprintf( reg
->filename
, 64, PRINTF_U64
, id
);
251 reg
->filename_hash
= vg_strdjb2( reg
->filename
);
252 reg
->workshop_id
= id
;
253 reg
->state
= k_registry_board_state_indexed
;
255 workshop_file_info_clear( ®
->workshop
);
256 strcpy( reg
->workshop
.title
, "Workshop file" );
258 /* load the metadata off the disk */
259 vg_async_item
*call
=
260 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
262 const char *meta_file
= "/board.mdl.inf";
264 struct async_workshop_filepath_info
*info
= call
->payload
;
266 info
->id
= reg
->workshop_id
;
267 info
->len
= vg_list_size(path
) - strlen(meta_file
)-1;
268 vg_async_dispatch( call
, async_workshop_get_filepath
);
269 vg_async_stall(); /* too bad! */
271 strcat( path
, meta_file
);
272 workshop_load_metadata( path
, ®
->workshop
);
279 * Async thread which scans local files for boards, as well as scheduling
280 * synchronous calls to the workshop
282 VG_STATIC
void workshop_scan_thread( void *_args
)
284 vg_linear_clear( vg_mem
.scratch
);
286 for( u32 i
=0; i
<global_skateshop
.t1_registry_count
; i
++ ){
287 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
288 reg
->state
= k_registry_board_state_indexed_absent
;
294 vg_info( "Scanning models/boards/*.mdl\n" );
296 tinydir_open( &dir
, "models/boards" );
298 while( dir
.has_next
){
300 tinydir_readfile( &dir
, &file
);
303 u32 hash
= vg_strdjb2( file
.name
);
305 for( u32 i
=0; i
<global_skateshop
.t1_registry_count
; i
++ ){
306 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
308 if( const_str_eq( hash
, file
.name
, reg
->filename
) ){
309 reg
->state
= k_registry_board_state_indexed
;
314 if( global_skateshop
.t1_registry_count
== SKATESHOP_REGISTRY_MAX
){
315 vg_error( "You have too many boards installed!\n" );
319 vg_info( "new listing!: %s\n", file
.name
);
321 struct registry_board
*reg
=
322 &global_skateshop
.registry
[global_skateshop
.t1_registry_count
++];
324 reg
->cache_ptr
= NULL
;
325 vg_strncpy( file
.name
, reg
->filename
, 64, k_strncpy_always_add_null
);
326 vg_strncpy( file
.name
, reg
->workshop
.title
,
327 64, k_strncpy_always_add_null
);
328 reg
->filename_hash
= hash
;
329 reg
->workshop_id
= 0;
330 reg
->state
= k_registry_board_state_indexed
;
331 reg
->workshop
.author
= 0;
332 strcpy( reg
->workshop
.author_name
, "custom" );
335 next_file
: tinydir_next( &dir
);
340 if( steam_ready
) workshop_steam_scan();
342 vg_async_call( workshop_async_reg_update
, NULL
, 0 );
344 workshop_visibile_load_loop_thread(NULL
);
348 * Asynchronous scan of local disk for items and add them to the registry
350 VG_STATIC
void workshop_op_item_scan(void)
352 skaterift_begin_op( k_workshop_op_item_scan
);
353 vg_loader_start( workshop_scan_thread
, NULL
);
357 * op: k_async_op_world_scan
358 * -----------------------------------------------------------------------------
362 * Reciever for scan completion. copies the registry_count back into t0
364 VG_STATIC
void workshop_async_world_reg_update( void *data
, u32 size
)
366 vg_info( "World registry update notify\n" );
367 global_skateshop
.world_registry_count
=
368 global_skateshop
.t1_world_registry_count
;
373 * Add a local world folder to the registry, it will verify existing ones are
376 VG_STATIC
void world_scan_register_local( const char *folder_name
)
378 u32 hash
= vg_strdjb2( folder_name
);
379 for( u32 i
=0; i
<global_skateshop
.t1_world_registry_count
; i
++ ){
380 struct registry_world
*reg
=
381 &global_skateshop
.world_registry
[i
];
383 if( const_str_eq( hash
, folder_name
, reg
->foldername
) ){
384 reg
->state
= k_registry_board_state_indexed
;
389 if( global_skateshop
.t1_world_registry_count
== SKATESHOP_WORLDS_MAX
){
390 vg_error( "You have too many worlds installed!\n" );
394 vg_info( "new listing!: %s\n", folder_name
);
396 struct registry_world
*reg
= &global_skateshop
.world_registry
[
397 global_skateshop
.t1_world_registry_count
++ ];
399 vg_strncpy( folder_name
, reg
->foldername
, 64, k_strncpy_overflow_fatal
);
400 reg
->foldername_hash
= hash
;
401 reg
->state
= k_registry_board_state_indexed
;
402 reg
->meta_present
= 0;
406 * Async thread which scans local files for boards, as well as scheduling
407 * synchronous calls to the workshop
409 VG_STATIC
void world_scan_thread( void *_args
)
411 vg_linear_clear( vg_mem
.scratch
);
413 for( u32 i
=0; i
<global_skateshop
.t1_world_registry_count
; i
++ ){
414 struct registry_world
*reg
= &global_skateshop
.world_registry
[i
];
415 reg
->state
= k_registry_board_state_indexed_absent
;
421 vg_info( "Scanning maps/*.mdl\n" );
425 vg_strnull( &path
, path_buf
, 4096 );
426 vg_strcat( &path
, "maps/" );
428 DIR *dir
= opendir( path
.buffer
);
430 vg_error( "opendir('maps') failed\n" );
431 vg_async_call( workshop_async_any_complete
, NULL
, 0 );
435 struct dirent
*entry
;
436 while( (entry
= readdir(dir
)) ){
437 if( entry
->d_type
== DT_DIR
){
438 if( entry
->d_name
[0] == '.' ) continue;
440 vg_str folder
= path
;
441 char *folder_name
= folder
.buffer
+folder
.i
;
443 if( strlen( entry
->d_name
) >
444 vg_list_size(global_skateshop
.world_registry
[0].foldername
)){
445 vg_warn( "Map folder too long: %s\n", entry
->d_name
);
449 vg_strcat( &folder
, entry
->d_name
);
450 if( !vg_strgood( &folder
) ) break;
452 DIR *subdir
= opendir( folder
.buffer
);
453 while( (entry
= readdir(subdir
)) ){
454 if( entry
->d_type
== DT_REG
){
455 if( entry
->d_name
[0] == '.' ) continue;
457 vg_str file
= folder
;
458 vg_strcat( &file
, "/" );
459 vg_strcat( &file
, entry
->d_name
);
460 if( !vg_strgood( &file
) ) break;
462 char *ext
= vg_strch( &file
, '.' );
464 if( strcmp(ext
,".mdl") ) continue;
466 vg_strcat( &folder
, "" );
467 world_scan_register_local( folder_name
);
475 vg_async_call( workshop_async_world_reg_update
, NULL
, 0 );
480 if( steam_ready
) workshop_steam_scan();
482 vg_async_call( workshop_async_reg_update
, NULL
, 0 );
484 workshop_visibile_load_loop_thread(NULL
);
489 * Asynchronous scan of local disk for worlds
491 VG_STATIC
void skateshop_op_world_scan(void)
493 skaterift_begin_op( k_async_op_world_scan
);
494 vg_loader_start( world_scan_thread
, NULL
);
499 * -----------------------------------------------------------------------------
502 /* we can only keep using a viewslot pointer for multiple frames if we watch it
503 * using this function */
504 VG_STATIC
void watch_cache_board( struct cache_board
*ptr
)
506 if( ptr
->ref_count
>= 32 ){
507 vg_fatal_error( "dynamic board watch missmatch (limit is 32)\n" );
510 ptr
->last_use_time
= vg
.time
;
514 /* after this is called, the calling code only has access to the pointer for the
515 * duration of the rest of the frame */
516 VG_STATIC
void unwatch_cache_board( struct cache_board
*ptr
)
518 if( ptr
->ref_count
== 0 ){
519 vg_fatal_error( "dynamic board unwatch missmatch (no watchers)\n" );
526 * Callback handler for persona state changes,
527 * it sets the author names on the registries
529 VG_STATIC
void callback_persona_statechange( CallbackMsg_t
*msg
)
531 PersonaStateChange_t
*info
= (PersonaStateChange_t
*)msg
->m_pubParam
;
532 ISteamFriends
*hSteamFriends
= SteamAPI_SteamFriends();
534 if( info
->m_nChangeFlags
& k_EPersonaChangeName
){
535 for( u32 i
=0; i
<global_skateshop
.registry_count
; i
++ ){
536 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
537 if( reg
->workshop
.author
== info
->m_ulSteamID
){
538 const char *name
= SteamAPI_ISteamFriends_GetFriendPersonaName(
539 hSteamFriends
, info
->m_ulSteamID
);
540 str_utf8_collapse( name
, reg
->workshop
.author_name
, 32 );
549 VG_STATIC
void skateshop_init(void)
551 u32 reg_size
= sizeof(struct registry_board
)*SKATESHOP_REGISTRY_MAX
,
552 wreg_size
= sizeof(struct registry_world
)*SKATESHOP_WORLDS_MAX
,
553 cache_size
= sizeof(struct cache_board
)*SKATESHOP_BOARD_CACHE_MAX
;
555 global_skateshop
.registry
= vg_linear_alloc( vg_mem
.rtmemory
, reg_size
);
556 global_skateshop
.world_registry
=
557 vg_linear_alloc( vg_mem
.rtmemory
, wreg_size
);
558 global_skateshop
.cache
= vg_linear_alloc( vg_mem
.rtmemory
, cache_size
);
560 memset( global_skateshop
.cache
, 0, cache_size
);
562 for( u32 i
=0; i
<SKATESHOP_BOARD_CACHE_MAX
; i
++ ){
563 struct cache_board
*board
= &global_skateshop
.cache
[i
];
564 board
->state
= k_cache_board_state_none
;
565 board
->registry_id
= 0xffffffff;
566 board
->last_use_time
= -99999.9;
567 board
->ref_count
= 0;
571 steam_register_callback( k_iPersonaStateChange
,
572 callback_persona_statechange
);
576 VG_STATIC
struct cache_board
*skateshop_selected_cache_if_loaded(void)
578 if( global_skateshop
.registry_count
> 0 ){
579 u32 reg_id
= global_skateshop
.selected_registry_id
;
580 struct registry_board
*reg
= &global_skateshop
.registry
[ reg_id
];
582 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
583 if( reg
->cache_ptr
&&
584 (reg
->cache_ptr
->state
== k_cache_board_state_loaded
) )
586 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
587 return reg
->cache_ptr
;
589 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
598 void temp_update_playermodel(void);
599 VG_STATIC
void global_skateshop_preupdate(void)
601 float rate
= vg_minf( 1.0f
, vg
.time_frame_delta
* 2.0f
);
602 global_skateshop
.factive
= vg_lerpf( global_skateshop
.factive
,
603 global_skateshop
.active
, rate
);
605 if( !global_skateshop
.active
) return;
607 world_instance
*world
= get_active_world();
609 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
611 /* camera positioning */
612 ent_camera
*ref
= mdl_arritm( &world
->ent_camera
,
613 mdl_entity_id_id(shop
->id_camera
) );
615 v3f dir
= {0.0f
,-1.0f
,0.0f
};
616 mdl_transform_vector( &ref
->transform
, dir
, dir
);
617 m3x3_mulv( localplayer
.invbasis
, dir
, dir
);
618 player_vector_angles( localplayer
.cam_override_angles
, dir
, 1.0f
, 0.0f
);
621 if( shop
->type
== k_skateshop_type_boardshop
||
622 shop
->type
== k_skateshop_type_worldshop
){
623 ent_marker
*display
= mdl_arritm( &world
->ent_marker
,
624 mdl_entity_id_id(shop
->boards
.id_display
) );
626 v3_sub( display
->transform
.co
, localplayer
.rb
.co
, lookat
);
629 else if( shop
->type
== k_skateshop_type_charshop
){
630 v3_sub( ref
->transform
.co
, localplayer
.rb
.co
, lookat
);
633 vg_fatal_error( "Unknown store (%u)\n", shop
->type
);
636 q_axis_angle( localplayer
.rb
.q
, (v3f
){0.0f
,1.0f
,0.0f
},
637 atan2f(lookat
[0],lookat
[2]) );
639 v3_copy( ref
->transform
.co
, localplayer
.cam_override_pos
);
640 localplayer
.cam_override_fov
= ref
->fov
;
641 localplayer
.cam_override_strength
= global_skateshop
.factive
;
644 if( shop
->type
== k_skateshop_type_boardshop
){
645 gui_helper_action( axis_display_string( k_sraxis_mbrowse_h
), "browse" );
646 gui_helper_action( button_display_string( k_srbind_mback
), "exit" );
648 struct cache_board
*selected_cache
= skateshop_selected_cache_if_loaded();
650 if( selected_cache
){
651 gui_helper_action( button_display_string( k_srbind_maccept
), "pick" );
656 * ----------------------
659 if( button_down( k_srbind_mleft
) ){
660 if( global_skateshop
.selected_registry_id
> 0 ){
661 global_skateshop
.selected_registry_id
--;
665 if( button_down( k_srbind_mright
) ){
666 if( global_skateshop
.selected_registry_id
+1 <
667 global_skateshop
.registry_count
)
669 global_skateshop
.selected_registry_id
++;
673 if( selected_cache
&& button_down( k_srbind_maccept
) ){
674 vg_info( "chose board from skateshop (%u)\n",
675 global_skateshop
.selected_registry_id
);
677 if( localplayer
.board_view_slot
){
678 unwatch_cache_board( localplayer
.board_view_slot
);
681 localplayer
.board_view_slot
= selected_cache
;
682 watch_cache_board( localplayer
.board_view_slot
);
684 global_skateshop_exit();
688 else if( shop
->type
== k_skateshop_type_charshop
){
689 gui_helper_action( axis_display_string( k_sraxis_mbrowse_h
), "browse" );
690 gui_helper_action( button_display_string( k_srbind_mback
), "exit" );
691 gui_helper_action( button_display_string( k_srbind_maccept
), "pick" );
693 if( button_down( k_srbind_mleft
) ){
694 if( cl_playermdl_id
> 0 ){
698 cl_playermdl_id
= 2; /* HACK */
700 temp_update_playermodel(); /* HACK */
703 if( button_down( k_srbind_mright
) ){
704 if( cl_playermdl_id
+1 < 3 ){
708 cl_playermdl_id
= 0; /* HACK */
710 temp_update_playermodel(); /* HACK */
714 if( button_down( k_srbind_maccept
) ){
715 global_skateshop_exit();
718 else if( shop
->type
== k_skateshop_type_worldshop
){
719 if( global_skateshop
.world_registry_count
){
720 gui_helper_action( axis_display_string(k_sraxis_mbrowse_h
), "browse" );
724 if( button_down( k_srbind_mleft
) ){
725 if( global_skateshop
.selected_world_id
> 0 ){
726 global_skateshop
.selected_world_id
--;
731 if( button_down( k_srbind_mright
) ){
732 if( global_skateshop
.selected_world_id
+1 <
733 global_skateshop
.world_registry_count
){
734 global_skateshop
.selected_world_id
++;
739 if( change
&& (pointcloud
.anim
== k_pointcloud_anim_idle
) ){
740 pointcloud
.anim
= k_pointcloud_anim_hiding
;
741 pointcloud
.anim_start
= vg
.time
;
744 if( button_down( k_srbind_maccept
) ){
745 vg_info( "Select world (%u)\n", global_skateshop
.selected_world_id
);
746 global_skateshop_exit();
751 vg_fatal_error( "Unknown store (%u)\n", shop
->type
);
754 if( button_down( k_srbind_mback
) ){
755 global_skateshop_exit();
760 VG_STATIC
void skateshop_render_boardshop(void)
762 world_instance
*world
= get_active_world();
763 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
765 u32 slot_count
= vg_list_size(global_skateshop
.shop_view_slots
);
767 ent_marker
*mark_rack
= mdl_arritm( &world
->ent_marker
,
768 mdl_entity_id_id(shop
->boards
.id_rack
)),
769 *mark_display
= mdl_arritm( &world
->ent_marker
,
770 mdl_entity_id_id(shop
->boards
.id_display
));
772 int visibility
[ SKATESHOP_VIEW_SLOT_MAX
];
773 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
774 for( u32 i
=0; i
<SKATESHOP_VIEW_SLOT_MAX
; i
++ ){
775 struct shop_view_slot
*slot
= &global_skateshop
.shop_view_slots
[i
];
779 if( slot
->cache_ptr
== NULL
) visibility
[i
] = 0;
780 else if( slot
->cache_ptr
->state
!= k_cache_board_state_loaded
)
783 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
785 /* Render loaded boards in the view slots */
786 for( u32 i
=0; i
<slot_count
; i
++ ){
787 struct shop_view_slot
*slot
= &global_skateshop
.shop_view_slots
[i
];
788 float selected
= 0.0f
;
790 if( !visibility
[i
] ) goto fade_out
;
793 transform_identity( &xform
);
795 xform
.co
[0] = -((float)i
- ((float)slot_count
)*0.5f
)*0.45f
;
796 mdl_transform_mul( &mark_rack
->transform
, &xform
, &xform
);
798 if( slot
->cache_ptr
->registry_id
==
799 global_skateshop
.selected_registry_id
){
803 float t
= slot
->view_blend
;
804 v3_lerp( xform
.co
, mark_display
->transform
.co
, t
, xform
.co
);
805 q_nlerp( xform
.q
, mark_display
->transform
.q
, t
, xform
.q
);
806 v3_lerp( xform
.s
, mark_display
->transform
.s
, t
, xform
.s
);
809 mdl_transform_m4x3( &xform
, mmdl
);
810 render_board( &main_camera
, world
, &slot
->cache_ptr
->board
, mmdl
,
811 k_board_shader_entity
);
814 float rate
= 5.0f
*vg
.time_delta
;
815 slot
->view_blend
= vg_lerpf( slot
->view_blend
, selected
, rate
);
818 ent_marker
*mark_info
= mdl_arritm( &world
->ent_marker
,
819 mdl_entity_id_id(shop
->boards
.id_info
));
821 mdl_transform_m4x3( &mark_info
->transform
, mtext
);
822 mdl_transform_m4x3( &mark_rack
->transform
, mrack
);
825 const char *text_title
= "Fish - Title";
826 const char *text_author
= "by Shaniqua";
830 m4x3_identity( mlocal
);
835 font3d_bind( &world_global
.font
, &main_camera
);
836 shader_model_font_uColour( (v4f
){1.0f
,1.0f
,1.0f
,1.0f
} );
839 * ------------------------------------------------------------------ */
841 v3_zero( mlocal
[3] );
842 mlocal
[0][0] = -scale
*2.0f
;
843 mlocal
[1][2] = -scale
*2.0f
;
844 mlocal
[2][1] = -thickness
;
845 mlocal
[3][2] = -0.7f
;
846 m4x3_mul( mrack
, mlocal
, mmdl
);
848 if( global_skateshop
.registry_count
== 0 ){
849 font3d_simple_draw( &world_global
.font
, 0,
850 "Nothing installed", &main_camera
, mmdl
);
855 i
+=highscore_intl( buf
+i
, global_skateshop
.selected_registry_id
+1, 3 );
857 i
+=highscore_intl( buf
+i
, global_skateshop
.registry_count
, 3 );
860 font3d_simple_draw( &world_global
.font
, 0, buf
, &main_camera
, mmdl
);
863 struct cache_board
*cache_ptr
= skateshop_selected_cache_if_loaded();
864 if( !cache_ptr
) return;
866 struct registry_board
*reg
=
867 &global_skateshop
.registry
[cache_ptr
->registry_id
];
868 struct workshop_file_info
*info
= ®
->workshop
;
871 * ----------------------------------------------------------------- */
873 m3x3_setdiagonalv3( mlocal
, (v3f
){ scale
, scale
, thickness
} );
874 mlocal
[3][0] = -font3d_string_width( &world_global
.font
, 0, info
->title
);
875 mlocal
[3][0] *= scale
*0.5f
;
878 m4x3_mul( mtext
, mlocal
, mmdl
);
879 font3d_simple_draw( &world_global
.font
, 0, info
->title
, &main_camera
, mmdl
);
882 * ----------------------------------------------------------------- */
884 m3x3_setdiagonalv3( mlocal
, (v3f
){ scale
, scale
, thickness
} );
885 mlocal
[3][0] = -font3d_string_width( &world_global
.font
, 0,
887 mlocal
[3][0] *= scale
*0.5f
;
890 m4x3_mul( mtext
, mlocal
, mmdl
);
891 font3d_simple_draw( &world_global
.font
, 0,
892 info
->author_name
, &main_camera
, mmdl
);
895 VG_STATIC
void skateshop_render_charshop(void)
899 VG_STATIC
void skateshop_render_worldshop(void)
901 world_instance
*world
= get_active_world();
903 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
904 ent_marker
*mark_display
= mdl_arritm( &world
->ent_marker
,
905 mdl_entity_id_id(shop
->worlds
.id_display
)),
906 *mark_info
= mdl_arritm( &world
->ent_marker
,
907 mdl_entity_id_id(shop
->boards
.id_info
));
910 char buftext
[128], bufsubtext
[128];
911 vg_str info
, subtext
;
912 vg_strnull( &info
, buftext
, 128 );
913 vg_strnull( &subtext
, bufsubtext
, 128 );
915 if( global_skateshop
.world_registry_count
){
916 struct registry_world
*rw
= &global_skateshop
.world_registry
[
917 global_skateshop
.selected_world_id
];
919 info
.i
+=highscore_intl( info
.buffer
+info
.i
,
920 global_skateshop
.selected_world_id
+1, 3 );
921 info
.buffer
[info
.i
++] = '/';
922 info
.i
+=highscore_intl( info
.buffer
+info
.i
,
923 global_skateshop
.world_registry_count
, 3 );
924 info
.buffer
[info
.i
++] = ' ';
925 info
.buffer
[info
.i
] = '\0';
927 if( rw
->meta_present
){
931 vg_strcat( &info
, rw
->foldername
);
932 vg_strcat( &subtext
, "No information" );
936 vg_strcat( &info
, "No worlds installed" );
939 m4x3f mtext
,mlocal
,mtextmdl
;
940 mdl_transform_m4x3( &mark_info
->transform
, mtext
);
942 font3d_bind( &world_global
.font
, &main_camera
);
943 shader_model_font_uColour( (v4f
){1.0f
,1.0f
,1.0f
,1.0f
} );
945 float scale
= 0.2f
, thickness
= 0.015f
, scale1
= 0.08f
;
947 m3x3_setdiagonalv3( mlocal
, (v3f
){ scale
, scale
, thickness
} );
948 mlocal
[3][0] = -font3d_string_width( &world_global
.font
, 0, buftext
);
949 mlocal
[3][0] *= scale
*0.5f
;
952 m4x3_mul( mtext
, mlocal
, mtextmdl
);
953 font3d_simple_draw( &world_global
.font
, 0, buftext
, &main_camera
, mtextmdl
);
955 m3x3_setdiagonalv3( mlocal
, (v3f
){ scale1
, scale1
, thickness
} );
956 mlocal
[3][0] = -font3d_string_width( &world_global
.font
, 0, bufsubtext
);
957 mlocal
[3][0] *= scale1
*0.5f
;
958 mlocal
[3][1] = -scale1
*0.3f
;
959 m4x3_mul( mtext
, mlocal
, mtextmdl
);
960 font3d_simple_draw( &world_global
.font
, 0, bufsubtext
,
961 &main_camera
, mtextmdl
);
965 mdl_transform_m4x3( &mark_display
->transform
, mmdl
);
966 m4x3_rotate_y( mmdl
, vg
.time
* 0.2 );
969 glBlendFunc(GL_ONE
, GL_ONE
);
970 glDisable(GL_DEPTH_TEST
);
971 pointcloud_render( world
, &main_camera
, mmdl
);
973 glEnable(GL_DEPTH_TEST
);
977 * World: render event
979 VG_STATIC
void skateshop_render(void)
981 if( !global_skateshop
.active
) return;
983 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
985 if( shop
->type
== k_skateshop_type_boardshop
){
986 skateshop_render_boardshop();
988 else if( shop
->type
== k_skateshop_type_charshop
){
989 skateshop_render_charshop();
991 else if( shop
->type
== k_skateshop_type_worldshop
){
992 skateshop_render_worldshop();
995 vg_fatal_error( "Unknown store (%u)\n", shop
->type
);
1000 * Entity logic: entrance event
1002 VG_STATIC
void ent_skateshop_call( world_instance
*world
, ent_call
*call
)
1004 u32 index
= mdl_entity_id_id( call
->id
);
1005 ent_skateshop
*shop
= mdl_arritm( &world
->ent_skateshop
, index
);
1006 vg_info( "skateshop_call\n" );
1008 if( menu
.active
) return;
1009 if( skaterift
.async_op
!= k_async_op_none
) return;
1011 if( call
->function
== k_ent_function_trigger
){
1012 if( localplayer
.subsystem
!= k_player_subsystem_walk
){
1016 vg_info( "Entering skateshop\n" );
1018 localplayer
.immobile
= 1;
1019 menu
.disable_open
= 1;
1020 global_skateshop
.active
= 1;
1022 v3_zero( localplayer
.rb
.v
);
1023 v3_zero( localplayer
.rb
.w
);
1024 localplayer
._walk
.move_speed
= 0.0f
;
1025 global_skateshop
.ptr_ent
= shop
;
1027 if( shop
->type
== k_skateshop_type_boardshop
){
1028 skateshop_update_viewpage();
1029 workshop_op_item_scan();
1031 else if( shop
->type
== k_skateshop_type_worldshop
){
1032 pointcloud
.anim
= k_pointcloud_anim_opening
;
1033 pointcloud
.anim_start
= vg
.time
;
1034 skateshop_op_world_scan();
1040 * Entity logic: exit event
1042 VG_STATIC
void global_skateshop_exit(void)
1044 vg_info( "exit skateshop\n" );
1045 localplayer
.immobile
= 0;
1046 global_skateshop
.active
= 0;
1047 menu
.disable_open
= 0;
1048 srinput
.ignore_input_frames
= 2;
1051 #endif /* ENT_SKATESHOP_C */