Merge branch 'master' of harrygodden.com:/home/fishladder into master
[fishladder.git] / fishladder.c
1 // Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
2
3 #define VG_STEAM
4 #define VG_STEAM_APPID 1218140U
5 #include "vg/vg.h"
6 #include "fishladder_resources.h"
7
8 // CONSTANTS
9 // ===========================================================================================================
10
11 enum cell_type
12 {
13 k_cell_type_stub = 0,
14 k_cell_type_ramp_right = 3,
15 k_cell_type_ramp_left = 6,
16 k_cell_type_split = 7,
17 k_cell_type_merge = 13,
18 k_cell_type_con_r = 1,
19 k_cell_type_con_u = 2,
20 k_cell_type_con_l = 4,
21 k_cell_type_con_d = 8
22 };
23
24 enum e_fish_state
25 {
26 k_fish_state_soon_dead = -1,
27 k_fish_state_dead = 0,
28 k_fish_state_alive,
29 k_fish_state_bg,
30 k_fish_state_soon_alive
31 };
32
33 enum e_world_button
34 {
35 k_world_button_none = -1,
36 k_world_button_sim = 0,
37 k_world_button_pause = 1,
38 k_world_button_wire_mode = 2
39 };
40
41 #define FLAG_CANAL 0x1
42 #define FLAG_IS_TRIGGER 0x2
43 #define FLAG_RESERVED0 0x4
44 #define FLAG_RESERVED1 0x8
45
46 #define FLAG_INPUT 0x10
47 #define FLAG_OUTPUT 0x20
48 #define FLAG_WALL 0x40
49
50 #define FLAG_FLIP_FLOP 0x100
51 #define FLAG_TRIGGERED 0x200
52 #define FLAG_FLIP_ROTATING 0x400
53 #define FLAG_TARGETED 0x800
54
55 /*
56 0000 0 | 0001 1 | 0010 2 | 0011 3
57 | | | | |
58 X | X= | X | X=
59 | | |
60 0100 4 | 0101 5 | 0110 6 | 0111 7
61 | | | | |
62 =X | =X= | =X | =X=
63 | | |
64 1000 8 | 1001 9 | 1010 10 | 1011 11
65 | | | | |
66 X | X= | X | X=
67 | | | | | | |
68 1100 12 | 1101 13 | 1110 14 | 1111 15
69 | | | | |
70 =X | =X= | =X | =X=
71 | | | | | | |
72 */
73
74 static struct cell_description
75 {
76 v2i start;
77 v2i end;
78
79 int is_special;
80 int is_linear;
81
82 v2f trigger_pos;
83 }
84 cell_descriptions[] =
85 {
86 // 0-3
87 {},
88 { .start = { 1, 0 }, .end = { -1, 0 }, .trigger_pos = { 0.5f, 0.25f } },
89 { .start = { 0, 1 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f } },
90 { .start = { 0, 1 }, .end = { 1, 0 }, .trigger_pos = { 0.25f, 0.25f } },
91 // 4-7
92 { .start = { -1, 0 }, .end = { 1, 0 }, .trigger_pos = { 0.5f, 0.25f } },
93 { .start = { -1, 0 }, .end = { 1, 0 }, .trigger_pos = { 0.5f, 0.25f }, .is_linear = 1 },
94 { .start = { 0, 1 }, .end = { -1, 0 }, .trigger_pos = { 0.5f, 0.25f } },
95 { .start = { 0, 1 }, .is_special = 1 },
96 // 8-11
97 { .start = { 0, -1 }, .end = { 0, 1 }, .trigger_pos = { 0.25f, 0.5f } },
98 { .start = { 1, 0 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.75f } },
99 { .start = { 0, 1 }, .end = { 0, -1 }, .trigger_pos = { 0.25f, 0.5f }, .is_linear = 1 },
100 { },
101 // 12-15
102 { .start = { -1, 0 }, .end = { 0, -1 }, .trigger_pos = { 0.75f, 0.75f } },
103 { .end = { 0, -1 }, .is_special = 1, .trigger_pos = { 0.5f, 0.75f } },
104 { },
105 { }
106 };
107
108 v2f const curve_3[] = {{0.5f,1.0f},{0.5f,0.625f},{0.625f,0.5f},{1.0f,0.5f}};
109 v2f const curve_6[] = {{0.5f,1.0f},{0.5f,0.625f},{0.375f,0.5f},{0.0f,0.5f}};
110 v2f const curve_9[] = {{1.0f,0.5f},{0.625f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
111 v2f const curve_12[]= {{0.0f,0.5f},{0.375f,0.5f},{0.5f,0.375f},{0.5f,0.0f}};
112
113 v2f const curve_1[] = {{1.0f,0.5f},{0.8f,0.5f},{0.3f,0.5f},{0.2f,0.5f}};
114 v2f const curve_4[] = {{0.0f,0.5f},{0.3f,0.5f},{0.5f,0.5f},{0.8f,0.5f}};
115 v2f const curve_2[] = {{0.5f,1.0f},{0.5f,0.8f},{0.5f,0.3f},{0.5f,0.2f}};
116 v2f const curve_8[] = {{0.5f,0.0f},{0.5f,0.3f},{0.5f,0.5f},{0.5f,0.8f}};
117
118 v2f const curve_7[] = {{0.5f,0.8438f},{0.875f,0.8438f},{0.625f,0.5f},{1.0f,0.5f}};
119 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}};
120
121 float const curve_7_linear_section = 0.1562f;
122
123 // Types
124 // ===========================================================================================================
125
126 struct mesh
127 {
128 GLuint vao, vbo;
129 u32 elements;
130 };
131
132 struct world
133 {
134 #pragma pack(push,1)
135 struct cell
136 {
137 u16 state;
138 u16 links[2];
139 u8 config;
140 u8 pad0;
141 }
142 *data;
143 #pragma pack(pop)
144
145
146 int initialzed;
147
148 int sim_run, max_runs;
149
150 int sim_frame, sim_target;
151 float sim_internal_time, // current tick-time
152 sim_internal_delta, // time delta
153 sim_internal_ref, // Reference point of internal time
154 sim_delta_ref, // Reference point of vg_time when we started at current sim_speed
155 sim_delta_speed, // Rate to apply time delta
156 frame_lerp, // Fractional part of sim_internal_time
157 pause_offset_target; //
158
159 struct cell_terminal
160 {
161 struct terminal_run
162 {
163 char conditions[8];
164 char recieved[8];
165
166 int condition_count, recv_count;
167 }
168 runs[8];
169
170 int run_count;
171 v2i pos;
172 //int id;
173 }
174 *io;
175
176 struct cell_button
177 {
178 float light_target, light;
179 int pressed;
180 }
181 buttons[4];
182
183 int w, h;
184
185 struct mesh shapes, numbers;
186 struct mesh_wire
187 {
188 GLuint vao, vbo, ebo;
189 u32 em;
190 }
191 wire;
192
193 GLuint background_data;
194 GLuint random_samples;
195
196 int selected, tile_x, tile_y;
197 v2f tile_pos;
198
199 struct fish
200 {
201 v2i pos;
202 v2i dir;
203 enum e_fish_state state;
204 char payload;
205 int flow_reversed;
206 float death_time;
207 v2f physics_v;
208 v2f physics_co;
209 }
210 fishes[16];
211
212 int num_fishes;
213
214 char map_name[64];
215 //struct career_level *ptr_career_level;
216 struct cmp_level *pCmpLevel;
217
218 u32 score;
219 u32 completed;
220 u32 time;
221
222 u16 id_drag_from;
223 v2f drag_from_co;
224 v2f drag_to_co;
225 };
226
227 // Forward declerations
228 // --------------------
229
230 static int console_credits( int argc, char const *argv[] );
231 static int console_save_map( int argc, char const *argv[] );
232 static int console_load_map( int argc, char const *argv[] );
233 static int console_changelevel( int argc, char const *argv[] );
234
235 // GLOBALS
236 // ===========================================================================================================
237
238 m3x3f m_projection;
239 m3x3f m_view;
240 m3x3f m_mdl;
241
242 struct world world;
243
244 // UTILITY
245 // ===========================================================================================================
246
247 static void colour_code_v3( char const cc, v3f target )
248 {
249 static v3f colour_sets[] =
250 { { 1.0f, 0.9f, 0.3f },
251 { 0.2f, 0.9f, 0.14f },
252 { 0.4f, 0.8f, 1.00f },
253 { 0.882f, 0.204f, 0.922f }
254 };
255
256 if( cc >= 'a' && cc <= 'z' )
257 {
258 int id = cc - 'a';
259
260 if( id < vg_list_size( colour_sets ) )
261 {
262 v3_copy( colour_sets[ id ], target );
263 return;
264 }
265 }
266
267 v3_copy( (v3f){0.0f,0.0f,0.0f}, target );
268 }
269
270 static int hash21i( v2i p, u32 umod )
271 {
272 static const int random_noise[] =
273 {
274 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,
275 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,
276 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,
277 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,
278 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,
279 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,
280 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,
281 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,
282 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,
283 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,
284 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,
285 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,
286 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,
287 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,
288 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,
289 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,
290 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,
291 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,
292 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,
293 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,
294 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,
295 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,
296 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,
297 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,
298 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,
299 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,
300 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,
301 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,
302 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,
303 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,
304 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,
305 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
306 };
307
308 return random_noise[ (random_noise[p[1] & 1023] + p[0]) & 1023 ] & umod;
309 }
310
311 // MESH
312 // ===========================================================================================================
313
314 static void init_mesh( struct mesh *m, float const *tris, u32 length )
315 {
316 m->elements = length/3;
317 glGenVertexArrays( 1, &m->vao );
318 glGenBuffers( 1, &m->vbo );
319
320 glBindVertexArray( m->vao );
321 glBindBuffer( GL_ARRAY_BUFFER, m->vbo );
322 glBufferData( GL_ARRAY_BUFFER, length*sizeof(float), tris, GL_STATIC_DRAW );
323
324 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0 );
325 glEnableVertexAttribArray( 0 );
326
327 VG_CHECK_GL();
328 }
329
330 static void free_mesh( struct mesh *m )
331 {
332 glDeleteVertexArrays( 1, &m->vao );
333 glDeleteBuffers( 1, &m->vbo );
334 }
335
336 static void draw_mesh( int const start, int const count )
337 {
338 glDrawArrays( GL_TRIANGLES, start*3, count*3 );
339 }
340
341 static void use_mesh( struct mesh *m )
342 {
343 glBindVertexArray( m->vao );
344 }
345
346
347
348
349
350 static struct cell_button *get_wbutton( enum e_world_button btn )
351 {
352 return &world.buttons[ btn ];
353 }
354
355 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score );
356
357 // WORLD/MAP
358 // ===========================================================================================================
359
360 static void map_free(void)
361 {
362 arrfree( world.data );
363 arrfree( world.io );
364
365 world.w = 0;
366 world.h = 0;
367 world.data = NULL;
368 world.io = NULL;
369 world.score = 0;
370 world.time = 0;
371 world.completed = 0;
372 world.max_runs = 0;
373 world.initialzed = 0;
374 }
375
376 static void io_reset(void)
377 {
378 for( int i = 0; i < arrlen( world.io ); i ++ )
379 {
380 struct cell_terminal *term = &world.io[i];
381
382 for( int j = 0; j < term->run_count; j ++ )
383 term->runs[j].recv_count = 0;
384 }
385 }
386
387 static struct cell *pcell( v2i pos )
388 {
389 return &world.data[ pos[1]*world.w + pos[0] ];
390 }
391
392 static void map_reclassify( v2i start, v2i end, int update_texbuffer )
393 {
394 v2i full_start = { 1,1 };
395 v2i full_end = { world.w-1, world.h-1 };
396
397 if( !start || !end )
398 {
399 start = full_start;
400 end = full_end;
401 }
402
403 // Texture data
404 u8 info_buffer[64*64*4];
405 u32 pixel_id = 0;
406
407 int px0 = vg_max( start[0], full_start[0] ),
408 px1 = vg_min( end[0], full_end[0] ),
409 py0 = vg_max( start[1], full_start[1] ),
410 py1 = vg_min( end[1], full_end[1] );
411
412 for( int y = py0; y < py1; y ++ )
413 {
414 for( int x = px0; x < px1; x ++ )
415 {
416 struct cell *cell = pcell((v2i){x,y});
417
418 v2i dirs[] = {{1,0},{0,1},{-1,0},{0,-1}};
419
420 u8 height = 0;
421 u8 config = 0x00;
422
423 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
424 {
425 for( int i = 0; i < vg_list_size( dirs ); i ++ )
426 {
427 struct cell *neighbour = pcell((v2i){x+dirs[i][0], y+dirs[i][1]});
428 if( neighbour->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
429 config |= 0x1 << i;
430 }
431
432 height = 128;
433 }
434 else
435 {
436 if( cell->state & FLAG_WALL )
437 height = 0xFF-0x3F + hash21i( (v2i){x,y}, 0x3F );
438
439 config = 0xF;
440 }
441
442 pcell((v2i){x,y})->config = config;
443
444 u8 *info_px = &info_buffer[ (pixel_id ++)*4 ];
445 info_px[0] = height;
446 info_px[1] = cell->state & FLAG_WALL? 0: 255;
447 info_px[2] = 0;
448 info_px[3] = 0;
449
450 if(
451 (
452 ((cell->state & FLAG_IS_TRIGGER) && (cell->config == 0xF || cell->config == k_cell_type_split)) ||
453 ((cell->state & FLAG_TARGETED) && (cell->config != k_cell_type_split))
454 ) && update_texbuffer
455 ){
456 cell->state &= ~(FLAG_TARGETED|FLAG_IS_TRIGGER);
457 for( u32 i = 0; i < 2; i ++ )
458 {
459 if( cell->links[i] )
460 {
461 struct cell *other_ptr = &world.data[ cell->links[i] ];
462 other_ptr->links[ i ] = 0;
463 other_ptr->state &= ~FLAG_IS_TRIGGER;
464
465 if( other_ptr->links[ i ^ 0x1 ] == 0 )
466 other_ptr->state &= ~FLAG_TARGETED;
467 }
468 }
469
470 cell->links[0] = 0;
471 cell->links[1] = 0;
472 }
473 }
474 }
475
476 if( update_texbuffer )
477 {
478 glBindTexture( GL_TEXTURE_2D, world.background_data );
479 glTexSubImage2D( GL_TEXTURE_2D, 0, px0 + 16, py0 + 16, px1-px0, py1-py0, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
480 }
481 }
482
483 static int map_load( const char *str, const char *name )
484 {
485 //TODO: It may be worthwhile, at this point, to switch to binary encoding for save data
486
487 map_free();
488
489 char const *c = str;
490
491 // Scan for width
492 for(;; world.w ++)
493 {
494 if( c[world.w] == ';' )
495 break;
496 else if( !c[world.w] )
497 {
498 vg_error( "Unexpected EOF when parsing level\n" );
499 return 0;
500 }
501 }
502
503 struct cell *row = arraddnptr( world.data, world.w );
504 int cx = 0;
505 int reg_start = 0, reg_end = 0;
506
507 u32 *links_to_make = NULL;
508 int links_satisfied = 0;
509
510 char link_id_buffer[32];
511 int link_id_n = 0;
512
513 for(;;)
514 {
515 if( !*c )
516 break;
517
518 if( *c == '\r' ) { c ++; continue; } // fuck off windows
519
520 if( *c == ';' )
521 {
522 c ++;
523
524 if( *c == '\r' ) c ++;
525
526 // Parse attribs
527 if( *c != '\n' )
528 {
529 while( *c )
530 {
531 if( *c == '\r' ) { c ++; continue; }
532
533 if( reg_start < reg_end )
534 {
535 struct cell_terminal *terminal = &world.io[ reg_start ];
536 struct terminal_run *run = &terminal->runs[ terminal->run_count-1 ];
537
538 if( (*c >= 'a' && *c <= 'z') || *c == ' ' )
539 {
540 run->conditions[ run->condition_count ++ ] = *c;
541 }
542 else
543 {
544 if( *c == ',' || *c == '\n' )
545 {
546 reg_start ++;
547
548 if( *c == '\n' )
549 break;
550 }
551 else if( *c == ':' )
552 {
553 terminal->runs[ terminal->run_count ].condition_count = 0;
554 terminal->run_count ++;
555 world.max_runs = vg_max( world.max_runs, terminal->run_count );
556 }
557 else
558 {
559 vg_error( "Unkown attribute '%c' (row: %u)\n", *c, world.h );
560 goto IL_REG_ERROR;
561 }
562 }
563 }
564 else
565 {
566 if( links_satisfied < arrlen( links_to_make ) )
567 {
568 struct cell *target = &world.data[ links_to_make[ links_satisfied ] ];
569
570 if( (((u32)*c >= (u32)'0') && ((u32)*c <= (u32)'9')) || *c == '-' )
571 {
572 if( link_id_n >= vg_list_size( link_id_buffer )-1 )
573 {
574 vg_error( "Number was way too long to be parsed (row: %u)\n", world.h );
575 goto IL_REG_ERROR;
576 }
577
578 link_id_buffer[ link_id_n ++ ] = *c;
579 }
580 else if( *c == ',' || *c == '\n' )
581 {
582 link_id_buffer[ link_id_n ] = 0x00;
583 int value = atoi( link_id_buffer );
584
585 target->links[value >= 0? 1:0] = abs(value);
586 links_satisfied ++;
587 link_id_n = 0;
588
589 if( *c == '\n' )
590 break;
591 }
592 else
593 {
594 vg_error( "Invalid character '%c' (row: %u)\n", *c, world.h );
595 goto IL_REG_ERROR;
596 }
597 }
598 else
599 {
600 vg_error( "Too many values to assign (row: %u)\n", world.h );
601 goto IL_REG_ERROR;
602 }
603 }
604
605 c ++;
606 }
607 }
608
609 // Registry length-error checks
610 if( reg_start != reg_end )
611 {
612 vg_error( "Not enough spawn values assigned (row: %u, %u of %u)\n", world.h, reg_start, reg_end );
613 goto IL_REG_ERROR;
614 }
615
616 if( links_satisfied != arrlen( links_to_make ) )
617 {
618 vg_error( "Not enough link values assigned (row: %u, %u of %u)\n", world.h, links_satisfied, arrlen( links_to_make ) );
619 goto IL_REG_ERROR;
620 }
621
622 if( cx != world.w )
623 {
624 vg_error( "Not enough cells to match previous row definition (row: %u, %u<%u)\n", world.h, cx, world.w );
625 goto IL_REG_ERROR;
626 }
627
628 row = arraddnptr( world.data, world.w );
629 cx = 0;
630 world.h ++;
631 reg_end = reg_start = arrlen( world.io );
632
633 arrsetlen( links_to_make, 0 );
634 links_satisfied = 0;
635 }
636 else
637 {
638 if( cx == world.w )
639 {
640 vg_error( "Too many cells to match previous row definition (row: %u, %u>%u)\n", world.h, cx, world.w );
641 goto IL_REG_ERROR;
642 }
643
644 // Tile initialization
645 // row[ cx ] .. etc
646 struct cell *cell = &row[ cx ];
647 cell->config = 0xF;
648
649 if( *c == '+' || *c == '-' )
650 {
651 struct cell_terminal *term = arraddnptr( world.io, 1 );
652 term->pos[0] = cx;
653 term->pos[1] = world.h;
654
655 term->run_count = 1;
656 term->runs[0].condition_count = 0;
657
658 cell->state = *c == '+'? FLAG_INPUT: FLAG_OUTPUT;
659 reg_end ++;
660 }
661 else if( *c == '#' ) cell->state = FLAG_WALL;
662 else if( ((u32)*c >= (u32)'A') && ((u32)*c <= (u32)'A'+0xf) )
663 {
664 // Canal flag bits (4bit/16 value):
665 // 0: Canal present
666 // 1: Is trigger
667 // 2: Reserved
668 // 3: Reserved
669
670 cell->state = ((u32)*c - (u32)'A') & (FLAG_CANAL|FLAG_IS_TRIGGER);
671
672 if( cell->state & FLAG_IS_TRIGGER )
673 arrpush( links_to_make, cx + world.h*world.w );
674
675 cell->links[0] = 0;
676 cell->links[1] = 0;
677 world.score ++;
678 }
679 else cell->state = 0x00;
680
681 cx ++;
682 }
683
684 c ++;
685 }
686
687 // Update data texture to fill out the background
688 {
689 u8 info_buffer[64*64*4];
690 for( int x = 0; x < 64; x ++ )
691 {
692 for( int y = 0; y < 64; y ++ )
693 {
694 u8 *px = &info_buffer[((x*64)+y)*4];
695
696 px[0] = 0xFF-0x3F + hash21i( (v2i){x,y}, 0x3F );
697 px[1] = 0;
698 px[2] = 0;
699 px[3] = 0;
700 }
701 }
702
703 // Random walks.. kinda
704 for( int i = 0; i < arrlen(world.io); i ++ )
705 {
706 struct cell_terminal *term = &world.io[ i ];
707
708 v2i turtle;
709 v2i turtle_dir;
710 int original_y;
711
712 turtle[0] = 16+term->pos[0];
713 turtle[1] = 16+term->pos[1];
714
715 turtle_dir[0] = 0;
716 turtle_dir[1] = pcell(term->pos)->state & FLAG_INPUT? 1: -1;
717 original_y = turtle_dir[1];
718
719 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
720 v2i_add( turtle_dir, turtle, turtle );
721
722 for( int i = 0; i < 100; i ++ )
723 {
724 info_buffer[((turtle[1]*64)+turtle[0])*4] = 0;
725
726 v2i_add( turtle_dir, turtle, turtle );
727
728 if( turtle[0] == 0 ) break;
729 if( turtle[0] == 63 ) break;
730 if( turtle[1] == 0 ) break;
731 if( turtle[1] == 63 ) break;
732
733 int random_value = hash21i( turtle, 0xFF );
734 if( random_value > 255-40 && !turtle_dir[0] )
735 {
736 turtle_dir[0] = -1;
737 turtle_dir[1] = 0;
738 }
739 else if( random_value > 255-80 && !turtle_dir[0] )
740 {
741 turtle_dir[0] = 1;
742 turtle_dir[1] = 0;
743 }
744 else if( random_value > 255-100 )
745 {
746 turtle_dir[0] = 0;
747 turtle_dir[1] = original_y;
748 }
749 }
750 }
751
752 glBindTexture( GL_TEXTURE_2D, world.background_data );
753 glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 64, 64, GL_RGBA, GL_UNSIGNED_BYTE, info_buffer );
754 }
755
756 arrfree( links_to_make );
757
758 map_reclassify( NULL, NULL, 1 );
759
760 // Validate links
761 for( int i = 0; i < world.h*world.w; i ++ )
762 {
763 struct cell *src = &world.data[i];
764 if( src->state & FLAG_IS_TRIGGER )
765 {
766 int link_id = src->links[0]?0:1;
767 if( src->links[link_id] <= world.h*world.w )
768 {
769 struct cell *target = &world.data[ src->links[link_id] ];
770 if( (target->state & FLAG_CANAL) && (target->config == k_cell_type_split) )
771 {
772 if( target->links[ link_id ] )
773 {
774 vg_error( "Link target was already targeted\n" );
775 goto IL_REG_ERROR;
776 }
777 else
778 {
779 // Valid link
780 target->links[ link_id ] = i;
781 target->state |= FLAG_TARGETED;
782 }
783 }
784 else
785 {
786 vg_error( "Link target was invalid\n" );
787 goto IL_REG_ERROR;
788 }
789 }
790 else
791 {
792 vg_error( "Link target out of bounds\n" );
793 goto IL_REG_ERROR;
794 }
795 }
796 }
797
798 vg_success( "Map '%s' loaded! (%u:%u)\n", name, world.w, world.h );
799
800 io_reset();
801
802 strncpy( world.map_name, name, vg_list_size( world.map_name )-1 );
803 world.initialzed = 1;
804 return 1;
805
806 IL_REG_ERROR:
807 arrfree( links_to_make );
808 map_free();
809 return 0;
810 }
811
812 static void map_serialize( FILE *stream )
813 {
814 for( int y = 0; y < world.h; y ++ )
815 {
816 for( int x = 0; x < world.w; x ++ )
817 {
818 struct cell *cell = pcell( (v2i){ x, y } );
819
820 if( cell->state & FLAG_WALL ) fputc( '#', stream );
821 else if( cell->state & FLAG_INPUT ) fputc( '+', stream );
822 else if( cell->state & FLAG_OUTPUT ) fputc( '-', stream );
823 else if( cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1) )
824 {
825 fputc( (cell->state & (FLAG_CANAL|FLAG_IS_TRIGGER|FLAG_RESERVED0|FLAG_RESERVED1)) + (u32)'A', stream );
826 }
827 else fputc( ' ', stream );
828 }
829
830 fputc( ';', stream );
831
832 int terminal_write_count = 0;
833
834 for( int x = 0; x < world.w; x ++ )
835 {
836 for( int i = 0; i < arrlen( world.io ); i ++ )
837 {
838 struct cell_terminal *term = &world.io[i];
839 if( v2i_eq( term->pos, (v2i){x,y} ) )
840 {
841 if( terminal_write_count )
842 fputc( ',', stream );
843 terminal_write_count ++;
844
845 for( int j = 0; j < term->run_count; j ++ )
846 {
847 struct terminal_run *run = &term->runs[j];
848
849 for( int k = 0; k < run->condition_count; k ++ )
850 fputc( run->conditions[k], stream );
851
852 if( j < term->run_count-1 )
853 fputc( ':', stream );
854 }
855 }
856 }
857 }
858
859 for( int x = 0; x < world.w; x ++ )
860 {
861 struct cell *cell = pcell( (v2i){ x,y } );
862 if( cell->state & FLAG_IS_TRIGGER )
863 {
864 if( terminal_write_count )
865 fputc( ',', stream );
866 terminal_write_count ++;
867
868 fprintf( stream, "%d", cell->links[0]? -cell->links[0]: cell->links[1] );
869 }
870 }
871
872 fputc( '\n', stream );
873 }
874 }
875
876 // CAREER STATE
877 // ===========================================================================================================
878
879 #pragma pack(push,1)
880 struct dcareer_state
881 {
882 u32 version;
883 i32 in_map;
884
885 u32 reserved[14];
886
887 struct dlevel_state
888 {
889 i32 score;
890 i32 unlocked;
891 i32 reserved[2];
892 }
893 levels[ NUM_CAMPAIGN_LEVELS ];
894 };
895 #pragma pack(pop)
896
897 static int career_load_success = 0;
898
899 static void career_serialize(void)
900 {
901 if( !career_load_success )
902 return;
903
904 struct dcareer_state encoded;
905 encoded.version = 2;
906 encoded.in_map = world.pCmpLevel? world.pCmpLevel->serial_id: -1;
907
908 memset( encoded.reserved, 0, sizeof( encoded.reserved ) );
909
910 for( int i = 0; i < vg_list_size( career_serializable ); i ++ )
911 {
912 struct serializable_set *set = &career_serializable[i];
913
914 for( int j = 0; j < set->count; j ++ )
915 {
916 struct cmp_level *lvl = &set->pack[j];
917 struct dlevel_state *dest = &encoded.levels[lvl->serial_id];
918
919 dest->score = lvl->completed_score;
920 dest->unlocked = lvl->unlocked;
921 dest->reserved[0] = 0;
922 dest->reserved[1] = 0;
923 }
924 }
925
926 vg_asset_write( "sav/game.sv2", &encoded, sizeof( struct dcareer_state ) );
927 }
928
929 static void career_unlock_level( struct cmp_level *lvl );
930 static void career_unlock_level( struct cmp_level *lvl )
931 {
932 lvl->unlocked = 1;
933
934 if( lvl->linked )
935 career_unlock_level( lvl->linked );
936 }
937
938 static void career_pass_level( struct cmp_level *lvl, int score, int upload )
939 {
940 if( score > 0 )
941 {
942 if( score < lvl->completed_score || lvl->completed_score == 0 )
943 {
944 if( !lvl->is_tutorial && upload )
945 leaderboard_set_score( lvl, score );
946
947 lvl->completed_score = score;
948 }
949
950 if( lvl->unlock ) career_unlock_level( lvl->unlock );
951 }
952 }
953
954 static void career_reset_level( struct cmp_level *lvl )
955 {
956 lvl->unlocked = 0;
957 lvl->completed_score = 0;
958 }
959
960 static void career_load(void)
961 {
962 i64 sz;
963 struct dcareer_state encoded;
964
965 // Blank save state
966 memset( (void*)&encoded, 0, sizeof( struct dcareer_state ) );
967 encoded.in_map = -1;
968 encoded.levels[0].unlocked = 1;
969
970 // Load and copy data into encoded
971 void *cr = vg_asset_read_s( "sav/game.sv2", &sz );
972
973 if( cr )
974 {
975 if( sz > sizeof( struct dcareer_state ) )
976 vg_warn( "This save file is too big! Some levels will be lost\n" );
977
978 if( sz <= offsetof( struct dcareer_state, levels ) )
979 {
980 vg_warn( "This save file is too small to have a header. Creating a blank one\n" );
981 free( cr );
982 }
983
984 memcpy( (void*)&encoded, cr, VG_MIN( sizeof( struct dcareer_state ), sz ) );
985 free( cr );
986 }
987 else
988 vg_info( "No save file... Using blank one\n" );
989
990 // Reset memory
991 for( int i = 0; i < vg_list_size( career_serializable ); i ++ )
992 {
993 struct serializable_set *set = &career_serializable[i];
994
995 for( int j = 0; j < set->count; j ++ )
996 career_reset_level( &set->pack[j] );
997 }
998
999 // Header information
1000 // =================================
1001
1002 // Decode everything from dstate
1003 for( int i = 0; i < vg_list_size( career_serializable ); i ++ )
1004 {
1005 struct serializable_set *set = &career_serializable[i];
1006
1007 for( int j = 0; j < set->count; j ++ )
1008 {
1009 struct cmp_level *lvl = &set->pack[j];
1010 struct dlevel_state *src = &encoded.levels[lvl->serial_id];
1011
1012 if( src->unlocked ) career_unlock_level( lvl );
1013 if( src->score ) lvl->completed_score = src->score;
1014
1015 // ...
1016 if( lvl->serial_id == encoded.in_map )
1017 {
1018 if( console_changelevel( 1, &lvl->map_name ) )
1019 world.pCmpLevel = lvl;
1020 }
1021 }
1022 }
1023
1024 career_load_success = 1;
1025 }
1026
1027 // MAIN GAMEPLAY
1028 // ===========================================================================================================
1029 static int is_simulation_running(void)
1030 {
1031 return world.buttons[ k_world_button_sim ].pressed;
1032 }
1033
1034 static void clear_animation_flags(void)
1035 {
1036 for( int i = 0; i < world.w*world.h; i ++ )
1037 world.data[ i ].state &= ~(FLAG_FLIP_FLOP|FLAG_FLIP_ROTATING);
1038 }
1039
1040 static void simulation_stop(void)
1041 {
1042 world.buttons[ k_world_button_sim ].pressed = 0;
1043 world.buttons[ k_world_button_pause ].pressed = 0;
1044
1045 world.num_fishes = 0;
1046 world.sim_frame = 0;
1047 world.sim_run = 0;
1048 world.frame_lerp = 0.0f;
1049
1050 io_reset();
1051
1052 sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
1053
1054 clear_animation_flags();
1055
1056 vg_info( "Stopping simulation!\n" );
1057 }
1058
1059 static void simulation_start(void)
1060 {
1061 vg_success( "Starting simulation!\n" );
1062
1063 sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
1064
1065 world.num_fishes = 0;
1066 world.sim_frame = 0;
1067 world.sim_run = 0;
1068
1069 world.sim_delta_speed = 2.5f;
1070 world.sim_delta_ref = vg_time;
1071 world.sim_internal_ref = 0.0f;
1072 world.sim_internal_time = 0.0f;
1073 world.pause_offset_target = 0.0f;
1074
1075 world.sim_target = 0;
1076
1077 clear_animation_flags();
1078
1079 io_reset();
1080 }
1081
1082 static int world_check_pos_ok( v2i co )
1083 {
1084 return (co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2)? 0: 1;
1085 }
1086
1087 static int cell_interactive( v2i co )
1088 {
1089 // Bounds check
1090 if( !world_check_pos_ok( co ) )
1091 return 0;
1092
1093 // Flags check
1094 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
1095 return 0;
1096
1097 // List of 3x3 configurations that we do not allow
1098 static u32 invalid_src[][9] =
1099 {
1100 { 0,1,0,
1101 1,1,1,
1102 0,1,0
1103 },
1104 { 0,0,0,
1105 0,1,1,
1106 0,1,1
1107 },
1108 { 0,0,0,
1109 1,1,0,
1110 1,1,0
1111 },
1112 { 0,1,1,
1113 0,1,1,
1114 0,0,0
1115 },
1116 { 1,1,0,
1117 1,1,0,
1118 0,0,0
1119 },
1120 { 0,1,0,
1121 0,1,1,
1122 0,1,0
1123 },
1124 { 0,1,0,
1125 1,1,0,
1126 0,1,0
1127 }
1128 };
1129
1130 // Statically compile invalid configurations into bitmasks
1131 static u32 invalid[ vg_list_size(invalid_src) ];
1132
1133 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
1134 {
1135 u32 comped = 0x00;
1136
1137 for( int j = 0; j < 3; j ++ )
1138 for( int k = 0; k < 3; k ++ )
1139 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
1140
1141 invalid[i] = comped;
1142 }
1143
1144 // Extract 5x5 grid surrounding tile
1145 u32 blob = 0x1000;
1146 for( int y = co[1]-2; y < co[1]+3; y ++ )
1147 for( int x = co[0]-2; x < co[0]+3; x ++ )
1148 {
1149 struct cell *cell = pcell((v2i){x,y});
1150
1151 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
1152 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
1153 }
1154
1155 // Run filter over center 3x3 grid to check for invalid configurations
1156 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
1157 for( int i = 0; i < vg_list_size(kernel); i ++ )
1158 {
1159 if( blob & (0x1 << (6+kernel[i])) )
1160 {
1161 u32 window = blob >> kernel[i];
1162
1163 for( int j = 0; j < vg_list_size(invalid); j ++ )
1164 if((window & invalid[j]) == invalid[j])
1165 return 0;
1166 }
1167 }
1168
1169 return 1;
1170 }
1171
1172 void vg_update(void)
1173 {
1174 // Camera
1175 // ========================================================================================================
1176
1177 float r1 = (float)vg_window_y / (float)vg_window_x,
1178 r2 = (float)world.h / (float)world.w,
1179 size;
1180
1181 size = ( r2 < r1? (float)world.w * 0.5f: ((float)world.h * 0.5f) / r1 ) + 2.5f;
1182 m3x3_projection( m_projection, -size, size, -size*r1, size*r1 );
1183
1184 v3f origin;
1185 origin[0] = floorf( -0.5f * world.w );
1186 origin[1] = floorf( -0.5f * world.h );
1187 origin[2] = 0.0f;
1188
1189 m3x3_identity( m_view );
1190 m3x3_translate( m_view, origin );
1191 m3x3_mul( m_projection, m_view, vg_pv );
1192 vg_projection_update();
1193
1194 // Mouse input
1195 // ========================================================================================================
1196 v2_copy( vg_mouse_ws, world.tile_pos );
1197
1198 world.tile_x = floorf( world.tile_pos[0] );
1199 world.tile_y = floorf( world.tile_pos[1] );
1200
1201 // Tilemap
1202 // ========================================================================================================
1203 if( !is_simulation_running() && !gui_want_mouse() )
1204 {
1205 v2_copy( vg_mouse_ws, world.drag_to_co );
1206
1207 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
1208 {
1209 world.selected = world.tile_y * world.w + world.tile_x;
1210
1211 static u32 modify_state = 0;
1212
1213 struct cell *cell_ptr = &world.data[world.selected];
1214
1215 if( vg_get_button_down("primary") )
1216 modify_state = (cell_ptr->state & FLAG_CANAL) ^ FLAG_CANAL;
1217
1218 if( vg_get_button("primary") && ((cell_ptr->state & FLAG_CANAL) != modify_state) )
1219 {
1220 cell_ptr->state &= ~FLAG_CANAL;
1221 cell_ptr->state |= modify_state;
1222
1223 if( cell_ptr->state & FLAG_CANAL )
1224 {
1225 cell_ptr->links[0] = 0;
1226 cell_ptr->links[1] = 0;
1227
1228 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
1229 world.score ++;
1230 }
1231 else
1232 {
1233 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
1234 world.score --;
1235 }
1236
1237 map_reclassify( (v2i){ world.tile_x -2, world.tile_y -2 },
1238 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
1239 }
1240
1241 if( vg_get_button_down("secondary") && (cell_ptr->state & FLAG_CANAL) && !(cell_ptr->config == k_cell_type_split) )
1242 {
1243 world.id_drag_from = world.selected;
1244
1245 struct cell_description *desc = &cell_descriptions[ world.data[world.id_drag_from].config ];
1246 v2_add( desc->trigger_pos, (v2f){ world.tile_x, world.tile_y }, world.drag_from_co );
1247 }
1248
1249 float local_x = vg_mouse_ws[0] - (float)world.tile_x;
1250
1251 if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
1252 {
1253 u32 link_id = local_x > 0.5f? 1: 0;
1254
1255 // break existing connection off
1256 if( cell_ptr->links[ link_id ] )
1257 {
1258 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1259
1260 if( !current_connection->links[ link_id ^ 0x1 ] )
1261 current_connection->state &= ~FLAG_TARGETED;
1262
1263 current_connection->links[ link_id ] = 0;
1264 cell_ptr->links[ link_id ] = 0;
1265 }
1266
1267 cell_ptr->state &= ~FLAG_IS_TRIGGER;
1268 world.id_drag_from = 0;
1269 }
1270
1271 if( world.id_drag_from && (cell_ptr->state & FLAG_CANAL) && (cell_ptr->config == k_cell_type_split) )
1272 {
1273 world.drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
1274 world.drag_to_co[1] = (float)world.tile_y + 0.25f;
1275
1276 if( vg_get_button_up("secondary") )
1277 {
1278 struct cell *drag_ptr = &world.data[world.id_drag_from];
1279 u32 link_id = local_x > 0.5f? 1: 0;
1280
1281 // Cleanup existing connections
1282 if( cell_ptr->links[ link_id ] )
1283 {
1284 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id, cell_ptr->links[ link_id ] );
1285
1286 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1287 current_connection->state &= ~FLAG_IS_TRIGGER;
1288 current_connection->links[ link_id ] = 0;
1289 }
1290
1291 if( drag_ptr->links[ link_id ^ 0x1 ] )
1292 {
1293 vg_warn( "Destroying alternate link %u (%hu)\n", link_id ^ 0x1, drag_ptr->links[ link_id ^ 0x1 ] );
1294
1295 struct cell *current_connection = &world.data[ drag_ptr->links[ link_id ^ 0x1 ]];
1296 if( !current_connection->links[ link_id ] )
1297 current_connection->state &= ~FLAG_TARGETED;
1298
1299 current_connection->links[ link_id ^ 0x1 ] = 0;
1300 drag_ptr->links[ link_id ^ 0x1 ] = 0;
1301 }
1302
1303 // Create the new connection
1304 vg_success( "Creating connection on link %u (%hu)\n", link_id, world.id_drag_from );
1305
1306 cell_ptr->links[ link_id ] = world.id_drag_from;
1307 drag_ptr->links[ link_id ] = world.selected;
1308
1309 cell_ptr->state |= FLAG_TARGETED;
1310 drag_ptr->state |= FLAG_IS_TRIGGER;
1311 world.id_drag_from = 0;
1312 }
1313 }
1314 }
1315 else
1316 {
1317 world.selected = -1;
1318 }
1319
1320 if( !(vg_get_button("secondary") && world.id_drag_from) )
1321 world.id_drag_from = 0;
1322 }
1323 else
1324 {
1325 world.selected = -1;
1326 world.id_drag_from = 0;
1327 }
1328
1329 // Marble state updates
1330 // ========================================================================================================
1331 if( is_simulation_running() )
1332 {
1333 float old_time = world.sim_internal_time;
1334
1335 if( !world.buttons[ k_world_button_pause ].pressed )
1336 world.sim_internal_time = world.sim_internal_ref + (vg_time-world.sim_delta_ref) * world.sim_delta_speed;
1337 else
1338 world.sim_internal_time = vg_lerpf( world.sim_internal_time, world.sim_internal_ref + world.pause_offset_target, vg_time_delta*15.0f );
1339 world.sim_internal_delta = world.sim_internal_time-old_time;
1340
1341 world.sim_target = (int)floorf(world.sim_internal_time);
1342
1343 while( world.sim_frame < world.sim_target )
1344 {
1345 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
1346
1347 // Update splitter deltas
1348 for( int i = 0; i < world.h*world.w; i ++ )
1349 {
1350 struct cell *cell = &world.data[i];
1351 if( cell->config == k_cell_type_split )
1352 {
1353 cell->state &= ~FLAG_FLIP_ROTATING;
1354 }
1355 if( cell->state & FLAG_IS_TRIGGER )
1356 cell->state &= ~FLAG_TRIGGERED;
1357 }
1358
1359 int alive_count = 0;
1360
1361 // Update fish positions
1362 for( int i = 0; i < world.num_fishes; i ++ )
1363 {
1364 struct fish *fish = &world.fishes[i];
1365
1366 if( fish->state == k_fish_state_soon_dead )
1367 fish->state = k_fish_state_dead;
1368
1369 if( fish->state == k_fish_state_soon_alive )
1370 fish->state = k_fish_state_alive;
1371
1372 if( fish->state < k_fish_state_alive )
1373 continue;
1374
1375 struct cell *cell_current = pcell( fish->pos );
1376
1377 if( fish->state == k_fish_state_alive )
1378 {
1379 // Apply to output
1380 if( cell_current->state & FLAG_OUTPUT )
1381 {
1382 for( int j = 0; j < arrlen( world.io ); j ++ )
1383 {
1384 struct cell_terminal *term = &world.io[j];
1385
1386 if( v2i_eq( term->pos, fish->pos ) )
1387 {
1388 struct terminal_run *run = &term->runs[ world.sim_run ];
1389 if( run->recv_count < vg_list_size( run->recieved ) )
1390 run->recieved[ run->recv_count ++ ] = fish->payload;
1391
1392 break;
1393 }
1394 }
1395
1396 fish->state = k_fish_state_dead;
1397 fish->death_time = -1000.0f;
1398 continue;
1399 }
1400
1401
1402 if( cell_current->config == k_cell_type_merge )
1403 {
1404 // Can only move up
1405 fish->dir[0] = 0;
1406 fish->dir[1] = -1;
1407 fish->flow_reversed = 0;
1408 }
1409 else
1410 {
1411 if( cell_current->config == k_cell_type_split )
1412 {
1413 // Flip flop L/R
1414 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
1415 fish->dir[1] = 0;
1416
1417 if( !(cell_current->state & FLAG_TARGETED) )
1418 cell_current->state ^= FLAG_FLIP_FLOP;
1419 }
1420 else
1421 {
1422 // Apply cell out-flow
1423 struct cell_description *desc = &cell_descriptions[ cell_current->config ];
1424
1425 v2i_copy( fish->flow_reversed? desc->start: desc->end, fish->dir );
1426 }
1427
1428 v2i pos_next;
1429 v2i_add( fish->pos, fish->dir, pos_next );
1430
1431 struct cell *cell_next = pcell( pos_next );
1432
1433 if( cell_next->state & (FLAG_CANAL|FLAG_OUTPUT) )
1434 {
1435 struct cell_description *desc = &cell_descriptions[ cell_next->config ];
1436
1437 if( cell_next->config == k_cell_type_merge )
1438 {
1439 if( fish->dir[0] == 0 )
1440 {
1441 fish->state = k_fish_state_dead;
1442 fish->death_time = world.sim_internal_time;
1443 }
1444 else
1445 fish->flow_reversed = 0;
1446 }
1447 else
1448 {
1449 if( cell_next->config == k_cell_type_split )
1450 {
1451 if( fish->dir[0] == 0 )
1452 {
1453 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
1454 cell_next->state |= FLAG_FLIP_ROTATING;
1455
1456 fish->flow_reversed = 0;
1457 }
1458 else
1459 {
1460 fish->state = k_fish_state_dead;
1461 fish->death_time = world.sim_internal_time;
1462 }
1463 }
1464 else
1465 fish->flow_reversed = ( fish->dir[0] != -desc->start[0] ||
1466 fish->dir[1] != -desc->start[1] )? 1: 0;
1467 }
1468 }
1469 else
1470 {
1471 if( world_check_pos_ok( fish->pos ) )
1472 fish->state = k_fish_state_bg;
1473 else
1474 {
1475 fish->state = k_fish_state_dead;
1476 fish->death_time = world.sim_internal_time;
1477 }
1478 }
1479 }
1480
1481 //v2i_add( fish->pos, fish->dir, fish->pos );
1482 }
1483 else if( fish->state == k_fish_state_bg )
1484 {
1485 v2i_add( fish->pos, fish->dir, fish->pos );
1486
1487 if( !world_check_pos_ok( fish->pos ) )
1488 {
1489 fish->state = k_fish_state_dead;
1490 fish->death_time = -1000.0f;
1491 }
1492 else
1493 {
1494 struct cell *cell_entry = pcell( fish->pos );
1495
1496 if( cell_entry->state & FLAG_CANAL )
1497 {
1498 if( cell_entry->config == k_cell_type_con_r || cell_entry->config == k_cell_type_con_u
1499 || cell_entry->config == k_cell_type_con_l || cell_entry->config == k_cell_type_con_d )
1500 {
1501 sw_set_achievement( "CAN_DO_THAT" );
1502
1503 fish->state = k_fish_state_soon_alive;
1504
1505 fish->dir[0] = 0;
1506 fish->dir[1] = 0;
1507 fish->flow_reversed = 1;
1508
1509 switch( cell_entry->config )
1510 {
1511 case k_cell_type_con_r: fish->dir[0] = 1; break;
1512 case k_cell_type_con_l: fish->dir[0] = -1; break;
1513 case k_cell_type_con_u: fish->dir[1] = 1; break;
1514 case k_cell_type_con_d: fish->dir[1] = -1; break;
1515 }
1516 }
1517 }
1518 }
1519 }
1520 else { vg_error( "fish behaviour unimplemented for behaviour type (%d)\n" ); }
1521
1522 if( fish->state >= k_fish_state_alive )
1523 alive_count ++;
1524 }
1525
1526 // Second pass (triggers)
1527 for( int i = 0; i < world.num_fishes; i ++ )
1528 {
1529 struct fish *fish = &world.fishes[i];
1530
1531 if( fish->state == k_fish_state_alive )
1532 {
1533 v2i_add( fish->pos, fish->dir, fish->pos );
1534 struct cell *cell_current = pcell( fish->pos );
1535
1536 if( cell_current->state & FLAG_IS_TRIGGER )
1537 {
1538 int trigger_id = cell_current->links[0]?0:1;
1539
1540 struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
1541
1542 cell_current->state |= FLAG_TRIGGERED;
1543
1544 if( trigger_id )
1545 target_peice->state |= FLAG_FLIP_FLOP;
1546 else
1547 target_peice->state &= ~FLAG_FLIP_FLOP;
1548 }
1549 }
1550 }
1551
1552 // Third pass (collisions)
1553 struct fish *fi, *fj;
1554
1555 for( int i = 0; i < world.num_fishes; i ++ )
1556 {
1557 fi = &world.fishes[i];
1558
1559 if( fi->state == k_fish_state_alive )
1560 {
1561 int continue_again = 0;
1562
1563 for( int j = i+1; j < world.num_fishes; j ++ )
1564 {
1565 fj = &world.fishes[j];
1566
1567 if( (fj->state == k_fish_state_alive) )
1568 {
1569 v2i fi_prev;
1570 v2i fj_prev;
1571
1572 v2i_sub( fi->pos, fi->dir, fi_prev );
1573 v2i_sub( fj->pos, fj->dir, fj_prev );
1574
1575 int
1576 collide_next_frame = (
1577 (fi->pos[0] == fj->pos[0]) &&
1578 (fi->pos[1] == fj->pos[1]))? 1: 0,
1579 collide_this_frame = (
1580 (fi_prev[0] == fj->pos[0]) &&
1581 (fi_prev[1] == fj->pos[1]) &&
1582 (fj_prev[0] == fi->pos[0]) &&
1583 (fj_prev[1] == fi->pos[1])
1584 )? 1: 0;
1585
1586 if( collide_next_frame || collide_this_frame )
1587 {
1588 sw_set_achievement( "BANG" );
1589
1590 // Shatter death (+0.5s)
1591 float death_time = world.sim_internal_time + ( collide_this_frame? 0.0f: 0.5f );
1592
1593 fi->state = k_fish_state_soon_dead;
1594 fj->state = k_fish_state_soon_dead;
1595 fi->death_time = death_time;
1596 fj->death_time = death_time;
1597
1598 continue_again = 1;
1599 break;
1600 }
1601 }
1602 }
1603 if( continue_again )
1604 continue;
1605 }
1606 }
1607
1608 // Spawn fishes
1609 for( int i = 0; i < arrlen( world.io ); i ++ )
1610 {
1611 struct cell_terminal *term = &world.io[ i ];
1612 int is_input = pcell(term->pos)->state & FLAG_INPUT;
1613
1614 if( is_input )
1615 {
1616 if( world.sim_frame < term->runs[ world.sim_run ].condition_count )
1617 {
1618 char emit = term->runs[ world.sim_run ].conditions[ world.sim_frame ];
1619 if( emit == ' ' )
1620 continue;
1621
1622 struct fish *fish = &world.fishes[ world.num_fishes ];
1623 v2i_copy( term->pos, fish->pos );
1624
1625 fish->state = k_fish_state_alive;
1626 fish->payload = emit;
1627
1628 struct cell *cell_ptr = pcell( fish->pos );
1629
1630 if( cell_ptr->config != k_cell_type_stub )
1631 {
1632 struct cell_description *desc = &cell_descriptions[ cell_ptr->config ];
1633
1634 v2i_copy( desc->start, fish->dir );
1635 fish->flow_reversed = 1;
1636
1637 world.num_fishes ++;
1638 alive_count ++;
1639 }
1640 }
1641 }
1642 }
1643
1644 if( alive_count == 0 )
1645 {
1646 world.completed = 1;
1647
1648 for( int i = 0; i < arrlen( world.io ); i ++ )
1649 {
1650 struct cell_terminal *term = &world.io[ i ];
1651 int is_input = pcell(term->pos)->state & FLAG_INPUT;
1652
1653 if( !is_input )
1654 {
1655 struct terminal_run *run = &term->runs[ world.sim_run ];
1656
1657 if( run->recv_count == run->condition_count )
1658 {
1659 for( int j = 0; j < run->condition_count; j ++ )
1660 {
1661 if( run->recieved[j] != run->conditions[j] )
1662 {
1663 world.completed = 0;
1664 break;
1665 }
1666 }
1667 }
1668 else
1669 {
1670 world.completed = 0;
1671 break;
1672 }
1673 }
1674 }
1675
1676 if( world.completed )
1677 {
1678 if( world.sim_run < world.max_runs-1 )
1679 {
1680 vg_success( "Run passed, starting next\n" );
1681 world.sim_run ++;
1682 world.sim_frame = 0;
1683 world.sim_target = 0;
1684 world.num_fishes = 0;
1685
1686 // Reset timing reference points
1687 world.sim_delta_ref = vg_time;
1688 world.sim_internal_ref = 0.0f;
1689
1690 if( world.buttons[ k_world_button_pause ].pressed )
1691 world.pause_offset_target = 0.5f;
1692 else
1693 world.pause_offset_target = 0.0f;
1694
1695 world.sim_internal_time = 0.0f;
1696
1697 for( int i = 0; i < world.w*world.h; i ++ )
1698 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
1699
1700 continue;
1701 }
1702 else
1703 {
1704 vg_success( "Level passed!\n" );
1705
1706 u32 score = 0;
1707 for( int i = 0; i < world.w*world.h; i ++ )
1708 if( world.data[ i ].state & FLAG_CANAL )
1709 score ++;
1710
1711 world.score = score;
1712 world.time = world.sim_frame;
1713
1714 // Copy into career data
1715 if( world.pCmpLevel )
1716 {
1717 career_pass_level( world.pCmpLevel, world.score, 1 );
1718 }
1719 }
1720 }
1721 else
1722 {
1723 if( world.sim_run > 0 )
1724 sw_set_achievement( "GOOD_ENOUGH" );
1725
1726 vg_error( "Level failed :(\n" );
1727 }
1728
1729 simulation_stop();
1730 break;
1731 }
1732
1733 world.sim_frame ++;
1734 }
1735
1736 // Position update
1737 // =====================================================================================================
1738
1739 world.frame_lerp = world.sim_internal_time - floorf( world.sim_internal_time );
1740
1741 for( int i = 0; i < world.num_fishes; i ++ )
1742 {
1743 struct fish *fish = &world.fishes[i];
1744
1745 if( fish->state == k_fish_state_dead )
1746 continue;
1747
1748 if( fish->state == k_fish_state_soon_dead && (world.sim_internal_time > fish->death_time) )
1749 continue; // Todo: particle thing?
1750
1751 struct cell *cell = pcell(fish->pos);
1752 struct cell_description *desc = &cell_descriptions[ cell->config ];
1753
1754 v2f const *curve;
1755
1756 float t = world.frame_lerp;
1757 if( fish->flow_reversed && !desc->is_linear )
1758 t = 1.0f-t;
1759
1760 v2_copy( fish->physics_co, fish->physics_v );
1761
1762 switch( cell->config )
1763 {
1764 case k_cell_type_merge:
1765 if( fish->dir[0] == 1 )
1766 curve = curve_12;
1767 else
1768 curve = curve_9;
1769 break;
1770 case k_cell_type_con_r: curve = curve_1; break;
1771 case k_cell_type_con_l: curve = curve_4; break;
1772 case k_cell_type_con_u: curve = curve_2; break;
1773 case k_cell_type_con_d: curve = curve_8; break;
1774 case 3: curve = curve_3; break;
1775 case 6: curve = curve_6; break;
1776 case 9: curve = curve_9; break;
1777 case 12: curve = curve_12; break;
1778 case 7:
1779 if( t > curve_7_linear_section )
1780 {
1781 t -= curve_7_linear_section;
1782 t *= (1.0f/(1.0f-curve_7_linear_section));
1783
1784 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
1785 }
1786 else curve = NULL;
1787 break;
1788 default: curve = NULL; break;
1789 }
1790
1791 if( curve )
1792 {
1793 float t2 = t * t;
1794 float t3 = t * t * t;
1795
1796 float cA = 3.0f*t2 - 3.0f*t3;
1797 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
1798 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
1799
1800 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
1801 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
1802 fish->physics_co[0] += (float)fish->pos[0];
1803 fish->physics_co[1] += (float)fish->pos[1];
1804 }
1805 else
1806 {
1807 v2f origin;
1808 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
1809 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
1810
1811 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
1812 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
1813 }
1814
1815 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
1816 v2_divs( fish->physics_v, world.sim_internal_delta, fish->physics_v );
1817 }
1818 }
1819 }
1820
1821 void leaderboard_found( LeaderboardFindResult_t *pCallback );
1822 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback );
1823
1824 static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
1825 {
1826 v2i full_start = { 0,0 };
1827 v2i full_end = { world.w, world.h };
1828
1829 if( !start || !end )
1830 {
1831 start = full_start;
1832 end = full_end;
1833 }
1834
1835 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1836
1837 for( int y = start[1]; y < end[1]; y ++ )
1838 {
1839 for( int x = start[0]; x < end[0]; x ++ )
1840 {
1841 struct cell *cell = pcell((v2i){x,y});
1842 int selected = world.selected == y*world.w + x;
1843
1844 int tile_offsets[][2] =
1845 {
1846 {2, 0}, {0, 3}, {0, 2}, {2, 2},
1847 {1, 0}, {2, 3}, {3, 2}, {1, 3},
1848 {3, 1}, {0, 1}, {1, 2}, {2, 1},
1849 {1, 1}, {3, 3}, {2, 1}, {2, 1}
1850 };
1851
1852 int uv[2] = { 3, 0 };
1853
1854 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
1855 {
1856 uv[0] = tile_offsets[ cell->config ][0];
1857 uv[1] = tile_offsets[ cell->config ][1];
1858 } else continue;
1859
1860 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] );
1861 if( selected )
1862 {
1863 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
1864 draw_mesh( 0, 2 );
1865 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1866 }
1867 else
1868 draw_mesh( 0, 2 );
1869 }
1870 }
1871 }
1872
1873 /*
1874 static void draw_numbers( v3f coord, int number )
1875 {
1876 v3f pos;
1877 v3_copy( coord, pos );
1878 int digits[8]; int i = 0;
1879
1880 while( number > 0 && i < 8 )
1881 {
1882 digits[i ++] = number % 10;
1883 number = number / 10;
1884 }
1885
1886 for( int j = 0; j < i; j ++ )
1887 {
1888 glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, pos );
1889 draw_mesh( MESH_NUMBERS_OFFSETS[digits[i-j-1]][0], MESH_NUMBERS_OFFSETS[digits[i-j-1]][1] );
1890 pos[0] += pos[2] * 0.75f;
1891 }
1892 }*/
1893
1894 static void wbutton_run( enum e_world_button btn_name )
1895 {
1896 static v3f button_colours[] = {
1897 {0.204f, 0.345f, 0.553f},
1898 {0.204f, 0.345f, 0.553f},
1899 {0.741f, 0.513f, 0.078f},
1900 {1.0f, 0.0f, 0.0f}
1901 };
1902
1903 struct cell_button *btn = &world.buttons[btn_name];
1904
1905 // Interaction
1906
1907 int is_hovering = (world.tile_x == world.w-1 && world.tile_y == world.h-btn_name-2)?1:0;
1908 if( vg_get_button_up( "primary" ) && is_hovering )
1909 {
1910 // Click event
1911 if( btn_name == k_world_button_sim )
1912 {
1913 if( world.buttons[ k_world_button_pause ].pressed )
1914 {
1915 if( !btn->pressed )
1916 {
1917 btn->pressed = 1;
1918 simulation_start();
1919
1920 world.pause_offset_target = 0.5f;
1921 }
1922 else
1923 world.pause_offset_target += 1.0f;
1924 }
1925 else
1926 {
1927 btn->pressed ^= 0x1;
1928
1929 if( btn->pressed )
1930 simulation_start();
1931 else
1932 simulation_stop();
1933 }
1934 }
1935 else if( btn_name == k_world_button_pause )
1936 {
1937 btn->pressed ^= 0x1;
1938
1939 world.sim_internal_ref = world.sim_internal_time;
1940 world.sim_delta_ref = vg_time;
1941
1942 if( btn->pressed )
1943 {
1944 float time_frac = world.sim_internal_time-floorf(world.sim_internal_time);
1945 world.pause_offset_target = 0.5f - time_frac;
1946 }
1947 else
1948 world.pause_offset_target = 0.0f;
1949 }
1950 else
1951 {
1952 btn->pressed ^= 0x1;
1953 }
1954
1955 sfx_set_play( &audio_clicks, &audio_system_ui, btn->pressed?1:0 );
1956 }
1957
1958 // Drawing
1959 if( btn->pressed )
1960 {
1961 if( is_hovering )
1962 {
1963 btn->light_target = 0.9f;
1964 }
1965 else
1966 {
1967 if( btn_name == k_world_button_sim && world.buttons[ k_world_button_pause ].pressed )
1968 btn->light_target = fabsf(sinf( vg_time * 2.0f )) * 0.3f + 0.3f;
1969 else
1970 btn->light_target = 0.8f;
1971 }
1972 }
1973 else
1974 {
1975 btn->light_target = is_hovering? 0.2f: 0.0f;
1976 }
1977
1978 if( vg_get_button( "primary" ) && is_hovering )
1979 btn->light_target = 1.0f;
1980
1981 btn->light = vg_lerpf( btn->light, btn->light_target, vg_time_delta*26.0f );
1982
1983 // Draw
1984
1985 v4f final_colour;
1986 v3_copy( button_colours[ btn_name ], final_colour );
1987 final_colour[3] = btn->light;
1988
1989 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
1990 world.w-1,
1991 world.h-btn_name-2,
1992 (float)btn_name,
1993 3.0f
1994 );
1995 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
1996
1997 draw_mesh( 0, 2 );
1998 }
1999
2000 void vg_render(void)
2001 {
2002 glViewport( 0,0, vg_window_x, vg_window_y );
2003
2004 glDisable( GL_DEPTH_TEST );
2005 glClearColor( 0.369768f, 0.3654f, 0.42f, 1.0f );
2006 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
2007
2008 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
2009 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
2010
2011 int const circle_base = 6;
2012 int const filled_start = circle_base+0;
2013 int const filled_count = circle_base+32;
2014 int const empty_start = circle_base+32;
2015 int const empty_count = circle_base+32*2;
2016
2017 // BACKGROUND
2018 // ========================================================================================================
2019 use_mesh( &world.shapes );
2020
2021 SHADER_USE( shader_background );
2022 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2023
2024 glActiveTexture( GL_TEXTURE0 );
2025 glBindTexture( GL_TEXTURE_2D, world.background_data );
2026 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
2027
2028 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
2029 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.02f );
2030
2031 glActiveTexture( GL_TEXTURE1 );
2032 glBindTexture( GL_TEXTURE_2D, world.random_samples );
2033 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
2034
2035 draw_mesh( 0, 2 );
2036
2037 // TILESET BACKGROUND LAYER
2038 // ========================================================================================================
2039 use_mesh( &world.shapes );
2040 SHADER_USE( shader_tile_main );
2041
2042 m2x2f subtransform;
2043 m2x2_identity( subtransform );
2044 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2045 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2046 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
2047 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
2048
2049 glEnable(GL_BLEND);
2050 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2051 glBlendEquation(GL_FUNC_ADD);
2052
2053 // Bind textures
2054 vg_tex2d_bind( &tex_tile_data, 0 );
2055 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2056
2057 vg_tex2d_bind( &tex_wood, 1 );
2058 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2059
2060 render_tiles( NULL, NULL, colour_default, colour_default );
2061
2062 // MARBLES
2063 // ========================================================================================================
2064 SHADER_USE( shader_ball );
2065 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2066
2067 vg_tex2d_bind( &tex_ball_noise, 0 );
2068 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
2069
2070 if( get_wbutton( k_world_button_sim )->pressed )
2071 {
2072 for( int i = 0; i < world.num_fishes; i ++ )
2073 {
2074 struct fish *fish = &world.fishes[i];
2075 v3f render_pos;
2076 render_pos[2] = 1.0f;
2077
2078 if( fish->state == k_fish_state_dead || fish->state == k_fish_state_soon_dead )
2079 {
2080 float death_anim_time = world.sim_internal_time - fish->death_time;
2081
2082 // Death animation
2083 if( death_anim_time > 0.0f && death_anim_time < 1.0f )
2084 {
2085 float amt = 1.0f-death_anim_time*death_anim_time;
2086
2087 v2_muladds( fish->physics_co, fish->physics_v, -1.0f * world.sim_internal_delta * amt, fish->physics_co );
2088 render_pos[2] = amt;
2089 }
2090 else if( world.sim_internal_time > fish->death_time )
2091 continue;
2092 }
2093 else if( fish->state == k_fish_state_bg )
2094 continue;
2095
2096 v2_copy( fish->physics_co, render_pos );
2097
2098 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2099 colour_code_v3( fish->payload, dot_colour );
2100
2101 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
2102 glUniform3fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, render_pos );
2103 glUniform2f( SHADER_UNIFORM( shader_ball, "uTexOffset" ), (float)i * 1.2334, (float)i * -0.3579f );
2104 draw_mesh( 0, 2 );
2105 }
2106 }
2107
2108 // TILESET FOREGROUND LAYER
2109 // ========================================================================================================
2110 SHADER_USE( shader_tile_main );
2111
2112 // Bind textures
2113 vg_tex2d_bind( &tex_tile_data, 0 );
2114 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2115
2116 vg_tex2d_bind( &tex_wood, 1 );
2117 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2118
2119 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
2120 render_tiles( NULL, NULL, colour_default, colour_selected );
2121
2122 // Draw splitters
2123 for( int y = 2; y < world.h-2; y ++ )
2124 {
2125 for( int x = 2; x < world.w-2; x ++ )
2126 {
2127 struct cell *cell = pcell((v2i){x,y});
2128
2129 if( cell->state & FLAG_CANAL )
2130 {
2131 if( cell->config == k_cell_type_split )
2132 {
2133 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
2134
2135 if( cell->state & FLAG_FLIP_ROTATING )
2136 {
2137 if( (world.frame_lerp > curve_7_linear_section) )
2138 {
2139 float const rotation_speed = 0.4f;
2140 if( (world.frame_lerp < 1.0f-rotation_speed) )
2141 {
2142 float t = world.frame_lerp - curve_7_linear_section;
2143 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
2144 t += 1.0f;
2145
2146 rotation *= t;
2147 }
2148 else
2149 rotation *= -1.0f;
2150 }
2151 }
2152
2153 m2x2_create_rotation( subtransform, rotation );
2154
2155 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2156 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
2157 (float)x,
2158 (float)y + 0.125f,
2159 cell->state & FLAG_TARGETED? 3.0f: 0.0f,
2160 0.0f
2161 );
2162 draw_mesh( 0, 2 );
2163 }
2164 }
2165 }
2166 }
2167
2168 // EDIT OVERLAY
2169 // ========================================================================================================
2170 if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) && !world.id_drag_from )
2171 {
2172 v2i new_begin = { world.tile_x - 2, world.tile_y - 2 };
2173 v2i new_end = { world.tile_x + 2, world.tile_y + 2 };
2174
2175 world.data[ world.selected ].state ^= FLAG_CANAL;
2176 map_reclassify( new_begin, new_end, 0 );
2177
2178 m2x2_identity( subtransform );
2179 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f );
2180 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2181 glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
2182
2183 render_tiles( new_begin, new_end, colour_default, colour_default );
2184
2185 world.data[ world.selected ].state ^= FLAG_CANAL;
2186 map_reclassify( new_begin, new_end, 0 );
2187 }
2188
2189 // BUTTONS
2190 // ========================================================================================================
2191 SHADER_USE( shader_buttons );
2192 glUniformMatrix3fv( SHADER_UNIFORM( shader_buttons, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2193
2194 vg_tex2d_bind( &tex_buttons, 0 );
2195 glUniform1i( SHADER_UNIFORM( shader_buttons, "uTexMain" ), 0 );
2196
2197 wbutton_run( k_world_button_sim );
2198 wbutton_run( k_world_button_pause );
2199 //wbutton_run( k_world_button_wire_mode );
2200
2201 // WIRES
2202 // ========================================================================================================
2203 //glDisable(GL_BLEND);
2204
2205 SHADER_USE( shader_wire );
2206 glBindVertexArray( world.wire.vao );
2207
2208 glUniformMatrix3fv( SHADER_UNIFORM( shader_wire, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2209
2210 v4f const wire_left_colour = { 0.5f, 0.5f, 0.5f, 1.0f };
2211 v4f const wire_right_colour = { 0.2f, 0.2f, 0.2f, 1.0f };
2212 v4f const wire_drag_colour = { 0.2f, 0.2f, 0.2f, 0.6f };
2213
2214 glUniform1f( SHADER_UNIFORM( shader_wire, "uTime" ), world.frame_lerp );
2215 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), 0.0f );
2216
2217 if( world.id_drag_from )
2218 {
2219 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_drag_colour );
2220 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), 0.4f );
2221 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), world.drag_from_co[0], world.drag_from_co[1], 0.20f );
2222 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.20f );
2223 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2224 }
2225
2226 // Pulling animation
2227 float rp_x1 = world.frame_lerp*9.0f;
2228 float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f;
2229 float rp_x2 = 1.0f-rp_xa;
2230
2231 for( int y = 2; y < world.h-2; y ++ )
2232 {
2233 for( int x = 2; x < world.w-2; x ++ )
2234 {
2235 struct cell *cell = pcell((v2i){x,y});
2236
2237 if( cell->state & FLAG_CANAL )
2238 {
2239 if( cell->state & FLAG_IS_TRIGGER )
2240 {
2241 struct cell_description *desc = &cell_descriptions[ cell->config ];
2242
2243 int trigger_id = cell->links[0]?0:1;
2244
2245 int x2 = cell->links[trigger_id] % world.w;
2246 int y2 = (cell->links[trigger_id] - x2) / world.w;
2247
2248 v2f startpoint;
2249 v2f endpoint;
2250
2251 startpoint[0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
2252 startpoint[1] = (float)y2 + 0.25f;
2253
2254 endpoint[0] = x;
2255 endpoint[1] = y;
2256
2257 v2_add( desc->trigger_pos, endpoint, endpoint );
2258
2259 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour );
2260 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
2261 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
2262 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.18f );
2263 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.18f );
2264 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2265 }
2266 }
2267 }
2268 }
2269
2270 // WIRE ENDPOINTS
2271 // ========================================================================================================
2272
2273 SHADER_USE( shader_tile_colour );
2274 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2275 use_mesh( &world.shapes );
2276
2277 for( int y = 2; y < world.h-2; y ++ )
2278 {
2279 for( int x = 2; x < world.w-2; x ++ )
2280 {
2281 struct cell *cell = pcell((v2i){x,y});
2282
2283 if( cell->state & FLAG_CANAL )
2284 {
2285 if( cell->state & FLAG_IS_TRIGGER )
2286 {
2287 struct cell_description *desc = &cell_descriptions[ cell->config ];
2288
2289 int trigger_id = cell->links[0]?0:1;
2290
2291 int x2 = cell->links[trigger_id] % world.w;
2292 int y2 = (cell->links[trigger_id] - x2) / world.w;
2293
2294 v2f pts[2];
2295
2296 pts[0][0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
2297 pts[0][1] = (float)y2 + 0.25f;
2298
2299 pts[1][0] = x;
2300 pts[1][1] = y;
2301
2302 v2_add( desc->trigger_pos, pts[1], pts[1] );
2303
2304 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
2305 1, trigger_id? wire_right_colour: wire_left_colour );
2306
2307 for( int i = 0; i < 2; i ++ )
2308 {
2309 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
2310 pts[i][0],
2311 pts[i][1],
2312 0.08f
2313 );
2314 draw_mesh( filled_start, filled_count );
2315 }
2316 }
2317 }
2318 }
2319 }
2320
2321 // SUB SPLITTER DIRECTION
2322 // ========================================================================================================
2323
2324 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
2325
2326 for( int y = 2; y < world.h-2; y ++ )
2327 {
2328 for( int x = 2; x < world.w-2; x ++ )
2329 {
2330 struct cell *cell = pcell((v2i){x,y});
2331
2332 if( cell->state & FLAG_CANAL && cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
2333 {
2334 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), x, y, 1.0f );
2335 draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
2336 }
2337 }
2338 }
2339
2340 // I/O ARRAYS
2341 // ========================================================================================================
2342
2343 //glEnable(GL_BLEND);
2344
2345 for( int i = 0; i < arrlen( world.io ); i ++ )
2346 {
2347 struct cell_terminal *term = &world.io[ i ];
2348 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2349
2350 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2351
2352 for( int k = 0; k < term->run_count; k ++ )
2353 {
2354 float arr_base = is_input? 1.2f: -0.2f,
2355 run_offset = (is_input? 0.2f: -0.2f) * (float)k,
2356 y_position = is_input?
2357 (arr_base + (float)term->pos[1] + (float)(term->run_count-1)*0.2f) - run_offset:
2358 (float)term->pos[1] + arr_base + run_offset;
2359
2360 v4f bar_colour;
2361 int bar_draw = 0;
2362
2363 if( is_simulation_running() )
2364 {
2365 if( k == world.sim_run )
2366 {
2367 float a = fabsf(sinf( vg_time * 2.0f )) * 0.075f + 0.075f;
2368
2369 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, a }, bar_colour );
2370 }
2371 else
2372 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
2373
2374 bar_draw = 1;
2375 }
2376 else if( 1 || k & 0x1 )
2377 {
2378 if( k & 0x1 )
2379 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, 0.07f }, bar_colour );
2380 else
2381 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
2382
2383 bar_draw = 1;
2384 }
2385
2386 if( bar_draw )
2387 {
2388 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, bar_colour );
2389 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)term->pos[0], y_position - 0.1f, 1.0f );
2390 draw_mesh( 2, 2 );
2391 }
2392
2393 for( int j = 0; j < term->runs[k].condition_count; j ++ )
2394 {
2395 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
2396 (float)term->pos[0] + 0.2f + 0.2f * (float)j,
2397 y_position,
2398 0.1f
2399 );
2400
2401 if( is_input )
2402 {
2403 char cc = term->runs[k].conditions[j];
2404 if( cc != ' ' )
2405 {
2406 colour_code_v3( cc, dot_colour );
2407 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2408
2409 // Draw filled if tick not passed, draw empty if empty
2410 if( (world.sim_frame > j && world.sim_run >= k) || world.sim_run > k )
2411 draw_mesh( empty_start, empty_count );
2412 else
2413 draw_mesh( filled_start, filled_count );
2414 }
2415 }
2416 else
2417 {
2418
2419 if( term->runs[k].recv_count > j )
2420 {
2421 colour_code_v3( term->runs[k].recieved[j], dot_colour );
2422 v3_muls( dot_colour, 0.8f, dot_colour );
2423 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2424
2425 draw_mesh( filled_start, filled_count );
2426 }
2427
2428 colour_code_v3( term->runs[k].conditions[j], dot_colour );
2429 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2430
2431 draw_mesh( empty_start, empty_count );
2432 }
2433 }
2434 }
2435 }
2436
2437 glDisable(GL_BLEND);
2438
2439 // Draw score
2440 /*
2441 float const score_bright = 1.25f;
2442 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
2443 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f );
2444
2445 use_mesh( &world.numbers );
2446 draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
2447 */
2448 }
2449
2450 static ui_colourset flcol_list_a = {
2451 .main = 0xff877979,
2452 .hover = 0xffa09393,
2453 .active = 0xffbfb1b0
2454 };
2455 static ui_colourset flcol_list_b = {
2456 .main = 0xff7c6e6e,
2457 .hover = 0xffa09393,
2458 .active = 0xffbfb1b0
2459 };
2460
2461 static ui_colourset flcol_list_complete_a = {
2462 .main = 0xff62a064,
2463 .hover = 0xff8dc18f,
2464 .active = 0xffb2ddb3
2465 };
2466
2467 static ui_colourset flcol_list_complete_b = {
2468 .main = 0xff79b37b,
2469 .hover = 0xff8dc18f,
2470 .active = 0xffb2ddb3
2471 };
2472
2473 static ui_colourset flcol_list_locked = {
2474 .main = 0xff655959,
2475 .hover = 0xff655959,
2476 .active = 0xff655959
2477 };
2478
2479 static struct
2480 {
2481 SteamLeaderboard_t steam_leaderboard;
2482 int leaderboard_show;
2483
2484 struct leaderboard_player
2485 {
2486 // Internal
2487 u64_steamid id;
2488 i32 score;
2489 int is_local_player;
2490
2491 // To be displayed
2492 char score_text[ 16 ];
2493 char player_name[ 48 ];
2494 GLuint texture; // Not dynamic
2495 }
2496 leaderboard_players[10];
2497 int leaderboard_count;
2498
2499 struct
2500 {
2501 struct cmp_level *level;
2502
2503 i32 score;
2504 int is_waiting;
2505 }
2506 upload_request;
2507
2508 struct cmp_level *level_selected;
2509 }
2510 ui_data;
2511
2512 void vg_ui(void)
2513 {
2514 // UI memory
2515 static int pack_selection = 0;
2516 static struct pack_info
2517 {
2518 struct cmp_level *levels;
2519 u32 level_count;
2520 const char *name;
2521 }
2522 pack_infos[] =
2523 {
2524 {
2525 .levels = cmp_levels_tutorials,
2526 .level_count = vg_list_size(cmp_levels_tutorials),
2527 .name = "Training"
2528 },
2529 {
2530 .levels = cmp_levels_basic,
2531 .level_count = vg_list_size(cmp_levels_basic),
2532 .name = "Main"
2533 },
2534 {
2535 .levels = cmp_levels_grad,
2536 .level_count = vg_list_size(cmp_levels_tutorials),
2537 .name = "Expert"
2538 }
2539 };
2540
2541 // UI Code
2542 ui_global_ctx.cursor[0] = 0;
2543 ui_global_ctx.cursor[1] = 0;
2544 ui_global_ctx.cursor[2] = 256;
2545
2546 gui_fill_y();
2547
2548 ui_global_ctx.id_base = 4 << 16;
2549
2550 gui_new_node();
2551 {
2552 gui_capture_mouse( 9999 );
2553 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2554
2555 gui_text( "ASSIGNMENTS", 8, 0 );
2556
2557 ui_global_ctx.cursor[1] += 30;
2558 ui_global_ctx.cursor[3] = 25;
2559
2560 gui_new_node();
2561 {
2562 ui_rect_pad( ui_global_ctx.cursor, 2 );
2563 ui_global_ctx.cursor[2] = 84;
2564
2565 for( int i = 0; i < 3; i ++ )
2566 {
2567 int pack_is_unlocked = pack_infos[i].levels[0].unlocked;
2568
2569 if( i == pack_selection || !pack_is_unlocked )
2570 gui_override_colours( &flcol_list_locked );
2571
2572 if( gui_button( 2000 + i ) == k_button_click && pack_is_unlocked )
2573 pack_selection = i;
2574
2575 ui_global_ctx.cursor[1] += 2;
2576 gui_text( pack_is_unlocked? pack_infos[i].name: "???", 4, 0 );
2577 gui_end_right();
2578
2579 gui_reset_colours();
2580 }
2581 }
2582 gui_end_down();
2583
2584 ui_global_ctx.cursor[3] = 500;
2585
2586 // DRAW LEVEL SELECTION LIST
2587 {
2588 struct cmp_level *levels = pack_infos[ pack_selection ].levels;
2589 int count = pack_infos[ pack_selection ].level_count;
2590
2591 static struct ui_scrollbar sb = {
2592 .bar_height = 400
2593 };
2594
2595 ui_px view_height = ui_global_ctx.cursor[3];
2596 ui_px level_height = 50;
2597
2598 // Level scroll view
2599 gui_new_node();
2600 {
2601 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2602 gui_set_clip( ui_global_ctx.cursor );
2603
2604 ui_global_ctx.cursor[2] = 14;
2605 gui_align_right();
2606
2607 ui_px content_height = count*level_height;
2608 if( content_height > view_height )
2609 {
2610 ui_scrollbar( &ui_global_ctx, &sb, 1 );
2611 ui_global_ctx.cursor[1] -= ui_calculate_content_scroll( &sb, content_height );
2612 }
2613 else
2614 {
2615 gui_fill_rect( ui_global_ctx.cursor, 0xff807373 );
2616 }
2617
2618 ui_global_ctx.cursor[2] = 240;
2619 ui_global_ctx.cursor[3] = level_height;
2620 gui_align_left();
2621
2622 for( int i = 0; i < count; i ++ )
2623 {
2624 struct cmp_level *lvl_info = &levels[i];
2625
2626 if( lvl_info->unlocked )
2627 {
2628 if( lvl_info->completed_score != 0 )
2629 gui_override_colours( i&0x1? &flcol_list_complete_a: &flcol_list_complete_b );
2630 else
2631 gui_override_colours( i&0x1? &flcol_list_a: &flcol_list_b );
2632 }
2633 else
2634 gui_override_colours( &flcol_list_locked );
2635
2636 if( lvl_info->unlocked )
2637 {
2638 if( gui_button( 2 + i ) == k_button_click )
2639 {
2640 ui_data.level_selected = &levels[i];
2641 ui_data.leaderboard_show = 0;
2642
2643 if( pack_selection >= 1 )
2644 sw_find_leaderboard( ui_data.level_selected->map_name );
2645 }
2646
2647 ui_global_ctx.override_colour = 0xffffffff;
2648 gui_text( lvl_info->title, 6, 0 );
2649 ui_global_ctx.cursor[1] += 18;
2650 gui_text( lvl_info->completed_score>0? "passed": "incomplete", 4, 0 );
2651 }
2652 else
2653 {
2654 gui_button( 2 + i );
2655
2656 ui_global_ctx.override_colour = 0xff786f6f;
2657 gui_text( "???", 6, 0 );
2658 ui_global_ctx.cursor[1] += 18;
2659 gui_text( "locked", 4, 0 );
2660 }
2661
2662 gui_end_down();
2663 }
2664
2665 gui_reset_colours();
2666 gui_release_clip();
2667 }
2668 gui_end_down();
2669 }
2670 }
2671 gui_end_right();
2672
2673 // Selected level UI
2674 // ============================================================
2675
2676 if( ui_data.level_selected )
2677 {
2678 ui_global_ctx.cursor[0] += 16;
2679 ui_global_ctx.cursor[1] += 16;
2680 ui_global_ctx.cursor[2] = 512-40;
2681 ui_global_ctx.cursor[3] = 560-16;
2682
2683 gui_new_node();
2684 {
2685 gui_capture_mouse( 9999 );
2686
2687 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2688 ui_global_ctx.cursor[1] += 4;
2689 gui_text( ui_data.level_selected->title, 6, 0 );
2690
2691 ui_global_ctx.cursor[1] += 30;
2692 ui_rect_pad( ui_global_ctx.cursor, 8 );
2693 ui_global_ctx.cursor[3] = 300;
2694
2695 gui_new_node();
2696 {
2697 gui_fill_rect( ui_global_ctx.cursor, 0xff655959 );
2698 }
2699 gui_end_down();
2700
2701 ui_text_use_paragraph( &ui_global_ctx );
2702 ui_global_ctx.cursor[1] += 2;
2703
2704 gui_text( ui_data.level_selected->description, 5, 0 );
2705 ui_text_use_title( &ui_global_ctx );
2706
2707 // Buttons at the bottom
2708 ui_global_ctx.cursor[3] = 25;
2709 ui_global_ctx.cursor[2] = 80;
2710
2711 gui_align_bottom();
2712 ui_global_ctx.cursor[1] -= 8;
2713
2714 if( gui_button( 3000 ) == k_button_click )
2715 {
2716 ui_data.level_selected = NULL;
2717 }
2718 gui_text( "BACK", 6, k_text_alignment_center );
2719 gui_end();
2720
2721 gui_align_right();
2722 ui_global_ctx.cursor[2] = 170;
2723 ui_global_ctx.cursor[0] -= 8 + 170 + 2;
2724
2725 {
2726 gui_override_colours( &flcol_list_locked );
2727 if( gui_button( 3001 ) == k_button_click )
2728 vg_error( "UNIMPLEMENTED\n" );
2729
2730 ui_global_ctx.override_colour = 0xff888888;
2731
2732 gui_text( "RESTORE SOLUTION", 6, k_text_alignment_center );
2733 gui_end_right();
2734 ui_global_ctx.override_colour = 0xffffffff;
2735 }
2736
2737 ui_global_ctx.cursor[0] += 2;
2738 ui_global_ctx.cursor[2] = 80;
2739
2740 {
2741 gui_override_colours( &flcol_list_complete_a );
2742 if( gui_button( 3002 ) == k_button_click )
2743 {
2744 if( console_changelevel( 1, &ui_data.level_selected->map_name ) )
2745 {
2746 world.pCmpLevel = ui_data.level_selected;
2747
2748 ui_data.level_selected = NULL;
2749 ui_data.leaderboard_show = 0;
2750 }
2751 }
2752 gui_text( "PLAY", 6, k_text_alignment_center );
2753 gui_end();
2754 }
2755
2756 gui_reset_colours();
2757 }
2758 gui_end_right();
2759
2760 if( ui_data.leaderboard_show )
2761 {
2762 ui_global_ctx.cursor[0] += 16;
2763 ui_global_ctx.cursor[2] = 350;
2764 ui_global_ctx.cursor[3] = 25;
2765
2766 // If has results
2767 gui_new_node();
2768 {
2769 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2770 gui_text( "FRIEND LEADERBOARD", 6, 0 );
2771 }
2772 gui_end_down();
2773
2774 ui_global_ctx.cursor[1] += 2;
2775
2776 gui_new_node();
2777 {
2778 ui_global_ctx.cursor[3] = 32+8;
2779
2780 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
2781 {
2782 gui_new_node();
2783 {
2784 gui_fill_rect( ui_global_ctx.cursor, i&0x1? flcol_list_a.main: flcol_list_b.main );
2785
2786 ui_global_ctx.cursor[0] += 4;
2787 ui_global_ctx.cursor[1] += 4;
2788
2789 // 1,2,3 ...
2790 static const char *places[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
2791 gui_text( places[i], 7, 0 );
2792 ui_global_ctx.cursor[0] += 32;
2793
2794 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
2795
2796 // Players image
2797 ui_global_ctx.cursor[2] = 32;
2798 ui_global_ctx.cursor[3] = 32;
2799
2800 gui_new_node();
2801 {
2802 gui_push_image( ui_global_ctx.cursor, player->texture );
2803 }
2804 gui_end_right();
2805
2806 // Players name
2807 gui_text( player->player_name, 7, 0 );
2808
2809 ui_global_ctx.cursor[2] = 50;
2810 gui_align_right();
2811
2812 gui_text( player->score_text, 7, k_text_alignment_right );
2813 }
2814 gui_end_down();
2815
2816 ui_global_ctx.cursor[1] += 2;
2817 }
2818 }
2819 gui_end();
2820 }
2821 }
2822 }
2823
2824 void leaderboard_dispatch_score(void)
2825 {
2826 sw_upload_leaderboard_score(
2827 ui_data.upload_request.level->steam_leaderboard,
2828 k_ELeaderboardUploadScoreMethodKeepBest,
2829 ui_data.upload_request.score,
2830 NULL,
2831 0
2832 );
2833
2834 ui_data.upload_request.is_waiting = 0;
2835
2836 vg_success( "Dispatched leaderboard score\n" );
2837 }
2838
2839 void leaderboard_found( LeaderboardFindResult_t *pCallback )
2840 {
2841 if( !pCallback->m_bLeaderboardFound )
2842 {
2843 vg_error( "Leaderboard could not be found\n" );
2844 ui_data.steam_leaderboard = 0;
2845 }
2846 else
2847 {
2848 const char *recieved_name = sw_get_leaderboard_name( pCallback->m_hSteamLeaderboard );
2849
2850 // Update UI state and request entries if this callback found the current UI level
2851 if( ui_data.level_selected )
2852 {
2853 if( !strcmp( recieved_name, ui_data.level_selected->map_name ) )
2854 {
2855 sw_download_leaderboard_entries( pCallback->m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 0, 8 );
2856 ui_data.level_selected->steam_leaderboard = pCallback->m_hSteamLeaderboard;
2857 }
2858 }
2859
2860 // Dispatch the waiting request if there was one
2861 if( ui_data.upload_request.is_waiting )
2862 {
2863 if( !strcmp( recieved_name, ui_data.upload_request.level->map_name ) )
2864 {
2865 ui_data.upload_request.level->steam_leaderboard = pCallback->m_hSteamLeaderboard;
2866 leaderboard_dispatch_score();
2867 }
2868 }
2869 }
2870 }
2871
2872 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
2873 {
2874 // Update UI if this leaderboard matches what we currently have in view
2875 if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
2876 {
2877 vg_info( "Recieved %d entries\n", pCallback->m_cEntryCount );
2878 ui_data.leaderboard_count = VG_MIN( pCallback->m_cEntryCount, 8 );
2879
2880 u64_steamid local_player = sw_get_steamid();
2881
2882 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
2883 {
2884 LeaderboardEntry_t entry;
2885 sw_get_downloaded_entry( pCallback->m_hSteamLeaderboardEntries, i, &entry, NULL, 0 );
2886
2887 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
2888
2889 player->id = entry.m_steamIDUser.m_unAll64Bits;
2890 strncpy( player->player_name, sw_get_friend_persona_name( player->id ), vg_list_size( player->player_name )-1 );
2891 player->score = entry.m_nScore;
2892
2893 snprintf( player->score_text, vg_list_size(player->score_text), "%d", player->score );
2894 player->texture = sw_get_player_image( player->id );
2895
2896 if( player->texture == 0 )
2897 player->texture = tex_unkown.name;
2898
2899 player->is_local_player = local_player == player->id? 1: 0;
2900 }
2901
2902 if( ui_data.leaderboard_count )
2903 ui_data.leaderboard_show = 1;
2904 else
2905 ui_data.leaderboard_show = 0;
2906 }
2907 else vg_warn( "Downloaded leaderboard does not match requested!\n" );
2908 }
2909
2910 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
2911 {
2912 if( ui_data.upload_request.is_waiting )
2913 vg_warn( "You are uploading leaderboard entries too quickly!\n" );
2914
2915 ui_data.upload_request.level = cmp_level;
2916 ui_data.upload_request.score = score;
2917 ui_data.upload_request.is_waiting = 1;
2918
2919 // If leaderboard ID has been downloaded already then just immediately dispatch this
2920 if( cmp_level->steam_leaderboard )
2921 leaderboard_dispatch_score();
2922 else
2923 sw_find_leaderboard( cmp_level->map_name );
2924 }
2925
2926 // CONSOLE COMMANDS
2927 // ===========================================================================================================
2928
2929 static int console_credits( int argc, char const *argv[] )
2930 {
2931 vg_info( "Aknowledgements:\n" );
2932 vg_info( " GLFW zlib/libpng glfw.org\n" );
2933 vg_info( " miniaudio MIT0 miniaud.io\n" );
2934 vg_info( " QOI MIT phoboslab.org\n" );
2935 vg_info( " STB library MIT nothings.org\n" );
2936 return 0;
2937 }
2938
2939 static int console_save_map( int argc, char const *argv[] )
2940 {
2941 if( !world.initialzed )
2942 {
2943 vg_error( "Tried to save uninitialized map!\n" );
2944 return 0;
2945 }
2946
2947 char map_path[ 256 ];
2948
2949 strcpy( map_path, "sav/" );
2950 strcat( map_path, world.map_name );
2951 strcat( map_path, ".map" );
2952
2953 FILE *test_writer = fopen( map_path, "wb" );
2954 if( test_writer )
2955 {
2956 vg_info( "Saving map to '%s'\n", map_path );
2957 map_serialize( test_writer );
2958
2959 fclose( test_writer );
2960 return 1;
2961 }
2962 else
2963 {
2964 vg_error( "Unable to open stream for writing\n" );
2965 return 0;
2966 }
2967 }
2968
2969 static int console_load_map( int argc, char const *argv[] )
2970 {
2971 char map_path[ 256 ];
2972
2973 if( argc >= 1 )
2974 {
2975 // try from saves
2976 strcpy( map_path, "sav/" );
2977 strcat( map_path, argv[0] );
2978 strcat( map_path, ".map" );
2979
2980 char *text_source = vg_textasset_read( map_path );
2981
2982 if( !text_source )
2983 {
2984 strcpy( map_path, "maps/" );
2985 strcat( map_path, argv[0] );
2986 strcat( map_path, ".map" );
2987
2988 text_source = vg_textasset_read( map_path );
2989 }
2990
2991 if( text_source )
2992 {
2993 vg_info( "Loading map: '%s'\n", map_path );
2994 world.pCmpLevel = NULL;
2995
2996 if( !map_load( text_source, argv[0] ) )
2997 {
2998 free( text_source );
2999 return 0;
3000 }
3001
3002 free( text_source );
3003 return 1;
3004 }
3005 else
3006 {
3007 vg_error( "Missing maps '%s'\n", argv[0] );
3008 return 0;
3009 }
3010 }
3011 else
3012 {
3013 vg_error( "Missing argument <map_path>\n" );
3014 return 0;
3015 }
3016 }
3017
3018 static int console_changelevel( int argc, char const *argv[] )
3019 {
3020 if( argc >= 1 )
3021 {
3022 // Save current level
3023 console_save_map( 0, NULL );
3024 if( console_load_map( argc, argv ) )
3025 {
3026 simulation_stop();
3027 return 1;
3028 }
3029 }
3030 else
3031 {
3032 vg_error( "Missing argument <map_path>\n" );
3033 }
3034
3035 return 0;
3036 }
3037
3038 // START UP / SHUTDOWN
3039 // ===========================================================================================================
3040
3041 #define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
3042 X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY
3043
3044 void vg_start(void)
3045 {
3046 // Steamworks callbacks
3047 sw_leaderboard_found = &leaderboard_found;
3048 sw_leaderboard_downloaded = &leaderboard_downloaded;
3049
3050 vg_function_push( (struct vg_cmd){
3051 .name = "_map_write",
3052 .function = console_save_map
3053 });
3054
3055 vg_function_push( (struct vg_cmd){
3056 .name = "_map_load",
3057 .function = console_load_map
3058 });
3059
3060 vg_function_push( (struct vg_cmd){
3061 .name = "map",
3062 .function = console_changelevel
3063 });
3064
3065 vg_function_push( (struct vg_cmd){
3066 .name = "credits",
3067 .function = console_credits
3068 });
3069
3070 // Combined quad, long quad / empty circle / filled circle mesh
3071 {
3072 float combined_mesh[6*6 + 32*6*3] = {
3073 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
3074 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
3075
3076 0.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.2f,
3077 0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f,
3078
3079 TRANSFORM_TRI_2D( 0.15f,0.05f,0.4f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f ),
3080 TRANSFORM_TRI_2D( 0.15f,0.80f,0.4f, 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 1.0f )
3081 };
3082
3083 float *circle_mesh = combined_mesh + 6*6;
3084 int const res = 32;
3085
3086 for( int i = 0; i < res; i ++ )
3087 {
3088 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
3089 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
3090
3091 circle_mesh[ i*6+0 ] = 0.0f;
3092 circle_mesh[ i*6+1 ] = 0.0f;
3093
3094 v2_copy( v0, circle_mesh + 32*6 + i*12 );
3095 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
3096 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
3097
3098 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
3099 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
3100 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
3101
3102 v2_copy( v0, circle_mesh + i*6+4 );
3103 v2_copy( v1, circle_mesh + i*6+2 );
3104 v2_copy( v0, circle_mesh+i*6+4 );
3105 v2_copy( v1, circle_mesh+i*6+2 );
3106 }
3107
3108 init_mesh( &world.shapes, combined_mesh, vg_list_size( combined_mesh ) );
3109 }
3110
3111 // Numbers mesh
3112 {
3113 init_mesh( &world.numbers,
3114 MESH_NUMBERS_BUFFER,
3115 vg_list_size( MESH_NUMBERS_BUFFER )
3116 );
3117
3118 for( int i = 0; i < 10; i ++ )
3119 {
3120 vg_info( "offset: %u, length: %u\n", MESH_NUMBERS_OFFSETS[i][0], MESH_NUMBERS_OFFSETS[i][1] );
3121 }
3122 }
3123
3124 // Create wire mesh
3125 {
3126 int const num_segments = 64;
3127
3128 struct mesh_wire *mw = &world.wire;
3129
3130 v2f wire_points[ num_segments * 2 ];
3131 u16 wire_indices[ 6*(num_segments-1) ];
3132
3133 for( int i = 0; i < num_segments; i ++ )
3134 {
3135 float l = (float)i / (float)(num_segments-1);
3136
3137 v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
3138 v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
3139
3140 if( i < num_segments-1 )
3141 {
3142 wire_indices[ i*6+0 ] = i*2 + 0;
3143 wire_indices[ i*6+1 ] = i*2 + 1;
3144 wire_indices[ i*6+2 ] = i*2 + 3;
3145 wire_indices[ i*6+3 ] = i*2 + 0;
3146 wire_indices[ i*6+4 ] = i*2 + 3;
3147 wire_indices[ i*6+5 ] = i*2 + 2;
3148 }
3149 }
3150
3151 glGenVertexArrays( 1, &mw->vao );
3152 glGenBuffers( 1, &mw->vbo );
3153 glGenBuffers( 1, &mw->ebo );
3154 glBindVertexArray( mw->vao );
3155
3156 glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
3157
3158 glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
3159 glBindVertexArray( mw->vao );
3160
3161 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
3162 glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
3163
3164 // XY
3165 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
3166 glEnableVertexAttribArray( 0 );
3167
3168 VG_CHECK_GL();
3169
3170 mw->em = vg_list_size( wire_indices );
3171 }
3172
3173 // Create info data texture
3174 {
3175 glGenTextures( 1, &world.background_data );
3176 glBindTexture( GL_TEXTURE_2D, world.background_data );
3177 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
3178 vg_tex2d_nearest();
3179 }
3180
3181 // Create random smaples texture
3182 {
3183 u8 *data = malloc(512*512*2);
3184 for( int i = 0; i < 512*512*2; i ++ )
3185 data[ i ] = rand()/(RAND_MAX/255);
3186
3187 glGenTextures( 1, &world.random_samples );
3188 glBindTexture( GL_TEXTURE_2D, world.random_samples );
3189 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
3190 vg_tex2d_linear();
3191 vg_tex2d_repeat();
3192
3193 free( data );
3194 }
3195
3196 resource_load_main();
3197
3198 // Restore gamestate
3199 career_local_data_init();
3200 career_load();
3201 }
3202
3203 void vg_free(void)
3204 {
3205 sw_free_opengl();
3206 console_save_map( 0, NULL );
3207 career_serialize();
3208
3209 resource_free_main();
3210
3211 glDeleteTextures( 1, &world.background_data );
3212 glDeleteTextures( 1, &world.random_samples );
3213
3214 glDeleteVertexArrays( 1, &world.wire.vao );
3215 glDeleteBuffers( 1, &world.wire.vbo );
3216 glDeleteBuffers( 1, &world.wire.ebo );
3217
3218 free_mesh( &world.shapes );
3219 free_mesh( &world.numbers );
3220
3221 map_free();
3222 }
3223
3224 int main( int argc, char *argv[] )
3225 {
3226 vg_init( argc, argv, "Marble Computing" );
3227 return 0;
3228 }