remove todos
[fishladder.git] / fishladder.c
index 3bb8abafc35f73c92b6cd40fb0e166c1d65c7fcf..d7b887d2f602bc62b0a7a2f03cb9af134a8e0cd6 100644 (file)
@@ -5,35 +5,6 @@
 #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 buttern, pause, speed control, step button )
-                       
-                       
-       After release:
-               
-*/
-
 // CONSTANTS
 // ===========================================================================================================
 
@@ -63,7 +34,7 @@ enum e_world_button
 {
        k_world_button_none = -1,
        k_world_button_sim = 0,
-       k_world_button_ff = 1,
+       k_world_button_pause = 1,
        k_world_button_wire_mode = 2
 };
 
@@ -107,27 +78,29 @@ static struct cell_description
        
        int is_special;
        int is_linear;
+       
+       v2f trigger_pos;
 }
 cell_descriptions[] =
 {
        // 0-3
        {},
-       { .start = {  1,  0 }, .end = { -1,  0 } },
-       { .start = {  0,  1 }, .end = {  0, -1 } },
-       { .start = {  0,  1 }, .end = {  1,  0 } },
+       { .start = {  1,  0 }, .end = { -1,  0 }, .trigger_pos = { 0.5f, 0.25f } },
+       { .start = {  0,  1 }, .end = {  0, -1 }, .trigger_pos = { 0.25f, 0.5f } },
+       { .start = {  0,  1 }, .end = {  1,  0 }, .trigger_pos = { 0.25f, 0.25f } },
        // 4-7
-       { .start = { -1,  0 }, .end = {  1,  0 } },
-       { .start = { -1,  0 }, .end = {  1,  0 }, .is_linear = 1 },
-       { .start = {  0,  1 }, .end = { -1,  0 } },
+       { .start = { -1,  0 }, .end = {  1,  0 }, .trigger_pos = { 0.5f, 0.25f } },
+       { .start = { -1,  0 }, .end = {  1,  0 }, .trigger_pos = { 0.5f, 0.25f }, .is_linear = 1 },
+       { .start = {  0,  1 }, .end = { -1,  0 }, .trigger_pos = { 0.5f, 0.25f } },
        { .start = {  0,  1 }, .is_special = 1 },
        // 8-11
-       { .start = {  0, -1 }, .end = {  0,  1 } },
-       { .start = {  1,  0 }, .end = {  0, -1 } },
-       { .start = {  0,  1 }, .end = {  0, -1 }, .is_linear = 1 },
+       { .start = {  0, -1 }, .end = {  0,  1 }, .trigger_pos = { 0.25f, 0.5f } },
+       { .start = {  1,  0 }, .end = {  0, -1 }, .trigger_pos = { 0.25f, 0.75f } },
+       { .start = {  0,  1 }, .end = {  0, -1 }, .trigger_pos = { 0.25f, 0.5f }, .is_linear = 1 },
        { },
        // 12-15
-       { .start = { -1,  0 }, .end = {  0, -1 } },
-       { .end = { 0, -1 }, .is_special = 1 },
+       { .start = { -1,  0 }, .end = {  0, -1 }, .trigger_pos = { 0.75f, 0.75f } },
+       { .end = { 0, -1 }, .is_special = 1, .trigger_pos = { 0.5f, 0.75f } },
        { },
        { }
 };
@@ -169,16 +142,19 @@ struct world
        *data;
 #pragma pack(pop)
        
-       int frame;
        
        int initialzed;
        
-       int sim_frame;
-       float sim_start;
        int sim_run, max_runs;
-       
-       float sim_speed;
-       float frame_lerp;
+
+       int sim_frame, sim_target;
+       float sim_internal_time,                // current tick-time 
+               sim_internal_delta,                     // time delta
+               sim_internal_ref,                               // Reference point of internal time
+               sim_delta_ref,                          // Reference point of vg_time when we started at current sim_speed
+               sim_delta_speed,                                // Rate to apply time delta
+               frame_lerp,                                             // Fractional part of sim_internal_time
+               pause_offset_target;                    // 
        
        struct cell_terminal
        {
@@ -559,7 +535,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;
                                                }
@@ -740,6 +716,9 @@ static int map_load( const char *str, const char *name )
                        turtle_dir[1] = pcell(term->pos)->state & FLAG_INPUT? 1: -1;
                        original_y = turtle_dir[1];
                        
