/***************************************************************************** * Copyright (c) 2014-2025 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 "T6Exporter.h" #include "../Context.h" #include "../Diagnostic.h" #include "../core/FileStream.h" #include "../core/MemoryStream.h" #include "../localisation/StringIds.h" #include "../object/ObjectList.h" #include "../rct12/SawyerChunkWriter.h" #include "../rct2/RCT2.h" #include "../ride/Ride.h" #include "../ride/RideData.h" #include "../ride/Station.h" #include "../ride/Track.h" #include "../ride/TrackData.h" #include "../ride/TrackDesign.h" #include "../ride/TrackDesignRepository.h" #include "../windows/Intent.h" #include namespace OpenRCT2::RCT2 { T6Exporter::T6Exporter(const TrackDesign& trackDesign) : _trackDesign(trackDesign) { } bool T6Exporter::SaveTrack(const utf8* path) { try { auto fs = OpenRCT2::FileStream(path, OpenRCT2::FileMode::write); return SaveTrack(&fs); } catch (const std::exception& e) { LOG_ERROR("Unable to save track design: %s", e.what()); return false; } } bool T6Exporter::SaveTrack(OpenRCT2::IStream* stream) { auto& rtd = GetRideTypeDescriptor(_trackDesign.trackAndVehicle.rtdIndex); auto td6rideType = OpenRCT2RideTypeToRCT2RideType(_trackDesign.trackAndVehicle.rtdIndex); OpenRCT2::MemoryStream tempStream; tempStream.WriteValue(td6rideType); tempStream.WriteValue(0); tempStream.WriteValue(0); tempStream.WriteValue(static_cast(_trackDesign.operation.rideMode)); tempStream.WriteValue(EnumValue(_trackDesign.appearance.vehicleColourSettings) | (2 << 2)); for (auto i = 0; i < RCT2::Limits::kMaxVehicleColours; i++) { tempStream.WriteValue(_trackDesign.appearance.vehicleColours[i].Body); tempStream.WriteValue(_trackDesign.appearance.vehicleColours[i].Trim); } tempStream.WriteValue(0); auto entranceStyle = GetStationStyleFromIdentifier(_trackDesign.appearance.stationObjectIdentifier); tempStream.WriteValue(entranceStyle); // The 512 added is to enforce correctly rounding up, as integer division will truncate. uint16_t _totalAirTime = std::min(255, ((_trackDesign.statistics.totalAirTime * 123) + 512) / 1024); tempStream.WriteValue(_totalAirTime); tempStream.WriteValue(_trackDesign.operation.departFlags); tempStream.WriteValue(_trackDesign.trackAndVehicle.numberOfTrains); tempStream.WriteValue(_trackDesign.trackAndVehicle.numberOfCarsPerTrain); tempStream.WriteValue(_trackDesign.operation.minWaitingTime); tempStream.WriteValue(_trackDesign.operation.maxWaitingTime); tempStream.WriteValue(_trackDesign.operation.operationSetting); tempStream.WriteValue(_trackDesign.statistics.maxSpeed); tempStream.WriteValue(_trackDesign.statistics.averageSpeed); tempStream.WriteValue(_trackDesign.statistics.rideLength); tempStream.WriteValue(_trackDesign.statistics.maxPositiveVerticalG / kTD46GForcesMultiplier); tempStream.WriteValue(_trackDesign.statistics.maxNegativeVerticalG / kTD46GForcesMultiplier); tempStream.WriteValue(_trackDesign.statistics.maxLateralG / kTD46GForcesMultiplier); if (rtd.specialType == RtdSpecialType::miniGolf) { auto numHoles = std::min(31, _trackDesign.statistics.holes); tempStream.WriteValue(numHoles & kRCT12InversionAndHoleMask); } else { auto numInversions = std::min(31, _trackDesign.statistics.inversions); tempStream.WriteValue(numInversions & kRCT12InversionAndHoleMask); } tempStream.WriteValue(std::min(63, _trackDesign.statistics.drops) & kRCT12RideNumDropsMask); tempStream.WriteValue(_trackDesign.statistics.highestDropHeight); tempStream.WriteValue(_trackDesign.statistics.ratings.excitement / kTD46RatingsMultiplier); tempStream.WriteValue(_trackDesign.statistics.ratings.intensity / kTD46RatingsMultiplier); tempStream.WriteValue(_trackDesign.statistics.ratings.nausea / kTD46RatingsMultiplier); tempStream.WriteValue(ToMoney16(_trackDesign.statistics.upkeepCost)); for (auto i = 0; i < Limits::kNumColourSchemes; i++) { tempStream.WriteValue(_trackDesign.appearance.trackColours[i].main); } for (auto i = 0; i < Limits::kNumColourSchemes; i++) { tempStream.WriteValue(_trackDesign.appearance.trackColours[i].additional); } for (auto i = 0; i < Limits::kNumColourSchemes; i++) { tempStream.WriteValue(_trackDesign.appearance.trackColours[i].supports); } tempStream.WriteValue(0); tempStream.Write(&_trackDesign.trackAndVehicle.vehicleObject.Entry, sizeof(RCTObjectEntry)); tempStream.WriteValue(_trackDesign.statistics.spaceRequired.x); tempStream.WriteValue(_trackDesign.statistics.spaceRequired.y); for (auto i = 0; i < RCT2::Limits::kMaxVehicleColours; i++) { tempStream.WriteValue(_trackDesign.appearance.vehicleColours[i].Tertiary); } auto liftSpeed = std::min(31, _trackDesign.operation.liftHillSpeed); auto numCircuits = std::min(7, _trackDesign.operation.numCircuits); tempStream.WriteValue(liftSpeed | (numCircuits << 5)); if (rtd.specialType == RtdSpecialType::maze) { for (const auto& mazeElement : _trackDesign.mazeElements) { tempStream.WriteValue(mazeElement.location.x); tempStream.WriteValue(mazeElement.location.y); tempStream.WriteValue(mazeElement.mazeEntry); } for (const auto& entranceElement : _trackDesign.entranceElements) { tempStream.WriteValue(entranceElement.location.x); tempStream.WriteValue(entranceElement.location.y); tempStream.WriteValue(entranceElement.location.direction); tempStream.WriteValue( EnumValue(entranceElement.isExit ? TD46MazeElementType::Exit : TD46MazeElementType::Entrance)); } tempStream.WriteValue(0); } else { for (const auto& trackElement : _trackDesign.trackElements) { auto trackType = OpenRCT2TrackTypeToRCT2(trackElement.type); if (trackElement.type == TrackElemType::MultiDimInvertedUp90ToFlatQuarterLoop) { trackType = OpenRCT2::RCT12::TrackElemType::InvertedUp90ToFlatQuarterLoopAlias; } tempStream.WriteValue(static_cast(trackType)); auto flags = ConvertToTD46Flags(trackElement); tempStream.WriteValue(flags); } tempStream.WriteValue(0xFF); for (const auto& entranceElement : _trackDesign.entranceElements) { tempStream.WriteValue( entranceElement.location.z == -1 ? static_cast(0x80) : entranceElement.location.z); tempStream.WriteValue(entranceElement.location.direction | (entranceElement.isExit << 7)); auto xy = entranceElement.location.ToCoordsXY(); tempStream.WriteValue(xy.x); tempStream.WriteValue(xy.y); } tempStream.WriteValue(0xFF); } for (const auto& sceneryElement : _trackDesign.sceneryElements) { auto flags = sceneryElement.flags; if (sceneryElement.sceneryObject.Entry.GetType() == ObjectType::walls) { flags &= ~0xFC; flags |= (sceneryElement.tertiaryColour << 2); } tempStream.Write(&sceneryElement.sceneryObject.Entry, sizeof(RCTObjectEntry)); auto tileCoords = TileCoordsXYZ(sceneryElement.loc); tempStream.WriteValue(tileCoords.x); tempStream.WriteValue(tileCoords.y); tempStream.WriteValue(tileCoords.z); tempStream.WriteValue(flags); tempStream.WriteValue(sceneryElement.primaryColour); tempStream.WriteValue(sceneryElement.secondaryColour); } tempStream.WriteValue(0xFF); SawyerChunkWriter sawyerCoding(stream); sawyerCoding.WriteChunkTrack(tempStream.GetData(), tempStream.GetLength()); return true; } } // namespace OpenRCT2::RCT2