theme selection
[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 colour_bg;
427 vg_tex2d *tex_tiles;
428 }
429 world_themes[] =
430 {
431 {
432 "Wood",
433 {},
434 &tex_tiles_wood
435 },
436 {
437 "Minimal",
438 {},
439 &tex_tiles_min
440 },
441 {
442 "Lab",
443 {},
444 &tex_tiles_lab
445 }
446 };
447
448 static void colour_code_v3( char const cc, v3f target )
449 {
450 if( cc >= 'a' && cc <= 'z' )
451 {
452 int id = cc - 'a';
453
454 if( id < vg_list_size( colour_sets[0] ) )
455 {
456 v3_copy( colour_sets[colour_set_id][ id ], target );
457 return;
458 }
459 }
460
461 v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
462 }
463
464 static int hash21i( v2i p, u32 umod )
465 {
466 static const int random_noise[] =
467 {
468 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,
469 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,
470 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,
471 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,
472 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,
473 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,
474 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,
475 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,
476 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,
477 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,
478 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,
479 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,
480 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,
481 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,
482 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,
483 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,
484 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,
485 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,
486 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,
487 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,
488 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,
489 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,
490 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,
491 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,
492 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,
493 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,
494 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,
495 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,
496 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,
497 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,
498 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,
499 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
500 };
501
502 return random_noise[ (random_noise[p[1] & 1023] + p[0]) & 1023 ] & umod;
503 }
504
505 // MESH
506 // ===========================================================================================================
507
508 static void init_mesh( struct mesh *m, float const *tris, u32 length )
509 {
510 m->elements = length/3;
511 glGenVertexArrays( 1, &m->vao );
512 glGenBuffers( 1, &m->vbo );
513
514 glBindVertexArray( m->vao );
515 glBindBuffer( GL_ARRAY_BUFFER, m->vbo );
516 glBufferData( GL_ARRAY_BUFFER, length*sizeof(float), tris, GL_STATIC_DRAW );
517
518 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
519 glEnableVertexAttribArray( 0 );
520
521 VG_CHECK_GL();
522 }
523
524 static void free_mesh( struct mesh *m )
525 {
526 glDeleteVertexArrays( 1, &m->vao );
527 glDeleteBuffers( 1, &m->vbo );
528 }
529
530 static void draw_mesh( int const start, int const count )
531 {
532 glDrawArrays( GL_TRIANGLES, start*3, count*3 );
533 }
534
535 static void use_mesh( struct mesh *m )
536 {
537 glBindVertexArray( m->vao );
538 }
539
540 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
541
542 // WORLD/MAP
543 // ===========================================================================================================
544
545 static void map_free(void)
546 {
547 arrfree( world.data );
548 arrfree( world.io );
549
550 free( world.cmd_buf_tiles );
551
552 world.w = 0;
553 world.h = 0;
554 world.data = NULL;
555 world.io = NULL;
556 world.score = 0;
557 world.time = 0;
558 world.completed = 0;
559 world.max_runs = 0;
560 world.initialzed = 0;
561 }
562
563 static void io_reset(void)
564 {
565 for( int i = 0; i < arrlen( world.io ); i ++ )
566 {
567 struct cell_terminal *term = &world.io[i];
568
569 for( int j = 0; j < term->run_count; j ++ )
570 term->runs[j].recv_count = 0;
571 }
572 }
573
574 static struct cell *pcell( v2i pos )
575 {
576 return &world.data[ pos[1]*world.w + pos[0] ];
577 }
578
579 static void lcell( int id, v2i pos )
580 {
581 pos[0] = id % world.w;
582 pos[1] = (id - pos[0]) / world.w;
583 }
584
585 static void map_reclassify( v2i start, v2i end, int update_texbuffer )
586 {
587 v2i full_start = { 1,1 };
588 v2i full_end = { world.w-1, world.h-1 };
589
590 if( !start || !end )
591 {
592 start = full_start;
593 end = full_end;
594 }
595
596 // Texture data
597 u8 info_buffer[64*64*4];
598 u32 pixel_id = 0;
599
600 int px0 = vg_max( start[0], full_start[0] ),
601 px1 = vg_min( end[0], full_end[0] ),
602 py0 = vg_max( start[1], full_start[1] ),
603 py1 = vg_min( end[1], full_end[1] );
604
605 for( int y = py0; y < py1; y ++ )
606 {
607 for( int x = px0; x < px1; x ++ )
608 {
609 struct cell *cell = pcell((v2i){x,y});
610
611 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
612
613 u8 height = 0;
614 u8 config = 0x00;
615
616 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
617 {
618 for( int i = 0; i < vg_list_size( dirs ); i ++ )
619 {
620 struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]});
621 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
622 config |= 0x1 << i;
623 }
624
625 height = 128;
626 }
627 else
628 {
629 if( cell->state & FLAG_WALL )
630 height = 0xFF-0x3F + hash21i( (v2i){x,y}, 0x3F );
631
632 config = 0xF;
633 }
634
635 pcell((v2i){x,y})->config = config;
636
637 u8 *info_px = &info_buffer[ (pixel_id ++)*4 ];
638 info_px[0] = height;
639 info_px[1] = cell->state & FLAG_WALL? 0: 255;
640 info_px[2] = 0;
641 info_px[3] = 0;
642
643 if(
644 (
645 ((cell->state & FLAG_IS_TRIGGER) && (cell->config == 0xF || cell->config == k_cell_type_split)) ||
646 ((cell->state & FLAG_TARGETED) && ((cell->config != k_cell_type_split) && !(cell->state & FLAG_EMITTER)))
647 ) && update_texbuffer
648 ){
649 cell->state &= ~(FLAG_TARGETED|FLAG_IS_TRIGGER);
650 for( u32 i = 0; i < 2; i ++ )
651 {
652 if( cell->links[i] )
653 {
654 struct cell *other_ptr = &world.data[ cell->links[i] ];
655 other_ptr->links[ i ] = 0;
656 other_ptr->state &= ~FLAG_IS_TRIGGER;
657
658 if( other_ptr->links[ i ^ 0x1 ] == 0 )
659 other_ptr->state &= ~FLAG_TARGETED;
660 }
661 }
662
663 cell->links[0] = 0;
664 cell->links[1] = 0;
665 }
666 }
667 }
668
669 if( update_texbuffer )
670 {
671 glBindTexture( GL_TEXTURE_2D, world.background_data );
672 glTexSubImage2D( GL_TEXTURE_2D, 0, px0 + 16, py0 + 16, px1-px0, py1-py0, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
673 }
674 }
675
676 static u32 gen_text_buffer( const char *str, struct sdf_font *font, v2f origin, float size, u32 start )
677 {
678 u32 count = 0;
679
680 v2f cursor;
681 v2f invUv;
682 v2_copy( origin, cursor );
683
684 float invScale = (size / (float)font->size);
685 invUv[0] = 1.0f / (float)font->width;
686 invUv[1] = 1.0f / (float)font->height;
687
688 u16 base_idx = start * 4;
689
690 const char *_c = str;
691 char c;
692 while( (c = *(_c ++)) )
693 {
694 if( c == '\n' )
695 {
696 cursor[1] -= size * 1.25f;
697 cursor[0] = origin[0];
698 }
699 else if( c >= 32 && c <= 126 )
700 {
701 struct sdf_char *pch = &font->characters[ c - ' ' ];
702 struct vector_glyph_vert *vt = &text_buffers.buffer[ count * 4 ];
703 u16 *ind = &text_buffers.indices[ count * 6 ];
704
705 // Emit quad
706 v2f p0; v2f uv0;
707 v2f p1; v2f uv1;
708
709 v2_muladds( cursor, (v2f){ pch->originX, -pch->originY }, -invScale, p0 );
710 v2_muladds( p0, (v2f){ pch->w, -pch->h }, invScale, p1 );
711
712 v2_mul( (v2f){ pch->uvx, pch->uvy }, invUv, uv0 );
713 v2_muladd( uv0, (v2f){ pch->w, pch->h }, invUv, uv1 );
714
715 v2_copy( p0, vt[0].co );
716 v2_copy( uv0, vt[0].uv );
717 vt[0].colour = 0xffffffff;
718
719 v2_copy( (v2f){ p0[0], p1[1] }, vt[1].co );
720 v2_copy( (v2f){ uv0[0], uv1[1] }, vt[1].uv );
721 vt[1].colour = 0xffffffff;
722
723 v2_copy( p1, vt[2].co );
724 v2_copy( uv1, vt[2].uv );
725 vt[2].colour = 0xffffffff;
726
727 v2_copy( (v2f){ p1[0], p0[1] }, vt[3].co );
728 v2_copy( (v2f){ uv1[0], uv0[1] }, vt[3].uv );
729 vt[3].colour = 0xffffffff;
730
731 // Emit indices
732 ind[0] = base_idx+count*4;
733 ind[1] = base_idx+count*4+1;
734 ind[2] = base_idx+count*4+2;
735 ind[3] = base_idx+count*4;
736 ind[4] = base_idx+count*4+2;
737 ind[5] = base_idx+count*4+3;
738
739 cursor[0] += (float)pch->advance * invScale;
740 count ++;
741 }
742 }
743
744 glBindVertexArray( text_buffers.vao );
745
746 glBindBuffer( GL_ARRAY_BUFFER, text_buffers.vbo );
747 glBufferSubData( GL_ARRAY_BUFFER,
748 start*4*sizeof( struct vector_glyph_vert ),
749 count*4*sizeof( struct vector_glyph_vert ),
750 text_buffers.buffer
751 );
752
753 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, text_buffers.ebo );
754 glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, start*6*sizeof(u16), count*6*sizeof(u16), text_buffers.indices );
755
756 return count;
757 }
758
759 static void gen_level_text( struct cmp_level *pLevel )
760 {
761 text_buffers.title_count = gen_text_buffer( pLevel->title, &font_Ubuntu, (v2f){ -5.0f, -0.6f }, 0.6f, text_buffers.title_start );
762 text_buffers.desc_count = gen_text_buffer( pLevel->description, &font_Ubuntu, (v2f){ -5.0, -0.9f }, 0.25f, text_buffers.desc_start );
763 }
764
765 static int map_load( const char *str, const char *name )
766 {
767 //TODO: It may be worthwhile, at this point, to switch to binary encoding for save data
768
769 map_free();
770
771 char const *c = str;
772
773 // Scan for width
774 for(;; world.w ++)
775 {
776 if( c[world.w] == ';' )
777 break;
778 else if( !c[world.w] )
779 {
780 vg_error( "Unexpected EOF when parsing level\n" );
781 return 0;
782 }
783 }
784
785 struct cell *row = arraddnptr( world.data, world.w );
786 int cx = 0;
787 int reg_start = 0, reg_end = 0;
788
789 u32 *links_to_make = NULL;
790 int links_satisfied = 0;
791
792 char link_id_buffer[32];
793 int link_id_n = 0;
794
795 for(;;)
796 {
797 if( !*c )
798 break;
799
800 if( *c == '\r' ) { c ++; continue; } // fuck off windows
801
802 if( *c == ';' )
803 {
804 c ++;
805
806 if( *c == '\r' ) c ++;
807
808 // Parse attribs
809 if( *c != '\n' )
810 {
811 while( *c )
812 {
813 if( *c == '\r' ) { c ++; continue; }
814
815 if( reg_start < reg_end )
816 {
817 struct cell_terminal *terminal = &world.io[ reg_start ];
818 struct terminal_run *run = &terminal->runs[ terminal->run_count-1 ];
819
820 if( (*c >= 'a' && *c <= 'z') || *c == ' ' )
821 {
822 run->conditions[ run->condition_count ++ ] = *c;
823 }
824 else
825 {
826 if( *c == ',' || *c == '\n' )
827 {
828 reg_start ++;
829
830 if( *c == '\n' )
831 break;
832 }
833 else if( *c == ':' )
834 {
835 terminal->runs[ terminal->run_count ].condition_count = 0;
836 terminal->run_count ++;
837 world.max_runs = vg_max( world.max_runs, terminal->run_count );
838 }
839 else
840 {
841 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
842 goto IL_REG_ERROR;
843 }
844 }
845 }
846 else
847 {
848 if( links_satisfied < arrlen( links_to_make ) )
849 {
850 struct cell *target = &world.data[ links_to_make[ links_satisfied ] ];
851
852 if( (((u32)*c >= (u32)'0') && ((u32)*c <= (u32)'9')) || *c == '-' )
853 {
854 if( link_id_n >= vg_list_size( link_id_buffer )-1 )
855 {
856 vg_error( "Number was way too long to be parsed (row: %u)\n", world.h );
857 goto IL_REG_ERROR;
858 }
859
860 link_id_buffer[ link_id_n ++ ] = *c;
861 }
862 else if( *c == ',' || *c == '\n' )
863 {
864 link_id_buffer[ link_id_n ] = 0x00;
865 int value = atoi( link_id_buffer );
866
867 target->links[value >= 0? 1:0] = abs(value);
868 links_satisfied ++;
869 link_id_n = 0;
870
871 if( *c == '\n' )
872 break;
873 }
874 else
875 {
876 vg_error( "Invalid character '%c' (row: %u)\n", *c, world.h );
877 goto IL_REG_ERROR;
878 }
879 }
880 else
881 {
882 vg_error( "Too many values to assign (row: %u)\n", world.h );
883 goto IL_REG_ERROR;
884 }
885 }
886
887 c ++;
888 }
889 }
890
891 // Registry length-error checks
892 if( reg_start != reg_end )
893 {
894 vg_error( "Not enough spawn values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
895 goto IL_REG_ERROR;
896 }
897
898 if( links_satisfied != arrlen( links_to_make ) )
899 {
900 vg_error( "Not enough link values assigned (row: %u, %u of %u)\n", world.h, links_satisfied, arrlen( links_to_make ) );
901 goto IL_REG_ERROR;
902 }
903
904 if( cx != world.w )
905 {
906 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
907 goto IL_REG_ERROR;
908 }
909
910 row = arraddnptr( world.data, world.w );
911 cx = 0;
912 world.h ++;
913 reg_end = reg_start = arrlen( world.io );
914
915 arrsetlen( links_to_make, 0 );
916 links_satisfied = 0;
917 }
918 else
919 {
920 if( cx == world.w )
921 {
922 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
923 goto IL_REG_ERROR;
924 }
925
926 // Tile initialization
927 // row[ cx ] .. etc
928 struct cell *cell = &row[ cx ];
929 cell->config = 0xF;
930
931 cell->links[0] = 0;
932 cell->links[1] = 0;
933
934 if( *c == '+' || *c == '-' || *c == '*' )
935 {
936 struct cell_terminal *term = arraddnptr( world.io, 1 );
937 term->pos[0] = cx;
938 term->pos[1] = world.h;
939
940 term->run_count = 1;
941 term->runs[0].condition_count = 0;
942
943 switch( *c )
944 {
945 case '+': cell->state = FLAG_INPUT; break;
946 case '-': cell->state = FLAG_OUTPUT; break;
947 case '*': cell->state = FLAG_EMITTER; break;
948 }
949
950 reg_end ++;
951 }
952 else if( *c == '#' ) cell->state = FLAG_WALL;
953 else if( ((u32)*c >= (u32)'A') && ((u32)*c <= (u32)'A'+0xf) )
954 {
955 // Canal flag bits (4bit/16 value):
956 // 0: Canal present
957 // 1: Is trigger
958 // 2: Reserved
959 // 3: Reserved
960
961 cell->state = ((u32)*c - (u32)'A') & (FLAG_CANAL|FLAG_IS_TRIGGER);
962
963 if( cell->state & FLAG_IS_TRIGGER )
964 arrpush( links_to_make, cx + world.h*world.w );
965
966 world.score ++;
967 }
968 else cell->state = 0x00;
969
970 cx ++;
971 }
972
973 c ++;
974 }
975
976 // Fix emitter CC code
977 for( int i = 0; i < arrlen( world.io ); i ++ )
978 {
979 struct cell_terminal *term = &world.io[i];
980 struct cell *cell = pcell( term->pos );
981
982 if( cell->state & FLAG_EMITTER )
983 {
984 cell->cc = term->runs[0].conditions[0];
985 }
986 }
987
988 // Update data texture to fill out the background
989 {
990 u8 info_buffer[64*64*4];
991 for( int x = 0; x < 64; x ++ )
992 {
993 for( int y = 0; y < 64; y ++ )
994 {
995 u8 *px = &info_buffer[((x*64)+y)*4];
996
997 // Fade out edges of world so that there isnt an obvious line
998 int dist_x = 16 - VG_MIN( VG_MIN( x, 16 ), 16-VG_MAX( x-16-world.w, 0 ) );
999 int dist_y = 16 - VG_MIN( VG_MIN( y, 16 ), 16-VG_MAX( y-16-world.h, 0 ) );
1000 int dist = VG_MAX( dist_x, dist_y ) * 16;
1001
1002 int value = VG_MAX( 0, 0xFF-0x3F + hash21i( (v2i){x,y}, 0x3F ) - dist );
1003
1004 px[0] = value;
1005 px[1] = 0;
1006 px[2] = 0;
1007 px[3] = 0;
1008 }
1009 }
1010
1011 // Level selection area
1012
1013 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1014 {
1015 struct career_level_pack *grid = &career_packs[ i ];
1016
1017 int j = 0;
1018
1019 for( int y = 0; y < grid->dims[1]; y ++ )
1020 {
1021 for( int x = 0; x < grid->dims[0]; x ++ )
1022 {
1023 u8 *px = &info_buffer[((y+16+grid->origin[1])*64+16+x+grid->origin[0])*4];
1024 px[0] = 0x10;
1025
1026 if( j < grid->count )
1027 {
1028 struct cmp_level *lvl = &grid->pack[ j ++ ];
1029 v2i_add( grid->origin, (v2i){x,y}, lvl->btn.position );
1030 }
1031 }
1032 }
1033 }
1034
1035 info_buffer[(((16+world.h-3)*64)+world.w+16-1)*4] = 0x30;
1036 info_buffer[(((16+world.h-2)*64)+world.w+16-1)*4] = 0x30;
1037
1038 // Random walks.. kinda
1039 for( int i = 0; i < arrlen(world.io); i ++ )
1040 {
1041 struct cell_terminal *term = &world.io[ i ];
1042
1043 v2i turtle;
1044 v2i turtle_dir;
1045 int original_y;
1046
1047 // Only make breakouts for terminals on the edge
1048 if( !(term->pos[1] == 1 || term->pos[1] == world.h-2) )
1049 continue;
1050
1051 turtle[0] = 16+term->pos[0];
1052 turtle[1] = 16+term->pos[1];
1053
1054 turtle_dir[0] = 0;
1055 turtle_dir[1] = pcell(term->pos)->state & (FLAG_INPUT|FLAG_EMITTER)? 1: -1;
1056 original_y = turtle_dir[1];
1057
1058 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
1059 v2i_add( turtle_dir, turtle, turtle );
1060
1061 for( int i = 0; i < 100; i ++ )
1062 {
1063 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
1064
1065 v2i_add( turtle_dir, turtle, turtle );
1066
1067 if( turtle[0] == 0 ) break;
1068 if( turtle[0] == 63 ) break;
1069 if( turtle[1] == 0 ) break;
1070 if( turtle[1] == 63 ) break;
1071
1072 int random_value = hash21i( turtle, 0xFF );
1073 if( random_value > 255-40 && !turtle_dir[0] )
1074 {
1075 turtle_dir[0] = -1;
1076 turtle_dir[1] = 0;
1077 }
1078 else if( random_value > 255-80 && !turtle_dir[0] )
1079 {
1080 turtle_dir[0] = 1;
1081 turtle_dir[1] = 0;
1082 }
1083 else if( random_value > 255-100 )
1084 {
1085 turtle_dir[0] = 0;
1086 turtle_dir[1] = original_y;
1087 }
1088 }
1089 }
1090
1091 glBindTexture( GL_TEXTURE_2D, world.background_data );
1092 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 64, 64, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
1093 }
1094
1095 arrfree( links_to_make );
1096
1097 map_reclassify( NULL, NULL, 1 );
1098
1099 // Validate links
1100 for( int i = 0; i < world.h*world.w; i ++ )
1101 {
1102 struct cell *src = &world.data[i];
1103 if( src->state & FLAG_IS_TRIGGER )
1104 {
1105 int link_id = src->links[0]?0:1;
1106 if( src->links[link_id] <= world.h*world.w )
1107 {
1108 struct cell *target = &world.data[ src->links[link_id] ];
1109 if( ((target->state & FLAG_CANAL) && (target->config == k_cell_type_split))
1110 || (target->state & FLAG_EMITTER) )
1111 {
1112 if( target->links[ link_id ] )
1113 {
1114 vg_error( "Link target was already targeted\n" );
1115 goto IL_REG_ERROR;
1116 }
1117 else
1118 {
1119 // Valid link
1120 target->links[ link_id ] = i;
1121 target->state |= FLAG_TARGETED;
1122 }
1123 }
1124 else
1125 {
1126 vg_error( "Link target was invalid\n" );
1127 goto IL_REG_ERROR;
1128 }
1129 }
1130 else
1131 {
1132 vg_error( "Link target out of bounds\n" );
1133 goto IL_REG_ERROR;
1134 }
1135 }
1136 }
1137
1138 vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h );
1139
1140 io_reset();
1141
1142 strncpy( world.map_name, name, vg_list_size( world.map_name )-1 );
1143 world.initialzed = 1;
1144
1145 // Setup world button locations
1146 for( int i = 0; i < vg_list_size( world.st.buttons ); i ++ )
1147 {
1148 struct world_button *btn = &world.st.buttons[i];
1149 btn->position[0] = world.w -1;
1150 btn->position[1] = world.h -i -2;
1151 }
1152
1153 // Allocate buffers for render commands
1154 world.cmd_buf_tiles = malloc( world.w*world.h* sizeof( struct render_cmd ) );
1155 world.max_commands = world.w*world.h;
1156
1157 return 1;
1158
1159 IL_REG_ERROR:
1160 arrfree( links_to_make );
1161 map_free();
1162 return 0;
1163 }
1164
1165 static void map_serialize( FILE *stream )
1166 {
1167 for( int y = 0; y < world.h; y ++ )
1168 {
1169 for( int x = 0; x < world.w; x ++ )
1170 {
1171 struct cell *cell = pcell( (v2i){ x, y } );
1172
1173 if( cell->state & FLAG_WALL ) fputc( '#', stream );
1174 else if( cell->state & FLAG_INPUT ) fputc( '+', stream );
1175 else if( cell->state & FLAG_OUTPUT ) fputc( '-', stream );
1176 else if( cell->state & FLAG_EMITTER ) fputc( '*', stream );
1177 else if( cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1) )
1178 {
1179 fputc( (cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1)) + (u32)'A', stream );
1180 }
1181 else fputc( ' ', stream );
1182 }
1183
1184 fputc( ';', stream );
1185
1186 int terminal_write_count = 0;
1187
1188 for( int x = 0; x < world.w; x ++ )
1189 {
1190 for( int i = 0; i < arrlen( world.io ); i ++ )
1191 {
1192 struct cell_terminal *term = &world.io[i];
1193 if( v2i_eq( term->pos, (v2i){x,y} ) )
1194 {
1195 if( terminal_write_count )
1196 fputc( ',', stream );
1197 terminal_write_count ++;
1198
1199 for( int j = 0; j < term->run_count; j ++ )
1200 {
1201 struct terminal_run *run = &term->runs[j];
1202
1203 for( int k = 0; k < run->condition_count; k ++ )
1204 fputc( run->conditions[k], stream );
1205
1206 if( j < term->run_count-1 )
1207 fputc( ':', stream );
1208 }
1209 }
1210 }
1211 }
1212
1213 for( int x = 0; x < world.w; x ++ )
1214 {
1215 struct cell *cell = pcell( (v2i){ x,y } );
1216 if( cell->state & FLAG_IS_TRIGGER )
1217 {
1218 if( terminal_write_count )
1219 fputc( ',', stream );
1220 terminal_write_count ++;
1221
1222 fprintf( stream, "%d", cell->links[0]? -cell->links[0]: cell->links[1] );
1223 }
1224 }
1225
1226 fputc( '\n', stream );
1227 }
1228 }
1229
1230 // CAREER STATE
1231 // ===========================================================================================================
1232
1233 #pragma pack(push,1)
1234 struct dcareer_state
1235 {
1236 u32 version;
1237 i32 in_map;
1238
1239 u32 reserved[14];
1240
1241 struct dlevel_state
1242 {
1243 i32 score;
1244 i32 unlocked;
1245 i32 reserved[2];
1246 }
1247 levels[ NUM_CAMPAIGN_LEVELS ];
1248 };
1249 #pragma pack(pop)
1250
1251 static int career_load_success = 0;
1252
1253 static void career_serialize(void)
1254 {
1255 if( !career_load_success )
1256 return;
1257
1258 struct dcareer_state encoded;
1259 encoded.version = 2;
1260 encoded.in_map = world.pCmpLevel? world.pCmpLevel->serial_id: -1;
1261
1262 memset( encoded.reserved, 0, sizeof( encoded.reserved ) );
1263
1264 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1265 {
1266 struct career_level_pack *set = &career_packs[i];
1267
1268 for( int j = 0; j < set->count; j ++ )
1269 {
1270 struct cmp_level *lvl = &set->pack[j];
1271 struct dlevel_state *dest = &encoded.levels[lvl->serial_id];
1272
1273 dest->score = lvl->completed_score;
1274 dest->unlocked = lvl->unlocked;
1275 dest->reserved[0] = 0;
1276 dest->reserved[1] = 0;
1277 }
1278 }
1279
1280 vg_asset_write( "sav/game.sv2", &encoded, sizeof( struct dcareer_state ) );
1281 }
1282
1283 static void career_unlock_level( struct cmp_level *lvl );
1284 static void career_unlock_level( struct cmp_level *lvl )
1285 {
1286 lvl->unlocked = 1;
1287
1288 if( lvl->linked )
1289 career_unlock_level( lvl->linked );
1290 }
1291
1292 static void career_pass_level( struct cmp_level *lvl, int score, int upload )
1293 {
1294 if( score > 0 )
1295 {
1296 if( score < lvl->completed_score || lvl->completed_score == 0 )
1297 {
1298 #ifdef VG_STEAM
1299 if( !lvl->is_tutorial && upload )
1300 leaderboard_set_score( lvl, score );
1301 #endif
1302
1303 lvl->completed_score = score;
1304 }
1305
1306 if( lvl->unlock ) career_unlock_level( lvl->unlock );
1307
1308 #ifdef VG_STEAM
1309 if( lvl->achievement )
1310 {
1311 sw_set_achievement( lvl->achievement );
1312 }
1313
1314 // Check ALL maps to trigger master engineer
1315 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1316 {
1317 struct career_level_pack *set = &career_packs[i];
1318
1319 for( int j = 0; j < set->count; j ++ )
1320 {
1321 if( set->pack[j].completed_score == 0 )
1322 return;
1323 }
1324 }
1325
1326 sw_set_achievement( "MASTER_ENGINEER" );
1327 #endif
1328 }
1329 }
1330
1331 static void career_reset_level( struct cmp_level *lvl )
1332 {
1333 lvl->unlocked = 0;
1334 lvl->completed_score = 0;
1335 }
1336
1337 static void career_load(void)
1338 {
1339 i64 sz;
1340 struct dcareer_state encoded;
1341
1342 // Blank save state
1343 memset( (void*)&encoded, 0, sizeof( struct dcareer_state ) );
1344 encoded.in_map = 0;
1345 encoded.levels[0].unlocked = 1;
1346
1347 // Load and copy data into encoded
1348 void *cr = vg_asset_read_s( "sav/game.sv2", &sz );
1349
1350 if( cr )
1351 {
1352 if( sz > sizeof( struct dcareer_state ) )
1353 vg_warn( "This save file is too big! Some levels will be lost\n" );
1354
1355 if( sz <= offsetof( struct dcareer_state, levels ) )
1356 {
1357 vg_warn( "This save file is too small to have a header. Creating a blank one\n" );
1358 free( cr );
1359 }
1360
1361 memcpy( (void*)&encoded, cr, VG_MIN( sizeof( struct dcareer_state ), sz ) );
1362 free( cr );
1363 }
1364 else
1365 vg_info( "No save file... Using blank one\n" );
1366
1367 // Reset memory
1368 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1369 {
1370 struct career_level_pack *set = &career_packs[i];
1371
1372 for( int j = 0; j < set->count; j ++ )
1373 career_reset_level( &set->pack[j] );
1374 }
1375
1376 // Header information
1377 // =================================
1378
1379 struct cmp_level *lvl_to_load = &career_packs[0].pack[0];
1380
1381 // Decode everything from dstate
1382 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1383 {
1384 struct career_level_pack *set = &career_packs[i];
1385
1386 for( int j = 0; j < set->count; j ++ )
1387 {
1388 struct cmp_level *lvl = &set->pack[j];
1389 struct dlevel_state *src = &encoded.levels[lvl->serial_id];
1390
1391 if( src->unlocked ) career_unlock_level( lvl );
1392 if( src->score ) lvl->completed_score = src->score;
1393
1394 if( lvl->serial_id == encoded.in_map )
1395 lvl_to_load = lvl;
1396 }
1397 }
1398
1399 if( console_changelevel( 1, &lvl_to_load->map_name ) )
1400 {
1401 world.pCmpLevel = lvl_to_load;
1402 gen_level_text( world.pCmpLevel );
1403 }
1404
1405 career_load_success = 1;
1406 }
1407
1408 // MAIN GAMEPLAY
1409 // ===========================================================================================================
1410 static int is_simulation_running(void)
1411 {
1412 return world.st.buttons[ k_world_button_sim ].state;
1413 }
1414
1415 static void clear_animation_flags(void)
1416 {
1417 for( int i = 0; i < world.w*world.h; i ++ )
1418 world.data[ i ].state &= ~(FLAG_FLIP_FLOP|FLAG_FLIP_ROTATING);
1419 }
1420
1421 static void simulation_stop(void)
1422 {
1423 world.st.buttons[ k_world_button_sim ].state = 0;
1424 world.st.buttons[ k_world_button_pause ].state = 0;
1425
1426 world.num_fishes = 0;
1427 world.sim_frame = 0;
1428 world.sim_run = 0;
1429 world.frame_lerp = 0.0f;
1430
1431 io_reset();
1432
1433 sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
1434
1435 clear_animation_flags();
1436
1437 vg_info( "Stopping simulation!\n" );
1438 }
1439
1440 static void simulation_start(void)
1441 {
1442 vg_success( "Starting simulation!\n" );
1443
1444 sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
1445
1446 world.num_fishes = 0;
1447 world.sim_frame = 0;
1448 world.sim_run = 0;
1449
1450 world.sim_delta_speed = world.st.buttons[ k_world_button_speedy ].state? 10.0f: 2.5f;
1451 world.sim_delta_ref = vg_time;
1452 world.sim_internal_ref = 0.0f;
1453 world.sim_internal_time = 0.0f;
1454 world.pause_offset_target = 0.0f;
1455
1456 world.sim_target = 0;
1457
1458 clear_animation_flags();
1459
1460 io_reset();
1461
1462 if( world.pCmpLevel )
1463 {
1464 world.pCmpLevel->completed_score = 0;
1465 }
1466 }
1467
1468 static int world_check_pos_ok( v2i co, int dist )
1469 {
1470 return (co[0] < dist || co[0] >= world.w-dist || co[1] < dist || co[1] >= world.h-dist)? 0: 1;
1471 }
1472
1473 static int cell_interactive( v2i co )
1474 {
1475 struct cell *cell;
1476
1477 // Bounds check
1478 if( world_check_pos_ok( co, 1 ) )
1479 {
1480 cell = pcell( co );
1481 if( cell->state & FLAG_EMITTER )
1482 return 1;
1483 }
1484
1485 if( !world_check_pos_ok( co, 2 ) )
1486 return 0;
1487
1488 // Flags check
1489 if( cell->state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
1490 return 0;
1491
1492 // List of 3x3 configurations that we do not allow
1493 static u32 invalid_src[][9] =
1494 {
1495 { 0,1,0,
1496 1,1,1,
1497 0,1,0
1498 },
1499 { 0,0,0,
1500 0,1,1,
1501 0,1,1
1502 },
1503 { 0,0,0,
1504 1,1,0,
1505 1,1,0
1506 },
1507 { 0,1,1,
1508 0,1,1,
1509 0,0,0
1510 },
1511 { 1,1,0,
1512 1,1,0,
1513 0,0,0
1514 },
1515 { 0,1,0,
1516 0,1,1,
1517 0,1,0
1518 },
1519 { 0,1,0,
1520 1,1,0,
1521 0,1,0
1522 }
1523 };
1524
1525 // Statically compile invalid configurations into bitmasks
1526 static u32 invalid[ vg_list_size(invalid_src) ];
1527
1528 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
1529 {
1530 u32 comped = 0x00;
1531
1532 for( int j = 0; j < 3; j ++ )
1533 for( int k = 0; k < 3; k ++ )
1534 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
1535
1536 invalid[i] = comped;
1537 }
1538
1539 // Extract 5x5 grid surrounding tile
1540 u32 blob = 0x1000;
1541 for( int y = co[1]-2; y < co[1]+3; y ++ )
1542 for( int x = co[0]-2; x < co[0]+3; x ++ )
1543 {
1544 struct cell *cell = pcell((v2i){x,y});
1545
1546 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER)) )
1547 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
1548 }
1549
1550 // Run filter over center 3x3 grid to check for invalid configurations
1551 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
1552 for( int i = 0; i < vg_list_size(kernel); i ++ )
1553 {
1554 if( blob & (0x1 << (6+kernel[i])) )
1555 {
1556 u32 window = blob >> kernel[i];
1557
1558 for( int j = 0; j < vg_list_size(invalid); j ++ )
1559 if((window & invalid[j]) == invalid[j])
1560 return 0;
1561 }
1562 }
1563
1564 return 1;
1565 }
1566
1567 static void vg_update(void)
1568 {
1569 // Async events
1570 if( world.st.lvl_to_load )
1571 {
1572 world.st.world_transition = (world.st.lvl_load_time-vg_time) * 4.0f;
1573
1574 if( vg_time > world.st.lvl_load_time )
1575 {
1576 if( console_changelevel( 1, &world.st.lvl_to_load->map_name ) )
1577 {
1578 world.pCmpLevel = world.st.lvl_to_load;
1579 gen_level_text( world.pCmpLevel );
1580 }
1581
1582 world.st.lvl_to_load = NULL;
1583 }
1584 }
1585 else
1586 {
1587 world.st.world_transition = vg_minf( 1.0f, (vg_time-world.st.lvl_load_time) * 4.0f );
1588 }
1589
1590 // Camera
1591 // ========================================================================================================
1592
1593 float r1 = (float)vg_window_y / (float)vg_window_x,
1594 r2 = (float)world.h / (float)world.w,
1595 size;
1596
1597 static float size_current = 2.0f;
1598 static v3f origin_current = { 0.0f, 0.0f, 0.0f };
1599 static v2f drag_offset = { 0.0f, 0.0f };
1600 static v2f view_point = { 0.0f, 0.0f };
1601 v2f result_view;
1602
1603 size = ( r2 < r1? (float)(world.w+5) * 0.5f: ((float)(world.h+5) * 0.5f) / r1 ) + 0.5f;
1604
1605 v2f origin;
1606 v2f vt_target;
1607
1608 origin[0] = floorf( -0.5f * ((float)world.w-4.5f) );
1609 origin[1] = floorf( -0.5f * world.h );
1610
1611 // Create and clamp result view
1612 v2_add( view_point, drag_offset, result_view );
1613 result_view[0] = vg_clampf( result_view[0], -world.st.zoom, world.st.zoom );
1614 result_view[1] = vg_clampf( result_view[1], -world.st.zoom*r1, world.st.zoom*r1 );
1615
1616 v2_add( origin, result_view, vt_target );
1617
1618 // Lerp towards target
1619 size_current = vg_lerpf( size_current, size - world.st.zoom, vg_time_delta * 6.0f );
1620 v2_lerp( origin_current, vt_target, vg_time_delta * 6.0f, origin_current );
1621
1622 m3x3_projection( m_projection, -size_current, size_current, -size_current*r1, size_current*r1 );
1623 m3x3_identity( m_view );
1624 m3x3_translate( m_view, origin_current );
1625 m3x3_mul( m_projection, m_view, vg_pv );
1626 vg_projection_update();
1627
1628 // Mouse input
1629 // ========================================================================================================
1630 v2_copy( vg_mouse_ws, world.tile_pos );
1631
1632 world.tile_x = floorf( world.tile_pos[0] );
1633 world.tile_y = floorf( world.tile_pos[1] );
1634
1635 // Camera dragging
1636 {
1637 static v2f drag_origin; // x/y pixel
1638
1639 if( vg_get_button_down( "tertiary" ) )
1640 v2_copy( vg_mouse, drag_origin );
1641 else if( vg_get_button( "tertiary" ) )
1642 {
1643 // get offset
1644 v2_sub( vg_mouse, drag_origin, drag_offset );
1645 v2_div( drag_offset, (v2f){ vg_window_x, vg_window_y }, drag_offset );
1646 v2_mul( drag_offset, (v2f){ size_current*2.0f, -size_current*r1*2.0f }, drag_offset );
1647 }
1648 else
1649 {
1650 v2_copy( result_view, view_point );
1651 v2_copy( (v2f){0.0f,0.0f}, drag_offset );
1652 }
1653 }
1654
1655 // Zooming
1656 {
1657 v2f mview_local;
1658 v2f mview_new;
1659 v2f mview_cur;
1660 v2f mview_delta;
1661 float rsize;
1662
1663 rsize = size-world.st.zoom;
1664
1665 v2_div( vg_mouse, (v2f){ vg_window_x*0.5f, vg_window_y*0.5f }, mview_local );
1666 v2_add( (v2f){ -rsize, -rsize*r1 }, (v2f){ mview_local[0]*rsize, (2.0f-mview_local[1])*rsize*r1 }, mview_cur );
1667
1668 world.st.zoom = vg_clampf( world.st.zoom + vg_mouse_wheel[1], 0.0f, size - 4.0f );
1669
1670 // Recalculate new position
1671 rsize = size-world.st.zoom;
1672 v2_add( (v2f){ -rsize, -rsize*r1 }, (v2f){ mview_local[0]*rsize, (2.0f-mview_local[1])*rsize*r1 }, mview_new );
1673
1674 // Apply offset
1675 v2_sub( mview_new, mview_cur, mview_delta );
1676 v2_add( mview_delta, view_point, view_point );
1677 }
1678
1679 // Tilemap
1680 // ========================================================================================================
1681 if( !is_simulation_running() && !gui_want_mouse() )
1682 {
1683 v2_copy( vg_mouse_ws, world.drag_to_co );
1684
1685 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
1686 {
1687 world.selected = world.tile_y * world.w + world.tile_x;
1688
1689 static u32 modify_state = 0;
1690 struct cell *cell_ptr = &world.data[world.selected];
1691
1692 if( !(cell_ptr->state & FLAG_EMITTER) )
1693 {
1694 if( vg_get_button_down("primary") )
1695 modify_state = (cell_ptr->state & FLAG_CANAL) ^ FLAG_CANAL;
1696
1697 if( vg_get_button("primary") && ((cell_ptr->state & FLAG_CANAL) != modify_state) )
1698 {
1699 cell_ptr->state &= ~FLAG_CANAL;
1700 cell_ptr->state |= modify_state;
1701
1702 if( cell_ptr->state & FLAG_CANAL )
1703 {
1704 cell_ptr->links[0] = 0;
1705 cell_ptr->links[1] = 0;
1706
1707 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
1708 world.score ++;
1709 }
1710 else
1711 {
1712 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
1713 world.score --;
1714 }
1715
1716 map_reclassify((v2i){ world.tile_x -2, world.tile_y -2 },
1717 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
1718 }
1719
1720 if( vg_get_button_down("secondary") && (cell_ptr->state & FLAG_CANAL) && !(cell_ptr->config == k_cell_type_split) )
1721 {
1722 world.id_drag_from = world.selected;
1723
1724 struct cell_description *desc = &cell_descriptions[ world.data[world.id_drag_from].config ];
1725 v2_add( desc->trigger_pos, (v2f){ world.tile_x, world.tile_y }, world.drag_from_co );
1726 }
1727 }
1728
1729 float local_x = vg_mouse_ws[0] - (float)world.tile_x;
1730
1731 if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
1732 {
1733 u32 link_id = cell_ptr->links[ 0 ]? 0: 1;
1734
1735 // break existing connection off
1736 if( cell_ptr->links[ link_id ] )
1737 {
1738 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1739
1740 if( !current_connection->links[ link_id ^ 0x1 ] )
1741 current_connection->state &= ~FLAG_TARGETED;
1742
1743 current_connection->links[ link_id ] = 0;
1744 cell_ptr->links[ link_id ] = 0;
1745 }
1746
1747 cell_ptr->state &= ~FLAG_IS_TRIGGER;
1748 world.id_drag_from = 0;
1749 }
1750
1751 else if( world.id_drag_from && (cell_ptr->state & (FLAG_CANAL|FLAG_EMITTER)) &&
1752 ((cell_ptr->config == k_cell_type_split) || (cell_ptr->state & FLAG_EMITTER)) )
1753 {
1754 world.drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
1755 world.drag_to_co[1] = (float)world.tile_y + 0.25f;
1756
1757 if( vg_get_button_up("secondary") )
1758 {
1759 struct cell *drag_ptr = &world.data[world.id_drag_from];
1760 u32 link_id = local_x > 0.5f? 1: 0;
1761
1762 // Cleanup existing connections
1763 if( cell_ptr->links[ link_id ] )
1764 {
1765 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id, cell_ptr->links[ link_id ] );
1766
1767 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1768 current_connection->state &= ~FLAG_IS_TRIGGER;
1769 current_connection->links[ link_id ] = 0;
1770 }
1771
1772 for( u32 i = 0; i < 2; i ++ )
1773 {
1774 if( drag_ptr->links[ i ] )
1775 {
1776 vg_warn( "Destroying link %u (%hu)\n", i, drag_ptr->links[ i ] );
1777
1778 struct cell *current_connection = &world.data[ drag_ptr->links[ i ]];
1779 if( current_connection->links[ i ^ 0x1 ] == 0 )
1780 current_connection->state &= ~FLAG_TARGETED;
1781
1782 current_connection->links[ i ] = 0;
1783 drag_ptr->links[ i ] = 0;
1784 }
1785 }
1786
1787 // Create the new connection
1788 vg_success( "Creating connection on link %u (%hu)\n", link_id, world.id_drag_from );
1789
1790 cell_ptr->links[ link_id ] = world.id_drag_from;
1791 drag_ptr->links[ link_id ] = world.selected;
1792
1793 cell_ptr->state |= FLAG_TARGETED;
1794 drag_ptr->state |= FLAG_IS_TRIGGER;
1795 world.id_drag_from = 0;
1796 }
1797 }
1798 }
1799 else
1800 {
1801 world.selected = -1;
1802 }
1803
1804 if( !(vg_get_button("secondary") && world.id_drag_from) )
1805 world.id_drag_from = 0;
1806 }
1807 else
1808 {
1809 world.selected = -1;
1810 world.id_drag_from = 0;
1811 }
1812
1813 // Marble state updates
1814 // ========================================================================================================
1815 if( is_simulation_running() )
1816 {
1817 float old_time = world.sim_internal_time;
1818
1819 if( !world.st.buttons[ k_world_button_pause ].state )
1820 world.sim_internal_time = world.sim_internal_ref + (vg_time-world.sim_delta_ref) * world.sim_delta_speed;
1821 else
1822 world.sim_internal_time = vg_lerpf( world.sim_internal_time, world.sim_internal_ref + world.pause_offset_target, vg_time_delta*15.0f );
1823 world.sim_internal_delta = world.sim_internal_time-old_time;
1824
1825 world.sim_target = (int)floorf(world.sim_internal_time);
1826
1827 int success_this_frame = 0;
1828 int failure_this_frame = 0;
1829
1830 while( world.sim_frame < world.sim_target )
1831 {
1832 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
1833
1834 // Update splitter deltas
1835 for( int i = 0; i < world.h*world.w; i ++ )
1836 {
1837 struct cell *cell = &world.data[i];
1838 if( cell->config == k_cell_type_split )
1839 {
1840 cell->state &= ~FLAG_FLIP_ROTATING;
1841 }
1842 if( cell->state & (FLAG_IS_TRIGGER|FLAG_EMITTER) )
1843 cell->state &= ~FLAG_TRIGGERED;
1844 }
1845
1846 int alive_count = 0;
1847
1848 // Update fish positions
1849 for( int i = 0; i < world.num_fishes; i ++ )
1850 {
1851 struct fish *fish = &world.fishes[i];
1852
1853 if( fish->state == k_fish_state_soon_dead )
1854 fish->state = k_fish_state_dead;
1855
1856 if( fish->state == k_fish_state_soon_alive )
1857 fish->state = k_fish_state_alive;
1858
1859 if( fish->state < k_fish_state_alive )
1860 continue;
1861
1862 struct cell *cell_current = pcell( fish->pos );
1863
1864 if( fish->state == k_fish_state_alive )
1865 {
1866 // Apply to output
1867 if( cell_current->state & FLAG_OUTPUT )
1868 {
1869 for( int j = 0; j < arrlen( world.io ); j ++ )
1870 {
1871 struct cell_terminal *term = &world.io[j];
1872
1873 if( v2i_eq( term->pos, fish->pos ) )
1874 {
1875 struct terminal_run *run = &term->runs[ world.sim_run ];
1876 if( run->recv_count < vg_list_size( run->recieved ) )
1877 {
1878 if( fish->payload == run->conditions[ run->recv_count ] )
1879 success_this_frame = 1;
1880 else
1881 failure_this_frame = 1;
1882
1883 run->recieved[ run->recv_count ++ ] = fish->payload;
1884 }
1885 else
1886 failure_this_frame = 1;
1887
1888 break;
1889 }
1890 }
1891
1892 fish->state = k_fish_state_dead;
1893 fish->death_time = -1000.0f;
1894 continue;
1895 }
1896
1897
1898 if( cell_current->config == k_cell_type_merge )
1899 {
1900 // Can only move up
1901 fish->dir[0] = 0;
1902 fish->dir[1] = -1;
1903 fish->flow_reversed = 0;
1904 }
1905 else
1906 {
1907 if( cell_current->config == k_cell_type_split )
1908 {
1909 // Flip flop L/R
1910 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
1911 fish->dir[1] = 0;
1912
1913 if( !(cell_current->state & FLAG_TARGETED) )
1914 cell_current->state ^= FLAG_FLIP_FLOP;
1915 }
1916 else
1917 {
1918 // Apply cell out-flow
1919 struct cell_description *desc = &cell_descriptions[ cell_current->config ];
1920
1921 v2i_copy( fish->flow_reversed? desc->start: desc->end, fish->dir );
1922 }
1923
1924 v2i pos_next;
1925 v2i_add( fish->pos, fish->dir, pos_next );
1926
1927 struct cell *cell_next = pcell( pos_next );
1928
1929 if( cell_next->state & (FLAG_CANAL|FLAG_OUTPUT) )
1930 {
1931 struct cell_description *desc = &cell_descriptions[ cell_next->config ];
1932
1933 if( cell_next->config == k_cell_type_merge )
1934 {
1935 if( fish->dir[0] == 0 )
1936 {
1937 fish->state = k_fish_state_dead;
1938 fish->death_time = world.sim_internal_time;
1939 }
1940 else
1941 fish->flow_reversed = 0;
1942 }
1943 else
1944 {
1945 if( cell_next->config == k_cell_type_split )
1946 {
1947 if( fish->dir[0] == 0 )
1948 {
1949 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
1950 cell_next->state |= FLAG_FLIP_ROTATING;
1951
1952 fish->flow_reversed = 0;
1953 }
1954 else
1955 {
1956 fish->state = k_fish_state_dead;
1957 fish->death_time = world.sim_internal_time;
1958 }
1959 }
1960 else
1961 fish->flow_reversed = ( fish->dir[0] != -desc->start[0] ||
1962 fish->dir[1] != -desc->start[1] )? 1: 0;
1963 }
1964 }
1965 else
1966 {
1967 if( world_check_pos_ok( fish->pos, 2 ) )
1968 fish->state = k_fish_state_bg;
1969 else
1970 {
1971 fish->state = k_fish_state_dead;
1972 fish->death_time = world.sim_internal_time;
1973 }
1974 }
1975 }
1976
1977 //v2i_add( fish->pos, fish->dir, fish->pos );
1978 }
1979 else if( fish->state == k_fish_state_bg )
1980 {
1981 v2i_add( fish->pos, fish->dir, fish->pos );
1982
1983 if( !world_check_pos_ok( fish->pos, 2 ) )
1984 {
1985 fish->state = k_fish_state_dead;
1986 fish->death_time = -1000.0f;
1987 }
1988 else
1989 {
1990 struct cell *cell_entry = pcell( fish->pos );
1991
1992 if( cell_entry->state & FLAG_CANAL )
1993 {
1994 if( cell_entry->config == k_cell_type_con_r || cell_entry->config == k_cell_type_con_u
1995 || cell_entry->config == k_cell_type_con_l || cell_entry->config == k_cell_type_con_d )
1996 {
1997 #ifdef VG_STEAM
1998 sw_set_achievement( "CAN_DO_THAT" );
1999 #endif
2000
2001 fish->state = k_fish_state_soon_alive;
2002
2003 fish->dir[0] = 0;
2004 fish->dir[1] = 0;
2005 fish->flow_reversed = 1;
2006
2007 switch( cell_entry->config )
2008 {
2009 case k_cell_type_con_r: fish->dir[0] = 1; break;
2010 case k_cell_type_con_l: fish->dir[0] = -1; break;
2011 case k_cell_type_con_u: fish->dir[1] = 1; break;
2012 case k_cell_type_con_d: fish->dir[1] = -1; break;
2013 }
2014 }
2015 }
2016 }
2017 }
2018 else { vg_error( "fish behaviour unimplemented for behaviour type (%d)\n" ); }
2019
2020 if( fish->state >= k_fish_state_alive )
2021 alive_count ++;
2022 }
2023
2024 // Second pass (triggers)
2025 for( int i = 0; i < world.num_fishes; i ++ )
2026 {
2027 struct fish *fish = &world.fishes[i];
2028
2029 // Apply movement
2030 if( fish->state == k_fish_state_alive )
2031 v2i_add( fish->pos, fish->dir, fish->pos );
2032
2033 if( fish->state == k_fish_state_alive || fish->state == k_fish_state_soon_alive )
2034 {
2035 struct cell *cell_current = pcell( fish->pos );
2036
2037 if( cell_current->state & FLAG_IS_TRIGGER )
2038 {
2039 int trigger_id = cell_current->links[0]?0:1;
2040
2041 struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
2042
2043 // Spawn new marble
2044 if( (target_peice->state & FLAG_EMITTER) && !(target_peice->state & FLAG_TRIGGERED))
2045 {
2046 struct fish *fish = &world.fishes[ world.num_fishes ];
2047 lcell( cell_current->links[trigger_id], fish->pos );
2048
2049 fish->state = k_fish_state_soon_alive;
2050 fish->payload = target_peice->cc;
2051
2052 if( target_peice->config != k_cell_type_stub )
2053 {
2054 struct cell_description *desc = &cell_descriptions[ target_peice->config ];
2055 v2i_copy( desc->start, fish->dir );
2056 fish->flow_reversed = 1;
2057
2058 world.num_fishes ++;
2059 alive_count ++;
2060 }
2061 }
2062 else
2063 {
2064 if( trigger_id )
2065 target_peice->state |= FLAG_FLIP_FLOP;
2066 else
2067 target_peice->state &= ~FLAG_FLIP_FLOP;
2068 }
2069
2070 cell_current->state |= FLAG_TRIGGERED;
2071 }
2072 }
2073 }
2074
2075 // Third pass (collisions)
2076 struct fish *fi, *fj;
2077
2078 for( int i = 0; i < world.num_fishes; i ++ )
2079 {
2080 fi = &world.fishes[i];
2081
2082 if( fi->state == k_fish_state_alive )
2083 {
2084 int continue_again = 0;
2085
2086 for( int j = i+1; j < world.num_fishes; j ++ )
2087 {
2088 fj = &world.fishes[j];
2089
2090 if( fj->state == k_fish_state_alive )
2091 {
2092 v2i fi_prev;
2093 v2i fj_prev;
2094
2095 v2i_sub( fi->pos, fi->dir, fi_prev );
2096 v2i_sub( fj->pos, fj->dir, fj_prev );
2097
2098 int
2099 collide_next_frame = (
2100 (fi->pos[0] == fj->pos[0]) &&
2101 (fi->pos[1] == fj->pos[1]))? 1: 0,
2102 collide_this_frame = (
2103 (fi_prev[0] == fj->pos[0]) &&
2104 (fi_prev[1] == fj->pos[1]) &&
2105 (fj_prev[0] == fi->pos[0]) &&
2106 (fj_prev[1] == fi->pos[1])
2107 )? 1: 0;
2108
2109 if( collide_next_frame || collide_this_frame )
2110 {
2111 #ifdef VG_STEAM
2112 sw_set_achievement( "BANG" );
2113 #endif
2114
2115 // Shatter death (+0.5s)
2116 float death_time = world.sim_internal_time + ( collide_this_frame? 0.0f: 0.5f );
2117
2118 fi->state = k_fish_state_soon_dead;
2119 fj->state = k_fish_state_soon_dead;
2120 fi->death_time = death_time;
2121 fj->death_time = death_time;
2122
2123 continue_again = 1;
2124 break;
2125 }
2126 }
2127 }
2128 if( continue_again )
2129 continue;
2130 }
2131 }
2132
2133 // Spawn fishes
2134 for( int i = 0; i < arrlen( world.io ); i ++ )
2135 {
2136 struct cell_terminal *term = &world.io[ i ];
2137 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2138
2139 if( is_input )
2140 {
2141 if( world.sim_frame < term->runs[ world.sim_run ].condition_count )
2142 {
2143 char emit = term->runs[ world.sim_run ].conditions[ world.sim_frame ];
2144 if( emit == ' ' )
2145 continue;
2146
2147 struct fish *fish = &world.fishes[ world.num_fishes ];
2148 v2i_copy( term->pos, fish->pos );
2149
2150 fish->state = k_fish_state_alive;
2151 fish->payload = emit;
2152
2153 struct cell *cell_ptr = pcell( fish->pos );
2154
2155 if( cell_ptr->config != k_cell_type_stub )
2156 {
2157 struct cell_description *desc = &cell_descriptions[ cell_ptr->config ];
2158
2159 v2i_copy( desc->start, fish->dir );
2160 fish->flow_reversed = 1;
2161
2162 world.num_fishes ++;
2163 alive_count ++;
2164 }
2165 }
2166 }
2167 }
2168
2169 if( alive_count == 0 )
2170 {
2171 world.completed = 1;
2172
2173 for( int i = 0; i < arrlen( world.io ); i ++ )
2174 {
2175 struct cell_terminal *term = &world.io[ i ];
2176
2177 if( pcell(term->pos)->state & FLAG_OUTPUT )
2178 {
2179 struct terminal_run *run = &term->runs[ world.sim_run ];
2180
2181 if( run->recv_count == run->condition_count )
2182 {
2183 for( int j = 0; j < run->condition_count; j ++ )
2184 {
2185 if( run->recieved[j] != run->conditions[j] )
2186 {
2187 world.completed = 0;
2188 break;
2189 }
2190 }
2191 }
2192 else
2193 {
2194 world.completed = 0;
2195 break;
2196 }
2197 }
2198 }
2199
2200 if( world.completed )
2201 {
2202 if( world.sim_run < world.max_runs-1 )
2203 {
2204 vg_success( "Run passed, starting next\n" );
2205 world.sim_run ++;
2206 world.sim_frame = 0;
2207 world.sim_target = 0;
2208 world.num_fishes = 0;
2209
2210 // Reset timing reference points
2211 world.sim_delta_ref = vg_time;
2212 world.sim_internal_ref = 0.0f;
2213
2214 if( world.st.buttons[ k_world_button_pause ].state )
2215 world.pause_offset_target = 0.5f;
2216 else
2217 world.pause_offset_target = 0.0f;
2218
2219 world.sim_internal_time = 0.0f;
2220
2221 for( int i = 0; i < world.w*world.h; i ++ )
2222 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
2223
2224 continue;
2225 }
2226 else
2227 {
2228 vg_success( "Level passed!\n" );
2229
2230 u32 score = 0;
2231 for( int i = 0; i < world.w*world.h; i ++ )
2232 if( world.data[ i ].state & FLAG_CANAL )
2233 score ++;
2234
2235 world.score = score;
2236 world.time = world.sim_frame;
2237
2238 // Copy into career data
2239 if( world.pCmpLevel )
2240 {
2241 career_pass_level( world.pCmpLevel, world.score, 1 );
2242 }
2243
2244 sfx_set_play( &audio_tones, &audio_system_balls_extra, 9 );
2245 failure_this_frame = 0;
2246 success_this_frame = 0;
2247 }
2248 }
2249 else
2250 {
2251 #ifdef VG_STEAM
2252 if( world.sim_run > 0 )
2253 sw_set_achievement( "GOOD_ENOUGH" );
2254 #endif
2255
2256 vg_error( "Level failed :(\n" );
2257 }
2258
2259 simulation_stop();
2260 break;
2261 }
2262
2263 world.sim_frame ++;
2264 }
2265
2266 // Sounds
2267 if( failure_this_frame )
2268 {
2269 sfx_set_play( &audio_tones, &audio_system_balls_extra, 0 );
2270 }
2271 else if( success_this_frame )
2272 {
2273 static int succes_counter = 0;
2274
2275 sfx_set_play( &audio_tones, &audio_system_balls_extra, 1+(succes_counter++) );
2276
2277 if( succes_counter == 7 )
2278 succes_counter = 0;
2279 }
2280
2281 // Position update
2282 // =====================================================================================================
2283
2284 world.frame_lerp = world.sim_internal_time - floorf( world.sim_internal_time );
2285
2286 for( int i = 0; i < world.num_fishes; i ++ )
2287 {
2288 struct fish *fish = &world.fishes[i];
2289
2290 if( fish->state == k_fish_state_dead )
2291 continue;
2292
2293 if( fish->state == k_fish_state_soon_dead && (world.sim_internal_time > fish->death_time) )
2294 continue; // Todo: particle thing?
2295
2296 struct cell *cell = pcell(fish->pos);
2297 struct cell_description *desc = &cell_descriptions[ cell->config ];
2298
2299 v2f const *curve;
2300
2301 float t = world.frame_lerp;
2302 if( fish->flow_reversed && !desc->is_linear )
2303 t = 1.0f-t;
2304
2305 v2_copy( fish->physics_co, fish->physics_v );
2306
2307 switch( cell->config )
2308 {
2309 case k_cell_type_merge:
2310 if( fish->dir[0] == 1 )
2311 curve = curve_12;
2312 else
2313 curve = curve_9;
2314 break;
2315 case k_cell_type_con_r: curve = curve_1; break;
2316 case k_cell_type_con_l: curve = curve_4; break;
2317 case k_cell_type_con_u: curve = curve_2; break;
2318 case k_cell_type_con_d: curve = curve_8; break;
2319 case 3: curve = curve_3; break;
2320 case 6: curve = curve_6; break;
2321 case 9: curve = curve_9; break;
2322 case 12: curve = curve_12; break;
2323 case 7:
2324 if( t > curve_7_linear_section )
2325 {
2326 t -= curve_7_linear_section;
2327 t *= (1.0f/(1.0f-curve_7_linear_section));
2328
2329 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
2330 }
2331 else curve = NULL;
2332 break;
2333 default: curve = NULL; break;
2334 }
2335
2336 if( curve )
2337 {
2338 float t2 = t * t;
2339 float t3 = t * t * t;
2340
2341 float cA = 3.0f*t2 - 3.0f*t3;
2342 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
2343 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
2344
2345 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
2346 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
2347 fish->physics_co[0] += (float)fish->pos[0];
2348 fish->physics_co[1] += (float)fish->pos[1];
2349 }
2350 else
2351 {
2352 v2f origin;
2353 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
2354 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
2355
2356 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
2357 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
2358 }
2359
2360 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
2361 v2_divs( fish->physics_v, world.sim_internal_delta, fish->physics_v );
2362 }
2363 }
2364 }
2365
2366 static void render_tile( v2i pos, struct cell *ptr, v4f const regular_colour, v4f const selected_colour )
2367 {
2368 int selected = world.selected == pos[1]*world.w + pos[0];
2369
2370 int tile_offsets[][2] =
2371 {
2372 {2, 0}, {0, 3}, {0, 2}, {2, 2},
2373 {1, 0}, {2, 3}, {3, 2}, {1, 3},
2374 {3, 1}, {0, 1}, {1, 2}, {2, 1},
2375 {1, 1}, {3, 3}, {2, 1}, {2, 1}
2376 };
2377
2378 int uv[2];
2379
2380 uv[0] = tile_offsets[ ptr->config ][0];
2381 uv[1] = tile_offsets[ ptr->config ][1];
2382
2383 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
2384 (float)pos[0],
2385 (float)pos[1],
2386 uv[0],
2387 uv[1]
2388 );
2389
2390 if( selected )
2391 {
2392 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
2393 draw_mesh( 0, 2 );
2394 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
2395 }
2396 else
2397 draw_mesh( 0, 2 );
2398 }
2399
2400 // Renders specific chunk of tiles
2401 static void render_tile_block( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
2402 {
2403 v2i full_start = { 0,0 };
2404 v2i full_end = { world.w, world.h };
2405
2406 if( !start || !end )
2407 {
2408 start = full_start;
2409 end = full_end;
2410 }
2411
2412 for( int y = start[1]; y < end[1]; y ++ )
2413 {
2414 for( int x = start[0]; x < end[0]; x ++ )
2415 {
2416 v2i pos = { x, y };
2417 struct cell *cell = pcell( pos );
2418
2419 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
2420 render_tile( pos, cell, regular_colour, selected_colour );
2421 }
2422 }
2423 }
2424
2425 // Renders all tiles in the command list
2426 static void render_tiles( v4f const regular_colour, v4f const selected_colour )
2427 {
2428 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
2429
2430 struct render_list
2431 {
2432 struct render_cmd *arr;
2433 u32 count;
2434 }
2435 render_lists[] = {
2436 { world.cmd_buf_tiles, world.tile_count },
2437 { world.cmd_buf_specials, world.tile_special_count }
2438 };
2439
2440 for( int i = 0; i < vg_list_size( render_lists ); i ++ )
2441 {
2442 struct render_list *list = &render_lists[i];
2443 for( int j = 0; j < list->count; j ++ )
2444 {
2445 struct render_cmd *cmd = &list->arr[j];
2446 struct cell *cell = cmd->ptr;
2447
2448 render_tile( cmd->pos, cell, regular_colour, selected_colour );
2449 }
2450 }
2451 }
2452
2453 static int world_button_exec( struct world_button *btn, v2f texture, v3f colour, enum world_button_status *status )
2454 {
2455 static v2i click_grab = { -9999, -9999 };
2456
2457 // Reset click_grab
2458 if( !btn )
2459 {
2460 click_grab[0] = -9999;
2461 click_grab[1] = -9999;
2462 return 0;
2463 }
2464
2465 v2i click_tile = { world.tile_x, world.tile_y };
2466
2467 int triggered = 0;
2468 int is_hovering = v2i_eq( click_tile, btn->position );
2469
2470 // Set up light targets before logic runs
2471 if( btn->state )
2472 btn->light_target = is_hovering? 0.9f: 0.8f;
2473 else
2474 btn->light_target = is_hovering? 0.2f: 0.0f;
2475
2476 if( vg_get_button( "primary" ) && is_hovering )
2477 btn->light_target = 1.0f;
2478
2479 // Process click action
2480 if( is_hovering )
2481 {
2482 if( vg_get_button_down( "primary" ) && is_hovering )
2483 v2i_copy( click_tile, click_grab );
2484 else if( v2i_eq( click_grab, click_tile ) && vg_get_button_up( "primary" ) )
2485 {
2486 // Click event
2487 *status = btn->state? k_world_button_on_disable: k_world_button_on_enable;
2488
2489 if( btn->mode == k_world_button_mode_toggle )
2490 btn->state ^= 0x1;
2491
2492 sfx_set_play( &audio_clicks, &audio_system_ui, btn->state? 1:0 );
2493 triggered = 1;
2494 }
2495 }
2496
2497 // Drawing stage
2498 v4f final_colour;
2499
2500 btn->light = vg_lerpf( btn->light, btn->light_target + btn->extra_light, vg_time_delta*26.0f );
2501
2502 v3_copy( colour, final_colour );
2503 final_colour[3] = btn->light;
2504
2505 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2506 btn->position[0],
2507 btn->position[1],
2508 texture[0],
2509 texture[1]
2510 );
2511 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
2512 draw_mesh( 0, 2 );
2513
2514 return triggered;
2515 }
2516
2517 static void level_selection_buttons(void)
2518 {
2519 v3f tutorial_colour = { 0.204f, 0.345f, 0.553f };
2520 v3f locked_colour = { 0.2f, 0.2f, 0.2f };
2521
2522 struct cmp_level *switch_level_to = NULL;
2523
2524 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
2525 {
2526 struct career_level_pack *grid = &career_packs[i];
2527
2528 for( int j = 0; j < grid->count; j ++ )
2529 {
2530 struct cmp_level *lvl = &grid->pack[ j ];
2531
2532 if( world.pCmpLevel == lvl )
2533 lvl->btn.extra_light = 0.35f + fabsf(sinf( vg_time * 2.0f )) * 0.05f;
2534 else lvl->btn.extra_light = 0.2f;
2535
2536 if( lvl->completed_score )
2537 lvl->btn.extra_light += 0.8f;
2538
2539 enum world_button_status status;
2540 if( world_button_exec(
2541 &lvl->btn,
2542 (v2f){0.0f,0.0f},
2543 lvl->unlocked? (lvl->is_tutorial? tutorial_colour: grid->primary_colour): locked_colour,
2544 &status
2545 ))
2546 {
2547 if( status == k_world_button_on_enable && lvl->unlocked )
2548 switch_level_to = lvl;
2549 }
2550 }
2551 }
2552
2553 if( switch_level_to )
2554 {
2555 world.st.lvl_to_load = switch_level_to;
2556 world.st.lvl_load_time = vg_time + 0.25f;
2557 world.st.world_transition = 1.0f;
2558
2559 /*
2560 if( console_changelevel( 1, &switch_level_to->map_name ) )
2561 {
2562 world.pCmpLevel = switch_level_to;
2563 gen_level_text( world.pCmpLevel );
2564 }
2565 */
2566 }
2567 }
2568
2569 static void render_sprite( enum sprites_auto_combine_index id, v3f pos )
2570 {
2571 struct vg_sprite *sp = &sprites_auto_combine[ id ];
2572
2573 glUniform4fv( SHADER_UNIFORM( shader_sprite, "uUv" ), 1, sp->uv_xywh );
2574 glUniform3f( SHADER_UNIFORM( shader_sprite, "uPos" ), pos[0], pos[1], pos[2] * world.st.world_transition );
2575
2576 draw_mesh( 0, 2 );
2577 }
2578
2579 void vg_render(void)
2580 {
2581 glViewport( 0,0, vg_window_x, vg_window_y );
2582
2583 glDisable( GL_DEPTH_TEST );
2584 glClearColor( 0.369768f, 0.3654f, 0.42f, 1.0f );
2585 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
2586
2587 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
2588 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
2589
2590 int const circle_base = 6;
2591 int const filled_start = circle_base+0;
2592 int const filled_count = circle_base+32;
2593 int const empty_start = circle_base+32;
2594 int const empty_count = circle_base+32*2;
2595
2596 struct world_theme *theme = &world_themes[ world_theme_id ];
2597
2598 if( !world.initialzed )
2599 return;
2600
2601 // Extract render commands
2602 world.tile_count = 0;
2603 world.tile_special_count = 0;
2604
2605 for( int y = 1; y < world.h-1; y ++ )
2606 {
2607 for( int x = 1; x < world.w-1; x ++ )
2608 {
2609 struct cell *cell = pcell((v2i){x,y});
2610
2611 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
2612 {
2613 struct render_cmd *cmd;
2614
2615 if( cell->config == k_cell_type_split || (cell->state & FLAG_EMITTER ) )
2616 cmd = &world.cmd_buf_tiles[ world.max_commands - (++ world.tile_special_count) ];
2617 else
2618 cmd = &world.cmd_buf_tiles[ world.tile_count ++ ];
2619
2620 cmd->pos[0] = x;
2621 cmd->pos[1] = y;
2622 cmd->ptr = cell;
2623 }
2624 }
2625 }
2626
2627 world.cmd_buf_specials = &world.cmd_buf_tiles[ world.max_commands - world.tile_special_count ];
2628
2629 // BACKGROUND
2630 // ========================================================================================================
2631 use_mesh( &world.shapes );
2632
2633 SHADER_USE( shader_background );
2634 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2635
2636 glActiveTexture( GL_TEXTURE0 );
2637 glBindTexture( GL_TEXTURE_2D, world.background_data );
2638 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
2639
2640 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
2641 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.02f );
2642
2643 glActiveTexture( GL_TEXTURE1 );
2644 glBindTexture( GL_TEXTURE_2D, world.random_samples );
2645 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
2646 glUniform1f( SHADER_UNIFORM( shader_background, "uVisibility" ), 1.0f ); //world.st.world_transition );
2647
2648 draw_mesh( 0, 2 );
2649
2650 // TILESET BACKGROUND LAYER
2651 // ========================================================================================================
2652 use_mesh( &world.shapes );
2653 SHADER_USE( shader_tile_main );
2654
2655 m2x2f subtransform;
2656 m2x2_identity( subtransform );
2657 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2658 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2659 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
2660 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
2661 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uVisibility" ), world.st.world_transition * 2.0f );
2662
2663 glEnable(GL_BLEND);
2664 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2665 glBlendEquation(GL_FUNC_ADD);
2666
2667 // Bind textures
2668 vg_tex2d_bind( &tex_tile_data, 0 );
2669 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2670
2671 vg_tex2d_bind( theme->tex_tiles, 1 );
2672 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
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 );
2940 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.20f );
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 {
2988 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, j? wire_right_colour: wire_left_colour );
2989 }
2990
2991 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), other_cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
2992 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), other_cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
2993 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), startpoint[0], startpoint[1], 0.18f );
2994 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), endpoint[0], endpoint[1], 0.18f );
2995 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2996 }
2997 }
2998 }
2999
3000 // WIRE ENDPOINTS
3001 // ========================================================================================================
3002
3003 SHADER_USE( shader_tile_colour );
3004 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3005 use_mesh( &world.shapes );
3006
3007 for( int i = 0; i < world.tile_special_count; i ++ )
3008 {
3009 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3010 struct cell *cell = cmd->ptr;
3011
3012 if( cell->state & FLAG_TARGETED )
3013 {
3014 for( int j = 0; j < 2; j ++ )
3015 {
3016 if( !cell->links[j] )
3017 continue;
3018
3019 struct cell *other_cell = &world.data[ cell->links[ j ]];
3020
3021 struct cell_description *desc = &cell_descriptions[ other_cell->config ];
3022
3023 int x2 = cell->links[j] % world.w;
3024 int y2 = (cell->links[j] - x2) / world.w;
3025
3026 v2f pts[2];
3027
3028 pts[0][0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f);
3029 pts[0][1] = (float)cmd->pos[1] + 0.25f;
3030
3031 pts[1][0] = x2;
3032 pts[1][1] = y2;
3033
3034 v2_add( desc->trigger_pos, pts[1], pts[1] );
3035
3036 if( other_cell->state & FLAG_EMITTER )
3037 {
3038 v4f wire_colour;
3039 colour_code_v3( other_cell->cc, wire_colour );
3040
3041 v3_muls( wire_colour, 0.8f, wire_colour );
3042 wire_colour[3] = 1.0f;
3043
3044 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour );
3045 }
3046 else
3047 {
3048 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, j? wire_right_colour: wire_left_colour );
3049 }
3050
3051 for( int i = 0; i < 2; i ++ )
3052 {
3053 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3054 pts[i][0],
3055 pts[i][1],
3056 0.08f
3057 );
3058 draw_mesh( filled_start, filled_count );
3059 }
3060 }
3061 }
3062 }
3063
3064 // SUB SPLITTER DIRECTION
3065 // ========================================================================================================
3066
3067 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
3068
3069 for( int i = 0; i < world.tile_special_count; i ++ )
3070 {
3071 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3072 struct cell *cell = cmd->ptr;
3073
3074 if( cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
3075 {
3076 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), cmd->pos[0], cmd->pos[1], 1.0f );
3077 draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
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 ui_global_ctx.cursor[0] += 45;
3298 ui_global_ctx.cursor[1] += 6;
3299 gui_text( (const char *[]){ "Normal", "Extra1", "Extra2" }[ colour_set_id ], 2 );
3300 }
3301 gui_end_right();
3302
3303 ui_global_ctx.cursor[2] = 25;
3304 if( gui_button( 1 ) == k_button_click )
3305 {
3306 if( colour_set_id < vg_list_size( colour_sets )-1 )
3307 colour_set_id ++;
3308 }
3309 gui_text( ">", 2 );
3310 gui_end_down();
3311 }
3312 gui_end_down();
3313 }
3314 gui_end();
3315 }
3316 }
3317
3318 #if STEAM_LEADERBOARDS
3319 void leaderboard_dispatch_score(void)
3320 {
3321
3322 sw_upload_leaderboard_score(
3323 ui_data.upload_request.level->steam_leaderboard,
3324 k_ELeaderboardUploadScoreMethodKeepBest,
3325 ui_data.upload_request.score,
3326 NULL,
3327 0
3328 );
3329
3330 ui_data.upload_request.is_waiting = 0;
3331
3332 vg_success( "Dispatched leaderboard score\n" );
3333 }
3334
3335 void leaderboard_found( LeaderboardFindResult_t *pCallback )
3336 {
3337 if( !pCallback->m_bLeaderboardFound )
3338 {
3339 vg_error( "Leaderboard could not be found\n" );
3340 ui_data.steam_leaderboard = 0;
3341 }
3342 else
3343 {
3344 const char *recieved_name = sw_get_leaderboard_name( pCallback->m_hSteamLeaderboard );
3345
3346 // Update UI state and request entries if this callback found the current UI level
3347 if( ui_data.level_selected )
3348 {
3349 if( !strcmp( recieved_name, ui_data.level_selected->map_name ) )
3350 {
3351 sw_download_leaderboard_entries( pCallback->m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 0, 8 );
3352 ui_data.level_selected->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3353 }
3354 }
3355
3356 // Dispatch the waiting request if there was one
3357 if( ui_data.upload_request.is_waiting )
3358 {
3359 if( !strcmp( recieved_name, ui_data.upload_request.level->map_name ) )
3360 {
3361 ui_data.upload_request.level->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3362 leaderboard_dispatch_score();
3363 }
3364 }
3365 }
3366 }
3367
3368 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
3369 {
3370 // Update UI if this leaderboard matches what we currently have in view
3371 if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
3372 {
3373 vg_info( "Recieved %d entries\n", pCallback->m_cEntryCount );
3374 ui_data.leaderboard_count = VG_MIN( pCallback->m_cEntryCount, 8 );
3375
3376 u64_steamid local_player = sw_get_steamid();
3377
3378 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
3379 {
3380 LeaderboardEntry_t entry;
3381 sw_get_downloaded_entry( pCallback->m_hSteamLeaderboardEntries, i, &entry, NULL, 0 );
3382
3383 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
3384
3385 player->id = entry.m_steamIDUser.m_unAll64Bits;
3386 strncpy( player->player_name, sw_get_friend_persona_name( player->id ), vg_list_size( player->player_name )-1 );
3387 player->score = entry.m_nScore;
3388
3389 snprintf( player->score_text, vg_list_size(player->score_text), "%d", player->score );
3390 player->texture = sw_get_player_image( player->id );
3391
3392 if( player->texture == 0 )
3393 player->texture = tex_unkown.name;
3394
3395 player->is_local_player = local_player == player->id? 1: 0;
3396 }
3397
3398 if( ui_data.leaderboard_count )
3399 ui_data.leaderboard_show = 1;
3400 else
3401 ui_data.leaderboard_show = 0;
3402 }
3403 else vg_warn( "Downloaded leaderboard does not match requested!\n" );
3404 }
3405
3406 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
3407 {
3408 if( ui_data.upload_request.is_waiting )
3409 vg_warn( "You are uploading leaderboard entries too quickly!\n" );
3410
3411 ui_data.upload_request.level = cmp_level;
3412 ui_data.upload_request.score = score;
3413 ui_data.upload_request.is_waiting = 1;
3414
3415 // If leaderboard ID has been downloaded already then just immediately dispatch this
3416 if( cmp_level->steam_leaderboard )
3417 leaderboard_dispatch_score();
3418 else
3419 sw_find_leaderboard( cmp_level->map_name );
3420 }
3421 #endif
3422
3423 // CONSOLE COMMANDS
3424 // ===========================================================================================================
3425
3426 static int console_credits( int argc, char const *argv[] )
3427 {
3428 vg_info( "Aknowledgements:\n" );
3429 vg_info( " GLFW zlib/libpng glfw.org\n" );
3430 vg_info( " miniaudio MIT0 miniaud.io\n" );
3431 vg_info( " QOI MIT phoboslab.org\n" );
3432 vg_info( " STB library MIT nothings.org\n" );
3433 vg_info( " Weiholmir JustFredrik\n" );
3434 vg_info( " Ubuntu Regular ubuntu.com\n" );
3435 return 0;
3436 }
3437
3438 static int console_save_map( int argc, char const *argv[] )
3439 {
3440 if( !world.initialzed )
3441 {
3442 vg_error( "Tried to save uninitialized map!\n" );
3443 return 0;
3444 }
3445
3446 char map_path[ 256 ];
3447
3448 strcpy( map_path, "sav/" );
3449 strcat( map_path, world.map_name );
3450 strcat( map_path, ".map" );
3451
3452 FILE *test_writer = fopen( map_path, "wb" );
3453 if( test_writer )
3454 {
3455 vg_info( "Saving map to '%s'\n", map_path );
3456 map_serialize( test_writer );
3457
3458 fclose( test_writer );
3459 return 1;
3460 }
3461 else
3462 {
3463 vg_error( "Unable to open stream for writing\n" );
3464 return 0;
3465 }
3466 }
3467
3468 static int console_load_map( int argc, char const *argv[] )
3469 {
3470 char map_path[ 256 ];
3471
3472 if( argc >= 1 )
3473 {
3474 // try from saves
3475 strcpy( map_path, "sav/" );
3476 strcat( map_path, argv[0] );
3477 strcat( map_path, ".map" );
3478
3479 char *text_source = vg_textasset_read( map_path );
3480
3481 if( !text_source )
3482 {
3483 strcpy( map_path, "maps/" );
3484 strcat( map_path, argv[0] );
3485 strcat( map_path, ".map" );
3486
3487 text_source = vg_textasset_read( map_path );
3488 }
3489
3490 if( text_source )
3491 {
3492 vg_info( "Loading map: '%s'\n", map_path );
3493 world.pCmpLevel = NULL;
3494
3495 if( !map_load( text_source, argv[0] ) )
3496 {
3497 free( text_source );
3498 return 0;
3499 }
3500
3501 free( text_source );
3502 return 1;
3503 }
3504 else
3505 {
3506 vg_error( "Missing maps '%s'\n", argv[0] );
3507 return 0;
3508 }
3509 }
3510 else
3511 {
3512 vg_error( "Missing argument <map_path>\n" );
3513 return 0;
3514 }
3515 }
3516
3517 static int console_changelevel( int argc, char const *argv[] )
3518 {
3519 if( argc >= 1 )
3520 {
3521 // Save current level
3522 console_save_map( 0, NULL );
3523
3524 if( console_load_map( argc, argv ) )
3525 {
3526 world.st.zoom = 0.0f;
3527 simulation_stop();
3528 return 1;
3529 }
3530 }
3531 else
3532 {
3533 vg_error( "Missing argument <map_path>\n" );
3534 }
3535
3536 return 0;
3537 }
3538
3539 // START UP / SHUTDOWN
3540 // ===========================================================================================================
3541
3542 #define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
3543 X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY
3544
3545 void vg_start(void)
3546 {
3547 // Steamworks callbacks
3548 #ifdef STEAM_LEADERBOARDS
3549 sw_leaderboard_found = &leaderboard_found;
3550 sw_leaderboard_downloaded = &leaderboard_downloaded;
3551 #endif
3552
3553 vg_function_push( (struct vg_cmd){
3554 .name = "_map_write",
3555 .function = console_save_map
3556 });
3557
3558 vg_function_push( (struct vg_cmd){
3559 .name = "_map_load",
3560 .function = console_load_map
3561 });
3562
3563 vg_function_push( (struct vg_cmd){
3564 .name = "map",
3565 .function = console_changelevel
3566 });
3567
3568 vg_function_push( (struct vg_cmd){
3569 .name = "credits",
3570 .function = console_credits
3571 });
3572
3573 vg_convar_push( (struct vg_convar){
3574 .name = "colours",
3575 .data = &colour_set_id,
3576 .data_type = k_convar_dtype_i32,
3577 .opt_i32 = { .min = 0, .max = 2, .clamp = 1 },
3578 .persistent = 1
3579 });
3580
3581 vg_convar_push( (struct vg_convar){
3582 .name = "theme",
3583 .data = &world_theme_id,
3584 .data_type = k_convar_dtype_i32,
3585 .opt_i32 = { .min = 0, .max = vg_list_size( world_themes )-1, .clamp = 1 },
3586 .persistent = 1,
3587 });
3588
3589 // Combined quad, long quad / empty circle / filled circle mesh
3590 {
3591 float combined_mesh[6*6 + 32*6*3] = {
3592 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
3593 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
3594
3595 0.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.2f,
3596 0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f,
3597
3598 TRANSFORM_TRI_2D( 0.15f,0.05f,0.4f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f ),
3599 TRANSFORM_TRI_2D( 0.15f,0.80f,0.4f, 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 1.0f )
3600 };
3601
3602 float *circle_mesh = combined_mesh + 6*6;
3603 int const res = 32;
3604
3605 for( int i = 0; i < res; i ++ )
3606 {
3607 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
3608 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
3609
3610 circle_mesh[ i*6+0 ] = 0.0f;
3611 circle_mesh[ i*6+1 ] = 0.0f;
3612
3613 v2_copy( v0, circle_mesh + 32*6 + i*12 );
3614 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
3615 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
3616
3617 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
3618 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
3619 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
3620
3621 v2_copy( v0, circle_mesh + i*6+4 );
3622 v2_copy( v1, circle_mesh + i*6+2 );
3623 v2_copy( v0, circle_mesh+i*6+4 );
3624 v2_copy( v1, circle_mesh+i*6+2 );
3625 }
3626
3627 init_mesh( &world.shapes, combined_mesh, vg_list_size( combined_mesh ) );
3628 }
3629
3630 // Create wire mesh
3631 {
3632 int const num_segments = 64;
3633
3634 struct mesh_wire *mw = &world.wire;
3635
3636 v2f wire_points[ num_segments * 2 ];
3637 u16 wire_indices[ 6*(num_segments-1) ];
3638
3639 for( int i = 0; i < num_segments; i ++ )
3640 {
3641 float l = (float)i / (float)(num_segments-1);
3642
3643 v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
3644 v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
3645
3646 if( i < num_segments-1 )
3647 {
3648 wire_indices[ i*6+0 ] = i*2 + 0;
3649 wire_indices[ i*6+1 ] = i*2 + 1;
3650 wire_indices[ i*6+2 ] = i*2 + 3;
3651 wire_indices[ i*6+3 ] = i*2 + 0;
3652 wire_indices[ i*6+4 ] = i*2 + 3;
3653 wire_indices[ i*6+5 ] = i*2 + 2;
3654 }
3655 }
3656
3657 glGenVertexArrays( 1, &mw->vao );
3658 glGenBuffers( 1, &mw->vbo );
3659 glGenBuffers( 1, &mw->ebo );
3660 glBindVertexArray( mw->vao );
3661
3662 glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
3663
3664 glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
3665 glBindVertexArray( mw->vao );
3666
3667 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
3668 glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
3669
3670 // XY
3671 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
3672 glEnableVertexAttribArray( 0 );
3673
3674 VG_CHECK_GL();
3675
3676 mw->em = vg_list_size( wire_indices );
3677 }
3678
3679 // Create info data texture
3680 {
3681 glGenTextures( 1, &world.background_data );
3682 glBindTexture( GL_TEXTURE_2D, world.background_data );
3683 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
3684 vg_tex2d_nearest();
3685 }
3686
3687 // Create random smaples texture
3688 {
3689 u8 *data = malloc(512*512*2);
3690 for( int i = 0; i < 512*512*2; i ++ )
3691 data[ i ] = rand()/(RAND_MAX/255);
3692
3693 glGenTextures( 1, &world.random_samples );
3694 glBindTexture( GL_TEXTURE_2D, world.random_samples );
3695 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
3696 vg_tex2d_linear();
3697 vg_tex2d_repeat();
3698
3699 free( data );
3700 }
3701
3702 resource_load_main();
3703
3704 // Create text buffers
3705 {
3706 // Work out the counts for each 'segment'
3707 u32 desc_max_size = 0, title_max_size = 0,
3708 score_max_size = 10,
3709 time_max_size = 10,
3710
3711 size_level_texts = 6*9*7
3712 ;
3713
3714 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
3715 {
3716 struct career_level_pack *set = &career_packs[i];
3717 for( int j = 0; j < set->count; j ++ )
3718 {
3719 struct cmp_level *lvl = &set->pack[j];
3720
3721 desc_max_size = VG_MAX( desc_max_size, strlen( lvl->description ) );
3722 title_max_size = VG_MAX( title_max_size, strlen( lvl->title ) );
3723 }
3724 }
3725
3726 // Full buffer
3727 u32 total_characters =
3728 title_max_size +
3729 desc_max_size +
3730 score_max_size +
3731 time_max_size +
3732 size_level_texts;
3733
3734 u32 total_faces = total_characters * 2,
3735 total_vertices = total_characters * 4,
3736 total_indices = total_faces * 3;
3737
3738 // Working buffer
3739 u32 work_buffer_total_chars =
3740 VG_MAX( 7, VG_MAX( VG_MAX( desc_max_size, title_max_size ), VG_MAX( score_max_size, time_max_size ) ) );
3741
3742 u32 total_work_faces = work_buffer_total_chars * 2,
3743 total_work_vertices = work_buffer_total_chars * 4,
3744 total_work_indices = total_work_faces * 3;
3745
3746 text_buffers.title_count = 0;
3747 text_buffers.desc_count = 0;
3748 text_buffers.score_count = 0;
3749 text_buffers.time_count = 0;
3750 text_buffers.grid_count = size_level_texts;
3751
3752 // Calculate offsets
3753 text_buffers.title_start = 0;
3754 text_buffers.desc_start = text_buffers.title_start + title_max_size;
3755 text_buffers.score_start = text_buffers.desc_start + desc_max_size;
3756 text_buffers.time_start = text_buffers.score_start + score_max_size;
3757 text_buffers.grid_start = text_buffers.time_start + time_max_size;
3758
3759 // Opengl
3760 glGenVertexArrays(1, &text_buffers.vao);
3761 glGenBuffers( 1, &text_buffers.vbo );
3762 glGenBuffers( 1, &text_buffers.ebo );
3763 glBindVertexArray( text_buffers.vao );
3764
3765 glBindBuffer( GL_ARRAY_BUFFER, text_buffers.vbo );
3766 glBufferData( GL_ARRAY_BUFFER, total_vertices * sizeof( struct vector_glyph_vert ), NULL, GL_DYNAMIC_DRAW );
3767
3768 glBindVertexArray( text_buffers.vao );
3769
3770 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, text_buffers.ebo );
3771 glBufferData( GL_ELEMENT_ARRAY_BUFFER, total_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
3772
3773 u32 const stride = sizeof( struct vector_glyph_vert );
3774
3775 // XY
3776 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, stride, (void *)offsetof( struct vector_glyph_vert, co ) );
3777 glEnableVertexAttribArray( 0 );
3778
3779 // UV
3780 glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, stride, (void *)offsetof( struct vector_glyph_vert, uv ) );
3781 glEnableVertexAttribArray( 1 );
3782
3783 // COLOUR
3784 glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void *)offsetof( struct vector_glyph_vert, colour ) );
3785 glEnableVertexAttribArray( 2 );
3786
3787 // Offline memory
3788 text_buffers.buffer = (struct vector_glyph_vert *)malloc( total_work_vertices * sizeof(struct vector_glyph_vert) );
3789 text_buffers.indices = (u16*)malloc( total_work_indices * sizeof(u16) );
3790
3791 char label[8];
3792 for( int i = 1; i < 7; i ++ )
3793 label[i] = ' ';
3794 label[7] = 0x00;
3795
3796 // Reset grid
3797 for( int x = 0; x < 6; x ++ )
3798 {
3799 for( int y = 0; y < 9; y ++ )
3800 {
3801 label[0] = ' ';
3802
3803 if( x == 0 )
3804 {
3805 if( y != 8 )
3806 label[0] = 'A' + y;
3807 }
3808 else if( y == 8 )
3809 {
3810 label[0] = '0' + x;
3811 }
3812
3813 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 );
3814 }
3815 }
3816 }
3817
3818 // Restore gamestate
3819 career_local_data_init();
3820 career_load();
3821 }
3822
3823 void vg_free(void)
3824 {
3825 #ifdef VG_STEAM
3826 sw_free_opengl();
3827 #endif
3828
3829 console_save_map( 0, NULL );
3830 career_serialize();
3831
3832 glDeleteVertexArrays( 1, &text_buffers.vao );
3833 glDeleteBuffers( 1, &text_buffers.vbo );
3834 glDeleteBuffers( 1, &text_buffers.ebo );
3835
3836 free( text_buffers.buffer );
3837 free( text_buffers.indices );
3838
3839 resource_free_main();
3840
3841 glDeleteTextures( 1, &world.background_data );
3842 glDeleteTextures( 1, &world.random_samples );
3843
3844 glDeleteVertexArrays( 1, &world.wire.vao );
3845 glDeleteBuffers( 1, &world.wire.vbo );
3846 glDeleteBuffers( 1, &world.wire.ebo );
3847
3848 free_mesh( &world.shapes );
3849
3850 map_free();
3851 }
3852
3853 int main( int argc, char *argv[] )
3854 {
3855 vg_init( argc, argv, "Marble Computing" );
3856 return 0;
3857 }