1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-23 15:52:55 +01:00

Add custom widget logic and network stats API

This commit is contained in:
Ted John
2021-01-31 23:23:22 +00:00
parent 82b1a5eb6d
commit 93f17f1175
14 changed files with 262 additions and 7 deletions

View File

@@ -1397,6 +1397,7 @@ declare global {
readonly players: Player[]; readonly players: Player[];
readonly currentPlayer: Player; readonly currentPlayer: Player;
defaultGroup: number; defaultGroup: number;
readonly stats: NetworkStats;
getServerInfo(): ServerInfo; getServerInfo(): ServerInfo;
addGroup(): void; addGroup(): void;
@@ -1442,6 +1443,11 @@ declare global {
readonly providerWebsite: string; readonly providerWebsite: string;
} }
interface NetworkStats {
bytesReceived: number[];
bytesSent: number[];
}
type PermissionType = type PermissionType =
"chat" | "chat" |
"terraform" | "terraform" |
@@ -1996,14 +2002,15 @@ declare global {
* Represents the type of a widget, e.g. button or label. * Represents the type of a widget, e.g. button or label.
*/ */
type WidgetType = type WidgetType =
"button" | "checkbox" | "colourpicker" | "dropdown" | "groupbox" | "button" | "checkbox" | "colourpicker" | "custom" | "dropdown" | "groupbox" |
"label" | "listview" | "spinner" | "textbox" | "viewport"; "label" | "listview" | "spinner" | "textbox" | "viewport";
type Widget = type Widget =
ButtonWidget | CheckboxWidget | ColourPickerWidget | DropdownWidget | GroupBoxWidget | ButtonWidget | CheckboxWidget | ColourPickerWidget | CustomWidget | DropdownWidget | GroupBoxWidget |
LabelWidget | ListView | SpinnerWidget | TextBoxWidget | ViewportWidget; LabelWidget | ListView | SpinnerWidget | TextBoxWidget | ViewportWidget;
interface WidgetBase { interface WidgetBase {
readonly window?: Window;
x: number; x: number;
y: number; y: number;
width: number; width: number;
@@ -2040,6 +2047,11 @@ declare global {
onChange?: (colour: number) => void; onChange?: (colour: number) => void;
} }
interface CustomWidget extends WidgetBase {
type: 'custom';
onDraw?: (this: CustomWidget, g: GraphicsContext) => void;
}
interface DropdownWidget extends WidgetBase { interface DropdownWidget extends WidgetBase {
type: 'dropdown'; type: 'dropdown';
items?: string[]; items?: string[];
@@ -2191,6 +2203,21 @@ declare global {
scrollTo(position: CoordsXY | CoordsXYZ): void; 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. * Listens for incoming connections.
* Based on node.js net.Server, see https://nodejs.org/api/net.html for more information. * Based on node.js net.Server, see https://nodejs.org/api/net.html for more information.

View File

@@ -632,8 +632,15 @@ void WindowDrawWidgets(rct_window* w, rct_drawpixelinfo* dpi)
{ {
// Check if widget is outside the draw region // 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.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) 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++; 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) void Window::InvalidateWidget(rct_widgetindex widgetIndex)
{ {
widget_invalidate(this, widgetIndex); widget_invalidate(this, widgetIndex);

View File

@@ -14,6 +14,10 @@
struct Window : rct_window 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); void InvalidateWidget(rct_widgetindex widgetIndex);
bool IsWidgetDisabled(rct_widgetindex widgetIndex) const; bool IsWidgetDisabled(rct_widgetindex widgetIndex) const;
bool IsWidgetPressed(rct_widgetindex widgetIndex) const; bool IsWidgetPressed(rct_widgetindex widgetIndex) const;

View File

@@ -54,6 +54,7 @@
<ClInclude Include="scripting\CustomListView.h" /> <ClInclude Include="scripting\CustomListView.h" />
<ClInclude Include="scripting\CustomMenu.h" /> <ClInclude Include="scripting\CustomMenu.h" />
<ClInclude Include="scripting\CustomWindow.h" /> <ClInclude Include="scripting\CustomWindow.h" />
<ClInclude Include="scripting\ScGraphicsContext.hpp" />
<ClInclude Include="scripting\ScTileSelection.hpp" /> <ClInclude Include="scripting\ScTileSelection.hpp" />
<ClInclude Include="scripting\ScTitleSequence.hpp" /> <ClInclude Include="scripting\ScTitleSequence.hpp" />
<ClInclude Include="scripting\ScUi.hpp" /> <ClInclude Include="scripting\ScUi.hpp" />

View File

@@ -10,6 +10,8 @@
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING
# include "../interface/Dropdown.h" # include "../interface/Dropdown.h"
# include "../scripting/ScGraphicsContext.hpp"
# include "../scripting/ScWidget.hpp"
# include "CustomListView.h" # include "CustomListView.h"
# include "ScUi.hpp" # include "ScUi.hpp"
# include "ScWindow.hpp" # include "ScWindow.hpp"
@@ -83,6 +85,7 @@ namespace OpenRCT2::Ui::Windows
// Event handlers // Event handlers
DukValue OnClick; DukValue OnClick;
DukValue OnChange; DukValue OnChange;
DukValue OnDraw;
DukValue OnIncrement; DukValue OnIncrement;
DukValue OnDecrement; DukValue OnDecrement;
DukValue OnHighlight; DukValue OnHighlight;
@@ -131,6 +134,10 @@ namespace OpenRCT2::Ui::Windows
} }
result.OnChange = desc["onChange"]; result.OnChange = desc["onChange"];
} }
else if (result.Type == "custom")
{
result.OnDraw = desc["onDraw"];
}
else if (result.Type == "dropdown") else if (result.Type == "dropdown")
{ {
if (desc["items"].is_array()) if (desc["items"].is_array())
@@ -375,7 +382,7 @@ namespace OpenRCT2::Ui::Windows
static void InvokeEventHandler( static void InvokeEventHandler(
const std::shared_ptr<Plugin>& owner, const DukValue& dukHandler, const std::vector<DukValue>& args); const std::shared_ptr<Plugin>& owner, const DukValue& dukHandler, const std::vector<DukValue>& args);
class CustomWindow : public rct_window class CustomWindow : public Window
{ {
private: private:
static rct_windownumber _nextWindowNumber; 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<ScGraphicsContext>(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 void OnMouseUp(rct_widgetindex widgetIndex) override
{ {
switch (widgetIndex) switch (widgetIndex)
@@ -988,6 +1026,11 @@ namespace OpenRCT2::Ui::Windows
widget.image = GetColourButtonImage(desc.Colour); widget.image = GetColourButtonImage(desc.Colour);
widgetList.push_back(widget); widgetList.push_back(widget);
} }
else if (desc.Type == "custom")
{
widget.type = WindowWidgetType::Custom;
widgetList.push_back(widget);
}
else if (desc.Type == "dropdown") else if (desc.Type == "dropdown")
{ {
widget.type = WindowWidgetType::DropdownMenu; widget.type = WindowWidgetType::DropdownMenu;
@@ -1101,6 +1144,8 @@ namespace OpenRCT2::Ui::Windows
} }
}; };
rct_windownumber CustomWindow::_nextWindowNumber;
rct_window* window_custom_open(std::shared_ptr<Plugin> owner, DukValue dukDesc) rct_window* window_custom_open(std::shared_ptr<Plugin> owner, DukValue dukDesc)
{ {
auto desc = CustomWindowDesc::FromDukValue(dukDesc); auto desc = CustomWindowDesc::FromDukValue(dukDesc);

View File

@@ -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 <openrct2/drawing/Drawing.h>
# include <openrct2/scripting/Duktape.hpp>
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

View File

@@ -25,6 +25,7 @@
namespace OpenRCT2::Scripting namespace OpenRCT2::Scripting
{ {
class ScWindow;
class ScWidget class ScWidget
{ {
protected: protected:
@@ -43,6 +44,8 @@ namespace OpenRCT2::Scripting
static DukValue ToDukValue(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex); static DukValue ToDukValue(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex);
private: private:
std::shared_ptr<ScWindow> window_get() const;
std::string name_get() const std::string name_get() const
{ {
auto w = GetWindow(); auto w = GetWindow();
@@ -376,7 +379,7 @@ namespace OpenRCT2::Scripting
public: public:
static void Register(duk_context* ctx) 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::name_get, &ScWidget::name_set, "name");
dukglue_register_property(ctx, &ScWidget::type_get, nullptr, "type"); dukglue_register_property(ctx, &ScWidget::type_get, nullptr, "type");
dukglue_register_property(ctx, &ScWidget::x_get, &ScWidget::x_set, "x"); dukglue_register_property(ctx, &ScWidget::x_get, &ScWidget::x_set, "x");

View File

@@ -12,6 +12,7 @@
# include "UiExtensions.h" # include "UiExtensions.h"
# include "CustomMenu.h" # include "CustomMenu.h"
# include "ScGraphicsContext.hpp"
# include "ScTileSelection.hpp" # include "ScTileSelection.hpp"
# include "ScTitleSequence.hpp" # include "ScTitleSequence.hpp"
# include "ScUi.hpp" # include "ScUi.hpp"
@@ -29,6 +30,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
dukglue_register_global(ctx, std::make_shared<ScTitleSequenceManager>(), "titleSequenceManager"); dukglue_register_global(ctx, std::make_shared<ScTitleSequenceManager>(), "titleSequenceManager");
dukglue_register_global(ctx, std::make_shared<ScUi>(scriptEngine), "ui"); dukglue_register_global(ctx, std::make_shared<ScUi>(scriptEngine), "ui");
ScGraphicsContext::Register(ctx);
ScTileSelection::Register(ctx); ScTileSelection::Register(ctx);
ScTool::Register(ctx); ScTool::Register(ctx);
ScUi::Register(ctx); ScUi::Register(ctx);
@@ -54,4 +56,9 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
InitialiseCustomMenuItems(scriptEngine); InitialiseCustomMenuItems(scriptEngine);
} }
std::shared_ptr<ScWindow> ScWidget::window_get() const
{
return std::make_shared<ScWindow>(_class, _number);
}
#endif #endif

View File

@@ -36,6 +36,7 @@ enum class WindowWidgetType : uint8_t
Scroll = 22, Scroll = 22,
Checkbox = 23, Checkbox = 23,
Placeholder = 25, Placeholder = 25,
Custom = 28,
TextBox = 27, TextBox = 27,
Last = 26, Last = 26,
}; };

View File

@@ -1532,7 +1532,7 @@ void window_event_scroll_mousedrag_call(rct_window* w, int32_t scrollIndex, cons
{ {
if (w->event_handlers == nullptr) if (w->event_handlers == nullptr)
w->OnScrollMouseDrag(scrollIndex, screenCoords); 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); w->event_handlers->scroll_mousedrag(w, scrollIndex, screenCoords);
} }

View File

@@ -112,6 +112,11 @@ struct rct_window
rct_window() = default; rct_window() = default;
virtual ~rct_window() = default; virtual ~rct_window() = default;
virtual bool IsLegacy()
{
return true;
}
// Events // Events
virtual void OnOpen() virtual void OnOpen()
{ {
@@ -134,6 +139,9 @@ struct rct_window
virtual void OnDraw(rct_drawpixelinfo& dpi) 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) virtual OpenRCT2String OnTooltip(rct_widgetindex widgetIndex, rct_string_id fallback)
{ {
return { fallback, {} }; return { fallback, {} };

View File

@@ -369,6 +369,39 @@ namespace OpenRCT2::Scripting
return nullptr; 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<ScPlayerGroup> getGroup(int32_t index) const std::shared_ptr<ScPlayerGroup> getGroup(int32_t index) const
{ {
# ifndef DISABLE_NETWORK # ifndef DISABLE_NETWORK
@@ -490,6 +523,7 @@ namespace OpenRCT2::Scripting
dukglue_register_property(ctx, &ScNetwork::players_get, nullptr, "players"); dukglue_register_property(ctx, &ScNetwork::players_get, nullptr, "players");
dukglue_register_property(ctx, &ScNetwork::currentPlayer_get, nullptr, "currentPlayer"); 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::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::addGroup, "addGroup");
dukglue_register_method(ctx, &ScNetwork::getGroup, "getGroup"); dukglue_register_method(ctx, &ScNetwork::getGroup, "getGroup");
dukglue_register_method(ctx, &ScNetwork::removeGroup, "removeGroup"); dukglue_register_method(ctx, &ScNetwork::removeGroup, "removeGroup");

View File

@@ -691,17 +691,27 @@ std::future<void> ScriptEngine::Eval(const std::string& s)
DukValue ScriptEngine::ExecutePluginCall( DukValue ScriptEngine::ExecutePluginCall(
const std::shared_ptr<Plugin>& plugin, const DukValue& func, const std::vector<DukValue>& args, bool isGameStateMutable) const std::shared_ptr<Plugin>& plugin, const DukValue& func, const std::vector<DukValue>& 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>& plugin, const DukValue& func, const DukValue& thisValue, const std::vector<DukValue>& args,
bool isGameStateMutable)
{ {
DukStackFrame frame(_context); DukStackFrame frame(_context);
if (func.is_function()) if (func.is_function())
{ {
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin, isGameStateMutable); ScriptExecutionInfo::PluginScope scope(_execInfo, plugin, isGameStateMutable);
func.push(); func.push();
thisValue.push();
for (const auto& arg : args) for (const auto& arg : args)
{ {
arg.push(); arg.push();
} }
auto result = duk_pcall(_context, static_cast<duk_idx_t>(args.size())); auto result = duk_pcall_method(_context, static_cast<duk_idx_t>(args.size()));
if (result == DUK_EXEC_SUCCESS) if (result == DUK_EXEC_SUCCESS)
{ {
return DukValue::take_from_stack(_context); return DukValue::take_from_stack(_context);

View File

@@ -202,6 +202,9 @@ namespace OpenRCT2::Scripting
DukValue ExecutePluginCall( DukValue ExecutePluginCall(
const std::shared_ptr<Plugin>& plugin, const DukValue& func, const std::vector<DukValue>& args, const std::shared_ptr<Plugin>& plugin, const DukValue& func, const std::vector<DukValue>& args,
bool isGameStateMutable); bool isGameStateMutable);
DukValue ExecutePluginCall(
const std::shared_ptr<Plugin>& plugin, const DukValue& func, const DukValue& thisValue,
const std::vector<DukValue>& args, bool isGameStateMutable);
void LogPluginInfo(const std::shared_ptr<Plugin>& plugin, std::string_view message); void LogPluginInfo(const std::shared_ptr<Plugin>& plugin, std::string_view message);