OpenRCT2/src/openrct2/GameState.cpp

390 lines
12 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "GameState.h"
2018-06-22 23:25:16 +02:00
2022-05-14 01:59:47 +02:00
#include "./peep/GuestPathfinding.h"
#include "Context.h"
#include "Date.h"
#include "Editor.h"
#include "Game.h"
#include "GameState.h"
#include "GameStateSnapshots.h"
#include "Input.h"
2018-06-22 23:25:16 +02:00
#include "OpenRCT2.h"
#include "ReplayManager.h"
#include "actions/GameAction.h"
#include "config/Config.h"
2021-11-24 15:58:01 +01:00
#include "entity/EntityRegistry.h"
#include "entity/EntityTweener.h"
2022-03-08 01:14:52 +01:00
#include "entity/PatrolArea.h"
2021-11-25 22:47:24 +01:00
#include "entity/Staff.h"
#include "interface/Screenshot.h"
2017-07-23 17:10:09 +02:00
#include "localisation/Date.h"
#include "localisation/Localisation.h"
#include "management/NewsItem.h"
#include "network/network.h"
#include "platform/Platform.h"
#include "profiling/Profiling.h"
#include "ride/Vehicle.h"
#include "scenario/Scenario.h"
#include "scenes/title/TitleScene.h"
#include "scenes/title/TitleSequencePlayer.h"
2018-03-19 00:35:58 +01:00
#include "scripting/ScriptEngine.h"
2018-06-22 23:25:16 +02:00
#include "ui/UiContext.h"
#include "windows/Intent.h"
#include "world/Climate.h"
#include "world/MapAnimation.h"
#include "world/Park.h"
#include "world/Scenery.h"
#include <algorithm>
#include <chrono>
using namespace OpenRCT2;
2018-03-19 00:35:58 +01:00
using namespace OpenRCT2::Scripting;
2023-12-27 22:57:53 +01:00
static GameState_t _gameState{};
namespace OpenRCT2
2023-12-27 22:57:53 +01:00
{
GameState_t& GetGameState()
{
return _gameState;
}
2017-07-23 02:06:24 +02:00
2024-03-26 15:08:17 +01:00
/**
* Initialises the map, park etc. basically all S6 data.
*/
void gameStateInitAll(GameState_t& gameState, const TileCoordsXY& mapSize)
{
PROFILED_FUNCTION();
2024-03-26 15:08:17 +01:00
gInMapInitCode = true;
gameState.CurrentTicks = 0;
2024-03-26 15:08:17 +01:00
MapInit(mapSize);
Park::Initialise(gameState);
2024-03-26 15:08:17 +01:00
FinanceInit();
BannerInit(gameState);
RideInitAll();
ResetAllEntities();
UpdateConsolidatedPatrolAreas();
ResetDate();
ClimateReset(ClimateType::CoolAndWet);
News::InitQueue();
2024-03-26 15:08:17 +01:00
gInMapInitCode = false;
2024-03-26 15:08:17 +01:00
GetGameState().NextGuestNumber = 1;
2024-03-26 15:08:17 +01:00
ContextInit();
ScenerySetDefaultPlacementConfiguration();
2024-03-26 15:08:17 +01:00
auto intent = Intent(INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD);
ContextBroadcastIntent(&intent);
2024-03-26 15:08:17 +01:00
LoadPalette();
2024-03-26 15:08:17 +01:00
CheatsReset();
ClearRestrictedScenery();
#ifdef ENABLE_SCRIPTING
2024-03-26 15:08:17 +01:00
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ClearParkStorage();
#endif
2024-03-26 15:08:17 +01:00
EntityTweener::Get().Reset();
}
2024-03-26 15:08:17 +01:00
/**
* Function will be called every kGameUpdateTimeMS.
* It has its own loop which might run multiple updates per call such as
* when operating as a client it may run multiple updates to catch up with the server tick,
* another influence can be the game speed setting.
*/
void gameStateTick()
{
PROFILED_FUNCTION();
2024-03-26 15:08:17 +01:00
// Normal game play will update only once every kGameUpdateTimeMS
uint32_t numUpdates = 1;
2024-03-26 15:08:17 +01:00
// 0x006E3AEC // screen_game_process_mouse_input();
ScreenshotCheck();
GameHandleKeyboardInput();
2024-03-26 15:08:17 +01:00
if (GameIsNotPaused() && gPreviewingTitleSequenceInGame)
{
2024-03-26 15:08:17 +01:00
auto player = GetContext()->GetUiContext()->GetTitleSequencePlayer();
if (player != nullptr)
{
player->Update();
}
}
2024-03-26 15:08:17 +01:00
NetworkUpdate();
2024-03-26 15:08:17 +01:00
if (NetworkGetMode() == NETWORK_MODE_CLIENT && NetworkGetStatus() == NETWORK_STATUS_CONNECTED
&& NetworkGetAuthstatus() == NetworkAuth::Ok)
{
2024-03-26 15:08:17 +01:00
numUpdates = std::clamp<uint32_t>(NetworkGetServerTick() - GetGameState().CurrentTicks, 0, 10);
}
2024-03-26 15:08:17 +01:00
else
{
2024-03-26 15:08:17 +01:00
// Determine how many times we need to update the game
if (gGameSpeed > 1)
{
// Update more often if game speed is above normal.
numUpdates = 1 << (gGameSpeed - 1);
}
}
2024-03-26 15:08:17 +01:00
bool isPaused = GameIsPaused();
if (NetworkGetMode() == NETWORK_MODE_SERVER && gConfigNetwork.PauseServerIfNoClients)
{
2024-03-26 15:08:17 +01:00
// If we are headless we always have 1 player (host), pause if no one else is around.
if (gOpenRCT2Headless && NetworkGetNumPlayers() == 1)
{
isPaused |= true;
}
}
2024-03-26 15:08:17 +01:00
bool didRunSingleFrame = false;
if (isPaused)
{
if (gDoSingleUpdate && NetworkGetMode() == NETWORK_MODE_NONE)
{
2024-03-26 15:08:17 +01:00
didRunSingleFrame = true;
PauseToggle();
numUpdates = 1;
}
2024-03-26 15:08:17 +01:00
else
{
// NOTE: Here are a few special cases that would be normally handled in UpdateLogic.
// If the game is paused it will not call UpdateLogic at all.
numUpdates = 0;
if (NetworkGetMode() == NETWORK_MODE_SERVER)
{
// Make sure the client always knows about what tick the host is on.
NetworkSendTick();
}
2024-03-26 15:08:17 +01:00
// Keep updating the money effect even when paused.
UpdateMoneyEffect();
2024-03-26 15:08:17 +01:00
// Update the animation list. Note this does not
// increment the map animation.
MapAnimationInvalidateAll();
2024-03-26 15:08:17 +01:00
// Post-tick network update
NetworkProcessPending();
2024-03-26 15:08:17 +01:00
// Post-tick game actions.
GameActions::ProcessQueue();
}
}
2024-03-26 15:08:17 +01:00
// Update the game one or more times
for (uint32_t i = 0; i < numUpdates; i++)
{
2024-03-26 15:08:17 +01:00
gameStateUpdateLogic();
if (gGameSpeed == 1)
{
2024-03-26 15:08:17 +01:00
if (InputGetState() == InputState::Reset || InputGetState() == InputState::Normal)
{
if (InputTestFlag(INPUT_FLAG_VIEWPORT_SCROLLING))
{
InputSetFlag(INPUT_FLAG_VIEWPORT_SCROLLING, false);
break;
}
}
else
{
break;
}
}
2024-03-26 15:08:17 +01:00
// Don't call UpdateLogic again if the game was just paused.
isPaused |= GameIsPaused();
if (isPaused)
break;
}
2024-03-26 15:08:17 +01:00
NetworkFlush();
2024-03-26 15:08:17 +01:00
if (!gOpenRCT2Headless)
{
InputSetFlag(INPUT_FLAG_VIEWPORT_SCROLLING, false);
// the flickering frequency is reduced by 4, compared to the original
// it was done due to inability to reproduce original frequency
// and decision that the original one looks too fast
if (gCurrentRealTimeTicks % 4 == 0)
gWindowMapFlashingFlags ^= MapFlashingFlags::SwitchColour;
// Handle guest map flashing
gWindowMapFlashingFlags &= ~MapFlashingFlags::FlashGuests;
if (gWindowMapFlashingFlags & MapFlashingFlags::GuestListOpen)
gWindowMapFlashingFlags |= MapFlashingFlags::FlashGuests;
gWindowMapFlashingFlags &= ~MapFlashingFlags::GuestListOpen;
// Handle staff map flashing
gWindowMapFlashingFlags &= ~MapFlashingFlags::FlashStaff;
if (gWindowMapFlashingFlags & MapFlashingFlags::StaffListOpen)
gWindowMapFlashingFlags |= MapFlashingFlags::FlashStaff;
gWindowMapFlashingFlags &= ~MapFlashingFlags::StaffListOpen;
ContextUpdateMapTooltip();
}
// Always perform autosave check, even when paused
if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) && !(gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER)
&& !(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER))
{
ScenarioAutosaveCheck();
}
WindowDispatchUpdateAll();
if (didRunSingleFrame && GameIsNotPaused() && !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO))
{
PauseToggle();
}
gDoSingleUpdate = false;
}
2024-03-26 15:08:17 +01:00
static void gameStateCreateStateSnapshot()
{
2024-03-26 15:08:17 +01:00
PROFILED_FUNCTION();
2024-03-26 15:08:17 +01:00
IGameStateSnapshots* snapshots = GetContext()->GetGameStateSnapshots();
2024-03-26 15:08:17 +01:00
auto& snapshot = snapshots->CreateSnapshot();
snapshots->Capture(snapshot);
snapshots->LinkSnapshot(snapshot, GetGameState().CurrentTicks, ScenarioRandState().s0);
}
2024-03-26 15:08:17 +01:00
void gameStateUpdateLogic()
{
PROFILED_FUNCTION();
2024-03-26 15:08:17 +01:00
gInUpdateCode = true;
2023-06-01 00:25:07 +02:00
2024-03-26 15:08:17 +01:00
gScreenAge++;
if (gScreenAge == 0)
gScreenAge--;
2024-03-26 15:08:17 +01:00
GetContext()->GetReplayManager()->Update();
2024-03-26 15:08:17 +01:00
NetworkUpdate();
2024-03-26 15:08:17 +01:00
if (NetworkGetMode() == NETWORK_MODE_SERVER)
{
2024-03-26 15:08:17 +01:00
if (NetworkGamestateSnapshotsEnabled())
{
gameStateCreateStateSnapshot();
}
2024-03-26 15:08:17 +01:00
// Send current tick out.
NetworkSendTick();
}
2024-03-26 15:08:17 +01:00
else if (NetworkGetMode() == NETWORK_MODE_CLIENT)
{
2024-03-26 15:08:17 +01:00
// Don't run past the server, this condition can happen during map changes.
if (NetworkGetServerTick() == GetGameState().CurrentTicks)
{
2024-03-26 15:08:17 +01:00
gInUpdateCode = false;
return;
}
2024-03-26 15:08:17 +01:00
// Check desync.
bool desynced = NetworkCheckDesynchronisation();
if (desynced)
{
// If desync debugging is enabled and we are still connected request the specific game state from server.
if (NetworkGamestateSnapshotsEnabled() && NetworkGetStatus() == NETWORK_STATUS_CONNECTED)
{
// Create snapshot from this tick so we can compare it later
// as we won't pause the game on this event.
gameStateCreateStateSnapshot();
NetworkRequestGamestateSnapshot();
}
}
}
2024-03-26 15:08:17 +01:00
auto& gameState = GetGameState();
#ifdef ENABLE_SCRIPTING
2024-03-26 15:08:17 +01:00
// Stash the current day number before updating the date so that we
// know if the day number changes on this tick.
auto day = gameState.Date.GetDay();
2020-02-23 13:55:48 +01:00
#endif
2020-02-18 23:31:54 +01:00
2024-03-26 17:32:36 +01:00
DateUpdate(gameState);
2024-03-26 15:08:17 +01:00
ScenarioUpdate(gameState);
ClimateUpdate();
MapUpdateTiles();
// Temporarily remove provisional paths to prevent peep from interacting with them
MapRemoveProvisionalElements();
MapUpdatePathWideFlags();
PeepUpdateAll();
MapRestoreProvisionalElements();
VehicleUpdateAll();
UpdateAllMiscEntities();
Ride::UpdateAll();
if (!(gScreenFlags & SCREEN_FLAGS_EDITOR))
{
Park::Update(gameState, gameState.Date);
2024-03-26 15:08:17 +01:00
}
2017-07-23 02:06:24 +02:00
2024-03-26 15:08:17 +01:00
ResearchUpdate();
RideRatingsUpdateAll();
RideMeasurementsUpdate();
News::UpdateCurrentItem();
2024-03-26 15:08:17 +01:00
MapAnimationInvalidateAll();
VehicleSoundsUpdate();
PeepUpdateCrowdNoise();
ClimateUpdateSound();
EditorOpenWindowsForCurrentStep();
2024-03-26 15:08:17 +01:00
// Update windows
// WindowDispatchUpdateAll();
2024-03-26 15:08:17 +01:00
// Start autosave timer after update
2024-04-04 18:18:25 +02:00
if (gLastAutoSaveUpdate == kAutosavePause)
2024-03-26 15:08:17 +01:00
{
gLastAutoSaveUpdate = Platform::GetTicks();
}
2024-03-26 15:08:17 +01:00
GameActions::ProcessQueue();
2024-03-26 15:08:17 +01:00
NetworkProcessPending();
NetworkFlush();
2024-03-26 15:08:17 +01:00
gameState.CurrentTicks++;
#ifdef ENABLE_SCRIPTING
2024-03-26 15:08:17 +01:00
auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
hookEngine.Call(HOOK_TYPE::INTERVAL_TICK, true);
2024-03-26 15:08:17 +01:00
if (day != gameState.Date.GetDay())
{
hookEngine.Call(HOOK_TYPE::INTERVAL_DAY, true);
}
#endif
2024-03-26 15:08:17 +01:00
gInUpdateCode = false;
}
} // namespace OpenRCT2