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