From 0ca52e58fd25c49b871e135a625ee9056ddb9708 Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 1 May 2020 17:04:37 +0100 Subject: [PATCH 1/4] Implement tile selection API --- distribution/openrct2.d.ts | 94 +++++++++ src/openrct2-ui/scripting/ScTileSelection.hpp | 179 ++++++++++++++++++ src/openrct2-ui/scripting/ScUi.hpp | 7 + src/openrct2-ui/scripting/UiExtensions.cpp | 2 + 4 files changed, 282 insertions(+) create mode 100644 src/openrct2-ui/scripting/ScTileSelection.hpp diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 798635f3a2..8a2e80e6f5 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -71,6 +71,14 @@ declare global { z: number; } + /** + * A rectangular area specified using two coordinates. + */ + interface MapRange { + leftTop: Coord2; + rightBottom: Coord2; + } + /** * Represents information about the plugin such as type, name, author and version. * It also includes the entry point. @@ -710,6 +718,7 @@ declare global { readonly height: number; readonly windows: number; readonly mainViewport: Viewport; + readonly tileSelection: TileSelection; getWindow(id: number): Window; getWindow(classification: string): Window; @@ -717,9 +726,94 @@ declare global { closeWindows(classification: string, id?: number): void; closeAllWindows(): void; + /** + * Begins a new tool session. The cursor will change to the style specified by the + * given tool descriptor and cursor events will be provided. + * @param tool The properties and event handlers for the tool. + */ + activateTool(tool: ToolDesc): void; + registerMenuItem(text: string, callback: () => void): void; } + interface TileSelection { + range: MapRange; + 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"; + + type InteractionType = + "none" | + "surface" | + "sprite" | + "ride" | + "water" | + "scenery" | + "footpath" | + "footpath_addition" | + "park" | + "wall" | + "large_scenery" | + "label" | + "banner"; + + interface ToolEventArgs { + kind: InteractionType; + screenCoords: Coord2; + mapCoords?: Coord3; + tileElementIndex?: number; + entity?: Entity; + } + + /** + * Describes the properties and event handlers for a custom tool. + */ + interface ToolDesc { + type: ToolType; + cursor: CursorType; + + onDown: (e: ToolEventArgs) => void; + onMove: (e: ToolEventArgs) => void; + onUp: (e: ToolEventArgs) => void; + onFinish: () => void; + } + + type CursorType = + "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"; + /** * Represents the type of a widget, e.g. button or label. */ diff --git a/src/openrct2-ui/scripting/ScTileSelection.hpp b/src/openrct2-ui/scripting/ScTileSelection.hpp new file mode 100644 index 0000000000..84d5472a63 --- /dev/null +++ b/src/openrct2-ui/scripting/ScTileSelection.hpp @@ -0,0 +1,179 @@ +/***************************************************************************** + * 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 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#ifdef ENABLE_SCRIPTING + +# include +# include +# include + +namespace OpenRCT2::Scripting +{ + class ScTileSelection + { + private: + duk_context* _ctx{}; + + public: + ScTileSelection(duk_context* ctx) + : _ctx(ctx) + { + } + + DukValue range_get() const + { + if (gMapSelectFlags & MAP_SELECT_FLAG_ENABLE) + { + DukObject range(_ctx); + + DukObject leftTop(_ctx); + leftTop.Set("x", gMapSelectPositionA.x); + leftTop.Set("y", gMapSelectPositionA.y); + range.Set("leftTop", leftTop.Take()); + + DukObject rightBottom(_ctx); + rightBottom.Set("x", gMapSelectPositionB.x); + rightBottom.Set("y", gMapSelectPositionB.y); + range.Set("rightBottom", rightBottom.Take()); + return range.Take(); + } + else + { + duk_push_null(_ctx); + return DukValue::take_from_stack(_ctx); + } + } + + void range_set(DukValue value) + { + if (value.type() == DukValue::Type::OBJECT) + { + auto range = GetMapRange(value); + if (range) + { + gMapSelectPositionA.x = range->GetLeft(); + gMapSelectPositionA.y = range->GetTop(); + gMapSelectPositionB.x = range->GetRight(); + gMapSelectPositionB.y = range->GetBottom(); + gMapSelectType = MAP_SELECT_TYPE_FULL; + gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; + } + } + else + { + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; + } + map_invalidate_selection_rect(); + } + + DukValue tiles_get() const + { + duk_push_array(_ctx); + if (gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT) + { + duk_uarridx_t index = 0; + for (const auto& tile : gMapSelectionTiles) + { + duk_push_object(_ctx); + duk_push_int(_ctx, tile.x); + duk_put_prop_string(_ctx, -2, "x"); + duk_push_int(_ctx, tile.y); + duk_put_prop_string(_ctx, -2, "y"); + duk_put_prop_index(_ctx, -2, index); + index++; + } + } + return DukValue::take_from_stack(_ctx); + } + + void tiles_set(DukValue value) + { + gMapSelectionTiles.clear(); + if (value.is_array()) + { + value.push(); + auto arrayLen = duk_get_length(_ctx, -1); + for (duk_uarridx_t i = 0; i < arrayLen; i++) + { + if (duk_get_prop_index(_ctx, -1, i)) + { + auto dukElement = DukValue::take_from_stack(_ctx); + auto coords = GetCoordsXY(dukElement); + if (coords) + { + gMapSelectionTiles.push_back(*coords); + } + } + } + duk_pop(_ctx); + } + + if (gMapSelectionTiles.empty()) + { + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; + gMapSelectFlags &= ~MAP_SELECT_FLAG_GREEN; + } + else + { + gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_CONSTRUCT; + } + map_invalidate_map_selection_tiles(); + } + + static void Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScTileSelection::range_get, &ScTileSelection::range_set, "range"); + dukglue_register_property(ctx, &ScTileSelection::tiles_get, &ScTileSelection::tiles_set, "tiles"); + } + + private: + static std::optional GetCoordsXY(const DukValue& dukCoords) + { + std::optional result; + if (dukCoords.type() == DukValue::Type::OBJECT) + { + auto dukX = dukCoords["x"]; + if (dukX.type() == DukValue::Type::NUMBER) + { + auto dukY = dukCoords["y"]; + if (dukY.type() == DukValue::Type::NUMBER) + { + CoordsXY coords; + coords.x = dukX.as_int(); + coords.y = dukY.as_int(); + result = coords; + } + } + } + return result; + } + + static std::optional GetMapRange(const DukValue& dukMapRange) + { + std::optional result; + if (dukMapRange.type() == DukValue::Type::OBJECT) + { + auto leftTop = GetCoordsXY(dukMapRange["leftTop"]); + if (leftTop.has_value()) + { + auto rightBottom = GetCoordsXY(dukMapRange["rightBottom"]); + if (rightBottom.has_value()) + { + result = MapRange(leftTop->x, leftTop->y, rightBottom->x, rightBottom->y); + } + } + } + return result; + } + }; +} // namespace OpenRCT2::Scripting + +#endif diff --git a/src/openrct2-ui/scripting/ScUi.hpp b/src/openrct2-ui/scripting/ScUi.hpp index d1b2f5094c..eda29c5826 100644 --- a/src/openrct2-ui/scripting/ScUi.hpp +++ b/src/openrct2-ui/scripting/ScUi.hpp @@ -12,6 +12,7 @@ #ifdef ENABLE_SCRIPTING # include "CustomMenu.h" +# include "ScTileSelection.hpp" # include "ScViewport.hpp" # include "ScWindow.hpp" @@ -64,6 +65,11 @@ namespace OpenRCT2::Scripting return std::make_shared(WC_MAIN_WINDOW); } + std::shared_ptr tileSelection_get() const + { + return std::make_shared(_scriptEngine.GetContext()); + } + std::shared_ptr openWindow(DukValue desc) { using namespace OpenRCT2::Ui::Windows; @@ -142,6 +148,7 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScUi::width_get, nullptr, "width"); 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_method(ctx, &ScUi::openWindow, "openWindow"); dukglue_register_method(ctx, &ScUi::closeWindows, "closeWindows"); dukglue_register_method(ctx, &ScUi::closeAllWindows, "closeAllWindows"); diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp index de973d5ef7..1bfd49b26d 100644 --- a/src/openrct2-ui/scripting/UiExtensions.cpp +++ b/src/openrct2-ui/scripting/UiExtensions.cpp @@ -12,6 +12,7 @@ # include "UiExtensions.h" # include "CustomMenu.h" +# include "ScTileSelection.hpp" # include "ScUi.hpp" # include "ScWidget.hpp" # include "ScWindow.hpp" @@ -26,6 +27,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine) dukglue_register_global(ctx, std::make_shared(scriptEngine), "ui"); + ScTileSelection::Register(ctx); ScUi::Register(ctx); ScViewport::Register(ctx); ScWidget::Register(ctx); From be0e4a2869267efeed94b5a42f8d9c88a1383aa8 Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 1 May 2020 21:32:41 +0100 Subject: [PATCH 2/4] Implement custom tools --- distribution/openrct2.d.ts | 39 ++-- src/openrct2-ui/scripting/CustomMenu.cpp | 178 +++++++++++++++++- src/openrct2-ui/scripting/CustomMenu.h | 30 +++ src/openrct2-ui/scripting/ScTileSelection.hpp | 2 + src/openrct2-ui/scripting/ScUi.hpp | 45 +++++ src/openrct2-ui/scripting/UiExtensions.cpp | 1 + src/openrct2-ui/windows/TopToolbar.cpp | 48 ++++- 7 files changed, 314 insertions(+), 29 deletions(-) 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 } } From e5b05747d30116faeed38adb90c1d8a7984e8622 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 2 May 2020 11:38:33 +0100 Subject: [PATCH 3/4] Apply code review refactors --- distribution/openrct2.d.ts | 41 ++++---- src/openrct2-ui/scripting/CustomMenu.cpp | 99 ++++++++++--------- src/openrct2-ui/scripting/CustomMenu.h | 5 +- src/openrct2-ui/scripting/ScTileSelection.hpp | 5 +- src/openrct2-ui/scripting/ScUi.hpp | 14 ++- src/openrct2/scripting/Duktape.hpp | 4 + 6 files changed, 93 insertions(+), 75 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 05e0c936ad..3a83276a6d 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -762,8 +762,7 @@ declare global { */ interface ToolDesc { id: string; - type: ToolType; - cursor: CursorType; + cursor?: CursorType; onStart: () => void; onDown: (e: ToolEventArgs) => void; @@ -774,32 +773,32 @@ declare global { type CursorType = "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" | + "blank" | + "cross_hair" | + "diagonal_arrows" | + "dig_down" | + "entrance_down" | "fence_down" | "flower_down" | - "path_down" | - "dig_down" | - "water_down" | + "fountain_down" | + "hand_closed" | + "hand_open" | + "hand_point" | "house_down" | + "lamppost_down" | + "paint_down" | + "path_down" | + "picker" | + "statue_down" | + "tree_down" | + "up_arrow" | + "up_down_arrow" | "volcano_down" | "walk_down" | - "paint_down" | - "entrance_down" | - "hand_open" | - "hand_closed"; + "water_down" | + "zzz"; /** * Represents the type of a widget, e.g. button or label. diff --git a/src/openrct2-ui/scripting/CustomMenu.cpp b/src/openrct2-ui/scripting/CustomMenu.cpp index 37a0322357..34333ad903 100644 --- a/src/openrct2-ui/scripting/CustomMenu.cpp +++ b/src/openrct2-ui/scripting/CustomMenu.cpp @@ -20,6 +20,53 @@ namespace OpenRCT2::Scripting std::optional ActiveCustomTool; std::vector CustomMenuItems; + static constexpr std::array 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", + }; + + template<> DukValue ToDuk(duk_context* ctx, const CURSOR_ID& value) + { + if (value >= 0 && static_cast(value) < std::size(CursorNames)) + { + auto str = CursorNames[value]; + duk_push_lstring(ctx, str.data(), str.size()); + DukValue::take_from_stack(ctx); + } + return {}; + } + + template<> CURSOR_ID FromDuk(const DukValue& s) + { + if (s.type() == DukValue::Type::STRING) + { + auto it = std::find(std::begin(CursorNames), std::end(CursorNames), s.as_c_string()); + if (it != std::end(CursorNames)) + { + return static_cast(std::distance(std::begin(CursorNames), it)); + } + } + return CURSOR_UNDEFINED; + } + + template<> DukValue ToDuk(duk_context* ctx, const CoordsXY& coords) + { + DukObject dukCoords(ctx); + dukCoords.Set("x", coords.x); + dukCoords.Set("y", coords.y); + return dukCoords.Take(); + } + + template<> DukValue ToDuk(duk_context* ctx, const ScreenCoordsXY& coords) + { + DukObject dukCoords(ctx); + dukCoords.Set("x", coords.x); + dukCoords.Set("y", coords.y); + return dukCoords.Take(); + } + static void RemoveMenuItemsAndTool(std::shared_ptr owner) { if (ActiveCustomTool) @@ -83,22 +130,6 @@ namespace OpenRCT2::Scripting 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()) @@ -113,8 +144,8 @@ namespace OpenRCT2::Scripting DukObject obj(dukHandler.context()); obj.Set("isDown", MouseDown); - obj.Set("screenCoords", CoordsXYToDuk(ctx, screenCoords)); - obj.Set("mapCoords", CoordsXYToDuk(ctx, mapCoords)); + obj.Set("screenCoords", ToDuk(ctx, screenCoords)); + obj.Set("mapCoords", ToDuk(ctx, mapCoords)); if (interactionType == VIEWPORT_INTERACTION_ITEM_SPRITE && tileElement != nullptr) { @@ -157,7 +188,11 @@ namespace OpenRCT2::Scripting CustomTool customTool; customTool.Owner = scriptEngine.GetExecInfo().GetCurrentPlugin(); customTool.Id = dukValue["id"].as_string(); - customTool.Cursor = StringToCursor(dukValue["cursor"].as_string()); + customTool.Cursor = FromDuk(dukValue["cursor"]); + if (customTool.Cursor == CURSOR_UNDEFINED) + { + customTool.Cursor = CURSOR_ARROW; + } customTool.onStart = dukValue["onStart"]; customTool.onDown = dukValue["onDown"]; customTool.onMove = dukValue["onMove"]; @@ -183,32 +218,6 @@ namespace OpenRCT2::Scripting } } - 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 71f4894d64..743fa111f0 100644 --- a/src/openrct2-ui/scripting/CustomMenu.h +++ b/src/openrct2-ui/scripting/CustomMenu.h @@ -72,8 +72,9 @@ namespace OpenRCT2::Scripting 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); + + template<> DukValue ToDuk(duk_context* ctx, const CURSOR_ID& value); + template<> CURSOR_ID FromDuk(const DukValue& s); } // namespace OpenRCT2::Scripting diff --git a/src/openrct2-ui/scripting/ScTileSelection.hpp b/src/openrct2-ui/scripting/ScTileSelection.hpp index 4f982cac55..850d180378 100644 --- a/src/openrct2-ui/scripting/ScTileSelection.hpp +++ b/src/openrct2-ui/scripting/ScTileSelection.hpp @@ -148,10 +148,7 @@ namespace OpenRCT2::Scripting auto dukY = dukCoords["y"]; if (dukY.type() == DukValue::Type::NUMBER) { - CoordsXY coords; - coords.x = dukX.as_int(); - coords.y = dukY.as_int(); - result = coords; + result = { dukX.as_int(), dukY.as_int() }; } } } diff --git a/src/openrct2-ui/scripting/ScUi.hpp b/src/openrct2-ui/scripting/ScUi.hpp index cf6020a59c..b4e527a738 100644 --- a/src/openrct2-ui/scripting/ScUi.hpp +++ b/src/openrct2-ui/scripting/ScUi.hpp @@ -39,7 +39,15 @@ namespace OpenRCT2::Scripting { class ScTool { + private: + duk_context* _ctx{}; + public: + ScTool(duk_context* ctx) + : _ctx(ctx) + { + } + static void Register(duk_context* ctx) { dukglue_register_property(ctx, &ScTool::id_get, nullptr, "id"); @@ -53,9 +61,9 @@ namespace OpenRCT2::Scripting return ActiveCustomTool ? ActiveCustomTool->Id : ""; } - std::string cursor_get() const + DukValue cursor_get() const { - return CursorToString(gCurrentToolId); + return ToDuk(_ctx, static_cast(gCurrentToolId)); } void cancel() @@ -103,7 +111,7 @@ namespace OpenRCT2::Scripting { if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE)) { - return std::make_shared(); + return std::make_shared(_scriptEngine.GetContext()); } return {}; } diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index 71ac11ab14..fe47adf8d1 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -189,6 +189,10 @@ namespace OpenRCT2::Scripting return std::nullopt; } } + + template DukValue ToDuk(duk_context* ctx, const T& value) = delete; + template T FromDuk(const DukValue& s) = delete; + } // namespace OpenRCT2::Scripting #endif From 1e56ae0d99a225179b7d87fbc4e0a147f06f59d5 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 2 May 2020 11:42:43 +0100 Subject: [PATCH 4/4] Rename Coords2 and Coords3 in d.ts to same names as in code base --- distribution/openrct2.d.ts | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 3a83276a6d..5220fbabb1 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -53,11 +53,19 @@ declare global { dispose(): void; } + /** + * A coordinate within the game's client screen in pixels. + */ + interface ScreenCoordsXY { + x: number; + y: number; + } + /** * A coordinate within the game. * Each in-game tile is a size of 32x32. */ - interface Coord2 { + interface CoordsXY { x: number; y: number; } @@ -67,7 +75,7 @@ declare global { * Each in-game tile is a size of 32x32. * The z-coordinate raises 16 per land increment. A full-height wall is 32 in height. */ - interface Coord3 extends Coord2 { + interface CoordsXYZ extends CoordsXY { z: number; } @@ -75,8 +83,8 @@ declare global { * A rectangular area specified using two coordinates. */ interface MapRange { - leftTop: Coord2; - rightBottom: Coord2; + leftTop: CoordsXY; + rightBottom: CoordsXY; } /** @@ -253,7 +261,7 @@ declare global { error?: string; errorTitle?: string; errorMessage?: string; - position?: Coord3; + position?: CoordsXYZ; cost?: number; expenditureType?: ExpenditureType; } @@ -336,7 +344,7 @@ declare global { * APIs for the map. */ interface GameMap { - readonly size: Coord2; + readonly size: CoordsXY; readonly numRides: number; readonly numEntities: number; readonly rides: Ride[]; @@ -739,7 +747,7 @@ declare global { interface TileSelection { range: MapRange; - tiles: Coord2[]; + tiles: CoordsXY[]; } interface Tool { @@ -751,8 +759,8 @@ declare global { interface ToolEventArgs { readonly isDown: boolean; - readonly screenCoords: Coord2; - readonly mapCoords?: Coord3; + readonly screenCoords: ScreenCoordsXY; + readonly mapCoords?: CoordsXYZ; readonly tileElementIndex?: number; readonly entityId?: number; } @@ -902,7 +910,7 @@ declare global { frameBase: number; frameCount?: number; frameDuration?: number; - offset?: Coord2; + offset?: ScreenCoordsXY; } interface WindowTabDesc { @@ -919,8 +927,8 @@ declare global { zoom: number; visibilityFlags: number; - getCentrePosition(): Coord2; - moveTo(position: Coord2 | Coord3): void; - scrollTo(position: Coord2 | Coord3): void; + getCentrePosition(): CoordsXY; + moveTo(position: CoordsXY | CoordsXYZ): void; + scrollTo(position: CoordsXY | CoordsXYZ): void; } }