From cc22b584fd472b03a7d966914c90d573e753931a Mon Sep 17 00:00:00 2001 From: Duncan Date: Sun, 28 Nov 2021 16:51:38 +0000 Subject: [PATCH] Introduce RCT2 namespace (#16037) * Introduce RCT2 namespace * Drop RCT2:: prefix where possible * Drop RCT2 from structure names * Reduce header includes --- src/openrct2-ui/windows/LoadSave.cpp | 2 +- src/openrct2-ui/windows/TrackDesignPlace.cpp | 2 - src/openrct2/FileClassifier.cpp | 2 +- src/openrct2/Game.cpp | 2 +- src/openrct2/Game.h | 7 +- src/openrct2/ParkFile.cpp | 14 +- src/openrct2/entity/EntityList.h | 5 - src/openrct2/management/Research.cpp | 2 +- src/openrct2/management/Research.h | 2 +- src/openrct2/object/RideObject.cpp | 2 +- src/openrct2/rct1/RCT1.h | 7 +- src/openrct2/rct1/S4Importer.cpp | 3 +- src/openrct2/rct2/RCT2.cpp | 473 +-- src/openrct2/rct2/RCT2.h | 2028 +++++----- src/openrct2/rct2/S6Importer.cpp | 3682 +++++++++--------- src/openrct2/rct2/T6Exporter.cpp | 196 +- src/openrct2/rct2/T6Exporter.h | 25 +- src/openrct2/rct2/T6Importer.cpp | 379 +- src/openrct2/ride/TrackDesign.cpp | 2 +- src/openrct2/ride/TrackDesignSave.cpp | 2 +- src/openrct2/scenario/ScenarioRepository.cpp | 10 +- 21 files changed, 3437 insertions(+), 3410 deletions(-) diff --git a/src/openrct2-ui/windows/LoadSave.cpp b/src/openrct2-ui/windows/LoadSave.cpp index c1fb6c311b..5cbf3319ad 100644 --- a/src/openrct2-ui/windows/LoadSave.cpp +++ b/src/openrct2-ui/windows/LoadSave.cpp @@ -1098,7 +1098,7 @@ static void WindowLoadsaveSelect(rct_window* w, const char* path) path_set_extension(pathBuffer, "td6", sizeof(pathBuffer)); - T6Exporter t6Export{ _trackDesign }; + RCT2::T6Exporter t6Export{ _trackDesign }; auto success = t6Export.SaveTrack(pathBuffer); diff --git a/src/openrct2-ui/windows/TrackDesignPlace.cpp b/src/openrct2-ui/windows/TrackDesignPlace.cpp index 50f894cb31..87ec03dbb5 100644 --- a/src/openrct2-ui/windows/TrackDesignPlace.cpp +++ b/src/openrct2-ui/windows/TrackDesignPlace.cpp @@ -41,8 +41,6 @@ constexpr int16_t TRACK_MINI_PREVIEW_WIDTH = 168; constexpr int16_t TRACK_MINI_PREVIEW_HEIGHT = 78; constexpr uint16_t TRACK_MINI_PREVIEW_SIZE = TRACK_MINI_PREVIEW_WIDTH * TRACK_MINI_PREVIEW_HEIGHT; -struct rct_track_td6; - static constexpr uint8_t _PaletteIndexColourEntrance = PALETTE_INDEX_20; // White static constexpr uint8_t _PaletteIndexColourExit = PALETTE_INDEX_10; // Black static constexpr uint8_t _PaletteIndexColourTrack = PALETTE_INDEX_248; // Grey (dark) diff --git a/src/openrct2/FileClassifier.cpp b/src/openrct2/FileClassifier.cpp index 8aee1095fc..51befdacdc 100644 --- a/src/openrct2/FileClassifier.cpp +++ b/src/openrct2/FileClassifier.cpp @@ -100,7 +100,7 @@ static bool TryClassifyAsS6(OpenRCT2::IStream* stream, ClassifiedFileInfo* resul try { auto chunkReader = SawyerChunkReader(stream); - auto s6Header = chunkReader.ReadChunkAs(); + auto s6Header = chunkReader.ReadChunkAs(); if (s6Header.type == S6_TYPE_SAVEDGAME) { result->Type = FILE_TYPE::SAVED_GAME; diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index 06e025d49d..8f42589603 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -385,7 +385,7 @@ void game_convert_strings_to_utf8() /** * Converts all the user strings and news item strings to RCT2 encoding. */ -void game_convert_strings_to_rct2(rct_s6_data* s6) +void game_convert_strings_to_rct2(RCT2::S6Data* s6) { // Scenario details utf8_to_rct2_self(s6->scenario_completed_name, sizeof(s6->scenario_completed_name)); diff --git a/src/openrct2/Game.h b/src/openrct2/Game.h index 53f85c78b6..d348352ce7 100644 --- a/src/openrct2/Game.h +++ b/src/openrct2/Game.h @@ -14,7 +14,10 @@ #include struct ParkLoadResult; -struct rct_s6_data; +namespace RCT2 +{ + struct S6Data; +} enum class GameCommand : int32_t { @@ -170,7 +173,7 @@ void save_game_cmd(const utf8* name = nullptr); void save_game_with_name(const utf8* name); void game_autosave(); void game_convert_strings_to_utf8(); -void game_convert_strings_to_rct2(rct_s6_data* s6); +void game_convert_strings_to_rct2(RCT2::S6Data* s6); void utf8_to_rct2_self(char* buffer, size_t length); void rct2_to_utf8_self(char* buffer, size_t length); void game_fix_save_vars(); diff --git a/src/openrct2/ParkFile.cpp b/src/openrct2/ParkFile.cpp index d169567b67..3de1d100d8 100644 --- a/src/openrct2/ParkFile.cpp +++ b/src/openrct2/ParkFile.cpp @@ -64,11 +64,11 @@ 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 const RCT2::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); + const RCT2::FootpathMapping* footpathMapping); namespace OpenRCT2 { @@ -4558,11 +4558,11 @@ static std::optional GetDATPathName(std::string_view newPathNa return std::nullopt; } -static FootpathMapping _extendedFootpathMappings[] = { +static RCT2::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) +static const RCT2::FootpathMapping* GetFootpathMapping(const ObjectEntryDescriptor& desc) { for (const auto& mapping : _extendedFootpathMappings) { @@ -4581,20 +4581,20 @@ static const FootpathMapping* GetFootpathMapping(const ObjectEntryDescriptor& de { rct_object_entry objectEntry = {}; objectEntry.SetName(datPathName.value()); - return GetFootpathSurfaceId(ObjectEntryDescriptor(objectEntry)); + return RCT2::GetFootpathSurfaceId(ObjectEntryDescriptor(objectEntry)); } return nullptr; } // Even old .park saves with DAT identifiers somehow exist. - return GetFootpathSurfaceId(desc); + return RCT2::GetFootpathSurfaceId(desc); } static void UpdateFootpathsFromMapping( ObjectEntryIndex* pathToSurfaceMap, ObjectEntryIndex* pathToQueueSurfaceMap, ObjectEntryIndex* pathToRailingsMap, ObjectList& requiredObjects, ObjectEntryIndex& surfaceCount, ObjectEntryIndex& railingCount, ObjectEntryIndex entryIndex, - const FootpathMapping* footpathMapping) + const RCT2::FootpathMapping* footpathMapping) { auto surfaceIndex = requiredObjects.Find(ObjectType::FootpathSurface, footpathMapping->NormalSurface); if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL) diff --git a/src/openrct2/entity/EntityList.h b/src/openrct2/entity/EntityList.h index a9699c8b94..ca181c6b34 100644 --- a/src/openrct2/entity/EntityList.h +++ b/src/openrct2/entity/EntityList.h @@ -18,11 +18,6 @@ #include #include -enum class EntityListId : uint8_t -{ - Count = 6, -}; - const std::list& GetEntityList(const EntityType id); uint16_t GetEntityListCount(EntityType list); diff --git a/src/openrct2/management/Research.cpp b/src/openrct2/management/Research.cpp index b5ae547ed7..9cecf92073 100644 --- a/src/openrct2/management/Research.cpp +++ b/src/openrct2/management/Research.cpp @@ -83,7 +83,7 @@ ResearchItem::ResearchItem(const RCT12ResearchItem& oldResearchItem) if (type == Research::EntryType::Ride) { auto* rideEntry = get_ride_entry(entryIndex); - baseRideType = rideEntry != nullptr ? RCT2RideTypeToOpenRCT2RideType(oldResearchItem.baseRideType, rideEntry) + baseRideType = rideEntry != nullptr ? RCT2::RCT2RideTypeToOpenRCT2RideType(oldResearchItem.baseRideType, rideEntry) : oldResearchItem.baseRideType; } else diff --git a/src/openrct2/management/Research.h b/src/openrct2/management/Research.h index 7fd9c444ad..def062b443 100644 --- a/src/openrct2/management/Research.h +++ b/src/openrct2/management/Research.h @@ -97,7 +97,7 @@ struct ResearchItem else { retItem.entryIndex = OpenRCT2EntryIndexToRCTEntryIndex(entryIndex); - retItem.baseRideType = OpenRCT2RideTypeToRCT2RideType(baseRideType); + retItem.baseRideType = RCT2::OpenRCT2RideTypeToRCT2RideType(baseRideType); retItem.type = static_cast(type); retItem.flags = (flags & ~RESEARCH_ENTRY_FLAG_FIRST_OF_TYPE); retItem.category = EnumValue(category); diff --git a/src/openrct2/object/RideObject.cpp b/src/openrct2/object/RideObject.cpp index 47f6ecaae2..e871c10bf5 100644 --- a/src/openrct2/object/RideObject.cpp +++ b/src/openrct2/object/RideObject.cpp @@ -46,7 +46,7 @@ static void RideObjectUpdateRideType(rct_ride_entry* rideEntry) auto oldRideType = rideEntry->ride_type[i]; if (oldRideType != RIDE_TYPE_NULL) { - rideEntry->ride_type[i] = RCT2RideTypeToOpenRCT2RideType(oldRideType, rideEntry); + rideEntry->ride_type[i] = RCT2::RCT2RideTypeToOpenRCT2RideType(oldRideType, rideEntry); } } } diff --git a/src/openrct2/rct1/RCT1.h b/src/openrct2/rct1/RCT1.h index d5c8e028d9..66b9957794 100644 --- a/src/openrct2/rct1/RCT1.h +++ b/src/openrct2/rct1/RCT1.h @@ -9,12 +9,9 @@ #pragma once -#include "../management/Award.h" -#include "../management/Research.h" #include "../rct12/RCT12.h" -#include "../world/Banner.h" -#include "../world/Climate.h" -#include "../world/MapAnimation.h" +#include "../ride/RideRatings.h" +#include "../ride/VehicleColour.h" #include "Limits.h" namespace RCT1 diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index 8e30d0aaa7..8527570c5e 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -36,6 +36,7 @@ #include "../interface/Window.h" #include "../localisation/Date.h" #include "../localisation/Localisation.h" +#include "../management/Award.h" #include "../management/Finance.h" #include "../management/Marketing.h" #include "../management/NewsItem.h" @@ -2499,7 +2500,7 @@ namespace RCT1 { const auto originalString = _s4.string_table[(stringId - USER_STRING_START) % 1024]; auto originalStringView = std::string_view( - originalString, GetRCT2StringBufferLen(originalString, USER_STRING_MAX_LENGTH)); + originalString, RCT2::GetRCT2StringBufferLen(originalString, USER_STRING_MAX_LENGTH)); auto asUtf8 = rct2_to_utf8(originalStringView, RCT2LanguageId::EnglishUK); auto justText = RCT12RemoveFormattingUTF8(asUtf8); return justText.data(); diff --git a/src/openrct2/rct2/RCT2.cpp b/src/openrct2/rct2/RCT2.cpp index 3e11da36c7..89eb75e1f3 100644 --- a/src/openrct2/rct2/RCT2.cpp +++ b/src/openrct2/rct2/RCT2.cpp @@ -18,249 +18,258 @@ #include -ObjectEntryIndex RCT2RideTypeToOpenRCT2RideType(uint8_t rct2RideType, const rct_ride_entry* rideEntry) +namespace RCT2 { - switch (rct2RideType) + ObjectEntryIndex RCT2RideTypeToOpenRCT2RideType(uint8_t rct2RideType, const rct_ride_entry* rideEntry) { - case RIDE_TYPE_CORKSCREW_ROLLER_COASTER: - if (rideEntry != nullptr && !(ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_VERTICAL_LOOP))) - return RIDE_TYPE_HYPERCOASTER; - return RIDE_TYPE_CORKSCREW_ROLLER_COASTER; - case RIDE_TYPE_JUNIOR_ROLLER_COASTER: - if (rideEntry != nullptr && ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_SLOPE_STEEP)) - return RIDE_TYPE_CLASSIC_MINI_ROLLER_COASTER; - return RIDE_TYPE_JUNIOR_ROLLER_COASTER; - case RIDE_TYPE_CAR_RIDE: - if (rideEntry != nullptr && ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_SLOPE_STEEP)) - return RIDE_TYPE_MONSTER_TRUCKS; - return RIDE_TYPE_CAR_RIDE; - case RIDE_TYPE_TWISTER_ROLLER_COASTER: - if (rideEntry != nullptr && rideEntry->flags & RIDE_ENTRY_FLAG_NO_INVERSIONS) - return RIDE_TYPE_HYPER_TWISTER; - return RIDE_TYPE_TWISTER_ROLLER_COASTER; - case RIDE_TYPE_STEEL_WILD_MOUSE: - if (rideEntry != nullptr && !(ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_SLOPE_STEEP))) - return RIDE_TYPE_SPINNING_WILD_MOUSE; - return RIDE_TYPE_STEEL_WILD_MOUSE; - - default: - return rct2RideType; - } -} - -bool RCT2RideTypeNeedsConversion(uint8_t rct2RideType) -{ - switch (rct2RideType) - { - case RIDE_TYPE_CORKSCREW_ROLLER_COASTER: - case RIDE_TYPE_JUNIOR_ROLLER_COASTER: - case RIDE_TYPE_CAR_RIDE: - case RIDE_TYPE_TWISTER_ROLLER_COASTER: - case RIDE_TYPE_STEEL_WILD_MOUSE: - return true; - - default: - return false; - } -} - -uint8_t OpenRCT2RideTypeToRCT2RideType(ObjectEntryIndex openrct2Type) -{ - switch (openrct2Type) - { - case RIDE_TYPE_HYPERCOASTER: - return RIDE_TYPE_CORKSCREW_ROLLER_COASTER; - case RIDE_TYPE_CLASSIC_MINI_ROLLER_COASTER: - return RIDE_TYPE_JUNIOR_ROLLER_COASTER; - case RIDE_TYPE_MONSTER_TRUCKS: - return RIDE_TYPE_CAR_RIDE; - case RIDE_TYPE_HYPER_TWISTER: - return RIDE_TYPE_TWISTER_ROLLER_COASTER; - case RIDE_TYPE_SPINNING_WILD_MOUSE: - return RIDE_TYPE_STEEL_WILD_MOUSE; - - default: - return openrct2Type; - } -} - -size_t GetRCT2StringBufferLen(const char* buffer, size_t maxBufferLen) -{ - constexpr char MULTIBYTE = static_cast(255); - size_t len = 0; - for (size_t i = 0; i < maxBufferLen; i++) - { - auto ch = buffer[i]; - if (ch == MULTIBYTE) + switch (rct2RideType) { - i += 2; + case RIDE_TYPE_CORKSCREW_ROLLER_COASTER: + if (rideEntry != nullptr && !(ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_VERTICAL_LOOP))) + return RIDE_TYPE_HYPERCOASTER; + return RIDE_TYPE_CORKSCREW_ROLLER_COASTER; + case RIDE_TYPE_JUNIOR_ROLLER_COASTER: + if (rideEntry != nullptr && ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_SLOPE_STEEP)) + return RIDE_TYPE_CLASSIC_MINI_ROLLER_COASTER; + return RIDE_TYPE_JUNIOR_ROLLER_COASTER; + case RIDE_TYPE_CAR_RIDE: + if (rideEntry != nullptr && ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_SLOPE_STEEP)) + return RIDE_TYPE_MONSTER_TRUCKS; + return RIDE_TYPE_CAR_RIDE; + case RIDE_TYPE_TWISTER_ROLLER_COASTER: + if (rideEntry != nullptr && rideEntry->flags & RIDE_ENTRY_FLAG_NO_INVERSIONS) + return RIDE_TYPE_HYPER_TWISTER; + return RIDE_TYPE_TWISTER_ROLLER_COASTER; + case RIDE_TYPE_STEEL_WILD_MOUSE: + if (rideEntry != nullptr && !(ride_entry_get_supported_track_pieces(rideEntry) & (1ULL << TRACK_SLOPE_STEEP))) + return RIDE_TYPE_SPINNING_WILD_MOUSE; + return RIDE_TYPE_STEEL_WILD_MOUSE; - // Check if reading two more bytes exceeds max buffer len - if (i < maxBufferLen) + default: + return rct2RideType; + } + } + + bool RCT2RideTypeNeedsConversion(uint8_t rct2RideType) + { + switch (rct2RideType) + { + case RIDE_TYPE_CORKSCREW_ROLLER_COASTER: + case RIDE_TYPE_JUNIOR_ROLLER_COASTER: + case RIDE_TYPE_CAR_RIDE: + case RIDE_TYPE_TWISTER_ROLLER_COASTER: + case RIDE_TYPE_STEEL_WILD_MOUSE: + return true; + + default: + return false; + } + } + + uint8_t OpenRCT2RideTypeToRCT2RideType(ObjectEntryIndex openrct2Type) + { + switch (openrct2Type) + { + case RIDE_TYPE_HYPERCOASTER: + return RIDE_TYPE_CORKSCREW_ROLLER_COASTER; + case RIDE_TYPE_CLASSIC_MINI_ROLLER_COASTER: + return RIDE_TYPE_JUNIOR_ROLLER_COASTER; + case RIDE_TYPE_MONSTER_TRUCKS: + return RIDE_TYPE_CAR_RIDE; + case RIDE_TYPE_HYPER_TWISTER: + return RIDE_TYPE_TWISTER_ROLLER_COASTER; + case RIDE_TYPE_SPINNING_WILD_MOUSE: + return RIDE_TYPE_STEEL_WILD_MOUSE; + + default: + return openrct2Type; + } + } + + size_t GetRCT2StringBufferLen(const char* buffer, size_t maxBufferLen) + { + constexpr char MULTIBYTE = static_cast(255); + size_t len = 0; + for (size_t i = 0; i < maxBufferLen; i++) + { + auto ch = buffer[i]; + if (ch == MULTIBYTE) { - len += 3; + i += 2; + + // Check if reading two more bytes exceeds max buffer len + if (i < maxBufferLen) + { + len += 3; + } } - } - else if (ch == '\0') - { - break; - } - else - { - len++; - } - } - return len; -} - -uint8_t rct2_ride::GetMinCarsPerTrain() const -{ - return min_max_cars_per_train >> 4; -} - -uint8_t rct2_ride::GetMaxCarsPerTrain() const -{ - return min_max_cars_per_train & 0xF; -} - -void rct2_ride::SetMinCarsPerTrain(uint8_t newValue) -{ - min_max_cars_per_train &= ~0xF0; - min_max_cars_per_train |= (newValue << 4); -} - -void rct2_ride::SetMaxCarsPerTrain(uint8_t newValue) -{ - min_max_cars_per_train &= ~0x0F; - min_max_cars_per_train |= newValue & 0x0F; -} - -bool RCT2TrackTypeIsBooster(uint8_t rideType, uint16_t trackType) -{ - // Boosters share their ID with the Spinning Control track. - return rideType != RIDE_TYPE_SPINNING_WILD_MOUSE && rideType != RIDE_TYPE_STEEL_WILD_MOUSE - && trackType == TrackElemType::Booster; -} - -track_type_t RCT2TrackTypeToOpenRCT2(RCT12TrackType origTrackType, uint8_t rideType, bool convertFlat) -{ - if (convertFlat && GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) - return RCT12FlatTrackTypeToOpenRCT2(origTrackType); - if (origTrackType == TrackElemType::RotationControlToggleAlias && !RCT2TrackTypeIsBooster(rideType, origTrackType)) - return TrackElemType::RotationControlToggle; - - return origTrackType; -} - -RCT12TrackType OpenRCT2TrackTypeToRCT2(track_type_t origTrackType) -{ - if (origTrackType == TrackElemType::RotationControlToggle) - return TrackElemType::RotationControlToggleAlias; - - // This function is safe to run this way round. - return OpenRCT2FlatTrackTypeToRCT12(origTrackType); -} - -static FootpathMapping _footpathMappings[] = { - // RCT2 mappings - { "PATHASH ", "rct2.footpath_surface.ash", "rct2.footpath_surface.queue_yellow", "rct2.footpath_railings.bamboo_black" }, - { "PATHCRZY", "rct2.footpath_surface.crazy_paving", "rct2.footpath_surface.queue_yellow", - "rct2.footpath_railings.concrete" }, - { "PATHDIRT", "rct2.footpath_surface.dirt", "rct2.footpath_surface.queue_yellow", "rct2.footpath_railings.bamboo_brown" }, - { "PATHSPCE", "rct2.footpath_surface.tarmac_red", "rct2.footpath_surface.queue_red", "rct2.footpath_railings.space" }, - { "ROAD ", "rct2.footpath_surface.road", "rct2.footpath_surface.queue_blue", "rct2.footpath_railings.wood" }, - { "TARMACB ", "rct2.footpath_surface.tarmac_brown", "rct2.footpath_surface.queue_yellow", - "rct2.footpath_railings.concrete" }, - { "TARMACG ", "rct2.footpath_surface.tarmac_green", "rct2.footpath_surface.queue_green", - "rct2.footpath_railings.concrete_green" }, - { "TARMAC ", "rct2.footpath_surface.tarmac", "rct2.footpath_surface.queue_blue", "rct2.footpath_railings.wood" }, - // Time Twister - { "1920PATH", "rct2tt.footpath_surface.pavement", "rct2tt.footpath_surface.queue_pavement", - "rct2tt.footpath_railings.pavement" }, - { "FUTRPATH", "rct2tt.footpath_surface.circuitboard", "rct2tt.footpath_surface.queue_circuitboard", - "rct2tt.footpath_railings.circuitboard" }, - { "FUTRPAT2", "rct2tt.footpath_surface.circuitboard", "rct2tt.footpath_surface.queue_circuitboard", - "rct2tt.footpath_railings.circuitboard_invisible" }, - { "JURRPATH", "rct2tt.footpath_surface.rocky", "rct2.footpath_surface.queue_yellow", "rct2tt.footpath_railings.rocky" }, - { "MEDIPATH", "rct2tt.footpath_surface.medieval", "rct2.footpath_surface.queue_yellow", - "rct2tt.footpath_railings.medieval" }, - { "MYTHPATH", "rct2tt.footpath_surface.mosaic", "rct2.footpath_surface.queue_yellow", - "rct2tt.footpath_railings.balustrade" }, - { "RANBPATH", "rct2tt.footpath_surface.rainbow", "rct2tt.footpath_surface.queue_rainbow", - "rct2tt.footpath_railings.rainbow" }, - - // RCT 1 mappings (for reverse lookup) - { "PATHASH ", "rct1aa.footpath_surface.ash", "rct1aa.footpath_surface.queue_yellow", - "rct2.footpath_railings.bamboo_black" }, - { "PATHCRZY", "rct1.footpath_surface.crazy_paving", "rct1aa.footpath_surface.queue_yellow", - "rct2.footpath_railings.concrete" }, - { "PATHDIRT", "rct1.footpath_surface.dirt", "rct1aa.footpath_surface.queue_yellow", "rct2.footpath_railings.bamboo_brown" }, - { "PATHSPCE", "rct1aa.footpath_surface.tarmac_red", "rct1.footpath_surface.queue_red", "rct1ll.footpath_railings.space" }, - { "TARMACB ", "rct1aa.footpath_surface.tarmac_brown", "rct1aa.footpath_surface.queue_yellow", - "rct2.footpath_railings.concrete" }, - { "TARMACG ", "rct1aa.footpath_surface.tarmac_green", "rct1aa.footpath_surface.queue_green", - "rct2.footpath_railings.concrete_green" }, - { "TARMAC ", "rct1.footpath_surface.tarmac", "rct1.footpath_surface.queue_blue", "rct2.footpath_railings.wood" }, - { "PATHCRZY", "rct1.footpath_surface.tiles_brown", "rct1aa.footpath_surface.queue_yellow", - "rct2.footpath_railings.concrete" }, - { "PATHCRZY", "rct1aa.footpath_surface.tiles_grey", "rct1.footpath_surface.queue_blue", "rct2.footpath_railings.concrete" }, - { "PATHCRZY", "rct1ll.footpath_surface.tiles_red", "rct1.footpath_surface.queue_red", "rct2.footpath_railings.concrete" }, - { "PATHCRZY", "rct1ll.footpath_surface.tiles_green", "rct1aa.footpath_surface.queue_green", - "rct2.footpath_railings.concrete" }, -}; - -const FootpathMapping* GetFootpathSurfaceId(const ObjectEntryDescriptor& desc, bool ideallyLoaded, bool isQueue) -{ - auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); - - auto name = desc.Entry.GetName(); - for (const auto& mapping : _footpathMappings) - { - if (mapping.Original == name) - { - if (ideallyLoaded) + else if (ch == '\0') { - auto obj = objManager.GetLoadedObject( - ObjectEntryDescriptor(isQueue ? mapping.QueueSurface : mapping.NormalSurface)); - if (obj == nullptr) - continue; - } - return &mapping; - } - } - return nullptr; -} - -std::optional GetBestObjectEntryForSurface(std::string_view surface, std::string_view railings) -{ - rct_object_entry result; - std::memset(&result, 0, sizeof(result)); - - result.SetType(ObjectType::Paths); - - auto foundMapping = false; - for (const auto& mapping : _footpathMappings) - { - if (surface == mapping.NormalSurface || surface == mapping.QueueSurface) - { - if (railings == mapping.Railing) - { - // Best match found - foundMapping = true; - result.SetName(mapping.Original); break; } - - if (!foundMapping) + else { - // Found a mapping, but keep searching to see if there is a closer match - foundMapping = true; - result.SetName(mapping.Original); + len++; } } + return len; } - if (foundMapping) - return result; - return {}; -} + uint8_t Ride::GetMinCarsPerTrain() const + { + return min_max_cars_per_train >> 4; + } + + uint8_t Ride::GetMaxCarsPerTrain() const + { + return min_max_cars_per_train & 0xF; + } + + void Ride::SetMinCarsPerTrain(uint8_t newValue) + { + min_max_cars_per_train &= ~0xF0; + min_max_cars_per_train |= (newValue << 4); + } + + void Ride::SetMaxCarsPerTrain(uint8_t newValue) + { + min_max_cars_per_train &= ~0x0F; + min_max_cars_per_train |= newValue & 0x0F; + } + + bool RCT2TrackTypeIsBooster(uint8_t rideType, uint16_t trackType) + { + // Boosters share their ID with the Spinning Control track. + return rideType != RIDE_TYPE_SPINNING_WILD_MOUSE && rideType != RIDE_TYPE_STEEL_WILD_MOUSE + && trackType == TrackElemType::Booster; + } + + track_type_t RCT2TrackTypeToOpenRCT2(RCT12TrackType origTrackType, uint8_t rideType, bool convertFlat) + { + if (convertFlat && GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) + return RCT12FlatTrackTypeToOpenRCT2(origTrackType); + if (origTrackType == TrackElemType::RotationControlToggleAlias && !RCT2TrackTypeIsBooster(rideType, origTrackType)) + return TrackElemType::RotationControlToggle; + + return origTrackType; + } + + RCT12TrackType OpenRCT2TrackTypeToRCT2(track_type_t origTrackType) + { + if (origTrackType == TrackElemType::RotationControlToggle) + return TrackElemType::RotationControlToggleAlias; + + // This function is safe to run this way round. + return OpenRCT2FlatTrackTypeToRCT12(origTrackType); + } + + static FootpathMapping _footpathMappings[] = { + // RCT2 mappings + { "PATHASH ", "rct2.footpath_surface.ash", "rct2.footpath_surface.queue_yellow", + "rct2.footpath_railings.bamboo_black" }, + { "PATHCRZY", "rct2.footpath_surface.crazy_paving", "rct2.footpath_surface.queue_yellow", + "rct2.footpath_railings.concrete" }, + { "PATHDIRT", "rct2.footpath_surface.dirt", "rct2.footpath_surface.queue_yellow", + "rct2.footpath_railings.bamboo_brown" }, + { "PATHSPCE", "rct2.footpath_surface.tarmac_red", "rct2.footpath_surface.queue_red", "rct2.footpath_railings.space" }, + { "ROAD ", "rct2.footpath_surface.road", "rct2.footpath_surface.queue_blue", "rct2.footpath_railings.wood" }, + { "TARMACB ", "rct2.footpath_surface.tarmac_brown", "rct2.footpath_surface.queue_yellow", + "rct2.footpath_railings.concrete" }, + { "TARMACG ", "rct2.footpath_surface.tarmac_green", "rct2.footpath_surface.queue_green", + "rct2.footpath_railings.concrete_green" }, + { "TARMAC ", "rct2.footpath_surface.tarmac", "rct2.footpath_surface.queue_blue", "rct2.footpath_railings.wood" }, + // Time Twister + { "1920PATH", "rct2tt.footpath_surface.pavement", "rct2tt.footpath_surface.queue_pavement", + "rct2tt.footpath_railings.pavement" }, + { "FUTRPATH", "rct2tt.footpath_surface.circuitboard", "rct2tt.footpath_surface.queue_circuitboard", + "rct2tt.footpath_railings.circuitboard" }, + { "FUTRPAT2", "rct2tt.footpath_surface.circuitboard", "rct2tt.footpath_surface.queue_circuitboard", + "rct2tt.footpath_railings.circuitboard_invisible" }, + { "JURRPATH", "rct2tt.footpath_surface.rocky", "rct2.footpath_surface.queue_yellow", "rct2tt.footpath_railings.rocky" }, + { "MEDIPATH", "rct2tt.footpath_surface.medieval", "rct2.footpath_surface.queue_yellow", + "rct2tt.footpath_railings.medieval" }, + { "MYTHPATH", "rct2tt.footpath_surface.mosaic", "rct2.footpath_surface.queue_yellow", + "rct2tt.footpath_railings.balustrade" }, + { "RANBPATH", "rct2tt.footpath_surface.rainbow", "rct2tt.footpath_surface.queue_rainbow", + "rct2tt.footpath_railings.rainbow" }, + + // RCT 1 mappings (for reverse lookup) + { "PATHASH ", "rct1aa.footpath_surface.ash", "rct1aa.footpath_surface.queue_yellow", + "rct2.footpath_railings.bamboo_black" }, + { "PATHCRZY", "rct1.footpath_surface.crazy_paving", "rct1aa.footpath_surface.queue_yellow", + "rct2.footpath_railings.concrete" }, + { "PATHDIRT", "rct1.footpath_surface.dirt", "rct1aa.footpath_surface.queue_yellow", + "rct2.footpath_railings.bamboo_brown" }, + { "PATHSPCE", "rct1aa.footpath_surface.tarmac_red", "rct1.footpath_surface.queue_red", + "rct1ll.footpath_railings.space" }, + { "TARMACB ", "rct1aa.footpath_surface.tarmac_brown", "rct1aa.footpath_surface.queue_yellow", + "rct2.footpath_railings.concrete" }, + { "TARMACG ", "rct1aa.footpath_surface.tarmac_green", "rct1aa.footpath_surface.queue_green", + "rct2.footpath_railings.concrete_green" }, + { "TARMAC ", "rct1.footpath_surface.tarmac", "rct1.footpath_surface.queue_blue", "rct2.footpath_railings.wood" }, + { "PATHCRZY", "rct1.footpath_surface.tiles_brown", "rct1aa.footpath_surface.queue_yellow", + "rct2.footpath_railings.concrete" }, + { "PATHCRZY", "rct1aa.footpath_surface.tiles_grey", "rct1.footpath_surface.queue_blue", + "rct2.footpath_railings.concrete" }, + { "PATHCRZY", "rct1ll.footpath_surface.tiles_red", "rct1.footpath_surface.queue_red", + "rct2.footpath_railings.concrete" }, + { "PATHCRZY", "rct1ll.footpath_surface.tiles_green", "rct1aa.footpath_surface.queue_green", + "rct2.footpath_railings.concrete" }, + }; + + const FootpathMapping* GetFootpathSurfaceId(const ObjectEntryDescriptor& desc, bool ideallyLoaded, bool isQueue) + { + auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); + + auto name = desc.Entry.GetName(); + for (const auto& mapping : _footpathMappings) + { + if (mapping.Original == name) + { + if (ideallyLoaded) + { + auto obj = objManager.GetLoadedObject( + ObjectEntryDescriptor(isQueue ? mapping.QueueSurface : mapping.NormalSurface)); + if (obj == nullptr) + continue; + } + return &mapping; + } + } + return nullptr; + } + + std::optional GetBestObjectEntryForSurface(std::string_view surface, std::string_view railings) + { + rct_object_entry result; + std::memset(&result, 0, sizeof(result)); + + result.SetType(ObjectType::Paths); + + auto foundMapping = false; + for (const auto& mapping : _footpathMappings) + { + if (surface == mapping.NormalSurface || surface == mapping.QueueSurface) + { + if (railings == mapping.Railing) + { + // Best match found + foundMapping = true; + result.SetName(mapping.Original); + break; + } + + if (!foundMapping) + { + // Found a mapping, but keep searching to see if there is a closer match + foundMapping = true; + result.SetName(mapping.Original); + } + } + } + + if (foundMapping) + return result; + return {}; + } +} // namespace RCT2 diff --git a/src/openrct2/rct2/RCT2.h b/src/openrct2/rct2/RCT2.h index 215ef7323f..1826a97a27 100644 --- a/src/openrct2/rct2/RCT2.h +++ b/src/openrct2/rct2/RCT2.h @@ -11,8 +11,6 @@ #include "../common.h" #include "../core/FileSystem.hpp" -#include "../entity/EntityList.h" -#include "../object/Object.h" #include "../rct12/RCT12.h" #include "../ride/RideRatings.h" #include "../ride/VehicleColour.h" @@ -21,1044 +19,1050 @@ #include #include -constexpr const rct_string_id RCT2_RIDE_STRING_START = 2; - -// clang-format off -constexpr const uint16_t RCT2_OBJECT_ENTRY_COUNT = - RCT2::Limits::MaxRideObject + - RCT2::Limits::MaxSmallSceneryObjects + - RCT2::Limits::MaxLargeSceneryObjects + - RCT2::Limits::MaxWallSceneryObjects + - RCT2::Limits::MaxBannerObjects + - RCT2::Limits::MaxPathObjects + - RCT2::Limits::MaxPathAdditionObjects + - RCT2::Limits::MaxScenereyGroupObjects + - RCT2::Limits::MaxParkEntranceObjects + - RCT2::Limits::MaxWaterObjects + - RCT2::Limits::MaxScenarioTextObjects; -// clang-format on -static_assert(RCT2_OBJECT_ENTRY_COUNT == 721); - -// clang-format off -constexpr const int32_t rct2_object_entry_group_counts[] = { - RCT2::Limits::MaxRideObject, - RCT2::Limits::MaxSmallSceneryObjects, - RCT2::Limits::MaxLargeSceneryObjects, - RCT2::Limits::MaxWallSceneryObjects, - RCT2::Limits::MaxBannerObjects, - RCT2::Limits::MaxPathObjects, - RCT2::Limits::MaxPathAdditionObjects, - RCT2::Limits::MaxScenereyGroupObjects, - RCT2::Limits::MaxParkEntranceObjects, - RCT2::Limits::MaxWaterObjects, - RCT2::Limits::MaxScenarioTextObjects, -}; -// clang-format on - -#pragma pack(push, 1) - struct rct_ride_entry; - -/** - * Ride structure. - * size: 0x0260 - */ -struct rct2_ride -{ - uint8_t type; // 0x000 - // pointer to static info. for example, wild mouse type is 0x36, subtype is - // 0x4c. - RCT12ObjectEntryIndex subtype; // 0x001 - uint16_t pad_002; // 0x002 - uint8_t mode; // 0x004 - uint8_t colour_scheme_type; // 0x005 - rct_vehicle_colour vehicle_colours[RCT2::Limits::MaxTrainsPerRide]; // 0x006 - uint8_t pad_046[0x03]; // 0x046, Used to be track colours in RCT1 without expansions - // 0 = closed, 1 = open, 2 = test - uint8_t status; // 0x049 - rct_string_id name; // 0x04A - union - { - uint32_t name_arguments; // 0x04C - struct - { - rct_string_id name_arguments_type_name; // 0x04C - uint16_t name_arguments_number; // 0x04E - }; - }; - RCT12xy8 overall_view; // 0x050 - RCT12xy8 station_starts[RCT2::Limits::MaxStationsPerRide]; // 0x052 - uint8_t station_heights[RCT2::Limits::MaxStationsPerRide]; // 0x05A - uint8_t station_length[RCT2::Limits::MaxStationsPerRide]; // 0x05E - uint8_t station_depart[RCT2::Limits::MaxStationsPerRide]; // 0x062 - // ride->vehicle index for current train waiting for passengers - // at station - uint8_t train_at_station[RCT2::Limits::MaxStationsPerRide]; // 0x066 - RCT12xy8 entrances[RCT2::Limits::MaxStationsPerRide]; // 0x06A - RCT12xy8 exits[RCT2::Limits::MaxStationsPerRide]; // 0x072 - uint16_t last_peep_in_queue[RCT2::Limits::MaxStationsPerRide]; // 0x07A - uint8_t pad_082[RCT2::Limits::MaxStationsPerRide]; // 0x082, Used to be number of peeps in queue in RCT1, but this - // has moved. - uint16_t vehicles[RCT2::Limits::MaxTrainsPerRide]; // 0x086, Points to the first car in the train - uint8_t depart_flags; // 0x0C6 - - // Not sure if these should be uint or sint. - uint8_t num_stations; // 0x0C7 - uint8_t num_vehicles; // 0x0C8 - uint8_t num_cars_per_train; // 0x0C9 - uint8_t proposed_num_vehicles; // 0x0CA - uint8_t proposed_num_cars_per_train; // 0x0CB - uint8_t max_trains; // 0x0CC - uint8_t min_max_cars_per_train; // 0x0CD - uint8_t min_waiting_time; // 0x0CE - uint8_t max_waiting_time; // 0x0CF - union - { - uint8_t operation_option; // 0x0D0 - uint8_t time_limit; // 0x0D0 - uint8_t num_laps; // 0x0D0 - uint8_t launch_speed; // 0x0D0 - uint8_t speed; // 0x0D0 - uint8_t rotations; // 0x0D0 - }; - - uint8_t boat_hire_return_direction; // 0x0D1 - RCT12xy8 boat_hire_return_position; // 0x0D2 - uint8_t measurement_index; // 0x0D4 - // bits 0 through 4 are the number of helix sections - // bit 5: spinning tunnel, water splash, or rapids - // bit 6: log reverser, waterfall - // bit 7: whirlpool - uint8_t special_track_elements; // 0x0D5 - uint8_t pad_0D6[2]; // 0x0D6 - // Divide this value by 29127 to get the human-readable max speed - // (in RCT2, display_speed = (max_speed * 9) >> 18) - int32_t max_speed; // 0x0D8 - int32_t average_speed; // 0x0DC - uint8_t current_test_segment; // 0x0E0 - uint8_t average_speed_test_timeout; // 0x0E1 - uint8_t pad_0E2[0x2]; // 0x0E2 - int32_t length[RCT2::Limits::MaxStationsPerRide]; // 0x0E4 - uint16_t time[RCT2::Limits::MaxStationsPerRide]; // 0x0F4 - fixed16_2dp max_positive_vertical_g; // 0x0FC - fixed16_2dp max_negative_vertical_g; // 0x0FE - fixed16_2dp max_lateral_g; // 0x100 - fixed16_2dp previous_vertical_g; // 0x102 - fixed16_2dp previous_lateral_g; // 0x104 - uint8_t pad_106[0x2]; // 0x106 - uint32_t testing_flags; // 0x108 - // x y map location of the current track piece during a test - // this is to prevent counting special tracks multiple times - RCT12xy8 cur_test_track_location; // 0x10C - // Next 3 variables are related (XXXX XYYY ZZZa aaaa) - uint16_t turn_count_default; // 0x10E X = current turn count - uint16_t turn_count_banked; // 0x110 - uint16_t turn_count_sloped; // 0x112 X = number turns > 3 elements - union - { - uint8_t inversions; // 0x114 (???X XXXX) - uint8_t holes; // 0x114 (???X XXXX) - // This is a very rough approximation of how much of the ride is undercover. - // It reaches the maximum value of 7 at about 50% undercover and doesn't increase beyond that. - uint8_t sheltered_eighths; // 0x114 (XXX?-????) - }; - // Y is number of powered lifts, X is drops - uint8_t drops; // 0x115 (YYXX XXXX) - uint8_t start_drop_height; // 0x116 - uint8_t highest_drop_height; // 0x117 - int32_t sheltered_length; // 0x118 - // Unused always 0? Should affect nausea - uint16_t var_11C; // 0x11C - uint8_t num_sheltered_sections; // 0x11E (?abY YYYY) - // see cur_test_track_location - uint8_t cur_test_track_z; // 0x11F - // Customer counter in the current 960 game tick (about 30 seconds) interval - uint16_t cur_num_customers; // 0x120 - // Counts ticks to update customer intervals, resets each 960 game ticks. - uint16_t num_customers_timeout; // 0x122 - // Customer count in the last 10 * 960 game ticks (sliding window) - uint16_t num_customers[RCT2::Limits::CustomerHistorySize]; // 0x124 - money16 price; // 0x138 - RCT12xy8 chairlift_bullwheel_location[2]; // 0x13A - uint8_t chairlift_bullwheel_z[2]; // 0x13E - union - { - RatingTuple ratings; // 0x140 - struct - { - ride_rating excitement; // 0x140 - ride_rating intensity; // 0x142 - ride_rating nausea; // 0x144 - }; - }; - uint16_t value; // 0x146 - uint16_t chairlift_bullwheel_rotation; // 0x148 - uint8_t satisfaction; // 0x14A - uint8_t satisfaction_time_out; // 0x14B - uint8_t satisfaction_next; // 0x14C - // Various flags stating whether a window needs to be refreshed - uint8_t window_invalidate_flags; // 0x14D - uint8_t pad_14E[0x02]; // 0x14E - uint32_t total_customers; // 0x150 - money32 total_profit; // 0x154 - uint8_t popularity; // 0x158 - uint8_t popularity_time_out; // 0x159 Updated every purchase and ?possibly by time? - uint8_t popularity_next; // 0x15A When timeout reached this will be the next popularity - uint8_t num_riders; // 0x15B - uint8_t music_tune_id; // 0x15C - uint8_t slide_in_use; // 0x15D - union - { - uint16_t slide_peep; // 0x15E - uint16_t maze_tiles; // 0x15E - }; - uint8_t pad_160[0xE]; // 0x160 - uint8_t slide_peep_t_shirt_colour; // 0x16E - uint8_t pad_16F[0x7]; // 0x16F - uint8_t spiral_slide_progress; // 0x176 - uint8_t pad_177[0x9]; // 0x177 - int16_t build_date; // 0x180 - money16 upkeep_cost; // 0x182 - uint16_t race_winner; // 0x184 - uint8_t pad_186[0x02]; // 0x186 - uint32_t music_position; // 0x188 - uint8_t breakdown_reason_pending; // 0x18C - uint8_t mechanic_status; // 0x18D - uint16_t mechanic; // 0x18E - uint8_t inspection_station; // 0x190 - uint8_t broken_vehicle; // 0x191 - uint8_t broken_car; // 0x192 - uint8_t breakdown_reason; // 0x193 - money16 price_secondary; // 0x194 - union - { - struct - { - uint8_t reliability_subvalue; // 0x196, 0 - 255, acts like the decimals for reliability_percentage - uint8_t reliability_percentage; // 0x197, Starts at 100 and decreases from there. - }; - uint16_t reliability; // 0x196 - }; - // Small constant used to increase the unreliability as the game continues, - // making breakdowns more and more likely. - uint8_t unreliability_factor; // 0x198 - // Range from [0, 100] - uint8_t downtime; // 0x199 - uint8_t inspection_interval; // 0x19A - uint8_t last_inspection; // 0x19B - uint8_t downtime_history[RCT2::Limits::DowntimeHistorySize]; // 0x19C - uint32_t no_primary_items_sold; // 0x1A4 - uint32_t no_secondary_items_sold; // 0x1A8 - uint8_t breakdown_sound_modifier; // 0x1AC - // Used to oscillate the sound when ride breaks down. - // 0 = no change, 255 = max change - uint8_t not_fixed_timeout; // 0x1AD - uint8_t last_crash_type; // 0x1AE - uint8_t connected_message_throttle; // 0x1AF - money32 income_per_hour; // 0x1B0 - money32 profit; // 0x1B4 - uint8_t queue_time[RCT2::Limits::MaxStationsPerRide]; // 0x1B8 - uint8_t track_colour_main[RCT2::Limits::NumColourSchemes]; // 0x1BC - uint8_t track_colour_additional[RCT2::Limits::NumColourSchemes]; // 0x1C0 - uint8_t track_colour_supports[RCT2::Limits::NumColourSchemes]; // 0x1C4 - uint8_t music; // 0x1C8 - uint8_t entrance_style; // 0x1C9 - uint16_t vehicle_change_timeout; // 0x1CA - uint8_t num_block_brakes; // 0x1CC - uint8_t lift_hill_speed; // 0x1CD - uint16_t guests_favourite; // 0x1CE - uint32_t lifecycle_flags; // 0x1D0 - uint8_t vehicle_colours_extended[RCT2::Limits::MaxTrainsPerRide]; // 0x1D4 - uint16_t total_air_time; // 0x1F4 - uint8_t current_test_station; // 0x1F6 - uint8_t num_circuits; // 0x1F7 - int16_t cable_lift_x; // 0x1F8 - int16_t cable_lift_y; // 0x1FA - uint8_t cable_lift_z; // 0x1FC - uint8_t pad_1FD; // 0x1FD - uint16_t cable_lift; // 0x1FE - uint16_t queue_length[RCT2::Limits::MaxStationsPerRide]; // 0x200 - uint8_t pad_208[0x58]; // 0x208 - - uint8_t GetMinCarsPerTrain() const; - uint8_t GetMaxCarsPerTrain() const; - void SetMinCarsPerTrain(uint8_t newValue); - void SetMaxCarsPerTrain(uint8_t newValue); -}; -assert_struct_size(rct2_ride, 0x260); - -/* Track Entrance entry size: 0x06 */ -struct rct_td6_entrance_element -{ - int8_t z; // 0x00 - uint8_t direction; // 0x01 - int16_t x; // 0x02 - int16_t y; // 0x04 -}; -assert_struct_size(rct_td6_entrance_element, 0x06); - -/* Track Scenery entry size: 0x16 */ -struct rct_td6_scenery_element -{ - rct_object_entry scenery_object; // 0x00 - int8_t x; // 0x10 - int8_t y; // 0x11 - int8_t z; // 0x12 - uint8_t flags; // 0x13 direction quadrant tertiary colour - uint8_t primary_colour; // 0x14 - uint8_t secondary_colour; // 0x15 -}; -assert_struct_size(rct_td6_scenery_element, 0x16); - -/** - * Track design structure. - * size: 0xA3 - */ -struct rct_track_td6 -{ - uint8_t type; // 0x00 - RCT12ObjectEntryIndex vehicle_type; - union - { - // After loading the track this is converted to - // a cost but before its a flags register - money32 cost; // 0x02 - uint32_t flags; // 0x02 - }; - union - { - // After loading the track this is converted to - // a flags register - uint8_t ride_mode; // 0x06 - uint8_t track_flags; // 0x06 - }; - uint8_t version_and_colour_scheme; // 0x07 0b0000_VVCC - rct_vehicle_colour vehicle_colours[RCT2::Limits::MaxTrainsPerRide]; // 0x08 - union - { - uint8_t pad_48; - uint8_t track_spine_colour_rct1; // 0x48 - }; - union - { - uint8_t entrance_style; // 0x49 - uint8_t track_rail_colour_rct1; // 0x49 - }; - union - { - uint8_t total_air_time; // 0x4A - uint8_t track_support_colour_rct1; // 0x4A - }; - uint8_t depart_flags; // 0x4B - uint8_t number_of_trains; // 0x4C - uint8_t number_of_cars_per_train; // 0x4D - uint8_t min_waiting_time; // 0x4E - uint8_t max_waiting_time; // 0x4F - uint8_t operation_setting; - int8_t max_speed; // 0x51 - int8_t average_speed; // 0x52 - uint16_t ride_length; // 0x53 - uint8_t max_positive_vertical_g; // 0x55 - int8_t max_negative_vertical_g; // 0x56 - uint8_t max_lateral_g; // 0x57 - union - { - uint8_t inversions; // 0x58 - uint8_t holes; // 0x58 - }; - uint8_t drops; // 0x59 - uint8_t highest_drop_height; // 0x5A - uint8_t excitement; // 0x5B - uint8_t intensity; // 0x5C - uint8_t nausea; // 0x5D - money16 upkeep_cost; // 0x5E - uint8_t track_spine_colour[RCT2::Limits::NumColourSchemes]; // 0x60 - uint8_t track_rail_colour[RCT2::Limits::NumColourSchemes]; // 0x64 - uint8_t track_support_colour[RCT2::Limits::NumColourSchemes]; // 0x68 - uint32_t flags2; // 0x6C - rct_object_entry vehicle_object; // 0x70 - uint8_t space_required_x; // 0x80 - uint8_t space_required_y; // 0x81 - uint8_t vehicle_additional_colour[RCT2::Limits::MaxTrainsPerRide]; // 0x82 - uint8_t lift_hill_speed_num_circuits; // 0xA2 0bCCCL_LLLL - // 0xA3 (data starts here in file) -}; -assert_struct_size(rct_track_td6, 0xA3); - -/** - * scores.dat file header. - * size: 0x10 - */ -struct rct_scores_header -{ - uint32_t var_0; - uint32_t var_4; - uint32_t var_8; - uint32_t ScenarioCount; -}; -assert_struct_size(rct_scores_header, 0x10); - -/** - * An entry of scores.dat - * size: 0x02B0 - */ -struct rct_scores_entry -{ - char Path[256]; - uint8_t Category; - uint8_t pad_0101[0x1F]; - int8_t ObjectiveType; - int8_t ObjectiveArg1; - int32_t objectiveArg2; - int16_t objectiveArg3; - char Name[64]; - char Details[256]; - int32_t Flags; - money32 CompanyValue; - char CompletedBy[64]; -}; -assert_struct_size(rct_scores_entry, 0x02B0); - -struct RCT2SpriteVehicle : RCT12SpriteBase -{ - uint8_t Pitch; // 0x1F - uint8_t bank_rotation; // 0x20 - uint8_t pad_21[3]; - int32_t remaining_distance; // 0x24 - int32_t velocity; // 0x28 - int32_t acceleration; // 0x2C - uint8_t ride; // 0x30 - uint8_t vehicle_type; // 0x31 - rct_vehicle_colour colours; // 0x32 - union - { - uint16_t track_progress; // 0x34 - struct - { - int8_t var_34; - uint8_t var_35; - }; - }; - union - { - int16_t TrackTypeAndDirection; // 0x36 - RCT12xy8 boat_location; // 0x36 - }; - uint16_t track_x; // 0x38 - uint16_t track_y; // 0x3A - uint16_t track_z; // 0x3C - uint16_t next_vehicle_on_train; // 0x3E - uint16_t prev_vehicle_on_ride; // 0x40 - uint16_t next_vehicle_on_ride; // 0x42 - uint16_t var_44; - uint16_t mass; // 0x46 - uint16_t update_flags; // 0x48 - uint8_t SwingSprite; - uint8_t current_station; // 0x4B - union - { - int16_t SwingPosition; // 0x4C - int16_t current_time; // 0x4C - struct - { - int8_t ferris_wheel_var_0; // 0x4C - int8_t ferris_wheel_var_1; // 0x4D - }; - }; - union - { - int16_t SwingSpeed; - int16_t crash_z; // 0x4E - }; - uint8_t status; // 0x50 - uint8_t sub_state; // 0x51 - uint16_t peep[32]; // 0x52 - uint8_t peep_tshirt_colours[32]; // 0x92 - uint8_t num_seats; // 0xB2 - uint8_t num_peeps; // 0xB3 - uint8_t next_free_seat; // 0xB4 - uint8_t restraints_position; // 0xB5 - union - { - int16_t spin_speed; // 0xB6 - int16_t crash_x; // 0xB6 - }; - uint16_t sound2_flags; // 0xB8 - uint8_t spin_sprite; // 0xBA - uint8_t sound1_id; // 0xBB - uint8_t sound1_volume; // 0xBC - uint8_t sound2_id; // 0xBD - uint8_t sound2_volume; // 0xBE - int8_t sound_vector_factor; - union - { - uint16_t var_C0; - int16_t crash_y; // 0xC0 - uint16_t time_waiting; // 0xC0 - uint16_t cable_lift_target; // 0xC0 - }; - uint8_t speed; // 0xC2 - uint8_t powered_acceleration; // 0xC3 - union - { - uint8_t dodgems_collision_direction; // 0xC4 - uint8_t var_C4; - }; - uint8_t animation_frame; // 0xC5 - uint8_t pad_C6[0x2]; - uint32_t animationState; - uint8_t scream_sound_id; // 0xCC - uint8_t TrackSubposition; - union - { - uint8_t var_CE; - uint8_t num_laps; // 0xCE - }; - union - { - uint8_t var_CF; - uint8_t brake_speed; // 0xCF - }; - uint16_t lost_time_out; // 0xD0 - int8_t vertical_drop_countdown; // 0xD1 - uint8_t var_D3; - uint8_t mini_golf_current_animation; - uint8_t mini_golf_flags; // 0xD5 - uint8_t ride_subtype; // 0xD6 - uint8_t colours_extended; // 0xD7 - uint8_t seat_rotation; // 0xD8 - uint8_t target_seat_rotation; // 0xD9 - - uint16_t GetTrackType() const - { - return TrackTypeAndDirection >> 2; - } - uint8_t GetTrackDirection() const - { - return TrackTypeAndDirection & RCT12VehicleTrackDirectionMask; - } - void SetTrackType(uint16_t trackType) - { - // set the upper 14 bits to 0 - TrackTypeAndDirection &= ~RCT12VehicleTrackTypeMask; - TrackTypeAndDirection |= trackType << 2; - } - void SetTrackDirection(uint8_t trackDirection) - { - // set the lower 2 bits only - TrackTypeAndDirection &= ~RCT12VehicleTrackDirectionMask; - TrackTypeAndDirection |= trackDirection & RCT12VehicleTrackDirectionMask; - } -}; -assert_struct_size(RCT2SpriteVehicle, 0xDA); - -struct RCT2SpritePeep : RCT12SpriteBase -{ - uint8_t pad_1F[0x22 - 0x1F]; - rct_string_id name_string_idx; // 0x22 - uint16_t next_x; // 0x24 - uint16_t next_y; // 0x26 - uint8_t next_z; // 0x28 - uint8_t next_flags; // 0x29 - uint8_t outside_of_park; // 0x2A - uint8_t state; // 0x2B - uint8_t sub_state; // 0x2C - uint8_t sprite_type; // 0x2D - uint8_t peep_type; // 0x2E - union - { - uint8_t staff_type; // 0x2F - uint8_t no_of_rides; // 0x2F - }; - uint8_t tshirt_colour; // 0x30 - uint8_t trousers_colour; // 0x31 - uint16_t destination_x; // 0x32 - uint16_t destination_y; // 0x34 - uint8_t destination_tolerance; // 0x36 - uint8_t var_37; - uint8_t energy; // 0x38 - uint8_t energy_target; // 0x39 - uint8_t happiness; // 0x3A - uint8_t happiness_target; // 0x3B - uint8_t nausea; // 0x3C - uint8_t nausea_target; // 0x3D - uint8_t hunger; // 0x3E - uint8_t thirst; // 0x3F - uint8_t toilet; // 0x40 - uint8_t mass; // 0x41 - uint8_t time_to_consume; // 0x42 - uint8_t intensity; // 0x43 - uint8_t nausea_tolerance; // 0x44 - uint8_t window_invalidate_flags; // 0x45 - money16 paid_on_drink; // 0x46 - uint8_t ride_types_been_on[16]; // 0x48 - uint32_t item_extra_flags; // 0x58 - RCT12RideId photo2_ride_ref; // 0x5C - RCT12RideId photo3_ride_ref; // 0x5D - RCT12RideId photo4_ride_ref; // 0x5E - uint8_t pad_5F[0x09]; // 0x5F - RCT12RideId current_ride; // 0x68 - uint8_t current_ride_station; // 0x69 - uint8_t current_train; // 0x6A - union - { - struct - { - uint8_t current_car; // 0x6B - uint8_t current_seat; // 0x6C - }; - uint16_t time_to_sitdown; // 0x6B - struct - { - uint8_t time_to_stand; // 0x6B - uint8_t standing_flags; // 0x6C - }; - }; - uint8_t special_sprite; // 0x6D - uint8_t action_sprite_type; // 0x6E - uint8_t next_action_sprite_type; // 0x6F - uint8_t action_sprite_image_offset; // 0x70 - uint8_t action; // 0x71 - uint8_t action_frame; // 0x72 - uint8_t step_progress; // 0x73 - union - { - uint16_t mechanic_time_since_call; - uint16_t next_in_queue; // 0x74 - }; - uint8_t pad_76; - uint8_t pad_77; - union - { - uint8_t maze_last_edge; // 0x78 - uint8_t direction; - }; - RCT12RideId interaction_ride_index; - uint16_t time_in_queue; // 0x7A - uint8_t rides_been_on[32]; // 0x7C - uint32_t id; // 0x9C - money32 cash_in_pocket; // 0xA0 - money32 cash_spent; // 0xA4 - int32_t park_entry_time; // 0xA8 - int8_t rejoin_queue_timeout; // 0xAC - RCT12RideId previous_ride; // 0xAD - uint16_t previous_ride_time_out; // 0xAE - RCT12PeepThought thoughts[RCT2::Limits::MaxPeepThoughts]; // 0xB0 - uint8_t path_check_optimisation; // 0xC4 - union - { - uint8_t staff_id; // 0xC5 - RCT12RideId guest_heading_to_ride_id; // 0xC5 - }; - union - { - uint8_t staff_orders; // 0xC6 - uint8_t peep_is_lost_countdown; // 0xC6 - }; - RCT12RideId photo1_ride_ref; // 0xC7 - uint32_t peep_flags; // 0xC8 - rct12_xyzd8 pathfind_goal; // 0xCC - rct12_xyzd8 pathfind_history[4]; // 0xD0 - uint8_t no_action_frame_num; // 0xE0 - uint8_t litter_count; // 0xE1 - union - { - uint8_t time_on_ride; // 0xE2 - uint8_t staff_mowing_timeout; // 0xE2 - }; - uint8_t disgusting_count; // 0xE3 - union - { - money16 paid_to_enter; // 0xE4 - uint16_t staff_lawns_mown; // 0xE4 - uint16_t staff_rides_fixed; // 0xE4 - }; - union - { - money16 paid_on_rides; // 0xE6 - uint16_t staff_gardens_watered; // 0xE6 - uint16_t staff_rides_inspected; // 0xE6 - }; - union - { - money16 paid_on_food; // 0xE8 - uint16_t staff_litter_swept; // 0xE8 - }; - union - { - money16 paid_on_souvenirs; // 0xEA - uint16_t staff_bins_emptied; // 0xEA - }; - uint8_t no_of_food; // 0xEC - uint8_t no_of_drinks; // 0xED - uint8_t no_of_souvenirs; // 0xEE - uint8_t vandalism_seen; // 0xEF 0xC0 vandalism thought timeout, 0x3F vandalism tiles seen - uint8_t voucher_type; // 0xF0 - RCT12RideId voucher_arguments; // 0xF1 ride_id or string_offset_id - uint8_t surroundings_thought_timeout; // 0xF2 - uint8_t angriness; // 0xF3 - uint8_t time_lost; // 0xF4 the time the peep has been lost when it reaches 254 generates the lost thought - uint8_t days_in_queue; // 0xF5 - uint8_t balloon_colour; // 0xF6 - uint8_t umbrella_colour; // 0xF7 - uint8_t hat_colour; // 0xF8 - RCT12RideId favourite_ride; // 0xF9 - uint8_t favourite_ride_rating; // 0xFA - uint8_t pad_FB; - uint32_t item_standard_flags; // 0xFC - uint64_t GetItemFlags() const - { - return item_standard_flags | (static_cast(item_extra_flags) << 32); - } -}; -assert_struct_size(RCT2SpritePeep, 0x100); - -enum class RCT2StaffMode : uint8_t -{ - None, - Walk, - Patrol = 3 -}; - -union RCT2Sprite -{ -private: - uint8_t pad_00[0x100]; - -public: - RCT12SpriteBase unknown; - RCT2SpriteVehicle vehicle; - RCT2SpritePeep peep; - RCT12SpriteLitter litter; - RCT12SpriteBalloon balloon; - RCT12SpriteDuck duck; - RCT12SpriteJumpingFountain jumping_fountain; - RCT12SpriteMoneyEffect money_effect; - RCT12SpriteCrashedVehicleParticle crashed_vehicle_particle; - RCT12SpriteCrashSplash crash_splash; - RCT12SpriteSteamParticle steam_particle; - RCT12SpriteParticle misc_particle; -}; -assert_struct_size(RCT2Sprite, 0x100); - -struct RCT2RideRatingCalculationData -{ - uint16_t proximity_x; - uint16_t proximity_y; - uint16_t proximity_z; - uint16_t proximity_start_x; - uint16_t proximity_start_y; - uint16_t proximity_start_z; - uint8_t current_ride; - uint8_t state; - uint8_t proximity_track_type; - uint8_t proximity_base_height; - uint16_t proximity_total; - uint16_t proximity_scores[26]; - uint16_t num_brakes; - uint16_t num_reversers; - uint16_t station_flags; -}; -assert_struct_size(RCT2RideRatingCalculationData, 76); - -/** - * SV6/SC6 header chunk - * size: 0x20 - */ -struct rct_s6_header -{ - uint8_t type; // 0x00 - uint8_t classic_flag; // 0x01 - uint16_t num_packed_objects; // 0x02 - uint32_t version; // 0x04 - uint32_t magic_number; // 0x08 - uint8_t pad_0C[0x14]; -}; -assert_struct_size(rct_s6_header, 0x20); - enum class EditorStep : uint8_t; -/** - * SC6 information chunk - * size: 0x198 - */ -struct rct_s6_info +namespace RCT2 { - EditorStep editor_step; - uint8_t category; // 0x01 - uint8_t objective_type; // 0x02 - uint8_t objective_arg_1; // 0x03 - int32_t objective_arg_2; // 0x04 - int16_t objective_arg_3; // 0x08 - uint8_t pad_00A[0x3E]; - char name[64]; // 0x48 - char details[256]; // 0x88 - rct_object_entry entry; // 0x188 -}; -assert_struct_size(rct_s6_info, 0x198); + constexpr const rct_string_id RCT2_RIDE_STRING_START = 2; -struct rct_s6_data -{ - // SC6[0] - rct_s6_header header; + // clang-format off + constexpr const uint16_t RCT2_OBJECT_ENTRY_COUNT = + Limits::MaxRideObject + + Limits::MaxSmallSceneryObjects + + Limits::MaxLargeSceneryObjects + + Limits::MaxWallSceneryObjects + + Limits::MaxBannerObjects + + Limits::MaxPathObjects + + Limits::MaxPathAdditionObjects + + Limits::MaxScenereyGroupObjects + + Limits::MaxParkEntranceObjects + + Limits::MaxWaterObjects + + Limits::MaxScenarioTextObjects; + // clang-format on + static_assert(RCT2_OBJECT_ENTRY_COUNT == 721); - // SC6[1] - rct_s6_info info; + // clang-format off + constexpr const int32_t rct2_object_entry_group_counts[] = { + Limits::MaxRideObject, + Limits::MaxSmallSceneryObjects, + Limits::MaxLargeSceneryObjects, + Limits::MaxWallSceneryObjects, + Limits::MaxBannerObjects, + Limits::MaxPathObjects, + Limits::MaxPathAdditionObjects, + Limits::MaxScenereyGroupObjects, + Limits::MaxParkEntranceObjects, + Limits::MaxWaterObjects, + Limits::MaxScenarioTextObjects, + }; + // clang-format on - // SC6[2] - // packed objects - - // SC6[3] - union + enum class EntityListId : uint8_t { - rct_object_entry Objects[RCT2_OBJECT_ENTRY_COUNT]; - struct + Count = 6, + }; +#pragma pack(push, 1) + /** + * Ride structure. + * size: 0x0260 + */ + struct Ride + { + uint8_t type; // 0x000 + // pointer to static info. for example, wild mouse type is 0x36, subtype is + // 0x4c. + RCT12ObjectEntryIndex subtype; // 0x001 + uint16_t pad_002; // 0x002 + uint8_t mode; // 0x004 + uint8_t colour_scheme_type; // 0x005 + rct_vehicle_colour vehicle_colours[Limits::MaxTrainsPerRide]; // 0x006 + uint8_t pad_046[0x03]; // 0x046, Used to be track colours in RCT1 without expansions + // 0 = closed, 1 = open, 2 = test + uint8_t status; // 0x049 + rct_string_id name; // 0x04A + union { - rct_object_entry RideObjects[RCT2::Limits::MaxRideObject]; - rct_object_entry SceneryObjects[RCT2::Limits::MaxSmallSceneryObjects]; - rct_object_entry LargeSceneryObjects[RCT2::Limits::MaxLargeSceneryObjects]; - rct_object_entry WallSceneryObjects[RCT2::Limits::MaxWallSceneryObjects]; - rct_object_entry BannerObjects[RCT2::Limits::MaxBannerObjects]; - rct_object_entry PathObjects[RCT2::Limits::MaxPathObjects]; - rct_object_entry PathAdditionObjects[RCT2::Limits::MaxPathAdditionObjects]; - rct_object_entry SceneryGroupObjects[RCT2::Limits::MaxScenereyGroupObjects]; - rct_object_entry ParkEntranceObjects[RCT2::Limits::MaxParkEntranceObjects]; - rct_object_entry WaterObjects[RCT2::Limits::MaxWaterObjects]; - rct_object_entry ScenarioTextObjects[RCT2::Limits::MaxScenarioTextObjects]; + uint32_t name_arguments; // 0x04C + struct + { + rct_string_id name_arguments_type_name; // 0x04C + uint16_t name_arguments_number; // 0x04E + }; }; + RCT12xy8 overall_view; // 0x050 + RCT12xy8 station_starts[Limits::MaxStationsPerRide]; // 0x052 + uint8_t station_heights[Limits::MaxStationsPerRide]; // 0x05A + uint8_t station_length[Limits::MaxStationsPerRide]; // 0x05E + uint8_t station_depart[Limits::MaxStationsPerRide]; // 0x062 + // ride->vehicle index for current train waiting for passengers + // at station + uint8_t train_at_station[Limits::MaxStationsPerRide]; // 0x066 + RCT12xy8 entrances[Limits::MaxStationsPerRide]; // 0x06A + RCT12xy8 exits[Limits::MaxStationsPerRide]; // 0x072 + uint16_t last_peep_in_queue[Limits::MaxStationsPerRide]; // 0x07A + uint8_t pad_082[Limits::MaxStationsPerRide]; // 0x082, Used to be number of peeps in queue in RCT1, but this + // has moved. + uint16_t vehicles[Limits::MaxTrainsPerRide]; // 0x086, Points to the first car in the train + uint8_t depart_flags; // 0x0C6 + + // Not sure if these should be uint or sint. + uint8_t num_stations; // 0x0C7 + uint8_t num_vehicles; // 0x0C8 + uint8_t num_cars_per_train; // 0x0C9 + uint8_t proposed_num_vehicles; // 0x0CA + uint8_t proposed_num_cars_per_train; // 0x0CB + uint8_t max_trains; // 0x0CC + uint8_t min_max_cars_per_train; // 0x0CD + uint8_t min_waiting_time; // 0x0CE + uint8_t max_waiting_time; // 0x0CF + union + { + uint8_t operation_option; // 0x0D0 + uint8_t time_limit; // 0x0D0 + uint8_t num_laps; // 0x0D0 + uint8_t launch_speed; // 0x0D0 + uint8_t speed; // 0x0D0 + uint8_t rotations; // 0x0D0 + }; + + uint8_t boat_hire_return_direction; // 0x0D1 + RCT12xy8 boat_hire_return_position; // 0x0D2 + uint8_t measurement_index; // 0x0D4 + // bits 0 through 4 are the number of helix sections + // bit 5: spinning tunnel, water splash, or rapids + // bit 6: log reverser, waterfall + // bit 7: whirlpool + uint8_t special_track_elements; // 0x0D5 + uint8_t pad_0D6[2]; // 0x0D6 + // Divide this value by 29127 to get the human-readable max speed + // (in RCT2, display_speed = (max_speed * 9) >> 18) + int32_t max_speed; // 0x0D8 + int32_t average_speed; // 0x0DC + uint8_t current_test_segment; // 0x0E0 + uint8_t average_speed_test_timeout; // 0x0E1 + uint8_t pad_0E2[0x2]; // 0x0E2 + int32_t length[Limits::MaxStationsPerRide]; // 0x0E4 + uint16_t time[Limits::MaxStationsPerRide]; // 0x0F4 + fixed16_2dp max_positive_vertical_g; // 0x0FC + fixed16_2dp max_negative_vertical_g; // 0x0FE + fixed16_2dp max_lateral_g; // 0x100 + fixed16_2dp previous_vertical_g; // 0x102 + fixed16_2dp previous_lateral_g; // 0x104 + uint8_t pad_106[0x2]; // 0x106 + uint32_t testing_flags; // 0x108 + // x y map location of the current track piece during a test + // this is to prevent counting special tracks multiple times + RCT12xy8 cur_test_track_location; // 0x10C + // Next 3 variables are related (XXXX XYYY ZZZa aaaa) + uint16_t turn_count_default; // 0x10E X = current turn count + uint16_t turn_count_banked; // 0x110 + uint16_t turn_count_sloped; // 0x112 X = number turns > 3 elements + union + { + uint8_t inversions; // 0x114 (???X XXXX) + uint8_t holes; // 0x114 (???X XXXX) + // This is a very rough approximation of how much of the ride is undercover. + // It reaches the maximum value of 7 at about 50% undercover and doesn't increase beyond that. + uint8_t sheltered_eighths; // 0x114 (XXX?-????) + }; + // Y is number of powered lifts, X is drops + uint8_t drops; // 0x115 (YYXX XXXX) + uint8_t start_drop_height; // 0x116 + uint8_t highest_drop_height; // 0x117 + int32_t sheltered_length; // 0x118 + // Unused always 0? Should affect nausea + uint16_t var_11C; // 0x11C + uint8_t num_sheltered_sections; // 0x11E (?abY YYYY) + // see cur_test_track_location + uint8_t cur_test_track_z; // 0x11F + // Customer counter in the current 960 game tick (about 30 seconds) interval + uint16_t cur_num_customers; // 0x120 + // Counts ticks to update customer intervals, resets each 960 game ticks. + uint16_t num_customers_timeout; // 0x122 + // Customer count in the last 10 * 960 game ticks (sliding window) + uint16_t num_customers[Limits::CustomerHistorySize]; // 0x124 + money16 price; // 0x138 + RCT12xy8 chairlift_bullwheel_location[2]; // 0x13A + uint8_t chairlift_bullwheel_z[2]; // 0x13E + union + { + RatingTuple ratings; // 0x140 + struct + { + ride_rating excitement; // 0x140 + ride_rating intensity; // 0x142 + ride_rating nausea; // 0x144 + }; + }; + uint16_t value; // 0x146 + uint16_t chairlift_bullwheel_rotation; // 0x148 + uint8_t satisfaction; // 0x14A + uint8_t satisfaction_time_out; // 0x14B + uint8_t satisfaction_next; // 0x14C + // Various flags stating whether a window needs to be refreshed + uint8_t window_invalidate_flags; // 0x14D + uint8_t pad_14E[0x02]; // 0x14E + uint32_t total_customers; // 0x150 + money32 total_profit; // 0x154 + uint8_t popularity; // 0x158 + uint8_t popularity_time_out; // 0x159 Updated every purchase and ?possibly by time? + uint8_t popularity_next; // 0x15A When timeout reached this will be the next popularity + uint8_t num_riders; // 0x15B + uint8_t music_tune_id; // 0x15C + uint8_t slide_in_use; // 0x15D + union + { + uint16_t slide_peep; // 0x15E + uint16_t maze_tiles; // 0x15E + }; + uint8_t pad_160[0xE]; // 0x160 + uint8_t slide_peep_t_shirt_colour; // 0x16E + uint8_t pad_16F[0x7]; // 0x16F + uint8_t spiral_slide_progress; // 0x176 + uint8_t pad_177[0x9]; // 0x177 + int16_t build_date; // 0x180 + money16 upkeep_cost; // 0x182 + uint16_t race_winner; // 0x184 + uint8_t pad_186[0x02]; // 0x186 + uint32_t music_position; // 0x188 + uint8_t breakdown_reason_pending; // 0x18C + uint8_t mechanic_status; // 0x18D + uint16_t mechanic; // 0x18E + uint8_t inspection_station; // 0x190 + uint8_t broken_vehicle; // 0x191 + uint8_t broken_car; // 0x192 + uint8_t breakdown_reason; // 0x193 + money16 price_secondary; // 0x194 + union + { + struct + { + uint8_t reliability_subvalue; // 0x196, 0 - 255, acts like the decimals for reliability_percentage + uint8_t reliability_percentage; // 0x197, Starts at 100 and decreases from there. + }; + uint16_t reliability; // 0x196 + }; + // Small constant used to increase the unreliability as the game continues, + // making breakdowns more and more likely. + uint8_t unreliability_factor; // 0x198 + // Range from [0, 100] + uint8_t downtime; // 0x199 + uint8_t inspection_interval; // 0x19A + uint8_t last_inspection; // 0x19B + uint8_t downtime_history[Limits::DowntimeHistorySize]; // 0x19C + uint32_t no_primary_items_sold; // 0x1A4 + uint32_t no_secondary_items_sold; // 0x1A8 + uint8_t breakdown_sound_modifier; // 0x1AC + // Used to oscillate the sound when ride breaks down. + // 0 = no change, 255 = max change + uint8_t not_fixed_timeout; // 0x1AD + uint8_t last_crash_type; // 0x1AE + uint8_t connected_message_throttle; // 0x1AF + money32 income_per_hour; // 0x1B0 + money32 profit; // 0x1B4 + uint8_t queue_time[Limits::MaxStationsPerRide]; // 0x1B8 + uint8_t track_colour_main[Limits::NumColourSchemes]; // 0x1BC + uint8_t track_colour_additional[Limits::NumColourSchemes]; // 0x1C0 + uint8_t track_colour_supports[Limits::NumColourSchemes]; // 0x1C4 + uint8_t music; // 0x1C8 + uint8_t entrance_style; // 0x1C9 + uint16_t vehicle_change_timeout; // 0x1CA + uint8_t num_block_brakes; // 0x1CC + uint8_t lift_hill_speed; // 0x1CD + uint16_t guests_favourite; // 0x1CE + uint32_t lifecycle_flags; // 0x1D0 + uint8_t vehicle_colours_extended[Limits::MaxTrainsPerRide]; // 0x1D4 + uint16_t total_air_time; // 0x1F4 + uint8_t current_test_station; // 0x1F6 + uint8_t num_circuits; // 0x1F7 + int16_t cable_lift_x; // 0x1F8 + int16_t cable_lift_y; // 0x1FA + uint8_t cable_lift_z; // 0x1FC + uint8_t pad_1FD; // 0x1FD + uint16_t cable_lift; // 0x1FE + uint16_t queue_length[Limits::MaxStationsPerRide]; // 0x200 + uint8_t pad_208[0x58]; // 0x208 + + uint8_t GetMinCarsPerTrain() const; + uint8_t GetMaxCarsPerTrain() const; + void SetMinCarsPerTrain(uint8_t newValue); + void SetMaxCarsPerTrain(uint8_t newValue); + }; + assert_struct_size(Ride, 0x260); + + /* Track Entrance entry size: 0x06 */ + struct TD6EntranceElement + { + int8_t z; // 0x00 + uint8_t direction; // 0x01 + int16_t x; // 0x02 + int16_t y; // 0x04 + }; + assert_struct_size(TD6EntranceElement, 0x06); + + /* Track Scenery entry size: 0x16 */ + struct TD6SceneryElement + { + rct_object_entry scenery_object; // 0x00 + int8_t x; // 0x10 + int8_t y; // 0x11 + int8_t z; // 0x12 + uint8_t flags; // 0x13 direction quadrant tertiary colour + uint8_t primary_colour; // 0x14 + uint8_t secondary_colour; // 0x15 + }; + assert_struct_size(TD6SceneryElement, 0x16); + + /** + * Track design structure. + * size: 0xA3 + */ + struct TD6Track + { + uint8_t type; // 0x00 + RCT12ObjectEntryIndex vehicle_type; + union + { + // After loading the track this is converted to + // a cost but before its a flags register + money32 cost; // 0x02 + uint32_t flags; // 0x02 + }; + union + { + // After loading the track this is converted to + // a flags register + uint8_t ride_mode; // 0x06 + uint8_t track_flags; // 0x06 + }; + uint8_t version_and_colour_scheme; // 0x07 0b0000_VVCC + rct_vehicle_colour vehicle_colours[Limits::MaxTrainsPerRide]; // 0x08 + union + { + uint8_t pad_48; + uint8_t track_spine_colour_rct1; // 0x48 + }; + union + { + uint8_t entrance_style; // 0x49 + uint8_t track_rail_colour_rct1; // 0x49 + }; + union + { + uint8_t total_air_time; // 0x4A + uint8_t track_support_colour_rct1; // 0x4A + }; + uint8_t depart_flags; // 0x4B + uint8_t number_of_trains; // 0x4C + uint8_t number_of_cars_per_train; // 0x4D + uint8_t min_waiting_time; // 0x4E + uint8_t max_waiting_time; // 0x4F + uint8_t operation_setting; + int8_t max_speed; // 0x51 + int8_t average_speed; // 0x52 + uint16_t ride_length; // 0x53 + uint8_t max_positive_vertical_g; // 0x55 + int8_t max_negative_vertical_g; // 0x56 + uint8_t max_lateral_g; // 0x57 + union + { + uint8_t inversions; // 0x58 + uint8_t holes; // 0x58 + }; + uint8_t drops; // 0x59 + uint8_t highest_drop_height; // 0x5A + uint8_t excitement; // 0x5B + uint8_t intensity; // 0x5C + uint8_t nausea; // 0x5D + money16 upkeep_cost; // 0x5E + uint8_t track_spine_colour[Limits::NumColourSchemes]; // 0x60 + uint8_t track_rail_colour[Limits::NumColourSchemes]; // 0x64 + uint8_t track_support_colour[Limits::NumColourSchemes]; // 0x68 + uint32_t flags2; // 0x6C + rct_object_entry vehicle_object; // 0x70 + uint8_t space_required_x; // 0x80 + uint8_t space_required_y; // 0x81 + uint8_t vehicle_additional_colour[Limits::MaxTrainsPerRide]; // 0x82 + uint8_t lift_hill_speed_num_circuits; // 0xA2 0bCCCL_LLLL + // 0xA3 (data starts here in file) + }; + assert_struct_size(TD6Track, 0xA3); + + /** + * scores.dat file header. + * size: 0x10 + */ + struct ScoresHeader + { + uint32_t var_0; + uint32_t var_4; + uint32_t var_8; + uint32_t ScenarioCount; + }; + assert_struct_size(ScoresHeader, 0x10); + + /** + * An entry of scores.dat + * size: 0x02B0 + */ + struct ScoresEntry + { + char Path[256]; + uint8_t Category; + uint8_t pad_0101[0x1F]; + int8_t ObjectiveType; + int8_t ObjectiveArg1; + int32_t objectiveArg2; + int16_t objectiveArg3; + char Name[64]; + char Details[256]; + int32_t Flags; + money32 CompanyValue; + char CompletedBy[64]; + }; + assert_struct_size(ScoresEntry, 0x02B0); + + struct Vehicle : RCT12SpriteBase + { + uint8_t Pitch; // 0x1F + uint8_t bank_rotation; // 0x20 + uint8_t pad_21[3]; + int32_t remaining_distance; // 0x24 + int32_t velocity; // 0x28 + int32_t acceleration; // 0x2C + uint8_t ride; // 0x30 + uint8_t vehicle_type; // 0x31 + rct_vehicle_colour colours; // 0x32 + union + { + uint16_t track_progress; // 0x34 + struct + { + int8_t var_34; + uint8_t var_35; + }; + }; + union + { + int16_t TrackTypeAndDirection; // 0x36 + RCT12xy8 boat_location; // 0x36 + }; + uint16_t track_x; // 0x38 + uint16_t track_y; // 0x3A + uint16_t track_z; // 0x3C + uint16_t next_vehicle_on_train; // 0x3E + uint16_t prev_vehicle_on_ride; // 0x40 + uint16_t next_vehicle_on_ride; // 0x42 + uint16_t var_44; + uint16_t mass; // 0x46 + uint16_t update_flags; // 0x48 + uint8_t SwingSprite; + uint8_t current_station; // 0x4B + union + { + int16_t SwingPosition; // 0x4C + int16_t current_time; // 0x4C + struct + { + int8_t ferris_wheel_var_0; // 0x4C + int8_t ferris_wheel_var_1; // 0x4D + }; + }; + union + { + int16_t SwingSpeed; + int16_t crash_z; // 0x4E + }; + uint8_t status; // 0x50 + uint8_t sub_state; // 0x51 + uint16_t peep[32]; // 0x52 + uint8_t peep_tshirt_colours[32]; // 0x92 + uint8_t num_seats; // 0xB2 + uint8_t num_peeps; // 0xB3 + uint8_t next_free_seat; // 0xB4 + uint8_t restraints_position; // 0xB5 + union + { + int16_t spin_speed; // 0xB6 + int16_t crash_x; // 0xB6 + }; + uint16_t sound2_flags; // 0xB8 + uint8_t spin_sprite; // 0xBA + uint8_t sound1_id; // 0xBB + uint8_t sound1_volume; // 0xBC + uint8_t sound2_id; // 0xBD + uint8_t sound2_volume; // 0xBE + int8_t sound_vector_factor; + union + { + uint16_t var_C0; + int16_t crash_y; // 0xC0 + uint16_t time_waiting; // 0xC0 + uint16_t cable_lift_target; // 0xC0 + }; + uint8_t speed; // 0xC2 + uint8_t powered_acceleration; // 0xC3 + union + { + uint8_t dodgems_collision_direction; // 0xC4 + uint8_t var_C4; + }; + uint8_t animation_frame; // 0xC5 + uint8_t pad_C6[0x2]; + uint32_t animationState; + uint8_t scream_sound_id; // 0xCC + uint8_t TrackSubposition; + union + { + uint8_t var_CE; + uint8_t num_laps; // 0xCE + }; + union + { + uint8_t var_CF; + uint8_t brake_speed; // 0xCF + }; + uint16_t lost_time_out; // 0xD0 + int8_t vertical_drop_countdown; // 0xD1 + uint8_t var_D3; + uint8_t mini_golf_current_animation; + uint8_t mini_golf_flags; // 0xD5 + uint8_t ride_subtype; // 0xD6 + uint8_t colours_extended; // 0xD7 + uint8_t seat_rotation; // 0xD8 + uint8_t target_seat_rotation; // 0xD9 + + uint16_t GetTrackType() const + { + return TrackTypeAndDirection >> 2; + } + uint8_t GetTrackDirection() const + { + return TrackTypeAndDirection & RCT12VehicleTrackDirectionMask; + } + void SetTrackType(uint16_t trackType) + { + // set the upper 14 bits to 0 + TrackTypeAndDirection &= ~RCT12VehicleTrackTypeMask; + TrackTypeAndDirection |= trackType << 2; + } + void SetTrackDirection(uint8_t trackDirection) + { + // set the lower 2 bits only + TrackTypeAndDirection &= ~RCT12VehicleTrackDirectionMask; + TrackTypeAndDirection |= trackDirection & RCT12VehicleTrackDirectionMask; + } + }; + assert_struct_size(Vehicle, 0xDA); + + struct Peep : RCT12SpriteBase + { + uint8_t pad_1F[0x22 - 0x1F]; + rct_string_id name_string_idx; // 0x22 + uint16_t next_x; // 0x24 + uint16_t next_y; // 0x26 + uint8_t next_z; // 0x28 + uint8_t next_flags; // 0x29 + uint8_t outside_of_park; // 0x2A + uint8_t state; // 0x2B + uint8_t sub_state; // 0x2C + uint8_t sprite_type; // 0x2D + uint8_t peep_type; // 0x2E + union + { + uint8_t staff_type; // 0x2F + uint8_t no_of_rides; // 0x2F + }; + uint8_t tshirt_colour; // 0x30 + uint8_t trousers_colour; // 0x31 + uint16_t destination_x; // 0x32 + uint16_t destination_y; // 0x34 + uint8_t destination_tolerance; // 0x36 + uint8_t var_37; + uint8_t energy; // 0x38 + uint8_t energy_target; // 0x39 + uint8_t happiness; // 0x3A + uint8_t happiness_target; // 0x3B + uint8_t nausea; // 0x3C + uint8_t nausea_target; // 0x3D + uint8_t hunger; // 0x3E + uint8_t thirst; // 0x3F + uint8_t toilet; // 0x40 + uint8_t mass; // 0x41 + uint8_t time_to_consume; // 0x42 + uint8_t intensity; // 0x43 + uint8_t nausea_tolerance; // 0x44 + uint8_t window_invalidate_flags; // 0x45 + money16 paid_on_drink; // 0x46 + uint8_t ride_types_been_on[16]; // 0x48 + uint32_t item_extra_flags; // 0x58 + RCT12RideId photo2_ride_ref; // 0x5C + RCT12RideId photo3_ride_ref; // 0x5D + RCT12RideId photo4_ride_ref; // 0x5E + uint8_t pad_5F[0x09]; // 0x5F + RCT12RideId current_ride; // 0x68 + uint8_t current_ride_station; // 0x69 + uint8_t current_train; // 0x6A + union + { + struct + { + uint8_t current_car; // 0x6B + uint8_t current_seat; // 0x6C + }; + uint16_t time_to_sitdown; // 0x6B + struct + { + uint8_t time_to_stand; // 0x6B + uint8_t standing_flags; // 0x6C + }; + }; + uint8_t special_sprite; // 0x6D + uint8_t action_sprite_type; // 0x6E + uint8_t next_action_sprite_type; // 0x6F + uint8_t action_sprite_image_offset; // 0x70 + uint8_t action; // 0x71 + uint8_t action_frame; // 0x72 + uint8_t step_progress; // 0x73 + union + { + uint16_t mechanic_time_since_call; + uint16_t next_in_queue; // 0x74 + }; + uint8_t pad_76; + uint8_t pad_77; + union + { + uint8_t maze_last_edge; // 0x78 + uint8_t direction; + }; + RCT12RideId interaction_ride_index; + uint16_t time_in_queue; // 0x7A + uint8_t rides_been_on[32]; // 0x7C + uint32_t id; // 0x9C + money32 cash_in_pocket; // 0xA0 + money32 cash_spent; // 0xA4 + int32_t park_entry_time; // 0xA8 + int8_t rejoin_queue_timeout; // 0xAC + RCT12RideId previous_ride; // 0xAD + uint16_t previous_ride_time_out; // 0xAE + RCT12PeepThought thoughts[Limits::MaxPeepThoughts]; // 0xB0 + uint8_t path_check_optimisation; // 0xC4 + union + { + uint8_t staff_id; // 0xC5 + RCT12RideId guest_heading_to_ride_id; // 0xC5 + }; + union + { + uint8_t staff_orders; // 0xC6 + uint8_t peep_is_lost_countdown; // 0xC6 + }; + RCT12RideId photo1_ride_ref; // 0xC7 + uint32_t peep_flags; // 0xC8 + rct12_xyzd8 pathfind_goal; // 0xCC + rct12_xyzd8 pathfind_history[4]; // 0xD0 + uint8_t no_action_frame_num; // 0xE0 + uint8_t litter_count; // 0xE1 + union + { + uint8_t time_on_ride; // 0xE2 + uint8_t staff_mowing_timeout; // 0xE2 + }; + uint8_t disgusting_count; // 0xE3 + union + { + money16 paid_to_enter; // 0xE4 + uint16_t staff_lawns_mown; // 0xE4 + uint16_t staff_rides_fixed; // 0xE4 + }; + union + { + money16 paid_on_rides; // 0xE6 + uint16_t staff_gardens_watered; // 0xE6 + uint16_t staff_rides_inspected; // 0xE6 + }; + union + { + money16 paid_on_food; // 0xE8 + uint16_t staff_litter_swept; // 0xE8 + }; + union + { + money16 paid_on_souvenirs; // 0xEA + uint16_t staff_bins_emptied; // 0xEA + }; + uint8_t no_of_food; // 0xEC + uint8_t no_of_drinks; // 0xED + uint8_t no_of_souvenirs; // 0xEE + uint8_t vandalism_seen; // 0xEF 0xC0 vandalism thought timeout, 0x3F vandalism tiles seen + uint8_t voucher_type; // 0xF0 + RCT12RideId voucher_arguments; // 0xF1 ride_id or string_offset_id + uint8_t surroundings_thought_timeout; // 0xF2 + uint8_t angriness; // 0xF3 + uint8_t time_lost; // 0xF4 the time the peep has been lost when it reaches 254 generates the lost thought + uint8_t days_in_queue; // 0xF5 + uint8_t balloon_colour; // 0xF6 + uint8_t umbrella_colour; // 0xF7 + uint8_t hat_colour; // 0xF8 + RCT12RideId favourite_ride; // 0xF9 + uint8_t favourite_ride_rating; // 0xFA + uint8_t pad_FB; + uint32_t item_standard_flags; // 0xFC + uint64_t GetItemFlags() const + { + return item_standard_flags | (static_cast(item_extra_flags) << 32); + } + }; + assert_struct_size(Peep, 0x100); + + enum class StaffMode : uint8_t + { + None, + Walk, + Patrol = 3 }; - // SC6[4] - uint16_t elapsed_months; - uint16_t current_day; - uint32_t scenario_ticks; - uint32_t scenario_srand_0; - uint32_t scenario_srand_1; + union Entity + { + private: + uint8_t pad_00[0x100]; - // SC6[5] - RCT12TileElement tile_elements[RCT2::Limits::MaxTileElements]; + public: + RCT12SpriteBase unknown; + Vehicle vehicle; + Peep peep; + RCT12SpriteLitter litter; + RCT12SpriteBalloon balloon; + RCT12SpriteDuck duck; + RCT12SpriteJumpingFountain jumping_fountain; + RCT12SpriteMoneyEffect money_effect; + RCT12SpriteCrashedVehicleParticle crashed_vehicle_particle; + RCT12SpriteCrashSplash crash_splash; + RCT12SpriteSteamParticle steam_particle; + RCT12SpriteParticle misc_particle; + }; + assert_struct_size(Entity, 0x100); - // SC6[6] - uint32_t next_free_tile_element_pointer_index; - RCT2Sprite sprites[RCT2::Limits::MaxEntities]; - uint16_t sprite_lists_head[static_cast(EntityListId::Count)]; - uint16_t sprite_lists_count[static_cast(EntityListId::Count)]; - rct_string_id park_name; - uint8_t pad_013573D6[2]; - uint32_t park_name_args; - money32 initial_cash; - money32 current_loan; - uint32_t park_flags; - money16 park_entrance_fee; - uint16_t rct1_park_entrance_x; - uint16_t rct1_park_entrance_y; - uint8_t pad_013573EE[2]; - uint8_t rct1_park_entrance_z; - uint8_t pad_013573F1; - rct12_peep_spawn peep_spawns[RCT2::Limits::MaxPeepSpawns]; - uint8_t guest_count_change_modifier; - uint8_t current_research_level; - uint8_t pad_01357400[4]; - uint32_t researched_ride_types[RCT2::Limits::MaxResearchedRideTypeQuads]; - uint32_t researched_ride_entries[RCT2::Limits::MaxResearchedRideEntryQuads]; - uint32_t researched_track_types_a[128]; - uint32_t researched_track_types_b[128]; + struct RideRatingCalculationData + { + uint16_t proximity_x; + uint16_t proximity_y; + uint16_t proximity_z; + uint16_t proximity_start_x; + uint16_t proximity_start_y; + uint16_t proximity_start_z; + uint8_t current_ride; + uint8_t state; + uint8_t proximity_track_type; + uint8_t proximity_base_height; + uint16_t proximity_total; + uint16_t proximity_scores[26]; + uint16_t num_brakes; + uint16_t num_reversers; + uint16_t station_flags; + }; + assert_struct_size(RideRatingCalculationData, 76); - // SC6[7] - uint16_t guests_in_park; - uint16_t guests_heading_for_park; + /** + * SV6/SC6 header chunk + * size: 0x20 + */ + struct S6Header + { + uint8_t type; // 0x00 + uint8_t classic_flag; // 0x01 + uint16_t num_packed_objects; // 0x02 + uint32_t version; // 0x04 + uint32_t magic_number; // 0x08 + uint8_t pad_0C[0x14]; + }; + assert_struct_size(S6Header, 0x20); - // Ignored in scenario - money32 expenditure_table[RCT2::Limits::ExpenditureTableMonthCount][RCT2::Limits::ExpenditureTypeCount]; + /** + * SC6 information chunk + * size: 0x198 + */ + struct S6Info + { + EditorStep editor_step; + uint8_t category; // 0x01 + uint8_t objective_type; // 0x02 + uint8_t objective_arg_1; // 0x03 + int32_t objective_arg_2; // 0x04 + int16_t objective_arg_3; // 0x08 + uint8_t pad_00A[0x3E]; + char name[64]; // 0x48 + char details[256]; // 0x88 + rct_object_entry entry; // 0x188 + }; + assert_struct_size(S6Info, 0x198); - // SC6[8] - uint16_t last_guests_in_park; - uint8_t pad_01357BCA[3]; - uint8_t handyman_colour; - uint8_t mechanic_colour; - uint8_t security_colour; + struct S6Data + { + // SC6[0] + S6Header header; - // Ignored in scenario - uint32_t researched_scenery_items[RCT2::Limits::MaxResearchedSceneryItemQuads]; + // SC6[1] + S6Info info; - // SC6[9] - uint16_t park_rating; + // SC6[2] + // packed objects - // Ignored in scenario - uint8_t park_rating_history[32]; - uint8_t guests_in_park_history[32]; + // SC6[3] + union + { + rct_object_entry Objects[RCT2_OBJECT_ENTRY_COUNT]; + struct + { + rct_object_entry RideObjects[Limits::MaxRideObject]; + rct_object_entry SceneryObjects[Limits::MaxSmallSceneryObjects]; + rct_object_entry LargeSceneryObjects[Limits::MaxLargeSceneryObjects]; + rct_object_entry WallSceneryObjects[Limits::MaxWallSceneryObjects]; + rct_object_entry BannerObjects[Limits::MaxBannerObjects]; + rct_object_entry PathObjects[Limits::MaxPathObjects]; + rct_object_entry PathAdditionObjects[Limits::MaxPathAdditionObjects]; + rct_object_entry SceneryGroupObjects[Limits::MaxScenereyGroupObjects]; + rct_object_entry ParkEntranceObjects[Limits::MaxParkEntranceObjects]; + rct_object_entry WaterObjects[Limits::MaxWaterObjects]; + rct_object_entry ScenarioTextObjects[Limits::MaxScenarioTextObjects]; + }; + }; - // SC6[10] - uint8_t active_research_types; - uint8_t research_progress_stage; - uint32_t last_researched_item_subject; - uint8_t pad_01357CF8[1000]; - uint32_t next_research_item; - uint16_t research_progress; - uint8_t next_research_category; - uint8_t next_research_expected_day; - uint8_t next_research_expected_month; - uint8_t guest_initial_happiness; - uint16_t park_size; - uint16_t guest_generation_probability; - uint16_t total_ride_value_for_money; - money32 maximum_loan; - money16 guest_initial_cash; - uint8_t guest_initial_hunger; - uint8_t guest_initial_thirst; - uint8_t objective_type; - uint8_t objective_year; - uint8_t pad_013580FA[2]; - money32 objective_currency; - uint16_t objective_guests; - uint8_t campaign_weeks_left[20]; - uint8_t campaign_ride_index[22]; + // SC6[4] + uint16_t elapsed_months; + uint16_t current_day; + uint32_t scenario_ticks; + uint32_t scenario_srand_0; + uint32_t scenario_srand_1; - // Ignored in scenario - money32 balance_history[RCT2::Limits::FinanceGraphSize]; + // SC6[5] + RCT12TileElement tile_elements[Limits::MaxTileElements]; - // SC6[11] - money32 current_expenditure; - money32 current_profit; - money32 weekly_profit_average_dividend; - uint16_t weekly_profit_average_divisor; - uint8_t pad_0135833A[2]; + // SC6[6] + uint32_t next_free_tile_element_pointer_index; + Entity sprites[Limits::MaxEntities]; + uint16_t sprite_lists_head[static_cast(EntityListId::Count)]; + uint16_t sprite_lists_count[static_cast(EntityListId::Count)]; + rct_string_id park_name; + uint8_t pad_013573D6[2]; + uint32_t park_name_args; + money32 initial_cash; + money32 current_loan; + uint32_t park_flags; + money16 park_entrance_fee; + uint16_t rct1_park_entrance_x; + uint16_t rct1_park_entrance_y; + uint8_t pad_013573EE[2]; + uint8_t rct1_park_entrance_z; + uint8_t pad_013573F1; + rct12_peep_spawn peep_spawns[Limits::MaxPeepSpawns]; + uint8_t guest_count_change_modifier; + uint8_t current_research_level; + uint8_t pad_01357400[4]; + uint32_t researched_ride_types[Limits::MaxResearchedRideTypeQuads]; + uint32_t researched_ride_entries[Limits::MaxResearchedRideEntryQuads]; + uint32_t researched_track_types_a[128]; + uint32_t researched_track_types_b[128]; - // Ignored in scenario - money32 weekly_profit_history[RCT2::Limits::FinanceGraphSize]; + // SC6[7] + uint16_t guests_in_park; + uint16_t guests_heading_for_park; - // SC6[12] - money32 park_value; + // Ignored in scenario + money32 expenditure_table[Limits::ExpenditureTableMonthCount][Limits::ExpenditureTypeCount]; - // Ignored in scenario - money32 park_value_history[RCT2::Limits::FinanceGraphSize]; + // SC6[8] + uint16_t last_guests_in_park; + uint8_t pad_01357BCA[3]; + uint8_t handyman_colour; + uint8_t mechanic_colour; + uint8_t security_colour; - // SC6[13] - money32 completed_company_value; - uint32_t total_admissions; - money32 income_from_admissions; - money32 company_value; - uint8_t peep_warning_throttle[16]; - rct12_award awards[RCT2::Limits::MaxAwards]; - money16 land_price; - money16 construction_rights_price; - uint16_t word_01358774; - uint8_t pad_01358776[2]; - uint32_t cd_key; - uint8_t pad_0135877C[64]; - uint32_t game_version_number; - money32 completed_company_value_record; - uint32_t loan_hash; - uint16_t ride_count; - uint8_t pad_013587CA[6]; - money32 historical_profit; - uint8_t pad_013587D4[4]; - char scenario_completed_name[32]; - money32 cash; - uint8_t pad_013587FC[50]; - uint16_t park_rating_casualty_penalty; - uint16_t map_size_units; - uint16_t map_size_minus_2; - uint16_t map_size; - uint16_t map_max_xy; - uint32_t same_price_throughout; - uint16_t suggested_max_guests; - uint16_t park_rating_warning_days; - uint8_t last_entrance_style; - uint8_t rct1_water_colour; - uint8_t pad_01358842[2]; - RCT12ResearchItem research_items[RCT2::Limits::MaxResearchItems]; - uint16_t map_base_z; - char scenario_name[64]; - char scenario_description[256]; - uint8_t current_interest_rate; - uint8_t pad_0135934B; - uint32_t same_price_throughout_extended; - int16_t park_entrance_x[RCT2::Limits::MaxParkEntrances]; - int16_t park_entrance_y[RCT2::Limits::MaxParkEntrances]; - int16_t park_entrance_z[RCT2::Limits::MaxParkEntrances]; - uint8_t park_entrance_direction[RCT2::Limits::MaxParkEntrances]; - char scenario_filename[256]; - uint8_t saved_expansion_pack_names[3256]; - RCT12Banner banners[RCT2::Limits::MaxBanners]; - char custom_strings[RCT2::Limits::MaxUserStrings][RCT2::Limits::MaxUserStringLength]; - uint32_t game_ticks_1; - rct2_ride rides[RCT2::Limits::MaxRidesInPark]; - uint16_t saved_age; - int16_t saved_view_x; - int16_t saved_view_y; - uint8_t saved_view_zoom; - uint8_t saved_view_rotation; - RCT12MapAnimation map_animations[RCT2::Limits::MaxAnimatedObjects]; - uint16_t num_map_animations; - uint8_t pad_0138B582[2]; - RCT2RideRatingCalculationData ride_ratings_calc_data; - uint8_t pad_0138B5D0[60]; - RCT12RideMeasurement ride_measurements[8]; - uint32_t next_guest_index; - uint16_t grass_and_scenery_tilepos; - uint32_t patrol_areas[(RCT2::Limits::MaxStaff + RCT2::Limits::StaffTypeCount) * RCT2::Limits::PatrolAreaSize]; - RCT2StaffMode staff_modes[RCT2::Limits::MaxStaff + RCT2::Limits::StaffTypeCount]; - uint8_t pad_13CA73E; - uint8_t pad_13CA73F; - uint8_t byte_13CA740; - uint8_t pad_13CA741; - uint8_t byte_13CA742[4]; // unused - uint8_t climate; - uint8_t pad_013CA747; - uint16_t climate_update_timer; - uint8_t current_weather; - uint8_t next_weather; - uint8_t temperature; - uint8_t next_temperature; - uint8_t current_weather_effect; - uint8_t next_weather_effect; - uint8_t current_weather_gloom; - uint8_t next_weather_gloom; - uint8_t current_weather_level; - uint8_t next_weather_level; - rct12_news_item news_items[RCT2::Limits::MaxNewsItems]; - char rct1_scenario_name[62]; // Unused in RCT2 - uint16_t rct1_scenario_slot_index; // Unused in RCT2 - uint32_t rct1_scenario_flags; // Unused in RCT2 - uint16_t wide_path_tile_loop_x; - uint16_t wide_path_tile_loop_y; - uint8_t pad_13CE778[434]; -}; -assert_struct_size(rct_s6_data, 0x46b44a); + // Ignored in scenario + uint32_t researched_scenery_items[Limits::MaxResearchedSceneryItemQuads]; -struct rct_stex_entry -{ - rct_string_id scenario_name; // 0x00 - rct_string_id park_name; // 0x02 - rct_string_id details; // 0x04 - uint8_t var_06; -}; -assert_struct_size(rct_stex_entry, 7); + // SC6[9] + uint16_t park_rating; + + // Ignored in scenario + uint8_t park_rating_history[32]; + uint8_t guests_in_park_history[32]; + + // SC6[10] + uint8_t active_research_types; + uint8_t research_progress_stage; + uint32_t last_researched_item_subject; + uint8_t pad_01357CF8[1000]; + uint32_t next_research_item; + uint16_t research_progress; + uint8_t next_research_category; + uint8_t next_research_expected_day; + uint8_t next_research_expected_month; + uint8_t guest_initial_happiness; + uint16_t park_size; + uint16_t guest_generation_probability; + uint16_t total_ride_value_for_money; + money32 maximum_loan; + money16 guest_initial_cash; + uint8_t guest_initial_hunger; + uint8_t guest_initial_thirst; + uint8_t objective_type; + uint8_t objective_year; + uint8_t pad_013580FA[2]; + money32 objective_currency; + uint16_t objective_guests; + uint8_t campaign_weeks_left[20]; + uint8_t campaign_ride_index[22]; + + // Ignored in scenario + money32 balance_history[Limits::FinanceGraphSize]; + + // SC6[11] + money32 current_expenditure; + money32 current_profit; + money32 weekly_profit_average_dividend; + uint16_t weekly_profit_average_divisor; + uint8_t pad_0135833A[2]; + + // Ignored in scenario + money32 weekly_profit_history[Limits::FinanceGraphSize]; + + // SC6[12] + money32 park_value; + + // Ignored in scenario + money32 park_value_history[Limits::FinanceGraphSize]; + + // SC6[13] + money32 completed_company_value; + uint32_t total_admissions; + money32 income_from_admissions; + money32 company_value; + uint8_t peep_warning_throttle[16]; + rct12_award awards[Limits::MaxAwards]; + money16 land_price; + money16 construction_rights_price; + uint16_t word_01358774; + uint8_t pad_01358776[2]; + uint32_t cd_key; + uint8_t pad_0135877C[64]; + uint32_t game_version_number; + money32 completed_company_value_record; + uint32_t loan_hash; + uint16_t ride_count; + uint8_t pad_013587CA[6]; + money32 historical_profit; + uint8_t pad_013587D4[4]; + char scenario_completed_name[32]; + money32 cash; + uint8_t pad_013587FC[50]; + uint16_t park_rating_casualty_penalty; + uint16_t map_size_units; + uint16_t map_size_minus_2; + uint16_t map_size; + uint16_t map_max_xy; + uint32_t same_price_throughout; + uint16_t suggested_max_guests; + uint16_t park_rating_warning_days; + uint8_t last_entrance_style; + uint8_t rct1_water_colour; + uint8_t pad_01358842[2]; + RCT12ResearchItem research_items[Limits::MaxResearchItems]; + uint16_t map_base_z; + char scenario_name[64]; + char scenario_description[256]; + uint8_t current_interest_rate; + uint8_t pad_0135934B; + uint32_t same_price_throughout_extended; + int16_t park_entrance_x[Limits::MaxParkEntrances]; + int16_t park_entrance_y[Limits::MaxParkEntrances]; + int16_t park_entrance_z[Limits::MaxParkEntrances]; + uint8_t park_entrance_direction[Limits::MaxParkEntrances]; + char scenario_filename[256]; + uint8_t saved_expansion_pack_names[3256]; + RCT12Banner banners[Limits::MaxBanners]; + char custom_strings[Limits::MaxUserStrings][Limits::MaxUserStringLength]; + uint32_t game_ticks_1; + Ride rides[Limits::MaxRidesInPark]; + uint16_t saved_age; + int16_t saved_view_x; + int16_t saved_view_y; + uint8_t saved_view_zoom; + uint8_t saved_view_rotation; + RCT12MapAnimation map_animations[Limits::MaxAnimatedObjects]; + uint16_t num_map_animations; + uint8_t pad_0138B582[2]; + RideRatingCalculationData ride_ratings_calc_data; + uint8_t pad_0138B5D0[60]; + RCT12RideMeasurement ride_measurements[8]; + uint32_t next_guest_index; + uint16_t grass_and_scenery_tilepos; + uint32_t patrol_areas[(Limits::MaxStaff + Limits::StaffTypeCount) * Limits::PatrolAreaSize]; + StaffMode staff_modes[Limits::MaxStaff + Limits::StaffTypeCount]; + uint8_t pad_13CA73E; + uint8_t pad_13CA73F; + uint8_t byte_13CA740; + uint8_t pad_13CA741; + uint8_t byte_13CA742[4]; // unused + uint8_t climate; + uint8_t pad_013CA747; + uint16_t climate_update_timer; + uint8_t current_weather; + uint8_t next_weather; + uint8_t temperature; + uint8_t next_temperature; + uint8_t current_weather_effect; + uint8_t next_weather_effect; + uint8_t current_weather_gloom; + uint8_t next_weather_gloom; + uint8_t current_weather_level; + uint8_t next_weather_level; + rct12_news_item news_items[Limits::MaxNewsItems]; + char rct1_scenario_name[62]; // Unused in RCT2 + uint16_t rct1_scenario_slot_index; // Unused in RCT2 + uint32_t rct1_scenario_flags; // Unused in RCT2 + uint16_t wide_path_tile_loop_x; + uint16_t wide_path_tile_loop_y; + uint8_t pad_13CE778[434]; + }; + assert_struct_size(S6Data, 0x46b44a); + + struct StexEntry + { + rct_string_id scenario_name; // 0x00 + rct_string_id park_name; // 0x02 + rct_string_id details; // 0x04 + uint8_t var_06; + }; + assert_struct_size(StexEntry, 7); #pragma pack(pop) + ObjectEntryIndex RCT2RideTypeToOpenRCT2RideType(uint8_t rct2RideType, const rct_ride_entry* rideEntry); + bool RCT2TrackTypeIsBooster(uint8_t rideType, uint16_t trackType); + bool RCT2RideTypeNeedsConversion(uint8_t rct2RideType); + uint8_t OpenRCT2RideTypeToRCT2RideType(ObjectEntryIndex openrct2Type); + track_type_t RCT2TrackTypeToOpenRCT2(RCT12TrackType origTrackType, uint8_t rideType, bool convertFlat); + RCT12TrackType OpenRCT2TrackTypeToRCT2(track_type_t origTrackType); + + /** + * Iterates an RCT2 string buffer and returns the length of the string in bytes. + * Handles single and multi-byte strings. + */ + size_t GetRCT2StringBufferLen(const char* buffer, size_t maxBufferLen); + + struct FootpathMapping + { + std::string_view Original; + std::string_view NormalSurface; + std::string_view QueueSurface; + std::string_view Railing; + }; + + const FootpathMapping* GetFootpathSurfaceId( + const ObjectEntryDescriptor& desc, bool ideallyLoaded = false, bool isQueue = false); + std::optional GetBestObjectEntryForSurface(std::string_view surface, std::string_view railings); +} // namespace RCT2 + std::vector DecryptSea(const fs::path& path); -ObjectEntryIndex RCT2RideTypeToOpenRCT2RideType(uint8_t rct2RideType, const rct_ride_entry* rideEntry); -bool RCT2TrackTypeIsBooster(uint8_t rideType, uint16_t trackType); -bool RCT2RideTypeNeedsConversion(uint8_t rct2RideType); -uint8_t OpenRCT2RideTypeToRCT2RideType(ObjectEntryIndex openrct2Type); -track_type_t RCT2TrackTypeToOpenRCT2(RCT12TrackType origTrackType, uint8_t rideType, bool convertFlat); -RCT12TrackType OpenRCT2TrackTypeToRCT2(track_type_t origTrackType); - -/** - * Iterates an RCT2 string buffer and returns the length of the string in bytes. - * Handles single and multi-byte strings. - */ -size_t GetRCT2StringBufferLen(const char* buffer, size_t maxBufferLen); - -struct FootpathMapping -{ - std::string_view Original; - std::string_view NormalSurface; - std::string_view QueueSurface; - std::string_view Railing; -}; - -const FootpathMapping* GetFootpathSurfaceId( - const ObjectEntryDescriptor& desc, bool ideallyLoaded = false, bool isQueue = false); -std::optional GetBestObjectEntryForSurface(std::string_view surface, std::string_view railings); diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index cbbef1a7e1..14c58e1eb8 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -74,427 +74,431 @@ #include #include +namespace RCT2 +{ #define DECRYPT_MONEY(money) (static_cast(Numerics::rol32((money) ^ 0xF4EC9621, 13))) -/** - * Class to import RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6). - */ -class S6Importer final : public IParkImporter -{ -private: - IObjectRepository& _objectRepository; - - const utf8* _s6Path = nullptr; - rct_s6_data _s6{}; - uint8_t _gameVersion = 0; - bool _isSV7 = false; - std::bitset _isFlatRide{}; - ObjectEntryIndex _pathToSurfaceMap[16]; - ObjectEntryIndex _pathToQueueSurfaceMap[16]; - ObjectEntryIndex _pathToRailingMap[16]; - -public: - S6Importer(IObjectRepository& objectRepository) - : _objectRepository(objectRepository) + /** + * Class to import RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6). + */ + class S6Importer final : public IParkImporter { - } + private: + IObjectRepository& _objectRepository; - ParkLoadResult Load(const utf8* path) override - { - const utf8* extension = Path::GetExtension(path); - if (String::Equals(extension, ".sc6", true)) + const utf8* _s6Path = nullptr; + S6Data _s6{}; + uint8_t _gameVersion = 0; + bool _isSV7 = false; + std::bitset _isFlatRide{}; + ObjectEntryIndex _pathToSurfaceMap[16]; + ObjectEntryIndex _pathToQueueSurfaceMap[16]; + ObjectEntryIndex _pathToRailingMap[16]; + + public: + S6Importer(IObjectRepository& objectRepository) + : _objectRepository(objectRepository) { - return LoadScenario(path); - } - if (String::Equals(extension, ".sv6", true)) - { - return LoadSavedGame(path); } - throw std::runtime_error("Invalid RCT2 park extension."); - } - - ParkLoadResult LoadSavedGame(const utf8* path, bool skipObjectCheck = false) override - { - auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); - auto result = LoadFromStream(&fs, false, skipObjectCheck); - _s6Path = path; - return result; - } - - ParkLoadResult LoadScenario(const utf8* path, bool skipObjectCheck = false) override - { - auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); - auto result = LoadFromStream(&fs, true, skipObjectCheck); - _s6Path = path; - return result; - } - - ParkLoadResult LoadFromStream( - OpenRCT2::IStream* stream, bool isScenario, [[maybe_unused]] bool skipObjectCheck = false, - const utf8* path = String::Empty) override - { - if (isScenario && !gConfigGeneral.allow_loading_with_incorrect_checksum && !SawyerEncoding::ValidateChecksum(stream)) + ParkLoadResult Load(const utf8* path) override { - throw IOException("Invalid checksum."); - } - - auto chunkReader = SawyerChunkReader(stream); - chunkReader.ReadChunk(&_s6.header, sizeof(_s6.header)); - - log_verbose("saved game classic_flag = 0x%02x", _s6.header.classic_flag); - if (isScenario) - { - if (_s6.header.type != S6_TYPE_SCENARIO) + const utf8* extension = Path::GetExtension(path); + if (String::Equals(extension, ".sc6", true)) { - throw std::runtime_error("Park is not a scenario."); + return LoadScenario(path); } - chunkReader.ReadChunk(&_s6.info, sizeof(_s6.info)); - } - else - { - if (_s6.header.type != S6_TYPE_SAVEDGAME) + if (String::Equals(extension, ".sv6", true)) { - throw std::runtime_error("Park is not a saved game."); + return LoadSavedGame(path); } + + throw std::runtime_error("Invalid RCT2 park extension."); } - if (_s6.header.classic_flag == 0xf) + ParkLoadResult LoadSavedGame(const utf8* path, bool skipObjectCheck = false) override { - throw UnsupportedRCTCFlagException(_s6.header.classic_flag); + auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); + auto result = LoadFromStream(&fs, false, skipObjectCheck); + _s6Path = path; + return result; } - // Read packed objects - // TODO try to contain this more and not store objects until later - for (uint16_t i = 0; i < _s6.header.num_packed_objects; i++) + ParkLoadResult LoadScenario(const utf8* path, bool skipObjectCheck = false) override { - _objectRepository.ExportPackedObject(stream); + auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); + auto result = LoadFromStream(&fs, true, skipObjectCheck); + _s6Path = path; + return result; } - if (path) + ParkLoadResult LoadFromStream( + OpenRCT2::IStream* stream, bool isScenario, [[maybe_unused]] bool skipObjectCheck = false, + const utf8* path = String::Empty) override { - auto extension = path_get_extension(path); - _isSV7 = _stricmp(extension, ".sv7") == 0; - } - - chunkReader.ReadChunk(&_s6.Objects, sizeof(_s6.Objects)); - - if (isScenario) - { - chunkReader.ReadChunk(&_s6.elapsed_months, 16); - chunkReader.ReadChunk(&_s6.tile_elements, sizeof(_s6.tile_elements)); - chunkReader.ReadChunk(&_s6.next_free_tile_element_pointer_index, 2560076); - chunkReader.ReadChunk(&_s6.guests_in_park, 4); - chunkReader.ReadChunk(&_s6.last_guests_in_park, 8); - chunkReader.ReadChunk(&_s6.park_rating, 2); - chunkReader.ReadChunk(&_s6.active_research_types, 1082); - chunkReader.ReadChunk(&_s6.current_expenditure, 16); - chunkReader.ReadChunk(&_s6.park_value, 4); - chunkReader.ReadChunk(&_s6.completed_company_value, 483816); - } - else - { - chunkReader.ReadChunk(&_s6.elapsed_months, 16); - chunkReader.ReadChunk(&_s6.tile_elements, sizeof(_s6.tile_elements)); - chunkReader.ReadChunk(&_s6.next_free_tile_element_pointer_index, 3048816); - } - - _s6Path = path; - - return ParkLoadResult(GetRequiredObjects()); - } - - bool GetDetails(scenario_index_entry* dst) override - { - *dst = {}; - return false; - } - - void Import() override - { - Initialise(); - - gEditorStep = _s6.info.editor_step; - gScenarioCategory = static_cast(_s6.info.category); - - // Some scenarios have their scenario details in UTF-8, due to earlier bugs in OpenRCT2. - auto loadMaybeUTF8 = [](std::string_view str) -> std::string { - return !IsLikelyUTF8(str) ? rct2_to_utf8(str, RCT2LanguageId::EnglishUK) : std::string(str); - }; - - if (_s6.header.type == S6_TYPE_SCENARIO) - { - gScenarioName = loadMaybeUTF8(_s6.info.name); - gScenarioDetails = loadMaybeUTF8(_s6.info.details); - } - else - { - // Saved games do not have an info chunk - gScenarioName = loadMaybeUTF8(_s6.scenario_name); - gScenarioDetails = loadMaybeUTF8(_s6.scenario_description); - } - - gDateMonthsElapsed = static_cast(_s6.elapsed_months); - gDateMonthTicks = _s6.current_day; - gCurrentTicks = _s6.game_ticks_1; - - scenario_rand_seed(_s6.scenario_srand_0, _s6.scenario_srand_1); - - DetermineFlatRideStatus(); - ImportTileElements(); - ImportEntities(); - - gInitialCash = ToMoney64(_s6.initial_cash); - gBankLoan = ToMoney64(_s6.current_loan); - gParkFlags = _s6.park_flags; - gParkEntranceFee = _s6.park_entrance_fee; - // rct1_park_entrance_x - // rct1_park_entrance_y - // pad_013573EE - // rct1_park_entrance_z - - ImportPeepSpawns(); - - gGuestChangeModifier = _s6.guest_count_change_modifier; - gResearchFundingLevel = _s6.current_research_level; - // pad_01357400 - // _s6.researched_track_types_a - // _s6.researched_track_types_b - - gNumGuestsInPark = _s6.guests_in_park; - gNumGuestsHeadingForPark = _s6.guests_heading_for_park; - - for (size_t i = 0; i < RCT2::Limits::ExpenditureTableMonthCount; i++) - { - for (size_t j = 0; j < RCT2::Limits::ExpenditureTypeCount; j++) + if (isScenario && !gConfigGeneral.allow_loading_with_incorrect_checksum + && !SawyerEncoding::ValidateChecksum(stream)) { - gExpenditureTable[i][j] = ToMoney64(_s6.expenditure_table[i][j]); + throw IOException("Invalid checksum."); } - } - gNumGuestsInParkLastWeek = _s6.last_guests_in_park; - // pad_01357BCA - gStaffHandymanColour = _s6.handyman_colour; - gStaffMechanicColour = _s6.mechanic_colour; - gStaffSecurityColour = _s6.security_colour; + auto chunkReader = SawyerChunkReader(stream); + chunkReader.ReadChunk(&_s6.header, sizeof(_s6.header)); - gParkRating = _s6.park_rating; - - auto& park = OpenRCT2::GetContext()->GetGameState()->GetPark(); - park.ResetHistories(); - std::copy(std::begin(_s6.park_rating_history), std::end(_s6.park_rating_history), gParkRatingHistory); - for (size_t i = 0; i < std::size(_s6.guests_in_park_history); i++) - { - if (_s6.guests_in_park_history[i] != RCT12ParkHistoryUndefined) + log_verbose("saved game classic_flag = 0x%02x", _s6.header.classic_flag); + if (isScenario) { - gGuestsInParkHistory[i] = _s6.guests_in_park_history[i] * RCT12GuestsInParkHistoryFactor; - } - } - - gResearchPriorities = _s6.active_research_types; - gResearchProgressStage = _s6.research_progress_stage; - if (_s6.last_researched_item_subject != RCT12_RESEARCHED_ITEMS_SEPARATOR) - gResearchLastItem = ResearchItem( - RCT12ResearchItem{ _s6.last_researched_item_subject, EnumValue(ResearchCategory::Transport) }); - else - gResearchLastItem = std::nullopt; - // pad_01357CF8 - if (_s6.next_research_item != RCT12_RESEARCHED_ITEMS_SEPARATOR) - gResearchNextItem = ResearchItem(RCT12ResearchItem{ _s6.next_research_item, _s6.next_research_category }); - else - gResearchNextItem = std::nullopt; - - gResearchProgress = _s6.research_progress; - gResearchExpectedDay = _s6.next_research_expected_day; - gResearchExpectedMonth = _s6.next_research_expected_month; - gGuestInitialHappiness = _s6.guest_initial_happiness; - gParkSize = _s6.park_size; - _guestGenerationProbability = _s6.guest_generation_probability; - gTotalRideValueForMoney = _s6.total_ride_value_for_money; - gMaxBankLoan = ToMoney64(_s6.maximum_loan); - gGuestInitialCash = _s6.guest_initial_cash; - gGuestInitialHunger = _s6.guest_initial_hunger; - gGuestInitialThirst = _s6.guest_initial_thirst; - gScenarioObjective.Type = _s6.objective_type; - gScenarioObjective.Year = _s6.objective_year; - // pad_013580FA - gScenarioObjective.Currency = _s6.objective_currency; - // In RCT2, the ride string IDs start at index STR_0002 and are directly mappable. - // This is not always the case in OpenRCT2, so we use the actual ride ID. - if (gScenarioObjective.Type == OBJECTIVE_BUILD_THE_BEST) - gScenarioObjective.RideId = _s6.objective_guests - RCT2_RIDE_STRING_START; - else - gScenarioObjective.NumGuests = _s6.objective_guests; - ImportMarketingCampaigns(); - - gCurrentExpenditure = ToMoney64(_s6.current_expenditure); - gCurrentProfit = ToMoney64(_s6.current_profit); - gWeeklyProfitAverageDividend = ToMoney64(_s6.weekly_profit_average_dividend); - gWeeklyProfitAverageDivisor = _s6.weekly_profit_average_divisor; - // pad_0135833A - - gParkValue = ToMoney64(_s6.park_value); - - for (size_t i = 0; i < RCT2::Limits::FinanceGraphSize; i++) - { - gCashHistory[i] = ToMoney64(_s6.balance_history[i]); - gWeeklyProfitHistory[i] = ToMoney64(_s6.weekly_profit_history[i]); - gParkValueHistory[i] = ToMoney64(_s6.park_value_history[i]); - } - - gScenarioCompletedCompanyValue = RCT12CompletedCompanyValueToOpenRCT2(_s6.completed_company_value); - gTotalAdmissions = _s6.total_admissions; - gTotalIncomeFromAdmissions = ToMoney64(_s6.income_from_admissions); - gCompanyValue = ToMoney64(_s6.company_value); - std::memcpy(gPeepWarningThrottle, _s6.peep_warning_throttle, sizeof(_s6.peep_warning_throttle)); - - // Awards - for (int32_t i = 0; i < RCT2::Limits::MaxAwards; i++) - { - rct12_award* src = &_s6.awards[i]; - Award* dst = &gCurrentAwards[i]; - dst->Time = src->time; - dst->Type = src->type; - } - - gLandPrice = _s6.land_price; - gConstructionRightsPrice = _s6.construction_rights_price; - // unk_01358774 - // pad_01358776 - // _s6.cd_key - _gameVersion = _s6.game_version_number; - gScenarioCompanyValueRecord = _s6.completed_company_value_record; - // _s6.loan_hash; - // pad_013587CA - gHistoricalProfit = ToMoney64(_s6.historical_profit); - // pad_013587D4 - gScenarioCompletedBy = std::string_view(_s6.scenario_completed_name, sizeof(_s6.scenario_completed_name)); - gCash = ToMoney64(DECRYPT_MONEY(_s6.cash)); - // pad_013587FC - gParkRatingCasualtyPenalty = _s6.park_rating_casualty_penalty; - gMapSize = _s6.map_size; - gSamePriceThroughoutPark = _s6.same_price_throughout - | (static_cast(_s6.same_price_throughout_extended) << 32); - _suggestedGuestMaximum = _s6.suggested_max_guests; - gScenarioParkRatingWarningDays = _s6.park_rating_warning_days; - gLastEntranceStyle = _s6.last_entrance_style; - // rct1_water_colour - // pad_01358842 - ImportResearchList(); - gMapBaseZ = _s6.map_base_z; - gBankLoanInterestRate = _s6.current_interest_rate; - // pad_0135934B - // Preserve compatibility with vanilla RCT2's save format. - gParkEntrances.clear(); - for (uint8_t i = 0; i < RCT2::Limits::MaxParkEntrances; i++) - { - if (_s6.park_entrance_x[i] != LOCATION_NULL) - { - CoordsXYZD entrance; - entrance.x = _s6.park_entrance_x[i]; - entrance.y = _s6.park_entrance_y[i]; - entrance.z = _s6.park_entrance_z[i]; - entrance.direction = _s6.park_entrance_direction[i]; - gParkEntrances.push_back(entrance); - } - } - if (_s6.header.type == S6_TYPE_SCENARIO) - { - // _s6.scenario_filename is wrong for some RCT2 expansion scenarios, so we use the real filename - gScenarioFileName = String::ToStd(Path::GetFileName(_s6Path)); - } - else - { - // For savegames the filename can be arbitrary, so we have no choice but to rely on the name provided - gScenarioFileName = std::string(String::ToStringView(_s6.scenario_filename, std::size(_s6.scenario_filename))); - } - gCurrentRealTimeTicks = 0; - - ImportRides(); - - gSavedAge = _s6.saved_age; - gSavedView = ScreenCoordsXY{ _s6.saved_view_x, _s6.saved_view_y }; - gSavedViewZoom = _s6.saved_view_zoom; - gSavedViewRotation = _s6.saved_view_rotation; - - ImportRideRatingsCalcData(); - ImportRideMeasurements(); - gNextGuestNumber = _s6.next_guest_index; - gGrassSceneryTileLoopPosition = _s6.grass_and_scenery_tilepos; - // unk_13CA73E - // pad_13CA73F - // unk_13CA740 - gClimate = ClimateType{ _s6.climate }; - // pad_13CA741; - // byte_13CA742 - // pad_013CA747 - gClimateUpdateTimer = _s6.climate_update_timer; - gClimateCurrent.Weather = WeatherType{ _s6.current_weather }; - gClimateNext.Weather = WeatherType{ _s6.next_weather }; - gClimateCurrent.Temperature = _s6.temperature; - gClimateNext.Temperature = _s6.next_temperature; - gClimateCurrent.WeatherEffect = WeatherEffectType{ _s6.current_weather_effect }; - gClimateNext.WeatherEffect = WeatherEffectType{ _s6.next_weather_effect }; - gClimateCurrent.WeatherGloom = _s6.current_weather_gloom; - gClimateNext.WeatherGloom = _s6.next_weather_gloom; - gClimateCurrent.Level = static_cast(_s6.current_weather_level); - gClimateNext.Level = static_cast(_s6.next_weather_level); - - // News items - News::InitQueue(); - for (size_t i = 0; i < RCT2::Limits::MaxNewsItems; i++) - { - const rct12_news_item* src = &_s6.news_items[i]; - News::Item* dst = &gNewsItems[i]; - if (src->Type < News::ItemTypeCount) - { - dst->Type = static_cast(src->Type); - dst->Flags = src->Flags; - dst->Assoc = src->Assoc; - dst->Ticks = src->Ticks; - dst->MonthYear = src->MonthYear; - dst->Day = src->Day; - dst->Text = ConvertFormattedStringToOpenRCT2(std::string_view(src->Text, sizeof(src->Text))); + if (_s6.header.type != S6_TYPE_SCENARIO) + { + throw std::runtime_error("Park is not a scenario."); + } + chunkReader.ReadChunk(&_s6.info, sizeof(_s6.info)); } else { - // In case where news item type is broken, consider all remaining news items invalid. - log_error("Invalid news type 0x%x for news item %d, ignoring remaining news items", src->Type, i); - // Still need to set the correct type to properly terminate the queue - dst->Type = News::ItemType::Null; - break; + if (_s6.header.type != S6_TYPE_SAVEDGAME) + { + throw std::runtime_error("Park is not a saved game."); + } } + + if (_s6.header.classic_flag == 0xf) + { + throw UnsupportedRCTCFlagException(_s6.header.classic_flag); + } + + // Read packed objects + // TODO try to contain this more and not store objects until later + for (uint16_t i = 0; i < _s6.header.num_packed_objects; i++) + { + _objectRepository.ExportPackedObject(stream); + } + + if (path) + { + auto extension = path_get_extension(path); + _isSV7 = _stricmp(extension, ".sv7") == 0; + } + + chunkReader.ReadChunk(&_s6.Objects, sizeof(_s6.Objects)); + + if (isScenario) + { + chunkReader.ReadChunk(&_s6.elapsed_months, 16); + chunkReader.ReadChunk(&_s6.tile_elements, sizeof(_s6.tile_elements)); + chunkReader.ReadChunk(&_s6.next_free_tile_element_pointer_index, 2560076); + chunkReader.ReadChunk(&_s6.guests_in_park, 4); + chunkReader.ReadChunk(&_s6.last_guests_in_park, 8); + chunkReader.ReadChunk(&_s6.park_rating, 2); + chunkReader.ReadChunk(&_s6.active_research_types, 1082); + chunkReader.ReadChunk(&_s6.current_expenditure, 16); + chunkReader.ReadChunk(&_s6.park_value, 4); + chunkReader.ReadChunk(&_s6.completed_company_value, 483816); + } + else + { + chunkReader.ReadChunk(&_s6.elapsed_months, 16); + chunkReader.ReadChunk(&_s6.tile_elements, sizeof(_s6.tile_elements)); + chunkReader.ReadChunk(&_s6.next_free_tile_element_pointer_index, 3048816); + } + + _s6Path = path; + + return ParkLoadResult(GetRequiredObjects()); } - // pad_13CE730 - // rct1_scenario_flags - gWidePathTileLoopPosition.x = _s6.wide_path_tile_loop_x; - gWidePathTileLoopPosition.y = _s6.wide_path_tile_loop_y; - // pad_13CE778 - - // Fix and set dynamic variables - map_strip_ghost_flag_from_elements(); - game_convert_strings_to_utf8(); - map_count_remaining_land_rights(); - determine_ride_entrance_and_exit_locations(); - - park.Name = GetUserString(_s6.park_name); - - FixLandOwnership(); - - research_determine_first_of_type(); - staff_update_greyed_patrol_areas(); - - CheatsReset(); - ClearRestrictedScenery(); - } - - void FixLandOwnership() const - { - if (String::Equals(_s6.scenario_filename, "Europe - European Cultural Festival.SC6")) + bool GetDetails(scenario_index_entry* dst) override { - // This scenario breaks pathfinding. Create passages between the worlds. (List is grouped by neighbouring tiles.) - // clang-format off + *dst = {}; + return false; + } + + void Import() override + { + Initialise(); + + gEditorStep = _s6.info.editor_step; + gScenarioCategory = static_cast(_s6.info.category); + + // Some scenarios have their scenario details in UTF-8, due to earlier bugs in OpenRCT2. + auto loadMaybeUTF8 = [](std::string_view str) -> std::string { + return !IsLikelyUTF8(str) ? rct2_to_utf8(str, RCT2LanguageId::EnglishUK) : std::string(str); + }; + + if (_s6.header.type == S6_TYPE_SCENARIO) + { + gScenarioName = loadMaybeUTF8(_s6.info.name); + gScenarioDetails = loadMaybeUTF8(_s6.info.details); + } + else + { + // Saved games do not have an info chunk + gScenarioName = loadMaybeUTF8(_s6.scenario_name); + gScenarioDetails = loadMaybeUTF8(_s6.scenario_description); + } + + gDateMonthsElapsed = static_cast(_s6.elapsed_months); + gDateMonthTicks = _s6.current_day; + gCurrentTicks = _s6.game_ticks_1; + + scenario_rand_seed(_s6.scenario_srand_0, _s6.scenario_srand_1); + + DetermineFlatRideStatus(); + ImportTileElements(); + ImportEntities(); + + gInitialCash = ToMoney64(_s6.initial_cash); + gBankLoan = ToMoney64(_s6.current_loan); + gParkFlags = _s6.park_flags; + gParkEntranceFee = _s6.park_entrance_fee; + // rct1_park_entrance_x + // rct1_park_entrance_y + // pad_013573EE + // rct1_park_entrance_z + + ImportPeepSpawns(); + + gGuestChangeModifier = _s6.guest_count_change_modifier; + gResearchFundingLevel = _s6.current_research_level; + // pad_01357400 + // _s6.researched_track_types_a + // _s6.researched_track_types_b + + gNumGuestsInPark = _s6.guests_in_park; + gNumGuestsHeadingForPark = _s6.guests_heading_for_park; + + for (size_t i = 0; i < Limits::ExpenditureTableMonthCount; i++) + { + for (size_t j = 0; j < Limits::ExpenditureTypeCount; j++) + { + gExpenditureTable[i][j] = ToMoney64(_s6.expenditure_table[i][j]); + } + } + + gNumGuestsInParkLastWeek = _s6.last_guests_in_park; + // pad_01357BCA + gStaffHandymanColour = _s6.handyman_colour; + gStaffMechanicColour = _s6.mechanic_colour; + gStaffSecurityColour = _s6.security_colour; + + gParkRating = _s6.park_rating; + + auto& park = OpenRCT2::GetContext()->GetGameState()->GetPark(); + park.ResetHistories(); + std::copy(std::begin(_s6.park_rating_history), std::end(_s6.park_rating_history), gParkRatingHistory); + for (size_t i = 0; i < std::size(_s6.guests_in_park_history); i++) + { + if (_s6.guests_in_park_history[i] != RCT12ParkHistoryUndefined) + { + gGuestsInParkHistory[i] = _s6.guests_in_park_history[i] * RCT12GuestsInParkHistoryFactor; + } + } + + gResearchPriorities = _s6.active_research_types; + gResearchProgressStage = _s6.research_progress_stage; + if (_s6.last_researched_item_subject != RCT12_RESEARCHED_ITEMS_SEPARATOR) + gResearchLastItem = ResearchItem( + RCT12ResearchItem{ _s6.last_researched_item_subject, EnumValue(ResearchCategory::Transport) }); + else + gResearchLastItem = std::nullopt; + // pad_01357CF8 + if (_s6.next_research_item != RCT12_RESEARCHED_ITEMS_SEPARATOR) + gResearchNextItem = ResearchItem(RCT12ResearchItem{ _s6.next_research_item, _s6.next_research_category }); + else + gResearchNextItem = std::nullopt; + + gResearchProgress = _s6.research_progress; + gResearchExpectedDay = _s6.next_research_expected_day; + gResearchExpectedMonth = _s6.next_research_expected_month; + gGuestInitialHappiness = _s6.guest_initial_happiness; + gParkSize = _s6.park_size; + _guestGenerationProbability = _s6.guest_generation_probability; + gTotalRideValueForMoney = _s6.total_ride_value_for_money; + gMaxBankLoan = ToMoney64(_s6.maximum_loan); + gGuestInitialCash = _s6.guest_initial_cash; + gGuestInitialHunger = _s6.guest_initial_hunger; + gGuestInitialThirst = _s6.guest_initial_thirst; + gScenarioObjective.Type = _s6.objective_type; + gScenarioObjective.Year = _s6.objective_year; + // pad_013580FA + gScenarioObjective.Currency = _s6.objective_currency; + // In RCT2, the ride string IDs start at index STR_0002 and are directly mappable. + // This is not always the case in OpenRCT2, so we use the actual ride ID. + if (gScenarioObjective.Type == OBJECTIVE_BUILD_THE_BEST) + gScenarioObjective.RideId = _s6.objective_guests - RCT2_RIDE_STRING_START; + else + gScenarioObjective.NumGuests = _s6.objective_guests; + ImportMarketingCampaigns(); + + gCurrentExpenditure = ToMoney64(_s6.current_expenditure); + gCurrentProfit = ToMoney64(_s6.current_profit); + gWeeklyProfitAverageDividend = ToMoney64(_s6.weekly_profit_average_dividend); + gWeeklyProfitAverageDivisor = _s6.weekly_profit_average_divisor; + // pad_0135833A + + gParkValue = ToMoney64(_s6.park_value); + + for (size_t i = 0; i < Limits::FinanceGraphSize; i++) + { + gCashHistory[i] = ToMoney64(_s6.balance_history[i]); + gWeeklyProfitHistory[i] = ToMoney64(_s6.weekly_profit_history[i]); + gParkValueHistory[i] = ToMoney64(_s6.park_value_history[i]); + } + + gScenarioCompletedCompanyValue = RCT12CompletedCompanyValueToOpenRCT2(_s6.completed_company_value); + gTotalAdmissions = _s6.total_admissions; + gTotalIncomeFromAdmissions = ToMoney64(_s6.income_from_admissions); + gCompanyValue = ToMoney64(_s6.company_value); + std::memcpy(gPeepWarningThrottle, _s6.peep_warning_throttle, sizeof(_s6.peep_warning_throttle)); + + // Awards + for (int32_t i = 0; i < Limits::MaxAwards; i++) + { + rct12_award* src = &_s6.awards[i]; + Award* dst = &gCurrentAwards[i]; + dst->Time = src->time; + dst->Type = src->type; + } + + gLandPrice = _s6.land_price; + gConstructionRightsPrice = _s6.construction_rights_price; + // unk_01358774 + // pad_01358776 + // _s6.cd_key + _gameVersion = _s6.game_version_number; + gScenarioCompanyValueRecord = _s6.completed_company_value_record; + // _s6.loan_hash; + // pad_013587CA + gHistoricalProfit = ToMoney64(_s6.historical_profit); + // pad_013587D4 + gScenarioCompletedBy = std::string_view(_s6.scenario_completed_name, sizeof(_s6.scenario_completed_name)); + gCash = ToMoney64(DECRYPT_MONEY(_s6.cash)); + // pad_013587FC + gParkRatingCasualtyPenalty = _s6.park_rating_casualty_penalty; + gMapSize = _s6.map_size; + gSamePriceThroughoutPark = _s6.same_price_throughout + | (static_cast(_s6.same_price_throughout_extended) << 32); + _suggestedGuestMaximum = _s6.suggested_max_guests; + gScenarioParkRatingWarningDays = _s6.park_rating_warning_days; + gLastEntranceStyle = _s6.last_entrance_style; + // rct1_water_colour + // pad_01358842 + ImportResearchList(); + gMapBaseZ = _s6.map_base_z; + gBankLoanInterestRate = _s6.current_interest_rate; + // pad_0135934B + // Preserve compatibility with vanilla RCT2's save format. + gParkEntrances.clear(); + for (uint8_t i = 0; i < Limits::MaxParkEntrances; i++) + { + if (_s6.park_entrance_x[i] != LOCATION_NULL) + { + CoordsXYZD entrance; + entrance.x = _s6.park_entrance_x[i]; + entrance.y = _s6.park_entrance_y[i]; + entrance.z = _s6.park_entrance_z[i]; + entrance.direction = _s6.park_entrance_direction[i]; + gParkEntrances.push_back(entrance); + } + } + if (_s6.header.type == S6_TYPE_SCENARIO) + { + // _s6.scenario_filename is wrong for some RCT2 expansion scenarios, so we use the real filename + gScenarioFileName = String::ToStd(Path::GetFileName(_s6Path)); + } + else + { + // For savegames the filename can be arbitrary, so we have no choice but to rely on the name provided + gScenarioFileName = std::string(String::ToStringView(_s6.scenario_filename, std::size(_s6.scenario_filename))); + } + gCurrentRealTimeTicks = 0; + + ImportRides(); + + gSavedAge = _s6.saved_age; + gSavedView = ScreenCoordsXY{ _s6.saved_view_x, _s6.saved_view_y }; + gSavedViewZoom = _s6.saved_view_zoom; + gSavedViewRotation = _s6.saved_view_rotation; + + ImportRideRatingsCalcData(); + ImportRideMeasurements(); + gNextGuestNumber = _s6.next_guest_index; + gGrassSceneryTileLoopPosition = _s6.grass_and_scenery_tilepos; + // unk_13CA73E + // pad_13CA73F + // unk_13CA740 + gClimate = ClimateType{ _s6.climate }; + // pad_13CA741; + // byte_13CA742 + // pad_013CA747 + gClimateUpdateTimer = _s6.climate_update_timer; + gClimateCurrent.Weather = WeatherType{ _s6.current_weather }; + gClimateNext.Weather = WeatherType{ _s6.next_weather }; + gClimateCurrent.Temperature = _s6.temperature; + gClimateNext.Temperature = _s6.next_temperature; + gClimateCurrent.WeatherEffect = WeatherEffectType{ _s6.current_weather_effect }; + gClimateNext.WeatherEffect = WeatherEffectType{ _s6.next_weather_effect }; + gClimateCurrent.WeatherGloom = _s6.current_weather_gloom; + gClimateNext.WeatherGloom = _s6.next_weather_gloom; + gClimateCurrent.Level = static_cast(_s6.current_weather_level); + gClimateNext.Level = static_cast(_s6.next_weather_level); + + // News items + News::InitQueue(); + for (size_t i = 0; i < Limits::MaxNewsItems; i++) + { + const rct12_news_item* src = &_s6.news_items[i]; + News::Item* dst = &gNewsItems[i]; + if (src->Type < News::ItemTypeCount) + { + dst->Type = static_cast(src->Type); + dst->Flags = src->Flags; + dst->Assoc = src->Assoc; + dst->Ticks = src->Ticks; + dst->MonthYear = src->MonthYear; + dst->Day = src->Day; + dst->Text = ConvertFormattedStringToOpenRCT2(std::string_view(src->Text, sizeof(src->Text))); + } + else + { + // In case where news item type is broken, consider all remaining news items invalid. + log_error("Invalid news type 0x%x for news item %d, ignoring remaining news items", src->Type, i); + // Still need to set the correct type to properly terminate the queue + dst->Type = News::ItemType::Null; + break; + } + } + + // pad_13CE730 + // rct1_scenario_flags + gWidePathTileLoopPosition.x = _s6.wide_path_tile_loop_x; + gWidePathTileLoopPosition.y = _s6.wide_path_tile_loop_y; + // pad_13CE778 + + // Fix and set dynamic variables + map_strip_ghost_flag_from_elements(); + game_convert_strings_to_utf8(); + map_count_remaining_land_rights(); + determine_ride_entrance_and_exit_locations(); + + park.Name = GetUserString(_s6.park_name); + + FixLandOwnership(); + + research_determine_first_of_type(); + staff_update_greyed_patrol_areas(); + + CheatsReset(); + ClearRestrictedScenery(); + } + + void FixLandOwnership() const + { + if (String::Equals(_s6.scenario_filename, "Europe - European Cultural Festival.SC6")) + { + // This scenario breaks pathfinding. Create passages between the worlds. (List is grouped by neighbouring + // tiles.) + // clang-format off FixLandOwnershipTilesWithOwnership( { { 67, 94 }, { 68, 94 }, { 69, 94 }, @@ -503,807 +507,701 @@ public: { 32, 79 }, { 32, 80 }, { 32, 81 }, }, OWNERSHIP_OWNED); - // clang-format on - } - else if (String::Equals(gScenarioFileName, "N America - Extreme Hawaiian Island.SC6")) - { - FixLandOwnershipTilesWithOwnership( - { - { 132, 124 }, - { 133, 124 }, - { 133, 125 }, - { 133, 126 }, - { 119, 35 }, - { 132, 62 }, - { 133, 67 }, - { 136, 71 }, - { 87, 33 }, - { 87, 34 }, - { 90, 36 }, - { 91, 36 }, - }, - OWNERSHIP_OWNED); - // We set the doNotDowngrade flag for cases where the player has used a cheat to own all land. - FixLandOwnershipTilesWithOwnership( - { - { 49, 99 }, - { 50, 99 }, - { 88, 110 }, - }, - OWNERSHIP_AVAILABLE, true); - } - } - - void ImportRides() - { - for (uint8_t index = 0; index < RCT2::Limits::MaxRidesInPark; index++) - { - auto src = &_s6.rides[index]; - if (src->type != RIDE_TYPE_NULL) + // clang-format on + } + else if (String::Equals(gScenarioFileName, "N America - Extreme Hawaiian Island.SC6")) { - const auto rideId = static_cast(index); - auto dst = GetOrAllocateRide(rideId); - ImportRide(dst, src, rideId); + FixLandOwnershipTilesWithOwnership( + { + { 132, 124 }, + { 133, 124 }, + { 133, 125 }, + { 133, 126 }, + { 119, 35 }, + { 132, 62 }, + { 133, 67 }, + { 136, 71 }, + { 87, 33 }, + { 87, 34 }, + { 90, 36 }, + { 91, 36 }, + }, + OWNERSHIP_OWNED); + // We set the doNotDowngrade flag for cases where the player has used a cheat to own all land. + FixLandOwnershipTilesWithOwnership( + { + { 49, 99 }, + { 50, 99 }, + { 88, 110 }, + }, + OWNERSHIP_AVAILABLE, true); } } - } - /** - * This code is needed to detect hacks where a tracked ride has been made invisible - * by setting its ride type to a flat ride. - * - * The function should classify rides as follows: - * 1. If the ride type is tracked and its vehicles also belong on tracks, it should be classified as tracked. - * 2. If the ride type is a flat ride, but its vehicles belong on tracks, - * it should be classified as tracked (Crooked House mod). - * 3. If the ride type is tracked and its vehicles belong to a flat ride, it should be classified as tracked. - * 4. If the ride type is a flat ride and its vehicles also belong to a flat ride, it should be classified as a flat ride. - */ - void DetermineFlatRideStatus() - { - for (uint8_t index = 0; index < RCT2::Limits::MaxRidesInPark; index++) + void ImportRides() { - auto src = &_s6.rides[index]; - if (src->type == RIDE_TYPE_NULL) - continue; + for (uint8_t index = 0; index < Limits::MaxRidesInPark; index++) + { + auto src = &_s6.rides[index]; + if (src->type != RIDE_TYPE_NULL) + { + const auto rideId = static_cast(index); + auto dst = GetOrAllocateRide(rideId); + ImportRide(dst, src, rideId); + } + } + } + /** + * This code is needed to detect hacks where a tracked ride has been made invisible + * by setting its ride type to a flat ride. + * + * The function should classify rides as follows: + * 1. If the ride type is tracked and its vehicles also belong on tracks, it should be classified as tracked. + * 2. If the ride type is a flat ride, but its vehicles belong on tracks, + * it should be classified as tracked (Crooked House mod). + * 3. If the ride type is tracked and its vehicles belong to a flat ride, it should be classified as tracked. + * 4. If the ride type is a flat ride and its vehicles also belong to a flat ride, it should be classified as a flat + * ride. + */ + void DetermineFlatRideStatus() + { + for (uint8_t index = 0; index < Limits::MaxRidesInPark; index++) + { + auto src = &_s6.rides[index]; + if (src->type == RIDE_TYPE_NULL) + continue; + + auto subtype = RCTEntryIndexToOpenRCT2EntryIndex(src->subtype); + auto* rideEntry = get_ride_entry(subtype); + // If the ride is tracked, we don’t need to check the vehicle any more. + if (!GetRideTypeDescriptor(src->type).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) + { + _isFlatRide[index] = false; + continue; + } + + // We have established the ride type is a flat ride, which means the vehicle now determines whether it is a + // true flat ride (scenario 4) or a tracked ride with an invisibility hack (scenario 2). + ObjectEntryIndex originalRideType = src->type; + if (rideEntry != nullptr) + { + originalRideType = ride_entry_get_first_non_null_ride_type(rideEntry); + } + const auto isFlatRide = GetRideTypeDescriptor(originalRideType).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE); + _isFlatRide.set(static_cast(index), isFlatRide); + } + } + + bool IsFlatRide(const uint8_t rct12RideIndex) + { + if (rct12RideIndex == RCT12_RIDE_ID_NULL) + return false; + return _isFlatRide[rct12RideIndex]; + } + + void ImportRide(::Ride* dst, const RCT2::Ride* src, const ride_id_t rideIndex) + { + *dst = {}; + dst->id = rideIndex; + + ObjectEntryIndex rideType = src->type; auto subtype = RCTEntryIndexToOpenRCT2EntryIndex(src->subtype); - auto* rideEntry = get_ride_entry(subtype); - // If the ride is tracked, we don’t need to check the vehicle any more. - if (!GetRideTypeDescriptor(src->type).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) + if (RCT2RideTypeNeedsConversion(src->type)) { - _isFlatRide[index] = false; - continue; + auto* rideEntry = get_ride_entry(subtype); + if (rideEntry != nullptr) + { + rideType = RCT2RideTypeToOpenRCT2RideType(src->type, rideEntry); + } } - // We have established the ride type is a flat ride, which means the vehicle now determines whether it is a - // true flat ride (scenario 4) or a tracked ride with an invisibility hack (scenario 2). - ObjectEntryIndex originalRideType = src->type; - if (rideEntry != nullptr) + if (rideType >= RIDE_TYPE_COUNT) { - originalRideType = ride_entry_get_first_non_null_ride_type(rideEntry); + log_error("Invalid ride type for a ride in this save."); + throw UnsupportedRideTypeException(rideType); } - const auto isFlatRide = GetRideTypeDescriptor(originalRideType).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE); - _isFlatRide.set(static_cast(index), isFlatRide); - } - } + dst->type = rideType; + dst->subtype = subtype; + // pad_002; + dst->mode = static_cast(src->mode); + dst->colour_scheme_type = src->colour_scheme_type; - bool IsFlatRide(const uint8_t rct12RideIndex) - { - if (rct12RideIndex == RCT12_RIDE_ID_NULL) - return false; - return _isFlatRide[rct12RideIndex]; - } - - void ImportRide(Ride* dst, const rct2_ride* src, const ride_id_t rideIndex) - { - *dst = {}; - dst->id = rideIndex; - - ObjectEntryIndex rideType = src->type; - auto subtype = RCTEntryIndexToOpenRCT2EntryIndex(src->subtype); - if (RCT2RideTypeNeedsConversion(src->type)) - { - auto* rideEntry = get_ride_entry(subtype); - if (rideEntry != nullptr) + for (uint8_t i = 0; i < Limits::MaxTrainsPerRide; i++) { - rideType = RCT2RideTypeToOpenRCT2RideType(src->type, rideEntry); + dst->vehicle_colours[i].Body = src->vehicle_colours[i].body_colour; + dst->vehicle_colours[i].Trim = src->vehicle_colours[i].trim_colour; } - } - if (rideType >= RIDE_TYPE_COUNT) - { - log_error("Invalid ride type for a ride in this save."); - throw UnsupportedRideTypeException(rideType); - } - dst->type = rideType; - dst->subtype = subtype; - // pad_002; - dst->mode = static_cast(src->mode); - dst->colour_scheme_type = src->colour_scheme_type; + // pad_046; + dst->status = static_cast(src->status); - for (uint8_t i = 0; i < RCT2::Limits::MaxTrainsPerRide; i++) - { - dst->vehicle_colours[i].Body = src->vehicle_colours[i].body_colour; - dst->vehicle_colours[i].Trim = src->vehicle_colours[i].trim_colour; - } - - // pad_046; - dst->status = static_cast(src->status); - - dst->default_name_number = src->name_arguments_number; - if (is_user_string_id(src->name)) - { - dst->custom_name = GetUserString(src->name); - } - else - { dst->default_name_number = src->name_arguments_number; - } + if (is_user_string_id(src->name)) + { + dst->custom_name = GetUserString(src->name); + } + else + { + dst->default_name_number = src->name_arguments_number; + } - if (src->overall_view.IsNull()) - { - dst->overall_view.SetNull(); - } - else - { - auto tileLoc = TileCoordsXY(src->overall_view.x, src->overall_view.y); - dst->overall_view = tileLoc.ToCoordsXY(); - } + if (src->overall_view.IsNull()) + { + dst->overall_view.SetNull(); + } + else + { + auto tileLoc = TileCoordsXY(src->overall_view.x, src->overall_view.y); + dst->overall_view = tileLoc.ToCoordsXY(); + } - for (int32_t i = 0; i < RCT2::Limits::MaxStationsPerRide; i++) - { - if (src->station_starts[i].IsNull()) + for (int32_t i = 0; i < Limits::MaxStationsPerRide; i++) + { + if (src->station_starts[i].IsNull()) + { + dst->stations[i].Start.SetNull(); + } + else + { + auto tileStartLoc = TileCoordsXY(src->station_starts[i].x, src->station_starts[i].y); + dst->stations[i].Start = tileStartLoc.ToCoordsXY(); + } + dst->stations[i].Height = src->station_heights[i]; + dst->stations[i].Length = src->station_length[i]; + dst->stations[i].Depart = src->station_depart[i]; + dst->stations[i].TrainAtStation = src->train_at_station[i]; + // Direction is fixed later. + + if (src->entrances[i].IsNull()) + ride_clear_entrance_location(dst, i); + else + ride_set_entrance_location( + dst, i, { src->entrances[i].x, src->entrances[i].y, src->station_heights[i], 0 }); + + if (src->exits[i].IsNull()) + ride_clear_exit_location(dst, i); + else + ride_set_exit_location(dst, i, { src->exits[i].x, src->exits[i].y, src->station_heights[i], 0 }); + + dst->stations[i].LastPeepInQueue = src->last_peep_in_queue[i]; + + dst->stations[i].SegmentLength = src->length[i]; + dst->stations[i].SegmentTime = src->time[i]; + + dst->stations[i].QueueTime = src->queue_time[i]; + + dst->stations[i].QueueLength = src->queue_length[i]; + } + // All other values take 0 as their default. Since they're already memset to that, no need to do it again. + for (int32_t i = Limits::MaxStationsPerRide; i < MAX_STATIONS; i++) { dst->stations[i].Start.SetNull(); - } - else - { - auto tileStartLoc = TileCoordsXY(src->station_starts[i].x, src->station_starts[i].y); - dst->stations[i].Start = tileStartLoc.ToCoordsXY(); - } - dst->stations[i].Height = src->station_heights[i]; - dst->stations[i].Length = src->station_length[i]; - dst->stations[i].Depart = src->station_depart[i]; - dst->stations[i].TrainAtStation = src->train_at_station[i]; - // Direction is fixed later. - - if (src->entrances[i].IsNull()) + dst->stations[i].TrainAtStation = RideStation::NO_TRAIN; ride_clear_entrance_location(dst, i); - else - ride_set_entrance_location(dst, i, { src->entrances[i].x, src->entrances[i].y, src->station_heights[i], 0 }); - - if (src->exits[i].IsNull()) ride_clear_exit_location(dst, i); - else - ride_set_exit_location(dst, i, { src->exits[i].x, src->exits[i].y, src->station_heights[i], 0 }); - - dst->stations[i].LastPeepInQueue = src->last_peep_in_queue[i]; - - dst->stations[i].SegmentLength = src->length[i]; - dst->stations[i].SegmentTime = src->time[i]; - - dst->stations[i].QueueTime = src->queue_time[i]; - - dst->stations[i].QueueLength = src->queue_length[i]; - } - // All other values take 0 as their default. Since they're already memset to that, no need to do it again. - for (int32_t i = RCT2::Limits::MaxStationsPerRide; i < MAX_STATIONS; i++) - { - dst->stations[i].Start.SetNull(); - dst->stations[i].TrainAtStation = RideStation::NO_TRAIN; - ride_clear_entrance_location(dst, i); - ride_clear_exit_location(dst, i); - dst->stations[i].LastPeepInQueue = SPRITE_INDEX_NULL; - } - - for (int32_t i = 0; i < RCT2::Limits::MaxTrainsPerRide; i++) - { - dst->vehicles[i] = src->vehicles[i]; - } - for (int32_t i = RCT2::Limits::MaxTrainsPerRide - 1; i <= MAX_VEHICLES_PER_RIDE; i++) - { - dst->vehicles[i] = SPRITE_INDEX_NULL; - } - - dst->depart_flags = src->depart_flags; - - dst->num_stations = src->num_stations; - dst->num_vehicles = src->num_vehicles; - dst->num_cars_per_train = src->num_cars_per_train; - dst->proposed_num_vehicles = src->proposed_num_vehicles; - dst->proposed_num_cars_per_train = src->proposed_num_cars_per_train; - dst->max_trains = src->max_trains; - dst->MinCarsPerTrain = src->GetMinCarsPerTrain(); - dst->MaxCarsPerTrain = src->GetMaxCarsPerTrain(); - dst->min_waiting_time = src->min_waiting_time; - dst->max_waiting_time = src->max_waiting_time; - - // Includes time_limit, num_laps, launch_speed, speed, rotations - dst->operation_option = src->operation_option; - - dst->boat_hire_return_direction = src->boat_hire_return_direction; - dst->boat_hire_return_position = { src->boat_hire_return_position.x, src->boat_hire_return_position.y }; - - dst->special_track_elements = src->special_track_elements; - // pad_0D6[2]; - - dst->max_speed = src->max_speed; - dst->average_speed = src->average_speed; - dst->current_test_segment = src->current_test_segment; - dst->average_speed_test_timeout = src->average_speed_test_timeout; - // pad_0E2[0x2]; - - dst->max_positive_vertical_g = src->max_positive_vertical_g; - dst->max_negative_vertical_g = src->max_negative_vertical_g; - dst->max_lateral_g = src->max_lateral_g; - dst->previous_vertical_g = src->previous_vertical_g; - dst->previous_lateral_g = src->previous_lateral_g; - // pad_106[0x2]; - dst->testing_flags = src->testing_flags; - - if (src->cur_test_track_location.IsNull()) - { - dst->CurTestTrackLocation.SetNull(); - } - else - { - dst->CurTestTrackLocation = { src->cur_test_track_location.x, src->cur_test_track_location.y, - src->cur_test_track_z }; - } - - dst->turn_count_default = src->turn_count_default; - dst->turn_count_banked = src->turn_count_banked; - dst->turn_count_sloped = src->turn_count_sloped; - if (dst->type == RIDE_TYPE_MINI_GOLF) - dst->holes = src->inversions & 0x1F; - else - dst->inversions = src->inversions & 0x1F; - dst->sheltered_eighths = src->inversions >> 5; - dst->drops = src->drops; - dst->start_drop_height = src->start_drop_height; - dst->highest_drop_height = src->highest_drop_height; - dst->sheltered_length = src->sheltered_length; - dst->var_11C = src->var_11C; - dst->num_sheltered_sections = src->num_sheltered_sections; - - dst->cur_num_customers = src->cur_num_customers; - dst->num_customers_timeout = src->num_customers_timeout; - - for (uint8_t i = 0; i < RCT2::Limits::CustomerHistorySize; i++) - { - dst->num_customers[i] = src->num_customers[i]; - } - - dst->price[0] = src->price; - - for (uint8_t i = 0; i < 2; i++) - { - dst->ChairliftBullwheelLocation[i] = { src->chairlift_bullwheel_location[i].x, - src->chairlift_bullwheel_location[i].y, src->chairlift_bullwheel_z[i] }; - } - - dst->ratings = src->ratings; - dst->value = src->value; - - dst->chairlift_bullwheel_rotation = src->chairlift_bullwheel_rotation; - - dst->satisfaction = src->satisfaction; - dst->satisfaction_time_out = src->satisfaction_time_out; - dst->satisfaction_next = src->satisfaction_next; - - dst->window_invalidate_flags = src->window_invalidate_flags; - // pad_14E[0x02]; - - dst->total_customers = src->total_customers; - dst->total_profit = ToMoney64(src->total_profit); - dst->popularity = src->popularity; - dst->popularity_time_out = src->popularity_time_out; - dst->popularity_next = src->popularity_next; - - ImportNumRiders(dst, rideIndex); - - dst->music_tune_id = src->music_tune_id; - dst->slide_in_use = src->slide_in_use; - // Includes maze_tiles - dst->slide_peep = src->slide_peep; - // pad_160[0xE]; - dst->slide_peep_t_shirt_colour = src->slide_peep_t_shirt_colour; - // pad_16F[0x7]; - dst->spiral_slide_progress = src->spiral_slide_progress; - // pad_177[0x9]; - dst->build_date = static_cast(src->build_date); - dst->upkeep_cost = src->upkeep_cost; - dst->race_winner = src->race_winner; - // pad_186[0x02]; - dst->music_position = src->music_position; - - dst->breakdown_reason_pending = src->breakdown_reason_pending; - dst->mechanic_status = src->mechanic_status; - dst->mechanic = src->mechanic; - dst->inspection_station = src->inspection_station; - dst->broken_vehicle = src->broken_vehicle; - dst->broken_car = src->broken_car; - dst->breakdown_reason = src->breakdown_reason; - - dst->price[1] = src->price_secondary; - - dst->reliability = src->reliability; - dst->unreliability_factor = src->unreliability_factor; - dst->downtime = src->downtime; - dst->inspection_interval = src->inspection_interval; - dst->last_inspection = src->last_inspection; - - for (uint8_t i = 0; i < RCT2::Limits::DowntimeHistorySize; i++) - { - dst->downtime_history[i] = src->downtime_history[i]; - } - - dst->no_primary_items_sold = src->no_primary_items_sold; - dst->no_secondary_items_sold = src->no_secondary_items_sold; - - dst->breakdown_sound_modifier = src->breakdown_sound_modifier; - dst->not_fixed_timeout = src->not_fixed_timeout; - dst->last_crash_type = src->last_crash_type; - dst->connected_message_throttle = src->connected_message_throttle; - - dst->income_per_hour = ToMoney64(src->income_per_hour); - dst->profit = ToMoney64(src->profit); - - for (uint8_t i = 0; i < RCT2::Limits::NumColourSchemes; i++) - { - dst->track_colour[i].main = src->track_colour_main[i]; - dst->track_colour[i].additional = src->track_colour_additional[i]; - dst->track_colour[i].supports = src->track_colour_supports[i]; - } - // This stall was not colourable in RCT2. - if (dst->type == RIDE_TYPE_FOOD_STALL) - { - auto object = object_entry_get_object(ObjectType::Ride, dst->subtype); - if (object != nullptr && object->GetIdentifier() == "rct2.icecr1") - { - dst->track_colour[0].main = COLOUR_LIGHT_BLUE; + dst->stations[i].LastPeepInQueue = SPRITE_INDEX_NULL; } - } - auto musicStyle = OBJECT_ENTRY_INDEX_NULL; - if (GetRideTypeDescriptor(dst->type).HasFlag(RIDE_TYPE_FLAG_ALLOW_MUSIC)) - { - musicStyle = src->music; - } - dst->music = musicStyle; - - // In SV7, "plain" entrances are invisible. - auto entranceStyle = OBJECT_ENTRY_INDEX_NULL; - if (!_isSV7 && GetRideTypeDescriptor(dst->type).HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT)) - { - entranceStyle = src->entrance_style; - } - dst->entrance_style = entranceStyle; - - dst->vehicle_change_timeout = src->vehicle_change_timeout; - dst->num_block_brakes = src->num_block_brakes; - dst->lift_hill_speed = src->lift_hill_speed; - dst->guests_favourite = src->guests_favourite; - dst->lifecycle_flags = src->lifecycle_flags; - - for (uint8_t i = 0; i < RCT2::Limits::MaxTrainsPerRide; i++) - { - dst->vehicle_colours[i].Ternary = src->vehicle_colours_extended[i]; - } - - dst->total_air_time = src->total_air_time; - dst->current_test_station = src->current_test_station; - dst->num_circuits = src->num_circuits; - dst->CableLiftLoc = { src->cable_lift_x, src->cable_lift_y, src->cable_lift_z * COORDS_Z_STEP }; - // pad_1FD; - dst->cable_lift = src->cable_lift; - - // pad_208[0x58]; - } - - void ImportRideRatingsCalcData() - { - const auto& src = _s6.ride_ratings_calc_data; - auto& dst = gRideRatingUpdateState; - dst = {}; - dst.Proximity = { src.proximity_x, src.proximity_y, src.proximity_z }; - dst.ProximityStart = { src.proximity_start_x, src.proximity_start_y, src.proximity_start_z }; - dst.CurrentRide = RCT12RideIdToOpenRCT2RideId(src.current_ride); - dst.State = src.state; - if (src.current_ride < RCT2::Limits::MaxRidesInPark - && _s6.rides[src.current_ride].type < std::size(RideTypeDescriptors)) - dst.ProximityTrackType = RCT2TrackTypeToOpenRCT2( - src.proximity_track_type, _s6.rides[src.current_ride].type, IsFlatRide(src.current_ride)); - else - dst.ProximityTrackType = 0xFF; - dst.ProximityBaseHeight = src.proximity_base_height; - dst.ProximityTotal = src.proximity_total; - for (size_t i = 0; i < std::size(src.proximity_scores); i++) - { - dst.ProximityScores[i] = src.proximity_scores[i]; - } - dst.AmountOfBrakes = src.num_brakes; - dst.AmountOfReversers = src.num_reversers; - dst.StationFlags = src.station_flags; - } - - void ImportRideMeasurements() - { - for (const auto& src : _s6.ride_measurements) - { - if (src.ride_index != RCT12_RIDE_ID_NULL) + for (int32_t i = 0; i < Limits::MaxTrainsPerRide; i++) { - const auto rideId = static_cast(src.ride_index); - auto ride = get_ride(rideId); - if (ride != nullptr) + dst->vehicles[i] = src->vehicles[i]; + } + for (int32_t i = Limits::MaxTrainsPerRide - 1; i <= MAX_VEHICLES_PER_RIDE; i++) + { + dst->vehicles[i] = SPRITE_INDEX_NULL; + } + + dst->depart_flags = src->depart_flags; + + dst->num_stations = src->num_stations; + dst->num_vehicles = src->num_vehicles; + dst->num_cars_per_train = src->num_cars_per_train; + dst->proposed_num_vehicles = src->proposed_num_vehicles; + dst->proposed_num_cars_per_train = src->proposed_num_cars_per_train; + dst->max_trains = src->max_trains; + dst->MinCarsPerTrain = src->GetMinCarsPerTrain(); + dst->MaxCarsPerTrain = src->GetMaxCarsPerTrain(); + dst->min_waiting_time = src->min_waiting_time; + dst->max_waiting_time = src->max_waiting_time; + + // Includes time_limit, num_laps, launch_speed, speed, rotations + dst->operation_option = src->operation_option; + + dst->boat_hire_return_direction = src->boat_hire_return_direction; + dst->boat_hire_return_position = { src->boat_hire_return_position.x, src->boat_hire_return_position.y }; + + dst->special_track_elements = src->special_track_elements; + // pad_0D6[2]; + + dst->max_speed = src->max_speed; + dst->average_speed = src->average_speed; + dst->current_test_segment = src->current_test_segment; + dst->average_speed_test_timeout = src->average_speed_test_timeout; + // pad_0E2[0x2]; + + dst->max_positive_vertical_g = src->max_positive_vertical_g; + dst->max_negative_vertical_g = src->max_negative_vertical_g; + dst->max_lateral_g = src->max_lateral_g; + dst->previous_vertical_g = src->previous_vertical_g; + dst->previous_lateral_g = src->previous_lateral_g; + // pad_106[0x2]; + dst->testing_flags = src->testing_flags; + + if (src->cur_test_track_location.IsNull()) + { + dst->CurTestTrackLocation.SetNull(); + } + else + { + dst->CurTestTrackLocation = { src->cur_test_track_location.x, src->cur_test_track_location.y, + src->cur_test_track_z }; + } + + dst->turn_count_default = src->turn_count_default; + dst->turn_count_banked = src->turn_count_banked; + dst->turn_count_sloped = src->turn_count_sloped; + if (dst->type == RIDE_TYPE_MINI_GOLF) + dst->holes = src->inversions & 0x1F; + else + dst->inversions = src->inversions & 0x1F; + dst->sheltered_eighths = src->inversions >> 5; + dst->drops = src->drops; + dst->start_drop_height = src->start_drop_height; + dst->highest_drop_height = src->highest_drop_height; + dst->sheltered_length = src->sheltered_length; + dst->var_11C = src->var_11C; + dst->num_sheltered_sections = src->num_sheltered_sections; + + dst->cur_num_customers = src->cur_num_customers; + dst->num_customers_timeout = src->num_customers_timeout; + + for (uint8_t i = 0; i < Limits::CustomerHistorySize; i++) + { + dst->num_customers[i] = src->num_customers[i]; + } + + dst->price[0] = src->price; + + for (uint8_t i = 0; i < 2; i++) + { + dst->ChairliftBullwheelLocation[i] = { src->chairlift_bullwheel_location[i].x, + src->chairlift_bullwheel_location[i].y, src->chairlift_bullwheel_z[i] }; + } + + dst->ratings = src->ratings; + dst->value = src->value; + + dst->chairlift_bullwheel_rotation = src->chairlift_bullwheel_rotation; + + dst->satisfaction = src->satisfaction; + dst->satisfaction_time_out = src->satisfaction_time_out; + dst->satisfaction_next = src->satisfaction_next; + + dst->window_invalidate_flags = src->window_invalidate_flags; + // pad_14E[0x02]; + + dst->total_customers = src->total_customers; + dst->total_profit = ToMoney64(src->total_profit); + dst->popularity = src->popularity; + dst->popularity_time_out = src->popularity_time_out; + dst->popularity_next = src->popularity_next; + + ImportNumRiders(dst, rideIndex); + + dst->music_tune_id = src->music_tune_id; + dst->slide_in_use = src->slide_in_use; + // Includes maze_tiles + dst->slide_peep = src->slide_peep; + // pad_160[0xE]; + dst->slide_peep_t_shirt_colour = src->slide_peep_t_shirt_colour; + // pad_16F[0x7]; + dst->spiral_slide_progress = src->spiral_slide_progress; + // pad_177[0x9]; + dst->build_date = static_cast(src->build_date); + dst->upkeep_cost = src->upkeep_cost; + dst->race_winner = src->race_winner; + // pad_186[0x02]; + dst->music_position = src->music_position; + + dst->breakdown_reason_pending = src->breakdown_reason_pending; + dst->mechanic_status = src->mechanic_status; + dst->mechanic = src->mechanic; + dst->inspection_station = src->inspection_station; + dst->broken_vehicle = src->broken_vehicle; + dst->broken_car = src->broken_car; + dst->breakdown_reason = src->breakdown_reason; + + dst->price[1] = src->price_secondary; + + dst->reliability = src->reliability; + dst->unreliability_factor = src->unreliability_factor; + dst->downtime = src->downtime; + dst->inspection_interval = src->inspection_interval; + dst->last_inspection = src->last_inspection; + + for (uint8_t i = 0; i < Limits::DowntimeHistorySize; i++) + { + dst->downtime_history[i] = src->downtime_history[i]; + } + + dst->no_primary_items_sold = src->no_primary_items_sold; + dst->no_secondary_items_sold = src->no_secondary_items_sold; + + dst->breakdown_sound_modifier = src->breakdown_sound_modifier; + dst->not_fixed_timeout = src->not_fixed_timeout; + dst->last_crash_type = src->last_crash_type; + dst->connected_message_throttle = src->connected_message_throttle; + + dst->income_per_hour = ToMoney64(src->income_per_hour); + dst->profit = ToMoney64(src->profit); + + for (uint8_t i = 0; i < Limits::NumColourSchemes; i++) + { + dst->track_colour[i].main = src->track_colour_main[i]; + dst->track_colour[i].additional = src->track_colour_additional[i]; + dst->track_colour[i].supports = src->track_colour_supports[i]; + } + // This stall was not colourable in RCT2. + if (dst->type == RIDE_TYPE_FOOD_STALL) + { + auto object = object_entry_get_object(ObjectType::Ride, dst->subtype); + if (object != nullptr && object->GetIdentifier() == "rct2.icecr1") { - ride->measurement = std::make_unique(); - ImportRideMeasurement(*ride->measurement, src); + dst->track_colour[0].main = COLOUR_LIGHT_BLUE; } } - } - } - void ImportRideMeasurement(RideMeasurement& dst, const RCT12RideMeasurement& src) - { - dst.flags = src.flags; - dst.last_use_tick = src.last_use_tick; - dst.num_items = src.num_items; - dst.current_item = src.current_item; - dst.vehicle_index = src.vehicle_index; - dst.current_station = src.current_station; - for (size_t i = 0; i < std::size(src.velocity); i++) - { - dst.velocity[i] = src.velocity[i]; - dst.altitude[i] = src.altitude[i]; - dst.vertical[i] = src.vertical[i]; - dst.lateral[i] = src.lateral[i]; - } - } - - void ImportResearchList() - { - bool invented = true; - for (const auto& researchItem : _s6.research_items) - { - if (researchItem.IsInventedEndMarker()) + auto musicStyle = OBJECT_ENTRY_INDEX_NULL; + if (GetRideTypeDescriptor(dst->type).HasFlag(RIDE_TYPE_FLAG_ALLOW_MUSIC)) { - invented = false; - continue; + musicStyle = src->music; } - if (researchItem.IsUninventedEndMarker() || researchItem.IsRandomEndMarker()) + dst->music = musicStyle; + + // In SV7, "plain" entrances are invisible. + auto entranceStyle = OBJECT_ENTRY_INDEX_NULL; + if (!_isSV7 && GetRideTypeDescriptor(dst->type).HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT)) { - break; + entranceStyle = src->entrance_style; + } + dst->entrance_style = entranceStyle; + + dst->vehicle_change_timeout = src->vehicle_change_timeout; + dst->num_block_brakes = src->num_block_brakes; + dst->lift_hill_speed = src->lift_hill_speed; + dst->guests_favourite = src->guests_favourite; + dst->lifecycle_flags = src->lifecycle_flags; + + for (uint8_t i = 0; i < Limits::MaxTrainsPerRide; i++) + { + dst->vehicle_colours[i].Ternary = src->vehicle_colours_extended[i]; } - if (invented) - gResearchItemsInvented.emplace_back(researchItem); + dst->total_air_time = src->total_air_time; + dst->current_test_station = src->current_test_station; + dst->num_circuits = src->num_circuits; + dst->CableLiftLoc = { src->cable_lift_x, src->cable_lift_y, src->cable_lift_z * COORDS_Z_STEP }; + // pad_1FD; + dst->cable_lift = src->cable_lift; + + // pad_208[0x58]; + } + + void ImportRideRatingsCalcData() + { + const auto& src = _s6.ride_ratings_calc_data; + auto& dst = gRideRatingUpdateState; + dst = {}; + dst.Proximity = { src.proximity_x, src.proximity_y, src.proximity_z }; + dst.ProximityStart = { src.proximity_start_x, src.proximity_start_y, src.proximity_start_z }; + dst.CurrentRide = RCT12RideIdToOpenRCT2RideId(src.current_ride); + dst.State = src.state; + if (src.current_ride < Limits::MaxRidesInPark && _s6.rides[src.current_ride].type < std::size(RideTypeDescriptors)) + dst.ProximityTrackType = RCT2TrackTypeToOpenRCT2( + src.proximity_track_type, _s6.rides[src.current_ride].type, IsFlatRide(src.current_ride)); else - gResearchItemsUninvented.emplace_back(researchItem); - } - } - - void ImportBanner(Banner* dst, const RCT12Banner* src) - { - auto id = dst->id; - - *dst = {}; - dst->id = id; - dst->type = RCTEntryIndexToOpenRCT2EntryIndex(src->type); - dst->flags = src->flags; - - if (!(src->flags & BANNER_FLAG_LINKED_TO_RIDE) && is_user_string_id(src->string_idx)) - { - dst->text = GetUserString(src->string_idx); - } - - if (src->flags & BANNER_FLAG_LINKED_TO_RIDE) - { - dst->ride_index = RCT12RideIdToOpenRCT2RideId(src->ride_index); - } - else - { - dst->colour = src->colour; - } - - dst->text_colour = src->text_colour; - dst->position.x = src->x; - dst->position.y = src->y; - } - - void Initialise() - { - OpenRCT2::GetContext()->GetGameState()->InitAll(_s6.map_size); - } - - /** - * Imports guest entry points. - * Includes fixes for incorrectly set guest entry points in some scenarios. - */ - void ImportPeepSpawns() - { - // Many WW and TT have scenario_filename fields containing an incorrect filename. Check for both this filename - // and the corrected filename. - - // In this park, peep_spawns[0] is incorrect, and peep_spawns[1] is correct. - if (String::Equals(_s6.scenario_filename, "WW South America - Rio Carnival.SC6") - || String::Equals(_s6.scenario_filename, "South America - Rio Carnival.SC6")) - { - _s6.peep_spawns[0] = { 2160, 3167, 6, 1 }; - _s6.peep_spawns[1].x = RCT12_PEEP_SPAWN_UNDEFINED; - } - // In this park, peep_spawns[0] is correct. Just clear the other. - else if ( - String::Equals(_s6.scenario_filename, "Great Wall of China Tourism Enhancement.SC6") - || String::Equals(_s6.scenario_filename, "Asia - Great Wall of China Tourism Enhancement.SC6")) - { - _s6.peep_spawns[1].x = RCT12_PEEP_SPAWN_UNDEFINED; - } - // Amity Airfield has peeps entering from the corner of the tile, instead of the middle. - else if (String::Equals(_s6.scenario_filename, "Amity Airfield.SC6")) - { - _s6.peep_spawns[0].y = 1296; - } - // #9926: Africa - Oasis has peeps spawning on the edge underground near the entrance - else if (String::Equals(_s6.scenario_filename, "Africa - Oasis.SC6")) - { - _s6.peep_spawns[0].y = 2128; - _s6.peep_spawns[0].z = 7; - } - - gPeepSpawns.clear(); - for (size_t i = 0; i < RCT2::Limits::MaxPeepSpawns; i++) - { - if (_s6.peep_spawns[i].x != RCT12_PEEP_SPAWN_UNDEFINED) + dst.ProximityTrackType = 0xFF; + dst.ProximityBaseHeight = src.proximity_base_height; + dst.ProximityTotal = src.proximity_total; + for (size_t i = 0; i < std::size(src.proximity_scores); i++) { - PeepSpawn spawn = { _s6.peep_spawns[i].x, _s6.peep_spawns[i].y, _s6.peep_spawns[i].z * 16, - _s6.peep_spawns[i].direction }; - gPeepSpawns.push_back(spawn); + dst.ProximityScores[i] = src.proximity_scores[i]; } + dst.AmountOfBrakes = src.num_brakes; + dst.AmountOfReversers = src.num_reversers; + dst.StationFlags = src.station_flags; } - } - void ImportNumRiders(Ride* dst, const ride_id_t rideIndex) - { - // The number of riders might have overflown or underflown. Re-calculate the value. - uint16_t numRiders = 0; - for (const auto& sprite : _s6.sprites) + void ImportRideMeasurements() { - if (sprite.unknown.sprite_identifier == RCT12SpriteIdentifier::Peep) + for (const auto& src : _s6.ride_measurements) { - if (sprite.peep.current_ride == static_cast(rideIndex) - && (static_cast(sprite.peep.state) == PeepState::OnRide - || static_cast(sprite.peep.state) == PeepState::EnteringRide)) + if (src.ride_index != RCT12_RIDE_ID_NULL) { - numRiders++; - } - } - } - dst->num_riders = numRiders; - } - - void ImportTileElements() - { - // Build tile pointer cache (needed to get the first element at a certain location) - auto tilePointerIndex = TilePointerIndex( - RCT2::Limits::MaxMapSize, _s6.tile_elements, std::size(_s6.tile_elements)); - - std::vector tileElements; - bool nextElementInvisible = false; - bool restOfTileInvisible = false; - const auto maxSize = std::min(RCT2::Limits::MaxMapSize, _s6.map_size); - for (TileCoordsXY coords = { 0, 0 }; coords.y < MAXIMUM_MAP_SIZE_TECHNICAL; coords.y++) - { - for (coords.x = 0; coords.x < MAXIMUM_MAP_SIZE_TECHNICAL; coords.x++) - { - nextElementInvisible = false; - restOfTileInvisible = false; - - auto tileAdded = false; - if (coords.x < maxSize && coords.y < maxSize) - { - const auto* srcElement = tilePointerIndex.GetFirstElementAt(coords); - if (srcElement != nullptr) + const auto rideId = static_cast(src.ride_index); + auto ride = get_ride(rideId); + if (ride != nullptr) { - do - { - if (srcElement->base_height == RCT12::Limits::MaxElementHeight) - { - continue; - } - - auto tileElementType = static_cast(srcElement->GetType()); - if (tileElementType == RCT12TileElementType::Corrupt) - { - // One property of corrupt elements was to hide tops of tower tracks, and to avoid the next - // element from being hidden, multiple consecutive corrupt elements were sometimes used. This - // would essentially toggle the flag, so we inverse nextElementInvisible here instead of always - // setting it to true. - nextElementInvisible = !nextElementInvisible; - continue; - } - if (tileElementType == RCT12TileElementType::EightCarsCorrupt14 - || tileElementType == RCT12TileElementType::EightCarsCorrupt15) - { - restOfTileInvisible = true; - continue; - } - - auto& dstElement = tileElements.emplace_back(); - ImportTileElement(&dstElement, srcElement, nextElementInvisible || restOfTileInvisible); - nextElementInvisible = false; - tileAdded = true; - } while (!(srcElement++)->IsLastForTile()); + ride->measurement = std::make_unique(); + ImportRideMeasurement(*ride->measurement, src); } } + } + } - if (!tileAdded) + void ImportRideMeasurement(RideMeasurement& dst, const RCT12RideMeasurement& src) + { + dst.flags = src.flags; + dst.last_use_tick = src.last_use_tick; + dst.num_items = src.num_items; + dst.current_item = src.current_item; + dst.vehicle_index = src.vehicle_index; + dst.current_station = src.current_station; + for (size_t i = 0; i < std::size(src.velocity); i++) + { + dst.velocity[i] = src.velocity[i]; + dst.altitude[i] = src.altitude[i]; + dst.vertical[i] = src.vertical[i]; + dst.lateral[i] = src.lateral[i]; + } + } + + void ImportResearchList() + { + bool invented = true; + for (const auto& researchItem : _s6.research_items) + { + if (researchItem.IsInventedEndMarker()) { - // Add a default surface element, we always need at least one element per tile - auto& dstElement = tileElements.emplace_back(); - dstElement.ClearAs(TILE_ELEMENT_TYPE_SURFACE); - dstElement.SetLastForTile(true); + invented = false; + continue; + } + if (researchItem.IsUninventedEndMarker() || researchItem.IsRandomEndMarker()) + { + break; } - // Set last element flag in case the original last element was never added - if (tileElements.size() > 0) + if (invented) + gResearchItemsInvented.emplace_back(researchItem); + else + gResearchItemsUninvented.emplace_back(researchItem); + } + } + + void ImportBanner(Banner* dst, const RCT12Banner* src) + { + auto id = dst->id; + + *dst = {}; + dst->id = id; + dst->type = RCTEntryIndexToOpenRCT2EntryIndex(src->type); + dst->flags = src->flags; + + if (!(src->flags & BANNER_FLAG_LINKED_TO_RIDE) && is_user_string_id(src->string_idx)) + { + dst->text = GetUserString(src->string_idx); + } + + if (src->flags & BANNER_FLAG_LINKED_TO_RIDE) + { + dst->ride_index = RCT12RideIdToOpenRCT2RideId(src->ride_index); + } + else + { + dst->colour = src->colour; + } + + dst->text_colour = src->text_colour; + dst->position.x = src->x; + dst->position.y = src->y; + } + + void Initialise() + { + OpenRCT2::GetContext()->GetGameState()->InitAll(_s6.map_size); + } + + /** + * Imports guest entry points. + * Includes fixes for incorrectly set guest entry points in some scenarios. + */ + void ImportPeepSpawns() + { + // Many WW and TT have scenario_filename fields containing an incorrect filename. Check for both this filename + // and the corrected filename. + + // In this park, peep_spawns[0] is incorrect, and peep_spawns[1] is correct. + if (String::Equals(_s6.scenario_filename, "WW South America - Rio Carnival.SC6") + || String::Equals(_s6.scenario_filename, "South America - Rio Carnival.SC6")) + { + _s6.peep_spawns[0] = { 2160, 3167, 6, 1 }; + _s6.peep_spawns[1].x = RCT12_PEEP_SPAWN_UNDEFINED; + } + // In this park, peep_spawns[0] is correct. Just clear the other. + else if ( + String::Equals(_s6.scenario_filename, "Great Wall of China Tourism Enhancement.SC6") + || String::Equals(_s6.scenario_filename, "Asia - Great Wall of China Tourism Enhancement.SC6")) + { + _s6.peep_spawns[1].x = RCT12_PEEP_SPAWN_UNDEFINED; + } + // Amity Airfield has peeps entering from the corner of the tile, instead of the middle. + else if (String::Equals(_s6.scenario_filename, "Amity Airfield.SC6")) + { + _s6.peep_spawns[0].y = 1296; + } + // #9926: Africa - Oasis has peeps spawning on the edge underground near the entrance + else if (String::Equals(_s6.scenario_filename, "Africa - Oasis.SC6")) + { + _s6.peep_spawns[0].y = 2128; + _s6.peep_spawns[0].z = 7; + } + + gPeepSpawns.clear(); + for (size_t i = 0; i < Limits::MaxPeepSpawns; i++) + { + if (_s6.peep_spawns[i].x != RCT12_PEEP_SPAWN_UNDEFINED) { - tileElements.back().SetLastForTile(true); + PeepSpawn spawn = { _s6.peep_spawns[i].x, _s6.peep_spawns[i].y, _s6.peep_spawns[i].z * 16, + _s6.peep_spawns[i].direction }; + gPeepSpawns.push_back(spawn); } } } - SetTileElements(std::move(tileElements)); - } - void ImportTileElement(TileElement* dst, const RCT12TileElement* src, bool invisible) - { - // Todo: allow for changing definition of OpenRCT2 tile element types - replace with a map - uint8_t tileElementType = src->GetType(); - dst->ClearAs(tileElementType); - dst->SetDirection(src->GetDirection()); - dst->SetBaseZ(src->base_height * COORDS_Z_STEP); - dst->SetClearanceZ(src->clearance_height * COORDS_Z_STEP); - - // All saved in "flags" - dst->SetOccupiedQuadrants(src->GetOccupiedQuadrants()); - dst->SetGhost(src->IsGhost()); - dst->SetLastForTile(src->IsLastForTile()); - dst->SetInvisible(invisible); - - switch (tileElementType) + void ImportNumRiders(::Ride* dst, const ride_id_t rideIndex) { - case TILE_ELEMENT_TYPE_SURFACE: + // The number of riders might have overflown or underflown. Re-calculate the value. + uint16_t numRiders = 0; + for (const auto& sprite : _s6.sprites) { - auto dst2 = dst->AsSurface(); - auto src2 = src->AsSurface(); - - dst2->SetSlope(src2->GetSlope()); - dst2->SetSurfaceStyle(src2->GetSurfaceStyle()); - dst2->SetEdgeStyle(src2->GetEdgeStyle()); - dst2->SetGrassLength(src2->GetGrassLength()); - dst2->SetOwnership(src2->GetOwnership()); - dst2->SetParkFences(src2->GetParkFences()); - dst2->SetWaterHeight(src2->GetWaterHeight()); - dst2->SetHasTrackThatNeedsWater(src2->HasTrackThatNeedsWater()); - - break; + if (sprite.unknown.sprite_identifier == RCT12SpriteIdentifier::Peep) + { + if (sprite.peep.current_ride == static_cast(rideIndex) + && (static_cast(sprite.peep.state) == PeepState::OnRide + || static_cast(sprite.peep.state) == PeepState::EnteringRide)) + { + numRiders++; + } + } } - case TILE_ELEMENT_TYPE_PATH: + dst->num_riders = numRiders; + } + + void ImportTileElements() + { + // Build tile pointer cache (needed to get the first element at a certain location) + auto tilePointerIndex = TilePointerIndex( + Limits::MaxMapSize, _s6.tile_elements, std::size(_s6.tile_elements)); + + std::vector tileElements; + bool nextElementInvisible = false; + bool restOfTileInvisible = false; + const auto maxSize = std::min(Limits::MaxMapSize, _s6.map_size); + for (TileCoordsXY coords = { 0, 0 }; coords.y < MAXIMUM_MAP_SIZE_TECHNICAL; coords.y++) { - auto dst2 = dst->AsPath(); - auto src2 = src->AsPath(); - - auto pathEntryIndex = src2->GetEntryIndex(); - auto surfaceEntry = src2->IsQueue() ? _pathToQueueSurfaceMap[pathEntryIndex] - : _pathToSurfaceMap[pathEntryIndex]; - if (surfaceEntry == OBJECT_ENTRY_INDEX_NULL) + for (coords.x = 0; coords.x < MAXIMUM_MAP_SIZE_TECHNICAL; coords.x++) { - // Legacy footpath object - dst2->SetLegacyPathEntryIndex(pathEntryIndex); - } - else - { - // Surface / railing - dst2->SetSurfaceEntryIndex(surfaceEntry); - dst2->SetRailingsEntryIndex(_pathToRailingMap[pathEntryIndex]); - } + nextElementInvisible = false; + restOfTileInvisible = false; - dst2->SetQueueBannerDirection(src2->GetQueueBannerDirection()); - dst2->SetSloped(src2->IsSloped()); - dst2->SetSlopeDirection(src2->GetSlopeDirection()); - dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex())); - dst2->SetStationIndex(src2->GetStationIndex()); - dst2->SetWide(src2->IsWide()); - dst2->SetIsQueue(src2->IsQueue()); - dst2->SetHasQueueBanner(src2->HasQueueBanner()); - dst2->SetEdges(src2->GetEdges()); - dst2->SetCorners(src2->GetCorners()); - dst2->SetAddition(src2->GetAddition()); - dst2->SetAdditionIsGhost(src2->AdditionIsGhost()); - dst2->SetAdditionStatus(src2->GetAdditionStatus()); - dst2->SetIsBroken(src2->IsBroken()); - dst2->SetIsBlockedByVehicle(src2->IsBlockedByVehicle()); + auto tileAdded = false; + if (coords.x < maxSize && coords.y < maxSize) + { + const auto* srcElement = tilePointerIndex.GetFirstElementAt(coords); + if (srcElement != nullptr) + { + do + { + if (srcElement->base_height == RCT12::Limits::MaxElementHeight) + { + continue; + } - break; + auto tileElementType = static_cast(srcElement->GetType()); + if (tileElementType == RCT12TileElementType::Corrupt) + { + // One property of corrupt elements was to hide tops of tower tracks, and to avoid the next + // element from being hidden, multiple consecutive corrupt elements were sometimes used. + // This would essentially toggle the flag, so we inverse nextElementInvisible here instead + // of always setting it to true. + nextElementInvisible = !nextElementInvisible; + continue; + } + if (tileElementType == RCT12TileElementType::EightCarsCorrupt14 + || tileElementType == RCT12TileElementType::EightCarsCorrupt15) + { + restOfTileInvisible = true; + continue; + } + + auto& dstElement = tileElements.emplace_back(); + ImportTileElement(&dstElement, srcElement, nextElementInvisible || restOfTileInvisible); + nextElementInvisible = false; + tileAdded = true; + } while (!(srcElement++)->IsLastForTile()); + } + } + + if (!tileAdded) + { + // Add a default surface element, we always need at least one element per tile + auto& dstElement = tileElements.emplace_back(); + dstElement.ClearAs(TILE_ELEMENT_TYPE_SURFACE); + dstElement.SetLastForTile(true); + } + + // Set last element flag in case the original last element was never added + if (tileElements.size() > 0) + { + tileElements.back().SetLastForTile(true); + } + } } - case TILE_ELEMENT_TYPE_TRACK: + SetTileElements(std::move(tileElements)); + } + + void ImportTileElement(TileElement* dst, const RCT12TileElement* src, bool invisible) + { + // Todo: allow for changing definition of OpenRCT2 tile element types - replace with a map + uint8_t tileElementType = src->GetType(); + dst->ClearAs(tileElementType); + dst->SetDirection(src->GetDirection()); + dst->SetBaseZ(src->base_height * COORDS_Z_STEP); + dst->SetClearanceZ(src->clearance_height * COORDS_Z_STEP); + + // All saved in "flags" + dst->SetOccupiedQuadrants(src->GetOccupiedQuadrants()); + dst->SetGhost(src->IsGhost()); + dst->SetLastForTile(src->IsLastForTile()); + dst->SetInvisible(invisible); + + switch (tileElementType) { - auto dst2 = dst->AsTrack(); - auto src2 = src->AsTrack(); - - auto rideType = _s6.rides[src2->GetRideIndex()].type; - track_type_t trackType = static_cast(src2->GetTrackType()); - - dst2->SetTrackType(RCT2TrackTypeToOpenRCT2(trackType, rideType, IsFlatRide(src2->GetRideIndex()))); - dst2->SetRideType(rideType); - dst2->SetSequenceIndex(src2->GetSequenceIndex()); - dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex())); - dst2->SetColourScheme(src2->GetColourScheme()); - dst2->SetHasChain(src2->HasChain()); - dst2->SetHasCableLift(src2->HasCableLift()); - dst2->SetInverted(src2->IsInverted()); - dst2->SetStationIndex(src2->GetStationIndex()); - dst2->SetHasGreenLight(src2->HasGreenLight()); - dst2->SetBlockBrakeClosed(src2->BlockBrakeClosed()); - dst2->SetIsIndestructible(src2->IsIndestructible()); - // Skipping IsHighlighted() - - if (TrackTypeHasSpeedSetting(trackType)) + case TILE_ELEMENT_TYPE_SURFACE: { - dst2->SetBrakeBoosterSpeed(src2->GetBrakeBoosterSpeed()); + auto dst2 = dst->AsSurface(); + auto src2 = src->AsSurface(); + + dst2->SetSlope(src2->GetSlope()); + dst2->SetSurfaceStyle(src2->GetSurfaceStyle()); + dst2->SetEdgeStyle(src2->GetEdgeStyle()); + dst2->SetGrassLength(src2->GetGrassLength()); + dst2->SetOwnership(src2->GetOwnership()); + dst2->SetParkFences(src2->GetParkFences()); + dst2->SetWaterHeight(src2->GetWaterHeight()); + dst2->SetHasTrackThatNeedsWater(src2->HasTrackThatNeedsWater()); + + break; } - else if (trackType == TrackElemType::OnRidePhoto) + case TILE_ELEMENT_TYPE_PATH: { - dst2->SetPhotoTimeout(src2->GetPhotoTimeout()); - } + auto dst2 = dst->AsPath(); + auto src2 = src->AsPath(); - // This has to be done last, since the maze entry shares fields with the colour and sequence fields. - if (rideType == RIDE_TYPE_MAZE) - { - dst2->SetMazeEntry(src2->GetMazeEntry()); - } - else if (rideType == RIDE_TYPE_GHOST_TRAIN) - { - dst2->SetDoorAState(src2->GetDoorAState()); - dst2->SetDoorBState(src2->GetDoorBState()); - } - else - { - dst2->SetSeatRotation(src2->GetSeatRotation()); - } - - break; - } - case TILE_ELEMENT_TYPE_SMALL_SCENERY: - { - auto dst2 = dst->AsSmallScenery(); - auto src2 = src->AsSmallScenery(); - - dst2->SetEntryIndex(src2->GetEntryIndex()); - dst2->SetAge(src2->GetAge()); - dst2->SetSceneryQuadrant(src2->GetSceneryQuadrant()); - dst2->SetPrimaryColour(src2->GetPrimaryColour()); - dst2->SetSecondaryColour(src2->GetSecondaryColour()); - if (src2->NeedsSupports()) - dst2->SetNeedsSupports(); - - break; - } - case TILE_ELEMENT_TYPE_ENTRANCE: - { - auto dst2 = dst->AsEntrance(); - auto src2 = src->AsEntrance(); - - dst2->SetEntranceType(src2->GetEntranceType()); - dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex())); - dst2->SetStationIndex(src2->GetStationIndex()); - dst2->SetSequenceIndex(src2->GetSequenceIndex()); - - if (src2->GetSequenceIndex() == 0) - { - auto pathEntryIndex = src2->GetPathType(); - auto surfaceEntry = _pathToSurfaceMap[pathEntryIndex]; + auto pathEntryIndex = src2->GetEntryIndex(); + auto surfaceEntry = src2->IsQueue() ? _pathToQueueSurfaceMap[pathEntryIndex] + : _pathToSurfaceMap[pathEntryIndex]; if (surfaceEntry == OBJECT_ENTRY_INDEX_NULL) { // Legacy footpath object @@ -1311,791 +1209,903 @@ public: } else { - // Surface + // Surface / railing dst2->SetSurfaceEntryIndex(surfaceEntry); + dst2->SetRailingsEntryIndex(_pathToRailingMap[pathEntryIndex]); } - } - else - { - dst2->SetSurfaceEntryIndex(OBJECT_ENTRY_INDEX_NULL); - } - break; - } - case TILE_ELEMENT_TYPE_WALL: - { - auto dst2 = dst->AsWall(); - auto src2 = src->AsWall(); - dst2->SetEntryIndex(src2->GetEntryIndex()); - dst2->SetSlope(src2->GetSlope()); - dst2->SetPrimaryColour(src2->GetPrimaryColour()); - dst2->SetSecondaryColour(src2->GetSecondaryColour()); - dst2->SetTertiaryColour(src2->GetTertiaryColour()); - dst2->SetAnimationFrame(src2->GetAnimationFrame()); - dst2->SetAcrossTrack(src2->IsAcrossTrack()); - dst2->SetAnimationIsBackwards(src2->AnimationIsBackwards()); + dst2->SetQueueBannerDirection(src2->GetQueueBannerDirection()); + dst2->SetSloped(src2->IsSloped()); + dst2->SetSlopeDirection(src2->GetSlopeDirection()); + dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex())); + dst2->SetStationIndex(src2->GetStationIndex()); + dst2->SetWide(src2->IsWide()); + dst2->SetIsQueue(src2->IsQueue()); + dst2->SetHasQueueBanner(src2->HasQueueBanner()); + dst2->SetEdges(src2->GetEdges()); + dst2->SetCorners(src2->GetCorners()); + dst2->SetAddition(src2->GetAddition()); + dst2->SetAdditionIsGhost(src2->AdditionIsGhost()); + dst2->SetAdditionStatus(src2->GetAdditionStatus()); + dst2->SetIsBroken(src2->IsBroken()); + dst2->SetIsBlockedByVehicle(src2->IsBlockedByVehicle()); - // Import banner information - dst2->SetBannerIndex(BANNER_INDEX_NULL); - auto entry = dst2->GetEntry(); - if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE) + break; + } + case TILE_ELEMENT_TYPE_TRACK: { - auto bannerIndex = src2->GetBannerIndex(); - if (bannerIndex < std::size(_s6.banners)) + auto dst2 = dst->AsTrack(); + auto src2 = src->AsTrack(); + + auto rideType = _s6.rides[src2->GetRideIndex()].type; + track_type_t trackType = static_cast(src2->GetTrackType()); + + dst2->SetTrackType(RCT2TrackTypeToOpenRCT2(trackType, rideType, IsFlatRide(src2->GetRideIndex()))); + dst2->SetRideType(rideType); + dst2->SetSequenceIndex(src2->GetSequenceIndex()); + dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex())); + dst2->SetColourScheme(src2->GetColourScheme()); + dst2->SetHasChain(src2->HasChain()); + dst2->SetHasCableLift(src2->HasCableLift()); + dst2->SetInverted(src2->IsInverted()); + dst2->SetStationIndex(src2->GetStationIndex()); + dst2->SetHasGreenLight(src2->HasGreenLight()); + dst2->SetBlockBrakeClosed(src2->BlockBrakeClosed()); + dst2->SetIsIndestructible(src2->IsIndestructible()); + // Skipping IsHighlighted() + + if (TrackTypeHasSpeedSetting(trackType)) { - auto srcBanner = &_s6.banners[bannerIndex]; - auto dstBanner = GetOrCreateBanner(bannerIndex); - if (dstBanner == nullptr) - { - dst2->SetBannerIndex(BANNER_INDEX_NULL); - } - else - { - ImportBanner(dstBanner, srcBanner); - dst2->SetBannerIndex(src2->GetBannerIndex()); - } + dst2->SetBrakeBoosterSpeed(src2->GetBrakeBoosterSpeed()); } - } - break; - } - case TILE_ELEMENT_TYPE_LARGE_SCENERY: - { - auto dst2 = dst->AsLargeScenery(); - auto src2 = src->AsLargeScenery(); - - dst2->SetEntryIndex(src2->GetEntryIndex()); - dst2->SetSequenceIndex(src2->GetSequenceIndex()); - dst2->SetPrimaryColour(src2->GetPrimaryColour()); - dst2->SetSecondaryColour(src2->GetSecondaryColour()); - - // Import banner information - dst2->SetBannerIndex(BANNER_INDEX_NULL); - auto entry = dst2->GetEntry(); - if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE) - { - auto bannerIndex = src2->GetBannerIndex(); - if (bannerIndex < std::size(_s6.banners)) + else if (trackType == TrackElemType::OnRidePhoto) { - auto srcBanner = &_s6.banners[bannerIndex]; - auto dstBanner = GetOrCreateBanner(bannerIndex); - if (dstBanner == nullptr) - { - dst2->SetBannerIndex(BANNER_INDEX_NULL); - } - else - { - ImportBanner(dstBanner, srcBanner); - dst2->SetBannerIndex(src2->GetBannerIndex()); - } + dst2->SetPhotoTimeout(src2->GetPhotoTimeout()); } - } - break; - } - case TILE_ELEMENT_TYPE_BANNER: - { - auto dst2 = dst->AsBanner(); - auto src2 = src->AsBanner(); - dst2->SetPosition(src2->GetPosition()); - dst2->SetAllowedEdges(src2->GetAllowedEdges()); - - auto bannerIndex = src2->GetIndex(); - if (bannerIndex < std::size(_s6.banners)) - { - auto srcBanner = &_s6.banners[bannerIndex]; - auto dstBanner = GetOrCreateBanner(bannerIndex); - if (dstBanner == nullptr) + // This has to be done last, since the maze entry shares fields with the colour and sequence fields. + if (rideType == RIDE_TYPE_MAZE) { - dst2->SetIndex(BANNER_INDEX_NULL); + dst2->SetMazeEntry(src2->GetMazeEntry()); + } + else if (rideType == RIDE_TYPE_GHOST_TRAIN) + { + dst2->SetDoorAState(src2->GetDoorAState()); + dst2->SetDoorBState(src2->GetDoorBState()); } else { - ImportBanner(dstBanner, srcBanner); - dst2->SetIndex(bannerIndex); + dst2->SetSeatRotation(src2->GetSeatRotation()); } - } - else - { - dst2->SetIndex(BANNER_INDEX_NULL); - } - break; - } - default: - assert(false); - } - } - void ImportMarketingCampaigns() - { - for (size_t i = 0; i < ADVERTISING_CAMPAIGN_COUNT; i++) - { - if (_s6.campaign_weeks_left[i] & CAMPAIGN_ACTIVE_FLAG) - { - MarketingCampaign campaign{}; - campaign.Type = static_cast(i); - campaign.WeeksLeft = _s6.campaign_weeks_left[i] & ~(CAMPAIGN_ACTIVE_FLAG | CAMPAIGN_FIRST_WEEK_FLAG); - if ((_s6.campaign_weeks_left[i] & CAMPAIGN_FIRST_WEEK_FLAG) != 0) - { - campaign.Flags |= MarketingCampaignFlags::FIRST_WEEK; + break; } - if (campaign.Type == ADVERTISING_CAMPAIGN_RIDE_FREE || campaign.Type == ADVERTISING_CAMPAIGN_RIDE) + case TILE_ELEMENT_TYPE_SMALL_SCENERY: { - campaign.RideId = RCT12RideIdToOpenRCT2RideId(_s6.campaign_ride_index[i]); + auto dst2 = dst->AsSmallScenery(); + auto src2 = src->AsSmallScenery(); + + dst2->SetEntryIndex(src2->GetEntryIndex()); + dst2->SetAge(src2->GetAge()); + dst2->SetSceneryQuadrant(src2->GetSceneryQuadrant()); + dst2->SetPrimaryColour(src2->GetPrimaryColour()); + dst2->SetSecondaryColour(src2->GetSecondaryColour()); + if (src2->NeedsSupports()) + dst2->SetNeedsSupports(); + + break; } - else if (campaign.Type == ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE) + case TILE_ELEMENT_TYPE_ENTRANCE: { - campaign.ShopItemType = ShopItem(_s6.campaign_ride_index[i]); + auto dst2 = dst->AsEntrance(); + auto src2 = src->AsEntrance(); + + dst2->SetEntranceType(src2->GetEntranceType()); + dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex())); + dst2->SetStationIndex(src2->GetStationIndex()); + dst2->SetSequenceIndex(src2->GetSequenceIndex()); + + if (src2->GetSequenceIndex() == 0) + { + auto pathEntryIndex = src2->GetPathType(); + auto surfaceEntry = _pathToSurfaceMap[pathEntryIndex]; + if (surfaceEntry == OBJECT_ENTRY_INDEX_NULL) + { + // Legacy footpath object + dst2->SetLegacyPathEntryIndex(pathEntryIndex); + } + else + { + // Surface + dst2->SetSurfaceEntryIndex(surfaceEntry); + } + } + else + { + dst2->SetSurfaceEntryIndex(OBJECT_ENTRY_INDEX_NULL); + } + break; } - gMarketingCampaigns.push_back(campaign); + case TILE_ELEMENT_TYPE_WALL: + { + auto dst2 = dst->AsWall(); + auto src2 = src->AsWall(); + + dst2->SetEntryIndex(src2->GetEntryIndex()); + dst2->SetSlope(src2->GetSlope()); + dst2->SetPrimaryColour(src2->GetPrimaryColour()); + dst2->SetSecondaryColour(src2->GetSecondaryColour()); + dst2->SetTertiaryColour(src2->GetTertiaryColour()); + dst2->SetAnimationFrame(src2->GetAnimationFrame()); + dst2->SetAcrossTrack(src2->IsAcrossTrack()); + dst2->SetAnimationIsBackwards(src2->AnimationIsBackwards()); + + // Import banner information + dst2->SetBannerIndex(BANNER_INDEX_NULL); + auto entry = dst2->GetEntry(); + if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE) + { + auto bannerIndex = src2->GetBannerIndex(); + if (bannerIndex < std::size(_s6.banners)) + { + auto srcBanner = &_s6.banners[bannerIndex]; + auto dstBanner = GetOrCreateBanner(bannerIndex); + if (dstBanner == nullptr) + { + dst2->SetBannerIndex(BANNER_INDEX_NULL); + } + else + { + ImportBanner(dstBanner, srcBanner); + dst2->SetBannerIndex(src2->GetBannerIndex()); + } + } + } + break; + } + case TILE_ELEMENT_TYPE_LARGE_SCENERY: + { + auto dst2 = dst->AsLargeScenery(); + auto src2 = src->AsLargeScenery(); + + dst2->SetEntryIndex(src2->GetEntryIndex()); + dst2->SetSequenceIndex(src2->GetSequenceIndex()); + dst2->SetPrimaryColour(src2->GetPrimaryColour()); + dst2->SetSecondaryColour(src2->GetSecondaryColour()); + + // Import banner information + dst2->SetBannerIndex(BANNER_INDEX_NULL); + auto entry = dst2->GetEntry(); + if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE) + { + auto bannerIndex = src2->GetBannerIndex(); + if (bannerIndex < std::size(_s6.banners)) + { + auto srcBanner = &_s6.banners[bannerIndex]; + auto dstBanner = GetOrCreateBanner(bannerIndex); + if (dstBanner == nullptr) + { + dst2->SetBannerIndex(BANNER_INDEX_NULL); + } + else + { + ImportBanner(dstBanner, srcBanner); + dst2->SetBannerIndex(src2->GetBannerIndex()); + } + } + } + break; + } + case TILE_ELEMENT_TYPE_BANNER: + { + auto dst2 = dst->AsBanner(); + auto src2 = src->AsBanner(); + + dst2->SetPosition(src2->GetPosition()); + dst2->SetAllowedEdges(src2->GetAllowedEdges()); + + auto bannerIndex = src2->GetIndex(); + if (bannerIndex < std::size(_s6.banners)) + { + auto srcBanner = &_s6.banners[bannerIndex]; + auto dstBanner = GetOrCreateBanner(bannerIndex); + if (dstBanner == nullptr) + { + dst2->SetIndex(BANNER_INDEX_NULL); + } + else + { + ImportBanner(dstBanner, srcBanner); + dst2->SetIndex(bannerIndex); + } + } + else + { + dst2->SetIndex(BANNER_INDEX_NULL); + } + break; + } + default: + assert(false); } } - } - void ImportStaffPatrolArea(Staff* staffmember, uint8_t staffId) - { - // First check staff mode as vanilla did not clean up patrol areas when switching from patrol to walk - // without doing this we could accidentally add a patrol when it didn't exist. - if (_s6.staff_modes[staffId] != RCT2StaffMode::Patrol) + void ImportMarketingCampaigns() { - return; - } - int32_t peepOffset = staffId * RCT2::Limits::PatrolAreaSize; - for (int32_t i = 0; i < RCT2::Limits::PatrolAreaSize; i++) - { - if (_s6.patrol_areas[peepOffset + i] == 0) + for (size_t i = 0; i < ADVERTISING_CAMPAIGN_COUNT; i++) { - // No patrol for this area - continue; + if (_s6.campaign_weeks_left[i] & CAMPAIGN_ACTIVE_FLAG) + { + MarketingCampaign campaign{}; + campaign.Type = static_cast(i); + campaign.WeeksLeft = _s6.campaign_weeks_left[i] & ~(CAMPAIGN_ACTIVE_FLAG | CAMPAIGN_FIRST_WEEK_FLAG); + if ((_s6.campaign_weeks_left[i] & CAMPAIGN_FIRST_WEEK_FLAG) != 0) + { + campaign.Flags |= MarketingCampaignFlags::FIRST_WEEK; + } + if (campaign.Type == ADVERTISING_CAMPAIGN_RIDE_FREE || campaign.Type == ADVERTISING_CAMPAIGN_RIDE) + { + campaign.RideId = RCT12RideIdToOpenRCT2RideId(_s6.campaign_ride_index[i]); + } + else if (campaign.Type == ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE) + { + campaign.ShopItemType = ShopItem(_s6.campaign_ride_index[i]); + } + gMarketingCampaigns.push_back(campaign); + } } + } - // Loop over the bits of the uint32_t - for (int32_t j = 0; j < 32; j++) + void ImportStaffPatrolArea(Staff* staffmember, uint8_t staffId) + { + // First check staff mode as vanilla did not clean up patrol areas when switching from patrol to walk + // without doing this we could accidentally add a patrol when it didn't exist. + if (_s6.staff_modes[staffId] != StaffMode::Patrol) { - int8_t bit = (_s6.patrol_areas[peepOffset + i] >> j) & 1; - if (bit == 0) + return; + } + int32_t peepOffset = staffId * Limits::PatrolAreaSize; + for (int32_t i = 0; i < Limits::PatrolAreaSize; i++) + { + if (_s6.patrol_areas[peepOffset + i] == 0) { // No patrol for this area continue; } - // val contains the 6 highest bits of both the x and y coordinates - int32_t val = j | (i << 5); - int32_t x = val & 0x03F; - x <<= 7; - int32_t y = val & 0xFC0; - y <<= 1; - staffmember->SetPatrolArea({ x, y }, true); + + // Loop over the bits of the uint32_t + for (int32_t j = 0; j < 32; j++) + { + int8_t bit = (_s6.patrol_areas[peepOffset + i] >> j) & 1; + if (bit == 0) + { + // No patrol for this area + continue; + } + // val contains the 6 highest bits of both the x and y coordinates + int32_t val = j | (i << 5); + int32_t x = val & 0x03F; + x <<= 7; + int32_t y = val & 0xFC0; + y <<= 1; + staffmember->SetPatrolArea({ x, y }, true); + } } } - } - void ImportEntities() - { - for (int32_t i = 0; i < RCT2::Limits::MaxEntities; i++) + void ImportEntities() { - ImportEntity(_s6.sprites[i].unknown); - } - } - - template void ImportEntity(const RCT12SpriteBase& src); - - void ImportEntityPeep(Peep* dst, const RCT2SpritePeep* src) - { - const auto isNullLocation = [](const rct12_xyzd8& pos) { - return pos.x == 0xFF && pos.y == 0xFF && pos.z == 0xFF && pos.direction == INVALID_DIRECTION; - }; - - ImportEntityCommonProperties(static_cast(dst), src); - if (is_user_string_id(src->name_string_idx)) - { - dst->SetName(GetUserString(src->name_string_idx)); - } - dst->NextLoc = { src->next_x, src->next_y, src->next_z * COORDS_Z_STEP }; - dst->NextFlags = src->next_flags; - dst->State = static_cast(src->state); - dst->SubState = src->sub_state; - dst->SpriteType = static_cast(src->sprite_type); - dst->TshirtColour = src->tshirt_colour; - dst->TrousersColour = src->trousers_colour; - dst->DestinationX = src->destination_x; - dst->DestinationY = src->destination_y; - dst->DestinationTolerance = src->destination_tolerance; - dst->Var37 = src->var_37; - dst->Energy = src->energy; - dst->EnergyTarget = src->energy_target; - dst->Mass = src->mass; - dst->WindowInvalidateFlags = src->window_invalidate_flags; - dst->CurrentRide = RCT12RideIdToOpenRCT2RideId(src->current_ride); - dst->CurrentRideStation = src->current_ride_station; - dst->CurrentTrain = src->current_train; - dst->TimeToSitdown = src->time_to_sitdown; - dst->SpecialSprite = src->special_sprite; - dst->ActionSpriteType = static_cast(src->action_sprite_type); - dst->NextActionSpriteType = static_cast(src->next_action_sprite_type); - dst->ActionSpriteImageOffset = src->action_sprite_image_offset; - dst->Action = static_cast(src->action); - dst->ActionFrame = src->action_frame; - dst->StepProgress = src->step_progress; - dst->PeepDirection = src->direction; - dst->InteractionRideIndex = RCT12RideIdToOpenRCT2RideId(src->interaction_ride_index); - dst->Id = src->id; - dst->PathCheckOptimisation = src->path_check_optimisation; - dst->PeepFlags = src->peep_flags; - if (isNullLocation(src->pathfind_goal)) - { - dst->PathfindGoal.SetNull(); - dst->PathfindGoal.direction = INVALID_DIRECTION; - } - else - { - dst->PathfindGoal = { src->pathfind_goal.x, src->pathfind_goal.y, src->pathfind_goal.z, - src->pathfind_goal.direction }; - } - for (size_t i = 0; i < std::size(src->pathfind_history); i++) - { - if (isNullLocation(src->pathfind_history[i])) + for (int32_t i = 0; i < Limits::MaxEntities; i++) { - dst->PathfindHistory[i].SetNull(); - dst->PathfindHistory[i].direction = INVALID_DIRECTION; + ImportEntity(_s6.sprites[i].unknown); + } + } + + template void ImportEntity(const RCT12SpriteBase& src); + + void ImportEntityPeep(::Peep* dst, const Peep* src) + { + const auto isNullLocation = [](const rct12_xyzd8& pos) { + return pos.x == 0xFF && pos.y == 0xFF && pos.z == 0xFF && pos.direction == INVALID_DIRECTION; + }; + + ImportEntityCommonProperties(static_cast(dst), src); + if (is_user_string_id(src->name_string_idx)) + { + dst->SetName(GetUserString(src->name_string_idx)); + } + dst->NextLoc = { src->next_x, src->next_y, src->next_z * COORDS_Z_STEP }; + dst->NextFlags = src->next_flags; + dst->State = static_cast(src->state); + dst->SubState = src->sub_state; + dst->SpriteType = static_cast(src->sprite_type); + dst->TshirtColour = src->tshirt_colour; + dst->TrousersColour = src->trousers_colour; + dst->DestinationX = src->destination_x; + dst->DestinationY = src->destination_y; + dst->DestinationTolerance = src->destination_tolerance; + dst->Var37 = src->var_37; + dst->Energy = src->energy; + dst->EnergyTarget = src->energy_target; + dst->Mass = src->mass; + dst->WindowInvalidateFlags = src->window_invalidate_flags; + dst->CurrentRide = RCT12RideIdToOpenRCT2RideId(src->current_ride); + dst->CurrentRideStation = src->current_ride_station; + dst->CurrentTrain = src->current_train; + dst->TimeToSitdown = src->time_to_sitdown; + dst->SpecialSprite = src->special_sprite; + dst->ActionSpriteType = static_cast(src->action_sprite_type); + dst->NextActionSpriteType = static_cast(src->next_action_sprite_type); + dst->ActionSpriteImageOffset = src->action_sprite_image_offset; + dst->Action = static_cast(src->action); + dst->ActionFrame = src->action_frame; + dst->StepProgress = src->step_progress; + dst->PeepDirection = src->direction; + dst->InteractionRideIndex = RCT12RideIdToOpenRCT2RideId(src->interaction_ride_index); + dst->Id = src->id; + dst->PathCheckOptimisation = src->path_check_optimisation; + dst->PeepFlags = src->peep_flags; + if (isNullLocation(src->pathfind_goal)) + { + dst->PathfindGoal.SetNull(); + dst->PathfindGoal.direction = INVALID_DIRECTION; } else { - dst->PathfindHistory[i] = { src->pathfind_history[i].x, src->pathfind_history[i].y, src->pathfind_history[i].z, - src->pathfind_history[i].direction }; + dst->PathfindGoal = { src->pathfind_goal.x, src->pathfind_goal.y, src->pathfind_goal.z, + src->pathfind_goal.direction }; } - } - dst->WalkingFrameNum = src->no_action_frame_num; - } - - constexpr EntityType GetEntityTypeFromRCT2Sprite(const RCT12SpriteBase* src) - { - EntityType output = EntityType::Null; - switch (src->sprite_identifier) - { - case RCT12SpriteIdentifier::Vehicle: - output = EntityType::Vehicle; - break; - case RCT12SpriteIdentifier::Peep: - if (RCT12PeepType(static_cast(src)->peep_type) == RCT12PeepType::Guest) + for (size_t i = 0; i < std::size(src->pathfind_history); i++) + { + if (isNullLocation(src->pathfind_history[i])) { - output = EntityType::Guest; + dst->PathfindHistory[i].SetNull(); + dst->PathfindHistory[i].direction = INVALID_DIRECTION; } else { - output = EntityType::Staff; + dst->PathfindHistory[i] = { src->pathfind_history[i].x, src->pathfind_history[i].y, + src->pathfind_history[i].z, src->pathfind_history[i].direction }; } - break; - case RCT12SpriteIdentifier::Misc: - - switch (RCT12MiscEntityType(src->type)) - { - case RCT12MiscEntityType::SteamParticle: - output = EntityType::SteamParticle; - break; - case RCT12MiscEntityType::MoneyEffect: - output = EntityType::MoneyEffect; - break; - case RCT12MiscEntityType::CrashedVehicleParticle: - output = EntityType::CrashedVehicleParticle; - break; - case RCT12MiscEntityType::ExplosionCloud: - output = EntityType::ExplosionCloud; - break; - case RCT12MiscEntityType::CrashSplash: - output = EntityType::CrashSplash; - break; - case RCT12MiscEntityType::ExplosionFlare: - output = EntityType::ExplosionFlare; - break; - case RCT12MiscEntityType::JumpingFountainWater: - case RCT12MiscEntityType::JumpingFountainSnow: - output = EntityType::JumpingFountain; - break; - case RCT12MiscEntityType::Balloon: - output = EntityType::Balloon; - break; - case RCT12MiscEntityType::Duck: - output = EntityType::Duck; - break; - default: - break; - } - break; - case RCT12SpriteIdentifier::Litter: - output = EntityType::Litter; - break; - default: - break; + } + dst->WalkingFrameNum = src->no_action_frame_num; } - return output; - } - void ImportEntityCommonProperties(EntityBase* dst, const RCT12SpriteBase* src) - { - dst->Type = GetEntityTypeFromRCT2Sprite(src); - dst->sprite_height_negative = src->sprite_height_negative; - dst->sprite_index = src->sprite_index; - dst->x = src->x; - dst->y = src->y; - dst->z = src->z; - dst->sprite_width = src->sprite_width; - dst->sprite_height_positive = src->sprite_height_positive; - dst->SpriteRect = ScreenRect(src->sprite_left, src->sprite_top, src->sprite_right, src->sprite_bottom); - dst->sprite_direction = src->sprite_direction; - } - - void ImportEntity(const RCT12SpriteBase& src); - - std::string GetUserString(rct_string_id stringId) - { - const auto originalString = _s6.custom_strings[(stringId - USER_STRING_START) % 1024]; - auto originalStringView = std::string_view( - originalString, GetRCT2StringBufferLen(originalString, USER_STRING_MAX_LENGTH)); - auto asUtf8 = rct2_to_utf8(originalStringView, RCT2LanguageId::EnglishUK); - auto justText = RCT12RemoveFormattingUTF8(asUtf8); - return justText.data(); - } - - ObjectList GetRequiredObjects() - { - 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(_pathToRailingMap), std::end(_pathToRailingMap), OBJECT_ENTRY_INDEX_NULL); - - ObjectList objectList; - int objectIt = 0; - ObjectEntryIndex surfaceCount = 0; - ObjectEntryIndex railingCount = 0; - for (int16_t objectType = EnumValue(ObjectType::Ride); objectType <= EnumValue(ObjectType::Water); objectType++) + constexpr EntityType GetEntityTypeFromRCT2Sprite(const RCT12SpriteBase* src) { - for (int16_t i = 0; i < rct2_object_entry_group_counts[objectType]; i++, objectIt++) + EntityType output = EntityType::Null; + switch (src->sprite_identifier) { - auto entry = ObjectEntryDescriptor(_s6.Objects[objectIt]); - if (entry.HasValue()) - { - if (objectType == EnumValue(ObjectType::Paths)) + case RCT12SpriteIdentifier::Vehicle: + output = EntityType::Vehicle; + break; + case RCT12SpriteIdentifier::Peep: + if (RCT12PeepType(static_cast(src)->peep_type) == RCT12PeepType::Guest) { - auto footpathMapping = GetFootpathSurfaceId(entry); - if (footpathMapping == nullptr) - { - // Unsupported footpath - objectList.SetObject(i, entry); - } - else - { - // We have surface objects for this footpath - auto surfaceIndex = objectList.Find(ObjectType::FootpathSurface, footpathMapping->NormalSurface); - if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL) - { - objectList.SetObject(ObjectType::FootpathSurface, surfaceCount, footpathMapping->NormalSurface); - surfaceIndex = surfaceCount++; - } - _pathToSurfaceMap[i] = surfaceIndex; - - surfaceIndex = objectList.Find(ObjectType::FootpathSurface, footpathMapping->QueueSurface); - if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL) - { - objectList.SetObject(ObjectType::FootpathSurface, surfaceCount, footpathMapping->QueueSurface); - surfaceIndex = surfaceCount++; - } - _pathToQueueSurfaceMap[i] = surfaceIndex; - - auto railingIndex = objectList.Find(ObjectType::FootpathRailings, footpathMapping->Railing); - if (railingIndex == OBJECT_ENTRY_INDEX_NULL) - { - objectList.SetObject(ObjectType::FootpathRailings, railingCount, footpathMapping->Railing); - railingIndex = railingCount++; - } - _pathToRailingMap[i] = railingIndex; - } + output = EntityType::Guest; } else { - objectList.SetObject(i, entry); + output = EntityType::Staff; + } + break; + case RCT12SpriteIdentifier::Misc: + + switch (RCT12MiscEntityType(src->type)) + { + case RCT12MiscEntityType::SteamParticle: + output = EntityType::SteamParticle; + break; + case RCT12MiscEntityType::MoneyEffect: + output = EntityType::MoneyEffect; + break; + case RCT12MiscEntityType::CrashedVehicleParticle: + output = EntityType::CrashedVehicleParticle; + break; + case RCT12MiscEntityType::ExplosionCloud: + output = EntityType::ExplosionCloud; + break; + case RCT12MiscEntityType::CrashSplash: + output = EntityType::CrashSplash; + break; + case RCT12MiscEntityType::ExplosionFlare: + output = EntityType::ExplosionFlare; + break; + case RCT12MiscEntityType::JumpingFountainWater: + case RCT12MiscEntityType::JumpingFountainSnow: + output = EntityType::JumpingFountain; + break; + case RCT12MiscEntityType::Balloon: + output = EntityType::Balloon; + break; + case RCT12MiscEntityType::Duck: + output = EntityType::Duck; + break; + default: + break; + } + break; + case RCT12SpriteIdentifier::Litter: + output = EntityType::Litter; + break; + default: + break; + } + return output; + } + + void ImportEntityCommonProperties(EntityBase* dst, const RCT12SpriteBase* src) + { + dst->Type = GetEntityTypeFromRCT2Sprite(src); + dst->sprite_height_negative = src->sprite_height_negative; + dst->sprite_index = src->sprite_index; + dst->x = src->x; + dst->y = src->y; + dst->z = src->z; + dst->sprite_width = src->sprite_width; + dst->sprite_height_positive = src->sprite_height_positive; + dst->SpriteRect = ScreenRect(src->sprite_left, src->sprite_top, src->sprite_right, src->sprite_bottom); + dst->sprite_direction = src->sprite_direction; + } + + void ImportEntity(const RCT12SpriteBase& src); + + std::string GetUserString(rct_string_id stringId) + { + const auto originalString = _s6.custom_strings[(stringId - USER_STRING_START) % 1024]; + auto originalStringView = std::string_view( + originalString, GetRCT2StringBufferLen(originalString, USER_STRING_MAX_LENGTH)); + auto asUtf8 = rct2_to_utf8(originalStringView, RCT2LanguageId::EnglishUK); + auto justText = RCT12RemoveFormattingUTF8(asUtf8); + return justText.data(); + } + + ObjectList GetRequiredObjects() + { + 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(_pathToRailingMap), std::end(_pathToRailingMap), OBJECT_ENTRY_INDEX_NULL); + + ObjectList objectList; + int objectIt = 0; + ObjectEntryIndex surfaceCount = 0; + ObjectEntryIndex railingCount = 0; + for (int16_t objectType = EnumValue(ObjectType::Ride); objectType <= EnumValue(ObjectType::Water); objectType++) + { + for (int16_t i = 0; i < rct2_object_entry_group_counts[objectType]; i++, objectIt++) + { + auto entry = ObjectEntryDescriptor(_s6.Objects[objectIt]); + if (entry.HasValue()) + { + if (objectType == EnumValue(ObjectType::Paths)) + { + auto footpathMapping = GetFootpathSurfaceId(entry); + if (footpathMapping == nullptr) + { + // Unsupported footpath + objectList.SetObject(i, entry); + } + else + { + // We have surface objects for this footpath + auto surfaceIndex = objectList.Find( + ObjectType::FootpathSurface, footpathMapping->NormalSurface); + if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL) + { + objectList.SetObject( + ObjectType::FootpathSurface, surfaceCount, footpathMapping->NormalSurface); + surfaceIndex = surfaceCount++; + } + _pathToSurfaceMap[i] = surfaceIndex; + + surfaceIndex = objectList.Find(ObjectType::FootpathSurface, footpathMapping->QueueSurface); + if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL) + { + objectList.SetObject( + ObjectType::FootpathSurface, surfaceCount, footpathMapping->QueueSurface); + surfaceIndex = surfaceCount++; + } + _pathToQueueSurfaceMap[i] = surfaceIndex; + + auto railingIndex = objectList.Find(ObjectType::FootpathRailings, footpathMapping->Railing); + if (railingIndex == OBJECT_ENTRY_INDEX_NULL) + { + objectList.SetObject(ObjectType::FootpathRailings, railingCount, footpathMapping->Railing); + railingIndex = railingCount++; + } + _pathToRailingMap[i] = railingIndex; + } + } + else + { + objectList.SetObject(i, entry); + } } } } + + SetDefaultRCT2TerrainObjects(objectList); + RCT12AddDefaultObjects(objectList); + return objectList; } + }; - SetDefaultRCT2TerrainObjects(objectList); - RCT12AddDefaultObjects(objectList); - return objectList; - } -}; - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - const auto& ride = _s6.rides[src->ride]; - - ImportEntityCommonProperties(dst, src); - dst->SubType = Vehicle::Type(src->type); - dst->Pitch = src->Pitch; - dst->bank_rotation = src->bank_rotation; - dst->remaining_distance = src->remaining_distance; - dst->velocity = src->velocity; - dst->acceleration = src->acceleration; - dst->ride = static_cast(src->ride); - dst->vehicle_type = src->vehicle_type; - dst->colours = src->colours; - dst->track_progress = src->track_progress; - dst->TrackLocation = { src->track_x, src->track_y, src->track_z }; - if (src->boat_location.IsNull() || static_cast(ride.mode) != RideMode::BoatHire - || src->status != static_cast(Vehicle::Status::TravellingBoat)) + template<> void S6Importer::ImportEntity<::Vehicle>(const RCT12SpriteBase& baseSrc) { - dst->BoatLocation.SetNull(); - dst->SetTrackDirection(src->GetTrackDirection()); - dst->SetTrackType(src->GetTrackType()); - // RotationControlToggle and Booster are saved as the same track piece ID - // Which one the vehicle is using must be determined - if (IsFlatRide(src->ride)) + auto dst = CreateEntityAt<::Vehicle>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + const auto& ride = _s6.rides[src->ride]; + + ImportEntityCommonProperties(dst, src); + dst->SubType = ::Vehicle::Type(src->type); + dst->Pitch = src->Pitch; + dst->bank_rotation = src->bank_rotation; + dst->remaining_distance = src->remaining_distance; + dst->velocity = src->velocity; + dst->acceleration = src->acceleration; + dst->ride = static_cast(src->ride); + dst->vehicle_type = src->vehicle_type; + dst->colours = src->colours; + dst->track_progress = src->track_progress; + dst->TrackLocation = { src->track_x, src->track_y, src->track_z }; + if (src->boat_location.IsNull() || static_cast(ride.mode) != RideMode::BoatHire + || src->status != static_cast(::Vehicle::Status::TravellingBoat)) { - dst->SetTrackType(RCT12FlatTrackTypeToOpenRCT2(src->GetTrackType())); + dst->BoatLocation.SetNull(); + dst->SetTrackDirection(src->GetTrackDirection()); + dst->SetTrackType(src->GetTrackType()); + // RotationControlToggle and Booster are saved as the same track piece ID + // Which one the vehicle is using must be determined + if (IsFlatRide(src->ride)) + { + dst->SetTrackType(RCT12FlatTrackTypeToOpenRCT2(src->GetTrackType())); + } + else if (src->GetTrackType() == TrackElemType::RotationControlToggleAlias) + { + // Merging hacks mean the track type that's appropriate for the ride type is not necessarily the track type the + // ride is on. It's possible to create unwanted behavior if a user layers spinning control track on top of + // booster track but this is unlikely since only two rides have spinning control track - by default they load as + // booster + TileElement* tileElement2 = map_get_track_element_at_of_type_seq( + dst->TrackLocation, TrackElemType::RotationControlToggle, 0); + + if (tileElement2 != nullptr) + dst->SetTrackType(TrackElemType::RotationControlToggle); + } } - else if (src->GetTrackType() == TrackElemType::RotationControlToggleAlias) - { - // Merging hacks mean the track type that's appropriate for the ride type is not necessarily the track type the - // ride is on. It's possible to create unwanted behavior if a user layers spinning control track on top of - // booster track but this is unlikely since only two rides have spinning control track - by default they load as - // booster - TileElement* tileElement2 = map_get_track_element_at_of_type_seq( - dst->TrackLocation, TrackElemType::RotationControlToggle, 0); - - if (tileElement2 != nullptr) - dst->SetTrackType(TrackElemType::RotationControlToggle); - } - } - else - { - dst->BoatLocation = TileCoordsXY{ src->boat_location.x, src->boat_location.y }.ToCoordsXY(); - dst->SetTrackDirection(0); - dst->SetTrackType(0); - } - - dst->next_vehicle_on_train = src->next_vehicle_on_train; - dst->prev_vehicle_on_ride = src->prev_vehicle_on_ride; - dst->next_vehicle_on_ride = src->next_vehicle_on_ride; - dst->var_44 = src->var_44; - dst->mass = src->mass; - dst->update_flags = src->update_flags; - dst->SwingSprite = src->SwingSprite; - dst->current_station = src->current_station; - dst->current_time = src->current_time; - dst->crash_z = src->crash_z; - - Vehicle::Status statusSrc = Vehicle::Status::MovingToEndOfStation; - if (src->status <= static_cast(Vehicle::Status::StoppedByBlockBrakes)) - { - statusSrc = static_cast(src->status); - } - - dst->status = statusSrc; - dst->sub_state = src->sub_state; - for (size_t i = 0; i < std::size(src->peep); i++) - { - dst->peep[i] = src->peep[i]; - dst->peep_tshirt_colours[i] = src->peep_tshirt_colours[i]; - } - dst->num_seats = src->num_seats; - dst->num_peeps = src->num_peeps; - dst->next_free_seat = src->next_free_seat; - dst->restraints_position = src->restraints_position; - dst->crash_x = src->crash_x; - dst->sound2_flags = src->sound2_flags; - dst->spin_sprite = src->spin_sprite; - dst->sound1_id = static_cast(src->sound1_id); - dst->sound1_volume = src->sound1_volume; - dst->sound2_id = static_cast(src->sound2_id); - dst->sound2_volume = src->sound2_volume; - dst->sound_vector_factor = src->sound_vector_factor; - dst->time_waiting = src->time_waiting; - dst->speed = src->speed; - dst->powered_acceleration = src->powered_acceleration; - dst->dodgems_collision_direction = src->dodgems_collision_direction; - dst->animation_frame = src->animation_frame; - dst->animationState = src->animationState; - dst->scream_sound_id = static_cast(src->scream_sound_id); - dst->TrackSubposition = VehicleTrackSubposition{ src->TrackSubposition }; - dst->var_CE = src->var_CE; - dst->var_CF = src->var_CF; - dst->lost_time_out = src->lost_time_out; - dst->vertical_drop_countdown = src->vertical_drop_countdown; - dst->var_D3 = src->var_D3; - dst->mini_golf_current_animation = MiniGolfAnimation(src->mini_golf_current_animation); - dst->mini_golf_flags = src->mini_golf_flags; - dst->ride_subtype = RCTEntryIndexToOpenRCT2EntryIndex(src->ride_subtype); - dst->colours_extended = src->colours_extended; - dst->seat_rotation = src->seat_rotation; - dst->target_seat_rotation = src->target_seat_rotation; - dst->IsCrashedVehicle = src->flags & RCT12_SPRITE_FLAGS_IS_CRASHED_VEHICLE_SPRITE; -} - -static uint32_t AdjustScenarioToCurrentTicks(const rct_s6_data& s6, uint32_t tick) -{ - // Previously gScenarioTicks was used as a time point, now it's gCurrentTicks. - // gCurrentTicks and gScenarioTicks are now exported as the same, older saves that have a different - // scenario tick must account for the difference between the two. - uint32_t ticksElapsed = s6.scenario_ticks - tick; - return s6.game_ticks_1 - ticksElapsed; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityPeep(dst, src); - - dst->OutsideOfPark = static_cast(src->outside_of_park); - dst->GuestNumRides = src->no_of_rides; - dst->Happiness = src->happiness; - dst->HappinessTarget = src->happiness_target; - dst->Nausea = src->nausea; - dst->NauseaTarget = src->nausea_target; - dst->Hunger = src->hunger; - dst->Thirst = src->thirst; - dst->Toilet = src->toilet; - dst->TimeToConsume = src->time_to_consume; - dst->Intensity = static_cast(src->intensity); - dst->NauseaTolerance = static_cast(src->nausea_tolerance); - dst->PaidOnDrink = src->paid_on_drink; - - OpenRCT2::RideUse::GetHistory().Set(dst->sprite_index, RCT12GetRidesBeenOn(src)); - OpenRCT2::RideUse::GetTypeHistory().Set(dst->sprite_index, RCT12GetRideTypesBeenOn(src)); - - dst->SetItemFlags(src->GetItemFlags()); - dst->Photo1RideRef = RCT12RideIdToOpenRCT2RideId(src->photo1_ride_ref); - dst->Photo2RideRef = RCT12RideIdToOpenRCT2RideId(src->photo2_ride_ref); - dst->Photo3RideRef = RCT12RideIdToOpenRCT2RideId(src->photo3_ride_ref); - dst->Photo4RideRef = RCT12RideIdToOpenRCT2RideId(src->photo4_ride_ref); - dst->GuestNextInQueue = src->next_in_queue; - dst->TimeInQueue = src->time_in_queue; - dst->CashInPocket = src->cash_in_pocket; - dst->CashSpent = src->cash_spent; - dst->ParkEntryTime = AdjustScenarioToCurrentTicks(_s6, src->park_entry_time); - dst->RejoinQueueTimeout = src->rejoin_queue_timeout; - dst->PreviousRide = RCT12RideIdToOpenRCT2RideId(src->previous_ride); - dst->PreviousRideTimeOut = src->previous_ride_time_out; - for (size_t i = 0; i < std::size(src->thoughts); i++) - { - auto srcThought = &src->thoughts[i]; - auto dstThought = &dst->Thoughts[i]; - dstThought->type = static_cast(srcThought->type); - if (srcThought->item == RCT12PeepThoughtItemNone) - dstThought->item = PeepThoughtItemNone; else - dstThought->item = srcThought->item; - dstThought->freshness = srcThought->freshness; - dstThought->fresh_timeout = srcThought->fresh_timeout; + { + dst->BoatLocation = TileCoordsXY{ src->boat_location.x, src->boat_location.y }.ToCoordsXY(); + dst->SetTrackDirection(0); + dst->SetTrackType(0); + } + + dst->next_vehicle_on_train = src->next_vehicle_on_train; + dst->prev_vehicle_on_ride = src->prev_vehicle_on_ride; + dst->next_vehicle_on_ride = src->next_vehicle_on_ride; + dst->var_44 = src->var_44; + dst->mass = src->mass; + dst->update_flags = src->update_flags; + dst->SwingSprite = src->SwingSprite; + dst->current_station = src->current_station; + dst->current_time = src->current_time; + dst->crash_z = src->crash_z; + + ::Vehicle::Status statusSrc = ::Vehicle::Status::MovingToEndOfStation; + if (src->status <= static_cast(::Vehicle::Status::StoppedByBlockBrakes)) + { + statusSrc = static_cast<::Vehicle::Status>(src->status); + } + + dst->status = statusSrc; + dst->sub_state = src->sub_state; + for (size_t i = 0; i < std::size(src->peep); i++) + { + dst->peep[i] = src->peep[i]; + dst->peep_tshirt_colours[i] = src->peep_tshirt_colours[i]; + } + dst->num_seats = src->num_seats; + dst->num_peeps = src->num_peeps; + dst->next_free_seat = src->next_free_seat; + dst->restraints_position = src->restraints_position; + dst->crash_x = src->crash_x; + dst->sound2_flags = src->sound2_flags; + dst->spin_sprite = src->spin_sprite; + dst->sound1_id = static_cast(src->sound1_id); + dst->sound1_volume = src->sound1_volume; + dst->sound2_id = static_cast(src->sound2_id); + dst->sound2_volume = src->sound2_volume; + dst->sound_vector_factor = src->sound_vector_factor; + dst->time_waiting = src->time_waiting; + dst->speed = src->speed; + dst->powered_acceleration = src->powered_acceleration; + dst->dodgems_collision_direction = src->dodgems_collision_direction; + dst->animation_frame = src->animation_frame; + dst->animationState = src->animationState; + dst->scream_sound_id = static_cast(src->scream_sound_id); + dst->TrackSubposition = VehicleTrackSubposition{ src->TrackSubposition }; + dst->var_CE = src->var_CE; + dst->var_CF = src->var_CF; + dst->lost_time_out = src->lost_time_out; + dst->vertical_drop_countdown = src->vertical_drop_countdown; + dst->var_D3 = src->var_D3; + dst->mini_golf_current_animation = MiniGolfAnimation(src->mini_golf_current_animation); + dst->mini_golf_flags = src->mini_golf_flags; + dst->ride_subtype = RCTEntryIndexToOpenRCT2EntryIndex(src->ride_subtype); + dst->colours_extended = src->colours_extended; + dst->seat_rotation = src->seat_rotation; + dst->target_seat_rotation = src->target_seat_rotation; + dst->IsCrashedVehicle = src->flags & RCT12_SPRITE_FLAGS_IS_CRASHED_VEHICLE_SPRITE; } - dst->GuestHeadingToRideId = RCT12RideIdToOpenRCT2RideId(src->guest_heading_to_ride_id); - dst->GuestIsLostCountdown = src->peep_is_lost_countdown; - dst->LitterCount = src->litter_count; - dst->GuestTimeOnRide = src->time_on_ride; - dst->DisgustingCount = src->disgusting_count; - dst->PaidToEnter = src->paid_to_enter; - dst->PaidOnRides = src->paid_on_rides; - dst->PaidOnFood = src->paid_on_food; - dst->PaidOnSouvenirs = src->paid_on_souvenirs; - dst->AmountOfFood = src->no_of_food; - dst->AmountOfDrinks = src->no_of_drinks; - dst->AmountOfSouvenirs = src->no_of_souvenirs; - dst->VandalismSeen = src->vandalism_seen; - dst->VoucherType = src->voucher_type; - dst->VoucherRideId = RCT12RideIdToOpenRCT2RideId(src->voucher_arguments); - dst->SurroundingsThoughtTimeout = src->surroundings_thought_timeout; - dst->Angriness = src->angriness; - dst->TimeLost = src->time_lost; - dst->DaysInQueue = src->days_in_queue; - dst->BalloonColour = src->balloon_colour; - dst->UmbrellaColour = src->umbrella_colour; - dst->HatColour = src->hat_colour; - dst->FavouriteRide = RCT12RideIdToOpenRCT2RideId(src->favourite_ride); - dst->FavouriteRideRating = src->favourite_ride_rating; -} -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityPeep(dst, src); - - dst->AssignedStaffType = StaffType(src->staff_type); - dst->MechanicTimeSinceCall = src->mechanic_time_since_call; - - dst->HireDate = src->park_entry_time; - dst->StaffOrders = src->staff_orders; - dst->StaffMowingTimeout = src->staff_mowing_timeout; - dst->StaffLawnsMown = src->paid_to_enter; - dst->StaffGardensWatered = src->paid_on_rides; - dst->StaffLitterSwept = src->paid_on_food; - dst->StaffBinsEmptied = src->paid_on_souvenirs; - - ImportStaffPatrolArea(dst, src->staff_id); -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->time_to_move = src->time_to_move; - dst->frame = src->frame; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->MoveDelay = src->move_delay; - dst->NumMovements = src->num_movements; - dst->Vertical = src->vertical; - dst->Value = src->value; - dst->OffsetX = src->offset_x; - dst->Wiggle = src->wiggle; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->frame = src->frame; - dst->time_to_live = src->time_to_live; - dst->frame = src->frame; - dst->colour[0] = src->colour[0]; - dst->colour[1] = src->colour[1]; - dst->crashed_sprite_base = src->crashed_sprite_base; - dst->velocity_x = src->velocity_x; - dst->velocity_y = src->velocity_y; - dst->velocity_z = src->velocity_z; - dst->acceleration_x = src->acceleration_x; - dst->acceleration_y = src->acceleration_y; - dst->acceleration_z = src->acceleration_z; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->frame = src->frame; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->frame = src->frame; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->frame = src->frame; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->NumTicksAlive = src->num_ticks_alive; - dst->frame = src->frame; - dst->FountainFlags = src->fountain_flags; - dst->TargetX = src->target_x; - dst->TargetY = src->target_y; - dst->Iteration = src->iteration; - dst->FountainType = RCT12MiscEntityType(src->type) == RCT12MiscEntityType::JumpingFountainSnow ? JumpingFountainType::Snow - : JumpingFountainType::Water; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->popped = src->popped; - dst->time_to_move = src->time_to_move; - dst->frame = src->frame; - dst->colour = src->colour; -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->frame = src->frame; - dst->target_x = src->target_x; - dst->target_y = src->target_y; - dst->state = static_cast(src->state); -} - -template<> void S6Importer::ImportEntity(const RCT12SpriteBase& baseSrc) -{ - auto dst = CreateEntityAt(baseSrc.sprite_index); - auto src = static_cast(&baseSrc); - ImportEntityCommonProperties(dst, src); - dst->SubType = Litter::Type(src->type); - dst->creationTick = AdjustScenarioToCurrentTicks(_s6, src->creationTick); -} - -void S6Importer::ImportEntity(const RCT12SpriteBase& src) -{ - switch (GetEntityTypeFromRCT2Sprite(&src)) + static uint32_t AdjustScenarioToCurrentTicks(const S6Data& s6, uint32_t tick) { - case EntityType::Vehicle: - ImportEntity(src); - break; - case EntityType::Guest: - ImportEntity(src); - break; - case EntityType::Staff: - ImportEntity(src); - break; - case EntityType::SteamParticle: - ImportEntity(src); - break; - case EntityType::MoneyEffect: - ImportEntity(src); - break; - case EntityType::CrashedVehicleParticle: - ImportEntity(src); - break; - case EntityType::ExplosionCloud: - ImportEntity(src); - break; - case EntityType::ExplosionFlare: - ImportEntity(src); - break; - case EntityType::CrashSplash: - ImportEntity(src); - break; - case EntityType::JumpingFountain: - ImportEntity(src); - break; - case EntityType::Balloon: - ImportEntity(src); - break; - case EntityType::Duck: - ImportEntity(src); - break; - case EntityType::Litter: - ImportEntity(src); - break; - default: - // Null elements do not need imported - break; + // Previously gScenarioTicks was used as a time point, now it's gCurrentTicks. + // gCurrentTicks and gScenarioTicks are now exported as the same, older saves that have a different + // scenario tick must account for the difference between the two. + uint32_t ticksElapsed = s6.scenario_ticks - tick; + return s6.game_ticks_1 - ticksElapsed; } -} + + template<> void S6Importer::ImportEntity<::Guest>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::Guest>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityPeep(dst, src); + + dst->OutsideOfPark = static_cast(src->outside_of_park); + dst->GuestNumRides = src->no_of_rides; + dst->Happiness = src->happiness; + dst->HappinessTarget = src->happiness_target; + dst->Nausea = src->nausea; + dst->NauseaTarget = src->nausea_target; + dst->Hunger = src->hunger; + dst->Thirst = src->thirst; + dst->Toilet = src->toilet; + dst->TimeToConsume = src->time_to_consume; + dst->Intensity = static_cast(src->intensity); + dst->NauseaTolerance = static_cast(src->nausea_tolerance); + dst->PaidOnDrink = src->paid_on_drink; + + OpenRCT2::RideUse::GetHistory().Set(dst->sprite_index, RCT12GetRidesBeenOn(src)); + OpenRCT2::RideUse::GetTypeHistory().Set(dst->sprite_index, RCT12GetRideTypesBeenOn(src)); + + dst->SetItemFlags(src->GetItemFlags()); + dst->Photo1RideRef = RCT12RideIdToOpenRCT2RideId(src->photo1_ride_ref); + dst->Photo2RideRef = RCT12RideIdToOpenRCT2RideId(src->photo2_ride_ref); + dst->Photo3RideRef = RCT12RideIdToOpenRCT2RideId(src->photo3_ride_ref); + dst->Photo4RideRef = RCT12RideIdToOpenRCT2RideId(src->photo4_ride_ref); + dst->GuestNextInQueue = src->next_in_queue; + dst->TimeInQueue = src->time_in_queue; + dst->CashInPocket = src->cash_in_pocket; + dst->CashSpent = src->cash_spent; + dst->ParkEntryTime = AdjustScenarioToCurrentTicks(_s6, src->park_entry_time); + dst->RejoinQueueTimeout = src->rejoin_queue_timeout; + dst->PreviousRide = RCT12RideIdToOpenRCT2RideId(src->previous_ride); + dst->PreviousRideTimeOut = src->previous_ride_time_out; + for (size_t i = 0; i < std::size(src->thoughts); i++) + { + auto srcThought = &src->thoughts[i]; + auto dstThought = &dst->Thoughts[i]; + dstThought->type = static_cast(srcThought->type); + if (srcThought->item == RCT12PeepThoughtItemNone) + dstThought->item = PeepThoughtItemNone; + else + dstThought->item = srcThought->item; + dstThought->freshness = srcThought->freshness; + dstThought->fresh_timeout = srcThought->fresh_timeout; + } + dst->GuestHeadingToRideId = RCT12RideIdToOpenRCT2RideId(src->guest_heading_to_ride_id); + dst->GuestIsLostCountdown = src->peep_is_lost_countdown; + dst->LitterCount = src->litter_count; + dst->GuestTimeOnRide = src->time_on_ride; + dst->DisgustingCount = src->disgusting_count; + dst->PaidToEnter = src->paid_to_enter; + dst->PaidOnRides = src->paid_on_rides; + dst->PaidOnFood = src->paid_on_food; + dst->PaidOnSouvenirs = src->paid_on_souvenirs; + dst->AmountOfFood = src->no_of_food; + dst->AmountOfDrinks = src->no_of_drinks; + dst->AmountOfSouvenirs = src->no_of_souvenirs; + dst->VandalismSeen = src->vandalism_seen; + dst->VoucherType = src->voucher_type; + dst->VoucherRideId = RCT12RideIdToOpenRCT2RideId(src->voucher_arguments); + dst->SurroundingsThoughtTimeout = src->surroundings_thought_timeout; + dst->Angriness = src->angriness; + dst->TimeLost = src->time_lost; + dst->DaysInQueue = src->days_in_queue; + dst->BalloonColour = src->balloon_colour; + dst->UmbrellaColour = src->umbrella_colour; + dst->HatColour = src->hat_colour; + dst->FavouriteRide = RCT12RideIdToOpenRCT2RideId(src->favourite_ride); + dst->FavouriteRideRating = src->favourite_ride_rating; + } + + template<> void S6Importer::ImportEntity<::Staff>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::Staff>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityPeep(dst, src); + + dst->AssignedStaffType = StaffType(src->staff_type); + dst->MechanicTimeSinceCall = src->mechanic_time_since_call; + + dst->HireDate = src->park_entry_time; + dst->StaffOrders = src->staff_orders; + dst->StaffMowingTimeout = src->staff_mowing_timeout; + dst->StaffLawnsMown = src->paid_to_enter; + dst->StaffGardensWatered = src->paid_on_rides; + dst->StaffLitterSwept = src->paid_on_food; + dst->StaffBinsEmptied = src->paid_on_souvenirs; + + ImportStaffPatrolArea(dst, src->staff_id); + } + + template<> void S6Importer::ImportEntity<::SteamParticle>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::SteamParticle>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->time_to_move = src->time_to_move; + dst->frame = src->frame; + } + + template<> void S6Importer::ImportEntity<::MoneyEffect>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::MoneyEffect>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->MoveDelay = src->move_delay; + dst->NumMovements = src->num_movements; + dst->Vertical = src->vertical; + dst->Value = src->value; + dst->OffsetX = src->offset_x; + dst->Wiggle = src->wiggle; + } + + template<> void S6Importer::ImportEntity<::VehicleCrashParticle>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::VehicleCrashParticle>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->frame = src->frame; + dst->time_to_live = src->time_to_live; + dst->frame = src->frame; + dst->colour[0] = src->colour[0]; + dst->colour[1] = src->colour[1]; + dst->crashed_sprite_base = src->crashed_sprite_base; + dst->velocity_x = src->velocity_x; + dst->velocity_y = src->velocity_y; + dst->velocity_z = src->velocity_z; + dst->acceleration_x = src->acceleration_x; + dst->acceleration_y = src->acceleration_y; + dst->acceleration_z = src->acceleration_z; + } + + template<> void S6Importer::ImportEntity<::ExplosionCloud>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::ExplosionCloud>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->frame = src->frame; + } + + template<> void S6Importer::ImportEntity<::ExplosionFlare>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::ExplosionFlare>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->frame = src->frame; + } + + template<> void S6Importer::ImportEntity<::CrashSplashParticle>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::CrashSplashParticle>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->frame = src->frame; + } + + template<> void S6Importer::ImportEntity<::JumpingFountain>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::JumpingFountain>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->NumTicksAlive = src->num_ticks_alive; + dst->frame = src->frame; + dst->FountainFlags = src->fountain_flags; + dst->TargetX = src->target_x; + dst->TargetY = src->target_y; + dst->Iteration = src->iteration; + dst->FountainType = RCT12MiscEntityType(src->type) == RCT12MiscEntityType::JumpingFountainSnow + ? ::JumpingFountainType::Snow + : ::JumpingFountainType::Water; + } + + template<> void S6Importer::ImportEntity<::Balloon>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::Balloon>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->popped = src->popped; + dst->time_to_move = src->time_to_move; + dst->frame = src->frame; + dst->colour = src->colour; + } + + template<> void S6Importer::ImportEntity<::Duck>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::Duck>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->frame = src->frame; + dst->target_x = src->target_x; + dst->target_y = src->target_y; + dst->state = static_cast<::Duck::DuckState>(src->state); + } + + template<> void S6Importer::ImportEntity<::Litter>(const RCT12SpriteBase& baseSrc) + { + auto dst = CreateEntityAt<::Litter>(baseSrc.sprite_index); + auto src = static_cast(&baseSrc); + ImportEntityCommonProperties(dst, src); + dst->SubType = ::Litter::Type(src->type); + dst->creationTick = AdjustScenarioToCurrentTicks(_s6, src->creationTick); + } + + void S6Importer::ImportEntity(const RCT12SpriteBase& src) + { + switch (GetEntityTypeFromRCT2Sprite(&src)) + { + case EntityType::Vehicle: + ImportEntity<::Vehicle>(src); + break; + case EntityType::Guest: + ImportEntity<::Guest>(src); + break; + case EntityType::Staff: + ImportEntity<::Staff>(src); + break; + case EntityType::SteamParticle: + ImportEntity<::SteamParticle>(src); + break; + case EntityType::MoneyEffect: + ImportEntity<::MoneyEffect>(src); + break; + case EntityType::CrashedVehicleParticle: + ImportEntity<::VehicleCrashParticle>(src); + break; + case EntityType::ExplosionCloud: + ImportEntity<::ExplosionCloud>(src); + break; + case EntityType::ExplosionFlare: + ImportEntity<::ExplosionFlare>(src); + break; + case EntityType::CrashSplash: + ImportEntity<::CrashSplashParticle>(src); + break; + case EntityType::JumpingFountain: + ImportEntity<::JumpingFountain>(src); + break; + case EntityType::Balloon: + ImportEntity<::Balloon>(src); + break; + case EntityType::Duck: + ImportEntity<::Duck>(src); + break; + case EntityType::Litter: + ImportEntity<::Litter>(src); + break; + default: + // Null elements do not need imported + break; + } + } +} // namespace RCT2 std::unique_ptr ParkImporter::CreateS6(IObjectRepository& objectRepository) { - return std::make_unique(objectRepository); + return std::make_unique(objectRepository); } static void show_error(uint8_t errorType, rct_string_id errorStringId) @@ -2110,7 +2120,7 @@ static void show_error(uint8_t errorType, rct_string_id errorStringId) void load_from_sv6(const char* path) { auto context = OpenRCT2::GetContext(); - auto s6Importer = std::make_unique(context->GetObjectRepository()); + auto s6Importer = std::make_unique(context->GetObjectRepository()); try { auto& objectMgr = context->GetObjectManager(); @@ -2151,7 +2161,7 @@ void load_from_sc6(const char* path) { auto context = OpenRCT2::GetContext(); auto& objManager = context->GetObjectManager(); - auto s6Importer = std::make_unique(context->GetObjectRepository()); + auto s6Importer = std::make_unique(context->GetObjectRepository()); try { auto result = s6Importer->LoadScenario(path); diff --git a/src/openrct2/rct2/T6Exporter.cpp b/src/openrct2/rct2/T6Exporter.cpp index 947cf876ca..276e84ddb2 100644 --- a/src/openrct2/rct2/T6Exporter.cpp +++ b/src/openrct2/rct2/T6Exporter.cpp @@ -26,115 +26,119 @@ #include -T6Exporter::T6Exporter(TrackDesign* trackDesign) - : _trackDesign(trackDesign) +namespace RCT2 { -} - -bool T6Exporter::SaveTrack(const utf8* path) -{ - try + T6Exporter::T6Exporter(TrackDesign* trackDesign) + : _trackDesign(trackDesign) { - auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE); - return SaveTrack(&fs); } - catch (const std::exception& e) - { - log_error("Unable to save track design: %s", e.what()); - return false; - } -} -bool T6Exporter::SaveTrack(OpenRCT2::IStream* stream) -{ - OpenRCT2::MemoryStream tempStream; - tempStream.WriteValue(OpenRCT2RideTypeToRCT2RideType(_trackDesign->type)); - tempStream.WriteValue(_trackDesign->vehicle_type); - tempStream.WriteValue(_trackDesign->flags); - tempStream.WriteValue(static_cast(_trackDesign->ride_mode)); - tempStream.WriteValue((_trackDesign->colour_scheme & 0x3) | (2 << 2)); - tempStream.WriteArray(_trackDesign->vehicle_colours.data(), RCT2::Limits::MaxVehicleColours); - tempStream.WriteValue(0); - tempStream.WriteValue(_trackDesign->entrance_style); - tempStream.WriteValue(_trackDesign->total_air_time); - tempStream.WriteValue(_trackDesign->depart_flags); - tempStream.WriteValue(_trackDesign->number_of_trains); - tempStream.WriteValue(_trackDesign->number_of_cars_per_train); - tempStream.WriteValue(_trackDesign->min_waiting_time); - tempStream.WriteValue(_trackDesign->max_waiting_time); - tempStream.WriteValue(_trackDesign->operation_setting); - tempStream.WriteValue(_trackDesign->max_speed); - tempStream.WriteValue(_trackDesign->average_speed); - tempStream.WriteValue(_trackDesign->ride_length); - tempStream.WriteValue(_trackDesign->max_positive_vertical_g); - tempStream.WriteValue(_trackDesign->max_negative_vertical_g); - tempStream.WriteValue(_trackDesign->max_lateral_g); - tempStream.WriteValue(_trackDesign->type == RIDE_TYPE_MINI_GOLF ? _trackDesign->holes : _trackDesign->inversions); - tempStream.WriteValue(_trackDesign->drops); - tempStream.WriteValue(_trackDesign->highest_drop_height); - tempStream.WriteValue(_trackDesign->excitement); - tempStream.WriteValue(_trackDesign->intensity); - tempStream.WriteValue(_trackDesign->nausea); - tempStream.WriteValue(_trackDesign->upkeep_cost); - tempStream.WriteArray(_trackDesign->track_spine_colour, RCT2::Limits::NumColourSchemes); - tempStream.WriteArray(_trackDesign->track_rail_colour, RCT2::Limits::NumColourSchemes); - tempStream.WriteArray(_trackDesign->track_support_colour, RCT2::Limits::NumColourSchemes); - tempStream.WriteValue(_trackDesign->flags2); - tempStream.Write(&_trackDesign->vehicle_object.Entry, sizeof(rct_object_entry)); - tempStream.WriteValue(_trackDesign->space_required_x); - tempStream.WriteValue(_trackDesign->space_required_y); - tempStream.WriteArray(_trackDesign->vehicle_additional_colour, RCT2::Limits::MaxTrainsPerRide); - tempStream.WriteValue(_trackDesign->lift_hill_speed | (_trackDesign->num_circuits << 5)); - - if (_trackDesign->type == RIDE_TYPE_MAZE) + bool T6Exporter::SaveTrack(const utf8* path) { - for (const auto& mazeElement : _trackDesign->maze_elements) + try { - tempStream.WriteValue(mazeElement.all); + auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE); + return SaveTrack(&fs); } - - tempStream.WriteValue(0); - } - else - { - for (const auto& trackElement : _trackDesign->track_elements) + catch (const std::exception& e) { - auto trackType = OpenRCT2TrackTypeToRCT2(trackElement.type); - if (trackType == TrackElemType::MultiDimInvertedUp90ToFlatQuarterLoop) + log_error("Unable to save track design: %s", e.what()); + return false; + } + } + + bool T6Exporter::SaveTrack(OpenRCT2::IStream* stream) + { + OpenRCT2::MemoryStream tempStream; + tempStream.WriteValue(OpenRCT2RideTypeToRCT2RideType(_trackDesign->type)); + tempStream.WriteValue(_trackDesign->vehicle_type); + tempStream.WriteValue(_trackDesign->flags); + tempStream.WriteValue(static_cast(_trackDesign->ride_mode)); + tempStream.WriteValue((_trackDesign->colour_scheme & 0x3) | (2 << 2)); + tempStream.WriteArray(_trackDesign->vehicle_colours.data(), Limits::MaxVehicleColours); + tempStream.WriteValue(0); + tempStream.WriteValue(_trackDesign->entrance_style); + tempStream.WriteValue(_trackDesign->total_air_time); + tempStream.WriteValue(_trackDesign->depart_flags); + tempStream.WriteValue(_trackDesign->number_of_trains); + tempStream.WriteValue(_trackDesign->number_of_cars_per_train); + tempStream.WriteValue(_trackDesign->min_waiting_time); + tempStream.WriteValue(_trackDesign->max_waiting_time); + tempStream.WriteValue(_trackDesign->operation_setting); + tempStream.WriteValue(_trackDesign->max_speed); + tempStream.WriteValue(_trackDesign->average_speed); + tempStream.WriteValue(_trackDesign->ride_length); + tempStream.WriteValue(_trackDesign->max_positive_vertical_g); + tempStream.WriteValue(_trackDesign->max_negative_vertical_g); + tempStream.WriteValue(_trackDesign->max_lateral_g); + tempStream.WriteValue( + _trackDesign->type == RIDE_TYPE_MINI_GOLF ? _trackDesign->holes : _trackDesign->inversions); + tempStream.WriteValue(_trackDesign->drops); + tempStream.WriteValue(_trackDesign->highest_drop_height); + tempStream.WriteValue(_trackDesign->excitement); + tempStream.WriteValue(_trackDesign->intensity); + tempStream.WriteValue(_trackDesign->nausea); + tempStream.WriteValue(_trackDesign->upkeep_cost); + tempStream.WriteArray(_trackDesign->track_spine_colour, Limits::NumColourSchemes); + tempStream.WriteArray(_trackDesign->track_rail_colour, Limits::NumColourSchemes); + tempStream.WriteArray(_trackDesign->track_support_colour, Limits::NumColourSchemes); + tempStream.WriteValue(_trackDesign->flags2); + tempStream.Write(&_trackDesign->vehicle_object.Entry, sizeof(rct_object_entry)); + tempStream.WriteValue(_trackDesign->space_required_x); + tempStream.WriteValue(_trackDesign->space_required_y); + tempStream.WriteArray(_trackDesign->vehicle_additional_colour, Limits::MaxTrainsPerRide); + tempStream.WriteValue(_trackDesign->lift_hill_speed | (_trackDesign->num_circuits << 5)); + + if (_trackDesign->type == RIDE_TYPE_MAZE) + { + for (const auto& mazeElement : _trackDesign->maze_elements) { - trackType = TrackElemType::InvertedUp90ToFlatQuarterLoopAlias; + tempStream.WriteValue(mazeElement.all); } - tempStream.WriteValue(static_cast(trackType)); - tempStream.WriteValue(trackElement.flags); + + tempStream.WriteValue(0); } - - tempStream.WriteValue(0xFF); - - for (const auto& entranceElement : _trackDesign->entrance_elements) + else { - tempStream.WriteValue(entranceElement.z == -1 ? static_cast(0x80) : entranceElement.z); - tempStream.WriteValue(entranceElement.direction | (entranceElement.isExit << 7)); - tempStream.WriteValue(entranceElement.x); - tempStream.WriteValue(entranceElement.y); + for (const auto& trackElement : _trackDesign->track_elements) + { + auto trackType = OpenRCT2TrackTypeToRCT2(trackElement.type); + if (trackType == TrackElemType::MultiDimInvertedUp90ToFlatQuarterLoop) + { + trackType = TrackElemType::InvertedUp90ToFlatQuarterLoopAlias; + } + tempStream.WriteValue(static_cast(trackType)); + tempStream.WriteValue(trackElement.flags); + } + + tempStream.WriteValue(0xFF); + + for (const auto& entranceElement : _trackDesign->entrance_elements) + { + tempStream.WriteValue(entranceElement.z == -1 ? static_cast(0x80) : entranceElement.z); + tempStream.WriteValue(entranceElement.direction | (entranceElement.isExit << 7)); + tempStream.WriteValue(entranceElement.x); + tempStream.WriteValue(entranceElement.y); + } + + tempStream.WriteValue(0xFF); + } + + for (const auto& sceneryElement : _trackDesign->scenery_elements) + { + tempStream.Write(&sceneryElement.scenery_object.Entry, sizeof(rct_object_entry)); + tempStream.WriteValue(sceneryElement.x); + tempStream.WriteValue(sceneryElement.y); + tempStream.WriteValue(sceneryElement.z); + tempStream.WriteValue(sceneryElement.flags); + tempStream.WriteValue(sceneryElement.primary_colour); + tempStream.WriteValue(sceneryElement.secondary_colour); } tempStream.WriteValue(0xFF); + + SawyerChunkWriter sawyerCoding(stream); + sawyerCoding.WriteChunkTrack(tempStream.GetData(), tempStream.GetLength()); + return true; } - - for (const auto& sceneryElement : _trackDesign->scenery_elements) - { - tempStream.Write(&sceneryElement.scenery_object.Entry, sizeof(rct_object_entry)); - tempStream.WriteValue(sceneryElement.x); - tempStream.WriteValue(sceneryElement.y); - tempStream.WriteValue(sceneryElement.z); - tempStream.WriteValue(sceneryElement.flags); - tempStream.WriteValue(sceneryElement.primary_colour); - tempStream.WriteValue(sceneryElement.secondary_colour); - } - - tempStream.WriteValue(0xFF); - - SawyerChunkWriter sawyerCoding(stream); - sawyerCoding.WriteChunkTrack(tempStream.GetData(), tempStream.GetLength()); - return true; -} +} // namespace RCT2 diff --git a/src/openrct2/rct2/T6Exporter.h b/src/openrct2/rct2/T6Exporter.h index 14cdeb23d9..2a4c773b9e 100644 --- a/src/openrct2/rct2/T6Exporter.h +++ b/src/openrct2/rct2/T6Exporter.h @@ -19,17 +19,20 @@ namespace OpenRCT2 struct IStream; } -/** - * Class to export RollerCoaster Tycoon 2 track designs (*.TD6). - */ -class T6Exporter final +namespace RCT2 { -public: - T6Exporter(TrackDesign* trackDesign); + /** + * Class to export RollerCoaster Tycoon 2 track designs (*.TD6). + */ + class T6Exporter final + { + public: + T6Exporter(TrackDesign* trackDesign); - bool SaveTrack(const utf8* path); - bool SaveTrack(OpenRCT2::IStream* stream); + bool SaveTrack(const utf8* path); + bool SaveTrack(OpenRCT2::IStream* stream); -private: - TrackDesign* _trackDesign; -}; + private: + TrackDesign* _trackDesign; + }; +} // namespace RCT2 diff --git a/src/openrct2/rct2/T6Importer.cpp b/src/openrct2/rct2/T6Importer.cpp index 85e51d36b1..6df127d463 100644 --- a/src/openrct2/rct2/T6Importer.cpp +++ b/src/openrct2/rct2/T6Importer.cpp @@ -24,220 +24,223 @@ #include -static std::mutex _objectLookupMutex; - -/** - * Class to import RollerCoaster Tycoon 2 track designs (*.TD6). - */ -class TD6Importer final : public ITrackImporter +namespace RCT2 { -private: - OpenRCT2::MemoryStream _stream; - std::string _name; + static std::mutex _objectLookupMutex; -public: - TD6Importer() + /** + * Class to import RollerCoaster Tycoon 2 track designs (*.TD6). + */ + class TD6Importer final : public ITrackImporter { - } + private: + OpenRCT2::MemoryStream _stream; + std::string _name; - bool Load(const utf8* path) override - { - const utf8* extension = Path::GetExtension(path); - if (String::Equals(extension, ".td6", true)) + public: + TD6Importer() { - _name = GetNameFromTrackPath(path); - auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); - return LoadFromStream(&fs); } - throw std::runtime_error("Invalid RCT2 track extension."); - } - - bool LoadFromStream(OpenRCT2::IStream* stream) override - { - if (!gConfigGeneral.allow_loading_with_incorrect_checksum - && SawyerEncoding::ValidateTrackChecksum(stream) != RCT12TrackDesignVersion::TD6) + bool Load(const utf8* path) override { - throw IOException("Invalid checksum."); - } - - auto chunkReader = SawyerChunkReader(stream); - auto data = chunkReader.ReadChunkTrack(); - _stream.WriteArray(reinterpret_cast(data->GetData()), data->GetLength()); - _stream.SetPosition(0); - return true; - } - - std::unique_ptr Import() override - { - std::unique_ptr td = std::make_unique(); - - rct_track_td6 td6{}; - // Rework td6 so that it is just the fields - _stream.Read(&td6, 0xA3); - - td->type = td6.type; // 0x00 - td->vehicle_type = td6.vehicle_type; - - td->cost = 0; - td->flags = td6.flags; - td->ride_mode = static_cast(td6.ride_mode); - td->track_flags = 0; - td->colour_scheme = td6.version_and_colour_scheme & 0x3; - for (auto i = 0; i < RCT2::Limits::MaxTrainsPerRide; ++i) - { - td->vehicle_colours[i] = td6.vehicle_colours[i]; - td->vehicle_additional_colour[i] = td6.vehicle_additional_colour[i]; - } - td->entrance_style = td6.entrance_style; - td->total_air_time = td6.total_air_time; - td->depart_flags = td6.depart_flags; - td->number_of_trains = td6.number_of_trains; - td->number_of_cars_per_train = td6.number_of_cars_per_train; - td->min_waiting_time = td6.min_waiting_time; - td->max_waiting_time = td6.max_waiting_time; - td->operation_setting = td6.operation_setting; - td->max_speed = td6.max_speed; - td->average_speed = td6.average_speed; - td->ride_length = td6.ride_length; - td->max_positive_vertical_g = td6.max_positive_vertical_g; - td->max_negative_vertical_g = td6.max_negative_vertical_g; - td->max_lateral_g = td6.max_lateral_g; - - if (td->type == RIDE_TYPE_MINI_GOLF) - { - td->holes = td6.holes; - } - else - { - td->inversions = td6.inversions; - } - - td->drops = td6.drops; - td->highest_drop_height = td6.highest_drop_height; - td->excitement = td6.excitement; - td->intensity = td6.intensity; - td->nausea = td6.nausea; - td->upkeep_cost = td6.upkeep_cost; - for (auto i = 0; i < RCT2::Limits::NumColourSchemes; ++i) - { - td->track_spine_colour[i] = td6.track_spine_colour[i]; - td->track_rail_colour[i] = td6.track_rail_colour[i]; - td->track_support_colour[i] = td6.track_support_colour[i]; - } - td->flags2 = td6.flags2; - td->vehicle_object = ObjectEntryDescriptor(td6.vehicle_object); - td->space_required_x = td6.space_required_x; - td->space_required_y = td6.space_required_y; - td->lift_hill_speed = td6.lift_hill_speed_num_circuits & 0b00011111; - td->num_circuits = td6.lift_hill_speed_num_circuits >> 5; - - auto version = static_cast((td6.version_and_colour_scheme >> 2) & 3); - if (version != RCT12TrackDesignVersion::TD6) - { - log_error("Unsupported track design."); - return nullptr; - } - - td->operation_setting = std::min(td->operation_setting, GetRideTypeDescriptor(td->type).OperatingSettings.MaxValue); - - if (td->type == RIDE_TYPE_MAZE) - { - rct_td46_maze_element t6MazeElement{}; - t6MazeElement.all = !0; - while (t6MazeElement.all != 0) + const utf8* extension = Path::GetExtension(path); + if (String::Equals(extension, ".td6", true)) { - _stream.Read(&t6MazeElement, sizeof(rct_td46_maze_element)); - if (t6MazeElement.all != 0) + _name = GetNameFromTrackPath(path); + auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); + return LoadFromStream(&fs); + } + + throw std::runtime_error("Invalid RCT2 track extension."); + } + + bool LoadFromStream(OpenRCT2::IStream* stream) override + { + if (!gConfigGeneral.allow_loading_with_incorrect_checksum + && SawyerEncoding::ValidateTrackChecksum(stream) != RCT12TrackDesignVersion::TD6) + { + throw IOException("Invalid checksum."); + } + + auto chunkReader = SawyerChunkReader(stream); + auto data = chunkReader.ReadChunkTrack(); + _stream.WriteArray(reinterpret_cast(data->GetData()), data->GetLength()); + _stream.SetPosition(0); + return true; + } + + std::unique_ptr Import() override + { + std::unique_ptr td = std::make_unique(); + + TD6Track td6{}; + // Rework td6 so that it is just the fields + _stream.Read(&td6, 0xA3); + + td->type = td6.type; // 0x00 + td->vehicle_type = td6.vehicle_type; + + td->cost = 0; + td->flags = td6.flags; + td->ride_mode = static_cast(td6.ride_mode); + td->track_flags = 0; + td->colour_scheme = td6.version_and_colour_scheme & 0x3; + for (auto i = 0; i < Limits::MaxTrainsPerRide; ++i) + { + td->vehicle_colours[i] = td6.vehicle_colours[i]; + td->vehicle_additional_colour[i] = td6.vehicle_additional_colour[i]; + } + td->entrance_style = td6.entrance_style; + td->total_air_time = td6.total_air_time; + td->depart_flags = td6.depart_flags; + td->number_of_trains = td6.number_of_trains; + td->number_of_cars_per_train = td6.number_of_cars_per_train; + td->min_waiting_time = td6.min_waiting_time; + td->max_waiting_time = td6.max_waiting_time; + td->operation_setting = td6.operation_setting; + td->max_speed = td6.max_speed; + td->average_speed = td6.average_speed; + td->ride_length = td6.ride_length; + td->max_positive_vertical_g = td6.max_positive_vertical_g; + td->max_negative_vertical_g = td6.max_negative_vertical_g; + td->max_lateral_g = td6.max_lateral_g; + + if (td->type == RIDE_TYPE_MINI_GOLF) + { + td->holes = td6.holes; + } + else + { + td->inversions = td6.inversions; + } + + td->drops = td6.drops; + td->highest_drop_height = td6.highest_drop_height; + td->excitement = td6.excitement; + td->intensity = td6.intensity; + td->nausea = td6.nausea; + td->upkeep_cost = td6.upkeep_cost; + for (auto i = 0; i < Limits::NumColourSchemes; ++i) + { + td->track_spine_colour[i] = td6.track_spine_colour[i]; + td->track_rail_colour[i] = td6.track_rail_colour[i]; + td->track_support_colour[i] = td6.track_support_colour[i]; + } + td->flags2 = td6.flags2; + td->vehicle_object = ObjectEntryDescriptor(td6.vehicle_object); + td->space_required_x = td6.space_required_x; + td->space_required_y = td6.space_required_y; + td->lift_hill_speed = td6.lift_hill_speed_num_circuits & 0b00011111; + td->num_circuits = td6.lift_hill_speed_num_circuits >> 5; + + auto version = static_cast((td6.version_and_colour_scheme >> 2) & 3); + if (version != RCT12TrackDesignVersion::TD6) + { + log_error("Unsupported track design."); + return nullptr; + } + + td->operation_setting = std::min(td->operation_setting, GetRideTypeDescriptor(td->type).OperatingSettings.MaxValue); + + if (td->type == RIDE_TYPE_MAZE) + { + rct_td46_maze_element t6MazeElement{}; + t6MazeElement.all = !0; + while (t6MazeElement.all != 0) { - TrackDesignMazeElement mazeElement{}; - mazeElement.x = t6MazeElement.x; - mazeElement.y = t6MazeElement.y; - mazeElement.direction = t6MazeElement.direction; - mazeElement.type = t6MazeElement.type; - td->maze_elements.push_back(mazeElement); + _stream.Read(&t6MazeElement, sizeof(rct_td46_maze_element)); + if (t6MazeElement.all != 0) + { + TrackDesignMazeElement mazeElement{}; + mazeElement.x = t6MazeElement.x; + mazeElement.y = t6MazeElement.y; + mazeElement.direction = t6MazeElement.direction; + mazeElement.type = t6MazeElement.type; + td->maze_elements.push_back(mazeElement); + } } } - } - else - { - rct_td46_track_element t6TrackElement{}; + else + { + rct_td46_track_element t6TrackElement{}; + for (uint8_t endFlag = _stream.ReadValue(); endFlag != 0xFF; endFlag = _stream.ReadValue()) + { + _stream.SetPosition(_stream.GetPosition() - 1); + _stream.Read(&t6TrackElement, sizeof(rct_td46_track_element)); + TrackDesignTrackElement trackElement{}; + + track_type_t trackType = RCT2TrackTypeToOpenRCT2(t6TrackElement.type, td->type, true); + if (trackType == TrackElemType::InvertedUp90ToFlatQuarterLoopAlias) + { + trackType = TrackElemType::MultiDimInvertedUp90ToFlatQuarterLoop; + } + + trackElement.type = trackType; + trackElement.flags = t6TrackElement.flags; + td->track_elements.push_back(trackElement); + } + + TD6EntranceElement t6EntranceElement{}; + for (uint8_t endFlag = _stream.ReadValue(); endFlag != 0xFF; endFlag = _stream.ReadValue()) + { + _stream.SetPosition(_stream.GetPosition() - 1); + _stream.Read(&t6EntranceElement, sizeof(TD6EntranceElement)); + TrackDesignEntranceElement entranceElement{}; + entranceElement.z = (t6EntranceElement.z == -128) ? -1 : t6EntranceElement.z; + entranceElement.direction = t6EntranceElement.direction & 0x7F; + entranceElement.x = t6EntranceElement.x; + entranceElement.y = t6EntranceElement.y; + entranceElement.isExit = t6EntranceElement.direction >> 7; + td->entrance_elements.push_back(entranceElement); + } + } + for (uint8_t endFlag = _stream.ReadValue(); endFlag != 0xFF; endFlag = _stream.ReadValue()) { _stream.SetPosition(_stream.GetPosition() - 1); - _stream.Read(&t6TrackElement, sizeof(rct_td46_track_element)); - TrackDesignTrackElement trackElement{}; - - track_type_t trackType = RCT2TrackTypeToOpenRCT2(t6TrackElement.type, td->type, true); - if (trackType == TrackElemType::InvertedUp90ToFlatQuarterLoopAlias) - { - trackType = TrackElemType::MultiDimInvertedUp90ToFlatQuarterLoop; - } - - trackElement.type = trackType; - trackElement.flags = t6TrackElement.flags; - td->track_elements.push_back(trackElement); + TD6SceneryElement t6SceneryElement{}; + _stream.Read(&t6SceneryElement, sizeof(TD6SceneryElement)); + TrackDesignSceneryElement sceneryElement{}; + sceneryElement.scenery_object = ObjectEntryDescriptor(t6SceneryElement.scenery_object); + sceneryElement.x = t6SceneryElement.x; + sceneryElement.y = t6SceneryElement.y; + sceneryElement.z = t6SceneryElement.z; + sceneryElement.flags = t6SceneryElement.flags; + sceneryElement.primary_colour = t6SceneryElement.primary_colour; + sceneryElement.secondary_colour = t6SceneryElement.secondary_colour; + td->scenery_elements.push_back(std::move(sceneryElement)); } - rct_td6_entrance_element t6EntranceElement{}; - for (uint8_t endFlag = _stream.ReadValue(); endFlag != 0xFF; endFlag = _stream.ReadValue()) - { - _stream.SetPosition(_stream.GetPosition() - 1); - _stream.Read(&t6EntranceElement, sizeof(rct_td6_entrance_element)); - TrackDesignEntranceElement entranceElement{}; - entranceElement.z = (t6EntranceElement.z == -128) ? -1 : t6EntranceElement.z; - entranceElement.direction = t6EntranceElement.direction & 0x7F; - entranceElement.x = t6EntranceElement.x; - entranceElement.y = t6EntranceElement.y; - entranceElement.isExit = t6EntranceElement.direction >> 7; - td->entrance_elements.push_back(entranceElement); - } + td->name = _name; + + UpdateRideType(td); + + return td; } - for (uint8_t endFlag = _stream.ReadValue(); endFlag != 0xFF; endFlag = _stream.ReadValue()) + void UpdateRideType(std::unique_ptr& td) { - _stream.SetPosition(_stream.GetPosition() - 1); - rct_td6_scenery_element t6SceneryElement{}; - _stream.Read(&t6SceneryElement, sizeof(rct_td6_scenery_element)); - TrackDesignSceneryElement sceneryElement{}; - sceneryElement.scenery_object = ObjectEntryDescriptor(t6SceneryElement.scenery_object); - sceneryElement.x = t6SceneryElement.x; - sceneryElement.y = t6SceneryElement.y; - sceneryElement.z = t6SceneryElement.z; - sceneryElement.flags = t6SceneryElement.flags; - sceneryElement.primary_colour = t6SceneryElement.primary_colour; - sceneryElement.secondary_colour = t6SceneryElement.secondary_colour; - td->scenery_elements.push_back(std::move(sceneryElement)); - } - - td->name = _name; - - UpdateRideType(td); - - return td; - } - - void UpdateRideType(std::unique_ptr& td) - { - if (RCT2RideTypeNeedsConversion(td->type)) - { - std::scoped_lock lock(_objectLookupMutex); - auto rawObject = object_repository_load_object(&td->vehicle_object.Entry); - if (rawObject != nullptr) + if (RCT2RideTypeNeedsConversion(td->type)) { - const auto* rideEntry = static_cast( - static_cast(rawObject.get())->GetLegacyData()); - if (rideEntry != nullptr) + std::scoped_lock lock(_objectLookupMutex); + auto rawObject = object_repository_load_object(&td->vehicle_object.Entry); + if (rawObject != nullptr) { - td->type = RCT2RideTypeToOpenRCT2RideType(td->type, rideEntry); + const auto* rideEntry = static_cast( + static_cast(rawObject.get())->GetLegacyData()); + if (rideEntry != nullptr) + { + td->type = RCT2RideTypeToOpenRCT2RideType(td->type, rideEntry); + } + rawObject->Unload(); } - rawObject->Unload(); } } - } -}; + }; +} // namespace RCT2 std::unique_ptr TrackImporter::CreateTD6() { - return std::make_unique(); + return std::make_unique(); } diff --git a/src/openrct2/ride/TrackDesign.cpp b/src/openrct2/ride/TrackDesign.cpp index ed77c8315a..d2bee192a6 100644 --- a/src/openrct2/ride/TrackDesign.cpp +++ b/src/openrct2/ride/TrackDesign.cpp @@ -695,7 +695,7 @@ static std::optional TrackDesignPlaceSceneryElementGetEntry(c auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager(); if (scenery.scenery_object.GetType() == ObjectType::Paths) { - auto footpathMapping = GetFootpathSurfaceId(scenery.scenery_object, true, scenery.IsQueue()); + auto footpathMapping = RCT2::GetFootpathSurfaceId(scenery.scenery_object, true, scenery.IsQueue()); if (footpathMapping == nullptr) { // Check if legacy path object is loaded diff --git a/src/openrct2/ride/TrackDesignSave.cpp b/src/openrct2/ride/TrackDesignSave.cpp index 90d5cbfb3c..67d1896348 100644 --- a/src/openrct2/ride/TrackDesignSave.cpp +++ b/src/openrct2/ride/TrackDesignSave.cpp @@ -357,7 +357,7 @@ static std::optional track_design_save_footpath_get_best_entry auto surfaceId = surfaceEntry->GetIdentifier(); auto railingsEntry = pathElement->GetRailingsEntry(); auto railingsId = railingsEntry == nullptr ? "" : railingsEntry->GetIdentifier(); - return GetBestObjectEntryForSurface(surfaceId, railingsId); + return RCT2::GetBestObjectEntryForSurface(surfaceId, railingsId); } } return {}; diff --git a/src/openrct2/scenario/ScenarioRepository.cpp b/src/openrct2/scenario/ScenarioRepository.cpp index 35286f14f9..2a67d498d9 100644 --- a/src/openrct2/scenario/ScenarioRepository.cpp +++ b/src/openrct2/scenario/ScenarioRepository.cpp @@ -244,10 +244,10 @@ private: auto stream = GetStreamFromRCT2Scenario(path); auto chunkReader = SawyerChunkReader(stream.get()); - rct_s6_header header = chunkReader.ReadChunkAs(); + const auto header = chunkReader.ReadChunkAs(); if (header.type == S6_TYPE_SCENARIO) { - rct_s6_info info = chunkReader.ReadChunkAs(); + auto info = chunkReader.ReadChunkAs(); // If the name or the details contain a colour code, they might be in UTF-8 already. // This is caused by a bug that was in OpenRCT2 for 3 years. if (!IsLikelyUTF8(info.name) && !IsLikelyUTF8(info.details)) @@ -269,7 +269,7 @@ private: return false; } - static scenario_index_entry CreateNewScenarioEntry(const std::string& path, uint64_t timestamp, rct_s6_info* s6Info) + static scenario_index_entry CreateNewScenarioEntry(const std::string& path, uint64_t timestamp, RCT2::S6Info* s6Info) { scenario_index_entry entry = {}; @@ -664,11 +664,11 @@ private: } // Load header - auto header = fs.ReadValue(); + auto header = fs.ReadValue(); for (uint32_t i = 0; i < header.ScenarioCount; i++) { // Read legacy entry - auto scBasic = fs.ReadValue(); + auto scBasic = fs.ReadValue(); // Ignore non-completed scenarios if (scBasic.Flags & SCENARIO_FLAGS_COMPLETED)