level smooth transition camera
[fishladder.git] / fishladder.c
index 412182cd21619e4821865ac1733290d1b3f5be71..fcdd58d4fe014c5d4ae3de204315e036e7a29af6 100644 (file)
@@ -1,38 +1,12 @@
 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
 
+//#define VG_CAPTURE_MODE
 #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                                                   DONE
-                       Play button / Speed controller                          PLAY/PAUSED.. todo: speed, wire connecty
-                       
-                       
-       After release:
-               
-*/
+// #define STEAM_LEADERBOARDS
 
 // CONSTANTS
 // ===========================================================================================================
@@ -64,7 +38,7 @@ enum e_world_button
        k_world_button_none = -1,
        k_world_button_sim = 0,
        k_world_button_pause = 1,
-       k_world_button_wire_mode = 2
+       k_world_button_speedy = 2
 };
 
 #define FLAG_CANAL             0x1
@@ -211,7 +185,7 @@ struct world
        
        int w, h;
        
-       struct mesh shapes, numbers;
+       struct mesh shapes;
        struct mesh_wire
        {
                GLuint vao, vbo, ebo;
@@ -372,10 +346,6 @@ static void use_mesh( struct mesh *m )
        glBindVertexArray( m->vao );
 }
 
-
-
-
-
 static struct cell_button *get_wbutton( enum e_world_button btn )
 {
        return &world.buttons[ btn ];
@@ -564,7 +534,7 @@ static int map_load( const char *str, const char *name )
                                                struct cell_terminal *terminal = &world.io[ reg_start ];
                                                struct terminal_run *run = &terminal->runs[ terminal->run_count-1 ];
 
-                                               if( *c >= 'a' && *c <= 'z' )
+                                               if( (*c >= 'a' && *c <= 'z') || *c == ' ' )
                                                {                                                       
                                                        run->conditions[ run->condition_count ++ ] = *c;
                                                }
@@ -728,6 +698,35 @@ static int map_load( const char *str, const char *name )
                                px[3] = 0;
                        }
                }
+               
+               // Level selection area
+               
+               // Beginner
+               for( int y = 16+2; y < 16+2+4; y ++ )
+               {
+                       u8 *px = &info_buffer[((y*64)+16-1)*4];
+                       px[0] = 0x10;
+               }
+               
+               // Intermediate
+               for( int y = 16+2; y < 16+2+6; y ++ )
+               {
+                       for( int x = 0; x < 3; x ++ )
+                       {
+                               u8 *px = &info_buffer[((y*64)+16-5+x)*4];
+                               px[0] = 0x10;
+                       }
+               }
+               
+               // Expert
+               for( int x = 1; x < 5; x ++ )
+               {
+                       u8 *px = &info_buffer[((16*64)+16-5+x)*4];
+                       px[0] = 0x10;
+               }
+               
+               info_buffer[(((16+world.h-3)*64)+world.w+16-1)*4] = 0x30;
+               info_buffer[(((16+world.h-2)*64)+world.w+16-1)*4] = 0x30;
 
                // Random walks.. kinda
                for( int i = 0; i < arrlen(world.io); i ++ )
@@ -923,8 +922,13 @@ struct dcareer_state
 };
 #pragma pack(pop)
 
+static int career_load_success = 0;
+
 static void career_serialize(void)
 {
+       if( !career_load_success )
+               return;
+
        struct dcareer_state encoded;
        encoded.version = 2;
        encoded.in_map = world.pCmpLevel? world.pCmpLevel->serial_id: -1;
@@ -1003,7 +1007,6 @@ static void career_load(void)
                {
                        vg_warn( "This save file is too small to have a header. Creating a blank one\n" );
                        free( cr );
-                       return;
                }
                
                memcpy( (void*)&encoded, cr, VG_MIN( sizeof( struct dcareer_state ), sz ) );
@@ -1045,6 +1048,8 @@ static void career_load(void)
                        }
                }
        }
+       
+       career_load_success = 1;
 }
 
 // MAIN GAMEPLAY
@@ -1054,6 +1059,12 @@ static int is_simulation_running(void)
        return world.buttons[ k_world_button_sim ].pressed;
 }
 
