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