X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=fishladder.c;h=7f47fdb54a49c96a1a5e9cfe8a913f88ef590861;hb=8f89e6bd64229500954908e79ce500f80dda774d;hp=eb56b5a3fd2cab8bb7505e3aea5232a3ec4217d3;hpb=4e524950cd061506839e4a036bb64f1cd61e12d9;p=fishladder.git diff --git a/fishladder.c b/fishladder.c index eb56b5a..7f47fdb 100644 --- a/fishladder.c +++ b/fishladder.c @@ -1,9 +1,39 @@ // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved -//#define VG_STEAM +#define VG_STEAM +#define VG_STEAM_APPID 1218140U #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", @@ -182,7 +212,7 @@ v2f const curve_7_1[] = {{0.5f,0.8438f},{1.0f-0.875f,0.8438f},{1.0-0.625f,0.5f}, float const curve_7_linear_section = 0.1562f; v3f colour_sets[] = -{ { 0.9f, 0.6f, 0.20f }, +{ { 1.0f, 0.9f, 0.3f }, { 0.2f, 0.9f, 0.14f }, { 0.4f, 0.8f, 1.00f } }; @@ -245,7 +275,8 @@ enum e_fish_state k_fish_state_soon_dead = -1, k_fish_state_dead = 0, k_fish_state_alive, - k_fish_state_bg + k_fish_state_bg, + k_fish_state_soon_alive }; struct world @@ -270,6 +301,7 @@ struct world int simulating; int sim_run, max_runs; + float sim_speed; float frame_lerp; struct cell_terminal @@ -323,13 +355,12 @@ struct world int num_fishes; - char map_name[128]; + char map_name[64]; struct career_level *ptr_career_level; u32 score; u32 completed; u32 time; - } world = {}; static void map_free(void) @@ -371,9 +402,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; @@ -706,6 +737,7 @@ static void map_serialize( FILE *stream ) int main( int argc, char *argv[] ) { vg_init( argc, argv, "Marble Computing | SPACE: Test | LeftClick: Toggle tile | RightClick: Drag wire" ); + return 0; } static int console_credits( int argc, char const *argv[] ) @@ -842,8 +874,12 @@ static int console_changelevel( int argc, char const *argv[] ) return 0; } +void leaderboard_found( LeaderboardFindResult_t *pCallback ); void vg_start(void) { + // Steamworks callbacks + sw_leaderboard_found = &leaderboard_found; + vg_function_push( (struct vg_cmd){ .name = "_map_write", .function = console_save_map @@ -1234,7 +1270,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 ); @@ -1356,6 +1392,7 @@ void vg_update(void) world.sim_frame = 0; world.sim_start = vg_time; world.sim_run = 0; + world.sim_speed = 2.5f; for( int i = 0; i < world.w*world.h; i ++ ) world.data[ i ].state &= ~FLAG_FLIP_FLOP; @@ -1367,7 +1404,7 @@ void vg_update(void) // Fish ticks if( world.simulating ) { - while( world.sim_frame < (int)((vg_time-world.sim_start)*2.0f) ) + while( world.sim_frame < (int)((vg_time-world.sim_start)*world.sim_speed) ) { //vg_info( "frame: %u\n", world.sim_frame ); sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 9 ); @@ -1394,6 +1431,9 @@ void vg_update(void) if( fish->state == k_fish_state_soon_dead ) fish->state = k_fish_state_dead; + if( fish->state == k_fish_state_soon_alive ) + fish->state = k_fish_state_alive; + if( fish->state < k_fish_state_alive ) continue; @@ -1488,7 +1528,7 @@ void vg_update(void) fish->state = world_check_pos_ok( fish->pos )? k_fish_state_bg: k_fish_state_dead; } - v2i_add( fish->pos, fish->dir, fish->pos ); + //v2i_add( fish->pos, fish->dir, fish->pos ); } else if( fish->state == k_fish_state_bg ) { @@ -1505,7 +1545,9 @@ void vg_update(void) if( cell_entry->config == k_cell_type_con_r || cell_entry->config == k_cell_type_con_u || cell_entry->config == k_cell_type_con_l || cell_entry->config == k_cell_type_con_d ) { - fish->state = k_fish_state_alive; + sw_set_achievement( "CAN_DO_THAT" ); + + fish->state = k_fish_state_soon_alive; fish->dir[0] = 0; fish->dir[1] = 0; @@ -1535,6 +1577,7 @@ void vg_update(void) if( fish->state == k_fish_state_alive ) { + v2i_add( fish->pos, fish->dir, fish->pos ); struct cell *cell_current = pcell( fish->pos ); if( cell_current->state & FLAG_IS_TRIGGER ) @@ -1559,23 +1602,58 @@ void vg_update(void) } // Third pass (collisions) + struct fish *fi, *fj; + for( int i = 0; i < world.num_fishes; i ++ ) { - if( world.fishes[i].state == k_fish_state_alive ) + fi = &world.fishes[i]; + + if( fi->state == k_fish_state_alive ) { + int continue_again = 0; + for( int j = i+1; j < world.num_fishes; j ++ ) { - if( (world.fishes[j].state == k_fish_state_alive) && - (world.fishes[i].pos[0] == world.fishes[j].pos[0]) && - (world.fishes[i].pos[1] == world.fishes[j].pos[1]) ) + fj = &world.fishes[j]; + + if( (fj->state == k_fish_state_alive) ) { - // Shatter death (+0.5s) - world.fishes[i].state = k_fish_state_soon_dead; - world.fishes[j].state = k_fish_state_soon_dead; - world.fishes[i].death_time = 0.5f; - world.fishes[j].death_time = 0.5f; + v2i fi_prev; + v2i fj_prev; + + v2i_sub( fi->pos, fi->dir, fi_prev ); + v2i_sub( fj->pos, fj->dir, fj_prev ); + + int + collide_next_frame = ( + (fi->pos[0] == fj->pos[0]) && + (fi->pos[1] == fj->pos[1]))? 1: 0, + collide_this_frame = ( + (fi_prev[0] == fj->pos[0]) && + (fi_prev[1] == fj->pos[1]) && + (fj_prev[0] == fi->pos[0]) && + (fj_prev[1] == fi->pos[1]) + )? 1: 0; + + if( collide_next_frame || collide_this_frame ) + { + sw_set_achievement( "BANG" ); + + // Shatter death (+0.5s) + float death_time = collide_this_frame? 0.0f: 0.5f; + + fi->state = k_fish_state_soon_dead; + fj->state = k_fish_state_soon_dead; + fi->death_time = death_time; + fj->death_time = death_time; + + continue_again = 1; + break; + } } } + if( continue_again ) + continue; } } @@ -1654,6 +1732,10 @@ void vg_update(void) world.sim_frame = 0; world.sim_start = vg_time; world.num_fishes = 0; + + for( int i = 0; i < world.w*world.h; i ++ ) + world.data[ i ].state &= ~FLAG_FLIP_FLOP; + continue; } else @@ -1671,6 +1753,9 @@ void vg_update(void) } else { + if( world.sim_run > 0 ) + sw_set_achievement( "GOOD_ENOUGH" ); + vg_error( "Level failed :(\n" ); } @@ -1690,7 +1775,7 @@ void vg_update(void) } float scaled_time = 0.0f; - scaled_time = (vg_time-world.sim_start)*2.0f; + scaled_time = (vg_time-world.sim_start)*world.sim_speed; world.frame_lerp = scaled_time - (float)world.sim_frame; // Update positions @@ -1884,6 +1969,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; @@ -1911,7 +2017,7 @@ void vg_render(void) SHADER_USE( shader_ball ); glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv ); - vg_tex2d_bind( &tex_ball, 0 ); + vg_tex2d_bind( &tex_ball_noise, 0 ); glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 ); // Draw 'fish' @@ -1932,7 +2038,8 @@ void vg_render(void) glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour ); glUniform2fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, fish->physics_co ); - draw_mesh( 0, 32 ); + glUniform2f( SHADER_UNIFORM( shader_ball, "uTexOffset" ), (float)i * 1.2334, (float)i * -0.3579f ); + draw_mesh( 0, 2 ); } } @@ -2137,104 +2244,308 @@ void vg_render(void) use_mesh( &world.numbers ); draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score ); - - // Level selection UI - use_mesh( &world.circle ); - float ratio = ((float)vg_window_x/(float)vg_window_y); - - 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 ); +} + +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 +}; - // 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 }; +static ui_colourset flcol_list_locked = { + .main = 0xff655959, + .hover = 0xff655959, + .active = 0xff655959 +}; - // Get selected level - const float selection_scale = 0.05f; - int const level_count = vg_list_size( level_pack_1 ); - int level_select = -1; +static struct +{ + SteamLeaderboard_t steam_leaderboard; + int leaderboard_matches; - if( mouse_ui_space[0] <= -0.8f ) + struct cmp_level *level_selected; +} +ui_data; + +void vg_ui(void) +{ + // UI memory + static int pack_selection = 0; + static struct pack_info { - 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 ); + 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; - // Draw selector - if( level_select >= 0 && level_select < vg_list_size( level_pack_1 ) ) + gui_fill_y(); + + ui_global_ctx.id_base = 4 << 16; + + gui_new_node(); + { + 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(); { - glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.369768f, 0.3654f, 0.42f, 1.0f ); - - 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 ); - - use_mesh( &world.circle ); + ui_rect_pad( ui_global_ctx.cursor, 2 ); + ui_global_ctx.cursor[2] = 84; - if( vg_get_button_down( "primary" ) ) + for( int i = 0; i < 3; i ++ ) { - console_changelevel( 1, level_pack_1 + level_select ); - } - } - } - else mouse_ui_space[1] = INFINITY; - - glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.4f, 0.39f, 0.45f, 1.0f ); + if( i == pack_selection ) + gui_override_colours( &flcol_list_locked ); - // Draw levels - for( int i = 0; i < level_count; i ++ ) - { - 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 - }; + 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(); - 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; + ui_global_ctx.cursor[3] = 500; - glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, level_ui_space ); + // DRAW LEVEL SELECTION LIST + { + struct cmp_level *levels = pack_infos[ pack_selection ].levels; + int count = pack_infos[ pack_selection ].level_count; + int unlocked = 3000; - if( clevel->completed ) - draw_mesh( filled_start, filled_count ); - else - draw_mesh( empty_start, empty_count ); + 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_matches = 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(); - // Level scores - glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.4f*1.25f, 0.39f*1.25f, 0.45f*1.25f, 1.0f ); + // Selected level UI + // ============================================================ - use_mesh( &world.numbers ); - for( int i = 0; i < level_count; i ++ ) + if( ui_data.level_selected ) { - 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 - }; + 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; - if( clevel->completed ) - draw_numbers( level_ui_space, clevel->score ); + gui_new_node(); + { + gui_capture_mouse( 9999 ); + + gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d ); + ui_global_ctx.cursor[1] += 4; + gui_text( ui_data.level_selected->title, 6, 0 ); + + ui_global_ctx.cursor[1] += 30; + ui_rect_pad( ui_global_ctx.cursor, 8 ); + ui_global_ctx.cursor[3] = 300; + + gui_new_node(); + { + gui_fill_rect( ui_global_ctx.cursor, 0xff655959 ); + } + gui_end_down(); - level_ui_space[0] = -0.975f; - level_ui_space[1] -= 0.01f; - draw_numbers( level_ui_space, i ); + 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 ); + ui_data.level_selected = NULL; + ui_data.leaderboard_matches = 0; + } + gui_text( "Play", 6, 0 ); + gui_end(); + } + + gui_reset_colours(); + } + gui_end_right(); + + if( ui_data.leaderboard_matches ) + { + 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 ); + } + gui_end(); + } } - - //use_mesh( &world.numbers ); - //draw_numbers( (v3f){ 0.0f, -0.5f, 0.1f }, 128765 ); } -void vg_ui(void) +void leaderboard_found( LeaderboardFindResult_t *pCallback ) { - ui_test(); + if( !pCallback->m_bLeaderboardFound ) + vg_error( "Leaderboard could not be found\n" ); + + ui_data.steam_leaderboard = pCallback->m_hSteamLeaderboard; + ui_data.leaderboard_matches = 0; + + if( ui_data.level_selected ) + if( !strcmp( sw_get_leaderboard_name( ui_data.steam_leaderboard ), ui_data.level_selected->map_name ) ) + ui_data.leaderboard_matches = 1; }