Merge pull request #4606 from IntelOrca/refactor/scenario-sources

Refactor scenario sources and scenario list to C++
This commit is contained in:
Ted John 2016-10-16 22:27:34 +01:00 committed by GitHub
commit dd3db116f7
19 changed files with 1335 additions and 1084 deletions

View File

@ -305,8 +305,6 @@
D442724A1CC81B3200D84D28 /* track_data.c in Sources */ = {isa = PBXBuildFile; fileRef = D442717B1CC81B3200D84D28 /* track_data.c */; };
D442724B1CC81B3200D84D28 /* track_paint.c in Sources */ = {isa = PBXBuildFile; fileRef = D442717D1CC81B3200D84D28 /* track_paint.c */; };
D442724C1CC81B3200D84D28 /* vehicle.c in Sources */ = {isa = PBXBuildFile; fileRef = D442717F1CC81B3200D84D28 /* vehicle.c */; };
D442724D1CC81B3200D84D28 /* scenario_list.c in Sources */ = {isa = PBXBuildFile; fileRef = D44271811CC81B3200D84D28 /* scenario_list.c */; };
D442724E1CC81B3200D84D28 /* scenario_sources.c in Sources */ = {isa = PBXBuildFile; fileRef = D44271821CC81B3200D84D28 /* scenario_sources.c */; };
D442724F1CC81B3200D84D28 /* scenario.c in Sources */ = {isa = PBXBuildFile; fileRef = D44271831CC81B3200D84D28 /* scenario.c */; };
D44272501CC81B3200D84D28 /* title.c in Sources */ = {isa = PBXBuildFile; fileRef = D44271861CC81B3200D84D28 /* title.c */; };
D44272511CC81B3200D84D28 /* sawyercoding.c in Sources */ = {isa = PBXBuildFile; fileRef = D44271891CC81B3200D84D28 /* sawyercoding.c */; };
@ -435,6 +433,8 @@
D464FEF51D31A6AA00CBABAC /* WaterObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEE31D31A6AA00CBABAC /* WaterObject.cpp */; };
D46F2A9E1D39A25A00A36AB7 /* peep_data.c in Sources */ = {isa = PBXBuildFile; fileRef = D46F2A9D1D39A25A00A36AB7 /* peep_data.c */; };
D47304D51C4FF8250015C0EA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D47304D41C4FF8250015C0EA /* libz.tbd */; };
D4867B861DAEF6200003B684 /* ScenarioRepository.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D4867B841DAEF6200003B684 /* ScenarioRepository.cpp */; };
D4867B871DAEF6200003B684 /* ScenarioSources.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D4867B851DAEF6200003B684 /* ScenarioSources.cpp */; };
D48A8D831D00272F00649DA7 /* TcpSocket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D48A8D811D00272F00649DA7 /* TcpSocket.cpp */; };
D49766831D03B9FE002222CD /* SoftwareDrawingEngine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D49766811D03B9FE002222CD /* SoftwareDrawingEngine.cpp */; };
D49766861D03BAA5002222CD /* NewDrawing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D49766841D03BAA5002222CD /* NewDrawing.cpp */; };
@ -801,8 +801,6 @@
D442717E1CC81B3200D84D28 /* track_paint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = track_paint.h; sourceTree = "<group>"; };
D442717F1CC81B3200D84D28 /* vehicle.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vehicle.c; sourceTree = "<group>"; };
D44271801CC81B3200D84D28 /* vehicle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vehicle.h; sourceTree = "<group>"; };
D44271811CC81B3200D84D28 /* scenario_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = scenario_list.c; sourceTree = "<group>"; };
D44271821CC81B3200D84D28 /* scenario_sources.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = scenario_sources.c; sourceTree = "<group>"; };
D44271831CC81B3200D84D28 /* scenario.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = scenario.c; sourceTree = "<group>"; };
D44271841CC81B3200D84D28 /* scenario.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scenario.h; sourceTree = "<group>"; };
D44271851CC81B3200D84D28 /* sprites.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sprites.h; sourceTree = "<group>"; };
@ -1113,6 +1111,10 @@
D464FEE41D31A6AA00CBABAC /* WaterObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WaterObject.h; sourceTree = "<group>"; };
D46F2A9D1D39A25A00A36AB7 /* peep_data.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = peep_data.c; sourceTree = "<group>"; };
D47304D41C4FF8250015C0EA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
D4867B841DAEF6200003B684 /* ScenarioRepository.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScenarioRepository.cpp; sourceTree = "<group>"; };
D4867B851DAEF6200003B684 /* ScenarioSources.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScenarioSources.cpp; sourceTree = "<group>"; };
D4867B881DAEF62B0003B684 /* ScenarioRepository.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScenarioRepository.h; sourceTree = "<group>"; };
D4867B891DAEF62B0003B684 /* ScenarioSources.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScenarioSources.h; sourceTree = "<group>"; };
D4895D321C23EFDD000CD788 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = distribution/macos/Info.plist; sourceTree = SOURCE_ROOT; };
D48A8D811D00272F00649DA7 /* TcpSocket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TcpSocket.cpp; sourceTree = "<group>"; usesTabs = 0; };
D48A8D821D00272F00649DA7 /* TcpSocket.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = TcpSocket.h; sourceTree = "<group>"; usesTabs = 0; };
@ -1404,9 +1406,9 @@
D44271591CC81B3200D84D28 /* openrct2.c */,
D44271691CC81B3200D84D28 /* rct1.c */,
D442716B1CC81B3200D84D28 /* rct2.c */,
D44271811CC81B3200D84D28 /* scenario_list.c */,
D44271821CC81B3200D84D28 /* scenario_sources.c */,
D44271831CC81B3200D84D28 /* scenario.c */,
D4867B841DAEF6200003B684 /* ScenarioRepository.cpp */,
D4867B851DAEF6200003B684 /* ScenarioSources.cpp */,
D44271861CC81B3200D84D28 /* title.c */,
D4CA88651D4E64C800060C11 /* version.c */,
D44270CE1CC81B3200D84D28 /* addresses.h */,
@ -1425,6 +1427,8 @@
D442716A1CC81B3200D84D28 /* rct1.h */,
D442716C1CC81B3200D84D28 /* rct2.h */,
D44271841CC81B3200D84D28 /* scenario.h */,
D4867B881DAEF62B0003B684 /* ScenarioRepository.h */,
D4867B891DAEF62B0003B684 /* ScenarioSources.h */,
D44271851CC81B3200D84D28 /* sprites.h */,
D44271871CC81B3200D84D28 /* title.h */,
D442718D1CC81B3200D84D28 /* version.h */,
@ -2494,7 +2498,6 @@
D442729C1CC81B3200D84D28 /* duck.c in Sources */,
C686F91D1CDBC3B7009F9BFC /* multi_dimension_roller_coaster.c in Sources */,
C686F8B31CDBC37E009F9BFC /* surface.c in Sources */,
D442724E1CC81B3200D84D28 /* scenario_sources.c in Sources */,
D442729A1CC81B3200D84D28 /* banner.c in Sources */,
C650B21C1CCABC4400B4D91C /* ConvertCommand.cpp in Sources */,
D44272211CC81B3200D84D28 /* viewport_interaction.c in Sources */,
@ -2636,6 +2639,7 @@
D44272231CC81B3200D84D28 /* window.c in Sources */,
D44272451CC81B3200D84D28 /* ride.c in Sources */,
C686F8B91CDBC37E009F9BFC /* supports.c in Sources */,
D4867B871DAEF6200003B684 /* ScenarioSources.cpp in Sources */,
D442726E1CC81B3200D84D28 /* maze_construction.c in Sources */,
D44272751CC81B3200D84D28 /* news_options.c in Sources */,
D44272551CC81B3200D84D28 /* changelog.c in Sources */,
@ -2655,7 +2659,6 @@
D44272411CC81B3200D84D28 /* rct1.c in Sources */,
D44272621CC81B3200D84D28 /* footpath.c in Sources */,
D44272521CC81B3200D84D28 /* util.c in Sources */,
D442724D1CC81B3200D84D28 /* scenario_list.c in Sources */,
D44272A21CC81B3200D84D28 /* mapgen.c in Sources */,
D44272201CC81B3200D84D28 /* viewport.c in Sources */,
D442722F1CC81B3200D84D28 /* finance.c in Sources */,
@ -2750,6 +2753,7 @@
D44272771CC81B3200D84D28 /* park.c in Sources */,
C686F9141CDBC3B7009F9BFC /* inverted_roller_coaster.c in Sources */,
C686F9551CDBC3B7009F9BFC /* water_coaster.c in Sources */,
D4867B861DAEF6200003B684 /* ScenarioRepository.cpp in Sources */,
D44272461CC81B3200D84D28 /* ride_data.c in Sources */,
D44272181CC81B3200D84D28 /* chat.c in Sources */,
C686F93B1CDBC3B7009F9BFC /* facility.c in Sources */,

View File

@ -263,7 +263,7 @@
<ClCompile Include="src\ride\water\submarine_ride.c" />
<ClCompile Include="src\ride\water\water_coaster.c" />
<ClCompile Include="src\scenario.c" />
<ClCompile Include="src\scenario_list.c" />
<ClCompile Include="src\ScenarioRepository.cpp" />
<ClCompile Include="src\windows\changelog.c" />
<ClCompile Include="src\windows\multiplayer.c" />
<ClCompile Include="src\windows\network_status.c" />
@ -499,7 +499,9 @@
<ClInclude Include="src\ride\vehicle_data.h" />
<ClInclude Include="src\ride\vehicle_paint.h" />
<ClInclude Include="src\scenario.h" />
<ClCompile Include="src\scenario_sources.c" />
<ClCompile Include="src\ScenarioSources.cpp" />
<ClInclude Include="src\ScenarioRepository.h" />
<ClInclude Include="src\ScenarioSources.h" />
<ClInclude Include="src\sprites.h" />
<ClInclude Include="src\version.h" />
<ClInclude Include="src\title.h" />

603
src/ScenarioRepository.cpp Normal file
View File

