diff --git a/src/highscore.cpp b/src/highscore.cpp index e48ed5e01b..bae91cee02 100644 --- a/src/highscore.cpp +++ b/src/highscore.cpp @@ -12,6 +12,7 @@ #include "company_base.h" #include "company_func.h" #include "cheat_func.h" +#include "fileio_func.h" #include "string_func.h" #include "strings_func.h" #include "table/strings.h" @@ -19,7 +20,7 @@ #include "safeguards.h" -HighScore _highscore_table[SP_HIGHSCORE_END][5]; ///< various difficulty-settings; top 5 +HighScoresTable _highscore_table; ///< Table with all the high scores. std::string _highscore_file; ///< The file to store the highscore data in. static const StringID _endgame_perf_titles[] = { @@ -48,31 +49,34 @@ StringID EndGameGetPerformanceTitleFromValue(uint value) return _endgame_perf_titles[value]; } -/** Save the highscore for the company */ -int8 SaveHighScoreValue(const Company *c) +/** + * Save the highscore for the company + * @param c The company to insert. + * @return The index the company got in the high score table, or -1 when it did not end up in the table. + */ +int8_t SaveHighScoreValue(const Company *c) { - HighScore *hs = _highscore_table[SP_CUSTOM]; - uint i; - uint16 score = c->old_economy[0].performance_history; - /* Exclude cheaters from the honour of being in the highscore table */ if (CheatHasBeenUsed()) return -1; - for (i = 0; i < lengthof(_highscore_table[0]); i++) { - /* You are in the TOP5. Move all values one down and save us there */ - if (hs[i].score <= score) { - /* move all elements one down starting from the replaced one */ - memmove(&hs[i + 1], &hs[i], sizeof(HighScore) * (lengthof(_highscore_table[0]) - i - 1)); - SetDParam(0, c->index); - SetDParam(1, c->index); - GetString(hs[i].company, STR_HIGHSCORE_NAME, lastof(hs[i].company)); // get manager/company name string - hs[i].score = score; - hs[i].title = EndGameGetPerformanceTitleFromValue(score); - return i; - } - } + auto &highscores = _highscore_table[SP_CUSTOM]; + uint16 score = c->old_economy[0].performance_history; - return -1; // too bad; we did not make it into the top5 + auto it = std::find_if(highscores.begin(), highscores.end(), [&score](auto &highscore) { return highscore.score <= score; }); + + /* If we cannot find it, our score is not high enough. */ + if (it == highscores.end()) return -1; + + /* Move all elements one down starting from the replaced one */ + std::move_backward(it, highscores.end() - 1, highscores.end()); + + /* Fill the elements. */ + SetDParam(0, c->index); + SetDParam(1, c->index); + it->name = GetString(STR_HIGHSCORE_NAME); // get manager/company name string + it->score = score; + it->title = EndGameGetPerformanceTitleFromValue(score); + return std::distance(highscores.begin(), it); } /** Sort all companies given their performance */ @@ -85,95 +89,82 @@ static bool HighScoreSorter(const Company * const &a, const Company * const &b) * Save the highscores in a network game when it has ended * @return Position of the local company in the highscore list. */ -int8 SaveHighScoreValueNetwork() +int8_t SaveHighScoreValueNetwork() { const Company *cl[MAX_COMPANIES]; - uint count = 0; - int8 company = -1; + size_t count = 0; + int8_t local_company_place = -1; /* Sort all active companies with the highest score first */ for (const Company *c : Company::Iterate()) cl[count++] = c; std::sort(std::begin(cl), std::begin(cl) + count, HighScoreSorter); - { - uint i; + /* Clear the high scores from the previous network game. */ + auto &highscores = _highscore_table[SP_MULTIPLAYER]; + std::fill(highscores.begin(), highscores.end(), HighScore{}); - memset(_highscore_table[SP_MULTIPLAYER], 0, sizeof(_highscore_table[SP_MULTIPLAYER])); + for (size_t i = 0; i < count && i < highscores.size(); i++) { + const Company *c = cl[i]; + auto &highscore = highscores[i]; + SetDParam(0, c->index); + SetDParam(1, c->index); + highscore.name = GetString(STR_HIGHSCORE_NAME); // get manager/company name string + highscore.score = c->old_economy[0].performance_history; + highscore.title = EndGameGetPerformanceTitleFromValue(highscore.score); - /* Copy over Top5 companies */ - for (i = 0; i < lengthof(_highscore_table[SP_MULTIPLAYER]) && i < count; i++) { - HighScore *hs = &_highscore_table[SP_MULTIPLAYER][i]; - - SetDParam(0, cl[i]->index); - SetDParam(1, cl[i]->index); - GetString(hs->company, STR_HIGHSCORE_NAME, lastof(hs->company)); // get manager/company name string - hs->score = cl[i]->old_economy[0].performance_history; - hs->title = EndGameGetPerformanceTitleFromValue(hs->score); - - /* get the ranking of the local company */ - if (cl[i]->index == _local_company) company = i; - } + if (c->index == _local_company) local_company_place = static_cast(i); } - /* Add top5 companies to highscore table */ - return company; + return local_company_place; } /** Save HighScore table to file */ void SaveToHighScore() { - FILE *fp = fopen(_highscore_file.c_str(), "wb"); + std::unique_ptr fp(fopen(_highscore_file.c_str(), "wb")); + if (fp == nullptr) return; - if (fp != nullptr) { - uint i; - HighScore *hs; - - for (i = 0; i < SP_SAVED_HIGHSCORE_END; i++) { - for (hs = _highscore_table[i]; hs != endof(_highscore_table[i]); hs++) { - /* First character is a command character, so strlen will fail on that */ - byte length = ClampTo(std::min(sizeof(hs->company), StrEmpty(hs->company) ? 0 : strlen(&hs->company[1]) + 1)); - - if (fwrite(&length, sizeof(length), 1, fp) != 1 || // write away string length - fwrite(hs->company, length, 1, fp) > 1 || // Yes... could be 0 bytes too - fwrite(&hs->score, sizeof(hs->score), 1, fp) != 1 || - fwrite(" ", 2, 1, fp) != 1) { // XXX - placeholder for hs->title, not saved anymore; compatibility - Debug(misc, 1, "Could not save highscore."); - i = SP_SAVED_HIGHSCORE_END; - break; - } + /* Does not iterate through the complete array!. */ + for (int i = 0; i < SP_SAVED_HIGHSCORE_END; i++) { + for (HighScore &hs : _highscore_table[i]) { + /* This code is weird and old fashioned to keep compatibility with the old high score files. */ + byte name_length = ClampTo(hs.name.size()); + if (fwrite(&name_length, sizeof(name_length), 1, fp.get()) != 1 || // Write the string length of the name + fwrite(hs.name.data(), name_length, 1, fp.get()) > 1 || // Yes... could be 0 bytes too + fwrite(&hs.score, sizeof(hs.score), 1, fp.get()) != 1 || + fwrite(" ", 2, 1, fp.get()) != 1) { // Used to be hs.title, not saved anymore; compatibility + Debug(misc, 1, "Could not save highscore."); + return; } } - fclose(fp); } } /** Initialize the highscore table to 0 and if any file exists, load in values */ void LoadFromHighScore() { - FILE *fp = fopen(_highscore_file.c_str(), "rb"); + std::fill(_highscore_table.begin(), _highscore_table.end(), HighScores{}); - memset(_highscore_table, 0, sizeof(_highscore_table)); + std::unique_ptr fp(fopen(_highscore_file.c_str(), "rb")); + if (fp == nullptr) return; - if (fp != nullptr) { - uint i; - HighScore *hs; + /* Does not iterate through the complete array!. */ + for (int i = 0; i < SP_SAVED_HIGHSCORE_END; i++) { + for (HighScore &hs : _highscore_table[i]) { + /* This code is weird and old fashioned to keep compatibility with the old high score files. */ + byte name_length; + char buffer[std::numeric_limits::max() + 1]; - for (i = 0; i < SP_SAVED_HIGHSCORE_END; i++) { - for (hs = _highscore_table[i]; hs != endof(_highscore_table[i]); hs++) { - byte length; - if (fread(&length, sizeof(length), 1, fp) != 1 || - fread(hs->company, std::min(lengthof(hs->company), length), 1, fp) > 1 || // Yes... could be 0 bytes too - fread(&hs->score, sizeof(hs->score), 1, fp) != 1 || - fseek(fp, 2, SEEK_CUR) == -1) { // XXX - placeholder for hs->title, not saved anymore; compatibility - Debug(misc, 1, "Highscore corrupted"); - i = SP_SAVED_HIGHSCORE_END; - break; - } - StrMakeValidInPlace(hs->company, lastof(hs->company), SVS_NONE); - hs->title = EndGameGetPerformanceTitleFromValue(hs->score); + if (fread(&name_length, sizeof(name_length), 1, fp.get()) != 1 || + fread(buffer, name_length, 1, fp.get()) > 1 || // Yes... could be 0 bytes too + fread(&hs.score, sizeof(hs.score), 1, fp.get()) != 1 || + fseek(fp.get(), 2, SEEK_CUR) == -1) { // Used to be hs.title, not saved anymore; compatibility + Debug(misc, 1, "Highscore corrupted"); + return; } + hs.name = StrMakeValid(std::string_view(buffer, name_length)); + hs.title = EndGameGetPerformanceTitleFromValue(hs.score); } - fclose(fp); } } diff --git a/src/highscore.h b/src/highscore.h index 8993e83afa..b1ddd9fa6f 100644 --- a/src/highscore.h +++ b/src/highscore.h @@ -15,23 +15,20 @@ #include "settings_type.h" struct HighScore { - /** - * The name of the company and president. - * The + 5 is for the comma and space or possibly other characters - * that join the two names in this single string and the '\0'. - */ - char company[(MAX_LENGTH_COMPANY_NAME_CHARS + MAX_LENGTH_PRESIDENT_NAME_CHARS + 5) * MAX_CHAR_LENGTH]; - StringID title; ///< NOSAVE, has troubles with changing string-numbers. - uint16 score; ///< The score for this high score. Do NOT change type, will break hs.dat + std::string name; ///< The name of the companyy and president. + StringID title = INVALID_STRING_ID; ///< NOSAVE, has troubles with changing string-numbers. + uint16 score = 0; ///< The score for this high score. Do NOT change type, will break hs.dat }; -extern HighScore _highscore_table[SP_HIGHSCORE_END][5]; +using HighScores = std::array; ///< Record 5 high scores +using HighScoresTable = std::array; ///< Record high score for each of the difficulty levels +extern HighScoresTable _highscore_table; void SaveToHighScore(); void LoadFromHighScore(); -int8 SaveHighScoreValue(const Company *c); -int8 SaveHighScoreValueNetwork(); +int8_t SaveHighScoreValue(const Company *c); +int8_t SaveHighScoreValueNetwork(); StringID EndGameGetPerformanceTitleFromValue(uint value); -void ShowHighscoreTable(int difficulty = SP_CUSTOM, int8 rank = -1); +void ShowHighscoreTable(int difficulty = SP_CUSTOM, int8_t rank = -1); #endif /* HIGHSCORE_H */ diff --git a/src/highscore_gui.cpp b/src/highscore_gui.cpp index 762aac2e11..16d8876bce 100644 --- a/src/highscore_gui.cpp +++ b/src/highscore_gui.cpp @@ -184,7 +184,7 @@ struct HighScoreWindow : EndGameHighScoreBaseWindow { void OnPaint() override { - const HighScore *hs = _highscore_table[this->window_number]; + const auto &hs = _highscore_table[this->window_number]; this->SetupHighScoreEndWindow(); Point pt = this->GetTopLeft(ScaleSpriteTrad(640), ScaleSpriteTrad(480)); @@ -193,14 +193,14 @@ struct HighScoreWindow : EndGameHighScoreBaseWindow { DrawStringMultiLine(pt.x + ScaleSpriteTrad(70), pt.x + ScaleSpriteTrad(570), pt.y, pt.y + ScaleSpriteTrad(140), !_networking ? STR_HIGHSCORE_TOP_COMPANIES_WHO_REACHED : STR_HIGHSCORE_TOP_COMPANIES_NETWORK_GAME, TC_FROMSTRING, SA_CENTER); /* Draw Highscore peepz */ - for (uint8 i = 0; i < lengthof(_highscore_table[0]); i++) { + for (uint8_t i = 0; i < ClampTo(hs.size()); i++) { SetDParam(0, i + 1); DrawString(pt.x + ScaleSpriteTrad(40), pt.x + ScaleSpriteTrad(600), pt.y + ScaleSpriteTrad(140 + i * 55), STR_HIGHSCORE_POSITION); - if (hs[i].company[0] != '\0') { + if (!hs[i].name.empty()) { TextColour colour = (this->rank == i) ? TC_RED : TC_BLACK; // draw new highscore in red - SetDParamStr(0, hs[i].company); + SetDParamStr(0, hs[i].name); DrawString(pt.x + ScaleSpriteTrad(71), pt.x + ScaleSpriteTrad(569), pt.y + ScaleSpriteTrad(140 + i * 55), STR_JUST_BIG_RAW_STRING, colour); SetDParam(0, hs[i].title); SetDParam(1, hs[i].score);