#include "anim.h" #include "enemies.h" #include "entities.h" #include "game.h" #include "geometry.h" #include "level.h" #include "map.h" #include "particles.h" #include "pathfinding.h" #include "render.h" #include #include #include #include #include #include #include #include #include #include #include int main(void) { /* Enable %f etc. in printf()-like functions */ __printf_enable_fp(); /* Enable %D for decimal fixed-point in printf()-like functions */ __printf_enable_fixed(); /* Initialize the PRNG */ srand(0xc0ffee + 1); /* Initialize the benchmarking/profiling library */ prof_init(); /* Open the USB connection (for screenshots with fxlink) */ usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL }; usb_open(interfaces, GINT_CALL_NULL); game_t game = { 0 }; map_t *m = &game.map; camera_t *c = &game.camera; game_load(&game, &lv_1); struct { /* Show variables */ bool show_vars; /* Show hitboxes */ bool show_hitboxes; /* Show BFS field around player */ bool show_bfs_field; pfg_all2one_t bfs_field; /* Show path to some fixed cell */ bool show_path; pfg_path_t grid_path; pfc_path_t continuous_path; /* Show performance metrics */ bool show_perf; } debug; memset(&debug, 0, sizeof debug); //--- // Spawn player //--- entity_movement_params_t emp_player = { .max_speed = fix(4), .propulsion = fix(12), .dash_speed = fix(55), .dash_duration = fix(1) / 32, .dash_cooldown = fix(1), }; entity_player_t player_data = { .combo_length = 2, .combo_next = 0, .combo_delay = fix(0), }; entity_t *player = enemy_spawn(ENEMY_BAT, 1); entity_player_t *playerd = &player_data; player->player = playerd; int x=1, y=1; for(int i = 0; i < 1000; i++) { x = rand() % m->width; y = rand() % m->height; struct tile *t = map_tile(m, x, y); if(t && !t->solid) break; } player->movement.x = fix(x) + fix(0.5); player->movement.y = fix(y) + fix(0.5); game_add_entity(&game, player); game.player = player; player->HP += 100; player->movement_params = &emp_player; player->identity = 0; player->hitbox = (frect_t){ -fix(5)/16, fix(5)/16, -fix(2)/16, fix(4)/16 }; player->sprite = (frect_t){ -fix(6)/16, fix(5)/16, -fix(12)/16, fix(4)/16 }; entity_set_anim(player, anims_player_Idle); //--- // Main loop //--- /* Start a timer at the FRAME_RATE setting to schedule new frames */ volatile int frame_tick = 1; int timer_id = timer_configure(TIMER_ANY, 1000000 / FRAME_RATE, GINT_CALL_SET(&frame_tick)); if(timer_id >= 0) timer_start(timer_id); bool stop = false; prof_t perf_render, perf_simul; uint32_t time_render=0, time_simul=0; while(!stop) { while(!frame_tick) sleep(); bool attack = false; /* This assumes the frame is not late; can do better with libprof */ fixed_t dt = fix(1) / FRAME_RATE; perf_render = prof_make(); prof_enter(perf_render); dclear(C_BLACK); render_game(&game, debug.show_hitboxes); /* Developer/tweaking menu */ if(debug.show_vars) { uint32_t *vram = (void *)gint_vram; for(int y = 0; y < 224; y++) { for(int i = 0; i < 396/4; i++) vram[i] = (vram[i] & 0xf7def7de) >> 1; vram += 396/2; } uint16_t gray = C_RGB(16, 16, 16); dprint(3, 40, C_WHITE, "Player speed: %g tiles/s", f2double(emp_player.max_speed)); dprint(15, 55, gray, "[frac] -/+ [X,0,T]"); dprint(3, 70, C_WHITE, "Propulsion: %g s^-1", f2double(emp_player.propulsion)); dprint(15, 85, gray, "[F<>D] -/+ [log]"); dprint(15, 100, C_WHITE, "(Friction: %g)", f2double(fix(1) - emp_player.propulsion / FRAME_RATE)); dprint(3, 115, C_WHITE, "Dash speed: %g tiles/s", f2double(emp_player.dash_speed)); dprint(15, 130, gray, "[(] -/+ [ln]"); dprint(3, 145, C_WHITE, "Dash duration: %g s", f2double(emp_player.dash_duration)); dprint(15, 160, gray, "[)] -/+ [sin]"); dprint(3, 175, C_WHITE, "Dash cooldown: %g s", f2double(emp_player.dash_cooldown)); dprint(15, 190, gray, "[,] -/+ [cos]"); } if(debug.show_path) { if(debug.grid_path.points) for(int i = 0; i < debug.grid_path.length; i++) { ipoint_t j = camera_map2screen(c, point_i2f_center(debug.grid_path.points[i])); ipoint_t k = camera_map2screen(c, point_i2f_center(debug.grid_path.points[i+1])); dline(j.x, j.y, k.x, k.y, C_RGB(31, 0, 31)); } if(debug.continuous_path.points) for(int i = 0; i < debug.continuous_path.length; i++) { ipoint_t j = camera_map2screen(c, debug.continuous_path.points[i]); ipoint_t k = camera_map2screen(c, debug.continuous_path.points[i+1]); dline(j.x, j.y, k.x, k.y, C_RGB(0, 31, 31)); } fpoint_t p = entity_pos(player); fpoint_t q = point_i2f_center((ipoint_t){ 6, 9 }); bool clear = raycast_clear_hitbox(m, p, q, player->hitbox); ipoint_t j = camera_map2screen(c, p); ipoint_t k = camera_map2screen(c, q); dline(j.x, j.y, k.x, k.y, clear ? C_GREEN : C_RED); extern int rch_c1, rch_c2; extern fpoint_t rch_p1[64], rch_p2[64]; for(int k = 0; k < rch_c1 && k < 64; k++) { ipoint_t i = camera_map2screen(c, rch_p1[k]); dline(i.x-2, i.y, i.x+2, i.y, C_RGB(0, 31, 31)); dline(i.x, i.y-2, i.x, i.y+2, C_RGB(0, 31, 31)); } for(int k = 0; k < rch_c2 && k < 64; k++) { ipoint_t i = camera_map2screen(c, rch_p2[k]); dline(i.x-2, i.y, i.x+2, i.y, C_RGB(0, 31, 31)); dline(i.x, i.y-2, i.x, i.y+2, C_RGB(0, 31, 31)); } } if(debug.show_bfs_field && game.paths_to_player.direction) { render_pfg_all2one(&game.paths_to_player, c); } if(debug.show_perf) { dprint(1, 15, C_WHITE, "Render: %.3D ms", time_render); dprint(1, 29, C_WHITE, "Simul: %.3D ms", time_simul); } if(keydown(KEY_VARS)) { int fg = C_RGB(31, 31, 0); fkey_button(1, "PARAMS", debug.show_vars ? fg : C_WHITE); fkey_button(2, "HITBOX", debug.show_hitboxes ? fg : C_WHITE); fkey_button(3, "BFS", debug.show_bfs_field ? fg : C_WHITE); fkey_button(4, "PATHS", debug.show_path ? fg : C_WHITE); fkey_button(5, "PERF", debug.show_perf ? fg : C_WHITE); } static bool record = false; if(keydown(KEY_F6) && !keydown(KEY_VARS) && usb_is_open()) { record = !record; } if(record) { usb_fxlink_videocapture(false); } /* Instead of dupdate(); for accurate performance measurements. Leaving the DMA running during the simulation affects the results in extreme proportions (can turn 1 ms of simulation into 11 ms measured) */ r61524_display(gint_vram, 0, 224, R61524_DMA_WAIT); prof_leave(perf_render); time_render = prof_time(perf_render); //--- perf_simul = prof_make(); prof_enter(perf_simul); game_spawn_enemies(&game); game_update_animations(&game, dt); game_sort_entities(&game); game_sort_particles(&game); key_event_t ev; while((ev = pollevent()).type != KEYEV_NONE) { if(ev.type == KEYEV_UP) continue; if(ev.key == KEY_MENU) gint_osmenu(); if(ev.key == KEY_EXIT) stop = true; /* Debug settings */ if(ev.key == KEY_F1 && keydown(KEY_VARS)) debug.show_vars ^= 1; if(ev.key == KEY_F2 && keydown(KEY_VARS)) debug.show_hitboxes ^= 1; if(ev.key == KEY_F3 && keydown(KEY_VARS)) debug.show_bfs_field ^= 1; if(ev.key == KEY_F4 && keydown(KEY_VARS)) debug.show_path ^= 1; if(ev.key == KEY_F5 && keydown(KEY_VARS)) debug.show_perf ^= 1; if(ev.key == KEY_XOT) emp_player.max_speed += fix(1)/8; if(ev.key == KEY_FRAC) { emp_player.max_speed -= fix(1)/8; if(emp_player.max_speed < 0) emp_player.max_speed = 0; } if(ev.key == KEY_LOG) { emp_player.propulsion += fix(1) / 8; if(emp_player.propulsion > fix(FRAME_RATE)) emp_player.propulsion = fix(FRAME_RATE); } if(ev.key == KEY_FD) { emp_player.propulsion -= fix(1) / 8; if(emp_player.propulsion <= 0) emp_player.propulsion = 0; } if(ev.key == KEY_LN) emp_player.dash_speed += fix(0.5); if(ev.key == KEY_LEFTP) { emp_player.dash_speed -= fix(0.5); if(emp_player.dash_speed <= 0) emp_player.dash_speed = 0; } if(ev.key == KEY_SIN) emp_player.dash_duration += fix(1) / 64; if(ev.key == KEY_RIGHTP) { emp_player.dash_duration -= fix(1) / 64; if(emp_player.dash_duration <= 0) emp_player.dash_duration = 0; } if(ev.key == KEY_COS) emp_player.dash_cooldown += fix(1) / 8; if(ev.key == KEY_COMMA) { emp_player.dash_cooldown -= fix(1) / 8; if(emp_player.dash_cooldown <= 0) emp_player.dash_cooldown = 0; } if(ev.key == KEY_PLUS) camera_zoom(c, c->zoom + 1); if(ev.key == KEY_MINUS) camera_zoom(c, c->zoom - 1); if(ev.key == KEY_SHIFT) attack = true; } /* Camera movement */ fixed_t vx = CAMERA_SPEED_X; fixed_t vy = CAMERA_SPEED_Y; if(keydown(KEY_4) || keydown(KEY_7) || keydown(KEY_1)) camera_move(c, -fmul(dt, vx), 0); if(keydown(KEY_6) || keydown(KEY_9) || keydown(KEY_3)) camera_move(c, fmul(dt, vx), 0); if(keydown(KEY_8) || keydown(KEY_7) || keydown(KEY_9)) camera_move(c, 0, -fmul(dt, vy)); if(keydown(KEY_2) || keydown(KEY_1) || keydown(KEY_3)) camera_move(c, 0, fmul(dt, vy)); /* Player movement */ if(player->HP > 0) { int dir = -1; if(keydown(KEY_UP)) dir = UP; if(keydown(KEY_DOWN)) dir = DOWN; if(keydown(KEY_LEFT)) dir = LEFT; if(keydown(KEY_RIGHT)) dir = RIGHT; if(keydown(KEY_F1) && !keydown(KEY_VARS)) { int dash_dir = (dir >= 0) ? dir : player->movement.facing; entity_dash(player, dash_dir); } entity_movement_t next = entity_move4(player, dir, dt); bool set_anim = (player->movement.facing != next.facing); game_try_move_entity(&game, player, &next); if(set_anim) entity_set_anim(player, anims_player_Walking); } /* Directions to reach the player from anywhere on the grid */ pfg_all2one_free(&game.paths_to_player); game.paths_to_player = pfg_bfs(m, point_f2i(entity_pos(player))); /* Enemy AI */ if(player->HP > 0) for(int i = 0; i < game.entity_count; i++) { entity_t *e = game.entities[i]; if(e == player || e->HP == 0) continue; /* Go within 1 block of the player */ fpoint_t direction = { 0, 0 }; fpoint_t pos = entity_pos(e); bool in_range = dist2(pos, entity_pos(player)) <= fix(1); if(!in_range) { pfg_path_t path = pfg_bfs_inwards(&game.paths_to_player, point_f2i(pos)); if(path.points) { direction = pfc_shortcut_one(&path, pos, entity_pos(player), e->hitbox); pfg_path_free(&path); } if(direction.x && direction.y) { direction.x -= pos.x; direction.y -= pos.y; } } entity_movement_t next = entity_move(e, direction, dt); if(direction.x > 0) next.facing = RIGHT; else if(direction.x < 0) next.facing = LEFT; else if(e->movement.x < player->movement.x) next.facing = RIGHT; else next.facing = LEFT; bool set_anim = (e->movement.facing != next.facing); game_try_move_entity(&game, e, &next); if(set_anim) entity_set_anim(e, enemies[e->identity]->anim_idle[e->movement.facing == RIGHT]); if(in_range && !e->current_attack) { /* Enemy attack */ int facing = frdir((fpoint_t){ player->movement.x - e->movement.x, player->movement.y - e->movement.y }); effect_area_t *area = effect_area_new_attack(EFFECT_ATTACK_HIT, e, facing); game_add_effect_area(&game, area); e->current_attack = area; e->attack_follows_movement = true; } } /* Player attack */ if(player->HP > 0 && attack && !player->current_attack) { int hit_number=0, effect=EFFECT_ATTACK_SLASH; /* If hitting within .25s of the previous hit ending, combo! */ if(abs(playerd->combo_delay) < fix(0.25)) hit_number = playerd->combo_next; playerd->combo_next = (hit_number + 1) % playerd->combo_length; if(hit_number == 0) effect = EFFECT_ATTACK_SLASH; if(hit_number == 1) effect = EFFECT_ATTACK_IMPALE; effect_area_t *area = effect_area_new_attack(effect, player, player->movement.facing); game_add_effect_area(&game, area); entity_set_anim(player, anims_player_Attack); player->current_attack = area; player->attack_follows_movement = true; playerd->combo_delay = area->lifetime; } /* Remove dead entities first as it will kill their attack areas */ game_remove_dead_entities(&game); game_update_effect_areas(&game, dt); game_update_particles(&game, dt); playerd->combo_delay -= dt; game.time_total += dt; game.time_wave += dt; if(game.time_defeat == 0 && player->HP == 0) game.time_defeat = game.time_total; if(game.time_victory == 0 && player->HP > 0 && game.entity_count == 1) game.time_victory = game.time_total; /* Next wave */ if(game.time_victory > 0 && game.time_total > game.time_victory+fix(2) && game.wave < game.level->wave_count) game_next_wave(&game); /* Visual pathfinding debug */ if(debug.show_path) { pfg_path_free(&debug.grid_path); pfc_path_free(&debug.continuous_path); fpoint_t target = point_i2f_center((ipoint_t){ 6, 9 }); debug.grid_path = pfg_bfs_outwards(&game.paths_to_player, point_f2i(target)); debug.continuous_path = pfc_shortcut_full(&debug.grid_path, entity_pos(player), target, player->hitbox); } prof_leave(perf_simul); time_simul = prof_time(perf_simul); } timer_stop(timer_id); prof_quit(); usb_close(); return 1; }