1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
3 #define MARBLE_COMP_VERSION 4
5 #define VG_CAPTURE_MODE
8 #define VG_STEAM_APPID 1218140U
9 #define VG_FRAMEBUFFER_RESIZE 1
12 enum world_button_mode
14 k_world_button_mode_once
,
15 k_world_button_mode_toggle
22 float light_target
, light
, extra_light
;
25 enum world_button_mode mode
;
28 enum world_button_status
30 k_world_button_on_enable
,
31 k_world_button_on_disable
34 #include "fishladder_resources.h"
37 #define STEAM_LEADERBOARDS
43 k_cell_type_ramp_right
= 3,
44 k_cell_type_ramp_left
= 6,
45 k_cell_type_split
= 7,
46 k_cell_type_merge
= 13,
47 k_cell_type_con_r
= 1,
48 k_cell_type_con_u
= 2,
49 k_cell_type_con_l
= 4,
55 k_fish_state_soon_dead
= -1,
56 k_fish_state_dead
= 0,
59 k_fish_state_soon_alive
64 k_world_button_none
= -1,
65 k_world_button_sim
= 0,
66 k_world_button_pause
= 1,
67 k_world_button_speedy
= 2,
68 k_world_button_settings
= 3
74 k_game_state_settings
,
78 #define FLAG_CANAL 0x1
79 #define FLAG_IS_TRIGGER 0x2
80 #define FLAG_RESERVED0 0x4
81 #define FLAG_RESERVED1 0x8
83 #define FLAG_4B_GROUP (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1)
85 #define FLAG_INPUT 0x10
86 #define FLAG_OUTPUT 0x20
87 #define FLAG_WALL 0x40
88 #define FLAG_EMITTER 0x80
90 #define FLAG_FLIP_FLOP 0x100
91 #define FLAG_TRIGGERED 0x200
92 #define FLAG_FLIP_ROTATING 0x400
93 #define FLAG_TARGETED 0x800
95 #define FLAG_INPUT_NICE 0x1000
98 0000 0 | 0001 1 | 0010 2 | 0011 3
102 0100 4 | 0101 5 | 0110 6 | 0111 7
106 1000 8 | 1001 9 | 1010 10 | 1011 11
110 1100 12 | 1101 13 | 1110 14 | 1111 15
116 static struct cell_description
125 enum sprites_auto_combine_index trigger_sprite
;
127 cell_descriptions
[] =
130 { .trigger_pos
= { 0.5f
, 0.25f
}, .trigger_sprite
= k_sprite_brk_d
},
131 { .start
= { 1, 0 }, .end
= { -1, 0 }, .trigger_pos
= { 0.5f
, 0.25f
},
132 .trigger_sprite
= k_sprite_brk_d
},
133 { .start
= { 0, 1 }, .end
= { 0, -1 }, .trigger_pos
= { 0.25f
, 0.5f
},
134 .trigger_sprite
= k_sprite_brk_l
},
135 { .start
= { 0, 1 }, .end
= { 1, 0 }, .trigger_pos
= { 0.25f
, 0.5f
},
136 .trigger_sprite
= k_sprite_brk_l
},
138 { .start
= { -1, 0 }, .end
= { 1, 0 }, .trigger_pos
= { 0.5f
, 0.25f
},
139 .trigger_sprite
= k_sprite_brk_d
},
140 { .start
= { -1, 0 }, .end
= { 1, 0 }, .trigger_pos
= { 0.5f
, 0.25f
},
141 .trigger_sprite
= k_sprite_brk_d
, .is_linear
= 1 },
142 { .start
= { 0, 1 }, .end
= { -1, 0 }, .trigger_pos
= { 0.5f
, 0.25f
},
143 .trigger_sprite
= k_sprite_brk_d
},
144 { .start
= { 0, 1 }, .is_special
= 1 },
146 { .start
= { 0, -1 }, .end
= { 0, 1 }, .trigger_pos
= { 0.25f
, 0.5f
},
147 .trigger_sprite
= k_sprite_brk_l
},
148 { .start
= { 1, 0 }, .end
= { 0, -1 }, .trigger_pos
= { 0.25f
, 0.5f
},
149 .trigger_sprite
= k_sprite_brk_l
},
150 { .start
= { 0, 1 }, .end
= { 0, -1 }, .trigger_pos
= { 0.25f
, 0.5f
},
151 .trigger_sprite
= k_sprite_brk_l
, .is_linear
= 1 },
154 { .start
= { -1, 0 }, .end
= { 0, -1 }, .trigger_pos
= { 0.5f
, 0.75f
},
155 .trigger_sprite
= k_sprite_brk_u
},
156 { .end
= { 0, -1 }, .is_special
= 1, .trigger_pos
= { 0.5f
, 0.75f
},
157 .trigger_sprite
= k_sprite_brk_u
},
162 v2f
const curve_3
[] = {{0.5f
,1.0f
},{0.5f
,0.625f
},{0.625f
,0.5f
},{1.0f
,0.5f
}};
163 v2f
const curve_6
[] = {{0.5f
,1.0f
},{0.5f
,0.625f
},{0.375f
,0.5f
},{0.0f
,0.5f
}};
164 v2f
const curve_9
[] = {{1.0f
,0.5f
},{0.625f
,0.5f
},{0.5f
,0.375f
},{0.5f
,0.0f
}};
165 v2f
const curve_12
[]= {{0.0f
,0.5f
},{0.375f
,0.5f
},{0.5f
,0.375f
},{0.5f
,0.0f
}};
167 v2f
const curve_1
[] = {{1.0f
,0.5f
},{0.8f
,0.5f
},{0.3f
,0.5f
},{0.2f
,0.5f
}};
168 v2f
const curve_4
[] = {{0.0f
,0.5f
},{0.3f
,0.5f
},{0.5f
,0.5f
},{0.8f
,0.5f
}};
169 v2f
const curve_2
[] = {{0.5f
,1.0f
},{0.5f
,0.8f
},{0.5f
,0.3f
},{0.5f
,0.2f
}};
170 v2f
const curve_8
[] = {{0.5f
,0.0f
},{0.5f
,0.3f
},{0.5f
,0.5f
},{0.5f
,0.8f
}};
172 v2f
const curve_7
[] =
173 {{0.5f
,0.8438f
},{0.875f
,0.8438f
},{0.625f
,0.5f
},{1.0f
,0.5f
}};
174 v2f
const curve_7_1
[] =
175 {{0.5f
,0.8438f
},{1.0f
-0.875f
,0.8438f
},{1.0-0.625f
,0.5f
},{0.0f
,0.5f
}};
177 float const curve_7_linear_section
= 0.1562f
;
185 #define EFFECT_BUFFER_RATIO 4
189 /* Things that are 'static', aka, initialized once */
192 struct world_button buttons
[4];
194 enum e_game_state state
;
196 struct cmp_level
*lvl_to_load
;
199 float world_transition
;
204 bloomframebuffer
[2], /* Quater res */
205 bloomcolourbuffer
[2];
225 *cmd_buf_tiles
, *cmd_buf_specials
;
227 u32 tile_count
, tile_special_count
, max_commands
;
230 int sim_run
, max_runs
;
232 int sim_frame
, sim_target
;
233 float sim_internal_time
,
248 int step_count
, recv_count
;
262 GLuint vao
, vbo
, ebo
;
267 GLuint background_data
;
268 GLuint random_samples
;
270 int selected
, tile_x
, tile_y
;
277 enum e_fish_state state
;
289 struct cmp_level
*pCmpLevel
;
303 .buttons
= { { .mode
= k_world_button_mode_toggle
},
304 { .mode
= k_world_button_mode_toggle
},
305 { .mode
= k_world_button_mode_toggle
},
306 { .mode
= k_world_button_mode_toggle
} }
311 static void colour_code_v3( i8 cc
, v3f target
);
312 static int hash21i( v2i p
, u32 umod
);
317 static void init_mesh( struct mesh
*m
, float const *tris
, u32 length
);
318 static void free_mesh( struct mesh
*m
);
319 static void use_mesh( struct mesh
*m
);
320 static void draw_mesh( int const start
, int const count
);
325 static void level_selection_buttons(void);
328 * Map/world interface
330 static void map_free(void);
331 static void io_reset(void);
332 static struct cell
*pcell( v2i pos
);
333 static void lcell( int id
, v2i pos
);
334 static void map_reclassify( v2i start
, v2i end
, int update_texbuffer
);
335 static void gen_level_text(void);
336 static int map_load( const char *str
, const char *name
);
337 static void map_serialize( FILE *stream
);
342 static void career_serialize(void);
343 static void career_unlock_level( struct cmp_level
*lvl
);
344 static void career_unlock_level( struct cmp_level
*lvl
);
345 static void career_pass_level( struct cmp_level
*lvl
, int score
, int upload
);
346 static void career_reset_level( struct cmp_level
*lvl
);
347 static void career_load(void);
348 static void clear_animation_flags(void);
353 static void simulation_stop(void);
354 static void simulation_start(void);
355 static int world_check_pos_ok( v2i co
, int dist
);
356 static int cell_interactive( v2i co
);
358 static void render_tiles( v4f
const regular_colour
,
359 v4f
const selected_colour
, int with_glow
);
360 static void render_tile_block( v2i start
, v2i end
, v4f
const regular_colour
,
361 v4f
const selected_colour
);
363 #ifdef STEAM_LEADERBOARDS
364 void leaderboard_set_score( struct cmp_level
*cmp_level
, u32 score
);
365 void leaderboard_dispatch_score(void);
366 void leaderboard_found( LeaderboardFindResult_t
*pCallback
);
367 void leaderboard_downloaded( LeaderboardScoresDownloaded_t
*pCallback
);
368 void leaderboard_set_score( struct cmp_level
*cmp_level
, u32 score
);
371 static int console_credits( int argc
, char const *argv
[] );
372 static int console_save_map( int argc
, char const *argv
[] );
373 static int console_load_map( int argc
, char const *argv
[] );
374 static int console_changelevel( int argc
, char const *argv
[] );
376 void vg_render(void);
378 void vg_update(void);
381 int main( int argc
, char *argv
[] );
390 static int colour_set_id
= 0;
391 static int world_theme_id
= 0;
392 static int enable_bloom
= 1;
393 static int enable_vignette
= 1;
394 static float music_volume
= 1.0f
;
396 static void music_volume_update(void)
398 sfx_vol_fset( &audio_volume_music
, music_volume
);
401 static v3f colour_sets
[][4] =
403 { { 1.0f
, 0.9f
, 0.3f
},
404 { 0.4f
, 0.8f
, 1.00f
},
405 { 0.2f
, 0.9f
, 0.14f
},
406 { 0.882f
, 0.204f
, 0.922f
}
408 { { 1.0f
, 0.9f
, 0.3f
},
409 { 0.4f
, 0.8f
, 1.00f
},
410 { 0.85f
, 0.85f
, 0.85f
},
413 { { 1.0f
, 0.9f
, 0.3f
},
414 { 0.827f
, 0.373f
, 0.718f
},
415 { 0.0f
, 0.353f
, 0.71f
},
416 { 0.863f
, 0.196f
, 0.125f
}
420 static struct world_theme
431 { 0.6f
, 0.6f
, 0.6f
},
436 { 0.89f
, 0.8f
, 0.7f
},
441 { 0.7f
, 0.7f
, 0.7f
},
446 static void colour_code_v3( i8 cc
, v3f target
)
448 if( (cc
>= 0) && (cc
< vg_list_size( colour_sets
[0] )) )
450 v3_copy( colour_sets
[colour_set_id
][ cc
], target
);
454 vg_error( "Invalid colour code used '%d'\n", (int)cc
);
455 v3_copy( (v3f
){0.0f
,0.0f
,0.0f
}, target
);
458 static int hash21i( v2i p
, u32 umod
)
460 static const int random_noise
[] = {
461 0x46,0xD5,0xB8,0xD3,0xF2,0xE5,0xCC,0x07,0xD0,0xB3,0x7A,0xA2,0xC3,0xDA,0xDC,0x7F,
462 0xE0,0xB7,0x42,0xA0,0xBF,0x41,0x92,0x32,0x6F,0x0D,0x45,0xC7,0x54,0xDB,0x30,0xC2,
463 0xD5,0xDA,0x55,0x09,0xDE,0x74,0x48,0x20,0xE1,0x24,0x5C,0x4D,0x6F,0x36,0xD8,0xE9,
464 0x8D,0x8F,0x54,0x99,0x98,0x51,0xFE,0xDB,0x26,0x04,0x65,0x57,0x56,0xF3,0x53,0x30,
465 0x3D,0x16,0xC0,0xB6,0xF2,0x47,0xCF,0x62,0xB0,0x6C,0x8F,0x4F,0x8C,0x4C,0x17,0xF0,
466 0x19,0x7E,0x2D,0x81,0x8D,0xFB,0x10,0xD3,0x49,0x50,0x60,0xFD,0x38,0x15,0x3B,0xEE,
467 0x05,0xC1,0xCF,0x62,0x97,0x75,0xDF,0x4E,0x4D,0x89,0x5E,0x88,0x5C,0x30,0x8C,0x54,
468 0x1D,0x39,0x41,0xEA,0xA2,0x63,0x12,0x1B,0x8E,0x35,0x22,0x9B,0x98,0xA3,0x7F,0x80,
469 0xD6,0x27,0x94,0x66,0xB5,0x1D,0x7E,0xDF,0x96,0x28,0x38,0x3A,0xA0,0xE8,0x71,0x09,
470 0x62,0x5E,0x9D,0x53,0x58,0x1B,0x7D,0x0D,0x2D,0x99,0x77,0x83,0xC3,0x89,0xC2,0xA2,
471 0xA7,0x1D,0x78,0x80,0x37,0xC1,0x87,0xFF,0x65,0xBF,0x2C,0xF1,0xE5,0xB3,0x09,0xE0,
472 0x25,0x92,0x83,0x0F,0x8A,0x57,0x3C,0x0B,0xC6,0xBC,0x44,0x16,0xE3,0xCE,0xC3,0x0D,
473 0x69,0xD3,0xC6,0x99,0xB8,0x46,0x44,0xC4,0xF3,0x1E,0xBF,0xF5,0xB4,0xDB,0xFB,0x93,
474 0xA1,0x7B,0xC9,0x08,0x77,0x22,0xE5,0x02,0xEF,0x9E,0x90,0x94,0x8A,0xA6,0x3D,0x7E,
475 0xA2,0xA0,0x10,0x82,0x47,0x5C,0xAA,0xF8,0x2F,0x0D,0x9F,0x76,0xDA,0x99,0x0F,0xCB,
476 0xE2,0x02,0x0C,0x75,0xCA,0x35,0x29,0xA6,0x49,0x83,0x6D,0x91,0xB4,0xEC,0x31,0x69,
477 0xBA,0x13,0xF3,0xC7,0x21,0x06,0xC8,0x79,0xEF,0xB1,0x9C,0x6A,0xEE,0x64,0x9A,0xDC,
478 0x1E,0xC6,0x18,0x93,0xA9,0x7E,0x89,0x7D,0x96,0xE5,0x44,0xB8,0x00,0x15,0xAF,0x8C,
479 0x78,0x8F,0xA8,0x05,0xA7,0x07,0x25,0x9A,0xC8,0x5D,0x90,0x1A,0x41,0x53,0x30,0xD3,
480 0x24,0x33,0x71,0xB4,0x50,0x6E,0xE4,0xEA,0x0D,0x2B,0x6D,0xF5,0x17,0x08,0x74,0x49,
481 0x71,0xC2,0xAC,0xF7,0xDC,0xB2,0x7E,0xCC,0xB6,0x1B,0xB8,0xA9,0x52,0xCF,0x6B,0x51,
482 0xD2,0x4E,0xC9,0x43,0xEE,0x2E,0x92,0x24,0xBB,0x47,0x4D,0x0C,0x3E,0x21,0x53,0x19,
483 0xD4,0x82,0xE2,0xC6,0x93,0x85,0x0A,0xF8,0xFA,0x04,0x07,0xD3,0x1D,0xEC,0x03,0x66,
484 0xFD,0xB1,0xFB,0x8F,0xC5,0xDE,0xE8,0x29,0xDF,0x23,0x09,0x9D,0x7C,0x43,0x3D,0x4D,
485 0x89,0xB9,0x6F,0xB4,0x6B,0x4A,0x51,0xC3,0x94,0xF4,0x7C,0x5E,0x19,0x87,0x79,0xC1,
486 0x80,0x0C,0x45,0x12,0xEC,0x95,0xF3,0x31,0x68,0x42,0xE1,0x06,0x57,0x0E,0xA7,0xFB,
487 0x78,0x96,0x87,0x23,0xA5,0x20,0x7A,0x09,0x3A,0x45,0xE6,0xD9,0x5E,0x6A,0xD6,0xAA,
488 0x29,0x50,0x92,0x4E,0xD0,0xB5,0x91,0xC2,0x9A,0xCF,0x07,0xFE,0xB2,0x15,0xEB,0xE4,
489 0x84,0x40,0x14,0x47,0xFA,0x93,0xB9,0x06,0x69,0xDB,0xBD,0x4E,0xEA,0x52,0x9B,0xDE,
490 0x5B,0x50,0x36,0xAB,0xB3,0x1F,0xD2,0xCD,0x9C,0x13,0x07,0x7E,0x8B,0xED,0x72,0x62,
491 0x74,0x77,0x3B,0x88,0xAC,0x5B,0x6A,0xBC,0xDA,0x99,0xE8,0x24,0x90,0x5A,0xCA,0x8D,
492 0x5C,0x2B,0xF8,0xF1,0xE1,0x1D,0x94,0x11,0xEA,0xCC,0x02,0x09,0x1E,0xA2,0x48,0x67,
493 0x87,0x5A,0x7E,0xC6,0xCC,0xA3,0xFB,0xC5,0x36,0xEB,0x5C,0xE1,0xAF,0x1E,0xBE,0xE7,
494 0xD8,0x8F,0x70,0xAE,0x42,0x05,0xF5,0xCD,0x2D,0xA2,0xB0,0xFD,0xEF,0x65,0x2C,0x22,
495 0xCB,0x8C,0x8B,0xAA,0x3D,0x86,0xE2,0xCD,0xBE,0xC3,0x42,0x38,0xE3,0x9C,0x08,0xB5,
496 0xAE,0xBD,0x54,0x73,0x83,0x70,0x24,0x47,0xCA,0x4C,0x04,0xC4,0xE0,0x1D,0x40,0xED,
497 0xF4,0x2B,0x50,0x8E,0x97,0xB3,0xF0,0xA6,0x76,0xDB,0x49,0x30,0xE5,0xD9,0x71,0x07,
498 0xB2,0xF1,0x0F,0xD6,0x77,0xAA,0x72,0xC0,0xAF,0x66,0xD8,0x40,0xC6,0x08,0x19,0x8C,
499 0xD9,0x8F,0x5A,0x75,0xAC,0xBE,0xC2,0x40,0x5B,0xBD,0x0D,0x1D,0x00,0xAF,0x26,0x5E,
500 0x78,0x43,0xAA,0xC6,0x4F,0xF3,0xD8,0xE2,0x7F,0x0C,0x1E,0x77,0x4D,0x35,0x96,0x23,
501 0x32,0x44,0x03,0x8D,0x92,0xE7,0xFD,0x48,0x07,0xD0,0x58,0xFC,0x6D,0xC9,0x91,0x33,
502 0xF0,0x23,0x45,0xA4,0x29,0xB9,0xF5,0xB0,0x68,0x8F,0x7B,0x59,0x15,0x8E,0xA6,0x66,
503 0x15,0xA0,0x76,0x9B,0x69,0xCB,0x38,0xA5,0xF4,0xB4,0x6B,0xDC,0x1F,0xAB,0xAE,0x12,
504 0x77,0xC0,0x8C,0x4A,0x03,0xB9,0x73,0xD3,0x6D,0x52,0xC5,0xF5,0x6E,0x4E,0x4B,0xA3,
505 0x24,0x02,0x58,0xEE,0x5F,0xF9,0xD6,0xD0,0x1D,0xBC,0xF4,0xB8,0x4F,0xFD,0x4B,0x2D,
506 0x34,0x77,0x46,0xE5,0xD4,0x33,0x7B,0x9C,0x35,0xCD,0xB0,0x5D,0x06,0x39,0x99,0xEB,
507 0x0C,0xD0,0x0F,0xF7,0x92,0xB5,0x58,0x5B,0x5E,0x79,0x12,0xF4,0x05,0xF6,0x21,0x07,
508 0x0B,0x49,0x1A,0xFB,0xD4,0x98,0xC4,0xEF,0x7A,0xD6,0xCA,0xA1,0xDA,0xB3,0x51,0x00,
509 0x76,0xEC,0x08,0x48,0x40,0x35,0xD7,0x94,0xBE,0xF5,0x7B,0xA4,0x20,0x81,0x5F,0x82,
510 0xF3,0x6F,0x96,0x24,0x98,0xB6,0x49,0x18,0xC8,0xC5,0x8C,0xD2,0x38,0x7F,0xC4,0xF6,
511 0xAA,0x87,0xDC,0x73,0x5B,0xA1,0xAF,0xE5,0x3D,0x37,0x6B,0x85,0xED,0x38,0x62,0x7D,
512 0x57,0xBD,0xCF,0xB5,0x1B,0xA8,0xBB,0x32,0x33,0xD3,0x34,0x5A,0xC1,0x5D,0xFB,0x28,
513 0x6E,0xE1,0x67,0x51,0xBB,0x31,0x92,0x83,0xAC,0xAA,0x72,0x52,0xFD,0x13,0x4F,0x73,
514 0xD3,0xF0,0x5E,0xFC,0xBA,0xB1,0x3C,0x7B,0x08,0x76,0x03,0x38,0x1E,0xD1,0xCC,0x33,
515 0xA3,0x1E,0xFC,0xE0,0x82,0x30,0x27,0x93,0x71,0x35,0x75,0x77,0xBA,0x78,0x10,0x33,
516 0xCD,0xAB,0xCF,0x8E,0xAD,0xF9,0x32,0xC9,0x15,0x9F,0xD6,0x6D,0xA8,0xAE,0xB1,0x3F,
517 0x90,0xEB,0xD4,0xF9,0x31,0x81,0xA3,0x53,0x99,0x4B,0x3C,0x93,0x3B,0xFE,0x55,0xFF,
518 0x25,0x9F,0xCC,0x07,0xC5,0x2C,0x14,0xA7,0xA4,0x1E,0x6C,0xB6,0x91,0x2A,0xE0,0x3E,
519 0x7F,0x39,0x0A,0xD9,0x24,0x3C,0x01,0xA0,0x30,0x99,0x8E,0xB8,0x1D,0xF9,0xA7,0x78,
520 0x86,0x95,0x35,0x0E,0x21,0xDA,0x7A,0x7B,0xAD,0x9F,0x4E,0xF6,0x63,0x5B,0x96,0xBB,
521 0x87,0x36,0x3F,0xA7,0x1A,0x66,0x91,0xCD,0xB0,0x3B,0xC0,0x4F,0x54,0xD2,0x5F,0xBB,
522 0x38,0x89,0x1C,0x79,0x7E,0xA2,0x02,0xE4,0x80,0x84,0x1E,0x33,0xAB,0x74,0xFA,0xBE,
523 0x31,0x46,0x2E,0xC5,0x15,0xB9,0x12,0xE9,0xD3,0x73,0x43,0xEA,0x74,0x11,0xA7,0xC0,
524 0xD5,0xD8,0x39,0x08,0x9F,0x4F,0xC7,0x71,0x25,0x09,0x51,0x65,0xD6,0xA8,0x02,0x1F
527 return random_noise
[ (random_noise
[p
[1] & 1023] + p
[0]) & 1023 ] & umod
;
530 static void init_mesh( struct mesh
*m
, float const *tris
, u32 length
)
532 m
->elements
= length
/3;
533 glGenVertexArrays( 1, &m
->vao
);
534 glGenBuffers( 1, &m
->vbo
);
536 glBindVertexArray( m
->vao
);
537 glBindBuffer( GL_ARRAY_BUFFER
, m
->vbo
);
538 glBufferData( GL_ARRAY_BUFFER
, length
*sizeof(float), tris
, GL_STATIC_DRAW
);
540 glVertexAttribPointer( 0, 2, GL_FLOAT
, GL_FALSE
, 2*sizeof(float), (void*)0 );
541 glEnableVertexAttribArray( 0 );
546 static void free_mesh( struct mesh
*m
)
548 glDeleteVertexArrays( 1, &m
->vao
);
549 glDeleteBuffers( 1, &m
->vbo
);
552 static void draw_mesh( int const start
, int const count
)
554 glDrawArrays( GL_TRIANGLES
, start
*3, count
*3 );
557 static void use_mesh( struct mesh
*m
)
559 glBindVertexArray( m
->vao
);
562 void leaderboard_set_score( struct cmp_level
*cmp_level
, u32 score
);
564 static void map_free(void)
566 arrfree( world
.data
);
569 free( world
.cmd_buf_tiles
);
570 world
.cmd_buf_tiles
= NULL
;
580 world
.initialzed
= 0;
583 static void io_reset(void)
585 for( int i
= 0; i
< arrlen( world
.io
); i
++ )
587 struct cell_terminal
*term
= &world
.io
[i
];
589 for( int j
= 0; j
< term
->run_count
; j
++ )
590 term
->runs
[j
].recv_count
= 0;
594 static struct cell
*pcell( v2i pos
)
596 return &world
.data
[ pos
[1]*world
.w
+ pos
[0] ];
599 static void lcell( int id
, v2i pos
)
601 pos
[0] = id
% world
.w
;
602 pos
[1] = (id
- pos
[0]) / world
.w
;
605 static void map_reclassify( v2i start
, v2i end
, int update_texbuffer
)
607 v2i full_start
= { 1,1 };
608 v2i full_end
= { world
.w
-1, world
.h
-1 };
617 u8 info_buffer
[64*64*4];
620 int px0
= vg_max( start
[0], full_start
[0] ),
621 px1
= vg_min( end
[0], full_end
[0] ),
622 py0
= vg_max( start
[1], full_start
[1] ),
623 py1
= vg_min( end
[1], full_end
[1] );
625 for( int y
= py0
; y
< py1
; y
++ )
627 for( int x
= px0
; x
< px1
; x
++ )
629 struct cell
*cell
= pcell((v2i
){x
,y
});
631 v2i dirs
[] = {{1,0},{0,1},{-1,0},{0,-1}};
636 if( cell
->state
& (FLAG_CANAL
|FLAG_INPUT
|FLAG_OUTPUT
|FLAG_EMITTER
) )
638 for( int i
= 0; i
< vg_list_size( dirs
); i
++ )
640 struct cell
*neighbour
=
641 pcell((v2i
){ x
+dirs
[i
][0], y
+dirs
[i
][1] });
643 if( neighbour
->state
&
644 (FLAG_CANAL
|FLAG_INPUT
|FLAG_OUTPUT
|FLAG_EMITTER
) )
654 if( cell
->state
& FLAG_WALL
)
655 height
= 0xFF; /*-0x3F + hash21i( (v2i){x,y}, 0x3F );*/
657 config
= cell
->state
& FLAG_INPUT_NICE
? 0xB: 0xF;
660 pcell((v2i
){x
,y
})->config
= config
;
662 u8
*info_px
= &info_buffer
[ (pixel_id
++)*4 ];
664 info_px
[1] = cell
->state
& FLAG_WALL
? 0: 255;
669 * Detecting hanging links that should be removed
671 int is_trigger
= cell
->state
& FLAG_IS_TRIGGER
;
672 int trigger_invalid
= cell
->config
== 0xF ||
673 cell
->config
== k_cell_type_split
;
674 int is_targeted
= cell
->state
& FLAG_TARGETED
;
675 int target_invalid
= (cell
->config
!= k_cell_type_split
) &&
676 !(cell
->state
& FLAG_EMITTER
);
679 (is_trigger
&& trigger_invalid
) ||
680 (is_targeted
&& target_invalid
)
681 ) && update_texbuffer
)
683 cell
->state
&= ~(FLAG_TARGETED
|FLAG_IS_TRIGGER
);
684 for( u32 i
= 0; i
< 2; i
++ )
688 struct cell
*other_ptr
= &world
.data
[ cell
->links
[i
] ];
689 other_ptr
->links
[ i
] = 0;
690 other_ptr
->state
&= ~FLAG_IS_TRIGGER
;
692 if( other_ptr
->links
[ i
^ 0x1 ] == 0 )
693 other_ptr
->state
&= ~FLAG_TARGETED
;
703 if( update_texbuffer
)
705 glBindTexture( GL_TEXTURE_2D
, world
.background_data
);
706 glTexSubImage2D( GL_TEXTURE_2D
, 0,
708 px1
-px0
, py1
-py0
, GL_RGBA
, GL_UNSIGNED_BYTE
, info_buffer
);
712 static void gen_level_text(void)
714 ui_px
const unit_scale_px
= 4*UI_GLYPH_SPACING_X
;
715 ui_begin( &world
.st
.world_text
, world
.w
*unit_scale_px
,
716 world
.h
*unit_scale_px
);
718 if( world
.pCmpLevel
)
720 for( int i
= 0; i
< vg_list_size( world
.pCmpLevel
->strings
); i
++ )
722 struct world_string
*wstr
= &world
.pCmpLevel
->strings
[i
];
728 pos
[0] = -UI_GLYPH_SPACING_X
/2;
730 if( wstr
->placement
== k_placement_bottom
)
731 pos
[1] = 2*-unit_scale_px
;
733 pos
[1] = (world
.h
-1)*-unit_scale_px
-6;
735 ui_text( &world
.st
.world_text
, pos
, wstr
->str
,
736 1, k_text_align_left
);
741 // re-create level scores
742 for( int i
= 0; i
< vg_list_size( career_packs
); i
++ )
744 struct career_level_pack
*set
= &career_packs
[i
];
747 ui_text( &world.st.world_text,
749 set->origin[0]*unit_scale_px,
750 -(set->origin[1]+set->dims[1]+1)*unit_scale_px + 18
752 set->title, 1, k_text_align_left );
755 for( int j
= 0; j
< set
->count
; j
++ )
757 struct cmp_level
*lvl
= &set
->pack
[j
];
759 if( lvl
->completed_score
&& !lvl
->is_tutorial
)
762 snprintf( num
, 9, "%d", lvl
->completed_score
);
764 ui_text( &world
.st
.world_text
,
766 lvl
->btn
.position
[0]*unit_scale_px
+ unit_scale_px
/2,
767 -lvl
->btn
.position
[1]*unit_scale_px
- unit_scale_px
/2
769 num
, 1, k_text_align_center
);
774 ui_resolve( &world
.st
.world_text
);
777 /* Usually for ignoring windows crap */
778 static int map_load_char_ignore( char c
)
780 if( c
== '\r' ) return 1;
784 static int map_load_sequence_char( struct terminal_run
*run
, char c
)
786 if( (c
>= 'a' && c
<= 'z') || c
== ' ' )
792 run
->steps
[ run
->step_count
++ ] = code
;
800 static int map_load_is_digit( char c
)
802 if( (((u32
)c
>= (u32
)'0') && ((u32
)c
<= (u32
)'9')) || c
== '-' )
810 static int map_load_is_terminal( char c
)
812 if( c
== '+' || c
== '-' || c
== '*' )
817 static int map_load_apply_emitter_codes( struct cell_terminal
*term
)
819 struct cell
*cell
= pcell( term
->pos
);
821 if( cell
->state
& FLAG_EMITTER
)
823 if( (term
->run_count
> 0) && (term
->runs
[0].step_count
>= 2) )
825 cell
->emit
[0] = term
->runs
[0].steps
[0];
826 cell
->emit
[1] = term
->runs
[0].steps
[1];
830 vg_error( "Emitter was not assigned emit values\n" );
838 static void map_load_cell( struct cell
*cell
, char c
, int cx
)
845 v3_zero( cell
->glow
[0] );
846 v3_zero( cell
->glow
[1] );
848 /* Input, output, emitter */
849 if( map_load_is_terminal(c
) )
851 struct cell_terminal
*term
= arraddnptr( world
.io
, 1 );
853 term
->pos
[1] = world
.h
;
856 term
->runs
[0].step_count
= 0;
860 case '+': cell
->state
= FLAG_INPUT
; break;
861 case '-': cell
->state
= FLAG_OUTPUT
; break;
862 case '*': cell
->state
= FLAG_EMITTER
; break;
865 else if( c
== '.' ) cell
->state
= FLAG_INPUT_NICE
;
866 else if( c
== '#' ) cell
->state
= FLAG_WALL
;
867 else if( ((u32
)c
>= (u32
)'A') && ((u32
)c
<= (u32
)'A'+0xf) )
870 * Canal flag bits (4bit/16 value):
876 cell
->state
= ((u32
)c
- (u32
)'A') & (FLAG_CANAL
|FLAG_IS_TRIGGER
);
879 else cell
->state
= 0x00;
882 static void map_load_draw_background(void)
884 u8 info_buffer
[64*64*4];
886 for( int x
= 0; x
< 64; x
++ )
888 for( int y
= 0; y
< 64; y
++ )
890 u8
*px
= &info_buffer
[((x
*64)+y
)*4];
893 /* Fade out edges of world so that there isnt an obvious line */
894 int dx
= 16 - VG_MIN( VG_MIN( x
, 16 ), 16-VG_MAX( x
-16-world
.w
, 0 ) ),
895 dy
= 16 - VG_MIN( VG_MIN( y
, 16 ), 16-VG_MAX( y
-16-world
.h
, 0 ) ),
897 dist
= VG_MAX( dx
, dy
) * 16,
898 value
= VG_MAX( 0, 0xFF-0x3F + hash21i( (v2i
){x
,y
}, 0x3F ) - dist
);
909 * Level selection indentation, to make it look like the buttons are in a
910 * recessed area of the map.
912 for( int i
= 0; i
< vg_list_size( career_packs
); i
++ )
914 struct career_level_pack
*grid
= &career_packs
[ i
];
918 for( int y
= 0; y
< grid
->dims
[1]; y
++ )
920 for( int x
= 0; x
< grid
->dims
[0]; x
++ )
922 int cy
= y
+16+grid
->origin
[1],
923 cx
= 16+x
+grid
->origin
[0];
925 u8
*px
= &info_buffer
[(cy
*64+cx
)*4];
928 if( j
< grid
->count
)
930 struct cmp_level
*lvl
= &grid
->pack
[ j
++ ];
931 v2i_add( grid
->origin
, (v2i
){x
,y
}, lvl
->btn
.position
);
938 * Recess the UI buttons, this adds a little bit of a (subtle) outline
939 * to them when the background shader is run
941 for( int i
=0; i
<4; i
++ )
942 info_buffer
[(((16+world
.h
-(i
+2))*64)+world
.w
+16-1)*4] = 0x30;
945 * Digging 'wires' from the output/input terminals, to make it look like
946 * there is something connecting our world.
948 for( int i
= 0; i
< arrlen(world
.io
); i
++ )
950 struct cell_terminal
*term
= &world
.io
[ i
];
957 * Only make breakouts for terminals on the edge,
958 * starting them from every position leads to some weird overlapping
959 * and confusing lines.
961 if( !(term
->pos
[1] == 1 || term
->pos
[1] == world
.h
-2) )
964 turtle
[0] = 16+term
->pos
[0];
965 turtle
[1] = 16+term
->pos
[1];
969 pcell(term
->pos
)->state
& (FLAG_INPUT
|FLAG_EMITTER
)? 1: -1;
970 original_y
= turtle_dir
[1];
972 info_buffer
[((turtle
[1]*64)+turtle
[0])*4] = 0;
973 v2i_add( turtle_dir
, turtle
, turtle
);
975 for( int i
= 0; i
< 100; i
++ )
977 info_buffer
[((turtle
[1]*64)+turtle
[0])*4] = 0;
979 v2i_add( turtle_dir
, turtle
, turtle
);
981 if( turtle
[0] == 0 ) break;
982 if( turtle
[0] == 63 ) break;
983 if( turtle
[1] == 0 ) break;
984 if( turtle
[1] == 63 ) break;
986 int random_value
= hash21i( turtle
, 0xFF );
987 if( random_value
> 255-40 && !turtle_dir
[0] )
992 else if( random_value
> 255-80 && !turtle_dir
[0] )
997 else if( random_value
> 255-100 )
1000 turtle_dir
[1] = original_y
;
1005 glBindTexture( GL_TEXTURE_2D
, world
.background_data
);
1006 glTexSubImage2D( GL_TEXTURE_2D
, 0, 0, 0, 64, 64,
1007 GL_RGBA
, GL_UNSIGNED_BYTE
, info_buffer
);
1010 static int map_load_validate(void)
1012 for( int i
= 0; i
< world
.h
*world
.w
; i
++ )
1014 struct cell
*src
= &world
.data
[i
];
1016 if( src
->state
& FLAG_IS_TRIGGER
)
1018 int link_id
= src
->links
[0]?0:1;
1019 if( src
->links
[link_id
] <= world
.h
*world
.w
)
1021 struct cell
*target
= &world
.data
[ src
->links
[link_id
] ];
1023 int is_canal
= target
->state
& FLAG_CANAL
,
1024 is_splitter
= target
->config
== k_cell_type_split
,
1025 is_emitter
= target
->state
& FLAG_EMITTER
;
1027 if((is_canal
&& is_splitter
) || is_emitter
)
1029 if( target
->links
[ link_id
] )
1031 vg_error( "Link target was already targeted\n" );
1037 * Valid link, apply reverse mapping to other cell so it
1038 * knows who is linking to it
1040 target
->links
[ link_id
] = i
;
1041 target
->state
|= FLAG_TARGETED
;
1046 vg_error( "Link target was invalid\n" );
1052 vg_error( "Link target out of bounds\n" );
1061 static int map_load( const char *str
, const char *name
)
1065 char const *c
= str
;
1067 /* Predetermine width */
1070 if( c
[world
.w
] == ';' )
1072 else if( !c
[world
.w
] )
1074 vg_error( "Unexpected EOF when parsing level\n" );
1079 struct cell
*row
= arraddnptr( world
.data
, world
.w
);
1081 int reg_start
= 0, reg_end
= 0;
1083 u32
*links_to_make
= NULL
;
1084 int links_satisfied
= 0;
1086 char link_id_buffer
[32];
1094 if( map_load_char_ignore( *c
) ) { c
++; continue; }
1100 if( *c
== '\r' ) c
++;
1107 if( map_load_char_ignore( *c
) ) { c
++; continue; }
1109 if( reg_start
< reg_end
)
1111 struct cell_terminal
*terminal
= &world
.io
[ reg_start
];
1112 struct terminal_run
*run
=
1113 &terminal
->runs
[ terminal
->run_count
-1 ];
1115 if( !map_load_sequence_char( run
, *c
) )
1117 /* Control characters */
1118 if( *c
== ',' || *c
== '\n' ) /* Next terminal */
1125 else if( *c
== ':' ) /* New run starting */
1127 terminal
->runs
[ terminal
->run_count
].step_count
= 0;
1128 terminal
->run_count
++;
1130 vg_max( world
.max_runs
, terminal
->run_count
);
1134 vg_error( "Unkown attribute '%c' (row: %u)\n",
1142 if( links_satisfied
< arrlen( links_to_make
) )
1144 struct cell
*target
=
1145 &world
.data
[ links_to_make
[ links_satisfied
] ];
1147 if( map_load_is_digit( *c
) )
1149 if( link_id_n
>= vg_list_size( link_id_buffer
)-1 )
1151 vg_error( "Number was way too long to be parsed"
1152 " (row: %u)\n", world
.h
);
1156 link_id_buffer
[ link_id_n
++ ] = *c
;
1158 else if( *c
== ',' || *c
== '\n' )
1160 link_id_buffer
[ link_id_n
] = 0x00;
1161 int value
= atoi( link_id_buffer
);
1163 target
->links
[value
>= 0? 1:0] = abs(value
);
1172 vg_error( "Invalid character '%c'"
1173 " (row: %u)\n", *c
, world
.h
);
1179 vg_error( "Too many values to assign"
1180 " (row: %u)\n", world
.h
);
1189 /* Registry length-error checks */
1190 if( reg_start
!= reg_end
)
1192 vg_error( "Not enough spawn values assigned (row: %u, %u of %u)\n",
1193 world
.h
, reg_start
, reg_end
);
1197 if( links_satisfied
!= arrlen( links_to_make
) )
1199 vg_error( "Not enough link values assigned (row: %u, %u of %u)\n",
1200 world
.h
, links_satisfied
, arrlen( links_to_make
) );
1206 vg_error( "Not enough cells to match previous row definition"
1207 " (row: %u, %u<%u)\n", world
.h
, cx
, world
.w
);
1211 row
= arraddnptr( world
.data
, world
.w
);
1214 reg_end
= reg_start
= arrlen( world
.io
);
1216 arrsetlen( links_to_make
, 0 );
1217 links_satisfied
= 0;
1223 vg_error( "Too many cells to match previous row definition"
1224 " (row: %u, %u>%u)\n", world
.h
, cx
, world
.w
);
1228 struct cell
*cell
= &row
[ cx
];
1229 map_load_cell( cell
, *c
, cx
);
1231 if( map_load_is_terminal(*c
) )
1234 if( cell
->state
& FLAG_IS_TRIGGER
)
1235 arrpush( links_to_make
, cx
+ world
.h
*world
.w
);
1243 for( int i
= 0; i
< arrlen( world
.io
); i
++ )
1245 if( !map_load_apply_emitter_codes( &world
.io
[i
] ) )
1249 map_load_draw_background();
1250 map_reclassify( NULL
, NULL
, 1 );
1252 if( !map_load_validate() )
1256 * At this point the world is in a fully loaded and complete state
1259 arrfree( links_to_make
);
1261 vg_success( "Map '%s' loaded! (%u:%u)\n", name
, world
.w
, world
.h
);
1265 strncpy( world
.map_name
, name
, vg_list_size( world
.map_name
)-1 );
1266 world
.initialzed
= 1;
1268 /* Setup world button locations */
1269 for( int i
= 0; i
< vg_list_size( world
.st
.buttons
); i
++ )
1271 struct world_button
*btn
= &world
.st
.buttons
[i
];
1272 btn
->position
[0] = world
.w
-1;
1273 btn
->position
[1] = world
.h
-i
-2;
1276 /* Allocate buffers for render commands */
1277 world
.cmd_buf_tiles
= malloc( world
.w
*world
.h
* sizeof( struct render_cmd
) );
1278 world
.max_commands
= world
.w
*world
.h
;
1282 arrfree( links_to_make
);
1287 static void map_serialize( FILE *stream
)
1289 for( int y
= 0; y
< world
.h
; y
++ )
1291 for( int x
= 0; x
< world
.w
; x
++ )
1293 struct cell
*cell
= pcell( (v2i
){ x
, y
} );
1295 if( cell
->state
& FLAG_WALL
) fputc( '#', stream
);
1296 else if( cell
->state
& FLAG_INPUT_NICE
) fputc( '.', stream
);
1297 else if( cell
->state
& FLAG_INPUT
) fputc( '+', stream
);
1298 else if( cell
->state
& FLAG_OUTPUT
) fputc( '-', stream
);
1299 else if( cell
->state
& FLAG_EMITTER
) fputc( '*', stream
);
1300 else if( cell
->state
& FLAG_4B_GROUP
)
1303 * Serialize the '4 bit group' into ABCD...
1305 fputc( (cell
->state
& FLAG_4B_GROUP
) + (u32
)'A', stream
);
1307 else fputc( ' ', stream
);
1310 fputc( ';', stream
);
1311 int terminal_write_count
= 0;
1313 for( int x
= 0; x
< world
.w
; x
++ )
1315 for( int i
= 0; i
< arrlen( world
.io
); i
++ )
1317 struct cell_terminal
*term
= &world
.io
[i
];
1318 if( v2i_eq( term
->pos
, (v2i
){x
,y
} ) )
1320 if( terminal_write_count
)
1321 fputc( ',', stream
);
1322 terminal_write_count
++;
1324 for( int j
= 0; j
< term
->run_count
; j
++ )
1326 struct terminal_run
*run
= &term
->runs
[j
];
1328 for( int k
= 0; k
< run
->step_count
; k
++ )
1330 i8 step
= run
->steps
[k
];
1331 fputc( step
== -1? ' ': ('a' + run
->steps
[k
]), stream
);
1334 if( j
< term
->run_count
-1 )
1335 fputc( ':', stream
);
1341 for( int x
= 0; x
< world
.w
; x
++ )
1343 struct cell
*cell
= pcell( (v2i
){ x
,y
} );
1344 if( cell
->state
& FLAG_IS_TRIGGER
)
1346 if( terminal_write_count
)
1347 fputc( ',', stream
);
1348 terminal_write_count
++;
1350 fprintf( stream
, "%d",
1351 cell
->links
[0]? -cell
->links
[0]: cell
->links
[1] );
1355 fputc( '\n', stream
);
1362 #pragma pack(push,1)
1363 struct dcareer_state
1377 levels
[ NUM_CAMPAIGN_LEVELS
];
1381 static int career_load_success
= 0;
1383 static void career_serialize(void)
1385 if( !career_load_success
)
1388 struct dcareer_state encoded
;
1389 encoded
.version
= MARBLE_COMP_VERSION
;
1390 encoded
.in_map
= world
.pCmpLevel
? world
.pCmpLevel
->serial_id
: -1;
1392 memset( encoded
.reserved
, 0, sizeof( encoded
.reserved
) );
1394 for( int i
= 0; i
< vg_list_size( career_packs
); i
++ )
1396 struct career_level_pack
*set
= &career_packs
[i
];
1398 for( int j
= 0; j
< set
->count
; j
++ )
1400 struct cmp_level
*lvl
= &set
->pack
[j
];
1401 struct dlevel_state
*dest
= &encoded
.levels
[lvl
->serial_id
];
1403 dest
->score
= lvl
->completed_score
;
1404 dest
->unlocked
= lvl
->unlocked
;
1405 dest
->reserved
[0] = 0;
1406 dest
->reserved
[1] = 0;
1410 vg_asset_write( "sav/game.sv2", &encoded
, sizeof( struct dcareer_state
) );
1413 static void career_unlock_level( struct cmp_level
*lvl
);
1414 static void career_unlock_level( struct cmp_level
*lvl
)
1419 career_unlock_level( lvl
->linked
);
1422 static void career_pass_level( struct cmp_level
*lvl
, int score
, int upload
)
1426 lvl
->completed_score
= score
;
1430 career_unlock_level( lvl
->unlock
);
1433 if( lvl
->achievement
)
1434 sw_set_achievement( lvl
->achievement
);
1436 /* Check ALL maps to trigger master engineer */
1437 for( int i
= 0; i
< vg_list_size( career_packs
); i
++ )
1439 struct career_level_pack
*set
= &career_packs
[i
];
1441 for( int j
= 0; j
< set
->count
; j
++ )
1443 if( set
->pack
[j
].completed_score
== 0 )
1448 sw_set_achievement( "MASTER_ENGINEER" );
1453 static void career_reset_level( struct cmp_level
*lvl
)
1456 lvl
->completed_score
= 0;
1459 static void career_load(void)
1462 struct dcareer_state encoded
;
1464 /* Blank save state */
1465 memset( (void*)&encoded
, 0, sizeof( struct dcareer_state
) );
1467 encoded
.levels
[0].unlocked
= 1;
1470 * Load and copy, this step is just to ensure old/newer saves can be loaded
1471 * without crashing. Old saves will load fine, too new saves will lose data,
1472 * such a situation should rarely (never) happen with the steam version.
1474 void *cr
= vg_asset_read_s( "sav/game.sv2", &sz
);
1478 if( sz
> sizeof( struct dcareer_state
) )
1479 vg_warn( "This save file is too big! Some levels will be lost\n" );
1481 if( sz
<= offsetof( struct dcareer_state
, levels
) )
1483 vg_warn( "This save file is too small to have a header. "
1484 "Creating a blank one\n" );
1488 memcpy( (void*)&encoded
, cr
, VG_MIN( sizeof( struct dcareer_state
), sz
));
1492 vg_info( "No save file... Using blank one\n" );
1495 for( int i
= 0; i
< vg_list_size( career_packs
); i
++ )
1497 struct career_level_pack
*set
= &career_packs
[i
];
1499 for( int j
= 0; j
< set
->count
; j
++ )
1500 career_reset_level( &set
->pack
[j
] );
1503 /* Header information */
1505 struct cmp_level
*lvl_to_load
= &career_packs
[0].pack
[0];
1507 /* Decode everything from dstate */
1508 for( int i
= 0; i
< vg_list_size( career_packs
); i
++ )
1510 struct career_level_pack
*set
= &career_packs
[i
];
1512 for( int j
= 0; j
< set
->count
; j
++ )
1514 struct cmp_level
*lvl
= &set
->pack
[j
];
1515 struct dlevel_state
*src
= &encoded
.levels
[lvl
->serial_id
];
1517 if( src
->unlocked
) career_unlock_level( lvl
);
1520 lvl
->completed_score
= src
->score
;
1523 * Apply unlocking trigger to next levels,
1524 * in case the level graph was updated in the future
1527 career_unlock_level( lvl
->unlock
);
1530 if( lvl
->serial_id
== encoded
.in_map
)
1535 if( console_changelevel( 1, &lvl_to_load
->map_name
) )
1537 world
.pCmpLevel
= lvl_to_load
;
1541 career_load_success
= 1;
1544 if( encoded
.version
< MARBLE_COMP_VERSION
)
1545 world
.st
.state
= k_game_state_update
;
1552 static int is_simulation_running(void)
1554 return world
.st
.buttons
[ k_world_button_sim
].state
;
1557 static void clear_animation_flags(void)
1559 for( int i
= 0; i
< world
.w
*world
.h
; i
++ )
1560 world
.data
[ i
].state
&= ~(FLAG_FLIP_FLOP
|FLAG_FLIP_ROTATING
);
1563 static void simulation_stop(void)
1565 world
.st
.buttons
[ k_world_button_sim
].state
= 0;
1566 world
.st
.buttons
[ k_world_button_pause
].state
= 0;
1568 world
.num_fishes
= 0;
1569 world
.sim_frame
= 0;
1571 world
.frame_lerp
= 0.0f
;
1575 sfx_system_fadeout( &audio_system_balls_rolling
, 44100 );
1577 clear_animation_flags();
1579 vg_info( "Stopping simulation!\n" );
1582 static void simulation_start(void)
1584 vg_success( "Starting simulation!\n" );
1586 sfx_set_playrnd( &audio_rolls
, &audio_system_balls_rolling
, 0, 1 );
1588 world
.num_fishes
= 0;
1589 world
.sim_frame
= 0;
1592 world
.sim_delta_speed
= world
.st
.buttons
[ k_world_button_speedy
].state
? 10.0f
: 2.5f
;
1593 world
.sim_delta_ref
= vg_time
;
1594 world
.sim_internal_ref
= 0.0f
;
1595 world
.sim_internal_time
= 0.0f
;
1596 world
.pause_offset_target
= 0.0f
;
1598 world
.sim_target
= 0;
1600 clear_animation_flags();
1604 if( world
.pCmpLevel
)
1606 world
.pCmpLevel
->completed_score
= 0;
1611 static int world_check_pos_ok( v2i co
, int dist
)
1613 return (co
[0] < dist
|| co
[0] >= world
.w
-dist
|| co
[1] < dist
|| co
[1] >= world
.h
-dist
)? 0: 1;
1616 static int cell_interactive( v2i co
)
1618 struct cell
*cell
= NULL
;
1621 if( world_check_pos_ok( co
, 1 ) )
1624 if( cell
->state
& FLAG_EMITTER
)
1628 if( !world_check_pos_ok( co
, 2 ) )
1632 if( cell
->state
& (FLAG_WALL
|FLAG_INPUT
|FLAG_OUTPUT
) )
1635 // List of 3x3 configurations that we do not allow
1636 static u32 invalid_src
[][9] =
1668 // Statically compile invalid configurations into bitmasks
1669 static u32 invalid
[ vg_list_size(invalid_src
) ];
1671 for( int i
= 0; i
< vg_list_size(invalid_src
); i
++ )
1675 for( int j
= 0; j
< 3; j
++ )
1676 for( int k
= 0; k
< 3; k
++ )
1677 comped
|= invalid_src
[i
][ j
*3+k
] << ((j
*5)+k
);
1679 invalid
[i
] = comped
;
1682 // Extract 5x5 grid surrounding tile
1684 for( int y
= co
[1]-2; y
< co
[1]+3; y
++ )
1685 for( int x
= co
[0]-2; x
< co
[0]+3; x
++ )
1687 struct cell
*cell
= pcell((v2i
){x
,y
});
1689 if( cell
&& (cell
->state
& (FLAG_CANAL
|FLAG_INPUT
|FLAG_OUTPUT
|FLAG_EMITTER
)) )
1690 blob
|= 0x1 << ((y
-(co
[1]-2))*5 + x
-(co
[0]-2));
1693 // Run filter over center 3x3 grid to check for invalid configurations
1694 int kernel
[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
1695 for( int i
= 0; i
< vg_list_size(kernel
); i
++ )
1697 if( blob
& (0x1 << (6+kernel
[i
])) )
1699 u32 window
= blob
>> kernel
[i
];
1701 for( int j
= 0; j
< vg_list_size(invalid
); j
++ )
1702 if((window
& invalid
[j
]) == invalid
[j
])
1710 static void vg_update(void)
1713 if( world
.st
.lvl_to_load
)
1715 world
.st
.world_transition
= (world
.st
.lvl_load_time
-vg_time
) * 4.0f
;
1717 if( vg_time
> world
.st
.lvl_load_time
)
1719 if( console_changelevel( 1, &world
.st
.lvl_to_load
->map_name
) )
1721 world
.pCmpLevel
= world
.st
.lvl_to_load
;
1725 world
.st
.lvl_to_load
= NULL
;
1730 world
.st
.world_transition
= vg_minf( 1.0f
, (vg_time
-world
.st
.lvl_load_time
) * 4.0f
);
1734 // ========================================================================================================
1736 float r1
= (float)vg_window_y
/ (float)vg_window_x
,
1737 r2
= (float)world
.h
/ (float)world
.w
,
1740 static float size_current
= 2.0f
;
1741 static v3f origin_current
= { 0.0f
, 0.0f
, 0.0f
};
1742 static v2f drag_offset
= { 0.0f
, 0.0f
};
1743 static v2f view_point
= { 0.0f
, 0.0f
};
1746 size
= ( r2
< r1
? (float)(world
.w
+5) * 0.5f
: ((float)(world
.h
+5) * 0.5f
) / r1
) + 0.5f
;
1751 origin
[0] = floorf( -0.5f
* ((float)world
.w
-4.5f
) );
1752 origin
[1] = floorf( -0.5f
* world
.h
);
1754 // Create and clamp result view
1755 v2_add( view_point
, drag_offset
, result_view
);
1756 result_view
[0] = vg_clampf( result_view
[0], -world
.st
.zoom
, world
.st
.zoom
);
1757 result_view
[1] = vg_clampf( result_view
[1], -world
.st
.zoom
*r1
, world
.st
.zoom
*r1
);
1759 v2_add( origin
, result_view
, vt_target
);
1761 // Lerp towards target
1762 size_current
= vg_lerpf( size_current
, size
- world
.st
.zoom
, vg_time_delta
* 6.0f
);
1763 v2_lerp( origin_current
, vt_target
, vg_time_delta
* 6.0f
, origin_current
);
1765 m3x3_projection( m_projection
, -size_current
, size_current
, -size_current
*r1
, size_current
*r1
);
1766 m3x3_identity( m_view
);
1767 m3x3_translate( m_view
, origin_current
);
1768 m3x3_mul( m_projection
, m_view
, vg_pv
);
1769 vg_projection_update();
1771 if( world
.st
.state
== k_game_state_update
)
1775 // ========================================================================================================
1776 v2_copy( vg_mouse_ws
, world
.tile_pos
);
1778 world
.tile_x
= floorf( world
.tile_pos
[0] );
1779 world
.tile_y
= floorf( world
.tile_pos
[1] );
1783 static v2f drag_origin
; // x/y pixel
1785 if( vg_get_button_down( "tertiary" ) )
1786 v2_copy( vg_mouse
, drag_origin
);
1787 else if( vg_get_button( "tertiary" ) )
1790 v2_sub( vg_mouse
, drag_origin
, drag_offset
);
1791 v2_div( drag_offset
, (v2f
){ vg_window_x
, vg_window_y
}, drag_offset
);
1792 v2_mul( drag_offset
, (v2f
){ size_current
*2.0f
, -size_current
*r1
*2.0f
}, drag_offset
);
1796 v2_copy( result_view
, view_point
);
1797 v2_copy( (v2f
){0.0f
,0.0f
}, drag_offset
);
1809 rsize
= size
-world
.st
.zoom
;
1811 v2_div( vg_mouse
, (v2f
){ vg_window_x
*0.5f
, vg_window_y
*0.5f
}, mview_local
);
1812 v2_add( (v2f
){ -rsize
, -rsize
*r1
}, (v2f
){ mview_local
[0]*rsize
, (2.0f
-mview_local
[1])*rsize
*r1
}, mview_cur
);
1814 world
.st
.zoom
= vg_clampf( world
.st
.zoom
+ vg_mouse_wheel
[1], 0.0f
, size
- 4.0f
);
1816 // Recalculate new position
1817 rsize
= size
-world
.st
.zoom
;
1818 v2_add( (v2f
){ -rsize
, -rsize
*r1
}, (v2f
){ mview_local
[0]*rsize
, (2.0f
-mview_local
[1])*rsize
*r1
}, mview_new
);
1821 v2_sub( mview_new
, mview_cur
, mview_delta
);
1822 v2_add( mview_delta
, view_point
, view_point
);
1826 // ========================================================================================================
1827 if( !is_simulation_running() && !gui_want_mouse() )
1829 v2_copy( vg_mouse_ws
, world
.drag_to_co
);
1831 if( cell_interactive( (v2i
){ world
.tile_x
, world
.tile_y
} ))
1833 world
.selected
= world
.tile_y
* world
.w
+ world
.tile_x
;
1835 static u32 modify_state
= 0;
1836 struct cell
*cell_ptr
= &world
.data
[world
.selected
];
1838 if( !(cell_ptr
->state
& FLAG_EMITTER
) )
1840 if( vg_get_button_down("primary") )
1841 modify_state
= (cell_ptr
->state
& FLAG_CANAL
) ^ FLAG_CANAL
;
1843 if( vg_get_button("primary") && ((cell_ptr
->state
& FLAG_CANAL
) != modify_state
) )
1845 cell_ptr
->state
&= ~FLAG_CANAL
;
1846 cell_ptr
->state
|= modify_state
;
1848 if( cell_ptr
->state
& FLAG_CANAL
)
1850 cell_ptr
->links
[0] = 0;
1851 cell_ptr
->links
[1] = 0;
1853 sfx_set_playrnd( &audio_tile_mod
, &audio_system_sfx
, 3, 6 );
1858 sfx_set_playrnd( &audio_tile_mod
, &audio_system_sfx
, 0, 3 );
1862 map_reclassify((v2i
){ world
.tile_x
-2, world
.tile_y
-2 },
1863 (v2i
){ world
.tile_x
+2, world
.tile_y
+2 }, 1 );
1866 if( vg_get_button_down("secondary") && (cell_ptr
->state
& FLAG_CANAL
) && !(cell_ptr
->config
== k_cell_type_split
) )
1868 world
.id_drag_from
= world
.selected
;
1870 struct cell_description
*desc
= &cell_descriptions
[ world
.data
[world
.id_drag_from
].config
];
1871 v2_add( desc
->trigger_pos
, (v2f
){ world
.tile_x
, world
.tile_y
}, world
.drag_from_co
);
1875 float local_x
= vg_mouse_ws
[0] - (float)world
.tile_x
;
1877 if( vg_get_button_up("secondary") && world
.id_drag_from
== world
.selected
)
1879 u32 link_id
= cell_ptr
->links
[ 0 ]? 0: 1;
1881 // break existing connection off
1882 if( cell_ptr
->links
[ link_id
] )
1884 struct cell
*current_connection
= &world
.data
[ cell_ptr
->links
[ link_id
]];
1886 if( !current_connection
->links
[ link_id
^ 0x1 ] )
1887 current_connection
->state
&= ~FLAG_TARGETED
;
1889 current_connection
->links
[ link_id
] = 0;
1890 cell_ptr
->links
[ link_id
] = 0;
1893 cell_ptr
->state
&= ~FLAG_IS_TRIGGER
;
1894 world
.id_drag_from
= 0;
1897 else if( world
.id_drag_from
&& (cell_ptr
->state
& (FLAG_CANAL
|FLAG_EMITTER
)) &&
1898 ((cell_ptr
->config
== k_cell_type_split
) || (cell_ptr
->state
& FLAG_EMITTER
)) )
1900 world
.drag_to_co
[0] = (float)world
.tile_x
+ (local_x
> 0.5f
? 0.75f
: 0.25f
);
1901 world
.drag_to_co
[1] = (float)world
.tile_y
+ 0.25f
;
1903 if( vg_get_button_up("secondary") )
1905 struct cell
*drag_ptr
= &world
.data
[world
.id_drag_from
];
1906 u32 link_id
= local_x
> 0.5f
? 1: 0;
1908 // Cleanup existing connections
1909 if( cell_ptr
->links
[ link_id
] )
1911 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id
, cell_ptr
->links
[ link_id
] );
1913 struct cell
*current_connection
= &world
.data
[ cell_ptr
->links
[ link_id
]];
1914 current_connection
->state
&= ~FLAG_IS_TRIGGER
;
1915 current_connection
->links
[ link_id
] = 0;
1918 for( u32 i
= 0; i
< 2; i
++ )
1920 if( drag_ptr
->links
[ i
] )
1922 vg_warn( "Destroying link %u (%hu)\n", i
, drag_ptr
->links
[ i
] );
1924 struct cell
*current_connection
= &world
.data
[ drag_ptr
->links
[ i
]];
1925 if( current_connection
->links
[ i
^ 0x1 ] == 0 )
1926 current_connection
->state
&= ~FLAG_TARGETED
;
1928 current_connection
->links
[ i
] = 0;
1929 drag_ptr
->links
[ i
] = 0;
1933 // Create the new connection
1934 vg_success( "Creating connection on link %u (%hu)\n", link_id
, world
.id_drag_from
);
1936 cell_ptr
->links
[ link_id
] = world
.id_drag_from
;
1937 drag_ptr
->links
[ link_id
] = world
.selected
;
1939 cell_ptr
->state
|= FLAG_TARGETED
;
1940 drag_ptr
->state
|= FLAG_IS_TRIGGER
;
1941 world
.id_drag_from
= 0;
1947 world
.selected
= -1;
1950 if( !(vg_get_button("secondary") && world
.id_drag_from
) )
1951 world
.id_drag_from
= 0;
1955 world
.selected
= -1;
1956 world
.id_drag_from
= 0;
1959 // Marble state updates
1960 // ========================================================================================================
1961 if( is_simulation_running() )
1963 float old_time
= world
.sim_internal_time
;
1965 if( !world
.st
.buttons
[ k_world_button_pause
].state
)
1966 world
.sim_internal_time
= world
.sim_internal_ref
+ (vg_time
-world
.sim_delta_ref
) * world
.sim_delta_speed
;
1968 world
.sim_internal_time
= vg_lerpf( world
.sim_internal_time
, world
.sim_internal_ref
+ world
.pause_offset_target
, vg_time_delta
*15.0f
);
1969 world
.sim_internal_delta
= world
.sim_internal_time
-old_time
;
1971 world
.sim_target
= (int)floorf(world
.sim_internal_time
);
1973 int success_this_frame
= 0;
1974 int failure_this_frame
= 0;
1976 while( world
.sim_frame
< world
.sim_target
)
1978 sfx_set_playrnd( &audio_random
, &audio_system_balls_switching
, 0, 8 );
1980 // Update splitter deltas
1981 for( int i
= 0; i
< world
.h
*world
.w
; i
++ )
1983 struct cell
*cell
= &world
.data
[i
];
1984 if( cell
->config
== k_cell_type_split
)
1986 cell
->state
&= ~FLAG_FLIP_ROTATING
;
1988 if( cell
->state
& (FLAG_IS_TRIGGER
|FLAG_EMITTER
) )
1989 cell
->state
&= ~FLAG_TRIGGERED
;
1992 int alive_count
= 0;
1994 // Update fish positions
1995 for( int i
= 0; i
< world
.num_fishes
; i
++ )
1997 struct fish
*fish
= &world
.fishes
[i
];
1999 if( fish
->state
== k_fish_state_soon_dead
)
2000 fish
->state
= k_fish_state_dead
;
2002 if( fish
->state
== k_fish_state_soon_alive
)
2003 fish
->state
= k_fish_state_alive
;
2005 if( fish
->state
< k_fish_state_alive
)
2008 struct cell
*cell_current
= pcell( fish
->pos
);
2010 if( fish
->state
== k_fish_state_alive
)
2013 if( cell_current
->state
& FLAG_OUTPUT
)
2015 for( int j
= 0; j
< arrlen( world
.io
); j
++ )
2017 struct cell_terminal
*term
= &world
.io
[j
];
2019 if( v2i_eq( term
->pos
, fish
->pos
) )
2021 struct terminal_run
*run
= &term
->runs
[ world
.sim_run
];
2022 if( run
->recv_count
< vg_list_size( run
->recieved
) )
2024 if( fish
->colour
== run
->steps
[ run
->recv_count
] )
2025 success_this_frame
= 1;
2027 failure_this_frame
= 1;
2029 run
->recieved
[ run
->recv_count
++ ] = fish
->colour
;
2032 failure_this_frame
= 1;
2038 fish
->state
= k_fish_state_dead
;
2039 fish
->death_time
= -1000.0f
;
2044 if( cell_current
->config
== k_cell_type_merge
)
2049 fish
->flow_reversed
= 0;
2053 if( cell_current
->config
== k_cell_type_split
)
2056 fish
->dir
[0] = cell_current
->state
&FLAG_FLIP_FLOP
?1:-1;
2059 if( !(cell_current
->state
& FLAG_TARGETED
) )
2060 cell_current
->state
^= FLAG_FLIP_FLOP
;
2064 // Apply cell out-flow
2065 struct cell_description
*desc
= &cell_descriptions
[ cell_current
->config
];
2067 v2i_copy( fish
->flow_reversed
? desc
->start
: desc
->end
, fish
->dir
);
2071 v2i_add( fish
->pos
, fish
->dir
, pos_next
);
2073 struct cell
*cell_next
= pcell( pos_next
);
2075 if( cell_next
->state
& (FLAG_CANAL
|FLAG_OUTPUT
) )
2077 struct cell_description
*desc
= &cell_descriptions
[ cell_next
->config
];
2079 if( cell_next
->config
== k_cell_type_merge
)
2081 if( fish
->dir
[0] == 0 )
2083 fish
->state
= k_fish_state_dead
;
2084 fish
->death_time
= world
.sim_internal_time
;
2087 fish
->flow_reversed
= 0;
2091 if( cell_next
->config
== k_cell_type_split
)
2093 if( fish
->dir
[0] == 0 )
2095 sfx_set_playrnd( &audio_splitter
, &audio_system_balls_important
, 0, 1 );
2096 cell_next
->state
|= FLAG_FLIP_ROTATING
;
2098 fish
->flow_reversed
= 0;
2102 fish
->state
= k_fish_state_dead
;
2103 fish
->death_time
= world
.sim_internal_time
;
2107 fish
->flow_reversed
= ( fish
->dir
[0] != -desc
->start
[0] ||
2108 fish
->dir
[1] != -desc
->start
[1] )? 1: 0;
2113 if( world_check_pos_ok( fish
->pos
, 2 ) )
2114 fish
->state
= k_fish_state_bg
;
2117 fish
->state
= k_fish_state_dead
;
2118 fish
->death_time
= world
.sim_internal_time
;
2123 //v2i_add( fish->pos, fish->dir, fish->pos );
2125 else if( fish
->state
== k_fish_state_bg
)
2127 v2i_add( fish
->pos
, fish
->dir
, fish
->pos
);
2129 if( !world_check_pos_ok( fish
->pos
, 2 ) )
2131 fish
->state
= k_fish_state_dead
;
2132 fish
->death_time
= -1000.0f
;
2136 struct cell
*cell_entry
= pcell( fish
->pos
);
2138 if( cell_entry
->state
& FLAG_CANAL
)
2140 if( cell_entry
->config
== k_cell_type_con_r
|| cell_entry
->config
== k_cell_type_con_u
2141 || cell_entry
->config
== k_cell_type_con_l
|| cell_entry
->config
== k_cell_type_con_d
)
2144 sw_set_achievement( "CAN_DO_THAT" );
2147 fish
->state
= k_fish_state_soon_alive
;
2151 fish
->flow_reversed
= 1;
2153 switch( cell_entry
->config
)
2155 case k_cell_type_con_r
: fish
->dir
[0] = 1; break;
2156 case k_cell_type_con_l
: fish
->dir
[0] = -1; break;
2157 case k_cell_type_con_u
: fish
->dir
[1] = 1; break;
2158 case k_cell_type_con_d
: fish
->dir
[1] = -1; break;
2164 else { vg_error( "fish behaviour unimplemented for behaviour type (%d)\n" ); }
2166 if( fish
->state
>= k_fish_state_alive
)
2170 // Second pass (triggers)
2171 for( int i
= 0; i
< world
.num_fishes
; i
++ )
2173 struct fish
*fish
= &world
.fishes
[i
];
2176 if( fish
->state
== k_fish_state_alive
)
2177 v2i_add( fish
->pos
, fish
->dir
, fish
->pos
);
2179 if( fish
->state
== k_fish_state_alive
|| fish
->state
== k_fish_state_soon_alive
)
2181 struct cell
*cell_current
= pcell( fish
->pos
);
2183 if( cell_current
->state
& FLAG_IS_TRIGGER
)
2185 int trigger_id
= cell_current
->links
[0]?0:1;
2187 struct cell
*target_peice
= &world
.data
[ cell_current
->links
[trigger_id
] ];
2190 if( (target_peice
->state
& FLAG_EMITTER
) && !(target_peice
->state
& FLAG_TRIGGERED
))
2192 if( world
.num_fishes
< vg_list_size( world
.fishes
) )
2194 struct fish
*fish
= &world
.fishes
[ world
.num_fishes
];
2195 lcell( cell_current
->links
[trigger_id
], fish
->pos
);
2197 fish
->state
= k_fish_state_soon_alive
;
2198 fish
->colour
= target_peice
->emit
[ trigger_id
];
2200 if( target_peice
->config
!= k_cell_type_stub
)
2202 struct cell_description
*desc
= &cell_descriptions
[ target_peice
->config
];
2203 v2i_copy( desc
->start
, fish
->dir
);
2204 fish
->flow_reversed
= 1;
2206 world
.num_fishes
++;
2211 vg_warn( "Max marbles exceeded\n" );
2216 target_peice
->state
|= FLAG_FLIP_FLOP
;
2218 target_peice
->state
&= ~FLAG_FLIP_FLOP
;
2221 cell_current
->state
|= FLAG_TRIGGERED
;
2226 // Third pass (collisions)
2227 struct fish
*fi
, *fj
;
2229 for( int i
= 0; i
< world
.num_fishes
; i
++ )
2231 fi
= &world
.fishes
[i
];
2233 if( (fi
->state
== k_fish_state_alive
) |
2234 (fi
->state
== k_fish_state_soon_alive
) )
2236 int continue_again
= 0;
2238 for( int j
= i
+1; j
< world
.num_fishes
; j
++ )
2240 fj
= &world
.fishes
[j
];
2242 if( (fj
->state
== k_fish_state_alive
) |
2243 (fj
->state
== k_fish_state_soon_alive
) )
2248 v2i_sub( fi
->pos
, fi
->dir
, fi_prev
);
2249 v2i_sub( fj
->pos
, fj
->dir
, fj_prev
);
2252 collide_next_frame
= (
2253 (fi
->pos
[0] == fj
->pos
[0]) &&
2254 (fi
->pos
[1] == fj
->pos
[1]))? 1: 0,
2255 collide_this_frame
= (
2256 (fi_prev
[0] == fj
->pos
[0]) &&
2257 (fi_prev
[1] == fj
->pos
[1]) &&
2258 (fj_prev
[0] == fi
->pos
[0]) &&
2259 (fj_prev
[1] == fi
->pos
[1])
2262 if( collide_next_frame
|| collide_this_frame
)
2265 sw_set_achievement( "BANG" );
2268 // Shatter death (+0.5s)
2269 float death_time
= world
.sim_internal_time
+ ( collide_this_frame
? 0.0f
: 0.5f
);
2271 fi
->state
= k_fish_state_soon_dead
;
2272 fj
->state
= k_fish_state_soon_dead
;
2273 fi
->death_time
= death_time
;
2274 fj
->death_time
= death_time
;
2281 if( continue_again
)
2287 for( int i
= 0; i
< arrlen( world
.io
); i
++ )
2289 struct cell_terminal
*term
= &world
.io
[ i
];
2290 int is_input
= pcell(term
->pos
)->state
& FLAG_INPUT
;
2294 if( world
.sim_frame
< term
->runs
[ world
.sim_run
].step_count
)
2296 i8 emit
= term
->runs
[ world
.sim_run
].steps
[ world
.sim_frame
];
2300 struct fish
*fish
= &world
.fishes
[ world
.num_fishes
];
2301 v2i_copy( term
->pos
, fish
->pos
);
2303 fish
->state
= k_fish_state_alive
;
2304 fish
->colour
= emit
;
2306 struct cell
*cell_ptr
= pcell( fish
->pos
);
2308 if( cell_ptr
->config
!= k_cell_type_stub
)
2310 if( world
.num_fishes
< vg_list_size(world
.fishes
))
2312 struct cell_description
*desc
= &cell_descriptions
[ cell_ptr
->config
];
2314 v2i_copy( desc
->start
, fish
->dir
);
2315 fish
->flow_reversed
= 1;
2317 world
.num_fishes
++;
2321 vg_warn( "Max marbles exceeded\n" );
2327 if( alive_count
== 0 )
2329 world
.completed
= 1;
2331 for( int i
= 0; i
< arrlen( world
.io
); i
++ )
2333 struct cell_terminal
*term
= &world
.io
[ i
];
2335 if( pcell(term
->pos
)->state
& FLAG_OUTPUT
)
2337 struct terminal_run
*run
= &term
->runs
[ world
.sim_run
];
2339 if( run
->recv_count
== run
->step_count
)
2341 for( int j
= 0; j
< run
->step_count
; j
++ )
2343 if( run
->recieved
[j
] != run
->steps
[j
] )
2345 world
.completed
= 0;
2352 world
.completed
= 0;
2358 if( world
.completed
)
2360 if( world
.sim_run
< world
.max_runs
-1 )
2362 vg_success( "Run passed, starting next\n" );
2364 world
.sim_frame
= 0;
2365 world
.sim_target
= 0;
2366 world
.num_fishes
= 0;
2368 // Reset timing reference points
2369 world
.sim_delta_ref
= vg_time
;
2370 world
.sim_internal_ref
= 0.0f
;
2372 if( world
.st
.buttons
[ k_world_button_pause
].state
)
2373 world
.pause_offset_target
= 0.5f
;
2375 world
.pause_offset_target
= 0.0f
;
2377 world
.sim_internal_time
= 0.0f
;
2379 for( int i
= 0; i
< world
.w
*world
.h
; i
++ )
2380 world
.data
[ i
].state
&= ~FLAG_FLIP_FLOP
;
2386 vg_success( "Level passed!\n" );
2389 for( int i
= 0; i
< world
.w
*world
.h
; i
++ )
2390 if( world
.data
[ i
].state
& FLAG_CANAL
)
2393 world
.score
= score
;
2394 world
.time
= world
.sim_frame
;
2396 // Copy into career data
2397 if( world
.pCmpLevel
)
2399 career_pass_level( world
.pCmpLevel
, world
.score
, 1 );
2402 sfx_set_play( &audio_tones
, &audio_system_balls_extra
, 9 );
2403 failure_this_frame
= 0;
2404 success_this_frame
= 0;
2410 if( world
.sim_run
> 0 )
2411 sw_set_achievement( "GOOD_ENOUGH" );
2414 vg_error( "Level failed :(\n" );
2425 if( failure_this_frame
)
2427 sfx_set_play( &audio_tones
, &audio_system_balls_extra
, 0 );
2429 else if( success_this_frame
)
2431 static int succes_counter
= 0;
2433 sfx_set_play( &audio_tones
, &audio_system_balls_extra
, 1+(succes_counter
++) );
2435 if( succes_counter
== 7 )
2440 // =====================================================================================================
2442 world
.frame_lerp
= world
.sim_internal_time
- floorf( world
.sim_internal_time
);
2444 for( int i
= 0; i
< world
.num_fishes
; i
++ )
2446 struct fish
*fish
= &world
.fishes
[i
];
2448 if( fish
->state
== k_fish_state_dead
)
2451 if( fish
->state
== k_fish_state_soon_dead
&& (world
.sim_internal_time
> fish
->death_time
) )
2452 continue; // Todo: particle thing?
2454 struct cell
*cell
= pcell(fish
->pos
);
2455 struct cell_description
*desc
= &cell_descriptions
[ cell
->config
];
2459 float t
= world
.frame_lerp
;
2460 if( fish
->flow_reversed
&& !desc
->is_linear
)
2463 v2_copy( fish
->physics_co
, fish
->physics_v
);
2465 switch( cell
->config
)
2467 case k_cell_type_merge
:
2468 if( fish
->dir
[0] == 1 )
2473 case k_cell_type_con_r
: curve
= curve_1
; break;
2474 case k_cell_type_con_l
: curve
= curve_4
; break;
2475 case k_cell_type_con_u
: curve
= curve_2
; break;
2476 case k_cell_type_con_d
: curve
= curve_8
; break;
2477 case 3: curve
= curve_3
; break;
2478 case 6: curve
= curve_6
; break;
2479 case 9: curve
= curve_9
; break;
2480 case 12: curve
= curve_12
; break;
2482 if( t
> curve_7_linear_section
)
2484 t
-= curve_7_linear_section
;
2485 t
*= (1.0f
/(1.0f
-curve_7_linear_section
));
2487 curve
= cell
->state
& FLAG_FLIP_FLOP
? curve_7
: curve_7_1
;
2491 default: curve
= NULL
; break;
2497 float t3
= t
* t
* t
;
2499 float cA
= 3.0f
*t2
- 3.0f
*t3
;
2500 float cB
= 3.0f
*t3
- 6.0f
*t2
+ 3.0f
*t
;
2501 float cC
= 3.0f
*t2
- t3
- 3.0f
*t
+ 1.0f
;
2503 fish
->physics_co
[0] = t3
*curve
[3][0] + cA
*curve
[2][0] + cB
*curve
[1][0] + cC
*curve
[0][0];
2504 fish
->physics_co
[1] = t3
*curve
[3][1] + cA
*curve
[2][1] + cB
*curve
[1][1] + cC
*curve
[0][1];
2505 fish
->physics_co
[0] += (float)fish
->pos
[0];
2506 fish
->physics_co
[1] += (float)fish
->pos
[1];
2511 origin
[0] = (float)fish
->pos
[0] + (float)fish
->dir
[0]*-0.5f
+ 0.5f
;
2512 origin
[1] = (float)fish
->pos
[1] + (float)fish
->dir
[1]*-0.5f
+ 0.5f
;
2514 fish
->physics_co
[0] = origin
[0] + (float)fish
->dir
[0]*t
;
2515 fish
->physics_co
[1] = origin
[1] + (float)fish
->dir
[1]*t
;
2518 v2_sub( fish
->physics_co
, fish
->physics_v
, fish
->physics_v
);
2519 v2_divs( fish
->physics_v
, world
.sim_internal_delta
, fish
->physics_v
);
2524 static void render_tile( v2i pos
, struct cell
*ptr
, v4f
const regular_colour
,
2525 v4f
const selected_colour
, int with_glow
)
2527 int selected
= world
.selected
== pos
[1]*world
.w
+ pos
[0];
2530 uv
[0] = ptr
->config
& 0x3;
2531 uv
[1] = ptr
->config
>> 2;
2533 glUniform4f( SHADER_UNIFORM( shader_tile_main
, "uOffset" ),
2542 glUniform3fv( SHADER_UNIFORM( shader_tile_main
, "uGlowA" ),
2544 glUniform3fv( SHADER_UNIFORM( shader_tile_main
, "uGlowB" ),
2549 glUniform3f( SHADER_UNIFORM( shader_tile_main
, "uGlowA" ),
2553 glUniform3f( SHADER_UNIFORM( shader_tile_main
, "uGlowB" ),
2561 glUniform4fv( SHADER_UNIFORM( shader_tile_main
, "uColour" ), 1, selected_colour
);
2563 glUniform4fv( SHADER_UNIFORM( shader_tile_main
, "uColour" ), 1, regular_colour
);
2569 // Renders specific chunk of tiles
2570 static void render_tile_block( v2i start
, v2i end
, v4f
const regular_colour
, v4f
const selected_colour
)
2572 v2i full_start
= { 0,0 };
2573 v2i full_end
= { world
.w
, world
.h
};
2575 if( !start
|| !end
)
2581 for( int y
= start
[1]; y
< end
[1]; y
++ )
2583 for( int x
= start
[0]; x
< end
[0]; x
++ )
2586 struct cell
*cell
= pcell( pos
);
2588 if( cell
->state
& (FLAG_CANAL
|FLAG_INPUT
|FLAG_OUTPUT
|FLAG_EMITTER
) )
2589 render_tile( pos
, cell
, regular_colour
, selected_colour
, 0 );
2594 // Renders all tiles in the command list
2595 static void render_tiles( v4f
const regular_colour
, v4f
const selected_colour
,
2598 glUniform4fv( SHADER_UNIFORM( shader_tile_main
, "uColour" ), 1, regular_colour
);
2602 struct render_cmd
*arr
;
2606 { world
.cmd_buf_tiles
, world
.tile_count
},
2607 { world
.cmd_buf_specials
, world
.tile_special_count
}
2610 int world_paused
= world
.st
.buttons
[k_world_button_pause
].state
;
2611 if( with_glow
&& !world_paused
)
2613 for( int i
= 0; i
< world
.num_fishes
; i
++ )
2615 struct fish
*fish
= &world
.fishes
[i
];
2617 if( !(fish
->state
== k_fish_state_alive
||
2618 fish
->state
== k_fish_state_soon_alive
) ) continue;
2620 struct cell
*cell_x
= pcell( fish
->pos
);
2622 colour_code_v3( fish
->colour
, glow_colour
);
2625 if( cell_x
->config
== k_cell_type_split
)
2626 c
= cell_x
->state
& FLAG_FLIP_FLOP
? 1:0;
2628 if( cell_x
->config
== k_cell_type_merge
)
2629 c
= fish
->dir
[0]==-1?1:0;
2631 v3_muladds( cell_x
->glow
[c
], glow_colour
,
2632 powf(world
.frame_lerp
,2.0f
)*0.03f
* world
.sim_delta_speed
,
2637 for( int i
= 0; i
< vg_list_size( render_lists
); i
++ )
2639 struct render_list
*list
= &render_lists
[i
];
2640 for( int j
= 0; j
< list
->count
; j
++ )
2642 struct render_cmd
*cmd
= &list
->arr
[j
];
2643 struct cell
*cell
= cmd
->ptr
;
2645 render_tile( cmd
->pos
, cell
, regular_colour
, selected_colour
, with_glow
);
2650 static int world_button_exec( struct world_button
*btn
, v2f texture
, v3f colour
, enum world_button_status
*status
)
2652 static v2i click_grab
= { -9999, -9999 };
2657 click_grab
[0] = -9999;
2658 click_grab
[1] = -9999;
2662 v2i click_tile
= { world
.tile_x
, world
.tile_y
};
2665 int is_hovering
= v2i_eq( click_tile
, btn
->position
) && !gui_want_mouse();
2667 // Set up light targets before logic runs
2669 btn
->light_target
= is_hovering
? 0.7f
: 0.6f
;
2671 btn
->light_target
= is_hovering
? 0.2f
: 0.0f
;
2673 if( vg_get_button( "primary" ) && is_hovering
)
2674 btn
->light_target
= 1.0f
;
2676 // Process click action
2679 if( vg_get_button_down( "primary" ) && is_hovering
)
2680 v2i_copy( click_tile
, click_grab
);
2681 else if( v2i_eq( click_grab
, click_tile
) && vg_get_button_up( "primary" ) )
2684 *status
= btn
->state
? k_world_button_on_disable
: k_world_button_on_enable
;
2686 if( btn
->mode
== k_world_button_mode_toggle
)
2689 sfx_set_play( &audio_clicks
, &audio_system_ui
, btn
->state
? 1:0 );
2697 btn
->light
= vg_lerpf( btn
->light
, btn
->light_target
+ btn
->extra_light
, vg_time_delta
*26.0f
);
2699 v3_copy( colour
, final_colour
);
2700 final_colour
[3] = btn
->light
;
2702 glUniform4f( SHADER_UNIFORM( shader_buttons
, "uOffset" ),
2708 glUniform4fv( SHADER_UNIFORM( shader_buttons
, "uColour" ), 1, final_colour
);
2714 static void level_selection_buttons(void)
2716 v3f tutorial_colour
= { 0.204f
, 0.345f
, 0.553f
};
2717 v3f locked_colour
= { 0.2f
, 0.2f
, 0.2f
};
2719 struct cmp_level
*switch_level_to
= NULL
;
2721 for( int i
= 0; i
< vg_list_size( career_packs
); i
++ )
2723 struct career_level_pack
*grid
= &career_packs
[i
];
2725 for( int j
= 0; j
< grid
->count
; j
++ )
2727 struct cmp_level
*lvl
= &grid
->pack
[ j
];
2729 if( world
.pCmpLevel
== lvl
)
2730 lvl
->btn
.extra_light
= 0.35f
+ fabsf(sinf( vg_time
* 2.0f
)) * 0.05f
;
2731 else lvl
->btn
.extra_light
= 0.2f
;
2733 if( lvl
->completed_score
)
2734 lvl
->btn
.extra_light
+= 0.6f
;
2736 enum world_button_status status
;
2737 if( world_button_exec(
2740 lvl
->unlocked
? (lvl
->is_tutorial
? tutorial_colour
: grid
->primary_colour
): locked_colour
,
2744 if( status
== k_world_button_on_enable
&& lvl
->unlocked
)
2745 switch_level_to
= lvl
;
2750 if( switch_level_to
)
2752 world
.st
.lvl_to_load
= switch_level_to
;
2753 world
.st
.lvl_load_time
= vg_time
+ 0.25f
;
2754 world
.st
.world_transition
= 1.0f
;
2757 if( console_changelevel( 1, &switch_level_to->map_name ) )
2759 world.pCmpLevel = switch_level_to;
2760 gen_level_text( world.pCmpLevel );
2766 static void render_sprite( enum sprites_auto_combine_index id
, v3f pos
)
2768 struct vg_sprite
*sp
= &sprites_auto_combine
[ id
];
2770 glUniform4fv( SHADER_UNIFORM( shader_sprite
, "uUv" ), 1, sp
->uv_xywh
);
2771 glUniform3f( SHADER_UNIFORM( shader_sprite
, "uPos" ),
2772 pos
[0], pos
[1], pos
[2] * world
.st
.world_transition
);
2777 static void vg_framebuffer_resize(int w
, int h
)
2779 glBindTexture( GL_TEXTURE_2D
, world
.st
.colourbuffer
);
2780 glTexImage2D( GL_TEXTURE_2D
, 0, GL_RGB
, w
, h
,
2781 0, GL_RGB
, GL_UNSIGNED_BYTE
, NULL
);
2783 for( int i
=0; i
<2; i
++ )
2785 glBindTexture( GL_TEXTURE_2D
, world
.st
.bloomcolourbuffer
[i
] );
2786 glTexImage2D( GL_TEXTURE_2D
, 0, GL_RGB
,
2787 w
/EFFECT_BUFFER_RATIO
, h
/EFFECT_BUFFER_RATIO
,
2788 0, GL_RGB
, GL_UNSIGNED_BYTE
, NULL
);
2792 void vg_render(void)
2794 if( enable_bloom
|| enable_vignette
)
2795 glBindFramebuffer( GL_FRAMEBUFFER
, world
.st
.framebuffer
);
2797 glBindFramebuffer( GL_FRAMEBUFFER
, 0 );
2799 glViewport( 0,0, vg_window_x
, vg_window_y
);
2801 glDisable( GL_DEPTH_TEST
);
2802 glClearColor( 0.14f
, 0.14f
, 0.14f
, 1.0f
);
2803 glClear( GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
);
2805 v4f
const colour_default
= {1.0f
, 1.0f
, 1.0f
, 1.0f
};
2806 v4f
const colour_selected
= {0.90f
, 0.92f
, 1.0f
, 1.0f
};
2808 int const circle_base
= 6;
2809 int const filled_start
= circle_base
+0;
2810 int const filled_count
= circle_base
+32;
2811 int const empty_start
= circle_base
+32;
2812 int const empty_count
= circle_base
+32*2;
2815 struct world_theme
*theme
= &world_themes
[ world_theme_id
];
2817 struct world_theme
*theme
= &world_themes
[ 0 ];
2820 if( !world
.initialzed
)
2823 // Extract render commands
2824 world
.tile_count
= 0;
2825 world
.tile_special_count
= 0;
2827 for( int y
= 1; y
< world
.h
-1; y
++ )
2829 for( int x
= 1; x
< world
.w
-1; x
++ )
2831 struct cell
*cell
= pcell((v2i
){x
,y
});
2833 if( cell
->state
& (FLAG_CANAL
|FLAG_INPUT
|FLAG_OUTPUT
|FLAG_EMITTER
|FLAG_INPUT_NICE
) )
2835 struct render_cmd
*cmd
;
2838 (cell
->config
== k_cell_type_split
&& (cell
->state
& FLAG_CANAL
))
2839 || (cell
->state
& (FLAG_EMITTER
|FLAG_IS_TRIGGER
))
2841 cmd
= &world
.cmd_buf_tiles
[ world
.max_commands
- (++ world
.tile_special_count
) ];
2843 cmd
= &world
.cmd_buf_tiles
[ world
.tile_count
++ ];
2849 int world_paused
= world
.st
.buttons
[k_world_button_pause
].state
;
2852 float decay
= 1.0f
- world
.sim_delta_speed
*0.005f
;
2853 v3_muls( cell
->glow
[0], decay
, cell
->glow
[0] );
2854 v3_muls( cell
->glow
[1], decay
, cell
->glow
[1] );
2860 world
.cmd_buf_specials
= &world
.cmd_buf_tiles
[ world
.max_commands
- world
.tile_special_count
];
2863 // ========================================================================================================
2864 use_mesh( &world
.shapes
);
2866 SHADER_USE( shader_background
);
2867 glUniformMatrix3fv( SHADER_UNIFORM( shader_background
, "uPv" ), 1, GL_FALSE
, (float *)vg_pv
);
2869 glActiveTexture( GL_TEXTURE0
);
2870 glBindTexture( GL_TEXTURE_2D
, world
.background_data
);
2871 glUniform1i( SHADER_UNIFORM( shader_background
, "uTexMain" ), 0 );
2873 glUniform3f( SHADER_UNIFORM( shader_background
, "uOffset" ), -16, -16, 64 );
2874 glUniform1f( SHADER_UNIFORM( shader_background
, "uVariance" ), 0.05f
);
2876 glActiveTexture( GL_TEXTURE1
);
2877 glBindTexture( GL_TEXTURE_2D
, world
.random_samples
);
2878 glUniform1i( SHADER_UNIFORM( shader_background
, "uSamplerNoise" ), 1 );
2879 glUniform1f( SHADER_UNIFORM( shader_background
, "uVisibility" ), 1.0f
); //world.st.world_transition );
2883 // TILESET BACKGROUND LAYER
2884 // ========================================================================================================
2885 use_mesh( &world
.shapes
);
2886 SHADER_USE( shader_tile_main
);
2889 m2x2_identity( subtransform
);
2890 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main
, "uSubTransform" ), 1, GL_FALSE
, (float *)subtransform
);
2891 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main
, "uPv" ), 1, GL_FALSE
, (float *)vg_pv
);
2892 glUniform1f( SHADER_UNIFORM( shader_tile_main
, "uGhost" ), 0.0f
);
2893 glUniform1f( SHADER_UNIFORM( shader_tile_main
, "uForeground" ), 0.0f
);
2894 glUniform1f( SHADER_UNIFORM( shader_tile_main
, "uVisibility" ), world
.st
.world_transition
* 2.0f
);
2897 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
2898 glBlendEquation(GL_FUNC_ADD
);
2901 vg_tex2d_bind( &tex_tile_data
, 0 );
2902 vg_tex2d_bind( theme
->tex_tiles
, 1 );
2903 vg_tex2d_bind( &tex_tile_glow
, 2 );
2905 glUniform1i( SHADER_UNIFORM( shader_tile_main
, "uTexGlyphs" ), 0 );
2906 glUniform1i( SHADER_UNIFORM( shader_tile_main
, "uTexWood" ), 1 );
2907 glUniform1i( SHADER_UNIFORM( shader_tile_main
, "uTexGlow" ), 2 );
2909 glUniform3fv( SHADER_UNIFORM( shader_tile_main
, "uShadowing" ), 1, theme
->col_shadow
);
2911 render_tiles( colour_default
, colour_default
, 1 );
2914 // ========================================================================================================
2915 SHADER_USE( shader_ball
);
2916 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball
, "uPv" ), 1, GL_FALSE
, (float *)vg_pv
);
2918 vg_tex2d_bind( &tex_ball_noise
, 0 );
2919 glUniform1i( SHADER_UNIFORM( shader_ball
, "uTexMain" ), 0 );
2921 if( world
.st
.buttons
[ k_world_button_sim
].state
)
2923 for( int i
= 0; i
< world
.num_fishes
; i
++ )
2925 struct fish
*fish
= &world
.fishes
[i
];
2927 render_pos
[2] = 1.0f
;
2929 if( fish
->state
== k_fish_state_dead
|| fish
->state
== k_fish_state_soon_dead
)
2931 float death_anim_time
= world
.sim_internal_time
- fish
->death_time
;
2934 if( death_anim_time
> 0.0f
&& death_anim_time
< 1.0f
)
2936 float amt
= 1.0f
-death_anim_time
*death_anim_time
;
2938 v2_muladds( fish
->physics_co
, fish
->physics_v
, -1.0f
* world
.sim_internal_delta
* amt
, fish
->physics_co
);
2939 render_pos
[2] = amt
;
2941 else if( world
.sim_internal_time
> fish
->death_time
)
2944 else if( fish
->state
== k_fish_state_bg
)
2947 v2_copy( fish
->physics_co
, render_pos
);
2949 v4f dot_colour
= { 0.0f
, 0.0f
, 0.0f
, 1.0f
};
2950 colour_code_v3( fish
->colour
, dot_colour
);
2952 glUniform3fv( SHADER_UNIFORM( shader_ball
, "uColour" ), 1, dot_colour
);
2953 glUniform3fv( SHADER_UNIFORM( shader_ball
, "uOffset" ), 1, render_pos
);
2954 glUniform2f( SHADER_UNIFORM( shader_ball
, "uTexOffset" ), (float)i
* 1.2334, (float)i
* -0.3579f
);
2959 // TILESET FOREGROUND LAYER
2960 // ========================================================================================================
2961 SHADER_USE( shader_tile_main
);
2964 vg_tex2d_bind( &tex_tile_data
, 0 );
2965 vg_tex2d_bind( theme
->tex_tiles
, 1 );
2966 vg_tex2d_bind( &tex_tile_glow
, 2 );
2968 glUniform1f( SHADER_UNIFORM( shader_tile_main
, "uForeground" ), 1.0f
);
2969 render_tiles( colour_default
, colour_selected
, 0 );
2972 for( int i
= 0; i
< world
.tile_special_count
; i
++ )
2974 struct render_cmd
*cmd
= &world
.cmd_buf_specials
[i
];
2975 struct cell
*cell
= cmd
->ptr
;
2977 if( cell
->config
== k_cell_type_split
)
2979 float rotation
= cell
->state
& FLAG_FLIP_FLOP
? vg_rad( -45.0f
): vg_rad( 45.0f
);
2981 if( cell
->state
& FLAG_FLIP_ROTATING
)
2983 if( (world
.frame_lerp
> curve_7_linear_section
) )
2985 float const rotation_speed
= 0.4f
;
2986 if( (world
.frame_lerp
< 1.0f
-rotation_speed
) )
2988 float t
= world
.frame_lerp
- curve_7_linear_section
;
2989 t
*= -2.0f
* (1.0f
/(1.0f
-(curve_7_linear_section
+rotation_speed
)));
2999 m2x2_create_rotation( subtransform
, rotation
);
3001 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main
,"uSubTransform" ),
3002 1, GL_FALSE
, (float *)subtransform
);
3004 glUniform4f( SHADER_UNIFORM( shader_tile_main
, "uOffset" ),
3006 (float)cmd
->pos
[1] + 0.125f
,
3007 cell
->state
& FLAG_TARGETED
? 3.0f
: 2.0f
,
3015 // ========================================================================================================
3016 if( world
.selected
!= -1 && !(world
.data
[ world
.selected
].state
& FLAG_CANAL
) && !world
.id_drag_from
)
3018 v2i new_begin
= { world
.tile_x
- 2, world
.tile_y
- 2 };
3019 v2i new_end
= { world
.tile_x
+ 2, world
.tile_y
+ 2 };
3021 world
.data
[ world
.selected
].state
^= FLAG_CANAL
;
3022 map_reclassify( new_begin
, new_end
, 0 );
3024 m2x2_identity( subtransform
);
3025 glUniform1f( SHADER_UNIFORM( shader_tile_main
, "uGhost" ), 1.0f
);
3026 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main
, "uSubTransform" ), 1, GL_FALSE
, (float *)subtransform
);
3027 glUniform2fv( SHADER_UNIFORM( shader_tile_main
, "uMousePos" ), 1, world
.tile_pos
);
3029 render_tile_block( new_begin
, new_end
, colour_default
, colour_default
);
3031 world
.data
[ world
.selected
].state
^= FLAG_CANAL
;
3032 map_reclassify( new_begin
, new_end
, 0 );
3036 // ========================================================================================================
3037 SHADER_USE( shader_buttons
);
3038 glUniformMatrix3fv( SHADER_UNIFORM( shader_buttons
, "uPv" ), 1, GL_FALSE
, (float *)vg_pv
);
3040 vg_tex2d_bind( &tex_buttons
, 0 );
3041 glUniform1i( SHADER_UNIFORM( shader_buttons
, "uTexMain" ), 0 );
3043 enum world_button_status stat
;
3044 int world_paused
= world
.st
.buttons
[k_world_button_pause
].state
;
3045 int world_running
= world
.st
.buttons
[k_world_button_sim
].state
;
3047 float sim_icon_x
= world_paused
? 3.0f
: (world_running
? 2.0f
: 0.0f
);
3049 v3f btn_dark_blue
= { 0.204f
, 0.345f
, 0.553f
};
3050 v3f btn_orange
= { 0.553f
, 0.345f
, 0.204f
};
3052 if( world_button_exec( &world
.st
.buttons
[k_world_button_sim
], (v2f
){ sim_icon_x
, 3.0f
}, btn_dark_blue
, &stat
))
3054 if( stat
== k_world_button_on_enable
)
3059 world
.pause_offset_target
= 0.5f
;
3065 // Trigger single step
3066 world
.pause_offset_target
+= 1.0f
;
3067 world
.st
.buttons
[k_world_button_sim
].state
= 1;
3076 if( world_button_exec( &world
.st
.buttons
[k_world_button_pause
], (v2f
){ 1.0f
, 3.0f
}, btn_dark_blue
, &stat
))
3078 world
.sim_internal_ref
= world
.sim_internal_time
;
3079 world
.sim_delta_ref
= vg_time
;
3081 if( stat
== k_world_button_on_enable
)
3083 float time_frac
= world
.sim_internal_time
-floorf(world
.sim_internal_time
);
3084 world
.pause_offset_target
= 0.5f
- time_frac
;
3087 world
.pause_offset_target
= 0.0f
;
3090 if( world_button_exec( &world
.st
.buttons
[k_world_button_speedy
], (v2f
){ 0.0f
, 2.0f
}, btn_orange
, &stat
))
3092 world
.sim_delta_speed
= stat
== k_world_button_on_enable
? 10.0f
: 2.5f
;
3096 world
.sim_delta_ref
= vg_time
;
3097 world
.sim_internal_ref
= world
.sim_internal_time
;
3101 if( world_button_exec( &world
.st
.buttons
[k_world_button_settings
], (v2f
){ 1.0f
, 2.0f
}, btn_orange
, &stat
))
3103 world
.st
.state
= stat
== k_world_button_on_enable
?
3104 k_game_state_settings
: k_game_state_main
;
3107 level_selection_buttons();
3109 if( vg_get_button_up( "primary" ) )
3110 world_button_exec( NULL
, NULL
, NULL
, NULL
);
3113 // ========================================================================================================
3115 //glEnable(GL_BLEND);
3116 SHADER_USE( shader_tile_colour
);
3117 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour
, "uPv" ), 1, GL_FALSE
, (float *)vg_pv
);
3119 for( int i
= 0; i
< arrlen( world
.io
); i
++ )
3121 struct cell_terminal
*term
= &world
.io
[ i
];
3122 struct cell
*cell
= pcell(term
->pos
);
3124 int is_input
= cell
->state
& FLAG_INPUT
;
3125 v4f dot_colour
= { 0.0f
, 0.0f
, 0.0f
, 1.0f
};
3127 if( cell
->state
& FLAG_EMITTER
)
3129 for( int j
= 0; j
< 2; j
++ )
3131 if( cell
->emit
[j
] != -1 )
3133 colour_code_v3( cell
->emit
[j
], dot_colour
);
3135 glUniform3f( SHADER_UNIFORM( shader_tile_colour
, "uOffset" ),
3136 term
->pos
[0] + 0.25f
+ (float)j
* 0.5f
,
3137 term
->pos
[1] + 0.25f
,
3141 glUniform4fv( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1, dot_colour
);
3142 draw_mesh( filled_start
, filled_count
);
3148 for( int k
= 0; k
< term
->run_count
; k
++ )
3150 float arr_base
= is_input
? 1.2f
: -0.2f
,
3151 run_offset
= (is_input
? 0.2f
: -0.2f
) * (float)k
,
3152 y_position
= is_input
?
3153 (arr_base
+ (float)term
->pos
[1] + (float)(term
->run_count
-1)*0.2f
) - run_offset
:
3154 (float)term
->pos
[1] + arr_base
+ run_offset
;
3159 if( is_simulation_running() )
3161 if( k
== world
.sim_run
)
3163 float a
= fabsf(sinf( vg_time
* 2.0f
)) * 0.075f
+ 0.075f
;
3165 v4_copy( (v4f
){ 1.0f
, 1.0f
, 1.0f
, a
}, bar_colour
);
3168 v4_copy( (v4f
){ 0.0f
, 0.0f
, 0.0f
, 0.13f
}, bar_colour
);
3172 else if( 1 || k
& 0x1 )
3175 v4_copy( (v4f
){ 1.0f
, 1.0f
, 1.0f
, 0.07f
}, bar_colour
);
3177 v4_copy( (v4f
){ 0.0f
, 0.0f
, 0.0f
, 0.13f
}, bar_colour
);
3184 glUniform4fv( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1, bar_colour
);
3185 glUniform3f( SHADER_UNIFORM( shader_tile_colour
, "uOffset" ),
3186 (float)term
->pos
[0], y_position
- 0.1f
, 1.0f
);
3191 for( int j
= 0; j
< term
->runs
[k
].step_count
; j
++ )
3193 glUniform3f( SHADER_UNIFORM( shader_tile_colour
, "uOffset" ),
3194 (float)term
->pos
[0] + 0.2f
+ 0.2f
* (float)j
,
3201 i8 colour
= term
->runs
[k
].steps
[j
];
3204 colour_code_v3( colour
, dot_colour
);
3205 glUniform4fv( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1, dot_colour
);
3207 // Draw filled if tick not passed, draw empty if empty
3208 if( (world
.sim_frame
> j
&& world
.sim_run
>= k
) || world
.sim_run
> k
)
3209 draw_mesh( empty_start
, empty_count
);
3211 draw_mesh( filled_start
, filled_count
);
3217 if( term
->runs
[k
].recv_count
> j
)
3219 colour_code_v3( term
->runs
[k
].recieved
[j
], dot_colour
);
3220 v3_muls( dot_colour
, 0.8f
, dot_colour
);
3221 glUniform4fv( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1, dot_colour
);
3223 draw_mesh( filled_start
, filled_count
);
3226 colour_code_v3( term
->runs
[k
].steps
[j
], dot_colour
);
3227 glUniform4fv( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1, dot_colour
);
3229 draw_mesh( empty_start
, empty_count
);
3236 // ========================================================================================================
3237 SHADER_USE( shader_sprite
);
3238 glUniformMatrix3fv( SHADER_UNIFORM( shader_sprite
, "uPv" ), 1, GL_FALSE
, (float *)vg_pv
);
3240 vg_tex2d_bind( &tex_sprites
, 0 );
3241 glUniform1i( SHADER_UNIFORM( shader_sprite
, "uTexMain" ), 0 );
3243 for( int i
= 0; i
< world
.tile_special_count
; i
++ )
3245 struct render_cmd
*cmd
= &world
.cmd_buf_specials
[i
];
3246 struct cell
*cell
= cmd
->ptr
;
3248 if( (cell
->config
== k_cell_type_split
) || (cell
->state
& FLAG_EMITTER
) )
3250 v2f center
= { cmd
->pos
[0] + 0.5f
, cmd
->pos
[1] + 0.5f
};
3252 v3f p0
= { 0.0f
, 0.0f
, 4.0f
};
3253 v3f p1
= { 0.0f
, 0.0f
, 4.0f
};
3255 v2_add( center
, (v2f
){ -0.25f
, -0.25f
}, p0
);
3256 v2_add( center
, (v2f
){ 0.25f
, -0.25f
}, p1
);
3258 render_sprite( k_sprite_jack_1
, p0
);
3259 render_sprite( k_sprite_jack_2
, p1
);
3261 else if( cell
->state
& FLAG_IS_TRIGGER
)
3263 v3f p0
= { 0.0f
, 0.0f
, 4.0f
};
3265 struct cell_description
*desc
= &cell_descriptions
[ cell
->config
];
3267 v2_add( (v2f
){ cmd
->pos
[0], cmd
->pos
[1] }, desc
->trigger_pos
, p0
);
3268 render_sprite( desc
->trigger_sprite
, p0
);
3273 // ========================================================================================================
3276 m3x3_identity( mvp_text
);
3277 m3x3_scale( mvp_text
, (v3f
){
3278 1.0f
/ ((float)UI_GLYPH_SPACING_X
*4.0f
),
3279 1.0f
/ -((float)UI_GLYPH_SPACING_X
*4.0f
),
3283 m3x3_mul( vg_pv
, mvp_text
, mvp_text
);
3284 ui_draw( &world
.st
.world_text
, mvp_text
);
3287 // ========================================================================================================
3290 SHADER_USE( shader_wire
);
3291 glBindVertexArray( world
.wire
.vao
);
3293 glUniformMatrix3fv( SHADER_UNIFORM( shader_wire
, "uPv" ), 1, GL_FALSE
, (float *)vg_pv
);
3295 v4f
const wire_left_colour
= { 0.9f
, 0.9f
, 0.9f
, 1.0f
};
3296 v4f
const wire_right_colour
= { 0.5f
, 0.5f
, 0.5f
, 1.0f
};
3297 v4f
const wire_drag_colour
= { 0.3f
, 0.3f
, 0.3f
, 0.6f
};
3299 glUniform1f( SHADER_UNIFORM( shader_wire
, "uTime" ), world
.frame_lerp
);
3300 glUniform1f( SHADER_UNIFORM( shader_wire
, "uGlow" ), 0.0f
);
3302 if( world
.id_drag_from
)
3304 glUniform4fv( SHADER_UNIFORM( shader_wire
, "uColour" ), 1, wire_drag_colour
);
3305 glUniform1f( SHADER_UNIFORM( shader_wire
, "uCurve" ), 0.4f
);
3306 glUniform3f( SHADER_UNIFORM( shader_wire
, "uStart" ), world
.drag_from_co
[0], world
.drag_from_co
[1], 0.20f
*world
.st
.world_transition
);
3307 glUniform3f( SHADER_UNIFORM( shader_wire
, "uEnd" ), world
.drag_to_co
[0], world
.drag_to_co
[1], 0.20f
*world
.st
.world_transition
);
3308 glDrawElements( GL_TRIANGLES
, world
.wire
.em
, GL_UNSIGNED_SHORT
, (void*)(0) );
3311 // Pulling animation
3312 float rp_x1
= world
.frame_lerp
*9.0f
;
3313 float rp_xa
= rp_x1
*expf(1.0f
-rp_x1
)* 0.36f
;
3314 float rp_x2
= 1.0f
-rp_xa
;
3316 for( int i
= 0; i
< world
.tile_special_count
; i
++ )
3318 struct render_cmd
*cmd
= &world
.cmd_buf_specials
[i
];
3319 struct cell
*cell
= cmd
->ptr
;
3321 if( cell
->state
& FLAG_TARGETED
)
3323 for( int j
= 0; j
< 2; j
++ )
3325 if( !cell
->links
[j
] )
3328 struct cell
*other_cell
= &world
.data
[ cell
->links
[ j
]];
3329 struct cell_description
*desc
= &cell_descriptions
[ other_cell
->config
];
3331 int x2
= cell
->links
[j
] % world
.w
;
3332 int y2
= (cell
->links
[j
] - x2
) / world
.w
;
3337 endpoint
[0] = (float)cmd
->pos
[0] + (j
? 0.75f
: 0.25f
);
3338 endpoint
[1] = (float)cmd
->pos
[1] + 0.25f
;
3343 v2_add( desc
->trigger_pos
, startpoint
, startpoint
);
3345 if( cmd
->ptr
->state
& FLAG_EMITTER
)
3348 colour_code_v3( cmd
->ptr
->emit
[j
], wire_colour
);
3349 wire_colour
[3] = 1.0f
;
3351 glUniform4fv( SHADER_UNIFORM( shader_wire
, "uColour" ), 1, wire_colour
);
3354 glUniform4fv( SHADER_UNIFORM( shader_wire
, "uColour" ), 1, j
? wire_right_colour
: wire_left_colour
);
3356 glUniform1f( SHADER_UNIFORM( shader_wire
, "uCurve" ), other_cell
->state
& FLAG_TRIGGERED
? rp_x2
* 0.4f
: 0.4f
);
3357 glUniform1f( SHADER_UNIFORM( shader_wire
, "uGlow" ), other_cell
->state
& FLAG_TRIGGERED
? rp_xa
: 0.0f
);
3358 glUniform3f( SHADER_UNIFORM( shader_wire
, "uEnd" ), startpoint
[0], startpoint
[1], 0.18f
*world
.st
.world_transition
);
3359 glUniform3f( SHADER_UNIFORM( shader_wire
, "uStart" ), endpoint
[0], endpoint
[1], 0.18f
*world
.st
.world_transition
);
3360 glDrawElements( GL_TRIANGLES
, world
.wire
.em
, GL_UNSIGNED_SHORT
, (void*)(0) );
3366 // ========================================================================================================
3368 SHADER_USE( shader_tile_colour
);
3369 use_mesh( &world
.shapes
);
3371 for( int i
= 0; i
< world
.tile_special_count
; i
++ )
3373 struct render_cmd
*cmd
= &world
.cmd_buf_specials
[i
];
3374 struct cell
*cell
= cmd
->ptr
;
3376 if( cell
->state
& FLAG_TARGETED
)
3378 for( int j
= 0; j
< 2; j
++ )
3380 if( !cell
->links
[j
] )
3383 struct cell
*other_cell
= &world
.data
[ cell
->links
[ j
]];
3384 struct cell_description
*desc
= &cell_descriptions
[ other_cell
->config
];
3386 int x2
= cell
->links
[j
] % world
.w
;
3387 int y2
= (cell
->links
[j
] - x2
) / world
.w
;
3391 pts
[0][0] = (float)cmd
->pos
[0] + (j
? 0.75f
: 0.25f
);
3392 pts
[0][1] = (float)cmd
->pos
[1] + 0.25f
;
3397 v2_add( desc
->trigger_pos
, pts
[1], pts
[1] );
3399 if( cell
->state
& FLAG_EMITTER
)
3402 colour_code_v3( cell
->emit
[j
], wire_colour
);
3404 v3_muls( wire_colour
, 0.8f
, wire_colour
);
3405 wire_colour
[3] = 1.0f
;
3407 glUniform4fv( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1, wire_colour
);
3410 glUniform4fv( SHADER_UNIFORM( shader_tile_colour
, "uColour" ), 1,j
?wire_right_colour
: wire_left_colour
);
3412 for( int i
= 0; i
< 2; i
++ )
3414 glUniform3f( SHADER_UNIFORM( shader_tile_colour
, "uOffset" ),
3417 0.08f
* world
.st
.world_transition
3419 draw_mesh( filled_start
, filled_count
);
3425 // SUB SPLITTER DIRECTION
3426 // ========================================================================================================
3429 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
3431 for( int i = 0; i < world.tile_special_count; i ++ )
3433 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3434 struct cell *cell = cmd->ptr;
3436 if( cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
3438 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), cmd->pos[0], cmd->pos[1], 1.0f );
3439 draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
3445 // ========================================================================================================
3446 glBlendFunc(GL_ONE
, GL_ONE
);
3447 glBlendEquation(GL_FUNC_ADD
);
3449 SHADER_USE( shader_sprite
);
3451 vg_tex2d_bind( &tex_sprites
, 0 );
3452 glUniform1i( SHADER_UNIFORM( shader_sprite
, "uTexMain" ), 0 );
3454 for( int i
= 0; i
< world
.tile_special_count
; i
++ )
3456 struct render_cmd
*cmd
= &world
.cmd_buf_specials
[i
];
3457 struct cell
*cell
= cmd
->ptr
;
3459 if( cell
->config
== k_cell_type_split
)
3461 v2f center
= { cmd
->pos
[0] + 0.5f
, cmd
->pos
[1] + 0.5f
};
3463 v3f p0
= { 0.0f
, 0.0f
, 12.0f
};
3464 v3f p1
= { 0.0f
, 0.0f
, 12.0f
};
3466 v2_add( center
, (v2f
){ -0.25f
, -0.25f
}, p0
);
3467 v2_add( center
, (v2f
){ 0.25f
, -0.25f
}, p1
);
3469 if( cell
->state
& FLAG_TARGETED
)
3471 if( cell
->state
& FLAG_FLIP_FLOP
)
3472 render_sprite( k_sprite_flare_y
, p1
);
3474 render_sprite( k_sprite_flare_b
, p0
);
3477 render_sprite( k_sprite_flare_w
, cell
->state
&FLAG_FLIP_FLOP
? p1
: p0
);
3481 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
3482 glBlendEquation(GL_FUNC_ADD
);
3484 glDisable(GL_BLEND
);
3488 float const score_bright = 1.25f;
3489 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
3490 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f );
3492 use_mesh( &world.numbers );
3493 draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
3498 if( enable_vignette
)
3499 goto image_composite
;
3504 /* Scale down image and remap colour values */
3506 vg_window_x
/EFFECT_BUFFER_RATIO
, vg_window_y
/EFFECT_BUFFER_RATIO
);
3507 glBindFramebuffer( GL_FRAMEBUFFER
, world
.st
.bloomframebuffer
[0] );
3509 SHADER_USE( shader_post_darken
);
3510 glActiveTexture( GL_TEXTURE0
);
3511 glBindTexture( GL_TEXTURE_2D
, world
.st
.colourbuffer
);
3512 glUniform1i( SHADER_UNIFORM( shader_post_darken
, "uTexMain" ), 0 );
3517 v2f res_inv
, blur_dir
;
3518 res_inv
[0] = 1.0f
/ (float)( vg_window_x
/EFFECT_BUFFER_RATIO
);
3519 res_inv
[1] = 1.0f
/ (float)( vg_window_y
/EFFECT_BUFFER_RATIO
);
3521 SHADER_USE( shader_post_blur
);
3522 glUniform1i( SHADER_UNIFORM( shader_post_blur
, "uTexMain" ), 0 );
3524 for( int i
=0; i
<1; i
++ )
3526 glBindFramebuffer( GL_FRAMEBUFFER
, world
.st
.bloomframebuffer
[1] );
3528 v2_mul( (v2f
){ 1.0f
*(float)(i
+1), 0.0f
}, res_inv
, blur_dir
);
3530 glUniform2fv( SHADER_UNIFORM(shader_post_blur
,"uDir"), 1, blur_dir
);
3531 glActiveTexture( GL_TEXTURE0
);
3532 glBindTexture( GL_TEXTURE_2D
, world
.st
.bloomcolourbuffer
[0] );
3536 v2_mul( (v2f
){ 0.0f
, 1.0f
*(float)(i
+1) }, res_inv
, blur_dir
);
3538 glBindFramebuffer( GL_FRAMEBUFFER
, world
.st
.bloomframebuffer
[0] );
3539 glUniform2fv( SHADER_UNIFORM(shader_post_blur
,"uDir"), 1, blur_dir
);
3540 glBindTexture( GL_TEXTURE_2D
, world
.st
.bloomcolourbuffer
[1] );
3544 /* Scene composite */
3545 glViewport( 0,0, vg_window_x
, vg_window_y
);
3548 glBindFramebuffer( GL_FRAMEBUFFER
, 0 );
3550 SHADER_USE( shader_post_comp
);
3552 glActiveTexture( GL_TEXTURE0
);
3553 glBindTexture( GL_TEXTURE_2D
, world
.st
.colourbuffer
);
3554 glUniform1i( SHADER_UNIFORM( shader_post_comp
, "uTexMain" ), 0 );
3556 glActiveTexture( GL_TEXTURE1
);
3557 glBindTexture( GL_TEXTURE_2D
, world
.st
.bloomcolourbuffer
[0] );
3558 glUniform1i( SHADER_UNIFORM( shader_post_comp
, "uTexBloom" ), 1 );
3560 glUniform2f( SHADER_UNIFORM( shader_post_comp
, "uComp" ),
3561 enable_bloom
? 1.0f
: 0.0f
,
3562 enable_vignette
? 0.0f
: 1.0f
);
3569 // Drawing world name
3570 if( world
.pCmpLevel
)
3572 gui_text( (ui_px
[2]){ vg_window_x
/ 2, 4 }, world
.pCmpLevel
->title
, 2, k_text_align_center
);
3573 gui_text( (ui_px
[2]){ vg_window_x
/ 2, 28 }, world
.pCmpLevel
->description
, 1, k_text_align_center
);
3577 if( world
.st
.state
== k_game_state_update
)
3581 ui_global_ctx
.cursor
[2] = 458;
3582 ui_global_ctx
.cursor
[3] = 316;
3583 ui_global_ctx
.cursor
[0] = vg_window_x
/ 2 - 229;
3584 ui_global_ctx
.cursor
[1] = vg_window_y
/ 2 - 158;
3588 gui_capture_mouse( 200 );
3589 gui_fill_rect( ui_global_ctx
.cursor
, 0xE8303030 );
3592 title_pos
[0] = ui_global_ctx
.cursor
[0] + 229;
3593 title_pos
[1] = ui_global_ctx
.cursor
[1] + 16;
3595 gui_text( title_pos
, "Update 1.5", 2, k_text_align_center
);
3597 gui_text( (ui_px
[2]){ ui_global_ctx
.cursor
[0] + 16, title_pos
[1] + 45 },
3598 "Welcome to the first update to marble computing!"
3600 "New features have been added:\n"
3602 " - Settings menu\n"
3604 " - More levels and a new block type\n"
3605 " - Scores for each level\n"
3606 " - Zooming and panning (mousewheel)\n"
3608 "There is much more in the works, such as a\n"
3609 "soundtrack, and the rest of the levels for the\n"
3612 "Thank you everyone for enjoying my game :)\n",
3613 1, k_text_align_left
3616 ui_global_ctx
.cursor
[2] = 100;
3617 ui_global_ctx
.cursor
[3] = 30;
3618 ui_global_ctx
.cursor
[0] += 229 - 50;
3619 ui_global_ctx
.cursor
[1] += 316 - 30 - 16;
3621 if( gui_button( 1 ) )
3623 world
.st
.state
= k_game_state_main
;
3625 gui_text( (ui_px
[2]){ ui_global_ctx
.cursor
[0] + 50,
3626 ui_global_ctx
.cursor
[1] + 4 }, "OK", 1, k_text_align_center
);
3633 if( world
.st
.state
== k_game_state_settings
)
3637 ui_global_ctx
.cursor
[2] = 225;
3643 gui_capture_mouse( 200 );
3645 gui_fill_rect( ui_global_ctx
.cursor
, 0xC0202020 );
3646 ui_rect_pad( ui_global_ctx
.cursor
, 8 );
3648 ui_global_ctx
.cursor
[3] = 25;
3652 gui_text( ui_global_ctx
.cursor
, "SETTINGS", 2, 0 );
3654 ui_global_ctx
.cursor
[2] = 25;
3657 if( gui_button(4) == k_button_click
)
3659 world
.st
.buttons
[ k_world_button_settings
].state
= 0;
3660 world
.st
.state
= k_game_state_main
;
3661 vg_info( "exit\n" );
3663 ui_global_ctx
.cursor
[0] += 4;
3664 ui_global_ctx
.cursor
[1] -= 4;
3665 gui_text( ui_global_ctx
.cursor
, "x", 2, 0 );
3670 // Colour scheme selection
3671 ui_global_ctx
.cursor
[1] += 30;
3673 gui_text( ui_global_ctx
.cursor
, "Colour Scheme", 1, 0 );
3674 ui_global_ctx
.cursor
[1] += 25;
3678 ui_global_ctx
.cursor
[2] = 50;
3680 for( int i
= 0; i
< 4; i
++ )
3685 u32 rgb
= 0xff000000;
3687 for( int j
= 0; j
< 3; j
++ )
3688 rgb
|= (u32
)(colour_sets
[ colour_set_id
][i
][j
]*255.0f
) << j
* 8;
3690 gui_fill_rect( ui_global_ctx
.cursor
, rgb
);
3699 ui_global_ctx
.cursor
[2] = 25;
3700 if( gui_button( 0 ) == k_button_click
)
3702 if( colour_set_id
> 0 )
3705 gui_text( ui_global_ctx
.cursor
, "<", 2, 0 );
3708 ui_global_ctx
.cursor
[2] = 150;
3711 gui_fill_rect( ui_global_ctx
.cursor
, 0x33ffffff );
3713 (ui_px
[2]){ ui_global_ctx
.cursor
[0] + 75, ui_global_ctx
.cursor
[1] + 6 },
3714 (const char *[]){ "Normal", "Extra1", "Extra2" }[ colour_set_id
],
3715 1, k_text_align_center
3720 ui_global_ctx
.cursor
[2] = 25;
3721 if( gui_button( 1 ) == k_button_click
)
3723 if( colour_set_id
< vg_list_size( colour_sets
)-1 )
3726 gui_text( ui_global_ctx
.cursor
, ">", 2, 0 );
3732 ui_global_ctx
.cursor
[1] += 16;
3735 gui_text( ui_global_ctx
.cursor
, "Tile Theme", 1, 0 );
3736 ui_global_ctx
.cursor
[1] += 20;
3740 ui_global_ctx
.cursor
[2] = 25;
3741 if( gui_button( 2 ) == k_button_click
)
3743 if( world_theme_id
> 0 )
3746 gui_text( ui_global_ctx
.cursor
, "<", 2, 0 );
3749 ui_global_ctx
.cursor
[2] = 150;
3752 gui_fill_rect( ui_global_ctx
.cursor
, 0x33ffffff );
3754 (ui_px
[2]){ ui_global_ctx
.cursor
[0] + 75, ui_global_ctx
.cursor
[1] + 6 },
3755 world_themes
[ world_theme_id
].name
, 1, k_text_align_center
3760 ui_global_ctx
.cursor
[2] = 25;
3761 if( gui_button( 3 ) == k_button_click
)
3763 if( world_theme_id
< vg_list_size( world_themes
)-1 )
3766 gui_text( ui_global_ctx
.cursor
, ">", 2, 0 );
3772 gui_text( ui_global_ctx
.cursor
, "Graphics", 1, 0 );
3773 ui_global_ctx
.cursor
[1] += 20;
3777 ui_global_ctx
.cursor
[2] = 200;
3778 if( gui_button( 5 ) == k_button_click
)
3780 enable_bloom
^= 0x1;
3782 ui_global_ctx
.cursor
[0] += 4;
3783 ui_global_ctx
.cursor
[1] += 4;
3784 gui_text( ui_global_ctx
.cursor
, enable_bloom
?
3786 "Bloom: DISABLED", 1, 0 );
3791 ui_global_ctx
.cursor
[1] += 10;
3794 ui_global_ctx
.cursor
[2] = 200;
3795 if( gui_button( 6 ) == k_button_click
)
3797 enable_vignette
^= 0x1;
3799 ui_global_ctx
.cursor
[0] += 4;
3800 ui_global_ctx
.cursor
[1] += 4;
3801 gui_text( ui_global_ctx
.cursor
, enable_vignette
?
3802 "Vignette: ENABLED":
3803 "Vignette: DISABLED", 1, 0 );
3808 ui_global_ctx
.cursor
[1] += 16;
3809 gui_text( ui_global_ctx
.cursor
, "Music Volume", 1, 0 );
3810 ui_global_ctx
.cursor
[1] += 20;
3814 ui_px slider_start
= ui_global_ctx
.cursor
[0];
3816 float const bar_width
= 45.0f
,
3818 bar_movement
= bar_total
-bar_width
,
3819 bar_start
= bar_width
* 0.5f
;
3821 ui_global_ctx
.cursor
[2] = bar_total
;
3822 ui_fill_rect( &ui_global_ctx
,
3823 ui_global_ctx
.cursor
,
3826 ui_global_ctx
.cursor
[2] = bar_width
;
3827 ui_global_ctx
.cursor
[0] = slider_start
+ music_volume
* bar_movement
;
3829 int status
= gui_button( 7 );
3831 static ui_px drag_start
= 0.0f
;
3833 if( status
== k_button_start_click
)
3834 drag_start
= ui_global_ctx
.mouse
[0];
3835 else if( ui_global_ctx
.capture_lock
&&
3836 (ui_global_ctx
.capture_mouse_id
== ui_group_id(&ui_global_ctx
,7)))
3838 ui_px drag_offset
= ui_global_ctx
.mouse
[0] - drag_start
;
3839 float offset_local
= (drag_start
+ drag_offset
- slider_start
- bar_start
) / bar_movement
;
3841 music_volume
= vg_minf( vg_maxf( offset_local
, 0.0f
), 1.0f
);
3842 music_volume_update();
3845 ui_global_ctx
.cursor
[0] += 4;
3846 ui_global_ctx
.cursor
[1] += 4;
3849 snprintf( volbuf
, 12, "%.2f", music_volume
);
3850 gui_text( ui_global_ctx
.cursor
, volbuf
, 1, 0 );
3859 #if STEAM_LEADERBOARDS
3860 void leaderboard_dispatch_score(void)
3863 sw_upload_leaderboard_score(
3864 ui_data
.upload_request
.level
->steam_leaderboard
,
3865 k_ELeaderboardUploadScoreMethodKeepBest
,
3866 ui_data
.upload_request
.score
,
3871 ui_data
.upload_request
.is_waiting
= 0;
3873 vg_success( "Dispatched leaderboard score\n" );
3876 void leaderboard_found( LeaderboardFindResult_t
*pCallback
)
3878 if( !pCallback
->m_bLeaderboardFound
)
3880 vg_error( "Leaderboard could not be found\n" );
3881 ui_data
.steam_leaderboard
= 0;
3885 const char *recieved_name
= sw_get_leaderboard_name( pCallback
->m_hSteamLeaderboard
);
3887 // Update UI state and request entries if this callback found the current UI level
3888 if( ui_data
.level_selected
)
3890 if( !strcmp( recieved_name
, ui_data
.level_selected
->map_name
) )
3892 sw_download_leaderboard_entries( pCallback
->m_hSteamLeaderboard
, k_ELeaderboardDataRequestFriends
, 0, 8 );
3893 ui_data
.level_selected
->steam_leaderboard
= pCallback
->m_hSteamLeaderboard
;
3897 // Dispatch the waiting request if there was one
3898 if( ui_data
.upload_request
.is_waiting
)
3900 if( !strcmp( recieved_name
, ui_data
.upload_request
.level
->map_name
) )
3902 ui_data
.upload_request
.level
->steam_leaderboard
= pCallback
->m_hSteamLeaderboard
;
3903 leaderboard_dispatch_score();
3909 void leaderboard_downloaded( LeaderboardScoresDownloaded_t
*pCallback
)
3911 // Update UI if this leaderboard matches what we currently have in view
3912 if( ui_data
.level_selected
->steam_leaderboard
== pCallback
->m_hSteamLeaderboard
)
3914 vg_info( "Recieved %d entries\n", pCallback
->m_cEntryCount
);
3915 ui_data
.leaderboard_count
= VG_MIN( pCallback
->m_cEntryCount
, 8 );
3917 u64_steamid local_player
= sw_get_steamid();
3919 for( int i
= 0; i
< ui_data
.leaderboard_count
; i
++ )
3921 LeaderboardEntry_t entry
;
3922 sw_get_downloaded_entry( pCallback
->m_hSteamLeaderboardEntries
, i
, &entry
, NULL
, 0 );
3924 struct leaderboard_player
*player
= &ui_data
.leaderboard_players
[i
];
3926 player
->id
= entry
.m_steamIDUser
.m_unAll64Bits
;
3927 strncpy( player
->player_name
, sw_get_friend_persona_name( player
->id
), vg_list_size( player
->player_name
)-1 );
3928 player
->score
= entry
.m_nScore
;
3930 snprintf( player
->score_text
, vg_list_size(player
->score_text
), "%d", player
->score
);
3931 player
->texture
= sw_get_player_image( player
->id
);
3933 if( player
->texture
== 0 )
3934 player
->texture
= tex_unkown
.name
;
3936 player
->is_local_player
= local_player
== player
->id
? 1: 0;
3939 if( ui_data
.leaderboard_count
)
3940 ui_data
.leaderboard_show
= 1;
3942 ui_data
.leaderboard_show
= 0;
3944 else vg_warn( "Downloaded leaderboard does not match requested!\n" );
3947 void leaderboard_set_score( struct cmp_level
*cmp_level
, u32 score
)
3949 if( ui_data
.upload_request
.is_waiting
)
3950 vg_warn( "You are uploading leaderboard entries too quickly!\n" );
3952 ui_data
.upload_request
.level
= cmp_level
;
3953 ui_data
.upload_request
.score
= score
;
3954 ui_data
.upload_request
.is_waiting
= 1;
3956 // If leaderboard ID has been downloaded already then just immediately dispatch this
3957 if( cmp_level
->steam_leaderboard
)
3958 leaderboard_dispatch_score();
3960 sw_find_leaderboard( cmp_level
->map_name
);
3965 // ===========================================================================================================
3967 static int console_credits( int argc
, char const *argv
[] )
3969 vg_info( "Aknowledgements:\n" );
3970 vg_info( " GLFW zlib/libpng glfw.org\n" );
3971 vg_info( " miniaudio MIT0 miniaud.io\n" );
3972 vg_info( " QOI MIT phoboslab.org\n" );
3973 vg_info( " STB library MIT nothings.org\n" );
3977 static int console_save_map( int argc
, char const *argv
[] )
3979 if( !world
.initialzed
)
3981 vg_error( "Tried to save uninitialized map!\n" );
3985 char map_path
[ 256 ];
3987 strcpy( map_path
, "sav/" );
3988 strcat( map_path
, world
.map_name
);
3989 strcat( map_path
, ".map" );
3991 FILE *test_writer
= fopen( map_path
, "wb" );
3994 vg_info( "Saving map to '%s'\n", map_path
);
3995 map_serialize( test_writer
);
3997 fclose( test_writer
);
4002 vg_error( "Unable to open stream for writing\n" );
4007 static int console_load_map( int argc
, char const *argv
[] )
4009 char map_path
[ 256 ];
4014 strcpy( map_path
, "sav/" );
4015 strcat( map_path
, argv
[0] );
4016 strcat( map_path
, ".map" );
4018 char *text_source
= vg_textasset_read( map_path
);
4022 strcpy( map_path
, "maps/" );
4023 strcat( map_path
, argv
[0] );
4024 strcat( map_path
, ".map" );
4026 text_source
= vg_textasset_read( map_path
);
4031 vg_info( "Loading map: '%s'\n", map_path
);
4032 world
.pCmpLevel
= NULL
;
4034 if( !map_load( text_source
, argv
[0] ) )
4036 free( text_source
);
4040 free( text_source
);
4045 vg_error( "Missing maps '%s'\n", argv
[0] );
4051 vg_error( "Missing argument <map_path>\n" );
4056 static int console_changelevel( int argc
, char const *argv
[] )
4060 // Save current level
4061 console_save_map( 0, NULL
);
4063 if( console_load_map( argc
, argv
) )
4065 world
.st
.zoom
= 0.0f
;
4072 vg_error( "Missing argument <map_path>\n" );
4078 // START UP / SHUTDOWN
4079 // ===========================================================================================================
4081 #define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
4082 X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY
4086 // Steamworks callbacks
4087 #ifdef STEAM_LEADERBOARDS
4088 sw_leaderboard_found
= &leaderboard_found
;
4089 sw_leaderboard_downloaded
= &leaderboard_downloaded
;
4092 vg_function_push( (struct vg_cmd
){
4093 .name
= "_map_write",
4094 .function
= console_save_map
4097 vg_function_push( (struct vg_cmd
){
4098 .name
= "_map_load",
4099 .function
= console_load_map
4102 vg_function_push( (struct vg_cmd
){
4104 .function
= console_changelevel
4107 vg_function_push( (struct vg_cmd
){
4109 .function
= console_credits
4112 vg_convar_push( (struct vg_convar
){
4114 .data
= &colour_set_id
,
4115 .data_type
= k_convar_dtype_i32
,
4116 .opt_i32
= { .min
= 0, .max
= 2, .clamp
= 1 },
4120 vg_convar_push( (struct vg_convar
){
4122 .data
= &world_theme_id
,
4123 .data_type
= k_convar_dtype_i32
,
4124 .opt_i32
= { .min
= 0, .max
= vg_list_size( world_themes
)-1, .clamp
= 1 },
4129 vg_convar_push( (struct vg_convar
){
4130 .name
= "enable_bloom",
4131 .data
= &enable_bloom
,
4132 .data_type
= k_convar_dtype_i32
,
4133 .opt_i32
= { .min
= 0, .max
= 1, .clamp
= 1 },
4138 vg_convar_push( (struct vg_convar
){
4139 .name
= "enable_vignette",
4140 .data
= &enable_vignette
,
4141 .data_type
= k_convar_dtype_i32
,
4142 .opt_i32
= { .min
= 0, .max
= 1, .clamp
= 1 },
4147 vg_convar_push( (struct vg_convar
){
4148 .name
= "music_volume",
4149 .data
= &music_volume
,
4150 .data_type
= k_convar_dtype_f32
,
4151 .opt_f32
= { .min
= 0.0f
, .max
= 1.0f
, .clamp
= 1 },
4153 .update
= music_volume_update
4156 // Combined quad, long quad / empty circle / filled circle mesh
4158 float combined_mesh
[6*6 + 32*6*3] = {
4159 0.0f
, 0.0f
, 0.0f
, 1.0f
, 1.0f
, 1.0f
,
4160 0.0f
, 0.0f
, 1.0f
, 1.0f
, 1.0f
, 0.0f
,
4162 0.0f
, 0.0f
, 0.0f
, 0.2f
, 1.0f
, 0.2f
,
4163 0.0f
, 0.0f
, 1.0f
, 0.2f
, 1.0f
, 0.0f
,
4165 TRANSFORM_TRI_2D( 0.15f
,0.05f
,0.4f
, 0.0f
, 1.0f
, 1.0f
, 2.0f
, 1.0f
, 0.0f
),
4166 TRANSFORM_TRI_2D( 0.15f
,0.80f
,0.4f
, 0.0f
, 0.0f
, 0.0f
, 2.0f
, 1.0f
, 1.0f
)
4169 float *circle_mesh
= combined_mesh
+ 6*6;
4172 for( int i
= 0; i
< res
; i
++ )
4174 v2f v0
= { sinf( ((float)i
/(float)res
)*VG_TAUf
), cosf( ((float)i
/(float)res
)*VG_TAUf
) };
4175 v2f v1
= { sinf( ((float)(i
+1)/(float)res
)*VG_TAUf
), cosf( ((float)(i
+1)/(float)res
)*VG_TAUf
) };
4177 circle_mesh
[ i
*6+0 ] = 0.0f
;
4178 circle_mesh
[ i
*6+1 ] = 0.0f
;
4180 v2_copy( v0
, circle_mesh
+ 32*6 + i
*12 );
4181 v2_muls( v0
, 0.8f
, circle_mesh
+ 32*6 + i
*12+2 );
4182 v2_copy( v1
, circle_mesh
+ 32*6 + i
*12+4 );
4184 v2_copy( v1
, circle_mesh
+ 32*6 + i
*12+6 );
4185 v2_muls( v1
, 0.8f
, circle_mesh
+ 32*6 + i
*12+8 );
4186 v2_muls( v0
, 0.8f
, circle_mesh
+ 32*6 + i
*12+10 );
4188 v2_copy( v0
, circle_mesh
+ i
*6+4 );
4189 v2_copy( v1
, circle_mesh
+ i
*6+2 );
4190 v2_copy( v0
, circle_mesh
+i
*6+4 );
4191 v2_copy( v1
, circle_mesh
+i
*6+2 );
4194 init_mesh( &world
.shapes
, combined_mesh
, vg_list_size( combined_mesh
) );
4199 int const num_segments
= 64;
4201 struct mesh_wire
*mw
= &world
.wire
;
4203 v2f wire_points
[ num_segments
* 2 ];
4204 u16 wire_indices
[ 6*(num_segments
-1) ];
4206 for( int i
= 0; i
< num_segments
; i
++ )
4208 float l
= (float)i
/ (float)(num_segments
-1);
4210 v2_copy( (v2f
){ l
, -0.5f
}, wire_points
[i
*2+0] );
4211 v2_copy( (v2f
){ l
, 0.5f
}, wire_points
[i
*2+1] );
4213 if( i
< num_segments
-1 )
4215 wire_indices
[ i
*6+0 ] = i
*2 + 0;
4216 wire_indices
[ i
*6+1 ] = i
*2 + 1;
4217 wire_indices
[ i
*6+2 ] = i
*2 + 3;
4218 wire_indices
[ i
*6+3 ] = i
*2 + 0;
4219 wire_indices
[ i
*6+4 ] = i
*2 + 3;
4220 wire_indices
[ i
*6+5 ] = i
*2 + 2;
4224 glGenVertexArrays( 1, &mw
->vao
);
4225 glGenBuffers( 1, &mw
->vbo
);
4226 glGenBuffers( 1, &mw
->ebo
);
4227 glBindVertexArray( mw
->vao
);
4229 glBindBuffer( GL_ARRAY_BUFFER
, mw
->vbo
);
4231 glBufferData( GL_ARRAY_BUFFER
, sizeof( wire_points
), wire_points
, GL_STATIC_DRAW
);
4232 glBindVertexArray( mw
->vao
);
4234 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER
, mw
->ebo
);
4235 glBufferData( GL_ELEMENT_ARRAY_BUFFER
, sizeof( wire_indices
), wire_indices
, GL_STATIC_DRAW
);
4238 glVertexAttribPointer( 0, 2, GL_FLOAT
, GL_FALSE
, 2*sizeof(float), (void*)0 );
4239 glEnableVertexAttribArray( 0 );
4243 mw
->em
= vg_list_size( wire_indices
);
4246 // Create info data texture
4248 glGenTextures( 1, &world
.background_data
);
4249 glBindTexture( GL_TEXTURE_2D
, world
.background_data
);
4250 glTexImage2D( GL_TEXTURE_2D
, 0, GL_RGBA
, 64, 64, 0, GL_RGBA
, GL_UNSIGNED_BYTE
, NULL
);
4254 // Create random smaples texture
4256 u8
*data
= malloc(512*512*2);
4257 for( int i
= 0; i
< 512*512*2; i
++ )
4258 data
[ i
] = rand()/(RAND_MAX
/255);
4260 glGenTextures( 1, &world
.random_samples
);
4261 glBindTexture( GL_TEXTURE_2D
, world
.random_samples
);
4262 glTexImage2D( GL_TEXTURE_2D
, 0, GL_RG
, 512, 512, 0, GL_RG
, GL_UNSIGNED_BYTE
, data
);
4269 resource_load_main();
4273 ui_init_context( &world
.st
.world_text
, 15000 );
4276 // Restore gamestate
4277 career_local_data_init();
4280 /* Create framebuffers */
4281 glGenFramebuffers( 1, &world
.st
.framebuffer
);
4282 glBindFramebuffer( GL_FRAMEBUFFER
, world
.st
.framebuffer
);
4284 glGenTextures( 1, &world
.st
.colourbuffer
);
4285 glBindTexture( GL_TEXTURE_2D
, world
.st
.colourbuffer
);
4286 glTexImage2D( GL_TEXTURE_2D
, 0, GL_RGB
, vg_window_x
, vg_window_y
,
4287 0, GL_RGB
, GL_UNSIGNED_BYTE
, NULL
);
4289 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
);
4290 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
);
4291 glFramebufferTexture2D( GL_FRAMEBUFFER
, GL_COLOR_ATTACHMENT0
, GL_TEXTURE_2D
,
4292 world
.st
.colourbuffer
, 0);
4294 /* Bloom framebuffer (quater res) */
4295 glGenFramebuffers( 2, world
.st
.bloomframebuffer
);
4296 glGenTextures( 2, world
.st
.bloomcolourbuffer
);
4298 for( int i
=0; i
<2; i
++ )
4300 glBindFramebuffer( GL_FRAMEBUFFER
, world
.st
.bloomframebuffer
[i
] );
4302 glBindTexture( GL_TEXTURE_2D
, world
.st
.bloomcolourbuffer
[i
] );
4303 glTexImage2D( GL_TEXTURE_2D
, 0, GL_RGB
,
4304 vg_window_x
/EFFECT_BUFFER_RATIO
, vg_window_y
/EFFECT_BUFFER_RATIO
,
4305 0, GL_RGB
, GL_UNSIGNED_BYTE
, NULL
);
4308 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
);
4309 glTexParameteri( GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, GL_LINEAR
);
4310 glFramebufferTexture2D( GL_FRAMEBUFFER
, GL_COLOR_ATTACHMENT0
,
4311 GL_TEXTURE_2D
, world
.st
.bloomcolourbuffer
[i
], 0);
4321 console_save_map( 0, NULL
);
4324 resource_free_main();
4326 glDeleteTextures( 1, &world
.background_data
);
4327 glDeleteTextures( 1, &world
.random_samples
);
4329 glDeleteVertexArrays( 1, &world
.wire
.vao
);
4330 glDeleteBuffers( 1, &world
.wire
.vbo
);
4331 glDeleteBuffers( 1, &world
.wire
.ebo
);
4333 free_mesh( &world
.shapes
);
4335 ui_context_free( &world
.st
.world_text
);
4340 int main( int argc
, char *argv
[] )
4342 vg_init( argc
, argv
, "Marble Computing" );