OpenRCT2/src/openrct2/rct2/S6Importer.cpp

2178 lines
86 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*****************************************************************************
* Copyright (c) 2014-2020 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 "../Context.h"
#include "../Diagnostic.h"
#include "../Editor.h"
#include "../Game.h"
#include "../GameState.h"
#include "../OpenRCT2.h"
#include "../ParkImporter.h"
#include "../config/Config.h"
#include "../core/Console.hpp"
#include "../core/FileStream.h"
#include "../core/IStream.hpp"
#include "../core/Numerics.hpp"
#include "../core/Path.hpp"
#include "../core/Random.hpp"
#include "../core/String.hpp"
#include "../interface/Viewport.h"
#include "../localisation/Date.h"
#include "../localisation/Localisation.h"
#include "../management/Award.h"
#include "../management/Finance.h"
#include "../management/Marketing.h"
#include "../management/NewsItem.h"
#include "../management/Research.h"
#include "../network/network.h"
#include "../object/ObjectLimits.h"
#include "../object/ObjectList.h"
#include "../object/ObjectManager.h"
#include "../object/ObjectRepository.h"
#include "../peep/Guest.h"
#include "../peep/RideUseSystem.h"
#include "../peep/Staff.h"
#include "../rct12/RCT12.h"
#include "../rct12/SawyerChunkReader.h"
#include "../rct12/SawyerEncoding.h"
#include "../rct2/RCT2.h"
#include "../ride/Ride.h"
#include "../ride/RideData.h"
#include "../ride/RideRatings.h"
#include "../ride/ShopItem.h"
#include "../ride/Station.h"
#include "../ride/Track.h"
#include "../ride/TrainManager.h"
#include "../ride/Vehicle.h"
#include "../scenario/Scenario.h"
#include "../scenario/ScenarioRepository.h"
#include "../util/SawyerCoding.h"
#include "../util/Util.h"
#include "../world/Balloon.h"
#include "../world/Climate.h"
#include "../world/Duck.h"
#include "../world/EntityList.h"
#include "../world/EntityTweener.h"
#include "../world/Entrance.h"
#include "../world/Fountain.h"
#include "../world/Litter.h"
#include "../world/MapAnimation.h"
#include "../world/MoneyEffect.h"
#include "../world/Park.h"
#include "../world/Particle.h"
#include "../world/Scenery.h"
#include "../world/Sprite.h"
#include "../world/Surface.h"
#include <algorithm>
#include <bitset>
#define DECRYPT_MONEY(money) (static_cast<money32>(Numerics::rol32((money) ^ 0xF4EC9621, 13)))
/**
* Class to import RollerCoaster Tycoon 2 scenarios (*.SC6) and saved games (*.SV6).
*/
class S6Importer final : public IParkImporter
{
private:
IObjectRepository& _objectRepository;
const utf8* _s6Path = nullptr;
rct_s6_data _s6{};
uint8_t _gameVersion = 0;
bool _isSV7 = false;
std::bitset<RCT12_MAX_RIDES_IN_PARK> _isFlatRide{};
ObjectEntryIndex _pathToSurfaceMap[16];
ObjectEntryIndex _pathToQueueSurfaceMap[16];
ObjectEntryIndex _pathToRailingMap[16];
public:
S6Importer(IObjectRepository& objectRepository)
: _objectRepository(objectRepository)
{
}
ParkLoadResult Load(const utf8* path) override
{
const utf8* extension = Path::GetExtension(path);
if (String::Equals(extension, ".sc6", true))
{
return LoadScenario(path);
}
if (String::Equals(extension, ".sv6", true))
{
return LoadSavedGame(path);
}
throw std::runtime_error("Invalid RCT2 park extension.");
}
ParkLoadResult LoadSavedGame(const utf8* path, bool skipObjectCheck = false) override
{
auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN);
auto result = LoadFromStream(&fs, false, skipObjectCheck);
_s6Path = path;
return result;
}
ParkLoadResult LoadScenario(const utf8* path, bool skipObjectCheck = false) override
{
auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN);
auto result = LoadFromStream(&fs, true, skipObjectCheck);
_s6Path = path;
return result;
}
ParkLoadResult LoadFromStream(
OpenRCT2::IStream* stream, bool isScenario, [[maybe_unused]] bool skipObjectCheck = false,
const utf8* path = String::Empty) override
{
if (isScenario && !gConfigGeneral.allow_loading_with_incorrect_checksum && !SawyerEncoding::ValidateChecksum(stream))
{
throw IOException("Invalid checksum.");
}
auto chunkReader = SawyerChunkReader(stream);
chunkReader.ReadChunk(&_s6.header, sizeof(_s6.header));
log_verbose("saved game classic_flag = 0x%02x", _s6.header.classic_flag);
if (isScenario)
{
if (_s6.header.type != S6_TYPE_SCENARIO)
{
throw std::runtime_error("Park is not a scenario.");
}
chunkReader.ReadChunk(&_s6.info, sizeof(_s6.info));
}
else
{
if (_s6.header.type != S6_TYPE_SAVEDGAME)
{
throw std::runtime_error("Park is not a saved game.");
}
}
if (_s6.header.classic_flag == 0xf)
{
throw UnsupportedRCTCFlagException(_s6.header.classic_flag);
}
// Read packed objects
// TODO try to contain this more and not store objects until later
for (uint16_t i = 0; i < _s6.header.num_packed_objects; i++)
{
_objectRepository.ExportPackedObject(stream);
}
if (path)
{
auto extension = path_get_extension(path);
_isSV7 = _stricmp(extension, ".sv7") == 0;
}
chunkReader.ReadChunk(&_s6.Objects, sizeof(_s6.Objects));
if (isScenario)
{
chunkReader.ReadChunk(&_s6.elapsed_months, 16);
chunkReader.ReadChunk(&_s6.tile_elements, sizeof(_s6.tile_elements));
chunkReader.ReadChunk(&_s6.next_free_tile_element_pointer_index, 2560076);
chunkReader.ReadChunk(&_s6.guests_in_park, 4);
chunkReader.ReadChunk(&_s6.last_guests_in_park, 8);
chunkReader.ReadChunk(&_s6.park_rating, 2);
chunkReader.ReadChunk(&_s6.active_research_types, 1082);
chunkReader.ReadChunk(&_s6.current_expenditure, 16);
chunkReader.ReadChunk(&_s6.park_value, 4);
chunkReader.ReadChunk(&_s6.completed_company_value, 483816);
}
else
{
chunkReader.ReadChunk(&_s6.elapsed_months, 16);
chunkReader.ReadChunk(&_s6.tile_elements, sizeof(_s6.tile_elements));
chunkReader.ReadChunk(&_s6.next_free_tile_element_pointer_index, 3048816);
}
_s6Path = path;
return ParkLoadResult(GetRequiredObjects());
}
bool GetDetails(scenario_index_entry* dst) override
{
*dst = {};
return false;
}
void Import() override
{
Initialise();
gEditorStep = _s6.info.editor_step;
gScenarioCategory = static_cast<SCENARIO_CATEGORY>(_s6.info.category);
// Some scenarios have their scenario details in UTF-8, due to earlier bugs in OpenRCT2.
auto loadMaybeUTF8 = [](std::string_view str) -> std::string {
return !IsLikelyUTF8(str) ? rct2_to_utf8(str, RCT2LanguageId::EnglishUK) : std::string(str);
};
if (_s6.header.type == S6_TYPE_SCENARIO)
{
gScenarioName = loadMaybeUTF8(_s6.info.name);
gScenarioDetails = loadMaybeUTF8(_s6.info.details);
}
else
{
// Saved games do not have an info chunk
gScenarioName = loadMaybeUTF8(_s6.scenario_name);
gScenarioDetails = loadMaybeUTF8(_s6.scenario_description);
}
gDateMonthsElapsed = static_cast<int32_t>(_s6.elapsed_months);
gDateMonthTicks = _s6.current_day;
gCurrentTicks = _s6.game_ticks_1;
scenario_rand_seed(_s6.scenario_srand_0, _s6.scenario_srand_1);
DetermineFlatRideStatus();
ImportTileElements();
ImportEntities();
gInitialCash = ToMoney64(_s6.initial_cash);
gBankLoan = ToMoney64(_s6.current_loan);
gParkFlags = _s6.park_flags;
gParkEntranceFee = _s6.park_entrance_fee;
// rct1_park_entrance_x
// rct1_park_entrance_y
// pad_013573EE
// rct1_park_entrance_z
ImportPeepSpawns();
gGuestChangeModifier = _s6.guest_count_change_modifier;
gResearchFundingLevel = _s6.current_research_level;
// pad_01357400
// _s6.researched_track_types_a
// _s6.researched_track_types_b
gNumGuestsInPark = _s6.guests_in_park;
gNumGuestsHeadingForPark = _s6.guests_heading_for_park;
for (size_t i = 0; i < RCT12_EXPENDITURE_TABLE_MONTH_COUNT; i++)
{
for (size_t j = 0; j < RCT12_EXPENDITURE_TYPE_COUNT; j++)
{
gExpenditureTable[i][j] = ToMoney64(_s6.expenditure_table[i][j]);
}
}
gNumGuestsInParkLastWeek = _s6.last_guests_in_park;
// pad_01357BCA
gStaffHandymanColour = _s6.handyman_colour;
gStaffMechanicColour = _s6.mechanic_colour;
gStaffSecurityColour = _s6.security_colour;
gParkRating = _s6.park_rating;
auto& park = OpenRCT2::GetContext()->GetGameState()->GetPark();
park.ResetHistories();
std::copy(std::begin(_s6.park_rating_history), std::end(_s6.park_rating_history), gParkRatingHistory);
for (size_t i = 0; i < std::size(_s6.guests_in_park_history); i++)
{
if (_s6.guests_in_park_history[i] != RCT12ParkHistoryUndefined)
{
gGuestsInParkHistory[i] = _s6.guests_in_park_history[i] * RCT12GuestsInParkHistoryFactor;
}
}
gResearchPriorities = _s6.active_research_types;
gResearchProgressStage = _s6.research_progress_stage;
if (_s6.last_researched_item_subject != RCT12_RESEARCHED_ITEMS_SEPARATOR)
gResearchLastItem = ResearchItem(
RCT12ResearchItem{ _s6.last_researched_item_subject, EnumValue(ResearchCategory::Transport) });
else
gResearchLastItem = std::nullopt;
// pad_01357CF8
if (_s6.next_research_item != RCT12_RESEARCHED_ITEMS_SEPARATOR)
gResearchNextItem = ResearchItem(RCT12ResearchItem{ _s6.next_research_item, _s6.next_research_category });
else
gResearchNextItem = std::nullopt;
gResearchProgress = _s6.research_progress;
gResearchExpectedDay = _s6.next_research_expected_day;
gResearchExpectedMonth = _s6.next_research_expected_month;
gGuestInitialHappiness = _s6.guest_initial_happiness;
gParkSize = _s6.park_size;
_guestGenerationProbability = _s6.guest_generation_probability;
gTotalRideValueForMoney = _s6.total_ride_value_for_money;
gMaxBankLoan = ToMoney64(_s6.maximum_loan);
gGuestInitialCash = _s6.guest_initial_cash;
gGuestInitialHunger = _s6.guest_initial_hunger;
gGuestInitialThirst = _s6.guest_initial_thirst;
gScenarioObjective.Type = _s6.objective_type;
gScenarioObjective.Year = _s6.objective_year;
// pad_013580FA
gScenarioObjective.Currency = _s6.objective_currency;
// In RCT2, the ride string IDs start at index STR_0002 and are directly mappable.
// This is not always the case in OpenRCT2, so we use the actual ride ID.
if (gScenarioObjective.Type == OBJECTIVE_BUILD_THE_BEST)
gScenarioObjective.RideId = _s6.objective_guests - RCT2_RIDE_STRING_START;
else
gScenarioObjective.NumGuests = _s6.objective_guests;
ImportMarketingCampaigns();
gCurrentExpenditure = ToMoney64(_s6.current_expenditure);
gCurrentProfit = ToMoney64(_s6.current_profit);
gWeeklyProfitAverageDividend = ToMoney64(_s6.weekly_profit_average_dividend);
gWeeklyProfitAverageDivisor = _s6.weekly_profit_average_divisor;
// pad_0135833A
gParkValue = ToMoney64(_s6.park_value);
for (size_t i = 0; i < RCT12_FINANCE_GRAPH_SIZE; i++)
{
gCashHistory[i] = ToMoney64(_s6.balance_history[i]);
gWeeklyProfitHistory[i] = ToMoney64(_s6.weekly_profit_history[i]);
gParkValueHistory[i] = ToMoney64(_s6.park_value_history[i]);
}
gScenarioCompletedCompanyValue = RCT12CompletedCompanyValueToOpenRCT2(_s6.completed_company_value);
gTotalAdmissions = _s6.total_admissions;
gTotalIncomeFromAdmissions = ToMoney64(_s6.income_from_admissions);
gCompanyValue = ToMoney64(_s6.company_value);
std::memcpy(gPeepWarningThrottle, _s6.peep_warning_throttle, sizeof(_s6.peep_warning_throttle));
// Awards
for (int32_t i = 0; i < RCT12_MAX_AWARDS; i++)
{
rct12_award* src = &_s6.awards[i];
Award* dst = &gCurrentAwards[i];
dst->Time = src->time;
dst->Type = src->type;
}
gLandPrice = _s6.land_price;
gConstructionRightsPrice = _s6.construction_rights_price;
// unk_01358774
// pad_01358776
// _s6.cd_key
_gameVersion = _s6.game_version_number;
gScenarioCompanyValueRecord = _s6.completed_company_value_record;
// _s6.loan_hash;
// pad_013587CA
gHistoricalProfit = ToMoney64(_s6.historical_profit);
// pad_013587D4
gScenarioCompletedBy = std::string_view(_s6.scenario_completed_name, sizeof(_s6.scenario_completed_name));
gCash = ToMoney64(DECRYPT_MONEY(_s6.cash));
// pad_013587FC
gParkRatingCasualtyPenalty = _s6.park_rating_casualty_penalty;
gMapSize = _s6.map_size;
gSamePriceThroughoutPark = _s6.same_price_throughout
| (static_cast<uint64_t>(_s6.same_price_throughout_extended) << 32);
_suggestedGuestMaximum = _s6.suggested_max_guests;
gScenarioParkRatingWarningDays = _s6.park_rating_warning_days;
gLastEntranceStyle = _s6.last_entrance_style;
// rct1_water_colour
// pad_01358842
ImportResearchList();
gMapBaseZ = _s6.map_base_z;
gBankLoanInterestRate = _s6.current_interest_rate;
// pad_0135934B
// Preserve compatibility with vanilla RCT2's save format.
gParkEntrances.clear();
for (uint8_t i = 0; i < RCT12_MAX_PARK_ENTRANCES; i++)
{
if (_s6.park_entrance_x[i] != LOCATION_NULL)
{
CoordsXYZD entrance;
entrance.x = _s6.park_entrance_x[i];
entrance.y = _s6.park_entrance_y[i];
entrance.z = _s6.park_entrance_z[i];
entrance.direction = _s6.park_entrance_direction[i];
gParkEntrances.push_back(entrance);
}
}
if (_s6.header.type == S6_TYPE_SCENARIO)
{
// _s6.scenario_filename is wrong for some RCT2 expansion scenarios, so we use the real filename
gScenarioFileName = String::ToStd(Path::GetFileName(_s6Path));
}
else
{
// For savegames the filename can be arbitrary, so we have no choice but to rely on the name provided
gScenarioFileName = std::string(String::ToStringView(_s6.scenario_filename, std::size(_s6.scenario_filename)));
}
gCurrentRealTimeTicks = 0;
ImportRides();
gSavedAge = _s6.saved_age;
gSavedView = ScreenCoordsXY{ _s6.saved_view_x, _s6.saved_view_y };
gSavedViewZoom = _s6.saved_view_zoom;
gSavedViewRotation = _s6.saved_view_rotation;
ImportRideRatingsCalcData();
ImportRideMeasurements();
gNextGuestNumber = _s6.next_guest_index;
gGrassSceneryTileLoopPosition = _s6.grass_and_scenery_tilepos;
// unk_13CA73E
// pad_13CA73F
// unk_13CA740
gClimate = ClimateType{ _s6.climate };
// pad_13CA741;
// byte_13CA742
// pad_013CA747
gClimateUpdateTimer = _s6.climate_update_timer;
gClimateCurrent.Weather = WeatherType{ _s6.current_weather };
gClimateNext.Weather = WeatherType{ _s6.next_weather };
gClimateCurrent.Temperature = _s6.temperature;
gClimateNext.Temperature = _s6.next_temperature;
gClimateCurrent.WeatherEffect = WeatherEffectType{ _s6.current_weather_effect };
gClimateNext.WeatherEffect = WeatherEffectType{ _s6.next_weather_effect };
gClimateCurrent.WeatherGloom = _s6.current_weather_gloom;
gClimateNext.WeatherGloom = _s6.next_weather_gloom;
gClimateCurrent.Level = static_cast<WeatherLevel>(_s6.current_weather_level);
gClimateNext.Level = static_cast<WeatherLevel>(_s6.next_weather_level);
// News items
News::InitQueue();
for (size_t i = 0; i < RCT12_MAX_NEWS_ITEMS; i++)
{
const rct12_news_item* src = &_s6.news_items[i];
News::Item* dst = &gNewsItems[i];
if (src->Type < News::ItemTypeCount)
{
dst->Type = static_cast<News::ItemType>(src->Type);
dst->Flags = src->Flags;
dst->Assoc = src->Assoc;
dst->Ticks = src->Ticks;
dst->MonthYear = src->MonthYear;
dst->Day = src->Day;
dst->Text = ConvertFormattedStringToOpenRCT2(std::string_view(src->Text, sizeof(src->Text)));
}
else
{
// In case where news item type is broken, consider all remaining news items invalid.
log_error("Invalid news type 0x%x for news item %d, ignoring remaining news items", src->Type, i);
// Still need to set the correct type to properly terminate the queue
dst->Type = News::ItemType::Null;
break;
}
}
// pad_13CE730
// rct1_scenario_flags
gWidePathTileLoopPosition.x = _s6.wide_path_tile_loop_x;
gWidePathTileLoopPosition.y = _s6.wide_path_tile_loop_y;
// pad_13CE778
// Fix and set dynamic variables
map_strip_ghost_flag_from_elements();
game_convert_strings_to_utf8();
map_count_remaining_land_rights();
determine_ride_entrance_and_exit_locations();
park.Name = GetUserString(_s6.park_name);
FixLandOwnership();
research_determine_first_of_type();
staff_update_greyed_patrol_areas();
CheatsReset();
ClearRestrictedScenery();
}
void FixLandOwnership() const
{
if (String::Equals(_s6.scenario_filename, "Europe - European Cultural Festival.SC6"))
{
// This scenario breaks pathfinding. Create passages between the worlds. (List is grouped by neighbouring tiles.)
// clang-format off
FixLandOwnershipTilesWithOwnership(
{
{ 67, 94 }, { 68, 94 }, { 69, 94 },
{ 58, 24 }, { 58, 25 }, { 58, 26 }, { 58, 27 }, { 58, 28 }, { 58, 29 }, { 58, 30 }, { 58, 31 }, { 58, 32 },
{ 26, 44 }, { 26, 45 },
{ 32, 79 }, { 32, 80 }, { 32, 81 },
},
OWNERSHIP_OWNED);
// clang-format on
}
else if (String::Equals(gScenarioFileName, "N America - Extreme Hawaiian Island.SC6"))
{
FixLandOwnershipTilesWithOwnership(
{
{ 132, 124 },
{ 133, 124 },
{ 133, 125 },
{ 133, 126 },
{ 119, 35 },
{ 132, 62 },
{ 133, 67 },
{ 136, 71 },
{ 87, 33 },
{ 87, 34 },
{ 90, 36 },
{ 91, 36 },
},
OWNERSHIP_OWNED);
// We set the doNotDowngrade flag for cases where the player has used a cheat to own all land.
FixLandOwnershipTilesWithOwnership(
{
{ 49, 99 },
{ 50, 99 },
{ 88, 110 },
},
OWNERSHIP_AVAILABLE, true);
}
}
void ImportRides()
{
for (uint8_t index = 0; index < RCT12_MAX_RIDES_IN_PARK; index++)
{
auto src = &_s6.rides[index];
if (src->type != RIDE_TYPE_NULL)
{
const auto rideId = static_cast<ride_id_t>(index);
auto dst = GetOrAllocateRide(rideId);
ImportRide(dst, src, rideId);
}
}
}
/**
* This code is needed to detect hacks where a tracked ride has been made invisible
* by setting its ride type to a flat ride.
*
* The function should classify rides as follows:
* 1. If the ride type is tracked and its vehicles also belong on tracks, it should be classified as tracked.
* 2. If the ride type is a flat ride, but its vehicles belong on tracks,
* it should be classified as tracked (Crooked House mod).
* 3. If the ride type is tracked and its vehicles belong to a flat ride, it should be classified as tracked.
* 4. If the ride type is a flat ride and its vehicles also belong to a flat ride, it should be classified as a flat ride.
*/
void DetermineFlatRideStatus()
{
for (uint8_t index = 0; index < RCT12_MAX_RIDES_IN_PARK; index++)
{
auto src = &_s6.rides[index];
if (src->type == RIDE_TYPE_NULL)
continue;
auto subtype = RCTEntryIndexToOpenRCT2EntryIndex(src->subtype);
auto* rideEntry = get_ride_entry(subtype);
// If the ride is tracked, we dont need to check the vehicle any more.
if (!GetRideTypeDescriptor(src->type).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
{
_isFlatRide[index] = false;
continue;
}
// We have established the ride type is a flat ride, which means the vehicle now determines whether it is a
// true flat ride (scenario 4) or a tracked ride with an invisibility hack (scenario 2).
ObjectEntryIndex originalRideType = src->type;
if (rideEntry != nullptr)
{
originalRideType = ride_entry_get_first_non_null_ride_type(rideEntry);
}
const auto isFlatRide = GetRideTypeDescriptor(originalRideType).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE);
_isFlatRide.set(static_cast<size_t>(index), isFlatRide);
}
}
bool IsFlatRide(const uint8_t rct12RideIndex)
{
if (rct12RideIndex == RCT12_RIDE_ID_NULL)
return false;
return _isFlatRide[rct12RideIndex];
}
void ImportRide(Ride* dst, const rct2_ride* src, const ride_id_t rideIndex)
{
*dst = {};
dst->id = rideIndex;
ObjectEntryIndex rideType = src->type;
auto subtype = RCTEntryIndexToOpenRCT2EntryIndex(src->subtype);
if (RCT2RideTypeNeedsConversion(src->type))
{
auto* rideEntry = get_ride_entry(subtype);
if (rideEntry != nullptr)
{
rideType = RCT2RideTypeToOpenRCT2RideType(src->type, rideEntry);
}
}
if (rideType >= RIDE_TYPE_COUNT)
{
log_error("Invalid ride type for a ride in this save.");
throw UnsupportedRideTypeException(rideType);
}
dst->type = rideType;
dst->subtype = subtype;
// pad_002;
dst->mode = static_cast<RideMode>(src->mode);
dst->colour_scheme_type = src->colour_scheme_type;
for (uint8_t i = 0; i < RCT2_MAX_CARS_PER_TRAIN; i++)
{
dst->vehicle_colours[i].Body = src->vehicle_colours[i].body_colour;
dst->vehicle_colours[i].Trim = src->vehicle_colours[i].trim_colour;
}
// pad_046;
dst->status = static_cast<RideStatus>(src->status);
dst->default_name_number = src->name_arguments_number;
if (is_user_string_id(src->name))
{
dst->custom_name = GetUserString(src->name);
}
else
{
dst->default_name_number = src->name_arguments_number;
}
if (src->overall_view.IsNull())
{
dst->overall_view.SetNull();
}
else
{
auto tileLoc = TileCoordsXY(src->overall_view.x, src->overall_view.y);
dst->overall_view = tileLoc.ToCoordsXY();
}
for (int32_t i = 0; i < RCT12_MAX_STATIONS_PER_RIDE; i++)
{
if (src->station_starts[i].IsNull())
{
dst->stations[i].Start.SetNull();
}
else
{
auto tileStartLoc = TileCoordsXY(src->station_starts[i].x, src->station_starts[i].y);
dst->stations[i].Start = tileStartLoc.ToCoordsXY();
}
dst->stations[i].Height = src->station_heights[i];
dst->stations[i].Length = src->station_length[i];
dst->stations[i].Depart = src->station_depart[i];
dst->stations[i].TrainAtStation = src->train_at_station[i];
// Direction is fixed later.
if (src->entrances[i].IsNull())
ride_clear_entrance_location(dst, i);
else
ride_set_entrance_location(dst, i, { src->entrances[i].x, src->entrances[i].y, src->station_heights[i], 0 });
if (src->exits[i].IsNull())
ride_clear_exit_location(dst, i);
else
ride_set_exit_location(dst, i, { src->exits[i].x, src->exits[i].y, src->station_heights[i], 0 });
dst->stations[i].LastPeepInQueue = src->last_peep_in_queue[i];
dst->stations[i].SegmentLength = src->length[i];
dst->stations[i].SegmentTime = src->time[i];
dst->stations[i].QueueTime = src->queue_time[i];
dst->stations[i].QueueLength = src->queue_length[i];
}
// All other values take 0 as their default. Since they're already memset to that, no need to do it again.
for (int32_t i = RCT12_MAX_STATIONS_PER_RIDE; i < MAX_STATIONS; i++)
{
dst->stations[i].Start.SetNull();
dst->stations[i].TrainAtStation = RideStation::NO_TRAIN;
ride_clear_entrance_location(dst, i);
ride_clear_exit_location(dst, i);
dst->stations[i].LastPeepInQueue = SPRITE_INDEX_NULL;
}
for (int32_t i = 0; i <= RCT2_MAX_VEHICLES_PER_RIDE; i++)
{
dst->vehicles[i] = src->vehicles[i];
}
for (int32_t i = RCT2_MAX_VEHICLES_PER_RIDE; i <= MAX_VEHICLES_PER_RIDE; i++)
{
dst->vehicles[i] = SPRITE_INDEX_NULL;
}
dst->depart_flags = src->depart_flags;
dst->num_stations = src->num_stations;
dst->num_vehicles = src->num_vehicles;
dst->num_cars_per_train = src->num_cars_per_train;
dst->proposed_num_vehicles = src->proposed_num_vehicles;
dst->proposed_num_cars_per_train = src->proposed_num_cars_per_train;
dst->max_trains = src->max_trains;
dst->MinCarsPerTrain = src->GetMinCarsPerTrain();
dst->MaxCarsPerTrain = src->GetMaxCarsPerTrain();
dst->min_waiting_time = src->min_waiting_time;
dst->max_waiting_time = src->max_waiting_time;
// Includes time_limit, num_laps, launch_speed, speed, rotations
dst->operation_option = src->operation_option;
dst->boat_hire_return_direction = src->boat_hire_return_direction;
dst->boat_hire_return_position = { src->boat_hire_return_position.x, src->boat_hire_return_position.y };
dst->special_track_elements = src->special_track_elements;
// pad_0D6[2];
dst->max_speed = src->max_speed;
dst->average_speed = src->average_speed;
dst->current_test_segment = src->current_test_segment;
dst->average_speed_test_timeout = src->average_speed_test_timeout;
// pad_0E2[0x2];
dst->max_positive_vertical_g = src->max_positive_vertical_g;
dst->max_negative_vertical_g = src->max_negative_vertical_g;
dst->max_lateral_g = src->max_lateral_g;
dst->previous_vertical_g = src->previous_vertical_g;
dst->previous_lateral_g = src->previous_lateral_g;
// pad_106[0x2];
dst->testing_flags = src->testing_flags;
if (src->cur_test_track_location.IsNull())
{
dst->CurTestTrackLocation.SetNull();
}
else
{
dst->CurTestTrackLocation = { src->cur_test_track_location.x, src->cur_test_track_location.y,
src->cur_test_track_z };
}
dst->turn_count_default = src->turn_count_default;
dst->turn_count_banked = src->turn_count_banked;
dst->turn_count_sloped = src->turn_count_sloped;
if (dst->type == RIDE_TYPE_MINI_GOLF)
dst->holes = src->inversions & 0x1F;
else
dst->inversions = src->inversions & 0x1F;
dst->sheltered_eighths = src->inversions >> 5;
dst->drops = src->drops;
dst->start_drop_height = src->start_drop_height;
dst->highest_drop_height = src->highest_drop_height;
dst->sheltered_length = src->sheltered_length;
dst->var_11C = src->var_11C;
dst->num_sheltered_sections = src->num_sheltered_sections;
dst->cur_num_customers = src->cur_num_customers;
dst->num_customers_timeout = src->num_customers_timeout;
for (uint8_t i = 0; i < RCT2_CUSTOMER_HISTORY_SIZE; i++)
{
dst->num_customers[i] = src->num_customers[i];
}
dst->price[0] = src->price;
for (uint8_t i = 0; i < 2; i++)
{
dst->ChairliftBullwheelLocation[i] = { src->chairlift_bullwheel_location[i].x,
src->chairlift_bullwheel_location[i].y, src->chairlift_bullwheel_z[i] };
}
dst->ratings = src->ratings;
dst->value = src->value;
dst->chairlift_bullwheel_rotation = src->chairlift_bullwheel_rotation;
dst->satisfaction = src->satisfaction;
dst->satisfaction_time_out = src->satisfaction_time_out;
dst->satisfaction_next = src->satisfaction_next;
dst->window_invalidate_flags = src->window_invalidate_flags;
// pad_14E[0x02];
dst->total_customers = src->total_customers;
dst->total_profit = ToMoney64(src->total_profit);
dst->popularity = src->popularity;
dst->popularity_time_out = src->popularity_time_out;
dst->popularity_next = src->popularity_next;
ImportNumRiders(dst, rideIndex);
dst->music_tune_id = src->music_tune_id;
dst->slide_in_use = src->slide_in_use;
// Includes maze_tiles
dst->slide_peep = src->slide_peep;
// pad_160[0xE];
dst->slide_peep_t_shirt_colour = src->slide_peep_t_shirt_colour;
// pad_16F[0x7];
dst->spiral_slide_progress = src->spiral_slide_progress;
// pad_177[0x9];
dst->build_date = static_cast<int32_t>(src->build_date);
dst->upkeep_cost = src->upkeep_cost;
dst->race_winner = src->race_winner;
// pad_186[0x02];
dst->music_position = src->music_position;
dst->breakdown_reason_pending = src->breakdown_reason_pending;
dst->mechanic_status = src->mechanic_status;
dst->mechanic = src->mechanic;
dst->inspection_station = src->inspection_station;
dst->broken_vehicle = src->broken_vehicle;
dst->broken_car = src->broken_car;
dst->breakdown_reason = src->breakdown_reason;
dst->price[1] = src->price_secondary;
dst->reliability = src->reliability;
dst->unreliability_factor = src->unreliability_factor;
dst->downtime = src->downtime;
dst->inspection_interval = src->inspection_interval;
dst->last_inspection = src->last_inspection;
for (uint8_t i = 0; i < RCT2_DOWNTIME_HISTORY_SIZE; i++)
{
dst->downtime_history[i] = src->downtime_history[i];
}
dst->no_primary_items_sold = src->no_primary_items_sold;
dst->no_secondary_items_sold = src->no_secondary_items_sold;
dst->breakdown_sound_modifier = src->breakdown_sound_modifier;
dst->not_fixed_timeout = src->not_fixed_timeout;
dst->last_crash_type = src->last_crash_type;
dst->connected_message_throttle = src->connected_message_throttle;
dst->income_per_hour = ToMoney64(src->income_per_hour);
dst->profit = ToMoney64(src->profit);
for (uint8_t i = 0; i < RCT12_NUM_COLOUR_SCHEMES; i++)
{
dst->track_colour[i].main = src->track_colour_main[i];
dst->track_colour[i].additional = src->track_colour_additional[i];
dst->track_colour[i].supports = src->track_colour_supports[i];
}
// This stall was not colourable in RCT2.
if (dst->type == RIDE_TYPE_FOOD_STALL)
{
auto object = object_entry_get_object(ObjectType::Ride, dst->subtype);
if (object != nullptr && object->GetIdentifier() == "rct2.icecr1")
{
dst->track_colour[0].main = COLOUR_LIGHT_BLUE;
}
}
auto musicStyle = OBJECT_ENTRY_INDEX_NULL;
if (GetRideTypeDescriptor(dst->type).HasFlag(RIDE_TYPE_FLAG_ALLOW_MUSIC))
{
musicStyle = src->music;
}
dst->music = musicStyle;
// In SV7, "plain" entrances are invisible.
auto entranceStyle = OBJECT_ENTRY_INDEX_NULL;
if (!_isSV7 && GetRideTypeDescriptor(dst->type).HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT))
{
entranceStyle = src->entrance_style;
}
dst->entrance_style = entranceStyle;
dst->vehicle_change_timeout = src->vehicle_change_timeout;
dst->num_block_brakes = src->num_block_brakes;
dst->lift_hill_speed = src->lift_hill_speed;
dst->guests_favourite = src->guests_favourite;
dst->lifecycle_flags = src->lifecycle_flags;
for (uint8_t i = 0; i < RCT2_MAX_CARS_PER_TRAIN; i++)
{
dst->vehicle_colours[i].Ternary = src->vehicle_colours_extended[i];
}
dst->total_air_time = src->total_air_time;
dst->current_test_station = src->current_test_station;
dst->num_circuits = src->num_circuits;
dst->CableLiftLoc = { src->cable_lift_x, src->cable_lift_y, src->cable_lift_z * COORDS_Z_STEP };
// pad_1FD;
dst->cable_lift = src->cable_lift;
// pad_208[0x58];
}
void ImportRideRatingsCalcData()
{
const auto& src = _s6.ride_ratings_calc_data;
auto& dst = gRideRatingUpdateState;
dst = {};
dst.Proximity = { src.proximity_x, src.proximity_y, src.proximity_z };
dst.ProximityStart = { src.proximity_start_x, src.proximity_start_y, src.proximity_start_z };
dst.CurrentRide = RCT12RideIdToOpenRCT2RideId(src.current_ride);
dst.State = src.state;
if (src.current_ride < RCT12_MAX_RIDES_IN_PARK && _s6.rides[src.current_ride].type < std::size(RideTypeDescriptors))
dst.ProximityTrackType = RCT2TrackTypeToOpenRCT2(
src.proximity_track_type, _s6.rides[src.current_ride].type, IsFlatRide(src.current_ride));
else
dst.ProximityTrackType = 0xFF;
dst.ProximityBaseHeight = src.proximity_base_height;
dst.ProximityTotal = src.proximity_total;
for (size_t i = 0; i < std::size(src.proximity_scores); i++)
{
dst.ProximityScores[i] = src.proximity_scores[i];
}
dst.AmountOfBrakes = src.num_brakes;
dst.AmountOfReversers = src.num_reversers;
dst.StationFlags = src.station_flags;
}
void ImportRideMeasurements()
{
for (const auto& src : _s6.ride_measurements)
{
if (src.ride_index != RCT12_RIDE_ID_NULL)
{
const auto rideId = static_cast<ride_id_t>(src.ride_index);
auto ride = get_ride(rideId);
if (ride != nullptr)
{
ride->measurement = std::make_unique<RideMeasurement>();
ImportRideMeasurement(*ride->measurement, src);
}
}
}
}
void ImportRideMeasurement(RideMeasurement& dst, const RCT12RideMeasurement& src)
{
dst.flags = src.flags;
dst.last_use_tick = src.last_use_tick;
dst.num_items = src.num_items;
dst.current_item = src.current_item;
dst.vehicle_index = src.vehicle_index;
dst.current_station = src.current_station;
for (size_t i = 0; i < std::size(src.velocity); i++)
{
dst.velocity[i] = src.velocity[i];
dst.altitude[i] = src.altitude[i];
dst.vertical[i] = src.vertical[i];
dst.lateral[i] = src.lateral[i];
}
}
void ImportResearchList()
{
bool invented = true;
for (const auto& researchItem : _s6.research_items)
{
if (researchItem.IsInventedEndMarker())
{
invented = false;
continue;
}
if (researchItem.IsUninventedEndMarker() || researchItem.IsRandomEndMarker())
{
break;
}
if (invented)
gResearchItemsInvented.emplace_back(researchItem);
else
gResearchItemsUninvented.emplace_back(researchItem);
}
}
void ImportBanner(Banner* dst, const RCT12Banner* src)
{
auto id = dst->id;
*dst = {};
dst->id = id;
dst->type = RCTEntryIndexToOpenRCT2EntryIndex(src->type);
dst->flags = src->flags;
if (!(src->flags & BANNER_FLAG_LINKED_TO_RIDE) && is_user_string_id(src->string_idx))
{
dst->text = GetUserString(src->string_idx);
}
if (src->flags & BANNER_FLAG_LINKED_TO_RIDE)
{
dst->ride_index = RCT12RideIdToOpenRCT2RideId(src->ride_index);
}
else
{
dst->colour = src->colour;
}
dst->text_colour = src->text_colour;
dst->position.x = src->x;
dst->position.y = src->y;
}
void Initialise()
{
OpenRCT2::GetContext()->GetGameState()->InitAll(_s6.map_size);
}
/**
* Imports guest entry points.
* Includes fixes for incorrectly set guest entry points in some scenarios.
*/
void ImportPeepSpawns()
{
// Many WW and TT have scenario_filename fields containing an incorrect filename. Check for both this filename
// and the corrected filename.
// In this park, peep_spawns[0] is incorrect, and peep_spawns[1] is correct.
if (String::Equals(_s6.scenario_filename, "WW South America - Rio Carnival.SC6")
|| String::Equals(_s6.scenario_filename, "South America - Rio Carnival.SC6"))
{
_s6.peep_spawns[0] = { 2160, 3167, 6, 1 };
_s6.peep_spawns[1].x = RCT12_PEEP_SPAWN_UNDEFINED;
}
// In this park, peep_spawns[0] is correct. Just clear the other.
else if (
String::Equals(_s6.scenario_filename, "Great Wall of China Tourism Enhancement.SC6")
|| String::Equals(_s6.scenario_filename, "Asia - Great Wall of China Tourism Enhancement.SC6"))
{
_s6.peep_spawns[1].x = RCT12_PEEP_SPAWN_UNDEFINED;
}
// Amity Airfield has peeps entering from the corner of the tile, instead of the middle.
else if (String::Equals(_s6.scenario_filename, "Amity Airfield.SC6"))
{
_s6.peep_spawns[0].y = 1296;
}
// #9926: Africa - Oasis has peeps spawning on the edge underground near the entrance
else if (String::Equals(_s6.scenario_filename, "Africa - Oasis.SC6"))
{
_s6.peep_spawns[0].y = 2128;
_s6.peep_spawns[0].z = 7;
}
gPeepSpawns.clear();
for (size_t i = 0; i < RCT12_MAX_PEEP_SPAWNS; i++)
{
if (_s6.peep_spawns[i].x != RCT12_PEEP_SPAWN_UNDEFINED)
{
PeepSpawn spawn = { _s6.peep_spawns[i].x, _s6.peep_spawns[i].y, _s6.peep_spawns[i].z * 16,
_s6.peep_spawns[i].direction };
gPeepSpawns.push_back(spawn);
}
}
}
void ImportNumRiders(Ride* dst, const ride_id_t rideIndex)
{
// The number of riders might have overflown or underflown. Re-calculate the value.
uint16_t numRiders = 0;
for (const auto& sprite : _s6.sprites)
{
if (sprite.unknown.sprite_identifier == RCT12SpriteIdentifier::Peep)
{
if (sprite.peep.current_ride == static_cast<RCT12RideId>(rideIndex)
&& (static_cast<PeepState>(sprite.peep.state) == PeepState::OnRide
|| static_cast<PeepState>(sprite.peep.state) == PeepState::EnteringRide))
{
numRiders++;
}
}
}
dst->num_riders = numRiders;
}
void ImportTileElements()
{
// Build tile pointer cache (needed to get the first element at a certain location)
auto tilePointerIndex = TilePointerIndex<RCT12TileElement>(RCT2_MAXIMUM_MAP_SIZE_TECHNICAL, _s6.tile_elements);
std::vector<TileElement> tileElements;
bool nextElementInvisible = false;
bool restOfTileInvisible = false;
for (TileCoordsXY coords = { 0, 0 }; coords.y < MAXIMUM_MAP_SIZE_TECHNICAL; coords.y++)
{
for (coords.x = 0; coords.x < MAXIMUM_MAP_SIZE_TECHNICAL; coords.x++)
{
nextElementInvisible = false;
restOfTileInvisible = false;
if (coords.x >= RCT2_MAXIMUM_MAP_SIZE_TECHNICAL || coords.y >= RCT2_MAXIMUM_MAP_SIZE_TECHNICAL)
{
auto& dstElement = tileElements.emplace_back();
dstElement.ClearAs(TILE_ELEMENT_TYPE_SURFACE);
dstElement.SetLastForTile(true);
continue;
}
RCT12TileElement* srcElement = tilePointerIndex.GetFirstElementAt(coords);
// This might happen with damaged parks. Make sure there is *something* to avoid crashes.
if (srcElement == nullptr)
{
auto& dstElement = tileElements.emplace_back();
dstElement.ClearAs(TILE_ELEMENT_TYPE_SURFACE);
dstElement.SetLastForTile(true);
continue;
}
do
{
if (srcElement->base_height == RCT12_MAX_ELEMENT_HEIGHT)
{
continue;
}
auto tileElementType = static_cast<RCT12TileElementType>(srcElement->GetType());
if (tileElementType == RCT12TileElementType::Corrupt)
{
// One property of corrupt elements was to hide tops of tower tracks, and to avoid the next element from
// being hidden, multiple consecutive corrupt elements were sometimes used. This would essentially
// toggle the flag, so we inverse nextElementInvisible here instead of always setting it to true.
nextElementInvisible = !nextElementInvisible;
continue;
}
if (tileElementType == RCT12TileElementType::EightCarsCorrupt14
|| tileElementType == RCT12TileElementType::EightCarsCorrupt15)
{
restOfTileInvisible = true;
continue;
}
auto& dstElement = tileElements.emplace_back();
ImportTileElement(&dstElement, srcElement, nextElementInvisible || restOfTileInvisible);
nextElementInvisible = false;
} while (!(srcElement++)->IsLastForTile());
// Set last element flag in case the original last element was never added
if (tileElements.size() > 0)
{
tileElements.back().SetLastForTile(true);
}
}
}
SetTileElements(std::move(tileElements));
}
void ImportTileElement(TileElement* dst, const RCT12TileElement* src, bool invisible)
{
// Todo: allow for changing definition of OpenRCT2 tile element types - replace with a map
uint8_t tileElementType = src->GetType();
dst->ClearAs(tileElementType);
dst->SetDirection(src->GetDirection());
dst->SetBaseZ(src->base_height * COORDS_Z_STEP);
dst->SetClearanceZ(src->clearance_height * COORDS_Z_STEP);
// All saved in "flags"
dst->SetOccupiedQuadrants(src->GetOccupiedQuadrants());
dst->SetGhost(src->IsGhost());
dst->SetLastForTile(src->IsLastForTile());
dst->SetInvisible(invisible);
switch (tileElementType)
{
case TILE_ELEMENT_TYPE_SURFACE:
{
auto dst2 = dst->AsSurface();
auto src2 = src->AsSurface();
dst2->SetSlope(src2->GetSlope());
dst2->SetSurfaceStyle(src2->GetSurfaceStyle());
dst2->SetEdgeStyle(src2->GetEdgeStyle());
dst2->SetGrassLength(src2->GetGrassLength());
dst2->SetOwnership(src2->GetOwnership());
dst2->SetParkFences(src2->GetParkFences());
dst2->SetWaterHeight(src2->GetWaterHeight());
dst2->SetHasTrackThatNeedsWater(src2->HasTrackThatNeedsWater());
break;
}
case TILE_ELEMENT_TYPE_PATH:
{
auto dst2 = dst->AsPath();
auto src2 = src->AsPath();
auto pathEntryIndex = src2->GetEntryIndex();
auto surfaceEntry = src2->IsQueue() ? _pathToQueueSurfaceMap[pathEntryIndex]
: _pathToSurfaceMap[pathEntryIndex];
if (surfaceEntry == OBJECT_ENTRY_INDEX_NULL)
{
// Legacy footpath object
dst2->SetLegacyPathEntryIndex(pathEntryIndex);
}
else
{
// Surface / railing
dst2->SetSurfaceEntryIndex(surfaceEntry);
dst2->SetRailingsEntryIndex(_pathToRailingMap[pathEntryIndex]);
}
dst2->SetQueueBannerDirection(src2->GetQueueBannerDirection());
dst2->SetSloped(src2->IsSloped());
dst2->SetSlopeDirection(src2->GetSlopeDirection());
dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex()));
dst2->SetStationIndex(src2->GetStationIndex());
dst2->SetWide(src2->IsWide());
dst2->SetIsQueue(src2->IsQueue());
dst2->SetHasQueueBanner(src2->HasQueueBanner());
dst2->SetEdges(src2->GetEdges());
dst2->SetCorners(src2->GetCorners());
dst2->SetAddition(src2->GetAddition());
dst2->SetAdditionIsGhost(src2->AdditionIsGhost());
dst2->SetAdditionStatus(src2->GetAdditionStatus());
dst2->SetIsBroken(src2->IsBroken());
dst2->SetIsBlockedByVehicle(src2->IsBlockedByVehicle());
break;
}
case TILE_ELEMENT_TYPE_TRACK:
{
auto dst2 = dst->AsTrack();
auto src2 = src->AsTrack();
auto rideType = _s6.rides[src2->GetRideIndex()].type;
track_type_t trackType = static_cast<track_type_t>(src2->GetTrackType());
dst2->SetTrackType(RCT2TrackTypeToOpenRCT2(trackType, rideType, IsFlatRide(src2->GetRideIndex())));
dst2->SetRideType(rideType);
dst2->SetSequenceIndex(src2->GetSequenceIndex());
dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex()));
dst2->SetColourScheme(src2->GetColourScheme());
dst2->SetHasChain(src2->HasChain());
dst2->SetHasCableLift(src2->HasCableLift());
dst2->SetInverted(src2->IsInverted());
dst2->SetStationIndex(src2->GetStationIndex());
dst2->SetHasGreenLight(src2->HasGreenLight());
dst2->SetBlockBrakeClosed(src2->BlockBrakeClosed());
dst2->SetIsIndestructible(src2->IsIndestructible());
// Skipping IsHighlighted()
if (TrackTypeHasSpeedSetting(trackType))
{
dst2->SetBrakeBoosterSpeed(src2->GetBrakeBoosterSpeed());
}
else if (trackType == TrackElemType::OnRidePhoto)
{
dst2->SetPhotoTimeout(src2->GetPhotoTimeout());
}
// This has to be done last, since the maze entry shares fields with the colour and sequence fields.
if (rideType == RIDE_TYPE_MAZE)
{
dst2->SetMazeEntry(src2->GetMazeEntry());
}
else if (rideType == RIDE_TYPE_GHOST_TRAIN)
{
dst2->SetDoorAState(src2->GetDoorAState());
dst2->SetDoorBState(src2->GetDoorBState());
}
else
{
dst2->SetSeatRotation(src2->GetSeatRotation());
}
break;
}
case TILE_ELEMENT_TYPE_SMALL_SCENERY:
{
auto dst2 = dst->AsSmallScenery();
auto src2 = src->AsSmallScenery();
dst2->SetEntryIndex(src2->GetEntryIndex());
dst2->SetAge(src2->GetAge());
dst2->SetSceneryQuadrant(src2->GetSceneryQuadrant());
dst2->SetPrimaryColour(src2->GetPrimaryColour());
dst2->SetSecondaryColour(src2->GetSecondaryColour());
if (src2->NeedsSupports())
dst2->SetNeedsSupports();
break;
}
case TILE_ELEMENT_TYPE_ENTRANCE:
{
auto dst2 = dst->AsEntrance();
auto src2 = src->AsEntrance();
dst2->SetEntranceType(src2->GetEntranceType());
dst2->SetRideIndex(RCT12RideIdToOpenRCT2RideId(src2->GetRideIndex()));
dst2->SetStationIndex(src2->GetStationIndex());
dst2->SetSequenceIndex(src2->GetSequenceIndex());
if (src2->GetSequenceIndex() == 0)
{
auto pathEntryIndex = src2->GetPathType();
auto surfaceEntry = _pathToSurfaceMap[pathEntryIndex];
if (surfaceEntry == OBJECT_ENTRY_INDEX_NULL)
{
// Legacy footpath object
dst2->SetLegacyPathEntryIndex(pathEntryIndex);
}
else
{
// Surface
dst2->SetSurfaceEntryIndex(surfaceEntry);
}
}
else
{
dst2->SetSurfaceEntryIndex(OBJECT_ENTRY_INDEX_NULL);
}
break;
}
case TILE_ELEMENT_TYPE_WALL:
{
auto dst2 = dst->AsWall();
auto src2 = src->AsWall();
dst2->SetEntryIndex(src2->GetEntryIndex());
dst2->SetSlope(src2->GetSlope());
dst2->SetPrimaryColour(src2->GetPrimaryColour());
dst2->SetSecondaryColour(src2->GetSecondaryColour());
dst2->SetTertiaryColour(src2->GetTertiaryColour());
dst2->SetAnimationFrame(src2->GetAnimationFrame());
dst2->SetAcrossTrack(src2->IsAcrossTrack());
dst2->SetAnimationIsBackwards(src2->AnimationIsBackwards());
// Import banner information
dst2->SetBannerIndex(BANNER_INDEX_NULL);
auto entry = dst2->GetEntry();
if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE)
{
auto bannerIndex = src2->GetBannerIndex();
if (bannerIndex < std::size(_s6.banners))
{
auto srcBanner = &_s6.banners[bannerIndex];
auto dstBanner = GetOrCreateBanner(bannerIndex);
if (dstBanner == nullptr)
{
dst2->SetBannerIndex(BANNER_INDEX_NULL);
}
else
{
ImportBanner(dstBanner, srcBanner);
dst2->SetBannerIndex(src2->GetBannerIndex());
}
}
}
break;
}
case TILE_ELEMENT_TYPE_LARGE_SCENERY:
{
auto dst2 = dst->AsLargeScenery();
auto src2 = src->AsLargeScenery();
dst2->SetEntryIndex(src2->GetEntryIndex());
dst2->SetSequenceIndex(src2->GetSequenceIndex());
dst2->SetPrimaryColour(src2->GetPrimaryColour());
dst2->SetSecondaryColour(src2->GetSecondaryColour());
// Import banner information
dst2->SetBannerIndex(BANNER_INDEX_NULL);
auto entry = dst2->GetEntry();
if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE)
{
auto bannerIndex = src2->GetBannerIndex();
if (bannerIndex < std::size(_s6.banners))
{
auto srcBanner = &_s6.banners[bannerIndex];
auto dstBanner = GetOrCreateBanner(bannerIndex);
if (dstBanner == nullptr)
{
dst2->SetBannerIndex(BANNER_INDEX_NULL);
}
else
{
ImportBanner(dstBanner, srcBanner);
dst2->SetBannerIndex(src2->GetBannerIndex());
}
}
}
break;
}
case TILE_ELEMENT_TYPE_BANNER:
{
auto dst2 = dst->AsBanner();
auto src2 = src->AsBanner();
dst2->SetPosition(src2->GetPosition());
dst2->SetAllowedEdges(src2->GetAllowedEdges());
auto bannerIndex = src2->GetIndex();
if (bannerIndex < std::size(_s6.banners))
{
auto srcBanner = &_s6.banners[bannerIndex];
auto dstBanner = GetOrCreateBanner(bannerIndex);
if (dstBanner == nullptr)
{
dst2->SetIndex(BANNER_INDEX_NULL);
}
else
{
ImportBanner(dstBanner, srcBanner);
dst2->SetIndex(bannerIndex);
}
}
else
{
dst2->SetIndex(BANNER_INDEX_NULL);
}
break;
}
default:
assert(false);
}
}
void ImportMarketingCampaigns()
{
for (size_t i = 0; i < ADVERTISING_CAMPAIGN_COUNT; i++)
{
if (_s6.campaign_weeks_left[i] & CAMPAIGN_ACTIVE_FLAG)
{
MarketingCampaign campaign{};
campaign.Type = static_cast<uint8_t>(i);
campaign.WeeksLeft = _s6.campaign_weeks_left[i] & ~(CAMPAIGN_ACTIVE_FLAG | CAMPAIGN_FIRST_WEEK_FLAG);
if ((_s6.campaign_weeks_left[i] & CAMPAIGN_FIRST_WEEK_FLAG) != 0)
{
campaign.Flags |= MarketingCampaignFlags::FIRST_WEEK;
}
if (campaign.Type == ADVERTISING_CAMPAIGN_RIDE_FREE || campaign.Type == ADVERTISING_CAMPAIGN_RIDE)
{
campaign.RideId = RCT12RideIdToOpenRCT2RideId(_s6.campaign_ride_index[i]);
}
else if (campaign.Type == ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE)
{
campaign.ShopItemType = ShopItem(_s6.campaign_ride_index[i]);
}
gMarketingCampaigns.push_back(campaign);
}
}
}
void ImportStaffPatrolArea(Staff* staffmember, uint8_t staffId)
{
// First check staff mode as vanilla did not clean up patrol areas when switching from patrol to walk
// without doing this we could accidentally add a patrol when it didn't exist.
if (_s6.staff_modes[staffId] != RCT2StaffMode::Patrol)
{
return;
}
int32_t peepOffset = staffId * RCT12_PATROL_AREA_SIZE;
for (int32_t i = 0; i < RCT12_PATROL_AREA_SIZE; i++)
{
if (_s6.patrol_areas[peepOffset + i] == 0)
{
// No patrol for this area
continue;
}
// Loop over the bits of the uint32_t
for (int32_t j = 0; j < 32; j++)
{
int8_t bit = (_s6.patrol_areas[peepOffset + i] >> j) & 1;
if (bit == 0)
{
// No patrol for this area
continue;
}
// val contains the 6 highest bits of both the x and y coordinates
int32_t val = j | (i << 5);
int32_t x = val & 0x03F;
x <<= 7;
int32_t y = val & 0xFC0;
y <<= 1;
staffmember->SetPatrolArea({ x, y }, true);
}
}
}
void ImportEntities()
{
for (int32_t i = 0; i < RCT2_MAX_SPRITES; i++)
{
ImportEntity(_s6.sprites[i].unknown);
}
}
template<typename OpenRCT2_T> void ImportEntity(const RCT12SpriteBase& src);
void ImportEntityPeep(Peep* dst, const RCT2SpritePeep* src)
{
const auto isNullLocation = [](const rct12_xyzd8& pos) {
return pos.x == 0xFF && pos.y == 0xFF && pos.z == 0xFF && pos.direction == INVALID_DIRECTION;
};
ImportEntityCommonProperties(static_cast<EntityBase*>(dst), src);
if (is_user_string_id(src->name_string_idx))
{
dst->SetName(GetUserString(src->name_string_idx));
}
dst->NextLoc = { src->next_x, src->next_y, src->next_z * COORDS_Z_STEP };
dst->NextFlags = src->next_flags;
dst->State = static_cast<PeepState>(src->state);
dst->SubState = src->sub_state;
dst->SpriteType = static_cast<PeepSpriteType>(src->sprite_type);
dst->TshirtColour = src->tshirt_colour;
dst->TrousersColour = src->trousers_colour;
dst->DestinationX = src->destination_x;
dst->DestinationY = src->destination_y;
dst->DestinationTolerance = src->destination_tolerance;
dst->Var37 = src->var_37;
dst->Energy = src->energy;
dst->EnergyTarget = src->energy_target;
dst->Mass = src->mass;
dst->WindowInvalidateFlags = src->window_invalidate_flags;
dst->CurrentRide = RCT12RideIdToOpenRCT2RideId(src->current_ride);
dst->CurrentRideStation = src->current_ride_station;
dst->CurrentTrain = src->current_train;
dst->TimeToSitdown = src->time_to_sitdown;
dst->SpecialSprite = src->special_sprite;
dst->ActionSpriteType = static_cast<PeepActionSpriteType>(src->action_sprite_type);
dst->NextActionSpriteType = static_cast<PeepActionSpriteType>(src->next_action_sprite_type);
dst->ActionSpriteImageOffset = src->action_sprite_image_offset;
dst->Action = static_cast<PeepActionType>(src->action);
dst->ActionFrame = src->action_frame;
dst->StepProgress = src->step_progress;
dst->PeepDirection = src->direction;
dst->InteractionRideIndex = RCT12RideIdToOpenRCT2RideId(src->interaction_ride_index);
dst->Id = src->id;
dst->PathCheckOptimisation = src->path_check_optimisation;
dst->PeepFlags = src->peep_flags;
if (isNullLocation(src->pathfind_goal))
{
dst->PathfindGoal.SetNull();
dst->PathfindGoal.direction = INVALID_DIRECTION;
}
else
{
dst->PathfindGoal = { src->pathfind_goal.x, src->pathfind_goal.y, src->pathfind_goal.z,
src->pathfind_goal.direction };
}
for (size_t i = 0; i < std::size(src->pathfind_history); i++)
{
if (isNullLocation(src->pathfind_history[i]))
{
dst->PathfindHistory[i].SetNull();
dst->PathfindHistory[i].direction = INVALID_DIRECTION;
}
else
{
dst->PathfindHistory[i] = { src->pathfind_history[i].x, src->pathfind_history[i].y, src->pathfind_history[i].z,
src->pathfind_history[i].direction };
}
}
dst->WalkingFrameNum = src->no_action_frame_num;
}
constexpr EntityType GetEntityTypeFromRCT2Sprite(const RCT12SpriteBase* src)
{
EntityType output = EntityType::Null;
switch (src->sprite_identifier)
{
case RCT12SpriteIdentifier::Vehicle:
output = EntityType::Vehicle;
break;
case RCT12SpriteIdentifier::Peep:
if (RCT12PeepType(static_cast<const RCT2SpritePeep*>(src)->peep_type) == RCT12PeepType::Guest)
{
output = EntityType::Guest;
}
else
{
output = EntityType::Staff;
}
break;
case RCT12SpriteIdentifier::Misc:
switch (RCT12MiscEntityType(src->type))
{
case RCT12MiscEntityType::SteamParticle:
output = EntityType::SteamParticle;
break;
case RCT12MiscEntityType::MoneyEffect:
output = EntityType::MoneyEffect;
break;
case RCT12MiscEntityType::CrashedVehicleParticle:
output = EntityType::CrashedVehicleParticle;
break;
case RCT12MiscEntityType::ExplosionCloud:
output = EntityType::ExplosionCloud;
break;
case RCT12MiscEntityType::CrashSplash:
output = EntityType::CrashSplash;
break;
case RCT12MiscEntityType::ExplosionFlare:
output = EntityType::ExplosionFlare;
break;
case RCT12MiscEntityType::JumpingFountainWater:
case RCT12MiscEntityType::JumpingFountainSnow:
output = EntityType::JumpingFountain;
break;
case RCT12MiscEntityType::Balloon:
output = EntityType::Balloon;
break;
case RCT12MiscEntityType::Duck:
output = EntityType::Duck;
break;
default:
break;
}
break;
case RCT12SpriteIdentifier::Litter:
output = EntityType::Litter;
break;
default:
break;
}
return output;
}
void ImportEntityCommonProperties(EntityBase* dst, const RCT12SpriteBase* src)
{
dst->Type = GetEntityTypeFromRCT2Sprite(src);
dst->sprite_height_negative = src->sprite_height_negative;
dst->sprite_index = src->sprite_index;
dst->x = src->x;
dst->y = src->y;
dst->z = src->z;
dst->sprite_width = src->sprite_width;
dst->sprite_height_positive = src->sprite_height_positive;
dst->SpriteRect = ScreenRect(src->sprite_left, src->sprite_top, src->sprite_right, src->sprite_bottom);
dst->sprite_direction = src->sprite_direction;
}
void ImportEntity(const RCT12SpriteBase& src);
std::string GetUserString(rct_string_id stringId)
{
const auto originalString = _s6.custom_strings[(stringId - USER_STRING_START) % 1024];
auto originalStringView = std::string_view(
originalString, GetRCT2StringBufferLen(originalString, USER_STRING_MAX_LENGTH));
auto asUtf8 = rct2_to_utf8(originalStringView, RCT2LanguageId::EnglishUK);
auto justText = RCT12RemoveFormattingUTF8(asUtf8);
return justText.data();
}
ObjectList GetRequiredObjects()
{
std::fill(std::begin(_pathToSurfaceMap), std::end(_pathToSurfaceMap), OBJECT_ENTRY_INDEX_NULL);
std::fill(std::begin(_pathToQueueSurfaceMap), std::end(_pathToQueueSurfaceMap), OBJECT_ENTRY_INDEX_NULL);
std::fill(std::begin(_pathToRailingMap), std::end(_pathToRailingMap), OBJECT_ENTRY_INDEX_NULL);
ObjectList objectList;
int objectIt = 0;
ObjectEntryIndex surfaceCount = 0;
ObjectEntryIndex railingCount = 0;
for (int16_t objectType = EnumValue(ObjectType::Ride); objectType <= EnumValue(ObjectType::Water); objectType++)
{
for (int16_t i = 0; i < rct2_object_entry_group_counts[objectType]; i++, objectIt++)
{
auto entry = ObjectEntryDescriptor(_s6.Objects[objectIt]);
if (entry.HasValue())
{
if (objectType == EnumValue(ObjectType::Paths))
{
auto footpathMapping = GetFootpathSurfaceId(entry);
if (footpathMapping == nullptr)
{
// Unsupported footpath
objectList.SetObject(i, entry);
}
else
{
// We have surface objects for this footpath
auto surfaceIndex = objectList.Find(ObjectType::FootpathSurface, footpathMapping->NormalSurface);
if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL)
{
objectList.SetObject(ObjectType::FootpathSurface, surfaceCount, footpathMapping->NormalSurface);
surfaceIndex = surfaceCount++;
}
_pathToSurfaceMap[i] = surfaceIndex;
surfaceIndex = objectList.Find(ObjectType::FootpathSurface, footpathMapping->QueueSurface);
if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL)
{
objectList.SetObject(ObjectType::FootpathSurface, surfaceCount, footpathMapping->QueueSurface);
surfaceIndex = surfaceCount++;
}
_pathToQueueSurfaceMap[i] = surfaceIndex;
auto railingIndex = objectList.Find(ObjectType::FootpathRailings, footpathMapping->Railing);
if (railingIndex == OBJECT_ENTRY_INDEX_NULL)
{
objectList.SetObject(ObjectType::FootpathRailings, railingCount, footpathMapping->Railing);
railingIndex = railingCount++;
}
_pathToRailingMap[i] = railingIndex;
}
}
else
{
objectList.SetObject(i, entry);
}
}
}
}
SetDefaultRCT2TerrainObjects(objectList);
RCT12AddDefaultObjects(objectList);
return objectList;
}
};
template<> void S6Importer::ImportEntity<Vehicle>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<Vehicle>(baseSrc.sprite_index);
auto src = static_cast<const RCT2SpriteVehicle*>(&baseSrc);
const auto& ride = _s6.rides[src->ride];
ImportEntityCommonProperties(dst, src);
dst->SubType = Vehicle::Type(src->type);
dst->Pitch = src->Pitch;
dst->bank_rotation = src->bank_rotation;
dst->remaining_distance = src->remaining_distance;
dst->velocity = src->velocity;
dst->acceleration = src->acceleration;
dst->ride = static_cast<ride_id_t>(src->ride);
dst->vehicle_type = src->vehicle_type;
dst->colours = src->colours;
dst->track_progress = src->track_progress;
dst->TrackLocation = { src->track_x, src->track_y, src->track_z };
if (src->boat_location.IsNull() || static_cast<RideMode>(ride.mode) != RideMode::BoatHire
|| src->status != static_cast<uint8_t>(Vehicle::Status::TravellingBoat))
{
dst->BoatLocation.SetNull();
dst->SetTrackDirection(src->GetTrackDirection());
dst->SetTrackType(src->GetTrackType());
// RotationControlToggle and Booster are saved as the same track piece ID
// Which one the vehicle is using must be determined
if (IsFlatRide(src->ride))
{
dst->SetTrackType(RCT12FlatTrackTypeToOpenRCT2(src->GetTrackType()));
}
else if (src->GetTrackType() == TrackElemType::RotationControlToggleAlias)
{
// Merging hacks mean the track type that's appropriate for the ride type is not necessarily the track type the
// ride is on. It's possible to create unwanted behavior if a user layers spinning control track on top of
// booster track but this is unlikely since only two rides have spinning control track - by default they load as
// booster
TileElement* tileElement2 = map_get_track_element_at_of_type_seq(
dst->TrackLocation, TrackElemType::RotationControlToggle, 0);
if (tileElement2 != nullptr)
dst->SetTrackType(TrackElemType::RotationControlToggle);
}
}
else
{
dst->BoatLocation = TileCoordsXY{ src->boat_location.x, src->boat_location.y }.ToCoordsXY();
dst->SetTrackDirection(0);
dst->SetTrackType(0);
}
dst->next_vehicle_on_train = src->next_vehicle_on_train;
dst->prev_vehicle_on_ride = src->prev_vehicle_on_ride;
dst->next_vehicle_on_ride = src->next_vehicle_on_ride;
dst->var_44 = src->var_44;
dst->mass = src->mass;
dst->update_flags = src->update_flags;
dst->SwingSprite = src->SwingSprite;
dst->current_station = src->current_station;
dst->current_time = src->current_time;
dst->crash_z = src->crash_z;
Vehicle::Status statusSrc = Vehicle::Status::MovingToEndOfStation;
if (src->status <= static_cast<uint8_t>(Vehicle::Status::StoppedByBlockBrakes))
{
statusSrc = static_cast<Vehicle::Status>(src->status);
}
dst->status = statusSrc;
dst->sub_state = src->sub_state;
for (size_t i = 0; i < std::size(src->peep); i++)
{
dst->peep[i] = src->peep[i];
dst->peep_tshirt_colours[i] = src->peep_tshirt_colours[i];
}
dst->num_seats = src->num_seats;
dst->num_peeps = src->num_peeps;
dst->next_free_seat = src->next_free_seat;
dst->restraints_position = src->restraints_position;
dst->crash_x = src->crash_x;
dst->sound2_flags = src->sound2_flags;
dst->spin_sprite = src->spin_sprite;
dst->sound1_id = static_cast<OpenRCT2::Audio::SoundId>(src->sound1_id);
dst->sound1_volume = src->sound1_volume;
dst->sound2_id = static_cast<OpenRCT2::Audio::SoundId>(src->sound2_id);
dst->sound2_volume = src->sound2_volume;
dst->sound_vector_factor = src->sound_vector_factor;
dst->time_waiting = src->time_waiting;
dst->speed = src->speed;
dst->powered_acceleration = src->powered_acceleration;
dst->dodgems_collision_direction = src->dodgems_collision_direction;
dst->animation_frame = src->animation_frame;
dst->animationState = src->animationState;
dst->scream_sound_id = static_cast<OpenRCT2::Audio::SoundId>(src->scream_sound_id);
dst->TrackSubposition = VehicleTrackSubposition{ src->TrackSubposition };
dst->var_CE = src->var_CE;
dst->var_CF = src->var_CF;
dst->lost_time_out = src->lost_time_out;
dst->vertical_drop_countdown = src->vertical_drop_countdown;
dst->var_D3 = src->var_D3;
dst->mini_golf_current_animation = MiniGolfAnimation(src->mini_golf_current_animation);
dst->mini_golf_flags = src->mini_golf_flags;
dst->ride_subtype = RCTEntryIndexToOpenRCT2EntryIndex(src->ride_subtype);
dst->colours_extended = src->colours_extended;
dst->seat_rotation = src->seat_rotation;
dst->target_seat_rotation = src->target_seat_rotation;
dst->IsCrashedVehicle = src->flags & RCT12_SPRITE_FLAGS_IS_CRASHED_VEHICLE_SPRITE;
}
static uint32_t AdjustScenarioToCurrentTicks(const rct_s6_data& s6, uint32_t tick)
{
// Previously gScenarioTicks was used as a time point, now it's gCurrentTicks.
// gCurrentTicks and gScenarioTicks are now exported as the same, older saves that have a different
// scenario tick must account for the difference between the two.
uint32_t ticksElapsed = s6.scenario_ticks - tick;
return s6.game_ticks_1 - ticksElapsed;
}
template<> void S6Importer::ImportEntity<Guest>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<Guest>(baseSrc.sprite_index);
auto src = static_cast<const RCT2SpritePeep*>(&baseSrc);
ImportEntityPeep(dst, src);
dst->OutsideOfPark = static_cast<bool>(src->outside_of_park);
dst->GuestNumRides = src->no_of_rides;
dst->Happiness = src->happiness;
dst->HappinessTarget = src->happiness_target;
dst->Nausea = src->nausea;
dst->NauseaTarget = src->nausea_target;
dst->Hunger = src->hunger;
dst->Thirst = src->thirst;
dst->Toilet = src->toilet;
dst->TimeToConsume = src->time_to_consume;
dst->Intensity = static_cast<IntensityRange>(src->intensity);
dst->NauseaTolerance = static_cast<PeepNauseaTolerance>(src->nausea_tolerance);
dst->PaidOnDrink = src->paid_on_drink;
OpenRCT2::RideUse::GetHistory().Set(dst->sprite_index, RCT12GetRidesBeenOn(src));
OpenRCT2::RideUse::GetTypeHistory().Set(dst->sprite_index, RCT12GetRideTypesBeenOn(src));
dst->SetItemFlags(src->GetItemFlags());
dst->Photo1RideRef = RCT12RideIdToOpenRCT2RideId(src->photo1_ride_ref);
dst->Photo2RideRef = RCT12RideIdToOpenRCT2RideId(src->photo2_ride_ref);
dst->Photo3RideRef = RCT12RideIdToOpenRCT2RideId(src->photo3_ride_ref);
dst->Photo4RideRef = RCT12RideIdToOpenRCT2RideId(src->photo4_ride_ref);
dst->GuestNextInQueue = src->next_in_queue;
dst->TimeInQueue = src->time_in_queue;
dst->CashInPocket = src->cash_in_pocket;
dst->CashSpent = src->cash_spent;
dst->ParkEntryTime = AdjustScenarioToCurrentTicks(_s6, src->park_entry_time);
dst->RejoinQueueTimeout = src->rejoin_queue_timeout;
dst->PreviousRide = RCT12RideIdToOpenRCT2RideId(src->previous_ride);
dst->PreviousRideTimeOut = src->previous_ride_time_out;
for (size_t i = 0; i < std::size(src->thoughts); i++)
{
auto srcThought = &src->thoughts[i];
auto dstThought = &dst->Thoughts[i];
dstThought->type = static_cast<PeepThoughtType>(srcThought->type);
if (srcThought->item == RCT12PeepThoughtItemNone)
dstThought->item = PeepThoughtItemNone;
else
dstThought->item = srcThought->item;
dstThought->freshness = srcThought->freshness;
dstThought->fresh_timeout = srcThought->fresh_timeout;
}
dst->GuestHeadingToRideId = RCT12RideIdToOpenRCT2RideId(src->guest_heading_to_ride_id);
dst->GuestIsLostCountdown = src->peep_is_lost_countdown;
dst->LitterCount = src->litter_count;
dst->GuestTimeOnRide = src->time_on_ride;
dst->DisgustingCount = src->disgusting_count;
dst->PaidToEnter = src->paid_to_enter;
dst->PaidOnRides = src->paid_on_rides;
dst->PaidOnFood = src->paid_on_food;
dst->PaidOnSouvenirs = src->paid_on_souvenirs;
dst->AmountOfFood = src->no_of_food;
dst->AmountOfDrinks = src->no_of_drinks;
dst->AmountOfSouvenirs = src->no_of_souvenirs;
dst->VandalismSeen = src->vandalism_seen;
dst->VoucherType = src->voucher_type;
dst->VoucherRideId = RCT12RideIdToOpenRCT2RideId(src->voucher_arguments);
dst->SurroundingsThoughtTimeout = src->surroundings_thought_timeout;
dst->Angriness = src->angriness;
dst->TimeLost = src->time_lost;
dst->DaysInQueue = src->days_in_queue;
dst->BalloonColour = src->balloon_colour;
dst->UmbrellaColour = src->umbrella_colour;
dst->HatColour = src->hat_colour;
dst->FavouriteRide = RCT12RideIdToOpenRCT2RideId(src->favourite_ride);
dst->FavouriteRideRating = src->favourite_ride_rating;
}
template<> void S6Importer::ImportEntity<Staff>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<Staff>(baseSrc.sprite_index);
auto src = static_cast<const RCT2SpritePeep*>(&baseSrc);
ImportEntityPeep(dst, src);
dst->AssignedStaffType = StaffType(src->staff_type);
dst->MechanicTimeSinceCall = src->mechanic_time_since_call;
dst->HireDate = src->park_entry_time;
dst->StaffOrders = src->staff_orders;
dst->StaffMowingTimeout = src->staff_mowing_timeout;
dst->StaffLawnsMown = src->paid_to_enter;
dst->StaffGardensWatered = src->paid_on_rides;
dst->StaffLitterSwept = src->paid_on_food;
dst->StaffBinsEmptied = src->paid_on_souvenirs;
ImportStaffPatrolArea(dst, src->staff_id);
}
template<> void S6Importer::ImportEntity<SteamParticle>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<SteamParticle>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteSteamParticle*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->time_to_move = src->time_to_move;
dst->frame = src->frame;
}
template<> void S6Importer::ImportEntity<MoneyEffect>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<MoneyEffect>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteMoneyEffect*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->MoveDelay = src->move_delay;
dst->NumMovements = src->num_movements;
dst->Vertical = src->vertical;
dst->Value = src->value;
dst->OffsetX = src->offset_x;
dst->Wiggle = src->wiggle;
}
template<> void S6Importer::ImportEntity<VehicleCrashParticle>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<VehicleCrashParticle>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteCrashedVehicleParticle*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->frame = src->frame;
dst->time_to_live = src->time_to_live;
dst->frame = src->frame;
dst->colour[0] = src->colour[0];
dst->colour[1] = src->colour[1];
dst->crashed_sprite_base = src->crashed_sprite_base;
dst->velocity_x = src->velocity_x;
dst->velocity_y = src->velocity_y;
dst->velocity_z = src->velocity_z;
dst->acceleration_x = src->acceleration_x;
dst->acceleration_y = src->acceleration_y;
dst->acceleration_z = src->acceleration_z;
}
template<> void S6Importer::ImportEntity<ExplosionCloud>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<ExplosionCloud>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteParticle*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->frame = src->frame;
}
template<> void S6Importer::ImportEntity<ExplosionFlare>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<ExplosionFlare>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteParticle*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->frame = src->frame;
}
template<> void S6Importer::ImportEntity<CrashSplashParticle>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<CrashSplashParticle>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteParticle*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->frame = src->frame;
}
template<> void S6Importer::ImportEntity<JumpingFountain>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<JumpingFountain>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteJumpingFountain*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->NumTicksAlive = src->num_ticks_alive;
dst->frame = src->frame;
dst->FountainFlags = src->fountain_flags;
dst->TargetX = src->target_x;
dst->TargetY = src->target_y;
dst->Iteration = src->iteration;
dst->FountainType = RCT12MiscEntityType(src->type) == RCT12MiscEntityType::JumpingFountainSnow ? JumpingFountainType::Snow
: JumpingFountainType::Water;
}
template<> void S6Importer::ImportEntity<Balloon>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<Balloon>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteBalloon*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->popped = src->popped;
dst->time_to_move = src->time_to_move;
dst->frame = src->frame;
dst->colour = src->colour;
}
template<> void S6Importer::ImportEntity<Duck>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<Duck>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteDuck*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->frame = src->frame;
dst->target_x = src->target_x;
dst->target_y = src->target_y;
dst->state = static_cast<Duck::DuckState>(src->state);
}
template<> void S6Importer::ImportEntity<Litter>(const RCT12SpriteBase& baseSrc)
{
auto dst = CreateEntityAt<Litter>(baseSrc.sprite_index);
auto src = static_cast<const RCT12SpriteLitter*>(&baseSrc);
ImportEntityCommonProperties(dst, src);
dst->SubType = Litter::Type(src->type);
dst->creationTick = AdjustScenarioToCurrentTicks(_s6, src->creationTick);
}
void S6Importer::ImportEntity(const RCT12SpriteBase& src)
{
switch (GetEntityTypeFromRCT2Sprite(&src))
{
case EntityType::Vehicle:
ImportEntity<Vehicle>(src);
break;
case EntityType::Guest:
ImportEntity<Guest>(src);
break;
case EntityType::Staff:
ImportEntity<Staff>(src);
break;
case EntityType::SteamParticle:
ImportEntity<SteamParticle>(src);
break;
case EntityType::MoneyEffect:
ImportEntity<MoneyEffect>(src);
break;
case EntityType::CrashedVehicleParticle:
ImportEntity<VehicleCrashParticle>(src);
break;
case EntityType::ExplosionCloud:
ImportEntity<ExplosionCloud>(src);
break;
case EntityType::ExplosionFlare:
ImportEntity<ExplosionFlare>(src);
break;
case EntityType::CrashSplash:
ImportEntity<CrashSplashParticle>(src);
break;
case EntityType::JumpingFountain:
ImportEntity<JumpingFountain>(src);
break;
case EntityType::Balloon:
ImportEntity<Balloon>(src);
break;
case EntityType::Duck:
ImportEntity<Duck>(src);
break;
case EntityType::Litter:
ImportEntity<Litter>(src);
break;
default:
// Null elements do not need imported
break;
}
}
std::unique_ptr<IParkImporter> ParkImporter::CreateS6(IObjectRepository& objectRepository)
{
return std::make_unique<S6Importer>(objectRepository);
}
static void show_error(uint8_t errorType, rct_string_id errorStringId)
{
if (errorType == ERROR_TYPE_GENERIC)
{
context_show_error(errorStringId, STR_NONE, {});
}
context_show_error(STR_UNABLE_TO_LOAD_FILE, errorStringId, {});
}
void load_from_sv6(const char* path)
{
auto context = OpenRCT2::GetContext();
auto s6Importer = std::make_unique<S6Importer>(context->GetObjectRepository());
try
{
auto& objectMgr = context->GetObjectManager();
auto result = s6Importer->LoadSavedGame(path);
objectMgr.LoadObjects(result.RequiredObjects);
s6Importer->Import();
game_fix_save_vars();
AutoCreateMapAnimations();
EntityTweener::Get().Reset();
gScreenAge = 0;
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
}
catch (const ObjectLoadException&)
{
show_error(ERROR_TYPE_FILE_LOAD, STR_FILE_CONTAINS_INVALID_DATA);
}
catch (const IOException& loadError)
{
log_error("Error loading: %s", loadError.what());
show_error(ERROR_TYPE_FILE_LOAD, STR_GAME_SAVE_FAILED);
}
catch (const UnsupportedRideTypeException&)
{
show_error(ERROR_TYPE_FILE_LOAD, STR_FILE_CONTAINS_UNSUPPORTED_RIDE_TYPES);
}
catch (const std::exception&)
{
show_error(ERROR_TYPE_FILE_LOAD, STR_FILE_CONTAINS_INVALID_DATA);
}
}
/**
*
* rct2: 0x00676053
* scenario (ebx)
*/
void load_from_sc6(const char* path)
{
auto context = OpenRCT2::GetContext();
auto& objManager = context->GetObjectManager();
auto s6Importer = std::make_unique<S6Importer>(context->GetObjectRepository());
try
{
auto result = s6Importer->LoadScenario(path);
objManager.LoadObjects(result.RequiredObjects);
s6Importer->Import();
game_fix_save_vars();
AutoCreateMapAnimations();
EntityTweener::Get().Reset();
return;
}
catch (const ObjectLoadException& loadError)
{
log_error("Error loading: %s", loadError.what());
show_error(ERROR_TYPE_FILE_LOAD, STR_GAME_SAVE_FAILED);
}
catch (const IOException& loadError)
{
log_error("Error loading: %s", loadError.what());
show_error(ERROR_TYPE_FILE_LOAD, STR_GAME_SAVE_FAILED);
}
catch (const std::exception&)
{
show_error(ERROR_TYPE_FILE_LOAD, STR_FILE_CONTAINS_INVALID_DATA);
}
gScreenAge = 0;
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
}