1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-18 04:23:20 +01:00
Files
OpenRCT2/src/openrct2/title/TitleSequencePlayer.cpp
Robert Jordan a3c64bb146 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

573 lines
17 KiB
C++

#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <memory>
#include "../common.h"
#include "../Context.h"
#include "../core/Console.hpp"
#include "../core/Exception.hpp"
#include "../core/Guard.hpp"
#include "../core/Math.hpp"
#include "../core/Path.hpp"
#include "../core/String.hpp"
#include "../OpenRCT2.h"
#include "../ParkImporter.h"
#include "../scenario/ScenarioRepository.h"
#include "../scenario/ScenarioSources.h"
#include "TitleScreen.h"
#include "TitleSequence.h"
#include "TitleSequenceManager.h"
#include "TitleSequencePlayer.h"
#include "../game.h"
#include "../interface/viewport.h"
#include "../interface/window.h"
#include "../management/NewsItem.h"
#include "../windows/Intent.h"
#include "../world/scenery.h"
using namespace OpenRCT2;
class TitleSequencePlayer final : public ITitleSequencePlayer
{
private:
static constexpr const char * SFMM_FILENAME = "Six Flags Magic Mountain.SC6";
IScenarioRepository * _scenarioRepository = nullptr;
uint32 _sequenceId = 0;
TitleSequence * _sequence = nullptr;
sint32 _position = 0;
sint32 _waitCounter = 0;
sint32 _lastScreenWidth = 0;
sint32 _lastScreenHeight = 0;
LocationXY32 _viewCentreLocation = { 0 };
public:
TitleSequencePlayer(IScenarioRepository * scenarioRepository)
{
Guard::ArgumentNotNull(scenarioRepository);
_scenarioRepository = scenarioRepository;
}
~TitleSequencePlayer() override
{
Eject();
}
sint32 GetCurrentPosition() const override
{
return _position;
}
void Eject() override
{
FreeTitleSequence(_sequence);
_sequence = nullptr;
}
bool Begin(uint32 titleSequenceId) override
{
size_t numSequences = TitleSequenceManager::GetCount();
if (titleSequenceId >= numSequences)
{
return false;
}
auto seqItem = TitleSequenceManager::GetItem(titleSequenceId);
auto sequence = LoadTitleSequence(seqItem->Path.c_str());
if (sequence == nullptr)
{
return false;
}
Eject();
_sequence = sequence;
_sequenceId = titleSequenceId;
Reset();
return true;
}
bool Update() override
{
sint32 entryPosition = _position;
FixViewLocation();
if (_sequence == nullptr)
{
SetViewLocation(75 * 32, 75 * 32);
return false;
}
// Check that position is valid
if (_position >= (sint32)_sequence->NumCommands)
{
_position = 0;
return false;
}
// Don't execute next command until we are done with the current wait command
if (_waitCounter != 0)
{
_waitCounter--;
if (_waitCounter == 0)
{
const TitleCommand * command = &_sequence->Commands[_position];
if (command->Type == TITLE_SCRIPT_WAIT)
{
IncrementPosition();
}
}
}
else
{
while (true)
{
const TitleCommand * command = &_sequence->Commands[_position];
if (ExecuteCommand(command))
{
if (command->Type == TITLE_SCRIPT_WAIT)
{
break;
}
if (command->Type != TITLE_SCRIPT_RESTART)
{
IncrementPosition();
}
if (_position == entryPosition)
{
Console::Error::WriteLine("Infinite loop detected in title sequence.");
Console::Error::WriteLine(" A wait command may be missing.");
return false;
}
}
else
{
SkipToNextLoadCommand();
if (_position == entryPosition)
{
Console::Error::WriteLine("Unable to load any parks from %s.", _sequence->Name);
return false;
}
}
}
}
return true;
}
void Reset() override
{
_position = 0;
_waitCounter = 0;
}
void Seek(sint32 targetPosition) override
{
if (targetPosition < 0 || targetPosition >= (sint32)_sequence->NumCommands)
{
throw Exception("Invalid position.");
}
if (_position >= targetPosition)
{
Reset();
}
if (_sequence->Commands[targetPosition].Type == TITLE_SCRIPT_RESTART)
{
targetPosition = 0;
}
// Set position to the last LOAD command before target position
for (sint32 i = targetPosition; i >= 0; i--)
{
const TitleCommand * command = &_sequence->Commands[i];
if ((_position == i && _position != targetPosition) || TitleSequenceIsLoadCommand(command))
{
// Break if we have a new load command or if we're already in the range of the correct load command
_position = i;
break;
}
}
// Keep updating until we reach target position
gInUpdateCode = true;
while (_position < targetPosition)
{
if (Update())
{
game_logic_update();
}
else
{
break;
}
}
gInUpdateCode = false;
_waitCounter = 0;
}
private:
void IncrementPosition()
{
_position++;
if (_position >= (sint32)_sequence->NumCommands)
{
_position = 0;
}
}
void SkipToNextLoadCommand()
{
const TitleCommand * command;
do
{
IncrementPosition();
command = &_sequence->Commands[_position];
}
while (!TitleSequenceIsLoadCommand(command));
}
bool ExecuteCommand(const TitleCommand * command)
{
switch (command->Type) {
case TITLE_SCRIPT_END:
_waitCounter = 1;
break;
case TITLE_SCRIPT_WAIT:
// The waitCounter is measured in 25-ms game ticks. Previously it was seconds * 40 ticks/second, now it is ms / 25 ms/tick
_waitCounter = Math::Max<sint32>(1, command->Milliseconds / (uint32)GAME_UPDATE_TIME_MS);
break;
case TITLE_SCRIPT_LOADMM:
{
const scenario_index_entry * entry = _scenarioRepository->GetByFilename(SFMM_FILENAME);
if (entry == nullptr)
{
Console::Error::WriteLine("%s not found.", SFMM_FILENAME);
return false;
}
const utf8 * path = entry->path;
if (!LoadParkFromFile(path))
{
Console::Error::WriteLine("Failed to load: \"%s\" for the title sequence.", path);
return false;
}
break;
}
case TITLE_SCRIPT_LOCATION:
{
sint32 x = command->X * 32 + 16;
sint32 y = command->Y * 32 + 16;
SetViewLocation(x, y);
break;
}
case TITLE_SCRIPT_ROTATE:
RotateView(command->Rotations);
break;
case TITLE_SCRIPT_ZOOM:
SetViewZoom(command->Zoom);
break;
case TITLE_SCRIPT_SPEED:
gGameSpeed = Math::Clamp<uint8>(1, command->Speed, 4);
break;
case TITLE_SCRIPT_RESTART:
Reset();
break;
case TITLE_SCRIPT_LOAD:
{
bool loadSuccess = false;
uint8 saveIndex = command->SaveIndex;
TitleSequenceParkHandle * parkHandle = TitleSequenceGetParkHandle(_sequence, saveIndex);
if (parkHandle != nullptr)
{
loadSuccess = LoadParkFromStream((IStream *)parkHandle->Stream, parkHandle->HintPath);
TitleSequenceCloseParkHandle(parkHandle);
}
if (!loadSuccess)
{
if (_sequence->NumSaves > saveIndex)
{
const utf8 * path = _sequence->Saves[saveIndex];
Console::Error::WriteLine("Failed to load: \"%s\" for the title sequence.", path);
}
return false;
}
break;
}
case TITLE_SCRIPT_LOADRCT1:
{
source_desc sourceDesc;
if (!ScenarioSources::TryGetById(command->SaveIndex, &sourceDesc) || sourceDesc.index == -1)
{
Console::Error::WriteLine("Invalid scenario id.");
return false;
}
const utf8 * path = nullptr;
size_t numScenarios = _scenarioRepository->GetCount();
for (size_t i = 0; i < numScenarios; i++)
{
const scenario_index_entry * scenario = _scenarioRepository->GetByIndex(i);
if (scenario && scenario->source_index == sourceDesc.index)
{
path = scenario->path;
break;
}
}
if (path == nullptr || !LoadParkFromFile(path))
{
return false;
}
break;
}
}
return true;
}
void SetViewZoom(const uint32 &zoom)
{
rct_window * w = window_get_main();
if (w != nullptr && w->viewport != nullptr)
{
window_zoom_set(w, zoom, false);
}
}
void RotateView(uint32 count)
{
rct_window * w = window_get_main();
if (w != nullptr)
{
for (uint32 i = 0; i < count; i++)
{
window_rotate_camera(w, 1);
}
}
}
bool LoadParkFromFile(const utf8 * path)
{
log_verbose("TitleSequencePlayer::LoadParkFromFile(%s)", path);
bool success = false;
try
{
if (gTestingTitleSequenceInGame)
{
gLoadKeepWindowsOpen = true;
CloseParkSpecificWindows();
context_load_park_from_file(path);
}
else
{
auto parkImporter = std::unique_ptr<IParkImporter>(ParkImporter::Create(path));
parkImporter->Load(path);
parkImporter->Import();
}
PrepareParkForPlayback();
success = true;
}
catch (Exception)
{
Console::Error::WriteLine("Unable to load park: %s", path);
}
gLoadKeepWindowsOpen = false;
return success;
}
/**
* @param stream The stream to read the park data from.
* @param pathHint Hint path, the extension is grabbed to determine what importer to use.
*/
bool LoadParkFromStream(IStream * stream, const std::string &hintPath)
{
log_verbose("TitleSequencePlayer::LoadParkFromStream(%s)", hintPath.c_str());
bool success = false;
try
{
if (gTestingTitleSequenceInGame)
{
gLoadKeepWindowsOpen = true;
CloseParkSpecificWindows();
context_load_park_from_stream(stream);
}
else
{
std::string extension = Path::GetExtension(hintPath);
bool isScenario = ParkImporter::ExtensionIsScenario(hintPath);
auto parkImporter = std::unique_ptr<IParkImporter>(ParkImporter::Create(hintPath));
parkImporter->LoadFromStream(stream, isScenario);
parkImporter->Import();
}
PrepareParkForPlayback();
success = true;
}
catch (Exception)
{
Console::Error::WriteLine("Unable to load park: %s", hintPath.c_str());
}
gLoadKeepWindowsOpen = false;
return success;
}
void CloseParkSpecificWindows()
{
window_close_by_class(WC_CONSTRUCT_RIDE);
window_close_by_class(WC_DEMOLISH_RIDE_PROMPT);
window_close_by_class(WC_EDITOR_INVENTION_LIST_DRAG);
window_close_by_class(WC_EDITOR_INVENTION_LIST);
window_close_by_class(WC_EDITOR_OBJECT_SELECTION);
window_close_by_class(WC_EDTIOR_OBJECTIVE_OPTIONS);
window_close_by_class(WC_EDITOR_SCENARIO_OPTIONS);
window_close_by_class(WC_FINANCES);
window_close_by_class(WC_FIRE_PROMPT);
window_close_by_class(WC_GUEST_LIST);
window_close_by_class(WC_INSTALL_TRACK);
window_close_by_class(WC_PEEP);
window_close_by_class(WC_RIDE);
window_close_by_class(WC_RIDE_CONSTRUCTION);
window_close_by_class(WC_RIDE_LIST);
window_close_by_class(WC_SCENERY);
window_close_by_class(WC_STAFF);
window_close_by_class(WC_TRACK_DELETE_PROMPT);
window_close_by_class(WC_TRACK_DESIGN_LIST);
window_close_by_class(WC_TRACK_DESIGN_PLACE);
}
void PrepareParkForPlayback()
{
rct_window * w = window_get_main();
w->viewport_target_sprite = SPRITE_INDEX_NULL;
w->saved_view_x = gSavedViewX;
w->saved_view_y = gSavedViewY;
char zoomDifference = gSavedViewZoom - w->viewport->zoom;
w->viewport->zoom = gSavedViewZoom;
gCurrentRotation = gSavedViewRotation;
if (zoomDifference != 0)
{
if (zoomDifference < 0)
{
zoomDifference = -zoomDifference;
w->viewport->view_width >>= zoomDifference;
w->viewport->view_height >>= zoomDifference;
}
else
{
w->viewport->view_width <<= zoomDifference;
w->viewport->view_height <<= zoomDifference;
}
}
w->saved_view_x -= w->viewport->view_width >> 1;
w->saved_view_y -= w->viewport->view_height >> 1;
window_invalidate(w);
reset_sprite_spatial_index();
reset_all_sprite_quadrant_placements();
auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES);
context_broadcast_intent(&intent);
scenery_set_default_placement_configuration();
news_item_init_queue();
load_palette();
gScreenAge = 0;
gGameSpeed = 1;
}
/**
* Sets the map location to the given tile coordinates. Z is automatic.
* @param x X position in map tiles.
* @param y Y position in map tiles.
*/
void SetViewLocation(sint32 x, sint32 y)
{
// Update viewport
rct_window * w = window_get_main();
if (w != nullptr)
{
sint32 z = map_element_height(x, y);
// Prevent scroll adjustment due to window placement when in-game
auto oldScreenFlags = gScreenFlags;
gScreenFlags = SCREEN_FLAGS_TITLE_DEMO;
window_set_location(w, x, y, z);
gScreenFlags = oldScreenFlags;
viewport_update_position(w);
// Save known tile position in case of window resize
_lastScreenWidth = w->width;
_lastScreenHeight = w->height;
_viewCentreLocation.x = x;
_viewCentreLocation.y = y;
}
}
/**
* Fixes the view location for when the game window has changed size.
*/
void FixViewLocation()
{
rct_window * w = window_get_main();
if (w != nullptr)
{
if (w->width != _lastScreenWidth ||
w->height != _lastScreenHeight)
{
SetViewLocation(_viewCentreLocation.x, _viewCentreLocation.y);
}
}
}
};
ITitleSequencePlayer * CreateTitleSequencePlayer(IScenarioRepository * scenarioRepository)
{
return new TitleSequencePlayer(scenarioRepository);
}
extern "C"
{
bool gTestingTitleSequenceInGame = false;
sint32 title_sequence_player_get_current_position(ITitleSequencePlayer * player)
{
return player->GetCurrentPosition();
}
bool title_sequence_player_begin(ITitleSequencePlayer * player, uint32 titleSequenceId)
{
return player->Begin(titleSequenceId);
}
void title_sequence_player_reset(ITitleSequencePlayer * player)
{
player->Reset();
}
bool title_sequence_player_update(ITitleSequencePlayer * player)
{
return player->Update();
}
void title_sequence_player_seek(ITitleSequencePlayer * player, uint32 position)
{
player->Seek(position);
}
}