zoom offset
[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 static v2f drag_origin; // x/y pixel
1514
1515 if( vg_get_button_down( "tertiary" ) )
1516 v2_copy( vg_mouse, drag_origin );
1517 else if( vg_get_button( "tertiary" ) )
1518 {
1519 // get offset
1520 v2_sub( vg_mouse, drag_origin, drag_offset );
1521 v2_div( drag_offset, (v2f){ vg_window_x, vg_window_y }, drag_offset );
1522 v2_mul( drag_offset, (v2f){ size_current*2.0f, -size_current*r1*2.0f }, drag_offset );
1523 }
1524 else
1525 {
1526 v2_copy( result_view, view_point );
1527 v2_copy( (v2f){0.0f,0.0f}, drag_offset );
1528 }
1529
1530 // calculate relative position of mouse in view_point space
1531
1532 { // Debug
1533 v2f lwr; v2f upr;
1534 v2f lwr1; v2f upr1;
1535 v2f p0; v2f p1;
1536 v2f vo;
1537 v2f mouse_viewspace;
1538
1539 float rsize = size-view_zoom_extra;
1540
1541 v2_sub( (v2f){ -size + 0.1f, -size*r1 + 0.1f }, origin, lwr );
1542 v2_sub( (v2f){ size - 0.1f, size*r1 - 0.1f }, origin, upr );
1543
1544 v2_sub( (v2f){ -rsize, -rsize*r1 }, vt_target, lwr1 );
1545 v2_sub( (v2f){ rsize, rsize*r1 }, vt_target, upr1 );
1546
1547 vg_line_box( lwr, upr, 0xffffff00 );
1548 vg_line_box( lwr1, upr1, 0xff00ff00 );
1549
1550 // Calculate vector towards mouse from vt_target
1551 v2f mouse_delta;
1552 v2_muls( vt_target, -1.0f, vo );
1553
1554 v2_sub( vt_target, vg_mouse_ws, mouse_delta );
1555
1556 vg_line( vo, vg_mouse_ws, 0xffffffff );
1557
1558 // Mouse Locally
1559 v2_div( vg_mouse, (v2f){ vg_window_x, vg_window_y }, mouse_viewspace );
1560 mouse_viewspace[0] = mouse_viewspace[0]*rsize*2.0f;
1561 mouse_viewspace[1] = (1.0f-mouse_viewspace[1])*rsize*2.0f*r1;
1562
1563 v2_add( lwr1, mouse_viewspace, mouse_viewspace );
1564
1565 vg_line( mouse_viewspace, (v2f){0.0f, 0.0f}, 0xff0000ff );
1566 //}
1567
1568 // Zoom
1569 view_zoom_extra = vg_clampf( view_zoom_extra + vg_mouse_wheel[1], 0.0f, size - 4.0f );
1570
1571 // Zoom offset adjust
1572 rsize = size-view_zoom_extra;
1573 v2_sub( (v2f){ -rsize, -rsize*r1 }, vt_target, lwr1 );
1574 v2_sub( (v2f){ rsize, rsize*r1 }, vt_target, upr1 );
1575
1576 v2f mouse_viewspace_new;
1577 v2_div( vg_mouse, (v2f){ vg_window_x, vg_window_y }, mouse_viewspace_new );
1578 mouse_viewspace_new[0] = mouse_viewspace_new[0]*rsize*2.0f;
1579 mouse_viewspace_new[1] = (1.0f-mouse_viewspace_new[1])*rsize*2.0f*r1;
1580
1581 v2_add( lwr1, mouse_viewspace_new, mouse_viewspace_new );
1582
1583 v2f zoom_offset;
1584 v2_sub( mouse_viewspace, mouse_viewspace_new, zoom_offset );
1585 v2_muladds( view_point, zoom_offset, -1.0f, view_point );
1586 }
1587
1588 // Tilemap
1589 // ========================================================================================================
1590 if( !is_simulation_running() && !gui_want_mouse() )
1591 {
1592 v2_copy( vg_mouse_ws, world.drag_to_co );
1593
1594 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
1595 {
1596 world.selected = world.tile_y * world.w + world.tile_x;
1597
1598 static u32 modify_state = 0;
1599
1600 struct cell *cell_ptr = &world.data[world.selected];
1601
1602 if( vg_get_button_down("primary") )
1603 modify_state = (cell_ptr->state & FLAG_CANAL) ^ FLAG_CANAL;
1604
1605 if( vg_get_button("primary") && ((cell_ptr->state & FLAG_CANAL) != modify_state) )
1606 {
1607 cell_ptr->state &= ~FLAG_CANAL;
1608 cell_ptr->state |= modify_state;
1609
1610 if( cell_ptr->state & FLAG_CANAL )
1611 {
1612 cell_ptr->links[0] = 0;
1613 cell_ptr->links[1] = 0;
1614
1615 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
1616 world.score ++;
1617 }
1618 else
1619 {
1620 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
1621 world.score --;
1622 }
1623
1624 map_reclassify( (v2i){ world.tile_x -2, world.tile_y -2 },
1625 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
1626 }
1627
1628 if( vg_get_button_down("secondary") && (cell_ptr->state & FLAG_CANAL) && !(cell_ptr->config == k_cell_type_split) )
1629 {
1630 world.id_drag_from = world.selected;
1631
1632 struct cell_description *desc = &cell_descriptions[ world.data[world.id_drag_from].config ];
1633 v2_add( desc->trigger_pos, (v2f){ world.tile_x, world.tile_y }, world.drag_from_co );
1634 }
1635
1636 float local_x = vg_mouse_ws[0] - (float)world.tile_x;
1637
1638 if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
1639 {
1640 u32 link_id = cell_ptr->links[ 0 ]? 0: 1;
1641
1642 // break existing connection off
1643 if( cell_ptr->links[ link_id ] )
1644 {
1645 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1646
1647 if( !current_connection->links[ link_id ^ 0x1 ] )
1648 current_connection->state &= ~FLAG_TARGETED;
1649
1650 current_connection->links[ link_id ] = 0;
1651 cell_ptr->links[ link_id ] = 0;
1652 }
1653
1654 cell_ptr->state &= ~FLAG_IS_TRIGGER;
1655 world.id_drag_from = 0;
1656 }
1657
1658 else if( world.id_drag_from && (cell_ptr->state & FLAG_CANAL) && (cell_ptr->config == k_cell_type_split) )
1659 {
1660 world.drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
1661 world.drag_to_co[1] = (float)world.tile_y + 0.25f;
1662
1663 if( vg_get_button_up("secondary") )
1664 {
1665 struct cell *drag_ptr = &world.data[world.id_drag_from];
1666 u32 link_id = local_x > 0.5f? 1: 0;
1667
1668 // Cleanup existing connections
1669 if( cell_ptr->links[ link_id ] )
1670 {
1671 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id, cell_ptr->links[ link_id ] );
1672
1673 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1674 current_connection->state &= ~FLAG_IS_TRIGGER;
1675 current_connection->links[ link_id ] = 0;
1676 }
1677
1678 if( drag_ptr->links[ link_id ^ 0x1 ] )
1679 {
1680 vg_warn( "Destroying alternate link %u (%hu)\n", link_id ^ 0x1, drag_ptr->links[ link_id ^ 0x1 ] );
1681
1682 struct cell *current_connection = &world.data[ drag_ptr->links[ link_id ^ 0x1 ]];
1683 if( !current_connection->links[ link_id ] )
1684 current_connection->state &= ~FLAG_TARGETED;
1685
1686 current_connection->links[ link_id ^ 0x1 ] = 0;
1687 drag_ptr->links[ link_id ^ 0x1 ] = 0;
1688 }
1689
1690 // Create the new connection
1691 vg_success( "Creating connection on link %u (%hu)\n", link_id, world.id_drag_from );
1692
1693 cell_ptr->links[ link_id ] = world.id_drag_from;
1694 drag_ptr->links[ link_id ] = world.selected;
1695
1696 cell_ptr->state |= FLAG_TARGETED;
1697 drag_ptr->state |= FLAG_IS_TRIGGER;
1698 world.id_drag_from = 0;
1699 }
1700 }
1701 }
1702 else
1703 {
1704 world.selected = -1;
1705 }
1706
1707 if( !(vg_get_button("secondary") && world.id_drag_from) )
1708 world.id_drag_from = 0;
1709 }
1710 else
1711 {
1712 world.selected = -1;
1713 world.id_drag_from = 0;
1714 }
1715
1716 // Marble state updates
1717 // ========================================================================================================
1718 if( is_simulation_running() )
1719 {
1720 float old_time = world.sim_internal_time;
1721
1722 if( !world_static.buttons[ k_world_button_pause ].state )
1723 world.sim_internal_time = world.sim_internal_ref + (vg_time-world.sim_delta_ref) * world.sim_delta_speed;
1724 else
1725 world.sim_internal_time = vg_lerpf( world.sim_internal_time, world.sim_internal_ref + world.pause_offset_target, vg_time_delta*15.0f );
1726 world.sim_internal_delta = world.sim_internal_time-old_time;
1727
1728 world.sim_target = (int)floorf(world.sim_internal_time);
1729
1730 int success_this_frame = 0;
1731 int failure_this_frame = 0;
1732
1733 while( world.sim_frame < world.sim_target )
1734 {
1735 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
1736
1737 // Update splitter deltas
1738 for( int i = 0; i < world.h*world.w; i ++ )
1739 {
1740 struct cell *cell = &world.data[i];
1741 if( cell->config == k_cell_type_split )
1742 {
1743 cell->state &= ~FLAG_FLIP_ROTATING;
1744 }
1745 if( cell->state & FLAG_IS_TRIGGER )
1746 cell->state &= ~FLAG_TRIGGERED;
1747 }
1748
1749 int alive_count = 0;
1750
1751 // Update fish positions
1752 for( int i = 0; i < world.num_fishes; i ++ )
1753 {
1754 struct fish *fish = &world.fishes[i];
1755
1756 if( fish->state == k_fish_state_soon_dead )
1757 fish->state = k_fish_state_dead;
1758
1759 if( fish->state == k_fish_state_soon_alive )
1760 fish->state = k_fish_state_alive;
1761
1762 if( fish->state < k_fish_state_alive )
1763 continue;
1764
1765 struct cell *cell_current = pcell( fish->pos );
1766
1767 if( fish->state == k_fish_state_alive )
1768 {
1769 // Apply to output
1770 if( cell_current->state & FLAG_OUTPUT )
1771 {
1772 for( int j = 0; j < arrlen( world.io ); j ++ )
1773 {
1774 struct cell_terminal *term = &world.io[j];
1775
1776 if( v2i_eq( term->pos, fish->pos ) )
1777 {
1778 struct terminal_run *run = &term->runs[ world.sim_run ];
1779 if( run->recv_count < vg_list_size( run->recieved ) )
1780 {
1781 if( fish->payload == run->conditions[ run->recv_count ] )
1782 success_this_frame = 1;
1783 else
1784 failure_this_frame = 1;
1785
1786 run->recieved[ run->recv_count ++ ] = fish->payload;
1787 }
1788 else
1789 failure_this_frame = 1;
1790
1791 break;
1792 }
1793 }
1794
1795 fish->state = k_fish_state_dead;
1796 fish->death_time = -1000.0f;
1797 continue;
1798 }
1799
1800
1801 if( cell_current->config == k_cell_type_merge )
1802 {
1803 // Can only move up
1804 fish->dir[0] = 0;
1805 fish->dir[1] = -1;
1806 fish->flow_reversed = 0;
1807 }
1808 else
1809 {
1810 if( cell_current->config == k_cell_type_split )
1811 {
1812 // Flip flop L/R
1813 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
1814 fish->dir[1] = 0;
1815
1816 if( !(cell_current->state & FLAG_TARGETED) )
1817 cell_current->state ^= FLAG_FLIP_FLOP;
1818 }
1819 else
1820 {
1821 // Apply cell out-flow
1822 struct cell_description *desc = &cell_descriptions[ cell_current->config ];
1823
1824 v2i_copy( fish->flow_reversed? desc->start: desc->end, fish->dir );
1825 }
1826
1827 v2i pos_next;
1828 v2i_add( fish->pos, fish->dir, pos_next );
1829
1830 struct cell *cell_next = pcell( pos_next );
1831
1832 if( cell_next->state & (FLAG_CANAL|FLAG_OUTPUT) )
1833 {
1834 struct cell_description *desc = &cell_descriptions[ cell_next->config ];
1835
1836 if( cell_next->config == k_cell_type_merge )
1837 {
1838 if( fish->dir[0] == 0 )
1839 {
1840 fish->state = k_fish_state_dead;
1841 fish->death_time = world.sim_internal_time;
1842 }
1843 else
1844 fish->flow_reversed = 0;
1845 }
1846 else
1847 {
1848 if( cell_next->config == k_cell_type_split )
1849 {
1850 if( fish->dir[0] == 0 )
1851 {
1852 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
1853 cell_next->state |= FLAG_FLIP_ROTATING;
1854
1855 fish->flow_reversed = 0;
1856 }
1857 else
1858 {
1859 fish->state = k_fish_state_dead;
1860 fish->death_time = world.sim_internal_time;
1861 }
1862 }
1863 else
1864 fish->flow_reversed = ( fish->dir[0] != -desc->start[0] ||
1865 fish->dir[1] != -desc->start[1] )? 1: 0;
1866 }
1867 }
1868 else
1869 {
1870 if( world_check_pos_ok( fish->pos ) )
1871 fish->state = k_fish_state_bg;
1872 else
1873 {
1874 fish->state = k_fish_state_dead;
1875 fish->death_time = world.sim_internal_time;
1876 }
1877 }
1878 }
1879
1880 //v2i_add( fish->pos, fish->dir, fish->pos );
1881 }
1882 else if( fish->state == k_fish_state_bg )
1883 {
1884 v2i_add( fish->pos, fish->dir, fish->pos );
1885
1886 if( !world_check_pos_ok( fish->pos ) )
1887 {
1888 fish->state = k_fish_state_dead;
1889 fish->death_time = -1000.0f;
1890 }
1891 else
1892 {
1893 struct cell *cell_entry = pcell( fish->pos );
1894
1895 if( cell_entry->state & FLAG_CANAL )
1896 {
1897 if( cell_entry->config == k_cell_type_con_r || cell_entry->config == k_cell_type_con_u
1898 || cell_entry->config == k_cell_type_con_l || cell_entry->config == k_cell_type_con_d )
1899 {
1900 #ifdef VG_STEAM
1901 sw_set_achievement( "CAN_DO_THAT" );
1902 #endif
1903
1904 fish->state = k_fish_state_soon_alive;
1905
1906 fish->dir[0] = 0;
1907 fish->dir[1] = 0;
1908 fish->flow_reversed = 1;
1909
1910 switch( cell_entry->config )
1911 {
1912 case k_cell_type_con_r: fish->dir[0] = 1; break;
1913 case k_cell_type_con_l: fish->dir[0] = -1; break;
1914 case k_cell_type_con_u: fish->dir[1] = 1; break;
1915 case k_cell_type_con_d: fish->dir[1] = -1; break;
1916 }
1917 }
1918 }
1919 }
1920 }
1921 else { vg_error( "fish behaviour unimplemented for behaviour type (%d)\n" ); }
1922
1923 if( fish->state >= k_fish_state_alive )
1924 alive_count ++;
1925 }
1926
1927 // Second pass (triggers)
1928 for( int i = 0; i < world.num_fishes; i ++ )
1929 {
1930 struct fish *fish = &world.fishes[i];
1931
1932 if( fish->state == k_fish_state_alive )
1933 {
1934 v2i_add( fish->pos, fish->dir, fish->pos );
1935 struct cell *cell_current = pcell( fish->pos );
1936
1937 if( cell_current->state & FLAG_IS_TRIGGER )
1938 {
1939 int trigger_id = cell_current->links[0]?0:1;
1940
1941 struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
1942
1943 cell_current->state |= FLAG_TRIGGERED;
1944
1945 if( trigger_id )
1946 target_peice->state |= FLAG_FLIP_FLOP;
1947 else
1948 target_peice->state &= ~FLAG_FLIP_FLOP;
1949 }
1950 }
1951 }
1952
1953 // Third pass (collisions)
1954 struct fish *fi, *fj;
1955
1956 for( int i = 0; i < world.num_fishes; i ++ )
1957 {
1958 fi = &world.fishes[i];
1959
1960 if( fi->state == k_fish_state_alive )
1961 {
1962 int continue_again = 0;
1963
1964 for( int j = i+1; j < world.num_fishes; j ++ )
1965 {
1966 fj = &world.fishes[j];
1967
1968 if( (fj->state == k_fish_state_alive) )
1969 {
1970 v2i fi_prev;
1971 v2i fj_prev;
1972
1973 v2i_sub( fi->pos, fi->dir, fi_prev );
1974 v2i_sub( fj->pos, fj->dir, fj_prev );
1975
1976 int
1977 collide_next_frame = (
1978 (fi->pos[0] == fj->pos[0]) &&
1979 (fi->pos[1] == fj->pos[1]))? 1: 0,
1980 collide_this_frame = (
1981 (fi_prev[0] == fj->pos[0]) &&
1982 (fi_prev[1] == fj->pos[1]) &&
1983 (fj_prev[0] == fi->pos[0]) &&
1984 (fj_prev[1] == fi->pos[1])
1985 )? 1: 0;
1986
1987 if( collide_next_frame || collide_this_frame )
1988 {
1989 #ifdef VG_STEAM
1990 sw_set_achievement( "BANG" );
1991 #endif
1992
1993 // Shatter death (+0.5s)
1994 float death_time = world.sim_internal_time + ( collide_this_frame? 0.0f: 0.5f );
1995
1996 fi->state = k_fish_state_soon_dead;
1997 fj->state = k_fish_state_soon_dead;
1998 fi->death_time = death_time;
1999 fj->death_time = death_time;
2000
2001 continue_again = 1;
2002 break;
2003 }
2004 }
2005 }
2006 if( continue_again )
2007 continue;
2008 }
2009 }
2010
2011 // Spawn fishes
2012 for( int i = 0; i < arrlen( world.io ); i ++ )
2013 {
2014 struct cell_terminal *term = &world.io[ i ];
2015 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2016
2017 if( is_input )
2018 {
2019 if( world.sim_frame < term->runs[ world.sim_run ].condition_count )
2020 {
2021 char emit = term->runs[ world.sim_run ].conditions[ world.sim_frame ];
2022 if( emit == ' ' )
2023 continue;
2024
2025 struct fish *fish = &world.fishes[ world.num_fishes ];
2026 v2i_copy( term->pos, fish->pos );
2027
2028 fish->state = k_fish_state_alive;
2029 fish->payload = emit;
2030
2031 struct cell *cell_ptr = pcell( fish->pos );
2032
2033 if( cell_ptr->config != k_cell_type_stub )
2034 {
2035 struct cell_description *desc = &cell_descriptions[ cell_ptr->config ];
2036
2037 v2i_copy( desc->start, fish->dir );
2038 fish->flow_reversed = 1;
2039
2040 world.num_fishes ++;
2041 alive_count ++;
2042 }
2043 }
2044 }
2045 }
2046
2047 if( alive_count == 0 )
2048 {
2049 world.completed = 1;
2050
2051 for( int i = 0; i < arrlen( world.io ); i ++ )
2052 {
2053 struct cell_terminal *term = &world.io[ i ];
2054 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2055
2056 if( !is_input )
2057 {
2058 struct terminal_run *run = &term->runs[ world.sim_run ];
2059
2060 if( run->recv_count == run->condition_count )
2061 {
2062 for( int j = 0; j < run->condition_count; j ++ )
2063 {
2064 if( run->recieved[j] != run->conditions[j] )
2065 {
2066 world.completed = 0;
2067 break;
2068 }
2069 }
2070 }
2071 else
2072 {
2073 world.completed = 0;
2074 break;
2075 }
2076 }
2077 }
2078
2079 if( world.completed )
2080 {
2081 if( world.sim_run < world.max_runs-1 )
2082 {
2083 vg_success( "Run passed, starting next\n" );
2084 world.sim_run ++;
2085 world.sim_frame = 0;
2086 world.sim_target = 0;
2087 world.num_fishes = 0;
2088
2089 // Reset timing reference points
2090 world.sim_delta_ref = vg_time;
2091 world.sim_internal_ref = 0.0f;
2092
2093 if( world_static.buttons[ k_world_button_pause ].state )
2094 world.pause_offset_target = 0.5f;
2095 else
2096 world.pause_offset_target = 0.0f;
2097
2098 world.sim_internal_time = 0.0f;
2099
2100 for( int i = 0; i < world.w*world.h; i ++ )
2101 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
2102
2103 continue;
2104 }
2105 else
2106 {
2107 vg_success( "Level passed!\n" );
2108
2109 u32 score = 0;
2110 for( int i = 0; i < world.w*world.h; i ++ )
2111 if( world.data[ i ].state & FLAG_CANAL )
2112 score ++;
2113
2114 world.score = score;
2115 world.time = world.sim_frame;
2116
2117 // Copy into career data
2118 if( world.pCmpLevel )
2119 {
2120 career_pass_level( world.pCmpLevel, world.score, 1 );
2121 }
2122
2123 sfx_set_play( &audio_tones, &audio_system_balls_extra, 9 );
2124 failure_this_frame = 0;
2125 success_this_frame = 0;
2126 }
2127 }
2128 else
2129 {
2130 #ifdef VG_STEAM
2131 if( world.sim_run > 0 )
2132 sw_set_achievement( "GOOD_ENOUGH" );
2133 #endif
2134
2135 vg_error( "Level failed :(\n" );
2136 }
2137
2138 simulation_stop();
2139 break;
2140 }
2141
2142 world.sim_frame ++;
2143 }
2144
2145 // Sounds
2146 if( failure_this_frame )
2147 {
2148 sfx_set_play( &audio_tones, &audio_system_balls_extra, 0 );
2149 }
2150 else if( success_this_frame )
2151 {
2152 static int succes_counter = 0;
2153
2154 sfx_set_play( &audio_tones, &audio_system_balls_extra, 1+(succes_counter++) );
2155
2156 if( succes_counter == 7 )
2157 succes_counter = 0;
2158 }
2159
2160 // Position update
2161 // =====================================================================================================
2162
2163 world.frame_lerp = world.sim_internal_time - floorf( world.sim_internal_time );
2164
2165 for( int i = 0; i < world.num_fishes; i ++ )
2166 {
2167 struct fish *fish = &world.fishes[i];
2168
2169 if( fish->state == k_fish_state_dead )
2170 continue;
2171
2172 if( fish->state == k_fish_state_soon_dead && (world.sim_internal_time > fish->death_time) )
2173 continue; // Todo: particle thing?
2174
2175 struct cell *cell = pcell(fish->pos);
2176 struct cell_description *desc = &cell_descriptions[ cell->config ];
2177
2178 v2f const *curve;
2179
2180 float t = world.frame_lerp;
2181 if( fish->flow_reversed && !desc->is_linear )
2182 t = 1.0f-t;
2183
2184 v2_copy( fish->physics_co, fish->physics_v );
2185
2186 switch( cell->config )
2187 {
2188 case k_cell_type_merge:
2189 if( fish->dir[0] == 1 )
2190 curve = curve_12;
2191 else
2192 curve = curve_9;
2193 break;
2194 case k_cell_type_con_r: curve = curve_1; break;
2195 case k_cell_type_con_l: curve = curve_4; break;
2196 case k_cell_type_con_u: curve = curve_2; break;
2197 case k_cell_type_con_d: curve = curve_8; break;
2198 case 3: curve = curve_3; break;
2199 case 6: curve = curve_6; break;
2200 case 9: curve = curve_9; break;
2201 case 12: curve = curve_12; break;
2202 case 7:
2203 if( t > curve_7_linear_section )
2204 {
2205 t -= curve_7_linear_section;
2206 t *= (1.0f/(1.0f-curve_7_linear_section));
2207
2208 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
2209 }
2210 else curve = NULL;
2211 break;
2212 default: curve = NULL; break;
2213 }
2214
2215 if( curve )
2216 {
2217 float t2 = t * t;
2218 float t3 = t * t * t;
2219
2220 float cA = 3.0f*t2 - 3.0f*t3;
2221 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
2222 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
2223
2224 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
2225 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
2226 fish->physics_co[0] += (float)fish->pos[0];
2227 fish->physics_co[1] += (float)fish->pos[1];
2228 }
2229 else
2230 {
2231 v2f origin;
2232 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
2233 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
2234
2235 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
2236 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
2237 }
2238
2239 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
2240 v2_divs( fish->physics_v, world.sim_internal_delta, fish->physics_v );
2241 }
2242 }
2243 }
2244
2245 static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
2246 {
2247 v2i full_start = { 0,0 };
2248 v2i full_end = { world.w, world.h };
2249
2250 if( !start || !end )
2251 {
2252 start = full_start;
2253 end = full_end;
2254 }
2255
2256 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
2257
2258 for( int y = start[1]; y < end[1]; y ++ )
2259 {
2260 for( int x = start[0]; x < end[0]; x ++ )
2261 {
2262 struct cell *cell = pcell((v2i){x,y});
2263 int selected = world.selected == y*world.w + x;
2264
2265 int tile_offsets[][2] =
2266 {
2267 {2, 0}, {0, 3}, {0, 2}, {2, 2},
2268 {1, 0}, {2, 3}, {3, 2}, {1, 3},
2269 {3, 1}, {0, 1}, {1, 2}, {2, 1},
2270 {1, 1}, {3, 3}, {2, 1}, {2, 1}
2271 };
2272
2273 int uv[2] = { 3, 0 };
2274
2275 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
2276 {
2277 uv[0] = tile_offsets[ cell->config ][0];
2278 uv[1] = tile_offsets[ cell->config ][1];
2279 } else continue;
2280
2281 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] );
2282 if( selected )
2283 {
2284 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
2285 draw_mesh( 0, 2 );
2286 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
2287 }
2288 else
2289 draw_mesh( 0, 2 );
2290 }
2291 }
2292 }
2293
2294 static int world_button_exec( struct world_button *btn, v2f texture, v3f colour, enum world_button_status *status )
2295 {
2296 static v2i click_grab = { -9999, -9999 };
2297
2298 // Reset click_grab
2299 if( !btn )
2300 {
2301 click_grab[0] = -9999;
2302 click_grab[1] = -9999;
2303 return 0;
2304 }
2305
2306 v2i click_tile = { world.tile_x, world.tile_y };
2307
2308 int triggered = 0;
2309 int is_hovering = v2i_eq( click_tile, btn->position );
2310
2311 // Set up light targets before logic runs
2312 if( btn->state )
2313 btn->light_target = is_hovering? 0.9f: 0.8f;
2314 else
2315 btn->light_target = is_hovering? 0.2f: 0.0f;
2316
2317 if( vg_get_button( "primary" ) && is_hovering )
2318 btn->light_target = 1.0f;
2319
2320 // Process click action
2321 if( is_hovering )
2322 {
2323 if( vg_get_button_down( "primary" ) && is_hovering )
2324 v2i_copy( click_tile, click_grab );
2325 else if( v2i_eq( click_grab, click_tile ) && vg_get_button_up( "primary" ) )
2326 {
2327 // Click event
2328 *status = btn->state? k_world_button_on_disable: k_world_button_on_enable;
2329
2330 if( btn->mode == k_world_button_mode_toggle )
2331 btn->state ^= 0x1;
2332
2333 sfx_set_play( &audio_clicks, &audio_system_ui, btn->state? 1:0 );
2334 triggered = 1;
2335 }
2336 }
2337
2338 // Drawing stage
2339 v4f final_colour;
2340
2341 btn->light = vg_lerpf( btn->light, btn->light_target + btn->extra_light, vg_time_delta*26.0f );
2342
2343 v3_copy( colour, final_colour );
2344 final_colour[3] = btn->light;
2345
2346 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2347 btn->position[0],
2348 btn->position[1],
2349 texture[0],
2350 texture[1]
2351 );
2352 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
2353 draw_mesh( 0, 2 );
2354
2355 return triggered;
2356 }
2357
2358 /*
2359 static void wbutton_run( enum e_world_button btn_name, v2f btn_tex )
2360 {
2361 static v3f button_colours[] = {
2362 {0.204f, 0.345f, 0.553f},
2363 {0.204f, 0.345f, 0.553f},
2364 {0.553f, 0.345f, 0.204f},
2365 {1.0f, 0.0f, 0.0f}
2366 };
2367
2368 struct cell_button *btn = &world.buttons[btn_name];
2369
2370 // Interaction
2371 int tex_offset = 0;
2372
2373 int is_hovering = (world.tile_x == world.w-1 && world.tile_y == world.h-btn_name-2)?1:0;
2374 if( vg_get_button_up( "primary" ) && is_hovering )
2375 {
2376 // Click event
2377 if( btn_name == k_world_button_sim )
2378 {
2379 if( world.buttons[ k_world_button_pause ].pressed )
2380 {
2381 if( !btn->pressed )
2382 {
2383 btn->pressed = 1;
2384 simulation_start();
2385
2386 world.pause_offset_target = 0.5f;
2387 }
2388 else
2389 world.pause_offset_target += 1.0f;
2390 }
2391 else
2392 {
2393 btn->pressed ^= 0x1;
2394
2395 if( btn->pressed )
2396 simulation_start();
2397 else
2398 simulation_stop();
2399 }
2400 }
2401 else if( btn_name == k_world_button_pause )
2402 {
2403 btn->pressed ^= 0x1;
2404
2405 world.sim_internal_ref = world.sim_internal_time;
2406 world.sim_delta_ref = vg_time;
2407
2408 if( btn->pressed )
2409 {
2410 float time_frac = world.sim_internal_time-floorf(world.sim_internal_time);
2411 world.pause_offset_target = 0.5f - time_frac;
2412 }
2413 else
2414 world.pause_offset_target = 0.0f;
2415 }
2416 else if( btn_name == k_world_button_speedy )
2417 {
2418 btn->pressed ^= 0x1;
2419
2420 world.sim_delta_speed = btn->pressed? 10.0f: 2.5f;
2421 world.sim_delta_ref = vg_time;
2422 world.sim_internal_ref = world.sim_internal_time;
2423 }
2424 else
2425 {
2426 btn->pressed ^= 0x1;
2427 }
2428
2429 sfx_set_play( &audio_clicks, &audio_system_ui, btn->pressed?1:0 );
2430 }
2431
2432 // Drawing
2433 if( btn->pressed )
2434 {
2435 if( is_hovering )
2436 {
2437 btn->light_target = 0.9f;
2438 }
2439 else
2440 {
2441 if( btn_name == k_world_button_sim && world.buttons[ k_world_button_pause ].pressed )
2442 btn->light_target = fabsf(sinf( vg_time * 2.0f )) * 0.3f + 0.3f;
2443 else
2444 btn->light_target = 0.8f;
2445 }
2446 }
2447 else
2448 {
2449 btn->light_target = is_hovering? 0.2f: 0.0f;
2450 }
2451
2452 if( vg_get_button( "primary" ) && is_hovering )
2453 btn->light_target = 1.0f;
2454
2455 btn->light = vg_lerpf( btn->light, btn->light_target, vg_time_delta*26.0f );
2456
2457 // Draw
2458 if( btn_name == k_world_button_sim && world.buttons[ k_world_button_sim ].pressed )
2459 {
2460 if( world.buttons[ k_world_button_pause ].pressed )
2461 tex_offset = 3;
2462 else
2463 tex_offset = 2;
2464 }
2465
2466 v4f final_colour;
2467 v3_copy( button_colours[ btn_name ], final_colour );
2468 final_colour[3] = btn->light;
2469
2470 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2471 world.w-1,
2472 world.h-btn_name-2,
2473 (float)(btn_tex[0]+tex_offset),
2474 btn_tex[1]
2475 );
2476 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
2477
2478 draw_mesh( 0, 2 );
2479 }
2480
2481 static void wbutton_draw( v2i pos, v2f tex, v4f colour )
2482 {
2483 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2484 pos[0],
2485 pos[1],
2486 tex[0],
2487 tex[1]
2488 );
2489 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, colour );
2490 draw_mesh( 0, 2 );
2491 }
2492 */
2493
2494 static void level_selection_buttons(void)
2495 {
2496 v3f tutorial_colour = { 0.204f, 0.345f, 0.553f };
2497 v3f locked_colour = { 0.2f, 0.2f, 0.2f };
2498
2499 struct cmp_level *switch_level_to = NULL;
2500
2501 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
2502 {
2503 struct career_level_pack *grid = &career_packs[i];
2504
2505 for( int j = 0; j < grid->count; j ++ )
2506 {
2507 struct cmp_level *lvl = &grid->pack[ j ];
2508
2509 if( world.pCmpLevel == lvl )
2510 lvl->btn.extra_light = 0.35f + fabsf(sinf( vg_time * 2.0f )) * 0.05f;
2511 else lvl->btn.extra_light = 0.2f;
2512
2513 if( lvl->completed_score )
2514 lvl->btn.extra_light += 0.8f;
2515
2516 enum world_button_status status;
2517 if( world_button_exec(
2518 &lvl->btn,
2519 (v2f){0.0f,0.0f},
2520 lvl->unlocked? (lvl->is_tutorial? tutorial_colour: grid->primary_colour): locked_colour,
2521 &status
2522 ))
2523 {
2524 if( status == k_world_button_on_enable && lvl->unlocked )
2525 switch_level_to = lvl;
2526 }
2527 }
2528 }
2529
2530 if( switch_level_to )
2531 {
2532 if( console_changelevel( 1, &switch_level_to->map_name ) )
2533 {
2534 world.pCmpLevel = switch_level_to;
2535 gen_level_text( world.pCmpLevel );
2536 }
2537 }
2538 }
2539
2540 void vg_render(void)
2541 {
2542 glViewport( 0,0, vg_window_x, vg_window_y );
2543
2544 glDisable( GL_DEPTH_TEST );
2545 glClearColor( 0.369768f, 0.3654f, 0.42f, 1.0f );
2546 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
2547
2548 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
2549 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
2550
2551 int const circle_base = 6;
2552 int const filled_start = circle_base+0;
2553 int const filled_count = circle_base+32;
2554 int const empty_start = circle_base+32;
2555 int const empty_count = circle_base+32*2;
2556
2557 if( !world.initialzed )
2558 return;
2559
2560 // BACKGROUND
2561 // ========================================================================================================
2562 use_mesh( &world.shapes );
2563
2564 SHADER_USE( shader_background );
2565 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2566
2567 glActiveTexture( GL_TEXTURE0 );
2568 glBindTexture( GL_TEXTURE_2D, world.background_data );
2569 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
2570
2571 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
2572 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.02f );
2573
2574 glActiveTexture( GL_TEXTURE1 );
2575 glBindTexture( GL_TEXTURE_2D, world.random_samples );
2576 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
2577 glUniform1f( SHADER_UNIFORM( shader_background, "uVisibility" ), 1.0f ); // (sinf( vg_time ) + 1.0f) * 0.5f );
2578
2579 draw_mesh( 0, 2 );
2580
2581 // TILESET BACKGROUND LAYER
2582 // ========================================================================================================
2583 use_mesh( &world.shapes );
2584 SHADER_USE( shader_tile_main );
2585
2586 m2x2f subtransform;
2587 m2x2_identity( subtransform );
2588 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2589 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2590 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
2591 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
2592 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uVisibility" ), 2.0f ); // sinf( vg_time ) + 1.0f );
2593
2594 glEnable(GL_BLEND);
2595 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2596 glBlendEquation(GL_FUNC_ADD);
2597
2598 // Bind textures
2599 vg_tex2d_bind( &tex_tile_data, 0 );
2600 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2601
2602 vg_tex2d_bind( &tex_wood, 1 );
2603 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2604
2605 render_tiles( NULL, NULL, colour_default, colour_default );
2606
2607 // MARBLES
2608 // ========================================================================================================
2609 SHADER_USE( shader_ball );
2610 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2611
2612 vg_tex2d_bind( &tex_ball_noise, 0 );
2613 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
2614
2615 if( world_static.buttons[ k_world_button_sim ].state )
2616 {
2617 for( int i = 0; i < world.num_fishes; i ++ )
2618 {
2619 struct fish *fish = &world.fishes[i];
2620 v3f render_pos;
2621 render_pos[2] = 1.0f;
2622
2623 if( fish->state == k_fish_state_dead || fish->state == k_fish_state_soon_dead )
2624 {
2625 float death_anim_time = world.sim_internal_time - fish->death_time;
2626
2627 // Death animation
2628 if( death_anim_time > 0.0f && death_anim_time < 1.0f )
2629 {
2630 float amt = 1.0f-death_anim_time*death_anim_time;
2631
2632 v2_muladds( fish->physics_co, fish->physics_v, -1.0f * world.sim_internal_delta * amt, fish->physics_co );
2633 render_pos[2] = amt;
2634 }
2635 else if( world.sim_internal_time > fish->death_time )
2636 continue;
2637 }
2638 else if( fish->state == k_fish_state_bg )
2639 continue;
2640
2641 v2_copy( fish->physics_co, render_pos );
2642
2643 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2644 colour_code_v3( fish->payload, dot_colour );
2645
2646 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
2647 glUniform3fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, render_pos );
2648 glUniform2f( SHADER_UNIFORM( shader_ball, "uTexOffset" ), (float)i * 1.2334, (float)i * -0.3579f );
2649 draw_mesh( 0, 2 );
2650 }
2651 }
2652
2653 // TILESET FOREGROUND LAYER
2654 // ========================================================================================================
2655 SHADER_USE( shader_tile_main );
2656
2657 // Bind textures
2658 vg_tex2d_bind( &tex_tile_data, 0 );
2659 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2660
2661 vg_tex2d_bind( &tex_wood, 1 );
2662 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2663
2664 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
2665 render_tiles( NULL, NULL, colour_default, colour_selected );
2666
2667 // Draw splitters
2668 for( int y = 2; y < world.h-2; y ++ )
2669 {
2670 for( int x = 2; x < world.w-2; x ++ )
2671 {
2672 struct cell *cell = pcell((v2i){x,y});
2673
2674 if( cell->state & FLAG_CANAL )
2675 {
2676 if( cell->config == k_cell_type_split )
2677 {
2678 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
2679
2680 if( cell->state & FLAG_FLIP_ROTATING )
2681 {
2682 if( (world.frame_lerp > curve_7_linear_section) )
2683 {
2684 float const rotation_speed = 0.4f;
2685 if( (world.frame_lerp < 1.0f-rotation_speed) )
2686 {
2687 float t = world.frame_lerp - curve_7_linear_section;
2688 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
2689 t += 1.0f;
2690
2691 rotation *= t;
2692 }
2693 else
2694 rotation *= -1.0f;
2695 }
2696 }
2697
2698 m2x2_create_rotation( subtransform, rotation );
2699
2700 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2701 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
2702 (float)x,
2703 (float)y + 0.125f,
2704 cell->state & FLAG_TARGETED? 3.0f: 0.0f,
2705 0.0f
2706 );
2707 draw_mesh( 0, 2 );
2708 }
2709 }
2710 }
2711 }
2712
2713 // EDIT OVERLAY
2714 // ========================================================================================================
2715 if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) && !world.id_drag_from )
2716 {
2717 v2i new_begin = { world.tile_x - 2, world.tile_y - 2 };
2718 v2i new_end = { world.tile_x + 2, world.tile_y + 2 };
2719
2720 world.data[ world.selected ].state ^= FLAG_CANAL;
2721 map_reclassify( new_begin, new_end, 0 );
2722
2723 m2x2_identity( subtransform );
2724 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f );
2725 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2726 glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
2727
2728 render_tiles( new_begin, new_end, colour_default, colour_default );
2729
2730 world.data[ world.selected ].state ^= FLAG_CANAL;
2731 map_reclassify( new_begin, new_end, 0 );
2732 }
2733
2734 // BUTTONS
2735 // ========================================================================================================
2736 SHADER_USE( shader_buttons );
2737 glUniformMatrix3fv( SHADER_UNIFORM( shader_buttons, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2738
2739 vg_tex2d_bind( &tex_buttons, 0 );
2740 glUniform1i( SHADER_UNIFORM( shader_buttons, "uTexMain" ), 0 );
2741
2742 enum world_button_status stat;
2743 int world_paused = world_static.buttons[k_world_button_pause].state;
2744 int world_running = world_static.buttons[k_world_button_sim].state;
2745
2746 float sim_icon_x = world_paused? 3.0f: (world_running? 2.0f: 0.0f);
2747
2748 v3f btn_dark_blue = { 0.204f, 0.345f, 0.553f };
2749 v3f btn_orange = { 0.553f, 0.345f, 0.204f };
2750
2751 if( world_button_exec( &world_static.buttons[k_world_button_sim], (v2f){ sim_icon_x, 3.0f }, btn_dark_blue, &stat ))
2752 {
2753 if( stat == k_world_button_on_enable )
2754 {
2755 simulation_start();
2756
2757 if( world_paused )
2758 world.pause_offset_target = 0.5f;
2759 }
2760 else
2761 {
2762 if( world_paused )
2763 {
2764 // Trigger single step
2765 world.pause_offset_target += 1.0f;
2766 world_static.buttons[k_world_button_sim].state = 1;
2767 }
2768 else
2769 {
2770 simulation_stop();
2771 }
2772 }
2773 }
2774
2775 if( world_button_exec( &world_static.buttons[k_world_button_pause], (v2f){ 1.0f, 3.0f }, btn_dark_blue, &stat ))
2776 {
2777 world.sim_internal_ref = world.sim_internal_time;
2778 world.sim_delta_ref = vg_time;
2779
2780 if( stat == k_world_button_on_enable )
2781 {
2782 float time_frac = world.sim_internal_time-floorf(world.sim_internal_time);
2783 world.pause_offset_target = 0.5f - time_frac;
2784 }
2785 else
2786 world.pause_offset_target = 0.0f;
2787 }
2788
2789 if( world_button_exec( &world_static.buttons[k_world_button_speedy], (v2f){ 0.0f, 2.0f }, btn_orange, &stat ))
2790 {
2791 world.sim_delta_speed = stat == k_world_button_on_enable? 10.0f: 2.5f;
2792
2793 if( !world_paused )
2794 {
2795 world.sim_delta_ref = vg_time;
2796 world.sim_internal_ref = world.sim_internal_time;
2797 }
2798 }
2799
2800 if( world_button_exec( &world_static.buttons[k_world_button_settings], (v2f){ 1.0f, 2.0f }, btn_orange, &stat ))
2801 {
2802
2803 }
2804
2805 level_selection_buttons();
2806
2807 if( vg_get_button_up( "primary" ) )
2808 world_button_exec( NULL, NULL, NULL, NULL );
2809
2810 // TEXT ELEMENTS
2811 // ========================================================================================================
2812 SHADER_USE( shader_sdf );
2813 glBindVertexArray( text_buffers.vao );
2814 glUniformMatrix3fv( SHADER_UNIFORM( shader_sdf, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2815
2816 vg_tex2d_bind( &tex_ubuntu, 0 );
2817 glUniform1i( SHADER_UNIFORM( shader_sdf, "uTexGlyphs" ), 0 );
2818
2819 glUniform4f( SHADER_UNIFORM( shader_sdf, "uColour" ), 1.0f, 1.0f, 1.0f, 1.0f );
2820 glDrawElements( GL_TRIANGLES, text_buffers.title_count*6, GL_UNSIGNED_SHORT, (void*)( text_buffers.title_start*6*sizeof(u16) ) );
2821 glDrawElements( GL_TRIANGLES, text_buffers.desc_count*6, GL_UNSIGNED_SHORT, (void*)( text_buffers.desc_start*6*sizeof(u16) ) );
2822
2823 glUniform4f( SHADER_UNIFORM( shader_sdf, "uColour" ), 1.0f, 1.0f, 1.0f, 0.17f );
2824 glDrawElements( GL_TRIANGLES, text_buffers.grid_count*6, GL_UNSIGNED_SHORT, (void*)( text_buffers.grid_start*6*sizeof(u16) ) );
2825
2826 // WIRES
2827 // ========================================================================================================
2828 //glDisable(GL_BLEND);
2829
2830 SHADER_USE( shader_wire );
2831 glBindVertexArray( world.wire.vao );
2832
2833 glUniformMatrix3fv( SHADER_UNIFORM( shader_wire, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2834
2835 v4f const wire_left_colour = { 0.5f, 0.5f, 0.5f, 1.0f };
2836 v4f const wire_right_colour = { 0.2f, 0.2f, 0.2f, 1.0f };
2837 v4f const wire_drag_colour = { 0.2f, 0.2f, 0.2f, 0.6f };
2838
2839 glUniform1f( SHADER_UNIFORM( shader_wire, "uTime" ), world.frame_lerp );
2840 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), 0.0f );
2841
2842 if( world.id_drag_from )
2843 {
2844 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_drag_colour );
2845 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), 0.4f );
2846 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), world.drag_from_co[0], world.drag_from_co[1], 0.20f );
2847 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.20f );
2848 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2849 }
2850
2851 // Pulling animation
2852 float rp_x1 = world.frame_lerp*9.0f;
2853 float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f;
2854 float rp_x2 = 1.0f-rp_xa;
2855
2856 for( int y = 2; y < world.h-2; y ++ )
2857 {
2858 for( int x = 2; x < world.w-2; x ++ )
2859 {
2860 struct cell *cell = pcell((v2i){x,y});
2861
2862 if( cell->state & FLAG_CANAL )
2863 {
2864 if( cell->state & FLAG_IS_TRIGGER )
2865 {
2866 struct cell_description *desc = &cell_descriptions[ cell->config ];
2867
2868 int trigger_id = cell->links[0]?0:1;
2869
2870 int x2 = cell->links[trigger_id] % world.w;
2871 int y2 = (cell->links[trigger_id] - x2) / world.w;
2872
2873 v2f startpoint;
2874 v2f endpoint;
2875
2876 startpoint[0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
2877 startpoint[1] = (float)y2 + 0.25f;
2878
2879 endpoint[0] = x;
2880 endpoint[1] = y;
2881
2882 v2_add( desc->trigger_pos, endpoint, endpoint );
2883
2884 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour );
2885 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
2886 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
2887 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.18f );
2888 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.18f );
2889 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2890 }
2891 }
2892 }
2893 }
2894
2895 // WIRE ENDPOINTS
2896 // ========================================================================================================
2897
2898 SHADER_USE( shader_tile_colour );
2899 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2900 use_mesh( &world.shapes );
2901
2902 for( int y = 2; y < world.h-2; y ++ )
2903 {
2904 for( int x = 2; x < world.w-2; x ++ )
2905 {
2906 struct cell *cell = pcell((v2i){x,y});
2907
2908 if( cell->state & FLAG_CANAL )
2909 {
2910 if( cell->state & FLAG_IS_TRIGGER )
2911 {
2912 struct cell_description *desc = &cell_descriptions[ cell->config ];
2913
2914 int trigger_id = cell->links[0]?0:1;
2915
2916 int x2 = cell->links[trigger_id] % world.w;
2917 int y2 = (cell->links[trigger_id] - x2) / world.w;
2918
2919 v2f pts[2];
2920
2921 pts[0][0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
2922 pts[0][1] = (float)y2 + 0.25f;
2923
2924 pts[1][0] = x;
2925 pts[1][1] = y;
2926
2927 v2_add( desc->trigger_pos, pts[1], pts[1] );
2928
2929 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
2930 1, trigger_id? wire_right_colour: wire_left_colour );
2931
2932 for( int i = 0; i < 2; i ++ )
2933 {
2934 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
2935 pts[i][0],
2936 pts[i][1],
2937 0.08f
2938 );
2939 draw_mesh( filled_start, filled_count );
2940 }
2941 }
2942 }
2943 }
2944 }
2945
2946 // SUB SPLITTER DIRECTION
2947 // ========================================================================================================
2948
2949 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
2950
2951 for( int y = 2; y < world.h-2; y ++ )
2952 {
2953 for( int x = 2; x < world.w-2; x ++ )
2954 {
2955 struct cell *cell = pcell((v2i){x,y});
2956
2957 if( cell->state & FLAG_CANAL && cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
2958 {
2959 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), x, y, 1.0f );
2960 draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
2961 }
2962 }
2963 }
2964
2965 // I/O ARRAYS
2966 // ========================================================================================================
2967
2968 //glEnable(GL_BLEND);
2969
2970 for( int i = 0; i < arrlen( world.io ); i ++ )
2971 {
2972 struct cell_terminal *term = &world.io[ i ];
2973 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2974
2975 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2976
2977 for( int k = 0; k < term->run_count; k ++ )
2978 {
2979 float arr_base = is_input? 1.2f: -0.2f,
2980 run_offset = (is_input? 0.2f: -0.2f) * (float)k,
2981 y_position = is_input?
2982 (arr_base + (float)term->pos[1] + (float)(term->run_count-1)*0.2f) - run_offset:
2983 (float)term->pos[1] + arr_base + run_offset;
2984
2985 v4f bar_colour;
2986 int bar_draw = 0;
2987
2988 if( is_simulation_running() )
2989 {
2990 if( k == world.sim_run )
2991 {
2992 float a = fabsf(sinf( vg_time * 2.0f )) * 0.075f + 0.075f;
2993
2994 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, a }, bar_colour );
2995 }
2996 else
2997 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
2998
2999 bar_draw = 1;
3000 }
3001 else if( 1 || k & 0x1 )
3002 {
3003 if( k & 0x1 )
3004 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, 0.07f }, bar_colour );
3005 else
3006 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
3007
3008 bar_draw = 1;
3009 }
3010
3011 if( bar_draw )
3012 {
3013 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, bar_colour );
3014 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)term->pos[0], y_position - 0.1f, 1.0f );
3015 draw_mesh( 2, 2 );
3016 }
3017
3018 for( int j = 0; j < term->runs[k].condition_count; j ++ )
3019 {
3020 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3021 (float)term->pos[0] + 0.2f + 0.2f * (float)j,
3022 y_position,
3023 0.1f
3024 );
3025
3026 if( is_input )
3027 {
3028 char cc = term->runs[k].conditions[j];
3029 if( cc != ' ' )
3030 {
3031 colour_code_v3( cc, dot_colour );
3032 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3033
3034 // Draw filled if tick not passed, draw empty if empty
3035 if( (world.sim_frame > j && world.sim_run >= k) || world.sim_run > k )
3036 draw_mesh( empty_start, empty_count );
3037 else
3038 draw_mesh( filled_start, filled_count );
3039 }
3040 }
3041 else
3042 {
3043
3044 if( term->runs[k].recv_count > j )
3045 {
3046 colour_code_v3( term->runs[k].recieved[j], dot_colour );
3047 v3_muls( dot_colour, 0.8f, dot_colour );
3048 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3049
3050 draw_mesh( filled_start, filled_count );
3051 }
3052
3053 colour_code_v3( term->runs[k].conditions[j], dot_colour );
3054 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3055
3056 draw_mesh( empty_start, empty_count );
3057 }
3058 }
3059 }
3060 }
3061
3062 glDisable(GL_BLEND);
3063
3064 // Draw score
3065 /*
3066 float const score_bright = 1.25f;
3067 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
3068 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f );
3069
3070 use_mesh( &world.numbers );
3071 draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
3072 */
3073 }
3074
3075 void vg_ui(void) {}
3076
3077 #if STEAM_LEADERBOARDS
3078 void leaderboard_dispatch_score(void)
3079 {
3080 sw_upload_leaderboard_score(
3081 ui_data.upload_request.level->steam_leaderboard,
3082 k_ELeaderboardUploadScoreMethodKeepBest,
3083 ui_data.upload_request.score,
3084 NULL,
3085 0
3086 );
3087
3088 ui_data.upload_request.is_waiting = 0;
3089
3090 vg_success( "Dispatched leaderboard score\n" );
3091 }
3092
3093 void leaderboard_found( LeaderboardFindResult_t *pCallback )
3094 {
3095 if( !pCallback->m_bLeaderboardFound )
3096 {
3097 vg_error( "Leaderboard could not be found\n" );
3098 ui_data.steam_leaderboard = 0;
3099 }
3100 else
3101 {
3102 const char *recieved_name = sw_get_leaderboard_name( pCallback->m_hSteamLeaderboard );
3103
3104 // Update UI state and request entries if this callback found the current UI level
3105 if( ui_data.level_selected )
3106 {
3107 if( !strcmp( recieved_name, ui_data.level_selected->map_name ) )
3108 {
3109 sw_download_leaderboard_entries( pCallback->m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 0, 8 );
3110 ui_data.level_selected->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3111 }
3112 }
3113
3114 // Dispatch the waiting request if there was one
3115 if( ui_data.upload_request.is_waiting )
3116 {
3117 if( !strcmp( recieved_name, ui_data.upload_request.level->map_name ) )
3118 {
3119 ui_data.upload_request.level->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3120 leaderboard_dispatch_score();
3121 }
3122 }
3123 }
3124 }
3125
3126 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
3127 {
3128 // Update UI if this leaderboard matches what we currently have in view
3129 if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
3130 {
3131 vg_info( "Recieved %d entries\n", pCallback->m_cEntryCount );
3132 ui_data.leaderboard_count = VG_MIN( pCallback->m_cEntryCount, 8 );
3133
3134 u64_steamid local_player = sw_get_steamid();
3135
3136 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
3137 {
3138 LeaderboardEntry_t entry;
3139 sw_get_downloaded_entry( pCallback->m_hSteamLeaderboardEntries, i, &entry, NULL, 0 );
3140
3141 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
3142
3143 player->id = entry.m_steamIDUser.m_unAll64Bits;
3144 strncpy( player->player_name, sw_get_friend_persona_name( player->id ), vg_list_size( player->player_name )-1 );
3145 player->score = entry.m_nScore;
3146
3147 snprintf( player->score_text, vg_list_size(player->score_text), "%d", player->score );
3148 player->texture = sw_get_player_image( player->id );
3149
3150 if( player->texture == 0 )
3151 player->texture = tex_unkown.name;
3152
3153 player->is_local_player = local_player == player->id? 1: 0;
3154 }
3155
3156 if( ui_data.leaderboard_count )
3157 ui_data.leaderboard_show = 1;
3158 else
3159 ui_data.leaderboard_show = 0;
3160 }
3161 else vg_warn( "Downloaded leaderboard does not match requested!\n" );
3162 }
3163
3164 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
3165 {
3166 if( ui_data.upload_request.is_waiting )
3167 vg_warn( "You are uploading leaderboard entries too quickly!\n" );
3168
3169 ui_data.upload_request.level = cmp_level;
3170 ui_data.upload_request.score = score;
3171 ui_data.upload_request.is_waiting = 1;
3172
3173 // If leaderboard ID has been downloaded already then just immediately dispatch this
3174 if( cmp_level->steam_leaderboard )
3175 leaderboard_dispatch_score();
3176 else
3177 sw_find_leaderboard( cmp_level->map_name );
3178 }
3179 #endif
3180
3181 // CONSOLE COMMANDS
3182 // ===========================================================================================================
3183
3184 static int console_credits( int argc, char const *argv[] )
3185 {
3186 vg_info( "Aknowledgements:\n" );
3187 vg_info( " GLFW zlib/libpng glfw.org\n" );
3188 vg_info( " miniaudio MIT0 miniaud.io\n" );
3189 vg_info( " QOI MIT phoboslab.org\n" );
3190 vg_info( " STB library MIT nothings.org\n" );
3191 vg_info( " Weiholmir JustFredrik\n" );
3192 vg_info( " Ubuntu Regular ubuntu.com\n" );
3193 return 0;
3194 }
3195
3196 static int console_save_map( int argc, char const *argv[] )
3197 {
3198 if( !world.initialzed )
3199 {
3200 vg_error( "Tried to save uninitialized map!\n" );
3201 return 0;
3202 }
3203
3204 char map_path[ 256 ];
3205
3206 strcpy( map_path, "sav/" );
3207 strcat( map_path, world.map_name );
3208 strcat( map_path, ".map" );
3209
3210 FILE *test_writer = fopen( map_path, "wb" );
3211 if( test_writer )
3212 {
3213 vg_info( "Saving map to '%s'\n", map_path );
3214 map_serialize( test_writer );
3215
3216 fclose( test_writer );
3217 return 1;
3218 }
3219 else
3220 {
3221 vg_error( "Unable to open stream for writing\n" );
3222 return 0;
3223 }
3224 }
3225
3226 static int console_load_map( int argc, char const *argv[] )
3227 {
3228 char map_path[ 256 ];
3229
3230 if( argc >= 1 )
3231 {
3232 // try from saves
3233 strcpy( map_path, "sav/" );
3234 strcat( map_path, argv[0] );
3235 strcat( map_path, ".map" );
3236
3237 char *text_source = vg_textasset_read( map_path );
3238
3239 if( !text_source )
3240 {
3241 strcpy( map_path, "maps/" );
3242 strcat( map_path, argv[0] );
3243 strcat( map_path, ".map" );
3244
3245 text_source = vg_textasset_read( map_path );
3246 }
3247
3248 if( text_source )
3249 {
3250 vg_info( "Loading map: '%s'\n", map_path );
3251 world.pCmpLevel = NULL;
3252
3253 if( !map_load( text_source, argv[0] ) )
3254 {
3255 free( text_source );
3256 return 0;
3257 }
3258
3259 free( text_source );
3260 return 1;
3261 }
3262 else
3263 {
3264 vg_error( "Missing maps '%s'\n", argv[0] );
3265 return 0;
3266 }
3267 }
3268 else
3269 {
3270 vg_error( "Missing argument <map_path>\n" );
3271 return 0;
3272 }
3273 }
3274
3275 static int console_changelevel( int argc, char const *argv[] )
3276 {
3277 if( argc >= 1 )
3278 {
3279 // Save current level
3280 console_save_map( 0, NULL );
3281 if( console_load_map( argc, argv ) )
3282 {
3283 simulation_stop();
3284 return 1;
3285 }
3286 }
3287 else
3288 {
3289 vg_error( "Missing argument <map_path>\n" );
3290 }
3291
3292 return 0;
3293 }
3294
3295 // START UP / SHUTDOWN
3296 // ===========================================================================================================
3297
3298 #define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
3299 X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY
3300
3301 void vg_start(void)
3302 {
3303 // Steamworks callbacks
3304 #ifdef STEAM_LEADERBOARDS
3305 sw_leaderboard_found = &leaderboard_found;
3306 sw_leaderboard_downloaded = &leaderboard_downloaded;
3307 #endif
3308
3309 vg_function_push( (struct vg_cmd){
3310 .name = "_map_write",
3311 .function = console_save_map
3312 });
3313
3314 vg_function_push( (struct vg_cmd){
3315 .name = "_map_load",
3316 .function = console_load_map
3317 });
3318
3319 vg_function_push( (struct vg_cmd){
3320 .name = "map",
3321 .function = console_changelevel
3322 });
3323
3324 vg_function_push( (struct vg_cmd){
3325 .name = "credits",
3326 .function = console_credits
3327 });
3328
3329 vg_convar_push( (struct vg_convar){
3330 .name = "colours",
3331 .data = &colour_set_id,
3332 .data_type = k_convar_dtype_i32,
3333 .opt_i32 = { .min = 0, .max = 2, .clamp = 1 }
3334 });
3335
3336 // Combined quad, long quad / empty circle / filled circle mesh
3337 {
3338 float combined_mesh[6*6 + 32*6*3] = {
3339 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
3340 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
3341
3342 0.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.2f,
3343 0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f,
3344
3345 TRANSFORM_TRI_2D( 0.15f,0.05f,0.4f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f ),
3346 TRANSFORM_TRI_2D( 0.15f,0.80f,0.4f, 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 1.0f )
3347 };
3348
3349 float *circle_mesh = combined_mesh + 6*6;
3350 int const res = 32;
3351
3352 for( int i = 0; i < res; i ++ )
3353 {
3354 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
3355 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
3356
3357 circle_mesh[ i*6+0 ] = 0.0f;
3358 circle_mesh[ i*6+1 ] = 0.0f;
3359
3360 v2_copy( v0, circle_mesh + 32*6 + i*12 );
3361 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
3362 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
3363
3364 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
3365 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
3366 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
3367
3368 v2_copy( v0, circle_mesh + i*6+4 );
3369 v2_copy( v1, circle_mesh + i*6+2 );
3370 v2_copy( v0, circle_mesh+i*6+4 );
3371 v2_copy( v1, circle_mesh+i*6+2 );
3372 }
3373
3374 init_mesh( &world.shapes, combined_mesh, vg_list_size( combined_mesh ) );
3375 }
3376
3377 // Create wire mesh
3378 {
3379 int const num_segments = 64;
3380
3381 struct mesh_wire *mw = &world.wire;
3382
3383 v2f wire_points[ num_segments * 2 ];
3384 u16 wire_indices[ 6*(num_segments-1) ];
3385
3386 for( int i = 0; i < num_segments; i ++ )
3387 {
3388 float l = (float)i / (float)(num_segments-1);
3389
3390 v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
3391 v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
3392
3393 if( i < num_segments-1 )
3394 {
3395 wire_indices[ i*6+0 ] = i*2 + 0;
3396 wire_indices[ i*6+1 ] = i*2 + 1;
3397 wire_indices[ i*6+2 ] = i*2 + 3;
3398 wire_indices[ i*6+3 ] = i*2 + 0;
3399 wire_indices[ i*6+4 ] = i*2 + 3;
3400 wire_indices[ i*6+5 ] = i*2 + 2;
3401 }
3402 }
3403
3404 glGenVertexArrays( 1, &mw->vao );
3405 glGenBuffers( 1, &mw->vbo );
3406 glGenBuffers( 1, &mw->ebo );
3407 glBindVertexArray( mw->vao );
3408
3409 glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
3410
3411 glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
3412 glBindVertexArray( mw->vao );
3413
3414 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
3415 glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
3416
3417 // XY
3418 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
3419 glEnableVertexAttribArray( 0 );
3420
3421 VG_CHECK_GL();
3422
3423 mw->em = vg_list_size( wire_indices );
3424 }
3425
3426 // Create info data texture
3427 {
3428 glGenTextures( 1, &world.background_data );
3429 glBindTexture( GL_TEXTURE_2D, world.background_data );
3430 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
3431 vg_tex2d_nearest();
3432 }
3433
3434 // Create random smaples texture
3435 {
3436 u8 *data = malloc(512*512*2);
3437 for( int i = 0; i < 512*512*2; i ++ )
3438 data[ i ] = rand()/(RAND_MAX/255);
3439
3440 glGenTextures( 1, &world.random_samples );
3441 glBindTexture( GL_TEXTURE_2D, world.random_samples );
3442 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
3443 vg_tex2d_linear();
3444 vg_tex2d_repeat();
3445
3446 free( data );
3447 }
3448
3449 resource_load_main();
3450
3451 // Create text buffers
3452 {
3453 // Work out the counts for each 'segment'
3454 u32 desc_max_size = 0, title_max_size = 0,
3455 score_max_size = 10,
3456 time_max_size = 10,
3457
3458 size_level_texts = 6*9*7
3459 ;
3460
3461 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
3462 {
3463 struct career_level_pack *set = &career_packs[i];
3464 for( int j = 0; j < set->count; j ++ )
3465 {
3466 struct cmp_level *lvl = &set->pack[j];
3467
3468 desc_max_size = VG_MAX( desc_max_size, strlen( lvl->description ) );
3469 title_max_size = VG_MAX( title_max_size, strlen( lvl->title ) );
3470 }
3471 }
3472
3473 // Full buffer
3474 u32 total_characters =
3475 title_max_size +
3476 desc_max_size +
3477 score_max_size +
3478 time_max_size +
3479 size_level_texts;
3480
3481 u32 total_faces = total_characters * 2,
3482 total_vertices = total_characters * 4,
3483 total_indices = total_faces * 3;
3484
3485 // Working buffer
3486 u32 work_buffer_total_chars =
3487 VG_MAX( 7, VG_MAX( VG_MAX( desc_max_size, title_max_size ), VG_MAX( score_max_size, time_max_size ) ) );
3488
3489 u32 total_work_faces = work_buffer_total_chars * 2,
3490 total_work_vertices = work_buffer_total_chars * 4,
3491 total_work_indices = total_work_faces * 3;
3492
3493 text_buffers.title_count = 0;
3494 text_buffers.desc_count = 0;
3495 text_buffers.score_count = 0;
3496 text_buffers.time_count = 0;
3497 text_buffers.grid_count = size_level_texts;
3498
3499 // Calculate offsets
3500 text_buffers.title_start = 0;
3501 text_buffers.desc_start = text_buffers.title_start + title_max_size;
3502 text_buffers.score_start = text_buffers.desc_start + desc_max_size;
3503 text_buffers.time_start = text_buffers.score_start + score_max_size;
3504 text_buffers.grid_start = text_buffers.time_start + time_max_size;
3505
3506 // Opengl
3507 glGenVertexArrays(1, &text_buffers.vao);
3508 glGenBuffers( 1, &text_buffers.vbo );
3509 glGenBuffers( 1, &text_buffers.ebo );
3510 glBindVertexArray( text_buffers.vao );
3511
3512 glBindBuffer( GL_ARRAY_BUFFER, text_buffers.vbo );
3513 glBufferData( GL_ARRAY_BUFFER, total_vertices * sizeof( struct vector_glyph_vert ), NULL, GL_DYNAMIC_DRAW );
3514
3515 glBindVertexArray( text_buffers.vao );
3516
3517 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, text_buffers.ebo );
3518 glBufferData( GL_ELEMENT_ARRAY_BUFFER, total_indices * sizeof( u16 ), NULL, GL_DYNAMIC_DRAW );
3519
3520 u32 const stride = sizeof( struct vector_glyph_vert );
3521
3522 // XY
3523 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, stride, (void *)offsetof( struct vector_glyph_vert, co ) );
3524 glEnableVertexAttribArray( 0 );
3525
3526 // UV
3527 glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, stride, (void *)offsetof( struct vector_glyph_vert, uv ) );
3528 glEnableVertexAttribArray( 1 );
3529
3530 // COLOUR
3531 glVertexAttribPointer( 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void *)offsetof( struct vector_glyph_vert, colour ) );
3532 glEnableVertexAttribArray( 2 );
3533
3534 // Offline memory
3535 text_buffers.buffer = (struct vector_glyph_vert *)malloc( total_work_vertices * sizeof(struct vector_glyph_vert) );
3536 text_buffers.indices = (u16*)malloc( total_work_indices * sizeof(u16) );
3537
3538 char label[8];
3539 for( int i = 1; i < 7; i ++ )
3540 label[i] = ' ';
3541 label[7] = 0x00;
3542
3543 // Reset grid
3544 for( int x = 0; x < 6; x ++ )
3545 {
3546 for( int y = 0; y < 9; y ++ )
3547 {
3548 label[0] = ' ';
3549
3550 if( x == 0 )
3551 {
3552 if( y != 8 )
3553 label[0] = 'A' + y;
3554 }
3555 else if( y == 8 )
3556 {
3557 label[0] = '0' + x;
3558 }
3559
3560 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 );
3561 }
3562 }
3563 }
3564
3565 // Restore gamestate
3566 career_local_data_init();
3567 career_load();
3568 }
3569
3570 void vg_free(void)
3571 {
3572 #ifdef VG_STEAM
3573 sw_free_opengl();
3574 #endif
3575
3576 console_save_map( 0, NULL );
3577 career_serialize();
3578
3579 glDeleteVertexArrays( 1, &text_buffers.vao );
3580 glDeleteBuffers( 1, &text_buffers.vbo );
3581 glDeleteBuffers( 1, &text_buffers.ebo );
3582
3583 free( text_buffers.buffer );
3584 free( text_buffers.indices );
3585
3586 resource_free_main();
3587
3588 glDeleteTextures( 1, &world.background_data );
3589 glDeleteTextures( 1, &world.random_samples );
3590
3591 glDeleteVertexArrays( 1, &world.wire.vao );
3592 glDeleteBuffers( 1, &world.wire.vbo );
3593 glDeleteBuffers( 1, &world.wire.ebo );
3594
3595 free_mesh( &world.shapes );
3596
3597 map_free();
3598 }
3599
3600 int main( int argc, char *argv[] )
3601 {
3602 vg_init( argc, argv, "Marble Computing" );
3603 return 0;
3604 }