diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts
index 733310e66d..f944c78d78 100644
--- a/distribution/openrct2.d.ts
+++ b/distribution/openrct2.d.ts
@@ -37,6 +37,11 @@ declare global {
var park: Park;
/** APIs for the current scenario. */
var scenario: Scenario;
+ /**
+ * APIs for creating and editing title sequences.
+ * These will only be available to clients that are not running headless mode.
+ */
+ var titleSequenceManager: TitleSequenceManager;
/**
* APIs for controlling the user interface.
* These will only be available to servers and clients that are not running headless mode.
@@ -2168,4 +2173,82 @@ declare global {
off(event: 'error', callback: (hadError: boolean) => void): Socket;
off(event: 'data', callback: (data: string) => void): Socket;
}
+
+ interface TitleSequence {
+ /**
+ * The name of the title sequence.
+ */
+ name: string;
+
+ /**
+ * The full path of the title sequence.
+ */
+ readonly path: string;
+
+ /**
+ * Whether the title sequence is a single file or directory.
+ */
+ readonly isDirectory: boolean;
+
+ /**
+ * Whether or not the title sequence is read-only (e.g. a pre-installed sequence).
+ */
+ readonly isReadOnly: boolean;
+
+ /**
+ * The parks stored within this title sequence.
+ */
+ parks: string[];
+
+ /**
+ * The commands that describe how to play the title sequence.
+ */
+ commands: TitleSequenceCommand[];
+
+ /**
+ * Creates a new title sequence identical to this one.
+ * @param name The name of the new title sequence.
+ */
+ clone(name: string): TitleSequence;
+
+ /**
+ * Deletes this title sequence from disc.
+ */
+ delete(): void;
+ }
+
+ type TitleSequenceCommandType =
+ 'load' |
+ 'loadsc' |
+ 'location' |
+ 'rotate' |
+ 'zoom' |
+ 'speed' |
+ 'follow' |
+ 'wait' |
+ 'restart' |
+ 'end';
+
+ interface TitleSequenceCommand {
+ type: TitleSequenceCommandType;
+ }
+
+ interface LocationTitleSequenceCommand extends TitleSequenceCommand {
+ type: 'location';
+ x: number;
+ y: number;
+ }
+
+ interface TitleSequenceManager {
+ /**
+ * Gets all the available title sequences.
+ */
+ readonly titleSequences: TitleSequence[];
+
+ /**
+ * Creates a new blank title sequence.
+ * @param name The name of the title sequence.
+ */
+ create(name: string): TitleSequence;
+ }
}
diff --git a/src/openrct2-ui/libopenrct2ui.vcxproj b/src/openrct2-ui/libopenrct2ui.vcxproj
index 74b08e49ae..54276d1838 100644
--- a/src/openrct2-ui/libopenrct2ui.vcxproj
+++ b/src/openrct2-ui/libopenrct2ui.vcxproj
@@ -54,6 +54,7 @@
+
diff --git a/src/openrct2-ui/scripting/ScTitleSequence.hpp b/src/openrct2-ui/scripting/ScTitleSequence.hpp
new file mode 100644
index 0000000000..054153228f
--- /dev/null
+++ b/src/openrct2-ui/scripting/ScTitleSequence.hpp
@@ -0,0 +1,182 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2021 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
+
+#ifdef ENABLE_SCRIPTING
+
+# include
+# include
+# include
+
+namespace OpenRCT2::Scripting
+{
+ class ScTitleSequence
+ {
+ private:
+ std::string _path;
+
+ public:
+ ScTitleSequence(const std::string& path)
+ {
+ _path = path;
+ }
+
+ private:
+ std::string name_get() const
+ {
+ const auto* item = GetItem();
+ if (item != nullptr)
+ {
+ return item->Name;
+ }
+ return {};
+ }
+
+ void name_set(const std::string& value)
+ {
+ auto index = GetManagerIndex();
+ if (index)
+ {
+ auto newIndex = TitleSequenceManager::RenameItem(*index, value.c_str());
+
+ // Update path to new value
+ auto newItem = TitleSequenceManager::GetItem(newIndex);
+ _path = newItem != nullptr ? newItem->Path : std::string();
+ }
+ }
+
+ std::string path_get() const
+ {
+ const auto* item = GetItem();
+ if (item != nullptr)
+ {
+ return item->Path;
+ }
+ return {};
+ }
+
+ bool isDirectory_get() const
+ {
+ const auto* item = GetItem();
+ if (item != nullptr)
+ {
+ return !item->IsZip;
+ }
+ return {};
+ }
+
+ bool isReadOnly_get() const
+ {
+ const auto* item = GetItem();
+ if (item != nullptr)
+ {
+ return item->PredefinedIndex != std::numeric_limits::max();
+ }
+ return {};
+ }
+
+ std::shared_ptr clone(const std::string& name) const
+ {
+ auto copyIndex = GetManagerIndex();
+ if (copyIndex)
+ {
+ auto index = TitleSequenceManager::DuplicateItem(*copyIndex, name.c_str());
+ auto* item = TitleSequenceManager::GetItem(index);
+ if (item != nullptr)
+ {
+ return std::make_shared(item->Path);
+ }
+ }
+ return nullptr;
+ }
+
+ void delete_()
+ {
+ auto index = GetManagerIndex();
+ if (index)
+ {
+ TitleSequenceManager::DeleteItem(*index);
+ }
+ _path = {};
+ }
+
+ public:
+ static void Register(duk_context* ctx)
+ {
+ dukglue_register_property(ctx, &ScTitleSequence::name_get, &ScTitleSequence::name_set, "name");
+ dukglue_register_property(ctx, &ScTitleSequence::path_get, nullptr, "path");
+ dukglue_register_property(ctx, &ScTitleSequence::isDirectory_get, nullptr, "isDirectory");
+ dukglue_register_property(ctx, &ScTitleSequence::isReadOnly_get, nullptr, "isReadOnly");
+ dukglue_register_method(ctx, &ScTitleSequence::clone, "clone");
+ dukglue_register_method(ctx, &ScTitleSequence::delete_, "delete");
+ }
+
+ private:
+ std::optional GetManagerIndex() const
+ {
+ auto count = TitleSequenceManager::GetCount();
+ for (size_t i = 0; i < count; i++)
+ {
+ auto item = TitleSequenceManager::GetItem(i);
+ if (item != nullptr && item->Path == _path)
+ {
+ return i;
+ }
+ }
+ return {};
+ }
+
+ const TitleSequenceManagerItem* GetItem() const
+ {
+ auto index = GetManagerIndex();
+ if (index)
+ {
+ return TitleSequenceManager::GetItem(*index);
+ }
+ return nullptr;
+ }
+ };
+
+ class ScTitleSequenceManager
+ {
+ private:
+ std::vector> titleSequences_get() const
+ {
+ std::vector> result;
+ auto count = TitleSequenceManager::GetCount();
+ for (size_t i = 0; i < count; i++)
+ {
+ const auto& path = TitleSequenceManager::GetItem(i)->Path;
+ result.push_back(std::make_shared(path));
+ }
+ return result;
+ }
+
+ std::shared_ptr create(const std::string& name)
+ {
+ auto index = TitleSequenceManager::CreateItem(name.c_str());
+ auto* item = TitleSequenceManager::GetItem(index);
+ if (item != nullptr)
+ {
+ return std::make_shared(item->Path);
+ }
+ return nullptr;
+ }
+
+ public:
+ static void Register(duk_context* ctx)
+ {
+ dukglue_register_property(ctx, &ScTitleSequenceManager::titleSequences_get, nullptr, "titleSequences");
+ dukglue_register_method(ctx, &ScTitleSequenceManager::create, "create");
+ }
+ };
+} // namespace OpenRCT2::Scripting
+
+#endif
diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp
index 0361b58f1d..f97c58f653 100644
--- a/src/openrct2-ui/scripting/UiExtensions.cpp
+++ b/src/openrct2-ui/scripting/UiExtensions.cpp
@@ -13,6 +13,7 @@
# include "CustomMenu.h"
# include "ScTileSelection.hpp"
+# include "ScTitleSequence.hpp"
# include "ScUi.hpp"
# include "ScWidget.hpp"
# include "ScWindow.hpp"
@@ -25,6 +26,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
{
auto ctx = scriptEngine.GetContext();
+ dukglue_register_global(ctx, std::make_shared(), "titleSequenceManager");
dukglue_register_global(ctx, std::make_shared(scriptEngine), "ui");
ScTileSelection::Register(ctx);
@@ -43,6 +45,9 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
ScSpinnerWidget::Register(ctx);
ScTextBoxWidget::Register(ctx);
ScViewportWidget::Register(ctx);
+
+ ScTitleSequence::Register(ctx);
+ ScTitleSequenceManager::Register(ctx);
ScWindow::Register(ctx);
InitialiseCustomMenuItems(scriptEngine);