@ -0,0 +1,603 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <algorithm>
#include <memory>
#include <vector>
#include "core/Console.hpp"
#include "core/FileEnumerator.h"
#include "core/FileStream.hpp"
#include "core/Math.hpp"
#include "core/Path.hpp"
#include "core/String.hpp"
#include "ScenarioRepository.h"
#include "ScenarioSources.h"
extern "C"
{
#include "config.h"
#include "localisation/localisation.h"
#include "scenario.h"
}
static int ScenarioCategoryCompare(int categoryA, int categoryB)
{
if (categoryA == categoryB) return 0;
if (categoryA == SCENARIO_CATEGORY_DLC) return -1;
if (categoryB == SCENARIO_CATEGORY_DLC) return 1;
if (categoryA == SCENARIO_CATEGORY_BUILD_YOUR_OWN) return -1;
if (categoryB == SCENARIO_CATEGORY_BUILD_YOUR_OWN) return 1;
return Math::Sign(categoryA - categoryB);
}
static int scenario_index_entry_CompareByCategory(const scenario_index_entry &entryA,
const scenario_index_entry &entryB)
{
// Order by category
if (entryA.category != entryB.category)
{
return ScenarioCategoryCompare(entryA.category, entryB.category);
}
// Then by source game / name
switch (entryA.category) {
default:
if (entryA.source_game != entryB.source_game)
{
return entryA.source_game - entryB.source_game;
}
return strcmp(entryA.name, entryB.name);
case SCENARIO_CATEGORY_REAL:
case SCENARIO_CATEGORY_OTHER:
return strcmp(entryA.name, entryB.name);
}
}
static int scenario_index_entry_CompareByIndex(const scenario_index_entry &entryA,
const scenario_index_entry &entryB)
{
// Order by source game
if (entryA.source_game != entryB.source_game)
{
return entryA.source_game - entryB.source_game;
}
// Then by index / category / name
uint8 sourceGame = entryA.source_game;
switch (sourceGame) {
default:
if (entryA.source_index == -1 && entryB.source_index == -1)
{
if (entryA.category == entryB.category)
{
return scenario_index_entry_CompareByCategory(entryA, entryB);
}
else
{
return ScenarioCategoryCompare(entryA.category, entryB.category);
}
}
else if (entryA.source_index == -1)
{
return 1;
}
else if (entryB.source_index == -1)
{
return -1;
}
else
{
return entryA.source_index - entryB.source_index;
}
case SCENARIO_SOURCE_REAL:
return scenario_index_entry_CompareByCategory(entryA, entryB);
}
}
static void scenario_highscore_free(scenario_highscore_entry * highscore)
{
SafeFree(highscore->fileName);
SafeFree(highscore->name);
SafeDelete(highscore);
}
class ScenarioRepository final : public IScenarioRepository
{
private:
static constexpr uint32 HighscoreFileVersion = 1;
std::vector<scenario_index_entry> _scenarios;
std::vector<scenario_highscore_entry*> _highscores;
public:
virtual ~ScenarioRepository()
{
ClearHighscores();
}
void Scan() override
{
utf8 directory[MAX_PATH];
_scenarios.clear();
// Scan RCT2 directory
GetRCT2Directory(directory, sizeof(directory));
Scan(directory);
// Scan user directory
GetUserDirectory(directory, sizeof(directory));
Scan(directory);
Sort();
LoadScores();
LoadLegacyScores();
AttachHighscores();
}
size_t GetCount() const override
{
return _scenarios.size();
}
const scenario_index_entry * GetByIndex(size_t index) const override
{
const scenario_index_entry * result = nullptr;
if (index < _scenarios.size())
{
result = &_scenarios[index];
}
return result;
}
const scenario_index_entry * GetByFilename(const utf8 * filename) const override
{
for (size_t i = 0; i < _scenarios.size(); i++)
{
const scenario_index_entry * scenario = &_scenarios[i];
const utf8 * scenarioFilename = Path::GetFileName(scenario->path);
// Note: this is always case insensitive search for cross platform consistency
if (String::Equals(filename, scenarioFilename, true))
{
return &_scenarios[i];
}
}
return nullptr;
}
const scenario_index_entry * GetByPath(const utf8 * path) const override
{
for (size_t i = 0; i < _scenarios.size(); i++)
{
const scenario_index_entry * scenario = &_scenarios[i];
if (Path::Equals(path, scenario->path))
{
return scenario;
}
}
return nullptr;
}
bool TryRecordHighscore(const utf8 * scenarioFileName, money32 companyValue, const utf8 * name) override
{
scenario_index_entry * scenario = GetByFilename(scenarioFileName);
if (scenario != nullptr)
{
// Check if record company value has been broken or the highscore is the same but no name is registered
scenario_highscore_entry * highscore = scenario->highscore;
if (highscore == nullptr || companyValue > highscore->company_value ||
(highscore->name == nullptr && companyValue == highscore->company_value))
{
if (highscore == nullptr)
{
highscore = InsertHighscore();
highscore->timestamp = platform_get_datetime_now_utc();
scenario->highscore = highscore;
}
else
{
if (highscore->name != nullptr)
{
highscore->timestamp = platform_get_datetime_now_utc();
}
SafeFree(highscore->fileName);
SafeFree(highscore->name);
}
highscore->fileName = String::Duplicate(Path::GetFileName(scenario->path));
highscore->name = String::Duplicate(name);
highscore->company_value = companyValue;
SaveHighscores();
return true;
}
}
return false;
}
private:
scenario_index_entry * GetByFilename(const utf8 * filename)
{
const ScenarioRepository * repo = this;
return (scenario_index_entry *)repo->GetByFilename(filename);
}
scenario_index_entry * GetByPath(const utf8 * path)
{
const ScenarioRepository * repo = this;
return (scenario_index_entry *)repo->GetByPath(path);
}
void Scan(const utf8 * directory)
{
utf8 pattern[MAX_PATH];
String::Set(pattern, sizeof(pattern), directory);
Path::Append(pattern, sizeof(pattern), "*.sc6");
auto fileEnumerator = FileEnumerator(pattern, true);
while (fileEnumerator.Next())
{
auto path = fileEnumerator.GetPath();
auto fileInfo = fileEnumerator.GetFileInfo();
AddScenario(path, fileInfo->last_modified);
}
}
void AddScenario(const utf8 * path, uint64 timestamp)
{
rct_s6_header s6Header;
rct_s6_info s6Info;
if (!scenario_load_basic(path, &s6Header, &s6Info))
{
Console::Error::WriteLine("Unable to read scenario: '%s'", path);
return;
}
const utf8 * filename = Path::GetFileName(path);
scenario_index_entry * existingEntry = GetByFilename(filename);
if (existingEntry != nullptr)
{
const utf8 * conflictPath;
if (existingEntry->timestamp > timestamp)
{
// Existing entry is more recent
conflictPath = existingEntry->path;
// Overwrite existing entry with this one
*existingEntry = CreateNewScenarioEntry(path, timestamp, &s6Info);
}
else
{
// This entry is more recent
conflictPath = path;
}
Console::WriteLine("Scenario conflict: '%s' ignored because it is newer.", conflictPath);
}
else
{
scenario_index_entry entry = CreateNewScenarioEntry(path, timestamp, &s6Info);
_scenarios.push_back(entry);
}
}
scenario_index_entry CreateNewScenarioEntry(const utf8 * path, uint64 timestamp, rct_s6_info * s6Info)
{
scenario_index_entry entry = { 0 };
// Set new entry
String::Set(entry.path, sizeof(entry.path), path);
entry.timestamp = timestamp;
entry.category = s6Info->category;
entry.objective_type = s6Info->objective_type;
entry.objective_arg_1 = s6Info->objective_arg_1;
entry.objective_arg_2 = s6Info->objective_arg_2;
entry.objective_arg_3 = s6Info->objective_arg_3;
entry.highscore = nullptr;
String::Set(entry.name, sizeof(entry.name), s6Info->name);
String::Set(entry.details, sizeof(entry.details), s6Info->details);
// Normalise the name to make the scenario as recognisable as possible.
ScenarioSources::NormaliseName(entry.name, sizeof(entry.name), entry.name);
// Look up and store information regarding the origins of this scenario.
source_desc desc;
if (ScenarioSources::TryGetByName(entry.name, &desc))
{
entry.sc_id = desc.id;
entry.source_index = desc.index;
entry.source_game = desc.source;
entry.category = desc.category;
}
else
{
entry.sc_id = SC_UNIDENTIFIED;
entry.source_index = -1;
if (entry.category == SCENARIO_CATEGORY_REAL)
{
entry.source_game = SCENARIO_SOURCE_REAL;
}
else
{
entry.source_game = SCENARIO_SOURCE_OTHER;
}
}
scenario_translate(&entry, &s6Info->entry);
return entry;
}
void Sort()
{
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN)
{
std::sort(_scenarios.begin(), _scenarios.end(), [](const scenario_index_entry &a,
const scenario_index_entry &b) -> bool
{
return scenario_index_entry_CompareByIndex(a, b) < 0;
});
}
else
{
std::sort(_scenarios.begin(), _scenarios.end(), [](const scenario_index_entry &a,
const scenario_index_entry &b) -> bool
{
return scenario_index_entry_CompareByCategory(a, b) < 0;
});
}
}
void LoadScores()
{
utf8 scoresPath[MAX_PATH];
GetScoresPath(scoresPath, sizeof(scoresPath));
if (!platform_file_exists(scoresPath))
{
return;
}
try
{
auto fs = FileStream(scoresPath, FILE_MODE_OPEN);
uint32 fileVersion = fs.ReadValue<uint32>();
if (fileVersion != 1)
{
Console::Error::WriteLine("Invalid or incompatible highscores file.");
return;
}
ClearHighscores();
uint32 numHighscores = fs.ReadValue<uint32>();
for (uint32 i = 0; i < numHighscores; i++)
{
scenario_highscore_entry * highscore = InsertHighscore();
highscore->fileName = fs.ReadString();
highscore->name = fs.ReadString();
highscore->company_value = fs.ReadValue<money32>();
highscore->timestamp = fs.ReadValue<datetime64>();
}
}
catch (Exception ex)
{
Console::Error::WriteLine("Error reading highscores.");
}
}
/**
* Loads the original scores.dat file and replaces any highscores that
* are better for matching scenarios.
*/
void LoadLegacyScores()
{
utf8 scoresPath[MAX_PATH];
GetLegacyScoresPath(scoresPath, sizeof(scoresPath));
LoadLegacyScores(scoresPath);
GetRCT2ScoresPath(scoresPath, sizeof(scoresPath));
LoadLegacyScores(scoresPath);
}
void LoadLegacyScores(const utf8 * path)
{
if (!platform_file_exists(path))
{
return;
}
bool highscoresDirty = false;
try
{
auto fs = FileStream(path, FILE_MODE_OPEN);
if (fs.GetLength() <= 4)
{
// Initial value of scores for RCT2, just ignore
return;
}
// Load header
auto header = fs.ReadValue<rct_scenario_scores_header>();
for (uint32 i = 0; i < header.scenario_count; i++)
{
// Read legacy entry
auto scBasic = fs.ReadValue<rct_scenario_basic>();
// Ignore non-completed scenarios
if (scBasic.flags & SCENARIO_FLAGS_COMPLETED)
{
bool notFound = true;
for (size_t i = 0; i < _highscores.size(); i++)
{
scenario_highscore_entry * highscore = _highscores[i];
if (String::Equals(scBasic.path, highscore->fileName, true))
{
notFound = false;
// Check if legacy highscore is better
if (scBasic.company_value > highscore->company_value)
{
SafeFree(highscore->name);
highscore->name = win1252_to_utf8_alloc(scBasic.completed_by);
highscore->company_value = highscore->company_value;
highscore->timestamp = DATETIME64_MIN;
break;
}
}
}
if (notFound)
{
scenario_highscore_entry * highscore = InsertHighscore();
highscore->fileName = String::Duplicate(scBasic.path);
highscore->name = win1252_to_utf8_alloc(scBasic.completed_by);
highscore->company_value = highscore->company_value;
highscore->timestamp = DATETIME64_MIN;
}
}
}
}
catch (Exception ex)
{
Console::Error::WriteLine("Error reading legacy scenario scores file: '%s'", path);
}
if (highscoresDirty)
{
SaveHighscores();
}
}
void ClearHighscores()
{
for (auto highscore : _highscores)
{
scenario_highscore_free(highscore);
}
_highscores.clear();
}
scenario_highscore_entry * InsertHighscore()
{
auto highscore = new scenario_highscore_entry();
memset(highscore, 0, sizeof(scenario_highscore_entry));
_highscores.push_back(highscore);
return highscore;
}
void AttachHighscores()
{
for (size_t i = 0; i < _highscores.size(); i++)
{
scenario_highscore_entry * highscore = _highscores[i];
scenario_index_entry * scenerio = GetByFilename(highscore->fileName);
if (scenerio != nullptr)
{
scenerio->highscore = highscore;
}
}
}
void SaveHighscores()
{
utf8 scoresPath[MAX_PATH];
GetScoresPath(scoresPath, sizeof(scoresPath));
try
{
auto fs = FileStream(scoresPath, FILE_MODE_WRITE);
fs.WriteValue<uint32>(HighscoreFileVersion);
fs.WriteValue<uint32>((uint32)_highscores.size());
for (size_t i = 0; i < _highscores.size(); i++)
{
const scenario_highscore_entry * highscore = _highscores[i];
fs.WriteString(highscore->fileName);
fs.WriteString(highscore->name);
fs.WriteValue(highscore->company_value);
fs.WriteValue(highscore->timestamp);
}
}
catch (Exception ex)
{
Console::Error::WriteLine("Unable to save highscores to '%s'", scoresPath);
}
}
static utf8 * GetRCT2Directory(utf8 * buffer, size_t bufferSize)
{
String::Set(buffer, bufferSize, gRCT2AddressAppPath);
Path::Append(buffer, bufferSize, "Scenarios");
return buffer;
}
static utf8 * GetUserDirectory(utf8 * buffer, size_t bufferSize)
{
platform_get_user_directory(buffer, "scenario", bufferSize);
return buffer;
}
static void GetScoresPath(utf8 * buffer, size_t bufferSize)
{
platform_get_user_directory(buffer, nullptr, bufferSize);
Path::Append(buffer, bufferSize, "highscores.dat");
}
static void GetLegacyScoresPath(utf8 * buffer, size_t bufferSize)
{
platform_get_user_directory(buffer, nullptr, bufferSize);
Path::Append(buffer, bufferSize, "scores.dat");
}
static void GetRCT2ScoresPath(utf8 * buffer, size_t bufferSize)
{
String::Set(buffer, bufferSize, get_file_path(PATH_ID_SCORES));
}
};
static std::unique_ptr<ScenarioRepository> _scenarioRepository;
IScenarioRepository * GetScenarioRepository()
{
if (_scenarioRepository == nullptr)
{
_scenarioRepository = std::unique_ptr<ScenarioRepository>(new ScenarioRepository());
}
return _scenarioRepository.get();
}
extern "C"
{
void scenario_repository_scan()
{
IScenarioRepository * repo = GetScenarioRepository();
repo->Scan();
}
size_t scenario_repository_get_count()
{
IScenarioRepository * repo = GetScenarioRepository();
return repo->GetCount();
}
const scenario_index_entry *scenario_repository_get_by_index(size_t index)
{
IScenarioRepository * repo = GetScenarioRepository();
return repo->GetByIndex(index);
}
bool scenario_repository_try_record_highscore(const utf8 * scenarioFileName, money32 companyValue, const utf8 * name)
{
IScenarioRepository * repo = GetScenarioRepository();
return repo->TryRecordHighscore(scenarioFileName, companyValue, name);
}
}