+                       info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;  
+                       v2i_add( turtle_dir, turtle, turtle );
+                       
                        for( int i = 0; i < 100; i ++ )
                        {
                                info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
@@ -915,8 +894,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;
@@ -995,7 +979,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 ) );
@@ -1037,6 +1020,8 @@ static void career_load(void)
                        }
                }
        }
+       
+       career_load_success = 1;
 }
 
 // MAIN GAMEPLAY
@@ -1046,21 +1031,54 @@ 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;
+       world.buttons[ k_world_button_pause ].pressed = 0;
        
        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 = 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();
+}
+
 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;
@@ -1185,7 +1203,7 @@ void vg_update(void)
        if( !is_simulation_running() && !gui_want_mouse() )
        {
                v2_copy( vg_mouse_ws, world.drag_to_co );
-       
+               
                if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
                {
                        world.selected = world.tile_y * world.w + world.tile_x;
@@ -1220,16 +1238,38 @@ void vg_update(void)
                                                                                (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
                        }
                        
-                       if( vg_get_button_down("secondary") && !(cell_ptr->config == k_cell_type_split) )
+                       if( vg_get_button_down("secondary") && (cell_ptr->state & FLAG_CANAL) && !(cell_ptr->config == k_cell_type_split) )
                        {
                                world.id_drag_from = world.selected;
-                               world.drag_from_co[0] = world.tile_x + 0.5f;
-                               world.drag_from_co[1] = world.tile_y + 0.5f;
+                       
+                               struct cell_description *desc = &cell_descriptions[ world.data[world.id_drag_from].config ];
+                               v2_add( desc->trigger_pos, (v2f){ world.tile_x, world.tile_y }, world.drag_from_co );
                        }
                        
-                       if( world.id_drag_from && (cell_ptr->config == k_cell_type_split) )
+                       float local_x = vg_mouse_ws[0] - (float)world.tile_x;
+                       
+                       if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
+                       {
+                               u32 link_id = local_x > 0.5f? 1: 0;
+                               
+                               // break existing connection off
+                               if( cell_ptr->links[ link_id ] )
+                               {
+                                       struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
+                                       
+                                       if( !current_connection->links[ link_id ^ 0x1 ] )
+                                               current_connection->state &= ~FLAG_TARGETED;
+                                       
+                                       current_connection->links[ link_id ] = 0;
+                                       cell_ptr->links[ link_id ] = 0;
+                               }
+                               
+                               cell_ptr->state &= ~FLAG_IS_TRIGGER;
+                               world.id_drag_from = 0;
+                       }
+                       
+                       if( world.id_drag_from && (cell_ptr->state & FLAG_CANAL) && (cell_ptr->config == k_cell_type_split) )
                        {
-                               float local_x = vg_mouse_ws[0] - (float)world.tile_x;
                                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;
 
@@ -1290,9 +1330,19 @@ void vg_update(void)
        // ========================================================================================================
        if( is_simulation_running() )
        {       
-               while( world.sim_frame < (int)((vg_time-world.sim_start)*world.sim_speed) )
+               float old_time = world.sim_internal_time;
+       
+               if( !world.buttons[ k_world_button_pause ].pressed )
+                       world.sim_internal_time = world.sim_internal_ref + (vg_time-world.sim_delta_ref) * world.sim_delta_speed;
+               else
+                       world.sim_internal_time = vg_lerpf( world.sim_internal_time, world.sim_internal_ref + world.pause_offset_target, vg_time_delta*15.0f );
+               world.sim_internal_delta = world.sim_internal_time-old_time;
+               
+               world.sim_target = (int)floorf(world.sim_internal_time);
+               
+               while( world.sim_frame < world.sim_target )
                {
-                       sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 9 );
+                       sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
 
                        // Update splitter deltas
                        for( int i = 0; i < world.h*world.w; i ++ )
@@ -1344,6 +1394,7 @@ void vg_update(void)
                                                }
                                                
                                                fish->state = k_fish_state_dead;
+                                               fish->death_time = -1000.0f;
                                                continue;
                                        }
                                        
@@ -1386,7 +1437,10 @@ void vg_update(void)
                                                        if( cell_next->config == k_cell_type_merge )
                                                        {
                                                                if( fish->dir[0] == 0 )
+                                                               {
                                                                        fish->state = k_fish_state_dead;
+                                                                       fish->death_time = world.sim_internal_time;
+                                                               }
                                                                else
                                                                        fish->flow_reversed = 0;
                                                        }
@@ -1402,7 +1456,10 @@ void vg_update(void)
                                                                                fish->flow_reversed = 0;
                                                                        }
                                                                        else
+                                                                       {
                                                                                fish->state = k_fish_state_dead;
+                                                                               fish->death_time = world.sim_internal_time;
+                                                                       }
                                                                }
                                                                else
                                                                        fish->flow_reversed = ( fish->dir[0] != -desc->start[0] || 
@@ -1410,7 +1467,15 @@ void vg_update(void)
                                                        }
                                                }
                                                else