+static void clear_animation_flags(void)
+{
+       for( int i = 0; i < world.w*world.h; i ++ )
+               world.data[ i ].state &= ~(FLAG_FLIP_FLOP|FLAG_FLIP_ROTATING);
+}
+
 static void simulation_stop(void)
 {
        world.buttons[ k_world_button_sim ].pressed = 0;
@@ -1062,14 +1073,45 @@ static void simulation_stop(void)
        world.num_fishes = 0;
        world.sim_frame = 0;
        world.sim_run = 0;
+       world.frame_lerp = 0.0f;
        
        io_reset();
        
        sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
        
+       clear_animation_flags();
+       
        vg_info( "Stopping simulation!\n" );
 }
 
+static void simulation_start(void)
+{
+       vg_success( "Starting simulation!\n" );
+                               
+       sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
+       
+       world.num_fishes = 0;
+       world.sim_frame = 0;
+       world.sim_run = 0;
+       
+       world.sim_delta_speed = world.buttons[ k_world_button_speedy ].pressed? 10.0f: 2.5f;
+       world.sim_delta_ref = vg_time;
+       world.sim_internal_ref = 0.0f;
+       world.sim_internal_time = 0.0f;
+       world.pause_offset_target = 0.0f;
+       
+       world.sim_target = 0;
+       
+       clear_animation_flags();
+       
+       io_reset();
+       
+       if( world.pCmpLevel )
+       {
+               world.pCmpLevel->completed_score = 0;
+       }
+}
+
 static int world_check_pos_ok( v2i co )
 {
        return (co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2)? 0: 1;
@@ -1169,16 +1211,23 @@ void vg_update(void)
                        r2 = (float)world.h / (float)world.w,
                        size;
        
-       size = ( r2 < r1? (float)world.w * 0.5f: ((float)world.h * 0.5f) / r1 ) + 2.5f; 
-       m3x3_projection( m_projection, -size, size, -size*r1, size*r1 );
+       static float size_current = 2.0f;
+       static v3f origin_current = { 0.0f, 0.0f, 0.0f };
+       
+       size = ( r2 < r1? (float)(world.w+5) * 0.5f: ((float)(world.h+5) * 0.5f) / r1 ) + 0.5f; 
        
        v3f origin;
-       origin[0] = floorf( -0.5f * world.w );
+       origin[0] = floorf( -0.5f * ((float)world.w-4.5f) );
        origin[1] = floorf( -0.5f * world.h );
        origin[2] = 0.0f;
        
+       // Lerp towards target
+       size_current = vg_lerpf( size_current, size, vg_time_delta * 6.0f );
+       v2_lerp( origin_current, origin, vg_time_delta * 6.0f, origin_current ); 
+       
+       m3x3_projection( m_projection, -size_current, size_current, -size_current*r1, size_current*r1 );
        m3x3_identity( m_view );
-       m3x3_translate( m_view, origin );
+       m3x3_translate( m_view, origin_current );
        m3x3_mul( m_projection, m_view, vg_pv );
        vg_projection_update();
        
@@ -1241,7 +1290,7 @@ void vg_update(void)
                        
                        if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
                        {
-                               u32 link_id = local_x > 0.5f? 1: 0;
+                               u32 link_id = cell_ptr->links[ 0 ]? 0: 1;
                                
                                // break existing connection off
                                if( cell_ptr->links[ link_id ] )
@@ -1259,7 +1308,7 @@ void vg_update(void)
                                world.id_drag_from = 0;
                        }
                        
-                       if( world.id_drag_from && (cell_ptr->state & FLAG_CANAL) && (cell_ptr->config == k_cell_type_split) )
+                       else if( world.id_drag_from && (cell_ptr->state & FLAG_CANAL) && (cell_ptr->config == k_cell_type_split) )
                        {
                                world.drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
                                world.drag_to_co[1] = (float)world.tile_y + 0.25f;
@@ -1331,6 +1380,9 @@ void vg_update(void)
                
                world.sim_target = (int)floorf(world.sim_internal_time);
                
+               int success_this_frame = 0;
+               int failure_this_frame = 0;
+               
                while( world.sim_frame < world.sim_target )
                {
                        sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
@@ -1378,7 +1430,16 @@ void vg_update(void)
                                                        {
                                                                struct terminal_run *run = &term->runs[ world.sim_run ];
                                                                if( run->recv_count < vg_list_size( run->recieved ) )
+                                                               {
+                                                                       if( fish->payload == run->conditions[ run->recv_count ] )
+                                                                               success_this_frame = 1;
+                                                                       else
+                                                                               failure_this_frame = 1;
+                                                               
                                                                        run->recieved[ run->recv_count ++ ] = fish->payload;
+                                                               }
+                                                               else
+                                                                       failure_this_frame = 1;
                                                                
                                                                break;
                                                        }
@@ -1527,11 +1588,6 @@ void vg_update(void)
                                        if( cell_current->state & FLAG_IS_TRIGGER )
                                        {
                                                int trigger_id = cell_current->links[0]?0:1;
-                                               int connection_id = cell_current->links[trigger_id];
-                                               int target_px = connection_id % world.w;
-                                               int target_py = (connection_id - target_px)/world.w;
-                                               
-                                               vg_line2( (v2f){ fish->pos[0], fish->pos[1] }, (v2f){ target_px, target_py }, 0xffffffff, 0xffffffff );
                                                
                                                struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
                                                
@@ -1611,11 +1667,15 @@ void vg_update(void)
                                {
                                        if( world.sim_frame < term->runs[ world.sim_run ].condition_count )
                                        {
+                                               char emit = term->runs[ world.sim_run ].conditions[ world.sim_frame ];
+                                               if( emit == ' ' )
+                                                       continue;
+                                       
                                                struct fish *fish = &world.fishes[ world.num_fishes ];
                                                v2i_copy( term->pos, fish->pos );
                                                
                                                fish->state = k_fish_state_alive;
-                                               fish->payload = term->runs[ world.sim_run ].conditions[ world.sim_frame ];
+                                               fish->payload = emit;
                                                
                                                struct cell *cell_ptr = pcell( fish->pos );
                                                
@@ -1725,6 +1785,21 @@ void vg_update(void)
                        world.sim_frame ++;
                }
                
+               // Sounds
+               if( failure_this_frame )
+               {
+                       sfx_set_play( &audio_tones, &audio_system_balls_extra, 0 );
+               }
+               else if( success_this_frame )
+               {
+                       static int succes_counter = 0;
+                       
+                       sfx_set_play( &audio_tones, &audio_system_balls_extra, 1+(succes_counter++) );
+                       
+                       if( succes_counter == 7 )
+                               succes_counter = 0;
+               }
+               
                // Position update
                // =====================================================================================================
                
@@ -1862,63 +1937,19 @@ static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f cons
        }
 }
 
-/*
-static void draw_numbers( v3f coord, int number )
-{
-       v3f pos;
-       v3_copy( coord, pos );
-       int digits[8]; int i = 0;
-       
-       while( number > 0 && i < 8 )
-       {               
-               digits[i ++] = number % 10;
-               number = number / 10;
-       }
-       
-       for( int j = 0; j < i; j ++ )
-       {
-               glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, pos );
-               draw_mesh( MESH_NUMBERS_OFFSETS[digits[i-j-1]][0], MESH_NUMBERS_OFFSETS[digits[i-j-1]][1] );
-               pos[0] += pos[2] * 0.75f;
-       }
-}*/
-
-static void simulation_start(void)
-{
-       vg_success( "Starting simulation!\n" );
-                               
-       sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
-       
-       world.num_fishes = 0;
-       world.sim_frame = 0;
-       world.sim_run = 0;
-       
-       world.sim_delta_speed = 2.5f;
-       world.sim_delta_ref = vg_time;
-       world.sim_internal_ref = 0.0f;
-       world.sim_internal_time = 0.0f;
-       world.pause_offset_target = 0.0f;
-       
-       world.sim_target = 0;
-       
-       for( int i = 0; i < world.w*world.h; i ++ )
-               world.data[ i ].state &= ~FLAG_FLIP_FLOP;
-       
-       io_reset();
-}
-
-static void wbutton_run( enum e_world_button btn_name )
+static void wbutton_run( enum e_world_button btn_name, v2f btn_tex )
 {
        static v3f button_colours[] = {
                {0.204f, 0.345f, 0.553f},
                {0.204f, 0.345f, 0.553f},
-               {0.741f, 0.513f, 0.078f},
+               {0.553f, 0.345f, 0.204f},
                {1.0f, 0.0f, 0.0f}
        };
 
        struct cell_button *btn = &world.buttons[btn_name];
        
        // Interaction
+       int tex_offset = 0;
        
        int is_hovering = (world.tile_x == world.w-1 && world.tile_y == world.h-btn_name-2)?1:0;        
        if( vg_get_button_up( "primary" ) && is_hovering )
@@ -1963,6 +1994,14 @@ static void wbutton_run( enum e_world_button btn_name )
                        else
                                world.pause_offset_target = 0.0f;
                }
+               else if( btn_name == k_world_button_speedy )
+               {
+                       btn->pressed ^= 0x1;
+                       
+                       world.sim_delta_speed = btn->pressed? 10.0f: 2.5f;
+                       world.sim_delta_ref = vg_time;
+                       world.sim_internal_ref = world.sim_internal_time;
+               }
                else
                {
                        btn->pressed ^= 0x1;
@@ -1997,6 +2036,13 @@ static void wbutton_run( enum e_world_button btn_name )
        btn->light = vg_lerpf( btn->light, btn->light_target, vg_time_delta*26.0f );
        
        // Draw
+       if( btn_name == k_world_button_sim && world.buttons[ k_world_button_sim ].pressed )
+       {
+               if( world.buttons[ k_world_button_pause ].pressed )
+                       tex_offset = 3;
+               else
+                       tex_offset = 2;
+       }
        
        v4f final_colour;
        v3_copy( button_colours[ btn_name ], final_colour );
@@ -2005,14 +2051,146 @@ static void wbutton_run( enum e_world_button btn_name )
        glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ), 
                world.w-1, 
                world.h-btn_name-2, 
-               (float)btn_name
-               3.0f 
+               (float)(btn_tex[0]+tex_offset)
+               btn_tex[1]
        );
        glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
        
        draw_mesh( 0, 2 );
 }
 
+static void wbutton_draw( v2i pos, v2f tex, v4f colour )
+{
+       glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ), 
+               pos[0], 
+               pos[1], 
+               tex[0], 
+               tex[1] 
+       );
+       glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, colour );
+       draw_mesh( 0, 2 );
+}
+
+static void level_selection_buttons(void)
+{
+       v3f tutorial_colour = { 0.204f, 0.345f, 0.553f };
+       v3f locked_colour = { 0.2f, 0.2f, 0.2f };
+
+       struct button_grid
+       {
+               v3f primary_colour;
+               v2i origin;
+               v2i dims;
+               struct cmp_level *levels;
+               int count;
+       }
+       grids[] = 
+       {
+               {
+                       .primary_colour = { 0.204f, 0.345f, 0.553f },
+                       .origin = { -1, 2 },
+                       .dims = { 1, 4 },
+                       .levels = cmp_levels_tutorials,
+                       .count = vg_list_size( cmp_levels_tutorials )
+               },
+               {
+                       .primary_colour = { 0.304f, 0.245f, 0.553f },
+                       .origin = { -5, 2 },
+                       .dims = { 3, 6 },
+                       .levels = cmp_levels_basic,
+                       .count = vg_list_size( cmp_levels_basic )
+               },
+               {
+                       .primary_colour = { 0.553f, 0.345f, 0.204f },
+                       .origin = { -4, 0 },
+                       .dims = { 4, 1 },
+                       .levels = cmp_levels_grad,
+                       .count = vg_list_size( cmp_levels_grad )
+               }
+       };
+       
+       v2f tex_coord = { 0.0f, 0.0f };
+       v4f final_colour = { 0.0f, 0.0f, 0.0f, 0.2f };
+       v2i button_pos;
+       static struct cmp_level *select_from = NULL;
+       struct cmp_level *switch_level_to = NULL;
+       
+       if( vg_get_button_down( "primary" ) )
+               select_from = NULL;
+       
+       for( int i = 0; i < vg_list_size( grids ); i ++ )
+       {
+               struct button_grid *grid = &grids[i];
+               
+               int j = 0;
+               
+               for( int x = 0; x < grid->dims[0]; x ++ )
+               {
+                       for( int y = 0; y < grid->dims[1]; y ++ )
+                       {
+                               if( j < grid->count )
+                               {
+                                       struct cmp_level *lvl = &grid->levels[ j ];
+                                       
+                                       // Determine colour
+                                       if( lvl->unlocked )
+                                       {
+                                               if( lvl->is_tutorial )
+                                                       v3_copy( tutorial_colour, final_colour );
+                                               else
+                                                       v3_copy( grid->primary_colour, final_colour );
+                                                       
+                                               if( lvl->completed_score )
+                                                       final_colour[3] = 0.8f;
+                                               else
+                                                       final_colour[3] = 0.2f;
+                                       }
+                                       else v3_copy( locked_colour, final_colour );
+                                       
+                                       v2i_add( grid->origin, (v2i){ x,y }, button_pos );
+                                       int is_hovering = v2i_eq( (v2i){world.tile_x, world.tile_y}, button_pos );
+                               
+                                       if( is_hovering )
+                                       {
+                                               final_colour[3] += 0.1f;
+                                               
+                                               // Up click
+                                               if( vg_get_button_up( "primary" ) )
+                                                       if( select_from == lvl && lvl->unlocked )
+                                                       {
+                                                               switch_level_to = lvl;
+                                                               sfx_set_play( &audio_clicks, &audio_system_ui, 1 );
+                                                       }
+                                               
+                                               // Start click
+                                               if( vg_get_button_down( "primary" ) )
+                                                       select_from = lvl;
+                                               
+                                               if( vg_get_button( "primary" ) )
+                                                       final_colour[3] += 0.2f;
+                                       }
+                                       
+                                       if( world.pCmpLevel == lvl )
+                                               final_colour[3] += fabsf(sinf( vg_time * 2.0f )) * 0.2f;
+                                       
+                                       wbutton_draw( (v2i){ grid->origin[0] + x, grid->origin[1] + y }, tex_coord, final_colour );
+                               }
+                               else break;
+                               
+                               j ++;
+                       }
+               }
+       }
+       
+       if( switch_level_to )
+       {
+               if( console_changelevel( 1, &switch_level_to->map_name ) )
+               {
+                       world.pCmpLevel = switch_level_to;
+               }
+       }
+}
+
 void vg_render(void)
 {
        glViewport( 0,0, vg_window_x, vg_window_y );
@@ -2024,12 +2202,15 @@ void vg_render(void)
        v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
        v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
 
-       int const circle_base = 4;
+       int const circle_base = 6;
        int const filled_start = circle_base+0;
        int const filled_count = circle_base+32;
        int const empty_start = circle_base+32;
        int const empty_count = circle_base+32*2;
        
+       if( !world.initialzed )
+               return;
+       
        // BACKGROUND
        // ========================================================================================================
        use_mesh( &world.shapes );
@@ -2210,31 +2391,41 @@ void vg_render(void)
        vg_tex2d_bind( &tex_buttons, 0 );
        glUniform1i( SHADER_UNIFORM( shader_buttons, "uTexMain" ), 0 );
        
-       wbutton_run( k_world_button_sim );
-       wbutton_run( k_world_button_pause );
-       //wbutton_run( k_world_button_wire_mode );
+       wbutton_run( k_world_button_sim, (v2f){ 0.0f, 3.0f } );
+       wbutton_run( k_world_button_pause, (v2f){ 1.0f, 3.0f } );
+       wbutton_run( k_world_button_speedy, (v2f){ 0.0f, 2.0f } );
+       
+       level_selection_buttons();
        
        // WIRES
        // ========================================================================================================
-       glDisable(GL_BLEND);
+       //glDisable(GL_BLEND);
 
        SHADER_USE( shader_wire );
        glBindVertexArray( world.wire.vao );
 
        glUniformMatrix3fv( SHADER_UNIFORM( shader_wire, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
-       glUniform4f( SHADER_UNIFORM( shader_wire, "uColour" ), 0.2f, 0.2f, 0.2f, 1.0f );
+       
+       v4f const wire_left_colour = { 0.5f, 0.5f, 0.5f, 1.0f };
+       v4f const wire_right_colour = { 0.2f, 0.2f, 0.2f, 1.0f };
+       v4f const wire_drag_colour = { 0.2f, 0.2f, 0.2f, 0.6f };
+       
+       glUniform1f( SHADER_UNIFORM( shader_wire, "uTime" ), world.frame_lerp );
+       glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), 0.0f );
        
        if( world.id_drag_from )
-       {               
+       {
+               glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_drag_colour );
                glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), 0.4f );
