leaderboard have picture
[fishladder.git] / fishladder.c
index 1f9db88f07d4fe952231bcea148551cfbe48dfc8..3b2c09f599c903741d874ebb04e1e6f087d45b8a 100644 (file)
@@ -5,6 +5,35 @@
 #include "vg/vg.h"
 #include "fishladder_resources.h"
 
+/*
+       Todo for release:
+               Tutorial levels:
+                       1. Transport
+                       2. Split
+                       3. Merge (and explode)
+                       4. Principle 1 (divide colours)
+                       5. Principle 2 (combine colours)
+                       
+               Trainee levels:
+                       Simple maths                    (x3)
+                       Colour ordering                 (x2)
+                       Routing problems                (x2)
+                       
+               Medium levels:
+                       Reverse order
+               
+               New things to program:
+                       UI text element renderer (SDF)                          DONE(sorta)
+                       Particle system thing for ball collision        
+                       Level descriptions / titles                                     HALF
+                       Row Gridlines for I/O
+                       Play button / Speed controller
+                       
+                       
+       After release:
+               
+*/
+
 const char *level_pack_1[] = { 
        "level0", 
        "level1", 
@@ -326,14 +355,17 @@ struct world
        
        int num_fishes;
        
-       char map_name[128];
-       struct career_level *ptr_career_level;
+       char map_name[64];
+       //struct career_level *ptr_career_level;
+       struct cmp_level *pCmpLevel;
        
        u32 score;
        u32 completed;
        u32 time;
 } world = {};
 
