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