diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index a1e36d3067..ffa0bc929e 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -439,6 +439,7 @@ export interface Ui { closeAllWindows(): void; activateTool(options: ToolDesc): IDisposable; + registerMenuItem(text: string, callback: () => void): void; } /** diff --git a/src/openrct2-ui/scripting/CustomMenu.cpp b/src/openrct2-ui/scripting/CustomMenu.cpp new file mode 100644 index 0000000000..63e79e3b67 --- /dev/null +++ b/src/openrct2-ui/scripting/CustomMenu.cpp @@ -0,0 +1,36 @@ +/***************************************************************************** + * Copyright (c) 2020 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 "CustomMenu.h" + +namespace OpenRCT2::Scripting +{ + std::vector CustomMenuItems; + + static void RemoveMenuItems(std::shared_ptr owner) + { + auto& menuItems = CustomMenuItems; + for (auto it = menuItems.begin(); it != menuItems.end();) + { + if (it->Owner == owner) + { + it = menuItems.erase(it); + } + else + { + it++; + } + } + } + + void InitialiseCustomMenuItems(ScriptEngine& scriptEngine) + { + scriptEngine.SubscribeToPluginStoppedEvent([](std::shared_ptr plugin) -> void { RemoveMenuItems(plugin); }); + } +} // namespace OpenRCT2::Scripting diff --git a/src/openrct2-ui/scripting/CustomMenu.h b/src/openrct2-ui/scripting/CustomMenu.h new file mode 100644 index 0000000000..4a26fb1a56 --- /dev/null +++ b/src/openrct2-ui/scripting/CustomMenu.h @@ -0,0 +1,52 @@ +/***************************************************************************** + * Copyright (c) 2020 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 +#include +#include +#include +#include +#include + +namespace OpenRCT2::Scripting +{ + class CustomToolbarMenuItem + { + public: + std::shared_ptr Owner; + std::string Text; + DukValue Callback; + + CustomToolbarMenuItem(std::shared_ptr owner, std::string text, DukValue callback) + : Owner(owner) + , Text(text) + , Callback(callback) + { + } + + void Invoke() const + { + auto& scriptEngine = GetContext()->GetScriptEngine(); + auto& execInfo = scriptEngine.GetExecInfo(); + auto ctx = scriptEngine.GetContext(); + + ScriptExecutionInfo::PluginScope scope(execInfo, Owner); + Callback.push(); + duk_pcall(ctx, 0); + duk_pop(ctx); + } + }; + + extern std::vector CustomMenuItems; + + void InitialiseCustomMenuItems(ScriptEngine& scriptEngine); + +} // namespace OpenRCT2::Scripting diff --git a/src/openrct2-ui/scripting/ScUi.hpp b/src/openrct2-ui/scripting/ScUi.hpp index 5484286a6f..cd47708af6 100644 --- a/src/openrct2-ui/scripting/ScUi.hpp +++ b/src/openrct2-ui/scripting/ScUi.hpp @@ -9,6 +9,7 @@ #pragma once +#include "CustomMenu.h" #include "ScWindow.hpp" #include @@ -105,6 +106,13 @@ namespace OpenRCT2::Scripting return nullptr; } + void registerMenuItem(std::string text, DukValue callback) + { + auto& execInfo = _scriptEngine.GetExecInfo(); + auto owner = execInfo.GetCurrentPlugin(); + CustomMenuItems.emplace_back(owner, text, callback); + } + static void Register(duk_context* ctx) { dukglue_register_property(ctx, &ScUi::height_get, nullptr, "height"); @@ -114,6 +122,7 @@ namespace OpenRCT2::Scripting dukglue_register_method(ctx, &ScUi::closeWindows, "closeWindows"); dukglue_register_method(ctx, &ScUi::closeAllWindows, "closeAllWindows"); dukglue_register_method(ctx, &ScUi::getWindow, "getWindow"); + dukglue_register_method(ctx, &ScUi::registerMenuItem, "registerMenuItem"); } private: diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp index 773d466cd8..84b23901f2 100644 --- a/src/openrct2-ui/scripting/UiExtensions.cpp +++ b/src/openrct2-ui/scripting/UiExtensions.cpp @@ -9,6 +9,7 @@ #include "UiExtensions.h" +#include "CustomMenu.h" #include "ScUi.hpp" #include "ScWindow.hpp" @@ -24,4 +25,6 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine) ScUi::Register(ctx); ScWindow::Register(ctx); + + InitialiseCustomMenuItems(scriptEngine); } diff --git a/src/openrct2-ui/windows/TopToolbar.cpp b/src/openrct2-ui/windows/TopToolbar.cpp index 0de292c018..29be4af650 100644 --- a/src/openrct2-ui/windows/TopToolbar.cpp +++ b/src/openrct2-ui/windows/TopToolbar.cpp @@ -9,6 +9,7 @@ #include "../UiContext.h" #include "../interface/InGameConsole.h" +#include "../scripting/CustomMenu.h" #include #include @@ -305,6 +306,8 @@ static rct_window_event_list window_top_toolbar_events = { static void top_toolbar_init_view_menu(rct_window* window, rct_widget* widget); static void top_toolbar_view_menu_dropdown(int16_t dropdownIndex); +static void top_toolbar_init_map_menu(rct_window* window, rct_widget* widget); +static void top_toolbar_map_menu_dropdown(int16_t dropdownIndex); static void top_toolbar_init_fastforward_menu(rct_window* window, rct_widget* widget); static void top_toolbar_fastforward_menu_dropdown(int16_t dropdownIndex); static void top_toolbar_init_rotate_menu(rct_window* window, rct_widget* widget); @@ -521,20 +524,7 @@ static void window_top_toolbar_mousedown(rct_window* w, rct_widgetindex widgetIn top_toolbar_init_view_menu(w, widget); break; case WIDX_MAP: - gDropdownItemsFormat[0] = STR_SHORTCUT_SHOW_MAP; - gDropdownItemsFormat[1] = STR_EXTRA_VIEWPORT; - numItems = 2; - - if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && gS6Info.editor_step == EDITOR_STEP_LANDSCAPE_EDITOR) - { - gDropdownItemsFormat[2] = STR_MAPGEN_WINDOW_TITLE; - numItems++; - } - - window_dropdown_show_text( - w->windowPos.x + widget->left, w->windowPos.y + widget->top, widget->bottom - widget->top + 1, - w->colours[1] | 0x80, 0, numItems); - gDropdownDefaultIndex = DDIDX_SHOW_MAP; + top_toolbar_init_map_menu(w, widget); break; case WIDX_FASTFORWARD: top_toolbar_init_fastforward_menu(w, widget); @@ -651,18 +641,7 @@ static void window_top_toolbar_dropdown(rct_window* w, rct_widgetindex widgetInd top_toolbar_view_menu_dropdown(dropdownIndex); break; case WIDX_MAP: - switch (dropdownIndex) - { - case 0: - context_open_window(WC_MAP); - break; - case 1: - context_open_window(WC_VIEWPORT); - break; - case 2: - context_open_window(WC_MAPGEN); - break; - } + top_toolbar_map_menu_dropdown(dropdownIndex); break; case WIDX_FASTFORWARD: top_toolbar_fastforward_menu_dropdown(dropdownIndex); @@ -3293,6 +3272,68 @@ static void window_top_toolbar_tool_abort(rct_window* w, rct_widgetindex widgetI } } +static void top_toolbar_init_map_menu(rct_window* w, rct_widget* widget) +{ + auto i = 0; + gDropdownItemsFormat[i++] = STR_SHORTCUT_SHOW_MAP; + gDropdownItemsFormat[i++] = STR_EXTRA_VIEWPORT; + if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && gS6Info.editor_step == EDITOR_STEP_LANDSCAPE_EDITOR) + { + gDropdownItemsFormat[i++] = STR_MAPGEN_WINDOW_TITLE; + } + + const auto& customMenuItems = OpenRCT2::Scripting::CustomMenuItems; + if (!customMenuItems.empty()) + { + gDropdownItemsFormat[i++] = STR_EMPTY; + for (const auto& item : customMenuItems) + { + gDropdownItemsFormat[i] = STR_STRING; + set_format_arg_on((uint8_t*)&gDropdownItemsArgs[i], 0, const char*, item.Text.c_str()); + i++; + } + } + + window_dropdown_show_text( + w->windowPos.x + widget->left, w->windowPos.y + widget->top, widget->bottom - widget->top + 1, w->colours[1] | 0x80, 0, + i); + gDropdownDefaultIndex = DDIDX_SHOW_MAP; +} + +static void top_toolbar_map_menu_dropdown(int16_t dropdownIndex) +{ + int32_t customStartIndex = 3; + if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && gS6Info.editor_step == EDITOR_STEP_LANDSCAPE_EDITOR) + { + customStartIndex++; + } + + if (dropdownIndex < customStartIndex) + { + switch (dropdownIndex) + { + case 0: + context_open_window(WC_MAP); + break; + case 1: + context_open_window(WC_VIEWPORT); + break; + case 2: + context_open_window(WC_MAPGEN); + break; + } + } + else + { + const auto& customMenuItems = OpenRCT2::Scripting::CustomMenuItems; + auto customIndex = dropdownIndex - customStartIndex; + if (customMenuItems.size() > customIndex) + { + customMenuItems[customIndex].Invoke(); + } + } +} + static void top_toolbar_init_fastforward_menu(rct_window* w, rct_widget* widget) { int32_t num_items = 4; diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 9a318c9098..aa3c4fdebf 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -136,6 +136,28 @@ void ScriptEngine::LoadPlugin(const std::string& path) } } +void ScriptEngine::StopPlugin(std::shared_ptr plugin) +{ + if (plugin->HasStarted()) + { + _hookEngine.UnsubscribeAll(plugin); + for (auto callback : _pluginStoppedSubscriptions) + { + callback(plugin); + } + + ScriptExecutionInfo::PluginScope scope(_execInfo, plugin); + try + { + plugin->Stop(); + } + catch (const std::exception& e) + { + _console.WriteLineError(e.what()); + } + } +} + bool ScriptEngine::ShouldLoadScript(const std::string& path) { // A lot of JavaScript is often found in a node_modules directory tree and is most likely unwanted, so ignore it @@ -174,7 +196,7 @@ void ScriptEngine::AutoReloadPlugins() auto& plugin = *findResult; try { - _hookEngine.UnsubscribeAll(plugin); + StopPlugin(plugin); ScriptExecutionInfo::PluginScope scope(_execInfo, plugin); plugin->Load(); @@ -225,21 +247,9 @@ void ScriptEngine::StartPlugins() void ScriptEngine::StopPlugins() { - _hookEngine.UnsubscribeAll(); for (auto& plugin : _plugins) { - if (plugin->HasStarted()) - { - ScriptExecutionInfo::PluginScope scope(_execInfo, plugin); - try - { - plugin->Stop(); - } - catch (const std::exception& e) - { - _console.WriteLineError(e.what()); - } - } + StopPlugin(plugin); } _pluginsStarted = false; } diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index 70556f436a..c83ba577af 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -105,6 +105,7 @@ namespace OpenRCT2::Scripting std::unique_ptr _pluginFileWatcher; std::unordered_set _changedPluginFiles; std::mutex _changedPluginFilesMutex; + std::vector)>> _pluginStoppedSubscriptions; public: ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env); @@ -130,11 +131,17 @@ namespace OpenRCT2::Scripting void LogPluginInfo(const std::shared_ptr& plugin, const std::string_view& message); + void SubscribeToPluginStoppedEvent(std::function)> callback) + { + _pluginStoppedSubscriptions.push_back(callback); + } + private: void Initialise(); void StartPlugins(); void StopPlugins(); void LoadPlugin(const std::string& path); + void StopPlugin(std::shared_ptr plugin); bool ShouldLoadScript(const std::string& path); void SetupHotReloading(); void AutoReloadPlugins();