From 4fd7afe0eed27a6924dd5f41862b1c0029d59502 Mon Sep 17 00:00:00 2001 From: Gymnasiast Date: Tue, 27 Jul 2021 15:06:14 +0200 Subject: [PATCH] Map legacy paths in old .PARK saves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows loading up older .PARK saves, created before splitting paths. This includes saves like the one in https://github.com/OpenRCT2/OpenRCT2/issues/15006 and some of @IntelOrca ’s test saves. --- src/openrct2/ParkFile.cpp | 261 +++++++++++++++++++++++++------ src/openrct2/world/Footpath.cpp | 5 + src/openrct2/world/TileElement.h | 1 + 3 files changed, 215 insertions(+), 52 deletions(-) diff --git a/src/openrct2/ParkFile.cpp b/src/openrct2/ParkFile.cpp index 0b03c0a0e0..09639790ba 100644 --- a/src/openrct2/ParkFile.cpp +++ b/src/openrct2/ParkFile.cpp @@ -60,6 +60,12 @@ using namespace OpenRCT2; static std::string_view MapToNewObjectIdentifier(std::string_view s); +static std::optional GetDATPathName(std::string_view newPathName); +static const FootpathMapping* GetFootpathMapping(const ObjectEntryDescriptor& desc); +static void UpdateFootpathsFromMapping( + ObjectEntryIndex* pathToSurfaceMap, ObjectEntryIndex* pathToQueueSurfaceMap, ObjectEntryIndex* pathToRailingsMap, + ObjectList& requiredObjects, ObjectEntryIndex& surfaceCount, ObjectEntryIndex& railingCount, ObjectEntryIndex entryIndex, + const FootpathMapping* footpathMapping); namespace OpenRCT2 { @@ -106,6 +112,9 @@ namespace OpenRCT2 private: std::unique_ptr _os; + ObjectEntryIndex _pathToSurfaceMap[MAX_PATH_OBJECTS]; + ObjectEntryIndex _pathToQueueSurfaceMap[MAX_PATH_OBJECTS]; + ObjectEntryIndex _pathToRailingsMap[MAX_PATH_OBJECTS]; public: void Load(const std::string_view& path) @@ -230,44 +239,81 @@ namespace OpenRCT2 if (os.GetMode() == OrcaStream::Mode::READING) { - ObjectList requiredObjects; - os.ReadWriteChunk(ParkFileChunkType::OBJECTS, [&requiredObjects](OrcaStream::ChunkStream& cs) { - auto numSubLists = cs.Read(); - for (size_t i = 0; i < numSubLists; i++) - { - auto objectType = static_cast(cs.Read()); - auto subListSize = static_cast(cs.Read()); - for (ObjectEntryIndex j = 0; j < subListSize; j++) - { - auto kind = cs.Read(); + std::fill(std::begin(_pathToSurfaceMap), std::end(_pathToSurfaceMap), OBJECT_ENTRY_INDEX_NULL); + std::fill(std::begin(_pathToQueueSurfaceMap), std::end(_pathToQueueSurfaceMap), OBJECT_ENTRY_INDEX_NULL); + std::fill(std::begin(_pathToRailingsMap), std::end(_pathToRailingsMap), OBJECT_ENTRY_INDEX_NULL); + auto* pathToSurfaceMap = _pathToSurfaceMap; + auto* pathToQueueSurfaceMap = _pathToQueueSurfaceMap; + auto* pathToRailingsMap = _pathToRailingsMap; - switch (kind) + ObjectList requiredObjects; + os.ReadWriteChunk( + ParkFileChunkType::OBJECTS, + [&requiredObjects, pathToSurfaceMap, pathToQueueSurfaceMap, + pathToRailingsMap](OrcaStream::ChunkStream& cs) { + ObjectEntryIndex surfaceCount = 0; + ObjectEntryIndex railingsCount = 0; + auto numSubLists = cs.Read(); + for (size_t i = 0; i < numSubLists; i++) + { + auto objectType = static_cast(cs.Read()); + auto subListSize = static_cast(cs.Read()); + for (ObjectEntryIndex j = 0; j < subListSize; j++) { - case DESCRIPTOR_NONE: - break; - case DESCRIPTOR_DAT: + auto kind = cs.Read(); + + switch (kind) { - rct_object_entry datEntry; - cs.Read(&datEntry, sizeof(datEntry)); - ObjectEntryDescriptor desc(datEntry); - requiredObjects.SetObject(j, desc); - break; + case DESCRIPTOR_NONE: + break; + case DESCRIPTOR_DAT: + { + rct_object_entry datEntry; + cs.Read(&datEntry, sizeof(datEntry)); + ObjectEntryDescriptor desc(datEntry); + if (datEntry.GetType() == ObjectType::Paths) + { + auto footpathMapping = GetFootpathMapping(desc); + if (footpathMapping != nullptr) + { + UpdateFootpathsFromMapping( + pathToSurfaceMap, pathToQueueSurfaceMap, pathToRailingsMap, requiredObjects, + surfaceCount, railingsCount, j, footpathMapping); + + continue; + } + } + + requiredObjects.SetObject(j, desc); + break; + } + case DESCRIPTOR_JSON: + { + ObjectEntryDescriptor desc; + desc.Type = objectType; + desc.Identifier = MapToNewObjectIdentifier(cs.Read()); + desc.Version = cs.Read(); + + auto footpathMapping = GetFootpathMapping(desc); + if (footpathMapping != nullptr) + { + // We have surface objects for this footpath + UpdateFootpathsFromMapping( + pathToSurfaceMap, pathToQueueSurfaceMap, pathToRailingsMap, requiredObjects, + surfaceCount, railingsCount, j, footpathMapping); + + continue; + } + + requiredObjects.SetObject(j, desc); + break; + } + default: + throw std::runtime_error("Unknown object descriptor kind."); } - case DESCRIPTOR_JSON: - { - ObjectEntryDescriptor desc; - desc.Type = objectType; - desc.Identifier = MapToNewObjectIdentifier(cs.Read()); - desc.Version = cs.Read(); - requiredObjects.SetObject(j, desc); - break; - } - default: - throw std::runtime_error("Unknown object descriptor kind."); } } - } - }); + }); RequiredObjects = std::move(requiredObjects); } else @@ -814,29 +860,59 @@ namespace OpenRCT2 void ReadWriteTilesChunk(OrcaStream& os) { - auto found = os.ReadWriteChunk(ParkFileChunkType::TILES, [](OrcaStream::ChunkStream& cs) { - cs.ReadWrite(gMapSize); // x - cs.Write(gMapSize); // y + auto* pathToSurfaceMap = _pathToSurfaceMap; + auto* pathToQueueSurfaceMap = _pathToQueueSurfaceMap; + auto* pathToRailingsMap = _pathToRailingsMap; - if (cs.GetMode() == OrcaStream::Mode::READING) - { - OpenRCT2::GetContext()->GetGameState()->InitAll(gMapSize); + auto found = os.ReadWriteChunk( + ParkFileChunkType::TILES, + [pathToSurfaceMap, pathToQueueSurfaceMap, pathToRailingsMap](OrcaStream::ChunkStream& cs) { + cs.ReadWrite(gMapSize); // x + cs.Write(gMapSize); // y - auto numElements = cs.Read(); + if (cs.GetMode() == OrcaStream::Mode::READING) + { + OpenRCT2::GetContext()->GetGameState()->InitAll(gMapSize); - std::vector tileElements; - tileElements.resize(numElements); - cs.Read(tileElements.data(), tileElements.size() * sizeof(TileElement)); - SetTileElements(std::move(tileElements)); - UpdateParkEntranceLocations(); - } - else - { - auto tileElements = GetReorganisedTileElementsWithoutGhosts(); - cs.Write(static_cast(tileElements.size())); - cs.Write(tileElements.data(), tileElements.size() * sizeof(TileElement)); - } - }); + auto numElements = cs.Read(); + + std::vector tileElements; + tileElements.resize(numElements); + cs.Read(tileElements.data(), tileElements.size() * sizeof(TileElement)); + SetTileElements(std::move(tileElements)); + { + tile_element_iterator it; + tile_element_iterator_begin(&it); + while (tile_element_iterator_next(&it)) + { + if (it.element->GetType() == TILE_ELEMENT_TYPE_PATH) + { + auto* pathElement = it.element->AsPath(); + if (pathElement->HasLegacyPathEntry()) + { + auto pathEntryIndex = pathElement->GetPathEntryIndex(); + if (pathToRailingsMap[pathEntryIndex] != OBJECT_ENTRY_INDEX_NULL) + { + if (pathElement->IsQueue()) + pathElement->SetSurfaceEntryIndex(pathToQueueSurfaceMap[pathEntryIndex]); + else + pathElement->SetSurfaceEntryIndex(pathToSurfaceMap[pathEntryIndex]); + + pathElement->SetRailingEntryIndex(pathToRailingsMap[pathEntryIndex]); + } + } + } + } + } + UpdateParkEntranceLocations(); + } + else + { + auto tileElements = GetReorganisedTileElementsWithoutGhosts(); + cs.Write(static_cast(tileElements.size())); + cs.Write(tileElements.data(), tileElements.size() * sizeof(TileElement)); + } + }); if (!found) { throw std::runtime_error("No tiles chunk found."); @@ -4305,3 +4381,84 @@ static std::string_view MapToNewObjectIdentifier(std::string_view s) } return s; } + +static std::map DATPathNames = { + { "rct2.pathash", "PATHASH " }, { "rct2.pathcrzy", "PATHCRZY" }, { "rct2.pathdirt", "PATHDIRT" }, + { "rct2.pathspce", "PATHSPCE" }, { "rct2.road", "ROAD " }, { "rct2.tarmacb", "TARMACB " }, + { "rct2.tarmacg", "TARMACG " }, { "rct2.tarmac", "TARMAC " }, { "rct2.1920path", "1920PATH" }, + { "rct2.futrpath", "FUTRPATH" }, { "rct2.futrpat2", "FUTRPAT2" }, { "rct2.jurrpath", "JURRPATH" }, + { "rct2.medipath", "MEDIPATH" }, { "rct2.mythpath", "MYTHPATH" }, { "rct2.ranbpath", "RANBPATH" }, +}; + +static std::optional GetDATPathName(std::string_view newPathName) +{ + auto it = DATPathNames.find(newPathName); + if (it != DATPathNames.end()) + { + return it->second; + } + return std::nullopt; +} + +static FootpathMapping _extendedFootpathMappings[] = { + { "rct1.path.tarmac", "rct1.footpath_surface.tarmac", "rct1.footpath_surface.queue_blue", "rct2.footpath_railings.wood" }, +}; + +static const FootpathMapping* GetFootpathMapping(const ObjectEntryDescriptor& desc) +{ + for (const auto& mapping : _extendedFootpathMappings) + { + if (mapping.Original == desc.GetName()) + { + return &mapping; + } + } + + // GetFootpathSurfaceId expects an old-style DAT identifier. In early versions of the NSF, + // we used JSON ids for legacy paths, so we have to map those to old DAT identifiers first. + if (desc.Generation == ObjectGeneration::JSON) + { + auto datPathName = GetDATPathName(desc.Identifier); + if (datPathName == std::nullopt) + { + return nullptr; + } + + rct_object_entry objectEntry = {}; + objectEntry.SetName(datPathName.value()); + return GetFootpathSurfaceId(ObjectEntryDescriptor(objectEntry)); + } + + // Even old .park saves with DAT identifiers somehow exist. + return GetFootpathSurfaceId(desc); +} + +static void UpdateFootpathsFromMapping( + ObjectEntryIndex* pathToSurfaceMap, ObjectEntryIndex* pathToQueueSurfaceMap, ObjectEntryIndex* pathToRailingsMap, + ObjectList& requiredObjects, ObjectEntryIndex& surfaceCount, ObjectEntryIndex& railingCount, ObjectEntryIndex entryIndex, + const FootpathMapping* footpathMapping) +{ + auto surfaceIndex = requiredObjects.Find(ObjectType::FootpathSurface, footpathMapping->NormalSurface); + if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL) + { + requiredObjects.SetObject(ObjectType::FootpathSurface, surfaceCount, footpathMapping->NormalSurface); + surfaceIndex = surfaceCount++; + } + pathToSurfaceMap[entryIndex] = surfaceIndex; + + surfaceIndex = requiredObjects.Find(ObjectType::FootpathSurface, footpathMapping->QueueSurface); + if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL) + { + requiredObjects.SetObject(ObjectType::FootpathSurface, surfaceCount, footpathMapping->QueueSurface); + surfaceIndex = surfaceCount++; + } + pathToQueueSurfaceMap[entryIndex] = surfaceIndex; + + auto railingIndex = requiredObjects.Find(ObjectType::FootpathRailings, footpathMapping->Railing); + if (railingIndex == OBJECT_ENTRY_INDEX_NULL) + { + requiredObjects.SetObject(ObjectType::FootpathRailings, railingCount, footpathMapping->Railing); + railingIndex = railingCount++; + } + pathToRailingsMap[entryIndex] = railingIndex; +} diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index c38f0e0615..8a51848bdc 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -1666,6 +1666,11 @@ void PathElement::SetPathEntryIndex(ObjectEntryIndex newIndex) Flags2 |= FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY; } +bool PathElement::HasLegacyPathEntry() const +{ + return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) != 0; +} + ObjectEntryIndex PathElement::GetSurfaceEntryIndex() const { if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) diff --git a/src/openrct2/world/TileElement.h b/src/openrct2/world/TileElement.h index d0da2b20fd..b2a660fc94 100644 --- a/src/openrct2/world/TileElement.h +++ b/src/openrct2/world/TileElement.h @@ -277,6 +277,7 @@ public: FootpathObject* GetPathEntry() const; ObjectEntryIndex GetPathEntryIndex() const; void SetPathEntryIndex(ObjectEntryIndex newIndex); + bool HasLegacyPathEntry() const; ObjectEntryIndex GetSurfaceEntryIndex() const; FootpathSurfaceObject* GetSurfaceEntry() const;