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