93
src/ScenarioRepository.h Normal file
View File

@ -0,0 +1,93 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include "common.h"
#ifndef MAX_PATH
#define MAX_PATH 260
#endif
struct rct_object_entry;
typedef struct scenario_highscore_entry
{
utf8 * fileName;
utf8 * name;
money32 company_value;
datetime64 timestamp;
} scenario_highscore_entry;
typedef struct scenario_index_entry
{
utf8 path[MAX_PATH];
uint64 timestamp;
// Category / sequence
uint8 category;
uint8 source_game;
sint16 source_index;
uint16 sc_id;
// 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;
#ifdef __cplusplus
interface IScenarioRepository
{
virtual ~IScenarioRepository() = default;
/**
* Scans the scenario directories and grabs the metadata for all the scenarios.
*/
virtual void Scan() abstract;
virtual size_t GetCount() const abstract;
virtual const scenario_index_entry * GetByIndex(size_t index) const abstract;
virtual const scenario_index_entry * GetByFilename(const utf8 * filename) const abstract;
virtual const scenario_index_entry * GetByPath(const utf8 * path) const abstract;
virtual bool TryRecordHighscore(const utf8 * scenarioFileName, money32 companyValue, const utf8 * name) abstract;
};
IScenarioRepository * GetScenarioRepository();
#endif
#ifdef __cplusplus
extern "C"
{
#endif
void scenario_repository_scan();
size_t scenario_repository_get_count();
const scenario_index_entry *scenario_repository_get_by_index(size_t index);
bool scenario_repository_try_record_highscore(const utf8 * scenarioFileName, money32 companyValue, const utf8 * name);
void scenario_translate(scenario_index_entry * scenarioEntry, const struct rct_object_entry * stexObjectEntry);
#ifdef __cplusplus
}
#endif

379
src/ScenarioSources.cpp Normal file
View File

@ -0,0 +1,379 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "core/Guard.hpp"
#include "core/String.hpp"
#include "core/Util.hpp"
#include "ScenarioSources.h"
extern "C"
{
#include "scenario.h"
#include "util/util.h"
}
namespace ScenarioSources
{
struct ScenarioAlias
{
const utf8 * Original;
const utf8 * Alternative;
};
struct ScenarioTitleDescriptor
{
const uint8 Id;
const utf8 * Title;
const uint8 Category;
};
#pragma region Scenario Data
static const ScenarioAlias ScenarioAliases[] =
{
// UK - US differences:
{ "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" },
{ "Haunted Harbour", "Haunted Harbor" },
{ "Mythological - Cradle of Civilisation", "Mythological - Cradle of Civilization" },
// RCT1 pack by RCTScenarioLover has a mistake:
{ "Geoffrey Gardens", "Geoffery Gardens" },
};
// RCT
static const ScenarioTitleDescriptor ScenarioTitlesRCT1[] =
{
{ SC_FOREST_FRONTIERS, "Forest Frontiers", SCENARIO_CATEGORY_BEGINNER },
{ SC_DYNAMITE_DUNES, "Dynamite Dunes", SCENARIO_CATEGORY_BEGINNER },
{ SC_LEAFY_LAKES, "Leafy Lake", SCENARIO_CATEGORY_BEGINNER },
{ SC_DIAMOND_HEIGHTS, "Diamond Heights", SCENARIO_CATEGORY_BEGINNER },
{ SC_EVERGREEN_GARDENS, "Evergreen Gardens", SCENARIO_CATEGORY_BEGINNER },
{ SC_BUMBLY_BEACH, "Bumbly Beach", SCENARIO_CATEGORY_BEGINNER },
{ SC_TRINITY_ISLANDS, "Trinity Islands", SCENARIO_CATEGORY_CHALLENGING },
{ SC_KATIES_DREAMLAND, "Katie's Dreamland", SCENARIO_CATEGORY_CHALLENGING },
{ SC_POKEY_PARK, "Pokey Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_WHITE_WATER_PARK, "White Water Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_MILLENNIUM_MINES, "Millennium Mines", SCENARIO_CATEGORY_CHALLENGING },
{ SC_KARTS_COASTERS, "Karts & Coasters", SCENARIO_CATEGORY_CHALLENGING },
{ SC_MELS_WORLD, "Mel's World", SCENARIO_CATEGORY_CHALLENGING },
{ SC_MYSTIC_MOUNTAIN, "Mystic Mountain", SCENARIO_CATEGORY_CHALLENGING },
{ SC_PACIFIC_PYRAMIDS, "Pacific Pyramids", SCENARIO_CATEGORY_CHALLENGING },
{ SC_CRUMBLY_WOODS, "Crumbly Woods", SCENARIO_CATEGORY_CHALLENGING },
{ SC_PARADISE_PIER, "Paradise Pier", SCENARIO_CATEGORY_CHALLENGING },
{ SC_LIGHTNING_PEAKS, "Lightning Peaks", SCENARIO_CATEGORY_EXPERT },
{ SC_IVORY_TOWERS, "Ivory Towers", SCENARIO_CATEGORY_EXPERT },
{ SC_RAINBOW_VALLEY, "Rainbow Valley", SCENARIO_CATEGORY_EXPERT },
{ SC_THUNDER_ROCK, "Thunder Rock", SCENARIO_CATEGORY_EXPERT },
{ SC_MEGA_PARK, "Mega Park", SCENARIO_CATEGORY_OTHER },
};
// RCT: Added Attractions
static const ScenarioTitleDescriptor ScenarioTitlesRCT1AA[] =
{
{ SC_WHISPERING_CLIFFS, "Whispering Cliffs", SCENARIO_CATEGORY_BEGINNER },
{ SC_THREE_MONKEYS_PARK, "Three Monkeys Park", SCENARIO_CATEGORY_BEGINNER },
{ SC_CANARY_MINES, "Canary Mines", SCENARIO_CATEGORY_BEGINNER },
{ SC_BARONY_BRIDGE, "Barony Bridge", SCENARIO_CATEGORY_BEGINNER },
{ SC_FUNTOPIA, "Funtopia", SCENARIO_CATEGORY_BEGINNER },
{ SC_HAUNTED_HARBOUR, "Haunted Harbour", SCENARIO_CATEGORY_BEGINNER },
{ SC_FUN_FORTRESS, "Fun Fortress", SCENARIO_CATEGORY_BEGINNER },
{ SC_FUTURE_WORLD, "Future World", SCENARIO_CATEGORY_BEGINNER },
{ SC_GENTLE_GLEN, "Gentle Glen", SCENARIO_CATEGORY_BEGINNER },
{ SC_JOLLY_JUNGLE, "Jolly Jungle", SCENARIO_CATEGORY_CHALLENGING },
{ SC_HYDRO_HILLS, "Hydro Hills", SCENARIO_CATEGORY_CHALLENGING },
{ SC_SPRIGHTLY_PARK, "Sprightly Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_MAGIC_QUARTERS, "Magic Quarters", SCENARIO_CATEGORY_CHALLENGING },
{ SC_FRUIT_FARM, "Fruit Farm", SCENARIO_CATEGORY_CHALLENGING },
{ SC_BUTTERFLY_DAM, "Butterfly Dam", SCENARIO_CATEGORY_CHALLENGING },
{ SC_COASTER_CANYON, "Coaster Canyon", SCENARIO_CATEGORY_CHALLENGING },
{ SC_THUNDERSTORM_PARK, "Thunderstorm Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_HARMONIC_HILLS, "Harmonic Hills", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ROMAN_VILLAGE, "Roman Village", SCENARIO_CATEGORY_CHALLENGING },
{ SC_SWAMP_COVE, "Swamp Cove", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ADRENALINE_HEIGHTS, "Adrenaline Heights", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UTOPIA, "Utopia", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ROTTING_HEIGHTS, "Rotting Heights", SCENARIO_CATEGORY_EXPERT },
{ SC_FIASCO_FOREST, "Fiasco Forest", SCENARIO_CATEGORY_EXPERT },
{ SC_PICKLE_PARK, "Pickle Park", SCENARIO_CATEGORY_EXPERT },
{ SC_GIGGLE_DOWNS, "Giggle Downs", SCENARIO_CATEGORY_EXPERT },
{ SC_MINERAL_PARK, "Mineral Park", SCENARIO_CATEGORY_EXPERT },
{ SC_COASTER_CRAZY, "Coaster Crazy", SCENARIO_CATEGORY_EXPERT },
{ SC_URBAN_PARK, "Urban Park", SCENARIO_CATEGORY_EXPERT },
{ SC_GEOFFREY_GARDENS, "Geoffrey Gardens", SCENARIO_CATEGORY_EXPERT },
};
// RCT: Loopy Landscapes
static const ScenarioTitleDescriptor ScenarioTitlesRCT1LL[] =
{
{ SC_ICEBERG_ISLANDS, "Iceberg Islands", SCENARIO_CATEGORY_BEGINNER },
{ SC_VOLCANIA, "Volcania", SCENARIO_CATEGORY_BEGINNER },
{ SC_ARID_HEIGHTS, "Arid Heights", SCENARIO_CATEGORY_BEGINNER },
{ SC_RAZOR_ROCKS, "Razor Rocks", SCENARIO_CATEGORY_BEGINNER },
{ SC_CRATER_LAKE, "Crater Lake", SCENARIO_CATEGORY_BEGINNER },
{ SC_VERTIGO_VIEWS, "Vertigo Views", SCENARIO_CATEGORY_BEGINNER },
{ SC_PARADISE_PIER_2, "Paradise Pier 2", SCENARIO_CATEGORY_CHALLENGING },
{ SC_DRAGONS_COVE, "Dragon's Cove", SCENARIO_CATEGORY_CHALLENGING },
{ SC_GOOD_KNIGHT_PARK, "Good Knight Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_WACKY_WARREN, "Wacky Warren", SCENARIO_CATEGORY_CHALLENGING },
{ SC_GRAND_GLACIER, "Grand Glacier", SCENARIO_CATEGORY_CHALLENGING },
{ SC_CRAZY_CRATERS, "Crazy Craters", SCENARIO_CATEGORY_CHALLENGING },
{ SC_DUSTY_DESERT, "Dusty Desert", SCENARIO_CATEGORY_CHALLENGING },
{ SC_WOODWORM_PARK, "Woodworm Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ICARUS_PARK, "Icarus Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_SUNNY_SWAMPS, "Sunny Swamps", SCENARIO_CATEGORY_CHALLENGING },
{ SC_FRIGHTMARE_HILLS, "Frightmare Hills", SCENARIO_CATEGORY_CHALLENGING },
{ SC_THUNDER_ROCKS, "Thunder Rocks", SCENARIO_CATEGORY_CHALLENGING },
{ SC_OCTAGON_PARK, "Octagon Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_PLEASURE_ISLAND, "Pleasure Island", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ICICLE_WORLDS, "Icicle Worlds", SCENARIO_CATEGORY_CHALLENGING },
{ SC_SOUTHERN_SANDS, "Southern Sands", SCENARIO_CATEGORY_CHALLENGING },
{ SC_TINY_TOWERS, "Tiny Towers", SCENARIO_CATEGORY_CHALLENGING },
{ SC_NEVERMORE_PARK, "Nevermore Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_PACIFICA, "Pacifica", SCENARIO_CATEGORY_CHALLENGING },
{ SC_URBAN_JUNGLE, "Urban Jungle", SCENARIO_CATEGORY_EXPERT },
{ SC_TERROR_TOWN, "Terror Town", SCENARIO_CATEGORY_EXPERT },
{ SC_MEGAWORLD_PARK, "Megaworld Park", SCENARIO_CATEGORY_EXPERT },
{ SC_VENUS_PONDS, "Venus Ponds", SCENARIO_CATEGORY_EXPERT },
{ SC_MICRO_PARK, "Micro Park", SCENARIO_CATEGORY_EXPERT },
};
// RCT2
static const ScenarioTitleDescriptor ScenarioTitlesRCT2[] =
{
{ SC_UNIDENTIFIED, "Crazy Castle", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Electric Fields", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Factory Capers", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Amity Airfield", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Botany Breakers", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Bumbly Bazaar", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Dusty Greens", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Fungus Woods", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Gravity Gardens", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Infernal Views", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Alpine Adventures", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Extreme Heights", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Ghost Town", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Lucky Lake", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Rainbow Summit", SCENARIO_CATEGORY_EXPERT },
};
// RCT2: Wacky Worlds
static const ScenarioTitleDescriptor ScenarioTitlesRCT2WW[] =
{
{ SC_UNIDENTIFIED, "Africa - Victoria Falls", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Asia - Great Wall of China Tourism Enhancement", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "North America - Grand Canyon", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "South America - Rio Carnival", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Africa - African Diamond Mine", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Asia - Maharaja Palace", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Australasia - Ayers Rock", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Europe - European Cultural Festival", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "North America - Rollercoaster Heaven", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "South America - Inca Lost City", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Africa - Oasis", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Antarctic - Ecological Salvage", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Asia - Japanese Coastal Reclaim", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Australasia - Fun at the Beach", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Europe - Renovation", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "N. America - Extreme Hawaiian Island", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "South America - Rain Forest Plateau", SCENARIO_CATEGORY_EXPERT },
};
// RCT2: Time Twister
static const ScenarioTitleDescriptor ScenarioTitlesRCT2TT[] =
{
{ SC_UNIDENTIFIED, "Dark Age - Robin Hood", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Prehistoric - After the Asteroid", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Roaring Twenties - Prison Island", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Rock 'n' Roll - Flower Power", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Dark Age - Castle", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Future - First Encounters", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Mythological - Animatronic Film Set", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Prehistoric - Jurassic Safari", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Roaring Twenties - Schneider Cup", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Future - Future World", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Mythological - Cradle of Civilisation", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Prehistoric - Stone Age", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Roaring Twenties - Skyscrapers", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Rock 'n' Roll - Rock 'n' Roll", SCENARIO_CATEGORY_EXPERT },
};
// Real parks
static const ScenarioTitleDescriptor ScenarioTitlesRealParks[] =
{
{ SC_UNIDENTIFIED, "Alton Towers", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Heide-Park", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Blackpool Pleasure Beach", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags Belgium", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags Great Adventure", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags Holland", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags Magic Mountain", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags over Texas", SCENARIO_CATEGORY_REAL },
};
// Other parks
static const ScenarioTitleDescriptor ScenarioTitlesOtherParks[] =
{
{ SC_UNIDENTIFIED, "Fort Anachronism", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "PC Player", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "PC Gaming World", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "gameplay", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "Panda World", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "Competition Land 1", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "Competition Land 2", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "Build your own Six Flags Belgium", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags Great Adventure", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags Holland", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags Magic Mountain", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags Park", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags over Texas", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
};
#define DEFINE_SCENARIO_TITLE_DESC_GROUP(x) { Util::CountOf(x), x }
const struct {
size_t count;
const ScenarioTitleDescriptor * const titles;
} ScenarioTitlesBySource[] = {
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT1),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT1AA),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT1LL),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT2),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT2WW),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT2TT),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRealParks),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesOtherParks),
};
#pragma endregion
bool TryGetByName(const utf8 * name, source_desc * outDesc)
{
Guard::ArgumentNotNull(outDesc, GUARD_LINE);
sint32 currentIndex = 0;
for (size_t i = 0; i < Util::CountOf(ScenarioTitlesBySource); i++)
{
for (size_t j = 0; j < ScenarioTitlesBySource[i].count; j++)
{
const ScenarioTitleDescriptor *desc = &ScenarioTitlesBySource[i].titles[j];
if (String::Equals(name, desc->Title, true))
{
outDesc->title = desc->Title;
outDesc->id = desc->Id;
outDesc->source = (uint8)i;
outDesc->index = currentIndex;
outDesc->category = desc->Category;
return true;
}
currentIndex++;
}
}
outDesc->title = NULL;
outDesc->id = SC_UNIDENTIFIED;
outDesc->source = SCENARIO_SOURCE_OTHER;
outDesc->index = -1;
outDesc->category = SCENARIO_CATEGORY_OTHER;
return false;
}
bool TryGetById(uint8 id, source_desc * outDesc)
{
Guard::ArgumentNotNull(outDesc, GUARD_LINE);
sint32 currentIndex = 0;
for (size_t i = 0; i < Util::CountOf(ScenarioTitlesBySource); i++)
{
for (size_t j = 0; j < ScenarioTitlesBySource[i].count; j++)
{
const ScenarioTitleDescriptor * desc = &ScenarioTitlesBySource[i].titles[j];
if (id == desc->Id)
{
outDesc->title = desc->Title;
outDesc->id = desc->Id;
outDesc->source = (uint8)i;
outDesc->index = currentIndex;
outDesc->category = desc->Category;
return true;
}
currentIndex++;
}
}
outDesc->title = NULL;
outDesc->id = SC_UNIDENTIFIED;
outDesc->source = SCENARIO_SOURCE_OTHER;
outDesc->index = -1;
outDesc->category = SCENARIO_CATEGORY_OTHER;
return false;
}
void NormaliseName(utf8 * buffer, size_t bufferSize, const utf8 * name)
{
size_t nameLength = String::LengthOf(name);
// 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);
String::Set(buffer, bufferSize, name + 4);
}
else
{
String::Set(buffer, bufferSize, name + 3);
}
}
// Trim (for the sake of the above and WW / TT scenarios
String::TrimStart(buffer, bufferSize, name);
// American scenario titles should be converted to British name
// Don't worry, names will be translated using language packs later
for (const ScenarioAlias &alias : ScenarioAliases)
{
if (String::Equals(alias.Alternative, name))
{
log_verbose("Found alias: %s; will treat as: %s", name, alias.Original);
String::Set(buffer, bufferSize, alias.Original);
}
}
}
}
extern "C"
{
bool scenario_get_source_desc(const utf8 * name, source_desc * outDesc)
{
return ScenarioSources::TryGetByName(name, outDesc);
}
bool scenario_get_source_desc_by_id(uint8 id, source_desc * outDesc)
{
return ScenarioSources::TryGetById(id, outDesc);
}
void scenario_normalise_name(utf8 * buffer, size_t bufferSize, utf8 * name)
{
ScenarioSources::NormaliseName(buffer, bufferSize, name);
}
}