+void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
+
 static void map_free(void)
 {      
        arrfree( world.data );
@@ -373,9 +405,9 @@ static int map_load( const char *str, const char *name )
        // Scan for width
        for(;; world.w ++)
        {
-               if( str[world.w] == ';' )
+               if( c[world.w] == ';' )
                        break;
-               else if( !str[world.w] )
+               else if( !c[world.w] )
                {
                        vg_error( "Unexpected EOF when parsing level\n" );
                        return 0;
@@ -777,7 +809,7 @@ static int console_load_map( int argc, char const *argv[] )
                if( text_source )
                {
                        vg_info( "Loading map: '%s'\n", map_path );
-                       world.ptr_career_level = NULL;
+                       world.pCmpLevel = NULL;
                        
                        if( !map_load( text_source, argv[0] ) )
                        {
@@ -785,17 +817,7 @@ static int console_load_map( int argc, char const *argv[] )
                                return 0;
                        }
                        
-                       free( text_source );
-                       
-                       for( int i = 0; i < vg_list_size( level_pack_1 ); i ++ )
-                       {
-                               if( !strcmp( level_pack_1[i], argv[0] ) )
-                               {
-                                       world.ptr_career_level = career.levels + i;
-                                       break;
-                               }
-                       }
-                       
+                       free( text_source );    
                        return 1;
                }
                else
@@ -845,8 +867,15 @@ static int console_changelevel( int argc, char const *argv[] )
        return 0;
 }
 
+void leaderboard_found( LeaderboardFindResult_t *pCallback );
+void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback );
+
 void vg_start(void)
 {
+       // Steamworks callbacks
+       sw_leaderboard_found = &leaderboard_found;
+       sw_leaderboard_downloaded = &leaderboard_downloaded;
+
        vg_function_push( (struct vg_cmd){
                .name = "_map_write",
                .function = console_save_map
@@ -1005,6 +1034,7 @@ void vg_start(void)
 
 void vg_free(void)
 {
+       sw_free_opengl();
        console_save_map( 0, NULL );
        career_serialize();
 
@@ -1237,7 +1267,7 @@ void vg_update(void)
        world.tile_y = floorf( world.tile_pos[1] );
 
        // Tilemap editing
-       if( !world.simulating )
+       if( !world.simulating && !gui_want_mouse() )
        {
                v2_copy( vg_mouse_ws, drag_to_co );
        
@@ -1727,11 +1757,12 @@ void vg_update(void)
                                }
                                
                                // Copy into career data
-                               if( world.ptr_career_level )
+                               if( world.pCmpLevel )
                                {
-                                       world.ptr_career_level->score = world.score;
-                                       world.ptr_career_level->time = world.time;
-                                       world.ptr_career_level->completed = world.completed;
+                                       if( world.score > world.pCmpLevel->completed_score )
+                                               leaderboard_set_score( world.pCmpLevel, world.score );
+                                       
+                                       world.pCmpLevel->completed_score = world.score;
                                }
                                
                                simulation_stop(); // TODO: Async?
@@ -1936,6 +1967,27 @@ void vg_render(void)
        }
        
        
+       // Level title
+       ui_begin( &ui_global_ctx, 512, 256 );
+       
+       ui_global_ctx.override_colour = 0xff9a8a89;
+       //ui_text( &ui_global_ctx, world.map_title, 6, 0 );
+       ui_global_ctx.override_colour = 0xffffffff;
+       
+       ui_resolve( &ui_global_ctx );
+       
+       m3x3f world_text;
+       m3x3_copy( vg_pv, world_text );
+       m3x3_translate( world_text, (v3f){ 1.55f, 1.9f, 0.0f } );
+       m3x3_rotate( world_text, VG_PIf*0.5f );
+       m3x3_scale( world_text, (v3f){0.01f,-0.01f,0.01f} );
+       
+       ui_draw( &ui_global_ctx, world_text );
+       
+       // Main
+       // =========================================================================================
+       
+       use_mesh( &world.tile );
        SHADER_USE( shader_tile_main );
 
        m2x2f subtransform;
@@ -2190,104 +2242,456 @@ void vg_render(void)
                
        use_mesh( &world.numbers );
        draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
+}
+
+static ui_colourset flcol_list_a = {
+       .main = 0xff877979,
+       .hover = 0xffa09393,
+       .active = 0xffbfb1b0
+};
+static ui_colourset flcol_list_b = {
+       .main = 0xff7c6e6e,
+       .hover = 0xffa09393,
+       .active = 0xffbfb1b0
+};
+
+static ui_colourset flcol_list_complete_a = {
+       .main = 0xff62a064,
+       .hover = 0xff8dc18f,
+       .active = 0xffb2ddb3
+};
+
+static ui_colourset flcol_list_complete_b = {
+       .main = 0xff79b37b,
+       .hover = 0xff8dc18f,
+       .active = 0xffb2ddb3
+};
+
+static ui_colourset flcol_list_locked = {
+       .main = 0xff655959,
+       .hover = 0xff655959,
+       .active = 0xff655959
+};
+
+static struct
+{
+       SteamLeaderboard_t steam_leaderboard;
+       int leaderboard_show;
+       
+       struct leaderboard_player
+       {
+               // Internal
+               u64_steamid id;
+               i32 score;
                
-       // Level selection UI
-       use_mesh( &world.circle );
-       float ratio = ((float)vg_window_x/(float)vg_window_y);
+               // To be displayed
+               char score_text[ 16 ];
+               char player_name[ 48 ];
+               GLuint texture;                 // Not dynamic
+       }
+       leaderboard_players[10];
+       int leaderboard_count;
+       
+       struct
+       {
+               struct cmp_level *level;
+               
+               i32 score;
+               int is_waiting;
+       }
+       upload_request;
        
-       m3x3f ui_view = M3X3_IDENTITY;
-       m3x3_scale( ui_view, (v3f){ 1.0f, ratio, 1.0f } );
-       glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)ui_view );
+       struct cmp_level *level_selected;
+} 
+ui_data;
 
-       // Calculate mouse in UIsp
-       v3f mouse_ui_space = { ((float)vg_mouse[0] / (float)(vg_window_x)) * 2.0f - 1.0f,
-                                                                 (((float)vg_mouse[1] / (float)(vg_window_y)) * 2.0f - 1.0f)*(-1.0f/ratio), 0.0125f };
+void vg_ui(void)
+{
+       // UI memory
+       static int pack_selection = 0;
+       static struct pack_info
+       {
+               struct cmp_level *levels;
+               u32 level_count;
+               const char *name;
+       }
+       pack_infos[] = 
+       {
+               {
+                       .levels = cmp_levels_tutorials,
+                       .level_count = vg_list_size(cmp_levels_tutorials),
+                       .name = "Training"
+               },
+               {
+                       .levels = cmp_levels_basic,
+                       .level_count = vg_list_size(cmp_levels_basic),
+                       .name = "Main"
+               },
+               {
+                       .levels = cmp_levels_grad,
+                       .level_count = vg_list_size(cmp_levels_tutorials),
+                       .name = "Expert"
+               }
+       };
+       
+       // UI Code
+       ui_global_ctx.cursor[0] = 0;
+       ui_global_ctx.cursor[1] = 0;
+       ui_global_ctx.cursor[2] = 256;
 
-       // Get selected level
-       const float selection_scale = 0.05f;
-       int const level_count = vg_list_size( level_pack_1 );
-       int level_select = -1;
+       gui_fill_y();
+       
+       ui_global_ctx.id_base = 4 << 16;
        
-       if( mouse_ui_space[0] <= -0.8f )
+       gui_new_node();
        {
-               float levels_range = (float)level_count*selection_scale*0.6f;
-               float level_offset = ((-mouse_ui_space[1] + levels_range) / levels_range) * 0.5f * (float)level_count;
-               level_select = ceilf( level_offset );
+               gui_capture_mouse( 9999 );
+               gui_fill_rect( ui_global_ctx.cursor, 0xff5577ff );
+               
+               gui_text( "ASSIGNMENTS", 8, 0 );
+               
+               ui_global_ctx.cursor[1] += 30;
+               ui_global_ctx.cursor[3] = 25;
+               
+               gui_new_node();
+               {
+                       ui_rect_pad( ui_global_ctx.cursor, 2 );
+                       ui_global_ctx.cursor[2] = 84;
+                       
+                       for( int i = 0; i < 3; i ++ )
+                       {
+                               if( i == pack_selection )
+                                       gui_override_colours( &flcol_list_locked );
 
-               // Draw selector
-               if( level_select >= 0 && level_select < vg_list_size( level_pack_1 ) )
+                               if( gui_button( 2000 + i ) == k_button_click )
+                                       pack_selection = i;
+                               
+                               ui_global_ctx.cursor[1] += 2;
+                               gui_text( pack_infos[i].name, 4, 0 );
+                               gui_end_right();
+                               
+                               gui_reset_colours();
+                       } 
+               }
+               gui_end_down();
+               
+               ui_global_ctx.cursor[3] = 500;
+               
+               // DRAW LEVEL SELECTION LIST
+               {
+                       struct cmp_level *levels = pack_infos[ pack_selection ].levels;
+                       int count = pack_infos[ pack_selection ].level_count;
+                       int unlocked = 3000;
+               
+                       static struct ui_scrollbar sb = {
+                               .bar_height = 400
+                       };
+                       
+                       ui_px view_height = ui_global_ctx.cursor[3];
+                       ui_px level_height = 50;
+
+                       // Level scroll view
+                       gui_new_node();
+                       {
+                               gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
+                               gui_set_clip( ui_global_ctx.cursor );
+                               
+                               ui_global_ctx.cursor[2] = 14;
+                               gui_align_right();
+                               
+                               ui_px content_height = count*level_height;
+                               if( content_height > view_height )
+                               {
+                                       ui_scrollbar( &ui_global_ctx, &sb, 1 );
+                                       ui_global_ctx.cursor[1] -= ui_calculate_content_scroll( &sb, content_height );
+                               }
+                               else
+                               {
+                                       gui_fill_rect( ui_global_ctx.cursor, 0xff807373 );
+                               }
+                               
+                               ui_global_ctx.cursor[2] = 240;
+                               ui_global_ctx.cursor[3] = level_height;
+                               gui_align_left();
+                               
+                               for( int i = 0; i < count; i ++ )
+                               {
+                                       struct cmp_level *lvl_info = &levels[i];
+                               
+                                       if( i < unlocked )
+                                       {
+                                               if( lvl_info->completed_score != 0 )
+                                                       gui_override_colours( i&0x1? &flcol_list_complete_a: &flcol_list_complete_b );
+                                               else
+                                                       gui_override_colours( i&0x1? &flcol_list_a: &flcol_list_b );
+                                       }
+                                       else
+                                               gui_override_colours( &flcol_list_locked );
+                                       
+                                       if( i < unlocked )
+                                       {
+                                               if( gui_button( 2 + i ) == k_button_click )
+                                               {
+                                                       ui_data.level_selected = &levels[i];
+                                                       ui_data.leaderboard_show = 0;
+                                                       sw_find_leaderboard( ui_data.level_selected->map_name );
+                                               }
+                                               
+                                               ui_global_ctx.override_colour = 0xffffffff;
+                                               gui_text( lvl_info->title, 6, 0 );
+                                               ui_global_ctx.cursor[1] += 18;
+                                               gui_text( "incomplete", 4, 0 );
+                                       }
+                                       else
+                                       {
+                                               gui_button( 2 + i );
+                                               
+                                               ui_global_ctx.override_colour = 0xff786f6f;
+                                               gui_text( "???", 6, 0 );
+                                               ui_global_ctx.cursor[1] += 18;
+                                               gui_text( "locked", 4, 0 );
+                                       }
+                                       
+                                       gui_end_down();
+                               }
+                               
+                               gui_reset_colours();            
+                               gui_release_clip();
+                       }
+                       gui_end_down();
+               }
+       }
+       gui_end_right();
+       
+       // Selected level UI
+       // ============================================================
+       
+       if( ui_data.level_selected )
+       {
+               ui_global_ctx.cursor[0] += 16;
+               ui_global_ctx.cursor[1] += 16;
+               ui_global_ctx.cursor[2] = 512-40;
+               ui_global_ctx.cursor[3] = 560-16;
+               
+               gui_new_node();
                {
-                       glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.369768f, 0.3654f, 0.42f, 1.0f );
+                       gui_capture_mouse( 9999 );
                        
-                       use_mesh( &world.tile );
-                       glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 
-                               -1.0f, 
-                               ((float)level_count - (float)level_select * 2.0f ) * selection_scale * 0.6f,
-                               selection_scale
-                       );
-                       draw_mesh( 2, 2 );
+                       gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
+                       ui_global_ctx.cursor[1] += 4;
+                       gui_text( ui_data.level_selected->title, 6, 0 );
                        
-                       use_mesh( &world.circle );
+                       ui_global_ctx.cursor[1] += 30;
+                       ui_rect_pad( ui_global_ctx.cursor, 8 );
+                       ui_global_ctx.cursor[3] = 300;
                        
-                       if( vg_get_button_down( "primary" ) )
+                       gui_new_node();
                        {
-                               console_changelevel( 1, level_pack_1 + level_select );
+                               gui_fill_rect( ui_global_ctx.cursor, 0xff655959 );
+                       }
+                       gui_end_down();
+                       
+                       ui_text_use_paragraph( &ui_global_ctx );
+                       ui_global_ctx.cursor[1] += 2;
+                       
+                       gui_text( ui_data.level_selected->description, 5, 0 );
+                       ui_text_use_title( &ui_global_ctx );
+                       
+                       // Buttons at the bottom
+                       ui_global_ctx.cursor[3] = 30;
+                       ui_global_ctx.cursor[2] = 80;
+                       
+                       gui_align_bottom();
+                       ui_global_ctx.cursor[1] -= 8;
+                       
+                       if( gui_button( 3000 ) == k_button_click )
+                       {
+                               ui_data.level_selected = NULL;
+                       }
+                       gui_text( "Back", 6, 0 );
+                       gui_end();
+                       
+                       gui_align_right();
+                       ui_global_ctx.cursor[2] = 170;
+                       ui_global_ctx.cursor[0] -= 8 + 170 + 2;
+                       
+                       {
+                               gui_override_colours( &flcol_list_locked );
+                               if( gui_button( 3001 ) == k_button_click )
+                                       vg_error( "UNIMPLEMENTED\n" );
+                                       
+                               gui_text( "Restore Solution", 6, 0 );
+                               gui_end_right();
+                       }
+                       
+                       ui_global_ctx.cursor[0] += 2;
+                       ui_global_ctx.cursor[2] = 80;
+                       
+                       {
+                               gui_override_colours( &flcol_list_complete_a );
+                               if( gui_button( 3002 ) == k_button_click )
+                               {
+                                       console_changelevel( 1, &ui_data.level_selected->map_name );
+                                       world.pCmpLevel = ui_data.level_selected;
+
+                                       ui_data.level_selected = NULL;
+                                       ui_data.leaderboard_show = 0;
+                               }
+                               gui_text( "Play", 6, 0 );
+                               gui_end();
+                       }
+                       
+                       gui_reset_colours();
+               }
+               gui_end_right();
+               
+               if( ui_data.leaderboard_show )
+               {
+                       ui_global_ctx.cursor[0] += 16;
+                       ui_global_ctx.cursor[3] = 250;
+                       
+                       // If has results
+                       gui_new_node();
+                       {
+                               gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
+                               
+                               ui_rect_pad( ui_global_ctx.cursor, 8 );
+                               
+                               gui_new_node();
+                               {
+                                       ui_global_ctx.cursor[3] = 32+8;
+                                       
+                                       for( int i = 0; i < ui_data.leaderboard_count; i ++ )
+                                       {
+                                               gui_new_node();
+                                               {
+                                                       struct leaderboard_player *player = &ui_data.leaderboard_players[i];
+                                                       
+                                                       ui_global_ctx.cursor[0] += 4;
+                                                       ui_global_ctx.cursor[1] += 4;                                                   
+                                                       ui_global_ctx.cursor[2] = 32;
+                                                       ui_global_ctx.cursor[3] = 32;
+                                                       
+                                                       gui_new_node();
+                                                       {
+                                                               gui_push_image( ui_global_ctx.cursor, player->texture );
+                                                       }
+                                                       gui_end_right();
+                                                       
+                                                       gui_text( player->player_name, 6, 0 );
+                                                       
+                                                       ui_global_ctx.cursor[2] = 50;
+                                                       gui_align_right();
+                                                       
+                                                       gui_text( player->score_text, 6, 0 );
+                                               }
+                                               gui_end_down();
+                                               
+                                               ui_global_ctx.cursor[1] += 2;
+                                               
+                                       }
+                               }
+                               gui_end();
                        }
+                       gui_end();
                }
        }
