mirror of https://github.com/OpenRCT2/OpenRCT2.git
Merge pull request #4606 from IntelOrca/refactor/scenario-sources
Refactor scenario sources and scenario list to C++
This commit is contained in:
commit
dd3db116f7
|
@ -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 */,
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
162
src/scenario.h
162
src/scenario.h
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
10
src/title.c
10
src/title.c
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue