1 #ifndef ENT_SKATESHOP_C
2 #define ENT_SKATESHOP_C
6 #include "vg/vg_steam_ugc.h"
7 #include "ent_skateshop.h"
13 * Checks string equality but does a hash check first
15 static inline int const_str_eq( u32 hash
, const char *str
, const char *cmp
)
17 if( hash
== vg_strdjb2(cmp
) )
18 if( !strcmp( str
, cmp
) )
24 * Get an existing cache instance, allocate a new one to be loaded, or NULL if
27 VG_STATIC
struct cache_board
*skateshop_cache_fetch( u32 registry_index
)
29 struct registry_board
*reg
= NULL
;
31 if( registry_index
< global_skateshop
.registry_count
){
32 reg
= &global_skateshop
.registry
[ registry_index
];
35 return reg
->cache_ptr
;
39 /* lru eviction. should be a linked list maybe... */
40 double min_time
= 1e300
;
41 struct cache_board
*min_board
= NULL
;
43 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
44 for( u32 i
=0; i
<SKATESHOP_BOARD_CACHE_MAX
; i
++ ){
45 struct cache_board
*cache_ptr
= &global_skateshop
.cache
[i
];
47 if( cache_ptr
->state
== k_cache_board_state_load_request
) continue;
48 if( cache_ptr
->ref_count
) continue;
50 if( cache_ptr
->last_use_time
< min_time
){
51 min_time
= cache_ptr
->last_use_time
;
52 min_board
= cache_ptr
;
57 if( min_board
->state
== k_cache_board_state_loaded
){
58 struct registry_board
*other
=
59 &global_skateshop
.registry
[ min_board
->registry_id
];
61 vg_info( "Deallocating board: '%s'\n", min_board
, other
->filename
);
63 player_board_unload( &min_board
->board
);
64 other
->cache_ptr
= NULL
;
68 vg_info( "Allocating board (reg:%u) '%s'\n",
69 registry_index
, reg
->filename
);
72 vg_info( "Pre-allocating board (reg:%u) 'null'\n", registry_index
);
75 min_board
->registry_id
= registry_index
;
76 min_board
->last_use_time
= vg
.time
;
77 min_board
->ref_count
= 0;
78 min_board
->state
= k_cache_board_state_load_request
;
81 vg_error( "No free boards to load registry!\n" );
84 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
88 VG_STATIC
void skateshop_update_viewpage(void)
90 u32 page
= global_skateshop
.selected_registry_id
/SKATESHOP_VIEW_SLOT_MAX
;
92 for( u32 i
=0; i
<SKATESHOP_VIEW_SLOT_MAX
; i
++ ){
93 struct shop_view_slot
*slot
= &global_skateshop
.shop_view_slots
[i
];
94 u32 request_id
= page
*SKATESHOP_VIEW_SLOT_MAX
+ i
;
96 if( slot
->cache_ptr
) unwatch_cache_board( slot
->cache_ptr
);
98 slot
->cache_ptr
= skateshop_cache_fetch( request_id
);
99 if( slot
->cache_ptr
) watch_cache_board( slot
->cache_ptr
);
103 /* generic reciever */
104 VG_STATIC
void workshop_async_any_complete( void *data
, u32 size
)
110 * op/subroutine: k_workshop_op_item_load
111 * -----------------------------------------------------------------------------
115 * Reciever for board completion; only promotes the status in the main thread
117 VG_STATIC
void skateshop_async_board_loaded( void *payload
, u32 size
)
119 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
120 struct cache_board
*cache_ptr
= payload
;
121 cache_ptr
->last_use_time
= vg
.time
;
122 cache_ptr
->state
= k_cache_board_state_loaded
;
124 struct registry_board
*reg
=
125 &global_skateshop
.registry
[ cache_ptr
->registry_id
];
126 reg
->cache_ptr
= cache_ptr
;
127 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
129 vg_success( "Async board loaded (%s)\n", reg
->filename
);
133 * Thread(or subroutine of thread), for checking view slots that weve installed.
134 * Load the model if a view slot wants it
136 VG_STATIC
void workshop_visibile_load_loop_thread( void *_args
)
139 for( u32 i
=0; i
<SKATESHOP_BOARD_CACHE_MAX
; i
++ ){
140 struct cache_board
*cache_ptr
= &global_skateshop
.cache
[i
];
142 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
143 if( cache_ptr
->state
== k_cache_board_state_load_request
){
144 if( cache_ptr
->registry_id
>= global_skateshop
.registry_count
){
145 /* should maybe have a different value for this case */
146 cache_ptr
->state
= k_cache_board_state_none
;
147 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
151 /* continue with the request */
152 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
154 struct registry_board
*reg
=
155 &global_skateshop
.registry
[ cache_ptr
->registry_id
];
157 if( reg
->workshop_id
){
158 vg_async_item
*call
=
159 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
161 struct async_workshop_filepath_info
*info
= call
->payload
;
163 info
->id
= reg
->workshop_id
;
164 info
->len
= vg_list_size(path
) - strlen("/board.mdl")-1;
165 vg_async_dispatch( call
, async_workshop_get_filepath
);
166 vg_async_stall(); /* too bad! */
168 if( path
[0] == '\0' ){
169 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
170 cache_ptr
->state
= k_cache_board_state_none
;
171 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
173 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64
")\n",
178 strcat( path
, "/board.mdl" );
182 snprintf( path
, 256, "models/boards/%s", reg
->filename
);
185 player_board_load( &cache_ptr
->board
, path
);
186 vg_async_call( skateshop_async_board_loaded
, cache_ptr
, 0 );
189 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
191 vg_async_call( workshop_async_any_complete
, NULL
, 0 );
195 * op: k_workshop_op_item_scan
196 * -----------------------------------------------------------------------------
200 * Reciever for scan completion. copies the registry_count back into t0
202 VG_STATIC
void workshop_async_reg_update( void *data
, u32 size
)
204 vg_info( "Registry update notify\n" );
205 global_skateshop
.registry_count
= global_skateshop
.t1_registry_count
;
209 * Async thread which scans local files for boards, as well as scheduling
210 * synchronous calls to the workshop
212 VG_STATIC
void workshop_scan_thread( void *_args
)
214 vg_linear_clear( vg_mem
.scratch
);
216 for( u32 i
=0; i
<global_skateshop
.t1_registry_count
; i
++ ){
217 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
218 reg
->state
= k_registry_board_state_indexed_absent
;
224 vg_info( "Scanning models/boards/*.mdl\n" );
226 tinydir_open( &dir
, "models/boards" );
228 while( dir
.has_next
){
230 tinydir_readfile( &dir
, &file
);
233 u32 hash
= vg_strdjb2( file
.name
);
235 for( u32 i
=0; i
<global_skateshop
.t1_registry_count
; i
++ ){
236 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
238 if( const_str_eq( hash
, file
.name
, reg
->filename
) ){
239 reg
->state
= k_registry_board_state_indexed
;
244 if( global_skateshop
.t1_registry_count
== SKATESHOP_REGISTRY_MAX
){
245 vg_error( "You have too many boards installed!\n" );
249 vg_info( "new listing!: %s\n", file
.name
);
251 struct registry_board
*reg
=
252 &global_skateshop
.registry
[global_skateshop
.t1_registry_count
++];
254 reg
->cache_ptr
= NULL
;
255 vg_strncpy( file
.name
, reg
->filename
, 64, k_strncpy_always_add_null
);
256 vg_strncpy( file
.name
, reg
->workshop
.title
,
257 64, k_strncpy_always_add_null
);
258 reg
->filename_hash
= hash
;
259 reg
->workshop_id
= 0;
260 reg
->state
= k_registry_board_state_indexed
;
261 reg
->workshop
.author
= 0;
262 strcpy( reg
->workshop
.author_name
, "custom" );
265 next_file
: tinydir_next( &dir
);
271 * Steam workshop scan
273 vg_info( "Scanning steam workshop for boards\n" );
274 PublishedFileId_t workshop_ids
[ SKATESHOP_REGISTRY_MAX
];
275 u32 workshop_count
= SKATESHOP_REGISTRY_MAX
;
277 vg_async_item
*call
= vg_async_alloc(
278 sizeof(struct async_workshop_installed_files_info
));
279 struct async_workshop_installed_files_info
*info
= call
->payload
;
280 info
->buffer
= workshop_ids
;
281 info
->len
= &workshop_count
;
282 vg_async_dispatch( call
, async_workshop_get_installed_files
);
285 for( u32 j
=0; j
<workshop_count
; j
++ ){
286 PublishedFileId_t id
= workshop_ids
[j
];
288 for( u32 i
=0; i
<global_skateshop
.t1_registry_count
; i
++ ){
289 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
291 if( reg
->workshop_id
== id
){
292 reg
->state
= k_registry_board_state_indexed
;
293 goto next_file_workshop
;
297 if( global_skateshop
.t1_registry_count
== SKATESHOP_REGISTRY_MAX
){
298 vg_error( "You have too many boards installed!\n" );
302 vg_info( "new listing from the steam workshop!: "PRINTF_U64
"\n", id
);
304 struct registry_board
*reg
= &global_skateshop
.registry
[
305 global_skateshop
.t1_registry_count
++ ];
307 reg
->cache_ptr
= NULL
;
308 snprintf( reg
->filename
, 64, PRINTF_U64
, id
);
309 reg
->filename_hash
= vg_strdjb2( reg
->filename
);
310 reg
->workshop_id
= id
;
311 reg
->state
= k_registry_board_state_indexed
;
313 workshop_file_info_clear( ®
->workshop
);
314 strcpy( reg
->workshop
.title
, "Workshop file" );
316 /* load the metadata off the disk */
317 vg_async_item
*call
=
318 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
320 const char *meta_file
= "/board.mdl.inf";
322 struct async_workshop_filepath_info
*info
= call
->payload
;
324 info
->id
= reg
->workshop_id
;
325 info
->len
= vg_list_size(path
) - strlen(meta_file
)-1;
326 vg_async_dispatch( call
, async_workshop_get_filepath
);
327 vg_async_stall(); /* too bad! */
329 strcat( path
, meta_file
);
330 workshop_load_metadata( path
, ®
->workshop
);
335 vg_async_call( workshop_async_reg_update
, NULL
, 0 );
337 workshop_visibile_load_loop_thread(NULL
);
341 * Asynchronous scan of local disk for items and add them to the registry
343 VG_STATIC
void workshop_op_item_scan(void)
345 workshop_begin_op( k_workshop_op_item_scan
);
346 vg_loader_start( workshop_scan_thread
, NULL
);
351 * -----------------------------------------------------------------------------
354 /* we can only keep using a viewslot pointer for multiple frames if we watch it
355 * using this function */
356 VG_STATIC
void watch_cache_board( struct cache_board
*ptr
)
358 if( ptr
->ref_count
>= 32 ){
359 vg_fatal_error( "dynamic board watch missmatch (limit is 32)\n" );
362 ptr
->last_use_time
= vg
.time
;
366 /* after this is called, the calling code only has access to the pointer for the
367 * duration of the rest of the frame */
368 VG_STATIC
void unwatch_cache_board( struct cache_board
*ptr
)
370 if( ptr
->ref_count
== 0 ){
371 vg_fatal_error( "dynamic board unwatch missmatch (no watchers)\n" );
378 * Callback handler for persona state changes,
379 * it sets the author names on the registries
381 VG_STATIC
void callback_persona_statechange( CallbackMsg_t
*msg
)
383 PersonaStateChange_t
*info
= (PersonaStateChange_t
*)msg
->m_pubParam
;
384 ISteamFriends
*hSteamFriends
= SteamAPI_SteamFriends();
386 if( info
->m_nChangeFlags
& k_EPersonaChangeName
){
387 for( u32 i
=0; i
<global_skateshop
.registry_count
; i
++ ){
388 struct registry_board
*reg
= &global_skateshop
.registry
[i
];
389 if( reg
->workshop
.author
== info
->m_ulSteamID
){
390 const char *name
= SteamAPI_ISteamFriends_GetFriendPersonaName(
391 hSteamFriends
, info
->m_ulSteamID
);
392 str_utf8_collapse( name
, reg
->workshop
.author_name
, 32 );
401 VG_STATIC
void skateshop_init(void)
403 u32 reg_size
= sizeof(struct registry_board
)*SKATESHOP_REGISTRY_MAX
,
404 cache_size
= sizeof(struct cache_board
)*SKATESHOP_BOARD_CACHE_MAX
;
406 global_skateshop
.registry
= vg_linear_alloc( vg_mem
.rtmemory
, reg_size
);
407 global_skateshop
.registry_count
= 0;
408 global_skateshop
.cache
= vg_linear_alloc( vg_mem
.rtmemory
, cache_size
);
410 memset( global_skateshop
.cache
, 0, cache_size
);
412 for( u32 i
=0; i
<SKATESHOP_BOARD_CACHE_MAX
; i
++ ){
413 struct cache_board
*board
= &global_skateshop
.cache
[i
];
414 board
->state
= k_cache_board_state_none
;
415 board
->registry_id
= 0xffffffff;
416 board
->last_use_time
= -99999.9;
417 board
->ref_count
= 0;
421 steam_register_callback( k_iPersonaStateChange
,
422 callback_persona_statechange
);
426 VG_STATIC
struct cache_board
*skateshop_selected_cache_if_loaded(void)
428 if( global_skateshop
.registry_count
> 0 ){
429 u32 reg_id
= global_skateshop
.selected_registry_id
;
430 struct registry_board
*reg
= &global_skateshop
.registry
[ reg_id
];
432 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
433 if( reg
->cache_ptr
&&
434 (reg
->cache_ptr
->state
== k_cache_board_state_loaded
) )
436 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
437 return reg
->cache_ptr
;
439 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
448 VG_STATIC
void global_skateshop_preupdate(void)
450 float rate
= vg_minf( 1.0f
, vg
.time_frame_delta
* 2.0f
);
451 global_skateshop
.factive
= vg_lerpf( global_skateshop
.factive
,
452 global_skateshop
.active
, rate
);
454 if( !global_skateshop
.active
) return;
456 world_instance
*world
= get_active_world();
458 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
459 ent_camera
*ref
= mdl_arritm( &world
->ent_camera
,
460 mdl_entity_id_id(shop
->id_camera
) );
461 ent_marker
*display
= mdl_arritm( &world
->ent_marker
,
462 mdl_entity_id_id(shop
->id_display
) );
464 v3f dir
= {0.0f
,-1.0f
,0.0f
};
465 mdl_transform_vector( &ref
->transform
, dir
, dir
);
466 player_vector_angles( localplayer
.cam_override_angles
, dir
, 1.0f
, 0.0f
);
469 v3_sub( display
->transform
.co
, localplayer
.rb
.co
, lookat
);
471 q_axis_angle( localplayer
.rb
.q
, (v3f
){0.0f
,1.0f
,0.0f
},
472 atan2f(lookat
[0],lookat
[2]) );
474 v3_copy( ref
->transform
.co
, localplayer
.cam_override_pos
);
475 localplayer
.cam_override_fov
= ref
->fov
;
476 localplayer
.cam_override_strength
= global_skateshop
.factive
;
478 gui_helper_action( axis_display_string( k_sraxis_mbrowse_h
), "browse" );
479 gui_helper_action( button_display_string( k_srbind_mback
), "exit" );
482 struct cache_board
*selected_cache
= skateshop_selected_cache_if_loaded();
484 if( selected_cache
){
485 gui_helper_action( button_display_string( k_srbind_maccept
), "pick" );
490 * ----------------------
493 if( global_skateshop
.interaction_cooldown
> 0.0f
){
494 global_skateshop
.interaction_cooldown
-= vg
.time_delta
;
498 if( button_down( k_srbind_mleft
) ){
499 if( global_skateshop
.selected_registry_id
> 0 ){
500 global_skateshop
.selected_registry_id
--;
505 if( button_down( k_srbind_mright
) ){
506 if( global_skateshop
.selected_registry_id
+1 <
507 global_skateshop
.registry_count
)
509 global_skateshop
.selected_registry_id
++;
515 global_skateshop
.interaction_cooldown
= 0.125f
;
519 if( selected_cache
&& button_down( k_srbind_maccept
) ){
520 vg_info( "chose board from skateshop (%u)\n",
521 global_skateshop
.selected_registry_id
);
523 if( localplayer
.board_view_slot
){
524 unwatch_cache_board( localplayer
.board_view_slot
);
527 localplayer
.board_view_slot
= selected_cache
;
528 watch_cache_board( localplayer
.board_view_slot
);
530 global_skateshop_exit();
534 if( button_down( k_srbind_mback
) ){
535 global_skateshop_exit();
541 * World: render event
543 VG_STATIC
void skateshop_render(void)
545 if( !global_skateshop
.active
) return;
547 ent_skateshop
*shop
= global_skateshop
.ptr_ent
;
548 world_instance
*world
= get_active_world();
550 u32 slot_count
= vg_list_size(global_skateshop
.shop_view_slots
);
552 ent_marker
*mark_rack
= mdl_arritm( &world
->ent_marker
,
553 mdl_entity_id_id(shop
->id_rack
)),
554 *mark_display
= mdl_arritm( &world
->ent_marker
,
555 mdl_entity_id_id(shop
->id_display
));
557 int visibility
[ SKATESHOP_VIEW_SLOT_MAX
];
558 SDL_AtomicLock( &global_skateshop
.sl_cache_access
);
559 for( u32 i
=0; i
<SKATESHOP_VIEW_SLOT_MAX
; i
++ ){
560 struct shop_view_slot
*slot
= &global_skateshop
.shop_view_slots
[i
];
564 if( slot
->cache_ptr
== NULL
) visibility
[i
] = 0;
565 else if( slot
->cache_ptr
->state
!= k_cache_board_state_loaded
)
568 SDL_AtomicUnlock( &global_skateshop
.sl_cache_access
);
570 /* Render loaded boards in the view slots */
571 for( u32 i
=0; i
<slot_count
; i
++ ){
572 struct shop_view_slot
*slot
= &global_skateshop
.shop_view_slots
[i
];
573 float selected
= 0.0f
;
575 if( !visibility
[i
] ) goto fade_out
;
578 transform_identity( &xform
);
580 xform
.co
[0] = -((float)i
- ((float)slot_count
)*0.5f
)*0.45f
;
581 mdl_transform_mul( &mark_rack
->transform
, &xform
, &xform
);
583 if( slot
->cache_ptr
->registry_id
==
584 global_skateshop
.selected_registry_id
){
588 float t
= slot
->view_blend
;
589 v3_lerp( xform
.co
, mark_display
->transform
.co
, t
, xform
.co
);
590 q_nlerp( xform
.q
, mark_display
->transform
.q
, t
, xform
.q
);
591 v3_lerp( xform
.s
, mark_display
->transform
.s
, t
, xform
.s
);
594 mdl_transform_m4x3( &xform
, mmdl
);
595 render_board( &main_camera
, world
, &slot
->cache_ptr
->board
, mmdl
,
596 k_board_shader_entity
);
599 float rate
= 5.0f
*vg
.time_delta
;
600 slot
->view_blend
= vg_lerpf( slot
->view_blend
, selected
, rate
);
603 ent_marker
*mark_info
= mdl_arritm( &world
->ent_marker
,
604 mdl_entity_id_id(shop
->id_info
));
606 mdl_transform_m4x3( &mark_info
->transform
, mtext
);
607 mdl_transform_m4x3( &mark_rack
->transform
, mrack
);
610 const char *text_title
= "Fish - Title";
611 const char *text_author
= "by Shaniqua";
615 m4x3_identity( mlocal
);
620 font3d_bind( &world_global
.font
, &main_camera
);
621 shader_model_font_uColour( (v4f
){1.0f
,1.0f
,1.0f
,1.0f
} );
624 * ------------------------------------------------------------------ */
626 v3_zero( mlocal
[3] );
627 mlocal
[0][0] = -scale
*2.0f
;
628 mlocal
[1][2] = -scale
*2.0f
;
629 mlocal
[2][1] = -thickness
;
630 mlocal
[3][2] = -0.7f
;
631 m4x3_mul( mrack
, mlocal
, mmdl
);
633 if( global_skateshop
.registry_count
== 0 ){
634 font3d_simple_draw( &world_global
.font
, 0,
635 "Nothing installed", &main_camera
, mmdl
);
640 i
+=highscore_intl( buf
+i
, global_skateshop
.selected_registry_id
+1, 3 );
642 i
+=highscore_intl( buf
+i
, global_skateshop
.registry_count
, 3 );
645 font3d_simple_draw( &world_global
.font
, 0, buf
, &main_camera
, mmdl
);
648 struct cache_board
*cache_ptr
= skateshop_selected_cache_if_loaded();
649 if( !cache_ptr
) return;
651 struct registry_board
*reg
=
652 &global_skateshop
.registry
[cache_ptr
->registry_id
];
653 struct workshop_file_info
*info
= ®
->workshop
;
656 * ----------------------------------------------------------------- */
658 m3x3_setdiagonalv3( mlocal
, (v3f
){ scale
, scale
, thickness
} );
659 mlocal
[3][0] = -font3d_string_width( &world_global
.font
, 0, info
->title
);
660 mlocal
[3][0] *= scale
*0.5f
;
662 m4x3_mul( mtext
, mlocal
, mmdl
);
663 font3d_simple_draw( &world_global
.font
, 0, info
->title
, &main_camera
, mmdl
);
666 * ----------------------------------------------------------------- */
668 m3x3_setdiagonalv3( mlocal
, (v3f
){ scale
, scale
, thickness
} );
669 mlocal
[3][0] = -font3d_string_width( &world_global
.font
, 0,
671 mlocal
[3][0] *= scale
*0.5f
;
673 m4x3_mul( mtext
, mlocal
, mmdl
);
674 font3d_simple_draw( &world_global
.font
, 0,
675 info
->author_name
, &main_camera
, mmdl
);
679 * Entity logic: entrance event
681 VG_STATIC
void ent_skateshop_call( world_instance
*world
, ent_call
*call
)
683 u32 index
= mdl_entity_id_id( call
->id
);
684 ent_skateshop
*shop
= mdl_arritm( &world
->ent_skateshop
, index
);
685 vg_info( "skateshop_call\n" );
687 if( call
->function
== k_ent_function_trigger
){
688 if( localplayer
.subsystem
!= k_player_subsystem_walk
){
692 vg_info( "Entering skateshop\n" );
694 localplayer
.immobile
= 1;
695 global_skateshop
.active
= 1;
697 v3_zero( localplayer
.rb
.v
);
698 v3_zero( localplayer
.rb
.w
);
699 localplayer
._walk
.move_speed
= 0.0f
;
700 global_skateshop
.ptr_ent
= shop
;
702 skateshop_update_viewpage();
703 workshop_op_item_scan();
708 * Entity logic: exit event
710 VG_STATIC
void global_skateshop_exit(void)
712 vg_info( "exit skateshop\n" );
713 localplayer
.immobile
= 0;
714 global_skateshop
.active
= 0;
715 srinput
.ignore_input_frames
= 2;
718 #endif /* ENT_SKATESHOP_C */