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