RogueLife/src/main.c

483 lines
17 KiB
C

#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 <gint/display.h>
#include <gint/keyboard.h>
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#include <gint/cpu.h>
#include <gint/timer.h>
#include <gint/drivers/r61524.h>
#include <stdlib.h>
#include <string.h>
#include <fxlibc/printf.h>
#include <libprof.h>
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);
//---
// Load random entities on the map
//---
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) / 2,
};
for(int i = 0; i < 10; i++) {
int type = (rand() & 1) ? ENEMY_BAT : ENEMY_SLIME;
entity_t *e = enemy_spawn(type, 1);
if(!e) continue;
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;
}
e->movement.x = fix(x) + fix(1) / 2;
e->movement.y = fix(y) + fix(1) / 2;
game_add_entity(&game, e);
}
entity_t *player = game.entities[5];
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, anim_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);
if(game.time_victory != 0) dprint(1, 1, C_WHITE, "Victory in %.1f s!",
f2double(game.time_victory));
else if(game.time_defeat != 0) dprint(1, 1, C_WHITE, "Defeat! :(");
else dprint(1, 1, C_WHITE, "HP:%d ATK:%d DEF:%d dpc:%.1f",
player->HP, player->ATK, player->DEF,
f2double(player->movement.dash_particle_cooldown));
/* 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);
}
if(keydown(KEY_F6) && !keydown(KEY_VARS) && usb_is_open())
usb_fxlink_screenshot(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_update_animations(&game, dt);
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(1) / 2;
if(ev.key == KEY_LEFTP) {
emp_player.dash_speed -= fix(1) / 2;
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, anim_player_idle);
}
/* 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);
game_try_move_entity(&game, e, &next);
if(in_range && !e->current_attack) {
/* Attack */
frect_t hitbox = {
-fix(4)/16, fix(3)/16, -fix(4)/16, fix(3)/16,
};
hitbox = rect_rotate(hitbox, UP, e->movement.facing);
fpoint_t dir = fdir(e->movement.facing);
fpoint_t anchor = rect_center(entity_sprite(e));
anchor.x += fmul(fix(3)/4, dir.x);
anchor.y += fmul(fix(3)/4, dir.y);
effect_area_t *area = effect_area_new(EFFECT_HIT);
area->sprite = hitbox;
area->anchor = anchor;
area->lifetime = anim_duration(anim_hit);
area->repeat_delay = 0;
area->origin = e;
area->data.slash.strength = e->ATK;
area->data.slash.dir = e->movement.facing;
effect_area_set_anim(area, anim_hit);
game_add_effect_area(&game, area);
// entity_set_anim(player, anim_player_attack);
e->current_attack = area;
e->attack_follows_movement = true;
}
}
/* Player attack */
if(player->HP > 0 && attack && !player->current_attack) {
frect_t hitbox = {
-fix(5)/8, fix(5)/8, -fix(1)/4, fix(1)/4,
};
hitbox = rect_rotate(hitbox, UP, player->movement.facing);
fpoint_t dir = fdir(player->movement.facing);
fpoint_t anchor = rect_center(entity_sprite(player));
anchor.x += fmul(fix(3)/4, dir.x);
anchor.y += fmul(fix(3)/4, dir.y);
effect_area_t *area = effect_area_new(EFFECT_SLASH);
area->sprite = hitbox;
area->anchor = anchor;
area->lifetime = anim_duration(anim_swing);
area->repeat_delay = 0;
area->origin = player;
area->data.slash.strength = player->ATK;
area->data.slash.dir = player->movement.facing;
effect_area_set_anim(area, anim_swing[player->movement.facing]);
game_add_effect_area(&game, area);
entity_set_anim(player, anim_player_attack);
player->current_attack = area;
player->attack_follows_movement = true;
}
/* Remove dead entities first as it will kill their attack areas */
game_remove_dead_entities(&game);
game_update_effect_areas(&game, dt);
game_sort_entities(&game);
game_update_particles(&game, dt);
game.time_total += 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;
/* 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;
}