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