#include "vg/vg_tex.h"
#include "ent_skateshop.h"
+#include "vg/vg_steam_auth.h"
#include "vg/vg_steam_ugc.h"
+#include "vg/vg_steam_friends.h"
+
+#define WORKSHOP_VIEW_PER_PAGE 15
struct workshop_form{
- char title[80];
- char description[512];
+ struct {
+ char title[80];
+ char description[512];
+
+ PublishedFileId_t file_id; /* 0 if not published yet */
+ ERemoteStoragePublishedFileVisibility visibility;
+
+ int submit_title, /* set if the respective controls are touched */
+ submit_description,
+ submit_file_and_image;
+ }
+ submission;
+
+ enum workshop_form_page{
+ k_workshop_form_hidden,
+ k_workshop_form_open, /* open but not looking at anything */
+ k_workshop_form_edit, /* editing a submission */
+ k_workshop_form_cclosing,
+ k_workshop_form_closing_good, /* post upload screen */
+ k_workshop_form_closing_bad,
+ }
+ page;
+
+ enum workshop_form_operation{
+ k_workshop_form_op_none,
+ k_workshop_form_op_loading_model,
+ k_workshop_form_op_downloading_submission,
+ k_workshop_form_op_publishing_update,
+ }
+ operation;
+
+ /* model viewer
+ * -----------------------------
+ */
+
char model_path[128];
struct player_board board_model;
+ /* what does the user want to do with the image preview? */
+ enum workshop_form_file_intent{
+ k_workshop_form_file_intent_none, /* loading probably */
+ k_workshop_form_file_intent_new, /* board_model is valid */
+ k_workshop_form_file_intent_keep_old /* just browsing */
+ }
+ file_intent;
+
world_instance *view_world;
ent_swspreview *ptr_ent;
-
v2f view_angles,
view_angles_begin;
v3f view_offset,
float view_dist;
int view_changed;
- enum workshop_form_status{
- k_workshop_form_hidden,
- k_workshop_form_editing,
- k_workshop_form_submitting,
- k_workshop_form_failed
+ /*
+ * published UGC request
+ * ------------------------------
+ */
+
+ struct {
+ UGCQueryHandle_t handle;
+ EResult result;
+
+ int all_item_count,
+ returned_item_count;
}
- status;
- const char *failure_reason;
- PublishedFileId_t publishing_file_id;
+ ugc_query;
+
+ /*
+ * UI information
+ * ------------------------------------------
+ */
+
+ const char *failure_or_success_string;
int img_w, img_h;
u8 *img_buffer;
+
+ int view_published_page_count,
+ view_published_page_id;
+
+ struct published_file{
+ EResult result;
+ int result_index;
+ char title[80];
+ }
+ published_files_list[WORKSHOP_VIEW_PER_PAGE];
+ int published_files_list_length;
}
static workshop_form;
-struct workshop_package_info {
- int success;
- char abs_preview_image[ 1024 ];
- char abs_content_folder[ 1024 ];
- char abs_content_file[ 1024 ];
-};
+/*
+ * Start a new operation and crash if we are already running one.
+ */
+VG_STATIC void workshop_begin_op( enum workshop_form_operation op )
+{
+ if( workshop_form.operation != k_workshop_form_op_none ){
+ vg_fatal_error( "Workshop form currently executing op(%d), tried to "
+ "start op(%d)\n", workshop_form.operation, op );
+ }
+
+ workshop_form.operation = op;
+ vg_info( "Starting op( %d )\n", op );
+}
+
+/*
+ * Finished operation, otheres can now run
+ */
+VG_STATIC void workshop_end_op(void)
+{
+ vg_info( "Finishing op( %d )\n", workshop_form.operation );
+ workshop_form.operation = k_workshop_form_op_none;
+}
+
+/*
+ * Close the form and discard UGC query result
+ */
+VG_STATIC void workshop_quit_form(void)
+{
+ workshop_begin_op( k_workshop_form_op_none ); /* safeguard */
+ player_board_unload( &workshop_form.board_model );
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+
+ if( workshop_form.ugc_query.result == k_EResultOK ){
+ workshop_form.ugc_query.result = k_EResultNone;
+
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(
+ hSteamUGC, workshop_form.ugc_query.handle );
+ }
+
+ workshop_form.page = k_workshop_form_hidden;
+}
+
+/*
+ * Delete all information about the submission
+ */
+VG_STATIC void workshop_reset_submission_data(void)
+{
+ workshop_form.submission.file_id = 0; /* assuming id of 0 is none/invalid */
+ workshop_form.submission.description[0] = '\0';
+ workshop_form.submission.title[0] = '\0';
+ workshop_form.model_path[0] = '\0';
+ player_board_unload( &workshop_form.board_model );
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+}
+
+
+/*
+ * Mostly copies of what it sais on the Steam API documentation
+ */
+VG_STATIC const char *workshop_EResult_user_string( EResult result )
+{
+ switch( result ){
+ case k_EResultInsufficientPrivilege:
+ return "Your account is currently restricted from uploading content "
+ "due to a hub ban, account lock, or community ban. You need to "
+ "contact Steam Support to resolve the issue.";
+ case k_EResultBanned:
+ return "You do not have permission to upload content to this hub "
+ "because you have an active VAC or Game ban.";
+ case k_EResultTimeout:
+ return "The operation took longer than expected, so it was discarded. "
+ "Please try again.";
+ case k_EResultNotLoggedOn:
+ return "You are currently not logged into Steam.";
+ case k_EResultServiceUnavailable:
+ return "The workshop server is having issues or is unavailable, "
+ "please try again.";
+ case k_EResultInvalidParam:
+ return "One of the submission fields contains something not being "
+ "accepted by that field.";
+ case k_EResultAccessDenied:
+ return "There was a problem trying to save the title and description. "
+ "Access was denied.";
+ case k_EResultLimitExceeded:
+ return "You have exceeded your Steam Cloud quota. If you wish to "
+ "upload this file, you must remove some published items.";
+ case k_EResultFileNotFound:
+ return "The uploaded file could not be found.";
+ case k_EResultDuplicateRequest:
+ return "The file was already successfully uploaded.";
+ case k_EResultDuplicateName:
+ return "You already have a Steam Workshop item with that name.";
+ case k_EResultServiceReadOnly:
+ return "Due to a recent password or email change, you are not allowed "
+ "to upload new content. Usually this restriction will expire in"
+ " 5 days, but can last up to 30 days if the account has been "
+ "inactive recently.";
+ default:
+ return "Operation failed for an error which has not been accounted for "
+ "by the programmer. Try again, sorry :)";
+ }
+}
+/*
+ * op: k_workshop_form_op_publishing_update
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * The endpoint of this operation
+ */
VG_STATIC void on_workshop_update_result( void *data, void *user )
{
vg_info( "Recieved workshop update result\n" );
SubmitItemUpdateResult_t *result = data;
+ /* this seems to be set here, but my account definitely has accepted it */
if( result->m_bUserNeedsToAcceptWorkshopLegalAgreement ){
vg_warn( "Workshop agreement currently not accepted\n" );
}
if( result->m_eResult == k_EResultOK ){
- vg_success( "Successfully uploaded workshop file\n" );
+ workshop_form.page = k_workshop_form_closing_good;
+ workshop_form.failure_or_success_string = "Uploaded workshop file!";
+ vg_success( "file uploaded\n" );
}
else{
+ workshop_form.page = k_workshop_form_closing_bad;
+ workshop_form.failure_or_success_string =
+ workshop_EResult_user_string( result->m_eResult );
+
vg_error( "Error with the submitted file (%d)\n", result->m_eResult );
}
+
+ workshop_end_op();
}
+struct workshop_package_info {
+ PublishedFileId_t publishing_file_id;
+
+ int success;
+ char abs_preview_image[ 1024 ];
+ char abs_content_folder[ 1024 ];
+ char abs_content_file[ 1024 ];
+
+ const char *failure_reason;
+};
+
+/*
+ * reciever on completion of packaging the files, it will then start the item
+ * update with Steam API
+ */
VG_STATIC void workshop_form_async_package_complete( void *data, u32 size )
{
struct workshop_package_info *info = data;
if( !info->success ){
- workshop_form.status = k_workshop_form_failed;
- workshop_form.failure_reason = "Packaging workshop folder failed.";
+ workshop_form.page = k_workshop_form_closing_bad;
+ workshop_form.failure_or_success_string = info->failure_reason;
+ workshop_end_op();
return;
}
ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
UGCUpdateHandle_t handle
= SteamAPI_ISteamUGC_StartItemUpdate( hSteamUGC, SKATERIFT_APPID,
- workshop_form.publishing_file_id );
+ info->publishing_file_id );
/* TODO: Handle failure cases for these */
- vg_info( "Setting title\n" );
- SteamAPI_ISteamUGC_SetItemTitle( hSteamUGC, handle, workshop_form.title );
-
- vg_info( "Setting description\n" );
- SteamAPI_ISteamUGC_SetItemDescription( hSteamUGC, handle,
- workshop_form.description );
- vg_info( "Setting preview image\n" );
- SteamAPI_ISteamUGC_SetItemPreview( hSteamUGC,
- handle, info->abs_preview_image );
- vg_info( "Setting item content\n" );
- SteamAPI_ISteamUGC_SetItemContent( hSteamUGC, handle,
- info->abs_content_folder );
+
+ if( workshop_form.submission.submit_title ){
+ vg_info( "Setting title\n" );
+ SteamAPI_ISteamUGC_SetItemTitle( hSteamUGC, handle,
+ workshop_form.submission.title );
+ }
+
+ if( workshop_form.submission.submit_description ){
+ vg_info( "Setting description\n" );
+ SteamAPI_ISteamUGC_SetItemDescription( hSteamUGC, handle,
+ workshop_form.submission.description);
+ }
+
+ if( workshop_form.submission.submit_file_and_image ){
+ vg_info( "Setting preview image\n" );
+ SteamAPI_ISteamUGC_SetItemPreview( hSteamUGC,
+ handle, info->abs_preview_image );
+
+ vg_info( "Setting item content\n" );
+ SteamAPI_ISteamUGC_SetItemContent( hSteamUGC, handle,
+ info->abs_content_folder );
+ }
+
vg_info( "Setting visibility\n" );
- SteamAPI_ISteamUGC_SetItemVisibility( hSteamUGC, handle,
- k_ERemoteStoragePublishedFileVisibilityPublic );
+ SteamAPI_ISteamUGC_SetItemVisibility( hSteamUGC, handle,
+ workshop_form.submission.visibility );
vg_info( "Submitting updates\n" );
vg_steam_async_call *call = vg_alloc_async_steam_api_call();
call->id = SteamAPI_ISteamUGC_SubmitItemUpdate( hSteamUGC, handle, "" );
}
-VG_STATIC void workshop_package_thread( void *data )
+/*
+ * Async thread for rearranging files into a reasonable workshop folder
+ */
+struct workshop_package_thread_args{
+ PublishedFileId_t file_id;
+};
+VG_STATIC void _workshop_package_thread( void *_args )
{
+ struct workshop_package_thread_args *args = _args;
+ PublishedFileId_t file_id = args->file_id;
+
vg_info( "Packaging workshop content folder\n" );
vg_async_item *call = vg_async_alloc( sizeof(struct workshop_package_info) );
struct workshop_package_info *info = call->payload;
+ info->publishing_file_id = file_id;
+ info->failure_reason = "Unknown failure reason";
/* build content folder path */
snprintf( info->abs_content_folder, 1024, "%smodels/boards/workshop/%lu",
- vg.base_path, (u64)workshop_form.publishing_file_id );
+ vg.base_path, file_id );
/* build content file path */
- snprintf( info->abs_content_file, 1024, "%s/%s",
- info->abs_content_folder,
- vg_path_filename( workshop_form.model_path ) );
+ snprintf( info->abs_content_file, 1024, "%s/%s", info->abs_content_folder,
+ "board.mdl" );
/* build workshop preview file path */
snprintf( info->abs_preview_image, 1024, "%sworkshop_preview.jpg",
/* arange files */
if( !vg_mkdir( info->abs_content_folder ) ){
info->success = 0;
+ info->failure_reason = "Could not make directory.";
vg_async_dispatch( call, workshop_form_async_package_complete );
return;
}
if( !vg_file_copy( workshop_form.model_path, info->abs_content_file,
vg_mem.scratch ) ){
info->success = 0;
+ info->failure_reason = "Copy file failed.";
vg_async_dispatch( call, workshop_form_async_package_complete );
return;
}
vg_async_dispatch( call, workshop_form_async_package_complete );
}
+/*
+ * Begins the packaging thread using file_id. Thread definition above.
+ */
+VG_STATIC void workshop_package_submission( PublishedFileId_t file_id )
+{
+ vg_linear_clear( vg_mem.scratch );
+ struct workshop_package_thread_args *args =
+ vg_linear_alloc( vg_mem.scratch,
+ sizeof(struct workshop_package_thread_args));
+
+ args->file_id = file_id;
+ vg_loader_start( _workshop_package_thread, args );
+}
+
+/*
+ * Steam API call result for when we've created a new item on their network, or
+ * not, if it has failed
+ */
VG_STATIC void on_workshop_createitem( void *data, void *user )
{
CreateItemResult_t *result = data;
vg_info( "Created workshop file with id: %lu\n",
result->m_nPublishedFileId );
-
if( result->m_bUserNeedsToAcceptWorkshopLegalAgreement ){
vg_warn( "Workshop agreement currently not accepted\n" );
}
- workshop_form.publishing_file_id = result->m_nPublishedFileId;
- vg_loader_start( workshop_package_thread, NULL );
+ workshop_package_submission( result->m_nPublishedFileId );
}
else{
- const char *error = NULL;
- switch( result->m_eResult ){
- case k_EResultInsufficientPrivilege:
- error = "Your account currently is restricted from uploading content "
- "due to a hub ban, account lock, or community ban. You need to "
- "contact Steam Support to resolve the issue.\n";
- break;
- case k_EResultBanned:
- error = "You do not have permission to upload content to this hub "
- "because you have an active VAC or Game ban.";
- break;
- case k_EResultTimeout:
- error = "Timeout: The operation took longer than expected, please try "
- "again.";
- break;
- case k_EResultNotLoggedOn:
- error = "You are currently not logged into Steam.";
- break;
- case k_EResultServiceUnavailable:
- error = "The workshop server is having issues, please try again.";
- break;
- case k_EResultInvalidParam:
- error = "One of the submission fields contains something not being "
- "accepted by that field.";
- break;
- case k_EResultAccessDenied:
- error = "There was a problem trying to save the title and description. "
- "Access was denied.";
- break;
- case k_EResultLimitExceeded:
- error = "You have exceeded your Steam Cloud quota. If you wish to "
- "upload this file, you must remove some published items.";
- break;
- case k_EResultFileNotFound:
- error = "The uploaded file could not be found.";
- break;
- case k_EResultDuplicateRequest:
- error = "The file was already successfully uploaded.";
- break;
- case k_EResultDuplicateName:
- error = "You already have a Steam Workshop item with that name.";
- break;
- case k_EResultServiceReadOnly:
- error = "Due to a recent password or email change, you are not allowed "
- "to upload new content. Usually this restriction will expire in"
- " 5 days, but can last up to 30 days if the account has been "
- "inactive recently.";
- break;
- default: workshop_form.failure_reason = "Unknown failure";
- }
+ const char *errstr = workshop_EResult_user_string( result->m_eResult );
- vg_error( "ISteamUGC_CreateItem() failed(%d): '%s' \n",
- result->m_eResult, error );
- workshop_form.failure_reason = error;
- workshop_form.status = k_workshop_form_failed;
+ if( errstr ){
+ vg_error( "ISteamUGC_CreateItem() failed(%d): '%s' \n",
+ result->m_eResult, errstr );
+ }
+
+ workshop_form.page = k_workshop_form_closing_bad;
+ workshop_form.failure_or_success_string = errstr;
+ workshop_end_op();
}
}
-VG_STATIC void workshop_form_async_submit_complete( void *payload, u32 size )
+/*
+ * Starts the workshop upload process through Steam API
+ */
+VG_STATIC void workshop_form_async_submit_begin( void *payload, u32 size )
{
- vg_steam_async_call *call = vg_alloc_async_steam_api_call();
- call->userdata = NULL;
- call->p_handler = on_workshop_createitem;
-
- ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- call->id = SteamAPI_ISteamUGC_CreateItem( hSteamUGC, SKATERIFT_APPID,
+ /* use existing file */
+ if( workshop_form.submission.file_id ){
+ workshop_package_submission( workshop_form.submission.file_id );
+ }
+ else{
+ vg_steam_async_call *call = vg_alloc_async_steam_api_call();
+ call->userdata = NULL;
+ call->p_handler = on_workshop_createitem;
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ call->id = SteamAPI_ISteamUGC_CreateItem( hSteamUGC, SKATERIFT_APPID,
k_EWorkshopFileTypeCommunity );
+ }
}
-VG_STATIC void workshop_form_sync_download_image( void *payload, u32 size )
+/*
+ * Downloads the framebuffer into scratch memory
+ */
+VG_STATIC void workshop_form_async_download_image( void *payload, u32 size )
{
int w, h;
render_fb_get_current_res( gpipeline.fb_workshop_preview, &w, &h );
workshop_form.img_h = h;
}
-VG_STATIC void workshop_form_submit_thread( void *data )
+/*
+ * Thread which kicks off the upload process
+ */
+VG_STATIC void _workshop_form_submit_thread( void *data )
{
- vg_async_call( workshop_form_sync_download_image, NULL, 0 );
+ vg_async_call( workshop_form_async_download_image, NULL, 0 );
vg_async_stall();
int w = workshop_form.img_w,
stbi_write_jpg( "workshop_preview.jpg", w,h, 3,
workshop_form.img_buffer, 90 );
- vg_async_call( workshop_form_async_submit_complete, NULL, 0 );
+ vg_async_call( workshop_form_async_submit_begin, NULL, 0 );
}
-VG_STATIC void workshop_form_async_complete( void *payload, u32 size )
+/*
+ * Entry point for the publishing submission operation
+ */
+VG_STATIC void workshop_op_submit(void)
+{
+ /* TODO: Show these errors to the user */
+
+ if( workshop_form.submission.submit_title ){
+ if( !workshop_form.submission.title[0] ){
+ vg_error( "Cannot submit because a title is required\n" );
+ return;
+ }
+ }
+
+ if( workshop_form.submission.submit_description ){
+ if( !workshop_form.submission.description[0] ){
+ vg_error( "Cannot submit because a description is required\n" );
+ return;
+ }
+ }
+
+ if( workshop_form.submission.submit_file_and_image ){
+ if( workshop_form.file_intent == k_workshop_form_file_intent_none ){
+ vg_error( "Cannot submit because the file is empty or unspecified\n" );
+ return;
+ }
+ }
+
+ workshop_begin_op( k_workshop_form_op_publishing_update );
+
+ player_board_unload( &workshop_form.board_model );
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+
+ vg_loader_start( _workshop_form_submit_thread, NULL );
+}
+
+/*
+ * op: k_workshop_form_op_loading_model
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * Reciever for completion of the model file load
+ */
+VG_STATIC void workshop_form_loadmodel_async_complete( void *payload, u32 size )
{
v2_zero( workshop_form.view_angles );
v3_zero( workshop_form.view_offset );
workshop_form.view_dist = 1.0f;
- workshop_form.status = k_workshop_form_editing;
workshop_form.view_changed = 1;
+ workshop_form.file_intent = k_workshop_form_file_intent_new;
+
+ vg_success( "workshop async load complete\n" );
+ workshop_end_op();
}
-VG_STATIC void workshop_form_load_thread( void *data )
+/*
+ * Thread which loads the model from the disk
+ */
+VG_STATIC void _workshop_form_load_thread( void *data )
{
- vg_info( "workshop_form_load_thread()\n" );
player_board_load( &workshop_form.board_model, workshop_form.model_path );
- vg_success( "loaded\n" );
+ vg_async_call( workshop_form_loadmodel_async_complete, NULL, 0 );
+}
- vg_async_call( workshop_form_async_complete, NULL, 0 );
+/*
+ * Entry point for load model operation
+ */
+VG_STATIC void workshop_op_load_model(void)
+{
+ workshop_begin_op( k_workshop_form_op_loading_model );
+ vg_loader_start( _workshop_form_load_thread, NULL );
}
-VG_STATIC void workshop_start_submission( const char *path )
+/*
+ * op: k_workshop_form_op_downloading_submission
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * The image has been decoded and is ready to slap into the framebuffer
+ */
+VG_STATIC void workshop_form_async_imageload( void *data, u32 len )
{
- if( workshop_form.status != k_workshop_form_hidden ){
- vg_error( "Workshop form is already open\n" );
- return;
+ if( data ){
+ struct framebuffer_attachment *a =
+ &gpipeline.fb_workshop_preview->attachments[0];
+
+ glBindTexture( GL_TEXTURE_2D, a->id );
+ glTexSubImage2D( GL_TEXTURE_2D, 0,0,0,
+ WORKSHOP_PREVIEW_WIDTH, WORKSHOP_PREVIEW_HEIGHT,
+ a->format, a->type, data );
+ stbi_image_free( data );
+ vg_success( "Loaded workshop preview image\n" );
}
+
+ workshop_end_op();
+}
- workshop_form.view_world = get_active_world();
+struct workshop_loadpreview_info {
+ char abs_preview_image[ 1024 ];
+};
- if( mdl_arrcount( &workshop_form.view_world->ent_swspreview ) ){
- workshop_form.ptr_ent =
- mdl_arritm( &workshop_form.view_world->ent_swspreview, 0 );
+/*
+ * Load the image located at ./workshop_preview.jpg into our framebuffer
+ */
+VG_STATIC void _workshop_load_preview_thread( void *data )
+{
+ struct workshop_loadpreview_info *info = data;
- vg_strncpy( path, workshop_form.model_path, 128,
- k_strncpy_always_add_null );
- workshop_form.description[0] = '\0';
- workshop_form.title[0] = '\0';
+ stbi_set_flip_vertically_on_load(1);
+ int x, y, nc;
+ u8 *rgb = stbi_load( info->abs_preview_image, &x, &y, &nc, 3 );
- vg_loader_start( workshop_form_load_thread, NULL );
+ if( rgb ){
+ if( (x == WORKSHOP_PREVIEW_WIDTH) && (y == WORKSHOP_PREVIEW_HEIGHT) ){
+ vg_async_call( workshop_form_async_imageload, rgb, x*y*3 );
+ }
+ else{
+ vg_error( "Resolution does not match framebuffer, so we can't"
+ " show it\n" );
+ stbi_image_free( rgb );
+ vg_async_call( workshop_form_async_imageload, NULL, 0 );
+ }
}
else{
- vg_error( "There is no ent_swspreview in the level. "
- "Cannot publish here\n" );
+ vg_error( "Failed to load workshop_preview.jpg: '%s'\n",
+ stbi_failure_reason() );
+ vg_async_call( workshop_form_async_imageload, NULL, 0 );
}
}
-VG_STATIC int workshop_submit_command( int argc, const char *argv[] )
+/*
+ * Reciever for the preview download result
+ */
+VG_STATIC void on_workshop_download_ugcpreview( void *data, void *user )
{
- if( argc == 1 ){
- workshop_start_submission( argv[0] );
+ struct workshop_loadpreview_info *info = user;
+ RemoteStorageDownloadUGCResult_t *result = data;
+
+ if( result->m_eResult == k_EResultOK ){
+ ISteamRemoteStorage *hSteamRemoteStorage = SteamAPI_SteamRemoteStorage();
+ vg_loader_start( _workshop_load_preview_thread, info );
}
else{
- vg_error( "usage: workshop_submit <path>\n" );
+ vg_error( "Error while donwloading UGC preview( %d )\n",
+ result->m_eResult );
+ workshop_end_op();
}
- return 0;
}
-VG_STATIC void workshop_init(void)
+/*
+ * Entry point to view operation
+ */
+VG_STATIC void workshop_op_download_and_view_submission( int result_index )
{
- vg_console_reg_cmd( "workshop_submit", workshop_submit_command, NULL );
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ ISteamRemoteStorage *hSteamRemoteStorage = SteamAPI_SteamRemoteStorage();
+ SteamUGCDetails_t details;
+ if( SteamAPI_ISteamUGC_GetQueryUGCResult( hSteamUGC,
+ workshop_form.ugc_query.handle,
+ result_index,
+ &details ) )
+ {
+ workshop_begin_op( k_workshop_form_op_downloading_submission );
+ workshop_reset_submission_data();
+
+ vg_strncpy( details.m_rgchDescription,
+ workshop_form.submission.description,
+ vg_list_size( workshop_form.submission.description ),
+ k_strncpy_always_add_null );
+
+ vg_strncpy( details.m_rgchTitle,
+ workshop_form.submission.title,
+ vg_list_size( workshop_form.submission.title ),
+ k_strncpy_always_add_null );
+
+ snprintf( workshop_form.model_path,
+ vg_list_size( workshop_form.model_path ),
+ "Steam Cloud (%lu)", details.m_nPublishedFileId );
+
+ workshop_form.submission.file_id = details.m_nPublishedFileId;
+ workshop_form.file_intent = k_workshop_form_file_intent_keep_old;
+ workshop_form.page = k_workshop_form_edit;
+
+ if( details.m_hPreviewFile == 0 ){
+ vg_error( "m_hPreviewFile is 0\n" );
+ workshop_end_op();
+ }
+ else{
+ /* Now need to begin downloading the image so we can display it */
+ vg_steam_async_call *call = vg_alloc_async_steam_api_call();
+
+ struct workshop_loadpreview_info *info =
+ vg_linear_alloc( vg_mem.scratch,
+ sizeof( struct workshop_loadpreview_info ) );
+
+ snprintf( info->abs_preview_image, 1024,
+ "%smodels/boards/workshop/%lu.jpg",
+ vg.base_path, details.m_hPreviewFile );
+
+ call->p_handler = on_workshop_download_ugcpreview;
+ call->userdata = info;
+ call->id = SteamAPI_ISteamRemoteStorage_UGCDownloadToLocation(
+ hSteamRemoteStorage,
+ details.m_hPreviewFile, info->abs_preview_image, 1 );
+
+ vg_info( "preview file id: %lu\n", details.m_hPreviewFile );
+ }
+ }
+ else{
+ vg_error( "GetQueryUGCResult: Index out of range\n" );
+ workshop_end_op();
+ }
}
-VG_STATIC void workshop_form_gui(void)
+/*
+ * Regular stuff
+ * -----------------------------------------------------------------------------
+ */
+
+/*
+ * View a page of results on the sidebar
+ */
+VG_STATIC void workshop_view_page( int req )
{
- if( workshop_form.status == k_workshop_form_hidden ) return;
+ if( workshop_form.ugc_query.result != k_EResultOK ){
+ vg_error( "Tried to change page without complete data\n" );
+ return;
+ }
- ui_rect null;
- ui_rect screen = { 0, 0, vg.window_x, vg.window_y };
- ui_rect window = { 0, 0, 1000, 700 };
- ui_rect_center( screen, window );
- vg_ui.wants_mouse = 1;
+ int page = VG_MAX(VG_MIN(req, workshop_form.view_published_page_count-1),0),
+ start = page * WORKSHOP_VIEW_PER_PAGE,
+ end = VG_MIN( (page+1) * WORKSHOP_VIEW_PER_PAGE,
+ workshop_form.ugc_query.returned_item_count ),
+ count = end-start;
- ui_fill( window, ui_colour( k_ui_bg+1 ) );
- ui_outline( window, 1, ui_colour( k_ui_bg+7 ) );
+ vg_info( "View page %d\n", page );
- ui_rect title, panel;
- ui_split_px( window, k_ui_axis_h, 28, 0, title, panel );
- ui_fill( title, ui_colour( k_ui_bg+7 ) );
- ui_text( title, "Workshop tool", 1, k_ui_align_middle_center,
- ui_colourcont(k_ui_bg+7) );
+ workshop_form.view_published_page_id = page;
+ workshop_form.published_files_list_length = count;
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
- if( workshop_form.status == k_workshop_form_submitting ){
- ui_text( panel, "Submitting... status: ...\n", 1,
- k_ui_align_middle_center, 0 );
- return;
+ for( int i=0; i<count; i ++ ){
+ struct published_file *pfile = &workshop_form.published_files_list[i];
+
+ SteamUGCDetails_t details;
+ if( SteamAPI_ISteamUGC_GetQueryUGCResult( hSteamUGC,
+ workshop_form.ugc_query.handle,
+ start+i,
+ &details ) )
+ {
+ if( details.m_eResult != k_EResultOK ){
+ snprintf( pfile->title, 80, "Error (%d)", details.m_eResult );
+ }
+ else{
+ vg_strncpy( details.m_rgchTitle, pfile->title, 80,
+ k_strncpy_always_add_null );
+ }
+
+ pfile->result = details.m_eResult;
+ pfile->result_index = start+i;
+ }
+ else{
+ pfile->result = k_EResultValueOutOfRange;
+ pfile->result_index = -1;
+ snprintf( pfile->title, 80, "Error (invalid index)" );
+ }
}
+}
- /* re draw board preview if need to */
- if( workshop_form.view_changed ){
- render_fb_bind( gpipeline.fb_workshop_preview, 0 );
-
- glClearColor( 0.0f, 0.0f, 0.3f, 1.0f );
- glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
- glEnable( GL_DEPTH_TEST );
- glDisable( GL_BLEND );
-
- ent_swspreview *swsprev = workshop_form.ptr_ent;
- world_instance *world = workshop_form.view_world;
-
- ent_camera *ref = mdl_arritm( &world->ent_camera,
- mdl_entity_id_id(swsprev->id_camera) );
- ent_marker *display = mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(swsprev->id_display) ),
- *display1= mdl_arritm( &world->ent_marker,
- mdl_entity_id_id(swsprev->id_display1) );
-
- v3f baseco;
- v3_add( display->transform.co, display1->transform.co, baseco );
- v3_muls( baseco, 0.5f, baseco );
-
- camera cam;
- v3f basevector;
- v3_sub( display->transform.co, ref->transform.co, basevector );
- float dist = v3_length( basevector );
-
- v3f baseangles;
- player_vector_angles( baseangles, basevector, 1.0f, 0.0f );
-
- v2_add( workshop_form.view_angles, baseangles, cam.angles );
- cam.angles[2] = 0.0f;
-
- float sX = sinf( cam.angles[0] ),
- cX = cosf( cam.angles[0] ),
- sY = sinf( cam.angles[1] ),
- cY = cosf( cam.angles[1] );
-
- v3f offset = { -sX * cY, sY, cX * cY };
-
- v3_muladds( display->transform.co, offset,
- dist*workshop_form.view_dist, cam.pos );
-
- cam.pos[0] += -sX*workshop_form.view_offset[2];
- cam.pos[2] += cX*workshop_form.view_offset[2];
- cam.pos[0] += cX*workshop_form.view_offset[0];
- cam.pos[2] += sX*workshop_form.view_offset[0];
- cam.pos[1] += workshop_form.view_offset[1];
-
- cam.nearz = 0.01f;
- cam.farz = 100.0f;
- cam.fov = ref->fov;
-
- camera_update_transform( &cam );
- camera_update_view( &cam );
- camera_update_projection( &cam );
- camera_finalize( &cam );
-
- m4x3f mmdl, mmdl1;
- mdl_transform_m4x3( &display->transform, mmdl );
- mdl_transform_m4x3( &display1->transform, mmdl1 );
-
- /* force update this for nice shadows. its usually set in the world
- * pre-render step, but that includes timer stuff
- */
- struct player_board *board = &workshop_form.board_model;
- struct ub_world_lighting *ubo = &world->ub_lighting;
- v3f vp0, vp1;
- v3_copy((v3f){0.0f,0.1f, board->truck_positions[0][2]}, vp0 );
- v3_copy((v3f){0.0f,0.1f, board->truck_positions[1][2]}, vp1 );
- m4x3_mulv( mmdl1, vp0, ubo->g_board_0 );
- m4x3_mulv( mmdl1, vp1, ubo->g_board_1 );
- glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
- glBufferSubData( GL_UNIFORM_BUFFER, 0,
- sizeof(struct ub_world_lighting), &world->ub_lighting );
-
- render_world( world, &cam, 1 );
- render_board( &cam, world, board, mmdl, k_board_shader_entity );
- render_board( &cam, world, board, mmdl1, k_board_shader_entity );
-
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- glViewport( 0,0, vg.window_x, vg.window_y );
+/*
+ * Steam API result for when we recieve submitted UGC information about the user
+ */
+VG_STATIC void on_workshop_UGCQueryComplete( void *data, void *userdata )
+{
+ SteamUGCQueryCompleted_t *query = data;
+ workshop_form.ugc_query.result = query->m_eResult;
- workshop_form.view_changed = 0;
+ if( query->m_eResult == k_EResultOK ){
+ if( query->m_unTotalMatchingResults > 50 ){
+ vg_warn( "You have %d items submitted, "
+ "we can only view the last 50\n" );
+ }
+
+ workshop_form.ugc_query.all_item_count = query->m_unTotalMatchingResults;
+ workshop_form.ugc_query.returned_item_count =
+ query->m_unNumResultsReturned;
+
+ workshop_form.ugc_query.handle = query->m_handle;
+ workshop_form.view_published_page_count =
+ (query->m_unNumResultsReturned+WORKSHOP_VIEW_PER_PAGE-1)/
+ WORKSHOP_VIEW_PER_PAGE;
+ workshop_form.view_published_page_id = 0;
+ workshop_form.published_files_list_length = 0;
+
+ workshop_view_page( 0 );
}
+ else{
+ vg_error( "Steam UGCQuery failed (%d)\n", query->m_eResult );
+ workshop_form.view_published_page_count = 0;
+ workshop_form.view_published_page_id = 0;
+ workshop_form.published_files_list_length = 0;
- struct workshop_form *form = &workshop_form;
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ SteamAPI_ISteamUGC_ReleaseQueryUGCRequest( hSteamUGC, query->m_handle );
+ }
+}
- ui_rect sidebar, content;
- ui_split_ratio( panel, k_ui_axis_v, 0.3f, 1, sidebar, content );
+/*
+ * Console command to open the workshop publisher
+ */
+VG_STATIC int workshop_submit_command( int argc, const char *argv[] )
+{
+ if( !steam_ready ){
+ vg_error( "Steam API is not ready or loaded\n" );
+ return 0;
+ }
- /* entries panel */
- ui_fill( sidebar, ui_colour( k_ui_bg+2 ) );
+ workshop_form.page = k_workshop_form_open;
+ workshop_form.view_published_page_count = 0;
+ workshop_form.view_published_page_id = 0;
+ workshop_form.published_files_list_length = 0;
+ workshop_form.ugc_query.result = k_EResultNone;
- ui_split_px( sidebar, k_ui_axis_h, 28, 0, title, sidebar );
- ui_text( title, "Your submissions", 1, k_ui_align_middle_center, 0 );
+ vg_steam_async_call *call = vg_alloc_async_steam_api_call();
- ui_rect controls;
- ui_split_px( sidebar, k_ui_axis_h, 28, 0, controls, sidebar );
- ui_fill( controls, ui_colour( k_ui_bg+1 ) );
- ui_outline( controls, -1, ui_colour( k_ui_bg+4 ) );
+ ISteamUser *hSteamUser = SteamAPI_SteamUser();
+ CSteamID steamid;
+ steamid.m_unAll64Bits = SteamAPI_ISteamUser_GetSteamID( hSteamUser );
- ui_rect info;
- ui_split_ratio( controls, k_ui_axis_v, 0.25f, 0, info, controls );
- ui_text( info, "page 0/2", 1, k_ui_align_middle_center, 0 );
+ ISteamUGC *hSteamUGC = SteamAPI_SteamUGC();
+ call->p_handler = on_workshop_UGCQueryComplete;
+ call->userdata = NULL;
+ UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryUserUGCRequest
+ (
+ hSteamUGC,
+ steamid.m_comp.m_unAccountID,
+ k_EUserUGCList_Published,
+ k_EUGCMatchingUGCType_Items,
+ k_EUserUGCListSortOrder_CreationOrderDesc,
+ SKATERIFT_APPID, SKATERIFT_APPID,
+ 1 );
+ call->id = SteamAPI_ISteamUGC_SendQueryUGCRequest( hSteamUGC, handle );
+ return 0;
+}
- ui_rect btn_left, btn_right;
- ui_split_ratio( controls, k_ui_axis_v, 0.5f, 2, btn_left, btn_right );
-
- if( ui_button_text( btn_left, "prev", 1 ) ){
- vg_info( "left\n" );
+VG_STATIC void workshop_init(void)
+{
+ vg_console_reg_cmd( "workshop_submit", workshop_submit_command, NULL );
+}
+
+VG_STATIC void workshop_find_preview_entity(void)
+{
+ workshop_form.view_world = get_active_world();
+
+ if( mdl_arrcount( &workshop_form.view_world->ent_swspreview ) ){
+ workshop_form.ptr_ent =
+ mdl_arritm( &workshop_form.view_world->ent_swspreview, 0 );
+ workshop_form.page = k_workshop_form_edit;
+ }
+ else{
+ vg_error( "There is no ent_swspreview in the level. "
+ "Cannot publish here\n" );
}
+}
- if( ui_button_text( btn_right, "next", 1 ) ){
- vg_info( "right\n" );
+/*
+ * Redraw the model file into the workshop framebuffer
+ */
+VG_STATIC void workshop_render_preview(void)
+{
+ if( !workshop_form.ptr_ent ){
+ return;
}
- /* content page */
- ui_rect_pad( content, 8 );
+ render_fb_bind( gpipeline.fb_workshop_preview, 0 );
+
+ glClearColor( 0.0f, 0.0f, 0.3f, 1.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+ glEnable( GL_DEPTH_TEST );
+ glDisable( GL_BLEND );
+
+ ent_swspreview *swsprev = workshop_form.ptr_ent;
+ world_instance *world = workshop_form.view_world;
+
+ ent_camera *ref = mdl_arritm( &world->ent_camera,
+ mdl_entity_id_id(swsprev->id_camera) );
+ ent_marker *display = mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(swsprev->id_display) ),
+ *display1= mdl_arritm( &world->ent_marker,
+ mdl_entity_id_id(swsprev->id_display1) );
+
+ v3f baseco;
+ v3_add( display->transform.co, display1->transform.co, baseco );
+ v3_muls( baseco, 0.5f, baseco );
+
+ camera cam;
+ v3f basevector;
+ v3_sub( display->transform.co, ref->transform.co, basevector );
+ float dist = v3_length( basevector );
+
+ v3f baseangles;
+ player_vector_angles( baseangles, basevector, 1.0f, 0.0f );
+
+ v2_add( workshop_form.view_angles, baseangles, cam.angles );
+ cam.angles[2] = 0.0f;
+
+ float sX = sinf( cam.angles[0] ),
+ cX = cosf( cam.angles[0] ),
+ sY = sinf( cam.angles[1] ),
+ cY = cosf( cam.angles[1] );
+
+ v3f offset = { -sX * cY, sY, cX * cY };
+
+ v3_muladds( display->transform.co, offset,
+ dist*workshop_form.view_dist, cam.pos );
+
+ cam.pos[0] += -sX*workshop_form.view_offset[2];
+ cam.pos[2] += cX*workshop_form.view_offset[2];
+ cam.pos[0] += cX*workshop_form.view_offset[0];
+ cam.pos[2] += sX*workshop_form.view_offset[0];
+ cam.pos[1] += workshop_form.view_offset[1];
+
+ cam.nearz = 0.01f;
+ cam.farz = 100.0f;
+ cam.fov = ref->fov;
+ camera_update_transform( &cam );
+ camera_update_view( &cam );
+ camera_update_projection( &cam );
+ camera_finalize( &cam );
+
+ m4x3f mmdl, mmdl1;
+ mdl_transform_m4x3( &display->transform, mmdl );
+ mdl_transform_m4x3( &display1->transform, mmdl1 );
+
+ /* force update this for nice shadows. its usually set in the world
+ * pre-render step, but that includes timer stuff
+ */
+ struct player_board *board = &workshop_form.board_model;
+ struct ub_world_lighting *ubo = &world->ub_lighting;
+ v3f vp0, vp1;
+ v3_copy((v3f){0.0f,0.1f, board->truck_positions[0][2]}, vp0 );
+ v3_copy((v3f){0.0f,0.1f, board->truck_positions[1][2]}, vp1 );
+ m4x3_mulv( mmdl1, vp0, ubo->g_board_0 );
+ m4x3_mulv( mmdl1, vp1, ubo->g_board_1 );
+ glBindBuffer( GL_UNIFORM_BUFFER, world->ubo_lighting );
+ glBufferSubData( GL_UNIFORM_BUFFER, 0,
+ sizeof(struct ub_world_lighting), &world->ub_lighting );
+
+ render_world( world, &cam, 1 );
+ render_board( &cam, world, board, mmdl, k_board_shader_entity );
+ render_board( &cam, world, board, mmdl1, k_board_shader_entity );
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+ glViewport( 0,0, vg.window_x, vg.window_y );
+}
+
+/*
+ * ImGUI section for workshop form
+ * -----------------------------------------------------------------------------
+ */
+
+VG_STATIC void workshop_changed_model_path( char *buf, u32 len ){
+ workshop_form.submission.submit_file_and_image = 1;
+}
+
+VG_STATIC void workshop_changed_title( char *buf, u32 len ){
+ workshop_form.submission.submit_title = 1;
+}
+
+VG_STATIC void workshop_changed_description( char *buf, u32 len ){
+ workshop_form.submission.submit_description = 1;
+}
+
+VG_STATIC void workshop_form_gui_edit_page( ui_rect content )
+{
ui_rect image_plane;
ui_split_px( content, k_ui_axis_h, 300, 0, image_plane, content );
ui_fill( image_plane, ui_colour( k_ui_bg+0 ) );
ui_rect img_box;
ui_fit_item( image_plane, (ui_px[2]){ 3, 2 }, img_box );
- ui_image( img_box, gpipeline.fb_workshop_preview->attachments[0].id );
- {
+ if( workshop_form.file_intent == k_workshop_form_file_intent_keep_old ){
+ ui_image( img_box, gpipeline.fb_workshop_preview->attachments[0].id );
+ }
+ else if( workshop_form.file_intent == k_workshop_form_file_intent_new ){
+ ui_image( img_box, gpipeline.fb_workshop_preview->attachments[0].id );
int hover = ui_inside_rect( img_box, vg_ui.mouse ),
target = ui_inside_rect( img_box, vg_ui.mouse_click );
}
}
}
+ else{
+ ui_text( img_box, "No image", 1, k_ui_align_middle_center,
+ ui_colour( k_ui_orange ) );
+ }
/* file path */
- ui_rect file_label;
+ ui_rect null, file_entry, file_button;
ui_split_px( content, k_ui_axis_h, 8, 0, null, content );
- ui_split_px( content, k_ui_axis_h, 28, 0, file_label, content );
- ui_text( file_label, form->model_path, 1, k_ui_align_middle_center,
- ui_colour( k_ui_fg+4 ) );
+ ui_split_px( content, k_ui_axis_h, 28, 0, file_entry, content );
+ ui_split_px( file_entry, k_ui_axis_v, file_entry[2]-128, 0,
+ file_entry, file_button );
+
+ if( workshop_form.file_intent != k_workshop_form_file_intent_none ){
+ ui_text( file_entry, workshop_form.model_path, 1, k_ui_align_middle_left,
+ ui_colour( k_ui_fg+4 ) );
+
+ if( ui_button_text( file_button, "Remove", 1 ) ){
+ player_board_unload( &workshop_form.board_model );
+ workshop_form.file_intent = k_workshop_form_file_intent_none;
+ workshop_form.model_path[0] = '\0';
+ }
+ }
+ else{
+ struct ui_textbox_callbacks callbacks = {
+ .change = workshop_changed_model_path
+ };
+
+ ui_textbox( file_entry, workshop_form.model_path,
+ vg_list_size(workshop_form.model_path), 0, &callbacks );
+
+ if( ui_button_text( file_button, "Load", 1 ) ){
+ workshop_find_preview_entity();
+ workshop_op_load_model();
+ }
+ }
- /* title box */
ui_rect title_entry, label;
ui_split_px( content, k_ui_axis_h, 8, 0, null, content );
ui_split_px( content, k_ui_axis_h, 28, 0, title_entry, content );
ui_split_px( title_entry, k_ui_axis_v,
ui_text_line_width(str_title)+8, 0, label, title_entry );
- ui_text( label, str_title, 1, k_ui_align_middle_left, 0 );
- ui_textbox( title_entry, form->title, vg_list_size(form->title), 0 );
+ /* title box */
+ {
+ struct ui_textbox_callbacks callbacks = {
+ .change = workshop_changed_title
+ };
+ ui_text( label, str_title, 1, k_ui_align_middle_left, 0 );
+ ui_textbox( title_entry, workshop_form.submission.title,
+ vg_list_size(workshop_form.submission.title), 0, &callbacks );
+ }
/* description box */
- ui_rect desc_entry;
- ui_split_px( content, k_ui_axis_h, 8, 0, null, content );
- ui_split_px( content, k_ui_axis_h, 28*4, 0, desc_entry, content );
- ui_split_px( desc_entry, k_ui_axis_v,
- ui_text_line_width(str_desc)+8, 0, label, desc_entry );
- ui_text( label, str_desc, 1, k_ui_align_middle_left, 0 );
- ui_textbox( desc_entry, form->description,
- vg_list_size(form->description),
- UI_TEXTBOX_MULTILINE|UI_TEXTBOX_WRAP );
+ {
+ struct ui_textbox_callbacks callbacks = {
+ .change = workshop_changed_description
+ };
+ ui_rect desc_entry;
+ ui_split_px( content, k_ui_axis_h, 8, 0, null, content );
+ ui_split_px( content, k_ui_axis_h, 28, 0, label, content );
+ ui_split_px( content, k_ui_axis_h, 28*4, 0, desc_entry, content );
+ ui_text( label, str_desc, 1, k_ui_align_middle_left, 0 );
+ ui_textbox( desc_entry, workshop_form.submission.description,
+ vg_list_size(workshop_form.submission.description),
+ UI_TEXTBOX_MULTILINE|UI_TEXTBOX_WRAP, &callbacks );
+ }
/* submissionable */
ui_rect submission_row;
submission_center[2] = 256;
ui_rect_center( submission_row, submission_center );
+ ui_rect btn_left, btn_right;
ui_split_ratio( submission_center, k_ui_axis_v, 0.5f, 8,
btn_left, btn_right );
if( ui_button_text( btn_left, "Publish", 1 ) ){
- workshop_form.status = k_workshop_form_submitting;
- vg_loader_start( workshop_form_submit_thread, NULL );
+ workshop_op_submit();
}
if( ui_button_text( btn_right, "Cancel", 1 ) ){
vg_info( "left\n" );
}
/* disclaimer */
+ /* TODO!! OPEN THIS LINK */
const char *disclaimer_text =
"By submitting this item, you agree to the workshop terms of service";
ui_rect_pad( btn_right, 2 );
if( ui_button_text( btn_right, "\x91", 2 ) ){
- vg_info( "Open link\n" );
+ ISteamFriends *hSteamFriends = SteamAPI_SteamFriends();
+ SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage( hSteamFriends,
+ "https://steamcommunity.com/sharedfiles/workshoplegalagreement",
+ k_EActivateGameOverlayToWebPageMode_Default );
}
ui_text( label, disclaimer_text, 1, k_ui_align_middle_left,
ui_colour( k_ui_fg+4 ) );
}
+VG_STATIC void workshop_form_gui_sidebar( ui_rect sidebar )
+{
+ /*
+ * sidebar existing entries panel
+ */
+ ui_fill( sidebar, ui_colour( k_ui_bg+2 ) );
+
+ ui_rect title;
+ ui_split_px( sidebar, k_ui_axis_h, 28, 0, title, sidebar );
+ ui_text( title, "Your submissions", 1, k_ui_align_middle_center, 0 );
+
+ ui_rect controls, btn_create_new;
+ ui_split_px( sidebar, k_ui_axis_h, 32, 0, controls, sidebar );
+ ui_split_px( sidebar, k_ui_axis_h, sidebar[3]-32, 0,
+ sidebar, btn_create_new );
+ ui_fill( controls, ui_colour( k_ui_bg+1 ) );
+ ui_outline( controls, -1, ui_colour( k_ui_bg+4 ) );
+
+ char buf[32];
+ strcpy( buf, "page " );
+ int i = 5;
+
+ /* TODO: for what it is, this code is getting a bit.. special */
+ if( workshop_form.view_published_page_id )
+ i += highscore_intl( buf+i, workshop_form.view_published_page_id, 4 );
+ else buf[ i ++ ] = '0';
+ ;
+ buf[ i ++ ] = '/';
+ i += highscore_intl( buf+i, workshop_form.view_published_page_count, 4 );
+ buf[ i ++ ] = '\0';
+
+ ui_rect info;
+ ui_split_ratio( controls, k_ui_axis_v, 0.25f, 0, info, controls );
+ ui_text( info, buf, 1, k_ui_align_middle_center, 0 );
+
+ ui_rect btn_left, btn_right;
+ ui_split_ratio( controls, k_ui_axis_v, 0.5f, 2, btn_left, btn_right );
+
+ if( ui_button_text( btn_left, "newer", 1 ) ){
+ workshop_view_page( workshop_form.view_published_page_id-1 );
+ }
+
+ if( ui_button_text( btn_right, "older", 1 ) ){
+ workshop_view_page( workshop_form.view_published_page_count+1 );
+ }
+
+ if( ui_button_text( btn_create_new, "Create New Item", 1 ) ){
+ workshop_reset_submission_data();
+ workshop_form.submission.submit_title = 1;
+ workshop_form.submission.submit_description = 1;
+ workshop_form.submission.submit_file_and_image = 1;
+ workshop_form.page = k_workshop_form_edit;
+ workshop_find_preview_entity();
+ }
+
+ for( int i=0; i<workshop_form.published_files_list_length; i++ ){
+ ui_rect item;
+ ui_split_px( sidebar, k_ui_axis_h, 28, 0, item, sidebar );
+ ui_rect_pad( item, 4 );
+
+ struct published_file *pfile = &workshop_form.published_files_list[i];
+ if( ui_button_text( item, pfile->title, 1 ) ){
+ if( pfile->result == k_EResultOK ){
+ vg_info( "Select index: %d\n", pfile->result_index );
+ workshop_op_download_and_view_submission( pfile->result_index );
+ }
+ else{
+ vg_warn( "Cannot select that item, result not OK\n" );
+ }
+ }
+ }
+}
+
+VG_STATIC void workshop_form_gui(void)
+{
+ enum workshop_form_page stable_page = workshop_form.page;
+ if( stable_page == k_workshop_form_hidden ) return;
+
+ ui_rect null;
+ ui_rect screen = { 0, 0, vg.window_x, vg.window_y };
+ ui_rect window = { 0, 0, 1000, 700 };
+ ui_rect_center( screen, window );
+ vg_ui.wants_mouse = 1;
+
+ ui_fill( window, ui_colour( k_ui_bg+1 ) );
+ ui_outline( window, 1, ui_colour( k_ui_bg+7 ) );
+
+ ui_rect title, panel;
+ ui_split_px( window, k_ui_axis_h, 28, 0, title, panel );
+ ui_fill( title, ui_colour( k_ui_bg+7 ) );
+ ui_text( title, "Workshop tool", 1, k_ui_align_middle_center,
+ ui_colourcont(k_ui_bg+7) );
+
+ ui_rect quit_button;
+ ui_split_px( title, k_ui_axis_v, title[2]-title[3], 2, title, quit_button );
+ if( workshop_form.operation == k_workshop_form_op_none ){
+ if( ui_button_text( quit_button, "X", 1 ) ){
+ workshop_quit_form();
+ return;
+ }
+ }
+
+ /*
+ * temporary operation blinders, we don't yet have a nice way to show the
+ * user that we're doing something uninterruptable, so the code just
+ * escapes here and we show them a basic string
+ */
+
+ if( workshop_form.operation != k_workshop_form_op_none ){
+ const char *op_string = "The programmer has not bothered to describe "
+ "the current operation that is running.";
+
+ switch(workshop_form.operation){
+ case k_workshop_form_op_loading_model:
+ op_string = "Operation in progress: Loading model file.";
+ break;
+ case k_workshop_form_op_publishing_update:
+ op_string = "Operation in progress: publishing submission update "
+ "to steam.";
+ break;
+ case k_workshop_form_op_downloading_submission:
+ op_string = "Operation in progress: downloading existing submission"
+ " from Steam services.";
+ break;
+ default: break;
+ }
+
+ ui_text( panel, op_string, 1, k_ui_align_middle_center, 0 );
+ return;
+ }
+
+ /* re draw board preview if need to */
+ if( (stable_page == k_workshop_form_edit) &&
+ workshop_form.view_changed &&
+ workshop_form.file_intent == k_workshop_form_file_intent_new )
+ {
+ workshop_render_preview();
+ workshop_form.view_changed = 0;
+ }
+
+ struct workshop_form *form = &workshop_form;
+
+ ui_rect sidebar, content;
+ ui_split_ratio( panel, k_ui_axis_v, 0.3f, 1, sidebar, content );
+
+ /* content page */
+ ui_rect_pad( content, 8 );
+
+ if( stable_page == k_workshop_form_edit ){
+ workshop_form_gui_edit_page( content );
+ }
+ else if( stable_page == k_workshop_form_open ){
+ ui_text( content, "Nothing selected.", 1, k_ui_align_middle_center,
+ ui_colour( k_ui_fg+4 ) );
+ }
+ else if( stable_page >= k_workshop_form_cclosing ){
+ ui_rect submission_row;
+ ui_split_px( content, k_ui_axis_h, content[3]-32-8, 0, content,
+ submission_row );
+
+ u32 colour;
+
+ if( stable_page == k_workshop_form_closing_bad )
+ colour = ui_colour( k_ui_red+k_ui_brighter );
+ else
+ colour = ui_colour( k_ui_green+k_ui_brighter );
+
+ ui_text( content, workshop_form.failure_or_success_string, 1,
+ k_ui_align_middle_center, colour );
+
+ ui_rect submission_center;
+ rect_copy( submission_row, submission_center );
+ submission_center[2] = 128;
+ ui_rect_center( submission_row, submission_center );
+ ui_rect_pad( submission_center, 8 );
+
+ if( ui_button_text( submission_center, "OK", 1 ) ){
+ workshop_form.page = k_workshop_form_open;
+ }
+ }
+
+ workshop_form_gui_sidebar( sidebar );
+}
#endif /* WORKSHOP_C */