#include "game.h" #include "util.h" #include "enemies.h" #include 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); }