From a053a844863fd67ab75bed678c3d3587321774c5 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 17 Aug 2025 11:46:03 +0200 Subject: [PATCH] Move all object units into OpenRCT2 namespace (#24980) * Move all object units into OpenRCT2 namespace * Dealing with fallout, part 1 * Dealing with fallout, part 2 * Dealing with fallout, part 3 * Apply clang-format in a few more places * Remove redundant 'virtual' keyword --- src/openrct2-ui/interface/LandTool.h | 4 +- src/openrct2-ui/windows/Windows.h | 6 +- src/openrct2/AssetPack.cpp | 303 +-- src/openrct2/AssetPackManager.cpp | 332 +-- src/openrct2/AssetPackManager.h | 3 +- src/openrct2/Context.h | 6 +- src/openrct2/EditorObjectSelectionSession.h | 8 +- src/openrct2/GameState.h | 2 +- src/openrct2/ParkImporter.h | 17 +- src/openrct2/actions/BannerPlaceAction.h | 4 +- .../actions/FootpathAdditionPlaceAction.cpp | 2 +- .../actions/FootpathAdditionPlaceAction.h | 4 +- .../actions/FootpathLayoutPlaceAction.h | 8 +- src/openrct2/actions/FootpathPlaceAction.h | 6 +- .../actions/LargeSceneryPlaceAction.h | 14 +- .../actions/ParkEntrancePlaceAction.h | 7 +- src/openrct2/actions/RideCreateAction.h | 10 +- src/openrct2/actions/RideSetVehicleAction.h | 2 +- .../actions/ScenerySetRestrictedAction.h | 6 +- .../actions/SmallSceneryPlaceAction.h | 6 +- .../actions/SmallSceneryRemoveAction.h | 4 +- .../actions/SmallScenerySetColourAction.h | 6 +- src/openrct2/actions/StaffHireNewAction.h | 4 +- src/openrct2/actions/StaffSetCostumeAction.h | 4 +- src/openrct2/actions/SurfaceSetStyleAction.h | 6 +- src/openrct2/actions/WallPlaceAction.h | 9 +- src/openrct2/core/DataSerialiserTraits.h | 42 +- src/openrct2/entity/Peep.h | 2 +- src/openrct2/interface/InteractiveConsole.cpp | 2 +- src/openrct2/management/Research.h | 19 +- src/openrct2/network/NetworkBase.h | 11 +- src/openrct2/network/NetworkConnection.h | 8 +- src/openrct2/object/AudioObject.cpp | 66 +- src/openrct2/object/AudioObject.h | 27 +- src/openrct2/object/AudioSampleTable.cpp | 298 +-- src/openrct2/object/AudioSampleTable.h | 83 +- src/openrct2/object/BannerObject.cpp | 155 +- src/openrct2/object/BannerObject.h | 35 +- src/openrct2/object/BannerSceneryEntry.h | 29 +- src/openrct2/object/ClimateObject.cpp | 351 +-- src/openrct2/object/ClimateObject.h | 41 +- src/openrct2/object/DefaultObjects.cpp | 277 +-- src/openrct2/object/DefaultObjects.h | 27 +- src/openrct2/object/EntranceEntry.h | 15 +- src/openrct2/object/EntranceObject.cpp | 121 +- src/openrct2/object/EntranceObject.h | 41 +- src/openrct2/object/FootpathEntry.h | 69 +- src/openrct2/object/FootpathObject.cpp | 107 +- src/openrct2/object/FootpathObject.h | 73 +- .../object/FootpathRailingsObject.cpp | 163 +- src/openrct2/object/FootpathRailingsObject.h | 57 +- src/openrct2/object/FootpathSurfaceObject.cpp | 107 +- src/openrct2/object/FootpathSurfaceObject.h | 49 +- src/openrct2/object/ImageTable.cpp | 1026 ++++----- src/openrct2/object/ImageTable.h | 96 +- src/openrct2/object/LargeSceneryEntry.h | 169 +- src/openrct2/object/LargeSceneryObject.cpp | 575 ++--- src/openrct2/object/LargeSceneryObject.h | 53 +- src/openrct2/object/MusicObject.cpp | 332 +-- src/openrct2/object/MusicObject.h | 139 +- src/openrct2/object/Object.cpp | 748 ++++--- src/openrct2/object/Object.h | 589 ++--- src/openrct2/object/ObjectAsset.h | 57 +- src/openrct2/object/ObjectFactory.cpp | 282 +-- src/openrct2/object/ObjectFactory.h | 11 +- src/openrct2/object/ObjectLimits.h | 51 +- src/openrct2/object/ObjectList.cpp | 411 ++-- src/openrct2/object/ObjectList.h | 69 +- src/openrct2/object/ObjectManager.cpp | 1412 ++++++------ src/openrct2/object/ObjectManager.h | 83 +- src/openrct2/object/ObjectRepository.cpp | 1261 +++++------ src/openrct2/object/ObjectRepository.h | 150 +- src/openrct2/object/ObjectTypes.cpp | 128 +- src/openrct2/object/ObjectTypes.h | 75 +- src/openrct2/object/PathAdditionEntry.h | 61 +- src/openrct2/object/PathAdditionObject.cpp | 175 +- src/openrct2/object/PathAdditionObject.h | 35 +- src/openrct2/object/PeepAnimationsObject.cpp | 356 +-- src/openrct2/object/PeepAnimationsObject.h | 77 +- src/openrct2/object/PeepNamesObject.cpp | 53 +- src/openrct2/object/PeepNamesObject.h | 27 +- src/openrct2/object/ResourceTable.cpp | 157 +- src/openrct2/object/ResourceTable.h | 43 +- src/openrct2/object/RideObject.cpp | 1993 +++++++++-------- src/openrct2/object/RideObject.h | 89 +- src/openrct2/object/ScenarioMetaObject.cpp | 109 +- src/openrct2/object/ScenarioMetaObject.h | 31 +- src/openrct2/object/SceneryGroupEntry.h | 19 +- src/openrct2/object/SceneryGroupObject.cpp | 317 +-- src/openrct2/object/SceneryGroupObject.h | 53 +- src/openrct2/object/SceneryObject.h | 35 +- src/openrct2/object/SmallSceneryEntry.h | 119 +- src/openrct2/object/SmallSceneryObject.cpp | 377 ++-- src/openrct2/object/SmallSceneryObject.h | 45 +- src/openrct2/object/StationObject.cpp | 141 +- src/openrct2/object/StationObject.h | 47 +- src/openrct2/object/StringTable.cpp | 278 +-- src/openrct2/object/StringTable.h | 76 +- src/openrct2/object/TerrainEdgeObject.cpp | 101 +- src/openrct2/object/TerrainEdgeObject.h | 33 +- src/openrct2/object/TerrainSurfaceObject.cpp | 281 +-- src/openrct2/object/TerrainSurfaceObject.h | 87 +- src/openrct2/object/WallObject.cpp | 207 +- src/openrct2/object/WallObject.h | 35 +- src/openrct2/object/WallSceneryEntry.cpp | 11 +- src/openrct2/object/WallSceneryEntry.h | 67 +- src/openrct2/object/WaterEntry.h | 27 +- src/openrct2/object/WaterObject.cpp | 211 +- src/openrct2/object/WaterObject.h | 43 +- src/openrct2/park/Legacy.h | 12 +- src/openrct2/park/ParkFile.h | 3 +- src/openrct2/peep/PeepAnimations.h | 3 +- src/openrct2/rct12/EntryList.h | 5 +- src/openrct2/rct12/RCT12.h | 14 +- src/openrct2/rct12/ScenarioPatcher.cpp | 6 +- src/openrct2/rct2/RCT2.h | 6 +- src/openrct2/ride/Ride.h | 26 +- src/openrct2/ride/RideTypes.h | 2 +- src/openrct2/ride/TrackDesign.h | 4 +- src/openrct2/ride/TrackPaint.h | 16 +- src/openrct2/ride/Vehicle.h | 2 +- src/openrct2/world/Banner.h | 4 +- src/openrct2/world/Footpath.h | 26 +- src/openrct2/world/Scenery.h | 6 +- src/openrct2/world/ScenerySelection.h | 4 +- .../world/tile_element/BannerElement.cpp | 2 + .../world/tile_element/BannerElement.h | 7 +- .../world/tile_element/EntranceElement.cpp | 2 + .../world/tile_element/EntranceElement.h | 30 +- .../tile_element/LargeSceneryElement.cpp | 2 + .../world/tile_element/LargeSceneryElement.h | 10 +- .../world/tile_element/PathElement.cpp | 2 + src/openrct2/world/tile_element/PathElement.h | 52 +- .../tile_element/SmallSceneryElement.cpp | 2 + .../world/tile_element/SmallSceneryElement.h | 14 +- .../world/tile_element/SurfaceElement.cpp | 2 + .../world/tile_element/SurfaceElement.h | 12 +- .../world/tile_element/WallElement.cpp | 3 + src/openrct2/world/tile_element/WallElement.h | 12 +- 139 files changed, 8560 insertions(+), 8322 deletions(-) diff --git a/src/openrct2-ui/interface/LandTool.h b/src/openrct2-ui/interface/LandTool.h index e7cac0c701..ba700f0161 100644 --- a/src/openrct2-ui/interface/LandTool.h +++ b/src/openrct2-ui/interface/LandTool.h @@ -18,8 +18,8 @@ constexpr uint16_t kLandToolMaximumSize = 64; constexpr uint16_t kLandToolMaximumSizeWithSprite = 7; extern uint16_t gLandToolSize; -extern ObjectEntryIndex gLandToolTerrainSurface; -extern ObjectEntryIndex gLandToolTerrainEdge; +extern OpenRCT2::ObjectEntryIndex gLandToolTerrainSurface; +extern OpenRCT2::ObjectEntryIndex gLandToolTerrainEdge; namespace OpenRCT2::LandTool { diff --git a/src/openrct2-ui/windows/Windows.h b/src/openrct2-ui/windows/Windows.h index 9f5e0b7a26..2bc3cad8ba 100644 --- a/src/openrct2-ui/windows/Windows.h +++ b/src/openrct2-ui/windows/Windows.h @@ -17,7 +17,6 @@ class Formatter; -struct ObjectEntryDescriptor; struct OpenRCT2String; struct Peep; struct Ride; @@ -32,6 +31,11 @@ enum class ScatterToolDensity : uint8_t; using LoadSaveCallback = void (*)(ModalResult result, const utf8* path); using ScenarioSelectCallback = void (*)(const utf8* path); +namespace OpenRCT2 +{ + struct ObjectEntryDescriptor; +} + namespace OpenRCT2::Ui::Windows { constexpr int32_t kTabBarHeight = 43; diff --git a/src/openrct2/AssetPack.cpp b/src/openrct2/AssetPack.cpp index fc222f5bb3..e7c2ec07ca 100644 --- a/src/openrct2/AssetPack.cpp +++ b/src/openrct2/AssetPack.cpp @@ -17,173 +17,174 @@ #include "localisation/LocalisationService.h" #include "object/Object.h" -using namespace OpenRCT2; - -constexpr std::string_view ManifestFileName = "manifest.json"; - -AssetPack::AssetPack(const fs::path& path) - : Path(path) +namespace OpenRCT2 { -} + constexpr std::string_view ManifestFileName = "manifest.json"; -AssetPack::~AssetPack() -{ -} - -bool AssetPack::IsEnabled() const -{ - return _enabled; -} - -void AssetPack::SetEnabled(bool value) -{ - _enabled = value; -} - -void AssetPack::Fetch() -{ - auto archive = Zip::Open(Path.u8string(), ZipAccess::read); - if (!archive->Exists(ManifestFileName)) + AssetPack::AssetPack(const fs::path& path) + : Path(path) { - throw std::runtime_error("Manifest does not exist."); } - auto manifestJson = archive->GetFileData(ManifestFileName); - auto jManifest = Json::FromVector(manifestJson); - Id = jManifest["id"].get(); - Version = jManifest["version"].get(); - - // TODO use a better string table class that can be used for objects, park files and asset packs - auto& localisationService = GetContext()->GetLocalisationService(); - auto locale = std::string(localisationService.GetCurrentLanguageLocale()); - Name = GetString(jManifest, "name", locale); - Description = GetString(jManifest, "description", locale); -} - -std::string AssetPack::GetString(json_t& jManifest, const std::string& key, const std::string& locale) -{ - if (jManifest.contains("strings")) + AssetPack::~AssetPack() { - auto& jStrings = jManifest["strings"]; - if (jStrings.contains(key)) + } + + bool AssetPack::IsEnabled() const + { + return _enabled; + } + + void AssetPack::SetEnabled(bool value) + { + _enabled = value; + } + + void AssetPack::Fetch() + { + auto archive = Zip::Open(Path.u8string(), ZipAccess::read); + if (!archive->Exists(ManifestFileName)) { - auto& jKey = jStrings[key]; - if (jKey.contains(locale)) + throw std::runtime_error("Manifest does not exist."); + } + + auto manifestJson = archive->GetFileData(ManifestFileName); + auto jManifest = Json::FromVector(manifestJson); + Id = jManifest["id"].get(); + Version = jManifest["version"].get(); + + // TODO use a better string table class that can be used for objects, park files and asset packs + auto& localisationService = GetContext()->GetLocalisationService(); + auto locale = std::string(localisationService.GetCurrentLanguageLocale()); + Name = GetString(jManifest, "name", locale); + Description = GetString(jManifest, "description", locale); + } + + std::string AssetPack::GetString(json_t& jManifest, const std::string& key, const std::string& locale) + { + if (jManifest.contains("strings")) + { + auto& jStrings = jManifest["strings"]; + if (jStrings.contains(key)) { - return jKey[locale].get(); - } - if (jKey.contains("en-GB")) - { - return jKey["en-GB"].get(); - } - if (jKey.contains("en-US")) - { - return jKey["en-US"].get(); + auto& jKey = jStrings[key]; + if (jKey.contains(locale)) + { + return jKey[locale].get(); + } + if (jKey.contains("en-GB")) + { + return jKey["en-GB"].get(); + } + if (jKey.contains("en-US")) + { + return jKey["en-US"].get(); + } } } - } - return {}; -} - -bool AssetPack::ContainsObject(std::string_view id) const -{ - auto it = std::find_if(_entries.begin(), _entries.end(), [id](const Entry& entry) { return entry.ObjectId == id; }); - return it != _entries.end(); -} - -void AssetPack::LoadSamplesForObject(std::string_view id, AudioSampleTable& objectTable) -{ - auto it = std::find_if(_entries.begin(), _entries.end(), [id](const Entry& entry) { return entry.ObjectId == id; }); - if (it != _entries.end()) - { - objectTable.LoadFrom(_sampleTable, it->TableIndex, it->TableLength); - } -} - -class AssetPackLoadContext : public IReadObjectContext -{ -private: - std::string _zipPath; - IZipArchive* _zipArchive; - -public: - AssetPackLoadContext(std::string_view zipPath, IZipArchive* zipArchive) - : _zipPath(zipPath) - , _zipArchive(zipArchive) - { + return {}; } - virtual ~AssetPackLoadContext() override + bool AssetPack::ContainsObject(std::string_view id) const { + auto it = std::find_if(_entries.begin(), _entries.end(), [id](const Entry& entry) { return entry.ObjectId == id; }); + return it != _entries.end(); } - std::string_view GetObjectIdentifier() override + void AssetPack::LoadSamplesForObject(std::string_view id, AudioSampleTable& objectTable) { - throw std::runtime_error("Not implemented"); - } - - IObjectRepository& GetObjectRepository() override - { - throw std::runtime_error("Not implemented"); - } - - bool ShouldLoadImages() override - { - return true; - } - - std::vector GetData(std::string_view path) override - { - return _zipArchive->GetFileData(path); - } - - ObjectAsset GetAsset(std::string_view path) override - { - if (Path::IsAbsolute(path)) - return ObjectAsset(path); - return ObjectAsset(_zipPath, path); - } - - void LogVerbose(ObjectError code, const utf8* text) override - { - } - - void LogWarning(ObjectError code, const utf8* text) override - { - } - - void LogError(ObjectError code, const utf8* text) override - { - } -}; - -void AssetPack::Load() -{ - auto path = Path.u8string(); - auto archive = Zip::Open(path, ZipAccess::read); - if (!archive->Exists(ManifestFileName)) - { - throw std::runtime_error("Manifest does not exist."); - } - - AssetPackLoadContext loadContext(path, archive.get()); - - auto manifestJson = archive->GetFileData(ManifestFileName); - auto jManifest = Json::FromVector(manifestJson); - auto& jObjects = jManifest["objects"]; - - _entries.clear(); - for (auto& jObject : jObjects) - { - Entry entry; - entry.ObjectId = jObject["id"].get(); - - if (jObject.contains("samples")) + auto it = std::find_if(_entries.begin(), _entries.end(), [id](const Entry& entry) { return entry.ObjectId == id; }); + if (it != _entries.end()) { - entry.TableIndex = _sampleTable.GetCount(); - _sampleTable.ReadFromJson(&loadContext, jObject); - entry.TableLength = _sampleTable.GetCount() - entry.TableIndex; + objectTable.LoadFrom(_sampleTable, it->TableIndex, it->TableLength); } - _entries.push_back(entry); } -} + + class AssetPackLoadContext : public IReadObjectContext + { + private: + std::string _zipPath; + IZipArchive* _zipArchive; + + public: + AssetPackLoadContext(std::string_view zipPath, IZipArchive* zipArchive) + : _zipPath(zipPath) + , _zipArchive(zipArchive) + { + } + + ~AssetPackLoadContext() override + { + } + + std::string_view GetObjectIdentifier() override + { + throw std::runtime_error("Not implemented"); + } + + IObjectRepository& GetObjectRepository() override + { + throw std::runtime_error("Not implemented"); + } + + bool ShouldLoadImages() override + { + return true; + } + + std::vector GetData(std::string_view path) override + { + return _zipArchive->GetFileData(path); + } + + ObjectAsset GetAsset(std::string_view path) override + { + if (Path::IsAbsolute(path)) + return ObjectAsset(path); + return ObjectAsset(_zipPath, path); + } + + void LogVerbose(ObjectError code, const utf8* text) override + { + } + + void LogWarning(ObjectError code, const utf8* text) override + { + } + + void LogError(ObjectError code, const utf8* text) override + { + } + }; + + void AssetPack::Load() + { + auto path = Path.u8string(); + auto archive = Zip::Open(path, ZipAccess::read); + if (!archive->Exists(ManifestFileName)) + { + throw std::runtime_error("Manifest does not exist."); + } + + AssetPackLoadContext loadContext(path, archive.get()); + + auto manifestJson = archive->GetFileData(ManifestFileName); + auto jManifest = Json::FromVector(manifestJson); + auto& jObjects = jManifest["objects"]; + + _entries.clear(); + for (auto& jObject : jObjects) + { + Entry entry; + entry.ObjectId = jObject["id"].get(); + + if (jObject.contains("samples")) + { + entry.TableIndex = _sampleTable.GetCount(); + _sampleTable.ReadFromJson(&loadContext, jObject); + entry.TableLength = _sampleTable.GetCount() - entry.TableIndex; + } + _entries.push_back(entry); + } + } +} // namespace OpenRCT2 diff --git a/src/openrct2/AssetPackManager.cpp b/src/openrct2/AssetPackManager.cpp index 9f63a02529..8a8b1a9e95 100644 --- a/src/openrct2/AssetPackManager.cpp +++ b/src/openrct2/AssetPackManager.cpp @@ -23,191 +23,193 @@ #include #include -using namespace OpenRCT2; - -AssetPackManager::AssetPackManager() +namespace OpenRCT2 { -} -AssetPackManager::~AssetPackManager() -{ -} - -size_t AssetPackManager::GetCount() const -{ - return _assetPacks.size(); -} - -AssetPack* AssetPackManager::GetAssetPack(size_t index) -{ - if (index >= _assetPacks.size()) - return nullptr; - return _assetPacks[index].get(); -} - -AssetPack* AssetPackManager::GetAssetPack(std::string_view id) -{ - auto index = GetAssetPackIndex(id); - if (index != std::numeric_limits::max()) - return _assetPacks[index].get(); - return nullptr; -} - -size_t AssetPackManager::GetAssetPackIndex(std::string_view id) -{ - auto it = std::find_if(_assetPacks.begin(), _assetPacks.end(), [&](const std::unique_ptr& assetPack) { - return assetPack != nullptr ? assetPack->Id == id : false; - }); - if (it == _assetPacks.end()) - return std::numeric_limits::max(); - return std::distance(_assetPacks.begin(), it); -} - -void AssetPackManager::Scan() -{ - ClearAssetPacks(); - - auto context = GetContext(); - auto& env = context->GetPlatformEnvironment(); - - auto openrct2Dir = fs::u8path(env.GetDirectoryPath(DirBase::openrct2, DirId::assetPacks)); - Scan(openrct2Dir); - - auto userDirectory = fs::u8path(env.GetDirectoryPath(DirBase::user, DirId::assetPacks)); - Path::CreateDirectory(userDirectory.u8string()); - Scan(userDirectory); -} - -void AssetPackManager::Scan(const fs::path& directory) -{ - // Recursively scan for .parkap files - std::error_code ec; - for (const fs::directory_entry& entry : fs::recursive_directory_iterator(directory, ec)) + AssetPackManager::AssetPackManager() { - if (!entry.is_directory()) + } + + AssetPackManager::~AssetPackManager() + { + } + + size_t AssetPackManager::GetCount() const + { + return _assetPacks.size(); + } + + AssetPack* AssetPackManager::GetAssetPack(size_t index) + { + if (index >= _assetPacks.size()) + return nullptr; + return _assetPacks[index].get(); + } + + AssetPack* AssetPackManager::GetAssetPack(std::string_view id) + { + auto index = GetAssetPackIndex(id); + if (index != std::numeric_limits::max()) + return _assetPacks[index].get(); + return nullptr; + } + + size_t AssetPackManager::GetAssetPackIndex(std::string_view id) + { + auto it = std::find_if(_assetPacks.begin(), _assetPacks.end(), [&](const std::unique_ptr& assetPack) { + return assetPack != nullptr ? assetPack->Id == id : false; + }); + if (it == _assetPacks.end()) + return std::numeric_limits::max(); + return std::distance(_assetPacks.begin(), it); + } + + void AssetPackManager::Scan() + { + ClearAssetPacks(); + + auto context = GetContext(); + auto& env = context->GetPlatformEnvironment(); + + auto openrct2Dir = fs::u8path(env.GetDirectoryPath(DirBase::openrct2, DirId::assetPacks)); + Scan(openrct2Dir); + + auto userDirectory = fs::u8path(env.GetDirectoryPath(DirBase::user, DirId::assetPacks)); + Path::CreateDirectory(userDirectory.u8string()); + Scan(userDirectory); + } + + void AssetPackManager::Scan(const fs::path& directory) + { + // Recursively scan for .parkap files + std::error_code ec; + for (const fs::directory_entry& entry : fs::recursive_directory_iterator(directory, ec)) { - auto path = entry.path().u8string(); - if (String::endsWith(path, ".parkap", true)) + if (!entry.is_directory()) { - AddAssetPack(path); + auto path = entry.path().u8string(); + if (String::endsWith(path, ".parkap", true)) + { + AddAssetPack(path); + } } } } -} -void AssetPackManager::Reload() -{ - for (auto& assetPack : _assetPacks) + void AssetPackManager::Reload() { - assetPack->Load(); - } -} - -void AssetPackManager::Swap(size_t index, size_t otherIndex) -{ - if (index < _assetPacks.size() && otherIndex < _assetPacks.size() && index != otherIndex) - { - std::swap(_assetPacks[index], _assetPacks[otherIndex]); - } -} - -void AssetPackManager::LoadSamplesForObject(std::string_view id, AudioSampleTable& objectTable) -{ - std::for_each(_assetPacks.rbegin(), _assetPacks.rend(), [&](auto& assetPack) { - if (assetPack->IsEnabled() && assetPack->ContainsObject(id)) + for (auto& assetPack : _assetPacks) { - assetPack->LoadSamplesForObject(id, objectTable); - } - }); -} - -void AssetPackManager::ClearAssetPacks() -{ - _assetPacks.clear(); -} - -void AssetPackManager::AddAssetPack(const fs::path& path) -{ - auto szPath = path.u8string(); - LOG_VERBOSE("Scanning asset pack: %s", szPath.c_str()); - try - { - auto ap = std::make_unique(path); - ap->Fetch(); - _assetPacks.push_back(std::move(ap)); - } - catch (const std::exception& e) - { - auto fileName = path.filename().u8string(); - Console::Error::WriteFormat("Unable to load asset pack: %s (%s)", fileName.c_str(), e.what()); - } -} - -template -static void EnumerateCommaSeparatedList(std::string_view csl, TFunc func) -{ - size_t elStart = 0; - for (size_t i = 0; i <= csl.size(); i++) - { - if (i == csl.size() || csl[i] == ',') - { - auto el = csl.substr(elStart, i - elStart); - if (el.size() != 0) - func(el); - elStart = i + 1; + assetPack->Load(); } } -} -void AssetPackManager::LoadEnabledAssetPacks() -{ - // Re-order asset packs - std::vector> newAssetPacks; - EnumerateCommaSeparatedList(Config::Get().general.AssetPackOrder, [&](std::string_view id) { - auto index = GetAssetPackIndex(id); - if (index != std::numeric_limits::max()) - { - newAssetPacks.push_back(std::move(_assetPacks[index])); - } - }); - for (auto& assetPack : _assetPacks) + void AssetPackManager::Swap(size_t index, size_t otherIndex) { - if (assetPack != nullptr) + if (index < _assetPacks.size() && otherIndex < _assetPacks.size() && index != otherIndex) { - newAssetPacks.push_back(std::move(assetPack)); + std::swap(_assetPacks[index], _assetPacks[otherIndex]); } } - _assetPacks = std::move(newAssetPacks); - // Set which asset packs are enabled - EnumerateCommaSeparatedList(Config::Get().general.EnabledAssetPacks, [&](std::string_view id) { - auto assetPack = GetAssetPack(id); - if (assetPack != nullptr) - { - assetPack->SetEnabled(true); - } - }); -} - -void AssetPackManager::SaveEnabledAssetPacks() -{ - u8string orderList; - u8string enabledList; - for (const auto& assetPack : _assetPacks) + void AssetPackManager::LoadSamplesForObject(std::string_view id, AudioSampleTable& objectTable) { - orderList.append(assetPack->Id); - orderList.push_back(','); - if (assetPack->IsEnabled()) + std::for_each(_assetPacks.rbegin(), _assetPacks.rend(), [&](auto& assetPack) { + if (assetPack->IsEnabled() && assetPack->ContainsObject(id)) + { + assetPack->LoadSamplesForObject(id, objectTable); + } + }); + } + + void AssetPackManager::ClearAssetPacks() + { + _assetPacks.clear(); + } + + void AssetPackManager::AddAssetPack(const fs::path& path) + { + auto szPath = path.u8string(); + LOG_VERBOSE("Scanning asset pack: %s", szPath.c_str()); + try { - enabledList.append(assetPack->Id); - enabledList.push_back(','); + auto ap = std::make_unique(path); + ap->Fetch(); + _assetPacks.push_back(std::move(ap)); + } + catch (const std::exception& e) + { + auto fileName = path.filename().u8string(); + Console::Error::WriteFormat("Unable to load asset pack: %s (%s)", fileName.c_str(), e.what()); } } - if (orderList.size() > 0) - orderList.pop_back(); - if (enabledList.size() > 0) - enabledList.pop_back(); - Config::Get().general.AssetPackOrder = orderList; - Config::Get().general.EnabledAssetPacks = enabledList; - Config::Save(); -} + + template + static void EnumerateCommaSeparatedList(std::string_view csl, TFunc func) + { + size_t elStart = 0; + for (size_t i = 0; i <= csl.size(); i++) + { + if (i == csl.size() || csl[i] == ',') + { + auto el = csl.substr(elStart, i - elStart); + if (el.size() != 0) + func(el); + elStart = i + 1; + } + } + } + + void AssetPackManager::LoadEnabledAssetPacks() + { + // Re-order asset packs + std::vector> newAssetPacks; + EnumerateCommaSeparatedList(Config::Get().general.AssetPackOrder, [&](std::string_view id) { + auto index = GetAssetPackIndex(id); + if (index != std::numeric_limits::max()) + { + newAssetPacks.push_back(std::move(_assetPacks[index])); + } + }); + for (auto& assetPack : _assetPacks) + { + if (assetPack != nullptr) + { + newAssetPacks.push_back(std::move(assetPack)); + } + } + _assetPacks = std::move(newAssetPacks); + + // Set which asset packs are enabled + EnumerateCommaSeparatedList(Config::Get().general.EnabledAssetPacks, [&](std::string_view id) { + auto assetPack = GetAssetPack(id); + if (assetPack != nullptr) + { + assetPack->SetEnabled(true); + } + }); + } + + void AssetPackManager::SaveEnabledAssetPacks() + { + u8string orderList; + u8string enabledList; + for (const auto& assetPack : _assetPacks) + { + orderList.append(assetPack->Id); + orderList.push_back(','); + if (assetPack->IsEnabled()) + { + enabledList.append(assetPack->Id); + enabledList.push_back(','); + } + } + if (orderList.size() > 0) + orderList.pop_back(); + if (enabledList.size() > 0) + enabledList.pop_back(); + Config::Get().general.AssetPackOrder = orderList; + Config::Get().general.EnabledAssetPacks = enabledList; + Config::Save(); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/AssetPackManager.h b/src/openrct2/AssetPackManager.h index 1470f609b0..2fcf5dba11 100644 --- a/src/openrct2/AssetPackManager.h +++ b/src/openrct2/AssetPackManager.h @@ -15,11 +15,10 @@ #include #include -class AudioSampleTable; - namespace OpenRCT2 { class AssetPack; + class AudioSampleTable; class AssetPackManager { diff --git a/src/openrct2/Context.h b/src/openrct2/Context.h index 52fc372578..e007be6f13 100644 --- a/src/openrct2/Context.h +++ b/src/openrct2/Context.h @@ -17,16 +17,16 @@ #include -struct IObjectManager; -struct IObjectRepository; struct IScenarioRepository; enum class DrawingEngine : int32_t; enum class CursorID : uint8_t; namespace OpenRCT2 { - struct IStream; class Intent; + struct IObjectManager; + struct IObjectRepository; + struct IStream; struct WindowBase; } // namespace OpenRCT2 diff --git a/src/openrct2/EditorObjectSelectionSession.h b/src/openrct2/EditorObjectSelectionSession.h index 331fb100be..527704cc2b 100644 --- a/src/openrct2/EditorObjectSelectionSession.h +++ b/src/openrct2/EditorObjectSelectionSession.h @@ -28,9 +28,9 @@ using EditorInputFlags = FlagHolder; extern std::optional _gSceneryGroupPartialSelectError; extern std::vector _objectSelectionFlags; -extern uint32_t _numSelectedObjectsForType[EnumValue(ObjectType::count)]; +extern uint32_t _numSelectedObjectsForType[EnumValue(OpenRCT2::ObjectType::count)]; -bool EditorCheckObjectGroupAtLeastOneSelected(ObjectType checkObjectType); +bool EditorCheckObjectGroupAtLeastOneSelected(OpenRCT2::ObjectType checkObjectType); bool EditorCheckObjectGroupAtLeastOneOfPeepTypeSelected(uint8_t peepType); bool EditorCheckObjectGroupAtLeastOneSurfaceSelected(bool queue); void EditorObjectFlagsClear(); @@ -39,9 +39,9 @@ void Sub6AB211(); void ResetSelectedObjectCountAndSize(); void FinishObjectSelection(); ResultWithMessage WindowEditorObjectSelectionSelectObject( - uint8_t isMasterObject, EditorInputFlags flags, const ObjectRepositoryItem* item); + uint8_t isMasterObject, EditorInputFlags flags, const OpenRCT2::ObjectRepositoryItem* item); ResultWithMessage WindowEditorObjectSelectionSelectObject( - uint8_t isMasterObject, EditorInputFlags flags, const ObjectEntryDescriptor& entry); + uint8_t isMasterObject, EditorInputFlags flags, const OpenRCT2::ObjectEntryDescriptor& entry); /** * Removes all unused objects from the object selection. diff --git a/src/openrct2/GameState.h b/src/openrct2/GameState.h index c4a3b6d075..300ec9b20d 100644 --- a/src/openrct2/GameState.h +++ b/src/openrct2/GameState.h @@ -71,7 +71,7 @@ namespace OpenRCT2 std::vector banners; Entity_t entities[kMaxEntities]{}; // Ride storage for all the rides in the park, rides with RideId::Null are considered free. - std::array rides{}; + std::array rides{}; size_t ridesEndOfUsedRange{}; RideRatingUpdateStates rideRatingUpdateStates; std::vector tileElements; diff --git a/src/openrct2/ParkImporter.h b/src/openrct2/ParkImporter.h index 6f9163774c..15ccc0b3c0 100644 --- a/src/openrct2/ParkImporter.h +++ b/src/openrct2/ParkImporter.h @@ -16,11 +16,10 @@ #include #include -struct IObjectManager; -struct IObjectRepository; - namespace OpenRCT2 { + struct IObjectManager; + struct IObjectRepository; struct IStream; struct GameState_t; struct ParkPreview; @@ -31,12 +30,12 @@ struct ScenarioIndexEntry; struct ParkLoadResult final { public: - ObjectList RequiredObjects; + OpenRCT2::ObjectList RequiredObjects; bool SemiCompatibleVersion{}; uint32_t MinVersion{}; uint32_t TargetVersion{}; - explicit ParkLoadResult(ObjectList&& requiredObjects) + explicit ParkLoadResult(OpenRCT2::ObjectList&& requiredObjects) : RequiredObjects(std::move(requiredObjects)) { } @@ -77,9 +76,9 @@ namespace OpenRCT2::ParkImporter class ObjectLoadException : public std::exception { public: - std::vector const MissingObjects; + std::vector const MissingObjects; - explicit ObjectLoadException(std::vector&& missingObjects) + explicit ObjectLoadException(std::vector&& missingObjects) : MissingObjects(std::move(missingObjects)) { } @@ -93,9 +92,9 @@ public: class UnsupportedRideTypeException : public std::exception { public: - ObjectEntryIndex const Type; + OpenRCT2::ObjectEntryIndex const Type; - explicit UnsupportedRideTypeException(ObjectEntryIndex type) + explicit UnsupportedRideTypeException(OpenRCT2::ObjectEntryIndex type) : Type(type) { } diff --git a/src/openrct2/actions/BannerPlaceAction.h b/src/openrct2/actions/BannerPlaceAction.h index 719d61969a..40cacd9c51 100644 --- a/src/openrct2/actions/BannerPlaceAction.h +++ b/src/openrct2/actions/BannerPlaceAction.h @@ -20,12 +20,12 @@ class BannerPlaceAction final : public GameActionBase { private: CoordsXYZD _loc; - ObjectEntryIndex _bannerType{ kBannerNull }; + OpenRCT2::ObjectEntryIndex _bannerType{ kBannerNull }; uint8_t _primaryColour{}; public: BannerPlaceAction() = default; - BannerPlaceAction(const CoordsXYZD& loc, ObjectEntryIndex bannerType, colour_t primaryColour); + BannerPlaceAction(const CoordsXYZD& loc, OpenRCT2::ObjectEntryIndex bannerType, colour_t primaryColour); void AcceptParameters(GameActionParameterVisitor& visitor) override; diff --git a/src/openrct2/actions/FootpathAdditionPlaceAction.cpp b/src/openrct2/actions/FootpathAdditionPlaceAction.cpp index b7268903d3..801e30dc35 100644 --- a/src/openrct2/actions/FootpathAdditionPlaceAction.cpp +++ b/src/openrct2/actions/FootpathAdditionPlaceAction.cpp @@ -98,7 +98,7 @@ GameActions::Result FootpathAdditionPlaceAction::Query() const return res; } - auto* pathAdditionEntry = OpenRCT2::ObjectManager::GetObjectEntry(_entryIndex); + auto* pathAdditionEntry = ObjectManager::GetObjectEntry(_entryIndex); if (pathAdditionEntry == nullptr) { LOG_ERROR("Unknown footpath addition entry for entryIndex %d", _entryIndex); diff --git a/src/openrct2/actions/FootpathAdditionPlaceAction.h b/src/openrct2/actions/FootpathAdditionPlaceAction.h index 2c53154c70..14fe4fa4ed 100644 --- a/src/openrct2/actions/FootpathAdditionPlaceAction.h +++ b/src/openrct2/actions/FootpathAdditionPlaceAction.h @@ -15,11 +15,11 @@ class FootpathAdditionPlaceAction final : public GameActionBase private: CoordsXYZ _loc; uint8_t _slope{}; - ObjectEntryIndex _type{}; - ObjectEntryIndex _railingsType{}; + OpenRCT2::ObjectEntryIndex _type{}; + OpenRCT2::ObjectEntryIndex _railingsType{}; Direction _direction{ kInvalidDirection }; PathConstructFlags _constructFlags{}; public: FootpathPlaceAction() = default; FootpathPlaceAction( - const CoordsXYZ& loc, uint8_t slope, ObjectEntryIndex type, ObjectEntryIndex railingsType, + const CoordsXYZ& loc, uint8_t slope, OpenRCT2::ObjectEntryIndex type, OpenRCT2::ObjectEntryIndex railingsType, Direction direction = kInvalidDirection, PathConstructFlags constructFlags = 0); void AcceptParameters(GameActionParameterVisitor& visitor) override; diff --git a/src/openrct2/actions/LargeSceneryPlaceAction.h b/src/openrct2/actions/LargeSceneryPlaceAction.h index 4deb2af316..16097cd10f 100644 --- a/src/openrct2/actions/LargeSceneryPlaceAction.h +++ b/src/openrct2/actions/LargeSceneryPlaceAction.h @@ -18,14 +18,18 @@ struct LargeSceneryPlaceActionResult BannerIndex bannerId = BannerIndex::GetNull(); }; -struct LargeSceneryTile; +namespace OpenRCT2 +{ + struct LargeSceneryTile; +} + struct LargeSceneryElement; class LargeSceneryPlaceAction final : public GameActionBase { private: CoordsXYZD _loc; - ObjectEntryIndex _sceneryType{ kObjectEntryIndexNull }; + OpenRCT2::ObjectEntryIndex _sceneryType{ OpenRCT2::kObjectEntryIndexNull }; uint8_t _primaryColour{}; uint8_t _secondaryColour{}; uint8_t _tertiaryColour{}; @@ -34,7 +38,7 @@ public: LargeSceneryPlaceAction() = default; LargeSceneryPlaceAction( - const CoordsXYZD& loc, ObjectEntryIndex sceneryType, uint8_t primaryColour, uint8_t secondaryColour, + const CoordsXYZD& loc, OpenRCT2::ObjectEntryIndex sceneryType, uint8_t primaryColour, uint8_t secondaryColour, uint8_t tertiaryColour); void AcceptParameters(GameActionParameterVisitor& visitor) override; @@ -46,7 +50,7 @@ public: OpenRCT2::GameActions::Result Execute() const override; private: - bool CheckMapCapacity(std::span tiles, size_t numTiles) const; - int16_t GetMaxSurfaceHeight(std::span tiles) const; + bool CheckMapCapacity(std::span tiles, size_t numTiles) const; + int16_t GetMaxSurfaceHeight(std::span tiles) const; void SetNewLargeSceneryElement(LargeSceneryElement& sceneryElement, uint8_t tileNum) const; }; diff --git a/src/openrct2/actions/ParkEntrancePlaceAction.h b/src/openrct2/actions/ParkEntrancePlaceAction.h index 3ecd562592..f7ef28a523 100644 --- a/src/openrct2/actions/ParkEntrancePlaceAction.h +++ b/src/openrct2/actions/ParkEntrancePlaceAction.h @@ -15,12 +15,13 @@ class ParkEntrancePlaceAction final : public GameActionBase { private: ride_type_t _rideType{ kRideTypeNull }; - ObjectEntryIndex _subType{ kObjectEntryIndexNull }; - ObjectEntryIndex _entranceObjectIndex{ kObjectEntryIndexNull }; + OpenRCT2::ObjectEntryIndex _subType{ OpenRCT2::kObjectEntryIndexNull }; + OpenRCT2::ObjectEntryIndex _entranceObjectIndex{ OpenRCT2::kObjectEntryIndexNull }; colour_t _colour1{ COLOUR_NULL }; colour_t _colour2{ COLOUR_NULL }; public: RideCreateAction() = default; RideCreateAction( - ride_type_t rideType, ObjectEntryIndex subType, colour_t colour1, colour_t colour2, - ObjectEntryIndex entranceStyleIndex); + ride_type_t rideType, OpenRCT2::ObjectEntryIndex subType, colour_t colour1, colour_t colour2, + OpenRCT2::ObjectEntryIndex entranceStyleIndex); void AcceptParameters(GameActionParameterVisitor& visitor) override; ride_type_t GetRideType() const; - ObjectEntryIndex GetRideObject() const; + OpenRCT2::ObjectEntryIndex GetRideObject() const; uint16_t GetActionFlags() const override; void Serialise(DataSerialiser& stream) override; diff --git a/src/openrct2/actions/RideSetVehicleAction.h b/src/openrct2/actions/RideSetVehicleAction.h index 3c69452988..cc7dfd6e3a 100644 --- a/src/openrct2/actions/RideSetVehicleAction.h +++ b/src/openrct2/actions/RideSetVehicleAction.h @@ -43,5 +43,5 @@ public: private: bool RideIsVehicleTypeValid(const Ride& ride) const; - static_assert(sizeof(_value) >= sizeof(ObjectEntryIndex)); + static_assert(sizeof(_value) >= sizeof(OpenRCT2::ObjectEntryIndex)); }; diff --git a/src/openrct2/actions/ScenerySetRestrictedAction.h b/src/openrct2/actions/ScenerySetRestrictedAction.h index 2a088eb60b..523e140620 100644 --- a/src/openrct2/actions/ScenerySetRestrictedAction.h +++ b/src/openrct2/actions/ScenerySetRestrictedAction.h @@ -14,13 +14,13 @@ class ScenerySetRestrictedAction final : public GameActionBase { private: - ObjectType _objectType; - ObjectEntryIndex _objectIndex; + OpenRCT2::ObjectType _objectType; + OpenRCT2::ObjectEntryIndex _objectIndex; bool _isRestricted; public: ScenerySetRestrictedAction() = default; - ScenerySetRestrictedAction(ObjectType objectType, ObjectEntryIndex entryIndex, bool isRestricted); + ScenerySetRestrictedAction(OpenRCT2::ObjectType objectType, OpenRCT2::ObjectEntryIndex entryIndex, bool isRestricted); uint16_t GetActionFlags() const override; diff --git a/src/openrct2/actions/SmallSceneryPlaceAction.h b/src/openrct2/actions/SmallSceneryPlaceAction.h index 7a167a5529..a55cd96830 100644 --- a/src/openrct2/actions/SmallSceneryPlaceAction.h +++ b/src/openrct2/actions/SmallSceneryPlaceAction.h @@ -23,7 +23,7 @@ class SmallSceneryPlaceAction final : public GameActionBase { private: - ObjectEntryIndex _wallType{ kObjectEntryIndexNull }; + OpenRCT2::ObjectEntryIndex _wallType{ OpenRCT2::kObjectEntryIndexNull }; CoordsXYZ _loc; Direction _edge{ kInvalidDirection }; int32_t _primaryColour{ COLOUR_BLACK }; @@ -36,7 +39,7 @@ private: public: WallPlaceAction() = default; WallPlaceAction( - ObjectEntryIndex wallType, const CoordsXYZ& loc, uint8_t edge, int32_t primaryColour, int32_t secondaryColour, + OpenRCT2::ObjectEntryIndex wallType, const CoordsXYZ& loc, uint8_t edge, int32_t primaryColour, int32_t secondaryColour, int32_t tertiaryColour); void AcceptParameters(GameActionParameterVisitor& visitor) override; diff --git a/src/openrct2/core/DataSerialiserTraits.h b/src/openrct2/core/DataSerialiserTraits.h index fceac73229..b0b4b5a931 100644 --- a/src/openrct2/core/DataSerialiserTraits.h +++ b/src/openrct2/core/DataSerialiserTraits.h @@ -609,34 +609,34 @@ struct DataSerializerTraitsT }; template<> -struct DataSerializerTraitsT +struct DataSerializerTraitsT { - static void encode(OpenRCT2::IStream* stream, const RCTObjectEntry& val) + static void encode(OpenRCT2::IStream* stream, const OpenRCT2::RCTObjectEntry& val) { stream->WriteValue(ByteSwapBE(val.flags)); stream->WriteArray(val.nameWOC, 12); } - static void decode(OpenRCT2::IStream* stream, RCTObjectEntry& val) + static void decode(OpenRCT2::IStream* stream, OpenRCT2::RCTObjectEntry& val) { val.flags = ByteSwapBE(stream->ReadValue()); auto str = stream->ReadArray(12); memcpy(val.nameWOC, str.get(), 12); } - static void log(OpenRCT2::IStream* stream, const RCTObjectEntry& val) + static void log(OpenRCT2::IStream* stream, const OpenRCT2::RCTObjectEntry& val) { stream->WriteArray(val.name, 8); } }; template<> -struct DataSerializerTraitsT +struct DataSerializerTraitsT { - static void encode(OpenRCT2::IStream* stream, const ObjectEntryDescriptor& val) + static void encode(OpenRCT2::IStream* stream, const OpenRCT2::ObjectEntryDescriptor& val) { stream->WriteValue(static_cast(val.Generation)); - if (val.Generation == ObjectGeneration::DAT) + if (val.Generation == OpenRCT2::ObjectGeneration::DAT) { - DataSerializerTraits s; + DataSerializerTraits s; s.encode(stream, val.Entry); } else @@ -646,25 +646,25 @@ struct DataSerializerTraitsT } } - static void decode(OpenRCT2::IStream* stream, ObjectEntryDescriptor& val) + static void decode(OpenRCT2::IStream* stream, OpenRCT2::ObjectEntryDescriptor& val) { - auto generation = static_cast(stream->ReadValue()); - if (generation == ObjectGeneration::DAT) + auto generation = static_cast(stream->ReadValue()); + if (generation == OpenRCT2::ObjectGeneration::DAT) { - DataSerializerTraits s; - RCTObjectEntry entry; + DataSerializerTraits s; + OpenRCT2::RCTObjectEntry entry; s.decode(stream, entry); - val = ObjectEntryDescriptor(entry); + val = OpenRCT2::ObjectEntryDescriptor(entry); } else { - auto type = static_cast(stream->ReadValue()); + auto type = static_cast(stream->ReadValue()); auto identifier = stream->ReadString(); - val = ObjectEntryDescriptor(type, identifier); + val = OpenRCT2::ObjectEntryDescriptor(type, identifier); } } - static void log(OpenRCT2::IStream* stream, const ObjectEntryDescriptor& val) + static void log(OpenRCT2::IStream* stream, const OpenRCT2::ObjectEntryDescriptor& val) { auto identifier = std::string(val.GetName()); char msg[128] = {}; @@ -758,7 +758,7 @@ struct DataSerializerTraitsT stream->WriteValue(val.primaryColour); stream->WriteValue(val.secondaryColour); stream->WriteValue(val.tertiaryColour); - DataSerializerTraits s; + DataSerializerTraits s; s.encode(stream, val.sceneryObject); } static void decode(OpenRCT2::IStream* stream, TrackDesignSceneryElement& val) @@ -768,7 +768,7 @@ struct DataSerializerTraitsT stream->ReadValue(val.primaryColour); stream->ReadValue(val.secondaryColour); stream->ReadValue(val.tertiaryColour); - DataSerializerTraits s; + DataSerializerTraits s; s.decode(stream, val.sceneryObject); } static void log(OpenRCT2::IStream* stream, const TrackDesignSceneryElement& val) @@ -961,7 +961,7 @@ struct DataSerializerTraitsT static void encode(OpenRCT2::IStream* stream, const Banner& banner) { DataSerializerTraits().encode(stream, banner.id); - DataSerializerTraits().encode(stream, banner.type); + DataSerializerTraits().encode(stream, banner.type); stream->WriteValue(banner.flags); stream->WriteString(banner.text); stream->WriteValue(banner.colour); @@ -973,7 +973,7 @@ struct DataSerializerTraitsT static void decode(OpenRCT2::IStream* stream, Banner& banner) { DataSerializerTraits().decode(stream, banner.id); - DataSerializerTraits().decode(stream, banner.type); + DataSerializerTraits().decode(stream, banner.type); stream->ReadValue(banner.flags); banner.text = stream->ReadString(); stream->ReadValue(banner.colour); diff --git a/src/openrct2/entity/Peep.h b/src/openrct2/entity/Peep.h index 85c287cdf1..d4fb13bcc6 100644 --- a/src/openrct2/entity/Peep.h +++ b/src/openrct2/entity/Peep.h @@ -303,7 +303,7 @@ struct Peep : EntityBase PeepRideSubState RideSubState; PeepUsingBinSubState UsingBinSubState; }; - ObjectEntryIndex AnimationObjectIndex; + OpenRCT2::ObjectEntryIndex AnimationObjectIndex; PeepAnimationGroup AnimationGroup; uint8_t TshirtColour; uint8_t TrousersColour; diff --git a/src/openrct2/interface/InteractiveConsole.cpp b/src/openrct2/interface/InteractiveConsole.cpp index d4f5f45c37..255ca9f1a7 100644 --- a/src/openrct2/interface/InteractiveConsole.cpp +++ b/src/openrct2/interface/InteractiveConsole.cpp @@ -1050,7 +1050,7 @@ static void ConsoleCommandLoadObject(InteractiveConsole& console, const argument return; } - const RCTObjectEntry* entry = &ori->ObjectEntry; + const auto* entry = &ori->ObjectEntry; const auto* loadedObject = ObjectManagerGetLoadedObject(ObjectEntryDescriptor(*ori)); if (loadedObject != nullptr) { diff --git a/src/openrct2/management/Research.h b/src/openrct2/management/Research.h index 49fdf0aabd..5684950146 100644 --- a/src/openrct2/management/Research.h +++ b/src/openrct2/management/Research.h @@ -57,7 +57,7 @@ struct ResearchItem uint32_t rawValue; struct { - ObjectEntryIndex entryIndex; + OpenRCT2::ObjectEntryIndex entryIndex; uint8_t baseRideType; OpenRCT2::Research::EntryType type; // 0: scenery entry, 1: ride entry }; @@ -81,8 +81,8 @@ struct ResearchItem { } ResearchItem( - OpenRCT2::Research::EntryType _type, ObjectEntryIndex _entryIndex, uint8_t _baseRideType, ResearchCategory _category, - uint8_t _flags) + OpenRCT2::Research::EntryType _type, OpenRCT2::ObjectEntryIndex _entryIndex, uint8_t _baseRideType, + ResearchCategory _category, uint8_t _flags) : entryIndex(_entryIndex) , baseRideType(_baseRideType) , type(_type) @@ -127,17 +127,18 @@ void ResearchFinishItem(const ResearchItem& researchItem); void ResearchInsert(ResearchItem&& item, bool researched); void ResearchRemove(const ResearchItem& researchItem); -bool ResearchInsertRideEntry(ride_type_t rideType, ObjectEntryIndex entryIndex, ResearchCategory category, bool researched); -void ResearchInsertRideEntry(ObjectEntryIndex entryIndex, bool researched); -bool ResearchInsertSceneryGroupEntry(ObjectEntryIndex entryIndex, bool researched); +bool ResearchInsertRideEntry( + ride_type_t rideType, OpenRCT2::ObjectEntryIndex entryIndex, ResearchCategory category, bool researched); +void ResearchInsertRideEntry(OpenRCT2::ObjectEntryIndex entryIndex, bool researched); +bool ResearchInsertSceneryGroupEntry(OpenRCT2::ObjectEntryIndex entryIndex, bool researched); -bool ResearchIsInvented(ObjectType objectType, ObjectEntryIndex index); +bool ResearchIsInvented(OpenRCT2::ObjectType objectType, OpenRCT2::ObjectEntryIndex index); void RideTypeSetInvented(ride_type_t rideType); -void RideEntrySetInvented(ObjectEntryIndex rideEntryIndex); +void RideEntrySetInvented(OpenRCT2::ObjectEntryIndex rideEntryIndex); void ScenerySetInvented(const ScenerySelection& sceneryItem); void ScenerySetNotInvented(const ScenerySelection& sceneryItem); bool RideTypeIsInvented(ride_type_t rideType); -bool RideEntryIsInvented(ObjectEntryIndex rideEntryIndex); +bool RideEntryIsInvented(OpenRCT2::ObjectEntryIndex rideEntryIndex); bool SceneryGroupIsInvented(int32_t sgIndex); void SceneryGroupSetInvented(int32_t sgIndex); bool SceneryIsInvented(const ScenerySelection& sceneryItem); diff --git a/src/openrct2/network/NetworkBase.h b/src/openrct2/network/NetworkBase.h index 8a65b81f9b..a144b995ac 100644 --- a/src/openrct2/network/NetworkBase.h +++ b/src/openrct2/network/NetworkBase.h @@ -79,8 +79,8 @@ public: // Server void RemovePlayer(std::unique_ptr& connection); void UpdateServer(); void ServerClientDisconnected(std::unique_ptr& connection); - bool SaveMap(OpenRCT2::IStream* stream, const std::vector& objects) const; - std::vector SaveForNetwork(const std::vector& objects) const; + bool SaveMap(OpenRCT2::IStream* stream, const std::vector& objects) const; + std::vector SaveForNetwork(const std::vector& objects) const; std::string MakePlayerNameUnique(const std::string& name); // Packet dispatchers. @@ -100,7 +100,8 @@ public: // Server void ServerSendGroupList(NetworkConnection& connection); void ServerSendEventPlayerJoined(const char* playerName); void ServerSendEventPlayerDisconnected(const char* playerName, const char* reason); - void ServerSendObjectsList(NetworkConnection& connection, const std::vector& objects) const; + void ServerSendObjectsList( + NetworkConnection& connection, const std::vector& objects) const; void ServerSendScripts(NetworkConnection& connection); // Handlers @@ -143,7 +144,7 @@ public: // Client void Client_Send_GAME_ACTION(const GameAction* action); void Client_Send_PING(); void Client_Send_GAMEINFO(); - void Client_Send_MAPREQUEST(const std::vector& objects); + void Client_Send_MAPREQUEST(const std::vector& objects); void Client_Send_HEARTBEAT(NetworkConnection& connection) const; // Handlers. @@ -231,7 +232,7 @@ private: // Client Data std::map _pendingPlayerLists; std::multimap _pendingPlayerInfo; std::map _serverTickData; - std::vector _missingObjects; + std::vector _missingObjects; std::string _host; std::string _chatLogPath; std::string _chatLogFilenameFormat = "%Y%m%d-%H%M%S.txt"; diff --git a/src/openrct2/network/NetworkConnection.h b/src/openrct2/network/NetworkConnection.h index ad757accee..6850fd6012 100644 --- a/src/openrct2/network/NetworkConnection.h +++ b/src/openrct2/network/NetworkConnection.h @@ -21,7 +21,11 @@ #include class NetworkPlayer; -struct ObjectRepositoryItem; + +namespace OpenRCT2 +{ + struct ObjectRepositoryItem; +} class NetworkConnection final { @@ -34,7 +38,7 @@ public: uint32_t PingTime = 0; NetworkKey Key; std::vector Challenge; - std::vector RequestedObjects; + std::vector RequestedObjects; bool ShouldDisconnect = false; NetworkConnection() noexcept; diff --git a/src/openrct2/object/AudioObject.cpp b/src/openrct2/object/AudioObject.cpp index c395d98bed..2ffda38725 100644 --- a/src/openrct2/object/AudioObject.cpp +++ b/src/openrct2/object/AudioObject.cpp @@ -17,43 +17,43 @@ #include "../core/Json.hpp" #include "../core/Path.hpp" -using namespace OpenRCT2; -using namespace OpenRCT2::Audio; - -void AudioObject::Load() +namespace OpenRCT2 { - // Start with base samples - _loadedSampleTable.LoadFrom(_sampleTable, 0, _sampleTable.GetCount()); - - // Override samples from asset packs - auto context = GetContext(); - auto assetManager = context->GetAssetPackManager(); - if (assetManager != nullptr) + void AudioObject::Load() { - assetManager->LoadSamplesForObject(GetIdentifier(), _loadedSampleTable); + // Start with base samples + _loadedSampleTable.LoadFrom(_sampleTable, 0, _sampleTable.GetCount()); + + // Override samples from asset packs + auto context = GetContext(); + auto assetManager = context->GetAssetPackManager(); + if (assetManager != nullptr) + { + assetManager->LoadSamplesForObject(GetIdentifier(), _loadedSampleTable); + } + + _loadedSampleTable.Load(); } - _loadedSampleTable.Load(); -} + void AudioObject::Unload() + { + _loadedSampleTable.Unload(); + } -void AudioObject::Unload() -{ - _loadedSampleTable.Unload(); -} + void AudioObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "AudioObject::ReadJson expects parameter root to be object"); + _sampleTable.ReadFromJson(context, root); + PopulateTablesFromJson(context, root); + } -void AudioObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "AudioObject::ReadJson expects parameter root to be object"); - _sampleTable.ReadFromJson(context, root); - PopulateTablesFromJson(context, root); -} + Audio::IAudioSource* AudioObject::GetSample(uint32_t index) const + { + return _loadedSampleTable.GetSample(index); + } -IAudioSource* AudioObject::GetSample(uint32_t index) const -{ - return _loadedSampleTable.GetSample(index); -} - -int32_t AudioObject::GetSampleModifier(uint32_t index) const -{ - return _loadedSampleTable.GetSampleModifier(index); -} + int32_t AudioObject::GetSampleModifier(uint32_t index) const + { + return _loadedSampleTable.GetSampleModifier(index); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/AudioObject.h b/src/openrct2/object/AudioObject.h index e7b5c2bffb..64fd4f2d86 100644 --- a/src/openrct2/object/AudioObject.h +++ b/src/openrct2/object/AudioObject.h @@ -16,19 +16,22 @@ #include -class AudioObject final : public Object +namespace OpenRCT2 { -private: - AudioSampleTable _sampleTable; - AudioSampleTable _loadedSampleTable; + class AudioObject final : public Object + { + private: + AudioSampleTable _sampleTable; + AudioSampleTable _loadedSampleTable; -public: - static constexpr ObjectType kObjectType = ObjectType::audio; + public: + static constexpr ObjectType kObjectType = ObjectType::audio; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; - OpenRCT2::Audio::IAudioSource* GetSample(uint32_t index) const; - int32_t GetSampleModifier(uint32_t index) const; -}; + Audio::IAudioSource* GetSample(uint32_t index) const; + int32_t GetSampleModifier(uint32_t index) const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/AudioSampleTable.cpp b/src/openrct2/object/AudioSampleTable.cpp index 509dd59d7e..9c7d5f4200 100644 --- a/src/openrct2/object/AudioSampleTable.cpp +++ b/src/openrct2/object/AudioSampleTable.cpp @@ -20,177 +20,177 @@ #include "../ui/UiContext.h" #include "Object.h" -using namespace OpenRCT2; -using namespace OpenRCT2::Audio; - -std::vector& AudioSampleTable::GetEntries() +namespace OpenRCT2 { - return _entries; -} - -void AudioSampleTable::ReadFromJson(IReadObjectContext* context, const json_t& root) -{ - json_t jSamples = root["samples"]; - if (jSamples.is_array()) + std::vector& AudioSampleTable::GetEntries() { - for (auto& jSample : jSamples) + return _entries; + } + + void AudioSampleTable::ReadFromJson(IReadObjectContext* context, const json_t& root) + { + json_t jSamples = root["samples"]; + if (jSamples.is_array()) { - SourceInfo sourceInfo; - int32_t modifier{}; - if (jSample.is_string()) + for (auto& jSample : jSamples) { - sourceInfo = ParseSource(jSample.get()); - } - else if (jSample.is_object()) - { - auto& jSource = jSample.at("source"); - if (jSource.is_string()) + SourceInfo sourceInfo; + int32_t modifier{}; + if (jSample.is_string()) { - sourceInfo = ParseSource(jSource.get()); - if (jSample.contains("modifier")) + sourceInfo = ParseSource(jSample.get()); + } + else if (jSample.is_object()) + { + auto& jSource = jSample.at("source"); + if (jSource.is_string()) { - auto& jModifier = jSample.at("modifier"); - if (jModifier.is_number()) + sourceInfo = ParseSource(jSource.get()); + if (jSample.contains("modifier")) { - modifier = jModifier.get(); + auto& jModifier = jSample.at("modifier"); + if (jModifier.is_number()) + { + modifier = jModifier.get(); + } } } } - } - auto asset = context->GetAsset(sourceInfo.Path); - if (!sourceInfo.SourceRange) - { - auto& entry = _entries.emplace_back(); - entry.Asset = asset; - entry.Modifier = modifier; - } - else - { - Range r(1, 5); - for (auto index : *sourceInfo.SourceRange) + auto asset = context->GetAsset(sourceInfo.Path); + if (!sourceInfo.SourceRange) { auto& entry = _entries.emplace_back(); entry.Asset = asset; - entry.PathIndex = index; entry.Modifier = modifier; } - } - } - } -} - -void AudioSampleTable::LoadFrom(const AudioSampleTable& table, size_t sourceStartIndex, size_t length) -{ - // Ensure we stay in bounds of source table - if (sourceStartIndex >= table._entries.size()) - return; - length = std::min(length, table._entries.size() - sourceStartIndex); - - // Asset packs may allocate more images for an object that original, or original object may - // not allocate any images at all. - if (_entries.size() < length) - { - _entries.resize(length); - } - - for (size_t i = 0; i < length; i++) - { - const auto& sourceEntry = table._entries[sourceStartIndex + i]; - if (sourceEntry.Asset) - { - auto stream = sourceEntry.Asset->GetStream(); - if (stream != nullptr) - { - auto& entry = _entries[i]; - entry.Asset = sourceEntry.Asset; - entry.PathIndex = sourceEntry.PathIndex; - entry.Modifier = sourceEntry.Modifier; - } - } - } -} - -void AudioSampleTable::Load() -{ - for (size_t i = 0; i < _entries.size(); i++) - { - auto& entry = _entries[i]; - if (entry.Source == nullptr) - { - entry.Source = LoadSample(static_cast(i)); - } - } -} - -void AudioSampleTable::Unload() -{ - for (auto& entry : _entries) - { - if (entry.Source != nullptr) - { - entry.Source->Release(); - entry.Source = nullptr; - } - } -} - -size_t AudioSampleTable::GetCount() const -{ - return _entries.size(); -} - -IAudioSource* AudioSampleTable::GetSample(uint32_t index) const -{ - if (index < _entries.size()) - { - return _entries[index].Source; - } - return nullptr; -} - -IAudioSource* AudioSampleTable::LoadSample(uint32_t index) const -{ - IAudioSource* result{}; - if (index < _entries.size()) - { - auto& entry = _entries[index]; - if (entry.Asset) - { - auto stream = entry.Asset->GetStream(); - if (stream != nullptr) - { - auto& audioContext = GetContext()->GetAudioContext(); - if (entry.PathIndex) - { - auto originalPosition = stream->GetPosition(); - auto numSounds = stream->ReadValue(); - stream->SetPosition(originalPosition); - - if (*entry.PathIndex >= numSounds) - { - auto& ui = GetContext()->GetUiContext(); - ui.ShowMessageBox(FormatStringID( - STR_AUDIO_FILE_TRUNCATED, entry.Asset->GetPath().c_str(), *entry.PathIndex, numSounds)); - } - - result = audioContext.CreateStreamFromCSS(std::move(stream), *entry.PathIndex); - } else { - result = audioContext.CreateStreamFromWAV(std::move(stream)); + Range r(1, 5); + for (auto index : *sourceInfo.SourceRange) + { + auto& entry = _entries.emplace_back(); + entry.Asset = asset; + entry.PathIndex = index; + entry.Modifier = modifier; + } } } } } - return result; -} -int32_t AudioSampleTable::GetSampleModifier(uint32_t index) const -{ - if (index < _entries.size()) + void AudioSampleTable::LoadFrom(const AudioSampleTable& table, size_t sourceStartIndex, size_t length) { - return _entries[index].Modifier; + // Ensure we stay in bounds of source table + if (sourceStartIndex >= table._entries.size()) + return; + length = std::min(length, table._entries.size() - sourceStartIndex); + + // Asset packs may allocate more images for an object that original, or original object may + // not allocate any images at all. + if (_entries.size() < length) + { + _entries.resize(length); + } + + for (size_t i = 0; i < length; i++) + { + const auto& sourceEntry = table._entries[sourceStartIndex + i]; + if (sourceEntry.Asset) + { + auto stream = sourceEntry.Asset->GetStream(); + if (stream != nullptr) + { + auto& entry = _entries[i]; + entry.Asset = sourceEntry.Asset; + entry.PathIndex = sourceEntry.PathIndex; + entry.Modifier = sourceEntry.Modifier; + } + } + } } - return 0; -} + + void AudioSampleTable::Load() + { + for (size_t i = 0; i < _entries.size(); i++) + { + auto& entry = _entries[i]; + if (entry.Source == nullptr) + { + entry.Source = LoadSample(static_cast(i)); + } + } + } + + void AudioSampleTable::Unload() + { + for (auto& entry : _entries) + { + if (entry.Source != nullptr) + { + entry.Source->Release(); + entry.Source = nullptr; + } + } + } + + size_t AudioSampleTable::GetCount() const + { + return _entries.size(); + } + + Audio::IAudioSource* AudioSampleTable::GetSample(uint32_t index) const + { + if (index < _entries.size()) + { + return _entries[index].Source; + } + return nullptr; + } + + Audio::IAudioSource* AudioSampleTable::LoadSample(uint32_t index) const + { + Audio::IAudioSource* result{}; + if (index < _entries.size()) + { + auto& entry = _entries[index]; + if (entry.Asset) + { + auto stream = entry.Asset->GetStream(); + if (stream != nullptr) + { + auto& audioContext = GetContext()->GetAudioContext(); + if (entry.PathIndex) + { + auto originalPosition = stream->GetPosition(); + auto numSounds = stream->ReadValue(); + stream->SetPosition(originalPosition); + + if (*entry.PathIndex >= numSounds) + { + auto& ui = GetContext()->GetUiContext(); + ui.ShowMessageBox(FormatStringID( + STR_AUDIO_FILE_TRUNCATED, entry.Asset->GetPath().c_str(), *entry.PathIndex, numSounds)); + } + + result = audioContext.CreateStreamFromCSS(std::move(stream), *entry.PathIndex); + } + else + { + result = audioContext.CreateStreamFromWAV(std::move(stream)); + } + } + } + } + return result; + } + + int32_t AudioSampleTable::GetSampleModifier(uint32_t index) const + { + if (index < _entries.size()) + { + return _entries[index].Modifier; + } + return 0; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/AudioSampleTable.h b/src/openrct2/object/AudioSampleTable.h index 3cc3618858..f91fb0778b 100644 --- a/src/openrct2/object/AudioSampleTable.h +++ b/src/openrct2/object/AudioSampleTable.h @@ -17,46 +17,49 @@ #include -struct IReadObjectContext; - -class AudioSampleTable : public ResourceTable +namespace OpenRCT2 { -private: - struct Entry + struct IReadObjectContext; + + class AudioSampleTable : public ResourceTable { - OpenRCT2::Audio::IAudioSource* Source{}; - std::optional Asset; - std::optional PathIndex; - int32_t Modifier{}; + private: + struct Entry + { + Audio::IAudioSource* Source{}; + std::optional Asset; + std::optional PathIndex; + int32_t Modifier{}; + }; + + std::vector _entries; + + public: + std::vector& GetEntries(); + + /** + * Read the entries from the given JSON into the table, but do not load anything. + */ + void ReadFromJson(IReadObjectContext* context, const json_t& root); + + /** + * Load all available entries from the given table. + */ + void LoadFrom(const AudioSampleTable& table, size_t sourceIndex, size_t length); + + /** + * Load all available entries. + */ + void Load(); + + /** + * Unloads all entries that are currently loaded. + */ + void Unload(); + + size_t GetCount() const; + Audio::IAudioSource* GetSample(uint32_t index) const; + Audio::IAudioSource* LoadSample(uint32_t index) const; + int32_t GetSampleModifier(uint32_t index) const; }; - - std::vector _entries; - -public: - std::vector& GetEntries(); - - /** - * Read the entries from the given JSON into the table, but do not load anything. - */ - void ReadFromJson(IReadObjectContext* context, const json_t& root); - - /** - * Load all available entries from the given table. - */ - void LoadFrom(const AudioSampleTable& table, size_t sourceIndex, size_t length); - - /** - * Load all available entries. - */ - void Load(); - - /** - * Unloads all entries that are currently loaded. - */ - void Unload(); - - size_t GetCount() const; - OpenRCT2::Audio::IAudioSource* GetSample(uint32_t index) const; - OpenRCT2::Audio::IAudioSource* LoadSample(uint32_t index) const; - int32_t GetSampleModifier(uint32_t index) const; -}; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/BannerObject.cpp b/src/openrct2/object/BannerObject.cpp index 9d225a9b1f..873b33504e 100644 --- a/src/openrct2/object/BannerObject.cpp +++ b/src/openrct2/object/BannerObject.cpp @@ -18,91 +18,92 @@ #include "../object/ObjectRepository.h" #include "ObjectList.h" -using namespace OpenRCT2; - -void BannerObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) +namespace OpenRCT2 { - stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.scrolling_mode = stream->ReadValue(); - _legacyType.flags = stream->ReadValue(); - _legacyType.price = stream->ReadValue(); - _legacyType.scenery_tab_id = kObjectEntryIndexNull; - stream->Seek(2, OpenRCT2::STREAM_SEEK_CURRENT); - - GetStringTable().Read(context, stream, ObjectStringID::NAME); - - RCTObjectEntry sgEntry = stream->ReadValue(); - SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); - - GetImageTable().Read(context, stream); - - // Validate properties - if (_legacyType.price <= 0.00_GBP) + void BannerObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) { - context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); - } + stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); + _legacyType.scrolling_mode = stream->ReadValue(); + _legacyType.flags = stream->ReadValue(); + _legacyType.price = stream->ReadValue(); + _legacyType.scenery_tab_id = kObjectEntryIndexNull; + stream->Seek(2, OpenRCT2::STREAM_SEEK_CURRENT); - // Add banners to 'Signs and items for footpaths' group, rather than lumping them in the Miscellaneous tab. - // Since this is already done the other way round for original items, avoid adding those to prevent duplicates. + GetStringTable().Read(context, stream, ObjectStringID::NAME); - auto& objectRepository = context->GetObjectRepository(); - auto item = objectRepository.FindObject(GetDescriptor()); - if (item != nullptr) - { - auto sourceGame = item->GetFirstSourceGame(); - if (sourceGame == ObjectSourceGame::WackyWorlds || sourceGame == ObjectSourceGame::TimeTwister - || sourceGame == ObjectSourceGame::Custom) + RCTObjectEntry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); + + GetImageTable().Read(context, stream); + + // Validate properties + if (_legacyType.price <= 0.00_GBP) { - auto scgPathX = Object::GetScgPathXHeader(); - SetPrimarySceneryGroup(scgPathX); + context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); + } + + // Add banners to 'Signs and items for footpaths' group, rather than lumping them in the Miscellaneous tab. + // Since this is already done the other way round for original items, avoid adding those to prevent duplicates. + + auto& objectRepository = context->GetObjectRepository(); + auto item = objectRepository.FindObject(GetDescriptor()); + if (item != nullptr) + { + auto sourceGame = item->GetFirstSourceGame(); + if (sourceGame == ObjectSourceGame::WackyWorlds || sourceGame == ObjectSourceGame::TimeTwister + || sourceGame == ObjectSourceGame::Custom) + { + auto scgPathX = Object::GetScgPathXHeader(); + SetPrimarySceneryGroup(scgPathX); + } } } -} -void BannerObject::Load() -{ - GetStringTable().Sort(); - _legacyType.name = LanguageAllocateObjectString(GetName()); - _legacyType.image = LoadImages(); -} - -void BannerObject::Unload() -{ - LanguageFreeObjectString(_legacyType.name); - UnloadImages(); - - _legacyType.name = 0; - _legacyType.image = 0; -} - -void BannerObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; - - auto image0 = ImageId(_legacyType.image, COLOUR_BORDEAUX_RED); - auto image1 = ImageId(_legacyType.image + 1, COLOUR_BORDEAUX_RED); - - GfxDrawSprite(rt, image0, screenCoords + ScreenCoordsXY{ -12, 8 }); - GfxDrawSprite(rt, image1, screenCoords + ScreenCoordsXY{ -12, 8 }); -} - -void BannerObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "BannerObject::ReadJson expects parameter root to be object"); - json_t properties = root["properties"]; - - if (properties.is_object()) + void BannerObject::Load() { - _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"]); - _legacyType.price = Json::GetNumber(properties["price"]); - _legacyType.flags = Json::GetFlags( - properties, - { - { "hasPrimaryColour", BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR }, - }); - - SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + GetStringTable().Sort(); + _legacyType.name = LanguageAllocateObjectString(GetName()); + _legacyType.image = LoadImages(); } - PopulateTablesFromJson(context, root); -} + void BannerObject::Unload() + { + LanguageFreeObjectString(_legacyType.name); + UnloadImages(); + + _legacyType.name = 0; + _legacyType.image = 0; + } + + void BannerObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; + + auto image0 = ImageId(_legacyType.image, COLOUR_BORDEAUX_RED); + auto image1 = ImageId(_legacyType.image + 1, COLOUR_BORDEAUX_RED); + + GfxDrawSprite(rt, image0, screenCoords + ScreenCoordsXY{ -12, 8 }); + GfxDrawSprite(rt, image1, screenCoords + ScreenCoordsXY{ -12, 8 }); + } + + void BannerObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "BannerObject::ReadJson expects parameter root to be object"); + json_t properties = root["properties"]; + + if (properties.is_object()) + { + _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"]); + _legacyType.price = Json::GetNumber(properties["price"]); + _legacyType.flags = Json::GetFlags( + properties, + { + { "hasPrimaryColour", BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR }, + }); + + SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + } + + PopulateTablesFromJson(context, root); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/BannerObject.h b/src/openrct2/object/BannerObject.h index 3cc369017f..96c110d3d7 100644 --- a/src/openrct2/object/BannerObject.h +++ b/src/openrct2/object/BannerObject.h @@ -13,23 +13,26 @@ #include "BannerSceneryEntry.h" #include "SceneryObject.h" -class BannerObject final : public SceneryObject +namespace OpenRCT2 { -private: - BannerSceneryEntry _legacyType = {}; - -public: - static constexpr ObjectType kObjectType = ObjectType::banners; - - void* GetLegacyData() override + class BannerObject final : public SceneryObject { - return &_legacyType; - } + private: + BannerSceneryEntry _legacyType = {}; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + public: + static constexpr ObjectType kObjectType = ObjectType::banners; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; -}; + void* GetLegacyData() override + { + return &_legacyType; + } + + void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/BannerSceneryEntry.h b/src/openrct2/object/BannerSceneryEntry.h index 33b2d23907..8465c6a8fd 100644 --- a/src/openrct2/object/BannerSceneryEntry.h +++ b/src/openrct2/object/BannerSceneryEntry.h @@ -13,19 +13,22 @@ #include "../localisation/StringIdType.h" #include "ObjectTypes.h" -enum +namespace OpenRCT2 { - BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR = (1 << 0), -}; + enum + { + BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR = (1 << 0), + }; -struct BannerSceneryEntry -{ - static constexpr auto kObjectType = ObjectType::banners; + struct BannerSceneryEntry + { + static constexpr auto kObjectType = ObjectType::banners; - StringId name; - uint32_t image; - uint8_t scrolling_mode; - uint8_t flags; - money64 price; - ObjectEntryIndex scenery_tab_id; -}; + StringId name; + uint32_t image; + uint8_t scrolling_mode; + uint8_t flags; + money64 price; + ObjectEntryIndex scenery_tab_id; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ClimateObject.cpp b/src/openrct2/object/ClimateObject.cpp index e21f2c032c..a241e0820c 100644 --- a/src/openrct2/object/ClimateObject.cpp +++ b/src/openrct2/object/ClimateObject.cpp @@ -18,208 +18,209 @@ #include -using namespace OpenRCT2; - -struct RawClimateMonth +namespace OpenRCT2 { - int8_t baseTemperature; - int8_t randomBias; - uint8_t distribution[kNumWeatherTypes]{}; - int8_t distRotation{}; - int16_t distributionSum{}; -}; - -using RawClimate = std::array; - -static constexpr const char* kWeatherTypes[] = { - "sunny", "partiallyCloudy", "cloudy", "rain", "heavyRain", "thunder", "snow", "heavySnow", "blizzard", -}; -static_assert(std::size(kWeatherTypes) == kNumWeatherTypes); - -static constexpr const char* kMonthKeys[] = { - "march", "april", "may", "june", "july", "august", "september", "october", -}; -static_assert(std::size(kMonthKeys) == kNumClimateMonths); - -void ClimateObject::Load() -{ -} - -void ClimateObject::Unload() -{ -} - -static RawClimate readWeatherTable(json_t& weather); -static Climate convertRawClimate(const RawClimate& rawClimate); - -void ClimateObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - const auto dist = getYearlyDistribution(); - const auto totalSize = kNumClimateMonths * kWeatherDistSize; - - for (auto i = 0u; i < EnumValue(WeatherType::Count); i++) + struct RawClimateMonth { - auto type = WeatherType(i); - auto imageId = ImageId(ClimateGetWeatherSpriteId(type)); - auto coords = ScreenCoordsXY(8 + (i % 3) * 35, 3 + (i / 3) * 37); - GfxDrawSprite(rt, imageId, coords); - - auto ft = Formatter(); - ft.Add(dist[i] * 100 / totalSize); - DrawTextEllipsised( - rt, coords + ScreenCoordsXY{ 12, 22 }, 35, STR_CLIMATE_WEATHER_PERCENT, ft, - { FontStyle::Small, TextAlignment::CENTRE }); - } -} - -void ClimateObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "ClimateObject::ReadJson expects parameter root to be an object"); - PopulateTablesFromJson(context, root); - - Guard::Assert(root["weather"].is_object(), "ClimateObject::ReadJson expects weather key to be an object"); - auto rawClimate = readWeatherTable(root["weather"]); - _climate = convertRawClimate(rawClimate); - - _scriptName = Json::GetString(root["scriptName"], std::string(GetIdentifier())); - - Guard::Assert(root["properties"].is_object(), "ClimateObject::ReadJson expects properties key to be an object"); - _climate.itemThresholds.cold = Json::GetNumber(root["properties"]["coldItemTempThreshold"], 11); - _climate.itemThresholds.warm = Json::GetNumber(root["properties"]["warmItemTempThreshold"], 21); -} - -const TemperatureThresholds& ClimateObject::getItemThresholds() const -{ - return _climate.itemThresholds; -} - -const WeatherPattern& ClimateObject::getPatternForMonth(uint8_t month) const -{ - return _climate.patterns[month]; -} - -std::string ClimateObject::getScriptName() const -{ - return _scriptName; -} - -YearlyDistribution ClimateObject::getYearlyDistribution() const -{ - auto weatherTypeCount = [](const WeatherPattern& pattern, const WeatherType target) { - auto count = 0u; - for (auto type : pattern.distribution) - { - if (type == target) - count++; - } - return count; + int8_t baseTemperature; + int8_t randomBias; + uint8_t distribution[kNumWeatherTypes]{}; + int8_t distRotation{}; + int16_t distributionSum{}; }; - YearlyDistribution dist{}; - for (auto m = 0; m < kNumClimateMonths; m++) + using RawClimate = std::array; + + static constexpr const char* kWeatherTypes[] = { + "sunny", "partiallyCloudy", "cloudy", "rain", "heavyRain", "thunder", "snow", "heavySnow", "blizzard", + }; + static_assert(std::size(kWeatherTypes) == kNumWeatherTypes); + + static constexpr const char* kMonthKeys[] = { + "march", "april", "may", "june", "july", "august", "september", "october", + }; + static_assert(std::size(kMonthKeys) == kNumClimateMonths); + + void ClimateObject::Load() { - auto& pattern = getPatternForMonth(m); + } + + void ClimateObject::Unload() + { + } + + static RawClimate readWeatherTable(json_t& weather); + static Climate convertRawClimate(const RawClimate& rawClimate); + + void ClimateObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + const auto dist = getYearlyDistribution(); + const auto totalSize = kNumClimateMonths * kWeatherDistSize; + for (auto i = 0u; i < EnumValue(WeatherType::Count); i++) - dist[i] += weatherTypeCount(pattern, WeatherType(i)); + { + auto type = WeatherType(i); + auto imageId = ImageId(ClimateGetWeatherSpriteId(type)); + auto coords = ScreenCoordsXY(8 + (i % 3) * 35, 3 + (i / 3) * 37); + GfxDrawSprite(rt, imageId, coords); + + auto ft = Formatter(); + ft.Add(dist[i] * 100 / totalSize); + DrawTextEllipsised( + rt, coords + ScreenCoordsXY{ 12, 22 }, 35, STR_CLIMATE_WEATHER_PERCENT, ft, + { FontStyle::Small, TextAlignment::CENTRE }); + } } - return dist; -} - -static Climate convertRawClimate(const RawClimate& rawClimate) -{ - Climate climate{}; - - for (auto m = 0; m < kNumClimateMonths; m++) + void ClimateObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto& srcMonth = rawClimate[m]; - auto& dstMonth = climate.patterns[m]; + Guard::Assert(root.is_object(), "ClimateObject::ReadJson expects parameter root to be an object"); + PopulateTablesFromJson(context, root); - dstMonth.baseTemperature = srcMonth.baseTemperature; - dstMonth.randomBias = srcMonth.randomBias; + Guard::Assert(root["weather"].is_object(), "ClimateObject::ReadJson expects weather key to be an object"); + auto rawClimate = readWeatherTable(root["weather"]); + _climate = convertRawClimate(rawClimate); - auto i = 0; - for (auto w = 0u; w < kNumWeatherTypes; w++) - { - if (i > kWeatherDistSize) - break; + _scriptName = Json::GetString(root["scriptName"], std::string(GetIdentifier())); - for (auto k = 0u; k < srcMonth.distribution[w]; k++) + Guard::Assert(root["properties"].is_object(), "ClimateObject::ReadJson expects properties key to be an object"); + _climate.itemThresholds.cold = Json::GetNumber(root["properties"]["coldItemTempThreshold"], 11); + _climate.itemThresholds.warm = Json::GetNumber(root["properties"]["warmItemTempThreshold"], 21); + } + + const TemperatureThresholds& ClimateObject::getItemThresholds() const + { + return _climate.itemThresholds; + } + + const WeatherPattern& ClimateObject::getPatternForMonth(uint8_t month) const + { + return _climate.patterns[month]; + } + + std::string ClimateObject::getScriptName() const + { + return _scriptName; + } + + YearlyDistribution ClimateObject::getYearlyDistribution() const + { + auto weatherTypeCount = [](const WeatherPattern& pattern, const WeatherType target) { + auto count = 0u; + for (auto type : pattern.distribution) { - dstMonth.distribution[i] = WeatherType(w); - i++; + if (type == target) + count++; } + return count; + }; + + YearlyDistribution dist{}; + for (auto m = 0; m < kNumClimateMonths; m++) + { + auto& pattern = getPatternForMonth(m); + for (auto i = 0u; i < EnumValue(WeatherType::Count); i++) + dist[i] += weatherTypeCount(pattern, WeatherType(i)); } - // Rotate weather distribution to be less biased towards sunny weather - auto begin = std::begin(dstMonth.distribution); - auto end = std::end(dstMonth.distribution); - std::rotate(begin, begin + srcMonth.distRotation, end); + return dist; } - return climate; -} - -static RawClimate readWeatherTable(json_t& weather) -{ - // First read raw climate distribution from JSON - RawClimate rawClimate{}; - for (auto i = 0u; i < kNumClimateMonths; i++) + static Climate convertRawClimate(const RawClimate& rawClimate) { - auto& monthKey = kMonthKeys[i]; - if (!weather.contains(monthKey)) + Climate climate{}; + + for (auto m = 0; m < kNumClimateMonths; m++) { - LOG_ERROR("Climate month not defined: %s", monthKey); - continue; + auto& srcMonth = rawClimate[m]; + auto& dstMonth = climate.patterns[m]; + + dstMonth.baseTemperature = srcMonth.baseTemperature; + dstMonth.randomBias = srcMonth.randomBias; + + auto i = 0; + for (auto w = 0u; w < kNumWeatherTypes; w++) + { + if (i > kWeatherDistSize) + break; + + for (auto k = 0u; k < srcMonth.distribution[w]; k++) + { + dstMonth.distribution[i] = WeatherType(w); + i++; + } + } + + // Rotate weather distribution to be less biased towards sunny weather + auto begin = std::begin(dstMonth.distribution); + auto end = std::end(dstMonth.distribution); + std::rotate(begin, begin + srcMonth.distRotation, end); } - auto& month = weather[monthKey]; - for (auto j = 0u; j < kNumWeatherTypes; j++) - { - Guard::Assert( - month["baseTemperature"].is_number(), - "ClimateObject::ReadJson expects weather months to contain baseTemperature"); - Guard::Assert( - month["randomBias"].is_number(), "ClimateObject::ReadJson expects weather months to contain randomBias"); - Guard::Assert( - month["distribution"].is_object(), - "ClimateObject::ReadJson expects weather months to contain distribution object"); - - auto& weatherKey = kWeatherTypes[j]; - if (!month["distribution"].contains(weatherKey)) - continue; - - auto weight = Json::GetNumber(month["distribution"][weatherKey]); - if (weight <= 0) - continue; - - rawClimate[i].baseTemperature = Json::GetNumber(month["baseTemperature"]); - rawClimate[i].randomBias = Json::GetNumber(month["randomBias"]); - rawClimate[i].distribution[j] = weight; - rawClimate[i].distRotation = Json::GetNumber(month["distRotation"]); - rawClimate[i].distributionSum += weight; - } - - Guard::Assert(rawClimate[i].distributionSum != 0, "Month %s has no weather defined!", monthKey); + return climate; } - // Adjust distribution to fit internal format - for (auto& climateMonth : rawClimate) + static RawClimate readWeatherTable(json_t& weather) { - auto adjustedDistSum = 0u; - for (auto i = 0u; i < kNumWeatherTypes; i++) + // First read raw climate distribution from JSON + RawClimate rawClimate{}; + for (auto i = 0u; i < kNumClimateMonths; i++) { - auto rawWeight = climateMonth.distribution[i]; - auto adjustedWeight = rawWeight * kWeatherDistSize / climateMonth.distributionSum; + auto& monthKey = kMonthKeys[i]; + if (!weather.contains(monthKey)) + { + LOG_ERROR("Climate month not defined: %s", monthKey); + continue; + } - climateMonth.distribution[i] = adjustedWeight; - adjustedDistSum += adjustedWeight; + auto& month = weather[monthKey]; + for (auto j = 0u; j < kNumWeatherTypes; j++) + { + Guard::Assert( + month["baseTemperature"].is_number(), + "ClimateObject::ReadJson expects weather months to contain baseTemperature"); + Guard::Assert( + month["randomBias"].is_number(), "ClimateObject::ReadJson expects weather months to contain randomBias"); + Guard::Assert( + month["distribution"].is_object(), + "ClimateObject::ReadJson expects weather months to contain distribution object"); + + auto& weatherKey = kWeatherTypes[j]; + if (!month["distribution"].contains(weatherKey)) + continue; + + auto weight = Json::GetNumber(month["distribution"][weatherKey]); + if (weight <= 0) + continue; + + rawClimate[i].baseTemperature = Json::GetNumber(month["baseTemperature"]); + rawClimate[i].randomBias = Json::GetNumber(month["randomBias"]); + rawClimate[i].distribution[j] = weight; + rawClimate[i].distRotation = Json::GetNumber(month["distRotation"]); + rawClimate[i].distributionSum += weight; + } + + Guard::Assert(rawClimate[i].distributionSum != 0, "Month %s has no weather defined!", monthKey); } - Guard::Assert( - adjustedDistSum == kWeatherDistSize, "Weather distribution size mismatches: %d != %d", adjustedDistSum, - kWeatherDistSize); - } + // Adjust distribution to fit internal format + for (auto& climateMonth : rawClimate) + { + auto adjustedDistSum = 0u; + for (auto i = 0u; i < kNumWeatherTypes; i++) + { + auto rawWeight = climateMonth.distribution[i]; + auto adjustedWeight = rawWeight * kWeatherDistSize / climateMonth.distributionSum; - return rawClimate; -} + climateMonth.distribution[i] = adjustedWeight; + adjustedDistSum += adjustedWeight; + } + + Guard::Assert( + adjustedDistSum == kWeatherDistSize, "Weather distribution size mismatches: %d != %d", adjustedDistSum, + kWeatherDistSize); + } + + return rawClimate; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ClimateObject.h b/src/openrct2/object/ClimateObject.h index 4b664daec7..3ca67858d6 100644 --- a/src/openrct2/object/ClimateObject.h +++ b/src/openrct2/object/ClimateObject.h @@ -12,27 +12,30 @@ #include "../world/Climate.h" #include "Object.h" -struct IReadObjectContext; - -using YearlyDistribution = std::array; - -class ClimateObject final : public Object +namespace OpenRCT2 { -private: - Climate _climate; - std::string _scriptName; + struct IReadObjectContext; -public: - static constexpr ObjectType kObjectType = ObjectType::climate; + using YearlyDistribution = std::array; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + class ClimateObject final : public Object + { + private: + Climate _climate; + std::string _scriptName; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + public: + static constexpr ObjectType kObjectType = ObjectType::climate; - const TemperatureThresholds& getItemThresholds() const; - const WeatherPattern& getPatternForMonth(uint8_t month) const; - std::string getScriptName() const; - YearlyDistribution getYearlyDistribution() const; -}; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + + const TemperatureThresholds& getItemThresholds() const; + const WeatherPattern& getPatternForMonth(uint8_t month) const; + std::string getScriptName() const; + YearlyDistribution getYearlyDistribution() const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/DefaultObjects.cpp b/src/openrct2/object/DefaultObjects.cpp index 347f893331..c093b85c57 100644 --- a/src/openrct2/object/DefaultObjects.cpp +++ b/src/openrct2/object/DefaultObjects.cpp @@ -11,155 +11,158 @@ #include "Object.h" -constexpr std::array kMinimumRequiredObjects = { - "rct2.terrain_surface.grass", - "rct2.terrain_edge.rock", +namespace OpenRCT2 +{ + constexpr std::array kMinimumRequiredObjects = { + "rct2.terrain_surface.grass", + "rct2.terrain_edge.rock", - "rct2.station.plain", -}; + "rct2.station.plain", + }; -constexpr std::array kCommonScenarioAndTrackDesignerObjects = { - // An initial default selection - "rct2.scenery_group.scgtrees", // Scenery: Trees - "rct2.scenery_group.scgshrub", // Scenery: Shrubs and Ornaments - "rct2.scenery_group.scggardn", // Scenery: Gardens - "rct2.scenery_group.scgfence", // Scenery: Fences and Walls - "rct2.scenery_group.scgwalls", // Scenery: Walls and Roofs - "rct2.scenery_group.scgpathx", // Scenery: Signs and Items for Footpaths + constexpr std::array kCommonScenarioAndTrackDesignerObjects = { + // An initial default selection + "rct2.scenery_group.scgtrees", // Scenery: Trees + "rct2.scenery_group.scgshrub", // Scenery: Shrubs and Ornaments + "rct2.scenery_group.scggardn", // Scenery: Gardens + "rct2.scenery_group.scgfence", // Scenery: Fences and Walls + "rct2.scenery_group.scgwalls", // Scenery: Walls and Roofs + "rct2.scenery_group.scgpathx", // Scenery: Signs and Items for Footpaths - "rct2.park_entrance.pkent1", // Park Entrance: Traditional Park Entrance + "rct2.park_entrance.pkent1", // Park Entrance: Traditional Park Entrance - "rct2.water.wtrcyan", // Water: Natural Water + "rct2.water.wtrcyan", // Water: Natural Water - // Stations - "rct2.station.wooden", - "rct2.station.canvas_tent", - "rct2.station.castle_grey", - "rct2.station.castle_brown", - "rct2.station.jungle", - "rct2.station.log", - "rct2.station.classical", - "rct2.station.abstract", - "rct2.station.snow", - "rct2.station.pagoda", - "rct2.station.space", + // Stations + "rct2.station.wooden", + "rct2.station.canvas_tent", + "rct2.station.castle_grey", + "rct2.station.castle_brown", + "rct2.station.jungle", + "rct2.station.log", + "rct2.station.classical", + "rct2.station.abstract", + "rct2.station.snow", + "rct2.station.pagoda", + "rct2.station.space", - // Music - "rct2.music.dodgems", - "rct2.music.fairground", - "rct2.music.roman", - "rct2.music.oriental", - "rct2.music.martian", - "rct2.music.jungle", - "rct2.music.egyptian", - "rct2.music.toyland", - "rct2.music.space", - "rct2.music.horror", - "rct2.music.techno", - "rct2.music.gentle", - "rct2.music.summer", - "rct2.music.water", - "rct2.music.wildwest", - "rct2.music.jurassic", - "rct2.music.rock1", - "rct2.music.ragtime", - "rct2.music.fantasy", - "rct2.music.rock2", - "rct2.music.ice", - "rct2.music.snow", - "rct2.music.medieval", - "rct2.music.urban", - "rct2.music.organ", - "rct2.music.mechanical", - "rct2.music.modern", - "rct2.music.pirate", - "rct2.music.rock3", - "rct2.music.candy", - "openrct2.music.galaxy", - "openrct2.music.acid", - "openrct2.music.dodgems", - "openrct2.music.blizzard", - "openrct2.music.extraterrestrial", - "openrct2.music.fairground2", - "openrct2.music.ragtime2", - "openrct2.music.prehistoric", - "openrct2.music.mystic", - "openrct2.music.rock4", - "openrct2.music.progressive", + // Music + "rct2.music.dodgems", + "rct2.music.fairground", + "rct2.music.roman", + "rct2.music.oriental", + "rct2.music.martian", + "rct2.music.jungle", + "rct2.music.egyptian", + "rct2.music.toyland", + "rct2.music.space", + "rct2.music.horror", + "rct2.music.techno", + "rct2.music.gentle", + "rct2.music.summer", + "rct2.music.water", + "rct2.music.wildwest", + "rct2.music.jurassic", + "rct2.music.rock1", + "rct2.music.ragtime", + "rct2.music.fantasy", + "rct2.music.rock2", + "rct2.music.ice", + "rct2.music.snow", + "rct2.music.medieval", + "rct2.music.urban", + "rct2.music.organ", + "rct2.music.mechanical", + "rct2.music.modern", + "rct2.music.pirate", + "rct2.music.rock3", + "rct2.music.candy", + "openrct2.music.galaxy", + "openrct2.music.acid", + "openrct2.music.dodgems", + "openrct2.music.blizzard", + "openrct2.music.extraterrestrial", + "openrct2.music.fairground2", + "openrct2.music.ragtime2", + "openrct2.music.prehistoric", + "openrct2.music.mystic", + "openrct2.music.rock4", + "openrct2.music.progressive", - // Footpath surfaces - "rct2.footpath_surface.tarmac", - "rct2.footpath_surface.tarmac_brown", - "rct2.footpath_surface.tarmac_red", - "rct2.footpath_surface.dirt", - "rct2.footpath_surface.crazy_paving", - "rct2.footpath_surface.ash", - "rct2.footpath_surface.queue_blue", - "rct2.footpath_surface.queue_green", - "rct2.footpath_surface.queue_red", - "rct2.footpath_surface.queue_yellow", + // Footpath surfaces + "rct2.footpath_surface.tarmac", + "rct2.footpath_surface.tarmac_brown", + "rct2.footpath_surface.tarmac_red", + "rct2.footpath_surface.dirt", + "rct2.footpath_surface.crazy_paving", + "rct2.footpath_surface.ash", + "rct2.footpath_surface.queue_blue", + "rct2.footpath_surface.queue_green", + "rct2.footpath_surface.queue_red", + "rct2.footpath_surface.queue_yellow", - // Footpath railings - "rct2.footpath_railings.bamboo_black", - "rct2.footpath_railings.bamboo_brown", - "rct2.footpath_railings.concrete", - "rct2.footpath_railings.concrete_green", - "rct2.footpath_railings.space", - "rct2.footpath_railings.wood", + // Footpath railings + "rct2.footpath_railings.bamboo_black", + "rct2.footpath_railings.bamboo_brown", + "rct2.footpath_railings.concrete", + "rct2.footpath_railings.concrete_green", + "rct2.footpath_railings.space", + "rct2.footpath_railings.wood", - // Peep name objects - "rct2.peep_names.original", + // Peep name objects + "rct2.peep_names.original", - // Peep animation objects - "rct2.peep_animations.guest", - "rct2.peep_animations.handyman", - "rct2.peep_animations.mechanic", - "rct2.peep_animations.security", + // Peep animation objects + "rct2.peep_animations.guest", + "rct2.peep_animations.handyman", + "rct2.peep_animations.mechanic", + "rct2.peep_animations.security", - // Climate object - "rct2.climate.warm", -}; + // Climate object + "rct2.climate.warm", + }; -constexpr std::array kDefaultScenarioObjects = { - "rct2.ride.twist1", // Ride: Twist - "rct2.ride.ptct1", // Ride: Wooden Roller Coaster (Wooden Roller Coaster Trains) - "rct2.ride.zldb", // Ride: Junior Roller Coaster (Ladybird Trains) - "rct2.ride.lfb1", // Ride: Log Flume - "rct2.ride.vcr", // Ride: Vintage Cars - "rct2.ride.mgr1", // Ride: Merry-Go-Round - "rct2.ride.tlt1", // Ride: Toilet - "rct2.ride.atm1", // Ride: Cash Machine - "rct2.ride.faid1", // Ride: First Aid Room - "rct2.ride.infok", // Ride: Information Kiosk - "rct2.ride.drnks", // Ride: Drinks Stall - "rct2.ride.cndyf", // Ride: Candyfloss Stall - "rct2.ride.burgb", // Ride: Burger Bar - "rct2.ride.balln", // Ride: Balloon Stall - "rct2.ride.arrt1", // Ride: Corkscrew Roller Coaster - "rct2.ride.rboat", // Ride: Rowing Boats + constexpr std::array kDefaultScenarioObjects = { + "rct2.ride.twist1", // Ride: Twist + "rct2.ride.ptct1", // Ride: Wooden Roller Coaster (Wooden Roller Coaster Trains) + "rct2.ride.zldb", // Ride: Junior Roller Coaster (Ladybird Trains) + "rct2.ride.lfb1", // Ride: Log Flume + "rct2.ride.vcr", // Ride: Vintage Cars + "rct2.ride.mgr1", // Ride: Merry-Go-Round + "rct2.ride.tlt1", // Ride: Toilet + "rct2.ride.atm1", // Ride: Cash Machine + "rct2.ride.faid1", // Ride: First Aid Room + "rct2.ride.infok", // Ride: Information Kiosk + "rct2.ride.drnks", // Ride: Drinks Stall + "rct2.ride.cndyf", // Ride: Candyfloss Stall + "rct2.ride.burgb", // Ride: Burger Bar + "rct2.ride.balln", // Ride: Balloon Stall + "rct2.ride.arrt1", // Ride: Corkscrew Roller Coaster + "rct2.ride.rboat", // Ride: Rowing Boats - // The following are for all random map generation features to work out the box - "rct2.scenery_group.scgjungl", // Jungle Theming - "rct2.scenery_group.scgsnow", // Snow and Ice Theming - "rct2.scenery_group.scgwater", // Water Feature Theming + // The following are for all random map generation features to work out the box + "rct2.scenery_group.scgjungl", // Jungle Theming + "rct2.scenery_group.scgsnow", // Snow and Ice Theming + "rct2.scenery_group.scgwater", // Water Feature Theming - // Surfaces - "rct2.terrain_surface.sand", - "rct2.terrain_surface.dirt", - "rct2.terrain_surface.rock", - "rct2.terrain_surface.martian", - "rct2.terrain_surface.chequerboard", - "rct2.terrain_surface.grass_clumps", - "rct2.terrain_surface.ice", - "rct2.terrain_surface.grid_red", - "rct2.terrain_surface.grid_yellow", - "rct2.terrain_surface.grid_purple", - "rct2.terrain_surface.grid_green", - "rct2.terrain_surface.sand_red", - "rct2.terrain_surface.sand_brown", + // Surfaces + "rct2.terrain_surface.sand", + "rct2.terrain_surface.dirt", + "rct2.terrain_surface.rock", + "rct2.terrain_surface.martian", + "rct2.terrain_surface.chequerboard", + "rct2.terrain_surface.grass_clumps", + "rct2.terrain_surface.ice", + "rct2.terrain_surface.grid_red", + "rct2.terrain_surface.grid_yellow", + "rct2.terrain_surface.grid_purple", + "rct2.terrain_surface.grid_green", + "rct2.terrain_surface.sand_red", + "rct2.terrain_surface.sand_brown", - // Edges - "rct2.terrain_edge.wood_red", - "rct2.terrain_edge.wood_black", - "rct2.terrain_edge.ice", -}; + // Edges + "rct2.terrain_edge.wood_red", + "rct2.terrain_edge.wood_black", + "rct2.terrain_edge.ice", + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/DefaultObjects.h b/src/openrct2/object/DefaultObjects.h index 30261d23db..c28720c0ae 100644 --- a/src/openrct2/object/DefaultObjects.h +++ b/src/openrct2/object/DefaultObjects.h @@ -13,17 +13,20 @@ #include -/** - * Used by all editor modes: Scenario Editor, Track Designer and Track Designs Manager. - */ -extern const std::array kMinimumRequiredObjects; +namespace OpenRCT2 +{ + /** + * Used by all editor modes: Scenario Editor, Track Designer and Track Designs Manager. + */ + extern const std::array kMinimumRequiredObjects; -/** - * Used by the Scenario Editor and Track Designer. - */ -extern const std::array kCommonScenarioAndTrackDesignerObjects; + /** + * Used by the Scenario Editor and Track Designer. + */ + extern const std::array kCommonScenarioAndTrackDesignerObjects; -/** - * Used only by the Scenario Editor. - */ -extern const std::array kDefaultScenarioObjects; + /** + * Used only by the Scenario Editor. + */ + extern const std::array kDefaultScenarioObjects; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/EntranceEntry.h b/src/openrct2/object/EntranceEntry.h index 7c9a900a51..a3d8dd782f 100644 --- a/src/openrct2/object/EntranceEntry.h +++ b/src/openrct2/object/EntranceEntry.h @@ -12,10 +12,13 @@ #include "../localisation/StringIdType.h" #include "ObjectTypes.h" -struct EntranceEntry +namespace OpenRCT2 { - StringId string_idx; // 0x00 - uint32_t image_id; // 0x02 - uint8_t scrolling_mode; // 0x06 - uint8_t text_height; // 0x07 -}; + struct EntranceEntry + { + StringId string_idx; // 0x00 + uint32_t image_id; // 0x02 + uint8_t scrolling_mode; // 0x06 + uint8_t text_height; // 0x07 + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/EntranceObject.cpp b/src/openrct2/object/EntranceObject.cpp index 3ead6aae15..1e911c6b85 100644 --- a/src/openrct2/object/EntranceObject.cpp +++ b/src/openrct2/object/EntranceObject.cpp @@ -17,70 +17,71 @@ #include "../localisation/Language.h" #include "../paint/tile_element/Paint.TileElement.h" -using namespace OpenRCT2; - -void EntranceObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) +namespace OpenRCT2 { - stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.scrolling_mode = stream->ReadValue(); - _legacyType.text_height = stream->ReadValue(); - - GetStringTable().Read(context, stream, ObjectStringID::NAME); - GetImageTable().Read(context, stream); -} - -void EntranceObject::Load() -{ - GetStringTable().Sort(); - _legacyType.string_idx = LanguageAllocateObjectString(GetName()); - _legacyType.image_id = LoadImages(); -} - -void EntranceObject::Unload() -{ - LanguageFreeObjectString(_legacyType.string_idx); - UnloadImages(); - - _legacyType.string_idx = 0; - _legacyType.image_id = 0; -} - -void EntranceObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; - GfxDrawSprite(rt, ImageId(_legacyType.image_id + 1), screenCoords + ScreenCoordsXY{ -32, 14 }); - GfxDrawSprite(rt, ImageId(_legacyType.image_id + 0), screenCoords + ScreenCoordsXY{ 0, 28 }); - GfxDrawSprite(rt, ImageId(_legacyType.image_id + 2), screenCoords + ScreenCoordsXY{ 32, 44 }); -} - -void EntranceObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "EntranceObject::ReadJson expects parameter root to be object"); - - json_t properties = root["properties"]; - - if (properties.is_object()) + void EntranceObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) { - _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"]); - _legacyType.text_height = Json::GetNumber(properties["textHeight"]); + stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); + _legacyType.scrolling_mode = stream->ReadValue(); + _legacyType.text_height = stream->ReadValue(); + + GetStringTable().Read(context, stream, ObjectStringID::NAME); + GetImageTable().Read(context, stream); } - PopulateTablesFromJson(context, root); -} + void EntranceObject::Load() + { + GetStringTable().Sort(); + _legacyType.string_idx = LanguageAllocateObjectString(GetName()); + _legacyType.image_id = LoadImages(); + } -ImageIndex EntranceObject::GetImage(uint8_t sequence, Direction direction) const -{ - if (sequence > 2) - return kImageIndexUndefined; - return _legacyType.image_id + ((direction & 3) * 3) + sequence; -} + void EntranceObject::Unload() + { + LanguageFreeObjectString(_legacyType.string_idx); + UnloadImages(); -uint8_t EntranceObject::GetScrollingMode() const -{ - return _legacyType.scrolling_mode; -} + _legacyType.string_idx = 0; + _legacyType.image_id = 0; + } -uint8_t EntranceObject::GetTextHeight() const -{ - return _legacyType.text_height; -} + void EntranceObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; + GfxDrawSprite(rt, ImageId(_legacyType.image_id + 1), screenCoords + ScreenCoordsXY{ -32, 14 }); + GfxDrawSprite(rt, ImageId(_legacyType.image_id + 0), screenCoords + ScreenCoordsXY{ 0, 28 }); + GfxDrawSprite(rt, ImageId(_legacyType.image_id + 2), screenCoords + ScreenCoordsXY{ 32, 44 }); + } + + void EntranceObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "EntranceObject::ReadJson expects parameter root to be object"); + + json_t properties = root["properties"]; + + if (properties.is_object()) + { + _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"]); + _legacyType.text_height = Json::GetNumber(properties["textHeight"]); + } + + PopulateTablesFromJson(context, root); + } + + ImageIndex EntranceObject::GetImage(uint8_t sequence, Direction direction) const + { + if (sequence > 2) + return kImageIndexUndefined; + return _legacyType.image_id + ((direction & 3) * 3) + sequence; + } + + uint8_t EntranceObject::GetScrollingMode() const + { + return _legacyType.scrolling_mode; + } + + uint8_t EntranceObject::GetTextHeight() const + { + return _legacyType.text_height; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/EntranceObject.h b/src/openrct2/object/EntranceObject.h index 02b10092d6..c1cc6722db 100644 --- a/src/openrct2/object/EntranceObject.h +++ b/src/openrct2/object/EntranceObject.h @@ -14,27 +14,30 @@ #include "EntranceEntry.h" #include "Object.h" -class EntranceObject final : public Object +namespace OpenRCT2 { -private: - EntranceEntry _legacyType = {}; - -public: - static constexpr ObjectType kObjectType = ObjectType::parkEntrance; - - void* GetLegacyData() override + class EntranceObject final : public Object { - return &_legacyType; - } + private: + EntranceEntry _legacyType = {}; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + public: + static constexpr ObjectType kObjectType = ObjectType::parkEntrance; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void* GetLegacyData() override + { + return &_legacyType; + } - ImageIndex GetImage(uint8_t sequence, Direction direction) const; - uint8_t GetScrollingMode() const; - uint8_t GetTextHeight() const; -}; + void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + + ImageIndex GetImage(uint8_t sequence, Direction direction) const; + uint8_t GetScrollingMode() const; + uint8_t GetTextHeight() const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/FootpathEntry.h b/src/openrct2/object/FootpathEntry.h index 8d329c7a55..ae0fb0052c 100644 --- a/src/openrct2/object/FootpathEntry.h +++ b/src/openrct2/object/FootpathEntry.h @@ -14,42 +14,45 @@ enum class RailingEntrySupportType : uint8_t; -enum +namespace OpenRCT2 { - FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR = (1 << 2), - FOOTPATH_ENTRY_FLAG_IS_QUEUE = (1 << 3), - FOOTPATH_ENTRY_FLAG_NO_SLOPE_RAILINGS = (1 << 4), -}; + enum + { + FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR = (1 << 2), + FOOTPATH_ENTRY_FLAG_IS_QUEUE = (1 << 3), + FOOTPATH_ENTRY_FLAG_NO_SLOPE_RAILINGS = (1 << 4), + }; -struct FootpathEntry -{ - StringId string_idx; // 0x00 - uint32_t image; // 0x02 - uint32_t bridge_image; // 0x06 - RailingEntrySupportType support_type; // 0x0A - uint8_t flags; // 0x0B - uint8_t scrolling_mode; // 0x0C + struct FootpathEntry + { + StringId string_idx; // 0x00 + uint32_t image; // 0x02 + uint32_t bridge_image; // 0x06 + RailingEntrySupportType support_type; // 0x0A + uint8_t flags; // 0x0B + uint8_t scrolling_mode; // 0x0C - constexpr uint32_t GetQueueImage() const - { - return image + 51; - } - constexpr uint32_t GetPreviewImage() const - { - return image + 71; - } - constexpr uint32_t GetQueuePreviewImage() const - { - // Editor-only paths usually lack queue images. In this case, use the main path image. - if (flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR) + constexpr uint32_t GetQueueImage() const { - return GetPreviewImage(); + return image + 51; } + constexpr uint32_t GetPreviewImage() const + { + return image + 71; + } + constexpr uint32_t GetQueuePreviewImage() const + { + // Editor-only paths usually lack queue images. In this case, use the main path image. + if (flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR) + { + return GetPreviewImage(); + } - return image + 72; - } - constexpr uint32_t GetRailingsImage() const - { - return image + 73; - } -}; + return image + 72; + } + constexpr uint32_t GetRailingsImage() const + { + return image + 73; + } + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/FootpathObject.cpp b/src/openrct2/object/FootpathObject.cpp index 4efd6474eb..c83b6a4956 100644 --- a/src/openrct2/object/FootpathObject.cpp +++ b/src/openrct2/object/FootpathObject.cpp @@ -15,66 +15,69 @@ #include "../localisation/Language.h" #include "../world/Footpath.h" -void FootpathObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) +namespace OpenRCT2 { - stream->Seek(10, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.support_type = static_cast(stream->ReadValue()); - _legacyType.flags = stream->ReadValue(); - _legacyType.scrolling_mode = stream->ReadValue(); - stream->Seek(1, OpenRCT2::STREAM_SEEK_CURRENT); - - GetStringTable().Read(context, stream, ObjectStringID::NAME); - GetImageTable().Read(context, stream); - - // Validate properties - if (_legacyType.support_type >= RailingEntrySupportType::Count) + void FootpathObject::ReadLegacy(IReadObjectContext* context, IStream* stream) { - context->LogError(ObjectError::InvalidProperty, "RailingEntrySupportType not supported."); + stream->Seek(10, STREAM_SEEK_CURRENT); + _legacyType.support_type = static_cast(stream->ReadValue()); + _legacyType.flags = stream->ReadValue(); + _legacyType.scrolling_mode = stream->ReadValue(); + stream->Seek(1, STREAM_SEEK_CURRENT); + + GetStringTable().Read(context, stream, ObjectStringID::NAME); + GetImageTable().Read(context, stream); + + // Validate properties + if (_legacyType.support_type >= RailingEntrySupportType::Count) + { + context->LogError(ObjectError::InvalidProperty, "RailingEntrySupportType not supported."); + } } -} -void FootpathObject::Load() -{ - GetStringTable().Sort(); - _legacyType.string_idx = LanguageAllocateObjectString(GetName()); - _legacyType.image = LoadImages(); - _legacyType.bridge_image = _legacyType.image + 109; + void FootpathObject::Load() + { + GetStringTable().Sort(); + _legacyType.string_idx = LanguageAllocateObjectString(GetName()); + _legacyType.image = LoadImages(); + _legacyType.bridge_image = _legacyType.image + 109; - _pathSurfaceDescriptor.Name = _legacyType.string_idx; - _pathSurfaceDescriptor.Image = _legacyType.image; - _pathSurfaceDescriptor.PreviewImage = _legacyType.GetPreviewImage(); - _pathSurfaceDescriptor.Flags = _legacyType.flags; + _pathSurfaceDescriptor.Name = _legacyType.string_idx; + _pathSurfaceDescriptor.Image = _legacyType.image; + _pathSurfaceDescriptor.PreviewImage = _legacyType.GetPreviewImage(); + _pathSurfaceDescriptor.Flags = _legacyType.flags; - _queueSurfaceDescriptor.Name = _legacyType.string_idx; - _queueSurfaceDescriptor.Image = _legacyType.GetQueueImage(); - _queueSurfaceDescriptor.PreviewImage = _legacyType.GetQueuePreviewImage(); - _queueSurfaceDescriptor.Flags = _legacyType.flags | FOOTPATH_ENTRY_FLAG_IS_QUEUE; + _queueSurfaceDescriptor.Name = _legacyType.string_idx; + _queueSurfaceDescriptor.Image = _legacyType.GetQueueImage(); + _queueSurfaceDescriptor.PreviewImage = _legacyType.GetQueuePreviewImage(); + _queueSurfaceDescriptor.Flags = _legacyType.flags | FOOTPATH_ENTRY_FLAG_IS_QUEUE; - _pathRailingsDescriptor.Name = _legacyType.string_idx; - _pathRailingsDescriptor.BridgeImage = _legacyType.bridge_image; - _pathRailingsDescriptor.PreviewImage = _legacyType.GetPreviewImage(); - _pathRailingsDescriptor.Flags = _legacyType.flags; - _pathRailingsDescriptor.ScrollingMode = _legacyType.scrolling_mode; - _pathRailingsDescriptor.SupportType = _legacyType.support_type; - _pathRailingsDescriptor.RailingsImage = _legacyType.GetRailingsImage(); -} + _pathRailingsDescriptor.Name = _legacyType.string_idx; + _pathRailingsDescriptor.BridgeImage = _legacyType.bridge_image; + _pathRailingsDescriptor.PreviewImage = _legacyType.GetPreviewImage(); + _pathRailingsDescriptor.Flags = _legacyType.flags; + _pathRailingsDescriptor.ScrollingMode = _legacyType.scrolling_mode; + _pathRailingsDescriptor.SupportType = _legacyType.support_type; + _pathRailingsDescriptor.RailingsImage = _legacyType.GetRailingsImage(); + } -void FootpathObject::Unload() -{ - LanguageFreeObjectString(_legacyType.string_idx); - UnloadImages(); + void FootpathObject::Unload() + { + LanguageFreeObjectString(_legacyType.string_idx); + UnloadImages(); - _legacyType.string_idx = 0; - _legacyType.image = 0; -} + _legacyType.string_idx = 0; + _legacyType.image = 0; + } -void FootpathObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; - GfxDrawSprite(rt, ImageId(_pathSurfaceDescriptor.PreviewImage), screenCoords - ScreenCoordsXY{ 49, 17 }); - GfxDrawSprite(rt, ImageId(_queueSurfaceDescriptor.PreviewImage), screenCoords + ScreenCoordsXY{ 4, -17 }); -} + void FootpathObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; + GfxDrawSprite(rt, ImageId(_pathSurfaceDescriptor.PreviewImage), screenCoords - ScreenCoordsXY{ 49, 17 }); + GfxDrawSprite(rt, ImageId(_queueSurfaceDescriptor.PreviewImage), screenCoords + ScreenCoordsXY{ 4, -17 }); + } -void FootpathObject::ReadJson(IReadObjectContext* context, json_t& root) -{ -} + void FootpathObject::ReadJson(IReadObjectContext* context, json_t& root) + { + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/FootpathObject.h b/src/openrct2/object/FootpathObject.h index 23a0851aa4..fbfa964024 100644 --- a/src/openrct2/object/FootpathObject.h +++ b/src/openrct2/object/FootpathObject.h @@ -13,46 +13,49 @@ #include "FootpathEntry.h" #include "Object.h" -class FootpathObject final : public Object +namespace OpenRCT2 { -private: - FootpathEntry _legacyType = {}; - PathSurfaceDescriptor _pathSurfaceDescriptor = {}; - PathSurfaceDescriptor _queueSurfaceDescriptor = {}; - PathRailingsDescriptor _pathRailingsDescriptor = {}; - -public: - static constexpr ObjectType kObjectType = ObjectType::paths; - - void* GetLegacyData() override + class FootpathObject final : public Object { - return &_legacyType; - } + private: + FootpathEntry _legacyType = {}; + PathSurfaceDescriptor _pathSurfaceDescriptor = {}; + PathSurfaceDescriptor _queueSurfaceDescriptor = {}; + PathRailingsDescriptor _pathRailingsDescriptor = {}; - const void* GetLegacyData() const - { - return &_legacyType; - } + public: + static constexpr ObjectType kObjectType = ObjectType::paths; - const PathSurfaceDescriptor& GetPathSurfaceDescriptor() const - { - return _pathSurfaceDescriptor; - } + void* GetLegacyData() override + { + return &_legacyType; + } - const PathSurfaceDescriptor& GetQueueSurfaceDescriptor() const - { - return _queueSurfaceDescriptor; - } + const void* GetLegacyData() const + { + return &_legacyType; + } - const PathRailingsDescriptor& GetPathRailingsDescriptor() const - { - return _pathRailingsDescriptor; - } + const PathSurfaceDescriptor& GetPathSurfaceDescriptor() const + { + return _pathSurfaceDescriptor; + } - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + const PathSurfaceDescriptor& GetQueueSurfaceDescriptor() const + { + return _queueSurfaceDescriptor; + } - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; -}; + const PathRailingsDescriptor& GetPathRailingsDescriptor() const + { + return _pathRailingsDescriptor; + } + + void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/FootpathRailingsObject.cpp b/src/openrct2/object/FootpathRailingsObject.cpp index 350d4f3bdf..9784fe917e 100644 --- a/src/openrct2/object/FootpathRailingsObject.cpp +++ b/src/openrct2/object/FootpathRailingsObject.cpp @@ -13,99 +13,100 @@ #include "../core/IStream.hpp" #include "../core/Json.hpp" -using namespace OpenRCT2; - -void FootpathRailingsObject::Load() +namespace OpenRCT2 { - GetStringTable().Sort(); - NameStringId = LanguageAllocateObjectString(GetName()); - - auto numImages = GetImageTable().GetCount(); - if (numImages != 0) + void FootpathRailingsObject::Load() { - PreviewImageId = LoadImages(); - BridgeImageId = PreviewImageId + 37; - RailingsImageId = PreviewImageId + 1; - } + GetStringTable().Sort(); + NameStringId = LanguageAllocateObjectString(GetName()); - _descriptor.Name = NameStringId; - _descriptor.BridgeImage = BridgeImageId; - _descriptor.PreviewImage = PreviewImageId; - _descriptor.Flags = Flags; - _descriptor.ScrollingMode = ScrollingMode; - _descriptor.SupportType = SupportType; - _descriptor.SupportColour = Colour; - _descriptor.RailingsImage = RailingsImageId; -} - -void FootpathRailingsObject::Unload() -{ - LanguageFreeObjectString(NameStringId); - UnloadImages(); - - NameStringId = 0; - PreviewImageId = 0; - BridgeImageId = 0; - RailingsImageId = 0; -} - -void FootpathRailingsObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto x = width / 2; - auto y = height / 2; - auto helper = ImageId(kImageIndexUndefined); - if (Colour != COLOUR_NULL) - helper = helper.WithPrimary(Colour); - - if (SupportType == RailingEntrySupportType::Pole) - { - auto img = helper.WithIndex(BridgeImageId + 20 + 15); - for (int i = 0; i < 2; i++) + auto numImages = GetImageTable().GetCount(); + if (numImages != 0) { - auto h = i * 16; - GfxDrawSprite(rt, img, { x - 8, y + 8 + h }); - GfxDrawSprite(rt, img, { x + 8, y + 16 + h }); + PreviewImageId = LoadImages(); + BridgeImageId = PreviewImageId + 37; + RailingsImageId = PreviewImageId + 1; } - GfxDrawSprite(rt, helper.WithIndex(BridgeImageId + 5), { x, y - 17 }); - GfxDrawSprite(rt, ImageId(RailingsImageId + 1), { x + 4, y - 14 }); - GfxDrawSprite(rt, ImageId(RailingsImageId + 1), { x + 27, y - 2 }); + _descriptor.Name = NameStringId; + _descriptor.BridgeImage = BridgeImageId; + _descriptor.PreviewImage = PreviewImageId; + _descriptor.Flags = Flags; + _descriptor.ScrollingMode = ScrollingMode; + _descriptor.SupportType = SupportType; + _descriptor.SupportColour = Colour; + _descriptor.RailingsImage = RailingsImageId; } - else + + void FootpathRailingsObject::Unload() { - GfxDrawSprite(rt, helper.WithIndex(BridgeImageId + 22), { x + 0, y + 16 }); - GfxDrawSprite(rt, helper.WithIndex(BridgeImageId + 49), { x, y - 17 }); - GfxDrawSprite(rt, ImageId(RailingsImageId + 1), { x + 4, y - 14 }); - GfxDrawSprite(rt, ImageId(RailingsImageId + 1), { x + 27, y - 3 }); + LanguageFreeObjectString(NameStringId); + UnloadImages(); + + NameStringId = 0; + PreviewImageId = 0; + BridgeImageId = 0; + RailingsImageId = 0; } -} -void FootpathRailingsObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "FootpathObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - if (properties.is_object()) + void FootpathRailingsObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const { - SupportType = ParseSupportType(Json::GetString(properties["supportType"])); - ScrollingMode = Json::GetNumber(properties["scrollingMode"]); - Colour = Colour::FromString(Json::GetString(properties["colour"]), COLOUR_NULL); - Flags = Json::GetFlags( - properties, + auto x = width / 2; + auto y = height / 2; + auto helper = ImageId(kImageIndexUndefined); + if (Colour != COLOUR_NULL) + helper = helper.WithPrimary(Colour); + + if (SupportType == RailingEntrySupportType::Pole) + { + auto img = helper.WithIndex(BridgeImageId + 20 + 15); + for (int i = 0; i < 2; i++) { - { "hasSupportImages", RAILING_ENTRY_FLAG_HAS_SUPPORT_BASE_SPRITE }, - { "hasElevatedPathImages", RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS }, - { "noQueueBanner", RAILING_ENTRY_FLAG_NO_QUEUE_BANNER }, - }); + auto h = i * 16; + GfxDrawSprite(rt, img, { x - 8, y + 8 + h }); + GfxDrawSprite(rt, img, { x + 8, y + 16 + h }); + } + + GfxDrawSprite(rt, helper.WithIndex(BridgeImageId + 5), { x, y - 17 }); + GfxDrawSprite(rt, ImageId(RailingsImageId + 1), { x + 4, y - 14 }); + GfxDrawSprite(rt, ImageId(RailingsImageId + 1), { x + 27, y - 2 }); + } + else + { + GfxDrawSprite(rt, helper.WithIndex(BridgeImageId + 22), { x + 0, y + 16 }); + GfxDrawSprite(rt, helper.WithIndex(BridgeImageId + 49), { x, y - 17 }); + GfxDrawSprite(rt, ImageId(RailingsImageId + 1), { x + 4, y - 14 }); + GfxDrawSprite(rt, ImageId(RailingsImageId + 1), { x + 27, y - 3 }); + } } - PopulateTablesFromJson(context, root); -} + void FootpathRailingsObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "FootpathObject::ReadJson expects parameter root to be object"); -RailingEntrySupportType FootpathRailingsObject::ParseSupportType(std::string_view s) -{ - if (s == "pole") - return RailingEntrySupportType::Pole; - else /* if (s == "box") */ - return RailingEntrySupportType::Box; -} + auto properties = root["properties"]; + if (properties.is_object()) + { + SupportType = ParseSupportType(Json::GetString(properties["supportType"])); + ScrollingMode = Json::GetNumber(properties["scrollingMode"]); + Colour = Colour::FromString(Json::GetString(properties["colour"]), COLOUR_NULL); + Flags = Json::GetFlags( + properties, + { + { "hasSupportImages", RAILING_ENTRY_FLAG_HAS_SUPPORT_BASE_SPRITE }, + { "hasElevatedPathImages", RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS }, + { "noQueueBanner", RAILING_ENTRY_FLAG_NO_QUEUE_BANNER }, + }); + } + + PopulateTablesFromJson(context, root); + } + + RailingEntrySupportType FootpathRailingsObject::ParseSupportType(std::string_view s) + { + if (s == "pole") + return RailingEntrySupportType::Pole; + else /* if (s == "box") */ + return RailingEntrySupportType::Box; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/FootpathRailingsObject.h b/src/openrct2/object/FootpathRailingsObject.h index f4bbe95706..46e960ee08 100644 --- a/src/openrct2/object/FootpathRailingsObject.h +++ b/src/openrct2/object/FootpathRailingsObject.h @@ -12,33 +12,36 @@ #include "../world/Footpath.h" #include "Object.h" -class FootpathRailingsObject final : public Object +namespace OpenRCT2 { -public: - StringId NameStringId{}; - uint32_t PreviewImageId{}; - uint32_t BridgeImageId{}; - uint32_t RailingsImageId{}; - RailingEntrySupportType SupportType{}; - uint8_t Flags{}; - uint8_t ScrollingMode{}; - colour_t Colour{}; - PathRailingsDescriptor _descriptor = {}; - -public: - static constexpr ObjectType kObjectType = ObjectType::footpathRailings; - - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; - - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; - - const PathRailingsDescriptor& GetDescriptor() const + class FootpathRailingsObject final : public Object { - return _descriptor; - } + public: + StringId NameStringId{}; + uint32_t PreviewImageId{}; + uint32_t BridgeImageId{}; + uint32_t RailingsImageId{}; + RailingEntrySupportType SupportType{}; + uint8_t Flags{}; + uint8_t ScrollingMode{}; + colour_t Colour{}; + PathRailingsDescriptor _descriptor = {}; -private: - RailingEntrySupportType ParseSupportType(std::string_view s); -}; + public: + static constexpr ObjectType kObjectType = ObjectType::footpathRailings; + + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + + const PathRailingsDescriptor& GetDescriptor() const + { + return _descriptor; + } + + private: + RailingEntrySupportType ParseSupportType(std::string_view s); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/FootpathSurfaceObject.cpp b/src/openrct2/object/FootpathSurfaceObject.cpp index e81f1cfbb2..5348c585b8 100644 --- a/src/openrct2/object/FootpathSurfaceObject.cpp +++ b/src/openrct2/object/FootpathSurfaceObject.cpp @@ -16,64 +16,65 @@ #include "FootpathEntry.h" #include "ObjectRepository.h" -using namespace OpenRCT2; - -void FootpathSurfaceObject::Load() +namespace OpenRCT2 { - GetStringTable().Sort(); - NameStringId = LanguageAllocateObjectString(GetName()); - - auto numImages = GetImageTable().GetCount(); - if (numImages != 0) + void FootpathSurfaceObject::Load() { - PreviewImageId = LoadImages(); - BaseImageId = PreviewImageId + 1; + GetStringTable().Sort(); + NameStringId = LanguageAllocateObjectString(GetName()); + + auto numImages = GetImageTable().GetCount(); + if (numImages != 0) + { + PreviewImageId = LoadImages(); + BaseImageId = PreviewImageId + 1; + } + + _descriptor.Name = NameStringId; + _descriptor.Image = BaseImageId; + _descriptor.PreviewImage = PreviewImageId; + _descriptor.Flags = Flags; } - _descriptor.Name = NameStringId; - _descriptor.Image = BaseImageId; - _descriptor.PreviewImage = PreviewImageId; - _descriptor.Flags = Flags; -} - -void FootpathSurfaceObject::Unload() -{ - LanguageFreeObjectString(NameStringId); - UnloadImages(); - - NameStringId = 0; - PreviewImageId = 0; - BaseImageId = 0; -} - -void FootpathSurfaceObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2 - 16, height / 2 }; - GfxDrawSprite(rt, ImageId(BaseImageId + 3), screenCoords); - GfxDrawSprite(rt, ImageId(BaseImageId + 16), { screenCoords.x + 32, screenCoords.y - 16 }); - GfxDrawSprite(rt, ImageId(BaseImageId + 8), { screenCoords.x + 32, screenCoords.y + 16 }); -} - -void FootpathSurfaceObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "FootpathSurfaceObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - if (properties.is_object()) + void FootpathSurfaceObject::Unload() { - Flags = Json::GetFlags( - properties, - { - { "editorOnly", FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR }, - { "isQueue", FOOTPATH_ENTRY_FLAG_IS_QUEUE }, - { "noSlopeRailings", FOOTPATH_ENTRY_FLAG_NO_SLOPE_RAILINGS }, - }); + LanguageFreeObjectString(NameStringId); + UnloadImages(); + + NameStringId = 0; + PreviewImageId = 0; + BaseImageId = 0; } - PopulateTablesFromJson(context, root); -} + void FootpathSurfaceObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto screenCoords = ScreenCoordsXY{ width / 2 - 16, height / 2 }; + GfxDrawSprite(rt, ImageId(BaseImageId + 3), screenCoords); + GfxDrawSprite(rt, ImageId(BaseImageId + 16), { screenCoords.x + 32, screenCoords.y - 16 }); + GfxDrawSprite(rt, ImageId(BaseImageId + 8), { screenCoords.x + 32, screenCoords.y + 16 }); + } -void FootpathSurfaceObject::SetRepositoryItem(ObjectRepositoryItem* item) const -{ - item->FootpathSurfaceInfo.Flags = Flags; -} + void FootpathSurfaceObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "FootpathSurfaceObject::ReadJson expects parameter root to be object"); + + auto properties = root["properties"]; + if (properties.is_object()) + { + Flags = Json::GetFlags( + properties, + { + { "editorOnly", FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR }, + { "isQueue", FOOTPATH_ENTRY_FLAG_IS_QUEUE }, + { "noSlopeRailings", FOOTPATH_ENTRY_FLAG_NO_SLOPE_RAILINGS }, + }); + } + + PopulateTablesFromJson(context, root); + } + + void FootpathSurfaceObject::SetRepositoryItem(ObjectRepositoryItem* item) const + { + item->FootpathSurfaceInfo.Flags = Flags; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/FootpathSurfaceObject.h b/src/openrct2/object/FootpathSurfaceObject.h index 0ec65938c7..73089ae4f8 100644 --- a/src/openrct2/object/FootpathSurfaceObject.h +++ b/src/openrct2/object/FootpathSurfaceObject.h @@ -12,28 +12,31 @@ #include "../world/Footpath.h" #include "Object.h" -class FootpathSurfaceObject final : public Object +namespace OpenRCT2 { -public: - StringId NameStringId{}; - uint32_t PreviewImageId{}; - uint32_t BaseImageId{}; - uint8_t Flags{}; - PathSurfaceDescriptor _descriptor = {}; - -public: - static constexpr ObjectType kObjectType = ObjectType::footpathSurface; - - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; - - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; - - void SetRepositoryItem(ObjectRepositoryItem* item) const override; - - const PathSurfaceDescriptor& GetDescriptor() const + class FootpathSurfaceObject final : public Object { - return _descriptor; - } -}; + public: + StringId NameStringId{}; + uint32_t PreviewImageId{}; + uint32_t BaseImageId{}; + uint8_t Flags{}; + PathSurfaceDescriptor _descriptor = {}; + + public: + static constexpr ObjectType kObjectType = ObjectType::footpathSurface; + + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + + void SetRepositoryItem(ObjectRepositoryItem* item) const override; + + const PathSurfaceDescriptor& GetDescriptor() const + { + return _descriptor; + } + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ImageTable.cpp b/src/openrct2/object/ImageTable.cpp index 873e8ec0c8..cb0ba7631f 100644 --- a/src/openrct2/object/ImageTable.cpp +++ b/src/openrct2/object/ImageTable.cpp @@ -28,230 +28,295 @@ #include #include -using namespace OpenRCT2; -using namespace OpenRCT2::Drawing; - -static thread_local std::map> _objDataCache = {}; - -struct ImageTable::RequiredImage +namespace OpenRCT2 { - G1Element g1{}; - std::unique_ptr next_zoom; + static thread_local std::map> _objDataCache = {}; - bool HasData() const + struct ImageTable::RequiredImage { - return g1.offset != nullptr; - } + G1Element g1{}; + std::unique_ptr next_zoom; - RequiredImage() = default; - RequiredImage(const RequiredImage&) = delete; - - RequiredImage(const G1Element& orig) - { - auto length = G1CalculateDataSize(&orig); - g1 = orig; - g1.offset = new uint8_t[length]; - std::memcpy(g1.offset, orig.offset, length); - g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; - } - - RequiredImage(uint32_t idx, std::function getter) - { - auto orig = getter(idx); - if (orig != nullptr) + bool HasData() const { - auto length = G1CalculateDataSize(orig); - g1 = *orig; + return g1.offset != nullptr; + } + + RequiredImage() = default; + RequiredImage(const RequiredImage&) = delete; + + RequiredImage(const G1Element& orig) + { + auto length = G1CalculateDataSize(&orig); + g1 = orig; g1.offset = new uint8_t[length]; - std::memcpy(g1.offset, orig->offset, length); - if ((g1.flags & G1_FLAG_HAS_ZOOM_SPRITE) && g1.zoomed_offset != 0) + std::memcpy(g1.offset, orig.offset, length); + g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; + } + + RequiredImage(uint32_t idx, std::function getter) + { + auto orig = getter(idx); + if (orig != nullptr) { - // Fetch image for next zoom level - next_zoom = std::make_unique(static_cast(idx - g1.zoomed_offset), getter); - if (!next_zoom->HasData()) + auto length = G1CalculateDataSize(orig); + g1 = *orig; + g1.offset = new uint8_t[length]; + std::memcpy(g1.offset, orig->offset, length); + if ((g1.flags & G1_FLAG_HAS_ZOOM_SPRITE) && g1.zoomed_offset != 0) { - next_zoom = nullptr; - g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; + // Fetch image for next zoom level + next_zoom = std::make_unique(static_cast(idx - g1.zoomed_offset), getter); + if (!next_zoom->HasData()) + { + next_zoom = nullptr; + g1.flags &= ~G1_FLAG_HAS_ZOOM_SPRITE; + } } } } - } - ~RequiredImage() - { - delete[] g1.offset; - } -}; - -std::vector> ImageTable::ParseImages(IReadObjectContext* context, std::string s) -{ - std::vector> result; - if (s.empty()) - { - result.push_back(std::make_unique()); - } - else if (String::startsWith(s, "$CSG")) - { - auto rangeStart = s.find('['); - auto rangeEnd = s.find(']'); - if (rangeStart != std::string::npos && rangeEnd != std::string::npos) + ~RequiredImage() { - auto rangeString = s.substr(rangeStart, rangeEnd - rangeStart + 1); - auto range = ParseRange(rangeString); - if (IsCsgLoaded()) + delete[] g1.offset; + } + }; + + std::vector> ImageTable::ParseImages(IReadObjectContext* context, std::string s) + { + std::vector> result; + if (s.empty()) + { + result.push_back(std::make_unique()); + } + else if (String::startsWith(s, "$CSG")) + { + auto rangeStart = s.find('['); + auto rangeEnd = s.find(']'); + if (rangeStart != std::string::npos && rangeEnd != std::string::npos) { + auto rangeString = s.substr(rangeStart, rangeEnd - rangeStart + 1); + auto range = ParseRange(rangeString); + if (IsCsgLoaded()) + { + for (auto i : range) + { + result.push_back( + std::make_unique( + static_cast(SPR_CSG_BEGIN + i), + [](uint32_t idx) -> const G1Element* { return GfxGetG1Element(idx); })); + } + } + else + { + std::string id(context->GetObjectIdentifier()); + LOG_WARNING("CSG not loaded inserting placeholder images for %s", id.c_str()); + result.resize(range.size()); + for (auto& res : result) + { + res = std::make_unique(); + } + } + } + } + else if (String::startsWith(s, "$G1")) + { + auto rangeStart = s.find('['); + auto rangeEnd = s.find(']'); + if (rangeStart != std::string::npos && rangeEnd != std::string::npos) + { + auto rangeString = s.substr(rangeStart, rangeEnd - rangeStart + 1); + auto range = ParseRange(rangeString); for (auto i : range) { result.push_back( std::make_unique( - static_cast(SPR_CSG_BEGIN + i), - [](uint32_t idx) -> const G1Element* { return GfxGetG1Element(idx); })); + static_cast(i), [](uint32_t idx) -> const G1Element* { return GfxGetG1Element(idx); })); } } + } + else if (String::startsWith(s, "$RCT2:OBJDATA/")) + { + auto name = s.substr(14); + auto rangeStart = name.find('['); + auto rangeEnd = name.find(']'); + if (rangeStart != std::string::npos && rangeEnd != std::string::npos) + { + auto rangeString = name.substr(rangeStart, rangeEnd - rangeStart + 1); + auto range = ParseRange(rangeString); + name = name.substr(0, rangeStart); + result = LoadObjectImages(context, name, range); + } + } + else if (String::startsWith(s, "$LGX:")) + { + auto name = s.substr(5); + auto rangeStart = name.find('['); + auto rangeEnd = name.find(']'); + if (rangeStart != std::string::npos && rangeEnd != std::string::npos) + { + auto rangeString = name.substr(rangeStart, rangeEnd - rangeStart + 1); + auto range = ParseRange(rangeString); + name = name.substr(0, rangeStart); + result = LoadImageArchiveImages(context, name, range); + } else { - std::string id(context->GetObjectIdentifier()); - LOG_WARNING("CSG not loaded inserting placeholder images for %s", id.c_str()); - result.resize(range.size()); - for (auto& res : result) - { - res = std::make_unique(); - } + result = LoadImageArchiveImages(context, name); } } - } - else if (String::startsWith(s, "$G1")) - { - auto rangeStart = s.find('['); - auto rangeEnd = s.find(']'); - if (rangeStart != std::string::npos && rangeEnd != std::string::npos) - { - auto rangeString = s.substr(rangeStart, rangeEnd - rangeStart + 1); - auto range = ParseRange(rangeString); - for (auto i : range) - { - result.push_back( - std::make_unique( - static_cast(i), [](uint32_t idx) -> const G1Element* { return GfxGetG1Element(idx); })); - } - } - } - else if (String::startsWith(s, "$RCT2:OBJDATA/")) - { - auto name = s.substr(14); - auto rangeStart = name.find('['); - auto rangeEnd = name.find(']'); - if (rangeStart != std::string::npos && rangeEnd != std::string::npos) - { - auto rangeString = name.substr(rangeStart, rangeEnd - rangeStart + 1); - auto range = ParseRange(rangeString); - name = name.substr(0, rangeStart); - result = LoadObjectImages(context, name, range); - } - } - else if (String::startsWith(s, "$LGX:")) - { - auto name = s.substr(5); - auto rangeStart = name.find('['); - auto rangeEnd = name.find(']'); - if (rangeStart != std::string::npos && rangeEnd != std::string::npos) - { - auto rangeString = name.substr(rangeStart, rangeEnd - rangeStart + 1); - auto range = ParseRange(rangeString); - name = name.substr(0, rangeStart); - result = LoadImageArchiveImages(context, name, range); - } else { - result = LoadImageArchiveImages(context, name); + try + { + auto imageData = context->GetData(s); + auto image = Imaging::ReadFromBuffer(imageData); + auto meta = Drawing::ImageImportMeta{}; + + Drawing::ImageImporter importer; + auto importResult = importer.Import(image, meta); + + result.push_back(std::make_unique(importResult.Element)); + } + catch (const std::exception& e) + { + auto msg = String::stdFormat("Unable to load image '%s': %s", s.c_str(), e.what()); + context->LogWarning(ObjectError::BadImageTable, msg.c_str()); + result.push_back(std::make_unique()); + } } + return result; } - else + + std::vector> ImageTable::ParseImages( + IReadObjectContext* context, std::vector>& imageSources, json_t& el) { + Guard::Assert(el.is_object(), "ImageTable::ParseImages expects parameter el to be object"); + + auto path = Json::GetString(el["path"]); + auto meta = Drawing::createImageImportMetaFromJson(el); + + std::vector> result; try { - auto imageData = context->GetData(s); - auto image = Imaging::ReadFromBuffer(imageData); - auto meta = ImageImportMeta{}; + auto itSource = std::find_if( + imageSources.begin(), imageSources.end(), + [&path](const std::pair& item) { return item.first == path; }); + if (itSource == imageSources.end()) + { + throw std::runtime_error("Unable to find image in image source list."); + } + auto& image = itSource->second; - ImageImporter importer; + Drawing::ImageImporter importer; auto importResult = importer.Import(image, meta); - - result.push_back(std::make_unique(importResult.Element)); + auto g1element = importResult.Element; + result.push_back(std::make_unique(g1element)); } catch (const std::exception& e) { - auto msg = String::stdFormat("Unable to load image '%s': %s", s.c_str(), e.what()); + auto msg = String::stdFormat("Unable to load image '%s': %s", path.c_str(), e.what()); context->LogWarning(ObjectError::BadImageTable, msg.c_str()); result.push_back(std::make_unique()); } + return result; } - return result; -} -std::vector> ImageTable::ParseImages( - IReadObjectContext* context, std::vector>& imageSources, json_t& el) -{ - Guard::Assert(el.is_object(), "ImageTable::ParseImages expects parameter el to be object"); - - auto path = Json::GetString(el["path"]); - auto meta = createImageImportMetaFromJson(el); - - std::vector> result; - try + std::vector> ImageTable::LoadImageArchiveImages( + IReadObjectContext* context, const std::string& path, const std::vector& range) { - auto itSource = std::find_if( - imageSources.begin(), imageSources.end(), - [&path](const std::pair& item) { return item.first == path; }); - if (itSource == imageSources.end()) + std::vector> result; + auto gxRaw = context->GetData(path); + std::optional gxData = GfxLoadGx(gxRaw); + if (gxData.has_value()) { - throw std::runtime_error("Unable to find image in image source list."); - } - auto& image = itSource->second; - - ImageImporter importer; - auto importResult = importer.Import(image, meta); - auto g1element = importResult.Element; - result.push_back(std::make_unique(g1element)); - } - catch (const std::exception& e) - { - auto msg = String::stdFormat("Unable to load image '%s': %s", path.c_str(), e.what()); - context->LogWarning(ObjectError::BadImageTable, msg.c_str()); - result.push_back(std::make_unique()); - } - return result; -} - -std::vector> ImageTable::LoadImageArchiveImages( - IReadObjectContext* context, const std::string& path, const std::vector& range) -{ - std::vector> result; - auto gxRaw = context->GetData(path); - std::optional gxData = GfxLoadGx(gxRaw); - if (gxData.has_value()) - { - // Fix entry data offsets - for (uint32_t i = 0; i < gxData->header.num_entries; i++) - { - if (gxData->elements[i].offset == nullptr) + // Fix entry data offsets + for (uint32_t i = 0; i < gxData->header.num_entries; i++) { - gxData->elements[i].offset = gxData->data.get(); + if (gxData->elements[i].offset == nullptr) + { + gxData->elements[i].offset = gxData->data.get(); + } + else + { + gxData->elements[i].offset += reinterpret_cast(gxData->data.get()); + } + } + + if (range.size() > 0) + { + size_t placeHoldersAdded = 0; + for (auto i : range) + { + if (i >= 0 && (i < static_cast(gxData->header.num_entries))) + { + result.push_back(std::make_unique(gxData->elements[i])); + } + else + { + result.push_back(std::make_unique()); + placeHoldersAdded++; + } + } + + // Log place holder information + if (placeHoldersAdded > 0) + { + std::string msg = "Adding " + std::to_string(placeHoldersAdded) + " placeholders"; + context->LogWarning(ObjectError::InvalidProperty, msg.c_str()); + } } else { - gxData->elements[i].offset += reinterpret_cast(gxData->data.get()); + for (int i = 0; i < static_cast(gxData->header.num_entries); i++) + result.push_back(std::make_unique(gxData->elements[i])); } } - - if (range.size() > 0) + else { + auto msg = String::stdFormat("Unable to load Gx '%s'", path.c_str()); + context->LogWarning(ObjectError::BadImageTable, msg.c_str()); + for (size_t i = 0; i < range.size(); i++) + { + result.push_back(std::make_unique()); + } + } + return result; + } + + std::vector> ImageTable::LoadObjectImages( + IReadObjectContext* context, const std::string& name, const std::vector& range) + { + std::vector> result; + Object* obj; + + auto cached = _objDataCache.find(name); + if (cached != _objDataCache.end()) + { + obj = cached->second.get(); + } + else + { + auto objectPath = FindLegacyObject(name); + auto tmp = ObjectFactory::CreateObjectFromLegacyFile( + context->GetObjectRepository(), objectPath.c_str(), !gOpenRCT2NoGraphics); + auto inserted = _objDataCache.insert({ name, std::move(tmp) }); + obj = inserted.first->second.get(); + } + + if (obj != nullptr) + { + auto& imgTable = static_cast(obj)->GetImageTable(); + auto numImages = static_cast(imgTable.GetCount()); + auto images = imgTable.GetImages(); size_t placeHoldersAdded = 0; for (auto i : range) { - if (i >= 0 && (i < static_cast(gxData->header.num_entries))) + if (i >= 0 && i < numImages) { - result.push_back(std::make_unique(gxData->elements[i])); + result.push_back( + std::make_unique( + static_cast(i), [images](uint32_t idx) -> const G1Element* { return &images[idx]; })); } else { @@ -269,378 +334,313 @@ std::vector> ImageTable::LoadImageArc } else { - for (int i = 0; i < static_cast(gxData->header.num_entries); i++) - result.push_back(std::make_unique(gxData->elements[i])); - } - } - else - { - auto msg = String::stdFormat("Unable to load Gx '%s'", path.c_str()); - context->LogWarning(ObjectError::BadImageTable, msg.c_str()); - for (size_t i = 0; i < range.size(); i++) - { - result.push_back(std::make_unique()); - } - } - return result; -} - -std::vector> ImageTable::LoadObjectImages( - IReadObjectContext* context, const std::string& name, const std::vector& range) -{ - std::vector> result; - Object* obj; - - auto cached = _objDataCache.find(name); - if (cached != _objDataCache.end()) - { - obj = cached->second.get(); - } - else - { - auto objectPath = FindLegacyObject(name); - auto tmp = ObjectFactory::CreateObjectFromLegacyFile( - context->GetObjectRepository(), objectPath.c_str(), !gOpenRCT2NoGraphics); - auto inserted = _objDataCache.insert({ name, std::move(tmp) }); - obj = inserted.first->second.get(); - } - - if (obj != nullptr) - { - auto& imgTable = static_cast(obj)->GetImageTable(); - auto numImages = static_cast(imgTable.GetCount()); - auto images = imgTable.GetImages(); - size_t placeHoldersAdded = 0; - for (auto i : range) - { - if (i >= 0 && i < numImages) - { - result.push_back( - std::make_unique( - static_cast(i), [images](uint32_t idx) -> const G1Element* { return &images[idx]; })); - } - else + std::string msg = "Unable to open '" + name + "'"; + context->LogWarning(ObjectError::InvalidProperty, msg.c_str()); + for (size_t i = 0; i < range.size(); i++) { result.push_back(std::make_unique()); - placeHoldersAdded++; } } - - // Log place holder information - if (placeHoldersAdded > 0) - { - std::string msg = "Adding " + std::to_string(placeHoldersAdded) + " placeholders"; - context->LogWarning(ObjectError::InvalidProperty, msg.c_str()); - } + return result; } - else - { - std::string msg = "Unable to open '" + name + "'"; - context->LogWarning(ObjectError::InvalidProperty, msg.c_str()); - for (size_t i = 0; i < range.size(); i++) - { - result.push_back(std::make_unique()); - } - } - return result; -} -std::vector ImageTable::ParseRange(std::string s) -{ - // Currently only supports [###] or [###..###] - std::vector result = {}; - if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') + std::vector ImageTable::ParseRange(std::string s) { - s = s.substr(1, s.length() - 2); - auto parts = String::split(s, ".."); - if (parts.size() == 1) + // Currently only supports [###] or [###..###] + std::vector result = {}; + if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') { - result.push_back(std::stoi(parts[0])); - } - else - { - auto left = std::stoi(parts[0]); - auto right = std::stoi(parts[1]); - if (left <= right) + s = s.substr(1, s.length() - 2); + auto parts = String::split(s, ".."); + if (parts.size() == 1) { - for (auto i = left; i <= right; i++) - { - result.push_back(i); - } + result.push_back(std::stoi(parts[0])); } else { - for (auto i = right; i >= left; i--) + auto left = std::stoi(parts[0]); + auto right = std::stoi(parts[1]); + if (left <= right) { - result.push_back(i); - } - } - } - } - return result; -} - -std::string ImageTable::FindLegacyObject(const std::string& name) -{ - const auto& env = GetContext()->GetPlatformEnvironment(); - auto objectsPath = env.GetDirectoryPath(DirBase::rct2, DirId::objects); - auto objectPath = Path::Combine(objectsPath, name); - if (File::Exists(objectPath)) - { - return objectPath; - } - - std::string altName = name; - auto rangeStart = name.find(".DAT"); - if (rangeStart != std::string::npos) - { - altName.replace(rangeStart, 4, ".POB"); - } - objectPath = Path::Combine(objectsPath, altName); - if (File::Exists(objectPath)) - { - return objectPath; - } - - if (!File::Exists(objectPath)) - { - // Search recursively for any file with the target name (case insensitive) - auto filter = Path::Combine(objectsPath, u8"*.dat;*.pob"); - auto scanner = Path::ScanDirectory(filter, true); - while (scanner->Next()) - { - auto currentName = Path::GetFileName(scanner->GetPathRelative()); - if (String::iequals(currentName, name) || String::iequals(currentName, altName)) - { - objectPath = scanner->GetPath(); - break; - } - } - } - return objectPath; -} - -ImageTable::~ImageTable() -{ - if (_data == nullptr) - { - for (auto& entry : _entries) - { - delete[] entry.offset; - } - } -} - -void ImageTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream) -{ - if (gOpenRCT2NoGraphics) - { - return; - } - - try - { - uint32_t numImages = stream->ReadValue(); - uint32_t imageDataSize = stream->ReadValue(); - - uint64_t headerTableSize = numImages * 16; - uint64_t remainingBytes = stream->GetLength() - stream->GetPosition() - headerTableSize; - if (remainingBytes > imageDataSize) - { - context->LogVerbose(ObjectError::BadImageTable, "Image table size longer than expected."); - imageDataSize = static_cast(remainingBytes); - } - - auto dataSize = static_cast(imageDataSize); - auto data = std::make_unique(dataSize); - if (data == nullptr) - { - context->LogError(ObjectError::BadImageTable, "Image table too large."); - throw std::runtime_error("Image table too large."); - } - - // Read g1 element headers - uintptr_t imageDataBase = reinterpret_cast(data.get()); - std::vector newEntries; - for (uint32_t i = 0; i < numImages; i++) - { - G1Element g1Element{}; - - uintptr_t imageDataOffset = static_cast(stream->ReadValue()); - g1Element.offset = reinterpret_cast(imageDataBase + imageDataOffset); - - g1Element.width = stream->ReadValue(); - g1Element.height = stream->ReadValue(); - g1Element.x_offset = stream->ReadValue(); - g1Element.y_offset = stream->ReadValue(); - g1Element.flags = stream->ReadValue(); - g1Element.zoomed_offset = stream->ReadValue(); - - newEntries.push_back(std::move(g1Element)); - } - - // Read g1 element data - size_t readBytes = static_cast(stream->TryRead(data.get(), dataSize)); - - // If data is shorter than expected (some custom objects are unfortunately like that) - size_t unreadBytes = dataSize - readBytes; - if (unreadBytes > 0) - { - std::fill_n(data.get() + readBytes, unreadBytes, 0); - context->LogWarning(ObjectError::BadImageTable, "Image table size shorter than expected."); - } - - _data = std::move(data); - _entries.insert(_entries.end(), newEntries.begin(), newEntries.end()); - } - catch (const std::exception&) - { - context->LogError(ObjectError::BadImageTable, "Bad image table."); - throw; - } -} - -std::vector> ImageTable::GetImageSources(IReadObjectContext* context, json_t& jsonImages) -{ - std::vector> result; - for (auto& jsonImage : jsonImages) - { - if (jsonImage.is_object() && jsonImage.contains("path")) - { - auto path = Json::GetString(jsonImage["path"]); - auto keepPalette = Json::GetString(jsonImage["palette"]) == "keep"; - auto itSource = std::find_if(result.begin(), result.end(), [&path](const std::pair& item) { - return item.first == path; - }); - if (itSource == result.end()) - { - auto imageData = context->GetData(path); - auto imageFormat = keepPalette ? ImageFormat::png : ImageFormat::png32; - auto image = Imaging::ReadFromBuffer(imageData, imageFormat); - auto pair = std::make_pair(std::move(path), std::move(image)); - result.push_back(std::move(pair)); - } - } - } - return result; -} - -bool ImageTable::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "ImageTable::ReadJson expects parameter root to be object"); - - bool usesFallbackSprites = false; - - if (context->ShouldLoadImages()) - { - // First gather all the required images from inspecting the JSON - std::vector> allImages; - auto jsonImages = root["images"]; - if (!IsCsgLoaded() && root.contains("noCsgImages")) - { - jsonImages = root["noCsgImages"]; - usesFallbackSprites = true; - } - - auto imageSources = GetImageSources(context, jsonImages); - - for (auto& jsonImage : jsonImages) - { - if (jsonImage.is_string()) - { - auto strImage = jsonImage.get(); - auto images = ParseImages(context, strImage); - allImages.insert( - allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); - } - else if (jsonImage.is_object()) - { - if (jsonImage.contains("gx")) - { - auto xOverride = Json::GetNumber(jsonImage["x"], std::numeric_limits::max()); - auto yOverride = Json::GetNumber(jsonImage["y"], std::numeric_limits::max()); - const bool hasXOverride = xOverride != std::numeric_limits::max(); - const bool hasYOverride = yOverride != std::numeric_limits::max(); - - auto strImage = jsonImage["gx"].get(); - auto images = ParseImages(context, strImage); - - if (hasXOverride || hasYOverride) + for (auto i = left; i <= right; i++) { - for (auto& image : images) - { - if (hasXOverride) - image->g1.x_offset = xOverride; - if (hasYOverride) - image->g1.y_offset = yOverride; - } + result.push_back(i); } - - allImages.insert( - allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); } else { - auto images = ParseImages(context, imageSources, jsonImage); + for (auto i = right; i >= left; i--) + { + result.push_back(i); + } + } + } + } + return result; + } + + std::string ImageTable::FindLegacyObject(const std::string& name) + { + const auto& env = GetContext()->GetPlatformEnvironment(); + auto objectsPath = env.GetDirectoryPath(DirBase::rct2, DirId::objects); + auto objectPath = Path::Combine(objectsPath, name); + if (File::Exists(objectPath)) + { + return objectPath; + } + + std::string altName = name; + auto rangeStart = name.find(".DAT"); + if (rangeStart != std::string::npos) + { + altName.replace(rangeStart, 4, ".POB"); + } + objectPath = Path::Combine(objectsPath, altName); + if (File::Exists(objectPath)) + { + return objectPath; + } + + if (!File::Exists(objectPath)) + { + // Search recursively for any file with the target name (case insensitive) + auto filter = Path::Combine(objectsPath, u8"*.dat;*.pob"); + auto scanner = Path::ScanDirectory(filter, true); + while (scanner->Next()) + { + auto currentName = Path::GetFileName(scanner->GetPathRelative()); + if (String::iequals(currentName, name) || String::iequals(currentName, altName)) + { + objectPath = scanner->GetPath(); + break; + } + } + } + return objectPath; + } + + ImageTable::~ImageTable() + { + if (_data == nullptr) + { + for (auto& entry : _entries) + { + delete[] entry.offset; + } + } + } + + void ImageTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream) + { + if (gOpenRCT2NoGraphics) + { + return; + } + + try + { + uint32_t numImages = stream->ReadValue(); + uint32_t imageDataSize = stream->ReadValue(); + + uint64_t headerTableSize = numImages * 16; + uint64_t remainingBytes = stream->GetLength() - stream->GetPosition() - headerTableSize; + if (remainingBytes > imageDataSize) + { + context->LogVerbose(ObjectError::BadImageTable, "Image table size longer than expected."); + imageDataSize = static_cast(remainingBytes); + } + + auto dataSize = static_cast(imageDataSize); + auto data = std::make_unique(dataSize); + if (data == nullptr) + { + context->LogError(ObjectError::BadImageTable, "Image table too large."); + throw std::runtime_error("Image table too large."); + } + + // Read g1 element headers + uintptr_t imageDataBase = reinterpret_cast(data.get()); + std::vector newEntries; + for (uint32_t i = 0; i < numImages; i++) + { + G1Element g1Element{}; + + uintptr_t imageDataOffset = static_cast(stream->ReadValue()); + g1Element.offset = reinterpret_cast(imageDataBase + imageDataOffset); + + g1Element.width = stream->ReadValue(); + g1Element.height = stream->ReadValue(); + g1Element.x_offset = stream->ReadValue(); + g1Element.y_offset = stream->ReadValue(); + g1Element.flags = stream->ReadValue(); + g1Element.zoomed_offset = stream->ReadValue(); + + newEntries.push_back(std::move(g1Element)); + } + + // Read g1 element data + size_t readBytes = static_cast(stream->TryRead(data.get(), dataSize)); + + // If data is shorter than expected (some custom objects are unfortunately like that) + size_t unreadBytes = dataSize - readBytes; + if (unreadBytes > 0) + { + std::fill_n(data.get() + readBytes, unreadBytes, 0); + context->LogWarning(ObjectError::BadImageTable, "Image table size shorter than expected."); + } + + _data = std::move(data); + _entries.insert(_entries.end(), newEntries.begin(), newEntries.end()); + } + catch (const std::exception&) + { + context->LogError(ObjectError::BadImageTable, "Bad image table."); + throw; + } + } + + std::vector> ImageTable::GetImageSources(IReadObjectContext* context, json_t& jsonImages) + { + std::vector> result; + for (auto& jsonImage : jsonImages) + { + if (jsonImage.is_object() && jsonImage.contains("path")) + { + auto path = Json::GetString(jsonImage["path"]); + auto keepPalette = Json::GetString(jsonImage["palette"]) == "keep"; + auto itSource = std::find_if(result.begin(), result.end(), [&path](const std::pair& item) { + return item.first == path; + }); + if (itSource == result.end()) + { + auto imageData = context->GetData(path); + auto imageFormat = keepPalette ? ImageFormat::png : ImageFormat::png32; + auto image = Imaging::ReadFromBuffer(imageData, imageFormat); + auto pair = std::make_pair(std::move(path), std::move(image)); + result.push_back(std::move(pair)); + } + } + } + return result; + } + + bool ImageTable::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "ImageTable::ReadJson expects parameter root to be object"); + + bool usesFallbackSprites = false; + + if (context->ShouldLoadImages()) + { + // First gather all the required images from inspecting the JSON + std::vector> allImages; + auto jsonImages = root["images"]; + if (!IsCsgLoaded() && root.contains("noCsgImages")) + { + jsonImages = root["noCsgImages"]; + usesFallbackSprites = true; + } + + auto imageSources = GetImageSources(context, jsonImages); + + for (auto& jsonImage : jsonImages) + { + if (jsonImage.is_string()) + { + auto strImage = jsonImage.get(); + auto images = ParseImages(context, strImage); allImages.insert( allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); } - } - } - - // Now add all the images to the image table - auto imagesStartIndex = GetCount(); - for (const auto& img : allImages) - { - const auto& g1 = img->g1; - AddImage(&g1); - } - - // Add all the zoom images at the very end of the image table. - // This way it should not affect the offsets used within the object logic. - for (size_t j = 0; j < allImages.size(); j++) - { - const auto tableIndex = imagesStartIndex + j; - const auto* img = allImages[j].get(); - if (img->next_zoom != nullptr) - { - img = img->next_zoom.get(); - - // Set old image zoom offset to zoom image which we are about to add - auto g1a = const_cast(&GetImages()[tableIndex]); - g1a->zoomed_offset = static_cast(tableIndex) - static_cast(GetCount()); - - while (img != nullptr) + else if (jsonImage.is_object()) { - auto g1b = img->g1; - if (img->next_zoom != nullptr) + if (jsonImage.contains("gx")) { - g1b.zoomed_offset = -1; + auto xOverride = Json::GetNumber(jsonImage["x"], std::numeric_limits::max()); + auto yOverride = Json::GetNumber(jsonImage["y"], std::numeric_limits::max()); + const bool hasXOverride = xOverride != std::numeric_limits::max(); + const bool hasYOverride = yOverride != std::numeric_limits::max(); + + auto strImage = jsonImage["gx"].get(); + auto images = ParseImages(context, strImage); + + if (hasXOverride || hasYOverride) + { + for (auto& image : images) + { + if (hasXOverride) + image->g1.x_offset = xOverride; + if (hasYOverride) + image->g1.y_offset = yOverride; + } + } + + allImages.insert( + allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); } - AddImage(&g1b); + else + { + auto images = ParseImages(context, imageSources, jsonImage); + allImages.insert( + allImages.end(), std::make_move_iterator(images.begin()), std::make_move_iterator(images.end())); + } + } + } + + // Now add all the images to the image table + auto imagesStartIndex = GetCount(); + for (const auto& img : allImages) + { + const auto& g1 = img->g1; + AddImage(&g1); + } + + // Add all the zoom images at the very end of the image table. + // This way it should not affect the offsets used within the object logic. + for (size_t j = 0; j < allImages.size(); j++) + { + const auto tableIndex = imagesStartIndex + j; + const auto* img = allImages[j].get(); + if (img->next_zoom != nullptr) + { img = img->next_zoom.get(); + + // Set old image zoom offset to zoom image which we are about to add + auto g1a = const_cast(&GetImages()[tableIndex]); + g1a->zoomed_offset = static_cast(tableIndex) - static_cast(GetCount()); + + while (img != nullptr) + { + auto g1b = img->g1; + if (img->next_zoom != nullptr) + { + g1b.zoomed_offset = -1; + } + AddImage(&g1b); + img = img->next_zoom.get(); + } } } } + + _objDataCache.clear(); + + return usesFallbackSprites; } - _objDataCache.clear(); - - return usesFallbackSprites; -} - -void ImageTable::AddImage(const G1Element* g1) -{ - G1Element newg1 = *g1; - auto length = G1CalculateDataSize(g1); - if (length == 0) + void ImageTable::AddImage(const G1Element* g1) { - newg1.offset = nullptr; + G1Element newg1 = *g1; + auto length = G1CalculateDataSize(g1); + if (length == 0) + { + newg1.offset = nullptr; + } + else + { + newg1.offset = new uint8_t[length]; + std::copy_n(g1->offset, length, newg1.offset); + } + _entries.push_back(std::move(newg1)); } - else - { - newg1.offset = new uint8_t[length]; - std::copy_n(g1->offset, length, newg1.offset); - } - _entries.push_back(std::move(newg1)); -} +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ImageTable.h b/src/openrct2/object/ImageTable.h index 134c6191f5..1a4ff37535 100644 --- a/src/openrct2/object/ImageTable.h +++ b/src/openrct2/object/ImageTable.h @@ -16,55 +16,57 @@ #include struct Image; -struct IReadObjectContext; + namespace OpenRCT2 { + struct IReadObjectContext; struct IStream; -} -class ImageTable -{ -private: - std::unique_ptr _data; - std::vector _entries; - - /** - * Container for a G1 image, additional information and RAII. Used by ReadJson - */ - struct RequiredImage; - [[nodiscard]] std::vector> GetImageSources(IReadObjectContext* context, json_t& jsonImages); - [[nodiscard]] static std::vector> ParseImages( - IReadObjectContext* context, std::string s); - /** - * @note root is deliberately left non-const: json_t behaviour changes when const - */ - [[nodiscard]] static std::vector> ParseImages( - IReadObjectContext* context, std::vector>& imageSources, json_t& el); - [[nodiscard]] static std::vector> LoadObjectImages( - IReadObjectContext* context, const std::string& name, const std::vector& range); - [[nodiscard]] static std::vector ParseRange(std::string s); - [[nodiscard]] static std::string FindLegacyObject(const std::string& name); - [[nodiscard]] static std::vector> LoadImageArchiveImages( - IReadObjectContext* context, const std::string& path, const std::vector& range = {}); - -public: - ImageTable() = default; - ImageTable(const ImageTable&) = delete; - ImageTable& operator=(const ImageTable&) = delete; - ~ImageTable(); - - void Read(IReadObjectContext* context, OpenRCT2::IStream* stream); - /** - * @note root is deliberately left non-const: json_t behaviour changes when const - */ - bool ReadJson(IReadObjectContext* context, json_t& root); - const G1Element* GetImages() const + class ImageTable { - return _entries.data(); - } - uint32_t GetCount() const - { - return static_cast(_entries.size()); - } - void AddImage(const G1Element* g1); -}; + private: + std::unique_ptr _data; + std::vector _entries; + + /** + * Container for a G1 image, additional information and RAII. Used by ReadJson + */ + struct RequiredImage; + [[nodiscard]] std::vector> GetImageSources( + IReadObjectContext* context, json_t& jsonImages); + [[nodiscard]] static std::vector> ParseImages( + IReadObjectContext* context, std::string s); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + [[nodiscard]] static std::vector> ParseImages( + IReadObjectContext* context, std::vector>& imageSources, json_t& el); + [[nodiscard]] static std::vector> LoadObjectImages( + IReadObjectContext* context, const std::string& name, const std::vector& range); + [[nodiscard]] static std::vector ParseRange(std::string s); + [[nodiscard]] static std::string FindLegacyObject(const std::string& name); + [[nodiscard]] static std::vector> LoadImageArchiveImages( + IReadObjectContext* context, const std::string& path, const std::vector& range = {}); + + public: + ImageTable() = default; + ImageTable(const ImageTable&) = delete; + ImageTable& operator=(const ImageTable&) = delete; + ~ImageTable(); + + void Read(IReadObjectContext* context, OpenRCT2::IStream* stream); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + bool ReadJson(IReadObjectContext* context, json_t& root); + const G1Element* GetImages() const + { + return _entries.data(); + } + uint32_t GetCount() const + { + return static_cast(_entries.size()); + } + void AddImage(const G1Element* g1); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/LargeSceneryEntry.h b/src/openrct2/object/LargeSceneryEntry.h index 232fc7f1f8..c78ea40e28 100644 --- a/src/openrct2/object/LargeSceneryEntry.h +++ b/src/openrct2/object/LargeSceneryEntry.h @@ -19,94 +19,97 @@ enum class CursorID : uint8_t; -struct LargeSceneryText; - -struct LargeSceneryTile +namespace OpenRCT2 { - CoordsXYZ offset; - int32_t zClearance; // BigZ - bool hasSupports; - bool allowSupportsAbove; - uint8_t corners; // occupied corners of the tile - uint8_t walls; // sides that walls can be placed on - uint8_t index; // Purely to save having to look this up all the time -}; + struct LargeSceneryText; -struct LargeSceneryTextGlyph -{ - uint8_t image_offset; - uint8_t width; - uint8_t height; - uint8_t Pad3; -}; - -// // TODO: Remove not required -struct RCTLargeSceneryText -{ - struct + struct LargeSceneryTile { - int16_t x, y; - } offset[2]; // 0x0 - uint16_t max_width; // 0x8 - uint16_t PadA; // 0xA - uint8_t flags; // 0xC - uint8_t num_images; // 0xD - LargeSceneryTextGlyph glyphs[256]; // 0xE -}; + CoordsXYZ offset; + int32_t zClearance; // BigZ + bool hasSupports; + bool allowSupportsAbove; + uint8_t corners; // occupied corners of the tile + uint8_t walls; // sides that walls can be placed on + uint8_t index; // Purely to save having to look this up all the time + }; -enum LARGE_SCENERY_TEXT_FLAGS -{ - LARGE_SCENERY_TEXT_FLAG_VERTICAL = (1 << 0), // 0x1 - LARGE_SCENERY_TEXT_FLAG_TWO_LINE = (1 << 1), // 0x2 -}; - -struct LargeSceneryEntry -{ - static constexpr auto kObjectType = ObjectType::largeScenery; - - StringId name; - uint32_t image; - CursorID tool_id; - uint16_t flags; - money64 price; - money64 removal_price; - std::span tiles; - ObjectEntryIndex scenery_tab_id; - uint8_t scrolling_mode; - LargeSceneryText* text; - uint32_t text_image; - - constexpr bool HasFlag(const uint16_t _flags) const + struct LargeSceneryTextGlyph { - return (flags & _flags) != 0; - } -}; + uint8_t image_offset; + uint8_t width; + uint8_t height; + uint8_t Pad3; + }; -enum LARGE_SCENERY_FLAGS : uint16_t -{ - LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR = (1 << 0), // 0x1 - LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR = (1 << 1), // 0x2 - LARGE_SCENERY_FLAG_3D_TEXT = (1 << 2), // 0x4 - LARGE_SCENERY_FLAG_ANIMATED = (1 << 3), // 0x8 - LARGE_SCENERY_FLAG_PHOTOGENIC = (1 << 4), // 0x10 - LARGE_SCENERY_FLAG_IS_TREE = (1 << 5), // 0x20 - LARGE_SCENERY_FLAG_HAS_TERTIARY_COLOUR = (1 << 6), // 0x40 - LARGE_SCENERY_FLAG_HIDE_PRIMARY_REMAP_BUTTON = (1 << 7), // 0x80 - LARGE_SCENERY_FLAG_HIDE_SECONDARY_REMAP_BUTTON = (1 << 8), // 0x100 -}; + // // TODO: Remove not required + struct RCTLargeSceneryText + { + struct + { + int16_t x, y; + } offset[2]; // 0x0 + uint16_t max_width; // 0x8 + uint16_t PadA; // 0xA + uint8_t flags; // 0xC + uint8_t num_images; // 0xD + LargeSceneryTextGlyph glyphs[256]; // 0xE + }; -struct LargeSceneryText -{ - CoordsXY offset[2]; - uint16_t max_width; - uint8_t flags; - uint16_t num_images; - LargeSceneryTextGlyph glyphs[256]; + enum LARGE_SCENERY_TEXT_FLAGS + { + LARGE_SCENERY_TEXT_FLAG_VERTICAL = (1 << 0), // 0x1 + LARGE_SCENERY_TEXT_FLAG_TWO_LINE = (1 << 1), // 0x2 + }; - LargeSceneryText() = default; - explicit LargeSceneryText(const RCTLargeSceneryText& original); - const LargeSceneryTextGlyph* GetGlyph(char32_t codepoint) const; - const LargeSceneryTextGlyph& GetGlyph(char32_t codepoint, char32_t defaultCodepoint) const; - int32_t MeasureWidth(std::string_view text) const; - int32_t MeasureHeight(std::string_view text) const; -}; + struct LargeSceneryEntry + { + static constexpr auto kObjectType = ObjectType::largeScenery; + + StringId name; + uint32_t image; + CursorID tool_id; + uint16_t flags; + money64 price; + money64 removal_price; + std::span tiles; + ObjectEntryIndex scenery_tab_id; + uint8_t scrolling_mode; + LargeSceneryText* text; + uint32_t text_image; + + constexpr bool HasFlag(const uint16_t _flags) const + { + return (flags & _flags) != 0; + } + }; + + enum LARGE_SCENERY_FLAGS : uint16_t + { + LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR = (1 << 0), // 0x1 + LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR = (1 << 1), // 0x2 + LARGE_SCENERY_FLAG_3D_TEXT = (1 << 2), // 0x4 + LARGE_SCENERY_FLAG_ANIMATED = (1 << 3), // 0x8 + LARGE_SCENERY_FLAG_PHOTOGENIC = (1 << 4), // 0x10 + LARGE_SCENERY_FLAG_IS_TREE = (1 << 5), // 0x20 + LARGE_SCENERY_FLAG_HAS_TERTIARY_COLOUR = (1 << 6), // 0x40 + LARGE_SCENERY_FLAG_HIDE_PRIMARY_REMAP_BUTTON = (1 << 7), // 0x80 + LARGE_SCENERY_FLAG_HIDE_SECONDARY_REMAP_BUTTON = (1 << 8), // 0x100 + }; + + struct LargeSceneryText + { + CoordsXY offset[2]; + uint16_t max_width; + uint8_t flags; + uint16_t num_images; + LargeSceneryTextGlyph glyphs[256]; + + LargeSceneryText() = default; + explicit LargeSceneryText(const RCTLargeSceneryText& original); + const LargeSceneryTextGlyph* GetGlyph(char32_t codepoint) const; + const LargeSceneryTextGlyph& GetGlyph(char32_t codepoint, char32_t defaultCodepoint) const; + int32_t MeasureWidth(std::string_view text) const; + int32_t MeasureHeight(std::string_view text) const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/LargeSceneryObject.cpp b/src/openrct2/object/LargeSceneryObject.cpp index a107b41afb..5cde609e1e 100644 --- a/src/openrct2/object/LargeSceneryObject.cpp +++ b/src/openrct2/object/LargeSceneryObject.cpp @@ -22,320 +22,321 @@ #include -using namespace OpenRCT2; - -static RCTLargeSceneryText ReadLegacy3DFont(OpenRCT2::IStream& stream) +namespace OpenRCT2 { - RCTLargeSceneryText _3dFontLegacy = {}; - _3dFontLegacy.offset[0].x = stream.ReadValue(); - _3dFontLegacy.offset[0].y = stream.ReadValue(); - _3dFontLegacy.offset[1].x = stream.ReadValue(); - _3dFontLegacy.offset[1].y = stream.ReadValue(); - _3dFontLegacy.max_width = stream.ReadValue(); - stream.ReadValue(); - _3dFontLegacy.flags = stream.ReadValue(); - _3dFontLegacy.num_images = stream.ReadValue(); - - auto ReadLegacyTextGlyph = [&stream]() { - LargeSceneryTextGlyph glyph{}; - glyph.image_offset = stream.ReadValue(); - glyph.width = stream.ReadValue(); - glyph.height = stream.ReadValue(); - stream.ReadValue(); - return glyph; - }; - - for (size_t i = 0; i < std::size(_3dFontLegacy.glyphs); ++i) + static RCTLargeSceneryText ReadLegacy3DFont(OpenRCT2::IStream& stream) { - _3dFontLegacy.glyphs[i] = ReadLegacyTextGlyph(); - } - return _3dFontLegacy; -} + RCTLargeSceneryText _3dFontLegacy = {}; + _3dFontLegacy.offset[0].x = stream.ReadValue(); + _3dFontLegacy.offset[0].y = stream.ReadValue(); + _3dFontLegacy.offset[1].x = stream.ReadValue(); + _3dFontLegacy.offset[1].y = stream.ReadValue(); + _3dFontLegacy.max_width = stream.ReadValue(); + stream.ReadValue(); + _3dFontLegacy.flags = stream.ReadValue(); + _3dFontLegacy.num_images = stream.ReadValue(); -void LargeSceneryObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) -{ - stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.tool_id = static_cast(stream->ReadValue()); - _legacyType.flags = stream->ReadValue(); - _legacyType.price = stream->ReadValue() * 10; - _legacyType.removal_price = stream->ReadValue() * 10; - stream->Seek(5, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.scenery_tab_id = kObjectEntryIndexNull; - _legacyType.scrolling_mode = stream->ReadValue(); - stream->Seek(4, OpenRCT2::STREAM_SEEK_CURRENT); + auto ReadLegacyTextGlyph = [&stream]() { + LargeSceneryTextGlyph glyph{}; + glyph.image_offset = stream.ReadValue(); + glyph.width = stream.ReadValue(); + glyph.height = stream.ReadValue(); + stream.ReadValue(); + return glyph; + }; - GetStringTable().Read(context, stream, ObjectStringID::NAME); - - RCTObjectEntry sgEntry = stream->ReadValue(); - SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); - - if (_legacyType.flags & LARGE_SCENERY_FLAG_3D_TEXT) - { - RCTLargeSceneryText _3dFontLegacy = ReadLegacy3DFont(*stream); - _3dFont = std::make_unique(_3dFontLegacy); - _legacyType.text = _3dFont.get(); - } - - _tiles = ReadTiles(stream); - - GetImageTable().Read(context, stream); - - // Validate properties - if (_legacyType.price <= 0.00_GBP) - { - context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); - } - if (_legacyType.removal_price <= 0.00_GBP) - { - // Make sure you don't make a profit when placing then removing. - const auto reimbursement = _legacyType.removal_price; - if (reimbursement > _legacyType.price) + for (size_t i = 0; i < std::size(_3dFontLegacy.glyphs); ++i) { - context->LogError(ObjectError::InvalidProperty, "Sell price can not be more than buy price."); + _3dFontLegacy.glyphs[i] = ReadLegacyTextGlyph(); } + return _3dFontLegacy; } - // RCT2 would always remap primary and secondary colours for large scenery - // This meant some custom large scenery objects did not get exported with the required flags, because they still - // functioned, but without the ability to change the colours when the object was selected in the scenery window. - // OpenRCT2 changes the rendering so that the flags are required, we therefore have to assume all custom objects - // can be recoloured. - if (!(_legacyType.flags & LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR)) + void LargeSceneryObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) { - _legacyType.flags |= LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR | LARGE_SCENERY_FLAG_HIDE_PRIMARY_REMAP_BUTTON; - } - if (!(_legacyType.flags & LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR)) - { - _legacyType.flags |= LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR | LARGE_SCENERY_FLAG_HIDE_SECONDARY_REMAP_BUTTON; - } -} + stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); + _legacyType.tool_id = static_cast(stream->ReadValue()); + _legacyType.flags = stream->ReadValue(); + _legacyType.price = stream->ReadValue() * 10; + _legacyType.removal_price = stream->ReadValue() * 10; + stream->Seek(5, OpenRCT2::STREAM_SEEK_CURRENT); + _legacyType.scenery_tab_id = kObjectEntryIndexNull; + _legacyType.scrolling_mode = stream->ReadValue(); + stream->Seek(4, OpenRCT2::STREAM_SEEK_CURRENT); -void LargeSceneryObject::Load() -{ - GetStringTable().Sort(); - _legacyType.name = LanguageAllocateObjectString(GetName()); - _baseImageId = LoadImages(); - _legacyType.image = _baseImageId; + GetStringTable().Read(context, stream, ObjectStringID::NAME); - _legacyType.tiles = _tiles; + RCTObjectEntry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); - if (_legacyType.flags & LARGE_SCENERY_FLAG_3D_TEXT) - { - _legacyType.text_image = _legacyType.image; - if (_3dFont->flags & LARGE_SCENERY_TEXT_FLAG_VERTICAL) + if (_legacyType.flags & LARGE_SCENERY_FLAG_3D_TEXT) { - _legacyType.image += _3dFont->num_images * 2; + RCTLargeSceneryText _3dFontLegacy = ReadLegacy3DFont(*stream); + _3dFont = std::make_unique(_3dFontLegacy); + _legacyType.text = _3dFont.get(); } - else + + _tiles = ReadTiles(stream); + + GetImageTable().Read(context, stream); + + // Validate properties + if (_legacyType.price <= 0.00_GBP) { - _legacyType.image += _3dFont->num_images * 4; + context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); } - _legacyType.text = _3dFont.get(); - } -} - -void LargeSceneryObject::Unload() -{ - LanguageFreeObjectString(_legacyType.name); - UnloadImages(); - - _legacyType.name = 0; - _baseImageId = _legacyType.image = 0; -} - -void LargeSceneryObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, (height / 2) - 39 }; - - auto image = ImageId(_legacyType.image); - if (_legacyType.flags & LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR) - image = image.WithPrimary(COLOUR_BORDEAUX_RED); - if (_legacyType.flags & LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR) - image = image.WithSecondary(COLOUR_YELLOW); - if (_legacyType.flags & LARGE_SCENERY_FLAG_HAS_TERTIARY_COLOUR) - image = image.WithTertiary(COLOUR_DARK_BROWN); - - GfxDrawSprite(rt, image, screenCoords); -} - -enum -{ - LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS = 0x20, - LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE = 0x40, -}; - -std::vector LargeSceneryObject::ReadTiles(OpenRCT2::IStream* stream) -{ - auto tiles = std::vector(); - - auto ReadLegacyTile = [&stream]() { - LargeSceneryTile tile{}; - tile.offset.x = stream->ReadValue(); - tile.offset.y = stream->ReadValue(); - tile.offset.z = stream->ReadValue(); - tile.zClearance = stream->ReadValue(); - uint16_t flags = stream->ReadValue(); - tile.walls = (flags >> 8) & 0xF; - tile.corners = (flags >> 12) & 0xF; - tile.allowSupportsAbove = flags & LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE; - tile.hasSupports = !(flags & LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS); - return tile; - }; - - while (stream->ReadValue() != 0xFFFF) - { - stream->Seek(-2, OpenRCT2::STREAM_SEEK_CURRENT); - tiles.push_back(ReadLegacyTile()); - } - uint8_t index = 0; - for (auto& tile : tiles) - { - tile.index = index++; - } - return tiles; -} - -void LargeSceneryObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "LargeSceneryObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - - if (properties.is_object()) - { - _legacyType.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::StatueDown); - - _legacyType.price = Json::GetNumber(properties["price"]) * 10; - _legacyType.removal_price = Json::GetNumber(properties["removalPrice"]) * 10; - - _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"], kScrollingModeNone); - - _legacyType.flags = Json::GetFlags( - properties, + if (_legacyType.removal_price <= 0.00_GBP) + { + // Make sure you don't make a profit when placing then removing. + const auto reimbursement = _legacyType.removal_price; + if (reimbursement > _legacyType.price) { - { "hasPrimaryColour", LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, - { "hasSecondaryColour", LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, - { "hasTertiaryColour", LARGE_SCENERY_FLAG_HAS_TERTIARY_COLOUR }, - { "isAnimated", LARGE_SCENERY_FLAG_ANIMATED }, - { "isPhotogenic", LARGE_SCENERY_FLAG_PHOTOGENIC }, - { "isTree", LARGE_SCENERY_FLAG_IS_TREE }, + context->LogError(ObjectError::InvalidProperty, "Sell price can not be more than buy price."); + } + } + + // RCT2 would always remap primary and secondary colours for large scenery + // This meant some custom large scenery objects did not get exported with the required flags, because they still + // functioned, but without the ability to change the colours when the object was selected in the scenery window. + // OpenRCT2 changes the rendering so that the flags are required, we therefore have to assume all custom objects + // can be recoloured. + if (!(_legacyType.flags & LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR)) + { + _legacyType.flags |= LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR | LARGE_SCENERY_FLAG_HIDE_PRIMARY_REMAP_BUTTON; + } + if (!(_legacyType.flags & LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR)) + { + _legacyType.flags |= LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR | LARGE_SCENERY_FLAG_HIDE_SECONDARY_REMAP_BUTTON; + } + } + + void LargeSceneryObject::Load() + { + GetStringTable().Sort(); + _legacyType.name = LanguageAllocateObjectString(GetName()); + _baseImageId = LoadImages(); + _legacyType.image = _baseImageId; + + _legacyType.tiles = _tiles; + + if (_legacyType.flags & LARGE_SCENERY_FLAG_3D_TEXT) + { + _legacyType.text_image = _legacyType.image; + if (_3dFont->flags & LARGE_SCENERY_TEXT_FLAG_VERTICAL) + { + _legacyType.image += _3dFont->num_images * 2; + } + else + { + _legacyType.image += _3dFont->num_images * 4; + } + _legacyType.text = _3dFont.get(); + } + } + + void LargeSceneryObject::Unload() + { + LanguageFreeObjectString(_legacyType.name); + UnloadImages(); + + _legacyType.name = 0; + _baseImageId = _legacyType.image = 0; + } + + void LargeSceneryObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto screenCoords = ScreenCoordsXY{ width / 2, (height / 2) - 39 }; + + auto image = ImageId(_legacyType.image); + if (_legacyType.flags & LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR) + image = image.WithPrimary(COLOUR_BORDEAUX_RED); + if (_legacyType.flags & LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR) + image = image.WithSecondary(COLOUR_YELLOW); + if (_legacyType.flags & LARGE_SCENERY_FLAG_HAS_TERTIARY_COLOUR) + image = image.WithTertiary(COLOUR_DARK_BROWN); + + GfxDrawSprite(rt, image, screenCoords); + } + + enum + { + LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS = 0x20, + LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE = 0x40, + }; + + std::vector LargeSceneryObject::ReadTiles(OpenRCT2::IStream* stream) + { + auto tiles = std::vector(); + + auto ReadLegacyTile = [&stream]() { + LargeSceneryTile tile{}; + tile.offset.x = stream->ReadValue(); + tile.offset.y = stream->ReadValue(); + tile.offset.z = stream->ReadValue(); + tile.zClearance = stream->ReadValue(); + uint16_t flags = stream->ReadValue(); + tile.walls = (flags >> 8) & 0xF; + tile.corners = (flags >> 12) & 0xF; + tile.allowSupportsAbove = flags & LARGE_SCENERY_TILE_FLAG_ALLOW_SUPPORTS_ABOVE; + tile.hasSupports = !(flags & LARGE_SCENERY_TILE_FLAG_NO_SUPPORTS); + return tile; + }; + + while (stream->ReadValue() != 0xFFFF) + { + stream->Seek(-2, OpenRCT2::STREAM_SEEK_CURRENT); + tiles.push_back(ReadLegacyTile()); + } + uint8_t index = 0; + for (auto& tile : tiles) + { + tile.index = index++; + } + return tiles; + } + + void LargeSceneryObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "LargeSceneryObject::ReadJson expects parameter root to be object"); + + auto properties = root["properties"]; + + if (properties.is_object()) + { + _legacyType.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::StatueDown); + + _legacyType.price = Json::GetNumber(properties["price"]) * 10; + _legacyType.removal_price = Json::GetNumber(properties["removalPrice"]) * 10; + + _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"], kScrollingModeNone); + + _legacyType.flags = Json::GetFlags( + properties, + { + { "hasPrimaryColour", LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, + { "hasSecondaryColour", LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, + { "hasTertiaryColour", LARGE_SCENERY_FLAG_HAS_TERTIARY_COLOUR }, + { "isAnimated", LARGE_SCENERY_FLAG_ANIMATED }, + { "isPhotogenic", LARGE_SCENERY_FLAG_PHOTOGENIC }, + { "isTree", LARGE_SCENERY_FLAG_IS_TREE }, + }); + + // Tiles + auto jTiles = properties["tiles"]; + if (jTiles.is_array()) + { + _tiles = ReadJsonTiles(jTiles); + } + + // Read text + auto j3dFont = properties["3dFont"]; + if (j3dFont.is_object()) + { + _3dFont = ReadJson3dFont(j3dFont); + _legacyType.flags |= LARGE_SCENERY_FLAG_3D_TEXT; + } + + SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + } + + PopulateTablesFromJson(context, root); + } + + std::vector LargeSceneryObject::ReadJsonTiles(json_t& jTiles) + { + std::vector tiles; + for (auto& jTile : jTiles) + { + if (jTile.is_object()) + { + LargeSceneryTile tile = {}; + tile.offset.x = Json::GetNumber(jTile["x"]); + tile.offset.y = Json::GetNumber(jTile["y"]); + tile.offset.z = Json::GetNumber(jTile["z"]); + tile.zClearance = Json::GetNumber(jTile["clearance"]); + + tile.hasSupports = Json::GetBoolean(jTile["hasSupports"]); + tile.allowSupportsAbove = Json::GetBoolean(jTile["allowSupportsAbove"]); + + // All corners are by default occupied + tile.corners = Json::GetNumber(jTile["corners"], 0xF); + + tile.walls = Json::GetNumber(jTile["walls"]); + + tile.index = static_cast(tiles.size()); + + tiles.push_back(std::move(tile)); + } + } + + return tiles; + } + + std::unique_ptr LargeSceneryObject::ReadJson3dFont(json_t& j3dFont) + { + Guard::Assert(j3dFont.is_object(), "LargeSceneryObject::ReadJson3dFont expects parameter j3dFont to be object"); + + auto font = std::make_unique(); + + auto jOffsets = j3dFont["offsets"]; + if (jOffsets.is_array()) + { + auto offsets = ReadJsonOffsets(jOffsets); + auto numOffsets = std::min(std::size(font->offset), offsets.size()); + std::copy_n(offsets.data(), numOffsets, font->offset); + } + + font->max_width = Json::GetNumber(j3dFont["maxWidth"]); + font->num_images = Json::GetNumber(j3dFont["numImages"]); + + font->flags = Json::GetFlags( + j3dFont, + { + { "isVertical", LARGE_SCENERY_TEXT_FLAG_VERTICAL }, + { "isTwoLine", LARGE_SCENERY_TEXT_FLAG_TWO_LINE }, }); - // Tiles - auto jTiles = properties["tiles"]; - if (jTiles.is_array()) + auto jGlyphs = j3dFont["glyphs"]; + if (jGlyphs.is_array()) { - _tiles = ReadJsonTiles(jTiles); + auto glyphs = ReadJsonGlyphs(jGlyphs); + auto numGlyphs = std::min(std::size(font->glyphs), glyphs.size()); + std::copy_n(glyphs.data(), numGlyphs, font->glyphs); } - // Read text - auto j3dFont = properties["3dFont"]; - if (j3dFont.is_object()) + return font; + } + + std::vector LargeSceneryObject::ReadJsonOffsets(json_t& jOffsets) + { + std::vector offsets; + for (auto& jOffset : jOffsets) { - _3dFont = ReadJson3dFont(j3dFont); - _legacyType.flags |= LARGE_SCENERY_FLAG_3D_TEXT; + if (jOffset.is_object()) + { + CoordsXY offset = {}; + offset.x = Json::GetNumber(jOffset["x"]); + offset.y = Json::GetNumber(jOffset["y"]); + offsets.push_back(offset); + } } - - SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + return offsets; } - PopulateTablesFromJson(context, root); -} - -std::vector LargeSceneryObject::ReadJsonTiles(json_t& jTiles) -{ - std::vector tiles; - for (auto& jTile : jTiles) + std::vector LargeSceneryObject::ReadJsonGlyphs(json_t& jGlyphs) { - if (jTile.is_object()) + std::vector glyphs; + for (auto& jGlyph : jGlyphs) { - LargeSceneryTile tile = {}; - tile.offset.x = Json::GetNumber(jTile["x"]); - tile.offset.y = Json::GetNumber(jTile["y"]); - tile.offset.z = Json::GetNumber(jTile["z"]); - tile.zClearance = Json::GetNumber(jTile["clearance"]); - - tile.hasSupports = Json::GetBoolean(jTile["hasSupports"]); - tile.allowSupportsAbove = Json::GetBoolean(jTile["allowSupportsAbove"]); - - // All corners are by default occupied - tile.corners = Json::GetNumber(jTile["corners"], 0xF); - - tile.walls = Json::GetNumber(jTile["walls"]); - - tile.index = static_cast(tiles.size()); - - tiles.push_back(std::move(tile)); + if (jGlyph.is_object()) + { + LargeSceneryTextGlyph glyph = {}; + glyph.image_offset = Json::GetNumber(jGlyph["image"]); + glyph.width = Json::GetNumber(jGlyph["width"]); + glyph.height = Json::GetNumber(jGlyph["height"]); + glyphs.push_back(glyph); + } } + return glyphs; } - - return tiles; -} - -std::unique_ptr LargeSceneryObject::ReadJson3dFont(json_t& j3dFont) -{ - Guard::Assert(j3dFont.is_object(), "LargeSceneryObject::ReadJson3dFont expects parameter j3dFont to be object"); - - auto font = std::make_unique(); - - auto jOffsets = j3dFont["offsets"]; - if (jOffsets.is_array()) - { - auto offsets = ReadJsonOffsets(jOffsets); - auto numOffsets = std::min(std::size(font->offset), offsets.size()); - std::copy_n(offsets.data(), numOffsets, font->offset); - } - - font->max_width = Json::GetNumber(j3dFont["maxWidth"]); - font->num_images = Json::GetNumber(j3dFont["numImages"]); - - font->flags = Json::GetFlags( - j3dFont, - { - { "isVertical", LARGE_SCENERY_TEXT_FLAG_VERTICAL }, - { "isTwoLine", LARGE_SCENERY_TEXT_FLAG_TWO_LINE }, - }); - - auto jGlyphs = j3dFont["glyphs"]; - if (jGlyphs.is_array()) - { - auto glyphs = ReadJsonGlyphs(jGlyphs); - auto numGlyphs = std::min(std::size(font->glyphs), glyphs.size()); - std::copy_n(glyphs.data(), numGlyphs, font->glyphs); - } - - return font; -} - -std::vector LargeSceneryObject::ReadJsonOffsets(json_t& jOffsets) -{ - std::vector offsets; - for (auto& jOffset : jOffsets) - { - if (jOffset.is_object()) - { - CoordsXY offset = {}; - offset.x = Json::GetNumber(jOffset["x"]); - offset.y = Json::GetNumber(jOffset["y"]); - offsets.push_back(offset); - } - } - return offsets; -} - -std::vector LargeSceneryObject::ReadJsonGlyphs(json_t& jGlyphs) -{ - std::vector glyphs; - for (auto& jGlyph : jGlyphs) - { - if (jGlyph.is_object()) - { - LargeSceneryTextGlyph glyph = {}; - glyph.image_offset = Json::GetNumber(jGlyph["image"]); - glyph.width = Json::GetNumber(jGlyph["width"]); - glyph.height = Json::GetNumber(jGlyph["height"]); - glyphs.push_back(glyph); - } - } - return glyphs; -} +} // namespace OpenRCT2 diff --git a/src/openrct2/object/LargeSceneryObject.h b/src/openrct2/object/LargeSceneryObject.h index 7a7b5c03ee..db3745fb02 100644 --- a/src/openrct2/object/LargeSceneryObject.h +++ b/src/openrct2/object/LargeSceneryObject.h @@ -15,33 +15,36 @@ #include #include -class LargeSceneryObject final : public SceneryObject +namespace OpenRCT2 { -private: - LargeSceneryEntry _legacyType = {}; - uint32_t _baseImageId = 0; - std::vector _tiles; - std::unique_ptr _3dFont; - -public: - static constexpr ObjectType kObjectType = ObjectType::largeScenery; - - void* GetLegacyData() override + class LargeSceneryObject final : public SceneryObject { - return &_legacyType; - } + private: + LargeSceneryEntry _legacyType = {}; + uint32_t _baseImageId = 0; + std::vector _tiles; + std::unique_ptr _3dFont; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + public: + static constexpr ObjectType kObjectType = ObjectType::largeScenery; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void* GetLegacyData() override + { + return &_legacyType; + } -private: - [[nodiscard]] static std::vector ReadTiles(OpenRCT2::IStream* stream); - [[nodiscard]] static std::vector ReadJsonTiles(json_t& jTiles); - [[nodiscard]] static std::unique_ptr ReadJson3dFont(json_t& j3dFont); - [[nodiscard]] static std::vector ReadJsonOffsets(json_t& jOffsets); - [[nodiscard]] static std::vector ReadJsonGlyphs(json_t& jGlyphs); -}; + void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + + private: + [[nodiscard]] static std::vector ReadTiles(OpenRCT2::IStream* stream); + [[nodiscard]] static std::vector ReadJsonTiles(json_t& jTiles); + [[nodiscard]] static std::unique_ptr ReadJson3dFont(json_t& j3dFont); + [[nodiscard]] static std::vector ReadJsonOffsets(json_t& jOffsets); + [[nodiscard]] static std::vector ReadJsonGlyphs(json_t& jGlyphs); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/MusicObject.cpp b/src/openrct2/object/MusicObject.cpp index e54e1c8a6b..ae7f1945c5 100644 --- a/src/openrct2/object/MusicObject.cpp +++ b/src/openrct2/object/MusicObject.cpp @@ -25,40 +25,45 @@ #include -using namespace OpenRCT2; -using namespace OpenRCT2::Audio; - -constexpr size_t kDefaultBytesPerTick = 1378; - -void MusicObject::Load() +namespace OpenRCT2 { - GetStringTable().Sort(); - NameStringId = LanguageAllocateObjectString(GetName()); + constexpr size_t kDefaultBytesPerTick = 1378; - // Start with base images - _loadedSampleTable.LoadFrom(_sampleTable, 0, _sampleTable.GetCount()); - - // Override samples from asset packs - auto context = GetContext(); - auto assetManager = context->GetAssetPackManager(); - if (assetManager != nullptr) + void MusicObject::Load() { - assetManager->LoadSamplesForObject(GetIdentifier(), _loadedSampleTable); - } + GetStringTable().Sort(); + NameStringId = LanguageAllocateObjectString(GetName()); - // Load metadata of samples - auto& audioContext = GetContext()->GetAudioContext(); - for (auto& track : _tracks) - { - auto stream = track.Asset.GetStream(); - if (stream != nullptr) + // Start with base images + _loadedSampleTable.LoadFrom(_sampleTable, 0, _sampleTable.GetCount()); + + // Override samples from asset packs + auto context = GetContext(); + auto assetManager = context->GetAssetPackManager(); + if (assetManager != nullptr) { - auto source = audioContext.CreateStreamFromWAV(std::move(stream)); - if (source != nullptr) + assetManager->LoadSamplesForObject(GetIdentifier(), _loadedSampleTable); + } + + // Load metadata of samples + auto& audioContext = GetContext()->GetAudioContext(); + for (auto& track : _tracks) + { + auto stream = track.Asset.GetStream(); + if (stream != nullptr) { - track.BytesPerTick = source->GetBytesPerSecond() / 40; - track.Size = source->GetLength(); - source->Release(); + auto source = audioContext.CreateStreamFromWAV(std::move(stream)); + if (source != nullptr) + { + track.BytesPerTick = source->GetBytesPerSecond() / 40; + track.Size = source->GetLength(); + source->Release(); + } + else + { + track.BytesPerTick = kDefaultBytesPerTick; + track.Size = track.Asset.GetSize(); + } } else { @@ -66,170 +71,165 @@ void MusicObject::Load() track.Size = track.Asset.GetSize(); } } + + _hasPreview = !!GetImageTable().GetCount(); + _previewImageId = LoadImages(); + } + + void MusicObject::Unload() + { + LanguageFreeObjectString(NameStringId); + UnloadImages(); + + _hasPreview = false; + _previewImageId = 0; + NameStringId = 0; + } + + void MusicObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + // Write (no image) + int32_t x = width / 2; + int32_t y = height / 2; + if (_hasPreview) + GfxDrawSprite(rt, ImageId(_previewImageId), { 0, 0 }); else - { - track.BytesPerTick = kDefaultBytesPerTick; - track.Size = track.Asset.GetSize(); - } + DrawTextBasic(rt, { x, y }, STR_WINDOW_NO_IMAGE, {}, { TextAlignment::CENTRE }); } - _hasPreview = !!GetImageTable().GetCount(); - _previewImageId = LoadImages(); -} - -void MusicObject::Unload() -{ - LanguageFreeObjectString(NameStringId); - UnloadImages(); - - _hasPreview = false; - _previewImageId = 0; - NameStringId = 0; -} - -void MusicObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - // Write (no image) - int32_t x = width / 2; - int32_t y = height / 2; - if (_hasPreview) - GfxDrawSprite(rt, ImageId(_previewImageId), { 0, 0 }); - else - DrawTextBasic(rt, { x, y }, STR_WINDOW_NO_IMAGE, {}, { TextAlignment::CENTRE }); -} - -bool MusicObject::HasPreview() const -{ - return _hasPreview; -} - -void MusicObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - _originalStyleId = {}; - _rideTypes.clear(); - _tracks.clear(); - _niceFactor = MusicNiceFactor::Neutral; - - auto& properties = root["properties"]; - if (properties != nullptr) + bool MusicObject::HasPreview() const { - const auto& originalStyleId = properties["originalStyleId"]; - if (originalStyleId.is_number_integer()) - { - _originalStyleId = originalStyleId.get(); - } - - const auto& niceFactor = properties["niceFactor"]; - if (niceFactor.is_number_integer()) - { - _niceFactor = static_cast(std::clamp(niceFactor.get(), -1, 1)); - } - - const auto& jRideTypes = properties["rideTypes"]; - if (jRideTypes.is_array()) - { - ParseRideTypes(jRideTypes); - } - - auto& jTracks = properties["tracks"]; - if (jTracks.is_array()) - { - ParseTracks(*context, jTracks); - } + return _hasPreview; } - PopulateTablesFromJson(context, root); -} - -void MusicObject::ParseRideTypes(const json_t& jRideTypes) -{ - for (const auto& jRideType : jRideTypes) + void MusicObject::ReadJson(IReadObjectContext* context, json_t& root) { - auto szRideType = Json::GetString(jRideType); - if (!szRideType.empty()) + _originalStyleId = {}; + _rideTypes.clear(); + _tracks.clear(); + _niceFactor = MusicNiceFactor::Neutral; + + auto& properties = root["properties"]; + if (properties != nullptr) { - auto rideType = RideObject::ParseRideType(szRideType); - if (rideType != kRideTypeNull) + const auto& originalStyleId = properties["originalStyleId"]; + if (originalStyleId.is_number_integer()) { - _rideTypes.push_back(rideType); + _originalStyleId = originalStyleId.get(); + } + + const auto& niceFactor = properties["niceFactor"]; + if (niceFactor.is_number_integer()) + { + _niceFactor = static_cast(std::clamp(niceFactor.get(), -1, 1)); + } + + const auto& jRideTypes = properties["rideTypes"]; + if (jRideTypes.is_array()) + { + ParseRideTypes(jRideTypes); + } + + auto& jTracks = properties["tracks"]; + if (jTracks.is_array()) + { + ParseTracks(*context, jTracks); + } + } + + PopulateTablesFromJson(context, root); + } + + void MusicObject::ParseRideTypes(const json_t& jRideTypes) + { + for (const auto& jRideType : jRideTypes) + { + auto szRideType = Json::GetString(jRideType); + if (!szRideType.empty()) + { + auto rideType = RideObject::ParseRideType(szRideType); + if (rideType != kRideTypeNull) + { + _rideTypes.push_back(rideType); + } } } } -} -void MusicObject::ParseTracks(IReadObjectContext& context, json_t& jTracks) -{ - auto& entries = _sampleTable.GetEntries(); - for (auto& jTrack : jTracks) + void MusicObject::ParseTracks(IReadObjectContext& context, json_t& jTracks) { - if (jTrack.is_object()) + auto& entries = _sampleTable.GetEntries(); + for (auto& jTrack : jTracks) { - MusicObjectTrack track; - track.Name = Json::GetString(jTrack["name"]); - track.Composer = Json::GetString(jTrack["composer"]); - auto source = Json::GetString(jTrack["source"]); - if (source.empty()) + if (jTrack.is_object()) { - context.LogError(ObjectError::InvalidProperty, "Invalid audio track definition."); - } - else - { - auto asset = GetAsset(context, source); + MusicObjectTrack track; + track.Name = Json::GetString(jTrack["name"]); + track.Composer = Json::GetString(jTrack["composer"]); + auto source = Json::GetString(jTrack["source"]); + if (source.empty()) + { + context.LogError(ObjectError::InvalidProperty, "Invalid audio track definition."); + } + else + { + auto asset = GetAsset(context, source); - auto& entry = entries.emplace_back(); - entry.Asset = asset; + auto& entry = entries.emplace_back(); + entry.Asset = asset; - track.Asset = asset; - _tracks.push_back(std::move(track)); + track.Asset = asset; + _tracks.push_back(std::move(track)); + } } } } -} -std::optional MusicObject::GetOriginalStyleId() const -{ - return _originalStyleId; -} - -bool MusicObject::SupportsRideType(ride_type_t rideType, bool onlyAllowIfExplicitlyListed) -{ - if (_rideTypes.size() == 0) + std::optional MusicObject::GetOriginalStyleId() const { - // Some rides, like the merry-go-round, need music objects to explicitly list them. - return !onlyAllowIfExplicitlyListed; + return _originalStyleId; } - auto it = std::find(_rideTypes.begin(), _rideTypes.end(), rideType); - return it != _rideTypes.end(); -} - -size_t MusicObject::GetTrackCount() const -{ - return _tracks.size(); -} - -const MusicObjectTrack* MusicObject::GetTrack(size_t trackIndex) const -{ - if (_tracks.size() > trackIndex) + bool MusicObject::SupportsRideType(ride_type_t rideType, bool onlyAllowIfExplicitlyListed) { - return &_tracks[trackIndex]; - } - return {}; -} + if (_rideTypes.size() == 0) + { + // Some rides, like the merry-go-round, need music objects to explicitly list them. + return !onlyAllowIfExplicitlyListed; + } -IAudioSource* MusicObject::GetTrackSample(size_t trackIndex) const -{ - return _loadedSampleTable.LoadSample(static_cast(trackIndex)); -} - -ObjectAsset MusicObject::GetAsset(IReadObjectContext& context, std::string_view path) -{ - if (path.find("$RCT2:DATA/") == 0) - { - auto& env = GetContext()->GetPlatformEnvironment(); - auto path2 = env.FindFile(DirBase::rct2, DirId::data, path.substr(11)); - return ObjectAsset(path2); + auto it = std::find(_rideTypes.begin(), _rideTypes.end(), rideType); + return it != _rideTypes.end(); } - return context.GetAsset(path); -} + size_t MusicObject::GetTrackCount() const + { + return _tracks.size(); + } + + const MusicObjectTrack* MusicObject::GetTrack(size_t trackIndex) const + { + if (_tracks.size() > trackIndex) + { + return &_tracks[trackIndex]; + } + return {}; + } + + Audio::IAudioSource* MusicObject::GetTrackSample(size_t trackIndex) const + { + return _loadedSampleTable.LoadSample(static_cast(trackIndex)); + } + + ObjectAsset MusicObject::GetAsset(IReadObjectContext& context, std::string_view path) + { + if (path.find("$RCT2:DATA/") == 0) + { + auto& env = GetContext()->GetPlatformEnvironment(); + auto path2 = env.FindFile(DirBase::rct2, DirId::data, path.substr(11)); + return ObjectAsset(path2); + } + + return context.GetAsset(path); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/MusicObject.h b/src/openrct2/object/MusicObject.h index faea0116bc..a9f28fea9b 100644 --- a/src/openrct2/object/MusicObject.h +++ b/src/openrct2/object/MusicObject.h @@ -15,74 +15,77 @@ #include #include -class MusicObjectTrack +namespace OpenRCT2 { -public: - std::string Name; - std::string Composer; - ObjectAsset Asset; - - /** - * The number of PCM bytes to seek per game tick when the music is playing offscreen. - */ - size_t BytesPerTick; - - /** - * The length of the PCM track in bytes. - */ - size_t Size; -}; - -enum class MusicNiceFactor : int8_t -{ - Overbearing = -1, // Drowns out other music - Neutral = 0, - Nice = 1, -}; - -class MusicObject final : public Object -{ -private: - std::vector _rideTypes; - std::vector _tracks; - std::optional _originalStyleId; - MusicNiceFactor _niceFactor; - AudioSampleTable _sampleTable; - AudioSampleTable _loadedSampleTable; - bool _hasPreview{}; - uint32_t _previewImageId{}; - -public: - static constexpr ObjectType kObjectType = ObjectType::music; - - StringId NameStringId{}; - - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; - - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; - bool HasPreview() const; - - std::optional GetOriginalStyleId() const; - /** - * - * @param rideType - * @param onlyAllowIfExplicitlyListed If a music object does not provide a list of ride types, - * it will be enabled for all ride types, unless this parameter is set to true. - * @return - */ - bool SupportsRideType(ride_type_t rideType, bool onlyAllowIfExplicitlyListed); - size_t GetTrackCount() const; - const MusicObjectTrack* GetTrack(size_t trackIndex) const; - OpenRCT2::Audio::IAudioSource* GetTrackSample(size_t trackIndex) const; - constexpr MusicNiceFactor GetNiceFactor() const + class MusicObjectTrack { - return _niceFactor; - } + public: + std::string Name; + std::string Composer; + ObjectAsset Asset; -private: - void ParseRideTypes(const json_t& jRideTypes); - void ParseTracks(IReadObjectContext& context, json_t& jTracks); - static ObjectAsset GetAsset(IReadObjectContext& context, std::string_view path); -}; + /** + * The number of PCM bytes to seek per game tick when the music is playing offscreen. + */ + size_t BytesPerTick; + + /** + * The length of the PCM track in bytes. + */ + size_t Size; + }; + + enum class MusicNiceFactor : int8_t + { + Overbearing = -1, // Drowns out other music + Neutral = 0, + Nice = 1, + }; + + class MusicObject final : public Object + { + private: + std::vector _rideTypes; + std::vector _tracks; + std::optional _originalStyleId; + MusicNiceFactor _niceFactor; + AudioSampleTable _sampleTable; + AudioSampleTable _loadedSampleTable; + bool _hasPreview{}; + uint32_t _previewImageId{}; + + public: + static constexpr ObjectType kObjectType = ObjectType::music; + + StringId NameStringId{}; + + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + bool HasPreview() const; + + std::optional GetOriginalStyleId() const; + /** + * + * @param rideType + * @param onlyAllowIfExplicitlyListed If a music object does not provide a list of ride types, + * it will be enabled for all ride types, unless this parameter is set to true. + * @return + */ + bool SupportsRideType(ride_type_t rideType, bool onlyAllowIfExplicitlyListed); + size_t GetTrackCount() const; + const MusicObjectTrack* GetTrack(size_t trackIndex) const; + Audio::IAudioSource* GetTrackSample(size_t trackIndex) const; + constexpr MusicNiceFactor GetNiceFactor() const + { + return _niceFactor; + } + + private: + void ParseRideTypes(const json_t& jRideTypes); + void ParseTracks(IReadObjectContext& context, json_t& jTracks); + static ObjectAsset GetAsset(IReadObjectContext& context, std::string_view path); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/Object.cpp b/src/openrct2/object/Object.cpp index b4b2fa0d53..0707764277 100644 --- a/src/openrct2/object/Object.cpp +++ b/src/openrct2/object/Object.cpp @@ -28,422 +28,426 @@ #include #include -using namespace OpenRCT2; - -ObjectEntryDescriptor::ObjectEntryDescriptor(const RCTObjectEntry& newEntry) -{ - if (!newEntry.IsEmpty()) - { - Generation = ObjectGeneration::DAT; - Entry = newEntry; - } -} - -ObjectEntryDescriptor::ObjectEntryDescriptor(std::string_view newIdentifier) -{ - Generation = ObjectGeneration::JSON; - Identifier = std::string(newIdentifier); -} - -ObjectEntryDescriptor::ObjectEntryDescriptor(ObjectType type, std::string_view newIdentifier) -{ - Generation = ObjectGeneration::JSON; - Identifier = std::string(newIdentifier); - Type = type; -} - -ObjectEntryDescriptor::ObjectEntryDescriptor(const ObjectRepositoryItem& ori) -{ - if (!ori.Identifier.empty()) - { - Generation = ObjectGeneration::JSON; - Identifier = std::string(ori.Identifier); - } - else - { - Generation = ObjectGeneration::DAT; - Entry = ori.ObjectEntry; - } -} - -bool ObjectEntryDescriptor::HasValue() const -{ - return Generation != ObjectGeneration::JSON || !Identifier.empty(); -}; - -ObjectType ObjectEntryDescriptor::GetType() const -{ - return Generation == ObjectGeneration::JSON ? Type : Entry.GetType(); -} - -std::string_view ObjectEntryDescriptor::GetName() const -{ - return Generation == ObjectGeneration::JSON ? Identifier : Entry.GetName(); -} - -std::string ObjectEntryDescriptor::ToString() const -{ - if (Generation == ObjectGeneration::DAT) - { - char buffer[32]; - std::snprintf(&buffer[0], 9, "%08X", Entry.flags); - buffer[8] = '|'; - std::memcpy(&buffer[9], Entry.name, 8); - buffer[17] = '|'; - std::snprintf(&buffer[18], 9, "%8X", Entry.checksum); - return std::string(buffer); - } - else - { - return std::string(GetName()); - } -} - -static uint32_t ParseHex(std::string_view x) -{ - assert(x.size() != 8); - char buffer[9]; - std::memcpy(buffer, x.data(), 8); - buffer[8] = 0; - char* endp{}; - return static_cast(std::strtol(buffer, &endp, 16)); -} - -ObjectEntryDescriptor ObjectEntryDescriptor::Parse(std::string_view identifier) -{ - if (identifier.size() == 26 && identifier[8] == '|' && identifier[17] == '|') - { - RCTObjectEntry entry{}; - entry.flags = ParseHex(identifier.substr(0, 8)); - entry.SetName(identifier.substr(9, 8)); - entry.checksum = ParseHex(identifier.substr(18)); - return ObjectEntryDescriptor(entry); - } - return ObjectEntryDescriptor(identifier); -} - -bool ObjectEntryDescriptor::operator==(const ObjectEntryDescriptor& rhs) const -{ - if (Generation != rhs.Generation) - return false; - if (Generation == ObjectGeneration::DAT) - { - return Entry == rhs.Entry; - } - else - { - return Type == rhs.Type && Identifier == rhs.Identifier; - } -} - -bool ObjectEntryDescriptor::operator!=(const ObjectEntryDescriptor& rhs) const -{ - return !(*this == rhs); -} - -void* Object::GetLegacyData() -{ - throw std::runtime_error("Not supported."); -} - -void Object::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) -{ - throw std::runtime_error("Not supported."); -} - -void Object::PopulateTablesFromJson(IReadObjectContext* context, json_t& root) -{ - _stringTable.ReadJson(root); - _usesFallbackImages = _imageTable.ReadJson(context, root); -} - -std::string Object::GetString(ObjectStringID index) const -{ - return GetStringTable().GetString(index); -} - -std::string Object::GetString(int32_t language, ObjectStringID index) const -{ - return GetStringTable().GetString(language, index); -} - -ObjectEntryDescriptor Object::GetScgPathXHeader() const -{ - return ObjectEntryDescriptor("rct2.scenery_group.scgpathx"); -} - -RCTObjectEntry Object::CreateHeader(const char name[kDatNameLength + 1], uint32_t flags, uint32_t checksum) -{ - RCTObjectEntry header = {}; - header.flags = flags; - std::copy_n(name, kDatNameLength, header.name); - header.checksum = checksum; - return header; -} - -std::vector Object::GetSourceGames() -{ - return _sourceGames; -} - -void Object::SetSourceGames(const std::vector& sourceGames) -{ - _sourceGames = sourceGames; -} - +// TODO: why is this here? #ifdef __WARN_SUGGEST_FINAL_METHODS__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsuggest-final-methods" #endif -std::string Object::GetName() const +namespace OpenRCT2 { - return GetString(ObjectStringID::NAME); -} - -std::string Object::GetName(int32_t language) const -{ - return GetString(language, ObjectStringID::NAME); -} - -ImageIndex Object::LoadImages() -{ - if (_baseImageId == kImageIndexUndefined) + ObjectEntryDescriptor::ObjectEntryDescriptor(const RCTObjectEntry& newEntry) { - _baseImageId = GfxObjectAllocateImages(GetImageTable().GetImages(), GetImageTable().GetCount()); + if (!newEntry.IsEmpty()) + { + Generation = ObjectGeneration::DAT; + Entry = newEntry; + } } - return _baseImageId; -} -void Object::UnloadImages() -{ - if (_baseImageId != kImageIndexUndefined) + ObjectEntryDescriptor::ObjectEntryDescriptor(std::string_view newIdentifier) { - GfxObjectFreeImages(_baseImageId, GetImageTable().GetCount()); - _baseImageId = kImageIndexUndefined; + Generation = ObjectGeneration::JSON; + Identifier = std::string(newIdentifier); } -} -void RCTObjectEntry::SetName(std::string_view value) -{ - std::memset(name, ' ', sizeof(name)); - std::memcpy(name, value.data(), std::min(sizeof(name), value.size())); -} + ObjectEntryDescriptor::ObjectEntryDescriptor(ObjectType type, std::string_view newIdentifier) + { + Generation = ObjectGeneration::JSON; + Identifier = std::string(newIdentifier); + Type = type; + } -const std::vector& Object::GetAuthors() const -{ - return _authors; -} + ObjectEntryDescriptor::ObjectEntryDescriptor(const ObjectRepositoryItem& ori) + { + if (!ori.Identifier.empty()) + { + Generation = ObjectGeneration::JSON; + Identifier = std::string(ori.Identifier); + } + else + { + Generation = ObjectGeneration::DAT; + Entry = ori.ObjectEntry; + } + } -void Object::SetAuthors(std::vector&& authors) -{ - _authors = std::move(authors); -} + bool ObjectEntryDescriptor::HasValue() const + { + return Generation != ObjectGeneration::JSON || !Identifier.empty(); + }; -bool Object::IsCompatibilityObject() const -{ - return _isCompatibilityObject; -} -void Object::SetIsCompatibilityObject(const bool on) -{ - _isCompatibilityObject = on; -} + ObjectType ObjectEntryDescriptor::GetType() const + { + return Generation == ObjectGeneration::JSON ? Type : Entry.GetType(); + } -bool RCTObjectEntry::IsEmpty() const -{ - uint64_t a, b; - std::memcpy(&a, reinterpret_cast(this), 8); - std::memcpy(&b, reinterpret_cast(this) + 8, 8); + std::string_view ObjectEntryDescriptor::GetName() const + { + return Generation == ObjectGeneration::JSON ? Identifier : Entry.GetName(); + } - if (a == 0xFFFFFFFFFFFFFFFF && b == 0xFFFFFFFFFFFFFFFF) + std::string ObjectEntryDescriptor::ToString() const + { + if (Generation == ObjectGeneration::DAT) + { + char buffer[32]; + std::snprintf(&buffer[0], 9, "%08X", Entry.flags); + buffer[8] = '|'; + std::memcpy(&buffer[9], Entry.name, 8); + buffer[17] = '|'; + std::snprintf(&buffer[18], 9, "%8X", Entry.checksum); + return std::string(buffer); + } + else + { + return std::string(GetName()); + } + } + + static uint32_t ParseHex(std::string_view x) + { + assert(x.size() != 8); + char buffer[9]; + std::memcpy(buffer, x.data(), 8); + buffer[8] = 0; + char* endp{}; + return static_cast(std::strtol(buffer, &endp, 16)); + } + + ObjectEntryDescriptor ObjectEntryDescriptor::Parse(std::string_view identifier) + { + if (identifier.size() == 26 && identifier[8] == '|' && identifier[17] == '|') + { + RCTObjectEntry entry{}; + entry.flags = ParseHex(identifier.substr(0, 8)); + entry.SetName(identifier.substr(9, 8)); + entry.checksum = ParseHex(identifier.substr(18)); + return ObjectEntryDescriptor(entry); + } + return ObjectEntryDescriptor(identifier); + } + + bool ObjectEntryDescriptor::operator==(const ObjectEntryDescriptor& rhs) const + { + if (Generation != rhs.Generation) + return false; + if (Generation == ObjectGeneration::DAT) + { + return Entry == rhs.Entry; + } + else + { + return Type == rhs.Type && Identifier == rhs.Identifier; + } + } + + bool ObjectEntryDescriptor::operator!=(const ObjectEntryDescriptor& rhs) const + { + return !(*this == rhs); + } + + void* Object::GetLegacyData() + { + throw std::runtime_error("Not supported."); + } + + void Object::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) + { + throw std::runtime_error("Not supported."); + } + + void Object::PopulateTablesFromJson(IReadObjectContext* context, json_t& root) + { + _stringTable.ReadJson(root); + _usesFallbackImages = _imageTable.ReadJson(context, root); + } + + std::string Object::GetString(ObjectStringID index) const + { + return GetStringTable().GetString(index); + } + + std::string Object::GetString(int32_t language, ObjectStringID index) const + { + return GetStringTable().GetString(language, index); + } + + ObjectEntryDescriptor Object::GetScgPathXHeader() const + { + return ObjectEntryDescriptor("rct2.scenery_group.scgpathx"); + } + + RCTObjectEntry Object::CreateHeader(const char name[kDatNameLength + 1], uint32_t flags, uint32_t checksum) + { + RCTObjectEntry header = {}; + header.flags = flags; + std::copy_n(name, kDatNameLength, header.name); + header.checksum = checksum; + return header; + } + + std::vector Object::GetSourceGames() + { + return _sourceGames; + } + + void Object::SetSourceGames(const std::vector& sourceGames) + { + _sourceGames = sourceGames; + } + + std::string Object::GetName() const + { + return GetString(ObjectStringID::NAME); + } + + std::string Object::GetName(int32_t language) const + { + return GetString(language, ObjectStringID::NAME); + } + + ImageIndex Object::LoadImages() + { + if (_baseImageId == kImageIndexUndefined) + { + _baseImageId = GfxObjectAllocateImages(GetImageTable().GetImages(), GetImageTable().GetCount()); + } + return _baseImageId; + } + + void Object::UnloadImages() + { + if (_baseImageId != kImageIndexUndefined) + { + GfxObjectFreeImages(_baseImageId, GetImageTable().GetCount()); + _baseImageId = kImageIndexUndefined; + } + } + + void RCTObjectEntry::SetName(std::string_view value) + { + std::memset(name, ' ', sizeof(name)); + std::memcpy(name, value.data(), std::min(sizeof(name), value.size())); + } + + const std::vector& Object::GetAuthors() const + { + return _authors; + } + + void Object::SetAuthors(std::vector&& authors) + { + _authors = std::move(authors); + } + + bool Object::IsCompatibilityObject() const + { + return _isCompatibilityObject; + } + void Object::SetIsCompatibilityObject(const bool on) + { + _isCompatibilityObject = on; + } + + bool RCTObjectEntry::IsEmpty() const + { + uint64_t a, b; + std::memcpy(&a, reinterpret_cast(this), 8); + std::memcpy(&b, reinterpret_cast(this) + 8, 8); + + if (a == 0xFFFFFFFFFFFFFFFF && b == 0xFFFFFFFFFFFFFFFF) + return true; + if (a == 0 && b == 0) + return true; + return false; + } + + bool RCTObjectEntry::operator==(const RCTObjectEntry& rhs) const + { + const auto a = this; + const auto b = &rhs; + + // If an official object don't bother checking checksum + if ((a->flags & 0xF0) || (b->flags & 0xF0)) + { + if (a->GetType() != b->GetType()) + { + return false; + } + int32_t match = memcmp(a->name, b->name, 8); + if (match) + { + return false; + } + } + else + { + if (a->flags != b->flags) + { + return false; + } + int32_t match = memcmp(a->name, b->name, 8); + if (match) + { + return false; + } + if (a->checksum != b->checksum) + { + return false; + } + } return true; - if (a == 0 && b == 0) - return true; - return false; -} - -bool RCTObjectEntry::operator==(const RCTObjectEntry& rhs) const -{ - const auto a = this; - const auto b = &rhs; - - // If an official object don't bother checking checksum - if ((a->flags & 0xF0) || (b->flags & 0xF0)) - { - if (a->GetType() != b->GetType()) - { - return false; - } - int32_t match = memcmp(a->name, b->name, 8); - if (match) - { - return false; - } - } - else - { - if (a->flags != b->flags) - { - return false; - } - int32_t match = memcmp(a->name, b->name, 8); - if (match) - { - return false; - } - if (a->checksum != b->checksum) - { - return false; - } - } - return true; -} - -bool RCTObjectEntry::operator!=(const RCTObjectEntry& rhs) const -{ - return !(*this == rhs); -} - -bool ObjectAsset::IsAvailable() const -{ - if (_zipPath.empty()) - { - return File::Exists(_path); } - auto zipArchive = Zip::TryOpen(_zipPath, ZipAccess::read); - return zipArchive != nullptr && zipArchive->Exists(_path); -} - -uint64_t ObjectAsset::GetSize() const -{ - if (_zipPath.empty()) + bool RCTObjectEntry::operator!=(const RCTObjectEntry& rhs) const { - return File::GetSize(_path); + return !(*this == rhs); } - auto zipArchive = Zip::TryOpen(_zipPath, ZipAccess::read); - if (zipArchive != nullptr) - { - auto index = zipArchive->GetIndexFromPath(_path); - if (index.has_value()) - { - auto size = zipArchive->GetFileSize(index.value()); - return size; - } - } - return 0; -} - -std::vector ObjectAsset::GetData() const -{ - if (_zipPath.empty()) - { - return File::ReadAllBytes(_path); - } - - auto zipArchive = Zip::TryOpen(_zipPath, ZipAccess::read); - if (zipArchive != nullptr) - { - return zipArchive->GetFileData(_path); - } - return {}; -} - -std::unique_ptr ObjectAsset::GetStream() const -{ - try + bool ObjectAsset::IsAvailable() const { if (_zipPath.empty()) { - return std::make_unique(_path, FileMode::open); + return File::Exists(_path); + } + + auto zipArchive = Zip::TryOpen(_zipPath, ZipAccess::read); + return zipArchive != nullptr && zipArchive->Exists(_path); + } + + uint64_t ObjectAsset::GetSize() const + { + if (_zipPath.empty()) + { + return File::GetSize(_path); } auto zipArchive = Zip::TryOpen(_zipPath, ZipAccess::read); if (zipArchive != nullptr) { - auto stream = zipArchive->GetFileStream(_path); - if (stream != nullptr) + auto index = zipArchive->GetIndexFromPath(_path); + if (index.has_value()) { - return std::make_unique(std::move(zipArchive), std::move(stream)); + auto size = zipArchive->GetFileSize(index.value()); + return size; } } - } - catch (...) - { - } - return {}; -} - -const std::string& ObjectAsset::GetZipPath() const -{ - return _zipPath; -} - -const std::string& ObjectAsset::GetPath() const -{ - return _path; -} - -size_t ObjectAsset::GetHash() const -{ - // Combine hashes of zipPath and path - std::hash hasher; - auto h1 = hasher(_zipPath); - auto h2 = hasher(_path); - // Combine the hashes based on example from https://en.cppreference.com/w/cpp/utility/hash.html - return h1 ^ (h2 << 1); -} - -u8string VersionString(const ObjectVersion& version) -{ - return std::to_string(std::get<0>(version)) + "." + std::to_string(std::get<1>(version)) + "." - + std::to_string(std::get<2>(version)); -} - -ObjectVersion VersionTuple(std::string_view version) -{ - if (version.empty()) - { - return std::make_tuple(0, 0, 0); + return 0; } - auto nums = String::split(version, "."); - uint16_t versions[VersionNumFields] = {}; - if (nums.size() > VersionNumFields) + std::vector ObjectAsset::GetData() const { - LOG_WARNING("%i fields found in version string '%s', expected X.Y.Z", nums.size(), version); - } - if (nums.size() == 0) - { - LOG_WARNING("No fields found in version string '%s', expected X.Y.Z", version); - return std::make_tuple(0, 0, 0); - } - try - { - size_t highestIndex = std::min(nums.size(), VersionNumFields); - for (size_t i = 0; i < highestIndex; i++) + if (_zipPath.empty()) { - auto value = stoll(nums.at(i)); - constexpr auto maxValue = std::numeric_limits().max(); - if (value > maxValue) - { - LOG_WARNING( - "Version value too high in version string '%.*s', version value will be capped to %i.", - static_cast(version.size()), version.data(), maxValue); - value = maxValue; - } - versions[i] = value; + return File::ReadAllBytes(_path); } + + auto zipArchive = Zip::TryOpen(_zipPath, ZipAccess::read); + if (zipArchive != nullptr) + { + return zipArchive->GetFileData(_path); + } + return {}; } - catch (const std::exception&) + + std::unique_ptr ObjectAsset::GetStream() const { - LOG_WARNING("Malformed version string '%.*s', expected X.Y.Z", static_cast(version.size()), version.data()); + try + { + if (_zipPath.empty()) + { + return std::make_unique(_path, FileMode::open); + } + + auto zipArchive = Zip::TryOpen(_zipPath, ZipAccess::read); + if (zipArchive != nullptr) + { + auto stream = zipArchive->GetFileStream(_path); + if (stream != nullptr) + { + return std::make_unique(std::move(zipArchive), std::move(stream)); + } + } + } + catch (...) + { + } + return {}; } - return std::make_tuple(versions[0], versions[1], versions[2]); -} + const std::string& ObjectAsset::GetZipPath() const + { + return _zipPath; + } + const std::string& ObjectAsset::GetPath() const + { + return _path; + } + + size_t ObjectAsset::GetHash() const + { + // Combine hashes of zipPath and path + std::hash hasher; + auto h1 = hasher(_zipPath); + auto h2 = hasher(_path); + // Combine the hashes based on example from https://en.cppreference.com/w/cpp/utility/hash.html + return h1 ^ (h2 << 1); + } + + u8string VersionString(const ObjectVersion& version) + { + return std::to_string(std::get<0>(version)) + "." + std::to_string(std::get<1>(version)) + "." + + std::to_string(std::get<2>(version)); + } + + ObjectVersion VersionTuple(std::string_view version) + { + if (version.empty()) + { + return std::make_tuple(0, 0, 0); + } + + auto nums = String::split(version, "."); + uint16_t versions[VersionNumFields] = {}; + if (nums.size() > VersionNumFields) + { + LOG_WARNING("%i fields found in version string '%s', expected X.Y.Z", nums.size(), version); + } + if (nums.size() == 0) + { + LOG_WARNING("No fields found in version string '%s', expected X.Y.Z", version); + return std::make_tuple(0, 0, 0); + } + try + { + size_t highestIndex = std::min(nums.size(), VersionNumFields); + for (size_t i = 0; i < highestIndex; i++) + { + auto value = stoll(nums.at(i)); + constexpr auto maxValue = std::numeric_limits().max(); + if (value > maxValue) + { + LOG_WARNING( + "Version value too high in version string '%.*s', version value will be capped to %i.", + static_cast(version.size()), version.data(), maxValue); + value = maxValue; + } + versions[i] = value; + } + } + catch (const std::exception&) + { + LOG_WARNING("Malformed version string '%.*s', expected X.Y.Z", static_cast(version.size()), version.data()); + } + + return std::make_tuple(versions[0], versions[1], versions[2]); + } + +} // namespace OpenRCT2 + +// TODO: why is this here? #ifdef __WARN_SUGGEST_FINAL_METHODS__ #pragma GCC diagnostic pop #endif diff --git a/src/openrct2/object/Object.h b/src/openrct2/object/Object.h index 9f2bed6d4f..fae9216340 100644 --- a/src/openrct2/object/Object.h +++ b/src/openrct2/object/Object.h @@ -22,345 +22,346 @@ #include #include -struct ObjectRepositoryItem; +struct RenderTarget; using ride_type_t = uint16_t; -constexpr size_t VersionNumFields = 3; -using ObjectVersion = std::tuple; -static_assert(std::tuple_size{} == VersionNumFields); - -namespace OpenRCT2::ObjectSelectionFlags +namespace OpenRCT2 { - constexpr uint8_t Selected = (1 << 0); - constexpr uint8_t InUse = (1 << 2); - // constexpr uint8_t Required = (1 << 3); // Unused feature - constexpr uint8_t AlwaysRequired = (1 << 4); - constexpr uint8_t Flag6 = (1 << 5); - constexpr uint8_t AllFlags = 0xFF; -}; // namespace OpenRCT2::ObjectSelectionFlags + struct ObjectRepositoryItem; -enum class ObjectSourceGame : uint8_t -{ - Custom, - WackyWorlds, - TimeTwister, - OpenRCT2Official, - RCT1, - AddedAttractions, - LoopyLandscapes, - RCT2 = 8 -}; + constexpr size_t VersionNumFields = 3; + using ObjectVersion = std::tuple; + static_assert(std::tuple_size{} == VersionNumFields); + + namespace ObjectSelectionFlags + { + constexpr uint8_t Selected = (1 << 0); + constexpr uint8_t InUse = (1 << 2); + // constexpr uint8_t Required = (1 << 3); // Unused feature + constexpr uint8_t AlwaysRequired = (1 << 4); + constexpr uint8_t Flag6 = (1 << 5); + constexpr uint8_t AllFlags = 0xFF; + }; // namespace ObjectSelectionFlags + + enum class ObjectSourceGame : uint8_t + { + Custom, + WackyWorlds, + TimeTwister, + OpenRCT2Official, + RCT1, + AddedAttractions, + LoopyLandscapes, + RCT2 = 8 + }; #pragma pack(push, 1) -/** - * Object entry structure. - * size: 0x10 - */ -struct RCTObjectEntry -{ - union + /** + * Object entry structure. + * size: 0x10 + */ + struct RCTObjectEntry { - uint8_t end_flag; // needed not to read past allocated buffer. - uint32_t flags; - }; - union - { - char nameWOC[12]; - struct + union { - char name[8]; - uint32_t checksum; + uint8_t end_flag; // needed not to read past allocated buffer. + uint32_t flags; }; + union + { + char nameWOC[12]; + struct + { + char name[8]; + uint32_t checksum; + }; + }; + + std::string_view GetName() const + { + return std::string_view(name, std::size(name)); + } + + void SetName(std::string_view value); + + ObjectType GetType() const + { + return static_cast(flags & 0x0F); + } + + void SetType(ObjectType newType) + { + flags &= ~0x0F; + flags |= (static_cast(newType) & 0x0F); + } + + ObjectSourceGame GetSourceGame() const + { + return static_cast((flags & 0xF0) >> 4); + } + + bool IsEmpty() const; + bool operator==(const RCTObjectEntry& rhs) const; + bool operator!=(const RCTObjectEntry& rhs) const; }; - - std::string_view GetName() const - { - return std::string_view(name, std::size(name)); - } - - void SetName(std::string_view value); - - ObjectType GetType() const - { - return static_cast(flags & 0x0F); - } - - void SetType(ObjectType newType) - { - flags &= ~0x0F; - flags |= (static_cast(newType) & 0x0F); - } - - ObjectSourceGame GetSourceGame() const - { - return static_cast((flags & 0xF0) >> 4); - } - - bool IsEmpty() const; - bool operator==(const RCTObjectEntry& rhs) const; - bool operator!=(const RCTObjectEntry& rhs) const; -}; -static_assert(sizeof(RCTObjectEntry) == 0x10); + static_assert(sizeof(RCTObjectEntry) == 0x10); #pragma pack(pop) -struct RideFilters -{ - uint8_t category[2]; - ride_type_t ride_type; -}; + struct RideFilters + { + uint8_t category[2]; + ride_type_t ride_type; + }; -enum class ObjectGeneration : uint8_t -{ - DAT, - JSON, -}; + enum class ObjectGeneration : uint8_t + { + DAT, + JSON, + }; -struct ObjectEntryDescriptor -{ - ObjectGeneration Generation = ObjectGeneration::JSON; + struct ObjectEntryDescriptor + { + ObjectGeneration Generation = ObjectGeneration::JSON; - // DAT - RCTObjectEntry Entry{}; + // DAT + RCTObjectEntry Entry{}; - // JSON - ObjectType Type{}; - std::string Identifier; - ObjectVersion Version; + // JSON + ObjectType Type{}; + std::string Identifier; + ObjectVersion Version; - ObjectEntryDescriptor() = default; - explicit ObjectEntryDescriptor(const RCTObjectEntry& newEntry); - explicit ObjectEntryDescriptor(std::string_view newIdentifier); - explicit ObjectEntryDescriptor(ObjectType type, std::string_view newIdentifier); - explicit ObjectEntryDescriptor(const ObjectRepositoryItem& ori); - bool HasValue() const; - ObjectType GetType() const; - std::string_view GetName() const; - std::string ToString() const; + ObjectEntryDescriptor() = default; + explicit ObjectEntryDescriptor(const RCTObjectEntry& newEntry); + explicit ObjectEntryDescriptor(std::string_view newIdentifier); + explicit ObjectEntryDescriptor(ObjectType type, std::string_view newIdentifier); + explicit ObjectEntryDescriptor(const ObjectRepositoryItem& ori); + bool HasValue() const; + ObjectType GetType() const; + std::string_view GetName() const; + std::string ToString() const; - bool operator==(const ObjectEntryDescriptor& rhs) const; - bool operator!=(const ObjectEntryDescriptor& rhs) const; + bool operator==(const ObjectEntryDescriptor& rhs) const; + bool operator!=(const ObjectEntryDescriptor& rhs) const; - static ObjectEntryDescriptor Parse(std::string_view identifier); -}; + static ObjectEntryDescriptor Parse(std::string_view identifier); + }; -struct IObjectRepository; -namespace OpenRCT2 -{ + struct IObjectRepository; struct IStream; -} -struct ObjectRepositoryItem; -struct RenderTarget; + struct ObjectRepositoryItem; -enum class ObjectError : uint32_t -{ - Ok, - Unknown, - BadEncoding, - InvalidProperty, - BadStringTable, - BadImageTable, - UnexpectedEOF, -}; + enum class ObjectError : uint32_t + { + Ok, + Unknown, + BadEncoding, + InvalidProperty, + BadStringTable, + BadImageTable, + UnexpectedEOF, + }; -struct IReadObjectContext -{ - virtual ~IReadObjectContext() = default; + struct IReadObjectContext + { + virtual ~IReadObjectContext() = default; - virtual std::string_view GetObjectIdentifier() = 0; - virtual IObjectRepository& GetObjectRepository() = 0; - virtual bool ShouldLoadImages() = 0; - virtual std::vector GetData(std::string_view path) = 0; - virtual ObjectAsset GetAsset(std::string_view path) = 0; + virtual std::string_view GetObjectIdentifier() = 0; + virtual IObjectRepository& GetObjectRepository() = 0; + virtual bool ShouldLoadImages() = 0; + virtual std::vector GetData(std::string_view path) = 0; + virtual ObjectAsset GetAsset(std::string_view path) = 0; - virtual void LogVerbose(ObjectError code, const utf8* text) = 0; - virtual void LogWarning(ObjectError code, const utf8* text) = 0; - virtual void LogError(ObjectError code, const utf8* text) = 0; -}; + virtual void LogVerbose(ObjectError code, const utf8* text) = 0; + virtual void LogWarning(ObjectError code, const utf8* text) = 0; + virtual void LogError(ObjectError code, const utf8* text) = 0; + }; #ifdef __WARN_SUGGEST_FINAL_TYPES__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsuggest-final-types" #pragma GCC diagnostic ignored "-Wsuggest-final-methods" #endif -class Object -{ -private: - std::string _identifier; - ObjectVersion _version; - ObjectEntryDescriptor _descriptor{}; - StringTable _stringTable; - ImageTable _imageTable; - std::vector _sourceGames; - std::vector _authors; - ObjectGeneration _generation{}; - bool _usesFallbackImages{}; - bool _isCompatibilityObject{}; - ImageIndex _baseImageId{ kImageIndexUndefined }; - std::string _fileName; - -protected: - StringTable& GetStringTable() + class Object { - return _stringTable; - } - const StringTable& GetStringTable() const - { - return _stringTable; - } - ImageTable& GetImageTable() - { - return _imageTable; - } + private: + std::string _identifier; + ObjectVersion _version; + ObjectEntryDescriptor _descriptor{}; + StringTable _stringTable; + ImageTable _imageTable; + std::vector _sourceGames; + std::vector _authors; + ObjectGeneration _generation{}; + bool _usesFallbackImages{}; + bool _isCompatibilityObject{}; + ImageIndex _baseImageId{ kImageIndexUndefined }; + std::string _fileName; - /** - * Populates the image and string tables from a JSON object - * @param context - * @param root JSON node of type object containing image and string info - * @note root is deliberately left non-const: json_t behaviour changes when const - */ - void PopulateTablesFromJson(IReadObjectContext* context, json_t& root); + protected: + StringTable& GetStringTable() + { + return _stringTable; + } + const StringTable& GetStringTable() const + { + return _stringTable; + } + ImageTable& GetImageTable() + { + return _imageTable; + } - std::string GetString(ObjectStringID index) const; - std::string GetString(int32_t language, ObjectStringID index) const; + /** + * Populates the image and string tables from a JSON object + * @param context + * @param root JSON node of type object containing image and string info + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + void PopulateTablesFromJson(IReadObjectContext* context, json_t& root); -public: - virtual ~Object() = default; + std::string GetString(ObjectStringID index) const; + std::string GetString(int32_t language, ObjectStringID index) const; - std::string_view GetIdentifier() const - { - return _identifier; - } - void SetIdentifier(std::string_view identifier) - { - _identifier = identifier; - } + public: + virtual ~Object() = default; - void MarkAsJsonObject() - { - _generation = ObjectGeneration::JSON; - } + std::string_view GetIdentifier() const + { + return _identifier; + } + void SetIdentifier(std::string_view identifier) + { + _identifier = identifier; + } - ObjectGeneration GetGeneration() const - { - return _generation; + void MarkAsJsonObject() + { + _generation = ObjectGeneration::JSON; + } + + ObjectGeneration GetGeneration() const + { + return _generation; + }; + + ObjectType GetObjectType() const + { + return _descriptor.GetType(); + } + + ObjectEntryDescriptor GetDescriptor() const + { + return _descriptor; + } + void SetDescriptor(const ObjectEntryDescriptor& value) + { + _descriptor = value; + } + + std::string_view GetFileName() const + { + return _fileName; + } + void SetFileName(const std::string_view fileName) + { + _fileName = fileName; + } + + constexpr bool UsesFallbackImages() const + { + return _usesFallbackImages; + } + + // DONOT USE THIS CAN LEAD TO OBJECT COLLISIONS + std::string_view GetLegacyIdentifier() const + { + return _descriptor.GetName(); + } + + // TODO remove this, we should no longer assume objects have a legacy object entry + const RCTObjectEntry& GetObjectEntry() const + { + return _descriptor.Entry; + } + virtual void* GetLegacyData(); + + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + virtual void ReadJson(IReadObjectContext* /*context*/, json_t& /*root*/) + { + } + virtual void ReadLegacy(IReadObjectContext* context, IStream* stream); + virtual void Load() = 0; + virtual void Unload() = 0; + + virtual void DrawPreview(RenderTarget& /*rt*/, int32_t /*width*/, int32_t /*height*/) const + { + } + + virtual std::string GetName() const; + virtual std::string GetName(int32_t language) const; + + virtual void SetRepositoryItem(ObjectRepositoryItem* /*item*/) const + { + } + std::vector GetSourceGames(); + void SetSourceGames(const std::vector& sourceGames); + + const std::vector& GetAuthors() const; + void SetAuthors(std::vector&& authors); + const ObjectVersion& GetVersion() const + { + return _version; + } + void SetVersion(const ObjectVersion& version) + { + _version = version; + } + + bool IsCompatibilityObject() const; + void SetIsCompatibilityObject(const bool on); + + const ImageTable& GetImageTable() const + { + return _imageTable; + } + + ObjectEntryDescriptor GetScgPathXHeader() const; + RCTObjectEntry CreateHeader(const char name[9], uint32_t flags, uint32_t checksum); + + uint32_t GetNumImages() const + { + return GetImageTable().GetCount(); + } + + ImageIndex GetBaseImageId() const + { + return _baseImageId; + } + + uint32_t LoadImages(); + void UnloadImages(); }; - - ObjectType GetObjectType() const - { - return _descriptor.GetType(); - } - - ObjectEntryDescriptor GetDescriptor() const - { - return _descriptor; - } - void SetDescriptor(const ObjectEntryDescriptor& value) - { - _descriptor = value; - } - - std::string_view GetFileName() const - { - return _fileName; - } - void SetFileName(const std::string_view fileName) - { - _fileName = fileName; - } - - constexpr bool UsesFallbackImages() const - { - return _usesFallbackImages; - } - - // DONOT USE THIS CAN LEAD TO OBJECT COLLISIONS - std::string_view GetLegacyIdentifier() const - { - return _descriptor.GetName(); - } - - // TODO remove this, we should no longer assume objects have a legacy object entry - const RCTObjectEntry& GetObjectEntry() const - { - return _descriptor.Entry; - } - virtual void* GetLegacyData(); - - /** - * @note root is deliberately left non-const: json_t behaviour changes when const - */ - virtual void ReadJson(IReadObjectContext* /*context*/, json_t& /*root*/) - { - } - virtual void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream); - virtual void Load() = 0; - virtual void Unload() = 0; - - virtual void DrawPreview(RenderTarget& /*rt*/, int32_t /*width*/, int32_t /*height*/) const - { - } - - virtual std::string GetName() const; - virtual std::string GetName(int32_t language) const; - - virtual void SetRepositoryItem(ObjectRepositoryItem* /*item*/) const - { - } - std::vector GetSourceGames(); - void SetSourceGames(const std::vector& sourceGames); - - const std::vector& GetAuthors() const; - void SetAuthors(std::vector&& authors); - const ObjectVersion& GetVersion() const - { - return _version; - } - void SetVersion(const ObjectVersion& version) - { - _version = version; - } - - bool IsCompatibilityObject() const; - void SetIsCompatibilityObject(const bool on); - - const ImageTable& GetImageTable() const - { - return _imageTable; - } - - ObjectEntryDescriptor GetScgPathXHeader() const; - RCTObjectEntry CreateHeader(const char name[9], uint32_t flags, uint32_t checksum); - - uint32_t GetNumImages() const - { - return GetImageTable().GetCount(); - } - - ImageIndex GetBaseImageId() const - { - return _baseImageId; - } - - uint32_t LoadImages(); - void UnloadImages(); -}; #ifdef __WARN_SUGGEST_FINAL_TYPES__ #pragma GCC diagnostic pop #endif -int32_t ObjectCalculateChecksum(const RCTObjectEntry* entry, const void* data, size_t dataLength); -void ObjectCreateIdentifierName(char* string_buffer, size_t size, const RCTObjectEntry* object); + int32_t ObjectCalculateChecksum(const RCTObjectEntry* entry, const void* data, size_t dataLength); + void ObjectCreateIdentifierName(char* string_buffer, size_t size, const RCTObjectEntry* object); -void ObjectEntryGetNameFixed(utf8* buffer, size_t bufferSize, const RCTObjectEntry* entry); + void ObjectEntryGetNameFixed(utf8* buffer, size_t bufferSize, const RCTObjectEntry* entry); -void* ObjectEntryGetChunk(ObjectType objectType, ObjectEntryIndex index); -const Object* ObjectEntryGetObject(ObjectType objectType, ObjectEntryIndex index); + void* ObjectEntryGetChunk(ObjectType objectType, ObjectEntryIndex index); + const Object* ObjectEntryGetObject(ObjectType objectType, ObjectEntryIndex index); -constexpr bool IsIntransientObjectType(ObjectType type) -{ - return type == ObjectType::audio; -} + constexpr bool IsIntransientObjectType(ObjectType type) + { + return type == ObjectType::audio; + } -u8string VersionString(const ObjectVersion& version); -ObjectVersion VersionTuple(std::string_view version); + u8string VersionString(const ObjectVersion& version); + ObjectVersion VersionTuple(std::string_view version); +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectAsset.h b/src/openrct2/object/ObjectAsset.h index 600510469d..5b0f9a7649 100644 --- a/src/openrct2/object/ObjectAsset.h +++ b/src/openrct2/object/ObjectAsset.h @@ -9,7 +9,6 @@ #pragma once -#include #include #include #include @@ -17,41 +16,41 @@ namespace OpenRCT2 { struct IStream; -} -class ObjectAsset -{ -private: - std::string _zipPath; - std::string _path; - -public: - ObjectAsset() = default; - ObjectAsset(std::string_view path) - : _path(path) + class ObjectAsset { - } - ObjectAsset(std::string_view zipPath, std::string_view path) - : _zipPath(zipPath) - , _path(path) - { - } + private: + std::string _zipPath; + std::string _path; - [[nodiscard]] bool IsAvailable() const; - [[nodiscard]] uint64_t GetSize() const; - [[nodiscard]] std::vector GetData() const; - [[nodiscard]] std::unique_ptr GetStream() const; - const std::string& GetZipPath() const; - const std::string& GetPath() const; - size_t GetHash() const; + public: + ObjectAsset() = default; + ObjectAsset(std::string_view path) + : _path(path) + { + } + ObjectAsset(std::string_view zipPath, std::string_view path) + : _zipPath(zipPath) + , _path(path) + { + } - friend bool operator==(const ObjectAsset& l, const ObjectAsset& r); -}; + [[nodiscard]] bool IsAvailable() const; + [[nodiscard]] uint64_t GetSize() const; + [[nodiscard]] std::vector GetData() const; + [[nodiscard]] std::unique_ptr GetStream() const; + const std::string& GetZipPath() const; + const std::string& GetPath() const; + size_t GetHash() const; + + friend bool operator==(const ObjectAsset& l, const ObjectAsset& r); + }; +} // namespace OpenRCT2 template<> -struct std::hash +struct std::hash { - std::size_t operator()(const ObjectAsset& asset) const noexcept + std::size_t operator()(const OpenRCT2::ObjectAsset& asset) const noexcept { return asset.GetHash(); } diff --git a/src/openrct2/object/ObjectFactory.cpp b/src/openrct2/object/ObjectFactory.cpp index 84357b5b92..0b6184b601 100644 --- a/src/openrct2/object/ObjectFactory.cpp +++ b/src/openrct2/object/ObjectFactory.cpp @@ -53,171 +53,173 @@ #include #include -using namespace OpenRCT2; -using namespace OpenRCT2::SawyerCoding; - -struct IFileDataRetriever +namespace OpenRCT2 { - virtual ~IFileDataRetriever() = default; - virtual std::vector GetData(std::string_view path) const = 0; - virtual ObjectAsset GetAsset(std::string_view path) const = 0; -}; + using namespace OpenRCT2::SawyerCoding; -class FileSystemDataRetriever : public IFileDataRetriever -{ -private: - std::string _basePath; - -public: - FileSystemDataRetriever(std::string_view basePath) - : _basePath(basePath) + struct IFileDataRetriever { - } + virtual ~IFileDataRetriever() = default; + virtual std::vector GetData(std::string_view path) const = 0; + virtual ObjectAsset GetAsset(std::string_view path) const = 0; + }; - std::vector GetData(std::string_view path) const override + class FileSystemDataRetriever : public IFileDataRetriever { - auto absolutePath = Path::Combine(_basePath, path); - return File::ReadAllBytes(absolutePath); - } + private: + std::string _basePath; - ObjectAsset GetAsset(std::string_view path) const override - { - if (Path::IsAbsolute(path)) + public: + FileSystemDataRetriever(std::string_view basePath) + : _basePath(basePath) { - return ObjectAsset(path); } - else + + std::vector GetData(std::string_view path) const override { auto absolutePath = Path::Combine(_basePath, path); - return ObjectAsset(absolutePath); + return File::ReadAllBytes(absolutePath); } - } -}; -class ZipDataRetriever : public IFileDataRetriever -{ -private: - const std::string _path; - const IZipArchive& _zipArchive; - -public: - ZipDataRetriever(std::string_view path, const IZipArchive& zipArchive) - : _path(path) - , _zipArchive(zipArchive) - { - } - - std::vector GetData(std::string_view path) const override - { - return _zipArchive.GetFileData(path); - } - - ObjectAsset GetAsset(std::string_view path) const override - { - return ObjectAsset(_path, path); - } -}; - -class ReadObjectContext : public IReadObjectContext -{ -private: - IObjectRepository& _objectRepository; - const IFileDataRetriever* _fileDataRetriever; - - std::string _identifier; - bool _loadImages; - std::string _basePath; - bool _wasVerbose = false; - bool _wasWarning = false; - bool _wasError = false; - -public: - bool WasVerbose() const - { - return _wasVerbose; - } - bool WasWarning() const - { - return _wasWarning; - } - bool WasError() const - { - return _wasError; - } - - ReadObjectContext( - IObjectRepository& objectRepository, const std::string& identifier, bool loadImages, - const IFileDataRetriever* fileDataRetriever) - : _objectRepository(objectRepository) - , _fileDataRetriever(fileDataRetriever) - , _identifier(identifier) - , _loadImages(loadImages) - { - } - - std::string_view GetObjectIdentifier() override - { - return _identifier; - } - - IObjectRepository& GetObjectRepository() override - { - return _objectRepository; - } - - bool ShouldLoadImages() override - { - return _loadImages; - } - - std::vector GetData(std::string_view path) override - { - if (_fileDataRetriever != nullptr) + ObjectAsset GetAsset(std::string_view path) const override { - return _fileDataRetriever->GetData(path); + if (Path::IsAbsolute(path)) + { + return ObjectAsset(path); + } + else + { + auto absolutePath = Path::Combine(_basePath, path); + return ObjectAsset(absolutePath); + } } - return {}; - } + }; - ObjectAsset GetAsset(std::string_view path) override + class ZipDataRetriever : public IFileDataRetriever { - if (_fileDataRetriever != nullptr) - { - return _fileDataRetriever->GetAsset(path); - } - return {}; - } + private: + const std::string _path; + const IZipArchive& _zipArchive; - void LogVerbose(ObjectError code, const utf8* text) override + public: + ZipDataRetriever(std::string_view path, const IZipArchive& zipArchive) + : _path(path) + , _zipArchive(zipArchive) + { + } + + std::vector GetData(std::string_view path) const override + { + return _zipArchive.GetFileData(path); + } + + ObjectAsset GetAsset(std::string_view path) const override + { + return ObjectAsset(_path, path); + } + }; + + class ReadObjectContext : public IReadObjectContext { - _wasVerbose = true; + private: + IObjectRepository& _objectRepository; + const IFileDataRetriever* _fileDataRetriever; - if (!String::isNullOrEmpty(text)) + std::string _identifier; + bool _loadImages; + std::string _basePath; + bool _wasVerbose = false; + bool _wasWarning = false; + bool _wasError = false; + + public: + bool WasVerbose() const { - LOG_VERBOSE("[%s] Info (%d): %s", _identifier.c_str(), code, text); + return _wasVerbose; } - } - - void LogWarning(ObjectError code, const utf8* text) override - { - _wasWarning = true; - - if (!String::isNullOrEmpty(text)) + bool WasWarning() const { - Console::Error::WriteLine("[%s] Warning (%d): %s", _identifier.c_str(), code, text); + return _wasWarning; } - } - - void LogError(ObjectError code, const utf8* text) override - { - _wasError = true; - - if (!String::isNullOrEmpty(text)) + bool WasError() const { - Console::Error::WriteLine("[%s] Error (%d): %s", _identifier.c_str(), code, text); + return _wasError; } - } -}; + + ReadObjectContext( + IObjectRepository& objectRepository, const std::string& identifier, bool loadImages, + const IFileDataRetriever* fileDataRetriever) + : _objectRepository(objectRepository) + , _fileDataRetriever(fileDataRetriever) + , _identifier(identifier) + , _loadImages(loadImages) + { + } + + std::string_view GetObjectIdentifier() override + { + return _identifier; + } + + IObjectRepository& GetObjectRepository() override + { + return _objectRepository; + } + + bool ShouldLoadImages() override + { + return _loadImages; + } + + std::vector GetData(std::string_view path) override + { + if (_fileDataRetriever != nullptr) + { + return _fileDataRetriever->GetData(path); + } + return {}; + } + + ObjectAsset GetAsset(std::string_view path) override + { + if (_fileDataRetriever != nullptr) + { + return _fileDataRetriever->GetAsset(path); + } + return {}; + } + + void LogVerbose(ObjectError code, const utf8* text) override + { + _wasVerbose = true; + + if (!String::isNullOrEmpty(text)) + { + LOG_VERBOSE("[%s] Info (%d): %s", _identifier.c_str(), code, text); + } + } + + void LogWarning(ObjectError code, const utf8* text) override + { + _wasWarning = true; + + if (!String::isNullOrEmpty(text)) + { + Console::Error::WriteLine("[%s] Warning (%d): %s", _identifier.c_str(), code, text); + } + } + + void LogError(ObjectError code, const utf8* text) override + { + _wasError = true; + + if (!String::isNullOrEmpty(text)) + { + Console::Error::WriteLine("[%s] Error (%d): %s", _identifier.c_str(), code, text); + } + } + }; +} // namespace OpenRCT2 namespace OpenRCT2::ObjectFactory { diff --git a/src/openrct2/object/ObjectFactory.h b/src/openrct2/object/ObjectFactory.h index a87f4b7434..3510919f8e 100644 --- a/src/openrct2/object/ObjectFactory.h +++ b/src/openrct2/object/ObjectFactory.h @@ -14,10 +14,13 @@ #include #include -struct IObjectRepository; -class Object; -struct RCTObjectEntry; -enum class ObjectType : uint8_t; +namespace OpenRCT2 +{ + struct IObjectRepository; + class Object; + struct RCTObjectEntry; + enum class ObjectType : uint8_t; +} // namespace OpenRCT2 namespace OpenRCT2::ObjectFactory { diff --git a/src/openrct2/object/ObjectLimits.h b/src/openrct2/object/ObjectLimits.h index ce0360e4b7..cd2af1fa22 100644 --- a/src/openrct2/object/ObjectLimits.h +++ b/src/openrct2/object/ObjectLimits.h @@ -11,28 +11,31 @@ #include -// Maximums based on number of values that can be represented in bit group. -// Subtract 1 to reserve the NULL entry identifier. -constexpr uint16_t kMaxRideObjects = 2047; -constexpr uint16_t kMaxSmallSceneryObjects = 2047; -constexpr uint16_t kMaxLargeSceneryObjects = 2047; -constexpr uint16_t kMaxWallSceneryObjects = 2047; -constexpr uint16_t kMaxBannerObjects = 255; -constexpr uint16_t kMaxPathObjects = 255; -constexpr uint16_t kMaxPathAdditionObjects = 255; -constexpr uint16_t kMaxSceneryGroupObjects = 255; -constexpr uint16_t kMaxParkEntranceObjects = 255; -constexpr uint16_t kMaxWaterObjects = 1; -constexpr uint16_t kMaxScenarioMetaObjects = 1; -constexpr uint16_t kMaxTerrainSurfaceObjects = 255; -constexpr uint16_t kMaxTerrainEdgeObjects = 255; -constexpr uint16_t kMaxStationObjects = 255; -constexpr uint16_t kMaxMusicObjects = 255; -constexpr uint16_t kMaxFootpathSurfaceObjects = 255; -constexpr uint16_t kMaxFootpathRailingsObjects = 255; -constexpr uint16_t kMaxAudioObjects = 255; -constexpr uint16_t kMaxPeepNamesObjects = 1; -constexpr uint16_t kMaxPeepAnimationsObjects = 255; -constexpr uint16_t kMaxClimateObjects = 1; +namespace OpenRCT2 +{ + // Maximums based on number of values that can be represented in bit group. + // Subtract 1 to reserve the NULL entry identifier. + constexpr uint16_t kMaxRideObjects = 2047; + constexpr uint16_t kMaxSmallSceneryObjects = 2047; + constexpr uint16_t kMaxLargeSceneryObjects = 2047; + constexpr uint16_t kMaxWallSceneryObjects = 2047; + constexpr uint16_t kMaxBannerObjects = 255; + constexpr uint16_t kMaxPathObjects = 255; + constexpr uint16_t kMaxPathAdditionObjects = 255; + constexpr uint16_t kMaxSceneryGroupObjects = 255; + constexpr uint16_t kMaxParkEntranceObjects = 255; + constexpr uint16_t kMaxWaterObjects = 1; + constexpr uint16_t kMaxScenarioMetaObjects = 1; + constexpr uint16_t kMaxTerrainSurfaceObjects = 255; + constexpr uint16_t kMaxTerrainEdgeObjects = 255; + constexpr uint16_t kMaxStationObjects = 255; + constexpr uint16_t kMaxMusicObjects = 255; + constexpr uint16_t kMaxFootpathSurfaceObjects = 255; + constexpr uint16_t kMaxFootpathRailingsObjects = 255; + constexpr uint16_t kMaxAudioObjects = 255; + constexpr uint16_t kMaxPeepNamesObjects = 1; + constexpr uint16_t kMaxPeepAnimationsObjects = 255; + constexpr uint16_t kMaxClimateObjects = 1; -constexpr uint8_t kDatNameLength = 8; + constexpr uint8_t kDatNameLength = 8; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectList.cpp b/src/openrct2/object/ObjectList.cpp index f30525ad5d..56f3fe57b6 100644 --- a/src/openrct2/object/ObjectList.cpp +++ b/src/openrct2/object/ObjectList.cpp @@ -21,237 +21,240 @@ #include #include -// 0x0098DA00 -static constexpr std::array kObjectEntryGroupCounts = { - kMaxRideObjects, // rides - kMaxSmallSceneryObjects, // small scenery - kMaxLargeSceneryObjects, // large scenery - kMaxWallSceneryObjects, // walls - kMaxBannerObjects, // banners - kMaxPathObjects, // paths - kMaxPathAdditionObjects, // path additions - kMaxSceneryGroupObjects, // scenery sets - kMaxParkEntranceObjects, // park entrance - kMaxWaterObjects, // water - kMaxScenarioMetaObjects, // scenario meta - kMaxTerrainSurfaceObjects, kMaxTerrainEdgeObjects, kMaxStationObjects, - kMaxMusicObjects, kMaxFootpathSurfaceObjects, kMaxFootpathRailingsObjects, - kMaxAudioObjects, kMaxPeepNamesObjects, kMaxPeepAnimationsObjects, - kMaxClimateObjects, -}; -static_assert(std::size(kObjectEntryGroupCounts) == EnumValue(ObjectType::count)); - -size_t getObjectEntryGroupCount(ObjectType objectType) +namespace OpenRCT2 { - return kObjectEntryGroupCounts[EnumValue(objectType)]; -} + // 0x0098DA00 + static constexpr std::array kObjectEntryGroupCounts = { + kMaxRideObjects, // rides + kMaxSmallSceneryObjects, // small scenery + kMaxLargeSceneryObjects, // large scenery + kMaxWallSceneryObjects, // walls + kMaxBannerObjects, // banners + kMaxPathObjects, // paths + kMaxPathAdditionObjects, // path additions + kMaxSceneryGroupObjects, // scenery sets + kMaxParkEntranceObjects, // park entrance + kMaxWaterObjects, // water + kMaxScenarioMetaObjects, // scenario meta + kMaxTerrainSurfaceObjects, kMaxTerrainEdgeObjects, kMaxStationObjects, + kMaxMusicObjects, kMaxFootpathSurfaceObjects, kMaxFootpathRailingsObjects, + kMaxAudioObjects, kMaxPeepNamesObjects, kMaxPeepAnimationsObjects, + kMaxClimateObjects, + }; + static_assert(std::size(kObjectEntryGroupCounts) == EnumValue(ObjectType::count)); -size_t getObjectTypeLimit(ObjectType type) -{ - auto index = EnumValue(type); - if (index >= EnumValue(ObjectType::count)) - return 0; - return static_cast(kObjectEntryGroupCounts[index]); -} - -ObjectList::const_iterator::const_iterator(const ObjectList* parent, bool end) -{ - _parent = parent; - _subList = _parent->_subLists.size(); - _index = 0; -} - -void ObjectList::const_iterator::MoveToNextEntry() -{ - do + size_t getObjectEntryGroupCount(ObjectType objectType) { - if (_subList < _parent->_subLists.size()) + return kObjectEntryGroupCounts[EnumValue(objectType)]; + } + + size_t getObjectTypeLimit(ObjectType type) + { + auto index = EnumValue(type); + if (index >= EnumValue(ObjectType::count)) + return 0; + return static_cast(kObjectEntryGroupCounts[index]); + } + + ObjectList::const_iterator::const_iterator(const ObjectList* parent, bool end) + { + _parent = parent; + _subList = _parent->_subLists.size(); + _index = 0; + } + + void ObjectList::const_iterator::MoveToNextEntry() + { + do { - auto subListSize = _parent->_subLists[_subList].size(); - if (_index < subListSize) + if (_subList < _parent->_subLists.size()) { - _index++; - if (_index == subListSize) + auto subListSize = _parent->_subLists[_subList].size(); + if (_index < subListSize) { - _subList++; - _index = 0; + _index++; + if (_index == subListSize) + { + _subList++; + _index = 0; + } } } - } - else + else + { + break; + } + } while (!_parent->_subLists[_subList][_index].HasValue()); + } + + ObjectList::const_iterator& ObjectList::const_iterator::operator++() + { + MoveToNextEntry(); + return *this; + } + + ObjectList::const_iterator ObjectList::const_iterator::operator++(int) + { + return *this; + } + + const ObjectEntryDescriptor& ObjectList::const_iterator::operator*() + { + return _parent->_subLists[_subList][_index]; + } + + bool ObjectList::const_iterator::operator==(const_iterator& rhs) + { + return _parent == rhs._parent && _subList == rhs._subList && _index == rhs._index; + } + + bool ObjectList::const_iterator::operator!=(const_iterator& rhs) + { + return !(*this == rhs); + } + + ObjectList::const_iterator ObjectList::begin() const + { + return const_iterator(this, false); + } + + ObjectList::const_iterator ObjectList::end() const + { + return const_iterator(this, true); + } + + std::vector& ObjectList::GetList(ObjectType type) + { + auto index = static_cast(type); + while (_subLists.size() <= index) { - break; + _subLists.resize(static_cast(index) + 1); } - } while (!_parent->_subLists[_subList][_index].HasValue()); -} - -ObjectList::const_iterator& ObjectList::const_iterator::operator++() -{ - MoveToNextEntry(); - return *this; -} - -ObjectList::const_iterator ObjectList::const_iterator::operator++(int) -{ - return *this; -} - -const ObjectEntryDescriptor& ObjectList::const_iterator::operator*() -{ - return _parent->_subLists[_subList][_index]; -} - -bool ObjectList::const_iterator::operator==(const_iterator& rhs) -{ - return _parent == rhs._parent && _subList == rhs._subList && _index == rhs._index; -} - -bool ObjectList::const_iterator::operator!=(const_iterator& rhs) -{ - return !(*this == rhs); -} - -ObjectList::const_iterator ObjectList::begin() const -{ - return const_iterator(this, false); -} - -ObjectList::const_iterator ObjectList::end() const -{ - return const_iterator(this, true); -} - -std::vector& ObjectList::GetList(ObjectType type) -{ - auto index = static_cast(type); - while (_subLists.size() <= index) - { - _subLists.resize(static_cast(index) + 1); - } - return _subLists[index]; -} - -std::vector& ObjectList::GetList(ObjectType type) const -{ - return const_cast(this)->GetList(type); -} - -const ObjectEntryDescriptor& ObjectList::GetObject(ObjectType type, ObjectEntryIndex index) const -{ - const auto& subList = GetList(type); - if (subList.size() > index) - { - return subList[index]; + return _subLists[index]; } - static ObjectEntryDescriptor placeholder; - return placeholder; -} - -ObjectEntryIndex ObjectList::Add(const ObjectEntryDescriptor& entry) -{ - auto& subList = GetList(entry.GetType()); - auto index = subList.size(); - subList.push_back(entry); - return static_cast(index); -} - -void ObjectList::SetObject(ObjectEntryIndex index, const ObjectEntryDescriptor& entry) -{ - auto& subList = GetList(entry.GetType()); - if (subList.size() <= index) + std::vector& ObjectList::GetList(ObjectType type) const { - subList.resize(static_cast(index) + 1); + return const_cast(this)->GetList(type); } - subList[index] = entry; -} -void ObjectList::SetObject(ObjectType type, ObjectEntryIndex index, std::string_view identifier) -{ - auto entry = ObjectEntryDescriptor(identifier); - entry.Type = type; - SetObject(index, entry); -} - -ObjectEntryIndex ObjectList::Find(ObjectType type, std::string_view identifier) const -{ - auto& subList = GetList(type); - for (size_t i = 0; i < subList.size(); i++) + const ObjectEntryDescriptor& ObjectList::GetObject(ObjectType type, ObjectEntryIndex index) const { - if (subList[i].Generation == ObjectGeneration::JSON && subList[i].Identifier == identifier) + const auto& subList = GetList(type); + if (subList.size() > index) { - return static_cast(i); + return subList[index]; } - } - return kObjectEntryIndexNull; -} -// Intended to be used to find non-custom legacy objects. For internal use only. -ObjectEntryIndex ObjectList::FindLegacy(ObjectType type, std::string_view identifier) const -{ - auto& subList = GetList(type); - for (size_t i = 0; i < subList.size(); i++) + static ObjectEntryDescriptor placeholder; + return placeholder; + } + + ObjectEntryIndex ObjectList::Add(const ObjectEntryDescriptor& entry) { - if (subList[i].Generation == ObjectGeneration::DAT && subList[i].Entry.GetName() == identifier - && subList[i].Entry.GetSourceGame() != ObjectSourceGame::Custom) - { - return static_cast(i); - } + auto& subList = GetList(entry.GetType()); + auto index = subList.size(); + subList.push_back(entry); + return static_cast(index); } - return kObjectEntryIndexNull; -} -/** - * - * rct2: 0x006AB344 - */ -void ObjectCreateIdentifierName(char* string_buffer, size_t size, const RCTObjectEntry* object) -{ - snprintf(string_buffer, size, "%.8s/%4X%4X", object->name, object->flags, object->checksum); -} - -void ObjectGetTypeEntryIndex(size_t index, ObjectType* outObjectType, ObjectEntryIndex* outEntryIndex) -{ - uint8_t objectType = EnumValue(ObjectType::ride); - for (size_t groupCount : kObjectEntryGroupCounts) + void ObjectList::SetObject(ObjectEntryIndex index, const ObjectEntryDescriptor& entry) { - if (index >= groupCount) + auto& subList = GetList(entry.GetType()); + if (subList.size() <= index) { - index -= groupCount; - objectType++; - } - else - { - break; + subList.resize(static_cast(index) + 1); } + subList[index] = entry; } - if (outObjectType != nullptr) - *outObjectType = static_cast(objectType); - if (outEntryIndex != nullptr) - *outEntryIndex = static_cast(index); -} - -void ObjectEntryGetNameFixed(utf8* buffer, size_t bufferSize, const RCTObjectEntry* entry) -{ - bufferSize = std::min(static_cast(kDatNameLength) + 1, bufferSize); - std::memcpy(buffer, entry->name, bufferSize - 1); - buffer[bufferSize - 1] = 0; -} - -void* ObjectEntryGetChunk(ObjectType objectType, ObjectEntryIndex index) -{ - auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager(); - auto* object = objectMgr.GetLoadedObject(objectType, index); - if (object != nullptr) + void ObjectList::SetObject(ObjectType type, ObjectEntryIndex index, std::string_view identifier) { - return object->GetLegacyData(); + auto entry = ObjectEntryDescriptor(identifier); + entry.Type = type; + SetObject(index, entry); } - return nullptr; -} -const Object* ObjectEntryGetObject(ObjectType objectType, ObjectEntryIndex index) -{ - auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager(); - return objectMgr.GetLoadedObject(objectType, index); -} + ObjectEntryIndex ObjectList::Find(ObjectType type, std::string_view identifier) const + { + auto& subList = GetList(type); + for (size_t i = 0; i < subList.size(); i++) + { + if (subList[i].Generation == ObjectGeneration::JSON && subList[i].Identifier == identifier) + { + return static_cast(i); + } + } + return kObjectEntryIndexNull; + } + + // Intended to be used to find non-custom legacy objects. For internal use only. + ObjectEntryIndex ObjectList::FindLegacy(ObjectType type, std::string_view identifier) const + { + auto& subList = GetList(type); + for (size_t i = 0; i < subList.size(); i++) + { + if (subList[i].Generation == ObjectGeneration::DAT && subList[i].Entry.GetName() == identifier + && subList[i].Entry.GetSourceGame() != ObjectSourceGame::Custom) + { + return static_cast(i); + } + } + return kObjectEntryIndexNull; + } + + /** + * + * rct2: 0x006AB344 + */ + void ObjectCreateIdentifierName(char* string_buffer, size_t size, const RCTObjectEntry* object) + { + snprintf(string_buffer, size, "%.8s/%4X%4X", object->name, object->flags, object->checksum); + } + + void ObjectGetTypeEntryIndex(size_t index, ObjectType* outObjectType, ObjectEntryIndex* outEntryIndex) + { + uint8_t objectType = EnumValue(ObjectType::ride); + for (size_t groupCount : kObjectEntryGroupCounts) + { + if (index >= groupCount) + { + index -= groupCount; + objectType++; + } + else + { + break; + } + } + + if (outObjectType != nullptr) + *outObjectType = static_cast(objectType); + if (outEntryIndex != nullptr) + *outEntryIndex = static_cast(index); + } + + void ObjectEntryGetNameFixed(utf8* buffer, size_t bufferSize, const RCTObjectEntry* entry) + { + bufferSize = std::min(static_cast(kDatNameLength) + 1, bufferSize); + std::memcpy(buffer, entry->name, bufferSize - 1); + buffer[bufferSize - 1] = 0; + } + + void* ObjectEntryGetChunk(ObjectType objectType, ObjectEntryIndex index) + { + auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager(); + auto* object = objectMgr.GetLoadedObject(objectType, index); + if (object != nullptr) + { + return object->GetLegacyData(); + } + return nullptr; + } + + const Object* ObjectEntryGetObject(ObjectType objectType, ObjectEntryIndex index) + { + auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager(); + return objectMgr.GetLoadedObject(objectType, index); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectList.h b/src/openrct2/object/ObjectList.h index 15307c6e87..e0413d715c 100644 --- a/src/openrct2/object/ObjectList.h +++ b/src/openrct2/object/ObjectList.h @@ -13,44 +13,47 @@ #include -class ObjectList +namespace OpenRCT2 { -private: - std::vector> _subLists; - -public: - ObjectEntryIndex Add(const ObjectEntryDescriptor& entry); - std::vector& GetList(ObjectType type); - std::vector& GetList(ObjectType type) const; - const ObjectEntryDescriptor& GetObject(ObjectType type, ObjectEntryIndex index) const; - void SetObject(ObjectEntryIndex index, const ObjectEntryDescriptor& entry); - void SetObject(ObjectType type, ObjectEntryIndex index, std::string_view identifier); - ObjectEntryIndex Find(ObjectType type, std::string_view identifier) const; - ObjectEntryIndex FindLegacy(ObjectType type, std::string_view identifier) const; - - struct const_iterator + class ObjectList { private: - const ObjectList* _parent; - size_t _subList; - size_t _index; - - void MoveToNextEntry(); + std::vector> _subLists; public: - const_iterator(const ObjectList* parent, bool end); - const ObjectEntryDescriptor& operator*(); - bool operator==(const_iterator& rhs); - bool operator!=(const_iterator& rhs); - const_iterator& operator++(); - const_iterator operator++(int); + ObjectEntryIndex Add(const ObjectEntryDescriptor& entry); + std::vector& GetList(ObjectType type); + std::vector& GetList(ObjectType type) const; + const ObjectEntryDescriptor& GetObject(ObjectType type, ObjectEntryIndex index) const; + void SetObject(ObjectEntryIndex index, const ObjectEntryDescriptor& entry); + void SetObject(ObjectType type, ObjectEntryIndex index, std::string_view identifier); + ObjectEntryIndex Find(ObjectType type, std::string_view identifier) const; + ObjectEntryIndex FindLegacy(ObjectType type, std::string_view identifier) const; + + struct const_iterator + { + private: + const ObjectList* _parent; + size_t _subList; + size_t _index; + + void MoveToNextEntry(); + + public: + const_iterator(const ObjectList* parent, bool end); + const ObjectEntryDescriptor& operator*(); + bool operator==(const_iterator& rhs); + bool operator!=(const_iterator& rhs); + const_iterator& operator++(); + const_iterator operator++(int); + }; + + const_iterator begin() const; + const_iterator end() const; }; - const_iterator begin() const; - const_iterator end() const; -}; + void ObjectGetTypeEntryIndex(size_t index, ObjectType* outObjectType, ObjectEntryIndex* outEntryIndex); -void ObjectGetTypeEntryIndex(size_t index, ObjectType* outObjectType, ObjectEntryIndex* outEntryIndex); - -size_t getObjectEntryGroupCount(ObjectType objectType); -size_t getObjectTypeLimit(ObjectType type); + size_t getObjectEntryGroupCount(ObjectType objectType); + size_t getObjectTypeLimit(ObjectType type); +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectManager.cpp b/src/openrct2/object/ObjectManager.cpp index 582b812968..f6c4028db8 100644 --- a/src/openrct2/object/ObjectManager.cpp +++ b/src/openrct2/object/ObjectManager.cpp @@ -40,792 +40,794 @@ #include #include -using namespace OpenRCT2; - -/** - * Represents an object that is to be loaded or is loaded and ready - * to be placed in an object list. - */ -struct ObjectToLoad +namespace OpenRCT2 { - const ObjectRepositoryItem* RepositoryItem{}; - Object* LoadedObject{}; - ObjectEntryIndex Index{}; -}; -class ObjectManager final : public IObjectManager -{ -private: - IObjectRepository& _objectRepository; - - std::array, EnumValue(ObjectType::count)> _loadedObjects; - std::array, RIDE_TYPE_COUNT> _rideTypeToObjectMap; - - // Used to return a safe empty vector back from GetAllRideEntries, can be removed when std::span is available - std::vector _nullRideTypeEntries; - -public: - explicit ObjectManager(IObjectRepository& objectRepository) - : _objectRepository(objectRepository) + /** + * Represents an object that is to be loaded or is loaded and ready + * to be placed in an object list. + */ + struct ObjectToLoad { - UpdateSceneryGroupIndexes(); - ResetTypeToRideEntryIndexMap(); - } + const ObjectRepositoryItem* RepositoryItem{}; + Object* LoadedObject{}; + ObjectEntryIndex Index{}; + }; - ~ObjectManager() override + class ObjectManager final : public IObjectManager { - UnloadAll(); - } + private: + IObjectRepository& _objectRepository; - Object* GetLoadedObject(ObjectType objectType, size_t index) override - { - // This is sometimes done deliberately (to avoid boilerplate), so no need to log_warn for this. - if (index == kObjectEntryIndexNull) - { - return nullptr; - } + std::array, EnumValue(ObjectType::count)> _loadedObjects; + std::array, RIDE_TYPE_COUNT> _rideTypeToObjectMap; - if (index >= static_cast(getObjectEntryGroupCount(objectType))) - { -#ifdef DEBUG - if (index != kObjectEntryIndexNull) - { - LOG_WARNING("Object index %u exceeds maximum for type %d.", index, objectType); - } -#endif - return nullptr; - } + // Used to return a safe empty vector back from GetAllRideEntries, can be removed when std::span is available + std::vector _nullRideTypeEntries; - const auto& list = GetObjectList(objectType); - if (index >= list.size()) - { - return nullptr; - } - - return list[index]; - } - - Object* GetLoadedObject(const ObjectEntryDescriptor& entry) override - { - const ObjectRepositoryItem* ori = _objectRepository.FindObject(entry); - if (ori == nullptr) - return nullptr; - - return ori->LoadedObject.get(); - } - - ObjectEntryIndex GetLoadedObjectEntryIndex(std::string_view identifier) override - { - const auto* obj = GetLoadedObject(ObjectEntryDescriptor(identifier)); - if (obj != nullptr) - { - return GetLoadedObjectEntryIndex(obj); - } - return kObjectEntryIndexNull; - } - - ObjectEntryIndex GetLoadedObjectEntryIndex(const ObjectEntryDescriptor& descriptor) override - { - auto obj = GetLoadedObject(descriptor); - if (obj != nullptr) - { - return GetLoadedObjectEntryIndex(obj); - } - return kObjectEntryIndexNull; - } - - ObjectEntryIndex GetLoadedObjectEntryIndex(const Object* object) override - { - ObjectEntryIndex result = kObjectEntryIndexNull; - size_t index = GetLoadedObjectIndex(object); - if (index != SIZE_MAX) - { - ObjectGetTypeEntryIndex(index, nullptr, &result); - } - return result; - } - - ObjectList GetLoadedObjects() override - { - ObjectList objectList; - for (auto objectType : getAllObjectTypes()) - { - auto maxObjectsOfType = static_cast(getObjectEntryGroupCount(objectType)); - for (ObjectEntryIndex i = 0; i < maxObjectsOfType; i++) - { - auto obj = GetLoadedObject(objectType, i); - if (obj != nullptr) - { - objectList.SetObject(i, obj->GetDescriptor()); - } - } - } - return objectList; - } - - std::unique_ptr LoadTempObject(std::string_view id) override - { - const ObjectRepositoryItem* ori = _objectRepository.FindObject(id); - if (ori == nullptr) - { - LOG_ERROR("Object '%s' not found in repository.", std::string{ id }.c_str()); - return nullptr; - } - - auto object = _objectRepository.LoadObject(ori); - return object; - } - - Object* LoadObject(std::string_view identifier) override - { - const ObjectRepositoryItem* ori = _objectRepository.FindObject(identifier); - return RepositoryItemToObject(ori); - } - - Object* LoadObject(const RCTObjectEntry* entry) override - { - const ObjectRepositoryItem* ori = _objectRepository.FindObject(entry); - return RepositoryItemToObject(ori); - } - - Object* LoadObject(const ObjectEntryDescriptor& descriptor) override - { - const ObjectRepositoryItem* ori = _objectRepository.FindObject(descriptor); - return RepositoryItemToObject(ori); - } - - Object* LoadObject(const ObjectEntryDescriptor& descriptor, ObjectEntryIndex slot) override - { - const ObjectRepositoryItem* ori = _objectRepository.FindObject(descriptor); - return RepositoryItemToObject(ori, slot); - } - - void LoadObjects(const ObjectList& objectList, const bool reportProgress) override - { - // Find all the required objects - auto requiredObjects = GetRequiredObjects(objectList); - - // Load the required objects - LoadObjects(requiredObjects, reportProgress); - - // Update indices. - UpdateSceneryGroupIndexes(); - ResetTypeToRideEntryIndexMap(); - } - - void UnloadObjects(const std::vector& entries) override - { - // TODO there are two performance issues here: - // - FindObject for every entry which is a dictionary lookup - // - GetLoadedObjectIndex for every entry which enumerates _loadedList - - size_t numObjectsUnloaded = 0; - for (const auto& descriptor : entries) - { - const auto* ori = _objectRepository.FindObject(descriptor); - if (ori != nullptr) - { - auto* loadedObject = ori->LoadedObject.get(); - if (loadedObject != nullptr) - { - UnloadObject(loadedObject); - numObjectsUnloaded++; - } - } - } - - if (numObjectsUnloaded > 0) + public: + explicit ObjectManager(IObjectRepository& objectRepository) + : _objectRepository(objectRepository) { UpdateSceneryGroupIndexes(); ResetTypeToRideEntryIndexMap(); } - } - void UnloadAllTransient() override - { - UnloadAll(true); - } - - void UnloadAll() override - { - UnloadAll(false); - } - - void ResetObjects() override - { - for (auto& list : _loadedObjects) + ~ObjectManager() override { - for (auto* loadedObject : list) + UnloadAll(); + } + + Object* GetLoadedObject(ObjectType objectType, size_t index) override + { + // This is sometimes done deliberately (to avoid boilerplate), so no need to log_warn for this. + if (index == kObjectEntryIndexNull) { - if (loadedObject != nullptr) - { - loadedObject->Unload(); - loadedObject->Load(); - } - } - } - UpdateSceneryGroupIndexes(); - ResetTypeToRideEntryIndexMap(); - - // We will need to replay the title music if the title music object got reloaded - OpenRCT2::Audio::StopTitleMusic(); - OpenRCT2::Audio::PlayTitleMusic(); - OpenRCT2::RideAudio::StopAllChannels(); - } - - std::vector GetPackableObjects() override - { - std::vector objects; - size_t numObjects = _objectRepository.GetNumObjects(); - for (size_t i = 0; i < numObjects; i++) - { - const ObjectRepositoryItem* item = &_objectRepository.GetObjects()[i]; - if (item->LoadedObject != nullptr && IsObjectCustom(item)) - { - objects.push_back(item); - } - } - return objects; - } - - static StringId GetObjectSourceGameString(const ObjectSourceGame sourceGame) - { - switch (sourceGame) - { - case ObjectSourceGame::RCT1: - return STR_SCENARIO_CATEGORY_RCT1; - case ObjectSourceGame::AddedAttractions: - return STR_SCENARIO_CATEGORY_RCT1_AA; - case ObjectSourceGame::LoopyLandscapes: - return STR_SCENARIO_CATEGORY_RCT1_LL; - case ObjectSourceGame::RCT2: - return STR_ROLLERCOASTER_TYCOON_2_DROPDOWN; - case ObjectSourceGame::WackyWorlds: - return STR_OBJECT_FILTER_WW; - case ObjectSourceGame::TimeTwister: - return STR_OBJECT_FILTER_TT; - case ObjectSourceGame::OpenRCT2Official: - return STR_OBJECT_FILTER_OPENRCT2_OFFICIAL; - default: - return STR_OBJECT_FILTER_CUSTOM; - } - } - - const std::vector& GetAllRideEntries(ride_type_t rideType) override - { - if (rideType >= RIDE_TYPE_COUNT) - { - // Return an empty vector - return _nullRideTypeEntries; - } - return _rideTypeToObjectMap[rideType]; - } - -private: - std::vector& GetObjectList(ObjectType type) - { - auto typeIndex = EnumValue(type); - return _loadedObjects[typeIndex]; - } - - void UnloadAll(bool onlyTransient) - { - for (auto type : getAllObjectTypes()) - { - if (!onlyTransient || !IsIntransientObjectType(type)) - { - auto& list = GetObjectList(type); - for (auto* loadedObject : list) - { - UnloadObject(loadedObject); - } - list.clear(); - } - } - UpdateSceneryGroupIndexes(); - ResetTypeToRideEntryIndexMap(); - } - - Object* LoadObject(ObjectEntryIndex slot, std::string_view identifier) - { - const ObjectRepositoryItem* ori = _objectRepository.FindObject(identifier); - return RepositoryItemToObject(ori, slot); - } - - Object* RepositoryItemToObject(const ObjectRepositoryItem* ori, std::optional slot = {}) - { - if (ori == nullptr) - return nullptr; - - Object* loadedObject = ori->LoadedObject.get(); - if (loadedObject != nullptr) - return loadedObject; - - ObjectType objectType = ori->Type; - if (slot) - { - auto& list = GetObjectList(objectType); - if (list.size() > *slot && list[*slot] != nullptr) - { - // Slot already taken return nullptr; } - } - else - { - slot = FindSpareSlot(objectType); - } - if (slot) - { - auto* object = GetOrLoadObject(ori); - if (object != nullptr) + + if (index >= static_cast(getObjectEntryGroupCount(objectType))) { - auto& list = GetObjectList(objectType); - if (list.size() <= *slot) +#ifdef DEBUG + if (index != kObjectEntryIndexNull) { - list.resize(*slot + 1); + LOG_WARNING("Object index %u exceeds maximum for type %d.", index, objectType); } - loadedObject = object; - list[*slot] = object; - UpdateSceneryGroupIndexes(); - ResetTypeToRideEntryIndexMap(); +#endif + return nullptr; } - } - return loadedObject; - } - std::optional FindSpareSlot(ObjectType objectType) - { - auto& list = GetObjectList(objectType); - auto it = std::find(list.begin(), list.end(), nullptr); - if (it != list.end()) - { - return static_cast(std::distance(list.begin(), it)); - } - - auto maxSize = getObjectEntryGroupCount(objectType); - if (list.size() < static_cast(maxSize)) - { - list.emplace_back(); - return static_cast(list.size() - 1); - } - return std::nullopt; - } - - size_t GetLoadedObjectIndex(const Object* object) - { - Guard::ArgumentNotNull(object, GUARD_LINE); - - auto result = std::numeric_limits().max(); - auto& list = GetObjectList(object->GetObjectType()); - auto it = std::find(list.begin(), list.end(), object); - if (it != list.end()) - { - result = std::distance(list.begin(), it); - } - return result; - } - - void UnloadObject(Object* object) - { - if (object == nullptr) - return; - - // Because it's possible to have the same loaded object for multiple - // slots, we have to make sure find and set all of them to nullptr - auto& list = GetObjectList(object->GetObjectType()); - std::replace(list.begin(), list.end(), object, static_cast(nullptr)); - - object->Unload(); - - // TODO try to prevent doing a repository search - const auto* ori = _objectRepository.FindObject(object->GetDescriptor()); - if (ori != nullptr) - { - _objectRepository.UnregisterLoadedObject(ori, object); - } - } - - void UnloadObjectsExcept(const std::vector& newLoadedObjects) - { - // Build a hash set for quick checking - auto exceptSet = std::unordered_set(); - for (auto& object : newLoadedObjects) - { - if (object != nullptr) + const auto& list = GetObjectList(objectType); + if (index >= list.size()) { - exceptSet.insert(object); + return nullptr; } + + return list[index]; } - // Unload objects that are not in the hash set - size_t totalObjectsLoaded = 0; - size_t numObjectsUnloaded = 0; - for (auto type : getAllObjectTypes()) + Object* GetLoadedObject(const ObjectEntryDescriptor& entry) override { - if (!IsIntransientObjectType(type)) + const ObjectRepositoryItem* ori = _objectRepository.FindObject(entry); + if (ori == nullptr) + return nullptr; + + return ori->LoadedObject.get(); + } + + ObjectEntryIndex GetLoadedObjectEntryIndex(std::string_view identifier) override + { + const auto* obj = GetLoadedObject(ObjectEntryDescriptor(identifier)); + if (obj != nullptr) { - auto& list = GetObjectList(type); - for (auto& object : list) + return GetLoadedObjectEntryIndex(obj); + } + return kObjectEntryIndexNull; + } + + ObjectEntryIndex GetLoadedObjectEntryIndex(const ObjectEntryDescriptor& descriptor) override + { + auto obj = GetLoadedObject(descriptor); + if (obj != nullptr) + { + return GetLoadedObjectEntryIndex(obj); + } + return kObjectEntryIndexNull; + } + + ObjectEntryIndex GetLoadedObjectEntryIndex(const Object* object) override + { + ObjectEntryIndex result = kObjectEntryIndexNull; + size_t index = GetLoadedObjectIndex(object); + if (index != SIZE_MAX) + { + ObjectGetTypeEntryIndex(index, nullptr, &result); + } + return result; + } + + ObjectList GetLoadedObjects() override + { + ObjectList objectList; + for (auto objectType : getAllObjectTypes()) + { + auto maxObjectsOfType = static_cast(getObjectEntryGroupCount(objectType)); + for (ObjectEntryIndex i = 0; i < maxObjectsOfType; i++) { - if (object == nullptr) - continue; - - totalObjectsLoaded++; - if (exceptSet.find(object) == exceptSet.end()) + auto obj = GetLoadedObject(objectType, i); + if (obj != nullptr) { - UnloadObject(object); - object = nullptr; + objectList.SetObject(i, obj->GetDescriptor()); + } + } + } + return objectList; + } + + std::unique_ptr LoadTempObject(std::string_view id) override + { + const ObjectRepositoryItem* ori = _objectRepository.FindObject(id); + if (ori == nullptr) + { + LOG_ERROR("Object '%s' not found in repository.", std::string{ id }.c_str()); + return nullptr; + } + + auto object = _objectRepository.LoadObject(ori); + return object; + } + + Object* LoadObject(std::string_view identifier) override + { + const ObjectRepositoryItem* ori = _objectRepository.FindObject(identifier); + return RepositoryItemToObject(ori); + } + + Object* LoadObject(const RCTObjectEntry* entry) override + { + const ObjectRepositoryItem* ori = _objectRepository.FindObject(entry); + return RepositoryItemToObject(ori); + } + + Object* LoadObject(const ObjectEntryDescriptor& descriptor) override + { + const ObjectRepositoryItem* ori = _objectRepository.FindObject(descriptor); + return RepositoryItemToObject(ori); + } + + Object* LoadObject(const ObjectEntryDescriptor& descriptor, ObjectEntryIndex slot) override + { + const ObjectRepositoryItem* ori = _objectRepository.FindObject(descriptor); + return RepositoryItemToObject(ori, slot); + } + + void LoadObjects(const ObjectList& objectList, const bool reportProgress) override + { + // Find all the required objects + auto requiredObjects = GetRequiredObjects(objectList); + + // Load the required objects + LoadObjects(requiredObjects, reportProgress); + + // Update indices. + UpdateSceneryGroupIndexes(); + ResetTypeToRideEntryIndexMap(); + } + + void UnloadObjects(const std::vector& entries) override + { + // TODO there are two performance issues here: + // - FindObject for every entry which is a dictionary lookup + // - GetLoadedObjectIndex for every entry which enumerates _loadedList + + size_t numObjectsUnloaded = 0; + for (const auto& descriptor : entries) + { + const auto* ori = _objectRepository.FindObject(descriptor); + if (ori != nullptr) + { + auto* loadedObject = ori->LoadedObject.get(); + if (loadedObject != nullptr) + { + UnloadObject(loadedObject); numObjectsUnloaded++; } } } - } - LOG_VERBOSE("%u / %u objects unloaded", numObjectsUnloaded, totalObjectsLoaded); - } - - template - void UpdateSceneryGroupIndexes(ObjectType type) - { - auto& list = GetObjectList(type); - for (auto* loadedObject : list) - { - if (loadedObject != nullptr) + if (numObjectsUnloaded > 0) { - auto* sceneryEntry = static_cast(loadedObject->GetLegacyData()); - sceneryEntry->scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject); + UpdateSceneryGroupIndexes(); + ResetTypeToRideEntryIndexMap(); } } - } - void UpdateSceneryGroupIndexes() - { - UpdateSceneryGroupIndexes(ObjectType::smallScenery); - UpdateSceneryGroupIndexes(ObjectType::largeScenery); - UpdateSceneryGroupIndexes(ObjectType::walls); - UpdateSceneryGroupIndexes(ObjectType::banners); - UpdateSceneryGroupIndexes(ObjectType::pathAdditions); - - auto& list = GetObjectList(ObjectType::sceneryGroup); - for (auto* loadedObject : list) + void UnloadAllTransient() override { - auto sgObject = static_cast(loadedObject); - if (sgObject != nullptr) - { - sgObject->UpdateEntryIndexes(); - } + UnloadAll(true); } - } - ObjectEntryIndex GetPrimarySceneryGroupEntryIndex(Object* loadedObject) - { - auto* sceneryObject = dynamic_cast(loadedObject); - const auto& primarySGEntry = sceneryObject->GetPrimarySceneryGroup(); - Object* sgObject = GetLoadedObject(primarySGEntry); - - auto entryIndex = kObjectEntryIndexNull; - if (sgObject != nullptr) + void UnloadAll() override { - entryIndex = GetLoadedObjectEntryIndex(sgObject); + UnloadAll(false); } - return entryIndex; - } - std::vector GetRequiredObjects(const ObjectList& objectList) - { - std::vector requiredObjects; - std::vector missingObjects; - - for (auto objectType : getAllObjectTypes()) + void ResetObjects() override { - auto& descriptors = objectList.GetList(objectType); - auto maxSize = static_cast(getObjectEntryGroupCount(objectType)); - auto listSize = static_cast(std::min(descriptors.size(), maxSize)); - for (ObjectEntryIndex i = 0; i < listSize; i++) + for (auto& list : _loadedObjects) { - const auto& entry = objectList.GetObject(objectType, i); - if (entry.HasValue()) + for (auto* loadedObject : list) { - const auto* ori = _objectRepository.FindObject(entry); - if (ori == nullptr && entry.GetType() == ObjectType::scenarioMeta) + if (loadedObject != nullptr) { - continue; + loadedObject->Unload(); + loadedObject->Load(); } - - if (ori == nullptr) - { - missingObjects.push_back(entry); - ReportMissingObject(entry); - } - - ObjectToLoad otl; - otl.RepositoryItem = ori; - otl.Index = i; - requiredObjects.push_back(otl); } } + UpdateSceneryGroupIndexes(); + ResetTypeToRideEntryIndexMap(); + + // We will need to replay the title music if the title music object got reloaded + OpenRCT2::Audio::StopTitleMusic(); + OpenRCT2::Audio::PlayTitleMusic(); + OpenRCT2::RideAudio::StopAllChannels(); } - if (!missingObjects.empty()) + std::vector GetPackableObjects() override { - throw ObjectLoadException(std::move(missingObjects)); - } - - return requiredObjects; - } - - void ReportProgress(size_t numLoaded, size_t numRequired) - { - constexpr auto kObjectLoadMinProgress = 10; - constexpr auto kObjectLoadMaxProgress = 90; - constexpr auto kObjectLoadProgressRange = kObjectLoadMaxProgress - kObjectLoadMinProgress; - - const auto currentProgress = kObjectLoadMinProgress + (numLoaded * kObjectLoadProgressRange / numRequired); - OpenRCT2::GetContext()->SetProgress(static_cast(currentProgress), 100, STR_STRING_M_PERCENT); - } - - void LoadObjects(std::vector& requiredObjects, bool reportProgress) - { - std::vector objects; - std::vector newLoadedObjects; - std::vector badObjects; - - // Create a list of objects that are currently not loaded but required. - std::vector objectsToLoad; - for (auto& requiredObject : requiredObjects) - { - auto* repositoryItem = requiredObject.RepositoryItem; - if (repositoryItem == nullptr) + std::vector objects; + size_t numObjects = _objectRepository.GetNumObjects(); + for (size_t i = 0; i < numObjects; i++) { - continue; + const ObjectRepositoryItem* item = &_objectRepository.GetObjects()[i]; + if (item->LoadedObject != nullptr && IsObjectCustom(item)) + { + objects.push_back(item); + } } + return objects; + } - auto* loadedObject = repositoryItem->LoadedObject.get(); - if (loadedObject == nullptr) + static StringId GetObjectSourceGameString(const ObjectSourceGame sourceGame) + { + switch (sourceGame) { - objectsToLoad.push_back(repositoryItem); + case ObjectSourceGame::RCT1: + return STR_SCENARIO_CATEGORY_RCT1; + case ObjectSourceGame::AddedAttractions: + return STR_SCENARIO_CATEGORY_RCT1_AA; + case ObjectSourceGame::LoopyLandscapes: + return STR_SCENARIO_CATEGORY_RCT1_LL; + case ObjectSourceGame::RCT2: + return STR_ROLLERCOASTER_TYCOON_2_DROPDOWN; + case ObjectSourceGame::WackyWorlds: + return STR_OBJECT_FILTER_WW; + case ObjectSourceGame::TimeTwister: + return STR_OBJECT_FILTER_TT; + case ObjectSourceGame::OpenRCT2Official: + return STR_OBJECT_FILTER_OPENRCT2_OFFICIAL; + default: + return STR_OBJECT_FILTER_CUSTOM; } } - // De-duplicate the list, since loading happens in parallel we can't have it race the repository item. - std::sort(objectsToLoad.begin(), objectsToLoad.end()); - objectsToLoad.erase(std::unique(objectsToLoad.begin(), objectsToLoad.end()), objectsToLoad.end()); - - // Prepare for loading objects multi-threaded - auto numProcessed = 0; - auto numRequired = objectsToLoad.size(); - std::mutex commonMutex; - auto loadSingleObject = [&](const ObjectRepositoryItem* requiredObject) { - // Object requires to be loaded, if the object successfully loads it will register it - // as a loaded object otherwise placed into the badObjects list. - auto newObject = _objectRepository.LoadObject(requiredObject); - - std::lock_guard guard(commonMutex); - if (newObject == nullptr) + const std::vector& GetAllRideEntries(ride_type_t rideType) override + { + if (rideType >= RIDE_TYPE_COUNT) { - badObjects.push_back(ObjectEntryDescriptor(requiredObject->ObjectEntry)); - ReportObjectLoadProblem(&requiredObject->ObjectEntry); + // Return an empty vector + return _nullRideTypeEntries; + } + return _rideTypeToObjectMap[rideType]; + } + + private: + std::vector& GetObjectList(ObjectType type) + { + auto typeIndex = EnumValue(type); + return _loadedObjects[typeIndex]; + } + + void UnloadAll(bool onlyTransient) + { + for (auto type : getAllObjectTypes()) + { + if (!onlyTransient || !IsIntransientObjectType(type)) + { + auto& list = GetObjectList(type); + for (auto* loadedObject : list) + { + UnloadObject(loadedObject); + } + list.clear(); + } + } + UpdateSceneryGroupIndexes(); + ResetTypeToRideEntryIndexMap(); + } + + Object* LoadObject(ObjectEntryIndex slot, std::string_view identifier) + { + const ObjectRepositoryItem* ori = _objectRepository.FindObject(identifier); + return RepositoryItemToObject(ori, slot); + } + + Object* RepositoryItemToObject(const ObjectRepositoryItem* ori, std::optional slot = {}) + { + if (ori == nullptr) + return nullptr; + + Object* loadedObject = ori->LoadedObject.get(); + if (loadedObject != nullptr) + return loadedObject; + + ObjectType objectType = ori->Type; + if (slot) + { + auto& list = GetObjectList(objectType); + if (list.size() > *slot && list[*slot] != nullptr) + { + // Slot already taken + return nullptr; + } } else { - newLoadedObjects.push_back(newObject.get()); - // Connect the ori to the registered object - _objectRepository.RegisterLoadedObject(requiredObject, std::move(newObject)); + slot = FindSpareSlot(objectType); } - - numProcessed++; - }; - - auto completionFn = [&]() { - if (reportProgress && (numProcessed % 100) == 0) - ReportProgress(numProcessed, numRequired); - }; - - // Dispatch loading the objects - JobPool jobs{}; - for (auto* object : objectsToLoad) - { - jobs.AddTask([object, &loadSingleObject]() { loadSingleObject(object); }, completionFn); - } - - // Wait until all jobs are fully completed - jobs.Join(); - - // Assign the loaded objects to the required objects - for (auto& requiredObject : requiredObjects) - { - auto* repositoryItem = requiredObject.RepositoryItem; - if (repositoryItem == nullptr) + if (slot) { - continue; - } - auto* loadedObject = repositoryItem->LoadedObject.get(); - if (loadedObject == nullptr) - { - continue; - } - requiredObject.LoadedObject = loadedObject; - objects.push_back(loadedObject); - } - - // Load objects - for (auto* obj : newLoadedObjects) - { - obj->Load(); - } - - if (!badObjects.empty()) - { - // Unload all the new objects we loaded - for (auto* object : newLoadedObjects) - { - UnloadObject(object); - } - throw ObjectLoadException(std::move(badObjects)); - } - - // Unload objects which are not in the required list. - if (objects.empty()) - { - UnloadAllTransient(); - } - else - { - UnloadObjectsExcept(objects); - } - - // Set the new object lists - for (auto type : getAllObjectTypes()) - { - if (!IsIntransientObjectType(type)) - { - auto& list = GetObjectList(type); - list.clear(); - } - } - for (auto& otl : requiredObjects) - { - auto objectType = otl.LoadedObject->GetObjectType(); - auto& list = GetObjectList(objectType); - if (list.size() <= otl.Index) - { - list.resize(otl.Index + 1); - } - list[otl.Index] = otl.LoadedObject; - } - - LOG_VERBOSE("%u / %u new objects loaded", newLoadedObjects.size(), requiredObjects.size()); - } - - Object* GetOrLoadObject(const ObjectRepositoryItem* ori) - { - auto* loadedObject = ori->LoadedObject.get(); - if (loadedObject != nullptr) - return loadedObject; - - // Try to load object - auto object = _objectRepository.LoadObject(ori); - if (object != nullptr) - { - loadedObject = object.get(); - - object->Load(); - - // Connect the ori to the registered object - _objectRepository.RegisterLoadedObject(ori, std::move(object)); - } - - return loadedObject; - } - - void ResetTypeToRideEntryIndexMap() - { - // Clear all ride objects - for (auto& v : _rideTypeToObjectMap) - { - v.clear(); - } - - // Build object lists - const auto maxRideObjects = static_cast(getObjectEntryGroupCount(ObjectType::ride)); - for (size_t i = 0; i < maxRideObjects; i++) - { - auto* rideObject = static_cast(GetLoadedObject(ObjectType::ride, i)); - if (rideObject == nullptr) - continue; - - const auto& entry = rideObject->GetEntry(); - - for (auto rideType : entry.ride_type) - { - if (rideType < _rideTypeToObjectMap.size()) + auto* object = GetOrLoadObject(ori); + if (object != nullptr) { - auto& v = _rideTypeToObjectMap[rideType]; - v.push_back(static_cast(i)); + auto& list = GetObjectList(objectType); + if (list.size() <= *slot) + { + list.resize(*slot + 1); + } + loadedObject = object; + list[*slot] = object; + UpdateSceneryGroupIndexes(); + ResetTypeToRideEntryIndexMap(); + } + } + return loadedObject; + } + + std::optional FindSpareSlot(ObjectType objectType) + { + auto& list = GetObjectList(objectType); + auto it = std::find(list.begin(), list.end(), nullptr); + if (it != list.end()) + { + return static_cast(std::distance(list.begin(), it)); + } + + auto maxSize = getObjectEntryGroupCount(objectType); + if (list.size() < static_cast(maxSize)) + { + list.emplace_back(); + return static_cast(list.size() - 1); + } + return std::nullopt; + } + + size_t GetLoadedObjectIndex(const Object* object) + { + Guard::ArgumentNotNull(object, GUARD_LINE); + + auto result = std::numeric_limits().max(); + auto& list = GetObjectList(object->GetObjectType()); + auto it = std::find(list.begin(), list.end(), object); + if (it != list.end()) + { + result = std::distance(list.begin(), it); + } + return result; + } + + void UnloadObject(Object* object) + { + if (object == nullptr) + return; + + // Because it's possible to have the same loaded object for multiple + // slots, we have to make sure find and set all of them to nullptr + auto& list = GetObjectList(object->GetObjectType()); + std::replace(list.begin(), list.end(), object, static_cast(nullptr)); + + object->Unload(); + + // TODO try to prevent doing a repository search + const auto* ori = _objectRepository.FindObject(object->GetDescriptor()); + if (ori != nullptr) + { + _objectRepository.UnregisterLoadedObject(ori, object); + } + } + + void UnloadObjectsExcept(const std::vector& newLoadedObjects) + { + // Build a hash set for quick checking + auto exceptSet = std::unordered_set(); + for (auto& object : newLoadedObjects) + { + if (object != nullptr) + { + exceptSet.insert(object); + } + } + + // Unload objects that are not in the hash set + size_t totalObjectsLoaded = 0; + size_t numObjectsUnloaded = 0; + for (auto type : getAllObjectTypes()) + { + if (!IsIntransientObjectType(type)) + { + auto& list = GetObjectList(type); + for (auto& object : list) + { + if (object == nullptr) + continue; + + totalObjectsLoaded++; + if (exceptSet.find(object) == exceptSet.end()) + { + UnloadObject(object); + object = nullptr; + numObjectsUnloaded++; + } + } + } + } + + LOG_VERBOSE("%u / %u objects unloaded", numObjectsUnloaded, totalObjectsLoaded); + } + + template + void UpdateSceneryGroupIndexes(ObjectType type) + { + auto& list = GetObjectList(type); + for (auto* loadedObject : list) + { + if (loadedObject != nullptr) + { + auto* sceneryEntry = static_cast(loadedObject->GetLegacyData()); + sceneryEntry->scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject); } } } - } - static void ReportMissingObject(const ObjectEntryDescriptor& entry) + void UpdateSceneryGroupIndexes() + { + UpdateSceneryGroupIndexes(ObjectType::smallScenery); + UpdateSceneryGroupIndexes(ObjectType::largeScenery); + UpdateSceneryGroupIndexes(ObjectType::walls); + UpdateSceneryGroupIndexes(ObjectType::banners); + UpdateSceneryGroupIndexes(ObjectType::pathAdditions); + + auto& list = GetObjectList(ObjectType::sceneryGroup); + for (auto* loadedObject : list) + { + auto sgObject = static_cast(loadedObject); + if (sgObject != nullptr) + { + sgObject->UpdateEntryIndexes(); + } + } + } + + ObjectEntryIndex GetPrimarySceneryGroupEntryIndex(Object* loadedObject) + { + auto* sceneryObject = dynamic_cast(loadedObject); + const auto& primarySGEntry = sceneryObject->GetPrimarySceneryGroup(); + Object* sgObject = GetLoadedObject(primarySGEntry); + + auto entryIndex = kObjectEntryIndexNull; + if (sgObject != nullptr) + { + entryIndex = GetLoadedObjectEntryIndex(sgObject); + } + return entryIndex; + } + + std::vector GetRequiredObjects(const ObjectList& objectList) + { + std::vector requiredObjects; + std::vector missingObjects; + + for (auto objectType : getAllObjectTypes()) + { + auto& descriptors = objectList.GetList(objectType); + auto maxSize = static_cast(getObjectEntryGroupCount(objectType)); + auto listSize = static_cast(std::min(descriptors.size(), maxSize)); + for (ObjectEntryIndex i = 0; i < listSize; i++) + { + const auto& entry = objectList.GetObject(objectType, i); + if (entry.HasValue()) + { + const auto* ori = _objectRepository.FindObject(entry); + if (ori == nullptr && entry.GetType() == ObjectType::scenarioMeta) + { + continue; + } + + if (ori == nullptr) + { + missingObjects.push_back(entry); + ReportMissingObject(entry); + } + + ObjectToLoad otl; + otl.RepositoryItem = ori; + otl.Index = i; + requiredObjects.push_back(otl); + } + } + } + + if (!missingObjects.empty()) + { + throw ObjectLoadException(std::move(missingObjects)); + } + + return requiredObjects; + } + + void ReportProgress(size_t numLoaded, size_t numRequired) + { + constexpr auto kObjectLoadMinProgress = 10; + constexpr auto kObjectLoadMaxProgress = 90; + constexpr auto kObjectLoadProgressRange = kObjectLoadMaxProgress - kObjectLoadMinProgress; + + const auto currentProgress = kObjectLoadMinProgress + (numLoaded * kObjectLoadProgressRange / numRequired); + OpenRCT2::GetContext()->SetProgress(static_cast(currentProgress), 100, STR_STRING_M_PERCENT); + } + + void LoadObjects(std::vector& requiredObjects, bool reportProgress) + { + std::vector objects; + std::vector newLoadedObjects; + std::vector badObjects; + + // Create a list of objects that are currently not loaded but required. + std::vector objectsToLoad; + for (auto& requiredObject : requiredObjects) + { + auto* repositoryItem = requiredObject.RepositoryItem; + if (repositoryItem == nullptr) + { + continue; + } + + auto* loadedObject = repositoryItem->LoadedObject.get(); + if (loadedObject == nullptr) + { + objectsToLoad.push_back(repositoryItem); + } + } + + // De-duplicate the list, since loading happens in parallel we can't have it race the repository item. + std::sort(objectsToLoad.begin(), objectsToLoad.end()); + objectsToLoad.erase(std::unique(objectsToLoad.begin(), objectsToLoad.end()), objectsToLoad.end()); + + // Prepare for loading objects multi-threaded + auto numProcessed = 0; + auto numRequired = objectsToLoad.size(); + std::mutex commonMutex; + auto loadSingleObject = [&](const ObjectRepositoryItem* requiredObject) { + // Object requires to be loaded, if the object successfully loads it will register it + // as a loaded object otherwise placed into the badObjects list. + auto newObject = _objectRepository.LoadObject(requiredObject); + + std::lock_guard guard(commonMutex); + if (newObject == nullptr) + { + badObjects.push_back(ObjectEntryDescriptor(requiredObject->ObjectEntry)); + ReportObjectLoadProblem(&requiredObject->ObjectEntry); + } + else + { + newLoadedObjects.push_back(newObject.get()); + // Connect the ori to the registered object + _objectRepository.RegisterLoadedObject(requiredObject, std::move(newObject)); + } + + numProcessed++; + }; + + auto completionFn = [&]() { + if (reportProgress && (numProcessed % 100) == 0) + ReportProgress(numProcessed, numRequired); + }; + + // Dispatch loading the objects + JobPool jobs{}; + for (auto* object : objectsToLoad) + { + jobs.AddTask([object, &loadSingleObject]() { loadSingleObject(object); }, completionFn); + } + + // Wait until all jobs are fully completed + jobs.Join(); + + // Assign the loaded objects to the required objects + for (auto& requiredObject : requiredObjects) + { + auto* repositoryItem = requiredObject.RepositoryItem; + if (repositoryItem == nullptr) + { + continue; + } + auto* loadedObject = repositoryItem->LoadedObject.get(); + if (loadedObject == nullptr) + { + continue; + } + requiredObject.LoadedObject = loadedObject; + objects.push_back(loadedObject); + } + + // Load objects + for (auto* obj : newLoadedObjects) + { + obj->Load(); + } + + if (!badObjects.empty()) + { + // Unload all the new objects we loaded + for (auto* object : newLoadedObjects) + { + UnloadObject(object); + } + throw ObjectLoadException(std::move(badObjects)); + } + + // Unload objects which are not in the required list. + if (objects.empty()) + { + UnloadAllTransient(); + } + else + { + UnloadObjectsExcept(objects); + } + + // Set the new object lists + for (auto type : getAllObjectTypes()) + { + if (!IsIntransientObjectType(type)) + { + auto& list = GetObjectList(type); + list.clear(); + } + } + for (auto& otl : requiredObjects) + { + auto objectType = otl.LoadedObject->GetObjectType(); + auto& list = GetObjectList(objectType); + if (list.size() <= otl.Index) + { + list.resize(otl.Index + 1); + } + list[otl.Index] = otl.LoadedObject; + } + + LOG_VERBOSE("%u / %u new objects loaded", newLoadedObjects.size(), requiredObjects.size()); + } + + Object* GetOrLoadObject(const ObjectRepositoryItem* ori) + { + auto* loadedObject = ori->LoadedObject.get(); + if (loadedObject != nullptr) + return loadedObject; + + // Try to load object + auto object = _objectRepository.LoadObject(ori); + if (object != nullptr) + { + loadedObject = object.get(); + + object->Load(); + + // Connect the ori to the registered object + _objectRepository.RegisterLoadedObject(ori, std::move(object)); + } + + return loadedObject; + } + + void ResetTypeToRideEntryIndexMap() + { + // Clear all ride objects + for (auto& v : _rideTypeToObjectMap) + { + v.clear(); + } + + // Build object lists + const auto maxRideObjects = static_cast(getObjectEntryGroupCount(ObjectType::ride)); + for (size_t i = 0; i < maxRideObjects; i++) + { + auto* rideObject = static_cast(GetLoadedObject(ObjectType::ride, i)); + if (rideObject == nullptr) + continue; + + const auto& entry = rideObject->GetEntry(); + + for (auto rideType : entry.ride_type) + { + if (rideType < _rideTypeToObjectMap.size()) + { + auto& v = _rideTypeToObjectMap[rideType]; + v.push_back(static_cast(i)); + } + } + } + } + + static void ReportMissingObject(const ObjectEntryDescriptor& entry) + { + std::string name(entry.GetName()); + Console::Error::WriteLine("[%s] Object not found.", name.c_str()); + } + + void ReportObjectLoadProblem(const RCTObjectEntry* entry) + { + utf8 objName[kDatNameLength + 1] = { 0 }; + std::copy_n(entry->name, kDatNameLength, objName); + Console::Error::WriteLine("[%s] Object could not be loaded.", objName); + } + }; + + std::unique_ptr CreateObjectManager(IObjectRepository& objectRepository) { - std::string name(entry.GetName()); - Console::Error::WriteLine("[%s] Object not found.", name.c_str()); + return std::make_unique(objectRepository); } - void ReportObjectLoadProblem(const RCTObjectEntry* entry) + Object* ObjectManagerGetLoadedObject(const ObjectEntryDescriptor& entry) { - utf8 objName[kDatNameLength + 1] = { 0 }; - std::copy_n(entry->name, kDatNameLength, objName); - Console::Error::WriteLine("[%s] Object could not be loaded.", objName); + auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); + Object* loadedObject = objectManager.GetLoadedObject(entry); + return loadedObject; } -}; -std::unique_ptr CreateObjectManager(IObjectRepository& objectRepository) -{ - return std::make_unique(objectRepository); -} + ObjectEntryIndex ObjectManagerGetLoadedObjectEntryIndex(const Object* loadedObject) + { + auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); + auto entryIndex = objectManager.GetLoadedObjectEntryIndex(loadedObject); + return entryIndex; + } -Object* ObjectManagerGetLoadedObject(const ObjectEntryDescriptor& entry) -{ - auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); - Object* loadedObject = objectManager.GetLoadedObject(entry); - return loadedObject; -} + ObjectEntryIndex ObjectManagerGetLoadedObjectEntryIndex(const ObjectEntryDescriptor& entry) + { + return ObjectManagerGetLoadedObjectEntryIndex(ObjectManagerGetLoadedObject(entry)); + } -ObjectEntryIndex ObjectManagerGetLoadedObjectEntryIndex(const Object* loadedObject) -{ - auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); - auto entryIndex = objectManager.GetLoadedObjectEntryIndex(loadedObject); - return entryIndex; -} + Object* ObjectManagerLoadObject(const RCTObjectEntry* entry) + { + auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); + Object* loadedObject = objectManager.LoadObject(entry); + return loadedObject; + } -ObjectEntryIndex ObjectManagerGetLoadedObjectEntryIndex(const ObjectEntryDescriptor& entry) -{ - return ObjectManagerGetLoadedObjectEntryIndex(ObjectManagerGetLoadedObject(entry)); -} + void ObjectManagerUnloadObjects(const std::vector& entries) + { + auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); + objectManager.UnloadObjects(entries); + } -Object* ObjectManagerLoadObject(const RCTObjectEntry* entry) -{ - auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); - Object* loadedObject = objectManager.LoadObject(entry); - return loadedObject; -} + void ObjectManagerUnloadAllObjects() + { + auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); + objectManager.UnloadAllTransient(); + } -void ObjectManagerUnloadObjects(const std::vector& entries) -{ - auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); - objectManager.UnloadObjects(entries); -} - -void ObjectManagerUnloadAllObjects() -{ - auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); - objectManager.UnloadAllTransient(); -} - -StringId ObjectManagerGetSourceGameString(const ObjectSourceGame sourceGame) -{ - return ObjectManager::GetObjectSourceGameString(sourceGame); -} + StringId ObjectManagerGetSourceGameString(const ObjectSourceGame sourceGame) + { + return ObjectManager::GetObjectSourceGameString(sourceGame); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectManager.h b/src/openrct2/object/ObjectManager.h index 168ddd37e5..3e1420e0c2 100644 --- a/src/openrct2/object/ObjectManager.h +++ b/src/openrct2/object/ObjectManager.h @@ -14,51 +14,54 @@ #include #include -struct IObjectRepository; -class Object; -class ObjectList; -struct ObjectRepositoryItem; - -struct IObjectManager +namespace OpenRCT2 { - virtual ~IObjectManager() + struct IObjectRepository; + class Object; + class ObjectList; + struct ObjectRepositoryItem; + + struct IObjectManager { - } + virtual ~IObjectManager() + { + } - virtual Object* GetLoadedObject(ObjectType objectType, size_t index) = 0; - template - TClass* GetLoadedObject(size_t index) - { - return static_cast(GetLoadedObject(TClass::kObjectType, index)); - } - virtual Object* GetLoadedObject(const ObjectEntryDescriptor& entry) = 0; - virtual ObjectEntryIndex GetLoadedObjectEntryIndex(std::string_view identifier) = 0; - virtual ObjectEntryIndex GetLoadedObjectEntryIndex(const ObjectEntryDescriptor& descriptor) = 0; - virtual ObjectEntryIndex GetLoadedObjectEntryIndex(const Object* object) = 0; - virtual ObjectList GetLoadedObjects() = 0; + virtual Object* GetLoadedObject(ObjectType objectType, size_t index) = 0; + template + TClass* GetLoadedObject(size_t index) + { + return static_cast(GetLoadedObject(TClass::kObjectType, index)); + } + virtual Object* GetLoadedObject(const ObjectEntryDescriptor& entry) = 0; + virtual ObjectEntryIndex GetLoadedObjectEntryIndex(std::string_view identifier) = 0; + virtual ObjectEntryIndex GetLoadedObjectEntryIndex(const ObjectEntryDescriptor& descriptor) = 0; + virtual ObjectEntryIndex GetLoadedObjectEntryIndex(const Object* object) = 0; + virtual ObjectList GetLoadedObjects() = 0; - virtual std::unique_ptr LoadTempObject(std::string_view identifier) = 0; - virtual Object* LoadObject(std::string_view identifier) = 0; - virtual Object* LoadObject(const RCTObjectEntry* entry) = 0; - virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor) = 0; - virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor, ObjectEntryIndex slot) = 0; - virtual void LoadObjects(const ObjectList& entries, const bool reportProgress = false) = 0; - virtual void UnloadObjects(const std::vector& entries) = 0; - virtual void UnloadAllTransient() = 0; - virtual void UnloadAll() = 0; + virtual std::unique_ptr LoadTempObject(std::string_view identifier) = 0; + virtual Object* LoadObject(std::string_view identifier) = 0; + virtual Object* LoadObject(const RCTObjectEntry* entry) = 0; + virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor) = 0; + virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor, ObjectEntryIndex slot) = 0; + virtual void LoadObjects(const ObjectList& entries, const bool reportProgress = false) = 0; + virtual void UnloadObjects(const std::vector& entries) = 0; + virtual void UnloadAllTransient() = 0; + virtual void UnloadAll() = 0; - virtual void ResetObjects() = 0; + virtual void ResetObjects() = 0; - virtual std::vector GetPackableObjects() = 0; - virtual const std::vector& GetAllRideEntries(ride_type_t rideType) = 0; -}; + virtual std::vector GetPackableObjects() = 0; + virtual const std::vector& GetAllRideEntries(ride_type_t rideType) = 0; + }; -[[nodiscard]] std::unique_ptr CreateObjectManager(IObjectRepository& objectRepository); + [[nodiscard]] std::unique_ptr CreateObjectManager(IObjectRepository& objectRepository); -[[nodiscard]] Object* ObjectManagerGetLoadedObject(const ObjectEntryDescriptor& entry); -[[nodiscard]] ObjectEntryIndex ObjectManagerGetLoadedObjectEntryIndex(const Object* loadedObject); -[[nodiscard]] ObjectEntryIndex ObjectManagerGetLoadedObjectEntryIndex(const ObjectEntryDescriptor& entry); -Object* ObjectManagerLoadObject(const RCTObjectEntry* entry); -void ObjectManagerUnloadObjects(const std::vector& entries); -void ObjectManagerUnloadAllObjects(); -[[nodiscard]] StringId ObjectManagerGetSourceGameString(const ObjectSourceGame sourceGame); + [[nodiscard]] Object* ObjectManagerGetLoadedObject(const ObjectEntryDescriptor& entry); + [[nodiscard]] ObjectEntryIndex ObjectManagerGetLoadedObjectEntryIndex(const Object* loadedObject); + [[nodiscard]] ObjectEntryIndex ObjectManagerGetLoadedObjectEntryIndex(const ObjectEntryDescriptor& entry); + Object* ObjectManagerLoadObject(const RCTObjectEntry* entry); + void ObjectManagerUnloadObjects(const std::vector& entries); + void ObjectManagerUnloadAllObjects(); + [[nodiscard]] StringId ObjectManagerGetSourceGameString(const ObjectSourceGame sourceGame); +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectRepository.cpp b/src/openrct2/object/ObjectRepository.cpp index 2b89c28401..0bb04d95fc 100644 --- a/src/openrct2/object/ObjectRepository.cpp +++ b/src/openrct2/object/ObjectRepository.cpp @@ -46,714 +46,717 @@ // windows.h defines CP_UTF8 #undef CP_UTF8 -using namespace OpenRCT2; -using namespace OpenRCT2::SawyerCoding; - -struct ObjectEntryHash +namespace OpenRCT2 { - size_t operator()(const RCTObjectEntry& entry) const + using namespace OpenRCT2::SawyerCoding; + + struct ObjectEntryHash { - uint32_t hash = 5381; - for (auto i : entry.name) + size_t operator()(const RCTObjectEntry& entry) const { - hash = ((hash << 5) + hash) + i; - } - return hash; - } -}; - -struct ObjectEntryEqual -{ - bool operator()(const RCTObjectEntry& lhs, const RCTObjectEntry& rhs) const - { - return memcmp(&lhs.name, &rhs.name, 8) == 0; - } -}; - -using ObjectIdentifierMap = std::unordered_map>; -using ObjectEntryMap = std::unordered_map; - -class ObjectFileIndex final : public FileIndex -{ -private: - static constexpr uint32_t kMagicNumber = 0x5844494F; // OIDX - static constexpr uint16_t kVersion = 31; - static constexpr auto kPattern = "*.dat;*.pob;*.json;*.parkobj"; - - IObjectRepository& _objectRepository; - -public: - explicit ObjectFileIndex(IObjectRepository& objectRepository, const IPlatformEnvironment& env) - : FileIndex( - "object index", kMagicNumber, kVersion, env.GetFilePath(PathId::cacheObjects), std::string(kPattern), - std::vector{ - env.GetDirectoryPath(DirBase::openrct2, DirId::objects), - env.GetDirectoryPath(DirBase::user, DirId::objects), - }) - , _objectRepository(objectRepository) - { - } - -public: - std::optional Create([[maybe_unused]] int32_t language, const std::string& path) const override - { - std::unique_ptr object; - auto extension = Path::GetExtension(path); - if (String::iequals(extension, ".json")) - { - object = ObjectFactory::CreateObjectFromJsonFile(_objectRepository, path, false); - } - else if (String::iequals(extension, ".parkobj")) - { - object = ObjectFactory::CreateObjectFromZipFile(_objectRepository, path, false); - } - else - { - object = ObjectFactory::CreateObjectFromLegacyFile(_objectRepository, path.c_str(), false); - } - - // All official DAT files have a JSON object counterpart. Avoid loading the obsolete .DAT versions, - // which can happen if the user copies the official DAT objects to their custom content folder. - if (object == nullptr - || (object->GetGeneration() == ObjectGeneration::DAT - && object->GetObjectEntry().GetSourceGame() != ObjectSourceGame::Custom)) - { - return std::nullopt; - } - - ObjectRepositoryItem item = {}; - item.Type = object->GetObjectType(); - item.Generation = object->GetGeneration(); - item.Identifier = object->GetIdentifier(); - item.ObjectEntry = object->GetObjectEntry(); - item.Version = object->GetVersion(); - item.Path = path; - item.Name = object->GetName(); - item.Authors = object->GetAuthors(); - item.Sources = object->GetSourceGames(); - if (object->IsCompatibilityObject()) - item.Flags |= ObjectItemFlags::IsCompatibilityObject; - object->SetRepositoryItem(&item); - return item; - } - -protected: - void Serialise(DataSerialiser& ds, const ObjectRepositoryItem& item) const override - { - ds << item.Type; - ds << item.Generation; - ds << item.Identifier; - ds << item.ObjectEntry; - ds << item.Path; - ds << item.Name; - - ds << item.Sources; - ds << item.Authors; - ds << item.Flags; - - switch (item.Type) - { - case ObjectType::ride: - ds << item.RideInfo.RideFlags; - ds << item.RideInfo.RideType; - break; - case ObjectType::sceneryGroup: + uint32_t hash = 5381; + for (auto i : entry.name) { - ds << item.SceneryGroupInfo.Entries; - break; + hash = ((hash << 5) + hash) + i; } - case ObjectType::footpathSurface: - ds << item.FootpathSurfaceInfo.Flags; - break; - case ObjectType::peepAnimations: - ds << item.PeepAnimationsInfo.PeepType; - break; - default: - // Switch processes only ObjectType::ride and ObjectType::sceneryGroup - break; + return hash; } - } + }; -private: - bool IsTrackReadOnly(const std::string& path) const + struct ObjectEntryEqual { - return String::startsWith(path, SearchPaths[0]) || String::startsWith(path, SearchPaths[1]); - } -}; - -class ObjectRepository final : public IObjectRepository -{ - IPlatformEnvironment& _env; - ObjectFileIndex const _fileIndex; - std::vector _items; - ObjectIdentifierMap _newItemMap; - ObjectEntryMap _itemMap; - -public: - explicit ObjectRepository(IPlatformEnvironment& env) - : _env(env) - , _fileIndex(*this, env) - { - } - - ~ObjectRepository() final - { - ClearItems(); - } - - void LoadOrConstruct(int32_t language) override - { - ClearItems(); - auto items = _fileIndex.LoadOrBuild(language); - AddItems(items); - SortItems(); - } - - void Construct(int32_t language) override - { - auto items = _fileIndex.Rebuild(language); - AddItems(items); - SortItems(); - } - - size_t GetNumObjects() const override - { - return _items.size(); - } - - const ObjectRepositoryItem* GetObjects() const override - { - return _items.data(); - } - - const ObjectRepositoryItem* FindObjectLegacy(std::string_view legacyIdentifier) const override - { - RCTObjectEntry entry = {}; - entry.SetName(legacyIdentifier); - - auto kvp = _itemMap.find(entry); - if (kvp != _itemMap.end()) + bool operator()(const RCTObjectEntry& lhs, const RCTObjectEntry& rhs) const { - return &_items[kvp->second]; + return memcmp(&lhs.name, &rhs.name, 8) == 0; } - return nullptr; - } + }; - const ObjectRepositoryItem* FindObject(std::string_view identifier) const override final + using ObjectIdentifierMap = std::unordered_map>; + using ObjectEntryMap = std::unordered_map; + + class ObjectFileIndex final : public FileIndex { - auto kvp = _newItemMap.find(identifier); - if (kvp != _newItemMap.end()) + private: + static constexpr uint32_t kMagicNumber = 0x5844494F; // OIDX + static constexpr uint16_t kVersion = 31; + static constexpr auto kPattern = "*.dat;*.pob;*.json;*.parkobj"; + + IObjectRepository& _objectRepository; + + public: + explicit ObjectFileIndex(IObjectRepository& objectRepository, const IPlatformEnvironment& env) + : FileIndex( + "object index", kMagicNumber, kVersion, env.GetFilePath(PathId::cacheObjects), std::string(kPattern), + std::vector{ + env.GetDirectoryPath(DirBase::openrct2, DirId::objects), + env.GetDirectoryPath(DirBase::user, DirId::objects), + }) + , _objectRepository(objectRepository) { - return &_items[kvp->second]; - } - return nullptr; - } - - const ObjectRepositoryItem* FindObject(const RCTObjectEntry* objectEntry) const override final - { - auto kvp = _itemMap.find(*objectEntry); - if (kvp != _itemMap.end()) - { - return &_items[kvp->second]; - } - return nullptr; - } - - const ObjectRepositoryItem* FindObject(const ObjectEntryDescriptor& entry) const override final - { - if (entry.Generation == ObjectGeneration::DAT) - return FindObject(&entry.Entry); - - return FindObject(entry.Identifier); - } - - std::unique_ptr LoadObject(const ObjectRepositoryItem* ori) override - { - Guard::ArgumentNotNull(ori, GUARD_LINE); - - auto extension = Path::GetExtension(ori->Path); - if (String::iequals(extension, ".json")) - { - return ObjectFactory::CreateObjectFromJsonFile(*this, ori->Path, !gOpenRCT2NoGraphics); - } - if (String::iequals(extension, ".parkobj")) - { - return ObjectFactory::CreateObjectFromZipFile(*this, ori->Path, !gOpenRCT2NoGraphics); } - return ObjectFactory::CreateObjectFromLegacyFile(*this, ori->Path.c_str(), !gOpenRCT2NoGraphics); - } - - void RegisterLoadedObject(const ObjectRepositoryItem* ori, std::unique_ptr&& object) override - { - ObjectRepositoryItem* item = &_items[ori->Id]; - - Guard::Assert(item->LoadedObject == nullptr, GUARD_LINE); - item->LoadedObject = std::move(object); - } - - void UnregisterLoadedObject(const ObjectRepositoryItem* ori, Object* object) override - { - ObjectRepositoryItem* item = &_items[ori->Id]; - if (item->LoadedObject.get() == object) + public: + std::optional Create([[maybe_unused]] int32_t language, const std::string& path) const override { - item->LoadedObject = nullptr; + std::unique_ptr object; + auto extension = Path::GetExtension(path); + if (String::iequals(extension, ".json")) + { + object = ObjectFactory::CreateObjectFromJsonFile(_objectRepository, path, false); + } + else if (String::iequals(extension, ".parkobj")) + { + object = ObjectFactory::CreateObjectFromZipFile(_objectRepository, path, false); + } + else + { + object = ObjectFactory::CreateObjectFromLegacyFile(_objectRepository, path.c_str(), false); + } + + // All official DAT files have a JSON object counterpart. Avoid loading the obsolete .DAT versions, + // which can happen if the user copies the official DAT objects to their custom content folder. + if (object == nullptr + || (object->GetGeneration() == ObjectGeneration::DAT + && object->GetObjectEntry().GetSourceGame() != ObjectSourceGame::Custom)) + { + return std::nullopt; + } + + ObjectRepositoryItem item = {}; + item.Type = object->GetObjectType(); + item.Generation = object->GetGeneration(); + item.Identifier = object->GetIdentifier(); + item.ObjectEntry = object->GetObjectEntry(); + item.Version = object->GetVersion(); + item.Path = path; + item.Name = object->GetName(); + item.Authors = object->GetAuthors(); + item.Sources = object->GetSourceGames(); + if (object->IsCompatibilityObject()) + item.Flags |= ObjectItemFlags::IsCompatibilityObject; + object->SetRepositoryItem(&item); + return item; } - } - void AddObject(const RCTObjectEntry* objectEntry, const void* data, size_t dataSize) override - { - utf8 objectName[9]; - ObjectEntryGetNameFixed(objectName, sizeof(objectName), objectEntry); - - // Check that the object is loadable before writing it - auto object = ObjectFactory::CreateObjectFromLegacyData(*this, objectEntry, data, dataSize); - if (object == nullptr) + protected: + void Serialise(DataSerialiser& ds, const ObjectRepositoryItem& item) const override { - Console::Error::WriteLine("[%s] Unable to export object.", objectName); + ds << item.Type; + ds << item.Generation; + ds << item.Identifier; + ds << item.ObjectEntry; + ds << item.Path; + ds << item.Name; + + ds << item.Sources; + ds << item.Authors; + ds << item.Flags; + + switch (item.Type) + { + case ObjectType::ride: + ds << item.RideInfo.RideFlags; + ds << item.RideInfo.RideType; + break; + case ObjectType::sceneryGroup: + { + ds << item.SceneryGroupInfo.Entries; + break; + } + case ObjectType::footpathSurface: + ds << item.FootpathSurfaceInfo.Flags; + break; + case ObjectType::peepAnimations: + ds << item.PeepAnimationsInfo.PeepType; + break; + default: + // Switch processes only ObjectType::ride and ObjectType::sceneryGroup + break; + } } - else + + private: + bool IsTrackReadOnly(const std::string& path) const { - LOG_VERBOSE("Adding object: [%s]", objectName); - auto path = GetPathForNewObject(ObjectGeneration::DAT, objectName); + return String::startsWith(path, SearchPaths[0]) || String::startsWith(path, SearchPaths[1]); + } + }; + + class ObjectRepository final : public IObjectRepository + { + IPlatformEnvironment& _env; + ObjectFileIndex const _fileIndex; + std::vector _items; + ObjectIdentifierMap _newItemMap; + ObjectEntryMap _itemMap; + + public: + explicit ObjectRepository(IPlatformEnvironment& env) + : _env(env) + , _fileIndex(*this, env) + { + } + + ~ObjectRepository() final + { + ClearItems(); + } + + void LoadOrConstruct(int32_t language) override + { + ClearItems(); + auto items = _fileIndex.LoadOrBuild(language); + AddItems(items); + SortItems(); + } + + void Construct(int32_t language) override + { + auto items = _fileIndex.Rebuild(language); + AddItems(items); + SortItems(); + } + + size_t GetNumObjects() const override + { + return _items.size(); + } + + const ObjectRepositoryItem* GetObjects() const override + { + return _items.data(); + } + + const ObjectRepositoryItem* FindObjectLegacy(std::string_view legacyIdentifier) const override + { + RCTObjectEntry entry = {}; + entry.SetName(legacyIdentifier); + + auto kvp = _itemMap.find(entry); + if (kvp != _itemMap.end()) + { + return &_items[kvp->second]; + } + return nullptr; + } + + const ObjectRepositoryItem* FindObject(std::string_view identifier) const override final + { + auto kvp = _newItemMap.find(identifier); + if (kvp != _newItemMap.end()) + { + return &_items[kvp->second]; + } + return nullptr; + } + + const ObjectRepositoryItem* FindObject(const RCTObjectEntry* objectEntry) const override final + { + auto kvp = _itemMap.find(*objectEntry); + if (kvp != _itemMap.end()) + { + return &_items[kvp->second]; + } + return nullptr; + } + + const ObjectRepositoryItem* FindObject(const ObjectEntryDescriptor& entry) const override final + { + if (entry.Generation == ObjectGeneration::DAT) + return FindObject(&entry.Entry); + + return FindObject(entry.Identifier); + } + + std::unique_ptr LoadObject(const ObjectRepositoryItem* ori) override + { + Guard::ArgumentNotNull(ori, GUARD_LINE); + + auto extension = Path::GetExtension(ori->Path); + if (String::iequals(extension, ".json")) + { + return ObjectFactory::CreateObjectFromJsonFile(*this, ori->Path, !gOpenRCT2NoGraphics); + } + if (String::iequals(extension, ".parkobj")) + { + return ObjectFactory::CreateObjectFromZipFile(*this, ori->Path, !gOpenRCT2NoGraphics); + } + + return ObjectFactory::CreateObjectFromLegacyFile(*this, ori->Path.c_str(), !gOpenRCT2NoGraphics); + } + + void RegisterLoadedObject(const ObjectRepositoryItem* ori, std::unique_ptr&& object) override + { + ObjectRepositoryItem* item = &_items[ori->Id]; + + Guard::Assert(item->LoadedObject == nullptr, GUARD_LINE); + item->LoadedObject = std::move(object); + } + + void UnregisterLoadedObject(const ObjectRepositoryItem* ori, Object* object) override + { + ObjectRepositoryItem* item = &_items[ori->Id]; + if (item->LoadedObject.get() == object) + { + item->LoadedObject = nullptr; + } + } + + void AddObject(const RCTObjectEntry* objectEntry, const void* data, size_t dataSize) override + { + utf8 objectName[9]; + ObjectEntryGetNameFixed(objectName, sizeof(objectName), objectEntry); + + // Check that the object is loadable before writing it + auto object = ObjectFactory::CreateObjectFromLegacyData(*this, objectEntry, data, dataSize); + if (object == nullptr) + { + Console::Error::WriteLine("[%s] Unable to export object.", objectName); + } + else + { + LOG_VERBOSE("Adding object: [%s]", objectName); + auto path = GetPathForNewObject(ObjectGeneration::DAT, objectName); + try + { + SaveObject(path, objectEntry, data, dataSize); + ScanObject(path); + } + catch (const std::exception&) + { + Console::Error::WriteLine("Failed saving object: [%s] to '%s'.", objectName, path.c_str()); + } + } + } + + void AddObjectFromFile( + ObjectGeneration generation, std::string_view objectName, const void* data, size_t dataSize) override + { + LOG_VERBOSE("Adding object: [%s]", std::string(objectName).c_str()); + auto path = GetPathForNewObject(generation, objectName); try { - SaveObject(path, objectEntry, data, dataSize); + File::WriteAllBytes(path, data, dataSize); ScanObject(path); } catch (const std::exception&) { - Console::Error::WriteLine("Failed saving object: [%s] to '%s'.", objectName, path.c_str()); + Console::Error::WriteLine("Failed saving object: [%s] to '%s'.", std::string(objectName).c_str(), path.c_str()); } } - } - void AddObjectFromFile(ObjectGeneration generation, std::string_view objectName, const void* data, size_t dataSize) override - { - LOG_VERBOSE("Adding object: [%s]", std::string(objectName).c_str()); - auto path = GetPathForNewObject(generation, objectName); - try + void ExportPackedObject(IStream* stream) override { - File::WriteAllBytes(path, data, dataSize); - ScanObject(path); - } - catch (const std::exception&) - { - Console::Error::WriteLine("Failed saving object: [%s] to '%s'.", std::string(objectName).c_str(), path.c_str()); - } - } + auto chunkReader = SawyerChunkReader(stream); - void ExportPackedObject(IStream* stream) override - { - auto chunkReader = SawyerChunkReader(stream); - - // Check if we already have this object - RCTObjectEntry entry = stream->ReadValue(); - if (FindObject(&entry) != nullptr) - { - chunkReader.SkipChunk(); - } - else - { - // Read object and save to new file - std::shared_ptr chunk = chunkReader.ReadChunk(); - AddObject(&entry, chunk->GetData(), chunk->GetLength()); - } - } - -private: - void ClearItems() - { - _items.clear(); - _newItemMap.clear(); - _itemMap.clear(); - } - - void SortItems() - { - std::sort(_items.begin(), _items.end(), [](const ObjectRepositoryItem& a, const ObjectRepositoryItem& b) -> bool { - return String::compare(a.Name, b.Name) < 0; - }); - - // Fix the IDs - for (size_t i = 0; i < _items.size(); i++) - { - _items[i].Id = i; - } - - // Rebuild item map - _itemMap.clear(); - _newItemMap.clear(); - for (size_t i = 0; i < _items.size(); i++) - { - RCTObjectEntry entry = _items[i].ObjectEntry; - _itemMap[entry] = i; - if (!_items[i].Identifier.empty()) + // Check if we already have this object + RCTObjectEntry entry = stream->ReadValue(); + if (FindObject(&entry) != nullptr) { - _newItemMap[_items[i].Identifier] = i; + chunkReader.SkipChunk(); } - } - } - - void AddItems(const std::vector& items) - { - size_t numConflicts = 0; - for (const auto& item : items) - { - if (!AddItem(item)) + else { - numConflicts++; + // Read object and save to new file + std::shared_ptr chunk = chunkReader.ReadChunk(); + AddObject(&entry, chunk->GetData(), chunk->GetLength()); } } - if (numConflicts > 0) - { - Console::Error::WriteLine("%zu object conflicts found.", numConflicts); - } - } - bool AddItem(const ObjectRepositoryItem& item) - { - const auto newIdent = MapToNewObjectIdentifier(item.Identifier); - if (!newIdent.empty()) + private: + void ClearItems() { - Console::Error::WriteLine("Mixed install detected. Not loading: '%s'", item.Identifier.c_str()); + _items.clear(); + _newItemMap.clear(); + _itemMap.clear(); + } + + void SortItems() + { + std::sort(_items.begin(), _items.end(), [](const ObjectRepositoryItem& a, const ObjectRepositoryItem& b) -> bool { + return String::compare(a.Name, b.Name) < 0; + }); + + // Fix the IDs + for (size_t i = 0; i < _items.size(); i++) + { + _items[i].Id = i; + } + + // Rebuild item map + _itemMap.clear(); + _newItemMap.clear(); + for (size_t i = 0; i < _items.size(); i++) + { + RCTObjectEntry entry = _items[i].ObjectEntry; + _itemMap[entry] = i; + if (!_items[i].Identifier.empty()) + { + _newItemMap[_items[i].Identifier] = i; + } + } + } + + void AddItems(const std::vector& items) + { + size_t numConflicts = 0; + for (const auto& item : items) + { + if (!AddItem(item)) + { + numConflicts++; + } + } + if (numConflicts > 0) + { + Console::Error::WriteLine("%zu object conflicts found.", numConflicts); + } + } + + bool AddItem(const ObjectRepositoryItem& item) + { + const auto newIdent = MapToNewObjectIdentifier(item.Identifier); + if (!newIdent.empty()) + { + Console::Error::WriteLine("Mixed install detected. Not loading: '%s'", item.Identifier.c_str()); + return false; + } + const ObjectRepositoryItem* conflict{}; + if (item.ObjectEntry.name[0] != '\0') + { + conflict = FindObject(&item.ObjectEntry); + } + if (conflict == nullptr) + { + conflict = FindObject(item.Identifier); + } + + if (conflict == nullptr) + { + size_t index = _items.size(); + auto copy = item; + copy.Id = index; + _items.push_back(std::move(copy)); + if (!item.Identifier.empty()) + { + _newItemMap[item.Identifier] = index; + } + if (!item.ObjectEntry.IsEmpty()) + { + _itemMap[item.ObjectEntry] = index; + } + return true; + } + // When there is a conflict between a DAT file and a JSON file, the JSON should take precedence. + else if (item.Generation == ObjectGeneration::JSON && conflict->Generation == ObjectGeneration::DAT) + { + const auto id = conflict->Id; + const auto oldPath = conflict->Path; + _items[id] = item; + _items[id].Id = id; + if (!item.Identifier.empty()) + { + _newItemMap[item.Identifier] = id; + } + + Console::Error::WriteLine("Object conflict: '%s' was overridden by '%s'", oldPath.c_str(), item.Path.c_str()); + return true; + } + + Console::Error::WriteLine("Object conflict: '%s'", conflict->Path.c_str()); + Console::Error::WriteLine(" : '%s'", item.Path.c_str()); return false; } - const ObjectRepositoryItem* conflict{}; - if (item.ObjectEntry.name[0] != '\0') - { - conflict = FindObject(&item.ObjectEntry); - } - if (conflict == nullptr) - { - conflict = FindObject(item.Identifier); - } - if (conflict == nullptr) + void ScanObject(const std::string& path) { - size_t index = _items.size(); - auto copy = item; - copy.Id = index; - _items.push_back(std::move(copy)); - if (!item.Identifier.empty()) + auto language = LocalisationService_GetCurrentLanguage(); + if (auto result = _fileIndex.Create(language, path); result.has_value()) { - _newItemMap[item.Identifier] = index; + AddItem(result.value()); } - if (!item.ObjectEntry.IsEmpty()) - { - _itemMap[item.ObjectEntry] = index; - } - return true; - } - // When there is a conflict between a DAT file and a JSON file, the JSON should take precedence. - else if (item.Generation == ObjectGeneration::JSON && conflict->Generation == ObjectGeneration::DAT) - { - const auto id = conflict->Id; - const auto oldPath = conflict->Path; - _items[id] = item; - _items[id].Id = id; - if (!item.Identifier.empty()) - { - _newItemMap[item.Identifier] = id; - } - - Console::Error::WriteLine("Object conflict: '%s' was overridden by '%s'", oldPath.c_str(), item.Path.c_str()); - return true; } - Console::Error::WriteLine("Object conflict: '%s'", conflict->Path.c_str()); - Console::Error::WriteLine(" : '%s'", item.Path.c_str()); - return false; - } + // 0x0098DA2C + static constexpr std::array kLegacyObjectEntryGroupEncoding = { + ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rle, + ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rle, + ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rotate, + }; - void ScanObject(const std::string& path) - { - auto language = LocalisationService_GetCurrentLanguage(); - if (auto result = _fileIndex.Create(language, path); result.has_value()) + static void SaveObject( + std::string_view path, const RCTObjectEntry* entry, const void* data, size_t dataSize, bool fixChecksum = true) { - AddItem(result.value()); - } - } - - // 0x0098DA2C - static constexpr std::array kLegacyObjectEntryGroupEncoding = { - ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rle, - ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rle, - ChunkEncoding::rle, ChunkEncoding::rle, ChunkEncoding::rotate, - }; - - static void SaveObject( - std::string_view path, const RCTObjectEntry* entry, const void* data, size_t dataSize, bool fixChecksum = true) - { - if (fixChecksum) - { - uint32_t realChecksum = ObjectCalculateChecksum(entry, data, dataSize); - if (realChecksum != entry->checksum) + if (fixChecksum) { - char objectName[9]; - ObjectEntryGetNameFixed(objectName, sizeof(objectName), entry); - LOG_VERBOSE("[%s] Incorrect checksum, adding salt bytes...", objectName); - - // Calculate the value of extra bytes that can be appended to the data so that the - // data is then valid for the object's checksum - size_t extraBytesCount = 0; - void* extraBytes = CalculateExtraBytesToFixChecksum(realChecksum, entry->checksum, &extraBytesCount); - - // Create new data blob with appended bytes - size_t newDataSize = dataSize + extraBytesCount; - uint8_t* newData = Memory::Allocate(newDataSize); - uint8_t* newDataSaltOffset = newData + dataSize; - std::copy_n(static_cast(data), dataSize, newData); - std::copy_n(static_cast(extraBytes), extraBytesCount, newDataSaltOffset); - - try + uint32_t realChecksum = ObjectCalculateChecksum(entry, data, dataSize); + if (realChecksum != entry->checksum) { - uint32_t newRealChecksum = ObjectCalculateChecksum(entry, newData, newDataSize); - if (newRealChecksum != entry->checksum) - { - Console::Error::WriteLine("CalculateExtraBytesToFixChecksum failed to fix checksum."); + char objectName[9]; + ObjectEntryGetNameFixed(objectName, sizeof(objectName), entry); + LOG_VERBOSE("[%s] Incorrect checksum, adding salt bytes...", objectName); - // Save old data form - SaveObject(path, entry, data, dataSize, false); + // Calculate the value of extra bytes that can be appended to the data so that the + // data is then valid for the object's checksum + size_t extraBytesCount = 0; + void* extraBytes = CalculateExtraBytesToFixChecksum(realChecksum, entry->checksum, &extraBytesCount); + + // Create new data blob with appended bytes + size_t newDataSize = dataSize + extraBytesCount; + uint8_t* newData = Memory::Allocate(newDataSize); + uint8_t* newDataSaltOffset = newData + dataSize; + std::copy_n(static_cast(data), dataSize, newData); + std::copy_n(static_cast(extraBytes), extraBytesCount, newDataSaltOffset); + + try + { + uint32_t newRealChecksum = ObjectCalculateChecksum(entry, newData, newDataSize); + if (newRealChecksum != entry->checksum) + { + Console::Error::WriteLine("CalculateExtraBytesToFixChecksum failed to fix checksum."); + + // Save old data form + SaveObject(path, entry, data, dataSize, false); + } + else + { + // Save new data form + SaveObject(path, entry, newData, newDataSize, false); + } + Memory::Free(newData); + Memory::Free(extraBytes); + } + catch (const std::exception&) + { + Memory::Free(newData); + Memory::Free(extraBytes); + throw; + } + return; + } + } + + // Encode data + ObjectType objectType = entry->GetType(); + SawyerCoding::ChunkHeader chunkHeader; + chunkHeader.encoding = kLegacyObjectEntryGroupEncoding[EnumValue(objectType)]; + chunkHeader.length = static_cast(dataSize); + uint8_t* encodedDataBuffer = Memory::Allocate(0x600000); + size_t encodedDataSize = SawyerCoding::WriteChunkBuffer( + encodedDataBuffer, reinterpret_cast(data), chunkHeader); + + // Save to file + try + { + auto fs = FileStream(std::string(path), FileMode::write); + fs.Write(entry, sizeof(RCTObjectEntry)); + fs.Write(encodedDataBuffer, encodedDataSize); + + Memory::Free(encodedDataBuffer); + } + catch (const std::exception&) + { + Memory::Free(encodedDataBuffer); + throw; + } + } + + static void* CalculateExtraBytesToFixChecksum(int32_t currentChecksum, int32_t targetChecksum, size_t* outSize) + { + // Allocate 11 extra bytes to manipulate the checksum + uint8_t* salt = Memory::Allocate(11); + if (outSize != nullptr) + *outSize = 11; + + // Next work out which bits need to be flipped to make the current checksum match the one in the file + // The bitwise rotation compensates for the rotation performed during the checksum calculation*/ + int32_t bitsToFlip = targetChecksum ^ ((currentChecksum << 25) | (currentChecksum >> 7)); + + // Each set bit encountered during encoding flips one bit of the resulting checksum (so each bit of the checksum is + // an XOR of bits from the file). Here, we take each bit that should be flipped in the checksum and set one of the + // bits in the data that maps to it. 11 bytes is the minimum needed to touch every bit of the checksum - with less + // than that, you wouldn't always be able to make the checksum come out to the desired target + salt[0] = (bitsToFlip & 0x00000001) << 7; + salt[1] = ((bitsToFlip & 0x00200000) >> 14); + salt[2] = ((bitsToFlip & 0x000007F8) >> 3); + salt[3] = ((bitsToFlip & 0xFF000000) >> 24); + salt[4] = ((bitsToFlip & 0x00100000) >> 13); + salt[5] = (bitsToFlip & 0x00000004) >> 2; + salt[6] = 0; + salt[7] = ((bitsToFlip & 0x000FF000) >> 12); + salt[8] = (bitsToFlip & 0x00000002) >> 1; + salt[9] = (bitsToFlip & 0x00C00000) >> 22; + salt[10] = (bitsToFlip & 0x00000800) >> 11; + + return salt; + } + + std::string GetPathForNewObject(ObjectGeneration generation, std::string_view name) + { + // Get object directory and create it if it doesn't exist + auto userObjPath = _env.GetDirectoryPath(DirBase::user, DirId::objects); + Path::CreateDirectory(userObjPath); + + // Find a unique file name + auto fileName = GetFileNameForNewObject(generation, name); + auto extension = (generation == ObjectGeneration::DAT ? u8".DAT" : u8".parkobj"); + auto fullPath = Path::Combine(userObjPath, fileName + extension); + auto counter = 1u; + while (File::Exists(fullPath)) + { + counter++; + fullPath = Path::Combine(userObjPath, String::stdFormat("%s-%02X%s", fileName.c_str(), counter, extension)); + } + + return fullPath; + } + + std::string GetFileNameForNewObject(ObjectGeneration generation, std::string_view name) + { + if (generation == ObjectGeneration::DAT) + { + // Trim name + char normalisedName[9] = { 0 }; + auto maxLength = std::min(name.size(), 8); + for (size_t i = 0; i < maxLength; i++) + { + if (name[i] != ' ') + { + normalisedName[i] = toupper(name[i]); } else { - // Save new data form - SaveObject(path, entry, newData, newDataSize, false); + normalisedName[i] = '\0'; + break; } - Memory::Free(newData); - Memory::Free(extraBytes); } - catch (const std::exception&) - { - Memory::Free(newData); - Memory::Free(extraBytes); - throw; - } - return; + + // Convert to UTF-8 filename + return String::convertToUtf8(normalisedName, OpenRCT2::CodePage::CP_1252); } - } - - // Encode data - ObjectType objectType = entry->GetType(); - SawyerCoding::ChunkHeader chunkHeader; - chunkHeader.encoding = kLegacyObjectEntryGroupEncoding[EnumValue(objectType)]; - chunkHeader.length = static_cast(dataSize); - uint8_t* encodedDataBuffer = Memory::Allocate(0x600000); - size_t encodedDataSize = SawyerCoding::WriteChunkBuffer( - encodedDataBuffer, reinterpret_cast(data), chunkHeader); - - // Save to file - try - { - auto fs = FileStream(std::string(path), FileMode::write); - fs.Write(entry, sizeof(RCTObjectEntry)); - fs.Write(encodedDataBuffer, encodedDataSize); - - Memory::Free(encodedDataBuffer); - } - catch (const std::exception&) - { - Memory::Free(encodedDataBuffer); - throw; - } - } - - static void* CalculateExtraBytesToFixChecksum(int32_t currentChecksum, int32_t targetChecksum, size_t* outSize) - { - // Allocate 11 extra bytes to manipulate the checksum - uint8_t* salt = Memory::Allocate(11); - if (outSize != nullptr) - *outSize = 11; - - // Next work out which bits need to be flipped to make the current checksum match the one in the file - // The bitwise rotation compensates for the rotation performed during the checksum calculation*/ - int32_t bitsToFlip = targetChecksum ^ ((currentChecksum << 25) | (currentChecksum >> 7)); - - // Each set bit encountered during encoding flips one bit of the resulting checksum (so each bit of the checksum is an - // XOR of bits from the file). Here, we take each bit that should be flipped in the checksum and set one of the bits in - // the data that maps to it. 11 bytes is the minimum needed to touch every bit of the checksum - with less than that, - // you wouldn't always be able to make the checksum come out to the desired target - salt[0] = (bitsToFlip & 0x00000001) << 7; - salt[1] = ((bitsToFlip & 0x00200000) >> 14); - salt[2] = ((bitsToFlip & 0x000007F8) >> 3); - salt[3] = ((bitsToFlip & 0xFF000000) >> 24); - salt[4] = ((bitsToFlip & 0x00100000) >> 13); - salt[5] = (bitsToFlip & 0x00000004) >> 2; - salt[6] = 0; - salt[7] = ((bitsToFlip & 0x000FF000) >> 12); - salt[8] = (bitsToFlip & 0x00000002) >> 1; - salt[9] = (bitsToFlip & 0x00C00000) >> 22; - salt[10] = (bitsToFlip & 0x00000800) >> 11; - - return salt; - } - - std::string GetPathForNewObject(ObjectGeneration generation, std::string_view name) - { - // Get object directory and create it if it doesn't exist - auto userObjPath = _env.GetDirectoryPath(DirBase::user, DirId::objects); - Path::CreateDirectory(userObjPath); - - // Find a unique file name - auto fileName = GetFileNameForNewObject(generation, name); - auto extension = (generation == ObjectGeneration::DAT ? u8".DAT" : u8".parkobj"); - auto fullPath = Path::Combine(userObjPath, fileName + extension); - auto counter = 1u; - while (File::Exists(fullPath)) - { - counter++; - fullPath = Path::Combine(userObjPath, String::stdFormat("%s-%02X%s", fileName.c_str(), counter, extension)); - } - - return fullPath; - } - - std::string GetFileNameForNewObject(ObjectGeneration generation, std::string_view name) - { - if (generation == ObjectGeneration::DAT) - { - // Trim name - char normalisedName[9] = { 0 }; - auto maxLength = std::min(name.size(), 8); - for (size_t i = 0; i < maxLength; i++) + else { - if (name[i] != ' ') - { - normalisedName[i] = toupper(name[i]); - } - else - { - normalisedName[i] = '\0'; - break; - } + return std::string(name); + } + } + + void WritePackedObject(OpenRCT2::IStream* stream, const RCTObjectEntry* entry) + { + const ObjectRepositoryItem* item = FindObject(entry); + if (item == nullptr) + { + throw std::runtime_error(String::stdFormat("Unable to find object '%.8s'", entry->name)); } - // Convert to UTF-8 filename - return String::convertToUtf8(normalisedName, OpenRCT2::CodePage::CP_1252); + // Read object data from file + auto fs = OpenRCT2::FileStream(item->Path, OpenRCT2::FileMode::open); + auto fileEntry = fs.ReadValue(); + if (*entry != fileEntry) + { + throw std::runtime_error("Header found in object file does not match object to pack."); + } + auto chunkReader = SawyerChunkReader(&fs); + auto chunk = chunkReader.ReadChunk(); + + // Write object data to stream + auto chunkWriter = SawyerChunkWriter(stream); + stream->WriteValue(*entry); + chunkWriter.WriteChunk(chunk.get()); } - else + }; + + std::unique_ptr CreateObjectRepository(IPlatformEnvironment& env) + { + return std::make_unique(env); + } + + bool IsObjectCustom(const ObjectRepositoryItem* object) + { + Guard::ArgumentNotNull(object); + switch (object->GetFirstSourceGame()) { - return std::string(name); + case ObjectSourceGame::RCT1: + case ObjectSourceGame::AddedAttractions: + case ObjectSourceGame::LoopyLandscapes: + case ObjectSourceGame::RCT2: + case ObjectSourceGame::WackyWorlds: + case ObjectSourceGame::TimeTwister: + case ObjectSourceGame::OpenRCT2Official: + return false; + default: + return true; } } - void WritePackedObject(OpenRCT2::IStream* stream, const RCTObjectEntry* entry) + std::unique_ptr ObjectRepositoryLoadObject(const RCTObjectEntry* objectEntry) { - const ObjectRepositoryItem* item = FindObject(entry); - if (item == nullptr) + std::unique_ptr object; + auto& objRepository = GetContext()->GetObjectRepository(); + const ObjectRepositoryItem* ori = objRepository.FindObject(objectEntry); + if (ori != nullptr) { - throw std::runtime_error(String::stdFormat("Unable to find object '%.8s'", entry->name)); + object = objRepository.LoadObject(ori); + if (object != nullptr) + { + object->Load(); + } } - - // Read object data from file - auto fs = OpenRCT2::FileStream(item->Path, OpenRCT2::FileMode::open); - auto fileEntry = fs.ReadValue(); - if (*entry != fileEntry) - { - throw std::runtime_error("Header found in object file does not match object to pack."); - } - auto chunkReader = SawyerChunkReader(&fs); - auto chunk = chunkReader.ReadChunk(); - - // Write object data to stream - auto chunkWriter = SawyerChunkWriter(stream); - stream->WriteValue(*entry); - chunkWriter.WriteChunk(chunk.get()); + return object; } -}; -std::unique_ptr CreateObjectRepository(IPlatformEnvironment& env) -{ - return std::make_unique(env); -} - -bool IsObjectCustom(const ObjectRepositoryItem* object) -{ - Guard::ArgumentNotNull(object); - switch (object->GetFirstSourceGame()) + size_t ObjectRepositoryGetItemsCount() { - case ObjectSourceGame::RCT1: - case ObjectSourceGame::AddedAttractions: - case ObjectSourceGame::LoopyLandscapes: - case ObjectSourceGame::RCT2: - case ObjectSourceGame::WackyWorlds: - case ObjectSourceGame::TimeTwister: - case ObjectSourceGame::OpenRCT2Official: - return false; - default: - return true; + auto& objectRepository = GetContext()->GetObjectRepository(); + return objectRepository.GetNumObjects(); } -} -std::unique_ptr ObjectRepositoryLoadObject(const RCTObjectEntry* objectEntry) -{ - std::unique_ptr object; - auto& objRepository = GetContext()->GetObjectRepository(); - const ObjectRepositoryItem* ori = objRepository.FindObject(objectEntry); - if (ori != nullptr) + const ObjectRepositoryItem* ObjectRepositoryGetItems() { - object = objRepository.LoadObject(ori); - if (object != nullptr) - { - object->Load(); - } + auto& objectRepository = GetContext()->GetObjectRepository(); + return objectRepository.GetObjects(); } - return object; -} -size_t ObjectRepositoryGetItemsCount() -{ - auto& objectRepository = GetContext()->GetObjectRepository(); - return objectRepository.GetNumObjects(); -} - -const ObjectRepositoryItem* ObjectRepositoryGetItems() -{ - auto& objectRepository = GetContext()->GetObjectRepository(); - return objectRepository.GetObjects(); -} - -const ObjectRepositoryItem* ObjectRepositoryFindObjectByEntry(const RCTObjectEntry* entry) -{ - auto& objectRepository = GetContext()->GetObjectRepository(); - return objectRepository.FindObject(entry); -} - -const ObjectRepositoryItem* ObjectRepositoryFindObjectByName(const char* name) -{ - auto& objectRepository = GetContext()->GetObjectRepository(); - return objectRepository.FindObjectLegacy(name); -} - -int32_t ObjectCalculateChecksum(const RCTObjectEntry* entry, const void* data, size_t dataLength) -{ - const uint8_t* entryBytePtr = reinterpret_cast(entry); - - uint32_t checksum = 0xF369A75B; - checksum ^= entryBytePtr[0]; - checksum = Numerics::rol32(checksum, 11); - for (int32_t i = 4; i < 12; i++) + const ObjectRepositoryItem* ObjectRepositoryFindObjectByEntry(const RCTObjectEntry* entry) { - checksum ^= entryBytePtr[i]; + auto& objectRepository = GetContext()->GetObjectRepository(); + return objectRepository.FindObject(entry); + } + + const ObjectRepositoryItem* ObjectRepositoryFindObjectByName(const char* name) + { + auto& objectRepository = GetContext()->GetObjectRepository(); + return objectRepository.FindObjectLegacy(name); + } + + int32_t ObjectCalculateChecksum(const RCTObjectEntry* entry, const void* data, size_t dataLength) + { + const uint8_t* entryBytePtr = reinterpret_cast(entry); + + uint32_t checksum = 0xF369A75B; + checksum ^= entryBytePtr[0]; checksum = Numerics::rol32(checksum, 11); - } - - const uint8_t* dataBytes = reinterpret_cast(data); - const size_t dataLength32 = dataLength - (dataLength & 31); - for (size_t i = 0; i < 32; i++) - { - for (size_t j = i; j < dataLength32; j += 32) + for (int32_t i = 4; i < 12; i++) { - checksum ^= dataBytes[j]; + checksum ^= entryBytePtr[i]; + checksum = Numerics::rol32(checksum, 11); } - checksum = Numerics::rol32(checksum, 11); - } - for (size_t i = dataLength32; i < dataLength; i++) - { - checksum ^= dataBytes[i]; - checksum = Numerics::rol32(checksum, 11); - } - return static_cast(checksum); -} + const uint8_t* dataBytes = reinterpret_cast(data); + const size_t dataLength32 = dataLength - (dataLength & 31); + for (size_t i = 0; i < 32; i++) + { + for (size_t j = i; j < dataLength32; j += 32) + { + checksum ^= dataBytes[j]; + } + checksum = Numerics::rol32(checksum, 11); + } + for (size_t i = dataLength32; i < dataLength; i++) + { + checksum ^= dataBytes[i]; + checksum = Numerics::rol32(checksum, 11); + } + + return static_cast(checksum); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectRepository.h b/src/openrct2/object/ObjectRepository.h index d71c5abab6..1f18dec50c 100644 --- a/src/openrct2/object/ObjectRepository.h +++ b/src/openrct2/object/ObjectRepository.h @@ -15,13 +15,7 @@ #include #include -namespace OpenRCT2 -{ - struct IStream; - struct IPlatformEnvironment; -} // namespace OpenRCT2 - -class Object; +struct RenderTarget; enum class RideCategory : uint8_t; namespace OpenRCT2::Localisation @@ -29,84 +23,90 @@ namespace OpenRCT2::Localisation class LocalisationService; } -struct RenderTarget; - -enum ObjectItemFlags : uint8_t +namespace OpenRCT2 { - IsCompatibilityObject = 1, -}; + struct IStream; + struct IPlatformEnvironment; + class Object; -struct ObjectRepositoryItem -{ - size_t Id; - ObjectType Type; - ObjectGeneration Generation; - std::string Identifier; // e.g. rct2.c3d - RCTObjectEntry ObjectEntry; - std::string Path; - std::string Name; - ObjectVersion Version; - std::vector Authors; - std::vector Sources; - uint8_t Flags{}; - std::shared_ptr LoadedObject{}; - struct + enum ObjectItemFlags : uint8_t { - uint8_t RideFlags; - ride_type_t RideType[OpenRCT2::RCT2::ObjectLimits::kMaxRideTypesPerRideEntry]; - } RideInfo; - struct - { - std::vector Entries; - } SceneryGroupInfo; - struct + IsCompatibilityObject = 1, + }; + + struct ObjectRepositoryItem { + size_t Id; + ObjectType Type; + ObjectGeneration Generation; + std::string Identifier; // e.g. rct2.c3d + RCTObjectEntry ObjectEntry; + std::string Path; + std::string Name; + ObjectVersion Version; + std::vector Authors; + std::vector Sources; uint8_t Flags{}; - } FootpathSurfaceInfo; - struct + std::shared_ptr LoadedObject{}; + struct + { + uint8_t RideFlags; + ride_type_t RideType[RCT2::ObjectLimits::kMaxRideTypesPerRideEntry]; + } RideInfo; + struct + { + std::vector Entries; + } SceneryGroupInfo; + struct + { + uint8_t Flags{}; + } FootpathSurfaceInfo; + struct + { + uint8_t PeepType{}; + } PeepAnimationsInfo; + + [[nodiscard]] ObjectSourceGame GetFirstSourceGame() const + { + if (Sources.empty()) + return ObjectSourceGame::Custom; + + return static_cast(Sources[0]); + } + }; + + struct IObjectRepository { - uint8_t PeepType{}; - } PeepAnimationsInfo; + virtual ~IObjectRepository() = default; - [[nodiscard]] ObjectSourceGame GetFirstSourceGame() const - { - if (Sources.empty()) - return ObjectSourceGame::Custom; + virtual void LoadOrConstruct(int32_t language) = 0; + virtual void Construct(int32_t language) = 0; + [[nodiscard]] virtual size_t GetNumObjects() const = 0; + [[nodiscard]] virtual const ObjectRepositoryItem* GetObjects() const = 0; + [[nodiscard]] virtual const ObjectRepositoryItem* FindObjectLegacy(std::string_view legacyIdentifier) const = 0; + [[nodiscard]] virtual const ObjectRepositoryItem* FindObject(std::string_view identifier) const = 0; + [[nodiscard]] virtual const ObjectRepositoryItem* FindObject(const RCTObjectEntry* objectEntry) const = 0; + [[nodiscard]] virtual const ObjectRepositoryItem* FindObject(const ObjectEntryDescriptor& oed) const = 0; - return static_cast(Sources[0]); - } -}; + [[nodiscard]] virtual std::unique_ptr LoadObject(const ObjectRepositoryItem* ori) = 0; + virtual void RegisterLoadedObject(const ObjectRepositoryItem* ori, std::unique_ptr&& object) = 0; + virtual void UnregisterLoadedObject(const ObjectRepositoryItem* ori, Object* object) = 0; -struct IObjectRepository -{ - virtual ~IObjectRepository() = default; + virtual void AddObject(const RCTObjectEntry* objectEntry, const void* data, size_t dataSize) = 0; + virtual void AddObjectFromFile( + ObjectGeneration generation, std::string_view objectName, const void* data, size_t dataSize) + = 0; - virtual void LoadOrConstruct(int32_t language) = 0; - virtual void Construct(int32_t language) = 0; - [[nodiscard]] virtual size_t GetNumObjects() const = 0; - [[nodiscard]] virtual const ObjectRepositoryItem* GetObjects() const = 0; - [[nodiscard]] virtual const ObjectRepositoryItem* FindObjectLegacy(std::string_view legacyIdentifier) const = 0; - [[nodiscard]] virtual const ObjectRepositoryItem* FindObject(std::string_view identifier) const = 0; - [[nodiscard]] virtual const ObjectRepositoryItem* FindObject(const RCTObjectEntry* objectEntry) const = 0; - [[nodiscard]] virtual const ObjectRepositoryItem* FindObject(const ObjectEntryDescriptor& oed) const = 0; + virtual void ExportPackedObject(IStream* stream) = 0; + }; - [[nodiscard]] virtual std::unique_ptr LoadObject(const ObjectRepositoryItem* ori) = 0; - virtual void RegisterLoadedObject(const ObjectRepositoryItem* ori, std::unique_ptr&& object) = 0; - virtual void UnregisterLoadedObject(const ObjectRepositoryItem* ori, Object* object) = 0; + [[nodiscard]] std::unique_ptr CreateObjectRepository(IPlatformEnvironment& env); - virtual void AddObject(const RCTObjectEntry* objectEntry, const void* data, size_t dataSize) = 0; - virtual void AddObjectFromFile(ObjectGeneration generation, std::string_view objectName, const void* data, size_t dataSize) - = 0; + [[nodiscard]] bool IsObjectCustom(const ObjectRepositoryItem* object); - virtual void ExportPackedObject(OpenRCT2::IStream* stream) = 0; -}; - -[[nodiscard]] std::unique_ptr CreateObjectRepository(OpenRCT2::IPlatformEnvironment& env); - -[[nodiscard]] bool IsObjectCustom(const ObjectRepositoryItem* object); - -[[nodiscard]] size_t ObjectRepositoryGetItemsCount(); -[[nodiscard]] const ObjectRepositoryItem* ObjectRepositoryGetItems(); -[[nodiscard]] const ObjectRepositoryItem* ObjectRepositoryFindObjectByEntry(const RCTObjectEntry* entry); -[[nodiscard]] const ObjectRepositoryItem* ObjectRepositoryFindObjectByName(const char* name); -[[nodiscard]] std::unique_ptr ObjectRepositoryLoadObject(const RCTObjectEntry* objectEntry); + [[nodiscard]] size_t ObjectRepositoryGetItemsCount(); + [[nodiscard]] const ObjectRepositoryItem* ObjectRepositoryGetItems(); + [[nodiscard]] const ObjectRepositoryItem* ObjectRepositoryFindObjectByEntry(const RCTObjectEntry* entry); + [[nodiscard]] const ObjectRepositoryItem* ObjectRepositoryFindObjectByName(const char* name); + [[nodiscard]] std::unique_ptr ObjectRepositoryLoadObject(const RCTObjectEntry* objectEntry); +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectTypes.cpp b/src/openrct2/object/ObjectTypes.cpp index e98768a4c6..30f88aa313 100644 --- a/src/openrct2/object/ObjectTypes.cpp +++ b/src/openrct2/object/ObjectTypes.cpp @@ -14,70 +14,74 @@ #include -constexpr std::array kAllObjectTypes = { - ObjectType::ride, - ObjectType::smallScenery, - ObjectType::largeScenery, - ObjectType::walls, - ObjectType::banners, - ObjectType::paths, - ObjectType::pathAdditions, - ObjectType::sceneryGroup, - ObjectType::parkEntrance, - ObjectType::water, - ObjectType::scenarioMeta, - ObjectType::terrainSurface, - ObjectType::terrainEdge, - ObjectType::station, - ObjectType::music, - ObjectType::footpathSurface, - ObjectType::footpathRailings, - ObjectType::audio, - ObjectType::peepNames, - ObjectType::peepAnimations, - ObjectType::climate, -}; - -static_assert(kAllObjectTypes.size() == EnumValue(ObjectType::count)); - -// Object types that can be saved in a park file. -static constexpr std::array kTransientObjectTypes = { - ObjectType::ride, ObjectType::smallScenery, ObjectType::largeScenery, ObjectType::walls, - ObjectType::banners, ObjectType::paths, ObjectType::pathAdditions, ObjectType::sceneryGroup, - ObjectType::parkEntrance, ObjectType::water, ObjectType::terrainSurface, ObjectType::terrainEdge, - ObjectType::station, ObjectType::music, ObjectType::footpathSurface, ObjectType::footpathRailings, - ObjectType::peepNames, ObjectType::peepAnimations, ObjectType::climate, -}; - -// Object types that cannot be saved in a park file. -static constexpr std::array kIntransientObjectTypes = { - ObjectType::scenarioMeta, - ObjectType::audio, -}; - -static_assert(kNumTransientObjectTypes + kNumIntransientObjectTypes == static_cast(ObjectType::count)); - -bool ObjectTypeIsTransient(ObjectType type) +namespace OpenRCT2 { - return std::find(kTransientObjectTypes.begin(), kTransientObjectTypes.end(), type) != std::end(kTransientObjectTypes); -} + constexpr std::array kAllObjectTypes = { + ObjectType::ride, + ObjectType::smallScenery, + ObjectType::largeScenery, + ObjectType::walls, + ObjectType::banners, + ObjectType::paths, + ObjectType::pathAdditions, + ObjectType::sceneryGroup, + ObjectType::parkEntrance, + ObjectType::water, + ObjectType::scenarioMeta, + ObjectType::terrainSurface, + ObjectType::terrainEdge, + ObjectType::station, + ObjectType::music, + ObjectType::footpathSurface, + ObjectType::footpathRailings, + ObjectType::audio, + ObjectType::peepNames, + ObjectType::peepAnimations, + ObjectType::climate, + }; -bool ObjectTypeIsIntransient(ObjectType type) -{ - return std::find(kIntransientObjectTypes.begin(), kIntransientObjectTypes.end(), type) != std::end(kIntransientObjectTypes); -} + static_assert(kAllObjectTypes.size() == EnumValue(ObjectType::count)); -std::span getAllObjectTypes() -{ - return kAllObjectTypes; -} + // Object types that can be saved in a park file. + static constexpr std::array kTransientObjectTypes = { + ObjectType::ride, ObjectType::smallScenery, ObjectType::largeScenery, ObjectType::walls, + ObjectType::banners, ObjectType::paths, ObjectType::pathAdditions, ObjectType::sceneryGroup, + ObjectType::parkEntrance, ObjectType::water, ObjectType::terrainSurface, ObjectType::terrainEdge, + ObjectType::station, ObjectType::music, ObjectType::footpathSurface, ObjectType::footpathRailings, + ObjectType::peepNames, ObjectType::peepAnimations, ObjectType::climate, + }; -std::span getTransientObjectTypes() -{ - return kTransientObjectTypes; -} + // Object types that cannot be saved in a park file. + static constexpr std::array kIntransientObjectTypes = { + ObjectType::scenarioMeta, + ObjectType::audio, + }; -std::span getIntransientObjectTypes() -{ - return kIntransientObjectTypes; -} + static_assert(kNumTransientObjectTypes + kNumIntransientObjectTypes == static_cast(ObjectType::count)); + + bool ObjectTypeIsTransient(ObjectType type) + { + return std::find(kTransientObjectTypes.begin(), kTransientObjectTypes.end(), type) != std::end(kTransientObjectTypes); + } + + bool ObjectTypeIsIntransient(ObjectType type) + { + return std::find(kIntransientObjectTypes.begin(), kIntransientObjectTypes.end(), type) + != std::end(kIntransientObjectTypes); + } + + std::span getAllObjectTypes() + { + return kAllObjectTypes; + } + + std::span getTransientObjectTypes() + { + return kTransientObjectTypes; + } + + std::span getIntransientObjectTypes() + { + return kIntransientObjectTypes; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ObjectTypes.h b/src/openrct2/object/ObjectTypes.h index a4cb8a8568..89c6b9489a 100644 --- a/src/openrct2/object/ObjectTypes.h +++ b/src/openrct2/object/ObjectTypes.h @@ -15,44 +15,47 @@ #include #include -using ObjectEntryIndex = uint16_t; -constexpr ObjectEntryIndex kObjectEntryIndexNull = std::numeric_limits::max(); - -// First 0xF of RCTObjectEntry->flags -enum class ObjectType : uint8_t +namespace OpenRCT2 { - ride, - smallScenery, - largeScenery, - walls, - banners, - paths, - pathAdditions, - sceneryGroup, - parkEntrance, - water, - scenarioMeta, - terrainSurface, - terrainEdge, - station, - music, - footpathSurface, - footpathRailings, - audio, - peepNames, - peepAnimations, - climate, + using ObjectEntryIndex = uint16_t; + constexpr ObjectEntryIndex kObjectEntryIndexNull = std::numeric_limits::max(); - count, - none = 255 -}; + // First 0xF of RCTObjectEntry->flags + enum class ObjectType : uint8_t + { + ride, + smallScenery, + largeScenery, + walls, + banners, + paths, + pathAdditions, + sceneryGroup, + parkEntrance, + water, + scenarioMeta, + terrainSurface, + terrainEdge, + station, + music, + footpathSurface, + footpathRailings, + audio, + peepNames, + peepAnimations, + climate, -static constexpr size_t kNumTransientObjectTypes = 19; -static constexpr size_t kNumIntransientObjectTypes = 2; + count, + none = 255 + }; -bool ObjectTypeIsTransient(ObjectType type); -bool ObjectTypeIsIntransient(ObjectType type); + static constexpr size_t kNumTransientObjectTypes = 19; + static constexpr size_t kNumIntransientObjectTypes = 2; -std::span getAllObjectTypes(); -std::span getTransientObjectTypes(); -std::span getIntransientObjectTypes(); + bool ObjectTypeIsTransient(ObjectType type); + bool ObjectTypeIsIntransient(ObjectType type); + + std::span getAllObjectTypes(); + std::span getTransientObjectTypes(); + std::span getIntransientObjectTypes(); +} // namespace OpenRCT2 diff --git a/src/openrct2/object/PathAdditionEntry.h b/src/openrct2/object/PathAdditionEntry.h index 9eb5ebfbcc..60fbb78850 100644 --- a/src/openrct2/object/PathAdditionEntry.h +++ b/src/openrct2/object/PathAdditionEntry.h @@ -15,36 +15,39 @@ enum class CursorID : uint8_t; -enum class PathAdditionDrawType : uint8_t +namespace OpenRCT2 { - Light, - Bin, - Bench, - JumpingFountain, -}; + enum class PathAdditionDrawType : uint8_t + { + Light, + Bin, + Bench, + JumpingFountain, + }; -enum -{ - PATH_ADDITION_FLAG_IS_BIN = 1 << 0, - PATH_ADDITION_FLAG_IS_BENCH = 1 << 1, - PATH_ADDITION_FLAG_BREAKABLE = 1 << 2, - PATH_ADDITION_FLAG_LAMP = 1 << 3, - PATH_ADDITION_FLAG_JUMPING_FOUNTAIN_WATER = 1 << 4, - PATH_ADDITION_FLAG_JUMPING_FOUNTAIN_SNOW = 1 << 5, - PATH_ADDITION_FLAG_DONT_ALLOW_ON_QUEUE = 1 << 6, - PATH_ADDITION_FLAG_DONT_ALLOW_ON_SLOPE = 1 << 7, - PATH_ADDITION_FLAG_IS_QUEUE_SCREEN = 1 << 8 -}; + enum + { + PATH_ADDITION_FLAG_IS_BIN = 1 << 0, + PATH_ADDITION_FLAG_IS_BENCH = 1 << 1, + PATH_ADDITION_FLAG_BREAKABLE = 1 << 2, + PATH_ADDITION_FLAG_LAMP = 1 << 3, + PATH_ADDITION_FLAG_JUMPING_FOUNTAIN_WATER = 1 << 4, + PATH_ADDITION_FLAG_JUMPING_FOUNTAIN_SNOW = 1 << 5, + PATH_ADDITION_FLAG_DONT_ALLOW_ON_QUEUE = 1 << 6, + PATH_ADDITION_FLAG_DONT_ALLOW_ON_SLOPE = 1 << 7, + PATH_ADDITION_FLAG_IS_QUEUE_SCREEN = 1 << 8 + }; -struct PathAdditionEntry -{ - static constexpr auto kObjectType = ObjectType::pathAdditions; + struct PathAdditionEntry + { + static constexpr auto kObjectType = ObjectType::pathAdditions; - StringId name; - uint32_t image; - uint16_t flags; - PathAdditionDrawType draw_type; - CursorID tool_id; - money64 price; - ObjectEntryIndex scenery_tab_id; -}; + StringId name; + uint32_t image; + uint16_t flags; + PathAdditionDrawType draw_type; + CursorID tool_id; + money64 price; + ObjectEntryIndex scenery_tab_id; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/PathAdditionObject.cpp b/src/openrct2/object/PathAdditionObject.cpp index 0996d0544c..24f0e6c78d 100644 --- a/src/openrct2/object/PathAdditionObject.cpp +++ b/src/openrct2/object/PathAdditionObject.cpp @@ -20,100 +20,100 @@ #include -using namespace OpenRCT2; - -void PathAdditionObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) +namespace OpenRCT2 { - stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.flags = stream->ReadValue(); - _legacyType.draw_type = static_cast(stream->ReadValue()); - _legacyType.tool_id = static_cast(stream->ReadValue()); - _legacyType.price = stream->ReadValue(); - _legacyType.scenery_tab_id = kObjectEntryIndexNull; - stream->Seek(2, OpenRCT2::STREAM_SEEK_CURRENT); - - GetStringTable().Read(context, stream, ObjectStringID::NAME); - - RCTObjectEntry sgEntry = stream->ReadValue(); - SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); - - GetImageTable().Read(context, stream); - - // Validate properties - if (_legacyType.price <= 0.00_GBP) + void PathAdditionObject::ReadLegacy(IReadObjectContext* context, IStream* stream) { - context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); - } + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.flags = stream->ReadValue(); + _legacyType.draw_type = static_cast(stream->ReadValue()); + _legacyType.tool_id = static_cast(stream->ReadValue()); + _legacyType.price = stream->ReadValue(); + _legacyType.scenery_tab_id = kObjectEntryIndexNull; + stream->Seek(2, STREAM_SEEK_CURRENT); - // Add path additions to 'Signs and items for footpaths' group, rather than lumping them in the Miscellaneous tab. - // Since this is already done the other way round for original items, avoid adding those to prevent duplicates. + GetStringTable().Read(context, stream, ObjectStringID::NAME); - auto& objectRepository = context->GetObjectRepository(); - auto item = objectRepository.FindObject(GetDescriptor()); - if (item != nullptr) - { - auto sourceGame = item->GetFirstSourceGame(); - if (sourceGame == ObjectSourceGame::WackyWorlds || sourceGame == ObjectSourceGame::TimeTwister - || sourceGame == ObjectSourceGame::Custom) + RCTObjectEntry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); + + GetImageTable().Read(context, stream); + + // Validate properties + if (_legacyType.price <= 0.00_GBP) { - auto scgPathX = Object::GetScgPathXHeader(); - SetPrimarySceneryGroup(scgPathX); + context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); + } + + // Add path additions to 'Signs and items for footpaths' group, rather than lumping them in the Miscellaneous tab. + // Since this is already done the other way round for original items, avoid adding those to prevent duplicates. + + auto& objectRepository = context->GetObjectRepository(); + auto item = objectRepository.FindObject(GetDescriptor()); + if (item != nullptr) + { + auto sourceGame = item->GetFirstSourceGame(); + if (sourceGame == ObjectSourceGame::WackyWorlds || sourceGame == ObjectSourceGame::TimeTwister + || sourceGame == ObjectSourceGame::Custom) + { + auto scgPathX = Object::GetScgPathXHeader(); + SetPrimarySceneryGroup(scgPathX); + } } } -} -void PathAdditionObject::Load() -{ - GetStringTable().Sort(); - _legacyType.name = LanguageAllocateObjectString(GetName()); - _legacyType.image = LoadImages(); - - _legacyType.scenery_tab_id = kObjectEntryIndexNull; -} - -void PathAdditionObject::Unload() -{ - LanguageFreeObjectString(_legacyType.name); - UnloadImages(); - - _legacyType.name = 0; - _legacyType.image = 0; -} - -void PathAdditionObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; - GfxDrawSprite(rt, ImageId(_legacyType.image), screenCoords - ScreenCoordsXY{ 22, 24 }); -} - -static PathAdditionDrawType ParseDrawType(const std::string& s) -{ - if (s == "lamp") - return PathAdditionDrawType::Light; - if (s == "bin") - return PathAdditionDrawType::Bin; - if (s == "bench") - return PathAdditionDrawType::Bench; - if (s == "fountain") - return PathAdditionDrawType::JumpingFountain; - return PathAdditionDrawType::Light; -} - -void PathAdditionObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "PathAdditionObject::ReadJson expects parameter root to be object"); - - json_t properties = root["properties"]; - - if (properties.is_object()) + void PathAdditionObject::Load() { - _legacyType.draw_type = ParseDrawType(Json::GetString(properties["renderAs"])); - _legacyType.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::LamppostDown); - _legacyType.price = Json::GetNumber(properties["price"]); + GetStringTable().Sort(); + _legacyType.name = LanguageAllocateObjectString(GetName()); + _legacyType.image = LoadImages(); - SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + _legacyType.scenery_tab_id = kObjectEntryIndexNull; + } - // clang-format off + void PathAdditionObject::Unload() + { + LanguageFreeObjectString(_legacyType.name); + UnloadImages(); + + _legacyType.name = 0; + _legacyType.image = 0; + } + + void PathAdditionObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; + GfxDrawSprite(rt, ImageId(_legacyType.image), screenCoords - ScreenCoordsXY{ 22, 24 }); + } + + static PathAdditionDrawType ParseDrawType(const std::string& s) + { + if (s == "lamp") + return PathAdditionDrawType::Light; + if (s == "bin") + return PathAdditionDrawType::Bin; + if (s == "bench") + return PathAdditionDrawType::Bench; + if (s == "fountain") + return PathAdditionDrawType::JumpingFountain; + return PathAdditionDrawType::Light; + } + + void PathAdditionObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "PathAdditionObject::ReadJson expects parameter root to be object"); + + json_t properties = root["properties"]; + + if (properties.is_object()) + { + _legacyType.draw_type = ParseDrawType(Json::GetString(properties["renderAs"])); + _legacyType.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::LamppostDown); + _legacyType.price = Json::GetNumber(properties["price"]); + + SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + + // clang-format off _legacyType.flags = Json::GetFlags( properties, { @@ -127,8 +127,9 @@ void PathAdditionObject::ReadJson(IReadObjectContext* context, json_t& root) { "isAllowedOnSlope", PATH_ADDITION_FLAG_DONT_ALLOW_ON_SLOPE, Json::FlagType::Inverted }, { "isTelevision", PATH_ADDITION_FLAG_IS_QUEUE_SCREEN, Json::FlagType::Normal }, }); - // clang-format on - } + // clang-format on + } - PopulateTablesFromJson(context, root); -} + PopulateTablesFromJson(context, root); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/PathAdditionObject.h b/src/openrct2/object/PathAdditionObject.h index 3a5c7b9104..f2d57737b6 100644 --- a/src/openrct2/object/PathAdditionObject.h +++ b/src/openrct2/object/PathAdditionObject.h @@ -12,23 +12,26 @@ #include "PathAdditionEntry.h" #include "SceneryObject.h" -class PathAdditionObject final : public SceneryObject +namespace OpenRCT2 { -private: - PathAdditionEntry _legacyType = {}; - -public: - static constexpr ObjectType kObjectType = ObjectType::pathAdditions; - - void* GetLegacyData() override + class PathAdditionObject final : public SceneryObject { - return &_legacyType; - } + private: + PathAdditionEntry _legacyType = {}; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + public: + static constexpr ObjectType kObjectType = ObjectType::pathAdditions; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; -}; + void* GetLegacyData() override + { + return &_legacyType; + } + + void ReadLegacy(IReadObjectContext* context, IStream* stream) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/PeepAnimationsObject.cpp b/src/openrct2/object/PeepAnimationsObject.cpp index 9be8a5380d..21d961aa70 100644 --- a/src/openrct2/object/PeepAnimationsObject.cpp +++ b/src/openrct2/object/PeepAnimationsObject.cpp @@ -18,205 +18,209 @@ #include "../rct12/RCT12.h" #include "ObjectRepository.h" -using namespace OpenRCT2; - -static const EnumMap animationPeepTypeMap( - { - { "guest", AnimationPeepType::Guest }, - { "handyman", AnimationPeepType::Handyman }, - { "mechanic", AnimationPeepType::Mechanic }, - { "security", AnimationPeepType::Security }, - { "entertainer", AnimationPeepType::Entertainer }, - }); - -void PeepAnimationsObject::Load() +namespace OpenRCT2 { - auto numImages = GetImageTable().GetCount(); - if (numImages == 0) - return; - - _imageOffsetId = LoadImages(); - - // Set loaded image offsets for all animations - for (auto groupKey = 0u; groupKey < _animationGroups.size(); groupKey++) - { - auto& group = _animationGroups[groupKey]; - auto& requiredAnimationMap = getAnimationsByPeepType(_peepType); - for (auto& [typeStr, typeEnum] : requiredAnimationMap) + static const EnumMap animationPeepTypeMap( { - group[typeEnum].base_image = _imageOffsetId + group[typeEnum].imageTableOffset; - group[typeEnum].bounds = inferMaxAnimationDimensions(group[typeEnum]); + { "guest", AnimationPeepType::Guest }, + { "handyman", AnimationPeepType::Handyman }, + { "mechanic", AnimationPeepType::Mechanic }, + { "security", AnimationPeepType::Security }, + { "entertainer", AnimationPeepType::Entertainer }, + }); - // Balloons, hats and umbrellas are painted separately, so the inference - // algorithm doesn't account for those. We manually compensate for these here. - // Between 8-12 pixels are needed, depending on rotation, so we're generalising. - auto pag = PeepAnimationGroup(groupKey); - if (pag == PeepAnimationGroup::Balloon || pag == PeepAnimationGroup::Hat || pag == PeepAnimationGroup::Umbrella) + void PeepAnimationsObject::Load() + { + auto numImages = GetImageTable().GetCount(); + if (numImages == 0) + return; + + _imageOffsetId = LoadImages(); + + // Set loaded image offsets for all animations + for (auto groupKey = 0u; groupKey < _animationGroups.size(); groupKey++) + { + auto& group = _animationGroups[groupKey]; + auto& requiredAnimationMap = getAnimationsByPeepType(_peepType); + for (auto& [typeStr, typeEnum] : requiredAnimationMap) { - group[typeEnum].bounds.sprite_height_negative += 12; - } - } - } -} + group[typeEnum].base_image = _imageOffsetId + group[typeEnum].imageTableOffset; + group[typeEnum].bounds = inferMaxAnimationDimensions(group[typeEnum]); -void PeepAnimationsObject::Unload() -{ - UnloadImages(); -} - -void PeepAnimationsObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "PeepAnimationsObject::ReadJson expects parameter root to be an object"); - PopulateTablesFromJson(context, root); - - Guard::Assert(root["properties"].is_object(), "PeepAnimationsObject::ReadJson expects properties to be an object"); - ReadProperties(root["properties"]); - - auto& requiredAnimationMap = getAnimationsByPeepType(_peepType); - _animationGroups.clear(); - - Guard::Assert(root["animationGroups"].is_array(), "PeepAnimationsObject::ReadJson expects animationGroups to be an array"); - for (auto& groupJson : root["animationGroups"]) - { - Guard::Assert(groupJson["animations"].is_object(), "PeepAnimationsObject::ReadJson expects animations to be an array"); - - PeepAnimations group = ReadAnimations(requiredAnimationMap, groupJson["animations"]); - - if (groupJson.contains("legacyPosition")) - { - auto position = Json::GetNumber(groupJson["legacyPosition"]); - if (position <= EnumValue(RCT12PeepAnimationGroup::Count)) - { - group.legacyPosition = static_cast(position); - } - } - else - { - group.legacyPosition = RCT12PeepAnimationGroup::Invalid; - } - - group.isSlowWalking = Json::GetBoolean(groupJson["isSlowWalking"], false); - group.scriptName = Json::GetString(groupJson["scriptName"], std::string(GetIdentifier())); - - _animationGroups.push_back(group); - } -} - -PeepAnimations PeepAnimationsObject::ReadAnimations(const EnumMap& requiredAnimationMap, json_t& animations) -{ - PeepAnimations group{}; - for (auto& [typeStr, typeEnum] : requiredAnimationMap) - { - if (!animations.contains(typeStr)) - { - // Successive animation groups can copy the basic animations from the primary group - if (!_animationGroups.empty()) - { - auto& referenceAnim = _animationGroups[0][typeEnum]; - if (referenceAnim.imageTableOffset != 0) + // Balloons, hats and umbrellas are painted separately, so the inference + // algorithm doesn't account for those. We manually compensate for these here. + // Between 8-12 pixels are needed, depending on rotation, so we're generalising. + auto pag = PeepAnimationGroup(groupKey); + if (pag == PeepAnimationGroup::Balloon || pag == PeepAnimationGroup::Hat || pag == PeepAnimationGroup::Umbrella) { - LOG_VERBOSE("Copying animation '%s' from primary group", std::string(typeStr).c_str()); - std::vector sequence = referenceAnim.frame_offsets; - group[typeEnum] = { - .imageTableOffset = referenceAnim.imageTableOffset, - .frame_offsets = sequence, - }; - continue; + group[typeEnum].bounds.sprite_height_negative += 12; } } - - // No primary animation bail us out -- error here! - LOG_ERROR("Required animation does not exist: %s", std::string(typeStr).c_str()); - continue; } - - // The `.data()` here is a workaround for older versions of nlohmann-json. - // TODO: remove when we no longer support Ubuntu 22.04 (Jammy). - auto& animJson = animations[typeStr.data()]; - - // Store animation sequence in vector - auto sequence = animJson["sequence"].get>(); - - group[typeEnum] = { - .imageTableOffset = Json::GetNumber(animJson["offset"]), - .frame_offsets = sequence, - }; } - return group; -} + void PeepAnimationsObject::Unload() + { + UnloadImages(); + } -void PeepAnimationsObject::ReadProperties(json_t& props) -{ - Guard::Assert(props["peepType"].is_string(), "PeepAnimationsObject::ReadProperties expects peepType to be a string"); - _peepType = animationPeepTypeMap[Json::GetString(props["peepType"])]; + void PeepAnimationsObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "PeepAnimationsObject::ReadJson expects parameter root to be an object"); + PopulateTablesFromJson(context, root); - Guard::Assert( - props["noRandomPlacement"].is_boolean(), - "PeepAnimationsObject::ReadProperties expects noRandomPlacement to be a boolean"); - _noRandomPlacement = Json::GetBoolean(props["noRandomPlacement"], false); -} + Guard::Assert(root["properties"].is_object(), "PeepAnimationsObject::ReadJson expects properties to be an object"); + ReadProperties(root["properties"]); -std::string PeepAnimationsObject::GetCostumeName() const -{ - return GetStringTable().GetString(ObjectStringID::NAME); -} + auto& requiredAnimationMap = getAnimationsByPeepType(_peepType); + _animationGroups.clear(); -ImageIndex PeepAnimationsObject::GetInlineImageId() const -{ - return _imageOffsetId; -} + Guard::Assert( + root["animationGroups"].is_array(), "PeepAnimationsObject::ReadJson expects animationGroups to be an array"); + for (auto& groupJson : root["animationGroups"]) + { + Guard::Assert( + groupJson["animations"].is_object(), "PeepAnimationsObject::ReadJson expects animations to be an array"); -AnimationPeepType PeepAnimationsObject::GetPeepType() const -{ - return _peepType; -} + PeepAnimations group = ReadAnimations(requiredAnimationMap, groupJson["animations"]); -const PeepAnimation& PeepAnimationsObject::GetPeepAnimation(PeepAnimationGroup animGroup, PeepAnimationType animType) const -{ - return _animationGroups[EnumValue(animGroup)][animType]; -} + if (groupJson.contains("legacyPosition")) + { + auto position = Json::GetNumber(groupJson["legacyPosition"]); + if (position <= EnumValue(RCT12PeepAnimationGroup::Count)) + { + group.legacyPosition = static_cast(position); + } + } + else + { + group.legacyPosition = RCT12PeepAnimationGroup::Invalid; + } -const SpriteBounds& PeepAnimationsObject::GetSpriteBounds(PeepAnimationGroup animGroup, PeepAnimationType animType) const -{ - return _animationGroups[EnumValue(animGroup)][animType].bounds; -} + group.isSlowWalking = Json::GetBoolean(groupJson["isSlowWalking"], false); + group.scriptName = Json::GetString(groupJson["scriptName"], std::string(GetIdentifier())); -size_t PeepAnimationsObject::GetNumAnimationGroups() const -{ - return _animationGroups.size(); -} + _animationGroups.push_back(group); + } + } -RCT12PeepAnimationGroup PeepAnimationsObject::GetLegacyPosition(PeepAnimationGroup animGroup) const -{ - return _animationGroups[EnumValue(animGroup)].legacyPosition; -} + PeepAnimations PeepAnimationsObject::ReadAnimations( + const EnumMap& requiredAnimationMap, json_t& animations) + { + PeepAnimations group{}; + for (auto& [typeStr, typeEnum] : requiredAnimationMap) + { + if (!animations.contains(typeStr)) + { + // Successive animation groups can copy the basic animations from the primary group + if (!_animationGroups.empty()) + { + auto& referenceAnim = _animationGroups[0][typeEnum]; + if (referenceAnim.imageTableOffset != 0) + { + LOG_VERBOSE("Copying animation '%s' from primary group", std::string(typeStr).c_str()); + std::vector sequence = referenceAnim.frame_offsets; + group[typeEnum] = { + .imageTableOffset = referenceAnim.imageTableOffset, + .frame_offsets = sequence, + }; + continue; + } + } -std::string_view PeepAnimationsObject::GetScriptName(PeepAnimationGroup animGroup) const -{ - return _animationGroups[EnumValue(animGroup)].scriptName; -} + // No primary animation bail us out -- error here! + LOG_ERROR("Required animation does not exist: %s", std::string(typeStr).c_str()); + continue; + } -bool PeepAnimationsObject::IsSlowWalking(PeepAnimationGroup animGroup) const -{ - return _animationGroups[EnumValue(animGroup)].isSlowWalking; -} + // The `.data()` here is a workaround for older versions of nlohmann-json. + // TODO: remove when we no longer support Ubuntu 22.04 (Jammy). + auto& animJson = animations[typeStr.data()]; -void PeepAnimationsObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto centre = ScreenCoordsXY{ width / 2, height / 2 }; + // Store animation sequence in vector + auto sequence = animJson["sequence"].get>(); - // Draw inline sprite in the centre - GfxDrawSprite(rt, ImageId(_imageOffsetId + 0), centre + ScreenCoordsXY{ -8, -8 }); + group[typeEnum] = { + .imageTableOffset = Json::GetNumber(animJson["offset"]), + .frame_offsets = sequence, + }; + } - // Draw four cardinal directions around the inline sprite - GfxDrawSprite(rt, ImageId(_imageOffsetId + 4, COLOUR_BRIGHT_RED, COLOUR_TEAL), centre + ScreenCoordsXY{ -32, -24 }); - GfxDrawSprite(rt, ImageId(_imageOffsetId + 2, COLOUR_BRIGHT_RED, COLOUR_TEAL), centre + ScreenCoordsXY{ +32, +32 }); - GfxDrawSprite(rt, ImageId(_imageOffsetId + 1, COLOUR_BRIGHT_RED, COLOUR_TEAL), centre + ScreenCoordsXY{ +32, -24 }); - GfxDrawSprite(rt, ImageId(_imageOffsetId + 3, COLOUR_BRIGHT_RED, COLOUR_TEAL), centre + ScreenCoordsXY{ -32, +32 }); -} + return group; + } -void PeepAnimationsObject::SetRepositoryItem(ObjectRepositoryItem* item) const -{ - item->PeepAnimationsInfo.PeepType = EnumValue(_peepType); -} + void PeepAnimationsObject::ReadProperties(json_t& props) + { + Guard::Assert(props["peepType"].is_string(), "PeepAnimationsObject::ReadProperties expects peepType to be a string"); + _peepType = animationPeepTypeMap[Json::GetString(props["peepType"])]; + + Guard::Assert( + props["noRandomPlacement"].is_boolean(), + "PeepAnimationsObject::ReadProperties expects noRandomPlacement to be a boolean"); + _noRandomPlacement = Json::GetBoolean(props["noRandomPlacement"], false); + } + + std::string PeepAnimationsObject::GetCostumeName() const + { + return GetStringTable().GetString(ObjectStringID::NAME); + } + + ImageIndex PeepAnimationsObject::GetInlineImageId() const + { + return _imageOffsetId; + } + + AnimationPeepType PeepAnimationsObject::GetPeepType() const + { + return _peepType; + } + + const PeepAnimation& PeepAnimationsObject::GetPeepAnimation(PeepAnimationGroup animGroup, PeepAnimationType animType) const + { + return _animationGroups[EnumValue(animGroup)][animType]; + } + + const SpriteBounds& PeepAnimationsObject::GetSpriteBounds(PeepAnimationGroup animGroup, PeepAnimationType animType) const + { + return _animationGroups[EnumValue(animGroup)][animType].bounds; + } + + size_t PeepAnimationsObject::GetNumAnimationGroups() const + { + return _animationGroups.size(); + } + + RCT12PeepAnimationGroup PeepAnimationsObject::GetLegacyPosition(PeepAnimationGroup animGroup) const + { + return _animationGroups[EnumValue(animGroup)].legacyPosition; + } + + std::string_view PeepAnimationsObject::GetScriptName(PeepAnimationGroup animGroup) const + { + return _animationGroups[EnumValue(animGroup)].scriptName; + } + + bool PeepAnimationsObject::IsSlowWalking(PeepAnimationGroup animGroup) const + { + return _animationGroups[EnumValue(animGroup)].isSlowWalking; + } + + void PeepAnimationsObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto centre = ScreenCoordsXY{ width / 2, height / 2 }; + + // Draw inline sprite in the centre + GfxDrawSprite(rt, ImageId(_imageOffsetId + 0), centre + ScreenCoordsXY{ -8, -8 }); + + // Draw four cardinal directions around the inline sprite + GfxDrawSprite(rt, ImageId(_imageOffsetId + 4, COLOUR_BRIGHT_RED, COLOUR_TEAL), centre + ScreenCoordsXY{ -32, -24 }); + GfxDrawSprite(rt, ImageId(_imageOffsetId + 2, COLOUR_BRIGHT_RED, COLOUR_TEAL), centre + ScreenCoordsXY{ +32, +32 }); + GfxDrawSprite(rt, ImageId(_imageOffsetId + 1, COLOUR_BRIGHT_RED, COLOUR_TEAL), centre + ScreenCoordsXY{ +32, -24 }); + GfxDrawSprite(rt, ImageId(_imageOffsetId + 3, COLOUR_BRIGHT_RED, COLOUR_TEAL), centre + ScreenCoordsXY{ -32, +32 }); + } + + void PeepAnimationsObject::SetRepositoryItem(ObjectRepositoryItem* item) const + { + item->PeepAnimationsInfo.PeepType = EnumValue(_peepType); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/PeepAnimationsObject.h b/src/openrct2/object/PeepAnimationsObject.h index 457302270f..62124b2704 100644 --- a/src/openrct2/object/PeepAnimationsObject.h +++ b/src/openrct2/object/PeepAnimationsObject.h @@ -18,43 +18,46 @@ enum class RCT12PeepAnimationGroup : uint8_t; -class PeepAnimationsObject final : public Object +namespace OpenRCT2 { -private: - ImageIndex _imageOffsetId; - std::vector _animationGroups; - OpenRCT2::AnimationPeepType _peepType; - bool _noRandomPlacement; - -public: - static constexpr ObjectType kObjectType = ObjectType::peepAnimations; - - void ReadJson(IReadObjectContext* context, json_t& root) override; - OpenRCT2::PeepAnimations ReadAnimations(const EnumMap& requiredAnimationMap, json_t& animations); - void ReadProperties(json_t& properties); - void Load() override; - void Unload() override; - - std::string GetCostumeName() const; - ImageIndex GetInlineImageId() const; - - const OpenRCT2::PeepAnimation& GetPeepAnimation( - PeepAnimationGroup animGroup, PeepAnimationType animType = PeepAnimationType::Walking) const; - const OpenRCT2::SpriteBounds& GetSpriteBounds( - PeepAnimationGroup spriteType, PeepAnimationType actionAnimationGroup = PeepAnimationType::Walking) const; - - OpenRCT2::AnimationPeepType GetPeepType() const; - size_t GetNumAnimationGroups() const; - RCT12PeepAnimationGroup GetLegacyPosition(PeepAnimationGroup animGroup) const; - std::string_view GetScriptName(PeepAnimationGroup animGroup) const; - - bool IsSlowWalking(PeepAnimationGroup animGroup) const; - - bool ShouldExcludeFromRandomPlacement() const + class PeepAnimationsObject final : public Object { - return _noRandomPlacement; - } + private: + ImageIndex _imageOffsetId; + std::vector _animationGroups; + AnimationPeepType _peepType; + bool _noRandomPlacement; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; - void SetRepositoryItem(ObjectRepositoryItem* item) const override; -}; + public: + static constexpr ObjectType kObjectType = ObjectType::peepAnimations; + + void ReadJson(IReadObjectContext* context, json_t& root) override; + PeepAnimations ReadAnimations(const EnumMap& requiredAnimationMap, json_t& animations); + void ReadProperties(json_t& properties); + void Load() override; + void Unload() override; + + std::string GetCostumeName() const; + ImageIndex GetInlineImageId() const; + + const PeepAnimation& GetPeepAnimation( + PeepAnimationGroup animGroup, PeepAnimationType animType = PeepAnimationType::Walking) const; + const SpriteBounds& GetSpriteBounds( + PeepAnimationGroup spriteType, PeepAnimationType actionAnimationGroup = PeepAnimationType::Walking) const; + + AnimationPeepType GetPeepType() const; + size_t GetNumAnimationGroups() const; + RCT12PeepAnimationGroup GetLegacyPosition(PeepAnimationGroup animGroup) const; + std::string_view GetScriptName(PeepAnimationGroup animGroup) const; + + bool IsSlowWalking(PeepAnimationGroup animGroup) const; + + bool ShouldExcludeFromRandomPlacement() const + { + return _noRandomPlacement; + } + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void SetRepositoryItem(ObjectRepositoryItem* item) const override; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/PeepNamesObject.cpp b/src/openrct2/object/PeepNamesObject.cpp index 99668b05cd..41b367a151 100644 --- a/src/openrct2/object/PeepNamesObject.cpp +++ b/src/openrct2/object/PeepNamesObject.cpp @@ -14,36 +14,37 @@ #include "../core/Guard.hpp" #include "../core/Json.hpp" -using namespace OpenRCT2; - -void PeepNamesObject::Load() +namespace OpenRCT2 { -} + void PeepNamesObject::Load() + { + } -void PeepNamesObject::Unload() -{ -} + void PeepNamesObject::Unload() + { + } -void PeepNamesObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "PeepNamesObject::ReadJson expects parameter root to be an object"); - PopulateTablesFromJson(context, root); + void PeepNamesObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "PeepNamesObject::ReadJson expects parameter root to be an object"); + PopulateTablesFromJson(context, root); - Guard::Assert(root["given_names"].is_array(), "PeepNamesObject::ReadJson expects given_names to be an array"); - _givenNames = root["given_names"].get>(); - std::sort(_givenNames.begin(), _givenNames.end()); + Guard::Assert(root["given_names"].is_array(), "PeepNamesObject::ReadJson expects given_names to be an array"); + _givenNames = root["given_names"].get>(); + std::sort(_givenNames.begin(), _givenNames.end()); - Guard::Assert(root["surnames"].is_array(), "PeepNamesObject::ReadJson expects surnames to be an array"); - _surnames = root["surnames"].get>(); - std::sort(_surnames.begin(), _surnames.end()); -} + Guard::Assert(root["surnames"].is_array(), "PeepNamesObject::ReadJson expects surnames to be an array"); + _surnames = root["surnames"].get>(); + std::sort(_surnames.begin(), _surnames.end()); + } -std::string PeepNamesObject::GetGivenNameAt(size_t index) const -{ - return _givenNames[index % _givenNames.size()]; -} + std::string PeepNamesObject::GetGivenNameAt(size_t index) const + { + return _givenNames[index % _givenNames.size()]; + } -std::string PeepNamesObject::GetSurnameAt(size_t index) const -{ - return _surnames[index % _surnames.size()]; -} + std::string PeepNamesObject::GetSurnameAt(size_t index) const + { + return _surnames[index % _surnames.size()]; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/PeepNamesObject.h b/src/openrct2/object/PeepNamesObject.h index 3709cc1430..d2e2bd4e2b 100644 --- a/src/openrct2/object/PeepNamesObject.h +++ b/src/openrct2/object/PeepNamesObject.h @@ -15,19 +15,22 @@ #include #include -class PeepNamesObject final : public Object +namespace OpenRCT2 { -private: - std::vector _givenNames; - std::vector _surnames; + class PeepNamesObject final : public Object + { + private: + std::vector _givenNames; + std::vector _surnames; -public: - static constexpr ObjectType kObjectType = ObjectType::peepNames; + public: + static constexpr ObjectType kObjectType = ObjectType::peepNames; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; - std::string GetGivenNameAt(size_t index) const; - std::string GetSurnameAt(size_t index) const; -}; + std::string GetGivenNameAt(size_t index) const; + std::string GetSurnameAt(size_t index) const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ResourceTable.cpp b/src/openrct2/object/ResourceTable.cpp index f667811c86..3e09dc7d3c 100644 --- a/src/openrct2/object/ResourceTable.cpp +++ b/src/openrct2/object/ResourceTable.cpp @@ -14,99 +14,100 @@ #include "../core/Path.hpp" #include "../core/String.hpp" -using namespace OpenRCT2; - -Range ResourceTable::ParseRange(std::string_view s) +namespace OpenRCT2 { - // Currently only supports [###] or [###..###] - Range result = {}; - if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') + Range ResourceTable::ParseRange(std::string_view s) { - s = s.substr(1, s.length() - 2); - auto parts = String::split(s, ".."); - if (parts.size() == 1) + // Currently only supports [###] or [###..###] + Range result = {}; + if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') { - result = Range(std::stoi(parts[0])); - } - else - { - auto left = std::stoi(parts[0]); - auto right = std::stoi(parts[1]); - if (left <= right) + s = s.substr(1, s.length() - 2); + auto parts = String::split(s, ".."); + if (parts.size() == 1) { - result = Range(left, right); + result = Range(std::stoi(parts[0])); } else { - result = Range(right, left); + auto left = std::stoi(parts[0]); + auto right = std::stoi(parts[1]); + if (left <= right) + { + result = Range(left, right); + } + else + { + result = Range(right, left); + } } } - } - return result; -} - -ResourceTable::SourceInfo ResourceTable::ParseSource(std::string_view source) -{ - SourceInfo info; - auto base = source; - auto rangeStart = source.find('['); - if (rangeStart != std::string::npos) - { - base = source.substr(0, rangeStart); - info.SourceRange = ParseRange(source.substr(rangeStart)); + return result; } - auto fileName = base; - auto fileNameStart = base.find('/'); - if (fileNameStart != std::string::npos) + ResourceTable::SourceInfo ResourceTable::ParseSource(std::string_view source) { - fileName = base.substr(fileNameStart + 1); - } - else - { - fileNameStart = base.find(':'); + SourceInfo info; + auto base = source; + auto rangeStart = source.find('['); + if (rangeStart != std::string::npos) + { + base = source.substr(0, rangeStart); + info.SourceRange = ParseRange(source.substr(rangeStart)); + } + + auto fileName = base; + auto fileNameStart = base.find('/'); if (fileNameStart != std::string::npos) { fileName = base.substr(fileNameStart + 1); } - } + else + { + fileNameStart = base.find(':'); + if (fileNameStart != std::string::npos) + { + fileName = base.substr(fileNameStart + 1); + } + } - auto& env = GetContext()->GetPlatformEnvironment(); - if (String::startsWith(base, "$LGX:")) - { - info.Kind = SourceKind::Gx; - info.Path = fileName; + auto& env = GetContext()->GetPlatformEnvironment(); + if (String::startsWith(base, "$LGX:")) + { + info.Kind = SourceKind::Gx; + info.Path = fileName; + } + else if (String::startsWith(base, "$G1")) + { + auto dataPath = env.GetDirectoryPath(DirBase::rct2, DirId::data); + info.Kind = SourceKind::G1; + // info.Path = env->FindFile(DirBase::rct2, DirId::data, "g1.dat"); + } + else if (String::startsWith(base, "$CSG")) + { + auto dataPath = env.GetDirectoryPath(DirBase::rct2, DirId::data); + info.Kind = SourceKind::Csg; + // info.Path = env->FindFile(DirBase::rct2, DirId::data, "g1.dat"); + } + else if (String::startsWith(base, "$RCT1:DATA/")) + { + info.Kind = SourceKind::Data; + info.Path = env.FindFile(DirBase::rct1, DirId::data, fileName); + } + else if (String::startsWith(base, "$RCT2:DATA/")) + { + info.Kind = SourceKind::Data; + info.Path = env.FindFile(DirBase::rct2, DirId::data, fileName); + } + else if (String::startsWith(base, "$RCT2:OBJDATA/")) + { + info.Kind = SourceKind::ObjData; + info.Path = env.FindFile(DirBase::rct2, DirId::objects, fileName); + } + else if (!String::startsWith(base, "$")) + { + info.Path = base; + } + return info; } - else if (String::startsWith(base, "$G1")) - { - auto dataPath = env.GetDirectoryPath(DirBase::rct2, DirId::data); - info.Kind = SourceKind::G1; - // info.Path = env->FindFile(DirBase::rct2, DirId::data, "g1.dat"); - } - else if (String::startsWith(base, "$CSG")) - { - auto dataPath = env.GetDirectoryPath(DirBase::rct2, DirId::data); - info.Kind = SourceKind::Csg; - // info.Path = env->FindFile(DirBase::rct2, DirId::data, "g1.dat"); - } - else if (String::startsWith(base, "$RCT1:DATA/")) - { - info.Kind = SourceKind::Data; - info.Path = env.FindFile(DirBase::rct1, DirId::data, fileName); - } - else if (String::startsWith(base, "$RCT2:DATA/")) - { - info.Kind = SourceKind::Data; - info.Path = env.FindFile(DirBase::rct2, DirId::data, fileName); - } - else if (String::startsWith(base, "$RCT2:OBJDATA/")) - { - info.Kind = SourceKind::ObjData; - info.Path = env.FindFile(DirBase::rct2, DirId::objects, fileName); - } - else if (!String::startsWith(base, "$")) - { - info.Path = base; - } - return info; -} +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ResourceTable.h b/src/openrct2/object/ResourceTable.h index 96d5989228..c8c5b97421 100644 --- a/src/openrct2/object/ResourceTable.h +++ b/src/openrct2/object/ResourceTable.h @@ -16,27 +16,30 @@ #include #include -class ResourceTable +namespace OpenRCT2 { -protected: - enum class SourceKind + class ResourceTable { - None, - Data, - ObjData, - Gx, - G1, - Csg, - Png, - }; + protected: + enum class SourceKind + { + None, + Data, + ObjData, + Gx, + G1, + Csg, + Png, + }; - struct SourceInfo - { - SourceKind Kind{}; - std::string Path; - std::optional> SourceRange; - }; + struct SourceInfo + { + SourceKind Kind{}; + std::string Path; + std::optional> SourceRange; + }; - static Range ParseRange(std::string_view s); - static SourceInfo ParseSource(std::string_view source); -}; + static Range ParseRange(std::string_view s); + static SourceInfo ParseSource(std::string_view source); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/RideObject.cpp b/src/openrct2/object/RideObject.cpp index 5d3ac6fe86..2bf4a49c51 100644 --- a/src/openrct2/object/RideObject.cpp +++ b/src/openrct2/object/RideObject.cpp @@ -35,1091 +35,1098 @@ #include #include -using namespace OpenRCT2; -using namespace OpenRCT2::Entity::Yaw; -using namespace OpenRCT2::Numerics; - -/* - * The number of sprites in the sprite group is the specified precision multiplied by this number. General rule is any slope or - * bank has its mirror included in the group: - * - flat unbanked is 1 - * - flat banked is 2 (left/right) - * - sloped unbanked is 2 (up/down) - * - sloped & banked is 4 (left/right * up/down) - * Exceptions: - * - slopesLoop is 10 (5 slope angles * up/down) - * - inlineTwists is 6 (3 bank angles * left/right) - * - slopes25InlineTwists is 12 (3 bank angles * left/right * up/down) - * - corkscrews is 20 (10 sprites for an entire corkscrew * left/right) - * - restraints is 3 - * - curvedLiftHillUp and curvedLiftHillDown are 1 (normally would be combined, but aren't due to RCT2) - */ -static const uint8_t SpriteGroupMultiplier[EnumValue(SpriteGroupType::Count)] = { - 1, 2, 2, 2, 2, 2, 2, 10, 1, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 4, 4, 4, 4, 4, 4, 4, 4, 20, 3, 1, 1, -}; -static_assert(std::size(SpriteGroupMultiplier) == EnumValue(SpriteGroupType::Count)); - -constexpr uint8_t DefaultSteamSpawnPosition[] = { 11, 22 }; - -static const EnumMap AnimationNameLookup{ - { "none", CarEntryAnimation::None }, - { "simpleVehicle", CarEntryAnimation::SimpleVehicle }, - { "steamLocomotive", CarEntryAnimation::SteamLocomotive }, - { "swanBoat", CarEntryAnimation::SwanBoat }, - { "monorailCycle", CarEntryAnimation::MonorailCycle }, - { "MultiDimension", CarEntryAnimation::MultiDimension }, - { "observationTower", CarEntryAnimation::ObservationTower }, - { "animalFlying", CarEntryAnimation::AnimalFlying }, -}; - -constexpr auto NumLegacyAnimationTypes = 11; - -struct LegacyAnimationParameters +namespace OpenRCT2 { - uint16_t Speed; - uint8_t NumFrames; - CarEntryAnimation Alias; -}; + using namespace OpenRCT2::Entity::Yaw; + using namespace OpenRCT2::Numerics; -constexpr LegacyAnimationParameters VehicleEntryDefaultAnimation[] = { - { 0, 1, CarEntryAnimation::None }, // None - { 1 << 12, 4, CarEntryAnimation::SteamLocomotive }, // Miniature Railway Locomotive - { 1 << 10, 2, CarEntryAnimation::SwanBoat }, // Swan Boat - { 1 << 11, 6, CarEntryAnimation::SimpleVehicle }, // Canoe - { 1 << 11, 7, CarEntryAnimation::SimpleVehicle }, // Rowboat - { 1 << 10, 2, CarEntryAnimation::SimpleVehicle }, // Water Tricycle - { 0x3333, 8, CarEntryAnimation::ObservationTower }, // Observation Tower - { 1 << 10, 4, CarEntryAnimation::SimpleVehicle }, // Mini Helicopter - { 1 << 11, 4, CarEntryAnimation::MonorailCycle }, // Monorail Cycle - { 0x3333, 8, CarEntryAnimation::MultiDimension }, // Multi Dimension Coaster - { 24, 4, CarEntryAnimation::AnimalFlying }, // Animal Flying -}; -static_assert(std::size(VehicleEntryDefaultAnimation) == NumLegacyAnimationTypes); + /* + * The number of sprites in the sprite group is the specified precision multiplied by this number. General rule is any slope + * or bank has its mirror included in the group: + * - flat unbanked is 1 + * - flat banked is 2 (left/right) + * - sloped unbanked is 2 (up/down) + * - sloped & banked is 4 (left/right * up/down) + * Exceptions: + * - slopesLoop is 10 (5 slope angles * up/down) + * - inlineTwists is 6 (3 bank angles * left/right) + * - slopes25InlineTwists is 12 (3 bank angles * left/right * up/down) + * - corkscrews is 20 (10 sprites for an entire corkscrew * left/right) + * - restraints is 3 + * - curvedLiftHillUp and curvedLiftHillDown are 1 (normally would be combined, but aren't due to RCT2) + */ + static const uint8_t SpriteGroupMultiplier[EnumValue(SpriteGroupType::Count)] = { + 1, 2, 2, 2, 2, 2, 2, 10, 1, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 12, 4, 4, 4, 4, 4, 4, 4, 4, 20, 3, 1, 1, + }; + static_assert(std::size(SpriteGroupMultiplier) == EnumValue(SpriteGroupType::Count)); -static CarEntryAnimation GetAnimationTypeFromString(const std::string& s) -{ - auto result = AnimationNameLookup.find(s); - return (result != AnimationNameLookup.end()) ? result->second : CarEntryAnimation::None; -} + constexpr uint8_t DefaultSteamSpawnPosition[] = { 11, 22 }; -static LegacyAnimationParameters GetDefaultAnimationParameters(uint8_t legacyAnimationType) -{ - if (legacyAnimationType >= NumLegacyAnimationTypes) - return VehicleEntryDefaultAnimation[0]; - return VehicleEntryDefaultAnimation[legacyAnimationType]; -} + static const EnumMap AnimationNameLookup{ + { "none", CarEntryAnimation::None }, + { "simpleVehicle", CarEntryAnimation::SimpleVehicle }, + { "steamLocomotive", CarEntryAnimation::SteamLocomotive }, + { "swanBoat", CarEntryAnimation::SwanBoat }, + { "monorailCycle", CarEntryAnimation::MonorailCycle }, + { "MultiDimension", CarEntryAnimation::MultiDimension }, + { "observationTower", CarEntryAnimation::ObservationTower }, + { "animalFlying", CarEntryAnimation::AnimalFlying }, + }; -static constexpr SpritePrecision PrecisionFromNumFrames(uint32_t numRotationFrames) -{ - if (numRotationFrames == 0) - return SpritePrecision::None; - else - return static_cast(Numerics::bitScanForward(numRotationFrames) + 1); -} + constexpr auto NumLegacyAnimationTypes = 11; -static void RideObjectUpdateRideType(RideObjectEntry& rideEntry) -{ - for (auto i = 0; i < RCT2::ObjectLimits::kMaxRideTypesPerRideEntry; i++) + struct LegacyAnimationParameters { - auto oldRideType = rideEntry.ride_type[i]; - if (oldRideType != kRideTypeNull) - { - rideEntry.ride_type[i] = RCT2::RCT2RideTypeToOpenRCT2RideType(oldRideType, rideEntry); - } - } -} + uint16_t Speed; + uint8_t NumFrames; + CarEntryAnimation Alias; + }; -void RideObject::ReadLegacy(IReadObjectContext* context, IStream* stream) -{ - stream->Seek(8, STREAM_SEEK_CURRENT); - _legacyType.flags = stream->ReadValue(); - for (auto& rideType : _legacyType.ride_type) + constexpr LegacyAnimationParameters VehicleEntryDefaultAnimation[] = { + { 0, 1, CarEntryAnimation::None }, // None + { 1 << 12, 4, CarEntryAnimation::SteamLocomotive }, // Miniature Railway Locomotive + { 1 << 10, 2, CarEntryAnimation::SwanBoat }, // Swan Boat + { 1 << 11, 6, CarEntryAnimation::SimpleVehicle }, // Canoe + { 1 << 11, 7, CarEntryAnimation::SimpleVehicle }, // Rowboat + { 1 << 10, 2, CarEntryAnimation::SimpleVehicle }, // Water Tricycle + { 0x3333, 8, CarEntryAnimation::ObservationTower }, // Observation Tower + { 1 << 10, 4, CarEntryAnimation::SimpleVehicle }, // Mini Helicopter + { 1 << 11, 4, CarEntryAnimation::MonorailCycle }, // Monorail Cycle + { 0x3333, 8, CarEntryAnimation::MultiDimension }, // Multi Dimension Coaster + { 24, 4, CarEntryAnimation::AnimalFlying }, // Animal Flying + }; + static_assert(std::size(VehicleEntryDefaultAnimation) == NumLegacyAnimationTypes); + + static CarEntryAnimation GetAnimationTypeFromString(const std::string& s) { - rideType = stream->ReadValue(); - if (!RideTypeIsValid(rideType)) - rideType = kRideTypeNull; - } - _legacyType.min_cars_in_train = stream->ReadValue(); - _legacyType.max_cars_in_train = stream->ReadValue(); - _legacyType.cars_per_flat_ride = stream->ReadValue(); - _legacyType.zero_cars = stream->ReadValue(); - _legacyType.TabCar = stream->ReadValue(); - _legacyType.DefaultCar = stream->ReadValue(); - _legacyType.FrontCar = stream->ReadValue(); - _legacyType.SecondCar = stream->ReadValue(); - _legacyType.RearCar = stream->ReadValue(); - _legacyType.ThirdCar = stream->ReadValue(); - - _legacyType.BuildMenuPriority = 0; - // Skip Pad019 - stream->Seek(1, STREAM_SEEK_CURRENT); - - for (auto& carEntry : _legacyType.Cars) - { - ReadLegacyCar(context, stream, &carEntry); - } - stream->Seek(4, STREAM_SEEK_CURRENT); - _legacyType.excitement_multiplier = stream->ReadValue(); - _legacyType.intensity_multiplier = stream->ReadValue(); - _legacyType.nausea_multiplier = stream->ReadValue(); - _legacyType.max_height = stream->ReadValue(); - // Skipping a uint64_t for the enabled track pieces and two uint8_ts for the categories. - stream->Seek(10, STREAM_SEEK_CURRENT); - _legacyType.shop_item[0] = static_cast(stream->ReadValue()); - _legacyType.shop_item[1] = static_cast(stream->ReadValue()); - - GetStringTable().Read(context, stream, ObjectStringID::NAME); - GetStringTable().Read(context, stream, ObjectStringID::DESCRIPTION); - GetStringTable().Read(context, stream, ObjectStringID::CAPACITY); - - // Read preset colours, by default there are 32 - _presetColours.count = stream->ReadValue(); - - int32_t coloursCount = _presetColours.count; - // To indicate a ride has different colours each train the count - // is set to 255. There are only actually 32 colours though. - if (coloursCount == 255) - { - coloursCount = 32; + auto result = AnimationNameLookup.find(s); + return (result != AnimationNameLookup.end()) ? result->second : CarEntryAnimation::None; } - for (uint8_t i = 0; i < coloursCount; i++) + static LegacyAnimationParameters GetDefaultAnimationParameters(uint8_t legacyAnimationType) { - _presetColours.list[i] = stream->ReadValue(); + if (legacyAnimationType >= NumLegacyAnimationTypes) + return VehicleEntryDefaultAnimation[0]; + return VehicleEntryDefaultAnimation[legacyAnimationType]; } - if (isRideTypeShopOrFacility(_legacyType.ride_type[0])) + static constexpr SpritePrecision PrecisionFromNumFrames(uint32_t numRotationFrames) { - // This used to be hard-coded. JSON objects set this themselves. - _presetColours.count = 1; - _presetColours.list[0] = { COLOUR_BRIGHT_RED, COLOUR_BRIGHT_RED, COLOUR_BRIGHT_RED }; - - if (_legacyType.ride_type[0] == RIDE_TYPE_FOOD_STALL || _legacyType.ride_type[0] == RIDE_TYPE_DRINK_STALL) - { - // In RCT2, no food or drink stall could be recoloured. - _legacyType.flags |= RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB; - } - } - - // Read peep loading positions - for (int32_t i = 0; i < RCT2::ObjectLimits::kMaxCarTypesPerRideEntry; i++) - { - _peepLoadingWaypoints[i].clear(); - _peepLoadingPositions[i].clear(); - - uint16_t numPeepLoadingPositions = stream->ReadValue(); - if (numPeepLoadingPositions == 255) - { - numPeepLoadingPositions = stream->ReadValue(); - } - - if (_legacyType.Cars[i].flags & CAR_ENTRY_FLAG_LOADING_WAYPOINTS) - { - _legacyType.Cars[i].peep_loading_waypoint_segments = stream->ReadValue() == 0 ? 0 : 4; - if (_legacyType.ride_type[0] == RIDE_TYPE_ENTERPRISE) - { - _legacyType.Cars[i].peep_loading_waypoint_segments = 8; - } - - Guard::Assert(((numPeepLoadingPositions - 1) % 8) == 0, "Malformed peep loading positions"); - - for (int32_t j = 1; j < numPeepLoadingPositions; j += 4 * 2) - { - std::array entry; - entry[0].x = stream->ReadValue(); - entry[0].y = stream->ReadValue(); - entry[1].x = stream->ReadValue(); - entry[1].y = stream->ReadValue(); - entry[2].x = stream->ReadValue(); - entry[2].y = stream->ReadValue(); - stream->ReadValue(); // Skip blanks - - _peepLoadingWaypoints[i].push_back(std::move(entry)); - } - } + if (numRotationFrames == 0) + return SpritePrecision::None; else + return static_cast(Numerics::bitScanForward(numRotationFrames) + 1); + } + + static void RideObjectUpdateRideType(RideObjectEntry& rideEntry) + { + for (auto i = 0; i < RCT2::ObjectLimits::kMaxRideTypesPerRideEntry; i++) { - _legacyType.Cars[i].peep_loading_waypoint_segments = 0; - - auto data = stream->ReadArray(numPeepLoadingPositions); - _peepLoadingPositions[i] = std::vector(data.get(), data.get() + numPeepLoadingPositions); - } - } - - GetImageTable().Read(context, stream); - - // Validate properties - if (_legacyType.excitement_multiplier > 75) - { - context->LogError(ObjectError::InvalidProperty, "Excitement multiplier too high."); - } - if (_legacyType.intensity_multiplier > 75) - { - context->LogError(ObjectError::InvalidProperty, "Intensity multiplier too high."); - } - if (_legacyType.nausea_multiplier > 75) - { - context->LogError(ObjectError::InvalidProperty, "Nausea multiplier too high."); - } - RideObjectUpdateRideType(_legacyType); - _legacyType.Clearance = GetDefaultClearance(); -} - -void RideObject::Load() -{ - GetStringTable().Sort(); - _legacyType.naming.Name = LanguageAllocateObjectString(GetName()); - _legacyType.naming.Description = LanguageAllocateObjectString(GetDescription()); - _legacyType.capacity = LanguageAllocateObjectString(GetCapacity()); - _legacyType.images_offset = LoadImages(); - _legacyType.vehicle_preset_list = &_presetColours; - - int32_t currentCarImagesOffset = _legacyType.images_offset + RCT2::ObjectLimits::kMaxRideTypesPerRideEntry; - for (int32_t i = 0; i < RCT2::ObjectLimits::kMaxCarTypesPerRideEntry; i++) - { - CarEntry& carEntry = _legacyType.Cars[i]; - if (carEntry.GroupEnabled(SpriteGroupType::SlopeFlat)) - { - // RCT2 calculates num_vertical_frames and num_horizontal_frames and overwrites these properties on the car - // entry. Immediately afterwards, the two were multiplied in order to calculate base_num_frames and were never used - // again. This has been changed to use the calculation results directly - num_vertical_frames and - // num_horizontal_frames are no longer set on the car entry. - // 0x6DE946 - carEntry.base_num_frames = CalculateNumVerticalFrames(carEntry) * CalculateNumHorizontalFrames(carEntry); - uint32_t baseImageId = currentCarImagesOffset; - uint32_t imageIndex = baseImageId; - carEntry.base_image_id = baseImageId; - - for (uint8_t spriteGroup = 0; spriteGroup < EnumValue(SpriteGroupType::Count); spriteGroup++) + auto oldRideType = rideEntry.ride_type[i]; + if (oldRideType != kRideTypeNull) { - if (carEntry.SpriteGroups[spriteGroup].Enabled()) - { - carEntry.SpriteGroups[spriteGroup].imageId = imageIndex; - const auto spriteCount = carEntry.base_num_frames - * carEntry.NumRotationSprites(static_cast(spriteGroup)) - * SpriteGroupMultiplier[spriteGroup]; - imageIndex += spriteCount; - } - } - - carEntry.NumCarImages = imageIndex - currentCarImagesOffset; - - // Move the offset over this car's images. Including peeps - currentCarImagesOffset = imageIndex + carEntry.no_seating_rows * carEntry.NumCarImages; - // 0x6DEB0D - - if (!(carEntry.flags & CAR_ENTRY_FLAG_RECALCULATE_SPRITE_BOUNDS)) - { - int32_t num_images = currentCarImagesOffset - baseImageId; - if (carEntry.flags & CAR_ENTRY_FLAG_SPRITE_BOUNDS_INCLUDE_INVERTED_SET) - { - num_images *= 2; - } - - if (!gOpenRCT2NoGraphics) - { - CarEntrySetImageMaxSizes(carEntry, num_images); - } - } - - if (!_peepLoadingPositions[i].empty()) - { - carEntry.peep_loading_positions = std::move(_peepLoadingPositions[i]); - } - - if (!_peepLoadingWaypoints[i].empty()) - { - carEntry.peep_loading_waypoints = std::move(_peepLoadingWaypoints[i]); + rideEntry.ride_type[i] = RCT2::RCT2RideTypeToOpenRCT2RideType(oldRideType, rideEntry); } } } -} -void RideObject::Unload() -{ - LanguageFreeObjectString(_legacyType.naming.Name); - LanguageFreeObjectString(_legacyType.naming.Description); - LanguageFreeObjectString(_legacyType.capacity); - UnloadImages(); - - _legacyType.naming.Name = 0; - _legacyType.naming.Description = 0; - _legacyType.capacity = 0; - _legacyType.images_offset = 0; -} - -void RideObject::DrawPreview(RenderTarget& rt, [[maybe_unused]] int32_t width, [[maybe_unused]] int32_t height) const -{ - uint32_t imageId = _legacyType.images_offset; - - for (auto rideType : _legacyType.ride_type) + void RideObject::ReadLegacy(IReadObjectContext* context, IStream* stream) { - if (rideType != kRideTypeNull) - break; - - imageId++; - } - - GfxDrawSprite(rt, ImageId(imageId), { 0, 0 }); -} - -std::string RideObject::GetDescription() const -{ - return GetString(ObjectStringID::DESCRIPTION); -} - -std::string RideObject::GetCapacity() const -{ - return GetString(ObjectStringID::CAPACITY); -} - -ImageIndex RideObject::GetPreviewImage(ride_type_t type) -{ - auto it = std::find(std::begin(_legacyType.ride_type), std::end(_legacyType.ride_type), type); - if (it == std::end(_legacyType.ride_type)) - { - return kImageIndexUndefined; - } - - return _legacyType.images_offset + std::distance(std::begin(_legacyType.ride_type), it); -} - -void RideObject::SetRepositoryItem(ObjectRepositoryItem* item) const -{ - for (int32_t i = 0; i < RCT2::ObjectLimits::kMaxRideTypesPerRideEntry; i++) - { - item->RideInfo.RideType[i] = _legacyType.ride_type[i]; - } - - item->RideInfo.RideFlags = 0; -} - -void RideObject::ReadLegacyCar([[maybe_unused]] IReadObjectContext* context, IStream* stream, CarEntry* car) -{ - car->TabRotationMask = stream->ReadValue(); - stream->Seek(2 * 1, STREAM_SEEK_CURRENT); - car->spacing = stream->ReadValue(); - car->car_mass = stream->ReadValue(); - car->tab_height = stream->ReadValue(); - car->num_seats = stream->ReadValue(); - uint16_t spriteGroups = stream->ReadValue(); - car->sprite_width = stream->ReadValue(); - car->sprite_height_negative = stream->ReadValue(); - car->sprite_height_positive = stream->ReadValue(); - auto legacyAnimation = stream->ReadValue(); - car->flags = stream->ReadValue(); - // Implied in vanilla, but can be turned off in OpenRCT2. - car->flags |= CAR_ENTRY_FLAG_ENABLE_BODY_COLOUR; - car->base_num_frames = stream->ReadValue(); - stream->Seek(15 * 4, STREAM_SEEK_CURRENT); - car->no_seating_rows = stream->ReadValue(); - car->spinning_inertia = stream->ReadValue(); - car->spinning_friction = stream->ReadValue(); - car->friction_sound_id = stream->ReadValue(); - car->ReversedCarIndex = stream->ReadValue(); - car->soundRange = stream->ReadValue(); - car->double_sound_frequency = stream->ReadValue(); - car->powered_acceleration = stream->ReadValue(); - car->powered_max_speed = stream->ReadValue(); - car->PaintStyle = stream->ReadValue(); - car->effect_visual = stream->ReadValue(); - car->draw_order = stream->ReadValue(); - car->num_vertical_frames_override = stream->ReadValue(); - stream->Seek(4, STREAM_SEEK_CURRENT); - - // OpenRCT2-specific features below - auto animationProperties = GetDefaultAnimationParameters(legacyAnimation); - car->animation = animationProperties.Alias; - car->AnimationSpeed = animationProperties.Speed; - car->AnimationFrames = animationProperties.NumFrames; - car->SteamEffect.Longitudinal = DefaultSteamSpawnPosition[0]; - car->SteamEffect.Vertical = DefaultSteamSpawnPosition[1]; - ReadLegacySpriteGroups(car, spriteGroups); -} - -uint8_t RideObject::CalculateNumVerticalFrames(const CarEntry& carEntry) -{ - // 0x6DE90B - uint8_t numVerticalFrames; - if (carEntry.flags & CAR_ENTRY_FLAG_OVERRIDE_NUM_VERTICAL_FRAMES) - { - numVerticalFrames = carEntry.num_vertical_frames_override; - } - else - { - if (!(carEntry.flags & CAR_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES)) + stream->Seek(8, STREAM_SEEK_CURRENT); + _legacyType.flags = stream->ReadValue(); + for (auto& rideType : _legacyType.ride_type) { - if ((carEntry.flags & CAR_ENTRY_FLAG_VEHICLE_ANIMATION) - && carEntry.animation != CarEntryAnimation::ObservationTower) - { - if (!(carEntry.flags & CAR_ENTRY_FLAG_DODGEM_INUSE_LIGHTS)) - { - numVerticalFrames = 4; - } - else - { - numVerticalFrames = 2; - } - } - else - { - numVerticalFrames = 1; - } + rideType = stream->ReadValue(); + if (!RideTypeIsValid(rideType)) + rideType = kRideTypeNull; } - else + _legacyType.min_cars_in_train = stream->ReadValue(); + _legacyType.max_cars_in_train = stream->ReadValue(); + _legacyType.cars_per_flat_ride = stream->ReadValue(); + _legacyType.zero_cars = stream->ReadValue(); + _legacyType.TabCar = stream->ReadValue(); + _legacyType.DefaultCar = stream->ReadValue(); + _legacyType.FrontCar = stream->ReadValue(); + _legacyType.SecondCar = stream->ReadValue(); + _legacyType.RearCar = stream->ReadValue(); + _legacyType.ThirdCar = stream->ReadValue(); + + _legacyType.BuildMenuPriority = 0; + // Skip Pad019 + stream->Seek(1, STREAM_SEEK_CURRENT); + + for (auto& carEntry : _legacyType.Cars) { - numVerticalFrames = 32; + ReadLegacyCar(context, stream, &carEntry); } - } + stream->Seek(4, STREAM_SEEK_CURRENT); + _legacyType.excitement_multiplier = stream->ReadValue(); + _legacyType.intensity_multiplier = stream->ReadValue(); + _legacyType.nausea_multiplier = stream->ReadValue(); + _legacyType.max_height = stream->ReadValue(); + // Skipping a uint64_t for the enabled track pieces and two uint8_ts for the categories. + stream->Seek(10, STREAM_SEEK_CURRENT); + _legacyType.shop_item[0] = static_cast(stream->ReadValue()); + _legacyType.shop_item[1] = static_cast(stream->ReadValue()); - return numVerticalFrames; -} + GetStringTable().Read(context, stream, ObjectStringID::NAME); + GetStringTable().Read(context, stream, ObjectStringID::DESCRIPTION); + GetStringTable().Read(context, stream, ObjectStringID::CAPACITY); -uint8_t RideObject::CalculateNumHorizontalFrames(const CarEntry& carEntry) -{ - uint8_t numHorizontalFrames; - if (carEntry.flags & CAR_ENTRY_FLAG_SWINGING) - { - if (!(carEntry.flags & CAR_ENTRY_FLAG_SUSPENDED_SWING) && !(carEntry.flags & CAR_ENTRY_FLAG_SLIDE_SWING)) + // Read preset colours, by default there are 32 + _presetColours.count = stream->ReadValue(); + + int32_t coloursCount = _presetColours.count; + // To indicate a ride has different colours each train the count + // is set to 255. There are only actually 32 colours though. + if (coloursCount == 255) { - if (carEntry.flags & CAR_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING) - { - numHorizontalFrames = 3; - } - else - { - numHorizontalFrames = 5; - } - } - else if (!(carEntry.flags & CAR_ENTRY_FLAG_SUSPENDED_SWING) || !(carEntry.flags & CAR_ENTRY_FLAG_SLIDE_SWING)) - { - numHorizontalFrames = 7; - } - else - { - numHorizontalFrames = 13; - } - } - else - { - numHorizontalFrames = 1; - } - - return numHorizontalFrames; -} - -void RideObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "RideObject::ReadJson expects parameter root to be object"); - - json_t properties = root["properties"]; - - if (properties.is_object()) - { - // This will convert a string to an array - json_t rideTypes = Json::AsArray(properties["type"]); - size_t numRideTypes = rideTypes.size(); - - for (size_t i = 0; i < RCT2::ObjectLimits::kMaxRideTypesPerRideEntry; i++) - { - auto rideType = kRideTypeNull; - - if (i < numRideTypes) - { - rideType = ParseRideType(Json::GetString(rideTypes[i])); - - if (rideType == kRideTypeNull) - { - context->LogError(ObjectError::InvalidProperty, "Unknown ride type"); - } - } - - _legacyType.ride_type[i] = rideType; + coloursCount = 32; } - _legacyType.max_height = Json::GetNumber(properties["maxHeight"]); - _legacyType.Clearance = Json::GetNumber(properties["clearance"], GetDefaultClearance()); - - // This needs to be set for both shops/facilities _and_ regular rides. - for (auto& item : _legacyType.shop_item) + for (uint8_t i = 0; i < coloursCount; i++) { - item = ShopItem::None; + _presetColours.list[i] = stream->ReadValue(); } - auto carColours = Json::AsArray(properties["carColours"]); - _presetColours = ReadJsonCarColours(carColours); - if (isRideTypeShopOrFacility(_legacyType.ride_type[0])) { - // Standard car info for a shop - auto& car = _legacyType.Cars[0]; - car.spacing = 544; - car.SpriteGroups[EnumValue(SpriteGroupType::SlopeFlat)].spritePrecision = SpritePrecision::Sprites4; - car.sprite_width = 1; - car.sprite_height_negative = 1; - car.sprite_height_positive = 1; - car.flags = CAR_ENTRY_FLAG_SPINNING; - car.PaintStyle = VEHICLE_VISUAL_FLAT_RIDE_OR_CAR_RIDE; - car.friction_sound_id = OpenRCT2::Audio::SoundId::Null; - car.soundRange = SoundRange::none; - car.draw_order = 6; + // This used to be hard-coded. JSON objects set this themselves. + _presetColours.count = 1; + _presetColours.list[0] = { COLOUR_BRIGHT_RED, COLOUR_BRIGHT_RED, COLOUR_BRIGHT_RED }; - // Shop item - auto rideSells = Json::AsArray(properties["sells"]); - auto numShopItems = std::min(static_cast(RCT2::ObjectLimits::kMaxShopItemsPerRideEntry), rideSells.size()); - for (size_t i = 0; i < numShopItems; i++) + if (_legacyType.ride_type[0] == RIDE_TYPE_FOOD_STALL || _legacyType.ride_type[0] == RIDE_TYPE_DRINK_STALL) { - auto shopItem = ParseShopItem(Json::GetString(rideSells[i])); - if (shopItem == ShopItem::None) + // In RCT2, no food or drink stall could be recoloured. + _legacyType.flags |= RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB; + } + } + + // Read peep loading positions + for (int32_t i = 0; i < RCT2::ObjectLimits::kMaxCarTypesPerRideEntry; i++) + { + _peepLoadingWaypoints[i].clear(); + _peepLoadingPositions[i].clear(); + + uint16_t numPeepLoadingPositions = stream->ReadValue(); + if (numPeepLoadingPositions == 255) + { + numPeepLoadingPositions = stream->ReadValue(); + } + + if (_legacyType.Cars[i].flags & CAR_ENTRY_FLAG_LOADING_WAYPOINTS) + { + _legacyType.Cars[i].peep_loading_waypoint_segments = stream->ReadValue() == 0 ? 0 : 4; + if (_legacyType.ride_type[0] == RIDE_TYPE_ENTERPRISE) { - context->LogWarning(ObjectError::InvalidProperty, "Unknown shop item"); + _legacyType.Cars[i].peep_loading_waypoint_segments = 8; } - _legacyType.shop_item[i] = shopItem; + Guard::Assert(((numPeepLoadingPositions - 1) % 8) == 0, "Malformed peep loading positions"); + + for (int32_t j = 1; j < numPeepLoadingPositions; j += 4 * 2) + { + std::array entry; + entry[0].x = stream->ReadValue(); + entry[0].y = stream->ReadValue(); + entry[1].x = stream->ReadValue(); + entry[1].y = stream->ReadValue(); + entry[2].x = stream->ReadValue(); + entry[2].y = stream->ReadValue(); + stream->ReadValue(); // Skip blanks + + _peepLoadingWaypoints[i].push_back(std::move(entry)); + } + } + else + { + _legacyType.Cars[i].peep_loading_waypoint_segments = 0; + + auto data = stream->ReadArray(numPeepLoadingPositions); + _peepLoadingPositions[i] = std::vector(data.get(), data.get() + numPeepLoadingPositions); + } + } + + GetImageTable().Read(context, stream); + + // Validate properties + if (_legacyType.excitement_multiplier > 75) + { + context->LogError(ObjectError::InvalidProperty, "Excitement multiplier too high."); + } + if (_legacyType.intensity_multiplier > 75) + { + context->LogError(ObjectError::InvalidProperty, "Intensity multiplier too high."); + } + if (_legacyType.nausea_multiplier > 75) + { + context->LogError(ObjectError::InvalidProperty, "Nausea multiplier too high."); + } + RideObjectUpdateRideType(_legacyType); + _legacyType.Clearance = GetDefaultClearance(); + } + + void RideObject::Load() + { + GetStringTable().Sort(); + _legacyType.naming.Name = LanguageAllocateObjectString(GetName()); + _legacyType.naming.Description = LanguageAllocateObjectString(GetDescription()); + _legacyType.capacity = LanguageAllocateObjectString(GetCapacity()); + _legacyType.images_offset = LoadImages(); + _legacyType.vehicle_preset_list = &_presetColours; + + int32_t currentCarImagesOffset = _legacyType.images_offset + RCT2::ObjectLimits::kMaxRideTypesPerRideEntry; + for (int32_t i = 0; i < RCT2::ObjectLimits::kMaxCarTypesPerRideEntry; i++) + { + CarEntry& carEntry = _legacyType.Cars[i]; + if (carEntry.GroupEnabled(SpriteGroupType::SlopeFlat)) + { + // RCT2 calculates num_vertical_frames and num_horizontal_frames and overwrites these properties on the car + // entry. Immediately afterwards, the two were multiplied in order to calculate base_num_frames and were never + // used again. This has been changed to use the calculation results directly - num_vertical_frames and + // num_horizontal_frames are no longer set on the car entry. + // 0x6DE946 + carEntry.base_num_frames = CalculateNumVerticalFrames(carEntry) * CalculateNumHorizontalFrames(carEntry); + uint32_t baseImageId = currentCarImagesOffset; + uint32_t imageIndex = baseImageId; + carEntry.base_image_id = baseImageId; + + for (uint8_t spriteGroup = 0; spriteGroup < EnumValue(SpriteGroupType::Count); spriteGroup++) + { + if (carEntry.SpriteGroups[spriteGroup].Enabled()) + { + carEntry.SpriteGroups[spriteGroup].imageId = imageIndex; + const auto spriteCount = carEntry.base_num_frames + * carEntry.NumRotationSprites(static_cast(spriteGroup)) + * SpriteGroupMultiplier[spriteGroup]; + imageIndex += spriteCount; + } + } + + carEntry.NumCarImages = imageIndex - currentCarImagesOffset; + + // Move the offset over this car's images. Including peeps + currentCarImagesOffset = imageIndex + carEntry.no_seating_rows * carEntry.NumCarImages; + // 0x6DEB0D + + if (!(carEntry.flags & CAR_ENTRY_FLAG_RECALCULATE_SPRITE_BOUNDS)) + { + int32_t num_images = currentCarImagesOffset - baseImageId; + if (carEntry.flags & CAR_ENTRY_FLAG_SPRITE_BOUNDS_INCLUDE_INVERTED_SET) + { + num_images *= 2; + } + + if (!gOpenRCT2NoGraphics) + { + CarEntrySetImageMaxSizes(carEntry, num_images); + } + } + + if (!_peepLoadingPositions[i].empty()) + { + carEntry.peep_loading_positions = std::move(_peepLoadingPositions[i]); + } + + if (!_peepLoadingWaypoints[i].empty()) + { + carEntry.peep_loading_waypoints = std::move(_peepLoadingWaypoints[i]); + } + } + } + } + + void RideObject::Unload() + { + LanguageFreeObjectString(_legacyType.naming.Name); + LanguageFreeObjectString(_legacyType.naming.Description); + LanguageFreeObjectString(_legacyType.capacity); + UnloadImages(); + + _legacyType.naming.Name = 0; + _legacyType.naming.Description = 0; + _legacyType.capacity = 0; + _legacyType.images_offset = 0; + } + + void RideObject::DrawPreview(RenderTarget& rt, [[maybe_unused]] int32_t width, [[maybe_unused]] int32_t height) const + { + uint32_t imageId = _legacyType.images_offset; + + for (auto rideType : _legacyType.ride_type) + { + if (rideType != kRideTypeNull) + break; + + imageId++; + } + + GfxDrawSprite(rt, ImageId(imageId), { 0, 0 }); + } + + std::string RideObject::GetDescription() const + { + return GetString(ObjectStringID::DESCRIPTION); + } + + std::string RideObject::GetCapacity() const + { + return GetString(ObjectStringID::CAPACITY); + } + + ImageIndex RideObject::GetPreviewImage(ride_type_t type) + { + auto it = std::find(std::begin(_legacyType.ride_type), std::end(_legacyType.ride_type), type); + if (it == std::end(_legacyType.ride_type)) + { + return kImageIndexUndefined; + } + + return _legacyType.images_offset + std::distance(std::begin(_legacyType.ride_type), it); + } + + void RideObject::SetRepositoryItem(ObjectRepositoryItem* item) const + { + for (int32_t i = 0; i < RCT2::ObjectLimits::kMaxRideTypesPerRideEntry; i++) + { + item->RideInfo.RideType[i] = _legacyType.ride_type[i]; + } + + item->RideInfo.RideFlags = 0; + } + + void RideObject::ReadLegacyCar([[maybe_unused]] IReadObjectContext* context, IStream* stream, CarEntry* car) + { + car->TabRotationMask = stream->ReadValue(); + stream->Seek(2 * 1, STREAM_SEEK_CURRENT); + car->spacing = stream->ReadValue(); + car->car_mass = stream->ReadValue(); + car->tab_height = stream->ReadValue(); + car->num_seats = stream->ReadValue(); + uint16_t spriteGroups = stream->ReadValue(); + car->sprite_width = stream->ReadValue(); + car->sprite_height_negative = stream->ReadValue(); + car->sprite_height_positive = stream->ReadValue(); + auto legacyAnimation = stream->ReadValue(); + car->flags = stream->ReadValue(); + // Implied in vanilla, but can be turned off in OpenRCT2. + car->flags |= CAR_ENTRY_FLAG_ENABLE_BODY_COLOUR; + car->base_num_frames = stream->ReadValue(); + stream->Seek(15 * 4, STREAM_SEEK_CURRENT); + car->no_seating_rows = stream->ReadValue(); + car->spinning_inertia = stream->ReadValue(); + car->spinning_friction = stream->ReadValue(); + car->friction_sound_id = stream->ReadValue(); + car->ReversedCarIndex = stream->ReadValue(); + car->soundRange = stream->ReadValue(); + car->double_sound_frequency = stream->ReadValue(); + car->powered_acceleration = stream->ReadValue(); + car->powered_max_speed = stream->ReadValue(); + car->PaintStyle = stream->ReadValue(); + car->effect_visual = stream->ReadValue(); + car->draw_order = stream->ReadValue(); + car->num_vertical_frames_override = stream->ReadValue(); + stream->Seek(4, STREAM_SEEK_CURRENT); + + // OpenRCT2-specific features below + auto animationProperties = GetDefaultAnimationParameters(legacyAnimation); + car->animation = animationProperties.Alias; + car->AnimationSpeed = animationProperties.Speed; + car->AnimationFrames = animationProperties.NumFrames; + car->SteamEffect.Longitudinal = DefaultSteamSpawnPosition[0]; + car->SteamEffect.Vertical = DefaultSteamSpawnPosition[1]; + ReadLegacySpriteGroups(car, spriteGroups); + } + + uint8_t RideObject::CalculateNumVerticalFrames(const CarEntry& carEntry) + { + // 0x6DE90B + uint8_t numVerticalFrames; + if (carEntry.flags & CAR_ENTRY_FLAG_OVERRIDE_NUM_VERTICAL_FRAMES) + { + numVerticalFrames = carEntry.num_vertical_frames_override; + } + else + { + if (!(carEntry.flags & CAR_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES)) + { + if ((carEntry.flags & CAR_ENTRY_FLAG_VEHICLE_ANIMATION) + && carEntry.animation != CarEntryAnimation::ObservationTower) + { + if (!(carEntry.flags & CAR_ENTRY_FLAG_DODGEM_INUSE_LIGHTS)) + { + numVerticalFrames = 4; + } + else + { + numVerticalFrames = 2; + } + } + else + { + numVerticalFrames = 1; + } + } + else + { + numVerticalFrames = 32; + } + } + + return numVerticalFrames; + } + + uint8_t RideObject::CalculateNumHorizontalFrames(const CarEntry& carEntry) + { + uint8_t numHorizontalFrames; + if (carEntry.flags & CAR_ENTRY_FLAG_SWINGING) + { + if (!(carEntry.flags & CAR_ENTRY_FLAG_SUSPENDED_SWING) && !(carEntry.flags & CAR_ENTRY_FLAG_SLIDE_SWING)) + { + if (carEntry.flags & CAR_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING) + { + numHorizontalFrames = 3; + } + else + { + numHorizontalFrames = 5; + } + } + else if (!(carEntry.flags & CAR_ENTRY_FLAG_SUSPENDED_SWING) || !(carEntry.flags & CAR_ENTRY_FLAG_SLIDE_SWING)) + { + numHorizontalFrames = 7; + } + else + { + numHorizontalFrames = 13; } } else { - ReadJsonVehicleInfo(context, properties); - - auto swingMode = Json::GetNumber(properties["swingMode"]); - if (swingMode == 1) - { - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1; - } - else if (swingMode == 2) - { - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1; - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_2; - } - - auto rotationMode = Json::GetNumber(properties["rotationMode"]); - if (rotationMode == 1) - { - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_1; - } - else if (rotationMode == 2) - { - _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_2; - } - - auto ratingMultiplier = properties["ratingMultipler"]; - if (ratingMultiplier.is_object()) - { - _legacyType.excitement_multiplier = Json::GetNumber(ratingMultiplier["excitement"]); - _legacyType.intensity_multiplier = Json::GetNumber(ratingMultiplier["intensity"]); - _legacyType.nausea_multiplier = Json::GetNumber(ratingMultiplier["nausea"]); - } + numHorizontalFrames = 1; } - _legacyType.BuildMenuPriority = Json::GetNumber(properties["buildMenuPriority"]); - _legacyType.flags |= Json::GetFlags( - properties, - { - { "noInversions", RIDE_ENTRY_FLAG_NO_INVERSIONS }, - { "noBanking", RIDE_ENTRY_FLAG_NO_BANKED_TRACK }, - { "playDepartSound", RIDE_ENTRY_FLAG_PLAY_DEPART_SOUND }, - // Skipping "disallowWandering", no vehicle sets this flag. - { "playSplashSound", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND }, - { "playSplashSoundSlide", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND_SLIDE }, - { "hasShelter", RIDE_ENTRY_FLAG_COVERED_RIDE }, - { "limitAirTimeBonus", RIDE_ENTRY_FLAG_LIMIT_AIRTIME_BONUS }, - { "disableBreakdown", RIDE_ENTRY_FLAG_CANNOT_BREAK_DOWN }, - // Skipping noDoorsOverTrack, moved to ride groups. - { "noCollisionCrashes", RIDE_ENTRY_FLAG_DISABLE_COLLISION_CRASHES }, - { "disablePainting", RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB }, - { "riderControlsSpeed", RIDE_ENTRY_FLAG_RIDER_CONTROLS_SPEED }, - { "hideEmptyTrains", RIDE_ENTRY_FLAG_HIDE_EMPTY_TRAINS }, - }); + return numHorizontalFrames; } - PopulateTablesFromJson(context, root); -} - -void RideObject::ReadJsonVehicleInfo([[maybe_unused]] IReadObjectContext* context, json_t& properties) -{ - Guard::Assert(properties.is_object(), "RideObject::ReadJsonVehicleInfo expects parameter properties to be object"); - - _legacyType.min_cars_in_train = Json::GetNumber(properties["minCarsPerTrain"], 1); - _legacyType.max_cars_in_train = Json::GetNumber(properties["maxCarsPerTrain"], 1); - _legacyType.cars_per_flat_ride = Json::GetNumber(properties["carsPerFlatRide"], kNoFlatRideCars); - _legacyType.zero_cars = Json::GetNumber(properties["numEmptyCars"]); - - // Train formation from car indices - _legacyType.DefaultCar = Json::GetNumber(properties["defaultCar"]); - _legacyType.TabCar = Json::GetNumber(properties["tabCar"]); - - float tabScale = Json::GetNumber(properties["tabScale"]); - if (tabScale != 0 && tabScale <= 0.5f) + void RideObject::ReadJson(IReadObjectContext* context, json_t& root) { - _legacyType.flags |= RIDE_ENTRY_FLAG_VEHICLE_TAB_SCALE_HALF; - } + Guard::Assert(root.is_object(), "RideObject::ReadJson expects parameter root to be object"); - json_t headCars = Json::AsArray(properties["headCars"]); - json_t tailCars = Json::AsArray(properties["tailCars"]); + json_t properties = root["properties"]; - // 0xFF means N/A. - _legacyType.FrontCar = Json::GetNumber(headCars[0], 0xFF); - _legacyType.SecondCar = Json::GetNumber(headCars[1], 0xFF); - _legacyType.ThirdCar = Json::GetNumber(headCars[2], 0xFF); - _legacyType.RearCar = Json::GetNumber(tailCars[0], 0xFF); - - auto cars = ReadJsonCars(context, properties["cars"]); - auto numCars = std::min(std::size(_legacyType.Cars), cars.size()); - for (size_t i = 0; i < numCars; i++) - { - _legacyType.Cars[i] = cars[i]; - } -} - -std::vector RideObject::ReadJsonCars([[maybe_unused]] IReadObjectContext* context, json_t& jCars) -{ - std::vector cars; - - if (jCars.is_array()) - { - for (auto& jCar : jCars) + if (properties.is_object()) { - if (jCar.is_object()) + // This will convert a string to an array + json_t rideTypes = Json::AsArray(properties["type"]); + size_t numRideTypes = rideTypes.size(); + + for (size_t i = 0; i < RCT2::ObjectLimits::kMaxRideTypesPerRideEntry; i++) { - cars.push_back(ReadJsonCar(context, jCar)); - } - } - } - else if (jCars.is_object()) - { - cars.push_back(ReadJsonCar(context, jCars)); - } + auto rideType = kRideTypeNull; - return cars; -} - -CarEntry RideObject::ReadJsonCar([[maybe_unused]] IReadObjectContext* context, json_t& jCar) -{ - Guard::Assert(jCar.is_object(), "RideObject::ReadJsonCar expects parameter jCar to be object"); - - CarEntry car = {}; - car.TabRotationMask = Json::GetNumber(jCar["rotationFrameMask"]); - car.spacing = Json::GetNumber(jCar["spacing"]); - car.car_mass = Json::GetNumber(jCar["mass"]); - car.tab_height = Json::GetNumber(jCar["tabOffset"]); - car.num_seats = Json::GetNumber(jCar["numSeats"]); - if (Json::GetBoolean(jCar["seatsInPairs"], true) && car.num_seats > 1) - { - car.num_seats |= kVehicleSeatPairFlag; - } - - car.sprite_width = Json::GetNumber(jCar["spriteWidth"]); - car.sprite_height_negative = Json::GetNumber(jCar["spriteHeightNegative"]); - car.sprite_height_positive = Json::GetNumber(jCar["spriteHeightPositive"]); - car.base_num_frames = Json::GetNumber(jCar["baseNumFrames"]); - car.NumCarImages = Json::GetNumber(jCar["numImages"]); - car.no_seating_rows = Json::GetNumber(jCar["numSeatRows"]); - car.spinning_inertia = Json::GetNumber(jCar["spinningInertia"]); - car.spinning_friction = Json::GetNumber(jCar["spinningFriction"]); - car.friction_sound_id = Json::GetEnum(jCar["frictionSoundId"], OpenRCT2::Audio::SoundId::Null); - car.ReversedCarIndex = Json::GetNumber(jCar["logFlumeReverserVehicleType"]); - car.soundRange = Json::GetEnum(jCar["soundRange"], SoundRange::none); - car.double_sound_frequency = Json::GetNumber(jCar["doubleSoundFrequency"]); - car.powered_acceleration = Json::GetNumber(jCar["poweredAcceleration"]); - car.powered_max_speed = Json::GetNumber(jCar["poweredMaxSpeed"]); - car.PaintStyle = Json::GetNumber(jCar["carVisual"]); - car.effect_visual = Json::GetNumber(jCar["effectVisual"], 1); - car.draw_order = Json::GetNumber(jCar["drawOrder"]); - car.num_vertical_frames_override = Json::GetNumber(jCar["numVerticalFramesOverride"]); - - auto jAnimation = jCar["animation"]; - if (jAnimation.is_object()) - { - car.animation = GetAnimationTypeFromString(Json::GetString(jAnimation["animationType"])); - car.AnimationSpeed = Json::GetNumber(jAnimation["animationSpeed"]); - car.AnimationFrames = Json::GetNumber(jAnimation["animationFrames"]); - } - else - { - auto animationProperties = GetDefaultAnimationParameters(Json::GetNumber(jAnimation)); - car.animation = animationProperties.Alias; - car.AnimationSpeed = animationProperties.Speed; - car.AnimationFrames = animationProperties.NumFrames; - - if (!jCar["animationSpeed"].is_null()) - car.AnimationSpeed = Json::GetNumber(jCar["animationSpeed"]); - if (!jCar["animationFrames"].is_null()) - car.AnimationFrames = Json::GetNumber(jCar["animationFrames"]); - } - - auto jSteamTranslation = jCar["steamPosition"]; - if (jSteamTranslation.is_object()) - { - car.SteamEffect.Longitudinal = Json::GetNumber(jSteamTranslation["longitudinal"], DefaultSteamSpawnPosition[0]); - car.SteamEffect.Vertical = Json::GetNumber(jSteamTranslation["vertical"], DefaultSteamSpawnPosition[1]); - } - else - { - car.SteamEffect.Longitudinal = DefaultSteamSpawnPosition[0]; - car.SteamEffect.Vertical = DefaultSteamSpawnPosition[1]; - } - - auto jLoadingPositions = jCar["loadingPositions"]; - if (jLoadingPositions.is_array()) - { - for (auto& jPos : jLoadingPositions) - { - car.peep_loading_positions.push_back(Json::GetNumber(jPos)); - } - } - else - { - auto jLoadingWaypoints = jCar["loadingWaypoints"]; - if (jLoadingWaypoints.is_array()) - { - car.flags |= CAR_ENTRY_FLAG_LOADING_WAYPOINTS; - car.peep_loading_waypoint_segments = Json::GetNumber(jCar["numSegments"]); - - for (auto& jRoute : jLoadingWaypoints) - { - if (jRoute.is_array()) + if (i < numRideTypes) { - std::array entry; + rideType = ParseRideType(Json::GetString(rideTypes[i])); - for (size_t j = 0; j < 3; ++j) + if (rideType == kRideTypeNull) { - auto jWaypoint = jRoute[j]; - if (jWaypoint.is_array() && jWaypoint.size() >= 2) - { - int32_t x = Json::GetNumber(jWaypoint[0]); - int32_t y = Json::GetNumber(jWaypoint[1]); - entry[j] = { x, y }; - } + context->LogError(ObjectError::InvalidProperty, "Unknown ride type"); + } + } + + _legacyType.ride_type[i] = rideType; + } + + _legacyType.max_height = Json::GetNumber(properties["maxHeight"]); + _legacyType.Clearance = Json::GetNumber(properties["clearance"], GetDefaultClearance()); + + // This needs to be set for both shops/facilities _and_ regular rides. + for (auto& item : _legacyType.shop_item) + { + item = ShopItem::None; + } + + auto carColours = Json::AsArray(properties["carColours"]); + _presetColours = ReadJsonCarColours(carColours); + + if (isRideTypeShopOrFacility(_legacyType.ride_type[0])) + { + // Standard car info for a shop + auto& car = _legacyType.Cars[0]; + car.spacing = 544; + car.SpriteGroups[EnumValue(SpriteGroupType::SlopeFlat)].spritePrecision = SpritePrecision::Sprites4; + car.sprite_width = 1; + car.sprite_height_negative = 1; + car.sprite_height_positive = 1; + car.flags = CAR_ENTRY_FLAG_SPINNING; + car.PaintStyle = VEHICLE_VISUAL_FLAT_RIDE_OR_CAR_RIDE; + car.friction_sound_id = OpenRCT2::Audio::SoundId::Null; + car.soundRange = SoundRange::none; + car.draw_order = 6; + + // Shop item + auto rideSells = Json::AsArray(properties["sells"]); + auto numShopItems = std::min( + static_cast(RCT2::ObjectLimits::kMaxShopItemsPerRideEntry), rideSells.size()); + for (size_t i = 0; i < numShopItems; i++) + { + auto shopItem = ParseShopItem(Json::GetString(rideSells[i])); + if (shopItem == ShopItem::None) + { + context->LogWarning(ObjectError::InvalidProperty, "Unknown shop item"); } - car.peep_loading_waypoints.push_back(std::move(entry)); + _legacyType.shop_item[i] = shopItem; + } + } + else + { + ReadJsonVehicleInfo(context, properties); + + auto swingMode = Json::GetNumber(properties["swingMode"]); + if (swingMode == 1) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1; + } + else if (swingMode == 2) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1; + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_2; + } + + auto rotationMode = Json::GetNumber(properties["rotationMode"]); + if (rotationMode == 1) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_1; + } + else if (rotationMode == 2) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_2; + } + + auto ratingMultiplier = properties["ratingMultipler"]; + if (ratingMultiplier.is_object()) + { + _legacyType.excitement_multiplier = Json::GetNumber(ratingMultiplier["excitement"]); + _legacyType.intensity_multiplier = Json::GetNumber(ratingMultiplier["intensity"]); + _legacyType.nausea_multiplier = Json::GetNumber(ratingMultiplier["nausea"]); + } + } + + _legacyType.BuildMenuPriority = Json::GetNumber(properties["buildMenuPriority"]); + _legacyType.flags |= Json::GetFlags( + properties, + { + { "noInversions", RIDE_ENTRY_FLAG_NO_INVERSIONS }, + { "noBanking", RIDE_ENTRY_FLAG_NO_BANKED_TRACK }, + { "playDepartSound", RIDE_ENTRY_FLAG_PLAY_DEPART_SOUND }, + // Skipping "disallowWandering", no vehicle sets this flag. + { "playSplashSound", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND }, + { "playSplashSoundSlide", RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND_SLIDE }, + { "hasShelter", RIDE_ENTRY_FLAG_COVERED_RIDE }, + { "limitAirTimeBonus", RIDE_ENTRY_FLAG_LIMIT_AIRTIME_BONUS }, + { "disableBreakdown", RIDE_ENTRY_FLAG_CANNOT_BREAK_DOWN }, + // Skipping noDoorsOverTrack, moved to ride groups. + { "noCollisionCrashes", RIDE_ENTRY_FLAG_DISABLE_COLLISION_CRASHES }, + { "disablePainting", RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB }, + { "riderControlsSpeed", RIDE_ENTRY_FLAG_RIDER_CONTROLS_SPEED }, + { "hideEmptyTrains", RIDE_ENTRY_FLAG_HIDE_EMPTY_TRAINS }, + }); + } + + PopulateTablesFromJson(context, root); + } + + void RideObject::ReadJsonVehicleInfo([[maybe_unused]] IReadObjectContext* context, json_t& properties) + { + Guard::Assert(properties.is_object(), "RideObject::ReadJsonVehicleInfo expects parameter properties to be object"); + + _legacyType.min_cars_in_train = Json::GetNumber(properties["minCarsPerTrain"], 1); + _legacyType.max_cars_in_train = Json::GetNumber(properties["maxCarsPerTrain"], 1); + _legacyType.cars_per_flat_ride = Json::GetNumber(properties["carsPerFlatRide"], kNoFlatRideCars); + _legacyType.zero_cars = Json::GetNumber(properties["numEmptyCars"]); + + // Train formation from car indices + _legacyType.DefaultCar = Json::GetNumber(properties["defaultCar"]); + _legacyType.TabCar = Json::GetNumber(properties["tabCar"]); + + float tabScale = Json::GetNumber(properties["tabScale"]); + if (tabScale != 0 && tabScale <= 0.5f) + { + _legacyType.flags |= RIDE_ENTRY_FLAG_VEHICLE_TAB_SCALE_HALF; + } + + json_t headCars = Json::AsArray(properties["headCars"]); + json_t tailCars = Json::AsArray(properties["tailCars"]); + + // 0xFF means N/A. + _legacyType.FrontCar = Json::GetNumber(headCars[0], 0xFF); + _legacyType.SecondCar = Json::GetNumber(headCars[1], 0xFF); + _legacyType.ThirdCar = Json::GetNumber(headCars[2], 0xFF); + _legacyType.RearCar = Json::GetNumber(tailCars[0], 0xFF); + + auto cars = ReadJsonCars(context, properties["cars"]); + auto numCars = std::min(std::size(_legacyType.Cars), cars.size()); + for (size_t i = 0; i < numCars; i++) + { + _legacyType.Cars[i] = cars[i]; + } + } + + std::vector RideObject::ReadJsonCars([[maybe_unused]] IReadObjectContext* context, json_t& jCars) + { + std::vector cars; + + if (jCars.is_array()) + { + for (auto& jCar : jCars) + { + if (jCar.is_object()) + { + cars.push_back(ReadJsonCar(context, jCar)); } } } + else if (jCars.is_object()) + { + cars.push_back(ReadJsonCar(context, jCars)); + } + + return cars; } - car.flags |= Json::GetFlags( - jCar, - { - { "isPoweredRideWithUnrestrictedGravity", CAR_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY }, - { "hasNoUpstopWheels", CAR_ENTRY_FLAG_NO_UPSTOP_WHEELS }, - { "hasNoUpstopWheelsBobsleigh", CAR_ENTRY_FLAG_NO_UPSTOP_BOBSLEIGH }, - { "isMiniGolf", CAR_ENTRY_FLAG_MINI_GOLF }, - { "isReverserBogie", CAR_ENTRY_FLAG_REVERSER_BOGIE }, - { "isReverserPassengerCar", CAR_ENTRY_FLAG_REVERSER_PASSENGER_CAR }, - { "hasInvertedSpriteSet", CAR_ENTRY_FLAG_HAS_INVERTED_SPRITE_SET }, - { "hasDodgemInUseLights", CAR_ENTRY_FLAG_DODGEM_INUSE_LIGHTS }, - { "hasAdditionalColour2", CAR_ENTRY_FLAG_ENABLE_TERTIARY_COLOUR }, - { "recalculateSpriteBounds", CAR_ENTRY_FLAG_RECALCULATE_SPRITE_BOUNDS }, - { "overrideNumberOfVerticalFrames", CAR_ENTRY_FLAG_OVERRIDE_NUM_VERTICAL_FRAMES }, - { "spriteBoundsIncludeInvertedSet", CAR_ENTRY_FLAG_SPRITE_BOUNDS_INCLUDE_INVERTED_SET }, - { "hasAdditionalSpinningFrames", CAR_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES }, - { "isLift", CAR_ENTRY_FLAG_LIFT }, - { "hasAdditionalColour1", CAR_ENTRY_FLAG_ENABLE_TRIM_COLOUR }, - { "hasSwinging", CAR_ENTRY_FLAG_SWINGING }, - { "hasSpinning", CAR_ENTRY_FLAG_SPINNING }, - { "isPowered", CAR_ENTRY_FLAG_POWERED }, - { "hasScreamingRiders", CAR_ENTRY_FLAG_RIDERS_SCREAM }, - { "useSuspendedSwing", CAR_ENTRY_FLAG_SUSPENDED_SWING }, - { "useBoatHireCollisionDetection", CAR_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION }, - { "hasVehicleAnimation", CAR_ENTRY_FLAG_VEHICLE_ANIMATION }, - { "hasRiderAnimation", CAR_ENTRY_FLAG_RIDER_ANIMATION }, - { "useWoodenWildMouseSwing", CAR_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING }, - { "useSlideSwing", CAR_ENTRY_FLAG_SLIDE_SWING }, - { "isChairlift", CAR_ENTRY_FLAG_CHAIRLIFT }, - { "isWaterRide", CAR_ENTRY_FLAG_WATER_RIDE }, - { "isGoKart", CAR_ENTRY_FLAG_GO_KART }, - { "useDodgemCarPlacement", CAR_ENTRY_FLAG_DODGEM_CAR_PLACEMENT }, - - // Obsolete flag, only used on Boat Hire. Remaining usages have not yet been updated as of 2022-07-11. - { "VEHICLE_ENTRY_FLAG_11", CAR_ENTRY_FLAG_USE_16_ROTATION_FRAMES }, - }); - if (Json::GetBoolean(jCar["hasBaseColour"], true)) - car.flags |= CAR_ENTRY_FLAG_ENABLE_BODY_COLOUR; - - // legacy sprite groups - auto jFrames = jCar["frames"]; - if (jFrames.is_object()) + CarEntry RideObject::ReadJsonCar([[maybe_unused]] IReadObjectContext* context, json_t& jCar) { - uint16_t spriteFlags = Json::GetFlags( - jFrames, + Guard::Assert(jCar.is_object(), "RideObject::ReadJsonCar expects parameter jCar to be object"); + + CarEntry car = {}; + car.TabRotationMask = Json::GetNumber(jCar["rotationFrameMask"]); + car.spacing = Json::GetNumber(jCar["spacing"]); + car.car_mass = Json::GetNumber(jCar["mass"]); + car.tab_height = Json::GetNumber(jCar["tabOffset"]); + car.num_seats = Json::GetNumber(jCar["numSeats"]); + if (Json::GetBoolean(jCar["seatsInPairs"], true) && car.num_seats > 1) + { + car.num_seats |= kVehicleSeatPairFlag; + } + + car.sprite_width = Json::GetNumber(jCar["spriteWidth"]); + car.sprite_height_negative = Json::GetNumber(jCar["spriteHeightNegative"]); + car.sprite_height_positive = Json::GetNumber(jCar["spriteHeightPositive"]); + car.base_num_frames = Json::GetNumber(jCar["baseNumFrames"]); + car.NumCarImages = Json::GetNumber(jCar["numImages"]); + car.no_seating_rows = Json::GetNumber(jCar["numSeatRows"]); + car.spinning_inertia = Json::GetNumber(jCar["spinningInertia"]); + car.spinning_friction = Json::GetNumber(jCar["spinningFriction"]); + car.friction_sound_id = Json::GetEnum( + jCar["frictionSoundId"], OpenRCT2::Audio::SoundId::Null); + car.ReversedCarIndex = Json::GetNumber(jCar["logFlumeReverserVehicleType"]); + car.soundRange = Json::GetEnum(jCar["soundRange"], SoundRange::none); + car.double_sound_frequency = Json::GetNumber(jCar["doubleSoundFrequency"]); + car.powered_acceleration = Json::GetNumber(jCar["poweredAcceleration"]); + car.powered_max_speed = Json::GetNumber(jCar["poweredMaxSpeed"]); + car.PaintStyle = Json::GetNumber(jCar["carVisual"]); + car.effect_visual = Json::GetNumber(jCar["effectVisual"], 1); + car.draw_order = Json::GetNumber(jCar["drawOrder"]); + car.num_vertical_frames_override = Json::GetNumber(jCar["numVerticalFramesOverride"]); + + auto jAnimation = jCar["animation"]; + if (jAnimation.is_object()) + { + car.animation = GetAnimationTypeFromString(Json::GetString(jAnimation["animationType"])); + car.AnimationSpeed = Json::GetNumber(jAnimation["animationSpeed"]); + car.AnimationFrames = Json::GetNumber(jAnimation["animationFrames"]); + } + else + { + auto animationProperties = GetDefaultAnimationParameters(Json::GetNumber(jAnimation)); + car.animation = animationProperties.Alias; + car.AnimationSpeed = animationProperties.Speed; + car.AnimationFrames = animationProperties.NumFrames; + + if (!jCar["animationSpeed"].is_null()) + car.AnimationSpeed = Json::GetNumber(jCar["animationSpeed"]); + if (!jCar["animationFrames"].is_null()) + car.AnimationFrames = Json::GetNumber(jCar["animationFrames"]); + } + + auto jSteamTranslation = jCar["steamPosition"]; + if (jSteamTranslation.is_object()) + { + car.SteamEffect.Longitudinal = Json::GetNumber( + jSteamTranslation["longitudinal"], DefaultSteamSpawnPosition[0]); + car.SteamEffect.Vertical = Json::GetNumber(jSteamTranslation["vertical"], DefaultSteamSpawnPosition[1]); + } + else + { + car.SteamEffect.Longitudinal = DefaultSteamSpawnPosition[0]; + car.SteamEffect.Vertical = DefaultSteamSpawnPosition[1]; + } + + auto jLoadingPositions = jCar["loadingPositions"]; + if (jLoadingPositions.is_array()) + { + for (auto& jPos : jLoadingPositions) { - { "flat", CAR_SPRITE_FLAG_FLAT }, - { "gentleSlopes", CAR_SPRITE_FLAG_GENTLE_SLOPES }, - { "steepSlopes", CAR_SPRITE_FLAG_STEEP_SLOPES }, - { "verticalSlopes", CAR_SPRITE_FLAG_VERTICAL_SLOPES }, - { "diagonalSlopes", CAR_SPRITE_FLAG_DIAGONAL_SLOPES }, - { "flatBanked", CAR_SPRITE_FLAG_FLAT_BANKED }, - { "inlineTwists", CAR_SPRITE_FLAG_INLINE_TWISTS }, - { "flatToGentleSlopeBankedTransitions", CAR_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS }, - { "diagonalGentleSlopeBankedTransitions", CAR_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS }, - { "gentleSlopeBankedTransitions", CAR_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS }, - { "gentleSlopeBankedTurns", CAR_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS }, - { "flatToGentleSlopeWhileBankedTransitions", CAR_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS }, - { "corkscrews", CAR_SPRITE_FLAG_CORKSCREWS }, - { "restraintAnimation", CAR_SPRITE_FLAG_RESTRAINT_ANIMATION }, - { "curvedLiftHill", CAR_SPRITE_FLAG_CURVED_LIFT_HILL }, - { "VEHICLE_SPRITE_FLAG_15", CAR_SPRITE_FLAG_USE_4_ROTATION_FRAMES }, + car.peep_loading_positions.push_back(Json::GetNumber(jPos)); + } + } + else + { + auto jLoadingWaypoints = jCar["loadingWaypoints"]; + if (jLoadingWaypoints.is_array()) + { + car.flags |= CAR_ENTRY_FLAG_LOADING_WAYPOINTS; + car.peep_loading_waypoint_segments = Json::GetNumber(jCar["numSegments"]); + + for (auto& jRoute : jLoadingWaypoints) + { + if (jRoute.is_array()) + { + std::array entry; + + for (size_t j = 0; j < 3; ++j) + { + auto jWaypoint = jRoute[j]; + if (jWaypoint.is_array() && jWaypoint.size() >= 2) + { + int32_t x = Json::GetNumber(jWaypoint[0]); + int32_t y = Json::GetNumber(jWaypoint[1]); + entry[j] = { x, y }; + } + } + + car.peep_loading_waypoints.push_back(std::move(entry)); + } + } + } + } + + car.flags |= Json::GetFlags( + jCar, + { + { "isPoweredRideWithUnrestrictedGravity", CAR_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY }, + { "hasNoUpstopWheels", CAR_ENTRY_FLAG_NO_UPSTOP_WHEELS }, + { "hasNoUpstopWheelsBobsleigh", CAR_ENTRY_FLAG_NO_UPSTOP_BOBSLEIGH }, + { "isMiniGolf", CAR_ENTRY_FLAG_MINI_GOLF }, + { "isReverserBogie", CAR_ENTRY_FLAG_REVERSER_BOGIE }, + { "isReverserPassengerCar", CAR_ENTRY_FLAG_REVERSER_PASSENGER_CAR }, + { "hasInvertedSpriteSet", CAR_ENTRY_FLAG_HAS_INVERTED_SPRITE_SET }, + { "hasDodgemInUseLights", CAR_ENTRY_FLAG_DODGEM_INUSE_LIGHTS }, + { "hasAdditionalColour2", CAR_ENTRY_FLAG_ENABLE_TERTIARY_COLOUR }, + { "recalculateSpriteBounds", CAR_ENTRY_FLAG_RECALCULATE_SPRITE_BOUNDS }, + { "overrideNumberOfVerticalFrames", CAR_ENTRY_FLAG_OVERRIDE_NUM_VERTICAL_FRAMES }, + { "spriteBoundsIncludeInvertedSet", CAR_ENTRY_FLAG_SPRITE_BOUNDS_INCLUDE_INVERTED_SET }, + { "hasAdditionalSpinningFrames", CAR_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES }, + { "isLift", CAR_ENTRY_FLAG_LIFT }, + { "hasAdditionalColour1", CAR_ENTRY_FLAG_ENABLE_TRIM_COLOUR }, + { "hasSwinging", CAR_ENTRY_FLAG_SWINGING }, + { "hasSpinning", CAR_ENTRY_FLAG_SPINNING }, + { "isPowered", CAR_ENTRY_FLAG_POWERED }, + { "hasScreamingRiders", CAR_ENTRY_FLAG_RIDERS_SCREAM }, + { "useSuspendedSwing", CAR_ENTRY_FLAG_SUSPENDED_SWING }, + { "useBoatHireCollisionDetection", CAR_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION }, + { "hasVehicleAnimation", CAR_ENTRY_FLAG_VEHICLE_ANIMATION }, + { "hasRiderAnimation", CAR_ENTRY_FLAG_RIDER_ANIMATION }, + { "useWoodenWildMouseSwing", CAR_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING }, + { "useSlideSwing", CAR_ENTRY_FLAG_SLIDE_SWING }, + { "isChairlift", CAR_ENTRY_FLAG_CHAIRLIFT }, + { "isWaterRide", CAR_ENTRY_FLAG_WATER_RIDE }, + { "isGoKart", CAR_ENTRY_FLAG_GO_KART }, + { "useDodgemCarPlacement", CAR_ENTRY_FLAG_DODGEM_CAR_PLACEMENT }, + + // Obsolete flag, only used on Boat Hire. Remaining usages have not yet been updated as of 2022-07-11. + { "VEHICLE_ENTRY_FLAG_11", CAR_ENTRY_FLAG_USE_16_ROTATION_FRAMES }, }); - ReadLegacySpriteGroups(&car, spriteFlags); + if (Json::GetBoolean(jCar["hasBaseColour"], true)) + car.flags |= CAR_ENTRY_FLAG_ENABLE_BODY_COLOUR; + + // legacy sprite groups + auto jFrames = jCar["frames"]; + if (jFrames.is_object()) + { + uint16_t spriteFlags = Json::GetFlags( + jFrames, + { + { "flat", CAR_SPRITE_FLAG_FLAT }, + { "gentleSlopes", CAR_SPRITE_FLAG_GENTLE_SLOPES }, + { "steepSlopes", CAR_SPRITE_FLAG_STEEP_SLOPES }, + { "verticalSlopes", CAR_SPRITE_FLAG_VERTICAL_SLOPES }, + { "diagonalSlopes", CAR_SPRITE_FLAG_DIAGONAL_SLOPES }, + { "flatBanked", CAR_SPRITE_FLAG_FLAT_BANKED }, + { "inlineTwists", CAR_SPRITE_FLAG_INLINE_TWISTS }, + { "flatToGentleSlopeBankedTransitions", CAR_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "diagonalGentleSlopeBankedTransitions", CAR_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "gentleSlopeBankedTransitions", CAR_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS }, + { "gentleSlopeBankedTurns", CAR_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS }, + { "flatToGentleSlopeWhileBankedTransitions", + CAR_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS }, + { "corkscrews", CAR_SPRITE_FLAG_CORKSCREWS }, + { "restraintAnimation", CAR_SPRITE_FLAG_RESTRAINT_ANIMATION }, + { "curvedLiftHill", CAR_SPRITE_FLAG_CURVED_LIFT_HILL }, + { "VEHICLE_SPRITE_FLAG_15", CAR_SPRITE_FLAG_USE_4_ROTATION_FRAMES }, + }); + ReadLegacySpriteGroups(&car, spriteFlags); + return car; + } + + // OpenRCT2 sprite groups + auto jRotationCount = jCar["spriteGroups"]; + if (jRotationCount.is_object()) + { + for (uint8_t i = 0; i < EnumValue(SpriteGroupType::Count); i++) + { + auto numRotationFrames = Json::GetNumber(jRotationCount[SpriteGroupNames[i]], 0); + if (numRotationFrames != 0) + { + if (!std::has_single_bit(numRotationFrames)) + { + context->LogError(ObjectError::InvalidProperty, "spriteGroups values must be powers of 2"); + continue; + } + car.SpriteGroups[i].spritePrecision = PrecisionFromNumFrames(numRotationFrames); + } + } + } + return car; } - // OpenRCT2 sprite groups - auto jRotationCount = jCar["spriteGroups"]; - if (jRotationCount.is_object()) + VehicleColourPresetList RideObject::ReadJsonCarColours(json_t& jCarColours) { - for (uint8_t i = 0; i < EnumValue(SpriteGroupType::Count); i++) + Guard::Assert(jCarColours.is_array(), "RideObject::ReadJsonCarColours expects parameter jCarColours to be array"); + + // The JSON supports multiple configurations of per car colours, but + // the ride entry structure currently doesn't allow for it. Assume that + // a single configuration with multiple colour entries is per car scheme. + if (jCarColours.size() == 1) { - auto numRotationFrames = Json::GetNumber(jRotationCount[SpriteGroupNames[i]], 0); - if (numRotationFrames != 0) + auto firstElement = Json::AsArray(jCarColours[0]); + auto numColours = firstElement.size(); + if (numColours >= 2) { - if (!std::has_single_bit(numRotationFrames)) + // Read all colours from first config + auto config = ReadJsonColourConfiguration(firstElement); + VehicleColourPresetList list = {}; + list.count = 255; + std::copy_n(config.data(), std::min(numColours, 32), list.list); + return list; + } + } + + // Read first colour for each config + VehicleColourPresetList list = {}; + for (size_t index = 0; index < jCarColours.size(); index++) + { + auto config = ReadJsonColourConfiguration(jCarColours[index]); + if (config.size() >= 1) + { + list.list[index] = config[0]; + list.count++; + + if (list.count == 254) { - context->LogError(ObjectError::InvalidProperty, "spriteGroups values must be powers of 2"); - continue; + // Reached maximum number of configurations + break; } - car.SpriteGroups[i].spritePrecision = PrecisionFromNumFrames(numRotationFrames); } } + return list; } - return car; -} - -VehicleColourPresetList RideObject::ReadJsonCarColours(json_t& jCarColours) -{ - Guard::Assert(jCarColours.is_array(), "RideObject::ReadJsonCarColours expects parameter jCarColours to be array"); - - // The JSON supports multiple configurations of per car colours, but - // the ride entry structure currently doesn't allow for it. Assume that - // a single configuration with multiple colour entries is per car scheme. - if (jCarColours.size() == 1) + std::vector RideObject::ReadJsonColourConfiguration(json_t& jColourConfig) { - auto firstElement = Json::AsArray(jCarColours[0]); - auto numColours = firstElement.size(); - if (numColours >= 2) + std::vector config; + + for (auto& jColours : jColourConfig) { - // Read all colours from first config - auto config = ReadJsonColourConfiguration(firstElement); - VehicleColourPresetList list = {}; - list.count = 255; - std::copy_n(config.data(), std::min(numColours, 32), list.list); - return list; + VehicleColour carColour = {}; + + auto colours = Json::AsArray(jColours); + if (colours.size() >= 1) + { + carColour.Body = Colour::FromString(Json::GetString(colours[0])); + carColour.Trim = carColour.Body; + carColour.Tertiary = carColour.Body; + if (colours.size() >= 2) + { + carColour.Trim = Colour::FromString(Json::GetString(colours[1])); + } + if (colours.size() >= 3) + { + carColour.Tertiary = Colour::FromString(Json::GetString(colours[2])); + } + } + config.push_back(carColour); } + return config; } - // Read first colour for each config - VehicleColourPresetList list = {}; - for (size_t index = 0; index < jCarColours.size(); index++) + bool RideObject::isRideTypeShopOrFacility(ride_type_t rideType) { - auto config = ReadJsonColourConfiguration(jCarColours[index]); - if (config.size() >= 1) + return GetRideTypeDescriptor(rideType).HasFlag(RtdFlag::isShopOrFacility); + } + + ride_type_t RideObject::ParseRideType(const std::string& s) + { + auto result = std::find_if( + std::begin(kRideTypeDescriptors), std::end(kRideTypeDescriptors), [s](const auto& rtd) { return rtd.Name == s; }); + if (result == std::end(kRideTypeDescriptors)) + return kRideTypeNull; + else + return std::distance(std::begin(kRideTypeDescriptors), result); + } + + static const EnumMap RideCategoryLookupTable{ + { "transport", RideCategory::transport }, + { "gentle", RideCategory::gentle }, + { "rollercoaster", RideCategory::rollerCoaster }, + { "thrill", RideCategory::thrill }, + { "water", RideCategory::water }, + { "stall", RideCategory::shop }, + }; + + RideCategory RideObject::ParseRideCategory(const std::string& s) + { + auto result = RideCategoryLookupTable.find(s); + return (result != RideCategoryLookupTable.end()) ? result->second : RideCategory::transport; + } + + static const EnumMap ShopItemLookupTable{ + { "burger", ShopItem::Burger }, + { "chips", ShopItem::Chips }, + { "ice_cream", ShopItem::IceCream }, + { "candyfloss", ShopItem::Candyfloss }, + { "pizza", ShopItem::Pizza }, + { "popcorn", ShopItem::Popcorn }, + { "hot_dog", ShopItem::HotDog }, + { "tentacle", ShopItem::Tentacle }, + { "toffee_apple", ShopItem::ToffeeApple }, + { "doughnut", ShopItem::Doughnut }, + { "chicken", ShopItem::Chicken }, + { "pretzel", ShopItem::Pretzel }, + { "funnel_cake", ShopItem::FunnelCake }, + { "beef_noodles", ShopItem::BeefNoodles }, + { "fried_rice_noodles", ShopItem::FriedRiceNoodles }, + { "wonton_soup", ShopItem::WontonSoup }, + { "meatball_soup", ShopItem::MeatballSoup }, + { "sub_sandwich", ShopItem::SubSandwich }, + { "cookie", ShopItem::Cookie }, + { "roast_sausage", ShopItem::RoastSausage }, + { "drink", ShopItem::Drink }, + { "coffee", ShopItem::Coffee }, + { "lemonade", ShopItem::Lemonade }, + { "chocolate", ShopItem::Chocolate }, + { "iced_tea", ShopItem::IcedTea }, + { "fruit_juice", ShopItem::FruitJuice }, + { "soybean_milk", ShopItem::SoybeanMilk }, + { "sujeonggwa", ShopItem::Sujeonggwa }, + { "balloon", ShopItem::Balloon }, + { "toy", ShopItem::Toy }, + { "map", ShopItem::Map }, + { "photo", ShopItem::Photo }, + { "umbrella", ShopItem::Umbrella }, + { "voucher", ShopItem::Voucher }, + { "hat", ShopItem::Hat }, + { "tshirt", ShopItem::TShirt }, + { "sunglasses", ShopItem::Sunglasses }, + }; + + ShopItem RideObject::ParseShopItem(const std::string& s) + { + auto result = ShopItemLookupTable.find(s); + return (result != ShopItemLookupTable.end()) ? result->second : ShopItem::None; + } + + // Converts legacy sprite groups into OpenRCT2 sprite groups + void RideObject::ReadLegacySpriteGroups(CarEntry* vehicle, uint16_t spriteGroups) + { + auto baseSpritePrecision = SpritePrecision::Sprites32; + if (vehicle->flags & CAR_ENTRY_FLAG_USE_16_ROTATION_FRAMES) + baseSpritePrecision = SpritePrecision::Sprites16; + if (vehicle->flags & CAR_SPRITE_FLAG_USE_4_ROTATION_FRAMES) + baseSpritePrecision = SpritePrecision::Sprites4; + + if (spriteGroups & CAR_SPRITE_FLAG_FLAT) { - list.list[index] = config[0]; - list.count++; - - if (list.count == 254) - { - // Reached maximum number of configurations - break; - } + vehicle->SpriteGroups[EnumValue(SpriteGroupType::SlopeFlat)].spritePrecision = baseSpritePrecision; } - } - return list; -} - -std::vector RideObject::ReadJsonColourConfiguration(json_t& jColourConfig) -{ - std::vector config; - - for (auto& jColours : jColourConfig) - { - VehicleColour carColour = {}; - - auto colours = Json::AsArray(jColours); - if (colours.size() >= 1) + if (spriteGroups & CAR_SPRITE_FLAG_GENTLE_SLOPES) { - carColour.Body = Colour::FromString(Json::GetString(colours[0])); - carColour.Trim = carColour.Body; - carColour.Tertiary = carColour.Body; - if (colours.size() >= 2) - { - carColour.Trim = Colour::FromString(Json::GetString(colours[1])); - } - if (colours.size() >= 3) - { - carColour.Tertiary = Colour::FromString(Json::GetString(colours[2])); - } + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes12)].spritePrecision = SpritePrecision::Sprites4; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes25)].spritePrecision = baseSpritePrecision; + if (vehicle->flags & CAR_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES) + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes25)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_STEEP_SLOPES) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes42)].spritePrecision = SpritePrecision::Sprites8; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes60)].spritePrecision = baseSpritePrecision; + } + if (spriteGroups & CAR_SPRITE_FLAG_VERTICAL_SLOPES) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes75)].spritePrecision = SpritePrecision::Sprites4; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes90)].spritePrecision = baseSpritePrecision; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::SlopesLoop)].spritePrecision = SpritePrecision::Sprites4; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::SlopeInverted)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_DIAGONAL_SLOPES) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes8)].spritePrecision = SpritePrecision::Sprites4; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes16)].spritePrecision = SpritePrecision::Sprites4; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes50)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_FLAT_BANKED) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::FlatBanked22)].spritePrecision = SpritePrecision::Sprites8; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::FlatBanked45)].spritePrecision = baseSpritePrecision; + } + if (spriteGroups & CAR_SPRITE_FLAG_INLINE_TWISTS) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::FlatBanked67)].spritePrecision = SpritePrecision::Sprites4; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::FlatBanked90)].spritePrecision = SpritePrecision::Sprites4; + vehicle->SpriteGroups[EnumValue(SpriteGroupType::InlineTwists)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes12Banked22)].spritePrecision = baseSpritePrecision; + } + if (spriteGroups & CAR_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes8Banked22)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes25Banked22)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes25Banked45)].spritePrecision = baseSpritePrecision; + } + if (spriteGroups & CAR_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes12Banked45)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_CORKSCREWS) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::Corkscrews)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_RESTRAINT_ANIMATION) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::RestraintAnimation)].spritePrecision = SpritePrecision::Sprites4; + } + if (spriteGroups & CAR_SPRITE_FLAG_CURVED_LIFT_HILL) + { + vehicle->SpriteGroups[EnumValue(SpriteGroupType::CurvedLiftHillUp)].spritePrecision = baseSpritePrecision; } - config.push_back(carColour); } - return config; -} -bool RideObject::isRideTypeShopOrFacility(ride_type_t rideType) -{ - return GetRideTypeDescriptor(rideType).HasFlag(RtdFlag::isShopOrFacility); -} - -ride_type_t RideObject::ParseRideType(const std::string& s) -{ - auto result = std::find_if( - std::begin(kRideTypeDescriptors), std::end(kRideTypeDescriptors), [s](const auto& rtd) { return rtd.Name == s; }); - if (result == std::end(kRideTypeDescriptors)) - return kRideTypeNull; - else - return std::distance(std::begin(kRideTypeDescriptors), result); -} - -static const EnumMap RideCategoryLookupTable{ - { "transport", RideCategory::transport }, - { "gentle", RideCategory::gentle }, - { "rollercoaster", RideCategory::rollerCoaster }, - { "thrill", RideCategory::thrill }, - { "water", RideCategory::water }, - { "stall", RideCategory::shop }, -}; - -RideCategory RideObject::ParseRideCategory(const std::string& s) -{ - auto result = RideCategoryLookupTable.find(s); - return (result != RideCategoryLookupTable.end()) ? result->second : RideCategory::transport; -} - -static const EnumMap ShopItemLookupTable{ - { "burger", ShopItem::Burger }, - { "chips", ShopItem::Chips }, - { "ice_cream", ShopItem::IceCream }, - { "candyfloss", ShopItem::Candyfloss }, - { "pizza", ShopItem::Pizza }, - { "popcorn", ShopItem::Popcorn }, - { "hot_dog", ShopItem::HotDog }, - { "tentacle", ShopItem::Tentacle }, - { "toffee_apple", ShopItem::ToffeeApple }, - { "doughnut", ShopItem::Doughnut }, - { "chicken", ShopItem::Chicken }, - { "pretzel", ShopItem::Pretzel }, - { "funnel_cake", ShopItem::FunnelCake }, - { "beef_noodles", ShopItem::BeefNoodles }, - { "fried_rice_noodles", ShopItem::FriedRiceNoodles }, - { "wonton_soup", ShopItem::WontonSoup }, - { "meatball_soup", ShopItem::MeatballSoup }, - { "sub_sandwich", ShopItem::SubSandwich }, - { "cookie", ShopItem::Cookie }, - { "roast_sausage", ShopItem::RoastSausage }, - { "drink", ShopItem::Drink }, - { "coffee", ShopItem::Coffee }, - { "lemonade", ShopItem::Lemonade }, - { "chocolate", ShopItem::Chocolate }, - { "iced_tea", ShopItem::IcedTea }, - { "fruit_juice", ShopItem::FruitJuice }, - { "soybean_milk", ShopItem::SoybeanMilk }, - { "sujeonggwa", ShopItem::Sujeonggwa }, - { "balloon", ShopItem::Balloon }, - { "toy", ShopItem::Toy }, - { "map", ShopItem::Map }, - { "photo", ShopItem::Photo }, - { "umbrella", ShopItem::Umbrella }, - { "voucher", ShopItem::Voucher }, - { "hat", ShopItem::Hat }, - { "tshirt", ShopItem::TShirt }, - { "sunglasses", ShopItem::Sunglasses }, -}; - -ShopItem RideObject::ParseShopItem(const std::string& s) -{ - auto result = ShopItemLookupTable.find(s); - return (result != ShopItemLookupTable.end()) ? result->second : ShopItem::None; -} - -// Converts legacy sprite groups into OpenRCT2 sprite groups -void RideObject::ReadLegacySpriteGroups(CarEntry* vehicle, uint16_t spriteGroups) -{ - auto baseSpritePrecision = SpritePrecision::Sprites32; - if (vehicle->flags & CAR_ENTRY_FLAG_USE_16_ROTATION_FRAMES) - baseSpritePrecision = SpritePrecision::Sprites16; - if (vehicle->flags & CAR_SPRITE_FLAG_USE_4_ROTATION_FRAMES) - baseSpritePrecision = SpritePrecision::Sprites4; - - if (spriteGroups & CAR_SPRITE_FLAG_FLAT) + uint8_t RideObject::GetDefaultClearance() const { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::SlopeFlat)].spritePrecision = baseSpritePrecision; + auto rideType = _legacyType.GetFirstNonNullRideType(); + const auto& rtd = GetRideTypeDescriptor(rideType); + return rtd.Heights.ClearanceHeight; } - if (spriteGroups & CAR_SPRITE_FLAG_GENTLE_SLOPES) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes12)].spritePrecision = SpritePrecision::Sprites4; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes25)].spritePrecision = baseSpritePrecision; - if (vehicle->flags & CAR_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES) - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes25)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_STEEP_SLOPES) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes42)].spritePrecision = SpritePrecision::Sprites8; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes60)].spritePrecision = baseSpritePrecision; - } - if (spriteGroups & CAR_SPRITE_FLAG_VERTICAL_SLOPES) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes75)].spritePrecision = SpritePrecision::Sprites4; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes90)].spritePrecision = baseSpritePrecision; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::SlopesLoop)].spritePrecision = SpritePrecision::Sprites4; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::SlopeInverted)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_DIAGONAL_SLOPES) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes8)].spritePrecision = SpritePrecision::Sprites4; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes16)].spritePrecision = SpritePrecision::Sprites4; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes50)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_FLAT_BANKED) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::FlatBanked22)].spritePrecision = SpritePrecision::Sprites8; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::FlatBanked45)].spritePrecision = baseSpritePrecision; - } - if (spriteGroups & CAR_SPRITE_FLAG_INLINE_TWISTS) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::FlatBanked67)].spritePrecision = SpritePrecision::Sprites4; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::FlatBanked90)].spritePrecision = SpritePrecision::Sprites4; - vehicle->SpriteGroups[EnumValue(SpriteGroupType::InlineTwists)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes12Banked22)].spritePrecision = baseSpritePrecision; - } - if (spriteGroups & CAR_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes8Banked22)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes25Banked22)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes25Banked45)].spritePrecision = baseSpritePrecision; - } - if (spriteGroups & CAR_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Slopes12Banked45)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_CORKSCREWS) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::Corkscrews)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_RESTRAINT_ANIMATION) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::RestraintAnimation)].spritePrecision = SpritePrecision::Sprites4; - } - if (spriteGroups & CAR_SPRITE_FLAG_CURVED_LIFT_HILL) - { - vehicle->SpriteGroups[EnumValue(SpriteGroupType::CurvedLiftHillUp)].spritePrecision = baseSpritePrecision; - } -} - -uint8_t RideObject::GetDefaultClearance() const -{ - auto rideType = _legacyType.GetFirstNonNullRideType(); - const auto& rtd = GetRideTypeDescriptor(rideType); - return rtd.Heights.ClearanceHeight; -} +} // namespace OpenRCT2 diff --git a/src/openrct2/object/RideObject.h b/src/openrct2/object/RideObject.h index c610b743d5..1f880513be 100644 --- a/src/openrct2/object/RideObject.h +++ b/src/openrct2/object/RideObject.h @@ -19,58 +19,61 @@ enum class RideCategory : uint8_t; -class RideObject final : public Object +namespace OpenRCT2 { -private: - RideObjectEntry _legacyType = {}; - VehicleColourPresetList _presetColours = {}; - std::vector _peepLoadingPositions[OpenRCT2::RCT2::ObjectLimits::kMaxCarTypesPerRideEntry]; - std::vector> _peepLoadingWaypoints[OpenRCT2::RCT2::ObjectLimits::kMaxCarTypesPerRideEntry]; - -public: - static constexpr ObjectType kObjectType = ObjectType::ride; - - void* GetLegacyData() override + class RideObject final : public Object { - return &_legacyType; - } - const RideObjectEntry& GetEntry() const - { - return _legacyType; - } + private: + RideObjectEntry _legacyType = {}; + VehicleColourPresetList _presetColours = {}; + std::vector _peepLoadingPositions[OpenRCT2::RCT2::ObjectLimits::kMaxCarTypesPerRideEntry]; + std::vector> _peepLoadingWaypoints[OpenRCT2::RCT2::ObjectLimits::kMaxCarTypesPerRideEntry]; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void Load() override; - void Unload() override; + public: + static constexpr ObjectType kObjectType = ObjectType::ride; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void* GetLegacyData() override + { + return &_legacyType; + } + const RideObjectEntry& GetEntry() const + { + return _legacyType; + } - std::string GetDescription() const; - std::string GetCapacity() const; - ImageIndex GetPreviewImage(ride_type_t type); + void ReadJson(IReadObjectContext* context, json_t& root) override; + void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; + void Load() override; + void Unload() override; - void SetRepositoryItem(ObjectRepositoryItem* item) const override; + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; - static ride_type_t ParseRideType(const std::string& s); + std::string GetDescription() const; + std::string GetCapacity() const; + ImageIndex GetPreviewImage(ride_type_t type); -private: - void ReadLegacyCar(IReadObjectContext* context, OpenRCT2::IStream* stream, CarEntry* car); + void SetRepositoryItem(ObjectRepositoryItem* item) const override; - void ReadJsonVehicleInfo(IReadObjectContext* context, json_t& properties); - std::vector ReadJsonCars([[maybe_unused]] IReadObjectContext* context, json_t& jCars); - CarEntry ReadJsonCar([[maybe_unused]] IReadObjectContext* context, json_t& jCar); - VehicleColourPresetList ReadJsonCarColours(json_t& jCarColours); - std::vector ReadJsonColourConfiguration(json_t& jColourConfig); + static ride_type_t ParseRideType(const std::string& s); - static uint8_t CalculateNumVerticalFrames(const CarEntry& carEntry); - static uint8_t CalculateNumHorizontalFrames(const CarEntry& carEntry); + private: + void ReadLegacyCar(IReadObjectContext* context, OpenRCT2::IStream* stream, CarEntry* car); - static bool isRideTypeShopOrFacility(ride_type_t rideType); - static RideCategory ParseRideCategory(const std::string& s); - static ShopItem ParseShopItem(const std::string& s); - static colour_t ParseColour(const std::string& s); + void ReadJsonVehicleInfo(IReadObjectContext* context, json_t& properties); + std::vector ReadJsonCars([[maybe_unused]] IReadObjectContext* context, json_t& jCars); + CarEntry ReadJsonCar([[maybe_unused]] IReadObjectContext* context, json_t& jCar); + VehicleColourPresetList ReadJsonCarColours(json_t& jCarColours); + std::vector ReadJsonColourConfiguration(json_t& jColourConfig); - void ReadLegacySpriteGroups(CarEntry* vehicle, uint16_t spriteGroups); - uint8_t GetDefaultClearance() const; -}; + static uint8_t CalculateNumVerticalFrames(const CarEntry& carEntry); + static uint8_t CalculateNumHorizontalFrames(const CarEntry& carEntry); + + static bool isRideTypeShopOrFacility(ride_type_t rideType); + static RideCategory ParseRideCategory(const std::string& s); + static ShopItem ParseShopItem(const std::string& s); + static colour_t ParseColour(const std::string& s); + + void ReadLegacySpriteGroups(CarEntry* vehicle, uint16_t spriteGroups); + uint8_t GetDefaultClearance() const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ScenarioMetaObject.cpp b/src/openrct2/object/ScenarioMetaObject.cpp index 816ed18614..fd483c313d 100644 --- a/src/openrct2/object/ScenarioMetaObject.cpp +++ b/src/openrct2/object/ScenarioMetaObject.cpp @@ -14,73 +14,74 @@ #include "../core/Guard.hpp" #include "../core/Json.hpp" -using namespace OpenRCT2; - -void ScenarioMetaObject::Load() +namespace OpenRCT2 { - auto numImages = GetImageTable().GetCount(); - if (numImages == 0) - return; + void ScenarioMetaObject::Load() + { + auto numImages = GetImageTable().GetCount(); + if (numImages == 0) + return; - _imageOffsetId = LoadImages(); -} + _imageOffsetId = LoadImages(); + } -void ScenarioMetaObject::Unload() -{ - UnloadImages(); -} + void ScenarioMetaObject::Unload() + { + UnloadImages(); + } -void ScenarioMetaObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "ScenarioMetaObject::ReadJson expects parameter root to be an object"); - PopulateTablesFromJson(context, root); -} + void ScenarioMetaObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "ScenarioMetaObject::ReadJson expects parameter root to be an object"); + PopulateTablesFromJson(context, root); + } -std::string ScenarioMetaObject::GetScenarioName() -{ - return GetStringTable().GetString(ObjectStringID::NAME); // SCENARIO_NAME -} + std::string ScenarioMetaObject::GetScenarioName() + { + return GetStringTable().GetString(ObjectStringID::NAME); // SCENARIO_NAME + } -std::string ScenarioMetaObject::GetParkName() -{ - return GetStringTable().GetString(ObjectStringID::PARK_NAME); -} + std::string ScenarioMetaObject::GetParkName() + { + return GetStringTable().GetString(ObjectStringID::PARK_NAME); + } -std::string ScenarioMetaObject::GetScenarioDetails() -{ - return GetStringTable().GetString(ObjectStringID::SCENARIO_DETAILS); -} + std::string ScenarioMetaObject::GetScenarioDetails() + { + return GetStringTable().GetString(ObjectStringID::SCENARIO_DETAILS); + } -PreviewImage ScenarioMetaObject::GetMiniMapImage() const -{ - PreviewImage preview{}; - preview.type = PreviewImageType::miniMap; + PreviewImage ScenarioMetaObject::GetMiniMapImage() const + { + PreviewImage preview{}; + preview.type = PreviewImageType::miniMap; + + auto* g1 = GfxGetG1Element(_imageOffsetId); + if (g1 == nullptr) + return preview; + + preview.width = g1->width; + preview.height = g1->height; + + std::copy_n(g1->offset, g1->width * g1->height, preview.pixels); - auto* g1 = GfxGetG1Element(_imageOffsetId); - if (g1 == nullptr) return preview; + } - preview.width = g1->width; - preview.height = g1->height; + PreviewImage ScenarioMetaObject::GetPreviewImage() const + { + PreviewImage preview{}; + preview.type = PreviewImageType::screenshot; - std::copy_n(g1->offset, g1->width * g1->height, preview.pixels); + auto* g1 = GfxGetG1Element(_imageOffsetId + 1); + if (g1 == nullptr) + return preview; - return preview; -} + preview.width = g1->width; + preview.height = g1->height; -PreviewImage ScenarioMetaObject::GetPreviewImage() const -{ - PreviewImage preview{}; - preview.type = PreviewImageType::screenshot; + std::copy_n(g1->offset, g1->width * g1->height, preview.pixels); - auto* g1 = GfxGetG1Element(_imageOffsetId + 1); - if (g1 == nullptr) return preview; - - preview.width = g1->width; - preview.height = g1->height; - - std::copy_n(g1->offset, g1->width * g1->height, preview.pixels); - - return preview; -} + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/ScenarioMetaObject.h b/src/openrct2/object/ScenarioMetaObject.h index 8a568703b4..4eb734f03b 100644 --- a/src/openrct2/object/ScenarioMetaObject.h +++ b/src/openrct2/object/ScenarioMetaObject.h @@ -15,22 +15,25 @@ #include -class ScenarioMetaObject final : public Object +namespace OpenRCT2 { -private: - ImageIndex _imageOffsetId; + class ScenarioMetaObject final : public Object + { + private: + ImageIndex _imageOffsetId; -public: - static constexpr ObjectType kObjectType = ObjectType::scenarioMeta; + public: + static constexpr ObjectType kObjectType = ObjectType::scenarioMeta; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; - std::string GetScenarioName(); - std::string GetParkName(); - std::string GetScenarioDetails(); + std::string GetScenarioName(); + std::string GetParkName(); + std::string GetScenarioDetails(); - OpenRCT2::PreviewImage GetMiniMapImage() const; - OpenRCT2::PreviewImage GetPreviewImage() const; -}; + PreviewImage GetMiniMapImage() const; + PreviewImage GetPreviewImage() const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/SceneryGroupEntry.h b/src/openrct2/object/SceneryGroupEntry.h index 4d74555963..bbaef394d5 100644 --- a/src/openrct2/object/SceneryGroupEntry.h +++ b/src/openrct2/object/SceneryGroupEntry.h @@ -15,13 +15,16 @@ #include -struct SceneryGroupEntry +namespace OpenRCT2 { - static constexpr auto kObjectType = ObjectType::sceneryGroup; + struct SceneryGroupEntry + { + static constexpr auto kObjectType = ObjectType::sceneryGroup; - StringId name; - uint32_t image; - std::vector SceneryEntries; - uint8_t priority; - uint32_t entertainer_costumes; -}; + StringId name; + uint32_t image; + std::vector SceneryEntries; + uint8_t priority; + uint32_t entertainer_costumes; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/SceneryGroupObject.cpp b/src/openrct2/object/SceneryGroupObject.cpp index bf3f1b80b4..1ccfeb56da 100644 --- a/src/openrct2/object/SceneryGroupObject.cpp +++ b/src/openrct2/object/SceneryGroupObject.cpp @@ -26,186 +26,187 @@ #include -using namespace OpenRCT2; - -// Example entry: "$DAT:09F55406|00STBEN " -// 5 for $DAT:, 8 for the checksum, 1 for the vertical bar, 8 for the .DAT name. -static constexpr uint8_t kDatEntryPrefixLength = 5; -static constexpr uint8_t kDatEntryFlagsLength = 8; -static constexpr uint8_t kDatEntrySeparatorLength = 1; -static constexpr uint8_t kDatEntryLength = kDatEntryPrefixLength + kDatEntryFlagsLength + kDatEntrySeparatorLength - + kDatNameLength; -static constexpr uint8_t kDatEntryFlagsStart = kDatEntryPrefixLength; -static constexpr uint8_t kDatEntryNameStart = kDatEntryPrefixLength + kDatEntryFlagsLength + kDatEntrySeparatorLength; - -void SceneryGroupObject::ReadLegacy(IReadObjectContext* context, IStream* stream) +namespace OpenRCT2 { - stream->Seek(6, STREAM_SEEK_CURRENT); - stream->Seek(0x80 * 2, STREAM_SEEK_CURRENT); - stream->Seek(1, STREAM_SEEK_CURRENT); // entry_count - stream->Seek(1, STREAM_SEEK_CURRENT); // Pad107; - _legacyType.priority = stream->ReadValue(); - stream->Seek(1, STREAM_SEEK_CURRENT); // Pad109; - _legacyType.entertainer_costumes = stream->ReadValue(); + // Example entry: "$DAT:09F55406|00STBEN " + // 5 for $DAT:, 8 for the checksum, 1 for the vertical bar, 8 for the .DAT name. + static constexpr uint8_t kDatEntryPrefixLength = 5; + static constexpr uint8_t kDatEntryFlagsLength = 8; + static constexpr uint8_t kDatEntrySeparatorLength = 1; + static constexpr uint8_t kDatEntryLength = kDatEntryPrefixLength + kDatEntryFlagsLength + kDatEntrySeparatorLength + + kDatNameLength; + static constexpr uint8_t kDatEntryFlagsStart = kDatEntryPrefixLength; + static constexpr uint8_t kDatEntryNameStart = kDatEntryPrefixLength + kDatEntryFlagsLength + kDatEntrySeparatorLength; - GetStringTable().Read(context, stream, ObjectStringID::NAME); - _items = ReadItems(stream); - GetImageTable().Read(context, stream); -} - -void SceneryGroupObject::Load() -{ - GetStringTable().Sort(); - _legacyType.name = LanguageAllocateObjectString(GetName()); - _legacyType.image = LoadImages(); - _legacyType.SceneryEntries.clear(); -} - -void SceneryGroupObject::Unload() -{ - LanguageFreeObjectString(_legacyType.name); - UnloadImages(); - - _legacyType.name = 0; - _legacyType.image = 0; -} - -void SceneryGroupObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; - - const auto imageId = ImageId(_legacyType.image + 1, COLOUR_DARK_GREEN); - GfxDrawSprite(rt, imageId, screenCoords - ScreenCoordsXY{ 15, 14 }); -} - -static std::optional GetSceneryType(const ObjectType type) -{ - switch (type) + void SceneryGroupObject::ReadLegacy(IReadObjectContext* context, IStream* stream) { - case ObjectType::smallScenery: - return SCENERY_TYPE_SMALL; - case ObjectType::largeScenery: - return SCENERY_TYPE_LARGE; - case ObjectType::walls: - return SCENERY_TYPE_WALL; - case ObjectType::banners: - return SCENERY_TYPE_BANNER; - case ObjectType::pathAdditions: - return SCENERY_TYPE_PATH_ITEM; - default: - return std::nullopt; + stream->Seek(6, STREAM_SEEK_CURRENT); + stream->Seek(0x80 * 2, STREAM_SEEK_CURRENT); + stream->Seek(1, STREAM_SEEK_CURRENT); // entry_count + stream->Seek(1, STREAM_SEEK_CURRENT); // Pad107; + _legacyType.priority = stream->ReadValue(); + stream->Seek(1, STREAM_SEEK_CURRENT); // Pad109; + _legacyType.entertainer_costumes = stream->ReadValue(); + + GetStringTable().Read(context, stream, ObjectStringID::NAME); + _items = ReadItems(stream); + GetImageTable().Read(context, stream); } -} -void SceneryGroupObject::UpdateEntryIndexes() -{ - auto context = GetContext(); - auto& objectRepository = context->GetObjectRepository(); - auto& objectManager = context->GetObjectManager(); - - _legacyType.SceneryEntries.clear(); - for (const auto& objectEntry : _items) + void SceneryGroupObject::Load() { - auto ori = objectRepository.FindObject(objectEntry); - if (ori == nullptr) - continue; - if (ori->LoadedObject == nullptr) - continue; + GetStringTable().Sort(); + _legacyType.name = LanguageAllocateObjectString(GetName()); + _legacyType.image = LoadImages(); + _legacyType.SceneryEntries.clear(); + } - auto entryIndex = objectManager.GetLoadedObjectEntryIndex(ori->LoadedObject.get()); - if (entryIndex == kObjectEntryIndexNull) - { - // Some parks have manually deleted objects from the save so they might not be loaded - // silently remove the object from the SceneryGroupObject - continue; - } + void SceneryGroupObject::Unload() + { + LanguageFreeObjectString(_legacyType.name); + UnloadImages(); - auto sceneryType = GetSceneryType(ori->Type); - if (sceneryType.has_value()) + _legacyType.name = 0; + _legacyType.image = 0; + } + + void SceneryGroupObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; + + const auto imageId = ImageId(_legacyType.image + 1, COLOUR_DARK_GREEN); + GfxDrawSprite(rt, imageId, screenCoords - ScreenCoordsXY{ 15, 14 }); + } + + static std::optional GetSceneryType(const ObjectType type) + { + switch (type) { - _legacyType.SceneryEntries.push_back({ sceneryType.value(), entryIndex }); + case ObjectType::smallScenery: + return SCENERY_TYPE_SMALL; + case ObjectType::largeScenery: + return SCENERY_TYPE_LARGE; + case ObjectType::walls: + return SCENERY_TYPE_WALL; + case ObjectType::banners: + return SCENERY_TYPE_BANNER; + case ObjectType::pathAdditions: + return SCENERY_TYPE_PATH_ITEM; + default: + return std::nullopt; } } -} -void SceneryGroupObject::SetRepositoryItem(ObjectRepositoryItem* item) const -{ - item->SceneryGroupInfo.Entries = _items; -} - -std::vector SceneryGroupObject::ReadItems(IStream* stream) -{ - auto items = std::vector(); - while (stream->ReadValue() != 0xFF) + void SceneryGroupObject::UpdateEntryIndexes() { - stream->Seek(-1, STREAM_SEEK_CURRENT); - auto entry = stream->ReadValue(); - items.emplace_back(entry); - } - return items; -} + auto context = GetContext(); + auto& objectRepository = context->GetObjectRepository(); + auto& objectManager = context->GetObjectManager(); -void SceneryGroupObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "SceneryGroupObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - - if (properties.is_object()) - { - _legacyType.priority = Json::GetNumber(properties["priority"], 40); - _legacyType.entertainer_costumes = 0; - - _items = ReadJsonEntries(context, properties["entries"]); - } - - PopulateTablesFromJson(context, root); -} - -std::vector SceneryGroupObject::ReadJsonEntries(IReadObjectContext* context, json_t& jEntries) -{ - std::vector entries; - - for (const auto& jEntry : jEntries) - { - auto entryName = Json::GetString(jEntry); - if (String::startsWith(entryName, "$DAT:")) + _legacyType.SceneryEntries.clear(); + for (const auto& objectEntry : _items) { - if (entryName.length() != kDatEntryLength) + auto ori = objectRepository.FindObject(objectEntry); + if (ori == nullptr) + continue; + if (ori->LoadedObject == nullptr) + continue; + + auto entryIndex = objectManager.GetLoadedObjectEntryIndex(ori->LoadedObject.get()); + if (entryIndex == kObjectEntryIndexNull) { - std::string errorMessage = "Malformed DAT entry in scenery group: " + entryName; - context->LogError(ObjectError::InvalidProperty, errorMessage.c_str()); + // Some parks have manually deleted objects from the save so they might not be loaded + // silently remove the object from the SceneryGroupObject continue; } - try + auto sceneryType = GetSceneryType(ori->Type); + if (sceneryType.has_value()) { - RCTObjectEntry entry = {}; - entry.flags = std::stoul(entryName.substr(kDatEntryFlagsStart, kDatEntryFlagsLength), nullptr, 16); - std::memcpy(entry.name, entryName.c_str() + kDatEntryNameStart, kDatNameLength); - entry.checksum = 0; - entries.emplace_back(entry); + _legacyType.SceneryEntries.push_back({ sceneryType.value(), entryIndex }); } - catch (std::invalid_argument&) - { - std::string errorMessage = "Malformed flags in DAT entry in scenery group: " + entryName; - context->LogError(ObjectError::InvalidProperty, errorMessage.c_str()); - } - } - else - { - entries.emplace_back(entryName); } } - return entries; -} -uint16_t SceneryGroupObject::GetNumIncludedObjects() const -{ - return static_cast(_items.size()); -} + void SceneryGroupObject::SetRepositoryItem(ObjectRepositoryItem* item) const + { + item->SceneryGroupInfo.Entries = _items; + } -const std::vector& SceneryGroupObject::GetItems() const -{ - return _items; -} + std::vector SceneryGroupObject::ReadItems(IStream* stream) + { + auto items = std::vector(); + while (stream->ReadValue() != 0xFF) + { + stream->Seek(-1, STREAM_SEEK_CURRENT); + auto entry = stream->ReadValue(); + items.emplace_back(entry); + } + return items; + } + + void SceneryGroupObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "SceneryGroupObject::ReadJson expects parameter root to be object"); + + auto properties = root["properties"]; + + if (properties.is_object()) + { + _legacyType.priority = Json::GetNumber(properties["priority"], 40); + _legacyType.entertainer_costumes = 0; + + _items = ReadJsonEntries(context, properties["entries"]); + } + + PopulateTablesFromJson(context, root); + } + + std::vector SceneryGroupObject::ReadJsonEntries(IReadObjectContext* context, json_t& jEntries) + { + std::vector entries; + + for (const auto& jEntry : jEntries) + { + auto entryName = Json::GetString(jEntry); + if (String::startsWith(entryName, "$DAT:")) + { + if (entryName.length() != kDatEntryLength) + { + std::string errorMessage = "Malformed DAT entry in scenery group: " + entryName; + context->LogError(ObjectError::InvalidProperty, errorMessage.c_str()); + continue; + } + + try + { + RCTObjectEntry entry = {}; + entry.flags = std::stoul(entryName.substr(kDatEntryFlagsStart, kDatEntryFlagsLength), nullptr, 16); + std::memcpy(entry.name, entryName.c_str() + kDatEntryNameStart, kDatNameLength); + entry.checksum = 0; + entries.emplace_back(entry); + } + catch (std::invalid_argument&) + { + std::string errorMessage = "Malformed flags in DAT entry in scenery group: " + entryName; + context->LogError(ObjectError::InvalidProperty, errorMessage.c_str()); + } + } + else + { + entries.emplace_back(entryName); + } + } + return entries; + } + + uint16_t SceneryGroupObject::GetNumIncludedObjects() const + { + return static_cast(_items.size()); + } + + const std::vector& SceneryGroupObject::GetItems() const + { + return _items; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/SceneryGroupObject.h b/src/openrct2/object/SceneryGroupObject.h index 6f1fa0e05e..b669f1d560 100644 --- a/src/openrct2/object/SceneryGroupObject.h +++ b/src/openrct2/object/SceneryGroupObject.h @@ -14,36 +14,39 @@ #include -struct ObjectRepositoryItem; - -class SceneryGroupObject final : public Object +namespace OpenRCT2 { -private: - SceneryGroupEntry _legacyType = {}; - std::vector _items; + struct ObjectRepositoryItem; -public: - static constexpr ObjectType kObjectType = ObjectType::sceneryGroup; - - void* GetLegacyData() override + class SceneryGroupObject final : public Object { - return &_legacyType; - } - void ReadJson(IReadObjectContext* context, json_t& root) override; + private: + SceneryGroupEntry _legacyType = {}; + std::vector _items; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void Load() override; - void Unload() override; - void UpdateEntryIndexes(); + public: + static constexpr ObjectType kObjectType = ObjectType::sceneryGroup; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void* GetLegacyData() override + { + return &_legacyType; + } + void ReadJson(IReadObjectContext* context, json_t& root) override; - void SetRepositoryItem(ObjectRepositoryItem* item) const override; + void ReadLegacy(IReadObjectContext* context, IStream* stream) override; + void Load() override; + void Unload() override; + void UpdateEntryIndexes(); - uint16_t GetNumIncludedObjects() const; - const std::vector& GetItems() const; + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; -private: - static std::vector ReadItems(OpenRCT2::IStream* stream); - static std::vector ReadJsonEntries(IReadObjectContext* context, json_t& jEntries); -}; + void SetRepositoryItem(ObjectRepositoryItem* item) const override; + + uint16_t GetNumIncludedObjects() const; + const std::vector& GetItems() const; + + private: + static std::vector ReadItems(IStream* stream); + static std::vector ReadJsonEntries(IReadObjectContext* context, json_t& jEntries); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/SceneryObject.h b/src/openrct2/object/SceneryObject.h index 8e6ee592c5..cea6c9cde6 100644 --- a/src/openrct2/object/SceneryObject.h +++ b/src/openrct2/object/SceneryObject.h @@ -13,22 +13,25 @@ #include -class SceneryObject : public Object +namespace OpenRCT2 { -private: - ObjectEntryDescriptor _primarySceneryGroupEntry = {}; - -public: - virtual ~SceneryObject() = default; - - const ObjectEntryDescriptor& GetPrimarySceneryGroup() const + class SceneryObject : public Object { - return _primarySceneryGroupEntry; - } + private: + ObjectEntryDescriptor _primarySceneryGroupEntry = {}; -protected: - void SetPrimarySceneryGroup(const ObjectEntryDescriptor& entry) - { - _primarySceneryGroupEntry = entry; - } -}; + public: + virtual ~SceneryObject() = default; + + const ObjectEntryDescriptor& GetPrimarySceneryGroup() const + { + return _primarySceneryGroupEntry; + } + + protected: + void SetPrimarySceneryGroup(const ObjectEntryDescriptor& entry) + { + _primarySceneryGroupEntry = entry; + } + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/SmallSceneryEntry.h b/src/openrct2/object/SmallSceneryEntry.h index aa35fd6af9..beedcd42da 100644 --- a/src/openrct2/object/SmallSceneryEntry.h +++ b/src/openrct2/object/SmallSceneryEntry.h @@ -15,63 +15,66 @@ enum class CursorID : uint8_t; -enum SMALL_SCENERY_FLAGS : uint32_t +namespace OpenRCT2 { - SMALL_SCENERY_FLAG_FULL_TILE = (1 << 0), // 0x1 - SMALL_SCENERY_FLAG_VOFFSET_CENTRE = (1 << 1), // 0x2 - SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE = (1 << 2), // 0x4 - SMALL_SCENERY_FLAG_ROTATABLE = (1 << 3), // 0x8; when set, user can set rotation, otherwise rotation is automatic - SMALL_SCENERY_FLAG_ANIMATED = (1 << 4), // 0x10 - SMALL_SCENERY_FLAG_CAN_WITHER = (1 << 5), // 0x20 - SMALL_SCENERY_FLAG_CAN_BE_WATERED = (1 << 6), // 0x40 - SMALL_SCENERY_FLAG_ANIMATED_FG = (1 << 7), // 0x80 - SMALL_SCENERY_FLAG_DIAGONAL = (1 << 8), // 0x100 - SMALL_SCENERY_FLAG_HAS_GLASS = (1 << 9), // 0x200 - SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR = (1 << 10), // 0x400 - SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 = (1 << 11), // 0x800 - SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 = (1 << 12), // 0x1000 - SMALL_SCENERY_FLAG_IS_CLOCK = (1 << 13), // 0x2000 - SMALL_SCENERY_FLAG_SWAMP_GOO = (1 << 14), // 0x4000 - SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS = (1 << 15), // 0x8000 - SMALL_SCENERY_FLAG17 = (1 << 16), // 0x10000 - SMALL_SCENERY_FLAG_STACKABLE = (1 << 17), // 0x20000; means scenery item can be placed in the air and over water - SMALL_SCENERY_FLAG_NO_WALLS = (1 << 18), // 0x40000 - SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR = (1 << 19), // 0x80000 - SMALL_SCENERY_FLAG_NO_SUPPORTS = (1 << 20), // 0x100000 - SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED = (1 << 21), // 0x200000 - SMALL_SCENERY_FLAG_COG = (1 << 22), // 0x400000 - SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP = (1 << 23), // 0x800000; means supports can be built on this object. Used for base - // blocks. - SMALL_SCENERY_FLAG_HALF_SPACE = (1 << 24), // 0x1000000 - SMALL_SCENERY_FLAG_THREE_QUARTERS = (1 << 25), // 0x2000000 - SMALL_SCENERY_FLAG_PAINT_SUPPORTS = (1 << 26), // 0x4000000; used for scenery items which are support structures - SMALL_SCENERY_FLAG27 = (1 << 27), // 0x8000000 - - // Added by OpenRCT2: - SMALL_SCENERY_FLAG_IS_TREE = (1 << 28), - SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR = (1 << 29), -}; - -struct SmallSceneryEntry -{ - static constexpr auto kObjectType = ObjectType::smallScenery; - - StringId name; - uint32_t image; - uint32_t flags; - uint8_t height; - CursorID tool_id; - money64 price; - money64 removal_price; - uint8_t* frame_offsets; - uint16_t FrameOffsetCount; - uint16_t animation_delay; - uint16_t animation_mask; - uint16_t num_frames; - ObjectEntryIndex scenery_tab_id; - - constexpr bool HasFlag(const uint32_t _flags) const + enum SMALL_SCENERY_FLAGS : uint32_t { - return (flags & _flags) != 0; - } -}; + SMALL_SCENERY_FLAG_FULL_TILE = (1 << 0), // 0x1 + SMALL_SCENERY_FLAG_VOFFSET_CENTRE = (1 << 1), // 0x2 + SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE = (1 << 2), // 0x4 + SMALL_SCENERY_FLAG_ROTATABLE = (1 << 3), // 0x8; when set, user can set rotation, otherwise rotation is automatic + SMALL_SCENERY_FLAG_ANIMATED = (1 << 4), // 0x10 + SMALL_SCENERY_FLAG_CAN_WITHER = (1 << 5), // 0x20 + SMALL_SCENERY_FLAG_CAN_BE_WATERED = (1 << 6), // 0x40 + SMALL_SCENERY_FLAG_ANIMATED_FG = (1 << 7), // 0x80 + SMALL_SCENERY_FLAG_DIAGONAL = (1 << 8), // 0x100 + SMALL_SCENERY_FLAG_HAS_GLASS = (1 << 9), // 0x200 + SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR = (1 << 10), // 0x400 + SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 = (1 << 11), // 0x800 + SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 = (1 << 12), // 0x1000 + SMALL_SCENERY_FLAG_IS_CLOCK = (1 << 13), // 0x2000 + SMALL_SCENERY_FLAG_SWAMP_GOO = (1 << 14), // 0x4000 + SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS = (1 << 15), // 0x8000 + SMALL_SCENERY_FLAG17 = (1 << 16), // 0x10000 + SMALL_SCENERY_FLAG_STACKABLE = (1 << 17), // 0x20000; means scenery item can be placed in the air and over water + SMALL_SCENERY_FLAG_NO_WALLS = (1 << 18), // 0x40000 + SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR = (1 << 19), // 0x80000 + SMALL_SCENERY_FLAG_NO_SUPPORTS = (1 << 20), // 0x100000 + SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED = (1 << 21), // 0x200000 + SMALL_SCENERY_FLAG_COG = (1 << 22), // 0x400000 + SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP = (1 << 23), // 0x800000; means supports can be built on this object. Used for + // base blocks. + SMALL_SCENERY_FLAG_HALF_SPACE = (1 << 24), // 0x1000000 + SMALL_SCENERY_FLAG_THREE_QUARTERS = (1 << 25), // 0x2000000 + SMALL_SCENERY_FLAG_PAINT_SUPPORTS = (1 << 26), // 0x4000000; used for scenery items which are support structures + SMALL_SCENERY_FLAG27 = (1 << 27), // 0x8000000 + + // Added by OpenRCT2: + SMALL_SCENERY_FLAG_IS_TREE = (1 << 28), + SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR = (1 << 29), + }; + + struct SmallSceneryEntry + { + static constexpr auto kObjectType = ObjectType::smallScenery; + + StringId name; + uint32_t image; + uint32_t flags; + uint8_t height; + CursorID tool_id; + money64 price; + money64 removal_price; + uint8_t* frame_offsets; + uint16_t FrameOffsetCount; + uint16_t animation_delay; + uint16_t animation_mask; + uint16_t num_frames; + ObjectEntryIndex scenery_tab_id; + + constexpr bool HasFlag(const uint32_t _flags) const + { + return (flags & _flags) != 0; + } + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/SmallSceneryObject.cpp b/src/openrct2/object/SmallSceneryObject.cpp index 5dded4c8c9..ddb20aca71 100644 --- a/src/openrct2/object/SmallSceneryObject.cpp +++ b/src/openrct2/object/SmallSceneryObject.cpp @@ -20,138 +20,138 @@ #include "../localisation/Language.h" #include "../world/Scenery.h" -using namespace OpenRCT2; - -void SmallSceneryObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) +namespace OpenRCT2 { - stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.flags = stream->ReadValue(); - _legacyType.height = stream->ReadValue(); - _legacyType.tool_id = static_cast(stream->ReadValue()); - _legacyType.price = stream->ReadValue() * 10; - _legacyType.removal_price = stream->ReadValue() * 10; - stream->Seek(4, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.animation_delay = stream->ReadValue(); - _legacyType.animation_mask = stream->ReadValue(); - _legacyType.num_frames = stream->ReadValue(); - _legacyType.scenery_tab_id = kObjectEntryIndexNull; - - GetStringTable().Read(context, stream, ObjectStringID::NAME); - - RCTObjectEntry sgEntry = stream->ReadValue(); - SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); - - if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS)) + void SmallSceneryObject::ReadLegacy(IReadObjectContext* context, IStream* stream) { - _frameOffsets = ReadFrameOffsets(stream); - } - // This crude method was used by RCT2. JSON objects have a flag for this property. - if (_legacyType.height > 64) - { - _legacyType.flags |= SMALL_SCENERY_FLAG_IS_TREE; - } + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.flags = stream->ReadValue(); + _legacyType.height = stream->ReadValue(); + _legacyType.tool_id = static_cast(stream->ReadValue()); + _legacyType.price = stream->ReadValue() * 10; + _legacyType.removal_price = stream->ReadValue() * 10; + stream->Seek(4, STREAM_SEEK_CURRENT); + _legacyType.animation_delay = stream->ReadValue(); + _legacyType.animation_mask = stream->ReadValue(); + _legacyType.num_frames = stream->ReadValue(); + _legacyType.scenery_tab_id = kObjectEntryIndexNull; - GetImageTable().Read(context, stream); + GetStringTable().Read(context, stream, ObjectStringID::NAME); - // Validate properties - if (_legacyType.price <= 0.00_GBP) - { - context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); - } - if (_legacyType.removal_price <= 0.00_GBP) - { - // Make sure you don't make a profit when placing then removing. - const auto reimbursement = _legacyType.removal_price; - if (reimbursement > _legacyType.price) + RCTObjectEntry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); + + if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS)) { - context->LogError(ObjectError::InvalidProperty, "Sell price can not be more than buy price."); + _frameOffsets = ReadFrameOffsets(stream); + } + // This crude method was used by RCT2. JSON objects have a flag for this property. + if (_legacyType.height > 64) + { + _legacyType.flags |= SMALL_SCENERY_FLAG_IS_TREE; + } + + GetImageTable().Read(context, stream); + + // Validate properties + if (_legacyType.price <= 0.00_GBP) + { + context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); + } + if (_legacyType.removal_price <= 0.00_GBP) + { + // Make sure you don't make a profit when placing then removing. + const auto reimbursement = _legacyType.removal_price; + if (reimbursement > _legacyType.price) + { + context->LogError(ObjectError::InvalidProperty, "Sell price can not be more than buy price."); + } } } -} -void SmallSceneryObject::Load() -{ - GetStringTable().Sort(); - _legacyType.name = LanguageAllocateObjectString(GetName()); - _legacyType.image = LoadImages(); - - _legacyType.scenery_tab_id = kObjectEntryIndexNull; - _legacyType.FrameOffsetCount = 0; - - if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS)) + void SmallSceneryObject::Load() { - _legacyType.frame_offsets = _frameOffsets.data(); - _legacyType.FrameOffsetCount = static_cast(_frameOffsets.size()); - } + GetStringTable().Sort(); + _legacyType.name = LanguageAllocateObjectString(GetName()); + _legacyType.image = LoadImages(); - PerformFixes(); -} + _legacyType.scenery_tab_id = kObjectEntryIndexNull; + _legacyType.FrameOffsetCount = 0; -void SmallSceneryObject::Unload() -{ - LanguageFreeObjectString(_legacyType.name); - UnloadImages(); - - _legacyType.name = 0; - _legacyType.image = 0; -} - -void SmallSceneryObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto imageId = ImageId(_legacyType.image); - if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR)) - { - imageId = imageId.WithPrimary(COLOUR_BORDEAUX_RED); - if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR)) + if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS)) { - imageId = imageId.WithSecondary(COLOUR_YELLOW); + _legacyType.frame_offsets = _frameOffsets.data(); + _legacyType.FrameOffsetCount = static_cast(_frameOffsets.size()); } - } - if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR)) - { - imageId = imageId.WithSecondary(COLOUR_DARK_BROWN); + + PerformFixes(); } - auto screenCoords = ScreenCoordsXY{ width / 2, (height / 2) + (_legacyType.height / 2) }; - screenCoords.y = std::min(screenCoords.y, height - 16); - - if ((_legacyType.HasFlag(SMALL_SCENERY_FLAG_FULL_TILE)) && (_legacyType.HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))) + void SmallSceneryObject::Unload() { - screenCoords.y -= 12; + LanguageFreeObjectString(_legacyType.name); + UnloadImages(); + + _legacyType.name = 0; + _legacyType.image = 0; } - GfxDrawSprite(rt, imageId, screenCoords); - - if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_GLASS)) + void SmallSceneryObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const { - imageId = ImageId(_legacyType.image + 4).WithTransparency(COLOUR_BORDEAUX_RED); + auto imageId = ImageId(_legacyType.image); + if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR)) + { + imageId = imageId.WithPrimary(COLOUR_BORDEAUX_RED); + if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR)) + { + imageId = imageId.WithSecondary(COLOUR_YELLOW); + } + } + if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR)) + { + imageId = imageId.WithSecondary(COLOUR_DARK_BROWN); + } + + auto screenCoords = ScreenCoordsXY{ width / 2, (height / 2) + (_legacyType.height / 2) }; + screenCoords.y = std::min(screenCoords.y, height - 16); + + if ((_legacyType.HasFlag(SMALL_SCENERY_FLAG_FULL_TILE)) && (_legacyType.HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))) + { + screenCoords.y -= 12; + } + GfxDrawSprite(rt, imageId, screenCoords); - } - if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_ANIMATED_FG)) - { - imageId = ImageId(_legacyType.image + 4); - if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR)) + if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_GLASS)) { - imageId = imageId.WithSecondary(COLOUR_YELLOW); + imageId = ImageId(_legacyType.image + 4).WithTransparency(COLOUR_BORDEAUX_RED); + GfxDrawSprite(rt, imageId, screenCoords); } - GfxDrawSprite(rt, imageId, screenCoords); - } -} -std::vector SmallSceneryObject::ReadFrameOffsets(OpenRCT2::IStream* stream) -{ - uint8_t frameOffset; - auto data = std::vector(); - data.push_back(stream->ReadValue()); - while ((frameOffset = stream->ReadValue()) != 0xFF) + if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_ANIMATED_FG)) + { + imageId = ImageId(_legacyType.image + 4); + if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR)) + { + imageId = imageId.WithSecondary(COLOUR_YELLOW); + } + GfxDrawSprite(rt, imageId, screenCoords); + } + } + + std::vector SmallSceneryObject::ReadFrameOffsets(IStream* stream) { - data.push_back(frameOffset); + uint8_t frameOffset; + auto data = std::vector(); + data.push_back(stream->ReadValue()); + while ((frameOffset = stream->ReadValue()) != 0xFF) + { + data.push_back(frameOffset); + } + return data; } - return data; -} -// clang-format off + // clang-format off void SmallSceneryObject::PerformFixes() { auto identifier = GetLegacyIdentifier(); @@ -173,100 +173,101 @@ void SmallSceneryObject::PerformFixes() SetPrimarySceneryGroup(ObjectEntryDescriptor("rct2.scenery_group.scgpirat")); } } -// clang-format on + // clang-format on -void SmallSceneryObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "SmallSceneryObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - - if (properties.is_object()) + void SmallSceneryObject::ReadJson(IReadObjectContext* context, json_t& root) { - _legacyType.height = Json::GetNumber(properties["height"]); - _legacyType.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::StatueDown); - _legacyType.price = Json::GetNumber(properties["price"]) * 10; - _legacyType.removal_price = Json::GetNumber(properties["removalPrice"]) * 10; - _legacyType.animation_delay = Json::GetNumber(properties["animationDelay"]); - _legacyType.animation_mask = Json::GetNumber(properties["animationMask"]); - _legacyType.num_frames = Json::GetNumber(properties["numFrames"]); + Guard::Assert(root.is_object(), "SmallSceneryObject::ReadJson expects parameter root to be object"); - _legacyType.flags = Json::GetFlags( - properties, - { - { "SMALL_SCENERY_FLAG_VOFFSET_CENTRE", SMALL_SCENERY_FLAG_VOFFSET_CENTRE }, - { "requiresFlatSurface", SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE }, - { "isRotatable", SMALL_SCENERY_FLAG_ROTATABLE }, - { "isAnimated", SMALL_SCENERY_FLAG_ANIMATED }, - { "canWither", SMALL_SCENERY_FLAG_CAN_WITHER }, - { "canBeWatered", SMALL_SCENERY_FLAG_CAN_BE_WATERED }, - { "hasOverlayImage", SMALL_SCENERY_FLAG_ANIMATED_FG }, - { "hasGlass", SMALL_SCENERY_FLAG_HAS_GLASS }, - { "hasPrimaryColour", SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, - { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 }, - { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 }, - { "isClock", SMALL_SCENERY_FLAG_IS_CLOCK }, - { "SMALL_SCENERY_FLAG_SWAMP_GOO", SMALL_SCENERY_FLAG_SWAMP_GOO }, - { "SMALL_SCENERY_FLAG17", SMALL_SCENERY_FLAG17 }, - { "isStackable", SMALL_SCENERY_FLAG_STACKABLE }, - { "prohibitWalls", SMALL_SCENERY_FLAG_NO_WALLS }, - { "hasSecondaryColour", SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, - { "hasNoSupports", SMALL_SCENERY_FLAG_NO_SUPPORTS }, - { "SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED", SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED }, - { "SMALL_SCENERY_FLAG_COG", SMALL_SCENERY_FLAG_COG }, - { "allowSupportsAbove", SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP }, - { "supportsHavePrimaryColour", SMALL_SCENERY_FLAG_PAINT_SUPPORTS }, - { "SMALL_SCENERY_FLAG27", SMALL_SCENERY_FLAG27 }, - { "isTree", SMALL_SCENERY_FLAG_IS_TREE }, - { "hasTertiaryColour", SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR }, - }); + auto properties = root["properties"]; - // Determine shape flags from a shape string - auto shape = Json::GetString(properties["shape"]); - if (!shape.empty()) + if (properties.is_object()) { - auto quarters = shape.substr(0, 3); - if (quarters == "2/4") - { - _legacyType.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_HALF_SPACE; - } - else if (quarters == "3/4") - { - _legacyType.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_THREE_QUARTERS; - } - else if (quarters == "4/4") - { - _legacyType.flags |= SMALL_SCENERY_FLAG_FULL_TILE; - } - if (shape.size() >= 5) - { - if ((shape.substr(3) == "+D")) + _legacyType.height = Json::GetNumber(properties["height"]); + _legacyType.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::StatueDown); + _legacyType.price = Json::GetNumber(properties["price"]) * 10; + _legacyType.removal_price = Json::GetNumber(properties["removalPrice"]) * 10; + _legacyType.animation_delay = Json::GetNumber(properties["animationDelay"]); + _legacyType.animation_mask = Json::GetNumber(properties["animationMask"]); + _legacyType.num_frames = Json::GetNumber(properties["numFrames"]); + + _legacyType.flags = Json::GetFlags( + properties, { - _legacyType.flags |= SMALL_SCENERY_FLAG_DIAGONAL; + { "SMALL_SCENERY_FLAG_VOFFSET_CENTRE", SMALL_SCENERY_FLAG_VOFFSET_CENTRE }, + { "requiresFlatSurface", SMALL_SCENERY_FLAG_REQUIRE_FLAT_SURFACE }, + { "isRotatable", SMALL_SCENERY_FLAG_ROTATABLE }, + { "isAnimated", SMALL_SCENERY_FLAG_ANIMATED }, + { "canWither", SMALL_SCENERY_FLAG_CAN_WITHER }, + { "canBeWatered", SMALL_SCENERY_FLAG_CAN_BE_WATERED }, + { "hasOverlayImage", SMALL_SCENERY_FLAG_ANIMATED_FG }, + { "hasGlass", SMALL_SCENERY_FLAG_HAS_GLASS }, + { "hasPrimaryColour", SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR }, + { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 }, + { "SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4", SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 }, + { "isClock", SMALL_SCENERY_FLAG_IS_CLOCK }, + { "SMALL_SCENERY_FLAG_SWAMP_GOO", SMALL_SCENERY_FLAG_SWAMP_GOO }, + { "SMALL_SCENERY_FLAG17", SMALL_SCENERY_FLAG17 }, + { "isStackable", SMALL_SCENERY_FLAG_STACKABLE }, + { "prohibitWalls", SMALL_SCENERY_FLAG_NO_WALLS }, + { "hasSecondaryColour", SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR }, + { "hasNoSupports", SMALL_SCENERY_FLAG_NO_SUPPORTS }, + { "SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED", SMALL_SCENERY_FLAG_VISIBLE_WHEN_ZOOMED }, + { "SMALL_SCENERY_FLAG_COG", SMALL_SCENERY_FLAG_COG }, + { "allowSupportsAbove", SMALL_SCENERY_FLAG_BUILD_DIRECTLY_ONTOP }, + { "supportsHavePrimaryColour", SMALL_SCENERY_FLAG_PAINT_SUPPORTS }, + { "SMALL_SCENERY_FLAG27", SMALL_SCENERY_FLAG27 }, + { "isTree", SMALL_SCENERY_FLAG_IS_TREE }, + { "hasTertiaryColour", SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR }, + }); + + // Determine shape flags from a shape string + auto shape = Json::GetString(properties["shape"]); + if (!shape.empty()) + { + auto quarters = shape.substr(0, 3); + if (quarters == "2/4") + { + _legacyType.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_HALF_SPACE; + } + else if (quarters == "3/4") + { + _legacyType.flags |= SMALL_SCENERY_FLAG_FULL_TILE | SMALL_SCENERY_FLAG_THREE_QUARTERS; + } + else if (quarters == "4/4") + { + _legacyType.flags |= SMALL_SCENERY_FLAG_FULL_TILE; + } + if (shape.size() >= 5) + { + if ((shape.substr(3) == "+D")) + { + _legacyType.flags |= SMALL_SCENERY_FLAG_DIAGONAL; + } } } + + auto jFrameOffsets = properties["frameOffsets"]; + if (jFrameOffsets.is_array()) + { + _frameOffsets = ReadJsonFrameOffsets(jFrameOffsets); + _legacyType.flags |= SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS; + } + + SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); } - auto jFrameOffsets = properties["frameOffsets"]; - if (jFrameOffsets.is_array()) - { - _frameOffsets = ReadJsonFrameOffsets(jFrameOffsets); - _legacyType.flags |= SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS; - } - - SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + PopulateTablesFromJson(context, root); } - PopulateTablesFromJson(context, root); -} - -std::vector SmallSceneryObject::ReadJsonFrameOffsets(json_t& jFrameOffsets) -{ - std::vector offsets; - - for (const auto& jOffset : jFrameOffsets) + std::vector SmallSceneryObject::ReadJsonFrameOffsets(json_t& jFrameOffsets) { - offsets.push_back(Json::GetNumber(jOffset)); + std::vector offsets; + + for (const auto& jOffset : jFrameOffsets) + { + offsets.push_back(Json::GetNumber(jOffset)); + } + return offsets; } - return offsets; -} +} // namespace OpenRCT2 diff --git a/src/openrct2/object/SmallSceneryObject.h b/src/openrct2/object/SmallSceneryObject.h index b8920f4c90..6bb3325050 100644 --- a/src/openrct2/object/SmallSceneryObject.h +++ b/src/openrct2/object/SmallSceneryObject.h @@ -15,29 +15,32 @@ #include -class SmallSceneryObject final : public SceneryObject +namespace OpenRCT2 { -private: - SmallSceneryEntry _legacyType = {}; - std::vector _frameOffsets; - -public: - static constexpr ObjectType kObjectType = ObjectType::smallScenery; - - void* GetLegacyData() override + class SmallSceneryObject final : public SceneryObject { - return &_legacyType; - } + private: + SmallSceneryEntry _legacyType = {}; + std::vector _frameOffsets; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + public: + static constexpr ObjectType kObjectType = ObjectType::smallScenery; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void* GetLegacyData() override + { + return &_legacyType; + } -private: - static std::vector ReadFrameOffsets(OpenRCT2::IStream* stream); - static std::vector ReadJsonFrameOffsets(json_t& jFrameOffsets); - void PerformFixes(); -}; + void ReadLegacy(IReadObjectContext* context, IStream* stream) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + + private: + static std::vector ReadFrameOffsets(IStream* stream); + static std::vector ReadJsonFrameOffsets(json_t& jFrameOffsets); + void PerformFixes(); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/StationObject.cpp b/src/openrct2/object/StationObject.cpp index d3c2fee1be..7bfbb30f71 100644 --- a/src/openrct2/object/StationObject.cpp +++ b/src/openrct2/object/StationObject.cpp @@ -16,88 +16,89 @@ #include "../drawing/Drawing.h" #include "../world/Banner.h" -using namespace OpenRCT2; - -void StationObject::Load() +namespace OpenRCT2 { - GetStringTable().Sort(); - NameStringId = LanguageAllocateObjectString(GetName()); - - auto numImages = GetImageTable().GetCount(); - if (numImages != 0) + void StationObject::Load() { - BaseImageId = LoadImages(); + GetStringTable().Sort(); + NameStringId = LanguageAllocateObjectString(GetName()); - uint32_t shelterOffset = (Flags & StationObjectFlags::isTransparent) ? 32 : 16; - if (numImages > shelterOffset) + auto numImages = GetImageTable().GetCount(); + if (numImages != 0) { - ShelterImageId = BaseImageId + shelterOffset; + BaseImageId = LoadImages(); + + uint32_t shelterOffset = (Flags & StationObjectFlags::isTransparent) ? 32 : 16; + if (numImages > shelterOffset) + { + ShelterImageId = BaseImageId + shelterOffset; + } } } -} -void StationObject::Unload() -{ - LanguageFreeObjectString(NameStringId); - UnloadImages(); - - NameStringId = 0; - BaseImageId = kImageIndexUndefined; - ShelterImageId = kImageIndexUndefined; -} - -void StationObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, (height / 2) + 16 }; - - auto colour0 = COLOUR_LIGHT_BLUE; - auto colour1 = COLOUR_BORDEAUX_RED; - auto tcolour0 = colour0; - - auto imageId = ImageId(BaseImageId); - auto tImageId = ImageId(BaseImageId + 16).WithTransparency(tcolour0); - if (Flags & StationObjectFlags::hasPrimaryColour) + void StationObject::Unload() { - imageId = imageId.WithPrimary(colour0); - } - if (Flags & StationObjectFlags::hasSecondaryColour) - { - imageId = imageId.WithSecondary(colour1); + LanguageFreeObjectString(NameStringId); + UnloadImages(); + + NameStringId = 0; + BaseImageId = kImageIndexUndefined; + ShelterImageId = kImageIndexUndefined; } - GfxDrawSprite(rt, imageId, screenCoords); - if (Flags & StationObjectFlags::isTransparent) + void StationObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const { - GfxDrawSprite(rt, tImageId, screenCoords); + auto screenCoords = ScreenCoordsXY{ width / 2, (height / 2) + 16 }; + + auto colour0 = COLOUR_LIGHT_BLUE; + auto colour1 = COLOUR_BORDEAUX_RED; + auto tcolour0 = colour0; + + auto imageId = ImageId(BaseImageId); + auto tImageId = ImageId(BaseImageId + 16).WithTransparency(tcolour0); + if (Flags & StationObjectFlags::hasPrimaryColour) + { + imageId = imageId.WithPrimary(colour0); + } + if (Flags & StationObjectFlags::hasSecondaryColour) + { + imageId = imageId.WithSecondary(colour1); + } + + GfxDrawSprite(rt, imageId, screenCoords); + if (Flags & StationObjectFlags::isTransparent) + { + GfxDrawSprite(rt, tImageId, screenCoords); + } + + GfxDrawSprite(rt, imageId.WithIndexOffset(4), screenCoords); + if (Flags & StationObjectFlags::isTransparent) + { + GfxDrawSprite(rt, tImageId.WithIndexOffset(4), screenCoords); + } } - GfxDrawSprite(rt, imageId.WithIndexOffset(4), screenCoords); - if (Flags & StationObjectFlags::isTransparent) + void StationObject::ReadJson(IReadObjectContext* context, json_t& root) { - GfxDrawSprite(rt, tImageId.WithIndexOffset(4), screenCoords); + Guard::Assert(root.is_object(), "StationObject::ReadJson expects parameter root to be object"); + + auto properties = root["properties"]; + + if (properties.is_object()) + { + Height = Json::GetNumber(properties["height"]); + ScrollingMode = Json::GetNumber(properties["scrollingMode"], kScrollingModeNone); + Flags = Json::GetFlags( + properties, + { + { "hasPrimaryColour", StationObjectFlags::hasPrimaryColour }, + { "hasSecondaryColour", StationObjectFlags::hasSecondaryColour }, + { "isTransparent", StationObjectFlags::isTransparent }, + { "noPlatforms", StationObjectFlags::noPlatforms }, + { "hasShelter", StationObjectFlags::hasShelter }, + }); + } + + PopulateTablesFromJson(context, root); } -} - -void StationObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "StationObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - - if (properties.is_object()) - { - Height = Json::GetNumber(properties["height"]); - ScrollingMode = Json::GetNumber(properties["scrollingMode"], kScrollingModeNone); - Flags = Json::GetFlags( - properties, - { - { "hasPrimaryColour", StationObjectFlags::hasPrimaryColour }, - { "hasSecondaryColour", StationObjectFlags::hasSecondaryColour }, - { "isTransparent", StationObjectFlags::isTransparent }, - { "noPlatforms", StationObjectFlags::noPlatforms }, - { "hasShelter", StationObjectFlags::hasShelter }, - }); - } - - PopulateTablesFromJson(context, root); -} +} // namespace OpenRCT2 diff --git a/src/openrct2/object/StationObject.h b/src/openrct2/object/StationObject.h index 278a41806b..e506f02a68 100644 --- a/src/openrct2/object/StationObject.h +++ b/src/openrct2/object/StationObject.h @@ -12,30 +12,33 @@ #include "../drawing/ImageId.hpp" #include "Object.h" -namespace OpenRCT2::StationObjectFlags +namespace OpenRCT2 { - const uint32_t hasPrimaryColour = 1 << 0; - const uint32_t hasSecondaryColour = 1 << 1; - const uint32_t isTransparent = 1 << 2; - const uint32_t noPlatforms = 1 << 3; - const uint32_t hasShelter = (1 << 4); -} // namespace OpenRCT2::StationObjectFlags + namespace StationObjectFlags + { + const uint32_t hasPrimaryColour = 1 << 0; + const uint32_t hasSecondaryColour = 1 << 1; + const uint32_t isTransparent = 1 << 2; + const uint32_t noPlatforms = 1 << 3; + const uint32_t hasShelter = (1 << 4); + } // namespace StationObjectFlags -class StationObject final : public Object -{ -public: - static constexpr ObjectType kObjectType = ObjectType::station; + class StationObject final : public Object + { + public: + static constexpr ObjectType kObjectType = ObjectType::station; - StringId NameStringId{}; - ImageIndex BaseImageId = kImageIndexUndefined; - ImageIndex ShelterImageId = kImageIndexUndefined; - uint32_t Flags{}; - int32_t Height{}; - uint8_t ScrollingMode{}; + StringId NameStringId{}; + ImageIndex BaseImageId = kImageIndexUndefined; + ImageIndex ShelterImageId = kImageIndexUndefined; + uint32_t Flags{}; + int32_t Height{}; + uint8_t ScrollingMode{}; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; -}; + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/StringTable.cpp b/src/openrct2/object/StringTable.cpp index 7957780d26..8b1c198ca7 100644 --- a/src/openrct2/object/StringTable.cpp +++ b/src/openrct2/object/StringTable.cpp @@ -19,170 +19,172 @@ #include "../rct12/CSStringConverter.h" #include "Object.h" -using namespace OpenRCT2; - -static constexpr uint8_t RCT2ToOpenRCT2LanguageId[] = { - LANGUAGE_ENGLISH_UK, - LANGUAGE_ENGLISH_US, - LANGUAGE_FRENCH, - LANGUAGE_GERMAN, - LANGUAGE_SPANISH, - LANGUAGE_ITALIAN, - LANGUAGE_DUTCH, - LANGUAGE_SWEDISH, - LANGUAGE_JAPANESE, - LANGUAGE_KOREAN, - LANGUAGE_CHINESE_SIMPLIFIED, - LANGUAGE_CHINESE_TRADITIONAL, - LANGUAGE_UNDEFINED, - LANGUAGE_PORTUGUESE_BR, -}; - -static bool StringIsBlank(const utf8* str) +namespace OpenRCT2 { - for (auto ch = str; *ch != '\0'; ch++) - { - if (!isblank(static_cast(*ch))) - { - return false; - } - } - return true; -} + static constexpr uint8_t RCT2ToOpenRCT2LanguageId[] = { + LANGUAGE_ENGLISH_UK, + LANGUAGE_ENGLISH_US, + LANGUAGE_FRENCH, + LANGUAGE_GERMAN, + LANGUAGE_SPANISH, + LANGUAGE_ITALIAN, + LANGUAGE_DUTCH, + LANGUAGE_SWEDISH, + LANGUAGE_JAPANESE, + LANGUAGE_KOREAN, + LANGUAGE_CHINESE_SIMPLIFIED, + LANGUAGE_CHINESE_TRADITIONAL, + LANGUAGE_UNDEFINED, + LANGUAGE_PORTUGUESE_BR, + }; -void StringTable::Read(IReadObjectContext* context, OpenRCT2::IStream* stream, ObjectStringID id) -{ - try + static bool StringIsBlank(const utf8* str) { - RCT2LanguageId rct2LanguageId; - while ((rct2LanguageId = static_cast(stream->ReadValue())) != RCT2LanguageId::End) + for (auto ch = str; *ch != '\0'; ch++) { - uint8_t languageId = (EnumValue(rct2LanguageId) <= EnumValue(RCT2LanguageId::Portuguese)) - ? RCT2ToOpenRCT2LanguageId[EnumValue(rct2LanguageId)] - : static_cast(LANGUAGE_UNDEFINED); - std::string stringAsWin1252 = stream->ReadString(); - auto stringAsUtf8 = RCT2StringToUTF8(stringAsWin1252, rct2LanguageId); - - if (!StringIsBlank(stringAsUtf8.data())) + if (!isblank(static_cast(*ch))) { - stringAsUtf8 = String::trim(stringAsUtf8); - StringTableEntry entry{}; - entry.Id = id; - entry.LanguageId = languageId; - entry.Text = std::move(stringAsUtf8); - _strings.push_back(std::move(entry)); + return false; } } + return true; } - catch (const std::exception&) + + void StringTable::Read(IReadObjectContext* context, IStream* stream, ObjectStringID id) { - context->LogError(ObjectError::BadStringTable, "Bad string table."); - throw; - } - Sort(); -} - -ObjectStringID StringTable::ParseStringId(const std::string& s) -{ - if (s == "name") - return ObjectStringID::NAME; - if (s == "description") - return ObjectStringID::DESCRIPTION; - if (s == "park_name") - return ObjectStringID::PARK_NAME; - if (s == "details") - return ObjectStringID::SCENARIO_DETAILS; - if (s == "capacity") - return ObjectStringID::CAPACITY; - if (s == "vehicleName") - return ObjectStringID::VEHICLE_NAME; - return ObjectStringID::UNKNOWN; -} - -void StringTable::ReadJson(json_t& root) -{ - Guard::Assert(root.is_object(), "StringTable::ReadJson expects parameter root to be object"); - - // We trust the JSON type of root is object - auto jsonStrings = root["strings"]; - - for (auto& [key, jsonLanguages] : jsonStrings.items()) - { - auto stringId = ParseStringId(key); - if (stringId != ObjectStringID::UNKNOWN) + try { - for (auto& [locale, jsonString] : jsonLanguages.items()) + RCT2LanguageId rct2LanguageId; + while ((rct2LanguageId = static_cast(stream->ReadValue())) != RCT2LanguageId::End) { - auto langId = LanguageGetIDFromLocale(locale.c_str()); - if (langId != LANGUAGE_UNDEFINED) + uint8_t languageId = (EnumValue(rct2LanguageId) <= EnumValue(RCT2LanguageId::Portuguese)) + ? RCT2ToOpenRCT2LanguageId[EnumValue(rct2LanguageId)] + : static_cast(LANGUAGE_UNDEFINED); + std::string stringAsWin1252 = stream->ReadString(); + auto stringAsUtf8 = RCT2StringToUTF8(stringAsWin1252, rct2LanguageId); + + if (!StringIsBlank(stringAsUtf8.data())) { - auto string = Json::GetString(jsonString); - SetString(stringId, langId, string); + stringAsUtf8 = String::trim(stringAsUtf8); + StringTableEntry entry{}; + entry.Id = id; + entry.LanguageId = languageId; + entry.Text = std::move(stringAsUtf8); + _strings.push_back(std::move(entry)); } } } - } - Sort(); -} - -std::string StringTable::GetString(ObjectStringID id) const -{ - for (auto& string : _strings) - { - if (string.Id == id) + catch (const std::exception&) { - return string.Text; + context->LogError(ObjectError::BadStringTable, "Bad string table."); + throw; } + Sort(); } - return std::string(); -} -std::string StringTable::GetString(uint8_t language, ObjectStringID id) const -{ - for (auto& string : _strings) + ObjectStringID StringTable::ParseStringId(const std::string& s) { - if (string.LanguageId == language && string.Id == id) - { - return string.Text; - } + if (s == "name") + return ObjectStringID::NAME; + if (s == "description") + return ObjectStringID::DESCRIPTION; + if (s == "park_name") + return ObjectStringID::PARK_NAME; + if (s == "details") + return ObjectStringID::SCENARIO_DETAILS; + if (s == "capacity") + return ObjectStringID::CAPACITY; + if (s == "vehicleName") + return ObjectStringID::VEHICLE_NAME; + return ObjectStringID::UNKNOWN; } - return std::string(); -} -void StringTable::SetString(ObjectStringID id, uint8_t language, const std::string& text) -{ - StringTableEntry entry; - entry.Id = id; - entry.LanguageId = language; - entry.Text = text; - _strings.push_back(std::move(entry)); -} + void StringTable::ReadJson(json_t& root) + { + Guard::Assert(root.is_object(), "StringTable::ReadJson expects parameter root to be object"); -void StringTable::Sort() -{ - const auto& languageOrder = OpenRCT2::GetContext()->GetLocalisationService().GetLanguageOrder(); - std::sort(_strings.begin(), _strings.end(), [languageOrder](const StringTableEntry& a, const StringTableEntry& b) -> bool { - if (a.Id == b.Id) + // We trust the JSON type of root is object + auto jsonStrings = root["strings"]; + + for (auto& [key, jsonLanguages] : jsonStrings.items()) { - if (a.LanguageId == b.LanguageId) + auto stringId = ParseStringId(key); + if (stringId != ObjectStringID::UNKNOWN) { - return String::compare(a.Text, b.Text, true) < 0; - } - - for (const auto& language : languageOrder) - { - if (a.LanguageId == language) + for (auto& [locale, jsonString] : jsonLanguages.items()) { - return true; - } - if (b.LanguageId == language) - { - return false; + auto langId = LanguageGetIDFromLocale(locale.c_str()); + if (langId != LANGUAGE_UNDEFINED) + { + auto string = Json::GetString(jsonString); + SetString(stringId, langId, string); + } } } - - return a.LanguageId < b.LanguageId; } - return a.Id < b.Id; - }); -} + Sort(); + } + + std::string StringTable::GetString(ObjectStringID id) const + { + for (auto& string : _strings) + { + if (string.Id == id) + { + return string.Text; + } + } + return std::string(); + } + + std::string StringTable::GetString(uint8_t language, ObjectStringID id) const + { + for (auto& string : _strings) + { + if (string.LanguageId == language && string.Id == id) + { + return string.Text; + } + } + return std::string(); + } + + void StringTable::SetString(ObjectStringID id, uint8_t language, const std::string& text) + { + StringTableEntry entry; + entry.Id = id; + entry.LanguageId = language; + entry.Text = text; + _strings.push_back(std::move(entry)); + } + + void StringTable::Sort() + { + const auto& languageOrder = GetContext()->GetLocalisationService().GetLanguageOrder(); + std::sort( + _strings.begin(), _strings.end(), [languageOrder](const StringTableEntry& a, const StringTableEntry& b) -> bool { + if (a.Id == b.Id) + { + if (a.LanguageId == b.LanguageId) + { + return String::compare(a.Text, b.Text, true) < 0; + } + + for (const auto& language : languageOrder) + { + if (a.LanguageId == language) + { + return true; + } + if (b.LanguageId == language) + { + return false; + } + } + + return a.LanguageId < b.LanguageId; + } + return a.Id < b.Id; + }); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/StringTable.h b/src/openrct2/object/StringTable.h index cb4b75397c..10577ab998 100644 --- a/src/openrct2/object/StringTable.h +++ b/src/openrct2/object/StringTable.h @@ -15,49 +15,49 @@ #include #include -struct IReadObjectContext; namespace OpenRCT2 { + struct IReadObjectContext; struct IStream; -} -enum class ObjectStringID : uint8_t -{ - UNKNOWN = 255, - NAME = 0, - DESCRIPTION, - SCENARIO_NAME = 0, - PARK_NAME = 1, - SCENARIO_DETAILS = 2, - CAPACITY = 2, - VEHICLE_NAME = 3, -}; + enum class ObjectStringID : uint8_t + { + UNKNOWN = 255, + NAME = 0, + DESCRIPTION, + SCENARIO_NAME = 0, + PARK_NAME = 1, + SCENARIO_DETAILS = 2, + CAPACITY = 2, + VEHICLE_NAME = 3, + }; -struct StringTableEntry -{ - ObjectStringID Id = ObjectStringID::UNKNOWN; - uint8_t LanguageId = LANGUAGE_UNDEFINED; - std::string Text; -}; + struct StringTableEntry + { + ObjectStringID Id = ObjectStringID::UNKNOWN; + uint8_t LanguageId = LANGUAGE_UNDEFINED; + std::string Text; + }; -class StringTable -{ -private: - std::vector _strings; - static ObjectStringID ParseStringId(const std::string& s); + class StringTable + { + private: + std::vector _strings; + static ObjectStringID ParseStringId(const std::string& s); -public: - StringTable() = default; - StringTable(const StringTable&) = delete; - StringTable& operator=(const StringTable&) = delete; + public: + StringTable() = default; + StringTable(const StringTable&) = delete; + StringTable& operator=(const StringTable&) = delete; - void Read(IReadObjectContext* context, OpenRCT2::IStream* stream, ObjectStringID id); - /** - * @note root is deliberately left non-const: json_t behaviour changes when const - */ - void ReadJson(json_t& root); - void Sort(); - std::string GetString(ObjectStringID id) const; - std::string GetString(uint8_t language, ObjectStringID id) const; - void SetString(ObjectStringID id, uint8_t language, const std::string& text); -}; + void Read(IReadObjectContext* context, IStream* stream, ObjectStringID id); + /** + * @note root is deliberately left non-const: json_t behaviour changes when const + */ + void ReadJson(json_t& root); + void Sort(); + std::string GetString(ObjectStringID id) const; + std::string GetString(uint8_t language, ObjectStringID id) const; + void SetString(ObjectStringID id, uint8_t language, const std::string& text); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/TerrainEdgeObject.cpp b/src/openrct2/object/TerrainEdgeObject.cpp index cd190509aa..05bb847fa7 100644 --- a/src/openrct2/object/TerrainEdgeObject.cpp +++ b/src/openrct2/object/TerrainEdgeObject.cpp @@ -17,58 +17,59 @@ #include "../drawing/Drawing.h" #include "ObjectManager.h" -using namespace OpenRCT2; - -void TerrainEdgeObject::Load() +namespace OpenRCT2 { - GetStringTable().Sort(); - NameStringId = LanguageAllocateObjectString(GetName()); - IconImageId = LoadImages(); - - // First image is icon followed by edge images - BaseImageId = IconImageId + 1; -} - -void TerrainEdgeObject::Unload() -{ - LanguageFreeObjectString(NameStringId); - UnloadImages(); - - NameStringId = 0; - IconImageId = 0; - BaseImageId = 0; -} - -void TerrainEdgeObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; - - auto imageId = ImageId(BaseImageId + 5); - GfxDrawSprite(rt, imageId, screenCoords + ScreenCoordsXY{ 8, -8 }); - GfxDrawSprite(rt, imageId, screenCoords + ScreenCoordsXY{ 8, 8 }); -} - -void TerrainEdgeObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "TerrainEdgeObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - - if (properties.is_object()) + void TerrainEdgeObject::Load() { - HasDoors = Json::GetBoolean(properties["hasDoors"]); - const uint32_t doorSoundNumber = Json::GetNumber(properties["doorSound"]); - if (doorSoundNumber < OpenRCT2::Audio::kDoorSoundTypeCount) - { - doorSound = static_cast(doorSoundNumber); - } + GetStringTable().Sort(); + NameStringId = LanguageAllocateObjectString(GetName()); + IconImageId = LoadImages(); + + // First image is icon followed by edge images + BaseImageId = IconImageId + 1; } - PopulateTablesFromJson(context, root); -} + void TerrainEdgeObject::Unload() + { + LanguageFreeObjectString(NameStringId); + UnloadImages(); -TerrainEdgeObject* TerrainEdgeObject::GetById(ObjectEntryIndex entryIndex) -{ - auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); - return objMgr.GetLoadedObject(entryIndex); -} + NameStringId = 0; + IconImageId = 0; + BaseImageId = 0; + } + + void TerrainEdgeObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; + + auto imageId = ImageId(BaseImageId + 5); + GfxDrawSprite(rt, imageId, screenCoords + ScreenCoordsXY{ 8, -8 }); + GfxDrawSprite(rt, imageId, screenCoords + ScreenCoordsXY{ 8, 8 }); + } + + void TerrainEdgeObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "TerrainEdgeObject::ReadJson expects parameter root to be object"); + + auto properties = root["properties"]; + + if (properties.is_object()) + { + HasDoors = Json::GetBoolean(properties["hasDoors"]); + const uint32_t doorSoundNumber = Json::GetNumber(properties["doorSound"]); + if (doorSoundNumber < Audio::kDoorSoundTypeCount) + { + doorSound = static_cast(doorSoundNumber); + } + } + + PopulateTablesFromJson(context, root); + } + + TerrainEdgeObject* TerrainEdgeObject::GetById(ObjectEntryIndex entryIndex) + { + auto& objMgr = GetContext()->GetObjectManager(); + return objMgr.GetLoadedObject(entryIndex); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/TerrainEdgeObject.h b/src/openrct2/object/TerrainEdgeObject.h index 93dcfd5145..53768dc8cb 100644 --- a/src/openrct2/object/TerrainEdgeObject.h +++ b/src/openrct2/object/TerrainEdgeObject.h @@ -12,23 +12,26 @@ #include "../audio/Audio.h" #include "Object.h" -class TerrainEdgeObject final : public Object +namespace OpenRCT2 { -private: -public: - static constexpr ObjectType kObjectType = ObjectType::terrainEdge; + class TerrainEdgeObject final : public Object + { + private: + public: + static constexpr ObjectType kObjectType = ObjectType::terrainEdge; - StringId NameStringId{}; - uint32_t IconImageId{}; - uint32_t BaseImageId{}; - bool HasDoors{}; - OpenRCT2::Audio::DoorSoundType doorSound{}; + StringId NameStringId{}; + uint32_t IconImageId{}; + uint32_t BaseImageId{}; + bool HasDoors{}; + Audio::DoorSoundType doorSound{}; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; - static TerrainEdgeObject* GetById(ObjectEntryIndex entryIndex); -}; + static TerrainEdgeObject* GetById(ObjectEntryIndex entryIndex); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/TerrainSurfaceObject.cpp b/src/openrct2/object/TerrainSurfaceObject.cpp index e224f1c428..ed6b4beb0c 100644 --- a/src/openrct2/object/TerrainSurfaceObject.cpp +++ b/src/openrct2/object/TerrainSurfaceObject.cpp @@ -18,169 +18,170 @@ #include "../world/Location.hpp" #include "ObjectManager.h" -using namespace OpenRCT2; - -void TerrainSurfaceObject::Load() +namespace OpenRCT2 { - GetStringTable().Sort(); - NameStringId = LanguageAllocateObjectString(GetName()); - IconImageId = LoadImages(); - if ((Flags & TerrainSurfaceFlags::smoothWithSelf) || (Flags & TerrainSurfaceFlags::smoothWithOther)) + void TerrainSurfaceObject::Load() { - PatternBaseImageId = IconImageId + 1; - EntryBaseImageId = PatternBaseImageId + 6; - } - else - { - EntryBaseImageId = IconImageId + 1; - } - NumEntries = (GetImageTable().GetCount() - EntryBaseImageId) / kNumImagesInEntry; -} - -void TerrainSurfaceObject::Unload() -{ - LanguageFreeObjectString(NameStringId); - UnloadImages(); - - NameStringId = 0; - IconImageId = 0; - PatternBaseImageId = 0; - EntryBaseImageId = 0; - NumEntries = 0; -} - -void TerrainSurfaceObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto imageId = ImageId(GetImageId({}, 1, 0, 0, false, false)); - if (Colour != kNoValue) - { - imageId = imageId.WithPrimary(Colour); - } - - ScreenCoordsXY screenCoords{}; - int32_t x0 = 0; - screenCoords.y = -16; - for (int32_t i = 0; i < 8; i++) - { - screenCoords.x = x0; - if (i % 2 == 0) + GetStringTable().Sort(); + NameStringId = LanguageAllocateObjectString(GetName()); + IconImageId = LoadImages(); + if ((Flags & TerrainSurfaceFlags::smoothWithSelf) || (Flags & TerrainSurfaceFlags::smoothWithOther)) { - screenCoords.x -= 32; + PatternBaseImageId = IconImageId + 1; + EntryBaseImageId = PatternBaseImageId + 6; } - for (int32_t j = 0; j < 4; j++) + else { - GfxDrawSprite(rt, imageId, screenCoords); - screenCoords.x += 64; + EntryBaseImageId = IconImageId + 1; } - screenCoords.y += 16; + NumEntries = (GetImageTable().GetCount() - EntryBaseImageId) / kNumImagesInEntry; } -} -void TerrainSurfaceObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "TerrainSurfaceObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - - if (properties.is_object()) + void TerrainSurfaceObject::Unload() { - Colour = Colour::FromString(Json::GetString(properties["colour"]), kNoValue); - Rotations = Json::GetNumber(properties["rotations"], 1); - Price = Json::GetNumber(properties["price"]); - Flags = Json::GetFlags( - properties, - { { "smoothWithSelf", TerrainSurfaceFlags::smoothWithSelf }, - { "smoothWithOther", TerrainSurfaceFlags::smoothWithOther }, - { "canGrow", TerrainSurfaceFlags::canGrow } }); + LanguageFreeObjectString(NameStringId); + UnloadImages(); - const auto mapColours = properties["mapColours"]; - const bool mapColoursAreValid = mapColours.is_array() && mapColours.size() == std::size(MapColours); - for (size_t i = 0; i < std::size(MapColours); i++) + NameStringId = 0; + IconImageId = 0; + PatternBaseImageId = 0; + EntryBaseImageId = 0; + NumEntries = 0; + } + + void TerrainSurfaceObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + auto imageId = ImageId(GetImageId({}, 1, 0, 0, false, false)); + if (Colour != kNoValue) { - if (mapColoursAreValid) - MapColours[i] = mapColours[i]; - else - MapColours[i] = PaletteIndex::pi0; + imageId = imageId.WithPrimary(Colour); } - for (auto& el : properties["special"]) + ScreenCoordsXY screenCoords{}; + int32_t x0 = 0; + screenCoords.y = -16; + for (int32_t i = 0; i < 8; i++) { - if (el.is_object()) + screenCoords.x = x0; + if (i % 2 == 0) { - SpecialEntry entry; - entry.Index = Json::GetNumber(el["index"]); - entry.Length = Json::GetNumber(el["length"], kNoValue); - entry.Rotation = Json::GetNumber(el["rotation"], kNoValue); - entry.Variation = Json::GetNumber(el["variation"], kNoValue); + screenCoords.x -= 32; + } + for (int32_t j = 0; j < 4; j++) + { + GfxDrawSprite(rt, imageId, screenCoords); + screenCoords.x += 64; + } + screenCoords.y += 16; + } + } - if (Json::GetBoolean(el["underground"])) - SpecialEntriesUnderground.push_back(entry); - else if (Json::GetBoolean(el["grid"])) - SpecialEntriesGrid.push_back(entry); + void TerrainSurfaceObject::ReadJson(IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "TerrainSurfaceObject::ReadJson expects parameter root to be object"); + + auto properties = root["properties"]; + + if (properties.is_object()) + { + Colour = Colour::FromString(Json::GetString(properties["colour"]), kNoValue); + Rotations = Json::GetNumber(properties["rotations"], 1); + Price = Json::GetNumber(properties["price"]); + Flags = Json::GetFlags( + properties, + { { "smoothWithSelf", TerrainSurfaceFlags::smoothWithSelf }, + { "smoothWithOther", TerrainSurfaceFlags::smoothWithOther }, + { "canGrow", TerrainSurfaceFlags::canGrow } }); + + const auto mapColours = properties["mapColours"]; + const bool mapColoursAreValid = mapColours.is_array() && mapColours.size() == std::size(MapColours); + for (size_t i = 0; i < std::size(MapColours); i++) + { + if (mapColoursAreValid) + MapColours[i] = mapColours[i]; else - SpecialEntries.push_back(entry); + MapColours[i] = PaletteIndex::pi0; + } + + for (auto& el : properties["special"]) + { + if (el.is_object()) + { + SpecialEntry entry; + entry.Index = Json::GetNumber(el["index"]); + entry.Length = Json::GetNumber(el["length"], kNoValue); + entry.Rotation = Json::GetNumber(el["rotation"], kNoValue); + entry.Variation = Json::GetNumber(el["variation"], kNoValue); + + if (Json::GetBoolean(el["underground"])) + SpecialEntriesUnderground.push_back(entry); + else if (Json::GetBoolean(el["grid"])) + SpecialEntriesGrid.push_back(entry); + else + SpecialEntries.push_back(entry); + } } } - } - auto jDefault = root["default"]; - if (jDefault.is_object()) - { - DefaultEntry = Json::GetNumber(jDefault["normal"]); - DefaultGridEntry = Json::GetNumber(jDefault["grid"]); - DefaultUndergroundEntry = Json::GetNumber(jDefault["underground"]); - } - else - { - DefaultEntry = 0; - DefaultGridEntry = 1; - DefaultUndergroundEntry = 2; - } - - PopulateTablesFromJson(context, root); -} - -ImageId TerrainSurfaceObject::GetImageId( - const CoordsXY& position, uint8_t length, uint8_t rotation, uint8_t offset, bool grid, bool underground) const -{ - uint32_t result = DefaultEntry; - std::span entries(SpecialEntries); - if (underground) - { - result = DefaultUndergroundEntry; - entries = std::span(SpecialEntriesUnderground); - } - else if (grid) - { - result = DefaultGridEntry; - entries = std::span(SpecialEntriesGrid); - } - - TileCoordsXY tilePos(position); - const uint8_t variation = (tilePos.x & 0b01) | ((tilePos.y << 1) & 0b10); - - // Look for a matching special - for (const SpecialEntry& special : entries) - { - if ((special.Length == kNoValue || special.Length == length) - && (special.Rotation == kNoValue || special.Rotation == rotation) - && (special.Variation == kNoValue || special.Variation == variation)) + auto jDefault = root["default"]; + if (jDefault.is_object()) { - result = special.Index; - break; + DefaultEntry = Json::GetNumber(jDefault["normal"]); + DefaultGridEntry = Json::GetNumber(jDefault["grid"]); + DefaultUndergroundEntry = Json::GetNumber(jDefault["underground"]); } + else + { + DefaultEntry = 0; + DefaultGridEntry = 1; + DefaultUndergroundEntry = 2; + } + + PopulateTablesFromJson(context, root); } - ImageId image(EntryBaseImageId + (result * kNumImagesInEntry) + offset); - if (Colour != kNoValue) + ImageId TerrainSurfaceObject::GetImageId( + const CoordsXY& position, uint8_t length, uint8_t rotation, uint8_t offset, bool grid, bool underground) const { - image = image.WithPrimary(Colour); - } - return image; -} + uint32_t result = DefaultEntry; + std::span entries(SpecialEntries); + if (underground) + { + result = DefaultUndergroundEntry; + entries = std::span(SpecialEntriesUnderground); + } + else if (grid) + { + result = DefaultGridEntry; + entries = std::span(SpecialEntriesGrid); + } -TerrainSurfaceObject* TerrainSurfaceObject::GetById(ObjectEntryIndex entryIndex) -{ - auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); - return objMgr.GetLoadedObject(entryIndex); -} + TileCoordsXY tilePos(position); + const uint8_t variation = (tilePos.x & 0b01) | ((tilePos.y << 1) & 0b10); + + // Look for a matching special + for (const SpecialEntry& special : entries) + { + if ((special.Length == kNoValue || special.Length == length) + && (special.Rotation == kNoValue || special.Rotation == rotation) + && (special.Variation == kNoValue || special.Variation == variation)) + { + result = special.Index; + break; + } + } + + ImageId image(EntryBaseImageId + (result * kNumImagesInEntry) + offset); + if (Colour != kNoValue) + { + image = image.WithPrimary(Colour); + } + return image; + } + + TerrainSurfaceObject* TerrainSurfaceObject::GetById(ObjectEntryIndex entryIndex) + { + auto& objMgr = GetContext()->GetObjectManager(); + return objMgr.GetLoadedObject(entryIndex); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/TerrainSurfaceObject.h b/src/openrct2/object/TerrainSurfaceObject.h index 29c6f97c2b..9447706430 100644 --- a/src/openrct2/object/TerrainSurfaceObject.h +++ b/src/openrct2/object/TerrainSurfaceObject.h @@ -14,57 +14,60 @@ struct CoordsXY; -enum TerrainSurfaceFlags +namespace OpenRCT2 { - smoothWithSelf = 1 << 0, - smoothWithOther = 1 << 1, - canGrow = 1 << 2, -}; - -class TerrainSurfaceObject final : public Object -{ -private: - struct SpecialEntry + enum TerrainSurfaceFlags { - uint8_t Index{}; - uint8_t Length{}; - uint8_t Rotation{}; - uint8_t Variation{}; + smoothWithSelf = 1 << 0, + smoothWithOther = 1 << 1, + canGrow = 1 << 2, }; - static constexpr auto kNumImagesInEntry = 19; + class TerrainSurfaceObject final : public Object + { + private: + struct SpecialEntry + { + uint8_t Index{}; + uint8_t Length{}; + uint8_t Rotation{}; + uint8_t Variation{}; + }; -public: - static constexpr ObjectType kObjectType = ObjectType::terrainSurface; + static constexpr auto kNumImagesInEntry = 19; - static constexpr uint8_t kNoValue = 0xFF; - StringId NameStringId{}; - uint32_t IconImageId{}; - uint32_t PatternBaseImageId{}; - uint32_t EntryBaseImageId{}; + public: + static constexpr ObjectType kObjectType = ObjectType::terrainSurface; - uint32_t NumEntries{}; - uint32_t DefaultEntry{}; - uint32_t DefaultGridEntry{}; - uint32_t DefaultUndergroundEntry{}; - std::vector SpecialEntries; - std::vector SpecialEntriesUnderground; - std::vector SpecialEntriesGrid; + static constexpr uint8_t kNoValue = 0xFF; + StringId NameStringId{}; + uint32_t IconImageId{}; + uint32_t PatternBaseImageId{}; + uint32_t EntryBaseImageId{}; - colour_t Colour{}; - uint8_t Rotations{}; - money64 Price{}; - TerrainSurfaceFlags Flags{}; - PaletteIndex MapColours[2]{}; + uint32_t NumEntries{}; + uint32_t DefaultEntry{}; + uint32_t DefaultGridEntry{}; + uint32_t DefaultUndergroundEntry{}; + std::vector SpecialEntries; + std::vector SpecialEntriesUnderground; + std::vector SpecialEntriesGrid; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + colour_t Colour{}; + uint8_t Rotations{}; + money64 Price{}; + TerrainSurfaceFlags Flags{}; + PaletteIndex MapColours[2]{}; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; - ImageId GetImageId( - const CoordsXY& position, uint8_t length, uint8_t rotation, uint8_t offset, bool grid, bool underground) const; + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; - static TerrainSurfaceObject* GetById(ObjectEntryIndex entryIndex); -}; + ImageId GetImageId( + const CoordsXY& position, uint8_t length, uint8_t rotation, uint8_t offset, bool grid, bool underground) const; + + static TerrainSurfaceObject* GetById(ObjectEntryIndex entryIndex); + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/WallObject.cpp b/src/openrct2/object/WallObject.cpp index 860698d56a..63f717d2d3 100644 --- a/src/openrct2/object/WallObject.cpp +++ b/src/openrct2/object/WallObject.cpp @@ -18,101 +18,101 @@ #include "../localisation/Language.h" #include "../world/Banner.h" -using namespace OpenRCT2; - -void WallObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) +namespace OpenRCT2 { - stream->Seek(6, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.tool_id = static_cast(stream->ReadValue()); - _legacyType.flags = stream->ReadValue(); - _legacyType.height = stream->ReadValue(); - _legacyType.flags2 = stream->ReadValue(); - _legacyType.price = stream->ReadValue(); - _legacyType.scenery_tab_id = kObjectEntryIndexNull; - stream->Seek(1, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.scrolling_mode = stream->ReadValue(); - - GetStringTable().Read(context, stream, ObjectStringID::NAME); - - RCTObjectEntry sgEntry = stream->ReadValue(); - SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); - - GetImageTable().Read(context, stream); - - // Validate properties - if (_legacyType.price <= 0.00_GBP) + void WallObject::ReadLegacy(IReadObjectContext* context, IStream* stream) { - context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.tool_id = static_cast(stream->ReadValue()); + _legacyType.flags = stream->ReadValue(); + _legacyType.height = stream->ReadValue(); + _legacyType.flags2 = stream->ReadValue(); + _legacyType.price = stream->ReadValue(); + _legacyType.scenery_tab_id = kObjectEntryIndexNull; + stream->Seek(1, STREAM_SEEK_CURRENT); + _legacyType.scrolling_mode = stream->ReadValue(); + + GetStringTable().Read(context, stream, ObjectStringID::NAME); + + RCTObjectEntry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(ObjectEntryDescriptor(sgEntry)); + + GetImageTable().Read(context, stream); + + // Validate properties + if (_legacyType.price <= 0.00_GBP) + { + context->LogError(ObjectError::InvalidProperty, "Price can not be free or negative."); + } + + // Autofix this object (will be turned into an official object later). + auto identifier = GetLegacyIdentifier(); + if (identifier == "XXWLBR03") + { + _legacyType.flags2 &= ~WALL_SCENERY_2_DOOR_SOUND_MASK; + _legacyType.flags2 |= (1u << WALL_SCENERY_2_DOOR_SOUND_SHIFT) & WALL_SCENERY_2_DOOR_SOUND_MASK; + } } - // Autofix this object (will be turned into an official object later). - auto identifier = GetLegacyIdentifier(); - if (identifier == "XXWLBR03") + void WallObject::Load() { - _legacyType.flags2 &= ~WALL_SCENERY_2_DOOR_SOUND_MASK; - _legacyType.flags2 |= (1u << WALL_SCENERY_2_DOOR_SOUND_SHIFT) & WALL_SCENERY_2_DOOR_SOUND_MASK; - } -} - -void WallObject::Load() -{ - GetStringTable().Sort(); - _legacyType.name = LanguageAllocateObjectString(GetName()); - _legacyType.image = LoadImages(); -} - -void WallObject::Unload() -{ - LanguageFreeObjectString(_legacyType.name); - UnloadImages(); - - _legacyType.name = 0; - _legacyType.image = 0; -} - -void WallObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; - - screenCoords.x += 14; - screenCoords.y += (_legacyType.height * 2) + 16; - - auto imageId = ImageId(_legacyType.image, COLOUR_BORDEAUX_RED); - if (_legacyType.flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) - { - imageId = imageId.WithSecondary(COLOUR_YELLOW); + GetStringTable().Sort(); + _legacyType.name = LanguageAllocateObjectString(GetName()); + _legacyType.image = LoadImages(); } - GfxDrawSprite(rt, imageId, screenCoords); - - if (_legacyType.flags & WALL_SCENERY_HAS_GLASS) + void WallObject::Unload() { - auto glassImageId = imageId.WithTransparency(COLOUR_BORDEAUX_RED).WithIndexOffset(6); - GfxDrawSprite(rt, glassImageId, screenCoords); + LanguageFreeObjectString(_legacyType.name); + UnloadImages(); + + _legacyType.name = 0; + _legacyType.image = 0; } - else if (_legacyType.flags & WALL_SCENERY_IS_DOOR) + + void WallObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const { - GfxDrawSprite(rt, imageId.WithIndexOffset(1), screenCoords); + auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; + + screenCoords.x += 14; + screenCoords.y += (_legacyType.height * 2) + 16; + + auto imageId = ImageId(_legacyType.image, COLOUR_BORDEAUX_RED); + if (_legacyType.flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) + { + imageId = imageId.WithSecondary(COLOUR_YELLOW); + } + + GfxDrawSprite(rt, imageId, screenCoords); + + if (_legacyType.flags & WALL_SCENERY_HAS_GLASS) + { + auto glassImageId = imageId.WithTransparency(COLOUR_BORDEAUX_RED).WithIndexOffset(6); + GfxDrawSprite(rt, glassImageId, screenCoords); + } + else if (_legacyType.flags & WALL_SCENERY_IS_DOOR) + { + GfxDrawSprite(rt, imageId.WithIndexOffset(1), screenCoords); + } } -} -void WallObject::ReadJson(IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "WallObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - - if (properties.is_object()) + void WallObject::ReadJson(IReadObjectContext* context, json_t& root) { - _legacyType.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::FenceDown); - _legacyType.height = Json::GetNumber(properties["height"]); - _legacyType.price = Json::GetNumber(properties["price"]); + Guard::Assert(root.is_object(), "WallObject::ReadJson expects parameter root to be object"); - _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"], kScrollingModeNone); + auto properties = root["properties"]; - SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + if (properties.is_object()) + { + _legacyType.tool_id = Cursor::FromString(Json::GetString(properties["cursor"]), CursorID::FenceDown); + _legacyType.height = Json::GetNumber(properties["height"]); + _legacyType.price = Json::GetNumber(properties["price"]); - // clang-format off + _legacyType.scrolling_mode = Json::GetNumber(properties["scrollingMode"], kScrollingModeNone); + + SetPrimarySceneryGroup(ObjectEntryDescriptor(Json::GetString(properties["sceneryGroup"]))); + + // clang-format off _legacyType.flags = Json::GetFlags( properties, { @@ -127,34 +127,35 @@ void WallObject::ReadJson(IReadObjectContext* context, json_t& root) { "isDoor", WALL_SCENERY_IS_DOOR, Json::FlagType::Normal }, { "isLongDoorAnimation", WALL_SCENERY_LONG_DOOR_ANIMATION, Json::FlagType::Normal }, }); - // clang-format on + // clang-format on - _legacyType.flags2 = Json::GetFlags( - properties, - { - { "isOpaque", WALL_SCENERY_2_IS_OPAQUE }, - { "isAnimated", WALL_SCENERY_2_ANIMATED }, - }); + _legacyType.flags2 = Json::GetFlags( + properties, + { + { "isOpaque", WALL_SCENERY_2_IS_OPAQUE }, + { "isAnimated", WALL_SCENERY_2_ANIMATED }, + }); - // HACK WALL_SCENERY_HAS_PRIMARY_COLOUR actually means, has any colour but we simplify the - // JSON and handle this on load. We should change code base in future to reflect the JSON. - if (!(_legacyType.flags & WALL_SCENERY_HAS_PRIMARY_COLOUR)) - { - if (_legacyType.flags & (WALL_SCENERY_HAS_SECONDARY_COLOUR | WALL_SCENERY_HAS_TERTIARY_COLOUR)) + // HACK WALL_SCENERY_HAS_PRIMARY_COLOUR actually means, has any colour but we simplify the + // JSON and handle this on load. We should change code base in future to reflect the JSON. + if (!(_legacyType.flags & WALL_SCENERY_HAS_PRIMARY_COLOUR)) { - _legacyType.flags |= WALL_SCENERY_HAS_PRIMARY_COLOUR; - _legacyType.flags2 |= WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR; + if (_legacyType.flags & (WALL_SCENERY_HAS_SECONDARY_COLOUR | WALL_SCENERY_HAS_TERTIARY_COLOUR)) + { + _legacyType.flags |= WALL_SCENERY_HAS_PRIMARY_COLOUR; + _legacyType.flags2 |= WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR; + } + } + + // Door sound + auto jDoorSound = properties["doorSound"]; + if (jDoorSound.is_number()) + { + auto doorSound = Json::GetNumber(jDoorSound); + _legacyType.flags2 |= (doorSound << WALL_SCENERY_2_DOOR_SOUND_SHIFT) & WALL_SCENERY_2_DOOR_SOUND_MASK; } } - // Door sound - auto jDoorSound = properties["doorSound"]; - if (jDoorSound.is_number()) - { - auto doorSound = Json::GetNumber(jDoorSound); - _legacyType.flags2 |= (doorSound << WALL_SCENERY_2_DOOR_SOUND_SHIFT) & WALL_SCENERY_2_DOOR_SOUND_MASK; - } + PopulateTablesFromJson(context, root); } - - PopulateTablesFromJson(context, root); -} +} // namespace OpenRCT2 diff --git a/src/openrct2/object/WallObject.h b/src/openrct2/object/WallObject.h index 7a3d2c633e..7dbb50f748 100644 --- a/src/openrct2/object/WallObject.h +++ b/src/openrct2/object/WallObject.h @@ -12,23 +12,26 @@ #include "SceneryObject.h" #include "WallSceneryEntry.h" -class WallObject final : public SceneryObject +namespace OpenRCT2 { -private: - WallSceneryEntry _legacyType = {}; - -public: - static constexpr ObjectType kObjectType = ObjectType::walls; - - void* GetLegacyData() override + class WallObject final : public SceneryObject { - return &_legacyType; - } + private: + WallSceneryEntry _legacyType = {}; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void Load() override; - void Unload() override; + public: + static constexpr ObjectType kObjectType = ObjectType::walls; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; -}; + void* GetLegacyData() override + { + return &_legacyType; + } + + void ReadLegacy(IReadObjectContext* context, IStream* stream) override; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/WallSceneryEntry.cpp b/src/openrct2/object/WallSceneryEntry.cpp index 97caf2282c..56b20a411e 100644 --- a/src/openrct2/object/WallSceneryEntry.cpp +++ b/src/openrct2/object/WallSceneryEntry.cpp @@ -9,8 +9,11 @@ #include "WallSceneryEntry.h" -OpenRCT2::Audio::DoorSoundType WallSceneryEntry::getDoorSoundType() const +namespace OpenRCT2 { - return static_cast( - (flags2 & WALL_SCENERY_2_DOOR_SOUND_MASK) >> WALL_SCENERY_2_DOOR_SOUND_SHIFT); -} + Audio::DoorSoundType WallSceneryEntry::getDoorSoundType() const + { + return static_cast( + (flags2 & WALL_SCENERY_2_DOOR_SOUND_MASK) >> WALL_SCENERY_2_DOOR_SOUND_SHIFT); + } +} // namespace OpenRCT2 diff --git a/src/openrct2/object/WallSceneryEntry.h b/src/openrct2/object/WallSceneryEntry.h index 0230a49daf..4eb7860b3f 100644 --- a/src/openrct2/object/WallSceneryEntry.h +++ b/src/openrct2/object/WallSceneryEntry.h @@ -16,40 +16,43 @@ enum class CursorID : uint8_t; -enum WALL_SCENERY_FLAGS +namespace OpenRCT2 { - WALL_SCENERY_HAS_PRIMARY_COLOUR = (1 << 0), // 0x1 - WALL_SCENERY_HAS_GLASS = (1 << 1), // 0x2 - WALL_SCENERY_CANT_BUILD_ON_SLOPE = (1 << 2), // 0x4 - WALL_SCENERY_IS_DOUBLE_SIDED = (1 << 3), // 0x8 - WALL_SCENERY_IS_DOOR = (1 << 4), // 0x10 - WALL_SCENERY_LONG_DOOR_ANIMATION = (1 << 5), // 0x20 - WALL_SCENERY_HAS_SECONDARY_COLOUR = (1 << 6), // 0x40 - WALL_SCENERY_HAS_TERTIARY_COLOUR = (1 << 7), // 0x80 -}; + enum WALL_SCENERY_FLAGS + { + WALL_SCENERY_HAS_PRIMARY_COLOUR = (1 << 0), // 0x1 + WALL_SCENERY_HAS_GLASS = (1 << 1), // 0x2 + WALL_SCENERY_CANT_BUILD_ON_SLOPE = (1 << 2), // 0x4 + WALL_SCENERY_IS_DOUBLE_SIDED = (1 << 3), // 0x8 + WALL_SCENERY_IS_DOOR = (1 << 4), // 0x10 + WALL_SCENERY_LONG_DOOR_ANIMATION = (1 << 5), // 0x20 + WALL_SCENERY_HAS_SECONDARY_COLOUR = (1 << 6), // 0x40 + WALL_SCENERY_HAS_TERTIARY_COLOUR = (1 << 7), // 0x80 + }; -enum WALL_SCENERY_2_FLAGS -{ - WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR = (1 << 0), // 0x1 - WALL_SCENERY_2_DOOR_SOUND_MASK = 0b0110, - WALL_SCENERY_2_DOOR_SOUND_SHIFT = 1, - WALL_SCENERY_2_IS_OPAQUE = (1 << 3), // 0x8 - WALL_SCENERY_2_ANIMATED = (1 << 4), // 0x10 -}; + enum WALL_SCENERY_2_FLAGS + { + WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR = (1 << 0), // 0x1 + WALL_SCENERY_2_DOOR_SOUND_MASK = 0b0110, + WALL_SCENERY_2_DOOR_SOUND_SHIFT = 1, + WALL_SCENERY_2_IS_OPAQUE = (1 << 3), // 0x8 + WALL_SCENERY_2_ANIMATED = (1 << 4), // 0x10 + }; -struct WallSceneryEntry -{ - static constexpr auto kObjectType = ObjectType::walls; + struct WallSceneryEntry + { + static constexpr auto kObjectType = ObjectType::walls; - StringId name; - uint32_t image; - CursorID tool_id; - uint8_t flags; - uint8_t height; - uint8_t flags2; - money64 price; - ObjectEntryIndex scenery_tab_id; - uint8_t scrolling_mode; + StringId name; + uint32_t image; + CursorID tool_id; + uint8_t flags; + uint8_t height; + uint8_t flags2; + money64 price; + ObjectEntryIndex scenery_tab_id; + uint8_t scrolling_mode; - OpenRCT2::Audio::DoorSoundType getDoorSoundType() const; -}; + Audio::DoorSoundType getDoorSoundType() const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/WaterEntry.h b/src/openrct2/object/WaterEntry.h index 83b5a57e84..8c226a364c 100644 --- a/src/openrct2/object/WaterEntry.h +++ b/src/openrct2/object/WaterEntry.h @@ -12,18 +12,21 @@ #include "../localisation/StringIdType.h" #include "ObjectTypes.h" -enum +namespace OpenRCT2 { - WATER_FLAGS_ALLOW_DUCKS = (1 << 0) -}; + enum + { + WATER_FLAGS_ALLOW_DUCKS = (1 << 0) + }; -struct WaterObjectEntry -{ - static constexpr auto kObjectType = ObjectType::water; + struct WaterObjectEntry + { + static constexpr auto kObjectType = ObjectType::water; - StringId string_idx; // 0x00 - uint32_t image_id; // 0x02 - uint32_t palette_index_1; // 0x06 - uint32_t palette_index_2; // 0x0A - uint16_t flags; // 0x0E -}; + StringId string_idx; // 0x00 + uint32_t image_id; // 0x02 + uint32_t palette_index_1; // 0x06 + uint32_t palette_index_2; // 0x0A + uint16_t flags; // 0x0E + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/object/WaterObject.cpp b/src/openrct2/object/WaterObject.cpp index ebd3147e0c..0719225a7d 100644 --- a/src/openrct2/object/WaterObject.cpp +++ b/src/openrct2/object/WaterObject.cpp @@ -23,125 +23,126 @@ #include #include -using namespace OpenRCT2; - -void WaterObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) +namespace OpenRCT2 { - stream->Seek(14, OpenRCT2::STREAM_SEEK_CURRENT); - _legacyType.flags = stream->ReadValue(); - - GetStringTable().Read(context, stream, ObjectStringID::NAME); - GetImageTable().Read(context, stream); -} - -void WaterObject::Load() -{ - GetStringTable().Sort(); - _legacyType.string_idx = LanguageAllocateObjectString(GetName()); - _legacyType.image_id = LoadImages(); - _legacyType.palette_index_1 = _legacyType.image_id + 1; - _legacyType.palette_index_2 = _legacyType.image_id + 4; - - LoadPalette(); -} - -void WaterObject::Unload() -{ - UnloadImages(); - LanguageFreeObjectString(_legacyType.string_idx); - - _legacyType.string_idx = 0; - _legacyType.image_id = 0; - _legacyType.palette_index_1 = 0; - _legacyType.palette_index_2 = 0; -} - -void WaterObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const -{ - // Write (no image) - auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; - DrawTextBasic(rt, screenCoords, STR_WINDOW_NO_IMAGE, {}, { TextAlignment::CENTRE }); -} - -void WaterObject::ReadJson([[maybe_unused]] IReadObjectContext* context, json_t& root) -{ - Guard::Assert(root.is_object(), "WaterObject::ReadJson expects parameter root to be object"); - - auto properties = root["properties"]; - - PopulateTablesFromJson(context, root); - - if (properties.is_object()) + void WaterObject::ReadLegacy(IReadObjectContext* context, IStream* stream) { - _legacyType.flags = Json::GetFlags( - properties, - { - { "allowDucks", WATER_FLAGS_ALLOW_DUCKS }, - }); + stream->Seek(14, STREAM_SEEK_CURRENT); + _legacyType.flags = stream->ReadValue(); - auto jPalettes = properties["palettes"]; - if (jPalettes.is_object()) + GetStringTable().Read(context, stream, ObjectStringID::NAME); + GetImageTable().Read(context, stream); + } + + void WaterObject::Load() + { + GetStringTable().Sort(); + _legacyType.string_idx = LanguageAllocateObjectString(GetName()); + _legacyType.image_id = LoadImages(); + _legacyType.palette_index_1 = _legacyType.image_id + 1; + _legacyType.palette_index_2 = _legacyType.image_id + 4; + + LoadPalette(); + } + + void WaterObject::Unload() + { + UnloadImages(); + LanguageFreeObjectString(_legacyType.string_idx); + + _legacyType.string_idx = 0; + _legacyType.image_id = 0; + _legacyType.palette_index_1 = 0; + _legacyType.palette_index_2 = 0; + } + + void WaterObject::DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const + { + // Write (no image) + auto screenCoords = ScreenCoordsXY{ width / 2, height / 2 }; + DrawTextBasic(rt, screenCoords, STR_WINDOW_NO_IMAGE, {}, { TextAlignment::CENTRE }); + } + + void WaterObject::ReadJson([[maybe_unused]] IReadObjectContext* context, json_t& root) + { + Guard::Assert(root.is_object(), "WaterObject::ReadJson expects parameter root to be object"); + + auto properties = root["properties"]; + + PopulateTablesFromJson(context, root); + + if (properties.is_object()) { - // Images which are actually palette data - static const char* paletteNames[] = { - "general", "waves-0", "waves-1", "waves-2", "sparkles-0", "sparkles-1", "sparkles-2", - }; - for (auto paletteName : paletteNames) - { - auto jPalette = jPalettes[paletteName]; - if (jPalette.is_object()) + _legacyType.flags = Json::GetFlags( + properties, { - ReadJsonPalette(jPalette); + { "allowDucks", WATER_FLAGS_ALLOW_DUCKS }, + }); + + auto jPalettes = properties["palettes"]; + if (jPalettes.is_object()) + { + // Images which are actually palette data + static const char* paletteNames[] = { + "general", "waves-0", "waves-1", "waves-2", "sparkles-0", "sparkles-1", "sparkles-2", + }; + for (auto paletteName : paletteNames) + { + auto jPalette = jPalettes[paletteName]; + if (jPalette.is_object()) + { + ReadJsonPalette(jPalette); + } } } } } -} -void WaterObject::ReadJsonPalette(json_t& jPalette) -{ - Guard::Assert(jPalette.is_object(), "WaterObject::ReadJsonPalette expects parameter jPalette to be object"); - - auto jColours = jPalette["colours"]; - auto numColours = jColours.size(); - - // This pointer gets memcopied in ImageTable::AddImage so it's fine for the unique_ptr to go out of scope - auto data = std::make_unique(numColours * 3); - size_t dataIndex = 0; - - for (auto& jColour : jColours) + void WaterObject::ReadJsonPalette(json_t& jPalette) { - if (jColour.is_string()) + Guard::Assert(jPalette.is_object(), "WaterObject::ReadJsonPalette expects parameter jPalette to be object"); + + auto jColours = jPalette["colours"]; + auto numColours = jColours.size(); + + // This pointer gets memcopied in ImageTable::AddImage so it's fine for the unique_ptr to go out of scope + auto data = std::make_unique(numColours * 3); + size_t dataIndex = 0; + + for (auto& jColour : jColours) { - auto colour = ParseColour(Json::GetString(jColour)); - data[dataIndex + 0] = (colour >> 16) & 0xFF; - data[dataIndex + 1] = (colour >> 8) & 0xFF; - data[dataIndex + 2] = colour & 0xFF; + if (jColour.is_string()) + { + auto colour = ParseColour(Json::GetString(jColour)); + data[dataIndex + 0] = (colour >> 16) & 0xFF; + data[dataIndex + 1] = (colour >> 8) & 0xFF; + data[dataIndex + 2] = colour & 0xFF; + } + dataIndex += 3; } - dataIndex += 3; + + G1Element g1 = {}; + g1.offset = data.get(); + g1.width = static_cast(numColours); + g1.x_offset = Json::GetNumber(jPalette["index"]); + g1.flags = G1_FLAG_PALETTE; + + auto& imageTable = GetImageTable(); + imageTable.AddImage(&g1); } - G1Element g1 = {}; - g1.offset = data.get(); - g1.width = static_cast(numColours); - g1.x_offset = Json::GetNumber(jPalette["index"]); - g1.flags = G1_FLAG_PALETTE; - - auto& imageTable = GetImageTable(); - imageTable.AddImage(&g1); -} - -uint32_t WaterObject::ParseColour(const std::string& s) const -{ - uint8_t r = 0; - uint8_t g = 0; - uint8_t b = 0; - if (s[0] == '#' && s.size() == 7) + uint32_t WaterObject::ParseColour(const std::string& s) const { - // Expect #RRGGBB - r = std::stoul(s.substr(1, 2), nullptr, 16) & 0xFF; - g = std::stoul(s.substr(3, 2), nullptr, 16) & 0xFF; - b = std::stoul(s.substr(5, 2), nullptr, 16) & 0xFF; + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + if (s[0] == '#' && s.size() == 7) + { + // Expect #RRGGBB + r = std::stoul(s.substr(1, 2), nullptr, 16) & 0xFF; + g = std::stoul(s.substr(3, 2), nullptr, 16) & 0xFF; + b = std::stoul(s.substr(5, 2), nullptr, 16) & 0xFF; + } + return (b << 16) | (g << 8) | r; } - return (b << 16) | (g << 8) | r; -} +} // namespace OpenRCT2 diff --git a/src/openrct2/object/WaterObject.h b/src/openrct2/object/WaterObject.h index 4445bb85ea..e2e4feb8e0 100644 --- a/src/openrct2/object/WaterObject.h +++ b/src/openrct2/object/WaterObject.h @@ -12,29 +12,30 @@ #include "Object.h" #include "WaterEntry.h" -#include - -class WaterObject final : public Object +namespace OpenRCT2 { -private: - WaterObjectEntry _legacyType = {}; - -public: - static constexpr ObjectType kObjectType = ObjectType::water; - - void* GetLegacyData() override + class WaterObject final : public Object { - return &_legacyType; - } + private: + WaterObjectEntry _legacyType = {}; - void ReadJson(IReadObjectContext* context, json_t& root) override; - void ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream) override; - void Load() override; - void Unload() override; + public: + static constexpr ObjectType kObjectType = ObjectType::water; - void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + void* GetLegacyData() override + { + return &_legacyType; + } -private: - void ReadJsonPalette(json_t& jPalette); - uint32_t ParseColour(const std::string& s) const; -}; + void ReadJson(IReadObjectContext* context, json_t& root) override; + void ReadLegacy(IReadObjectContext* context, IStream* stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(RenderTarget& rt, int32_t width, int32_t height) const override; + + private: + void ReadJsonPalette(json_t& jPalette); + uint32_t ParseColour(const std::string& s) const; + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/park/Legacy.h b/src/openrct2/park/Legacy.h index 809196d357..2cd07ef7f5 100644 --- a/src/openrct2/park/Legacy.h +++ b/src/openrct2/park/Legacy.h @@ -32,20 +32,22 @@ namespace OpenRCT2 { struct FootpathMapping; } + + struct ObjectEntryDescriptor; + class ObjectList; } // namespace OpenRCT2 -struct ObjectEntryDescriptor; -class ObjectList; using ride_type_t = uint16_t; enum class SpecialElement : uint8_t; using SpecialElements = FlagHolder; std::string_view MapToNewObjectIdentifier(std::string_view s); std::optional GetDATPathName(std::string_view newPathName); -const OpenRCT2::RCT2::FootpathMapping* GetFootpathMapping(const ObjectEntryDescriptor& desc); +const OpenRCT2::RCT2::FootpathMapping* GetFootpathMapping(const OpenRCT2::ObjectEntryDescriptor& desc); void UpdateFootpathsFromMapping( - ObjectEntryIndex* pathToSurfaceMap, ObjectEntryIndex* pathToQueueSurfaceMap, ObjectEntryIndex* pathToRailingsMap, - ObjectList& requiredObjects, ObjectEntryIndex entryIndex, const OpenRCT2::RCT2::FootpathMapping* footpathMapping); + OpenRCT2::ObjectEntryIndex* pathToSurfaceMap, OpenRCT2::ObjectEntryIndex* pathToQueueSurfaceMap, + OpenRCT2::ObjectEntryIndex* pathToRailingsMap, OpenRCT2::ObjectList& requiredObjects, OpenRCT2::ObjectEntryIndex entryIndex, + const OpenRCT2::RCT2::FootpathMapping* footpathMapping); std::span GetLegacyPeepAnimationObjects(); void ConvertPeepAnimationTypeToObjects(OpenRCT2::GameState_t& gameState); diff --git a/src/openrct2/park/ParkFile.h b/src/openrct2/park/ParkFile.h index e8f3b0f586..a702d4e8fa 100644 --- a/src/openrct2/park/ParkFile.h +++ b/src/openrct2/park/ParkFile.h @@ -15,11 +15,10 @@ #include #include -struct ObjectRepositoryItem; - namespace OpenRCT2 { struct GameState_t; + struct ObjectRepositoryItem; // Current version that is saved. constexpr uint32_t kParkFileCurrentVersion = 57; diff --git a/src/openrct2/peep/PeepAnimations.h b/src/openrct2/peep/PeepAnimations.h index a6ba242962..8400f0f49c 100644 --- a/src/openrct2/peep/PeepAnimations.h +++ b/src/openrct2/peep/PeepAnimations.h @@ -17,12 +17,13 @@ #include #include -class PeepAnimationsObject; enum class RCT12PeepAnimationGroup : uint8_t; enum class StaffType : uint8_t; namespace OpenRCT2 { + class PeepAnimationsObject; + enum class AnimationPeepType : uint8_t { Guest, diff --git a/src/openrct2/rct12/EntryList.h b/src/openrct2/rct12/EntryList.h index 2f3faf0211..d69b4aef7e 100644 --- a/src/openrct2/rct12/EntryList.h +++ b/src/openrct2/rct12/EntryList.h @@ -13,7 +13,10 @@ #include #include -using ObjectEntryIndex = uint16_t; +namespace OpenRCT2 +{ + using ObjectEntryIndex = uint16_t; +} namespace OpenRCT2::RCT12 { diff --git a/src/openrct2/rct12/RCT12.h b/src/openrct2/rct12/RCT12.h index 584cf3d316..9c5c4572bc 100644 --- a/src/openrct2/rct12/RCT12.h +++ b/src/openrct2/rct12/RCT12.h @@ -27,13 +27,13 @@ #include #include -class ObjectList; - namespace OpenRCT2 { + class ObjectList; enum class TrackElemType : uint16_t; enum class TextColour : uint8_t; } // namespace OpenRCT2 + namespace OpenRCT2::RCT12 { enum class ClimateType : uint8_t @@ -1202,7 +1202,7 @@ static_assert(sizeof(RCT12VehicleColour) == 2); #pragma pack(pop) -ObjectEntryIndex RCTEntryIndexToOpenRCT2EntryIndex(const RCT12ObjectEntryIndex index); +OpenRCT2::ObjectEntryIndex RCTEntryIndexToOpenRCT2EntryIndex(const RCT12ObjectEntryIndex index); RideId RCT12RideIdToOpenRCT2RideId(const RCT12RideId rideId); bool IsLikelyUTF8(std::string_view s); std::string RCT12RemoveFormattingUTF8(std::string_view s); @@ -1212,9 +1212,11 @@ OpenRCT2::RCT12::TrackElemType OpenRCT2FlatTrackTypeToRCT12(OpenRCT2::TrackElemT std::string_view GetStationIdentifierFromStyle(uint8_t style); uint8_t GetStationStyleFromIdentifier(u8string_view identifier); std::optional GetStyleFromMusicIdentifier(std::string_view identifier); -void RCT12AddDefaultObjects(ObjectList& objectList); -void AppendRequiredObjects(ObjectList& objectList, ObjectType objectType, std::span objectNames); -void AppendRequiredObjects(ObjectList& objectList, ObjectType objectType, const OpenRCT2::RCT12::EntryList& entryList); +void RCT12AddDefaultObjects(OpenRCT2::ObjectList& objectList); +void AppendRequiredObjects( + OpenRCT2::ObjectList& objectList, OpenRCT2::ObjectType objectType, std::span objectNames); +void AppendRequiredObjects( + OpenRCT2::ObjectList& objectList, OpenRCT2::ObjectType objectType, const OpenRCT2::RCT12::EntryList& entryList); bool IsUserStringID(StringId stringId); static constexpr money32 kRCT12CompanyValueOnFailedObjective = 0x80000001; diff --git a/src/openrct2/rct12/ScenarioPatcher.cpp b/src/openrct2/rct12/ScenarioPatcher.cpp index 447aa4bb15..ad54d6c7e5 100644 --- a/src/openrct2/rct12/ScenarioPatcher.cpp +++ b/src/openrct2/rct12/ScenarioPatcher.cpp @@ -425,7 +425,7 @@ static void ApplySurfaceFixes(const json_t& scenarioPatch) auto destinationSurface = OpenRCT2::Json::GetString(surfaceFixes[i][_destinationSurface]); auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); - auto surfaceObj = objectManager.GetLoadedObject(ObjectEntryDescriptor::Parse(destinationSurface)); + auto surfaceObj = objectManager.GetLoadedObject(OpenRCT2::ObjectEntryDescriptor::Parse(destinationSurface)); if (surfaceObj == nullptr) { OpenRCT2::Guard::Assert(0, "Surface object not found"); @@ -636,13 +636,13 @@ static void ApplyPathFixes(const json_t& scenarioPatch) auto railingsObjIndex = objectManager.GetLoadedObjectEntryIndex(railings); auto surfaceObjIndex = objectManager.GetLoadedObjectEntryIndex(surface); - if (railingsObjIndex == kObjectEntryIndexNull) + if (railingsObjIndex == OpenRCT2::kObjectEntryIndexNull) { OpenRCT2::Guard::Assert(0, "Railings object not found"); return; } - if (surfaceObjIndex == kObjectEntryIndexNull) + if (surfaceObjIndex == OpenRCT2::kObjectEntryIndexNull) { OpenRCT2::Guard::Assert(0, "Surface object not found"); return; diff --git a/src/openrct2/rct2/RCT2.h b/src/openrct2/rct2/RCT2.h index abc7fcf33c..2590d3d66b 100644 --- a/src/openrct2/rct2/RCT2.h +++ b/src/openrct2/rct2/RCT2.h @@ -20,11 +20,15 @@ #include struct RideObjectEntry; -class ObjectList; enum class EditorStep : uint8_t; enum class VehicleColourSettings : uint8_t; enum class ScenarioCategory : uint8_t; +namespace OpenRCT2 +{ + class ObjectList; +} + namespace OpenRCT2::RCT2 { constexpr StringId kRCT2RideStringStart = 2; diff --git a/src/openrct2/ride/Ride.h b/src/openrct2/ride/Ride.h index d2503250c7..10a376a101 100644 --- a/src/openrct2/ride/Ride.h +++ b/src/openrct2/ride/Ride.h @@ -34,7 +34,6 @@ struct IObjectManager; class Formatter; -class StationObject; struct Ride; struct RideTypeDescriptor; struct Guest; @@ -45,6 +44,11 @@ struct ResultWithMessage; struct TileElement; struct TrackElement; +namespace OpenRCT2 +{ + class StationObject; +} + constexpr uint8_t kRideAdjacencyCheckDistance = 5; constexpr uint8_t kTuneIDNull = 0xFF; @@ -151,7 +155,7 @@ struct Ride ride_type_t type{ kRideTypeNull }; // pointer to static info. for example, wild mouse type is 0x36, subtype is // 0x4c. - ObjectEntryIndex subtype{ kObjectEntryIndexNull }; + OpenRCT2::ObjectEntryIndex subtype{ OpenRCT2::kObjectEntryIndexNull }; RideMode mode{}; VehicleColourSettings vehicleColourSettings{}; VehicleColour vehicleColours[OpenRCT2::Limits::kMaxVehicleColours]{}; @@ -283,8 +287,8 @@ struct Ride money64 incomePerHour{}; money64 profit{}; TrackColour trackColours[kNumRideColourSchemes]{}; - ObjectEntryIndex music{ kObjectEntryIndexNull }; - ObjectEntryIndex entranceStyle{ kObjectEntryIndexNull }; + OpenRCT2::ObjectEntryIndex music{ OpenRCT2::kObjectEntryIndexNull }; + OpenRCT2::ObjectEntryIndex entranceStyle{ OpenRCT2::kObjectEntryIndexNull }; uint16_t vehicleChangeTimeout{}; uint8_t numBlockBrakes{}; uint8_t liftHillSpeed{}; @@ -346,7 +350,7 @@ public: void remove(); void crash(uint8_t vehicleIndex); void setToDefaultInspectionInterval(); - void setRideEntry(ObjectEntryIndex entryIndex); + void setRideEntry(OpenRCT2::ObjectEntryIndex entryIndex); void setNumTrains(int32_t newNumTrains); void setNumCarsPerTrain(int32_t numCarsPerVehicle); @@ -420,8 +424,8 @@ public: int32_t getTotalLength() const; int32_t getTotalTime() const; - const StationObject* getStationObject() const; - const MusicObject* getMusicObject() const; + const OpenRCT2::StationObject* getStationObject() const; + const OpenRCT2::MusicObject* getMusicObject() const; bool hasLifecycleFlag(uint32_t flag) const; void setLifecycleFlag(uint32_t flag, bool on); @@ -848,8 +852,8 @@ Ride* RideAllocateAtIndex(RideId index); Ride& RideGetTemporaryForPreview(); void RideDelete(RideId id); -const RideObjectEntry* GetRideEntryByIndex(ObjectEntryIndex index); -std::string_view GetRideEntryName(ObjectEntryIndex index); +const RideObjectEntry* GetRideEntryByIndex(OpenRCT2::ObjectEntryIndex index); +std::string_view GetRideEntryName(OpenRCT2::ObjectEntryIndex index); int32_t RideGetCount(); void RideInitAll(); @@ -862,7 +866,7 @@ void RideClearBlockedTiles(const Ride& ride); Staff* RideGetMechanic(const Ride& ride); Staff* RideGetAssignedMechanic(const Ride& ride); VehicleColour RideGetVehicleColour(const Ride& ride, int32_t vehicleIndex); -int32_t RideGetUnusedPresetVehicleColour(ObjectEntryIndex subType); +int32_t RideGetUnusedPresetVehicleColour(OpenRCT2::ObjectEntryIndex subType); void RideSetVehicleColoursToRandomPreset(Ride& ride, uint8_t preset_index); void RideMeasurementsUpdate(); void RideBreakdownAddNewsItem(const Ride& ride); @@ -934,7 +938,7 @@ int32_t GetUnifiedBoosterSpeed(ride_type_t rideType, int32_t relativeSpeed); void FixInvalidVehicleSpriteSizes(); bool RideEntryHasCategory(const RideObjectEntry& rideEntry, RideCategory category); -ObjectEntryIndex RideGetEntryIndex(ride_type_t rideType, ObjectEntryIndex rideSubType); +OpenRCT2::ObjectEntryIndex RideGetEntryIndex(ride_type_t rideType, OpenRCT2::ObjectEntryIndex rideSubType); void DetermineRideEntranceAndExitLocations(); void RideClearLeftoverEntrances(const Ride& ride); diff --git a/src/openrct2/ride/RideTypes.h b/src/openrct2/ride/RideTypes.h index c03540692f..241f3d81e7 100644 --- a/src/openrct2/ride/RideTypes.h +++ b/src/openrct2/ride/RideTypes.h @@ -27,7 +27,7 @@ static ride_type_t constexpr kRideTypeNull = 0xFF; struct RideSelection { ride_type_t Type = kRideTypeNull; - ObjectEntryIndex EntryIndex = kObjectEntryIndexNull; + OpenRCT2::ObjectEntryIndex EntryIndex = OpenRCT2::kObjectEntryIndexNull; bool operator==(const RideSelection& other) const { diff --git a/src/openrct2/ride/TrackDesign.h b/src/openrct2/ride/TrackDesign.h index 353d15a1bd..9333c18ee9 100644 --- a/src/openrct2/ride/TrackDesign.h +++ b/src/openrct2/ride/TrackDesign.h @@ -61,7 +61,7 @@ struct TrackDesignEntranceElement struct TrackDesignSceneryElement { - ObjectEntryDescriptor sceneryObject{}; + OpenRCT2::ObjectEntryDescriptor sceneryObject{}; CoordsXYZ loc{}; uint8_t flags{}; colour_t primaryColour{}; @@ -143,7 +143,7 @@ enum class TrackDesignGameStateFlag struct TrackDesignTrackAndVehicleSettings { ride_type_t rtdIndex{}; - ObjectEntryDescriptor vehicleObject{}; + OpenRCT2::ObjectEntryDescriptor vehicleObject{}; uint8_t numberOfTrains{}; uint8_t numberOfCarsPerTrain{}; }; diff --git a/src/openrct2/ride/TrackPaint.h b/src/openrct2/ride/TrackPaint.h index 664391c363..ba9ef56165 100644 --- a/src/openrct2/ride/TrackPaint.h +++ b/src/openrct2/ride/TrackPaint.h @@ -15,7 +15,11 @@ #include "../paint/track/Support.h" #include "../world/Map.h" -class StationObject; +namespace OpenRCT2 +{ + class StationObject; +} + struct Ride; constexpr uint8_t kTrackMap2x2[][4] = { @@ -408,7 +412,7 @@ bool TrackPaintUtilHasFence( enum edge_t edge, const CoordsXY& position, const TrackElement& trackElement, const Ride& ride, uint8_t rotation); void TrackPaintUtilPaintFloor( PaintSession& session, uint8_t edges, ImageId colourFlags, uint16_t height, const uint32_t floorSprites[4], - const StationObject* stationStyle); + const OpenRCT2::StationObject* stationStyle); void TrackPaintUtilPaintFences( PaintSession& session, uint8_t edges, const CoordsXY& position, const TrackElement& trackElement, const Ride& ride, const ImageId colourFlags, uint16_t height, const uint32_t fenceSprites[4], uint8_t rotation); @@ -423,10 +427,10 @@ enum class StationBaseType constexpr uint32_t kStationBaseTypeCount = 4; bool TrackPaintUtilDrawStationCovers( - PaintSession& session, enum edge_t edge, bool hasFence, const StationObject* stationObject, uint16_t height, + PaintSession& session, enum edge_t edge, bool hasFence, const OpenRCT2::StationObject* stationObject, uint16_t height, ImageId colour); bool TrackPaintUtilDrawStationCovers2( - PaintSession& session, enum edge_t edge, bool hasFence, const StationObject* stationObject, uint16_t height, + PaintSession& session, enum edge_t edge, bool hasFence, const OpenRCT2::StationObject* stationObject, uint16_t height, uint8_t stationVariant, ImageId colour); bool TrackPaintUtilDrawNarrowStationPlatform( PaintSession& session, const Ride& ride, Direction direction, int32_t height, int32_t zOffset, @@ -442,8 +446,8 @@ bool TrackPaintUtilDrawStationInverted( uint8_t stationVariant); bool TrackPaintUtilShouldPaintSupports(const CoordsXY& position); void TrackPaintUtilDrawPier( - PaintSession& session, const Ride& ride, const StationObject* stationObject, const CoordsXY& position, Direction direction, - int32_t height, const TrackElement& trackElement, uint8_t rotation); + PaintSession& session, const Ride& ride, const OpenRCT2::StationObject* stationObject, const CoordsXY& position, + Direction direction, int32_t height, const TrackElement& trackElement, uint8_t rotation); inline void TrackPaintUtilDrawStationTunnel(PaintSession& session, Direction direction, int32_t height) { PaintUtilPushTunnelRotated(session, direction, height, TunnelGroup::Square, TunnelSubType::Flat); diff --git a/src/openrct2/ride/Vehicle.h b/src/openrct2/ride/Vehicle.h index d0b641671a..9bf9bc0669 100644 --- a/src/openrct2/ride/Vehicle.h +++ b/src/openrct2/ride/Vehicle.h @@ -208,7 +208,7 @@ struct Vehicle : EntityBase uint8_t var_D3; MiniGolfAnimation mini_golf_current_animation; uint8_t mini_golf_flags; - ObjectEntryIndex ride_subtype; + OpenRCT2::ObjectEntryIndex ride_subtype; uint8_t seat_rotation; uint8_t target_seat_rotation; CoordsXY BoatLocation; diff --git a/src/openrct2/world/Banner.h b/src/openrct2/world/Banner.h index 45e3945bd7..6b2da60383 100644 --- a/src/openrct2/world/Banner.h +++ b/src/openrct2/world/Banner.h @@ -26,7 +26,7 @@ namespace OpenRCT2 struct GameState_t; } -constexpr ObjectEntryIndex kBannerNull = kObjectEntryIndexNull; +constexpr OpenRCT2::ObjectEntryIndex kBannerNull = OpenRCT2::kObjectEntryIndexNull; constexpr size_t kMaxBanners = 8192; constexpr uint8_t kScrollingModeNone = 255; @@ -43,7 +43,7 @@ using BannerFlags = FlagHolder; struct Banner { BannerIndex id = BannerIndex::GetNull(); - ObjectEntryIndex type = kBannerNull; + OpenRCT2::ObjectEntryIndex type = kBannerNull; BannerFlags flags{}; std::string text; mutable std::string formattedTextBuffer; diff --git a/src/openrct2/world/Footpath.h b/src/openrct2/world/Footpath.h index f73432c6dc..482d9c4b45 100644 --- a/src/openrct2/world/Footpath.h +++ b/src/openrct2/world/Footpath.h @@ -12,9 +12,13 @@ #include "../Identifiers.h" #include "../object/Object.h" -class FootpathObject; -class FootpathSurfaceObject; -class FootpathRailingsObject; +namespace OpenRCT2 +{ + class FootpathObject; + class FootpathSurfaceObject; + class FootpathRailingsObject; +} // namespace OpenRCT2 + struct PathElement; struct TileElement; @@ -59,13 +63,13 @@ namespace OpenRCT2::PathConstructFlag struct FootpathSelection { - ObjectEntryIndex LegacyPath = kObjectEntryIndexNull; - ObjectEntryIndex NormalSurface = kObjectEntryIndexNull; - ObjectEntryIndex QueueSurface = kObjectEntryIndexNull; - ObjectEntryIndex Railings = kObjectEntryIndexNull; + OpenRCT2::ObjectEntryIndex LegacyPath = OpenRCT2::kObjectEntryIndexNull; + OpenRCT2::ObjectEntryIndex NormalSurface = OpenRCT2::kObjectEntryIndexNull; + OpenRCT2::ObjectEntryIndex QueueSurface = OpenRCT2::kObjectEntryIndexNull; + OpenRCT2::ObjectEntryIndex Railings = OpenRCT2::kObjectEntryIndexNull; bool IsQueueSelected{}; - ObjectEntryIndex GetSelectedSurface() const + OpenRCT2::ObjectEntryIndex GetSelectedSurface() const { return IsQueueSelected ? QueueSurface : NormalSurface; } @@ -145,9 +149,9 @@ int32_t FootpathIsConnectedToMapEdge(const CoordsXYZ& footpathPos, int32_t direc void FootpathRemoveEdgesAt(const CoordsXY& footpathPos, TileElement* tileElement); bool FootpathSelectDefault(); -const FootpathObject* GetLegacyFootpathEntry(ObjectEntryIndex entryIndex); -const FootpathSurfaceObject* GetPathSurfaceEntry(ObjectEntryIndex entryIndex); -const FootpathRailingsObject* GetPathRailingsEntry(ObjectEntryIndex entryIndex); +const OpenRCT2::FootpathObject* GetLegacyFootpathEntry(OpenRCT2::ObjectEntryIndex entryIndex); +const OpenRCT2::FootpathSurfaceObject* GetPathSurfaceEntry(OpenRCT2::ObjectEntryIndex entryIndex); +const OpenRCT2::FootpathRailingsObject* GetPathRailingsEntry(OpenRCT2::ObjectEntryIndex entryIndex); void FootpathQueueChainReset(); void FootpathQueueChainPush(RideId rideIndex); diff --git a/src/openrct2/world/Scenery.h b/src/openrct2/world/Scenery.h index ec3bcbb98b..de4ec0ea49 100644 --- a/src/openrct2/world/Scenery.h +++ b/src/openrct2/world/Scenery.h @@ -77,7 +77,7 @@ void RestrictAllMiscScenery(); void MarkAllUnrestrictedSceneryAsInvented(); std::vector& GetRestrictedScenery(); void SetSceneryItemRestricted(const ScenerySelection& item, bool on); -bool ObjectTypeCanBeRestricted(ObjectType objectType); +bool ObjectTypeCanBeRestricted(OpenRCT2::ObjectType objectType); -ObjectType GetObjectTypeFromSceneryType(uint8_t type); -uint8_t GetSceneryTypeFromObjectType(ObjectType type); +OpenRCT2::ObjectType GetObjectTypeFromSceneryType(uint8_t type); +uint8_t GetSceneryTypeFromObjectType(OpenRCT2::ObjectType type); diff --git a/src/openrct2/world/ScenerySelection.h b/src/openrct2/world/ScenerySelection.h index 0e11d48f1f..1a1984ab73 100644 --- a/src/openrct2/world/ScenerySelection.h +++ b/src/openrct2/world/ScenerySelection.h @@ -17,7 +17,7 @@ struct ScenerySelection { uint8_t SceneryType{}; - ObjectEntryIndex EntryIndex = kObjectEntryIndexNull; + OpenRCT2::ObjectEntryIndex EntryIndex = OpenRCT2::kObjectEntryIndexNull; inline bool operator==(const ScenerySelection& rhs) const { @@ -31,6 +31,6 @@ struct ScenerySelection bool IsUndefined() const { - return EntryIndex == kObjectEntryIndexNull; + return EntryIndex == OpenRCT2::kObjectEntryIndexNull; } }; diff --git a/src/openrct2/world/tile_element/BannerElement.cpp b/src/openrct2/world/tile_element/BannerElement.cpp index 5f7bfbaea4..74c83ec4ed 100644 --- a/src/openrct2/world/tile_element/BannerElement.cpp +++ b/src/openrct2/world/tile_element/BannerElement.cpp @@ -5,6 +5,8 @@ #include "../../object/ObjectManager.h" #include "../Banner.h" +using namespace OpenRCT2; + Banner* BannerElement::GetBanner() const { return ::GetBanner(GetIndex()); diff --git a/src/openrct2/world/tile_element/BannerElement.h b/src/openrct2/world/tile_element/BannerElement.h index 93efbbbc77..341c806839 100644 --- a/src/openrct2/world/tile_element/BannerElement.h +++ b/src/openrct2/world/tile_element/BannerElement.h @@ -12,7 +12,10 @@ #include "../Banner.h" #include "TileElementBase.h" -struct BannerSceneryEntry; +namespace OpenRCT2 +{ + struct BannerSceneryEntry; +} #pragma pack(push, 1) struct BannerElement : TileElementBase @@ -29,7 +32,7 @@ private: #pragma clang diagnostic pop public: Banner* GetBanner() const; - const BannerSceneryEntry* GetEntry() const; + const OpenRCT2::BannerSceneryEntry* GetEntry() const; BannerIndex GetIndex() const; void SetIndex(BannerIndex newIndex); diff --git a/src/openrct2/world/tile_element/EntranceElement.cpp b/src/openrct2/world/tile_element/EntranceElement.cpp index 3d1b5794e4..addfcd975d 100644 --- a/src/openrct2/world/tile_element/EntranceElement.cpp +++ b/src/openrct2/world/tile_element/EntranceElement.cpp @@ -16,6 +16,8 @@ #include "../../object/ObjectManager.h" #include "../Entrance.h" +using namespace OpenRCT2; + // rct2: 0x0097B974 static constexpr uint16_t kEntranceDirections[] = { (4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_ENTRANCE, diff --git a/src/openrct2/world/tile_element/EntranceElement.h b/src/openrct2/world/tile_element/EntranceElement.h index f021e9a647..c6bd59bafc 100644 --- a/src/openrct2/world/tile_element/EntranceElement.h +++ b/src/openrct2/world/tile_element/EntranceElement.h @@ -45,13 +45,13 @@ struct EntranceElement : TileElementBase static constexpr TileElementType kElementType = TileElementType::Entrance; private: - uint8_t entranceType; // 5 - uint8_t SequenceIndex; // 6. Only uses the lower nibble. - StationIndex stationIndex; // 7 - ObjectEntryIndex PathType; // 8 - RideId rideIndex; // A - uint8_t flags2; // C - ObjectEntryIndex entryIndex; // D + uint8_t entranceType; // 5 + uint8_t SequenceIndex; // 6. Only uses the lower nibble. + StationIndex stationIndex; // 7 + OpenRCT2::ObjectEntryIndex PathType; // 8 + RideId rideIndex; // A + uint8_t flags2; // C + OpenRCT2::ObjectEntryIndex entryIndex; // D #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-private-field" uint8_t Pad0F[1]; @@ -72,20 +72,20 @@ public: bool HasLegacyPathEntry() const; - ObjectEntryIndex GetLegacyPathEntryIndex() const; - const FootpathObject* GetLegacyPathEntry() const; - void SetLegacyPathEntryIndex(ObjectEntryIndex newPathType); + OpenRCT2::ObjectEntryIndex GetLegacyPathEntryIndex() const; + const OpenRCT2::FootpathObject* GetLegacyPathEntry() const; + void SetLegacyPathEntryIndex(OpenRCT2::ObjectEntryIndex newPathType); - ObjectEntryIndex GetSurfaceEntryIndex() const; - const FootpathSurfaceObject* GetSurfaceEntry() const; - void SetSurfaceEntryIndex(ObjectEntryIndex newIndex); + OpenRCT2::ObjectEntryIndex GetSurfaceEntryIndex() const; + const OpenRCT2::FootpathSurfaceObject* GetSurfaceEntry() const; + void SetSurfaceEntryIndex(OpenRCT2::ObjectEntryIndex newIndex); const PathSurfaceDescriptor* GetPathSurfaceDescriptor() const; int32_t GetDirections() const; - ObjectEntryIndex getEntryIndex() const; - void setEntryIndex(ObjectEntryIndex newIndex); + OpenRCT2::ObjectEntryIndex getEntryIndex() const; + void setEntryIndex(OpenRCT2::ObjectEntryIndex newIndex); }; static_assert(sizeof(EntranceElement) == kTileElementSize); #pragma pack(pop) diff --git a/src/openrct2/world/tile_element/LargeSceneryElement.cpp b/src/openrct2/world/tile_element/LargeSceneryElement.cpp index 5a3b0ee184..bc58bd0917 100644 --- a/src/openrct2/world/tile_element/LargeSceneryElement.cpp +++ b/src/openrct2/world/tile_element/LargeSceneryElement.cpp @@ -16,6 +16,8 @@ #include +using namespace OpenRCT2; + colour_t LargeSceneryElement::GetPrimaryColour() const { return Colour[0]; diff --git a/src/openrct2/world/tile_element/LargeSceneryElement.h b/src/openrct2/world/tile_element/LargeSceneryElement.h index b37810e81f..40d4d5584b 100644 --- a/src/openrct2/world/tile_element/LargeSceneryElement.h +++ b/src/openrct2/world/tile_element/LargeSceneryElement.h @@ -27,7 +27,7 @@ struct LargeSceneryElement : TileElementBase static constexpr TileElementType kElementType = TileElementType::LargeScenery; private: - ObjectEntryIndex EntryIndex; + OpenRCT2::ObjectEntryIndex EntryIndex; ::BannerIndex BannerIndex; uint8_t SequenceIndex; uint8_t Colour[3]; @@ -38,10 +38,10 @@ private: #pragma clang diagnostic pop public: - ObjectEntryIndex GetEntryIndex() const; - void SetEntryIndex(ObjectEntryIndex newIndex); - const LargeSceneryEntry* GetEntry() const; - const LargeSceneryObject* GetObject() const; + OpenRCT2::ObjectEntryIndex GetEntryIndex() const; + void SetEntryIndex(OpenRCT2::ObjectEntryIndex newIndex); + const OpenRCT2::LargeSceneryEntry* GetEntry() const; + const OpenRCT2::LargeSceneryObject* GetObject() const; uint8_t GetSequenceIndex() const; void SetSequenceIndex(uint8_t newIndex); diff --git a/src/openrct2/world/tile_element/PathElement.cpp b/src/openrct2/world/tile_element/PathElement.cpp index e1ba6e89de..5bde81cef8 100644 --- a/src/openrct2/world/tile_element/PathElement.cpp +++ b/src/openrct2/world/tile_element/PathElement.cpp @@ -19,6 +19,8 @@ #include "../../object/PathAdditionEntry.h" #include "../Footpath.h" +using namespace OpenRCT2; + bool PathElement::IsSloped() const { return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED) != 0; diff --git a/src/openrct2/world/tile_element/PathElement.h b/src/openrct2/world/tile_element/PathElement.h index 10e3749f18..3c63603416 100644 --- a/src/openrct2/world/tile_element/PathElement.h +++ b/src/openrct2/world/tile_element/PathElement.h @@ -12,15 +12,19 @@ #include "../../Identifiers.h" #include "TileElementBase.h" -class FootpathObject; -class FootpathRailingsObject; -class FootpathSurfaceObject; -struct PathAdditionEntry; +namespace OpenRCT2 +{ + class FootpathObject; + class FootpathRailingsObject; + class FootpathSurfaceObject; + struct PathAdditionEntry; + + using ObjectEntryIndex = uint16_t; +} // namespace OpenRCT2 + struct PathRailingsDescriptor; struct PathSurfaceDescriptor; -using ObjectEntryIndex = uint16_t; - // Masks for values stored in TileElement.type enum { @@ -62,12 +66,12 @@ struct PathElement : TileElementBase static constexpr TileElementType kElementType = TileElementType::Path; private: - ObjectEntryIndex SurfaceIndex; // 5 - ObjectEntryIndex RailingsIndex; // 7 - uint8_t Additions; // 9 (0 means no addition) - uint8_t EdgesAndCorners; // 10 (edges in lower 4 bits, corners in upper 4) - uint8_t Flags2; // 11 - uint8_t SlopeDirection; // 12 + OpenRCT2::ObjectEntryIndex SurfaceIndex; // 5 + OpenRCT2::ObjectEntryIndex RailingsIndex; // 7 + uint8_t Additions; // 9 (0 means no addition) + uint8_t EdgesAndCorners; // 10 (edges in lower 4 bits, corners in upper 4) + uint8_t Flags2; // 11 + uint8_t SlopeDirection; // 12 union { uint8_t AdditionStatus; // 13, only used for litter bins @@ -76,18 +80,18 @@ private: ::StationIndex StationIndex; // 15 public: - ObjectEntryIndex GetLegacyPathEntryIndex() const; - const FootpathObject* GetLegacyPathEntry() const; - void SetLegacyPathEntryIndex(ObjectEntryIndex newIndex); + OpenRCT2::ObjectEntryIndex GetLegacyPathEntryIndex() const; + const OpenRCT2::FootpathObject* GetLegacyPathEntry() const; + void SetLegacyPathEntryIndex(OpenRCT2::ObjectEntryIndex newIndex); bool HasLegacyPathEntry() const; - ObjectEntryIndex GetSurfaceEntryIndex() const; - const FootpathSurfaceObject* GetSurfaceEntry() const; - void SetSurfaceEntryIndex(ObjectEntryIndex newIndex); + OpenRCT2::ObjectEntryIndex GetSurfaceEntryIndex() const; + const OpenRCT2::FootpathSurfaceObject* GetSurfaceEntry() const; + void SetSurfaceEntryIndex(OpenRCT2::ObjectEntryIndex newIndex); - ObjectEntryIndex GetRailingsEntryIndex() const; - const FootpathRailingsObject* GetRailingsEntry() const; - void SetRailingsEntryIndex(ObjectEntryIndex newIndex); + OpenRCT2::ObjectEntryIndex GetRailingsEntryIndex() const; + const OpenRCT2::FootpathRailingsObject* GetRailingsEntry() const; + void SetRailingsEntryIndex(OpenRCT2::ObjectEntryIndex newIndex); const PathSurfaceDescriptor* GetSurfaceDescriptor() const; const PathRailingsDescriptor* GetRailingsDescriptor() const; @@ -133,10 +137,10 @@ public: bool HasAddition() const; uint8_t GetAddition() const; - ObjectEntryIndex GetAdditionEntryIndex() const; - const PathAdditionEntry* GetAdditionEntry() const; + OpenRCT2::ObjectEntryIndex GetAdditionEntryIndex() const; + const OpenRCT2::PathAdditionEntry* GetAdditionEntry() const; void SetAddition(uint8_t newAddition); - void SetAdditionEntryIndex(ObjectEntryIndex entryIndex); + void SetAdditionEntryIndex(OpenRCT2::ObjectEntryIndex entryIndex); bool AdditionIsGhost() const; void SetAdditionIsGhost(bool isGhost); diff --git a/src/openrct2/world/tile_element/SmallSceneryElement.cpp b/src/openrct2/world/tile_element/SmallSceneryElement.cpp index 10fb8376c1..c1c0d6f025 100644 --- a/src/openrct2/world/tile_element/SmallSceneryElement.cpp +++ b/src/openrct2/world/tile_element/SmallSceneryElement.cpp @@ -27,6 +27,8 @@ #include +using namespace OpenRCT2; + uint8_t SmallSceneryElement::GetSceneryQuadrant() const { return (this->Type & kTileElementQuadrantMask) >> 6; diff --git a/src/openrct2/world/tile_element/SmallSceneryElement.h b/src/openrct2/world/tile_element/SmallSceneryElement.h index dc585ab490..9d37234664 100644 --- a/src/openrct2/world/tile_element/SmallSceneryElement.h +++ b/src/openrct2/world/tile_element/SmallSceneryElement.h @@ -25,19 +25,19 @@ struct SmallSceneryElement : TileElementBase static constexpr TileElementType kElementType = TileElementType::SmallScenery; private: - ObjectEntryIndex entryIndex; // 5 - uint8_t age; // 7 - uint8_t Colour[3]; // 8 - uint8_t Flags2; // B + OpenRCT2::ObjectEntryIndex entryIndex; // 5 + uint8_t age; // 7 + uint8_t Colour[3]; // 8 + uint8_t Flags2; // B #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-private-field" uint8_t Pad0B[4]; #pragma clang diagnostic pop public: - ObjectEntryIndex GetEntryIndex() const; - void SetEntryIndex(ObjectEntryIndex newIndex); - const SmallSceneryEntry* GetEntry() const; + OpenRCT2::ObjectEntryIndex GetEntryIndex() const; + void SetEntryIndex(OpenRCT2::ObjectEntryIndex newIndex); + const OpenRCT2::SmallSceneryEntry* GetEntry() const; uint8_t GetAge() const; void SetAge(uint8_t newAge); void IncreaseAge(const CoordsXY& sceneryPos); diff --git a/src/openrct2/world/tile_element/SurfaceElement.cpp b/src/openrct2/world/tile_element/SurfaceElement.cpp index 097482f0c5..c98bce2514 100644 --- a/src/openrct2/world/tile_element/SurfaceElement.cpp +++ b/src/openrct2/world/tile_element/SurfaceElement.cpp @@ -18,6 +18,8 @@ #include "Slope.h" #include "TileElement.h" +using namespace OpenRCT2; + ObjectEntryIndex SurfaceElement::GetSurfaceObjectIndex() const { return SurfaceStyle; diff --git a/src/openrct2/world/tile_element/SurfaceElement.h b/src/openrct2/world/tile_element/SurfaceElement.h index 036a15e448..94076a3e6f 100644 --- a/src/openrct2/world/tile_element/SurfaceElement.h +++ b/src/openrct2/world/tile_element/SurfaceElement.h @@ -62,13 +62,13 @@ public: uint8_t GetSlope() const; void SetSlope(uint8_t newSlope); - ObjectEntryIndex GetSurfaceObjectIndex() const; - TerrainSurfaceObject* GetSurfaceObject() const; - void SetSurfaceObjectIndex(ObjectEntryIndex newStyle); + OpenRCT2::ObjectEntryIndex GetSurfaceObjectIndex() const; + OpenRCT2::TerrainSurfaceObject* GetSurfaceObject() const; + void SetSurfaceObjectIndex(OpenRCT2::ObjectEntryIndex newStyle); - ObjectEntryIndex GetEdgeObjectIndex() const; - TerrainEdgeObject* GetEdgeObject() const; - void SetEdgeObjectIndex(ObjectEntryIndex newStyle); + OpenRCT2::ObjectEntryIndex GetEdgeObjectIndex() const; + OpenRCT2::TerrainEdgeObject* GetEdgeObject() const; + void SetEdgeObjectIndex(OpenRCT2::ObjectEntryIndex newStyle); bool CanGrassGrow() const; uint8_t GetGrassLength() const; diff --git a/src/openrct2/world/tile_element/WallElement.cpp b/src/openrct2/world/tile_element/WallElement.cpp index ac01477888..efad7d2637 100644 --- a/src/openrct2/world/tile_element/WallElement.cpp +++ b/src/openrct2/world/tile_element/WallElement.cpp @@ -6,12 +6,15 @@ * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ + #include "WallElement.h" #include "../../object/ObjectEntryManager.h" #include "../../object/WallSceneryEntry.h" #include "../Banner.h" +using namespace OpenRCT2; + uint8_t WallElement::GetSlope() const { return (Type & kTileElementQuadrantMask) >> 6; diff --git a/src/openrct2/world/tile_element/WallElement.h b/src/openrct2/world/tile_element/WallElement.h index 717a882c37..0985b5611c 100644 --- a/src/openrct2/world/tile_element/WallElement.h +++ b/src/openrct2/world/tile_element/WallElement.h @@ -34,11 +34,11 @@ struct WallElement : TileElementBase static constexpr TileElementType kElementType = TileElementType::Wall; private: - ObjectEntryIndex entryIndex; // 05 - colour_t colour_1; // 07 - colour_t colour_2; // 08 - colour_t colour_3; // 09 - BannerIndex banner_index; // 0A + OpenRCT2::ObjectEntryIndex entryIndex; // 05 + colour_t colour_1; // 07 + colour_t colour_2; // 08 + colour_t colour_3; // 09 + BannerIndex banner_index; // 0A uint8_t animation; // 0C 0b_dfff_fta0 d = direction, f = frame num, t = across track flag (not used), a = animating #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-private-field" @@ -48,7 +48,7 @@ private: public: uint16_t GetEntryIndex() const; void SetEntryIndex(uint16_t newIndex); - const WallSceneryEntry* GetEntry() const; + const OpenRCT2::WallSceneryEntry* GetEntry() const; uint8_t GetSlope() const; void SetSlope(uint8_t newslope);