fixed state resetting
[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 clear_animation_flags(void)
1064 {
1065 for( int i = 0; i < world.w*world.h; i ++ )
1066 world.data[ i ].state &= ~(FLAG_FLIP_FLOP|FLAG_FLIP_ROTATING);
1067 }
1068
1069 static void simulation_stop(void)
1070 {
1071 world.buttons[ k_world_button_sim ].pressed = 0;
1072 world.buttons[ k_world_button_pause ].pressed = 0;
1073
1074 world.num_fishes = 0;
1075 world.sim_frame = 0;
1076 world.sim_run = 0;
1077 world.frame_lerp = 0.0f;
1078
1079 io_reset();
1080
1081 sfx_system_fadeout( &audio_system_balls_rolling, 44100 );
1082
1083 clear_animation_flags();
1084
1085 vg_info( "Stopping simulation!\n" );
1086 }
1087
1088 static void simulation_start(void)
1089 {
1090 vg_success( "Starting simulation!\n" );
1091
1092 sfx_set_playrnd( &audio_rolls, &audio_system_balls_rolling, 0, 1 );
1093
1094 world.num_fishes = 0;
1095 world.sim_frame = 0;
1096 world.sim_run = 0;
1097
1098 world.sim_delta_speed = 2.5f;
1099 world.sim_delta_ref = vg_time;
1100 world.sim_internal_ref = 0.0f;
1101 world.sim_internal_time = 0.0f;
1102 world.pause_offset_target = 0.0f;
1103
1104 world.sim_target = 0;
1105
1106 clear_animation_flags();
1107
1108 io_reset();
1109 }
1110
1111 static int world_check_pos_ok( v2i co )
1112 {
1113 return (co[0] < 2 || co[0] >= world.w-2 || co[1] < 2 || co[1] >= world.h-2)? 0: 1;
1114 }
1115
1116 static int cell_interactive( v2i co )
1117 {
1118 // Bounds check
1119 if( !world_check_pos_ok( co ) )
1120 return 0;
1121
1122 // Flags check
1123 if( world.data[ world.w*co[1] + co[0] ].state & (FLAG_WALL|FLAG_INPUT|FLAG_OUTPUT) )
1124 return 0;
1125
1126 // List of 3x3 configurations that we do not allow
1127 static u32 invalid_src[][9] =
1128 {
1129 { 0,1,0,
1130 1,1,1,
1131 0,1,0
1132 },
1133 { 0,0,0,
1134 0,1,1,
1135 0,1,1
1136 },
1137 { 0,0,0,
1138 1,1,0,
1139 1,1,0
1140 },
1141 { 0,1,1,
1142 0,1,1,
1143 0,0,0
1144 },
1145 { 1,1,0,
1146 1,1,0,
1147 0,0,0
1148 },
1149 { 0,1,0,
1150 0,1,1,
1151 0,1,0
1152 },
1153 { 0,1,0,
1154 1,1,0,
1155 0,1,0
1156 }
1157 };
1158
1159 // Statically compile invalid configurations into bitmasks
1160 static u32 invalid[ vg_list_size(invalid_src) ];
1161
1162 for( int i = 0; i < vg_list_size(invalid_src); i ++ )
1163 {
1164 u32 comped = 0x00;
1165
1166 for( int j = 0; j < 3; j ++ )
1167 for( int k = 0; k < 3; k ++ )
1168 comped |= invalid_src[i][ j*3+k ] << ((j*5)+k);
1169
1170 invalid[i] = comped;
1171 }
1172
1173 // Extract 5x5 grid surrounding tile
1174 u32 blob = 0x1000;
1175 for( int y = co[1]-2; y < co[1]+3; y ++ )
1176 for( int x = co[0]-2; x < co[0]+3; x ++ )
1177 {
1178 struct cell *cell = pcell((v2i){x,y});
1179
1180 if( cell && (cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT)) )
1181 blob |= 0x1 << ((y-(co[1]-2))*5 + x-(co[0]-2));
1182 }
1183
1184 // Run filter over center 3x3 grid to check for invalid configurations
1185 int kernel[] = { 0, 1, 2, 5, 6, 7, 10, 11, 12 };
1186 for( int i = 0; i < vg_list_size(kernel); i ++ )
1187 {
1188 if( blob & (0x1 << (6+kernel[i])) )
1189 {
1190 u32 window = blob >> kernel[i];
1191
1192 for( int j = 0; j < vg_list_size(invalid); j ++ )
1193 if((window & invalid[j]) == invalid[j])
1194 return 0;
1195 }
1196 }
1197
1198 return 1;
1199 }
1200
1201 void vg_update(void)
1202 {
1203 // Camera
1204 // ========================================================================================================
1205
1206 float r1 = (float)vg_window_y / (float)vg_window_x,
1207 r2 = (float)world.h / (float)world.w,
1208 size;
1209
1210 size = ( r2 < r1? (float)world.w * 0.5f: ((float)world.h * 0.5f) / r1 ) + 2.5f;
1211 m3x3_projection( m_projection, -size, size, -size*r1, size*r1 );
1212
1213 v3f origin;
1214 origin[0] = floorf( -0.5f * world.w );
1215 origin[1] = floorf( -0.5f * world.h );
1216 origin[2] = 0.0f;
1217
1218 m3x3_identity( m_view );
1219 m3x3_translate( m_view, origin );
1220 m3x3_mul( m_projection, m_view, vg_pv );
1221 vg_projection_update();
1222
1223 // Mouse input
1224 // ========================================================================================================
1225 v2_copy( vg_mouse_ws, world.tile_pos );
1226
1227 world.tile_x = floorf( world.tile_pos[0] );
1228 world.tile_y = floorf( world.tile_pos[1] );
1229
1230 // Tilemap
1231 // ========================================================================================================
1232 if( !is_simulation_running() && !gui_want_mouse() )
1233 {
1234 v2_copy( vg_mouse_ws, world.drag_to_co );
1235
1236 if( cell_interactive( (v2i){ world.tile_x, world.tile_y } ))
1237 {
1238 world.selected = world.tile_y * world.w + world.tile_x;
1239
1240 static u32 modify_state = 0;
1241
1242 struct cell *cell_ptr = &world.data[world.selected];
1243
1244 if( vg_get_button_down("primary") )
1245 modify_state = (cell_ptr->state & FLAG_CANAL) ^ FLAG_CANAL;
1246
1247 if( vg_get_button("primary") && ((cell_ptr->state & FLAG_CANAL) != modify_state) )
1248 {
1249 cell_ptr->state &= ~FLAG_CANAL;
1250 cell_ptr->state |= modify_state;
1251
1252 if( cell_ptr->state & FLAG_CANAL )
1253 {
1254 cell_ptr->links[0] = 0;
1255 cell_ptr->links[1] = 0;
1256
1257 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 3, 6 );
1258 world.score ++;
1259 }
1260 else
1261 {
1262 sfx_set_playrnd( &audio_tile_mod, &audio_system_sfx, 0, 3 );
1263 world.score --;
1264 }
1265
1266 map_reclassify( (v2i){ world.tile_x -2, world.tile_y -2 },
1267 (v2i){ world.tile_x +2, world.tile_y +2 }, 1 );
1268 }
1269
1270 if( vg_get_button_down("secondary") && (cell_ptr->state & FLAG_CANAL) && !(cell_ptr->config == k_cell_type_split) )
1271 {
1272 world.id_drag_from = world.selected;
1273
1274 struct cell_description *desc = &cell_descriptions[ world.data[world.id_drag_from].config ];
1275 v2_add( desc->trigger_pos, (v2f){ world.tile_x, world.tile_y }, world.drag_from_co );
1276 }
1277
1278 float local_x = vg_mouse_ws[0] - (float)world.tile_x;
1279
1280 if( vg_get_button_up("secondary") && world.id_drag_from == world.selected )
1281 {
1282 u32 link_id = local_x > 0.5f? 1: 0;
1283
1284 // break existing connection off
1285 if( cell_ptr->links[ link_id ] )
1286 {
1287 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1288
1289 if( !current_connection->links[ link_id ^ 0x1 ] )
1290 current_connection->state &= ~FLAG_TARGETED;
1291
1292 current_connection->links[ link_id ] = 0;
1293 cell_ptr->links[ link_id ] = 0;
1294 }
1295
1296 cell_ptr->state &= ~FLAG_IS_TRIGGER;
1297 world.id_drag_from = 0;
1298 }
1299
1300 if( world.id_drag_from && (cell_ptr->state & FLAG_CANAL) && (cell_ptr->config == k_cell_type_split) )
1301 {
1302 world.drag_to_co[0] = (float)world.tile_x + (local_x > 0.5f? 0.75f: 0.25f);
1303 world.drag_to_co[1] = (float)world.tile_y + 0.25f;
1304
1305 if( vg_get_button_up("secondary") )
1306 {
1307 struct cell *drag_ptr = &world.data[world.id_drag_from];
1308 u32 link_id = local_x > 0.5f? 1: 0;
1309
1310 // Cleanup existing connections
1311 if( cell_ptr->links[ link_id ] )
1312 {
1313 vg_warn( "Destroying existing connection on link %u (%hu)\n", link_id, cell_ptr->links[ link_id ] );
1314
1315 struct cell *current_connection = &world.data[ cell_ptr->links[ link_id ]];
1316 current_connection->state &= ~FLAG_IS_TRIGGER;
1317 current_connection->links[ link_id ] = 0;
1318 }
1319
1320 if( drag_ptr->links[ link_id ^ 0x1 ] )
1321 {
1322 vg_warn( "Destroying alternate link %u (%hu)\n", link_id ^ 0x1, drag_ptr->links[ link_id ^ 0x1 ] );
1323
1324 struct cell *current_connection = &world.data[ drag_ptr->links[ link_id ^ 0x1 ]];
1325 if( !current_connection->links[ link_id ] )
1326 current_connection->state &= ~FLAG_TARGETED;
1327
1328 current_connection->links[ link_id ^ 0x1 ] = 0;
1329 drag_ptr->links[ link_id ^ 0x1 ] = 0;
1330 }
1331
1332 // Create the new connection
1333 vg_success( "Creating connection on link %u (%hu)\n", link_id, world.id_drag_from );
1334
1335 cell_ptr->links[ link_id ] = world.id_drag_from;
1336 drag_ptr->links[ link_id ] = world.selected;
1337
1338 cell_ptr->state |= FLAG_TARGETED;
1339 drag_ptr->state |= FLAG_IS_TRIGGER;
1340 world.id_drag_from = 0;
1341 }
1342 }
1343 }
1344 else
1345 {
1346 world.selected = -1;
1347 }
1348
1349 if( !(vg_get_button("secondary") && world.id_drag_from) )
1350 world.id_drag_from = 0;
1351 }
1352 else
1353 {
1354 world.selected = -1;
1355 world.id_drag_from = 0;
1356 }
1357
1358 // Marble state updates
1359 // ========================================================================================================
1360 if( is_simulation_running() )
1361 {
1362 float old_time = world.sim_internal_time;
1363
1364 if( !world.buttons[ k_world_button_pause ].pressed )
1365 world.sim_internal_time = world.sim_internal_ref + (vg_time-world.sim_delta_ref) * world.sim_delta_speed;
1366 else
1367 world.sim_internal_time = vg_lerpf( world.sim_internal_time, world.sim_internal_ref + world.pause_offset_target, vg_time_delta*15.0f );
1368 world.sim_internal_delta = world.sim_internal_time-old_time;
1369
1370 world.sim_target = (int)floorf(world.sim_internal_time);
1371
1372 while( world.sim_frame < world.sim_target )
1373 {
1374 sfx_set_playrnd( &audio_random, &audio_system_balls_switching, 0, 8 );
1375
1376 // Update splitter deltas
1377 for( int i = 0; i < world.h*world.w; i ++ )
1378 {
1379 struct cell *cell = &world.data[i];
1380 if( cell->config == k_cell_type_split )
1381 {
1382 cell->state &= ~FLAG_FLIP_ROTATING;
1383 }
1384 if( cell->state & FLAG_IS_TRIGGER )
1385 cell->state &= ~FLAG_TRIGGERED;
1386 }
1387
1388 int alive_count = 0;
1389
1390 // Update fish positions
1391 for( int i = 0; i < world.num_fishes; i ++ )
1392 {
1393 struct fish *fish = &world.fishes[i];
1394
1395 if( fish->state == k_fish_state_soon_dead )
1396 fish->state = k_fish_state_dead;
1397
1398 if( fish->state == k_fish_state_soon_alive )
1399 fish->state = k_fish_state_alive;
1400
1401 if( fish->state < k_fish_state_alive )
1402 continue;
1403
1404 struct cell *cell_current = pcell( fish->pos );
1405
1406 if( fish->state == k_fish_state_alive )
1407 {
1408 // Apply to output
1409 if( cell_current->state & FLAG_OUTPUT )
1410 {
1411 for( int j = 0; j < arrlen( world.io ); j ++ )
1412 {
1413 struct cell_terminal *term = &world.io[j];
1414
1415 if( v2i_eq( term->pos, fish->pos ) )
1416 {
1417 struct terminal_run *run = &term->runs[ world.sim_run ];
1418 if( run->recv_count < vg_list_size( run->recieved ) )
1419 run->recieved[ run->recv_count ++ ] = fish->payload;
1420
1421 break;
1422 }
1423 }
1424
1425 fish->state = k_fish_state_dead;
1426 fish->death_time = -1000.0f;
1427 continue;
1428 }
1429
1430
1431 if( cell_current->config == k_cell_type_merge )
1432 {
1433 // Can only move up
1434 fish->dir[0] = 0;
1435 fish->dir[1] = -1;
1436 fish->flow_reversed = 0;
1437 }
1438 else
1439 {
1440 if( cell_current->config == k_cell_type_split )
1441 {
1442 // Flip flop L/R
1443 fish->dir[0] = cell_current->state&FLAG_FLIP_FLOP?1:-1;
1444 fish->dir[1] = 0;
1445
1446 if( !(cell_current->state & FLAG_TARGETED) )
1447 cell_current->state ^= FLAG_FLIP_FLOP;
1448 }
1449 else
1450 {
1451 // Apply cell out-flow
1452 struct cell_description *desc = &cell_descriptions[ cell_current->config ];
1453
1454 v2i_copy( fish->flow_reversed? desc->start: desc->end, fish->dir );
1455 }
1456
1457 v2i pos_next;
1458 v2i_add( fish->pos, fish->dir, pos_next );
1459
1460 struct cell *cell_next = pcell( pos_next );
1461
1462 if( cell_next->state & (FLAG_CANAL|FLAG_OUTPUT) )
1463 {
1464 struct cell_description *desc = &cell_descriptions[ cell_next->config ];
1465
1466 if( cell_next->config == k_cell_type_merge )
1467 {
1468 if( fish->dir[0] == 0 )
1469 {
1470 fish->state = k_fish_state_dead;
1471 fish->death_time = world.sim_internal_time;
1472 }
1473 else
1474 fish->flow_reversed = 0;
1475 }
1476 else
1477 {
1478 if( cell_next->config == k_cell_type_split )
1479 {
1480 if( fish->dir[0] == 0 )
1481 {
1482 sfx_set_playrnd( &audio_splitter, &audio_system_balls_important, 0, 1 );
1483 cell_next->state |= FLAG_FLIP_ROTATING;
1484
1485 fish->flow_reversed = 0;
1486 }
1487 else
1488 {
1489 fish->state = k_fish_state_dead;
1490 fish->death_time = world.sim_internal_time;
1491 }
1492 }
1493 else
1494 fish->flow_reversed = ( fish->dir[0] != -desc->start[0] ||
1495 fish->dir[1] != -desc->start[1] )? 1: 0;
1496 }
1497 }
1498 else
1499 {
1500 if( world_check_pos_ok( fish->pos ) )
1501 fish->state = k_fish_state_bg;
1502 else
1503 {
1504 fish->state = k_fish_state_dead;
1505 fish->death_time = world.sim_internal_time;
1506 }
1507 }
1508 }
1509
1510 //v2i_add( fish->pos, fish->dir, fish->pos );
1511 }
1512 else if( fish->state == k_fish_state_bg )
1513 {
1514 v2i_add( fish->pos, fish->dir, fish->pos );
1515
1516 if( !world_check_pos_ok( fish->pos ) )
1517 {
1518 fish->state = k_fish_state_dead;
1519 fish->death_time = -1000.0f;
1520 }
1521 else
1522 {
1523 struct cell *cell_entry = pcell( fish->pos );
1524
1525 if( cell_entry->state & FLAG_CANAL )
1526 {
1527 if( cell_entry->config == k_cell_type_con_r || cell_entry->config == k_cell_type_con_u
1528 || cell_entry->config == k_cell_type_con_l || cell_entry->config == k_cell_type_con_d )
1529 {
1530 sw_set_achievement( "CAN_DO_THAT" );
1531
1532 fish->state = k_fish_state_soon_alive;
1533
1534 fish->dir[0] = 0;
1535 fish->dir[1] = 0;
1536 fish->flow_reversed = 1;
1537
1538 switch( cell_entry->config )
1539 {
1540 case k_cell_type_con_r: fish->dir[0] = 1; break;
1541 case k_cell_type_con_l: fish->dir[0] = -1; break;
1542 case k_cell_type_con_u: fish->dir[1] = 1; break;
1543 case k_cell_type_con_d: fish->dir[1] = -1; break;
1544 }
1545 }
1546 }
1547 }
1548 }
1549 else { vg_error( "fish behaviour unimplemented for behaviour type (%d)\n" ); }
1550
1551 if( fish->state >= k_fish_state_alive )
1552 alive_count ++;
1553 }
1554
1555 // Second pass (triggers)
1556 for( int i = 0; i < world.num_fishes; i ++ )
1557 {
1558 struct fish *fish = &world.fishes[i];
1559
1560 if( fish->state == k_fish_state_alive )
1561 {
1562 v2i_add( fish->pos, fish->dir, fish->pos );
1563 struct cell *cell_current = pcell( fish->pos );
1564
1565 if( cell_current->state & FLAG_IS_TRIGGER )
1566 {
1567 int trigger_id = cell_current->links[0]?0:1;
1568
1569 struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
1570
1571 cell_current->state |= FLAG_TRIGGERED;
1572
1573 if( trigger_id )
1574 target_peice->state |= FLAG_FLIP_FLOP;
1575 else
1576 target_peice->state &= ~FLAG_FLIP_FLOP;
1577 }
1578 }
1579 }
1580
1581 // Third pass (collisions)
1582 struct fish *fi, *fj;
1583
1584 for( int i = 0; i < world.num_fishes; i ++ )
1585 {
1586 fi = &world.fishes[i];
1587
1588 if( fi->state == k_fish_state_alive )
1589 {
1590 int continue_again = 0;
1591
1592 for( int j = i+1; j < world.num_fishes; j ++ )
1593 {
1594 fj = &world.fishes[j];
1595
1596 if( (fj->state == k_fish_state_alive) )
1597 {
1598 v2i fi_prev;
1599 v2i fj_prev;
1600
1601 v2i_sub( fi->pos, fi->dir, fi_prev );
1602 v2i_sub( fj->pos, fj->dir, fj_prev );
1603
1604 int
1605 collide_next_frame = (
1606 (fi->pos[0] == fj->pos[0]) &&
1607 (fi->pos[1] == fj->pos[1]))? 1: 0,
1608 collide_this_frame = (
1609 (fi_prev[0] == fj->pos[0]) &&
1610 (fi_prev[1] == fj->pos[1]) &&
1611 (fj_prev[0] == fi->pos[0]) &&
1612 (fj_prev[1] == fi->pos[1])
1613 )? 1: 0;
1614
1615 if( collide_next_frame || collide_this_frame )
1616 {
1617 sw_set_achievement( "BANG" );
1618
1619 // Shatter death (+0.5s)
1620 float death_time = world.sim_internal_time + ( collide_this_frame? 0.0f: 0.5f );
1621
1622 fi->state = k_fish_state_soon_dead;
1623 fj->state = k_fish_state_soon_dead;
1624 fi->death_time = death_time;
1625 fj->death_time = death_time;
1626
1627 continue_again = 1;
1628 break;
1629 }
1630 }
1631 }
1632 if( continue_again )
1633 continue;
1634 }
1635 }
1636
1637 // Spawn fishes
1638 for( int i = 0; i < arrlen( world.io ); i ++ )
1639 {
1640 struct cell_terminal *term = &world.io[ i ];
1641 int is_input = pcell(term->pos)->state & FLAG_INPUT;
1642
1643 if( is_input )
1644 {
1645 if( world.sim_frame < term->runs[ world.sim_run ].condition_count )
1646 {
1647 char emit = term->runs[ world.sim_run ].conditions[ world.sim_frame ];
1648 if( emit == ' ' )
1649 continue;
1650
1651 struct fish *fish = &world.fishes[ world.num_fishes ];
1652 v2i_copy( term->pos, fish->pos );
1653
1654 fish->state = k_fish_state_alive;
1655 fish->payload = emit;
1656
1657 struct cell *cell_ptr = pcell( fish->pos );
1658
1659 if( cell_ptr->config != k_cell_type_stub )
1660 {
1661 struct cell_description *desc = &cell_descriptions[ cell_ptr->config ];
1662
1663 v2i_copy( desc->start, fish->dir );
1664 fish->flow_reversed = 1;
1665
1666 world.num_fishes ++;
1667 alive_count ++;
1668 }
1669 }
1670 }
1671 }
1672
1673 if( alive_count == 0 )
1674 {
1675 world.completed = 1;
1676
1677 for( int i = 0; i < arrlen( world.io ); i ++ )
1678 {
1679 struct cell_terminal *term = &world.io[ i ];
1680 int is_input = pcell(term->pos)->state & FLAG_INPUT;
1681
1682 if( !is_input )
1683 {
1684 struct terminal_run *run = &term->runs[ world.sim_run ];
1685
1686 if( run->recv_count == run->condition_count )
1687 {
1688 for( int j = 0; j < run->condition_count; j ++ )
1689 {
1690 if( run->recieved[j] != run->conditions[j] )
1691 {
1692 world.completed = 0;
1693 break;
1694 }
1695 }
1696 }
1697 else
1698 {
1699 world.completed = 0;
1700 break;
1701 }
1702 }
1703 }
1704
1705 if( world.completed )
1706 {
1707 if( world.sim_run < world.max_runs-1 )
1708 {
1709 vg_success( "Run passed, starting next\n" );
1710 world.sim_run ++;
1711 world.sim_frame = 0;
1712 world.sim_target = 0;
1713 world.num_fishes = 0;
1714
1715 // Reset timing reference points
1716 world.sim_delta_ref = vg_time;
1717 world.sim_internal_ref = 0.0f;
1718
1719 if( world.buttons[ k_world_button_pause ].pressed )
1720 world.pause_offset_target = 0.5f;
1721 else
1722 world.pause_offset_target = 0.0f;
1723
1724 world.sim_internal_time = 0.0f;
1725
1726 for( int i = 0; i < world.w*world.h; i ++ )
1727 world.data[ i ].state &= ~FLAG_FLIP_FLOP;
1728
1729 continue;
1730 }
1731 else
1732 {
1733 vg_success( "Level passed!\n" );
1734
1735 u32 score = 0;
1736 for( int i = 0; i < world.w*world.h; i ++ )
1737 if( world.data[ i ].state & FLAG_CANAL )
1738 score ++;
1739
1740 world.score = score;
1741 world.time = world.sim_frame;
1742
1743 // Copy into career data
1744 if( world.pCmpLevel )
1745 {
1746 career_pass_level( world.pCmpLevel, world.score, 1 );
1747 }
1748 }
1749 }
1750 else
1751 {
1752 if( world.sim_run > 0 )
1753 sw_set_achievement( "GOOD_ENOUGH" );
1754
1755 vg_error( "Level failed :(\n" );
1756 }
1757
1758 simulation_stop();
1759 break;
1760 }
1761
1762 world.sim_frame ++;
1763 }
1764
1765 // Position update
1766 // =====================================================================================================
1767
1768 world.frame_lerp = world.sim_internal_time - floorf( world.sim_internal_time );
1769
1770 for( int i = 0; i < world.num_fishes; i ++ )
1771 {
1772 struct fish *fish = &world.fishes[i];
1773
1774 if( fish->state == k_fish_state_dead )
1775 continue;
1776
1777 if( fish->state == k_fish_state_soon_dead && (world.sim_internal_time > fish->death_time) )
1778 continue; // Todo: particle thing?
1779
1780 struct cell *cell = pcell(fish->pos);
1781 struct cell_description *desc = &cell_descriptions[ cell->config ];
1782
1783 v2f const *curve;
1784
1785 float t = world.frame_lerp;
1786 if( fish->flow_reversed && !desc->is_linear )
1787 t = 1.0f-t;
1788
1789 v2_copy( fish->physics_co, fish->physics_v );
1790
1791 switch( cell->config )
1792 {
1793 case k_cell_type_merge:
1794 if( fish->dir[0] == 1 )
1795 curve = curve_12;
1796 else
1797 curve = curve_9;
1798 break;
1799 case k_cell_type_con_r: curve = curve_1; break;
1800 case k_cell_type_con_l: curve = curve_4; break;
1801 case k_cell_type_con_u: curve = curve_2; break;
1802 case k_cell_type_con_d: curve = curve_8; break;
1803 case 3: curve = curve_3; break;
1804 case 6: curve = curve_6; break;
1805 case 9: curve = curve_9; break;
1806 case 12: curve = curve_12; break;
1807 case 7:
1808 if( t > curve_7_linear_section )
1809 {
1810 t -= curve_7_linear_section;
1811 t *= (1.0f/(1.0f-curve_7_linear_section));
1812
1813 curve = cell->state & FLAG_FLIP_FLOP? curve_7: curve_7_1;
1814 }
1815 else curve = NULL;
1816 break;
1817 default: curve = NULL; break;
1818 }
1819
1820 if( curve )
1821 {
1822 float t2 = t * t;
1823 float t3 = t * t * t;
1824
1825 float cA = 3.0f*t2 - 3.0f*t3;
1826 float cB = 3.0f*t3 - 6.0f*t2 + 3.0f*t;
1827 float cC = 3.0f*t2 - t3 - 3.0f*t + 1.0f;
1828
1829 fish->physics_co[0] = t3*curve[3][0] + cA*curve[2][0] + cB*curve[1][0] + cC*curve[0][0];
1830 fish->physics_co[1] = t3*curve[3][1] + cA*curve[2][1] + cB*curve[1][1] + cC*curve[0][1];
1831 fish->physics_co[0] += (float)fish->pos[0];
1832 fish->physics_co[1] += (float)fish->pos[1];
1833 }
1834 else
1835 {
1836 v2f origin;
1837 origin[0] = (float)fish->pos[0] + (float)fish->dir[0]*-0.5f + 0.5f;
1838 origin[1] = (float)fish->pos[1] + (float)fish->dir[1]*-0.5f + 0.5f;
1839
1840 fish->physics_co[0] = origin[0] + (float)fish->dir[0]*t;
1841 fish->physics_co[1] = origin[1] + (float)fish->dir[1]*t;
1842 }
1843
1844 v2_sub( fish->physics_co, fish->physics_v, fish->physics_v );
1845 v2_divs( fish->physics_v, world.sim_internal_delta, fish->physics_v );
1846 }
1847 }
1848 }
1849
1850 void leaderboard_found( LeaderboardFindResult_t *pCallback );
1851 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback );
1852
1853 static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
1854 {
1855 v2i full_start = { 0,0 };
1856 v2i full_end = { world.w, world.h };
1857
1858 if( !start || !end )
1859 {
1860 start = full_start;
1861 end = full_end;
1862 }
1863
1864 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1865
1866 for( int y = start[1]; y < end[1]; y ++ )
1867 {
1868 for( int x = start[0]; x < end[0]; x ++ )
1869 {
1870 struct cell *cell = pcell((v2i){x,y});
1871 int selected = world.selected == y*world.w + x;
1872
1873 int tile_offsets[][2] =
1874 {
1875 {2, 0}, {0, 3}, {0, 2}, {2, 2},
1876 {1, 0}, {2, 3}, {3, 2}, {1, 3},
1877 {3, 1}, {0, 1}, {1, 2}, {2, 1},
1878 {1, 1}, {3, 3}, {2, 1}, {2, 1}
1879 };
1880
1881 int uv[2] = { 3, 0 };
1882
1883 if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT) )
1884 {
1885 uv[0] = tile_offsets[ cell->config ][0];
1886 uv[1] = tile_offsets[ cell->config ][1];
1887 } else continue;
1888
1889 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] );
1890 if( selected )
1891 {
1892 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
1893 draw_mesh( 0, 2 );
1894 glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
1895 }
1896 else
1897 draw_mesh( 0, 2 );
1898 }
1899 }
1900 }
1901
1902 /*
1903 static void draw_numbers( v3f coord, int number )
1904 {
1905 v3f pos;
1906 v3_copy( coord, pos );
1907 int digits[8]; int i = 0;
1908
1909 while( number > 0 && i < 8 )
1910 {
1911 digits[i ++] = number % 10;
1912 number = number / 10;
1913 }
1914
1915 for( int j = 0; j < i; j ++ )
1916 {
1917 glUniform3fv( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 1, pos );
1918 draw_mesh( MESH_NUMBERS_OFFSETS[digits[i-j-1]][0], MESH_NUMBERS_OFFSETS[digits[i-j-1]][1] );
1919 pos[0] += pos[2] * 0.75f;
1920 }
1921 }*/
1922
1923 static void wbutton_run( enum e_world_button btn_name )
1924 {
1925 static v3f button_colours[] = {
1926 {0.204f, 0.345f, 0.553f},
1927 {0.204f, 0.345f, 0.553f},
1928 {0.741f, 0.513f, 0.078f},
1929 {1.0f, 0.0f, 0.0f}
1930 };
1931
1932 struct cell_button *btn = &world.buttons[btn_name];
1933
1934 // Interaction
1935
1936 int is_hovering = (world.tile_x == world.w-1 && world.tile_y == world.h-btn_name-2)?1:0;
1937 if( vg_get_button_up( "primary" ) && is_hovering )
1938 {
1939 // Click event
1940 if( btn_name == k_world_button_sim )
1941 {
1942 if( world.buttons[ k_world_button_pause ].pressed )
1943 {
1944 if( !btn->pressed )
1945 {
1946 btn->pressed = 1;
1947 simulation_start();
1948
1949 world.pause_offset_target = 0.5f;
1950 }
1951 else
1952 world.pause_offset_target += 1.0f;
1953 }
1954 else
1955 {
1956 btn->pressed ^= 0x1;
1957
1958 if( btn->pressed )
1959 simulation_start();
1960 else
1961 simulation_stop();
1962 }
1963 }
1964 else if( btn_name == k_world_button_pause )
1965 {
1966 btn->pressed ^= 0x1;
1967
1968 world.sim_internal_ref = world.sim_internal_time;
1969 world.sim_delta_ref = vg_time;
1970
1971 if( btn->pressed )
1972 {
1973 float time_frac = world.sim_internal_time-floorf(world.sim_internal_time);
1974 world.pause_offset_target = 0.5f - time_frac;
1975 }
1976 else
1977 world.pause_offset_target = 0.0f;
1978 }
1979 else
1980 {
1981 btn->pressed ^= 0x1;
1982 }
1983
1984 sfx_set_play( &audio_clicks, &audio_system_ui, btn->pressed?1:0 );
1985 }
1986
1987 // Drawing
1988 if( btn->pressed )
1989 {
1990 if( is_hovering )
1991 {
1992 btn->light_target = 0.9f;
1993 }
1994 else
1995 {
1996 if( btn_name == k_world_button_sim && world.buttons[ k_world_button_pause ].pressed )
1997 btn->light_target = fabsf(sinf( vg_time * 2.0f )) * 0.3f + 0.3f;
1998 else
1999 btn->light_target = 0.8f;
2000 }
2001 }
2002 else
2003 {
2004 btn->light_target = is_hovering? 0.2f: 0.0f;
2005 }
2006
2007 if( vg_get_button( "primary" ) && is_hovering )
2008 btn->light_target = 1.0f;
2009
2010 btn->light = vg_lerpf( btn->light, btn->light_target, vg_time_delta*26.0f );
2011
2012 // Draw
2013
2014 v4f final_colour;
2015 v3_copy( button_colours[ btn_name ], final_colour );
2016 final_colour[3] = btn->light;
2017
2018 glUniform4f( SHADER_UNIFORM( shader_buttons, "uOffset" ),
2019 world.w-1,
2020 world.h-btn_name-2,
2021 (float)btn_name,
2022 3.0f
2023 );
2024 glUniform4fv( SHADER_UNIFORM( shader_buttons, "uColour" ), 1, final_colour );
2025
2026 draw_mesh( 0, 2 );
2027 }
2028
2029 void vg_render(void)
2030 {
2031 glViewport( 0,0, vg_window_x, vg_window_y );
2032
2033 glDisable( GL_DEPTH_TEST );
2034 glClearColor( 0.369768f, 0.3654f, 0.42f, 1.0f );
2035 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
2036
2037 v4f const colour_default = {1.0f, 1.0f, 1.0f, 1.0f};
2038 v4f const colour_selected = {0.90f, 0.92f, 1.0f, 1.0f};
2039
2040 int const circle_base = 6;
2041 int const filled_start = circle_base+0;
2042 int const filled_count = circle_base+32;
2043 int const empty_start = circle_base+32;
2044 int const empty_count = circle_base+32*2;
2045
2046 // BACKGROUND
2047 // ========================================================================================================
2048 use_mesh( &world.shapes );
2049
2050 SHADER_USE( shader_background );
2051 glUniformMatrix3fv( SHADER_UNIFORM( shader_background, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2052
2053 glActiveTexture( GL_TEXTURE0 );
2054 glBindTexture( GL_TEXTURE_2D, world.background_data );
2055 glUniform1i( SHADER_UNIFORM( shader_background, "uTexMain" ), 0 );
2056
2057 glUniform3f( SHADER_UNIFORM( shader_background, "uOffset" ), -16, -16, 64 );
2058 glUniform1f( SHADER_UNIFORM( shader_background, "uVariance" ), 0.02f );
2059
2060 glActiveTexture( GL_TEXTURE1 );
2061 glBindTexture( GL_TEXTURE_2D, world.random_samples );
2062 glUniform1i( SHADER_UNIFORM( shader_background, "uSamplerNoise" ), 1 );
2063
2064 draw_mesh( 0, 2 );
2065
2066 // TILESET BACKGROUND LAYER
2067 // ========================================================================================================
2068 use_mesh( &world.shapes );
2069 SHADER_USE( shader_tile_main );
2070
2071 m2x2f subtransform;
2072 m2x2_identity( subtransform );
2073 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2074 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_main, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2075 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 0.0f );
2076 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 0.0f );
2077
2078 glEnable(GL_BLEND);
2079 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2080 glBlendEquation(GL_FUNC_ADD);
2081
2082 // Bind textures
2083 vg_tex2d_bind( &tex_tile_data, 0 );
2084 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2085
2086 vg_tex2d_bind( &tex_wood, 1 );
2087 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2088
2089 render_tiles( NULL, NULL, colour_default, colour_default );
2090
2091 // MARBLES
2092 // ========================================================================================================
2093 SHADER_USE( shader_ball );
2094 glUniformMatrix3fv( SHADER_UNIFORM( shader_ball, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2095
2096 vg_tex2d_bind( &tex_ball_noise, 0 );
2097 glUniform1i( SHADER_UNIFORM( shader_ball, "uTexMain" ), 0 );
2098
2099 if( get_wbutton( k_world_button_sim )->pressed )
2100 {
2101 for( int i = 0; i < world.num_fishes; i ++ )
2102 {
2103 struct fish *fish = &world.fishes[i];
2104 v3f render_pos;
2105 render_pos[2] = 1.0f;
2106
2107 if( fish->state == k_fish_state_dead || fish->state == k_fish_state_soon_dead )
2108 {
2109 float death_anim_time = world.sim_internal_time - fish->death_time;
2110
2111 // Death animation
2112 if( death_anim_time > 0.0f && death_anim_time < 1.0f )
2113 {
2114 float amt = 1.0f-death_anim_time*death_anim_time;
2115
2116 v2_muladds( fish->physics_co, fish->physics_v, -1.0f * world.sim_internal_delta * amt, fish->physics_co );
2117 render_pos[2] = amt;
2118 }
2119 else if( world.sim_internal_time > fish->death_time )
2120 continue;
2121 }
2122 else if( fish->state == k_fish_state_bg )
2123 continue;
2124
2125 v2_copy( fish->physics_co, render_pos );
2126
2127 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2128 colour_code_v3( fish->payload, dot_colour );
2129
2130 glUniform3fv( SHADER_UNIFORM( shader_ball, "uColour" ), 1, dot_colour );
2131 glUniform3fv( SHADER_UNIFORM( shader_ball, "uOffset" ), 1, render_pos );
2132 glUniform2f( SHADER_UNIFORM( shader_ball, "uTexOffset" ), (float)i * 1.2334, (float)i * -0.3579f );
2133 draw_mesh( 0, 2 );
2134 }
2135 }
2136
2137 // TILESET FOREGROUND LAYER
2138 // ========================================================================================================
2139 SHADER_USE( shader_tile_main );
2140
2141 // Bind textures
2142 vg_tex2d_bind( &tex_tile_data, 0 );
2143 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexGlyphs" ), 0 );
2144
2145 vg_tex2d_bind( &tex_wood, 1 );
2146 glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
2147
2148 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
2149 render_tiles( NULL, NULL, colour_default, colour_selected );
2150
2151 // Draw splitters
2152 for( int y = 2; y < world.h-2; y ++ )
2153 {
2154 for( int x = 2; x < world.w-2; x ++ )
2155 {
2156 struct cell *cell = pcell((v2i){x,y});
2157
2158 if( cell->state & FLAG_CANAL )
2159 {
2160 if( cell->config == k_cell_type_split )
2161 {
2162 float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
2163
2164 if( cell->state & FLAG_FLIP_ROTATING )
2165 {
2166 if( (world.frame_lerp > curve_7_linear_section) )
2167 {
2168 float const rotation_speed = 0.4f;
2169 if( (world.frame_lerp < 1.0f-rotation_speed) )
2170 {
2171 float t = world.frame_lerp - curve_7_linear_section;
2172 t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
2173 t += 1.0f;
2174
2175 rotation *= t;
2176 }
2177 else
2178 rotation *= -1.0f;
2179 }
2180 }
2181
2182 m2x2_create_rotation( subtransform, rotation );
2183
2184 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2185 glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ),
2186 (float)x,
2187 (float)y + 0.125f,
2188 cell->state & FLAG_TARGETED? 3.0f: 0.0f,
2189 0.0f
2190 );
2191 draw_mesh( 0, 2 );
2192 }
2193 }
2194 }
2195 }
2196
2197 // EDIT OVERLAY
2198 // ========================================================================================================
2199 if( world.selected != -1 && !(world.data[ world.selected ].state & FLAG_CANAL) && !world.id_drag_from )
2200 {
2201 v2i new_begin = { world.tile_x - 2, world.tile_y - 2 };
2202 v2i new_end = { world.tile_x + 2, world.tile_y + 2 };
2203
2204 world.data[ world.selected ].state ^= FLAG_CANAL;
2205 map_reclassify( new_begin, new_end, 0 );
2206
2207 m2x2_identity( subtransform );
2208 glUniform1f( SHADER_UNIFORM( shader_tile_main, "uGhost" ), 1.0f );
2209 glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );
2210 glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
2211
2212 render_tiles( new_begin, new_end, colour_default, colour_default );
2213
2214 world.data[ world.selected ].state ^= FLAG_CANAL;
2215 map_reclassify( new_begin, new_end, 0 );
2216 }
2217
2218 // BUTTONS
2219 // ========================================================================================================
2220 SHADER_USE( shader_buttons );
2221 glUniformMatrix3fv( SHADER_UNIFORM( shader_buttons, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2222
2223 vg_tex2d_bind( &tex_buttons, 0 );
2224 glUniform1i( SHADER_UNIFORM( shader_buttons, "uTexMain" ), 0 );
2225
2226 wbutton_run( k_world_button_sim );
2227 wbutton_run( k_world_button_pause );
2228 //wbutton_run( k_world_button_wire_mode );
2229
2230 // WIRES
2231 // ========================================================================================================
2232 //glDisable(GL_BLEND);
2233
2234 SHADER_USE( shader_wire );
2235 glBindVertexArray( world.wire.vao );
2236
2237 glUniformMatrix3fv( SHADER_UNIFORM( shader_wire, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2238
2239 v4f const wire_left_colour = { 0.5f, 0.5f, 0.5f, 1.0f };
2240 v4f const wire_right_colour = { 0.2f, 0.2f, 0.2f, 1.0f };
2241 v4f const wire_drag_colour = { 0.2f, 0.2f, 0.2f, 0.6f };
2242
2243 glUniform1f( SHADER_UNIFORM( shader_wire, "uTime" ), world.frame_lerp );
2244 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), 0.0f );
2245
2246 if( world.id_drag_from )
2247 {
2248 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_drag_colour );
2249 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), 0.4f );
2250 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), world.drag_from_co[0], world.drag_from_co[1], 0.20f );
2251 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), world.drag_to_co[0], world.drag_to_co[1], 0.20f );
2252 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2253 }
2254
2255 // Pulling animation
2256 float rp_x1 = world.frame_lerp*9.0f;
2257 float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f;
2258 float rp_x2 = 1.0f-rp_xa;
2259
2260 for( int y = 2; y < world.h-2; y ++ )
2261 {
2262 for( int x = 2; x < world.w-2; x ++ )
2263 {
2264 struct cell *cell = pcell((v2i){x,y});
2265
2266 if( cell->state & FLAG_CANAL )
2267 {
2268 if( cell->state & FLAG_IS_TRIGGER )
2269 {
2270 struct cell_description *desc = &cell_descriptions[ cell->config ];
2271
2272 int trigger_id = cell->links[0]?0:1;
2273
2274 int x2 = cell->links[trigger_id] % world.w;
2275 int y2 = (cell->links[trigger_id] - x2) / world.w;
2276
2277 v2f startpoint;
2278 v2f endpoint;
2279
2280 startpoint[0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
2281 startpoint[1] = (float)y2 + 0.25f;
2282
2283 endpoint[0] = x;
2284 endpoint[1] = y;
2285
2286 v2_add( desc->trigger_pos, endpoint, endpoint );
2287
2288 glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour );
2289 glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
2290 glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
2291 glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.18f );
2292 glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.18f );
2293 glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
2294 }
2295 }
2296 }
2297 }
2298
2299 // WIRE ENDPOINTS
2300 // ========================================================================================================
2301
2302 SHADER_USE( shader_tile_colour );
2303 glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
2304 use_mesh( &world.shapes );
2305
2306 for( int y = 2; y < world.h-2; y ++ )
2307 {
2308 for( int x = 2; x < world.w-2; x ++ )
2309 {
2310 struct cell *cell = pcell((v2i){x,y});
2311
2312 if( cell->state & FLAG_CANAL )
2313 {
2314 if( cell->state & FLAG_IS_TRIGGER )
2315 {
2316 struct cell_description *desc = &cell_descriptions[ cell->config ];
2317
2318 int trigger_id = cell->links[0]?0:1;
2319
2320 int x2 = cell->links[trigger_id] % world.w;
2321 int y2 = (cell->links[trigger_id] - x2) / world.w;
2322
2323 v2f pts[2];
2324
2325 pts[0][0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
2326 pts[0][1] = (float)y2 + 0.25f;
2327
2328 pts[1][0] = x;
2329 pts[1][1] = y;
2330
2331 v2_add( desc->trigger_pos, pts[1], pts[1] );
2332
2333 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
2334 1, trigger_id? wire_right_colour: wire_left_colour );
2335
2336 for( int i = 0; i < 2; i ++ )
2337 {
2338 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
2339 pts[i][0],
2340 pts[i][1],
2341 0.08f
2342 );
2343 draw_mesh( filled_start, filled_count );
2344 }
2345 }
2346 }
2347 }
2348 }
2349
2350 // SUB SPLITTER DIRECTION
2351 // ========================================================================================================
2352
2353 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
2354
2355 for( int y = 2; y < world.h-2; y ++ )
2356 {
2357 for( int x = 2; x < world.w-2; x ++ )
2358 {
2359 struct cell *cell = pcell((v2i){x,y});
2360
2361 if( cell->state & FLAG_CANAL && cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
2362 {
2363 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), x, y, 1.0f );
2364 draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
2365 }
2366 }
2367 }
2368
2369 // I/O ARRAYS
2370 // ========================================================================================================
2371
2372 //glEnable(GL_BLEND);
2373
2374 for( int i = 0; i < arrlen( world.io ); i ++ )
2375 {
2376 struct cell_terminal *term = &world.io[ i ];
2377 int is_input = pcell(term->pos)->state & FLAG_INPUT;
2378
2379 v4f dot_colour = { 0.0f, 0.0f, 0.0f, 1.0f };
2380
2381 for( int k = 0; k < term->run_count; k ++ )
2382 {
2383 float arr_base = is_input? 1.2f: -0.2f,
2384 run_offset = (is_input? 0.2f: -0.2f) * (float)k,
2385 y_position = is_input?
2386 (arr_base + (float)term->pos[1] + (float)(term->run_count-1)*0.2f) - run_offset:
2387 (float)term->pos[1] + arr_base + run_offset;
2388
2389 v4f bar_colour;
2390 int bar_draw = 0;
2391
2392 if( is_simulation_running() )
2393 {
2394 if( k == world.sim_run )
2395 {
2396 float a = fabsf(sinf( vg_time * 2.0f )) * 0.075f + 0.075f;
2397
2398 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, a }, bar_colour );
2399 }
2400 else
2401 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
2402
2403 bar_draw = 1;
2404 }
2405 else if( 1 || k & 0x1 )
2406 {
2407 if( k & 0x1 )
2408 v4_copy( (v4f){ 1.0f, 1.0f, 1.0f, 0.07f }, bar_colour );
2409 else
2410 v4_copy( (v4f){ 0.0f, 0.0f, 0.0f, 0.13f }, bar_colour );
2411
2412 bar_draw = 1;
2413 }
2414
2415 if( bar_draw )
2416 {
2417 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, bar_colour );
2418 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), (float)term->pos[0], y_position - 0.1f, 1.0f );
2419 draw_mesh( 2, 2 );
2420 }
2421
2422 for( int j = 0; j < term->runs[k].condition_count; j ++ )
2423 {
2424 glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ),
2425 (float)term->pos[0] + 0.2f + 0.2f * (float)j,
2426 y_position,
2427 0.1f
2428 );
2429
2430 if( is_input )
2431 {
2432 char cc = term->runs[k].conditions[j];
2433 if( cc != ' ' )
2434 {
2435 colour_code_v3( cc, dot_colour );
2436 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2437
2438 // Draw filled if tick not passed, draw empty if empty
2439 if( (world.sim_frame > j && world.sim_run >= k) || world.sim_run > k )
2440 draw_mesh( empty_start, empty_count );
2441 else
2442 draw_mesh( filled_start, filled_count );
2443 }
2444 }
2445 else
2446 {
2447
2448 if( term->runs[k].recv_count > j )
2449 {
2450 colour_code_v3( term->runs[k].recieved[j], dot_colour );
2451 v3_muls( dot_colour, 0.8f, dot_colour );
2452 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2453
2454 draw_mesh( filled_start, filled_count );
2455 }
2456
2457 colour_code_v3( term->runs[k].conditions[j], dot_colour );
2458 glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 1, dot_colour );
2459
2460 draw_mesh( empty_start, empty_count );
2461 }
2462 }
2463 }
2464 }
2465
2466 glDisable(GL_BLEND);
2467
2468 // Draw score
2469 /*
2470 float const score_bright = 1.25f;
2471 glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ),
2472 0.4f*score_bright, 0.39f*score_bright, 0.45f*score_bright, 1.0f );
2473
2474 use_mesh( &world.numbers );
2475 draw_numbers( (v3f){ 2.0f, (float)world.h-1.875f, 0.3333f }, world.score );
2476 */
2477 }
2478
2479 static ui_colourset flcol_list_a = {
2480 .main = 0xff877979,
2481 .hover = 0xffa09393,
2482 .active = 0xffbfb1b0
2483 };
2484 static ui_colourset flcol_list_b = {
2485 .main = 0xff7c6e6e,
2486 .hover = 0xffa09393,
2487 .active = 0xffbfb1b0
2488 };
2489
2490 static ui_colourset flcol_list_complete_a = {
2491 .main = 0xff62a064,
2492 .hover = 0xff8dc18f,
2493 .active = 0xffb2ddb3
2494 };
2495
2496 static ui_colourset flcol_list_complete_b = {
2497 .main = 0xff79b37b,
2498 .hover = 0xff8dc18f,
2499 .active = 0xffb2ddb3
2500 };
2501
2502 static ui_colourset flcol_list_locked = {
2503 .main = 0xff655959,
2504 .hover = 0xff655959,
2505 .active = 0xff655959
2506 };
2507
2508 static struct
2509 {
2510 SteamLeaderboard_t steam_leaderboard;
2511 int leaderboard_show;
2512
2513 struct leaderboard_player
2514 {
2515 // Internal
2516 u64_steamid id;
2517 i32 score;
2518 int is_local_player;
2519
2520 // To be displayed
2521 char score_text[ 16 ];
2522 char player_name[ 48 ];
2523 GLuint texture; // Not dynamic
2524 }
2525 leaderboard_players[10];
2526 int leaderboard_count;
2527
2528 struct
2529 {
2530 struct cmp_level *level;
2531
2532 i32 score;
2533 int is_waiting;
2534 }
2535 upload_request;
2536
2537 struct cmp_level *level_selected;
2538 }
2539 ui_data;
2540
2541 void vg_ui(void)
2542 {
2543 // UI memory
2544 static int pack_selection = 0;
2545 static struct pack_info
2546 {
2547 struct cmp_level *levels;
2548 u32 level_count;
2549 const char *name;
2550 }
2551 pack_infos[] =
2552 {
2553 {
2554 .levels = cmp_levels_tutorials,
2555 .level_count = vg_list_size(cmp_levels_tutorials),
2556 .name = "Training"
2557 },
2558 {
2559 .levels = cmp_levels_basic,
2560 .level_count = vg_list_size(cmp_levels_basic),
2561 .name = "Main"
2562 },
2563 {
2564 .levels = cmp_levels_grad,
2565 .level_count = vg_list_size(cmp_levels_tutorials),
2566 .name = "Expert"
2567 }
2568 };
2569
2570 // UI Code
2571 ui_global_ctx.cursor[0] = 0;
2572 ui_global_ctx.cursor[1] = 0;
2573 ui_global_ctx.cursor[2] = 256;
2574
2575 gui_fill_y();
2576
2577 ui_global_ctx.id_base = 4 << 16;
2578
2579 gui_new_node();
2580 {
2581 gui_capture_mouse( 9999 );
2582 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2583
2584 gui_text( "ASSIGNMENTS", 8, 0 );
2585
2586 ui_global_ctx.cursor[1] += 30;
2587 ui_global_ctx.cursor[3] = 25;
2588
2589 gui_new_node();
2590 {
2591 ui_rect_pad( ui_global_ctx.cursor, 2 );
2592 ui_global_ctx.cursor[2] = 84;
2593
2594 for( int i = 0; i < 3; i ++ )
2595 {
2596 int pack_is_unlocked = pack_infos[i].levels[0].unlocked;
2597
2598 if( i == pack_selection || !pack_is_unlocked )
2599 gui_override_colours( &flcol_list_locked );
2600
2601 if( gui_button( 2000 + i ) == k_button_click && pack_is_unlocked )
2602 pack_selection = i;
2603
2604 ui_global_ctx.cursor[1] += 2;
2605 gui_text( pack_is_unlocked? pack_infos[i].name: "???", 4, 0 );
2606 gui_end_right();
2607
2608 gui_reset_colours();
2609 }
2610 }
2611 gui_end_down();
2612
2613 ui_global_ctx.cursor[3] = 500;
2614
2615 // DRAW LEVEL SELECTION LIST
2616 {
2617 struct cmp_level *levels = pack_infos[ pack_selection ].levels;
2618 int count = pack_infos[ pack_selection ].level_count;
2619
2620 static struct ui_scrollbar sb = {
2621 .bar_height = 400
2622 };
2623
2624 ui_px view_height = ui_global_ctx.cursor[3];
2625 ui_px level_height = 50;
2626
2627 // Level scroll view
2628 gui_new_node();
2629 {
2630 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2631 gui_set_clip( ui_global_ctx.cursor );
2632
2633 ui_global_ctx.cursor[2] = 14;
2634 gui_align_right();
2635
2636 ui_px content_height = count*level_height;
2637 if( content_height > view_height )
2638 {
2639 ui_scrollbar( &ui_global_ctx, &sb, 1 );
2640 ui_global_ctx.cursor[1] -= ui_calculate_content_scroll( &sb, content_height );
2641 }
2642 else
2643 {
2644 gui_fill_rect( ui_global_ctx.cursor, 0xff807373 );
2645 }
2646
2647 ui_global_ctx.cursor[2] = 240;
2648 ui_global_ctx.cursor[3] = level_height;
2649 gui_align_left();
2650
2651 for( int i = 0; i < count; i ++ )
2652 {
2653 struct cmp_level *lvl_info = &levels[i];
2654
2655 if( lvl_info->unlocked )
2656 {
2657 if( lvl_info->completed_score != 0 )
2658 gui_override_colours( i&0x1? &flcol_list_complete_a: &flcol_list_complete_b );
2659 else
2660 gui_override_colours( i&0x1? &flcol_list_a: &flcol_list_b );
2661 }
2662 else
2663 gui_override_colours( &flcol_list_locked );
2664
2665 if( lvl_info->unlocked )
2666 {
2667 if( gui_button( 2 + i ) == k_button_click )
2668 {
2669 ui_data.level_selected = &levels[i];
2670 ui_data.leaderboard_show = 0;
2671
2672 if( pack_selection >= 1 )
2673 sw_find_leaderboard( ui_data.level_selected->map_name );
2674 }
2675
2676 ui_global_ctx.override_colour = 0xffffffff;
2677 gui_text( lvl_info->title, 6, 0 );
2678 ui_global_ctx.cursor[1] += 18;
2679 gui_text( lvl_info->completed_score>0? "passed": "incomplete", 4, 0 );
2680 }
2681 else
2682 {
2683 gui_button( 2 + i );
2684
2685 ui_global_ctx.override_colour = 0xff786f6f;
2686 gui_text( "???", 6, 0 );
2687 ui_global_ctx.cursor[1] += 18;
2688 gui_text( "locked", 4, 0 );
2689 }
2690
2691 gui_end_down();
2692 }
2693
2694 gui_reset_colours();
2695 gui_release_clip();
2696 }
2697 gui_end_down();
2698 }
2699 }
2700 gui_end_right();
2701
2702 // Selected level UI
2703 // ============================================================
2704
2705 if( ui_data.level_selected )
2706 {
2707 ui_global_ctx.cursor[0] += 16;
2708 ui_global_ctx.cursor[1] += 16;
2709 ui_global_ctx.cursor[2] = 512-40;
2710 ui_global_ctx.cursor[3] = 560-16;
2711
2712 gui_new_node();
2713 {
2714 gui_capture_mouse( 9999 );
2715
2716 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2717 ui_global_ctx.cursor[1] += 4;
2718 gui_text( ui_data.level_selected->title, 6, 0 );
2719
2720 ui_global_ctx.cursor[1] += 30;
2721 ui_rect_pad( ui_global_ctx.cursor, 8 );
2722 ui_global_ctx.cursor[3] = 300;
2723
2724 gui_new_node();
2725 {
2726 gui_fill_rect( ui_global_ctx.cursor, 0xff655959 );
2727 }
2728 gui_end_down();
2729
2730 ui_text_use_paragraph( &ui_global_ctx );
2731 ui_global_ctx.cursor[1] += 2;
2732
2733 gui_text( ui_data.level_selected->description, 5, 0 );
2734 ui_text_use_title( &ui_global_ctx );
2735
2736 // Buttons at the bottom
2737 ui_global_ctx.cursor[3] = 25;
2738 ui_global_ctx.cursor[2] = 80;
2739
2740 gui_align_bottom();
2741 ui_global_ctx.cursor[1] -= 8;
2742
2743 if( gui_button( 3000 ) == k_button_click )
2744 {
2745 ui_data.level_selected = NULL;
2746 }
2747 gui_text( "BACK", 6, k_text_alignment_center );
2748 gui_end();
2749
2750 gui_align_right();
2751 ui_global_ctx.cursor[2] = 170;
2752 ui_global_ctx.cursor[0] -= 8 + 170 + 2;
2753
2754 {
2755 gui_override_colours( &flcol_list_locked );
2756 if( gui_button( 3001 ) == k_button_click )
2757 vg_error( "UNIMPLEMENTED\n" );
2758
2759 ui_global_ctx.override_colour = 0xff888888;
2760
2761 gui_text( "RESTORE SOLUTION", 6, k_text_alignment_center );
2762 gui_end_right();
2763 ui_global_ctx.override_colour = 0xffffffff;
2764 }
2765
2766 ui_global_ctx.cursor[0] += 2;
2767 ui_global_ctx.cursor[2] = 80;
2768
2769 {
2770 gui_override_colours( &flcol_list_complete_a );
2771 if( gui_button( 3002 ) == k_button_click )
2772 {
2773 if( console_changelevel( 1, &ui_data.level_selected->map_name ) )
2774 {
2775 world.pCmpLevel = ui_data.level_selected;
2776
2777 ui_data.level_selected = NULL;
2778 ui_data.leaderboard_show = 0;
2779 }
2780 }
2781 gui_text( "PLAY", 6, k_text_alignment_center );
2782 gui_end();
2783 }
2784
2785 gui_reset_colours();
2786 }
2787 gui_end_right();
2788
2789 if( ui_data.leaderboard_show )
2790 {
2791 ui_global_ctx.cursor[0] += 16;
2792 ui_global_ctx.cursor[2] = 350;
2793 ui_global_ctx.cursor[3] = 25;
2794
2795 // If has results
2796 gui_new_node();
2797 {
2798 gui_fill_rect( ui_global_ctx.cursor, 0xff5a4e4d );
2799 gui_text( "FRIEND LEADERBOARD", 6, 0 );
2800 }
2801 gui_end_down();
2802
2803 ui_global_ctx.cursor[1] += 2;
2804
2805 gui_new_node();
2806 {
2807 ui_global_ctx.cursor[3] = 32+8;
2808
2809 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
2810 {
2811 gui_new_node();
2812 {
2813 gui_fill_rect( ui_global_ctx.cursor, i&0x1? flcol_list_a.main: flcol_list_b.main );
2814
2815 ui_global_ctx.cursor[0] += 4;
2816 ui_global_ctx.cursor[1] += 4;
2817
2818 // 1,2,3 ...
2819 static const char *places[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
2820 gui_text( places[i], 7, 0 );
2821 ui_global_ctx.cursor[0] += 32;
2822
2823 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
2824
2825 // Players image
2826 ui_global_ctx.cursor[2] = 32;
2827 ui_global_ctx.cursor[3] = 32;
2828
2829 gui_new_node();
2830 {
2831 gui_push_image( ui_global_ctx.cursor, player->texture );
2832 }
2833 gui_end_right();
2834
2835 // Players name
2836 gui_text( player->player_name, 7, 0 );
2837
2838 ui_global_ctx.cursor[2] = 50;
2839 gui_align_right();
2840
2841 gui_text( player->score_text, 7, k_text_alignment_right );
2842 }
2843 gui_end_down();
2844
2845 ui_global_ctx.cursor[1] += 2;
2846 }
2847 }
2848 gui_end();
2849 }
2850 }
2851 }
2852
2853 void leaderboard_dispatch_score(void)
2854 {
2855 sw_upload_leaderboard_score(
2856 ui_data.upload_request.level->steam_leaderboard,
2857 k_ELeaderboardUploadScoreMethodKeepBest,
2858 ui_data.upload_request.score,
2859 NULL,
2860 0
2861 );
2862
2863 ui_data.upload_request.is_waiting = 0;
2864
2865 vg_success( "Dispatched leaderboard score\n" );
2866 }
2867
2868 void leaderboard_found( LeaderboardFindResult_t *pCallback )
2869 {
2870 if( !pCallback->m_bLeaderboardFound )
2871 {
2872 vg_error( "Leaderboard could not be found\n" );
2873 ui_data.steam_leaderboard = 0;
2874 }
2875 else
2876 {
2877 const char *recieved_name = sw_get_leaderboard_name( pCallback->m_hSteamLeaderboard );
2878
2879 // Update UI state and request entries if this callback found the current UI level
2880 if( ui_data.level_selected )
2881 {
2882 if( !strcmp( recieved_name, ui_data.level_selected->map_name ) )
2883 {
2884 sw_download_leaderboard_entries( pCallback->m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 0, 8 );
2885 ui_data.level_selected->steam_leaderboard = pCallback->m_hSteamLeaderboard;
2886 }
2887 }
2888
2889 // Dispatch the waiting request if there was one
2890 if( ui_data.upload_request.is_waiting )
2891 {
2892 if( !strcmp( recieved_name, ui_data.upload_request.level->map_name ) )
2893 {
2894 ui_data.upload_request.level->steam_leaderboard = pCallback->m_hSteamLeaderboard;
2895 leaderboard_dispatch_score();
2896 }
2897 }
2898 }
2899 }
2900
2901 void leaderboard_downloaded( LeaderboardScoresDownloaded_t *pCallback )
2902 {
2903 // Update UI if this leaderboard matches what we currently have in view
2904 if( ui_data.level_selected->steam_leaderboard == pCallback->m_hSteamLeaderboard )
2905 {
2906 vg_info( "Recieved %d entries\n", pCallback->m_cEntryCount );
2907 ui_data.leaderboard_count = VG_MIN( pCallback->m_cEntryCount, 8 );
2908
2909 u64_steamid local_player = sw_get_steamid();
2910
2911 for( int i = 0; i < ui_data.leaderboard_count; i ++ )
2912 {
2913 LeaderboardEntry_t entry;
2914 sw_get_downloaded_entry( pCallback->m_hSteamLeaderboardEntries, i, &entry, NULL, 0 );
2915
2916 struct leaderboard_player *player = &ui_data.leaderboard_players[i];
2917
2918 player->id = entry.m_steamIDUser.m_unAll64Bits;
2919 strncpy( player->player_name, sw_get_friend_persona_name( player->id ), vg_list_size( player->player_name )-1 );
2920 player->score = entry.m_nScore;
2921
2922 snprintf( player->score_text, vg_list_size(player->score_text), "%d", player->score );
2923 player->texture = sw_get_player_image( player->id );
2924
2925 if( player->texture == 0 )
2926 player->texture = tex_unkown.name;
2927
2928 player->is_local_player = local_player == player->id? 1: 0;
2929 }
2930
2931 if( ui_data.leaderboard_count )
2932 ui_data.leaderboard_show = 1;
2933 else
2934 ui_data.leaderboard_show = 0;
2935 }
2936 else vg_warn( "Downloaded leaderboard does not match requested!\n" );
2937 }
2938
2939 void leaderboard_set_score( struct cmp_level *cmp_level, u32 score )
2940 {
2941 if( ui_data.upload_request.is_waiting )
2942 vg_warn( "You are uploading leaderboard entries too quickly!\n" );
2943
2944 ui_data.upload_request.level = cmp_level;
2945 ui_data.upload_request.score = score;
2946 ui_data.upload_request.is_waiting = 1;
2947
2948 // If leaderboard ID has been downloaded already then just immediately dispatch this
2949 if( cmp_level->steam_leaderboard )
2950 leaderboard_dispatch_score();
2951 else
2952 sw_find_leaderboard( cmp_level->map_name );
2953 }
2954
2955 // CONSOLE COMMANDS
2956 // ===========================================================================================================
2957
2958 static int console_credits( int argc, char const *argv[] )
2959 {
2960 vg_info( "Aknowledgements:\n" );
2961 vg_info( " GLFW zlib/libpng glfw.org\n" );
2962 vg_info( " miniaudio MIT0 miniaud.io\n" );
2963 vg_info( " QOI MIT phoboslab.org\n" );
2964 vg_info( " STB library MIT nothings.org\n" );
2965 return 0;
2966 }
2967
2968 static int console_save_map( int argc, char const *argv[] )
2969 {
2970 if( !world.initialzed )
2971 {
2972 vg_error( "Tried to save uninitialized map!\n" );
2973 return 0;
2974 }
2975
2976 char map_path[ 256 ];
2977
2978 strcpy( map_path, "sav/" );
2979 strcat( map_path, world.map_name );
2980 strcat( map_path, ".map" );
2981
2982 FILE *test_writer = fopen( map_path, "wb" );
2983 if( test_writer )
2984 {
2985 vg_info( "Saving map to '%s'\n", map_path );
2986 map_serialize( test_writer );
2987
2988 fclose( test_writer );
2989 return 1;
2990 }
2991 else
2992 {
2993 vg_error( "Unable to open stream for writing\n" );
2994 return 0;
2995 }
2996 }
2997
2998 static int console_load_map( int argc, char const *argv[] )
2999 {
3000 char map_path[ 256 ];
3001
3002 if( argc >= 1 )
3003 {
3004 // try from saves
3005 strcpy( map_path, "sav/" );
3006 strcat( map_path, argv[0] );
3007 strcat( map_path, ".map" );
3008
3009 char *text_source = vg_textasset_read( map_path );
3010
3011 if( !text_source )
3012 {
3013 strcpy( map_path, "maps/" );
3014 strcat( map_path, argv[0] );
3015 strcat( map_path, ".map" );
3016
3017 text_source = vg_textasset_read( map_path );
3018 }
3019
3020 if( text_source )
3021 {
3022 vg_info( "Loading map: '%s'\n", map_path );
3023 world.pCmpLevel = NULL;
3024
3025 if( !map_load( text_source, argv[0] ) )
3026 {
3027 free( text_source );
3028 return 0;
3029 }
3030
3031 free( text_source );
3032 return 1;
3033 }
3034 else
3035 {
3036 vg_error( "Missing maps '%s'\n", argv[0] );
3037 return 0;
3038 }
3039 }
3040 else
3041 {
3042 vg_error( "Missing argument <map_path>\n" );
3043 return 0;
3044 }
3045 }
3046
3047 static int console_changelevel( int argc, char const *argv[] )
3048 {
3049 if( argc >= 1 )
3050 {
3051 // Save current level
3052 console_save_map( 0, NULL );
3053 if( console_load_map( argc, argv ) )
3054 {
3055 simulation_stop();
3056 return 1;
3057 }
3058 }
3059 else
3060 {
3061 vg_error( "Missing argument <map_path>\n" );
3062 }
3063
3064 return 0;
3065 }
3066
3067 // START UP / SHUTDOWN
3068 // ===========================================================================================================
3069
3070 #define TRANSFORM_TRI_2D( S, OX, OY, X1, Y1, X2, Y2, X3, Y3 ) \
3071 X1*S+OX, Y1*S+OY, X2*S+OX, Y2*S+OY, X3*S+OX, Y3*S+OY
3072
3073 void vg_start(void)
3074 {
3075 // Steamworks callbacks
3076 sw_leaderboard_found = &leaderboard_found;
3077 sw_leaderboard_downloaded = &leaderboard_downloaded;
3078
3079 vg_function_push( (struct vg_cmd){
3080 .name = "_map_write",
3081 .function = console_save_map
3082 });
3083
3084 vg_function_push( (struct vg_cmd){
3085 .name = "_map_load",
3086 .function = console_load_map
3087 });
3088
3089 vg_function_push( (struct vg_cmd){
3090 .name = "map",
3091 .function = console_changelevel
3092 });
3093
3094 vg_function_push( (struct vg_cmd){
3095 .name = "credits",
3096 .function = console_credits
3097 });
3098
3099 // Combined quad, long quad / empty circle / filled circle mesh
3100 {
3101 float combined_mesh[6*6 + 32*6*3] = {
3102 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
3103 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
3104
3105 0.0f, 0.0f, 0.0f, 0.2f, 1.0f, 0.2f,
3106 0.0f, 0.0f, 1.0f, 0.2f, 1.0f, 0.0f,
3107
3108 TRANSFORM_TRI_2D( 0.15f,0.05f,0.4f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, 0.0f ),
3109 TRANSFORM_TRI_2D( 0.15f,0.80f,0.4f, 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 1.0f )
3110 };
3111
3112 float *circle_mesh = combined_mesh + 6*6;
3113 int const res = 32;
3114
3115 for( int i = 0; i < res; i ++ )
3116 {
3117 v2f v0 = { sinf( ((float)i/(float)res)*VG_TAUf ), cosf( ((float)i/(float)res)*VG_TAUf ) };
3118 v2f v1 = { sinf( ((float)(i+1)/(float)res)*VG_TAUf ), cosf( ((float)(i+1)/(float)res)*VG_TAUf ) };
3119
3120 circle_mesh[ i*6+0 ] = 0.0f;
3121 circle_mesh[ i*6+1 ] = 0.0f;
3122
3123 v2_copy( v0, circle_mesh + 32*6 + i*12 );
3124 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+2 );
3125 v2_copy( v1, circle_mesh + 32*6 + i*12+4 );
3126
3127 v2_copy( v1, circle_mesh + 32*6 + i*12+6 );
3128 v2_muls( v1, 0.8f, circle_mesh + 32*6 + i*12+8 );
3129 v2_muls( v0, 0.8f, circle_mesh + 32*6 + i*12+10 );
3130
3131 v2_copy( v0, circle_mesh + i*6+4 );
3132 v2_copy( v1, circle_mesh + i*6+2 );
3133 v2_copy( v0, circle_mesh+i*6+4 );
3134 v2_copy( v1, circle_mesh+i*6+2 );
3135 }
3136
3137 init_mesh( &world.shapes, combined_mesh, vg_list_size( combined_mesh ) );
3138 }
3139
3140 // Numbers mesh
3141 {
3142 init_mesh( &world.numbers,
3143 MESH_NUMBERS_BUFFER,
3144 vg_list_size( MESH_NUMBERS_BUFFER )
3145 );
3146
3147 for( int i = 0; i < 10; i ++ )
3148 {
3149 vg_info( "offset: %u, length: %u\n", MESH_NUMBERS_OFFSETS[i][0], MESH_NUMBERS_OFFSETS[i][1] );
3150 }
3151 }
3152
3153 // Create wire mesh
3154 {
3155 int const num_segments = 64;
3156
3157 struct mesh_wire *mw = &world.wire;
3158
3159 v2f wire_points[ num_segments * 2 ];
3160 u16 wire_indices[ 6*(num_segments-1) ];
3161
3162 for( int i = 0; i < num_segments; i ++ )
3163 {
3164 float l = (float)i / (float)(num_segments-1);
3165
3166 v2_copy( (v2f){ l, -0.5f }, wire_points[i*2+0] );
3167 v2_copy( (v2f){ l, 0.5f }, wire_points[i*2+1] );
3168
3169 if( i < num_segments-1 )
3170 {
3171 wire_indices[ i*6+0 ] = i*2 + 0;
3172 wire_indices[ i*6+1 ] = i*2 + 1;
3173 wire_indices[ i*6+2 ] = i*2 + 3;
3174 wire_indices[ i*6+3 ] = i*2 + 0;
3175 wire_indices[ i*6+4 ] = i*2 + 3;
3176 wire_indices[ i*6+5 ] = i*2 + 2;
3177 }
3178 }
3179
3180 glGenVertexArrays( 1, &mw->vao );
3181 glGenBuffers( 1, &mw->vbo );
3182 glGenBuffers( 1, &mw->ebo );
3183 glBindVertexArray( mw->vao );
3184
3185 glBindBuffer( GL_ARRAY_BUFFER, mw->vbo );
3186
3187 glBufferData( GL_ARRAY_BUFFER, sizeof( wire_points ), wire_points, GL_STATIC_DRAW );
3188 glBindVertexArray( mw->vao );
3189
3190 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mw->ebo );
3191 glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( wire_indices ), wire_indices, GL_STATIC_DRAW );
3192
3193 // XY
3194 glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)0 );
3195 glEnableVertexAttribArray( 0 );
3196
3197 VG_CHECK_GL();
3198
3199 mw->em = vg_list_size( wire_indices );
3200 }
3201
3202 // Create info data texture
3203 {
3204 glGenTextures( 1, &world.background_data );
3205 glBindTexture( GL_TEXTURE_2D, world.background_data );
3206 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
3207 vg_tex2d_nearest();
3208 }
3209
3210 // Create random smaples texture
3211 {
3212 u8 *data = malloc(512*512*2);
3213 for( int i = 0; i < 512*512*2; i ++ )
3214 data[ i ] = rand()/(RAND_MAX/255);
3215
3216 glGenTextures( 1, &world.random_samples );
3217 glBindTexture( GL_TEXTURE_2D, world.random_samples );
3218 glTexImage2D( GL_TEXTURE_2D, 0, GL_RG, 512, 512, 0, GL_RG, GL_UNSIGNED_BYTE, data );
3219 vg_tex2d_linear();
3220 vg_tex2d_repeat();
3221
3222 free( data );
3223 }
3224
3225 resource_load_main();
3226
3227 // Restore gamestate
3228 career_local_data_init();
3229 career_load();
3230 }
3231
3232 void vg_free(void)
3233 {
3234 sw_free_opengl();
3235 console_save_map( 0, NULL );
3236 career_serialize();
3237
3238 resource_free_main();
3239
3240 glDeleteTextures( 1, &world.background_data );
3241 glDeleteTextures( 1, &world.random_samples );
3242
3243 glDeleteVertexArrays( 1, &world.wire.vao );
3244 glDeleteBuffers( 1, &world.wire.vbo );
3245 glDeleteBuffers( 1, &world.wire.ebo );
3246
3247 free_mesh( &world.shapes );
3248 free_mesh( &world.numbers );
3249
3250 map_free();
3251 }
3252
3253 int main( int argc, char *argv[] )
3254 {
3255 vg_init( argc, argv, "Marble Computing" );
3256 return 0;
3257 }