1 /*****************************************************************************
2 * Copyright (c) 2014-2020 OpenRCT2 developers
3 *
4 * For a complete list of all authors, please refer to contributors.md
5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6 *
7 * OpenRCT2 is licensed under the GNU General Public License version 3.
8 *****************************************************************************/
9
10 #include "S6Exporter.h"
11
12 #include "../Context.h"
13 #include "../Editor.h"
14 #include "../Game.h"
15 #include "../GameState.h"
16 #include "../OpenRCT2.h"
17 #include "../common.h"
18 #include "../config/Config.h"
19 #include "../core/FileStream.h"
20 #include "../core/IStream.hpp"
21 #include "../core/Numerics.hpp"
22 #include "../core/String.hpp"
23 #include "../interface/Viewport.h"
24 #include "../interface/Window.h"
25 #include "../localisation/Date.h"
26 #include "../localisation/Localisation.h"
27 #include "../management/Award.h"
28 #include "../management/Finance.h"
29 #include "../management/Marketing.h"
30 #include "../management/NewsItem.h"
31 #include "../management/Research.h"
32 #include "../object/Object.h"
33 #include "../object/ObjectLimits.h"
34 #include "../object/ObjectManager.h"
35 #include "../object/ObjectRepository.h"
36 #include "../peep/Guest.h"
37 #include "../peep/RideUseSystem.h"
38 #include "../peep/Staff.h"
39 #include "../rct12/SawyerChunkWriter.h"
40 #include "../ride/Ride.h"
41 #include "../ride/RideData.h"
42 #include "../ride/RideRatings.h"
43 #include "../ride/ShopItem.h"
44 #include "../ride/Station.h"
45 #include "../ride/TrackData.h"
46 #include "../ride/Vehicle.h"
47 #include "../scenario/Scenario.h"
48 #include "../util/SawyerCoding.h"
49 #include "../util/Util.h"
50 #include "../world/Balloon.h"
51 #include "../world/Climate.h"
52 #include "../world/Duck.h"
53 #include "../world/EntityList.h"
54 #include "../world/Fountain.h"
55 #include "../world/Litter.h"
56 #include "../world/MapAnimation.h"
57 #include "../world/MoneyEffect.h"
58 #include "../world/Park.h"
59 #include "../world/Particle.h"
60 #include "../world/Sprite.h"
61
62 #include <algorithm>
63 #include <cstring>
64 #include <functional>
65 #include <iterator>
66 #include <optional>
67
68 #define ENCRYPT_MONEY(money) (static_cast<money32>(Numerics::ror32((money), 13) ^ 0xF4EC9621))
69
S6Exporter()70 S6Exporter::S6Exporter()
71 {
72 RemoveTracklessRides = false;
73 std::memset(&_s6, 0x00, sizeof(_s6));
74 }
75
SaveGame(const utf8 * path)76 void S6Exporter::SaveGame(const utf8* path)
77 {
78 auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE);
79 SaveGame(&fs);
80 }
81
SaveGame(OpenRCT2::IStream * stream)82 void S6Exporter::SaveGame(OpenRCT2::IStream* stream)
83 {
84 Save(stream, false);
85 }
86
SaveScenario(const utf8 * path)87 void S6Exporter::SaveScenario(const utf8* path)
88 {
89 auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_WRITE);
90 SaveScenario(&fs);
91 }
92
SaveScenario(OpenRCT2::IStream * stream)93 void S6Exporter::SaveScenario(OpenRCT2::IStream* stream)
94 {
95 Save(stream, true);
96 }
97
Save(OpenRCT2::IStream * stream,bool isScenario)98 void S6Exporter::Save(OpenRCT2::IStream* stream, bool isScenario)
99 {
100 _s6.header.type = isScenario ? S6_TYPE_SCENARIO : S6_TYPE_SAVEDGAME;
101 _s6.header.classic_flag = 0;
102 _s6.header.num_packed_objects = uint16_t(ExportObjectsList.size());
103 _s6.header.version = S6_RCT2_VERSION;
104 _s6.header.magic_number = S6_MAGIC_NUMBER;
105 _s6.game_version_number = 201028;
106
107 auto chunkWriter = SawyerChunkWriter(stream);
108
109 // 0: Write header chunk
110 chunkWriter.WriteChunk(&_s6.header, SAWYER_ENCODING::ROTATE);
111
112 // 1: Write scenario info chunk
113 if (_s6.header.type == S6_TYPE_SCENARIO)
114 {
115 chunkWriter.WriteChunk(&_s6.info, SAWYER_ENCODING::ROTATE);
116 }
117
118 // 2: Write packed objects
119 if (_s6.header.num_packed_objects > 0)
120 {
121 auto& objRepo = OpenRCT2::GetContext()->GetObjectRepository();
122 objRepo.WritePackedObjects(stream, ExportObjectsList);
123 }
124
125 // 3: Write available objects chunk
126 chunkWriter.WriteChunk(_s6.Objects, sizeof(_s6.Objects), SAWYER_ENCODING::ROTATE);
127
128 // 4: Misc fields (data, rand...) chunk
129 chunkWriter.WriteChunk(&_s6.elapsed_months, 16, SAWYER_ENCODING::RLECOMPRESSED);
130
131 // 5: Map elements + sprites and other fields chunk
132 chunkWriter.WriteChunk(&_s6.tile_elements, 0x180000, SAWYER_ENCODING::RLECOMPRESSED);
133
134 if (_s6.header.type == S6_TYPE_SCENARIO)
135 {
136 // 6 to 13:
137 chunkWriter.WriteChunk(&_s6.next_free_tile_element_pointer_index, 0x27104C, SAWYER_ENCODING::RLECOMPRESSED);
138 chunkWriter.WriteChunk(&_s6.guests_in_park, 4, SAWYER_ENCODING::RLECOMPRESSED);
139 chunkWriter.WriteChunk(&_s6.last_guests_in_park, 8, SAWYER_ENCODING::RLECOMPRESSED);
140 chunkWriter.WriteChunk(&_s6.park_rating, 2, SAWYER_ENCODING::RLECOMPRESSED);
141 chunkWriter.WriteChunk(&_s6.active_research_types, 1082, SAWYER_ENCODING::RLECOMPRESSED);
142 chunkWriter.WriteChunk(&_s6.current_expenditure, 16, SAWYER_ENCODING::RLECOMPRESSED);
143 chunkWriter.WriteChunk(&_s6.park_value, 4, SAWYER_ENCODING::RLECOMPRESSED);
144 chunkWriter.WriteChunk(&_s6.completed_company_value, 0x761E8, SAWYER_ENCODING::RLECOMPRESSED);
145 }
146 else
147 {
148 // 6: Everything else...
149 chunkWriter.WriteChunk(&_s6.next_free_tile_element_pointer_index, 0x2E8570, SAWYER_ENCODING::RLECOMPRESSED);
150 }
151
152 // Determine number of bytes written
153 size_t fileSize = stream->GetLength();
154
155 // Read all written bytes back into a single buffer
156 stream->SetPosition(0);
157 auto data = stream->ReadArray<uint8_t>(fileSize);
158 uint32_t checksum = sawyercoding_calculate_checksum(data.get(), fileSize);
159
160 // Write the checksum on the end
161 stream->SetPosition(fileSize);
162 stream->WriteValue(checksum);
163 }
164
ride_all_has_any_track_elements(std::array<bool,RCT12_MAX_RIDES_IN_PARK> & rideIndexArray)165 static void ride_all_has_any_track_elements(std::array<bool, RCT12_MAX_RIDES_IN_PARK>& rideIndexArray)
166 {
167 tile_element_iterator it;
168 tile_element_iterator_begin(&it);
169 while (tile_element_iterator_next(&it))
170 {
171 if (it.element->GetType() != TILE_ELEMENT_TYPE_TRACK)
172 continue;
173 if (it.element->IsGhost())
174 continue;
175
176 rideIndexArray[EnumValue(it.element->AsTrack()->GetRideIndex())] = true;
177 }
178 }
179
scenario_remove_trackless_rides(rct_s6_data * s6)180 static void scenario_remove_trackless_rides(rct_s6_data* s6)
181 {
182 std::array<bool, RCT12_MAX_RIDES_IN_PARK> rideHasTrack{};
183 ride_all_has_any_track_elements(rideHasTrack);
184 for (int32_t i = 0; i < RCT12_MAX_RIDES_IN_PARK; i++)
185 {
186 auto ride = &s6->rides[i];
187 if (rideHasTrack[i] || ride->type == RIDE_TYPE_NULL)
188 {
189 continue;
190 }
191
192 ride->type = RIDE_TYPE_NULL;
193 if (is_user_string_id(ride->name))
194 {
195 s6->custom_strings[(ride->name % RCT12_MAX_USER_STRINGS)][0] = 0;
196 }
197 }
198 }
199
200 /**
201 * Modifies the given S6 data so that ghost elements, rides with no track elements or unused banners / user strings are saved.
202 */
scenario_fix_ghosts(rct_s6_data * s6)203 static void scenario_fix_ghosts(rct_s6_data* s6)
204 {
205 // Build tile pointer cache (needed to get the first element at a certain location)
206 RCT12TileElement* tilePointers[MAX_TILE_TILE_ELEMENT_POINTERS];
207 for (size_t i = 0; i < MAX_TILE_TILE_ELEMENT_POINTERS; i++)
208 {
209 tilePointers[i] = TILE_UNDEFINED_TILE_ELEMENT;
210 }
211
212 RCT12TileElement* tileElement = s6->tile_elements;
213 RCT12TileElement** tile = tilePointers;
214 for (size_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
215 {
216 for (size_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
217 {
218 *tile++ = tileElement;
219 while (!(tileElement++)->IsLastForTile())
220 ;
221 }
222 }
223
224 // Remove all ghost elements
225 RCT12TileElement* destinationElement = s6->tile_elements;
226
227 for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
228 {
229 for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
230 {
231 // This is the equivalent of map_get_first_element_at(x, y), but on S6 data.
232 RCT12TileElement* originalElement = tilePointers[x + y * MAXIMUM_MAP_SIZE_TECHNICAL];
233 do
234 {
235 if (originalElement->IsGhost())
236 {
237 uint8_t bannerIndex = originalElement->GetBannerIndex();
238 if (bannerIndex != RCT12_BANNER_INDEX_NULL)
239 {
240 auto banner = &s6->banners[bannerIndex];
241 if (banner->type != RCT12_OBJECT_ENTRY_INDEX_NULL)
242 {
243 banner->type = RCT12_OBJECT_ENTRY_INDEX_NULL;
244 if (is_user_string_id(banner->string_idx))
245 s6->custom_strings[(banner->string_idx % RCT12_MAX_USER_STRINGS)][0] = 0;
246 }
247 }
248 }
249 else
250 {
251 *destinationElement++ = *originalElement;
252 }
253 } while (!(originalElement++)->IsLastForTile());
254
255 // Set last element flag in case the original last element was never added
256 (destinationElement - 1)->flags |= TILE_ELEMENT_FLAG_LAST_TILE;
257 }
258 }
259 }
260
261 template<ObjectType TObjectType, size_t TMaxEntries, typename T>
ExportObjectList(IObjectManager & objMgr,T & objects)262 static void ExportObjectList(IObjectManager& objMgr, T& objects)
263 {
264 for (size_t i = 0; i < TMaxEntries; i++)
265 {
266 auto& dst = objects[i];
267
268 const auto* object = objMgr.GetLoadedObject(TObjectType, i);
269 if (object == nullptr)
270 {
271 // The sv6 format expects null/invalid entries to be filled with 0xFF.
272 std::memset(&dst, 0xFF, sizeof(dst));
273 }
274 else
275 {
276 dst = object->GetObjectEntry();
277 }
278 }
279 }
280
Export()281 void S6Exporter::Export()
282 {
283 _s6.info = {};
284 _s6.info.editor_step = gEditorStep;
285 _s6.info.category = gScenarioCategory;
286 _s6.info.objective_type = gScenarioObjective.Type;
287 _s6.info.objective_arg_1 = gScenarioObjective.Year;
288 _s6.info.objective_arg_2 = gScenarioObjective.Currency;
289 _s6.info.objective_arg_3 = gScenarioObjective.NumGuests;
290 _s6.info.entry.flags = 0xFFFFFFFF;
291
292 {
293 auto temp = utf8_to_rct2(gScenarioName.c_str());
294 safe_strcpy(_s6.info.name, temp.data(), sizeof(_s6.info.name));
295 }
296 {
297 auto temp = utf8_to_rct2(gScenarioDetails.c_str());
298 safe_strcpy(_s6.info.details, temp.data(), sizeof(_s6.info.details));
299 }
300
301 uint32_t researchedTrackPiecesA[128] = {};
302 uint32_t researchedTrackPiecesB[128] = {};
303
304 auto& objectMgr = OpenRCT2::GetContext()->GetObjectManager();
305 ExportObjectList<ObjectType::Ride, RCT12_MAX_RIDE_OBJECTS>(objectMgr, _s6.RideObjects);
306 ExportObjectList<ObjectType::SmallScenery, RCT2_MAX_SMALL_SCENERY_OBJECTS>(objectMgr, _s6.SceneryObjects);
307 ExportObjectList<ObjectType::LargeScenery, RCT2_MAX_LARGE_SCENERY_OBJECTS>(objectMgr, _s6.LargeSceneryObjects);
308 ExportObjectList<ObjectType::Walls, RCT2_MAX_WALL_SCENERY_OBJECTS>(objectMgr, _s6.WallSceneryObjects);
309 ExportObjectList<ObjectType::Banners, RCT2_MAX_BANNER_OBJECTS>(objectMgr, _s6.BannerObjects);
310 ExportObjectList<ObjectType::Paths, RCT2_MAX_PATH_OBJECTS>(objectMgr, _s6.PathObjects);
311 ExportObjectList<ObjectType::PathBits, RCT2_MAX_PATH_ADDITION_OBJECTS>(objectMgr, _s6.PathAdditionObjects);
312 ExportObjectList<ObjectType::SceneryGroup, RCT2_MAX_SCENERY_GROUP_OBJECTS>(objectMgr, _s6.SceneryGroupObjects);
313 ExportObjectList<ObjectType::ParkEntrance, RCT2_MAX_PARK_ENTRANCE_OBJECTS>(objectMgr, _s6.ParkEntranceObjects);
314 ExportObjectList<ObjectType::Water, RCT2_MAX_WATER_OBJECTS>(objectMgr, _s6.WaterObjects);
315 ExportObjectList<ObjectType::ScenarioText, RCT2_MAX_SCENARIO_TEXT_OBJECTS>(objectMgr, _s6.ScenarioTextObjects);
316
317 _s6.elapsed_months = static_cast<uint16_t>(gDateMonthsElapsed);
318 _s6.current_day = gDateMonthTicks;
319 _s6.scenario_ticks = gCurrentTicks;
320
321 auto state = scenario_rand_state();
322 _s6.scenario_srand_0 = state.s0;
323 _s6.scenario_srand_1 = state.s1;
324
325 // Map elements must be reorganised prior to saving otherwise save may be invalid
326 ReorganiseTileElements();
327 ExportTileElements();
328 ExportEntities();
329 ExportParkName();
330
331 _s6.initial_cash = ToMoney32(gInitialCash);
332 _s6.current_loan = ToMoney32(gBankLoan);
333 _s6.park_flags = gParkFlags;
334 _s6.park_entrance_fee = gParkEntranceFee;
335 // rct1_park_entrance_x
336 // rct1_park_entrance_y
337 // pad_013573EE
338 // rct1_park_entrance_z
339 ExportPeepSpawns();
340 _s6.guest_count_change_modifier = gGuestChangeModifier;
341 _s6.current_research_level = gResearchFundingLevel;
342 // pad_01357400
343 ExportResearchedRideTypes();
344 ExportResearchedRideEntries();
345 // Not used by OpenRCT2 any more, but left in to keep RCT2 export working.
346 for (uint8_t i = 0; i < std::size(RideTypeDescriptors); i++)
347 {
348 researchedTrackPiecesA[i] = (GetRideTypeDescriptor(i).EnabledTrackPieces) & 0xFFFFFFFFULL;
349 researchedTrackPiecesB[i] = (GetRideTypeDescriptor(i).EnabledTrackPieces >> 32ULL) & 0xFFFFFFFFULL;
350 }
351 std::memcpy(_s6.researched_track_types_a, researchedTrackPiecesA, sizeof(_s6.researched_track_types_a));
352 std::memcpy(_s6.researched_track_types_b, researchedTrackPiecesB, sizeof(_s6.researched_track_types_b));
353
354 _s6.guests_in_park = gNumGuestsInPark;
355 _s6.guests_heading_for_park = gNumGuestsHeadingForPark;
356
357 for (size_t i = 0; i < RCT12_EXPENDITURE_TABLE_MONTH_COUNT; i++)
358 {
359 for (size_t j = 0; j < RCT12_EXPENDITURE_TYPE_COUNT; j++)
360 {
361 _s6.expenditure_table[i][j] = ToMoney32(gExpenditureTable[i][j]);
362 }
363 }
364
365 _s6.last_guests_in_park = gNumGuestsInParkLastWeek;
366 // pad_01357BCA
367 _s6.handyman_colour = gStaffHandymanColour;
368 _s6.mechanic_colour = gStaffMechanicColour;
369 _s6.security_colour = gStaffSecurityColour;
370
371 ExportResearchedSceneryItems();
372
373 _s6.park_rating = gParkRating;
374
375 std::copy(std::begin(gParkRatingHistory), std::end(gParkRatingHistory), _s6.park_rating_history);
376 std::fill(std::begin(_s6.guests_in_park_history), std::end(_s6.guests_in_park_history), RCT12ParkHistoryUndefined);
377 for (size_t i = 0; i < std::size(gGuestsInParkHistory); i++)
378 {
379 if (gGuestsInParkHistory[i] != GuestsInParkHistoryUndefined)
380 {
381 _s6.guests_in_park_history[i] = std::min<uint16_t>(gGuestsInParkHistory[i], 5000) / RCT12GuestsInParkHistoryFactor;
382 }
383 }
384
385 std::memcpy(_s6.guests_in_park_history, gGuestsInParkHistory, sizeof(_s6.guests_in_park_history));
386
387 _s6.active_research_types = gResearchPriorities;
388 _s6.research_progress_stage = gResearchProgressStage;
389 if (gResearchLastItem.has_value())
390 _s6.last_researched_item_subject = gResearchLastItem->ToRCT12ResearchItem().rawValue;
391 else
392 _s6.last_researched_item_subject = RCT12_RESEARCHED_ITEMS_SEPARATOR;
393 // pad_01357CF8
394 _s6.research_progress = gResearchProgress;
395
396 if (gResearchNextItem.has_value())
397 {
398 auto RCT2ResearchItem = gResearchNextItem->ToRCT12ResearchItem();
399 _s6.next_research_item = RCT2ResearchItem.rawValue;
400 _s6.next_research_category = RCT2ResearchItem.category;
401 }
402 else
403 {
404 _s6.next_research_item = RCT12_RESEARCHED_ITEMS_SEPARATOR;
405 _s6.next_research_category = 0;
406 }
407
408 _s6.next_research_expected_day = gResearchExpectedDay;
409 _s6.next_research_expected_month = gResearchExpectedMonth;
410 _s6.guest_initial_happiness = gGuestInitialHappiness;
411 _s6.park_size = gParkSize;
412 _s6.guest_generation_probability = _guestGenerationProbability;
413 _s6.total_ride_value_for_money = gTotalRideValueForMoney;
414 _s6.maximum_loan = ToMoney32(gMaxBankLoan);
415 _s6.guest_initial_cash = gGuestInitialCash;
416 _s6.guest_initial_hunger = gGuestInitialHunger;
417 _s6.guest_initial_thirst = gGuestInitialThirst;
418 _s6.objective_type = gScenarioObjective.Type;
419 _s6.objective_year = gScenarioObjective.Year;
420 // pad_013580FA
421 _s6.objective_currency = gScenarioObjective.Currency;
422 // In RCT2, the ride string IDs start at index STR_0002 and are directly mappable.
423 // This is not always the case in OpenRCT2, so we use the actual ride ID.
424 if (gScenarioObjective.Type == OBJECTIVE_BUILD_THE_BEST)
425 _s6.objective_guests = gScenarioObjective.RideId + RCT2_RIDE_STRING_START;
426 else
427 _s6.objective_guests = gScenarioObjective.NumGuests;
428
429 ExportMarketingCampaigns();
430
431 _s6.current_expenditure = ToMoney32(gCurrentExpenditure);
432 _s6.current_profit = ToMoney32(gCurrentProfit);
433 _s6.weekly_profit_average_dividend = ToMoney32(gWeeklyProfitAverageDividend);
434 _s6.weekly_profit_average_divisor = gWeeklyProfitAverageDivisor;
435 // pad_0135833A
436
437 _s6.park_value = ToMoney32(gParkValue);
438
439 for (size_t i = 0; i < RCT12_FINANCE_GRAPH_SIZE; i++)
440 {
441 _s6.balance_history[i] = ToMoney32(gCashHistory[i]);
442 _s6.weekly_profit_history[i] = ToMoney32(gWeeklyProfitHistory[i]);
443 _s6.park_value_history[i] = ToMoney32(gParkValueHistory[i]);
444 }
445
446 _s6.completed_company_value = OpenRCT2CompletedCompanyValueToRCT12(gScenarioCompletedCompanyValue);
447 _s6.total_admissions = gTotalAdmissions;
448 _s6.income_from_admissions = ToMoney32(gTotalIncomeFromAdmissions);
449 _s6.company_value = ToMoney32(gCompanyValue);
450 std::memcpy(_s6.peep_warning_throttle, gPeepWarningThrottle, sizeof(_s6.peep_warning_throttle));
451
452 // Awards
453 for (int32_t i = 0; i < RCT12_MAX_AWARDS; i++)
454 {
455 Award* src = &gCurrentAwards[i];
456 rct12_award* dst = &_s6.awards[i];
457 dst->time = src->Time;
458 dst->type = src->Type;
459 }
460
461 _s6.land_price = gLandPrice;
462 _s6.construction_rights_price = gConstructionRightsPrice;
463 // unk_01358774
464 // pad_01358776
465 // _s6.cd_key
466 // _s6.game_version_number
467 _s6.completed_company_value_record = gScenarioCompanyValueRecord;
468 _s6.loan_hash = GetLoanHash(gInitialCash, gBankLoan, gMaxBankLoan);
469 _s6.ride_count = ride_get_count();
470 // pad_013587CA
471 _s6.historical_profit = ToMoney32(gHistoricalProfit);
472 // pad_013587D4
473 String::Set(_s6.scenario_completed_name, sizeof(_s6.scenario_completed_name), gScenarioCompletedBy.c_str());
474 _s6.cash = ENCRYPT_MONEY(ToMoney32(gCash));
475 // pad_013587FC
476 _s6.park_rating_casualty_penalty = gParkRatingCasualtyPenalty;
477 _s6.map_size_units = GetMapSizeUnits();
478 _s6.map_size_minus_2 = GetMapSizeMinus2();
479 _s6.map_size = gMapSize;
480 _s6.map_max_xy = GetMapSizeMaxXY();
481 _s6.same_price_throughout = gSamePriceThroughoutPark & 0xFFFFFFFF;
482 _s6.suggested_max_guests = _suggestedGuestMaximum;
483 _s6.park_rating_warning_days = gScenarioParkRatingWarningDays;
484 _s6.last_entrance_style = gLastEntranceStyle;
485 // rct1_water_colour
486 // pad_01358842
487 ExportResearchList();
488 _s6.map_base_z = gMapBaseZ;
489 String::Set(_s6.scenario_name, sizeof(_s6.scenario_name), gScenarioName.c_str());
490 String::Set(_s6.scenario_description, sizeof(_s6.scenario_description), gScenarioDetails.c_str());
491 _s6.current_interest_rate = gBankLoanInterestRate;
492 // pad_0135934B
493 _s6.same_price_throughout_extended = gSamePriceThroughoutPark >> 32;
494 // Preserve compatibility with vanilla RCT2's save format.
495 for (uint8_t i = 0; i < RCT12_MAX_PARK_ENTRANCES; i++)
496 {
497 CoordsXYZD entrance = { LOCATION_NULL, LOCATION_NULL, 0, 0 };
498 if (gParkEntrances.size() > i)
499 {
500 entrance = gParkEntrances[i];
501 }
502 _s6.park_entrance_x[i] = entrance.x;
503 _s6.park_entrance_y[i] = entrance.y;
504 _s6.park_entrance_z[i] = entrance.z;
505 _s6.park_entrance_direction[i] = entrance.direction;
506 }
507 safe_strcpy(_s6.scenario_filename, gScenarioFileName, sizeof(_s6.scenario_filename));
508 std::memcpy(_s6.saved_expansion_pack_names, gScenarioExpansionPacks, sizeof(_s6.saved_expansion_pack_names));
509 ExportBanners();
510 _s6.game_ticks_1 = gCurrentTicks;
511
512 this->ExportRides();
513
514 _s6.saved_age = gSavedAge;
515 _s6.saved_view_x = gSavedView.x;
516 _s6.saved_view_y = gSavedView.y;
517 _s6.saved_view_zoom = static_cast<int8_t>(std::clamp<ZoomLevel>(gSavedViewZoom, 0, 3));
518 _s6.saved_view_rotation = gSavedViewRotation;
519
520 ExportMapAnimations();
521
522 ExportRideRatingsCalcData();
523 ExportRideMeasurements();
524 _s6.next_guest_index = gNextGuestNumber;
525 _s6.grass_and_scenery_tilepos = gGrassSceneryTileLoopPosition;
526 ExportStaffPatrolAreas();
527 // unk_13CA73E
528 // pad_13CA73F
529 // unk_13CA740
530 _s6.climate = static_cast<uint8_t>(gClimate);
531 // pad_13CA741;
532 // byte_13CA742
533 // pad_013CA747
534 _s6.climate_update_timer = gClimateUpdateTimer;
535 _s6.current_weather = EnumValue(gClimateCurrent.Weather);
536 _s6.next_weather = EnumValue(gClimateNext.Weather);
537 _s6.temperature = gClimateCurrent.Temperature;
538 _s6.next_temperature = gClimateNext.Temperature;
539 _s6.current_weather_effect = static_cast<uint8_t>(gClimateCurrent.WeatherEffect);
540 _s6.next_weather_effect = static_cast<uint8_t>(gClimateNext.WeatherEffect);
541 _s6.current_weather_gloom = gClimateCurrent.WeatherGloom;
542 _s6.next_weather_gloom = gClimateNext.WeatherGloom;
543 _s6.current_weather_level = static_cast<uint8_t>(gClimateCurrent.Level);
544 _s6.next_weather_level = static_cast<uint8_t>(gClimateNext.Level);
545
546 // News items
547 for (size_t i = 0; i < RCT12_MAX_NEWS_ITEMS; i++)
548 {
549 const News::Item* src = &gNewsItems[i];
550 rct12_news_item* dst = &_s6.news_items[i];
551
552 dst->Type = static_cast<uint8_t>(src->Type);
553 dst->Flags = src->Flags;
554 dst->Assoc = src->Assoc;
555 dst->Ticks = src->Ticks;
556 dst->MonthYear = src->MonthYear;
557 dst->Day = src->Day;
558
559 auto rct2text = ConvertFormattedStringToRCT2(src->Text, sizeof(dst->Text));
560 std::memcpy(dst->Text, rct2text.c_str(), std::min(sizeof(dst->Text), rct2text.size()));
561 }
562
563 // pad_13CE730
564 // rct1_scenario_flags
565 _s6.wide_path_tile_loop_x = gWidePathTileLoopPosition.x;
566 _s6.wide_path_tile_loop_y = gWidePathTileLoopPosition.y;
567 // pad_13CE778
568
569 String::Set(_s6.scenario_filename, sizeof(_s6.scenario_filename), gScenarioFileName);
570
571 if (RemoveTracklessRides)
572 {
573 scenario_remove_trackless_rides(&_s6);
574 }
575
576 scenario_fix_ghosts(&_s6);
577 game_convert_strings_to_rct2(&_s6);
578
579 ExportUserStrings();
580 }
581
ExportPeepSpawns()582 void S6Exporter::ExportPeepSpawns()
583 {
584 for (size_t i = 0; i < RCT12_MAX_PEEP_SPAWNS; i++)
585 {
586 if (gPeepSpawns.size() > i)
587 {
588 _s6.peep_spawns[i] = { static_cast<uint16_t>(gPeepSpawns[i].x), static_cast<uint16_t>(gPeepSpawns[i].y),
589 static_cast<uint8_t>(gPeepSpawns[i].z / 16), gPeepSpawns[i].direction };
590 }
591 else
592 {
593 _s6.peep_spawns[i] = { RCT12_PEEP_SPAWN_UNDEFINED, RCT12_PEEP_SPAWN_UNDEFINED, 0, 0 };
594 }
595 }
596 }
597
GetLoanHash(money32 initialCash,money32 bankLoan,uint32_t maxBankLoan)598 uint32_t S6Exporter::GetLoanHash(money32 initialCash, money32 bankLoan, uint32_t maxBankLoan)
599 {
600 int32_t value = 0x70093A;
601 value -= initialCash;
602 value = Numerics::ror32(value, 5);
603 value -= bankLoan;
604 value = Numerics::ror32(value, 7);
605 value += maxBankLoan;
606 value = Numerics::ror32(value, 3);
607 return value;
608 }
609
ExportParkName()610 void S6Exporter::ExportParkName()
611 {
612 auto& park = OpenRCT2::GetContext()->GetGameState()->GetPark();
613 auto stringId = AllocateUserString(park.Name);
614 if (stringId.has_value())
615 {
616 _s6.park_name = stringId.value();
617 _s6.park_name_args = 0;
618 }
619 else
620 {
621 log_warning("Unable to allocate user string for park name during S6 export.");
622 _s6.park_name = STR_UNNAMED_PARK;
623 _s6.park_name_args = 0;
624 }
625 }
626
ExportRides()627 void S6Exporter::ExportRides()
628 {
629 const Ride nullRide{};
630 for (int32_t index = 0; index < RCT12_MAX_RIDES_IN_PARK; index++)
631 {
632 const auto* src = get_ride(static_cast<ride_id_t>(index));
633 if (src == nullptr)
634 {
635 src = &nullRide;
636 }
637 auto dst = &_s6.rides[index];
638 *dst = {};
639 if (src->type == RIDE_TYPE_NULL)
640 {
641 dst->type = RIDE_TYPE_NULL;
642 }
643 else
644 {
645 ExportRide(dst, src);
646 }
647 }
648 }
649
ExportRide(rct2_ride * dst,const Ride * src)650 void S6Exporter::ExportRide(rct2_ride* dst, const Ride* src)
651 {
652 std::memset(dst, 0, sizeof(rct2_ride));
653
654 dst->type = OpenRCT2RideTypeToRCT2RideType(src->type);
655 dst->subtype = OpenRCT2EntryIndexToRCTEntryIndex(src->subtype);
656 // pad_002;
657 dst->mode = static_cast<uint8_t>(src->mode);
658 dst->colour_scheme_type = src->colour_scheme_type;
659
660 for (uint8_t i = 0; i < RCT2_MAX_CARS_PER_TRAIN; i++)
661 {
662 dst->vehicle_colours[i].body_colour = src->vehicle_colours[i].Body;
663 dst->vehicle_colours[i].trim_colour = src->vehicle_colours[i].Trim;
664 }
665
666 // pad_046;
667 dst->status = EnumValue(src->status);
668
669 bool useDefaultName = true;
670 if (!src->custom_name.empty())
671 {
672 // Custom name, allocate user string for ride
673 auto stringId = AllocateUserString(src->custom_name);
674 if (stringId.has_value())
675 {
676 dst->name = stringId.value();
677 dst->name_arguments = 0;
678 useDefaultName = false;
679 }
680 else
681 {
682 log_warning(
683 "Unable to allocate user string for ride #%d (%s).", static_cast<int>(src->id), src->custom_name.c_str());
684 }
685 }
686 if (useDefaultName)
687 {
688 // Default name with number
689 dst->name = GetRideTypeDescriptor(src->type).Naming.Name;
690 dst->name_arguments_number = src->default_name_number;
691 }
692
693 if (src->overall_view.IsNull())
694 {
695 dst->overall_view.SetNull();
696 }
697 else
698 {
699 auto tileLoc = TileCoordsXY(src->overall_view);
700 dst->overall_view = { static_cast<uint8_t>(tileLoc.x), static_cast<uint8_t>(tileLoc.y) };
701 }
702
703 for (int32_t i = 0; i < RCT12_MAX_STATIONS_PER_RIDE; i++)
704 {
705 if (src->stations[i].Start.IsNull())
706 {
707 dst->station_starts[i].SetNull();
708 }
709 else
710 {
711 auto tileStartLoc = TileCoordsXY(src->stations[i].Start);
712 dst->station_starts[i] = { static_cast<uint8_t>(tileStartLoc.x), static_cast<uint8_t>(tileStartLoc.y) };
713 }
714 dst->station_heights[i] = src->stations[i].Height;
715 dst->station_length[i] = src->stations[i].Length;
716 dst->station_depart[i] = src->stations[i].Depart;
717 dst->train_at_station[i] = src->stations[i].TrainAtStation;
718
719 TileCoordsXYZD entrance = ride_get_entrance_location(src, i);
720 if (entrance.IsNull())
721 dst->entrances[i].SetNull();
722 else
723 dst->entrances[i] = { static_cast<uint8_t>(entrance.x), static_cast<uint8_t>(entrance.y) };
724
725 TileCoordsXYZD exit = ride_get_exit_location(src, i);
726 if (exit.IsNull())
727 dst->exits[i].SetNull();
728 else
729 dst->exits[i] = { static_cast<uint8_t>(exit.x), static_cast<uint8_t>(exit.y) };
730
731 dst->last_peep_in_queue[i] = src->stations[i].LastPeepInQueue;
732
733 dst->length[i] = src->stations[i].SegmentLength;
734 dst->time[i] = src->stations[i].SegmentTime;
735
736 dst->queue_time[i] = src->stations[i].QueueTime;
737
738 dst->queue_length[i] = src->stations[i].QueueLength;
739 }
740
741 for (uint8_t i = 0; i <= RCT2_MAX_VEHICLES_PER_RIDE; i++)
742 {
743 dst->vehicles[i] = src->vehicles[i];
744 }
745
746 dst->depart_flags = src->depart_flags;
747
748 dst->num_stations = src->num_stations;
749 dst->num_vehicles = src->num_vehicles;
750 dst->num_cars_per_train = src->num_cars_per_train;
751 dst->proposed_num_vehicles = src->proposed_num_vehicles;
752 dst->proposed_num_cars_per_train = src->proposed_num_cars_per_train;
753 dst->max_trains = src->max_trains;
754 dst->SetMinCarsPerTrain(src->MinCarsPerTrain);
755 dst->SetMaxCarsPerTrain(src->MaxCarsPerTrain);
756 dst->min_waiting_time = src->min_waiting_time;
757 dst->max_waiting_time = src->max_waiting_time;
758
759 // Includes time_limit, num_laps, launch_speed, speed, rotations
760 dst->operation_option = src->operation_option;
761
762 dst->boat_hire_return_direction = src->boat_hire_return_direction;
763 dst->boat_hire_return_position = { static_cast<uint8_t>(src->boat_hire_return_position.x),
764 static_cast<uint8_t>(src->boat_hire_return_position.y) };
765
766 dst->special_track_elements = src->special_track_elements;
767 // pad_0D6[2];
768
769 dst->max_speed = src->max_speed;
770 dst->average_speed = src->average_speed;
771 dst->current_test_segment = src->current_test_segment;
772 dst->average_speed_test_timeout = src->average_speed_test_timeout;
773 // pad_0E2[0x2];
774
775 dst->max_positive_vertical_g = src->max_positive_vertical_g;
776 dst->max_negative_vertical_g = src->max_negative_vertical_g;
777 dst->max_lateral_g = src->max_lateral_g;
778 dst->previous_vertical_g = src->previous_vertical_g;
779 dst->previous_lateral_g = src->previous_lateral_g;
780 // pad_106[0x2];
781 dst->testing_flags = src->testing_flags;
782
783 if (src->CurTestTrackLocation.IsNull())
784 {
785 dst->cur_test_track_location.SetNull();
786 }
787 else
788 {
789 dst->cur_test_track_location = { static_cast<uint8_t>(src->CurTestTrackLocation.x),
790 static_cast<uint8_t>(src->CurTestTrackLocation.y) };
791 dst->cur_test_track_z = static_cast<uint8_t>(src->CurTestTrackLocation.z);
792 }
793
794 dst->turn_count_default = src->turn_count_default;
795 dst->turn_count_banked = src->turn_count_banked;
796 dst->turn_count_sloped = src->turn_count_sloped;
797 if (dst->type == RIDE_TYPE_MINI_GOLF)
798 dst->inversions = static_cast<uint8_t>(std::min(src->holes, RCT12_MAX_GOLF_HOLES));
799 else
800 dst->inversions = static_cast<uint8_t>(std::min(src->inversions, RCT12_MAX_INVERSIONS));
801 dst->inversions |= (src->sheltered_eighths << 5);
802 dst->drops = src->drops;
803 dst->start_drop_height = src->start_drop_height;
804 dst->highest_drop_height = src->highest_drop_height;
805 dst->sheltered_length = src->sheltered_length;
806 dst->var_11C = src->var_11C;
807 dst->num_sheltered_sections = src->num_sheltered_sections;
808
809 dst->cur_num_customers = src->cur_num_customers;
810 dst->num_customers_timeout = src->num_customers_timeout;
811
812 for (uint8_t i = 0; i < RCT2_CUSTOMER_HISTORY_SIZE; i++)
813 {
814 dst->num_customers[i] = src->num_customers[i];
815 }
816
817 dst->price = src->price[0];
818
819 for (uint8_t i = 0; i < 2; i++)
820 {
821 dst->chairlift_bullwheel_location[i] = { static_cast<uint8_t>(src->ChairliftBullwheelLocation[i].x),
822 static_cast<uint8_t>(src->ChairliftBullwheelLocation[i].y) };
823 dst->chairlift_bullwheel_z[i] = static_cast<uint8_t>(src->ChairliftBullwheelLocation[i].z);
824 }
825
826 dst->ratings = src->ratings;
827 dst->value = src->value;
828
829 dst->chairlift_bullwheel_rotation = src->chairlift_bullwheel_rotation;
830
831 dst->satisfaction = src->satisfaction;
832 dst->satisfaction_time_out = src->satisfaction_time_out;
833 dst->satisfaction_next = src->satisfaction_next;
834
835 dst->window_invalidate_flags = src->window_invalidate_flags;
836 // pad_14E[0x02];
837
838 dst->total_customers = src->total_customers;
839 dst->total_profit = ToMoney32(src->total_profit);
840 dst->popularity = src->popularity;
841 dst->popularity_time_out = src->popularity_time_out;
842 dst->popularity_next = src->popularity_next;
843 dst->num_riders = src->num_riders;
844 dst->music_tune_id = src->music_tune_id;
845 dst->slide_in_use = src->slide_in_use;
846 // Includes maze_tiles
847 dst->slide_peep = src->slide_peep;
848 // pad_160[0xE];
849 dst->slide_peep_t_shirt_colour = src->slide_peep_t_shirt_colour;
850 // pad_16F[0x7];
851 dst->spiral_slide_progress = src->spiral_slide_progress;
852 // pad_177[0x9];
853 dst->build_date = static_cast<int16_t>(src->build_date);
854 dst->upkeep_cost = src->upkeep_cost;
855 dst->race_winner = src->race_winner;
856 // pad_186[0x02];
857 dst->music_position = src->music_position;
858
859 dst->breakdown_reason_pending = src->breakdown_reason_pending;
860 dst->mechanic_status = src->mechanic_status;
861 dst->mechanic = src->mechanic;
862 dst->inspection_station = src->inspection_station;
863 dst->broken_vehicle = src->broken_vehicle;
864 dst->broken_car = src->broken_car;
865 dst->breakdown_reason = src->breakdown_reason;
866
867 dst->price_secondary = src->price[1];
868
869 dst->reliability = src->reliability;
870 dst->unreliability_factor = src->unreliability_factor;
871 dst->downtime = src->downtime;
872 dst->inspection_interval = src->inspection_interval;
873 dst->last_inspection = src->last_inspection;
874
875 for (uint8_t i = 0; i < RCT2_DOWNTIME_HISTORY_SIZE; i++)
876 {
877 dst->downtime_history[i] = src->downtime_history[i];
878 }
879
880 dst->no_primary_items_sold = src->no_primary_items_sold;
881 dst->no_secondary_items_sold = src->no_secondary_items_sold;
882
883 dst->breakdown_sound_modifier = src->breakdown_sound_modifier;
884 dst->not_fixed_timeout = src->not_fixed_timeout;
885 dst->last_crash_type = src->last_crash_type;
886 dst->connected_message_throttle = src->connected_message_throttle;
887
888 dst->income_per_hour = src->income_per_hour;
889 dst->profit = ToMoney32(src->profit);
890
891 for (uint8_t i = 0; i < RCT12_NUM_COLOUR_SCHEMES; i++)
892 {
893 dst->track_colour_main[i] = src->track_colour[i].main;
894 dst->track_colour_additional[i] = src->track_colour[i].additional;
895 dst->track_colour_supports[i] = src->track_colour[i].supports;
896 }
897
898 dst->music = src->music;
899 dst->entrance_style = src->entrance_style;
900 dst->vehicle_change_timeout = src->vehicle_change_timeout;
901 dst->num_block_brakes = src->num_block_brakes;
902 dst->lift_hill_speed = src->lift_hill_speed;
903 dst->guests_favourite = src->guests_favourite;
904 dst->lifecycle_flags = src->lifecycle_flags;
905
906 for (uint8_t i = 0; i < RCT2_MAX_CARS_PER_TRAIN; i++)
907 {
908 dst->vehicle_colours_extended[i] = src->vehicle_colours[i].Ternary;
909 }
910
911 dst->total_air_time = src->total_air_time;
912 dst->current_test_station = src->current_test_station;
913 dst->num_circuits = src->num_circuits;
914 dst->cable_lift_x = static_cast<int16_t>(src->CableLiftLoc.x);
915 dst->cable_lift_y = static_cast<int16_t>(src->CableLiftLoc.y);
916 dst->cable_lift_z = static_cast<int16_t>(src->CableLiftLoc.z / COORDS_Z_STEP);
917 // pad_1FD;
918 dst->cable_lift = src->cable_lift;
919
920 // pad_208[0x58];
921 }
922
ExportRideRatingsCalcData()923 void S6Exporter::ExportRideRatingsCalcData()
924 {
925 const auto& src = gRideRatingUpdateState;
926 auto& dst = _s6.ride_ratings_calc_data;
927 dst.proximity_x = src.Proximity.x;
928 dst.proximity_y = src.Proximity.y;
929 dst.proximity_z = src.Proximity.z;
930 dst.proximity_start_x = src.ProximityStart.x;
931 dst.proximity_start_y = src.ProximityStart.y;
932 dst.proximity_start_z = src.ProximityStart.z;
933 dst.current_ride = OpenRCT2RideIdToRCT12RideId(src.CurrentRide);
934 dst.state = src.State;
935 if (src.ProximityTrackType == TrackElemType::None)
936 dst.proximity_track_type = 0xFF;
937 else
938 dst.proximity_track_type = OpenRCT2TrackTypeToRCT2(src.ProximityTrackType);
939 dst.proximity_base_height = src.ProximityBaseHeight;
940 dst.proximity_total = src.ProximityTotal;
941 for (size_t i = 0; i < std::size(dst.proximity_scores); i++)
942 {
943 dst.proximity_scores[i] = src.ProximityScores[i];
944 }
945 dst.num_brakes = src.AmountOfBrakes;
946 dst.num_reversers = src.AmountOfReversers;
947 dst.station_flags = src.StationFlags;
948 }
949
ExportRideMeasurements()950 void S6Exporter::ExportRideMeasurements()
951 {
952 // Get all the ride measurements
953 std::vector<Ride*> ridesWithMeasurements;
954 for (size_t i = 0; i < RCT12_MAX_RIDES_IN_PARK; i++)
955 {
956 auto ride = get_ride(static_cast<ride_id_t>(i));
957 if (ride != nullptr && ride->measurement != nullptr)
958 {
959 ridesWithMeasurements.push_back(ride);
960 }
961 }
962
963 // If there are more than S6 can hold, trim it by LRU
964 if (ridesWithMeasurements.size() > RCT12_RIDE_MEASUREMENT_MAX_ITEMS)
965 {
966 // Sort in order of last recently used
967 std::sort(ridesWithMeasurements.begin(), ridesWithMeasurements.end(), [](const Ride* a, const Ride* b) {
968 return a->measurement->last_use_tick > b->measurement->last_use_tick;
969 });
970 ridesWithMeasurements.resize(RCT12_RIDE_MEASUREMENT_MAX_ITEMS);
971 }
972
973 // Convert ride measurements to S6 format
974 uint8_t i{};
975 for (auto src : ridesWithMeasurements)
976 {
977 auto& dst = _s6.ride_measurements[i];
978 ExportRideMeasurement(_s6.ride_measurements[i], *src->measurement.get());
979
980 auto rideId = src->id;
981 dst.ride_index = static_cast<uint8_t>(rideId);
982 _s6.rides[dst.ride_index].measurement_index = i;
983 i++;
984 }
985 }
986
ExportRideMeasurement(RCT12RideMeasurement & dst,const RideMeasurement & src)987 void S6Exporter::ExportRideMeasurement(RCT12RideMeasurement& dst, const RideMeasurement& src)
988 {
989 dst.flags = src.flags;
990 dst.last_use_tick = src.last_use_tick;
991 dst.num_items = src.num_items;
992 dst.current_item = src.current_item;
993 dst.vehicle_index = src.vehicle_index;
994 dst.current_station = src.current_station;
995 for (size_t i = 0; i < std::size(src.velocity); i++)
996 {
997 dst.velocity[i] = src.velocity[i];
998 dst.altitude[i] = src.altitude[i];
999 dst.vertical[i] = src.vertical[i];
1000 dst.lateral[i] = src.lateral[i];
1001 }
1002 }
1003
ExportResearchedRideTypes()1004 void S6Exporter::ExportResearchedRideTypes()
1005 {
1006 std::fill(std::begin(_s6.researched_ride_types), std::end(_s6.researched_ride_types), false);
1007
1008 for (int32_t rideType = 0; rideType < RCT2_RIDE_TYPE_COUNT; rideType++)
1009 {
1010 if (ride_type_is_invented(rideType))
1011 {
1012 int32_t quadIndex = rideType >> 5;
1013 int32_t bitIndex = rideType & 0x1F;
1014 _s6.researched_ride_types[quadIndex] |= 1UL << bitIndex;
1015 }
1016 }
1017 }
1018
ExportResearchedRideEntries()1019 void S6Exporter::ExportResearchedRideEntries()
1020 {
1021 std::fill(std::begin(_s6.researched_ride_entries), std::end(_s6.researched_ride_entries), false);
1022
1023 for (int32_t rideEntryIndex = 0; rideEntryIndex < MAX_RIDE_OBJECTS; rideEntryIndex++)
1024 {
1025 if (ride_entry_is_invented(rideEntryIndex))
1026 {
1027 int32_t quadIndex = rideEntryIndex >> 5;
1028 int32_t bitIndex = rideEntryIndex & 0x1F;
1029 _s6.researched_ride_entries[quadIndex] |= 1UL << bitIndex;
1030 }
1031 }
1032 }
1033
ExportResearchedSceneryItems()1034 void S6Exporter::ExportResearchedSceneryItems()
1035 {
1036 std::fill(std::begin(_s6.researched_scenery_items), std::end(_s6.researched_scenery_items), false);
1037
1038 for (uint16_t sceneryEntryIndex = 0; sceneryEntryIndex < RCT2_MAX_RESEARCHED_SCENERY_ITEMS; sceneryEntryIndex++)
1039 {
1040 ScenerySelection scenerySelection = { static_cast<uint8_t>((sceneryEntryIndex >> 8) & 0xFF),
1041 static_cast<uint16_t>(sceneryEntryIndex & 0xFF) };
1042
1043 // SV6 allows for more scenery types than there are. Avoid triggering an assertion in scenery_is_invented().
1044 if (scenerySelection.SceneryType >= SCENERY_TYPE_COUNT)
1045 break;
1046
1047 if (scenery_is_invented(scenerySelection))
1048 {
1049 int32_t quadIndex = sceneryEntryIndex >> 5;
1050 int32_t bitIndex = sceneryEntryIndex & 0x1F;
1051 _s6.researched_scenery_items[quadIndex] |= 1UL << bitIndex;
1052 }
1053 }
1054 }
1055
ExportResearchList()1056 void S6Exporter::ExportResearchList()
1057 {
1058 size_t i = 0;
1059 for (const auto& researchItem : gResearchItemsInvented)
1060 {
1061 _s6.research_items[i++] = researchItem.ToRCT12ResearchItem();
1062 }
1063 _s6.research_items[i++] = { RCT12_RESEARCHED_ITEMS_SEPARATOR, 0 };
1064 for (const auto& researchItem : gResearchItemsUninvented)
1065 {
1066 _s6.research_items[i++] = researchItem.ToRCT12ResearchItem();
1067 }
1068 _s6.research_items[i++] = { RCT12_RESEARCHED_ITEMS_END, 0 };
1069 _s6.research_items[i] = { RCT12_RESEARCHED_ITEMS_END_2, 0 };
1070 }
1071
ExportMarketingCampaigns()1072 void S6Exporter::ExportMarketingCampaigns()
1073 {
1074 std::memset(_s6.campaign_weeks_left, 0, sizeof(_s6.campaign_weeks_left));
1075 std::memset(_s6.campaign_ride_index, 0, sizeof(_s6.campaign_ride_index));
1076 for (const auto& campaign : gMarketingCampaigns)
1077 {
1078 _s6.campaign_weeks_left[campaign.Type] = campaign.WeeksLeft | CAMPAIGN_ACTIVE_FLAG;
1079 if ((campaign.Flags & MarketingCampaignFlags::FIRST_WEEK))
1080 _s6.campaign_weeks_left[campaign.Type] |= CAMPAIGN_FIRST_WEEK_FLAG;
1081 if (campaign.Type == ADVERTISING_CAMPAIGN_RIDE_FREE || campaign.Type == ADVERTISING_CAMPAIGN_RIDE)
1082 {
1083 _s6.campaign_ride_index[campaign.Type] = OpenRCT2RideIdToRCT12RideId(campaign.RideId);
1084 }
1085 else if (campaign.Type == ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE)
1086 {
1087 _s6.campaign_ride_index[campaign.Type] = EnumValue(campaign.ShopItemType);
1088 }
1089 }
1090 }
1091
RebuildEntitySpatialLocation(const TileCoordsXY & loc)1092 void S6Exporter::RebuildEntitySpatialLocation(const TileCoordsXY& loc)
1093 {
1094 uint16_t previous = SPRITE_INDEX_NULL;
1095 for (auto* entity : EntityTileList(loc.ToCoordsXY()))
1096 {
1097 if (previous != SPRITE_INDEX_NULL)
1098 {
1099 _s6.sprites[previous].unknown.next_in_quadrant = entity->sprite_index;
1100 }
1101 previous = entity->sprite_index;
1102 }
1103 if (previous != SPRITE_INDEX_NULL)
1104 {
1105 _s6.sprites[previous].unknown.next_in_quadrant = SPRITE_INDEX_NULL;
1106 }
1107 }
1108
ExportStaffPatrolAreas()1109 void S6Exporter::ExportStaffPatrolAreas()
1110 {
1111 std::fill(std::begin(_s6.staff_modes), std::end(_s6.staff_modes), RCT2StaffMode::None);
1112 std::fill(std::begin(_s6.patrol_areas), std::end(_s6.patrol_areas), 0);
1113
1114 auto staffId = 0;
1115 for (auto* staff : EntityList<Staff>())
1116 {
1117 if (staff->HasPatrolArea())
1118 {
1119 const size_t staffPatrolOffset = staffId * STAFF_PATROL_AREA_SIZE;
1120 std::copy(
1121 std::begin(staff->PatrolInfo->Data), std::end(staff->PatrolInfo->Data), &_s6.patrol_areas[staffPatrolOffset]);
1122 _s6.staff_modes[staffId] = RCT2StaffMode::Patrol;
1123 }
1124 else
1125 {
1126 _s6.staff_modes[staffId] = RCT2StaffMode::Walk;
1127 }
1128
1129 _s6.sprites[staff->sprite_index].peep.staff_id = staffId;
1130
1131 staffId++;
1132 }
1133
1134 constexpr auto hasData = [](const auto& datapoint) { return datapoint != 0; };
1135 for (const auto type : { StaffType::Handyman, StaffType::Mechanic, StaffType::Security, StaffType::Entertainer })
1136 {
1137 const size_t staffPatrolOffset = (EnumValue(type) + STAFF_MAX_COUNT) * STAFF_PATROL_AREA_SIZE;
1138 const auto& area = GetMergedPatrolArea(type);
1139 std::copy(std::begin(area.Data), std::end(area.Data), &_s6.patrol_areas[staffPatrolOffset]);
1140 if (std::any_of(std::begin(area.Data), std::end(area.Data), hasData))
1141 {
1142 _s6.staff_modes[EnumValue(type) + STAFF_MAX_COUNT] = RCT2StaffMode::Patrol;
1143 }
1144 else
1145 {
1146 _s6.staff_modes[EnumValue(type) + STAFF_MAX_COUNT] = RCT2StaffMode::Walk;
1147 }
1148 }
1149 }
1150
RebuildEntityLinks()1151 void S6Exporter::RebuildEntityLinks()
1152 {
1153 // Rebuild next/previous linked list entity indexs
1154 for (auto list :
1155 { RCT12EntityLinkListOffset::Free, RCT12EntityLinkListOffset::Litter, RCT12EntityLinkListOffset::Misc,
1156 RCT12EntityLinkListOffset::Peep, RCT12EntityLinkListOffset::TrainHead, RCT12EntityLinkListOffset::Vehicle })
1157 {
1158 uint16_t previous = SPRITE_INDEX_NULL;
1159 // Intialise Head to NULL for situations where there are no entities of this type.
1160 _s6.sprite_lists_head[EnumValue(list) >> 1] = SPRITE_INDEX_NULL;
1161
1162 for (auto& entity : _s6.sprites)
1163 {
1164 if (entity.unknown.linked_list_type_offset == list)
1165 {
1166 _s6.sprite_lists_count[EnumValue(list) >> 1]++;
1167 _s6.sprites[entity.unknown.sprite_index].unknown.previous = previous;
1168 if (previous != SPRITE_INDEX_NULL)
1169 {
1170 _s6.sprites[previous].unknown.next = entity.unknown.sprite_index;
1171 }
1172 else
1173 {
1174 _s6.sprite_lists_head[EnumValue(list) >> 1] = entity.unknown.sprite_index;
1175 }
1176 _s6.sprites[entity.unknown.sprite_index].unknown.next = SPRITE_INDEX_NULL;
1177 previous = entity.unknown.sprite_index;
1178 }
1179 }
1180 }
1181
1182 // Rebuild next_in_quadrant linked list entity indexs
1183 // This in theory is not required but to be on the safe side we are rebuilding it.
1184 for (auto x = 0; x < 255; ++x)
1185 {
1186 for (auto y = 0; y < 255; ++y)
1187 {
1188 RebuildEntitySpatialLocation(TileCoordsXY{ x, y });
1189 }
1190 }
1191 // Rebuild next_in_quadrant linked list for LOCATION_NULL
1192 TileCoordsXY invalid;
1193 invalid.SetNull();
1194 RebuildEntitySpatialLocation(invalid);
1195 }
1196
GetRCT2LinkListOffset(const EntityBase * src)1197 constexpr RCT12EntityLinkListOffset GetRCT2LinkListOffset(const EntityBase* src)
1198 {
1199 RCT12EntityLinkListOffset output = RCT12EntityLinkListOffset::Free;
1200 switch (src->Type)
1201 {
1202 case EntityType::Vehicle:
1203 {
1204 auto veh = src->As<Vehicle>();
1205 if (veh != nullptr && veh->IsHead())
1206 {
1207 output = RCT12EntityLinkListOffset::TrainHead;
1208 }
1209 else
1210 {
1211 output = RCT12EntityLinkListOffset::Vehicle;
1212 }
1213 }
1214 break;
1215 case EntityType::Guest:
1216 case EntityType::Staff:
1217 output = RCT12EntityLinkListOffset::Peep;
1218 break;
1219 case EntityType::SteamParticle:
1220 case EntityType::MoneyEffect:
1221 case EntityType::CrashedVehicleParticle:
1222 case EntityType::ExplosionCloud:
1223 case EntityType::CrashSplash:
1224 case EntityType::ExplosionFlare:
1225 case EntityType::JumpingFountain:
1226 case EntityType::Balloon:
1227 case EntityType::Duck:
1228 output = RCT12EntityLinkListOffset::Misc;
1229 break;
1230 case EntityType::Litter:
1231 output = RCT12EntityLinkListOffset::Litter;
1232 break;
1233 default:
1234 break;
1235 }
1236 return output;
1237 }
1238
GetRCT2SpriteIdentifier(const EntityBase * src)1239 constexpr RCT12SpriteIdentifier GetRCT2SpriteIdentifier(const EntityBase* src)
1240 {
1241 RCT12SpriteIdentifier output = RCT12SpriteIdentifier::Null;
1242 switch (src->Type)
1243 {
1244 case EntityType::Vehicle:
1245 output = RCT12SpriteIdentifier::Vehicle;
1246 break;
1247 case EntityType::Guest:
1248 case EntityType::Staff:
1249 output = RCT12SpriteIdentifier::Peep;
1250 break;
1251 case EntityType::SteamParticle:
1252 case EntityType::MoneyEffect:
1253 case EntityType::CrashedVehicleParticle:
1254 case EntityType::ExplosionCloud:
1255 case EntityType::CrashSplash:
1256 case EntityType::ExplosionFlare:
1257 case EntityType::JumpingFountain:
1258 case EntityType::Balloon:
1259 case EntityType::Duck:
1260 output = RCT12SpriteIdentifier::Misc;
1261 break;
1262 case EntityType::Litter:
1263 output = RCT12SpriteIdentifier::Litter;
1264 break;
1265 default:
1266 break;
1267 }
1268 return output;
1269 }
1270
ExportEntityCommonProperties(RCT12SpriteBase * dst,const EntityBase * src)1271 void S6Exporter::ExportEntityCommonProperties(RCT12SpriteBase* dst, const EntityBase* src)
1272 {
1273 dst->sprite_identifier = GetRCT2SpriteIdentifier(src);
1274 dst->linked_list_type_offset = GetRCT2LinkListOffset(src);
1275 dst->next_in_quadrant = SPRITE_INDEX_NULL;
1276 dst->sprite_height_negative = src->sprite_height_negative;
1277 dst->sprite_index = src->sprite_index;
1278 dst->flags = 0;
1279 dst->x = static_cast<int16_t>(src->x);
1280 dst->y = static_cast<int16_t>(src->y);
1281 dst->z = static_cast<int16_t>(src->z);
1282 dst->sprite_width = src->sprite_width;
1283 dst->sprite_height_positive = src->sprite_height_positive;
1284 dst->sprite_left = src->SpriteRect.GetLeft();
1285 dst->sprite_top = src->SpriteRect.GetTop();
1286 dst->sprite_right = src->SpriteRect.GetRight();
1287 dst->sprite_bottom = src->SpriteRect.GetBottom();
1288 dst->sprite_direction = src->sprite_direction;
1289 }
1290
ExportEntity(RCT2SpriteVehicle * dst,const Vehicle * src)1291 template<> void S6Exporter::ExportEntity(RCT2SpriteVehicle* dst, const Vehicle* src)
1292 {
1293 const auto* ride = src->GetRide();
1294
1295 ExportEntityCommonProperties(dst, src);
1296 dst->type = EnumValue(src->SubType);
1297 dst->Pitch = src->Pitch;
1298 dst->bank_rotation = src->bank_rotation;
1299 dst->remaining_distance = src->remaining_distance;
1300 dst->velocity = src->velocity;
1301 dst->acceleration = src->acceleration;
1302 dst->ride = static_cast<uint8_t>(src->ride);
1303 dst->vehicle_type = src->vehicle_type;
1304 dst->colours = src->colours;
1305 dst->track_progress = src->track_progress;
1306 if (ride != nullptr && ride->mode == RideMode::BoatHire && src->status == Vehicle::Status::TravellingBoat)
1307 {
1308 if (src->BoatLocation.IsNull())
1309 {
1310 dst->boat_location.SetNull();
1311 }
1312 else
1313 {
1314 dst->boat_location = { static_cast<uint8_t>(src->BoatLocation.x / COORDS_XY_STEP),
1315 static_cast<uint8_t>(src->BoatLocation.y / COORDS_XY_STEP) };
1316 }
1317 }
1318 else
1319 {
1320 auto trackType = OpenRCT2TrackTypeToRCT2(src->GetTrackType());
1321 // Track direction and type are in the same field
1322 dst->SetTrackType(trackType);
1323 dst->SetTrackDirection(src->GetTrackDirection());
1324 }
1325 dst->track_x = src->TrackLocation.x;
1326 dst->track_y = src->TrackLocation.y;
1327 dst->track_z = src->TrackLocation.z;
1328 dst->next_vehicle_on_train = src->next_vehicle_on_train;
1329 dst->prev_vehicle_on_ride = src->prev_vehicle_on_ride;
1330 dst->next_vehicle_on_ride = src->next_vehicle_on_ride;
1331 dst->var_44 = src->var_44;
1332 dst->mass = src->mass;
1333 dst->update_flags = src->update_flags;
1334 dst->SwingSprite = src->SwingSprite;
1335 dst->current_station = src->current_station;
1336 dst->current_time = src->current_time;
1337 dst->crash_z = src->crash_z;
1338 dst->status = static_cast<uint8_t>(src->status);
1339 dst->sub_state = src->sub_state;
1340 for (size_t i = 0; i < std::size(src->peep); i++)
1341 {
1342 dst->peep[i] = src->peep[i];
1343 dst->peep_tshirt_colours[i] = src->peep_tshirt_colours[i];
1344 }
1345 dst->num_seats = src->num_seats;
1346 dst->num_peeps = src->num_peeps;
1347 dst->next_free_seat = src->next_free_seat;
1348 dst->restraints_position = src->restraints_position;
1349 dst->crash_x = src->crash_x;
1350 dst->sound2_flags = src->sound2_flags;
1351 dst->spin_sprite = src->spin_sprite;
1352 dst->sound1_id = static_cast<uint8_t>(src->sound1_id);
1353 dst->sound1_volume = src->sound1_volume;
1354 dst->sound2_id = static_cast<uint8_t>(src->sound2_id);
1355 dst->sound2_volume = src->sound2_volume;
1356 dst->sound_vector_factor = src->sound_vector_factor;
1357 dst->time_waiting = src->time_waiting;
1358 dst->speed = src->speed;
1359 dst->powered_acceleration = src->powered_acceleration;
1360 dst->dodgems_collision_direction = src->dodgems_collision_direction;
1361 dst->animation_frame = src->animation_frame;
1362 dst->animationState = src->animationState;
1363 dst->scream_sound_id = static_cast<uint8_t>(src->scream_sound_id);
1364 dst->TrackSubposition = static_cast<uint8_t>(src->TrackSubposition);
1365 dst->var_CE = src->var_CE;
1366 dst->var_CF = src->var_CF;
1367 dst->lost_time_out = src->lost_time_out;
1368 dst->vertical_drop_countdown = src->vertical_drop_countdown;
1369 dst->var_D3 = src->var_D3;
1370 dst->mini_golf_current_animation = EnumValue(src->mini_golf_current_animation);
1371 dst->mini_golf_flags = src->mini_golf_flags;
1372 dst->ride_subtype = OpenRCT2EntryIndexToRCTEntryIndex(src->ride_subtype);
1373 dst->colours_extended = src->colours_extended;
1374 dst->seat_rotation = src->seat_rotation;
1375 dst->target_seat_rotation = src->target_seat_rotation;
1376 dst->flags = src->IsCrashedVehicle ? RCT12_SPRITE_FLAGS_IS_CRASHED_VEHICLE_SPRITE : 0;
1377 }
1378
ExportEntity(RCT2SpritePeep * dst,const Guest * src)1379 template<> void S6Exporter::ExportEntity(RCT2SpritePeep* dst, const Guest* src)
1380 {
1381 ExportEntityPeep(dst, src);
1382 dst->outside_of_park = static_cast<uint8_t>(src->OutsideOfPark);
1383 dst->no_of_rides = src->GuestNumRides;
1384 dst->happiness = src->Happiness;
1385 dst->happiness_target = src->HappinessTarget;
1386 dst->nausea = src->Nausea;
1387 dst->nausea_target = src->NauseaTarget;
1388 dst->hunger = src->Hunger;
1389 dst->thirst = src->Thirst;
1390 dst->toilet = src->Toilet;
1391 dst->time_to_consume = src->TimeToConsume;
1392 dst->intensity = static_cast<uint8_t>(src->Intensity);
1393 dst->nausea_tolerance = EnumValue(src->NauseaTolerance);
1394 dst->paid_on_drink = src->PaidOnDrink;
1395 auto* typeHistory = OpenRCT2::RideUse::GetTypeHistory().GetAll(src->sprite_index);
1396 if (typeHistory != nullptr)
1397 {
1398 for (auto typeId : *typeHistory)
1399 {
1400 dst->ride_types_been_on[typeId / 8] |= (1 << (typeId % 8));
1401 }
1402 }
1403 auto* rideHistory = OpenRCT2::RideUse::GetHistory().GetAll(src->sprite_index);
1404 if (rideHistory != nullptr)
1405 {
1406 for (auto rideId : *rideHistory)
1407 {
1408 const auto rideIndex = EnumValue(rideId);
1409 dst->rides_been_on[rideIndex / 8] |= (1 << (rideIndex % 8));
1410 }
1411 }
1412 dst->item_extra_flags = static_cast<uint32_t>(src->GetItemFlags() >> 32);
1413 dst->photo1_ride_ref = OpenRCT2RideIdToRCT12RideId(src->Photo1RideRef);
1414 dst->photo2_ride_ref = OpenRCT2RideIdToRCT12RideId(src->Photo2RideRef);
1415 dst->photo3_ride_ref = OpenRCT2RideIdToRCT12RideId(src->Photo3RideRef);
1416 dst->photo4_ride_ref = OpenRCT2RideIdToRCT12RideId(src->Photo4RideRef);
1417 dst->next_in_queue = src->GuestNextInQueue;
1418 dst->time_in_queue = src->TimeInQueue;
1419 dst->cash_in_pocket = src->CashInPocket;
1420 dst->cash_spent = src->CashSpent;
1421 dst->park_entry_time = src->ParkEntryTime;
1422 dst->rejoin_queue_timeout = src->RejoinQueueTimeout;
1423 dst->previous_ride = OpenRCT2RideIdToRCT12RideId(src->PreviousRide);
1424 dst->previous_ride_time_out = src->PreviousRideTimeOut;
1425 for (size_t i = 0; i < std::size(src->Thoughts); i++)
1426 {
1427 auto srcThought = &src->Thoughts[i];
1428 auto dstThought = &dst->thoughts[i];
1429 dstThought->type = static_cast<uint8_t>(srcThought->type);
1430 dstThought->item = static_cast<uint8_t>(srcThought->item);
1431 dstThought->freshness = srcThought->freshness;
1432 dstThought->fresh_timeout = srcThought->fresh_timeout;
1433 }
1434 dst->guest_heading_to_ride_id = OpenRCT2RideIdToRCT12RideId(src->GuestHeadingToRideId);
1435 dst->peep_is_lost_countdown = src->GuestIsLostCountdown;
1436 dst->litter_count = src->LitterCount;
1437 dst->time_on_ride = src->GuestTimeOnRide;
1438 dst->disgusting_count = src->DisgustingCount;
1439 dst->paid_to_enter = src->PaidToEnter;
1440 dst->paid_on_rides = src->PaidOnRides;
1441 dst->paid_on_food = src->PaidOnFood;
1442 dst->paid_on_souvenirs = src->PaidOnSouvenirs;
1443 dst->no_of_food = src->AmountOfFood;
1444 dst->no_of_drinks = src->AmountOfDrinks;
1445 dst->no_of_souvenirs = src->AmountOfSouvenirs;
1446 dst->vandalism_seen = src->VandalismSeen;
1447 dst->voucher_type = src->VoucherType;
1448 dst->voucher_arguments = OpenRCT2RideIdToRCT12RideId(src->VoucherRideId);
1449 dst->surroundings_thought_timeout = src->SurroundingsThoughtTimeout;
1450 dst->angriness = src->Angriness;
1451 dst->time_lost = src->TimeLost;
1452 dst->days_in_queue = src->DaysInQueue;
1453 dst->balloon_colour = src->BalloonColour;
1454 dst->umbrella_colour = src->UmbrellaColour;
1455 dst->hat_colour = src->HatColour;
1456 dst->favourite_ride = OpenRCT2RideIdToRCT12RideId(src->FavouriteRide);
1457 dst->favourite_ride_rating = src->FavouriteRideRating;
1458 dst->item_standard_flags = static_cast<uint32_t>(src->GetItemFlags());
1459 }
ExportEntity(RCT2SpritePeep * dst,const Staff * src)1460 template<> void S6Exporter::ExportEntity(RCT2SpritePeep* dst, const Staff* src)
1461 {
1462 ExportEntityPeep(dst, src);
1463 dst->staff_type = static_cast<uint8_t>(src->AssignedStaffType);
1464 dst->mechanic_time_since_call = src->MechanicTimeSinceCall;
1465 dst->park_entry_time = src->HireDate;
1466 dst->staff_orders = src->StaffOrders;
1467 dst->staff_mowing_timeout = src->StaffMowingTimeout;
1468 dst->paid_to_enter = src->StaffLawnsMown;
1469 dst->paid_on_rides = src->StaffGardensWatered;
1470 dst->paid_on_food = src->StaffLitterSwept;
1471 dst->paid_on_souvenirs = src->StaffBinsEmptied;
1472 }
1473
ExportEntityPeep(RCT2SpritePeep * dst,const Peep * src)1474 void S6Exporter::ExportEntityPeep(RCT2SpritePeep* dst, const Peep* src)
1475 {
1476 ExportEntityCommonProperties(dst, src);
1477
1478 auto generateName = true;
1479 if (src->Name != nullptr)
1480 {
1481 auto stringId = AllocateUserString(src->Name);
1482 if (stringId.has_value())
1483 {
1484 dst->name_string_idx = stringId.value();
1485 generateName = false;
1486 }
1487 else
1488 {
1489 log_warning(
1490 "Unable to allocate user string for peep #%d (%s) during S6 export.", static_cast<int>(src->sprite_index),
1491 src->Name);
1492 }
1493 }
1494 if (generateName)
1495 {
1496 auto* staff = src->As<Staff>();
1497 if (staff != nullptr)
1498 {
1499 static constexpr const rct_string_id staffNames[] = {
1500 STR_HANDYMAN_X,
1501 STR_MECHANIC_X,
1502 STR_SECURITY_GUARD_X,
1503 STR_ENTERTAINER_X,
1504 };
1505 dst->name_string_idx = staffNames[static_cast<uint8_t>(staff->AssignedStaffType) % sizeof(staffNames)];
1506 }
1507 else if (gParkFlags & PARK_FLAGS_SHOW_REAL_GUEST_NAMES)
1508 {
1509 dst->name_string_idx = get_real_name_string_id_from_id(src->Id);
1510 }
1511 else
1512 {
1513 dst->name_string_idx = STR_GUEST_X;
1514 }
1515 }
1516
1517 dst->next_x = src->NextLoc.x;
1518 dst->next_y = src->NextLoc.y;
1519 dst->next_z = src->NextLoc.z / COORDS_Z_STEP;
1520 dst->next_flags = src->NextFlags;
1521 dst->state = static_cast<uint8_t>(src->State);
1522 dst->sub_state = src->SubState;
1523 dst->sprite_type = static_cast<uint8_t>(src->SpriteType);
1524 dst->peep_type = static_cast<uint8_t>(src->Type == EntityType::Staff ? RCT12PeepType::Staff : RCT12PeepType::Guest);
1525 dst->tshirt_colour = src->TshirtColour;
1526 dst->trousers_colour = src->TrousersColour;
1527 dst->destination_x = src->DestinationX;
1528 dst->destination_y = src->DestinationY;
1529 dst->destination_tolerance = src->DestinationTolerance;
1530 dst->var_37 = src->Var37;
1531 dst->energy = src->Energy;
1532 dst->energy_target = src->EnergyTarget;
1533 dst->mass = src->Mass;
1534 dst->window_invalidate_flags = src->WindowInvalidateFlags;
1535 dst->current_ride = OpenRCT2RideIdToRCT12RideId(src->CurrentRide);
1536 dst->current_ride_station = src->CurrentRideStation;
1537 dst->current_train = src->CurrentTrain;
1538 dst->time_to_sitdown = src->TimeToSitdown;
1539 dst->special_sprite = src->SpecialSprite;
1540 dst->action_sprite_type = static_cast<uint8_t>(src->ActionSpriteType);
1541 dst->next_action_sprite_type = static_cast<uint8_t>(src->NextActionSpriteType);
1542 dst->action_sprite_image_offset = src->ActionSpriteImageOffset;
1543 dst->action = static_cast<uint8_t>(src->Action);
1544 dst->action_frame = src->ActionFrame;
1545 dst->step_progress = src->StepProgress;
1546 dst->direction = src->PeepDirection;
1547 dst->interaction_ride_index = OpenRCT2RideIdToRCT12RideId(src->InteractionRideIndex);
1548 dst->id = src->Id;
1549 dst->path_check_optimisation = src->PathCheckOptimisation;
1550 dst->peep_flags = src->PeepFlags;
1551 if (src->PathfindGoal.IsNull())
1552 {
1553 dst->pathfind_goal = { 0xFF, 0xFF, 0xFF, INVALID_DIRECTION };
1554 }
1555 else
1556 {
1557 dst->pathfind_goal = { static_cast<uint8_t>(src->PathfindGoal.x), static_cast<uint8_t>(src->PathfindGoal.y),
1558 static_cast<uint8_t>(src->PathfindGoal.z), src->PathfindGoal.direction };
1559 }
1560 for (size_t i = 0; i < std::size(src->PathfindHistory); i++)
1561 {
1562 if (src->PathfindHistory[i].IsNull())
1563 {
1564 dst->pathfind_history[i] = { 0xFF, 0xFF, 0xFF, INVALID_DIRECTION };
1565 }
1566 else
1567 {
1568 dst->pathfind_history[i] = { static_cast<uint8_t>(src->PathfindHistory[i].x),
1569 static_cast<uint8_t>(src->PathfindHistory[i].y),
1570 static_cast<uint8_t>(src->PathfindHistory[i].z), src->PathfindHistory[i].direction };
1571 }
1572 }
1573 dst->no_action_frame_num = src->WalkingFrameNum;
1574 }
1575
ExportEntity(RCT12SpriteSteamParticle * dst,const SteamParticle * src)1576 template<> void S6Exporter::ExportEntity(RCT12SpriteSteamParticle* dst, const SteamParticle* src)
1577 {
1578 ExportEntityCommonProperties(dst, src);
1579 dst->type = EnumValue(RCT12MiscEntityType::SteamParticle);
1580 dst->time_to_move = src->time_to_move;
1581 dst->frame = src->frame;
1582 }
ExportEntity(RCT12SpriteMoneyEffect * dst,const MoneyEffect * src)1583 template<> void S6Exporter::ExportEntity(RCT12SpriteMoneyEffect* dst, const MoneyEffect* src)
1584 {
1585 ExportEntityCommonProperties(dst, src);
1586 dst->type = EnumValue(RCT12MiscEntityType::MoneyEffect);
1587 dst->move_delay = src->MoveDelay;
1588 dst->num_movements = src->NumMovements;
1589 dst->vertical = src->Vertical;
1590 dst->value = src->Value;
1591 dst->offset_x = src->OffsetX;
1592 dst->wiggle = src->Wiggle;
1593 }
ExportEntity(RCT12SpriteCrashedVehicleParticle * dst,const VehicleCrashParticle * src)1594 template<> void S6Exporter::ExportEntity(RCT12SpriteCrashedVehicleParticle* dst, const VehicleCrashParticle* src)
1595 {
1596 ExportEntityCommonProperties(dst, src);
1597 dst->type = EnumValue(RCT12MiscEntityType::CrashedVehicleParticle);
1598 dst->frame = src->frame;
1599 dst->time_to_live = src->time_to_live;
1600 dst->frame = src->frame;
1601 dst->colour[0] = src->colour[0];
1602 dst->colour[1] = src->colour[1];
1603 dst->crashed_sprite_base = src->crashed_sprite_base;
1604 dst->velocity_x = src->velocity_x;
1605 dst->velocity_y = src->velocity_y;
1606 dst->velocity_z = src->velocity_z;
1607 dst->acceleration_x = src->acceleration_x;
1608 dst->acceleration_y = src->acceleration_y;
1609 dst->acceleration_z = src->acceleration_z;
1610 }
ExportEntity(RCT12SpriteJumpingFountain * dst,const JumpingFountain * src)1611 template<> void S6Exporter::ExportEntity(RCT12SpriteJumpingFountain* dst, const JumpingFountain* src)
1612 {
1613 ExportEntityCommonProperties(dst, src);
1614 dst->type = EnumValue(
1615 src->FountainType == JumpingFountainType::Snow ? RCT12MiscEntityType::JumpingFountainSnow
1616 : RCT12MiscEntityType::JumpingFountainWater);
1617 dst->num_ticks_alive = src->NumTicksAlive;
1618 dst->frame = src->frame;
1619 dst->fountain_flags = src->FountainFlags;
1620 dst->target_x = src->TargetX;
1621 dst->target_y = src->TargetY;
1622 dst->target_y = src->TargetY;
1623 dst->iteration = src->Iteration;
1624 }
ExportEntity(RCT12SpriteBalloon * dst,const Balloon * src)1625 template<> void S6Exporter::ExportEntity(RCT12SpriteBalloon* dst, const Balloon* src)
1626 {
1627 ExportEntityCommonProperties(dst, src);
1628 dst->type = EnumValue(RCT12MiscEntityType::Balloon);
1629 dst->popped = src->popped;
1630 dst->time_to_move = src->time_to_move;
1631 dst->frame = src->frame;
1632 dst->colour = src->colour;
1633 }
ExportEntity(RCT12SpriteDuck * dst,const Duck * src)1634 template<> void S6Exporter::ExportEntity(RCT12SpriteDuck* dst, const Duck* src)
1635 {
1636 ExportEntityCommonProperties(dst, src);
1637 dst->type = EnumValue(RCT12MiscEntityType::Duck);
1638 dst->frame = src->frame;
1639 dst->target_x = src->target_x;
1640 dst->target_y = src->target_y;
1641 dst->state = EnumValue(src->state);
1642 }
ExportEntity(RCT12SpriteParticle * dst,const ExplosionCloud * src)1643 template<> void S6Exporter::ExportEntity(RCT12SpriteParticle* dst, const ExplosionCloud* src)
1644 {
1645 ExportEntityCommonProperties(dst, src);
1646 dst->type = EnumValue(RCT12MiscEntityType::ExplosionCloud);
1647 dst->frame = src->frame;
1648 }
ExportEntity(RCT12SpriteParticle * dst,const ExplosionFlare * src)1649 template<> void S6Exporter::ExportEntity(RCT12SpriteParticle* dst, const ExplosionFlare* src)
1650 {
1651 ExportEntityCommonProperties(dst, src);
1652 dst->type = EnumValue(RCT12MiscEntityType::ExplosionFlare);
1653 dst->frame = src->frame;
1654 }
ExportEntity(RCT12SpriteParticle * dst,const CrashSplashParticle * src)1655 template<> void S6Exporter::ExportEntity(RCT12SpriteParticle* dst, const CrashSplashParticle* src)
1656 {
1657 ExportEntityCommonProperties(dst, src);
1658 dst->type = EnumValue(RCT12MiscEntityType::CrashSplash);
1659 dst->frame = src->frame;
1660 }
1661
ExportEntity(RCT12SpriteLitter * dst,const Litter * src)1662 template<> void S6Exporter::ExportEntity(RCT12SpriteLitter* dst, const Litter* src)
1663 {
1664 ExportEntityCommonProperties(dst, src);
1665 dst->type = EnumValue(src->SubType);
1666 dst->creationTick = src->creationTick;
1667 }
1668
ExportEntities()1669 void S6Exporter::ExportEntities()
1670 {
1671 // Clear everything to free
1672 for (int32_t i = 0; i < RCT2_MAX_SPRITES; i++)
1673 {
1674 auto& entity = _s6.sprites[i];
1675 std::memset(&entity, 0, sizeof(entity));
1676 entity.unknown.sprite_identifier = RCT12SpriteIdentifier::Null;
1677 entity.unknown.sprite_index = i;
1678 entity.unknown.linked_list_type_offset = RCT12EntityLinkListOffset::Free;
1679 }
1680
1681 for (auto* entity : EntityList<Guest>())
1682 {
1683 ExportEntity(&_s6.sprites[entity->sprite_index].peep, entity);
1684 }
1685 for (auto* entity : EntityList<Staff>())
1686 {
1687 ExportEntity(&_s6.sprites[entity->sprite_index].peep, entity);
1688 }
1689 for (auto* entity : EntityList<Vehicle>())
1690 {
1691 ExportEntity(&_s6.sprites[entity->sprite_index].vehicle, entity);
1692 }
1693 for (auto* entity : EntityList<Litter>())
1694 {
1695 ExportEntity(&_s6.sprites[entity->sprite_index].litter, entity);
1696 }
1697 for (auto* entity : EntityList<Duck>())
1698 {
1699 ExportEntity(&_s6.sprites[entity->sprite_index].duck, entity);
1700 }
1701 for (auto* entity : EntityList<SteamParticle>())
1702 {
1703 ExportEntity(&_s6.sprites[entity->sprite_index].steam_particle, entity);
1704 }
1705 for (auto* entity : EntityList<MoneyEffect>())
1706 {
1707 ExportEntity(&_s6.sprites[entity->sprite_index].money_effect, entity);
1708 }
1709 for (auto* entity : EntityList<VehicleCrashParticle>())
1710 {
1711 ExportEntity(&_s6.sprites[entity->sprite_index].crashed_vehicle_particle, entity);
1712 }
1713 for (auto* entity : EntityList<JumpingFountain>())
1714 {
1715 ExportEntity(&_s6.sprites[entity->sprite_index].jumping_fountain, entity);
1716 }
1717 for (auto* entity : EntityList<Balloon>())
1718 {
1719 ExportEntity(&_s6.sprites[entity->sprite_index].balloon, entity);
1720 }
1721 for (auto* entity : EntityList<ExplosionCloud>())
1722 {
1723 ExportEntity(&_s6.sprites[entity->sprite_index].misc_particle, entity);
1724 }
1725 for (auto* entity : EntityList<ExplosionFlare>())
1726 {
1727 ExportEntity(&_s6.sprites[entity->sprite_index].misc_particle, entity);
1728 }
1729 for (auto* entity : EntityList<CrashSplashParticle>())
1730 {
1731 ExportEntity(&_s6.sprites[entity->sprite_index].misc_particle, entity);
1732 }
1733
1734 RebuildEntityLinks();
1735 }
1736
ExportBanners()1737 void S6Exporter::ExportBanners()
1738 {
1739 for (BannerIndex i = 0; i < RCT2_MAX_BANNERS_IN_PARK; i++)
1740 {
1741 auto src = GetBanner(i);
1742 auto dst = &_s6.banners[i];
1743 if (src != nullptr)
1744 {
1745 ExportBanner(*dst, *src);
1746 }
1747 else
1748 {
1749 ExportBanner(*dst, {});
1750 }
1751 }
1752 }
1753
ExportBanner(RCT12Banner & dst,const Banner & src)1754 void S6Exporter::ExportBanner(RCT12Banner& dst, const Banner& src)
1755 {
1756 dst = {};
1757 dst.type = OpenRCT2EntryIndexToRCTEntryIndex(src.type);
1758
1759 if (!src.IsNull())
1760 {
1761 dst.flags = src.flags;
1762
1763 dst.string_idx = STR_DEFAULT_SIGN;
1764
1765 std::string bannerText;
1766 if (!(src.flags & BANNER_FLAG_IS_WALL) && !(src.flags & BANNER_FLAG_IS_LARGE_SCENERY))
1767 {
1768 auto formatCode = static_cast<codepoint_t>(RCT2_STRING_FORMAT_COLOUR_START + src.text_colour);
1769 String::AppendCodepoint(bannerText, formatCode);
1770 }
1771 bannerText.append(src.text);
1772
1773 auto stringId = AllocateUserString(bannerText);
1774 if (stringId.has_value())
1775 {
1776 dst.string_idx = stringId.value();
1777 }
1778
1779 if (src.flags & BANNER_FLAG_LINKED_TO_RIDE)
1780 {
1781 dst.ride_index = OpenRCT2RideIdToRCT12RideId(src.ride_index);
1782 }
1783 else
1784 {
1785 dst.colour = src.colour;
1786 }
1787 dst.text_colour = src.text_colour;
1788 dst.x = src.position.x;
1789 dst.y = src.position.y;
1790 }
1791 }
1792
ExportMapAnimations()1793 void S6Exporter::ExportMapAnimations()
1794 {
1795 const auto& mapAnimations = GetMapAnimations();
1796 auto numAnimations = std::min(mapAnimations.size(), std::size(_s6.map_animations));
1797 _s6.num_map_animations = static_cast<uint16_t>(numAnimations);
1798 for (size_t i = 0; i < numAnimations; i++)
1799 {
1800 const auto& src = mapAnimations[i];
1801 auto& dst = _s6.map_animations[i];
1802
1803 dst.type = src.type;
1804 // In RCT12MapAnimation, the x and y coordinates use big coords, while the z coordinate uses small coords.
1805 dst.x = src.location.x;
1806 dst.y = src.location.y;
1807 dst.baseZ = src.location.z / COORDS_Z_STEP;
1808 }
1809 }
1810
ExportTileElements()1811 void S6Exporter::ExportTileElements()
1812 {
1813 const auto& tileElements = GetTileElements();
1814 for (uint32_t index = 0; index < RCT2_MAX_TILE_ELEMENTS; index++)
1815 {
1816 auto dst = &_s6.tile_elements[index];
1817 if (index >= tileElements.size())
1818 {
1819 dst = {};
1820 continue;
1821 }
1822 auto src = &tileElements[index];
1823 if (src->base_height == MAX_ELEMENT_HEIGHT)
1824 {
1825 std::memcpy(dst, src, sizeof(*dst));
1826 }
1827 else
1828 {
1829 auto tileElementType = static_cast<RCT12TileElementType>(src->GetType());
1830 if (tileElementType == RCT12TileElementType::Corrupt || tileElementType == RCT12TileElementType::EightCarsCorrupt14
1831 || tileElementType == RCT12TileElementType::EightCarsCorrupt15)
1832 std::memcpy(dst, src, sizeof(*dst));
1833 else
1834 ExportTileElement(dst, src);
1835 }
1836 }
1837 _s6.next_free_tile_element_pointer_index = static_cast<uint32_t>(tileElements.size());
1838 }
1839
ExportTileElement(RCT12TileElement * dst,const TileElement * src)1840 void S6Exporter::ExportTileElement(RCT12TileElement* dst, const TileElement* src)
1841 {
1842 // Todo: allow for changing definition of OpenRCT2 tile element types - replace with a map
1843 uint8_t tileElementType = src->GetType();
1844 dst->ClearAs(tileElementType);
1845 dst->SetDirection(src->GetDirection());
1846 dst->base_height = src->base_height;
1847 dst->clearance_height = src->clearance_height;
1848
1849 // All saved in "flags"
1850 dst->SetOccupiedQuadrants(src->GetOccupiedQuadrants());
1851 dst->SetGhost(src->IsGhost());
1852 dst->SetLastForTile(src->IsLastForTile());
1853
1854 switch (tileElementType)
1855 {
1856 case TILE_ELEMENT_TYPE_SURFACE:
1857 {
1858 auto dst2 = dst->AsSurface();
1859 auto src2 = src->AsSurface();
1860
1861 dst2->SetSlope(src2->GetSlope());
1862 dst2->SetSurfaceStyle(src2->GetSurfaceStyle());
1863 dst2->SetEdgeStyle(src2->GetEdgeStyle());
1864 dst2->SetGrassLength(src2->GetGrassLength());
1865 dst2->SetOwnership(src2->GetOwnership());
1866 dst2->SetParkFences(src2->GetParkFences());
1867 dst2->SetWaterHeight(src2->GetWaterHeight());
1868 dst2->SetHasTrackThatNeedsWater(src2->HasTrackThatNeedsWater());
1869
1870 break;
1871 }
1872 case TILE_ELEMENT_TYPE_PATH:
1873 {
1874 auto dst2 = dst->AsPath();
1875 auto src2 = src->AsPath();
1876
1877 dst2->SetPathEntryIndex(src2->GetLegacyPathEntryIndex());
1878 dst2->SetQueueBannerDirection(src2->GetQueueBannerDirection());
1879 dst2->SetSloped(src2->IsSloped());
1880 dst2->SetSlopeDirection(src2->GetSlopeDirection());
1881 dst2->SetRideIndex(OpenRCT2RideIdToRCT12RideId(src2->GetRideIndex()));
1882 dst2->SetStationIndex(src2->GetStationIndex());
1883 dst2->SetWide(src2->IsWide());
1884 dst2->SetIsQueue(src2->IsQueue());
1885 dst2->SetHasQueueBanner(src2->HasQueueBanner());
1886 dst2->SetEdges(src2->GetEdges());
1887 dst2->SetCorners(src2->GetCorners());
1888 dst2->SetAddition(src2->GetAddition());
1889 dst2->SetAdditionIsGhost(src2->AdditionIsGhost());
1890 dst2->SetAdditionStatus(src2->GetAdditionStatus());
1891 dst2->SetIsBroken(src2->IsBroken());
1892 dst2->SetIsBlockedByVehicle(src2->IsBlockedByVehicle());
1893
1894 break;
1895 }
1896 case TILE_ELEMENT_TYPE_TRACK:
1897 {
1898 auto dst2 = dst->AsTrack();
1899 auto src2 = src->AsTrack();
1900
1901 auto trackType = OpenRCT2TrackTypeToRCT2(src2->GetTrackType());
1902 dst2->SetTrackType(static_cast<uint8_t>(trackType));
1903 dst2->SetSequenceIndex(src2->GetSequenceIndex());
1904 dst2->SetRideIndex(OpenRCT2RideIdToRCT12RideId(src2->GetRideIndex()));
1905 dst2->SetColourScheme(src2->GetColourScheme());
1906 dst2->SetStationIndex(src2->GetStationIndex());
1907 dst2->SetHasGreenLight(src2->HasGreenLight());
1908 dst2->SetHasChain(src2->HasChain());
1909 dst2->SetHasCableLift(src2->HasCableLift());
1910 dst2->SetInverted(src2->IsInverted());
1911 dst2->SetBrakeBoosterSpeed(src2->GetBrakeBoosterSpeed());
1912 dst2->SetPhotoTimeout(src2->GetPhotoTimeout());
1913 dst2->SetBlockBrakeClosed(src2->BlockBrakeClosed());
1914 dst2->SetIsIndestructible(src2->IsIndestructible());
1915
1916 // Skipping IsHighlighted()
1917
1918 // This has to be done last, since the maze entry shares fields with the colour and sequence fields.
1919 auto ride = get_ride(static_cast<ride_id_t>(dst2->GetRideIndex()));
1920 if (ride != nullptr)
1921 {
1922 if (ride->type == RIDE_TYPE_MAZE)
1923 {
1924 dst2->SetMazeEntry(src2->GetMazeEntry());
1925 }
1926 else if (ride->type == RIDE_TYPE_GHOST_TRAIN)
1927 {
1928 dst2->SetDoorAState(src2->GetDoorAState());
1929 dst2->SetDoorBState(src2->GetDoorBState());
1930 }
1931 else
1932 {
1933 dst2->SetSeatRotation(src2->GetSeatRotation());
1934 }
1935 }
1936 // _Should_ not happen, but if it does, pick the most likely option.
1937 else
1938 {
1939 dst2->SetSeatRotation(src2->GetSeatRotation());
1940 }
1941
1942 break;
1943 }
1944 case TILE_ELEMENT_TYPE_SMALL_SCENERY:
1945 {
1946 auto dst2 = dst->AsSmallScenery();
1947 auto src2 = src->AsSmallScenery();
1948
1949 dst2->SetEntryIndex(src2->GetEntryIndex());
1950 dst2->SetAge(src2->GetAge());
1951 dst2->SetSceneryQuadrant(src2->GetSceneryQuadrant());
1952 dst2->SetPrimaryColour(src2->GetPrimaryColour());
1953 dst2->SetSecondaryColour(src2->GetSecondaryColour());
1954 if (src2->NeedsSupports())
1955 dst2->SetNeedsSupports();
1956
1957 break;
1958 }
1959 case TILE_ELEMENT_TYPE_ENTRANCE:
1960 {
1961 auto dst2 = dst->AsEntrance();
1962 auto src2 = src->AsEntrance();
1963
1964 dst2->SetEntranceType(src2->GetEntranceType());
1965 dst2->SetRideIndex(OpenRCT2RideIdToRCT12RideId(src2->GetRideIndex()));
1966 dst2->SetStationIndex(src2->GetStationIndex());
1967 dst2->SetSequenceIndex(src2->GetSequenceIndex());
1968 dst2->SetPathType(src2->GetLegacyPathEntryIndex());
1969
1970 break;
1971 }
1972 case TILE_ELEMENT_TYPE_WALL:
1973 {
1974 auto dst2 = dst->AsWall();
1975 auto src2 = src->AsWall();
1976
1977 dst2->SetEntryIndex(src2->GetEntryIndex());
1978 dst2->SetSlope(src2->GetSlope());
1979 dst2->SetPrimaryColour(src2->GetPrimaryColour());
1980 dst2->SetSecondaryColour(src2->GetSecondaryColour());
1981 dst2->SetTertiaryColour(src2->GetTertiaryColour());
1982 dst2->SetAnimationFrame(src2->GetAnimationFrame());
1983 dst2->SetAcrossTrack(src2->IsAcrossTrack());
1984 dst2->SetAnimationIsBackwards(src2->AnimationIsBackwards());
1985
1986 auto entry = src2->GetEntry();
1987 if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE)
1988 {
1989 auto bannerIndex = src2->GetBannerIndex();
1990 if (bannerIndex != BANNER_INDEX_NULL)
1991 dst2->SetBannerIndex(bannerIndex);
1992 else
1993 dst2->SetBannerIndex(RCT12_BANNER_INDEX_NULL);
1994 }
1995
1996 break;
1997 }
1998 case TILE_ELEMENT_TYPE_LARGE_SCENERY:
1999 {
2000 auto dst2 = dst->AsLargeScenery();
2001 auto src2 = src->AsLargeScenery();
2002
2003 dst2->SetEntryIndex(src2->GetEntryIndex());
2004 dst2->SetSequenceIndex(src2->GetSequenceIndex());
2005 dst2->SetPrimaryColour(src2->GetPrimaryColour());
2006 dst2->SetSecondaryColour(src2->GetSecondaryColour());
2007
2008 auto entry = src2->GetEntry();
2009 if (entry != nullptr && entry->scrolling_mode != SCROLLING_MODE_NONE)
2010 {
2011 auto bannerIndex = src2->GetBannerIndex();
2012 if (bannerIndex != BANNER_INDEX_NULL)
2013 dst2->SetBannerIndex(bannerIndex);
2014 else
2015 dst2->SetBannerIndex(RCT12_BANNER_INDEX_NULL);
2016 }
2017
2018 break;
2019 }
2020 case TILE_ELEMENT_TYPE_BANNER:
2021 {
2022 auto dst2 = dst->AsBanner();
2023 auto src2 = src->AsBanner();
2024
2025 dst2->SetPosition(src2->GetPosition());
2026 dst2->SetAllowedEdges(src2->GetAllowedEdges());
2027 auto bannerIndex = src2->GetIndex();
2028 if (bannerIndex != BANNER_INDEX_NULL)
2029 dst2->SetIndex(bannerIndex);
2030 else
2031 dst2->SetIndex(RCT12_BANNER_INDEX_NULL);
2032 break;
2033 }
2034 default:
2035 assert(false);
2036 }
2037 }
2038
AllocateUserString(std::string_view value)2039 std::optional<uint16_t> S6Exporter::AllocateUserString(std::string_view value)
2040 {
2041 auto nextId = _userStrings.size();
2042 if (nextId < RCT12_MAX_USER_STRINGS)
2043 {
2044 _userStrings.emplace_back(value);
2045 return static_cast<uint16_t>(USER_STRING_START + nextId);
2046 }
2047 return std::nullopt;
2048 }
2049
ExportUserStrings()2050 void S6Exporter::ExportUserStrings()
2051 {
2052 auto numUserStrings = std::min<size_t>(_userStrings.size(), RCT12_MAX_USER_STRINGS);
2053 for (size_t i = 0; i < numUserStrings; i++)
2054 {
2055 auto dst = _s6.custom_strings[i];
2056 const auto& src = _userStrings[i];
2057 auto encodedSrc = GetTruncatedRCT2String(src, RCT12_USER_STRING_MAX_LENGTH);
2058 auto stringLen = std::min<size_t>(encodedSrc.size(), RCT12_USER_STRING_MAX_LENGTH - 1);
2059 std::memcpy(dst, encodedSrc.data(), stringLen);
2060 }
2061 }
2062
2063 enum : uint32_t
2064 {
2065 S6_SAVE_FLAG_EXPORT = 1 << 0,
2066 S6_SAVE_FLAG_SCENARIO = 1 << 1,
2067 S6_SAVE_FLAG_AUTOMATIC = 1u << 31,
2068 };
2069
2070 /**
2071 *
2072 * rct2: 0x006754F5
2073 * @param flags bit 0: pack objects, 1: save as scenario
2074 */
scenario_save(const utf8 * path,int32_t flags)2075 int32_t scenario_save(const utf8* path, int32_t flags)
2076 {
2077 if (flags & S6_SAVE_FLAG_SCENARIO)
2078 {
2079 log_verbose("scenario_save(%s, SCENARIO)", path);
2080 }
2081 else
2082 {
2083 log_verbose("scenario_save(%s, SAVED GAME)", path);
2084 }
2085
2086 if (!(flags & S6_SAVE_FLAG_AUTOMATIC))
2087 {
2088 window_close_construction_windows();
2089 }
2090
2091 viewport_set_saved_view();
2092
2093 bool result = false;
2094 auto s6exporter = new S6Exporter();
2095 try
2096 {
2097 if (flags & S6_SAVE_FLAG_EXPORT)
2098 {
2099 auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
2100 s6exporter->ExportObjectsList = objManager.GetPackableObjects();
2101 }
2102 s6exporter->RemoveTracklessRides = true;
2103 s6exporter->Export();
2104 if (flags & S6_SAVE_FLAG_SCENARIO)
2105 {
2106 s6exporter->SaveScenario(path);
2107 }
2108 else
2109 {
2110 s6exporter->SaveGame(path);
2111 }
2112 result = true;
2113 }
2114 catch (const std::exception& e)
2115 {
2116 log_error("Unable to save park: '%s'", e.what());
2117 }
2118 delete s6exporter;
2119
2120 gfx_invalidate_screen();
2121
2122 if (result && !(flags & S6_SAVE_FLAG_AUTOMATIC))
2123 {
2124 gScreenAge = 0;
2125 }
2126 return result;
2127 }
2128