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