From 79bf3a0f2cd45afb56d458452f979995d7223d25 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 15:09:19 +0300 Subject: [PATCH 01/40] add triangle vcm shape - 'tri' --- src/coders/vcm.cpp | 35 ++++++++++++++++++++++++++++++++++ src/coders/xml.cpp | 13 +++++++++++++ src/coders/xml.hpp | 1 + src/graphics/commons/Model.cpp | 14 ++++++++++++++ src/graphics/commons/Model.hpp | 9 +++++++++ 5 files changed, 72 insertions(+) diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index b7d93b578..c797d8566 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -71,6 +71,39 @@ static void perform_rect(const xmlelement& root, model::Model& model) { ); } +static void perform_triangle(const xmlelement& root, model::Model& model) { + auto pointA = root.attr("a").asVec3(); + auto pointB = root.attr("b").asVec3(); + auto pointC = root.attr("c").asVec3(); + + float uvs[6] {0, 0, 1, 0, 1, 1}; + + bool shading = true; + if (root.has("shading")) { + shading = to_boolean(root.attr("shading")); + } + if (root.has("uv")) { + root.attr("uv").asNumbers(uvs, sizeof(uvs) / sizeof(float)); + } + + glm::vec3 ba = pointB - pointA; + glm::vec3 ca = pointC - pointA; + glm::vec3 normal = glm::normalize(glm::cross(ba, ca)); + + std::string texture = root.attr("texture", "$0").getText(); + auto& mesh = model.addMesh(texture, shading); + + mesh.addTriangle( + pointA, + pointB, + pointC, + normal, + {uvs[0], uvs[1]}, + {uvs[2], uvs[3]}, + {uvs[4], uvs[5]} + ); +} + static void perform_box(const xmlelement& root, model::Model& model) { auto from = root.attr("from").asVec3(); auto to = root.attr("to").asVec3(); @@ -181,6 +214,8 @@ static std::unique_ptr load_model(const xmlelement& root) { perform_rect(*elem, model); } else if (tag == "box") { perform_box(*elem, model); + } else if (tag == "tri") { + perform_triangle(*elem, model); } } diff --git a/src/coders/xml.cpp b/src/coders/xml.cpp index 0763fa430..4efabc959 100644 --- a/src/coders/xml.cpp +++ b/src/coders/xml.cpp @@ -105,6 +105,19 @@ glm::vec4 Attribute::asColor() const { } } +int Attribute::asNumbers(float* dst, size_t dstSize) const { + size_t pos = 0; + size_t count = 0; + while (pos < text.length() && count < dstSize) { + size_t end = text.find(',', pos); + dst[count++] = util::parse_double( + text, pos, std::min(end - pos, text.length() - pos) + ); + pos = end + 1; + } + return count; +} + Node::Node(std::string tag) : tag(std::move(tag)) { } diff --git a/src/coders/xml.hpp b/src/coders/xml.hpp index 24cf0fbdc..4954caa17 100644 --- a/src/coders/xml.hpp +++ b/src/coders/xml.hpp @@ -29,6 +29,7 @@ namespace xml { glm::vec3 asVec3() const; glm::vec4 asVec4() const; glm::vec4 asColor() const; + int asNumbers(float* dst, size_t dstSize) const; }; /// @brief XML element class. Text element has tag 'text' and attribute diff --git a/src/graphics/commons/Model.cpp b/src/graphics/commons/Model.cpp index 386ae1cce..e7c247868 100644 --- a/src/graphics/commons/Model.cpp +++ b/src/graphics/commons/Model.cpp @@ -41,6 +41,20 @@ void Mesh::addPlane( ); } +void Mesh::addTriangle( + const glm::vec3& a, + const glm::vec3& b, + const glm::vec3& c, + const glm::vec3& norm, + const glm::vec2& uvA, + const glm::vec2& uvB, + const glm::vec2& uvC +) { + vertices.push_back({a, uvA, norm}); + vertices.push_back({b, uvB, norm}); + vertices.push_back({c, uvC, norm}); +} + void Mesh::addRect( const glm::vec3& pos, const glm::vec3& right, diff --git a/src/graphics/commons/Model.hpp b/src/graphics/commons/Model.hpp index bbb0d398f..67618ed38 100644 --- a/src/graphics/commons/Model.hpp +++ b/src/graphics/commons/Model.hpp @@ -39,6 +39,15 @@ namespace model { const glm::vec3& up, const glm::vec3& norm, const UVRegion& region + ); + void addTriangle( + const glm::vec3& a, + const glm::vec3& b, + const glm::vec3& c, + const glm::vec3& norm, + const glm::vec2& uvA, + const glm::vec2& uvB, + const glm::vec2& uvC ); void addBox(const glm::vec3& pos, const glm::vec3& size); void addBox( From 152e447c29c98e0eb9434787ff1d0b5707fee0d3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 15:42:13 +0300 Subject: [PATCH 02/40] update doc/*/block-models.md --- doc/en/block-models.md | 5 ++++- doc/ru/block-models.md | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/en/block-models.md b/doc/en/block-models.md index c6fe7f19e..ba2f75858 100644 --- a/doc/en/block-models.md +++ b/doc/en/block-models.md @@ -1,4 +1,7 @@ -# Block Models +# Block Models (depricated) + +> ![WARNING] +> The `model-primitives` property is deprecated. Use the [VCM format](vcm.md) for a textual description of the model's primitives. Block model may be created with following properties: diff --git a/doc/ru/block-models.md b/doc/ru/block-models.md index 660f5d37e..a7ae6636c 100644 --- a/doc/ru/block-models.md +++ b/doc/ru/block-models.md @@ -1,4 +1,7 @@ -# Модели блоков +# Модели блоков (устарело) + +> ![WARNING] +> Свойство `model-primitives` является устаревшим, используйте [формат VCM](vcm.md) для текстового описания модели из примитивов. Создание собственной модели блока может быть реализовано при указании у блока свойств: ```js From 3e0ef334d40f0515c1db8dea0ce4ba2237340599 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 15:55:47 +0300 Subject: [PATCH 03/40] update doc/*/vcm.md --- doc/ru/vcm.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/ru/vcm.md b/doc/ru/vcm.md index 379e0328d..2a86fc990 100644 --- a/doc/ru/vcm.md +++ b/doc/ru/vcm.md @@ -20,15 +20,28 @@ В отличие от XML в корне может находиться несколько элементов. -На данный момент существует два вида примитивов: box и rect, а также, part (описывает часть примитива, такую как, например, сторона куба). +На данный момент существует три вида примитивов: tri, box и rect, а также, part (описывает часть примитива, такую как, например, сторона куба). > [!NOTE] > По-умолчанию текстурные координаты определяются размерами примитивов. > При размерах более 1.0 результат не определён и может измениться в последующих обновлениях. > Используйте `region` или `region-scale` для ручной настройки. +### Свойства `tri` + +Примитив описывает треугольник. + +- `a` - точка A. Пример: `a (0,0,0)` +- `b` - точка B. Пример: `b (1,0,0)` +- `c` - точка С. Пример: `c (1,1,0)` +- `uv` - нормализованные текстурные координаты точек (6 чисел, по 2 на точку). Пример: `uv (0,0, 1,0, 1,1)` +- `texture` - отображаемая текстура. По-умолчанию: `$0`. Пример: `texture "blocks:sand"` +- `shading` определяет возможность затенения на примитиве. Пример: `shading off` + ### Свойства `rect` +Примитив описывает параллелограмм через векторы right и up. + - `from` - точка начала примитива. Пример: `from (0,0.5,0.125)` - `right` - вектор X, также определяющий ширину примитива. Пример: `right (1,0,0)` - `up` - вектор Y, также определяющий высоту примитива. Пример: `up (0,1,0)` @@ -40,6 +53,8 @@ ### Свойства `box` +Примитив описывает прямоугольный параллелепипед. + - `from` - точка начала примитива. Пример: `from (0,0,0)` - `to` - противоположная от начала точка. Пример: `to (1,1,1)` - `origin` - точка, относительно которой будет применено вращение. По-умолчанию: центр примитива. Пример: `origin (0.5,0.5,0.5)` @@ -48,7 +63,9 @@ - `shading` определяет возможность затенения на примитиве. Пример: `shading off` - `delete` удаляет стороны по именам (top, bottom, east, west, north, south) -### Свойства `part` (в `box`) +### Свойства `part` (вложен в `box`) + +Примитив описывает свойства сторон прямоугольного параллелепипеда с указанными тегами (top/bottom/north/south/east/west). - `tags` - определяет то, на какаие из сторон будут применены свойства. Пример: `tags (top,bottom)` - `texture` - отображаемая текстура. По-умолчанию: `$индекс_стороны`. From 331215e783855aa15ed06bb70fc5222d84203862 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 16:28:04 +0300 Subject: [PATCH 04/40] feat: 'region', 'region-scale' support for 'tri' primitive --- doc/ru/vcm.md | 4 ++++ src/coders/vcm.cpp | 36 ++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/doc/ru/vcm.md b/doc/ru/vcm.md index 2a86fc990..8943a19a0 100644 --- a/doc/ru/vcm.md +++ b/doc/ru/vcm.md @@ -36,8 +36,12 @@ - `c` - точка С. Пример: `c (1,1,0)` - `uv` - нормализованные текстурные координаты точек (6 чисел, по 2 на точку). Пример: `uv (0,0, 1,0, 1,1)` - `texture` - отображаемая текстура. По-умолчанию: `$0`. Пример: `texture "blocks:sand"` +- `region` - UV регион в границах выбранной текстуры, определяется позициями противоположных углов квадрата нормализованных текстурных координат. Пример: `region (0,0,1,1)` +- `region-scale` вектор-множитель для UV региона. Пример: `region-scale (0.5,1)` - `shading` определяет возможность затенения на примитиве. Пример: `shading off` +Текстурные координаты по-умолчанию не зависят от формы из размера, в отличие от других примитивов. + ### Свойства `rect` Примитив описывает параллелограмм через векторы right и up. diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index c797d8566..5bf54ee99 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "xml.hpp" #include "util/stringutil.hpp" @@ -76,32 +77,35 @@ static void perform_triangle(const xmlelement& root, model::Model& model) { auto pointB = root.attr("b").asVec3(); auto pointC = root.attr("c").asVec3(); - float uvs[6] {0, 0, 1, 0, 1, 1}; + glm::vec2 uvs[3] {{0, 0}, {1, 0}, {1, 1}}; bool shading = true; if (root.has("shading")) { shading = to_boolean(root.attr("shading")); } - if (root.has("uv")) { - root.attr("uv").asNumbers(uvs, sizeof(uvs) / sizeof(float)); - } - + glm::vec3 ba = pointB - pointA; glm::vec3 ca = pointC - pointA; glm::vec3 normal = glm::normalize(glm::cross(ba, ca)); - + + if (root.has("uv")) { + root.attr("uv").asNumbers(glm::value_ptr(uvs[0]), 6); + } else { + UVRegion region {}; + if (root.has("region")) { + region.set(root.attr("region").asVec4()); + } + if (root.has("region-scale")) { + region.scale(root.attr("region-scale").asVec2()); + } + for (auto& uv : uvs) { + uv = region.apply(uv); + } + } + std::string texture = root.attr("texture", "$0").getText(); auto& mesh = model.addMesh(texture, shading); - - mesh.addTriangle( - pointA, - pointB, - pointC, - normal, - {uvs[0], uvs[1]}, - {uvs[2], uvs[3]}, - {uvs[4], uvs[5]} - ); + mesh.addTriangle(pointA, pointB, pointC, normal, uvs[0], uvs[1], uvs[2]); } static void perform_box(const xmlelement& root, model::Model& model) { From b3ae6eb59acfc34a338d4ad7ae2cc2d5fb8cbaa7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 16:40:00 +0300 Subject: [PATCH 05/40] add 'record-audio' permission --- res/layouts/pages/settings_audio.xml.lua | 5 ++++- res/project.toml | 3 ++- src/audio/audio.cpp | 14 ++++++++++++-- src/audio/audio.hpp | 2 +- src/devtools/Project.hpp | 1 + src/engine/Engine.cpp | 6 +++++- 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index eaa8b3fb1..03d8e1a9b 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -67,5 +67,8 @@ function on_open() table.insert(devices, {value=name, text=name}) end selectbox.options = devices - selectbox.value = audio.__get_input_info().device_specifier + local info = audio.__get_input_info() + if info then + selectbox.value = info.device_specifier + end end diff --git a/res/project.toml b/res/project.toml index c7e2fb69d..b641ad00f 100644 --- a/res/project.toml +++ b/res/project.toml @@ -2,5 +2,6 @@ name = "default" base_packs = ["base"] permissions = [ - "network" + "network", + "record-audio" ] diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 436fa1dad..dc0e57463 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -24,6 +24,7 @@ namespace { std::vector> channels; util::ObjectsKeeper objects_keeper {}; std::unique_ptr input_device = nullptr; + static bool input_enabled = false; } Channel::Channel(std::string name) : name(std::move(name)) { @@ -152,7 +153,9 @@ public: } }; -void audio::initialize(bool enabled, AudioSettings& settings) { +void audio::initialize( + bool enabled, bool inputEnabled, AudioSettings& settings +) { enabled = enabled && settings.enabled.get(); if (enabled) { logger.info() << "initializing ALAudio backend"; @@ -182,7 +185,10 @@ void audio::initialize(bool enabled, AudioSettings& settings) { }, true)); } - ::input_device = backend->openInputDevice("", 44100, 1, 16); + if (inputEnabled) { + ::input_device = backend->openInputDevice("", 44100, 1, 16); + ::input_enabled = true; + } if (::input_device) { ::input_device->startCapture(); } @@ -270,6 +276,10 @@ std::vector audio::get_output_devices_names() { void audio::set_input_device(const std::string& deviceName) { logger.info() << "setting input device to " << deviceName; + if (!::input_enabled) { + logger.warning() << "unable to set input device due to project permissions"; + return; + } if (deviceName == audio::DEVICE_NONE) { if (::input_device) { ::input_device->stopCapture(); diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 0ccd51f35..642b9235d 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -402,7 +402,7 @@ namespace audio { /// @brief Initialize audio system or use no audio mode /// @param enabled try to initialize actual audio - void initialize(bool enabled, AudioSettings& settings); + void initialize(bool enabled, bool inputEnabled, AudioSettings& settings); /// @brief Load audio file info and PCM data /// @param file audio file diff --git a/src/devtools/Project.hpp b/src/devtools/Project.hpp index ba2d0d407..9be0194ae 100644 --- a/src/devtools/Project.hpp +++ b/src/devtools/Project.hpp @@ -13,6 +13,7 @@ namespace scripting { } struct Permissions { + static inline std::string RECORD_AUDIO = "record-audio"; static inline std::string WRITE_TO_USER = "write-to-user"; static inline std::string NETWORK = "network"; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 9dcfbaa98..5cb0e7ca6 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -160,7 +160,11 @@ void Engine::initialize(CoreParameters coreParameters) { if (!params.headless) { initializeClient(); } - audio::initialize(!params.headless, settings.audio); + audio::initialize( + !params.headless, + project->permissions.has(Permissions::RECORD_AUDIO), + settings.audio + ); if (settings.ui.language.get() == "auto") { settings.ui.language.set( From 87deff97f3d1e386454de19efc4446fc623ebf69 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 16:45:07 +0300 Subject: [PATCH 06/40] add 'debugging' permission --- src/devtools/Project.hpp | 3 ++- src/logic/scripting/lua/libs/libapp.cpp | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/devtools/Project.hpp b/src/devtools/Project.hpp index 9be0194ae..23aa11ca5 100644 --- a/src/devtools/Project.hpp +++ b/src/devtools/Project.hpp @@ -13,9 +13,10 @@ namespace scripting { } struct Permissions { + static inline std::string DEBUGGING = "debugging"; + static inline std::string NETWORK = "network"; static inline std::string RECORD_AUDIO = "record-audio"; static inline std::string WRITE_TO_USER = "write-to-user"; - static inline std::string NETWORK = "network"; std::set permissions; diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 7c406d1aa..d23f96cb0 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -6,6 +6,7 @@ #include "content/ContentControl.hpp" #include "engine/Engine.hpp" #include "engine/EnginePaths.hpp" +#include "devtools/Project.hpp" #include "network/Network.hpp" #include "util/platform.hpp" #include "window/Window.hpp" @@ -13,6 +14,10 @@ using namespace scripting; static int l_start_debug_instance(lua::State* L) { + if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) { + throw std::runtime_error("project has no debugging permission"); + } + int port = lua::tointeger(L, 1); if (port == 0) { auto network = engine->getNetwork(); From 6942704c78d7ff3365003bb696a0ee5af935e661 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 16:45:17 +0300 Subject: [PATCH 07/40] cleanup --- res/layouts/pages/settings_audio.xml.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index 03d8e1a9b..f8a23baa9 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -18,8 +18,8 @@ function update_setting(x, id, name, postfix) -- updating label document[id..".L"].text = string.format( "%s: %s%s", - gui.str(name, "settings"), - core.str_setting(id), + gui.str(name, "settings"), + core.str_setting(id), postfix ) end From 72040b5dbb37c6588a53e6d7584f16e98ed0a8e2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 17:19:40 +0300 Subject: [PATCH 08/40] fix debugging server creation --- src/devtools/DebuggingServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devtools/DebuggingServer.cpp b/src/devtools/DebuggingServer.cpp index ad56e46ec..cb50c87a8 100644 --- a/src/devtools/DebuggingServer.cpp +++ b/src/devtools/DebuggingServer.cpp @@ -102,7 +102,7 @@ static network::Server& create_tcp_server( } u64id_t serverId = network->openTcpServer( port, - [&network, &dbgServer](u64id_t sid, u64id_t id) { + [network, &dbgServer](u64id_t sid, u64id_t id) { auto& connection = dynamic_cast( *network->getConnection(id, true) ); From e371153288c3eb99e568fe19fb725f151d6a58ac Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 5 Jan 2026 19:19:10 +0300 Subject: [PATCH 09/40] fix conditional jump or move depends on uninitialised value in GLFWWindow --- src/window/detail/GLFWWindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/window/detail/GLFWWindow.cpp b/src/window/detail/GLFWWindow.cpp index 074693066..a27b27b03 100644 --- a/src/window/detail/GLFWWindow.cpp +++ b/src/window/detail/GLFWWindow.cpp @@ -332,8 +332,8 @@ private: GLFWwindow* window; bool cursorLocked = false; bool cursorDrag = false; - glm::vec2 delta; - glm::vec2 cursor; + glm::vec2 delta {}; + glm::vec2 cursor {}; }; static_assert(!std::is_abstract()); @@ -577,7 +577,7 @@ private: CursorShape cursor = CursorShape::ARROW; int framerate = -1; std::stack scissorStack; - glm::vec4 scissorArea; + glm::vec4 scissorArea {}; double prevSwap = 0.0; int posX = 0; int posY = 0; From ce769143f881afbaa1136ec6a0692ff2b4cc2bd4 Mon Sep 17 00:00:00 2001 From: clasher113 Date: Mon, 5 Jan 2026 19:13:44 +0200 Subject: [PATCH 10/40] fix blocks raycast filter --- src/voxels/blocks_agent.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/voxels/blocks_agent.cpp b/src/voxels/blocks_agent.cpp index 46fae323c..723e01d24 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -264,8 +264,7 @@ static inline voxel* raycast_blocks( } const auto& def = blocks.require(voxel->id); - if ((filter.empty() && def.selectable) || - (!filter.empty() && filter.find(def.rt.id) == filter.end())) { + if (def.selectable && (filter.empty() || filter.find(def.rt.id) == filter.end())) { end.x = px + t * dx; end.y = py + t * dy; end.z = pz + t * dz; From 8f1739c85b90b6484580c79e03c3896873fd1859 Mon Sep 17 00:00:00 2001 From: clasher113 Date: Mon, 5 Jan 2026 23:05:47 +0200 Subject: [PATCH 11/40] add "include_non_selectable" parameter to blocks raycast --- doc/en/scripting/builtins/libblock.md | 7 ++++++- doc/en/scripting/builtins/libentities.md | 5 +++-- doc/ru/scripting/builtins/libblock.md | 7 ++++++- doc/ru/scripting/builtins/libentities.md | 5 +++-- src/logic/scripting/lua/libs/libblock.cpp | 12 +++++++++--- src/logic/scripting/lua/libs/libentity.cpp | 12 +++++++++--- src/voxels/Chunks.cpp | 5 +++-- src/voxels/Chunks.hpp | 3 ++- src/voxels/blocks_agent.cpp | 16 ++++++++++------ src/voxels/blocks_agent.hpp | 8 ++++++-- 10 files changed, 57 insertions(+), 23 deletions(-) diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md index c93735f78..b2528c364 100644 --- a/doc/en/scripting/builtins/libblock.md +++ b/doc/en/scripting/builtins/libblock.md @@ -136,7 +136,9 @@ Set specified bits. ## Raycast ```lua -block.raycast(start: vec3, dir: vec3, max_distance: number, [optional] dest: table, [optional] filter: table) -> { +block.raycast(start: vec3, dir: vec3, max_distance: number, [optional] dest: table, + [optional] filter: table, [optional] include_non_selectable = false +) -> { block: int, -- block id endpoint: vec3, -- point of the ray hit point iendpoint: vec3, -- position of the block hit by the ray @@ -150,6 +152,9 @@ Casts a ray from the start point in the direction of *dir*. Max_distance specifi Argument `filter` can be used to tell ray what blocks can be skipped(passed through) during ray-casting. To use filter `dest` argument must be filled with some value(can be nil), it's done for backwards compatability +The `include_non_selectable` argument determines whether blocks that cannot be selected by the cursor will be included. +Example - `base:water` + The function returns a table with the results or nil if the ray does not hit any block. The result will use the destination table instead of creating a new one if the optional argument specified. diff --git a/doc/en/scripting/builtins/libentities.md b/doc/en/scripting/builtins/libentities.md index 3fc242579..d65d95f05 100644 --- a/doc/en/scripting/builtins/libentities.md +++ b/doc/en/scripting/builtins/libentities.md @@ -63,8 +63,9 @@ entities.get_all_in_radius(center: vec3, radius: number) -> array ``` ```lua -entities.raycast(start: vec3, dir: vec3, max_distance: number, - ignore: int, [optional] destination: table, [optional] filter: table) -> table or nil +entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [optional] destination: table, + [optional] filter: table, [optional] include_non_selectable = false +) -> table or nil ``` The function is an extended version of [block.raycast](libblock.md#raycast). Returns a table with the results if the ray touches a block or entity. diff --git a/doc/ru/scripting/builtins/libblock.md b/doc/ru/scripting/builtins/libblock.md index 4e20e696c..a82d2ff66 100644 --- a/doc/ru/scripting/builtins/libblock.md +++ b/doc/ru/scripting/builtins/libblock.md @@ -92,7 +92,9 @@ block.material(blockid: int) -> string ## Raycast ```lua -block.raycast(start: vec3, dir: vec3, max_distance: number, [опционально] dest: table, [опционально] filter: table) -> { +block.raycast(start: vec3, dir: vec3, max_distance: number, [опционально] dest: table, + [опционально] filter: table, [опционально] include_non_selectable = false +) -> { block: int, -- id блока endpoint: vec3, -- точка касания луча iendpoint: vec3, -- позиция блока, которого касается луч @@ -106,6 +108,9 @@ block.raycast(start: vec3, dir: vec3, max_distance: number, [опциональ Аргумент `filter` позволяет указать какие блоки являются "прозрачными" для луча, прим.: {"base:glass","base:water"}. Для использования агрумент `dest` нужно чем-то заполнить(можно nil), это сделано для обратной совместимости +Аргумент `include_non_selectable` определяет, будут ли учтены блоки, которые нельзя выбрать курсором. +Пример - `base:water` + Функция возвращает таблицу с результатами или nil, если луч не касается блока. Для результата будет использоваться целевая (dest) таблица вместо создания новой, если указан опциональный аргумент. diff --git a/doc/ru/scripting/builtins/libentities.md b/doc/ru/scripting/builtins/libentities.md index a66bc83b8..f37dc1b32 100644 --- a/doc/ru/scripting/builtins/libentities.md +++ b/doc/ru/scripting/builtins/libentities.md @@ -62,8 +62,9 @@ entities.get_all_in_radius(center: vec3, radius: number) -> table ``` ```lua -entities.raycast(start: vec3, dir: vec3, max_distance: number, - ignore: int, [optional] destination: table, [optional] filter: table) -> table или nil +entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [optional] destination: table, + [optional] filter: table, [optional] include_non_selectable = false +) -> table или nil ``` Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности. diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index b87020536..6a84b4113 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -476,8 +476,10 @@ static int l_raycast(lua::State* L) { auto start = lua::tovec<3>(L, 1); auto dir = lua::tovec<3>(L, 2); auto maxDistance = lua::tonumber(L, 3); + bool includeNonSelectable = false; std::set filteredBlocks {}; - if (lua::gettop(L) >= 5) { + const int luaStackSize = lua::gettop(L); + if (luaStackSize >= 5) { if (lua::istable(L, 5)) { int addLen = lua::objlen(L, 5); for (int i = 0; i < addLen; i++) { @@ -493,6 +495,9 @@ static int l_raycast(lua::State* L) { throw std::runtime_error("table expected for filter"); } } + if (luaStackSize >= 6) { + includeNonSelectable = lua::toboolean(L, 6); + } glm::vec3 end; glm::ivec3 normal; glm::ivec3 iend; @@ -504,9 +509,10 @@ static int l_raycast(lua::State* L) { end, normal, iend, - filteredBlocks + filteredBlocks, + includeNonSelectable )) { - if (lua::gettop(L) >= 4 && !lua::isnil(L, 4)) { + if (luaStackSize >= 4 && !lua::isnil(L, 4)) { lua::pushvalue(L, 4); } else { lua::createtable(L, 0, 5); diff --git a/src/logic/scripting/lua/libs/libentity.cpp b/src/logic/scripting/lua/libs/libentity.cpp index 5cab675a0..5bf3e4d74 100644 --- a/src/logic/scripting/lua/libs/libentity.cpp +++ b/src/logic/scripting/lua/libs/libentity.cpp @@ -141,8 +141,10 @@ static int l_raycast(lua::State* L) { auto dir = lua::tovec<3>(L, 2); auto maxDistance = lua::tonumber(L, 3); auto ignoreEntityId = lua::tointeger(L, 4); + bool includeNonSelectable = false; std::set filteredBlocks {}; - if (lua::gettop(L) >= 6) { + const int luaStackSize = lua::gettop(L); + if (luaStackSize >= 6) { if (lua::istable(L, 6)) { int addLen = lua::objlen(L, 6); for (int i = 0; i < addLen; i++) { @@ -158,6 +160,9 @@ static int l_raycast(lua::State* L) { throw std::runtime_error("table expected for filter"); } } + if (luaStackSize >= 7) { + includeNonSelectable = lua::toboolean(L, 6); + } glm::vec3 end; glm::ivec3 normal; @@ -173,14 +178,15 @@ static int l_raycast(lua::State* L) { end, normal, iend, - filteredBlocks + filteredBlocks, + includeNonSelectable )) { maxDistance = glm::distance(start, end); block = voxel->id; } if (auto ray = level->entities->rayCast(start, dir, maxDistance, ignoreEntityId)) { - if (lua::gettop(L) >= 5 && !lua::isnil(L, 5)) { + if (luaStackSize >= 5 && !lua::isnil(L, 5)) { lua::pushvalue(L, 5); } else { lua::createtable(L, 0, 6); diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index a4b693829..7c4246cad 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -215,10 +215,11 @@ voxel* Chunks::rayCast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter + std::set filter, + bool includeNonSelectable ) const { return blocks_agent::raycast( - *this, start, dir, maxDist, end, norm, iend, std::move(filter) + *this, start, dir, maxDist, end, norm, iend, std::move(filter), includeNonSelectable ); } diff --git a/src/voxels/Chunks.hpp b/src/voxels/Chunks.hpp index e185ce584..5b8866bfc 100644 --- a/src/voxels/Chunks.hpp +++ b/src/voxels/Chunks.hpp @@ -111,7 +111,8 @@ public: glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter = {} + std::set filter = {}, + bool includeNonSelectable = false ) const; glm::vec3 rayCastToObstacle( diff --git a/src/voxels/blocks_agent.cpp b/src/voxels/blocks_agent.cpp index 723e01d24..b2a16a886 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -221,7 +221,8 @@ static inline voxel* raycast_blocks( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter + std::set filter, + bool includeNonSelectable ) { const auto& blocks = chunks.getContentIndices().blocks; float px = start.x; @@ -264,7 +265,8 @@ static inline voxel* raycast_blocks( } const auto& def = blocks.require(voxel->id); - if (def.selectable && (filter.empty() || filter.find(def.rt.id) == filter.end())) { + if (voxel->id != BLOCK_AIR && (def.selectable || includeNonSelectable) && + (filter.empty() || filter.find(def.rt.id) == filter.end())) { end.x = px + t * dx; end.y = py + t * dy; end.z = pz + t * dz; @@ -361,9 +363,10 @@ voxel* blocks_agent::raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter + std::set filter, + bool includeNonSelectable ) { - return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter); + return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter, includeNonSelectable); } voxel* blocks_agent::raycast( @@ -374,9 +377,10 @@ voxel* blocks_agent::raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter + std::set filter, + bool includeNonSelectable ) { - return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter); + return raycast_blocks(chunks, start, dir, maxDist, end, norm, iend, filter, includeNonSelectable); } // reduce nesting on next modification diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index 7c0223cbd..9ffac7f93 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -419,6 +419,7 @@ inline void set_rotation( /// @param norm [out] surface normal vector /// @param iend [out] ray end integer position (voxel position + normal) /// @param filter filtered ids +/// @param includeNonSelectable will non-selectable blocks be included /// @return voxel pointer or nullptr voxel* raycast( const Chunks& chunks, @@ -428,7 +429,8 @@ voxel* raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter + std::set filter, + bool includeNonSelectable ); /// @brief Cast ray to a selectable block with filter based on id. @@ -440,6 +442,7 @@ voxel* raycast( /// @param norm [out] surface normal vector /// @param iend [out] ray end integer position (voxel position + normal) /// @param filter filtered ids +/// @param includeNonSelectable will non-selectable blocks be included /// @return voxel pointer or nullptr voxel* raycast( const GlobalChunks& chunks, @@ -449,7 +452,8 @@ voxel* raycast( glm::vec3& end, glm::ivec3& norm, glm::ivec3& iend, - std::set filter + std::set filter, + bool includeNonSelectable ); void get_voxels(const Chunks& chunks, VoxelsVolume* volume, bool backlight=false); From f171d3e30844c69a0675d23db583f34abdcd4f6c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 6 Jan 2026 13:41:18 +0300 Subject: [PATCH 12/40] fix gfx.posteffects.set_intensity --- src/logic/scripting/lua/libs/libposteffects.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/logic/scripting/lua/libs/libposteffects.cpp b/src/logic/scripting/lua/libs/libposteffects.cpp index d16e0c85b..6eedaa6c8 100644 --- a/src/logic/scripting/lua/libs/libposteffects.cpp +++ b/src/logic/scripting/lua/libs/libposteffects.cpp @@ -34,7 +34,9 @@ static int l_get_intensity(lua::State* L) { static int l_set_intensity(lua::State* L) { size_t index = static_cast(lua::tointeger(L, 1)); float value = lua::tonumber(L, 2); - post_processing->getEffect(index)->setIntensity(value); + if (auto effect = post_processing->getEffect(index)) { + effect->setIntensity(value); + } return 0; } From 6cdd6b833cd8e0d9e2dbdfdfb486d261c2705d62 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 6 Jan 2026 23:13:17 +0300 Subject: [PATCH 13/40] feat: debugging server does not require network permission now --- src/engine/Engine.cpp | 3 ++- src/logic/scripting/lua/libs/libapp.cpp | 6 ++++++ src/logic/scripting/lua/libs/libnetwork.cpp | 8 +++++--- src/util/platform.cpp | 1 + 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 5cb0e7ca6..5223a6c38 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -139,7 +139,8 @@ void Engine::initialize(CoreParameters coreParameters) { editor = std::make_unique(*this); cmd = std::make_unique(); - if (project->permissions.has(Permissions::NETWORK)) { + if (project->permissions.has(Permissions::NETWORK) || + !params.debugServerString.empty()) { network = network::Network::create(settings.network); } diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index d23f96cb0..c40aa63bd 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -29,6 +29,7 @@ static int l_start_debug_instance(lua::State* L) { throw std::runtime_error("could not find free port"); } } + auto projectPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : ""; const auto& paths = engine->getPaths(); std::vector args { @@ -36,6 +37,11 @@ static int l_start_debug_instance(lua::State* L) { "--dir", paths.getUserFilesFolder().u8string(), "--dbg-server", "tcp:" + std::to_string(port), }; + if (!projectPath.empty()) { + args.emplace_back("--project"); + args.emplace_back(io::resolve(std::string(projectPath)).string()); + } + platform::new_engine_instance(std::move(args)); return lua::pushinteger(L, port); } diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 390845207..7fbf6df8d 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -2,6 +2,7 @@ #include "coders/json.hpp" #include "engine/Engine.hpp" #include "network/Network.hpp" +#include "devtools/Project.hpp" #include #include @@ -439,7 +440,7 @@ static int l_is_nodelay(lua::State* L, network::Network& network) { return lua::pushboolean(L, false); } -static int l_pull_events(lua::State* L, network::Network& network) { +static int l_pull_events(lua::State* L) { std::vector local_queue; { std::lock_guard lock(events_queue_mutex); @@ -528,7 +529,8 @@ int wrap(lua_State* L) { int result = 0; try { auto network = engine->getNetwork(); - if (network == nullptr) { + const auto& permissions = engine->getProject().permissions; + if (network == nullptr || !permissions.has(Permissions::NETWORK)) { throw std::runtime_error( "network subsystem is not available in the project" ); @@ -554,7 +556,7 @@ const luaL_Reg networklib[] = { {"get_total_download", wrap}, {"find_free_port", wrap}, {"is_available", lua::wrap}, - {"__pull_events", wrap}, + {"__pull_events", lua::wrap}, {"__open_tcp", wrap}, {"__open_udp", wrap}, {"__closeserver", wrap}, diff --git a/src/util/platform.cpp b/src/util/platform.cpp index dce6f89b7..a7ba041db 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -281,6 +281,7 @@ void platform::new_engine_instance(const std::vector& args) { ss << " >/dev/null &"; auto command = ss.str(); + logger.info() << command; if (int res = system(command.c_str())) { throw std::runtime_error( "starting an engine instance failed with code: " + From 9f39f8dbadf0506d4f8f374376a2c7d6ede90e23 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 6 Jan 2026 23:59:12 +0300 Subject: [PATCH 14/40] feat: disable app.load_content out of app script coroutine --- res/scripts/stdlib.lua | 141 ++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 64 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 1a8954172..7aabc20c7 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -30,6 +30,10 @@ function core.set_setting(name, value, ...) events.emit("core:setting."..name..".set", value) end +local __vc__app_script_coroutine +local __core_load_content = core.load_content +core.load_content = nil + local function complete_app_lib(app) app.sleep = sleep app.script = __VC_SCRIPT_NAME @@ -45,11 +49,16 @@ local function complete_app_lib(app) app.tick = function() coroutine.yield() end + + local __app_tick = app.tick app.get_version = core.get_version app.get_setting_info = core.get_setting_info app.load_content = function() - core.load_content() - app.tick() + if coroutine.running() ~= __vc__app_script_coroutine then + error("content must be reload in application script coroutine") + end + __core_load_content() + __app_tick() end app.reset_content = core.reset_content app.is_content_loaded = core.is_content_loaded @@ -123,6 +132,66 @@ function pack.unload(prefix) events.remove_by_prefix(prefix) end +local __vc_coroutines = {} +local __vc_named_coroutines = {} +local __vc_next_coroutine = 1 + +function __vc_start_coroutine(chunk) + local co = coroutine.create(chunk) + local id = __vc_next_coroutine + __vc_next_coroutine = __vc_next_coroutine + 1 + __vc_coroutines[id] = co + return id +end + +function __vc_resume_coroutine(id) + local co = __vc_coroutines[id] + if co then + local success, err = coroutine.resume(co) + if not success then + debug.error(err) + error(err) + end + return coroutine.status(co) ~= "dead" + end + return false +end + +function __vc_stop_coroutine(id) + local co = __vc_coroutines[id] + if co then + if coroutine.close then + coroutine.close(co) + end + __vc_coroutines[id] = nil + end +end + +function start_coroutine(chunk, name) + local co = coroutine.create(function() + local status, error = xpcall(chunk, function(err) + local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback() + + if hud then + gui.alert(fullmsg, function() + if world.is_open() then + __vc_app.close_world() + else + __vc_app.reset_content() + menu:reset() + menu.page = "main" + end + end) + end + return fullmsg + end) + if not status then + debug.error(error) + end + end) + __vc_named_coroutines[name] = co +end + function __vc_start_app_script(path, name) debug.log("starting application script "..path) @@ -134,9 +203,13 @@ function __vc_start_app_script(path, name) local script_env = setmetatable({app = app or __vc_app}, {__index=_G}) chunk = setfenv(chunk, script_env) if name then - return start_coroutine(chunk, name) + start_coroutine(chunk, name) + __vc__app_script_coroutine = __vc_named_coroutines[name] + return else - return __vc_start_coroutine(chunk) + local id = __vc_start_coroutine(chunk) + __vc__app_script_coroutine = __vc_coroutines[id] + return id end end @@ -435,66 +508,6 @@ function __vc_on_world_quit() file.__close_all_descriptors() end -local __vc_coroutines = {} -local __vc_named_coroutines = {} -local __vc_next_coroutine = 1 - -function __vc_start_coroutine(chunk) - local co = coroutine.create(chunk) - local id = __vc_next_coroutine - __vc_next_coroutine = __vc_next_coroutine + 1 - __vc_coroutines[id] = co - return id -end - -function __vc_resume_coroutine(id) - local co = __vc_coroutines[id] - if co then - local success, err = coroutine.resume(co) - if not success then - debug.error(err) - error(err) - end - return coroutine.status(co) ~= "dead" - end - return false -end - -function __vc_stop_coroutine(id) - local co = __vc_coroutines[id] - if co then - if coroutine.close then - coroutine.close(co) - end - __vc_coroutines[id] = nil - end -end - -function start_coroutine(chunk, name) - local co = coroutine.create(function() - local status, error = xpcall(chunk, function(err) - local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback() - - if hud then - gui.alert(fullmsg, function() - if world.is_open() then - __vc_app.close_world() - else - __vc_app.reset_content() - menu:reset() - menu.page = "main" - end - end) - end - return fullmsg - end) - if not status then - debug.error(error) - end - end) - __vc_named_coroutines[name] = co -end - local __post_runnables = {} local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer From 9d4a48096e6c4003c4f0a88a4cb23df812c2b26b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 00:07:17 +0300 Subject: [PATCH 15/40] feat: disable app.reset_content out of app script coroutine --- res/scripts/stdlib.lua | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 7aabc20c7..ac073df25 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -31,10 +31,14 @@ function core.set_setting(name, value, ...) end local __vc__app_script_coroutine -local __core_load_content = core.load_content -core.load_content = nil local function complete_app_lib(app) + local __core_load_content = core.load_content + local __core_reset_content = core.reset_content + local __app_tick = coroutine.yield + core.load_content = nil + core.reset_content = nil + app.sleep = sleep app.script = __VC_SCRIPT_NAME app.new_world = core.new_world @@ -46,21 +50,27 @@ local function complete_app_lib(app) app.reconfig_packs = core.reconfig_packs app.get_setting = core.get_setting app.set_setting = core.set_setting - app.tick = function() - coroutine.yield() - end - - local __app_tick = app.tick + app.tick = __app_tick app.get_version = core.get_version app.get_setting_info = core.get_setting_info - app.load_content = function() + + local function require_app_script_co() if coroutine.running() ~= __vc__app_script_coroutine then error("content must be reload in application script coroutine") end + end + + app.load_content = function() + require_app_script_co() __core_load_content() __app_tick() end - app.reset_content = core.reset_content + app.reset_content = function() + require_app_script_co() + __core_reset_content() + __app_tick() + end + app.is_content_loaded = core.is_content_loaded app.set_title = core.set_title From 43fa181398c6064fd165cd6244ed4847933cef4c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 00:19:34 +0300 Subject: [PATCH 16/40] remove core.set_title & fix app.reset_content args & update app.reconfig_packs --- res/scripts/stdlib.lua | 26 ++++++++++++------------ src/logic/scripting/lua/libs/libapp.cpp | 7 +++++++ src/logic/scripting/lua/libs/libcore.cpp | 7 ------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index ac073df25..155d95c0c 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -35,6 +35,7 @@ local __vc__app_script_coroutine local function complete_app_lib(app) local __core_load_content = core.load_content local __core_reset_content = core.reset_content + local __core_reconfig_packs = core.reconfig_packs local __app_tick = coroutine.yield core.load_content = nil core.reset_content = nil @@ -47,37 +48,36 @@ local function complete_app_lib(app) app.close_world = core.close_world app.reopen_world = core.reopen_world app.delete_world = core.delete_world - app.reconfig_packs = core.reconfig_packs app.get_setting = core.get_setting app.set_setting = core.set_setting app.tick = __app_tick app.get_version = core.get_version app.get_setting_info = core.get_setting_info - local function require_app_script_co() + local function call_in_app_script_co(func, ...) if coroutine.running() ~= __vc__app_script_coroutine then error("content must be reload in application script coroutine") end + func(...) + __app_tick() end - app.load_content = function() - require_app_script_co() - __core_load_content() - __app_tick() + app.reconfig_packs = function(...) + call_in_app_script_co(__core_reconfig_packs, ...) end - app.reset_content = function() - require_app_script_co() - __core_reset_content() - __app_tick() + app.load_content = function(...) + call_in_app_script_co(__core_load_content, ...) + end + app.reset_content = function(...) + call_in_app_script_co(__core_reset_content, ...) end app.is_content_loaded = core.is_content_loaded - app.set_title = core.set_title - + function app.config_packs(packs_list) -- Check if packs are valid and add dependencies to the configuration packs_list = pack.assemble(packs_list) - + local installed = pack.get_installed() local toremove = {} for _, packid in ipairs(installed) do diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index c40aa63bd..6d975af31 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -96,6 +96,12 @@ static int l_reset_content_sources(lua::State* L) { return 0; } +static int l_set_title(lua::State* L) { + auto title = lua::require_string(L, 1); + engine->getWindow().setTitle(title); + return 0; +} + const luaL_Reg applib[] = { {"start_debug_instance", lua::wrap}, {"focus", lua::wrap}, @@ -103,6 +109,7 @@ const luaL_Reg applib[] = { {"get_content_sources", lua::wrap}, {"set_content_sources", lua::wrap}, {"reset_content_sources", lua::wrap}, + {"set_title", lua::wrap}, // for other functions see libcore.cpp and stdlib.lua {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index e4d5474c7..a3e0f5687 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -300,12 +300,6 @@ static int l_capture_output(lua::State* L) { return 1; } -static int l_set_title(lua::State* L) { - auto title = lua::require_string(L, 1); - engine->getWindow().setTitle(title); - return 0; -} - const luaL_Reg corelib[] = { {"blank", lua::wrap}, {"get_version", lua::wrap}, @@ -327,6 +321,5 @@ const luaL_Reg corelib[] = { {"open_url", lua::wrap}, {"quit", lua::wrap}, {"capture_output", lua::wrap}, - {"set_title", lua::wrap}, {nullptr, nullptr} }; From 1644751b1a32a3ecc2b4604e4a5a1cb00c7692bd Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 00:39:02 +0300 Subject: [PATCH 17/40] fix gui.show_input_dialog key:escape reaction --- res/modules/internal/gui_util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index f9b29c193..894ca1bcc 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -161,7 +161,7 @@ function gui.show_input_dialog(text, actual_callback, validator, confirm_text) callback=callback, validator=validator or function() return #text > 0 end }) - input.add_callback("key:escape", callback, gui.root[id]) + input.add_callback("key:escape", function() gui.root[id]:destruct() end, gui.root[id]) input.add_callback("key:enter", callback, gui.root[id]) gui.root[id.."_input"].focused = true end From 37350a10bef43a462dda39a18fc56334306aadc2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 01:36:41 +0300 Subject: [PATCH 18/40] remove pages loader from GUI class --- src/engine/Engine.cpp | 2 +- src/graphics/ui/GUI.cpp | 9 --------- src/graphics/ui/GUI.hpp | 8 -------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 5223a6c38..5a63991bb 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -178,7 +178,7 @@ void Engine::initialize(CoreParameters coreParameters) { scripting::initialize(this); if (!isHeadless()) { - gui->setPageLoader(scripting::create_page_loader()); + gui->getMenu()->setPageLoader(scripting::create_page_loader()); } keepAlive(settings.ui.language.observe([this](auto lang) { langs::setup(lang, paths->resPaths.collectRoots()); diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index dc6dd6ac5..e69c20e3b 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -67,15 +67,6 @@ GUI::GUI(Engine& engine) GUI::~GUI() = default; -void GUI::setPageLoader(PageLoaderFunc pageLoader) { - this->pagesLoader = std::move(pageLoader); - menu->setPageLoader(this->pagesLoader); -} - -PageLoaderFunc GUI::getPagesLoader() { - return pagesLoader; -} - std::shared_ptr GUI::getMenu() { return menu; } diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index 5d6140c27..a27872d76 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -62,8 +62,6 @@ namespace gui { class Container; class Menu; - using PageLoaderFunc = std::function(const std::string&)>; - /// @brief The main UI controller class GUI { Engine& engine; @@ -83,8 +81,6 @@ namespace gui { std::queue postRunnables; std::vector> mouseOver; - PageLoaderFunc pagesLoader; - float tooltipTimer = 0.0f; float doubleClickTimer = 0.0f; float doubleClickDelay = 0.5f; @@ -101,10 +97,6 @@ namespace gui { GUI(Engine& engine); ~GUI(); - void setPageLoader(PageLoaderFunc pageLoader); - - PageLoaderFunc getPagesLoader(); - /// @brief Get the main menu (Menu) node std::shared_ptr getMenu(); From d6b6c6903ae5bce82e84deba41ee2557a1ccc863 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 01:37:43 +0300 Subject: [PATCH 19/40] update Element.new --- res/modules/internal/gui_util.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index 894ca1bcc..6d24a93a9 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -52,19 +52,21 @@ end -- class designed for simple UI-nodes access via properties syntax local Element = {} +local __gui_getattr = gui.getattr +local __gui_setattr = gui.setattr function Element.new(docname, name) return setmetatable({docname=docname, name=name}, { __index=function(self, k) - return gui.getattr(self.docname, self.name, k) + return __gui_getattr(self.docname, self.name, k) end, __newindex=function(self, k, v) - gui.setattr(self.docname, self.name, k, v) + __gui_setattr(self.docname, self.name, k, v) end, __ipairs=function(self) local i = 0 return function() i = i + 1 - local elem = gui.getattr(self.docname, self.name, i) + local elem = __gui_getattr(self.docname, self.name, i) if elem == nil then return end @@ -102,8 +104,8 @@ function RadioGroup:set(key) end function RadioGroup:__call(elements, onset, default) local group = setmetatable({ - elements=elements, - callback=onset, + elements=elements, + callback=onset, current=nil }, {__index=self}) group:set(default) From e794aa864d2ddeb3e101abfe7dae41ee1c0aaca5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 01:54:47 +0300 Subject: [PATCH 20/40] add gui.load_document namespace extension table argument & pass app library from standard pages loader --- res/modules/internal/gui_util.lua | 2 +- src/logic/scripting/lua/libs/libgui.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index 6d24a93a9..3309e94a0 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -34,7 +34,7 @@ function gui_util.load_page(query) local filename = file.find(string.format("layouts/pages/%s.xml", name)) if filename then name = file.prefix(filename)..":pages/"..name - gui.load_document(filename, name, args) + gui.load_document(filename, name, args, { app = __vc_app }) return name end end diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 4d1506e23..cb98de7d7 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -1111,6 +1111,16 @@ static int l_gui_load_document(lua::State* L) { ); auto document = documentPtr.get(); engine->getAssets()->store(std::move(documentPtr), alias); + + // namespace extension + if (lua::istable(L, 4)) { + if (lua::get_from(L, "table", "merge")) { + lua::pushenv(L, *document->getEnvironment()); + lua::pushvalue(L, 4); + lua::call(L, 2, 0); + lua::pop(L); + } + } scripting::on_ui_open(document, {args}); return 0; } From 13d13e89cf3075381f6dfd5cd2cf432301113531 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 02:03:33 +0300 Subject: [PATCH 21/40] move open_folder, open_url from core to app --- res/layouts/pages/content.xml | 4 ++-- res/layouts/pages/content_menu.xml | 2 +- res/layouts/pages/settings.xml | 2 +- res/layouts/pages/worlds.xml | 2 +- src/logic/scripting/lua/libs/libapp.cpp | 30 ++++++++++++++++++++++++ src/logic/scripting/lua/libs/libcore.cpp | 30 ------------------------ 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/res/layouts/pages/content.xml b/res/layouts/pages/content.xml index dfa7b6546..63474effd 100644 --- a/res/layouts/pages/content.xml +++ b/res/layouts/pages/content.xml @@ -15,7 +15,7 @@ - @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/res/layouts/pages/content_menu.xml b/res/layouts/pages/content_menu.xml index 85dd54d0d..6718a3028 100644 --- a/res/layouts/pages/content_menu.xml +++ b/res/layouts/pages/content_menu.xml @@ -9,7 +9,7 @@ - diff --git a/res/layouts/pages/settings.xml b/res/layouts/pages/settings.xml index 00437e68b..68a71aa04 100644 --- a/res/layouts/pages/settings.xml +++ b/res/layouts/pages/settings.xml @@ -10,7 +10,7 @@ - + diff --git a/res/layouts/pages/worlds.xml b/res/layouts/pages/worlds.xml index a8a536a21..34fe3400c 100644 --- a/res/layouts/pages/worlds.xml +++ b/res/layouts/pages/worlds.xml @@ -3,6 +3,6 @@ - + diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 6d975af31..1f0a45610 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -9,7 +9,12 @@ #include "devtools/Project.hpp" #include "network/Network.hpp" #include "util/platform.hpp" +#include "util/stringutil.hpp" #include "window/Window.hpp" +#include "frontend/locale.hpp" +#include "graphics/ui/GUI.hpp" +#include "graphics/ui/gui_util.hpp" +#include "graphics/ui/elements/Menu.hpp" using namespace scripting; @@ -102,6 +107,29 @@ static int l_set_title(lua::State* L) { return 0; } +static int l_open_folder(lua::State* L) { + platform::open_folder(io::resolve(lua::require_string(L, 1))); + return 0; +} + +static int l_open_url(lua::State* L) { + auto url = lua::require_string(L, 1); + + std::wstring msg = langs::get(L"Are you sure you want to open the link:") + + L"\n" + util::str2wstr_utf8(url) + + std::wstring(L"?"); + + auto menu = engine->getGUI().getMenu(); + + guiutil::confirm(*engine, msg, [url, menu]() { + platform::open_url(url); + if (!menu->back()) { + menu->reset(); + } + }); + return 0; +} + const luaL_Reg applib[] = { {"start_debug_instance", lua::wrap}, {"focus", lua::wrap}, @@ -110,6 +138,8 @@ const luaL_Reg applib[] = { {"set_content_sources", lua::wrap}, {"reset_content_sources", lua::wrap}, {"set_title", lua::wrap}, + {"open_folder", lua::wrap}, + {"open_url", lua::wrap}, // for other functions see libcore.cpp and stdlib.lua {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index a3e0f5687..922298a58 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -20,11 +20,6 @@ #include "util/platform.hpp" #include "world/Level.hpp" #include "world/generator/WorldGenerator.hpp" -#include "util/platform.hpp" -#include "frontend/locale.hpp" -#include "graphics/ui/gui_util.hpp" -#include "graphics/ui/GUI.hpp" -#include "graphics/ui/elements/Menu.hpp" #include "window/Window.hpp" using namespace scripting; @@ -240,29 +235,6 @@ static int l_get_setting_info(lua::State* L) { throw std::runtime_error("unsupported setting type"); } -static int l_open_folder(lua::State* L) { - platform::open_folder(io::resolve(lua::require_string(L, 1))); - return 0; -} - -static int l_open_url(lua::State* L) { - auto url = lua::require_string(L, 1); - - std::wstring msg = langs::get(L"Are you sure you want to open the link:") + - L"\n" + util::str2wstr_utf8(url) + - std::wstring(L"?"); - - auto menu = engine->getGUI().getMenu(); - - guiutil::confirm(*engine, msg, [url, menu]() { - platform::open_url(url); - if (!menu->back()) { - menu->reset(); - } - }); - return 0; -} - /// @brief Quit the game static int l_quit(lua::State*) { engine->quit(); @@ -317,8 +289,6 @@ const luaL_Reg corelib[] = { {"set_setting", lua::wrap}, {"str_setting", lua::wrap}, {"get_setting_info", lua::wrap}, - {"open_folder", lua::wrap}, - {"open_url", lua::wrap}, {"quit", lua::wrap}, {"capture_output", lua::wrap}, {nullptr, nullptr} From 52600621814592357058fd50894e58d83b6d2491 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 02:10:08 +0300 Subject: [PATCH 22/40] remove core.get_version --- res/scripts/stdlib.lua | 3 ++- src/logic/scripting/lua/libs/libapp.cpp | 7 +++++++ src/logic/scripting/lua/libs/libcore.cpp | 7 ------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 155d95c0c..2ba6f73b2 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -51,7 +51,6 @@ local function complete_app_lib(app) app.get_setting = core.get_setting app.set_setting = core.set_setting app.tick = __app_tick - app.get_version = core.get_version app.get_setting_info = core.get_setting_info local function call_in_app_script_co(func, ...) @@ -585,3 +584,5 @@ require "core:internal/deprecated" ffi = nil __vc_lock_internal_modules() + +debug.print(core) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 1f0a45610..4cc2c302d 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -18,6 +18,12 @@ using namespace scripting; +static int l_get_version(lua::State* L) { + return lua::pushvec_stack( + L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR) + ); +} + static int l_start_debug_instance(lua::State* L) { if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) { throw std::runtime_error("project has no debugging permission"); @@ -131,6 +137,7 @@ static int l_open_url(lua::State* L) { } const luaL_Reg applib[] = { + {"get_version", lua::wrap}, {"start_debug_instance", lua::wrap}, {"focus", lua::wrap}, {"create_memory_device", lua::wrap}, diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 922298a58..6f3056ea4 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -24,12 +24,6 @@ using namespace scripting; -static int l_get_version(lua::State* L) { - return lua::pushvec_stack( - L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR) - ); -} - static int l_load_content(lua::State* L) { content_control->loadContent(); return 0; @@ -274,7 +268,6 @@ static int l_capture_output(lua::State* L) { const luaL_Reg corelib[] = { {"blank", lua::wrap}, - {"get_version", lua::wrap}, {"load_content", lua::wrap}, {"reset_content", lua::wrap}, {"is_content_loaded", lua::wrap}, From a6301c88373b2ab88bb6b887e2ed185eba79234c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 02:39:29 +0300 Subject: [PATCH 23/40] remove settings-related functions from core --- res/layouts/pages/settings.xml.lua | 2 +- res/layouts/pages/settings_audio.xml.lua | 12 ++-- res/layouts/pages/settings_controls.xml.lua | 6 +- res/layouts/pages/settings_display.xml.lua | 22 +++--- res/layouts/pages/settings_graphics.xml.lua | 16 ++--- res/layouts/pages/settings_reset.xml.lua | 2 +- res/layouts/pages/worlds.xml.lua | 2 +- res/layouts/templates/language.xml | 2 +- res/layouts/templates/track_setting.xml | 2 +- res/scripts/hud.lua | 2 +- res/scripts/stdlib.lua | 17 ++--- src/logic/scripting/lua/libs/libapp.cpp | 72 +++++++++++++++++++ src/logic/scripting/lua/libs/libcore.cpp | 80 ++------------------- src/logic/scripting/lua/lua_engine.cpp | 2 + 14 files changed, 119 insertions(+), 120 deletions(-) diff --git a/res/layouts/pages/settings.xml.lua b/res/layouts/pages/settings.xml.lua index 8b41491a5..453583518 100644 --- a/res/layouts/pages/settings.xml.lua +++ b/res/layouts/pages/settings.xml.lua @@ -1,7 +1,7 @@ function on_open() document.langs_btn.text = string.format( "%s: %s", gui.str("Language", "settings"), - gui.get_locales_info()[core.get_setting("ui.language")].name + gui.get_locales_info()[app.get_setting("ui.language")].name ) sections = RadioGroup({ audio=document.s_aud, diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index f8a23baa9..c9b4be4b6 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -1,25 +1,25 @@ function create_setting(id, name, step, postfix) - local info = core.get_setting_info(id) + local info = app.get_setting_info(id) postfix = postfix or "" document.root:add(gui.template("track_setting", { id=id, name=gui.str(name, "settings"), - value=core.get_setting(id), + value=app.get_setting(id), min=info.min, max=info.max, step=step, postfix=postfix })) - update_setting(core.get_setting(id), id, name, postfix) + update_setting(app.get_setting(id), id, name, postfix) end function update_setting(x, id, name, postfix) - core.set_setting(id, x) + app.set_setting(id, x) -- updating label document[id..".L"].text = string.format( "%s: %s%s", gui.str(name, "settings"), - core.str_setting(id), + app.str_setting(id), postfix ) end @@ -54,7 +54,7 @@ function on_open() create_setting("audio.volume-music", "Music", 0.01) document.root:add("") document.root:add("".. + "", core.get_setting("display.window-mode")) + "", app.get_setting("display.window-mode")) ) create_checkbox("camera.shaking", "Camera Shaking") diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index d61057799..3b7f7b13a 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -1,12 +1,12 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease) - local info = core.get_setting_info(id) + local info = app.get_setting_info(id) postfix = postfix or "" tooltip = tooltip or "" changeonrelease = changeonrelease or "" document.root:add(gui.template("track_setting", { id=id, name=gui.str(name, "settings"), - value=core.get_setting(id), + value=app.get_setting(id), min=info.min, max=info.max, step=step, @@ -14,15 +14,15 @@ function create_setting(id, name, step, postfix, tooltip, changeonrelease) tooltip=tooltip, changeonrelease=changeonrelease })) - update_setting(core.get_setting(id), id, name, postfix) + update_setting(app.get_setting(id), id, name, postfix) end function update_setting(x, id, name, postfix) -- updating label document[id..".L"].text = string.format( - "%s: %s%s", - gui.str(name, "settings"), - core.str_setting(id), + "%s: %s%s", + gui.str(name, "settings"), + app.str_setting(id), postfix ) end @@ -30,8 +30,8 @@ end function create_checkbox(id, name, tooltip) tooltip = tooltip or '' document.root:add(string.format( - "%s", - id, core.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") + "%s", + id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings") )) end diff --git a/res/layouts/pages/settings_reset.xml.lua b/res/layouts/pages/settings_reset.xml.lua index a8ca2a9fc..59583eefa 100644 --- a/res/layouts/pages/settings_reset.xml.lua +++ b/res/layouts/pages/settings_reset.xml.lua @@ -11,7 +11,7 @@ function reset(category) end function reset_setting(name) - core.set_setting(name, core.get_setting_info(name).def) + app.set_setting(name, app.get_setting_info(name).def) end function reset_audio() diff --git a/res/layouts/pages/worlds.xml.lua b/res/layouts/pages/worlds.xml.lua index 1cbcd53db..748bdfb0b 100644 --- a/res/layouts/pages/worlds.xml.lua +++ b/res/layouts/pages/worlds.xml.lua @@ -1,7 +1,7 @@ function on_open() local worlds = world.get_list() for _, info in ipairs(worlds) do - local major, minor = core.get_version() + local major, minor = app.get_version() if info.version[1] > major or info.version[2] > minor then info.versionColor = "#A02010" else diff --git a/res/layouts/templates/language.xml b/res/layouts/templates/language.xml index fea34b313..692744c96 100644 --- a/res/layouts/templates/language.xml +++ b/res/layouts/templates/language.xml @@ -1,4 +1,4 @@ diff --git a/res/layouts/templates/track_setting.xml b/res/layouts/templates/track_setting.xml index 519ba334b..f01b8f842 100644 --- a/res/layouts/templates/track_setting.xml +++ b/res/layouts/templates/track_setting.xml @@ -2,6 +2,6 @@ diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 59d8de36a..322f03db5 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -29,7 +29,7 @@ local function configure_SSAO() end events.on("core:setting.graphics.ssao.set", update_ssao_quality) - update_ssao_quality(core.get_setting("graphics.ssao")) + update_ssao_quality(__vc_app.get_setting("graphics.ssao")) end local function update_hand() diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 2ba6f73b2..d7ee98b97 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -1,4 +1,4 @@ -local enable_experimental = core.get_setting("debug.enable-experimental") +local enable_experimental = __vc_app.get_setting("debug.enable-experimental") ------------------------------------------------ ------ Extended kit of standard functions ------ @@ -24,12 +24,6 @@ function tb_frame_tostring(frame) return s end -local core_set_setting = core.set_setting -function core.set_setting(name, value, ...) - core_set_setting(name, value, ...) - events.emit("core:setting."..name..".set", value) -end - local __vc__app_script_coroutine local function complete_app_lib(app) @@ -37,6 +31,7 @@ local function complete_app_lib(app) local __core_reset_content = core.reset_content local __core_reconfig_packs = core.reconfig_packs local __app_tick = coroutine.yield + local __app_set_setting = app.set_setting core.load_content = nil core.reset_content = nil @@ -48,10 +43,12 @@ local function complete_app_lib(app) app.close_world = core.close_world app.reopen_world = core.reopen_world app.delete_world = core.delete_world - app.get_setting = core.get_setting - app.set_setting = core.set_setting + + app.set_setting = function(name, value, ...) + __app_set_setting(name, value, ...) + events.emit("core:setting."..name..".set", value) + end app.tick = __app_tick - app.get_setting_info = core.get_setting_info local function call_in_app_script_co(func, ...) if coroutine.running() ~= __vc__app_script_coroutine then diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 4cc2c302d..b330e8a90 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -1,6 +1,7 @@ #include "api_lua.hpp" #include "io/io.hpp" +#include "io/settings_io.hpp" #include "io/devices/MemoryDevice.hpp" #include "logic/scripting/scripting.hpp" #include "content/ContentControl.hpp" @@ -113,6 +114,73 @@ static int l_set_title(lua::State* L) { return 0; } +/// @brief Get a setting value +/// @param name The name of the setting +/// @return The value of the setting +static int l_get_setting(lua::State* L) { + auto name = lua::require_string(L, 1); + const auto value = engine->getSettingsHandler().getValue(name); + return lua::pushvalue(L, value); +} + +/// @brief Set a setting value +/// @param name The name of the setting +/// @param value The new value for the setting +static int l_set_setting(lua::State* L) { + auto name = lua::require_string(L, 1); + const auto value = lua::tovalue(L, 2); + engine->getSettingsHandler().setValue(name, value); + return 0; +} + +/// @brief Convert a setting value to a string +/// @param name The name of the setting +/// @return The string representation of the setting value +static int l_str_setting(lua::State* L) { + auto name = lua::require_string(L, 1); + const auto string = engine->getSettingsHandler().toString(name); + return lua::pushstring(L, string); +} + +/// @brief Get information about a setting +/// @param name The name of the setting +/// @return A table with information about the setting +static int l_get_setting_info(lua::State* L) { + auto name = lua::require_string(L, 1); + auto setting = engine->getSettingsHandler().getSetting(name); + lua::createtable(L, 0, 1); + if (auto number = dynamic_cast(setting)) { + lua::pushnumber(L, number->getMin()); + lua::setfield(L, "min"); + lua::pushnumber(L, number->getMax()); + lua::setfield(L, "max"); + lua::pushnumber(L, number->getDefault()); + lua::setfield(L, "def"); + return 1; + } + if (auto integer = dynamic_cast(setting)) { + lua::pushinteger(L, integer->getMin()); + lua::setfield(L, "min"); + lua::pushinteger(L, integer->getMax()); + lua::setfield(L, "max"); + lua::pushinteger(L, integer->getDefault()); + lua::setfield(L, "def"); + return 1; + } + if (auto boolean = dynamic_cast(setting)) { + lua::pushboolean(L, boolean->getDefault()); + lua::setfield(L, "def"); + return 1; + } + if (auto string = dynamic_cast(setting)) { + lua::pushstring(L, string->getDefault()); + lua::setfield(L, "def"); + return 1; + } + lua::pop(L); + throw std::runtime_error("unsupported setting type"); +} + static int l_open_folder(lua::State* L) { platform::open_folder(io::resolve(lua::require_string(L, 1))); return 0; @@ -147,6 +215,10 @@ const luaL_Reg applib[] = { {"set_title", lua::wrap}, {"open_folder", lua::wrap}, {"open_url", lua::wrap}, + {"get_setting", lua::wrap}, + {"set_setting", lua::wrap}, + {"str_setting", lua::wrap}, + {"get_setting_info", lua::wrap}, // for other functions see libcore.cpp and stdlib.lua {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 6f3056ea4..be7ee614e 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -1,7 +1,3 @@ -#include -#include -#include - #include "api_lua.hpp" #include "constants.hpp" #include "assets/Assets.hpp" @@ -10,7 +6,6 @@ #include "engine/Engine.hpp" #include "engine/EnginePaths.hpp" #include "io/io.hpp" -#include "io/settings_io.hpp" #include "frontend/menu.hpp" #include "frontend/screens/MenuScreen.hpp" #include "graphics/core/Texture.hpp" @@ -22,6 +17,10 @@ #include "world/generator/WorldGenerator.hpp" #include "window/Window.hpp" +#include +#include +#include + using namespace scripting; static int l_load_content(lua::State* L) { @@ -162,73 +161,6 @@ static int l_reconfig_packs(lua::State* L) { return 0; } -/// @brief Get a setting value -/// @param name The name of the setting -/// @return The value of the setting -static int l_get_setting(lua::State* L) { - auto name = lua::require_string(L, 1); - const auto value = engine->getSettingsHandler().getValue(name); - return lua::pushvalue(L, value); -} - -/// @brief Set a setting value -/// @param name The name of the setting -/// @param value The new value for the setting -static int l_set_setting(lua::State* L) { - auto name = lua::require_string(L, 1); - const auto value = lua::tovalue(L, 2); - engine->getSettingsHandler().setValue(name, value); - return 0; -} - -/// @brief Convert a setting value to a string -/// @param name The name of the setting -/// @return The string representation of the setting value -static int l_str_setting(lua::State* L) { - auto name = lua::require_string(L, 1); - const auto string = engine->getSettingsHandler().toString(name); - return lua::pushstring(L, string); -} - -/// @brief Get information about a setting -/// @param name The name of the setting -/// @return A table with information about the setting -static int l_get_setting_info(lua::State* L) { - auto name = lua::require_string(L, 1); - auto setting = engine->getSettingsHandler().getSetting(name); - lua::createtable(L, 0, 1); - if (auto number = dynamic_cast(setting)) { - lua::pushnumber(L, number->getMin()); - lua::setfield(L, "min"); - lua::pushnumber(L, number->getMax()); - lua::setfield(L, "max"); - lua::pushnumber(L, number->getDefault()); - lua::setfield(L, "def"); - return 1; - } - if (auto integer = dynamic_cast(setting)) { - lua::pushinteger(L, integer->getMin()); - lua::setfield(L, "min"); - lua::pushinteger(L, integer->getMax()); - lua::setfield(L, "max"); - lua::pushinteger(L, integer->getDefault()); - lua::setfield(L, "def"); - return 1; - } - if (auto boolean = dynamic_cast(setting)) { - lua::pushboolean(L, boolean->getDefault()); - lua::setfield(L, "def"); - return 1; - } - if (auto string = dynamic_cast(setting)) { - lua::pushstring(L, string->getDefault()); - lua::setfield(L, "def"); - return 1; - } - lua::pop(L); - throw std::runtime_error("unsupported setting type"); -} - /// @brief Quit the game static int l_quit(lua::State*) { engine->quit(); @@ -278,10 +210,6 @@ const luaL_Reg corelib[] = { {"close_world", lua::wrap}, {"delete_world", lua::wrap}, {"reconfig_packs", lua::wrap}, - {"get_setting", lua::wrap}, - {"set_setting", lua::wrap}, - {"str_setting", lua::wrap}, - {"get_setting_info", lua::wrap}, {"quit", lua::wrap}, {"capture_output", lua::wrap}, {nullptr, nullptr} diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index eef391b54..2a9eb59cb 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -66,6 +66,8 @@ static void create_libs(State* L, StateType stateType) { if (stateType == StateType::SCRIPT) { openlib(L, "app", applib); + lua::getglobal(L, "app"); + lua::setglobal(L, "__vc_app"); } else if (stateType == StateType::BASE) { openlib(L, "__vc_app", applib); } From 24f867dbacd4db39ffb296d4821e0df08c2b54fe Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 02:43:22 +0300 Subject: [PATCH 24/40] remove core.is_content_loaded --- res/scripts/stdlib.lua | 4 ---- src/logic/scripting/lua/libs/libapp.cpp | 5 +++++ src/logic/scripting/lua/libs/libcore.cpp | 5 ----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index d7ee98b97..83e75322f 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -68,8 +68,6 @@ local function complete_app_lib(app) call_in_app_script_co(__core_reset_content, ...) end - app.is_content_loaded = core.is_content_loaded - function app.config_packs(packs_list) -- Check if packs are valid and add dependencies to the configuration packs_list = pack.assemble(packs_list) @@ -581,5 +579,3 @@ require "core:internal/deprecated" ffi = nil __vc_lock_internal_modules() - -debug.print(core) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index b330e8a90..4e8c6d9c4 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -25,6 +25,10 @@ static int l_get_version(lua::State* L) { ); } +static int l_is_content_loaded(lua::State* L) { + return lua::pushboolean(L, content != nullptr); +} + static int l_start_debug_instance(lua::State* L) { if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) { throw std::runtime_error("project has no debugging permission"); @@ -206,6 +210,7 @@ static int l_open_url(lua::State* L) { const luaL_Reg applib[] = { {"get_version", lua::wrap}, + {"is_content_loaded", lua::wrap}, {"start_debug_instance", lua::wrap}, {"focus", lua::wrap}, {"create_memory_device", lua::wrap}, diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index be7ee614e..bef6f52a5 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -45,10 +45,6 @@ static int l_reset_content(lua::State* L) { return 0; } -static int l_is_content_loaded(lua::State* L) { - return lua::pushboolean(L, content != nullptr); -} - /// @brief Creating new world /// @param name Name world /// @param seed Seed world @@ -202,7 +198,6 @@ const luaL_Reg corelib[] = { {"blank", lua::wrap}, {"load_content", lua::wrap}, {"reset_content", lua::wrap}, - {"is_content_loaded", lua::wrap}, {"new_world", lua::wrap}, {"open_world", lua::wrap}, {"reopen_world", lua::wrap}, From 9af675611f5a8c9fc8bfdebb69bc03ce9d35969f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 19:37:57 +0300 Subject: [PATCH 25/40] remove core.quit add 'silent' argument to app.quit --- res/layouts/pages/main.xml | 2 +- res/scripts/stdlib.lua | 21 ++++++++++++--------- src/logic/scripting/lua/libs/libapp.cpp | 7 +++++++ src/logic/scripting/lua/libs/libcore.cpp | 7 ------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/res/layouts/pages/main.xml b/res/layouts/pages/main.xml index b1fab2c9e..b05ac7158 100644 --- a/res/layouts/pages/main.xml +++ b/res/layouts/pages/main.xml @@ -3,5 +3,5 @@ - + diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 83e75322f..06ba4f6e8 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -32,6 +32,7 @@ local function complete_app_lib(app) local __core_reconfig_packs = core.reconfig_packs local __app_tick = coroutine.yield local __app_set_setting = app.set_setting + local __app_quit = app.quit core.load_content = nil core.reset_content = nil @@ -51,7 +52,8 @@ local function complete_app_lib(app) app.tick = __app_tick local function call_in_app_script_co(func, ...) - if coroutine.running() ~= __vc__app_script_coroutine then + local running = coroutine.running() + if not running or running ~= __vc__app_script_coroutine then error("content must be reload in application script coroutine") end func(...) @@ -88,15 +90,16 @@ local function complete_app_lib(app) app.reconfig_packs(toadd, toremove) end - function app.quit() - local tb = debug.get_traceback(1) - local s = "app.quit() traceback:" - for i, frame in ipairs(tb) do - s = s .. "\n\t"..tb_frame_tostring(frame) + function app.quit(silent) + if not silent then + local tb = debug.get_traceback(1) + local s = "app.quit() traceback:" + for i, frame in ipairs(tb) do + s = s .. "\n\t"..tb_frame_tostring(frame) + end + debug.log(s) end - debug.log(s) - core.quit() - coroutine.yield() + __app_quit() end function app.sleep_until(predicate, max_ticks, max_time) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 4e8c6d9c4..b949adf72 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -208,6 +208,12 @@ static int l_open_url(lua::State* L) { return 0; } +/// @brief Quit the game +static int l_quit(lua::State*) { + engine->quit(); + return 0; +} + const luaL_Reg applib[] = { {"get_version", lua::wrap}, {"is_content_loaded", lua::wrap}, @@ -224,6 +230,7 @@ const luaL_Reg applib[] = { {"set_setting", lua::wrap}, {"str_setting", lua::wrap}, {"get_setting_info", lua::wrap}, + {"quit", lua::wrap}, // for other functions see libcore.cpp and stdlib.lua {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index bef6f52a5..71a38d192 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -157,12 +157,6 @@ static int l_reconfig_packs(lua::State* L) { return 0; } -/// @brief Quit the game -static int l_quit(lua::State*) { - engine->quit(); - return 0; -} - static int l_blank(lua::State*) { return 0; } @@ -205,7 +199,6 @@ const luaL_Reg corelib[] = { {"close_world", lua::wrap}, {"delete_world", lua::wrap}, {"reconfig_packs", lua::wrap}, - {"quit", lua::wrap}, {"capture_output", lua::wrap}, {nullptr, nullptr} }; From 126a8485a1250cb8bf7412a8e1b9032cd7fa326d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 20:03:34 +0300 Subject: [PATCH 26/40] remove content-related functions from core library --- res/scripts/stdlib.lua | 14 +++--- src/logic/scripting/lua/libs/libapp.cpp | 61 ++++++++++++++++++++++++ src/logic/scripting/lua/libs/libcore.cpp | 60 ----------------------- 3 files changed, 67 insertions(+), 68 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 06ba4f6e8..cc0131fa7 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -27,14 +27,12 @@ end local __vc__app_script_coroutine local function complete_app_lib(app) - local __core_load_content = core.load_content - local __core_reset_content = core.reset_content - local __core_reconfig_packs = core.reconfig_packs + local __app_load_content = app.load_content + local __app_reset_content = app.reset_content + local __app_reconfig_packs = app.reconfig_packs local __app_tick = coroutine.yield local __app_set_setting = app.set_setting local __app_quit = app.quit - core.load_content = nil - core.reset_content = nil app.sleep = sleep app.script = __VC_SCRIPT_NAME @@ -61,13 +59,13 @@ local function complete_app_lib(app) end app.reconfig_packs = function(...) - call_in_app_script_co(__core_reconfig_packs, ...) + call_in_app_script_co(__app_reconfig_packs, ...) end app.load_content = function(...) - call_in_app_script_co(__core_load_content, ...) + call_in_app_script_co(__app_load_content, ...) end app.reset_content = function(...) - call_in_app_script_co(__core_reset_content, ...) + call_in_app_script_co(__app_reset_content, ...) end function app.config_packs(packs_list) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index b949adf72..9412baa36 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -4,6 +4,7 @@ #include "io/settings_io.hpp" #include "io/devices/MemoryDevice.hpp" #include "logic/scripting/scripting.hpp" +#include "logic/EngineController.hpp" #include "content/ContentControl.hpp" #include "engine/Engine.hpp" #include "engine/EnginePaths.hpp" @@ -29,6 +30,63 @@ static int l_is_content_loaded(lua::State* L) { return lua::pushboolean(L, content != nullptr); } +static int l_load_content(lua::State* L) { + content_control->loadContent(); + return 0; +} + +static int l_reset_content(lua::State* L) { + if (level != nullptr) { + throw std::runtime_error("world must be closed before"); + } + std::vector nonResetPacks; + if (lua::istable(L, 1)) { + int len = lua::objlen(L, 1); + for (int i = 0; i < len; i++) { + lua::rawgeti(L, i + 1, 1); + nonResetPacks.emplace_back(lua::require_lstring(L, -1)); + lua::pop(L); + } + } + content_control->resetContent(std::move(nonResetPacks)); + return 0; +} + +/// @brief Reconfigure packs +/// @param addPacks An array of packs to add +/// @param remPacks An array of packs to remove +static int l_reconfig_packs(lua::State* L) { + if (!lua::istable(L, 1)) { + throw std::runtime_error("strings array expected as the first argument"); + } + if (!lua::istable(L, 2)) { + throw std::runtime_error("strings array expected as the second argument"); + } + std::vector addPacks; + int addLen = lua::objlen(L, 1); + for (int i = 0; i < addLen; i++) { + lua::rawgeti(L, i + 1, 1); + addPacks.emplace_back(lua::require_lstring(L, -1)); + lua::pop(L); + } + std::vector remPacks; + int remLen = lua::objlen(L, 2); + for (int i = 0; i < remLen; i++) { + lua::rawgeti(L, i + 1, 2); + remPacks.emplace_back(lua::require_lstring(L, -1)); + lua::pop(L); + } + auto engineController = engine->getController(); + try { + engineController->reconfigPacks(controller, addPacks, remPacks); + } catch (const contentpack_error& err) { + throw std::runtime_error( + std::string(err.what()) + " [" + err.getPackId() + " ]" + ); + } + return 0; +} + static int l_start_debug_instance(lua::State* L) { if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) { throw std::runtime_error("project has no debugging permission"); @@ -217,6 +275,9 @@ static int l_quit(lua::State*) { const luaL_Reg applib[] = { {"get_version", lua::wrap}, {"is_content_loaded", lua::wrap}, + {"load_content", lua::wrap}, + {"reset_content", lua::wrap}, + {"reconfig_packs", lua::wrap}, {"start_debug_instance", lua::wrap}, {"focus", lua::wrap}, {"create_memory_device", lua::wrap}, diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 71a38d192..b9648ad35 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -23,28 +23,6 @@ using namespace scripting; -static int l_load_content(lua::State* L) { - content_control->loadContent(); - return 0; -} - -static int l_reset_content(lua::State* L) { - if (level != nullptr) { - throw std::runtime_error("world must be closed before"); - } - std::vector nonResetPacks; - if (lua::istable(L, 1)) { - int len = lua::objlen(L, 1); - for (int i = 0; i < len; i++) { - lua::rawgeti(L, i + 1, 1); - nonResetPacks.emplace_back(lua::require_lstring(L, -1)); - lua::pop(L); - } - } - content_control->resetContent(std::move(nonResetPacks)); - return 0; -} - /// @brief Creating new world /// @param name Name world /// @param seed Seed world @@ -122,41 +100,6 @@ static int l_delete_world(lua::State* L) { return 0; } -/// @brief Reconfigure packs -/// @param addPacks An array of packs to add -/// @param remPacks An array of packs to remove -static int l_reconfig_packs(lua::State* L) { - if (!lua::istable(L, 1)) { - throw std::runtime_error("strings array expected as the first argument"); - } - if (!lua::istable(L, 2)) { - throw std::runtime_error("strings array expected as the second argument"); - } - std::vector addPacks; - int addLen = lua::objlen(L, 1); - for (int i = 0; i < addLen; i++) { - lua::rawgeti(L, i + 1, 1); - addPacks.emplace_back(lua::require_lstring(L, -1)); - lua::pop(L); - } - std::vector remPacks; - int remLen = lua::objlen(L, 2); - for (int i = 0; i < remLen; i++) { - lua::rawgeti(L, i + 1, 2); - remPacks.emplace_back(lua::require_lstring(L, -1)); - lua::pop(L); - } - auto engineController = engine->getController(); - try { - engineController->reconfigPacks(controller, addPacks, remPacks); - } catch (const contentpack_error& err) { - throw std::runtime_error( - std::string(err.what()) + " [" + err.getPackId() + " ]" - ); - } - return 0; -} - static int l_blank(lua::State*) { return 0; } @@ -190,15 +133,12 @@ static int l_capture_output(lua::State* L) { const luaL_Reg corelib[] = { {"blank", lua::wrap}, - {"load_content", lua::wrap}, - {"reset_content", lua::wrap}, {"new_world", lua::wrap}, {"open_world", lua::wrap}, {"reopen_world", lua::wrap}, {"save_world", lua::wrap}, {"close_world", lua::wrap}, {"delete_world", lua::wrap}, - {"reconfig_packs", lua::wrap}, {"capture_output", lua::wrap}, {nullptr, nullptr} }; From c57fe211202b564557c180e10e81e4cbe303749c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 20:12:06 +0300 Subject: [PATCH 27/40] move world-related functions from core library --- res/layouts/pages/new_world.xml.lua | 2 +- res/layouts/pages/pause.xml | 2 +- res/layouts/templates/world.xml | 4 +- res/scripts/stdlib.lua | 6 -- src/logic/scripting/lua/libs/libapp.cpp | 106 ++++++++++++++++++++--- src/logic/scripting/lua/libs/libcore.cpp | 102 ---------------------- 6 files changed, 100 insertions(+), 122 deletions(-) diff --git a/res/layouts/pages/new_world.xml.lua b/res/layouts/pages/new_world.xml.lua index 694ca9565..ad95aad0d 100644 --- a/res/layouts/pages/new_world.xml.lua +++ b/res/layouts/pages/new_world.xml.lua @@ -21,7 +21,7 @@ function create_world() local seed = document.seed_box.text local generator = settings.generator session.reset_entry('new_world') - core.new_world(name, seed, generator) + app.new_world(name, seed, generator) end function world_name_placeholder() diff --git a/res/layouts/pages/pause.xml b/res/layouts/pages/pause.xml index 63d827e10..3e4427cae 100644 --- a/res/layouts/pages/pause.xml +++ b/res/layouts/pages/pause.xml @@ -2,7 +2,7 @@ - diff --git a/res/layouts/templates/world.xml b/res/layouts/templates/world.xml index a258604b4..4ae936b6e 100644 --- a/res/layouts/templates/world.xml +++ b/res/layouts/templates/world.xml @@ -2,7 +2,7 @@ size='380,66' color='#0F1E2DB2' hover-color='#162B3399' - onclick='core.open_world("%{name}")' + onclick='app.open_world("%{name}")' > @@ -11,7 +11,7 @@ color='#00000000' hover-color='#FFFFFF2B' padding='0' - onclick='core.delete_world("%{name}")'> + onclick='app.delete_world("%{name}")'> diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index cc0131fa7..4eb6dd17e 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -36,12 +36,6 @@ local function complete_app_lib(app) app.sleep = sleep app.script = __VC_SCRIPT_NAME - app.new_world = core.new_world - app.open_world = core.open_world - app.save_world = core.save_world - app.close_world = core.close_world - app.reopen_world = core.reopen_world - app.delete_world = core.delete_world app.set_setting = function(name, value, ...) __app_set_setting(name, value, ...) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 9412baa36..736fc8624 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -1,22 +1,24 @@ #include "api_lua.hpp" -#include "io/io.hpp" -#include "io/settings_io.hpp" -#include "io/devices/MemoryDevice.hpp" -#include "logic/scripting/scripting.hpp" -#include "logic/EngineController.hpp" #include "content/ContentControl.hpp" +#include "devtools/Project.hpp" #include "engine/Engine.hpp" #include "engine/EnginePaths.hpp" -#include "devtools/Project.hpp" +#include "frontend/locale.hpp" +#include "graphics/ui/elements/Menu.hpp" +#include "graphics/ui/gui_util.hpp" +#include "graphics/ui/GUI.hpp" +#include "io/devices/MemoryDevice.hpp" +#include "io/io.hpp" +#include "io/settings_io.hpp" +#include "logic/EngineController.hpp" +#include "logic/LevelController.hpp" +#include "logic/scripting/scripting.hpp" #include "network/Network.hpp" #include "util/platform.hpp" #include "util/stringutil.hpp" #include "window/Window.hpp" -#include "frontend/locale.hpp" -#include "graphics/ui/GUI.hpp" -#include "graphics/ui/gui_util.hpp" -#include "graphics/ui/elements/Menu.hpp" +#include "world/Level.hpp" using namespace scripting; @@ -266,6 +268,84 @@ static int l_open_url(lua::State* L) { return 0; } +/// @brief Creating new world +/// @param name Name world +/// @param seed Seed world +/// @param generator Type of generation +static int l_new_world(lua::State* L) { + auto name = lua::require_string(L, 1); + auto seed = lua::require_string(L, 2); + auto generator = lua::require_string(L, 3); + int64_t localPlayer = 0; + if (lua::gettop(L) >= 4) { + localPlayer = lua::tointeger(L, 4); + } + if (level != nullptr) { + throw std::runtime_error("world must be closed before"); + } + auto controller = engine->getController(); + controller->setLocalPlayer(localPlayer); + controller->createWorld(name, seed, generator); + return 0; +} + +/// @brief Open world +/// @param name Name world +static int l_open_world(lua::State* L) { + auto name = lua::require_string(L, 1); + if (level != nullptr) { + throw std::runtime_error("world must be closed before"); + } + auto controller = engine->getController(); + controller->setLocalPlayer(0); + controller->openWorld(name, false); + return 0; +} + +/// @brief Reopen world +static int l_reopen_world(lua::State*) { + auto controller = engine->getController(); + if (level == nullptr) { + throw std::runtime_error("no world open"); + } + controller->reopenWorld(level->getWorld()); + return 0; +} + +/// @brief Save world +static int l_save_world(lua::State* L) { + if (controller == nullptr) { + throw std::runtime_error("no world open"); + } + controller->saveWorld(); + return 0; +} + +/// @brief Close world +/// @param flag Save world (bool) +static int l_close_world(lua::State* L) { + if (controller == nullptr) { + throw std::runtime_error("no world open"); + } + controller->processBeforeQuit(); + bool save_world = lua::toboolean(L, 1); + if (save_world) { + controller->saveWorld(); + } + engine->onWorldClosed(); + return 0; +} + +/// @brief Delete world +/// @param name Name world +static int l_delete_world(lua::State* L) { + auto name = lua::require_string(L, 1); + auto controller = engine->getController(); + controller->deleteWorld(name); + return 0; +} + + /// @brief Quit the game static int l_quit(lua::State*) { engine->quit(); @@ -291,6 +371,12 @@ const luaL_Reg applib[] = { {"set_setting", lua::wrap}, {"str_setting", lua::wrap}, {"get_setting_info", lua::wrap}, + {"new_world", lua::wrap}, + {"open_world", lua::wrap}, + {"reopen_world", lua::wrap}, + {"save_world", lua::wrap}, + {"close_world", lua::wrap}, + {"delete_world", lua::wrap}, {"quit", lua::wrap}, // for other functions see libcore.cpp and stdlib.lua {nullptr, nullptr} diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index b9648ad35..324cd5471 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -1,105 +1,9 @@ #include "api_lua.hpp" -#include "constants.hpp" -#include "assets/Assets.hpp" -#include "content/Content.hpp" -#include "content/ContentControl.hpp" -#include "engine/Engine.hpp" -#include "engine/EnginePaths.hpp" -#include "io/io.hpp" -#include "frontend/menu.hpp" -#include "frontend/screens/MenuScreen.hpp" -#include "graphics/core/Texture.hpp" -#include "logic/EngineController.hpp" -#include "logic/LevelController.hpp" -#include "util/listutil.hpp" -#include "util/platform.hpp" -#include "world/Level.hpp" -#include "world/generator/WorldGenerator.hpp" -#include "window/Window.hpp" -#include -#include #include using namespace scripting; -/// @brief Creating new world -/// @param name Name world -/// @param seed Seed world -/// @param generator Type of generation -static int l_new_world(lua::State* L) { - auto name = lua::require_string(L, 1); - auto seed = lua::require_string(L, 2); - auto generator = lua::require_string(L, 3); - int64_t localPlayer = 0; - if (lua::gettop(L) >= 4) { - localPlayer = lua::tointeger(L, 4); - } - if (level != nullptr) { - throw std::runtime_error("world must be closed before"); - } - auto controller = engine->getController(); - controller->setLocalPlayer(localPlayer); - controller->createWorld(name, seed, generator); - return 0; -} - -/// @brief Open world -/// @param name Name world -static int l_open_world(lua::State* L) { - auto name = lua::require_string(L, 1); - if (level != nullptr) { - throw std::runtime_error("world must be closed before"); - } - auto controller = engine->getController(); - controller->setLocalPlayer(0); - controller->openWorld(name, false); - return 0; -} - -/// @brief Reopen world -static int l_reopen_world(lua::State*) { - auto controller = engine->getController(); - if (level == nullptr) { - throw std::runtime_error("no world open"); - } - controller->reopenWorld(level->getWorld()); - return 0; -} - -/// @brief Save world -static int l_save_world(lua::State* L) { - if (controller == nullptr) { - throw std::runtime_error("no world open"); - } - controller->saveWorld(); - return 0; -} - -/// @brief Close world -/// @param flag Save world (bool) -static int l_close_world(lua::State* L) { - if (controller == nullptr) { - throw std::runtime_error("no world open"); - } - controller->processBeforeQuit(); - bool save_world = lua::toboolean(L, 1); - if (save_world) { - controller->saveWorld(); - } - engine->onWorldClosed(); - return 0; -} - -/// @brief Delete world -/// @param name Name world -static int l_delete_world(lua::State* L) { - auto name = lua::require_string(L, 1); - auto controller = engine->getController(); - controller->deleteWorld(name); - return 0; -} - static int l_blank(lua::State*) { return 0; } @@ -133,12 +37,6 @@ static int l_capture_output(lua::State* L) { const luaL_Reg corelib[] = { {"blank", lua::wrap}, - {"new_world", lua::wrap}, - {"open_world", lua::wrap}, - {"reopen_world", lua::wrap}, - {"save_world", lua::wrap}, - {"close_world", lua::wrap}, - {"delete_world", lua::wrap}, {"capture_output", lua::wrap}, {nullptr, nullptr} }; From 7f51377db399c5531c5cfe940dc7713fb0bd8404 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 7 Jan 2026 21:06:22 +0300 Subject: [PATCH 28/40] cleanup a bit --- src/logic/scripting/lua/libs/libapp.cpp | 173 +++++++++++++----------- 1 file changed, 96 insertions(+), 77 deletions(-) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 736fc8624..09ea33ce3 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -22,21 +22,18 @@ using namespace scripting; -static int l_get_version(lua::State* L) { - return lua::pushvec_stack( - L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR) - ); -} - +/// @brief Check if content is loaded static int l_is_content_loaded(lua::State* L) { return lua::pushboolean(L, content != nullptr); } +/// @brief Load content static int l_load_content(lua::State* L) { content_control->loadContent(); return 0; } +/// @brief Reset content, excluding specified packs modules static int l_reset_content(lua::State* L) { if (level != nullptr) { throw std::runtime_error("world must be closed before"); @@ -89,59 +86,7 @@ static int l_reconfig_packs(lua::State* L) { return 0; } -static int l_start_debug_instance(lua::State* L) { - if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) { - throw std::runtime_error("project has no debugging permission"); - } - - int port = lua::tointeger(L, 1); - if (port == 0) { - auto network = engine->getNetwork(); - if (network == nullptr) { - throw std::runtime_error("project has no network permission"); - } - port = network->findFreePort(); - if (port == -1) { - throw std::runtime_error("could not find free port"); - } - } - auto projectPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : ""; - const auto& paths = engine->getPaths(); - - std::vector args { - "--res", paths.getResourcesFolder().u8string(), - "--dir", paths.getUserFilesFolder().u8string(), - "--dbg-server", "tcp:" + std::to_string(port), - }; - if (!projectPath.empty()) { - args.emplace_back("--project"); - args.emplace_back(io::resolve(std::string(projectPath)).string()); - } - - platform::new_engine_instance(std::move(args)); - return lua::pushinteger(L, port); -} - -static int l_focus(lua::State* L) { - engine->getWindow().focus(); - return 0; -} - -static int l_create_memory_device(lua::State* L) { - std::string name = lua::require_string(L, 1); - if (io::get_device(name)) { - throw std::runtime_error( - "entry-point '" + name + "' is already used" - ); - } - if (name.find(':') != std::string::npos) { - throw std::runtime_error("invalid entry point name"); - } - - io::set_device(name, std::make_unique()); - return 0; -} - +/// @brief Get content sources list static int l_get_content_sources(lua::State* L) { const auto& sources = engine->getContentControl().getContentSources(); lua::createtable(L, static_cast(sources.size()), 0); @@ -152,6 +97,7 @@ static int l_get_content_sources(lua::State* L) { return 1; } +/// @brief Set content sources list static int l_set_content_sources(lua::State* L) { if (!lua::istable(L, 1)) { throw std::runtime_error("table expected as argument 1"); @@ -167,17 +113,12 @@ static int l_set_content_sources(lua::State* L) { return 0; } +/// @brief Reset content sources to defaults static int l_reset_content_sources(lua::State* L) { engine->getContentControl().resetContentSources(); return 0; } -static int l_set_title(lua::State* L) { - auto title = lua::require_string(L, 1); - engine->getWindow().setTitle(title); - return 0; -} - /// @brief Get a setting value /// @param name The name of the setting /// @return The value of the setting @@ -245,11 +186,13 @@ static int l_get_setting_info(lua::State* L) { throw std::runtime_error("unsupported setting type"); } +/// @brief Open folder in file explorer static int l_open_folder(lua::State* L) { platform::open_folder(io::resolve(lua::require_string(L, 1))); return 0; } +/// @brief Open URL in default browser with confirmation dialog static int l_open_url(lua::State* L) { auto url = lua::require_string(L, 1); @@ -268,6 +211,26 @@ static int l_open_url(lua::State* L) { return 0; } + +/// @brief Bring application window to focus +static int l_focus(lua::State* L) { + engine->getWindow().focus(); + return 0; +} + +/// @brief Set window title +static int l_set_title(lua::State* L) { + auto title = lua::require_string(L, 1); + engine->getWindow().setTitle(title); + return 0; +} + +/// @brief Quit the game +static int l_quit(lua::State*) { + engine->quit(); + return 0; +} + /// @brief Creating new world /// @param name Name world /// @param seed Seed world @@ -345,39 +308,95 @@ static int l_delete_world(lua::State* L) { return 0; } +/// @brief Get engine version +/// @return major, minor +static int l_get_version(lua::State* L) { + return lua::pushvec_stack( + L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR) + ); +} -/// @brief Quit the game -static int l_quit(lua::State*) { - engine->quit(); +/// @brief Create in-memory named IO device +static int l_create_memory_device(lua::State* L) { + std::string name = lua::require_string(L, 1); + if (io::get_device(name)) { + throw std::runtime_error( + "entry-point '" + name + "' is already used" + ); + } + if (name.find(':') != std::string::npos) { + throw std::runtime_error("invalid entry point name"); + } + + io::set_device(name, std::make_unique()); return 0; } +/// @brief Start a new engine instance with debugging server +static int l_start_debug_instance(lua::State* L) { + if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) { + throw std::runtime_error("project has no debugging permission"); + } + + int port = lua::tointeger(L, 1); + if (port == 0) { + auto network = engine->getNetwork(); + if (network == nullptr) { + throw std::runtime_error("project has no network permission"); + } + port = network->findFreePort(); + if (port == -1) { + throw std::runtime_error("could not find free port"); + } + } + auto projectPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : ""; + const auto& paths = engine->getPaths(); + + std::vector args { + "--res", paths.getResourcesFolder().u8string(), + "--dir", paths.getUserFilesFolder().u8string(), + "--dbg-server", "tcp:" + std::to_string(port), + }; + if (!projectPath.empty()) { + args.emplace_back("--project"); + args.emplace_back(io::resolve(std::string(projectPath)).string()); + } + + platform::new_engine_instance(std::move(args)); + return lua::pushinteger(L, port); +} + const luaL_Reg applib[] = { - {"get_version", lua::wrap}, + /// content {"is_content_loaded", lua::wrap}, {"load_content", lua::wrap}, {"reset_content", lua::wrap}, {"reconfig_packs", lua::wrap}, - {"start_debug_instance", lua::wrap}, - {"focus", lua::wrap}, - {"create_memory_device", lua::wrap}, {"get_content_sources", lua::wrap}, {"set_content_sources", lua::wrap}, {"reset_content_sources", lua::wrap}, - {"set_title", lua::wrap}, - {"open_folder", lua::wrap}, - {"open_url", lua::wrap}, + /// settings {"get_setting", lua::wrap}, {"set_setting", lua::wrap}, {"str_setting", lua::wrap}, {"get_setting_info", lua::wrap}, + /// system applications + {"open_folder", lua::wrap}, + {"open_url", lua::wrap}, + /// window + {"focus", lua::wrap}, + {"set_title", lua::wrap}, + {"quit", lua::wrap}, + /// world {"new_world", lua::wrap}, {"open_world", lua::wrap}, {"reopen_world", lua::wrap}, {"save_world", lua::wrap}, {"close_world", lua::wrap}, {"delete_world", lua::wrap}, - {"quit", lua::wrap}, - // for other functions see libcore.cpp and stdlib.lua + /// other + {"get_version", lua::wrap}, + {"create_memory_device", lua::wrap}, + {"start_debug_instance", lua::wrap}, {nullptr, nullptr} }; From 336ae09f669fe734ac9a69ebcbb3550596f26a56 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 8 Jan 2026 20:06:23 +0300 Subject: [PATCH 29/40] fix unchecked indices buffer overflow --- src/graphics/render/BlocksRenderer.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 5320edd73..5cc0bb525 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -80,7 +80,7 @@ void BlocksRenderer::face( const glm::vec4(&lights)[4], const glm::vec4& tint ) { - if (vertexCount + 4 >= capacity) { + if (vertexCount + 4 >= capacity || indexCount + 6 >= capacity) { overflow = true; return; } @@ -120,7 +120,7 @@ void BlocksRenderer::faceAO( const UVRegion& region, bool lights ) { - if (vertexCount + 4 >= capacity) { + if (vertexCount + 4 >= capacity || indexCount + 6 >= capacity) { overflow = true; return; } @@ -159,7 +159,7 @@ void BlocksRenderer::face( glm::vec4 tint, bool lights ) { - if (vertexCount + 4 >= capacity) { + if (vertexCount + 4 >= capacity || indexCount + 6 >= capacity) { overflow = true; return; } @@ -305,12 +305,15 @@ void BlocksRenderer::blockCustomModel( const auto& model = cache.getModel(block.rt.id, block.getVariantIndex(states.userbits)); for (const auto& mesh : model.meshes) { - if (vertexCount + mesh.vertices.size() >= capacity) { + if (vertexCount + mesh.vertices.size() >= capacity + || indexCount + mesh.vertices.size() >= capacity) { overflow = true; return; } bool shading = mesh.shading && !block.shadeless; - for (int triangle = 0; triangle < mesh.vertices.size() / 3; triangle++) { + + int trianglesCount = mesh.vertices.size() / 3; + for (int triangle = 0; triangle < trianglesCount; triangle++) { auto r = mesh.vertices[triangle * 3 + (triangle % 2) * 2].coord - mesh.vertices[triangle * 3 + 1].coord; r = r.x * X + r.y * Y + r.z * Z; @@ -778,6 +781,10 @@ ChunkMesh BlocksRenderer::render( const Chunk* chunk, const VoxelsVolume& volume ) { build(chunk, volume); + + assert(vertexCount <= capacity); + assert(indexCount <= capacity); + assert(denseIndexCount <= capacity); return ChunkMesh{std::make_unique>( vertexBuffer.get(), vertexCount, From e451a4f3a3dcc16f8377f1b2a0098436463ac5bb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 8 Jan 2026 22:43:20 +0300 Subject: [PATCH 30/40] add explicit 'solid' block property --- res/scripts/post_content.lua | 2 +- src/content/ContentBuilder.cpp | 8 ++++++-- src/content/loading/BlockLoader.cpp | 1 + src/graphics/render/BlocksRenderer.cpp | 2 +- src/voxels/Block.hpp | 3 +++ 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/res/scripts/post_content.lua b/res/scripts/post_content.lua index b512882d7..b6ca05598 100644 --- a/res/scripts/post_content.lua +++ b/res/scripts/post_content.lua @@ -7,7 +7,7 @@ local names = { "hidden", "draw-group", "picking-item", "surface-replacement", "script-name", "ui-layout", "inventory-size", "tick-interval", "overlay-texture", "translucent", "fields", "particles", "icon-type", "icon", "placing-block", - "stack-size", "name", "script-file", "culling" + "stack-size", "name", "script-file", "culling", "solid" } for name, _ in pairs(user_props) do table.insert(names, name) diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index 5eb639b1a..d32cb2860 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -34,11 +34,15 @@ std::unique_ptr ContentBuilder::build() { if (def.variants) { for (auto& variant : def.variants->variants) { - variant.rt.solid = variant.model.type == BlockModelType::BLOCK; + variant.rt.solid = + variant.model.type == BlockModelType::BLOCK || + def.explictlySolid; } def.defaults = def.variants->variants.at(0); } else { - def.defaults.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; + def.defaults.rt.solid = + def.defaults.model.type == BlockModelType::BLOCK || + def.explictlySolid; } const float EPSILON = 0.01f; diff --git a/src/content/loading/BlockLoader.cpp b/src/content/loading/BlockLoader.cpp index 6866443bc..87c10fa67 100644 --- a/src/content/loading/BlockLoader.cpp +++ b/src/content/loading/BlockLoader.cpp @@ -227,6 +227,7 @@ template<> void ContentUnitLoader::loadUnit( root.at("tick-interval").get(def.tickInterval); root.at("overlay-texture").get(def.overlayTexture); root.at("translucent").get(def.translucent); + root.at("solid").get(def.explictlySolid); if (root.has("fields")) { def.dataStruct = std::make_unique(); diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 5cc0bb525..1980a5d97 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -331,7 +331,7 @@ void BlocksRenderer::blockCustomModel( if (!block.rt.extended && !isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), block, variant) - && is_aligned(n)) { + /*&& is_aligned(n)*/) { continue; } diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 29eda2494..96620c8f9 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -230,6 +230,9 @@ public: /// @brief Block has semi-transparent texture bool translucent = false; + /// @brief Explicitly overriding 'solid' property if true assigned + bool explictlySolid = false; + /// @brief Set of block physical hitboxes std::vector hitboxes {AABB()}; From a95afd777d80185b9ecac3565a3c237cd21f8b51 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 8 Jan 2026 23:24:38 +0300 Subject: [PATCH 31/40] update Block::cloneTo --- src/graphics/render/BlocksRenderer.cpp | 3 +-- src/voxels/Block.cpp | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 1980a5d97..503326b14 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -330,8 +330,7 @@ void BlocksRenderer::blockCustomModel( vp = vp.x * X + vp.y * Y + vp.z * Z; if (!block.rt.extended - && !isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), block, variant) - /*&& is_aligned(n)*/) { + && !isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), block, variant)) { continue; } diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index 17a3acc46..65830efc5 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -125,9 +125,6 @@ Block::Block(std::string name, const std::string& texture) void Block::cloneTo(Block& dst) { dst.caption = caption; - for (int i = 0; i < 6; i++) { - dst.defaults = defaults; - } dst.defaults = defaults; if (variants) { dst.variants = std::make_unique(*variants); @@ -155,6 +152,7 @@ void Block::cloneTo(Block& dst) { dst.tickInterval = tickInterval; dst.overlayTexture = overlayTexture; dst.translucent = translucent; + dst.explictlySolid = explictlySolid; dst.tags = tags; if (particles) { dst.particles = std::make_unique(*particles); From 9bc1e987d12f44045c6f3fae1326f597e4629ab3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 8 Jan 2026 23:34:12 +0300 Subject: [PATCH 32/40] update doc/*/block-properties.md --- doc/en/block-properties.md | 5 +++++ doc/ru/block-properties.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index 6233ea7ec..72a4b4c48 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -56,6 +56,11 @@ Enables translucency support in block textures (examples: water, ice). Should only be used when needed, as it impacts performance. Not required for full transparency (grass, flowers). +### *solid* + +Explicitly specifies that the block is solid - completely overlapping the blocks behind it. +Used by blocks with the `custom` model type, which must support culling of invisible geometry, as with the `block` type. If `false` specified then ignored. + ### *rotation* Rotation profile (set of available block rotations and behaviour of placing block rotation) from list: diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index b70c5376d..97e8fedc6 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -57,6 +57,11 @@ Следует использовать только при надобности, так как влияет на производительность. Не требуется для полной прозрачности (трава, цветы). +### Цельность - *solid* + +Явно указывает, что блок является цельным (полным) - полностью перекрывает блоки за собой. +Используется блоками с типом модели `custom`, которые должны поддерживать отсечение невидимой геометрии, как у типа `block`. Свойство игнорируется при указании `false`. + ### Вращение - *rotation* Профиль вращения (набор положений, в которые можно установить блок) из списка: From 19836bfde3eb2a0724de28da3661517127d7defd Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 8 Jan 2026 23:37:47 +0300 Subject: [PATCH 33/40] remove unused `is_aligned` --- src/graphics/render/BlocksRenderer.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 503326b14..2f700d659 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -274,19 +274,6 @@ void BlocksRenderer::blockAABB( } } -static bool is_aligned(const glm::vec3& v, float e = 1e-6f) { - if (std::abs(v.y) < e && std::abs(v.z) < e && std::abs(v.x) > e) { - return true; - } - if (std::abs(v.x) < e && std::abs(v.z) < e && std::abs(v.y) > e) { - return true; - } - if (std::abs(v.x) < e && std::abs(v.y) < e && std::abs(v.z) > e) { - return true; - } - return false; -} - void BlocksRenderer::blockCustomModel( const glm::ivec3& icoord, const Block& block, blockstate states, bool lights, bool ao ) { From a586fe5e92681376149061bb74a324e7d4dda15e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 8 Jan 2026 23:46:52 +0300 Subject: [PATCH 34/40] feat: custom model full culling case --- src/graphics/render/BlocksRenderer.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 2f700d659..36f12a196 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -290,6 +290,23 @@ void BlocksRenderer::blockCustomModel( Z = orient.axes[2]; } + if (!block.rt.extended) { + glm::ivec3 offsets[6] { + {-1, 0, 0}, {0, -1, 0}, {0, 0, -1}, + {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, + }; + bool culled = true; + for (int i = 0; i < 6; i++) { + if (isOpen(icoord + offsets[i], block, variant)) { + culled = false; + break; + } + } + if (culled) { + return; + } + } + const auto& model = cache.getModel(block.rt.id, block.getVariantIndex(states.userbits)); for (const auto& mesh : model.meshes) { if (vertexCount + mesh.vertices.size() >= capacity From a4fc4a5044d394a8f1d054ecf76d18c6ebba0a78 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 9 Jan 2026 02:00:54 +0300 Subject: [PATCH 35/40] fix: player.set_suspend(pid, true) leaves entity in the world --- src/logic/scripting/lua/libs/libplayer.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/logic/scripting/lua/libs/libplayer.cpp b/src/logic/scripting/lua/libs/libplayer.cpp index 14ec48456..9f634bddb 100644 --- a/src/logic/scripting/lua/libs/libplayer.cpp +++ b/src/logic/scripting/lua/libs/libplayer.cpp @@ -324,8 +324,10 @@ static int l_is_suspended(lua::State* L) { } static int l_set_suspended(lua::State* L) { - if (auto player = get_player(L, 1)) { - player->setSuspended(lua::toboolean(L, 2)); + if (lua::toboolean(L, 2)) { + level->players->suspend(lua::tointeger(L, 1)); + } else { + level->players->resume(lua::tointeger(L, 1)); } return 0; } From d9a914ee5423ec216e216f6839785a887eb47036 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 9 Jan 2026 02:29:45 +0300 Subject: [PATCH 36/40] fix incorrect 3d text preset for existing players --- src/graphics/render/Decorator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 8bbb2c9d4..420f6d0dc 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -53,6 +53,9 @@ Decorator::Decorator( addParticles(def, pos); } }); + playerNamePreset.deserialize(engine.getResPaths().readCombinedObject( + "presets/text3d/player_name.toml" + )); for (const auto& [id, player] : *level.players) { if (id == this->player.getId()) { continue; @@ -63,9 +66,6 @@ Decorator::Decorator( player->getPosition() )); } - playerNamePreset.deserialize(engine.getResPaths().readCombinedObject( - "presets/text3d/player_name.toml" - )); } void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { From 088bfc878efc05fdd92078581068e16d1e38f652 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 9 Jan 2026 02:36:13 +0300 Subject: [PATCH 37/40] fix: player not spawning entity after set_suspended(pid, false) --- src/objects/Players.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/objects/Players.cpp b/src/objects/Players.cpp index a90d6d07d..c925c9390 100644 --- a/src/objects/Players.cpp +++ b/src/objects/Players.cpp @@ -99,7 +99,7 @@ void Players::suspend(int64_t id) { } player->setSuspended(true); level.entities->despawn(player->getEntity()); - player->setEntity(0); + player->setEntity(ENTITY_AUTO); } } From 3282f5f8ae59bffa2b10a9491872fabeff7e5afe Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 9 Jan 2026 02:42:12 +0300 Subject: [PATCH 38/40] update doc/*/scripting/builtins/libplayer.md --- doc/en/scripting/builtins/libplayer.md | 1 + doc/ru/scripting/builtins/libplayer.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/en/scripting/builtins/libplayer.md b/doc/en/scripting/builtins/libplayer.md index 379ea89b6..4b632d798 100644 --- a/doc/en/scripting/builtins/libplayer.md +++ b/doc/en/scripting/builtins/libplayer.md @@ -117,6 +117,7 @@ player.set_suspended(pid: int, suspended: bool) Setter and getter for the player's suspended status. When suspended, the entity is deleted and the player is disabled from the world simulation. +When resumed, a new entity will be created with a different ID. ```lua player.set_name(playerid: int, name: str) diff --git a/doc/ru/scripting/builtins/libplayer.md b/doc/ru/scripting/builtins/libplayer.md index d4f77cac6..1590d0a47 100644 --- a/doc/ru/scripting/builtins/libplayer.md +++ b/doc/ru/scripting/builtins/libplayer.md @@ -30,6 +30,7 @@ player.get_name(playerid: int) -> string -- Сеттер и геттер статуса "заморозки" игрока. +-- При заморозке сущность игрока удаляется. При последующей разморозке будет создана новая, с другим id. player.set_suspended(pid: int, suspended: boolean) player.is_suspended(pid: int) -> boolean ``` @@ -125,4 +126,4 @@ player.get_all() -> table -- Возвращает id ближайшего к указанной позиции игрока, либо nil если игроков нет. player.get_nearest(position: vec3) -> int / nil -``` \ No newline at end of file +``` From 5785060ffb85edb33c72410e0c65123c094fb3dd Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 9 Jan 2026 02:59:38 +0300 Subject: [PATCH 39/40] fix: player entity body is active before player flight state applied --- res/scripts/components/player.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua index 2fc25b7e2..66fdda825 100644 --- a/res/scripts/components/player.lua +++ b/res/scripts/components/player.lua @@ -58,7 +58,15 @@ local function process_player_inputs(pid, rot, delta) end end +local prepared = false +body:set_enabled(false) + function on_physics_update(delta) + if not prepared then + prepared = true + body:set_enabled(true) + end + local pid = entity:get_player() if pid == -1 then return From f602384a94aaec943b590654604b460d6c1b0f22 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 9 Jan 2026 03:26:32 +0300 Subject: [PATCH 40/40] update player spawn point auto selection --- src/objects/Player.cpp | 6 ++---- src/objects/Player.hpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index f27dc743e..a63341d49 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -105,10 +105,8 @@ void Player::postUpdate() { if (flight && hitbox.grounded && !noclip) { flight = false; } - if (spawnpoint.y <= 0.1) { - for (int i = 0; i < SPAWN_ATTEMPTS_PER_UPDATE; i++) { - attemptToFindSpawnpoint(); - } + for (int i = 0; i < SPAWN_ATTEMPTS_PER_UPDATE && std::isnan(spawnpoint.x); i++) { + attemptToFindSpawnpoint(); } } diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index 18c5a5996..02a7ce54b 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -49,7 +49,7 @@ class Player : public Serializable { int chosenSlot; glm::vec3 position; - glm::vec3 spawnpoint {}; + glm::vec3 spawnpoint {std::nanf(""), std::nanf(""), std::nanf("")}; std::shared_ptr inventory; bool suspended = false; bool flight = false;