159
src/ScenarioSources.h Normal file
View File

@ -0,0 +1,159 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma once
#include "common.h"
typedef struct source_desc
{
const utf8 * title;
uint8 id;
uint8 source;
sint32 index;
uint8 category;
} source_desc;
#ifdef __cplusplus
namespace ScenarioSources
{
bool TryGetByName(const utf8 * name, source_desc * outDesc);
bool TryGetById(uint8 id, source_desc * outDesc);
void NormaliseName(utf8 * buffer, size_t bufferSize, const utf8 * name);
}
#endif
#ifdef __cplusplus
extern "C"
{
#endif
bool scenario_get_source_desc(const utf8 *name, source_desc *outDesc);
bool scenario_get_source_desc_by_id(uint8 id, source_desc *outDesc);
void scenario_normalise_name(utf8 *buffer, size_t bufferSize, utf8 *name);
#ifdef __cplusplus
}
#endif
// RCT1 scenario index map
enum
{
SC_UNIDENTIFIED = 255,
// RCT
SC_FOREST_FRONTIERS = 0,
SC_DYNAMITE_DUNES,
SC_LEAFY_LAKES,
SC_DIAMOND_HEIGHTS,
SC_EVERGREEN_GARDENS,
SC_BUMBLY_BEACH,
SC_TRINITY_ISLANDS,
SC_KATIES_DREAMLAND,
SC_POKEY_PARK,
SC_WHITE_WATER_PARK,
SC_MILLENNIUM_MINES,
SC_KARTS_COASTERS,
SC_MELS_WORLD,
SC_MYSTIC_MOUNTAIN,
SC_PACIFIC_PYRAMIDS,
SC_CRUMBLY_WOODS,
SC_PARADISE_PIER,
SC_LIGHTNING_PEAKS,
SC_IVORY_TOWERS,
SC_RAINBOW_VALLEY,
SC_THUNDER_ROCK,
SC_MEGA_PARK,
// Loopy Landscapes
SC_ICEBERG_ISLANDS,
SC_VOLCANIA,
SC_ARID_HEIGHTS,
SC_RAZOR_ROCKS,
SC_CRATER_LAKE,
SC_VERTIGO_VIEWS,
SC_PARADISE_PIER_2,
SC_DRAGONS_COVE,
SC_GOOD_KNIGHT_PARK,
SC_WACKY_WARREN,
// Special
ALTON_TOWERS,
FORT_ANACHRONISM,
// Added Attractions
SC_WHISPERING_CLIFFS = 40,
SC_THREE_MONKEYS_PARK,
SC_CANARY_MINES,
SC_BARONY_BRIDGE,
SC_FUNTOPIA,
SC_HAUNTED_HARBOUR,
SC_FUN_FORTRESS,
SC_FUTURE_WORLD,
SC_GENTLE_GLEN,
SC_JOLLY_JUNGLE,
SC_HYDRO_HILLS,
SC_SPRIGHTLY_PARK,
SC_MAGIC_QUARTERS,
SC_FRUIT_FARM,
SC_BUTTERFLY_DAM,
SC_COASTER_CANYON,
SC_THUNDERSTORM_PARK,
SC_HARMONIC_HILLS,
SC_ROMAN_VILLAGE,
SC_SWAMP_COVE,
SC_ADRENALINE_HEIGHTS,
SC_UTOPIA,
SC_ROTTING_HEIGHTS,
SC_FIASCO_FOREST,
SC_PICKLE_PARK,
SC_GIGGLE_DOWNS,
SC_MINERAL_PARK,
SC_COASTER_CRAZY,
SC_URBAN_PARK,
SC_GEOFFREY_GARDENS,
// Special
SC_HEIDE_PARK,
SC_PCPLAYER,
SC_PCGW,
SC_GAMEPLAY,
SC_BLACKPOOL_PLEASURE_BEACH,
// Loopy Landscapes
SC_GRAND_GLACIER = 80,
SC_CRAZY_CRATERS,
SC_DUSTY_DESERT,
SC_WOODWORM_PARK,
SC_ICARUS_PARK,
SC_SUNNY_SWAMPS,
SC_FRIGHTMARE_HILLS,
SC_THUNDER_ROCKS,
SC_OCTAGON_PARK,
SC_PLEASURE_ISLAND,
SC_ICICLE_WORLDS,
SC_SOUTHERN_SANDS,
SC_TINY_TOWERS,
SC_NEVERMORE_PARK,
SC_PACIFICA,
SC_URBAN_JUNGLE,
SC_TERROR_TOWN,
SC_MEGAWORLD_PARK,
SC_VENUS_PONDS,
SC_MICRO_PARK,
};

