#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * * OpenRCT2 is the work of many authors, a full list can be found in contributors.md * For more information, visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * A full copy of the GNU General Public License can be found in licence.txt *****************************************************************************/ #pragma endregion #include #include #include #include "../Context.h" #include "../config/Config.h" #include "../core/Collections.hpp" #include "../core/Console.hpp" #include "../core/File.h" #include "../core/FileIndex.hpp" #include "../core/FileStream.hpp" #include "../core/Path.hpp" #include "../core/String.hpp" #include "../object/ObjectRepository.h" #include "../object/RideObject.h" #include "../PlatformEnvironment.h" #include "RideGroupManager.h" #include "TrackDesignRepository.h" #include "TrackDesign.h" using namespace OpenRCT2; struct TrackRepositoryItem { std::string Name; std::string Path; uint8 RideType = 0; std::string ObjectEntry; uint32 Flags = 0; }; enum TRACK_REPO_ITEM_FLAGS { TRIF_READ_ONLY = (1 << 0), }; std::string GetNameFromTrackPath(const std::string &path) { std::string name = Path::GetFileNameWithoutExtension(path); //The track name should be the file name until the first instance of a dot name = name.substr(0, name.find_first_of(".")); return name; } class TrackDesignFileIndex final : public FileIndex { private: static constexpr uint32 MAGIC_NUMBER = 0x58444954; // TIDX static constexpr uint16 VERSION = 2; static constexpr auto PATTERN = "*.td4;*.td6"; public: explicit TrackDesignFileIndex(const IPlatformEnvironment &env) : FileIndex("track design index", MAGIC_NUMBER, VERSION, env.GetFilePath(PATHID::CACHE_TRACKS), std::string(PATTERN), std::vector({ env.GetDirectoryPath(DIRBASE::RCT1, DIRID::TRACK), env.GetDirectoryPath(DIRBASE::RCT2, DIRID::TRACK), env.GetDirectoryPath(DIRBASE::USER, DIRID::TRACK) })) { } public: std::tuple Create(const std::string &path) const override { auto td6 = track_design_open(path.c_str()); if (td6 != nullptr) { TrackRepositoryItem item; item.Name = GetNameFromTrackPath(path); item.Path = path; item.RideType = td6->type; item.ObjectEntry = std::string(td6->vehicle_object.name, 8); item.Flags = 0; if (IsTrackReadOnly(path)) { item.Flags |= TRIF_READ_ONLY; } track_design_dispose(td6); return std::make_tuple(true, item); } else { return std::make_tuple(true, TrackRepositoryItem()); } } protected: void Serialise(IStream * stream, const TrackRepositoryItem &item) const override { stream->WriteString(item.Name); stream->WriteString(item.Path); stream->WriteValue(item.RideType); stream->WriteString(item.ObjectEntry); stream->WriteValue(item.Flags); } TrackRepositoryItem Deserialise(IStream * stream) const override { TrackRepositoryItem item; item.Name = stream->ReadStdString(); item.Path = stream->ReadStdString(); item.RideType = stream->ReadValue(); item.ObjectEntry = stream->ReadStdString(); item.Flags = stream->ReadValue(); return item; } private: bool IsTrackReadOnly(const std::string &path) const { return String::StartsWith(path, SearchPaths[0]) || String::StartsWith(path, SearchPaths[1]); } }; class TrackDesignRepository final : public ITrackDesignRepository { private: std::shared_ptr const _env; TrackDesignFileIndex const _fileIndex; std::vector _items; public: explicit TrackDesignRepository(std::shared_ptr env) : _env(env), _fileIndex(*env) { Guard::ArgumentNotNull(env); } ~TrackDesignRepository() = default; size_t GetCount() const override { return _items.size(); } /** * * @param rideType * @param entry The entry name to count the track list of. Leave empty to count track list for the non-separated types (e.g. Hyper-Twister, Car Ride) * @return */ size_t GetCountForObjectEntry(uint8 rideType, const std::string &entry) const override { size_t count = 0; const IObjectRepository * repo = GetContext()->GetObjectRepository(); for (const auto &item : _items) { if (item.RideType != rideType) { continue; } bool entryIsNotSeparate = false; if (entry.empty()) { const ObjectRepositoryItem * ori = repo->FindObject(item.ObjectEntry.c_str()); if (ori == nullptr || !RideGroupManager::RideTypeIsIndependent(rideType)) entryIsNotSeparate = true; } if (entryIsNotSeparate || String::Equals(item.ObjectEntry, entry, true)) { count++; } } return count; } size_t GetCountForRideGroup(uint8 rideType, const RideGroup * rideGroup) const override { size_t count = 0; const IObjectRepository * repo = GetContext()->GetObjectRepository(); for (const auto &item : _items) { if (item.RideType != rideType) { continue; } const ObjectRepositoryItem * ori = repo->FindObject(item.ObjectEntry.c_str()); uint8 rideGroupIndex = (ori != nullptr) ? ori->RideGroupIndex : 0; const RideGroup * itemRideGroup = RideGroupManager::RideGroupFind(rideType, rideGroupIndex); if (itemRideGroup != nullptr && itemRideGroup->Equals(rideGroup)) { count++; } } return count; } /** * * @param outRefs * @param rideType * @param entry The entry name to build a track list for. Leave empty to build track list for the non-separated types (e.g. Hyper-Twister, Car Ride) * @return */ std::vector GetItemsForObjectEntry(uint8 rideType, const std::string &entry) const override { std::vector refs; const IObjectRepository * repo = GetContext()->GetObjectRepository(); for (const auto &item : _items) { if (item.RideType != rideType) { continue; } bool entryIsNotSeparate = false; if (entry.empty()) { const ObjectRepositoryItem * ori = repo->FindObject(item.ObjectEntry.c_str()); if (ori == nullptr || !RideGroupManager::RideTypeIsIndependent(rideType)) entryIsNotSeparate = true; } if (entryIsNotSeparate || String::Equals(item.ObjectEntry, entry, true)) { track_design_file_ref ref; ref.name = String::Duplicate(GetNameFromTrackPath(item.Path)); ref.path = String::Duplicate(item.Path); refs.push_back(ref); } } return refs; } std::vector GetItemsForRideGroup(uint8 rideType, const RideGroup * rideGroup) const override { std::vector refs; const IObjectRepository * repo = GetContext()->GetObjectRepository(); for (const auto &item : _items) { if (item.RideType != rideType) { continue; } const ObjectRepositoryItem * ori = repo->FindObject(item.ObjectEntry.c_str()); uint8 rideGroupIndex = (ori != nullptr) ? ori->RideGroupIndex : 0; const RideGroup * itemRideGroup = RideGroupManager::RideGroupFind(rideType, rideGroupIndex); if (itemRideGroup != nullptr && itemRideGroup->Equals(rideGroup)) { track_design_file_ref ref; ref.name = String::Duplicate(GetNameFromTrackPath(item.Path)); ref.path = String::Duplicate(item.Path); refs.push_back(ref); } } return refs; } void Scan() override { _items.clear(); auto trackDesigns = _fileIndex.LoadOrBuild(); for (const auto &td : trackDesigns) { _items.push_back(td); } SortItems(); } bool Delete(const std::string &path) override { bool result = false; size_t index = GetTrackIndex(path); if (index != SIZE_MAX) { const TrackRepositoryItem * item = &_items[index]; if (!(item->Flags & TRIF_READ_ONLY)) { if (File::Delete(path)) { _items.erase(_items.begin() + index); result = true; } } } return result; } std::string Rename(const std::string &path, const std::string &newName) override { std::string result; size_t index = GetTrackIndex(path); if (index != SIZE_MAX) { TrackRepositoryItem * item = &_items[index]; if (!(item->Flags & TRIF_READ_ONLY)) { std::string directory = Path::GetDirectory(path); std::string newPath = Path::Combine(directory, newName + Path::GetExtension(path)); if (File::Move(path, newPath)) { item->Name = newName; item->Path = newPath; SortItems(); result = newPath; } } } return result; } std::string Install(const std::string &path) override { std::string result; std::string fileName = Path::GetFileName(path); std::string installDir = _env->GetDirectoryPath(DIRBASE::USER, DIRID::TRACK); std::string newPath = Path::Combine(installDir, fileName); if (File::Copy(path, newPath, false)) { auto td = _fileIndex.Create(path); if (std::get<0>(td)) { _items.push_back(std::get<1>(td)); SortItems(); result = path; } } return result; } private: void SortItems() { std::sort(_items.begin(), _items.end(), [](const TrackRepositoryItem &a, const TrackRepositoryItem &b) -> bool { if (a.RideType != b.RideType) { return a.RideType < b.RideType; } return String::Compare(a.Name, b.Name) < 0; }); } size_t GetTrackIndex(const std::string &path) const { for (size_t i = 0; i < _items.size(); i++) { if (Path::Equals(_items[i].Path, path)) { return i; } } return SIZE_MAX; } TrackRepositoryItem * GetTrackItem(const std::string &path) { TrackRepositoryItem * result = nullptr; size_t index = GetTrackIndex(path); if (index != SIZE_MAX) { result = &_items[index]; } return result; } }; ITrackDesignRepository * CreateTrackDesignRepository(std::shared_ptr env) { return new TrackDesignRepository(env); } void track_repository_scan() { ITrackDesignRepository * repo = GetContext()->GetTrackDesignRepository(); repo->Scan(); } bool track_repository_delete(const utf8 * path) { ITrackDesignRepository * repo = GetContext()->GetTrackDesignRepository(); return repo->Delete(path); } bool track_repository_rename(const utf8 * path, const utf8 * newName) { ITrackDesignRepository * repo = GetContext()->GetTrackDesignRepository(); std::string newPath = repo->Rename(path, newName); return !newPath.empty(); } bool track_repository_install(const utf8 * srcPath) { ITrackDesignRepository * repo = GetContext()->GetTrackDesignRepository(); std::string newPath = repo->Install(srcPath); return !newPath.empty(); }