vg2 port: build script and resource loading
[fishladder.git] / fishladder_vg1.c
1 #define MARBLE_COMP_VERSION 4
2
3 enum world_button_mode
4 {
5 k_world_button_mode_once,
6 k_world_button_mode_toggle
7 };
8
9 struct world_button
10 {
11 v2i position;
12
13 float light_target, light, extra_light;
14 int state;
15
16 enum world_button_mode mode;
17 };
18
19 enum world_button_status
20 {
21 k_world_button_on_enable,
22 k_world_button_on_disable
23 };
24
25 #include "fishladder_resources_vg1.h"
26
27 #if 0
28 #define STEAM_LEADERBOARDS
29 #endif
30
31 enum cell_type
32 {
33 k_cell_type_stub = 0,
34 k_cell_type_ramp_right = 3,
35 k_cell_type_ramp_left = 6,
36 k_cell_type_split = 7,
37 k_cell_type_merge = 13,
38 k_cell_type_con_r = 1,
39 k_cell_type_con_u = 2,
40 k_cell_type_con_l = 4,
41 k_cell_type_con_d = 8
42 };
43
44 enum e_fish_state
45 {
46 k_fish_state_soon_dead = -1,
47 k_fish_state_dead = 0,
48 k_fish_state_alive,
49 k_fish_state_bg,
50 k_fish_state_soon_alive
51 };
52
53 enum e_world_button
54 {
55 k_world_button_none = -1,
56 k_world_button_sim = 0,
57 k_world_button_pause = 1,
58 k_world_button_speedy = 2,
59 k_world_button_settings = 3
60 };
61
62 enum e_game_state
63 {
64 k_game_state_main,
65 k_game_state_settings,
66 k_game_state_update
67 };
68
69 #define FLAG_CANAL 0x1
70 #define FLAG_IS_TRIGGER 0x2
71 #define FLAG_RESERVED0 0x4
72 #define FLAG_RESERVED1 0x8
73
74 #define FLAG_4B_GROUP (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1)
75
76 #define FLAG_INPUT 0x10
77 #define FLAG_OUTPUT 0x20
78 #define FLAG_WALL 0x40
79 #define FLAG_EMITTER 0x80
80
81 #define FLAG_FLIP_FLOP 0x100
82 #define FLAG_TRIGGERED 0x200
83 #define FLAG_FLIP_ROTATING 0x400
84 #define FLAG_TARGETED 0x800
85
86 #define FLAG_INPUT_NICE 0x1000
87
88 /*
89 0000 0 | 0001 1 | 0010 2 | 0011 3
90 | | | | |
91 X | X= | X | X=
92 | | |
93 0100 4 | 0101 5 | 0110 6 | 0111 7
94 | | | | |
95 =X | =X= | =X | =X=
96 | | |
97 1000 8 | 1001 9 | 1010 10 | 1011 11
98 | | | | |
99 X | X= | X | X=
100 | | | | | | |
101 1100 12 | 1101 13 | 1110 14 | 1111 15
102 | | | | |
103 =X | =X= | =X | =X=
104 | | | | | | |
105 */
106
107 static struct cell_description
108 {
109 v2i start;
110 v2i end;
111
112 int is_special;
113 int is_linear;
114
115 v2f trigger_pos;
116 enum sprites_auto_combine_index trigger_sprite;
117 }
118 cell_descriptions[] =
119 {
120 /* 0-3 */
121 { .trigger_pos = { 0.5f, 0.25f }, .trigger_sprite = k_sprite_brk_d },
122 { .start = { 1, 0 }, .end = { -1, 0 }, .trigger_pos = { 0.5f, 0.25f },
123 .trigger_sprite = k_sprite_brk_d },
124 { .start = { 0, 1 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f },
125 .trigger_sprite = k_sprite_brk_l },
126 { .start = { 0, 1 }, .end = { 1, 0 }, .trigger_pos = { 0.25f, 0.5f },
127 .trigger_sprite = k_sprite_brk_l },
128 /* 4-7 */
129 { .start = { -1, 0 }, .end = { 1, 0 }, .trigger_pos = { 0.5f, 0.25f },
130 .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, .is_linear = 1 },
133 { .start = { 0, 1 }, .end = { -1, 0 }, .trigger_pos = { 0.5f, 0.25f },
134 .trigger_sprite = k_sprite_brk_d },
135 { .start = { 0, 1 }, .is_special = 1 },
136 /* 8-11 */
137 { .start = { 0, -1 }, .end = { 0, 1 }, .trigger_pos = { 0.25f, 0.5f },
138 .trigger_sprite = k_sprite_brk_l },
139 { .start = { 1, 0 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f },
140 .trigger_sprite = k_sprite_brk_l },
141 { .start = { 0, 1 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f },
142 .trigger_sprite = k_sprite_brk_l, .is_linear = 1 },
143 { },
144 /* 12-15 */
145 { .start = { -1, 0 }, .end = { 0, -1 }, .trigger_pos = { 0.5f, 0.75f },
146 .trigger_sprite = k_sprite_brk_u },
147 { .end = { 0, -1 }, .is_special = 1, .trigger_pos = { 0.5f, 0.75f },
148 .trigger_sprite = k_sprite_brk_u },
149 { },
150 { }
151 };
152
153 v2f const curve_3[] = {{0.5f,1.0f},{0.5f,0.625f},{0.625f,0.5f},{1.0f,0.5f}};
154 v2f const curve_6[] = {{0.5f,1.0f},{0.5f,0.625f},{0.375f,0.5f},{0.0f,0.5f}};
155 v2f const curve_9[] = {{1.0f,0.5f},{0.625f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
156 v2f const curve_12[]= {{0.0f,0.5f},{0.375f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
157
158 v2f const curve_1[] = {{1.0f,0.5f},{0.8f,0.5f},{0.3f,0.5f},{0.2f,0.5f}};
159 v2f const curve_4[] = {{0.0f,0.5f},{0.3f,0.5f},{0.5f,0.5f},{0.8f,0.5f}};
160 v2f const curve_2[] = {{0.5f,1.0f},{0.5f,0.8f},{0.5f,0.3f},{0.5f,0.2f}};
161 v2f const curve_8[] = {{0.5f,0.0f},{0.5f,0.3f},{0.5f,0.5f},{0.5f,0.8f}};
162
163 v2f const curve_7[] =
164 {{0.5f,0.8438f},{0.875f,0.8438f},{0.625f,0.5f},{1.0f,0.5f}};
165 v2f const curve_7_1[] =
166 {{0.5f,0.8438f},{1.0f-0.875f,0.8438f},{1.0-0.625f,0.5f},{0.0f,0.5f}};
167
168 float const curve_7_linear_section = 0.1562f;
169
170 struct mesh
171 {
172 GLuint vao, vbo;
173 u32 elements;
174 };
175
176 #define EFFECT_BUFFER_RATIO 4
177
178 static struct world
179 {
180 /* Things that are 'static', aka, initialized once */
181 struct
182 {
183 struct world_button buttons[4];
184 float zoom;
185 enum e_game_state state;
186
187 struct cmp_level *lvl_to_load;
188 float lvl_load_time;
189
190 float world_transition;
191 ui_ctx world_text;
192
193 GLuint framebuffer,
194 colourbuffer,
195 bloomframebuffer[2], /* Quater res */
196 bloomcolourbuffer[2];
197 }
198 st;
199
200 struct cell
201 {
202 u16 state;
203 u16 links[2];
204 u8 config;
205 i8 emit[2];
206
207 v3f glow[2];
208 }
209 *data;
210
211 struct render_cmd
212 {
213 struct cell *ptr;
214 v2i pos;
215 }
216 *cmd_buf_tiles, *cmd_buf_specials;
217
218 u32 tile_count, tile_special_count, max_commands;
219
220 int initialzed;
221 int sim_run, max_runs;
222
223 int sim_frame, sim_target;
224 float sim_internal_time,
225 sim_internal_delta,
226 sim_internal_ref,
227 sim_delta_ref,
228 sim_delta_speed,
229 frame_lerp,
230 pause_offset_target;
231
232 struct cell_terminal
233 {
234 struct terminal_run
235 {
236 i8 steps[8];
237 i8 recieved[8];
238
239 int step_count, recv_count;
240 }
241 runs[8];
242
243 int run_count;
244 v2i pos;
245 }
246 *io;
247
248 int w, h;
249
250 struct mesh shapes;
251 struct mesh_wire
252 {
253 GLuint vao, vbo, ebo;
254 u32 em;
255 }
256 wire;
257
258 GLuint background_data;
259 GLuint random_samples;
260
261 int selected, tile_x, tile_y;
262 v2f tile_pos;
263
264 struct fish
265 {
266 v2i pos;
267 v2i dir;
268 enum e_fish_state state;
269 i8 colour;
270 int flow_reversed;
271 float death_time;
272 v2f physics_v;
273 v2f physics_co;
274 }
275 fishes[64];
276
277 int num_fishes;
278
279 char map_name[64];
280 struct cmp_level *pCmpLevel;
281
282 u32 score;
283 u32 completed;
284 u32 time;
285
286 u16 id_drag_from;
287 v2f drag_from_co;
288 v2f drag_to_co;
289 }
290 world =
291 {
292 .st =
293 {
294 .buttons = { { .mode = k_world_button_mode_toggle },
295 { .mode = k_world_button_mode_toggle },
296 { .mode = k_world_button_mode_toggle },
297 { .mode = k_world_button_mode_toggle } }
298 },
299 .selected = -1
300 };
301
302 static void colour_code_v3( i8 cc, v3f target );
303 static int hash21i( v2i p, u32 umod );
304
305 /*
306 * Mesh functions
307 */
308 static void init_mesh( struct mesh *m, float const *tris, u32 length );
309 static void free_mesh( struct mesh *m );
310 static void use_mesh( struct mesh *m );
311 static void draw_mesh( int const start, int const count );
312
313 /*
314 * World buttons
315 */
316 static void level_selection_buttons(void);
317
318 /*
319 * Map/world interface
320 */
321 static void map_free(void);
322 static void io_reset(void);
323 static struct cell *pcell( v2i pos );
324 static void lcell( int id, v2i pos );
325 static void map_reclassify( v2i start, v2i end, int update_texbuffer );
326 static void gen_level_text(void);
327 static int map_load( const char *str, const char *name );
328 static void map_serialize( FILE *stream );
329
330 /*
331 * Career
332 */
333 static void career_serialize(void);
334 static void career_unlock_level( struct cmp_level *lvl );
335 static void career_unlock_level( struct cmp_level *lvl );
336 static void career_pass_level( struct cmp_level *lvl, int score, int upload );
337 static void career_reset_level( struct cmp_level *lvl );
338 static void career_load(void);
339 static void clear_animation_flags(void);
340
341 /*
342 * Gameplay main
343 */
344 static void simulation_stop(void);
345 static void simulation_start(void);
346 static int world_check_pos_ok( v2i co, int dist );
347 static int cell_interactive( v2i co );
348
349 static void render_tiles( v4f const regular_colour,
350 v4f const selected_colour, int with_glow );
351 static void render_tile_block( v2i start, v2i end, v4f const regular_colour,
352 v4f const selected_colour );
353
354 #ifdef STEAM_LEADERBOARDS
355 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
356 void leaderboard_dispatch_score(void);
357 void leaderboard_found( LeaderboardFindResult_t *pCallback );
358 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback );
359 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
360 #endif
361
362 static int console_credits( int argc, char const *argv[] );
363 static int console_save_map( int argc, char const *argv[] );
364 static int console_load_map( int argc, char const *argv[] );
365 static int console_changelevel( int argc, char const *argv[] );
366
367 void vg_render(void);
368 void vg_ui(void);
369 void vg_update(void);
370 void vg_start(void);
371 void vg_free(void);
372 int main( int argc, char *argv[] );
373
374 /*
375 * Globals -- runtime
376 */
377 m3x3f m_projection;
378 m3x3f m_view;
379 m3x3f m_mdl;
380
381 static int colour_set_id = 0;
382 static int world_theme_id = 0;
383 static int enable_bloom = 1;
384 static int enable_vignette = 1;
385 static float music_volume = 1.0f;
386
387 static void music_volume_update(void)
388 {
389 sfx_vol_fset( &audio_volume_music, music_volume );
390 }
391
392 static v3f colour_sets[][4] =
393 {
394 { { 1.0f, 0.9f, 0.3f },
395 { 0.4f, 0.8f, 1.00f },
396 { 0.2f, 0.9f, 0.14f },
397 { 0.882f, 0.204f, 0.922f }
398 },
399 { { 1.0f, 0.9f, 0.3f },
400 { 0.4f, 0.8f, 1.00f },
401 { 0.85f, 0.85f, 0.85f },
402 { 0.2f, 0.2f, 0.2f }
403 },
404 { { 1.0f, 0.9f, 0.3f },
405 { 0.827f, 0.373f, 0.718f },
406 { 0.0f, 0.353f, 0.71f },
407 { 0.863f, 0.196f, 0.125f }
408 },
409 };
410
411 static struct world_theme
412 {
413 const char *name;
414 v3f col_shadow;
415
416 vg_tex2d *tex_tiles;
417 }
418 world_themes[] =
419 {
420 {
421 "Minimal",
422 { 0.6f, 0.6f, 0.6f },
423 &tex_tiles_min
424 },
425 {
426 "Wood",
427 { 0.89f, 0.8f, 0.7f },
428 &tex_tiles_wood
429 },
430 {
431 "Lab",
432 { 0.7f, 0.7f, 0.7f },
433 &tex_tiles_lab
434 }
435 };
436
437 static void colour_code_v3( i8 cc, v3f target )
438 {
439 if( (cc >= 0) && (cc < vg_list_size( colour_sets[0] )) )
440 {
441 v3_copy( colour_sets[colour_set_id][ cc ], target );
442 return;
443 }
444
445 vg_error( "Invalid colour code used '%d'\n", (int)cc );
446 v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
447 }
448
449 static int hash21i( v2i p, u32 umod )
450 {
451 static const int random_noise[] = {
452 0x46,0xD5,0xB8,0xD3,0xF2,0xE5,0xCC,0x07,0xD0,0xB3,0x7A,0xA2,0xC3,0xDA,0xDC,0x7F,
453 0xE0,0xB7,0x42,0xA0,0xBF,0x41,0x92,0x32,0x6F,0x0D,0x45,0xC7,0x54,0xDB,0x30,0xC2,
454 0xD5,0xDA,0x55,0x09,0xDE,0x74,0x48,0x20,0xE1,0x24,0x5C,0x4D,0x6F,0x36,0xD8,0xE9,
455 0x8D,0x8F,0x54,0x99,0x98,0x51,0xFE,0xDB,0x26,0x04,0x65,0x57,0x56,0xF3,0x53,0x30,
456 0x3D,0x16,0xC0,0xB6,0xF2,0x47,0xCF,0x62,0xB0,0x6C,0x8F,0x4F,0x8C,0x4C,0x17,0xF0,
457 0x19,0x7E,0x2D,0x81,0x8D,0xFB,0x10,0xD3,0x49,0x50,0x60,0xFD,0x38,0x15,0x3B,0xEE,
458 0x05,0xC1,0xCF,0x62,0x97,0x75,0xDF,0x4E,0x4D,0x89,0x5E,0x88,0x5C,0x30,0x8C,0x54,
459 0x1D,0x39,0x41,0xEA,0xA2,0x63,0x12,0x1B,0x8E,0x35,0x22,0x9B,0x98,0xA3,0x7F,0x80,
460 0xD6,0x27,0x94,0x66,0xB5,0x1D,0x7E,0xDF,0x96,0x28,0x38,0x3A,0xA0,0xE8,0x71,0x09,
461 0x62,0x5E,0x9D,0x53,0x58,0x1B,0x7D,0x0D,0x2D,0x99,0x77,0x83,0xC3,0x89,0xC2,0xA2,
462 0xA7,0x1D,0x78,0x80,0x37,0xC1,0x87,0xFF,0x65,0xBF,0x2C,0xF1,0xE5,0xB3,0x09,0xE0,
463 0x25,0x92,0x83,0x0F,0x8A,0x57,0x3C,0x0B,0xC6,0xBC,0x44,0x16,0xE3,0xCE,0xC3,0x0D,
464 0x69,0xD3,0xC6,0x99,0xB8,0x46,0x44,0xC4,0xF3,0x1E,0xBF,0xF5,0xB4,0xDB,0xFB,0x93,
465 0xA1,0x7B,0xC9,0x08,0x77,0x22,0xE5,0x02,0xEF,0x9E,0x90,0x94,0x8A,0xA6,0x3D,0x7E,
466 0xA2,0xA0,0x10,0x82,0x47,0x5C,0xAA,0xF8,0x2F,0x0D,0x9F,0x76,0xDA,0x99,0x0F,0xCB,
467 0xE2,0x02,0x0C,0x75,0xCA,0x35,0x29,0xA6,0x49,0x83,0x6D,0x91,0xB4,0xEC,0x31,0x69,
468 0xBA,0x13,0xF3,0xC7,0x21,0x06,0xC8,0x79,0xEF,0xB1,0x9C,0x6A,0xEE,0x64,0x9A,0xDC,
469 0x1E,0xC6,0x18,0x93,0xA9,0x7E,0x89,0x7D,0x96,0xE5,0x44,0xB8,0x00,0x15,0xAF,0x8C,
470 0x78,0x8F,0xA8,0x05,0xA7,0x07,0x25,0x9A,0xC8,0x5D,0x90,0x1A,0x41,0x53,0x30,0xD3,
471 0x24,0x33,0x71,0xB4,0x50,0x6E,0xE4,0xEA,0x0D,0x2B,0x6D,0xF5,0x17,0x08,0x74,0x49,
472 0x71,0xC2,0xAC,0xF7,0xDC,0xB2,0x7E,0xCC,0xB6,0x1B,0xB8,0xA9,0x52,0xCF,0x6B,0x51,
473 0xD2,0x4E,0xC9,0x43,0xEE,0x2E,0x92,0x24,0xBB,0x47,0x4D,0x0C,0x3E,0x21,0x53,0x19,
474 0xD4,0x82,0xE2,0xC6,0x93,0x85,0x0A,0xF8,0xFA,0x04,0x07,0xD3,0x1D,0xEC,0x03,0x66,
475 0xFD,0xB1,0xFB,0x8F,0xC5,0xDE,0xE8,0x29,0xDF,0x23,0x09,0x9D,0x7C,0x43,0x3D,0x4D,
476 0x89,0xB9,0x6F,0xB4,0x6B,0x4A,0x51,0xC3,0x94,0xF4,0x7C,0x5E,0x19,0x87,0x79,0xC1,
477 0x80,0x0C,0x45,0x12,0xEC,0x95,0xF3,0x31,0x68,0x42,0xE1,0x06,0x57,0x0E,0xA7,0xFB,
478 0x78,0x96,0x87,0x23,0xA5,0x20,0x7A,0x09,0x3A,0x45,0xE6,0xD9,0x5E,0x6A,0xD6,0xAA,
479 0x29,0x50,0x92,0x4E,0xD0,0xB5,0x91,0xC2,0x9A,0xCF,0x07,0xFE,0xB2,0x15,0xEB,0xE4,
480 0x84,0x40,0x14,0x47,0xFA,0x93,0xB9,0x06,0x69,0xDB,0xBD,0x4E,0xEA,0x52,0x9B,0xDE,
481 0x5B,0x50,0x36,0xAB,0xB3,0x1F,0xD2,0xCD,0x9C,0x13,0x07,0x7E,0x8B,0xED,0x72,0x62,
482 0x74,0x77,0x3B,0x88,0xAC,0x5B,0x6A,0xBC,0xDA,0x99,0xE8,0x24,0x90,0x5A,0xCA,0x8D,
483 0x5C,0x2B,0xF8,0xF1,0xE1,0x1D,0x94,0x11,0xEA,0xCC,0x02,0x09,0x1E,0xA2,0x48,0x67,
484 0x87,0x5A,0x7E,0xC6,0xCC,0xA3,0xFB,0xC5,0x36,0xEB,0x5C,0xE1,0xAF,0x1E,0xBE,0xE7,
485 0xD8,0x8F,0x70,0xAE,0x42,0x05,0xF5,0xCD,0x2D,0xA2,0xB0,0xFD,0xEF,0x65,0x2C,0x22,
486 0xCB,0x8C,0x8B,0xAA,0x3D,0x86,0xE2,0xCD,0xBE,0xC3,0x42,0x38,0xE3,0x9C,0x08,0xB5,
487 0xAE,0xBD,0x54,0x73,0x83,0x70,0x24,0x47,0xCA,0x4C,0x04,0xC4,0xE0,0x1D,0x40,0xED,
488 0xF4,0x2B,0x50,0x8E,0x97,0xB3,0xF0,0xA6,0x76,0xDB,0x49,0x30,0xE5,0xD9,0x71,0x07,
489 0xB2,0xF1,0x0F,0xD6,0x77,0xAA,0x72,0xC0,0xAF,0x66,0xD8,0x40,0xC6,0x08,0x19,0x8C,
490 0xD9,0x8F,0x5A,0x75,0xAC,0xBE,0xC2,0x40,0x5B,0xBD,0x0D,0x1D,0x00,0xAF,0x26,0x5E,
491 0x78,0x43,0xAA,0xC6,0x4F,0xF3,0xD8,0xE2,0x7F,0x0C,0x1E,0x77,0x4D,0x35,0x96,0x23,
492 0x32,0x44,0x03,0x8D,0x92,0xE7,0xFD,0x48,0x07,0xD0,0x58,0xFC,0x6D,0xC9,0x91,0x33,
493 0xF0,0x23,0x45,0xA4,0x29,0xB9,0xF5,0xB0,0x68,0x8F,0x7B,0x59,0x15,0x8E,0xA6,0x66,
494 0x15,0xA0,0x76,0x9B,0x69,0xCB,0x38,0xA5,0xF4,0xB4,0x6B,0xDC,0x1F,0xAB,0xAE,0x12,
495 0x77,0xC0,0x8C,0x4A,0x03,0xB9,0x73,0xD3,0x6D,0x52,0xC5,0xF5,0x6E,0x4E,0x4B,0xA3,
496 0x24,0x02,0x58,0xEE,0x5F,0xF9,0xD6,0xD0,0x1D,0xBC,0xF4,0xB8,0x4F,0xFD,0x4B,0x2D,
497 0x34,0x77,0x46,0xE5,0xD4,0x33,0x7B,0x9C,0x35,0xCD,0xB0,0x5D,0x06,0x39,0x99,0xEB,
498 0x0C,0xD0,0x0F,0xF7,0x92,0xB5,0x58,0x5B,0x5E,0x79,0x12,0xF4,0x05,0xF6,0x21,0x07,
499 0x0B,0x49,0x1A,0xFB,0xD4,0x98,0xC4,0xEF,0x7A,0xD6,0xCA,0xA1,0xDA,0xB3,0x51,0x00,
500 0x76,0xEC,0x08,0x48,0x40,0x35,0xD7,0x94,0xBE,0xF5,0x7B,0xA4,0x20,0x81,0x5F,0x82,
501 0xF3,0x6F,0x96,0x24,0x98,0xB6,0x49,0x18,0xC8,0xC5,0x8C,0xD2,0x38,0x7F,0xC4,0xF6,
502 0xAA,0x87,0xDC,0x73,0x5B,0xA1,0xAF,0xE5,0x3D,0x37,0x6B,0x85,0xED,0x38,0x62,0x7D,
503 0x57,0xBD,0xCF,0xB5,0x1B,0xA8,0xBB,0x32,0x33,0xD3,0x34,0x5A,0xC1,0x5D,0xFB,0x28,
504 0x6E,0xE1,0x67,0x51,0xBB,0x31,0x92,0x83,0xAC,0xAA,0x72,0x52,0xFD,0x13,0x4F,0x73,
505 0xD3,0xF0,0x5E,0xFC,0xBA,0xB1,0x3C,0x7B,0x08,0x76,0x03,0x38,0x1E,0xD1,0xCC,0x33,
506 0xA3,0x1E,0xFC,0xE0,0x82,0x30,0x27,0x93,0x71,0x35,0x75,0x77,0xBA,0x78,0x10,0x33,
507 0xCD,0xAB,0xCF,0x8E,0xAD,0xF9,0x32,0xC9,0x15,0x9F,0xD6,0x6D,0xA8,0xAE,0xB1,0x3F,
508 0x90,0xEB,0xD4,0xF9,0x31,0x81,0xA3,0x53,0x99,0x4B,0x3C,0x93,0x3B,0xFE,0x55,0xFF,
509 0x25,0x9F,0xCC,0x07,0xC5,0x2C,0x14,0xA7,0xA4,0x1E,0x6C,0xB6,0x91,0x2A,0xE0,0x3E,
510 0x7F,0x39,0x0A,0xD9,0x24,0x3C,0x01,0xA0,0x30,0x99,0x8E,0xB8,0x1D,0xF9,0xA7,0x78,
511 0x86,0x95,0x35,0x0E,0x21,0xDA,0x7A,0x7B,0xAD,0x9F,0x4E,0xF6,0x63,0x5B,0x96,0xBB,
512 0x87,0x36,0x3F,0xA7,0x1A,0x66,0x91,0xCD,0xB0,0x3B,0xC0,0x4F,0x54,0xD2,0x5F,0xBB,
513 0x38,0x89,0x1C,0x79,0x7E,0xA2,0x02,0xE4,0x80,0x84,0x1E,0x33,0xAB,0x74,0xFA,0xBE,
514 0x31,0x46,0x2E,0xC5,0x15,0xB9,0x12,0xE9,0xD3,0x73,0x43,0xEA,0x74,0x11,0xA7,0xC0,
515 0xD5,0xD8,0x39,0x08,0x9F,0x4F,0xC7,0x71,0x25,0x09,0x51,0x65,0xD6,0xA8,0x02,0x1F
516 };
517
518 return random_noise[ (random_noise[p[1] & 1023] + p[0]) & 1023 ] & umod;
519 }
520
521 static void init_mesh( struct mesh *m, float const *tris, u32 length )
522 {
523 m->elements = length/3;
524 glGenVertexArrays( 1, &m->vao );
525 glGenBuffers( 1, &m->vbo );
526
527 glBindVertexArray( m->vao );
528 glBindBuffer( GL_ARRAY_BUFFER, m->vbo );
529 glBufferData( GL_ARRAY_BUFFER, length*sizeof(float), tris, GL_STATIC_DRAW );
530
531 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
532 glEnableVertexAttribArray( 0 );
533
534 VG_CHECK_GL();
535 }
536
537 static void free_mesh( struct mesh *m )
538 {
539 glDeleteVertexArrays( 1, &m->vao );
540 glDeleteBuffers( 1, &m->vbo );
541 }
542
543 static void draw_mesh( int const start, int const count )
544 {
545 glDrawArrays( GL_TRIANGLES, start*3, count*3 );
546 }
547
548 static void use_mesh( struct mesh *m )
549 {
550 glBindVertexArray( m->vao );
551 }
552
553 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
554
555 static void map_free(void)
556 {
557 arrfree( world.data );
558 arrfree( world.io );
559
560 free( world.cmd_buf_tiles );
561 world.cmd_buf_tiles = NULL;
562
563 world.w = 0;
564 world.h = 0;
565 world.data = NULL;
566 world.io = NULL;
567 world.score = 0;
568 world.time = 0;
569 world.completed = 0;
570 world.max_runs = 0;
571 world.initialzed = 0;
572 }
573
574 static void io_reset(void)
575 {
576 for( int i = 0; i < arrlen( world.io ); i ++ )
577 {
578 struct cell_terminal *term = &world.io[i];
579
580 for( int j = 0; j < term->run_count; j ++ )
581 term->runs[j].recv_count = 0;
582 }
583 }
584
585 static struct cell *pcell( v2i pos )
586 {
587 return &world.data[ pos[1]*world.w + pos[0] ];
588 }
589
590 static void lcell( int id, v2i pos )
591 {
592 pos[0] = id % world.w;
593 pos[1] = (id - pos[0]) / world.w;
594 }
595
596 static void map_reclassify( v2i start, v2i end, int update_texbuffer )
597 {
598 v2i full_start = { 1,1 };
599 v2i full_end = { world.w-1, world.h-1 };
600
601 if( !start || !end )
602 {
603 start = full_start;
604 end = full_end;
605 }
606
607 /* Texture data */
608 u8 info_buffer[64*64*4];
609 u32 pixel_id = 0;
610
611 int px0 = vg_max( start[0], full_start[0] ),
612 px1 = vg_min( end[0], full_end[0] ),
613 py0 = vg_max( start[1], full_start[1] ),
614 py1 = vg_min( end[1], full_end[1] );
615
616 for( int y = py0; y < py1; y ++ )
617 {
618 for( int x = px0; x < px1; x ++ )
619 {
620 struct cell *cell = pcell((v2i){x,y});
621
622 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
623
624 u8 height = 0;
625 u8 config = 0x00;
626
627 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
628 {
629 for( int i = 0; i < vg_list_size( dirs ); i ++ )
630 {
631 struct cell *neighbour =
632 pcell((v2i){ x+dirs[i][0], y+dirs[i][1] });
633
634 if( neighbour->state &
635 (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
636 {
637 config |= 0x1 << i;
638 }
639 }
640
641 height = 128;
642 }
643 else
644 {
645 if( cell->state & FLAG_WALL )
646 height = 0xFF; /*-0x3F + hash21i( (v2i){x,y}, 0x3F );*/
647
648 config = cell->state & FLAG_INPUT_NICE? 0xB: 0xF;
649 }
650
651 pcell((v2i){x,y})->config = config;
652
653 u8 *info_px = &info_buffer[ (pixel_id ++)*4 ];
654 info_px[0] = height;
655 info_px[1] = cell->state & FLAG_WALL? 0: 255;
656 info_px[2] = 0;
657 info_px[3] = 0;
658
659 /*
660 * Detecting hanging links that should be removed
661 */
662 int is_trigger = cell->state & FLAG_IS_TRIGGER;
663 int trigger_invalid = cell->config == 0xF ||
664 cell->config == k_cell_type_split;
665 int is_targeted = cell->state & FLAG_TARGETED;
666 int target_invalid = (cell->config != k_cell_type_split) &&
667 !(cell->state & FLAG_EMITTER);
668
669 if((
670 (is_trigger && trigger_invalid) ||
671 (is_targeted && target_invalid)
672 ) && update_texbuffer)
673 {
674 cell->state &= ~(FLAG_TARGETED|FLAG_IS_TRIGGER);
675 for( u32 i = 0; i < 2; i ++ )
676 {
677 if( cell->links[i] )
678 {
679 struct cell *other_ptr = &world.data[ cell->links[i] ];
680 other_ptr->links[ i ] = 0;
681 other_ptr->state &= ~FLAG_IS_TRIGGER;
682
683 if( other_ptr->links[ i ^ 0x1 ] == 0 )
684 other_ptr->state &= ~FLAG_TARGETED;
685 }
686 }
687
688 cell->links[0] = 0;
689 cell->links[1] = 0;
690 }
691 }
692 }
693
694 if( update_texbuffer )
695 {
696 glBindTexture( GL_TEXTURE_2D, world.background_data );
697 glTexSubImage2D( GL_TEXTURE_2D, 0,
698 px0 + 16, py0 + 16,
699 px1-px0, py1-py0, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
700 }
701 }
702
703 static void gen_level_text(void)
704 {
705 ui_px const unit_scale_px = 4*UI_GLYPH_SPACING_X;
706 ui_begin( &world.st.world_text, world.w*unit_scale_px,
707 world.h*unit_scale_px );
708
709 if( world.pCmpLevel )
710 {
711 for( int i = 0; i < vg_list_size( world.pCmpLevel->strings ); i ++ )
712 {
713 struct world_string *wstr = &world.pCmpLevel->strings[i];
714
715 if( wstr->str )
716 {
717 ui_px pos[2];
718
719 pos[0] = -UI_GLYPH_SPACING_X/2;
720
721 if( wstr->placement == k_placement_bottom )
722 pos[1] = 2*-unit_scale_px;
723 else
724 pos[1] = (world.h-1)*-unit_scale_px -6;
725
726 ui_text( &world.st.world_text, pos, wstr->str,
727 1, k_text_align_left );
728 }
729 }
730 }
731
732 // re-create level scores
733 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
734 {
735 struct career_level_pack *set = &career_packs[i];
736
737 /*
738 ui_text( &world.st.world_text,
739 (ui_px [2]){
740 set->origin[0]*unit_scale_px,
741 -(set->origin[1]+set->dims[1]+1)*unit_scale_px + 18
742 },
743 set->title, 1, k_text_align_left );
744 */
745
746 for( int j = 0; j < set->count; j ++ )
747 {
748 struct cmp_level *lvl = &set->pack[j];
749
750 if( lvl->completed_score && !lvl->is_tutorial )
751 {
752 char num[10];
753 snprintf( num, 9, "%d", lvl->completed_score );
754
755 ui_text( &world.st.world_text,
756 (ui_px [2]){
757 lvl->btn.position[0]*unit_scale_px + unit_scale_px/2,
758 -lvl->btn.position[1]*unit_scale_px - unit_scale_px/2
759 },
760 num, 1, k_text_align_center );
761 }
762 }
763 }
764
765 ui_resolve( &world.st.world_text );
766 }
767
768 /* Usually for ignoring windows crap */
769 static int map_load_char_ignore( char c )
770 {
771 if( c == '\r' ) return 1;
772 return 0;
773 }
774
775 static int map_load_sequence_char( struct terminal_run *run, char c )
776 {
777 if( (c >= 'a' && c <= 'z') || c == ' ' )
778 {
779 i8 code = -1;
780 if( c != ' ' )
781 code = c - 'a';
782
783 run->steps[ run->step_count ++ ] = code;
784
785 return 1;
786 }
787
788 return 0;
789 }
790
791 static int map_load_is_digit( char c )
792 {
793 if( (((u32)c >= (u32)'0') && ((u32)c <= (u32)'9')) || c == '-' )
794 {
795 return 1;
796 }
797
798 return 0;
799 }
800
801 static int map_load_is_terminal( char c )
802 {
803 if( c == '+' || c == '-' || c == '*' )
804 return 1;
805 return 0;
806 }
807
808 static int map_load_apply_emitter_codes( struct cell_terminal *term )
809 {
810 struct cell *cell = pcell( term->pos );
811
812 if( cell->state & FLAG_EMITTER )
813 {
814 if( (term->run_count > 0) && (term->runs[0].step_count >= 2) )
815 {
816 cell->emit[0] = term->runs[0].steps[0];
817 cell->emit[1] = term->runs[0].steps[1];
818 }
819 else
820 {
821 vg_error( "Emitter was not assigned emit values\n" );
822 return 0;
823 }
824 }
825
826 return 1;
827 }
828
829 static void map_load_cell( struct cell *cell, char c, int cx )
830 {
831 cell->config = 0xF;
832
833 cell->links[0] = 0;
834 cell->links[1] = 0;
835
836 v3_zero( cell->glow[0] );
837 v3_zero( cell->glow[1] );
838
839 /* Input, output, emitter */
840 if( map_load_is_terminal(c) )
841 {
842 struct cell_terminal *term = arraddnptr( world.io, 1 );
843 term->pos[0] = cx;
844 term->pos[1] = world.h;
845
846 term->run_count = 1;
847 term->runs[0].step_count = 0;
848
849 switch( c )
850 {
851 case '+': cell->state = FLAG_INPUT; break;
852 case '-': cell->state = FLAG_OUTPUT; break;
853 case '*': cell->state = FLAG_EMITTER; break;
854 }
855 }
856 else if( c == '.' ) cell->state = FLAG_INPUT_NICE;
857 else if( c == '#' ) cell->state = FLAG_WALL;
858 else if( ((u32)c >= (u32)'A') && ((u32)c <= (u32)'A'+0xf) )
859 {
860 /*
861 * Canal flag bits (4bit/16 value):
862 * 0: Canal present
863 * 1: Is trigger
864 * 2: Reserved
865 * 3: Reserved
866 */
867 cell->state = ((u32)c - (u32)'A') & (FLAG_CANAL|FLAG_IS_TRIGGER);
868 world.score ++;
869 }
870 else cell->state = 0x00;
871 }
872
873 static void map_load_draw_background(void)
874 {
875 u8 info_buffer[64*64*4];
876
877 for( int x = 0; x < 64; x ++ )
878 {
879 for( int y = 0; y < 64; y ++ )
880 {
881 u8 *px = &info_buffer[((x*64)+y)*4];
882
883 #if 0
884 /* Fade out edges of world so that there isnt an obvious line */
885 int dx = 16 - VG_MIN( VG_MIN( x, 16 ), 16-VG_MAX( x-16-world.w, 0 ) ),
886 dy = 16 - VG_MIN( VG_MIN( y, 16 ), 16-VG_MAX( y-16-world.h, 0 ) ),
887
888 dist = VG_MAX( dx, dy ) * 16,
889 value = VG_MAX( 0, 0xFF-0x3F + hash21i( (v2i){x,y}, 0x3F ) - dist);
890 #endif
891
892 px[0] = 0xFF;
893 px[1] = 0;
894 px[2] = 0;
895 px[3] = 0;
896 }
897 }
898
899 /*
900 * Level selection indentation, to make it look like the buttons are in a
901 * recessed area of the map.
902 */
903 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
904 {
905 struct career_level_pack *grid = &career_packs[ i ];
906
907 int j = 0;
908
909 for( int y = 0; y < grid->dims[1]; y ++ )
910 {
911 for( int x = 0; x < grid->dims[0]; x ++ )
912 {
913 int cy = y+16+grid->origin[1],
914 cx = 16+x+grid->origin[0];
915
916 u8 *px = &info_buffer[(cy*64+cx)*4];
917 px[0] = 0x10;
918
919 if( j < grid->count )
920 {
921 struct cmp_level *lvl = &grid->pack[ j ++ ];
922 v2i_add( grid->origin, (v2i){x,y}, lvl->btn.position );
923 }
924 }
925 }
926 }
927
928 /*
929 * Recess the UI buttons, this adds a little bit of a (subtle) outline
930 * to them when the background shader is run
931 */
932 for( int i=0; i<4; i++ )
933 info_buffer[(((16+world.h-(i+2))*64)+world.w+16-1)*4] = 0x30;
934
935 /*
936 * Digging 'wires' from the output/input terminals, to make it look like
937 * there is something connecting our world.
938 */
939 for( int i = 0; i < arrlen(world.io); i ++ )
940 {
941 struct cell_terminal *term = &world.io[ i ];
942
943 v2i turtle;
944 v2i turtle_dir;
945 int original_y;
946
947 /*
948 * Only make breakouts for terminals on the edge,
949 * starting them from every position leads to some weird overlapping
950 * and confusing lines.
951 * */
952 if( !(term->pos[1] == 1 || term->pos[1] == world.h-2) )
953 continue;
954
955 turtle[0] = 16+term->pos[0];
956 turtle[1] = 16+term->pos[1];
957
958 turtle_dir[0] = 0;
959 turtle_dir[1] =
960 pcell(term->pos)->state & (FLAG_INPUT|FLAG_EMITTER)? 1: -1;
961 original_y = turtle_dir[1];
962
963 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
964 v2i_add( turtle_dir, turtle, turtle );
965
966 for( int i = 0; i < 100; i ++ )
967 {
968 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
969
970 v2i_add( turtle_dir, turtle, turtle );
971
972 if( turtle[0] == 0 ) break;
973 if( turtle[0] == 63 ) break;
974 if( turtle[1] == 0 ) break;
975 if( turtle[1] == 63 ) break;
976
977 int random_value = hash21i( turtle, 0xFF );
978 if( random_value > 255-40 && !turtle_dir[0] )
979 {
980 turtle_dir[0] = -1;
981 turtle_dir[1] = 0;
982 }
983 else if( random_value > 255-80 && !turtle_dir[0] )
984 {
985 turtle_dir[0] = 1;
986 turtle_dir[1] = 0;
987 }
988 else if( random_value > 255-100 )
989 {
990 turtle_dir[0] = 0;
991 turtle_dir[1] = original_y;
992 }
993 }
994 }
995
996 glBindTexture( GL_TEXTURE_2D, world.background_data );
997 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 64, 64,
998 GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
999 }
1000
1001 static int map_load_validate(void)
1002 {
1003 for( int i = 0; i < world.h*world.w; i ++ )
1004 {
1005 struct cell *src = &world.data[i];
1006
1007 if( src->state & FLAG_IS_TRIGGER )
1008 {
1009 int link_id = src->links[0]?0:1;
1010 if( src->links[link_id] <= world.h*world.w )
1011 {
1012 struct cell *target = &world.data[ src->links[link_id] ];
1013
1014 int is_canal = target->state & FLAG_CANAL,
1015 is_splitter = target->config == k_cell_type_split,
1016 is_emitter = target->state & FLAG_EMITTER;
1017
1018 if((is_canal && is_splitter) || is_emitter)
1019 {
1020 if( target->links[ link_id ] )
1021 {
1022 vg_error( "Link target was already targeted\n" );
1023 return 0;
1024 }
1025 else
1026 {
1027 /*
1028 * Valid link, apply reverse mapping to other cell so it
1029 * knows who is linking to it
1030 */
1031 target->links[ link_id ] = i;
1032 target->state |= FLAG_TARGETED;
1033 }
1034 }
1035 else
1036 {
1037 vg_error( "Link target was invalid\n" );
1038 return 0;
1039 }
1040 }
1041 else
1042 {
1043 vg_error( "Link target out of bounds\n" );
1044 return 0;
1045 }
1046 }
1047 }
1048
1049 return 1;
1050 }
1051
1052 static int map_load( const char *str, const char *name )
1053 {
1054 map_free();
1055
1056 char const *c = str;
1057
1058 /* Predetermine width */
1059 for(;; world.w ++)
1060 {
1061 if( c[world.w] == ';' )
1062 break;
1063 else if( !c[world.w] )
1064 {
1065 vg_error( "Unexpected EOF when parsing level\n" );
1066 return 0;
1067 }
1068 }
1069
1070 struct cell *row = arraddnptr( world.data, world.w );
1071 int cx = 0;
1072 int reg_start = 0, reg_end = 0;
1073
1074 u32 *links_to_make = NULL;
1075 int links_satisfied = 0;
1076
1077 char link_id_buffer[32];
1078 int link_id_n = 0;
1079
1080 for(;;)
1081 {
1082 if( !*c )
1083 break;
1084
1085 if( map_load_char_ignore( *c ) ) { c++; continue; }
1086
1087 if( *c == ';' )
1088 {
1089 c ++;
1090
1091 if( *c == '\r' ) c ++;
1092
1093 /* Parse attribs */
1094 if( *c != '\n' )
1095 {
1096 while( *c )
1097 {
1098 if( map_load_char_ignore( *c ) ) { c++; continue; }
1099
1100 if( reg_start < reg_end )
1101 {
1102 struct cell_terminal *terminal = &world.io[ reg_start ];
1103 struct terminal_run *run =
1104 &terminal->runs[ terminal->run_count-1 ];
1105
1106 if( !map_load_sequence_char( run, *c ) )
1107 {
1108 /* Control characters */
1109 if( *c == ',' || *c == '\n' ) /* Next terminal */
1110 {
1111 reg_start ++;
1112
1113 if( *c == '\n' )
1114 break;
1115 }
1116 else if( *c == ':' ) /* New run starting */
1117 {
1118 terminal->runs[ terminal->run_count ].step_count = 0;
1119 terminal->run_count ++;
1120 world.max_runs =
1121 vg_max( world.max_runs, terminal->run_count );
1122 }
1123 else
1124 {
1125 vg_error( "Unkown attribute '%c' (row: %u)\n",
1126 *c, world.h );
1127 goto IL_REG_ERROR;
1128 }
1129 }
1130 }
1131 else
1132 {
1133 if( links_satisfied < arrlen( links_to_make ) )
1134 {
1135 struct cell *target =
1136 &world.data[ links_to_make[ links_satisfied ] ];
1137
1138 if( map_load_is_digit( *c ) )
1139 {
1140 if( link_id_n >= vg_list_size( link_id_buffer )-1 )
1141 {
1142 vg_error( "Number was way too long to be parsed"
1143 " (row: %u)\n", world.h );
1144 goto IL_REG_ERROR;
1145 }
1146
1147 link_id_buffer[ link_id_n ++ ] = *c;
1148 }
1149 else if( *c == ',' || *c == '\n' )
1150 {
1151 link_id_buffer[ link_id_n ] = 0x00;
1152 int value = atoi( link_id_buffer );
1153
1154 target->links[value >= 0? 1:0] = abs(value);
1155 links_satisfied ++;
1156 link_id_n = 0;
1157
1158 if( *c == '\n' )
1159 break;
1160 }
1161 else
1162 {
1163 vg_error( "Invalid character '%c'"
1164 " (row: %u)\n", *c, world.h );
1165 goto IL_REG_ERROR;
1166 }
1167 }
1168 else
1169 {
1170 vg_error( "Too many values to assign"
1171 " (row: %u)\n", world.h );
1172 goto IL_REG_ERROR;
1173 }
1174 }
1175
1176 c ++;
1177 }
1178 }
1179
1180 /* Registry length-error checks */
1181 if( reg_start != reg_end )
1182 {
1183 vg_error( "Not enough spawn values assigned (row: %u, %u of %u)\n",
1184 world.h, reg_start, reg_end );
1185 goto IL_REG_ERROR;
1186 }
1187
1188 if( links_satisfied != arrlen( links_to_make ) )
1189 {
1190 vg_error( "Not enough link values assigned (row: %u, %u of %u)\n",
1191 world.h, links_satisfied, arrlen( links_to_make ) );
1192 goto IL_REG_ERROR;
1193 }
1194
1195 if( cx != world.w )
1196 {
1197 vg_error( "Not enough cells to match previous row definition"
1198 " (row: %u, %u<%u)\n", world.h, cx, world.w );
1199 goto IL_REG_ERROR;
1200 }
1201
1202 row = arraddnptr( world.data, world.w );
1203 cx = 0;
1204 world.h ++;
1205 reg_end = reg_start = arrlen( world.io );
1206
1207 arrsetlen( links_to_make, 0 );
1208 links_satisfied = 0;
1209 }
1210 else
1211 {
1212 if( cx == world.w )
1213 {
1214 vg_error( "Too many cells to match previous row definition"
1215 " (row: %u, %u>%u)\n", world.h, cx, world.w );
1216 goto IL_REG_ERROR;
1217 }
1218
1219 struct cell *cell = &row[ cx ];
1220 map_load_cell( cell, *c, cx );
1221
1222 if( map_load_is_terminal(*c) )
1223 reg_end ++;
1224
1225 if( cell->state & FLAG_IS_TRIGGER )
1226 arrpush( links_to_make, cx + world.h*world.w );
1227
1228 cx ++;
1229 }
1230
1231 c ++;
1232 }
1233
1234 for( int i = 0; i < arrlen( world.io ); i ++ )
1235 {
1236 if( !map_load_apply_emitter_codes( &world.io[i] ) )
1237 goto IL_REG_ERROR;
1238 }
1239
1240 map_load_draw_background();
1241 map_reclassify( NULL, NULL, 1 );
1242
1243 if( !map_load_validate() )
1244 goto IL_REG_ERROR;
1245
1246 /*
1247 * At this point the world is in a fully loaded and complete state
1248 */
1249
1250 arrfree( links_to_make );
1251
1252 vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h );
1253
1254 io_reset();
1255
1256 strncpy( world.map_name, name, vg_list_size( world.map_name )-1 );
1257 world.initialzed = 1;
1258
1259 /* Setup world button locations */
1260 for( int i = 0; i < vg_list_size( world.st.buttons ); i ++ )
1261 {
1262 struct world_button *btn = &world.st.buttons[i];
1263 btn->position[0] = world.w -1;
1264 btn->position[1] = world.h -i -2;
1265 }
1266
1267 /* Allocate buffers for render commands */
1268 world.cmd_buf_tiles = malloc( world.w*world.h* sizeof( struct render_cmd ) );
1269 world.max_commands = world.w*world.h;
1270 return 1;
1271
1272 IL_REG_ERROR:
1273 arrfree( links_to_make );
1274 map_free();
1275 return 0;
1276 }
1277
1278 static void map_serialize( FILE *stream )
1279 {
1280 for( int y = 0; y < world.h; y ++ )
1281 {
1282 for( int x = 0; x < world.w; x ++ )
1283 {
1284 struct cell *cell = pcell( (v2i){ x, y } );
1285
1286 if( cell->state & FLAG_WALL ) fputc( '#', stream );
1287 else if( cell->state & FLAG_INPUT_NICE ) fputc( '.', stream );
1288 else if( cell->state & FLAG_INPUT ) fputc( '+', stream );
1289 else if( cell->state & FLAG_OUTPUT ) fputc( '-', stream );
1290 else if( cell->state & FLAG_EMITTER ) fputc( '*', stream );
1291 else if( cell->state & FLAG_4B_GROUP )
1292 {
1293 /*
1294 * Serialize the '4 bit group' into ABCD...
1295 */
1296 fputc( (cell->state & FLAG_4B_GROUP) + (u32)'A', stream );
1297 }
1298 else fputc( ' ', stream );
1299 }
1300
1301 fputc( ';', stream );
1302 int terminal_write_count = 0;
1303
1304 for( int x = 0; x < world.w; x ++ )
1305 {
1306 for( int i = 0; i < arrlen( world.io ); i ++ )
1307 {
1308 struct cell_terminal *term = &world.io[i];
1309 if( v2i_eq( term->pos, (v2i){x,y} ) )
1310 {
1311 if( terminal_write_count )
1312 fputc( ',', stream );
1313 terminal_write_count ++;
1314
1315 for( int j = 0; j < term->run_count; j ++ )
1316 {
1317 struct terminal_run *run = &term->runs[j];
1318
1319 for( int k = 0; k < run->step_count; k ++ )
1320 {
1321 i8 step = run->steps[k];
1322 fputc( step == -1? ' ': ('a' + run->steps[k]), stream );
1323 }
1324
1325 if( j < term->run_count-1 )
1326 fputc( ':', stream );
1327 }
1328 }
1329 }
1330 }
1331
1332 for( int x = 0; x < world.w; x ++ )
1333 {
1334 struct cell *cell = pcell( (v2i){ x,y } );
1335 if( cell->state & FLAG_IS_TRIGGER )
1336 {
1337 if( terminal_write_count )
1338 fputc( ',', stream );
1339 terminal_write_count ++;
1340
1341 fprintf( stream, "%d",
1342 cell->links[0]? -cell->links[0]: cell->links[1] );
1343 }
1344 }
1345
1346 fputc( '\n', stream );
1347 }
1348 }
1349
1350 /*
1351 * Career state
1352 */
1353 #pragma pack(push,1)
1354 struct dcareer_state
1355 {
1356 u32 version;
1357 i32 in_map;
1358
1359 u32 reserved[14];
1360
1361 struct dlevel_state
1362 {
1363 i32 score;
1364
1365 i32 unlocked;
1366 i32 reserved[2];
1367 }
1368 levels[ NUM_CAMPAIGN_LEVELS ];
1369 };
1370 #pragma pack(pop)
1371
1372 static int career_load_success = 0;
1373
1374 static void career_serialize(void)
1375 {
1376 if( !career_load_success )
1377 return;
1378
1379 struct dcareer_state encoded;
1380 encoded.version = MARBLE_COMP_VERSION;
1381 encoded.in_map = world.pCmpLevel? world.pCmpLevel->serial_id: -1;
1382
1383 memset( encoded.reserved, 0, sizeof( encoded.reserved ) );
1384
1385 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1386 {
1387 struct career_level_pack *set = &career_packs[i];
1388
1389 for( int j = 0; j < set->count; j ++ )
1390 {
1391 struct cmp_level *lvl = &set->pack[j];
1392 struct dlevel_state *dest = &encoded.levels[lvl->serial_id];
1393
1394 dest->score = lvl->completed_score;
1395 dest->unlocked = lvl->unlocked;
1396 dest->reserved[0] = 0;
1397 dest->reserved[1] = 0;
1398 }
1399 }
1400
1401 vg_asset_write( "sav/game.sv2", &encoded, sizeof( struct dcareer_state ) );
1402 }
1403
1404 static void career_unlock_level( struct cmp_level *lvl );
1405 static void career_unlock_level( struct cmp_level *lvl )
1406 {
1407 lvl->unlocked = 1;
1408
1409 if( lvl->linked )
1410 career_unlock_level( lvl->linked );
1411 }
1412
1413 static void career_pass_level( struct cmp_level *lvl, int score, int upload )
1414 {
1415 if( score > 0 )
1416 {
1417 lvl->completed_score = score;
1418 gen_level_text();
1419
1420 if( lvl->unlock )
1421 career_unlock_level( lvl->unlock );
1422
1423 #ifdef VG_STEAM
1424 if( lvl->achievement )
1425 sw_set_achievement( lvl->achievement );
1426
1427 /* Check ALL maps to trigger master engineer */
1428 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1429 {
1430 struct career_level_pack *set = &career_packs[i];
1431
1432 for( int j = 0; j < set->count; j ++ )
1433 {
1434 if( set->pack[j].completed_score == 0 )
1435 return;
1436 }
1437 }
1438
1439 sw_set_achievement( "MASTER_ENGINEER" );
1440 #endif
1441 }
1442 }
1443
1444 static void career_reset_level( struct cmp_level *lvl )
1445 {
1446 lvl->unlocked = 0;
1447 lvl->completed_score = 0;
1448 }
1449
1450 static void career_load(void)
1451 {
1452 i64 sz;
1453 struct dcareer_state encoded;
1454
1455 /* Blank save state */
1456 memset( (void*)&encoded, 0, sizeof( struct dcareer_state ) );
1457 encoded.in_map = 0;
1458 encoded.levels[0].unlocked = 1;
1459
1460 /*
1461 * Load and copy, this step is just to ensure old/newer saves can be loaded
1462 * without crashing. Old saves will load fine, too new saves will lose data,
1463 * such a situation should rarely (never) happen with the steam version.
1464 */
1465 void *cr = vg_asset_read_s( "sav/game.sv2", &sz );
1466
1467 if( cr )
1468 {
1469 if( sz > sizeof( struct dcareer_state ) )
1470 vg_warn( "This save file is too big! Some levels will be lost\n" );
1471
1472 if( sz <= offsetof( struct dcareer_state, levels ) )
1473 {
1474 vg_warn( "This save file is too small to have a header. "
1475 "Creating a blank one\n" );
1476 free( cr );
1477 }
1478
1479 memcpy( (void*)&encoded, cr, VG_MIN( sizeof( struct dcareer_state ), sz));
1480 free( cr );
1481 }
1482 else
1483 vg_info( "No save file... Using blank one\n" );
1484
1485 /* Reset memory */
1486 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1487 {
1488 struct career_level_pack *set = &career_packs[i];
1489
1490 for( int j = 0; j < set->count; j ++ )
1491 career_reset_level( &set->pack[j] );
1492 }
1493
1494 /* Header information */
1495
1496 struct cmp_level *lvl_to_load = &career_packs[0].pack[0];
1497
1498 /* Decode everything from dstate */
1499 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1500 {
1501 struct career_level_pack *set = &career_packs[i];
1502
1503 for( int j = 0; j < set->count; j ++ )
1504 {
1505 struct cmp_level *lvl = &set->pack[j];
1506 struct dlevel_state *src = &encoded.levels[lvl->serial_id];
1507
1508 if( src->unlocked ) career_unlock_level( lvl );
1509 if( src->score )
1510 {
1511 lvl->completed_score = src->score;
1512
1513 /*
1514 * Apply unlocking trigger to next levels,
1515 * in case the level graph was updated in the future
1516 */
1517 if( lvl->unlock )
1518 career_unlock_level( lvl->unlock );
1519 }
1520
1521 if( lvl->serial_id == encoded.in_map )
1522 lvl_to_load = lvl;
1523 }
1524 }
1525
1526 if( console_changelevel( 1, &lvl_to_load->map_name ) )
1527 {
1528 world.pCmpLevel = lvl_to_load;
1529 gen_level_text();
1530 }
1531
1532 career_load_success = 1;
1533
1534 #if 0
1535 if( encoded.version < MARBLE_COMP_VERSION )
1536 world.st.state = k_game_state_update;
1537 #endif
1538 }
1539
1540 /*
1541 * Main gameplay
1542 */
1543 static int is_simulation_running(void)
1544 {
1545 return world.st.buttons[ k_world_button_sim ].state;
1546 }
1547
1548 static void clear_animation_flags(void)
1549 {
1550 for( int i = 0; i < world.w*world.h; i ++ )
1551 world.data[ i ].state &= ~(FLAG_FLIP_FLOP|FLAG_FLIP_ROTATING);
1552 }
1553
1554 static void simulation_stop(void)
1555 {
1556 world.st.buttons[ k_world_button_sim ].state = 0;
1557 world.st.buttons[ k_world_button_pause ].state = 0;
1558
1559 world.num_fishes = 0;
1560 world.sim_frame = 0;
1561 world.sim_run = 0;
1562 world.frame_lerp = 0.0f;
1563
1564 io_reset();
1565
1566 sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
1567
1568 clear_animation_flags();
1569
1570 vg_info( "Stopping simulation!\n" );
1571 }
1572
1573 static void simulation_start(void)
1574 {
1575 vg_success( "Starting simulation!\n" );
1576
1577 sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
1578
1579 world.num_fishes = 0;
1580 world.sim_frame = 0;
1581 world.sim_run = 0;
1582
1583 world.sim_delta_speed = world.st.buttons[ k_world_button_speedy ].state? 10.0f: 2.5f;
1584 world.sim_delta_ref = vg_time;
1585 world.sim_internal_ref = 0.0f;
1586 world.sim_internal_time = 0.0f;
1587 world.pause_offset_target = 0.0f;
1588
1589 world.sim_target = 0;
1590
1591 clear_animation_flags();
1592
1593 io_reset();
1594
1595 if( world.pCmpLevel )
1596 {
1597 world.pCmpLevel->completed_score = 0;
1598 gen_level_text();
1599 }
1600 }
1601
1602 static int world_check_pos_ok( v2i co, int dist )
1603 {
1604 return (co[0] < dist || co[0] >= world.w-dist || co[1] < dist || co[1] >= world.h-dist)? 0: 1;
1605 }
1606
1607 static int cell_interactive( v2i co )
1608 {
1609 struct cell *cell = NULL;
1610
1611 // Bounds check
1612 if( world_check_pos_ok( co, 1 ) )
1613 {
1614 cell = pcell( co );
1615 if( cell->state & FLAG_EMITTER )
1616 return 1;
1617 }
1618
1619 if( !world_check_pos_ok( co, 2 ) )
1620 return 0;
1621
1622 // Flags check
1623 if( cell->state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
1624 return 0;
1625
1626 // List of 3x3 configurations that we do not allow
1627 static u32 invalid_src[][9] =
1628 {
1629 { 0,1,0,
1630 1,1,1,
1631 0,1,0
1632 },
1633 { 0,0,0,
1634 0,1,1,
1635 0,1,1
1636 },
1637 { 0,0,0,
1638 1,1,0,
1639 1,1,0
1640 },
1641 { 0,1,1,
1642 0,1,1,
1643 0,0,0
1644 },
1645 { 1,1,0,
1646 1,1,0,
1647 0,0,0
1648 },
1649 { 0,1,0,
1650 0,1,1,
1651 0,1,0
1652 },
1653 { 0,1,0,
1654 1,1,0,
1655 0,1,0
1656 }
1657 };
1658
1659 // Statically compile invalid configurations into bitmasks
1660 static u32 invalid[ vg_list_size(invalid_src) ];
1661
1662 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
1663 {
1664 u32 comped = 0x00;
1665
1666 for( int j = 0; j < 3; j ++ )
1667 for( int k = 0; k < 3; k ++ )
1668 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
1669
1670 invalid[i] = comped;
1671 }
1672
1673 // Extract 5x5 grid surrounding tile
1674 u32 blob = 0x1000;
1675 for( int y = co[1]-2; y < co[1]+3; y ++ )
1676 for( int x = co[0]-2; x < co[0]+3; x ++ )
1677 {
1678 struct cell *cell = pcell((v2i){x,y});
1679
1680 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER)) )
1681 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
1682 }
1683
1684 // Run filter over center 3x3 grid to check for invalid configurations
1685 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
1686 for( int i = 0; i < vg_list_size(kernel); i ++ )
1687 {
1688 if( blob & (0x1 << (6+kernel[i])) )
1689 {
1690 u32 window = blob >> kernel[i];
1691
1692 for( int j = 0; j < vg_list_size(invalid); j ++ )
1693 if((window & invalid[j]) == invalid[j])
1694 return 0;
1695 }
1696 }
1697
1698 return 1;
1699 }
1700
1701 static void vg_update(void)
1702 {
1703 // Async events
1704 if( world.st.lvl_to_load )
1705 {
1706 world.st.world_transition = (world.st.lvl_load_time-vg_time) * 4.0f;
1707
1708 if( vg_time > world.st.lvl_load_time )
1709 {
1710 if( console_changelevel( 1, &world.st.lvl_to_load->map_name ) )
1711 {
1712 world.pCmpLevel = world.st.lvl_to_load;
1713 gen_level_text();
1714 }
1715
1716 world.st.lvl_to_load = NULL;
1717 }
1718 }
1719 else
1720 {
1721 world.st.world_transition = vg_minf( 1.0f, (vg_time-world.st.lvl_load_time) * 4.0f );
1722 }
1723
1724 // Camera
1725 // ========================================================================================================
1726
1727 float r1 = (float)vg_window_y / (float)vg_window_x,
1728 r2 = (float)world.h / (float)world.w,
1729 size;
1730
1731 static float size_current = 2.0f;
1732 static v3f origin_current = { 0.0f, 0.0f, 0.0f };
1733 static v2f drag_offset = { 0.0f, 0.0f };
1734 static v2f view_point = { 0.0f, 0.0f };
1735 v2f result_view;
1736
1737 size = ( r2 < r1? (float)(world.w+5) * 0.5f: ((float)(world.h+5) * 0.5f) / r1 ) + 0.5f;
1738
1739 v2f origin;
1740 v2f vt_target;
1741
1742 origin[0] = floorf( -0.5f * ((float)world.w-4.5f) );
1743 origin[1] = floorf( -0.5f * world.h );
1744
1745 // Create and clamp result view
1746 v2_add( view_point, drag_offset, result_view );
1747 result_view[0] = vg_clampf( result_view[0], -world.st.zoom, world.st.zoom );
1748 result_view[1] = vg_clampf( result_view[1], -world.st.zoom*r1, world.st.zoom*r1 );
1749
1750 v2_add( origin, result_view, vt_target );
1751
1752 // Lerp towards target
1753 size_current = vg_lerpf( size_current, size - world.st.zoom, vg_time_delta * 6.0f );
1754 v2_lerp( origin_current, vt_target, vg_time_delta * 6.0f, origin_current );
1755
1756 m3x3_projection( m_projection, -size_current, size_current, -size_current*r1, size_current*r1 );
1757 m3x3_identity( m_view );
1758 m3x3_translate( m_view, origin_current );
1759 m3x3_mul( m_projection, m_view, vg_pv );
1760 vg_projection_update();
1761
1762 if( world.st.state == k_game_state_update )
1763 return;
1764
1765 // Mouse input
1766 // ========================================================================================================
1767 v2_copy( vg_mouse_ws, world.tile_pos );
1768
1769 world.tile_x = floorf( world.tile_pos[0] );
1770 world.tile_y = floorf( world.tile_pos[1] );
1771
1772 // Camera dragging
1773 {
1774 static v2f drag_origin; // x/y pixel
1775
1776 if( vg_get_button_down( "tertiary" ) )
1777 v2_copy( vg_mouse, drag_origin );
1778 else if( vg_get_button( "tertiary" ) )
1779 {
1780 // get offset
1781 v2_sub( vg_mouse, drag_origin, drag_offset );
1782 v2_div( drag_offset, (v2f){ vg_window_x, vg_window_y }, drag_offset );
1783 v2_mul( drag_offset, (v2f){ size_current*2.0f, -size_current*r1*2.0f }, drag_offset );
1784 }
1785 else
1786 {
1787 v2_copy( result_view, view_point );
1788 v2_copy( (v2f){0.0f,0.0f}, drag_offset );
1789 }
1790 }
1791
1792 // Zooming
1793 {
1794 v2f mview_local;
1795 v2f mview_new;
1796 v2f mview_cur;
1797 v2f mview_delta;
1798 float rsize;
1799
1800 rsize = size-world.st.zoom;
1801
1802 v2_div( vg_mouse, (v2f){ vg_window_x*0.5f, vg_window_y*0.5f }, mview_local );
1803 v2_add( (v2f){ -rsize, -rsize*r1 }, (v2f){ mview_local[0]*rsize, (2.0f-mview_local[1])*rsize*r1 }, mview_cur );
1804
1805 world.st.zoom = vg_clampf( world.st.zoom + vg_mouse_wheel[1], 0.0f, size - 4.0f );
1806
1807 // Recalculate new position
1808 rsize = size-world.st.zoom;
1809 v2_add( (v2f){ -rsize, -rsize*r1 }, (v2f){ mview_local[0]*rsize, (2.0f-mview_local[1])*rsize*r1 }, mview_new );
1810
1811 // Apply offset
1812 v2_sub( mview_new, mview_cur, mview_delta );
1813 v2_add( mview_delta, view_point, view_point );
1814 }
1815
1816 // Tilemap
1817 // ========================================================================================================
1818 if( !is_simulation_running() && !gui_want_mouse() )
1819 {
1820 v2_copy( vg_mouse_ws, world.drag_to_co );
1821
1822 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
1823 {
1824 world.selected = world.tile_y * world.w + world.tile_x;
1825
1826 static u32 modify_state = 0;
1827 struct cell *cell_ptr = &world.data[world.selected];
1828
1829 if( !(cell_ptr->state & FLAG_EMITTER) )
1830 {
1831 if( vg_get_button_down("primary") )
1832 modify_state = (cell_ptr->state & FLAG_CANAL) ^ FLAG_CANAL;
1833
1834 if( vg_get_button("primary") && ((cell_ptr->state & FLAG_CANAL) != modify_state) )
1835 {
1836 cell_ptr->state &= ~FLAG_CANAL;
1837 cell_ptr->state |= modify_state;
1838
1839 if( cell_ptr->state & FLAG_CANAL )
1840 {
1841 cell_ptr->links[0] = 0;
1842 cell_ptr->links[1] = 0;
1843
1844 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
1845 world.score ++;
1846 }
1847 else
1848 {
1849 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
1850 world.score --;
1851 }
1852
1853 map_reclassify((v2i){ world.tile_x -2, world.tile_y -2 },
1854 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
1855 }
1856
1857 if( vg_get_button_down("secondary") && (cell_ptr->state & FLAG_CANAL) && !(cell_ptr->config == k_cell_type_split) )
1858 {
1859 world.id_drag_from = world.selected;
1860
1861 struct cell_description *desc = &cell_descriptions[ world.data[world.id_drag_from].config ];
1862 v2_add( desc->trigger_pos, (v2f){ world.tile_x, world.tile_y }, world.drag_from_co );
1863 }
1864 }
1865
1866 float local_x = vg_mouse_ws[0] - (float)world.tile_x;
1867
1868 if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
1869 {
1870 u32 link_id = cell_ptr->links[ 0 ]? 0: 1;
1871
1872 // break existing connection off
1873 if( cell_ptr->links[ link_id ] )
1874 {
1875 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1876
1877 if( !current_connection->links[ link_id ^ 0x1 ] )
1878 current_connection->state &= ~FLAG_TARGETED;
1879
1880 current_connection->links[ link_id ] = 0;
1881 cell_ptr->links[ link_id ] = 0;
1882 }
1883
1884 cell_ptr->state &= ~FLAG_IS_TRIGGER;
1885 world.id_drag_from = 0;
1886 }
1887
1888 else if( world.id_drag_from && (cell_ptr->state & (FLAG_CANAL|FLAG_EMITTER)) &&
1889 ((cell_ptr->config == k_cell_type_split) || (cell_ptr->state & FLAG_EMITTER)) )
1890 {
1891 world.drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
1892 world.drag_to_co[1] = (float)world.tile_y + 0.25f;
1893
1894 if( vg_get_button_up("secondary") )
1895 {
1896 struct cell *drag_ptr = &world.data[world.id_drag_from];
1897 u32 link_id = local_x > 0.5f? 1: 0;
1898
1899 // Cleanup existing connections
1900 if( cell_ptr->links[ link_id ] )
1901 {
1902 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id, cell_ptr->links[ link_id ] );
1903
1904 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1905 current_connection->state &= ~FLAG_IS_TRIGGER;
1906 current_connection->links[ link_id ] = 0;
1907 }
1908
1909 for( u32 i = 0; i < 2; i ++ )
1910 {
1911 if( drag_ptr->links[ i ] )
1912 {
1913 vg_warn( "Destroying link %u (%hu)\n", i, drag_ptr->links[ i ] );
1914
1915 struct cell *current_connection = &world.data[ drag_ptr->links[ i ]];
1916 if( current_connection->links[ i ^ 0x1 ] == 0 )
1917 current_connection->state &= ~FLAG_TARGETED;
1918
1919 current_connection->links[ i ] = 0;
1920 drag_ptr->links[ i ] = 0;
1921 }
1922 }
1923
1924 // Create the new connection
1925 vg_success( "Creating connection on link %u (%hu)\n", link_id, world.id_drag_from );
1926
1927 cell_ptr->links[ link_id ] = world.id_drag_from;
1928 drag_ptr->links[ link_id ] = world.selected;
1929
1930 cell_ptr->state |= FLAG_TARGETED;
1931 drag_ptr->state |= FLAG_IS_TRIGGER;
1932 world.id_drag_from = 0;
1933 }
1934 }
1935 }
1936 else
1937 {
1938 world.selected = -1;
1939 }
1940
1941 if( !(vg_get_button("secondary") && world.id_drag_from) )
1942 world.id_drag_from = 0;
1943 }
1944 else
1945 {
1946 world.selected = -1;
1947 world.id_drag_from = 0;
1948 }
1949
1950 // Marble state updates
1951 // ========================================================================================================
1952 if( is_simulation_running() )
1953 {
1954 float old_time = world.sim_internal_time;
1955
1956 if( !world.st.buttons[ k_world_button_pause ].state )
1957 world.sim_internal_time = world.sim_internal_ref + (vg_time-world.sim_delta_ref) * world.sim_delta_speed;
1958 else
1959 world.sim_internal_time = vg_lerpf( world.sim_internal_time, world.sim_internal_ref + world.pause_offset_target, vg_time_delta*15.0f );
1960 world.sim_internal_delta = world.sim_internal_time-old_time;
1961
1962 world.sim_target = (int)floorf(world.sim_internal_time);
1963
1964 int success_this_frame = 0;
1965 int failure_this_frame = 0;
1966
1967 while( world.sim_frame < world.sim_target )
1968 {
1969 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
1970
1971 // Update splitter deltas
1972 for( int i = 0; i < world.h*world.w; i ++ )
1973 {
1974 struct cell *cell = &world.data[i];
1975 if( cell->config == k_cell_type_split )
1976 {
1977 cell->state &= ~FLAG_FLIP_ROTATING;
1978 }
1979 if( cell->state & (FLAG_IS_TRIGGER|FLAG_EMITTER) )
1980 cell->state &= ~FLAG_TRIGGERED;
1981 }
1982
1983 int alive_count = 0;
1984
1985 // Update fish positions
1986 for( int i = 0; i < world.num_fishes; i ++ )
1987 {
1988 struct fish *fish = &world.fishes[i];
1989
1990 if( fish->state == k_fish_state_soon_dead )
1991 fish->state = k_fish_state_dead;
1992
1993 if( fish->state == k_fish_state_soon_alive )
1994 fish->state = k_fish_state_alive;
1995
1996 if( fish->state < k_fish_state_alive )
1997 continue;
1998
1999 struct cell *cell_current = pcell( fish->pos );
2000
2001 if( fish->state == k_fish_state_alive )
2002 {
2003 // Apply to output
2004 if( cell_current->state & FLAG_OUTPUT )
2005 {
2006 for( int j = 0; j < arrlen( world.io ); j ++ )
2007 {
2008 struct cell_terminal *term = &world.io[j];
2009
2010 if( v2i_eq( term->pos, fish->pos ) )
2011 {
2012 struct terminal_run *run = &term->runs[ world.sim_run ];
2013 if( run->recv_count < vg_list_size( run->recieved ) )
2014 {
2015 if( fish->colour == run->steps[ run->recv_count ] )
2016 success_this_frame = 1;
2017 else
2018 failure_this_frame = 1;
2019
2020 run->recieved[ run->recv_count ++ ] = fish->colour;
2021 }
2022 else
2023 failure_this_frame = 1;
2024
2025 break;
2026 }
2027 }
2028
2029 fish->state = k_fish_state_dead;
2030 fish->death_time = -1000.0f;
2031 continue;
2032 }
2033
2034
2035 if( cell_current->config == k_cell_type_merge )
2036 {
2037 // Can only move up
2038 fish->dir[0] = 0;
2039 fish->dir[1] = -1;
2040 fish->flow_reversed = 0;
2041 }
2042 else
2043 {
2044 if( cell_current->config == k_cell_type_split )
2045 {
2046 // Flip flop L/R
2047 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
2048 fish->dir[1] = 0;
2049
2050 if( !(cell_current->state & FLAG_TARGETED) )
2051 cell_current->state ^= FLAG_FLIP_FLOP;
2052 }
2053 else
2054 {
2055 // Apply cell out-flow
2056 struct cell_description *desc = &cell_descriptions[ cell_current->config ];
2057
2058 v2i_copy( fish->flow_reversed? desc->start: desc->end, fish->dir );
2059 }
2060
2061 v2i pos_next;
2062 v2i_add( fish->pos, fish->dir, pos_next );
2063
2064 struct cell *cell_next = pcell( pos_next );
2065
2066 if( cell_next->state & (FLAG_CANAL|FLAG_OUTPUT) )
2067 {
2068 struct cell_description *desc = &cell_descriptions[ cell_next->config ];
2069
2070 if( cell_next->config == k_cell_type_merge )
2071 {
2072 if( fish->dir[0] == 0 )
2073 {
2074 fish->state = k_fish_state_dead;
2075 fish->death_time = world.sim_internal_time;
2076 }
2077 else
2078 fish->flow_reversed = 0;
2079 }
2080 else
2081 {
2082 if( cell_next->config == k_cell_type_split )
2083 {
2084 if( fish->dir[0] == 0 )
2085 {
2086 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
2087 cell_next->state |= FLAG_FLIP_ROTATING;
2088
2089 fish->flow_reversed = 0;
2090 }
2091 else
2092 {
2093 fish->state = k_fish_state_dead;
2094 fish->death_time = world.sim_internal_time;
2095 }
2096 }
2097 else
2098 fish->flow_reversed = ( fish->dir[0] != -desc->start[0] ||
2099 fish->dir[1] != -desc->start[1] )? 1: 0;
2100 }
2101 }
2102 else
2103 {
2104 if( world_check_pos_ok( fish->pos, 2 ) )
2105 fish->state = k_fish_state_bg;
2106 else
2107 {
2108 fish->state = k_fish_state_dead;
2109 fish->death_time = world.sim_internal_time;
2110 }
2111 }
2112 }
2113
2114 //v2i_add( fish->pos, fish->dir, fish->pos );
2115 }
2116 else if( fish->state == k_fish_state_bg )
2117 {
2118 v2i_add( fish->pos, fish->dir, fish->pos );
2119
2120 if( !world_check_pos_ok( fish->pos, 2 ) )
2121 {
2122 fish->state = k_fish_state_dead;
2123 fish->death_time = -1000.0f;
2124 }
2125 else
2126 {
2127 struct cell *cell_entry = pcell( fish->pos );
2128
2129 if( cell_entry->state & FLAG_CANAL )
2130 {
2131 if( cell_entry->config == k_cell_type_con_r || cell_entry->config == k_cell_type_con_u
2132 || cell_entry->config == k_cell_type_con_l || cell_entry->config == k_cell_type_con_d )
2133 {
2134 #ifdef VG_STEAM
2135 sw_set_achievement( "CAN_DO_THAT" );
2136 #endif
2137
2138 fish->state = k_fish_state_soon_alive;
2139
2140 fish->dir[0] = 0;
2141 fish->dir[1] = 0;
2142 fish->flow_reversed = 1;
2143
2144 switch( cell_entry->config )
2145 {
2146 case k_cell_type_con_r: fish->dir[0] = 1; break;
2147 case k_cell_type_con_l: fish->dir[0] = -1; break;
2148 case k_cell_type_con_u: fish->dir[1] = 1; break;
2149 case k_cell_type_con_d: fish->dir[1] = -1; break;
2150 }
2151 }
2152 }
2153 }
2154 }
2155 else { vg_error( "fish behaviour unimplemented for behaviour type (%d)\n" ); }
2156
2157 if( fish->state >= k_fish_state_alive )
2158 alive_count ++;
2159 }
2160
2161 // Second pass (triggers)
2162 for( int i = 0; i < world.num_fishes; i ++ )
2163 {
2164 struct fish *fish = &world.fishes[i];
2165
2166 // Apply movement
2167 if( fish->state == k_fish_state_alive )
2168 v2i_add( fish->pos, fish->dir, fish->pos );
2169
2170 if( fish->state == k_fish_state_alive || fish->state == k_fish_state_soon_alive )
2171 {
2172 struct cell *cell_current = pcell( fish->pos );
2173
2174 if( cell_current->state & FLAG_IS_TRIGGER )
2175 {
2176 int trigger_id = cell_current->links[0]?0:1;
2177
2178 struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
2179
2180 // Spawn new marble
2181 if( (target_peice->state & FLAG_EMITTER) && !(target_peice->state & FLAG_TRIGGERED))
2182 {
2183 if( world.num_fishes < vg_list_size( world.fishes ) )
2184 {
2185 struct fish *fish = &world.fishes[ world.num_fishes ];
2186 lcell( cell_current->links[trigger_id], fish->pos );
2187
2188 fish->state = k_fish_state_soon_alive;
2189 fish->colour = target_peice->emit[ trigger_id ];
2190
2191 if( target_peice->config != k_cell_type_stub )
2192 {
2193 struct cell_description *desc = &cell_descriptions[ target_peice->config ];
2194 v2i_copy( desc->start, fish->dir );
2195 fish->flow_reversed = 1;
2196
2197 world.num_fishes ++;
2198 alive_count ++;
2199 }
2200 }
2201 else
2202 vg_warn( "Max marbles exceeded\n" );
2203 }
2204 else
2205 {
2206 if( trigger_id )
2207 target_peice->state |= FLAG_FLIP_FLOP;
2208 else
2209 target_peice->state &= ~FLAG_FLIP_FLOP;
2210 }
2211
2212 cell_current->state |= FLAG_TRIGGERED;
2213 }
2214 }
2215 }
2216
2217 // Third pass (collisions)
2218 struct fish *fi, *fj;
2219
2220 for( int i = 0; i < world.num_fishes; i ++ )
2221 {
2222 fi = &world.fishes[i];
2223
2224 if( (fi->state == k_fish_state_alive) |
2225 (fi->state == k_fish_state_soon_alive) )
2226 {
2227 int continue_again = 0;
2228
2229 for( int j = i+1; j < world.num_fishes; j ++ )
2230 {
2231 fj = &world.fishes[j];
2232
2233 if( (fj->state == k_fish_state_alive) |
2234 (fj->state == k_fish_state_soon_alive) )
2235 {
2236 v2i fi_prev;
2237 v2i fj_prev;
2238
2239 v2i_sub( fi->pos, fi->dir, fi_prev );
2240 v2i_sub( fj->pos, fj->dir, fj_prev );
2241
2242 int
2243 collide_next_frame = (
2244 (fi->pos[0] == fj->pos[0]) &&
2245 (fi->pos[1] == fj->pos[1]))? 1: 0,
2246 collide_this_frame = (
2247 (fi_prev[0] == fj->pos[0]) &&
2248 (fi_prev[1] == fj->pos[1]) &&
2249 (fj_prev[0] == fi->pos[0]) &&
2250 (fj_prev[1] == fi->pos[1])
2251 )? 1: 0;
2252
2253 if( collide_next_frame || collide_this_frame )
2254 {
2255 #ifdef VG_STEAM
2256 sw_set_achievement( "BANG" );
2257 #endif
2258
2259 // Shatter death (+0.5s)
2260 float death_time = world.sim_internal_time + ( collide_this_frame? 0.0f: 0.5f );
2261
2262 fi->state = k_fish_state_soon_dead;
2263 fj->state = k_fish_state_soon_dead;
2264 fi->death_time = death_time;
2265 fj->death_time = death_time;
2266
2267 continue_again = 1;
2268 break;
2269 }
2270 }
2271 }
2272 if( continue_again )
2273 continue;
2274 }
2275 }
2276
2277 // Spawn fishes
2278 for( int i = 0; i < arrlen( world.io ); i ++ )
2279 {
2280 struct cell_terminal *term = &world.io[ i ];
2281 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2282
2283 if( is_input )
2284 {
2285 if( world.sim_frame < term->runs[ world.sim_run ].step_count )
2286 {
2287 i8 emit = term->runs[ world.sim_run ].steps[ world.sim_frame ];
2288 if( emit == -1 )
2289 continue;
2290
2291 struct fish *fish = &world.fishes[ world.num_fishes ];
2292 v2i_copy( term->pos, fish->pos );
2293
2294 fish->state = k_fish_state_alive;
2295 fish->colour = emit;
2296
2297 struct cell *cell_ptr = pcell( fish->pos );
2298
2299 if( cell_ptr->config != k_cell_type_stub )
2300 {
2301 if( world.num_fishes < vg_list_size(world.fishes))
2302 {
2303 struct cell_description *desc = &cell_descriptions[ cell_ptr->config ];
2304
2305 v2i_copy( desc->start, fish->dir );
2306 fish->flow_reversed = 1;
2307
2308 world.num_fishes ++;
2309 alive_count ++;
2310 }
2311 else
2312 vg_warn( "Max marbles exceeded\n" );
2313 }
2314 }
2315 }
2316 }
2317
2318 if( alive_count == 0 )
2319 {
2320 world.completed = 1;
2321
2322 for( int i = 0; i < arrlen( world.io ); i ++ )
2323 {
2324 struct cell_terminal *term = &world.io[ i ];
2325
2326 if( pcell(term->pos)->state & FLAG_OUTPUT )
2327 {
2328 struct terminal_run *run = &term->runs[ world.sim_run ];
2329
2330 if( run->recv_count == run->step_count )
2331 {
2332 for( int j = 0; j < run->step_count; j ++ )
2333 {
2334 if( run->recieved[j] != run->steps[j] )
2335 {
2336 world.completed = 0;
2337 break;
2338 }
2339 }
2340 }
2341 else
2342 {
2343 world.completed = 0;
2344 break;
2345 }
2346 }
2347 }
2348
2349 if( world.completed )
2350 {
2351 if( world.sim_run < world.max_runs-1 )
2352 {
2353 vg_success( "Run passed, starting next\n" );
2354 world.sim_run ++;
2355 world.sim_frame = 0;
2356 world.sim_target = 0;
2357 world.num_fishes = 0;
2358
2359 // Reset timing reference points
2360 world.sim_delta_ref = vg_time;
2361 world.sim_internal_ref = 0.0f;
2362
2363 if( world.st.buttons[ k_world_button_pause ].state )
2364 world.pause_offset_target = 0.5f;
2365 else
2366 world.pause_offset_target = 0.0f;
2367
2368 world.sim_internal_time = 0.0f;
2369
2370 for( int i = 0; i < world.w*world.h; i ++ )
2371 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
2372
2373 continue;
2374 }
2375 else
2376 {
2377 vg_success( "Level passed!\n" );
2378
2379 u32 score = 0;
2380 for( int i = 0; i < world.w*world.h; i ++ )
2381 if( world.data[ i ].state & FLAG_CANAL )
2382 score ++;
2383
2384 world.score = score;
2385 world.time = world.sim_frame;
2386
2387 // Copy into career data
2388 if( world.pCmpLevel )
2389 {
2390 career_pass_level( world.pCmpLevel, world.score, 1 );
2391 }
2392
2393 sfx_set_play( &audio_tones, &audio_system_balls_extra, 9 );
2394 failure_this_frame = 0;
2395 success_this_frame = 0;
2396 }
2397 }
2398 else
2399 {
2400 #ifdef VG_STEAM
2401 if( world.sim_run > 0 )
2402 sw_set_achievement( "GOOD_ENOUGH" );
2403 #endif
2404
2405 vg_error( "Level failed :(\n" );
2406 }
2407
2408 simulation_stop();
2409 break;
2410 }
2411
2412 world.sim_frame ++;
2413 }
2414
2415 // Sounds
2416 if( failure_this_frame )
2417 {
2418 sfx_set_play( &audio_tones, &audio_system_balls_extra, 0 );
2419 }
2420 else if( success_this_frame )
2421 {
2422 static int succes_counter = 0;
2423
2424 sfx_set_play( &audio_tones, &audio_system_balls_extra, 1+(succes_counter++) );
2425
2426 if( succes_counter == 7 )
2427 succes_counter = 0;
2428 }
2429
2430 // Position update
2431 // =====================================================================================================
2432
2433 world.frame_lerp = world.sim_internal_time - floorf( world.sim_internal_time );
2434
2435 for( int i = 0; i < world.num_fishes; i ++ )
2436 {
2437 struct fish *fish = &world.fishes[i];
2438
2439 if( fish->state == k_fish_state_dead )
2440 continue;
2441
2442 if( fish->state == k_fish_state_soon_dead && (world.sim_internal_time > fish->death_time) )
2443 continue; // Todo: particle thing?
2444
2445 struct cell *cell = pcell(fish->pos);
2446 struct cell_description *desc = &cell_descriptions[ cell->config ];
2447
2448 v2f const *curve;
2449
2450 float t = world.frame_lerp;
2451 if( fish->flow_reversed && !desc->is_linear )
2452 t = 1.0f-t;
2453
2454 v2_copy( fish->physics_co, fish->physics_v );
2455
2456 switch( cell->config )
2457 {
2458 case k_cell_type_merge:
2459 if( fish->dir[0] == 1 )
2460 curve = curve_12;
2461 else
2462 curve = curve_9;
2463 break;
2464 case k_cell_type_con_r: curve = curve_1; break;
2465 case k_cell_type_con_l: curve = curve_4; break;
2466 case k_cell_type_con_u: curve = curve_2; break;
2467 case k_cell_type_con_d: curve = curve_8; break;
2468 case 3: curve = curve_3; break;
2469 case 6: curve = curve_6; break;
2470 case 9: curve = curve_9; break;
2471 case 12: curve = curve_12; break;
2472 case 7:
2473 if( t > curve_7_linear_section )
2474 {
2475 t -= curve_7_linear_section;
2476 t *= (1.0f/(1.0f-curve_7_linear_section));
2477
2478 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
2479 }
2480 else curve = NULL;
2481 break;
2482 default: curve = NULL; break;
2483 }
2484
2485 if( curve )
2486 {
2487 float t2 = t * t;
2488 float t3 = t * t * t;
2489
2490 float cA = 3.0f*t2 - 3.0f*t3;
2491 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
2492 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
2493
2494 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
2495 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
2496 fish->physics_co[0] += (float)fish->pos[0];
2497 fish->physics_co[1] += (float)fish->pos[1];
2498 }
2499 else
2500 {
2501 v2f origin;
2502 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
2503 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
2504
2505 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
2506 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
2507 }
2508
2509 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
2510 v2_divs( fish->physics_v, world.sim_internal_delta, fish->physics_v );
2511 }
2512 }
2513 }
2514
2515 static void render_tile( v2i pos, struct cell *ptr, v4f const regular_colour,
2516 v4f const selected_colour, int with_glow )
2517 {
2518 int selected = world.selected == pos[1]*world.w + pos[0];
2519 int uv[2];
2520
2521 uv[0] = ptr->config & 0x3;
2522 uv[1] = ptr->config >> 2;
2523
2524 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
2525 (float)pos[0],
2526 (float)pos[1],
2527 uv[0],
2528 uv[1]
2529 );
2530
2531 if( with_glow )
2532 {
2533 glUniform3fv( SHADER_UNIFORM( shader_tile_main, "uGlowA" ),
2534 1, ptr->glow[0] );
2535 glUniform3fv( SHADER_UNIFORM( shader_tile_main, "uGlowB" ),
2536 1, ptr->glow[1] );
2537 }
2538 else
2539 {
2540 glUniform3f( SHADER_UNIFORM( shader_tile_main, "uGlowA" ),
2541 0.0f,
2542 0.0f,
2543 0.0f );
2544 glUniform3f( SHADER_UNIFORM( shader_tile_main, "uGlowB" ),
2545 0.0f,
2546 0.0f,
2547 0.0f );
2548 }
2549
2550 if( selected )
2551 {
2552 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
2553 draw_mesh( 0, 2 );
2554 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
2555 }
2556 else
2557 draw_mesh( 0, 2 );
2558 }
2559
2560 // Renders specific chunk of tiles
2561 static void render_tile_block( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
2562 {
2563 v2i full_start = { 0,0 };
2564 v2i full_end = { world.w, world.h };
2565
2566 if( !start || !end )
2567 {
2568 start = full_start;
2569 end = full_end;
2570 }
2571
2572 for( int y = start[1]; y < end[1]; y ++ )
2573 {
2574 for( int x = start[0]; x < end[0]; x ++ )
2575 {
2576 v2i pos = { x, y };
2577 struct cell *cell = pcell( pos );
2578
2579 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
2580 render_tile( pos, cell, regular_colour, selected_colour, 0 );
2581 }
2582 }
2583 }
2584
2585 // Renders all tiles in the command list
2586 static void render_tiles( v4f const regular_colour, v4f const selected_colour,
2587 int with_glow )
2588 {
2589 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
2590
2591 struct render_list
2592 {
2593 struct render_cmd *arr;
2594 u32 count;
2595 }
2596 render_lists[] = {
2597 { world.cmd_buf_tiles, world.tile_count },
2598 { world.cmd_buf_specials, world.tile_special_count }
2599 };
2600
2601 int world_paused = world.st.buttons[k_world_button_pause].state;
2602 if( with_glow && !world_paused )
2603 {
2604 for( int i = 0; i < world.num_fishes; i ++ )
2605 {
2606 struct fish *fish = &world.fishes[i];
2607
2608 if( !(fish->state == k_fish_state_alive ||
2609 fish->state == k_fish_state_soon_alive) ) continue;
2610
2611 struct cell *cell_x = pcell( fish->pos );
2612 v3f glow_colour;
2613 colour_code_v3( fish->colour, glow_colour );
2614
2615 int c = 0;
2616 if( cell_x->config == k_cell_type_split )
2617 c = cell_x->state & FLAG_FLIP_FLOP? 1:0;
2618
2619 if( cell_x->config == k_cell_type_merge )
2620 c = fish->dir[0]==-1?1:0;
2621
2622 v3_muladds( cell_x->glow[c], glow_colour,
2623 powf(world.frame_lerp,2.0f)*0.03f * world.sim_delta_speed,
2624 cell_x->glow[c]);
2625 }
2626 }
2627
2628 for( int i = 0; i < vg_list_size( render_lists ); i ++ )
2629 {
2630 struct render_list *list = &render_lists[i];
2631 for( int j = 0; j < list->count; j ++ )
2632 {
2633 struct render_cmd *cmd = &list->arr[j];
2634 struct cell *cell = cmd->ptr;
2635
2636 render_tile( cmd->pos, cell, regular_colour, selected_colour, with_glow );
2637 }
2638 }
2639 }
2640
2641 static int world_button_exec( struct world_button *btn, v2f texture, v3f colour, enum world_button_status *status )
2642 {
2643 static v2i click_grab = { -9999, -9999 };
2644
2645 // Reset click_grab
2646 if( !btn )
2647 {
2648 click_grab[0] = -9999;
2649 click_grab[1] = -9999;
2650 return 0;
2651 }
2652
2653 v2i click_tile = { world.tile_x, world.tile_y };
2654
2655 int triggered = 0;
2656 int is_hovering = v2i_eq( click_tile, btn->position ) && !gui_want_mouse();
2657
2658 // Set up light targets before logic runs
2659 if( btn->state )
2660 btn->light_target = is_hovering? 0.7f: 0.6f;
2661 else
2662 btn->light_target = is_hovering? 0.2f: 0.0f;
2663
2664 if( vg_get_button( "primary" ) && is_hovering )
2665 btn->light_target = 1.0f;
2666
2667 // Process click action
2668 if( is_hovering )
2669 {
2670 if( vg_get_button_down( "primary" ) && is_hovering )
2671 v2i_copy( click_tile, click_grab );
2672 else if( v2i_eq( click_grab, click_tile ) && vg_get_button_up( "primary" ) )
2673 {
2674 // Click event
2675 *status = btn->state? k_world_button_on_disable: k_world_button_on_enable;
2676
2677 if( btn->mode == k_world_button_mode_toggle )
2678 btn->state ^= 0x1;
2679
2680 sfx_set_play( &audio_clicks, &audio_system_ui, btn->state? 1:0 );
2681 triggered = 1;
2682 }
2683 }
2684
2685 // Drawing stage
2686 v4f final_colour;
2687
2688 btn->light = vg_lerpf( btn->light, btn->light_target + btn->extra_light, vg_time_delta*26.0f );
2689
2690 v3_copy( colour, final_colour );
2691 final_colour[3] = btn->light;
2692
2693 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2694 btn->position[0],
2695 btn->position[1],
2696 texture[0],
2697 texture[1]
2698 );
2699 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
2700 draw_mesh( 0, 2 );
2701
2702 return triggered;
2703 }
2704
2705 static void level_selection_buttons(void)
2706 {
2707 v3f tutorial_colour = { 0.204f, 0.345f, 0.553f };
2708 v3f locked_colour = { 0.2f, 0.2f, 0.2f };
2709
2710 struct cmp_level *switch_level_to = NULL;
2711
2712 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
2713 {
2714 struct career_level_pack *grid = &career_packs[i];
2715
2716 for( int j = 0; j < grid->count; j ++ )
2717 {
2718 struct cmp_level *lvl = &grid->pack[ j ];
2719
2720 if( world.pCmpLevel == lvl )
2721 lvl->btn.extra_light = 0.35f + fabsf(sinf( vg_time * 2.0f )) * 0.05f;
2722 else lvl->btn.extra_light = 0.2f;
2723
2724 if( lvl->completed_score )
2725 lvl->btn.extra_light += 0.6f;
2726
2727 enum world_button_status status;
2728 if( world_button_exec(
2729 &lvl->btn,
2730 (v2f){0.0f,0.0f},
2731 lvl->unlocked? (lvl->is_tutorial? tutorial_colour: grid->primary_colour): locked_colour,
2732 &status
2733 ))
2734 {
2735 if( status == k_world_button_on_enable && lvl->unlocked )
2736 switch_level_to = lvl;
2737 }
2738 }
2739 }
2740
2741 if( switch_level_to )
2742 {
2743 world.st.lvl_to_load = switch_level_to;
2744 world.st.lvl_load_time = vg_time + 0.25f;
2745 world.st.world_transition = 1.0f;
2746
2747 /*
2748 if( console_changelevel( 1, &switch_level_to->map_name ) )
2749 {
2750 world.pCmpLevel = switch_level_to;
2751 gen_level_text( world.pCmpLevel );
2752 }
2753 */
2754 }
2755 }
2756
2757 static void render_sprite( enum sprites_auto_combine_index id, v3f pos )
2758 {
2759 struct vg_sprite *sp = &sprites_auto_combine[ id ];
2760
2761 glUniform4fv( SHADER_UNIFORM( shader_sprite, "uUv" ), 1, sp->uv_xywh );
2762 glUniform3f( SHADER_UNIFORM( shader_sprite, "uPos" ),
2763 pos[0], pos[1], pos[2] * world.st.world_transition );
2764
2765 draw_mesh( 0, 2 );
2766 }
2767
2768 static void vg_framebuffer_resize(int w, int h)
2769 {
2770 glBindTexture( GL_TEXTURE_2D, world.st.colourbuffer );
2771 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, w, h,
2772 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
2773
2774 for( int i=0; i<2; i++ )
2775 {
2776 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[i] );
2777 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB,
2778 w/EFFECT_BUFFER_RATIO, h/EFFECT_BUFFER_RATIO,
2779 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
2780 }
2781 }
2782
2783 void vg_render(void)
2784 {
2785 if( enable_bloom || enable_vignette )
2786 glBindFramebuffer( GL_FRAMEBUFFER, world.st.framebuffer );
2787 else
2788 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
2789
2790 glViewport( 0,0, vg_window_x, vg_window_y );
2791
2792 glDisable( GL_DEPTH_TEST );
2793 glClearColor( 0.14f, 0.14f, 0.14f, 1.0f );
2794 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
2795
2796 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
2797 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
2798
2799 int const circle_base = 6;
2800 int const filled_start = circle_base+0;
2801 int const filled_count = circle_base+32;
2802 int const empty_start = circle_base+32;
2803 int const empty_count = circle_base+32*2;
2804
2805 #if 0
2806 struct world_theme *theme = &world_themes[ world_theme_id ];
2807 #else
2808 struct world_theme *theme = &world_themes[ 0 ];
2809 #endif
2810
2811 if( !world.initialzed )
2812 return;
2813
2814 // Extract render commands
2815 world.tile_count = 0;
2816 world.tile_special_count = 0;
2817
2818 for( int y = 1; y < world.h-1; y ++ )
2819 {
2820 for( int x = 1; x < world.w-1; x ++ )
2821 {
2822 struct cell *cell = pcell((v2i){x,y});
2823
2824 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER|FLAG_INPUT_NICE) )
2825 {
2826 struct render_cmd *cmd;
2827
2828 if(
2829 (cell->config == k_cell_type_split && (cell->state & FLAG_CANAL))
2830 || (cell->state & (FLAG_EMITTER|FLAG_IS_TRIGGER))
2831 )
2832 cmd = &world.cmd_buf_tiles[ world.max_commands - (++ world.tile_special_count) ];
2833 else
2834 cmd = &world.cmd_buf_tiles[ world.tile_count ++ ];
2835
2836 cmd->pos[0] = x;
2837 cmd->pos[1] = y;
2838 cmd->ptr = cell;
2839
2840 int world_paused = world.st.buttons[k_world_button_pause].state;
2841 if( !world_paused )
2842 {
2843 float decay = 1.0f - world.sim_delta_speed*0.005f;
2844 v3_muls( cell->glow[0], decay, cell->glow[0] );
2845 v3_muls( cell->glow[1], decay, cell->glow[1] );
2846 }
2847 }
2848 }
2849 }
2850
2851 world.cmd_buf_specials = &world.cmd_buf_tiles[ world.max_commands - world.tile_special_count ];
2852
2853 // BACKGROUND
2854 // ========================================================================================================
2855 use_mesh( &world.shapes );
2856
2857 SHADER_USE( shader_background );
2858 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2859
2860 glActiveTexture( GL_TEXTURE0 );
2861 glBindTexture( GL_TEXTURE_2D, world.background_data );
2862 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
2863
2864 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
2865 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.05f );
2866
2867 glActiveTexture( GL_TEXTURE1 );
2868 glBindTexture( GL_TEXTURE_2D, world.random_samples );
2869 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
2870 glUniform1f( SHADER_UNIFORM( shader_background, "uVisibility" ), 1.0f ); //world.st.world_transition );
2871
2872 draw_mesh( 0, 2 );
2873
2874 // TILESET BACKGROUND LAYER
2875 // ========================================================================================================
2876 use_mesh( &world.shapes );
2877 SHADER_USE( shader_tile_main );
2878
2879 m2x2f subtransform;
2880 m2x2_identity( subtransform );
2881 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2882 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2883 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
2884 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
2885 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uVisibility" ), world.st.world_transition * 2.0f );
2886
2887 glEnable(GL_BLEND);
2888 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2889 glBlendEquation(GL_FUNC_ADD);
2890
2891 // rebind textures
2892 vg_tex2d_bind( &tex_tile_data, 0 );
2893 vg_tex2d_bind( theme->tex_tiles, 1 );
2894 vg_tex2d_bind( &tex_tile_glow, 2 );
2895
2896 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2897 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2898 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlow" ), 2 );
2899
2900 glUniform3fv( SHADER_UNIFORM( shader_tile_main, "uShadowing" ), 1, theme->col_shadow );
2901
2902 render_tiles( colour_default, colour_default, 1 );
2903
2904 // MARBLES
2905 // ========================================================================================================
2906 SHADER_USE( shader_ball );
2907 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2908
2909 vg_tex2d_bind( &tex_ball_noise, 0 );
2910 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
2911
2912 if( world.st.buttons[ k_world_button_sim ].state )
2913 {
2914 for( int i = 0; i < world.num_fishes; i ++ )
2915 {
2916 struct fish *fish = &world.fishes[i];
2917 v3f render_pos;
2918 render_pos[2] = 1.0f;
2919
2920 if( fish->state == k_fish_state_dead || fish->state == k_fish_state_soon_dead )
2921 {
2922 float death_anim_time = world.sim_internal_time - fish->death_time;
2923
2924 // Death animation
2925 if( death_anim_time > 0.0f && death_anim_time < 1.0f )
2926 {
2927 float amt = 1.0f-death_anim_time*death_anim_time;
2928
2929 v2_muladds( fish->physics_co, fish->physics_v, -1.0f * world.sim_internal_delta * amt, fish->physics_co );
2930 render_pos[2] = amt;
2931 }
2932 else if( world.sim_internal_time > fish->death_time )
2933 continue;
2934 }
2935 else if( fish->state == k_fish_state_bg )
2936 continue;
2937
2938 v2_copy( fish->physics_co, render_pos );
2939
2940 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2941 colour_code_v3( fish->colour, dot_colour );
2942
2943 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
2944 glUniform3fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, render_pos );
2945 glUniform2f( SHADER_UNIFORM( shader_ball, "uTexOffset" ), (float)i * 1.2334, (float)i * -0.3579f );
2946 draw_mesh( 0, 2 );
2947 }
2948 }
2949
2950 // TILESET FOREGROUND LAYER
2951 // ========================================================================================================
2952 SHADER_USE( shader_tile_main );
2953
2954 // Re Bind textures
2955 vg_tex2d_bind( &tex_tile_data, 0 );
2956 vg_tex2d_bind( theme->tex_tiles, 1 );
2957 vg_tex2d_bind( &tex_tile_glow, 2 );
2958
2959 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
2960 render_tiles( colour_default, colour_selected, 0 );
2961
2962 // Draw splitters
2963 for( int i = 0; i < world.tile_special_count; i ++ )
2964 {
2965 struct render_cmd *cmd = &world.cmd_buf_specials[i];
2966 struct cell *cell = cmd->ptr;
2967
2968 if( cell->config == k_cell_type_split )
2969 {
2970 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
2971
2972 if( cell->state & FLAG_FLIP_ROTATING )
2973 {
2974 if( (world.frame_lerp > curve_7_linear_section) )
2975 {
2976 float const rotation_speed = 0.4f;
2977 if( (world.frame_lerp < 1.0f-rotation_speed) )
2978 {
2979 float t = world.frame_lerp - curve_7_linear_section;
2980 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
2981 t += 1.0f;
2982
2983 rotation *= t;
2984 }
2985 else
2986 rotation *= -1.0f;
2987 }
2988 }
2989
2990 m2x2_create_rotation( subtransform, rotation );
2991
2992 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main,"uSubTransform" ),
2993 1, GL_FALSE, (float *)subtransform );
2994
2995 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
2996 (float)cmd->pos[0],
2997 (float)cmd->pos[1] + 0.125f,
2998 cell->state & FLAG_TARGETED? 3.0f: 2.0f,
2999 3.0f
3000 );
3001 draw_mesh( 0, 2 );
3002 }
3003 }
3004
3005 // EDIT OVERLAY
3006 // ========================================================================================================
3007 if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) && !world.id_drag_from )
3008 {
3009 v2i new_begin = { world.tile_x - 2, world.tile_y - 2 };
3010 v2i new_end = { world.tile_x + 2, world.tile_y + 2 };
3011
3012 world.data[ world.selected ].state ^= FLAG_CANAL;
3013 map_reclassify( new_begin, new_end, 0 );
3014
3015 m2x2_identity( subtransform );
3016 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f );
3017 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
3018 glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
3019
3020 render_tile_block( new_begin, new_end, colour_default, colour_default );
3021
3022 world.data[ world.selected ].state ^= FLAG_CANAL;
3023 map_reclassify( new_begin, new_end, 0 );
3024 }
3025
3026 // BUTTONS
3027 // ========================================================================================================
3028 SHADER_USE( shader_buttons );
3029 glUniformMatrix3fv( SHADER_UNIFORM( shader_buttons, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3030
3031 vg_tex2d_bind( &tex_buttons, 0 );
3032 glUniform1i( SHADER_UNIFORM( shader_buttons, "uTexMain" ), 0 );
3033
3034 enum world_button_status stat;
3035 int world_paused = world.st.buttons[k_world_button_pause].state;
3036 int world_running = world.st.buttons[k_world_button_sim].state;
3037
3038 float sim_icon_x = world_paused? 3.0f: (world_running? 2.0f: 0.0f);
3039
3040 v3f btn_dark_blue = { 0.204f, 0.345f, 0.553f };
3041 v3f btn_orange = { 0.553f, 0.345f, 0.204f };
3042
3043 if( world_button_exec( &world.st.buttons[k_world_button_sim], (v2f){ sim_icon_x, 3.0f }, btn_dark_blue, &stat ))
3044 {
3045 if( stat == k_world_button_on_enable )
3046 {
3047 simulation_start();
3048
3049 if( world_paused )
3050 world.pause_offset_target = 0.5f;
3051 }
3052 else
3053 {
3054 if( world_paused )
3055 {
3056 // Trigger single step
3057 world.pause_offset_target += 1.0f;
3058 world.st.buttons[k_world_button_sim].state = 1;
3059 }
3060 else
3061 {
3062 simulation_stop();
3063 }
3064 }
3065 }
3066
3067 if( world_button_exec( &world.st.buttons[k_world_button_pause], (v2f){ 1.0f, 3.0f }, btn_dark_blue, &stat ))
3068 {
3069 world.sim_internal_ref = world.sim_internal_time;
3070 world.sim_delta_ref = vg_time;
3071
3072 if( stat == k_world_button_on_enable )
3073 {
3074 float time_frac = world.sim_internal_time-floorf(world.sim_internal_time);
3075 world.pause_offset_target = 0.5f - time_frac;
3076 }
3077 else
3078 world.pause_offset_target = 0.0f;
3079 }
3080
3081 if( world_button_exec( &world.st.buttons[k_world_button_speedy], (v2f){ 0.0f, 2.0f }, btn_orange, &stat ))
3082 {
3083 world.sim_delta_speed = stat == k_world_button_on_enable? 10.0f: 2.5f;
3084
3085 if( !world_paused )
3086 {
3087 world.sim_delta_ref = vg_time;
3088 world.sim_internal_ref = world.sim_internal_time;
3089 }
3090 }
3091
3092 if( world_button_exec( &world.st.buttons[k_world_button_settings], (v2f){ 1.0f, 2.0f }, btn_orange, &stat ))
3093 {
3094 world.st.state = stat == k_world_button_on_enable?
3095 k_game_state_settings: k_game_state_main;
3096 }
3097
3098 level_selection_buttons();
3099
3100 if( vg_get_button_up( "primary" ) )
3101 world_button_exec( NULL, NULL, NULL, NULL );
3102
3103 // I/O ARRAYS
3104 // ========================================================================================================
3105
3106 //glEnable(GL_BLEND);
3107 SHADER_USE( shader_tile_colour );
3108 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3109
3110 for( int i = 0; i < arrlen( world.io ); i ++ )
3111 {
3112 struct cell_terminal *term = &world.io[ i ];
3113 struct cell *cell = pcell(term->pos);
3114
3115 int is_input = cell->state & FLAG_INPUT;
3116 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
3117
3118 if( cell->state & FLAG_EMITTER )
3119 {
3120 for( int j = 0; j < 2; j ++ )
3121 {
3122 if( cell->emit[j] != -1 )
3123 {
3124 colour_code_v3( cell->emit[j], dot_colour );
3125
3126 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3127 term->pos[0] + 0.25f + (float)j * 0.5f,
3128 term->pos[1] + 0.25f,
3129 0.12f
3130 );
3131
3132 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3133 draw_mesh( filled_start, filled_count );
3134 }
3135 }
3136 continue;
3137 }
3138
3139 for( int k = 0; k < term->run_count; k ++ )
3140 {
3141 float arr_base = is_input? 1.2f: -0.2f,
3142 run_offset = (is_input? 0.2f: -0.2f) * (float)k,
3143 y_position = is_input?
3144 (arr_base + (float)term->pos[1] + (float)(term->run_count-1)*0.2f) - run_offset:
3145 (float)term->pos[1] + arr_base + run_offset;
3146
3147 v4f bar_colour;
3148 int bar_draw = 0;
3149
3150 if( is_simulation_running() )
3151 {
3152 if( k == world.sim_run )
3153 {
3154 float a = fabsf(sinf( vg_time * 2.0f )) * 0.075f + 0.075f;
3155
3156 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, a }, bar_colour );
3157 }
3158 else
3159 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
3160
3161 bar_draw = 1;
3162 }
3163 else if( 1 || k & 0x1 )
3164 {
3165 if( k & 0x1 )
3166 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, 0.07f }, bar_colour );
3167 else
3168 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
3169
3170 bar_draw = 1;
3171 }
3172
3173 if( bar_draw )
3174 {
3175 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, bar_colour );
3176 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3177 (float)term->pos[0], y_position - 0.1f, 1.0f );
3178
3179 draw_mesh( 2, 2 );
3180 }
3181
3182 for( int j = 0; j < term->runs[k].step_count; j ++ )
3183 {
3184 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3185 (float)term->pos[0] + 0.2f + 0.2f * (float)j,
3186 y_position,
3187 0.1f
3188 );
3189
3190 if( is_input )
3191 {
3192 i8 colour = term->runs[k].steps[j];
3193 if( colour != -1 )
3194 {
3195 colour_code_v3( colour, dot_colour );
3196 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3197
3198 // Draw filled if tick not passed, draw empty if empty
3199 if( (world.sim_frame > j && world.sim_run >= k) || world.sim_run > k )
3200 draw_mesh( empty_start, empty_count );
3201 else
3202 draw_mesh( filled_start, filled_count );
3203 }
3204 }
3205 else
3206 {
3207
3208 if( term->runs[k].recv_count > j )
3209 {
3210 colour_code_v3( term->runs[k].recieved[j], dot_colour );
3211 v3_muls( dot_colour, 0.8f, dot_colour );
3212 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3213
3214 draw_mesh( filled_start, filled_count );
3215 }
3216
3217 colour_code_v3( term->runs[k].steps[j], dot_colour );
3218 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3219
3220 draw_mesh( empty_start, empty_count );
3221 }
3222 }
3223 }
3224 }
3225
3226 // SPRITES
3227 // ========================================================================================================
3228 SHADER_USE( shader_sprite );
3229 glUniformMatrix3fv( SHADER_UNIFORM( shader_sprite, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3230
3231 vg_tex2d_bind( &tex_sprites, 0 );
3232 glUniform1i( SHADER_UNIFORM( shader_sprite, "uTexMain" ), 0 );
3233
3234 for( int i = 0; i < world.tile_special_count; i ++ )
3235 {
3236 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3237 struct cell *cell = cmd->ptr;
3238
3239 if( (cell->config == k_cell_type_split) || (cell->state & FLAG_EMITTER) )
3240 {
3241 v2f center = { cmd->pos[0] + 0.5f, cmd->pos[1] + 0.5f };
3242
3243 v3f p0 = { 0.0f, 0.0f, 4.0f };
3244 v3f p1 = { 0.0f, 0.0f, 4.0f };
3245
3246 v2_add( center, (v2f){ -0.25f, -0.25f }, p0 );
3247 v2_add( center, (v2f){ 0.25f, -0.25f }, p1 );
3248
3249 render_sprite( k_sprite_jack_1, p0 );
3250 render_sprite( k_sprite_jack_2, p1 );
3251 }
3252 else if( cell->state & FLAG_IS_TRIGGER )
3253 {
3254 v3f p0 = { 0.0f, 0.0f, 4.0f };
3255
3256 struct cell_description *desc = &cell_descriptions[ cell->config ];
3257
3258 v2_add( (v2f){ cmd->pos[0], cmd->pos[1] }, desc->trigger_pos, p0 );
3259 render_sprite( desc->trigger_sprite, p0 );
3260 }
3261 }
3262
3263 // TEXT ELEMENTS
3264 // ========================================================================================================
3265 // Old style
3266 m3x3f mvp_text;
3267 m3x3_identity( mvp_text );
3268 m3x3_scale( mvp_text, (v3f){
3269 1.0f/ ((float)UI_GLYPH_SPACING_X*4.0f),
3270 1.0f/ -((float)UI_GLYPH_SPACING_X*4.0f),
3271 1.0f
3272 });
3273
3274 m3x3_mul( vg_pv, mvp_text, mvp_text );
3275 ui_draw( &world.st.world_text, mvp_text );
3276
3277 // WIRES
3278 // ========================================================================================================
3279 glEnable(GL_BLEND);
3280
3281 SHADER_USE( shader_wire );
3282 glBindVertexArray( world.wire.vao );
3283
3284 glUniformMatrix3fv( SHADER_UNIFORM( shader_wire, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3285
3286 v4f const wire_left_colour = { 0.9f, 0.9f, 0.9f, 1.0f };
3287 v4f const wire_right_colour = { 0.5f, 0.5f, 0.5f, 1.0f };
3288 v4f const wire_drag_colour = { 0.3f, 0.3f, 0.3f, 0.6f };
3289
3290 glUniform1f( SHADER_UNIFORM( shader_wire, "uTime" ), world.frame_lerp );
3291 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), 0.0f );
3292
3293 if( world.id_drag_from )
3294 {
3295 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_drag_colour );
3296 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), 0.4f );
3297 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), world.drag_from_co[0], world.drag_from_co[1], 0.20f*world.st.world_transition );
3298 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.20f*world.st.world_transition );
3299 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
3300 }
3301
3302 // Pulling animation
3303 float rp_x1 = world.frame_lerp*9.0f;
3304 float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f;
3305 float rp_x2 = 1.0f-rp_xa;
3306
3307 for( int i = 0; i < world.tile_special_count; i ++ )
3308 {
3309 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3310 struct cell *cell = cmd->ptr;
3311
3312 if( cell->state & FLAG_TARGETED )
3313 {
3314 for( int j = 0; j < 2; j ++ )
3315 {
3316 if( !cell->links[j] )
3317 continue;
3318
3319 struct cell *other_cell = &world.data[ cell->links[ j ]];
3320 struct cell_description *desc = &cell_descriptions[ other_cell->config ];
3321
3322 int x2 = cell->links[j] % world.w;
3323 int y2 = (cell->links[j] - x2) / world.w;
3324
3325 v2f startpoint;
3326 v2f endpoint;
3327
3328 endpoint[0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f);
3329 endpoint[1] = (float)cmd->pos[1] + 0.25f;
3330
3331 startpoint[0] = x2;
3332 startpoint[1] = y2;
3333
3334 v2_add( desc->trigger_pos, startpoint, startpoint );
3335
3336 if( cmd->ptr->state & FLAG_EMITTER )
3337 {
3338 v4f wire_colour;
3339 colour_code_v3( cmd->ptr->emit[j], wire_colour );
3340 wire_colour[3] = 1.0f;
3341
3342 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour );
3343 }
3344 else
3345 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, j? wire_right_colour: wire_left_colour );
3346
3347 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), other_cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
3348 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), other_cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
3349 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), startpoint[0], startpoint[1], 0.18f*world.st.world_transition );
3350 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), endpoint[0], endpoint[1], 0.18f*world.st.world_transition );
3351 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
3352 }
3353 }
3354 }
3355
3356 // WIRE ENDPOINTS
3357 // ========================================================================================================
3358
3359 SHADER_USE( shader_tile_colour );
3360 use_mesh( &world.shapes );
3361
3362 for( int i = 0; i < world.tile_special_count; i ++ )
3363 {
3364 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3365 struct cell *cell = cmd->ptr;
3366
3367 if( cell->state & FLAG_TARGETED )
3368 {
3369 for( int j = 0; j < 2; j ++ )
3370 {
3371 if( !cell->links[j] )
3372 continue;
3373
3374 struct cell *other_cell = &world.data[ cell->links[ j ]];
3375 struct cell_description *desc = &cell_descriptions[ other_cell->config ];
3376
3377 int x2 = cell->links[j] % world.w;
3378 int y2 = (cell->links[j] - x2) / world.w;
3379
3380 v2f pts[2];
3381
3382 pts[0][0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f);
3383 pts[0][1] = (float)cmd->pos[1] + 0.25f;
3384
3385 pts[1][0] = x2;
3386 pts[1][1] = y2;
3387
3388 v2_add( desc->trigger_pos, pts[1], pts[1] );
3389
3390 if( cell->state & FLAG_EMITTER )
3391 {
3392 v4f wire_colour;
3393 colour_code_v3( cell->emit[j], wire_colour );
3394
3395 v3_muls( wire_colour, 0.8f, wire_colour );
3396 wire_colour[3] = 1.0f;
3397
3398 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, wire_colour );
3399 }
3400 else
3401 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1,j?wire_right_colour: wire_left_colour );
3402
3403 for( int i = 0; i < 2; i ++ )
3404 {
3405 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3406 pts[i][0],
3407 pts[i][1],
3408 0.08f * world.st.world_transition
3409 );
3410 draw_mesh( filled_start, filled_count );
3411 }
3412 }
3413 }
3414 }
3415
3416 // SUB SPLITTER DIRECTION
3417 // ========================================================================================================
3418
3419 /*
3420 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
3421
3422 for( int i = 0; i < world.tile_special_count; i ++ )
3423 {
3424 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3425 struct cell *cell = cmd->ptr;
3426
3427 if( cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
3428 {
3429 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), cmd->pos[0], cmd->pos[1], 1.0f );
3430 draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
3431 }
3432 }
3433 */
3434
3435 // LIGHT FLARES
3436 // ========================================================================================================
3437 glBlendFunc(GL_ONE, GL_ONE);
3438 glBlendEquation(GL_FUNC_ADD);
3439
3440 SHADER_USE( shader_sprite );
3441
3442 vg_tex2d_bind( &tex_sprites, 0 );
3443 glUniform1i( SHADER_UNIFORM( shader_sprite, "uTexMain" ), 0 );
3444
3445 for( int i = 0; i < world.tile_special_count; i ++ )
3446 {
3447 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3448 struct cell *cell = cmd->ptr;
3449
3450 if( cell->config == k_cell_type_split )
3451 {
3452 v2f center = { cmd->pos[0] + 0.5f, cmd->pos[1] + 0.5f };
3453
3454 v3f p0 = { 0.0f, 0.0f, 12.0f };
3455 v3f p1 = { 0.0f, 0.0f, 12.0f };
3456
3457 v2_add( center, (v2f){ -0.25f, -0.25f }, p0 );
3458 v2_add( center, (v2f){ 0.25f, -0.25f }, p1 );
3459
3460 if( cell->state & FLAG_TARGETED )
3461 {
3462 if( cell->state & FLAG_FLIP_FLOP )
3463 render_sprite( k_sprite_flare_y, p1 );
3464 else
3465 render_sprite( k_sprite_flare_b, p0 );
3466 }
3467 else
3468 render_sprite( k_sprite_flare_w, cell->state &FLAG_FLIP_FLOP? p1: p0 );
3469 }
3470 }
3471
3472 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3473 glBlendEquation(GL_FUNC_ADD);
3474
3475 glDisable(GL_BLEND);
3476
3477 // Draw score
3478 /*
3479 float const score_bright = 1.25f;
3480 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
3481 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f );
3482
3483 use_mesh( &world.numbers );
3484 draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
3485 */
3486
3487 if( !enable_bloom )
3488 {
3489 if( enable_vignette )
3490 goto image_composite;
3491
3492 return;
3493 }
3494
3495 /* Scale down image and remap colour values */
3496 glViewport( 0,0,
3497 vg_window_x/EFFECT_BUFFER_RATIO, vg_window_y/EFFECT_BUFFER_RATIO );
3498 glBindFramebuffer( GL_FRAMEBUFFER, world.st.bloomframebuffer[0] );
3499
3500 SHADER_USE( shader_post_darken );
3501 glActiveTexture( GL_TEXTURE0 );
3502 glBindTexture( GL_TEXTURE_2D, world.st.colourbuffer );
3503 glUniform1i( SHADER_UNIFORM( shader_post_darken, "uTexMain" ), 0 );
3504
3505 draw_mesh( 0, 2 );
3506
3507 /* Two pass blur */
3508 v2f res_inv, blur_dir;
3509 res_inv[0] = 1.0f / (float)( vg_window_x/EFFECT_BUFFER_RATIO );
3510 res_inv[1] = 1.0f / (float)( vg_window_y/EFFECT_BUFFER_RATIO );
3511
3512 SHADER_USE( shader_post_blur );
3513 glUniform1i( SHADER_UNIFORM( shader_post_blur, "uTexMain" ), 0 );
3514
3515 for( int i=0; i<1; i++ )
3516 {
3517 glBindFramebuffer( GL_FRAMEBUFFER, world.st.bloomframebuffer[1] );
3518
3519 v2_mul( (v2f){ 1.0f*(float)(i+1), 0.0f }, res_inv, blur_dir );
3520
3521 glUniform2fv( SHADER_UNIFORM(shader_post_blur,"uDir"), 1, blur_dir );
3522 glActiveTexture( GL_TEXTURE0 );
3523 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[0] );
3524
3525 draw_mesh( 0, 2 );
3526
3527 v2_mul( (v2f){ 0.0f, 1.0f*(float)(i+1) }, res_inv, blur_dir );
3528
3529 glBindFramebuffer( GL_FRAMEBUFFER, world.st.bloomframebuffer[0] );
3530 glUniform2fv( SHADER_UNIFORM(shader_post_blur,"uDir"), 1, blur_dir );
3531 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[1] );
3532 draw_mesh( 0, 2 );
3533 }
3534
3535 /* Scene composite */
3536 glViewport( 0,0, vg_window_x, vg_window_y );
3537
3538 image_composite:
3539 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
3540
3541 SHADER_USE( shader_post_comp );
3542
3543 glActiveTexture( GL_TEXTURE0 );
3544 glBindTexture( GL_TEXTURE_2D, world.st.colourbuffer );
3545 glUniform1i( SHADER_UNIFORM( shader_post_comp, "uTexMain" ), 0 );
3546
3547 glActiveTexture( GL_TEXTURE1 );
3548 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[0] );
3549 glUniform1i( SHADER_UNIFORM( shader_post_comp, "uTexBloom" ), 1 );
3550
3551 glUniform2f( SHADER_UNIFORM( shader_post_comp, "uComp" ),
3552 enable_bloom? 1.0f: 0.0f,
3553 enable_vignette? 0.0f: 1.0f );
3554
3555 draw_mesh( 0, 2 );
3556 }
3557
3558 void vg_ui(void)
3559 {
3560 // Drawing world name
3561 if( world.pCmpLevel )
3562 {
3563 gui_text( (ui_px [2]){ vg_window_x / 2, 4 }, world.pCmpLevel->title, 2, k_text_align_center );
3564 gui_text( (ui_px [2]){ vg_window_x / 2, 28 }, world.pCmpLevel->description, 1, k_text_align_center );
3565 }
3566
3567 #if 0
3568 if( world.st.state == k_game_state_update )
3569 {
3570 gui_group_id( 34 );
3571
3572 ui_global_ctx.cursor[2] = 458;
3573 ui_global_ctx.cursor[3] = 316;
3574 ui_global_ctx.cursor[0] = vg_window_x / 2 - 229;
3575 ui_global_ctx.cursor[1] = vg_window_y / 2 - 158;
3576
3577 gui_new_node();
3578 {
3579 gui_capture_mouse( 200 );
3580 gui_fill_rect( ui_global_ctx.cursor, 0xE8303030 );
3581
3582 ui_px title_pos[2];
3583 title_pos[0] = ui_global_ctx.cursor[0] + 229;
3584 title_pos[1] = ui_global_ctx.cursor[1] + 16;
3585
3586 gui_text( title_pos, "Update 1.5", 2, k_text_align_center );
3587
3588 gui_text( (ui_px [2]){ ui_global_ctx.cursor[0] + 16, title_pos[1] + 45 },
3589 "Welcome to the first update to marble computing!"
3590 "\n"
3591 "New features have been added:\n"
3592 "\n"
3593 " - Settings menu\n"
3594 " - Map skins\n"
3595 " - More levels and a new block type\n"
3596 " - Scores for each level\n"
3597 " - Zooming and panning (mousewheel)\n"
3598 "\n"
3599 "There is much more in the works, such as a\n"
3600 "soundtrack, and the rest of the levels for the\n"
3601 "3 bit computer!\n"
3602 "\n"
3603 "Thank you everyone for enjoying my game :)\n",
3604 1, k_text_align_left
3605 );
3606
3607 ui_global_ctx.cursor[2] = 100;
3608 ui_global_ctx.cursor[3] = 30;
3609 ui_global_ctx.cursor[0] += 229 - 50;
3610 ui_global_ctx.cursor[1] += 316 - 30 - 16;
3611
3612 if( gui_button( 1 ) )
3613 {
3614 world.st.state = k_game_state_main;
3615 }
3616 gui_text( (ui_px [2]){ ui_global_ctx.cursor[0] + 50,
3617 ui_global_ctx.cursor[1] + 4 }, "OK", 1, k_text_align_center );
3618 gui_end();
3619 }
3620 gui_end();
3621 }
3622 else
3623 #endif
3624 if( world.st.state == k_game_state_settings )
3625 {
3626 gui_group_id( 35 );
3627
3628 ui_global_ctx.cursor[2] = 225;
3629 gui_fill_y();
3630 gui_align_right();
3631
3632 gui_new_node();
3633 {
3634 gui_capture_mouse( 200 );
3635
3636 gui_fill_rect( ui_global_ctx.cursor, 0xC0202020 );
3637 ui_rect_pad( ui_global_ctx.cursor, 8 );
3638
3639 ui_global_ctx.cursor[3] = 25;
3640
3641 gui_new_node();
3642 {
3643 gui_text( ui_global_ctx.cursor, "SETTINGS", 2, 0 );
3644
3645 ui_global_ctx.cursor[2] = 25;
3646 gui_align_right();
3647
3648 if( gui_button(4) == k_button_click )
3649 {
3650 world.st.buttons[ k_world_button_settings ].state = 0;
3651 world.st.state = k_game_state_main;
3652 vg_info( "exit\n" );
3653 }
3654 ui_global_ctx.cursor[0] += 4;
3655 ui_global_ctx.cursor[1] -= 4;
3656 gui_text( ui_global_ctx.cursor, "x", 2, 0 );
3657 gui_end();
3658 }
3659 gui_end();
3660
3661 // Colour scheme selection
3662 ui_global_ctx.cursor[1] += 30;
3663
3664 gui_text( ui_global_ctx.cursor, "Colour Scheme", 1, 0 );
3665 ui_global_ctx.cursor[1] += 25;
3666
3667 gui_new_node();
3668 {
3669 ui_global_ctx.cursor[2] = 50;
3670
3671 for( int i = 0; i < 4; i ++ )
3672 {
3673 gui_new_node();
3674 {
3675 // Convert to RGB
3676 u32 rgb = 0xff000000;
3677
3678 for( int j = 0; j < 3; j ++ )
3679 rgb |= (u32)(colour_sets[ colour_set_id ][i][j]*255.0f) << j * 8;
3680
3681 gui_fill_rect( ui_global_ctx.cursor, rgb );
3682 }
3683 gui_end_right();
3684 }
3685 }
3686 gui_end_down();
3687
3688 gui_new_node();
3689 {
3690 ui_global_ctx.cursor[2] = 25;
3691 if( gui_button( 0 ) == k_button_click )
3692 {
3693 if( colour_set_id > 0 )
3694 colour_set_id --;
3695 }
3696 gui_text( ui_global_ctx.cursor, "<", 2, 0 );
3697 gui_end_right();
3698
3699 ui_global_ctx.cursor[2] = 150;
3700 gui_new_node();
3701 {
3702 gui_fill_rect( ui_global_ctx.cursor, 0x33ffffff );
3703 gui_text(
3704 (ui_px [2]){ ui_global_ctx.cursor[0] + 75, ui_global_ctx.cursor[1] + 6 },
3705 (const char *[]){ "Normal", "Extra1", "Extra2" }[ colour_set_id ],
3706 1, k_text_align_center
3707 );
3708 }
3709 gui_end_right();
3710
3711 ui_global_ctx.cursor[2] = 25;
3712 if( gui_button( 1 ) == k_button_click )
3713 {
3714 if( colour_set_id < vg_list_size( colour_sets )-1 )
3715 colour_set_id ++;
3716 }
3717 gui_text( ui_global_ctx.cursor, ">", 2, 0 );
3718 gui_end_down();
3719 }
3720 gui_end_down();
3721
3722 // Theme select
3723 ui_global_ctx.cursor[1] += 16;
3724
3725 #if 0
3726 gui_text( ui_global_ctx.cursor, "Tile Theme", 1, 0 );
3727 ui_global_ctx.cursor[1] += 20;
3728
3729 gui_new_node();
3730 {
3731 ui_global_ctx.cursor[2] = 25;
3732 if( gui_button( 2 ) == k_button_click )
3733 {
3734 if( world_theme_id > 0 )
3735 world_theme_id --;
3736 }
3737 gui_text( ui_global_ctx.cursor, "<", 2, 0 );
3738 gui_end_right();
3739
3740 ui_global_ctx.cursor[2] = 150;
3741 gui_new_node();
3742 {
3743 gui_fill_rect( ui_global_ctx.cursor, 0x33ffffff );
3744 gui_text(
3745 (ui_px [2]){ ui_global_ctx.cursor[0] + 75, ui_global_ctx.cursor[1] + 6 },
3746 world_themes[ world_theme_id ].name, 1, k_text_align_center
3747 );
3748 }
3749 gui_end_right();
3750
3751 ui_global_ctx.cursor[2] = 25;
3752 if( gui_button( 3 ) == k_button_click )
3753 {
3754 if( world_theme_id < vg_list_size( world_themes )-1 )
3755 world_theme_id ++;
3756 }
3757 gui_text( ui_global_ctx.cursor, ">", 2, 0 );
3758 gui_end_down();
3759 }
3760 gui_end_down();
3761 #endif
3762
3763 gui_text( ui_global_ctx.cursor, "Graphics", 1, 0 );
3764 ui_global_ctx.cursor[1] += 20;
3765
3766 gui_new_node();
3767 {
3768 ui_global_ctx.cursor[2] = 200;
3769 if( gui_button( 5 ) == k_button_click )
3770 {
3771 enable_bloom ^= 0x1;
3772 }
3773 ui_global_ctx.cursor[0] += 4;
3774 ui_global_ctx.cursor[1] += 4;
3775 gui_text( ui_global_ctx.cursor, enable_bloom?
3776 "Bloom: ENABLED":
3777 "Bloom: DISABLED", 1, 0 );
3778 gui_end_down();
3779 }
3780 gui_end_down();
3781
3782 ui_global_ctx.cursor[1] += 10;
3783 gui_new_node();
3784 {
3785 ui_global_ctx.cursor[2] = 200;
3786 if( gui_button( 6 ) == k_button_click )
3787 {
3788 enable_vignette ^= 0x1;
3789 }
3790 ui_global_ctx.cursor[0] += 4;
3791 ui_global_ctx.cursor[1] += 4;
3792 gui_text( ui_global_ctx.cursor, enable_vignette?
3793 "Vignette: ENABLED":
3794 "Vignette: DISABLED", 1, 0 );
3795 gui_end_down();
3796 }
3797 gui_end_down();
3798
3799 ui_global_ctx.cursor[1] += 16;
3800 gui_text( ui_global_ctx.cursor, "Music Volume", 1, 0 );
3801 ui_global_ctx.cursor[1] += 20;
3802
3803 gui_new_node();
3804 {
3805 ui_px slider_start = ui_global_ctx.cursor[0];
3806
3807 float const bar_width = 45.0f,
3808 bar_total = 200.0f,
3809 bar_movement = bar_total-bar_width,
3810 bar_start = bar_width * 0.5f;
3811
3812 ui_global_ctx.cursor[2] = bar_total;
3813 ui_fill_rect( &ui_global_ctx,
3814 ui_global_ctx.cursor,
3815 0xff111111 );
3816
3817 ui_global_ctx.cursor[2] = bar_width;
3818 ui_global_ctx.cursor[0] = slider_start + music_volume * bar_movement;
3819
3820 int status = gui_button( 7 );
3821
3822 static ui_px drag_start = 0.0f;
3823
3824 if( status == k_button_start_click )
3825 drag_start = ui_global_ctx.mouse[0];
3826 else if( ui_global_ctx.capture_lock &&
3827 (ui_global_ctx.capture_mouse_id == ui_group_id(&ui_global_ctx,7)))
3828 {
3829 ui_px drag_offset = ui_global_ctx.mouse[0] - drag_start;
3830 float offset_local = (drag_start + drag_offset - slider_start - bar_start) / bar_movement;
3831
3832 music_volume = vg_minf( vg_maxf( offset_local, 0.0f ), 1.0f );
3833 music_volume_update();
3834 }
3835
3836 ui_global_ctx.cursor[0] += 4;
3837 ui_global_ctx.cursor[1] += 4;
3838
3839 char volbuf[12];
3840 snprintf( volbuf, 12, "%.2f", music_volume );
3841 gui_text( ui_global_ctx.cursor, volbuf, 1, 0 );
3842 gui_end_down();
3843 }
3844 gui_end_down();
3845 }
3846 gui_end();
3847 }
3848 }
3849
3850 #if STEAM_LEADERBOARDS
3851 void leaderboard_dispatch_score(void)
3852 {
3853
3854 sw_upload_leaderboard_score(
3855 ui_data.upload_request.level->steam_leaderboard,
3856 k_ELeaderboardUploadScoreMethodKeepBest,
3857 ui_data.upload_request.score,
3858 NULL,
3859 0
3860 );
3861
3862 ui_data.upload_request.is_waiting = 0;
3863
3864 vg_success( "Dispatched leaderboard score\n" );
3865 }
3866
3867 void leaderboard_found( LeaderboardFindResult_t *pCallback )
3868 {
3869 if( !pCallback->m_bLeaderboardFound )
3870 {
3871 vg_error( "Leaderboard could not be found\n" );
3872 ui_data.steam_leaderboard = 0;
3873 }
3874 else
3875 {
3876 const char *recieved_name = sw_get_leaderboard_name( pCallback->m_hSteamLeaderboard );
3877
3878 // Update UI state and request entries if this callback found the current UI level
3879 if( ui_data.level_selected )
3880 {
3881 if( !strcmp( recieved_name, ui_data.level_selected->map_name ) )
3882 {
3883 sw_download_leaderboard_entries( pCallback->m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 0, 8 );
3884 ui_data.level_selected->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3885 }
3886 }
3887
3888 // Dispatch the waiting request if there was one
3889 if( ui_data.upload_request.is_waiting )
3890 {
3891 if( !strcmp( recieved_name, ui_data.upload_request.level->map_name ) )
3892 {
3893 ui_data.upload_request.level->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3894 leaderboard_dispatch_score();
3895 }
3896 }
3897 }
3898 }
3899
3900 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
3901 {
3902 // Update UI if this leaderboard matches what we currently have in view
3903 if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
3904 {
3905 vg_info( "Recieved %d entries\n", pCallback->m_cEntryCount );
3906 ui_data.leaderboard_count = VG_MIN( pCallback->m_cEntryCount, 8 );
3907
3908 u64_steamid local_player = sw_get_steamid();
3909
3910 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
3911 {
3912 LeaderboardEntry_t entry;
3913 sw_get_downloaded_entry( pCallback->m_hSteamLeaderboardEntries, i, &entry, NULL, 0 );
3914
3915 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
3916
3917 player->id = entry.m_steamIDUser.m_unAll64Bits;
3918 strncpy( player->player_name, sw_get_friend_persona_name( player->id ), vg_list_size( player->player_name )-1 );
3919 player->score = entry.m_nScore;
3920
3921 snprintf( player->score_text, vg_list_size(player->score_text), "%d", player->score );
3922 player->texture = sw_get_player_image( player->id );
3923
3924 if( player->texture == 0 )
3925 player->texture = tex_unkown.name;
3926
3927 player->is_local_player = local_player == player->id? 1: 0;
3928 }
3929
3930 if( ui_data.leaderboard_count )
3931 ui_data.leaderboard_show = 1;
3932 else
3933 ui_data.leaderboard_show = 0;
3934 }
3935 else vg_warn( "Downloaded leaderboard does not match requested!\n" );
3936 }
3937
3938 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
3939 {
3940 if( ui_data.upload_request.is_waiting )
3941 vg_warn( "You are uploading leaderboard entries too quickly!\n" );
3942
3943 ui_data.upload_request.level = cmp_level;
3944 ui_data.upload_request.score = score;
3945 ui_data.upload_request.is_waiting = 1;
3946
3947 // If leaderboard ID has been downloaded already then just immediately dispatch this
3948 if( cmp_level->steam_leaderboard )
3949 leaderboard_dispatch_score();
3950 else
3951 sw_find_leaderboard( cmp_level->map_name );
3952 }
3953 #endif
3954
3955 // CONSOLE COMMANDS
3956 // ===========================================================================================================
3957
3958 static int console_credits( int argc, char const *argv[] )
3959 {
3960 vg_info( "Aknowledgements:\n" );
3961 vg_info( " GLFW zlib/libpng glfw.org\n" );
3962 vg_info( " miniaudio MIT0 miniaud.io\n" );
3963 vg_info( " QOI MIT phoboslab.org\n" );
3964 vg_info( " STB library MIT nothings.org\n" );
3965 return 0;
3966 }
3967
3968 static int console_save_map( int argc, char const *argv[] )
3969 {
3970 if( !world.initialzed )
3971 {
3972 vg_error( "Tried to save uninitialized map!\n" );
3973 return 0;
3974 }
3975
3976 char map_path[ 256 ];
3977
3978 strcpy( map_path, "sav/" );
3979 strcat( map_path, world.map_name );
3980 strcat( map_path, ".map" );
3981
3982 FILE *test_writer = fopen( map_path, "wb" );
3983 if( test_writer )
3984 {
3985 vg_info( "Saving map to '%s'\n", map_path );
3986 map_serialize( test_writer );
3987
3988 fclose( test_writer );
3989 return 1;
3990 }
3991 else
3992 {
3993 vg_error( "Unable to open stream for writing\n" );
3994 return 0;
3995 }
3996 }
3997
3998 static int console_load_map( int argc, char const *argv[] )
3999 {
4000 char map_path[ 256 ];
4001
4002 if( argc >= 1 )
4003 {
4004 // try from saves
4005 strcpy( map_path, "sav/" );
4006 strcat( map_path, argv[0] );
4007 strcat( map_path, ".map" );
4008
4009 char *text_source = vg_textasset_read( map_path );
4010
4011 if( !text_source )
4012 {
4013 strcpy( map_path, "maps/" );
4014 strcat( map_path, argv[0] );
4015 strcat( map_path, ".map" );
4016
4017 text_source = vg_textasset_read( map_path );
4018 }
4019
4020 if( text_source )
4021 {
4022 vg_info( "Loading map: '%s'\n", map_path );
4023 world.pCmpLevel = NULL;
4024
4025 if( !map_load( text_source, argv[0] ) )
4026 {
4027 free( text_source );
4028 return 0;
4029 }
4030
4031 free( text_source );
4032 return 1;
4033 }
4034 else
4035 {
4036 vg_error( "Missing maps '%s'\n", argv[0] );
4037 return 0;
4038 }
4039 }
4040 else
4041 {
4042 vg_error( "Missing argument <map_path>\n" );
4043 return 0;
4044 }
4045 }
4046
4047 static int console_changelevel( int argc, char const *argv[] )
4048 {
4049 if( argc >= 1 )
4050 {
4051 // Save current level
4052 console_save_map( 0, NULL );
4053
4054 if( console_load_map( argc, argv ) )
4055 {
4056 world.st.zoom = 0.0f;
4057 simulation_stop();
4058 return 1;
4059 }
4060 }
4061 else
4062 {
4063 vg_error( "Missing argument <map_path>\n" );
4064 }
4065
4066 return 0;
4067 }
4068
4069 // START UP / SHUTDOWN
4070 // ===========================================================================================================
4071
4072 #define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
4073 X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY
4074
4075 void vg_start(void)
4076 {
4077 // Steamworks callbacks
4078 #ifdef STEAM_LEADERBOARDS
4079 sw_leaderboard_found = &leaderboard_found;
4080 sw_leaderboard_downloaded = &leaderboard_downloaded;
4081 #endif
4082
4083 vg_function_push( (struct vg_cmd){
4084 .name = "_map_write",
4085 .function = console_save_map
4086 });
4087
4088 vg_function_push( (struct vg_cmd){
4089 .name = "_map_load",
4090 .function = console_load_map
4091 });
4092
4093 vg_function_push( (struct vg_cmd){
4094 .name = "map",
4095 .function = console_changelevel
4096 });
4097
4098 vg_function_push( (struct vg_cmd){
4099 .name = "credits",
4100 .function = console_credits
4101 });
4102
4103 vg_convar_push( (struct vg_convar){
4104 .name = "colours",
4105 .data = &colour_set_id,
4106 .data_type = k_convar_dtype_i32,
4107 .opt_i32 = { .min = 0, .max = 2, .clamp = 1 },
4108 .persistent = 1
4109 });
4110
4111 vg_convar_push( (struct vg_convar){
4112 .name = "theme",
4113 .data = &world_theme_id,
4114 .data_type = k_convar_dtype_i32,
4115 .opt_i32 = { .min = 0, .max = vg_list_size( world_themes )-1, .clamp = 1 },
4116 .persistent = 1,
4117 .update = NULL
4118 });
4119
4120 vg_convar_push( (struct vg_convar){
4121 .name = "enable_bloom",
4122 .data = &enable_bloom,
4123 .data_type = k_convar_dtype_i32,
4124 .opt_i32 = { .min = 0, .max = 1, .clamp = 1 },
4125 .persistent = 1,
4126 .update = NULL
4127 });
4128
4129 vg_convar_push( (struct vg_convar){
4130 .name = "enable_vignette",
4131 .data = &enable_vignette,
4132 .data_type = k_convar_dtype_i32,
4133 .opt_i32 = { .min = 0, .max = 1, .clamp = 1 },
4134 .persistent = 1,
4135 .update = NULL
4136 });
4137
4138 vg_convar_push( (struct vg_convar){
4139 .name = "music_volume",
4140 .data = &music_volume,
4141 .data_type = k_convar_dtype_f32,
4142 .opt_f32 = { .min = 0.0f, .max = 1.0f, .clamp = 1 },
4143 .persistent = 1,
4144 .update = music_volume_update
4145 });
4146
4147 // Combined quad, long quad / empty circle / filled circle mesh
4148 {
4149 float combined_mesh[6*6 + 32*6*3] = {
4150 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
4151 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
4152
4153 0.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.2f,
4154 0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f,
4155
4156 TRANSFORM_TRI_2D( 0.15f,0.05f,0.4f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f ),
4157 TRANSFORM_TRI_2D( 0.15f,0.80f,0.4f, 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 1.0f )
4158 };
4159
4160 float *circle_mesh = combined_mesh + 6*6;
4161 int const res = 32;
4162
4163 for( int i = 0; i < res; i ++ )
4164 {
4165 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
4166 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
4167
4168 circle_mesh[ i*6+0 ] = 0.0f;
4169 circle_mesh[ i*6+1 ] = 0.0f;
4170
4171 v2_copy( v0, circle_mesh + 32*6 + i*12 );
4172 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
4173 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
4174
4175 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
4176 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
4177 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
4178
4179 v2_copy( v0, circle_mesh + i*6+4 );
4180 v2_copy( v1, circle_mesh + i*6+2 );
4181 v2_copy( v0, circle_mesh+i*6+4 );
4182 v2_copy( v1, circle_mesh+i*6+2 );
4183 }
4184
4185 init_mesh( &world.shapes, combined_mesh, vg_list_size( combined_mesh ) );
4186 }
4187
4188 // Create wire mesh
4189 {
4190 int const num_segments = 64;
4191
4192 struct mesh_wire *mw = &world.wire;
4193
4194 v2f wire_points[ num_segments * 2 ];
4195 u16 wire_indices[ 6*(num_segments-1) ];
4196
4197 for( int i = 0; i < num_segments; i ++ )
4198 {
4199 float l = (float)i / (float)(num_segments-1);
4200
4201 v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
4202 v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
4203
4204 if( i < num_segments-1 )
4205 {
4206 wire_indices[ i*6+0 ] = i*2 + 0;
4207 wire_indices[ i*6+1 ] = i*2 + 1;
4208 wire_indices[ i*6+2 ] = i*2 + 3;
4209 wire_indices[ i*6+3 ] = i*2 + 0;
4210 wire_indices[ i*6+4 ] = i*2 + 3;
4211 wire_indices[ i*6+5 ] = i*2 + 2;
4212 }
4213 }
4214
4215 glGenVertexArrays( 1, &mw->vao );
4216 glGenBuffers( 1, &mw->vbo );
4217 glGenBuffers( 1, &mw->ebo );
4218 glBindVertexArray( mw->vao );
4219
4220 glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
4221
4222 glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
4223 glBindVertexArray( mw->vao );
4224
4225 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
4226 glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
4227
4228 // XY
4229 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
4230 glEnableVertexAttribArray( 0 );
4231
4232 VG_CHECK_GL();
4233
4234 mw->em = vg_list_size( wire_indices );
4235 }
4236
4237 // Create info data texture
4238 {
4239 glGenTextures( 1, &world.background_data );
4240 glBindTexture( GL_TEXTURE_2D, world.background_data );
4241 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
4242 vg_tex2d_nearest();
4243 }
4244
4245 // Create random smaples texture
4246 {
4247 u8 *data = malloc(512*512*2);
4248 for( int i = 0; i < 512*512*2; i ++ )
4249 data[ i ] = rand()/(RAND_MAX/255);
4250
4251 glGenTextures( 1, &world.random_samples );
4252 glBindTexture( GL_TEXTURE_2D, world.random_samples );
4253 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
4254 vg_tex2d_nearest();
4255 vg_tex2d_repeat();
4256
4257 free( data );
4258 }
4259
4260 resource_load_main();
4261
4262 // Init world text
4263 {
4264 ui_init_context( &world.st.world_text, 15000 );
4265 }
4266
4267 // Restore gamestate
4268 career_local_data_init();
4269 career_load();
4270
4271 /* Create framebuffers */
4272 glGenFramebuffers( 1, &world.st.framebuffer );
4273 glBindFramebuffer( GL_FRAMEBUFFER, world.st.framebuffer );
4274
4275 glGenTextures( 1, &world.st.colourbuffer );
4276 glBindTexture( GL_TEXTURE_2D, world.st.colourbuffer );
4277 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vg_window_x, vg_window_y,
4278 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
4279
4280 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
4281 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
4282 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
4283 world.st.colourbuffer, 0);
4284
4285 /* Bloom framebuffer (quater res) */
4286 glGenFramebuffers( 2, world.st.bloomframebuffer );
4287 glGenTextures( 2, world.st.bloomcolourbuffer );
4288
4289 for( int i=0; i<2; i++ )
4290 {
4291 glBindFramebuffer( GL_FRAMEBUFFER, world.st.bloomframebuffer[i] );
4292
4293 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[i] );
4294 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB,
4295 vg_window_x/EFFECT_BUFFER_RATIO, vg_window_y/EFFECT_BUFFER_RATIO,
4296 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
4297 vg_tex2d_clamp();
4298
4299 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
4300 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
4301 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
4302 GL_TEXTURE_2D, world.st.bloomcolourbuffer[i], 0);
4303 }
4304 }
4305
4306 void vg_free(void)
4307 {
4308 #ifdef VG_STEAM
4309 sw_free_opengl();
4310 #endif
4311
4312 console_save_map( 0, NULL );
4313 career_serialize();
4314
4315 resource_free_main();
4316
4317 glDeleteTextures( 1, &world.background_data );
4318 glDeleteTextures( 1, &world.random_samples );
4319
4320 glDeleteVertexArrays( 1, &world.wire.vao );
4321 glDeleteBuffers( 1, &world.wire.vbo );
4322 glDeleteBuffers( 1, &world.wire.ebo );
4323
4324 free_mesh( &world.shapes );
4325
4326 ui_context_free( &world.st.world_text );
4327
4328 map_free();
4329 }
4330
4331 int main( int argc, char *argv[] )
4332 {
4333 vg_init( argc, argv, "Marble Computing" );
4334 return 0;
4335 }