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