diff --git a/src/scenario.c b/src/scenario.c index 6585378823..a764a6140d 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -438,7 +438,6 @@ void scenario_success() } else { scenario_highscore_free(scenario->highscore); } - scenario->highscore->fileNameRoot = scenario->path_root; scenario->highscore->fileName = (utf8*)path_get_filename(scenario->path); scenario->highscore->name = NULL; scenario->highscore->company_value = companyValue; diff --git a/src/scenario.h b/src/scenario.h index d186a1dbc4..0748172cbb 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -421,18 +421,16 @@ enum { }; typedef struct { - uint8 fileNameRoot; utf8 *fileName; utf8 *name; money32 company_value; } scenario_highscore_entry; typedef struct { - uint8 path_root; utf8 path[MAX_PATH]; + uint64 timestamp; // Category / sequence - uint8 flags; uint8 category; uint8 source_game; sint16 source_index; @@ -448,11 +446,6 @@ typedef struct { utf8 details[256]; } scenario_index_entry; -enum { - SCENARIO_ROOT_RCT2, - SCENARIO_ROOT_USER, -}; - // Scenario list extern int gScenarioListCount; extern int gScenarioListCapacity; @@ -466,7 +459,6 @@ void scenario_load_list(); void scenario_list_dispose(); scenario_index_entry *scenario_list_find_by_filename(const utf8 *filename); 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); diff --git a/src/scenario_list.c b/src/scenario_list.c index aec4895635..86ec20f150 100644 --- a/src/scenario_list.c +++ b/src/scenario_list.c @@ -33,14 +33,15 @@ int gScenarioHighscoreListCount = 0; int gScenarioHighscoreListCapacity = 0; scenario_highscore_entry *gScenarioHighscoreList = NULL; -static void scenario_list_include(uint8 pathRoot, const utf8 *directory); -static void scenario_list_add(uint8 pathRoot, const char *path); +static void scenario_list_include(const utf8 *directory); +static void scenario_list_add(const utf8 *path, uint64 timestamp); 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 bool scenario_scores_load(); -static bool scenario_scores_legacy_load(); +static void scenario_scores_legacy_get_path(utf8 *outPath); +static bool scenario_scores_legacy_load(const utf8 *path); static void scenario_highscore_remove(scenario_highscore_entry *higscore); static void scenario_highscore_list_dispose(); static utf8 *io_read_string(SDL_RWops *file); @@ -59,18 +60,23 @@ void scenario_load_list() // Get scenario directory from RCT2 safe_strncpy(directory, gConfigGeneral.game_path, sizeof(directory)); safe_strcat_path(directory, "Scenarios", sizeof(directory)); - scenario_list_include(SCENARIO_ROOT_RCT2, directory); + scenario_list_include(directory); // Get scenario directory from user directory platform_get_user_directory(directory, "scenario"); - scenario_list_include(SCENARIO_ROOT_USER, directory); + scenario_list_include(directory); scenario_list_sort(); scenario_scores_load(); - scenario_scores_legacy_load(); + + + utf8 scoresPath[MAX_PATH]; + scenario_scores_legacy_get_path(scoresPath); + scenario_scores_legacy_load(scoresPath); + scenario_scores_legacy_load(get_file_path(PATH_ID_SCORES)); } -static void scenario_list_include(uint8 pathRoot, const utf8 *directory) +static void scenario_list_include(const utf8 *directory) { int handle; file_info fileInfo; @@ -85,7 +91,7 @@ static void scenario_list_include(uint8 pathRoot, const utf8 *directory) utf8 path[MAX_PATH]; safe_strncpy(path, directory, sizeof(pattern)); safe_strcat_path(path, fileInfo.path, sizeof(pattern)); - scenario_list_add(pathRoot, path); + scenario_list_add(path, fileInfo.last_modified); } platform_enumerate_files_end(handle); @@ -96,12 +102,12 @@ static void scenario_list_include(uint8 pathRoot, const utf8 *directory) utf8 path[MAX_PATH]; safe_strncpy(path, directory, sizeof(pattern)); safe_strcat_path(path, subDirectory, sizeof(pattern)); - scenario_list_include(pathRoot, path); + scenario_list_include(path); } platform_enumerate_directories_end(handle); } -static void scenario_list_add(uint8 pathRoot, const utf8 *path) +static void scenario_list_add(const utf8 *path, uint64 timestamp) { // Load the basic scenario information rct_s6_header s6Header; @@ -110,19 +116,44 @@ static void scenario_list_add(uint8 pathRoot, const utf8 *path) 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 = NULL; + + const utf8 *filename = path_get_filename(path); + scenario_index_entry *existingEntry = scenario_list_find_by_filename(filename); + if (existingEntry != NULL) { + bool bail = false; + const utf8 *conflictPath; + if (existingEntry->timestamp > timestamp) { + // Existing entry is more recent + conflictPath = existingEntry->path; + + // Overwrite existing entry with this one + newEntry = existingEntry; + } else { + // This entry is more recent + conflictPath = path; + bail = true; + } + printf("Scenario conflict: '%s' ignored because it is newer.\n", conflictPath); + if (bail) { + return; + } + } + + if (newEntry == NULL) { + // Increase list size + if (gScenarioListCount == gScenarioListCapacity) { + gScenarioListCapacity = max(8, gScenarioListCapacity * 2); + gScenarioList = (scenario_index_entry*)realloc(gScenarioList, gScenarioListCapacity * sizeof(scenario_index_entry)); + } + newEntry = &gScenarioList[gScenarioListCount]; + gScenarioListCount++; } - scenario_index_entry *newEntry = &gScenarioList[gScenarioListCount]; - gScenarioListCount++; // Set new entry - newEntry->path_root = pathRoot; safe_strncpy(newEntry->path, path, sizeof(newEntry->path)); + newEntry->timestamp = timestamp; 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; @@ -203,22 +234,6 @@ scenario_index_entry *scenario_list_find_by_path(const utf8 *path) return NULL; } -scenario_index_entry *scenario_list_find_by_root_path(uint8 root, const utf8 *filename) -{ - // 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 { - platform_get_user_directory(path, "scenario"); - } - safe_strcat_path(path, filename, sizeof(path)); - - // Find matching scenario entry - return scenario_list_find_by_path(path); -} - /** * Gets the path for the scenario scores path. */ @@ -241,18 +256,12 @@ static void scenario_scores_legacy_get_path(utf8 *outPath) * Loads the original scores.dat file and replaces any highscores that * are better for matching scenarios. */ -static bool scenario_scores_legacy_load() +static bool scenario_scores_legacy_load(const utf8 *path) { - utf8 scoresPath[MAX_PATH]; - scenario_scores_legacy_get_path(scoresPath); - // First check user folder and then fallback to install directory - SDL_RWops *file = SDL_RWFromFile(scoresPath, "rb"); + SDL_RWops *file = SDL_RWFromFile(path, "rb"); if (file == NULL) { - file = SDL_RWFromFile(get_file_path(PATH_ID_SCORES), "rb"); - if (file == NULL) { - return false; - } + return false; } // Load header @@ -278,7 +287,7 @@ static bool scenario_scores_legacy_load() } // Find matching scenario entry - scenario_index_entry *scenarioIndexEntry = scenario_list_find_by_root_path(SCENARIO_ROOT_RCT2, scBasic.path); + scenario_index_entry *scenarioIndexEntry = scenario_list_find_by_filename(scBasic.path); if (scenarioIndexEntry != NULL) { // Check if legacy highscore is better scenario_highscore_entry *highscore = scenarioIndexEntry->highscore; @@ -294,7 +303,6 @@ static bool scenario_scores_legacy_load() // 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; @@ -341,8 +349,6 @@ static bool scenario_scores_load() // 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); @@ -379,7 +385,6 @@ bool scenario_scores_save() 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); diff --git a/src/windows/options.c b/src/windows/options.c index 173aa49445..650e6e07ee 100644 --- a/src/windows/options.c +++ b/src/windows/options.c @@ -664,8 +664,7 @@ static void window_options_mouseup(rct_window *w, int widgetIndex) case WIDX_DEBUGGING_TOOLS: gConfigGeneral.debugging_tools ^= 1; config_save_default(); - window_invalidate(w); - window_invalidate_by_class(WC_TOP_TOOLBAR); + gfx_invalidate_screen(); break; case WIDX_TEST_UNFINISHED_TRACKS: gConfigGeneral.test_unfinished_tracks ^= 1; diff --git a/src/windows/title_scenarioselect.c b/src/windows/title_scenarioselect.c index 208b4ae989..1b6e706acf 100644 --- a/src/windows/title_scenarioselect.c +++ b/src/windows/title_scenarioselect.c @@ -187,14 +187,12 @@ static void window_scenarioselect_init_tabs() int show_pages = 0; for (int i = 0; i < gScenarioListCount; i++) { 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 { show_pages |= 1 << scenario->category; } } - } int x = 3; for (int i = 0; i < 8; i++) { @@ -220,7 +218,7 @@ static void window_scenarioselect_mouseup(rct_window *w, int widgetIndex) { if (widgetIndex == WIDX_CLOSE) { window_close(w); -} + } } static void window_scenarioselect_mousedown(int widgetIndex, rct_window*w, rct_widget* widget) @@ -263,18 +261,18 @@ static void window_scenarioselect_scrollmousedown(rct_window *w, int scrollIndex switch (listItem->type) { case LIST_ITEM_TYPE_HEADING: y -= 18; - break; + break; case LIST_ITEM_TYPE_SCENARIO: - y -= 24; + y -= 24; if (y < 0 && !listItem->scenario.is_locked) { - audio_play_sound_panned(SOUND_CLICK_1, w->width / 2 + w->x, 0, 0, 0); + audio_play_sound_panned(SOUND_CLICK_1, w->width / 2 + w->x, 0, 0, 0); scenario_load_and_play_from_path(listItem->scenario.scenario->path); } - break; - } + break; + } if (y < 0) { break; -} + } } } @@ -295,11 +293,11 @@ static void window_scenarioselect_scrollmouseover(rct_window *w, int scrollIndex if (y < 0 && !listItem->scenario.is_locked) { selected = listItem->scenario.scenario; } - break; + break; } if (y < 0) { - break; - } + break; + } } if (w->highlighted_item != (uint32)selected) { @@ -329,7 +327,9 @@ static void window_scenarioselect_invalidate(rct_window *w) int windowHeight = w->height; window_scenarioselect_widgets[WIDX_BACKGROUND].bottom = windowHeight - 1; window_scenarioselect_widgets[WIDX_TABCONTENT].bottom = windowHeight - 1; - window_scenarioselect_widgets[WIDX_SCENARIOLIST].bottom = windowHeight - 5; + + const int bottomMargin = gConfigGeneral.debugging_tools ? 17 : 5; + window_scenarioselect_widgets[WIDX_SCENARIOLIST].bottom = windowHeight - bottomMargin; } static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi) @@ -364,6 +364,12 @@ static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi) if (scenario == NULL) return; + // Scenario path + if (gConfigGeneral.debugging_tools) { + const utf8 *path = scenario->path; + gfx_draw_string_left(dpi, 1170, (void*)&path, w->colours[1], w->x + 3, w->y + w->height - 3 - 11); + } + // Scenario name x = w->x + window_scenarioselect_widgets[WIDX_SCENARIOLIST].right + 4; y = w->y + window_scenarioselect_widgets[WIDX_TABCONTENT].top + 5; @@ -386,7 +392,11 @@ static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi) // Scenario score if (scenario->highscore != NULL) { - safe_strncpy((char*)0x009BC677, scenario->highscore->name, 64); + const utf8 *completedByName = "???"; + if (!str_is_null_or_empty(scenario->highscore->name)) { + completedByName = scenario->highscore->name; + } + safe_strncpy((char*)0x009BC677, completedByName, 64); RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = 3165; // empty string 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); @@ -421,36 +431,40 @@ static void window_scenarioselect_scrollpaint(rct_window *w, rct_drawpixelinfo * y += 18; break; case LIST_ITEM_TYPE_SCENARIO:; - // Draw hover highlight + // Draw hover highlight scenario_index_entry *scenario = listItem->scenario.scenario; bool isHighlighted = w->highlighted_item == (uint32)scenario; if (isHighlighted) { - gfx_fill_rect(dpi, 0, y, w->width, y + 23, 0x02000031); - } + gfx_fill_rect(dpi, 0, y, w->width, y + 23, 0x02000031); + } bool isCompleted = scenario->highscore != NULL; bool isDisabled = listItem->scenario.is_locked; - // Draw scenario name - rct_string_id placeholderStringId = 3165; - safe_strncpy((char*)language_get_string(placeholderStringId), scenario->name, 64); + // Draw scenario name + rct_string_id placeholderStringId = 3165; + safe_strncpy((char*)language_get_string(placeholderStringId), scenario->name, 64); int format = isDisabled ? 865 : (isHighlighted ? highlighted_format : unhighlighted_format); colour = isDisabled ? w->colours[1] | 0x40 : COLOUR_BLACK; - gfx_draw_string_centred(dpi, format, wide ? 270 : 210, y + 1, colour, &placeholderStringId); + gfx_draw_string_centred(dpi, format, wide ? 270 : 210, y + 1, colour, &placeholderStringId); - // Check if scenario is completed + // Check if scenario is completed if (isCompleted) { - // Draw completion tick - gfx_draw_sprite(dpi, 0x5A9F, wide ? 500 : 395, y + 1, 0); + // Draw completion tick + gfx_draw_sprite(dpi, 0x5A9F, wide ? 500 : 395, y + 1, 0); - // Draw completion score - 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); - } + // Draw completion score + const utf8 *completedByName = "???"; + if (!str_is_null_or_empty(scenario->highscore->name)) { + completedByName = scenario->highscore->name; + } + safe_strncpy((char*)language_get_string(placeholderStringId), completedByName, 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); + } - y += 24; + y += 24; break; } } @@ -482,7 +496,7 @@ static void draw_category_heading(rct_window *w, rct_drawpixelinfo *dpi, int lef lineY++; gfx_draw_line(dpi, left, lineY, strLeft, lineY, darkColour); gfx_draw_line(dpi, strRight, lineY, right, lineY, darkColour); - } +} static void initialise_list_items(rct_window *w) { @@ -560,10 +574,6 @@ static bool is_scenario_visible(rct_window *w, scenario_index_entry *scenario) return false; } - if (!(scenario->flags & SCENARIO_FLAGS_VISIBLE)) { - return false; - } - return true; }