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