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