1 /*
2 This file is part of cave9.
3
4 cave9 is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 cave9 is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with cave9. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <math.h>
22 #include <float.h>
23 #include <assert.h>
24 #include <SDL_opengl.h>
25 #include <GL/gl.h>
26 #include "vec.h"
27 #include "game.h"
28 #include "util.h"
29 #include "detrand.h"
30 #include "time.h"
31
32 const char* data_paths[] =
33 {
34 DATADIR,
35 NULL
36 };
37
cave_len(Cave * cave)38 float cave_len (Cave* cave)
39 {
40 int head = cave->i;
41 int tail = (head - 1 + SEGMENT_COUNT) % SEGMENT_COUNT;
42 return cave->segs[tail][0][2] - cave->segs[head][0][2];
43 }
44
generate_stalactites(Cave * cave,float mult_y,float cos_a)45 static inline float generate_stalactites (Cave* cave, float mult_y, float cos_a)
46 {
47 static const float change_prob = 0.001;
48 static const float some_prob = 0.01;
49 static const float many_prob = 0.05;
50 float prob = 0;
51 float change = DRAND;
52
53 switch (cave->stalactites_status)
54 {
55 case STALACT_NONE:
56 if (change < change_prob)
57 cave->stalactites_status = STALACT_SOME;
58 return mult_y;
59 case STALACT_SOME:
60 prob = some_prob;
61 if (change < change_prob)
62 cave->stalactites_status = STALACT_NONE;
63 else if (change > (1 - change_prob))
64 cave->stalactites_status = STALACT_MANY;
65 break;
66 case STALACT_MANY:
67 prob = many_prob;
68 if (change < change_prob)
69 cave->stalactites_status = STALACT_SOME;
70 break;
71 default:
72 //WTF??
73 assert ("stalactites_status not possible" == 0);
74 }
75
76 return DRAND < prob ? 1 : mult_y;
77 }
78
cave_gen(Cave * cave,Digger * digger)79 void cave_gen (Cave* cave, Digger* digger)
80 {
81 Ship *ship = SHIP(digger);
82 // check if the ship advanced to the next segment
83 int i = (cave->i - 1 + SEGMENT_COUNT) % SEGMENT_COUNT;
84 if (ship->pos[2] > ship->start+1 && ship->pos[2]-SEGMENT_LEN < cave->segs[i][0][2])
85 return;
86
87 // invalidate GL list for this segment
88 cave->dirty[cave->i] = true;
89
90 const float B = 2/(WALL_MULT_MAX - WALL_MULT_MIN);
91 const float A = B*WALL_MULT_MAX - 1;
92
93 // generate new segment
94 for (i = 0; i < SECTOR_COUNT; ++i) {
95 float a = M_PI_2+(i-1)*M_PI*2/SECTOR_COUNT;
96 float r = ship->radius;
97
98 float cos_a = cos(a);
99 float sin_a = sin(a);
100
101 float mult_x = (cos_a > 0)? digger->y_top_radius : digger->y_bottom_radius;
102 float mult_y = (sin_a > 0)? digger->x_left_radius: digger->x_right_radius;
103
104 // clamp the multipliers to [mult_min .. mult_max]
105 mult_x = (A + sin(mult_x))/B;
106 mult_y = (A + sin(mult_y))/B;
107
108 if (cave->stalactites_status != STALACT_DISABLED)
109 mult_y = generate_stalactites(cave, mult_y, cos_a);
110
111 SET(cave->segs[cave->i][i],
112 ship->pos[0] + (r * mult_x * cos_a) + 2 * DRAND,
113 ship->pos[1] + (r * mult_y * sin_a) + 2 * DRAND,
114 ship->pos[2]
115 );
116 }
117 COPY (cave->centers[cave->i], ship->pos);
118
119 // increment segment circular pointer
120 cave->i = (cave->i + 1) % SEGMENT_COUNT;
121
122 // place monolith on rooms
123 if (ship->pos[2] > cave->monolith_pos[2]+ROOM_SPACING && ship->pos[2] > ROOM_START)
124 {
125 cave->monolith_pos[0] = ship->pos[0];
126 cave->monolith_pos[1] = ship->pos[1];
127 cave->monolith_pos[2] = ROOM_LEN/2 + ROOM_SPACING*(int)(ship->pos[2]/ROOM_SPACING);
128 cave->monolith_yaw = atan2 (ship->vel[0], ship->vel[2]);
129 }
130 }
131
cave_init(Cave * cave,Digger * digger,Args * args)132 static void cave_init (Cave* cave, Digger* digger, Args* args)
133 {
134 memset (cave, 0, sizeof(Cave));
135
136 int game_mode = TWO_BUTTONS;
137 if (args != NULL) {
138 game_mode = args->game_mode;
139 cave->stalactites_status = args->stalactites?STALACT_NONE:STALACT_DISABLED;
140 }
141
142 Ship *ship = SHIP(digger);
143 cave->i = 0;
144 do {
145 digger_control(digger, game_mode);
146 ship_move(ship, 1./50);
147 cave_gen(cave, digger);
148 }
149 while(cave->i != 0);
150 }
151
ship_init(Ship * ship,float radius)152 static void ship_init (Ship* ship, float radius)
153 {
154 SET (ship->pos, 0,0,ship->start);
155 SET (ship->vel, 0,0,VELOCITY);
156 SET (ship->lookAt, 0,0,VELOCITY);
157 ship->roll = 0;
158 ship->radius = radius;
159 ship->dist = FLT_MAX;
160 SET (ship->repulsion,0,1,0);
161 ship->lefton = ship->righton = false;
162 }
163
digger_init(Digger * digger,float radius)164 static void digger_init(Digger *digger, float radius)
165 {
166 ship_init(SHIP(digger), radius);
167
168 digger->x_left_radius = 0.0;
169 digger->x_right_radius = 0.0;
170 digger->y_top_radius = 0.0;
171 digger->y_bottom_radius = 0.0;
172 }
173
fast_forward(Game * game)174 void fast_forward (Game* game)
175 {
176 while ((game->digger.ship.pos[2] - cave_len(&game->cave)) / (game->mode==ONE_BUTTON?2:1) < game->start)
177 {
178 digger_control (&game->digger, game->mode);
179 cave_gen (&game->cave, &game->digger);
180 ship_move (SHIP(&game->digger), 0.05);
181 }
182 // put the player in the middle of the cave
183 COPY(game->player.pos, game->cave.centers[(game->cave.i + 1) % SEGMENT_COUNT]);
184
185 game->player.vel[0] += (game->cave.centers[(game->cave.i + 2) % SEGMENT_COUNT][0] -
186 game->cave.centers[(game->cave.i + 1) % SEGMENT_COUNT][0]) * 20;
187
188 game->player.vel[1] += (game->cave.centers[(game->cave.i + 2) % SEGMENT_COUNT][1] -
189 game->cave.centers[(game->cave.i + 1) % SEGMENT_COUNT][1]) * 20;
190
191 // the y-component of the vector should be biased towards upwards movement,
192 // as reverting downwards-movement is *very* hard.
193 if (game->player.vel[1] < 0)
194 game->player.vel[1] *= 0.1;
195
196 COPY(game->player.lookAt, game->player.vel);
197 }
198
game_init(Game * game,Args * args)199 void game_init (Game* game, Args* args)
200 {
201 assert(args != NULL);
202
203 game->mode = args->game_mode;
204 game->monoliths = args->monoliths;
205 game->caveseed = args->caveseed;
206 if (game->caveseed != 0)
207 {
208 game->start = args->start;
209 detsrand(game->caveseed);
210 } else {
211 game->player.start = game->digger.ship.start = (float)args->start;
212 game->start = 0;
213 detsrand(time(NULL));
214 }
215
216 ship_init (&game->player, SHIP_RADIUS);
217 digger_init (&game->digger, MAX_CAVE_RADIUS);
218 cave_init (&game->cave, &game->digger, args);
219 if (game->start)
220 fast_forward(game);
221
222 score_init (&game->score, args, game->caveseed, game->monoliths * 2/* + game->stalactites*/); // XXX uncomment this, once stalactites are implemented
223 }
224
ship_move(Ship * ship,float dt)225 void ship_move (Ship* ship, float dt)
226 {
227 float acc = THRUST*dt;
228 int roll = 0;
229
230 if(ship->lefton) {
231 Vec3 leftup = { -acc, acc/2, 0 };
232 ADD(ship->vel, leftup);
233 roll--;
234 }
235
236 if(ship->righton) {
237 Vec3 rightup = { +acc, acc/2, 0 };
238 ADD(ship->vel, rightup);
239 roll++;
240 }
241
242 ship->vel[1] -= GRAVITY*dt;
243
244 ADDSCALE(ship->pos, ship->vel, dt);
245
246 // smooth lookAt
247 Vec3 d;
248 SUB2 (d, ship->vel, ship->lookAt);
249 ADDSCALE (ship->lookAt, d, .2);
250
251 // smooth roll
252 ship->roll += (roll?.015:.06)*(roll - ship->roll);
253 }
254
digger_control(Digger * digger,int game_mode)255 void digger_control (Digger* digger, int game_mode)
256 {
257 Ship *ship = SHIP(digger);
258
259 float twist_factor = 500;
260 float noise = .1;
261 float twist = 1 - 1/(1 + ship->pos[2]/twist_factor);
262 float max_vel[3] = {
263 MAX_VEL_X * twist,
264 MAX_VEL_Y * twist,
265 MAX_VEL_Z
266 };
267
268 if(
269 DRAND < twist*noise ||
270 ship->vel[1] > max_vel[1] ||
271 ship->vel[1] < -max_vel[1] ||
272 ship->vel[0] > max_vel[0] ||
273 ship->vel[0] < -max_vel[0]
274 )
275 {
276 if(DRAND>twist/2)
277 ship->lefton = DRAND<twist*noise ? DRAND_BIG % 2 :
278 ship->vel[1] < 0 || ship->vel[0] > +max_vel[0];
279
280 if(DRAND>twist/2)
281 ship->righton = DRAND<twist*noise ? DRAND_BIG % 2 :
282 ship->vel[1] < 0 || ship->vel[0] < -max_vel[0];
283
284 if (game_mode == ONE_BUTTON)
285 ship->lefton = ship->righton = (ship->lefton | ship->righton);
286 }
287
288 float scale = 1-MIN(1,log(1+ship->pos[2])/log(1+MIN_CAVE_RADIUS_DEPTH));
289 ship->radius = MIN_CAVE_RADIUS+(MAX_CAVE_RADIUS-MIN_CAVE_RADIUS)*scale;
290
291 // rooms
292 if (ship->pos[2] >= ROOM_START) {
293 int r = ((int)ship->pos[2])%ROOM_SPACING;
294 if (r < ROOM_LEN)
295 ship->radius *= 1+(ROOM_MUL-1)*(cos(M_PI*(r-ROOM_LEN/2)/ROOM_LEN));
296 }
297
298 // start falling
299 if (ship->pos[2] - ship->start < .33*SEGMENT_COUNT*SEGMENT_LEN)
300 ship->lefton = ship->righton = false;
301
302 digger->x_right_radius += DRAND - 0.5;
303 digger->y_top_radius += DRAND - 0.5;
304 digger->x_left_radius += DRAND - 0.5;
305 digger->y_bottom_radius += DRAND - 0.5;
306 }
307
autopilot(Game * game,float dt)308 void autopilot (Game* game, float dt)
309 {
310 Vec3 d;
311 SUB2 (d, game->player.pos, game->cave.centers[(game->cave.i+2)%SEGMENT_COUNT]);
312
313 bool* R = &game->player.righton;
314 bool* L = &game->player.lefton;
315
316 if (fabsf(d[1]) > fabsf(d[0])) {
317 *R = *L = (d[1] < 0) && (game->player.vel[1] < VELOCITY/8);
318 }
319 else {
320 *R = d[0] < 0 && (game->player.vel[0] < VELOCITY/8);
321 *L = !*R;
322 }
323 }
324
X(Cave * cave,int i,float xn,float yn,int k0,int k1)325 static float X (Cave* cave, int i, float xn, float yn, int k0, int k1)
326 {// used by collision()
327
328 float x1 = cave->segs[i][k0][0];
329 float y1 = cave->segs[i][k0][1];
330 float x2 = cave->segs[i][k1][0];
331 float y2 = cave->segs[i][k1][1];
332 float t = (yn - y2)/(y1 - y2);
333 if( t < 0 || t > 1 )
334 return 0;
335 float x = (x1 - xn)*t + (x2 - xn)*(1 - t);
336 return x;
337 }
338
collision(Cave * cave,Ship * ship)339 float collision (Cave* cave, Ship* ship)
340 {
341 float min = FLT_MAX;
342 Vec3 center = {0,0,0};
343
344 // This method counts the number of intersections of a semi-line
345 // starting from the point being checked against the poligon,
346 // that in this case is the segment of the cave.
347 // If the number of intersections is odd, the point is inside.
348
349 // In fact we'll check four points around the center of the ship
350 // (to simulate a diamond-shaped bounding box).
351
352 // The return value is the distance from the ship to the cave.
353
354 int intersection_count[4];
355 memset(intersection_count, 0, sizeof(intersection_count));
356
357
358 for(int n = 0, i = (cave->i + SEGMENT_COUNT) % SEGMENT_COUNT; // -1
359 n < 3; ++n, i = (i+1) % SEGMENT_COUNT ) // -1,0,1
360 {
361
362 for(int j = 0; j < SECTOR_COUNT; ++j ) {
363
364 // find center
365 ADD(center,cave->segs[i][j]);
366
367 // find minimum distance
368 // FIXME needs to be a line to point distance
369 Vec3 dist;
370 SUB2(dist, ship->pos, cave->segs[i][j]);
371 float len = LEN(dist);
372 if(len < min)
373 min = len;
374 }
375 }
376
377 // find if it's inside
378 // TODO take in account the radius toward the previous and next segment,
379 // like in the distance calculation before, and merge both loops
380 int i = cave->i;
381 {
382 for(int j = 0; j < SECTOR_COUNT; ++j ) {
383 int j0 = (j+0)%SECTOR_COUNT;
384 int j1 = (j+1)%SECTOR_COUNT;
385
386 // optimize
387 if(cave->segs[i][j0][0] < ship->pos[0]-ship->radius &&
388 cave->segs[i][j1][0] < ship->pos[0]-ship->radius)
389 continue;
390 if(cave->segs[i][j0][1] > ship->pos[1]+ship->radius &&
391 cave->segs[i][j1][1] > ship->pos[1]+ship->radius)
392 continue;
393 if(cave->segs[i][j0][1] < ship->pos[1]-ship->radius &&
394 cave->segs[i][j1][1] < ship->pos[1]-ship->radius)
395 continue;
396
397 if(X(cave, i, ship->pos[0] - ship->radius, ship->pos[1], j0, j1) > 0)
398 ++intersection_count[0];
399
400 if(X(cave, i, ship->pos[0], ship->pos[1] + ship->radius, j0, j1) > 0)
401 ++intersection_count[1];
402
403 if(X(cave, i, ship->pos[0] + ship->radius, ship->pos[1], j0, j1) > 0)
404 ++intersection_count[2];
405
406 if(X(cave, i, ship->pos[0], ship->pos[1] - ship->radius, j0, j1) > 0)
407 ++intersection_count[3];
408 }
409 }
410
411 SCALE(center,SECTOR_COUNT);
412 SUB2(ship->repulsion, center, ship->pos);
413 ship->repulsion[2] = 0;
414 NORM(ship->repulsion);
415
416 for(int i = 0; i < 4; ++i) {
417 if(intersection_count[i] % 2 == 0) {
418 return ship->dist = 0; // hit
419 }
420 }
421
422 ship->dist = min - 2*ship->radius;
423 return min; // miss
424 }
425
game_nocheat(Game * game)426 bool game_nocheat (Game *game)
427 {
428 return (game->player.start == 0 && game->start == 0);
429 }
430
game_score(Game * game)431 int game_score (Game *game)
432 {
433 return game->player.pos[2] / (game->mode==ONE_BUTTON?2:1);
434 }
435
game_score_update(Game * game)436 void game_score_update (Game *game)
437 {
438 score_update (&game->score, game_score(game), game_nocheat(game));
439 }
440
ship_hit(Ship * ship)441 float ship_hit (Ship *ship)
442 {
443 return 1-CLAMP(ship->dist / (2*SHIP_RADIUS),0,1);
444 }
445
446 float MAX_VEL[3] = { MAX_VEL_X, MAX_VEL_Y, MAX_VEL_Z };
ship_speed(Ship * ship)447 float ship_speed (Ship *ship)
448 {
449 return MIN(1,
450 log(1+MAX(0,LEN(ship->vel)-MAX_VEL_Z)) /
451 log(1+MAX(0,LEN(MAX_VEL )-MAX_VEL_Z)));
452 }
453
454 // vim600:fdm=syntax:fdn=1:
455