diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 5edc75169f..5a707d838a 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -70,6 +70,14 @@ declare global { y: number; } + /** + * Represents the width and height in pixels. + */ + interface ScreenSize { + width: number; + height: number; + } + /** * A coordinate within the game. * Each in-game tile is a size of 32x32. @@ -2207,19 +2215,40 @@ declare global { * API for drawing graphics. */ interface GraphicsContext { - colour: number; + colour: number | undefined; + secondaryColour: number | undefined; + ternaryColour: number | undefined; stroke: number; fill: number; + paletteId: number | undefined; + readonly width: number; + readonly height: number; + + getImage(id: number): ImageInfo | undefined; + measureText(text: string): ScreenSize; clear(): void; clip(x: number, y: number, width: number, height: number): void; box(x: number, y: number, width: number, height: number): void; - image(image: number, x: number, y: number): void; + image(id: number, x: number, y: number): void; line(x1: number, y1: number, x2: number, y2: number): void; rect(x: number, y: number, width: number, height: number): void; + text(text: string, x: number, y: number): void; well(x: number, y: number, width: number, height: number): void; } + interface ImageInfo { + readonly id: number; + readonly offset: ScreenCoordsXY; + readonly width: number; + readonly height: number; + readonly isBMP: boolean; + readonly isRLE: boolean; + readonly isPalette: boolean; + readonly noZoom: boolean; + readonly nextZoomId: number | undefined; + } + /** * 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/scripting/ScGraphicsContext.hpp b/src/openrct2-ui/scripting/ScGraphicsContext.hpp index 40c2f3e9d4..a8470b1822 100644 --- a/src/openrct2-ui/scripting/ScGraphicsContext.hpp +++ b/src/openrct2-ui/scripting/ScGraphicsContext.hpp @@ -22,7 +22,10 @@ namespace OpenRCT2::Scripting duk_context* _ctx{}; rct_drawpixelinfo _dpi{}; - colour_t _colour{}; + std::optional _colour{}; + std::optional _secondaryColour{}; + std::optional _ternaryColour{}; + std::optional _paletteId{}; uint8_t _stroke{}; uint8_t _fill{}; @@ -36,25 +39,80 @@ namespace OpenRCT2::Scripting static void Register(duk_context* ctx) { dukglue_register_property(ctx, &ScGraphicsContext::colour_get, &ScGraphicsContext::colour_set, "colour"); + dukglue_register_property( + ctx, &ScGraphicsContext::secondaryColour_get, &ScGraphicsContext::secondaryColour_set, "secondaryColour"); + dukglue_register_property( + ctx, &ScGraphicsContext::ternaryColour_get, &ScGraphicsContext::ternaryColour_set, "ternaryColour"); + dukglue_register_property(ctx, &ScGraphicsContext::paletteId_get, &ScGraphicsContext::paletteId_set, "paletteId"); dukglue_register_property(ctx, &ScGraphicsContext::fill_get, &ScGraphicsContext::fill_set, "fill"); dukglue_register_property(ctx, &ScGraphicsContext::stroke_get, &ScGraphicsContext::stroke_set, "stroke"); + dukglue_register_property(ctx, &ScGraphicsContext::width_get, nullptr, "width"); + dukglue_register_property(ctx, &ScGraphicsContext::height_get, nullptr, "height"); + + dukglue_register_method(ctx, &ScGraphicsContext::getImage, "getImage"); + dukglue_register_method(ctx, &ScGraphicsContext::measureText, "measureText"); + dukglue_register_method(ctx, &ScGraphicsContext::box, "box"); dukglue_register_method(ctx, &ScGraphicsContext::clear, "clear"); dukglue_register_method(ctx, &ScGraphicsContext::clip, "clip"); + dukglue_register_method(ctx, &ScGraphicsContext::image, "image"); dukglue_register_method(ctx, &ScGraphicsContext::line, "line"); dukglue_register_method(ctx, &ScGraphicsContext::rect, "rect"); + dukglue_register_method(ctx, &ScGraphicsContext::text, "text"); dukglue_register_method(ctx, &ScGraphicsContext::well, "well"); } private: - colour_t colour_get() const + DukValue colour_get() const { - return _colour; + return ToDuk(_ctx, _colour); } - void colour_set(colour_t value) + void colour_set(DukValue value) { - _colour = value; + if (value.type() == DukValue::NUMBER) + _colour = static_cast(value.as_int()); + else + _colour = {}; + } + + DukValue secondaryColour_get() const + { + return ToDuk(_ctx, _secondaryColour); + } + + void secondaryColour_set(DukValue value) + { + if (value.type() == DukValue::NUMBER) + _secondaryColour = static_cast(value.as_int()); + else + _secondaryColour = {}; + } + + DukValue ternaryColour_get() const + { + return ToDuk(_ctx, _ternaryColour); + } + + void ternaryColour_set(DukValue value) + { + if (value.type() == DukValue::NUMBER) + _ternaryColour = static_cast(value.as_int()); + else + _ternaryColour = {}; + } + + DukValue paletteId_get() const + { + return ToDuk(_ctx, _paletteId); + } + + void paletteId_set(DukValue value) + { + if (value.type() == DukValue::NUMBER) + _paletteId = static_cast(value.as_int()); + else + _paletteId = {}; } uint8_t fill_get() const @@ -77,14 +135,66 @@ namespace OpenRCT2::Scripting _stroke = value; } + int32_t width_get() const + { + return _dpi.width; + } + + int32_t height_get() const + { + return _dpi.height; + } + + DukValue getImage(uint32_t id) + { + auto* g1 = gfx_get_g1_element(id); + if (g1 == nullptr) + { + return ToDuk(_ctx, undefined); + } + else + { + DukObject obj(_ctx); + obj.Set("id", id); + obj.Set("offset", ToDuk(_ctx, { g1->x_offset, g1->y_offset })); + obj.Set("width", g1->width); + obj.Set("height", g1->height); + + obj.Set("isBMP", (g1->flags & G1_FLAG_BMP) != 0); + obj.Set("isRLE", (g1->flags & G1_FLAG_RLE_COMPRESSION) != 0); + obj.Set("isPalette", (g1->flags & G1_FLAG_PALETTE) != 0); + obj.Set("noZoom", (g1->flags & G1_FLAG_NO_ZOOM_DRAW) != 0); + + if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE) + { + obj.Set("nextZoomId", id - g1->zoomed_offset); + } + else + { + obj.Set("nextZoomId", undefined); + } + return obj.Take(); + } + } + + DukValue measureText(const std::string& text) + { + gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; + auto width = gfx_get_string_width(text); + auto height = string_get_height_raw(text.c_str()); + return ToDuk(_ctx, { width, height }); + } + void box(int32_t x, int32_t y, int32_t width, int32_t height) { - gfx_fill_rect_inset(&_dpi, { x, y, x + width - 1, y + height - 1 }, _colour, 0); + gfx_fill_rect_inset(&_dpi, { x, y, x + width - 1, y + height - 1 }, _colour.value_or(0), 0); } void well(int32_t x, int32_t y, int32_t width, int32_t height) { - gfx_fill_rect_inset(&_dpi, { x, y, x + width - 1, y + height - 1 }, _colour, INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_DONT_LIGHTEN); + gfx_fill_rect_inset( + &_dpi, { x, y, x + width - 1, y + height - 1 }, _colour.value_or(0), + INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_DONT_LIGHTEN); } void clear() @@ -99,6 +209,28 @@ namespace OpenRCT2::Scripting _dpi = newDpi; } + void image(uint32_t id, int32_t x, int32_t y) + { + ImageId img; + img = img.WithIndex(id); + if (_paletteId) + { + img = img.WithRemap(*_paletteId); + } + else + { + if (_colour) + { + img = img.WithPrimary(*_colour); + } + if (_secondaryColour) + { + img = img.WithSecondary(*_secondaryColour); + } + } + gfx_draw_sprite(&_dpi, static_cast(img.ToUInt32()), { x, y }, _ternaryColour.value_or(0)); + } + void line(int32_t x1, int32_t y1, int32_t x2, int32_t y2) { gfx_draw_line(&_dpi, { { x1, y1 }, { x2, y2 } }, _stroke); @@ -106,7 +238,28 @@ namespace OpenRCT2::Scripting void rect(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); + if (_stroke != 0) + { + line(x, y, x + width, y); + line(x + width - 1, y + 1, x + width - 1, y + height - 1); + line(x, y + height - 1, x + width, y + height - 1); + line(x, y + 1, x, y + height - 1); + + x++; + y++; + width -= 2; + height -= 2; + } + if (_fill != 0) + { + gfx_fill_rect(&_dpi, { x, y, x + width - 1, y + height - 1 }, _fill); + } + } + + void text(const std::string& text, int32_t x, int32_t y) + { + gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; + gfx_draw_string(&_dpi, text.c_str(), _colour.value_or(0), { x, y }); } }; } // namespace OpenRCT2::Scripting diff --git a/src/openrct2/drawing/Drawing.String.cpp b/src/openrct2/drawing/Drawing.String.cpp index 84f9eea202..a793c19dce 100644 --- a/src/openrct2/drawing/Drawing.String.cpp +++ b/src/openrct2/drawing/Drawing.String.cpp @@ -356,7 +356,7 @@ void draw_string_centred_raw(rct_drawpixelinfo* dpi, const ScreenCoordsXY& coord } } -int32_t string_get_height_raw(char* buffer) +int32_t string_get_height_raw(std::string_view text) { uint16_t fontBase = gCurrentFontSpriteBase; @@ -366,7 +366,7 @@ int32_t string_get_height_raw(char* buffer) else if (fontBase == FONT_SPRITE_BASE_TINY) height += 6; - FmtString fmt(buffer); + FmtString fmt(text); for (const auto& token : fmt) { switch (token.kind) diff --git a/src/openrct2/drawing/Drawing.h b/src/openrct2/drawing/Drawing.h index 8143c7f65d..221eaa5e1a 100644 --- a/src/openrct2/drawing/Drawing.h +++ b/src/openrct2/drawing/Drawing.h @@ -459,6 +459,13 @@ public: return result; } + constexpr ImageId WithRemap(uint8_t paletteId) + { + ImageId result = *this; + result._value = (_value & ~MASK_REMAP) | ((paletteId << SHIFT_REMAP) & MASK_REMAP) | FLAG_PRIMARY; + return result; + } + constexpr ImageId WithPrimary(colour_t colour) { ImageId result = *this; @@ -757,7 +764,7 @@ int32_t gfx_wrap_string(char* buffer, int32_t width, int32_t* num_lines, int32_t int32_t gfx_get_string_width(std::string_view text); int32_t gfx_get_string_width_new_lined(std::string_view text); int32_t gfx_get_string_width_no_formatting(std::string_view text); -int32_t string_get_height_raw(char* buffer); +int32_t string_get_height_raw(std::string_view text); int32_t gfx_clip_string(char* buffer, int32_t width); void shorten_path(utf8* buffer, size_t bufferSize, const utf8* path, int32_t availableWidth); void ttf_draw_string( diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index ced2950f18..73cddc4e52 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -50,6 +50,11 @@ namespace OpenRCT2::Scripting return value.type() == DukValue::BOOLEAN ? value.as_bool() : defaultValue; } + enum class DukUndefined + { + }; + constexpr DukUndefined undefined{}; + /** * Allows creation of an object on the duktape stack and setting properties on it before * retrieving the DukValue instance of it. @@ -88,6 +93,13 @@ namespace OpenRCT2::Scripting duk_put_prop_string(_ctx, _idx, name); } + void Set(const char* name, DukUndefined) + { + EnsureObjectPushed(); + duk_push_undefined(_ctx); + duk_put_prop_string(_ctx, _idx, name); + } + void Set(const char* name, bool value) { EnsureObjectPushed(); @@ -277,12 +289,24 @@ namespace OpenRCT2::Scripting return DukValue::take_from_stack(ctx); } + template<> inline DukValue ToDuk(duk_context* ctx, const DukUndefined&) + { + duk_push_undefined(ctx); + return DukValue::take_from_stack(ctx); + } + template<> inline DukValue ToDuk(duk_context* ctx, const bool& value) { duk_push_boolean(ctx, value); return DukValue::take_from_stack(ctx); } + template<> inline DukValue ToDuk(duk_context* ctx, const uint8_t& value) + { + duk_push_int(ctx, value); + return DukValue::take_from_stack(ctx); + } + template<> inline DukValue ToDuk(duk_context* ctx, const int32_t& value) { duk_push_int(ctx, value); @@ -401,6 +425,14 @@ namespace OpenRCT2::Scripting return result; } + template<> inline DukValue ToDuk(duk_context* ctx, const ScreenSize& value) + { + DukObject dukCoords(ctx); + dukCoords.Set("width", value.width); + dukCoords.Set("height", value.height); + return dukCoords.Take(); + } + } // namespace OpenRCT2::Scripting #endif