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