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 "Game.h"
11
12 #include "Cheats.h"
13 #include "Context.h"
14 #include "Editor.h"
15 #include "FileClassifier.h"
16 #include "GameStateSnapshots.h"
17 #include "Input.h"
18 #include "OpenRCT2.h"
19 #include "ParkImporter.h"
20 #include "PlatformEnvironment.h"
21 #include "ReplayManager.h"
22 #include "actions/LoadOrQuitAction.h"
23 #include "audio/audio.h"
24 #include "config/Config.h"
25 #include "core/Console.hpp"
26 #include "core/FileScanner.h"
27 #include "core/Path.hpp"
28 #include "interface/Colour.h"
29 #include "interface/Screenshot.h"
30 #include "interface/Viewport.h"
31 #include "interface/Window.h"
32 #include "localisation/Localisation.h"
33 #include "management/Finance.h"
34 #include "management/Marketing.h"
35 #include "management/Research.h"
36 #include "network/network.h"
37 #include "object/Object.h"
38 #include "object/ObjectList.h"
39 #include "peep/Peep.h"
40 #include "peep/Staff.h"
41 #include "platform/Platform2.h"
42 #include "rct1/RCT1.h"
43 #include "ride/Ride.h"
44 #include "ride/RideRatings.h"
45 #include "ride/Station.h"
46 #include "ride/Track.h"
47 #include "ride/TrackDesign.h"
48 #include "ride/Vehicle.h"
49 #include "scenario/Scenario.h"
50 #include "scripting/ScriptEngine.h"
51 #include "title/TitleScreen.h"
52 #include "ui/UiContext.h"
53 #include "ui/WindowManager.h"
54 #include "util/SawyerCoding.h"
55 #include "util/Util.h"
56 #include "windows/Intent.h"
57 #include "world/Banner.h"
58 #include "world/Climate.h"
59 #include "world/Entrance.h"
60 #include "world/Footpath.h"
61 #include "world/Map.h"
62 #include "world/MapAnimation.h"
63 #include "world/Park.h"
64 #include "world/Scenery.h"
65 #include "world/Sprite.h"
66 #include "world/Surface.h"
67 #include "world/Water.h"
68
69 #include <algorithm>
70 #include <cstdio>
71 #include <iterator>
72 #include <memory>
73
74 uint16_t gCurrentDeltaTime;
75 uint8_t gGamePaused = 0;
76 int32_t gGameSpeed = 1;
77 bool gDoSingleUpdate = false;
78 float gDayNightCycle = 0;
79 bool gInUpdateCode = false;
80 bool gInMapInitCode = false;
81 std::string gCurrentLoadedPath;
82
83 bool gLoadKeepWindowsOpen = false;
84
85 uint32_t gCurrentTicks;
86 uint32_t gCurrentRealTimeTicks;
87
88 rct_string_id gGameCommandErrorTitle;
89 rct_string_id gGameCommandErrorText;
90
91 using namespace OpenRCT2;
92
game_reset_speed()93 void game_reset_speed()
94 {
95 gGameSpeed = 1;
96 window_invalidate_by_class(WC_TOP_TOOLBAR);
97 }
98
game_increase_game_speed()99 void game_increase_game_speed()
100 {
101 gGameSpeed = std::min(gConfigGeneral.debugging_tools ? 5 : 4, gGameSpeed + 1);
102 if (gGameSpeed == 5)
103 gGameSpeed = 8;
104 window_invalidate_by_class(WC_TOP_TOOLBAR);
105 }
106
game_reduce_game_speed()107 void game_reduce_game_speed()
108 {
109 gGameSpeed = std::max(1, gGameSpeed - 1);
110 if (gGameSpeed == 7)
111 gGameSpeed = 4;
112 window_invalidate_by_class(WC_TOP_TOOLBAR);
113 }
114
115 /**
116 *
117 * rct2: 0x0066B5C0 (part of 0x0066B3E8)
118 */
game_create_windows()119 void game_create_windows()
120 {
121 context_open_window(WC_MAIN_WINDOW);
122 context_open_window(WC_TOP_TOOLBAR);
123 context_open_window(WC_BOTTOM_TOOLBAR);
124 window_resize_gui(context_get_width(), context_get_height());
125 }
126
127 enum
128 {
129 SPR_GAME_PALETTE_DEFAULT = 1532,
130 SPR_GAME_PALETTE_WATER = 1533,
131 SPR_GAME_PALETTE_WATER_DARKER_1 = 1534,
132 SPR_GAME_PALETTE_WATER_DARKER_2 = 1535,
133 SPR_GAME_PALETTE_3 = 1536,
134 SPR_GAME_PALETTE_3_DARKER_1 = 1537,
135 SPR_GAME_PALETTE_3_DARKER_2 = 1538,
136 SPR_GAME_PALETTE_4 = 1539,
137 SPR_GAME_PALETTE_4_DARKER_1 = 1540,
138 SPR_GAME_PALETTE_4_DARKER_2 = 1541,
139 };
140
141 /**
142 *
143 * rct2: 0x006838BD
144 */
update_palette_effects()145 void update_palette_effects()
146 {
147 auto water_type = static_cast<rct_water_type*>(object_entry_get_chunk(ObjectType::Water, 0));
148
149 if (gClimateLightningFlash == 1)
150 {
151 // Change palette to lighter colour during lightning
152 int32_t palette = SPR_GAME_PALETTE_DEFAULT;
153
154 if (water_type != nullptr)
155 {
156 palette = water_type->image_id;
157 }
158 const rct_g1_element* g1 = gfx_get_g1_element(palette);
159 if (g1 != nullptr)
160 {
161 int32_t xoffset = g1->x_offset;
162 xoffset = xoffset * 4;
163 uint8_t* paletteOffset = gGamePalette + xoffset;
164 for (int32_t i = 0; i < g1->width; i++)
165 {
166 paletteOffset[(i * 4) + 0] = -((0xFF - g1->offset[(i * 3) + 0]) / 2) - 1;
167 paletteOffset[(i * 4) + 1] = -((0xFF - g1->offset[(i * 3) + 1]) / 2) - 1;
168 paletteOffset[(i * 4) + 2] = -((0xFF - g1->offset[(i * 3) + 2]) / 2) - 1;
169 }
170 platform_update_palette(gGamePalette, PALETTE_OFFSET_DYNAMIC, PALETTE_LENGTH_DYNAMIC);
171 }
172 gClimateLightningFlash++;
173 }
174 else
175 {
176 if (gClimateLightningFlash == 2)
177 {
178 // Change palette back to normal after lightning
179 int32_t palette = SPR_GAME_PALETTE_DEFAULT;
180
181 if (water_type != nullptr)
182 {
183 palette = water_type->image_id;
184 }
185
186 const rct_g1_element* g1 = gfx_get_g1_element(palette);
187 if (g1 != nullptr)
188 {
189 int32_t xoffset = g1->x_offset;
190 xoffset = xoffset * 4;
191 uint8_t* paletteOffset = gGamePalette + xoffset;
192 for (int32_t i = 0; i < g1->width; i++)
193 {
194 paletteOffset[(i * 4) + 0] = g1->offset[(i * 3) + 0];
195 paletteOffset[(i * 4) + 1] = g1->offset[(i * 3) + 1];
196 paletteOffset[(i * 4) + 2] = g1->offset[(i * 3) + 2];
197 }
198 }
199 }
200
201 // Animate the water/lava/chain movement palette
202 uint32_t shade = 0;
203 if (gConfigGeneral.render_weather_gloom)
204 {
205 auto paletteId = climate_get_weather_gloom_palette_id(gClimateCurrent);
206 if (paletteId != FilterPaletteID::PaletteNull)
207 {
208 shade = 1;
209 if (paletteId != FilterPaletteID::PaletteDarken1)
210 {
211 shade = 2;
212 }
213 }
214 }
215 uint32_t j = gPaletteEffectFrame;
216 j = ((static_cast<uint16_t>((~j / 2) * 128) * 15) >> 16);
217 uint32_t waterId = SPR_GAME_PALETTE_WATER;
218 if (water_type != nullptr)
219 {
220 waterId = water_type->palette_index_1;
221 }
222 const rct_g1_element* g1 = gfx_get_g1_element(shade + waterId);
223 if (g1 != nullptr)
224 {
225 uint8_t* vs = &g1->offset[j * 3];
226 uint8_t* vd = &gGamePalette[PALETTE_OFFSET_WATER_WAVES * 4];
227 int32_t n = PALETTE_LENGTH_WATER_WAVES;
228 for (int32_t i = 0; i < n; i++)
229 {
230 vd[0] = vs[0];
231 vd[1] = vs[1];
232 vd[2] = vs[2];
233 vs += 9;
234 if (vs >= &g1->offset[9 * n])
235 {
236 vs -= 9 * n;
237 }
238 vd += 4;
239 }
240 }
241
242 waterId = SPR_GAME_PALETTE_3;
243 if (water_type != nullptr)
244 {
245 waterId = water_type->palette_index_2;
246 }
247 g1 = gfx_get_g1_element(shade + waterId);
248 if (g1 != nullptr)
249 {
250 uint8_t* vs = &g1->offset[j * 3];
251 uint8_t* vd = &gGamePalette[PALETTE_OFFSET_WATER_SPARKLES * 4];
252 int32_t n = PALETTE_LENGTH_WATER_SPARKLES;
253 for (int32_t i = 0; i < n; i++)
254 {
255 vd[0] = vs[0];
256 vd[1] = vs[1];
257 vd[2] = vs[2];
258 vs += 9;
259 if (vs >= &g1->offset[9 * n])
260 {
261 vs -= 9 * n;
262 }
263 vd += 4;
264 }
265 }
266
267 j = (static_cast<uint16_t>(gPaletteEffectFrame * -960) * 3) >> 16;
268 waterId = SPR_GAME_PALETTE_4;
269 g1 = gfx_get_g1_element(shade + waterId);
270 if (g1 != nullptr)
271 {
272 uint8_t* vs = &g1->offset[j * 3];
273 uint8_t* vd = &gGamePalette[PALETTE_INDEX_243 * 4];
274 int32_t n = 3;
275 for (int32_t i = 0; i < n; i++)
276 {
277 vd[0] = vs[0];
278 vd[1] = vs[1];
279 vd[2] = vs[2];
280 vs += 3;
281 if (vs >= &g1->offset[3 * n])
282 {
283 vs -= 3 * n;
284 }
285 vd += 4;
286 }
287 }
288
289 platform_update_palette(gGamePalette, PALETTE_OFFSET_ANIMATED, PALETTE_LENGTH_ANIMATED);
290 if (gClimateLightningFlash == 2)
291 {
292 platform_update_palette(gGamePalette, PALETTE_OFFSET_DYNAMIC, PALETTE_LENGTH_DYNAMIC);
293 gClimateLightningFlash = 0;
294 }
295 }
296 }
297
pause_toggle()298 void pause_toggle()
299 {
300 gGamePaused ^= GAME_PAUSED_NORMAL;
301 window_invalidate_by_class(WC_TOP_TOOLBAR);
302 if (gGamePaused & GAME_PAUSED_NORMAL)
303 {
304 OpenRCT2::Audio::StopAll();
305 }
306 }
307
game_is_paused()308 bool game_is_paused()
309 {
310 return gGamePaused != 0;
311 }
312
game_is_not_paused()313 bool game_is_not_paused()
314 {
315 return gGamePaused == 0;
316 }
317
318 /**
319 *
320 * rct2: 0x0066DC0F
321 */
load_landscape()322 static void load_landscape()
323 {
324 auto intent = Intent(WC_LOADSAVE);
325 intent.putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_LANDSCAPE);
326 context_open_intent(&intent);
327 }
328
utf8_to_rct2_self(char * buffer,size_t length)329 void utf8_to_rct2_self(char* buffer, size_t length)
330 {
331 auto temp = utf8_to_rct2(buffer);
332
333 size_t i = 0;
334 const char* src = temp.data();
335 char* dst = buffer;
336 while (*src != 0 && i < length - 1)
337 {
338 if (*src == static_cast<char>(static_cast<uint8_t>(0xFF)))
339 {
340 if (i < length - 3)
341 {
342 *dst++ = *src++;
343 *dst++ = *src++;
344 *dst++ = *src++;
345 }
346 else
347 {
348 break;
349 }
350 i += 3;
351 }
352 else
353 {
354 *dst++ = *src++;
355 i++;
356 }
357 }
358 do
359 {
360 *dst++ = '\0';
361 i++;
362 } while (i < length);
363 }
364
rct2_to_utf8_self(char * buffer,size_t length)365 void rct2_to_utf8_self(char* buffer, size_t length)
366 {
367 if (length > 0)
368 {
369 auto temp = rct2_to_utf8(buffer, RCT2LanguageId::EnglishUK);
370 safe_strcpy(buffer, temp.data(), length);
371 }
372 }
373
374 /**
375 * Converts all the user strings and news item strings to UTF-8.
376 */
game_convert_strings_to_utf8()377 void game_convert_strings_to_utf8()
378 {
379 // Scenario details
380 gScenarioCompletedBy = rct2_to_utf8(gScenarioCompletedBy, RCT2LanguageId::EnglishUK);
381 gScenarioName = rct2_to_utf8(gScenarioName, RCT2LanguageId::EnglishUK);
382 gScenarioDetails = rct2_to_utf8(gScenarioDetails, RCT2LanguageId::EnglishUK);
383 }
384
385 /**
386 * Converts all the user strings and news item strings to RCT2 encoding.
387 */
game_convert_strings_to_rct2(rct_s6_data * s6)388 void game_convert_strings_to_rct2(rct_s6_data* s6)
389 {
390 // Scenario details
391 utf8_to_rct2_self(s6->scenario_completed_name, sizeof(s6->scenario_completed_name));
392 utf8_to_rct2_self(s6->scenario_name, sizeof(s6->scenario_name));
393 utf8_to_rct2_self(s6->scenario_description, sizeof(s6->scenario_description));
394
395 // User strings
396 for (auto* userString : s6->custom_strings)
397 {
398 if (!str_is_null_or_empty(userString))
399 {
400 utf8_to_rct2_self(userString, RCT12_USER_STRING_MAX_LENGTH);
401 }
402 }
403 }
404
405 // OpenRCT2 workaround to recalculate some values which are saved redundantly in the save to fix corrupted files.
406 // For example recalculate guest count by looking at all the guests instead of trusting the value in the file.
game_fix_save_vars()407 void game_fix_save_vars()
408 {
409 // Recalculates peep count after loading a save to fix corrupted files
410 uint32_t guestCount = 0;
411 {
412 for (auto guest : EntityList<Guest>())
413 {
414 if (!guest->OutsideOfPark)
415 {
416 guestCount++;
417 }
418 }
419 }
420
421 gNumGuestsInPark = guestCount;
422
423 // Peeps to remove have to be cached here, as removing them from within the loop breaks iteration
424 std::vector<Peep*> peepsToRemove;
425
426 // Fix possibly invalid field values
427 for (auto peep : EntityList<Guest>())
428 {
429 if (peep->CurrentRideStation >= MAX_STATIONS)
430 {
431 const auto srcStation = peep->CurrentRideStation;
432 const auto rideIdx = peep->CurrentRide;
433 if (rideIdx == RIDE_ID_NULL)
434 {
435 continue;
436 }
437 Ride* ride = get_ride(rideIdx);
438 if (ride == nullptr)
439 {
440 log_warning("Couldn't find ride %u, resetting ride on peep %u", rideIdx, peep->sprite_index);
441 peep->CurrentRide = RIDE_ID_NULL;
442 continue;
443 }
444 auto curName = peep->GetName();
445 log_warning(
446 "Peep %u (%s) has invalid ride station = %u for ride %u.", peep->sprite_index, curName.c_str(), srcStation,
447 rideIdx);
448 auto station = ride_get_first_valid_station_exit(ride);
449 if (station == STATION_INDEX_NULL)
450 {
451 log_warning("Couldn't find station, removing peep %u", peep->sprite_index);
452 peepsToRemove.push_back(peep);
453 }
454 else
455 {
456 log_warning("Amending ride station to %u.", station);
457 peep->CurrentRideStation = station;
458 }
459 }
460 }
461
462 if (!peepsToRemove.empty())
463 {
464 // Some broken saves have broken spatial indexes
465 reset_sprite_spatial_index();
466 }
467
468 for (auto ptr : peepsToRemove)
469 {
470 ptr->Remove();
471 }
472
473 // Fixes broken saves where a surface element could be null
474 // and broken saves with incorrect invisible map border tiles
475 for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
476 {
477 for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
478 {
479 auto* surfaceElement = map_get_surface_element_at(TileCoordsXY{ x, y }.ToCoordsXY());
480
481 if (surfaceElement == nullptr)
482 {
483 log_error("Null map element at x = %d and y = %d. Fixing...", x, y);
484 surfaceElement = TileElementInsert<SurfaceElement>(TileCoordsXYZ{ x, y, 14 }.ToCoordsXYZ(), 0b0000);
485 if (surfaceElement == nullptr)
486 {
487 log_error("Unable to fix: Map element limit reached.");
488 return;
489 }
490 }
491
492 // Fix the invisible border tiles.
493 // At this point, we can be sure that surfaceElement is not NULL.
494 if (x == 0 || x == gMapSize - 1 || y == 0 || y == gMapSize - 1)
495 {
496 surfaceElement->SetBaseZ(MINIMUM_LAND_HEIGHT_BIG);
497 surfaceElement->SetClearanceZ(MINIMUM_LAND_HEIGHT_BIG);
498 surfaceElement->SetSlope(0);
499 surfaceElement->SetWaterHeight(0);
500 }
501 }
502 }
503
504 research_fix();
505
506 // Fix banner list pointing to NULL map elements
507 banner_reset_broken_index();
508
509 // Fix banners which share their index
510 fix_duplicated_banners();
511
512 // Fix invalid vehicle sprite sizes, thus preventing visual corruption of sprites
513 fix_invalid_vehicle_sprite_sizes();
514
515 // Fix gParkEntrance locations for which the tile_element no longer exists
516 fix_park_entrance_locations();
517 }
518
game_load_init()519 void game_load_init()
520 {
521 IGameStateSnapshots* snapshots = GetContext()->GetGameStateSnapshots();
522 snapshots->Reset();
523
524 gScreenFlags = SCREEN_FLAGS_PLAYING;
525 OpenRCT2::Audio::StopAll();
526 if (!gLoadKeepWindowsOpen)
527 {
528 viewport_init_all();
529 game_create_windows();
530 }
531 else
532 {
533 auto* mainWindow = window_get_main();
534 window_unfollow_sprite(mainWindow);
535 }
536
537 auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
538 windowManager->SetMainView(gSavedView, gSavedViewZoom, gSavedViewRotation);
539
540 if (network_get_mode() != NETWORK_MODE_CLIENT)
541 {
542 GameActions::ClearQueue();
543 }
544 reset_sprite_spatial_index();
545 reset_all_sprite_quadrant_placements();
546 scenery_set_default_placement_configuration();
547
548 auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES);
549 context_broadcast_intent(&intent);
550
551 gWindowUpdateTicks = 0;
552
553 load_palette();
554
555 if (!gOpenRCT2Headless)
556 {
557 intent = Intent(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD);
558 context_broadcast_intent(&intent);
559 window_update_all();
560 }
561
562 OpenRCT2::Audio::StopTitleMusic();
563 gGameSpeed = 1;
564 }
565
game_load_scripts()566 void game_load_scripts()
567 {
568 #ifdef ENABLE_SCRIPTING
569 GetContext()->GetScriptEngine().LoadPlugins();
570 #endif
571 }
572
game_unload_scripts()573 void game_unload_scripts()
574 {
575 #ifdef ENABLE_SCRIPTING
576 GetContext()->GetScriptEngine().UnloadPlugins();
577 #endif
578 }
579
580 /**
581 *
582 * rct2: 0x0069E9A7
583 * Call after a rotation or loading of a save to reset sprite quadrants
584 */
reset_all_sprite_quadrant_placements()585 void reset_all_sprite_quadrant_placements()
586 {
587 for (size_t i = 0; i < MAX_ENTITIES; i++)
588 {
589 auto* spr = GetEntity(i);
590 if (spr != nullptr && spr->Type != EntityType::Null)
591 {
592 spr->MoveTo(spr->GetLocation());
593 }
594 }
595 }
596
save_game()597 void save_game()
598 {
599 if (!gFirstTimeSaving)
600 {
601 save_game_with_name(gScenarioSavePath.c_str());
602 }
603 else
604 {
605 save_game_as();
606 }
607 }
608
save_game_cmd(const utf8 * name)609 void save_game_cmd(const utf8* name /* = nullptr */)
610 {
611 if (name == nullptr)
612 {
613 save_game_with_name(gScenarioSavePath.c_str());
614 }
615 else
616 {
617 char savePath[MAX_PATH];
618 platform_get_user_directory(savePath, "save", sizeof(savePath));
619 safe_strcat_path(savePath, name, sizeof(savePath));
620 path_append_extension(savePath, ".sv6", sizeof(savePath));
621 save_game_with_name(savePath);
622 }
623 }
624
save_game_with_name(const utf8 * name)625 void save_game_with_name(const utf8* name)
626 {
627 log_verbose("Saving to %s", name);
628 if (scenario_save(name, 0x80000000 | (gConfigGeneral.save_plugin_data ? 1 : 0)))
629 {
630 log_verbose("Saved to %s", name);
631 gCurrentLoadedPath = name;
632 gScreenAge = 0;
633 }
634 }
635
create_save_game_as_intent()636 void* create_save_game_as_intent()
637 {
638 char name[MAX_PATH];
639 safe_strcpy(name, path_get_filename(gScenarioSavePath.c_str()), MAX_PATH);
640 path_remove_extension(name);
641
642 Intent* intent = new Intent(WC_LOADSAVE);
643 intent->putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_SAVE | LOADSAVETYPE_GAME);
644 intent->putExtra(INTENT_EXTRA_PATH, std::string{ name });
645
646 return intent;
647 }
648
save_game_as()649 void save_game_as()
650 {
651 auto* intent = static_cast<Intent*>(create_save_game_as_intent());
652 context_open_intent(intent);
653 delete intent;
654 }
655
limit_autosave_count(const size_t numberOfFilesToKeep,bool processLandscapeFolder)656 static void limit_autosave_count(const size_t numberOfFilesToKeep, bool processLandscapeFolder)
657 {
658 size_t autosavesCount = 0;
659 size_t numAutosavesToDelete = 0;
660
661 auto environment = GetContext()->GetPlatformEnvironment();
662 auto folderDirectory = environment->GetDirectoryPath(DIRBASE::USER, DIRID::SAVE);
663 char const* fileFilter = "autosave_*.sv6";
664 if (processLandscapeFolder)
665 {
666 folderDirectory = environment->GetDirectoryPath(DIRBASE::USER, DIRID::LANDSCAPE);
667 fileFilter = "autosave_*.sc6";
668 }
669
670 utf8 filter[MAX_PATH];
671 safe_strcpy(filter, folderDirectory.c_str(), sizeof(filter));
672 safe_strcat_path(filter, "autosave", sizeof(filter));
673 safe_strcat_path(filter, fileFilter, sizeof(filter));
674
675 // At first, count how many autosaves there are
676 {
677 auto scanner = Path::ScanDirectory(filter, false);
678 while (scanner->Next())
679 {
680 autosavesCount++;
681 }
682 }
683
684 // If there are fewer autosaves than the number of files to keep we don't need to delete anything
685 if (autosavesCount <= numberOfFilesToKeep)
686 {
687 return;
688 }
689
690 auto autosaveFiles = std::vector<std::string>(autosavesCount);
691 {
692 auto scanner = Path::ScanDirectory(filter, false);
693 for (size_t i = 0; i < autosavesCount; i++)
694 {
695 autosaveFiles[i].resize(MAX_PATH, 0);
696 if (scanner->Next())
697 {
698 safe_strcpy(autosaveFiles[i].data(), folderDirectory.c_str(), sizeof(utf8) * MAX_PATH);
699 safe_strcat_path(autosaveFiles[i].data(), "autosave", sizeof(utf8) * MAX_PATH);
700 safe_strcat_path(autosaveFiles[i].data(), scanner->GetPathRelative(), sizeof(utf8) * MAX_PATH);
701 }
702 }
703 }
704
705 std::sort(autosaveFiles.begin(), autosaveFiles.end(), [](const auto& saveFile0, const auto& saveFile1) {
706 return saveFile0.compare(saveFile1) < 0;
707 });
708
709 // Calculate how many saves we need to delete.
710 numAutosavesToDelete = autosavesCount - numberOfFilesToKeep;
711
712 for (size_t i = 0; numAutosavesToDelete > 0; i++, numAutosavesToDelete--)
713 {
714 if (!platform_file_delete(autosaveFiles[i].data()))
715 {
716 log_warning("Failed to delete autosave file: %s", autosaveFiles[i].data());
717 }
718 }
719 }
720
game_autosave()721 void game_autosave()
722 {
723 const char* subDirectory = "save";
724 const char* fileExtension = ".sv6";
725 uint32_t saveFlags = 0x80000000;
726 if (gScreenFlags & SCREEN_FLAGS_EDITOR)
727 {
728 subDirectory = "landscape";
729 fileExtension = ".sc6";
730 saveFlags |= 2;
731 }
732
733 // Retrieve current time
734 auto currentDate = Platform::GetDateLocal();
735 auto currentTime = Platform::GetTimeLocal();
736
737 utf8 timeName[44];
738 snprintf(
739 timeName, sizeof(timeName), "autosave_%04u-%02u-%02u_%02u-%02u-%02u%s", currentDate.year, currentDate.month,
740 currentDate.day, currentTime.hour, currentTime.minute, currentTime.second, fileExtension);
741
742 int32_t autosavesToKeep = gConfigGeneral.autosave_amount;
743 limit_autosave_count(autosavesToKeep - 1, (gScreenFlags & SCREEN_FLAGS_EDITOR));
744
745 utf8 path[MAX_PATH];
746 utf8 backupPath[MAX_PATH];
747 platform_get_user_directory(path, subDirectory, sizeof(path));
748 safe_strcat_path(path, "autosave", sizeof(path));
749 platform_ensure_directory_exists(path);
750 safe_strcpy(backupPath, path, sizeof(backupPath));
751 safe_strcat_path(path, timeName, sizeof(path));
752 safe_strcat_path(backupPath, "autosave", sizeof(backupPath));
753 safe_strcat(backupPath, fileExtension, sizeof(backupPath));
754 safe_strcat(backupPath, ".bak", sizeof(backupPath));
755
756 if (Platform::FileExists(path))
757 {
758 platform_file_copy(path, backupPath, true);
759 }
760
761 if (!scenario_save(path, saveFlags))
762 Console::Error::WriteLine("Could not autosave the scenario. Is the save folder writeable?");
763 }
764
game_load_or_quit_no_save_prompt_callback(int32_t result,const utf8 * path)765 static void game_load_or_quit_no_save_prompt_callback(int32_t result, const utf8* path)
766 {
767 if (result == MODAL_RESULT_OK)
768 {
769 game_unload_scripts();
770 window_close_by_class(WC_EDITOR_OBJECT_SELECTION);
771 context_load_park_from_file(path);
772 game_load_scripts();
773 }
774 }
775
776 /**
777 *
778 * rct2: 0x0066DB79
779 */
game_load_or_quit_no_save_prompt()780 void game_load_or_quit_no_save_prompt()
781 {
782 switch (gSavePromptMode)
783 {
784 case PromptMode::SaveBeforeLoad:
785 {
786 auto loadOrQuitAction = LoadOrQuitAction(LoadOrQuitModes::CloseSavePrompt);
787 GameActions::Execute(&loadOrQuitAction);
788 tool_cancel();
789 if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
790 {
791 load_landscape();
792 }
793 else
794 {
795 auto intent = Intent(WC_LOADSAVE);
796 intent.putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_GAME);
797 intent.putExtra(INTENT_EXTRA_CALLBACK, reinterpret_cast<void*>(game_load_or_quit_no_save_prompt_callback));
798 context_open_intent(&intent);
799 }
800 break;
801 }
802 case PromptMode::SaveBeforeQuit:
803 {
804 auto loadOrQuitAction = LoadOrQuitAction(LoadOrQuitModes::CloseSavePrompt);
805 GameActions::Execute(&loadOrQuitAction);
806 tool_cancel();
807 if (input_test_flag(INPUT_FLAG_5))
808 {
809 input_set_flag(INPUT_FLAG_5, false);
810 }
811 gGameSpeed = 1;
812 gFirstTimeSaving = true;
813 game_unload_scripts();
814 title_load();
815 break;
816 }
817 default:
818 game_unload_scripts();
819 openrct2_finish();
820 break;
821 }
822 }
823
start_silent_record()824 void start_silent_record()
825 {
826 std::string name = Path::Combine(
827 OpenRCT2::GetContext()->GetPlatformEnvironment()->GetDirectoryPath(OpenRCT2::DIRBASE::USER), "debug_replay.sv6r");
828 auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
829 if (replayManager->StartRecording(name, OpenRCT2::k_MaxReplayTicks, OpenRCT2::IReplayManager::RecordType::SILENT))
830 {
831 OpenRCT2::ReplayRecordInfo info;
832 replayManager->GetCurrentReplayInfo(info);
833 safe_strcpy(gSilentRecordingName, info.FilePath.c_str(), MAX_PATH);
834
835 const char* logFmt = "Silent replay recording started: (%s) %s\n";
836 Console::WriteLine(logFmt, info.Name.c_str(), info.FilePath.c_str());
837 }
838 }
839
stop_silent_record()840 bool stop_silent_record()
841 {
842 auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
843 if (!replayManager->IsRecording() && !replayManager->IsNormalising())
844 {
845 return false;
846 }
847
848 OpenRCT2::ReplayRecordInfo info;
849 replayManager->GetCurrentReplayInfo(info);
850
851 if (replayManager->StopRecording())
852 {
853 const char* logFmt = "Replay recording stopped: (%s) %s\n"
854 " Ticks: %u\n"
855 " Commands: %u\n"
856 " Checksums: %u";
857
858 Console::WriteLine(logFmt, info.Name.c_str(), info.FilePath.c_str(), info.Ticks, info.NumCommands, info.NumChecksums);
859
860 return true;
861 }
862
863 return false;
864 }
865