OpenRCT2/src/openrct2/Game.cpp

922 lines
26 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
2015-10-09 18:22:37 +02:00
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
2015-10-09 18:22:37 +02:00
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
2015-10-09 18:22:37 +02:00
2018-06-22 23:25:16 +02:00
#include "Game.h"
2017-12-13 13:02:24 +01:00
#include "Cheats.h"
#include "Context.h"
#include "Editor.h"
#include "FileClassifier.h"
#include "GameState.h"
#include "GameStateSnapshots.h"
2017-12-12 14:52:57 +01:00
#include "Input.h"
2018-06-22 23:25:16 +02:00
#include "OpenRCT2.h"
#include "ParkImporter.h"
2020-03-31 23:12:35 +02:00
#include "PlatformEnvironment.h"
#include "ReplayManager.h"
#include "actions/GameSetSpeedAction.h"
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include "actions/LoadOrQuitAction.h"
2018-06-22 23:25:16 +02:00
#include "audio/audio.h"
#include "config/Config.h"
2021-02-13 03:50:29 +01:00
#include "core/Console.hpp"
2022-01-08 13:57:29 +01:00
#include "core/File.h"
2018-06-22 23:25:16 +02:00
#include "core/FileScanner.h"
2020-03-31 23:12:35 +02:00
#include "core/Path.hpp"
2021-11-24 15:58:01 +01:00
#include "entity/EntityRegistry.h"
2022-03-08 01:14:52 +01:00
#include "entity/PatrolArea.h"
2021-11-25 22:47:24 +01:00
#include "entity/Peep.h"
#include "entity/Staff.h"
2020-05-28 23:37:50 +02:00
#include "interface/Colour.h"
2017-06-09 00:02:39 +02:00
#include "interface/Screenshot.h"
2018-01-06 00:45:53 +01:00
#include "interface/Viewport.h"
2018-01-06 01:05:16 +01:00
#include "interface/Window.h"
2018-01-06 18:32:25 +01:00
#include "localisation/Localisation.h"
2017-10-06 22:37:06 +02:00
#include "management/Finance.h"
2017-10-11 21:38:26 +02:00
#include "management/Marketing.h"
#include "management/Research.h"
2015-02-12 12:30:57 +01:00
#include "network/network.h"
2018-01-02 20:36:42 +01:00
#include "object/Object.h"
#include "object/ObjectEntryManager.h"
2018-06-22 23:25:16 +02:00
#include "object/ObjectList.h"
#include "object/WaterEntry.h"
#include "platform/Platform.h"
2017-12-31 13:21:34 +01:00
#include "ride/Ride.h"
2018-01-18 23:33:06 +01:00
#include "ride/RideRatings.h"
#include "ride/Station.h"
2017-10-17 13:51:47 +02:00
#include "ride/Track.h"
#include "ride/TrackDesign.h"
2017-10-11 23:32:15 +02:00
#include "ride/Vehicle.h"
2018-01-02 18:58:43 +01:00
#include "scenario/Scenario.h"
#include "scenes/title/TitleScene.h"
2020-02-07 00:15:20 +01:00
#include "scripting/ScriptEngine.h"
2018-06-02 01:07:14 +02:00
#include "ui/UiContext.h"
#include "ui/WindowManager.h"
2017-12-13 13:02:24 +01:00
#include "util/SawyerCoding.h"
#include "util/Util.h"
#include "windows/Intent.h"
2017-12-14 10:34:12 +01:00
#include "world/Banner.h"
2017-03-11 12:24:18 +01:00
#include "world/Climate.h"
2017-12-14 10:34:12 +01:00
#include "world/Entrance.h"
2018-01-11 10:59:26 +01:00
#include "world/Footpath.h"
2018-01-03 14:56:08 +01:00
#include "world/Map.h"
2018-01-11 10:59:26 +01:00
#include "world/MapAnimation.h"
2017-12-31 21:40:00 +01:00
#include "world/Park.h"
2018-01-11 10:59:26 +01:00
#include "world/Scenery.h"
2018-05-01 16:33:16 +02:00
#include "world/Surface.h"
2018-06-22 23:25:16 +02:00
#include <algorithm>
#include <cstdio>
2018-11-21 23:16:04 +01:00
#include <iterator>
2018-06-22 23:25:16 +02:00
#include <memory>
2016-01-04 16:22:15 +01:00
using namespace OpenRCT2;
uint16_t gCurrentDeltaTime;
2018-06-22 23:25:16 +02:00
uint8_t gGamePaused = 0;
int32_t gGameSpeed = 1;
bool gDoSingleUpdate = false;
2018-06-22 23:25:16 +02:00
float gDayNightCycle = 0;
bool gInUpdateCode = false;
bool gInMapInitCode = false;
std::string gCurrentLoadedPath;
bool gIsAutosave = false;
bool gIsAutosaveLoaded = false;
Feature: Preview title sequences in-game Title sequences can now be played back in-game, allowing for much easier editing. Improved title sequence playback in general. Clicking play while on a different title sequence will play the new one. Clicking stop will make the title screen go back to the config title sequence. And the closing the title sequence window will also make the game go back to the config title sequence, and reload the sequence if it was modified. Changes made to title sequences in-game are now correctly loaded in the title screen. Starting a title sequence within the editor will now always reset it even if it's the current playing sequence. (Not for playing in the editor though). Get Location in title sequence command editor now has 100% accuracy compared to before where it would usually get some offset value. Added `get_map_coordinates_from_pos_window` which will allow getting the viewport coordinates of a specific window even if the input coordinates are under another window. This has use with getting 2D positions from the main window without the other windows getting in the way. Options window will now always specify the config title sequence in the dropdown and not the current title sequence. Made a global variable `gLoadKeepWindowsOpen`, in game.h to keep windows open when loading a park. When loading a title sequence park in-game. The sequence player will force-close all park-specific windows ahead of time. Skipping while testing title sequences no longer needs to reload the park if the current playback position is already before the target position and ahead of the load position. Added changelog entry.
2017-10-30 12:07:01 +01:00
bool gLoadKeepWindowsOpen = false;
uint32_t gCurrentRealTimeTicks;
#ifdef ENABLE_SCRIPTING
static bool _mapChangedExpected;
#endif
using namespace OpenRCT2;
void GameResetSpeed()
{
auto setSpeedAction = GameSetSpeedAction(1);
GameActions::Execute(&setSpeedAction);
}
void GameIncreaseGameSpeed()
{
auto newSpeed = std::min(gConfigGeneral.DebuggingTools ? 5 : 4, gGameSpeed + 1);
if (newSpeed == 5)
newSpeed = 8;
auto setSpeedAction = GameSetSpeedAction(newSpeed);
GameActions::Execute(&setSpeedAction);
}
void GameReduceGameSpeed()
{
auto newSpeed = std::max(1, gGameSpeed - 1);
if (newSpeed == 7)
newSpeed = 4;
auto setSpeedAction = GameSetSpeedAction(newSpeed);
GameActions::Execute(&setSpeedAction);
}
2014-04-11 03:42:39 +02:00
/**
2015-10-09 18:22:37 +02:00
*
2014-04-11 03:42:39 +02:00
* rct2: 0x0066B5C0 (part of 0x0066B3E8)
*/
void GameCreateWindows()
2014-04-11 03:42:39 +02:00
{
2022-11-06 21:49:07 +01:00
ContextOpenWindow(WindowClass::MainWindow);
ContextOpenWindow(WindowClass::TopToolbar);
ContextOpenWindow(WindowClass::BottomToolbar);
WindowResizeGui(ContextGetWidth(), ContextGetHeight());
2014-04-11 03:42:39 +02:00
}
enum
{
2018-06-22 23:25:16 +02:00
SPR_GAME_PALETTE_DEFAULT = 1532,
SPR_GAME_PALETTE_WATER = 1533,
SPR_GAME_PALETTE_WATER_DARKER_1 = 1534,
SPR_GAME_PALETTE_WATER_DARKER_2 = 1535,
2018-06-22 23:25:16 +02:00
SPR_GAME_PALETTE_3 = 1536,
SPR_GAME_PALETTE_3_DARKER_1 = 1537,
SPR_GAME_PALETTE_3_DARKER_2 = 1538,
SPR_GAME_PALETTE_4 = 1539,
SPR_GAME_PALETTE_4_DARKER_1 = 1540,
SPR_GAME_PALETTE_4_DARKER_2 = 1541,
2016-11-10 13:21:47 +01:00
};
/**
2018-06-22 23:25:16 +02:00
*
* rct2: 0x006838BD
*/
void UpdatePaletteEffects()
{
auto water_type = OpenRCT2::ObjectManager::GetObjectEntry<WaterObjectEntry>(0);
if (gClimateLightningFlash == 1)
{
// Change palette to lighter colour during lightning
int32_t palette = SPR_GAME_PALETTE_DEFAULT;
2017-11-30 18:21:12 +01:00
if (water_type != nullptr)
{
palette = water_type->image_id;
}
const G1Element* g1 = GfxGetG1Element(palette);
2017-11-30 18:21:12 +01:00
if (g1 != nullptr)
2017-10-26 14:14:37 +02:00
{
int32_t xoffset = g1->x_offset;
2017-10-26 14:14:37 +02:00
xoffset = xoffset * 4;
2018-06-22 23:25:16 +02:00
uint8_t* paletteOffset = gGamePalette + xoffset;
for (int32_t i = 0; i < g1->width; i++)
2017-10-26 14:14:37 +02:00
{
paletteOffset[(i * 4) + 0] = -((0xFF - g1->offset[(i * 3) + 0]) / 2) - 1;
paletteOffset[(i * 4) + 1] = -((0xFF - g1->offset[(i * 3) + 1]) / 2) - 1;
paletteOffset[(i * 4) + 2] = -((0xFF - g1->offset[(i * 3) + 2]) / 2) - 1;
}
UpdatePalette(gGamePalette, PALETTE_OFFSET_DYNAMIC, PALETTE_LENGTH_DYNAMIC);
}
gClimateLightningFlash++;
}
else
{
if (gClimateLightningFlash == 2)
{
// Change palette back to normal after lightning
int32_t palette = SPR_GAME_PALETTE_DEFAULT;
2017-11-30 18:21:12 +01:00
if (water_type != nullptr)
{
palette = water_type->image_id;
}
const G1Element* g1 = GfxGetG1Element(palette);
2017-11-30 18:21:12 +01:00
if (g1 != nullptr)
2017-10-26 14:14:37 +02:00
{
int32_t xoffset = g1->x_offset;
2017-10-26 14:14:37 +02:00
xoffset = xoffset * 4;
2018-06-22 23:25:16 +02:00
uint8_t* paletteOffset = gGamePalette + xoffset;
for (int32_t i = 0; i < g1->width; i++)
2017-10-26 14:14:37 +02:00
{
paletteOffset[(i * 4) + 0] = g1->offset[(i * 3) + 0];
paletteOffset[(i * 4) + 1] = g1->offset[(i * 3) + 1];
paletteOffset[(i * 4) + 2] = g1->offset[(i * 3) + 2];
}
}
}
// Animate the water/lava/chain movement palette
uint32_t shade = 0;
if (gConfigGeneral.RenderWeatherGloom)
{
2024-01-24 11:18:54 +01:00
auto paletteId = ClimateGetWeatherGloomPaletteId(GetGameState().ClimateCurrent);
if (paletteId != FilterPaletteID::PaletteNull)
{
shade = 1;
if (paletteId != FilterPaletteID::PaletteDarken1)
{
shade = 2;
}
}
}
uint32_t j = gPaletteEffectFrame;
j = ((static_cast<uint16_t>((~j / 2) * 128) * 15) >> 16);
uint32_t waterId = SPR_GAME_PALETTE_WATER;
2017-11-30 18:21:12 +01:00
if (water_type != nullptr)
{
waterId = water_type->palette_index_1;
}
const G1Element* g1 = GfxGetG1Element(shade + waterId);
2017-11-30 18:21:12 +01:00
if (g1 != nullptr)
2017-10-26 14:14:37 +02:00
{
2018-06-22 23:25:16 +02:00
uint8_t* vs = &g1->offset[j * 3];
2020-05-28 23:37:50 +02:00
uint8_t* vd = &gGamePalette[PALETTE_OFFSET_WATER_WAVES * 4];
int32_t n = PALETTE_LENGTH_WATER_WAVES;
for (int32_t i = 0; i < n; i++)
2017-10-26 14:14:37 +02:00
{
vd[0] = vs[0];
vd[1] = vs[1];
vd[2] = vs[2];
vs += 9;
if (vs >= &g1->offset[9 * n])
{
vs -= 9 * n;
}
vd += 4;
}
}
waterId = SPR_GAME_PALETTE_3;
2017-11-30 18:21:12 +01:00
if (water_type != nullptr)
{
waterId = water_type->palette_index_2;
}
g1 = GfxGetG1Element(shade + waterId);
2017-11-30 18:21:12 +01:00
if (g1 != nullptr)
2017-10-26 14:14:37 +02:00
{
2018-06-22 23:25:16 +02:00
uint8_t* vs = &g1->offset[j * 3];
2020-05-28 23:37:50 +02:00
uint8_t* vd = &gGamePalette[PALETTE_OFFSET_WATER_SPARKLES * 4];
int32_t n = PALETTE_LENGTH_WATER_SPARKLES;
for (int32_t i = 0; i < n; i++)
2017-10-26 14:14:37 +02:00
{
vd[0] = vs[0];
vd[1] = vs[1];
vd[2] = vs[2];
vs += 9;
if (vs >= &g1->offset[9 * n])
{
vs -= 9 * n;
}
vd += 4;
}
}
j = (static_cast<uint16_t>(gPaletteEffectFrame * -960) * 3) >> 16;
waterId = SPR_GAME_PALETTE_4;
g1 = GfxGetG1Element(shade + waterId);
2017-11-30 18:21:12 +01:00
if (g1 != nullptr)
2017-10-26 14:14:37 +02:00
{
2018-06-22 23:25:16 +02:00
uint8_t* vs = &g1->offset[j * 3];
2020-05-28 23:37:50 +02:00
uint8_t* vd = &gGamePalette[PALETTE_INDEX_243 * 4];
2018-06-22 23:25:16 +02:00
int32_t n = 3;
for (int32_t i = 0; i < n; i++)
2017-10-26 14:14:37 +02:00
{
vd[0] = vs[0];
vd[1] = vs[1];
vd[2] = vs[2];
vs += 3;
if (vs >= &g1->offset[3 * n])
{
2017-10-26 14:14:37 +02:00
vs -= 3 * n;
}
vd += 4;
}
}
UpdatePalette(gGamePalette, PALETTE_OFFSET_ANIMATED, PALETTE_LENGTH_ANIMATED);
if (gClimateLightningFlash == 2)
{
UpdatePalette(gGamePalette, PALETTE_OFFSET_DYNAMIC, PALETTE_LENGTH_DYNAMIC);
gClimateLightningFlash = 0;
}
}
}
2014-08-05 19:15:28 +02:00
void PauseToggle()
{
gGamePaused ^= GAME_PAUSED_NORMAL;
WindowInvalidateByClass(WindowClass::TopToolbar);
if (gGamePaused & GAME_PAUSED_NORMAL)
{
OpenRCT2::Audio::StopAll();
}
}
2014-08-05 19:15:28 +02:00
bool GameIsPaused()
{
return gGamePaused != 0;
}
bool GameIsNotPaused()
{
return gGamePaused == 0;
}
2014-05-02 23:21:08 +02:00
/**
2014-11-20 19:40:10 +01:00
*
2014-05-02 23:21:08 +02:00
* rct2: 0x0066DC0F
*/
static void LoadLandscape()
2014-05-02 23:21:08 +02:00
{
auto intent = Intent(WindowClass::Loadsave);
intent.PutExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_LANDSCAPE);
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
2014-05-02 23:21:08 +02:00
}
void RCT2StringToUTF8Self(char* buffer, size_t length)
{
if (length > 0)
{
auto temp = RCT2StringToUTF8(buffer, RCT2LanguageId::EnglishUK);
SafeStrCpy(buffer, temp.data(), length);
}
}
static void FixGuestsHeadingToParkCount()
{
auto& gameState = GetGameState();
uint32_t guestsHeadingToPark = 0;
for (auto* peep : EntityList<Guest>())
{
if (peep->OutsideOfPark && peep->State != PeepState::LeavingPark)
{
guestsHeadingToPark++;
}
}
if (gameState.NumGuestsHeadingForPark != guestsHeadingToPark)
{
LOG_WARNING(
"Corrected bad amount of guests heading to park: %u -> %u", gameState.NumGuestsHeadingForPark, guestsHeadingToPark);
}
gameState.NumGuestsHeadingForPark = guestsHeadingToPark;
}
static void FixGuestCount()
{
// Recalculates peep count after loading a save to fix corrupted files
uint32_t guestCount = 0;
2023-10-20 13:27:02 +02:00
for (auto guest : EntityList<Guest>())
2017-11-30 18:24:44 +01:00
{
2023-10-20 13:27:02 +02:00
if (!guest->OutsideOfPark)
{
2023-10-20 13:27:02 +02:00
guestCount++;
}
2017-11-30 18:24:44 +01:00
}
auto& gameState = GetGameState();
if (gameState.NumGuestsInPark != guestCount)
{
LOG_WARNING("Corrected bad amount of guests in park: %u -> %u", gameState.NumGuestsInPark, guestCount);
}
gameState.NumGuestsInPark = guestCount;
}
static void FixPeepsWithInvalidRideReference()
{
// Peeps to remove have to be cached here, as removing them from within the loop breaks iteration
2019-02-28 20:28:58 +01:00
std::vector<Peep*> peepsToRemove;
// Fix possibly invalid field values
for (auto peep : EntityList<Guest>())
{
if (peep->CurrentRideStation.ToUnderlying() >= OpenRCT2::Limits::MaxStationsPerRide)
{
2020-06-08 14:04:07 +02:00
const auto srcStation = peep->CurrentRideStation;
const auto rideIdx = peep->CurrentRide;
if (rideIdx.IsNull())
{
continue;
}
Ride* ride = GetRide(rideIdx);
if (ride == nullptr)
{
2023-01-27 18:18:44 +01:00
LOG_WARNING("Couldn't find ride %u, resetting ride on peep %u", rideIdx, peep->Id);
peep->CurrentRide = RideId::GetNull();
continue;
}
2019-07-21 21:44:19 +02:00
auto curName = peep->GetName();
LOG_WARNING(
2023-01-27 18:18:44 +01:00
"Peep %u (%s) has invalid ride station = %u for ride %u.", peep->Id, curName.c_str(), srcStation.ToUnderlying(),
rideIdx);
auto station = RideGetFirstValidStationExit(*ride);
if (station.IsNull())
{
2023-01-27 18:18:44 +01:00
LOG_WARNING("Couldn't find station, removing peep %u", peep->Id);
peepsToRemove.push_back(peep);
2018-06-22 23:25:16 +02:00
}
else
{
LOG_WARNING("Amending ride station to %u.", station);
peep->CurrentRideStation = station;
}
}
}
if (!peepsToRemove.empty())
{
// Some broken saves have broken spatial indexes
2021-11-24 14:37:47 +01:00
ResetEntitySpatialIndices();
}
for (auto ptr : peepsToRemove)
{
2018-03-27 22:04:57 +02:00
ptr->Remove();
}
}
static void FixInvalidSurfaces()
{
// Fixes broken saves where a surface element could be null
// and broken saves with incorrect invisible map border tiles
for (int32_t y = 0; y < kMaximumMapSizeTechnical; y++)
{
for (int32_t x = 0; x < kMaximumMapSizeTechnical; x++)
{
auto* surfaceElement = MapGetSurfaceElementAt(TileCoordsXY{ x, y });
if (surfaceElement == nullptr)
{
LOG_ERROR("Null map element at x = %d and y = %d. Fixing...", x, y);
surfaceElement = TileElementInsert<SurfaceElement>(TileCoordsXYZ{ x, y, 14 }.ToCoordsXYZ(), 0b0000);
if (surfaceElement == nullptr)
{
LOG_ERROR("Unable to fix: Map element limit reached.");
return;
}
}
// Fix the invisible border tiles.
// At this point, we can be sure that surfaceElement is not NULL.
2024-02-12 22:32:08 +01:00
auto& gameState = GetGameState();
if (x == 0 || x == gameState.MapSize.x - 1 || y == 0 || y == gameState.MapSize.y - 1)
{
surfaceElement->SetBaseZ(kMinimumLandZ);
surfaceElement->SetClearanceZ(kMinimumLandZ);
surfaceElement->SetSlope(0);
surfaceElement->SetWaterHeight(0);
}
}
}
}
// OpenRCT2 workaround to recalculate some values which are saved redundantly in the save to fix corrupted files.
// For example recalculate guest count by looking at all the guests instead of trusting the value in the file.
void GameFixSaveVars()
{
FixGuestsHeadingToParkCount();
FixGuestCount();
FixPeepsWithInvalidRideReference();
FixInvalidSurfaces();
ResearchFix();
// Fix banners which share their index
BannerApplyFixes();
// Fix invalid vehicle sprite sizes, thus preventing visual corruption of sprites
FixInvalidVehicleSpriteSizes();
2017-10-31 12:57:40 +01:00
// Fix gParkEntrance locations for which the tile_element no longer exists
2022-10-04 08:38:00 +02:00
ParkEntranceFixLocations();
2022-03-08 01:14:52 +01:00
UpdateConsolidatedPatrolAreas();
MapCountRemainingLandRights();
}
void GameLoadInit()
2015-07-05 17:19:01 +02:00
{
auto* context = GetContext();
IGameStateSnapshots* snapshots = context->GetGameStateSnapshots();
snapshots->Reset();
context->SetActiveScene(context->GetGameScene());
Feature: Preview title sequences in-game Title sequences can now be played back in-game, allowing for much easier editing. Improved title sequence playback in general. Clicking play while on a different title sequence will play the new one. Clicking stop will make the title screen go back to the config title sequence. And the closing the title sequence window will also make the game go back to the config title sequence, and reload the sequence if it was modified. Changes made to title sequences in-game are now correctly loaded in the title screen. Starting a title sequence within the editor will now always reset it even if it's the current playing sequence. (Not for playing in the editor though). Get Location in title sequence command editor now has 100% accuracy compared to before where it would usually get some offset value. Added `get_map_coordinates_from_pos_window` which will allow getting the viewport coordinates of a specific window even if the input coordinates are under another window. This has use with getting 2D positions from the main window without the other windows getting in the way. Options window will now always specify the config title sequence in the dropdown and not the current title sequence. Made a global variable `gLoadKeepWindowsOpen`, in game.h to keep windows open when loading a park. When loading a title sequence park in-game. The sequence player will force-close all park-specific windows ahead of time. Skipping while testing title sequences no longer needs to reload the park if the current playback position is already before the target position and ahead of the load position. Added changelog entry.
2017-10-30 12:07:01 +01:00
if (!gLoadKeepWindowsOpen)
{
2023-01-16 21:14:50 +01:00
ViewportInitAll();
GameCreateWindows();
}
else
{
auto* mainWindow = WindowGetMain();
WindowUnfollowSprite(*mainWindow);
Feature: Preview title sequences in-game Title sequences can now be played back in-game, allowing for much easier editing. Improved title sequence playback in general. Clicking play while on a different title sequence will play the new one. Clicking stop will make the title screen go back to the config title sequence. And the closing the title sequence window will also make the game go back to the config title sequence, and reload the sequence if it was modified. Changes made to title sequences in-game are now correctly loaded in the title screen. Starting a title sequence within the editor will now always reset it even if it's the current playing sequence. (Not for playing in the editor though). Get Location in title sequence command editor now has 100% accuracy compared to before where it would usually get some offset value. Added `get_map_coordinates_from_pos_window` which will allow getting the viewport coordinates of a specific window even if the input coordinates are under another window. This has use with getting 2D positions from the main window without the other windows getting in the way. Options window will now always specify the config title sequence in the dropdown and not the current title sequence. Made a global variable `gLoadKeepWindowsOpen`, in game.h to keep windows open when loading a park. When loading a title sequence park in-game. The sequence player will force-close all park-specific windows ahead of time. Skipping while testing title sequences no longer needs to reload the park if the current playback position is already before the target position and ahead of the load position. Added changelog entry.
2017-10-30 12:07:01 +01:00
}
auto windowManager = context->GetUiContext()->GetWindowManager();
auto& gameState = GetGameState();
windowManager->SetMainView(gameState.SavedView, gameState.SavedViewZoom, gameState.SavedViewRotation);
if (NetworkGetMode() != NETWORK_MODE_CLIENT)
{
GameActions::ClearQueue();
}
2021-11-24 14:37:47 +01:00
ResetEntitySpatialIndices();
ResetAllSpriteQuadrantPlacements();
ScenerySetDefaultPlacementConfiguration();
2017-10-07 01:28:00 +02:00
2018-02-05 22:59:44 +01:00
auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES);
2022-11-06 21:49:07 +01:00
ContextBroadcastIntent(&intent);
2017-10-07 01:28:00 +02:00
gWindowUpdateTicks = 0;
gCurrentRealTimeTicks = 0;
LoadPalette();
if (!gOpenRCT2Headless)
{
2018-02-05 22:59:44 +01:00
intent = Intent(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD);
2022-11-06 21:49:07 +01:00
ContextBroadcastIntent(&intent);
}
gGameSpeed = 1;
}
2020-02-07 00:15:20 +01:00
void GameLoadScripts()
{
#ifdef ENABLE_SCRIPTING
GetContext()->GetScriptEngine().LoadTransientPlugins();
2020-02-23 13:55:48 +01:00
#endif
2020-02-07 00:15:20 +01:00
}
void GameUnloadScripts()
2020-02-07 00:15:20 +01:00
{
#ifdef ENABLE_SCRIPTING
GetContext()->GetScriptEngine().UnloadTransientPlugins();
2020-02-23 13:55:48 +01:00
#endif
2014-05-04 17:21:15 +02:00
}
void GameNotifyMapChange()
2022-02-20 03:05:24 +01:00
{
#ifdef ENABLE_SCRIPTING
// Ensure we don't get a two lots of change events
if (_mapChangedExpected)
return;
2022-02-20 03:05:24 +01:00
using namespace OpenRCT2::Scripting;
auto& scriptEngine = GetContext()->GetScriptEngine();
auto& hookEngine = scriptEngine.GetHookEngine();
hookEngine.Call(HOOK_TYPE::MAP_CHANGE, false);
_mapChangedExpected = true;
#endif
}
void GameNotifyMapChanged()
{
#ifdef ENABLE_SCRIPTING
using namespace OpenRCT2::Scripting;
auto& scriptEngine = GetContext()->GetScriptEngine();
auto& hookEngine = scriptEngine.GetHookEngine();
hookEngine.Call(HOOK_TYPE::MAP_CHANGED, false);
_mapChangedExpected = false;
2022-02-20 03:05:24 +01:00
#endif
}
/**
2014-06-21 16:50:13 +02:00
*
* rct2: 0x0069E9A7
* Call after a rotation or loading of a save to reset sprite quadrants
2014-06-21 16:50:13 +02:00
*/
void ResetAllSpriteQuadrantPlacements()
{
for (EntityId::UnderlyingType i = 0; i < MAX_ENTITIES; i++)
{
auto* spr = GetEntity(EntityId::FromUnderlying(i));
if (spr != nullptr && spr->Type != EntityType::Null)
{
2021-09-28 02:16:04 +02:00
spr->MoveTo(spr->GetLocation());
}
}
2014-06-21 16:50:13 +02:00
}
void SaveGame()
2014-05-27 23:33:16 +02:00
{
if (!gFirstTimeSaving && !gIsAutosaveLoaded)
{
2022-02-26 17:40:49 +01:00
const auto savePath = Path::WithExtension(gScenarioSavePath, ".park");
SaveGameWithName(savePath);
}
else
{
SaveGameAs();
}
}
2017-09-12 00:04:03 +02:00
void SaveGameCmd(u8string_view name /* = {} */)
{
if (name.empty())
{
2022-02-26 17:40:49 +01:00
const auto savePath = Path::WithExtension(gScenarioSavePath, ".park");
SaveGameWithName(savePath);
}
else
{
auto env = GetContext()->GetPlatformEnvironment();
auto savePath = Path::Combine(env->GetDirectoryPath(DIRBASE::USER, DIRID::SAVE), u8string(name) + u8".park");
SaveGameWithName(savePath);
}
}
void SaveGameWithName(u8string_view name)
{
LOG_VERBOSE("Saving to %s", u8string(name).c_str());
auto& gameState = GetGameState();
if (ScenarioSave(gameState, name, gConfigGeneral.SavePluginData ? 1 : 0))
{
LOG_VERBOSE("Saved to %s", u8string(name).c_str());
gCurrentLoadedPath = name;
gIsAutosaveLoaded = false;
gScreenAge = 0;
}
}
std::unique_ptr<Intent> CreateSaveGameAsIntent()
{
2022-02-26 17:20:07 +01:00
auto name = Path::GetFileNameWithoutExtension(gScenarioSavePath);
2017-09-12 00:04:03 +02:00
auto intent = std::make_unique<Intent>(WindowClass::Loadsave);
intent->PutExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_SAVE | LOADSAVETYPE_GAME);
intent->PutExtra(INTENT_EXTRA_PATH, name);
2017-09-12 00:04:03 +02:00
return intent;
}
void SaveGameAs()
2017-09-12 00:04:03 +02:00
{
auto intent = CreateSaveGameAsIntent();
2022-11-06 21:49:07 +01:00
ContextOpenIntent(intent.get());
2014-05-27 23:33:16 +02:00
}
static void LimitAutosaveCount(const size_t numberOfFilesToKeep, bool processLandscapeFolder)
2016-01-04 16:22:15 +01:00
{
2018-06-22 23:25:16 +02:00
size_t autosavesCount = 0;
size_t numAutosavesToDelete = 0;
2016-01-04 16:22:15 +01:00
2020-10-17 16:57:18 +02:00
auto environment = GetContext()->GetPlatformEnvironment();
auto folderDirectory = environment->GetDirectoryPath(DIRBASE::USER, DIRID::SAVE);
char const* fileFilter = "autosave_*.park";
if (processLandscapeFolder)
{
2020-10-17 16:57:18 +02:00
folderDirectory = environment->GetDirectoryPath(DIRBASE::USER, DIRID::LANDSCAPE);
2022-01-24 20:03:20 +01:00
fileFilter = "autosave_*.park";
}
2016-11-13 20:17:49 +01:00
2022-03-10 10:51:34 +01:00
const u8string filter = Path::Combine(folderDirectory, "autosave", fileFilter);
// At first, count how many autosaves there are
{
auto scanner = Path::ScanDirectory(filter, false);
while (scanner->Next())
{
autosavesCount++;
}
}
2016-11-13 20:17:49 +01:00
// If there are fewer autosaves than the number of files to keep we don't need to delete anything
if (autosavesCount <= numberOfFilesToKeep)
{
return;
}
2016-11-13 20:17:49 +01:00
2022-03-10 10:51:34 +01:00
std::vector<u8string> autosaveFiles;
{
auto scanner = Path::ScanDirectory(filter, false);
for (size_t i = 0; i < autosavesCount; i++)
{
if (scanner->Next())
{
2022-03-10 10:51:34 +01:00
autosaveFiles.emplace_back(Path::Combine(folderDirectory, "autosave", scanner->GetPathRelative()));
}
}
}
2016-01-04 16:22:15 +01:00
std::sort(autosaveFiles.begin(), autosaveFiles.end(), [](const auto& saveFile0, const auto& saveFile1) {
return saveFile0.compare(saveFile1) < 0;
});
2016-01-04 16:22:15 +01:00
// Calculate how many saves we need to delete.
2022-03-10 10:51:34 +01:00
numAutosavesToDelete = autosaveFiles.size() - numberOfFilesToKeep;
2016-11-13 20:17:49 +01:00
2017-12-03 22:38:02 +01:00
for (size_t i = 0; numAutosavesToDelete > 0; i++, numAutosavesToDelete--)
{
2022-03-10 10:51:34 +01:00
if (!File::Delete(autosaveFiles[i]))
{
LOG_WARNING("Failed to delete autosave file: %s", autosaveFiles[i].data());
}
}
2016-01-04 16:22:15 +01:00
}
void GameAutosave()
2015-02-21 12:05:15 +01:00
{
auto subDirectory = DIRID::SAVE;
const char* fileExtension = ".park";
uint32_t saveFlags = 0x80000000;
if (gScreenFlags & SCREEN_FLAGS_EDITOR)
{
subDirectory = DIRID::LANDSCAPE;
2022-01-24 20:03:20 +01:00
fileExtension = ".park";
saveFlags |= 2;
}
// Retrieve current time
auto currentDate = Platform::GetDateLocal();
auto currentTime = Platform::GetTimeLocal();
utf8 timeName[44];
2018-06-22 23:25:16 +02:00
snprintf(
timeName, sizeof(timeName), "autosave_%04u-%02u-%02u_%02u-%02u-%02u%s", currentDate.year, currentDate.month,
currentDate.day, currentTime.hour, currentTime.minute, currentTime.second, fileExtension);
int32_t autosavesToKeep = gConfigGeneral.AutosaveAmount;
LimitAutosaveCount(autosavesToKeep - 1, (gScreenFlags & SCREEN_FLAGS_EDITOR));
auto env = GetContext()->GetPlatformEnvironment();
auto autosaveDir = Path::Combine(env->GetDirectoryPath(DIRBASE::USER, subDirectory), u8"autosave");
Path::CreateDirectory(autosaveDir);
auto path = Path::Combine(autosaveDir, timeName);
auto backupFileName = u8string(u8"autosave") + fileExtension + u8".bak";
auto backupPath = Path::Combine(autosaveDir, backupFileName);
if (File::Exists(path))
{
2022-01-08 13:57:29 +01:00
File::Copy(path, backupPath, true);
}
auto& gameState = GetGameState();
if (!ScenarioSave(gameState, path, saveFlags))
2021-02-13 01:15:27 +01:00
Console::Error::WriteLine("Could not autosave the scenario. Is the save folder writeable?");
2015-02-21 12:05:15 +01:00
}
static void GameLoadOrQuitNoSavePromptCallback(int32_t result, const utf8* path)
{
if (result == MODAL_RESULT_OK)
{
GameNotifyMapChange();
GameUnloadScripts();
WindowCloseByClass(WindowClass::EditorObjectSelection);
GetContext()->LoadParkFromFile(path);
GameLoadScripts();
GameNotifyMapChanged();
gIsAutosaveLoaded = gIsAutosave;
gFirstTimeSaving = false;
}
}
static void NewGameWindowCallback(const utf8* path)
{
WindowCloseByClass(WindowClass::EditorObjectSelection);
GameNotifyMapChange();
GetContext()->LoadParkFromFile(path, false, true);
GameLoadScripts();
GameNotifyMapChanged();
}
2014-05-02 23:21:08 +02:00
/**
2015-10-09 18:22:37 +02:00
*
2014-05-02 23:21:08 +02:00
* rct2: 0x0066DB79
*/
void GameLoadOrQuitNoSavePrompt()
2014-05-02 23:21:08 +02:00
{
switch (gSavePromptMode)
{
case PromptMode::SaveBeforeLoad:
2019-02-21 10:34:30 +01:00
{
auto loadOrQuitAction = LoadOrQuitAction(LoadOrQuitModes::CloseSavePrompt);
GameActions::Execute(&loadOrQuitAction);
ToolCancel();
2018-06-22 23:25:16 +02:00
if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
{
LoadLandscape();
2018-06-22 23:25:16 +02:00
}
else
{
auto intent = Intent(WindowClass::Loadsave);
intent.PutExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_LOAD | LOADSAVETYPE_GAME);
intent.PutExtra(INTENT_EXTRA_CALLBACK, reinterpret_cast<void*>(GameLoadOrQuitNoSavePromptCallback));
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
2018-06-22 23:25:16 +02:00
}
break;
2019-02-21 10:34:30 +01:00
}
case PromptMode::SaveBeforeQuit:
2019-02-21 10:34:30 +01:00
{
auto loadOrQuitAction = LoadOrQuitAction(LoadOrQuitModes::CloseSavePrompt);
GameActions::Execute(&loadOrQuitAction);
ToolCancel();
if (InputTestFlag(INPUT_FLAG_5))
2018-06-22 23:25:16 +02:00
{
InputSetFlag(INPUT_FLAG_5, false);
2018-06-22 23:25:16 +02:00
}
GameResetSpeed();
2018-06-22 23:25:16 +02:00
gFirstTimeSaving = true;
GameNotifyMapChange();
GameUnloadScripts();
auto* context = OpenRCT2::GetContext();
context->SetActiveScene(context->GetTitleScene());
2018-06-22 23:25:16 +02:00
break;
2019-02-21 10:34:30 +01:00
}
case PromptMode::SaveBeforeNewGame:
{
auto loadOrQuitAction = LoadOrQuitAction(LoadOrQuitModes::CloseSavePrompt);
GameActions::Execute(&loadOrQuitAction);
ToolCancel();
auto intent = Intent(WindowClass::ScenarioSelect);
intent.PutExtra(INTENT_EXTRA_CALLBACK, reinterpret_cast<void*>(NewGameWindowCallback));
ContextOpenIntent(&intent);
break;
}
2018-06-22 23:25:16 +02:00
default:
GameUnloadScripts();
OpenRCT2Finish();
2018-06-22 23:25:16 +02:00
break;
}
2014-05-02 23:21:08 +02:00
}
2020-03-31 23:12:35 +02:00
void StartSilentRecord()
2020-03-31 23:12:35 +02:00
{
std::string name = Path::Combine(
OpenRCT2::GetContext()->GetPlatformEnvironment()->GetDirectoryPath(OpenRCT2::DIRBASE::USER), u8"debug_replay.parkrep");
2020-03-31 23:12:35 +02:00
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
if (replayManager->StartRecording(name, OpenRCT2::k_MaxReplayTicks, OpenRCT2::IReplayManager::RecordType::SILENT))
{
OpenRCT2::ReplayRecordInfo info;
replayManager->GetCurrentReplayInfo(info);
2022-01-27 14:21:46 +01:00
gSilentRecordingName = info.FilePath;
2020-03-31 23:12:35 +02:00
const char* logFmt = "Silent replay recording started: (%s) %s\n";
Console::WriteLine(logFmt, info.Name.c_str(), info.FilePath.c_str());
2020-03-31 23:12:35 +02:00
}
}
bool StopSilentRecord()
2020-03-31 23:12:35 +02:00
{
auto* replayManager = OpenRCT2::GetContext()->GetReplayManager();
if (!replayManager->IsRecording() && !replayManager->IsNormalising())
{
return false;
}
OpenRCT2::ReplayRecordInfo info;
replayManager->GetCurrentReplayInfo(info);
if (replayManager->StopRecording())
{
const char* logFmt = "Replay recording stopped: (%s) %s\n"
" Ticks: %u\n"
" Commands: %u\n"
" Checksums: %u";
Console::WriteLine(logFmt, info.Name.c_str(), info.FilePath.c_str(), info.Ticks, info.NumCommands, info.NumChecksums);
2020-03-31 23:12:35 +02:00
return true;
}
return false;
}
2022-03-07 21:40:48 +01:00
void PrepareMapForSave()
{
2023-01-16 21:14:50 +01:00
ViewportSetSavedView();
2022-03-07 21:40:48 +01:00
#ifdef ENABLE_SCRIPTING
auto& scriptEngine = GetContext()->GetScriptEngine();
auto& hookEngine = scriptEngine.GetHookEngine();
if (hookEngine.HasSubscriptions(OpenRCT2::Scripting::HOOK_TYPE::MAP_SAVE))
{
hookEngine.Call(OpenRCT2::Scripting::HOOK_TYPE::MAP_SAVE, false);
}
#endif
}