-                                                       fish->state = world_check_pos_ok( fish->pos )? k_fish_state_bg: k_fish_state_dead;
+                                               {
+                                                       if( world_check_pos_ok( fish->pos ) )
+                                                               fish->state = k_fish_state_bg;
+                                                       else
+                                                       {
+                                                               fish->state = k_fish_state_dead;
+                                                               fish->death_time = world.sim_internal_time;     
+                                                       }
+                                               }
                                        }
                                        
                                        //v2i_add( fish->pos, fish->dir, fish->pos );
@@ -1420,7 +1485,10 @@ void vg_update(void)
                                        v2i_add( fish->pos, fish->dir, fish->pos );
                                        
                                        if( !world_check_pos_ok( fish->pos ) )
+                                       {
                                                fish->state = k_fish_state_dead;
+                                               fish->death_time = -1000.0f;
+                                       }
                                        else
                                        {
                                                struct cell *cell_entry = pcell( fish->pos );
@@ -1468,11 +1536,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] ];
                                                
@@ -1525,7 +1588,7 @@ void vg_update(void)
                                                                sw_set_achievement( "BANG" );
                                                        
                                                                // Shatter death (+0.5s)
-                                                               float death_time = collide_this_frame? 0.0f: 0.5f;
+                                                               float death_time = world.sim_internal_time + ( collide_this_frame? 0.0f: 0.5f );
                                                                
                                                                fi->state = k_fish_state_soon_dead;
                                                                fj->state = k_fish_state_soon_dead;
@@ -1552,11 +1615,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 );
                                                
@@ -1613,9 +1680,20 @@ void vg_update(void)
                                                vg_success( "Run passed, starting next\n" );
                                                world.sim_run ++;
                                                world.sim_frame = 0;
-                                               world.sim_start = vg_time;
+                                               world.sim_target = 0;
                                                world.num_fishes = 0;
                                                
+                                               // Reset timing reference points
+                                               world.sim_delta_ref = vg_time;
+                                               world.sim_internal_ref = 0.0f;
+                                               
+                                               if( world.buttons[ k_world_button_pause ].pressed )
+                                                       world.pause_offset_target = 0.5f;
+                                               else
+                                                       world.pause_offset_target = 0.0f;
+                                               
+                                               world.sim_internal_time = 0.0f;
+                                               
                                                for( int i = 0; i < world.w*world.h; i ++ )
                                                        world.data[ i ].state &= ~FLAG_FLIP_FLOP;
                                                
@@ -1658,9 +1736,7 @@ void vg_update(void)
                // Position update
                // =====================================================================================================
                
-               float scaled_time = 0.0f;
-               scaled_time = (vg_time-world.sim_start)*world.sim_speed;
-               world.frame_lerp = scaled_time - (float)world.sim_frame;
+               world.frame_lerp = world.sim_internal_time - floorf( world.sim_internal_time );
                
                for( int i = 0; i < world.num_fishes; i ++ )
                {
@@ -1669,7 +1745,7 @@ void vg_update(void)
                        if( fish->state == k_fish_state_dead )
                                continue;
                        
-                       if( fish->state == k_fish_state_soon_dead && (world.frame_lerp > fish->death_time) )
+                       if( fish->state == k_fish_state_soon_dead && (world.sim_internal_time > fish->death_time) )
                                continue; // Todo: particle thing?
                                
                        struct cell *cell = pcell(fish->pos);
@@ -1735,6 +1811,9 @@ void vg_update(void)
                                fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
                                fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
                        }
+                       
+                       v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
+                       v2_divs( fish->physics_v, world.sim_internal_delta, fish->physics_v );
                }
        }
 }