View File

@ -40,4 +40,12 @@ namespace Math
{
return (std::min)((std::max)(low, x), high);
}
template<typename T>
T Sign(T x)
{
if (x < 0) return -1;
if (x > 0) return 1;
return 0;
}
}

View File

@ -177,8 +177,13 @@ namespace String
utf8 * Duplicate(const utf8 * src)
{
size_t srcSize = SizeOf(src);
return Memory::DuplicateArray(src, srcSize + 1);
utf8 * result = nullptr;
if (src != nullptr)
{
size_t srcSize = SizeOf(src);
result = Memory::DuplicateArray(src, srcSize + 1);
}
return result;
}
utf8 * DiscardUse(utf8 * * ptr, utf8 * replacement)
@ -252,7 +257,7 @@ namespace String
size_t newStringSize = ch - firstNonWhitespace;
#if DEBUG
size_t currentStringSize = String::SizeOf(str);
assert(newStringSize < currentStringSize);
Guard::Assert(newStringSize < currentStringSize, GUARD_LINE);
#endif
Memory::Move(str, firstNonWhitespace, newStringSize);
@ -265,4 +270,25 @@ namespace String
return str;
}
const utf8 * TrimStart(const utf8 * str)
{
codepoint_t codepoint;
const utf8 * ch = str;
const utf8 * nextCh;
while ((codepoint = GetNextCodepoint(ch, &nextCh)) != '\0')
{
if (codepoint <= WCHAR_MAX && !iswspace((wchar_t)codepoint))
{
return ch;
}
ch = nextCh;
}
return str;
}
utf8 * TrimStart(utf8 * buffer, size_t bufferSize, const utf8 * src)
{
return String::Set(buffer, bufferSize, TrimStart(src));
}
}

View File

@ -65,5 +65,7 @@ namespace String
codepoint_t GetNextCodepoint(const utf8 * ptr, const utf8 * * nextPtr = nullptr);
utf8 * WriteCodepoint(utf8 * dst, codepoint_t codepoint);
utf8 * Trim(utf8 * str);
utf8 * Trim(utf8 * str);
const utf8 * TrimStart(const utf8 * str);
utf8 * TrimStart(utf8 * buffer, size_t bufferSize, const utf8 * src);
}

View File

@ -25,6 +25,7 @@
#include "../ride/track_design.h"
#include "../ride/vehicle.h"
#include "../scenario.h"
#include "../ScenarioRepository.h"
#include "../world/park.h"
#include "colour.h"
@ -282,7 +283,7 @@ typedef struct rct_window {
uint16 ride_colour;
rct_research_item* research_item;
rct_object_entry* object_entry;
scenario_index_entry* highlighted_scenario;
const scenario_index_entry* highlighted_scenario;
struct {
uint16 var_494;
uint16 var_496;

View File

@ -30,6 +30,7 @@
#include "../core/Path.hpp"
#include "../core/Stopwatch.hpp"
#include "../core/String.hpp"
#include "../ScenarioRepository.h"
#include "Object.h"
#include "ObjectFactory.h"
#include "ObjectManager.h"
@ -44,7 +45,6 @@ extern "C"
#include "../object.h"
#include "../object_list.h"
#include "../platform/platform.h"
#include "../scenario.h"
#include "../util/sawyercoding.h"
#include "../util/util.h"
}

View File

@ -23,6 +23,7 @@
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../core/Util.hpp"
#include "../ScenarioSources.h"
#include "../object/ObjectManager.h"
#include "S4Importer.h"
#include "Tables.h"

View File

@ -42,7 +42,7 @@
#include "ride/ride.h"
#include "ride/track.h"
#include "ride/track_design.h"
#include "scenario.h"
#include "ScenarioRepository.h"
#include "title.h"
#include "util/util.h"
#include "world/map.h"
@ -165,7 +165,7 @@ bool rct2_init()
}
object_list_load();
scenario_load_list();
scenario_repository_scan();
track_design_index_create();
font_sprite_initialise_characters();

View File

