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