wave events and timeline preview
This commit is contained in:
parent
4a513cf1f5
commit
1653942111
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
86
src/game.c
86
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);
|
||||
|
|
26
src/game.h
26
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);
|
||||
|
|
16
src/level.h
16
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;
|
||||
|
||||
|
|
14
src/main.c
14
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) {
|
||||
|
|
81
src/render.c
81
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;
|
||||
|
|
Loading…
Reference in New Issue