-       else mouse_ui_space[1] = INFINITY;
+}
 
-       glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.4f, 0.39f, 0.45f, 1.0f );
+void leaderboard_dispatch_score(void)
+{
+       sw_upload_leaderboard_score( 
+               ui_data.upload_request.level->steam_leaderboard, 
+               k_ELeaderboardUploadScoreMethodKeepBest,
+               ui_data.upload_request.score,
+               NULL,
+               0
+       );
+       
+       ui_data.upload_request.is_waiting = 0;
+       
+       vg_success( "Dispatched leaderboard score\n" );
+}
 
-       // Draw levels
-       for( int i = 0; i < level_count; i ++ )
+void leaderboard_found( LeaderboardFindResult_t *pCallback )
+{
+       if( !pCallback->m_bLeaderboardFound )
        {
-               struct career_level *clevel = &career.levels[i];
-       
-               v3f level_ui_space = { 
-                       -0.97f, 
-                       ((float)level_count - (float)i * 2.0f ) * selection_scale * 0.6f + selection_scale * 0.5f,
-                       selection_scale * 0.5f
-               };
-               
-               float scale = vg_clampf( 1.0f - fabsf(level_ui_space[1] - mouse_ui_space[1]) * 2.0f, 0.9f, 1.0f );
-               level_ui_space[2] *= scale;
+               vg_error( "Leaderboard could not be found\n" );
+               ui_data.steam_leaderboard = 0;
+       }
+       else
+       {
+               const char *recieved_name = sw_get_leaderboard_name( pCallback->m_hSteamLeaderboard );
                
-               glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, level_ui_space );
+               // Update UI state and request entries if this callback found the current UI level
+               if( ui_data.level_selected )
+               {
+                       if( !strcmp( recieved_name, ui_data.level_selected->map_name ) )
+                       {
+                               sw_download_leaderboard_entries( pCallback->m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 0, 10 );
+                               ui_data.level_selected->steam_leaderboard = pCallback->m_hSteamLeaderboard;
+                       }
+               }
                
-               if( clevel->completed )
-                       draw_mesh( filled_start, filled_count );
-               else
-                       draw_mesh( empty_start, empty_count );
+               // Dispatch the waiting request if there was one
+               if( ui_data.upload_request.is_waiting )
+               {
+                       if( !strcmp( recieved_name, ui_data.upload_request.level->map_name ) )
+                       {
+                               ui_data.upload_request.level->steam_leaderboard = pCallback->m_hSteamLeaderboard;
+                               leaderboard_dispatch_score();
+                       }
+               }
        }
