redo entire scenario highscore load / saving

- load and save highscores to new file format (highscores.dat)
- scan scenarios from RCT2 data and user data
- load highscores from legacy file (scores.dat)
- fix various issues with new scenario select window
This commit is contained in:
IntelOrca 2015-12-31 02:59:08 +00:00
parent 63529a5213
commit 6a768c4a3c
13 changed files with 595 additions and 299 deletions

View file

@ -264,6 +264,7 @@
<ClInclude Include="src\ride\track_paint.h" />
<ClInclude Include="src\ride\vehicle.h" />
<ClInclude Include="src\scenario.h" />
<ClInclude Include="src\scenario_sources.h" />
<ClInclude Include="src\sprites.h" />
<ClInclude Include="src\version.h" />
<ClInclude Include="test\management\finance_test.h" />

View file

@ -845,5 +845,6 @@
<ClInclude Include="src\image_io.h">
<Filter>Source</Filter>
</ClInclude>
<ClInclude Include="src\scenario_sources.h" />
</ItemGroup>
</Project>

View file

@ -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 },
};

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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",

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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);
}

View file

@ -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