1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-25 15:54:31 +01:00
Files
OpenRCT2/src/openrct2/rct1/S4Importer.cpp
Gymnasiast 7f4bb206af Correct identification of 'Utopia Park'
Utopia Park is the only scenario in RCT1 that uses different names in the scenario list and for the park. Since we use the former to identify scenarios, correct it.
Also add an alias to keep the very popular pack by Crappage working.
2018-08-03 18:41:33 +02:00

2915 lines
106 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2018 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "../Cheats.h"
#include "../Context.h"
#include "../Editor.h"
#include "../Game.h"
#include "../GameState.h"
#include "../ParkImporter.h"
#include "../audio/audio.h"
#include "../core/Collections.hpp"
#include "../core/Console.hpp"
#include "../core/FileStream.hpp"
#include "../core/Guard.hpp"
#include "../core/IStream.hpp"
#include "../core/Memory.hpp"
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../core/Util.hpp"
#include "../interface/Window.h"
#include "../localisation/Date.h"
#include "../localisation/Localisation.h"
#include "../management/Finance.h"
#include "../management/Marketing.h"
#include "../object/Object.h"
#include "../object/ObjectList.h"
#include "../object/ObjectManager.h"
#include "../object/ObjectRepository.h"
#include "../peep/Peep.h"
#include "../peep/Staff.h"
#include "../ride/RideData.h"
#include "../ride/Station.h"
#include "../ride/Track.h"
#include "../scenario/Scenario.h"
#include "../scenario/ScenarioRepository.h"
#include "../scenario/ScenarioSources.h"
#include "../util/SawyerCoding.h"
#include "../util/Util.h"
#include "../world/Climate.h"
#include "../world/Entrance.h"
#include "../world/Footpath.h"
#include "../world/LargeScenery.h"
#include "../world/MapAnimation.h"
#include "../world/Park.h"
#include "../world/Scenery.h"
#include "../world/SmallScenery.h"
#include "../world/Surface.h"
#include "RCT1.h"
#include "Tables.h"
#include <algorithm>
#include <memory>
#include <vector>
using namespace OpenRCT2;
static uint8_t GetPathType(rct_tile_element* tileElement);
static int32_t GetWallType(rct_tile_element* tileElement, int32_t edge);
static uint8_t GetWallColour(rct_tile_element* tileElement);
class EntryList
{
private:
std::vector<const char*> _entries;
public:
size_t GetCount() const
{
return _entries.size();
}
const std::vector<const char*>& GetEntries() const
{
return _entries;
}
size_t GetOrAddEntry(const char* entryName)
{
size_t entryIndex = Collections::IndexOf(_entries, entryName, true);
if (entryIndex == SIZE_MAX)
{
entryIndex = _entries.size();
_entries.push_back(entryName);
}
return entryIndex;
}
void AddRange(std::initializer_list<const char*> initializerList)
{
for (auto entry : initializerList)
{
GetOrAddEntry(entry);
}
}
};
class S4Importer final : public IParkImporter
{
private:
std::string _s4Path;
rct1_s4 _s4 = {};
uint8_t _gameVersion = 0;
uint8_t _parkValueConversionFactor = 0;
// Lists of dynamic object entries
EntryList _rideEntries;
EntryList _smallSceneryEntries;
EntryList _largeSceneryEntries;
EntryList _wallEntries;
EntryList _pathEntries;
EntryList _pathAdditionEntries;
EntryList _sceneryGroupEntries;
EntryList _waterEntry;
// Lookup tables for converting from RCT1 hard coded types to the new dynamic object entries
uint8_t _rideTypeToRideEntryMap[RCT1_RIDE_TYPE_COUNT]{};
uint8_t _vehicleTypeToRideEntryMap[RCT1_VEHICLE_TYPE_COUNT]{};
uint8_t _smallSceneryTypeToEntryMap[256]{};
uint8_t _largeSceneryTypeToEntryMap[256]{};
uint8_t _wallTypeToEntryMap[256]{};
uint8_t _pathTypeToEntryMap[24]{};
uint8_t _pathAdditionTypeToEntryMap[16]{};
uint8_t _sceneryThemeTypeToEntryMap[24]{};
// Research
uint8_t _researchRideEntryUsed[MAX_RIDE_OBJECTS]{};
uint8_t _researchRideTypeUsed[RCT1_RIDE_TYPE_COUNT]{};
// Scenario repository - used for determining scenario name
IScenarioRepository* _scenarioRepository = GetScenarioRepository();
public:
ParkLoadResult Load(const utf8* path) override
{
const utf8* extension = Path::GetExtension(path);
if (String::Equals(extension, ".sc4", true))
{
return LoadScenario(path);
}
else if (String::Equals(extension, ".sv4", true))
{
return LoadSavedGame(path);
}
else
{
throw std::runtime_error("Invalid RCT1 park extension.");
}
}
ParkLoadResult LoadSavedGame(const utf8* path, bool skipObjectCheck = false) override
{
auto fs = FileStream(path, FILE_MODE_OPEN);
auto result = LoadFromStream(&fs, false, skipObjectCheck, path);
return result;
}
ParkLoadResult LoadScenario(const utf8* path, bool skipObjectCheck = false) override
{
auto fs = FileStream(path, FILE_MODE_OPEN);
auto result = LoadFromStream(&fs, true, skipObjectCheck, path);
return result;
}
ParkLoadResult LoadFromStream(
IStream* stream, bool isScenario, [[maybe_unused]] bool skipObjectCheck, const utf8* path) override
{
_s4 = *ReadAndDecodeS4(stream, isScenario);
_s4Path = path;
// Only determine what objects we required to import this saved game
InitialiseEntryMaps();
CreateAvailableObjectMappings();
return ParkLoadResult(GetRequiredObjects());
}
void Import() override
{
Initialise();
CreateAvailableObjectMappings();
LoadObjects();
ImportRides();
ImportRideMeasurements();
ImportSprites();
ImportTileElements();
ImportMapAnimations();
ImportPeepSpawns();
ImportFinance();
ImportResearch();
ImportParkName();
ImportParkFlags();
ImportClimate();
ImportScenarioNameDetails();
ImportScenarioObjective();
ImportSavedView();
FixLandOwnership();
CountBlockSections();
determine_ride_entrance_and_exit_locations();
// Importing the strings is done later on, although that approach needs looking at.
// game_convert_strings_to_utf8();
game_convert_news_items_to_utf8();
map_count_remaining_land_rights();
}
bool GetDetails(scenario_index_entry* dst) override
{
*dst = {};
source_desc desc;
// If no entry is found, this is a custom scenario.
bool isOfficial = ScenarioSources::TryGetById(_s4.scenario_slot_index, &desc);
dst->category = desc.category;
dst->source_game = desc.source;
dst->source_index = desc.index;
dst->sc_id = desc.id;
dst->objective_type = _s4.scenario_objective_type;
dst->objective_arg_1 = _s4.scenario_objective_years;
// RCT1 used another way of calculating park value.
if (_s4.scenario_objective_type == OBJECTIVE_PARK_VALUE_BY)
dst->objective_arg_2 = CorrectRCT1ParkValue(_s4.scenario_objective_currency);
else
dst->objective_arg_2 = _s4.scenario_objective_currency;
dst->objective_arg_3 = _s4.scenario_objective_num_guests;
auto name = rct2_to_utf8(_s4.scenario_name, RCT2_LANGUAGE_ID_ENGLISH_UK);
std::string details;
// TryGetById won't set this property if the scenario is not recognised,
// but localisation needs it.
if (!isOfficial)
{
desc.title = name.c_str();
}
String::Set(dst->internal_name, sizeof(dst->internal_name), desc.title);
rct_string_id localisedStringIds[3];
if (language_get_localised_scenario_strings(desc.title, localisedStringIds))
{
if (localisedStringIds[0] != STR_NONE)
{
name = String::ToStd(language_get_string(localisedStringIds[0]));
}
if (localisedStringIds[2] != STR_NONE)
{
details = String::ToStd(language_get_string(localisedStringIds[2]));
}
}
String::Set(dst->name, sizeof(dst->name), name.c_str());
String::Set(dst->details, sizeof(dst->details), details.c_str());
return true;
}
int32_t CorrectRCT1ParkValue(money32 oldParkValue)
{
if (oldParkValue == MONEY32_UNDEFINED)
{
return MONEY32_UNDEFINED;
}
if (_parkValueConversionFactor == 0)
{
if (_s4.park_value != 0)
{
// Use the ratio between the old and new park value to calcute the ratio to
// use for the park value history and the goal.
auto& park = GetContext()->GetGameState()->GetPark();
_parkValueConversionFactor = (park.CalculateParkValue() * 10) / _s4.park_value;
}
else
{
// In new games, the park value isn't set.
_parkValueConversionFactor = 100;
}
}
return (oldParkValue * _parkValueConversionFactor) / 10;
}
private:
std::unique_ptr<rct1_s4> ReadAndDecodeS4(IStream* stream, bool isScenario)
{
auto s4 = std::make_unique<rct1_s4>();
size_t dataSize = stream->GetLength() - stream->GetPosition();
auto deleter_lambda = [dataSize](uint8_t* ptr) { Memory::FreeArray(ptr, dataSize); };
auto data = std::unique_ptr<uint8_t, decltype(deleter_lambda)>(stream->ReadArray<uint8_t>(dataSize), deleter_lambda);
auto decodedData = std::unique_ptr<uint8_t, decltype(&Memory::Free<uint8_t>)>(
Memory::Allocate<uint8_t>(sizeof(rct1_s4)), &Memory::Free<uint8_t>);
size_t decodedSize;
int32_t fileType = sawyercoding_detect_file_type(data.get(), dataSize);
if (isScenario && (fileType & FILE_VERSION_MASK) != FILE_VERSION_RCT1)
{
decodedSize = sawyercoding_decode_sc4(data.get(), decodedData.get(), dataSize, sizeof(rct1_s4));
}
else
{
decodedSize = sawyercoding_decode_sv4(data.get(), decodedData.get(), dataSize, sizeof(rct1_s4));
}
if (decodedSize == sizeof(rct1_s4))
{
std::memcpy(s4.get(), decodedData.get(), sizeof(rct1_s4));
return s4;
}
else
{
throw std::runtime_error("Unable to decode park.");
}
}
void Initialise()
{
_gameVersion = sawyercoding_detect_rct1_version(_s4.game_version) & FILE_VERSION_MASK;
// Avoid reusing the value used for last import
_parkValueConversionFactor = 0;
InitialiseEntryMaps();
uint16_t mapSize = _s4.map_size == 0 ? 128 : _s4.map_size;
String::Set(gScenarioFileName, sizeof(gScenarioFileName), GetRCT1ScenarioName().c_str());
// Do map initialisation, same kind of stuff done when loading scenario editor
auto context = OpenRCT2::GetContext();
context->GetObjectManager()->UnloadAll();
context->GetGameState()->InitAll(mapSize);
gS6Info.editor_step = EDITOR_STEP_OBJECT_SELECTION;
gParkFlags |= PARK_FLAGS_SHOW_REAL_GUEST_NAMES;
gS6Info.category = SCENARIO_CATEGORY_OTHER;
}
std::string GetRCT1ScenarioName()
{
const scenario_index_entry* scenarioEntry = _scenarioRepository->GetByInternalName(_s4.scenario_name);
if (scenarioEntry == nullptr)
{
return "";
}
else
{
return path_get_filename(scenarioEntry->path);
}
}
void InitialiseEntryMaps()
{
std::fill(std::begin(_rideTypeToRideEntryMap), std::end(_rideTypeToRideEntryMap), 255);
std::fill(std::begin(_vehicleTypeToRideEntryMap), std::end(_vehicleTypeToRideEntryMap), 255);
std::fill(std::begin(_smallSceneryTypeToEntryMap), std::end(_smallSceneryTypeToEntryMap), 255);
std::fill(std::begin(_largeSceneryTypeToEntryMap), std::end(_largeSceneryTypeToEntryMap), 255);
std::fill(std::begin(_wallTypeToEntryMap), std::end(_wallTypeToEntryMap), 255);
std::fill(std::begin(_pathTypeToEntryMap), std::end(_pathTypeToEntryMap), 255);
std::fill(std::begin(_pathAdditionTypeToEntryMap), std::end(_pathAdditionTypeToEntryMap), 255);
std::fill(std::begin(_sceneryThemeTypeToEntryMap), std::end(_sceneryThemeTypeToEntryMap), 255);
}
/**
* Scans the map and research list for all the object types used and builds lists and
* lookup tables for converting from hard coded RCT1 object types to dynamic object entries.
*/
void CreateAvailableObjectMappings()
{
AddDefaultEntries();
AddAvailableEntriesFromResearchList();
AddAvailableEntriesFromMap();
AddAvailableEntriesFromRides();
AddAvailableEntriesFromSceneryGroups();
AddEntryForWater();
}
void AddDefaultEntries()
{
// Add default scenery groups
_sceneryGroupEntries.AddRange({
"SCGTREES",
"SCGSHRUB",
"SCGGARDN",
"SCGFENCE",
"SCGWALLS",
"SCGPATHX",
});
// Add default footpaths
_pathEntries.AddRange({
"TARMAC ",
"TARMACG ",
"TARMACB ",
"PATHCRZY",
"PATHSPCE",
"PATHDIRT",
"PATHASH ",
"ROAD ",
});
}
void AddAvailableEntriesFromResearchList()
{
size_t researchListCount;
const rct1_research_item* researchList = GetResearchList(&researchListCount);
for (size_t i = 0; i < researchListCount; i++)
{
const rct1_research_item* researchItem = &researchList[i];
if (researchItem->flags == RCT1_RESEARCH_FLAGS_SEPARATOR)
{
if (researchItem->item == RCT1_RESEARCH_END)
{
break;
}
if (researchItem->item == RCT1_RESEARCH_END_AVAILABLE || researchItem->item == RCT1_RESEARCH_END_RESEARCHABLE)
{
continue;
}
}
switch (researchItem->type)
{
case RCT1_RESEARCH_TYPE_THEME:
AddEntriesForSceneryTheme(researchItem->item);
break;
case RCT1_RESEARCH_TYPE_RIDE:
{
uint8_t rideType = researchItem->item;
// Add all vehicles for this ride type
uint32_t numVehicles = 0;
for (size_t j = 0; j < researchListCount; j++)
{
const rct1_research_item* researchItem2 = &researchList[j];
if (researchItem2->flags == RCT1_RESEARCH_FLAGS_SEPARATOR)
{
if (researchItem2->item == RCT1_RESEARCH_END_RESEARCHABLE
|| researchItem2->item == RCT1_RESEARCH_END_AVAILABLE)
{
continue;
}
else if (researchItem2->item == RCT1_RESEARCH_END)
{
break;
}
}
if (researchItem2->type == RCT1_RESEARCH_TYPE_VEHICLE && researchItem2->related_ride == rideType)
{
AddEntryForVehicleType(rideType, researchItem2->item);
numVehicles++;
}
}
// If no vehicles found so just add the default for this ride
if (numVehicles == 0)
{
AddEntryForRideType(rideType);
}
break;
}
}
}
}
void AddAvailableEntriesFromMap()
{
size_t maxTiles = 128 * 128;
size_t tileIndex = 0;
rct_tile_element* tileElement = _s4.tile_elements;
while (tileIndex < maxTiles)
{
switch (tileElement->GetType())
{
case TILE_ELEMENT_TYPE_PATH:
{
uint8_t pathType = GetPathType(tileElement);
uint8_t pathAdditionsType = tileElement->properties.path.additions & 0x0F;
AddEntryForPath(pathType);
AddEntryForPathAddition(pathAdditionsType);
break;
}
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
AddEntryForSmallScenery(tileElement->properties.scenery.type);
break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
AddEntryForLargeScenery(scenery_large_get_type(tileElement));
break;
case TILE_ELEMENT_TYPE_WALL:
{
for (int32_t edge = 0; edge < 4; edge++)
{
int32_t type = GetWallType(tileElement, edge);
if (type != -1)
{
AddEntryForWall(type);
}
}
break;
}
}
if ((tileElement++)->IsLastForTile())
{
tileIndex++;
}
}
}
void AddAvailableEntriesFromRides()
{
for (size_t i = 0; i < Util::CountOf(_s4.rides); i++)
{
rct1_ride* ride = &_s4.rides[i];
if (ride->type != RCT1_RIDE_TYPE_NULL)
{
if (RCT1::RideTypeUsesVehicles(ride->type))
AddEntryForVehicleType(ride->type, ride->vehicle_type);
else
AddEntryForRideType(ride->type);
}
}
}
void AddAvailableEntriesFromSceneryGroups()
{
for (int32_t sceneryTheme = 0; sceneryTheme <= RCT1_SCENERY_THEME_PAGODA; sceneryTheme++)
{
if (sceneryTheme != 0 && _sceneryThemeTypeToEntryMap[sceneryTheme] == 255)
continue;
std::vector<const char*> objects = RCT1::GetSceneryObjects(sceneryTheme);
for (const char* objectName : objects)
{
auto objectRepository = OpenRCT2::GetContext()->GetObjectRepository();
auto foundObject = objectRepository->FindObject(objectName);
if (foundObject != nullptr)
{
uint8_t objectType = object_entry_get_type(&foundObject->ObjectEntry);
switch (objectType)
{
case OBJECT_TYPE_SMALL_SCENERY:
case OBJECT_TYPE_LARGE_SCENERY:
case OBJECT_TYPE_WALLS:
case OBJECT_TYPE_PATHS:
case OBJECT_TYPE_PATH_BITS:
EntryList* entries = GetEntryList(objectType);
// Check if there are spare entries available
size_t maxEntries = (size_t)object_entry_group_counts[objectType];
if (entries != nullptr && entries->GetCount() < maxEntries)
{
entries->GetOrAddEntry(objectName);
}
break;
}
}
}
}
}
void AddEntryForWater()
{
const char* entryName;
if (_s4.game_version < FILE_VERSION_RCT1_LL)
{
entryName = RCT1::GetWaterObject(RCT1_WATER_CYAN);
}
else
{
entryName = RCT1::GetWaterObject(_s4.water_colour);
}
_waterEntry.GetOrAddEntry(entryName);
}
void AddEntryForRideType(uint8_t rideType)
{
assert(rideType < Util::CountOf(_rideTypeToRideEntryMap));
if (_rideTypeToRideEntryMap[rideType] == 255)
{
const char* entryName = RCT1::GetRideTypeObject(rideType);
size_t entryIndex = _rideEntries.GetOrAddEntry(entryName);
_rideTypeToRideEntryMap[rideType] = (uint8_t)entryIndex;
}
}
void AddEntryForVehicleType(uint8_t rideType, uint8_t vehicleType)
{
assert(vehicleType < Util::CountOf(_vehicleTypeToRideEntryMap));
if (_vehicleTypeToRideEntryMap[vehicleType] == 255)
{
const char* entryName = RCT1::GetVehicleObject(vehicleType);
size_t entryIndex = _rideEntries.GetOrAddEntry(entryName);
_vehicleTypeToRideEntryMap[vehicleType] = (uint8_t)entryIndex;
_rideTypeToRideEntryMap[rideType] = (uint8_t)entryIndex;
}
}
void AddEntryForSmallScenery(uint8_t smallSceneryType)
{
assert(smallSceneryType < Util::CountOf(_smallSceneryTypeToEntryMap));
if (_smallSceneryTypeToEntryMap[smallSceneryType] == 255)
{
const char* entryName = RCT1::GetSmallSceneryObject(smallSceneryType);
size_t entryIndex = _smallSceneryEntries.GetOrAddEntry(entryName);
_smallSceneryTypeToEntryMap[smallSceneryType] = (uint8_t)entryIndex;
}
}
void AddEntryForLargeScenery(uint8_t largeSceneryType)
{
assert(largeSceneryType < Util::CountOf(_largeSceneryTypeToEntryMap));
if (_largeSceneryTypeToEntryMap[largeSceneryType] == 255)
{
const char* entryName = RCT1::GetLargeSceneryObject(largeSceneryType);
size_t entryIndex = _largeSceneryEntries.GetOrAddEntry(entryName);
_largeSceneryTypeToEntryMap[largeSceneryType] = (uint8_t)entryIndex;
}
}
void AddEntryForWall(uint8_t wallType)
{
assert(wallType < Util::CountOf(_wallTypeToEntryMap));
if (_wallTypeToEntryMap[wallType] == 255)
{
const char* entryName = RCT1::GetWallObject(wallType);
size_t entryIndex = _wallEntries.GetOrAddEntry(entryName);
_wallTypeToEntryMap[wallType] = (uint8_t)entryIndex;
}
}
void AddEntryForPath(uint8_t pathType)
{
assert(pathType < Util::CountOf(_pathTypeToEntryMap));
if (_pathTypeToEntryMap[pathType] == 255)
{
const char* entryName = RCT1::GetPathObject(pathType);
size_t entryIndex = _pathEntries.GetOrAddEntry(entryName);
_pathTypeToEntryMap[pathType] = (uint8_t)entryIndex;
}
}
void AddEntryForPathAddition(uint8_t pathAdditionType)
{
if (pathAdditionType == RCT1_PATH_ADDITION_NONE)
return;
if (_pathAdditionTypeToEntryMap[pathAdditionType] == 255)
{
uint8_t normalisedPathAdditionType = RCT1::NormalisePathAddition(pathAdditionType);
if (_pathAdditionTypeToEntryMap[normalisedPathAdditionType] == 255)
{
const char* entryName = RCT1::GetPathAddtionObject(normalisedPathAdditionType);
size_t entryIndex = _pathAdditionEntries.GetOrAddEntry(entryName);
_pathAdditionTypeToEntryMap[normalisedPathAdditionType] = (uint8_t)entryIndex;
}
_pathAdditionTypeToEntryMap[pathAdditionType] = _pathAdditionTypeToEntryMap[normalisedPathAdditionType];
}
}
void AddEntriesForSceneryTheme(uint8_t sceneryThemeType)
{
if (sceneryThemeType == RCT1_SCENERY_THEME_GENERAL || sceneryThemeType == RCT1_SCENERY_THEME_JUMPING_FOUNTAINS
|| sceneryThemeType == RCT1_SCENERY_THEME_GARDEN_CLOCK)
{
_sceneryThemeTypeToEntryMap[sceneryThemeType] = 254;
}
else
{
const char* entryName = RCT1::GetSceneryGroupObject(sceneryThemeType);
if (_sceneryGroupEntries.GetCount() >= MAX_SCENERY_GROUP_OBJECTS)
{
Console::WriteLine("Warning: More than %d (max scenery groups) in RCT1 park.", MAX_SCENERY_GROUP_OBJECTS);
Console::WriteLine(" [%s] scenery group not added.", entryName);
}
else
{
size_t entryIndex = _sceneryGroupEntries.GetOrAddEntry(entryName);
_sceneryThemeTypeToEntryMap[sceneryThemeType] = (uint8_t)entryIndex;
}
}
}
void ImportRides()
{
for (int32_t i = 0; i < RCT12_MAX_RIDES_IN_PARK; i++)
{
if (_s4.rides[i].type != RIDE_TYPE_NULL)
{
ImportRide(get_ride(i), &_s4.rides[i]);
}
}
}
void ImportRide(Ride* dst, rct1_ride* src)
{
memset(dst, 0, sizeof(Ride));
// This is a peculiarity of this exact version number, which only Heide-Park seems to use.
if (_s4.game_version == 110018 && src->type == RCT1_RIDE_TYPE_INVERTED_ROLLER_COASTER)
{
dst->type = RIDE_TYPE_COMPACT_INVERTED_COASTER;
}
else
{
dst->type = RCT1::GetRideType(src->type);
}
if (RCT1::RideTypeUsesVehicles(src->type))
{
dst->subtype = _vehicleTypeToRideEntryMap[src->vehicle_type];
}
else
{
dst->subtype = _rideTypeToRideEntryMap[src->type];
}
rct_ride_entry* rideEntry = get_ride_entry(dst->subtype);
// This can happen with hacked parks
if (rideEntry == nullptr)
{
log_warning("Discarding ride with invalid ride entry");
dst->type = RIDE_TYPE_NULL;
return;
}
// Ride name
dst->name = 0;
if (is_user_string_id(src->name))
{
std::string rideName = GetUserString(src->name);
if (!rideName.empty())
{
rct_string_id rideNameStringId = user_string_allocate(USER_STRING_HIGH_ID_NUMBER, rideName.c_str());
if (rideNameStringId != 0)
{
dst->name = rideNameStringId;
}
}
}
if (dst->name == 0)
{
ride_set_name_to_default(dst, rideEntry);
}
dst->status = src->status;
// Flags
dst->lifecycle_flags = src->lifecycle_flags;
// These flags were not in the base game
if (_gameVersion == FILE_VERSION_RCT1)
{
dst->lifecycle_flags &= ~RIDE_LIFECYCLE_MUSIC;
dst->lifecycle_flags &= ~RIDE_LIFECYCLE_INDESTRUCTIBLE;
dst->lifecycle_flags &= ~RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK;
}
// Station
dst->overall_view = src->overall_view;
for (int32_t i = 0; i < RCT12_MAX_STATIONS_PER_RIDE; i++)
{
dst->station_starts[i] = src->station_starts[i];
dst->station_heights[i] = src->station_height[i] / 2;
dst->station_length[i] = src->station_length[i];
dst->station_depart[i] = src->station_light[i];
dst->train_at_station[i] = src->station_depart[i];
// Direction is fixed later.
if (src->entrance[i].xy == RCT_XY8_UNDEFINED)
ride_clear_entrance_location(dst, i);
else
ride_set_entrance_location(dst, i, { src->entrance[i].x, src->entrance[i].y, src->station_height[i] / 2, 0 });
if (src->exit[i].xy == RCT_XY8_UNDEFINED)
ride_clear_exit_location(dst, i);
else
ride_set_exit_location(dst, i, { src->exit[i].x, src->exit[i].y, src->station_height[i] / 2, 0 });
dst->queue_time[i] = src->queue_time[i];
dst->last_peep_in_queue[i] = src->last_peep_in_queue[i];
dst->queue_length[i] = src->num_peeps_in_queue[i];
dst->time[i] = src->time[i];
dst->length[i] = src->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 = RCT12_MAX_STATIONS_PER_RIDE; i < MAX_STATIONS; i++)
{
dst->station_starts[i].xy = RCT_XY8_UNDEFINED;
dst->train_at_station[i] = 255;
ride_clear_entrance_location(dst, i);
ride_clear_exit_location(dst, i);
dst->last_peep_in_queue[i] = SPRITE_INDEX_NULL;
}
dst->num_stations = src->num_stations;
// Vehicle links (indexes converted later)
for (int32_t i = 0; i < RCT1_MAX_TRAINS_PER_RIDE; i++)
{
dst->vehicles[i] = src->vehicles[i];
}
for (int32_t i = RCT1_MAX_TRAINS_PER_RIDE; i < MAX_VEHICLES_PER_RIDE; i++)
{
dst->vehicles[i] = SPRITE_INDEX_NULL;
}
dst->num_vehicles = src->num_trains;
dst->num_cars_per_train = src->num_cars_per_train + rideEntry->zero_cars;
dst->proposed_num_vehicles = src->num_trains;
dst->max_trains = src->max_trains;
dst->proposed_num_cars_per_train = src->num_cars_per_train + rideEntry->zero_cars;
dst->special_track_elements = src->special_track_elements;
dst->num_sheltered_sections = src->num_sheltered_sections;
dst->sheltered_length = src->sheltered_length;
// Operation
dst->depart_flags = src->depart_flags;
dst->min_waiting_time = src->min_waiting_time;
dst->max_waiting_time = src->max_waiting_time;
dst->operation_option = src->operation_option;
dst->num_circuits = 1;
dst->min_max_cars_per_train = (rideEntry->min_cars_in_train << 4) | rideEntry->max_cars_in_train;
// RCT1 used 5mph / 8 km/h for every lift hill
dst->lift_hill_speed = 5;
if (_gameVersion == FILE_VERSION_RCT1)
{
// Original RCT had no music settings, take default style
dst->music = RideData4[dst->type].default_music;
// Only merry-go-round and dodgems had music and used
// the same flag as synchronise stations for the option to enable it
if (src->type == RCT1_RIDE_TYPE_MERRY_GO_ROUND || src->type == RCT1_RIDE_TYPE_DODGEMS)
{
if (src->depart_flags & RCT1_RIDE_DEPART_PLAY_MUSIC)
{
dst->depart_flags &= ~RCT1_RIDE_DEPART_PLAY_MUSIC;
dst->lifecycle_flags |= RIDE_LIFECYCLE_MUSIC;
}
}
}
else
{
dst->music = src->music;
}
if (src->operating_mode == RCT1_RIDE_MODE_POWERED_LAUNCH)
{
// Launched rides never passed through the station in RCT1.
dst->mode = RIDE_MODE_POWERED_LAUNCH;
}
else
{
dst->mode = src->operating_mode;
}
SetRideColourScheme(dst, src);
// Maintenance
dst->build_date = src->build_date;
dst->inspection_interval = src->inspection_interval;
dst->last_inspection = src->last_inspection;
dst->reliability = src->reliability;
dst->unreliability_factor = src->unreliability_factor;
dst->downtime = src->downtime;
dst->breakdown_reason = src->breakdown_reason;
dst->mechanic_status = src->mechanic_status;
dst->mechanic = src->mechanic;
dst->breakdown_reason_pending = src->breakdown_reason_pending;
dst->inspection_station = src->inspection_station;
dst->broken_car = src->broken_car;
dst->broken_vehicle = src->broken_vehicle;
// Measurement data
dst->excitement = src->excitement;
dst->intensity = src->intensity;
dst->nausea = src->nausea;
dst->max_speed = src->max_speed;
dst->average_speed = src->average_speed;
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_lateral_g = src->previous_lateral_g;
dst->previous_vertical_g = src->previous_vertical_g;
dst->turn_count_banked = src->turn_count_banked;
dst->turn_count_default = src->turn_count_default;
dst->turn_count_sloped = src->turn_count_sloped;
dst->drops = src->num_drops;
dst->start_drop_height = src->start_drop_height / 2;
dst->highest_drop_height = src->highest_drop_height / 2;
dst->inversions = src->num_inversions;
dst->boat_hire_return_direction = src->boat_hire_return_direction;
dst->boat_hire_return_position = src->boat_hire_return_position;
dst->measurement_index = src->data_logging_index;
dst->chairlift_bullwheel_rotation = src->chairlift_bullwheel_rotation;
for (int i = 0; i < 2; i++)
{
dst->chairlift_bullwheel_location[i] = src->chairlift_bullwheel_location[i];
dst->chairlift_bullwheel_z[i] = src->chairlift_bullwheel_z[i] / 2;
}
dst->cur_test_track_z = src->cur_test_track_z / 2;
dst->cur_test_track_location = src->cur_test_track_location;
dst->testing_flags = src->testing_flags;
dst->current_test_segment = src->current_test_segment;
dst->current_test_station = 0xFF;
dst->average_speed_test_timeout = src->average_speed_test_timeout;
dst->slide_in_use = src->slide_in_use;
dst->slide_peep_t_shirt_colour = RCT1::GetColour(src->slide_peep_t_shirt_colour);
dst->spiral_slide_progress = src->spiral_slide_progress;
// Doubles as slide_peep
dst->maze_tiles = src->maze_tiles;
// Finance / customers
dst->upkeep_cost = src->upkeep_cost;
dst->price = src->price;
dst->income_per_hour = src->income_per_hour;
dst->total_customers = src->total_customers;
dst->profit = src->profit;
dst->total_profit = src->total_profit;
dst->value = src->value;
dst->satisfaction = src->satisfaction;
dst->satisfaction_time_out = src->satisfaction_time_out;
dst->satisfaction_next = src->satisfaction_next;
dst->popularity = src->popularity;
dst->popularity_next = src->popularity_next;
dst->popularity_time_out = src->popularity_time_out;
dst->num_riders = src->num_riders;
dst->music_tune_id = 255;
}
void SetRideColourScheme(Ride* dst, rct1_ride* src)
{
// Colours
dst->colour_scheme_type = src->colour_scheme;
if (_gameVersion == FILE_VERSION_RCT1)
{
dst->track_colour_main[0] = RCT1::GetColour(src->track_primary_colour);
dst->track_colour_additional[0] = RCT1::GetColour(src->track_secondary_colour);
dst->track_colour_supports[0] = RCT1::GetColour(src->track_support_colour);
// Balloons were always blue in the original RCT.
if (src->type == RCT1_RIDE_TYPE_BALLOON_STALL)
{
dst->track_colour_main[0] = COLOUR_LIGHT_BLUE;
}
else if (src->type == RCT1_RIDE_TYPE_RIVER_RAPIDS)
{
dst->track_colour_main[0] = COLOUR_WHITE;
}
}
else
{
for (int i = 0; i < RCT12_NUM_COLOUR_SCHEMES; i++)
{
dst->track_colour_main[i] = RCT1::GetColour(src->track_colour_main[i]);
dst->track_colour_additional[i] = RCT1::GetColour(src->track_colour_additional[i]);
dst->track_colour_supports[i] = RCT1::GetColour(src->track_colour_supports[i]);
}
// Entrance styles were introduced with AA. They correspond directly with those in RCT2.
dst->entrance_style = src->entrance_style;
}
if (_gameVersion < FILE_VERSION_RCT1_LL && dst->type == RIDE_TYPE_MERRY_GO_ROUND)
{
// The merry-go-round in pre-LL versions was always yellow with red
dst->vehicle_colours[0].body_colour = COLOUR_YELLOW;
dst->vehicle_colours[0].trim_colour = COLOUR_BRIGHT_RED;
}
else
{
for (int i = 0; i < RCT1_MAX_TRAINS_PER_RIDE; i++)
{
// RCT1 had no third colour
RCT1::RCT1VehicleColourSchemeCopyDescriptor colourSchemeCopyDescriptor = RCT1::GetColourSchemeCopyDescriptor(
src->vehicle_type);
if (colourSchemeCopyDescriptor.colour1 == COPY_COLOUR_1)
{
dst->vehicle_colours[i].body_colour = RCT1::GetColour(src->vehicle_colours[i].body);
}
else if (colourSchemeCopyDescriptor.colour1 == COPY_COLOUR_2)
{
dst->vehicle_colours[i].body_colour = RCT1::GetColour(src->vehicle_colours[i].trim);
}
else
{
dst->vehicle_colours[i].body_colour = colourSchemeCopyDescriptor.colour1;
}
if (colourSchemeCopyDescriptor.colour2 == COPY_COLOUR_1)
{
dst->vehicle_colours[i].trim_colour = RCT1::GetColour(src->vehicle_colours[i].body);
}
else if (colourSchemeCopyDescriptor.colour2 == COPY_COLOUR_2)
{
dst->vehicle_colours[i].trim_colour = RCT1::GetColour(src->vehicle_colours[i].trim);
}
else
{
dst->vehicle_colours[i].trim_colour = colourSchemeCopyDescriptor.colour2;
}
if (colourSchemeCopyDescriptor.colour3 == COPY_COLOUR_1)
{
dst->vehicle_colours_extended[i] = RCT1::GetColour(src->vehicle_colours[i].body);
}
else if (colourSchemeCopyDescriptor.colour3 == COPY_COLOUR_2)
{
dst->vehicle_colours_extended[i] = RCT1::GetColour(src->vehicle_colours[i].trim);
}
else
{
dst->vehicle_colours_extended[i] = colourSchemeCopyDescriptor.colour3;
}
}
}
// In RCT1 and AA, the maze was always hedges.
// LL has 4 types, like RCT2. For LL, only guard against invalid values.
if (dst->type == RIDE_TYPE_MAZE)
{
if (_gameVersion < FILE_VERSION_RCT1_LL || src->track_colour_supports[0] > 3)
dst->track_colour_supports[0] = MAZE_WALL_TYPE_HEDGE;
else
dst->track_colour_supports[0] = src->track_colour_supports[0];
}
}
void FixRideVehicleLinks(const uint16_t* spriteIndexMap)
{
uint8_t i;
Ride* ride;
FOR_ALL_RIDES (i, ride)
{
for (uint8_t j = 0; j < Util::CountOf(ride->vehicles); j++)
{
uint16_t originalIndex = ride->vehicles[j];
if (originalIndex != SPRITE_INDEX_NULL)
{
ride->vehicles[j] = spriteIndexMap[originalIndex];
}
}
}
}
void ImportRideMeasurements()
{
for (int32_t i = 0; i < MAX_RIDE_MEASUREMENTS; i++)
{
rct_ride_measurement* dst = get_ride_measurement(i);
rct_ride_measurement* src = &_s4.ride_measurements[i];
ImportRideMeasurement(dst, src);
}
}
void ImportRideMeasurement(rct_ride_measurement* dst, rct_ride_measurement* src)
{
*dst = *src;
for (int32_t i = 0; i < RIDE_MEASUREMENT_MAX_ITEMS; i++)
{
dst->velocity[i] /= 2;
dst->altitude[i] /= 2;
dst->vertical[i] /= 2;
dst->lateral[i] /= 2;
}
}
void ImportSprites()
{
ImportVehicles();
ImportPeeps();
ImportLitter();
ImportMiscSprites();
}
void ImportVehicles()
{
std::vector<rct_vehicle*> vehicles;
uint16_t spriteIndexMap[RCT1_MAX_SPRITES];
for (int i = 0; i < RCT1_MAX_SPRITES; i++)
{
spriteIndexMap[i] = SPRITE_INDEX_NULL;
if (_s4.sprites[i].unknown.sprite_identifier == SPRITE_IDENTIFIER_VEHICLE)
{
rct1_vehicle* srcVehicle = &_s4.sprites[i].vehicle;
if (srcVehicle->x != LOCATION_NULL)
{
rct_vehicle* vehicle = (rct_vehicle*)create_sprite(SPRITE_IDENTIFIER_VEHICLE);
spriteIndexMap[i] = vehicle->sprite_index;
vehicles.push_back(vehicle);
ImportVehicle(vehicle, srcVehicle);
// If vehicle is the first car on a train add to train list
if (!vehicle->is_child)
{
move_sprite_to_list((rct_sprite*)vehicle, SPRITE_LIST_TRAIN * 2);
}
}
}
}
for (auto vehicle : vehicles)
{
FixVehicleLinks(vehicle, spriteIndexMap);
}
FixRideVehicleLinks(spriteIndexMap);
}
void ImportVehicle(rct_vehicle* dst, rct1_vehicle* src)
{
Ride* ride = get_ride(src->ride);
uint8_t vehicleEntryIndex = RCT1::GetVehicleSubEntryIndex(src->vehicle_type);
dst->sprite_identifier = SPRITE_IDENTIFIER_VEHICLE;
dst->ride = src->ride;
dst->ride_subtype = ride->subtype;
dst->vehicle_type = vehicleEntryIndex;
dst->is_child = src->is_child;
dst->var_44 = src->var_44;
dst->remaining_distance = src->remaining_distance;
// Properties from vehicle entry
dst->sprite_width = src->sprite_width;
dst->sprite_height_negative = src->sprite_height_negative;
dst->sprite_height_positive = src->sprite_height_positive;
dst->sprite_direction = src->sprite_direction;
dst->sprite_left = src->sprite_left;
dst->sprite_top = src->sprite_top;
dst->sprite_right = src->sprite_right;
dst->sprite_bottom = src->sprite_bottom;
dst->mass = src->mass;
dst->num_seats = src->num_seats;
dst->speed = src->speed;
dst->powered_acceleration = src->powered_acceleration;
dst->brake_speed = src->brake_speed;
dst->velocity = src->velocity;
dst->acceleration = src->acceleration;
dst->swing_sprite = src->swing_sprite;
dst->swinging_car_var_0 = src->swinging_car_var_0;
dst->var_4E = src->var_4E;
dst->restraints_position = src->restraints_position;
dst->spin_sprite = src->spin_sprite;
dst->sound_vector_factor = src->sound_vector_factor;
dst->spin_speed = src->spin_speed;
dst->sound2_flags = src->sound2_flags;
dst->sound1_id = RCT12_SOUND_ID_NULL;
dst->sound2_id = RCT12_SOUND_ID_NULL;
dst->var_C0 = src->var_C0;
dst->var_C4 = src->var_C4;
dst->animation_frame = src->animation_frame;
dst->var_C8 = src->var_C8;
dst->var_CA = src->var_CA;
dst->var_CE = src->var_CE;
dst->var_D3 = src->var_D3;
dst->scream_sound_id = 255;
dst->vehicle_sprite_type = src->vehicle_sprite_type;
dst->bank_rotation = src->bank_rotation;
// Seat rotation was not in RCT1
dst->target_seat_rotation = 4;
dst->seat_rotation = 4;
// Vehicle links (indexes converted later)
dst->prev_vehicle_on_ride = src->prev_vehicle_on_ride;
dst->next_vehicle_on_ride = src->next_vehicle_on_ride;
dst->next_vehicle_on_train = src->next_vehicle_on_train;
// Guests (indexes converted later)
for (int i = 0; i < 32; i++)
{
uint16_t spriteIndex = src->peep[i];
dst->peep[i] = spriteIndex;
if (spriteIndex != SPRITE_INDEX_NULL)
{
dst->peep_tshirt_colours[i] = RCT1::GetColour(src->peep_tshirt_colours[i]);
}
}
dst->var_CD = src->var_CD;
dst->track_x = src->track_x;
dst->track_y = src->track_y;
dst->track_z = src->track_z;
dst->current_station = src->current_station;
dst->track_type = src->track_type;
dst->track_progress = src->track_progress;
dst->vertical_drop_countdown = src->vertical_drop_countdown;
dst->status = src->status;
dst->sub_state = src->sub_state;
dst->update_flags = src->update_flags;
SetVehicleColours(dst, src);
dst->mini_golf_current_animation = src->mini_golf_current_animation;
dst->mini_golf_flags = src->mini_golf_flags;
sprite_move(src->x, src->y, src->z, (rct_sprite*)dst);
invalidate_sprite_2((rct_sprite*)dst);
dst->num_peeps = src->num_peeps;
dst->next_free_seat = src->next_free_seat;
}
void SetVehicleColours(rct_vehicle* dst, rct1_vehicle* src)
{
rct1_ride* srcRide = &_s4.rides[src->ride];
uint8_t vehicleTypeIndex = srcRide->vehicle_type;
RCT1::RCT1VehicleColourSchemeCopyDescriptor colourSchemeCopyDescriptor = RCT1::GetColourSchemeCopyDescriptor(
vehicleTypeIndex);
// RCT1 had no third colour
if (colourSchemeCopyDescriptor.colour1 == COPY_COLOUR_1)
{
dst->colours.body_colour = RCT1::GetColour(src->colours.body_colour);
}
else if (colourSchemeCopyDescriptor.colour1 == COPY_COLOUR_2)
{
dst->colours.body_colour = RCT1::GetColour(src->colours.trim_colour);
}
else
{
dst->colours.body_colour = colourSchemeCopyDescriptor.colour1;
}
if (colourSchemeCopyDescriptor.colour2 == COPY_COLOUR_1)
{
dst->colours.trim_colour = RCT1::GetColour(src->colours.body_colour);
}
else if (colourSchemeCopyDescriptor.colour2 == COPY_COLOUR_2)
{
dst->colours.trim_colour = RCT1::GetColour(src->colours.trim_colour);
}
else
{
dst->colours.trim_colour = colourSchemeCopyDescriptor.colour2;
}
if (colourSchemeCopyDescriptor.colour3 == COPY_COLOUR_1)
{
dst->colours_extended = RCT1::GetColour(src->colours.body_colour);
}
else if (colourSchemeCopyDescriptor.colour3 == COPY_COLOUR_2)
{
dst->colours_extended = RCT1::GetColour(src->colours.trim_colour);
}
else
{
dst->colours_extended = colourSchemeCopyDescriptor.colour3;
}
}
void FixVehicleLinks(rct_vehicle* vehicle, const uint16_t* spriteIndexMap)
{
if (vehicle->prev_vehicle_on_ride != SPRITE_INDEX_NULL)
{
vehicle->prev_vehicle_on_ride = spriteIndexMap[vehicle->prev_vehicle_on_ride];
}
if (vehicle->next_vehicle_on_ride != SPRITE_INDEX_NULL)
{
vehicle->next_vehicle_on_ride = spriteIndexMap[vehicle->next_vehicle_on_ride];
}
if (vehicle->next_vehicle_on_train != SPRITE_INDEX_NULL)
{
vehicle->next_vehicle_on_train = spriteIndexMap[vehicle->next_vehicle_on_train];
}
}
void FixVehiclePeepLinks(rct_vehicle* vehicle, const uint16_t* spriteIndexMap)
{
for (auto& peep : vehicle->peep)
{
peep = MapSpriteIndex(peep, spriteIndexMap);
}
}
void ImportPeeps()
{
uint16_t spriteIndexMap[RCT1_MAX_SPRITES];
for (size_t i = 0; i < RCT1_MAX_SPRITES; i++)
{
spriteIndexMap[i] = SPRITE_INDEX_NULL;
if (_s4.sprites[i].unknown.sprite_identifier == SPRITE_IDENTIFIER_PEEP)
{
rct1_peep* srcPeep = &_s4.sprites[i].peep;
rct_peep* peep = (rct_peep*)create_sprite(SPRITE_IDENTIFIER_PEEP);
move_sprite_to_list((rct_sprite*)peep, SPRITE_LIST_PEEP * 2);
spriteIndexMap[i] = peep->sprite_index;
ImportPeep(peep, srcPeep);
}
}
for (size_t i = 0; i < MAX_SPRITES; i++)
{
rct_sprite* sprite = get_sprite(i);
if (sprite->unknown.sprite_identifier == SPRITE_IDENTIFIER_VEHICLE)
{
rct_vehicle* vehicle = (rct_vehicle*)sprite;
FixVehiclePeepLinks(vehicle, spriteIndexMap);
}
}
int i;
Ride* ride;
rct_peep* peep;
FOR_ALL_RIDES (i, ride)
{
FixRidePeepLinks(ride, spriteIndexMap);
}
FOR_ALL_GUESTS (i, peep)
{
FixPeepNextInQueue(peep, spriteIndexMap);
}
// Fix the news items in advance
for (i = 0; i < MAX_NEWS_ITEMS; i++)
{
rct12_news_item* newsItem = &_s4.messages[i];
if (newsItem->Type == NEWS_ITEM_PEEP || newsItem->Type == NEWS_ITEM_PEEP_ON_RIDE)
{
newsItem->Assoc = MapSpriteIndex(newsItem->Assoc, spriteIndexMap);
}
}
// The RCT2/OpenRCT2 structures are bigger than in RCT1, so set them to zero
std::fill(std::begin(gStaffModes), std::end(gStaffModes), 0);
std::fill(std::begin(gStaffPatrolAreas), std::end(gStaffPatrolAreas), 0);
std::copy(std::begin(_s4.staff_modes), std::end(_s4.staff_modes), gStaffModes);
FOR_ALL_STAFF (i, peep)
{
ImportStaffPatrolArea(peep);
}
// Only the individual patrol areas have been converted, so generate the combined patrol areas of each staff type
staff_update_greyed_patrol_areas();
}
void ImportPeep(rct_peep* dst, rct1_peep* src)
{
dst->sprite_identifier = SPRITE_IDENTIFIER_PEEP;
// Peep vs. staff (including which kind)
dst->sprite_type = RCT1::GetPeepSpriteType(src->sprite_type);
dst->action = src->action;
dst->special_sprite = src->special_sprite;
dst->next_action_sprite_type = src->next_action_sprite_type;
dst->action_sprite_image_offset = src->action_sprite_image_offset;
dst->no_action_frame_num = src->no_action_frame_num;
dst->action_sprite_type = src->action_sprite_type;
dst->action_frame = src->action_frame;
const rct_sprite_bounds* spriteBounds = g_peep_animation_entries[dst->sprite_type].sprite_bounds;
dst->sprite_width = spriteBounds[dst->action_sprite_type].sprite_width;
dst->sprite_height_negative = spriteBounds[dst->action_sprite_type].sprite_height_negative;
dst->sprite_height_positive = spriteBounds[dst->action_sprite_type].sprite_height_positive;
sprite_move(src->x, src->y, src->z, (rct_sprite*)dst);
invalidate_sprite_2((rct_sprite*)dst);
dst->sprite_direction = src->sprite_direction;
// Peep name
dst->name_string_idx = src->name_string_idx;
if (is_user_string_id(src->name_string_idx))
{
std::string peepName = GetUserString(src->name_string_idx);
if (!peepName.empty())
{
rct_string_id peepNameStringId = user_string_allocate(USER_STRING_HIGH_ID_NUMBER, peepName.c_str());
if (peepNameStringId != 0)
{
dst->name_string_idx = peepNameStringId;
}
}
}
dst->outside_of_park = src->outside_of_park;
dst->state = src->state;
dst->sub_state = src->sub_state;
dst->next_x = src->next_x;
dst->next_y = src->next_y;
dst->next_z = src->next_z / 2;
dst->next_flags = src->next_flags;
dst->var_37 = src->var_37;
dst->time_to_consume = src->time_to_consume;
dst->step_progress = src->step_progress;
dst->vandalism_seen = src->vandalism_seen;
dst->type = src->type;
dst->tshirt_colour = RCT1::GetColour(src->tshirt_colour);
dst->trousers_colour = RCT1::GetColour(src->trousers_colour);
dst->umbrella_colour = RCT1::GetColour(src->umbrella_colour);
dst->hat_colour = RCT1::GetColour(src->hat_colour);
// Balloons were always blue in RCT1 without AA/LL
if (_gameVersion == FILE_VERSION_RCT1)
{
dst->balloon_colour = COLOUR_LIGHT_BLUE;
}
else
{
dst->balloon_colour = RCT1::GetColour(src->balloon_colour);
}
dst->destination_x = src->destination_x;
dst->destination_y = src->destination_y;
dst->destination_tolerance = src->destination_tolerance;
dst->direction = src->direction;
dst->energy = src->energy;
dst->energy_target = src->energy_target;
dst->happiness = src->happiness;
dst->happiness_target = src->happiness_target;
dst->nausea = src->nausea;
dst->nausea_target = src->nausea_target;
dst->hunger = src->hunger;
dst->thirst = src->thirst;
dst->toilet = src->toilet;
dst->mass = src->mass;
dst->litter_count = src->litter_count;
dst->disgusting_count = src->disgusting_count;
dst->intensity = src->intensity;
dst->nausea_tolerance = src->nausea_tolerance;
dst->window_invalidate_flags = 0;
dst->current_ride = src->current_ride;
dst->current_ride_station = src->current_ride_station;
dst->current_train = src->current_train;
dst->current_car = src->current_car;
dst->current_seat = src->current_seat;
dst->time_on_ride = src->time_on_ride;
dst->days_in_queue = src->days_in_queue;
dst->interaction_ride_index = src->interaction_ride_index;
dst->id = src->id;
dst->cash_in_pocket = src->cash_in_pocket;
dst->cash_spent = src->cash_spent;
dst->time_in_park = src->time_in_park;
// This doubles as staff type
dst->no_of_rides = src->no_of_rides;
dst->no_of_drinks = src->no_of_drinks;
dst->no_of_food = src->no_of_food;
dst->no_of_souvenirs = src->no_of_souvenirs;
dst->paid_to_enter = src->paid_to_enter;
dst->paid_on_rides = src->paid_on_rides;
dst->paid_on_drink = src->paid_on_drink;
dst->paid_on_food = src->paid_on_food;
dst->paid_on_souvenirs = src->paid_on_souvenirs;
dst->voucher_arguments = src->voucher_arguments;
dst->voucher_type = src->voucher_type;
dst->surroundings_thought_timeout = src->surroundings_thought_timeout;
dst->angriness = src->angriness;
dst->time_lost = src->time_lost;
for (size_t i = 0; i < 32; i++)
{
dst->rides_been_on[i] = src->rides_been_on[i];
}
for (size_t i = 0; i < 16; i++)
{
dst->ride_types_been_on[i] = src->ride_types_been_on[i];
}
dst->photo1_ride_ref = src->photo1_ride_ref;
for (size_t i = 0; i < PEEP_MAX_THOUGHTS; i++)
{
dst->thoughts[i] = src->thoughts[i];
}
dst->previous_ride = src->previous_ride;
dst->previous_ride_time_out = src->previous_ride_time_out;
dst->path_check_optimisation = 0;
dst->guest_heading_to_ride_id = src->guest_heading_to_ride_id;
// Doubles as staff orders
dst->peep_is_lost_countdown = src->peep_is_lost_countdown;
// The ID is fixed later
dst->next_in_queue = src->next_in_queue;
dst->peep_flags = 0;
dst->pathfind_goal.x = 0xFF;
dst->pathfind_goal.y = 0xFF;
dst->pathfind_goal.z = 0xFF;
dst->pathfind_goal.direction = 0xFF;
// Guests' favourite ride was only saved in LL.
// Set it to N/A if the save comes from the original or AA.
if (_gameVersion == FILE_VERSION_RCT1_LL)
{
dst->favourite_ride = src->favourite_ride;
dst->favourite_ride_rating = src->favourite_ride_rating;
}
else
{
dst->favourite_ride = RIDE_ID_NULL;
dst->favourite_ride_rating = 0;
}
dst->item_standard_flags = src->item_standard_flags;
peep_update_name_sort(dst);
if (dst->type == PEEP_TYPE_GUEST)
{
if (dst->outside_of_park && dst->state != PEEP_STATE_LEAVING_PARK)
{
increment_guests_heading_for_park();
}
else
{
increment_guests_in_park();
}
}
}
void FixRidePeepLinks(Ride* ride, const uint16_t* spriteIndexMap)
{
for (auto& peep : ride->last_peep_in_queue)
{
peep = MapSpriteIndex(peep, spriteIndexMap);
}
ride->mechanic = MapSpriteIndex(ride->mechanic, spriteIndexMap);
if (ride->type == RIDE_TYPE_SPIRAL_SLIDE)
{
ride->slide_peep = MapSpriteIndex(ride->slide_peep, spriteIndexMap);
}
}
void FixPeepNextInQueue(rct_peep* peep, const uint16_t* spriteIndexMap)
{
peep->next_in_queue = MapSpriteIndex(peep->next_in_queue, spriteIndexMap);
}
void ImportStaffPatrolArea(rct_peep* staffmember)
{
// The patrol areas in RCT1 are encoded as follows, for coordinates x and y, separately for every staff member:
// - Chop off the 7 lowest bits of the x and y coordinates, which leaves 5 bits per coordinate.
// This step also "produces" the 4x4 patrol squares.
// - Append the two bitstrings to a 10-bit value like so: yyyyyxxxxx
// - Use this 10-bit value as an index into an 8-bit array. The array is sized such that every 4x4 square
// used for the patrols on the map has a bit in that array. If a bit is 1, that square is part of the patrol.
// The correct bit position in that array is found like this: yyyyyxx|xxx
// index in the array ----^ ^--- bit position in the 8-bit value
// We do the opposite in this function to recover the x and y values.
int32_t peepOffset = staffmember->staff_id * RCT12_PATROL_AREA_SIZE;
for (int32_t i = 0; i < RCT12_PATROL_AREA_SIZE; i++)
{
if (_s4.patrol_areas[peepOffset + i] == 0)
{
// No patrol for this area
continue;
}
// Loop over the bits of the uint8_t
for (int32_t j = 0; j < 8; j++)
{
int8_t bit = (_s4.patrol_areas[peepOffset + i] >> j) & 1;
if (bit == 0)
{
// No patrol for this area
continue;
}
// val contains the 5 highest bits of both the x and y coordinates
int32_t val = j | (i << 3);
int32_t x = val & 0x1F;
x <<= 7;
int32_t y = val & 0x3E0;
y <<= 2;
staff_set_patrol_area(staffmember->staff_id, x, y, true);
}
}
}
void ImportLitter()
{
for (auto& sprite : _s4.sprites)
{
if (sprite.unknown.sprite_identifier == SPRITE_IDENTIFIER_LITTER)
{
rct_litter* srcLitter = &sprite.litter;
rct_litter* litter = (rct_litter*)create_sprite(SPRITE_IDENTIFIER_LITTER);
move_sprite_to_list((rct_sprite*)litter, SPRITE_LIST_LITTER * 2);
litter->sprite_identifier = srcLitter->sprite_identifier;
litter->type = srcLitter->type;
litter->x = srcLitter->x;
litter->y = srcLitter->y;
litter->z = srcLitter->z;
litter->sprite_direction = srcLitter->sprite_direction;
litter->sprite_width = srcLitter->sprite_width;
litter->sprite_height_positive = srcLitter->sprite_height_positive;
litter->sprite_height_negative = srcLitter->sprite_height_negative;
sprite_move(srcLitter->x, srcLitter->y, srcLitter->z, (rct_sprite*)litter);
invalidate_sprite_2((rct_sprite*)litter);
}
}
}
void ImportMiscSprites()
{
for (auto& sprite : _s4.sprites)
{
if (sprite.unknown.sprite_identifier == SPRITE_IDENTIFIER_MISC)
{
rct1_unk_sprite* src = &sprite.unknown;
rct_unk_sprite* dst = (rct_unk_sprite*)create_sprite(SPRITE_IDENTIFIER_MISC);
move_sprite_to_list((rct_sprite*)dst, SPRITE_LIST_MISC * 2);
dst->sprite_identifier = src->sprite_identifier;
dst->misc_identifier = src->misc_identifier;
dst->flags = src->flags;
dst->sprite_direction = src->sprite_direction;
dst->sprite_width = src->sprite_width;
dst->sprite_height_negative = src->sprite_height_negative;
dst->sprite_height_positive = src->sprite_height_positive;
sprite_move(src->x, src->y, src->z, (rct_sprite*)dst);
switch (src->misc_identifier)
{
case SPRITE_MISC_STEAM_PARTICLE:
ImportSteamParticle((rct_steam_particle*)dst, (rct_steam_particle*)src);
break;
case SPRITE_MISC_MONEY_EFFECT:
ImportMoneyEffect((rct_money_effect*)dst, (rct_money_effect*)src);
break;
case SPRITE_MISC_CRASHED_VEHICLE_PARTICLE:
break;
case SPRITE_MISC_EXPLOSION_CLOUD:
break;
case SPRITE_MISC_CRASH_SPLASH:
break;
case SPRITE_MISC_EXPLOSION_FLARE:
break;
case SPRITE_MISC_JUMPING_FOUNTAIN_WATER:
ImportJumpingFountainWater((rct_jumping_fountain*)dst, (rct_jumping_fountain*)src);
break;
case SPRITE_MISC_BALLOON:
ImportBalloon((rct_balloon*)dst, (rct_balloon*)src);
break;
case SPRITE_MISC_DUCK:
ImportDuck((rct_duck*)dst, (rct_duck*)src);
break;
}
sprite_move(src->x, src->y, src->z, (rct_sprite*)dst);
invalidate_sprite_2((rct_sprite*)dst);
}
}
}
void ImportMoneyEffect(rct_money_effect* dst, rct_money_effect* src)
{
dst->move_delay = src->move_delay;
dst->num_movements = src->num_movements;
dst->value = src->value;
dst->offset_x = src->offset_x;
dst->wiggle = src->wiggle;
}
void ImportSteamParticle(rct_steam_particle* dst, rct_steam_particle* src)
{
dst->frame = src->frame;
}
void ImportJumpingFountainWater(rct_jumping_fountain* dst, rct_jumping_fountain* src)
{
dst->fountain_flags = src->fountain_flags;
dst->iteration = src->iteration;
dst->num_ticks_alive = src->num_ticks_alive;
dst->frame = src->frame;
}
void ImportBalloon(rct_balloon* dst, rct_balloon* src)
{
// Balloons were always blue in RCT1 without AA/LL
if (_gameVersion == FILE_VERSION_RCT1)
{
dst->colour = COLOUR_LIGHT_BLUE;
}
else
{
dst->colour = RCT1::GetColour(src->colour);
}
}
void ImportDuck(rct_duck* dst, rct_duck* src)
{
dst->frame = src->frame;
dst->state = src->state;
}
uint16_t MapSpriteIndex(uint16_t originalSpriteIndex, const uint16_t* spriteIndexMap)
{
uint16_t newSpriteIndex = SPRITE_INDEX_NULL;
if (originalSpriteIndex != SPRITE_INDEX_NULL)
{
if (originalSpriteIndex >= RCT1_MAX_SPRITES)
{
log_warning("Incorrect sprite index: %d", originalSpriteIndex);
}
else
{
newSpriteIndex = spriteIndexMap[originalSpriteIndex];
}
}
return newSpriteIndex;
}
void ImportPeepSpawns()
{
for (size_t i = 0; i < RCT12_MAX_PEEP_SPAWNS; i++)
{
gPeepSpawns[i] = { _s4.peep_spawn[i].x, _s4.peep_spawn[i].y, _s4.peep_spawn[i].z * 16,
_s4.peep_spawn[i].direction };
}
for (size_t i = RCT12_MAX_PEEP_SPAWNS; i < MAX_PEEP_SPAWNS; i++)
{
gPeepSpawns[i].x = PEEP_SPAWN_UNDEFINED;
}
}
void ImportMapAnimations()
{
// This is sketchy, ideally we should try to re-create them
rct_map_animation* s4Animations = _s4.map_animations;
for (size_t i = 0; i < RCT1_MAX_ANIMATED_OBJECTS; i++)
{
gAnimatedObjects[i] = s4Animations[i];
gAnimatedObjects[i].baseZ /= 2;
}
gNumMapAnimations = _s4.num_map_animations;
}
void ImportFinance()
{
gParkEntranceFee = _s4.park_entrance_fee;
gLandPrice = _s4.land_price;
gConstructionRightsPrice = _s4.construction_rights_price;
gCash = _s4.cash;
gBankLoan = _s4.loan;
gMaxBankLoan = _s4.max_loan;
// It's more like 1.33%, but we can only use integers. Can be fixed once we have our own save format.
gBankLoanInterestRate = 1;
gInitialCash = _s4.cash;
gCompanyValue = _s4.company_value;
gParkValue = CorrectRCT1ParkValue(_s4.park_value);
gCurrentProfit = _s4.profit;
for (size_t i = 0; i < RCT12_FINANCE_GRAPH_SIZE; i++)
{
gCashHistory[i] = _s4.cash_history[i];
gParkValueHistory[i] = CorrectRCT1ParkValue(_s4.park_value_history[i]);
gWeeklyProfitHistory[i] = _s4.weekly_profit_history[i];
}
for (size_t i = 0; i < RCT12_EXPENDITURE_TABLE_MONTH_COUNT; i++)
{
for (size_t j = 0; j < RCT12_EXPENDITURE_TYPE_COUNT; j++)
{
gExpenditureTable[i][j] = _s4.expenditure[i][j];
}
}
gCurrentExpenditure = _s4.total_expenditure;
gScenarioCompletedCompanyValue = _s4.completed_company_value;
gTotalAdmissions = _s4.num_admissions;
gTotalIncomeFromAdmissions = _s4.admission_total_income;
// TODO marketing campaigns not working
for (size_t i = 0; i < 6; i++)
{
gMarketingCampaignDaysLeft[i] = _s4.marketing_status[i];
gMarketingCampaignRideIndex[i] = _s4.marketing_assoc[i];
}
}
void LoadObjects()
{
auto objectManager = OpenRCT2::GetContext()->GetObjectManager();
objectManager->LoadDefaultObjects();
LoadObjects(OBJECT_TYPE_RIDE, _rideEntries);
LoadObjects(OBJECT_TYPE_SMALL_SCENERY, _smallSceneryEntries);
LoadObjects(OBJECT_TYPE_LARGE_SCENERY, _largeSceneryEntries);
LoadObjects(OBJECT_TYPE_WALLS, _wallEntries);
LoadObjects(OBJECT_TYPE_PATHS, _pathEntries);
LoadObjects(OBJECT_TYPE_PATH_BITS, _pathAdditionEntries);
LoadObjects(OBJECT_TYPE_SCENERY_GROUP, _sceneryGroupEntries);
LoadObjects(
OBJECT_TYPE_BANNERS,
std::vector<const char*>({
"BN1 ",
"BN2 ",
"BN3 ",
"BN4 ",
"BN5 ",
"BN6 ",
"BN7 ",
"BN8 ",
"BN9 ",
}));
LoadObjects(OBJECT_TYPE_PARK_ENTRANCE, std::vector<const char*>({ "PKENT1 " }));
LoadObjects(OBJECT_TYPE_WATER, _waterEntry);
}
void LoadObjects(uint8_t objectType, const EntryList& entries)
{
LoadObjects(objectType, entries.GetEntries());
}
void LoadObjects(uint8_t objectType, const std::vector<const char*>& entries)
{
auto objectManager = OpenRCT2::GetContext()->GetObjectManager();
uint32_t entryIndex = 0;
for (const char* objectName : entries)
{
rct_object_entry entry;
entry.flags = 0x00008000 + objectType;
std::copy_n(objectName, 8, entry.name);
entry.checksum = 0;
Object* object = objectManager->LoadObject(&entry);
if (object == nullptr && objectType != OBJECT_TYPE_SCENERY_GROUP)
{
log_error("Failed to load %s.", objectName);
throw std::runtime_error("Failed to load object.");
}
entryIndex++;
}
}
void AppendRequiredObjects(std::vector<rct_object_entry>& entries, uint8_t objectType, const EntryList& entryList)
{
AppendRequiredObjects(entries, objectType, entryList.GetEntries());
}
void AppendRequiredObjects(
std::vector<rct_object_entry>& entries, uint8_t objectType, const std::vector<const char*>& objectNames)
{
for (const auto objectName : objectNames)
{
rct_object_entry entry{};
entry.flags = ((OBJECT_SOURCE_RCT2 << 4) & 0xF0) | (objectType & 0x0F);
entry.SetName(objectName);
entries.push_back(entry);
}
}
std::vector<rct_object_entry> GetRequiredObjects()
{
std::vector<rct_object_entry> result;
AppendRequiredObjects(result, OBJECT_TYPE_RIDE, _rideEntries);
AppendRequiredObjects(result, OBJECT_TYPE_SMALL_SCENERY, _smallSceneryEntries);
AppendRequiredObjects(result, OBJECT_TYPE_LARGE_SCENERY, _largeSceneryEntries);
AppendRequiredObjects(result, OBJECT_TYPE_WALLS, _wallEntries);
AppendRequiredObjects(result, OBJECT_TYPE_PATHS, _pathEntries);
AppendRequiredObjects(result, OBJECT_TYPE_PATH_BITS, _pathAdditionEntries);
AppendRequiredObjects(result, OBJECT_TYPE_SCENERY_GROUP, _sceneryGroupEntries);
AppendRequiredObjects(
result, OBJECT_TYPE_BANNERS,
std::vector<const char*>({
"BN1 ",
"BN2 ",
"BN3 ",
"BN4 ",
"BN5 ",
"BN6 ",
"BN7 ",
"BN8 ",
"BN9 ",
}));
AppendRequiredObjects(result, OBJECT_TYPE_PARK_ENTRANCE, std::vector<const char*>({ "PKENT1 " }));
AppendRequiredObjects(result, OBJECT_TYPE_WATER, _waterEntry);
return result;
}
void GetInvalidObjects(
uint8_t objectType, const std::vector<const char*>& entries, std::vector<rct_object_entry>& missingObjects)
{
auto objectRepository = OpenRCT2::GetContext()->GetObjectRepository();
for (const char* objectName : entries)
{
rct_object_entry entry;
entry.flags = 0x00008000 + objectType;
std::copy_n(objectName, DAT_NAME_LENGTH, entry.name);
entry.checksum = 0;
const ObjectRepositoryItem* ori = objectRepository->FindObject(&entry);
if (ori == nullptr)
{
missingObjects.push_back(entry);
Console::Error::WriteLine("[%s] Object not found.", objectName);
}
else
{
Object* object = objectRepository->LoadObject(ori);
if (object == nullptr && objectType != OBJECT_TYPE_SCENERY_GROUP)
{
missingObjects.push_back(entry);
Console::Error::WriteLine("[%s] Object could not be loaded.", objectName);
}
delete object;
}
}
}
void ImportTileElements()
{
std::copy(std::begin(_s4.tile_elements), std::end(_s4.tile_elements), gTileElements);
ClearExtraTileEntries();
FixSceneryColours();
FixTileElementZ();
FixPaths();
FixWalls();
FixBanners();
FixTerrain();
FixEntrancePositions();
FixTileElementEntryTypes();
}
void ImportResearch()
{
// All available objects must be loaded before this method is called as it
// requires them to correctly insert objects into the research list
research_reset_items();
size_t researchListCount;
const rct1_research_item* researchList = GetResearchList(&researchListCount);
// Initialise the "seen" tables
std::fill(std::begin(_researchRideEntryUsed), std::end(_researchRideEntryUsed), 0);
std::fill(std::begin(_researchRideTypeUsed), std::end(_researchRideTypeUsed), 0);
// The first six scenery groups are always available
for (uint8_t i = 0; i < 6; i++)
{
research_insert_scenery_group_entry(i, true);
}
bool researched = true;
for (size_t i = 0; i < researchListCount; i++)
{
const rct1_research_item* researchItem = &researchList[i];
if (researchItem->flags == RCT1_RESEARCH_FLAGS_SEPARATOR)
{
if (researchItem->item == RCT1_RESEARCH_END_AVAILABLE)
{
researched = false;
continue;
}
else if (researchItem->item == RCT1_RESEARCH_END_RESEARCHABLE)
{
continue;
}
else if (researchItem->item == RCT1_RESEARCH_END)
{
break;
}
}
switch (researchItem->type)
{
case RCT1_RESEARCH_TYPE_THEME:
{
uint8_t rct1SceneryTheme = researchItem->item;
uint8_t sceneryGroupEntryIndex = _sceneryThemeTypeToEntryMap[rct1SceneryTheme];
if (sceneryGroupEntryIndex != 254 && sceneryGroupEntryIndex != 255)
{
research_insert_scenery_group_entry(sceneryGroupEntryIndex, researched);
}
break;
}
case RCT1_RESEARCH_TYPE_RIDE:
{
uint8_t rct1RideType = researchItem->item;
_researchRideTypeUsed[rct1RideType] = true;
// Add all vehicles for this ride type that are researched or before this research item
uint32_t numVehicles = 0;
for (size_t j = 0; j < researchListCount; j++)
{
const rct1_research_item* researchItem2 = &researchList[j];
if (researchItem2->flags == RCT1_RESEARCH_FLAGS_SEPARATOR
&& (researchItem2->item == RCT1_RESEARCH_END_RESEARCHABLE
|| researchItem2->item == RCT1_RESEARCH_END_AVAILABLE))
{
continue;
}
if (researchItem2->type == RCT1_RESEARCH_TYPE_VEHICLE && researchItem2->related_ride == rct1RideType)
{
// Only add the vehicles that were listed before this ride, otherwise we might
// change the research order
if (j < i)
{
InsertResearchVehicle(researchItem2, researched);
}
numVehicles++;
}
}
if (numVehicles == 0)
{
// No vehicles found so just add the default for this ride
uint8_t rideEntryIndex = _rideTypeToRideEntryMap[rct1RideType];
Guard::Assert(rideEntryIndex != RIDE_ENTRY_INDEX_NULL, "rideEntryIndex was RIDE_ENTRY_INDEX_NULL");
if (!_researchRideEntryUsed[rideEntryIndex])
{
_researchRideEntryUsed[rideEntryIndex] = true;
research_insert_ride_entry(rideEntryIndex, researched);
}
}
break;
}
case RCT1_RESEARCH_TYPE_VEHICLE:
// Only add vehicle if the related ride has been seen, this to make sure that vehicles
// are researched only after the ride has been researched
if (_researchRideTypeUsed[researchItem->related_ride])
{
InsertResearchVehicle(researchItem, researched);
}
break;
case RCT1_RESEARCH_TYPE_SPECIAL:
// Not supported
break;
}
}
// Research funding / priority
uint8_t activeResearchTypes = 0;
if (_s4.research_priority & RCT1_RESEARCH_CATEGORY_ROLLERCOASTERS)
{
activeResearchTypes |= (1 << RESEARCH_CATEGORY_ROLLERCOASTER);
}
if (_s4.research_priority & RCT1_RESEARCH_CATEGORY_THRILL_RIDES)
{
activeResearchTypes |= (1 << RESEARCH_CATEGORY_THRILL);
activeResearchTypes |= (1 << RESEARCH_CATEGORY_WATER);
}
if (_s4.research_priority & RCT1_RESEARCH_CATEGORY_GENTLE_TRANSPORT_RIDES)
{
activeResearchTypes |= (1 << RESEARCH_CATEGORY_GENTLE);
activeResearchTypes |= (1 << RESEARCH_CATEGORY_TRANSPORT);
}
if (_s4.research_priority & RCT1_RESEARCH_CATEGORY_SHOPS)
{
activeResearchTypes |= (1 << RESEARCH_CATEGORY_SHOP);
}
if (_s4.research_priority & RCT1_RESEARCH_CATEGORY_SCENERY_THEMEING)
{
activeResearchTypes |= (1 << RESEARCH_CATEGORY_SCENERY_GROUP);
}
gResearchPriorities = activeResearchTypes;
gResearchFundingLevel = _s4.research_level;
// This will mark items as researched/unresearched according to the research list.
// This needs to be called before importing progress, as it will reset it.
research_reset_current_item();
// Research history
gResearchProgress = _s4.research_progress;
gResearchProgressStage = _s4.research_progress_stage;
gResearchExpectedDay = _s4.next_research_expected_day;
gResearchExpectedMonth = _s4.next_research_expected_month;
ConvertResearchEntry(&gResearchNextItem, _s4.next_research_item, _s4.next_research_type);
if (gResearchNextItem.IsInventedEndMarker())
{
gResearchProgressStage = RESEARCH_STAGE_INITIAL_RESEARCH;
gResearchProgress = 0;
}
ConvertResearchEntry(&gResearchLastItem, _s4.last_research_item, _s4.last_research_type);
}
void InsertResearchVehicle(const rct1_research_item* researchItem, bool researched)
{
uint8_t vehicle = researchItem->item;
uint8_t rideEntryIndex = _vehicleTypeToRideEntryMap[vehicle];
if (!_researchRideEntryUsed[rideEntryIndex])
{
_researchRideEntryUsed[rideEntryIndex] = true;
research_insert_ride_entry(rideEntryIndex, researched);
}
}
void ImportParkName()
{
std::string parkName = std::string(_s4.scenario_name);
if (is_user_string_id((rct_string_id)_s4.park_name_string_index))
{
std::string userString = GetUserString(_s4.park_name_string_index);
if (!userString.empty())
{
parkName = userString;
}
}
rct_string_id stringId = user_string_allocate(USER_STRING_HIGH_ID_NUMBER, parkName.c_str());
if (stringId != 0)
{
gParkName = stringId;
gParkNameArgs = 0;
}
}
void ImportParkFlags()
{
// Date and srand
gScenarioTicks = _s4.ticks;
gScenarioSrand0 = _s4.random_a;
gScenarioSrand1 = _s4.random_b;
gDateMonthsElapsed = _s4.month;
gDateMonthTicks = _s4.day;
// Park rating
gParkRating = _s4.park_rating;
for (size_t i = 0; i < 32; i++)
{
gParkRatingHistory[i] = _s4.park_rating_history[i];
}
// Awards
for (int32_t i = 0; i < RCT12_MAX_AWARDS; i++)
{
rct12_award* src = &_s4.awards[i];
Award* dst = &gCurrentAwards[i];
dst->Time = src->time;
dst->Type = src->type;
}
// Number of guests history
for (size_t i = 0; i < 32; i++)
{
gGuestsInParkHistory[i] = _s4.guests_in_park_history[i];
}
// News items
for (size_t i = 0; i < RCT12_MAX_NEWS_ITEMS; i++)
{
const rct12_news_item* src = &_s4.messages[i];
NewsItem* dst = &gNewsItems[i];
dst->Type = src->Type;
dst->Flags = src->Flags;
dst->Ticks = src->Ticks;
dst->MonthYear = src->MonthYear;
dst->Day = src->Day;
std::copy(std::begin(src->Text), std::end(src->Text), dst->Text);
if (dst->Type == NEWS_ITEM_RESEARCH)
{
uint8_t researchItem = src->Assoc & 0x000000FF;
uint8_t researchType = (src->Assoc & 0x00FF0000) >> 16;
rct_research_item tmpResearchItem = {};
ConvertResearchEntry(&tmpResearchItem, researchItem, researchType);
dst->Assoc = (uint32_t)tmpResearchItem.rawValue;
}
else
{
dst->Assoc = src->Assoc;
}
}
// Initial guest status
gGuestInitialCash = _s4.guest_initial_cash;
gGuestInitialHunger = _s4.guest_initial_hunger;
gGuestInitialThirst = _s4.guest_initial_thirst;
gGuestInitialHappiness = _s4.guest_initial_happiness;
_guestGenerationProbability = _s4.guest_generation_probability;
// Staff colours
gStaffHandymanColour = RCT1::GetColour(_s4.handman_colour);
gStaffMechanicColour = RCT1::GetColour(_s4.mechanic_colour);
gStaffSecurityColour = RCT1::GetColour(_s4.security_guard_colour);
// Flags
gParkFlags = _s4.park_flags;
gParkFlags &= ~PARK_FLAGS_ANTI_CHEAT_DEPRECATED;
// Loopy Landscape parks can set a flag to lock the entry price to free.
// If this flag is not set, the player can ask money for both rides and entry.
if (!(_s4.park_flags & RCT1_PARK_FLAGS_PARK_ENTRY_LOCKED_AT_FREE))
{
gParkFlags |= PARK_FLAGS_UNLOCK_ALL_PRICES;
}
// RCT2 uses two flags for no money (due to the scenario editor). RCT1 used only one.
// Copy its value to make no money scenarios such as Arid Heights work properly.
if (_s4.park_flags & RCT1_PARK_FLAGS_NO_MONEY)
{
gParkFlags |= PARK_FLAGS_NO_MONEY_SCENARIO;
}
gParkSize = _s4.park_size;
gTotalRideValueForMoney = _s4.total_ride_value_for_money;
}
void ConvertResearchEntry(rct_research_item* dst, uint8_t srcItem, uint8_t srcType)
{
dst->rawValue = RESEARCHED_ITEMS_SEPARATOR;
if (srcType == RCT1_RESEARCH_TYPE_RIDE)
{
uint8_t entryIndex = _rideTypeToRideEntryMap[srcItem];
if (entryIndex != 255)
{
rct_ride_entry* rideEntry = get_ride_entry(entryIndex);
if (rideEntry != nullptr)
{
dst->entryIndex = entryIndex;
dst->baseRideType = ride_entry_get_first_non_null_ride_type(rideEntry);
dst->type = RESEARCH_ENTRY_TYPE_RIDE;
dst->flags = 0;
dst->category = rideEntry->category[0];
}
}
}
else if (srcType == RCT1_RESEARCH_TYPE_VEHICLE)
{
uint8_t entryIndex = _vehicleTypeToRideEntryMap[srcItem];
if (entryIndex != 255)
{
rct_ride_entry* rideEntry = get_ride_entry(entryIndex);
if (rideEntry != nullptr)
{
dst->entryIndex = entryIndex;
dst->baseRideType = ride_entry_get_first_non_null_ride_type(rideEntry);
dst->type = RESEARCH_ENTRY_TYPE_RIDE;
dst->flags = 0;
dst->category = rideEntry->category[0];
}
}
}
else if (srcType == RCT1_RESEARCH_TYPE_THEME)
{
uint8_t entryIndex = _sceneryThemeTypeToEntryMap[srcItem];
if (entryIndex != 254 && entryIndex != 255)
{
dst->entryIndex = entryIndex;
dst->type = RESEARCH_ENTRY_TYPE_SCENERY;
dst->category = RESEARCH_CATEGORY_SCENERY_GROUP;
dst->flags = 0;
}
}
}
void ImportClimate()
{
gClimate = _s4.climate;
gClimateUpdateTimer = _s4.climate_timer;
gClimateCurrent.Temperature = _s4.temperature;
gClimateCurrent.Weather = _s4.weather;
gClimateCurrent.WeatherEffect = WEATHER_EFFECT_NONE;
gClimateCurrent.WeatherGloom = _s4.weather_gloom;
gClimateCurrent.RainLevel = _s4.rain;
gClimateNext.Temperature = _s4.target_temperature;
gClimateNext.Weather = _s4.target_weather;
gClimateNext.WeatherEffect = WEATHER_EFFECT_NONE;
gClimateNext.WeatherGloom = _s4.target_weather_gloom;
gClimateNext.RainLevel = _s4.target_rain;
}
void ImportScenarioNameDetails()
{
std::string name = String::ToStd(_s4.scenario_name);
std::string details;
int32_t scNumber = _s4.scenario_slot_index;
if (scNumber != -1)
{
source_desc sourceDesc;
if (scenario_get_source_desc_by_id(scNumber, &sourceDesc))
{
rct_string_id localisedStringIds[3];
if (language_get_localised_scenario_strings(sourceDesc.title, localisedStringIds))
{
if (localisedStringIds[0] != STR_NONE)
{
name = String::ToStd(language_get_string(localisedStringIds[0]));
}
if (localisedStringIds[2] != STR_NONE)
{
details = String::ToStd(language_get_string(localisedStringIds[2]));
}
}
}
}
String::Set(gS6Info.name, sizeof(gS6Info.name), name.c_str());
String::Set(gS6Info.details, sizeof(gS6Info.details), details.c_str());
String::Set(gScenarioName, sizeof(gScenarioName), name.c_str());
String::Set(gScenarioDetails, sizeof(gScenarioDetails), details.c_str());
}
void ImportScenarioObjective()
{
gScenarioObjectiveType = _s4.scenario_objective_type;
gScenarioObjectiveYear = _s4.scenario_objective_years;
gScenarioObjectiveNumGuests = _s4.scenario_objective_num_guests;
// RCT1 used a different way of calculating the park value.
// This is corrected here, but since scenario_objective_currency doubles as minimum excitement rating,
// we need to check the goal to avoid affecting scenarios like Volcania.
if (_s4.scenario_objective_type == OBJECTIVE_PARK_VALUE_BY)
gScenarioObjectiveCurrency = CorrectRCT1ParkValue(_s4.scenario_objective_currency);
else
gScenarioObjectiveCurrency = _s4.scenario_objective_currency;
}
void ImportSavedView()
{
gSavedViewX = _s4.view_x;
gSavedViewY = _s4.view_y;
gSavedViewZoom = _s4.view_zoom;
gSavedViewRotation = _s4.view_rotation;
}
void ClearExtraTileEntries()
{
// Reset the map tile pointers
std::fill(std::begin(gTileElementTilePointers), std::end(gTileElementTilePointers), nullptr);
// Get the first free map element
rct_tile_element* nextFreeTileElement = gTileElements;
for (size_t i = 0; i < RCT1_MAX_MAP_SIZE * RCT1_MAX_MAP_SIZE; i++)
{
while (!(nextFreeTileElement++)->IsLastForTile())
;
}
rct_tile_element* tileElement = gTileElements;
rct_tile_element** tilePointer = gTileElementTilePointers;
// 128 rows of map data from RCT1 map
for (int32_t x = 0; x < RCT1_MAX_MAP_SIZE; x++)
{
// Assign the first half of this row
for (int32_t y = 0; y < RCT1_MAX_MAP_SIZE; y++)
{
*tilePointer++ = tileElement;
while (!(tileElement++)->IsLastForTile())
;
}
// Fill the rest of the row with blank tiles
for (int32_t y = 0; y < RCT1_MAX_MAP_SIZE; y++)
{
nextFreeTileElement->type = TILE_ELEMENT_TYPE_SURFACE;
nextFreeTileElement->flags = TILE_ELEMENT_FLAG_LAST_TILE;
nextFreeTileElement->base_height = 2;
nextFreeTileElement->clearance_height = 0;
nextFreeTileElement->properties.surface.slope = TILE_ELEMENT_SLOPE_FLAT;
nextFreeTileElement->properties.surface.terrain = 0;
nextFreeTileElement->properties.surface.grass_length = GRASS_LENGTH_CLEAR_0;
nextFreeTileElement->properties.surface.ownership = 0;
*tilePointer++ = nextFreeTileElement++;
}
}
// 128 extra rows left to fill with blank tiles
for (int32_t y = 0; y < 128 * 256; y++)
{
nextFreeTileElement->type = TILE_ELEMENT_TYPE_SURFACE;
nextFreeTileElement->flags = TILE_ELEMENT_FLAG_LAST_TILE;
nextFreeTileElement->base_height = 2;
nextFreeTileElement->clearance_height = 0;
nextFreeTileElement->properties.surface.slope = TILE_ELEMENT_SLOPE_FLAT;
nextFreeTileElement->properties.surface.terrain = 0;
nextFreeTileElement->properties.surface.grass_length = GRASS_LENGTH_CLEAR_0;
nextFreeTileElement->properties.surface.ownership = 0;
*tilePointer++ = nextFreeTileElement++;
}
gNextFreeTileElement = nextFreeTileElement;
}
void FixSceneryColours()
{
colour_t colour;
rct_tile_element* tileElement = gTileElements;
while (tileElement < gNextFreeTileElement)
{
if (tileElement->base_height != 255)
{
// This skips walls, which are fixed later.
switch (tileElement->GetType())
{
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
colour = RCT1::GetColour(scenery_small_get_primary_colour(tileElement));
scenery_small_set_primary_colour(tileElement, colour);
// Copied from [rct2: 0x006A2956]
switch (tileElement->properties.scenery.type)
{
case RCT1_SCENERY_GEOMETRIC_SCULPTURE_1:
case RCT1_SCENERY_GEOMETRIC_SCULPTURE_2:
case RCT1_SCENERY_GEOMETRIC_SCULPTURE_3:
case RCT1_SCENERY_GEOMETRIC_SCULPTURE_4:
case RCT1_SCENERY_GEOMETRIC_SCULPTURE_5:
scenery_small_set_secondary_colour(tileElement, COLOUR_WHITE);
break;
case RCT1_SCENERY_TULIPS_1:
case RCT1_SCENERY_TULIPS_2:
scenery_small_set_primary_colour(tileElement, COLOUR_BRIGHT_RED);
scenery_small_set_secondary_colour(tileElement, COLOUR_YELLOW);
}
break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
colour = RCT1::GetColour(scenery_large_get_primary_colour(tileElement));
scenery_large_set_primary_colour(tileElement, colour);
colour = RCT1::GetColour(scenery_large_get_secondary_colour(tileElement));
scenery_large_set_secondary_colour(tileElement, colour);
break;
}
}
tileElement++;
}
}
void FixTileElementZ()
{
rct_tile_element* tileElement = gTileElements;
while (tileElement < gNextFreeTileElement)
{
if (tileElement->base_height != 255)
{
tileElement->base_height /= 2;
tileElement->clearance_height /= 2;
}
tileElement++;
}
gMapBaseZ = 7;
}
void FixPaths()
{
rct_tile_element* tileElement = gTileElements;
while (tileElement < gNextFreeTileElement)
{
switch (tileElement->GetType())
{
case TILE_ELEMENT_TYPE_PATH:
{
// Type
uint8_t pathType = GetPathType(tileElement);
uint8_t entryIndex = _pathTypeToEntryMap[pathType];
tileElement->type &= ~TILE_ELEMENT_DIRECTION_MASK;
tileElement->flags &= ~(TILE_ELEMENT_FLAG_BROKEN | TILE_ELEMENT_FLAG_INDESTRUCTIBLE_TRACK_PIECE);
footpath_element_set_type(tileElement, entryIndex);
if (RCT1::PathIsQueue(pathType))
{
footpath_element_set_queue(tileElement);
}
footpath_scenery_set_is_ghost(tileElement, false);
// Additions
uint8_t additionType = footpath_element_get_path_scenery(tileElement);
if (additionType != RCT1_PATH_ADDITION_NONE)
{
uint8_t normalisedType = RCT1::NormalisePathAddition(additionType);
entryIndex = _pathAdditionTypeToEntryMap[normalisedType];
if (additionType != normalisedType)
{
tileElement->flags |= TILE_ELEMENT_FLAG_BROKEN;
}
footpath_element_set_path_scenery(tileElement, entryIndex + 1);
}
break;
}
case TILE_ELEMENT_TYPE_ENTRANCE:
if (tileElement->properties.entrance.type == ENTRANCE_TYPE_PARK_ENTRANCE)
{
uint8_t pathType = tileElement->properties.entrance.path_type;
if (pathType == 0)
{
pathType = RCT1_FOOTPATH_TYPE_TARMAC_GRAY;
}
uint8_t entryIndex = _pathTypeToEntryMap[pathType];
tileElement->properties.entrance.path_type = entryIndex & 0x7F;
}
break;
}
tileElement++;
}
}
void FixWalls()
{
// The user might attempt to load a save while in pause mode.
// Since we cannot place walls in pause mode without a cheat, temporarily turn it on.
bool oldCheatValue = gCheatsBuildInPauseMode;
gCheatsBuildInPauseMode = true;
for (int32_t x = 0; x < RCT1_MAX_MAP_SIZE; x++)
{
for (int32_t y = 0; y < RCT1_MAX_MAP_SIZE; y++)
{
rct_tile_element* tileElement = map_get_first_element_at(x, y);
do
{
if (tileElement->GetType() == TILE_ELEMENT_TYPE_WALL)
{
rct_tile_element originalTileElement = *tileElement;
tile_element_remove(tileElement);
for (int32_t edge = 0; edge < 4; edge++)
{
int32_t type = GetWallType(&originalTileElement, edge);
if (type != -1)
{
int32_t colourA = RCT1::GetColour(GetWallColour(&originalTileElement));
int32_t colourB = 0;
int32_t colourC = 0;
ConvertWall(&type, &colourA, &colourB);
type = _wallTypeToEntryMap[type];
const uint8_t flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
| GAME_COMMAND_FLAG_5 | GAME_COMMAND_FLAG_PATH_SCENERY;
wall_place(type, x * 32, y * 32, 0, edge, colourA, colourB, colourC, flags);
}
}
break;
}
} while (!(tileElement++)->IsLastForTile());
}
}
gCheatsBuildInPauseMode = oldCheatValue;
}
void ConvertWall(int32_t* type, int32_t* colourA, int32_t* colourB)
{
switch (*type)
{
case RCT1_WALL_TYPE_WOODEN_PANEL_FENCE:
*colourA = COLOUR_DARK_BROWN;
break;
case RCT1_WALL_TYPE_WHITE_WOODEN_PANEL_FENCE:
*type = RCT1_WALL_TYPE_WOODEN_PANEL_FENCE;
*colourA = COLOUR_WHITE;
break;
case RCT1_WALL_TYPE_RED_WOODEN_PANEL_FENCE:
*type = RCT1_WALL_TYPE_WOODEN_PANEL_FENCE;
*colourA = COLOUR_SALMON_PINK;
break;
case RCT1_WALL_TYPE_WOODEN_PANEL_FENCE_WITH_SNOW:
*colourA = COLOUR_DARK_BROWN;
break;
case RCT1_WALL_TYPE_WOODEN_PANEL_FENCE_WITH_GATE:
*colourB = *colourA;
*colourA = COLOUR_DARK_BROWN;
break;
case RCT1_WALL_TYPE_GLASS_SMOOTH:
case RCT1_WALL_TYPE_GLASS_PANELS:
*colourB = COLOUR_WHITE;
break;
case RCT1_WALL_TYPE_SMALL_GREY_CASTLE:
case RCT1_WALL_TYPE_LARGE_CREY_CASTLE:
case RCT1_WALL_TYPE_LARGE_CREY_CASTLE_CROSS:
case RCT1_WALL_TYPE_LARGE_CREY_CASTLE_GATE:
case RCT1_WALL_TYPE_LARGE_CREY_CASTLE_WINDOW:
case RCT1_WALL_TYPE_MEDIUM_CREY_CASTLE:
*colourA = COLOUR_GREY;
break;
}
}
void FixBanners()
{
for (int32_t x = 0; x < RCT1_MAX_MAP_SIZE; x++)
{
for (int32_t y = 0; y < RCT1_MAX_MAP_SIZE; y++)
{
rct_tile_element* tileElement = map_get_first_element_at(x, y);
do
{
if (tileElement->GetType() == TILE_ELEMENT_TYPE_BANNER)
{
uint8_t index = tileElement->properties.banner.index;
rct_banner* src = &_s4.banners[index];
rct_banner* dst = &gBanners[index];
ImportBanner(dst, src);
}
} while (!(tileElement++)->IsLastForTile());
}
}
}
void ImportBanner(rct_banner* dst, rct_banner* src)
{
*dst = *src;
dst->colour = RCT1::GetColour(src->colour);
dst->string_idx = STR_DEFAULT_SIGN;
if (is_user_string_id(src->string_idx))
{
std::string bannerText = GetUserString(src->string_idx);
if (!bannerText.empty())
{
rct_string_id bannerTextStringId = user_string_allocate(USER_STRING_DUPLICATION_PERMITTED, bannerText.c_str());
if (bannerTextStringId != 0)
{
dst->string_idx = bannerTextStringId;
}
}
}
}
void FixTerrain()
{
tile_element_iterator it;
tile_element_iterator_begin(&it);
while (tile_element_iterator_next(&it))
{
rct_tile_element* element = it.element;
if (element->GetType() == TILE_ELEMENT_TYPE_SURFACE)
{
surface_set_terrain(element, RCT1::GetTerrain(surface_get_terrain(element)));
surface_set_terrain_edge(element, RCT1::GetTerrainEdge(surface_get_terrain_edge(element)));
}
}
}
void FixEntrancePositions()
{
for (size_t i = 0; i < Util::CountOf(gParkEntrances); i++)
{
gParkEntrances[i].x = LOCATION_NULL;
}
uint8_t entranceIndex = 0;
tile_element_iterator it;
tile_element_iterator_begin(&it);
while (tile_element_iterator_next(&it) && entranceIndex < RCT12_MAX_PARK_ENTRANCES)
{
rct_tile_element* element = it.element;
if (element->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
continue;
if (element->properties.entrance.type != ENTRANCE_TYPE_PARK_ENTRANCE)
continue;
if ((element->properties.entrance.index & 0x0F) != 0)
continue;
gParkEntrances[entranceIndex].x = it.x * 32;
gParkEntrances[entranceIndex].y = it.y * 32;
gParkEntrances[entranceIndex].z = element->base_height * 8;
gParkEntrances[entranceIndex].direction = tile_element_get_direction(element);
entranceIndex++;
}
}
void FixTileElementEntryTypes()
{
tile_element_iterator it;
tile_element_iterator_begin(&it);
while (tile_element_iterator_next(&it))
{
rct_tile_element* tileElement = it.element;
switch (tileElement->GetType())
{
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
tileElement->properties.scenery.type = _smallSceneryTypeToEntryMap[tileElement->properties.scenery.type];
break;
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
{
uint8_t type = scenery_large_get_type(tileElement);
scenery_large_set_type(tileElement, _largeSceneryTypeToEntryMap[type]);
break;
}
}
}
}
EntryList* GetEntryList(uint8_t objectType)
{
switch (objectType)
{
case OBJECT_TYPE_RIDE:
return &_rideEntries;
case OBJECT_TYPE_SMALL_SCENERY:
return &_smallSceneryEntries;
case OBJECT_TYPE_LARGE_SCENERY:
return &_largeSceneryEntries;
case OBJECT_TYPE_WALLS:
return &_wallEntries;
case OBJECT_TYPE_PATHS:
return &_pathEntries;
case OBJECT_TYPE_PATH_BITS:
return &_pathAdditionEntries;
case OBJECT_TYPE_SCENERY_GROUP:
return &_sceneryGroupEntries;
case OBJECT_TYPE_WATER:
return &_waterEntry;
}
return nullptr;
}
const rct1_research_item* GetResearchList(size_t* count)
{
// Loopy Landscapes stores research items in a different place
if (_gameVersion == FILE_VERSION_RCT1_LL)
{
*count = Util::CountOf(_s4.research_items_LL);
return _s4.research_items_LL;
}
else
{
*count = Util::CountOf(_s4.research_items);
return _s4.research_items;
}
}
std::string GetUserString(rct_string_id stringId)
{
const char* originalString = _s4.string_table[(stringId - USER_STRING_START) % 1024];
return rct2_to_utf8(originalString, RCT2_LANGUAGE_ID_ENGLISH_UK);
}
void FixLandOwnership()
{
switch (_s4.scenario_slot_index)
{
case SC_DYNAMITE_DUNES:
FixLandOwnershipTiles({ { 97, 18 }, { 99, 19 }, { 83, 34 } });
break;
case SC_LEAFY_LAKE:
FixLandOwnershipTiles({ { 49, 66 } });
break;
case SC_KATIES_DREAMLAND:
FixLandOwnershipTiles({ { 74, 70 }, { 75, 70 }, { 76, 70 }, { 77, 73 }, { 80, 77 } });
break;
case SC_POKEY_PARK:
FixLandOwnershipTiles({ { 64, 102 } });
break;
case SC_MYSTIC_MOUNTAIN:
FixLandOwnershipTiles({ { 98, 69 }, { 98, 70 }, { 103, 64 }, { 53, 79 }, { 86, 93 }, { 87, 93 } });
break;
case SC_PACIFIC_PYRAMIDS:
FixLandOwnershipTiles({ { 93, 105 }, { 63, 34 }, { 76, 25 }, { 85, 31 }, { 96, 47 }, { 96, 48 } });
break;
case SC_UTOPIA_PARK:
FixLandOwnershipTiles({ { 85, 73 } });
break;
case SC_URBAN_PARK:
FixLandOwnershipTiles({ { 64, 77 }, { 61, 66 }, { 61, 67 }, { 39, 20 } });
break;
}
}
/**
* Counts the block sections. The reason this iterates over the map is to avoid getting into infinite loops,
* which can happen with hacked parks.
*/
void CountBlockSections()
{
for (int32_t x = 0; x < RCT1_MAX_MAP_SIZE; x++)
{
for (int32_t y = 0; y < RCT1_MAX_MAP_SIZE; y++)
{
rct_tile_element* tileElement = map_get_first_element_at(x, y);
do
{
if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
{
// Lift hill tops are the only pieces present in RCT1 that can count as a block brake.
if (!track_element_is_lift_hill(tileElement))
continue;
uint8_t trackType = track_element_get_type(tileElement);
switch (trackType)
{
case TRACK_ELEM_25_DEG_UP_TO_FLAT:
case TRACK_ELEM_60_DEG_UP_TO_FLAT:
case TRACK_ELEM_DIAG_25_DEG_UP_TO_FLAT:
case TRACK_ELEM_DIAG_60_DEG_UP_TO_FLAT:
break;
default:
continue;
}
uint8_t rideIndex = track_element_get_ride_index(tileElement);
Ride* ride = get_ride(rideIndex);
ride->num_block_brakes++;
}
} while (!(tileElement++)->IsLastForTile());
}
}
}
};
std::unique_ptr<IParkImporter> ParkImporter::CreateS4()
{
return std::make_unique<S4Importer>();
}
void load_from_sv4(const utf8* path)
{
auto objectMgr = GetContext()->GetObjectManager();
auto s4Importer = std::make_unique<S4Importer>();
auto result = s4Importer->LoadSavedGame(path);
objectMgr->LoadObjects(result.RequiredObjects.data(), result.RequiredObjects.size());
s4Importer->Import();
}
void load_from_sc4(const utf8* path)
{
auto objectMgr = GetContext()->GetObjectManager();
auto s4Importer = std::make_unique<S4Importer>();
auto result = s4Importer->LoadScenario(path);
objectMgr->LoadObjects(result.RequiredObjects.data(), result.RequiredObjects.size());
s4Importer->Import();
}
static uint8_t GetPathType(rct_tile_element* tileElement)
{
uint8_t pathColour = tileElement->type & 3;
uint8_t pathType = (tileElement->properties.path.type & FOOTPATH_PROPERTIES_TYPE_MASK) >> 2;
pathType = pathType | pathColour;
return pathType;
}
static int32_t GetWallType(rct_tile_element* tileElement, int32_t edge)
{
uint8_t var_05 = tileElement->properties.wall.colour_3;
uint16_t var_06 = tileElement->properties.wall.colour_1 | (tileElement->properties.wall.animation << 8);
int32_t typeA = (var_05 >> (edge * 2)) & 3;
int32_t typeB = (var_06 >> (edge * 4)) & 0x0F;
if (typeB != 0x0F)
{
return typeA | (typeB << 2);
}
else
{
return -1;
}
}
static uint8_t GetWallColour(rct_tile_element* tileElement)
{
return ((tileElement->type & 0xC0) >> 3) | ((tileElement->properties.wall.type & 0xE0) >> 5);
}