diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index f4ffe233..16fcf57a 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -7,7 +7,7 @@ AppDir: icon: VoxelCore version: latest exec: usr/bin/VoxelEngine - exec_args: --dir $HOME/.voxeng --res $APPDIR/usr/share/VoxelCore/res $@ + exec_args: --dir $HOME/.config/voxelcore --res $APPDIR/usr/share/VoxelCore/res $@ apt: arch: amd64 sources: diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 441115c8..4cac91b7 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -166,13 +166,21 @@ Properties: Methods: +Here, *color* can be specified in the following ways: +- rgba: int +- r: int, g: int, b: int +- r: int, g: int, b: int, a: int + | Method | Description | |----------------------------------------------------------|---------------------------------------------------------| | data:at(x: int, y: int) | returns an RGBA pixel at the given coordinates | -| data:set(x: int, y: int, rgba: int) | updates an RGBA pixel at the given coordinates | -| data:set(x: int, y: int, r: int, g: int, b: int) | updates an RGBA pixel at the given coordinates | -| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | updates an RGBA pixel at the given coordinates | +| data:set(x: int, y: int, *color*) | updates an RGBA pixel at the given coordinates | +| data:line(x1: int, y1: int, x2: int, y2: int, *color*) | draws a line with the specified RGBA color | +| data:blit(src: Canvas, dst_x: int, dst_y: int) | draws the src canvas at the specified coordinates | +| data:clear() | clears the canvas | +| data:clear(*color*) | fills the canvas with the specified RGBA color | | data:update() | applies changes to the canvas and uploads it to the GPU | +| data:set_data(data: table) | replaces pixel data (width * height * 4 numbers) | ## Inventory diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index cca9790e..137ff57b 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -167,13 +167,21 @@ document["worlds-panel"]:clear() Методы: -| Метод | Описание | -|----------------------------------------------------------|-----------------------------------------------------| -| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, rgba: int) | изменяет RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, r: int, g: int, b: int) | изменяет RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, r: int, g: int, b: int, a: int) | изменяет RGBA пиксель по указанным координатам | -| data:update() | применяет изменения и загружает холст в видеопамять | +Здесь *цвет* может быть указан следующими способами: +- rgba: int +- r: int, g: int, b: int +- r: int, g: int, b: int, a: int + +| Метод | Описание | +|----------------------------------------------------------|------------------------------------------------------| +| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | +| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | +| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | +| data:clear() | очищает холст | +| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | +| data:update() | применяет изменения и загружает холст в видеопамять | +| data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | ## Inventory (inventory) diff --git a/res/layouts/pages/content.xml b/res/layouts/pages/content.xml index c4ff39e2..37025c69 100644 --- a/res/layouts/pages/content.xml +++ b/res/layouts/pages/content.xml @@ -1,13 +1,41 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 0eae8c00..3f1903f3 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -5,9 +5,28 @@ function on_open(params) refresh() end +-- add - packs to be added to the world (after apply) +-- rem - packs that should be removed from the world (after apply) add_packs = {} rem_packs = {} +-- included - connected packs to the world +-- excluded - packs that are not connected to the world +packs_included = {} +packs_excluded = {} + +packs_info = {} + +local function include(id, is_include) + if is_include then + table.insert(packs_included, id) + table.remove_value(packs_excluded, id) + else + table.insert(packs_excluded, id) + table.remove_value(packs_included, id) + end +end + function apply() core.reconfig_packs(add_packs, rem_packs) if mode ~= "world" then @@ -15,8 +34,70 @@ function apply() end end +function reposition_func(_pack) + local INTERVAL = 2 + local STEP = 1 + local SIZE = 80 + + local tbl = nil + if table.has(packs_included, _pack) then + tbl = packs_included + elseif table.has(packs_excluded, _pack) then + tbl = packs_excluded + else + tbl = packs_excluded + local packinfo = pack.get_info(_pack) + packinfo[packinfo.id] = {packinfo.id, packinfo.title} + table.insert(packs_excluded, packinfo.id) + end + + local indx = table.index(tbl, _pack) - 1 + local pos = {0, (SIZE + INTERVAL) * indx + STEP} + + return pos[1], pos[2] +end + + +function refresh_search() + local search_text = document.search_textbox.text:lower() + + local new_included = table.copy(packs_included) + local new_excluded = table.copy(packs_excluded) + + local function score(pack_name) + if pack_name:lower():find(search_text) then + return 1 + end + return 0 + end + + local function sorting(a, b) + local score_a = score(packs_info[a][2]) + local score_b = score(packs_info[b][2]) + + if score_a ~= score_b then + return score_a > score_b + else + return packs_info[a][2] < packs_info[b][2] + end + end + + table.sort(new_included, sorting) + table.sort(new_excluded, sorting) + + packs_included = new_included + packs_excluded = new_excluded + + for _, id in ipairs(table.merge(table.copy(packs_included), packs_excluded)) do + local content = document["pack_" .. id] + content:reposition() + end +end + + function refresh_changes() document.apply_btn.enabled = (#add_packs>0) or (#rem_packs>0) + refresh_search() end function move_pack(id) @@ -24,23 +105,61 @@ function move_pack(id) if table.has(add_packs, id) then document["pack_"..id]:moveInto(document.packs_add) table.remove_value(add_packs, id) + include(id, false) -- cancel pack removal elseif table.has(rem_packs, id) then document["pack_"..id]:moveInto(document.packs_cur) table.remove_value(rem_packs, id) + include(id, true) -- add pack elseif table.has(packs_installed, id) then document["pack_"..id]:moveInto(document.packs_add) table.insert(rem_packs, id) + include(id, false) -- remove pack else document["pack_"..id]:moveInto(document.packs_cur) table.insert(add_packs, id) + include(id, true) end refresh_changes() end -function place_pack(panel, packinfo, callback) +function move_left() + for _, id in pairs(table.copy(packs_excluded)) do + if not document["pack_"..id].enabled then goto continue end + + include(id, true) + table.insert(add_packs, id) + table.remove_value(rem_packs, id) + document["pack_"..id]:moveInto(document.packs_cur) + + ::continue:: + end + + refresh_changes() +end + +function move_right() + for _, id in pairs(table.copy(packs_included)) do + if not document["pack_"..id].enabled then goto continue end + + include(id, false) + + if table.has(packs_installed, id) then + table.insert(rem_packs, id) + end + + table.remove_value(add_packs, id) + document["pack_"..id]:moveInto(document.packs_add) + + ::continue:: + end + + refresh_changes() +end + +function place_pack(panel, packinfo, callback, position_func) if packinfo.error then callback = nil end @@ -50,6 +169,7 @@ function place_pack(panel, packinfo, callback) packinfo.id_verbose = packinfo.id end packinfo.callback = callback + packinfo.position_func = position_func or function () end panel:add(gui.template("pack", packinfo)) if not callback then document["pack_"..packinfo.id].enabled = false @@ -76,15 +196,30 @@ function check_dependencies(packinfo) return end +function check_deleted() + for i = 1, math.max(#packs_included, #packs_excluded) do + local pack = packs_included[i] + if pack and not table.has(packs_all, pack) then + table.remove(packs_included, i) + table.insert(rem_packs, pack) + end + + pack = packs_excluded[i] + if pack and not table.has(packs_all, pack) then + table.remove(packs_excluded, i) + table.insert(rem_packs, pack) + end + end +end + function refresh() packs_installed = pack.get_installed() packs_available = pack.get_available() base_packs = pack.get_base_packs() packs_all = {unpack(packs_installed)} required = {} - for i,k in ipairs(packs_available) do - table.insert(packs_all, k) - end + + table.merge(packs_all, packs_available) local packs_cur = document.packs_cur local packs_add = document.packs_add @@ -105,20 +240,14 @@ function refresh() end local packinfos = pack.get_info(packids) - for i,id in ipairs(packs_installed) do - local packinfo = packinfos[id] - packinfo.index = i - callback = not table.has(base_packs, id) and string.format('move_pack("%s")', id) or nil - packinfo.error = check_dependencies(packinfo) - place_pack(packs_cur, packinfo, callback) + for _,id in ipairs(base_packs) do + local packinfo = pack.get_info(id) + packs_info[id] = {packinfo.id, packinfo.title} end - for i,id in ipairs(packs_available) do - local packinfo = packinfos[id] - packinfo.index = i - callback = string.format('move_pack("%s")', id) - packinfo.error = check_dependencies(packinfo) - place_pack(packs_add, packinfo, callback) + for _,id in ipairs(packs_all) do + local packinfo = pack.get_info(id) + packs_info[id] = {packinfo.id, packinfo.title} end for i,id in ipairs(packs_installed) do @@ -127,6 +256,26 @@ function refresh() end end + if #packs_excluded == 0 then packs_excluded = table.copy(packs_available) end + if #packs_included == 0 then packs_included = table.copy(packs_installed) end + + for i,id in ipairs(packs_installed) do + local packinfo = packinfos[id] + packinfo.index = i + callback = not table.has(base_packs, id) and string.format('move_pack("%s")', id) or nil + packinfo.error = check_dependencies(packinfo) + place_pack(packs_cur, packinfo, callback, string.format('reposition_func("%s")', packinfo.id)) + end + + for i,id in ipairs(packs_available) do + local packinfo = packinfos[id] + packinfo.index = i + callback = string.format('move_pack("%s")', id) + packinfo.error = check_dependencies(packinfo) + place_pack(packs_add, packinfo, callback, string.format('reposition_func("%s")', packinfo.id)) + end + + check_deleted() apply_movements(packs_cur, packs_add) refresh_changes() end diff --git a/res/layouts/pages/new_world.xml.lua b/res/layouts/pages/new_world.xml.lua index 25e6f424..0dafa505 100644 --- a/res/layouts/pages/new_world.xml.lua +++ b/res/layouts/pages/new_world.xml.lua @@ -28,7 +28,8 @@ function on_open() document.content_btn.text = string.format( "%s [%s]", gui.str("Content", "menu"), #pack.get_installed() ) - if settings.generator == nil then + + if settings.generator == nil or generation.get_generators()[settings.generator] == nil then settings.generator = generation.get_default_generator() end document.generator_btn.text = string.format( diff --git a/res/layouts/templates/pack.xml b/res/layouts/templates/pack.xml index 5eb2f74c..3b24aa96 100644 --- a/res/layouts/templates/pack.xml +++ b/res/layouts/templates/pack.xml @@ -1,4 +1,4 @@ - + diff --git a/res/preload.json b/res/preload.json index 866ee9bf..c4e60cad 100644 --- a/res/preload.json +++ b/res/preload.json @@ -21,7 +21,10 @@ "gui/folder_icon", "gui/settings_icon", "misc/rain", - "misc/snow" + "misc/snow", + "gui/check_mark", + "gui/left_arrow", + "gui/right_arrow" ], "fonts": [ { diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 5ebf0176..e2e4d3c7 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,3 +1,31 @@ +local _ffi = ffi +ffi = nil + +-- Lua has no parallelizm, also _set_data does not call any lua functions so +-- may be reused one global ffi buffer per lua_State +local canvas_ffi_buffer +local canvas_ffi_buffer_size = 0 + +function __vc_Canvas_set_data(self, data) + if type(data) == "cdata" then + self:_set_data(tostring(_ffi.cast("uintptr_t", data))) + end + local width = self.width + local height = self.height + + local size = width * height * 4 + if size > canvas_ffi_buffer_size then + canvas_ffi_buffer = _ffi.new( + string.format("unsigned char[%s]", size) + ) + canvas_ffi_buffer_size = size + end + for i=0, size - 1 do + canvas_ffi_buffer[i] = data[i + 1] + end + self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer))) +end + -- Check if given table is an array function is_array(x) if #x > 0 then @@ -151,9 +179,27 @@ function table.map(t, func) end function table.filter(t, func) + + for i = #t, 1, -1 do + if not func(i, t[i]) then + table.remove(t, i) + end + end + + local size = #t + for i, v in pairs(t) do - if not func(i, v) then - t[i] = nil + local i_type = type(i) + if i_type == "number" then + if i < 1 or i > size then + if not func(i, v) then + t[i] = nil + end + end + else + if not func(i, v) then + t[i] = nil + end end end diff --git a/res/textures/gui/check_mark.png b/res/textures/gui/check_mark.png new file mode 100644 index 00000000..61e61711 Binary files /dev/null and b/res/textures/gui/check_mark.png differ diff --git a/res/textures/gui/left_arrow.png b/res/textures/gui/left_arrow.png new file mode 100644 index 00000000..0d719ff2 Binary files /dev/null and b/res/textures/gui/left_arrow.png differ diff --git a/res/textures/gui/loupe.png b/res/textures/gui/loupe.png new file mode 100644 index 00000000..1b8a946b Binary files /dev/null and b/res/textures/gui/loupe.png differ diff --git a/res/textures/gui/right_arrow.png b/res/textures/gui/right_arrow.png new file mode 100644 index 00000000..e34208bd Binary files /dev/null and b/res/textures/gui/right_arrow.png differ diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index 4eeaa5eb..49c3014c 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -2,7 +2,6 @@ #include #include -#include #include "util/listutil.hpp" @@ -125,9 +124,7 @@ std::vector PacksManager::assemble( std::queue queue; std::queue queue2; - std::sort(allNames.begin(), allNames.end()); - - for (auto& name : allNames) { + for (auto& name : names) { auto found = packs.find(name); if (found == packs.end()) { throw contentpack_error(name, io::path(), "pack not found"); diff --git a/src/graphics/core/Atlas.cpp b/src/graphics/core/Atlas.cpp index 039db4d2..be205d1e 100644 --- a/src/graphics/core/Atlas.cpp +++ b/src/graphics/core/Atlas.cpp @@ -97,7 +97,7 @@ std::unique_ptr AtlasBuilder::build(uint extrusion, bool prepare, uint ma uint y = rect.y; uint w = rect.width; uint h = rect.height; - canvas->blit(entry.image.get(), rect.x, rect.y); + canvas->blit(*entry.image, rect.x, rect.y); for (uint j = 0; j < extrusion; j++) { canvas->extrude(x - j, y - j, w + j*2, h + j*2); } diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index d6617c57..a2b1d524 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include ImageData::ImageData(ImageFormat format, uint width, uint height) @@ -80,23 +81,116 @@ void ImageData::flipY() { } } -void ImageData::blit(const ImageData* image, int x, int y) { - if (format == image->format) { +void ImageData::blit(const ImageData& image, int x, int y) { + if (format == image.format) { blitMatchingFormat(image, x, y); return; } if (format == ImageFormat::rgba8888 && - image->format == ImageFormat::rgb888) { + image.format == ImageFormat::rgb888) { blitRGB_on_RGBA(image, x, y); return; } throw std::runtime_error("mismatching format"); } -void ImageData::blitRGB_on_RGBA(const ImageData* image, int x, int y) { - ubyte* source = image->getData(); - uint srcwidth = image->getWidth(); - uint srcheight = image->getHeight(); +static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) { + const int left = 0; + const int right = width; + const int bottom = 0; + const int top = height; + + int dx = x2 - x1; + int dy = y2 - y1; + + float t0 = 0.0f; + float t1 = 1.0f; + + auto clip = [](int p, int q, float& t0, float& t1) { + if (p == 0) { + return q >= 0; + } + float t = static_cast(q) / p; + if (p < 0) { + if (t > t1) return false; + if (t > t0) t0 = t; + } else { + if (t < t0) return false; + if (t < t1) t1 = t; + } + return true; + }; + + if (!clip(-dx, x1 - left, t0, t1)) return false; + if (!clip( dx, right - x1, t0, t1)) return false; + if (!clip(-dy, y1 - bottom, t0, t1)) return false; + if (!clip( dy, top - y1, t0, t1)) return false; + + if (t1 < 1.0f) { + x2 = x1 + static_cast(std::round(t1 * dx)); + y2 = y1 + static_cast(std::round(t1 * dy)); + } + if (t0 > 0.0f) { + x1 = x1 + static_cast(std::round(t0 * dx)); + y1 = y1 + static_cast(std::round(t0 * dy)); + } + return true; +} + +template +static void draw_line(ImageData& image, int x1, int y1, int x2, int y2, const glm::ivec4& color) { + ubyte* data = image.getData(); + uint width = image.getWidth(); + uint height = image.getHeight(); + + if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width || + y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) && + !clip_line(x1, y1, x2, y2, width, height)) { + return; + } + + int dx = std::abs(x2 - x1); + int dy = -std::abs(y2 - y1); + int sx = x1 < x2 ? 1 : -1; + int sy = y1 < y2 ? 1 : -1; + int err = dx + dy; + + while (true) { + size_t pos = (y1 * width + x1) * channels; + for (int i = 0; i < channels; i++) { + data[pos + i] = color[i]; + } + if (x1 == x2 && y1 == y2) break; + + int e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x1 += sx; + } + if (e2 <= dx) { + err += dx; + y1 += sy; + } + } +} + +void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color) { + switch (format) { + case ImageFormat::rgb888: + draw_line<3>(*this, x1, y1, x2, y2, color); + break; + case ImageFormat::rgba8888: + draw_line<4>(*this, x1, y1, x2, y2, color); + break; + default: + break; + } +} + +void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) { + ubyte* source = image.getData(); + uint srcwidth = image.getWidth(); + uint srcheight = image.getHeight(); for (uint srcy = std::max(0, -y); srcy < std::min(srcheight, height - y); @@ -116,7 +210,7 @@ void ImageData::blitRGB_on_RGBA(const ImageData* image, int x, int y) { } } -void ImageData::blitMatchingFormat(const ImageData* image, int x, int y) { +void ImageData::blitMatchingFormat(const ImageData& image, int x, int y) { uint comps; switch (format) { case ImageFormat::rgb888: comps = 3; break; @@ -124,20 +218,24 @@ void ImageData::blitMatchingFormat(const ImageData* image, int x, int y) { default: throw std::runtime_error("only unsigned byte formats supported"); } - ubyte* source = image->getData(); - uint srcwidth = image->getWidth(); - uint srcheight = image->getHeight(); + ubyte* source = image.getData(); + + const uint width = this->width; + const uint height = this->height; + const uint src_width = image.getWidth(); + const uint src_height = image.getHeight(); + ubyte* data = this->data.get(); for (uint srcy = std::max(0, -y); - srcy < std::min(srcheight, height - y); + srcy < std::min(src_height, height - y); srcy++) { for (uint srcx = std::max(0, -x); - srcx < std::min(srcwidth, width - x); + srcx < std::min(src_width, width - x); srcx++) { uint dstx = srcx + x; uint dsty = srcy + y; uint dstidx = (dsty * width + dstx) * comps; - uint srcidx = (srcy * srcwidth + srcx) * comps; + uint srcidx = (srcy * src_width + srcx) * comps; for (uint c = 0; c < comps; c++) { data[dstidx + c] = source[srcidx + c]; } diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index 328ff896..213a352c 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -2,6 +2,7 @@ #include "typedefs.hpp" +#include #include enum class ImageFormat { @@ -14,6 +15,9 @@ class ImageData { uint width; uint height; std::unique_ptr data; + + void blitRGB_on_RGBA(const ImageData& image, int x, int y); + void blitMatchingFormat(const ImageData& image, int x, int y); public: ImageData(ImageFormat format, uint width, uint height); ImageData(ImageFormat format, uint width, uint height, std::unique_ptr data); @@ -23,9 +27,8 @@ public: void flipX(); void flipY(); - void blitRGB_on_RGBA(const ImageData* image, int x, int y); - void blitMatchingFormat(const ImageData* image, int x, int y); - void blit(const ImageData* image, int x, int y); + void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color); + void blit(const ImageData& image, int x, int y); void extrude(int x, int y, int w, int h); void fixAlphaColor(); @@ -44,6 +47,11 @@ public: uint getHeight() const { return height; } + + size_t getDataSize() const { + size_t channels = 3 + (format == ImageFormat::rgba8888); + return width * height * channels; + } }; std::unique_ptr add_atlas_margins(ImageData* image, int grid_size); diff --git a/src/graphics/ui/elements/Canvas.cpp b/src/graphics/ui/elements/Canvas.cpp index dd69ce22..be3fc145 100644 --- a/src/graphics/ui/elements/Canvas.cpp +++ b/src/graphics/ui/elements/Canvas.cpp @@ -16,5 +16,5 @@ void gui::Canvas::draw(const DrawContext& pctx, const Assets& assets) { auto batch = pctx.getBatch2D(); batch->texture(mTexture.get()); - batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, true, col); + batch->rect(pos.x, pos.y, size.x, size.y, 0, 0, 0, {}, false, false, col); } diff --git a/src/graphics/ui/elements/Canvas.hpp b/src/graphics/ui/elements/Canvas.hpp index ae1f02b0..adfb5e2a 100644 --- a/src/graphics/ui/elements/Canvas.hpp +++ b/src/graphics/ui/elements/Canvas.hpp @@ -26,4 +26,4 @@ namespace gui { std::shared_ptr<::Texture> mTexture; std::shared_ptr mData; }; -} \ No newline at end of file +} diff --git a/src/logic/scripting/lua/lua_custom_types.hpp b/src/logic/scripting/lua/lua_custom_types.hpp index 145cc4d8..a844b1f1 100644 --- a/src/logic/scripting/lua/lua_custom_types.hpp +++ b/src/logic/scripting/lua/lua_custom_types.hpp @@ -114,10 +114,14 @@ namespace lua { return *mData; } + [[nodiscard]] bool hasTexture() const { + return mTexture != nullptr; + } + static int createMetatable(lua::State*); inline static std::string TYPENAME = "Canvas"; private: - std::shared_ptr mTexture; + std::shared_ptr mTexture; // nullable std::shared_ptr mData; }; static_assert(!std::is_abstract()); diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 251f789b..09c0f424 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -88,14 +88,17 @@ static void create_libs(State* L, StateType stateType) { void lua::init_state(State* L, StateType stateType) { // Allowed standard libraries - pop(L, luaopen_base(L)); - pop(L, luaopen_math(L)); - pop(L, luaopen_string(L)); - pop(L, luaopen_table(L)); - pop(L, luaopen_debug(L)); - pop(L, luaopen_jit(L)); - pop(L, luaopen_bit(L)); - pop(L, luaopen_os(L)); + luaL_openlibs(L); + + if (getglobal(L, "require")) { + pushstring(L, "ffi"); + if (call_nothrow(L, 1, 1)) { + setglobal(L, "ffi"); + } + } + pushnil(L); + setglobal(L, "io"); + const char* removed_os[] { "execute", "exit", "remove", "rename", "setlocale", "tmpname", nullptr}; remove_lib_funcs(L, "os", removed_os); diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index d740165b..8cd77fb1 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -163,20 +163,23 @@ int lua::call(State* L, int argc, int nresults) { int handler_pos = gettop(L) - argc; pushcfunction(L, l_error_handler); insert(L, handler_pos); + int top = gettop(L); if (lua_pcall(L, argc, nresults, handler_pos)) { std::string log = tostring(L, -1); pop(L); remove(L, handler_pos); throw luaerror(log); } + int added = gettop(L) - (top - argc - 1); remove(L, handler_pos); - return nresults == -1 ? 1 : nresults; + return added; } int lua::call_nothrow(State* L, int argc, int nresults) { int handler_pos = gettop(L) - argc; pushcfunction(L, l_error_handler); insert(L, handler_pos); + int top = gettop(L); if (lua_pcall(L, argc, -1, handler_pos)) { auto errorstr = tostring(L, -1); if (errorstr) { @@ -188,8 +191,9 @@ int lua::call_nothrow(State* L, int argc, int nresults) { remove(L, handler_pos); return 0; } + int added = gettop(L) - (top - argc - 1); remove(L, handler_pos); - return 1; + return added; } void lua::dump_stack(State* L) { diff --git a/src/logic/scripting/lua/lua_wrapper.hpp b/src/logic/scripting/lua/lua_wrapper.hpp index 417314cd..f82f5bd4 100644 --- a/src/logic/scripting/lua/lua_wrapper.hpp +++ b/src/logic/scripting/lua/lua_wrapper.hpp @@ -25,7 +25,8 @@ namespace lua { if (n < 0) { abort(); } - if (lua_gettop(L) < n) { + int top = lua_gettop(L); + if (top < n) { abort(); } #endif @@ -63,6 +64,9 @@ namespace lua { inline void rawseti(lua::State* L, int n, int idx = -2) { lua_rawseti(L, idx, n); } + inline void rawset(lua::State* L, int idx = -3) { + lua_rawset(L, idx); + } inline int createtable(lua::State* L, int narr, int nrec) { lua_createtable(L, narr, nrec); diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index acba361d..a4292f56 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -17,6 +17,7 @@ union RGBA { struct { uint8_t r, g, b, a; }; + uint8_t arr[4]; uint32_t rgba; }; @@ -49,38 +50,118 @@ static int l_at(State* L) { return 0; } +static RGBA get_rgba(State* L, int first) { + RGBA rgba {}; + rgba.a = 255; + switch (gettop(L) - first) { + case 0: + rgba.rgba = static_cast(tointeger(L, first)); + break; + case 3: + rgba.a = static_cast(tointeger(L, first + 3)); + [[fallthrough]]; + case 2: + rgba.r = static_cast(tointeger(L, first)); + rgba.g = static_cast(tointeger(L, first + 1)); + rgba.b = static_cast(tointeger(L, first + 2)); + break; + } + return rgba; +} + static int l_set(State* L) { auto x = static_cast(tointeger(L, 2)); auto y = static_cast(tointeger(L, 3)); if (auto pixel = get_at(L, x, y)) { - switch (gettop(L)) { - case 4: - pixel->rgba = static_cast(tointeger(L, 4)); - return 1; - case 6: - pixel->r = static_cast(tointeger(L, 4)); - pixel->g = static_cast(tointeger(L, 5)); - pixel->b = static_cast(tointeger(L, 6)); - pixel->a = 255; - return 1; - case 7: - pixel->r = static_cast(tointeger(L, 4)); - pixel->g = static_cast(tointeger(L, 5)); - pixel->b = static_cast(tointeger(L, 6)); - pixel->a = static_cast(tointeger(L, 7)); - return 1; - default: - return 0; - } + *pixel = get_rgba(L, 4); } + return 0; +} +static LuaCanvas& require_canvas(State* L, int idx) { + if (const auto canvas = touserdata(L, idx)) { + return *canvas; + } + throw std::runtime_error( + "canvas expected as argument #" + std::to_string(idx) + ); +} + +static int l_clear(State* L) { + auto& canvas = require_canvas(L, 1); + auto& image = canvas.data(); + ubyte* data = image.getData(); + RGBA rgba {}; + if (gettop(L) == 1) { + std::fill(data, data + image.getDataSize(), 0); + return 0; + } + rgba = get_rgba(L, 2); + size_t pixels = image.getWidth() * image.getHeight(); + const size_t channels = 4; + for (size_t i = 0; i < pixels * channels; i++) { + data[i] = rgba.arr[i % channels]; + } + return 0; +} + +static int l_line(State* L) { + int x1 = tointeger(L, 2); + int y1 = tointeger(L, 3); + + int x2 = tointeger(L, 4); + int y2 = tointeger(L, 5); + + RGBA rgba = get_rgba(L, 6); + if (auto canvas = touserdata(L, 1)) { + auto& image = canvas->data(); + image.drawLine( + x1, y1, x2, y2, glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a} + ); + } + return 0; +} + +static int l_blit(State* L) { + auto& dst = require_canvas(L, 1); + auto& src = require_canvas(L, 2); + int dst_x = tointeger(L, 3); + int dst_y = tointeger(L, 4); + dst.data().blit(src.data(), dst_x, dst_y); + return 0; +} + +static int l_set_data(State* L) { + auto& canvas = require_canvas(L, 1); + auto& image = canvas.data(); + auto data = image.getData(); + + if (lua::isstring(L, 2)) { + auto ptr = reinterpret_cast(std::stoull(lua::tostring(L, 2))); + std::memcpy(data, ptr, image.getDataSize()); + return 0; + } + int len = objlen(L, 2); + if (len < image.getDataSize()) { + throw std::runtime_error( + "data size mismatch expected " + + std::to_string(image.getDataSize()) + ", got " + std::to_string(len) + ); + } + for (size_t i = 0; i < len; i++) { + rawgeti(L, i + 1, 2); + data[i] = tointeger(L, -1); + pop(L); + } return 0; } static int l_update(State* L) { if (auto canvas = touserdata(L, 1)) { - canvas->texture().reload(canvas->data()); + if (canvas->hasTexture()) { + canvas->texture().reload(canvas->data()); + } } return 0; } @@ -88,7 +169,11 @@ static int l_update(State* L) { static std::unordered_map methods { {"at", lua::wrap}, {"set", lua::wrap}, - {"update", lua::wrap} + {"line", lua::wrap}, + {"blit", lua::wrap}, + {"clear", lua::wrap}, + {"update", lua::wrap}, + {"_set_data", lua::wrap}, }; static int l_meta_index(State* L) { @@ -110,6 +195,9 @@ static int l_meta_index(State* L) { if (!strcmp(name, "height")) { return pushinteger(L, data.getHeight()); } + if (!strcmp(name, "set_data")) { + return getglobal(L, "__vc_Canvas_set_data"); + } if (auto func = methods.find(tostring(L, 2)); func != methods.end()) { return pushcfunction(L, func->second); } @@ -126,18 +214,33 @@ static int l_meta_newindex(State* L) { if (isnumber(L, 2) && isnumber(L, 3)) { if (auto pixel = get_at(data, static_cast(tointeger(L, 2)))) { pixel->rgba = static_cast(tointeger(L, 3)); - return 1; } - return 1; } return 0; } +static int l_meta_meta_call(lua::State* L) { + auto size = glm::ivec2(tovec2(L, 2)); + if (size.x <= 0 || size.y <= 0) { + throw std::runtime_error("size must be positive"); + } + return newuserdata( + L, + nullptr, + std::make_shared(ImageFormat::rgba8888, size.x, size.y) + ); +} + int LuaCanvas::createMetatable(State* L) { createtable(L, 0, 3); pushcfunction(L, lua::wrap); setfield(L, "__index"); pushcfunction(L, lua::wrap); setfield(L, "__newindex"); + + createtable(L, 0, 1); + pushcfunction(L, lua::wrap); + setfield(L, "__call"); + setmetatable(L); return 1; } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 92ed66a2..66606776 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -120,7 +120,7 @@ std::unique_ptr scripting::start_coroutine( lua::loadbuffer(L, 0, source, script.name()); if (lua::call(L, 1)) { int id = lua::tointeger(L, -1); - lua::pop(L, 2); + lua::pop(L, 1); return std::make_unique(L, id); } lua::pop(L); diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 09bb7dbb..4e9f8894 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -370,11 +370,14 @@ dv::value Entities::serialize(const Entity& entity) { dv::value Entities::serialize(const std::vector& entities) { auto list = dv::list(); for (auto& entity : entities) { - if (!entity.getDef().save.enabled) { + const EntityId& eid = entity.getID(); + if (!entity.getDef().save.enabled || eid.destroyFlag) { continue; } level.entities->onSave(entity); - list.add(level.entities->serialize(entity)); + if (!eid.destroyFlag) { + list.add(level.entities->serialize(entity)); + } } return list; } @@ -597,9 +600,9 @@ bool Entities::hasBlockingInside(AABB aabb) { std::vector Entities::getAllInside(AABB aabb) { std::vector collected; - auto view = registry.view(); - for (auto [entity, transform] : view.each()) { - if (aabb.contains(transform.pos)) { + auto view = registry.view(); + for (auto [entity, eid, transform] : view.each()) { + if (!eid.destroyFlag && aabb.contains(transform.pos)) { const auto& found = uids.find(entity); if (found == uids.end()) { continue;