vg2 port: build script and resource loading
[fishladder.git] / fishladder.c
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
2
3 #define MARBLE_COMP_VERSION 4
4 #if 0
5 #define VG_CAPTURE_MODE
6 #define VG_STEAM
7 #endif
8 #define VG_STEAM_APPID 1218140U
9 #define VG_FRAMEBUFFER_RESIZE 1
10 #include "vg/vg.h"
11
12 enum world_button_mode
13 {
14 k_world_button_mode_once,
15 k_world_button_mode_toggle
16 };
17
18 struct world_button
19 {
20 v2i position;
21
22 float light_target, light, extra_light;
23 int state;
24
25 enum world_button_mode mode;
26 };
27
28 enum world_button_status
29 {
30 k_world_button_on_enable,
31 k_world_button_on_disable
32 };
33
34 #include "fishladder_resources.h"
35
36 #if 0
37 #define STEAM_LEADERBOARDS
38 #endif
39
40 enum cell_type
41 {
42 k_cell_type_stub = 0,
43 k_cell_type_ramp_right = 3,
44 k_cell_type_ramp_left = 6,
45 k_cell_type_split = 7,
46 k_cell_type_merge = 13,
47 k_cell_type_con_r = 1,
48 k_cell_type_con_u = 2,
49 k_cell_type_con_l = 4,
50 k_cell_type_con_d = 8
51 };
52
53 enum e_fish_state
54 {
55 k_fish_state_soon_dead = -1,
56 k_fish_state_dead = 0,
57 k_fish_state_alive,
58 k_fish_state_bg,
59 k_fish_state_soon_alive
60 };
61
62 enum e_world_button
63 {
64 k_world_button_none = -1,
65 k_world_button_sim = 0,
66 k_world_button_pause = 1,
67 k_world_button_speedy = 2,
68 k_world_button_settings = 3
69 };
70
71 enum e_game_state
72 {
73 k_game_state_main,
74 k_game_state_settings,
75 k_game_state_update
76 };
77
78 #define FLAG_CANAL 0x1
79 #define FLAG_IS_TRIGGER 0x2
80 #define FLAG_RESERVED0 0x4
81 #define FLAG_RESERVED1 0x8
82
83 #define FLAG_4B_GROUP (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1)
84
85 #define FLAG_INPUT 0x10
86 #define FLAG_OUTPUT 0x20
87 #define FLAG_WALL 0x40
88 #define FLAG_EMITTER 0x80
89
90 #define FLAG_FLIP_FLOP 0x100
91 #define FLAG_TRIGGERED 0x200
92 #define FLAG_FLIP_ROTATING 0x400
93 #define FLAG_TARGETED 0x800
94
95 #define FLAG_INPUT_NICE 0x1000
96
97 /*
98 0000 0 | 0001 1 | 0010 2 | 0011 3
99 | | | | |
100 X | X= | X | X=
101 | | |
102 0100 4 | 0101 5 | 0110 6 | 0111 7
103 | | | | |
104 =X | =X= | =X | =X=
105 | | |
106 1000 8 | 1001 9 | 1010 10 | 1011 11
107 | | | | |
108 X | X= | X | X=
109 | | | | | | |
110 1100 12 | 1101 13 | 1110 14 | 1111 15
111 | | | | |
112 =X | =X= | =X | =X=
113 | | | | | | |
114 */
115
116 static struct cell_description
117 {
118 v2i start;
119 v2i end;
120
121 int is_special;
122 int is_linear;
123
124 v2f trigger_pos;
125 enum sprites_auto_combine_index trigger_sprite;
126 }
127 cell_descriptions[] =
128 {
129 /* 0-3 */
130 { .trigger_pos = { 0.5f, 0.25f }, .trigger_sprite = k_sprite_brk_d },
131 { .start = { 1, 0 }, .end = { -1, 0 }, .trigger_pos = { 0.5f, 0.25f },
132 .trigger_sprite = k_sprite_brk_d },
133 { .start = { 0, 1 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f },
134 .trigger_sprite = k_sprite_brk_l },
135 { .start = { 0, 1 }, .end = { 1, 0 }, .trigger_pos = { 0.25f, 0.5f },
136 .trigger_sprite = k_sprite_brk_l },
137 /* 4-7 */
138 { .start = { -1, 0 }, .end = { 1, 0 }, .trigger_pos = { 0.5f, 0.25f },
139 .trigger_sprite = k_sprite_brk_d },
140 { .start = { -1, 0 }, .end = { 1, 0 }, .trigger_pos = { 0.5f, 0.25f },
141 .trigger_sprite = k_sprite_brk_d, .is_linear = 1 },
142 { .start = { 0, 1 }, .end = { -1, 0 }, .trigger_pos = { 0.5f, 0.25f },
143 .trigger_sprite = k_sprite_brk_d },
144 { .start = { 0, 1 }, .is_special = 1 },
145 /* 8-11 */
146 { .start = { 0, -1 }, .end = { 0, 1 }, .trigger_pos = { 0.25f, 0.5f },
147 .trigger_sprite = k_sprite_brk_l },
148 { .start = { 1, 0 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f },
149 .trigger_sprite = k_sprite_brk_l },
150 { .start = { 0, 1 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f },
151 .trigger_sprite = k_sprite_brk_l, .is_linear = 1 },
152 { },
153 /* 12-15 */
154 { .start = { -1, 0 }, .end = { 0, -1 }, .trigger_pos = { 0.5f, 0.75f },
155 .trigger_sprite = k_sprite_brk_u },
156 { .end = { 0, -1 }, .is_special = 1, .trigger_pos = { 0.5f, 0.75f },
157 .trigger_sprite = k_sprite_brk_u },
158 { },
159 { }
160 };
161
162 v2f const curve_3[] = {{0.5f,1.0f},{0.5f,0.625f},{0.625f,0.5f},{1.0f,0.5f}};
163 v2f const curve_6[] = {{0.5f,1.0f},{0.5f,0.625f},{0.375f,0.5f},{0.0f,0.5f}};
164 v2f const curve_9[] = {{1.0f,0.5f},{0.625f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
165 v2f const curve_12[]= {{0.0f,0.5f},{0.375f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
166
167 v2f const curve_1[] = {{1.0f,0.5f},{0.8f,0.5f},{0.3f,0.5f},{0.2f,0.5f}};
168 v2f const curve_4[] = {{0.0f,0.5f},{0.3f,0.5f},{0.5f,0.5f},{0.8f,0.5f}};
169 v2f const curve_2[] = {{0.5f,1.0f},{0.5f,0.8f},{0.5f,0.3f},{0.5f,0.2f}};
170 v2f const curve_8[] = {{0.5f,0.0f},{0.5f,0.3f},{0.5f,0.5f},{0.5f,0.8f}};
171
172 v2f const curve_7[] =
173 {{0.5f,0.8438f},{0.875f,0.8438f},{0.625f,0.5f},{1.0f,0.5f}};
174 v2f const curve_7_1[] =
175 {{0.5f,0.8438f},{1.0f-0.875f,0.8438f},{1.0-0.625f,0.5f},{0.0f,0.5f}};
176
177 float const curve_7_linear_section = 0.1562f;
178
179 struct mesh
180 {
181 GLuint vao, vbo;
182 u32 elements;
183 };
184
185 #define EFFECT_BUFFER_RATIO 4
186
187 static struct world
188 {
189 /* Things that are 'static', aka, initialized once */
190 struct
191 {
192 struct world_button buttons[4];
193 float zoom;
194 enum e_game_state state;
195
196 struct cmp_level *lvl_to_load;
197 float lvl_load_time;
198
199 float world_transition;
200 ui_ctx world_text;
201
202 GLuint framebuffer,
203 colourbuffer,
204 bloomframebuffer[2], /* Quater res */
205 bloomcolourbuffer[2];
206 }
207 st;
208
209 struct cell
210 {
211 u16 state;
212 u16 links[2];
213 u8 config;
214 i8 emit[2];
215
216 v3f glow[2];
217 }
218 *data;
219
220 struct render_cmd
221 {
222 struct cell *ptr;
223 v2i pos;
224 }
225 *cmd_buf_tiles, *cmd_buf_specials;
226
227 u32 tile_count, tile_special_count, max_commands;
228
229 int initialzed;
230 int sim_run, max_runs;
231
232 int sim_frame, sim_target;
233 float sim_internal_time,
234 sim_internal_delta,
235 sim_internal_ref,
236 sim_delta_ref,
237 sim_delta_speed,
238 frame_lerp,
239 pause_offset_target;
240
241 struct cell_terminal
242 {
243 struct terminal_run
244 {
245 i8 steps[8];
246 i8 recieved[8];
247
248 int step_count, recv_count;
249 }
250 runs[8];
251
252 int run_count;
253 v2i pos;
254 }
255 *io;
256
257 int w, h;
258
259 struct mesh shapes;
260 struct mesh_wire
261 {
262 GLuint vao, vbo, ebo;
263 u32 em;
264 }
265 wire;
266
267 GLuint background_data;
268 GLuint random_samples;
269
270 int selected, tile_x, tile_y;
271 v2f tile_pos;
272
273 struct fish
274 {
275 v2i pos;
276 v2i dir;
277 enum e_fish_state state;
278 i8 colour;
279 int flow_reversed;
280 float death_time;
281 v2f physics_v;
282 v2f physics_co;
283 }
284 fishes[64];
285
286 int num_fishes;
287
288 char map_name[64];
289 struct cmp_level *pCmpLevel;
290
291 u32 score;
292 u32 completed;
293 u32 time;
294
295 u16 id_drag_from;
296 v2f drag_from_co;
297 v2f drag_to_co;
298 }
299 world =
300 {
301 .st =
302 {
303 .buttons = { { .mode = k_world_button_mode_toggle },
304 { .mode = k_world_button_mode_toggle },
305 { .mode = k_world_button_mode_toggle },
306 { .mode = k_world_button_mode_toggle } }
307 },
308 .selected = -1
309 };
310
311 static void colour_code_v3( i8 cc, v3f target );
312 static int hash21i( v2i p, u32 umod );
313
314 /*
315 * Mesh functions
316 */
317 static void init_mesh( struct mesh *m, float const *tris, u32 length );
318 static void free_mesh( struct mesh *m );
319 static void use_mesh( struct mesh *m );
320 static void draw_mesh( int const start, int const count );
321
322 /*
323 * World buttons
324 */
325 static void level_selection_buttons(void);
326
327 /*
328 * Map/world interface
329 */
330 static void map_free(void);
331 static void io_reset(void);
332 static struct cell *pcell( v2i pos );
333 static void lcell( int id, v2i pos );
334 static void map_reclassify( v2i start, v2i end, int update_texbuffer );
335 static void gen_level_text(void);
336 static int map_load( const char *str, const char *name );
337 static void map_serialize( FILE *stream );
338
339 /*
340 * Career
341 */
342 static void career_serialize(void);
343 static void career_unlock_level( struct cmp_level *lvl );
344 static void career_unlock_level( struct cmp_level *lvl );
345 static void career_pass_level( struct cmp_level *lvl, int score, int upload );
346 static void career_reset_level( struct cmp_level *lvl );
347 static void career_load(void);
348 static void clear_animation_flags(void);
349
350 /*
351 * Gameplay main
352 */
353 static void simulation_stop(void);
354 static void simulation_start(void);
355 static int world_check_pos_ok( v2i co, int dist );
356 static int cell_interactive( v2i co );
357
358 static void render_tiles( v4f const regular_colour,
359 v4f const selected_colour, int with_glow );
360 static void render_tile_block( v2i start, v2i end, v4f const regular_colour,
361 v4f const selected_colour );
362
363 #ifdef STEAM_LEADERBOARDS
364 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
365 void leaderboard_dispatch_score(void);
366 void leaderboard_found( LeaderboardFindResult_t *pCallback );
367 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback );
368 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
369 #endif
370
371 static int console_credits( int argc, char const *argv[] );
372 static int console_save_map( int argc, char const *argv[] );
373 static int console_load_map( int argc, char const *argv[] );
374 static int console_changelevel( int argc, char const *argv[] );
375
376 void vg_render(void);
377 void vg_ui(void);
378 void vg_update(void);
379 void vg_start(void);
380 void vg_free(void);
381 int main( int argc, char *argv[] );
382
383 /*
384 * Globals -- runtime
385 */
386 m3x3f m_projection;
387 m3x3f m_view;
388 m3x3f m_mdl;
389
390 static int colour_set_id = 0;
391 static int world_theme_id = 0;
392 static int enable_bloom = 1;
393 static int enable_vignette = 1;
394 static float music_volume = 1.0f;
395
396 static void music_volume_update(void)
397 {
398 sfx_vol_fset( &audio_volume_music, music_volume );
399 }
400
401 static v3f colour_sets[][4] =
402 {
403 { { 1.0f, 0.9f, 0.3f },
404 { 0.4f, 0.8f, 1.00f },
405 { 0.2f, 0.9f, 0.14f },
406 { 0.882f, 0.204f, 0.922f }
407 },
408 { { 1.0f, 0.9f, 0.3f },
409 { 0.4f, 0.8f, 1.00f },
410 { 0.85f, 0.85f, 0.85f },
411 { 0.2f, 0.2f, 0.2f }
412 },
413 { { 1.0f, 0.9f, 0.3f },
414 { 0.827f, 0.373f, 0.718f },
415 { 0.0f, 0.353f, 0.71f },
416 { 0.863f, 0.196f, 0.125f }
417 },
418 };
419
420 static struct world_theme
421 {
422 const char *name;
423 v3f col_shadow;
424
425 vg_tex2d *tex_tiles;
426 }
427 world_themes[] =
428 {
429 {
430 "Minimal",
431 { 0.6f, 0.6f, 0.6f },
432 &tex_tiles_min
433 },
434 {
435 "Wood",
436 { 0.89f, 0.8f, 0.7f },
437 &tex_tiles_wood
438 },
439 {
440 "Lab",
441 { 0.7f, 0.7f, 0.7f },
442 &tex_tiles_lab
443 }
444 };
445
446 static void colour_code_v3( i8 cc, v3f target )
447 {
448 if( (cc >= 0) && (cc < vg_list_size( colour_sets[0] )) )
449 {
450 v3_copy( colour_sets[colour_set_id][ cc ], target );
451 return;
452 }
453
454 vg_error( "Invalid colour code used '%d'\n", (int)cc );
455 v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
456 }
457
458 static int hash21i( v2i p, u32 umod )
459 {
460 static const int random_noise[] = {
461 0x46,0xD5,0xB8,0xD3,0xF2,0xE5,0xCC,0x07,0xD0,0xB3,0x7A,0xA2,0xC3,0xDA,0xDC,0x7F,
462 0xE0,0xB7,0x42,0xA0,0xBF,0x41,0x92,0x32,0x6F,0x0D,0x45,0xC7,0x54,0xDB,0x30,0xC2,
463 0xD5,0xDA,0x55,0x09,0xDE,0x74,0x48,0x20,0xE1,0x24,0x5C,0x4D,0x6F,0x36,0xD8,0xE9,
464 0x8D,0x8F,0x54,0x99,0x98,0x51,0xFE,0xDB,0x26,0x04,0x65,0x57,0x56,0xF3,0x53,0x30,
465 0x3D,0x16,0xC0,0xB6,0xF2,0x47,0xCF,0x62,0xB0,0x6C,0x8F,0x4F,0x8C,0x4C,0x17,0xF0,
466 0x19,0x7E,0x2D,0x81,0x8D,0xFB,0x10,0xD3,0x49,0x50,0x60,0xFD,0x38,0x15,0x3B,0xEE,
467 0x05,0xC1,0xCF,0x62,0x97,0x75,0xDF,0x4E,0x4D,0x89,0x5E,0x88,0x5C,0x30,0x8C,0x54,
468 0x1D,0x39,0x41,0xEA,0xA2,0x63,0x12,0x1B,0x8E,0x35,0x22,0x9B,0x98,0xA3,0x7F,0x80,
469 0xD6,0x27,0x94,0x66,0xB5,0x1D,0x7E,0xDF,0x96,0x28,0x38,0x3A,0xA0,0xE8,0x71,0x09,
470 0x62,0x5E,0x9D,0x53,0x58,0x1B,0x7D,0x0D,0x2D,0x99,0x77,0x83,0xC3,0x89,0xC2,0xA2,
471 0xA7,0x1D,0x78,0x80,0x37,0xC1,0x87,0xFF,0x65,0xBF,0x2C,0xF1,0xE5,0xB3,0x09,0xE0,
472 0x25,0x92,0x83,0x0F,0x8A,0x57,0x3C,0x0B,0xC6,0xBC,0x44,0x16,0xE3,0xCE,0xC3,0x0D,
473 0x69,0xD3,0xC6,0x99,0xB8,0x46,0x44,0xC4,0xF3,0x1E,0xBF,0xF5,0xB4,0xDB,0xFB,0x93,
474 0xA1,0x7B,0xC9,0x08,0x77,0x22,0xE5,0x02,0xEF,0x9E,0x90,0x94,0x8A,0xA6,0x3D,0x7E,
475 0xA2,0xA0,0x10,0x82,0x47,0x5C,0xAA,0xF8,0x2F,0x0D,0x9F,0x76,0xDA,0x99,0x0F,0xCB,
476 0xE2,0x02,0x0C,0x75,0xCA,0x35,0x29,0xA6,0x49,0x83,0x6D,0x91,0xB4,0xEC,0x31,0x69,
477 0xBA,0x13,0xF3,0xC7,0x21,0x06,0xC8,0x79,0xEF,0xB1,0x9C,0x6A,0xEE,0x64,0x9A,0xDC,
478 0x1E,0xC6,0x18,0x93,0xA9,0x7E,0x89,0x7D,0x96,0xE5,0x44,0xB8,0x00,0x15,0xAF,0x8C,
479 0x78,0x8F,0xA8,0x05,0xA7,0x07,0x25,0x9A,0xC8,0x5D,0x90,0x1A,0x41,0x53,0x30,0xD3,
480 0x24,0x33,0x71,0xB4,0x50,0x6E,0xE4,0xEA,0x0D,0x2B,0x6D,0xF5,0x17,0x08,0x74,0x49,
481 0x71,0xC2,0xAC,0xF7,0xDC,0xB2,0x7E,0xCC,0xB6,0x1B,0xB8,0xA9,0x52,0xCF,0x6B,0x51,
482 0xD2,0x4E,0xC9,0x43,0xEE,0x2E,0x92,0x24,0xBB,0x47,0x4D,0x0C,0x3E,0x21,0x53,0x19,
483 0xD4,0x82,0xE2,0xC6,0x93,0x85,0x0A,0xF8,0xFA,0x04,0x07,0xD3,0x1D,0xEC,0x03,0x66,
484 0xFD,0xB1,0xFB,0x8F,0xC5,0xDE,0xE8,0x29,0xDF,0x23,0x09,0x9D,0x7C,0x43,0x3D,0x4D,
485 0x89,0xB9,0x6F,0xB4,0x6B,0x4A,0x51,0xC3,0x94,0xF4,0x7C,0x5E,0x19,0x87,0x79,0xC1,
486 0x80,0x0C,0x45,0x12,0xEC,0x95,0xF3,0x31,0x68,0x42,0xE1,0x06,0x57,0x0E,0xA7,0xFB,
487 0x78,0x96,0x87,0x23,0xA5,0x20,0x7A,0x09,0x3A,0x45,0xE6,0xD9,0x5E,0x6A,0xD6,0xAA,
488 0x29,0x50,0x92,0x4E,0xD0,0xB5,0x91,0xC2,0x9A,0xCF,0x07,0xFE,0xB2,0x15,0xEB,0xE4,
489 0x84,0x40,0x14,0x47,0xFA,0x93,0xB9,0x06,0x69,0xDB,0xBD,0x4E,0xEA,0x52,0x9B,0xDE,
490 0x5B,0x50,0x36,0xAB,0xB3,0x1F,0xD2,0xCD,0x9C,0x13,0x07,0x7E,0x8B,0xED,0x72,0x62,
491 0x74,0x77,0x3B,0x88,0xAC,0x5B,0x6A,0xBC,0xDA,0x99,0xE8,0x24,0x90,0x5A,0xCA,0x8D,
492 0x5C,0x2B,0xF8,0xF1,0xE1,0x1D,0x94,0x11,0xEA,0xCC,0x02,0x09,0x1E,0xA2,0x48,0x67,
493 0x87,0x5A,0x7E,0xC6,0xCC,0xA3,0xFB,0xC5,0x36,0xEB,0x5C,0xE1,0xAF,0x1E,0xBE,0xE7,
494 0xD8,0x8F,0x70,0xAE,0x42,0x05,0xF5,0xCD,0x2D,0xA2,0xB0,0xFD,0xEF,0x65,0x2C,0x22,
495 0xCB,0x8C,0x8B,0xAA,0x3D,0x86,0xE2,0xCD,0xBE,0xC3,0x42,0x38,0xE3,0x9C,0x08,0xB5,
496 0xAE,0xBD,0x54,0x73,0x83,0x70,0x24,0x47,0xCA,0x4C,0x04,0xC4,0xE0,0x1D,0x40,0xED,
497 0xF4,0x2B,0x50,0x8E,0x97,0xB3,0xF0,0xA6,0x76,0xDB,0x49,0x30,0xE5,0xD9,0x71,0x07,
498 0xB2,0xF1,0x0F,0xD6,0x77,0xAA,0x72,0xC0,0xAF,0x66,0xD8,0x40,0xC6,0x08,0x19,0x8C,
499 0xD9,0x8F,0x5A,0x75,0xAC,0xBE,0xC2,0x40,0x5B,0xBD,0x0D,0x1D,0x00,0xAF,0x26,0x5E,
500 0x78,0x43,0xAA,0xC6,0x4F,0xF3,0xD8,0xE2,0x7F,0x0C,0x1E,0x77,0x4D,0x35,0x96,0x23,
501 0x32,0x44,0x03,0x8D,0x92,0xE7,0xFD,0x48,0x07,0xD0,0x58,0xFC,0x6D,0xC9,0x91,0x33,
502 0xF0,0x23,0x45,0xA4,0x29,0xB9,0xF5,0xB0,0x68,0x8F,0x7B,0x59,0x15,0x8E,0xA6,0x66,
503 0x15,0xA0,0x76,0x9B,0x69,0xCB,0x38,0xA5,0xF4,0xB4,0x6B,0xDC,0x1F,0xAB,0xAE,0x12,
504 0x77,0xC0,0x8C,0x4A,0x03,0xB9,0x73,0xD3,0x6D,0x52,0xC5,0xF5,0x6E,0x4E,0x4B,0xA3,
505 0x24,0x02,0x58,0xEE,0x5F,0xF9,0xD6,0xD0,0x1D,0xBC,0xF4,0xB8,0x4F,0xFD,0x4B,0x2D,
506 0x34,0x77,0x46,0xE5,0xD4,0x33,0x7B,0x9C,0x35,0xCD,0xB0,0x5D,0x06,0x39,0x99,0xEB,
507 0x0C,0xD0,0x0F,0xF7,0x92,0xB5,0x58,0x5B,0x5E,0x79,0x12,0xF4,0x05,0xF6,0x21,0x07,
508 0x0B,0x49,0x1A,0xFB,0xD4,0x98,0xC4,0xEF,0x7A,0xD6,0xCA,0xA1,0xDA,0xB3,0x51,0x00,
509 0x76,0xEC,0x08,0x48,0x40,0x35,0xD7,0x94,0xBE,0xF5,0x7B,0xA4,0x20,0x81,0x5F,0x82,
510 0xF3,0x6F,0x96,0x24,0x98,0xB6,0x49,0x18,0xC8,0xC5,0x8C,0xD2,0x38,0x7F,0xC4,0xF6,
511 0xAA,0x87,0xDC,0x73,0x5B,0xA1,0xAF,0xE5,0x3D,0x37,0x6B,0x85,0xED,0x38,0x62,0x7D,
512 0x57,0xBD,0xCF,0xB5,0x1B,0xA8,0xBB,0x32,0x33,0xD3,0x34,0x5A,0xC1,0x5D,0xFB,0x28,
513 0x6E,0xE1,0x67,0x51,0xBB,0x31,0x92,0x83,0xAC,0xAA,0x72,0x52,0xFD,0x13,0x4F,0x73,
514 0xD3,0xF0,0x5E,0xFC,0xBA,0xB1,0x3C,0x7B,0x08,0x76,0x03,0x38,0x1E,0xD1,0xCC,0x33,
515 0xA3,0x1E,0xFC,0xE0,0x82,0x30,0x27,0x93,0x71,0x35,0x75,0x77,0xBA,0x78,0x10,0x33,
516 0xCD,0xAB,0xCF,0x8E,0xAD,0xF9,0x32,0xC9,0x15,0x9F,0xD6,0x6D,0xA8,0xAE,0xB1,0x3F,
517 0x90,0xEB,0xD4,0xF9,0x31,0x81,0xA3,0x53,0x99,0x4B,0x3C,0x93,0x3B,0xFE,0x55,0xFF,
518 0x25,0x9F,0xCC,0x07,0xC5,0x2C,0x14,0xA7,0xA4,0x1E,0x6C,0xB6,0x91,0x2A,0xE0,0x3E,
519 0x7F,0x39,0x0A,0xD9,0x24,0x3C,0x01,0xA0,0x30,0x99,0x8E,0xB8,0x1D,0xF9,0xA7,0x78,
520 0x86,0x95,0x35,0x0E,0x21,0xDA,0x7A,0x7B,0xAD,0x9F,0x4E,0xF6,0x63,0x5B,0x96,0xBB,
521 0x87,0x36,0x3F,0xA7,0x1A,0x66,0x91,0xCD,0xB0,0x3B,0xC0,0x4F,0x54,0xD2,0x5F,0xBB,
522 0x38,0x89,0x1C,0x79,0x7E,0xA2,0x02,0xE4,0x80,0x84,0x1E,0x33,0xAB,0x74,0xFA,0xBE,
523 0x31,0x46,0x2E,0xC5,0x15,0xB9,0x12,0xE9,0xD3,0x73,0x43,0xEA,0x74,0x11,0xA7,0xC0,
524 0xD5,0xD8,0x39,0x08,0x9F,0x4F,0xC7,0x71,0x25,0x09,0x51,0x65,0xD6,0xA8,0x02,0x1F
525 };
526
527 return random_noise[ (random_noise[p[1] & 1023] + p[0]) & 1023 ] & umod;
528 }
529
530 static void init_mesh( struct mesh *m, float const *tris, u32 length )
531 {
532 m->elements = length/3;
533 glGenVertexArrays( 1, &m->vao );
534 glGenBuffers( 1, &m->vbo );
535
536 glBindVertexArray( m->vao );
537 glBindBuffer( GL_ARRAY_BUFFER, m->vbo );
538 glBufferData( GL_ARRAY_BUFFER, length*sizeof(float), tris, GL_STATIC_DRAW );
539
540 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
541 glEnableVertexAttribArray( 0 );
542
543 VG_CHECK_GL();
544 }
545
546 static void free_mesh( struct mesh *m )
547 {
548 glDeleteVertexArrays( 1, &m->vao );
549 glDeleteBuffers( 1, &m->vbo );
550 }
551
552 static void draw_mesh( int const start, int const count )
553 {
554 glDrawArrays( GL_TRIANGLES, start*3, count*3 );
555 }
556
557 static void use_mesh( struct mesh *m )
558 {
559 glBindVertexArray( m->vao );
560 }
561
562 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
563
564 static void map_free(void)
565 {
566 arrfree( world.data );
567 arrfree( world.io );
568
569 free( world.cmd_buf_tiles );
570 world.cmd_buf_tiles = NULL;
571
572 world.w = 0;
573 world.h = 0;
574 world.data = NULL;
575 world.io = NULL;
576 world.score = 0;
577 world.time = 0;
578 world.completed = 0;
579 world.max_runs = 0;
580 world.initialzed = 0;
581 }
582
583 static void io_reset(void)
584 {
585 for( int i = 0; i < arrlen( world.io ); i ++ )
586 {
587 struct cell_terminal *term = &world.io[i];
588
589 for( int j = 0; j < term->run_count; j ++ )
590 term->runs[j].recv_count = 0;
591 }
592 }
593
594 static struct cell *pcell( v2i pos )
595 {
596 return &world.data[ pos[1]*world.w + pos[0] ];
597 }
598
599 static void lcell( int id, v2i pos )
600 {
601 pos[0] = id % world.w;
602 pos[1] = (id - pos[0]) / world.w;
603 }
604
605 static void map_reclassify( v2i start, v2i end, int update_texbuffer )
606 {
607 v2i full_start = { 1,1 };
608 v2i full_end = { world.w-1, world.h-1 };
609
610 if( !start || !end )
611 {
612 start = full_start;
613 end = full_end;
614 }
615
616 /* Texture data */
617 u8 info_buffer[64*64*4];
618 u32 pixel_id = 0;
619
620 int px0 = vg_max( start[0], full_start[0] ),
621 px1 = vg_min( end[0], full_end[0] ),
622 py0 = vg_max( start[1], full_start[1] ),
623 py1 = vg_min( end[1], full_end[1] );
624
625 for( int y = py0; y < py1; y ++ )
626 {
627 for( int x = px0; x < px1; x ++ )
628 {
629 struct cell *cell = pcell((v2i){x,y});
630
631 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
632
633 u8 height = 0;
634 u8 config = 0x00;
635
636 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
637 {
638 for( int i = 0; i < vg_list_size( dirs ); i ++ )
639 {
640 struct cell *neighbour =
641 pcell((v2i){ x+dirs[i][0], y+dirs[i][1] });
642
643 if( neighbour->state &
644 (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
645 {
646 config |= 0x1 << i;
647 }
648 }
649
650 height = 128;
651 }
652 else
653 {
654 if( cell->state & FLAG_WALL )
655 height = 0xFF; /*-0x3F + hash21i( (v2i){x,y}, 0x3F );*/
656
657 config = cell->state & FLAG_INPUT_NICE? 0xB: 0xF;
658 }
659
660 pcell((v2i){x,y})->config = config;
661
662 u8 *info_px = &info_buffer[ (pixel_id ++)*4 ];
663 info_px[0] = height;
664 info_px[1] = cell->state & FLAG_WALL? 0: 255;
665 info_px[2] = 0;
666 info_px[3] = 0;
667
668 /*
669 * Detecting hanging links that should be removed
670 */
671 int is_trigger = cell->state & FLAG_IS_TRIGGER;
672 int trigger_invalid = cell->config == 0xF ||
673 cell->config == k_cell_type_split;
674 int is_targeted = cell->state & FLAG_TARGETED;
675 int target_invalid = (cell->config != k_cell_type_split) &&
676 !(cell->state & FLAG_EMITTER);
677
678 if((
679 (is_trigger && trigger_invalid) ||
680 (is_targeted && target_invalid)
681 ) && update_texbuffer)
682 {
683 cell->state &= ~(FLAG_TARGETED|FLAG_IS_TRIGGER);
684 for( u32 i = 0; i < 2; i ++ )
685 {
686 if( cell->links[i] )
687 {
688 struct cell *other_ptr = &world.data[ cell->links[i] ];
689 other_ptr->links[ i ] = 0;
690 other_ptr->state &= ~FLAG_IS_TRIGGER;
691
692 if( other_ptr->links[ i ^ 0x1 ] == 0 )
693 other_ptr->state &= ~FLAG_TARGETED;
694 }
695 }
696
697 cell->links[0] = 0;
698 cell->links[1] = 0;
699 }
700 }
701 }
702
703 if( update_texbuffer )
704 {
705 glBindTexture( GL_TEXTURE_2D, world.background_data );
706 glTexSubImage2D( GL_TEXTURE_2D, 0,
707 px0 + 16, py0 + 16,
708 px1-px0, py1-py0, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
709 }
710 }
711
712 static void gen_level_text(void)
713 {
714 ui_px const unit_scale_px = 4*UI_GLYPH_SPACING_X;
715 ui_begin( &world.st.world_text, world.w*unit_scale_px,
716 world.h*unit_scale_px );
717
718 if( world.pCmpLevel )
719 {
720 for( int i = 0; i < vg_list_size( world.pCmpLevel->strings ); i ++ )
721 {
722 struct world_string *wstr = &world.pCmpLevel->strings[i];
723
724 if( wstr->str )
725 {
726 ui_px pos[2];
727
728 pos[0] = -UI_GLYPH_SPACING_X/2;
729
730 if( wstr->placement == k_placement_bottom )
731 pos[1] = 2*-unit_scale_px;
732 else
733 pos[1] = (world.h-1)*-unit_scale_px -6;
734
735 ui_text( &world.st.world_text, pos, wstr->str,
736 1, k_text_align_left );
737 }
738 }
739 }
740
741 // re-create level scores
742 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
743 {
744 struct career_level_pack *set = &career_packs[i];
745
746 /*
747 ui_text( &world.st.world_text,
748 (ui_px [2]){
749 set->origin[0]*unit_scale_px,
750 -(set->origin[1]+set->dims[1]+1)*unit_scale_px + 18
751 },
752 set->title, 1, k_text_align_left );
753 */
754
755 for( int j = 0; j < set->count; j ++ )
756 {
757 struct cmp_level *lvl = &set->pack[j];
758
759 if( lvl->completed_score && !lvl->is_tutorial )
760 {
761 char num[10];
762 snprintf( num, 9, "%d", lvl->completed_score );
763
764 ui_text( &world.st.world_text,
765 (ui_px [2]){
766 lvl->btn.position[0]*unit_scale_px + unit_scale_px/2,
767 -lvl->btn.position[1]*unit_scale_px - unit_scale_px/2
768 },
769 num, 1, k_text_align_center );
770 }
771 }
772 }
773
774 ui_resolve( &world.st.world_text );
775 }
776
777 /* Usually for ignoring windows crap */
778 static int map_load_char_ignore( char c )
779 {
780 if( c == '\r' ) return 1;
781 return 0;
782 }
783
784 static int map_load_sequence_char( struct terminal_run *run, char c )
785 {
786 if( (c >= 'a' && c <= 'z') || c == ' ' )
787 {
788 i8 code = -1;
789 if( c != ' ' )
790 code = c - 'a';
791
792 run->steps[ run->step_count ++ ] = code;
793
794 return 1;
795 }
796
797 return 0;
798 }
799
800 static int map_load_is_digit( char c )
801 {
802 if( (((u32)c >= (u32)'0') && ((u32)c <= (u32)'9')) || c == '-' )
803 {
804 return 1;
805 }
806
807 return 0;
808 }
809
810 static int map_load_is_terminal( char c )
811 {
812 if( c == '+' || c == '-' || c == '*' )
813 return 1;
814 return 0;
815 }
816
817 static int map_load_apply_emitter_codes( struct cell_terminal *term )
818 {
819 struct cell *cell = pcell( term->pos );
820
821 if( cell->state & FLAG_EMITTER )
822 {
823 if( (term->run_count > 0) && (term->runs[0].step_count >= 2) )
824 {
825 cell->emit[0] = term->runs[0].steps[0];
826 cell->emit[1] = term->runs[0].steps[1];
827 }
828 else
829 {
830 vg_error( "Emitter was not assigned emit values\n" );
831 return 0;
832 }
833 }
834
835 return 1;
836 }
837
838 static void map_load_cell( struct cell *cell, char c, int cx )
839 {
840 cell->config = 0xF;
841
842 cell->links[0] = 0;
843 cell->links[1] = 0;
844
845 v3_zero( cell->glow[0] );
846 v3_zero( cell->glow[1] );
847
848 /* Input, output, emitter */
849 if( map_load_is_terminal(c) )
850 {
851 struct cell_terminal *term = arraddnptr( world.io, 1 );
852 term->pos[0] = cx;
853 term->pos[1] = world.h;
854
855 term->run_count = 1;
856 term->runs[0].step_count = 0;
857
858 switch( c )
859 {
860 case '+': cell->state = FLAG_INPUT; break;
861 case '-': cell->state = FLAG_OUTPUT; break;
862 case '*': cell->state = FLAG_EMITTER; break;
863 }
864 }
865 else if( c == '.' ) cell->state = FLAG_INPUT_NICE;
866 else if( c == '#' ) cell->state = FLAG_WALL;
867 else if( ((u32)c >= (u32)'A') && ((u32)c <= (u32)'A'+0xf) )
868 {
869 /*
870 * Canal flag bits (4bit/16 value):
871 * 0: Canal present
872 * 1: Is trigger
873 * 2: Reserved
874 * 3: Reserved
875 */
876 cell->state = ((u32)c - (u32)'A') & (FLAG_CANAL|FLAG_IS_TRIGGER);
877 world.score ++;
878 }
879 else cell->state = 0x00;
880 }
881
882 static void map_load_draw_background(void)
883 {
884 u8 info_buffer[64*64*4];
885
886 for( int x = 0; x < 64; x ++ )
887 {
888 for( int y = 0; y < 64; y ++ )
889 {
890 u8 *px = &info_buffer[((x*64)+y)*4];
891
892 #if 0
893 /* Fade out edges of world so that there isnt an obvious line */
894 int dx = 16 - VG_MIN( VG_MIN( x, 16 ), 16-VG_MAX( x-16-world.w, 0 ) ),
895 dy = 16 - VG_MIN( VG_MIN( y, 16 ), 16-VG_MAX( y-16-world.h, 0 ) ),
896
897 dist = VG_MAX( dx, dy ) * 16,
898 value = VG_MAX( 0, 0xFF-0x3F + hash21i( (v2i){x,y}, 0x3F ) - dist);
899 #endif
900
901 px[0] = 0xFF;
902 px[1] = 0;
903 px[2] = 0;
904 px[3] = 0;
905 }
906 }
907
908 /*
909 * Level selection indentation, to make it look like the buttons are in a
910 * recessed area of the map.
911 */
912 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
913 {
914 struct career_level_pack *grid = &career_packs[ i ];
915
916 int j = 0;
917
918 for( int y = 0; y < grid->dims[1]; y ++ )
919 {
920 for( int x = 0; x < grid->dims[0]; x ++ )
921 {
922 int cy = y+16+grid->origin[1],
923 cx = 16+x+grid->origin[0];
924
925 u8 *px = &info_buffer[(cy*64+cx)*4];
926 px[0] = 0x10;
927
928 if( j < grid->count )
929 {
930 struct cmp_level *lvl = &grid->pack[ j ++ ];
931 v2i_add( grid->origin, (v2i){x,y}, lvl->btn.position );
932 }
933 }
934 }
935 }
936
937 /*
938 * Recess the UI buttons, this adds a little bit of a (subtle) outline
939 * to them when the background shader is run
940 */
941 for( int i=0; i<4; i++ )
942 info_buffer[(((16+world.h-(i+2))*64)+world.w+16-1)*4] = 0x30;
943
944 /*
945 * Digging 'wires' from the output/input terminals, to make it look like
946 * there is something connecting our world.
947 */
948 for( int i = 0; i < arrlen(world.io); i ++ )
949 {
950 struct cell_terminal *term = &world.io[ i ];
951
952 v2i turtle;
953 v2i turtle_dir;
954 int original_y;
955
956 /*
957 * Only make breakouts for terminals on the edge,
958 * starting them from every position leads to some weird overlapping
959 * and confusing lines.
960 * */
961 if( !(term->pos[1] == 1 || term->pos[1] == world.h-2) )
962 continue;
963
964 turtle[0] = 16+term->pos[0];
965 turtle[1] = 16+term->pos[1];
966
967 turtle_dir[0] = 0;
968 turtle_dir[1] =
969 pcell(term->pos)->state & (FLAG_INPUT|FLAG_EMITTER)? 1: -1;
970 original_y = turtle_dir[1];
971
972 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
973 v2i_add( turtle_dir, turtle, turtle );
974
975 for( int i = 0; i < 100; i ++ )
976 {
977 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
978
979 v2i_add( turtle_dir, turtle, turtle );
980
981 if( turtle[0] == 0 ) break;
982 if( turtle[0] == 63 ) break;
983 if( turtle[1] == 0 ) break;
984 if( turtle[1] == 63 ) break;
985
986 int random_value = hash21i( turtle, 0xFF );
987 if( random_value > 255-40 && !turtle_dir[0] )
988 {
989 turtle_dir[0] = -1;
990 turtle_dir[1] = 0;
991 }
992 else if( random_value > 255-80 && !turtle_dir[0] )
993 {
994 turtle_dir[0] = 1;
995 turtle_dir[1] = 0;
996 }
997 else if( random_value > 255-100 )
998 {
999 turtle_dir[0] = 0;
1000 turtle_dir[1] = original_y;
1001 }
1002 }
1003 }
1004
1005 glBindTexture( GL_TEXTURE_2D, world.background_data );
1006 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 64, 64,
1007 GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
1008 }
1009
1010 static int map_load_validate(void)
1011 {
1012 for( int i = 0; i < world.h*world.w; i ++ )
1013 {
1014 struct cell *src = &world.data[i];
1015
1016 if( src->state & FLAG_IS_TRIGGER )
1017 {
1018 int link_id = src->links[0]?0:1;
1019 if( src->links[link_id] <= world.h*world.w )
1020 {
1021 struct cell *target = &world.data[ src->links[link_id] ];
1022
1023 int is_canal = target->state & FLAG_CANAL,
1024 is_splitter = target->config == k_cell_type_split,
1025 is_emitter = target->state & FLAG_EMITTER;
1026
1027 if((is_canal && is_splitter) || is_emitter)
1028 {
1029 if( target->links[ link_id ] )
1030 {
1031 vg_error( "Link target was already targeted\n" );
1032 return 0;
1033 }
1034 else
1035 {
1036 /*
1037 * Valid link, apply reverse mapping to other cell so it
1038 * knows who is linking to it
1039 */
1040 target->links[ link_id ] = i;
1041 target->state |= FLAG_TARGETED;
1042 }
1043 }
1044 else
1045 {
1046 vg_error( "Link target was invalid\n" );
1047 return 0;
1048 }
1049 }
1050 else
1051 {
1052 vg_error( "Link target out of bounds\n" );
1053 return 0;
1054 }
1055 }
1056 }
1057
1058 return 1;
1059 }
1060
1061 static int map_load( const char *str, const char *name )
1062 {
1063 map_free();
1064
1065 char const *c = str;
1066
1067 /* Predetermine width */
1068 for(;; world.w ++)
1069 {
1070 if( c[world.w] == ';' )
1071 break;
1072 else if( !c[world.w] )
1073 {
1074 vg_error( "Unexpected EOF when parsing level\n" );
1075 return 0;
1076 }
1077 }
1078
1079 struct cell *row = arraddnptr( world.data, world.w );
1080 int cx = 0;
1081 int reg_start = 0, reg_end = 0;
1082
1083 u32 *links_to_make = NULL;
1084 int links_satisfied = 0;
1085
1086 char link_id_buffer[32];
1087 int link_id_n = 0;
1088
1089 for(;;)
1090 {
1091 if( !*c )
1092 break;
1093
1094 if( map_load_char_ignore( *c ) ) { c++; continue; }
1095
1096 if( *c == ';' )
1097 {
1098 c ++;
1099
1100 if( *c == '\r' ) c ++;
1101
1102 /* Parse attribs */
1103 if( *c != '\n' )
1104 {
1105 while( *c )
1106 {
1107 if( map_load_char_ignore( *c ) ) { c++; continue; }
1108
1109 if( reg_start < reg_end )
1110 {
1111 struct cell_terminal *terminal = &world.io[ reg_start ];
1112 struct terminal_run *run =
1113 &terminal->runs[ terminal->run_count-1 ];
1114
1115 if( !map_load_sequence_char( run, *c ) )
1116 {
1117 /* Control characters */
1118 if( *c == ',' || *c == '\n' ) /* Next terminal */
1119 {
1120 reg_start ++;
1121
1122 if( *c == '\n' )
1123 break;
1124 }
1125 else if( *c == ':' ) /* New run starting */
1126 {
1127 terminal->runs[ terminal->run_count ].step_count = 0;
1128 terminal->run_count ++;
1129 world.max_runs =
1130 vg_max( world.max_runs, terminal->run_count );
1131 }
1132 else
1133 {
1134 vg_error( "Unkown attribute '%c' (row: %u)\n",
1135 *c, world.h );
1136 goto IL_REG_ERROR;
1137 }
1138 }
1139 }
1140 else
1141 {
1142 if( links_satisfied < arrlen( links_to_make ) )
1143 {
1144 struct cell *target =
1145 &world.data[ links_to_make[ links_satisfied ] ];
1146
1147 if( map_load_is_digit( *c ) )
1148 {
1149 if( link_id_n >= vg_list_size( link_id_buffer )-1 )
1150 {
1151 vg_error( "Number was way too long to be parsed"
1152 " (row: %u)\n", world.h );
1153 goto IL_REG_ERROR;
1154 }
1155
1156 link_id_buffer[ link_id_n ++ ] = *c;
1157 }
1158 else if( *c == ',' || *c == '\n' )
1159 {
1160 link_id_buffer[ link_id_n ] = 0x00;
1161 int value = atoi( link_id_buffer );
1162
1163 target->links[value >= 0? 1:0] = abs(value);
1164 links_satisfied ++;
1165 link_id_n = 0;
1166
1167 if( *c == '\n' )
1168 break;
1169 }
1170 else
1171 {
1172 vg_error( "Invalid character '%c'"
1173 " (row: %u)\n", *c, world.h );
1174 goto IL_REG_ERROR;
1175 }
1176 }
1177 else
1178 {
1179 vg_error( "Too many values to assign"
1180 " (row: %u)\n", world.h );
1181 goto IL_REG_ERROR;
1182 }
1183 }
1184
1185 c ++;
1186 }
1187 }
1188
1189 /* Registry length-error checks */
1190 if( reg_start != reg_end )
1191 {
1192 vg_error( "Not enough spawn values assigned (row: %u, %u of %u)\n",
1193 world.h, reg_start, reg_end );
1194 goto IL_REG_ERROR;
1195 }
1196
1197 if( links_satisfied != arrlen( links_to_make ) )
1198 {
1199 vg_error( "Not enough link values assigned (row: %u, %u of %u)\n",
1200 world.h, links_satisfied, arrlen( links_to_make ) );
1201 goto IL_REG_ERROR;
1202 }
1203
1204 if( cx != world.w )
1205 {
1206 vg_error( "Not enough cells to match previous row definition"
1207 " (row: %u, %u<%u)\n", world.h, cx, world.w );
1208 goto IL_REG_ERROR;
1209 }
1210
1211 row = arraddnptr( world.data, world.w );
1212 cx = 0;
1213 world.h ++;
1214 reg_end = reg_start = arrlen( world.io );
1215
1216 arrsetlen( links_to_make, 0 );
1217 links_satisfied = 0;
1218 }
1219 else
1220 {
1221 if( cx == world.w )
1222 {
1223 vg_error( "Too many cells to match previous row definition"
1224 " (row: %u, %u>%u)\n", world.h, cx, world.w );
1225 goto IL_REG_ERROR;
1226 }
1227
1228 struct cell *cell = &row[ cx ];
1229 map_load_cell( cell, *c, cx );
1230
1231 if( map_load_is_terminal(*c) )
1232 reg_end ++;
1233
1234 if( cell->state & FLAG_IS_TRIGGER )
1235 arrpush( links_to_make, cx + world.h*world.w );
1236
1237 cx ++;
1238 }
1239
1240 c ++;
1241 }
1242
1243 for( int i = 0; i < arrlen( world.io ); i ++ )
1244 {
1245 if( !map_load_apply_emitter_codes( &world.io[i] ) )
1246 goto IL_REG_ERROR;
1247 }
1248
1249 map_load_draw_background();
1250 map_reclassify( NULL, NULL, 1 );
1251
1252 if( !map_load_validate() )
1253 goto IL_REG_ERROR;
1254
1255 /*
1256 * At this point the world is in a fully loaded and complete state
1257 */
1258
1259 arrfree( links_to_make );
1260
1261 vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h );
1262
1263 io_reset();
1264
1265 strncpy( world.map_name, name, vg_list_size( world.map_name )-1 );
1266 world.initialzed = 1;
1267
1268 /* Setup world button locations */
1269 for( int i = 0; i < vg_list_size( world.st.buttons ); i ++ )
1270 {
1271 struct world_button *btn = &world.st.buttons[i];
1272 btn->position[0] = world.w -1;
1273 btn->position[1] = world.h -i -2;
1274 }
1275
1276 /* Allocate buffers for render commands */
1277 world.cmd_buf_tiles = malloc( world.w*world.h* sizeof( struct render_cmd ) );
1278 world.max_commands = world.w*world.h;
1279 return 1;
1280
1281 IL_REG_ERROR:
1282 arrfree( links_to_make );
1283 map_free();
1284 return 0;
1285 }
1286
1287 static void map_serialize( FILE *stream )
1288 {
1289 for( int y = 0; y < world.h; y ++ )
1290 {
1291 for( int x = 0; x < world.w; x ++ )
1292 {
1293 struct cell *cell = pcell( (v2i){ x, y } );
1294
1295 if( cell->state & FLAG_WALL ) fputc( '#', stream );
1296 else if( cell->state & FLAG_INPUT_NICE ) fputc( '.', stream );
1297 else if( cell->state & FLAG_INPUT ) fputc( '+', stream );
1298 else if( cell->state & FLAG_OUTPUT ) fputc( '-', stream );
1299 else if( cell->state & FLAG_EMITTER ) fputc( '*', stream );
1300 else if( cell->state & FLAG_4B_GROUP )
1301 {
1302 /*
1303 * Serialize the '4 bit group' into ABCD...
1304 */
1305 fputc( (cell->state & FLAG_4B_GROUP) + (u32)'A', stream );
1306 }
1307 else fputc( ' ', stream );
1308 }
1309
1310 fputc( ';', stream );
1311 int terminal_write_count = 0;
1312
1313 for( int x = 0; x < world.w; x ++ )
1314 {
1315 for( int i = 0; i < arrlen( world.io ); i ++ )
1316 {
1317 struct cell_terminal *term = &world.io[i];
1318 if( v2i_eq( term->pos, (v2i){x,y} ) )
1319 {
1320 if( terminal_write_count )
1321 fputc( ',', stream );
1322 terminal_write_count ++;
1323
1324 for( int j = 0; j < term->run_count; j ++ )
1325 {
1326 struct terminal_run *run = &term->runs[j];
1327
1328 for( int k = 0; k < run->step_count; k ++ )
1329 {
1330 i8 step = run->steps[k];
1331 fputc( step == -1? ' ': ('a' + run->steps[k]), stream );
1332 }
1333
1334 if( j < term->run_count-1 )
1335 fputc( ':', stream );
1336 }
1337 }
1338 }
1339 }
1340
1341 for( int x = 0; x < world.w; x ++ )
1342 {
1343 struct cell *cell = pcell( (v2i){ x,y } );
1344 if( cell->state & FLAG_IS_TRIGGER )
1345 {
1346 if( terminal_write_count )
1347 fputc( ',', stream );
1348 terminal_write_count ++;
1349
1350 fprintf( stream, "%d",
1351 cell->links[0]? -cell->links[0]: cell->links[1] );
1352 }
1353 }
1354
1355 fputc( '\n', stream );
1356 }
1357 }
1358
1359 /*
1360 * Career state
1361 */
1362 #pragma pack(push,1)
1363 struct dcareer_state
1364 {
1365 u32 version;
1366 i32 in_map;
1367
1368 u32 reserved[14];
1369
1370 struct dlevel_state
1371 {
1372 i32 score;
1373
1374 i32 unlocked;
1375 i32 reserved[2];
1376 }
1377 levels[ NUM_CAMPAIGN_LEVELS ];
1378 };
1379 #pragma pack(pop)
1380
1381 static int career_load_success = 0;
1382
1383 static void career_serialize(void)
1384 {
1385 if( !career_load_success )
1386 return;
1387
1388 struct dcareer_state encoded;
1389 encoded.version = MARBLE_COMP_VERSION;
1390 encoded.in_map = world.pCmpLevel? world.pCmpLevel->serial_id: -1;
1391
1392 memset( encoded.reserved, 0, sizeof( encoded.reserved ) );
1393
1394 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1395 {
1396 struct career_level_pack *set = &career_packs[i];
1397
1398 for( int j = 0; j < set->count; j ++ )
1399 {
1400 struct cmp_level *lvl = &set->pack[j];
1401 struct dlevel_state *dest = &encoded.levels[lvl->serial_id];
1402
1403 dest->score = lvl->completed_score;
1404 dest->unlocked = lvl->unlocked;
1405 dest->reserved[0] = 0;
1406 dest->reserved[1] = 0;
1407 }
1408 }
1409
1410 vg_asset_write( "sav/game.sv2", &encoded, sizeof( struct dcareer_state ) );
1411 }
1412
1413 static void career_unlock_level( struct cmp_level *lvl );
1414 static void career_unlock_level( struct cmp_level *lvl )
1415 {
1416 lvl->unlocked = 1;
1417
1418 if( lvl->linked )
1419 career_unlock_level( lvl->linked );
1420 }
1421
1422 static void career_pass_level( struct cmp_level *lvl, int score, int upload )
1423 {
1424 if( score > 0 )
1425 {
1426 lvl->completed_score = score;
1427 gen_level_text();
1428
1429 if( lvl->unlock )
1430 career_unlock_level( lvl->unlock );
1431
1432 #ifdef VG_STEAM
1433 if( lvl->achievement )
1434 sw_set_achievement( lvl->achievement );
1435
1436 /* Check ALL maps to trigger master engineer */
1437 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1438 {
1439 struct career_level_pack *set = &career_packs[i];
1440
1441 for( int j = 0; j < set->count; j ++ )
1442 {
1443 if( set->pack[j].completed_score == 0 )
1444 return;
1445 }
1446 }
1447
1448 sw_set_achievement( "MASTER_ENGINEER" );
1449 #endif
1450 }
1451 }
1452
1453 static void career_reset_level( struct cmp_level *lvl )
1454 {
1455 lvl->unlocked = 0;
1456 lvl->completed_score = 0;
1457 }
1458
1459 static void career_load(void)
1460 {
1461 i64 sz;
1462 struct dcareer_state encoded;
1463
1464 /* Blank save state */
1465 memset( (void*)&encoded, 0, sizeof( struct dcareer_state ) );
1466 encoded.in_map = 0;
1467 encoded.levels[0].unlocked = 1;
1468
1469 /*
1470 * Load and copy, this step is just to ensure old/newer saves can be loaded
1471 * without crashing. Old saves will load fine, too new saves will lose data,
1472 * such a situation should rarely (never) happen with the steam version.
1473 */
1474 void *cr = vg_asset_read_s( "sav/game.sv2", &sz );
1475
1476 if( cr )
1477 {
1478 if( sz > sizeof( struct dcareer_state ) )
1479 vg_warn( "This save file is too big! Some levels will be lost\n" );
1480
1481 if( sz <= offsetof( struct dcareer_state, levels ) )
1482 {
1483 vg_warn( "This save file is too small to have a header. "
1484 "Creating a blank one\n" );
1485 free( cr );
1486 }
1487
1488 memcpy( (void*)&encoded, cr, VG_MIN( sizeof( struct dcareer_state ), sz));
1489 free( cr );
1490 }
1491 else
1492 vg_info( "No save file... Using blank one\n" );
1493
1494 /* Reset memory */
1495 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1496 {
1497 struct career_level_pack *set = &career_packs[i];
1498
1499 for( int j = 0; j < set->count; j ++ )
1500 career_reset_level( &set->pack[j] );
1501 }
1502
1503 /* Header information */
1504
1505 struct cmp_level *lvl_to_load = &career_packs[0].pack[0];
1506
1507 /* Decode everything from dstate */
1508 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
1509 {
1510 struct career_level_pack *set = &career_packs[i];
1511
1512 for( int j = 0; j < set->count; j ++ )
1513 {
1514 struct cmp_level *lvl = &set->pack[j];
1515 struct dlevel_state *src = &encoded.levels[lvl->serial_id];
1516
1517 if( src->unlocked ) career_unlock_level( lvl );
1518 if( src->score )
1519 {
1520 lvl->completed_score = src->score;
1521
1522 /*
1523 * Apply unlocking trigger to next levels,
1524 * in case the level graph was updated in the future
1525 */
1526 if( lvl->unlock )
1527 career_unlock_level( lvl->unlock );
1528 }
1529
1530 if( lvl->serial_id == encoded.in_map )
1531 lvl_to_load = lvl;
1532 }
1533 }
1534
1535 if( console_changelevel( 1, &lvl_to_load->map_name ) )
1536 {
1537 world.pCmpLevel = lvl_to_load;
1538 gen_level_text();
1539 }
1540
1541 career_load_success = 1;
1542
1543 #if 0
1544 if( encoded.version < MARBLE_COMP_VERSION )
1545 world.st.state = k_game_state_update;
1546 #endif
1547 }
1548
1549 /*
1550 * Main gameplay
1551 */
1552 static int is_simulation_running(void)
1553 {
1554 return world.st.buttons[ k_world_button_sim ].state;
1555 }
1556
1557 static void clear_animation_flags(void)
1558 {
1559 for( int i = 0; i < world.w*world.h; i ++ )
1560 world.data[ i ].state &= ~(FLAG_FLIP_FLOP|FLAG_FLIP_ROTATING);
1561 }
1562
1563 static void simulation_stop(void)
1564 {
1565 world.st.buttons[ k_world_button_sim ].state = 0;
1566 world.st.buttons[ k_world_button_pause ].state = 0;
1567
1568 world.num_fishes = 0;
1569 world.sim_frame = 0;
1570 world.sim_run = 0;
1571 world.frame_lerp = 0.0f;
1572
1573 io_reset();
1574
1575 sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
1576
1577 clear_animation_flags();
1578
1579 vg_info( "Stopping simulation!\n" );
1580 }
1581
1582 static void simulation_start(void)
1583 {
1584 vg_success( "Starting simulation!\n" );
1585
1586 sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
1587
1588 world.num_fishes = 0;
1589 world.sim_frame = 0;
1590 world.sim_run = 0;
1591
1592 world.sim_delta_speed = world.st.buttons[ k_world_button_speedy ].state? 10.0f: 2.5f;
1593 world.sim_delta_ref = vg_time;
1594 world.sim_internal_ref = 0.0f;
1595 world.sim_internal_time = 0.0f;
1596 world.pause_offset_target = 0.0f;
1597
1598 world.sim_target = 0;
1599
1600 clear_animation_flags();
1601
1602 io_reset();
1603
1604 if( world.pCmpLevel )
1605 {
1606 world.pCmpLevel->completed_score = 0;
1607 gen_level_text();
1608 }
1609 }
1610
1611 static int world_check_pos_ok( v2i co, int dist )
1612 {
1613 return (co[0] < dist || co[0] >= world.w-dist || co[1] < dist || co[1] >= world.h-dist)? 0: 1;
1614 }
1615
1616 static int cell_interactive( v2i co )
1617 {
1618 struct cell *cell = NULL;
1619
1620 // Bounds check
1621 if( world_check_pos_ok( co, 1 ) )
1622 {
1623 cell = pcell( co );
1624 if( cell->state & FLAG_EMITTER )
1625 return 1;
1626 }
1627
1628 if( !world_check_pos_ok( co, 2 ) )
1629 return 0;
1630
1631 // Flags check
1632 if( cell->state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
1633 return 0;
1634
1635 // List of 3x3 configurations that we do not allow
1636 static u32 invalid_src[][9] =
1637 {
1638 { 0,1,0,
1639 1,1,1,
1640 0,1,0
1641 },
1642 { 0,0,0,
1643 0,1,1,
1644 0,1,1
1645 },
1646 { 0,0,0,
1647 1,1,0,
1648 1,1,0
1649 },
1650 { 0,1,1,
1651 0,1,1,
1652 0,0,0
1653 },
1654 { 1,1,0,
1655 1,1,0,
1656 0,0,0
1657 },
1658 { 0,1,0,
1659 0,1,1,
1660 0,1,0
1661 },
1662 { 0,1,0,
1663 1,1,0,
1664 0,1,0
1665 }
1666 };
1667
1668 // Statically compile invalid configurations into bitmasks
1669 static u32 invalid[ vg_list_size(invalid_src) ];
1670
1671 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
1672 {
1673 u32 comped = 0x00;
1674
1675 for( int j = 0; j < 3; j ++ )
1676 for( int k = 0; k < 3; k ++ )
1677 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
1678
1679 invalid[i] = comped;
1680 }
1681
1682 // Extract 5x5 grid surrounding tile
1683 u32 blob = 0x1000;
1684 for( int y = co[1]-2; y < co[1]+3; y ++ )
1685 for( int x = co[0]-2; x < co[0]+3; x ++ )
1686 {
1687 struct cell *cell = pcell((v2i){x,y});
1688
1689 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER)) )
1690 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
1691 }
1692
1693 // Run filter over center 3x3 grid to check for invalid configurations
1694 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
1695 for( int i = 0; i < vg_list_size(kernel); i ++ )
1696 {
1697 if( blob & (0x1 << (6+kernel[i])) )
1698 {
1699 u32 window = blob >> kernel[i];
1700
1701 for( int j = 0; j < vg_list_size(invalid); j ++ )
1702 if((window & invalid[j]) == invalid[j])
1703 return 0;
1704 }
1705 }
1706
1707 return 1;
1708 }
1709
1710 static void vg_update(void)
1711 {
1712 // Async events
1713 if( world.st.lvl_to_load )
1714 {
1715 world.st.world_transition = (world.st.lvl_load_time-vg_time) * 4.0f;
1716
1717 if( vg_time > world.st.lvl_load_time )
1718 {
1719 if( console_changelevel( 1, &world.st.lvl_to_load->map_name ) )
1720 {
1721 world.pCmpLevel = world.st.lvl_to_load;
1722 gen_level_text();
1723 }
1724
1725 world.st.lvl_to_load = NULL;
1726 }
1727 }
1728 else
1729 {
1730 world.st.world_transition = vg_minf( 1.0f, (vg_time-world.st.lvl_load_time) * 4.0f );
1731 }
1732
1733 // Camera
1734 // ========================================================================================================
1735
1736 float r1 = (float)vg_window_y / (float)vg_window_x,
1737 r2 = (float)world.h / (float)world.w,
1738 size;
1739
1740 static float size_current = 2.0f;
1741 static v3f origin_current = { 0.0f, 0.0f, 0.0f };
1742 static v2f drag_offset = { 0.0f, 0.0f };
1743 static v2f view_point = { 0.0f, 0.0f };
1744 v2f result_view;
1745
1746 size = ( r2 < r1? (float)(world.w+5) * 0.5f: ((float)(world.h+5) * 0.5f) / r1 ) + 0.5f;
1747
1748 v2f origin;
1749 v2f vt_target;
1750
1751 origin[0] = floorf( -0.5f * ((float)world.w-4.5f) );
1752 origin[1] = floorf( -0.5f * world.h );
1753
1754 // Create and clamp result view
1755 v2_add( view_point, drag_offset, result_view );
1756 result_view[0] = vg_clampf( result_view[0], -world.st.zoom, world.st.zoom );
1757 result_view[1] = vg_clampf( result_view[1], -world.st.zoom*r1, world.st.zoom*r1 );
1758
1759 v2_add( origin, result_view, vt_target );
1760
1761 // Lerp towards target
1762 size_current = vg_lerpf( size_current, size - world.st.zoom, vg_time_delta * 6.0f );
1763 v2_lerp( origin_current, vt_target, vg_time_delta * 6.0f, origin_current );
1764
1765 m3x3_projection( m_projection, -size_current, size_current, -size_current*r1, size_current*r1 );
1766 m3x3_identity( m_view );
1767 m3x3_translate( m_view, origin_current );
1768 m3x3_mul( m_projection, m_view, vg_pv );
1769 vg_projection_update();
1770
1771 if( world.st.state == k_game_state_update )
1772 return;
1773
1774 // Mouse input
1775 // ========================================================================================================
1776 v2_copy( vg_mouse_ws, world.tile_pos );
1777
1778 world.tile_x = floorf( world.tile_pos[0] );
1779 world.tile_y = floorf( world.tile_pos[1] );
1780
1781 // Camera dragging
1782 {
1783 static v2f drag_origin; // x/y pixel
1784
1785 if( vg_get_button_down( "tertiary" ) )
1786 v2_copy( vg_mouse, drag_origin );
1787 else if( vg_get_button( "tertiary" ) )
1788 {
1789 // get offset
1790 v2_sub( vg_mouse, drag_origin, drag_offset );
1791 v2_div( drag_offset, (v2f){ vg_window_x, vg_window_y }, drag_offset );
1792 v2_mul( drag_offset, (v2f){ size_current*2.0f, -size_current*r1*2.0f }, drag_offset );
1793 }
1794 else
1795 {
1796 v2_copy( result_view, view_point );
1797 v2_copy( (v2f){0.0f,0.0f}, drag_offset );
1798 }
1799 }
1800
1801 // Zooming
1802 {
1803 v2f mview_local;
1804 v2f mview_new;
1805 v2f mview_cur;
1806 v2f mview_delta;
1807 float rsize;
1808
1809 rsize = size-world.st.zoom;
1810
1811 v2_div( vg_mouse, (v2f){ vg_window_x*0.5f, vg_window_y*0.5f }, mview_local );
1812 v2_add( (v2f){ -rsize, -rsize*r1 }, (v2f){ mview_local[0]*rsize, (2.0f-mview_local[1])*rsize*r1 }, mview_cur );
1813
1814 world.st.zoom = vg_clampf( world.st.zoom + vg_mouse_wheel[1], 0.0f, size - 4.0f );
1815
1816 // Recalculate new position
1817 rsize = size-world.st.zoom;
1818 v2_add( (v2f){ -rsize, -rsize*r1 }, (v2f){ mview_local[0]*rsize, (2.0f-mview_local[1])*rsize*r1 }, mview_new );
1819
1820 // Apply offset
1821 v2_sub( mview_new, mview_cur, mview_delta );
1822 v2_add( mview_delta, view_point, view_point );
1823 }
1824
1825 // Tilemap
1826 // ========================================================================================================
1827 if( !is_simulation_running() && !gui_want_mouse() )
1828 {
1829 v2_copy( vg_mouse_ws, world.drag_to_co );
1830
1831 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
1832 {
1833 world.selected = world.tile_y * world.w + world.tile_x;
1834
1835 static u32 modify_state = 0;
1836 struct cell *cell_ptr = &world.data[world.selected];
1837
1838 if( !(cell_ptr->state & FLAG_EMITTER) )
1839 {
1840 if( vg_get_button_down("primary") )
1841 modify_state = (cell_ptr->state & FLAG_CANAL) ^ FLAG_CANAL;
1842
1843 if( vg_get_button("primary") && ((cell_ptr->state & FLAG_CANAL) != modify_state) )
1844 {
1845 cell_ptr->state &= ~FLAG_CANAL;
1846 cell_ptr->state |= modify_state;
1847
1848 if( cell_ptr->state & FLAG_CANAL )
1849 {
1850 cell_ptr->links[0] = 0;
1851 cell_ptr->links[1] = 0;
1852
1853 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
1854 world.score ++;
1855 }
1856 else
1857 {
1858 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
1859 world.score --;
1860 }
1861
1862 map_reclassify((v2i){ world.tile_x -2, world.tile_y -2 },
1863 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
1864 }
1865
1866 if( vg_get_button_down("secondary") && (cell_ptr->state & FLAG_CANAL) && !(cell_ptr->config == k_cell_type_split) )
1867 {
1868 world.id_drag_from = world.selected;
1869
1870 struct cell_description *desc = &cell_descriptions[ world.data[world.id_drag_from].config ];
1871 v2_add( desc->trigger_pos, (v2f){ world.tile_x, world.tile_y }, world.drag_from_co );
1872 }
1873 }
1874
1875 float local_x = vg_mouse_ws[0] - (float)world.tile_x;
1876
1877 if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
1878 {
1879 u32 link_id = cell_ptr->links[ 0 ]? 0: 1;
1880
1881 // break existing connection off
1882 if( cell_ptr->links[ link_id ] )
1883 {
1884 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1885
1886 if( !current_connection->links[ link_id ^ 0x1 ] )
1887 current_connection->state &= ~FLAG_TARGETED;
1888
1889 current_connection->links[ link_id ] = 0;
1890 cell_ptr->links[ link_id ] = 0;
1891 }
1892
1893 cell_ptr->state &= ~FLAG_IS_TRIGGER;
1894 world.id_drag_from = 0;
1895 }
1896
1897 else if( world.id_drag_from && (cell_ptr->state & (FLAG_CANAL|FLAG_EMITTER)) &&
1898 ((cell_ptr->config == k_cell_type_split) || (cell_ptr->state & FLAG_EMITTER)) )
1899 {
1900 world.drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
1901 world.drag_to_co[1] = (float)world.tile_y + 0.25f;
1902
1903 if( vg_get_button_up("secondary") )
1904 {
1905 struct cell *drag_ptr = &world.data[world.id_drag_from];
1906 u32 link_id = local_x > 0.5f? 1: 0;
1907
1908 // Cleanup existing connections
1909 if( cell_ptr->links[ link_id ] )
1910 {
1911 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id, cell_ptr->links[ link_id ] );
1912
1913 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1914 current_connection->state &= ~FLAG_IS_TRIGGER;
1915 current_connection->links[ link_id ] = 0;
1916 }
1917
1918 for( u32 i = 0; i < 2; i ++ )
1919 {
1920 if( drag_ptr->links[ i ] )
1921 {
1922 vg_warn( "Destroying link %u (%hu)\n", i, drag_ptr->links[ i ] );
1923
1924 struct cell *current_connection = &world.data[ drag_ptr->links[ i ]];
1925 if( current_connection->links[ i ^ 0x1 ] == 0 )
1926 current_connection->state &= ~FLAG_TARGETED;
1927
1928 current_connection->links[ i ] = 0;
1929 drag_ptr->links[ i ] = 0;
1930 }
1931 }
1932
1933 // Create the new connection
1934 vg_success( "Creating connection on link %u (%hu)\n", link_id, world.id_drag_from );
1935
1936 cell_ptr->links[ link_id ] = world.id_drag_from;
1937 drag_ptr->links[ link_id ] = world.selected;
1938
1939 cell_ptr->state |= FLAG_TARGETED;
1940 drag_ptr->state |= FLAG_IS_TRIGGER;
1941 world.id_drag_from = 0;
1942 }
1943 }
1944 }
1945 else
1946 {
1947 world.selected = -1;
1948 }
1949
1950 if( !(vg_get_button("secondary") && world.id_drag_from) )
1951 world.id_drag_from = 0;
1952 }
1953 else
1954 {
1955 world.selected = -1;
1956 world.id_drag_from = 0;
1957 }
1958
1959 // Marble state updates
1960 // ========================================================================================================
1961 if( is_simulation_running() )
1962 {
1963 float old_time = world.sim_internal_time;
1964
1965 if( !world.st.buttons[ k_world_button_pause ].state )
1966 world.sim_internal_time = world.sim_internal_ref + (vg_time-world.sim_delta_ref) * world.sim_delta_speed;
1967 else
1968 world.sim_internal_time = vg_lerpf( world.sim_internal_time, world.sim_internal_ref + world.pause_offset_target, vg_time_delta*15.0f );
1969 world.sim_internal_delta = world.sim_internal_time-old_time;
1970
1971 world.sim_target = (int)floorf(world.sim_internal_time);
1972
1973 int success_this_frame = 0;
1974 int failure_this_frame = 0;
1975
1976 while( world.sim_frame < world.sim_target )
1977 {
1978 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
1979
1980 // Update splitter deltas
1981 for( int i = 0; i < world.h*world.w; i ++ )
1982 {
1983 struct cell *cell = &world.data[i];
1984 if( cell->config == k_cell_type_split )
1985 {
1986 cell->state &= ~FLAG_FLIP_ROTATING;
1987 }
1988 if( cell->state & (FLAG_IS_TRIGGER|FLAG_EMITTER) )
1989 cell->state &= ~FLAG_TRIGGERED;
1990 }
1991
1992 int alive_count = 0;
1993
1994 // Update fish positions
1995 for( int i = 0; i < world.num_fishes; i ++ )
1996 {
1997 struct fish *fish = &world.fishes[i];
1998
1999 if( fish->state == k_fish_state_soon_dead )
2000 fish->state = k_fish_state_dead;
2001
2002 if( fish->state == k_fish_state_soon_alive )
2003 fish->state = k_fish_state_alive;
2004
2005 if( fish->state < k_fish_state_alive )
2006 continue;
2007
2008 struct cell *cell_current = pcell( fish->pos );
2009
2010 if( fish->state == k_fish_state_alive )
2011 {
2012 // Apply to output
2013 if( cell_current->state & FLAG_OUTPUT )
2014 {
2015 for( int j = 0; j < arrlen( world.io ); j ++ )
2016 {
2017 struct cell_terminal *term = &world.io[j];
2018
2019 if( v2i_eq( term->pos, fish->pos ) )
2020 {
2021 struct terminal_run *run = &term->runs[ world.sim_run ];
2022 if( run->recv_count < vg_list_size( run->recieved ) )
2023 {
2024 if( fish->colour == run->steps[ run->recv_count ] )
2025 success_this_frame = 1;
2026 else
2027 failure_this_frame = 1;
2028
2029 run->recieved[ run->recv_count ++ ] = fish->colour;
2030 }
2031 else
2032 failure_this_frame = 1;
2033
2034 break;
2035 }
2036 }
2037
2038 fish->state = k_fish_state_dead;
2039 fish->death_time = -1000.0f;
2040 continue;
2041 }
2042
2043
2044 if( cell_current->config == k_cell_type_merge )
2045 {
2046 // Can only move up
2047 fish->dir[0] = 0;
2048 fish->dir[1] = -1;
2049 fish->flow_reversed = 0;
2050 }
2051 else
2052 {
2053 if( cell_current->config == k_cell_type_split )
2054 {
2055 // Flip flop L/R
2056 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
2057 fish->dir[1] = 0;
2058
2059 if( !(cell_current->state & FLAG_TARGETED) )
2060 cell_current->state ^= FLAG_FLIP_FLOP;
2061 }
2062 else
2063 {
2064 // Apply cell out-flow
2065 struct cell_description *desc = &cell_descriptions[ cell_current->config ];
2066
2067 v2i_copy( fish->flow_reversed? desc->start: desc->end, fish->dir );
2068 }
2069
2070 v2i pos_next;
2071 v2i_add( fish->pos, fish->dir, pos_next );
2072
2073 struct cell *cell_next = pcell( pos_next );
2074
2075 if( cell_next->state & (FLAG_CANAL|FLAG_OUTPUT) )
2076 {
2077 struct cell_description *desc = &cell_descriptions[ cell_next->config ];
2078
2079 if( cell_next->config == k_cell_type_merge )
2080 {
2081 if( fish->dir[0] == 0 )
2082 {
2083 fish->state = k_fish_state_dead;
2084 fish->death_time = world.sim_internal_time;
2085 }
2086 else
2087 fish->flow_reversed = 0;
2088 }
2089 else
2090 {
2091 if( cell_next->config == k_cell_type_split )
2092 {
2093 if( fish->dir[0] == 0 )
2094 {
2095 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
2096 cell_next->state |= FLAG_FLIP_ROTATING;
2097
2098 fish->flow_reversed = 0;
2099 }
2100 else
2101 {
2102 fish->state = k_fish_state_dead;
2103 fish->death_time = world.sim_internal_time;
2104 }
2105 }
2106 else
2107 fish->flow_reversed = ( fish->dir[0] != -desc->start[0] ||
2108 fish->dir[1] != -desc->start[1] )? 1: 0;
2109 }
2110 }
2111 else
2112 {
2113 if( world_check_pos_ok( fish->pos, 2 ) )
2114 fish->state = k_fish_state_bg;
2115 else
2116 {
2117 fish->state = k_fish_state_dead;
2118 fish->death_time = world.sim_internal_time;
2119 }
2120 }
2121 }
2122
2123 //v2i_add( fish->pos, fish->dir, fish->pos );
2124 }
2125 else if( fish->state == k_fish_state_bg )
2126 {
2127 v2i_add( fish->pos, fish->dir, fish->pos );
2128
2129 if( !world_check_pos_ok( fish->pos, 2 ) )
2130 {
2131 fish->state = k_fish_state_dead;
2132 fish->death_time = -1000.0f;
2133 }
2134 else
2135 {
2136 struct cell *cell_entry = pcell( fish->pos );
2137
2138 if( cell_entry->state & FLAG_CANAL )
2139 {
2140 if( cell_entry->config == k_cell_type_con_r || cell_entry->config == k_cell_type_con_u
2141 || cell_entry->config == k_cell_type_con_l || cell_entry->config == k_cell_type_con_d )
2142 {
2143 #ifdef VG_STEAM
2144 sw_set_achievement( "CAN_DO_THAT" );
2145 #endif
2146
2147 fish->state = k_fish_state_soon_alive;
2148
2149 fish->dir[0] = 0;
2150 fish->dir[1] = 0;
2151 fish->flow_reversed = 1;
2152
2153 switch( cell_entry->config )
2154 {
2155 case k_cell_type_con_r: fish->dir[0] = 1; break;
2156 case k_cell_type_con_l: fish->dir[0] = -1; break;
2157 case k_cell_type_con_u: fish->dir[1] = 1; break;
2158 case k_cell_type_con_d: fish->dir[1] = -1; break;
2159 }
2160 }
2161 }
2162 }
2163 }
2164 else { vg_error( "fish behaviour unimplemented for behaviour type (%d)\n" ); }
2165
2166 if( fish->state >= k_fish_state_alive )
2167 alive_count ++;
2168 }
2169
2170 // Second pass (triggers)
2171 for( int i = 0; i < world.num_fishes; i ++ )
2172 {
2173 struct fish *fish = &world.fishes[i];
2174
2175 // Apply movement
2176 if( fish->state == k_fish_state_alive )
2177 v2i_add( fish->pos, fish->dir, fish->pos );
2178
2179 if( fish->state == k_fish_state_alive || fish->state == k_fish_state_soon_alive )
2180 {
2181 struct cell *cell_current = pcell( fish->pos );
2182
2183 if( cell_current->state & FLAG_IS_TRIGGER )
2184 {
2185 int trigger_id = cell_current->links[0]?0:1;
2186
2187 struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
2188
2189 // Spawn new marble
2190 if( (target_peice->state & FLAG_EMITTER) && !(target_peice->state & FLAG_TRIGGERED))
2191 {
2192 if( world.num_fishes < vg_list_size( world.fishes ) )
2193 {
2194 struct fish *fish = &world.fishes[ world.num_fishes ];
2195 lcell( cell_current->links[trigger_id], fish->pos );
2196
2197 fish->state = k_fish_state_soon_alive;
2198 fish->colour = target_peice->emit[ trigger_id ];
2199
2200 if( target_peice->config != k_cell_type_stub )
2201 {
2202 struct cell_description *desc = &cell_descriptions[ target_peice->config ];
2203 v2i_copy( desc->start, fish->dir );
2204 fish->flow_reversed = 1;
2205
2206 world.num_fishes ++;
2207 alive_count ++;
2208 }
2209 }
2210 else
2211 vg_warn( "Max marbles exceeded\n" );
2212 }
2213 else
2214 {
2215 if( trigger_id )
2216 target_peice->state |= FLAG_FLIP_FLOP;
2217 else
2218 target_peice->state &= ~FLAG_FLIP_FLOP;
2219 }
2220
2221 cell_current->state |= FLAG_TRIGGERED;
2222 }
2223 }
2224 }
2225
2226 // Third pass (collisions)
2227 struct fish *fi, *fj;
2228
2229 for( int i = 0; i < world.num_fishes; i ++ )
2230 {
2231 fi = &world.fishes[i];
2232
2233 if( (fi->state == k_fish_state_alive) |
2234 (fi->state == k_fish_state_soon_alive) )
2235 {
2236 int continue_again = 0;
2237
2238 for( int j = i+1; j < world.num_fishes; j ++ )
2239 {
2240 fj = &world.fishes[j];
2241
2242 if( (fj->state == k_fish_state_alive) |
2243 (fj->state == k_fish_state_soon_alive) )
2244 {
2245 v2i fi_prev;
2246 v2i fj_prev;
2247
2248 v2i_sub( fi->pos, fi->dir, fi_prev );
2249 v2i_sub( fj->pos, fj->dir, fj_prev );
2250
2251 int
2252 collide_next_frame = (
2253 (fi->pos[0] == fj->pos[0]) &&
2254 (fi->pos[1] == fj->pos[1]))? 1: 0,
2255 collide_this_frame = (
2256 (fi_prev[0] == fj->pos[0]) &&
2257 (fi_prev[1] == fj->pos[1]) &&
2258 (fj_prev[0] == fi->pos[0]) &&
2259 (fj_prev[1] == fi->pos[1])
2260 )? 1: 0;
2261
2262 if( collide_next_frame || collide_this_frame )
2263 {
2264 #ifdef VG_STEAM
2265 sw_set_achievement( "BANG" );
2266 #endif
2267
2268 // Shatter death (+0.5s)
2269 float death_time = world.sim_internal_time + ( collide_this_frame? 0.0f: 0.5f );
2270
2271 fi->state = k_fish_state_soon_dead;
2272 fj->state = k_fish_state_soon_dead;
2273 fi->death_time = death_time;
2274 fj->death_time = death_time;
2275
2276 continue_again = 1;
2277 break;
2278 }
2279 }
2280 }
2281 if( continue_again )
2282 continue;
2283 }
2284 }
2285
2286 // Spawn fishes
2287 for( int i = 0; i < arrlen( world.io ); i ++ )
2288 {
2289 struct cell_terminal *term = &world.io[ i ];
2290 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2291
2292 if( is_input )
2293 {
2294 if( world.sim_frame < term->runs[ world.sim_run ].step_count )
2295 {
2296 i8 emit = term->runs[ world.sim_run ].steps[ world.sim_frame ];
2297 if( emit == -1 )
2298 continue;
2299
2300 struct fish *fish = &world.fishes[ world.num_fishes ];
2301 v2i_copy( term->pos, fish->pos );
2302
2303 fish->state = k_fish_state_alive;
2304 fish->colour = emit;
2305
2306 struct cell *cell_ptr = pcell( fish->pos );
2307
2308 if( cell_ptr->config != k_cell_type_stub )
2309 {
2310 if( world.num_fishes < vg_list_size(world.fishes))
2311 {
2312 struct cell_description *desc = &cell_descriptions[ cell_ptr->config ];
2313
2314 v2i_copy( desc->start, fish->dir );
2315 fish->flow_reversed = 1;
2316
2317 world.num_fishes ++;
2318 alive_count ++;
2319 }
2320 else
2321 vg_warn( "Max marbles exceeded\n" );
2322 }
2323 }
2324 }
2325 }
2326
2327 if( alive_count == 0 )
2328 {
2329 world.completed = 1;
2330
2331 for( int i = 0; i < arrlen( world.io ); i ++ )
2332 {
2333 struct cell_terminal *term = &world.io[ i ];
2334
2335 if( pcell(term->pos)->state & FLAG_OUTPUT )
2336 {
2337 struct terminal_run *run = &term->runs[ world.sim_run ];
2338
2339 if( run->recv_count == run->step_count )
2340 {
2341 for( int j = 0; j < run->step_count; j ++ )
2342 {
2343 if( run->recieved[j] != run->steps[j] )
2344 {
2345 world.completed = 0;
2346 break;
2347 }
2348 }
2349 }
2350 else
2351 {
2352 world.completed = 0;
2353 break;
2354 }
2355 }
2356 }
2357
2358 if( world.completed )
2359 {
2360 if( world.sim_run < world.max_runs-1 )
2361 {
2362 vg_success( "Run passed, starting next\n" );
2363 world.sim_run ++;
2364 world.sim_frame = 0;
2365 world.sim_target = 0;
2366 world.num_fishes = 0;
2367
2368 // Reset timing reference points
2369 world.sim_delta_ref = vg_time;
2370 world.sim_internal_ref = 0.0f;
2371
2372 if( world.st.buttons[ k_world_button_pause ].state )
2373 world.pause_offset_target = 0.5f;
2374 else
2375 world.pause_offset_target = 0.0f;
2376
2377 world.sim_internal_time = 0.0f;
2378
2379 for( int i = 0; i < world.w*world.h; i ++ )
2380 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
2381
2382 continue;
2383 }
2384 else
2385 {
2386 vg_success( "Level passed!\n" );
2387
2388 u32 score = 0;
2389 for( int i = 0; i < world.w*world.h; i ++ )
2390 if( world.data[ i ].state & FLAG_CANAL )
2391 score ++;
2392
2393 world.score = score;
2394 world.time = world.sim_frame;
2395
2396 // Copy into career data
2397 if( world.pCmpLevel )
2398 {
2399 career_pass_level( world.pCmpLevel, world.score, 1 );
2400 }
2401
2402 sfx_set_play( &audio_tones, &audio_system_balls_extra, 9 );
2403 failure_this_frame = 0;
2404 success_this_frame = 0;
2405 }
2406 }
2407 else
2408 {
2409 #ifdef VG_STEAM
2410 if( world.sim_run > 0 )
2411 sw_set_achievement( "GOOD_ENOUGH" );
2412 #endif
2413
2414 vg_error( "Level failed :(\n" );
2415 }
2416
2417 simulation_stop();
2418 break;
2419 }
2420
2421 world.sim_frame ++;
2422 }
2423
2424 // Sounds
2425 if( failure_this_frame )
2426 {
2427 sfx_set_play( &audio_tones, &audio_system_balls_extra, 0 );
2428 }
2429 else if( success_this_frame )
2430 {
2431 static int succes_counter = 0;
2432
2433 sfx_set_play( &audio_tones, &audio_system_balls_extra, 1+(succes_counter++) );
2434
2435 if( succes_counter == 7 )
2436 succes_counter = 0;
2437 }
2438
2439 // Position update
2440 // =====================================================================================================
2441
2442 world.frame_lerp = world.sim_internal_time - floorf( world.sim_internal_time );
2443
2444 for( int i = 0; i < world.num_fishes; i ++ )
2445 {
2446 struct fish *fish = &world.fishes[i];
2447
2448 if( fish->state == k_fish_state_dead )
2449 continue;
2450
2451 if( fish->state == k_fish_state_soon_dead && (world.sim_internal_time > fish->death_time) )
2452 continue; // Todo: particle thing?
2453
2454 struct cell *cell = pcell(fish->pos);
2455 struct cell_description *desc = &cell_descriptions[ cell->config ];
2456
2457 v2f const *curve;
2458
2459 float t = world.frame_lerp;
2460 if( fish->flow_reversed && !desc->is_linear )
2461 t = 1.0f-t;
2462
2463 v2_copy( fish->physics_co, fish->physics_v );
2464
2465 switch( cell->config )
2466 {
2467 case k_cell_type_merge:
2468 if( fish->dir[0] == 1 )
2469 curve = curve_12;
2470 else
2471 curve = curve_9;
2472 break;
2473 case k_cell_type_con_r: curve = curve_1; break;
2474 case k_cell_type_con_l: curve = curve_4; break;
2475 case k_cell_type_con_u: curve = curve_2; break;
2476 case k_cell_type_con_d: curve = curve_8; break;
2477 case 3: curve = curve_3; break;
2478 case 6: curve = curve_6; break;
2479 case 9: curve = curve_9; break;
2480 case 12: curve = curve_12; break;
2481 case 7:
2482 if( t > curve_7_linear_section )
2483 {
2484 t -= curve_7_linear_section;
2485 t *= (1.0f/(1.0f-curve_7_linear_section));
2486
2487 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
2488 }
2489 else curve = NULL;
2490 break;
2491 default: curve = NULL; break;
2492 }
2493
2494 if( curve )
2495 {
2496 float t2 = t * t;
2497 float t3 = t * t * t;
2498
2499 float cA = 3.0f*t2 - 3.0f*t3;
2500 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
2501 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
2502
2503 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
2504 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
2505 fish->physics_co[0] += (float)fish->pos[0];
2506 fish->physics_co[1] += (float)fish->pos[1];
2507 }
2508 else
2509 {
2510 v2f origin;
2511 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
2512 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
2513
2514 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
2515 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
2516 }
2517
2518 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
2519 v2_divs( fish->physics_v, world.sim_internal_delta, fish->physics_v );
2520 }
2521 }
2522 }
2523
2524 static void render_tile( v2i pos, struct cell *ptr, v4f const regular_colour,
2525 v4f const selected_colour, int with_glow )
2526 {
2527 int selected = world.selected == pos[1]*world.w + pos[0];
2528 int uv[2];
2529
2530 uv[0] = ptr->config & 0x3;
2531 uv[1] = ptr->config >> 2;
2532
2533 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
2534 (float)pos[0],
2535 (float)pos[1],
2536 uv[0],
2537 uv[1]
2538 );
2539
2540 if( with_glow )
2541 {
2542 glUniform3fv( SHADER_UNIFORM( shader_tile_main, "uGlowA" ),
2543 1, ptr->glow[0] );
2544 glUniform3fv( SHADER_UNIFORM( shader_tile_main, "uGlowB" ),
2545 1, ptr->glow[1] );
2546 }
2547 else
2548 {
2549 glUniform3f( SHADER_UNIFORM( shader_tile_main, "uGlowA" ),
2550 0.0f,
2551 0.0f,
2552 0.0f );
2553 glUniform3f( SHADER_UNIFORM( shader_tile_main, "uGlowB" ),
2554 0.0f,
2555 0.0f,
2556 0.0f );
2557 }
2558
2559 if( selected )
2560 {
2561 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
2562 draw_mesh( 0, 2 );
2563 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
2564 }
2565 else
2566 draw_mesh( 0, 2 );
2567 }
2568
2569 // Renders specific chunk of tiles
2570 static void render_tile_block( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
2571 {
2572 v2i full_start = { 0,0 };
2573 v2i full_end = { world.w, world.h };
2574
2575 if( !start || !end )
2576 {
2577 start = full_start;
2578 end = full_end;
2579 }
2580
2581 for( int y = start[1]; y < end[1]; y ++ )
2582 {
2583 for( int x = start[0]; x < end[0]; x ++ )
2584 {
2585 v2i pos = { x, y };
2586 struct cell *cell = pcell( pos );
2587
2588 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
2589 render_tile( pos, cell, regular_colour, selected_colour, 0 );
2590 }
2591 }
2592 }
2593
2594 // Renders all tiles in the command list
2595 static void render_tiles( v4f const regular_colour, v4f const selected_colour,
2596 int with_glow )
2597 {
2598 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
2599
2600 struct render_list
2601 {
2602 struct render_cmd *arr;
2603 u32 count;
2604 }
2605 render_lists[] = {
2606 { world.cmd_buf_tiles, world.tile_count },
2607 { world.cmd_buf_specials, world.tile_special_count }
2608 };
2609
2610 int world_paused = world.st.buttons[k_world_button_pause].state;
2611 if( with_glow && !world_paused )
2612 {
2613 for( int i = 0; i < world.num_fishes; i ++ )
2614 {
2615 struct fish *fish = &world.fishes[i];
2616
2617 if( !(fish->state == k_fish_state_alive ||
2618 fish->state == k_fish_state_soon_alive) ) continue;
2619
2620 struct cell *cell_x = pcell( fish->pos );
2621 v3f glow_colour;
2622 colour_code_v3( fish->colour, glow_colour );
2623
2624 int c = 0;
2625 if( cell_x->config == k_cell_type_split )
2626 c = cell_x->state & FLAG_FLIP_FLOP? 1:0;
2627
2628 if( cell_x->config == k_cell_type_merge )
2629 c = fish->dir[0]==-1?1:0;
2630
2631 v3_muladds( cell_x->glow[c], glow_colour,
2632 powf(world.frame_lerp,2.0f)*0.03f * world.sim_delta_speed,
2633 cell_x->glow[c]);
2634 }
2635 }
2636
2637 for( int i = 0; i < vg_list_size( render_lists ); i ++ )
2638 {
2639 struct render_list *list = &render_lists[i];
2640 for( int j = 0; j < list->count; j ++ )
2641 {
2642 struct render_cmd *cmd = &list->arr[j];
2643 struct cell *cell = cmd->ptr;
2644
2645 render_tile( cmd->pos, cell, regular_colour, selected_colour, with_glow );
2646 }
2647 }
2648 }
2649
2650 static int world_button_exec( struct world_button *btn, v2f texture, v3f colour, enum world_button_status *status )
2651 {
2652 static v2i click_grab = { -9999, -9999 };
2653
2654 // Reset click_grab
2655 if( !btn )
2656 {
2657 click_grab[0] = -9999;
2658 click_grab[1] = -9999;
2659 return 0;
2660 }
2661
2662 v2i click_tile = { world.tile_x, world.tile_y };
2663
2664 int triggered = 0;
2665 int is_hovering = v2i_eq( click_tile, btn->position ) && !gui_want_mouse();
2666
2667 // Set up light targets before logic runs
2668 if( btn->state )
2669 btn->light_target = is_hovering? 0.7f: 0.6f;
2670 else
2671 btn->light_target = is_hovering? 0.2f: 0.0f;
2672
2673 if( vg_get_button( "primary" ) && is_hovering )
2674 btn->light_target = 1.0f;
2675
2676 // Process click action
2677 if( is_hovering )
2678 {
2679 if( vg_get_button_down( "primary" ) && is_hovering )
2680 v2i_copy( click_tile, click_grab );
2681 else if( v2i_eq( click_grab, click_tile ) && vg_get_button_up( "primary" ) )
2682 {
2683 // Click event
2684 *status = btn->state? k_world_button_on_disable: k_world_button_on_enable;
2685
2686 if( btn->mode == k_world_button_mode_toggle )
2687 btn->state ^= 0x1;
2688
2689 sfx_set_play( &audio_clicks, &audio_system_ui, btn->state? 1:0 );
2690 triggered = 1;
2691 }
2692 }
2693
2694 // Drawing stage
2695 v4f final_colour;
2696
2697 btn->light = vg_lerpf( btn->light, btn->light_target + btn->extra_light, vg_time_delta*26.0f );
2698
2699 v3_copy( colour, final_colour );
2700 final_colour[3] = btn->light;
2701
2702 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2703 btn->position[0],
2704 btn->position[1],
2705 texture[0],
2706 texture[1]
2707 );
2708 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
2709 draw_mesh( 0, 2 );
2710
2711 return triggered;
2712 }
2713
2714 static void level_selection_buttons(void)
2715 {
2716 v3f tutorial_colour = { 0.204f, 0.345f, 0.553f };
2717 v3f locked_colour = { 0.2f, 0.2f, 0.2f };
2718
2719 struct cmp_level *switch_level_to = NULL;
2720
2721 for( int i = 0; i < vg_list_size( career_packs ); i ++ )
2722 {
2723 struct career_level_pack *grid = &career_packs[i];
2724
2725 for( int j = 0; j < grid->count; j ++ )
2726 {
2727 struct cmp_level *lvl = &grid->pack[ j ];
2728
2729 if( world.pCmpLevel == lvl )
2730 lvl->btn.extra_light = 0.35f + fabsf(sinf( vg_time * 2.0f )) * 0.05f;
2731 else lvl->btn.extra_light = 0.2f;
2732
2733 if( lvl->completed_score )
2734 lvl->btn.extra_light += 0.6f;
2735
2736 enum world_button_status status;
2737 if( world_button_exec(
2738 &lvl->btn,
2739 (v2f){0.0f,0.0f},
2740 lvl->unlocked? (lvl->is_tutorial? tutorial_colour: grid->primary_colour): locked_colour,
2741 &status
2742 ))
2743 {
2744 if( status == k_world_button_on_enable && lvl->unlocked )
2745 switch_level_to = lvl;
2746 }
2747 }
2748 }
2749
2750 if( switch_level_to )
2751 {
2752 world.st.lvl_to_load = switch_level_to;
2753 world.st.lvl_load_time = vg_time + 0.25f;
2754 world.st.world_transition = 1.0f;
2755
2756 /*
2757 if( console_changelevel( 1, &switch_level_to->map_name ) )
2758 {
2759 world.pCmpLevel = switch_level_to;
2760 gen_level_text( world.pCmpLevel );
2761 }
2762 */
2763 }
2764 }
2765
2766 static void render_sprite( enum sprites_auto_combine_index id, v3f pos )
2767 {
2768 struct vg_sprite *sp = &sprites_auto_combine[ id ];
2769
2770 glUniform4fv( SHADER_UNIFORM( shader_sprite, "uUv" ), 1, sp->uv_xywh );
2771 glUniform3f( SHADER_UNIFORM( shader_sprite, "uPos" ),
2772 pos[0], pos[1], pos[2] * world.st.world_transition );
2773
2774 draw_mesh( 0, 2 );
2775 }
2776
2777 static void vg_framebuffer_resize(int w, int h)
2778 {
2779 glBindTexture( GL_TEXTURE_2D, world.st.colourbuffer );
2780 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, w, h,
2781 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
2782
2783 for( int i=0; i<2; i++ )
2784 {
2785 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[i] );
2786 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB,
2787 w/EFFECT_BUFFER_RATIO, h/EFFECT_BUFFER_RATIO,
2788 0, GL_RGB, GL_UNSIGNED_BYTE, NULL );
2789 }
2790 }
2791
2792 void vg_render(void)
2793 {
2794 if( enable_bloom || enable_vignette )
2795 glBindFramebuffer( GL_FRAMEBUFFER, world.st.framebuffer );
2796 else
2797 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
2798
2799 glViewport( 0,0, vg_window_x, vg_window_y );
2800
2801 glDisable( GL_DEPTH_TEST );
2802 glClearColor( 0.14f, 0.14f, 0.14f, 1.0f );
2803 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
2804
2805 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
2806 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
2807
2808 int const circle_base = 6;
2809 int const filled_start = circle_base+0;
2810 int const filled_count = circle_base+32;
2811 int const empty_start = circle_base+32;
2812 int const empty_count = circle_base+32*2;
2813
2814 #if 0
2815 struct world_theme *theme = &world_themes[ world_theme_id ];
2816 #else
2817 struct world_theme *theme = &world_themes[ 0 ];
2818 #endif
2819
2820 if( !world.initialzed )
2821 return;
2822
2823 // Extract render commands
2824 world.tile_count = 0;
2825 world.tile_special_count = 0;
2826
2827 for( int y = 1; y < world.h-1; y ++ )
2828 {
2829 for( int x = 1; x < world.w-1; x ++ )
2830 {
2831 struct cell *cell = pcell((v2i){x,y});
2832
2833 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER|FLAG_INPUT_NICE) )
2834 {
2835 struct render_cmd *cmd;
2836
2837 if(
2838 (cell->config == k_cell_type_split && (cell->state & FLAG_CANAL))
2839 || (cell->state & (FLAG_EMITTER|FLAG_IS_TRIGGER))
2840 )
2841 cmd = &world.cmd_buf_tiles[ world.max_commands - (++ world.tile_special_count) ];
2842 else
2843 cmd = &world.cmd_buf_tiles[ world.tile_count ++ ];
2844
2845 cmd->pos[0] = x;
2846 cmd->pos[1] = y;
2847 cmd->ptr = cell;
2848
2849 int world_paused = world.st.buttons[k_world_button_pause].state;
2850 if( !world_paused )
2851 {
2852 float decay = 1.0f - world.sim_delta_speed*0.005f;
2853 v3_muls( cell->glow[0], decay, cell->glow[0] );
2854 v3_muls( cell->glow[1], decay, cell->glow[1] );
2855 }
2856 }
2857 }
2858 }
2859
2860 world.cmd_buf_specials = &world.cmd_buf_tiles[ world.max_commands - world.tile_special_count ];
2861
2862 // BACKGROUND
2863 // ========================================================================================================
2864 use_mesh( &world.shapes );
2865
2866 SHADER_USE( shader_background );
2867 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2868
2869 glActiveTexture( GL_TEXTURE0 );
2870 glBindTexture( GL_TEXTURE_2D, world.background_data );
2871 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
2872
2873 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
2874 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.05f );
2875
2876 glActiveTexture( GL_TEXTURE1 );
2877 glBindTexture( GL_TEXTURE_2D, world.random_samples );
2878 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
2879 glUniform1f( SHADER_UNIFORM( shader_background, "uVisibility" ), 1.0f ); //world.st.world_transition );
2880
2881 draw_mesh( 0, 2 );
2882
2883 // TILESET BACKGROUND LAYER
2884 // ========================================================================================================
2885 use_mesh( &world.shapes );
2886 SHADER_USE( shader_tile_main );
2887
2888 m2x2f subtransform;
2889 m2x2_identity( subtransform );
2890 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2891 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2892 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
2893 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
2894 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uVisibility" ), world.st.world_transition * 2.0f );
2895
2896 glEnable(GL_BLEND);
2897 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2898 glBlendEquation(GL_FUNC_ADD);
2899
2900 // rebind textures
2901 vg_tex2d_bind( &tex_tile_data, 0 );
2902 vg_tex2d_bind( theme->tex_tiles, 1 );
2903 vg_tex2d_bind( &tex_tile_glow, 2 );
2904
2905 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2906 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2907 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlow" ), 2 );
2908
2909 glUniform3fv( SHADER_UNIFORM( shader_tile_main, "uShadowing" ), 1, theme->col_shadow );
2910
2911 render_tiles( colour_default, colour_default, 1 );
2912
2913 // MARBLES
2914 // ========================================================================================================
2915 SHADER_USE( shader_ball );
2916 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2917
2918 vg_tex2d_bind( &tex_ball_noise, 0 );
2919 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
2920
2921 if( world.st.buttons[ k_world_button_sim ].state )
2922 {
2923 for( int i = 0; i < world.num_fishes; i ++ )
2924 {
2925 struct fish *fish = &world.fishes[i];
2926 v3f render_pos;
2927 render_pos[2] = 1.0f;
2928
2929 if( fish->state == k_fish_state_dead || fish->state == k_fish_state_soon_dead )
2930 {
2931 float death_anim_time = world.sim_internal_time - fish->death_time;
2932
2933 // Death animation
2934 if( death_anim_time > 0.0f && death_anim_time < 1.0f )
2935 {
2936 float amt = 1.0f-death_anim_time*death_anim_time;
2937
2938 v2_muladds( fish->physics_co, fish->physics_v, -1.0f * world.sim_internal_delta * amt, fish->physics_co );
2939 render_pos[2] = amt;
2940 }
2941 else if( world.sim_internal_time > fish->death_time )
2942 continue;
2943 }
2944 else if( fish->state == k_fish_state_bg )
2945 continue;
2946
2947 v2_copy( fish->physics_co, render_pos );
2948
2949 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2950 colour_code_v3( fish->colour, dot_colour );
2951
2952 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
2953 glUniform3fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, render_pos );
2954 glUniform2f( SHADER_UNIFORM( shader_ball, "uTexOffset" ), (float)i * 1.2334, (float)i * -0.3579f );
2955 draw_mesh( 0, 2 );
2956 }
2957 }
2958
2959 // TILESET FOREGROUND LAYER
2960 // ========================================================================================================
2961 SHADER_USE( shader_tile_main );
2962
2963 // Re Bind textures
2964 vg_tex2d_bind( &tex_tile_data, 0 );
2965 vg_tex2d_bind( theme->tex_tiles, 1 );
2966 vg_tex2d_bind( &tex_tile_glow, 2 );
2967
2968 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
2969 render_tiles( colour_default, colour_selected, 0 );
2970
2971 // Draw splitters
2972 for( int i = 0; i < world.tile_special_count; i ++ )
2973 {
2974 struct render_cmd *cmd = &world.cmd_buf_specials[i];
2975 struct cell *cell = cmd->ptr;
2976
2977 if( cell->config == k_cell_type_split )
2978 {
2979 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
2980
2981 if( cell->state & FLAG_FLIP_ROTATING )
2982 {
2983 if( (world.frame_lerp > curve_7_linear_section) )
2984 {
2985 float const rotation_speed = 0.4f;
2986 if( (world.frame_lerp < 1.0f-rotation_speed) )
2987 {
2988 float t = world.frame_lerp - curve_7_linear_section;
2989 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
2990 t += 1.0f;
2991
2992 rotation *= t;
2993 }
2994 else
2995 rotation *= -1.0f;
2996 }
2997 }
2998
2999 m2x2_create_rotation( subtransform, rotation );
3000
3001 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main,"uSubTransform" ),
3002 1, GL_FALSE, (float *)subtransform );
3003
3004 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
3005 (float)cmd->pos[0],
3006 (float)cmd->pos[1] + 0.125f,
3007 cell->state & FLAG_TARGETED? 3.0f: 2.0f,
3008 3.0f
3009 );
3010 draw_mesh( 0, 2 );
3011 }
3012 }
3013
3014 // EDIT OVERLAY
3015 // ========================================================================================================
3016 if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) && !world.id_drag_from )
3017 {
3018 v2i new_begin = { world.tile_x - 2, world.tile_y - 2 };
3019 v2i new_end = { world.tile_x + 2, world.tile_y + 2 };
3020
3021 world.data[ world.selected ].state ^= FLAG_CANAL;
3022 map_reclassify( new_begin, new_end, 0 );
3023
3024 m2x2_identity( subtransform );
3025 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f );
3026 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
3027 glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
3028
3029 render_tile_block( new_begin, new_end, colour_default, colour_default );
3030
3031 world.data[ world.selected ].state ^= FLAG_CANAL;
3032 map_reclassify( new_begin, new_end, 0 );
3033 }
3034
3035 // BUTTONS
3036 // ========================================================================================================
3037 SHADER_USE( shader_buttons );
3038 glUniformMatrix3fv( SHADER_UNIFORM( shader_buttons, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3039
3040 vg_tex2d_bind( &tex_buttons, 0 );
3041 glUniform1i( SHADER_UNIFORM( shader_buttons, "uTexMain" ), 0 );
3042
3043 enum world_button_status stat;
3044 int world_paused = world.st.buttons[k_world_button_pause].state;
3045 int world_running = world.st.buttons[k_world_button_sim].state;
3046
3047 float sim_icon_x = world_paused? 3.0f: (world_running? 2.0f: 0.0f);
3048
3049 v3f btn_dark_blue = { 0.204f, 0.345f, 0.553f };
3050 v3f btn_orange = { 0.553f, 0.345f, 0.204f };
3051
3052 if( world_button_exec( &world.st.buttons[k_world_button_sim], (v2f){ sim_icon_x, 3.0f }, btn_dark_blue, &stat ))
3053 {
3054 if( stat == k_world_button_on_enable )
3055 {
3056 simulation_start();
3057
3058 if( world_paused )
3059 world.pause_offset_target = 0.5f;
3060 }
3061 else
3062 {
3063 if( world_paused )
3064 {
3065 // Trigger single step
3066 world.pause_offset_target += 1.0f;
3067 world.st.buttons[k_world_button_sim].state = 1;
3068 }
3069 else
3070 {
3071 simulation_stop();
3072 }
3073 }
3074 }
3075
3076 if( world_button_exec( &world.st.buttons[k_world_button_pause], (v2f){ 1.0f, 3.0f }, btn_dark_blue, &stat ))
3077 {
3078 world.sim_internal_ref = world.sim_internal_time;
3079 world.sim_delta_ref = vg_time;
3080
3081 if( stat == k_world_button_on_enable )
3082 {
3083 float time_frac = world.sim_internal_time-floorf(world.sim_internal_time);
3084 world.pause_offset_target = 0.5f - time_frac;
3085 }
3086 else
3087 world.pause_offset_target = 0.0f;
3088 }
3089
3090 if( world_button_exec( &world.st.buttons[k_world_button_speedy], (v2f){ 0.0f, 2.0f }, btn_orange, &stat ))
3091 {
3092 world.sim_delta_speed = stat == k_world_button_on_enable? 10.0f: 2.5f;
3093
3094 if( !world_paused )
3095 {
3096 world.sim_delta_ref = vg_time;
3097 world.sim_internal_ref = world.sim_internal_time;
3098 }
3099 }
3100
3101 if( world_button_exec( &world.st.buttons[k_world_button_settings], (v2f){ 1.0f, 2.0f }, btn_orange, &stat ))
3102 {
3103 world.st.state = stat == k_world_button_on_enable?
3104 k_game_state_settings: k_game_state_main;
3105 }
3106
3107 level_selection_buttons();
3108
3109 if( vg_get_button_up( "primary" ) )
3110 world_button_exec( NULL, NULL, NULL, NULL );
3111
3112 // I/O ARRAYS
3113 // ========================================================================================================
3114
3115 //glEnable(GL_BLEND);
3116 SHADER_USE( shader_tile_colour );
3117 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3118
3119 for( int i = 0; i < arrlen( world.io ); i ++ )
3120 {
3121 struct cell_terminal *term = &world.io[ i ];
3122 struct cell *cell = pcell(term->pos);
3123
3124 int is_input = cell->state & FLAG_INPUT;
3125 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
3126
3127 if( cell->state & FLAG_EMITTER )
3128 {
3129 for( int j = 0; j < 2; j ++ )
3130 {
3131 if( cell->emit[j] != -1 )
3132 {
3133 colour_code_v3( cell->emit[j], dot_colour );
3134
3135 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3136 term->pos[0] + 0.25f + (float)j * 0.5f,
3137 term->pos[1] + 0.25f,
3138 0.12f
3139 );
3140
3141 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3142 draw_mesh( filled_start, filled_count );
3143 }
3144 }
3145 continue;
3146 }
3147
3148 for( int k = 0; k < term->run_count; k ++ )
3149 {
3150 float arr_base = is_input? 1.2f: -0.2f,
3151 run_offset = (is_input? 0.2f: -0.2f) * (float)k,
3152 y_position = is_input?
3153 (arr_base + (float)term->pos[1] + (float)(term->run_count-1)*0.2f) - run_offset:
3154 (float)term->pos[1] + arr_base + run_offset;
3155
3156 v4f bar_colour;
3157 int bar_draw = 0;
3158
3159 if( is_simulation_running() )
3160 {
3161 if( k == world.sim_run )
3162 {
3163 float a = fabsf(sinf( vg_time * 2.0f )) * 0.075f + 0.075f;
3164
3165 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, a }, bar_colour );
3166 }
3167 else
3168 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
3169
3170 bar_draw = 1;
3171 }
3172 else if( 1 || k & 0x1 )
3173 {
3174 if( k & 0x1 )
3175 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, 0.07f }, bar_colour );
3176 else
3177 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
3178
3179 bar_draw = 1;
3180 }
3181
3182 if( bar_draw )
3183 {
3184 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, bar_colour );
3185 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3186 (float)term->pos[0], y_position - 0.1f, 1.0f );
3187
3188 draw_mesh( 2, 2 );
3189 }
3190
3191 for( int j = 0; j < term->runs[k].step_count; j ++ )
3192 {
3193 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3194 (float)term->pos[0] + 0.2f + 0.2f * (float)j,
3195 y_position,
3196 0.1f
3197 );
3198
3199 if( is_input )
3200 {
3201 i8 colour = term->runs[k].steps[j];
3202 if( colour != -1 )
3203 {
3204 colour_code_v3( colour, dot_colour );
3205 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3206
3207 // Draw filled if tick not passed, draw empty if empty
3208 if( (world.sim_frame > j && world.sim_run >= k) || world.sim_run > k )
3209 draw_mesh( empty_start, empty_count );
3210 else
3211 draw_mesh( filled_start, filled_count );
3212 }
3213 }
3214 else
3215 {
3216
3217 if( term->runs[k].recv_count > j )
3218 {
3219 colour_code_v3( term->runs[k].recieved[j], dot_colour );
3220 v3_muls( dot_colour, 0.8f, dot_colour );
3221 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3222
3223 draw_mesh( filled_start, filled_count );
3224 }
3225
3226 colour_code_v3( term->runs[k].steps[j], dot_colour );
3227 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
3228
3229 draw_mesh( empty_start, empty_count );
3230 }
3231 }
3232 }
3233 }
3234
3235 // SPRITES
3236 // ========================================================================================================
3237 SHADER_USE( shader_sprite );
3238 glUniformMatrix3fv( SHADER_UNIFORM( shader_sprite, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3239
3240 vg_tex2d_bind( &tex_sprites, 0 );
3241 glUniform1i( SHADER_UNIFORM( shader_sprite, "uTexMain" ), 0 );
3242
3243 for( int i = 0; i < world.tile_special_count; i ++ )
3244 {
3245 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3246 struct cell *cell = cmd->ptr;
3247
3248 if( (cell->config == k_cell_type_split) || (cell->state & FLAG_EMITTER) )
3249 {
3250 v2f center = { cmd->pos[0] + 0.5f, cmd->pos[1] + 0.5f };
3251
3252 v3f p0 = { 0.0f, 0.0f, 4.0f };
3253 v3f p1 = { 0.0f, 0.0f, 4.0f };
3254
3255 v2_add( center, (v2f){ -0.25f, -0.25f }, p0 );
3256 v2_add( center, (v2f){ 0.25f, -0.25f }, p1 );
3257
3258 render_sprite( k_sprite_jack_1, p0 );
3259 render_sprite( k_sprite_jack_2, p1 );
3260 }
3261 else if( cell->state & FLAG_IS_TRIGGER )
3262 {
3263 v3f p0 = { 0.0f, 0.0f, 4.0f };
3264
3265 struct cell_description *desc = &cell_descriptions[ cell->config ];
3266
3267 v2_add( (v2f){ cmd->pos[0], cmd->pos[1] }, desc->trigger_pos, p0 );
3268 render_sprite( desc->trigger_sprite, p0 );
3269 }
3270 }
3271
3272 // TEXT ELEMENTS
3273 // ========================================================================================================
3274 // Old style
3275 m3x3f mvp_text;
3276 m3x3_identity( mvp_text );
3277 m3x3_scale( mvp_text, (v3f){
3278 1.0f/ ((float)UI_GLYPH_SPACING_X*4.0f),
3279 1.0f/ -((float)UI_GLYPH_SPACING_X*4.0f),
3280 1.0f
3281 });
3282
3283 m3x3_mul( vg_pv, mvp_text, mvp_text );
3284 ui_draw( &world.st.world_text, mvp_text );
3285
3286 // WIRES
3287 // ========================================================================================================
3288 glEnable(GL_BLEND);
3289
3290 SHADER_USE( shader_wire );
3291 glBindVertexArray( world.wire.vao );
3292
3293 glUniformMatrix3fv( SHADER_UNIFORM( shader_wire, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
3294
3295 v4f const wire_left_colour = { 0.9f, 0.9f, 0.9f, 1.0f };
3296 v4f const wire_right_colour = { 0.5f, 0.5f, 0.5f, 1.0f };
3297 v4f const wire_drag_colour = { 0.3f, 0.3f, 0.3f, 0.6f };
3298
3299 glUniform1f( SHADER_UNIFORM( shader_wire, "uTime" ), world.frame_lerp );
3300 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), 0.0f );
3301
3302 if( world.id_drag_from )
3303 {
3304 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_drag_colour );
3305 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), 0.4f );
3306 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), world.drag_from_co[0], world.drag_from_co[1], 0.20f*world.st.world_transition );
3307 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.20f*world.st.world_transition );
3308 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
3309 }
3310
3311 // Pulling animation
3312 float rp_x1 = world.frame_lerp*9.0f;
3313 float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f;
3314 float rp_x2 = 1.0f-rp_xa;
3315
3316 for( int i = 0; i < world.tile_special_count; i ++ )
3317 {
3318 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3319 struct cell *cell = cmd->ptr;
3320
3321 if( cell->state & FLAG_TARGETED )
3322 {
3323 for( int j = 0; j < 2; j ++ )
3324 {
3325 if( !cell->links[j] )
3326 continue;
3327
3328 struct cell *other_cell = &world.data[ cell->links[ j ]];
3329 struct cell_description *desc = &cell_descriptions[ other_cell->config ];
3330
3331 int x2 = cell->links[j] % world.w;
3332 int y2 = (cell->links[j] - x2) / world.w;
3333
3334 v2f startpoint;
3335 v2f endpoint;
3336
3337 endpoint[0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f);
3338 endpoint[1] = (float)cmd->pos[1] + 0.25f;
3339
3340 startpoint[0] = x2;
3341 startpoint[1] = y2;
3342
3343 v2_add( desc->trigger_pos, startpoint, startpoint );
3344
3345 if( cmd->ptr->state & FLAG_EMITTER )
3346 {
3347 v4f wire_colour;
3348 colour_code_v3( cmd->ptr->emit[j], wire_colour );
3349 wire_colour[3] = 1.0f;
3350
3351 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour );
3352 }
3353 else
3354 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, j? wire_right_colour: wire_left_colour );
3355
3356 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), other_cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
3357 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), other_cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
3358 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), startpoint[0], startpoint[1], 0.18f*world.st.world_transition );
3359 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), endpoint[0], endpoint[1], 0.18f*world.st.world_transition );
3360 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
3361 }
3362 }
3363 }
3364
3365 // WIRE ENDPOINTS
3366 // ========================================================================================================
3367
3368 SHADER_USE( shader_tile_colour );
3369 use_mesh( &world.shapes );
3370
3371 for( int i = 0; i < world.tile_special_count; i ++ )
3372 {
3373 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3374 struct cell *cell = cmd->ptr;
3375
3376 if( cell->state & FLAG_TARGETED )
3377 {
3378 for( int j = 0; j < 2; j ++ )
3379 {
3380 if( !cell->links[j] )
3381 continue;
3382
3383 struct cell *other_cell = &world.data[ cell->links[ j ]];
3384 struct cell_description *desc = &cell_descriptions[ other_cell->config ];
3385
3386 int x2 = cell->links[j] % world.w;
3387 int y2 = (cell->links[j] - x2) / world.w;
3388
3389 v2f pts[2];
3390
3391 pts[0][0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f);
3392 pts[0][1] = (float)cmd->pos[1] + 0.25f;
3393
3394 pts[1][0] = x2;
3395 pts[1][1] = y2;
3396
3397 v2_add( desc->trigger_pos, pts[1], pts[1] );
3398
3399 if( cell->state & FLAG_EMITTER )
3400 {
3401 v4f wire_colour;
3402 colour_code_v3( cell->emit[j], wire_colour );
3403
3404 v3_muls( wire_colour, 0.8f, wire_colour );
3405 wire_colour[3] = 1.0f;
3406
3407 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, wire_colour );
3408 }
3409 else
3410 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1,j?wire_right_colour: wire_left_colour );
3411
3412 for( int i = 0; i < 2; i ++ )
3413 {
3414 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
3415 pts[i][0],
3416 pts[i][1],
3417 0.08f * world.st.world_transition
3418 );
3419 draw_mesh( filled_start, filled_count );
3420 }
3421 }
3422 }
3423 }
3424
3425 // SUB SPLITTER DIRECTION
3426 // ========================================================================================================
3427
3428 /*
3429 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
3430
3431 for( int i = 0; i < world.tile_special_count; i ++ )
3432 {
3433 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3434 struct cell *cell = cmd->ptr;
3435
3436 if( cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
3437 {
3438 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), cmd->pos[0], cmd->pos[1], 1.0f );
3439 draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
3440 }
3441 }
3442 */
3443
3444 // LIGHT FLARES
3445 // ========================================================================================================
3446 glBlendFunc(GL_ONE, GL_ONE);
3447 glBlendEquation(GL_FUNC_ADD);
3448
3449 SHADER_USE( shader_sprite );
3450
3451 vg_tex2d_bind( &tex_sprites, 0 );
3452 glUniform1i( SHADER_UNIFORM( shader_sprite, "uTexMain" ), 0 );
3453
3454 for( int i = 0; i < world.tile_special_count; i ++ )
3455 {
3456 struct render_cmd *cmd = &world.cmd_buf_specials[i];
3457 struct cell *cell = cmd->ptr;
3458
3459 if( cell->config == k_cell_type_split )
3460 {
3461 v2f center = { cmd->pos[0] + 0.5f, cmd->pos[1] + 0.5f };
3462
3463 v3f p0 = { 0.0f, 0.0f, 12.0f };
3464 v3f p1 = { 0.0f, 0.0f, 12.0f };
3465
3466 v2_add( center, (v2f){ -0.25f, -0.25f }, p0 );
3467 v2_add( center, (v2f){ 0.25f, -0.25f }, p1 );
3468
3469 if( cell->state & FLAG_TARGETED )
3470 {
3471 if( cell->state & FLAG_FLIP_FLOP )
3472 render_sprite( k_sprite_flare_y, p1 );
3473 else
3474 render_sprite( k_sprite_flare_b, p0 );
3475 }
3476 else
3477 render_sprite( k_sprite_flare_w, cell->state &FLAG_FLIP_FLOP? p1: p0 );
3478 }
3479 }
3480
3481 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3482 glBlendEquation(GL_FUNC_ADD);
3483
3484 glDisable(GL_BLEND);
3485
3486 // Draw score
3487 /*
3488 float const score_bright = 1.25f;
3489 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
3490 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f );
3491
3492 use_mesh( &world.numbers );
3493 draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
3494 */
3495
3496 if( !enable_bloom )
3497 {
3498 if( enable_vignette )
3499 goto image_composite;
3500
3501 return;
3502 }
3503
3504 /* Scale down image and remap colour values */
3505 glViewport( 0,0,
3506 vg_window_x/EFFECT_BUFFER_RATIO, vg_window_y/EFFECT_BUFFER_RATIO );
3507 glBindFramebuffer( GL_FRAMEBUFFER, world.st.bloomframebuffer[0] );
3508
3509 SHADER_USE( shader_post_darken );
3510 glActiveTexture( GL_TEXTURE0 );
3511 glBindTexture( GL_TEXTURE_2D, world.st.colourbuffer );
3512 glUniform1i( SHADER_UNIFORM( shader_post_darken, "uTexMain" ), 0 );
3513
3514 draw_mesh( 0, 2 );
3515
3516 /* Two pass blur */
3517 v2f res_inv, blur_dir;
3518 res_inv[0] = 1.0f / (float)( vg_window_x/EFFECT_BUFFER_RATIO );
3519 res_inv[1] = 1.0f / (float)( vg_window_y/EFFECT_BUFFER_RATIO );
3520
3521 SHADER_USE( shader_post_blur );
3522 glUniform1i( SHADER_UNIFORM( shader_post_blur, "uTexMain" ), 0 );
3523
3524 for( int i=0; i<1; i++ )
3525 {
3526 glBindFramebuffer( GL_FRAMEBUFFER, world.st.bloomframebuffer[1] );
3527
3528 v2_mul( (v2f){ 1.0f*(float)(i+1), 0.0f }, res_inv, blur_dir );
3529
3530 glUniform2fv( SHADER_UNIFORM(shader_post_blur,"uDir"), 1, blur_dir );
3531 glActiveTexture( GL_TEXTURE0 );
3532 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[0] );
3533
3534 draw_mesh( 0, 2 );
3535
3536 v2_mul( (v2f){ 0.0f, 1.0f*(float)(i+1) }, res_inv, blur_dir );
3537
3538 glBindFramebuffer( GL_FRAMEBUFFER, world.st.bloomframebuffer[0] );
3539 glUniform2fv( SHADER_UNIFORM(shader_post_blur,"uDir"), 1, blur_dir );
3540 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[1] );
3541 draw_mesh( 0, 2 );
3542 }
3543
3544 /* Scene composite */
3545 glViewport( 0,0, vg_window_x, vg_window_y );
3546
3547 image_composite:
3548 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
3549
3550 SHADER_USE( shader_post_comp );
3551
3552 glActiveTexture( GL_TEXTURE0 );
3553 glBindTexture( GL_TEXTURE_2D, world.st.colourbuffer );
3554 glUniform1i( SHADER_UNIFORM( shader_post_comp, "uTexMain" ), 0 );
3555
3556 glActiveTexture( GL_TEXTURE1 );
3557 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[0] );
3558 glUniform1i( SHADER_UNIFORM( shader_post_comp, "uTexBloom" ), 1 );
3559
3560 glUniform2f( SHADER_UNIFORM( shader_post_comp, "uComp" ),
3561 enable_bloom? 1.0f: 0.0f,
3562 enable_vignette? 0.0f: 1.0f );
3563
3564 draw_mesh( 0, 2 );
3565 }
3566
3567 void vg_ui(void)
3568 {
3569 // Drawing world name
3570 if( world.pCmpLevel )
3571 {
3572 gui_text( (ui_px [2]){ vg_window_x / 2, 4 }, world.pCmpLevel->title, 2, k_text_align_center );
3573 gui_text( (ui_px [2]){ vg_window_x / 2, 28 }, world.pCmpLevel->description, 1, k_text_align_center );
3574 }
3575
3576 #if 0
3577 if( world.st.state == k_game_state_update )
3578 {
3579 gui_group_id( 34 );
3580
3581 ui_global_ctx.cursor[2] = 458;
3582 ui_global_ctx.cursor[3] = 316;
3583 ui_global_ctx.cursor[0] = vg_window_x / 2 - 229;
3584 ui_global_ctx.cursor[1] = vg_window_y / 2 - 158;
3585
3586 gui_new_node();
3587 {
3588 gui_capture_mouse( 200 );
3589 gui_fill_rect( ui_global_ctx.cursor, 0xE8303030 );
3590
3591 ui_px title_pos[2];
3592 title_pos[0] = ui_global_ctx.cursor[0] + 229;
3593 title_pos[1] = ui_global_ctx.cursor[1] + 16;
3594
3595 gui_text( title_pos, "Update 1.5", 2, k_text_align_center );
3596
3597 gui_text( (ui_px [2]){ ui_global_ctx.cursor[0] + 16, title_pos[1] + 45 },
3598 "Welcome to the first update to marble computing!"
3599 "\n"
3600 "New features have been added:\n"
3601 "\n"
3602 " - Settings menu\n"
3603 " - Map skins\n"
3604 " - More levels and a new block type\n"
3605 " - Scores for each level\n"
3606 " - Zooming and panning (mousewheel)\n"
3607 "\n"
3608 "There is much more in the works, such as a\n"
3609 "soundtrack, and the rest of the levels for the\n"
3610 "3 bit computer!\n"
3611 "\n"
3612 "Thank you everyone for enjoying my game :)\n",
3613 1, k_text_align_left
3614 );
3615
3616 ui_global_ctx.cursor[2] = 100;
3617 ui_global_ctx.cursor[3] = 30;
3618 ui_global_ctx.cursor[0] += 229 - 50;
3619 ui_global_ctx.cursor[1] += 316 - 30 - 16;
3620
3621 if( gui_button( 1 ) )
3622 {
3623 world.st.state = k_game_state_main;
3624 }
3625 gui_text( (ui_px [2]){ ui_global_ctx.cursor[0] + 50,
3626 ui_global_ctx.cursor[1] + 4 }, "OK", 1, k_text_align_center );
3627 gui_end();
3628 }
3629 gui_end();
3630 }
3631 else
3632 #endif
3633 if( world.st.state == k_game_state_settings )
3634 {
3635 gui_group_id( 35 );
3636
3637 ui_global_ctx.cursor[2] = 225;
3638 gui_fill_y();
3639 gui_align_right();
3640
3641 gui_new_node();
3642 {
3643 gui_capture_mouse( 200 );
3644
3645 gui_fill_rect( ui_global_ctx.cursor, 0xC0202020 );
3646 ui_rect_pad( ui_global_ctx.cursor, 8 );
3647
3648 ui_global_ctx.cursor[3] = 25;
3649
3650 gui_new_node();
3651 {
3652 gui_text( ui_global_ctx.cursor, "SETTINGS", 2, 0 );
3653
3654 ui_global_ctx.cursor[2] = 25;
3655 gui_align_right();
3656
3657 if( gui_button(4) == k_button_click )
3658 {
3659 world.st.buttons[ k_world_button_settings ].state = 0;
3660 world.st.state = k_game_state_main;
3661 vg_info( "exit\n" );
3662 }
3663 ui_global_ctx.cursor[0] += 4;
3664 ui_global_ctx.cursor[1] -= 4;
3665 gui_text( ui_global_ctx.cursor, "x", 2, 0 );
3666 gui_end();
3667 }
3668 gui_end();
3669
3670 // Colour scheme selection
3671 ui_global_ctx.cursor[1] += 30;
3672
3673 gui_text( ui_global_ctx.cursor, "Colour Scheme", 1, 0 );
3674 ui_global_ctx.cursor[1] += 25;
3675
3676 gui_new_node();
3677 {
3678 ui_global_ctx.cursor[2] = 50;
3679
3680 for( int i = 0; i < 4; i ++ )
3681 {
3682 gui_new_node();
3683 {
3684 // Convert to RGB
3685 u32 rgb = 0xff000000;
3686
3687 for( int j = 0; j < 3; j ++ )
3688 rgb |= (u32)(colour_sets[ colour_set_id ][i][j]*255.0f) << j * 8;
3689
3690 gui_fill_rect( ui_global_ctx.cursor, rgb );
3691 }
3692 gui_end_right();
3693 }
3694 }
3695 gui_end_down();
3696
3697 gui_new_node();
3698 {
3699 ui_global_ctx.cursor[2] = 25;
3700 if( gui_button( 0 ) == k_button_click )
3701 {
3702 if( colour_set_id > 0 )
3703 colour_set_id --;
3704 }
3705 gui_text( ui_global_ctx.cursor, "<", 2, 0 );
3706 gui_end_right();
3707
3708 ui_global_ctx.cursor[2] = 150;
3709 gui_new_node();
3710 {
3711 gui_fill_rect( ui_global_ctx.cursor, 0x33ffffff );
3712 gui_text(
3713 (ui_px [2]){ ui_global_ctx.cursor[0] + 75, ui_global_ctx.cursor[1] + 6 },
3714 (const char *[]){ "Normal", "Extra1", "Extra2" }[ colour_set_id ],
3715 1, k_text_align_center
3716 );
3717 }
3718 gui_end_right();
3719
3720 ui_global_ctx.cursor[2] = 25;
3721 if( gui_button( 1 ) == k_button_click )
3722 {
3723 if( colour_set_id < vg_list_size( colour_sets )-1 )
3724 colour_set_id ++;
3725 }
3726 gui_text( ui_global_ctx.cursor, ">", 2, 0 );
3727 gui_end_down();
3728 }
3729 gui_end_down();
3730
3731 // Theme select
3732 ui_global_ctx.cursor[1] += 16;
3733
3734 #if 0
3735 gui_text( ui_global_ctx.cursor, "Tile Theme", 1, 0 );
3736 ui_global_ctx.cursor[1] += 20;
3737
3738 gui_new_node();
3739 {
3740 ui_global_ctx.cursor[2] = 25;
3741 if( gui_button( 2 ) == k_button_click )
3742 {
3743 if( world_theme_id > 0 )
3744 world_theme_id --;
3745 }
3746 gui_text( ui_global_ctx.cursor, "<", 2, 0 );
3747 gui_end_right();
3748
3749 ui_global_ctx.cursor[2] = 150;
3750 gui_new_node();
3751 {
3752 gui_fill_rect( ui_global_ctx.cursor, 0x33ffffff );
3753 gui_text(
3754 (ui_px [2]){ ui_global_ctx.cursor[0] + 75, ui_global_ctx.cursor[1] + 6 },
3755 world_themes[ world_theme_id ].name, 1, k_text_align_center
3756 );
3757 }
3758 gui_end_right();
3759
3760 ui_global_ctx.cursor[2] = 25;
3761 if( gui_button( 3 ) == k_button_click )
3762 {
3763 if( world_theme_id < vg_list_size( world_themes )-1 )
3764 world_theme_id ++;
3765 }
3766 gui_text( ui_global_ctx.cursor, ">", 2, 0 );
3767 gui_end_down();
3768 }
3769 gui_end_down();
3770 #endif
3771
3772 gui_text( ui_global_ctx.cursor, "Graphics", 1, 0 );
3773 ui_global_ctx.cursor[1] += 20;
3774
3775 gui_new_node();
3776 {
3777 ui_global_ctx.cursor[2] = 200;
3778 if( gui_button( 5 ) == k_button_click )
3779 {
3780 enable_bloom ^= 0x1;
3781 }
3782 ui_global_ctx.cursor[0] += 4;
3783 ui_global_ctx.cursor[1] += 4;
3784 gui_text( ui_global_ctx.cursor, enable_bloom?
3785 "Bloom: ENABLED":
3786 "Bloom: DISABLED", 1, 0 );
3787 gui_end_down();
3788 }
3789 gui_end_down();
3790
3791 ui_global_ctx.cursor[1] += 10;
3792 gui_new_node();
3793 {
3794 ui_global_ctx.cursor[2] = 200;
3795 if( gui_button( 6 ) == k_button_click )
3796 {
3797 enable_vignette ^= 0x1;
3798 }
3799 ui_global_ctx.cursor[0] += 4;
3800 ui_global_ctx.cursor[1] += 4;
3801 gui_text( ui_global_ctx.cursor, enable_vignette?
3802 "Vignette: ENABLED":
3803 "Vignette: DISABLED", 1, 0 );
3804 gui_end_down();
3805 }
3806 gui_end_down();
3807
3808 ui_global_ctx.cursor[1] += 16;
3809 gui_text( ui_global_ctx.cursor, "Music Volume", 1, 0 );
3810 ui_global_ctx.cursor[1] += 20;
3811
3812 gui_new_node();
3813 {
3814 ui_px slider_start = ui_global_ctx.cursor[0];
3815
3816 float const bar_width = 45.0f,
3817 bar_total = 200.0f,
3818 bar_movement = bar_total-bar_width,
3819 bar_start = bar_width * 0.5f;
3820
3821 ui_global_ctx.cursor[2] = bar_total;
3822 ui_fill_rect( &ui_global_ctx,
3823 ui_global_ctx.cursor,
3824 0xff111111 );
3825
3826 ui_global_ctx.cursor[2] = bar_width;
3827 ui_global_ctx.cursor[0] = slider_start + music_volume * bar_movement;
3828
3829 int status = gui_button( 7 );
3830
3831 static ui_px drag_start = 0.0f;
3832
3833 if( status == k_button_start_click )
3834 drag_start = ui_global_ctx.mouse[0];
3835 else if( ui_global_ctx.capture_lock &&
3836 (ui_global_ctx.capture_mouse_id == ui_group_id(&ui_global_ctx,7)))
3837 {
3838 ui_px drag_offset = ui_global_ctx.mouse[0] - drag_start;
3839 float offset_local = (drag_start + drag_offset - slider_start - bar_start) / bar_movement;
3840
3841 music_volume = vg_minf( vg_maxf( offset_local, 0.0f ), 1.0f );
3842 music_volume_update();
3843 }
3844
3845 ui_global_ctx.cursor[0] += 4;
3846 ui_global_ctx.cursor[1] += 4;
3847
3848 char volbuf[12];
3849 snprintf( volbuf, 12, "%.2f", music_volume );
3850 gui_text( ui_global_ctx.cursor, volbuf, 1, 0 );
3851 gui_end_down();
3852 }
3853 gui_end_down();
3854 }
3855 gui_end();
3856 }
3857 }
3858
3859 #if STEAM_LEADERBOARDS
3860 void leaderboard_dispatch_score(void)
3861 {
3862
3863 sw_upload_leaderboard_score(
3864 ui_data.upload_request.level->steam_leaderboard,
3865 k_ELeaderboardUploadScoreMethodKeepBest,
3866 ui_data.upload_request.score,
3867 NULL,
3868 0
3869 );
3870
3871 ui_data.upload_request.is_waiting = 0;
3872
3873 vg_success( "Dispatched leaderboard score\n" );
3874 }
3875
3876 void leaderboard_found( LeaderboardFindResult_t *pCallback )
3877 {
3878 if( !pCallback->m_bLeaderboardFound )
3879 {
3880 vg_error( "Leaderboard could not be found\n" );
3881 ui_data.steam_leaderboard = 0;
3882 }
3883 else
3884 {
3885 const char *recieved_name = sw_get_leaderboard_name( pCallback->m_hSteamLeaderboard );
3886
3887 // Update UI state and request entries if this callback found the current UI level
3888 if( ui_data.level_selected )
3889 {
3890 if( !strcmp( recieved_name, ui_data.level_selected->map_name ) )
3891 {
3892 sw_download_leaderboard_entries( pCallback->m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 0, 8 );
3893 ui_data.level_selected->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3894 }
3895 }
3896
3897 // Dispatch the waiting request if there was one
3898 if( ui_data.upload_request.is_waiting )
3899 {
3900 if( !strcmp( recieved_name, ui_data.upload_request.level->map_name ) )
3901 {
3902 ui_data.upload_request.level->steam_leaderboard = pCallback->m_hSteamLeaderboard;
3903 leaderboard_dispatch_score();
3904 }
3905 }
3906 }
3907 }
3908
3909 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
3910 {
3911 // Update UI if this leaderboard matches what we currently have in view
3912 if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
3913 {
3914 vg_info( "Recieved %d entries\n", pCallback->m_cEntryCount );
3915 ui_data.leaderboard_count = VG_MIN( pCallback->m_cEntryCount, 8 );
3916
3917 u64_steamid local_player = sw_get_steamid();
3918
3919 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
3920 {
3921 LeaderboardEntry_t entry;
3922 sw_get_downloaded_entry( pCallback->m_hSteamLeaderboardEntries, i, &entry, NULL, 0 );
3923
3924 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
3925
3926 player->id = entry.m_steamIDUser.m_unAll64Bits;
3927 strncpy( player->player_name, sw_get_friend_persona_name( player->id ), vg_list_size( player->player_name )-1 );
3928 player->score = entry.m_nScore;
3929
3930 snprintf( player->score_text, vg_list_size(player->score_text), "%d", player->score );
3931 player->texture = sw_get_player_image( player->id );
3932
3933 if( player->texture == 0 )
3934 player->texture = tex_unkown.name;
3935
3936 player->is_local_player = local_player == player->id? 1: 0;
3937 }
3938
3939 if( ui_data.leaderboard_count )
3940 ui_data.leaderboard_show = 1;
3941 else
3942 ui_data.leaderboard_show = 0;
3943 }
3944 else vg_warn( "Downloaded leaderboard does not match requested!\n" );
3945 }
3946
3947 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
3948 {
3949 if( ui_data.upload_request.is_waiting )
3950 vg_warn( "You are uploading leaderboard entries too quickly!\n" );
3951
3952 ui_data.upload_request.level = cmp_level;
3953 ui_data.upload_request.score = score;
3954 ui_data.upload_request.is_waiting = 1;
3955
3956 // If leaderboard ID has been downloaded already then just immediately dispatch this
3957 if( cmp_level->steam_leaderboard )
3958 leaderboard_dispatch_score();
3959 else
3960 sw_find_leaderboard( cmp_level->map_name );
3961 }
3962 #endif
3963
3964 // CONSOLE COMMANDS
3965 // ===========================================================================================================
3966
3967 static int console_credits( int argc, char const *argv[] )
3968 {
3969 vg_info( "Aknowledgements:\n" );
3970 vg_info( " GLFW zlib/libpng glfw.org\n" );
3971 vg_info( " miniaudio MIT0 miniaud.io\n" );
3972 vg_info( " QOI MIT phoboslab.org\n" );
3973 vg_info( " STB library MIT nothings.org\n" );
3974 return 0;
3975 }
3976
3977 static int console_save_map( int argc, char const *argv[] )
3978 {
3979 if( !world.initialzed )
3980 {
3981 vg_error( "Tried to save uninitialized map!\n" );
3982 return 0;
3983 }
3984
3985 char map_path[ 256 ];
3986
3987 strcpy( map_path, "sav/" );
3988 strcat( map_path, world.map_name );
3989 strcat( map_path, ".map" );
3990
3991 FILE *test_writer = fopen( map_path, "wb" );
3992 if( test_writer )
3993 {
3994 vg_info( "Saving map to '%s'\n", map_path );
3995 map_serialize( test_writer );
3996
3997 fclose( test_writer );
3998 return 1;
3999 }
4000 else
4001 {
4002 vg_error( "Unable to open stream for writing\n" );
4003 return 0;
4004 }
4005 }
4006
4007 static int console_load_map( int argc, char const *argv[] )
4008 {
4009 char map_path[ 256 ];
4010
4011 if( argc >= 1 )
4012 {
4013 // try from saves
4014 strcpy( map_path, "sav/" );
4015 strcat( map_path, argv[0] );
4016 strcat( map_path, ".map" );
4017
4018 char *text_source = vg_textasset_read( map_path );
4019
4020 if( !text_source )
4021 {
4022 strcpy( map_path, "maps/" );
4023 strcat( map_path, argv[0] );
4024 strcat( map_path, ".map" );
4025
4026 text_source = vg_textasset_read( map_path );
4027 }
4028
4029 if( text_source )
4030 {
4031 vg_info( "Loading map: '%s'\n", map_path );
4032 world.pCmpLevel = NULL;
4033
4034 if( !map_load( text_source, argv[0] ) )
4035 {
4036 free( text_source );
4037 return 0;
4038 }
4039
4040 free( text_source );
4041 return 1;
4042 }
4043 else
4044 {
4045 vg_error( "Missing maps '%s'\n", argv[0] );
4046 return 0;
4047 }
4048 }
4049 else
4050 {
4051 vg_error( "Missing argument <map_path>\n" );
4052 return 0;
4053 }
4054 }
4055
4056 static int console_changelevel( int argc, char const *argv[] )
4057 {
4058 if( argc >= 1 )
4059 {
4060 // Save current level
4061 console_save_map( 0, NULL );
4062
4063 if( console_load_map( argc, argv ) )
4064 {
4065 world.st.zoom = 0.0f;
4066 simulation_stop();
4067 return 1;
4068 }
4069 }
4070 else
4071 {
4072 vg_error( "Missing argument <map_path>\n" );
4073 }
4074
4075 return 0;
4076 }
4077
4078 // START UP / SHUTDOWN
4079 // ===========================================================================================================
4080
4081 #define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
4082 X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY
4083
4084 void vg_start(void)
4085 {
4086 // Steamworks callbacks
4087 #ifdef STEAM_LEADERBOARDS
4088 sw_leaderboard_found = &leaderboard_found;
4089 sw_leaderboard_downloaded = &leaderboard_downloaded;
4090 #endif
4091
4092 vg_function_push( (struct vg_cmd){
4093 .name = "_map_write",
4094 .function = console_save_map
4095 });
4096
4097 vg_function_push( (struct vg_cmd){
4098 .name = "_map_load",
4099 .function = console_load_map
4100 });
4101
4102 vg_function_push( (struct vg_cmd){
4103 .name = "map",
4104 .function = console_changelevel
4105 });
4106
4107 vg_function_push( (struct vg_cmd){
4108 .name = "credits",
4109 .function = console_credits
4110 });
4111
4112 vg_convar_push( (struct vg_convar){
4113 .name = "colours",
4114 .data = &colour_set_id,
4115 .data_type = k_convar_dtype_i32,
4116 .opt_i32 = { .min = 0, .max = 2, .clamp = 1 },
4117 .persistent = 1
4118 });
4119
4120 vg_convar_push( (struct vg_convar){
4121 .name = "theme",
4122 .data = &world_theme_id,
4123 .data_type = k_convar_dtype_i32,
4124 .opt_i32 = { .min = 0, .max = vg_list_size( world_themes )-1, .clamp = 1 },
4125 .persistent = 1,
4126 .update = NULL
4127 });
4128
4129 vg_convar_push( (struct vg_convar){
4130 .name = "enable_bloom",
4131 .data = &enable_bloom,
4132 .data_type = k_convar_dtype_i32,
4133 .opt_i32 = { .min = 0, .max = 1, .clamp = 1 },
4134 .persistent = 1,
4135 .update = NULL
4136 });
4137
4138 vg_convar_push( (struct vg_convar){
4139 .name = "enable_vignette",
4140 .data = &enable_vignette,
4141 .data_type = k_convar_dtype_i32,
4142 .opt_i32 = { .min = 0, .max = 1, .clamp = 1 },
4143 .persistent = 1,
4144 .update = NULL
4145 });
4146
4147 vg_convar_push( (struct vg_convar){
4148 .name = "music_volume",
4149 .data = &music_volume,
4150 .data_type = k_convar_dtype_f32,
4151 .opt_f32 = { .min = 0.0f, .max = 1.0f, .clamp = 1 },
4152 .persistent = 1,
4153 .update = music_volume_update
4154 });
4155
4156 // Combined quad, long quad / empty circle / filled circle mesh
4157 {
4158 float combined_mesh[6*6 + 32*6*3] = {
4159 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
4160 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
4161
4162 0.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.2f,
4163 0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f,
4164
4165 TRANSFORM_TRI_2D( 0.15f,0.05f,0.4f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f ),
4166 TRANSFORM_TRI_2D( 0.15f,0.80f,0.4f, 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 1.0f )
4167 };
4168
4169 float *circle_mesh = combined_mesh + 6*6;
4170 int const res = 32;
4171
4172 for( int i = 0; i < res; i ++ )
4173 {
4174 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
4175 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
4176
4177 circle_mesh[ i*6+0 ] = 0.0f;
4178 circle_mesh[ i*6+1 ] = 0.0f;
4179
4180 v2_copy( v0, circle_mesh + 32*6 + i*12 );
4181 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
4182 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
4183
4184 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
4185 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
4186 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
4187
4188 v2_copy( v0, circle_mesh + i*6+4 );
4189 v2_copy( v1, circle_mesh + i*6+2 );
4190 v2_copy( v0, circle_mesh+i*6+4 );
4191 v2_copy( v1, circle_mesh+i*6+2 );
4192 }
4193
4194 init_mesh( &world.shapes, combined_mesh, vg_list_size( combined_mesh ) );
4195 }
4196
4197 // Create wire mesh
4198 {
4199 int const num_segments = 64;
4200
4201 struct mesh_wire *mw = &world.wire;
4202
4203 v2f wire_points[ num_segments * 2 ];
4204 u16 wire_indices[ 6*(num_segments-1) ];
4205
4206 for( int i = 0; i < num_segments; i ++ )
4207 {
4208 float l = (float)i / (float)(num_segments-1);
4209
4210 v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
4211 v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
4212
4213 if( i < num_segments-1 )
4214 {
4215 wire_indices[ i*6+0 ] = i*2 + 0;
4216 wire_indices[ i*6+1 ] = i*2 + 1;
4217 wire_indices[ i*6+2 ] = i*2 + 3;
4218 wire_indices[ i*6+3 ] = i*2 + 0;
4219 wire_indices[ i*6+4 ] = i*2 + 3;
4220 wire_indices[ i*6+5 ] = i*2 + 2;
4221 }
4222 }
4223
4224 glGenVertexArrays( 1, &mw->vao );
4225 glGenBuffers( 1, &mw->vbo );
4226 glGenBuffers( 1, &mw->ebo );
4227 glBindVertexArray( mw->vao );
4228
4229 glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
4230
4231 glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
4232 glBindVertexArray( mw->vao );
4233
4234 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
4235 glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
4236
4237 // XY
4238 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
4239 glEnableVertexAttribArray( 0 );
4240
4241 VG_CHECK_GL();
4242
4243 mw->em = vg_list_size( wire_indices );
4244 }
4245
4246 // Create info data texture
4247 {
4248 glGenTextures( 1, &world.background_data );
4249 glBindTexture( GL_TEXTURE_2D, world.background_data );
4250 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
4251 vg_tex2d_nearest();
4252 }
4253
4254 // Create random smaples texture
4255 {
4256 u8 *data = malloc(512*512*2);
4257 for( int i = 0; i < 512*512*2; i ++ )
4258 data[ i ] = rand()/(RAND_MAX/255);
4259
4260 glGenTextures( 1, &world.random_samples );
4261 glBindTexture( GL_TEXTURE_2D, world.random_samples );
4262 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
4263 vg_tex2d_nearest();
4264 vg_tex2d_repeat();
4265
4266 free( data );
4267 }
4268
4269 resource_load_main();
4270
4271 // Init world text
4272 {
4273 ui_init_context( &world.st.world_text, 15000 );
4274 }
4275
4276 // Restore gamestate
4277 career_local_data_init();
4278 career_load();
4279
4280 /* Create framebuffers */
4281 glGenFramebuffers( 1, &world.st.framebuffer );
4282 glBindFramebuffer( GL_FRAMEBUFFER, world.st.framebuffer );
4283
4284 glGenTextures( 1, &world.st.colourbuffer );
4285 glBindTexture( GL_TEXTURE_2D, world.st.colourbuffer );
4286 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vg_window_x, vg_window_y,
4287 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
4288
4289 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
4290 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
4291 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
4292 world.st.colourbuffer, 0);
4293
4294 /* Bloom framebuffer (quater res) */
4295 glGenFramebuffers( 2, world.st.bloomframebuffer );
4296 glGenTextures( 2, world.st.bloomcolourbuffer );
4297
4298 for( int i=0; i<2; i++ )
4299 {
4300 glBindFramebuffer( GL_FRAMEBUFFER, world.st.bloomframebuffer[i] );
4301
4302 glBindTexture( GL_TEXTURE_2D, world.st.bloomcolourbuffer[i] );
4303 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB,
4304 vg_window_x/EFFECT_BUFFER_RATIO, vg_window_y/EFFECT_BUFFER_RATIO,
4305 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
4306 vg_tex2d_clamp();
4307
4308 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
4309 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
4310 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
4311 GL_TEXTURE_2D, world.st.bloomcolourbuffer[i], 0);
4312 }
4313 }
4314
4315 void vg_free(void)
4316 {
4317 #ifdef VG_STEAM
4318 sw_free_opengl();
4319 #endif
4320
4321 console_save_map( 0, NULL );
4322 career_serialize();
4323
4324 resource_free_main();
4325
4326 glDeleteTextures( 1, &world.background_data );
4327 glDeleteTextures( 1, &world.random_samples );
4328
4329 glDeleteVertexArrays( 1, &world.wire.vao );
4330 glDeleteBuffers( 1, &world.wire.vbo );
4331 glDeleteBuffers( 1, &world.wire.ebo );
4332
4333 free_mesh( &world.shapes );
4334
4335 ui_context_free( &world.st.world_text );
4336
4337 map_free();
4338 }
4339
4340 int main( int argc, char *argv[] )
4341 {
4342 vg_init( argc, argv, "Marble Computing" );
4343 return 0;
4344 }