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