-       
-       // Level scores
-       glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.4f*1.25f, 0.39f*1.25f, 0.45f*1.25f, 1.0f );
-       
-       use_mesh( &world.numbers );
-       for( int i = 0; i < level_count; i ++ )
+}
+
+void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
+{
+       // Update UI if this leaderboard matches what we currently have in view
+       if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
        {
-               struct career_level *clevel = &career.levels[i];
-       
-               v3f level_ui_space = { 
-                       -0.94f, 
-                       ((float)level_count - (float)i * 2.0f ) * selection_scale * 0.6f + selection_scale * 0.5f,
-                       0.02f
-               };
+               vg_info( "Recieved %d entries\n", pCallback->m_cEntryCount );
+               ui_data.leaderboard_count = VG_MIN( pCallback->m_cEntryCount, 10 );
                
-               if( clevel->completed )
-                       draw_numbers( level_ui_space, clevel->score );
+               for( int i = 0; i < ui_data.leaderboard_count; i ++ )
+               {
+                       LeaderboardEntry_t entry;
+                       sw_get_downloaded_entry( pCallback->m_hSteamLeaderboardEntries, i, &entry, NULL, 0 );
+                       
+                       struct leaderboard_player *player = &ui_data.leaderboard_players[i];
+
+                       player->id = entry.m_steamIDUser.m_unAll64Bits;
+                       strncpy( player->player_name, sw_get_friend_persona_name( player->id ), vg_list_size( player->player_name )-1 );
+                       player->score = entry.m_nScore;
                        
-               level_ui_space[0] = -0.975f;
-               level_ui_space[1] -= 0.01f;
-               draw_numbers( level_ui_space, i );
+                       snprintf( player->score_text, vg_list_size(player->score_text), "%d", player->score );
+                       player->texture = sw_get_player_image( player->id );
+                       
+                       if( player->texture == 0 )
+                               player->texture = tex_unkown.name;
+               }
+               
+               if( ui_data.leaderboard_count )
+                       ui_data.leaderboard_show = 1;
+               else
+                       ui_data.leaderboard_show = 0;
        }
-       
-       //use_mesh( &world.numbers );
-       //draw_numbers( (v3f){ 0.0f, -0.5f, 0.1f }, 128765 );
+       else vg_warn( "Downloaded leaderboard does not match requested!\n" );
 }
 
-void vg_ui(void)
+void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
 {
-       ui_test();
+       if( ui_data.upload_request.is_waiting )
+               vg_warn( "You are uploading leaderboard entries too quickly!\n" );
+               
+       ui_data.upload_request.level = cmp_level;
+       ui_data.upload_request.score = score;
+       ui_data.upload_request.is_waiting = 1;
+       
+       // If leaderboard ID has been downloaded already then just immediately dispatch this
+       if( cmp_level->steam_leaderboard )
+               leaderboard_dispatch_score();
+       else
+               sw_find_leaderboard( cmp_level->map_name );
 }