/*
This file is part of cave9.
cave9 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cave9 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cave9. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "vec.h"
#include "game.h"
#include "util.h"
#include "detrand.h"
#include "time.h"
const char* data_paths[] =
{
DATADIR,
NULL
};
float cave_len (Cave* cave)
{
int head = cave->i;
int tail = (head - 1 + SEGMENT_COUNT) % SEGMENT_COUNT;
return cave->segs[tail][0][2] - cave->segs[head][0][2];
}
static inline float generate_stalactites (Cave* cave, float mult_y, float cos_a)
{
static const float change_prob = 0.001;
static const float some_prob = 0.01;
static const float many_prob = 0.05;
float prob = 0;
float change = DRAND;
switch (cave->stalactites_status)
{
case STALACT_NONE:
if (change < change_prob)
cave->stalactites_status = STALACT_SOME;
return mult_y;
case STALACT_SOME:
prob = some_prob;
if (change < change_prob)
cave->stalactites_status = STALACT_NONE;
else if (change > (1 - change_prob))
cave->stalactites_status = STALACT_MANY;
break;
case STALACT_MANY:
prob = many_prob;
if (change < change_prob)
cave->stalactites_status = STALACT_SOME;
break;
default:
//WTF??
assert ("stalactites_status not possible" == 0);
}
return DRAND < prob ? 1 : mult_y;
}
void cave_gen (Cave* cave, Digger* digger)
{
Ship *ship = SHIP(digger);
// check if the ship advanced to the next segment
int i = (cave->i - 1 + SEGMENT_COUNT) % SEGMENT_COUNT;
if (ship->pos[2] > ship->start+1 && ship->pos[2]-SEGMENT_LEN < cave->segs[i][0][2])
return;
// invalidate GL list for this segment
cave->dirty[cave->i] = true;
const float B = 2/(WALL_MULT_MAX - WALL_MULT_MIN);
const float A = B*WALL_MULT_MAX - 1;
// generate new segment
for (i = 0; i < SECTOR_COUNT; ++i) {
float a = M_PI_2+(i-1)*M_PI*2/SECTOR_COUNT;
float r = ship->radius;
float cos_a = cos(a);
float sin_a = sin(a);
float mult_x = (cos_a > 0)? digger->y_top_radius : digger->y_bottom_radius;
float mult_y = (sin_a > 0)? digger->x_left_radius: digger->x_right_radius;
// clamp the multipliers to [mult_min .. mult_max]
mult_x = (A + sin(mult_x))/B;
mult_y = (A + sin(mult_y))/B;
if (cave->stalactites_status != STALACT_DISABLED)
mult_y = generate_stalactites(cave, mult_y, cos_a);
SET(cave->segs[cave->i][i],
ship->pos[0] + (r * mult_x * cos_a) + 2 * DRAND,
ship->pos[1] + (r * mult_y * sin_a) + 2 * DRAND,
ship->pos[2]
);
}
COPY (cave->centers[cave->i], ship->pos);
// increment segment circular pointer
cave->i = (cave->i + 1) % SEGMENT_COUNT;
// place monolith on rooms
if (ship->pos[2] > cave->monolith_pos[2]+ROOM_SPACING && ship->pos[2] > ROOM_START)
{
cave->monolith_pos[0] = ship->pos[0];
cave->monolith_pos[1] = ship->pos[1];
cave->monolith_pos[2] = ROOM_LEN/2 + ROOM_SPACING*(int)(ship->pos[2]/ROOM_SPACING);
cave->monolith_yaw = atan2 (ship->vel[0], ship->vel[2]);
}
}
static void cave_init (Cave* cave, Digger* digger, Args* args)
{
memset (cave, 0, sizeof(Cave));
int game_mode = TWO_BUTTONS;
if (args != NULL) {
game_mode = args->game_mode;
cave->stalactites_status = args->stalactites?STALACT_NONE:STALACT_DISABLED;
}
Ship *ship = SHIP(digger);
cave->i = 0;
do {
digger_control(digger, game_mode);
ship_move(ship, 1./50);
cave_gen(cave, digger);
}
while(cave->i != 0);
}
static void ship_init (Ship* ship, float radius)
{
SET (ship->pos, 0,0,ship->start);
SET (ship->vel, 0,0,VELOCITY);
SET (ship->lookAt, 0,0,VELOCITY);
ship->roll = 0;
ship->radius = radius;
ship->dist = FLT_MAX;
SET (ship->repulsion,0,1,0);
ship->lefton = ship->righton = false;
}
static void digger_init(Digger *digger, float radius)
{
ship_init(SHIP(digger), radius);
digger->x_left_radius = 0.0;
digger->x_right_radius = 0.0;
digger->y_top_radius = 0.0;
digger->y_bottom_radius = 0.0;
}
void fast_forward (Game* game)
{
while ((game->digger.ship.pos[2] - cave_len(&game->cave)) / (game->mode==ONE_BUTTON?2:1) < game->start)
{
digger_control (&game->digger, game->mode);
cave_gen (&game->cave, &game->digger);
ship_move (SHIP(&game->digger), 0.05);
}
// put the player in the middle of the cave
COPY(game->player.pos, game->cave.centers[(game->cave.i + 1) % SEGMENT_COUNT]);
game->player.vel[0] += (game->cave.centers[(game->cave.i + 2) % SEGMENT_COUNT][0] -
game->cave.centers[(game->cave.i + 1) % SEGMENT_COUNT][0]) * 20;
game->player.vel[1] += (game->cave.centers[(game->cave.i + 2) % SEGMENT_COUNT][1] -
game->cave.centers[(game->cave.i + 1) % SEGMENT_COUNT][1]) * 20;
// the y-component of the vector should be biased towards upwards movement,
// as reverting downwards-movement is *very* hard.
if (game->player.vel[1] < 0)
game->player.vel[1] *= 0.1;
COPY(game->player.lookAt, game->player.vel);
}
void game_init (Game* game, Args* args)
{
assert(args != NULL);
game->mode = args->game_mode;
game->monoliths = args->monoliths;
game->caveseed = args->caveseed;
if (game->caveseed != 0)
{
game->start = args->start;
detsrand(game->caveseed);
} else {
game->player.start = game->digger.ship.start = (float)args->start;
game->start = 0;
detsrand(time(NULL));
}
ship_init (&game->player, SHIP_RADIUS);
digger_init (&game->digger, MAX_CAVE_RADIUS);
cave_init (&game->cave, &game->digger, args);
if (game->start)
fast_forward(game);
score_init (&game->score, args, game->caveseed, game->monoliths * 2/* + game->stalactites*/); // XXX uncomment this, once stalactites are implemented
}
void ship_move (Ship* ship, float dt)
{
float acc = THRUST*dt;
int roll = 0;
if(ship->lefton) {
Vec3 leftup = { -acc, acc/2, 0 };
ADD(ship->vel, leftup);
roll--;
}
if(ship->righton) {
Vec3 rightup = { +acc, acc/2, 0 };
ADD(ship->vel, rightup);
roll++;
}
ship->vel[1] -= GRAVITY*dt;
ADDSCALE(ship->pos, ship->vel, dt);
// smooth lookAt
Vec3 d;
SUB2 (d, ship->vel, ship->lookAt);
ADDSCALE (ship->lookAt, d, .2);
// smooth roll
ship->roll += (roll?.015:.06)*(roll - ship->roll);
}
void digger_control (Digger* digger, int game_mode)
{
Ship *ship = SHIP(digger);
float twist_factor = 500;
float noise = .1;
float twist = 1 - 1/(1 + ship->pos[2]/twist_factor);
float max_vel[3] = {
MAX_VEL_X * twist,
MAX_VEL_Y * twist,
MAX_VEL_Z
};
if(
DRAND < twist*noise ||
ship->vel[1] > max_vel[1] ||
ship->vel[1] < -max_vel[1] ||
ship->vel[0] > max_vel[0] ||
ship->vel[0] < -max_vel[0]
)
{
if(DRAND>twist/2)
ship->lefton = DRANDvel[1] < 0 || ship->vel[0] > +max_vel[0];
if(DRAND>twist/2)
ship->righton = DRANDvel[1] < 0 || ship->vel[0] < -max_vel[0];
if (game_mode == ONE_BUTTON)
ship->lefton = ship->righton = (ship->lefton | ship->righton);
}
float scale = 1-MIN(1,log(1+ship->pos[2])/log(1+MIN_CAVE_RADIUS_DEPTH));
ship->radius = MIN_CAVE_RADIUS+(MAX_CAVE_RADIUS-MIN_CAVE_RADIUS)*scale;
// rooms
if (ship->pos[2] >= ROOM_START) {
int r = ((int)ship->pos[2])%ROOM_SPACING;
if (r < ROOM_LEN)
ship->radius *= 1+(ROOM_MUL-1)*(cos(M_PI*(r-ROOM_LEN/2)/ROOM_LEN));
}
// start falling
if (ship->pos[2] - ship->start < .33*SEGMENT_COUNT*SEGMENT_LEN)
ship->lefton = ship->righton = false;
digger->x_right_radius += DRAND - 0.5;
digger->y_top_radius += DRAND - 0.5;
digger->x_left_radius += DRAND - 0.5;
digger->y_bottom_radius += DRAND - 0.5;
}
void autopilot (Game* game, float dt)
{
Vec3 d;
SUB2 (d, game->player.pos, game->cave.centers[(game->cave.i+2)%SEGMENT_COUNT]);
bool* R = &game->player.righton;
bool* L = &game->player.lefton;
if (fabsf(d[1]) > fabsf(d[0])) {
*R = *L = (d[1] < 0) && (game->player.vel[1] < VELOCITY/8);
}
else {
*R = d[0] < 0 && (game->player.vel[0] < VELOCITY/8);
*L = !*R;
}
}
static float X (Cave* cave, int i, float xn, float yn, int k0, int k1)
{// used by collision()
float x1 = cave->segs[i][k0][0];
float y1 = cave->segs[i][k0][1];
float x2 = cave->segs[i][k1][0];
float y2 = cave->segs[i][k1][1];
float t = (yn - y2)/(y1 - y2);
if( t < 0 || t > 1 )
return 0;
float x = (x1 - xn)*t + (x2 - xn)*(1 - t);
return x;
}
float collision (Cave* cave, Ship* ship)
{
float min = FLT_MAX;
Vec3 center = {0,0,0};
// This method counts the number of intersections of a semi-line
// starting from the point being checked against the poligon,
// that in this case is the segment of the cave.
// If the number of intersections is odd, the point is inside.
// In fact we'll check four points around the center of the ship
// (to simulate a diamond-shaped bounding box).
// The return value is the distance from the ship to the cave.
int intersection_count[4];
memset(intersection_count, 0, sizeof(intersection_count));
for(int n = 0, i = (cave->i + SEGMENT_COUNT) % SEGMENT_COUNT; // -1
n < 3; ++n, i = (i+1) % SEGMENT_COUNT ) // -1,0,1
{
for(int j = 0; j < SECTOR_COUNT; ++j ) {
// find center
ADD(center,cave->segs[i][j]);
// find minimum distance
// FIXME needs to be a line to point distance
Vec3 dist;
SUB2(dist, ship->pos, cave->segs[i][j]);
float len = LEN(dist);
if(len < min)
min = len;
}
}
// find if it's inside
// TODO take in account the radius toward the previous and next segment,
// like in the distance calculation before, and merge both loops
int i = cave->i;
{
for(int j = 0; j < SECTOR_COUNT; ++j ) {
int j0 = (j+0)%SECTOR_COUNT;
int j1 = (j+1)%SECTOR_COUNT;
// optimize
if(cave->segs[i][j0][0] < ship->pos[0]-ship->radius &&
cave->segs[i][j1][0] < ship->pos[0]-ship->radius)
continue;
if(cave->segs[i][j0][1] > ship->pos[1]+ship->radius &&
cave->segs[i][j1][1] > ship->pos[1]+ship->radius)
continue;
if(cave->segs[i][j0][1] < ship->pos[1]-ship->radius &&
cave->segs[i][j1][1] < ship->pos[1]-ship->radius)
continue;
if(X(cave, i, ship->pos[0] - ship->radius, ship->pos[1], j0, j1) > 0)
++intersection_count[0];
if(X(cave, i, ship->pos[0], ship->pos[1] + ship->radius, j0, j1) > 0)
++intersection_count[1];
if(X(cave, i, ship->pos[0] + ship->radius, ship->pos[1], j0, j1) > 0)
++intersection_count[2];
if(X(cave, i, ship->pos[0], ship->pos[1] - ship->radius, j0, j1) > 0)
++intersection_count[3];
}
}
SCALE(center,SECTOR_COUNT);
SUB2(ship->repulsion, center, ship->pos);
ship->repulsion[2] = 0;
NORM(ship->repulsion);
for(int i = 0; i < 4; ++i) {
if(intersection_count[i] % 2 == 0) {
return ship->dist = 0; // hit
}
}
ship->dist = min - 2*ship->radius;
return min; // miss
}
bool game_nocheat (Game *game)
{
return (game->player.start == 0 && game->start == 0);
}
int game_score (Game *game)
{
return game->player.pos[2] / (game->mode==ONE_BUTTON?2:1);
}
void game_score_update (Game *game)
{
score_update (&game->score, game_score(game), game_nocheat(game));
}
float ship_hit (Ship *ship)
{
return 1-CLAMP(ship->dist / (2*SHIP_RADIUS),0,1);
}
float MAX_VEL[3] = { MAX_VEL_X, MAX_VEL_Y, MAX_VEL_Z };
float ship_speed (Ship *ship)
{
return MIN(1,
log(1+MAX(0,LEN(ship->vel)-MAX_VEL_Z)) /
log(1+MAX(0,LEN(MAX_VEL )-MAX_VEL_Z)));
}
// vim600:fdm=syntax:fdn=1: