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