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