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