diff --git a/openrct2.vcxproj b/openrct2.vcxproj index a430d4746c..e104ee19a7 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -264,6 +264,7 @@ + diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters index 4d1a1f4363..eee82ee7c2 100644 --- a/openrct2.vcxproj.filters +++ b/openrct2.vcxproj.filters @@ -845,5 +845,6 @@ Source + \ No newline at end of file diff --git a/src/config.c b/src/config.c index 056c2740b9..ed5dcdd3bc 100644 --- a/src/config.c +++ b/src/config.c @@ -203,7 +203,7 @@ config_property_definition _generalDefinitions[] = { { offsetof(general_configuration, show_fps), "show_fps", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL }, { offsetof(general_configuration, trap_cursor), "trap_cursor", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL }, { offsetof(general_configuration, auto_open_shops), "auto_open_shops", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL }, - { offsetof(general_configuration, scenario_select_mode), "scenario_select_mode", CONFIG_VALUE_TYPE_UINT8, 1, NULL }, + { offsetof(general_configuration, scenario_select_mode), "scenario_select_mode", CONFIG_VALUE_TYPE_UINT8, SCENARIO_SELECT_MODE_ORIGIN, NULL }, { offsetof(general_configuration, scenario_unlocking_enabled), "scenario_unlocking_enabled", CONFIG_VALUE_TYPE_BOOLEAN, false, NULL }, }; diff --git a/src/config.h b/src/config.h index f3d0cbb2d6..daa64f9e2b 100644 --- a/src/config.h +++ b/src/config.h @@ -128,6 +128,11 @@ enum { SORT_DATE_DESCENDING, }; +enum { + SCENARIO_SELECT_MODE_DIFFICULTY, + SCENARIO_SELECT_MODE_ORIGIN, +}; + typedef struct { uint8 play_intro; uint8 confirmation_prompt; diff --git a/src/scenario.c b/src/scenario.c index 9bc0f7e262..80b9a61304 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -219,19 +219,6 @@ int scenario_load(const char *path) return 0; } -/** - * - * rct2: 0x00678282 - * scenario (ebx) - */ -int scenario_load_and_play(const rct_scenario_basic *scenario) -{ - char path[MAX_PATH]; - - substitute_path(path, RCT2_ADDRESS(RCT2_ADDRESS_SCENARIOS_PATH, char), scenario->path); - return scenario_load_and_play_from_path(path); -} - int scenario_load_and_play_from_path(const char *path) { window_close_construction_windows(); @@ -437,29 +424,35 @@ void scenario_failure() */ void scenario_success() { - int i; - rct_scenario_basic* scenario; - uint32 current_val = RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_COMPANY_VALUE, uint32); + const money32 companyValue = RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_COMPANY_VALUE, money32); - RCT2_GLOBAL(RCT2_ADDRESS_COMPLETED_COMPANY_VALUE, uint32) = current_val; + RCT2_GLOBAL(RCT2_ADDRESS_COMPLETED_COMPANY_VALUE, uint32) = companyValue; peep_applause(); - for (i = 0; i < gScenarioListCount; i++) { - scenario = &gScenarioList[i]; + uint8 scenarioRoot = SCENARIO_ROOT_RCT2; + scenario_index_entry *scenario = scenario_list_find_by_root_path(scenarioRoot, _scenarioFileName); + if (scenario == NULL) { + scenarioRoot = SCENARIO_ROOT_USER; + scenario = scenario_list_find_by_root_path(scenarioRoot, _scenarioFileName); + } - if (strequals(scenario->path, _scenarioFileName, 256, true)) { - // Check if record company value has been broken - if ((scenario->flags & SCENARIO_FLAGS_COMPLETED) && scenario->company_value >= current_val) - break; + if (scenario != NULL) { + // Check if record company value has been broken + if (scenario->highscore == NULL || scenario->highscore->company_value < companyValue) { + if (scenario->highscore == NULL) { + scenario->highscore = scenario_highscore_insert(); + } else { + scenario_highscore_free(scenario->highscore); + } + scenario->highscore->fileNameRoot = scenarioRoot; + scenario->highscore->fileName = (utf8*)path_get_filename(scenario->path); + scenario->highscore->name = NULL; + scenario->highscore->company_value = companyValue; // Allow name entry RCT2_GLOBAL(RCT2_ADDRESS_PARK_FLAGS, uint32) |= PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT; - scenario->company_value = current_val; - scenario->flags |= SCENARIO_FLAGS_COMPLETED; - scenario->completed_by[0] = 0; - RCT2_GLOBAL(0x013587C0, uint32) = current_val; + RCT2_GLOBAL(0x013587C0, money32) = companyValue; scenario_scores_save(); - break; } } scenario_end(); @@ -471,21 +464,19 @@ void scenario_success() */ void scenario_success_submit_name(const char *name) { - int i; - rct_scenario_basic* scenario; - uint32 scenarioWinCompanyValue; + uint8 scenarioRoot = SCENARIO_ROOT_RCT2; + scenario_index_entry *scenario = scenario_list_find_by_root_path(scenarioRoot, _scenarioFileName); + if (scenario == NULL) { + scenarioRoot = SCENARIO_ROOT_USER; + scenario = scenario_list_find_by_root_path(scenarioRoot, _scenarioFileName); + } - for (i = 0; i < gScenarioListCount; i++) { - scenario = &gScenarioList[i]; - - if (strequals(scenario->path, _scenarioFileName, 256, true)) { - scenarioWinCompanyValue = RCT2_GLOBAL(0x013587C0, uint32); - if (scenario->company_value == scenarioWinCompanyValue) { - safe_strncpy(scenario->completed_by, name, 64); - safe_strncpy((char*)0x013587D8, name, 32); - scenario_scores_save(); - } - break; + if (scenario != NULL) { + money32 scenarioWinCompanyValue = RCT2_GLOBAL(0x013587C0, money32); + if (scenario->highscore->company_value == scenarioWinCompanyValue) { + scenario->highscore->name = _strdup(name); + safe_strncpy((char*)0x013587D8, name, 32); + scenario_scores_save(); } } diff --git a/src/scenario.h b/src/scenario.h index 19f3d84bab..b547d1269a 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -100,8 +100,8 @@ typedef struct { sint32 flags; // 0x0268 uint32 company_value; // 0x026C char completed_by[64]; // 0x0270 - uint8 source_game; // new in OpenRCT2 - sint16 source_index; // new in OpenRCT2 + // uint8 source_game; // new in OpenRCT2 + // sint16 source_index; // new in OpenRCT2 } rct_scenario_basic; typedef struct { @@ -420,20 +420,55 @@ enum { OBJECTIVE_MONTHLY_FOOD_INCOME }; +typedef struct { + uint8 fileNameRoot; + utf8 *fileName; + utf8 *name; + money32 company_value; +} scenario_highscore_entry; + +typedef struct { + utf8 path[MAX_PATH]; + + // Category / sequence + uint8 flags; + uint8 category; + uint8 source_game; + sint16 source_index; + + // Objective + uint8 objective_type; + uint8 objective_arg_1; + sint32 objective_arg_2; + sint16 objective_arg_3; + scenario_highscore_entry *highscore; + + utf8 name[64]; + utf8 details[256]; +} scenario_index_entry; + +enum { + SCENARIO_ROOT_RCT2, + SCENARIO_ROOT_USER, +}; + // Scenario list extern int gScenarioListCount; extern int gScenarioListCapacity; -extern rct_scenario_basic *gScenarioList; +extern scenario_index_entry *gScenarioList; extern char gScenarioSavePath[MAX_PATH]; extern int gFirstTimeSave; -int scenario_scores_save(); +bool scenario_scores_save(); void scenario_load_list(); -rct_scenario_basic *get_scenario_by_filename(const char *filename); +void scenario_list_dispose(); +scenario_index_entry *scenario_list_find_by_path(const utf8 *path); +scenario_index_entry *scenario_list_find_by_root_path(uint8 root, const utf8 *filename); +scenario_highscore_entry *scenario_highscore_insert(); +void scenario_highscore_free(scenario_highscore_entry *highscore); int scenario_load_basic(const char *path, rct_s6_header *header, rct_s6_info *info); int scenario_load(const char *path); -int scenario_load_and_play(const rct_scenario_basic *scenario); int scenario_load_and_play_from_path(const char *path); void scenario_begin(); void scenario_update(); diff --git a/src/scenario_list.c b/src/scenario_list.c index 8980250d97..24e7f35159 100644 --- a/src/scenario_list.c +++ b/src/scenario_list.c @@ -28,55 +28,194 @@ // Scenario list int gScenarioListCount = 0; int gScenarioListCapacity = 0; -rct_scenario_basic *gScenarioList = NULL; +scenario_index_entry *gScenarioList = NULL; +int gScenarioHighscoreListCount = 0; +int gScenarioHighscoreListCapacity = 0; +scenario_highscore_entry *gScenarioHighscoreList = NULL; + +static void scenario_list_include(const utf8 *directory); static void scenario_list_add(const char *path); static void scenario_list_sort(); static int scenario_list_sort_by_name(const void *a, const void *b); static int scenario_list_sort_by_index(const void *a, const void *b); -static int scenario_scores_load(); +static sint32 get_scenario_index(utf8 *name); +static void normalise_scenario_name(utf8 *name); +static scenario_source source_by_index(sint32 index); -rct_scenario_basic *get_scenario_by_filename(const char *filename) +static bool scenario_scores_load(); +static bool scenario_scores_legacy_load(); +static void scenario_highscore_remove(scenario_highscore_entry *higscore); +static void scenario_highscore_list_dispose(); +static utf8 *io_read_string(SDL_RWops *file); +static void io_write_string(SDL_RWops *file, utf8 *source); + +/** + * Searches and grabs the metadata for all the scenarios. + */ +void scenario_load_list() { - int i; - for (i = 0; i < gScenarioListCount; i++) - if (strcmp(gScenarioList[i].path, filename) == 0) - return &gScenarioList[i]; + utf8 directory[MAX_PATH]; - return NULL; + // Clear scenario list + gScenarioListCount = 0; + + // Get scenario directory from RCT2 + safe_strncpy(directory, gConfigGeneral.game_path, sizeof(directory)); + safe_strcat_path(directory, "Scenarios", sizeof(directory)); + scenario_list_include(directory); + + // Get scenario directory from user directory + platform_get_user_directory(directory, "scenario"); + scenario_list_include(directory); + + scenario_list_sort(); + scenario_scores_load(); + scenario_scores_legacy_load(); } -sint16 get_scenario_index(rct_scenario_basic *scenario) +static void scenario_list_include(const utf8 *directory) { - for (int i = 0; i < NUM_ORIGINAL_SCENARIOS; i++) { - if (strcmp(original_scenario_names[i], scenario->name) == 0) - return i; + int handle; + file_info fileInfo; + + // Scenarios in this directory + utf8 pattern[MAX_PATH]; + safe_strncpy(pattern, directory, sizeof(pattern)); + safe_strcat_path(pattern, "*.sc6", sizeof(pattern)); + + handle = platform_enumerate_files_begin(pattern); + while (platform_enumerate_files_next(handle, &fileInfo)) { + utf8 path[MAX_PATH]; + safe_strncpy(path, directory, sizeof(pattern)); + safe_strcat_path(path, fileInfo.path, sizeof(pattern)); + scenario_list_add(path); + } + platform_enumerate_files_end(handle); + + // Include sub-directories + utf8 subDirectory[MAX_PATH]; + handle = platform_enumerate_directories_begin(directory); + while (platform_enumerate_directories_next(handle, subDirectory)) { + utf8 path[MAX_PATH]; + safe_strncpy(path, directory, sizeof(pattern)); + safe_strcat_path(path, subDirectory, sizeof(pattern)); + scenario_list_include(path); + } + platform_enumerate_directories_end(handle); +} + +static void scenario_list_add(const utf8 *path) +{ + // Load the basic scenario information + rct_s6_header s6Header; + rct_s6_info s6Info; + if (!scenario_load_basic(path, &s6Header, &s6Info)) { + return; } + // Increase cache size + if (gScenarioListCount == gScenarioListCapacity) { + gScenarioListCapacity = max(8, gScenarioListCapacity * 2); + gScenarioList = (scenario_index_entry*)realloc(gScenarioList, gScenarioListCapacity * sizeof(scenario_index_entry)); + } + scenario_index_entry *newEntry = &gScenarioList[gScenarioListCount]; + gScenarioListCount++; + + // Set new entry + safe_strncpy(newEntry->path, path, sizeof(newEntry->path)); + newEntry->category = s6Info.category; + newEntry->flags = SCENARIO_FLAGS_VISIBLE; + newEntry->objective_type = s6Info.objective_type; + newEntry->objective_arg_1 = s6Info.objective_arg_1; + newEntry->objective_arg_2 = s6Info.objective_arg_2; + newEntry->objective_arg_3 = s6Info.objective_arg_3; + newEntry->highscore = NULL; + safe_strncpy(newEntry->name, s6Info.name, sizeof(newEntry->name)); + safe_strncpy(newEntry->details, s6Info.details, sizeof(newEntry->details)); + + // Normalise the name to make the scenario as recognisable as possible. + normalise_scenario_name(newEntry->name); + + // Look up and store information regarding the origins of this scenario. + newEntry->source_index = get_scenario_index(newEntry->name); + newEntry->source_game = source_by_index(newEntry->source_index); +} + +void scenario_list_dispose() +{ + gScenarioListCapacity = 0; + gScenarioListCount = 0; + SafeFree(gScenarioList); +} + +static void scenario_list_sort() +{ + int(*compareFunc)(void const*, void const*); + + compareFunc = gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN ? + scenario_list_sort_by_index : + scenario_list_sort_by_name; + + qsort(gScenarioList, gScenarioListCount, sizeof(scenario_index_entry), compareFunc); +} + +static int scenario_list_sort_by_name(const void *a, const void *b) +{ + const scenario_index_entry *entryA = (const scenario_index_entry*)a; + const scenario_index_entry *entryB = (const scenario_index_entry*)b; + + return strcmp(entryA->name, entryB->name); +} + +static int scenario_list_sort_by_index(const void *a, const void *b) +{ + const scenario_index_entry *entryA = (const scenario_index_entry*)a; + const scenario_index_entry *entryB = (const scenario_index_entry*)b; + + if (entryA->source_game == SCENARIO_SOURCE_OTHER && entryB->source_game == SCENARIO_SOURCE_OTHER) { + return scenario_list_sort_by_name(a, b); + } + return entryA->source_index - entryB->source_index; +} + +static sint32 get_scenario_index(utf8 *name) +{ + for (sint32 i = 0; i < NUM_ORIGINAL_SCENARIOS; i++) { + if (_strcmpi(original_scenario_names[i], name) == 0) { + return i; + } + } return -1; } -void normalise_scenario_name(rct_scenario_basic *scenario) +static void normalise_scenario_name(utf8 *name) { - char* name = scenario->name; + size_t nameLength = strlen(name); - // Strip "RCT2 " prefix off scenario names. - if (name[0] == 'R' && name[1] == 'C' && name[2] == 'T' && name[3] == '2') { - log_verbose("Stripping RCT2 from name: %s", name); - safe_strncpy(scenario->name, name + 5, 64); + // Strip "RCT(1|2)? *" prefix off scenario names. + if (nameLength >= 3 && (name[0] == 'R' && name[1] == 'C' && name[2] == 'T')) { + if (nameLength >= 4 && (name[3] == '1' || name[3] == '2')) { + log_verbose("Stripping RCT/1/2 from name: %s", name); + safe_strncpy(name, name + 4, 64); + } else { + safe_strncpy(name, name + 3, 64); + } + + safe_strtrimleft(name, name, 64); } - // American scenario titles should be handled by their British counterpart, internally. + // American scenario titles should be converted to British name + // Don't worry, names will be translated using language packs later for (int i = 0; i < NUM_ALIASES; i++) { - if (strcmp(scenario_aliases[i * 2], name) == 0) - { - log_verbose("Found alias: %s; will treat as: %s", scenario->name, scenario_aliases[i * 2 + 1]); - safe_strncpy(scenario->name, scenario_aliases[i * 2 + 1], 64); + if (strcmp(scenario_aliases[(i * 2) + 1], name) == 0) { + log_verbose("Found alias: %s; will treat as: %s", name, scenario_aliases[i * 2]); + safe_strncpy(name, scenario_aliases[i * 2], 64); } } } -scenario_source source_by_index(uint8 index) +static scenario_source source_by_index(sint32 index) { if (index >= SCENARIO_SOURCE_RCT1_INDEX && index < SCENARIO_SOURCE_RCT1_AA_INDEX) { return SCENARIO_SOURCE_RCT1; @@ -97,168 +236,65 @@ scenario_source source_by_index(uint8 index) } } -/** - * - * rct2: 0x006775A8 - */ -void scenario_load_list() +scenario_index_entry *scenario_list_find_by_path(const utf8 *path) { - int i, enumFileHandle; - file_info enumFileInfo; - - // Load scores - scenario_scores_load(); - - // Set all scenarios to be invisible - for (i = 0; i < gScenarioListCount; i++) - gScenarioList[i].flags &= ~SCENARIO_FLAGS_VISIBLE; - - // Enumerate through each scenario in the directory - enumFileHandle = platform_enumerate_files_begin(RCT2_ADDRESS(RCT2_ADDRESS_SCENARIOS_PATH, char)); - if (enumFileHandle != INVALID_HANDLE) { - while (platform_enumerate_files_next(enumFileHandle, &enumFileInfo)) { - scenario_list_add(enumFileInfo.path); + for (int i = 0; i < gScenarioListCount; i++) { + if (_strcmpi(path, gScenarioList[i].path) == 0) { + return &gScenarioList[i]; } - platform_enumerate_files_end(enumFileHandle); } - - // Sort alphabetically - scenario_list_sort(); - - // Save the scores - scenario_scores_save(); + return NULL; } -static void scenario_list_add(const char *path) +scenario_index_entry *scenario_list_find_by_root_path(uint8 root, const utf8 *filename) { - char scenarioPath[MAX_PATH]; - rct_scenario_basic *scenario; - rct_s6_header s6Header; - rct_s6_info s6Info; - - // Get absolute path - substitute_path(scenarioPath, RCT2_ADDRESS(RCT2_ADDRESS_SCENARIOS_PATH, char), path); - - // Load the basic scenario information - if (!scenario_load_basic(scenarioPath, &s6Header, &s6Info)) - return; - - // Ignore scenarios where first header byte is not 255 - if (s6Info.editor_step != 255) - return; - - // Check if scenario already exists in list, likely if in scores - scenario = get_scenario_by_filename(path); - if (scenario != NULL) { - // Update the scenario information - scenario->flags |= SCENARIO_FLAGS_VISIBLE; - scenario->category = s6Info.category; - scenario->objective_type = s6Info.objective_type; - scenario->objective_arg_1 = s6Info.objective_arg_1; - scenario->objective_arg_2 = s6Info.objective_arg_2; - scenario->objective_arg_3 = s6Info.objective_arg_3; - safe_strncpy(scenario->name, s6Info.name, 64); - safe_strncpy(scenario->details, s6Info.details, 256); + // Derive path + utf8 path[MAX_PATH]; + if (root == SCENARIO_ROOT_RCT2) { + safe_strncpy(path, gConfigGeneral.game_path, sizeof(path)); + safe_strcat_path(path, "Scenarios", sizeof(path)); } else { - // Check if the scenario list buffer has room for another scenario - if (gScenarioListCount >= gScenarioListCapacity) { - // Allocate more room - gScenarioListCapacity += 16; - gScenarioList = realloc(gScenarioList, gScenarioListCapacity * sizeof(rct_scenario_basic)); - } - - // Increment the number of scenarios - scenario = &gScenarioList[gScenarioListCount]; - gScenarioListCount++; - - // Add this new scenario to the list - safe_strncpy(scenario->path, path, 256); - scenario->flags = SCENARIO_FLAGS_VISIBLE; - if (RCT2_GLOBAL(0x009AA00C, uint8) & 1) - scenario->flags |= SCENARIO_FLAGS_SIXFLAGS; - scenario->category = s6Info.category; - scenario->objective_type = s6Info.objective_type; - scenario->objective_arg_1 = s6Info.objective_arg_1; - scenario->objective_arg_2 = s6Info.objective_arg_2; - scenario->objective_arg_3 = s6Info.objective_arg_3; - safe_strncpy(scenario->name, s6Info.name, 64); - safe_strncpy(scenario->details, s6Info.details, 256); + platform_get_user_directory(path, "scenario"); } + safe_strcat_path(path, filename, sizeof(path)); - // Normalize the name to make the scenario as recognisable as possible. - normalise_scenario_name(scenario); - - // Look up and store information regarding the origins of this scenario. - scenario->source_index = get_scenario_index(scenario); - scenario->source_game = source_by_index(scenario->source_index); -} - -/** -* Sort the list of scenarios. This used to be an insertion sort which took -* place as each scenario loaded. It has now been changed to a quicksort which -* takes place after all the scenarios have been loaded in. -* rct2: 0x00677C3B -*/ -static void scenario_list_sort() -{ - if (gConfigGeneral.scenario_select_mode == 1) // and not tabIndex > REAL, OTHER - qsort(gScenarioList, gScenarioListCount, sizeof(rct_scenario_basic), scenario_list_sort_by_index); - else - qsort(gScenarioList, gScenarioListCount, sizeof(rct_scenario_basic), scenario_list_sort_by_name); -} - -static int scenario_list_sort_by_index(const void *a, const void *b) -{ - if (((rct_scenario_basic*)a)->source_game == SCENARIO_SOURCE_OTHER && ((rct_scenario_basic*)b)->source_game == SCENARIO_SOURCE_OTHER) - return scenario_list_sort_by_name(a, b); - - return ((rct_scenario_basic*)a)->source_index - ((rct_scenario_basic*)b)->source_index; -} - -/** - * Basic scenario information compare function for sorting. - * rct2: 0x00677C08 - */ -static int scenario_list_sort_by_name(const void *a, const void *b) -{ - return strcmp(((rct_scenario_basic*)a)->name, ((rct_scenario_basic*)b)->name); + // Find matching scenario entry + return scenario_list_find_by_path(path); } /** * Gets the path for the scenario scores path. */ static void scenario_scores_get_path(utf8 *outPath) +{ + platform_get_user_directory(outPath, NULL); + strcat(outPath, "highscores.dat"); +} + +/** + * Gets the path for the scenario scores path. + */ +static void scenario_scores_legacy_get_path(utf8 *outPath) { platform_get_user_directory(outPath, NULL); strcat(outPath, "scores.dat"); } /** - * - * rct2: 0x006775A8 + * Loads the original scores.dat file and replaces any highscores that + * are better for matching scenarios. */ -static int scenario_scores_load() +static bool scenario_scores_legacy_load() { - SDL_RWops *file; - char scoresPath[MAX_PATH]; - - scenario_scores_get_path(scoresPath); - - // Free scenario list if already allocated - if (gScenarioList != NULL) { - free(gScenarioList); - gScenarioList = NULL; - } - - // Try and load the scores file + utf8 scoresPath[MAX_PATH]; + scenario_scores_legacy_get_path(scoresPath); // First check user folder and then fallback to install directory - file = SDL_RWFromFile(scoresPath, "rb"); + SDL_RWops *file = SDL_RWFromFile(scoresPath, "rb"); if (file == NULL) { file = SDL_RWFromFile(get_file_path(PATH_ID_SCORES), "rb"); if (file == NULL) { - log_error("Unable to load scenario scores."); - return 0; + return false; } } @@ -266,53 +302,208 @@ static int scenario_scores_load() rct_scenario_scores_header header; if (SDL_RWread(file, &header, 16, 1) != 1) { SDL_RWclose(file); - log_error("Invalid header in scenario scores file."); - return 0; - } - gScenarioListCount = header.scenario_count; - - // Load scenario information with scores - int scenarioListBufferSize = gScenarioListCount * sizeof(rct_scenario_basic); - gScenarioListCapacity = gScenarioListCount; - gScenarioList = malloc(scenarioListBufferSize); - if (SDL_RWread(file, gScenarioList, scenarioListBufferSize, 1) == 1) { - SDL_RWclose(file); - return 1; + log_error("Invalid header in legacy scenario scores file."); + return false; } - // Unable to load scores, free scenario list + // Read scenarios + bool highscoresDirty = false; + for (uint32 i = 0; i < header.scenario_count; i++) { + // Read legacy entry + rct_scenario_basic scBasic; + if (SDL_RWread(file, &scBasic, sizeof(rct_scenario_basic), 1) != 1) { + break; + } + + // Ignore non-completed scenarios + if (!(scBasic.flags & SCENARIO_FLAGS_COMPLETED)) { + continue; + } + + // Find matching scenario entry + scenario_index_entry *scenarioIndexEntry = scenario_list_find_by_root_path(SCENARIO_ROOT_RCT2, scBasic.path); + if (scenarioIndexEntry != NULL) { + // Check if legacy highscore is better + scenario_highscore_entry *highscore = scenarioIndexEntry->highscore; + if (highscore == NULL) { + highscore = scenario_highscore_insert(); + scenarioIndexEntry->highscore = highscore; + } else if (highscore->company_value < (money32)scBasic.company_value) { + scenario_highscore_free(highscore); + // Re-use highscore entry + } else { + highscore = NULL; + } + + // Set new highscore + if (highscore != NULL) { + highscore->fileNameRoot = SCENARIO_ROOT_RCT2; + highscore->fileName = _strdup(scBasic.path); + highscore->name = _strdup(scBasic.completed_by); + highscore->company_value = (money32)scBasic.company_value; + highscoresDirty = true; + } + + // Exit loop + break; + } + } SDL_RWclose(file); - gScenarioListCount = 0; - gScenarioListCapacity = 0; - free(gScenarioList); - gScenarioList = NULL; - return 0; + + if (highscoresDirty) { + scenario_scores_save(); + } + return true; +} + +static bool scenario_scores_load() +{ + utf8 scoresPath[MAX_PATH]; + scenario_scores_get_path(scoresPath); + + // Load scores file + SDL_RWops *file = SDL_RWFromFile(scoresPath, "rb"); + if (file == NULL) { + return false; + } + + // Check file version + uint32 fileVersion; + SDL_RWread(file, &fileVersion, sizeof(fileVersion), 1); + if (fileVersion != 1) { + log_error("Invalid or incompatible highscores file."); + return false; + } + + // Read and allocate the highscore list + scenario_highscore_list_dispose(); + SDL_RWread(file, &gScenarioHighscoreListCount, sizeof(gScenarioHighscoreListCount), 1); + gScenarioHighscoreListCapacity = gScenarioHighscoreListCount; + gScenarioHighscoreList = malloc(gScenarioHighscoreListCapacity * sizeof(scenario_highscore_entry)); + + // Read highscores + for (int i = 0; i < gScenarioHighscoreListCount; i++) { + scenario_highscore_entry *highscore = &gScenarioHighscoreList[i]; + + SDL_RWread(file, &highscore->fileNameRoot, sizeof(highscore->fileNameRoot), 1); + highscore->fileName = io_read_string(file); + highscore->name = io_read_string(file); + SDL_RWread(file, &highscore->company_value, sizeof(highscore->company_value), 1); + + // Attach highscore to correct scenario entry + scenario_index_entry *scenarioIndexEntry = scenario_list_find_by_root_path(highscore->fileNameRoot, highscore->fileName); + if (scenarioIndexEntry != NULL) { + scenarioIndexEntry->highscore = highscore; + } + } + + SDL_RWclose(file); + return true; } /** * * rct2: 0x00677B50 */ -int scenario_scores_save() +bool scenario_scores_save() { - SDL_RWops *file; utf8 scoresPath[MAX_PATH]; - scenario_scores_get_path(scoresPath); - file = SDL_RWFromFile(scoresPath, "wb"); + SDL_RWops *file = SDL_RWFromFile(scoresPath, "wb"); if (file == NULL) { log_error("Unable to save scenario scores."); - return 0; + return false; } - rct_scenario_scores_header header; - header.scenario_count = gScenarioListCount; - - SDL_RWwrite(file, &header, sizeof(header), 1); - if (gScenarioListCount > 0) - SDL_RWwrite(file, gScenarioList, gScenarioListCount * sizeof(rct_scenario_basic), 1); + const uint32 fileVersion = 1; + SDL_RWwrite(file, &fileVersion, sizeof(fileVersion), 1); + SDL_RWwrite(file, &gScenarioHighscoreListCount, sizeof(gScenarioHighscoreListCount), 1); + for (int i = 0; i < gScenarioHighscoreListCount; i++) { + scenario_highscore_entry *highscore = &gScenarioHighscoreList[i]; + SDL_RWwrite(file, &highscore->fileNameRoot, sizeof(highscore->fileNameRoot), 1); + io_write_string(file, highscore->fileName); + io_write_string(file, highscore->name); + SDL_RWwrite(file, &highscore->company_value, sizeof(highscore->company_value), 1); + } SDL_RWclose(file); - return 1; + + return true; +} + +scenario_highscore_entry *scenario_highscore_insert() +{ + if (gScenarioHighscoreListCount >= gScenarioHighscoreListCapacity) { + gScenarioHighscoreListCapacity = max(8, gScenarioHighscoreListCapacity * 2); + gScenarioHighscoreList = realloc(gScenarioHighscoreList, gScenarioHighscoreListCapacity * sizeof(scenario_highscore_entry)); + } + return &gScenarioHighscoreList[gScenarioHighscoreListCount++]; +} + +static void scenario_highscore_remove(scenario_highscore_entry *highscore) +{ + for (int i = 0; i < gScenarioHighscoreListCount; i++) { + if (&gScenarioHighscoreList[i] == highscore) { + size_t moveSize = (gScenarioHighscoreListCount - i - 1) * sizeof(scenario_highscore_entry); + if (moveSize > 0) { + memmove(&gScenarioHighscoreList[i], &gScenarioHighscoreList[i + 1], moveSize); + } + return; + } + } +} + +void scenario_highscore_free(scenario_highscore_entry *highscore) +{ + SafeFree(highscore->fileName); + SafeFree(highscore->name); +} + +static void scenario_highscore_list_dispose() +{ + for (int i = 0; i < gScenarioHighscoreListCount; i++) { + scenario_highscore_free(&gScenarioHighscoreList[i]); + } + gScenarioHighscoreListCapacity = 0; + gScenarioHighscoreListCount = 0; + SafeFree(gScenarioHighscoreList); +} + +static utf8 *io_read_string(SDL_RWops *file) +{ + size_t bufferCount = 0; + size_t bufferCapacity = 0; + utf8 *buffer = NULL; + + utf8 ch; + do { + SDL_RWread(file, &ch, sizeof(ch), 1); + if (ch == '\0' && buffer == NULL) { + break; + } + + if (bufferCount >= bufferCapacity) { + bufferCapacity = max(32, bufferCapacity * 2); + buffer = realloc(buffer, bufferCapacity * sizeof(uint8)); + } + + buffer[bufferCount] = ch; + bufferCount++; + } while (ch != '\0'); + + if (bufferCount < bufferCapacity) { + buffer = realloc(buffer, bufferCount); + } + return buffer; +} + +static void io_write_string(SDL_RWops *file, utf8 *source) +{ + if (source == NULL) { + utf8 empty = 0; + SDL_RWwrite(file, &empty, sizeof(utf8), 1); + } else { + SDL_RWwrite(file, source, strlen(source) + 1, 1); + } } diff --git a/src/scenario_sources.h b/src/scenario_sources.h index 9c82307844..78efee823a 100644 --- a/src/scenario_sources.h +++ b/src/scenario_sources.h @@ -1,5 +1,5 @@ #define NUM_ORIGINAL_SCENARIOS 136 -#define NUM_ALIASES 5 +#define NUM_ALIASES 6 #define SCENARIO_SOURCE_RCT1_INDEX 0 #define SCENARIO_SOURCE_RCT1_AA_INDEX 22 @@ -10,15 +10,16 @@ #define SCENARIO_SOURCE_REAL_INDEX 128 const char * const scenario_aliases[NUM_ALIASES * 2] = { - "Katie's World", "Katie's Dreamland", - "Dinky Park", "Pokey Park", - "Aqua Park", "White Water Park", - "Mothball Mountain","Mystic Mountain", - "Big Pier", "Paradise Pier" + "Katie's Dreamland", "Katie's World", + "Pokey Park", "Dinky Park", + "White Water Park", "Aqua Park", + "Mystic Mountain", "Mothball Mountain", + "Paradise Pier", "Big Pier", + "Paradise Pier 2", "Big Pier 2", }; const char * const original_scenario_names[NUM_ORIGINAL_SCENARIOS] = { - // RCT Classic + // RCT "Forest Frontiers", "Dynamite Dunes", "Leafy Lake", @@ -42,7 +43,7 @@ const char * const original_scenario_names[NUM_ORIGINAL_SCENARIOS] = { "Thunder Rock", "Mega Park", - // RCT: Corkscrew Follies + // RCT: Added Attractions "Whispering Cliffs", "Three Monkeys Park", "Canary Mines", @@ -81,7 +82,7 @@ const char * const original_scenario_names[NUM_ORIGINAL_SCENARIOS] = { "Razor Rocks", "Crater Lake", "Vertigo Views", - "Big Pier 2", + "Paradise Pier 2", "Dragon's Cove", "Good Knight Park", "Wacky Warren", @@ -106,7 +107,7 @@ const char * const original_scenario_names[NUM_ORIGINAL_SCENARIOS] = { "Venus Ponds", "Micro Park", - // RCT2 Vanilla + // RCT2 "Crazy Castle", "Electric Fields", "Factory Capers", @@ -123,7 +124,7 @@ const char * const original_scenario_names[NUM_ORIGINAL_SCENARIOS] = { "Lucky Lake", "Rainbow Summit", - // RCT2 Wacky Worlds + // RCT2: Wacky Worlds "Africa - Victoria Falls", "Asia - Great Wall of China Tourism Enhancement", "North America - Grand Canyon", @@ -142,7 +143,7 @@ const char * const original_scenario_names[NUM_ORIGINAL_SCENARIOS] = { "N. America - Extreme Hawaiian Island", "South America - Rain Forest Plateau", - // RCT2 Time Twister + // RCT2: Time Twister "Dark Age - Robin Hood", "Prehistoric - After the Asteroid", "Roaring Twenties - Prison Island", @@ -158,7 +159,7 @@ const char * const original_scenario_names[NUM_ORIGINAL_SCENARIOS] = { "Roaring Twenties - Skyscrapers", "Rock 'n' Roll - Rock 'n' Roll", - // Real parks + // Real parks "Alton Towers", "Heide-Park", "Blackpool Pleasure Beach", diff --git a/src/util/util.c b/src/util/util.c index c20876a1f9..bc18de7d65 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -222,6 +222,70 @@ char *safe_strncpy(char * destination, const char * source, size_t size) return result; } +char *safe_strcat(char *destination, const char *source, size_t size) +{ + assert(destination != NULL); + assert(source != NULL); + + if (size == 0) { + return destination; + } + + char *result = destination; + + size_t i; + for (i = 0; i < size; i++) { + if (*destination == '\0') { + break; + } else { + destination++; + } + } + + bool terminated = false; + for (; i < size; i++) { + if (*source != '\0') { + *destination++ = *source++; + } else { + *destination = *source; + terminated = true; + break; + } + } + + if (!terminated) { + result[size - 1] = '\0'; + log_warning("Truncating string \"%s\" to %d bytes.", result, size); + } + + return result; +} + +char *safe_strcat_path(char *destination, const char *source, size_t size) +{ + const char pathSeparator = platform_get_path_separator(); + + size_t length = strlen(destination); + if (length >= size - 1) { + return destination; + } + + if (destination[length - 1] != pathSeparator) { + destination[length] = pathSeparator; + destination[length + 1] = '\0'; + } + + return safe_strcat(destination, source, size); +} + +char *safe_strtrimleft(char *destination, const char *source, size_t size) +{ + while (*source == ' ' && *source != '\0') { + source++; + } + return safe_strncpy(destination, source, size); +} + bool utf8_is_bom(const char *str) { return str[0] == (char)0xEF && str[1] == (char)0xBB && str[2] == (char)0xBF; diff --git a/src/util/util.h b/src/util/util.h index fad8a6bbc6..6cef39d0c5 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -42,6 +42,9 @@ int bitcount(int source); bool strequals(const char *a, const char *b, int length, bool caseInsensitive); int strcicmp(char const *a, char const *b); char *safe_strncpy(char * destination, const char * source, size_t num); +char *safe_strcat(char *destination, const char *source, size_t size); +char *safe_strcat_path(char *destination, const char *source, size_t size); +char *safe_strtrimleft(char *destination, const char *source, size_t size); bool utf8_is_bom(const char *str); bool str_is_null_or_empty(const char *str); diff --git a/src/windows/options.c b/src/windows/options.c index 881d947839..173aa49445 100644 --- a/src/windows/options.c +++ b/src/windows/options.c @@ -1179,6 +1179,7 @@ static void window_options_dropdown(rct_window *w, int widgetIndex, int dropdown gConfigGeneral.scenario_select_mode = dropdownIndex; config_save_default(); window_invalidate(w); + window_close_by_class(WC_SCENARIO_SELECT); } break; } @@ -1530,7 +1531,9 @@ static void window_options_paint(rct_window *w, rct_drawpixelinfo *dpi) gfx_draw_string_left(dpi, STR_OPTIONS_SCENARIO_GROUPING, NULL, w->colours[1], w->x + 10, w->y + window_options_controls_and_interface_widgets[WIDX_SCENARIO_GROUPING].top + 1); gfx_draw_string_left_clipped( dpi, - gConfigGeneral.scenario_select_mode == 0 ? STR_OPTIONS_SCENARIO_DIFFICULTY : STR_OPTIONS_SCENARIO_ORIGIN, + gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_DIFFICULTY ? + STR_OPTIONS_SCENARIO_DIFFICULTY : + STR_OPTIONS_SCENARIO_ORIGIN, NULL, w->colours[1], w->x + window_options_controls_and_interface_widgets[WIDX_SCENARIO_GROUPING].left + 1, diff --git a/src/windows/title_scenarioselect.c b/src/windows/title_scenarioselect.c index 62bb58e979..f2b44a3340 100644 --- a/src/windows/title_scenarioselect.c +++ b/src/windows/title_scenarioselect.c @@ -121,8 +121,7 @@ void window_scenarioselect_open() scenario_load_list(); // Shrink the window if we're showing scenarios by difficulty level. - if (gConfigGeneral.scenario_select_mode == 2) - { + if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_DIFFICULTY) { window_width = 610; window_scenarioselect_widgets[WIDX_BACKGROUND].right = 609; window_scenarioselect_widgets[WIDX_TITLEBAR].right = 608; @@ -130,9 +129,9 @@ void window_scenarioselect_open() window_scenarioselect_widgets[WIDX_CLOSE].right = 607; window_scenarioselect_widgets[WIDX_TABCONTENT].right = 609; window_scenarioselect_widgets[WIDX_SCENARIOLIST].right = 433; - } - else + } else { window_width = 733; + } window = window_create_centred( window_width, @@ -164,15 +163,15 @@ static void window_scenarioselect_init_tabs() { int show_pages = 0; for (int i = 0; i < gScenarioListCount; i++) { - rct_scenario_basic* scenario = &gScenarioList[i]; - if (scenario->flags & SCENARIO_FLAGS_VISIBLE) - { - if (gConfigGeneral.scenario_select_mode == 1) + scenario_index_entry *scenario = &gScenarioList[i]; + if (scenario->flags & SCENARIO_FLAGS_VISIBLE) { + if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN) { show_pages |= 1 << scenario->source_game; - else + } else { show_pages |= 1 << scenario->category; } } + } int x = 3; for (int i = 0; i < 8; i++) { @@ -212,10 +211,10 @@ static void window_scenarioselect_scrollgetsize(rct_window *w, int scrollIndex, { *height = 0; for (int i = 0; i < gScenarioListCount; i++) { - rct_scenario_basic *scenario = &gScenarioList[i]; + scenario_index_entry *scenario = &gScenarioList[i]; - if ((gConfigGeneral.scenario_select_mode == 1 && scenario->source_game != w->selected_tab) || - (gConfigGeneral.scenario_select_mode == 2 && scenario->category != w->selected_tab)) + if ((gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN && scenario->source_game != w->selected_tab) || + (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_DIFFICULTY && scenario->category != w->selected_tab)) continue; if (scenario->flags & SCENARIO_FLAGS_VISIBLE) @@ -231,20 +230,20 @@ static void window_scenarioselect_scrollmousedown(rct_window *w, int scrollIndex { int num_unlocks = 5; for (int i = 0; i < gScenarioListCount; i++) { - rct_scenario_basic *scenario = &gScenarioList[i]; + scenario_index_entry *scenario = &gScenarioList[i]; - if ((gConfigGeneral.scenario_select_mode == 1 && scenario->source_game != w->selected_tab) || - (gConfigGeneral.scenario_select_mode == 2 && scenario->category != w->selected_tab)) + if ((gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN && scenario->source_game != w->selected_tab) || + (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_DIFFICULTY && scenario->category != w->selected_tab)) continue; if (!(scenario->flags & SCENARIO_FLAGS_VISIBLE)) continue; - if (gConfigGeneral.scenario_unlocking_enabled) { + if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN && gConfigGeneral.scenario_unlocking_enabled) { if (num_unlocks <= 0) break; - bool is_completed = scenario->flags & SCENARIO_FLAGS_COMPLETED; + bool is_completed = scenario->highscore != NULL; if (is_completed) { num_unlocks++; } else { @@ -257,7 +256,7 @@ static void window_scenarioselect_scrollmousedown(rct_window *w, int scrollIndex continue; audio_play_sound_panned(SOUND_CLICK_1, w->width / 2 + w->x, 0, 0, 0); - scenario_load_and_play(scenario); + scenario_load_and_play_from_path(scenario->path); break; } } @@ -268,22 +267,22 @@ static void window_scenarioselect_scrollmousedown(rct_window *w, int scrollIndex */ static void window_scenarioselect_scrollmouseover(rct_window *w, int scrollIndex, int x, int y) { - rct_scenario_basic *selected = NULL; + scenario_index_entry *selected = NULL; int num_unlocks = 5; for (int i = 0; i < gScenarioListCount; i++) { - rct_scenario_basic *scenario = &gScenarioList[i]; - if ((gConfigGeneral.scenario_select_mode == 1 && scenario->source_game != w->selected_tab) || - (gConfigGeneral.scenario_select_mode == 2 && scenario->category != w->selected_tab)) + scenario_index_entry *scenario = &gScenarioList[i]; + if ((gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN && scenario->source_game != w->selected_tab) || + (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_DIFFICULTY && scenario->category != w->selected_tab)) continue; if (!(scenario->flags & SCENARIO_FLAGS_VISIBLE)) continue; - if (gConfigGeneral.scenario_unlocking_enabled) { + if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN && gConfigGeneral.scenario_unlocking_enabled) { if (num_unlocks <= 0) break; - bool is_completed = scenario->flags & SCENARIO_FLAGS_COMPLETED; + bool is_completed = scenario->highscore != NULL; num_unlocks += is_completed ? 1 : -1; } @@ -315,7 +314,7 @@ static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi) { int i, x, y, format; rct_widget *widget; - rct_scenario_basic *scenario; + scenario_index_entry *scenario; window_draw_widgets(w, dpi); @@ -330,7 +329,7 @@ static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi) x = (widget->left + widget->right) / 2 + w->x; y = (widget->top + widget->bottom) / 2 + w->y - 3; - if (gConfigGeneral.scenario_select_mode == 1) { + if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN) { RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = STR_SCENARIO_CATEGORY_RCT1 + i; } else { // old-style RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = STR_BEGINNER_PARKS + i; @@ -339,7 +338,7 @@ static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi) } // Return if no scenario highlighted - scenario = w->scenario; + scenario = (scenario_index_entry*)w->highlighted_item; if (scenario == NULL) return; @@ -364,10 +363,10 @@ static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi) y += gfx_draw_string_left_wrapped(dpi, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, x, y, 170, STR_OBJECTIVE, 0) + 5; // Scenario score - if (scenario->flags & SCENARIO_FLAGS_COMPLETED) { - safe_strncpy((char*)0x009BC677, scenario->completed_by, 64); + if (scenario->highscore != NULL) { + safe_strncpy((char*)0x009BC677, scenario->highscore->name, 64); RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = 3165; // empty string - RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 2, int) = scenario->company_value; + RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 2, int) = scenario->highscore->company_value; y += gfx_draw_string_left_wrapped(dpi, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, x, y, 170, STR_COMPLETED_BY_WITH_COMPANY_VALUE, 0); } } @@ -382,15 +381,15 @@ static void window_scenarioselect_scrollpaint(rct_window *w, rct_drawpixelinfo * int unhighlighted_format = (theme_get_preset()->features.rct1_scenario_font) ? 5139 : 1191; int disabled_format = 5619; - bool wide = gConfigGeneral.scenario_select_mode == 1; + bool wide = gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN; int y = 0; int num_unlocks = 5; for (int i = 0; i < gScenarioListCount; i++) { - rct_scenario_basic *scenario = &gScenarioList[i]; + scenario_index_entry *scenario = &gScenarioList[i]; - if ((gConfigGeneral.scenario_select_mode == 1 && scenario->source_game != w->selected_tab) || - (gConfigGeneral.scenario_select_mode == 2 && scenario->category != w->selected_tab)) + if ((gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN && scenario->source_game != w->selected_tab) || + (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_DIFFICULTY && scenario->category != w->selected_tab)) continue; if (!(scenario->flags & SCENARIO_FLAGS_VISIBLE)) @@ -401,24 +400,26 @@ static void window_scenarioselect_scrollpaint(rct_window *w, rct_drawpixelinfo * // Draw hover highlight bool is_highlighted = w->highlighted_item == (int)scenario; - if (is_highlighted) + if (is_highlighted) { gfx_fill_rect(dpi, 0, y, w->width, y + 23, 0x02000031); + } - bool is_completed = scenario->flags & SCENARIO_FLAGS_COMPLETED; + bool is_completed = scenario->highscore != NULL; bool is_disabled = false; - if (gConfigGeneral.scenario_unlocking_enabled) { - if (num_unlocks <= 0) + if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN && gConfigGeneral.scenario_unlocking_enabled) { + if (num_unlocks <= 0) { is_disabled = true; - + } num_unlocks += is_completed ? 1 : -1; } - int format = is_disabled ? 5619 : (is_highlighted ? highlighted_format : unhighlighted_format); // Draw scenario name - safe_strncpy((char*)0x009BC677, scenario->name, 64); - RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS, short) = 3165; - gfx_draw_string_centred(dpi, format, wide ? 270 : 210, y + 1, 0, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS); + rct_string_id placeholderStringId = 3165; + safe_strncpy((char*)language_get_string(placeholderStringId), scenario->name, 64); + int format = is_disabled ? 865 : (is_highlighted ? highlighted_format : unhighlighted_format); + colour = is_disabled ? w->colours[1] | 0x40 : COLOUR_BLACK; + gfx_draw_string_centred(dpi, format, wide ? 270 : 210, y + 1, colour, &placeholderStringId); // Check if scenario is completed if (is_completed) { @@ -426,9 +427,9 @@ static void window_scenarioselect_scrollpaint(rct_window *w, rct_drawpixelinfo * gfx_draw_sprite(dpi, 0x5A9F, wide ? 500 : 395, y + 1, 0); // Draw completion score - safe_strncpy((char*)0x009BC677, scenario->completed_by, 64); - RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS, short) = 2793; - RCT2_GLOBAL(0x013CE954, short) = 3165; + safe_strncpy((char*)language_get_string(placeholderStringId), scenario->highscore->name, 64); + RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, rct_string_id) = 2793; + RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 2, rct_string_id) = placeholderStringId; gfx_draw_string_centred(dpi, format, wide ? 270 : 210, y + 11, 0, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS); } diff --git a/test/tests.h b/test/tests.h index 7691a3dd88..06619ec167 100644 --- a/test/tests.h +++ b/test/tests.h @@ -33,11 +33,11 @@ int run_all_tests(); #include "../src/scenario.h" static void test_load_scenario(CuTest* tc, const char* file_name) { - const rct_scenario_basic* scenario = get_scenario_by_filename(file_name); + const scenario_index_entry* scenario = scenario_list_find_by_path(file_name); if (scenario == NULL) { CuFail(tc, "Could not load scenario"); } - scenario_load_and_play(scenario); + scenario_load_and_play_from_path(scenario->name); } #endif