RogueLife/src/game.c

359 lines
9.1 KiB
C

#include "game.h"
#include "util.h"
#include "enemies.h"
#include <stdlib.h>
bool game_load(game_t *g, level_t *level)
{
game_unload(g);
map_t *m = &g->map;
m->width = level->map->width;
m->height = level->map->height;
m->tiles = malloc(m->width * m->height * sizeof *m->tiles);
if(!m->tiles) {
game_unload(g);
return false;
}
for(int y = 0; y < m->height; y++)
for(int x = 0; x < m->width; x++) {
struct tile *t = map_tile(m, x, y);
t->base = level->map->tiles[level->map->width * y + x].base;
t->decor = level->map->tiles[level->map->width * y + x].decor;
t->solid = (t->base == 0) || (t->base >= 16);
}
camera_init(&g->camera, m);
g->time_total = fix(0);
g->time_victory = fix(0);
g->time_defeat = fix(0);
g->entities = NULL;
g->entity_count = 0;
g->effect_areas = NULL;
g->effect_area_count = 0;
g->particles = NULL;
g->particle_count = 0;
g->level = level;
g->wave = 1;
g->wave_spawned = 0;
g->time_wave = fix(0);
return true;
}
void game_unload(game_t *g)
{
g->map.width = 0;
g->map.height = 0;
free(g->map.tiles);
for(int i = 0; i < g->entity_count; i++)
free(g->entities[i]);
free(g->entities);
for(int i = 0; i < g->effect_area_count; i++)
free(g->effect_areas[i]);
free(g->effect_areas);
for(int i = 0; i < g->particle_count; i++)
free(g->particles[i]);
free(g->particles);
}
level_wave_t const *game_current_wave(game_t const *g)
{
if(!g->level) return NULL;
if(g->wave < 1 || g->wave > g->level->wave_count) return NULL;
return &g->level->waves[g->wave-1];
}
void game_next_wave(game_t *g)
{
if(g->wave >= g->level->wave_count) return;
g->wave++;
g->wave_spawned = 0;
g->time_wave = fix(0);
}
//---
// Object management functions
//---
void game_add_entity(game_t *g, entity_t *entity)
{
size_t new_size = (g->entity_count + 1) * sizeof *g->entities;
entity_t **new_entities = realloc(g->entities, new_size);
if(!new_entities) return;
g->entities = new_entities;
g->entities[g->entity_count] = entity;
g->entity_count++;
}
void game_spawn_entity(game_t *g, entity_t *e, fpoint_t pos)
{
game_add_entity(g, e);
e->movement.x = pos.x;
e->movement.y = pos.y;
if(e->current_attack) return;
/* Teleport hitbox */
frect_t hitbox = {
-fix(8)/16, fix(7)/16, -fix(20)/16, fix(4)/16,
};
effect_area_t *area = effect_area_new(EFFECT_SPAWN);
area->sprite = hitbox;
area->anchor = entity_pos(e);
area->lifetime = anim_duration(anim_teleport);
area->repeat_delay = 0;
area->origin = e;
effect_area_set_anim(area, anim_teleport);
game_add_effect_area(g, area);
e->current_attack = area;
e->attack_follows_movement = true;
}
/* Remove an entity and rearrange the array. */
static void game_remove_entity(game_t *g, int i)
{
if(i < 0 || i >= g->entity_count) return;
entity_t *e = g->entities[i];
if(e->current_attack) {
/* Kill the effect area */
e->current_attack->lifetime = fix(0);
}
free(g->entities[i]);
g->entities[i] = g->entities[--g->entity_count];
}
void game_remove_dead_entities(game_t *g)
{
int i = 0;
while(i < g->entity_count) {
entity_t *e = g->entities[i];
if(e->HP == 0 && !e->anim.frame && e->identity != 0)
game_remove_entity(g, i);
else i++;
}
}
void game_add_effect_area(game_t *g, effect_area_t *ea)
{
size_t new_size = (g->effect_area_count + 1) * sizeof *g->effect_areas;
effect_area_t **new_effect_areas = realloc(g->effect_areas, new_size);
if(!new_effect_areas) return;
g->effect_areas = new_effect_areas;
g->effect_areas[g->effect_area_count] = ea;
g->effect_area_count++;
}
/* Remove an effect area and rearrange the array. */
static void game_remove_effect_area(game_t *g, int i)
{
if(i < 0 || i >= g->effect_area_count) return;
effect_area_t *ea = g->effect_areas[i];
if(ea->origin && ea->origin->current_attack == ea) {
ea->origin->current_attack = NULL;
ea->origin->attack_follows_movement = false;
}
free(g->effect_areas[i]->hits);
free(g->effect_areas[i]);
g->effect_areas[i] = g->effect_areas[--g->effect_area_count];
/* Don't realloc, we'll likely add new areas soon enough and space will be
reclaimed as needed at that time. Don't need to add heap work now. */
}
/* Remove all dead effect areas */
static void game_remove_dead_effect_areas(game_t *g)
{
int i = 0;
while(i < g->effect_area_count) {
effect_area_t *ea = g->effect_areas[i];
if(ea->lifetime <= 0) game_remove_effect_area(g, i);
else i++;
}
}
void game_add_particle(game_t *g, particle_t *p)
{
size_t new_size = (g->particle_count + 1) * sizeof *g->particles;
particle_t **new_particles = realloc(g->particles, new_size);
if(!new_particles) return;
g->particles = new_particles;
g->particles[g->particle_count] = p;
g->particle_count++;
}
/* Remove a particle and rearrange the array. */
static void game_remove_particle(game_t *g, int i)
{
if(i < 0 || i >= g->particle_count) return;
free(g->particles[i]);
g->particles[i] = g->particles[--g->particle_count];
/* Again, don't realloc to save on useless heap movement */
}
//---
// Interacting with game elements
//---
void game_try_move_entity(game_t *g, entity_t *e,
entity_movement_t const * next)
{
entity_movement_t *m = &e->movement;
frect_t hitbox = rect_translate(e->hitbox, (fpoint_t){ next->x, next->y });
if(!map_collides(&g->map, hitbox)) {
/* Movement is allowed */
fixed_t dx = next->x - m->x;
fixed_t dy = next->y - m->y;
/* Update attached attack animation */
if(next->facing != m->facing) {
e->attack_follows_movement = false;
}
if(e->current_attack && e->attack_follows_movement) {
e->current_attack->anchor.x += dx;
e->current_attack->anchor.y += dy;
}
*m = *next;
}
else {
/* Movement is denied. Halve speed so that high-speed movement doesn't
halt at a large distance from a wall. */
m->facing = next->facing;
m->vx = next->vx / 2;
m->vy = next->vy / 2;
m->dash = next->dash;
}
}
//---
// Per-frame update functions
//---
void game_spawn_enemies(game_t *g)
{
level_wave_t const *wave = game_current_wave(g);
if(!wave) return;
for(int i = g->wave_spawned; i < wave->enemy_count; i++) {
level_wave_spawn_t *s = &wave->enemies[i];
if(s->time > g->time_wave) break;
entity_t *e = enemy_spawn(s->identity, s->level);
fpoint_t pos = { fix(s->x) + fix(0.5), fix(s->y) + fix(0.5) };
game_spawn_entity(g, e, pos);
g->wave_spawned++;
}
}
void game_update_animations(game_t *g, fixed_t dt)
{
for(int i = 0; i < g->entity_count; i++) {
entity_t *e = g->entities[i];
anim_state_update(&e->anim, dt);
if(e->anim.frame == NULL)
e->anim_priority = 0;
}
for(int i = 0; i < g->effect_area_count; i++) {
effect_area_t *ea = g->effect_areas[i];
anim_state_update(&ea->anim, dt);
}
}
void game_update_effect_areas(game_t *g, fixed_t dt)
{
for(int i = 0; i < g->effect_area_count; i++) {
effect_area_t *ea = g->effect_areas[i];
frect_t hitbox = rect_translate(ea->sprite, ea->anchor);
for(int i = 0; i < g->entity_count; i++) {
entity_t *e = g->entities[i];
if(rect_collide(hitbox, entity_sprite(e)))
effect_area_apply(g, ea, e);
}
ea->lifetime -= dt;
}
game_remove_dead_effect_areas(g);
}
void game_update_particles(game_t *g, fixed_t dt)
{
int i = 0;
while(i < g->particle_count) {
bool remove = particle_update(g->particles[i], dt);
if(remove) game_remove_particle(g, i);
else i++;
}
/* Spawn dash particles */
for(int i = 0; i < g->entity_count; i++) {
entity_t *e = g->entities[i];
if(entity_dashing(e)) {
particle_dash_t *p = malloc(sizeof *p);
p->particle.type = PARTICLE_DASH;
p->particle.age = 0;
p->particle.pos = entity_pos(e);
game_add_particle(g, &p->particle);
}
}
}
/* Compare the y values of two entities. */
static int game_sort_entities_compare(void const *p1, void const *p2)
{
entity_t const *e1 = *(entity_t const **)p1;
entity_t const *e2 = *(entity_t const **)p2;
return e1->movement.y - e2->movement.y;
}
void game_sort_entities(game_t *g)
{
heap_sort(g->entities, g->entity_count, sizeof *g->entities,
game_sort_entities_compare);
}
/* Compare the y values of two particles. */
static int game_sort_particles_compare(void const *v1, void const *v2)
{
particle_t const *p1 = *(particle_t const **)v1;
particle_t const *p2 = *(particle_t const **)v2;
return p1->pos.y - p2->pos.y;
}
void game_sort_particles(game_t *g)
{
heap_sort(g->particles, g->particle_count, sizeof *g->particles,
game_sort_particles_compare);
}