mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-19 04:53:12 +01:00
Implement custom tools
This commit is contained in:
39
distribution/openrct2.d.ts
vendored
39
distribution/openrct2.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user