#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",
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 );
// 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;
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] ) )
{
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
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
void vg_free(void)
{
+ sw_free_opengl();
console_save_map( 0, NULL );
career_serialize();
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 );
}
// 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?
}
+ // 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;
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 );
}