@@ -1814,6 +1893,13 @@ static void draw_numbers( v3f coord, int number )
 
 static void wbutton_run( enum e_world_button btn_name )
 {
+       static v3f button_colours[] = {
+               {0.204f, 0.345f, 0.553f},
+               {0.204f, 0.345f, 0.553f},
+               {0.741f, 0.513f, 0.078f},
+               {1.0f, 0.0f, 0.0f}
+       };
+
        struct cell_button *btn = &world.buttons[btn_name];
        
        // Interaction
@@ -1822,50 +1908,91 @@ static void wbutton_run( enum e_world_button btn_name )
        if( vg_get_button_up( "primary" ) && is_hovering )
        {
                // Click event
-               btn->pressed ^= 0x1;
-               
                if( btn_name == k_world_button_sim )
                {
-                       if( btn->pressed )
+                       if( world.buttons[ k_world_button_pause ].pressed )
                        {
-                               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_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;
-                               
-                               io_reset();
+                               if( !btn->pressed )
+                               {
+                                       btn->pressed = 1;
+                                       simulation_start();
+                                       
+                                       world.pause_offset_target = 0.5f;
+                               }
+                               else
+                                       world.pause_offset_target += 1.0f;
                        }
                        else
                        {
-                               simulation_stop();
+                               btn->pressed ^= 0x1;
+                               
+                               if( btn->pressed )
+                                       simulation_start();
+                               else
+                                       simulation_stop();
                        }
                }
+               else if( btn_name == k_world_button_pause )
+               {
+                       btn->pressed ^= 0x1;
+               
+                       world.sim_internal_ref = world.sim_internal_time;
+                       world.sim_delta_ref = vg_time;
+                       
+                       if( btn->pressed )
+                       {
+                               float time_frac = world.sim_internal_time-floorf(world.sim_internal_time);
+                               world.pause_offset_target = 0.5f - time_frac;
+                       }
+                       else
+                               world.pause_offset_target = 0.0f;
+               }
+               else
+               {
+                       btn->pressed ^= 0x1;
+               }
+               
+               sfx_set_play( &audio_clicks, &audio_system_ui, btn->pressed?1:0 );
        }
        
        // Drawing
-       if( btn->pressed ) btn->light_target = is_hovering? 0.9f: 0.8f;
-       else btn->light_target = is_hovering? 0.2f: 0.0f;
+       if( btn->pressed ) 
+       {
+               if( is_hovering )
+               {
+                       btn->light_target = 0.9f;
+               }
+               else
+               {
+                       if( btn_name == k_world_button_sim && world.buttons[ k_world_button_pause ].pressed )
+                               btn->light_target = fabsf(sinf( vg_time * 2.0f )) * 0.3f + 0.3f;
+                       else
+                               btn->light_target = 0.8f;
+               }
+       }
+       else 
+       {
+               btn->light_target = is_hovering? 0.2f: 0.0f;
+       }
 
        if( vg_get_button( "primary" ) && is_hovering )
                btn->light_target = 1.0f;
-
+       
        btn->light = vg_lerpf( btn->light, btn->light_target, vg_time_delta*26.0f );
        
+       // Draw
+       
+       v4f final_colour;
+       v3_copy( button_colours[ btn_name ], final_colour );
+       final_colour[3] = btn->light;
+       
        glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ), 
                world.w-1, 
                world.h-btn_name-2, 
                (float)btn_name, 
                3.0f 
        );
-       glUniform4f( SHADER_UNIFORM( shader_buttons, "uColour" ), 0.204f, 0.345f, 0.553f, btn->light );
+       glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
        
        draw_mesh( 0, 2 );
 }
@@ -1881,6 +2008,11 @@ 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 = 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;
        
        // BACKGROUND
        // ========================================================================================================
