wave events and timeline preview

This commit is contained in:
Lephenixnoir 2022-03-14 21:50:24 +00:00
parent 4a513cf1f5
commit 1653942111
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
8 changed files with 172 additions and 109 deletions

View File

@ -51,6 +51,7 @@ set(ASSETS
assets-cg/menu_arrows.png
# HUD
assets-cg/hud.png
assets-cg/hud_event.png
assets-cg/hud_life.png
assets-cg/hud_stars.png
assets-cg/hud_window.png

View File

@ -87,17 +87,24 @@ def convert_level(input, params):
spawner_x += bytes([int(x)])
spawner_y += bytes([int(y)])
# Get list of waves; each wave is (duration, delay_after, monster list)
waves = []
# Get list of events; waves are (duration, monster list)
LEVEL_EVENT_DELAY = 0
LEVEL_EVENT_WAVE = 1
LEVEL_EVENT_ITEM = 2
event_count = 0
events = fxconv.Structure()
for key, value in lines:
if key == "delay":
delay = int(value[:-1])
duration, delay_after, monsters = waves[-1]
waves[-1] = (duration, delay_after+delay, monsters)
if key == "wave":
events += fxconv.u32(LEVEL_EVENT_DELAY)
events += fxconv.u32(delay * 65536)
events += bytes(4)
event_count += 1
elif key == "wave":
duration, *monsters = value.split()
duration = int(duration[:-1])
delay_after = 0
for i, desc in enumerate(monsters):
count, identity = desc.split("*")
if identity == "slime/1":
@ -111,19 +118,26 @@ def convert_level(input, params):
else:
raise fxconv.FxconvError(f"unknown monster {identity}")
monsters[i] = (identity, int(count))
waves.append((duration, delay_after, monsters))
# Turns waves into bytes
w = fxconv.Structure()
for duration, delay_after, monsters in waves:
m = bytes()
for identity, amount in monsters:
m += bytes([identity, amount])
m = bytes()
for identity, amount in monsters:
m += bytes([identity, amount])
w += fxconv.u32(len(monsters))
w += fxconv.ptr(m)
w += fxconv.u32(duration)
w += fxconv.u32(delay_after)
w = fxconv.Structure()
w += fxconv.u32(len(monsters))
w += fxconv.ptr(m)
events += fxconv.u32(LEVEL_EVENT_WAVE)
events += fxconv.u32(duration * 65536)
events += fxconv.ptr(w)
event_count += 1
elif key == "item":
raise fxconv.FxconvError("coming soon x3")
events += fxconv.u32(LEVEL_EVENT_ITEM)
events += fxconv.u32(2 * 65536)
events += fxconv.u32(the_item)
event_count += 1
o = fxconv.Structure()
o += fxconv.string(level_name)
@ -133,8 +147,8 @@ def convert_level(input, params):
o += fxconv.u32(len(spawner_x))
o += fxconv.ptr(spawner_x)
o += fxconv.ptr(spawner_y)
o += fxconv.u32(len(waves))
o += fxconv.ptr(w)
o += fxconv.u32(event_count)
o += fxconv.ptr(events)
return o
def convert_animation(input, params):

View File

@ -7,13 +7,12 @@ spawner: 18,5
spawner: 5,7
wave: 8s 8*slime/1
delay: 2s
wave: 12s 10*slime/1 4*bat/2
delay: 2s
wave: 4s 8*bat/2
delay: 2s
wave: 8s 16*slime/1 8*bat/2
delay: 6s
wave: 2s 8*slime/1 8*bat/2 2*fire_slime/4
delay: 2s
wave: 6s 8*slime/1 8*bat/2 2*fire_slime/4
delay: 2s
wave: 8s 12*bat/2 4*fire_slime/4 1*gunslinger/8

View File

@ -29,20 +29,21 @@ bool game_load(game_t *g, level_t const *level)
camera_init(&g->camera, g->map);
g->time_total = fix(0);
g->time_victory = fix(0);
g->time_defeat = fix(0);
g->entities = NULL;
g->entity_count = 0;
g->level = level;
g->wave = 0;
g->event = -1;
g->event_time = fix(0);
g->wave_number = 0;
g->wave_spawned = 0;
g->wave_left = NULL;
game_next_wave(g);
g->hud_xp_anim.frame = anims_hud_xp_Idle.start[0];
g->hud_xp_anim.elapsed = 0;
game_next_event(g);
return true;
}
@ -65,42 +66,67 @@ void game_unload(game_t *g)
g->wave_left = NULL;
}
level_wave_t const *game_current_wave(game_t const *g)
level_event_t const *game_current_event(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];
if(g->event < 0 || g->event >= g->level->event_count) return NULL;
return &g->level->events[g->event];
}
bool game_current_wave_finished(game_t const *g)
level_wave_t const *game_current_wave(game_t const *g)
{
level_wave_t const *wave = game_current_wave(g);
if(!wave || !g->wave_left) return false;
int wave_enemies = 0;
for(int i = 0; i < wave->entry_count; i++)
wave_enemies += wave->entries[i].amount;
return g->wave_spawned >= wave_enemies;
level_event_t const *event = game_current_event(g);
if(!event)
return NULL;
return (event->type == LEVEL_EVENT_WAVE) ? event->wave : NULL;
}
void game_next_wave(game_t *g)
bool game_current_event_finished(game_t const *g)
{
if(g->wave >= g->level->wave_count) return;
level_event_t const *event = game_current_event(g);
/* The implicit event #level->event_count+1 never finishes */
if(!event)
return false;
if(event->type == LEVEL_EVENT_DELAY) {
return g->event_time >= event->duration;
}
if(event->type == LEVEL_EVENT_WAVE) {
int wave_enemies = 0;
for(int i = 0; i < event->wave->entry_count; i++)
wave_enemies += event->wave->entries[i].amount;
return g->wave_spawned >= wave_enemies;
}
if(event->type == LEVEL_EVENT_ITEM) {
return g->event_time >= event->duration;
}
return false;
}
void game_next_event(game_t *g)
{
if(g->event >= g->level->event_count) return;
g->event++;
g->event_time = fix(0);
g->wave++;
g->wave_spawned = 0;
g->time_wave = fix(0);
g->time_victory = fix(0);
free(g->wave_left);
g->wave_left = NULL;
/* Copy the amounts of monsters to spawn for the next wave */
level_wave_t const *wave = game_current_wave(g);
g->wave_left = malloc(wave->entry_count * sizeof *g->wave_left);
if(!g->wave_left) return;
level_event_t const *event = game_current_event(g);
for(int i = 0; i < wave->entry_count; i++)
g->wave_left[i] = wave->entries[i].amount;
if(event && event->type == LEVEL_EVENT_WAVE) {
g->wave_number++;
/* Copy the amounts of monsters to spawn for the next wave */
g->wave_left = malloc(event->wave->entry_count * sizeof *g->wave_left);
if(!g->wave_left) return;
for(int i = 0; i < event->wave->entry_count; i++)
g->wave_left[i] = event->wave->entries[i].amount;
}
}
void game_shake(game_t *g, int amplitude, fixed_t duration)
@ -296,6 +322,7 @@ int game_sort_entities(game_t const *g, entity_measure_t *measure,
void game_spawn_enemies(game_t *g)
{
level_t const *level = g->level;
level_event_t const *event = game_current_event(g);
level_wave_t const *wave = game_current_wave(g);
if(!wave || !g->wave_left) return;
@ -303,13 +330,14 @@ void game_spawn_enemies(game_t *g)
for(int i = 0; i < wave->entry_count; i++)
wave_enemies += wave->entries[i].amount;
int current_time_ms = fround(g->time_wave);
int ev_duration_ms = fround(event->duration);
int current_time_ms = fround(g->event_time);
/* Keep spawning enemies until we're up-to-date. The time-based test
makes sure enemies are spawned somewhat regularly and the count-based
test makes sure we stop at the limit even if the delays don't align
exactly at the unit */
while(g->wave_spawned * wave->duration < wave_enemies * current_time_ms
while(g->wave_spawned * ev_duration_ms < wave_enemies * current_time_ms
&& g->wave_spawned < wave_enemies) {
/* Select one random enemy from those remaining */
int r = rand() % (wave_enemies - g->wave_spawned);

View File

@ -28,9 +28,6 @@ typedef struct game {
/* Time played */
fixed_t time_total;
/* Time when victory was reached or defeat was dealt */
fixed_t time_victory;
fixed_t time_defeat;
/* Screenshake duration left (effect disabled when 0), and amplitude */
fixed_t screenshake_duration;
int screenshake_amplitude;
@ -44,11 +41,13 @@ typedef struct game {
/* Level being played */
level_t const *level;
/* Current wave, number of enemies spawned in wave, time spent in wave */
int wave;
/* Current event, time spent in event */
int event;
fixed_t event_time;
/* Number of waves encountered so far; if current event is a wave, number of
enemies spawned so far and enemies left to spawn */
int wave_number;
int wave_spawned;
fixed_t time_wave;
/* Enemies left to spawn; has level->waves[wave]->entry_count elements */
uint8_t *wave_left;
/* XP bar animation */
@ -62,14 +61,17 @@ bool game_load(game_t *g, level_t const *level);
/* Free resources allocated for the level. */
void game_unload(game_t *g);
/* Current wave */
/* Current level event. */
level_event_t const *game_current_event(game_t const *g);
/* Current wave. NULL if the current event is not a wave. */
level_wave_t const *game_current_wave(game_t const *g);
/* Whether the current wave is finished */
bool game_current_wave_finished(game_t const *g);
/* Whether the current event is finished */
bool game_current_event_finished(game_t const *g);
/* Move to next wave */
void game_next_wave(game_t *g);
/* Move to next event */
void game_next_event(game_t *g);
/* Shake the screen for the specified amount of time */
void game_shake(game_t *g, int amplitude, fixed_t duration);

View File

@ -28,11 +28,6 @@ typedef struct {
uint8_t amount;
} *entries;
/* Duration in seconds */
int duration;
/* Delay after the wave in seconds */
int delay_after;
} level_wave_t;
enum {
@ -47,10 +42,11 @@ enum {
/* Levels are made of a sequence of simple events. */
typedef struct {
int type;
fixed_t duration;
union {
/* LEVEL_EVENT_DELAY */
int delay_s;
/* LEVEL_EVENT_DELAY: Nothing */
/* LEVEL_EVENT_WAVE */
level_wave_t *wave;
/* LEVEL_EVENT_ITEM */
@ -72,9 +68,9 @@ typedef struct {
uint8_t *spawner_x;
uint8_t *spawner_y;
/* Waves; the array is not terminated */
int wave_count;
level_wave_t *waves;
/* List of events (the array is not terminated) */
int event_count;
level_event_t *events;
} level_t;

View File

@ -558,19 +558,11 @@ int main(void)
}
game.time_total += dt;
game.time_wave += dt;
if(game.time_defeat == 0 && player_f->HP == 0)
game.time_defeat = game.time_total;
if(game.time_victory == 0 && player_f->HP > 0 &&
game_current_wave_finished(&game))
game.time_victory = game.time_total;
game.event_time += dt;
/* Next wave */
if(game.time_victory > 0 && game.time_total > game.time_victory +
fix(game_current_wave(&game)->delay_after)
&& game.wave < game.level->wave_count)
game_next_wave(&game);
if(player_f->HP > 0 && game_current_event_finished(&game))
game_next_event(&game);
/* Visual pathfinding debug */
if(debug.show_path) {

View File

@ -297,42 +297,73 @@ static void render_entities(game_t const *g, camera_t const *camera,
free(rendering_order);
}
static void render_wave_info(game_t const *g, int y0)
static void render_info_wave(int x0, int y0, level_event_t const *event, int w)
{
level_t const *level = g->level;
level_wave_t const *wave = game_current_wave(g);
int wave_enemies = 0;
for(int i = 0; i < wave->entry_count; i++)
wave_enemies += wave->entries[i].amount;
level_wave_t const *wave = event->wave;
dprint(2, y0+2, C_WHITE, "Wave %d (%d left)",
g->wave, wave_enemies - g->wave_spawned);
int x = x0 + 5;
int y = y0 + 8;
if(g->wave + 1 > level->wave_count)
return;
level_wave_t const *next_wave = &level->waves[g->wave+1 - 1];
int x = DWIDTH - 2;
int y = y0 + 10;
for(int i = next_wave->entry_count - 1; i >= 0; i--) {
enemy_t const *enemy = enemy_data(next_wave->entries[i].identity);
int amount = next_wave->entries[i].amount;
for(int i = 0; i < wave->entry_count; i++) {
enemy_t const *enemy = enemy_data(wave->entries[i].identity);
int amount = wave->entries[i].amount;
int text_w;
font_damage_size(amount, &text_w, NULL);
anim_frame_t *frame = enemy->anim_idle->start[0];
dsubimage(x - frame->w - text_w + 4, y - frame->h / 2, frame->sheet,
dsubimage(x, y - frame->h / 2, frame->sheet,
frame->x, frame->y, frame->w, frame->h, DIMAGE_NONE);
font_damage_print(x - text_w, y + frame->h / 2 - 4, C_WHITE,
font_damage_print(x + frame->w - 4, y + frame->h / 2 - 4, C_WHITE,
DTEXT_LEFT, DTEXT_TOP, amount);
x = x - (text_w-4) - frame->w - 4;
x += frame->w - 6 + text_w;
}
}
static void render_info_item(int x, int y, level_event_t const *event, int w)
{
dprint(x, y+3, C_WHITE, "Item");
}
static void render_info_bg(int x, int y, int w)
{
extern bopti_image_t img_hud_event;
int h = img_hud_event.height;
dsubimage(x, y, &img_hud_event, 0, 0, 8, h, DIMAGE_NONE);
x += 8; w -= 8;
while(w >= 24) {
dsubimage(x, y, &img_hud_event, 8, 0, 16, h, DIMAGE_NONE);
x += 16; w -= 16;
}
dtext_opt(x-2, y, C_WHITE, C_NONE, DTEXT_RIGHT, DTEXT_MIDDLE, "Next:");
dsubimage(x, y, &img_hud_event, 32-w, 0, w, h, DIMAGE_NONE);
}
static void render_info(int y, game_t const *g)
{
int const PX_PER_SECOND = 12;
int x = fround(-g->time_total * PX_PER_SECOND);
for(int i = 0; i < g->level->event_count && x <= DWIDTH; i++) {
level_event_t const *event = &g->level->events[i];
int w = fround(event->duration * PX_PER_SECOND);
if(i > g->event && x + w > 0) {
if(event->type != LEVEL_EVENT_DELAY)
render_info_bg(x, y, w);
if(event->type == LEVEL_EVENT_WAVE)
render_info_wave(x, y, event, w);
if(event->type == LEVEL_EVENT_ITEM)
render_info_item(x, y, event, w);
}
x += w;
}
}
void render_window(int x, int y, int w, int h)
@ -425,10 +456,10 @@ void render_game(game_t const *g, bool show_hitboxes)
if(g->time_total < MAX_GUI_TIME)
gui_time = g->time_total;
int HUD_Y = cubic(DHEIGHT+30, DHEIGHT, gui_time, MAX_GUI_TIME);
int HEADER_Y = cubic(-15, 0, gui_time, MAX_GUI_TIME);
int HEADER_Y = cubic(-15, 2, gui_time, MAX_GUI_TIME);
/* Render wave information */
render_wave_info(g, HEADER_Y);
render_info(HEADER_Y, g);
/* Render HUD */
extern bopti_image_t img_hud;