#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * * OpenRCT2 is the work of many authors, a full list can be found in contributors.md * For more information, visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * A full copy of the GNU General Public License can be found in licence.txt *****************************************************************************/ #pragma endregion #include "S4Importer.h" #include "../core/Exception.hpp" #include "../core/Guard.hpp" #include "../core/List.hpp" #include "../core/Path.hpp" #include "../core/String.hpp" #include "../core/Util.hpp" #include "../object/ObjectManager.h" #include "Tables.h" extern "C" { #include "../audio/audio.h" #include "../cheats.h" #include "../editor.h" #include "../game.h" #include "../interface/window.h" #include "../localisation/date.h" #include "../localisation/localisation.h" #include "../management/finance.h" #include "../management/marketing.h" #include "../object.h" #include "../peep/staff.h" #include "../rct1.h" #include "../util/sawyercoding.h" #include "../util/util.h" #include "../world/climate.h" #include "../world/footpath.h" #include "../world/map_animation.h" #include "../world/park.h" #include "../world/scenery.h" } static bool ObjectNameComparer(const char * a, const char * b) { return String::Equals(a, b, true); } void S4Importer::LoadSavedGame(const utf8 * path) { if (!rct1_read_sv4(path, &_s4)) { throw Exception("Unable to load SV4."); } _s4Path = path; } void S4Importer::LoadScenario(const utf8 * path) { if (!rct1_read_sc4(path, &_s4)) { throw Exception("Unable to load SC4."); } _s4Path = path; } void S4Importer::Import() { Initialise(); CreateAvailableObjectMappings(); LoadObjects(); ImportRides(); ImportRideMeasurements(); ImportMapElements(); ImportMapAnimations(); ImportPeepSpawns(); ImportFinance(); ImportResearch(); ImportParkName(); ImportParkFlags(); ImportClimate(); ImportScenarioNameDetails(); ImportScenarioObjective(); ImportSavedView(); game_convert_strings_to_utf8(); } void S4Importer::Initialise() { _gameVersion = sawyercoding_detect_rct1_version(_s4.game_version) & FILE_VERSION_MASK; Memory::Set(_rideTypeToRideEntryMap, 255, sizeof(_rideTypeToRideEntryMap)); Memory::Set(_vehicleTypeToRideEntryMap, 255, sizeof(_vehicleTypeToRideEntryMap)); Memory::Set(_smallSceneryTypeToEntryMap, 255, sizeof(_smallSceneryTypeToEntryMap)); Memory::Set(_largeSceneryTypeToEntryMap, 255, sizeof(_largeSceneryTypeToEntryMap)); Memory::Set(_wallTypeToEntryMap, 255, sizeof(_wallTypeToEntryMap)); Memory::Set(_pathTypeToEntryMap, 255, sizeof(_pathTypeToEntryMap)); Memory::Set(_pathAdditionTypeToEntryMap, 255, sizeof(_pathAdditionTypeToEntryMap)); Memory::Set(_sceneryThemeTypeToEntryMap, 255, sizeof(_sceneryThemeTypeToEntryMap)); uint16 mapSize = _s4.map_size == 0 ? 128 : _s4.map_size; // Do map initialisation, same kind of stuff done when loading scenario editor audio_pause_sounds(); audio_unpause_sounds(); object_unload_all(); map_init(mapSize); banner_init(); reset_park_entrances(); user_string_clear_all(); reset_sprite_list(); ride_init_all(); window_guest_list_init_vars_a(); staff_reset_modes(); park_init(); finance_init(); date_reset(); window_guest_list_init_vars_b(); window_staff_list_init_vars(); gS6Info->editor_step = EDITOR_STEP_OBJECT_SELECTION; gParkFlags |= PARK_FLAGS_SHOW_REAL_GUEST_NAMES; window_new_ride_init_vars(); RCT2_GLOBAL(0x0141F571, uint8) = 4; news_item_init_queue(); } void S4Importer::CreateAvailableObjectMappings() { AddDefaultEntries(); AddAvailableEntriesFromResearchList(); AddAvailableEntriesFromMap(); AddAvailableEntriesFromRides(); AddAvailableEntriesFromSceneryGroups(); } void S4Importer::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 S4Importer::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->item == RCT1_RESEARCH_END_RESEARCHABLE || researchItem->item == RCT1_RESEARCH_END) { break; } if (researchItem->item == RCT1_RESEARCH_END_AVAILABLE) { continue; } switch (researchItem->category) { case RCT1_RESEARCH_CATEGORY_THEME: AddEntriesForSceneryTheme(researchItem->item); break; case RCT1_RESEARCH_CATEGORY_RIDE: { uint8 rideType = researchItem->item; // Add all vehicles for this ride type uint32 numVehicles = 0; for (size_t j = 0; j < researchListCount; j++) { const rct1_research_item *researchItem2 = &researchList[j]; if (researchItem2->item == RCT1_RESEARCH_END_RESEARCHABLE || researchItem2->item == RCT1_RESEARCH_END_AVAILABLE) { break; } if (researchItem2->category == RCT1_RESEARCH_CATEGORY_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 S4Importer::AddAvailableEntriesFromMap() { size_t maxTiles = 128 * 128; size_t tileIndex = 0; rct_map_element * mapElement = _s4.map_elements; while (tileIndex < maxTiles) { switch (map_element_get_type(mapElement)) { case MAP_ELEMENT_TYPE_PATH: { uint8 pathColour = mapElement->type & 3; uint8 pathType = (mapElement->properties.path.type & 0xF0) >> 4; pathType = (pathType << 2) | pathColour; uint8 pathAdditionsType = mapElement->properties.path.additions & 0x0F; AddEntryForPath(pathType); AddEntryForPathAddition(pathAdditionsType); break; } case MAP_ELEMENT_TYPE_SCENERY: AddEntryForSmallScenery(mapElement->properties.scenery.type); break; case MAP_ELEMENT_TYPE_SCENERY_MULTIPLE: AddEntryForLargeScenery(mapElement->properties.scenerymultiple.type & MAP_ELEMENT_LARGE_TYPE_MASK); break; case MAP_ELEMENT_TYPE_FENCE: { uint8 var_05 = mapElement->properties.fence.item[0]; uint16 var_06 = mapElement->properties.fence.item[1] | (mapElement->properties.fence.item[2] << 8); for (int edge = 0; edge < 4; edge++) { int typeA = (var_05 >> (edge * 2)) & 3; int typeB = (var_06 >> (edge * 4)) & 0x0F; if (typeB != 0x0F) { uint8 type = typeA | (typeB << 2); AddEntryForWall(type); } } break; } } if (map_element_is_last_for_tile(mapElement++)) { tileIndex++; } } } void S4Importer::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 && RCT1::RideTypeUsesVehicles(ride->type)) { AddEntryForVehicleType(ride->type, ride->vehicle_type); } } } void S4Importer::AddAvailableEntriesFromSceneryGroups() { for (int sceneryTheme = 0; sceneryTheme <= RCT1_SCENERY_THEME_PAGODA; sceneryTheme++) { if (sceneryTheme != 0 && _sceneryThemeTypeToEntryMap[sceneryTheme] == 255) continue; List objects = RCT1::GetSceneryObjects(sceneryTheme); for (const char * objectName : objects) { rct_object_entry * foundEntry = object_list_find_by_name(objectName); if (foundEntry != nullptr) { uint8 objectType = foundEntry->flags & 0x0F; 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: { List * entries = GetEntryList(objectType); // Ran out of available entries if (entries->GetCount() >= (size_t)object_entry_group_counts[objectType]) { break; } if (!entries->Contains(objectName, ObjectNameComparer)) { entries->Add(objectName); } break; } } } } } } void S4Importer::AddEntryForRideType(uint8 rideType) { assert(rideType < Util::CountOf(_rideTypeToRideEntryMap)); if (_rideTypeToRideEntryMap[rideType] == 255) { const char * entryName = RCT1::GetRideTypeObject(rideType); _rideTypeToRideEntryMap[rideType] = (uint8)_rideEntries.GetCount(); _rideEntries.Add(entryName); } } void S4Importer::AddEntryForVehicleType(uint8 rideType, uint8 vehicleType) { assert(vehicleType < Util::CountOf(_vehicleTypeToRideEntryMap)); if (_vehicleTypeToRideEntryMap[vehicleType] == 255) { const char * entryName = RCT1::GetVehicleObject(vehicleType); uint8 rideEntryIndex = (uint8)_rideEntries.GetCount(); _vehicleTypeToRideEntryMap[vehicleType] = rideEntryIndex; _rideEntries.Add(entryName); // Just overwrite this with the vehicle entry for now... _rideTypeToRideEntryMap[rideType] = rideEntryIndex; } } void S4Importer::AddEntryForSmallScenery(uint8 smallSceneryType) { assert(smallSceneryType < Util::CountOf(_smallSceneryTypeToEntryMap)); if (_smallSceneryTypeToEntryMap[smallSceneryType] == 255) { const char * entryName = RCT1::GetSmallSceneryObject(smallSceneryType); _smallSceneryTypeToEntryMap[smallSceneryType] = (uint8)_smallSceneryEntries.GetCount(); _smallSceneryEntries.Add(entryName); } } void S4Importer::AddEntryForLargeScenery(uint8 largeSceneryType) { assert(largeSceneryType < Util::CountOf(_largeSceneryTypeToEntryMap)); if (_largeSceneryTypeToEntryMap[largeSceneryType] == 255) { const char * entryName = RCT1::GetLargeSceneryObject(largeSceneryType); _largeSceneryTypeToEntryMap[largeSceneryType] = (uint8)_largeSceneryEntries.GetCount(); _largeSceneryEntries.Add(entryName); } } void S4Importer::AddEntryForWall(uint8 wallType) { assert(wallType < Util::CountOf(_wallTypeToEntryMap)); if (_wallTypeToEntryMap[wallType] == 255) { const char * entryName = RCT1::GetWallObject(wallType); _wallTypeToEntryMap[wallType] = (uint8)_wallEntries.GetCount(); _wallEntries.Add(entryName); } } void S4Importer::AddEntryForPath(uint8 pathType) { assert(pathType < Util::CountOf(_pathTypeToEntryMap)); if (_pathTypeToEntryMap[pathType] == 255) { const char * entryName = RCT1::GetPathObject(pathType); size_t index = _pathEntries.IndexOf(entryName, ObjectNameComparer); if (index != SIZE_MAX) { _pathTypeToEntryMap[pathType] = (uint8)index; } else { _pathTypeToEntryMap[pathType] = (uint8)_pathEntries.GetCount(); _pathEntries.Add(entryName); } } } void S4Importer::AddEntryForPathAddition(uint8 pathAdditionType) { if (pathAdditionType == RCT1_PATH_ADDITION_NONE) return; if (_pathAdditionTypeToEntryMap[pathAdditionType] == 255) { uint8 normalisedPathAdditionType = RCT1::NormalisePathAddition(pathAdditionType); if (_pathAdditionTypeToEntryMap[normalisedPathAdditionType] == 255) { const char * entryName = RCT1::GetPathAddtionObject(normalisedPathAdditionType); _pathAdditionTypeToEntryMap[normalisedPathAdditionType] = (uint8)_pathAdditionEntries.GetCount(); _pathAdditionEntries.Add(entryName); } _pathAdditionTypeToEntryMap[pathAdditionType] = _pathAdditionTypeToEntryMap[normalisedPathAdditionType]; } } void S4Importer::AddEntriesForSceneryTheme(uint8 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); _sceneryThemeTypeToEntryMap[sceneryThemeType] = (uint8)_sceneryGroupEntries.GetCount(); _sceneryGroupEntries.Add(entryName); } } void S4Importer::ImportRides() { for (int i = 0; i < MAX_RIDES; i++) { if (_s4.rides[i].type != RIDE_TYPE_NULL) { ImportRide(get_ride(i), &_s4.rides[i]); } } } void S4Importer::ImportRide(rct_ride * dst, rct1_ride * src) { memset(dst, 0, sizeof(rct_ride)); 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); // Ride name dst->name = 0; if (is_user_string_id(src->name)) { const char * rideName = GetUserString(src->name); if (rideName[0] != 0) { rct_string_id rideNameStringId = user_string_allocate(4, rideName); if (rideNameStringId != 0) { dst->name = rideNameStringId; } } } if (dst->name == 0) { dst->name = 1; uint16 * args = (uint16*)&dst->name_arguments; args[0] = 2 + dst->type; args[1] = src->name_argument_number; } // We can't convert vehicles yet so just close the ride dst->status = RIDE_STATUS_CLOSED; // Flags if (src->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO) dst->lifecycle_flags |= RIDE_LIFECYCLE_ON_RIDE_PHOTO; if (src->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) dst->lifecycle_flags |= RIDE_LIFECYCLE_MUSIC; if (src->lifecycle_flags & RIDE_LIFECYCLE_INDESTRUCTIBLE) dst->lifecycle_flags |= RIDE_LIFECYCLE_INDESTRUCTIBLE; if (src->lifecycle_flags & RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK) dst->lifecycle_flags |= RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK; // Station dst->overall_view = src->overall_view; for (int i = 0; i < 4; 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]; // Use src->station_depart[i] when we import with guests and vehicles intact dst->train_at_station[i] = 0xFF; dst->entrances[i] = src->entrance[i]; dst->exits[i] = src->exit[i]; dst->queue_time[i] = src->queue_time[i]; dst->last_peep_in_queue[i] = 0xFFFF; } dst->num_stations = src->num_stations; for (int i = 0; i < 32; 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 = 32; dst->proposed_num_cars_per_train = src->num_cars_per_train + rideEntry->zero_cars; // 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 = RCT2_ADDRESS(0x0097D4F4, uint8)[dst->type * 8]; } 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; } // 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); } else { for (int i = 0; i < 4; 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 < 12; i++) { dst->vehicle_colours[i].body_colour = RCT1::GetColour(src->vehicle_colours[i].body); dst->vehicle_colours[i].trim_colour = RCT1::GetColour(src->vehicle_colours[i].trim); } } // Fix other Z // dst->start_drop_height /= 2; // dst->highest_drop_height = 1; // if (dst->cur_test_track_z != 255) // { // dst->cur_test_track_z /= 2; // } // dst->chairlift_bullwheel_z[0] /= 2; // dst->chairlift_bullwheel_z[1] /= 2; // 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->breakdown_reason = src->breakdown_reason; // Finance dst->upkeep_cost = src->upkeep_cost; dst->price = src->price; dst->income_per_hour = src->income_per_hour; dst->value = src->value; dst->satisfaction = 255; dst->satisfaction_time_out = 0; dst->satisfaction_next = 0; dst->popularity = src->popularity; dst->popularity_next = src->popularity_next; dst->popularity_time_out = src->popularity_time_out; dst->music_tune_id = 255; dst->measurement_index = 255; dst->excitement = (ride_rating)-1; } void S4Importer::ImportRideMeasurements() { for (int 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 S4Importer::ImportRideMeasurement(rct_ride_measurement * dst, rct_ride_measurement * src) { // Not yet supported // *dst = *src; // for (int i = 0; i < RIDE_MEASUREMENT_MAX_ITEMS; i++) // { // dst->altitude[i] /= 2; // } } void S4Importer::ImportPeepSpawns() { for (int i = 0; i < 2; i++) { gPeepSpawns[i] = _s4.peep_spawn[i]; } } void S4Importer::ImportMapAnimations() { // This is sketchy, ideally we should try to re-create them rct_map_animation * s4Animations = (rct_map_animation*)_s4.map_animations; for (int i = 0; i < 1000; i++) { gAnimatedObjects[i] = s4Animations[i]; gAnimatedObjects[i].baseZ /= 2; } gNumMapAnimations = _s4.num_map_animations; } void S4Importer::ImportFinance() { gParkEntranceFee = _s4.park_entrance_fee; gLandPrice = _s4.land_price; gConstructionRightsPrice = _s4.construction_rights_price; gCashEncrypted = ENCRYPT_MONEY(_s4.cash); gBankLoan = _s4.loan; gMaxBankLoan = _s4.max_loan; gInitialCash = _s4.cash; finance_update_loan_hash(); gCompanyValue = _s4.company_value; gParkValue = _s4.park_value; gCurrentProfit = _s4.profit; for (int i = 0; i < 128; i++) { gCashHistory[i] = _s4.cash_history[i]; gParkValueHistory[i] = _s4.park_value_history[i]; gWeeklyProfitHistory[i] = _s4.weekly_profit_history[i]; } for (int i = 0; i < 14 * 16; i++) { gExpenditureTable[i] = _s4.expenditure[i]; } gCurrentExpenditure = _s4.total_expenditure; gTotalAdmissions = _s4.num_admissions; gTotalIncomeFromAdmissions = _s4.admission_total_income; // TODO marketing campaigns not working for (int i = 0; i < 6; i++) { gMarketingCampaignDaysLeft[i] = _s4.marketing_status[i]; gMarketingCampaignRideIndex[i] = _s4.marketing_assoc[i]; } } void S4Importer::LoadObjects() { 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_SETS, _sceneryGroupEntries); LoadObjects(OBJECT_TYPE_BANNERS, List({ "BN1 ", "BN2 ", "BN3 ", "BN4 ", "BN5 ", "BN6 ", "BN7 ", "BN8 ", "BN9 " })); LoadObjects(OBJECT_TYPE_PARK_ENTRANCE, List({ "PKENT1 " })); LoadObjects(OBJECT_TYPE_WATER, List({ "WTRCYAN " })); reset_loaded_objects(); } void S4Importer::LoadObjects(uint8 objectType, List entries) { IObjectManager * objectManager = GetObjectManager(); uint32 entryIndex = 0; for (const char * objectName : entries) { rct_object_entry entry; entry.flags = 0x00008000 + objectType; Memory::Copy(entry.name, objectName, 8); entry.checksum = 0; Object * object = objectManager->LoadObject(&entry); if (object == nullptr) { log_error("Failed to load %s.", objectName); throw Exception("Failed to load object."); } entryIndex++; } } void S4Importer::ImportMapElements() { memcpy(gMapElements, _s4.map_elements, 0xC000 * sizeof(rct_map_element)); ClearExtraTileEntries(); FixColours(); FixZ(); FixPaths(); FixWalls(); FixBanners(); FixTerrain(); FixEntrancePositions(); FixMapElementEntryTypes(); } void S4Importer::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 Memory::Set(_researchRideEntryUsed, 0, sizeof(_researchRideEntryUsed)); Memory::Set(_researchRideTypeUsed, 0, sizeof(_researchRideTypeUsed)); // The first six scenery groups are always available for (int 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->item == RCT1_RESEARCH_END_AVAILABLE) { researched = false; } else if (researchItem->item == RCT1_RESEARCH_END_RESEARCHABLE || researchItem->item == RCT1_RESEARCH_END) { break; } switch (researchItem->category) { case RCT1_RESEARCH_CATEGORY_THEME: { uint8 rct1SceneryTheme = researchItem->item; if (rct1SceneryTheme != RCT1_SCENERY_THEME_GENERAL && rct1SceneryTheme != RCT1_SCENERY_THEME_JUMPING_FOUNTAINS && rct1SceneryTheme != RCT1_SCENERY_THEME_GARDEN_CLOCK) { uint8 sceneryGroupEntryIndex = _sceneryThemeTypeToEntryMap[rct1SceneryTheme]; research_insert_scenery_group_entry(sceneryGroupEntryIndex, researched); } break; } case RCT1_RESEARCH_CATEGORY_RIDE: { uint8 rct1RideType = researchItem->item; // Add all vehicles for this ride type that are researched or before this research item uint32 numVehicles = 0; for (size_t j = 0; j < researchListCount; j++) { const rct1_research_item *researchItem2 = &researchList[j]; if (researchItem2->item == RCT1_RESEARCH_END_RESEARCHABLE || researchItem2->item == RCT1_RESEARCH_END_AVAILABLE) { break; } if (researchItem2->category == RCT1_RESEARCH_CATEGORY_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 rideEntryIndex = _rideTypeToRideEntryMap[rct1RideType]; Guard::Assert(rideEntryIndex != 255, "rideEntryIndex was 255"); if (!_researchRideEntryUsed[rideEntryIndex]) { _researchRideEntryUsed[rideEntryIndex] = true; research_insert_ride_entry(rideEntryIndex, researched); } } break; } case RCT1_RESEARCH_CATEGORY_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_CATEGORY_SPECIAL: // Not supported break; } } research_remove_non_separate_vehicle_types(); // Fixes availability of rides sub_684AC3(); // Research funding / priority uint8 activeResearchTypes = 0; if (_s4.research_priority & RCT1_RESEARCH_EXPENDITURE_ROLLERCOASTERS) { activeResearchTypes |= (1 << RESEARCH_CATEGORY_ROLLERCOASTER); } if (_s4.research_priority & RCT1_RESEARCH_EXPENDITURE_THRILL_RIDES) { activeResearchTypes |= (1 << RESEARCH_CATEGORY_THRILL); activeResearchTypes |= (1 << RESEARCH_CATEGORY_WATER); } if (_s4.research_priority & RCT1_RESEARCH_EXPENDITURE_GENTLE_TRANSPORT_RIDES) { activeResearchTypes |= (1 << RESEARCH_CATEGORY_GENTLE); activeResearchTypes |= (1 << RESEARCH_CATEGORY_TRANSPORT); } if (_s4.research_priority & RCT1_RESEARCH_EXPENDITURE_SHOPS) { activeResearchTypes |= (1 << RESEARCH_CATEGORY_SHOP); } if (_s4.research_priority & RCT1_RESEARCH_EXPENDITURE_SCENERY_THEMEING) { activeResearchTypes |= (1 << RESEARCH_CATEGORY_SCENERYSET); } gResearchPriorities = activeResearchTypes; gResearchFundingLevel = _s4.research_level; // Research history gResearchProgress = _s4.research_progress; // gResearchProgressStage = gResearchNextItem = _s4.next_research_item; gResearchNextCategory = _s4.next_research_category; // gResearchExpectedDay = // gResearchExpectedMonth = } void S4Importer::InsertResearchVehicle(const rct1_research_item * researchItem, bool researched) { uint8 vehicle = researchItem->item; uint8 rideEntryIndex = _vehicleTypeToRideEntryMap[vehicle]; if (!_researchRideEntryUsed[rideEntryIndex]) { _researchRideEntryUsed[rideEntryIndex] = true; research_insert_ride_entry(rideEntryIndex, researched); } } void S4Importer::ImportParkName() { const char * parkName = _s4.scenario_name; if (is_user_string_id((rct_string_id)_s4.park_name_string_index)) { const char * userString = GetUserString(_s4.park_name_string_index); if (userString[0] != '\0') { parkName = userString; } } rct_string_id stringId = user_string_allocate(4, parkName); if (stringId != 0) { gParkName = stringId; gParkNameArgs = 0; } } void S4Importer::ImportParkFlags() { // Date and srand gCurrentTicks = _s4.ticks; gScenarioSrand0 = _s4.random_a; gScenarioSrand1 = _s4.random_b; gDateMonthsElapsed = _s4.month; gDateMonthTicks = _s4.day; // Park rating gParkRating = _s4.park_rating; for (int i = 0; i < 32; i++) { gParkRatingHistory[i] = _s4.park_rating_history[i]; } // Awards for (int i = 0; i < 4; i++) { gCurrentAwards[i] = _s4.awards[i]; } // Number of guests history for (int i = 0; i < 32; i++) { gGuestsInParkHistory[i] = _s4.guests_in_park_history[i]; } // News items rct_news_item *newsItems = RCT2_ADDRESS(RCT2_ADDRESS_NEWS_ITEM_LIST, rct_news_item); for (int i = 0; i < 61; i++) { newsItems[i] = _s4.messages[i]; } // Initial guest status gGuestInitialCash = _s4.guest_initial_cash; gGuestInitialHunger = _s4.guest_initial_hunger; gGuestInitialThirst = _s4.guest_initial_thirst; // 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; if (!(_s4.park_flags & RCT1_PARK_FLAGS_PARK_ENTRY_LOCKED_AT_FREE)) { gCheatsUnlockAllPrices = true; } // RCT2 uses two flags for no money (for cheat detection). 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; } } void S4Importer::ImportClimate() { gClimate = _s4.climate; gClimateUpdateTimer = _s4.climate_timer; gClimateCurrentTemperature = _s4.temperature; gClimateCurrentWeather = _s4.weather; gClimateCurrentWeatherEffect = 0; gClimateCurrentWeatherGloom = _s4.weather_gloom; gClimateCurrentRainLevel = _s4.rain; gClimateNextTemperature = _s4.target_temperature; gClimateNextWeather = _s4.target_weather; gClimateNextWeatherEffect = 0; gClimateNextWeatherGloom = _s4.target_weather_gloom; gClimateNextRainLevel = _s4.target_rain; } void S4Importer::ImportScenarioNameDetails() { rct_s6_info * s6Info = gS6Info; String::Set(s6Info->name, sizeof(s6Info->name), _s4.scenario_name); String::Set(s6Info->details, sizeof(s6Info->details), ""); int scNumber = GetSCNumber(); 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) { String::Set(s6Info->name, sizeof(s6Info->name), language_get_string(localisedStringIds[0])); } if (localisedStringIds[2] != STR_NONE) { String::Set(s6Info->details, sizeof(s6Info->details), language_get_string(localisedStringIds[2])); } } } } } void S4Importer::ImportScenarioObjective() { gScenarioObjectiveType = _s4.scenario_objective_type; gScenarioObjectiveYear = _s4.scenario_objective_years; gScenarioObjectiveCurrency = _s4.scenario_objective_currency; gScenarioObjectiveNumGuests = _s4.scenario_objective_num_guests; } void S4Importer::ImportSavedView() { gSavedViewX = _s4.view_x; gSavedViewY = _s4.view_y; gSavedViewZoom = _s4.view_zoom; gSavedViewRotation = _s4.view_rotation; } void S4Importer::ClearExtraTileEntries() { // Reset the map tile pointers for (int i = 0; i < 0x10000; i++) { gMapElementTilePointers[i] = (rct_map_element *)-1; } // Get the first free map element rct_map_element * nextFreeMapElement = gMapElements; for (int i = 0; i < 128 * 128; i++) { do { } while (!map_element_is_last_for_tile(nextFreeMapElement++)); } rct_map_element * mapElement = gMapElements; rct_map_element * * tilePointer = gMapElementTilePointers; // 128 rows of map data from RCT1 map for (int x = 0; x < 128; x++) { // Assign the first half of this row for (int y = 0; y < 128; y++) { *tilePointer++ = mapElement; do { } while (!map_element_is_last_for_tile(mapElement++)); } // Fill the rest of the row with blank tiles for (int y = 0; y < 128; y++) { nextFreeMapElement->type = MAP_ELEMENT_TYPE_SURFACE; nextFreeMapElement->flags = MAP_ELEMENT_FLAG_LAST_TILE; nextFreeMapElement->base_height = 2; nextFreeMapElement->clearance_height = 0; nextFreeMapElement->properties.surface.slope = 0; nextFreeMapElement->properties.surface.terrain = 0; nextFreeMapElement->properties.surface.grass_length = GRASS_LENGTH_CLEAR_0; nextFreeMapElement->properties.surface.ownership = 0; *tilePointer++ = nextFreeMapElement++; } } // 128 extra rows left to fill with blank tiles for (int y = 0; y < 128 * 256; y++) { nextFreeMapElement->type = MAP_ELEMENT_TYPE_SURFACE; nextFreeMapElement->flags = MAP_ELEMENT_FLAG_LAST_TILE; nextFreeMapElement->base_height = 2; nextFreeMapElement->clearance_height = 0; nextFreeMapElement->properties.surface.slope = 0; nextFreeMapElement->properties.surface.terrain = 0; nextFreeMapElement->properties.surface.grass_length = GRASS_LENGTH_CLEAR_0; nextFreeMapElement->properties.surface.ownership = 0; *tilePointer++ = nextFreeMapElement++; } gNextFreeMapElement = nextFreeMapElement; } void S4Importer::FixColours() { colour_t colour; // The following code would be worth doing if we were able to import sprites // for (int i = 0; i < MAX_SPRITES; i++) // { // rct_unk_sprite * sprite = &(g_sprite_list[i].unknown); // switch (sprite->sprite_identifier) { // case SPRITE_IDENTIFIER_PEEP: // { // rct_peep * peep = (rct_peep*)sprite; // peep->tshirt_colour = RCT1ColourConversionTable[peep->tshirt_colour]; // peep->trousers_colour = RCT1ColourConversionTable[peep->trousers_colour]; // peep->balloon_colour = RCT1ColourConversionTable[peep->balloon_colour]; // peep->umbrella_colour = RCT1ColourConversionTable[peep->umbrella_colour]; // peep->hat_colour = RCT1ColourConversionTable[peep->hat_colour]; // break; // } // case SPRITE_IDENTIFIER_MISC: // { // rct_balloon * balloon = (rct_balloon*)sprite; // balloon->colour = RCT1ColourConversionTable[balloon->colour]; // balloon->var_2D = RCT1ColourConversionTable[balloon->var_2D]; // break; // } // } // } rct_map_element * mapElement = gMapElements; while (mapElement < gNextFreeMapElement) { if (mapElement->base_height != 255) { switch (map_element_get_type(mapElement)) { case MAP_ELEMENT_TYPE_SCENERY: colour = RCT1::GetColour(mapElement->properties.scenery.colour_1 & 0x1F); mapElement->properties.scenery.colour_1 &= 0xE0; mapElement->properties.scenery.colour_1 |= colour; // Copied from [rct2: 0x006A2956] switch (mapElement->properties.scenery.type) { case 157: // TGE1 (Geometric Sculpture) case 162: // TGE2 (Geometric Sculpture) case 168: // TGE3 (Geometric Sculpture) case 170: // TGE4 (Geometric Sculpture) case 171: // TGE5 (Geometric Sculpture) mapElement->properties.scenery.colour_2 = COLOUR_WHITE; break; } break; case MAP_ELEMENT_TYPE_FENCE: colour = ((mapElement->type & 0xC0) >> 3) | ((mapElement->properties.fence.type & 0xE0) >> 5); colour = RCT1::GetColour(colour); mapElement->type &= 0x3F; mapElement->properties.fence.type &= 0x1F; mapElement->type |= (colour & 0x18) << 3; mapElement->properties.fence.type |= (colour & 7) << 5; break; case MAP_ELEMENT_TYPE_SCENERY_MULTIPLE: colour = RCT1::GetColour(mapElement->properties.scenerymultiple.colour[0] & 0x1F); mapElement->properties.scenerymultiple.colour[0] &= 0xE0; mapElement->properties.scenerymultiple.colour[0] |= colour; colour = RCT1::GetColour(mapElement->properties.scenerymultiple.colour[1] & 0x1F); mapElement->properties.scenerymultiple.colour[1] &= 0xE0; mapElement->properties.scenerymultiple.colour[1] |= colour; break; } } mapElement++; } } void S4Importer::FixZ() { // The following code would be useful if we imported sprites // for (int i = 0; i < MAX_SPRITES; i++) // { // rct_unk_sprite * sprite = &(g_sprite_list[i].unknown); // if (sprite->sprite_identifier == SPRITE_IDENTIFIER_PEEP) { // rct_peep * peep = (rct_peep*)sprite; // peep->next_z /= 2; // RCT2_GLOBAL((int)peep + 0xCE, uint8) /= 2; // } // } rct_map_element * mapElement = gMapElements; while (mapElement < gNextFreeMapElement) { if (mapElement->base_height != 255) { mapElement->base_height /= 2; mapElement->clearance_height /= 2; } mapElement++; } RCT2_GLOBAL(0x01359208, uint16) = 7; } void S4Importer::FixPaths() { rct_map_element * mapElement = gMapElements; while (mapElement < gNextFreeMapElement) { switch (map_element_get_type(mapElement)) { case MAP_ELEMENT_TYPE_PATH: { // Type uint8 pathColour = mapElement->type & 3; uint8 pathType = (mapElement->properties.path.type & 0xF0) >> 4; pathType = (pathType << 2) | pathColour; uint8 entryIndex = _pathTypeToEntryMap[pathType]; mapElement->type &= 0xFC; mapElement->flags &= ~0x60; mapElement->flags &= ~MAP_ELEMENT_FLAG_BROKEN; mapElement->properties.path.type &= 0x0F; footpath_scenery_set_is_ghost(mapElement, false); if (RCT1::PathIsQueue(pathType)) { mapElement->type |= 1; } mapElement->properties.path.type |= entryIndex << 4; // Additions uint8 additionType = footpath_element_get_path_scenery(mapElement); if (additionType != RCT1_PATH_ADDITION_NONE) { uint8 normalisedType = RCT1::NormalisePathAddition(additionType); uint8 entryIndex = _pathAdditionTypeToEntryMap[normalisedType]; if (additionType != normalisedType) { mapElement->flags |= MAP_ELEMENT_FLAG_BROKEN; } footpath_element_set_path_scenery(mapElement, entryIndex + 1); } break; } case MAP_ELEMENT_TYPE_ENTRANCE: if (mapElement->properties.entrance.type == ENTRANCE_TYPE_PARK_ENTRANCE) { uint8 pathType = mapElement->properties.entrance.path_type; if (pathType == 0) { pathType = RCT1_FOOTPATH_TYPE_TARMAC_GRAY; } uint8 entryIndex = _pathTypeToEntryMap[pathType]; mapElement->properties.entrance.path_type = entryIndex & 0x7F; } break; } mapElement++; } } void S4Importer::FixWalls() { for (int x = 0; x < 128; x++) { for (int y = 0; y < 128; y++) { rct_map_element * mapElement = map_get_first_element_at(x, y); do { if (map_element_get_type(mapElement) == MAP_ELEMENT_TYPE_FENCE) { rct_map_element originalMapElement = *mapElement; map_element_remove(mapElement); uint8 var_05 = originalMapElement.properties.fence.item[0]; uint16 var_06 = originalMapElement.properties.fence.item[1] | (originalMapElement.properties.fence.item[2] << 8); for (int edge = 0; edge < 4; edge++) { int typeA = (var_05 >> (edge * 2)) & 3; int typeB = (var_06 >> (edge * 4)) & 0x0F; if (typeB != 0x0F) { int type = typeA | (typeB << 2); int colourA = ((originalMapElement.type & 0xC0) >> 3) | (originalMapElement.properties.fence.type >> 5); int colourB = 0; int colourC = 0; ConvertWall(&type, &colourA, &colourB, &colourC); type = _wallTypeToEntryMap[type]; map_place_fence(type, x * 32, y * 32, 0, edge, colourA, colourB, colourC, 169); } } break; } } while (!map_element_is_last_for_tile(mapElement++)); } } } void S4Importer::ConvertWall(int * type, int * colourA, int * colourB, int * colourC) { switch (*type) { case 12: // creepy gate *colourA = 24; break; case 26: // white wooden fence *type = 12; *colourA = 2; break; case 27: // red wooden fence *type = 12; *colourA = 25; break; case 50: // plate glass *colourA = 24; break; case 13: *colourB = *colourA; *colourA = 24; break; case 11: // tall castle wall with grey gate case 22: // brick wall with gate *colourB = 2; break; case 35: // wood post fence case 42: // tall grey castle wall case 43: // wooden fence with snow case 44: case 45: case 46: *colourA = 1; break; } } void S4Importer::FixBanners() { for (int x = 0; x < 128; x++) { for (int y = 0; y < 128; y++) { rct_map_element * mapElement = map_get_first_element_at(x, y); do { if (map_element_get_type(mapElement) == MAP_ELEMENT_TYPE_BANNER) { uint8 index = mapElement->properties.banner.index; rct_banner * src = &_s4.banners[index]; rct_banner * dst = &gBanners[index]; ImportBanner(dst, src); } } while (!map_element_is_last_for_tile(mapElement++)); } } } void S4Importer::ImportBanner(rct_banner * dst, rct_banner * src) { *dst = *src; dst->colour = RCT1::GetColour(src->colour); dst->string_idx = 778; if (is_user_string_id(src->string_idx)) { const char * bannerText = GetUserString(src->string_idx); if (!String::IsNullOrEmpty(bannerText)) { rct_string_id bannerTextStringId = user_string_allocate(128, bannerText); if (bannerTextStringId != 0) { dst->string_idx = bannerTextStringId; } } } } void S4Importer::FixTerrain() { map_element_iterator it; map_element_iterator_begin(&it); while (map_element_iterator_next(&it)) { rct_map_element * element = it.element; if (map_element_get_type(element) == MAP_ELEMENT_TYPE_SURFACE) { map_element_set_terrain(element, RCT1::GetTerrain(map_element_get_terrain(element))); map_element_set_terrain_edge(element, RCT1::GetTerrainEdge(map_element_get_terrain_edge(element))); } } } void S4Importer::FixEntrancePositions() { for (int i = 0; i < 4; i++) { gParkEntranceX[i] = (sint16)0x8000; } uint8 entranceIndex = 0; map_element_iterator it; map_element_iterator_begin(&it); while (map_element_iterator_next(&it) && entranceIndex < 4) { rct_map_element * element = it.element; if (map_element_get_type(element) != MAP_ELEMENT_TYPE_ENTRANCE) continue; if (element->properties.entrance.type != ENTRANCE_TYPE_PARK_ENTRANCE) continue; if ((element->properties.entrance.index & 0x0F) != 0) continue; gParkEntranceX[entranceIndex] = it.x * 32; gParkEntranceY[entranceIndex] = it.y * 32; gParkEntranceZ[entranceIndex] = element->base_height * 8; gParkEntranceDirection[entranceIndex] = element->type & 3; entranceIndex++; } } void S4Importer::FixMapElementEntryTypes() { map_element_iterator it; map_element_iterator_begin(&it); while (map_element_iterator_next(&it)) { rct_map_element * mapElement = it.element; switch (map_element_get_type(mapElement)) { case MAP_ELEMENT_TYPE_SCENERY: mapElement->properties.scenery.type = _smallSceneryTypeToEntryMap[mapElement->properties.scenery.type]; break; case MAP_ELEMENT_TYPE_SCENERY_MULTIPLE: { uint8 type = mapElement->properties.scenerymultiple.type & MAP_ELEMENT_LARGE_TYPE_MASK; mapElement->properties.scenerymultiple.type &= ~MAP_ELEMENT_LARGE_TYPE_MASK; mapElement->properties.scenerymultiple.type |= _largeSceneryTypeToEntryMap[type]; break; } } } } List * S4Importer::GetEntryList(uint8 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_SETS: return &_sceneryGroupEntries; } return nullptr; } const rct1_research_item * S4Importer::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; } } int S4Importer::GetSCNumber() { const utf8 * fileName = Path::GetFileName(_s4Path); if (tolower(fileName[0]) == 's' && tolower(fileName[1]) == 'c') { constexpr size_t maxDigits = 7; utf8 digitBuffer[maxDigits + 1]; utf8 * dst = digitBuffer; const utf8 * src = fileName + 2; for (size_t i = 0; i < maxDigits && *src != '.'; i++) { *dst++ = *src++; } *dst++ = 0; if (digitBuffer[0] == '0' && digitBuffer[1] == '\0') { return 0; } else { int digits = atoi(digitBuffer); return digits == 0 ? -1 : digits; } } else { return -1; } } const char * S4Importer::GetUserString(rct_string_id stringId) { return _s4.string_table[(stringId - 0x8000) % 1024]; } ///////////////////////////////////////// // C -> C++ transfer ///////////////////////////////////////// extern "C" { bool rct1_load_saved_game(const utf8 * path) { bool result; auto s4Importer = new S4Importer(); try { s4Importer->LoadSavedGame(path); s4Importer->Import(); result = true; } catch (Exception ex) { result = false; } delete s4Importer; return result; } bool rct1_load_scenario(const utf8 * path) { bool result; auto s4Importer = new S4Importer(); try { s4Importer->LoadScenario(path); s4Importer->Import(); result = true; } catch (Exception ex) { result = false; } delete s4Importer; return result; } colour_t rct1_get_colour(colour_t colour) { return RCT1::GetColour(colour); } /** * This function keeps a list of the preferred vehicle for every generic track * type, out of the available vehicle types in the current game. It determines * which picture is shown on the new ride tab and which train type is selected * by default. */ int vehicle_preference_compare(uint8 rideType, const char * a, const char * b) { List rideEntryOrder = RCT1::GetPreferedRideEntryOrder(rideType); for (const char * object : rideEntryOrder) { if (String::Equals(object, a, true)) { return -1; } if (String::Equals(object, b, true)) { return 1; } } return 0; } }