1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-19 04:53:12 +01:00

Implement custom tools

This commit is contained in:
Ted John
2020-05-01 21:32:41 +01:00
parent 0ca52e58fd
commit be0e4a2869
7 changed files with 314 additions and 29 deletions

View File

@@ -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;

View File

@@ -11,12 +11,25 @@
# include "CustomMenu.h"
# include <openrct2/Input.h>
# include <openrct2/world/Map.h>
# include <openrct2/world/Sprite.h>
namespace OpenRCT2::Scripting
{
std::optional<CustomTool> ActiveCustomTool;
std::vector<CustomToolbarMenuItem> CustomMenuItems;
static void RemoveMenuItems(std::shared_ptr<Plugin> owner)
static void RemoveMenuItemsAndTool(std::shared_ptr<Plugin> 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> plugin) -> void { RemoveMenuItems(plugin); });
scriptEngine.SubscribeToPluginStoppedEvent(
[](std::shared_ptr<Plugin> 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<rct_sprite*>(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<DukValue> 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<TOOL_IDX>(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<size_t>(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<CURSOR_ID>(std::distance(std::begin(CursorNames), it));
}
return CURSOR_UNDEFINED;
}
} // namespace OpenRCT2::Scripting
#endif

View File

@@ -13,6 +13,7 @@
# include <memory>
# include <openrct2/Context.h>
# include <openrct2/interface/Cursors.h>
# include <openrct2/scripting/Duktape.hpp>
# include <openrct2/scripting/ScriptEngine.h>
# include <string>
@@ -41,9 +42,38 @@ namespace OpenRCT2::Scripting
}
};
struct CustomTool
{
std::shared_ptr<Plugin> 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<CustomTool> ActiveCustomTool;
extern std::vector<CustomToolbarMenuItem> 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

View File

@@ -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())
{

View File

@@ -16,8 +16,10 @@
# include "ScViewport.hpp"
# include "ScWindow.hpp"
# include <algorithm>
# include <memory>
# include <openrct2/Context.h>
# include <openrct2/Input.h>
# include <openrct2/common.h>
# include <openrct2/scripting/Duktape.hpp>
# include <openrct2/scripting/ScriptEngine.h>
@@ -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<ScTileSelection>(_scriptEngine.GetContext());
}
std::shared_ptr<ScTool> tool_get() const
{
if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
{
return std::make_shared<ScTool>();
}
return {};
}
std::shared_ptr<ScWindow> 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");
}

View File

@@ -28,6 +28,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
dukglue_register_global(ctx, std::make_shared<ScUi>(scriptEngine), "ui");
ScTileSelection::Register(ctx);
ScTool::Register(ctx);
ScUi::Register(ctx);
ScViewport::Register(ctx);
ScWidget::Register(ctx);

View File

@@ -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
}
}