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