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