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