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