RogueLife/src/game.c

393 lines
10 KiB
C

#include "game.h"
#include "util.h"
#include "enemies.h"
#include "comp/fighter.h"
#include "comp/physical.h"
#include "comp/visible.h"
#include "aoe.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++) {
tile_t *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->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++)
entity_destroy(g->entities[i]);
free(g->entities);
g->entities = NULL;
g->entity_count = 0;
// TODo ECS: Remove particles
for(int i = 0; i < g->particle_count; i++)
free(g->particles[i]);
free(g->particles);
g->particles = NULL;
g->particle_count = 0;
}
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);
g->time_victory = 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)
{
game_add_entity(g, e);
fighter_t *f = getcomp(e, fighter);
if(!f || f->current_attack) return;
/* Teleport hitbox */
rect hitbox = {
-fix(8)/16, fix(7)/16, -fix(20)/16, fix(4)/16,
};
entity_t *spawn = aoe_make(EFFECT_SPAWN, physical_pos(e), fix(0));
getcomp(spawn, physical)->hitbox = hitbox;
aoe_t *aoe = getcomp(spawn, aoe);
aoe->lifetime = anim_duration(&anims_skill_teleport);
aoe->repeat_delay = 0;
aoe->origin = e;
visible_set_anim(spawn, &anims_skill_teleport, 2);
game_add_entity(g, spawn);
f->current_attack = spawn;
f->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_destroy(g->entities[i]);
g->entities[i] = g->entities[--g->entity_count];
}
void game_remove_dead_entities(game_t *g)
{
for(int i = 0; i < g->entity_count; i++) {
entity_t *e = g->entities[i];
/* First remove dead fighters */
fighter_t *f = getcomp(e, fighter);
visible_t *v = getcomp(e, visible);
bool anim_finished = !v || (v->anim.frame == NULL);
if(f && f->HP == 0 && f->identity != 0 && anim_finished) {
/* Disown remaining areas of effect */
fighter_t *f = getcomp(e, fighter);
if(f->current_attack) {
aoe_t *aoe = getcomp(f->current_attack, aoe);
aoe->origin = NULL;
}
entity_mark_to_delete(e);
}
/* Then remove areas of effect with expired lifetime */
aoe_t *aoe = getcomp(e, aoe);
if(aoe && aoe->lifetime <= 0) {
/* Notify origin of area removal */
if(aoe->origin) {
fighter_t *f = getcomp(aoe->origin, fighter);
f->current_attack = NULL;
f->attack_follows_movement = false;
}
entity_mark_to_delete(e);
}
}
int i = 0;
while(i < g->entity_count) {
if(g->entities[i]->deleted)
game_remove_entity(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 */
}
//---
// Generic entity functions
//---
int game_count_entities(game_t const *g, entity_predicate_t *predicate)
{
int total = 0;
for(int i = 0; i < g->entity_count; i++)
total += (predicate(g->entities[i]) != 0);
return total;
}
/* Not re-entrant, but we can deal with that */
static entity_measure_t *gse_measure = NULL;
static entity_t **gse_entities = NULL;
static int gse_compare(const void *ptr1, const void *ptr2)
{
int i1 = *(uint16_t *)ptr1;
int i2 = *(uint16_t *)ptr2;
return gse_measure(gse_entities[i1]) - gse_measure(gse_entities[i2]);
}
int game_sort_entities(game_t const *g, entity_measure_t *measure,
uint16_t **result)
{
int count = 0;
*result = NULL;
for(int i = 0; i < g->entity_count; i++)
count += (measure(g->entities[i]) >= 0);
if(count == 0)
return 0;
/* Initialize array with matching entities in storage order */
uint16_t *array = malloc(count * sizeof *array);
*result = array;
if(array == NULL)
return -1;
for(int i=0, j=0; i < g->entity_count; i++) {
if(measure(g->entities[i]) >= 0)
array[j++] = i;
}
/* Sort and return */
gse_measure = measure;
gse_entities = g->entities;
qsort(array, count, sizeof *array, gse_compare);
return count;
}
#if 0
// TODO ECS: New collision/ejection system based on teleports
void game_try_move_entity(game_t *g, entity_t *e,
entity_movement_t const * next)
{
entity_movement_t *m = &e->movement;
rect hitbox = rect_translate(e->hitbox, (vec2){ 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;
}
}
#endif
//---
// 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_make(s->identity, s->level);
physical_t *p = getcomp(e, physical);
p->x = fix(s->x) + fix(0.5);
p->y = fix(s->y) + fix(0.5);
game_spawn_entity(g, e);
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];
if(getcomp(e, visible))
visible_update(e, dt);
}
}
void game_update_aoes(game_t *g, fixed_t dt)
{
for(int i = 0; i < g->entity_count; i++) {
entity_t *e = g->entities[i];
aoe_t *aoe = getcomp(e, aoe);
if(aoe == NULL)
continue;
/* Movement and collisions, when relevant */
aoe_update(g, e, dt);
rect hitbox = physical_abs_hitbox(e);
// TODO ECS: Move collisions in a proper system
// TODO ECS: Quadratic collision check is a no-no for high performance
for(int i = 0; i < g->entity_count; i++) {
entity_t *target = g->entities[i];
physical_t *p = getcomp(target, physical);
if(p && rect_collide(hitbox, rect_translate(p->hitbox,
(vec2){ p->x, p->y })))
aoe_apply(g, e, target);
}
aoe->lifetime -= dt;
}
}
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];
mechanical_t *m = getcomp(e, mechanical);
if(m && mechanical_dashing(e)) {
particle_dash_t *p = malloc(sizeof *p);
p->particle.type = PARTICLE_DASH;
p->particle.age = 0;
p->particle.pos = physical_pos(e);
game_add_particle(g, &p->particle);
}
}
}
/* 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);
}