progress: track per-plrmode stage stats
This commit is contained in:
parent
efb234284d
commit
a952fb1426
5 changed files with 274 additions and 70 deletions
|
@ -502,7 +502,8 @@ static void draw_spell_name(Boss *b, int time, bool healthbar_radial) {
|
|||
bool kern = font_get_kerning_enabled(font);
|
||||
font_set_kerning_enabled(font, false);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%u / %u", p->num_cleared, p->num_played);
|
||||
// TODO: display plrmode-specific data?
|
||||
snprintf(buf, sizeof(buf), "%u / %u", p->global.num_cleared, p->global.num_played);
|
||||
|
||||
draw_boss_text(ALIGN_RIGHT,
|
||||
VIEWPORT_W - 10 - text_width(font, buf, 0), 0,
|
||||
|
@ -1055,7 +1056,7 @@ void boss_finish_current_attack(Boss *boss) {
|
|||
StageProgress *p = get_spellstage_progress(boss->current, NULL, true);
|
||||
|
||||
if(p) {
|
||||
++p->num_cleared;
|
||||
progress_register_stage_cleared(p, global.plr.mode);
|
||||
}
|
||||
|
||||
// HACK
|
||||
|
@ -1387,12 +1388,12 @@ void boss_start_next_attack(Boss *b, Attack *a) {
|
|||
StageProgress *p = get_spellstage_progress(a, &i, true);
|
||||
|
||||
if(p) {
|
||||
++p->num_played;
|
||||
|
||||
if(!p->unlocked) {
|
||||
log_info("Spellcard unlocked! %s: %s", i->title, i->subtitle);
|
||||
p->unlocked = true;
|
||||
}
|
||||
|
||||
progress_register_stage_played(p, global.plr.mode);
|
||||
}
|
||||
|
||||
// This should go before a->rule(b,EVENT_BIRTH), so it doesn’t reset values set by the attack rule.
|
||||
|
|
|
@ -43,6 +43,10 @@ typedef enum {
|
|||
PLR_SHOT_REIMU_DREAM = PLR_SHOT_B,
|
||||
} ShotModeID;
|
||||
|
||||
enum {
|
||||
NUM_PLAYER_MODES = NUM_CHARACTERS * NUM_SHOT_MODES_PER_CHARACTER,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
// vpu = viewport units
|
||||
|
||||
|
@ -92,10 +96,6 @@ typedef struct PlayerMode {
|
|||
} procs;
|
||||
} PlayerMode;
|
||||
|
||||
enum {
|
||||
NUM_PLAYER_MODES = NUM_CHARACTERS * NUM_SHOT_MODES_PER_CHARACTER,
|
||||
};
|
||||
|
||||
PlayerCharacter *plrchar_get(CharacterID id);
|
||||
void plrchar_preload(PlayerCharacter *pc, ResourceGroup *rg);
|
||||
void plrchar_render_bomb_portrait(PlayerCharacter *pc, Sprite *out_spr);
|
||||
|
|
287
src/progress.c
287
src/progress.c
|
@ -68,6 +68,9 @@ typedef enum ProgfileCommand {
|
|||
|
||||
// Sets the high-score (64-bit value)
|
||||
PCMD_HISCORE_64BIT = 0x09,
|
||||
|
||||
// Sets the high scores and per-plrmode play/clear counters for stage/difficulty combinations
|
||||
PCMD_STAGE_PLAYINFO2 = 0x10,
|
||||
} ProgfileCommand;
|
||||
|
||||
/*
|
||||
|
@ -107,6 +110,136 @@ typedef struct UnknownCmd {
|
|||
uint8_t *data;
|
||||
} UnknownCmd;
|
||||
|
||||
#define PLAYINFO_HEADER_FIELDS \
|
||||
PLAYINFO_FIELD(uint16_t, stage) \
|
||||
PLAYINFO_FIELD(uint8_t, diff) \
|
||||
|
||||
#define PLAYINFO_FIELDS \
|
||||
PLAYINFO_FIELD(uint32_t, num_played) \
|
||||
PLAYINFO_FIELD(uint32_t, num_cleared) \
|
||||
|
||||
#define PLAYINFO2_FIELDS_GLOBAL \
|
||||
PLAYINFO_FIELD(uint64_t, hiscore) \
|
||||
|
||||
#define PLAYINFO2_FIELDS_PERMODE \
|
||||
PLAYINFO_FIELD(uint32_t, num_played) \
|
||||
PLAYINFO_FIELD(uint32_t, num_cleared) \
|
||||
PLAYINFO_FIELD(uint64_t, hiscore) \
|
||||
|
||||
#define WRITEFUNC_uint64_t SDL_WriteLE64
|
||||
#define WRITEFUNC_uint32_t SDL_WriteLE32
|
||||
#define WRITEFUNC_uint16_t SDL_WriteLE16
|
||||
#define WRITEFUNC_uint8_t SDL_WriteU8
|
||||
#define WRITEFUNC(t) WRITEFUNC_##t
|
||||
|
||||
#define READFUNC_uint64_t SDL_ReadLE64
|
||||
#define READFUNC_uint32_t SDL_ReadLE32
|
||||
#define READFUNC_uint16_t SDL_ReadLE16
|
||||
#define READFUNC_uint8_t SDL_ReadU8
|
||||
#define READFUNC(t) READFUNC_##t
|
||||
|
||||
#define PLAYINFO_FIELD(t,f) + sizeof(t)
|
||||
|
||||
INLINE size_t playinfo_header_size(void) {
|
||||
return PLAYINFO_HEADER_FIELDS;
|
||||
}
|
||||
|
||||
INLINE size_t playinfo_size(uint num_entries) {
|
||||
return num_entries * (PLAYINFO_HEADER_FIELDS PLAYINFO_FIELDS);
|
||||
}
|
||||
|
||||
INLINE size_t playinfo2_size(uint num_entries) {
|
||||
return num_entries * (
|
||||
PLAYINFO_HEADER_FIELDS PLAYINFO2_FIELDS_GLOBAL
|
||||
+ NUM_PLAYER_MODES * (PLAYINFO2_FIELDS_PERMODE)
|
||||
);
|
||||
}
|
||||
|
||||
#undef PLAYINFO_FIELD
|
||||
|
||||
typedef struct PlayInfoHeader {
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
t f;
|
||||
PLAYINFO_HEADER_FIELDS
|
||||
#undef PLAYINFO_FIELD
|
||||
} PlayInfoHeader;
|
||||
|
||||
static size_t playinfo_header_read(SDL_RWops *vfile, PlayInfoHeader *h) {
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
h->f = READFUNC(t)(vfile);
|
||||
PLAYINFO_HEADER_FIELDS
|
||||
#undef PLAYINFO_FIELD
|
||||
return playinfo_header_size();
|
||||
}
|
||||
|
||||
static size_t playinfo_header_write(SDL_RWops *vfile, const PlayInfoHeader *h) {
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
WRITEFUNC(t)(vfile, h->f);
|
||||
PLAYINFO_HEADER_FIELDS
|
||||
#undef PLAYINFO_FIELD
|
||||
return playinfo_header_size();
|
||||
}
|
||||
|
||||
static size_t playinfo_data_read(SDL_RWops *vfile, StageProgress *p) {
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
p->global.f = READFUNC(t)(vfile);
|
||||
PLAYINFO_FIELDS
|
||||
#undef PLAYINFO_FIELD
|
||||
return playinfo_size(1) - playinfo_header_size();
|
||||
}
|
||||
|
||||
static size_t playinfo_data_write(SDL_RWops *vfile, const StageProgress *p) {
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
WRITEFUNC(t)(vfile, p->global.f);
|
||||
PLAYINFO_FIELDS
|
||||
#undef PLAYINFO_FIELD
|
||||
return playinfo_size(1) - playinfo_header_size();
|
||||
}
|
||||
|
||||
static size_t playinfo2_data_read(SDL_RWops *vfile, StageProgress *p) {
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
p->global.f = READFUNC(t)(vfile);
|
||||
PLAYINFO2_FIELDS_GLOBAL
|
||||
#undef PLAYINFO_FIELD
|
||||
|
||||
static_assert(ARRAY_SIZE(p->per_plrmode) == NUM_CHARACTERS);
|
||||
static_assert(ARRAY_SIZE(p->per_plrmode[0]) == NUM_SHOT_MODES_PER_CHARACTER);
|
||||
|
||||
for(int chr = 0; chr < ARRAY_SIZE(p->per_plrmode); ++chr) {
|
||||
for(int mod = 0; mod < ARRAY_SIZE(p->per_plrmode[chr]); ++mod) {
|
||||
StageProgressContents *c = &p->per_plrmode[chr][mod];
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
c->f = READFUNC(t)(vfile);
|
||||
PLAYINFO2_FIELDS_PERMODE
|
||||
#undef PLAYINFO_FIELD
|
||||
}
|
||||
}
|
||||
|
||||
return playinfo2_size(1) - playinfo_header_size();
|
||||
}
|
||||
|
||||
static size_t playinfo2_data_write(SDL_RWops *vfile, const StageProgress *p) {
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
WRITEFUNC(t)(vfile, p->global.f);
|
||||
PLAYINFO2_FIELDS_GLOBAL
|
||||
#undef PLAYINFO_FIELD
|
||||
|
||||
static_assert(ARRAY_SIZE(p->per_plrmode) == NUM_CHARACTERS);
|
||||
static_assert(ARRAY_SIZE(p->per_plrmode[0]) == NUM_SHOT_MODES_PER_CHARACTER);
|
||||
|
||||
for(int chr = 0; chr < ARRAY_SIZE(p->per_plrmode); ++chr) {
|
||||
for(int mod = 0; mod < ARRAY_SIZE(p->per_plrmode[chr]); ++mod) {
|
||||
const StageProgressContents *c = &p->per_plrmode[chr][mod];
|
||||
#define PLAYINFO_FIELD(t,f) \
|
||||
WRITEFUNC(t)(vfile, c->f);
|
||||
PLAYINFO2_FIELDS_PERMODE
|
||||
#undef PLAYINFO_FIELD
|
||||
}
|
||||
}
|
||||
|
||||
return playinfo2_size(1) - playinfo_header_size();
|
||||
}
|
||||
|
||||
static bool progress_read_verify_cmd_size(SDL_RWops *vfile, uint8_t cmd, uint16_t cmdsize, uint16_t expectsize) {
|
||||
if(cmdsize == expectsize) {
|
||||
return true;
|
||||
|
@ -175,6 +308,8 @@ static void progress_read(SDL_RWops *file) {
|
|||
uint16_t cur = 0;
|
||||
uint16_t cmdsize = SDL_ReadLE16(vfile);
|
||||
|
||||
log_debug("at %i: %i (%i)", cur, cmd, cmdsize);
|
||||
|
||||
switch(cmd) {
|
||||
case PCMD_UNLOCK_STAGES:
|
||||
while(cur < cmdsize) {
|
||||
|
@ -215,21 +350,33 @@ static void progress_read(SDL_RWops *file) {
|
|||
|
||||
case PCMD_STAGE_PLAYINFO:
|
||||
while(cur < cmdsize) {
|
||||
uint16_t stg = SDL_ReadLE16(vfile); cur += sizeof(uint16_t);
|
||||
Difficulty diff = SDL_ReadU8(vfile); cur += sizeof(uint8_t);
|
||||
StageProgress *p = stageinfo_get_progress_by_id(stg, diff, true);
|
||||
PlayInfoHeader h = {};
|
||||
cur += playinfo_header_read(vfile, &h);
|
||||
StageProgress *p = stageinfo_get_progress_by_id(h.stage, h.diff, true);
|
||||
StageProgress discard;
|
||||
|
||||
// assert(p != NULL);
|
||||
|
||||
uint32_t np = SDL_ReadLE32(vfile); cur += sizeof(uint32_t);
|
||||
uint32_t nc = SDL_ReadLE32(vfile); cur += sizeof(uint32_t);
|
||||
|
||||
if(p) {
|
||||
p->num_played = np;
|
||||
p->num_cleared = nc;
|
||||
} else {
|
||||
log_warn("Invalid stage %X ignored", stg);
|
||||
if(!p) {
|
||||
log_warn("Invalid stage %X ignored", h.stage);
|
||||
p = &discard;
|
||||
}
|
||||
|
||||
cur += playinfo_data_read(vfile, p);
|
||||
}
|
||||
break;
|
||||
|
||||
case PCMD_STAGE_PLAYINFO2:
|
||||
while(cur < cmdsize) {
|
||||
PlayInfoHeader h = {};
|
||||
cur += playinfo_header_read(vfile, &h);
|
||||
StageProgress *p = stageinfo_get_progress_by_id(h.stage, h.diff, true);
|
||||
StageProgress discard;
|
||||
|
||||
if(!p) {
|
||||
log_warn("Invalid stage %X ignored", h.stage);
|
||||
p = &discard;
|
||||
}
|
||||
|
||||
cur += playinfo2_data_read(vfile, p);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -473,20 +620,16 @@ static void progress_write_cmd_hiscore(SDL_RWops *vfile, void **arg) {
|
|||
}
|
||||
|
||||
//
|
||||
// PCMD_STAGE_PLAYINFO
|
||||
// PCMD_STAGE_PLAYINFO / PCMD_STAGE_PLAYINFO2
|
||||
//
|
||||
|
||||
struct cmd_stage_playinfo_data_elem {
|
||||
LIST_INTERFACE(struct cmd_stage_playinfo_data_elem);
|
||||
uint16_t stage;
|
||||
uint8_t diff;
|
||||
uint32_t num_played;
|
||||
uint32_t num_cleared;
|
||||
PlayInfoHeader head;
|
||||
StageProgress *prog;
|
||||
};
|
||||
|
||||
struct cmd_stage_playinfo_data {
|
||||
size_t size;
|
||||
struct cmd_stage_playinfo_data_elem *elems;
|
||||
DYNAMIC_ARRAY(struct cmd_stage_playinfo_data_elem) elems;
|
||||
};
|
||||
|
||||
static void progress_prepare_cmd_stage_playinfo(size_t *bufsize, void **arg) {
|
||||
|
@ -507,45 +650,48 @@ static void progress_prepare_cmd_stage_playinfo(size_t *bufsize, void **arg) {
|
|||
for(Difficulty d = d_first; d <= d_last; ++d) {
|
||||
StageProgress *p = stageinfo_get_progress(stg, d, false);
|
||||
|
||||
if(p && (p->num_played || p->num_cleared)) {
|
||||
auto e = ALLOC(struct cmd_stage_playinfo_data_elem);
|
||||
|
||||
e->stage = stg->id; data->size += sizeof(uint16_t);
|
||||
e->diff = d; data->size += sizeof(uint8_t);
|
||||
e->num_played = p->num_played; data->size += sizeof(uint32_t);
|
||||
e->num_cleared = p->num_cleared; data->size += sizeof(uint32_t);
|
||||
|
||||
(void)list_push(&data->elems, e);
|
||||
if(p && (p->global.num_played || p->global.num_cleared)) {
|
||||
auto e = dynarray_append(&data->elems);
|
||||
e->head.stage = stg->id;
|
||||
e->head.diff = d;
|
||||
e->prog = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*arg = data;
|
||||
|
||||
if(data->size) {
|
||||
*bufsize += CMD_HEADER_SIZE + data->size;
|
||||
if(data->elems.num_elements) {
|
||||
*bufsize += CMD_HEADER_SIZE + playinfo_size(data->elems.num_elements);
|
||||
*bufsize += CMD_HEADER_SIZE + playinfo2_size(data->elems.num_elements);
|
||||
}
|
||||
}
|
||||
|
||||
static void progress_write_cmd_stage_playinfo(SDL_RWops *vfile, void **arg) {
|
||||
struct cmd_stage_playinfo_data *data = *arg;
|
||||
|
||||
if(!data->size) {
|
||||
if(!data->elems.num_elements) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
SDL_WriteU8(vfile, PCMD_STAGE_PLAYINFO);
|
||||
SDL_WriteLE16(vfile, data->size);
|
||||
SDL_WriteLE16(vfile, playinfo_size(data->elems.num_elements));
|
||||
|
||||
for(struct cmd_stage_playinfo_data_elem *e = data->elems; e; e = e->next) {
|
||||
SDL_WriteLE16(vfile, e->stage);
|
||||
SDL_WriteU8(vfile, e->diff);
|
||||
SDL_WriteLE32(vfile, e->num_played);
|
||||
SDL_WriteLE32(vfile, e->num_cleared);
|
||||
}
|
||||
dynarray_foreach_elem(&data->elems, auto e, {
|
||||
playinfo_header_write(vfile, &e->head);
|
||||
playinfo_data_write(vfile, e->prog);
|
||||
});
|
||||
|
||||
SDL_WriteU8(vfile, PCMD_STAGE_PLAYINFO2);
|
||||
SDL_WriteLE16(vfile, playinfo2_size(data->elems.num_elements));
|
||||
|
||||
dynarray_foreach_elem(&data->elems, auto e, {
|
||||
playinfo_header_write(vfile, &e->head);
|
||||
playinfo2_data_write(vfile, e->prog);
|
||||
});
|
||||
|
||||
cleanup:
|
||||
list_free_all(&data->elems);
|
||||
dynarray_free_data(&data->elems);
|
||||
mem_free(data);
|
||||
}
|
||||
|
||||
|
@ -914,3 +1060,62 @@ bool progress_is_cutscene_unlocked(CutsceneID id) {
|
|||
void progress_unlock_cutscene(CutsceneID id) {
|
||||
progress.unlocked_cutscenes |= progress_cutscene_bit(id);
|
||||
}
|
||||
|
||||
attr_returns_nonnull attr_nonnull_all
|
||||
static StageProgressContents *stage_plrmode_data(StageProgress *p, PlayerMode *pm) {
|
||||
uint ichar = pm->character->id;
|
||||
uint imode = pm->shot_mode;
|
||||
|
||||
assert(ichar < ARRAY_SIZE(p->per_plrmode));
|
||||
assert(imode < ARRAY_SIZE(p->per_plrmode[ichar]));
|
||||
|
||||
return &p->per_plrmode[ichar][imode];
|
||||
}
|
||||
|
||||
void progress_register_stage_played(StageProgress *p, PlayerMode *pm) {
|
||||
p->unlocked = true;
|
||||
p->global.num_played += 1;
|
||||
auto c = stage_plrmode_data(p, pm);
|
||||
c->num_played += 1;
|
||||
|
||||
char tmp[64];
|
||||
plrmode_repr(tmp, sizeof(tmp), pm, false);
|
||||
|
||||
log_debug("Stage played %i times total; %i as %s",
|
||||
p->global.num_played,
|
||||
c->num_played,
|
||||
tmp
|
||||
);
|
||||
}
|
||||
|
||||
void progress_register_stage_cleared(StageProgress *p, PlayerMode *pm) {
|
||||
p->unlocked = true;
|
||||
p->global.num_cleared += 1;
|
||||
auto c = stage_plrmode_data(p, pm);
|
||||
c->num_cleared += 1;
|
||||
|
||||
char tmp[64];
|
||||
plrmode_repr(tmp, sizeof(tmp), pm, false);
|
||||
|
||||
log_debug("Stage cleared %i times total; %i as %s",
|
||||
p->global.num_cleared,
|
||||
c->num_cleared,
|
||||
tmp
|
||||
);
|
||||
}
|
||||
|
||||
void progress_register_hiscore(StageProgress *p, PlayerMode *pm, uint64_t score) {
|
||||
if(score > progress.hiscore) {
|
||||
progress.hiscore = score;
|
||||
}
|
||||
|
||||
if(score > p->global.hiscore) {
|
||||
p->global.hiscore = score;
|
||||
}
|
||||
|
||||
auto c = stage_plrmode_data(p, pm);
|
||||
|
||||
if(score > c->hiscore) {
|
||||
c->hiscore = score;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,17 @@
|
|||
|
||||
#include "cutscenes/cutscene.h"
|
||||
#include "endings.h"
|
||||
#include "plrmodes.h"
|
||||
|
||||
typedef struct StageProgress {
|
||||
// Keep this struct small if you can
|
||||
// See stage_get_progress_from_info() in stage.c for more information
|
||||
|
||||
typedef struct StageProgressContents {
|
||||
uint32_t num_played;
|
||||
uint32_t num_cleared;
|
||||
uint64_t hiscore;
|
||||
} StageProgressContents;
|
||||
|
||||
typedef struct StageProgress {
|
||||
StageProgressContents global;
|
||||
StageProgressContents per_plrmode[NUM_CHARACTERS][NUM_SHOT_MODES_PER_CHARACTER];
|
||||
uchar unlocked : 1;
|
||||
} StageProgress;
|
||||
|
||||
|
@ -78,3 +82,7 @@ void progress_unlock_bgm(const char *name);
|
|||
void progress_track_ending(EndingID id);
|
||||
bool progress_is_cutscene_unlocked(CutsceneID id);
|
||||
void progress_unlock_cutscene(CutsceneID id);
|
||||
|
||||
void progress_register_stage_played(StageProgress *p, PlayerMode *pm);
|
||||
void progress_register_stage_cleared(StageProgress *p, PlayerMode *pm);
|
||||
void progress_register_hiscore(StageProgress *p, PlayerMode *pm, uint64_t score);
|
||||
|
|
24
src/stage.c
24
src/stage.c
|
@ -743,12 +743,8 @@ void stage_finish(int gameover) {
|
|||
prev_gameover != GAMEOVER_SCORESCREEN &&
|
||||
(gameover == GAMEOVER_SCORESCREEN || gameover == GAMEOVER_WIN)
|
||||
) {
|
||||
StageProgress *p = stageinfo_get_progress(global.stage, global.diff, true);
|
||||
|
||||
if(p) {
|
||||
++p->num_cleared;
|
||||
log_debug("Stage cleared %u times now", p->num_cleared);
|
||||
}
|
||||
StageProgress *p = NOT_NULL(stageinfo_get_progress(global.stage, global.diff, true));
|
||||
progress_register_stage_cleared(p, global.plr.mode);
|
||||
}
|
||||
|
||||
if(gameover == GAMEOVER_SCORESCREEN && stage_is_skip_mode()) {
|
||||
|
@ -952,8 +948,9 @@ static LogicFrameAction stage_logic_frame(void *arg) {
|
|||
update_transition();
|
||||
}
|
||||
|
||||
if(global.replay.input.replay == NULL && global.plr.points > progress.hiscore) {
|
||||
progress.hiscore = global.plr.points;
|
||||
if(global.replay.input.replay == NULL) {
|
||||
StageProgress *p = NOT_NULL(stageinfo_get_progress(fstate->stage, global.diff, true));
|
||||
progress_register_hiscore(p, global.plr.mode, global.plr.points);
|
||||
}
|
||||
|
||||
if(fstate->quickload_requested) {
|
||||
|
@ -1122,15 +1119,8 @@ static void _stage_enter(
|
|||
start_time = (uint64_t)time(0);
|
||||
seed = makeseed();
|
||||
|
||||
StageProgress *p = stageinfo_get_progress(stage, global.diff, true);
|
||||
|
||||
if(p) {
|
||||
log_debug("You played this stage %u times", p->num_played);
|
||||
log_debug("You cleared this stage %u times", p->num_cleared);
|
||||
|
||||
++p->num_played;
|
||||
p->unlocked = true;
|
||||
}
|
||||
StageProgress *p = NOT_NULL(stageinfo_get_progress(stage, global.diff, true));
|
||||
progress_register_stage_played(p, global.plr.mode);
|
||||
}
|
||||
|
||||
rng_seed(&global.rand_game, seed);
|
||||
|
|
Loading…
Reference in a new issue