7 #include "ent_skateshop.h"
9 #include "vg/vg_steam_auth.h"
10 #include "vg/vg_steam_ugc.h"
11 #include "vg/vg_steam_friends.h"
14 #include "highscores.h"
16 #define WORKSHOP_VIEW_PER_PAGE 15
21 char description
[512];
23 PublishedFileId_t file_id
; /* 0 if not published yet */
25 struct ui_dropdown_value visibility
;
27 int submit_title
, /* set if the respective controls are touched */
29 submit_file_and_image
;
33 enum workshop_form_page
{
34 k_workshop_form_hidden
,
35 k_workshop_form_open
, /* open but not looking at anything */
36 k_workshop_form_edit
, /* editing a submission */
37 k_workshop_form_cclosing
,
38 k_workshop_form_closing_good
, /* post upload screen */
39 k_workshop_form_closing_bad
,
44 * -----------------------------
48 struct player_board board_model
;
50 /* what does the user want to do with the image preview? */
51 enum workshop_form_file_intent
{
52 k_workshop_form_file_intent_none
, /* loading probably */
53 k_workshop_form_file_intent_new
, /* board_model is valid */
54 k_workshop_form_file_intent_keep_old
/* just browsing */
58 world_instance
*view_world
;
59 ent_swspreview
*ptr_ent
;
69 * published UGC request
70 * ------------------------------
74 UGCQueryHandle_t handle
;
84 * ------------------------------------------
87 const char *failure_or_success_string
;
92 int view_published_page_count
,
93 view_published_page_id
;
95 struct published_file
{
100 published_files_list
[WORKSHOP_VIEW_PER_PAGE
];
101 int published_files_list_length
;
103 static workshop_form
;
105 static struct ui_dropdown_opt workshop_form_visibility_opts
[] = {
106 { "Public", k_ERemoteStoragePublishedFileVisibilityPublic
},
107 { "Unlisted", k_ERemoteStoragePublishedFileVisibilityUnlisted
},
108 { "Friends Only", k_ERemoteStoragePublishedFileVisibilityFriendsOnly
},
109 { "Private", k_ERemoteStoragePublishedFileVisibilityPrivate
},
113 * Close the form and discard UGC query result
115 VG_STATIC
void workshop_quit_form(void)
117 skaterift_begin_op( k_async_op_none
); /* safeguard */
118 player_board_unload( &workshop_form
.board_model
);
119 workshop_form
.file_intent
= k_workshop_form_file_intent_none
;
121 if( workshop_form
.ugc_query
.result
== k_EResultOK
){
122 workshop_form
.ugc_query
.result
= k_EResultNone
;
124 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
125 SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(
126 hSteamUGC
, workshop_form
.ugc_query
.handle
);
129 workshop_form
.page
= k_workshop_form_hidden
;
134 * Delete all information about the submission
136 VG_STATIC
void workshop_reset_submission_data(void)
138 workshop_form
.submission
.file_id
= 0; /* assuming id of 0 is none/invalid */
139 workshop_form
.submission
.description
[0] = '\0';
140 workshop_form
.submission
.title
[0] = '\0';
142 workshop_form
.submission
.visibility
.value
=
143 k_ERemoteStoragePublishedFileVisibilityPublic
;
144 workshop_form
.submission
.visibility
.index
= 0;
146 workshop_form
.model_path
[0] = '\0';
147 player_board_unload( &workshop_form
.board_model
);
148 workshop_form
.file_intent
= k_workshop_form_file_intent_none
;
153 * Mostly copies of what it sais on the Steam API documentation
155 VG_STATIC
const char *workshop_EResult_user_string( EResult result
)
158 case k_EResultInsufficientPrivilege
:
159 return "Your account is currently restricted from uploading content "
160 "due to a hub ban, account lock, or community ban. You need to "
161 "contact Steam Support to resolve the issue.";
162 case k_EResultBanned
:
163 return "You do not have permission to upload content to this hub "
164 "because you have an active VAC or Game ban.";
165 case k_EResultTimeout
:
166 return "The operation took longer than expected, so it was discarded. "
168 case k_EResultNotLoggedOn
:
169 return "You are currently not logged into Steam.";
170 case k_EResultServiceUnavailable
:
171 return "The workshop server is having issues or is unavailable, "
173 case k_EResultInvalidParam
:
174 return "One of the submission fields contains something not being "
175 "accepted by that field.";
176 case k_EResultAccessDenied
:
177 return "There was a problem trying to save the title and description. "
178 "Access was denied.";
179 case k_EResultLimitExceeded
:
180 return "You have exceeded your Steam Cloud quota. If you wish to "
181 "upload this file, you must remove some published items.";
182 case k_EResultFileNotFound
:
183 return "The uploaded file could not be found.";
184 case k_EResultDuplicateRequest
:
185 return "The file was already successfully uploaded.";
186 case k_EResultDuplicateName
:
187 return "You already have a Steam Workshop item with that name.";
188 case k_EResultServiceReadOnly
:
189 return "Due to a recent password or email change, you are not allowed "
190 "to upload new content. Usually this restriction will expire in"
191 " 5 days, but can last up to 30 days if the account has been "
192 "inactive recently.";
194 return "Operation failed for an error which has not been accounted for "
195 "by the programmer. Try again, sorry :)";
200 * op: k_workshop_form_op_publishing_update
201 * ----------------------------------------------------------------------------
205 * The endpoint of this operation
207 VG_STATIC
void on_workshop_update_result( void *data
, void *user
)
209 vg_info( "Recieved workshop update result\n" );
210 SubmitItemUpdateResult_t
*result
= data
;
212 /* this seems to be set here, but my account definitely has accepted it */
213 if( result
->m_bUserNeedsToAcceptWorkshopLegalAgreement
){
214 vg_warn( "Workshop agreement currently not accepted\n" );
217 if( result
->m_eResult
== k_EResultOK
){
218 workshop_form
.page
= k_workshop_form_closing_good
;
219 workshop_form
.failure_or_success_string
= "Uploaded workshop file!";
220 vg_success( "file uploaded\n" );
223 workshop_form
.page
= k_workshop_form_closing_bad
;
224 workshop_form
.failure_or_success_string
=
225 workshop_EResult_user_string( result
->m_eResult
);
227 vg_error( "Error with the submitted file (%d)\n", result
->m_eResult
);
233 struct workshop_package_info
{
234 PublishedFileId_t publishing_file_id
;
237 char abs_preview_image
[ 1024 ];
238 char abs_content_folder
[ 1024 ];
239 char abs_content_file
[ 1024 ];
241 const char *failure_reason
;
245 * reciever on completion of packaging the files, it will then start the item
246 * update with Steam API
248 VG_STATIC
void workshop_form_async_package_complete( void *data
, u32 size
)
250 struct workshop_package_info
*info
= data
;
251 if( !info
->success
){
252 workshop_form
.page
= k_workshop_form_closing_bad
;
253 workshop_form
.failure_or_success_string
= info
->failure_reason
;
258 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
259 UGCUpdateHandle_t handle
260 = SteamAPI_ISteamUGC_StartItemUpdate( hSteamUGC
, SKATERIFT_APPID
,
261 info
->publishing_file_id
);
263 /* TODO: Handle failure cases for these */
265 if( workshop_form
.submission
.submit_title
){
266 vg_info( "Setting title\n" );
267 SteamAPI_ISteamUGC_SetItemTitle( hSteamUGC
, handle
,
268 workshop_form
.submission
.title
);
271 if( workshop_form
.submission
.submit_description
){
272 vg_info( "Setting description\n" );
273 SteamAPI_ISteamUGC_SetItemDescription( hSteamUGC
, handle
,
274 workshop_form
.submission
.description
);
277 if( workshop_form
.submission
.submit_file_and_image
){
278 vg_info( "Setting preview image\n" );
279 SteamAPI_ISteamUGC_SetItemPreview( hSteamUGC
,
280 handle
, info
->abs_preview_image
);
282 vg_info( "Setting item content\n" );
283 SteamAPI_ISteamUGC_SetItemContent( hSteamUGC
, handle
,
284 info
->abs_content_folder
);
287 vg_info( "Setting visibility\n" );
288 SteamAPI_ISteamUGC_SetItemVisibility( hSteamUGC
, handle
,
289 workshop_form
.submission
.visibility
.value
);
291 vg_info( "Submitting updates\n" );
292 vg_steam_async_call
*call
= vg_alloc_async_steam_api_call();
293 call
->userdata
= NULL
;
294 call
->p_handler
= on_workshop_update_result
;
295 call
->id
= SteamAPI_ISteamUGC_SubmitItemUpdate( hSteamUGC
, handle
, "" );
299 * Async thread for rearranging files into a reasonable workshop folder
301 struct workshop_package_thread_args
{
302 PublishedFileId_t file_id
;
307 VG_STATIC
void _workshop_package_thread( void *_args
)
309 struct workshop_package_thread_args
*args
= _args
;
310 PublishedFileId_t file_id
= args
->file_id
;
312 vg_info( "Packaging workshop content folder\n" );
314 vg_async_item
*call
= vg_async_alloc( sizeof(struct workshop_package_info
) );
315 struct workshop_package_info
*info
= call
->payload
;
316 info
->publishing_file_id
= file_id
;
317 info
->failure_reason
= "Unknown failure reason";
319 /* build content folder path */
320 snprintf( info
->abs_content_folder
, 1024, "%smodels/boards/workshop/%lu",
321 vg
.base_path
, file_id
);
323 /* build content file path */
324 snprintf( info
->abs_content_file
, 1024, "%s/%s", info
->abs_content_folder
,
327 /* build workshop preview file path */
328 snprintf( info
->abs_preview_image
, 1024, "%sworkshop_preview.jpg",
332 if( !vg_mkdir( info
->abs_content_folder
) ){
334 info
->failure_reason
= "Could not make directory.";
335 vg_async_dispatch( call
, workshop_form_async_package_complete
);
339 vg_linear_clear( vg_mem
.scratch
);
340 if( !vg_file_copy( workshop_form
.model_path
, info
->abs_content_file
,
343 info
->failure_reason
= "Copy file failed.";
344 vg_async_dispatch( call
, workshop_form_async_package_complete
);
348 /* write the metadata file */
349 struct workshop_file_info meta
;
350 meta
.author
= args
->steamid
;
351 vg_strncpy( args
->username
, meta
.author_name
, vg_list_size(meta
.author_name
),
352 k_strncpy_always_add_null
);
353 vg_strncpy( workshop_form
.submission
.title
, meta
.title
,
354 vg_list_size(meta
.title
), k_strncpy_always_add_null
);
358 vg_strnull( &path
, _path
, vg_list_size( _path
) );
359 vg_strcat( &path
, info
->abs_content_file
);
360 vg_strcat( &path
, ".inf" );
362 if( vg_strgood( &path
) ){
363 FILE *fp
= fopen( _path
, "wb" );
366 fwrite( &meta
, sizeof(struct workshop_file_info
), 1, fp
);
371 info
->failure_reason
= "Cant write .inf file";
372 vg_async_dispatch( call
, workshop_form_async_package_complete
);
377 info
->failure_reason
= "Path too long";
378 vg_async_dispatch( call
, workshop_form_async_package_complete
);
383 vg_async_dispatch( call
, workshop_form_async_package_complete
);
387 * Begins the packaging thread using file_id. Thread definition above.
389 VG_STATIC
void workshop_package_submission( PublishedFileId_t file_id
)
391 vg_linear_clear( vg_mem
.scratch
);
392 struct workshop_package_thread_args
*args
=
393 vg_linear_alloc( vg_mem
.scratch
,
394 sizeof(struct workshop_package_thread_args
));
396 ISteamFriends
*hSteamFriends
= SteamAPI_SteamFriends();
397 ISteamUser
*hSteamUser
= SteamAPI_SteamUser();
399 args
->steamid
= SteamAPI_ISteamUser_GetSteamID( hSteamUser
);
401 const char *username
= SteamAPI_ISteamFriends_GetPersonaName(hSteamFriends
);
402 str_utf8_collapse( username
, args
->username
, vg_list_size( args
->username
));
403 vg_info( "Steamid: "PRINTF_U64
", Name: %s(%s)\n",
404 args
->steamid
, username
, args
->username
);
406 args
->file_id
= file_id
;
407 vg_loader_start( _workshop_package_thread
, args
);
411 * Steam API call result for when we've created a new item on their network, or
412 * not, if it has failed
414 VG_STATIC
void on_workshop_createitem( void *data
, void *user
)
416 CreateItemResult_t
*result
= data
;
418 if( result
->m_eResult
== k_EResultOK
){
419 vg_info( "Created workshop file with id: %lu\n",
420 result
->m_nPublishedFileId
);
422 if( result
->m_bUserNeedsToAcceptWorkshopLegalAgreement
){
423 vg_warn( "Workshop agreement currently not accepted\n" );
426 workshop_package_submission( result
->m_nPublishedFileId
);
429 const char *errstr
= workshop_EResult_user_string( result
->m_eResult
);
432 vg_error( "ISteamUGC_CreateItem() failed(%d): '%s' \n",
433 result
->m_eResult
, errstr
);
436 workshop_form
.page
= k_workshop_form_closing_bad
;
437 workshop_form
.failure_or_success_string
= errstr
;
443 * Starts the workshop upload process through Steam API
445 VG_STATIC
void workshop_form_async_submit_begin( void *payload
, u32 size
)
447 /* use existing file */
448 if( workshop_form
.submission
.file_id
){
449 if( workshop_form
.submission
.submit_file_and_image
){
450 workshop_package_submission( workshop_form
.submission
.file_id
);
453 struct workshop_package_info info
;
454 info
.abs_content_file
[0] = '\0';
455 info
.abs_content_folder
[0] = '\0';
456 info
.abs_preview_image
[0] = '\0';
457 info
.failure_reason
= NULL
;
458 info
.publishing_file_id
= workshop_form
.submission
.file_id
;
460 workshop_form_async_package_complete( &info
,
461 sizeof(struct workshop_package_info
) );
465 vg_steam_async_call
*call
= vg_alloc_async_steam_api_call();
466 call
->userdata
= NULL
;
467 call
->p_handler
= on_workshop_createitem
;
468 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
469 call
->id
= SteamAPI_ISteamUGC_CreateItem( hSteamUGC
, SKATERIFT_APPID
,
470 k_EWorkshopFileTypeCommunity
);
475 * Downloads the framebuffer into scratch memory
477 VG_STATIC
void workshop_form_async_download_image( void *payload
, u32 size
)
480 render_fb_get_current_res( gpipeline
.fb_workshop_preview
, &w
, &h
);
481 vg_linear_clear( vg_mem
.scratch
);
482 workshop_form
.img_buffer
= vg_linear_alloc( vg_mem
.scratch
, w
*h
*3 );
484 vg_info( "read framebuffer: glReadPixels( %dx%d )\n", w
,h
);
486 glBindFramebuffer( GL_READ_FRAMEBUFFER
, gpipeline
.fb_workshop_preview
->fb
);
487 glReadBuffer( GL_COLOR_ATTACHMENT0
);
488 glReadPixels( 0,0, w
,h
, GL_RGB
, GL_UNSIGNED_BYTE
, workshop_form
.img_buffer
);
490 workshop_form
.img_w
= w
;
491 workshop_form
.img_h
= h
;
495 * Thread which kicks off the upload process
497 VG_STATIC
void _workshop_form_submit_thread( void *data
)
499 vg_async_call( workshop_form_async_download_image
, NULL
, 0 );
502 int w
= workshop_form
.img_w
,
503 h
= workshop_form
.img_h
;
505 vg_info( "writing: workshop_preview.jpg (%dx%d @90%%)\n", w
,h
);
506 stbi_flip_vertically_on_write(1);
507 stbi_write_jpg( "workshop_preview.jpg", w
,h
, 3,
508 workshop_form
.img_buffer
, 90 );
510 vg_async_call( workshop_form_async_submit_begin
, NULL
, 0 );
514 * Entry point for the publishing submission operation
516 VG_STATIC
void workshop_op_submit(void)
518 /* TODO: Show these errors to the user */
520 if( workshop_form
.submission
.submit_title
){
521 if( !workshop_form
.submission
.title
[0] ){
522 vg_error( "Cannot submit because a title is required\n" );
527 if( workshop_form
.submission
.submit_description
){
528 if( !workshop_form
.submission
.description
[0] ){
529 vg_error( "Cannot submit because a description is required\n" );
534 if( workshop_form
.submission
.submit_file_and_image
){
535 if( workshop_form
.file_intent
== k_workshop_form_file_intent_none
){
536 vg_error( "Cannot submit because the file is empty or unspecified\n" );
541 skaterift_begin_op( k_workshop_form_op_publishing_update
);
543 player_board_unload( &workshop_form
.board_model
);
544 workshop_form
.file_intent
= k_workshop_form_file_intent_none
;
546 vg_loader_start( _workshop_form_submit_thread
, NULL
);
550 * op: k_workshop_form_op_loading_model
551 * -----------------------------------------------------------------------------
555 * Reciever for completion of the model file load
557 VG_STATIC
void workshop_form_loadmodel_async_complete( void *payload
, u32 size
)
559 v2_zero( workshop_form
.view_angles
);
560 v3_zero( workshop_form
.view_offset
);
561 workshop_form
.view_dist
= 1.0f
;
562 workshop_form
.view_changed
= 1;
563 workshop_form
.file_intent
= k_workshop_form_file_intent_new
;
565 vg_success( "workshop async load complete\n" );
570 * Reciever for failure to load
572 VG_STATIC
void workshop_form_loadmodel_async_error( void *payload
, u32 size
)
578 * Thread which loads the model from the disk
580 VG_STATIC
void _workshop_form_load_thread( void *data
)
582 FILE *test
= fopen( workshop_form
.model_path
, "rb" );
585 player_board_load( &workshop_form
.board_model
, workshop_form
.model_path
);
586 vg_async_call( workshop_form_loadmodel_async_complete
, NULL
, 0 );
589 vg_error( "workshop async load failed: file not found\n" );
590 vg_async_call( workshop_form_loadmodel_async_error
, NULL
, 0 );
595 * Entry point for load model operation
597 VG_STATIC
void workshop_op_load_model(void)
599 skaterift_begin_op( k_workshop_form_op_loading_model
);
600 vg_loader_start( _workshop_form_load_thread
, NULL
);
604 * op: k_workshop_form_op_downloading_submission
605 * -----------------------------------------------------------------------------
609 * The image has been decoded and is ready to slap into the framebuffer
611 VG_STATIC
void workshop_form_async_imageload( void *data
, u32 len
)
614 struct framebuffer_attachment
*a
=
615 &gpipeline
.fb_workshop_preview
->attachments
[0];
617 glBindTexture( GL_TEXTURE_2D
, a
->id
);
618 glTexSubImage2D( GL_TEXTURE_2D
, 0,0,0,
619 WORKSHOP_PREVIEW_WIDTH
, WORKSHOP_PREVIEW_HEIGHT
,
620 a
->format
, a
->type
, data
);
621 stbi_image_free( data
);
622 vg_success( "Loaded workshop preview image\n" );
628 struct workshop_loadpreview_info
{
629 char abs_preview_image
[ 1024 ];
633 * Load the image located at ./workshop_preview.jpg into our framebuffer
635 VG_STATIC
void _workshop_load_preview_thread( void *data
)
637 struct workshop_loadpreview_info
*info
= data
;
639 stbi_set_flip_vertically_on_load(1);
641 u8
*rgb
= stbi_load( info
->abs_preview_image
, &x
, &y
, &nc
, 3 );
644 if( (x
== WORKSHOP_PREVIEW_WIDTH
) && (y
== WORKSHOP_PREVIEW_HEIGHT
) ){
645 vg_async_call( workshop_form_async_imageload
, rgb
, x
*y
*3 );
648 vg_error( "Resolution does not match framebuffer, so we can't"
650 stbi_image_free( rgb
);
651 vg_async_call( workshop_form_async_imageload
, NULL
, 0 );
655 vg_error( "Failed to load workshop_preview.jpg: '%s'\n",
656 stbi_failure_reason() );
657 vg_async_call( workshop_form_async_imageload
, NULL
, 0 );
662 * Reciever for the preview download result
664 VG_STATIC
void on_workshop_download_ugcpreview( void *data
, void *user
)
666 struct workshop_loadpreview_info
*info
= user
;
667 RemoteStorageDownloadUGCResult_t
*result
= data
;
669 if( result
->m_eResult
== k_EResultOK
){
670 ISteamRemoteStorage
*hSteamRemoteStorage
= SteamAPI_SteamRemoteStorage();
671 vg_loader_start( _workshop_load_preview_thread
, info
);
674 vg_error( "Error while donwloading UGC preview( %d )\n",
681 * Entry point to view operation
683 VG_STATIC
void workshop_op_download_and_view_submission( int result_index
)
685 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
686 ISteamRemoteStorage
*hSteamRemoteStorage
= SteamAPI_SteamRemoteStorage();
687 SteamUGCDetails_t details
;
688 if( SteamAPI_ISteamUGC_GetQueryUGCResult( hSteamUGC
,
689 workshop_form
.ugc_query
.handle
,
693 skaterift_begin_op( k_workshop_form_op_downloading_submission
);
694 workshop_reset_submission_data();
695 workshop_form
.submission
.submit_description
= 0;
696 workshop_form
.submission
.submit_file_and_image
= 0;
697 workshop_form
.submission
.submit_title
= 0;
699 vg_strncpy( details
.m_rgchDescription
,
700 workshop_form
.submission
.description
,
701 vg_list_size( workshop_form
.submission
.description
),
702 k_strncpy_always_add_null
);
704 vg_strncpy( details
.m_rgchTitle
,
705 workshop_form
.submission
.title
,
706 vg_list_size( workshop_form
.submission
.title
),
707 k_strncpy_always_add_null
);
709 snprintf( workshop_form
.model_path
,
710 vg_list_size( workshop_form
.model_path
),
711 "Steam Cloud (%lu)", details
.m_nPublishedFileId
);
713 workshop_form
.submission
.file_id
= details
.m_nPublishedFileId
;
714 workshop_form
.file_intent
= k_workshop_form_file_intent_keep_old
;
715 workshop_form
.page
= k_workshop_form_edit
;
716 workshop_form
.submission
.visibility
.value
= details
.m_eVisibility
;
718 for( i32 i
=0; i
<vg_list_size(workshop_form_visibility_opts
); i
++ ){
719 if( workshop_form_visibility_opts
[i
].value
== details
.m_eVisibility
){
720 workshop_form
.submission
.visibility
.index
= i
;
725 if( details
.m_hPreviewFile
== 0 ){
726 vg_error( "m_hPreviewFile is 0\n" );
730 /* Now need to begin downloading the image so we can display it */
731 vg_steam_async_call
*call
= vg_alloc_async_steam_api_call();
733 struct workshop_loadpreview_info
*info
=
734 vg_linear_alloc( vg_mem
.scratch
,
735 sizeof( struct workshop_loadpreview_info
) );
737 snprintf( info
->abs_preview_image
, 1024,
738 "%smodels/boards/workshop/" PRINTF_U64
".jpg",
739 vg
.base_path
, details
.m_hPreviewFile
);
741 call
->p_handler
= on_workshop_download_ugcpreview
;
742 call
->userdata
= info
;
743 call
->id
= SteamAPI_ISteamRemoteStorage_UGCDownloadToLocation(
745 details
.m_hPreviewFile
, info
->abs_preview_image
, 1 );
747 vg_info( "preview file id: " PRINTF_U64
"\n",
748 details
.m_hPreviewFile
);
752 vg_error( "GetQueryUGCResult: Index out of range\n" );
759 * -----------------------------------------------------------------------------
763 * View a page of results on the sidebar
765 VG_STATIC
void workshop_view_page( int req
)
767 if( workshop_form
.ugc_query
.result
!= k_EResultOK
){
768 vg_error( "Tried to change page without complete data\n" );
772 int page
= VG_MAX(VG_MIN(req
, workshop_form
.view_published_page_count
-1),0),
773 start
= page
* WORKSHOP_VIEW_PER_PAGE
,
774 end
= VG_MIN( (page
+1) * WORKSHOP_VIEW_PER_PAGE
,
775 workshop_form
.ugc_query
.returned_item_count
),
778 vg_info( "View page %d\n", page
);
780 workshop_form
.view_published_page_id
= page
;
781 workshop_form
.published_files_list_length
= count
;
782 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
784 for( int i
=0; i
<count
; i
++ ){
785 struct published_file
*pfile
= &workshop_form
.published_files_list
[i
];
787 SteamUGCDetails_t details
;
788 if( SteamAPI_ISteamUGC_GetQueryUGCResult( hSteamUGC
,
789 workshop_form
.ugc_query
.handle
,
793 if( details
.m_eResult
!= k_EResultOK
){
794 snprintf( pfile
->title
, 80, "Error (%d)", details
.m_eResult
);
797 vg_strncpy( details
.m_rgchTitle
, pfile
->title
, 80,
798 k_strncpy_always_add_null
);
801 pfile
->result
= details
.m_eResult
;
802 pfile
->result_index
= start
+i
;
805 pfile
->result
= k_EResultValueOutOfRange
;
806 pfile
->result_index
= -1;
807 snprintf( pfile
->title
, 80, "Error (invalid index)" );
813 * Steam API result for when we recieve submitted UGC information about the user
815 VG_STATIC
void on_workshop_UGCQueryComplete( void *data
, void *userdata
)
817 SteamUGCQueryCompleted_t
*query
= data
;
818 workshop_form
.ugc_query
.result
= query
->m_eResult
;
820 if( query
->m_eResult
== k_EResultOK
){
821 if( query
->m_unTotalMatchingResults
> 50 ){
822 vg_warn( "You have %d items submitted, "
823 "we can only view the last 50\n" );
826 workshop_form
.ugc_query
.all_item_count
= query
->m_unTotalMatchingResults
;
827 workshop_form
.ugc_query
.returned_item_count
=
828 query
->m_unNumResultsReturned
;
830 workshop_form
.ugc_query
.handle
= query
->m_handle
;
831 workshop_form
.view_published_page_count
=
832 (query
->m_unNumResultsReturned
+WORKSHOP_VIEW_PER_PAGE
-1)/
833 WORKSHOP_VIEW_PER_PAGE
;
834 workshop_form
.view_published_page_id
= 0;
835 workshop_form
.published_files_list_length
= 0;
837 workshop_view_page( 0 );
840 vg_error( "Steam UGCQuery failed (%d)\n", query
->m_eResult
);
841 workshop_form
.view_published_page_count
= 0;
842 workshop_form
.view_published_page_id
= 0;
843 workshop_form
.published_files_list_length
= 0;
845 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
846 SteamAPI_ISteamUGC_ReleaseQueryUGCRequest( hSteamUGC
, query
->m_handle
);
851 * Console command to open the workshop publisher
853 VG_STATIC
int workshop_submit_command( int argc
, const char *argv
[] )
856 vg_error( "Steam API is not ready or loaded\n" );
860 workshop_form
.page
= k_workshop_form_open
;
861 workshop_form
.view_published_page_count
= 0;
862 workshop_form
.view_published_page_id
= 0;
863 workshop_form
.published_files_list_length
= 0;
864 workshop_form
.ugc_query
.result
= k_EResultNone
;
866 vg_steam_async_call
*call
= vg_alloc_async_steam_api_call();
868 ISteamUser
*hSteamUser
= SteamAPI_SteamUser();
870 steamid
.m_unAll64Bits
= SteamAPI_ISteamUser_GetSteamID( hSteamUser
);
872 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
873 call
->p_handler
= on_workshop_UGCQueryComplete
;
874 call
->userdata
= NULL
;
875 UGCQueryHandle_t handle
= SteamAPI_ISteamUGC_CreateQueryUserUGCRequest
878 steamid
.m_comp
.m_unAccountID
,
879 k_EUserUGCList_Published
,
880 k_EUGCMatchingUGCType_Items
,
881 k_EUserUGCListSortOrder_CreationOrderDesc
,
882 SKATERIFT_APPID
, SKATERIFT_APPID
,
884 call
->id
= SteamAPI_ISteamUGC_SendQueryUGCRequest( hSteamUGC
, handle
);
888 VG_STATIC
void workshop_init(void)
890 vg_console_reg_cmd( "workshop_submit", workshop_submit_command
, NULL
);
893 VG_STATIC
void workshop_find_preview_entity(void)
895 workshop_form
.view_world
= world_current_instance();
897 if( mdl_arrcount( &workshop_form
.view_world
->ent_swspreview
) ){
898 workshop_form
.ptr_ent
=
899 mdl_arritm( &workshop_form
.view_world
->ent_swspreview
, 0 );
900 workshop_form
.page
= k_workshop_form_edit
;
903 vg_error( "There is no ent_swspreview in the level. "
904 "Cannot publish here\n" );
909 * Redraw the model file into the workshop framebuffer
911 VG_STATIC
void workshop_render_preview(void)
913 if( !workshop_form
.ptr_ent
){
917 render_fb_bind( gpipeline
.fb_workshop_preview
, 0 );
919 glClearColor( 0.0f
, 0.0f
, 0.3f
, 1.0f
);
920 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
921 glEnable( GL_DEPTH_TEST
);
922 glDisable( GL_BLEND
);
924 ent_swspreview
*swsprev
= workshop_form
.ptr_ent
;
925 world_instance
*world
= workshop_form
.view_world
;
927 ent_camera
*ref
= mdl_arritm( &world
->ent_camera
,
928 mdl_entity_id_id(swsprev
->id_camera
) );
929 ent_marker
*display
= mdl_arritm( &world
->ent_marker
,
930 mdl_entity_id_id(swsprev
->id_display
) ),
931 *display1
= mdl_arritm( &world
->ent_marker
,
932 mdl_entity_id_id(swsprev
->id_display1
) );
935 v3_add( display
->transform
.co
, display1
->transform
.co
, baseco
);
936 v3_muls( baseco
, 0.5f
, baseco
);
940 v3_sub( display
->transform
.co
, ref
->transform
.co
, basevector
);
941 float dist
= v3_length( basevector
);
944 player_vector_angles( baseangles
, basevector
, 1.0f
, 0.0f
);
946 v2_add( workshop_form
.view_angles
, baseangles
, cam
.angles
);
947 cam
.angles
[2] = 0.0f
;
949 float sX
= sinf( cam
.angles
[0] ),
950 cX
= cosf( cam
.angles
[0] ),
951 sY
= sinf( cam
.angles
[1] ),
952 cY
= cosf( cam
.angles
[1] );
954 v3f offset
= { -sX
* cY
, sY
, cX
* cY
};
956 v3_muladds( display
->transform
.co
, offset
,
957 dist
*workshop_form
.view_dist
, cam
.pos
);
959 cam
.pos
[0] += -sX
*workshop_form
.view_offset
[2];
960 cam
.pos
[2] += cX
*workshop_form
.view_offset
[2];
961 cam
.pos
[0] += cX
*workshop_form
.view_offset
[0];
962 cam
.pos
[2] += sX
*workshop_form
.view_offset
[0];
963 cam
.pos
[1] += workshop_form
.view_offset
[1];
969 camera_update_transform( &cam
);
970 camera_update_view( &cam
);
971 camera_update_projection( &cam
);
972 camera_finalize( &cam
);
975 mdl_transform_m4x3( &display
->transform
, mmdl
);
976 mdl_transform_m4x3( &display1
->transform
, mmdl1
);
978 /* force update this for nice shadows. its usually set in the world
979 * pre-render step, but that includes timer stuff
981 struct player_board
*board
= &workshop_form
.board_model
;
982 struct ub_world_lighting
*ubo
= &world
->ub_lighting
;
984 v3_copy((v3f
){0.0f
,0.1f
, board
->truck_positions
[0][2]}, vp0
);
985 v3_copy((v3f
){0.0f
,0.1f
, board
->truck_positions
[1][2]}, vp1
);
986 m4x3_mulv( mmdl1
, vp0
, ubo
->g_board_0
);
987 m4x3_mulv( mmdl1
, vp1
, ubo
->g_board_1
);
988 glBindBuffer( GL_UNIFORM_BUFFER
, world
->ubo_lighting
);
989 glBufferSubData( GL_UNIFORM_BUFFER
, 0,
990 sizeof(struct ub_world_lighting
), &world
->ub_lighting
);
992 render_world( world
, &cam
, 1 );
993 render_board( &cam
, world
, board
, mmdl
, k_board_shader_entity
);
994 render_board( &cam
, world
, board
, mmdl1
, k_board_shader_entity
);
996 glBindFramebuffer( GL_FRAMEBUFFER
, 0 );
997 glViewport( 0,0, vg
.window_x
, vg
.window_y
);
1001 * ImGUI section for workshop form
1002 * -----------------------------------------------------------------------------
1005 VG_STATIC
void workshop_changed_model_path( char *buf
, u32 len
){
1006 workshop_form
.submission
.submit_file_and_image
= 1;
1009 VG_STATIC
void workshop_changed_title( char *buf
, u32 len
){
1010 workshop_form
.submission
.submit_title
= 1;
1013 VG_STATIC
void workshop_changed_description( char *buf
, u32 len
){
1014 workshop_form
.submission
.submit_description
= 1;
1017 VG_STATIC
void workshop_form_gui_edit_page( ui_rect content
)
1019 ui_rect image_plane
;
1020 ui_split( content
, k_ui_axis_h
, 300, 0, image_plane
, content
);
1021 ui_fill( image_plane
, ui_colour( k_ui_bg
+0 ) );
1024 ui_fit_item( image_plane
, (ui_px
[2]){ 3, 2 }, img_box
);
1026 if( workshop_form
.file_intent
== k_workshop_form_file_intent_keep_old
){
1027 ui_image( img_box
, gpipeline
.fb_workshop_preview
->attachments
[0].id
);
1029 else if( workshop_form
.file_intent
== k_workshop_form_file_intent_new
){
1030 ui_image( img_box
, gpipeline
.fb_workshop_preview
->attachments
[0].id
);
1031 int hover
= ui_inside_rect( img_box
, vg_ui
.mouse
),
1032 target
= ui_inside_rect( img_box
, vg_ui
.mouse_click
);
1034 if( ui_click_down(UI_MOUSE_MIDDLE
) && target
){
1035 v3_copy( workshop_form
.view_offset
,
1036 workshop_form
.view_offset_begin
);
1038 else if( ui_click_down(UI_MOUSE_LEFT
) && target
){
1039 v2_copy( workshop_form
.view_angles
,
1040 workshop_form
.view_angles_begin
);
1043 if( ui_clicking(UI_MOUSE_MIDDLE
) && target
){
1044 v2f delta
= { vg_ui
.mouse
[0]-vg_ui
.mouse_click
[0],
1045 vg_ui
.mouse
[1]-vg_ui
.mouse_click
[1] };
1047 float *begin
= workshop_form
.view_offset_begin
,
1048 *offset
= workshop_form
.view_offset
;
1049 offset
[0] = vg_clampf( begin
[0]-delta
[0]*0.002f
, -1.0f
, 1.0f
);
1050 offset
[2] = vg_clampf( begin
[2]-delta
[1]*0.002f
, -1.0f
, 1.0f
);
1051 workshop_form
.view_changed
= 1;
1053 else if( ui_clicking(UI_MOUSE_LEFT
) && target
){
1054 v2f delta
= { vg_ui
.mouse
[0]-vg_ui
.mouse_click
[0],
1055 vg_ui
.mouse
[1]-vg_ui
.mouse_click
[1] };
1058 v2_muladds( workshop_form
.view_angles_begin
, delta
, 0.002f
, angles
);
1060 float limit
= VG_PIf
*0.2f
;
1062 angles
[0] = vg_clampf( angles
[0], -limit
, limit
);
1063 angles
[1] = vg_clampf( angles
[1], -limit
, limit
);
1065 v2_copy( angles
, workshop_form
.view_angles
);
1066 workshop_form
.view_changed
= 1;
1069 if( !ui_clicking(UI_MOUSE_LEFT
) && hover
){
1070 float zoom
= workshop_form
.view_dist
;
1071 zoom
+= vg
.mouse_wheel
[1] * -0.07f
;
1072 zoom
= vg_clampf( zoom
, 0.4f
, 2.0f
);
1074 if( zoom
!= workshop_form
.view_dist
){
1075 workshop_form
.view_changed
= 1;
1076 workshop_form
.view_dist
= zoom
;
1081 ui_text( img_box
, "No image", 1, k_ui_align_middle_center
,
1082 ui_colour( k_ui_orange
) );
1086 ui_rect null
, file_entry
, file_button
, file_label
;
1087 ui_split( content
, k_ui_axis_h
, 8, 0, null
, content
);
1088 ui_split( content
, k_ui_axis_h
, 28, 0, file_entry
, content
);
1089 ui_split( file_entry
, k_ui_axis_v
, -128, 0, file_entry
, file_button
);
1090 ui_split_label( file_entry
, "File:", 1, 8, file_label
, file_entry
);
1091 ui_text( file_label
, "File:", 1, k_ui_align_middle_left
, 0 );
1093 if( workshop_form
.file_intent
!= k_workshop_form_file_intent_none
){
1094 ui_text( file_entry
, workshop_form
.model_path
, 1, k_ui_align_middle_left
,
1095 ui_colour( k_ui_fg
+4 ) );
1097 if( ui_button_text( file_button
, "Remove", 1 ) ){
1098 player_board_unload( &workshop_form
.board_model
);
1099 workshop_form
.file_intent
= k_workshop_form_file_intent_none
;
1100 workshop_form
.model_path
[0] = '\0';
1104 struct ui_textbox_callbacks callbacks
= {
1105 .change
= workshop_changed_model_path
1108 ui_textbox( file_entry
, workshop_form
.model_path
,
1109 vg_list_size(workshop_form
.model_path
), 0, &callbacks
);
1111 if( ui_button_text( file_button
, "Load", 1 ) ){
1112 workshop_find_preview_entity();
1113 workshop_op_load_model();
1117 ui_rect title_entry
, label
;
1118 ui_split( content
, k_ui_axis_h
, 8, 0, null
, content
);
1119 ui_split( content
, k_ui_axis_h
, 28, 0, title_entry
, content
);
1121 const char *str_title
= "Title:", *str_desc
= "Description:";
1122 ui_split( title_entry
, k_ui_axis_v
,
1123 ui_text_line_width(str_title
)+8, 0, label
, title_entry
);
1125 ui_rect vis_dropdown
;
1126 ui_split_ratio( title_entry
, k_ui_axis_v
, 0.6f
, 16,
1127 title_entry
, vis_dropdown
);
1131 struct ui_textbox_callbacks callbacks
= {
1132 .change
= workshop_changed_title
1134 ui_text( label
, str_title
, 1, k_ui_align_middle_left
, 0 );
1135 ui_textbox( title_entry
, workshop_form
.submission
.title
,
1136 vg_list_size(workshop_form
.submission
.title
), 0, &callbacks
);
1139 /* visibility option */
1141 ui_enum( vis_dropdown
, "Visibility:", workshop_form_visibility_opts
,
1142 4, &workshop_form
.submission
.visibility
);
1145 /* description box */
1147 struct ui_textbox_callbacks callbacks
= {
1148 .change
= workshop_changed_description
1151 ui_split( content
, k_ui_axis_h
, 8, 0, null
, content
);
1152 ui_split( content
, k_ui_axis_h
, 28, 0, label
, content
);
1153 ui_split( content
, k_ui_axis_h
, 28*4, 0, desc_entry
, content
);
1154 ui_text( label
, str_desc
, 1, k_ui_align_middle_left
, 0 );
1155 ui_textbox( desc_entry
, workshop_form
.submission
.description
,
1156 vg_list_size(workshop_form
.submission
.description
),
1157 UI_TEXTBOX_MULTILINE
|UI_TEXTBOX_WRAP
, &callbacks
);
1160 /* submissionable */
1161 ui_rect submission_row
;
1162 ui_split( content
, k_ui_axis_h
, 8, 0, null
, content
);
1163 ui_split( content
, k_ui_axis_h
, content
[3]-32-8, 0, content
,
1166 ui_rect submission_center
;
1167 rect_copy( submission_row
, submission_center
);
1168 submission_center
[2] = 256;
1169 ui_rect_center( submission_row
, submission_center
);
1171 ui_rect btn_left
, btn_right
;
1172 ui_split_ratio( submission_center
, k_ui_axis_v
, 0.5f
, 8,
1173 btn_left
, btn_right
);
1175 if( ui_button_text( btn_left
, "Publish", 1 ) ){
1176 workshop_op_submit();
1178 if( ui_button_text( btn_right
, "Cancel", 1 ) ){
1179 workshop_form
.page
= k_workshop_form_open
;
1180 player_board_unload( &workshop_form
.board_model
);
1181 workshop_form
.file_intent
= k_workshop_form_file_intent_none
;
1185 const char *disclaimer_text
=
1186 "By submitting this item, you agree to the workshop terms of service";
1188 ui_rect disclaimer_row
, inner
, link
;
1189 ui_split( content
, k_ui_axis_h
, 8, 0, null
, content
);
1190 ui_split( content
, k_ui_axis_h
, content
[3]-32, 0, content
,
1193 ui_px btn_width
= 32;
1195 rect_copy( disclaimer_row
, inner
);
1196 inner
[2] = ui_text_line_width( disclaimer_text
) + btn_width
+8;
1198 ui_rect_center( disclaimer_row
, inner
);
1199 ui_split( inner
, k_ui_axis_v
, inner
[2]-btn_width
, 0, label
, btn_right
);
1200 ui_rect_pad( btn_right
, (ui_px
[2]){2,2} );
1202 if( ui_button_text( btn_right
, "\x91", 2 ) ){
1203 ISteamFriends
*hSteamFriends
= SteamAPI_SteamFriends();
1204 SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage( hSteamFriends
,
1205 "https://steamcommunity.com/sharedfiles/workshoplegalagreement",
1206 k_EActivateGameOverlayToWebPageMode_Default
);
1209 ui_text( label
, disclaimer_text
, 1, k_ui_align_middle_left
,
1210 ui_colour( k_ui_fg
+4 ) );
1213 VG_STATIC
void workshop_form_gui_sidebar( ui_rect sidebar
)
1216 * sidebar existing entries panel
1218 ui_fill( sidebar
, ui_colour( k_ui_bg
+2 ) );
1221 ui_split( sidebar
, k_ui_axis_h
, 28, 0, title
, sidebar
);
1222 ui_text( title
, "Your submissions", 1, k_ui_align_middle_center
, 0 );
1224 ui_rect controls
, btn_create_new
;
1225 ui_split( sidebar
, k_ui_axis_h
, 32, 0, controls
, sidebar
);
1226 ui_split( sidebar
, k_ui_axis_h
, -32, 0, sidebar
, btn_create_new
);
1227 ui_fill( controls
, ui_colour( k_ui_bg
+1 ) );
1230 strcpy( buf
, "page " );
1232 i
+= highscore_intl( buf
+i
, workshop_form
.view_published_page_id
+1, 4 );
1234 i
+= highscore_intl( buf
+i
, workshop_form
.view_published_page_count
, 4 );
1237 ui_rect_pad( controls
, (ui_px
[2]){0,4} );
1239 ui_split_ratio( controls
, k_ui_axis_v
, 0.25f
, 0, info
, controls
);
1240 ui_text( info
, buf
, 1, k_ui_align_middle_center
, 0 );
1242 ui_rect btn_left
, btn_right
;
1243 ui_split_ratio( controls
, k_ui_axis_v
, 0.5f
, 2, btn_left
, btn_right
);
1245 if( ui_button_text( btn_left
, "newer", 1 ) ){
1246 workshop_view_page( workshop_form
.view_published_page_id
-1 );
1249 if( ui_button_text( btn_right
, "older", 1 ) ){
1250 workshop_view_page( workshop_form
.view_published_page_count
+1 );
1253 if( ui_button_text( btn_create_new
, "Create New Item", 1 ) ){
1254 workshop_reset_submission_data();
1255 workshop_form
.submission
.submit_title
= 1;
1256 workshop_form
.submission
.submit_description
= 1;
1257 workshop_form
.submission
.submit_file_and_image
= 1;
1258 workshop_form
.page
= k_workshop_form_edit
;
1259 workshop_find_preview_entity();
1262 for( int i
=0; i
<workshop_form
.published_files_list_length
; i
++ ){
1264 ui_split( sidebar
, k_ui_axis_h
, 28, 0, item
, sidebar
);
1265 ui_rect_pad( item
, (ui_px
[2]){4,4} );
1267 struct published_file
*pfile
= &workshop_form
.published_files_list
[i
];
1268 if( ui_button_text( item
, pfile
->title
, 1 ) ){
1269 if( pfile
->result
== k_EResultOK
){
1270 vg_info( "Select index: %d\n", pfile
->result_index
);
1271 workshop_op_download_and_view_submission( pfile
->result_index
);
1274 vg_warn( "Cannot select that item, result not OK\n" );
1280 VG_STATIC
void workshop_form_gui(void)
1282 enum workshop_form_page stable_page
= workshop_form
.page
;
1283 if( stable_page
== k_workshop_form_hidden
) return;
1286 ui_rect screen
= { 0, 0, vg
.window_x
, vg
.window_y
};
1287 ui_rect window
= { 0, 0, 1000, 700 };
1288 ui_rect_center( screen
, window
);
1289 vg_ui
.wants_mouse
= 1;
1291 ui_fill( window
, ui_colour( k_ui_bg
+1 ) );
1292 ui_outline( window
, 1, ui_colour( k_ui_bg
+7 ) );
1294 ui_rect title
, panel
;
1295 ui_split( window
, k_ui_axis_h
, 28, 0, title
, panel
);
1296 ui_fill( title
, ui_colour( k_ui_bg
+7 ) );
1297 ui_text( title
, "Workshop tool", 1, k_ui_align_middle_center
,
1298 ui_colourcont(k_ui_bg
+7) );
1300 ui_rect quit_button
;
1301 ui_split( title
, k_ui_axis_v
, title
[2]-title
[3], 2, title
, quit_button
);
1303 if( skaterift
.async_op
== k_async_op_none
){
1304 if( ui_button_text( quit_button
, "X", 1 ) ){
1305 workshop_quit_form();
1311 * temporary operation blinders, we don't yet have a nice way to show the
1312 * user that we're doing something uninterruptable, so the code just
1313 * escapes here and we show them a basic string
1316 if( skaterift
.async_op
!= k_async_op_none
){
1317 const char *op_string
= "The programmer has not bothered to describe "
1318 "the current operation that is running.";
1320 switch( skaterift
.async_op
){
1321 case k_workshop_form_op_loading_model
:
1322 op_string
= "Operation in progress: Loading model file.";
1324 case k_workshop_form_op_publishing_update
:
1325 op_string
= "Operation in progress: publishing submission update "
1328 case k_workshop_form_op_downloading_submission
:
1329 op_string
= "Operation in progress: downloading existing submission"
1330 " from Steam services.";
1335 ui_text( panel
, op_string
, 1, k_ui_align_middle_center
, 0 );
1339 /* re draw board preview if need to */
1340 if( (stable_page
== k_workshop_form_edit
) &&
1341 workshop_form
.view_changed
&&
1342 workshop_form
.file_intent
== k_workshop_form_file_intent_new
)
1344 workshop_render_preview();
1345 workshop_form
.view_changed
= 0;
1348 struct workshop_form
*form
= &workshop_form
;
1350 ui_rect sidebar
, content
;
1351 ui_split_ratio( panel
, k_ui_axis_v
, 0.3f
, 1, sidebar
, content
);
1354 ui_rect_pad( content
, (ui_px
[2]){8,8} );
1356 if( stable_page
== k_workshop_form_edit
){
1357 workshop_form_gui_edit_page( content
);
1359 else if( stable_page
== k_workshop_form_open
){
1360 ui_text( content
, "Nothing selected.", 1, k_ui_align_middle_center
,
1361 ui_colour( k_ui_fg
+4 ) );
1363 else if( stable_page
>= k_workshop_form_cclosing
){
1364 ui_rect submission_row
;
1365 ui_split( content
, k_ui_axis_h
, content
[3]-32-8, 0, content
,
1370 if( stable_page
== k_workshop_form_closing_bad
)
1371 colour
= ui_colour( k_ui_red
+k_ui_brighter
);
1373 colour
= ui_colour( k_ui_green
+k_ui_brighter
);
1375 ui_text( content
, workshop_form
.failure_or_success_string
, 1,
1376 k_ui_align_middle_center
, colour
);
1378 ui_rect submission_center
;
1379 rect_copy( submission_row
, submission_center
);
1380 submission_center
[2] = 128;
1381 ui_rect_center( submission_row
, submission_center
);
1382 ui_rect_pad( submission_center
, (ui_px
[2]){8,8} );
1384 if( ui_button_text( submission_center
, "OK", 1 ) ){
1385 workshop_form
.page
= k_workshop_form_open
;
1389 workshop_form_gui_sidebar( sidebar
);
1393 * Some async api stuff
1394 * -----------------------------------------------------------------------------
1397 VG_STATIC
void async_workshop_get_filepath( void *data
, u32 len
)
1399 struct async_workshop_filepath_info
*info
= data
;
1404 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
1405 if( !SteamAPI_ISteamUGC_GetItemInstallInfo( hSteamUGC
, info
->id
, &_size
,
1406 info
->buf
, info
->len
, &_ts
))
1408 info
->buf
[0] = '\0';
1412 VG_STATIC
void async_workshop_get_installed_files( void *data
, u32 len
)
1414 struct async_workshop_installed_files_info
*info
= data
;
1416 ISteamUGC
*hSteamUGC
= SteamAPI_SteamUGC();
1417 u32 count
= SteamAPI_ISteamUGC_GetSubscribedItems( hSteamUGC
, info
->buffer
,
1420 vg_info( "Found %u subscribed items\n", count
);
1423 for( u32 i
=0; i
<count
; i
++ ){
1424 u32 state
= SteamAPI_ISteamUGC_GetItemState( hSteamUGC
, info
->buffer
[i
] );
1425 if( state
& k_EItemStateInstalled
){
1426 info
->buffer
[j
++] = info
->buffer
[i
];
1433 VG_STATIC
void vg_strsan_ascii( char *buf
, u32 len
)
1435 for( u32 i
=0; i
<len
-1; i
++ ){
1436 if( buf
[i
] == 0 ) return;
1438 if( buf
[i
] < 32 || buf
[i
] > 126 ){
1445 #define VG_STRSAN_ASCII( X ) vg_strsan_ascii( X, vg_list_size(X) )
1447 VG_STATIC
void workshop_load_metadata( const char *path
,
1448 struct workshop_file_info
*info
)
1450 FILE *fp
= fopen( path
, "rb" );
1453 if( fread( info
, sizeof( struct workshop_file_info
), 1, fp
) ){
1454 VG_STRSAN_ASCII( info
->author_name
);
1455 VG_STRSAN_ASCII( info
->title
);
1461 #endif /* WORKSHOP_C */