-               glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), world.drag_from_co[0], world.drag_from_co[1], 0.06f );
-               glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.06f );
+               glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), world.drag_from_co[0], world.drag_from_co[1], 0.20f );
+               glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.20f );
                glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
        }
        
        // Pulling animation
        float rp_x1 = world.frame_lerp*9.0f;
-       float rp_x2 = 1.0f-rp_x1*expf(1.0f-rp_x1)* 0.36f;
+       float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f;
+       float rp_x2 = 1.0f-rp_xa;
        
        for( int y = 2; y < world.h-2; y ++ )
        {
@@ -2264,23 +2455,91 @@ void vg_render(void)
                                        
                                        v2_add( desc->trigger_pos, endpoint, endpoint );
                                        
+                                       glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour );
                                        glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
-                                       glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.04f );
-                                       glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.04f );
+                                       glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
+                                       glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.18f );
+                                       glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.18f );
                                        glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
                                }
                        }
                }
        }
        
-       // I/O ARRAYS
+       // WIRE ENDPOINTS
        // ========================================================================================================
        
        SHADER_USE( shader_tile_colour );
        glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
        use_mesh( &world.shapes );
        
-       glEnable(GL_BLEND);
+       for( int y = 2; y < world.h-2; y ++ )
+       {
+               for( int x = 2; x < world.w-2; x ++ )
+               {
+                       struct cell *cell = pcell((v2i){x,y});
+
+                       if( cell->state & FLAG_CANAL )
+                       {
+                               if( cell->state & FLAG_IS_TRIGGER )
+                               {
+                                       struct cell_description *desc = &cell_descriptions[ cell->config ];
+                               
+                                       int trigger_id = cell->links[0]?0:1;
+                                       
+                                       int x2 = cell->links[trigger_id] % world.w;
+                                       int y2 = (cell->links[trigger_id] - x2) / world.w;
+                                       
+                                       v2f pts[2];
+                                       
+                                       pts[0][0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
+                                       pts[0][1] = (float)y2 + 0.25f;
+                                       
+                                       pts[1][0] = x;
+                                       pts[1][1] = y;
+                                       
+                                       v2_add( desc->trigger_pos, pts[1], pts[1] );
+                                       
+                                       glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 
+                                                       1, trigger_id? wire_right_colour: wire_left_colour );
+                                       
+                                       for( int i = 0; i < 2; i ++ )
+                                       {
+                                               glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 
+                                                       pts[i][0],
+                                                       pts[i][1], 
+                                                       0.08f 
+                                               );
+                                               draw_mesh( filled_start, filled_count );        
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       // SUB SPLITTER DIRECTION
+       // ========================================================================================================
+       
+       glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
+       
+       for( int y = 2; y < world.h-2; y ++ )
+       {
+               for( int x = 2; x < world.w-2; x ++ )
+               {
+                       struct cell *cell = pcell((v2i){x,y});
+
+                       if( cell->state & FLAG_CANAL && cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
+                       {
+                               glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), x, y, 1.0f );
+                               draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
+                       }
+               }
+       }
+       
+       // I/O ARRAYS
+       // ========================================================================================================
+       
+       //glEnable(GL_BLEND);
        
        for( int i = 0; i < arrlen( world.io ); i ++ )
        {
@@ -2340,14 +2599,18 @@ void vg_render(void)
                        
                                if( is_input )
                                {
-                                       colour_code_v3( term->runs[k].conditions[j], dot_colour );
-                                       glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
-                               
-                                       // Draw filled if tick not passed, draw empty if empty
-                                       if( (world.sim_frame > j && world.sim_run >= k) || world.sim_run > k )
-                                               draw_mesh( empty_start, empty_count );
-                                       else
-                                               draw_mesh( filled_start, filled_count );
+                                       char cc = term->runs[k].conditions[j];
+                                       if( cc != ' ' )
+                                       {                                       
+                                               colour_code_v3( cc, dot_colour );
+                                               glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
+                                       
+                                               // Draw filled if tick not passed, draw empty if empty
+                                               if( (world.sim_frame > j && world.sim_run >= k) || world.sim_run > k )
+                                                       draw_mesh( empty_start, empty_count );
+                                               else
+                                                       draw_mesh( filled_start, filled_count );
+                                       }
                                }
                                else
                                {
@@ -2447,6 +2710,7 @@ ui_data;
 
 void vg_ui(void)
 {
+       /*
        // UI memory
        static int pack_selection = 0;
        static struct pack_info
@@ -2488,7 +2752,7 @@ void vg_ui(void)
                gui_capture_mouse( 9999 );
                gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
                
-               gui_text( "ASSIGNMENTS", 8, 0 );
+               gui_text( "ASSIGNMENTS", 32, 0 );
                
                ui_global_ctx.cursor[1] += 30;
                ui_global_ctx.cursor[3] = 25;
@@ -2509,7 +2773,7 @@ void vg_ui(void)
                                        pack_selection = i;
                                
                                ui_global_ctx.cursor[1] += 2;
-                               gui_text( pack_is_unlocked? pack_infos[i].name: "", 4, 0 );
+                               gui_text( pack_is_unlocked? pack_infos[i].name: "???", 24, 0 );
                                gui_end_right();
                                
                                gui_reset_colours();
@@ -2581,18 +2845,18 @@ void vg_ui(void)
                                                }
                                                
                                                ui_global_ctx.override_colour = 0xffffffff;
-                                               gui_text( lvl_info->title, 6, 0 );
+                                               gui_text( lvl_info->title, 24, 0 );
                                                ui_global_ctx.cursor[1] += 18;
-                                               gui_text( lvl_info->completed_score>0? "passed": "incomplete", 4, 0 );
+                                               gui_text( lvl_info->completed_score>0? "passed": "incomplete", 24, 0 );
                                        }
                                        else
                                        {
                                                gui_button( 2 + i );
                                                
                                                ui_global_ctx.override_colour = 0xff786f6f;
-                                               gui_text( "???", 6, 0 );
+                                               gui_text( "???", 24, 0 );
                                                ui_global_ctx.cursor[1] += 18;
-                                               gui_text( "locked", 4, 0 );
+                                               gui_text( "locked", 24, 0 );
                                        }
                                        
                                        gui_end_down();
@@ -2622,7 +2886,7 @@ void vg_ui(void)
                        
                        gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
                        ui_global_ctx.cursor[1] += 4;
-                       gui_text( ui_data.level_selected->title, 6, 0 );
+                       gui_text( ui_data.level_selected->title, 24, 0 );
                        
                        ui_global_ctx.cursor[1] += 30;
                        ui_rect_pad( ui_global_ctx.cursor, 8 );
@@ -2634,11 +2898,11 @@ void vg_ui(void)
                        }
                        gui_end_down();
                        
-                       ui_text_use_paragraph( &ui_global_ctx );
+                       //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 );
+                       gui_text( ui_data.level_selected->description, 16, 0 );
+                       //ui_text_use_title( &ui_global_ctx );
                        
                        // Buttons at the bottom
                        ui_global_ctx.cursor[3] = 25;
@@ -2651,7 +2915,7 @@ void vg_ui(void)
                        {
                                ui_data.level_selected = NULL;
                        }
-                       gui_text( "BACK", 6, k_text_alignment_center );
+                       gui_text( "BACK", 24, k_text_alignment_center );
                        gui_end();
                        
                        gui_align_right();
@@ -2665,7 +2929,7 @@ void vg_ui(void)
                                
                                ui_global_ctx.override_colour = 0xff888888;
                                
-                               gui_text( "RESTORE SOLUTION", 6, k_text_alignment_center );
+                               gui_text( "RESTORE SOLUTION", 24, k_text_alignment_center );
                                gui_end_right();
                                ui_global_ctx.override_colour = 0xffffffff;
                        }
@@ -2685,7 +2949,7 @@ void vg_ui(void)
                                                ui_data.leaderboard_show = 0;
                                        }
                                }
-                               gui_text( "PLAY", 6, k_text_alignment_center );
+                               gui_text( "PLAY", 24, k_text_alignment_center );
                                gui_end();
                        }
                        
@@ -2703,7 +2967,7 @@ void vg_ui(void)
                        gui_new_node();
                        {
                                gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
-                               gui_text( "FRIEND LEADERBOARD", 6, 0 );
+                               gui_text( "FRIEND LEADERBOARD", 24, 0 );
                        }
                        gui_end_down();
                        
@@ -2724,7 +2988,7 @@ void vg_ui(void)
                                                
                                                // 1,2,3 ...
                                                static const char *places[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
-                                               gui_text( places[i], 7, 0 );
+                                               gui_text( places[i], 24, 0 );
                                                ui_global_ctx.cursor[0] += 32;
                                                
                                                struct leaderboard_player *player = &ui_data.leaderboard_players[i];
@@ -2740,12 +3004,12 @@ void vg_ui(void)
                                                gui_end_right();
                                                
                                                // Players name
-                                               gui_text( player->player_name, 7, 0 );
+                                               gui_text( player->player_name, 24, 0 );
                                                
                                                ui_global_ctx.cursor[2] = 50;
                                                gui_align_right();
                                                
-                                               gui_text( player->score_text, 7, k_text_alignment_right );
+                                               gui_text( player->score_text, 24, k_text_alignment_right );
                                        }
                                        gui_end_down();
                                        
@@ -2755,10 +3019,12 @@ void vg_ui(void)
                        gui_end();
                }
        }
+       */
 }
 
 void leaderboard_dispatch_score(void)
 {
+#if STEAM_LEADERBOARDS
        sw_upload_leaderboard_score( 
                ui_data.upload_request.level->steam_leaderboard, 
                k_ELeaderboardUploadScoreMethodKeepBest,
@@ -2770,10 +3036,12 @@ void leaderboard_dispatch_score(void)
        ui_data.upload_request.is_waiting = 0;
        
        vg_success( "Dispatched leaderboard score\n" );
+#endif
 }
 
 void leaderboard_found( LeaderboardFindResult_t *pCallback )
 {
+#ifdef STEAM_LEADERBOARDS
        if( !pCallback->m_bLeaderboardFound )
        {
                vg_error( "Leaderboard could not be found\n" );
@@ -2803,10 +3071,12 @@ void leaderboard_found( LeaderboardFindResult_t *pCallback )
                        }
                }
        }
+#endif
 }
 
 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
 {
+#ifdef STEAM_LEADERBOARDS
        // Update UI if this leaderboard matches what we currently have in view
        if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
        {
@@ -2841,10 +3111,12 @@ void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
                        ui_data.leaderboard_show = 0;
        }
        else vg_warn( "Downloaded leaderboard does not match requested!\n" );
+#endif
 }
 
 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
 {
+#ifdef STEAM_LEADERBOARDS
        if( ui_data.upload_request.is_waiting )
                vg_warn( "You are uploading leaderboard entries too quickly!\n" );
                
@@ -2857,6 +3129,7 @@ void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
                leaderboard_dispatch_score();
        else
                sw_find_leaderboard( cmp_level->map_name );
+#endif
 }
 
 // CONSOLE COMMANDS
@@ -2974,11 +3247,16 @@ static int console_changelevel( int argc, char const *argv[] )
 // START UP / SHUTDOWN
 // ===========================================================================================================
 
+#define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
+       X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY 
+
 void vg_start(void)
 {
        // Steamworks callbacks
+       #ifdef STEAM_LEADERBOARDS
        sw_leaderboard_found = &leaderboard_found;
        sw_leaderboard_downloaded = &leaderboard_downloaded;
+       #endif
 
        vg_function_push( (struct vg_cmd){
                .name = "_map_write",
@@ -3002,15 +3280,18 @@ void vg_start(void)
 
        // Combined quad, long quad / empty circle / filled circle mesh
        {
-               float combined_mesh[6*8 + 32*6*3] = {
+               float combined_mesh[6*6 + 32*6*3] = {
                        0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
                        0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
                        
                        0.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.2f,
-                       0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f
+                       0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f,
+                       
+                       TRANSFORM_TRI_2D( 0.15f,0.05f,0.4f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f ),
+                       TRANSFORM_TRI_2D( 0.15f,0.80f,0.4f, 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 1.0f )
                };
                
-               float *circle_mesh = combined_mesh + 6*4;
+               float *circle_mesh = combined_mesh + 6*6;
                int const res = 32;
 
                for( int i = 0; i < res; i ++ )
@@ -3038,19 +3319,6 @@ void vg_start(void)
                init_mesh( &world.shapes, combined_mesh, vg_list_size( combined_mesh ) );
        }
        
-       // Numbers mesh
-       {
-               init_mesh( &world.numbers,
-                       MESH_NUMBERS_BUFFER,
-                       vg_list_size( MESH_NUMBERS_BUFFER )
-               );
-               
-               for( int i = 0; i < 10; i ++ )
-               {
-                       vg_info( "offset: %u, length: %u\n", MESH_NUMBERS_OFFSETS[i][0], MESH_NUMBERS_OFFSETS[i][1] );
-               }
-       }
-       
        // Create wire mesh
        {
                int const num_segments = 64;
@@ -3146,7 +3414,6 @@ void vg_free(void)
        glDeleteBuffers( 1, &world.wire.ebo );
 
        free_mesh( &world.shapes );
-       free_mesh( &world.numbers );
        
        map_free();
 }