diff --git a/src/openrct2-ui/UiContext.cpp b/src/openrct2-ui/UiContext.cpp index 04d9dc39bf..858fa3dd50 100644 --- a/src/openrct2-ui/UiContext.cpp +++ b/src/openrct2-ui/UiContext.cpp @@ -710,7 +710,7 @@ public: { auto context = GetContext(); auto gameState = context->GetGameState(); - _titleSequencePlayer = CreateTitleSequencePlayer(*gameState); + _titleSequencePlayer = OpenRCT2::Title::CreateTitleSequencePlayer(*gameState); } return _titleSequencePlayer.get(); } diff --git a/src/openrct2-ui/scripting/ScTitleSequence.hpp b/src/openrct2-ui/scripting/ScTitleSequence.hpp index 60e0c4b4be..c2e61a04bf 100644 --- a/src/openrct2-ui/scripting/ScTitleSequence.hpp +++ b/src/openrct2-ui/scripting/ScTitleSequence.hpp @@ -24,20 +24,39 @@ # include # include # include +# include +# include namespace OpenRCT2::Scripting { + enum class TitleScript : uint8_t + { + Undefined = 0xFF, + Wait = 0, + Location, + Rotate, + Zoom, + Follow, + Restart, + Load, + End, + Speed, + Loop, + EndLoop, + LoadSc, + }; + static const DukEnumMap TitleScriptMap({ - { "load", TitleScript::Load }, - { "location", TitleScript::Location }, - { "rotate", TitleScript::Rotate }, - { "zoom", TitleScript::Zoom }, - { "follow", TitleScript::Follow }, - { "speed", TitleScript::Speed }, - { "wait", TitleScript::Wait }, - { "loadsc", TitleScript::LoadSc }, - { "restart", TitleScript::Restart }, - { "end", TitleScript::End }, + { OpenRCT2::Title::LoadParkCommand::ScriptingName, TitleScript::Load }, + { OpenRCT2::Title::SetLocationCommand::ScriptingName, TitleScript::Location }, + { OpenRCT2::Title::RotateViewCommand::ScriptingName, TitleScript::Rotate }, + { OpenRCT2::Title::SetZoomCommand::ScriptingName, TitleScript::Zoom }, + { OpenRCT2::Title::FollowEntityCommand::ScriptingName, TitleScript::Follow }, + { OpenRCT2::Title::SetSpeedCommand::ScriptingName, TitleScript::Speed }, + { OpenRCT2::Title::WaitCommand::ScriptingName, TitleScript::Wait }, + { OpenRCT2::Title::LoadScenarioCommand::ScriptingName, TitleScript::LoadSc }, + { OpenRCT2::Title::RestartCommand::ScriptingName, TitleScript::Restart }, + { OpenRCT2::Title::EndCommand::ScriptingName, TitleScript::End }, }); template<> DukValue ToDuk(duk_context* ctx, const TitleScript& value) @@ -45,43 +64,52 @@ namespace OpenRCT2::Scripting return ToDuk(ctx, TitleScriptMap[value]); } - template<> DukValue ToDuk(duk_context* ctx, const TitleCommand& value) + template<> DukValue ToDuk(duk_context* ctx, const OpenRCT2::Title::TitleCommand& value) { + using namespace OpenRCT2::Title; DukObject obj(ctx); - obj.Set("type", ToDuk(ctx, value.Type)); - switch (value.Type) - { - case TitleScript::Load: - obj.Set("index", value.SaveIndex); - break; - case TitleScript::Location: - obj.Set("x", value.Location.X); - obj.Set("y", value.Location.Y); - break; - case TitleScript::Rotate: - obj.Set("rotations", value.Rotations); - break; - case TitleScript::Zoom: - obj.Set("zoom", value.Zoom); - break; - case TitleScript::Follow: - if (value.Follow.SpriteIndex.IsNull()) - obj.Set("id", nullptr); - else - obj.Set("id", value.Follow.SpriteIndex.ToUnderlying()); - break; - case TitleScript::Speed: - obj.Set("speed", value.Speed); - break; - case TitleScript::Wait: - obj.Set("duration", value.Milliseconds); - break; - case TitleScript::LoadSc: - obj.Set("scenario", String::ToStringView(value.Scenario, sizeof(value.Scenario))); - break; - default: - break; - } + std::visit( + [&obj](auto&& command) { + using T = std::decay_t; + obj.Set("type", T::ScriptingName); + if constexpr (std::is_same_v) + { + obj.Set("index", command.SaveIndex); + } + else if constexpr (std::is_same_v) + { + obj.Set("x", command.Location.X); + obj.Set("y", command.Location.Y); + } + else if constexpr (std::is_same_v) + { + obj.Set("rotations", command.Rotations); + } + else if constexpr (std::is_same_v) + { + obj.Set("zoom", command.Zoom); + } + else if constexpr (std::is_same_v) + { + if (command.Follow.SpriteIndex.IsNull()) + obj.Set("id", nullptr); + else + obj.Set("id", command.Follow.SpriteIndex.ToUnderlying()); + } + else if constexpr (std::is_same_v) + { + obj.Set("speed", command.Speed); + } + else if constexpr (std::is_same_v) + { + obj.Set("duration", command.Milliseconds); + } + else if constexpr (std::is_same_v) + { + obj.Set("scenario", String::ToStringView(command.Scenario, sizeof(command.Scenario))); + } + }, + value); return obj.Take(); } @@ -92,47 +120,60 @@ namespace OpenRCT2::Scripting throw DukException() << "Invalid title command id"; } - template<> TitleCommand FromDuk(const DukValue& value) + template<> OpenRCT2::Title::TitleCommand FromDuk(const DukValue& value) { + using namespace OpenRCT2::Title; auto type = FromDuk(value["type"]); TitleCommand command{}; - command.Type = type; switch (type) { case TitleScript::Load: - command.SaveIndex = value["index"].as_int(); + command = LoadParkCommand{ static_cast(value["index"].as_int()) }; break; case TitleScript::Location: - command.Location.X = value["x"].as_int(); - command.Location.Y = value["y"].as_int(); + command = SetLocationCommand{ + static_cast(value["x"].as_int()), + static_cast(value["y"].as_int()), + }; break; case TitleScript::Rotate: - command.Rotations = value["rotations"].as_int(); + command = RotateViewCommand{ static_cast(value["rotations"].as_int()) }; break; case TitleScript::Zoom: - command.Zoom = value["zoom"].as_int(); + command = SetZoomCommand{ static_cast(value["zoom"].as_int()) }; break; case TitleScript::Follow: { auto dukId = value["id"]; if (dukId.type() == DukValue::Type::NUMBER) { - command.Follow.SpriteIndex = EntityId::FromUnderlying(dukId.as_int()); + command = FollowEntityCommand{ EntityId::FromUnderlying(dukId.as_int()) }; } else { - command.Follow.SpriteIndex = EntityId::GetNull(); + command = FollowEntityCommand{ EntityId::GetNull() }; } break; } case TitleScript::Speed: - command.Speed = value["speed"].as_int(); + command = SetSpeedCommand{ static_cast(value["speed"].as_int()) }; break; case TitleScript::Wait: - command.Milliseconds = value["duration"].as_int(); + command = WaitCommand{ static_cast(value["duration"].as_int()) }; break; case TitleScript::LoadSc: - String::Set(command.Scenario, sizeof(command.Scenario), value["scenario"].as_c_string()); + { + auto loadScenarioCommand = LoadScenarioCommand{}; + String::Set( + loadScenarioCommand.Scenario, sizeof(loadScenarioCommand.Scenario), value["scenario"].as_c_string()); + command = loadScenarioCommand; + break; + } + case TitleScript::Restart: + command = RestartCommand{}; + break; + case TitleScript::End: + command = EndCommand{}; break; default: break; @@ -164,7 +205,7 @@ namespace OpenRCT2::Scripting if (value == _fileName) return; - auto seq = LoadTitleSequence(_titleSequencePath); + auto seq = OpenRCT2::Title::LoadTitleSequence(_titleSequencePath); if (seq != nullptr) { // Check if name already in use @@ -183,27 +224,27 @@ namespace OpenRCT2::Scripting void delete_() { - auto seq = LoadTitleSequence(_titleSequencePath); + auto seq = OpenRCT2::Title::LoadTitleSequence(_titleSequencePath); if (seq != nullptr) { auto index = GetIndex(*seq, _fileName); if (index) { - TitleSequenceRemovePark(*seq, *index); - TitleSequenceSave(*seq); + OpenRCT2::Title::TitleSequenceRemovePark(*seq, *index); + OpenRCT2::Title::TitleSequenceSave(*seq); } } } void load() { - auto seq = LoadTitleSequence(_titleSequencePath); + auto seq = OpenRCT2::Title::LoadTitleSequence(_titleSequencePath); if (seq != nullptr) { auto index = GetIndex(*seq, _fileName); if (index) { - auto handle = TitleSequenceGetParkHandle(*seq, *index); + auto handle = OpenRCT2::Title::TitleSequenceGetParkHandle(*seq, *index); auto isScenario = ParkImporter::ExtensionIsScenario(handle->HintPath); try { @@ -246,7 +287,7 @@ namespace OpenRCT2::Scripting } private: - static std::optional GetIndex(const TitleSequence& seq, const std::string_view needle) + static std::optional GetIndex(const OpenRCT2::Title::TitleSequence& seq, const std::string_view needle) { for (size_t i = 0; i < seq.Saves.size(); i++) { @@ -327,7 +368,7 @@ namespace OpenRCT2::Scripting std::vector> parks_get() const { std::vector> result; - auto titleSeq = LoadTitleSequence(_path); + auto titleSeq = OpenRCT2::Title::LoadTitleSequence(_path); if (titleSeq != nullptr) { for (size_t i = 0; i < titleSeq->Saves.size(); i++) @@ -344,7 +385,7 @@ namespace OpenRCT2::Scripting auto ctx = scriptEngine.GetContext(); std::vector result; - auto titleSeq = LoadTitleSequence(_path); + auto titleSeq = OpenRCT2::Title::LoadTitleSequence(_path); if (titleSeq != nullptr) { for (const auto& command : titleSeq->Commands) @@ -357,23 +398,23 @@ namespace OpenRCT2::Scripting void commands_set(const std::vector& value) { - std::vector commands; + std::vector commands; for (const auto& v : value) { - auto command = FromDuk(v); + auto command = FromDuk(v); commands.push_back(std::move(command)); } - auto titleSeq = LoadTitleSequence(_path); + auto titleSeq = OpenRCT2::Title::LoadTitleSequence(_path); titleSeq->Commands = commands; - TitleSequenceSave(*titleSeq); + OpenRCT2::Title::TitleSequenceSave(*titleSeq); } void addPark(const std::string& path, const std::string& fileName) { - auto titleSeq = LoadTitleSequence(_path); - TitleSequenceAddPark(*titleSeq, path.c_str(), fileName.c_str()); - TitleSequenceSave(*titleSeq); + auto titleSeq = OpenRCT2::Title::LoadTitleSequence(_path); + OpenRCT2::Title::TitleSequenceAddPark(*titleSeq, path.c_str(), fileName.c_str()); + OpenRCT2::Title::TitleSequenceSave(*titleSeq); } std::shared_ptr clone(const std::string& name) const diff --git a/src/openrct2-ui/title/TitleSequencePlayer.cpp b/src/openrct2-ui/title/TitleSequencePlayer.cpp index 9101fb6aef..cde1ca3c7a 100644 --- a/src/openrct2-ui/title/TitleSequencePlayer.cpp +++ b/src/openrct2-ui/title/TitleSequencePlayer.cpp @@ -39,489 +39,377 @@ #include #include #include +#include -using namespace OpenRCT2; - -class TitleSequencePlayer final : public ITitleSequencePlayer +namespace OpenRCT2::Title { -private: - GameState& _gameState; - - std::unique_ptr _sequence; - int32_t _position = 0; - int32_t _waitCounter = 0; - - int32_t _lastScreenWidth = 0; - int32_t _lastScreenHeight = 0; - CoordsXY _viewCentreLocation = {}; - -public: - explicit TitleSequencePlayer(GameState& gameState) - : _gameState(gameState) + class TitleSequencePlayer final : public ITitleSequencePlayer { - } + private: + GameState& _gameState; - ~TitleSequencePlayer() override - { - Eject(); - } + std::unique_ptr _sequence; + int32_t _position = 0; + int32_t _waitCounter = 0; - int32_t GetCurrentPosition() const override - { - return _position; - } + int32_t _previousWindowWidth = 0; + int32_t _previousWindowHeight = 0; + ScreenCoordsXY _previousViewPosition = {}; - void Eject() override - { - _sequence = nullptr; - } - - bool Begin(size_t titleSequenceId) override - { - size_t numSequences = TitleSequenceManager::GetCount(); - if (titleSequenceId >= numSequences) + public: + explicit TitleSequencePlayer(GameState& gameState) + : _gameState(gameState) { - return false; } - auto seqItem = TitleSequenceManager::GetItem(titleSequenceId); - auto sequence = LoadTitleSequence(seqItem->Path); - if (sequence == nullptr) + ~TitleSequencePlayer() override { - return false; + Eject(); } - Eject(); - _sequence = std::move(sequence); - - Reset(); - return true; - } - - bool Update() override - { - int32_t entryPosition = _position; - FixViewLocation(); - - if (_sequence == nullptr) + int32_t GetCurrentPosition() const override { - SetViewLocation(TileCoordsXY(75, 75).ToCoordsXY()); - return false; + return _position; } - // Check that position is valid - if (_position >= static_cast(_sequence->Commands.size())) + void Eject() override { - _position = 0; - return false; + _sequence = nullptr; } - // Don't execute next command until we are done with the current wait command - if (_waitCounter != 0) + bool Begin(size_t titleSequenceId) override { - _waitCounter--; - if (_waitCounter == 0) + StoreCurrentViewLocation(); + + size_t numSequences = TitleSequenceManager::GetCount(); + if (titleSequenceId >= numSequences) { - const auto& command = _sequence->Commands[_position]; - if (command.Type == TitleScript::Wait) - { - IncrementPosition(); - } + return false; } + + auto seqItem = TitleSequenceManager::GetItem(titleSequenceId); + auto sequence = LoadTitleSequence(seqItem->Path); + if (sequence == nullptr) + { + return false; + } + + Eject(); + _sequence = std::move(sequence); + + Reset(); + return true; } - else + + bool Update() override { + RestoreViewLocationIfResized(); + + if (_sequence == nullptr) + { + return false; + } + + // Run commands in order, until we reach one that is not instantly done + int32_t entryPosition = _position; while (true) { - const auto& command = _sequence->Commands[_position]; - if (ExecuteCommand(command)) + auto& currentCommand = _sequence->Commands[_position]; + try { - if (command.Type == TitleScript::Wait) + int framesToWait = std::visit([&](auto& command) { return command(_waitCounter); }, currentCommand); + if (framesToWait > _waitCounter) { + _waitCounter++; break; } - if (command.Type != TitleScript::Restart) + + // TODO: Make the loading interface simpler so these blocks can be moved to their respective command classes + if (std::holds_alternative(currentCommand)) { - IncrementPosition(); + bool loadSuccess = false; + const auto saveIndex = std::get(currentCommand).SaveIndex; + auto parkHandle = TitleSequenceGetParkHandle(*_sequence, saveIndex); + if (parkHandle != nullptr) + { + game_notify_map_change(); + loadSuccess = LoadParkFromStream(parkHandle->Stream.get(), parkHandle->HintPath); + } + if (!loadSuccess) + { + if (_sequence->Saves.size() > saveIndex) + { + const auto& path = _sequence->Saves[saveIndex]; + throw std::domain_error("Failed to load: \"" + path + "\" for the title sequence."); + } + + throw std::out_of_range("Failed to load park; index out of range."); + } + + game_notify_map_changed(); } - if (_position == entryPosition) + else if (std::holds_alternative(currentCommand)) { - Console::Error::WriteLine("Infinite loop detected in title sequence."); - Console::Error::WriteLine(" A wait command may be missing."); - return false; + auto& scenarioName = std::get(currentCommand).Scenario; + bool loadSuccess = false; + auto scenario = GetScenarioRepository()->GetByInternalName(scenarioName); + if (scenario != nullptr) + { + game_notify_map_change(); + loadSuccess = LoadParkFromFile(scenario->path); + } + + if (!loadSuccess) + { + auto message = std::string("Failed to load: \"") + scenarioName + " for the title sequence."; + throw std::domain_error(message); + } + + game_notify_map_changed(); } } - else + catch (std::exception& e) { - if (!SkipToNextLoadCommand() || _position == entryPosition) - { - Console::Error::WriteLine("Unable to load any parks from %s.", _sequence->Name.c_str()); - return false; - } + const char* commandName = std::visit( + [](auto&& command) { return std::decay_t::Name; }, currentCommand); + Console::Error::WriteLine("%s (command %i) failed with error: %s", commandName, _position, e.what()); + Console::Error::WriteLine(" Skipping to the next command."); + } + + IncrementPosition(); + + if (_position == entryPosition) + { + Console::Error::WriteLine("Infinite loop detected in title sequence."); + Console::Error::WriteLine(" A wait command may be missing."); + return false; } } - } - return true; - } - void Reset() override - { - _position = 0; - _waitCounter = 0; - } + // Store current window size and screen position in case the window resizes and the main focus changes + StoreCurrentViewLocation(); - void Seek(int32_t targetPosition) override - { - if (targetPosition < 0 || targetPosition >= static_cast(_sequence->Commands.size())) - { - throw std::runtime_error("Invalid position."); - } - if (_position >= targetPosition) - { - Reset(); + return true; } - if (_sequence->Commands[targetPosition].Type == TitleScript::Restart) - { - targetPosition = 0; - } - // Set position to the last LOAD command before target position - for (int32_t 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()) - { - _gameState.UpdateLogic(); - } - else - { - break; - } - } - - gInUpdateCode = false; - - _waitCounter = 0; - } - -private: - void IncrementPosition() - { - _position++; - if (_position >= static_cast(_sequence->Commands.size())) + void Reset() override { _position = 0; + _waitCounter = 0; } - } - bool SkipToNextLoadCommand() - { - int32_t entryPosition = _position; - const TitleCommand* command; - do + void Seek(int32_t targetPosition) override { - IncrementPosition(); - command = &_sequence->Commands[_position]; - } while (!TitleSequenceIsLoadCommand(*command) && _position != entryPosition); - return _position != entryPosition; - } - - bool ExecuteCommand(const TitleCommand& command) - { - switch (command.Type) - { - case TitleScript::End: - _waitCounter = 1; - break; - case TitleScript::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 = std::max( - 1, command.Milliseconds / static_cast(GAME_UPDATE_TIME_MS * 1000.0f)); - break; - case TitleScript::Location: + if (targetPosition < 0 || targetPosition >= static_cast(_sequence->Commands.size())) { - auto loc = TileCoordsXY(command.Location.X, command.Location.Y).ToCoordsXY().ToTileCentre(); - SetViewLocation(loc); - break; + throw std::runtime_error("Invalid position."); } - case TitleScript::Undefined: - break; - case TitleScript::Loop: - break; - case TitleScript::EndLoop: - break; - case TitleScript::Rotate: - RotateView(command.Rotations); - break; - case TitleScript::Zoom: - SetViewZoom(ZoomLevel{ static_cast(command.Zoom) }); - break; - case TitleScript::Speed: - gGameSpeed = std::clamp(command.Speed, 1, 4); - break; - case TitleScript::Follow: - FollowSprite(command.Follow.SpriteIndex); - break; - case TitleScript::Restart: + if (_position >= targetPosition) + { Reset(); - break; - case TitleScript::Load: + } + + if (std::holds_alternative(_sequence->Commands[targetPosition])) { - bool loadSuccess = false; - uint8_t saveIndex = command.SaveIndex; - auto parkHandle = TitleSequenceGetParkHandle(*_sequence, saveIndex); - if (parkHandle != nullptr) + targetPosition = 0; + } + + // Set position to the last LOAD command before target position + for (int32_t i = targetPosition; i >= 0; i--) + { + const TitleCommand& command = _sequence->Commands[i]; + if ((_position == i && _position != targetPosition) || TitleSequenceIsLoadCommand(command)) { - game_notify_map_change(); - loadSuccess = LoadParkFromStream(parkHandle->Stream.get(), parkHandle->HintPath); + // Break if we have a new load command or if we're already in the range of the correct load command + _position = i; + break; } - if (loadSuccess) + } + + // Keep updating until we reach target position + gInUpdateCode = true; + + while (_position < targetPosition) + { + if (Update()) { - game_notify_map_changed(); + _gameState.UpdateLogic(); } else { - if (_sequence->Saves.size() > saveIndex) - { - const auto& path = _sequence->Saves[saveIndex]; - Console::Error::WriteLine("Failed to load: \"%s\" for the title sequence.", path.c_str()); - } - return false; + break; } - break; } - case TitleScript::LoadSc: + + gInUpdateCode = false; + + _waitCounter = 0; + } + + private: + void IncrementPosition() + { + _position++; + if (_position >= static_cast(_sequence->Commands.size())) { - bool loadSuccess = false; - auto scenario = GetScenarioRepository()->GetByInternalName(command.Scenario); - if (scenario != nullptr) + _position = 0; + } + _waitCounter = 0; + } + + bool SkipToNextLoadCommand() + { + int32_t entryPosition = _position; + const TitleCommand* command; + do + { + IncrementPosition(); + command = &_sequence->Commands[_position]; + } while (!TitleSequenceIsLoadCommand(*command) && _position != entryPosition); + return _position != entryPosition; + } + + bool LoadParkFromFile(const utf8* path) + { + log_verbose("TitleSequencePlayer::LoadParkFromFile(%s)", path); + bool success = false; + try + { + if (gPreviewingTitleSequenceInGame) { - game_notify_map_change(); - loadSuccess = LoadParkFromFile(scenario->path); - } - if (loadSuccess) - { - game_notify_map_changed(); + gLoadKeepWindowsOpen = true; + CloseParkSpecificWindows(); + context_load_park_from_file(path); } else { - Console::Error::WriteLine("Failed to load: \"%s\" for the title sequence.", command.Scenario); - return false; + auto parkImporter = ParkImporter::Create(path); + auto result = parkImporter->Load(path); + + auto& objectManager = GetContext()->GetObjectManager(); + objectManager.LoadObjects(result.RequiredObjects); + + parkImporter->Import(); } - break; + PrepareParkForPlayback(); + success = true; } - } - return true; - } - - void SetViewZoom(ZoomLevel zoom) - { - rct_window* w = window_get_main(); - if (w != nullptr && w->viewport != nullptr) - { - window_zoom_set(w, zoom, false); - } - } - - void RotateView(uint32_t count) - { - rct_window* w = window_get_main(); - if (w != nullptr) - { - for (uint32_t i = 0; i < count; i++) + catch (const std::exception&) { - window_rotate_camera(w, 1); + Console::Error::WriteLine("Unable to load park: %s", path); } + gLoadKeepWindowsOpen = false; + return success; } - } - void FollowSprite(EntityId spriteIndex) - { - rct_window* w = window_get_main(); - if (w != nullptr) + /** + * @param stream The stream to read the park data from. + * @param hintPath Hint path, the extension is grabbed to determine what importer to use. + */ + bool LoadParkFromStream(OpenRCT2::IStream* stream, const std::string& hintPath) { - window_follow_sprite(w, spriteIndex); - } - } - - void UnfollowSprite() - { - rct_window* w = window_get_main(); - if (w != nullptr) - { - window_unfollow_sprite(w); - } - } - - bool LoadParkFromFile(const utf8* path) - { - log_verbose("TitleSequencePlayer::LoadParkFromFile(%s)", path); - bool success = false; - try - { - if (gPreviewingTitleSequenceInGame) + log_verbose("TitleSequencePlayer::LoadParkFromStream(%s)", hintPath.c_str()); + bool success = false; + try { - gLoadKeepWindowsOpen = true; - CloseParkSpecificWindows(); - context_load_park_from_file(path); + if (gPreviewingTitleSequenceInGame) + { + gLoadKeepWindowsOpen = true; + CloseParkSpecificWindows(); + context_load_park_from_stream(stream); + } + else + { + bool isScenario = ParkImporter::ExtensionIsScenario(hintPath); + auto parkImporter = ParkImporter::Create(hintPath); + auto result = parkImporter->LoadFromStream(stream, isScenario); + + auto& objectManager = GetContext()->GetObjectManager(); + objectManager.LoadObjects(result.RequiredObjects); + + parkImporter->Import(); + } + PrepareParkForPlayback(); + success = true; } - else + catch (const std::exception&) { - auto parkImporter = ParkImporter::Create(path); - auto result = parkImporter->Load(path); - - auto& objectManager = GetContext()->GetObjectManager(); - objectManager.LoadObjects(result.RequiredObjects); - - parkImporter->Import(); + Console::Error::WriteLine("Unable to load park: %s", hintPath.c_str()); } - PrepareParkForPlayback(); - success = true; + gLoadKeepWindowsOpen = false; + return success; } - catch (const std::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 hintPath Hint path, the extension is grabbed to determine what importer to use. - */ - bool LoadParkFromStream(OpenRCT2::IStream* stream, const std::string& hintPath) - { - log_verbose("TitleSequencePlayer::LoadParkFromStream(%s)", hintPath.c_str()); - bool success = false; - try + void CloseParkSpecificWindows() { - if (gPreviewingTitleSequenceInGame) + 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_EDITOR_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() + { + auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); + windowManager->SetMainView(gSavedView, gSavedViewZoom, gSavedViewRotation); + ResetEntitySpatialIndices(); + reset_all_sprite_quadrant_placements(); + auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES); + context_broadcast_intent(&intent); + scenery_set_default_placement_configuration(); + News::InitQueue(); + load_palette(); + gScreenAge = 0; + gGamePaused = false; + gGameSpeed = 1; + } + + void StoreCurrentViewLocation() + { + rct_window* w = window_get_main(); + if (w != nullptr && w->viewport_smart_follow_sprite.IsNull()) { - gLoadKeepWindowsOpen = true; - CloseParkSpecificWindows(); - context_load_park_from_stream(stream); + _previousWindowWidth = w->width; + _previousWindowHeight = w->height; + _previousViewPosition = w->savedViewPos; } - else + } + + /** + * Fixes the view location for when the game window has changed size. + */ + void RestoreViewLocationIfResized() + { + rct_window* w = window_get_main(); + if (w != nullptr && w->viewport_smart_follow_sprite.IsNull()) { - bool isScenario = ParkImporter::ExtensionIsScenario(hintPath); - auto parkImporter = ParkImporter::Create(hintPath); - auto result = parkImporter->LoadFromStream(stream, isScenario); - - auto& objectManager = GetContext()->GetObjectManager(); - objectManager.LoadObjects(result.RequiredObjects); - - parkImporter->Import(); - } - PrepareParkForPlayback(); - success = true; - } - catch (const std::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_EDITOR_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() - { - auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); - windowManager->SetMainView(gSavedView, gSavedViewZoom, gSavedViewRotation); - ResetEntitySpatialIndices(); - reset_all_sprite_quadrant_placements(); - auto intent = Intent(INTENT_ACTION_REFRESH_NEW_RIDES); - context_broadcast_intent(&intent); - scenery_set_default_placement_configuration(); - News::InitQueue(); - load_palette(); - gScreenAge = 0; - gGamePaused = false; - gGameSpeed = 1; - } - - /** - * Sets the map location to the given (big) coordinates. Z is automatic. - * @param loc X and Y position in big coordinates. - */ - void SetViewLocation(const CoordsXY& loc) - { - // Update viewport - rct_window* w = window_get_main(); - if (w != nullptr) - { - int32_t z = tile_element_height(loc); - - // Prevent scroll adjustment due to window placement when in-game - auto oldScreenFlags = gScreenFlags; - gScreenFlags = SCREEN_FLAGS_TITLE_DEMO; - w->SetLocation({ loc, z }); - gScreenFlags = oldScreenFlags; - - viewport_update_position(w); - - // Save known tile position in case of window resize - _lastScreenWidth = w->width; - _lastScreenHeight = w->height; - _viewCentreLocation = loc; - } - } - - /** - * Fixes the view location for when the game window has changed size. - */ - void FixViewLocation() - { - rct_window* w = window_get_main(); - if (w != nullptr && w->viewport_smart_follow_sprite.IsNull()) - { - if (w->width != _lastScreenWidth || w->height != _lastScreenHeight) - { - SetViewLocation(_viewCentreLocation); + if (w->width != _previousWindowWidth || w->height != _previousWindowHeight) + { + w->savedViewPos.x += (_previousWindowWidth - w->width) / 2; + w->savedViewPos.y += (_previousWindowHeight - w->height) / 2; + } } } - } -}; + }; -std::unique_ptr CreateTitleSequencePlayer(GameState& gameState) -{ - return std::make_unique(gameState); -} + std::unique_ptr CreateTitleSequencePlayer(GameState& gameState) + { + return std::make_unique(gameState); + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2-ui/title/TitleSequencePlayer.h b/src/openrct2-ui/title/TitleSequencePlayer.h index 1d38e714fc..ed868235b3 100644 --- a/src/openrct2-ui/title/TitleSequencePlayer.h +++ b/src/openrct2-ui/title/TitleSequencePlayer.h @@ -18,6 +18,9 @@ struct IScenarioRepository; namespace OpenRCT2 { class GameState; -} -[[nodiscard]] std::unique_ptr CreateTitleSequencePlayer(OpenRCT2::GameState& gameState); + namespace Title + { + [[nodiscard]] std::unique_ptr CreateTitleSequencePlayer(GameState& gameState); + } // namespace Title +} // namespace OpenRCT2 diff --git a/src/openrct2/interface/Window.h b/src/openrct2/interface/Window.h index ed42c0ab67..488ba30c0e 100644 --- a/src/openrct2/interface/Window.h +++ b/src/openrct2/interface/Window.h @@ -27,7 +27,6 @@ struct rct_drawpixelinfo; struct rct_window; union rct_window_event; struct track_design_file_ref; -struct TitleSequence; struct TextInputSession; struct scenario_index_entry; diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 0249cf33b8..4b3cbcb56f 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -497,6 +497,16 @@ + + + + + + + + + + @@ -941,6 +951,16 @@ + + + + + + + + + + @@ -975,4 +995,4 @@ - + \ No newline at end of file diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index 4ea7c497cd..7e162cbc28 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -46,7 +46,7 @@ namespace OpenRCT2 namespace OpenRCT2::Scripting { - static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 56; + static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 57; // Versions marking breaking changes. static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33; diff --git a/src/openrct2/title/Command/End.cpp b/src/openrct2/title/Command/End.cpp new file mode 100644 index 0000000000..467cc28bd5 --- /dev/null +++ b/src/openrct2/title/Command/End.cpp @@ -0,0 +1,19 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "End.h" + +namespace OpenRCT2::Title +{ + int16_t EndCommand::operator()(int16_t timer) + { + // The end command is used as a tag, no logic required here. + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/End.h b/src/openrct2/title/Command/End.h new file mode 100644 index 0000000000..9f40acefe3 --- /dev/null +++ b/src/openrct2/title/Command/End.h @@ -0,0 +1,23 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include + +namespace OpenRCT2::Title +{ + struct EndCommand + { + static constexpr const char* Name = "End Command"; + static constexpr const char* ScriptingName = "end"; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/FollowEntity.cpp b/src/openrct2/title/Command/FollowEntity.cpp new file mode 100644 index 0000000000..83d4625bca --- /dev/null +++ b/src/openrct2/title/Command/FollowEntity.cpp @@ -0,0 +1,26 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "FollowEntity.h" + +#include "../../interface/Window.h" + +namespace OpenRCT2::Title +{ + int16_t FollowEntityCommand::operator()(int16_t timer) + { + auto* w = window_get_main(); + if (w != nullptr) + { + window_follow_sprite(w, Follow.SpriteIndex); + } + + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/FollowEntity.h b/src/openrct2/title/Command/FollowEntity.h new file mode 100644 index 0000000000..0fbb7a5908 --- /dev/null +++ b/src/openrct2/title/Command/FollowEntity.h @@ -0,0 +1,33 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include "../../Identifiers.h" +#include "../../core/String.hpp" +#include "../../localisation/Localisation.h" + +#include + +namespace OpenRCT2::Title +{ + struct FollowEntityCommand + { + static constexpr const char* Name = "Follow Entity Command"; + static constexpr const char* ScriptingName = "follow"; + + struct + { + EntityId SpriteIndex{ EntityId::GetNull() }; + utf8 SpriteName[USER_STRING_MAX_LENGTH]{}; + } Follow; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/LoadPark.cpp b/src/openrct2/title/Command/LoadPark.cpp new file mode 100644 index 0000000000..ff83e2aa65 --- /dev/null +++ b/src/openrct2/title/Command/LoadPark.cpp @@ -0,0 +1,19 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "LoadPark.h" + +namespace OpenRCT2::Title +{ + int16_t LoadParkCommand::operator()(int16_t timer) + { + // Park loading is currently handled by the title sequence player + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/LoadPark.h b/src/openrct2/title/Command/LoadPark.h new file mode 100644 index 0000000000..8198f8a184 --- /dev/null +++ b/src/openrct2/title/Command/LoadPark.h @@ -0,0 +1,25 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include + +namespace OpenRCT2::Title +{ + struct LoadParkCommand + { + static constexpr const char* Name = "Load Park Command"; + static constexpr const char* ScriptingName = "load"; + + uint8_t SaveIndex{}; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/LoadScenario.cpp b/src/openrct2/title/Command/LoadScenario.cpp new file mode 100644 index 0000000000..fb9789d541 --- /dev/null +++ b/src/openrct2/title/Command/LoadScenario.cpp @@ -0,0 +1,19 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "LoadScenario.h" + +namespace OpenRCT2::Title +{ + int16_t LoadScenarioCommand::operator()(int16_t timer) + { + // Scenario loading is currently handled by the title sequence player + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/LoadScenario.h b/src/openrct2/title/Command/LoadScenario.h new file mode 100644 index 0000000000..62b96e90e7 --- /dev/null +++ b/src/openrct2/title/Command/LoadScenario.h @@ -0,0 +1,29 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include "../../core/String.hpp" + +#include + +#define TITLE_COMMAND_SCENARIO_LENGTH 64 + +namespace OpenRCT2::Title +{ + struct LoadScenarioCommand + { + static constexpr const char* Name = "Load Scenario Command"; + static constexpr const char* ScriptingName = "loadsc"; + + utf8 Scenario[TITLE_COMMAND_SCENARIO_LENGTH]{}; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/Restart.cpp b/src/openrct2/title/Command/Restart.cpp new file mode 100644 index 0000000000..4473ae48f6 --- /dev/null +++ b/src/openrct2/title/Command/Restart.cpp @@ -0,0 +1,19 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "Restart.h" + +namespace OpenRCT2::Title +{ + int16_t RestartCommand::operator()(int16_t timer) + { + // The restart command is used as a tag, no logic required here. + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/Restart.h b/src/openrct2/title/Command/Restart.h new file mode 100644 index 0000000000..45c76af956 --- /dev/null +++ b/src/openrct2/title/Command/Restart.h @@ -0,0 +1,23 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include + +namespace OpenRCT2::Title +{ + struct RestartCommand + { + static constexpr const char* Name = "Restart Command"; + static constexpr const char* ScriptingName = "restart"; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/RotateView.cpp b/src/openrct2/title/Command/RotateView.cpp new file mode 100644 index 0000000000..448e1455de --- /dev/null +++ b/src/openrct2/title/Command/RotateView.cpp @@ -0,0 +1,29 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "RotateView.h" + +#include "../../interface/Window.h" + +namespace OpenRCT2::Title +{ + int16_t RotateViewCommand::operator()(int16_t timer) + { + rct_window* w = window_get_main(); + if (w != nullptr) + { + for (uint_fast8_t i = 0; i < Rotations; i++) + { + window_rotate_camera(w, 1); + } + } + + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/RotateView.h b/src/openrct2/title/Command/RotateView.h new file mode 100644 index 0000000000..c3bf0bb423 --- /dev/null +++ b/src/openrct2/title/Command/RotateView.h @@ -0,0 +1,25 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include + +namespace OpenRCT2::Title +{ + struct RotateViewCommand + { + static constexpr const char* Name = "Rotate View Command"; + static constexpr const char* ScriptingName = "rotate"; + + uint8_t Rotations{}; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/SetLocation.cpp b/src/openrct2/title/Command/SetLocation.cpp new file mode 100644 index 0000000000..c5548f453f --- /dev/null +++ b/src/openrct2/title/Command/SetLocation.cpp @@ -0,0 +1,38 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "SetLocation.h" + +#include "../../OpenRCT2.h" +#include "../../interface/Window.h" +#include "../../interface/Window_internal.h" +#include "../../world/Map.h" + +namespace OpenRCT2::Title +{ + int16_t SetLocationCommand::operator()(int16_t timer) + { + rct_window* w = window_get_main(); + if (w != nullptr) + { + auto loc = TileCoordsXY(Location.X, Location.Y).ToCoordsXY().ToTileCentre(); + int32_t z = tile_element_height(loc); + + // Prevent scroll adjustment due to window placement when in-game + auto oldScreenFlags = gScreenFlags; + gScreenFlags = SCREEN_FLAGS_TITLE_DEMO; + w->SetLocation({ loc, z }); + gScreenFlags = oldScreenFlags; + + viewport_update_position(w); + } + + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/SetLocation.h b/src/openrct2/title/Command/SetLocation.h new file mode 100644 index 0000000000..639e47e043 --- /dev/null +++ b/src/openrct2/title/Command/SetLocation.h @@ -0,0 +1,30 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include + +namespace OpenRCT2::Title +{ + struct SetLocationCommand + { + static constexpr const char* Name = "Set Location Command"; + static constexpr const char* ScriptingName = "location"; + + // TODO: Use TileCoordsXY instead + struct + { + uint8_t X; + uint8_t Y; + } Location; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/SetSpeed.cpp b/src/openrct2/title/Command/SetSpeed.cpp new file mode 100644 index 0000000000..40189530d0 --- /dev/null +++ b/src/openrct2/title/Command/SetSpeed.cpp @@ -0,0 +1,24 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "SetSpeed.h" + +#include "../../Game.h" + +#include + +namespace OpenRCT2::Title +{ + int16_t SetSpeedCommand::operator()(int16_t timer) + { + gGameSpeed = std::clamp(Speed, 1, 4); + + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/SetSpeed.h b/src/openrct2/title/Command/SetSpeed.h new file mode 100644 index 0000000000..36d5e7daf0 --- /dev/null +++ b/src/openrct2/title/Command/SetSpeed.h @@ -0,0 +1,25 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include + +namespace OpenRCT2::Title +{ + struct SetSpeedCommand + { + static constexpr const char* Name = "Set Speed Command"; + static constexpr const char* ScriptingName = "speed"; + + uint8_t Speed{}; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/SetZoom.cpp b/src/openrct2/title/Command/SetZoom.cpp new file mode 100644 index 0000000000..8a56b760e6 --- /dev/null +++ b/src/openrct2/title/Command/SetZoom.cpp @@ -0,0 +1,27 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "SetZoom.h" + +#include "../../interface/Window.h" +#include "../../interface/ZoomLevel.h" + +namespace OpenRCT2::Title +{ + int16_t SetZoomCommand::operator()(int16_t timer) + { + rct_window* w = window_get_main(); + if (w != nullptr) + { + window_zoom_set(w, ZoomLevel{ static_cast(Zoom) }, false); + } + + return 0; + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/SetZoom.h b/src/openrct2/title/Command/SetZoom.h new file mode 100644 index 0000000000..70374fac86 --- /dev/null +++ b/src/openrct2/title/Command/SetZoom.h @@ -0,0 +1,26 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include + +namespace OpenRCT2::Title +{ + struct SetZoomCommand + { + static constexpr const char* Name = "Set Zoom Command"; + static constexpr const char* ScriptingName = "zoom"; + + // TODO: Use ZoomLevel instead + uint8_t Zoom{}; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/Wait.cpp b/src/openrct2/title/Command/Wait.cpp new file mode 100644 index 0000000000..ef06225a5f --- /dev/null +++ b/src/openrct2/title/Command/Wait.cpp @@ -0,0 +1,23 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "Wait.h" + +#include "../../Context.h" + +#include + +namespace OpenRCT2::Title +{ + int16_t WaitCommand::operator()(int16_t timer) + { + // Return number of game ticks this wait command lasts + return std::max(1, GAME_UPDATE_FPS * Milliseconds / 1000); + } +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/Command/Wait.h b/src/openrct2/title/Command/Wait.h new file mode 100644 index 0000000000..9bc216955c --- /dev/null +++ b/src/openrct2/title/Command/Wait.h @@ -0,0 +1,25 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#include + +namespace OpenRCT2::Title +{ + struct WaitCommand + { + static constexpr const char* Name = "Wait Command"; + static constexpr const char* ScriptingName = "wait"; + + uint16_t Milliseconds{}; + + int16_t operator()(int16_t timer); + }; +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/TitleSequence.cpp b/src/openrct2/title/TitleSequence.cpp index b828387199..35bc7c1fed 100644 --- a/src/openrct2/title/TitleSequence.cpp +++ b/src/openrct2/title/TitleSequence.cpp @@ -29,547 +29,562 @@ #include #include #include +#include +#include +#include +#include #include -static std::vector GetSaves(const std::string& path); -static std::vector GetSaves(IZipArchive* zip); -static std::vector LegacyScriptRead(const std::vector& script, std::vector saves); -static void LegacyScriptGetLine(OpenRCT2::IStream* stream, std::vector>& parts); -static std::vector ReadScriptFile(const std::string& path); -static std::string LegacyScriptWrite(const TitleSequence& seq); - -std::unique_ptr CreateTitleSequence() +namespace OpenRCT2::Title { - return std::make_unique(); -} + static std::vector GetSaves(const std::string& path); + static std::vector GetSaves(IZipArchive* zip); + static std::vector LegacyScriptRead(const std::vector& script, std::vector saves); + static void LegacyScriptGetLine(OpenRCT2::IStream* stream, std::vector>& parts); + static std::vector ReadScriptFile(const std::string& path); + static std::string LegacyScriptWrite(const TitleSequence& seq); -std::unique_ptr LoadTitleSequence(const std::string& path) -{ - std::vector script; - std::vector saves; - bool isZip; - - log_verbose("Loading title sequence: %s", path.c_str()); - - auto ext = Path::GetExtension(path); - if (String::Equals(ext, TITLE_SEQUENCE_EXTENSION)) + std::unique_ptr CreateTitleSequence() { - auto zip = Zip::TryOpen(path, ZIP_ACCESS::READ); - if (zip == nullptr) - { - Console::Error::WriteLine("Unable to open '%s'", path.c_str()); - return nullptr; - } - - script = zip->GetFileData("script.txt"); - if (script.empty()) - { - Console::Error::WriteLine("Unable to open script.txt in '%s'", path.c_str()); - return nullptr; - } - - saves = GetSaves(zip.get()); - isZip = true; - } - else - { - auto scriptPath = Path::Combine(path, u8"script.txt"); - script = ReadScriptFile(scriptPath); - if (script.empty()) - { - Console::Error::WriteLine("Unable to open '%s'", scriptPath.c_str()); - return nullptr; - } - - saves = GetSaves(path); - isZip = false; + return std::make_unique(); } - auto commands = LegacyScriptRead(script, saves); - - auto seq = CreateTitleSequence(); - seq->Name = Path::GetFileNameWithoutExtension(path); - seq->Path = path; - seq->Saves = saves; - seq->Commands = commands; - seq->IsZip = isZip; - return seq; -} - -std::unique_ptr TitleSequenceGetParkHandle(const TitleSequence& seq, size_t index) -{ - std::unique_ptr handle; - if (index < seq.Saves.size()) + std::unique_ptr LoadTitleSequence(const std::string& path) { - const auto& filename = seq.Saves[index]; - if (seq.IsZip) + std::vector script; + std::vector saves; + bool isZip; + + log_verbose("Loading title sequence: %s", path.c_str()); + + auto ext = Path::GetExtension(path); + if (String::Equals(ext, TITLE_SEQUENCE_EXTENSION)) { - auto zip = Zip::TryOpen(seq.Path, ZIP_ACCESS::READ); - if (zip != nullptr) + auto zip = Zip::TryOpen(path, ZIP_ACCESS::READ); + if (zip == nullptr) { - auto data = zip->GetFileData(filename); - auto ms = std::make_unique(); - ms->Write(data.data(), data.size()); - ms->SetPosition(0); + Console::Error::WriteLine("Unable to open '%s'", path.c_str()); + return nullptr; + } - handle = std::make_unique(); - handle->Stream = std::move(ms); - handle->HintPath = filename; + script = zip->GetFileData("script.txt"); + if (script.empty()) + { + Console::Error::WriteLine("Unable to open script.txt in '%s'", path.c_str()); + return nullptr; + } + + saves = GetSaves(zip.get()); + isZip = true; + } + else + { + auto scriptPath = Path::Combine(path, u8"script.txt"); + script = ReadScriptFile(scriptPath); + if (script.empty()) + { + Console::Error::WriteLine("Unable to open '%s'", scriptPath.c_str()); + return nullptr; + } + + saves = GetSaves(path); + isZip = false; + } + + auto commands = LegacyScriptRead(script, saves); + + auto seq = OpenRCT2::Title::CreateTitleSequence(); + seq->Name = Path::GetFileNameWithoutExtension(path); + seq->Path = path; + seq->Saves = saves; + seq->Commands = commands; + seq->IsZip = isZip; + return seq; + } + + std::unique_ptr TitleSequenceGetParkHandle(const TitleSequence& seq, size_t index) + { + std::unique_ptr handle; + if (index < seq.Saves.size()) + { + const auto& filename = seq.Saves[index]; + if (seq.IsZip) + { + auto zip = Zip::TryOpen(seq.Path, ZIP_ACCESS::READ); + if (zip != nullptr) + { + auto data = zip->GetFileData(filename); + auto ms = std::make_unique(); + ms->Write(data.data(), data.size()); + ms->SetPosition(0); + + handle = std::make_unique(); + handle->Stream = std::move(ms); + handle->HintPath = filename; + } + else + { + Console::Error::WriteLine( + "Failed to open zipped path '%s' from zip '%s'", filename.c_str(), seq.Path.c_str()); + } } else { - Console::Error::WriteLine("Failed to open zipped path '%s' from zip '%s'", filename.c_str(), seq.Path.c_str()); + auto absolutePath = Path::Combine(seq.Path, filename); + std::unique_ptr fileStream = nullptr; + try + { + fileStream = std::make_unique(absolutePath, OpenRCT2::FILE_MODE_OPEN); + } + catch (const IOException& exception) + { + Console::Error::WriteLine(exception.what()); + } + + if (fileStream != nullptr) + { + handle = std::make_unique(); + handle->Stream = std::move(fileStream); + handle->HintPath = filename; + } } } - else - { - auto absolutePath = Path::Combine(seq.Path, filename); - std::unique_ptr fileStream = nullptr; - try - { - fileStream = std::make_unique(absolutePath, OpenRCT2::FILE_MODE_OPEN); - } - catch (const IOException& exception) - { - Console::Error::WriteLine(exception.what()); - } - - if (fileStream != nullptr) - { - handle = std::make_unique(); - handle->Stream = std::move(fileStream); - handle->HintPath = filename; - } - } - } - return handle; -} - -bool TitleSequenceSave(const TitleSequence& seq) -{ - try - { - auto script = LegacyScriptWrite(seq); - if (seq.IsZip) - { - auto fdata = std::vector(script.begin(), script.end()); - auto zip = Zip::Open(seq.Path, ZIP_ACCESS::WRITE); - zip->SetFileData("script.txt", std::move(fdata)); - } - else - { - auto scriptPath = Path::Combine(seq.Path, u8"script.txt"); - File::WriteAllBytes(scriptPath, script.data(), script.size()); - } - return true; - } - catch (const std::exception&) - { - return false; - } -} - -bool TitleSequenceAddPark(TitleSequence& seq, const utf8* path, const utf8* name) -{ - // Get new save index - auto it = std::find(seq.Saves.begin(), seq.Saves.end(), path); - if (it == seq.Saves.end()) - { - seq.Saves.push_back(name); + return handle; } - if (seq.IsZip) + bool TitleSequenceSave(const TitleSequence& seq) { try { - auto fdata = File::ReadAllBytes(path); + auto script = LegacyScriptWrite(seq); + if (seq.IsZip) + { + auto fdata = std::vector(script.begin(), script.end()); + auto zip = Zip::Open(seq.Path, ZIP_ACCESS::WRITE); + zip->SetFileData("script.txt", std::move(fdata)); + } + else + { + auto scriptPath = Path::Combine(seq.Path, u8"script.txt"); + File::WriteAllBytes(scriptPath, script.data(), script.size()); + } + return true; + } + catch (const std::exception&) + { + return false; + } + } + + bool TitleSequenceAddPark(TitleSequence& seq, const utf8* path, const utf8* name) + { + // Get new save index + auto it = std::find(seq.Saves.begin(), seq.Saves.end(), path); + if (it == seq.Saves.end()) + { + seq.Saves.push_back(name); + } + + if (seq.IsZip) + { + try + { + auto fdata = File::ReadAllBytes(path); + auto zip = Zip::TryOpen(seq.Path, ZIP_ACCESS::WRITE); + if (zip == nullptr) + { + Console::Error::WriteLine("Unable to open '%s'", seq.Path.c_str()); + return false; + } + zip->SetFileData(name, std::move(fdata)); + } + catch (const std::exception& ex) + { + Console::Error::WriteLine(ex.what()); + } + } + else + { + // Determine destination path + auto dstPath = Path::Combine(seq.Path, name); + if (!File::Copy(path, dstPath, true)) + { + Console::Error::WriteLine("Unable to copy '%s' to '%s'", path, dstPath.c_str()); + return false; + } + } + return true; + } + + bool TitleSequenceRenamePark(TitleSequence& seq, size_t index, const utf8* name) + { + Guard::Assert(index < seq.Saves.size(), GUARD_LINE); + + auto& oldRelativePath = seq.Saves[index]; + if (seq.IsZip) + { auto zip = Zip::TryOpen(seq.Path, ZIP_ACCESS::WRITE); if (zip == nullptr) { Console::Error::WriteLine("Unable to open '%s'", seq.Path.c_str()); return false; } - zip->SetFileData(name, std::move(fdata)); + zip->RenameFile(oldRelativePath, name); } - catch (const std::exception& ex) + else { - Console::Error::WriteLine(ex.what()); - } - } - else - { - // Determine destination path - auto dstPath = Path::Combine(seq.Path, name); - if (!File::Copy(path, dstPath, true)) - { - Console::Error::WriteLine("Unable to copy '%s' to '%s'", path, dstPath.c_str()); - return false; - } - } - return true; -} - -bool TitleSequenceRenamePark(TitleSequence& seq, size_t index, const utf8* name) -{ - Guard::Assert(index < seq.Saves.size(), GUARD_LINE); - - auto& oldRelativePath = seq.Saves[index]; - if (seq.IsZip) - { - auto zip = Zip::TryOpen(seq.Path, ZIP_ACCESS::WRITE); - if (zip == nullptr) - { - Console::Error::WriteLine("Unable to open '%s'", seq.Path.c_str()); - return false; - } - zip->RenameFile(oldRelativePath, name); - } - else - { - auto srcPath = Path::Combine(seq.Path, oldRelativePath); - auto dstPath = Path::Combine(seq.Path, name); - if (!File::Move(srcPath, dstPath)) - { - Console::Error::WriteLine("Unable to move '%s' to '%s'", srcPath.c_str(), dstPath.c_str()); - return false; - } - } - seq.Saves[index] = name; - return true; -} - -bool TitleSequenceRemovePark(TitleSequence& seq, size_t index) -{ - Guard::Assert(index < seq.Saves.size(), GUARD_LINE); - - // Delete park file - auto& relativePath = seq.Saves[index]; - if (seq.IsZip) - { - auto zip = Zip::TryOpen(seq.Path, ZIP_ACCESS::WRITE); - if (zip == nullptr) - { - Console::Error::WriteLine("Unable to open '%s'", seq.Path.c_str()); - return false; - } - zip->DeleteFile(relativePath); - } - else - { - auto absolutePath = Path::Combine(seq.Path, relativePath); - if (!File::Delete(absolutePath)) - { - Console::Error::WriteLine("Unable to delete '%s'", absolutePath.c_str()); - return false; - } - } - - // Remove from sequence - seq.Saves.erase(seq.Saves.begin() + index); - - // Update load commands - for (auto& command : seq.Commands) - { - if (command.Type == TitleScript::Load) - { - if (command.SaveIndex == index) + auto srcPath = Path::Combine(seq.Path, oldRelativePath); + auto dstPath = Path::Combine(seq.Path, name); + if (!File::Move(srcPath, dstPath)) { - // Park no longer exists, so reset load command to invalid - command.SaveIndex = SAVE_INDEX_INVALID; - } - else if (command.SaveIndex > index) - { - // Park index will have shifted by -1 - command.SaveIndex--; + Console::Error::WriteLine("Unable to move '%s' to '%s'", srcPath.c_str(), dstPath.c_str()); + return false; } } + seq.Saves[index] = name; + return true; } - return true; -} - -static std::vector GetSaves(const std::string& directory) -{ - std::vector saves; - - auto pattern = Path::Combine(directory, u8"*.sc6;*.sv6;*.park;*.sv4;*.sc4"); - auto scanner = Path::ScanDirectory(pattern, true); - while (scanner->Next()) + bool TitleSequenceRemovePark(TitleSequence& seq, size_t index) { - const utf8* path = scanner->GetPathRelative(); - saves.push_back(path); - } - return saves; -} + Guard::Assert(index < seq.Saves.size(), GUARD_LINE); -static std::vector GetSaves(IZipArchive* zip) -{ - std::vector saves; - size_t numFiles = zip->GetNumFiles(); - for (size_t i = 0; i < numFiles; i++) - { - auto name = zip->GetFileName(i); - auto ext = Path::GetExtension(name); - if (String::Equals(ext, ".sv6", true) || String::Equals(ext, ".sc6", true) || String::Equals(ext, ".park", true)) + // Delete park file + auto& relativePath = seq.Saves[index]; + if (seq.IsZip) { - saves.push_back(std::move(name)); - } - } - return saves; -} - -static std::vector LegacyScriptRead(const std::vector& script, std::vector saves) -{ - std::vector commands; - auto fs = OpenRCT2::MemoryStream(script.data(), script.size()); - do - { - std::vector> parts; - LegacyScriptGetLine(&fs, parts); - - const char* token = parts[0].data(); - TitleCommand command = {}; - command.Type = TitleScript::Undefined; - - if (token[0] != 0) - { - if (_stricmp(token, "LOAD") == 0) + auto zip = Zip::TryOpen(seq.Path, ZIP_ACCESS::WRITE); + if (zip == nullptr) { - command.Type = TitleScript::Load; - command.SaveIndex = SAVE_INDEX_INVALID; - for (size_t i = 0; i < saves.size(); i++) - { - if (String::Equals(parts[1].data(), saves[i], true)) + Console::Error::WriteLine("Unable to open '%s'", seq.Path.c_str()); + return false; + } + zip->DeleteFile(relativePath); + } + else + { + auto absolutePath = Path::Combine(seq.Path, relativePath); + if (!File::Delete(absolutePath)) + { + Console::Error::WriteLine("Unable to delete '%s'", absolutePath.c_str()); + return false; + } + } + + // Remove from sequence + seq.Saves.erase(seq.Saves.begin() + index); + + // Update load commands + for (auto& seqCommand : seq.Commands) + { + std::visit( + [index](auto&& command) { + if constexpr (std::is_same_v, LoadParkCommand>) { - command.SaveIndex = static_cast(i); - break; + if (command.SaveIndex == index) + { + // Park no longer exists, so reset load command to invalid + command.SaveIndex = SAVE_INDEX_INVALID; + } + else if (command.SaveIndex > index) + { + // Park index will have shifted by -1 + command.SaveIndex--; + } } - } - } - else if (_stricmp(token, "LOCATION") == 0) - { - command.Type = TitleScript::Location; - command.Location.X = atoi(parts[1].data()) & 0xFF; - command.Location.Y = atoi(parts[2].data()) & 0xFF; - } - else if (_stricmp(token, "ROTATE") == 0) - { - command.Type = TitleScript::Rotate; - command.Rotations = atoi(parts[1].data()) & 0xFF; - } - else if (_stricmp(token, "ZOOM") == 0) - { - command.Type = TitleScript::Zoom; - command.Zoom = atoi(parts[1].data()) & 0xFF; - } - else if (_stricmp(token, "SPEED") == 0) - { - command.Type = TitleScript::Speed; - command.Speed = std::max(1, std::min(4, atoi(parts[1].data()) & 0xFF)); - } - else if (_stricmp(token, "FOLLOW") == 0) - { - command.Type = TitleScript::Follow; - command.Follow.SpriteIndex = EntityId::FromUnderlying(atoi(parts[1].data()) & 0xFFFF); - safe_strcpy(command.Follow.SpriteName, parts[2].data(), USER_STRING_MAX_LENGTH); - } - else if (_stricmp(token, "WAIT") == 0) - { - command.Type = TitleScript::Wait; - command.Milliseconds = atoi(parts[1].data()) & 0xFFFF; - } - else if (_stricmp(token, "RESTART") == 0) - { - command.Type = TitleScript::Restart; - } - else if (_stricmp(token, "END") == 0) - { - command.Type = TitleScript::End; - } - else if (_stricmp(token, "LOADSC") == 0) - { - command.Type = TitleScript::LoadSc; - safe_strcpy(command.Scenario, parts[1].data(), sizeof(command.Scenario)); - } + }, + seqCommand); } - if (command.Type != TitleScript::Undefined) - { - commands.push_back(std::move(command)); - } - } while (fs.GetPosition() < fs.GetLength()); - return commands; -} -static void LegacyScriptGetLine(OpenRCT2::IStream* stream, std::vector>& parts) -{ - int32_t part = 0; - int32_t cindex = 0; - int32_t whitespace = 1; - int32_t comment = 0; - bool load = false; - bool sprite = false; + return true; + } - parts.resize(1); - - while (true) + static std::vector GetSaves(const std::string& directory) { - int32_t c = 0; - if (stream->TryRead(&c, 1) != 1) + std::vector saves; + + auto pattern = Path::Combine(directory, u8"*.sc6;*.sv6;*.park;*.sv4;*.sc4"); + auto scanner = Path::ScanDirectory(pattern, true); + while (scanner->Next()) { - c = EOF; + const utf8* path = scanner->GetPathRelative(); + saves.push_back(path); } - if (c == '\n' || c == '\r' || c == EOF) + return saves; + } + + static std::vector GetSaves(IZipArchive* zip) + { + std::vector saves; + size_t numFiles = zip->GetNumFiles(); + for (size_t i = 0; i < numFiles; i++) { - parts[part][cindex] = 0; - return; - } - if (c == '#') - { - parts[part][cindex] = 0; - comment = 1; - } - else if (c == ' ' && !comment && !load && (!sprite || part != 2)) - { - if (!whitespace) + auto name = zip->GetFileName(i); + auto ext = Path::GetExtension(name); + if (String::Equals(ext, ".sv6", true) || String::Equals(ext, ".sc6", true) || String::Equals(ext, ".park", true)) { - if (part == 0 - && ((cindex == 4 && _strnicmp(parts[0].data(), "LOAD", 4) == 0) - || (cindex == 6 && _strnicmp(parts[0].data(), "LOADSC", 6) == 0))) - { - load = true; - } - else if (part == 0 && cindex == 6 && _strnicmp(parts[0].data(), "FOLLOW", 6) == 0) - { - sprite = true; - } - parts[part][cindex] = 0; - part++; - parts.resize(part + 1); - cindex = 0; + saves.push_back(std::move(name)); } } - else if (!comment) + return saves; + } + + static std::vector LegacyScriptRead(const std::vector& script, std::vector saves) + { + std::vector commands; + auto fs = OpenRCT2::MemoryStream(script.data(), script.size()); + do { - whitespace = 0; - if (cindex < 127) + std::vector> parts; + LegacyScriptGetLine(&fs, parts); + + const char* token = parts[0].data(); + std::optional command = std::nullopt; + + if (token[0] != 0) { - parts[part][cindex] = c; - cindex++; + if (_stricmp(token, "LOAD") == 0) + { + auto saveIndex = SAVE_INDEX_INVALID; + const std::string relativePath = parts[1].data(); + for (size_t i = 0; i < saves.size(); i++) + { + if (String::Equals(relativePath, saves[i], true)) + { + saveIndex = static_cast(i); + break; + } + } + command = LoadParkCommand{ saveIndex }; + } + else if (_stricmp(token, "LOCATION") == 0) + { + uint8_t locationX = atoi(parts[1].data()) & 0xFF; + uint8_t locationY = atoi(parts[2].data()) & 0xFF; + command = SetLocationCommand{ locationX, locationY }; + } + else if (_stricmp(token, "ROTATE") == 0) + { + uint8_t rotations = atoi(parts[1].data()) & 0xFF; + command = RotateViewCommand{ rotations }; + } + else if (_stricmp(token, "ZOOM") == 0) + { + uint8_t zoom = atoi(parts[1].data()) & 0xFF; + command = SetZoomCommand{ zoom }; + } + else if (_stricmp(token, "SPEED") == 0) + { + uint8_t speed = std::max(1, std::min(4, atoi(parts[1].data()) & 0xFF)); + command = SetSpeedCommand{ speed }; + } + else if (_stricmp(token, "FOLLOW") == 0) + { + auto entityID = EntityId::FromUnderlying(atoi(parts[1].data()) & 0xFFFF); + auto followCommand = FollowEntityCommand{ entityID }; + safe_strcpy(followCommand.Follow.SpriteName, parts[2].data(), USER_STRING_MAX_LENGTH); + command = followCommand; + } + else if (_stricmp(token, "WAIT") == 0) + { + uint16_t milliseconds = atoi(parts[1].data()) & 0xFFFF; + command = WaitCommand{ milliseconds }; + } + else if (_stricmp(token, "RESTART") == 0) + { + command = RestartCommand{}; + } + else if (_stricmp(token, "END") == 0) + { + command = EndCommand{}; + } + else if (_stricmp(token, "LOADSC") == 0) + { + auto loadScenarioCommand = LoadScenarioCommand{}; + safe_strcpy(loadScenarioCommand.Scenario, parts[1].data(), sizeof(loadScenarioCommand.Scenario)); + command = loadScenarioCommand; + } } - else + + if (command.has_value()) + { + commands.push_back(std::move(*command)); + } + } while (fs.GetPosition() < fs.GetLength()); + return commands; + } + + static void LegacyScriptGetLine(OpenRCT2::IStream* stream, std::vector>& parts) + { + int32_t part = 0; + int32_t cindex = 0; + int32_t whitespace = 1; + int32_t comment = 0; + bool load = false; + bool sprite = false; + + parts.resize(1); + + while (true) + { + int32_t c = 0; + if (stream->TryRead(&c, 1) != 1) + { + c = EOF; + } + if (c == '\n' || c == '\r' || c == EOF) { parts[part][cindex] = 0; - part++; - parts.resize(part + 1); - cindex = 0; + return; } - } - } -} - -static std::vector ReadScriptFile(const std::string& path) -{ - std::vector result; - try - { - auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); - auto size = static_cast(fs.GetLength()); - result.resize(size); - fs.Read(result.data(), size); - } - catch (const std::exception&) - { - result.clear(); - result.shrink_to_fit(); - } - return result; -} - -static std::string LegacyScriptWrite(const TitleSequence& seq) -{ - utf8 buffer[128]; - auto sb = StringBuilder(128); - - sb.Append("# SCRIPT FOR "); - sb.Append(seq.Name.c_str()); - sb.Append("\n"); - for (const auto& command : seq.Commands) - { - switch (command.Type) - { - case TitleScript::Load: - if (command.SaveIndex < seq.Saves.size()) + if (c == '#') + { + parts[part][cindex] = 0; + comment = 1; + } + else if (c == ' ' && !comment && !load && (!sprite || part != 2)) + { + if (!whitespace) { - sb.Append("LOAD "); - sb.Append(seq.Saves[command.SaveIndex].c_str()); + if (part == 0 + && ((cindex == 4 && _strnicmp(parts[0].data(), "LOAD", 4) == 0) + || (cindex == 6 && _strnicmp(parts[0].data(), "LOADSC", 6) == 0))) + { + load = true; + } + else if (part == 0 && cindex == 6 && _strnicmp(parts[0].data(), "FOLLOW", 6) == 0) + { + sprite = true; + } + parts[part][cindex] = 0; + part++; + parts.resize(part + 1); + cindex = 0; + } + } + else if (!comment) + { + whitespace = 0; + if (cindex < 127) + { + parts[part][cindex] = c; + cindex++; } else { - sb.Append("LOAD "); + parts[part][cindex] = 0; + part++; + parts.resize(part + 1); + cindex = 0; } - break; - case TitleScript::LoadSc: - if (command.Scenario[0] == '\0') - { - sb.Append("LOADSC "); - } - else - { - sb.Append("LOADSC "); - sb.Append(command.Scenario); - } - break; - case TitleScript::Undefined: - break; - case TitleScript::Loop: - break; - case TitleScript::EndLoop: - break; - case TitleScript::Location: - String::Format(buffer, sizeof(buffer), "LOCATION %u %u", command.Location.X, command.Location.Y); - sb.Append(buffer); - break; - case TitleScript::Rotate: - String::Format(buffer, sizeof(buffer), "ROTATE %u", command.Rotations); - sb.Append(buffer); - break; - case TitleScript::Zoom: - String::Format(buffer, sizeof(buffer), "ZOOM %u", command.Zoom); - sb.Append(buffer); - break; - case TitleScript::Follow: - String::Format(buffer, sizeof(buffer), "FOLLOW %u ", command.Follow.SpriteIndex); - sb.Append(buffer); - sb.Append(command.Follow.SpriteName); - break; - case TitleScript::Speed: - String::Format(buffer, sizeof(buffer), "SPEED %u", command.Speed); - sb.Append(buffer); - break; - case TitleScript::Wait: - String::Format(buffer, sizeof(buffer), "WAIT %u", command.Milliseconds); - sb.Append(buffer); - break; - case TitleScript::Restart: - sb.Append("RESTART"); - break; - case TitleScript::End: - sb.Append("END"); + } } + } + + static std::vector ReadScriptFile(const std::string& path) + { + std::vector result; + try + { + auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); + auto size = static_cast(fs.GetLength()); + result.resize(size); + fs.Read(result.data(), size); + } + catch (const std::exception&) + { + result.clear(); + result.shrink_to_fit(); + } + return result; + } + + static std::string LegacyScriptWrite(const TitleSequence& seq) + { + utf8 buffer[128]; + auto sb = StringBuilder(128); + + sb.Append("# SCRIPT FOR "); + sb.Append(seq.Name.c_str()); sb.Append("\n"); + for (const auto& seqCommand : seq.Commands) + { + std::visit( + [&buffer, &seq, &sb](auto&& command) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + if (command.SaveIndex < seq.Saves.size()) + { + sb.Append("LOAD "); + sb.Append(seq.Saves[command.SaveIndex].c_str()); + } + else + { + sb.Append("LOAD "); + } + } + else if constexpr (std::is_same_v) + { + if (command.Scenario[0] == '\0') + { + sb.Append("LOADSC "); + } + else + { + sb.Append("LOADSC "); + sb.Append(command.Scenario); + } + } + else if constexpr (std::is_same_v) + { + String::Format(buffer, sizeof(buffer), "LOCATION %u %u", command.Location.X, command.Location.Y); + sb.Append(buffer); + } + else if constexpr (std::is_same_v) + { + String::Format(buffer, sizeof(buffer), "ROTATE %u", command.Rotations); + sb.Append(buffer); + } + else if constexpr (std::is_same_v) + { + String::Format(buffer, sizeof(buffer), "ZOOM %u", command.Zoom); + sb.Append(buffer); + } + else if constexpr (std::is_same_v) + { + String::Format(buffer, sizeof(buffer), "FOLLOW %u ", command.Follow.SpriteIndex); + sb.Append(buffer); + sb.Append(command.Follow.SpriteName); + } + else if constexpr (std::is_same_v) + { + String::Format(buffer, sizeof(buffer), "SPEED %u", command.Speed); + sb.Append(buffer); + } + else if constexpr (std::is_same_v) + { + String::Format(buffer, sizeof(buffer), "WAIT %u", command.Milliseconds); + sb.Append(buffer); + } + else if constexpr (std::is_same_v) + { + sb.Append("RESTART"); + } + else if constexpr (std::is_same_v) + { + sb.Append("END"); + } + }, + seqCommand); + sb.Append("\n"); + } + + return sb.GetBuffer(); } - return sb.GetBuffer(); -} - -bool TitleSequenceIsLoadCommand(const TitleCommand& command) -{ - switch (command.Type) + bool TitleSequenceIsLoadCommand(const TitleCommand& command) { - case TitleScript::Load: - case TitleScript::LoadSc: - return true; - default: - return false; + return std::holds_alternative(command) || std::holds_alternative(command); } -} +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/TitleSequence.h b/src/openrct2/title/TitleSequence.h index 9dfe3d707e..af5c08fc6f 100644 --- a/src/openrct2/title/TitleSequence.h +++ b/src/openrct2/title/TitleSequence.h @@ -10,81 +10,56 @@ #pragma once #include "../common.h" -#include "../localisation/Localisation.h" #include "../openrct2/core/IStream.hpp" +#include "Command/End.h" +#include "Command/FollowEntity.h" +#include "Command/LoadPark.h" +#include "Command/LoadScenario.h" +#include "Command/Restart.h" +#include "Command/RotateView.h" +#include "Command/SetLocation.h" +#include "Command/SetSpeed.h" +#include "Command/SetZoom.h" +#include "Command/Wait.h" #include +#include +#include -#define TITLE_COMMAND_SCENARIO_LENGTH 64 -enum class TitleScript : uint8_t; -struct TitleCommand +namespace OpenRCT2::Title { - TitleScript Type; - union + using TitleCommand = std::variant< + WaitCommand, SetLocationCommand, RotateViewCommand, SetZoomCommand, FollowEntityCommand, RestartCommand, + LoadParkCommand, EndCommand, SetSpeedCommand, LoadScenarioCommand>; + + struct TitleSequence { - uint8_t SaveIndex; // LOAD (this index is internal only) - struct // LOCATION - { - uint8_t X; - uint8_t Y; - } Location; - uint8_t Rotations; // ROTATE (counter-clockwise) - uint8_t Zoom; // ZOOM - struct // FOLLOW - { - EntityId SpriteIndex; - utf8 SpriteName[USER_STRING_MAX_LENGTH]; - } Follow; - uint8_t Speed; // SPEED - uint16_t Milliseconds; // WAIT - utf8 Scenario[TITLE_COMMAND_SCENARIO_LENGTH]; // LOADSC + std::string Name; + std::string Path; + + std::vector Commands; + std::vector Saves; + + bool IsZip = false; }; -}; -struct TitleSequence -{ - std::string Name; - std::string Path; + struct TitleSequenceParkHandle + { + std::string HintPath; + std::unique_ptr Stream; + }; - std::vector Commands; - std::vector Saves; + constexpr const utf8* TITLE_SEQUENCE_EXTENSION = ".parkseq"; + constexpr uint8_t SAVE_INDEX_INVALID = UINT8_MAX; - bool IsZip = false; -}; + [[nodiscard]] std::unique_ptr CreateTitleSequence(); + [[nodiscard]] std::unique_ptr LoadTitleSequence(const std::string& path); + [[nodiscard]] std::unique_ptr TitleSequenceGetParkHandle(const TitleSequence& seq, size_t index); -struct TitleSequenceParkHandle -{ - std::string HintPath; - std::unique_ptr Stream; -}; + bool TitleSequenceSave(const TitleSequence& seq); + bool TitleSequenceAddPark(TitleSequence& seq, const utf8* path, const utf8* name); + bool TitleSequenceRenamePark(TitleSequence& seq, size_t index, const utf8* name); + bool TitleSequenceRemovePark(TitleSequence& seq, size_t index); -enum class TitleScript : uint8_t -{ - Undefined = 0xFF, - Wait = 0, - Location, - Rotate, - Zoom, - Follow, - Restart, - Load, - End, - Speed, - Loop, - EndLoop, - LoadSc, -}; - -constexpr const utf8* TITLE_SEQUENCE_EXTENSION = ".parkseq"; -constexpr uint8_t SAVE_INDEX_INVALID = UINT8_MAX; - -[[nodiscard]] std::unique_ptr CreateTitleSequence(); -[[nodiscard]] std::unique_ptr LoadTitleSequence(const std::string& path); -[[nodiscard]] std::unique_ptr TitleSequenceGetParkHandle(const TitleSequence& seq, size_t index); - -bool TitleSequenceSave(const TitleSequence& seq); -bool TitleSequenceAddPark(TitleSequence& seq, const utf8* path, const utf8* name); -bool TitleSequenceRenamePark(TitleSequence& seq, size_t index, const utf8* name); -bool TitleSequenceRemovePark(TitleSequence& seq, size_t index); - -bool TitleSequenceIsLoadCommand(const TitleCommand& command); + bool TitleSequenceIsLoadCommand(const TitleCommand& command); +} // namespace OpenRCT2::Title diff --git a/src/openrct2/title/TitleSequenceManager.cpp b/src/openrct2/title/TitleSequenceManager.cpp index f80eaf04ab..ce8033b052 100644 --- a/src/openrct2/title/TitleSequenceManager.cpp +++ b/src/openrct2/title/TitleSequenceManager.cpp @@ -103,7 +103,7 @@ namespace TitleSequenceManager auto newPath = Path::Combine(Path::GetDirectory(oldPath), newName); if (item->IsZip) { - newPath += TITLE_SEQUENCE_EXTENSION; + newPath += OpenRCT2::Title::TITLE_SEQUENCE_EXTENSION; File::Move(oldPath, newPath); } else @@ -138,7 +138,7 @@ namespace TitleSequenceManager size_t CreateItem(const utf8* name) { - auto seq = CreateTitleSequence(); + auto seq = OpenRCT2::Title::CreateTitleSequence(); seq->Name = name; seq->Path = GetNewTitleSequencePath(seq->Name, true); seq->IsZip = true; @@ -159,7 +159,7 @@ namespace TitleSequenceManager auto path = Path::Combine(GetUserSequencesPath(), name); if (isZip) { - path += TITLE_SEQUENCE_EXTENSION; + path += OpenRCT2::Title::TITLE_SEQUENCE_EXTENSION; } return path; }