You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1908 lines
54 KiB
1908 lines
54 KiB
#include "game.h" |
|
#include "draw.h" |
|
#include "audio.h" |
|
#include "types.h" |
|
#include <SDL_mixer.h> |
|
#include <SDL_scancode.h> |
|
#include <SDL_timer.h> |
|
#include <SDL_ttf.h> |
|
#include <SDL_mutex.h> |
|
|
|
#define FLOOR_THICKNESS 200 |
|
#define MAX_ROPE_GRAB_LEN 80000 |
|
#define MIN_PHYSICS_STEP 6.0 |
|
|
|
#define TIMESTEP_LENGTH 3.0 |
|
|
|
#define BREAKPOINT *(int *)0 = 1; |
|
|
|
GlobWorld world; |
|
|
|
player_st *glob_player; |
|
SDL_Renderer *debug_ren; |
|
|
|
|
|
bool in_game; |
|
bool game_paused = false; |
|
extern bool mute; |
|
int level; |
|
int quality = 100; |
|
long level_time; |
|
|
|
int get_floor_ceiling(); |
|
|
|
#ifdef SCORE_SYSTEM |
|
struct sg_times_list save_times_lst; |
|
#endif |
|
|
|
/* object that draw.c watches */ |
|
struct draw_watcher draw_watch = {}; |
|
/* object that handles ui interactions in game.c and which draw watches */ |
|
struct textbox_info textboxes[1]; |
|
struct button mute_button; |
|
|
|
|
|
struct ui_state gameui = {}; |
|
|
|
struct timespec last_tick; |
|
double time_remaining = 0; |
|
struct timespec get_now(); |
|
|
|
/* array of all the things in the world and their kinds */ |
|
//world_thing *world; |
|
|
|
void startgame(SDL_Renderer * ren) ; |
|
|
|
void process_keydown(SDL_Keysym key); |
|
void process_keyup(SDL_Keysym key); |
|
|
|
void add_to_world(world_thing thing); |
|
void set_motor_timeout(Body *thing, int motorID, uint32_t timeout); |
|
void set_motor_status(Body *thing, int motorID, bool run); |
|
void stop_pull_rope(void); |
|
|
|
player_st player; |
|
|
|
// local |
|
void set_motor_newtons(Body *thing, int motorID, double x, double y); |
|
void set_motor_max_velocity(Body *thing, int motorID, double max); |
|
void get_new_physics(Body **phys); |
|
void ratcheted_winch_motor_update(Motor* motor); |
|
void winch_motor_update (struct motorstruct *motor); |
|
void load_level(); |
|
|
|
void reset_textbox(struct textbox_info *tb) { |
|
tb->text_input_bufpos = 0; |
|
memset(tb->text_input, 0, sizeof(tb->text_input)); |
|
} |
|
|
|
void write_to_textbox(struct textbox_info *tb, char c) { |
|
if (tb->text_input_bufpos >= sizeof(tb->text_input)) { |
|
return; |
|
} |
|
|
|
tb->text_input[tb->text_input_bufpos++] = c; |
|
} |
|
|
|
void delete_from_textbox(struct textbox_info *tb) { |
|
if (tb->text_input_bufpos > 0) |
|
tb->text_input[--tb->text_input_bufpos] = 0; |
|
} |
|
|
|
// move the collision poly to the position of the player |
|
void default_update_collision_poly(Body *body) { |
|
for (int i=0; i < body->collision_poly_size; i++) { |
|
|
|
double x = body->collision_shape[i].x + body->position.x; |
|
double y = body->collision_shape[i].y + body->position.y; |
|
|
|
body->collision_poly[i].x = x; |
|
body->collision_poly[i].y = y; |
|
} |
|
} |
|
|
|
void new_level_tb_close_callback(struct textbox_info*textbox, void*callback) { |
|
for (int i = 0; i < textbox->text_input_bufpos; i++) { |
|
if (textbox->text_input[i] < '0' || textbox->text_input[i] > '9') |
|
return; |
|
} |
|
|
|
int lid = atoi(textbox->text_input); |
|
|
|
gameui.goto_new_level = lid; |
|
|
|
} |
|
|
|
void level_timer_update(bool reset) { |
|
static int start_point = 0; |
|
long now = SDL_GetTicks(); |
|
if (reset) { |
|
level_time = 0; |
|
start_point = now; |
|
} else { |
|
level_time = now - start_point; |
|
} |
|
} |
|
|
|
|
|
int long_comparator(const void *a, const void *b, void *non) { |
|
long A = *(long *)a; |
|
long B = *(long *)b; |
|
|
|
return A - B; |
|
} |
|
|
|
int win_long_comparator(void *context_unused, const void *a, const void *b) { |
|
long A = *(long *)a; |
|
long B = *(long *)b; |
|
|
|
return A - B; |
|
} |
|
|
|
|
|
#ifdef SCORE_SYSTEM |
|
void sort_times(void) { |
|
#ifdef __linux__ |
|
qsort_r(save_times_lst.times, save_times_lst.size, sizeof(long), long_comparator, NULL); |
|
#elif WIN32 |
|
qsort_s(save_times_lst.times, save_times_lst.size, sizeof(long), win_long_comparator, NULL); |
|
#endif |
|
} |
|
|
|
long get_best_time(void) { |
|
save_times_lst.sort(); |
|
if (save_times_lst.size > 0) |
|
return save_times_lst.times[0]; |
|
return -1; |
|
} |
|
|
|
|
|
int load_score_times(char *filename) { |
|
|
|
char *fn; |
|
char lvl_str[250]; |
|
snprintf(lvl_str, 250, "%d", level); |
|
int fnlen = strlen(filename) + strlen(lvl_str) + 3; |
|
fn = malloc(fnlen); |
|
snprintf(fn, fnlen, "%s-%s", filename, lvl_str); |
|
|
|
//printf("%d, lvl_str %s fn %s\n", level, lvl_str, fn); |
|
|
|
FILE *f = fopen(fn, "r"); |
|
// unload existing score times |
|
if (save_times_lst.times) { |
|
write_times(); |
|
free(save_times_lst.times); |
|
free(save_times_lst.filename); |
|
} |
|
|
|
if (f) { |
|
// get file size |
|
fseek(f, 0L, SEEK_END); |
|
long sz = ftell(f); |
|
fseek(f, 0L, SEEK_SET); |
|
char *text = calloc((sz + 1), (sizeof(char))); |
|
|
|
// read file |
|
fread(text,sizeof(char),sz, f); |
|
fclose(f); |
|
|
|
int count = 1; |
|
for (int i = 0; i < strlen(text); i++) { |
|
if (text[i] == '\n') { |
|
count++; |
|
} |
|
} |
|
|
|
long *times = malloc(count * sizeof(long)); |
|
char *saveptr; |
|
|
|
// extract times |
|
char * token = strtok_r(text, "\n", &saveptr); |
|
int i = 0; |
|
while (token != NULL) { |
|
if (strlen(token) > 2) { |
|
times[i] = atol(token); |
|
i++; |
|
} |
|
token = strtok_r(NULL, "\n", &saveptr); |
|
} |
|
/* here somweher? */ |
|
|
|
struct sg_times_list stl = {.size=i, .capacity = count, .times=times, .sort=sort_times}; |
|
save_times_lst = stl; |
|
sort_times(); |
|
free(text); |
|
} else { |
|
perror("loading times"); |
|
save_times_lst = (struct sg_times_list){.size=0, .capacity = 5, .times=calloc(5, sizeof(long)), .sort=sort_times}; |
|
} |
|
|
|
save_times_lst.filename = fn; |
|
return 0; |
|
} |
|
|
|
int add_time(long time) { |
|
if (save_times_lst.capacity == save_times_lst.size + 5) { |
|
long newcap = save_times_lst.capacity * 2; |
|
save_times_lst.times = realloc(save_times_lst.times, newcap); |
|
save_times_lst.capacity = newcap; |
|
} |
|
save_times_lst.times[save_times_lst.size] = time; |
|
save_times_lst.size++; |
|
|
|
return 0; |
|
} |
|
|
|
int write_times(void) { |
|
FILE *f = fopen(save_times_lst.filename, "w"); |
|
if (!f) { |
|
perror("Saving game"); |
|
return 1; |
|
} |
|
for (int i = 0; i < save_times_lst.size; i++) { |
|
fprintf(f, "%ld\n", save_times_lst.times[i]); |
|
} |
|
fclose(f); |
|
return 0; |
|
} |
|
|
|
#endif // SCORE_SYSTEM |
|
|
|
|
|
// collision poly size must be 2x shape_size + 1 |
|
void cast_update_collision_poly(Body *body) { |
|
for (int i=0; i < body->collision_shape_size; i++) { |
|
double x = body->collision_shape[i].x + body->position.x; |
|
double y = body->collision_shape[i].y + body->position.y; |
|
|
|
body->collision_poly[i].x = x; |
|
body->collision_poly[i].y = y; |
|
} |
|
for (int i=body->collision_shape_size - 1; i >= 0; i--) { |
|
double x = body->collision_shape[i].x + body->next_position.x; |
|
double y = body->collision_shape[i].y + body->next_position.y; |
|
int k = body->collision_shape_size + i; |
|
|
|
body->collision_poly[k].x = x; |
|
body->collision_poly[k].y = y; |
|
} |
|
|
|
/*int i=body->collision_shape_size - 1;*/ |
|
/*double x = body->collision_shape[i].x + body->next_position.x;*/ |
|
/*double y = body->collision_shape[i].y + body->next_position.y;*/ |
|
|
|
/*body->collision_poly[body->collision_shape_size - 1 + i].x = x; */ |
|
/*body->collision_poly[body->collision_shape_size - 1 + i].y = y; */ |
|
|
|
|
|
/*body->collision_poly[(body->collision_poly_size - 1) * 2 + 1] = body->collision_poly[(body->collision_poly_size - 1) * 2 + 1];*/ |
|
/*body->collision_poly[(body->collision_poly_size - 1) * 2 + 2] = body->collision_poly[(body->collision_poly_size - 1)];*/ |
|
} |
|
|
|
void default_motor_curve(Motor *motor) { |
|
// constant |
|
return; |
|
} |
|
|
|
world_thing world_getter(int i) { |
|
world_thing *item = arlst_get(&world.items, i); |
|
return *item; |
|
} |
|
|
|
|
|
FloorPoly* generate_floor_simple(int num_polys, bool extend_down, int st_height) { |
|
FloorPoly *floor = calloc(num_polys, sizeof(FloorPoly)); |
|
Vect last, next; |
|
last.x = 10; |
|
last.y = st_height; |
|
|
|
int n = 1; |
|
for (int i = 0; i < num_polys; i++) { |
|
double run = (get_rand(&n) % 900); |
|
double rise = (get_rand(&n) % 100) - (get_rand(&n) % 100); |
|
next.x = last.x + run; |
|
next.y = last.y + rise; |
|
|
|
FloorPoly poly; |
|
poly.left = last; |
|
poly.right = next; |
|
get_new_physics(&poly.physics); |
|
|
|
poly.physics->position = last; |
|
|
|
int offset = extend_down ? FLOOR_THICKNESS : -FLOOR_THICKNESS; |
|
|
|
poly.physics->collision_poly_size = 4; |
|
poly.physics->collision_poly = calloc(4, sizeof(Vect)); |
|
poly.physics->collision_shape = calloc(4, sizeof(Vect)); |
|
|
|
poly.physics->collision_shape[0].x = 0; |
|
poly.physics->collision_shape[0].y = 0; |
|
|
|
poly.physics->collision_shape[1].x = run; |
|
poly.physics->collision_shape[1].y = rise; |
|
|
|
poly.physics->collision_shape[2].x = run; |
|
poly.physics->collision_shape[2].y = rise+offset; |
|
|
|
poly.physics->collision_shape[3].x = 0; |
|
poly.physics->collision_shape[3].y = rise+offset; |
|
|
|
default_update_collision_poly(poly.physics); |
|
|
|
last = next; |
|
floor[i] = poly; |
|
} |
|
|
|
return floor; |
|
} |
|
|
|
// @param uninitialised Body pointer |
|
// @result: malloc and configure a physics thing pointer |
|
void get_new_physics(Body **phys) { |
|
static int uid = 0; |
|
|
|
/* physics */ |
|
Body * physics = malloc(sizeof(Body)); |
|
memset(physics, 0, sizeof(Body)); |
|
|
|
// give it the next uid |
|
physics->uid = uid++; |
|
|
|
physics->dynamics = false; |
|
physics->glob_gravity = false; |
|
physics->glob_friction = 0.0000; |
|
|
|
physics->obj_mass = 100; |
|
//kgs |
|
|
|
physics->motors = malloc(sizeof(Motor) * 100); |
|
memset(physics->motors, 0, sizeof(Motor) * 100); |
|
physics->max_motors = 100; |
|
physics->num_motors = 0; |
|
|
|
// gravity |
|
add_motor(physics, 0.0, 0.0); |
|
// friction |
|
add_motor(physics, 0.0, 0.0); |
|
|
|
clock_gettime(CLOCK_MONOTONIC, &physics->last_advance_time); |
|
|
|
physics->updateCollisionPoly = default_update_collision_poly; |
|
*phys = physics; |
|
return; |
|
} |
|
|
|
/*void updatePlayerCollision(Body *physics) {*/ |
|
/*physics->collision_poly[0].x = physics->x_pos;*/ |
|
/*physics->collision_poly[0].y = physics->y_pos;*/ |
|
/*}*/ |
|
|
|
|
|
int string_update_fixed(String *string) { |
|
return 0; |
|
} |
|
|
|
int string_set_end(String *string, Vect *setting) { |
|
string->end_point = *setting; |
|
return 0; |
|
} |
|
|
|
String *get_fixed_strings(int number) { |
|
String *strings = calloc(number, sizeof(String)); |
|
for (int i = 0; i < number; i++) { |
|
|
|
strings[i].attached = false; |
|
|
|
// takes self (String) and does nothing. |
|
strings[i].update_end_point = string_update_fixed; |
|
|
|
// takes self and vect to set string end point |
|
strings[i].set_end_point = string_set_end; |
|
} |
|
return strings; |
|
} |
|
|
|
player_st get_player(int x, int y) { |
|
/* creates player at given postion and zeroes physics */ |
|
|
|
// player |
|
player_st player; |
|
memset(&player, 0, sizeof(player)); |
|
player.max_walking_speed = 100; |
|
|
|
// physics settings |
|
get_new_physics(&player.physics); |
|
player.physics->dynamics = true; |
|
player.physics->lock = SDL_CreateMutex(); |
|
|
|
player.physics->position.x = x; |
|
player.physics->position.y = y; |
|
player.physics->next_position = player.physics->position; |
|
player.physics->obj_elasticity = 0.5; |
|
player.physics->obj_friction = 0.2; |
|
|
|
// friction (not in use) |
|
player.physics->glob_friction = 40; // drag coef * area |
|
|
|
player.physics->collision_poly_size = 4 + 4; |
|
player.physics->collision_poly = calloc(player.physics->collision_poly_size, sizeof(Vect)); |
|
|
|
player.physics->collision_shape_size = 4; |
|
player.physics->collision_shape = calloc(4, sizeof(Vect)); |
|
|
|
player.physics->updateCollisionPoly = cast_update_collision_poly; |
|
|
|
// swing rope |
|
player.physics->num_strings = 1; |
|
player.physics->max_strings = 1; |
|
player.physics->strings = get_fixed_strings(1); |
|
player.physics->strings->max_length = 210; |
|
|
|
Vect *rect = player.physics->collision_shape; |
|
rect[0].x = -10; rect[0].y = -10; |
|
rect[1].x = 10; rect[1].y = -10; |
|
rect[2].x = 10; rect[2].y = 10; |
|
rect[3].x = -10; rect[3].y = 10; |
|
|
|
// gravity |
|
set_motor_newtons(player.physics, M_GRAVITY, 0.0, |
|
player.physics->obj_mass * 5); |
|
set_motor_status(player.physics, M_GRAVITY, true); |
|
set_motor_timeout(player.physics, M_GRAVITY, 99999999); |
|
|
|
// walking motor |
|
add_motor(player.physics, 0.0, player.physics->obj_mass * 9.81); |
|
set_motor_max_velocity(player.physics, M_PLAYER_WALK, 5); // has to be > grav |
|
set_motor_max_velocity(player.physics, M_GRAVITY, 5); |
|
|
|
// winch motor for string |
|
add_motor(player.physics, 0.0, 0); |
|
player.physics->motors[M_WINCH].update_motor = ratcheted_winch_motor_update; |
|
|
|
return (player); |
|
} |
|
|
|
bool point_point_colcheck(Vect one, Vect two) { |
|
if (one.x == two.x && one.y == two.y) { |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
typedef struct { |
|
Vect one; |
|
Vect two; |
|
} Collision; |
|
|
|
bool point_line_colcheck(Vect line[2], Vect point) { |
|
// point is outside the rectangle made by the line |
|
if ((point.x > line[0].x && point.x > line[1].x) |
|
|| (point.x < line[0].x && point.x < line[0].x) |
|
|| (point.y > line[0].y && point.y > line[0].y) |
|
|| (point.y < line[0].y && point.y < line[0].y) |
|
){ |
|
return false; |
|
} |
|
|
|
double m = (double)(line[1].y - line[0].y) / (double)(line[1].x - line[0].x); |
|
double c = (double)(line[0].y - (m * line[0].x)); |
|
|
|
double y = point.x * m + c; |
|
|
|
// point is in the line +- 1 |
|
if ((int)y == point.y || ((int)y < point.y && (int)y + 1 > point.y)) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
double *project_col_poly(Body *shape, Vect V) { |
|
double *proj = calloc(shape->collision_poly_size, sizeof(double)); |
|
for (int i = 0; i < shape->collision_poly_size; i++) { |
|
Vect point; |
|
point.x = shape->collision_poly[i].x; |
|
point.y = shape->collision_poly[i].y; |
|
//double mag = vect_mag(point); |
|
// printf("point: %f %f mag: %f\n", point.x, point.y, vect_mag(point)); |
|
// printf("Vectp: %f %f mag: %f\n", V.x, V.y, vect_mag(V)); |
|
proj[i] = vect_scalar_projection(point, V); |
|
} |
|
double min, max; |
|
max = -99999999; |
|
min = 99999999; |
|
for (int i = 0; i < shape->collision_poly_size; i++) { |
|
if (proj[i] > max) { |
|
max = proj[i]; |
|
} |
|
if (proj[i] < min) { |
|
min = proj[i]; |
|
} |
|
} |
|
double *res = calloc(2, sizeof(double)); |
|
res[0] = min; |
|
res[1] = max; |
|
|
|
free(proj); |
|
return res; |
|
} |
|
|
|
Vect get_normal(Vect start, Vect end) { |
|
Vect norm; |
|
double x = (end.x - start.x); |
|
double y = (end.y - start.y); |
|
norm.x = -y; |
|
norm.y = x; |
|
double len = vect_mag(norm); |
|
norm.x /= len; |
|
norm.y /= len; |
|
|
|
return norm; |
|
} |
|
|
|
// reference: |
|
// http://www.dyn4j.org/2010/01/sat/#sat-inter |
|
// |
|
// TODO: Maybe avoid calculating the centre of the boxes; have body->position |
|
// be the centre and translate the collision box to be around that. It does |
|
// make placing a little more complex but no bother. |
|
bool sat_collision_check(Body *one, Body *two, Vect *translation) { |
|
int num_axes = one->collision_poly_size + two->collision_poly_size; |
|
Vect *axes = calloc(num_axes, sizeof(Vect)); |
|
|
|
Vect end; |
|
Vect start = one->collision_poly[0]; |
|
Vect one_position; |
|
Vect two_position; |
|
|
|
two_position = two->collision_poly[0]; |
|
one_position = one->collision_poly[0]; |
|
|
|
for (int i = 1; i < one->collision_poly_size; i++) { |
|
one_position = vect_add(one_position, one->collision_poly[i]); |
|
end = one->collision_poly[i]; |
|
axes[i-1] = get_normal(start, end); |
|
start = end; |
|
} |
|
|
|
end = one->collision_poly[0]; |
|
axes[one->collision_poly_size - 1] = get_normal(start, end); |
|
|
|
start = two->collision_poly[0]; |
|
for (int i = 1; i < two->collision_poly_size; i++) { |
|
two_position = vect_add(two_position, two->collision_poly[i]); |
|
end = two->collision_poly[i]; |
|
axes[i - 1 + one->collision_poly_size] = get_normal(start, end); |
|
start = end; |
|
} |
|
|
|
end = two->collision_poly[0]; |
|
axes[two->collision_poly_size + one->collision_poly_size - 1] = get_normal(start, end); |
|
|
|
double *proj_one, *proj_two; |
|
proj_one = proj_two = 0; |
|
|
|
Vect min_axis; |
|
double min_overlap = 99999999; |
|
for (int i = 0; i < num_axes; i++) { |
|
// project |
|
if (proj_one) { |
|
free(proj_one); |
|
} |
|
if (proj_two) { |
|
free(proj_two); |
|
} |
|
proj_one = project_col_poly(one, axes[i]); |
|
proj_two = project_col_poly(two, axes[i]); |
|
|
|
if ((proj_one[0] >= proj_two[1]) |
|
|| (proj_two[0] >= proj_one[1])) { |
|
free(axes); |
|
free(proj_one); |
|
free(proj_two); |
|
return false; |
|
} else { |
|
double overlap; |
|
double left = proj_one[1] < proj_two[1] ? proj_one[1] : proj_two[1]; |
|
double right = proj_one[0] > proj_two[0] ? proj_one[0] : proj_two[0]; |
|
overlap = left - right; |
|
|
|
// one of the shapes is contained |
|
if ((overlap > (proj_one[1] - proj_one[0]) |
|
|| (overlap > (proj_two[1] - proj_two[0])))) { |
|
|
|
double min = proj_one[0] - proj_two[0]; |
|
double max = proj_one[1] - proj_two[1]; |
|
|
|
min = min < 0 ? -min : min; |
|
max = max < 0 ? -max : max; |
|
|
|
overlap += min < max ? min : max; |
|
|
|
} |
|
|
|
if (overlap < min_overlap && overlap > 0) { |
|
min_overlap = overlap; |
|
min_axis = axes[i]; |
|
} |
|
} |
|
} |
|
|
|
// flip the MTV if it is pointing INTO the object |
|
Vect trans; |
|
trans.x = min_overlap * min_axis.x; |
|
trans.y = min_overlap * min_axis.y; |
|
|
|
// https://gamedev.stackexchange.com/questions/27596/implementing-separating-axis-theorem-sat-and-minimum-translation-vector-mtv/27629#27629 |
|
Vect direction; |
|
one_position.x /= one->collision_poly_size; |
|
one_position.y /= one->collision_poly_size; |
|
two_position.x /= two->collision_poly_size; |
|
two_position.y /= two->collision_poly_size; |
|
|
|
/*printf("ONE POS: %f %f", one_position.x, one_position.y);*/ |
|
/*printf("TWO POS: %f %f", two_position.x, one_position.y);*/ |
|
|
|
direction.x = one_position.x - two_position.x; |
|
direction.y = one_position.y - two_position.y; |
|
|
|
double dot = vect_scalar_projection(trans, direction); |
|
|
|
/*printf("DIRECTION: %f %f\n\n", direction.x, direction.y);*/ |
|
/*printf("DOT: %f\n\n", dot);*/ |
|
|
|
if (dot > 0) { |
|
trans = vect_scalar(trans, -1); |
|
} |
|
|
|
// return the MTV |
|
*translation = trans; |
|
|
|
|
|
free(axes); |
|
free(proj_one); |
|
free(proj_two); |
|
return true; |
|
} |
|
|
|
bool check_collision(Body *one, Body *two, Vect *translation) { |
|
int onesize = one->collision_poly_size; |
|
int twosize = two->collision_poly_size; |
|
|
|
// point-point |
|
if (one->collision_poly_size == 1 && two->collision_poly_size == 1) { |
|
if (point_point_colcheck(one->collision_poly[0], two->collision_poly[0])) { |
|
return true; |
|
} |
|
} |
|
// point-line |
|
if ((onesize == 1 || twosize == 1) && (onesize == 2 || twosize == 2)) { |
|
Vect line[2]; |
|
Vect point; |
|
if (onesize > twosize) { |
|
line[0] = one->collision_poly[0]; |
|
line[1] = one->collision_poly[1]; |
|
point = two->collision_poly[0]; |
|
} else { |
|
line[0] = two->collision_poly[0]; |
|
line[1] = two->collision_poly[1]; |
|
point = one->collision_poly[0]; |
|
} |
|
|
|
return point_line_colcheck(line, point); |
|
} |
|
|
|
// line-line |
|
if ((onesize == 2 && twosize == 2)) { |
|
return false; |
|
} |
|
|
|
// point-poly |
|
if ((onesize == 1 || twosize == 1) && (onesize > 2 || twosize > 2)) { |
|
return false; |
|
} |
|
|
|
// line-poly |
|
if ((onesize == 2 || twosize == 2) && (onesize > 2 || twosize > 2)) { |
|
return false; |
|
} |
|
|
|
// poly-poly |
|
if ((onesize > 2 && twosize > 2)) { |
|
return sat_collision_check(one, two, translation); |
|
|
|
} |
|
} |
|
|
|
void destroy_physics_body(Body *b) { |
|
// collisions |
|
Vect *collision_poly; |
|
Vect *collision_shape; |
|
int collision_poly_size; |
|
int collision_shape_size; |
|
void (*updateCollisionPoly)(struct BodyStruct *); |
|
// applying forces |
|
int num_strings; |
|
int max_strings; |
|
String *strings; |
|
|
|
} |
|
|
|
void destroy_physics_collection(struct physics_collection *s) { |
|
for (int i = 0; i < s->numItems; i++) { |
|
if (s->items[i]) { |
|
//free(s->items[i]); |
|
} |
|
} |
|
|
|
free(s->items); |
|
} |
|
|
|
|
|
|
|
void next_level(int lvl) { |
|
level = lvl; |
|
load_level(); |
|
} |
|
|
|
void load_level() { |
|
SDL_LockMutex(player.physics->lock); |
|
Vect v; |
|
v.x = 0; |
|
v.y = 0; |
|
player.physics->vel = v; |
|
player.physics->acc = v; |
|
stop_pull_rope(); |
|
player.physics->strings[0].attached = false; |
|
|
|
int d = -1; |
|
for (int i = 0; i < world.items.size; i++) { |
|
if (world.get(i).kind == ROOM_W) { |
|
d = i; |
|
break; |
|
} |
|
} |
|
|
|
if (d != -1) { |
|
world_thing *w = arlst_del(&world.items, d); |
|
} |
|
|
|
destroy_physics_collection(&world.uniques_index[ROOM_W]->room->ceil); |
|
destroy_physics_collection(&world.uniques_index[ROOM_W]->room->floor); |
|
//destroy_environment(&world.uniques_index[ROOM_W]->room->env); |
|
|
|
get_floor_ceiling(); |
|
#ifdef SCORE_SYSTEM |
|
draw_watch.best_time = get_best_time(); |
|
load_score_times("saves/times"); |
|
#endif |
|
|
|
v = world.uniques_index[ROOM_W]->room->ceil.items[2]->collision_poly[0]; |
|
v.y -= 15; |
|
|
|
player.physics->position = v; |
|
player.physics->next_position = v; |
|
|
|
quality = 100; |
|
level_timer_update(true); |
|
draw_watch.finish_level = false; |
|
|
|
viewport_pos.x = player.physics->position.x - width / 2; |
|
viewport_pos.y = player.physics->position.y - height / 2; |
|
|
|
SDL_UnlockMutex(player.physics->lock); |
|
|
|
Mix_HaltChannel(-1); |
|
|
|
// reset physics |
|
|
|
struct timespec now = get_now(); |
|
double time_delta = now.tv_nsec - last_tick.tv_nsec; |
|
if (now.tv_nsec < last_tick.tv_nsec) { |
|
time_delta = now.tv_nsec - (last_tick.tv_nsec - 1000000000); |
|
} |
|
last_tick = now; |
|
|
|
|
|
|
|
} |
|
|
|
int get_floor_ceiling() { |
|
struct environment e = get_scene_at(viewport_pos, level); |
|
struct physics_collection floor; |
|
struct physics_collection ceil; |
|
|
|
floor.items = calloc(e.floor.size - 1, sizeof(Body*)); |
|
ceil.items = calloc(e.ceil.size - 1, sizeof(Body*)); |
|
|
|
floor.numItems = e.floor.size - 1; |
|
ceil.numItems = e.ceil.size - 1; |
|
|
|
for (int i = 0; i < e.floor.size-1; i++) { |
|
get_new_physics(&floor.items[i]); |
|
get_new_physics(&ceil.items[i]); |
|
Body *fseg = floor.items[i]; |
|
Body *cseg = ceil.items[i]; |
|
|
|
fseg->position = *(Vect *)arlst_get(&e.floor, i); |
|
fseg->collision_poly_size = 4; |
|
fseg->collision_poly = calloc(4, sizeof(Vect)); |
|
fseg->collision_shape = calloc(4, sizeof(Vect)); |
|
|
|
cseg->position = *(Vect *)arlst_get(&e.ceil, i); |
|
cseg->collision_poly_size = 4; |
|
cseg->collision_poly = calloc(4, sizeof(Vect)); |
|
cseg->collision_shape = calloc(4, sizeof(Vect)); |
|
|
|
Vect fthis = fseg->position; |
|
Vect cthis = cseg->position; |
|
|
|
Vect fnext = *(Vect *)arlst_get(&e.floor, i + 1); |
|
Vect cnext= *(Vect *)arlst_get(&e.ceil, i + 1); |
|
|
|
double frise = fnext.y - fthis.y; |
|
double frun = fnext.x - fthis.x; |
|
|
|
double crise = cnext.y - cthis.y; |
|
double crun = cnext.x - cthis.x; |
|
|
|
double fdepth = -(fabs(frise) * 2 + 100); |
|
double cdepth = fabs(crise) * 2 + 100; |
|
|
|
fseg->collision_shape[0].x = 0; |
|
fseg->collision_shape[0].y = 0; |
|
|
|
fseg->collision_shape[1].x = frun; |
|
fseg->collision_shape[1].y = frise; |
|
|
|
fseg->collision_shape[2].x = frun; |
|
fseg->collision_shape[2].y = frise + fdepth; |
|
|
|
fseg->collision_shape[3].x = 0; |
|
fseg->collision_shape[3].y = frise + fdepth; |
|
|
|
cseg->collision_shape[0].x = 0; |
|
cseg->collision_shape[0].y = 0; |
|
|
|
cseg->collision_shape[1].x = crun; |
|
cseg->collision_shape[1].y = crise; |
|
|
|
cseg->collision_shape[2].x = crun; |
|
cseg->collision_shape[2].y = crise + cdepth; |
|
|
|
cseg->collision_shape[3].x = 0; |
|
cseg->collision_shape[3].y = crise + cdepth; |
|
|
|
default_update_collision_poly(cseg); |
|
default_update_collision_poly(fseg); |
|
} |
|
|
|
world_thing room_world; |
|
room_world.kind = ROOM_W; |
|
|
|
struct room* room = malloc(sizeof(struct room)); |
|
room->ceil = ceil; |
|
room->floor = floor; |
|
room_world.room = room; |
|
|
|
add_to_world(room_world); |
|
world.uniques_index[ROOM_W]->room->env = e; |
|
return 0; |
|
} |
|
|
|
Wall *get_long_wall(int numNodes, int *nodes) { |
|
Wall wall; |
|
memset(&wall, 0, sizeof(wall)); |
|
|
|
wall.numNodes = numNodes; |
|
wall.nodes = calloc(numNodes, sizeof(SDL_Point)); |
|
get_new_physics(&wall.physics); |
|
|
|
wall.physics->collision_poly = calloc(numNodes, sizeof(Vect)); |
|
wall.physics->collision_shape = calloc(numNodes, sizeof(Vect)); |
|
wall.physics->collision_poly_size = numNodes; |
|
|
|
// collisions |
|
//SDL_Point *collision_poly; |
|
for (int i = 0; i < numNodes; i++) { |
|
wall.nodes[i].x = nodes[2*i]; |
|
wall.nodes[i].y = nodes[2*i+1]; |
|
|
|
wall.physics->collision_shape[i].x = nodes[2*i]; |
|
wall.physics->collision_shape[i].y = nodes[2*i+1]; |
|
} |
|
|
|
Wall *wallwall = malloc(sizeof(wall)); |
|
*wallwall = wall; |
|
return wallwall; |
|
} |
|
|
|
void accel_thing(Body * thing, double x, double y) { |
|
/* takes acceleration in m/s2 and converts to m/ms adding |
|
* it to physics_thing |
|
*/ |
|
|
|
// convert to m / millisecond |
|
double x_adj = x / 1000.0; |
|
double y_adj = y / 1000.0; |
|
|
|
thing->acc.y += y_adj; |
|
thing->acc.x += x_adj; |
|
|
|
} |
|
|
|
void set_motor_max_velocity(Body *thing, int motorID, double max) { |
|
thing->motors[motorID].max_velocity = max; |
|
} |
|
|
|
void set_motor_timeout(Body *thing, int motorID, uint32_t timeout) { |
|
// this don't work yo |
|
clock_gettime(CLOCK_MONOTONIC, &thing->motors[motorID].timeout); |
|
thing->motors[motorID].timeout.tv_nsec += 1000000 * timeout; |
|
thing->motors[motorID].timeout.tv_sec |
|
= thing->motors[motorID].timeout.tv_nsec * 0.000000001; |
|
} |
|
|
|
void set_motor_status(Body *thing, int motorID, bool run) { |
|
if (motorID == M_GRAVITY && run == false) { |
|
*(int *)0 = 1; |
|
} |
|
thing->motors[motorID].stop = !run; |
|
|
|
} |
|
|
|
void set_motor_newtons(Body *thing, int motorID, double x, double y) { |
|
thing->motors[motorID].x =x; |
|
thing->motors[motorID].y =y; |
|
} |
|
|
|
void add_motor_newtons(Body *thing, int motorID, double x, double y) { |
|
thing->motors[motorID].x +=x; |
|
thing->motors[motorID].y +=y; |
|
} |
|
|
|
// @param thing: the body to apply the motor to |
|
// @param x, y: The initial motor force vector. |
|
void add_motor(Body *thing, double x, double y) { |
|
Motor motor; |
|
memset(&motor, 0, sizeof(Motor)); |
|
motor.x = x; |
|
motor.y = y; |
|
motor.stop = true; |
|
|
|
motor.timeout.tv_sec = 0; |
|
motor.timeout.tv_nsec = 0; |
|
|
|
motor.max_velocity = 999899; |
|
|
|
motor.update_motor = default_motor_curve; |
|
|
|
motor.end_object = thing; |
|
|
|
if (thing->num_motors == thing->max_motors) { |
|
thing->motors = realloc(thing->motors, sizeof(Motor) * (thing->max_motors *=2)); |
|
} |
|
|
|
thing->motors[thing->num_motors] = motor; |
|
thing->num_motors += 1; |
|
} |
|
|
|
void ratcheted_winch_motor_update(Motor* motor) { |
|
|
|
Body *body = motor->end_object; |
|
|
|
if (body->strings[0].max_length < 0.1 |
|
|| !body->strings[0].attached || motor->stop) { |
|
motor->stop = true; |
|
return; |
|
} |
|
|
|
Vect st; |
|
st.x = body->position.x - body->strings[0].end_point.x; |
|
st.y = body->position.y - body->strings[0].end_point.y; |
|
|
|
if (body->strings[0].max_length > vect_mag(st)) |
|
body->strings[0].max_length = vect_mag(st); |
|
winch_motor_update(motor); |
|
|
|
play_game_sound(game_sounds.rope_pull, 300, BC_ROPE_PULL); |
|
|
|
} |
|
|
|
void winch_motor_update (Motor* motor) { |
|
Vect v; |
|
v.x = motor->x; |
|
v.y = motor->y; |
|
double mod = vect_mag(v); |
|
|
|
Body *body = motor->end_object; |
|
|
|
if (body->strings[0].max_length < 0.1 |
|
|| !body->strings[0].attached) { |
|
motor->stop = true; |
|
return; |
|
} |
|
|
|
// set the motor direction to the string direction |
|
Vect end_point = body->strings[0].end_point; |
|
Vect start_point = body->position; |
|
Vect dir = vect_add(end_point, vect_scalar(start_point, -1)); |
|
|
|
double arg = vect_arg(dir); |
|
Vect force = vect_modarg(mod, arg); |
|
|
|
motor->x = force.x; |
|
motor->y = force.y; |
|
} |
|
|
|
void pull_rope(double newtons) { |
|
if (!player.physics->strings[0].attached) { |
|
return; |
|
} |
|
if (!player.physics->motors[M_WINCH].stop) { |
|
return; |
|
} |
|
|
|
// set force |
|
set_motor_newtons(player.physics, M_WINCH, 0, newtons); |
|
set_motor_status(player.physics, M_WINCH, true); |
|
// point it in the right direction |
|
winch_motor_update(player.physics->motors + M_WINCH); |
|
player.physics->motors[M_WINCH].stop = false; |
|
} |
|
|
|
void stop_pull_rope(void) { |
|
Mix_HaltChannel(BC_ROPE_PULL); |
|
player.physics->motors[M_WINCH].stop = true; |
|
} |
|
|
|
|
|
void add_rope(int mouse_x, int mouse_y) { |
|
if (player.physics->strings[0].attached) { |
|
return; |
|
} |
|
if (game_paused || !in_game) { |
|
return; |
|
} |
|
|
|
Vect mouse; |
|
mouse.x = mouse_x; |
|
mouse.y = mouse_y; |
|
mouse = vect_add(mouse, viewport_pos); |
|
|
|
Vect start = player.physics->position; |
|
|
|
Vect end; |
|
end.x = 0; |
|
end.y = 0; |
|
|
|
struct room *room = world.uniques_index[ROOM_W]->room; |
|
struct physics_collection floor = room->floor; |
|
struct physics_collection ceil = room->ceil; |
|
|
|
Vect ray_e = player.physics->position; |
|
Vect ray_s = mouse; |
|
|
|
double x1 = ray_s.x; |
|
double y1 = ray_s.y; |
|
|
|
double x2 = ray_e.x; |
|
double y2 = ray_e.y; |
|
|
|
x1 += (x1 - x2) * 100; |
|
y1 += (y1 - y2) * 100; |
|
|
|
|
|
int i; |
|
struct physics_collection s; |
|
bool found = false; |
|
double len = MAX_ROPE_GRAB_LEN; |
|
double t, u; |
|
for (int j = 0; j < (floor.numItems + ceil.numItems - 2); j++) { |
|
if (j >= floor.numItems - 1) { |
|
s = ceil; |
|
i = j - floor.numItems + 1; |
|
} else { |
|
s = floor; |
|
i = j; |
|
} |
|
|
|
Vect wall_s = s.items[i]->position; |
|
Vect wall_e = s.items[i+1]->position; |
|
|
|
Vect intersect; |
|
|
|
double x3 = wall_s.x; |
|
double y3 = wall_s.y; |
|
|
|
double x4 = wall_e.x; |
|
double y4 = wall_e.y; |
|
|
|
t = ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3-x4)) |
|
/ ((x1-x2)*(y3-y4) - (y1 - y2)*(x3-x4)); |
|
|
|
u = ((x1 - x2)*(y1 - y3) - (y1 - y2)*(x1 - x3)) |
|
/ ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); |
|
|
|
if (t < 0 || u > 0 || t > 1 || u < -1) { |
|
continue; |
|
} else { |
|
intersect.x = ((x1*y2 - y1*x2)*(x3 - x4) -(x1 - x2)*(x3*y4 - y3*x4)) |
|
/ ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); |
|
|
|
intersect.y = ((x1*y2 - y1*x2)*(y3 - y4) - (y1 - y2) * (x3*y4 - y3 * x4)) |
|
/ ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); |
|
|
|
Vect rope = vect_add(intersect, vect_scalar(start, -1)); |
|
double mag = vect_mag(rope); |
|
if (mag < len) { |
|
len = mag; |
|
found = true; |
|
end = intersect; |
|
} |
|
} |
|
} |
|
|
|
if (found) { |
|
|
|
SDL_SetRenderDrawColor(debug_ren, 200, 255, 30, 255); |
|
SDL_RenderDrawLine(debug_ren, start.x - viewport_pos.x,start.y-viewport_pos.y, |
|
end.x - viewport_pos.x, end.y -viewport_pos.y); |
|
|
|
Vect rop = vect_add(end, vect_scalar(start, -1)); |
|
|
|
player.physics->strings[0].max_length = vect_mag(rop); |
|
|
|
player.physics->strings[0].set_end_point(player.physics->strings, &end); |
|
player.physics->strings[0].attached = true; |
|
play_game_sound(game_sounds.rope_attach, 100, BC_ROPE_ATTACH); |
|
|
|
} |
|
} |
|
|
|
void delete_rope(void) { |
|
player.physics->strings[0].attached = false; |
|
} |
|
|
|
// basic collision handler for testing |
|
// |
|
int process_collisions(Body *thing, Vect *trans) { |
|
Vect translation; |
|
translation.x = translation.y = 0; |
|
Vect temptrans; |
|
temptrans.x = temptrans.y = 0; |
|
int num_cols = 0; |
|
int num = 0; |
|
|
|
thing->was_colliding += thing->colliding > 0 ? 1 : -1; |
|
if (thing->was_colliding < -1000) { |
|
thing->was_colliding = -1000; |
|
} |
|
if (thing->was_colliding > 1000) { |
|
thing->was_colliding = 1000; |
|
} |
|
|
|
for (int k = 0; k < world.items.size; k++) { |
|
if (world.get(k).kind == STATIC_WALL_W |
|
&& world.get(k).wall->physics->uid != thing->uid) { |
|
if ((world.get(k).wall->physics->position.x - viewport_pos.x) > (width * 2) |
|
|| world.get(k).wall->physics->position.x - viewport_pos.x < 0) { |
|
continue; |
|
} else { |
|
num++; |
|
} |
|
if (check_collision(world.get(k).wall->physics, thing, &temptrans)) { |
|
num++; |
|
thing->colliding = true; |
|
translation = vect_add(translation, temptrans); |
|
} |
|
} else if (world.get(k).kind == FLOOR || world.get(k).kind == CEILING) { |
|
for (int i = 0; i < world.get(k).floor->numPolys; i++) { |
|
if ((world.get(k).floor->polys[i].physics->position.x - viewport_pos.x) > (2 *width) |
|
|| (world.get(k).floor->polys[i].physics->position.x - viewport_pos.x) < -width) { |
|
continue; |
|
} else { |
|
num++; |
|
} |
|
if (check_collision(world.get(k).floor->polys[i].physics, thing, &temptrans)) { |
|
num_cols++; |
|
thing->colliding = true; |
|
translation = vect_add(translation, temptrans); |
|
} |
|
} |
|
} else if (world.get(k).kind == ROOM_W) { |
|
for (int i = 0; i < world.get(k).room->floor.numItems; i++) { |
|
Body *s = world.get(k).room->floor.items[i]; |
|
if (s->position.x + E_ROOM_RES < viewport_pos.x) { |
|
continue; |
|
} else if (s->position.x > viewport_pos.x + width) { |
|
continue; |
|
} else { |
|
num++; |
|
} |
|
|
|
if (check_collision(s, thing, &temptrans)) { |
|
num_cols++; |
|
thing->colliding = true; |
|
translation = vect_add(translation, temptrans); |
|
} |
|
} |
|
|
|
for (int i = 0; i < world.get(k).room->ceil.numItems; i++) { |
|
Body *s = world.get(k).room->ceil.items[i]; |
|
if (s->position.x + E_ROOM_RES < viewport_pos.x) { |
|
continue; |
|
} else if (s->position.x > viewport_pos.x + width) { |
|
continue; |
|
} else { |
|
num++; |
|
} |
|
|
|
if (check_collision(s, thing, &temptrans)) { |
|
num_cols++; |
|
thing->colliding = true; |
|
translation = vect_add(translation, temptrans); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
if (!num_cols) { |
|
thing->colliding = false; |
|
return false; |
|
} else { |
|
*trans = translation; |
|
return true; |
|
} |
|
} |
|
|
|
struct timespec get_now() { |
|
struct timespec now; |
|
clock_gettime(CLOCK_MONOTONIC, &now); |
|
if (now.tv_nsec >= 1000000000) { |
|
now.tv_nsec -= 1000000000; |
|
} |
|
return now; |
|
} |
|
|
|
bool get_motor_active(Body *thing, int i) { |
|
if (thing->motors[i].stop) { |
|
return false; |
|
} |
|
|
|
struct timespec now = get_now(); |
|
if (thing->motors[i].timeout.tv_nsec |
|
|| thing->motors[i].timeout.tv_sec) { |
|
// 0 is a sentinel for infinity |
|
if (thing->motors[i].timeout.tv_sec > now.tv_sec |
|
&& thing->motors[i].timeout.tv_nsec > now.tv_nsec) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/* Basic physics works by adding up the acceleratino caused by all the forces on |
|
* the object, converting it to velocity, then doing collision detection and |
|
* applying various reactive forces (friction etc) and finally adjusting the |
|
* object's position. |
|
*/ |
|
void advance_thing(Body * thing) { |
|
// TODO: fix ordering of collision detection + physics sim so that collisions |
|
// are less bad. |
|
|
|
thing->acc.x = 0; |
|
thing->acc.y = 0; |
|
|
|
if (!thing->dynamics) { |
|
return; |
|
} |
|
|
|
double time_delta = TIMESTEP_LENGTH; |
|
|
|
Vect translation; |
|
Vect friction; |
|
translation.x = translation.y = 0; |
|
int numcols = 0; |
|
|
|
// collisions |
|
if ((numcols = process_collisions(thing, &translation))) { |
|
/*double check = vect_scalar_projection(translation, thing->vel);*/ |
|
/*if (check >= 0) {*/ |
|
/*translation.x *= -1;*/ |
|
/*translation.y *= -1;*/ |
|
/*}*/ |
|
|
|
// correct position using translation vector. |
|
thing->next_position.x += translation.x; |
|
thing->next_position.y += translation.y; |
|
|
|
// make unit vector |
|
double mag = vect_mag(translation); |
|
translation.x = translation.x / mag; |
|
translation.y = translation.y / mag; |
|
|
|
if (mag > 0.02) { |
|
Mix_Volume(BC_COLLISION, 1.2 * mag * MIX_MAX_VOLUME); |
|
play_game_sound(game_sounds.collision, 100, BC_COLLISION); |
|
Mix_FadeOutChannel(BC_COLLISION, 100* 0.8); |
|
} |
|
|
|
// get velocity in direction of collision |
|
mag = vect_scalar_projection(thing->vel, translation); |
|
|
|
Vect revert_vel; |
|
|
|
revert_vel.x = cos(vect_dir(translation)) * mag; |
|
revert_vel.y = sin(vect_dir(translation)) * mag; |
|
|
|
thing->vel.x -= revert_vel.x; |
|
thing->vel.y -= revert_vel.y; |
|
|
|
// add elasticity |
|
thing->vel.x -= revert_vel.x * sqrt(thing->obj_elasticity); |
|
thing->vel.y -= revert_vel.y * sqrt(thing->obj_elasticity); |
|
|
|
// add friction |
|
|
|
if (vect_mag(translation) < 0.1) { |
|
Vect friction = vect_rotate(revert_vel, M_PI / 2); |
|
double dir = vect_scalar_projection(thing->vel, friction); |
|
if (dir > 0) { |
|
friction = vect_rotate(friction, M_PI); |
|
} |
|
|
|
if (fabs(thing->vel.x) <= fabs(friction.x)) { |
|
thing->vel.x = 0; |
|
} else { |
|
thing->vel.x += thing->obj_friction * friction.x; |
|
} |
|
if (fabs(thing->vel.y) <= fabs(friction.y)) { |
|
thing->vel.y = 0; |
|
} else { |
|
thing->vel.y += thing->obj_friction * friction.y; |
|
} |
|
|
|
} |
|
|
|
/*double norm = 9.81 * thing->obj_mass;*/ |
|
/*Vect x;*/ |
|
/*x.y = 0; x.x = 1;*/ |
|
/*if (vect_scalar_projection(thing->vel, translation) > 0) {*/ |
|
/*friction.x = translation.y;*/ |
|
/*friction.y = -translation.x;*/ |
|
/*} else {*/ |
|
/*friction.x = -translation.y;*/ |
|
/*friction.y = translation.x;*/ |
|
/*}*/ |
|
|
|
// force |
|
/*friction.x = friction.x * norm * fric_const;*/ |
|
/*friction.y = friction.y * norm * fric_const;*/ |
|
// printf("friction accel: %e %e\n", friction.x, friction.y); |
|
// printf("velocity: %e %e\n", thing->vel.x, thing->vel.y); |
|
|
|
/*set_motor_newtons(thing, M_FRICTION, friction.x, friction.y);*/ |
|
/*set_motor_max_velocity(thing, M_FRICTION, vect_mag(project_vect(thing->vel, friction)));*/ |
|
|
|
// restitution force |
|
/*rest.x = 0;*/ |
|
/*rest.y = 0;*/ |
|
/*double impulse = thing->obj_mass * vect_mag(thing->vel) * thing->obj_elasticity;*/ |
|
/*rest.x = cos(vect_dir(translation)) * impulse;*/ |
|
/*rest.y = sin(vect_dir(translation)) * impulse;*/ |
|
/*accel_thing(thing, rest.x, rest.y);*/ |
|
} |
|
|
|
// pendulums |
|
for (int i = 0; i < thing->num_strings; i++) { |
|
if (!thing->strings[i].attached) { |
|
continue; |
|
} |
|
|
|
double st_len = vect_distance(thing->next_position, thing->strings[i].end_point); |
|
double max_len = thing->strings[i].max_length; |
|
if (st_len > max_len) { |
|
Vect string_end = thing->strings[i].end_point; |
|
Vect thing_position = thing->next_position; |
|
|
|
Vect string; |
|
string.x = string_end.x - thing_position.x; |
|
string.y = string_end.y - thing_position.y; |
|
double mag = vect_mag(string); |
|
string.x /= mag; |
|
string.y /= mag; |
|
double angle = atan2(string.y, string.x); |
|
double disp = mag - max_len; |
|
|
|
thing->next_position.x += cos(angle) * disp; |
|
thing->next_position.y += sin(angle) * disp; |
|
|
|
// set velocity to 0 in direction of string |
|
double corr_mag = vect_scalar_projection(thing->vel, string); |
|
|
|
thing->vel.x -= corr_mag * cos(angle); |
|
thing->vel.y -= corr_mag * sin(angle); |
|
} |
|
} |
|
|
|
// motors |
|
for (int i = 0; i < thing->num_motors; i++) { |
|
|
|
if (!get_motor_active(thing, i)) { |
|
continue; |
|
} |
|
|
|
// dynamic motor curve |
|
if (thing->motors[i].update_motor) { |
|
thing->motors[i].update_motor(thing->motors + i); |
|
} |
|
Vect F; |
|
Vect V; |
|
F.x = thing->motors[i].x; |
|
F.y = thing->motors[i].y; |
|
V.x = thing->vel.x; |
|
V.y = thing->vel.y; |
|
|
|
Vect vel_in_dir = project_vect(V, F); |
|
double dirF = atan2(F.y, F.x); |
|
double dirV = atan2(V.y, V.x); |
|
|
|
double diff = dirV > dirF ? dirV - dirF: dirF - dirV; |
|
|
|
if (thing->motors[i].max_velocity > vect_mag(vel_in_dir) |
|
|| diff >= M_PI) { |
|
double acc_x = thing->motors[i].x / thing->obj_mass; |
|
double acc_y = thing->motors[i].y / thing->obj_mass; |
|
accel_thing(thing, acc_x, acc_y); |
|
} |
|
} |
|
|
|
// accelerate based on accel |
|
thing->vel.x += thing->acc.x * (double)time_delta; |
|
thing->vel.y += thing->acc.y * (double)time_delta; |
|
|
|
double velocity = sqrt((double)(thing->vel.x * thing->vel.x + thing->vel.y * thing->vel.y)); |
|
|
|
// simple air drag |
|
|
|
/*if (velocity > 0.000000000000000) {*/ |
|
/*double dir = atan2((double)thing->y_vel, (double)thing->x_vel) + M_PI;*/ |
|
|
|
/*double absolute_force = 5 * thing->obj_mass * (velocity / time_delta); // 2 * 0.1231 * 5;*/ |
|
/*printf("dir %e %e\n\n", dir, dir - M_PI);*/ |
|
/*printf("force %e\n\n", absolute_force);*/ |
|
|
|
/*double f_x = (cos(dir)) * absolute_force;*/ |
|
/*double f_y = (sin(dir)) * absolute_force;*/ |
|
|
|
/*accel_thing(thing, (float)f_x, (float)f_y);*/ |
|
|
|
/*}*/ |
|
|
|
if (fabsl((*thing).vel.x) < 0.001) { |
|
(*thing).vel.x = 0; |
|
} |
|
if (fabsl((*thing).vel.y) < 0.001) { |
|
(*thing).vel.y = 0; |
|
} |
|
|
|
if (thing->lock) { |
|
SDL_LockMutex(thing->lock); |
|
} |
|
thing->position = thing->next_position; |
|
|
|
double oldx = thing->position.x; |
|
double oldy = thing->position.y; |
|
thing->next_position.x += (thing->vel.x * 1/2 * (double)time_delta); |
|
thing->next_position.y += (thing->vel.y * 1/2 * (double)time_delta); |
|
|
|
|
|
if (!thing->collision_poly[0].y) { |
|
thing->updateCollisionPoly(thing); |
|
} |
|
|
|
thing->updateCollisionPoly(thing); |
|
if (thing->lock) { |
|
SDL_UnlockMutex(thing->lock); |
|
} |
|
|
|
} |
|
|
|
/*void destroy_projectile(Projectile** proj) {*/ |
|
/*for (int i = 0; i < things_in_world; i ++) {*/ |
|
/*if (world[i].projectile == *proj) {*/ |
|
|
|
/*}*/ |
|
/*}*/ |
|
/*}*/ |
|
|
|
void *default_projectile_step(struct Projectile *self) { |
|
advance_thing(self->physics); |
|
return NULL; |
|
} |
|
|
|
void *default_projectile_on_collision(struct Projectile *self) { |
|
self->physics->dynamics = false; |
|
return NULL; |
|
} |
|
|
|
void get_projectile(Projectile** proj) { |
|
*proj = calloc(1, sizeof(Projectile)); |
|
get_new_physics(&(*proj)->physics); |
|
(*proj)->physics->dynamics = true; |
|
(*proj)->on_step = default_projectile_step; |
|
(*proj)->on_collision = default_projectile_on_collision; |
|
} |
|
|
|
void advance_things(void) { |
|
int numcols; |
|
Vect translation; |
|
|
|
/* update the timer */ |
|
struct timespec now = get_now(); |
|
double time_delta = now.tv_nsec - last_tick.tv_nsec; |
|
|
|
if (now.tv_nsec < last_tick.tv_nsec) { |
|
time_delta = now.tv_nsec - (last_tick.tv_nsec - 1000000000); |
|
} |
|
time_delta *= 0.000001; // convert to ms from ns |
|
time_remaining += time_delta; |
|
last_tick = now; |
|
|
|
while (time_remaining > TIMESTEP_LENGTH) { |
|
time_remaining -= TIMESTEP_LENGTH; |
|
for (int i = 0; i < world.items.size; i++) { |
|
switch (world.get(i).kind) { |
|
case PLAYER_W : |
|
advance_thing((world.get(i).player->physics)); |
|
continue; |
|
case STATIC_WALL_W: |
|
advance_thing(world.get(i).wall->physics); |
|
continue; |
|
case FLOOR: |
|
continue; |
|
/*for (int k = 0; k < world.get(i).floor->numPolys; k++) {*/ |
|
/*advance_thing(world.get(i).floor->polys[k].physics);*/ |
|
/*}*/ |
|
/*break;*/ |
|
case CEILING: |
|
continue; |
|
/*for (int k = 0; k < world.get(i).floor->numPolys; k++) {*/ |
|
/*advance_thing(world.get(i).floor->polys[k].physics);*/ |
|
/*}*/ |
|
/*break;*/ |
|
case PROJECTILE: |
|
if ((numcols = process_collisions(world.get(i).projectile->physics, |
|
&translation))) { |
|
world.get(i).projectile->on_collision(world.get(i).projectile); |
|
} |
|
world.get(i).projectile->on_step(world.get(i).projectile); |
|
continue; |
|
default: |
|
continue; |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool unique_world_kind(enum world_thing_kind k) { |
|
switch (k) { |
|
case PLAYER_W: |
|
case FLOOR: |
|
case CEILING: |
|
case ROOM_W: |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
// grow array of world things if needed |
|
void add_to_world(world_thing thing) { |
|
thing.nid = world.items.size; |
|
world_thing *t = calloc(1, sizeof(world_thing)); |
|
memcpy(t, &thing, sizeof(world_thing)); |
|
|
|
arlst_add(&world.items, t); |
|
|
|
if (unique_world_kind(thing.kind)) { |
|
void *ref = arlst_get(&world.items, world.items.size - 1); |
|
world.uniques_index[thing.kind] = ref; |
|
} |
|
|
|
} |
|
|
|
/* Send a projectile from body in direction dir (in degrees) with the force of |
|
* sten newtons */ |
|
void fire_projectile(Body *from, int stren, int dir) { |
|
Projectile *proj; |
|
get_projectile(&proj); |
|
|
|
double radians = (double)dir * (M_PI / 180); |
|
set_motor_newtons(proj->physics, 0, stren * cos(radians), |
|
stren * sin(radians)); |
|
set_motor_timeout(proj->physics, 0, 2); |
|
|
|
world_thing proj_world; |
|
proj_world.projectile = proj; |
|
proj_world.kind = PROJECTILE; |
|
add_to_world(proj_world); |
|
} |
|
|
|
void get_room(void) { |
|
int floorsize = 100; |
|
FloorPoly *polys = generate_floor_simple(floorsize, true, 1300); |
|
Floor *floor = calloc(1, sizeof(Floor)); |
|
floor->polys = polys; |
|
floor->numPolys = floorsize; |
|
|
|
FloorPoly *ceil = generate_floor_simple(floorsize, false, 1000); |
|
|
|
// printf("floor: %f %f\n", polys[2].left.x, polys[2].left.y); |
|
// printf("ceil: %f %f\n", ceil[2].left.x, ceil[2].left.y); |
|
|
|
Floor* ceiling = calloc(1, sizeof(Floor)); |
|
ceiling->polys = ceil; |
|
ceiling->numPolys = floorsize; |
|
|
|
world_thing ceilingthing; |
|
ceilingthing.kind = CEILING; |
|
ceilingthing.floor = ceiling; |
|
add_to_world(ceilingthing); |
|
|
|
world_thing floorthing; |
|
floorthing.kind = FLOOR; |
|
floorthing.floor = floor; |
|
add_to_world(floorthing); |
|
} |
|
|
|
GlobWorld create_world() { |
|
GlobWorld c; |
|
c.items = new_arlst(100); |
|
c.uniques_index = calloc(10, sizeof(world_thing*)); |
|
c.get = world_getter; // gross |
|
return c; |
|
} |
|
|
|
void startgame(SDL_Renderer * ren) { |
|
get_input_map(); |
|
|
|
textboxes[TB_LEVEL_CHOOSER].id = TB_LEVEL_CHOOSER; |
|
textboxes[TB_LEVEL_CHOOSER].close_callback = new_level_tb_close_callback; |
|
|
|
debug_ren = ren; |
|
|
|
level = rand(); |
|
|
|
#ifdef SCORE_SYSTEM |
|
save_times_lst = (struct sg_times_list){}; |
|
load_score_times("saves/times"); |
|
#endif |
|
|
|
world = create_world(); |
|
|
|
SDL_GetRendererOutputSize(ren, &width, &height); |
|
|
|
// player = get_player(200,1300); |
|
|
|
get_floor_ceiling(); |
|
Vect stpos = *world.uniques_index[ROOM_W]->room->ceil.items[2]->collision_poly; |
|
player = get_player(stpos.x, stpos.y - 15); |
|
world_thing player_world; |
|
|
|
player_world.kind = PLAYER_W; |
|
player_world.player = malloc(sizeof(player)); |
|
*player_world.player = player; |
|
glob_player = player_world.player; |
|
|
|
add_to_world(player_world); |
|
|
|
viewport_pos.x = 700; |
|
viewport_pos.y = 0; |
|
|
|
level_timer_update(true); |
|
game_paused = 0; |
|
viewport_pos.x = player.physics->position.x - width / 2; |
|
viewport_pos.y = player.physics->position.y - height / 2; |
|
|
|
|
|
mute_button.x = 90.0/100 * width; |
|
mute_button.y = 85.0/100 * height; |
|
mute_button.w = 90; |
|
mute_button.h = 90; |
|
mute_button.state = mute; |
|
mute_button.held = false; |
|
|
|
//get_room(); |
|
} |
|
|
|
bool aabb_check(float x, float y, float rx, float ry, float w, float h) { |
|
return x > rx && x < rx + w && y > ry && y < ry + h; |
|
} |
|
|
|
double get_abs(double x,double y) { |
|
return (sqrt(x*x + y*y)); |
|
} |
|
|
|
void walk_player(int x, int y) { |
|
|
|
set_motor_status(player.physics, M_PLAYER_WALK, true); |
|
set_motor_newtons(player.physics, M_PLAYER_WALK, 0, 0); |
|
// turned off special case for going down for TESTing |
|
if (false && y == -1) { |
|
add_motor_newtons(glob_player->physics, M_PLAYER_WALK, 0, 100 * 10 * y); |
|
return; |
|
} |
|
add_motor_newtons(glob_player->physics, M_PLAYER_WALK, 100 *5* x , 100 * 5 * y); |
|
} |
|
|
|
void handle_input_event(SDL_Event event) { |
|
SDL_Scancode sc = event.key.keysym.scancode; |
|
static bool mouse_down = false; |
|
switch (event.type) { |
|
case SDL_KEYDOWN: |
|
|
|
if (gameui.currently_bound_textbox) { |
|
if (event.key.keysym.scancode == SDL_SCANCODE_BACKSPACE |
|
|| event.key.keysym.scancode == SDL_SCANCODE_DELETE) { |
|
delete_from_textbox(gameui.currently_bound_textbox); |
|
} else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN |
|
|| event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { |
|
if (gameui.currently_bound_textbox->close_callback) { |
|
gameui.currently_bound_textbox->close_callback( |
|
gameui.currently_bound_textbox, NULL); |
|
} |
|
gameui.currently_bound_textbox = NULL; |
|
} |
|
else if (event.key.keysym.sym >= 32 |
|
&& event.key.keysym.sym <= 126) { |
|
write_to_textbox(gameui.currently_bound_textbox, |
|
(char)event.key.keysym.sym); |
|
} |
|
// dont do anything else when the textbox is bound; |
|
break; |
|
} |
|
|
|
|
|
#ifdef DEBUGMODE |
|
if (sc == input_map.player_up) { |
|
walk_player(0, -1); |
|
} if ( sc == input_map.player_left) { |
|
walk_player(-1, 0); |
|
} if (sc == input_map.player_down) { |
|
walk_player(0, 1); |
|
} if (sc == input_map.player_right) { |
|
walk_player(1, 0); |
|
} |
|
#endif |
|
if (sc == input_map.player_pull_rope) { |
|
pull_rope(900); |
|
} if (sc == input_map.mute) { |
|
mute = !mute; |
|
mute_button.state = mute; |
|
} if (sc == input_map.pause) { |
|
if (in_game) |
|
game_paused = !game_paused; |
|
} |
|
|
|
if (sc == input_map.goto_level) { |
|
gameui.currently_bound_textbox = textboxes + TB_LEVEL_CHOOSER; |
|
}; |
|
|
|
|
|
break; |
|
case SDL_KEYUP: |
|
if (event.key.keysym.scancode == input_map.player_rope) { |
|
delete_rope(); |
|
} |
|
#ifdef DEBUGMODE |
|
if (sc == input_map.player_up || sc == input_map.player_down |
|
|| sc == input_map.player_left |
|
|| sc == input_map.player_right) { |
|
set_motor_newtons(player.physics, M_PLAYER_WALK, 0, 0); |
|
set_motor_status(player.physics, M_PLAYER_WALK, false); |
|
} |
|
if (event.key.keysym.scancode == SDL_SCANCODE_F10) { |
|
next_level(); |
|
} |
|
#endif |
|
if (sc == input_map.player_pull_rope) { |
|
stop_pull_rope(); |
|
} |
|
|
|
break; |
|
case SDL_MOUSEBUTTONDOWN: |
|
|
|
if (aabb_check(event.button.x, event.button.y, |
|
mute_button.x, mute_button.y, mute_button.w, |
|
mute_button.h)) { |
|
mute_button.held = true; |
|
break; |
|
} |
|
|
|
add_rope(event.button.x, event.button.y); |
|
if (event.button.button == input_map.mouse_attach_rope) |
|
mouse_down = true; |
|
if (event.button.button == input_map.mouse_attach_rope_pull) |
|
pull_rope(900); |
|
break; |
|
case SDL_MOUSEBUTTONUP: |
|
|
|
mute_button.held = false; |
|
if (aabb_check(event.button.x, event.button.y, |
|
mute_button.x, mute_button.y, mute_button.w, |
|
mute_button.y)) { |
|
mute = !mute; |
|
mute_button.state = mute; |
|
break; |
|
} |
|
|
|
|
|
if (event.button.button == input_map.mouse_attach_rope) { |
|
mouse_down = false; |
|
delete_rope(); |
|
} |
|
if (event.button.button == input_map.mouse_attach_rope_pull) { |
|
stop_pull_rope(); |
|
if (!mouse_down) { |
|
delete_rope(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
/* temporary storage variable *n should be initialised to 1 */ |
|
int get_rand(int *n) { |
|
const unsigned c = 11; |
|
const unsigned m = (1 << 31) - 1; |
|
const unsigned initial_n = 1; |
|
const unsigned a = 48271; |
|
|
|
*n = (a * *n + c) % m; |
|
return *n; |
|
} |
|
|
|
|
|
int step(void) { |
|
static int first_run = 0; |
|
if (!first_run) { |
|
start_audio(); |
|
// play_game_sound(game_sounds.background, -1, BC_MUSIC); |
|
first_run = 1; |
|
game_paused = 0; |
|
} |
|
|
|
if (gameui.goto_new_level) { |
|
int nevl = gameui.goto_new_level; |
|
gameui.goto_new_level = 0; |
|
return nevl; |
|
} |
|
|
|
if (player.physics->position.x > world.uniques_index[ROOM_W]->room |
|
->floor.items[world.uniques_index[ROOM_W]->room->floor.numItems - 1]->position.x) { |
|
draw_watch.finish_level = true; |
|
return level + 1; |
|
} |
|
|
|
if (!game_paused) { |
|
if (player.physics->position.x > world.uniques_index[ROOM_W]->room |
|
->floor.items[3]->position.x) { |
|
level_timer_update(false); |
|
} |
|
|
|
if (in_game) { |
|
advance_things(); |
|
} |
|
} else { |
|
last_tick = get_now(); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|