diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 8a2e80e6f5..05e0c936ad 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -719,6 +719,7 @@ declare global { readonly windows: number; readonly mainViewport: Viewport; readonly tileSelection: TileSelection; + readonly tool: Tool; getWindow(id: number): Window; getWindow(classification: string): Window; @@ -741,44 +742,30 @@ declare global { tiles: Coord2[]; } - /** - * The type of tool. - * Raw will provide only the screen coordinates of where the cursor is. - * Tile will provide a coordinates of the tile the cursor is over. - * Interact will provide information about the ride or entity the cursor is over. - */ - type ToolType = "raw" | "tile" | "interact"; + interface Tool { + id: string; + cursor: CursorType; - type InteractionType = - "none" | - "surface" | - "sprite" | - "ride" | - "water" | - "scenery" | - "footpath" | - "footpath_addition" | - "park" | - "wall" | - "large_scenery" | - "label" | - "banner"; + cancel: () => void; + } interface ToolEventArgs { - kind: InteractionType; - screenCoords: Coord2; - mapCoords?: Coord3; - tileElementIndex?: number; - entity?: Entity; + readonly isDown: boolean; + readonly screenCoords: Coord2; + readonly mapCoords?: Coord3; + readonly tileElementIndex?: number; + readonly entityId?: number; } /** * Describes the properties and event handlers for a custom tool. */ interface ToolDesc { + id: string; type: ToolType; cursor: CursorType; + onStart: () => void; onDown: (e: ToolEventArgs) => void; onMove: (e: ToolEventArgs) => void; onUp: (e: ToolEventArgs) => void; diff --git a/src/openrct2-ui/scripting/CustomMenu.cpp b/src/openrct2-ui/scripting/CustomMenu.cpp index a2d7ef47b4..37a0322357 100644 --- a/src/openrct2-ui/scripting/CustomMenu.cpp +++ b/src/openrct2-ui/scripting/CustomMenu.cpp @@ -11,12 +11,25 @@ # include "CustomMenu.h" +# include +# include +# include + namespace OpenRCT2::Scripting { + std::optional ActiveCustomTool; std::vector CustomMenuItems; - static void RemoveMenuItems(std::shared_ptr owner) + static void RemoveMenuItemsAndTool(std::shared_ptr owner) { + if (ActiveCustomTool) + { + if (ActiveCustomTool->Owner == owner) + { + tool_cancel(); + } + } + auto& menuItems = CustomMenuItems; for (auto it = menuItems.begin(); it != menuItems.end();) { @@ -33,8 +46,169 @@ namespace OpenRCT2::Scripting void InitialiseCustomMenuItems(ScriptEngine& scriptEngine) { - scriptEngine.SubscribeToPluginStoppedEvent([](std::shared_ptr plugin) -> void { RemoveMenuItems(plugin); }); + scriptEngine.SubscribeToPluginStoppedEvent( + [](std::shared_ptr plugin) -> void { RemoveMenuItemsAndTool(plugin); }); } + + void CustomTool::OnUpdate(const ScreenCoordsXY& screenCoords) + { + InvokeEventHandler(onMove, screenCoords); + } + + void CustomTool::OnDown(const ScreenCoordsXY& screenCoords) + { + MouseDown = true; + InvokeEventHandler(onDown, screenCoords); + } + + void CustomTool::OnDrag(const ScreenCoordsXY& screenCoords) + { + } + + void CustomTool::Start() + { + auto& scriptEngine = GetContext()->GetScriptEngine(); + scriptEngine.ExecutePluginCall(Owner, onStart, {}, false); + } + + void CustomTool::OnUp(const ScreenCoordsXY& screenCoords) + { + MouseDown = false; + InvokeEventHandler(onUp, screenCoords); + } + + void CustomTool::OnAbort() + { + auto& scriptEngine = GetContext()->GetScriptEngine(); + scriptEngine.ExecutePluginCall(Owner, onFinish, {}, false); + } + + static DukValue CoordsXYToDuk(duk_context* ctx, const CoordsXY& coords) + { + DukObject dukCoords(ctx); + dukCoords.Set("x", coords.x); + dukCoords.Set("y", coords.y); + return dukCoords.Take(); + } + + static DukValue CoordsXYToDuk(duk_context* ctx, const ScreenCoordsXY& coords) + { + DukObject dukCoords(ctx); + dukCoords.Set("x", coords.x); + dukCoords.Set("y", coords.y); + return dukCoords.Take(); + } + + void CustomTool::InvokeEventHandler(const DukValue& dukHandler, const ScreenCoordsXY& screenCoords) + { + if (dukHandler.is_function()) + { + auto ctx = dukHandler.context(); + + auto flags = 0; + CoordsXY mapCoords{}; + int32_t interactionType = 0; + TileElement* tileElement{}; + get_map_coordinates_from_pos(screenCoords, flags, mapCoords, &interactionType, &tileElement, nullptr); + + DukObject obj(dukHandler.context()); + obj.Set("isDown", MouseDown); + obj.Set("screenCoords", CoordsXYToDuk(ctx, screenCoords)); + obj.Set("mapCoords", CoordsXYToDuk(ctx, mapCoords)); + + if (interactionType == VIEWPORT_INTERACTION_ITEM_SPRITE && tileElement != nullptr) + { + // get_map_coordinates_from_pos returns the sprite using tileElement... ugh + auto sprite = reinterpret_cast(tileElement); + obj.Set("entityId", sprite->generic.sprite_index); + } + else if (tileElement != nullptr) + { + int32_t index = 0; + auto el = map_get_first_element_at(mapCoords); + if (el != nullptr) + { + do + { + if (el == tileElement) + { + obj.Set("tileElementIndex", index); + break; + } + index++; + } while (!(el++)->IsLastForTile()); + } + } + auto eventArgs = obj.Take(); + + auto& scriptEngine = GetContext()->GetScriptEngine(); + std::vector args; + args.push_back(eventArgs); + scriptEngine.ExecutePluginCall(Owner, dukHandler, args, false); + } + } + + void InitialiseCustomTool(ScriptEngine& scriptEngine, const DukValue& dukValue) + { + try + { + if (dukValue.type() == DukValue::Type::OBJECT) + { + CustomTool customTool; + customTool.Owner = scriptEngine.GetExecInfo().GetCurrentPlugin(); + customTool.Id = dukValue["id"].as_string(); + customTool.Cursor = StringToCursor(dukValue["cursor"].as_string()); + customTool.onStart = dukValue["onStart"]; + customTool.onDown = dukValue["onDown"]; + customTool.onMove = dukValue["onMove"]; + customTool.onUp = dukValue["onUp"]; + customTool.onFinish = dukValue["onFinish"]; + + auto toolbarWindow = window_find_by_class(WC_TOP_TOOLBAR); + if (toolbarWindow != nullptr) + { + // Use a widget that does not exist on top toolbar but also make sure it isn't -1 as that + // prevents abort from being called. + rct_widgetindex widgetIndex = -2; + tool_cancel(); + tool_set(toolbarWindow, widgetIndex, static_cast(customTool.Cursor)); + ActiveCustomTool = std::move(customTool); + ActiveCustomTool->Start(); + } + } + } + catch (const DukException&) + { + duk_error(scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid parameters."); + } + } + + static constexpr const char* CursorNames[] = { + "arrow", "blank", "up_arrow", "up_down_arrow", "hand_point", "zzz", "diagonal_arrows", + "picker", "tree_down", "fountain_down", "statue_down", "bench_down", "cross_hair", "bin_down", + "lamppost_down", "fence_down", "flower_down", "path_down", "dig_down", "water_down", "house_down", + "volcano_down", "walk_down", "paint_down", "entrance_down", "hand_open", "hand_closed", + }; + + std::string CursorToString(int32_t cursor) + { + if (cursor >= 0 && static_cast(cursor) < std::size(CursorNames)) + { + return CursorNames[cursor]; + } + return {}; + } + + CURSOR_ID StringToCursor(const std::string_view& cursor) + { + auto it = std::find(std::begin(CursorNames), std::end(CursorNames), cursor); + if (it != std::end(CursorNames)) + { + return static_cast(std::distance(std::begin(CursorNames), it)); + } + return CURSOR_UNDEFINED; + } + } // namespace OpenRCT2::Scripting #endif diff --git a/src/openrct2-ui/scripting/CustomMenu.h b/src/openrct2-ui/scripting/CustomMenu.h index 6d41982e21..71f4894d64 100644 --- a/src/openrct2-ui/scripting/CustomMenu.h +++ b/src/openrct2-ui/scripting/CustomMenu.h @@ -13,6 +13,7 @@ # include # include +# include # include # include # include @@ -41,9 +42,38 @@ namespace OpenRCT2::Scripting } }; + struct CustomTool + { + std::shared_ptr Owner; + std::string Id; + CURSOR_ID Cursor{}; + bool MouseDown{}; + + // Event handlers + DukValue onStart; + DukValue onDown; + DukValue onMove; + DukValue onUp; + DukValue onFinish; + + void Start(); + void OnUpdate(const ScreenCoordsXY& screenCoords); + void OnDown(const ScreenCoordsXY& screenCoords); + void OnDrag(const ScreenCoordsXY& screenCoords); + void OnUp(const ScreenCoordsXY& screenCoords); + void OnAbort(); + + private: + void InvokeEventHandler(const DukValue& dukHandler, const ScreenCoordsXY& screenCoords); + }; + + extern std::optional ActiveCustomTool; extern std::vector CustomMenuItems; void InitialiseCustomMenuItems(ScriptEngine& scriptEngine); + void InitialiseCustomTool(ScriptEngine& scriptEngine, const DukValue& dukValue); + std::string CursorToString(int32_t cursor); + CURSOR_ID StringToCursor(const std::string_view& cursor); } // namespace OpenRCT2::Scripting diff --git a/src/openrct2-ui/scripting/ScTileSelection.hpp b/src/openrct2-ui/scripting/ScTileSelection.hpp index 84d5472a63..4f982cac55 100644 --- a/src/openrct2-ui/scripting/ScTileSelection.hpp +++ b/src/openrct2-ui/scripting/ScTileSelection.hpp @@ -54,6 +54,7 @@ namespace OpenRCT2::Scripting void range_set(DukValue value) { + map_invalidate_selection_rect(); if (value.type() == DukValue::Type::OBJECT) { auto range = GetMapRange(value); @@ -96,6 +97,7 @@ namespace OpenRCT2::Scripting void tiles_set(DukValue value) { + map_invalidate_map_selection_tiles(); gMapSelectionTiles.clear(); if (value.is_array()) { diff --git a/src/openrct2-ui/scripting/ScUi.hpp b/src/openrct2-ui/scripting/ScUi.hpp index eda29c5826..cf6020a59c 100644 --- a/src/openrct2-ui/scripting/ScUi.hpp +++ b/src/openrct2-ui/scripting/ScUi.hpp @@ -16,8 +16,10 @@ # include "ScViewport.hpp" # include "ScWindow.hpp" +# include # include # include +# include # include # include # include @@ -35,6 +37,33 @@ namespace OpenRCT2::Ui::Windows namespace OpenRCT2::Scripting { + class ScTool + { + public: + static void Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScTool::id_get, nullptr, "id"); + dukglue_register_property(ctx, &ScTool::cursor_get, nullptr, "cursor"); + dukglue_register_method(ctx, &ScTool::cancel, "cancel"); + } + + private: + std::string id_get() const + { + return ActiveCustomTool ? ActiveCustomTool->Id : ""; + } + + std::string cursor_get() const + { + return CursorToString(gCurrentToolId); + } + + void cancel() + { + tool_cancel(); + } + }; + class ScUi { private: @@ -70,6 +99,15 @@ namespace OpenRCT2::Scripting return std::make_shared(_scriptEngine.GetContext()); } + std::shared_ptr tool_get() const + { + if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE)) + { + return std::make_shared(); + } + return {}; + } + std::shared_ptr openWindow(DukValue desc) { using namespace OpenRCT2::Ui::Windows; @@ -134,6 +172,11 @@ namespace OpenRCT2::Scripting return {}; } + void activateTool(const DukValue& desc) + { + InitialiseCustomTool(_scriptEngine, desc); + } + void registerMenuItem(std::string text, DukValue callback) { auto& execInfo = _scriptEngine.GetExecInfo(); @@ -149,10 +192,12 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScUi::windows_get, nullptr, "windows"); dukglue_register_property(ctx, &ScUi::mainViewport_get, nullptr, "mainViewport"); dukglue_register_property(ctx, &ScUi::tileSelection_get, nullptr, "tileSelection"); + dukglue_register_property(ctx, &ScUi::tool_get, nullptr, "tool"); dukglue_register_method(ctx, &ScUi::openWindow, "openWindow"); 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::activateTool, "activateTool"); dukglue_register_method(ctx, &ScUi::registerMenuItem, "registerMenuItem"); } diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp index 1bfd49b26d..54a00df453 100644 --- a/src/openrct2-ui/scripting/UiExtensions.cpp +++ b/src/openrct2-ui/scripting/UiExtensions.cpp @@ -28,6 +28,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine) dukglue_register_global(ctx, std::make_shared(scriptEngine), "ui"); ScTileSelection::Register(ctx); + ScTool::Register(ctx); ScUi::Register(ctx); ScViewport::Register(ctx); ScWidget::Register(ctx); diff --git a/src/openrct2-ui/windows/TopToolbar.cpp b/src/openrct2-ui/windows/TopToolbar.cpp index 265ac9856f..8ffa06d91d 100644 --- a/src/openrct2-ui/windows/TopToolbar.cpp +++ b/src/openrct2-ui/windows/TopToolbar.cpp @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2014-2019 OpenRCT2 developers + * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 @@ -2961,6 +2961,15 @@ static void window_top_toolbar_tool_update(rct_window* w, rct_widgetindex widget case WIDX_SCENERY: top_toolbar_tool_update_scenery(screenCoords); break; +#ifdef ENABLE_SCRIPTING + default: + auto& customTool = OpenRCT2::Scripting::ActiveCustomTool; + if (customTool) + { + customTool->OnUpdate(screenCoords); + } + break; +#endif } } @@ -3009,6 +3018,15 @@ static void window_top_toolbar_tool_down(rct_window* w, rct_widgetindex widgetIn case WIDX_SCENERY: window_top_toolbar_scenery_tool_down(screenCoords, w, widgetIndex); break; +#ifdef ENABLE_SCRIPTING + default: + auto& customTool = OpenRCT2::Scripting::ActiveCustomTool; + if (customTool) + { + customTool->OnDown(screenCoords); + } + break; +#endif } } @@ -3227,6 +3245,15 @@ static void window_top_toolbar_tool_drag(rct_window* w, rct_widgetindex widgetIn if (gWindowSceneryEyedropperEnabled) window_top_toolbar_scenery_tool_down(screenCoords, w, widgetIndex); break; +#ifdef ENABLE_SCRIPTING + default: + auto& customTool = OpenRCT2::Scripting::ActiveCustomTool; + if (customTool) + { + customTool->OnDrag(screenCoords); + } + break; +#endif } } @@ -3254,6 +3281,15 @@ static void window_top_toolbar_tool_up(rct_window* w, rct_widgetindex widgetInde gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; gCurrentToolId = TOOL_CROSSHAIR; break; +#ifdef ENABLE_SCRIPTING + default: + auto& customTool = OpenRCT2::Scripting::ActiveCustomTool; + if (customTool) + { + customTool->OnUp(screenCoords); + } + break; +#endif } } @@ -3270,6 +3306,16 @@ static void window_top_toolbar_tool_abort(rct_window* w, rct_widgetindex widgetI case WIDX_CLEAR_SCENERY: hide_gridlines(); break; +#ifdef ENABLE_SCRIPTING + default: + auto& customTool = OpenRCT2::Scripting::ActiveCustomTool; + if (customTool) + { + customTool->OnAbort(); + customTool = {}; + } + break; +#endif } }