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