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