From 61625ffe00f70df9f8a80e391aad7c947b90a7b8 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:40:38 +0300 Subject: [PATCH 01/32] Added script-listener to packs loading --- src/content/ContentLoader.cpp | 24 ++++++++++++++++- src/content/ContentLoader.hpp | 1 + src/content/ContentPack.hpp | 5 ++++ src/logic/scripting/scripting.cpp | 45 +++++++++++++++++++++++++++++++ src/logic/scripting/scripting.hpp | 18 +++++++++++++ 5 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index e2a463cac..47af7232a 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -334,12 +334,16 @@ void ContentLoader::load() { fixPackIndices(); + loadMainScript(*runtime); + auto folder = pack->folder; builder.defaults = paths.readCombinedObject( EnginePaths::CONFIG_DEFAULTS.string() ); + scripting::on_content_initialization(); + // Load world generators io::path generatorsDir = folder / "generators"; foreach_file(generatorsDir, [this](const io::path& file) { @@ -462,6 +466,21 @@ void ContentLoader::reloadScript(const Content& content, ItemDef& item) { load_script(content, item); } +void ContentLoader::loadMainScript(ContentPackRuntime& runtime) { + const auto& pack = runtime.getInfo(); + const auto& folder = pack.folder; + io::path scriptFile = folder / "scripts/main.lua"; + if (io::is_regular_file(scriptFile)) { + scripting::load_main_script( + runtime.getEnvironment(), + pack.id, + scriptFile, + pack.id + ":scripts/main.lua", + runtime.mainfuncsset + ); + } +} + void ContentLoader::loadWorldScript(ContentPackRuntime& runtime) { const auto& pack = runtime.getInfo(); const auto& folder = pack.folder; @@ -478,6 +497,7 @@ void ContentLoader::loadWorldScript(ContentPackRuntime& runtime) { } void ContentLoader::loadScripts(Content& content) { + scripting::on_content_loading(); load_scripts(content, content.blocks); load_scripts(content, content.items); @@ -486,7 +506,7 @@ void ContentLoader::loadScripts(Content& content) { const auto& pack = runtime->getInfo(); const auto& folder = pack.folder; - // Load main world script + // Load world script loadWorldScript(*runtime); // Load entity components @@ -501,6 +521,8 @@ void ContentLoader::loadScripts(Content& content) { ); }); } + + scripting::on_content_loaded(); } void ContentLoader::loadResources(ResourceType type, const dv::value& list) { diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp index 3bf385c5b..0dd292809 100644 --- a/src/content/ContentLoader.hpp +++ b/src/content/ContentLoader.hpp @@ -58,6 +58,7 @@ public: void load(); static void loadScripts(Content& content); + static void loadMainScript(ContentPackRuntime& pack); static void loadWorldScript(ContentPackRuntime& pack); static void reloadScript(const Content& content, Block& block); static void reloadScript(const Content& content, ItemDef& item); diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index a4548d038..146858f55 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -129,12 +129,17 @@ struct WorldFuncsSet { bool oninventoryclosed; }; +struct MainFuncsSet { + bool oncontentloading; +}; + class ContentPackRuntime { ContentPack info; ContentPackStats stats {}; scriptenv env; public: WorldFuncsSet worldfuncsset {}; + MainFuncsSet mainfuncsset {}; ContentPackRuntime(ContentPack info, scriptenv env); ~ContentPackRuntime(); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index e3c23ee1e..b49f2c5ee 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -627,6 +627,36 @@ void scripting::on_ui_close(UiDocument* layout, Inventory* inventory) { }); } +void scripting::on_content_initialization() { + auto L = lua::get_main_state(); + + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event(L, pack.id + ":.oncontentinitialization", [](auto L) { + return 0; + }); + } +} + +void scripting::on_content_loading() { + auto L = lua::get_main_state(); + + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event(L, pack.id + ":.oncontentloading", [](auto L) { + return 0; + }); + } +} + +void scripting::on_content_loaded() { + auto L = lua::get_main_state(); + + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event(L, pack.id + ":.oncontentloaded", [](auto L) { + return 0; + }); + } +} + bool scripting::register_event( int env, const std::string& name, const std::string& id ) { @@ -763,6 +793,21 @@ void scripting::load_world_script( register_event(env, "on_inventory_closed", prefix + ":.inventoryclosed"); } +void scripting::load_main_script( + const scriptenv& senv, + const std::string& prefix, + const io::path& file, + const std::string& fileName, + MainFuncsSet& funcsset +) { + int env = *senv; + lua::pop(lua::get_main_state(), load_script(env, "main", file, fileName)); + + register_event(env, "on_content_initialization", prefix + ":.oncontentinitialization"); + register_event(env, "on_content_loading", prefix + ":.oncontentloading"); + register_event(env, "on_content_loaded", prefix + ":.oncontentloaded"); +} + void scripting::load_layout_script( const scriptenv& senv, const std::string& prefix, diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index db27e8873..90f5ac1ea 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -27,6 +27,7 @@ struct BlockFuncNamesCache; struct ItemFuncsSet; struct ItemFuncNamesCache; struct WorldFuncsSet; +struct MainFuncsSet; struct UserComponent; struct UiDocScript; class BlocksController; @@ -160,6 +161,15 @@ namespace scripting { /// @brief Called on UI view close void on_ui_close(UiDocument* layout, Inventory* inventory); + /// @brief Called before loading + void on_content_initialization(); + + /// @brief Called on Content loading + void on_content_loading(); + + /// @brief Called on Content loading finish + void on_content_loaded(); + /// @brief Load script associated with a Block /// @param env environment /// @param prefix pack id @@ -221,6 +231,14 @@ namespace scripting { WorldFuncsSet& funcsset ); + void load_main_script( + const scriptenv& env, + const std::string& packid, + const io::path& file, + const std::string& fileName, + MainFuncsSet& funcsset + ); + /// @brief Load script associated with an UiDocument /// @param env environment /// @param prefix pack id From 89a6747c218e49cfd6d05c636dd2e02288153a15 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Sun, 22 Mar 2026 22:59:57 +0300 Subject: [PATCH 02/32] rename on_content_loading to on_scripts_loading --- src/content/ContentLoader.cpp | 2 +- src/content/ContentPack.hpp | 4 +++- src/logic/scripting/scripting.cpp | 6 +++--- src/logic/scripting/scripting.hpp | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 47af7232a..ee2903bb6 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -497,7 +497,7 @@ void ContentLoader::loadWorldScript(ContentPackRuntime& runtime) { } void ContentLoader::loadScripts(Content& content) { - scripting::on_content_loading(); + scripting::on_scripts_loading(); load_scripts(content, content.blocks); load_scripts(content, content.items); diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index 146858f55..9940732fb 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -130,7 +130,9 @@ struct WorldFuncsSet { }; struct MainFuncsSet { - bool oncontentloading; + bool oncontentinitialization; + bool onscriptsloading; + bool oncontentloaded; }; class ContentPackRuntime { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index b49f2c5ee..2be28695b 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -637,11 +637,11 @@ void scripting::on_content_initialization() { } } -void scripting::on_content_loading() { +void scripting::on_scripts_loading() { auto L = lua::get_main_state(); for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event(L, pack.id + ":.oncontentloading", [](auto L) { + lua::emit_event(L, pack.id + ":.onscriptsloading", [](auto L) { return 0; }); } @@ -804,7 +804,7 @@ void scripting::load_main_script( lua::pop(lua::get_main_state(), load_script(env, "main", file, fileName)); register_event(env, "on_content_initialization", prefix + ":.oncontentinitialization"); - register_event(env, "on_content_loading", prefix + ":.oncontentloading"); + register_event(env, "on_scripts_loading", prefix + ":.onscriptsloading"); register_event(env, "on_content_loaded", prefix + ":.oncontentloaded"); } diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 90f5ac1ea..b9152921e 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -165,7 +165,7 @@ namespace scripting { void on_content_initialization(); /// @brief Called on Content loading - void on_content_loading(); + void on_scripts_loading(); /// @brief Called on Content loading finish void on_content_loaded(); From b0356212496394b06a50659c6f903e65150c6a23 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:03:43 +0300 Subject: [PATCH 03/32] fixes --- src/content/ContentLoader.cpp | 13 +++++-------- src/content/ContentLoader.hpp | 2 +- src/content/ContentPack.hpp | 7 ------- src/logic/scripting/scripting.cpp | 18 +++--------------- src/logic/scripting/scripting.hpp | 9 ++------- 5 files changed, 11 insertions(+), 38 deletions(-) diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index ee2903bb6..b38d933fe 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -334,7 +334,7 @@ void ContentLoader::load() { fixPackIndices(); - loadMainScript(*runtime); + loadContentScript(*runtime); auto folder = pack->folder; @@ -342,8 +342,6 @@ void ContentLoader::load() { EnginePaths::CONFIG_DEFAULTS.string() ); - scripting::on_content_initialization(); - // Load world generators io::path generatorsDir = folder / "generators"; foreach_file(generatorsDir, [this](const io::path& file) { @@ -466,17 +464,16 @@ void ContentLoader::reloadScript(const Content& content, ItemDef& item) { load_script(content, item); } -void ContentLoader::loadMainScript(ContentPackRuntime& runtime) { +void ContentLoader::loadContentScript(ContentPackRuntime& runtime) { const auto& pack = runtime.getInfo(); const auto& folder = pack.folder; - io::path scriptFile = folder / "scripts/main.lua"; + io::path scriptFile = folder / "scripts/content.lua"; if (io::is_regular_file(scriptFile)) { - scripting::load_main_script( + scripting::load_content_script( runtime.getEnvironment(), pack.id, scriptFile, - pack.id + ":scripts/main.lua", - runtime.mainfuncsset + pack.id + ":scripts/content.lua" ); } } diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp index 0dd292809..aa65478ae 100644 --- a/src/content/ContentLoader.hpp +++ b/src/content/ContentLoader.hpp @@ -58,7 +58,7 @@ public: void load(); static void loadScripts(Content& content); - static void loadMainScript(ContentPackRuntime& pack); + static void loadContentScript(ContentPackRuntime& pack); static void loadWorldScript(ContentPackRuntime& pack); static void reloadScript(const Content& content, Block& block); static void reloadScript(const Content& content, ItemDef& item); diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index 9940732fb..a4548d038 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -129,19 +129,12 @@ struct WorldFuncsSet { bool oninventoryclosed; }; -struct MainFuncsSet { - bool oncontentinitialization; - bool onscriptsloading; - bool oncontentloaded; -}; - class ContentPackRuntime { ContentPack info; ContentPackStats stats {}; scriptenv env; public: WorldFuncsSet worldfuncsset {}; - MainFuncsSet mainfuncsset {}; ContentPackRuntime(ContentPack info, scriptenv env); ~ContentPackRuntime(); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 2be28695b..bba9f6837 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -627,16 +627,6 @@ void scripting::on_ui_close(UiDocument* layout, Inventory* inventory) { }); } -void scripting::on_content_initialization() { - auto L = lua::get_main_state(); - - for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event(L, pack.id + ":.oncontentinitialization", [](auto L) { - return 0; - }); - } -} - void scripting::on_scripts_loading() { auto L = lua::get_main_state(); @@ -793,17 +783,15 @@ void scripting::load_world_script( register_event(env, "on_inventory_closed", prefix + ":.inventoryclosed"); } -void scripting::load_main_script( +void scripting::load_content_script( const scriptenv& senv, const std::string& prefix, const io::path& file, - const std::string& fileName, - MainFuncsSet& funcsset + const std::string& fileName ) { int env = *senv; - lua::pop(lua::get_main_state(), load_script(env, "main", file, fileName)); + lua::pop(lua::get_main_state(), load_script(env, "content", file, fileName)); - register_event(env, "on_content_initialization", prefix + ":.oncontentinitialization"); register_event(env, "on_scripts_loading", prefix + ":.onscriptsloading"); register_event(env, "on_content_loaded", prefix + ":.oncontentloaded"); } diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index b9152921e..9d29528b8 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -27,7 +27,6 @@ struct BlockFuncNamesCache; struct ItemFuncsSet; struct ItemFuncNamesCache; struct WorldFuncsSet; -struct MainFuncsSet; struct UserComponent; struct UiDocScript; class BlocksController; @@ -161,9 +160,6 @@ namespace scripting { /// @brief Called on UI view close void on_ui_close(UiDocument* layout, Inventory* inventory); - /// @brief Called before loading - void on_content_initialization(); - /// @brief Called on Content loading void on_scripts_loading(); @@ -231,12 +227,11 @@ namespace scripting { WorldFuncsSet& funcsset ); - void load_main_script( + void load_content_script( const scriptenv& env, const std::string& packid, const io::path& file, - const std::string& fileName, - MainFuncsSet& funcsset + const std::string& fileName ); /// @brief Load script associated with an UiDocument From b707c18931f2ebd7e9a0cff1e0d4703d5d29430d Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:23:04 +0300 Subject: [PATCH 04/32] doc update --- doc/en/scripting/events.md | 17 ++++++++++++++++- doc/ru/scripting/events.md | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 504bde532..fe7d5eecb 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -180,6 +180,22 @@ function on_inventory_closed(invid: int, playerid: int) Called when the inventory is closed. +### CONTENT events + +Callbacks specified in *content.lua* script. + +```lua +function on_scripts_loading() +``` + +Called before the content pack scripts are loaded + +```lua +function on_content_loaded() +``` + +Called after the content pack has been fully loaded + ## Layout events Script *layouts/layout_name.xml.lua* events. @@ -202,7 +218,6 @@ Called on element removed from the screen. Callbacks specified in *hud.lua* script. - ```lua function on_hud_open(playerid: int) ``` diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 116c73551..c71e58ecb 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -4,6 +4,7 @@ - [События блоков](#события-блоков) - [События предметов](#события-предметов) - [События мира](#события-мира) +- [События CONTENT](#События CONTENT) - [События макета](#события-макета) - [События HUD](#события-hud) - [События ECS](ecs.md#события-компонента) @@ -189,6 +190,21 @@ function on_inventory_closed(invid: int, playerid: int) Вызывается при закрытии инвентаря. +### События CONTENT + +События жизненного цикла контент-пака прописываются в файле `scripts/content.lua` + +```lua +function on_scripts_loading() +``` + +Вызывается перед загрузкой скриптов контент-пака + +```lua +function on_content_loaded() +``` + +Вызывается после того, как контент-пак был полностью загружен ## События макета From 4f7039730a96ab8391b5b4a6e1d14c94277f5b8d Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:26:06 +0300 Subject: [PATCH 05/32] Update events.md --- doc/ru/scripting/events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index c71e58ecb..2d8ed97a1 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -4,7 +4,7 @@ - [События блоков](#события-блоков) - [События предметов](#события-предметов) - [События мира](#события-мира) -- [События CONTENT](#События CONTENT) +- [События CONTENT](#события-content) - [События макета](#события-макета) - [События HUD](#события-hud) - [События ECS](ecs.md#события-компонента) From f486219a77ec5fb67c6c452d0d767a662a8977e7 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:27:52 +0300 Subject: [PATCH 06/32] Update events.md --- doc/ru/scripting/events.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 2d8ed97a1..e8e76b2db 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -4,8 +4,8 @@ - [События блоков](#события-блоков) - [События предметов](#события-предметов) - [События мира](#события-мира) -- [События CONTENT](#события-content) - [События макета](#события-макета) +- [События CONTENT](#события-content) - [События HUD](#события-hud) - [События ECS](ecs.md#события-компонента) - [Библиотека *Events*](#библиотека-events) @@ -190,22 +190,6 @@ function on_inventory_closed(invid: int, playerid: int) Вызывается при закрытии инвентаря. -### События CONTENT - -События жизненного цикла контент-пака прописываются в файле `scripts/content.lua` - -```lua -function on_scripts_loading() -``` - -Вызывается перед загрузкой скриптов контент-пака - -```lua -function on_content_loaded() -``` - -Вызывается после того, как контент-пак был полностью загружен - ## События макета События прописываются в файле `layouts/имя_макета.xml.lua`. @@ -224,6 +208,22 @@ function on_close(invid: int) Вызывается при удалении элемента с экрана. +## События CONTENT + +События жизненного цикла контент-пака прописываются в файле `scripts/content.lua` + +```lua +function on_scripts_loading() +``` + +Вызывается перед загрузкой скриптов контент-пака + +```lua +function on_content_loaded() +``` + +Вызывается после того, как контент-пак был полностью загружен + ## События HUD События связанные с игровым интерфейсом прописываются в файле `scripts/hud.lua` From 4f80a56362b471da9fd2d68be18b426727365752 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:28:57 +0300 Subject: [PATCH 07/32] Update en/events.md --- doc/en/scripting/events.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index fe7d5eecb..0b61d5b4b 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -180,22 +180,6 @@ function on_inventory_closed(invid: int, playerid: int) Called when the inventory is closed. -### CONTENT events - -Callbacks specified in *content.lua* script. - -```lua -function on_scripts_loading() -``` - -Called before the content pack scripts are loaded - -```lua -function on_content_loaded() -``` - -Called after the content pack has been fully loaded - ## Layout events Script *layouts/layout_name.xml.lua* events. @@ -214,6 +198,22 @@ function on_close(invid: int) Called on element removed from the screen. +## CONTENT events + +Callbacks specified in *content.lua* script. + +```lua +function on_scripts_loading() +``` + +Called before the content pack scripts are loaded + +```lua +function on_content_loaded() +``` + +Called after the content pack has been fully loaded + ## HUD events Callbacks specified in *hud.lua* script. From 8b66b8890af97c21b2c2832122f54d919b2c5a82 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Thu, 2 Apr 2026 23:43:09 +0300 Subject: [PATCH 08/32] doc fixes --- doc/en/scripting/events.md | 2 +- doc/ru/scripting/events.md | 4 ++-- src/content/ContentLoader.cpp | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 0b61d5b4b..fea9de7df 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -198,7 +198,7 @@ function on_close(invid: int) Called on element removed from the screen. -## CONTENT events +## Content events Callbacks specified in *content.lua* script. diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index e8e76b2db..7dbe7bc07 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -5,7 +5,7 @@ - [События предметов](#события-предметов) - [События мира](#события-мира) - [События макета](#события-макета) -- [События CONTENT](#события-content) +- [События контента](#события-контента) - [События HUD](#события-hud) - [События ECS](ecs.md#события-компонента) - [Библиотека *Events*](#библиотека-events) @@ -208,7 +208,7 @@ function on_close(invid: int) Вызывается при удалении элемента с экрана. -## События CONTENT +## События контента События жизненного цикла контент-пака прописываются в файле `scripts/content.lua` diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index b38d933fe..5120f5c59 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -503,7 +503,6 @@ void ContentLoader::loadScripts(Content& content) { const auto& pack = runtime->getInfo(); const auto& folder = pack.folder; - // Load world script loadWorldScript(*runtime); // Load entity components From 518d5d5ae4bc407676b5f6bc5f71a6f576aa2753 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Sat, 4 Apr 2026 01:53:49 +0300 Subject: [PATCH 09/32] Add on_entity_spawned and on_entity_despawned events --- doc/en/scripting/ecs.md | 4 +++ doc/en/scripting/events.md | 11 ++++++++ doc/ru/scripting/ecs.md | 4 +++ doc/ru/scripting/events.md | 12 ++++++++ src/content/ContentPack.hpp | 2 ++ src/logic/scripting/scripting.cpp | 32 ++++++++++++++++++++++ src/logic/scripting/scripting.hpp | 3 ++ src/logic/scripting/scripting_entities.cpp | 8 +++++- 8 files changed, 75 insertions(+), 1 deletion(-) diff --git a/doc/en/scripting/ecs.md b/doc/en/scripting/ecs.md index 7b1f87a1a..878452928 100644 --- a/doc/en/scripting/ecs.md +++ b/doc/en/scripting/ecs.md @@ -180,6 +180,10 @@ rig:get_color() -> vec3 rig:set_color(color: vec3) ``` +> [!WARNING] +> When an entity moves outside the loaded area, it is removed, triggering the event. +> When it re-enters the loaded area, the entity is spawned again. + ## Component events ```lua diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index fea9de7df..f475dc1c1 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -152,6 +152,17 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool Called on block RMB click interaction. Prevents block placing if **true** returned. +```lua +function on_entity_spawned(uid) +``` +Called after an entity is spawned. + +```lua +function on_entity_despawned(uid) +``` + +Called after an entity is removed. At the time of the call, the entity with the given uid has already been deleted. + ### Chunk Events (world.lua) ```lua diff --git a/doc/ru/scripting/ecs.md b/doc/ru/scripting/ecs.md index 4f9d45f08..1a5f6ccf3 100644 --- a/doc/ru/scripting/ecs.md +++ b/doc/ru/scripting/ecs.md @@ -180,6 +180,10 @@ rig:get_color() -> vec3 rig:set_color(color: vec3) ``` +> [!WARNING] +> При выходе сущности за пределы зоны прогрузки она удаляется, вызывая события. +> При повторном попадании в зону прогрузки сущность спавнится заново. + ## События компонента ```lua diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 7dbe7bc07..fa20e4030 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -156,6 +156,18 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool Вызывается при нажатии на блок ПКМ. Предотвращает установку блоков, если возвращает `true` +```lua +function on_entity_spawned(uid) +``` + +Вызывается после спавна сущности + +```lua +function on_entity_despawned(uid) +``` + +Вызывается после удаления сущности, на момент вызова события сущность с переданным uid уже удалена + ```lua function on_player_tick(playerid: int, tps: int) ``` diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index a4548d038..bd0ccc79f 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -127,6 +127,8 @@ struct WorldFuncsSet { bool onchunkremove; bool oninventoryopen; bool oninventoryclosed; + bool onentityspawned; + bool onentitydespawned; }; class ContentPackRuntime { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index bba9f6837..232c72796 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -555,6 +555,34 @@ void scripting::on_player_tick(Player* player, int tps) { } } +void scripting::on_entity_spawned(entityid_t eid) { + auto L = lua::get_main_state(); + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event( + L, + pack.id + ":.entityspawned", + [&](lua::State* L) { + lua::pushinteger(L, eid); + return 1; + } + ); + } +} + +void scripting::on_entity_despawned(entityid_t eid) { + auto L = lua::get_main_state(); + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event( + L, + pack.id + ":.entitydespawned", + [&](lua::State* L) { + lua::pushinteger(L, eid); + return 1; + } + ); + } +} + bool scripting::on_item_use(Player* player, const ItemDef& item) { std::string name = item.name + ".use"; return lua::emit_event( @@ -781,6 +809,10 @@ void scripting::load_world_script( register_event(env, "on_inventory_open", prefix + ":.inventoryopen"); funcsset.oninventoryclosed = register_event(env, "on_inventory_closed", prefix + ":.inventoryclosed"); + funcsset.onentityspawned = + register_event(env, "on_entity_spawned", prefix + ":.entityspawned"); + funcsset.onentitydespawned = + register_event(env, "on_entity_despawned", prefix + ":.entitydespawned"); } void scripting::load_content_script( diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 9d29528b8..3d9737c6a 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -112,6 +112,9 @@ namespace scripting { void on_player_tick(Player* player, int tps); + void on_entity_spawned(entityid_t eid); + void on_entity_despawned(entityid_t eid); + /// @brief Called on RMB click with the item selected /// @return true if prevents default action bool on_item_use(Player* player, const ItemDef& item); diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index c068d7007..0be289038 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -129,6 +129,8 @@ void scripting::on_entity_spawn( for (auto& component : components) { create_component(L, -1, *component, args, saved); } + + scripting::on_entity_spawned(eid); } static void process_entity_callback( @@ -171,9 +173,13 @@ void scripting::on_entity_despawn(const Entity& entity) { entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr ); auto L = lua::get_main_state(); + entityid_t uid = entity.getUID(); + lua::get_from(L, "stdcomp", "remove_Entity", true); - lua::pushinteger(L, entity.getUID()); + lua::pushinteger(L, uid); lua::call(L, 1, 0); + + scripting::on_entity_despawned(uid); } void scripting::on_entity_grounded(const Entity& entity, float force) { From d06d566f6553cf5e3eabd414874f69d0f40a6ccf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 5 Apr 2026 22:41:28 +0300 Subject: [PATCH 10/32] add output file argument --- src/logic/scripting/lua/libs/libapp.cpp | 2 +- src/util/platform.cpp | 30 ++++++++++++++++++++++--- src/util/platform.hpp | 4 +++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 09ea33ce3..39acd97c0 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -362,7 +362,7 @@ static int l_start_debug_instance(lua::State* L) { args.emplace_back(io::resolve(std::string(projectPath)).string()); } - platform::new_engine_instance(std::move(args)); + platform::new_engine_instance(std::move(args), ""); return lua::pushinteger(L, port); } diff --git a/src/util/platform.cpp b/src/util/platform.cpp index f9ecf070d..f113641e5 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -215,7 +215,9 @@ std::filesystem::path platform::get_executable_path() { #endif } -void platform::new_engine_instance(const std::vector& args) { +void platform::new_engine_instance( + const std::vector& args, std::filesystem::path outputFile +) { auto executable = get_executable_path(); #ifdef _WIN32 @@ -250,6 +252,24 @@ void platform::new_engine_instance(const std::vector& args) { auto commandString = toWString(ss.str()); STARTUPINFOW si = { sizeof(si) }; + if (!outputFile.empty()) { + si.dwFlags |= STARTF_USESTDHANDLES; + si.hStdOutput = CreateFileW( + toWString(outputFile.u8string()).data(), + GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr + ); + if (si.hStdOutput == INVALID_HANDLE_VALUE) { + throw std::runtime_error( + "CreateFileW failed with code: " + + std::to_string(GetLastError()) + ); + } + } PROCESS_INFORMATION pi = { 0 }; DWORD flags = CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS; // | CREATE_NO_WINDOW; @@ -280,7 +300,11 @@ void platform::new_engine_instance(const std::vector& args) { for (int i = 0; i < args.size(); i++) { ss << " " << util::quote(args[i]); } - ss << " >/dev/null &"; + if (outputFile.empty()) { + ss << " >/dev/null &"; + } else { + ss << " >" << util::quote(outputFile.u8string()) << " &"; + } auto command = ss.str(); logger.info() << command; @@ -302,4 +326,4 @@ bool platform::stdin_has_data() { fds.events = POLLIN; return poll(&fds, 1, 0) == 1; #endif -} \ No newline at end of file +} diff --git a/src/util/platform.hpp b/src/util/platform.hpp index 620178fc1..54c56a0c5 100644 --- a/src/util/platform.hpp +++ b/src/util/platform.hpp @@ -18,7 +18,9 @@ namespace platform { /// @brief Get current process running executable path std::filesystem::path get_executable_path(); /// @brief Run a separate engine instance with specified arguments - void new_engine_instance(const std::vector& args); + void new_engine_instance( + const std::vector& args, std::filesystem::path outputFile + ); /// @brief Open URL in web browser bool open_url(const std::string& url); /// @brief Check if stdin has input From 9ce7726cab79f4df88d0dfb159fda728f3a2f4c6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 5 Apr 2026 23:56:09 +0300 Subject: [PATCH 11/32] cleanup --- .github/workflows/windows-clang.yml | 2 +- src/main.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-clang.yml b/.github/workflows/windows-clang.yml index af0b060ae..5dd91a576 100644 --- a/.github/workflows/windows-clang.yml +++ b/.github/workflows/windows-clang.yml @@ -4,7 +4,7 @@ on: push: branches: [ "main", "release-**"] pull_request: - branches: [ "main", "dev" ] + branches: [ "main", "dev" ] workflow_call: inputs: build_name: diff --git a/src/main.cpp b/src/main.cpp index ea6ad550e..77175bd8a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,9 @@ static void sigterm_handler(int signum) { int main(int argc, char** argv) { #ifdef VC_BUILD_NAME - logger.info() << "build: " << VC_BUILD_NAME; + if constexpr (VC_BUILD_NAME[0]) { + logger.info() << "build: " << VC_BUILD_NAME; + } #endif CoreParameters coreParameters; From 0773e34fe44f7deffde20326f6fe472ddb1dfabf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 6 Apr 2026 00:43:53 +0300 Subject: [PATCH 12/32] add app.start_debug_instance output file argument --- src/logic/scripting/lua/libs/libapp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 39acd97c0..6442fcbad 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -350,6 +350,7 @@ static int l_start_debug_instance(lua::State* L) { } } auto projectPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : ""; + auto outputPath = lua::isstring(L, 3) ? lua::require_lstring(L, 3) : ""; const auto& paths = engine->getPaths(); std::vector args { @@ -362,7 +363,10 @@ static int l_start_debug_instance(lua::State* L) { args.emplace_back(io::resolve(std::string(projectPath)).string()); } - platform::new_engine_instance(std::move(args), ""); + platform::new_engine_instance( + std::move(args), + outputPath.empty() ? "" : io::resolve(std::string(outputPath)) + ); return lua::pushinteger(L, port); } From 35c040abcf2d31f9a2364063a6f91d557396bdab Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 7 Apr 2026 22:27:52 +0300 Subject: [PATCH 13/32] start 0.32 development --- doc/en/main-page.md | 3 ++- doc/ru/main-page.md | 3 ++- res/content/base/package.json | 2 +- src/constants.hpp | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/en/main-page.md b/doc/en/main-page.md index 16b11d1eb..65a22ee85 100644 --- a/doc/en/main-page.md +++ b/doc/en/main-page.md @@ -1,6 +1,7 @@ # Documentation -Documentation for 0.31. +> [!WARNING] +> Version is in development. Proceed to [Documentation for 0.31.](https://github.com/MihailRis/voxelcore/blob/release-0.31/doc/en/main-page.md) ## Sections diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md index ee8cc5bc1..772f327fd 100644 --- a/doc/ru/main-page.md +++ b/doc/ru/main-page.md @@ -1,6 +1,7 @@ # Документация -Документация версии 0.31. +> [!WARNING] +> Версия находится в разработке. Перейдите к [документации для 0.31.](https://github.com/MihailRis/voxelcore/blob/release-0.31/doc/ru/main-page.md) ## Разделы diff --git a/res/content/base/package.json b/res/content/base/package.json index a9214111b..71b4dca9f 100644 --- a/res/content/base/package.json +++ b/res/content/base/package.json @@ -1,6 +1,6 @@ { "id": "base", "title": "Base", - "version": "0.31", + "version": "0.32", "description": "basic content package" } diff --git a/src/constants.hpp b/src/constants.hpp index 1cf3e72ce..f43314f4a 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -6,7 +6,7 @@ #include inline constexpr int ENGINE_VERSION_MAJOR = 0; -inline constexpr int ENGINE_VERSION_MINOR = 31; +inline constexpr int ENGINE_VERSION_MINOR = 32; #ifdef NDEBUG inline constexpr bool ENGINE_DEBUG_BUILD = false; @@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false; inline constexpr bool ENGINE_DEBUG_BUILD = true; #endif // NDEBUG -inline const std::string ENGINE_VERSION_STRING = "0.31"; +inline const std::string ENGINE_VERSION_STRING = "0.32"; /// @brief world regions format version inline constexpr uint REGION_FORMAT_VERSION = 3; From acf8112644952559f8d21e9ba6902aa5a8423762 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:44:17 +0300 Subject: [PATCH 14/32] rename events + fix --- doc/en/scripting/events.md | 6 ++-- doc/ru/scripting/events.md | 6 ++-- src/content/ContentPack.hpp | 4 +-- src/logic/scripting/scripting.cpp | 36 +++------------------- src/logic/scripting/scripting.hpp | 3 -- src/logic/scripting/scripting_entities.cpp | 26 ++++++++++++++-- 6 files changed, 35 insertions(+), 46 deletions(-) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index f475dc1c1..75180edc3 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -153,15 +153,15 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool Called on block RMB click interaction. Prevents block placing if **true** returned. ```lua -function on_entity_spawned(uid) +function on_entity_spawn(uid) ``` Called after an entity is spawned. ```lua -function on_entity_despawned(uid) +function on_entity_despawn(uid) ``` -Called after an entity is removed. At the time of the call, the entity with the given uid has already been deleted. +Called before an entity is despawned. ### Chunk Events (world.lua) diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index fa20e4030..ff4bfb456 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -157,16 +157,16 @@ function on_block_interact(blockid, x, y, z, playerid) -> bool Вызывается при нажатии на блок ПКМ. Предотвращает установку блоков, если возвращает `true` ```lua -function on_entity_spawned(uid) +function on_entity_spawn(uid) ``` Вызывается после спавна сущности ```lua -function on_entity_despawned(uid) +function on_entity_despawn(uid) ``` -Вызывается после удаления сущности, на момент вызова события сущность с переданным uid уже удалена +Вызывается перед удалением сущности ```lua function on_player_tick(playerid: int, tps: int) diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index bd0ccc79f..06a8cf4dc 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -127,8 +127,8 @@ struct WorldFuncsSet { bool onchunkremove; bool oninventoryopen; bool oninventoryclosed; - bool onentityspawned; - bool onentitydespawned; + bool onentityspawn; + bool onentitydespawn; }; class ContentPackRuntime { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 232c72796..341470c17 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -555,34 +555,6 @@ void scripting::on_player_tick(Player* player, int tps) { } } -void scripting::on_entity_spawned(entityid_t eid) { - auto L = lua::get_main_state(); - for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event( - L, - pack.id + ":.entityspawned", - [&](lua::State* L) { - lua::pushinteger(L, eid); - return 1; - } - ); - } -} - -void scripting::on_entity_despawned(entityid_t eid) { - auto L = lua::get_main_state(); - for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event( - L, - pack.id + ":.entitydespawned", - [&](lua::State* L) { - lua::pushinteger(L, eid); - return 1; - } - ); - } -} - bool scripting::on_item_use(Player* player, const ItemDef& item) { std::string name = item.name + ".use"; return lua::emit_event( @@ -809,10 +781,10 @@ void scripting::load_world_script( register_event(env, "on_inventory_open", prefix + ":.inventoryopen"); funcsset.oninventoryclosed = register_event(env, "on_inventory_closed", prefix + ":.inventoryclosed"); - funcsset.onentityspawned = - register_event(env, "on_entity_spawned", prefix + ":.entityspawned"); - funcsset.onentitydespawned = - register_event(env, "on_entity_despawned", prefix + ":.entitydespawned"); + funcsset.onentityspawn = + register_event(env, "on_entity_spawn", prefix + ":.entityspawn"); + funcsset.onentitydespawn = + register_event(env, "on_entity_despawn", prefix + ":.entitydespawn"); } void scripting::load_content_script( diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 3d9737c6a..9d29528b8 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -112,9 +112,6 @@ namespace scripting { void on_player_tick(Player* player, int tps); - void on_entity_spawned(entityid_t eid); - void on_entity_despawned(entityid_t eid); - /// @brief Called on RMB click with the item selected /// @return true if prevents default action bool on_item_use(Player* player, const ItemDef& item); diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 0be289038..afed77e85 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -6,6 +6,7 @@ #include "objects/Entity.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" +#include "content/ContentControl.hpp" using namespace scripting; @@ -130,7 +131,16 @@ void scripting::on_entity_spawn( create_component(L, -1, *component, args, saved); } - scripting::on_entity_spawned(eid); + for (auto& pack : scripting::content_control->getAllContentPacks()) { + lua::emit_event( + L, + pack.id + ":.entityspawn", + [&](lua::State* L) { + lua::pushinteger(L, eid); + return 1; + } + ); + } } static void process_entity_callback( @@ -172,14 +182,24 @@ void scripting::on_entity_despawn(const Entity& entity) { process_entity_callback( entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr ); + auto L = lua::get_main_state(); entityid_t uid = entity.getUID(); + for (auto& pack : content_control->getAllContentPacks()) { + lua::emit_event( + L, + pack.id + ":.entitydespawn", + [&](lua::State* L) { + lua::pushinteger(L, uid); + return 1; + } + ); + } + lua::get_from(L, "stdcomp", "remove_Entity", true); lua::pushinteger(L, uid); lua::call(L, 1, 0); - - scripting::on_entity_despawned(uid); } void scripting::on_entity_grounded(const Entity& entity, float force) { From 2dd8024b7e0f82bfc79cd6e5e389c3dd6a5a700c Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:59:47 +0300 Subject: [PATCH 15/32] fix entity spawn/despawn --- src/logic/scripting/scripting_entities.cpp | 27 ++++++++++------------ 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index afed77e85..32a396b25 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -6,7 +6,8 @@ #include "objects/Entity.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" -#include "content/ContentControl.hpp" +#include "content/Content.hpp" +#include "content/ContentPack.hpp" using namespace scripting; @@ -131,15 +132,13 @@ void scripting::on_entity_spawn( create_component(L, -1, *component, args, saved); } - for (auto& pack : scripting::content_control->getAllContentPacks()) { - lua::emit_event( - L, - pack.id + ":.entityspawn", - [&](lua::State* L) { + for (auto& [packid, pack] : content->getPacks()) { + if (pack->worldfuncsset.onentityspawn) { + lua::emit_event(L, packid + ":.entityspawn", [&](lua::State* L) { lua::pushinteger(L, eid); return 1; - } - ); + }); + } } } @@ -186,15 +185,13 @@ void scripting::on_entity_despawn(const Entity& entity) { auto L = lua::get_main_state(); entityid_t uid = entity.getUID(); - for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event( - L, - pack.id + ":.entitydespawn", - [&](lua::State* L) { + for (auto& [packid, pack] : content->getPacks()) { + if (pack->worldfuncsset.onentitydespawn) { + lua::emit_event(L, packid + ":.entitydespawn", [&](lua::State* L) { lua::pushinteger(L, uid); return 1; - } - ); + }); + } } lua::get_from(L, "stdcomp", "remove_Entity", true); From 43e0b1d671f521026393d955ac1aa2e2f8a27b55 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 11 Apr 2026 12:16:16 +0300 Subject: [PATCH 16/32] update preload.json --- res/preload.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/res/preload.json b/res/preload.json index 00948068e..51acb129c 100644 --- a/res/preload.json +++ b/res/preload.json @@ -50,7 +50,10 @@ "gui/hud", "gui/entity", "gui/half_block", - "misc/blank" + { + "name": "", + "path": "textures/misc/blank" + } ], "fonts": [ { From dc29f65fa83c5a4fd16d069b73d98bbd9950e1dd Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 15 Apr 2026 23:26:08 +0300 Subject: [PATCH 17/32] add entity to TextNote --- src/graphics/render/TextNote.cpp | 8 ++++++++ src/graphics/render/TextNote.hpp | 4 ++++ src/graphics/render/TextsRenderer.cpp | 17 +++++++++++++++-- src/graphics/render/TextsRenderer.hpp | 9 ++++++++- src/graphics/render/WorldRenderer.cpp | 6 ++++-- 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/graphics/render/TextNote.cpp b/src/graphics/render/TextNote.cpp index 127414c37..7c4797fd3 100644 --- a/src/graphics/render/TextNote.cpp +++ b/src/graphics/render/TextNote.cpp @@ -10,6 +10,14 @@ void TextNote::setText(std::wstring_view text) { this->text = text; } +void TextNote::setEntity(entityid_t eid) { + this->entity = eid; +} + +entityid_t TextNote::getEntity() const { + return entity; +} + const std::wstring& TextNote::getText() const { return text; } diff --git a/src/graphics/render/TextNote.hpp b/src/graphics/render/TextNote.hpp index 65a26ac17..0fc8f4afd 100644 --- a/src/graphics/render/TextNote.hpp +++ b/src/graphics/render/TextNote.hpp @@ -1,5 +1,6 @@ #pragma once +#include "constants.hpp" #include "presets/NotePreset.hpp" /// @brief 3D text instance @@ -7,6 +8,7 @@ class TextNote { std::wstring text; NotePreset preset; glm::vec3 position; + entityid_t entity = ENTITY_NONE; glm::vec3 xAxis {1, 0, 0}; glm::vec3 yAxis {0, 1, 0}; @@ -14,6 +16,8 @@ public: TextNote(std::wstring text, NotePreset preset, glm::vec3 position); void setText(std::wstring_view text); + void setEntity(entityid_t eid); + entityid_t getEntity() const; const std::wstring& getText() const; diff --git a/src/graphics/render/TextsRenderer.cpp b/src/graphics/render/TextsRenderer.cpp index ed822fc9f..ac93d1e43 100644 --- a/src/graphics/render/TextsRenderer.cpp +++ b/src/graphics/render/TextsRenderer.cpp @@ -10,12 +10,18 @@ #include "graphics/core/Shader.hpp" #include "graphics/core/DrawContext.hpp" #include "presets/NotePreset.hpp" +#include "world/Level.hpp" +#include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "constants.hpp" TextsRenderer::TextsRenderer( - Batch3D& batch, const Assets& assets, const Frustum& frustum + const Level& level, + Batch3D& batch, + const Assets& assets, + const Frustum& frustum ) - : batch(batch), assets(assets), frustum(frustum) { + : level(level), batch(batch), assets(assets), frustum(frustum) { } void TextsRenderer::renderNote( @@ -31,6 +37,13 @@ void TextsRenderer::renderNote( const auto& preset = note.getPreset(); auto pos = note.getPosition(); + entityid_t eid = note.getEntity(); + if (eid != ENTITY_NONE) { + if (auto entity = level.entities->get(eid)) { + pos += entity->getTransform().displayPos; + } + } + if (util::distance2(pos, camera.position) > util::sqr(preset.renderDistance / camera.zoom)) { return; diff --git a/src/graphics/render/TextsRenderer.hpp b/src/graphics/render/TextsRenderer.hpp index 629bc3cd2..77af2581a 100644 --- a/src/graphics/render/TextsRenderer.hpp +++ b/src/graphics/render/TextsRenderer.hpp @@ -12,8 +12,10 @@ class Batch3D; class Frustum; class TextNote; struct EngineSettings; +class Level; class TextsRenderer { + const Level& level; Batch3D& batch; const Assets& assets; const Frustum& frustum; @@ -31,7 +33,12 @@ class TextsRenderer { bool projected ); public: - TextsRenderer(Batch3D& batch, const Assets& assets, const Frustum& frustum); + TextsRenderer( + const Level& level, + Batch3D& batch, + const Assets& assets, + const Frustum& frustum + ); void render( const DrawContext& context, diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 9f173eb38..2e150e1f7 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -91,9 +91,11 @@ WorldRenderer::WorldRenderer( assets, level, *player.chunks, &engine.getSettings().graphics )), particles(std::make_unique( - assets, level, *player.chunks, engine.getSettings().graphics + assets, level, *player.chunks, engine.getSettings().graphics + )), + texts(std::make_unique( + level, *batch3d, assets, *frustumCulling )), - texts(std::make_unique(*batch3d, assets, *frustumCulling)), blockWraps(std::make_unique( assets, level.content, *player.chunks )) { From 6426fff26e7a6269a0ef10a0e836ed6ad2a2a844 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 15 Apr 2026 23:29:36 +0300 Subject: [PATCH 18/32] add gfx.text3d.get_entity, set_entity --- src/logic/scripting/lua/libs/libtext3d.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/logic/scripting/lua/libs/libtext3d.cpp b/src/logic/scripting/lua/libs/libtext3d.cpp index 310f5d78e..0c3957cce 100644 --- a/src/logic/scripting/lua/libs/libtext3d.cpp +++ b/src/logic/scripting/lua/libs/libtext3d.cpp @@ -95,6 +95,20 @@ static int l_set_rotation(lua::State* L) { return 0; } +static int l_get_entity(lua::State* L) { + if (auto note = renderer->texts->get(lua::tointeger(L, 1))) { + return lua::pushinteger(L, note->getEntity()); + } + return 0; +} + +static int l_set_entity(lua::State* L) { + if (auto note = renderer->texts->get(lua::tointeger(L, 1))) { + note->setEntity(lua::touinteger(L, 2)); + } + return 0; +} + const luaL_Reg text3dlib[] = { {"show", wrap_hud}, {"hide", wrap_hud}, @@ -108,5 +122,7 @@ const luaL_Reg text3dlib[] = { {"set_axis_y", wrap_hud}, {"set_rotation", wrap_hud}, {"update_settings", wrap_hud}, + {"get_entity", wrap_hud}, + {"set_entity", wrap_hud}, {nullptr, nullptr} }; From 25cc16fdb9f8c9d3e4afc7d51a6e09d24273c89c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 15 Apr 2026 23:38:41 +0300 Subject: [PATCH 19/32] update doc/*/3d-text.md --- doc/en/3d-text.md | 8 ++++++++ doc/ru/3d-text.md | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/doc/en/3d-text.md b/doc/en/3d-text.md index 6e957a37e..b0c9fd1f6 100644 --- a/doc/en/3d-text.md +++ b/doc/en/3d-text.md @@ -79,6 +79,14 @@ gfx.text3d.set_rotation(id: int, rotation: mat4) Sets the text rotation (Sets the rotated vectors X,Y). +```lua +gfx.text3d.get_entity(id: int) -> int +gfx.text3d.set_entity(id: int, entity: int) +``` + +Getter and setter for the entity to which the 3D text position is bound. +The position set via `gfx.text3d.set_pos` is then relative to the entity's position. + ```lua gfx.text3d.update_settings(id: int, preset: table) ``` diff --git a/doc/ru/3d-text.md b/doc/ru/3d-text.md index a53075ca2..c2d209434 100644 --- a/doc/ru/3d-text.md +++ b/doc/ru/3d-text.md @@ -79,6 +79,14 @@ gfx.text3d.set_rotation(id: int, rotation: mat4) Устанавливает вращение текста (Устанавливает повернутые вектора X,Y). +```lua +gfx.text3d.get_entity(id: int) -> int +gfx.text3d.set_entity(id: int, entity: int) +``` + +Геттер и сеттер сущности, к позиции которой привязан 3D текст. +Позиция, устанавливаемая через `gfx.text3d.set_pos`, в таком случае, относительна позиции сущности. + ```lua gfx.text3d.update_settings(id: int, preset: table) ``` From 711f6963d4d61d2bcc740349c5331be8987d7b31 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 19:36:36 +0300 Subject: [PATCH 20/32] add VC_HEADLESS constant --- src/logic/scripting/lua/lua_engine.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 5edb24fd9..bfca17328 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -144,6 +144,9 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { ); lua::pushstring(main_thread, params.scriptFile.stem().u8string()); lua::setglobal(main_thread, "__VC_SCRIPT_NAME"); + + lua::pushboolean(main_thread, params.headless); + lua::setglobal(main_thread, "VC_HEADLESS"); } void lua::finalize() { From fc80feeb840f4a37111edec934d6e56ea2c84ca4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 19:36:48 +0300 Subject: [PATCH 21/32] test removing client-side logic in headless (core:drop component) --- res/content/base/scripts/components/drop.lua | 90 +++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index 2b798a386..c3d629828 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -2,7 +2,6 @@ local tsf = entity.transform local body = entity.rigidbody local rig = entity.skeleton -inair = true target = -1 timer = 0.3 @@ -20,40 +19,59 @@ if SAVED_DATA.item then dropitem.data = SAVED_DATA.data end -local DROP_SCALE = 0.3 -local scale = {1, 1, 1} -local rotation = mat4.rotate({ - math.random(), math.random(), math.random() -}, 360) - function on_save() SAVED_DATA.item = item.name(dropitem.id) SAVED_DATA.count = dropitem.count SAVED_DATA.data = dropitem.data end -do -- setup visuals - local matrix = mat4.idt() - rig:set_model(0, item.model_name(dropitem.id)) - local bodysize = math.min(scale[1], scale[2], scale[3]) * DROP_SCALE - body:set_size({scale[1] * DROP_SCALE, bodysize, scale[3] * DROP_SCALE}) - mat4.mul(matrix, rotation, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) +if not VC_HEADLESS then + local SCALE = 0.3 + local scale = {1, 1, 1} + local rotation = mat4.rotate({ + math.random(), math.random(), math.random() + }, 360) + + do + local matrix = mat4.idt() + rig:set_model(0, item.model_name(dropitem.id)) + local bodysize = math.min(scale[1], scale[2], scale[3]) * SCALE + body:set_size({scale[1] * SCALE, bodysize, scale[3] * SCALE}) + mat4.mul(matrix, rotation, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + end + + local inair = true + + function on_grounded() + local matrix = mat4.idt() + mat4.rotate(matrix, {0, 1, 0}, math.random() * 360, matrix) + mat4.rotate(matrix, {1, 0, 0}, 90, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + inair = false + end + + function on_fall() + inair = true + end + + function on_render() + if inair then + local dt = time.delta(); + + mat4.rotate(rotation, {0, 1, 0}, 240*dt, rotation) + mat4.rotate(rotation, {0, 0, 1}, 240*dt, rotation) + + local matrix = mat4.idt() + mat4.mul(matrix, rotation, matrix) + mat4.scale(matrix, scale, matrix) + rig:set_matrix(0, matrix) + end + end end -function on_grounded(force) - local matrix = mat4.idt() - mat4.rotate(matrix, {0, 1, 0}, math.random() * 360, matrix) - mat4.rotate(matrix, {1, 0, 0}, 90, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) - inair = false -end - -function on_fall() - inair = true -end function on_sensor_enter(index, oid) local other = entities.get(oid) @@ -85,7 +103,9 @@ function on_sensor_enter(index, oid) if timer < 0.0 and index == 0 then entity:despawn() inventory.add(player.get_inventory(pid), dropitem.id, dropitem.count, dropitem.data) - audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") + if not VC_HEADLESS then + audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") + end end if index == 1 then target = oid @@ -98,20 +118,6 @@ function on_sensor_exit(index, oid) end end -function on_render() - if inair then - local dt = time.delta(); - - mat4.rotate(rotation, {0, 1, 0}, 240*dt, rotation) - mat4.rotate(rotation, {0, 0, 1}, 240*dt, rotation) - - local matrix = mat4.idt() - mat4.mul(matrix, rotation, matrix) - mat4.scale(matrix, scale, matrix) - rig:set_matrix(0, matrix) - end -end - function on_update(tps) timer = timer - 1.0/tps From 2afadb4ae6b5b3159a5ba4130e93dc910cf73b80 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 21:06:29 +0300 Subject: [PATCH 22/32] test removing client-side logic in headless (base pack scripts/components) --- res/content/base/scripts/components/drop.lua | 9 ++---- .../base/scripts/components/falling_block.lua | 4 +-- .../scripts/components/player_animator.lua | 2 -- res/content/base/scripts/grass_block.lua | 32 +++++++++++-------- res/content/base/scripts/world.lua | 2 +- res/scripts/components/player.lua | 2 +- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index c3d629828..f8ca6c9c7 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -26,7 +26,6 @@ function on_save() end if not VC_HEADLESS then - local SCALE = 0.3 local scale = {1, 1, 1} local rotation = mat4.rotate({ math.random(), math.random(), math.random() @@ -35,8 +34,8 @@ if not VC_HEADLESS then do local matrix = mat4.idt() rig:set_model(0, item.model_name(dropitem.id)) - local bodysize = math.min(scale[1], scale[2], scale[3]) * SCALE - body:set_size({scale[1] * SCALE, bodysize, scale[3] * SCALE}) + local bodysize = math.min(scale[1], scale[2], scale[3]) + body:set_size({scale[1], bodysize, scale[3]}) mat4.mul(matrix, rotation, matrix) mat4.scale(matrix, scale, matrix) rig:set_matrix(0, matrix) @@ -103,9 +102,7 @@ function on_sensor_enter(index, oid) if timer < 0.0 and index == 0 then entity:despawn() inventory.add(player.get_inventory(pid), dropitem.id, dropitem.count, dropitem.data) - if not VC_HEADLESS then - audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") - end + audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") end if index == 1 then target = oid diff --git a/res/content/base/scripts/components/falling_block.lua b/res/content/base/scripts/components/falling_block.lua index b6bbe4656..9bb02e25a 100644 --- a/res/content/base/scripts/components/falling_block.lua +++ b/res/content/base/scripts/components/falling_block.lua @@ -1,6 +1,5 @@ local tsf = entity.transform local body = entity.rigidbody -local rig = entity.skeleton local blockid = ARGS.block local blockstates = ARGS.states or 0 @@ -12,7 +11,8 @@ else SAVED_DATA.states = blockstates end -do -- setup visuals +if not VC_HEADLESS then-- setup visuals + local rig = entity.skeleton local id = block.index(blockid) local rotation = block.decompose_state(blockstates)[1] local textures = block.get_textures(id) diff --git a/res/content/base/scripts/components/player_animator.lua b/res/content/base/scripts/components/player_animator.lua index f82927ccd..6bd118906 100644 --- a/res/content/base/scripts/components/player_animator.lua +++ b/res/content/base/scripts/components/player_animator.lua @@ -1,5 +1,3 @@ -local tsf = entity.transform -local body = entity.rigidbody local rig = entity.skeleton local mob = entity:require_component("core:mob") diff --git a/res/content/base/scripts/grass_block.lua b/res/content/base/scripts/grass_block.lua index e58597cc4..fb7482c20 100644 --- a/res/content/base/scripts/grass_block.lua +++ b/res/content/base/scripts/grass_block.lua @@ -1,20 +1,26 @@ +local offsets = {} +for lx=-1,1 do + for ly=-1,1 do + for lz=-1,1 do + offsets[(lx * 3 + ly) * 3 + lz] = {lx, ly, lz} + end + end +end + function on_random_update(x, y, z) local dirtid = block.index('base:dirt'); if block.is_solid_at(x, y+1, z) then block.set(x, y, z, dirtid, 0) - else - local grassblockid = block.index('base:grass_block') - for lx=-1,1 do - for ly=-1,1 do - for lz=-1,1 do - if block.get(x + lx, y + ly, z + lz) == dirtid then - if not block.is_solid_at(x + lx, y + ly + 1, z + lz) then - block.set(x + lx, y + ly, z + lz, grassblockid, 0) - return - end - end - end - end + return + end + local grassblockid = block.index('base:grass_block') + for _, offset in ipairs(offsets) do + local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3] + + if block.get(nx, ny, nz) == dirtid and + not block.is_solid_at(nx, ny + 1, nz) then + block.set(nx, ny, nz, grassblockid, 0) + return end end end diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 7f468c291..9698eef54 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -1,5 +1,5 @@ function on_block_broken(id, x, y, z, playerid) - if gfx then + if not VC_HEADLESS then local size = {block.get_size(id)} gfx.particles.emit({ x + size[1] * 0.5, diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua index 49a03377f..96b92fe81 100644 --- a/res/scripts/components/player.lua +++ b/res/scripts/components/player.lua @@ -80,7 +80,7 @@ function on_physics_update(delta) local front = mat4.mul(rot, {0, 0, -1}) local pos = tsf:get_pos() - if hud and pid == hud.get_player() then + if not VC_HEADLESS and pid == hud.get_player() then process_player_inputs(pid, rot, delta) end mob.look_at(vec3.add(pos, front)) From dcaf87988bfac2b622eba4003ff7c52b41088601 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 22:04:40 +0300 Subject: [PATCH 23/32] fix components parent environment --- src/content/ContentPack.hpp | 8 ++++++++ src/logic/scripting/scripting_entities.cpp | 14 +++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index 06a8cf4dc..37dbe6b1b 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -114,6 +114,14 @@ struct ContentPack { default: return ""; } } + + static std::string_view getPrefix(std::string_view path) { + size_t pos = path.find(':'); + if (pos == std::string::npos) { + return ""; + } + return path.substr(0, pos); + } }; struct WorldFuncsSet { diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 32a396b25..7e286c713 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -1,5 +1,6 @@ #include "scripting.hpp" +#include "debug/Logger.hpp" #include "lua/lua_engine.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" @@ -11,6 +12,7 @@ using namespace scripting; +static debug::Logger logger("scripting-entities"); static inline const std::string STDCOMP = "stdcomp"; [[nodiscard]] static scriptenv create_component_environment( @@ -61,9 +63,19 @@ static void create_component( const dv::value& args, const dv::value& saved ) { + auto parentEnv = get_root_environment(); + auto prefix = ContentPack::getPrefix(component.name); + const auto& content = *scripting::content; + if (auto runtime = content.getPackRuntime(std::string(prefix))) { + parentEnv = runtime->getEnvironment(); + } else { + logger.warning() << "pack environment is not available for " + << component.name; + } + logger.debug() << "creating instance of component " << component.name; lua::pushvalue(L, entityIdx); auto compenv = create_component_environment( - get_root_environment(), -1, component.name + std::move(parentEnv), -1, component.name ); lua::get_from(L, lua::CHUNKS_TABLE, component.name, true); lua::pushenv(L, *compenv); From 84a47b92987278e5f5771aad0452c82de47bf855 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 22:53:34 +0300 Subject: [PATCH 24/32] cleanup --- src/content/ContentPack.cpp | 161 +++++++++++---------- src/content/ContentPack.hpp | 1 - src/logic/scripting/scripting_entities.cpp | 34 +++-- 3 files changed, 106 insertions(+), 90 deletions(-) diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index d2ffdc9e5..ec7168a28 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -47,10 +47,6 @@ io::path ContentPack::getContentFile() const { return folder / CONTENT_FILENAME; } -bool ContentPack::is_pack(const io::path& folder) { - return io::is_regular_file(folder / PACKAGE_FILENAME); -} - std::optional ContentPack::loadStats() const { auto contentFile = getContentFile(); if (!io::exists(contentFile)) { @@ -85,6 +81,9 @@ static void checkContentPackId(const std::string& id, const io::path& folder) { ); } } + if (id == "none") { + throw contentpack_error(id, folder, "content-pack id is not specified"); + } if (std::find( ContentPack::RESERVED_NAMES.begin(), ContentPack::RESERVED_NAMES.end(), @@ -94,10 +93,71 @@ static void checkContentPackId(const std::string& id, const io::path& folder) { } } +static DependencyPack parse_dependency(std::string depName) { + auto level = DependencyLevel::REQUIRED; + switch (depName.at(0)) { + case '!': + depName = depName.substr(1); + break; + case '?': + depName = depName.substr(1); + level = DependencyLevel::OPTIONAL; + break; + case '~': + depName = depName.substr(1); + level = DependencyLevel::WEAK; + break; + } + + std::string depVer = "*"; + std::string depVerOperator = "="; + + size_t versionPos = depName.rfind("@"); + if (versionPos != std::string::npos) { + depVer = depName.substr(versionPos + 1); + depName = depName.substr(0, versionPos); + + if (depVer.size() >= 2) { + std::string op = depVer.substr(0, 2); + std::uint8_t op_size = 0; + + // Two symbol operators + if (op == ">=" || op == "<=") { + op_size = 2; + depVerOperator = op; + } + + // One symbol operators + else { + op = depVer.substr(0, 1); + + if (op == ">" || op == "<") { + op_size = 1; + depVerOperator = op; + } + } + + depVer = depVer.substr(op_size); + } else { + if (depVer == ">" || depVer == "<"){ + depVer = "*"; + } + } + } + + VersionOperator versionOperator; + if (VersionOperatorMeta.getItem(depVerOperator, versionOperator)) { + return DependencyPack{level, depName, depVer, versionOperator}; + } else { + throw std::runtime_error("invalid version operator"); + } +} + ContentPack ContentPack::read(const io::path& folder) { auto root = io::read_json(folder / PACKAGE_FILENAME); ContentPack pack; root.at("id").get(pack.id); + checkContentPackId(pack.id, folder); root.at("title").get(pack.title); root.at("version").get(pack.version); if (root.has("creators")) { @@ -115,81 +175,26 @@ ContentPack ContentPack::read(const io::path& folder) { root.at("source").get(pack.source); pack.folder = folder; + auto dependenciesList = root.at("dependencies"); + if (!dependenciesList) { + return pack; + } + if (auto found = root.at("dependencies")) { const auto& dependencies = *found; for (const auto& elem : dependencies) { std::string depName = elem.asString(); - auto level = DependencyLevel::REQUIRED; - switch (depName.at(0)) { - case '!': - depName = depName.substr(1); - break; - case '?': - depName = depName.substr(1); - level = DependencyLevel::OPTIONAL; - break; - case '~': - depName = depName.substr(1); - level = DependencyLevel::WEAK; - break; - } - - std::string depVer = "*"; - std::string depVerOperator = "="; - - size_t versionPos = depName.rfind("@"); - if (versionPos != std::string::npos) { - depVer = depName.substr(versionPos + 1); - depName = depName.substr(0, versionPos); - - if (depVer.size() >= 2) { - std::string op = depVer.substr(0, 2); - std::uint8_t op_size = 0; - - // Two symbol operators - if (op == ">=" || op == "<=") { - op_size = 2; - depVerOperator = op; - } - - // One symbol operators - else { - op = depVer.substr(0, 1); - - if (op == ">" || op == "<") { - op_size = 1; - depVerOperator = op; - } - } - - depVer = depVer.substr(op_size); - } else { - if (depVer == ">" || depVer == "<"){ - depVer = "*"; - } - } - } - - VersionOperator versionOperator; - if (VersionOperatorMeta.getItem(depVerOperator, versionOperator)) { - pack.dependencies.push_back( - {level, depName, depVer, versionOperator} - ); - } else { + try { + pack.dependencies.push_back(parse_dependency(std::move(depName))); + } catch (const std::runtime_error& err) { throw contentpack_error( - pack.id, folder, "invalid version operator" + pack.id, + folder, + "dependency parsing error: " + std::string(err.what()) ); } } } - - if (pack.id == "none") { - throw contentpack_error( - pack.id, folder, "content-pack id is not specified" - ); - } - checkContentPackId(pack.id, folder); - return pack; } @@ -200,15 +205,21 @@ void ContentPack::scanFolder( return; } for (const auto& packFolder : io::directory_iterator(folder)) { - if (!io::is_directory(packFolder)) continue; - if (!is_pack(packFolder)) continue; + if (!io::is_directory(packFolder)) { + continue; + } + auto packageFile = packFolder / PACKAGE_FILENAME; + if (!io::is_regular_file(packageFile)) { + continue; + } try { packs.push_back(read(packFolder)); } catch (const contentpack_error& err) { - std::cerr << "package.json error at " << err.getFolder().string(); - std::cerr << ": " << err.what() << std::endl; + logger.warning() << "package.json error at " + << err.getFolder().string() << ": " << err.what(); } catch (const std::runtime_error& err) { - std::cerr << err.what() << std::endl; + logger.error() << "reading " << packageFile.string() + << " error: " << err.what(); } } } diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index 37dbe6b1b..cd5f93dbc 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -85,7 +85,6 @@ struct ContentPack { static inline const io::path GENERATORS_FOLDER = "generators"; static const std::vector RESERVED_NAMES; - static bool is_pack(const io::path& folder); static ContentPack read(const io::path& folder); static void scanFolder( diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 7e286c713..656cbbba6 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -56,6 +56,24 @@ dv::value scripting::get_component_value( return nullptr; } +static void init_component_events( + lua::State* L, const scriptenv& env, UserComponent& component +) { + lua::pushenv(L, *env); + auto& funcsset = component.funcsset; + funcsset.on_grounded = lua::hasfield(L, "on_grounded"); + funcsset.on_fall = lua::hasfield(L, "on_fall"); + funcsset.on_despawn = lua::hasfield(L, "on_despawn"); + funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter"); + funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit"); + funcsset.on_save = lua::hasfield(L, "on_save"); + funcsset.on_aim_on = lua::hasfield(L, "on_aim_on"); + funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); + funcsset.on_attacked = lua::hasfield(L, "on_attacked"); + funcsset.on_used = lua::hasfield(L, "on_used"); + lua::pop(L); +} + static void create_component( lua::State* L, int entityIdx, @@ -72,7 +90,6 @@ static void create_component( logger.warning() << "pack environment is not available for " << component.name; } - logger.debug() << "creating instance of component " << component.name; lua::pushvalue(L, entityIdx); auto compenv = create_component_environment( std::move(parentEnv), -1, component.name @@ -109,19 +126,8 @@ static void create_component( lua::setfenv(L); lua::call_nothrow(L, 0, 0); - lua::pushenv(L, *compenv); - auto& funcsset = component.funcsset; - funcsset.on_grounded = lua::hasfield(L, "on_grounded"); - funcsset.on_fall = lua::hasfield(L, "on_fall"); - funcsset.on_despawn = lua::hasfield(L, "on_despawn"); - funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter"); - funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit"); - funcsset.on_save = lua::hasfield(L, "on_save"); - funcsset.on_aim_on = lua::hasfield(L, "on_aim_on"); - funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); - funcsset.on_attacked = lua::hasfield(L, "on_attacked"); - funcsset.on_used = lua::hasfield(L, "on_used"); - lua::pop(L, 2); + init_component_events(L, compenv, component); + lua::pop(L); component.env = compenv; } From 66413531ff734cb2dde6fd6e037cae2bf67a12e3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 19 Apr 2026 23:41:11 +0300 Subject: [PATCH 25/32] move $L, $C tables to registry --- src/logic/scripting/lua/lua_engine.cpp | 4 +- src/logic/scripting/lua/lua_util.cpp | 16 +-- src/logic/scripting/lua/lua_util.hpp | 110 +++++++++++++++------ src/logic/scripting/scripting.cpp | 2 +- src/logic/scripting/scripting_entities.cpp | 2 +- 5 files changed, 94 insertions(+), 40 deletions(-) diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index bfca17328..c27f31293 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -123,10 +123,10 @@ void lua::init_state(State* L, StateType stateType) { setglobal(L, env_name(0)); createtable(L, 0, 0); - setglobal(L, LAMBDAS_TABLE); + setregistry(L, LAMBDAS_TABLE); createtable(L, 0, 0); - setglobal(L, CHUNKS_TABLE); + setregistry(L, CHUNKS_TABLE); initialize_libs_extends(L); diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index 6fdbe91ea..a563239a7 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -220,7 +220,7 @@ void lua::dump_stack(State* L) { static std::shared_ptr create_lambda_handler(State* L) { auto ptr = reinterpret_cast(topointer(L, -1)); auto name = util::mangleid(ptr); - requireglobal(L, LAMBDAS_TABLE); + reguireregistry(L, LAMBDAS_TABLE); pushvalue(L, -2); setfield(L, name); pop(L, 2); @@ -229,7 +229,7 @@ static std::shared_ptr create_lambda_handler(State* L) { new std::string(name), [=](std::string* name) { auto L = lua::get_main_state(); - requireglobal(L, LAMBDAS_TABLE); + reguireregistry(L, LAMBDAS_TABLE); pushnil(L); setfield(L, *name); pop(L); @@ -242,8 +242,9 @@ runnable lua::create_runnable(State* L) { auto funcptr = create_lambda_handler(L); return [=]() { auto L = lua::get_main_state(); - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return; + } call_nothrow(L, 0, 0); pop(L); }; @@ -252,8 +253,9 @@ runnable lua::create_runnable(State* L) { KeyCallback lua::create_simple_handler(State* L) { auto funcptr = create_lambda_handler(L); return [=]() -> bool { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return false; + } int top = gettop(L) - 1; if (call_nothrow(L, 0)) { int nres = gettop(L) - top; @@ -271,8 +273,9 @@ KeyCallback lua::create_simple_handler(State* L) { scripting::common_func lua::create_lambda(State* L) { auto funcptr = create_lambda_handler(L); return [=](const std::vector& args) -> dv::value { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return nullptr; + } int top = gettop(L) - 1; for (const auto& arg : args) { pushvalue(L, arg); @@ -294,8 +297,9 @@ scripting::common_func lua::create_lambda(State* L) { scripting::common_func lua::create_lambda_nothrow(State* L) { auto funcptr = create_lambda_handler(L); return [=](const std::vector& args) -> dv::value { - if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) + if (!get_from_registry(L, LAMBDAS_TABLE, *funcptr, false)) { return nullptr; + } int top = gettop(L) - 1; for (const auto& arg : args) { pushvalue(L, arg); diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index f87853256..c7fa60cca 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -20,15 +20,27 @@ namespace lua { std::string env_name(int env); void dump_stack(lua::State*); - inline bool getglobal(lua::State* L, const std::string& name) { - lua_getglobal(L, name.c_str()); - if (isnil(L, -1)) { + inline bool isnoneornil(lua::State* L, int idx) { + return lua_isnoneornil(L, idx); + } + + inline bool getfield(lua::State* L, const std::string& name, int idx = -1) { + lua_getfield(L, idx, name.c_str()); + if (isnoneornil(L, -1)) { pop(L); return false; } return true; } + inline bool getglobal(lua::State* L, const std::string& name) { + return getfield(L, name, LUA_GLOBALSINDEX); + } + + inline bool getregistry(lua::State* L, const std::string& name) { + return getfield(L, name, LUA_REGISTRYINDEX); + } + inline int requireglobal(lua::State* L, const std::string& name) { if (getglobal(L, name)) { return 1; @@ -37,6 +49,14 @@ namespace lua { } } + inline int reguireregistry(lua::State* L, const std::string& name) { + if (getregistry(L, name)) { + return 1; + } else { + throw std::runtime_error("registry entry " + name + " not found"); + } + } + inline bool hasglobal(lua::State* L, const std::string& name) { lua_getglobal(L, name.c_str()); if (isnil(L, -1)) { @@ -203,9 +223,6 @@ namespace lua { inline int pushglobals(lua::State* L) { return pushvalue(L, LUA_GLOBALSINDEX); } - inline bool isnoneornil(lua::State* L, int idx) { - return lua_isnoneornil(L, idx); - } inline bool isboolean(lua::State* L, int idx) { return lua_isboolean(L, idx); } @@ -267,6 +284,9 @@ namespace lua { inline void setglobal(lua::State* L, const std::string& name) { lua_setglobal(L, name.c_str()); } + inline void setregistry(lua::State* L, const std::string& key) { + lua_setfield(L, LUA_REGISTRYINDEX, key.c_str()); + } template inline T* touserdata(lua::State* L, int idx) { if (void* rawptr = lua_touserdata(L, idx)) { @@ -461,15 +481,6 @@ namespace lua { [[nodiscard]] dv::value tovalue(lua::State*, int idx); - inline bool getfield(lua::State* L, const std::string& name, int idx = -1) { - lua_getfield(L, idx, name.c_str()); - if (isnoneornil(L, -1)) { - pop(L); - return false; - } - return true; - } - inline int requirefield( lua::State* L, const std::string& name, int idx = -1 ) { @@ -551,7 +562,46 @@ namespace lua { setfield(L, name); pop(L, 2); } else { - throw std::runtime_error("table " + tableName + " not found"); + throw std::runtime_error("global table " + tableName + " not found"); + } + } + + inline void store_in_registry( + lua::State* L, const std::string& tableName, const std::string& name + ) { + if (getregistry(L, tableName)) { + pushvalue(L, -2); + setfield(L, name); + pop(L, 2); + } else { + throw std::runtime_error("table " + tableName + " not found in registry"); + } + } + + inline int get_from( + lua::State* L, + const std::string& tableName, + const std::string& name, + bool required, + int idx, + std::string_view context + ) { + if (getfield(L, tableName, idx)) { + if (getfield(L, name)) { + return 1; + } else if (required) { + pop(L); + throw std::runtime_error( + std::string(context) + " table " + tableName + + " has no member " + name + ); + } + pop(L); + return 0; + } else { + throw std::runtime_error( + std::string(context) + " table " + tableName + " not found" + ); } } @@ -561,20 +611,20 @@ namespace lua { const std::string& name, bool required = false ) { - if (getglobal(L, tableName)) { - if (getfield(L, name)) { - return 1; - } else if (required) { - pop(L); - throw std::runtime_error( - "table " + tableName + " has no member " + name - ); - } - pop(L); - return 0; - } else { - throw std::runtime_error("table " + tableName + " not found"); - } + return get_from( + L, tableName, name, required, LUA_GLOBALSINDEX, "global" + ); + } + + inline int get_from_registry( + lua::State* L, + const std::string& tableName, + const std::string& name, + bool required = false + ) { + return get_from( + L, tableName, name, required, LUA_REGISTRYINDEX, "registry" + ); } int call(lua::State*, int argc, int nresults = -1); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 341470c17..bf9227390 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -742,7 +742,7 @@ void scripting::load_entity_component( std::string src = io::read_string(file); logger.info() << "script (component) " << file.string(); lua::loadbuffer(L, *env, src, fileName); - lua::store_in(L, lua::CHUNKS_TABLE, name); + lua::store_in_registry(L, lua::CHUNKS_TABLE, name); } void scripting::load_world_script( diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 656cbbba6..375b382a9 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -94,7 +94,7 @@ static void create_component( auto compenv = create_component_environment( std::move(parentEnv), -1, component.name ); - lua::get_from(L, lua::CHUNKS_TABLE, component.name, true); + lua::get_from_registry(L, lua::CHUNKS_TABLE, component.name, true); lua::pushenv(L, *compenv); if (args != nullptr) { From 4403df78d99d871632f0e6a6a94ed3901d868199 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 26 Apr 2026 17:38:48 +0300 Subject: [PATCH 26/32] feat: bytearray support in gfx.posteffects.set_array --- doc/en/scripting/builtins/libgfx-posteffects.md | 4 ++-- doc/ru/scripting/builtins/libgfx-posteffects.md | 6 +++--- src/logic/scripting/lua/lua_util.hpp | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/doc/en/scripting/builtins/libgfx-posteffects.md b/doc/en/scripting/builtins/libgfx-posteffects.md index c624d557c..5e36683c4 100644 --- a/doc/en/scripting/builtins/libgfx-posteffects.md +++ b/doc/en/scripting/builtins/libgfx-posteffects.md @@ -41,8 +41,8 @@ gfx.posteffects.set_array( slot: int, -- parameter (array) name name: str - -- data string (use Bytearray_as_string function) - data: str + -- data + data: Bytearray ) -- Example of filling an array `#param vec3 u_arrayName[64]`: diff --git a/doc/ru/scripting/builtins/libgfx-posteffects.md b/doc/ru/scripting/builtins/libgfx-posteffects.md index 01c13d888..64fb6c064 100644 --- a/doc/ru/scripting/builtins/libgfx-posteffects.md +++ b/doc/ru/scripting/builtins/libgfx-posteffects.md @@ -41,8 +41,8 @@ gfx.posteffects.set_array( slot: int, -- имя параметра (массива) name: string - -- строка данных (используйте функцию Bytearray_as_string) - data: string + -- данные + data: Bytearray ) -- Пример заполнения массива `#param vec3 u_имяМассива[64]`: @@ -54,4 +54,4 @@ for i = 0, 63 do Bytearray.append(buffer, byteutil.pack("fff", x, y, z)) end gfx.posteffects.set_array(slot, "u_имяМассива", Bytearray_as_string(buffer)) -``` \ No newline at end of file +``` diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index f87853256..236eaf401 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -770,7 +770,10 @@ namespace lua { } inline std::string_view bytearray_as_string(lua::State* L, int idx) { - lua::pushvalue(L, idx); + if (lua::type(L, idx) == LUA_TSTRING) { + return lua::tolstring(L, idx); + } + lua::pushvalue(L, idx); lua::requireglobal(L, "Bytearray_as_string"); lua::pushvalue(L, -2); lua::call(L, 1, 1); From 0cec5057f772e9f82ff11cc1a20a78cba47cd5bf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 26 Apr 2026 18:35:56 +0300 Subject: [PATCH 27/32] do it completely --- src/logic/scripting/lua/libs/libposteffects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logic/scripting/lua/libs/libposteffects.cpp b/src/logic/scripting/lua/libs/libposteffects.cpp index 6eedaa6c8..3d714203b 100644 --- a/src/logic/scripting/lua/libs/libposteffects.cpp +++ b/src/logic/scripting/lua/libs/libposteffects.cpp @@ -65,7 +65,7 @@ static int l_set_params(lua::State* L) { static int l_set_array(lua::State* L) { size_t index = static_cast(lua::tointeger(L, 1)); auto key = lua::require_string(L, 2); - auto data = lua::require_lstring(L, 3); + auto data = lua::bytearray_as_string(L, 3); auto effect = post_processing->getEffect(index); if (effect == nullptr) { return 0; From 678e5db03d1822581457f4d48fae1fa880aa2a6f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 26 Apr 2026 22:39:41 +0300 Subject: [PATCH 28/32] add vc.is_headless, vc.is_client --- res/content/base/scripts/components/drop.lua | 2 +- .../base/scripts/components/falling_block.lua | 2 +- res/content/base/scripts/world.lua | 2 +- res/scripts/components/player.lua | 2 +- res/scripts/stdmin.lua | 13 +++++++++++++ src/logic/scripting/lua/lua_engine.cpp | 4 ++-- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index f8ca6c9c7..39bd03ceb 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -25,7 +25,7 @@ function on_save() SAVED_DATA.data = dropitem.data end -if not VC_HEADLESS then +if vc.is_client() then local scale = {1, 1, 1} local rotation = mat4.rotate({ math.random(), math.random(), math.random() diff --git a/res/content/base/scripts/components/falling_block.lua b/res/content/base/scripts/components/falling_block.lua index 9bb02e25a..01c5bc5f2 100644 --- a/res/content/base/scripts/components/falling_block.lua +++ b/res/content/base/scripts/components/falling_block.lua @@ -11,7 +11,7 @@ else SAVED_DATA.states = blockstates end -if not VC_HEADLESS then-- setup visuals +if vc.is_client() then-- setup visuals local rig = entity.skeleton local id = block.index(blockid) local rotation = block.decompose_state(blockstates)[1] diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 9698eef54..419dcc769 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -1,5 +1,5 @@ function on_block_broken(id, x, y, z, playerid) - if not VC_HEADLESS then + if vc.is_client() then local size = {block.get_size(id)} gfx.particles.emit({ x + size[1] * 0.5, diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua index f033a2998..4f2ec53a8 100644 --- a/res/scripts/components/player.lua +++ b/res/scripts/components/player.lua @@ -80,7 +80,7 @@ function on_physics_update(delta) local front = mat4.mul(rot, {0, 0, -1}) local pos = tsf:get_pos() - if not VC_HEADLESS and pid == hud.get_player() then + if vc.is_client() and pid == hud.get_player() then process_player_inputs(pid, rot, delta) end mob.look_at(vec3.add(pos, front)) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 4ae34b2fd..be437513e 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,5 +1,18 @@ +local _vc_headless = __VC_HEADLESS +__VC_HEADLESS = nil + +vc = { + is_headless = function() + return _vc_headless + end, + is_client = function () + return not _vc_headless + end +} + local _ffi = ffi local _debug_getinfo = debug.getinfo +local _crc32 = crc32 function crc32(bytes, chksum) chksum = chksum or 0 diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index c27f31293..07fdfe8d2 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -95,7 +95,7 @@ static void create_libs(State* L, StateType stateType) { } addfunc(L, "print", lua::wrap); - addfunc(L, "_crc32", lua::wrap); + addfunc(L, "crc32", lua::wrap); } void lua::init_state(State* L, StateType stateType) { @@ -146,7 +146,7 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { lua::setglobal(main_thread, "__VC_SCRIPT_NAME"); lua::pushboolean(main_thread, params.headless); - lua::setglobal(main_thread, "VC_HEADLESS"); + lua::setglobal(main_thread, "__VC_HEADLESS"); } void lua::finalize() { From ab179881e8458f763887a0bef865338430267b2d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 26 Apr 2026 23:30:11 +0300 Subject: [PATCH 29/32] remove __vc_app global variable --- res/modules/internal/gui_util.lua | 4 +- res/scripts/hud.lua | 36 ------------- res/scripts/stdlib.lua | 73 +++++++++++++++++++------- res/scripts/stdmin.lua | 3 +- src/logic/scripting/lua/lua_engine.cpp | 11 ++-- 5 files changed, 63 insertions(+), 64 deletions(-) diff --git a/res/modules/internal/gui_util.lua b/res/modules/internal/gui_util.lua index 0f53faaba..0ec47291e 100644 --- a/res/modules/internal/gui_util.lua +++ b/res/modules/internal/gui_util.lua @@ -24,7 +24,7 @@ function gui_util.parse_query(query) end --- @param query string page query string -function gui_util.load_page(query) +function gui_util.load_page(app, query) local name, args = gui_util.parse_query(query) for i = #gui_util.local_dispatchers, 1, -1 do local newname, newargs = gui_util.local_dispatchers[i](name, args) @@ -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, { app = __vc_app }) + gui.load_document(filename, name, args, { app = app }) return name end end diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 322f03db5..562fe19d1 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -1,37 +1,3 @@ -local function configure_SSAO() - -- Temporary using slot to configure built-in SSAO effect - local slot = gfx.posteffects.index("core:ssao") - gfx.posteffects.set_effect(slot, "ssao") - - -- Generating random SSAO samples - local buffer = Bytearray(0) - for i = 0, 63 do - local x = math.random() * 2.0 - 1.0 - local y = math.random() * 2.0 - 1.0 - local z = math.random() * 2.0 - local len = math.sqrt(x * x + y * y + z * z) - if len > 0 then - x = x / len - y = y / len - z = z / len - end - Bytearray.append(buffer, byteutil.pack("fff", x, y, z)) - end - gfx.posteffects.set_array(slot, "u_ssaoSamples", Bytearray_as_string(buffer)) - - local function update_ssao_quality(value) - value = math.min(value, 3) - gfx.posteffects.set_params(slot, { - u_kernelSize = value * 16, - u_radius = 0.4 / value, - u_bias = 0.006 / value / value, - }) - end - events.on("core:setting.graphics.ssao.set", update_ssao_quality) - - update_ssao_quality(__vc_app.get_setting("graphics.ssao")) -end - local function update_hand() local skeleton = gfx.skeletons local pid = hud.get_player() @@ -123,8 +89,6 @@ function on_hud_open() end end) - configure_SSAO() - hud.default_hand_controller = update_hand end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 900b71b2e..3a7ecf02d 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -1,4 +1,5 @@ -local enable_experimental = __vc_app.get_setting("debug.enable-experimental") +local __app = __vc_app +local enable_experimental = __app.get_setting("debug.enable-experimental") ------------------------------------------------ ------ Extended kit of standard functions ------ @@ -119,12 +120,7 @@ local function complete_app_lib(app) end end -if app then - complete_app_lib(app) -elseif __vc_app then - complete_app_lib(__vc_app) -end - +complete_app_lib(__app) require "core:internal/maths_inline" require "core:internal/debugging" require "core:internal/audio_input" @@ -175,18 +171,18 @@ 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) + if vc.is_headless() then + return fullmsg end + gui.alert(fullmsg, function() + if world.is_open() then + __app.close_world() + else + __app.reset_content() + menu:reset() + menu.page = "main" + end + end) return fullmsg end) if not status then @@ -204,7 +200,7 @@ function __vc_start_app_script(path, name) if chunk == nil then error(err) end - local script_env = setmetatable({app = app or __vc_app}, {__index=_G}) + local script_env = setmetatable({app = __app}, {__index=_G}) chunk = setfenv(chunk, script_env) if name then start_coroutine(chunk, name) @@ -222,7 +218,7 @@ gui_util = require "core:internal/gui_util" Document = gui_util.Document Element = gui_util.Element RadioGroup = gui_util.RadioGroup -__vc_page_loader = gui_util.load_page +__vc_page_loader = function(...) return gui_util.load_page(__app, ...) end function __vc_get_document_node(docname, nodeid) return Element.new(docname, nodeid) @@ -384,6 +380,40 @@ math.randomseed(time.uptime() * 1536227939) rules = require "core:internal/rules" local _rules = rules +local function configure_SSAO() + -- Temporary using slot to configure built-in SSAO effect + local slot = gfx.posteffects.index("core:ssao") + gfx.posteffects.set_effect(slot, "ssao") + + -- Generating random SSAO samples + local buffer = Bytearray(0) + for i = 0, 63 do + local x = math.random() * 2.0 - 1.0 + local y = math.random() * 2.0 - 1.0 + local z = math.random() * 2.0 + local len = math.sqrt(x * x + y * y + z * z) + if len > 0 then + x = x / len + y = y / len + z = z / len + end + Bytearray.append(buffer, byteutil.pack("fff", x, y, z)) + end + gfx.posteffects.set_array(slot, "u_ssaoSamples", Bytearray_as_string(buffer)) + + local function update_ssao_quality(value) + value = math.min(value, 3) + gfx.posteffects.set_params(slot, { + u_kernelSize = value * 16, + u_radius = 0.4 / value, + u_bias = 0.006 / value / value, + }) + end + events.on("core:setting.graphics.ssao.set", update_ssao_quality) + + update_ssao_quality(__app.get_setting("graphics.ssao")) +end + function __vc_on_hud_open() local _hud_is_content_access = hud._is_content_access local _hud_set_content_access = hud._set_content_access @@ -446,6 +476,8 @@ function __vc_on_hud_open() end end) hud.open_permanent("core:ingame_chat") + + configure_SSAO() end local Schedule = require "core:schedule" @@ -590,4 +622,5 @@ end require "core:internal/deprecated" ffi = nil +__vc_app = nil __vc_lock_internal_modules() diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index be437513e..fa1ec0422 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -11,7 +11,6 @@ vc = { } local _ffi = ffi -local _debug_getinfo = debug.getinfo local _crc32 = crc32 function crc32(bytes, chksum) @@ -116,6 +115,8 @@ end ---------------------------------------------- +local _debug_getinfo = debug.getinfo + function debug.count_frames() local frames = 1 while true do diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 07fdfe8d2..5dfc8ebd1 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -64,12 +64,13 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "vec4", vec4lib); openlib(L, "yaml", yamllib); + openlib(L, "__vc_app", applib); + lua::getglobal(L, "__vc_app"); + lua::setregistry(L, "app"); + 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); + lua::getregistry(L, "app"); + lua::setglobal(L, "app"); } if (stateType == StateType::BASE || stateType == StateType::SCRIPT) { openlib(L, "assets", assetslib); From 629f672d38fff9ca8b730fb7d24863dffb847836 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 27 Apr 2026 00:24:25 +0300 Subject: [PATCH 30/32] cleanup internal functions --- res/scripts/stdlib.lua | 24 ++++++++++++++++-------- res/scripts/stdmin.lua | 18 ++++++++++++------ src/logic/scripting/lua/lua_engine.cpp | 2 ++ src/logic/scripting/lua/lua_util.cpp | 6 +++--- src/logic/scripting/lua/lua_util.hpp | 3 ++- src/logic/scripting/scripting.cpp | 8 ++------ 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 3a7ecf02d..0e72ad86d 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -540,20 +540,27 @@ function __vc_on_world_save() file.write(RULES_FILE, toml.tostring(rule_values)) end +local __close_all_descriptors = file.__close_all_descriptors +local __gui_util_reset_local = gui_util.__reset_local +local __stdcomp_reset = stdcomp.__reset +file.__close_all_descriptors = nil +gui_util.__reset_local = nil +stdcomp.reset = nil + function __vc_on_world_quit() _rules.clear() - gui_util:__reset_local() - stdcomp.__reset() - file.__close_all_descriptors() + __gui_util_reset_local() + __stdcomp_reset() + __close_all_descriptors() end -local __post_runnables = {} - local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer audio.__reset_fetch_buffer = nil core.get_core_token = audio.input.__get_core_token -local function __vc__process_post_runnables() +local __post_runnables = {} + +local function __process_post_runnables() if #__post_runnables > 0 then for _, func in ipairs(__post_runnables) do local status, result = xpcall(func, __vc__error) @@ -587,9 +594,9 @@ local function __vc__process_post_runnables() end end -function __process_post_runnables() +function __vc__process_post_runnables() __vc__is_post_runnable = true - local success, err = pcall(__vc__process_post_runnables) + local success, err = pcall(__process_post_runnables) if not success then debug.error("an error ocurred while processing post-runnables: ".. err) end @@ -624,3 +631,4 @@ require "core:internal/deprecated" ffi = nil __vc_app = nil __vc_lock_internal_modules() +__vc_lock_internal_modules = nil diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index fa1ec0422..70e27f561 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -192,7 +192,7 @@ function reload_module(name) end end -local internal_locked = false +local __internal_locked = false -- Load script with caching -- @@ -202,9 +202,12 @@ local internal_locked = false -- nocache - ignore cached script, load anyway function __load_script(path, nocache, env) local packname, filename = parse_path(path) + local is_internal = (packname == "res" or packname == "core") + and filename:find("modules/internal") == 1 - if internal_locked and (packname == "res" or packname == "core") - and filename:starts_with("modules/internal") then + nocache = nocache or is_internal + + if is_internal and __internal_locked then error("access to core:internal modules outside of [core]") end @@ -232,16 +235,19 @@ function __load_script(path, nocache, env) end function __vc_lock_internal_modules() - internal_locked = true + __internal_locked = true end +local __pack_envs = __vc__pack_envs +__vc__pack_envs = nil + function require(path) if not string.find(path, ':') then local prefix, _ = parse_path(_debug_getinfo(2).source) return require(prefix .. ':' .. path) end local prefix, file = parse_path(path) - local env = __vc__pack_envs[prefix] + local env = __pack_envs[prefix] return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env) end @@ -262,7 +268,7 @@ function __scripts_cleanup(non_reset_packs) __cached_scripts[k] = nil package.loaded[k] = nil end - __vc__pack_envs[packname] = nil + __pack_envs[packname] = nil ::continue:: end end diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 5dfc8ebd1..1536cb41f 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -113,7 +113,9 @@ void lua::init_state(State* L, StateType stateType) { setglobal(L, "io"); createtable(L, 0, 0); + pushvalue(L, -1); setglobal(L, "__vc__pack_envs"); + setregistry(L, lua::PACK_ENVS_TABLE); const char* removed_os[] { "execute", "exit", "remove", "rename", "setlocale", "tmpname", nullptr}; diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index a563239a7..51bfb4d65 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -220,7 +220,7 @@ void lua::dump_stack(State* L) { static std::shared_ptr create_lambda_handler(State* L) { auto ptr = reinterpret_cast(topointer(L, -1)); auto name = util::mangleid(ptr); - reguireregistry(L, LAMBDAS_TABLE); + requireregistry(L, LAMBDAS_TABLE); pushvalue(L, -2); setfield(L, name); pop(L, 2); @@ -229,7 +229,7 @@ static std::shared_ptr create_lambda_handler(State* L) { new std::string(name), [=](std::string* name) { auto L = lua::get_main_state(); - reguireregistry(L, LAMBDAS_TABLE); + requireregistry(L, LAMBDAS_TABLE); pushnil(L); setfield(L, *name); pop(L); @@ -341,7 +341,7 @@ int lua::create_environment(State* L, int parent) { } int lua::restore_pack_environment(lua::State* L, const std::string& packid) { - if(!lua::getglobal(L, "__vc__pack_envs")) { + if(!lua::requireregistry(L, lua::PACK_ENVS_TABLE)) { return -1; } int id = nextEnvironment++; diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index c7fa60cca..d6a45fecd 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -14,6 +14,7 @@ namespace lua { inline std::string LAMBDAS_TABLE = "$L"; // lambdas storage inline std::string CHUNKS_TABLE = "$C"; // precompiled lua chunks + inline std::string PACK_ENVS_TABLE = "$P"; extern std::unordered_map usertypeNames; int userdata_destructor(lua::State* L); @@ -49,7 +50,7 @@ namespace lua { } } - inline int reguireregistry(lua::State* L, const std::string& name) { + inline int requireregistry(lua::State* L, const std::string& name) { if (getregistry(L, name)) { return 1; } else { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index bf9227390..806f92c24 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -188,11 +188,7 @@ std::unique_ptr scripting::start_app_script(const io::path& script) { lua::pushstring(L, pack.id); lua::setfield(L, "PACK_ID"); - if(!lua::getglobal(L, "__vc__pack_envs")) { - lua::createtable(L, 0, 0); - lua::setglobal(L, "__vc__pack_envs"); - lua::pushvalue(L, -1); - } + lua::requireregistry(L, lua::PACK_ENVS_TABLE); lua::pushenv(L, id); lua::setfield(L, pack.id); lua::pop(L); @@ -248,7 +244,7 @@ std::unique_ptr scripting::start_app_script(const io::path& script) { void scripting::process_post_runnables() { auto L = lua::get_main_state(); - if (lua::getglobal(L, "__process_post_runnables")) { + if (lua::getglobal(L, "__vc__process_post_runnables")) { lua::call_nothrow(L, 0, 0); } } From ad1d7477adeb618f5e08b482b2b1f33c1b9d41ba Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 27 Apr 2026 02:16:24 +0300 Subject: [PATCH 31/32] cleanup global environment (_ENV* variables) --- src/logic/scripting/lua/libs/libgui.cpp | 5 +-- src/logic/scripting/lua/lua_engine.cpp | 8 +++-- src/logic/scripting/lua/lua_util.cpp | 32 ++++++++++------- src/logic/scripting/lua/lua_util.hpp | 46 +++++++++++++++---------- 4 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 382113171..0e4645adc 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -1010,8 +1010,9 @@ static int l_gui_get_env(lua::State* L) { "document '" + std::string(name) + "' not found" ); } - lua::getglobal(L, lua::env_name(*doc->getEnvironment())); - return 1; + return lua::getregistry( + L, lua::ENVS_TABLE, lua::env_name(*doc->getEnvironment()) + ); } static int l_gui_str(lua::State* L) { diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 1536cb41f..0143f6a9e 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -122,15 +122,17 @@ void lua::init_state(State* L, StateType stateType) { remove_lib_funcs(L, "os", removed_os); create_libs(L, stateType); - pushglobals(L); - setglobal(L, env_name(0)); - createtable(L, 0, 0); setregistry(L, LAMBDAS_TABLE); createtable(L, 0, 0); setregistry(L, CHUNKS_TABLE); + createtable(L, 0, 0); + pushglobals(L); + setfield(L, env_name(0)); + setregistry(L, ENVS_TABLE); + initialize_libs_extends(L); newusertype(L); diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index 51bfb4d65..ded9cc79b 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -8,7 +8,7 @@ using namespace lua; -static int nextEnvironment = 1; +static int next_environment = 1; std::unordered_map lua::usertypeNames; @@ -20,7 +20,7 @@ int lua::userdata_destructor(lua::State* L) { } std::string lua::env_name(int env) { - return "_ENV" + util::mangleid(env); + return std::to_string(env); } int lua::pushvalue(State* L, const dv::value& value) { @@ -317,8 +317,16 @@ scripting::common_func lua::create_lambda_nothrow(State* L) { }; } +static void store_env(lua::State* L, int id) { + requireregistry(L, ENVS_TABLE); + pushvalue(L, -2); + lua_remove(L, -3); + setfield(L, env_name(id)); + pop(L); +} + int lua::create_environment(State* L, int parent) { - int id = nextEnvironment++; + int id = next_environment++; // local env = {} createtable(L, 0, 1); @@ -335,24 +343,22 @@ int lua::create_environment(State* L, int parent) { setfield(L, "__index"); setmetatable(L); - // envname = env - setglobal(L, env_name(id)); + store_env(L, id); return id; } int lua::restore_pack_environment(lua::State* L, const std::string& packid) { - if(!lua::requireregistry(L, lua::PACK_ENVS_TABLE)) { + if(!requireregistry(L, PACK_ENVS_TABLE)) { return -1; } - int id = nextEnvironment++; + int id = next_environment++; - if (lua::getfield(L, packid)) { - // envname = env - setglobal(L, env_name(id)); - lua::pop(L); + if (getfield(L, packid)) { + store_env(L, id); + pop(L); return id; } - lua::pop(L); + pop(L); return -1; } @@ -361,5 +367,5 @@ void lua::remove_environment(State* L, int id) { return; } pushnil(L); - setglobal(L, env_name(id)); + store_env(L, id); } diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index d6a45fecd..9ae1e48f6 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -15,6 +15,7 @@ namespace lua { inline std::string LAMBDAS_TABLE = "$L"; // lambdas storage inline std::string CHUNKS_TABLE = "$C"; // precompiled lua chunks inline std::string PACK_ENVS_TABLE = "$P"; + inline std::string ENVS_TABLE = "$E"; extern std::unordered_map usertypeNames; int userdata_destructor(lua::State* L); @@ -58,6 +59,16 @@ namespace lua { } } + inline bool getregistry(lua::State* L, const std::string& name, const std::string& key) { + requireregistry(L, name); + if (getfield(L, key)) { + lua_remove(L, -2); + return true; + } + pop(L); + return false; + } + inline bool hasglobal(lua::State* L, const std::string& name) { lua_getglobal(L, name.c_str()); if (isnil(L, -1)) { @@ -550,7 +561,7 @@ namespace lua { if (luaL_loadbuffer(L, src.c_str(), src.length(), file.c_str())) { throw luaerror(tostring(L, -1)); } - if (env && getglobal(L, env_name(env))) { + if (env && getregistry(L, ENVS_TABLE, env_name(env))) { lua_setfenv(L, -2); } } @@ -658,10 +669,7 @@ namespace lua { scripting::common_func create_lambda_nothrow(lua::State*); inline int pushenv(lua::State* L, int env) { - if (getglobal(L, env_name(env))) { - return 1; - } - return 0; + return getregistry(L, ENVS_TABLE, env_name(env)); } int create_environment(lua::State*, int parent); int restore_pack_environment(lua::State*, const std::string& packid); @@ -790,14 +798,14 @@ namespace lua { inline void read_bytes_from_table( lua::State* L, int tableIndex, std::vector& bytes ) { - if (!lua::istable(L, tableIndex)) { + if (!istable(L, tableIndex)) { throw std::runtime_error("table expected"); } else { - size_t size = lua::objlen(L, tableIndex); + size_t size = objlen(L, tableIndex); for (size_t i = 0; i < size; i++) { - lua::rawgeti(L, i + 1, tableIndex); - const int byte = lua::tointeger(L, -1); - lua::pop(L); + rawgeti(L, i + 1, tableIndex); + const int byte = tointeger(L, -1); + pop(L); if (byte < 0 || byte > 255) { throw std::runtime_error( "invalid byte '" + std::to_string(byte) + "'" @@ -809,11 +817,11 @@ namespace lua { } inline int create_bytearray(lua::State* L, const void* bytes, size_t size) { - lua::requireglobal(L, "Bytearray_construct"); - lua::pushlstring( + requireglobal(L, "Bytearray_construct"); + pushlstring( L, std::string_view(reinterpret_cast(bytes), size) ); - return lua::call(L, 1, 1); + return call(L, 1, 1); } inline int create_bytearray(lua::State* L, const std::vector& bytes) { @@ -821,12 +829,12 @@ namespace lua { } inline std::string_view bytearray_as_string(lua::State* L, int idx) { - lua::pushvalue(L, idx); - lua::requireglobal(L, "Bytearray_as_string"); - lua::pushvalue(L, -2); - lua::call(L, 1, 1); - auto view = lua::tolstring(L, -1); - lua::pop(L, 2); + pushvalue(L, idx); + requireglobal(L, "Bytearray_as_string"); + pushvalue(L, -2); + call(L, 1, 1); + auto view = tolstring(L, -1); + pop(L, 2); return view; } } From bc7d3abeade838a781813924882e98e7d8ebc759 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 27 Apr 2026 02:16:46 +0300 Subject: [PATCH 32/32] add lua panic handler --- src/logic/scripting/lua/lua_engine.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 0143f6a9e..bd2f0359b 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -99,7 +99,15 @@ static void create_libs(State* L, StateType stateType) { addfunc(L, "crc32", lua::wrap); } +static int l_panic_handler(lua::State* L) { + logger.error() << "PANIC: unprotected error in call to Lua API: " << lua::tostring(L, -1); + logger.flush(); + abort(); +} + void lua::init_state(State* L, StateType stateType) { + lua_atpanic(L, l_panic_handler); + // Allowed standard libraries luaL_openlibs(L);