mirror of https://github.com/OpenRCT2/OpenRCT2.git
Map legacy paths in old .PARK saves
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.
This commit is contained in:
parent
d885c545d8
commit
4fd7afe0ee
|
@ -60,6 +60,12 @@
|
||||||
using namespace OpenRCT2;
|
using namespace OpenRCT2;
|
||||||
|
|
||||||
static std::string_view MapToNewObjectIdentifier(std::string_view s);
|
static std::string_view MapToNewObjectIdentifier(std::string_view s);
|
||||||
|
static std::optional<std::string_view> 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
|
namespace OpenRCT2
|
||||||
{
|
{
|
||||||
|
@ -106,6 +112,9 @@ namespace OpenRCT2
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<OrcaStream> _os;
|
std::unique_ptr<OrcaStream> _os;
|
||||||
|
ObjectEntryIndex _pathToSurfaceMap[MAX_PATH_OBJECTS];
|
||||||
|
ObjectEntryIndex _pathToQueueSurfaceMap[MAX_PATH_OBJECTS];
|
||||||
|
ObjectEntryIndex _pathToRailingsMap[MAX_PATH_OBJECTS];
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void Load(const std::string_view& path)
|
void Load(const std::string_view& path)
|
||||||
|
@ -230,44 +239,81 @@ namespace OpenRCT2
|
||||||
|
|
||||||
if (os.GetMode() == OrcaStream::Mode::READING)
|
if (os.GetMode() == OrcaStream::Mode::READING)
|
||||||
{
|
{
|
||||||
ObjectList requiredObjects;
|
std::fill(std::begin(_pathToSurfaceMap), std::end(_pathToSurfaceMap), OBJECT_ENTRY_INDEX_NULL);
|
||||||
os.ReadWriteChunk(ParkFileChunkType::OBJECTS, [&requiredObjects](OrcaStream::ChunkStream& cs) {
|
std::fill(std::begin(_pathToQueueSurfaceMap), std::end(_pathToQueueSurfaceMap), OBJECT_ENTRY_INDEX_NULL);
|
||||||
auto numSubLists = cs.Read<uint16_t>();
|
std::fill(std::begin(_pathToRailingsMap), std::end(_pathToRailingsMap), OBJECT_ENTRY_INDEX_NULL);
|
||||||
for (size_t i = 0; i < numSubLists; i++)
|
auto* pathToSurfaceMap = _pathToSurfaceMap;
|
||||||
{
|
auto* pathToQueueSurfaceMap = _pathToQueueSurfaceMap;
|
||||||
auto objectType = static_cast<ObjectType>(cs.Read<uint16_t>());
|
auto* pathToRailingsMap = _pathToRailingsMap;
|
||||||
auto subListSize = static_cast<ObjectEntryIndex>(cs.Read<uint32_t>());
|
|
||||||
for (ObjectEntryIndex j = 0; j < subListSize; j++)
|
|
||||||
{
|
|
||||||
auto kind = cs.Read<uint8_t>();
|
|
||||||
|
|
||||||
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<uint16_t>();
|
||||||
|
for (size_t i = 0; i < numSubLists; i++)
|
||||||
|
{
|
||||||
|
auto objectType = static_cast<ObjectType>(cs.Read<uint16_t>());
|
||||||
|
auto subListSize = static_cast<ObjectEntryIndex>(cs.Read<uint32_t>());
|
||||||
|
for (ObjectEntryIndex j = 0; j < subListSize; j++)
|
||||||
{
|
{
|
||||||
case DESCRIPTOR_NONE:
|
auto kind = cs.Read<uint8_t>();
|
||||||
break;
|
|
||||||
case DESCRIPTOR_DAT:
|
switch (kind)
|
||||||
{
|
{
|
||||||
rct_object_entry datEntry;
|
case DESCRIPTOR_NONE:
|
||||||
cs.Read(&datEntry, sizeof(datEntry));
|
break;
|
||||||
ObjectEntryDescriptor desc(datEntry);
|
case DESCRIPTOR_DAT:
|
||||||
requiredObjects.SetObject(j, desc);
|
{
|
||||||
break;
|
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<std::string>());
|
||||||
|
desc.Version = cs.Read<std::string>();
|
||||||
|
|
||||||
|
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<std::string>());
|
|
||||||
desc.Version = cs.Read<std::string>();
|
|
||||||
requiredObjects.SetObject(j, desc);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Unknown object descriptor kind.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
RequiredObjects = std::move(requiredObjects);
|
RequiredObjects = std::move(requiredObjects);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -814,29 +860,59 @@ namespace OpenRCT2
|
||||||
|
|
||||||
void ReadWriteTilesChunk(OrcaStream& os)
|
void ReadWriteTilesChunk(OrcaStream& os)
|
||||||
{
|
{
|
||||||
auto found = os.ReadWriteChunk(ParkFileChunkType::TILES, [](OrcaStream::ChunkStream& cs) {
|
auto* pathToSurfaceMap = _pathToSurfaceMap;
|
||||||
cs.ReadWrite(gMapSize); // x
|
auto* pathToQueueSurfaceMap = _pathToQueueSurfaceMap;
|
||||||
cs.Write(gMapSize); // y
|
auto* pathToRailingsMap = _pathToRailingsMap;
|
||||||
|
|
||||||
if (cs.GetMode() == OrcaStream::Mode::READING)
|
auto found = os.ReadWriteChunk(
|
||||||
{
|
ParkFileChunkType::TILES,
|
||||||
OpenRCT2::GetContext()->GetGameState()->InitAll(gMapSize);
|
[pathToSurfaceMap, pathToQueueSurfaceMap, pathToRailingsMap](OrcaStream::ChunkStream& cs) {
|
||||||
|
cs.ReadWrite(gMapSize); // x
|
||||||
|
cs.Write(gMapSize); // y
|
||||||
|
|
||||||
auto numElements = cs.Read<uint32_t>();
|
if (cs.GetMode() == OrcaStream::Mode::READING)
|
||||||
|
{
|
||||||
|
OpenRCT2::GetContext()->GetGameState()->InitAll(gMapSize);
|
||||||
|
|
||||||
std::vector<TileElement> tileElements;
|
auto numElements = cs.Read<uint32_t>();
|
||||||
tileElements.resize(numElements);
|
|
||||||
cs.Read(tileElements.data(), tileElements.size() * sizeof(TileElement));
|
std::vector<TileElement> tileElements;
|
||||||
SetTileElements(std::move(tileElements));
|
tileElements.resize(numElements);
|
||||||
UpdateParkEntranceLocations();
|
cs.Read(tileElements.data(), tileElements.size() * sizeof(TileElement));
|
||||||
}
|
SetTileElements(std::move(tileElements));
|
||||||
else
|
{
|
||||||
{
|
tile_element_iterator it;
|
||||||
auto tileElements = GetReorganisedTileElementsWithoutGhosts();
|
tile_element_iterator_begin(&it);
|
||||||
cs.Write(static_cast<uint32_t>(tileElements.size()));
|
while (tile_element_iterator_next(&it))
|
||||||
cs.Write(tileElements.data(), tileElements.size() * sizeof(TileElement));
|
{
|
||||||
}
|
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<uint32_t>(tileElements.size()));
|
||||||
|
cs.Write(tileElements.data(), tileElements.size() * sizeof(TileElement));
|
||||||
|
}
|
||||||
|
});
|
||||||
if (!found)
|
if (!found)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("No tiles chunk found.");
|
throw std::runtime_error("No tiles chunk found.");
|
||||||
|
@ -4305,3 +4381,84 @@ static std::string_view MapToNewObjectIdentifier(std::string_view s)
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::map<std::string_view, std::string_view> 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<std::string_view> 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;
|
||||||
|
}
|
||||||
|
|
|
@ -1666,6 +1666,11 @@ void PathElement::SetPathEntryIndex(ObjectEntryIndex newIndex)
|
||||||
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY;
|
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PathElement::HasLegacyPathEntry() const
|
||||||
|
{
|
||||||
|
return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
ObjectEntryIndex PathElement::GetSurfaceEntryIndex() const
|
ObjectEntryIndex PathElement::GetSurfaceEntryIndex() const
|
||||||
{
|
{
|
||||||
if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY)
|
if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY)
|
||||||
|
|
|
@ -277,6 +277,7 @@ public:
|
||||||
FootpathObject* GetPathEntry() const;
|
FootpathObject* GetPathEntry() const;
|
||||||
ObjectEntryIndex GetPathEntryIndex() const;
|
ObjectEntryIndex GetPathEntryIndex() const;
|
||||||
void SetPathEntryIndex(ObjectEntryIndex newIndex);
|
void SetPathEntryIndex(ObjectEntryIndex newIndex);
|
||||||
|
bool HasLegacyPathEntry() const;
|
||||||
|
|
||||||
ObjectEntryIndex GetSurfaceEntryIndex() const;
|
ObjectEntryIndex GetSurfaceEntryIndex() const;
|
||||||
FootpathSurfaceObject* GetSurfaceEntry() const;
|
FootpathSurfaceObject* GetSurfaceEntry() const;
|
||||||
|
|
Loading…
Reference in New Issue