diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index fd3dad75de..8c462582f7 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1397,6 +1397,7 @@ declare global { readonly players: Player[]; readonly currentPlayer: Player; defaultGroup: number; + readonly stats: NetworkStats; getServerInfo(): ServerInfo; addGroup(): void; @@ -1442,6 +1443,11 @@ declare global { readonly providerWebsite: string; } + interface NetworkStats { + bytesReceived: number[]; + bytesSent: number[]; + } + type PermissionType = "chat" | "terraform" | @@ -1996,14 +2002,15 @@ declare global { * Represents the type of a widget, e.g. button or label. */ type WidgetType = - "button" | "checkbox" | "colourpicker" | "dropdown" | "groupbox" | + "button" | "checkbox" | "colourpicker" | "custom" | "dropdown" | "groupbox" | "label" | "listview" | "spinner" | "textbox" | "viewport"; type Widget = - ButtonWidget | CheckboxWidget | ColourPickerWidget | DropdownWidget | GroupBoxWidget | + ButtonWidget | CheckboxWidget | ColourPickerWidget | CustomWidget | DropdownWidget | GroupBoxWidget | LabelWidget | ListView | SpinnerWidget | TextBoxWidget | ViewportWidget; interface WidgetBase { + readonly window?: Window; x: number; y: number; width: number; @@ -2040,6 +2047,11 @@ declare global { onChange?: (colour: number) => void; } + interface CustomWidget extends WidgetBase { + type: 'custom'; + onDraw?: (this: CustomWidget, g: GraphicsContext) => void; + } + interface DropdownWidget extends WidgetBase { type: 'dropdown'; items?: string[]; @@ -2191,6 +2203,21 @@ declare global { scrollTo(position: CoordsXY | CoordsXYZ): void; } + /** + * API for drawing graphics. + */ + interface GraphicsContext { + stroke: number; + fill: number; + + clear(): void; + clip(x: number, y: number, width: number, height: number): void; + line(x1: number, y1: number, x2: number, y2: number): void; + rect(x: number, y: number, width: number, height: number): void; + fillRect(x: number, y: number, width: number, height: number): void; + image(image: number, x: number, y: number): void; + } + /** * Listens for incoming connections. * Based on node.js net.Server, see https://nodejs.org/api/net.html for more information. diff --git a/src/openrct2-ui/interface/Window.cpp b/src/openrct2-ui/interface/Window.cpp index c1f9d09b60..8942e803e2 100644 --- a/src/openrct2-ui/interface/Window.cpp +++ b/src/openrct2-ui/interface/Window.cpp @@ -632,8 +632,15 @@ void WindowDrawWidgets(rct_window* w, rct_drawpixelinfo* dpi) { // Check if widget is outside the draw region if (w->windowPos.x + widget->left < dpi->x + dpi->width && w->windowPos.x + widget->right >= dpi->x) + { if (w->windowPos.y + widget->top < dpi->y + dpi->height && w->windowPos.y + widget->bottom >= dpi->y) - WidgetDraw(dpi, w, widgetIndex); + { + if (w->IsLegacy()) + WidgetDraw(dpi, w, widgetIndex); + else + w->OnDrawWidget(widgetIndex, *dpi); + } + } } widgetIndex++; } @@ -681,6 +688,21 @@ void InvalidateAllWindowsAfterInput() }); } +bool Window::IsLegacy() +{ + return false; +} + +void Window::OnDraw(rct_drawpixelinfo& dpi) +{ + WindowDrawWidgets(this, &dpi); +} + +void Window::OnDrawWidget(rct_widgetindex widgetIndex, rct_drawpixelinfo& dpi) +{ + WidgetDraw(&dpi, this, widgetIndex); +} + void Window::InvalidateWidget(rct_widgetindex widgetIndex) { widget_invalidate(this, widgetIndex); diff --git a/src/openrct2-ui/interface/Window.h b/src/openrct2-ui/interface/Window.h index ee1de33ffa..19b0bacc0a 100644 --- a/src/openrct2-ui/interface/Window.h +++ b/src/openrct2-ui/interface/Window.h @@ -14,6 +14,10 @@ struct Window : rct_window { + virtual bool IsLegacy() override; + virtual void OnDraw(rct_drawpixelinfo& dpi) override; + virtual void OnDrawWidget(rct_widgetindex widgetIndex, rct_drawpixelinfo& dpi) override; + void InvalidateWidget(rct_widgetindex widgetIndex); bool IsWidgetDisabled(rct_widgetindex widgetIndex) const; bool IsWidgetPressed(rct_widgetindex widgetIndex) const; diff --git a/src/openrct2-ui/libopenrct2ui.vcxproj b/src/openrct2-ui/libopenrct2ui.vcxproj index d454e99e66..736db9ad95 100644 --- a/src/openrct2-ui/libopenrct2ui.vcxproj +++ b/src/openrct2-ui/libopenrct2ui.vcxproj @@ -54,6 +54,7 @@ + diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 522fb26ec5..c6306abc3c 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -10,6 +10,8 @@ #ifdef ENABLE_SCRIPTING # include "../interface/Dropdown.h" +# include "../scripting/ScGraphicsContext.hpp" +# include "../scripting/ScWidget.hpp" # include "CustomListView.h" # include "ScUi.hpp" # include "ScWindow.hpp" @@ -83,6 +85,7 @@ namespace OpenRCT2::Ui::Windows // Event handlers DukValue OnClick; DukValue OnChange; + DukValue OnDraw; DukValue OnIncrement; DukValue OnDecrement; DukValue OnHighlight; @@ -131,6 +134,10 @@ namespace OpenRCT2::Ui::Windows } result.OnChange = desc["onChange"]; } + else if (result.Type == "custom") + { + result.OnDraw = desc["onDraw"]; + } else if (result.Type == "dropdown") { if (desc["items"].is_array()) @@ -375,7 +382,7 @@ namespace OpenRCT2::Ui::Windows static void InvokeEventHandler( const std::shared_ptr& owner, const DukValue& dukHandler, const std::vector& args); - class CustomWindow : public rct_window + class CustomWindow : public Window { private: static rct_windownumber _nextWindowNumber; @@ -536,6 +543,37 @@ namespace OpenRCT2::Ui::Windows } } + void OnDrawWidget(rct_widgetindex widgetIndex, rct_drawpixelinfo& dpi) override + { + const auto& widget = widgets[widgetIndex]; + const auto& info = GetInfo(this); + const auto widgetDesc = info.GetCustomWidgetDesc(this, widgetIndex); + if (widgetDesc != nullptr && widgetDesc->Type == "custom") + { + auto& onDraw = widgetDesc->OnDraw; + if (onDraw.is_function()) + { + rct_drawpixelinfo widgetDpi; + if (clip_drawpixelinfo( + &widgetDpi, &dpi, { windowPos.x + widget.left, windowPos.y + widget.top }, widget.width(), + widget.height())) + { + auto ctx = onDraw.context(); + auto dukWidget = ScWidget::ToDukValue(ctx, this, widgetIndex); + auto dukG = GetObjectAsDukValue(ctx, std::make_shared(ctx, widgetDpi)); + auto& scriptEngine = GetContext()->GetScriptEngine(); + scriptEngine.ExecutePluginCall(info.Owner, widgetDesc->OnDraw, dukWidget, { dukG }, false); + } + // auto widgetDpi = dpi.Crop( + // { windowPos.x + widget.left, windowPos.y + widget.top }, { widget.width(), widget.height() }); + } + } + else + { + Window::OnDrawWidget(widgetIndex, dpi); + } + } + void OnMouseUp(rct_widgetindex widgetIndex) override { switch (widgetIndex) @@ -988,6 +1026,11 @@ namespace OpenRCT2::Ui::Windows widget.image = GetColourButtonImage(desc.Colour); widgetList.push_back(widget); } + else if (desc.Type == "custom") + { + widget.type = WindowWidgetType::Custom; + widgetList.push_back(widget); + } else if (desc.Type == "dropdown") { widget.type = WindowWidgetType::DropdownMenu; @@ -1101,6 +1144,8 @@ namespace OpenRCT2::Ui::Windows } }; + rct_windownumber CustomWindow::_nextWindowNumber; + rct_window* window_custom_open(std::shared_ptr owner, DukValue dukDesc) { auto desc = CustomWindowDesc::FromDukValue(dukDesc); diff --git a/src/openrct2-ui/scripting/ScGraphicsContext.hpp b/src/openrct2-ui/scripting/ScGraphicsContext.hpp new file mode 100644 index 0000000000..0313d0a6e8 --- /dev/null +++ b/src/openrct2-ui/scripting/ScGraphicsContext.hpp @@ -0,0 +1,90 @@ +/***************************************************************************** + * 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 + +namespace OpenRCT2::Scripting +{ + class ScGraphicsContext + { + private: + duk_context* _ctx{}; + rct_drawpixelinfo _dpi{}; + + uint8_t _stroke{}; + uint8_t _fill{}; + + public: + ScGraphicsContext(duk_context* ctx, const rct_drawpixelinfo& dpi) + : _ctx(ctx) + , _dpi(dpi) + { + } + + static void Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScGraphicsContext::fill_get, &ScGraphicsContext::fill_set, "fill"); + dukglue_register_property(ctx, &ScGraphicsContext::stroke_get, &ScGraphicsContext::stroke_set, "stroke"); + dukglue_register_method(ctx, &ScGraphicsContext::clear, "clear"); + dukglue_register_method(ctx, &ScGraphicsContext::clip, "clip"); + dukglue_register_method(ctx, &ScGraphicsContext::line, "line"); + dukglue_register_method(ctx, &ScGraphicsContext::fillRect, "fillRect"); + } + + private: + uint8_t fill_get() const + { + return _fill; + } + + void fill_set(uint8_t value) + { + _fill = value; + } + + uint8_t stroke_get() const + { + return _stroke; + } + + void stroke_set(uint8_t value) + { + _stroke = value; + } + + void clear() + { + gfx_clear(&_dpi, _fill); + } + + void clip(int32_t x, int32_t y, int32_t width, int32_t height) + { + rct_drawpixelinfo newDpi; + clip_drawpixelinfo(&newDpi, &_dpi, { x, y }, width, height); + _dpi = newDpi; + } + + void line(int32_t x1, int32_t y1, int32_t x2, int32_t y2) + { + gfx_draw_line(&_dpi, { { x1, y1 }, { x2, y2 } }, _stroke); + } + + void fillRect(int32_t x, int32_t y, int32_t width, int32_t height) + { + gfx_fill_rect(&_dpi, { x, y, x + width - 1, y + height - 1 }, _fill); + } + }; +} // namespace OpenRCT2::Scripting + +#endif diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 2973d7c7b2..6a85ceec98 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -25,6 +25,7 @@ namespace OpenRCT2::Scripting { + class ScWindow; class ScWidget { protected: @@ -43,6 +44,8 @@ namespace OpenRCT2::Scripting static DukValue ToDukValue(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex); private: + std::shared_ptr window_get() const; + std::string name_get() const { auto w = GetWindow(); @@ -376,7 +379,7 @@ namespace OpenRCT2::Scripting public: static void Register(duk_context* ctx) { - // Common + dukglue_register_property(ctx, &ScWidget::window_get, nullptr, "window"); dukglue_register_property(ctx, &ScWidget::name_get, &ScWidget::name_set, "name"); dukglue_register_property(ctx, &ScWidget::type_get, nullptr, "type"); dukglue_register_property(ctx, &ScWidget::x_get, &ScWidget::x_set, "x"); diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp index 316b9af0b2..ea3d4a7ce2 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 "ScGraphicsContext.hpp" # include "ScTileSelection.hpp" # include "ScTitleSequence.hpp" # include "ScUi.hpp" @@ -29,6 +30,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine) dukglue_register_global(ctx, std::make_shared(), "titleSequenceManager"); dukglue_register_global(ctx, std::make_shared(scriptEngine), "ui"); + ScGraphicsContext::Register(ctx); ScTileSelection::Register(ctx); ScTool::Register(ctx); ScUi::Register(ctx); @@ -54,4 +56,9 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine) InitialiseCustomMenuItems(scriptEngine); } +std::shared_ptr ScWidget::window_get() const +{ + return std::make_shared(_class, _number); +} + #endif diff --git a/src/openrct2/interface/Widget.h b/src/openrct2/interface/Widget.h index 565c203242..c4b7a7b333 100644 --- a/src/openrct2/interface/Widget.h +++ b/src/openrct2/interface/Widget.h @@ -36,6 +36,7 @@ enum class WindowWidgetType : uint8_t Scroll = 22, Checkbox = 23, Placeholder = 25, + Custom = 28, TextBox = 27, Last = 26, }; diff --git a/src/openrct2/interface/Window.cpp b/src/openrct2/interface/Window.cpp index 207564a551..087cad7c5c 100644 --- a/src/openrct2/interface/Window.cpp +++ b/src/openrct2/interface/Window.cpp @@ -1532,7 +1532,7 @@ void window_event_scroll_mousedrag_call(rct_window* w, int32_t scrollIndex, cons { if (w->event_handlers == nullptr) w->OnScrollMouseDrag(scrollIndex, screenCoords); - else if (w->event_handlers->scroll_mouseover != nullptr) + else if (w->event_handlers->scroll_mousedrag != nullptr) w->event_handlers->scroll_mousedrag(w, scrollIndex, screenCoords); } diff --git a/src/openrct2/interface/Window_internal.h b/src/openrct2/interface/Window_internal.h index 7189c99c8d..ed4dbc64f1 100644 --- a/src/openrct2/interface/Window_internal.h +++ b/src/openrct2/interface/Window_internal.h @@ -112,6 +112,11 @@ struct rct_window rct_window() = default; virtual ~rct_window() = default; + virtual bool IsLegacy() + { + return true; + } + // Events virtual void OnOpen() { @@ -134,6 +139,9 @@ struct rct_window virtual void OnDraw(rct_drawpixelinfo& dpi) { } + virtual void OnDrawWidget(rct_widgetindex widgetIndex, rct_drawpixelinfo& dpi) + { + } virtual OpenRCT2String OnTooltip(rct_widgetindex widgetIndex, rct_string_id fallback) { return { fallback, {} }; diff --git a/src/openrct2/scripting/ScNetwork.hpp b/src/openrct2/scripting/ScNetwork.hpp index 9497eaf600..ae8ddec4b1 100644 --- a/src/openrct2/scripting/ScNetwork.hpp +++ b/src/openrct2/scripting/ScNetwork.hpp @@ -369,6 +369,39 @@ namespace OpenRCT2::Scripting return nullptr; } + DukValue stats_get() const + { +# ifndef DISABLE_NETWORK + auto obj = OpenRCT2::Scripting::DukObject(_context); + auto networkStats = network_get_stats(); + { + duk_push_array(_context); + duk_uarridx_t index = 0; + for (auto v : networkStats.bytesReceived) + { + duk_push_number(_context, v); + duk_put_prop_index(_context, -2, index); + index++; + } + obj.Set("bytesReceived", DukValue::take_from_stack(_context)); + } + { + duk_push_array(_context); + duk_uarridx_t index = 0; + for (auto v : networkStats.bytesSent) + { + duk_push_number(_context, v); + duk_put_prop_index(_context, -2, index); + index++; + } + obj.Set("bytesSent", DukValue::take_from_stack(_context)); + } + return obj.Take(); +# else + return ToDuk(_context, nullptr); +# endif + } + std::shared_ptr getGroup(int32_t index) const { # ifndef DISABLE_NETWORK @@ -490,6 +523,7 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScNetwork::players_get, nullptr, "players"); dukglue_register_property(ctx, &ScNetwork::currentPlayer_get, nullptr, "currentPlayer"); dukglue_register_property(ctx, &ScNetwork::defaultGroup_get, &ScNetwork::defaultGroup_set, "defaultGroup"); + dukglue_register_property(ctx, &ScNetwork::stats_get, nullptr, "stats"); dukglue_register_method(ctx, &ScNetwork::addGroup, "addGroup"); dukglue_register_method(ctx, &ScNetwork::getGroup, "getGroup"); dukglue_register_method(ctx, &ScNetwork::removeGroup, "removeGroup"); diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index b7c3911337..9f639dcbe6 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -691,17 +691,27 @@ std::future ScriptEngine::Eval(const std::string& s) DukValue ScriptEngine::ExecutePluginCall( const std::shared_ptr& plugin, const DukValue& func, const std::vector& args, bool isGameStateMutable) +{ + duk_push_undefined(_context); + auto dukUndefined = DukValue::take_from_stack(_context); + return ExecutePluginCall(plugin, func, dukUndefined, args, isGameStateMutable); +} + +DukValue ScriptEngine::ExecutePluginCall( + const std::shared_ptr& plugin, const DukValue& func, const DukValue& thisValue, const std::vector& args, + bool isGameStateMutable) { DukStackFrame frame(_context); if (func.is_function()) { ScriptExecutionInfo::PluginScope scope(_execInfo, plugin, isGameStateMutable); func.push(); + thisValue.push(); for (const auto& arg : args) { arg.push(); } - auto result = duk_pcall(_context, static_cast(args.size())); + auto result = duk_pcall_method(_context, static_cast(args.size())); if (result == DUK_EXEC_SUCCESS) { return DukValue::take_from_stack(_context); diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index 9a4c034b90..0c5ba8df20 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -202,6 +202,9 @@ namespace OpenRCT2::Scripting DukValue ExecutePluginCall( const std::shared_ptr& plugin, const DukValue& func, const std::vector& args, bool isGameStateMutable); + DukValue ExecutePluginCall( + const std::shared_ptr& plugin, const DukValue& func, const DukValue& thisValue, + const std::vector& args, bool isGameStateMutable); void LogPluginInfo(const std::shared_ptr& plugin, std::string_view message);