@ -35,6 +35,8 @@
#include "platform/platform.h"
#include "ride/ride.h"
#include "scenario.h"
#include "ScenarioRepository.h"
#include "ScenarioSources.h"
#include "title.h"
#include "util/sawyercoding.h"
#include "util/util.h"
@ -217,8 +219,7 @@ void scenario_begin()
{
utf8 normalisedName[64];
safe_strcpy(normalisedName, gS6Info.name, sizeof(normalisedName));
scenario_normalise_name(normalisedName);
scenario_normalise_name(normalisedName, sizeof(normalisedName), gS6Info.name);
rct_string_id localisedStringIds[3];
if (language_get_localised_scenario_strings(normalisedName, localisedStringIds)) {
@ -337,25 +338,11 @@ void scenario_success()
gScenarioCompletedCompanyValue = companyValue;
peep_applause();
scenario_index_entry *scenario = scenario_list_find_by_filename(_scenarioFileName);
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->fileName = _strdup(path_get_filename(scenario->path));
scenario->highscore->name = NULL;
scenario->highscore->company_value = companyValue;
scenario->highscore->timestamp = platform_get_datetime_now_utc();
// Allow name entry
gParkFlags |= PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT;
gScenarioCompanyValueRecord = companyValue;
scenario_scores_save();
}
if (scenario_repository_try_record_highscore(_scenarioFileName, companyValue, NULL))
{
// Allow name entry
gParkFlags |= PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT;
gScenarioCompanyValueRecord = companyValue;
}
scenario_end();
}
@ -366,16 +353,10 @@ void scenario_success()
*/
void scenario_success_submit_name(const char *name)
{
scenario_index_entry *scenario = scenario_list_find_by_filename(_scenarioFileName);
if (scenario != NULL) {
money32 scenarioWinCompanyValue = gScenarioCompanyValueRecord;
if (scenario->highscore->company_value == scenarioWinCompanyValue) {
scenario->highscore->name = _strdup(name);
safe_strcpy(gScenarioCompletedBy, name, 32);
scenario_scores_save();
}
if (scenario_repository_try_record_highscore(_scenarioFileName, gScenarioCompanyValueRecord, name))
{
safe_strcpy(gScenarioCompletedBy, name, 32);
}
gParkFlags &= ~PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT;
}

View File

@ -102,7 +102,7 @@ typedef struct rct_scenario_basic {
char name[64]; // 0x0128
char details[256]; // 0x0168
sint32 flags; // 0x0268
uint32 company_value; // 0x026C
money32 company_value; // 0x026C
char completed_by[64]; // 0x0270
// uint8 source_game; // new in OpenRCT2
// sint16 source_index; // new in OpenRCT2
@ -376,42 +376,6 @@ enum {
OBJECTIVE_MONTHLY_FOOD_INCOME
};
typedef struct scenario_highscore_entry {
utf8 *fileName;
utf8 *name;
money32 company_value;
datetime64 timestamp;
} scenario_highscore_entry;
typedef struct scenario_index_entry {
utf8 path[MAX_PATH];
uint64 timestamp;
// Category / sequence
uint8 category;
uint8 source_game;
sint16 source_index;
uint16 sc_id;
// 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;
typedef struct source_desc {
const utf8 *title;
uint8 id;
uint8 source;
sint32 index;
uint8 category;
} source_desc;
extern const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT];
#if defined(NO_RCT2)
@ -431,11 +395,6 @@ extern uint16 gScenarioParkRatingWarningDays;
extern money32 gScenarioCompletedCompanyValue;
extern money32 gScenarioCompanyValueRecord;
// Scenario list
extern int gScenarioListCount;
extern int gScenarioListCapacity;
extern scenario_index_entry *gScenarioList;
extern rct_s6_info gS6Info;
extern char gScenarioName[64];
extern char gScenarioDetails[256];
@ -448,13 +407,6 @@ extern uint32 gLastAutoSaveTick;
extern const char *_scenarioFileName;
bool scenario_scores_save();
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_highscore_entry *scenario_highscore_insert();
void scenario_highscore_free(scenario_highscore_entry *highscore);
bool 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_from_path(const char *path);
@ -475,116 +427,4 @@ void scenario_success();
void scenario_success_submit_name(const char *name);
void scenario_autosave_check();
bool scenario_get_source_desc(const utf8 *name, source_desc *outDesc);
bool scenario_get_source_desc_by_id(uint8 id, source_desc *outDesc);
void scenario_normalise_name(utf8 *name);
void scenario_translate(scenario_index_entry *scenarioEntry, const rct_object_entry *stexObjectEntry);
// RCT1 scenario index map
enum {
SC_UNIDENTIFIED = 255,
// RCT
SC_FOREST_FRONTIERS = 0,
SC_DYNAMITE_DUNES,
SC_LEAFY_LAKES,
SC_DIAMOND_HEIGHTS,
SC_EVERGREEN_GARDENS,
SC_BUMBLY_BEACH,
SC_TRINITY_ISLANDS,
SC_KATIES_DREAMLAND,
SC_POKEY_PARK,
SC_WHITE_WATER_PARK,
SC_MILLENNIUM_MINES,
SC_KARTS_COASTERS,
SC_MELS_WORLD,
SC_MYSTIC_MOUNTAIN,
SC_PACIFIC_PYRAMIDS,
SC_CRUMBLY_WOODS,
SC_PARADISE_PIER,
SC_LIGHTNING_PEAKS,
SC_IVORY_TOWERS,
SC_RAINBOW_VALLEY,
SC_THUNDER_ROCK,
SC_MEGA_PARK,
// Loopy Landscapes
SC_ICEBERG_ISLANDS,
SC_VOLCANIA,
SC_ARID_HEIGHTS,
SC_RAZOR_ROCKS,
SC_CRATER_LAKE,
SC_VERTIGO_VIEWS,
SC_PARADISE_PIER_2,
SC_DRAGONS_COVE,
SC_GOOD_KNIGHT_PARK,
SC_WACKY_WARREN,
// Special
ALTON_TOWERS,
FORT_ANACHRONISM,
// Added Attractions
SC_WHISPERING_CLIFFS = 40,
SC_THREE_MONKEYS_PARK,
SC_CANARY_MINES,
SC_BARONY_BRIDGE,
SC_FUNTOPIA,
SC_HAUNTED_HARBOUR,
SC_FUN_FORTRESS,
SC_FUTURE_WORLD,
SC_GENTLE_GLEN,
SC_JOLLY_JUNGLE,
SC_HYDRO_HILLS,
SC_SPRIGHTLY_PARK,
SC_MAGIC_QUARTERS,
SC_FRUIT_FARM,
SC_BUTTERFLY_DAM,
SC_COASTER_CANYON,
SC_THUNDERSTORM_PARK,
SC_HARMONIC_HILLS,
SC_ROMAN_VILLAGE,
SC_SWAMP_COVE,
SC_ADRENALINE_HEIGHTS,
SC_UTOPIA,
SC_ROTTING_HEIGHTS,
SC_FIASCO_FOREST,
SC_PICKLE_PARK,
SC_GIGGLE_DOWNS,
SC_MINERAL_PARK,
SC_COASTER_CRAZY,
SC_URBAN_PARK,
SC_GEOFFREY_GARDENS,
// Special
SC_HEIDE_PARK,
SC_PCPLAYER,
SC_PCGW,
SC_GAMEPLAY,
SC_BLACKPOOL_PLEASURE_BEACH,
// Loopy Landscapes
SC_GRAND_GLACIER = 80,
SC_CRAZY_CRATERS,
SC_DUSTY_DESERT,
SC_WOODWORM_PARK,
SC_ICARUS_PARK,
SC_SUNNY_SWAMPS,
SC_FRIGHTMARE_HILLS,
SC_THUNDER_ROCKS,
SC_OCTAGON_PARK,
SC_PLEASURE_ISLAND,
SC_ICICLE_WORLDS,
SC_SOUTHERN_SANDS,
SC_TINY_TOWERS,
SC_NEVERMORE_PARK,
SC_PACIFICA,
SC_URBAN_JUNGLE,
SC_TERROR_TOWN,
SC_MEGAWORLD_PARK,
SC_VENUS_PONDS,
SC_MICRO_PARK,
};
#endif

View File

@ -1,532 +0,0 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "config.h"
#include "localisation/localisation.h"
#include "object_list.h"
#include "platform/platform.h"
#include "rct2.h"
#include "scenario.h"
#include "util/util.h"
// Scenario list
int gScenarioListCount = 0;
int gScenarioListCapacity = 0;
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 utf8 *path, uint64 timestamp);
static void scenario_list_sort();
static int scenario_list_sort_by_category(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 void scenario_scores_legacy_get_path(utf8 *outPath, size_t size);
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);
static void io_write_string(SDL_RWops *file, utf8 *source);
/**
* Searches and grabs the metadata for all the scenarios.
*/
void scenario_load_list()
{
utf8 directory[MAX_PATH];
// Clear scenario list
gScenarioListCount = 0;
// Get scenario directory from RCT2
safe_strcpy(directory, gRCT2AddressAppPath, 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", sizeof(directory));
scenario_list_include(directory);
scenario_list_sort();
scenario_scores_load();
utf8 scoresPath[MAX_PATH];
scenario_scores_legacy_get_path(scoresPath, sizeof(scoresPath));
scenario_scores_legacy_load(scoresPath);
scenario_scores_legacy_load(get_file_path(PATH_ID_SCORES));
}
static void scenario_list_include(const utf8 *directory)
{
int handle;
file_info fileInfo;
// Scenarios in this directory
utf8 pattern[MAX_PATH];
safe_strcpy(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_strcpy(path, directory, sizeof(pattern));
safe_strcat_path(path, fileInfo.path, sizeof(pattern));
scenario_list_add(path, fileInfo.last_modified);
}
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_strcpy(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, uint64 timestamp)
{
// Load the basic scenario information
rct_s6_header s6Header;
rct_s6_info s6Info;
if (!scenario_load_basic(path, &s6Header, &s6Info)) {
return;
}
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++;
}
// Set new entry
safe_strcpy(newEntry->path, path, sizeof(newEntry->path));
newEntry->timestamp = timestamp;
newEntry->category = s6Info.category;
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_strcpy(newEntry->name, s6Info.name, sizeof(newEntry->name));
safe_strcpy(newEntry->details, s6Info.details, sizeof(newEntry->details));
// Normalise the name to make the scenario as recognisable as possible.
scenario_normalise_name(newEntry->name);
// Look up and store information regarding the origins of this scenario.
source_desc desc;
if (scenario_get_source_desc(newEntry->name, &desc)) {
newEntry->sc_id = desc.id;
newEntry->source_index = desc.index;
newEntry->source_game = desc.source;
newEntry->category = desc.category;
} else {
newEntry->sc_id = SC_UNIDENTIFIED;
newEntry->source_index = -1;
if (newEntry->category == SCENARIO_CATEGORY_REAL) {
newEntry->source_game = SCENARIO_SOURCE_REAL;
} else {
newEntry->source_game = SCENARIO_SOURCE_OTHER;
}
}
scenario_translate(newEntry, &s6Info.entry);
}
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_category;
qsort(gScenarioList, gScenarioListCount, sizeof(scenario_index_entry), compareFunc);
}
static int scenario_list_category_compare(int categoryA, int categoryB)
{
if (categoryA == categoryB) return 0;
if (categoryA == SCENARIO_CATEGORY_DLC) return -1;
if (categoryB == SCENARIO_CATEGORY_DLC) return 1;
if (categoryA == SCENARIO_CATEGORY_BUILD_YOUR_OWN) return -1;
if (categoryB == SCENARIO_CATEGORY_BUILD_YOUR_OWN) return 1;
return sgn(categoryA - categoryB);
}
static int scenario_list_sort_by_category(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;
// Order by category
if (entryA->category != entryB->category) {
return scenario_list_category_compare(entryA->category, entryB->category);
}
// Then by source game / name
switch (entryA->category) {
default:
if (entryA->source_game != entryB->source_game) {
return entryA->source_game - entryB->source_game;
}
return strcmp(entryA->name, entryB->name);
case SCENARIO_CATEGORY_REAL:
case SCENARIO_CATEGORY_OTHER:
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;
// Order by source game
if (entryA->source_game != entryB->source_game) {
return entryA->source_game - entryB->source_game;
}
// Then by index / category / name
uint8 sourceGame = entryA->source_game;
switch (sourceGame) {
default:
if (entryA->source_index == -1 && entryB->source_index == -1) {
if (entryA->category == entryB->category) {
return scenario_list_sort_by_category(a, b);
} else {
return scenario_list_category_compare(entryA->category, entryB->category);
}
} else if (entryA->source_index == -1) {
return 1;
} else if (entryB->source_index == -1) {
return -1;
} else {
return entryA->source_index - entryB->source_index;
}
case SCENARIO_SOURCE_REAL:
return scenario_list_sort_by_category(a, b);
}
}
scenario_index_entry *scenario_list_find_by_filename(const utf8 *filename)
{
for (int i = 0; i < gScenarioListCount; i++) {
const utf8 *scenarioFilename = path_get_filename(gScenarioList[i].path);
if (_strcmpi(filename, scenarioFilename) == 0) {
return &gScenarioList[i];
}
}
return NULL;
}
scenario_index_entry *scenario_list_find_by_path(const utf8 *path)
{
for (int i = 0; i < gScenarioListCount; i++) {
if (_strcmpi(path, gScenarioList[i].path) == 0) {
return &gScenarioList[i];
}
}
return NULL;
}
/**
* Gets the path for the scenario scores path.
*/
static void scenario_scores_get_path(utf8 *outPath, size_t size)
{
platform_get_user_directory(outPath, NULL, size);
safe_strcat_path(outPath, "highscores.dat", size);
}
/**
* Gets the path for the scenario scores path.
*/
static void scenario_scores_legacy_get_path(utf8 *outPath, size_t size)
{
platform_get_user_directory(outPath, NULL, size);
safe_strcat_path(outPath, "scores.dat", size);
}
/**
* Loads the original scores.dat file and replaces any highscores that
* are better for matching scenarios.
*/
static bool scenario_scores_legacy_load(const utf8 *path)
{
// First check user folder and then fallback to install directory
SDL_RWops *file = SDL_RWFromFile(path, "rb");
if (file == NULL) {
return false;
}
Sint64 fileSize = SDL_RWsize(file);
if (fileSize <= 4) {
// Initial value of scores for RCT2, just ignore
return false;
}
// Load header
rct_scenario_scores_header header;
if (SDL_RWread(file, &header, 16, 1) != 1) {
SDL_RWclose(file);
log_error("Invalid header in legacy scenario scores file.");
return false;
}
// 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_filename(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->fileName = _strdup(scBasic.path);
highscore->name = win1252_to_utf8_alloc(scBasic.completed_by);
highscore->company_value = (money32)scBasic.company_value;
highscore->timestamp = DATETIME64_MIN;
highscoresDirty = true;
}
}
}
SDL_RWclose(file);
if (highscoresDirty) {
scenario_scores_save();
}
return true;
}
static bool scenario_scores_load()
{
utf8 scoresPath[MAX_PATH];
scenario_scores_get_path(scoresPath, sizeof(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];
highscore->fileName = io_read_string(file);
highscore->name = io_read_string(file);
SDL_RWread(file, &highscore->company_value, sizeof(highscore->company_value), 1);
SDL_RWread(file, &highscore->timestamp, sizeof(highscore->timestamp), 1);
// Attach highscore to correct scenario entry
if (highscore->fileName == NULL) {
continue;
}
scenario_index_entry *scenarioIndexEntry = scenario_list_find_by_filename(highscore->fileName);
if (scenarioIndexEntry != NULL) {
scenarioIndexEntry->highscore = highscore;
}
}
SDL_RWclose(file);
return true;
}
/**
*
* rct2: 0x00677B50
*/
bool scenario_scores_save()
{
utf8 scoresPath[MAX_PATH];
scenario_scores_get_path(scoresPath, sizeof(scoresPath));
SDL_RWops *file = SDL_RWFromFile(scoresPath, "wb");
if (file == NULL) {
log_error("Unable to save scenario scores.");
return false;
}
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];
io_write_string(file, highscore->fileName);
io_write_string(file, highscore->name);
SDL_RWwrite(file, &highscore->company_value, sizeof(highscore->company_value), 1);
SDL_RWwrite(file, &highscore->timestamp, sizeof(highscore->timestamp), 1);
}
SDL_RWclose(file);
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,323 +0,0 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "scenario.h"
#include "util/util.h"
typedef struct scenario_alias {
const utf8 *original;
const utf8 *alternative;
} scenario_alias;
const scenario_alias ScenarioAliases[] = {
// UK - US differences:
{ "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" },
{ "Haunted Harbour", "Haunted Harbor" },
{ "Mythological - Cradle of Civilisation", "Mythological - Cradle of Civilization" },
// RCT1 pack by RCTScenarioLover has a mistake:
{ "Geoffrey Gardens", "Geoffery Gardens" },
};
typedef struct scenario_title_desc {
const uint8 id;
const utf8 *title;
const uint8 category;
} scenario_title_desc;
// RCT
const scenario_title_desc ScenarioTitlesRCT1[] = {
{ SC_FOREST_FRONTIERS, "Forest Frontiers", SCENARIO_CATEGORY_BEGINNER },
{ SC_DYNAMITE_DUNES, "Dynamite Dunes", SCENARIO_CATEGORY_BEGINNER },
{ SC_LEAFY_LAKES, "Leafy Lake", SCENARIO_CATEGORY_BEGINNER },
{ SC_DIAMOND_HEIGHTS, "Diamond Heights", SCENARIO_CATEGORY_BEGINNER },
{ SC_EVERGREEN_GARDENS, "Evergreen Gardens", SCENARIO_CATEGORY_BEGINNER },
{ SC_BUMBLY_BEACH, "Bumbly Beach", SCENARIO_CATEGORY_BEGINNER },
{ SC_TRINITY_ISLANDS, "Trinity Islands", SCENARIO_CATEGORY_CHALLENGING },
{ SC_KATIES_DREAMLAND, "Katie's Dreamland", SCENARIO_CATEGORY_CHALLENGING },
{ SC_POKEY_PARK, "Pokey Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_WHITE_WATER_PARK, "White Water Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_MILLENNIUM_MINES, "Millennium Mines", SCENARIO_CATEGORY_CHALLENGING },
{ SC_KARTS_COASTERS, "Karts & Coasters", SCENARIO_CATEGORY_CHALLENGING },
{ SC_MELS_WORLD, "Mel's World", SCENARIO_CATEGORY_CHALLENGING },
{ SC_MYSTIC_MOUNTAIN, "Mystic Mountain", SCENARIO_CATEGORY_CHALLENGING },
{ SC_PACIFIC_PYRAMIDS, "Pacific Pyramids", SCENARIO_CATEGORY_CHALLENGING },
{ SC_CRUMBLY_WOODS, "Crumbly Woods", SCENARIO_CATEGORY_CHALLENGING },
{ SC_PARADISE_PIER, "Paradise Pier", SCENARIO_CATEGORY_CHALLENGING },
{ SC_LIGHTNING_PEAKS, "Lightning Peaks", SCENARIO_CATEGORY_EXPERT },
{ SC_IVORY_TOWERS, "Ivory Towers", SCENARIO_CATEGORY_EXPERT },
{ SC_RAINBOW_VALLEY, "Rainbow Valley", SCENARIO_CATEGORY_EXPERT },
{ SC_THUNDER_ROCK, "Thunder Rock", SCENARIO_CATEGORY_EXPERT },
{ SC_MEGA_PARK, "Mega Park", SCENARIO_CATEGORY_OTHER },
};
// RCT: Added Attractions
const scenario_title_desc ScenarioTitlesRCT1AA[] = {
{ SC_WHISPERING_CLIFFS, "Whispering Cliffs", SCENARIO_CATEGORY_BEGINNER },
{ SC_THREE_MONKEYS_PARK, "Three Monkeys Park", SCENARIO_CATEGORY_BEGINNER },
{ SC_CANARY_MINES, "Canary Mines", SCENARIO_CATEGORY_BEGINNER },
{ SC_BARONY_BRIDGE, "Barony Bridge", SCENARIO_CATEGORY_BEGINNER },
{ SC_FUNTOPIA, "Funtopia", SCENARIO_CATEGORY_BEGINNER },
{ SC_HAUNTED_HARBOUR, "Haunted Harbour", SCENARIO_CATEGORY_BEGINNER },
{ SC_FUN_FORTRESS, "Fun Fortress", SCENARIO_CATEGORY_BEGINNER },
{ SC_FUTURE_WORLD, "Future World", SCENARIO_CATEGORY_BEGINNER },
{ SC_GENTLE_GLEN, "Gentle Glen", SCENARIO_CATEGORY_BEGINNER },
{ SC_JOLLY_JUNGLE, "Jolly Jungle", SCENARIO_CATEGORY_CHALLENGING },
{ SC_HYDRO_HILLS, "Hydro Hills", SCENARIO_CATEGORY_CHALLENGING },
{ SC_SPRIGHTLY_PARK, "Sprightly Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_MAGIC_QUARTERS, "Magic Quarters", SCENARIO_CATEGORY_CHALLENGING },
{ SC_FRUIT_FARM, "Fruit Farm", SCENARIO_CATEGORY_CHALLENGING },
{ SC_BUTTERFLY_DAM, "Butterfly Dam", SCENARIO_CATEGORY_CHALLENGING },
{ SC_COASTER_CANYON, "Coaster Canyon", SCENARIO_CATEGORY_CHALLENGING },
{ SC_THUNDERSTORM_PARK, "Thunderstorm Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_HARMONIC_HILLS, "Harmonic Hills", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ROMAN_VILLAGE, "Roman Village", SCENARIO_CATEGORY_CHALLENGING },
{ SC_SWAMP_COVE, "Swamp Cove", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ADRENALINE_HEIGHTS, "Adrenaline Heights", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UTOPIA, "Utopia", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ROTTING_HEIGHTS, "Rotting Heights", SCENARIO_CATEGORY_EXPERT },
{ SC_FIASCO_FOREST, "Fiasco Forest", SCENARIO_CATEGORY_EXPERT },
{ SC_PICKLE_PARK, "Pickle Park", SCENARIO_CATEGORY_EXPERT },
{ SC_GIGGLE_DOWNS, "Giggle Downs", SCENARIO_CATEGORY_EXPERT },
{ SC_MINERAL_PARK, "Mineral Park", SCENARIO_CATEGORY_EXPERT },
{ SC_COASTER_CRAZY, "Coaster Crazy", SCENARIO_CATEGORY_EXPERT },
{ SC_URBAN_PARK, "Urban Park", SCENARIO_CATEGORY_EXPERT },
{ SC_GEOFFREY_GARDENS, "Geoffrey Gardens", SCENARIO_CATEGORY_EXPERT },
};
// RCT: Loopy Landscapes
const scenario_title_desc ScenarioTitlesRCT1LL[] = {
{ SC_ICEBERG_ISLANDS, "Iceberg Islands", SCENARIO_CATEGORY_BEGINNER },
{ SC_VOLCANIA, "Volcania", SCENARIO_CATEGORY_BEGINNER },
{ SC_ARID_HEIGHTS, "Arid Heights", SCENARIO_CATEGORY_BEGINNER },
{ SC_RAZOR_ROCKS, "Razor Rocks", SCENARIO_CATEGORY_BEGINNER },
{ SC_CRATER_LAKE, "Crater Lake", SCENARIO_CATEGORY_BEGINNER },
{ SC_VERTIGO_VIEWS, "Vertigo Views", SCENARIO_CATEGORY_BEGINNER },
{ SC_PARADISE_PIER_2, "Paradise Pier 2", SCENARIO_CATEGORY_CHALLENGING },
{ SC_DRAGONS_COVE, "Dragon's Cove", SCENARIO_CATEGORY_CHALLENGING },
{ SC_GOOD_KNIGHT_PARK, "Good Knight Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_WACKY_WARREN, "Wacky Warren", SCENARIO_CATEGORY_CHALLENGING },
{ SC_GRAND_GLACIER, "Grand Glacier", SCENARIO_CATEGORY_CHALLENGING },
{ SC_CRAZY_CRATERS, "Crazy Craters", SCENARIO_CATEGORY_CHALLENGING },
{ SC_DUSTY_DESERT, "Dusty Desert", SCENARIO_CATEGORY_CHALLENGING },
{ SC_WOODWORM_PARK, "Woodworm Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ICARUS_PARK, "Icarus Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_SUNNY_SWAMPS, "Sunny Swamps", SCENARIO_CATEGORY_CHALLENGING },
{ SC_FRIGHTMARE_HILLS, "Frightmare Hills", SCENARIO_CATEGORY_CHALLENGING },
{ SC_THUNDER_ROCKS, "Thunder Rocks", SCENARIO_CATEGORY_CHALLENGING },
{ SC_OCTAGON_PARK, "Octagon Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_PLEASURE_ISLAND, "Pleasure Island", SCENARIO_CATEGORY_CHALLENGING },
{ SC_ICICLE_WORLDS, "Icicle Worlds", SCENARIO_CATEGORY_CHALLENGING },
{ SC_SOUTHERN_SANDS, "Southern Sands", SCENARIO_CATEGORY_CHALLENGING },
{ SC_TINY_TOWERS, "Tiny Towers", SCENARIO_CATEGORY_CHALLENGING },
{ SC_NEVERMORE_PARK, "Nevermore Park", SCENARIO_CATEGORY_CHALLENGING },
{ SC_PACIFICA, "Pacifica", SCENARIO_CATEGORY_CHALLENGING },
{ SC_URBAN_JUNGLE, "Urban Jungle", SCENARIO_CATEGORY_EXPERT },
{ SC_TERROR_TOWN, "Terror Town", SCENARIO_CATEGORY_EXPERT },
{ SC_MEGAWORLD_PARK, "Megaworld Park", SCENARIO_CATEGORY_EXPERT },
{ SC_VENUS_PONDS, "Venus Ponds", SCENARIO_CATEGORY_EXPERT },
{ SC_MICRO_PARK, "Micro Park", SCENARIO_CATEGORY_EXPERT },
};
// RCT2
const scenario_title_desc ScenarioTitlesRCT2[] = {
{ SC_UNIDENTIFIED, "Crazy Castle", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Electric Fields", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Factory Capers", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Amity Airfield", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Botany Breakers", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Bumbly Bazaar", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Dusty Greens", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Fungus Woods", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Gravity Gardens", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Infernal Views", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Alpine Adventures", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Extreme Heights", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Ghost Town", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Lucky Lake", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Rainbow Summit", SCENARIO_CATEGORY_EXPERT },
};
// RCT2: Wacky Worlds
const scenario_title_desc ScenarioTitlesRCT2WW[] = {
{ SC_UNIDENTIFIED, "Africa - Victoria Falls", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Asia - Great Wall of China Tourism Enhancement", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "North America - Grand Canyon", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "South America - Rio Carnival", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Africa - African Diamond Mine", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Asia - Maharaja Palace", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Australasia - Ayers Rock", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Europe - European Cultural Festival", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "North America - Rollercoaster Heaven", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "South America - Inca Lost City", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Africa - Oasis", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Antarctic - Ecological Salvage", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Asia - Japanese Coastal Reclaim", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Australasia - Fun at the Beach", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Europe - Renovation", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "N. America - Extreme Hawaiian Island", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "South America - Rain Forest Plateau", SCENARIO_CATEGORY_EXPERT },
};
// RCT2: Time Twister
const scenario_title_desc ScenarioTitlesRCT2TT[] = {
{ SC_UNIDENTIFIED, "Dark Age - Robin Hood", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Prehistoric - After the Asteroid", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Roaring Twenties - Prison Island", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Rock 'n' Roll - Flower Power", SCENARIO_CATEGORY_BEGINNER },
{ SC_UNIDENTIFIED, "Dark Age - Castle", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Future - First Encounters", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Mythological - Animatronic Film Set", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Prehistoric - Jurassic Safari", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Roaring Twenties - Schneider Cup", SCENARIO_CATEGORY_CHALLENGING },
{ SC_UNIDENTIFIED, "Future - Future World", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Mythological - Cradle of Civilisation", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Prehistoric - Stone Age", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Roaring Twenties - Skyscrapers", SCENARIO_CATEGORY_EXPERT },
{ SC_UNIDENTIFIED, "Rock 'n' Roll - Rock 'n' Roll", SCENARIO_CATEGORY_EXPERT },
};
// Real parks
const scenario_title_desc ScenarioTitlesRealParks[] = {
{ SC_UNIDENTIFIED, "Alton Towers", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Heide-Park", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Blackpool Pleasure Beach", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags Belgium", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags Great Adventure", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags Holland", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags Magic Mountain", SCENARIO_CATEGORY_REAL },
{ SC_UNIDENTIFIED, "Six Flags over Texas", SCENARIO_CATEGORY_REAL },
};
// Other parks
const scenario_title_desc ScenarioTitlesOtherParks[] = {
{ SC_UNIDENTIFIED, "Fort Anachronism", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "PC Player", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "PC Gaming World", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "gameplay", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "Panda World", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "Competition Land 1", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "Competition Land 2", SCENARIO_CATEGORY_DLC },
{ SC_UNIDENTIFIED, "Build your own Six Flags Belgium", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags Great Adventure", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags Holland", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags Magic Mountain", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags Park", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
{ SC_UNIDENTIFIED, "Build your own Six Flags over Texas", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
};
#define DEFINE_SCENARIO_TITLE_DESC_GROUP(x) { countof(x), x }
const struct {
int count;
const scenario_title_desc * const titles;
} ScenarioTitlesBySource[] = {
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT1),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT1AA),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT1LL),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT2),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT2WW),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRCT2TT),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesRealParks),
DEFINE_SCENARIO_TITLE_DESC_GROUP(ScenarioTitlesOtherParks),
};
bool scenario_get_source_desc(const utf8 *name, source_desc *outDesc)
{
assert(outDesc != NULL);
sint32 currentIndex = 0;
for (int i = 0; i < countof(ScenarioTitlesBySource); i++) {
for (int j = 0; j < ScenarioTitlesBySource[i].count; j++) {
const scenario_title_desc *desc = &ScenarioTitlesBySource[i].titles[j];
if (_strcmpi(name, desc->title) == 0) {
outDesc->title = desc->title;
outDesc->id = desc->id;
outDesc->source = i;
outDesc->index = currentIndex;
outDesc->category = desc->category;
return true;
}
currentIndex++;
}
}
outDesc->title = NULL;
outDesc->id = SC_UNIDENTIFIED;
outDesc->source = SCENARIO_SOURCE_OTHER;
outDesc->index = -1;
outDesc->category = SCENARIO_CATEGORY_OTHER;
return false;
}
bool scenario_get_source_desc_by_id(uint8 id, source_desc *outDesc)
{
assert(outDesc != NULL);
sint32 currentIndex = 0;
for (int i = 0; i < countof(ScenarioTitlesBySource); i++) {
for (int j = 0; j < ScenarioTitlesBySource[i].count; j++) {
const scenario_title_desc *desc = &ScenarioTitlesBySource[i].titles[j];
if (id == desc->id) {
outDesc->title = desc->title;
outDesc->id = desc->id;
outDesc->source = i;
outDesc->index = currentIndex;
outDesc->category = desc->category;
return true;
}
currentIndex++;
}
}
outDesc->title = NULL;
outDesc->id = SC_UNIDENTIFIED;
outDesc->source = SCENARIO_SOURCE_OTHER;
outDesc->index = -1;
outDesc->category = SCENARIO_CATEGORY_OTHER;
return false;
}
void scenario_normalise_name(utf8 *name)
{
size_t nameLength = strlen(name);
// 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_strcpy(name, name + 4, 64);
} else {
safe_strcpy(name, name + 3, 64);
}
}
// Trim (for the sake of the above and WW / TT scenarios
safe_strtrimleft(name, name, 64);
// 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 < countof(ScenarioAliases); i++) {
if (strcmp(ScenarioAliases[i].alternative, name) == 0) {
log_verbose("Found alias: %s; will treat as: %s", name, ScenarioAliases[i].original);
safe_strcpy(name, ScenarioAliases[i].original, 64);
}
}
}

