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