@@ -1940,18 +2072,34 @@ void vg_render(void)
                for( int i = 0; i < world.num_fishes; i ++ )
                {
                        struct fish *fish = &world.fishes[i];
+                       v3f render_pos;
+                       render_pos[2] = 1.0f;
                        
-                       if( fish->state == k_fish_state_dead || fish->state == k_fish_state_bg )
+                       if( fish->state == k_fish_state_dead || fish->state == k_fish_state_soon_dead )
+                       {
+                               float death_anim_time = world.sim_internal_time - fish->death_time;
+                               
+                               // Death animation
+                               if( death_anim_time > 0.0f && death_anim_time < 1.0f )
+                               {
+                                       float amt = 1.0f-death_anim_time*death_anim_time;
+                               
+                                       v2_muladds( fish->physics_co, fish->physics_v, -1.0f * world.sim_internal_delta * amt, fish->physics_co );
+                                       render_pos[2] = amt;
+                               }
+                               else if( world.sim_internal_time > fish->death_time )
+                                       continue;
+                       }
+                       else if( fish->state == k_fish_state_bg )
                                continue;
                        
-                       if( fish->state == k_fish_state_soon_dead && (world.frame_lerp > fish->death_time) )
-                               continue;
+                       v2_copy( fish->physics_co, render_pos );
                        
                        v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
                        colour_code_v3( fish->payload, dot_colour );
                        
                        glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
-                       glUniform2fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, fish->physics_co );
+                       glUniform3fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, render_pos );
                        glUniform2f( SHADER_UNIFORM( shader_ball, "uTexOffset" ), (float)i * 1.2334, (float)i * -0.3579f );
                        draw_mesh( 0, 2 );
                }
@@ -2047,29 +2195,38 @@ void vg_render(void)
        glUniform1i( SHADER_UNIFORM( shader_buttons, "uTexMain" ), 0 );
        
        wbutton_run( k_world_button_sim );
-       wbutton_run( k_world_button_ff );
+       wbutton_run( k_world_button_pause );
+       //wbutton_run( k_world_button_wire_mode );
        
        // 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 ++ )
        {
@@ -2081,6 +2238,8 @@ void vg_render(void)
                        {
                                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;
@@ -2092,32 +2251,96 @@ void vg_render(void)
                                        startpoint[0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
                                        startpoint[1] = (float)y2 + 0.25f;
                                        
-                                       endpoint[0] = x+0.5f;
-                                       endpoint[1] = y+0.5f;
+                                       endpoint[0] = x;
+                                       endpoint[1] = y;
                                        
+                                       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 );
+       
+       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});
 
-       int const circle_base = 4;
-       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( 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 );        
+                                       }
+                               }
+                       }
+               }
+       }
        
-       glEnable(GL_BLEND);
+       // 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 ++ )
        {
@@ -2134,9 +2357,35 @@ void vg_render(void)
                                                (arr_base + (float)term->pos[1] + (float)(term->run_count-1)*0.2f) - run_offset:
                                                (float)term->pos[1] + arr_base + run_offset;
                        
-                       if( k & 0x1 )
+                       v4f bar_colour;
+                       int bar_draw = 0;
+                       
+                       if( is_simulation_running() )
+                       {
+                               if( k == world.sim_run )
+                               {
+                                       float a = fabsf(sinf( vg_time * 2.0f )) * 0.075f + 0.075f;
+                                       
+                                       v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, a }, bar_colour );
+                               }
+                               else
+                                       v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
+                               
+                               bar_draw = 1;
+                       }
+                       else if( 1 || k & 0x1 )
+                       {
+                               if( k & 0x1 )
+                                       v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, 0.07f }, bar_colour );
+                               else
+                                       v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );                        
+                                                       
+                               bar_draw = 1;
+                       }
+                       
+                       if( bar_draw )
                        {
-                               glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1.0f, 1.0f, 1.0f, 0.1f );
+                               glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, bar_colour );
                                glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)term->pos[0], y_position - 0.1f, 1.0f );
                                draw_mesh( 2, 2 );
                        }
@@ -2151,14 +2400,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
                                {
@@ -2311,14 +2564,16 @@ void vg_ui(void)
                        
                        for( int i = 0; i < 3; i ++ )
                        {
-                               if( i == pack_selection )
+                               int pack_is_unlocked = pack_infos[i].levels[0].unlocked;
+                       
+                               if( i == pack_selection || !pack_is_unlocked )
                                        gui_override_colours( &flcol_list_locked );
-
-                               if( gui_button( 2000 + i ) == k_button_click )
+                               
+                               if( gui_button( 2000 + i ) == k_button_click && pack_is_unlocked )
                                        pack_selection = i;
                                
                                ui_global_ctx.cursor[1] += 2;
-                               gui_text( pack_infos[i].name, 4, 0 );
+                               gui_text( pack_is_unlocked? pack_infos[i].name: "???", 4, 0 );
                                gui_end_right();
                                
                                gui_reset_colours();
@@ -2783,6 +3038,9 @@ 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
@@ -2811,15 +3069,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 ++ )