waves and enemy spawn sequences

This commit is contained in:
Lephenixnoir 2021-07-16 15:51:32 +02:00
parent 2c8f74cb2f
commit 72e154a618
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
9 changed files with 172 additions and 39 deletions

View File

@ -6,8 +6,8 @@ from PIL import Image, ImageChops
def convert(input, output, params, target):
recognized = True
if params["custom-type"] == "level":
o = convert_level(input, params)
if params["custom-type"] == "level_map":
o = convert_level_map(input, params)
elif params["custom-type"] == "animation":
o = convert_animation(input, params)
else:
@ -19,7 +19,7 @@ def convert(input, output, params, target):
return 1
def convert_level(input, params):
def convert_level_map(input, params):
RE_CATEGORY = re.compile(r'^\[([^\]]+)\]$', re.MULTILINE)
with open(input, "r") as fp:

View File

@ -1,3 +1,3 @@
*.txt:
custom-type: level
name_regex: (.*)\.txt lv_\1
custom-type: level_map
name_regex: (.*)\.txt lv_map_\1

View File

@ -1,5 +1,6 @@
#include "game.h"
#include "util.h"
#include "enemies.h"
#include <stdlib.h>
@ -8,8 +9,8 @@ bool game_load(game_t *g, level_t *level)
game_unload(g);
map_t *m = &g->map;
m->width = level->width;
m->height = level->height;
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);
@ -19,8 +20,8 @@ bool game_load(game_t *g, level_t *level)
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->tiles[level->width * y + x].base;
t->decor = level->tiles[level->width * y + x].decor;
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);
}
@ -37,7 +38,11 @@ bool game_load(game_t *g, level_t *level)
g->particles = NULL;
g->particle_count = 0;
g->level = level;
g->wave = 1;
g->wave_spawned = 0;
g->time_wave = fix(0);
return true;
}
@ -60,6 +65,22 @@ void game_unload(game_t *g)
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
//---
@ -232,6 +253,22 @@ void game_try_move_entity(game_t *g, entity_t *e,
// 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(1)/2, fix(s->y) + fix(1)/2 };
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++) {

View File

@ -35,8 +35,13 @@ typedef struct game {
int particle_count;
/* Field of movement to reach the player (used by most enemy AIs) */
pfg_all2one_t paths_to_player;
/* Current wave */
/* Level begin played */
level_t *level;
/* Current wave, number of enemies spawned in wave, time spent in wave */
int wave;
int wave_spawned;
fixed_t time_wave;
} game_t;
@ -46,6 +51,12 @@ bool game_load(game_t *g, level_t *level);
/* Free resources allocated for the level. */
void game_unload(game_t *g);
/* Current wave */
level_wave_t const *game_current_wave(game_t const *g);
/* Move to next wave */
void game_next_wave(game_t *g);
//---
// Adding dynamic game elements
//---
@ -73,12 +84,13 @@ void game_spawn_entity(game_t *g, entity_t *e, fpoint_t pos);
void game_try_move_entity(game_t *g, entity_t *e,
entity_movement_t const *next_movement);
//---
// Per-frame update functions
//---
/* Spawn enemies from the current wave. */
void game_spawn_enemies(game_t *g);
/* Update all entities' and effect areas' animations. */
void game_update_animations(game_t *g, fixed_t dt);

View File

@ -7,6 +7,7 @@
#include "fixed.h"
#include <stdbool.h>
#include <stdlib.h>
/* Directions */
enum { UP=0, RIGHT=1, DOWN=2, LEFT=3 };
@ -73,6 +74,15 @@ static inline fpoint_t fdir(int dir)
};
}
/* Direction closest to specified vector */
static inline int frdir(fpoint_t v)
{
if(v.x >= abs(v.y)) return RIGHT;
if(v.x <= -abs(v.y)) return LEFT;
if(v.y < 0) return UP;
return DOWN;
}
/* Normalize vector */
static inline fpoint_t fnormalize(fpoint_t v)
{

View File

@ -1 +1,35 @@
#include "level.h"
#include "enemies.h"
/* List of maps */
extern level_map_t lv_map_demo;
extern level_map_t lv_map_1;
level_t lv_demo = {
.map = &lv_map_demo,
};
level_t lv_1 = {
.map = &lv_map_1,
.wave_count = 2,
.waves = (level_wave_t []){
/* Wave 1: Just some slimes */
{ .enemy_count = 6,
.enemies = (level_wave_spawn_t []){
{ ENEMY_SLIME, 1, 2,1, FIX(0)/2 },
{ ENEMY_SLIME, 1, 19,9, FIX(1)/2 },
{ ENEMY_SLIME, 1, 2,1, FIX(2)/2 },
{ ENEMY_SLIME, 1, 19,9, FIX(3)/2 },
{ ENEMY_SLIME, 2, 2,1, FIX(4)/2 },
{ ENEMY_SLIME, 2, 19,9, FIX(5)/2 },
}},
/* Wave 2: Stronger slimes and a bat */
{ .enemy_count = 4,
.enemies = (level_wave_spawn_t []){
{ ENEMY_SLIME, 2, 2,1, FIX(0)/2 },
{ ENEMY_SLIME, 2, 19,9, FIX(0)/2 },
{ ENEMY_BAT, 1, 2,1, FIX(2)/2 },
{ ENEMY_BAT, 1, 19,9, FIX(2)/2 },
}},
},
};

View File

@ -9,7 +9,29 @@
#pragma once
#include "fixed.h"
#include <stdint.h>
#include <stdbool.h>
typedef struct {
/* Enemy type */
uint8_t identity;
/* Enemy level */
uint8_t level;
/* Spawn location */
uint8_t x, y;
/* Spawn time */
fixed_t time;
} level_wave_spawn_t;
typedef struct {
/* Number of enemies */
int enemy_count;
/* List of enemies */
level_wave_spawn_t *enemies;
} level_wave_t;
typedef struct {
/* Dimensions of the level (yeah no surprise right) */
@ -20,6 +42,15 @@ typedef struct {
uint8_t decor;
} tiles[];
} level_map_t;
typedef struct {
level_map_t *map;
/* Waves */
int wave_count;
level_wave_t *waves;
} level_t;
/* List of levels */

View File

@ -62,7 +62,7 @@ int main(void)
memset(&debug, 0, sizeof debug);
//---
// Load random entities on the map
// Spawn player
//---
entity_movement_params_t emp_player = {
@ -73,27 +73,22 @@ int main(void)
.dash_cooldown = fix(1),
};
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;
entity_t *player = enemy_spawn(ENEMY_BAT, 1);
int x=1, y=1;
for(int i = 0; i < 1000; i++) {
x = rand() % m->width;
y = rand() % m->height;
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_spawn_entity(&game, e, entity_pos(e));
struct tile *t = map_tile(m, x, y);
if(t && !t->solid) break;
}
player->movement.x = fix(x) + fix(1) / 2;
player->movement.y = fix(y) + fix(1) / 2;
entity_t *player = game.entities[5];
game_add_entity(&game, player);
game.player = player;
player->HP += 100;
player->movement_params = &emp_player;
player->identity = 0;
@ -133,12 +128,6 @@ int main(void)
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",
player->HP, player->ATK, player->DEF); */
/* Developer/tweaking menu */
if(debug.show_vars) {
uint32_t *vram = (void *)gint_vram;
@ -250,6 +239,8 @@ int main(void)
perf_simul = prof_make();
prof_enter(perf_simul);
game_spawn_enemies(&game);
game_update_animations(&game, dt);
key_event_t ev;
@ -402,8 +393,11 @@ int main(void)
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);
int facing = frdir((fpoint_t){
player->movement.x - e->movement.x,
player->movement.y - e->movement.y });
hitbox = rect_rotate(hitbox, UP, facing);
fpoint_t dir = fdir(facing);
fpoint_t anchor = rect_center(entity_sprite(e));
anchor.x += fmul(fix(3)/4, dir.x);
@ -461,12 +455,18 @@ int main(void)
game_update_particles(&game, 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);

View File

@ -208,10 +208,19 @@ void render_game(game_t const *g, bool show_hitboxes)
if(enemies_left > 0)
dprint_opt(DWIDTH / 2, 2, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP,
"Wave %d: %d enemies left", g->wave, enemies_left);
else
"Wave %d: %d/%d enemies left", g->wave, enemies_left,
g->level->waves[g->wave-1].enemy_count);
else if(g->wave_spawned >= game_current_wave(g)->enemy_count)
dprint_opt(DWIDTH / 2, 2, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP,
"Wave %d: Victory!", g->wave);
else
dprint_opt(DWIDTH / 2, 2, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP,
"Wave %d: Starting...", g->wave);
entity_t const *player = g->player;
dprint(2, 2, C_WHITE, "HP: %d", player->HP);
dprint_opt(DWIDTH - 2, 2, C_WHITE, C_NONE, DTEXT_RIGHT, DTEXT_TOP,
"ATK:%d DEF:%d", player->ATK, player->DEF);
dfont(old_font);
}