mirror of https://github.com/OpenRCT2/OpenRCT2.git
Merge pull request #13969 from IntelOrca/plugin/title-seq
[Plugin] Add APIs for editing title sequences
This commit is contained in:
commit
0a8cfe75b0
|
@ -19,6 +19,7 @@
|
|||
- Feature: [#13614] Add terrain surfaces from RollerCoaster Tycoon 1.
|
||||
- Feature: [#13675] [Plugin] Add context.setInterval and context.setTimeout.
|
||||
- Feature: [#13927] [Plugin] Add isVisible and text box widget.
|
||||
- Feature: [#13969] [Plugin] Add APIs for editing title sequences.
|
||||
- Feature: [#14002] [Plugin] Feature: Use allowed_hosts when checking the binding IP for listening
|
||||
- Change: [#13346] [Plugin] Renamed FootpathScenery to FootpathAddition, fix typos.
|
||||
- Change: [#13857] Change Rotation Control Toggle to track element number 256
|
||||
|
|
|
@ -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,178 @@ 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.
|
||||
*/
|
||||
readonly parks: TitleSequencePark[];
|
||||
|
||||
/**
|
||||
* The commands that describe how to play the title sequence.
|
||||
*/
|
||||
commands: TitleSequenceCommand[];
|
||||
|
||||
/**
|
||||
* Whether the title sequence is currently playing.
|
||||
*/
|
||||
readonly isPlaying: boolean;
|
||||
|
||||
/**
|
||||
* The current command the title sequence is on if playing.
|
||||
*/
|
||||
readonly position: number | null;
|
||||
|
||||
addPark(path: string, fileName: string): void;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Play the title sequence.
|
||||
*/
|
||||
play(): void;
|
||||
|
||||
/**
|
||||
* Seek to a specific command in the sequence.
|
||||
* @param position The index of the command to seek to.
|
||||
*/
|
||||
seek(position: number): void;
|
||||
|
||||
/**
|
||||
* Stops playing the title sequence.
|
||||
*/
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
interface TitleSequencePark {
|
||||
/**
|
||||
* The file name of the park.
|
||||
*/
|
||||
fileName: string;
|
||||
|
||||
/**
|
||||
* Deletes this park from the title sequence.
|
||||
*/
|
||||
delete(): void;
|
||||
|
||||
/**
|
||||
* Loads this park.
|
||||
*/
|
||||
load(): void;
|
||||
}
|
||||
|
||||
type TitleSequenceCommandType =
|
||||
'load' |
|
||||
'loadsc' |
|
||||
'location' |
|
||||
'rotate' |
|
||||
'zoom' |
|
||||
'speed' |
|
||||
'follow' |
|
||||
'wait' |
|
||||
'restart' |
|
||||
'end';
|
||||
|
||||
interface LoadTitleSequenceCommand {
|
||||
type: 'load';
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface LocationTitleSequenceCommand {
|
||||
type: 'location';
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface RotateTitleSequenceCommand {
|
||||
type: 'rotate';
|
||||
rotations: number;
|
||||
}
|
||||
|
||||
interface ZoomTitleSequenceCommand {
|
||||
type: 'zoom';
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
interface FollowTitleSequenceCommand {
|
||||
type: 'follow';
|
||||
id: number | null;
|
||||
}
|
||||
|
||||
interface SpeedTitleSequenceCommand {
|
||||
type: 'speed';
|
||||
speed: number;
|
||||
}
|
||||
|
||||
interface WaitTitleSequenceCommand {
|
||||
type: 'wait';
|
||||
duration: number;
|
||||
}
|
||||
|
||||
interface LoadScenarioTitleSequenceCommand {
|
||||
type: 'loadsc';
|
||||
scenario: string;
|
||||
}
|
||||
|
||||
interface RestartTitleSequenceCommand {
|
||||
type: 'restart';
|
||||
}
|
||||
|
||||
interface EndTitleSequenceCommand {
|
||||
type: 'end';
|
||||
}
|
||||
|
||||
type TitleSequenceCommand =
|
||||
LoadTitleSequenceCommand |
|
||||
LocationTitleSequenceCommand |
|
||||
RotateTitleSequenceCommand |
|
||||
ZoomTitleSequenceCommand |
|
||||
FollowTitleSequenceCommand |
|
||||
SpeedTitleSequenceCommand |
|
||||
WaitTitleSequenceCommand |
|
||||
LoadScenarioTitleSequenceCommand |
|
||||
RestartTitleSequenceCommand |
|
||||
EndTitleSequenceCommand;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -236,8 +236,10 @@ public:
|
|||
loadsave_callback callback = reinterpret_cast<loadsave_callback>(
|
||||
intent->GetPointerExtra(INTENT_EXTRA_CALLBACK));
|
||||
TrackDesign* trackDesign = static_cast<TrackDesign*>(intent->GetPointerExtra(INTENT_EXTRA_TRACK_DESIGN));
|
||||
rct_window* w = window_loadsave_open(type, defaultName.c_str(), callback, trackDesign);
|
||||
|
||||
auto* w = window_loadsave_open(
|
||||
type, defaultName,
|
||||
[callback](int32_t result, std::string_view path) { callback(result, std::string(path).c_str()); },
|
||||
trackDesign);
|
||||
return w;
|
||||
}
|
||||
case WC_MANAGE_TRACK_DESIGN:
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
<ClInclude Include="scripting\CustomMenu.h" />
|
||||
<ClInclude Include="scripting\CustomWindow.h" />
|
||||
<ClInclude Include="scripting\ScTileSelection.hpp" />
|
||||
<ClInclude Include="scripting\ScTitleSequence.hpp" />
|
||||
<ClInclude Include="scripting\ScUi.hpp" />
|
||||
<ClInclude Include="scripting\ScViewport.hpp" />
|
||||
<ClInclude Include="scripting\ScWidget.hpp" />
|
||||
|
|
|
@ -0,0 +1,542 @@
|
|||
/*****************************************************************************
|
||||
* 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 <memory>
|
||||
# include <openrct2/Context.h>
|
||||
# include <openrct2/Game.h>
|
||||
# include <openrct2/OpenRCT2.h>
|
||||
# include <openrct2/ParkImporter.h>
|
||||
# include <openrct2/core/String.hpp>
|
||||
# include <openrct2/object/ObjectManager.h>
|
||||
# include <openrct2/scenario/Scenario.h>
|
||||
# include <openrct2/scripting/ScriptEngine.h>
|
||||
# include <openrct2/title/TitleScreen.h>
|
||||
# include <openrct2/title/TitleSequence.h>
|
||||
# include <openrct2/title/TitleSequenceManager.h>
|
||||
# include <openrct2/title/TitleSequencePlayer.h>
|
||||
# include <openrct2/world/Sprite.h>
|
||||
|
||||
namespace OpenRCT2::Scripting
|
||||
{
|
||||
static const DukEnumMap<TitleScript> 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 },
|
||||
});
|
||||
|
||||
template<> DukValue ToDuk(duk_context* ctx, const TitleScript& value)
|
||||
{
|
||||
return ToDuk(ctx, TitleScriptMap[value]);
|
||||
}
|
||||
|
||||
template<> DukValue ToDuk(duk_context* ctx, const TitleCommand& value)
|
||||
{
|
||||
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.X);
|
||||
obj.Set("y", value.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.SpriteIndex == SPRITE_INDEX_NULL)
|
||||
obj.Set("id", nullptr);
|
||||
else
|
||||
obj.Set("id", value.SpriteIndex);
|
||||
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;
|
||||
}
|
||||
return obj.Take();
|
||||
}
|
||||
|
||||
template<> TitleScript FromDuk(const DukValue& value)
|
||||
{
|
||||
if (value.type() == DukValue::Type::STRING)
|
||||
return TitleScriptMap[value.as_string()];
|
||||
throw DukException() << "Invalid title command id";
|
||||
}
|
||||
|
||||
template<> TitleCommand FromDuk(const DukValue& value)
|
||||
{
|
||||
auto type = FromDuk<TitleScript>(value["type"]);
|
||||
TitleCommand command{};
|
||||
command.Type = type;
|
||||
switch (type)
|
||||
{
|
||||
case TitleScript::Load:
|
||||
command.SaveIndex = value["index"].as_int();
|
||||
break;
|
||||
case TitleScript::Location:
|
||||
command.X = value["x"].as_int();
|
||||
command.Y = value["y"].as_int();
|
||||
break;
|
||||
case TitleScript::Rotate:
|
||||
command.Rotations = value["rotations"].as_int();
|
||||
break;
|
||||
case TitleScript::Zoom:
|
||||
command.Zoom = value["zoom"].as_int();
|
||||
break;
|
||||
case TitleScript::Follow:
|
||||
{
|
||||
auto dukId = value["id"];
|
||||
if (dukId.type() == DukValue::Type::NUMBER)
|
||||
{
|
||||
command.SpriteIndex = dukId.as_int();
|
||||
}
|
||||
else
|
||||
{
|
||||
command.SpriteIndex = SPRITE_INDEX_NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TitleScript::Speed:
|
||||
command.Speed = value["speed"].as_int();
|
||||
break;
|
||||
case TitleScript::Wait:
|
||||
command.Milliseconds = value["duration"].as_int();
|
||||
break;
|
||||
case TitleScript::LoadSc:
|
||||
String::Set(command.Scenario, sizeof(command.Scenario), value["scenario"].as_c_string());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
class ScTitleSequencePark
|
||||
{
|
||||
private:
|
||||
std::string _titleSequencePath;
|
||||
std::string _fileName;
|
||||
|
||||
public:
|
||||
ScTitleSequencePark(std::string_view path, std::string_view fileName)
|
||||
: _titleSequencePath(path)
|
||||
, _fileName(fileName)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
std::string fileName_get() const
|
||||
{
|
||||
return _fileName;
|
||||
}
|
||||
|
||||
void fileName_set(const std::string& value)
|
||||
{
|
||||
if (value == _fileName)
|
||||
return;
|
||||
|
||||
auto seq = LoadTitleSequence(_titleSequencePath);
|
||||
if (seq != nullptr)
|
||||
{
|
||||
// Check if name already in use
|
||||
auto index = GetIndex(*seq, value);
|
||||
if (!index)
|
||||
{
|
||||
index = GetIndex(*seq, _fileName);
|
||||
if (index)
|
||||
{
|
||||
TitleSequenceRenamePark(*seq, *index, value.c_str());
|
||||
TitleSequenceSave(*seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void delete_()
|
||||
{
|
||||
auto seq = LoadTitleSequence(_titleSequencePath);
|
||||
if (seq != nullptr)
|
||||
{
|
||||
auto index = GetIndex(*seq, _fileName);
|
||||
if (index)
|
||||
{
|
||||
TitleSequenceRemovePark(*seq, *index);
|
||||
TitleSequenceSave(*seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
auto seq = LoadTitleSequence(_titleSequencePath);
|
||||
if (seq != nullptr)
|
||||
{
|
||||
auto index = GetIndex(*seq, _fileName);
|
||||
if (index)
|
||||
{
|
||||
auto handle = TitleSequenceGetParkHandle(*seq, *index);
|
||||
auto isScenario = ParkImporter::ExtensionIsScenario(handle->HintPath);
|
||||
try
|
||||
{
|
||||
auto& objectMgr = GetContext()->GetObjectManager();
|
||||
auto parkImporter = std::unique_ptr<IParkImporter>(ParkImporter::Create(handle->HintPath));
|
||||
auto result = parkImporter->LoadFromStream(handle->Stream.get(), isScenario);
|
||||
objectMgr.LoadObjects(result.RequiredObjects.data(), result.RequiredObjects.size());
|
||||
parkImporter->Import();
|
||||
|
||||
auto old = gLoadKeepWindowsOpen;
|
||||
gLoadKeepWindowsOpen = true;
|
||||
if (isScenario)
|
||||
scenario_begin();
|
||||
else
|
||||
game_load_init();
|
||||
gLoadKeepWindowsOpen = old;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
auto ctx = GetContext()->GetScriptEngine().GetContext();
|
||||
duk_error(ctx, DUK_ERR_ERROR, "Unable to load park.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static void Register(duk_context* ctx)
|
||||
{
|
||||
dukglue_register_property(ctx, &ScTitleSequencePark::fileName_get, &ScTitleSequencePark::fileName_set, "fileName");
|
||||
dukglue_register_method(ctx, &ScTitleSequencePark::delete_, "delete");
|
||||
dukglue_register_method(ctx, &ScTitleSequencePark::load, "load");
|
||||
}
|
||||
|
||||
private:
|
||||
static std::optional<size_t> GetIndex(const TitleSequence& seq, const std::string_view needle)
|
||||
{
|
||||
for (size_t i = 0; i < seq.Saves.size(); i++)
|
||||
{
|
||||
if (seq.Saves[i] == needle)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
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<size_t>::max();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ScTitleSequencePark>> parks_get() const
|
||||
{
|
||||
std::vector<std::shared_ptr<ScTitleSequencePark>> result;
|
||||
auto titleSeq = LoadTitleSequence(_path);
|
||||
if (titleSeq != nullptr)
|
||||
{
|
||||
for (size_t i = 0; i < titleSeq->Saves.size(); i++)
|
||||
{
|
||||
result.push_back(std::make_shared<ScTitleSequencePark>(_path, titleSeq->Saves[i]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<DukValue> commands_get() const
|
||||
{
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
auto ctx = scriptEngine.GetContext();
|
||||
|
||||
std::vector<DukValue> result;
|
||||
auto titleSeq = LoadTitleSequence(_path);
|
||||
if (titleSeq != nullptr)
|
||||
{
|
||||
for (const auto& command : titleSeq->Commands)
|
||||
{
|
||||
result.push_back(ToDuk(ctx, command));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void commands_set(const std::vector<DukValue>& value)
|
||||
{
|
||||
std::vector<TitleCommand> commands;
|
||||
for (const auto& v : value)
|
||||
{
|
||||
auto command = FromDuk<TitleCommand>(v);
|
||||
commands.push_back(std::move(command));
|
||||
}
|
||||
|
||||
auto titleSeq = LoadTitleSequence(_path);
|
||||
titleSeq->Commands = commands;
|
||||
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);
|
||||
}
|
||||
|
||||
std::shared_ptr<ScTitleSequence> 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<ScTitleSequence>(item->Path);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void delete_()
|
||||
{
|
||||
auto index = GetManagerIndex();
|
||||
if (index)
|
||||
{
|
||||
TitleSequenceManager::DeleteItem(*index);
|
||||
}
|
||||
_path = {};
|
||||
}
|
||||
|
||||
bool isPlaying_get() const
|
||||
{
|
||||
auto index = GetManagerIndex();
|
||||
return index && title_is_previewing_sequence() && *index == title_get_current_sequence();
|
||||
}
|
||||
|
||||
DukValue position_get() const
|
||||
{
|
||||
auto ctx = GetContext()->GetScriptEngine().GetContext();
|
||||
if (isPlaying_get())
|
||||
{
|
||||
auto* player = static_cast<ITitleSequencePlayer*>(title_get_sequence_player());
|
||||
if (player != nullptr)
|
||||
{
|
||||
return ToDuk(ctx, player->GetCurrentPosition());
|
||||
}
|
||||
}
|
||||
return ToDuk(ctx, nullptr);
|
||||
}
|
||||
|
||||
void play()
|
||||
{
|
||||
auto ctx = GetContext()->GetScriptEngine().GetContext();
|
||||
auto index = GetManagerIndex();
|
||||
if (index && (!title_is_previewing_sequence() || *index != title_get_current_sequence()))
|
||||
{
|
||||
if (!title_preview_sequence(*index))
|
||||
{
|
||||
duk_error(ctx, DUK_ERR_ERROR, "Failed to load title sequence");
|
||||
}
|
||||
else if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO))
|
||||
{
|
||||
gPreviewingTitleSequenceInGame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void seek(int32_t position)
|
||||
{
|
||||
auto ctx = GetContext()->GetScriptEngine().GetContext();
|
||||
if (isPlaying_get())
|
||||
{
|
||||
auto* player = static_cast<ITitleSequencePlayer*>(title_get_sequence_player());
|
||||
try
|
||||
{
|
||||
player->Seek(position);
|
||||
player->Update();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
duk_error(ctx, DUK_ERR_ERROR, "Failed to seek");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (isPlaying_get())
|
||||
{
|
||||
title_stop_previewing_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
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_property(ctx, &ScTitleSequence::parks_get, nullptr, "parks");
|
||||
dukglue_register_property(ctx, &ScTitleSequence::commands_get, &ScTitleSequence::commands_set, "commands");
|
||||
dukglue_register_property(ctx, &ScTitleSequence::isPlaying_get, nullptr, "isPlaying");
|
||||
dukglue_register_property(ctx, &ScTitleSequence::position_get, nullptr, "position");
|
||||
dukglue_register_method(ctx, &ScTitleSequence::addPark, "addPark");
|
||||
dukglue_register_method(ctx, &ScTitleSequence::clone, "clone");
|
||||
dukglue_register_method(ctx, &ScTitleSequence::delete_, "delete");
|
||||
|
||||
dukglue_register_method(ctx, &ScTitleSequence::play, "play");
|
||||
dukglue_register_method(ctx, &ScTitleSequence::seek, "seek");
|
||||
dukglue_register_method(ctx, &ScTitleSequence::stop, "stop");
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<size_t> 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<std::shared_ptr<ScTitleSequence>> titleSequences_get() const
|
||||
{
|
||||
std::vector<std::shared_ptr<ScTitleSequence>> 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<ScTitleSequence>(path));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<ScTitleSequence> create(const std::string& name)
|
||||
{
|
||||
auto index = TitleSequenceManager::CreateItem(name.c_str());
|
||||
auto* item = TitleSequenceManager::GetItem(index);
|
||||
if (item != nullptr)
|
||||
{
|
||||
return std::make_shared<ScTitleSequence>(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
|
|
@ -22,6 +22,7 @@
|
|||
# include <openrct2/Context.h>
|
||||
# include <openrct2/Input.h>
|
||||
# include <openrct2/common.h>
|
||||
# include <openrct2/scenario/ScenarioRepository.h>
|
||||
# include <openrct2/scripting/Duktape.hpp>
|
||||
# include <openrct2/scripting/ScriptEngine.h>
|
||||
# include <string>
|
||||
|
@ -38,6 +39,37 @@ namespace OpenRCT2::Ui::Windows
|
|||
|
||||
namespace OpenRCT2::Scripting
|
||||
{
|
||||
static const DukEnumMap<SCENARIO_CATEGORY> ScenarioCategoryMap({
|
||||
{ "beginner", SCENARIO_CATEGORY_BEGINNER },
|
||||
{ "challenging", SCENARIO_CATEGORY_CHALLENGING },
|
||||
{ "expert", SCENARIO_CATEGORY_EXPERT },
|
||||
{ "real", SCENARIO_CATEGORY_REAL },
|
||||
{ "other", SCENARIO_CATEGORY_OTHER },
|
||||
{ "dlc", SCENARIO_CATEGORY_DLC },
|
||||
{ "build_your_own", SCENARIO_CATEGORY_BUILD_YOUR_OWN },
|
||||
});
|
||||
|
||||
static const DukEnumMap<ScenarioSource> ScenarioSourceMap({
|
||||
{ "rct1", ScenarioSource::RCT1 },
|
||||
{ "rct1_aa", ScenarioSource::RCT1_AA },
|
||||
{ "rct1_ll", ScenarioSource::RCT1_LL },
|
||||
{ "rct2", ScenarioSource::RCT2 },
|
||||
{ "rct2_ww", ScenarioSource::RCT2_WW },
|
||||
{ "rct2_tt", ScenarioSource::RCT2_TT },
|
||||
{ "real", ScenarioSource::Real },
|
||||
{ "other", ScenarioSource::Other },
|
||||
});
|
||||
|
||||
template<> inline DukValue ToDuk(duk_context* ctx, const SCENARIO_CATEGORY& value)
|
||||
{
|
||||
return ToDuk(ctx, ScenarioCategoryMap[value]);
|
||||
}
|
||||
|
||||
template<> inline DukValue ToDuk(duk_context* ctx, const ScenarioSource& value)
|
||||
{
|
||||
return ToDuk(ctx, ScenarioSourceMap[value]);
|
||||
}
|
||||
|
||||
class ScTool
|
||||
{
|
||||
private:
|
||||
|
@ -210,6 +242,59 @@ namespace OpenRCT2::Scripting
|
|||
}
|
||||
}
|
||||
|
||||
void showFileBrowse(const DukValue& desc)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto plugin = _scriptEngine.GetExecInfo().GetCurrentPlugin();
|
||||
auto type = desc["type"].as_string();
|
||||
auto fileType = desc["fileType"].as_string();
|
||||
auto defaultPath = AsOrDefault(desc["defaultPath"], "");
|
||||
auto callback = desc["callback"];
|
||||
|
||||
int32_t loadSaveType{};
|
||||
if (type == "load")
|
||||
loadSaveType = LOADSAVETYPE_LOAD;
|
||||
else
|
||||
throw DukException();
|
||||
|
||||
if (fileType == "game")
|
||||
loadSaveType |= LOADSAVETYPE_GAME;
|
||||
else if (fileType == "heightmap")
|
||||
loadSaveType |= LOADSAVETYPE_HEIGHTMAP;
|
||||
else
|
||||
throw DukException();
|
||||
|
||||
window_loadsave_open(
|
||||
loadSaveType, defaultPath,
|
||||
[this, plugin, callback](int32_t result, std::string_view path) {
|
||||
if (result == MODAL_RESULT_OK)
|
||||
{
|
||||
auto dukValue = ToDuk(_scriptEngine.GetContext(), path);
|
||||
_scriptEngine.ExecutePluginCall(plugin, callback, { dukValue }, false);
|
||||
}
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
catch (const DukException&)
|
||||
{
|
||||
duk_error(_scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid parameters.");
|
||||
}
|
||||
}
|
||||
|
||||
void showScenarioSelect(const DukValue& desc)
|
||||
{
|
||||
auto plugin = _scriptEngine.GetExecInfo().GetCurrentPlugin();
|
||||
auto callback = desc["callback"];
|
||||
|
||||
window_scenarioselect_open(
|
||||
[this, plugin, callback](std::string_view path) {
|
||||
auto dukValue = GetScenarioFile(path);
|
||||
_scriptEngine.ExecutePluginCall(plugin, callback, { dukValue }, false);
|
||||
},
|
||||
false, true);
|
||||
}
|
||||
|
||||
void activateTool(const DukValue& desc)
|
||||
{
|
||||
InitialiseCustomTool(_scriptEngine, desc);
|
||||
|
@ -237,6 +322,8 @@ namespace OpenRCT2::Scripting
|
|||
dukglue_register_method(ctx, &ScUi::getWindow, "getWindow");
|
||||
dukglue_register_method(ctx, &ScUi::showError, "showError");
|
||||
dukglue_register_method(ctx, &ScUi::showTextInput, "showTextInput");
|
||||
dukglue_register_method(ctx, &ScUi::showFileBrowse, "showFileBrowse");
|
||||
dukglue_register_method(ctx, &ScUi::showScenarioSelect, "showScenarioSelect");
|
||||
dukglue_register_method(ctx, &ScUi::activateTool, "activateTool");
|
||||
dukglue_register_method(ctx, &ScUi::registerMenuItem, "registerMenuItem");
|
||||
}
|
||||
|
@ -246,6 +333,39 @@ namespace OpenRCT2::Scripting
|
|||
{
|
||||
return WC_NULL;
|
||||
}
|
||||
|
||||
DukValue GetScenarioFile(std::string_view path)
|
||||
{
|
||||
auto ctx = _scriptEngine.GetContext();
|
||||
DukObject obj(ctx);
|
||||
obj.Set("path", path);
|
||||
|
||||
auto* scenarioRepo = GetScenarioRepository();
|
||||
auto entry = scenarioRepo->GetByPath(std::string(path).c_str());
|
||||
if (entry != nullptr)
|
||||
{
|
||||
obj.Set("id", entry->sc_id);
|
||||
obj.Set("category", ToDuk(ctx, static_cast<SCENARIO_CATEGORY>(entry->category)));
|
||||
obj.Set("sourceGame", ToDuk(ctx, entry->source_game));
|
||||
obj.Set("internalName", entry->internal_name);
|
||||
obj.Set("name", entry->name);
|
||||
obj.Set("details", entry->details);
|
||||
|
||||
auto* highscore = entry->highscore;
|
||||
if (highscore == nullptr)
|
||||
{
|
||||
obj.Set("highscore", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
DukObject dukHighscore(ctx);
|
||||
dukHighscore.Set("name", highscore->name);
|
||||
dukHighscore.Set("companyValue", highscore->company_value);
|
||||
obj.Set("highscore", dukHighscore.Take());
|
||||
}
|
||||
}
|
||||
return obj.Take();
|
||||
}
|
||||
};
|
||||
} // namespace OpenRCT2::Scripting
|
||||
|
||||
|
|
|
@ -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<ScTitleSequenceManager>(), "titleSequenceManager");
|
||||
dukglue_register_global(ctx, std::make_shared<ScUi>(scriptEngine), "ui");
|
||||
|
||||
ScTileSelection::Register(ctx);
|
||||
|
@ -43,6 +45,10 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
|
|||
ScSpinnerWidget::Register(ctx);
|
||||
ScTextBoxWidget::Register(ctx);
|
||||
ScViewportWidget::Register(ctx);
|
||||
|
||||
ScTitleSequence::Register(ctx);
|
||||
ScTitleSequenceManager::Register(ctx);
|
||||
ScTitleSequencePark::Register(ctx);
|
||||
ScWindow::Register(ctx);
|
||||
|
||||
InitialiseCustomMenuItems(scriptEngine);
|
||||
|
|
|
@ -126,7 +126,7 @@ struct LoadSaveListItem
|
|||
bool loaded;
|
||||
};
|
||||
|
||||
static loadsave_callback _loadSaveCallback;
|
||||
static std::function<void(int32_t result, std::string_view)> _loadSaveCallback;
|
||||
static TrackDesign* _trackDesign;
|
||||
|
||||
static std::vector<LoadSaveListItem> _listItems;
|
||||
|
@ -134,7 +134,7 @@ static char _directory[MAX_PATH];
|
|||
static char _shortenedDirectory[MAX_PATH];
|
||||
static char _parentDirectory[MAX_PATH];
|
||||
static char _extension[256];
|
||||
static char _defaultName[MAX_PATH];
|
||||
static std::string _defaultPath;
|
||||
static int32_t _type;
|
||||
|
||||
static int32_t maxDateWidth = 0;
|
||||
|
@ -234,17 +234,14 @@ static int32_t window_loadsave_get_dir(const int32_t type, char* path, size_t pa
|
|||
|
||||
static bool browse(bool isSave, char* path, size_t pathSize);
|
||||
|
||||
rct_window* window_loadsave_open(int32_t type, const char* defaultName, loadsave_callback callback, TrackDesign* trackDesign)
|
||||
rct_window* window_loadsave_open(
|
||||
int32_t type, std::string_view defaultPath, std::function<void(int32_t result, std::string_view)> callback,
|
||||
TrackDesign* trackDesign)
|
||||
{
|
||||
_loadSaveCallback = callback;
|
||||
_trackDesign = trackDesign;
|
||||
_type = type;
|
||||
_defaultName[0] = '\0';
|
||||
|
||||
if (!str_is_null_or_empty(defaultName))
|
||||
{
|
||||
safe_strcpy(_defaultName, defaultName, sizeof(_defaultName));
|
||||
}
|
||||
_defaultPath = defaultPath;
|
||||
|
||||
bool isSave = (type & 0x01) == LOADSAVETYPE_SAVE;
|
||||
char path[MAX_PATH];
|
||||
|
@ -394,9 +391,9 @@ static bool browse(bool isSave, char* path, size_t pathSize)
|
|||
if (isSave)
|
||||
{
|
||||
// The file browser requires a file path instead of just a directory
|
||||
if (String::SizeOf(_defaultName) > 0)
|
||||
if (!_defaultPath.empty())
|
||||
{
|
||||
safe_strcat_path(path, _defaultName, pathSize);
|
||||
safe_strcat_path(path, _defaultPath.c_str(), pathSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -454,7 +451,7 @@ static void window_loadsave_mouseup(rct_window* w, rct_widgetindex widgetIndex)
|
|||
case WIDX_NEW_FILE:
|
||||
window_text_input_open(
|
||||
w, WIDX_NEW_FILE, STR_NONE, STR_FILEBROWSER_FILE_NAME_PROMPT, STR_STRING,
|
||||
reinterpret_cast<uintptr_t>(&_defaultName), 64);
|
||||
reinterpret_cast<uintptr_t>(_defaultPath.c_str()), 64);
|
||||
break;
|
||||
|
||||
case WIDX_NEW_FOLDER:
|
||||
|
|
|
@ -130,32 +130,40 @@ static void initialise_list_items(rct_window* w);
|
|||
static bool is_scenario_visible(rct_window* w, const scenario_index_entry* scenario);
|
||||
static bool is_locking_enabled(rct_window* w);
|
||||
|
||||
static scenarioselect_callback _callback;
|
||||
static std::function<void(std::string_view)> _callback;
|
||||
static bool _showLockedInformation = false;
|
||||
static bool _titleEditor = false;
|
||||
static bool _disableLocking{};
|
||||
|
||||
/**
|
||||
*
|
||||
* rct2: 0x006781B5
|
||||
*/
|
||||
rct_window* window_scenarioselect_open(scenarioselect_callback callback, bool titleEditor)
|
||||
{
|
||||
rct_window* window;
|
||||
int32_t windowWidth;
|
||||
int32_t windowHeight = 334;
|
||||
|
||||
_callback = callback;
|
||||
|
||||
if (_titleEditor != titleEditor)
|
||||
{
|
||||
_titleEditor = titleEditor;
|
||||
window_close_by_class(WC_SCENARIO_SELECT);
|
||||
}
|
||||
|
||||
window = window_bring_to_front_by_class(WC_SCENARIO_SELECT);
|
||||
auto window = window_bring_to_front_by_class(WC_SCENARIO_SELECT);
|
||||
if (window != nullptr)
|
||||
return window;
|
||||
|
||||
return window_scenarioselect_open(
|
||||
[callback](std::string_view scenario) { callback(std::string(scenario).c_str()); }, titleEditor, titleEditor);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* rct2: 0x006781B5
|
||||
*/
|
||||
rct_window* window_scenarioselect_open(std::function<void(std::string_view)> callback, bool titleEditor, bool disableLocking)
|
||||
{
|
||||
rct_window* window;
|
||||
int32_t windowWidth;
|
||||
int32_t windowHeight = 334;
|
||||
|
||||
_callback = callback;
|
||||
_disableLocking = disableLocking;
|
||||
|
||||
// Load scenario list
|
||||
scenario_repository_scan();
|
||||
|
||||
|
@ -328,10 +336,7 @@ static void window_scenarioselect_scrollmousedown(rct_window* w, int32_t scrollI
|
|||
OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, w->windowPos.x + (w->width / 2));
|
||||
gFirstTimeSaving = true;
|
||||
_callback(listItem.scenario.scenario->path);
|
||||
if (_titleEditor)
|
||||
{
|
||||
window_close(w);
|
||||
}
|
||||
window_close(w);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -105,11 +105,14 @@ rct_window* window_staff_fire_prompt_open(Peep* peep);
|
|||
void window_title_editor_open(int32_t tab);
|
||||
void window_title_command_editor_open(struct TitleSequence* sequence, int32_t command, bool insert);
|
||||
rct_window* window_scenarioselect_open(scenarioselect_callback callback, bool titleEditor);
|
||||
rct_window* window_scenarioselect_open(std::function<void(std::string_view)> callback, bool titleEditor, bool disableLocking);
|
||||
|
||||
rct_window* window_error_open(rct_string_id title, rct_string_id message, const class Formatter& formatter);
|
||||
rct_window* window_error_open(std::string_view title, std::string_view message);
|
||||
struct TrackDesign;
|
||||
rct_window* window_loadsave_open(int32_t type, const char* defaultName, loadsave_callback callback, TrackDesign* t6Exporter);
|
||||
rct_window* window_loadsave_open(
|
||||
int32_t type, std::string_view defaultPath, std::function<void(int32_t result, std::string_view)> callback,
|
||||
TrackDesign* trackDesign);
|
||||
rct_window* window_track_place_open(const struct track_design_file_ref* tdFileRef);
|
||||
rct_window* window_track_manage_open(struct track_design_file_ref* tdFileRef);
|
||||
|
||||
|
|
|
@ -318,7 +318,7 @@ enum
|
|||
#define S6_RCT2_VERSION 120001
|
||||
#define S6_MAGIC_NUMBER 0x00031144
|
||||
|
||||
enum
|
||||
enum SCENARIO_CATEGORY
|
||||
{
|
||||
// RCT2 categories (keep order)
|
||||
SCENARIO_CATEGORY_BEGINNER,
|
||||
|
|
|
@ -80,6 +80,13 @@ namespace OpenRCT2::Scripting
|
|||
PopObjectIfExists();
|
||||
}
|
||||
|
||||
void Set(const char* name, std::nullptr_t)
|
||||
{
|
||||
EnsureObjectPushed();
|
||||
duk_push_null(_ctx);
|
||||
duk_put_prop_string(_ctx, _idx, name);
|
||||
}
|
||||
|
||||
void Set(const char* name, bool value)
|
||||
{
|
||||
EnsureObjectPushed();
|
||||
|
@ -101,6 +108,13 @@ namespace OpenRCT2::Scripting
|
|||
duk_put_prop_string(_ctx, _idx, name);
|
||||
}
|
||||
|
||||
void Set(const char* name, uint64_t value)
|
||||
{
|
||||
EnsureObjectPushed();
|
||||
duk_push_number(_ctx, value);
|
||||
duk_put_prop_string(_ctx, _idx, name);
|
||||
}
|
||||
|
||||
void Set(const char* name, std::string_view value)
|
||||
{
|
||||
EnsureObjectPushed();
|
||||
|
@ -108,6 +122,11 @@ namespace OpenRCT2::Scripting
|
|||
duk_put_prop_string(_ctx, _idx, name);
|
||||
}
|
||||
|
||||
void Set(const char* name, const char* value)
|
||||
{
|
||||
Set(name, std::string_view(value));
|
||||
}
|
||||
|
||||
void Set(const char* name, const DukValue& value)
|
||||
{
|
||||
EnsureObjectPushed();
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Scripting;
|
||||
|
||||
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 19;
|
||||
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 20;
|
||||
|
||||
struct ExpressionStringifier final
|
||||
{
|
||||
|
|
|
@ -298,6 +298,11 @@ bool TitleScreen::TryLoadSequence(bool loadPreview)
|
|||
{
|
||||
if (_loadedTitleSequenceId != _currentSequence || loadPreview)
|
||||
{
|
||||
if (_sequencePlayer == nullptr)
|
||||
{
|
||||
_sequencePlayer = GetContext()->GetUiContext()->GetTitleSequencePlayer();
|
||||
}
|
||||
|
||||
size_t numSequences = TitleSequenceManager::GetCount();
|
||||
if (numSequences > 0)
|
||||
{
|
||||
|
|
|
@ -498,15 +498,15 @@ static std::string LegacyScriptWrite(const TitleSequence& seq)
|
|||
switch (command.Type)
|
||||
{
|
||||
case TitleScript::Load:
|
||||
if (command.SaveIndex == 0xFF)
|
||||
{
|
||||
sb.Append("LOAD <No save file>");
|
||||
}
|
||||
else
|
||||
if (command.SaveIndex < seq.Saves.size())
|
||||
{
|
||||
sb.Append("LOAD ");
|
||||
sb.Append(seq.Saves[command.SaveIndex].c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("LOAD <No save file>");
|
||||
}
|
||||
break;
|
||||
case TitleScript::LoadSc:
|
||||
if (command.Scenario[0] == '\0')
|
||||
|
|
Loading…
Reference in New Issue