From 56e64498a65199e2810f3d479c2eb93206081817 Mon Sep 17 00:00:00 2001 From: Alice D <34408664+StarWitch@users.noreply.github.com> Date: Sun, 26 Apr 2020 15:27:13 -0400 Subject: [PATCH] Player Statistics (#219) --- src/ending.c | 2 +- src/main.c | 1 + src/menu/common.c | 1 + src/menu/gameovermenu.c | 7 ++++--- src/meson.build | 1 + src/player.c | 20 ++++++++++++++++-- src/player.h | 4 +++- src/replay.c | 35 ++++++++++++++++++++++++++++++-- src/replay.h | 13 +++++++++++- src/stage.c | 4 +++- src/stagetext.c | 10 --------- src/stagetext.h | 2 -- src/stats.c | 45 +++++++++++++++++++++++++++++++++++++++++ src/stats.h | 32 +++++++++++++++++++++++++++++ 14 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 src/stats.c create mode 100644 src/stats.h diff --git a/src/ending.c b/src/ending.c index a8367fc5..b66bfdb6 100644 --- a/src/ending.c +++ b/src/ending.c @@ -328,7 +328,7 @@ void good_ending_reimu(Ending *e) { static void init_ending(Ending *e) { dynarray_ensure_capacity(&e->entries, 32); - if(global.plr.continues_used) { + if(global.plr.stats.total.continues_used) { global.plr.mode->character->ending.bad(e); } else { global.plr.mode->character->ending.good(e); diff --git a/src/main.c b/src/main.c index 35c658d0..a3700451 100644 --- a/src/main.c +++ b/src/main.c @@ -343,6 +343,7 @@ static void main_singlestg_begin_game(CallChainResult ccr) { replay_init(&global.replay); global.gameover = 0; player_init(&global.plr); + stats_init(&global.plr.stats); if(ctx->plrmode) { global.plr.mode = ctx->plrmode; diff --git a/src/menu/common.c b/src/menu/common.c index 485e0422..ee8ef27b 100644 --- a/src/menu/common.c +++ b/src/menu/common.c @@ -91,6 +91,7 @@ static void reset_game(StartGameContext *ctx) { replay_destroy(&global.replay); replay_init(&global.replay); player_init(&global.plr); + stats_init(&global.plr.stats); global.plr.mode = plrmode_find( progress.game_settings.character, progress.game_settings.shotmode diff --git a/src/menu/gameovermenu.c b/src/menu/gameovermenu.c index 5cd8e7a9..6d0cb9d2 100644 --- a/src/menu/gameovermenu.c +++ b/src/menu/gameovermenu.c @@ -11,6 +11,7 @@ #include "menu.h" #include "gameovermenu.h" #include "ingamemenu.h" +#include "stats.h" #include "global.h" static void continue_game(MenuData *m, void *arg) { @@ -20,7 +21,7 @@ static void continue_game(MenuData *m, void *arg) { } static void give_up(MenuData *m, void *arg) { - global.gameover = (MAX_CONTINUES - global.plr.continues_used)? GAMEOVER_ABORT : GAMEOVER_DEFEAT; + global.gameover = (MAX_CONTINUES - global.plr.stats.total.continues_used) ? GAMEOVER_ABORT : GAMEOVER_DEFEAT; } MenuData* create_gameover_menu(void) { @@ -40,9 +41,9 @@ MenuData* create_gameover_menu(void) { m->context = "Game Over"; char s[64]; - int c = MAX_CONTINUES - global.plr.continues_used; + int c = MAX_CONTINUES - global.plr.stats.total.continues_used; snprintf(s, sizeof(s), "Continue (%i)", c); - add_menu_entry(m, s, c? continue_game : NULL, NULL); + add_menu_entry(m, s, c ? continue_game : NULL, NULL); add_menu_entry(m, "Restart the Game", restart_game, NULL)->transition = TransFadeBlack; add_menu_entry(m, c? "Give up" : "Return to Title", give_up, NULL)->transition = TransFadeBlack; diff --git a/src/meson.build b/src/meson.build index f1aa3002..89077fb3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -93,6 +93,7 @@ taisei_src = files( 'stageobjects.c', 'stagetext.c', 'stageutils.c', + 'stats.c', 'taskmanager.c', 'transition.c', 'version.c', diff --git a/src/player.c b/src/player.c index 96c8a413..7f16f048 100644 --- a/src/player.c +++ b/src/player.c @@ -16,6 +16,7 @@ #include "stage.h" #include "stagetext.h" #include "stagedraw.h" +#include "stats.h" #include "entity.h" #include "util/glm.h" @@ -590,7 +591,7 @@ DEFINE_TASK(player_logic) { plr->point_item_value = PLR_START_PIV; plr->life_fragments = 0; plr->bomb_fragments = 0; - plr->continues_used += 1; + stats_track_continue_used(&plr->stats); player_set_power(plr, 0); stage_clear_hazards(CLEAR_HAZARDS_ALL); spawn_items(plr->deathpos, ITEM_POWER, (int)ceil(PLR_MAX_POWER/(double)POWER_VALUE)); @@ -638,6 +639,19 @@ DEFINE_TASK(player_logic) { void player_logic(Player* plr) { } +static bool player_can_bomb(Player *plr) { + return ( + !player_is_bomb_active(plr) + && ( + plr->bombs > 0 || + plr->iddqd + ) + && global.frames >= plr->respawntime + ); + +} + + static bool player_bomb(Player *plr) { if(global.boss && global.boss->current && global.boss->current->type == AT_ExtraSpell) return false; @@ -648,7 +662,8 @@ static bool player_bomb(Player *plr) { return false; } - if(!player_is_bomb_active(plr) && (plr->bombs > 0 || plr->iddqd) && global.frames >= plr->respawntime) { + if(player_can_bomb(plr)) { + stats_track_bomb_used(&plr->stats); player_fail_spell(plr); // player_cancel_powersurge(plr); // stage_clear_hazards(CLEAR_HAZARDS_ALL); @@ -885,6 +900,7 @@ void player_realdeath(Player *plr) { plr->bomb_fragments = 0; plr->voltage *= 0.9; plr->lives--; + stats_track_life_used(&plr->stats); } static void player_death_effect_draw_overlay(Projectile *p, int t, ProjDrawRuleArgs args) { diff --git a/src/player.h b/src/player.h index 6dd78239..7270cbe1 100644 --- a/src/player.h +++ b/src/player.h @@ -25,6 +25,7 @@ #include "enemy.h" #include "gamepad.h" #include "aniplayer.h" +#include "stats.h" #include "resource/animation.h" #include "entity.h" @@ -102,6 +103,8 @@ DEFINE_ENTITY_TYPE(Player, { AniPlayer ani; Sprite bomb_portrait; + Stats stats; + struct { float positive; float negative; @@ -133,7 +136,6 @@ DEFINE_ENTITY_TYPE(Player, { int bombs; int life_fragments; int bomb_fragments; - int continues_used; int power; int power_overflow; diff --git a/src/replay.c b/src/replay.c index b080458a..6d773fe1 100644 --- a/src/replay.c +++ b/src/replay.c @@ -41,7 +41,7 @@ ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t start_t s->plr_pos_y = floor(cimag(plr->pos)); s->plr_points = plr->points; - s->plr_continues_used = plr->continues_used; + s->plr_continues_used = plr->stats.total.continues_used; // s->plr_focus = plr->focus; FIXME remove and bump version s->plr_char = plr->mode->character->id; s->plr_shot = plr->mode->shot_mode; @@ -60,7 +60,7 @@ ReplayStage* replay_create_stage(Replay *rpy, StageInfo *stage, uint64_t start_t void replay_stage_sync_player_state(ReplayStage *stg, Player *plr) { plr->points = stg->plr_points; - plr->continues_used = stg->plr_continues_used; + plr->stats.total.continues_used = stg->plr_continues_used; plr->mode = plrmode_find(stg->plr_char, stg->plr_shot); plr->pos = stg->plr_pos_x + I * stg->plr_pos_y; // plr->focus = stg->plr_focus; FIXME remove and bump version @@ -73,6 +73,12 @@ void replay_stage_sync_player_state(ReplayStage *stg, Player *plr) { plr->graze = stg->plr_graze; plr->point_item_value = stg->plr_point_item_value; plr->inputflags = stg->plr_inputflags; + + plr->stats.total.lives_used = stg->plr_stats_total_lives_used; + plr->stats.stage.lives_used = stg->plr_stats_stage_lives_used; + plr->stats.total.bombs_used = stg->plr_stats_total_bombs_used; + plr->stats.stage.bombs_used = stg->plr_stats_stage_bombs_used; + plr->stats.stage.continues_used = stg->plr_stats_stage_continues_used; } static void replay_destroy_stage(ReplayStage *stage) { @@ -194,6 +200,14 @@ static uint32_t replay_calc_stageinfo_checksum(ReplayStage *stg, uint16_t versio cs += stg->plr_points_final; } + if(version >= REPLAY_STRUCT_VERSION_TS104000_REV0) { + cs += stg->plr_stats_total_lives_used; + cs += stg->plr_stats_stage_lives_used; + cs += stg->plr_stats_total_bombs_used; + cs += stg->plr_stats_stage_bombs_used; + cs += stg->plr_stats_stage_continues_used; + } + log_debug("%08x", cs); return cs; } @@ -226,6 +240,14 @@ static bool replay_write_stage(ReplayStage *stg, SDL_RWops *file, uint16_t versi SDL_WriteLE64(file, stg->plr_points_final); } + if(version >= REPLAY_STRUCT_VERSION_TS104000_REV0) { + SDL_WriteU8(file, stg->plr_stats_total_lives_used); + SDL_WriteU8(file, stg->plr_stats_stage_lives_used); + SDL_WriteU8(file, stg->plr_stats_total_bombs_used); + SDL_WriteU8(file, stg->plr_stats_stage_bombs_used); + SDL_WriteU8(file, stg->plr_stats_stage_continues_used); + } + if(stg->events.num_elements > UINT16_MAX) { log_error("Too many events in replay, cannot write this"); return false; @@ -371,6 +393,7 @@ static bool replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize, s case REPLAY_STRUCT_VERSION_TS103000_REV1: case REPLAY_STRUCT_VERSION_TS103000_REV2: case REPLAY_STRUCT_VERSION_TS103000_REV3: + case REPLAY_STRUCT_VERSION_TS104000_REV0: { if(taisei_version_read(file, &rpy->game_version) != TAISEI_VERSION_SIZE) { log_error("%s: Failed to read game version", source); @@ -485,6 +508,14 @@ static bool _replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize, co CHECKPROP(stg->plr_points_final = SDL_ReadLE64(file), zu); } + if(version >= REPLAY_STRUCT_VERSION_TS104000_REV0) { + CHECKPROP(stg->plr_stats_total_lives_used = SDL_ReadU8(file), u); + CHECKPROP(stg->plr_stats_stage_lives_used = SDL_ReadU8(file), u); + CHECKPROP(stg->plr_stats_total_bombs_used = SDL_ReadU8(file), u); + CHECKPROP(stg->plr_stats_stage_bombs_used = SDL_ReadU8(file), u); + CHECKPROP(stg->plr_stats_stage_continues_used = SDL_ReadU8(file), u); + } + CHECKPROP(stg->num_events = SDL_ReadLE16(file), u); if(replay_calc_stageinfo_checksum(stg, version) + SDL_ReadLE32(file)) { diff --git a/src/replay.h b/src/replay.h index 59f345fb..08b19bb2 100644 --- a/src/replay.h +++ b/src/replay.h @@ -54,13 +54,16 @@ // Taisei v1.3 revision 3: add final score at the end of each stage #define REPLAY_STRUCT_VERSION_TS103000_REV3 12 + + // Taisei v1.4 revision 0: add statistics for player + #define REPLAY_STRUCT_VERSION_TS104000_REV0 13 /* END supported struct versions */ #define REPLAY_VERSION_COMPRESSION_BIT 0x8000 #define REPLAY_COMPRESSION_CHUNK_SIZE 4096 // What struct version to use when saving recorded replays -#define REPLAY_STRUCT_VERSION_WRITE (REPLAY_STRUCT_VERSION_TS103000_REV3 | REPLAY_VERSION_COMPRESSION_BIT) +#define REPLAY_STRUCT_VERSION_WRITE (REPLAY_STRUCT_VERSION_TS104000_REV0 | REPLAY_VERSION_COMPRESSION_BIT) #define REPLAY_ALLOC_INITIAL 256 @@ -130,6 +133,14 @@ typedef struct ReplayStage { uint64_t plr_points_final; /* END REPLAY_STRUCT_VERSION_TS102000_REV3 and above */ + /* BEGIN REPLAY_STRUCT_VERSION_TS104000_REV0 and above */ + uint8_t plr_stats_total_lives_used; + uint8_t plr_stats_stage_lives_used; + uint8_t plr_stats_total_bombs_used; + uint8_t plr_stats_stage_bombs_used; + uint8_t plr_stats_stage_continues_used; + /* END REPLAY_STRUCT_VERSION_TS104000_REV0 and above */ + // player input // NOTE: only used to read the number of events from file. // Actual numbers of events stored/allocated are tracked by the .events dynamic array. diff --git a/src/stage.c b/src/stage.c index 505bfbb8..e565f5b9 100644 --- a/src/stage.c +++ b/src/stage.c @@ -214,6 +214,8 @@ static void stage_start(StageInfo *stage) { player_stage_pre_init(&global.plr); + stats_stage_reset(&global.plr.stats); + if(stage->type == STAGE_SPELL) { global.is_practice_mode = true; global.plr.lives = 0; @@ -1051,7 +1053,7 @@ void stage_end_loop(void* ctx) { } void stage_unlock_bgm(const char *bgm) { - if(global.replaymode != REPLAY_PLAY && !global.plr.continues_used) { + if(global.replaymode != REPLAY_PLAY && !global.plr.stats.total.continues_used) { progress_unlock_bgm(bgm); } } diff --git a/src/stagetext.c b/src/stagetext.c index a610adbd..a748b611 100644 --- a/src/stagetext.c +++ b/src/stagetext.c @@ -187,16 +187,6 @@ void stagetext_table_add_separator(StageTextTable *tbl) { tbl->pos += I * 0.5 * font_get_lineskip(get_font("standard")); } -void stagetext_table_test(void) { - StageTextTable tbl; - stagetext_begin_table(&tbl, "Test", RGB(1, 1, 1), RGB(1, 1, 1), VIEWPORT_W/2, 60, 300, 30, 60); - stagetext_table_add(&tbl, "foo", "bar"); - stagetext_table_add(&tbl, "qwerty", "asdfg"); - stagetext_table_add(&tbl, "top", "kek"); - stagetext_table_add_separator(&tbl); - stagetext_table_add_numeric(&tbl, "Total Score", 9000000); - stagetext_end_table(&tbl); -} StageText *stagetext_list_head(void) { return textlist; diff --git a/src/stagetext.h b/src/stagetext.h index 667b6b46..a9dd0c8d 100644 --- a/src/stagetext.h +++ b/src/stagetext.h @@ -75,6 +75,4 @@ void stagetext_table_add_numeric(StageTextTable *tbl, const char *title, int n); void stagetext_table_add_numeric_nonzero(StageTextTable *tbl, const char *title, int n); void stagetext_table_add_separator(StageTextTable *tbl); -void stagetext_table_test(void); - #endif // IGUARD_stagetext_h diff --git a/src/stats.c b/src/stats.c new file mode 100644 index 00000000..c3d46da0 --- /dev/null +++ b/src/stats.c @@ -0,0 +1,45 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + + +#include "stats.h" + +#include "global.h" +#include "log.h" + +void stats_init(Stats *stats) { + memset(stats, 0, sizeof(*stats)); +} + +void stats_track_life_used(Stats *stats) { + stats->total.lives_used++; + stats->stage.lives_used++; + log_debug("lives used (total): %d", stats->total.lives_used); + log_debug("lives used (stage): %d", stats->stage.lives_used); +} + +void stats_track_bomb_used(Stats *stats) { + stats->total.bombs_used++; + stats->stage.bombs_used++; + log_debug("bombs used (total): %d", stats->total.bombs_used); + log_debug("bombs used (stage): %d", stats->stage.bombs_used); +} + +void stats_track_continue_used(Stats *stats) { + stats->total.continues_used++; + stats->stage.continues_used++; + log_debug("bombs used (total): %d", stats->total.continues_used); + log_debug("bombs used (stage): %d", stats->stage.continues_used); +} + +void stats_stage_reset(Stats *stats) { + memset(&stats->stage, 0, sizeof(stats->stage)); + log_debug("statistics reset"); +} diff --git a/src/stats.h b/src/stats.h new file mode 100644 index 00000000..d5245869 --- /dev/null +++ b/src/stats.h @@ -0,0 +1,32 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_stats_h +#define IGUARD_stats_h + +#include "taisei.h" + +typedef struct Stats Stats; + +struct Stats { + struct { + int lives_used; + int bombs_used; + int continues_used; + } total, stage; +}; + +void stats_init(Stats *stats); + +void stats_track_life_used(Stats *stats); +void stats_track_bomb_used(Stats *stats); +void stats_track_continue_used(Stats *stats); + +void stats_stage_reset(Stats *stats); + +#endif // IGUARD_stats_h