From 16539421115e843814f38c89c1ff99d3741b6c15 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Mon, 14 Mar 2022 21:50:24 +0000 Subject: [PATCH] wave events and timeline preview --- CMakeLists.txt | 1 + assets-cg/converters.py | 52 +++++++++++++++--------- assets-cg/levels/lv1.txt | 5 +-- src/game.c | 86 ++++++++++++++++++++++++++-------------- src/game.h | 26 ++++++------ src/level.h | 16 +++----- src/main.c | 14 ++----- src/render.c | 81 +++++++++++++++++++++++++------------ 8 files changed, 172 insertions(+), 109 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0185c36..ee2f999 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/assets-cg/converters.py b/assets-cg/converters.py index 7c78fa4..02e2e05 100644 --- a/assets-cg/converters.py +++ b/assets-cg/converters.py @@ -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): diff --git a/assets-cg/levels/lv1.txt b/assets-cg/levels/lv1.txt index 7071d5b..37e5f39 100644 --- a/assets-cg/levels/lv1.txt +++ b/assets-cg/levels/lv1.txt @@ -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 diff --git a/src/game.c b/src/game.c index 38a30d5..fc39f6c 100644 --- a/src/game.c +++ b/src/game.c @@ -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); diff --git a/src/game.h b/src/game.h index a6de39b..de34999 100644 --- a/src/game.h +++ b/src/game.h @@ -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); diff --git a/src/level.h b/src/level.h index 1685bb9..f605e80 100644 --- a/src/level.h +++ b/src/level.h @@ -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; diff --git a/src/main.c b/src/main.c index 198c6ed..32c04f7 100644 --- a/src/main.c +++ b/src/main.c @@ -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) { diff --git a/src/render.c b/src/render.c index e7757b4..1c5cfc8 100644 --- a/src/render.c +++ b/src/render.c @@ -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;