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