View File

@ -33,6 +33,8 @@
#include "peep/staff.h"
#include "ride/ride.h"
#include "scenario.h"
#include "ScenarioRepository.h"
#include "ScenarioSources.h"
#include "util/util.h"
#include "world/climate.h"
#include "world/map.h"
@ -423,9 +425,11 @@ static void title_do_next_script_opcode()
}
const utf8 *path = NULL;
for (int i = 0; i < gScenarioListCount; i++) {
if (gScenarioList[i].source_index == sourceDesc.index) {
path = gScenarioList[i].path;
size_t numScenarios = scenario_repository_get_count();
for (size_t i = 0; i < numScenarios; i++) {
const scenario_index_entry * scenario = scenario_repository_get_by_index(i);
if (scenario->source_index == sourceDesc.index) {
path = scenario->path;
break;
}
}

View File

@ -18,7 +18,8 @@
#include "../audio/audio.h"
#include "../localisation/date.h"
#include "../localisation/localisation.h"
#include "../scenario.h"
#include "../ScenarioRepository.h"
#include "../ScenarioSources.h"
#include "../sprites.h"
#include "../interface/widget.h"
#include "../interface/window.h"
@ -41,7 +42,7 @@ typedef struct sc_list_item {
rct_string_id string_id;
} heading;
struct {
scenario_index_entry *scenario;
const scenario_index_entry *scenario;
bool is_locked;
} scenario;
};
@ -138,7 +139,7 @@ static rct_window_event_list window_scenarioselect_events = {
static void draw_category_heading(rct_window *w, rct_drawpixelinfo *dpi, int left, int right, int y, rct_string_id stringId);
static void initialise_list_items(rct_window *w);
static bool is_scenario_visible(rct_window *w, scenario_index_entry *scenario);
static bool is_scenario_visible(rct_window *w, const scenario_index_entry *scenario);
static bool is_locking_enabled(rct_window *w);
static scenarioselect_callback _callback;
@ -160,7 +161,7 @@ void window_scenarioselect_open(scenarioselect_callback callback)
return;
// Load scenario list
scenario_load_list();
scenario_repository_scan();
// Shrink the window if we're showing scenarios by difficulty level.
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_DIFFICULTY) {
@ -196,8 +197,9 @@ void window_scenarioselect_open(scenarioselect_callback callback)
static void window_scenarioselect_init_tabs(rct_window *w)
{
int showPages = 0;
for (int i = 0; i < gScenarioListCount; i++) {
scenario_index_entry *scenario = &gScenarioList[i];
size_t numScenarios = scenario_repository_get_count();
for (size_t i = 0; i < numScenarios; i++) {
const scenario_index_entry *scenario = scenario_repository_get_by_index(i);
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN) {
showPages |= 1 << scenario->source_game;
} else {
@ -304,7 +306,7 @@ static void window_scenarioselect_scrollmouseover(rct_window *w, int scrollIndex
{
bool originalShowLockedInformation = _showLockedInformation;
_showLockedInformation = false;
scenario_index_entry *selected = NULL;
const scenario_index_entry *selected = NULL;
for (sc_list_item *listItem = _listItems; listItem->type != LIST_ITEM_TYPE_END; listItem++) {
switch (listItem->type) {
case LIST_ITEM_TYPE_HEADING:
@ -364,7 +366,7 @@ static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi)
{
int i, x, y, format;
rct_widget *widget;
scenario_index_entry *scenario;
const scenario_index_entry *scenario;
window_draw_widgets(w, dpi);
@ -474,7 +476,7 @@ static void window_scenarioselect_scrollpaint(rct_window *w, rct_drawpixelinfo *
break;
case LIST_ITEM_TYPE_SCENARIO:;
// Draw hover highlight
scenario_index_entry *scenario = listItem->scenario.scenario;
const scenario_index_entry *scenario = listItem->scenario.scenario;
bool isHighlighted = w->highlighted_scenario == scenario;
if (isHighlighted) {
gfx_fill_rect(dpi, 0, y, w->width, y + 23, 0x02000031);
@ -550,19 +552,20 @@ static void initialise_list_items(rct_window *w)
{
SafeFree(_listItems);
int capacity = gScenarioListCount + 16;
int length = 0;
size_t numScenarios = scenario_repository_get_count();
size_t capacity = numScenarios + 16;
size_t length = 0;
_listItems = malloc(capacity * sizeof(sc_list_item));
// Mega park unlock
const uint32 rct1RequiredCompletedScenarios = (1 << SC_MEGA_PARK) - 1;
uint32 rct1CompletedScenarios = 0;
int megaParkListItemIndex = -1;
size_t megaParkListItemIndex = SIZE_MAX;
int numUnlocks = INITIAL_NUM_UNLOCKED_SCENARIOS;
uint8 currentHeading = UINT8_MAX;
for (int i = 0; i < gScenarioListCount; i++) {
scenario_index_entry *scenario = &gScenarioList[i];
for (size_t i = 0; i < numScenarios; i++) {
const scenario_index_entry *scenario = scenario_repository_get_by_index(i);
if (!is_scenario_visible(w, scenario)) {
continue;
}
@ -640,12 +643,12 @@ static void initialise_list_items(rct_window *w)
_listItems[length - 1].type = LIST_ITEM_TYPE_END;
// Mega park handling
if (megaParkListItemIndex != -1) {
if (megaParkListItemIndex != SIZE_MAX) {
bool megaParkLocked = (rct1CompletedScenarios & rct1RequiredCompletedScenarios) != rct1RequiredCompletedScenarios;
_listItems[megaParkListItemIndex].scenario.is_locked = megaParkLocked;
if (megaParkLocked && gConfigGeneral.scenario_hide_mega_park) {
// Remove mega park
int remainingItems = length - megaParkListItemIndex - 1;
size_t remainingItems = length - megaParkListItemIndex - 1;
memmove(&_listItems[megaParkListItemIndex], &_listItems[megaParkListItemIndex + 1], remainingItems);
// Remove empty headings
@ -663,7 +666,7 @@ static void initialise_list_items(rct_window *w)
}
}
static bool is_scenario_visible(rct_window *w, scenario_index_entry *scenario)
static bool is_scenario_visible(rct_window *w, const scenario_index_entry *scenario)
{
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN) {
if (scenario->source_game != w->selected_tab) {