#include "game.h" #include "draw.h" #include "audio.h" #include "types.h" #include #include #include #include #include #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; }