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