From b4b49ccd83f57187808ee5f2b836e6334a01807d Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sat, 26 Jul 2025 16:19:00 +0300 Subject: [PATCH 001/177] added custom caption to items. fixed glyph render. --- src/graphics/core/Font.cpp | 21 +++- src/graphics/ui/GUI.cpp | 2 +- src/graphics/ui/elements/InventoryView.cpp | 131 ++++++++++++--------- 3 files changed, 97 insertions(+), 57 deletions(-) diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp index 68cba17c..19abb9f0 100644 --- a/src/graphics/core/Font.cpp +++ b/src/graphics/core/Font.cpp @@ -57,6 +57,14 @@ static inline void draw_glyph( const FontStyle& style ) { for (int i = 0; i <= style.bold; i++) { + glm::vec4 color; + + if (style.color == glm::vec4(1, 1, 1, 1)) { + color = batch.getColor(); + } else { + color = style.color; + } + batch.sprite( pos.x + (offset.x + i / (right.x/glyphInterval/2.0f)) * right.x, pos.y + offset.y * right.y, @@ -65,7 +73,7 @@ static inline void draw_glyph( -0.15f * style.italic, 16, c, - batch.getColor() * style.color + color ); } } @@ -81,6 +89,15 @@ static inline void draw_glyph( const FontStyle& style ) { for (int i = 0; i <= style.bold; i++) { + glm::vec4 color; + + if (style.color == glm::vec4(1, 1, 1, 1)) { + color = batch.getColor(); + } else { + color = style.color; + } + + batch.sprite( pos + right * (offset.x + i) + up * offset.y, up, right / glyphInterval, @@ -88,7 +105,7 @@ static inline void draw_glyph( 0.5f, 16, c, - batch.getColor() * style.color + color ); } } diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 0cea8a66..ceb2f4a0 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -50,7 +50,7 @@ GUI::GUI(Engine& engine) tooltip = guiutil::create( *this, "" - "" + "" "" ); store("tooltip", tooltip); diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index b41dce98..cbb1dde6 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -1,10 +1,21 @@ #include "InventoryView.hpp" +#include +#include + #include "assets/Assets.hpp" #include "assets/assets_util.hpp" #include "content/Content.hpp" #include "frontend/LevelFrontend.hpp" #include "frontend/locale.hpp" +#include "graphics/core/Atlas.hpp" +#include "graphics/core/Batch2D.hpp" +#include "graphics/core/DrawContext.hpp" +#include "graphics/core/Font.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/Texture.hpp" +#include "graphics/render/BlocksPreview.hpp" +#include "graphics/ui/GUI.hpp" #include "items/Inventories.hpp" #include "items/Inventory.hpp" #include "items/ItemDef.hpp" @@ -15,17 +26,6 @@ #include "voxels/Block.hpp" #include "window/input.hpp" #include "world/Level.hpp" -#include "graphics/core/Atlas.hpp" -#include "graphics/core/Batch2D.hpp" -#include "graphics/core/Font.hpp" -#include "graphics/core/DrawContext.hpp" -#include "graphics/core/Shader.hpp" -#include "graphics/core/Texture.hpp" -#include "graphics/render/BlocksPreview.hpp" -#include "graphics/ui/GUI.hpp" - -#include -#include using namespace gui; @@ -37,21 +37,24 @@ SlotLayout::SlotLayout( slotcallback updateFunc, slotcallback shareFunc, slotcallback rightClick -) : index(index), - position(position), - background(background), - itemSource(itemSource), - updateFunc(std::move(updateFunc)), - shareFunc(std::move(shareFunc)), - rightClick(std::move(rightClick)) {} +) + : index(index), + position(position), + background(background), + itemSource(itemSource), + updateFunc(std::move(updateFunc)), + shareFunc(std::move(shareFunc)), + rightClick(std::move(rightClick)) { +} InventoryBuilder::InventoryBuilder(GUI& gui) : gui(gui) { view = std::make_shared(gui); } void InventoryBuilder::addGrid( - int cols, int count, - glm::vec2 pos, + int cols, + int count, + glm::vec2 pos, glm::vec4 padding, bool addpanel, const SlotLayout& slotLayout @@ -61,9 +64,11 @@ void InventoryBuilder::addGrid( int rows = ceildiv(count, cols); - uint width = cols * (slotSize + interval) - interval + padding.x + padding.z; - uint height = rows * (slotSize + interval) - interval + padding.y + padding.w; - + uint width = + cols * (slotSize + interval) - interval + padding.x + padding.z; + uint height = + rows * (slotSize + interval) - interval + padding.y + padding.w; + glm::vec2 vsize = view->getSize(); if (pos.x + width > vsize.x) { vsize.x = pos.x + width; @@ -85,7 +90,7 @@ void InventoryBuilder::addGrid( if (row * cols + col >= count) { break; } - glm::vec2 position ( + glm::vec2 position( col * (slotSize + interval) + padding.x, row * (slotSize + interval) + padding.y ); @@ -105,11 +110,9 @@ std::shared_ptr InventoryBuilder::build() { return view; } -SlotView::SlotView( - GUI& gui, SlotLayout layout -) : UINode(gui, glm::vec2(InventoryView::SLOT_SIZE)), - layout(std::move(layout)) -{ +SlotView::SlotView(GUI& gui, SlotLayout layout) + : UINode(gui, glm::vec2(InventoryView::SLOT_SIZE)), + layout(std::move(layout)) { setColor(glm::vec4(0, 0, 0, 0.2f)); setTooltipDelay(0.0f); } @@ -120,9 +123,16 @@ void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) { return; } if (itemid) { - tooltip = util::pascal_case( - langs::get(util::str2wstr_utf8(item.caption)) - ); + dv::value* caption = stack.getField("caption"); + if (caption != nullptr) { + tooltip = util::pascal_case( + langs::get(util::str2wstr_utf8(caption->asString())) + ); + } else { + tooltip = util::pascal_case( + langs::get(util::str2wstr_utf8(item.caption)) + ); + } } else { tooltip.clear(); } @@ -148,19 +158,37 @@ void SlotView::drawItemIcon( UVRegion region = previews.get(block.name); batch.rect( - pos.x, pos.y, SLOT_SIZE, SLOT_SIZE, - 0, 0, 0, region, false, true, tint + pos.x, + pos.y, + SLOT_SIZE, + SLOT_SIZE, + 0, + 0, + 0, + region, + false, + true, + tint ); break; } case ItemIconType::SPRITE: { auto textureRegion = util::get_texture_region(assets, item.icon, "blocks:notfound"); - + batch.texture(textureRegion.texture); batch.rect( - pos.x, pos.y, SLOT_SIZE, SLOT_SIZE, - 0, 0, 0, textureRegion.region, false, true, tint + pos.x, + pos.y, + SLOT_SIZE, + SLOT_SIZE, + 0, + 0, + 0, + textureRegion.region, + false, + true, + tint ); break; } @@ -184,7 +212,7 @@ void SlotView::draw(const DrawContext& pctx, const Assets& assets) { glm::vec4 tint(1, 1, 1, isEnabled() ? 1 : 0.5f); glm::vec2 pos = calcPos(); glm::vec4 color = getColor(); - + if (hover || highlighted) { tint *= 1.333f; color = glm::vec4(1, 1, 1, 0.2f); @@ -262,7 +290,7 @@ void SlotView::drawItemInfo( batch.setColor({0, 0, 0, 0.75f}); batch.rect(pos.x - 2, pos.y - 2, 6, SLOT_SIZE + 4); float t = static_cast(uses) / item.uses; - + int height = SLOT_SIZE * t; batch.setColor({(1.0f - t * 0.8f), 0.4f, t * 0.8f + 0.2f, 1.0f}); batch.rect(pos.x, pos.y + SLOT_SIZE - height, 2, height); @@ -317,8 +345,7 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) { } return; } - if (layout.itemSource) - return; + if (layout.itemSource) return; if (grabbed.isEmpty()) { if (!stack.isEmpty() && layout.taking) { grabbed.set(std::move(stack)); @@ -342,15 +369,15 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) { } else { grabbed = ItemStack(stack.getItemId(), count - 1); } - } else if (stack.accepts(grabbed) && stack.getCount() < stackDef.stackSize) { + } else if (stack.accepts(grabbed) && + stack.getCount() < stackDef.stackSize) { stack.setCount(stack.getCount() + 1); grabbed.setCount(grabbed.getCount() - 1); } } void SlotView::clicked(Mousecode button) { - if (bound == nullptr) - return; + if (bound == nullptr) return; auto exchangeSlot = std::dynamic_pointer_cast(gui.get(EXCHANGE_SLOT_NAME)); if (exchangeSlot == nullptr) { @@ -358,7 +385,7 @@ void SlotView::clicked(Mousecode button) { } ItemStack& grabbed = exchangeSlot->getStack(); ItemStack& stack = *bound; - + if (button == Mousecode::BUTTON_1) { performLeftClick(stack, grabbed); } else if (button == Mousecode::BUTTON_2) { @@ -382,9 +409,7 @@ const std::wstring& SlotView::getTooltip() const { } void SlotView::bind( - int64_t inventoryid, - ItemStack& stack, - const Content* content + int64_t inventoryid, ItemStack& stack, const Content* content ) { this->inventoryid = inventoryid; bound = &stack; @@ -403,11 +428,11 @@ InventoryView::InventoryView(GUI& gui) : Container(gui, glm::vec2()) { setColor(glm::vec4(0, 0, 0, 0.0f)); } -InventoryView::~InventoryView() {} - +InventoryView::~InventoryView() { +} std::shared_ptr InventoryView::addSlot(const SlotLayout& layout) { - uint width = InventoryView::SLOT_SIZE + layout.padding; + uint width = InventoryView::SLOT_SIZE + layout.padding; uint height = InventoryView::SLOT_SIZE + layout.padding; auto pos = layout.position; @@ -432,14 +457,12 @@ std::shared_ptr InventoryView::getInventory() const { return inventory; } - size_t InventoryView::getSlotsCount() const { return slots.size(); } void InventoryView::bind( - const std::shared_ptr& inventory, - const Content* content + const std::shared_ptr& inventory, const Content* content ) { this->inventory = inventory; this->content = content; From 21364af7a3986e63985f0fcf872d50b152dadd6b Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sat, 26 Jul 2025 16:49:39 +0300 Subject: [PATCH 002/177] added description to item --- res/scripts/stdlib.lua | 22 ++++++++++++++++++++++ src/graphics/ui/elements/InventoryView.cpp | 22 +++++++++++++++------- src/items/ItemDef.cpp | 1 + src/items/ItemDef.hpp | 3 +++ 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 0765199e..ebde9d12 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -128,6 +128,28 @@ function inventory.decrement(invid, slot, count) end end +function inventory.set_caption(invid, slot, caption) + local itemid, itemcount = inventory.get(invid, slot) + if itemid == 0 then + return + end + if caption == nil or type(caption) ~= "string" then + caption = "" + end + inventory.set_data(invid, slot, "caption", caption) +end + +function inventory.set_description(invid, slot, description) + local itemid, itemcount = inventory.get(invid, slot) + if itemid == 0 then + return + end + if description == nil or type(description) ~= "string" then + description = "" + end + inventory.set_data(invid, slot, "description", description) +end + ------------------------------------------------ ------------------- Events --------------------- ------------------------------------------------ diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index cbb1dde6..a02a49da 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -124,15 +124,23 @@ void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) { } if (itemid) { dv::value* caption = stack.getField("caption"); - if (caption != nullptr) { - tooltip = util::pascal_case( - langs::get(util::str2wstr_utf8(caption->asString())) - ); + dv::value* description = stack.getField("description"); + std::wstring captionText; + std::wstring descriptionText; + + if (description != nullptr) { + descriptionText = util::pascal_case( langs::get( util::str2wstr_utf8( description->asString() ) ) ); } else { - tooltip = util::pascal_case( - langs::get(util::str2wstr_utf8(item.caption)) - ); + descriptionText = util::pascal_case( langs::get( util::str2wstr_utf8( item.description ) ) ); } + + if (caption != nullptr) { + captionText = util::pascal_case( langs::get( util::str2wstr_utf8( caption->asString() ) ) ); + } else { + captionText = util::pascal_case( langs::get( util::str2wstr_utf8( item.caption ) ) ); + } + + tooltip = captionText + L"\n" + descriptionText; } else { tooltip.clear(); } diff --git a/src/items/ItemDef.cpp b/src/items/ItemDef.cpp index e2b54b81..8162323b 100644 --- a/src/items/ItemDef.cpp +++ b/src/items/ItemDef.cpp @@ -4,6 +4,7 @@ ItemDef::ItemDef(const std::string& name) : name(name) { caption = util::id_to_caption(name); + description = ""; } void ItemDef::cloneTo(ItemDef& dst) { dst.caption = caption; diff --git a/src/items/ItemDef.hpp b/src/items/ItemDef.hpp index 00870cc3..4e7e120c 100644 --- a/src/items/ItemDef.hpp +++ b/src/items/ItemDef.hpp @@ -34,6 +34,9 @@ struct ItemDef { /// @brief Item name will shown in inventory std::string caption; + /// @brief Item description will shown in inventory + std::string description; + dv::value properties = nullptr; /// @brief Item max stack size From b4d41a59e474c2780bc89c1dc1de30cb12eec2fe Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sat, 26 Jul 2025 16:59:58 +0300 Subject: [PATCH 003/177] added docs --- doc/en/scripting/builtins/libinventory.md | 20 ++++++++++++++++++++ doc/ru/scripting/builtins/libinventory.md | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/doc/en/scripting/builtins/libinventory.md b/doc/en/scripting/builtins/libinventory.md index 428761d5..dfd0b6de 100644 --- a/doc/en/scripting/builtins/libinventory.md +++ b/doc/en/scripting/builtins/libinventory.md @@ -97,6 +97,26 @@ inventory.set(...) inventory.set_all_data(...) ``` for moving is inefficient, use inventory.move or inventory.move_range. +```lua +-- Set item caption +inventory.set_caption( + -- id of inventory + invid: int, + -- slot id + slot: int, + -- Item Caption + caption: string +) +-- Set item description +inventory.set_description( + -- id of inventory + invid: int, + -- slot id + slot: int, + -- Item Description + description: string +) +``` ```lua -- Returns a copy of value of a local property of an item by name or nil. diff --git a/doc/ru/scripting/builtins/libinventory.md b/doc/ru/scripting/builtins/libinventory.md index 7e939ddd..c8440a3f 100644 --- a/doc/ru/scripting/builtins/libinventory.md +++ b/doc/ru/scripting/builtins/libinventory.md @@ -94,6 +94,26 @@ inventory.set(...) inventory.set_all_data(...) ``` для перемещения вляется неэффективным, используйте inventory.move или inventory.move_range. +```lua +-- Задает имя предмету в слоте +inventory.set_caption( + -- id инвентаря + invid: int, + -- индекс слота + slot: int, + -- Имя предмета + caption: string +) +-- Задает описание предмету в слоте +inventory.set_description( + -- id инвентаря + invid: int, + -- индекс слота + slot: int, + -- Описание предмета + description: string +) +``` ```lua -- Проверяет наличие локального свойства по имени без копирования его значения. From 59d706323a3811b8f19883f5491373cf93609867 Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sat, 26 Jul 2025 17:21:00 +0300 Subject: [PATCH 004/177] added item.description() to itemlib, and update docs --- doc/en/scripting/builtins/libinventory.md | 14 ++++++++++++++ doc/ru/scripting/builtins/libinventory.md | 14 ++++++++++++++ doc/ru/scripting/builtins/libitem.md | 3 +++ res/scripts/stdlib.lua | 16 ++++++++++++++++ src/logic/scripting/lua/libs/libitem.cpp | 8 ++++++++ 5 files changed, 55 insertions(+) diff --git a/doc/en/scripting/builtins/libinventory.md b/doc/en/scripting/builtins/libinventory.md index dfd0b6de..8cf2d7c3 100644 --- a/doc/en/scripting/builtins/libinventory.md +++ b/doc/en/scripting/builtins/libinventory.md @@ -98,6 +98,13 @@ inventory.set_all_data(...) ``` for moving is inefficient, use inventory.move or inventory.move_range. ```lua +-- Get item caption +inventory.get_caption( + -- id of inventory + invid: int, + -- slot id + slot: int +) -- Set item caption inventory.set_caption( -- id of inventory @@ -107,6 +114,13 @@ inventory.set_caption( -- Item Caption caption: string ) +-- Get item description +inventory.get_description( + -- id of inventory + invid: int, + -- slot id + slot: int +) -- Set item description inventory.set_description( -- id of inventory diff --git a/doc/ru/scripting/builtins/libinventory.md b/doc/ru/scripting/builtins/libinventory.md index c8440a3f..97fa56ff 100644 --- a/doc/ru/scripting/builtins/libinventory.md +++ b/doc/ru/scripting/builtins/libinventory.md @@ -95,6 +95,13 @@ inventory.set_all_data(...) ``` для перемещения вляется неэффективным, используйте inventory.move или inventory.move_range. ```lua +-- Получает имя предмета в слоте +inventory.get_caption( + -- id инвентаря + invid: int, + -- индекс слота + slot: int +) -- Задает имя предмету в слоте inventory.set_caption( -- id инвентаря @@ -104,6 +111,13 @@ inventory.set_caption( -- Имя предмета caption: string ) +-- Получает описание предмета в слоте +inventory.get_description( + -- id инвентаря + invid: int, + -- индекс слота + slot: int +) -- Задает описание предмету в слоте inventory.set_description( -- id инвентаря diff --git a/doc/ru/scripting/builtins/libitem.md b/doc/ru/scripting/builtins/libitem.md index 5baaed68..265bf099 100644 --- a/doc/ru/scripting/builtins/libitem.md +++ b/doc/ru/scripting/builtins/libitem.md @@ -10,6 +10,9 @@ item.index(name: str) -> int -- Возвращает название предмета, отображаемое в интерфейсе. item.caption(itemid: int) -> str +-- Возвращает описание предмета, отображаемое в интерфейсе. +item.description(itemid: int) -> str + -- Возвращает максимальный размер стопки для предмета. item.stack_size(itemid: int) -> int diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index ebde9d12..8f71f997 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -128,6 +128,14 @@ function inventory.decrement(invid, slot, count) end end +function inventory.get_caption(invid, slot) + local item_id, count = inventory.get(invid, slot) + local caption = inventory.get_data(invid, slot, "caption") + if not caption then return item.caption(item_id) end + + return caption +end + function inventory.set_caption(invid, slot, caption) local itemid, itemcount = inventory.get(invid, slot) if itemid == 0 then @@ -139,6 +147,14 @@ function inventory.set_caption(invid, slot, caption) inventory.set_data(invid, slot, "caption", caption) end +function inventory.get_description(invid, slot) + local item_id, count = inventory.get(invid, slot) + local description = inventory.get_data(invid, slot, "description") + if not description then return item.description(item_id) end + + return description +end + function inventory.set_description(invid, slot, description) local itemid, itemcount = inventory.get(invid, slot) if itemid == 0 then diff --git a/src/logic/scripting/lua/libs/libitem.cpp b/src/logic/scripting/lua/libs/libitem.cpp index 84cfc859..d70a7211 100644 --- a/src/logic/scripting/lua/libs/libitem.cpp +++ b/src/logic/scripting/lua/libs/libitem.cpp @@ -57,6 +57,13 @@ static int l_caption(lua::State* L) { return 0; } +static int l_description(lua::State* L) { + if (auto def = get_item_def(L, 1)) { + return lua::pushstring(L, def->description); + } + return 0; +} + static int l_placing_block(lua::State* L) { if (auto def = get_item_def(L, 1)) { return lua::pushinteger(L, def->rt.placingBlock); @@ -108,6 +115,7 @@ const luaL_Reg itemlib[] = { {"defs_count", lua::wrap}, {"icon", lua::wrap}, {"caption", lua::wrap}, + {"description", lua::wrap}, {"placing_block", lua::wrap}, {"model_name", lua::wrap}, {"emission", lua::wrap}, From 0caed92baa0665d5d9c8141c0107117bf1df639f Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sat, 26 Jul 2025 17:58:57 +0300 Subject: [PATCH 005/177] added docs and fixed desc loading from item def --- doc/en/item-properties.md | 8 ++++++++ doc/en/scripting/builtins/libitem.md | 3 +++ doc/ru/item-properties.md | 8 ++++++++ src/content/loading/ItemLoader.cpp | 1 + src/items/ItemDef.cpp | 1 + 5 files changed, 21 insertions(+) diff --git a/doc/en/item-properties.md b/doc/en/item-properties.md index 0240676b..8abcb93c 100644 --- a/doc/en/item-properties.md +++ b/doc/en/item-properties.md @@ -18,6 +18,14 @@ Name of the item model. The model will be loaded automatically. Default value is `packid:itemname.model`. If the model is not specified, an automatic one will be generated. +### Caption and Description +`caption` - name of item in inventory +`description` - item description in inventory + +this props allow to use `md` + +*see [Text Styles](/doc/en/text-styles.md)* + ## Behaviour ### *placing-block* diff --git a/doc/en/scripting/builtins/libitem.md b/doc/en/scripting/builtins/libitem.md index a2d01632..25a7e48f 100644 --- a/doc/en/scripting/builtins/libitem.md +++ b/doc/en/scripting/builtins/libitem.md @@ -10,6 +10,9 @@ item.index(name: str) -> int -- Returns the item display name. block.caption(blockid: int) -> str +-- Returns the item display description. +item.description(itemid: int) -> str + -- Returns max stack size for the item item.stack_size(itemid: int) -> int diff --git a/doc/ru/item-properties.md b/doc/ru/item-properties.md index 68803296..8a75ca72 100644 --- a/doc/ru/item-properties.md +++ b/doc/ru/item-properties.md @@ -17,6 +17,14 @@ Значение по-умолчанию - `packid:itemname.model`. Если модель не указана, будет сгенерирована автоматическию +### Имя и Описание +`caption` - имя предмета в инвентаре +`description` - описание предмета в инвентаре + +Можно использовать `md` + +*см. [Text Styles](/doc/en/text-styles.md)* + ## Поведение ### Устанавливаемый блок - `placing-block` diff --git a/src/content/loading/ItemLoader.cpp b/src/content/loading/ItemLoader.cpp index 6367ccd5..1703b778 100644 --- a/src/content/loading/ItemLoader.cpp +++ b/src/content/loading/ItemLoader.cpp @@ -29,6 +29,7 @@ template<> void ContentUnitLoader::loadUnit( parentDef->cloneTo(def); } root.at("caption").get(def.caption); + root.at("description").get(def.description); std::string iconTypeStr = ""; root.at("icon-type").get(iconTypeStr); diff --git a/src/items/ItemDef.cpp b/src/items/ItemDef.cpp index 8162323b..1d362ebd 100644 --- a/src/items/ItemDef.cpp +++ b/src/items/ItemDef.cpp @@ -8,6 +8,7 @@ ItemDef::ItemDef(const std::string& name) : name(name) { } void ItemDef::cloneTo(ItemDef& dst) { dst.caption = caption; + dst.description = description; dst.stackSize = stackSize; dst.generated = generated; std::copy(&emission[0], &emission[3], dst.emission); From f90e1b332115d1e565333858e18be914ee99b63a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 26 Jul 2025 18:43:00 +0300 Subject: [PATCH 006/177] begin the 0.29 development --- doc/en/main-page.md | 4 +++- doc/ru/main-page.md | 4 +++- res/content/base/package.json | 2 +- src/constants.hpp | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/en/main-page.md b/doc/en/main-page.md index 2443cd9a..b9a1044f 100644 --- a/doc/en/main-page.md +++ b/doc/en/main-page.md @@ -1,6 +1,8 @@ # Documentation -Documentation for release 0.28. +Documentation for 0.29 (in-development). + +[Documentation for release 0.28](https://github.com/MihailRis/voxelcore/blob/release-0.28/doc/en/main-page.md) ## Sections diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md index eda864e8..b823042c 100644 --- a/doc/ru/main-page.md +++ b/doc/ru/main-page.md @@ -1,6 +1,8 @@ # Документация -Документация версии 0.28. +Документация версии 0.29 (в разработке). + +[Документация версии 0.28](https://github.com/MihailRis/voxelcore/blob/release-0.28/doc/ru/main-page.md) ## Разделы diff --git a/res/content/base/package.json b/res/content/base/package.json index 73b8dcd0..4aeca4d9 100644 --- a/res/content/base/package.json +++ b/res/content/base/package.json @@ -1,6 +1,6 @@ { "id": "base", "title": "Base", - "version": "0.28", + "version": "0.29", "description": "basic content package" } diff --git a/src/constants.hpp b/src/constants.hpp index de954717..0a6df9e5 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 = 28; +inline constexpr int ENGINE_VERSION_MINOR = 29; #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.28"; +inline const std::string ENGINE_VERSION_STRING = "0.29"; /// @brief world regions format version inline constexpr uint REGION_FORMAT_VERSION = 3; From a784a68c44768ca28d83c795811c63c96f2e3466 Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sat, 26 Jul 2025 22:02:53 +0300 Subject: [PATCH 007/177] refactor tooltip handling in SlotView to improve caption and description retrieval --- src/graphics/ui/elements/InventoryView.cpp | 26 ++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index a02a49da..38361857 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -119,25 +119,37 @@ SlotView::SlotView(GUI& gui, SlotLayout layout) void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) { itemid_t itemid = stack.getItemId(); - if (itemid == cache.stack.getItemId()) { + dv::value* caption = stack.getField("caption"); + dv::value* description = stack.getField("description"); + if ( + itemid == cache.stack.getItemId() && + caption == cache.stack.getField("caption") && + description == cache.stack.getField("description") + ) { return; } if (itemid) { - dv::value* caption = stack.getField("caption"); - dv::value* description = stack.getField("description"); std::wstring captionText; std::wstring descriptionText; if (description != nullptr) { - descriptionText = util::pascal_case( langs::get( util::str2wstr_utf8( description->asString() ) ) ); + descriptionText = util::pascal_case( + langs::get(util::str2wstr_utf8(description->asString())) + ); } else { - descriptionText = util::pascal_case( langs::get( util::str2wstr_utf8( item.description ) ) ); + descriptionText = util::pascal_case( + langs::get(util::str2wstr_utf8(item.description)) + ); } if (caption != nullptr) { - captionText = util::pascal_case( langs::get( util::str2wstr_utf8( caption->asString() ) ) ); + captionText = util::pascal_case( + langs::get(util::str2wstr_utf8(caption->asString())) + ); } else { - captionText = util::pascal_case( langs::get( util::str2wstr_utf8( item.caption ) ) ); + captionText = util::pascal_case( + langs::get(util::str2wstr_utf8(item.caption)) + ); } tooltip = captionText + L"\n" + descriptionText; From d06a2081889ac90ca0f028fa544a2331635dc838 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 26 Jul 2025 23:53:27 +0300 Subject: [PATCH 008/177] cleanup __skeleton library --- .../scripting/lua/libs/lib__skeleton.cpp | 97 +++++++++---------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/src/logic/scripting/lua/libs/lib__skeleton.cpp b/src/logic/scripting/lua/libs/lib__skeleton.cpp index 2cb9c44e..208c3483 100644 --- a/src/logic/scripting/lua/libs/lib__skeleton.cpp +++ b/src/logic/scripting/lua/libs/lib__skeleton.cpp @@ -13,25 +13,30 @@ static int index_range_check( return static_cast(index); } -static int l_get_model(lua::State* L) { +static rigging::Skeleton* get_skeleton(lua::State* L) { if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - auto* rigConfig = skeleton.config; - auto index = index_range_check(skeleton, lua::tointeger(L, 2)); - const auto& modelOverride = skeleton.modelOverrides[index]; + return &entity->getSkeleton(); + } + return nullptr; +} + +static int l_get_model(lua::State* L) { + if (auto skeleton = get_skeleton(L)) { + auto& rigConfig = *skeleton->config; + auto index = index_range_check(*skeleton, lua::tointeger(L, 2)); + const auto& modelOverride = skeleton->modelOverrides[index]; if (!modelOverride.model) { return lua::pushstring(L, modelOverride.name); } - return lua::pushstring(L, rigConfig->getBones()[index]->model.name); + return lua::pushstring(L, rigConfig.getBones()[index]->model.name); } return 0; } static int l_set_model(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - auto index = index_range_check(skeleton, lua::tointeger(L, 2)); - auto& modelOverride = skeleton.modelOverrides[index]; + if (auto skeleton = get_skeleton(L)) { + auto index = index_range_check(*skeleton, lua::tointeger(L, 2)); + auto& modelOverride = skeleton->modelOverrides[index]; if (lua::isnoneornil(L, 3)) { modelOverride = {"", nullptr, true}; } else { @@ -42,28 +47,25 @@ static int l_set_model(lua::State* L) { } static int l_get_matrix(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - auto index = index_range_check(skeleton, lua::tointeger(L, 2)); - return lua::pushmat4(L, skeleton.pose.matrices[index]); + if (auto skeleton = get_skeleton(L)) { + auto index = index_range_check(*skeleton, lua::tointeger(L, 2)); + return lua::pushmat4(L, skeleton->pose.matrices[index]); } return 0; } static int l_set_matrix(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - auto index = index_range_check(skeleton, lua::tointeger(L, 2)); - skeleton.pose.matrices[index] = lua::tomat4(L, 3); + if (auto skeleton = get_skeleton(L)) { + auto index = index_range_check(*skeleton, lua::tointeger(L, 2)); + skeleton->pose.matrices[index] = lua::tomat4(L, 3); } return 0; } static int l_get_texture(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - const auto& found = skeleton.textures.find(lua::require_string(L, 2)); - if (found != skeleton.textures.end()) { + if (auto skeleton = get_skeleton(L)) { + const auto& found = skeleton->textures.find(lua::require_string(L, 2)); + if (found != skeleton->textures.end()) { return lua::pushstring(L, found->second); } } @@ -71,18 +73,16 @@ static int l_get_texture(lua::State* L) { } static int l_set_texture(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - skeleton.textures[lua::require_string(L, 2)] = + if (auto skeleton = get_skeleton(L)) { + skeleton->textures[lua::require_string(L, 2)] = lua::require_string(L, 3); } return 0; } static int l_index(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - if (auto bone = skeleton.config->find(lua::require_string(L, 2))) { + if (auto skeleton= get_skeleton(L)) { + if (auto bone = skeleton->config->find(lua::require_string(L, 2))) { return lua::pushinteger(L, bone->getIndex()); } } @@ -90,58 +90,52 @@ static int l_index(lua::State* L) { } static int l_is_visible(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); + if (auto skeleton = get_skeleton(L)) { if (!lua::isnoneornil(L, 2)) { - auto index = index_range_check(skeleton, lua::tointeger(L, 2)); - return lua::pushboolean(L, skeleton.flags.at(index).visible); + auto index = index_range_check(*skeleton, lua::tointeger(L, 2)); + return lua::pushboolean(L, skeleton->flags.at(index).visible); } - return lua::pushboolean(L, skeleton.visible); + return lua::pushboolean(L, skeleton->visible); } return 0; } static int l_set_visible(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); + if (auto skeleton = get_skeleton(L)) { if (!lua::isnoneornil(L, 3)) { - auto index = index_range_check(skeleton, lua::tointeger(L, 2)); - skeleton.flags.at(index).visible = lua::toboolean(L, 3); + auto index = index_range_check(*skeleton, lua::tointeger(L, 2)); + skeleton->flags.at(index).visible = lua::toboolean(L, 3); } else { - skeleton.visible = lua::toboolean(L, 2); + skeleton->visible = lua::toboolean(L, 2); } } return 0; } static int l_get_color(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - return lua::pushvec(L, skeleton.tint); + if (auto skeleton = get_skeleton(L)) { + return lua::pushvec(L, skeleton->tint); } return 0; } static int l_set_color(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - skeleton.tint = lua::tovec3(L, 2); + if (auto skeleton = get_skeleton(L)) { + skeleton->tint = lua::tovec3(L, 2); } return 0; } static int l_is_interpolated(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - return lua::pushboolean(L, skeleton.interpolation.isEnabled()); + if (auto skeleton = get_skeleton(L)) { + return lua::pushboolean(L, skeleton->interpolation.isEnabled()); } return 0; } static int l_set_interpolated(lua::State* L) { - if (auto entity = get_entity(L, 1)) { - auto& skeleton = entity->getSkeleton(); - skeleton.interpolation.setEnabled(lua::toboolean(L, 2)); + if (auto skeleton = get_skeleton(L)) { + skeleton->interpolation.setEnabled(lua::toboolean(L, 2)); } return 0; } @@ -160,4 +154,5 @@ const luaL_Reg skeletonlib[] = { {"set_color", lua::wrap}, {"is_interpolated", lua::wrap}, {"set_interpolated", lua::wrap}, - {NULL, NULL}}; + {NULL, NULL} +}; From 7893338b6773ff33009482448a499d69626d82cf Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sun, 27 Jul 2025 00:29:23 +0300 Subject: [PATCH 009/177] fix: update tooltip handling to display caption only when description is empty --- src/graphics/ui/elements/InventoryView.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index 38361857..2b7dc270 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -152,7 +152,11 @@ void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) { ); } - tooltip = captionText + L"\n" + descriptionText; + if (descriptionText.length() > 0) { + tooltip = captionText + L"\n" + descriptionText; + } else { + tooltip = captionText; + } } else { tooltip.clear(); } From d489dab013439f4379f1b3001bff93626d0b6279 Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sun, 27 Jul 2025 00:51:42 +0300 Subject: [PATCH 010/177] refactor: streamline tooltip handling by separating caption and description retrieval --- src/graphics/ui/elements/InventoryView.cpp | 63 +++++++++++----------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index 2b7dc270..be58dd50 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -116,46 +116,43 @@ SlotView::SlotView(GUI& gui, SlotLayout layout) setColor(glm::vec4(0, 0, 0, 0.2f)); setTooltipDelay(0.0f); } +// TODO: Refactor +std::wstring get_caption_string(const ItemStack& stack, const ItemDef& item) { + dv::value* caption = stack.getField("caption"); + if (caption != nullptr) { + return util::pascal_case( + langs::get(util::str2wstr_utf8(caption->asString())) + ); + } else { + return util::pascal_case(langs::get(util::str2wstr_utf8(item.caption))); + } +} +// TODO: Refactor +std::wstring get_description_string(const ItemStack& stack, const ItemDef& item) { + dv::value* description = stack.getField("description"); + + if (description != nullptr) { + return langs::get(util::str2wstr_utf8(description->asString())); + } else { + return langs::get(util::str2wstr_utf8(item.description)); + } +} void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) { itemid_t itemid = stack.getItemId(); - dv::value* caption = stack.getField("caption"); - dv::value* description = stack.getField("description"); - if ( - itemid == cache.stack.getItemId() && - caption == cache.stack.getField("caption") && - description == cache.stack.getField("description") - ) { + std::wstring caption = get_caption_string(stack, item); + std::wstring cached_caption = get_caption_string(cache.stack, item); + std::wstring description = get_description_string(stack, item); + std::wstring cached_description = get_description_string(cache.stack, item); + + if (itemid == cache.stack.getItemId() && cached_caption == caption && cached_description == description) { return; } if (itemid) { - std::wstring captionText; - std::wstring descriptionText; - - if (description != nullptr) { - descriptionText = util::pascal_case( - langs::get(util::str2wstr_utf8(description->asString())) - ); + if (description.length() > 0) { + tooltip = caption + L"\n" + description; } else { - descriptionText = util::pascal_case( - langs::get(util::str2wstr_utf8(item.description)) - ); - } - - if (caption != nullptr) { - captionText = util::pascal_case( - langs::get(util::str2wstr_utf8(caption->asString())) - ); - } else { - captionText = util::pascal_case( - langs::get(util::str2wstr_utf8(item.caption)) - ); - } - - if (descriptionText.length() > 0) { - tooltip = captionText + L"\n" + descriptionText; - } else { - tooltip = captionText; + tooltip = caption; } } else { tooltip.clear(); From 8bdf6d36ff61bd5c11219c86ae6a6e9ef09faf45 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 27 Jul 2025 01:01:37 +0300 Subject: [PATCH 011/177] update workflows --- .github/workflows/appimage.yml | 4 ++-- .github/workflows/macos.yml | 4 ++-- .github/workflows/windows-clang.yml | 4 ++-- .github/workflows/windows.yml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index f5224c8f..5a01a618 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -2,9 +2,9 @@ name: x86-64 AppImage on: push: - branches: [ "main", "release-**"] + branches: [ "main", "dev", "release-**"] pull_request: - branches: [ "main" ] + branches: [ "main", "dev" ] jobs: build-appimage: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3af2ee8b..f8fe3dfa 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -2,9 +2,9 @@ name: Macos Build on: push: - branches: [ "main", "release-**"] + branches: [ "main", "dev", "release-**"] pull_request: - branches: [ "main" ] + branches: [ "main", "dev" ] jobs: build-dmg: diff --git a/.github/workflows/windows-clang.yml b/.github/workflows/windows-clang.yml index 1356149f..cdf354dd 100644 --- a/.github/workflows/windows-clang.yml +++ b/.github/workflows/windows-clang.yml @@ -2,9 +2,9 @@ name: Windows Build (CLang) on: push: - branches: [ "main", "release-**"] + branches: [ "main", "dev", "release-**"] pull_request: - branches: [ "main" ] + branches: [ "main", "dev" ] jobs: build-windows: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 3f7efc9c..e5ee5a69 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -2,9 +2,9 @@ name: MSVC Build on: push: - branches: [ "main", "release-**"] + branches: [ "main", "dev", "release-**"] pull_request: - branches: [ "main" ] + branches: [ "main", "dev" ] jobs: build-windows: From 1e270d4adbbf4ec6bb6e314f15120d8ff224a364 Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sun, 27 Jul 2025 02:26:05 +0300 Subject: [PATCH 012/177] refactor: enhance tooltip handling by making caption and description retrieval static --- src/graphics/ui/elements/InventoryView.cpp | 38 +++++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index be58dd50..563d36a7 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -117,18 +117,24 @@ SlotView::SlotView(GUI& gui, SlotLayout layout) setTooltipDelay(0.0f); } // TODO: Refactor -std::wstring get_caption_string(const ItemStack& stack, const ItemDef& item) { +static std::wstring get_caption_string( + const ItemStack& stack, const ItemDef& item +) { dv::value* caption = stack.getField("caption"); if (caption != nullptr) { return util::pascal_case( langs::get(util::str2wstr_utf8(caption->asString())) ); } else { - return util::pascal_case(langs::get(util::str2wstr_utf8(item.caption))); + return util::pascal_case( + langs::get(util::str2wstr_utf8(item.caption)) + ); } } // TODO: Refactor -std::wstring get_description_string(const ItemStack& stack, const ItemDef& item) { +static std::wstring get_description_string( + const ItemStack& stack, const ItemDef& item +) { dv::value* description = stack.getField("description"); if (description != nullptr) { @@ -138,17 +144,33 @@ std::wstring get_description_string(const ItemStack& stack, const ItemDef& item) } } +static bool is_same_tooltip(const ItemStack& stack, const ItemStack& cache) { + if (stack.getItemId() != cache.getItemId()) { + return false; + } + auto caption = stack.getField("caption"); + auto cCaption = cache.getField("caption"); + auto description = stack.getField("description"); + auto cDescription = cache.getField("description"); + + if (((caption != nullptr) != (description != nullptr)) || + ((description != nullptr) != (cDescription != nullptr))) { + return false; + } + return (caption ? caption->asString() == cCaption->asString() : true) && + (description ? description->asString() == cDescription->asString() + : true); +} + void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) { itemid_t itemid = stack.getItemId(); - std::wstring caption = get_caption_string(stack, item); - std::wstring cached_caption = get_caption_string(cache.stack, item); - std::wstring description = get_description_string(stack, item); - std::wstring cached_description = get_description_string(cache.stack, item); - if (itemid == cache.stack.getItemId() && cached_caption == caption && cached_description == description) { + if (is_same_tooltip(stack, cache.stack)) { return; } if (itemid) { + std::wstring caption = get_caption_string(stack, item); + std::wstring description = get_description_string(stack, item); if (description.length() > 0) { tooltip = caption + L"\n" + description; } else { From 5632c9ccfead7210bf2f71ccc523783a5f43787e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 27 Jul 2025 02:45:25 +0300 Subject: [PATCH 013/177] add hand skeleton --- res/content/base/config/defaults.toml | 1 + res/content/base/skeletons/hand.json | 9 +++ src/content/Content.cpp | 8 +++ src/content/Content.hpp | 1 + src/graphics/render/HandsRenderer.cpp | 94 +++++++++++++++++++++++++ src/graphics/render/HandsRenderer.hpp | 32 +++++++++ src/graphics/render/NamedSkeletons.cpp | 23 ++++++ src/graphics/render/NamedSkeletons.hpp | 23 ++++++ src/graphics/render/WorldRenderer.cpp | 96 +++++++++----------------- src/graphics/render/WorldRenderer.hpp | 6 +- 10 files changed, 226 insertions(+), 67 deletions(-) create mode 100644 res/content/base/skeletons/hand.json create mode 100644 src/graphics/render/HandsRenderer.cpp create mode 100644 src/graphics/render/HandsRenderer.hpp create mode 100644 src/graphics/render/NamedSkeletons.cpp create mode 100644 src/graphics/render/NamedSkeletons.hpp diff --git a/res/content/base/config/defaults.toml b/res/content/base/config/defaults.toml index a099c198..650c24d7 100644 --- a/res/content/base/config/defaults.toml +++ b/res/content/base/config/defaults.toml @@ -1,2 +1,3 @@ generator = "base:demo" player-entity = "base:player" +hand-skeleton = "base:hand" diff --git a/res/content/base/skeletons/hand.json b/res/content/base/skeletons/hand.json new file mode 100644 index 00000000..8ff9d841 --- /dev/null +++ b/res/content/base/skeletons/hand.json @@ -0,0 +1,9 @@ +{ + "root": { + "nodes": [ + { + "name": "item" + } + ] + } +} diff --git a/src/content/Content.cpp b/src/content/Content.cpp index ce92be18..5ee68859 100644 --- a/src/content/Content.cpp +++ b/src/content/Content.cpp @@ -63,6 +63,14 @@ const rigging::SkeletonConfig* Content::getSkeleton(const std::string& id return found->second.get(); } +const rigging::SkeletonConfig& Content::requireSkeleton(const std::string& id) const { + auto skeleton = getSkeleton(id); + if (skeleton == nullptr) { + throw std::runtime_error("skeleton '" + id + "' not loaded"); + } + return *skeleton; +} + const BlockMaterial* Content::findBlockMaterial(const std::string& id) const { auto found = blockMaterials.find(id); if (found == blockMaterials.end()) { diff --git a/src/content/Content.hpp b/src/content/Content.hpp index 539325a8..81e816b6 100644 --- a/src/content/Content.hpp +++ b/src/content/Content.hpp @@ -212,6 +212,7 @@ public: } const rigging::SkeletonConfig* getSkeleton(const std::string& id) const; + const rigging::SkeletonConfig& requireSkeleton(const std::string& id) const; const BlockMaterial* findBlockMaterial(const std::string& id) const; const ContentPackRuntime* getPackRuntime(const std::string& id) const; ContentPackRuntime* getPackRuntime(const std::string& id); diff --git a/src/graphics/render/HandsRenderer.cpp b/src/graphics/render/HandsRenderer.cpp new file mode 100644 index 00000000..aa5666f1 --- /dev/null +++ b/src/graphics/render/HandsRenderer.cpp @@ -0,0 +1,94 @@ +#include "HandsRenderer.hpp" + +#include +#include + +#include "ModelBatch.hpp" +#include "assets/Assets.hpp" +#include "content/Content.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/commons/Model.hpp" +#include "items/Inventory.hpp" +#include "items/ItemStack.hpp" +#include "items/ItemDef.hpp" +#include "objects/Player.hpp" +#include "objects/rigging.hpp" +#include "world/Level.hpp" +#include "window/Camera.hpp" +#include "window/Window.hpp" + +using namespace rigging; + +HandsRenderer::HandsRenderer( + const Assets& assets, + const Level& level, + const Player& player, + ModelBatch& modelBatch, + std::shared_ptr skeleton +) + : assets(assets), + level(level), + player(player), + modelBatch(modelBatch), + skeleton(std::move(skeleton)) { +} + +void HandsRenderer::renderHands( + const Camera& camera, float delta +) { + // configure model matrix + const glm::vec3 itemOffset(0.06f, 0.035f, -0.1); + + static glm::mat4 prevRotation(1.0f); + + const float speed = 24.0f; + glm::mat4 matrix = glm::translate(glm::mat4(1.0f), itemOffset); + matrix = glm::scale(matrix, glm::vec3(0.1f)); + glm::mat4 rotation = camera.rotation; + + // rotation interpolation + glm::quat rot0 = glm::quat_cast(prevRotation); + glm::quat rot1 = glm::quat_cast(rotation); + glm::quat finalRot = + glm::slerp(rot0, rot1, static_cast(delta * speed)); + rotation = glm::mat4_cast(finalRot); + prevRotation = rotation; + + // building matrix + matrix = rotation * matrix * + glm::rotate( + glm::mat4(1.0f), -glm::pi() * 0.5f, glm::vec3(0, 1, 0) + ); + + // getting offset + glm::vec3 cameraRotation = player.getRotation(); + auto offset = -(camera.position - player.getPosition()); + float angle = glm::radians(cameraRotation.x - 90); + float cos = glm::cos(angle); + float sin = glm::sin(angle); + + float newX = offset.x * cos - offset.z * sin; + float newZ = offset.x * sin + offset.z * cos; + offset = glm::vec3(newX, offset.y, newZ); + matrix = matrix * glm::translate(glm::mat4(1.0f), offset); + + // get current chosen item + auto indices = level.content.getIndices(); + const auto& inventory = player.getInventory(); + int slot = player.getChosenSlot(); + const ItemStack& stack = inventory->getSlot(slot); + const auto& def = indices->items.require(stack.getItemId()); + + auto& skeleton = *this->skeleton; + const auto& config = *skeleton.config; + + auto itemBone = config.find("item"); + size_t itemBoneIndex = itemBone->getIndex(); + skeleton.modelOverrides.at(itemBoneIndex).model = assets.get(def.modelName); + skeleton.pose.matrices.at(itemBoneIndex) = matrix; + + // render + modelBatch.setLightsOffset(camera.position); + config.update(skeleton, glm::mat4(1.0f), glm::vec3()); + config.render(assets, modelBatch, skeleton, glm::mat4(1.0f), glm::vec3()); +} diff --git a/src/graphics/render/HandsRenderer.hpp b/src/graphics/render/HandsRenderer.hpp new file mode 100644 index 00000000..156035eb --- /dev/null +++ b/src/graphics/render/HandsRenderer.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +class Assets; +class Camera; +class Level; +class Player; +class ModelBatch; + +namespace rigging { + struct Skeleton; +} + +class HandsRenderer { +public: + HandsRenderer( + const Assets& assets, + const Level& level, + const Player& player, + ModelBatch& modelBatch, + std::shared_ptr skeleton + ); + + void renderHands(const Camera& camera, float delta); +private: + const Assets& assets; + const Level& level; + const Player& player; + ModelBatch& modelBatch; + std::shared_ptr skeleton; +}; diff --git a/src/graphics/render/NamedSkeletons.cpp b/src/graphics/render/NamedSkeletons.cpp new file mode 100644 index 00000000..35dd57ec --- /dev/null +++ b/src/graphics/render/NamedSkeletons.cpp @@ -0,0 +1,23 @@ +#include "NamedSkeletons.hpp" + +#include "objects/rigging.hpp" + +using namespace rigging; + +NamedSkeletons::NamedSkeletons() = default; + +std::shared_ptr NamedSkeletons::createSkeleton( + const std::string& name, const SkeletonConfig* config +) { + auto skeleton = std::make_shared(config); + skeletons[name] = skeleton; + return skeleton; +} + +rigging::Skeleton* NamedSkeletons::getSkeleton(const std::string& name) { + const auto& found = skeletons.find(name); + if (found == skeletons.end()) { + return nullptr; + } + return found->second.get(); +} diff --git a/src/graphics/render/NamedSkeletons.hpp b/src/graphics/render/NamedSkeletons.hpp new file mode 100644 index 00000000..4e4fb77a --- /dev/null +++ b/src/graphics/render/NamedSkeletons.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace rigging { + struct Skeleton; + class SkeletonConfig; +} + +class NamedSkeletons { +public: + NamedSkeletons(); + + std::shared_ptr createSkeleton( + const std::string& name, const rigging::SkeletonConfig* config + ); + + rigging::Skeleton* getSkeleton(const std::string& name); +private: + std::unordered_map> skeletons; +}; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index e8511fa5..594d39be 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -47,6 +47,8 @@ #include "BlockWrapsRenderer.hpp" #include "ParticlesRenderer.hpp" #include "PrecipitationRenderer.hpp" +#include "HandsRenderer.hpp" +#include "NamedSkeletons.hpp" #include "TextsRenderer.hpp" #include "ChunksRenderer.hpp" #include "GuidesRenderer.hpp" @@ -107,6 +109,19 @@ WorldRenderer::WorldRenderer( settings.graphics.skyboxResolution.get(), assets->require("skybox_gen") ); + + const auto& content = level.content; + skeletons = std::make_unique(); + const auto& skeletonConfig = content.requireSkeleton( + content.getDefaults()["hand-skeleton"].asString() + ); + hands = std::make_unique( + *assets, + level, + player, + *modelBatch, + skeletons->createSkeleton("hand", &skeletonConfig) + ); } WorldRenderer::~WorldRenderer() = default; @@ -273,70 +288,6 @@ void WorldRenderer::renderLines( } } -void WorldRenderer::renderHands( - const Camera& camera, float delta -) { - auto& entityShader = assets.require("entity"); - auto indices = level.content.getIndices(); - - // get current chosen item - const auto& inventory = player.getInventory(); - int slot = player.getChosenSlot(); - const ItemStack& stack = inventory->getSlot(slot); - const auto& def = indices->items.require(stack.getItemId()); - - // prepare modified HUD camera - Camera hudcam = camera; - hudcam.far = 10.0f; - hudcam.setFov(0.9f); - hudcam.position = {}; - - // configure model matrix - const glm::vec3 itemOffset(0.06f, 0.035f, -0.1); - - static glm::mat4 prevRotation(1.0f); - - const float speed = 24.0f; - glm::mat4 matrix = glm::translate(glm::mat4(1.0f), itemOffset); - matrix = glm::scale(matrix, glm::vec3(0.1f)); - glm::mat4 rotation = camera.rotation; - glm::quat rot0 = glm::quat_cast(prevRotation); - glm::quat rot1 = glm::quat_cast(rotation); - glm::quat finalRot = - glm::slerp(rot0, rot1, static_cast(delta * speed)); - rotation = glm::mat4_cast(finalRot); - matrix = rotation * matrix * - glm::rotate( - glm::mat4(1.0f), -glm::pi() * 0.5f, glm::vec3(0, 1, 0) - ); - prevRotation = rotation; - glm::vec3 cameraRotation = player.getRotation(); - auto offset = -(camera.position - player.getPosition()); - float angle = glm::radians(cameraRotation.x - 90); - float cos = glm::cos(angle); - float sin = glm::sin(angle); - - float newX = offset.x * cos - offset.z * sin; - float newZ = offset.x * sin + offset.z * cos; - offset = glm::vec3(newX, offset.y, newZ); - matrix = matrix * glm::translate(glm::mat4(1.0f), offset); - - // render - modelBatch->setLightsOffset(camera.position); - modelBatch->draw( - matrix, - glm::vec3(1.0f), - assets.get(def.modelName), - nullptr - ); - display::clearDepth(); - setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f); - skybox->bind(); - modelBatch->render(); - modelBatch->setLightsOffset(glm::vec3()); - skybox->unbind(); -} - void WorldRenderer::generateShadowsMap( const Camera& camera, const DrawContext& pctx, @@ -568,7 +519,22 @@ void WorldRenderer::draw( DrawContext ctx = pctx.sub(); ctx.setDepthTest(true); ctx.setCullFace(true); - renderHands(camera, delta); + + // prepare modified HUD camera + Camera hudcam = camera; + hudcam.far = 10.0f; + hudcam.setFov(0.9f); + hudcam.position = {}; + + hands->renderHands(camera, delta); + + display::clearDepth(); + setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f); + + skybox->bind(); + modelBatch->render(); + modelBatch->setLightsOffset(glm::vec3()); + skybox->unbind(); } renderBlockOverlay(pctx); diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index 5d33f8cd..e53a5d80 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -20,6 +20,8 @@ class ChunksRenderer; class ParticlesRenderer; class BlockWrapsRenderer; class PrecipitationRenderer; +class HandsRenderer; +class NamedSkeletons; class GuidesRenderer; class TextsRenderer; class Shader; @@ -52,6 +54,7 @@ class WorldRenderer { std::unique_ptr modelBatch; std::unique_ptr guides; std::unique_ptr chunks; + std::unique_ptr hands; std::unique_ptr skybox; std::unique_ptr shadowMap; std::unique_ptr wideShadowMap; @@ -69,8 +72,6 @@ class WorldRenderer { /// @brief Render block selection lines void renderBlockSelection(); - - void renderHands(const Camera& camera, float delta); /// @brief Render lines (selection and debug) /// @param camera active camera @@ -100,6 +101,7 @@ public: std::unique_ptr texts; std::unique_ptr blockWraps; std::unique_ptr precipitation; + std::unique_ptr skeletons; static bool showChunkBorders; static bool showEntitiesDebug; From 310dc9ebb239c10c455eb85a81ceccfd776bc000 Mon Sep 17 00:00:00 2001 From: GHOST11111100 Date: Sun, 27 Jul 2025 14:48:41 +0300 Subject: [PATCH 014/177] fix: correct tooltip comparison logic and streamline stack setting in SlotView --- src/graphics/ui/elements/InventoryView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index 563d36a7..6aadd3c4 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -153,7 +153,7 @@ static bool is_same_tooltip(const ItemStack& stack, const ItemStack& cache) { auto description = stack.getField("description"); auto cDescription = cache.getField("description"); - if (((caption != nullptr) != (description != nullptr)) || + if (((caption != nullptr) != (cCaption != nullptr)) || ((description != nullptr) != (cDescription != nullptr))) { return false; } @@ -250,7 +250,7 @@ void SlotView::draw(const DrawContext& pctx, const Assets& assets) { cache.countStr = std::to_wstring(stack.getCount()); } refreshTooltip(stack, item); - cache.stack.set(ItemStack(stack.getItemId(), stack.getCount())); + cache.stack.set(stack); glm::vec4 tint(1, 1, 1, isEnabled() ? 1 : 0.5f); glm::vec2 pos = calcPos(); From 567eec88c82a1924b526efed03dc751d80c4efa3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 27 Jul 2025 16:04:25 +0300 Subject: [PATCH 015/177] add named skeletons support --- src/logic/scripting/lua/libs/lib__skeleton.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/logic/scripting/lua/libs/lib__skeleton.cpp b/src/logic/scripting/lua/libs/lib__skeleton.cpp index 208c3483..ff4a0eba 100644 --- a/src/logic/scripting/lua/libs/lib__skeleton.cpp +++ b/src/logic/scripting/lua/libs/lib__skeleton.cpp @@ -1,6 +1,13 @@ #include "objects/rigging.hpp" #include "libentity.hpp" +#include "graphics/render/WorldRenderer.hpp" +#include "graphics/render/NamedSkeletons.hpp" + +namespace scripting { + extern WorldRenderer* renderer; +} + static int index_range_check( const rigging::Skeleton& skeleton, lua::Integer index ) { @@ -14,6 +21,9 @@ static int index_range_check( } static rigging::Skeleton* get_skeleton(lua::State* L) { + if (lua::isstring(L, 1)) { + return scripting::renderer->skeletons->getSkeleton(lua::tostring(L, 1)); + } if (auto entity = get_entity(L, 1)) { return &entity->getSkeleton(); } From 6f1174131033a09c6f5d4669d0b2189672457ebb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 27 Jul 2025 19:20:30 +0300 Subject: [PATCH 016/177] move hand item control to lua --- res/scripts/hud.lua | 39 +++++++++++++++++ src/graphics/render/HandsRenderer.cpp | 60 --------------------------- src/graphics/render/HandsRenderer.hpp | 6 --- src/graphics/render/WorldRenderer.cpp | 6 +-- 4 files changed, 40 insertions(+), 71 deletions(-) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 3fd1375c..c3c872bd 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -82,3 +82,42 @@ function on_hud_open() configure_SSAO() end + +local prev_rotation = mat4.idt() + +function update_hand() + local skeleton = __skeleton + local pid = hud.get_player() + local invid, slot = player.get_inventory(pid) + local itemid = inventory.get(invid, slot) + + local cam = cameras.get("core:first-person") + local bone = skeleton.index("hand", "item") + + local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1) + + local rotation = cam:get_rot() + + local angle = player.get_rot() - 90 + local cos = math.cos(angle / (180 / math.pi)) + local sin = math.sin(angle / (180 / math.pi)) + + local newX = offset[1] * cos - offset[3] * sin + local newZ = offset[1] * sin + offset[3] * cos + + offset[1] = newX + offset[3] = newZ + + local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1}) + mat4.scale(mat, {0.1, 0.1, 0.1}, mat) + mat4.mul(rotation, mat, mat) + mat4.rotate(mat, {0, 1, 0}, -90, mat) + mat4.translate(mat, offset, mat) + + skeleton.set_matrix("hand", bone, mat) + skeleton.set_model("hand", bone, item.model_name(itemid)) +end + +function on_hud_render() + timeit(1, update_hand) +end diff --git a/src/graphics/render/HandsRenderer.cpp b/src/graphics/render/HandsRenderer.cpp index aa5666f1..db598a43 100644 --- a/src/graphics/render/HandsRenderer.cpp +++ b/src/graphics/render/HandsRenderer.cpp @@ -4,31 +4,19 @@ #include #include "ModelBatch.hpp" -#include "assets/Assets.hpp" #include "content/Content.hpp" -#include "graphics/core/Shader.hpp" #include "graphics/commons/Model.hpp" -#include "items/Inventory.hpp" -#include "items/ItemStack.hpp" -#include "items/ItemDef.hpp" -#include "objects/Player.hpp" #include "objects/rigging.hpp" -#include "world/Level.hpp" #include "window/Camera.hpp" -#include "window/Window.hpp" using namespace rigging; HandsRenderer::HandsRenderer( const Assets& assets, - const Level& level, - const Player& player, ModelBatch& modelBatch, std::shared_ptr skeleton ) : assets(assets), - level(level), - player(player), modelBatch(modelBatch), skeleton(std::move(skeleton)) { } @@ -36,56 +24,8 @@ HandsRenderer::HandsRenderer( void HandsRenderer::renderHands( const Camera& camera, float delta ) { - // configure model matrix - const glm::vec3 itemOffset(0.06f, 0.035f, -0.1); - - static glm::mat4 prevRotation(1.0f); - - const float speed = 24.0f; - glm::mat4 matrix = glm::translate(glm::mat4(1.0f), itemOffset); - matrix = glm::scale(matrix, glm::vec3(0.1f)); - glm::mat4 rotation = camera.rotation; - - // rotation interpolation - glm::quat rot0 = glm::quat_cast(prevRotation); - glm::quat rot1 = glm::quat_cast(rotation); - glm::quat finalRot = - glm::slerp(rot0, rot1, static_cast(delta * speed)); - rotation = glm::mat4_cast(finalRot); - prevRotation = rotation; - - // building matrix - matrix = rotation * matrix * - glm::rotate( - glm::mat4(1.0f), -glm::pi() * 0.5f, glm::vec3(0, 1, 0) - ); - - // getting offset - glm::vec3 cameraRotation = player.getRotation(); - auto offset = -(camera.position - player.getPosition()); - float angle = glm::radians(cameraRotation.x - 90); - float cos = glm::cos(angle); - float sin = glm::sin(angle); - - float newX = offset.x * cos - offset.z * sin; - float newZ = offset.x * sin + offset.z * cos; - offset = glm::vec3(newX, offset.y, newZ); - matrix = matrix * glm::translate(glm::mat4(1.0f), offset); - - // get current chosen item - auto indices = level.content.getIndices(); - const auto& inventory = player.getInventory(); - int slot = player.getChosenSlot(); - const ItemStack& stack = inventory->getSlot(slot); - const auto& def = indices->items.require(stack.getItemId()); - auto& skeleton = *this->skeleton; const auto& config = *skeleton.config; - - auto itemBone = config.find("item"); - size_t itemBoneIndex = itemBone->getIndex(); - skeleton.modelOverrides.at(itemBoneIndex).model = assets.get(def.modelName); - skeleton.pose.matrices.at(itemBoneIndex) = matrix; // render modelBatch.setLightsOffset(camera.position); diff --git a/src/graphics/render/HandsRenderer.hpp b/src/graphics/render/HandsRenderer.hpp index 156035eb..1aba08a2 100644 --- a/src/graphics/render/HandsRenderer.hpp +++ b/src/graphics/render/HandsRenderer.hpp @@ -4,8 +4,6 @@ class Assets; class Camera; -class Level; -class Player; class ModelBatch; namespace rigging { @@ -16,8 +14,6 @@ class HandsRenderer { public: HandsRenderer( const Assets& assets, - const Level& level, - const Player& player, ModelBatch& modelBatch, std::shared_ptr skeleton ); @@ -25,8 +21,6 @@ public: void renderHands(const Camera& camera, float delta); private: const Assets& assets; - const Level& level; - const Player& player; ModelBatch& modelBatch; std::shared_ptr skeleton; }; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 594d39be..ba1e580b 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -116,11 +116,7 @@ WorldRenderer::WorldRenderer( content.getDefaults()["hand-skeleton"].asString() ); hands = std::make_unique( - *assets, - level, - player, - *modelBatch, - skeletons->createSkeleton("hand", &skeletonConfig) + *assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig) ); } From b991ca3c9aa4b5d33e6a7e73baad3f9fbb6ab87a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 27 Jul 2025 21:38:09 +0300 Subject: [PATCH 017/177] add gfx.skeletons library --- res/scripts/hud.lua | 2 +- res/scripts/stdlib.lua | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index c3c872bd..9111c794 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -86,7 +86,7 @@ end local prev_rotation = mat4.idt() function update_hand() - local skeleton = __skeleton + local skeleton = gfx.skeletons local pid = hud.get_player() local invid, slot = player.get_inventory(pid) local itemid = inventory.get(invid, slot) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 765d2c08..d4be90b3 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -396,6 +396,8 @@ function _rules.clear() end function __vc_on_hud_open() + gfx.skeletons = __skeleton + _rules.create("allow-cheats", true) _rules.create("allow-content-access", hud._is_content_access(), function(value) From b5a2f2ae7162c11b409ca89c50bab795038fd856 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 27 Jul 2025 22:18:28 +0300 Subject: [PATCH 018/177] add gfx.skeletons library docs --- doc/en/scripting/builtins/libgfx-skeletons.md | 48 ++++++++++++++++++ doc/ru/scripting/builtins/libgfx-skeletons.md | 49 +++++++++++++++++++ res/scripts/hud.lua | 2 - res/scripts/hud_classes.lua | 20 ++++++++ .../scripting/lua/libs/lib__skeleton.cpp | 5 ++ 5 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 doc/en/scripting/builtins/libgfx-skeletons.md create mode 100644 doc/ru/scripting/builtins/libgfx-skeletons.md diff --git a/doc/en/scripting/builtins/libgfx-skeletons.md b/doc/en/scripting/builtins/libgfx-skeletons.md new file mode 100644 index 00000000..4146b4be --- /dev/null +++ b/doc/en/scripting/builtins/libgfx-skeletons.md @@ -0,0 +1,48 @@ +# gfx.skeletons library + +A library for working with named skeletons, such as 'hand', +used to control the hand and the carried item displayed in first-person view. +The set of functions is similar to the skeleton component of entities. + +The first argument to the function is the name of the skeleton. + +```lua +-- Returns an object wrapper over the skeleton +local skeleton = gfx.skeletons.get(name: str) + +-- Returns the index of the bone by name or nil +skeleton:index(name: str) -> int + +-- Returns the name of the model assigned to the bone with the specified index +skeleton:get_model(index: int) -> str + +-- Reassigns the model of the bone with the specified index +-- Resets to the original if you do not specify a name +skeleton:set_model(index: int, name: str) + +-- Returns the transformation matrix of the bone with the specified index +skeleton:get_matrix(index: int) -> mat4 + +-- Sets the transformation matrix of the bone with the specified index +skeleton:set_matrix(index: int, matrix: mat4) + +-- Returns the texture by key (dynamically assigned textures - '$name') +skeleton:get_texture(key: str) -> str + +-- Assigns a texture by key +skeleton:set_texture(key: str, value: str) + +-- Checks the visibility status of a bone by index +-- or the entire skeleton if index is not specified +skeleton:is_visible([optional] index: int) -> bool + +-- Sets the visibility status of a bone by index +-- or the entire skeleton if index is not specified +skeleton:set_visible([optional] index: int, status: bool) + +-- Returns the color of the entity +skeleton:get_color() -> vec3 + +-- Sets the color of the entity +skeleton:set_color(color: vec3) +``` diff --git a/doc/ru/scripting/builtins/libgfx-skeletons.md b/doc/ru/scripting/builtins/libgfx-skeletons.md new file mode 100644 index 00000000..0e80b8f9 --- /dev/null +++ b/doc/ru/scripting/builtins/libgfx-skeletons.md @@ -0,0 +1,49 @@ +# Библиотека gfx.skeletons + +Библиотека для работы с именованными скелетами, такими как 'hand', +использующийся для управления, отображаемыми при виде от первого лица, +рукой и переносимым предметом. Набор функций аналогичен компоненту skeleton +у сущностей. + +Первым аргументом в функции передаётся имя скелета. + +```lua +-- Возвращает объектную обёртку над скелетом +local skeleton = gfx.skeletons.get(name: str) + +-- Возвращает индекс кости по имени или nil +skeleton:index(name: str) -> int + +-- Возвращает имя модели, назначенной на кость с указанным индексом +skeleton:get_model(index: int) -> str + +-- Переназначает модель кости с указанным индексом +-- Сбрасывает до изначальной, если не указывать имя +skeleton:set_model(index: int, name: str) + +-- Возвращает матрицу трансформации кости с указанным индексом +skeleton:get_matrix(index: int) -> mat4 + +-- Устанавливает матрицу трансформации кости с указанным индексом +skeleton:set_matrix(index: int, matrix: mat4) + +-- Возвращает текстуру по ключу (динамически назначаемые текстуры - '$имя') +skeleton:get_texture(key: str) -> str + +-- Назначает текстуру по ключу +skeleton:set_texture(key: str, value: str) + +-- Проверяет статус видимости кости по индесу +-- или всего скелета, если индекс не указан +skeleton:is_visible([опционально] index: int) -> bool + +-- Устанавливает статус видимости кости по индексу +-- или всего скелета, если индекс не указан +skeleton:set_visible([опционально] index: int, status: bool) + +-- Возвращает цвет сущности +skeleton:get_color() -> vec3 + +-- Устанавливает цвет сущности +skeleton:set_color(color: vec3) +``` diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 9111c794..e189d098 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -83,8 +83,6 @@ function on_hud_open() configure_SSAO() end -local prev_rotation = mat4.idt() - function update_hand() local skeleton = gfx.skeletons local pid = hud.get_player() diff --git a/res/scripts/hud_classes.lua b/res/scripts/hud_classes.lua index dc373c6e..f8a28944 100644 --- a/res/scripts/hud_classes.lua +++ b/res/scripts/hud_classes.lua @@ -12,7 +12,27 @@ local Text3D = {__index={ update_settings=function(self, t) return gfx.text3d.update_settings(self.id, t) end, }} +local Skeleton = {__index={ + index=function(self, s) return gfx.skeletons.index(self.name, s) end, + get_model=function(self, i) return gfx.skeletons.get_model(self.name, i) end, + set_model=function(self, i, s) return gfx.skeletons.set_model(self.name, i, s) end, + get_matrix=function(self, i) return gfx.skeletons.get_matrix(self.name, i) end, + set_matrix=function(self, i, m) return gfx.skeletons.set_matrix(self.name, i, m) end, + get_texture=function(self, i) return gfx.skeletons.get_texture(self.name, i) end, + set_texture=function(self, i, s) return gfx.skeletons.set_texture(self.name, i, s) end, + is_visible=function(self, i) return gfx.skeletons.is_visible(self.name, i) end, + set_visible=function(self, i, b) return gfx.skeletons.set_visible(self.name, i, b) end, + get_color=function(self, i) return gfx.skeletons.get_color(self.name, i) end, + set_color=function(self, i, c) return gfx.skeletons.set_color(self.name, i, c) end, +}} + gfx.text3d.new = function(pos, text, preset, extension) local id = gfx.text3d.show(pos, text, preset, extension) return setmetatable({id=id}, Text3D) end + +gfx.skeletons.get = function(name) + if gfx.skeletons.exists(name) then + return setmetatable({name=name}, Skeleton) + end +end diff --git a/src/logic/scripting/lua/libs/lib__skeleton.cpp b/src/logic/scripting/lua/libs/lib__skeleton.cpp index ff4a0eba..7272548c 100644 --- a/src/logic/scripting/lua/libs/lib__skeleton.cpp +++ b/src/logic/scripting/lua/libs/lib__skeleton.cpp @@ -150,6 +150,10 @@ static int l_set_interpolated(lua::State* L) { return 0; } +static int l_exists(lua::State* L) { + return lua::pushboolean(L, get_skeleton(L)); +} + const luaL_Reg skeletonlib[] = { {"get_model", lua::wrap}, {"set_model", lua::wrap}, @@ -164,5 +168,6 @@ const luaL_Reg skeletonlib[] = { {"set_color", lua::wrap}, {"is_interpolated", lua::wrap}, {"set_interpolated", lua::wrap}, + {"exists", lua::wrap}, {NULL, NULL} }; From 93c922be998105504d0362ca2eacdef30ba59ce5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 27 Jul 2025 22:21:14 +0300 Subject: [PATCH 019/177] update doc/*/scripting.md --- doc/en/scripting.md | 1 + doc/ru/scripting.md | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/en/scripting.md b/doc/en/scripting.md index b9661cd3..05a89a26 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -20,6 +20,7 @@ Subsections: - [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md) - [gfx.particles](particles.md#gfxparticles-library) - [gfx.posteffects](scripting/builtins/libgfx-posteffects.md) + - [gfx.skeletons](scripting/builtins/libgfx-skeletons.md) - [gfx.text3d](3d-text.md#gfxtext3d-library) - [gfx.weather](scripting/builtins/libgfx-weather.md) - [gui](scripting/builtins/libgui.md) diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index 6fb86dce..eaae3302 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -20,6 +20,7 @@ - [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md) - [gfx.particles](particles.md#библиотека-gfxparticles) - [gfx.posteffects](scripting/builtins/libgfx-posteffects.md) + - [gfx.skeletons](scripting/builtins/libgfx-skeletons.md) - [gfx.text3d](3d-text.md#библиотека-gfxtext3d) - [gfx.weather](scripting/builtins/libgfx-weather.md) - [gui](scripting/builtins/libgui.md) From ecf3b94d5375d6207492b86d10b1d42e1fd7c570 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 27 Jul 2025 22:30:30 +0300 Subject: [PATCH 020/177] add hud.hand_controller --- doc/en/scripting/builtins/libhud.md | 3 +++ doc/ru/scripting/builtins/libhud.md | 3 +++ res/scripts/hud.lua | 8 ++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/en/scripting/builtins/libhud.md b/doc/en/scripting/builtins/libhud.md index 06766422..58d78302 100644 --- a/doc/en/scripting/builtins/libhud.md +++ b/doc/en/scripting/builtins/libhud.md @@ -65,4 +65,7 @@ hud.is_inventory_open() -> bool -- Sets whether to allow pausing. If false, the pause menu will not pause the game. hud.set_allow_pause(flag: bool) + +-- Function that controls the named skeleton 'hand' (see gfx.skeletons) +hud.hand_controller: function() ``` diff --git a/doc/ru/scripting/builtins/libhud.md b/doc/ru/scripting/builtins/libhud.md index 311542cb..5f0856b3 100644 --- a/doc/ru/scripting/builtins/libhud.md +++ b/doc/ru/scripting/builtins/libhud.md @@ -68,4 +68,7 @@ hud.is_inventory_open() -> bool -- Устанавливает разрешение на паузу. При значении false меню паузы не приостанавливает игру. hud.set_allow_pause(flag: bool) + +-- Функция, управляющая именованным скелетом 'hand' (см. gfx.skeletons) +hud.hand_controller: function() ``` diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index e189d098..abd1eaf4 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -83,7 +83,7 @@ function on_hud_open() configure_SSAO() end -function update_hand() +local function update_hand() local skeleton = gfx.skeletons local pid = hud.get_player() local invid, slot = player.get_inventory(pid) @@ -117,5 +117,9 @@ function update_hand() end function on_hud_render() - timeit(1, update_hand) + if hud.hand_controller then + hud.hand_controller() + else + update_hand() + end end From 0d2d5770d3dda959af4069243cf9552c781cec08 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 28 Jul 2025 00:50:14 +0300 Subject: [PATCH 021/177] add ImageData.drawRect --- src/graphics/core/ImageData.cpp | 36 +++++++++++++++++++++++++++++++++ src/graphics/core/ImageData.hpp | 1 + 2 files changed, 37 insertions(+) diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index a2b1d524..44a1936e 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -1,5 +1,6 @@ #include "ImageData.hpp" +#include #include #include #include @@ -187,6 +188,41 @@ void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color } } +template +static void draw_rect(ImageData& image, int dstX, int dstY, int width, int height, const glm::ivec4& color) { + ubyte* data = image.getData(); + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + + int x1 = glm::min(glm::max(dstX, 0), imageWidth - 1); + int y1 = glm::min(glm::max(dstY, 0), imageHeight - 1); + + int x2 = glm::min(glm::max(dstX + width, 0), imageWidth - 1); + int y2 = glm::min(glm::max(dstY + height, 0), imageHeight - 1); + + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + int index = (y * imageWidth + x) * channels; + for (int i = 0; i < channels; i++) { + data[index + i] = color[i]; + } + } + } +} + +void ImageData::drawRect(int x, int y, int width, int height, const glm::ivec4& color) { + switch (format) { + case ImageFormat::rgb888: + draw_rect<3>(*this, x, y, width, height, color); + break; + case ImageFormat::rgba8888: + draw_rect<4>(*this, x, y, width, height, color); + break; + default: + break; + } +} + void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) { ubyte* source = image.getData(); uint srcwidth = image.getWidth(); diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index 213a352c..d09a0411 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -28,6 +28,7 @@ public: void flipY(); void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color); + void drawRect(int x, int y, int width, int height, const glm::ivec4& color); void blit(const ImageData& image, int x, int y); void extrude(int x, int y, int w, int h); void fixAlphaColor(); From c392b75d113f141f6bc46570e34226d6f52fd24a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 28 Jul 2025 21:31:15 +0300 Subject: [PATCH 022/177] fix gfx.skeletons is nil --- res/scripts/hud_classes.lua | 1 + res/scripts/stdlib.lua | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/res/scripts/hud_classes.lua b/res/scripts/hud_classes.lua index f8a28944..e6259e22 100644 --- a/res/scripts/hud_classes.lua +++ b/res/scripts/hud_classes.lua @@ -31,6 +31,7 @@ gfx.text3d.new = function(pos, text, preset, extension) return setmetatable({id=id}, Text3D) end +gfx.skeletons = __skeleton gfx.skeletons.get = function(name) if gfx.skeletons.exists(name) then return setmetatable({name=name}, Skeleton) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index d4be90b3..765d2c08 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -396,8 +396,6 @@ function _rules.clear() end function __vc_on_hud_open() - gfx.skeletons = __skeleton - _rules.create("allow-cheats", true) _rules.create("allow-content-access", hud._is_content_access(), function(value) From 70c5c67bd1759ff16831d8f2a39407cc4a6aec08 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 28 Jul 2025 23:00:27 +0300 Subject: [PATCH 023/177] add in-world lines renderer --- src/graphics/render/LinesRenderer.cpp | 14 ++++++++++++++ src/graphics/render/LinesRenderer.hpp | 22 ++++++++++++++++++++++ src/graphics/render/WorldRenderer.cpp | 6 ++++++ src/graphics/render/WorldRenderer.hpp | 2 ++ 4 files changed, 44 insertions(+) create mode 100644 src/graphics/render/LinesRenderer.cpp create mode 100644 src/graphics/render/LinesRenderer.hpp diff --git a/src/graphics/render/LinesRenderer.cpp b/src/graphics/render/LinesRenderer.cpp new file mode 100644 index 00000000..a5d92550 --- /dev/null +++ b/src/graphics/render/LinesRenderer.cpp @@ -0,0 +1,14 @@ +#include "LinesRenderer.hpp" + +#include "graphics/core/LineBatch.hpp" + +void LinesRenderer::draw(LineBatch& batch) { + for (const auto& line : queue) { + batch.line(line.a, line.b, line.color); + } + queue.clear(); +} + +void LinesRenderer::pushLine(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color) { + queue.push_back({a, b, color}); +} diff --git a/src/graphics/render/LinesRenderer.hpp b/src/graphics/render/LinesRenderer.hpp new file mode 100644 index 00000000..4c42e4f5 --- /dev/null +++ b/src/graphics/render/LinesRenderer.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +class LineBatch; + +class LinesRenderer { +public: + struct Line { + glm::vec3 a; + glm::vec3 b; + glm::vec4 color; + }; + + void draw(LineBatch& batch); + + void pushLine(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color); +private: + std::vector queue; +}; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index ba1e580b..f2425e0b 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -52,6 +52,7 @@ #include "TextsRenderer.hpp" #include "ChunksRenderer.hpp" #include "GuidesRenderer.hpp" +#include "LinesRenderer.hpp" #include "ModelBatch.hpp" #include "Skybox.hpp" #include "Emitter.hpp" @@ -118,6 +119,7 @@ WorldRenderer::WorldRenderer( hands = std::make_unique( *assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig) ); + lines = std::make_unique(); } WorldRenderer::~WorldRenderer() = default; @@ -481,6 +483,10 @@ void WorldRenderer::draw( // Drawing background sky plane skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); + linesShader.use(); + lines->draw(*lineBatch); + lineBatch->flush(); + { auto sctx = ctx.sub(); sctx.setCullFace(true); diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index e53a5d80..600e2e4f 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -23,6 +23,7 @@ class PrecipitationRenderer; class HandsRenderer; class NamedSkeletons; class GuidesRenderer; +class LinesRenderer; class TextsRenderer; class Shader; class Frustum; @@ -102,6 +103,7 @@ public: std::unique_ptr blockWraps; std::unique_ptr precipitation; std::unique_ptr skeletons; + std::unique_ptr lines; static bool showChunkBorders; static bool showEntitiesDebug; From 8290c147c54dc47b2c5e3eb8c8345e3026317417 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 29 Jul 2025 00:29:55 +0300 Subject: [PATCH 024/177] refactor: move shadows-related code to separate module --- src/frontend/screens/LevelScreen.cpp | 4 +- src/graphics/core/ShadowMap.cpp | 47 ------ src/graphics/core/ShadowMap.hpp | 18 --- src/graphics/core/Shadows.cpp | 199 +++++++++++++++++++++++++ src/graphics/core/Shadows.hpp | 46 ++++++ src/graphics/render/ChunksRenderer.cpp | 2 +- src/graphics/render/ChunksRenderer.hpp | 2 +- src/graphics/render/WorldRenderer.cpp | 199 +++++++------------------ src/graphics/render/WorldRenderer.hpp | 43 ++---- 9 files changed, 317 insertions(+), 243 deletions(-) delete mode 100644 src/graphics/core/ShadowMap.cpp delete mode 100644 src/graphics/core/ShadowMap.hpp create mode 100644 src/graphics/core/Shadows.cpp create mode 100644 src/graphics/core/Shadows.hpp diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index ca80ed11..7b2a2a5d 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -173,7 +173,7 @@ void LevelScreen::saveWorldPreview() { static_cast(previewSize)} ); - renderer->draw(ctx, camera, false, true, 0.0f, *postProcessing); + renderer->renderFrame(ctx, camera, false, true, 0.0f, *postProcessing); auto image = postProcessing->toImage(); image->flipY(); imageio::write("world:preview.png", image.get()); @@ -260,7 +260,7 @@ void LevelScreen::draw(float delta) { if (!hud->isPause()) { scripting::on_entities_render(engine.getTime().getDelta()); } - renderer->draw( + renderer->renderFrame( ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing ); diff --git a/src/graphics/core/ShadowMap.cpp b/src/graphics/core/ShadowMap.cpp deleted file mode 100644 index e5032d8b..00000000 --- a/src/graphics/core/ShadowMap.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "ShadowMap.hpp" - -#include - -ShadowMap::ShadowMap(int resolution) : resolution(resolution) { - glGenTextures(1, &depthMap); - glBindTexture(GL_TEXTURE_2D, depthMap); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, - resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); - float border[4] {1.0f, 1.0f, 1.0f, 1.0f}; - glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, border); - - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -ShadowMap::~ShadowMap() { - glDeleteFramebuffers(1, &fbo); - glDeleteTextures(1, &depthMap); -} - -void ShadowMap::bind() { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glClear(GL_DEPTH_BUFFER_BIT); -} - -void ShadowMap::unbind() { - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -uint ShadowMap::getDepthMap() const { - return depthMap; -} - -int ShadowMap::getResolution() const { - return resolution; -} diff --git a/src/graphics/core/ShadowMap.hpp b/src/graphics/core/ShadowMap.hpp deleted file mode 100644 index 828c51aa..00000000 --- a/src/graphics/core/ShadowMap.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "typedefs.hpp" - -class ShadowMap { -public: - ShadowMap(int resolution); - ~ShadowMap(); - - void bind(); - void unbind(); - uint getDepthMap() const; - int getResolution() const; -private: - uint fbo; - uint depthMap; - int resolution; -}; diff --git a/src/graphics/core/Shadows.cpp b/src/graphics/core/Shadows.cpp new file mode 100644 index 00000000..c6ac8e37 --- /dev/null +++ b/src/graphics/core/Shadows.cpp @@ -0,0 +1,199 @@ +#include "Shadows.hpp" + +#include + +#include + +#include "assets/Assets.hpp" +#include "graphics/core/DrawContext.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/commons.hpp" +#include "world/Level.hpp" +#include "world/Weather.hpp" +#include "world/World.hpp" + +using namespace advanced_pipeline; + +inline constexpr int MIN_SHADOW_MAP_RES = 512; +inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0; + +class ShadowMap { +public: + ShadowMap(int resolution) : resolution(resolution) { + glGenTextures(1, &depthMap); + glBindTexture(GL_TEXTURE_2D, depthMap); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, + resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER); + glTexParameteri( + GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE + ); + float border[4] {1.0f, 1.0f, 1.0f, 1.0f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border); + + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0 + ); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + ~ShadowMap() { + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &depthMap); + } + + void bind(){ + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glClear(GL_DEPTH_BUFFER_BIT); + } + + void unbind() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + uint getDepthMap() const { + return depthMap; + } + int getResolution() const { + return resolution; + } +private: + uint fbo; + uint depthMap; + int resolution; +}; + +Shadows::Shadows(const Level& level) : level(level) {} + +Shadows::~Shadows() = default; + +void Shadows::setQuality(int quality) { + int resolution = MIN_SHADOW_MAP_RES << quality; + if (quality > 0 && !shadows) { + shadowMap = std::make_unique(resolution); + wideShadowMap = std::make_unique(resolution); + shadows = true; + } else if (quality == 0 && shadows) { + shadowMap.reset(); + wideShadowMap.reset(); + shadows = false; + } + if (shadows && shadowMap->getResolution() != resolution) { + shadowMap = std::make_unique(resolution); + wideShadowMap = std::make_unique(resolution); + } + this->quality = quality; +} + +void Shadows::setup(Shader& shader, const Weather& weather) { + if (shadows) { + const auto& worldInfo = level.getWorld()->getInfo(); + float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds()); + shader.uniform1i("u_screen", 0); + shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView()); + shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView()); + shader.uniform3f("u_sunDir", shadowCamera.front); + shader.uniform1i("u_shadowsRes", shadowMap->getResolution()); + shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable + shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable + + glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0); + shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0); + glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap()); + + glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1); + shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1); + glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap()); + + glActiveTexture(TEXTURE_MAIN); + } +} + +void Shadows::refresh(const Camera& camera, const DrawContext& pctx, std::function renderShadowPass) { + static int frameid = 0; + if (shadows) { + if (frameid % 2 == 0) { + generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass); + } else { + generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f, renderShadowPass); + } + } + frameid++; +} + +void Shadows::generateShadowsMap( + const Camera& camera, + const DrawContext& pctx, + ShadowMap& shadowMap, + Camera& shadowCamera, + float scale, + std::function renderShadowPass +) { + auto world = level.getWorld(); + const auto& worldInfo = world->getInfo(); + + int resolution = shadowMap.getResolution(); + float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale; + float shadowMapSize = resolution * shadowMapScale; + + glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f; + glm::vec3 prevPos = shadowCamera.position; + shadowCamera = Camera( + glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos, + shadowMapSize + ); + shadowCamera.near = 0.1f; + shadowCamera.far = 1000.0f; + shadowCamera.perspective = false; + shadowCamera.setAspectRatio(1.0f); + + float t = worldInfo.daytime - 0.25f; + if (t < 0.0f) { + t += 1.0f; + } + t = fmod(t, 0.5f); + + float sunCycleStep = 1.0f / 500.0f; + float sunAngle = glm::radians( + 90.0f - + ((static_cast(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f + ); + float sunAltitude = glm::pi() * 0.25f; + shadowCamera.rotate( + -glm::cos(sunAngle + glm::pi() * 0.5f) * sunAltitude, + sunAngle - glm::pi() * 0.5f, + glm::radians(0.0f) + ); + + shadowCamera.position -= shadowCamera.front * 500.0f; + shadowCamera.position += shadowCamera.up * 0.0f; + shadowCamera.position += camera.front * 0.0f; + + auto view = shadowCamera.getView(); + + auto currentPos = shadowCamera.position; + auto topRight = shadowCamera.right + shadowCamera.up; + auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f); + auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f); + + shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f)); + + { + auto sctx = pctx.sub(); + sctx.setDepthTest(true); + sctx.setCullFace(true); + sctx.setViewport({resolution, resolution}); + shadowMap.bind(); + if (renderShadowPass) { + renderShadowPass(shadowCamera); + } + shadowMap.unbind(); + } +} diff --git a/src/graphics/core/Shadows.hpp b/src/graphics/core/Shadows.hpp new file mode 100644 index 00000000..6f21a2d7 --- /dev/null +++ b/src/graphics/core/Shadows.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include "typedefs.hpp" +#include "window/Camera.hpp" + +class Shader; +class Level; +class Assets; +struct Weather; +class DrawContext; +struct EngineSettings; +class ShadowMap; + +class Shadows { +public: + Shadows(const Level& level); + ~Shadows(); + + void setup(Shader& shader, const Weather& weather); + void setQuality(int quality); + void refresh( + const Camera& camera, + const DrawContext& pctx, + std::function renderShadowPass + ); +private: + const Level& level; + bool shadows = false; + Camera shadowCamera; + Camera wideShadowCamera; + std::unique_ptr shadowMap; + std::unique_ptr wideShadowMap; + int quality = 0; + + void generateShadowsMap( + const Camera& camera, + const DrawContext& pctx, + ShadowMap& shadowMap, + Camera& shadowCamera, + float scale, + std::function renderShadowPass + ); +}; diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index bb816c37..571a10e5 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -184,7 +184,7 @@ const Mesh* ChunksRenderer::retrieveChunk( return mesh; } -void ChunksRenderer::drawChunksShadowsPass( +void ChunksRenderer::drawShadowsPass( const Camera& camera, Shader& shader, const Camera& playerCamera ) { Frustum frustum; diff --git a/src/graphics/render/ChunksRenderer.hpp b/src/graphics/render/ChunksRenderer.hpp index 70925343..9215a863 100644 --- a/src/graphics/render/ChunksRenderer.hpp +++ b/src/graphics/render/ChunksRenderer.hpp @@ -73,7 +73,7 @@ public: const std::shared_ptr& chunk, bool important ); - void drawChunksShadowsPass( + void drawShadowsPass( const Camera& camera, Shader& shader, const Camera& playerCamera ); diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index f2425e0b..97bf870d 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -42,7 +42,7 @@ #include "graphics/core/Shader.hpp" #include "graphics/core/Texture.hpp" #include "graphics/core/Font.hpp" -#include "graphics/core/ShadowMap.hpp" +#include "graphics/core/Shadows.hpp" #include "graphics/core/GBuffer.hpp" #include "BlockWrapsRenderer.hpp" #include "ParticlesRenderer.hpp" @@ -62,8 +62,6 @@ using namespace advanced_pipeline; inline constexpr size_t BATCH3D_CAPACITY = 4096; inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000; -inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0; -inline constexpr int MIN_SHADOW_MAP_RES = 512; bool WorldRenderer::showChunkBorders = false; bool WorldRenderer::showEntitiesDebug = false; @@ -82,7 +80,7 @@ WorldRenderer::WorldRenderer( MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings() )), guides(std::make_unique()), - chunks(std::make_unique( + chunksRenderer(std::make_unique( &level, *player.chunks, assets, @@ -103,7 +101,7 @@ WorldRenderer::WorldRenderer( auto& settings = engine.getSettings(); level.events->listen( LevelEventType::CHUNK_HIDDEN, - [this](LevelEventType, Chunk* chunk) { chunks->unload(chunk); } + [this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); } ); auto assets = engine.getAssets(); skybox = std::make_unique( @@ -120,10 +118,24 @@ WorldRenderer::WorldRenderer( *assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig) ); lines = std::make_unique(); + shadowMapping = std::make_unique(level); } WorldRenderer::~WorldRenderer() = default; +static void setup_weather(Shader& shader, const Weather& weather) { + shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); + shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); + shader.uniform1f("u_weatherFogCurve", weather.fogCurve()); +} + +static void setup_camera(Shader& shader, const Camera& camera) { + shader.uniformMatrix("u_model", glm::mat4(1.0f)); + shader.uniformMatrix("u_proj", camera.getProjection()); + shader.uniformMatrix("u_view", camera.getView()); + shader.uniform3f("u_cameraPos", camera.position); +} + void WorldRenderer::setupWorldShader( Shader& shader, const Camera& camera, @@ -131,45 +143,20 @@ void WorldRenderer::setupWorldShader( float fogFactor ) { shader.use(); - shader.uniformMatrix("u_model", glm::mat4(1.0f)); - shader.uniformMatrix("u_proj", camera.getProjection()); - shader.uniformMatrix("u_view", camera.getView()); + + setup_camera(shader, camera); + setup_weather(shader, weather); + shadowMapping->setup(shader, weather); + shader.uniform1f("u_timer", timer); shader.uniform1f("u_gamma", settings.graphics.gamma.get()); shader.uniform1f("u_fogFactor", fogFactor); shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader.uniform1i("u_debugLights", lightsDebug); shader.uniform1i("u_debugNormals", false); - shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); - shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); - shader.uniform1f("u_weatherFogCurve", weather.fogCurve()); shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime); shader.uniform2f("u_lightDir", skybox->getLightDir()); - shader.uniform3f("u_cameraPos", camera.position); - shader.uniform1i("u_skybox", 1); - shader.uniform1i("u_enableShadows", shadows); - - if (shadows) { - const auto& worldInfo = level.getWorld()->getInfo(); - float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds()); - shader.uniform1i("u_screen", 0); - shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView()); - shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView()); - shader.uniform3f("u_sunDir", shadowCamera.front); - shader.uniform1i("u_shadowsRes", shadowMap->getResolution()); - shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable - shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable - - glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0); - shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0); - glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap()); - - glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1); - shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1); - glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap()); - - glActiveTexture(TEXTURE_MAIN); - } + shader.uniform1i("u_skybox", TARGET_SKYBOX); auto indices = level.content.getIndices(); // Light emission when an emissive item is chosen @@ -188,7 +175,7 @@ void WorldRenderer::setupWorldShader( } } -void WorldRenderer::renderLevel( +void WorldRenderer::renderOpaque( const DrawContext& ctx, const Camera& camera, const EngineSettings& settings, @@ -227,7 +214,7 @@ void WorldRenderer::renderLevel( setupWorldShader(shader, camera, settings, fogFactor); - chunks->drawChunks(camera, shader); + chunksRenderer->drawChunks(camera, shader); blockWraps->draw(ctx, player); if (hudVisible) { @@ -286,79 +273,7 @@ void WorldRenderer::renderLines( } } -void WorldRenderer::generateShadowsMap( - const Camera& camera, - const DrawContext& pctx, - ShadowMap& shadowMap, - Camera& shadowCamera, - float scale -) { - auto& shadowsShader = assets.require("shadows"); - - auto world = level.getWorld(); - const auto& worldInfo = world->getInfo(); - - const auto& settings = engine.getSettings(); - int resolution = shadowMap.getResolution(); - int quality = settings.graphics.shadowsQuality.get(); - float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale; - float shadowMapSize = resolution * shadowMapScale; - - glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f; - glm::vec3 prevPos = shadowCamera.position; - shadowCamera = Camera( - glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos, - shadowMapSize - ); - shadowCamera.near = 0.1f; - shadowCamera.far = 1000.0f; - shadowCamera.perspective = false; - shadowCamera.setAspectRatio(1.0f); - - float t = worldInfo.daytime - 0.25f; - if (t < 0.0f) { - t += 1.0f; - } - t = fmod(t, 0.5f); - - float sunCycleStep = 1.0f / 500.0f; - float sunAngle = glm::radians( - 90.0f - - ((static_cast(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f - ); - float sunAltitude = glm::pi() * 0.25f; - shadowCamera.rotate( - -glm::cos(sunAngle + glm::pi() * 0.5f) * sunAltitude, - sunAngle - glm::pi() * 0.5f, - glm::radians(0.0f) - ); - - shadowCamera.position -= shadowCamera.front * 500.0f; - shadowCamera.position += shadowCamera.up * 0.0f; - shadowCamera.position += camera.front * 0.0f; - - auto view = shadowCamera.getView(); - - auto currentPos = shadowCamera.position; - auto topRight = shadowCamera.right + shadowCamera.up; - auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f); - auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f); - - shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f)); - - { - auto sctx = pctx.sub(); - sctx.setDepthTest(true); - sctx.setCullFace(true); - sctx.setViewport({resolution, resolution}); - shadowMap.bind(); - setupWorldShader(shadowsShader, shadowCamera, settings, 0.0f); - chunks->drawChunksShadowsPass(shadowCamera, shadowsShader, camera); - shadowMap.unbind(); - } -} - -void WorldRenderer::draw( +void WorldRenderer::renderFrame( const DrawContext& pctx, Camera& camera, bool hudVisible, @@ -382,22 +297,17 @@ void WorldRenderer::draw( auto& deferredShader = assets.require("deferred_lighting").getShader(); const auto& settings = engine.getSettings(); + Shader* affectedShaders[] { + &mainShader, &entityShader, &translucentShader, &deferredShader + }; + gbufferPipeline = settings.graphics.advancedRender.get(); int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline; - int resolution = MIN_SHADOW_MAP_RES << shadowsQuality; - if (shadowsQuality > 0 && !shadows) { - shadowMap = std::make_unique(resolution); - wideShadowMap = std::make_unique(resolution); - shadows = true; - } else if (shadowsQuality == 0 && shadows) { - shadowMap.reset(); - wideShadowMap.reset(); - shadows = false; - } + shadowMapping->setQuality(shadowsQuality); CompileTimeShaderSettings currentSettings { gbufferPipeline, - shadows, + shadowsQuality != 0, settings.graphics.ssao.get() && gbufferPipeline }; if ( @@ -408,18 +318,12 @@ void WorldRenderer::draw( Shader::preprocessor->setDefined("ENABLE_SHADOWS", currentSettings.shadows); Shader::preprocessor->setDefined("ENABLE_SSAO", currentSettings.ssao); Shader::preprocessor->setDefined("ADVANCED_RENDER", currentSettings.advancedRender); - mainShader.recompile(); - entityShader.recompile(); - deferredShader.recompile(); - translucentShader.recompile(); + for (auto shader : affectedShaders) { + shader->recompile(); + } prevCTShaderSettings = currentSettings; } - if (shadows && shadowMap->getResolution() != resolution) { - shadowMap = std::make_unique(resolution); - wideShadowMap = std::make_unique(resolution); - } - const auto& worldInfo = world->getInfo(); float clouds = weather.clouds(); @@ -428,30 +332,27 @@ void WorldRenderer::draw( skybox->refresh(pctx, worldInfo.daytime, mie, 4); - chunks->update(); + chunksRenderer->update(); - static int frameid = 0; - if (shadows) { - if (frameid % 2 == 0) { - generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f); - } else { - generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f); - } - } - frameid++; + shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) { + auto& shader = assets.require("shadows"); + setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f); + chunksRenderer->drawShadowsPass(shadowCamera, shader, camera); + }); auto& linesShader = assets.require("lines"); - /* World render scope with diegetic HUD included */ { + + { DrawContext wctx = pctx.sub(); postProcessing.use(wctx, gbufferPipeline); display::clearDepth(); - /* Actually world render with depth buffer on */ { + /* Main opaque pass (GBuffer pass) */ { DrawContext ctx = wctx.sub(); ctx.setDepthTest(true); ctx.setCullFace(true); - renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible); + renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible); // Debug lines if (hudVisible) { if (debug) { @@ -480,23 +381,27 @@ void WorldRenderer::draw( } else { postProcessing.getFramebuffer()->bind(); } - // Drawing background sky plane + + // Background sky plane skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); + // In-world lines linesShader.use(); lines->draw(*lineBatch); lineBatch->flush(); + // Translucent blocks { auto sctx = ctx.sub(); sctx.setCullFace(true); skybox->bind(); translucentShader.use(); setupWorldShader(translucentShader, camera, settings, fogFactor); - chunks->drawSortedMeshes(camera, translucentShader); + chunksRenderer->drawSortedMeshes(camera, translucentShader); skybox->unbind(); } + // Weather effects entityShader.use(); setupWorldShader(entityShader, camera, settings, fogFactor); @@ -588,7 +493,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { } void WorldRenderer::clear() { - chunks->clear(); + chunksRenderer->clear(); } void WorldRenderer::setDebug(bool flag) { diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index 600e2e4f..955568fa 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -34,7 +34,7 @@ class PostProcessing; class DrawContext; class ModelBatch; class Assets; -class ShadowMap; +class Shadows; class GBuffer; struct EngineSettings; @@ -54,20 +54,17 @@ class WorldRenderer { std::unique_ptr batch3d; std::unique_ptr modelBatch; std::unique_ptr guides; - std::unique_ptr chunks; + std::unique_ptr chunksRenderer; std::unique_ptr hands; std::unique_ptr skybox; - std::unique_ptr shadowMap; - std::unique_ptr wideShadowMap; + std::unique_ptr shadowMapping; Weather weather {}; - Camera shadowCamera; - Camera wideShadowCamera; float timer = 0.0f; bool debug = false; bool lightsDebug = false; bool gbufferPipeline = false; - bool shadows = false; + CompileTimeShaderSettings prevCTShaderSettings {}; @@ -90,12 +87,17 @@ class WorldRenderer { float fogFactor ); - void generateShadowsMap( - const Camera& camera, - const DrawContext& pctx, - ShadowMap& shadowMap, - Camera& shadowCamera, - float scale + /// @brief Render opaque pass + /// @param context graphics context + /// @param camera active camera + /// @param settings engine settings + void renderOpaque( + const DrawContext& context, + const Camera& camera, + const EngineSettings& settings, + float delta, + bool pause, + bool hudVisible ); public: std::unique_ptr particles; @@ -111,7 +113,7 @@ public: WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player); ~WorldRenderer(); - void draw( + void renderFrame( const DrawContext& context, Camera& camera, bool hudVisible, @@ -120,19 +122,6 @@ public: PostProcessing& postProcessing ); - /// @brief Render level without diegetic interface - /// @param context graphics context - /// @param camera active camera - /// @param settings engine settings - void renderLevel( - const DrawContext& context, - const Camera& camera, - const EngineSettings& settings, - float delta, - bool pause, - bool hudVisible - ); - void clear(); void setDebug(bool flag); From 530670cfdb69be33ca8005c21b1f9a5ce4eec9a3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 29 Jul 2025 00:35:26 +0300 Subject: [PATCH 025/177] add `#define GLM_ENABLE_EXPERIMENTAL` --- src/graphics/core/Shadows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/core/Shadows.cpp b/src/graphics/core/Shadows.cpp index c6ac8e37..d37dc557 100644 --- a/src/graphics/core/Shadows.cpp +++ b/src/graphics/core/Shadows.cpp @@ -1,7 +1,7 @@ #include "Shadows.hpp" #include - +#define GLM_ENABLE_EXPERIMENTAL #include #include "assets/Assets.hpp" From 88721344c1198ec3754645eca752f0ccc87ae176 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 30 Jul 2025 21:55:21 +0300 Subject: [PATCH 026/177] fix in-world lines projection --- src/graphics/render/WorldRenderer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 97bf870d..fba83c64 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -387,6 +387,7 @@ void WorldRenderer::renderFrame( // In-world lines linesShader.use(); + linesShader.uniformMatrix("u_projview", camera.getProjView()); lines->draw(*lineBatch); lineBatch->flush(); From 9bb50db29771e9452c2f66b3dc15b09696cd8beb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 31 Jul 2025 23:29:22 +0300 Subject: [PATCH 027/177] add pathfinding --- src/voxels/Pathfinding.cpp | 115 +++++++++++++++++++++++++++++++++++++ src/voxels/Pathfinding.hpp | 48 ++++++++++++++++ src/world/Level.cpp | 4 +- src/world/Level.hpp | 5 ++ 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/voxels/Pathfinding.cpp create mode 100644 src/voxels/Pathfinding.hpp diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp new file mode 100644 index 00000000..f235b554 --- /dev/null +++ b/src/voxels/Pathfinding.cpp @@ -0,0 +1,115 @@ +#include "Pathfinding.hpp" + +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include +#include +#include + +#include "world/Level.hpp" +#include "voxels/GlobalChunks.hpp" +#include "voxels/Chunk.hpp" +#include "voxels/blocks_agent.hpp" +#include "content/Content.hpp" + +using namespace voxels; + +struct Node { + glm::ivec3 pos; + glm::ivec3 parent; + float gScore; + float fScore; +}; + + +struct NodeLess { + bool operator()(const Node& l, const Node& r) const { + return l.fScore > r.fScore; + } +}; + +static float distance(const glm::ivec3& a, const glm::ivec3& b) { + return glm::distance(glm::vec3(a), glm::vec3(b)); +} + +Pathfinding::Pathfinding(const Level& level) : level(level) {} + +Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { + Route route {}; + + std::priority_queue, NodeLess> queue; + queue.push({start, {}, 0, distance(start, end)}); + + std::unordered_set blocked; + std::unordered_map parents; + + const auto& chunks = *level.chunks; + + while (!queue.empty()) { + auto node = queue.top(); + queue.pop(); + + if (blocked.find(node.pos) != blocked.end()) { + continue; + } + + if (node.pos.x == end.x && node.pos.z == end.z) { + auto prev = glm::ivec3(); + auto pos = node.pos; + while (pos != start) { + const auto& found = parents.find(pos); + if (found == parents.end()) { + route.nodes.push_back({pos}); + break; + } + route.nodes.push_back({pos}); + + prev = pos; + pos = found->second.pos; + } + route.nodes.push_back({start}); + route.found = true; + break; + } + + blocked.emplace(node.pos); + glm::ivec2 neighbors[8] { + {0, 1}, {1, 0}, {0, -1}, {-1, 0}, + {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, + }; + + for (int i = 0; i < sizeof(neighbors) / sizeof(glm::ivec2); i++) { + auto offset = neighbors[i]; + auto point = node.pos + glm::ivec3(offset.x, 0, offset.y); + if (blocks_agent::is_obstacle_at( + chunks, point.x + 0.5f, point.y + 0.5f, point.z + 0.5f + )) { + continue; + } + if (i >= 4) { + auto a = node.pos + glm::ivec3(offset.x, 0, offset.y); + auto b = node.pos + glm::ivec3(offset.x, 0, offset.y); + if (blocks_agent::is_obstacle_at(chunks, a.x, a.y, a.z)) + continue; + if (blocks_agent::is_obstacle_at(chunks, b.x, b.y, b.z)) + continue; + } + if (blocked.find(point) != blocked.end()) { + continue; + } + float gScore = + node.gScore + glm::abs(offset.x) + glm::abs(offset.y); + const auto& foundParent = parents.find(point); + bool queued = foundParent != parents.end(); + if (!queued || gScore < foundParent->second.gScore) { + float hScore = distance(point, end); + float fScore = gScore + hScore; + Node nNode {point, node.pos, gScore, fScore}; + parents[point] = Node {node.pos, node.parent, gScore, fScore}; + queue.push(nNode); + } + } + } + return route; +} diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp new file mode 100644 index 00000000..cefecdb2 --- /dev/null +++ b/src/voxels/Pathfinding.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + +class Level; + +namespace voxels { + struct RouteNode { + glm::ivec3 pos; + }; + + struct Route { + bool found; + std::vector nodes; + }; + + struct Map { + int width; + int height; + std::unique_ptr map; + + Map(int width, int height) + : width(width), + height(height), + map(std::make_unique(width * height)) { + } + + uint8_t& operator[](int i) { + return map[i]; + } + + const uint8_t& operator[](int i) const { + return map[i]; + } + }; + + class Pathfinding { + public: + Pathfinding(const Level& level); + + Route perform(const glm::ivec3& start, const glm::ivec3& end); + private: + const Level& level; + }; +} diff --git a/src/world/Level.cpp b/src/world/Level.cpp index 159b79de..c33c7e0f 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -12,6 +12,7 @@ #include "settings.hpp" #include "voxels/Chunk.hpp" #include "voxels/GlobalChunks.hpp" +#include "voxels/Pathfinding.hpp" #include "window/Camera.hpp" #include "LevelEvents.hpp" #include "World.hpp" @@ -27,7 +28,8 @@ Level::Level( physics(std::make_unique(glm::vec3(0, -22.6f, 0))), events(std::make_unique()), entities(std::make_unique(*this)), - players(std::make_unique(*this)) { + players(std::make_unique(*this)), + pathfinding(std::make_unique(*this)) { const auto& worldInfo = world->getInfo(); auto& cameraIndices = content.getIndices(ResourceType::CAMERA); for (size_t i = 0; i < cameraIndices.size(); i++) { diff --git a/src/world/Level.hpp b/src/world/Level.hpp index 8809b3f1..0dd3251f 100644 --- a/src/world/Level.hpp +++ b/src/world/Level.hpp @@ -18,6 +18,10 @@ class Camera; class Players; struct EngineSettings; +namespace voxels { + class Pathfinding; +} + /// @brief A level, contains chunks and objects class Level { std::unique_ptr world; @@ -30,6 +34,7 @@ public: std::unique_ptr events; std::unique_ptr entities; std::unique_ptr players; + std::unique_ptr pathfinding; std::vector> cameras; // move somewhere? Level( From aae642a13e6d4eb18ed887f91040188a6f4e9943 Mon Sep 17 00:00:00 2001 From: Onran <100285264+Onran0@users.noreply.github.com> Date: Sat, 2 Aug 2025 02:26:43 +0900 Subject: [PATCH 028/177] Streaming I/O and support of named pipes (#570) * added streaming i/o for scripting, and a byteutil.get_size function * added i/o stream class, also added named pipes support on lua side via ffi * added constant file.named_pipes_prefix * added buffered and yield modes for io_stream * added new time function for work with UTC - utc_time, utc_offset, local_time * docs updated * constant pid moved to os.pid * now gmtime_s and localtime_s used only in windows --- doc/ru/scripting/builtins/libfile.md | 23 + doc/ru/scripting/builtins/libtime.md | 18 + doc/ru/scripting/extensions.md | 6 + doc/ru/scripting/io_stream.md | 188 +++++++++ .../internal/stream_providers/file.lua | 17 + .../internal/stream_providers/named_pipe.lua | 7 + .../named_pipe_path_validate.lua | 21 + .../stream_providers/named_pipe_unix.lua | 104 +++++ .../stream_providers/named_pipe_windows.lua | 144 +++++++ res/modules/io_stream.lua | 398 ++++++++++++++++++ res/scripts/stdlib.lua | 23 +- src/logic/scripting/descriptors_manager.cpp | 104 +++++ src/logic/scripting/descriptors_manager.hpp | 39 ++ src/logic/scripting/lua/libs/libbyteutil.cpp | 7 + src/logic/scripting/lua/libs/libfile.cpp | 151 +++++++ src/logic/scripting/lua/libs/libtime.cpp | 54 +++ 16 files changed, 1303 insertions(+), 1 deletion(-) create mode 100644 doc/ru/scripting/io_stream.md create mode 100644 res/modules/internal/stream_providers/file.lua create mode 100644 res/modules/internal/stream_providers/named_pipe.lua create mode 100644 res/modules/internal/stream_providers/named_pipe_path_validate.lua create mode 100644 res/modules/internal/stream_providers/named_pipe_unix.lua create mode 100644 res/modules/internal/stream_providers/named_pipe_windows.lua create mode 100644 res/modules/io_stream.lua create mode 100644 src/logic/scripting/descriptors_manager.cpp create mode 100644 src/logic/scripting/descriptors_manager.hpp diff --git a/doc/ru/scripting/builtins/libfile.md b/doc/ru/scripting/builtins/libfile.md index 6f35306d..e06e420c 100644 --- a/doc/ru/scripting/builtins/libfile.md +++ b/doc/ru/scripting/builtins/libfile.md @@ -183,3 +183,26 @@ file.join(директория: str, путь: str) --> str Соединяет путь. Пример: `file.join("world:data", "base/config.toml)` -> `world:data/base/config.toml`. Следует использовать данную функцию вместо конкатенации с `/`, так как `префикс:/путь` не является валидным. + +```lua +file.open(путь: str, режим: str) --> io_stream +``` + +Открывает поток для записи/чтения в файл по пути `путь`. + +Аргумент `режим` это список отдельных режимов, в котором каждый обозначается одним символом + +`r` - Чтение из файла +`w` - Запись в файл +`b` - Открыть поток в двоичном режиме (см. `../io_stream.md`) +`+` - Работает совместно с `w`. Добавляет к существующим данным новые (`append-mode`) + +```lua +file.open_named_pipe(имя: str, режим: str) -> io_stream +``` + +Открывает поток для записи/чтения в Named Pipe по пути `путь` + +`/tmp/` или `\\\\.\\pipe\\` добавлять не нужно - движок делает это автоматически. + +Доступные режимы такие же, как и в `file.open`, за исключением `+` \ No newline at end of file diff --git a/doc/ru/scripting/builtins/libtime.md b/doc/ru/scripting/builtins/libtime.md index d62a7af2..5bf9ad01 100644 --- a/doc/ru/scripting/builtins/libtime.md +++ b/doc/ru/scripting/builtins/libtime.md @@ -11,3 +11,21 @@ time.delta() -> float ``` Возвращает дельту времени (время прошедшее с предыдущего кадра) + +```python +time.utc_time() -> int +``` + +Возвращает время UTC в секундах + +```python +time.local_time() -> int +``` + +Возвращает локальное (системное) время в секундах + +```python +time.utc_offset() -> int +``` + +Возвращает смещение локального времени от UTC в секундах \ No newline at end of file diff --git a/doc/ru/scripting/extensions.md b/doc/ru/scripting/extensions.md index 32798e43..0de8e4a7 100644 --- a/doc/ru/scripting/extensions.md +++ b/doc/ru/scripting/extensions.md @@ -258,3 +258,9 @@ function sleep(timesec: number) ``` Вызывает остановку корутины до тех пор, пока не пройдёт количество секунд, указанное в **timesec**. Функция может быть использована только внутри корутины. + +```lua +os.pid -> number +``` + +Константа, в которой хранится PID текущего инстанса движка \ No newline at end of file diff --git a/doc/ru/scripting/io_stream.md b/doc/ru/scripting/io_stream.md new file mode 100644 index 00000000..16a978db --- /dev/null +++ b/doc/ru/scripting/io_stream.md @@ -0,0 +1,188 @@ +# Класс *io_stream* + +Класс, предназначенный для работы с потоками + +## Режимы + +Поток имеет три различных вида режима: + +**general** - Общий режим работы I/O +**binary** - Формат записи и чтения I/O +**flush** - Режим работы flush + +### general + +Имеет три режима: + +**default** - Дефолтный режим работы потока. При read может вернуть только часть от требуемых данных, при write сразу записывает данные в поток. + +**yield** - Почти тоже самое, что и **default**, но всегда будет возвращать все требуемые данные. Пока они не будут прочитаны, будет вызывать `coroutine.yield()`. Предназначен для работы в корутинах. + +**buffered** - Буферизирует записываемые и читаемые данные. + +При вызове `available`/`read` обновляет буфер чтения. + +После обновления в `read`, если буфер чтения переполнен, то бросает ошибку `buffer overflow`. + +Если требуемого кол-ва байт недостаточно в буфере для чтения, то бросает ошибку `buffer-underflow`. + +При вызове `write` записывает итоговые байты в буфер для записи. Если он переполнен, то бросает ошибку `buffer overflow`. + +При вызове `flush` проталкивает данные из буфера для записи в напрямую в поток + +### flush + +**all** - Сначала проталкивает данные из буфера напрямую в поток (если используется **buffered** режим), а после вызывает `flush` напрямую из библиотеки + +**buffer** - Только проталкивает данные из буфера в поток (если используется **buffered** режим) + +## Методы + +Методы, позволяющие изменить или получить различные режимы поведения потока + +```lua +-- Возвращает true, если поток используется в двоичном режиме +io_stream:is_binary_mode() --> bool + +-- Включает или выключает двоичный режим +io_stream:set_binary_mode(bool) + +-- Возвращает режим работы потока +io_stream:get_mode() --> string + +-- Задаёт режим работы потока. Выбрасывает ошибку, если передан неизвестный режим +io_stream:set_mode(string) + +-- Возвращает режим работы flush +io_stream:get_flush_mode() --> string + +-- Задаёт режим работы flush +io_stream:set_flush_mode(string) +``` + +I/O методы + +```lua + +--[[ +Читает данные из потока + +В двоичном режиме: + +Если arg - int, то читает из потока arg байт и возвращает ввиде Bytearray или таблицы, если useTable = true + +Если arg - string, то функция интерпретирует arg как шаблон для byteutil. Прочитает кол-во байт, которое определено шаблоном, передаст их в byteutil.unpack и вернёт результат + + +В текстовом режиме: + +Если arg - int, то читает нужное кол-во строк с окончанием CRLF/LF из arg и возвращает ввиде таблицы. Также, если trimEmptyLines = true, то удаляет пустые строки с начала и конца из итоговой таблицы + +Если arg не определён, то читает одну строку с окончанием CRLF/LF и возвращает её. +--]] +io_stream:read( + [опционально] arg: int | string, + [опционально] useTable | trimEmptyLines: bool +) --> Bytearray | table | string | table | ... + +--[[ +Записывает данные в поток + +В двоичном режиме: + +Если arg - string, то функция интерпретирует arg как шаблон для byteutil, передаст его и ... в byteutil.pack и результат запишет в поток + +Если arg - Bytearray | table, то записывает байты в поток + +В текстовом режиме: + +Если arg - string, то записывает строку в поток (вместе с окончанием LF) + +Если arg - table, то записывает каждую строку из таблицы отдельно +--]] +io_stream:write( + arg: Bytearray | table | string | table, + [опционально] ... +) + +-- Читает одну строку с окончанием CRLF/LF из потока вне зависимости от двоичного режима +io_stream:read_line() --> string + +-- Записывает одну строку с окончанием LF в поток вне зависимости от двоичного режима +io_stream:write_line(string) + +--[[ + +В двоичном режиме: + +Читает все доступные байты из потока и возвращает ввиде Bytearray или table, если useTable = true + +В текстовом режиме: + +Читает все доступные строки из потока в table если useTable = true, или в одну строку вместе с окончаниями, если нет + +--]] +io_stream:read_fully( + [опционально] useTable: bool +) --> Bytearray | table | table | string +``` + +Методы, имеющие смысл в использовании только в buffered режиме + +```lua +--[[ + +Если length определён, то возвращает true, если length байт доступно к чтению. Иначе возвращает false + +Если не определён, то возвращает количество байт, которое можно прочитать + +--]] +io_stream:available( + [опционально] length: int +) --> int | bool + +-- Возвращает максимальный размер буферов +io_stream:get_max_buffer_size() --> int + +-- Задаёт новый максимальный размер буферов +io_stream:set_max_buffer_size(max_size: int) +``` + +Методы, контролирующие состояние потока + +```lua + +-- Возвращает true, если поток открыт на данный момент +io_stream:is_alive() --> bool + +-- Возвращает true, если поток закрыт на данный момент +io_stream:is_closed() --> bool + +-- Закрывает поток +io_stream:close() + +--[[ + +Записывает все данные из write-буфера в поток в buffer/all flush-режимах +Вызывает ioLib.flush() в all flush-режиме + +--]] +io_stream:flush() +``` + +Создание нового потока + +```lua +--[[ + +Создаёт новый поток с переданным дескриптором и использующим переданную I/O библиотеку. (Более подробно в core:io_stream.lua) + +--]] +io_stream.new( + descriptor: int, + binaryMode: bool, + ioLib: table, + [опционально] mode: string = "default", + [опционально] flushMode: string = "all" +) -> io_stream +``` \ No newline at end of file diff --git a/res/modules/internal/stream_providers/file.lua b/res/modules/internal/stream_providers/file.lua new file mode 100644 index 00000000..80bc8c5b --- /dev/null +++ b/res/modules/internal/stream_providers/file.lua @@ -0,0 +1,17 @@ +local io_stream = require "core:io_stream" + +local lib = { + read = file.__read_descriptor, + write = file.__write_descriptor, + flush = file.__flush_descriptor, + is_alive = file.__has_descriptor, + close = file.__close_descriptor +} + +return function(path, mode) + return io_stream.new( + file.__open_descriptor(path, mode), + mode:find('b') ~= nil, + lib + ) +end \ No newline at end of file diff --git a/res/modules/internal/stream_providers/named_pipe.lua b/res/modules/internal/stream_providers/named_pipe.lua new file mode 100644 index 00000000..bdce841c --- /dev/null +++ b/res/modules/internal/stream_providers/named_pipe.lua @@ -0,0 +1,7 @@ +local FFI = ffi + +if FFI.os == "Windows" then + return require "core:internal/stream_providers/named_pipe_windows" +else + return require "core:internal/stream_providers/named_pipe_unix" +end \ No newline at end of file diff --git a/res/modules/internal/stream_providers/named_pipe_path_validate.lua b/res/modules/internal/stream_providers/named_pipe_path_validate.lua new file mode 100644 index 00000000..d2ad446d --- /dev/null +++ b/res/modules/internal/stream_providers/named_pipe_path_validate.lua @@ -0,0 +1,21 @@ +local forbiddenPaths = { + "/..\\", "\\../", + "/../", "\\..\\" +} + +return function(path) + local corrected = true + + if path:starts_with("../") or path:starts_with("..\\") then + corrected = false + else + for _, forbiddenPath in ipairs(forbiddenPaths) do + if path:find(forbiddenPath) then + corrected = false + break + end + end + end + + if not corrected then error "special path \"../\" is not allowed in path to named pipe" end +end \ No newline at end of file diff --git a/res/modules/internal/stream_providers/named_pipe_unix.lua b/res/modules/internal/stream_providers/named_pipe_unix.lua new file mode 100644 index 00000000..0a9c8904 --- /dev/null +++ b/res/modules/internal/stream_providers/named_pipe_unix.lua @@ -0,0 +1,104 @@ +local path_validate = require "core:internal/stream_providers/named_pipe_path_validate" +local io_stream = require "core:io_stream" + +local FFI = ffi + +FFI.cdef[[ +int open(const char *pathname, int flags); +int close(int fd); +ssize_t read(int fd, void *buf, size_t count); +ssize_t write(int fd, const void *buf, size_t count); +int fcntl(int fd, int cmd, ...); + +const char *strerror(int errnum); +]] + +local C = FFI.C + +local O_RDONLY = 0x0 +local O_WRONLY = 0x1 +local O_RDWR = 0x2 +local O_NONBLOCK = 0x800 +local F_GETFL = 3 + +local function getError() + local err = ffi.errno() + + return ffi.string(C.strerror(err)).." ("..err..")" +end + +local lib = {} + +function lib.read(fd, len) + local buffer = FFI.new("uint8_t[?]", len) + local result = C.read(fd, buffer, len) + + local out = Bytearray() + + if result <= 0 then + return out + end + + for i = 0, result - 1 do + out[i+1] = buffer[i] + end + + return out +end + +function lib.write(fd, bytearray) + local len = #bytearray + local buffer = FFI.new("uint8_t[?]", len) + for i = 1, len do + buffer[i-1] = bytearray[i] + end + + if C.write(fd, buffer, len) == -1 then + error("failed to write to named pipe: "..getError()) + end +end + +function lib.flush(fd) + -- no flush on unix +end + +function lib.is_alive(fd) + if fd == nil or fd < 0 then return false end + + return C.fcntl(fd, F_GETFL) ~= -1 +end + +function lib.close(fd) + C.close(fd) +end + +return function(path, mode) + path_validate(path) + + path = "/tmp/"..path + + local read = mode:find('r') ~= nil + local write = mode:find('w') ~= nil + + local flags + + if read and write then + flags = O_RDWR + elseif read then + flags = O_RDONLY + elseif write then + flags = O_WRONLY + else + error "mode must contain read or write flag" + end + + flags = bit.bor(flags, O_NONBLOCK) + + local fd = C.open(path, flags) + + if fd == -1 then + error("failed to open named pipe: "..getError()) + end + + return io_stream.new(fd, mode:find('b') ~= nil, lib) +end \ No newline at end of file diff --git a/res/modules/internal/stream_providers/named_pipe_windows.lua b/res/modules/internal/stream_providers/named_pipe_windows.lua new file mode 100644 index 00000000..83691aa5 --- /dev/null +++ b/res/modules/internal/stream_providers/named_pipe_windows.lua @@ -0,0 +1,144 @@ +local path_validate = require "core:internal/stream_providers/named_pipe_path_validate" +local io_stream = require "core:io_stream" + +local FFI = ffi + +FFI.cdef[[ +typedef void* HANDLE; +typedef uint32_t DWORD; +typedef int BOOL; +typedef void* LPVOID; +typedef const char* LPCSTR; + +BOOL CloseHandle(HANDLE hObject); +DWORD GetFileType(HANDLE hFile); +BOOL ReadFile(HANDLE hFile, void* lpBuffer, DWORD nNumberOfBytesToRead, + DWORD* lpNumberOfBytesRead, void* lpOverlapped); +BOOL WriteFile(HANDLE hFile, const void* lpBuffer, DWORD nNumberOfBytesToWrite, + DWORD* lpNumberOfBytesWritten, void* lpOverlapped); +HANDLE CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, + void* lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); + +BOOL PeekNamedPipe( + HANDLE hNamedPipe, + LPVOID lpBuffer, + DWORD nBufferSize, + DWORD* lpBytesRead, + DWORD* lpTotalBytesAvail, + DWORD* lpBytesLeftThisMessage +); + +DWORD GetLastError(void); +BOOL FlushFileBuffers(HANDLE hFile); +]] + +local C = FFI.C + +local GENERIC_READ = 0x80000000 +local GENERIC_WRITE = 0x40000000 +local OPEN_EXISTING = 3 +local FILE_ATTRIBUTE_NORMAL = 0x00000080 +local FILE_TYPE_UNKNOWN = 0x0000 +local INVALID_HANDLE_VALUE = FFI.cast("HANDLE", -1) + +local lib = {} + +local function is_data_available(handle) + local bytes_available = FFI.new("DWORD[1]") + local success = FFI.C.PeekNamedPipe(handle, nil, 0, nil, bytes_available, nil) + + if success == 0 then + return -1 + end + + return bytes_available[0] > 0 +end + +function lib.read(handle, len) + local out = Bytearray() + + local has_data, err = is_data_available(handle) + + if not has_data then + return out + elseif hasData == -1 then + error("failed to read from named pipe: "..tostring(C.GetLastError())) + end + + local buffer = FFI.new("uint8_t[?]", len) + local read = FFI.new("DWORD[1]") + + local ok = C.ReadFile(handle, buffer, len, read, nil) + + if ok == 0 or read[0] == 0 then + return out + end + + for i = 0, read[0] - 1 do + out[i+1] = buffer[i] + end + + return out +end + +function lib.write(handle, bytearray) + local len = #bytearray + + local buffer = FFI.new("uint8_t[?]", len) + for i = 1, len do + buffer[i-1] = bytearray[i] + end + + local written = FFI.new("DWORD[1]") + + if C.WriteFile(handle, buffer, len, written, nil) == 0 then + error("failed to write to named pipe: "..tostring(C.GetLastError())) + end +end + +function lib.flush(handle) + C.FlushFileBuffers(handle) +end + +function lib.is_alive(handle) + if handle == nil or handle == INVALID_HANDLE_VALUE then + return false + else + return C.GetFileType(handle) ~= FILE_TYPE_UNKNOWN + end +end + +function lib.close(handle) + C.CloseHandle(handle) +end + +return function(path, mode) + path_validate(path) + + path = "\\\\.\\pipe\\"..path + + local read = mode:find('r') ~= nil + local write = mode:find('w') ~= nil + + local flags + + if read and write then + flags = bit.bor(GENERIC_READ, GENERIC_WRITE) + elseif read then + flags = GENERIC_READ + elseif write then + flags = GENERIC_WRITE + else + error("mode must contain read or write flag") + end + + local handle = C.CreateFileA(path, flags, 0, nil, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nil) + + if handle == INVALID_HANDLE_VALUE then + error("failed to open named pipe: "..tostring(C.GetLastError())) + end + + return io_stream.new(handle, mode:find('b') ~= nil, lib) +end \ No newline at end of file diff --git a/res/modules/io_stream.lua b/res/modules/io_stream.lua new file mode 100644 index 00000000..185a29c2 --- /dev/null +++ b/res/modules/io_stream.lua @@ -0,0 +1,398 @@ +local io_stream = { } + +io_stream.__index = io_stream + +local MAX_BUFFER_SIZE = 8192 + +local DEFAULT_MODE = "default" +local BUFFERED_MODE = "buffered" +local YIELD_MODE = "yield" + +local ALL_MODES = { + DEFAULT_MODE, + BUFFERED_MODE, + YIELD_MODE +} + +local FLUSH_MODE_ALL = "all" +local FLUSH_MODE_ONLY_BUFFER = "buffer" + +local ALL_FLUSH_MODES = { + FLUSH_MODE_ALL, + FLUSH_MODE_ONLY_BUFFER +} + +local CR = string.byte('\r') +local LF = string.byte('\n') + +local function readFully(result, readFunc) + local isTable = type(result) == "table" + + local buf + + repeat + buf = readFunc(MAX_BUFFER_SIZE) + + if isTable then + for i = 1, #buf do + result[#result + 1] = buf[i] + end + else result:append(buf) end + until #buf == 0 +end + +--[[ + +descriptor - descriptor of stream for provided I/O library +binaryMode - if enabled, most methods will expect bytes instead of strings +ioLib - I/O library. Should include the following functions: + read(descriptor: int, length: int) -> Bytearray + May return bytearray with a smaller size if bytes have not arrived yet or have run out + write(descriptor: int, data: Bytearray) + flush(descriptor: int) + is_alive(descriptor: int) -> bool + close(descriptor: int) +--]] + +function io_stream.new(descriptor, binaryMode, ioLib, mode, flushMode) + mode = mode or DEFAULT_MODE + flushMode = flushMode or FLUSH_MODE_ALL + + local self = setmetatable({}, io_stream) + + self.descriptor = descriptor + self.binaryMode = binaryMode + self.maxBufferSize = MAX_BUFFER_SIZE + self.ioLib = ioLib + + self:set_mode(mode) + self:set_flush_mode(flushMode) + + return self +end + +function io_stream:is_binary_mode() + return self.binaryMode +end + +function io_stream:set_binary_mode(binaryMode) + self.binaryMode = binaryMode ~= nil +end + +function io_stream:get_mode() + return self.mode +end + +function io_stream:set_mode(mode) + if not table.has(ALL_MODES, mode) then + error("invalid stream mode: "..mode) + end + + if self.mode == BUFFERED_MODE then + self.writeBuffer:clear() + self.readBuffer:clear() + end + + if mode == BUFFERED_MODE and not self.writeBuffer then + self.writeBuffer = Bytearray() + self.readBuffer = Bytearray() + end + + self.mode = mode +end + +function io_stream:get_flush_mode() + return self.flushMode +end + +function io_stream:set_flush_mode(flushMode) + if not table.has(ALL_FLUSH_MODES, flushMode) then + error("invalid flush mode: "..flushMode) + end + + self.flushMode = flushMode +end + +function io_stream:get_max_buffer_size() + return self.maxBufferSize +end + +function io_stream:set_max_buffer_size(maxBufferSize) + self.maxBufferSize = maxBufferSize +end + +function io_stream:available(length) + if self.mode == BUFFERED_MODE then + self:__update_read_buffer() + + if not length then + return #self.readBuffer + else + return #self.readBuffer >= length + end + end +end + +function io_stream:__update_read_buffer() + local readed = Bytearray() + + readFully(readed, function(length) return self.ioLib.read(self.descriptor, length) end) + + self.readBuffer:append(readed) + + if #self.readBuffer > self.maxBufferSize then + error "buffer overflow" + end +end + +function io_stream:__read(length) + if self.mode == YIELD_MODE then + local buffer = Bytearray() + + while #buffer < length do + buffer:append(self.ioLib.read(self.descriptor, length - #buffer)) + + if #buffer < length then coroutine.yield() end + end + + return buffer + elseif self.mode == BUFFERED_MODE then + self:__update_read_buffer() + + if #self.readBuffer < length then + error "buffer underflow" + end + + local copy + + if #self.readBuffer == length then + copy = Bytearray() + + copy:append(self.readBuffer) + + self.readBuffer:clear() + else + copy = Bytearray() + + for i = 1, length do + copy[i] = self.readBuffer[i] + end + + self.readBuffer:remove(1, length) + end + + return copy + elseif self.mode == DEFAULT_MODE then + return self.ioLib.read(self.descriptor, length) + end +end + +function io_stream:__write(data) + if self.mode == BUFFERED_MODE then + self.writeBuffer:append(data) + + if #self.writeBuffer > self.maxBufferSize then + error "buffer overflow" + end + elseif self.mode == DEFAULT_MODE or self.mode == YIELD_MODE then + return self.ioLib.write(self.descriptor, data) + end +end + +function io_stream:read_fully(useTable) + if self.binaryMode then + local result = useTable and Bytearray() or { } + + readFully(result, function() return self:__read(self.maxBufferSize) end) + else + if useTable then + local lines = { } + + local line + + repeat + line = self:read_line() + + lines[#lines + 1] = line + until not line + + return lines + else + local result = Bytearray() + + readFully(result, function() return self:__read(self.maxBufferSize) end) + + return utf8.tostring(result) + end + end +end + +function io_stream:read_line() + local result = Bytearray() + + local first = true + + while true do + local char = self:__read(1) + + if #char == 0 then + if first then return else break end + end + + char = char[1] + + if char == LF then break + elseif char == CR then + char = self:__read(1) + + if char[1] == LF then break + else + result:append(CR) + result:append(char[1]) + end + else result:append(char) end + + first = false + end + + return utf8.tostring(result) +end + +function io_stream:write_line(str) + self:__write(utf8.tobytes(str .. LF)) +end + +function io_stream:read(arg, useTable) + local argType = type(arg) + + if self.binaryMode then + local byteArr + + if argType == "number" then + -- using 'arg' as length + + byteArr = self:__read(arg) + + if useTable == true then + local t = { } + + for i = 1, #byteArr do + t[i] = byteArr[i] + end + + return t + else + return byteArr + end + elseif argType == "string" then + return byteutil.unpack( + arg, + self:__read(byteutil.get_size(arg)) + ) + elseif argType == nil then + error( + "in binary mode the first argument must be a string data format".. + " for the library \"byteutil\" or the number of bytes to read" + ) + else + error("unknown argument type: "..argType) + end + else + if not arg then + return self:read_line() + else + local linesCount = arg + local trimLastEmptyLines = useTable or true + + if linesCount < 0 then error "count of lines to read must be positive" end + + local result = { } + + for i = 1, linesCount do + result[i] = self:read_line() + end + + if trimLastEmptyLines then + local i = #result + + while i >= 0 do + local length = utf8.length(result[i]) + + if length > 0 then break + else result[i] = nil end + + i = i - 1 + end + + local i = 1 + + while #result > 0 do + local length = utf8.length(result[i]) + + if length > 0 then break + else table.remove(result, i) end + end + end + + return result + end + end +end + +function io_stream:write(arg, ...) + local argType = type(arg) + + if self.binaryMode then + local byteArr + + if argType ~= "string" then + -- using arg as bytes table/bytearray + + if argType == "table" then + byteArr = Bytearray(arg) + else + byteArr = arg + end + else + byteArr = byteutil.pack(arg, ...) + end + + self:__write(byteArr) + else + if argType == "string" then + self:write_line(arg) + elseif argType == "table" then + for i = 1, #arg do + self:write_line(arg[i]) + end + else error("unknown argument type: "..argType) end + end +end + +function io_stream:is_alive() + return self.ioLib.is_alive(self.descriptor) +end + +function io_stream:is_closed() + return not self:is_alive() +end + +function io_stream:close() + if self.mode == BUFFERED_MODE then + self.readBuffer:clear() + self.writeBuffer:clear() + end + + return self.ioLib.close(self.descriptor) +end + +function io_stream:flush() + if self.mode == BUFFERED_MODE and #self.writeBuffer > 0 then + self.ioLib.write(self.descriptor, self.writeBuffer) + self.writeBuffer:clear() + end + + if self.flushMode ~= FLUSH_MODE_ONLY_BUFFER then self.ioLib.flush(self.descriptor) end +end + +return io_stream \ No newline at end of file diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 765d2c08..a5142fdf 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -317,10 +317,30 @@ entities.get_all = function(uids) return stdcomp.get_all(uids) end end + local bytearray = require "core:internal/bytearray" + Bytearray = bytearray.FFIBytearray Bytearray_as_string = bytearray.FFIBytearray_as_string Bytearray_construct = function(...) return Bytearray(...) end + +file.open = require "core:internal/stream_providers/file" +file.open_named_pipe = require "core:internal/stream_providers/named_pipe" + +if ffi.os == "Windows" then + ffi.cdef[[ + unsigned long GetCurrentProcessId(); + ]] + + os.pid = ffi.C.GetCurrentProcessId() +else + ffi.cdef[[ + int getpid(void); + ]] + + os.pid = ffi.C.getpid() +end + ffi = nil math.randomseed(time.uptime() * 1536227939) @@ -473,6 +493,7 @@ function __vc_on_world_quit() _rules.clear() gui_util:__reset_local() stdcomp.__reset() + file.__close_all_descriptors() end local __vc_coroutines = {} @@ -629,4 +650,4 @@ function dofile(path) end end return _dofile(path) -end +end \ No newline at end of file diff --git a/src/logic/scripting/descriptors_manager.cpp b/src/logic/scripting/descriptors_manager.cpp new file mode 100644 index 00000000..f7458ccc --- /dev/null +++ b/src/logic/scripting/descriptors_manager.cpp @@ -0,0 +1,104 @@ +#include "logic/scripting/descriptors_manager.hpp" + +#include "debug/Logger.hpp" + +static debug::Logger logger("descriptors-manager"); + +namespace scripting { + + std::vector> descriptors_manager::descriptors; + + std::istream* descriptors_manager::get_input(int descriptor) { + if (!is_readable(descriptor)) + return nullptr; + + return descriptors[descriptor]->in.get(); + } + + std::ostream* descriptors_manager::get_output(int descriptor) { + if (!is_writeable(descriptor)) + return nullptr; + + return descriptors[descriptor]->out.get(); + } + + void descriptors_manager::flush(int descriptor) { + if (is_writeable(descriptor)) { + descriptors[descriptor]->out->flush(); + } + } + + bool descriptors_manager::has_descriptor(int descriptor) { + return is_readable(descriptor) || is_writeable(descriptor); + } + + bool descriptors_manager::is_readable(int descriptor) { + return descriptor >= 0 && descriptor < static_cast(descriptors.size()) + && descriptors[descriptor].has_value() + && descriptors[descriptor]->in != nullptr; + } + + bool descriptors_manager::is_writeable(int descriptor) { + return descriptor >= 0 && descriptor < static_cast(descriptors.size()) + && descriptors[descriptor].has_value() + && descriptors[descriptor]->out != nullptr; + } + + void descriptors_manager::close(int descriptor) { + if (descriptor >= 0 && descriptor < static_cast(descriptors.size())) { + if (descriptors[descriptor].has_value()) { + auto& desc = descriptors[descriptor].value(); + + if (desc.out) + desc.out->flush(); + + desc.in.reset(); + desc.out.reset(); + } + + descriptors[descriptor].reset(); + + descriptors[descriptor] = std::nullopt; + } + } + + int descriptors_manager::open_descriptor(const io::path& path, bool write, bool read) { + std::unique_ptr in; + std::unique_ptr out; + + try { + if (read) + in = io::read(path); + + if (write) + out = io::write(path); + } catch (const std::exception& e) { + logger.error() << "failed to open descriptor for " << path.string() + << ": " << e.what(); + + return -1; + } + + for (int i = 0; i < static_cast(descriptors.size()); ++i) { + if (!descriptors[i].has_value()) { + descriptors[i] = StreamDescriptor{ std::move(in), std::move(out) }; + return i; + } + } + + descriptors.emplace_back(StreamDescriptor{ std::move(in), std::move(out) }); + + return static_cast(descriptors.size() - 1); + } + + + void descriptors_manager::close_all_descriptors() { + for (int i = 0; i < static_cast(descriptors.size()); ++i) { + if (descriptors[i].has_value()) { + close(i); + } + } + + descriptors.clear(); + } +} \ No newline at end of file diff --git a/src/logic/scripting/descriptors_manager.hpp b/src/logic/scripting/descriptors_manager.hpp new file mode 100644 index 00000000..ccd1b67a --- /dev/null +++ b/src/logic/scripting/descriptors_manager.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "io/io.hpp" + +namespace scripting { + + struct StreamDescriptor { + std::unique_ptr in; + std::unique_ptr out; + }; + + class descriptors_manager { + private: + static std::vector> descriptors; + + public: + static std::istream* get_input(int descriptor); + static std::ostream* get_output(int descriptor); + + static void flush(int descriptor); + + static bool has_descriptor(int descriptor); + + static bool is_readable(int descriptor); + static bool is_writeable(int descriptor); + + static void close(int descriptor); + static int open_descriptor(const io::path& path, bool write, bool read); + + static void close_all_descriptors(); + }; +} \ No newline at end of file diff --git a/src/logic/scripting/lua/libs/libbyteutil.cpp b/src/logic/scripting/lua/libs/libbyteutil.cpp index 76bd9d37..54485fc1 100644 --- a/src/logic/scripting/lua/libs/libbyteutil.cpp +++ b/src/logic/scripting/lua/libs/libbyteutil.cpp @@ -197,9 +197,16 @@ static int l_tpack(lua::State* L) { return pack(L, format, true); } +static int l_get_size(lua::State* L) { + return lua::pushinteger( + L, static_cast(calc_size(lua::require_string(L, 1))) + ); +} + const luaL_Reg byteutillib[] = { {"pack", lua::wrap}, {"tpack", lua::wrap}, {"unpack", lua::wrap}, + {"get_size", lua::wrap}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/libs/libfile.cpp b/src/logic/scripting/lua/libs/libfile.cpp index 717ff52b..f5cb2bfc 100644 --- a/src/logic/scripting/lua/libs/libfile.cpp +++ b/src/logic/scripting/lua/libs/libfile.cpp @@ -9,6 +9,7 @@ #include "util/stringutil.hpp" #include "api_lua.hpp" #include "../lua_engine.hpp" +#include "logic/scripting/descriptors_manager.hpp" namespace fs = std::filesystem; using namespace scripting; @@ -258,6 +259,149 @@ static int l_create_zip(lua::State* L) { return 0; } +static int l_open_descriptor(lua::State* L) { + io::path path = lua::require_string(L, 1); + auto mode = lua::require_lstring(L, 2); + + bool write = mode.find('w') != std::string::npos; + bool read = mode.find('r') != std::string::npos; + + if (write && !is_writeable(path.entryPoint())) { + throw std::runtime_error("access denied"); + } + + if(!write && !read) { + throw std::runtime_error("mode must contain read or write flag"); + } + + if(write && read) { + throw std::runtime_error("random access file i/o is not supported"); + } + + bool wplusMode = write && mode.find('+') != std::string::npos; + + std::vector buffer; + + if(wplusMode) { + int temp_descriptor = scripting::descriptors_manager::open_descriptor(path, false, true); + + if (temp_descriptor == -1) { + throw std::runtime_error("failed to open descriptor for initial reading"); + } + + auto* in_stream = scripting::descriptors_manager::get_input(temp_descriptor); + + in_stream->seekg(0, std::ios::end); + std::streamsize size = in_stream->tellg(); + in_stream->seekg(0, std::ios::beg); + + buffer.resize(size); + in_stream->read(buffer.data(), size); + + scripting::descriptors_manager::close(temp_descriptor); + } + + int descriptor = scripting::descriptors_manager::open_descriptor(path, write, read); + + if(descriptor == -1) { + throw std::runtime_error("failed to open descriptor"); + } + + if(wplusMode) { + auto* out_stream = scripting::descriptors_manager::get_output(descriptor); + out_stream->write(buffer.data(), buffer.size()); + out_stream->flush(); + } + + return lua::pushinteger(L, descriptor); +} + +static int l_has_descriptor(lua::State* L) { + return lua::pushboolean(L, scripting::descriptors_manager::has_descriptor(lua::tointeger(L, 1))); +} + +static int l_read_descriptor(lua::State* L) { + int descriptor = lua::tointeger(L, 1); + + if (!scripting::descriptors_manager::has_descriptor(descriptor)) { + throw std::runtime_error("unknown descriptor"); + } + + if (!scripting::descriptors_manager::is_readable(descriptor)) { + throw std::runtime_error("descriptor is not readable"); + } + + int maxlen = lua::tointeger(L, 2); + + auto* stream = scripting::descriptors_manager::get_input(descriptor); + + util::Buffer buffer(maxlen); + + stream->read(buffer.data(), maxlen); + + std::streamsize read_len = stream->gcount(); + + return lua::create_bytearray(L, buffer.data(), read_len); +} + +static int l_write_descriptor(lua::State* L) { + int descriptor = lua::tointeger(L, 1); + + if (!scripting::descriptors_manager::has_descriptor(descriptor)) { + throw std::runtime_error("unknown descriptor"); + } + + if (!scripting::descriptors_manager::is_writeable(descriptor)) { + throw std::runtime_error("descriptor is not writeable"); + } + + auto data = lua::bytearray_as_string(L, 2); + + auto* stream = scripting::descriptors_manager::get_output(descriptor); + + stream->write(data.data(), static_cast(data.size())); + + if (!stream->good()) { + throw std::runtime_error("failed to write to stream"); + } + + return 0; +} + +static int l_flush_descriptor(lua::State* L) { + int descriptor = lua::tointeger(L, 1); + + if (!scripting::descriptors_manager::has_descriptor(descriptor)) { + throw std::runtime_error("unknown descriptor"); + } + + if (!scripting::descriptors_manager::is_writeable(descriptor)) { + throw std::runtime_error("descriptor is not writeable"); + } + + scripting::descriptors_manager::flush(descriptor); + + return 0; +} + +static int l_close_descriptor(lua::State* L) { + int descriptor = lua::tointeger(L, 1); + + if (!scripting::descriptors_manager::has_descriptor(descriptor)) { + throw std::runtime_error("unknown descriptor"); + } + + scripting::descriptors_manager::close(descriptor); + + return 0; +} + +static int l_close_all_descriptors(lua::State* L) { + scripting::descriptors_manager::close_all_descriptors(); + + return 0; +} + const luaL_Reg filelib[] = { {"exists", lua::wrap}, {"find", lua::wrap}, @@ -283,5 +427,12 @@ const luaL_Reg filelib[] = { {"mount", lua::wrap}, {"unmount", lua::wrap}, {"create_zip", lua::wrap}, + {"__open_descriptor", lua::wrap}, + {"__has_descriptor", lua::wrap}, + {"__read_descriptor", lua::wrap}, + {"__write_descriptor", lua::wrap}, + {"__flush_descriptor", lua::wrap}, + {"__close_descriptor", lua::wrap}, + {"__close_all_descriptors", lua::wrap}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/libs/libtime.cpp b/src/logic/scripting/lua/libs/libtime.cpp index 988e403a..dc878455 100644 --- a/src/logic/scripting/lua/libs/libtime.cpp +++ b/src/logic/scripting/lua/libs/libtime.cpp @@ -1,8 +1,13 @@ #include "engine/Engine.hpp" #include "api_lua.hpp" +#include using namespace scripting; +#if defined(_WIN32) || defined(_WIN64) + #define USE_MSVC_TIME_SAFE +#endif + static int l_uptime(lua::State* L) { return lua::pushnumber(L, engine->getTime().getTime()); } @@ -11,8 +16,57 @@ static int l_delta(lua::State* L) { return lua::pushnumber(L, engine->getTime().getDelta()); } +static int l_utc_time(lua::State* L) { + return lua::pushnumber(L, std::time(nullptr)); +} + +static int l_local_time(lua::State* L) { + std::time_t t = std::time(nullptr); + + std::tm gmt_tm{}; + std::tm local_tm{}; + +#if defined(USE_MSVC_TIME_SAFE) + gmtime_s(&gmt_tm, &t); + localtime_s(&local_tm, &t); +#else + gmtime_r(&t, &gmt_tm); + localtime_r(&t, &local_tm); +#endif + + std::time_t utc_time = std::mktime(&gmt_tm); + std::time_t local_time = std::mktime(&local_tm); + std::time_t offset = local_time - utc_time; + + return lua::pushnumber(L, t + offset); +} + +static int l_utc_offset(lua::State* L) { + std::time_t t = std::time(nullptr); + + std::tm gmt_tm{}; + std::tm local_tm{}; + +#if defined(USE_MSVC_TIME_SAFE) + gmtime_s(&gmt_tm, &t); + localtime_s(&local_tm, &t); +#else + gmtime_r(&t, &gmt_tm); + localtime_r(&t, &local_tm); +#endif + + std::time_t utc_time = std::mktime(&gmt_tm); + std::time_t local_time = std::mktime(&local_tm); + std::time_t offset = local_time - utc_time; + + return lua::pushnumber(L, offset); +} + const luaL_Reg timelib[] = { {"uptime", lua::wrap}, {"delta", lua::wrap}, + {"utc_time", lua::wrap}, + {"utc_offset", lua::wrap}, + {"local_time", lua::wrap}, {NULL, NULL} }; From b28aa718459f8b5b960870063d6e10946cc5ca06 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Aug 2025 21:08:23 +0300 Subject: [PATCH 029/177] add Schedule class & add time.schedule.world schedule group --- res/modules/schedule.lua | 37 +++++++++++++++++++++++++++++++ res/scripts/stdlib.lua | 33 +++++++++++++++++++++++++++ src/logic/scripting/scripting.cpp | 3 +++ 3 files changed, 73 insertions(+) create mode 100644 res/modules/schedule.lua diff --git a/res/modules/schedule.lua b/res/modules/schedule.lua new file mode 100644 index 00000000..91e47813 --- /dev/null +++ b/res/modules/schedule.lua @@ -0,0 +1,37 @@ +local Schedule = { + __index = { + set_interval = function(self, ms, callback) + local id = self._next_interval + self._intervals[id] = { + last_called = 0.0, + delay = ms / 1000.0, + callback = callback, + } + self._next_interval = id + 1 + return id + end, + tick = function(self, dt) + local timer = self._timer + dt + for id, interval in pairs(self._intervals) do + if timer - interval.last_called >= interval.delay then + xpcall(interval.callback, function(s) + debug.error(s..'\n'..debug.traceback()) + end) + interval.last_called = timer + end + end + self._timer = timer + end, + remove_interval = function (self, id) + self._intervals[id] = nil + end + } +} + +return function () + return setmetatable({ + _next_interval = 1, + _timer = 0.0, + _intervals = {}, + }, Schedule) +end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 765d2c08..060d40f0 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -450,8 +450,37 @@ function __vc_on_hud_open() hud.open_permanent("core:ingame_chat") end +local ScheduleGroup_mt = { + __index = { + publish = function(self, schedule) + local id = self._next_schedule + self._schedules[id] = schedule + self._next_schedule = id + 1 + end, + tick = function(self, dt) + for id, schedule in pairs(self._schedules) do + schedule:tick(dt) + end + end, + remove = function(self, id) + self._schedules[id] = nil + end + } +} + +local function ScheduleGroup() + return setmetatable({ + _next_schedule = 1, + _schedules = {}, + }, ScheduleGroup_mt) +end + +time.schedules = {} + local RULES_FILE = "world:rules.toml" function __vc_on_world_open() + time.schedules.world = ScheduleGroup() + if not file.exists(RULES_FILE) then return end @@ -461,6 +490,10 @@ function __vc_on_world_open() end end +function __vc_on_world_tick() + time.schedules.world:tick(1.0 / 20.0) +end + function __vc_on_world_save() local rule_values = {} for name, rule in pairs(rules.rules) do diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index f766f29d..5da5b3ba 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -299,6 +299,9 @@ void scripting::on_world_load(LevelController* controller) { void scripting::on_world_tick() { auto L = lua::get_main_state(); + if (lua::getglobal(L, "__vc_on_world_tick")) { + lua::call_nothrow(L, 0, 0); + } for (auto& pack : content_control->getAllContentPacks()) { lua::emit_event(L, pack.id + ":.worldtick"); } From ddb04418ba3cd270bb78b072bda56b50ba1d0534 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Aug 2025 21:09:03 +0300 Subject: [PATCH 030/177] add 'await' function --- res/scripts/stdmin.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 9f8c48c2..38fdbfdc 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -20,6 +20,18 @@ if not ipairs_mt_supported then end end +function await(co) + local res, err + while coroutine.status(co) ~= 'dead' do + coroutine.yield() + res, err = coroutine.resume(co) + if err then + return res, err + end + end + return res, err +end + local _ffi = ffi function __vc_Canvas_set_data(self, data) if type(data) == "cdata" then From f37bbc64f4a6f2f563f8914a7401a14fa1be2c1e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Aug 2025 21:15:04 +0300 Subject: [PATCH 031/177] add set_interval 'repetions' argument --- res/modules/schedule.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/res/modules/schedule.lua b/res/modules/schedule.lua index 91e47813..24d216f0 100644 --- a/res/modules/schedule.lua +++ b/res/modules/schedule.lua @@ -1,11 +1,12 @@ local Schedule = { __index = { - set_interval = function(self, ms, callback) + set_interval = function(self, ms, callback, repetions) local id = self._next_interval self._intervals[id] = { last_called = 0.0, delay = ms / 1000.0, callback = callback, + repetions = repetions, } self._next_interval = id + 1 return id @@ -18,6 +19,14 @@ local Schedule = { debug.error(s..'\n'..debug.traceback()) end) interval.last_called = timer + local repetions = interval.repetions + if repetions then + if repetions <= 1 then + self:remove_interval(id) + else + interval.repetions = repetions - 1 + end + end end end self._timer = timer From 8e7a5a0b8c067060fee7a25f4b4d89587b8497d8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Aug 2025 21:34:00 +0300 Subject: [PATCH 032/177] update extensions.md --- doc/ru/scripting/extensions.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/ru/scripting/extensions.md b/doc/ru/scripting/extensions.md index 32798e43..08c8fa33 100644 --- a/doc/ru/scripting/extensions.md +++ b/doc/ru/scripting/extensions.md @@ -258,3 +258,10 @@ function sleep(timesec: number) ``` Вызывает остановку корутины до тех пор, пока не пройдёт количество секунд, указанное в **timesec**. Функция может быть использована только внутри корутины. + +```lua +function await(co: coroutine) -> result, error +``` + +Ожидает завершение переданной корутины, возвращая поток управления. Функция может быть использована только внутри корутины. +Возвращает значения аналогичные возвращаемым значениям *pcall*. From 9e8acac783f41cb57ab0149c6bf86d2bf4def34c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Aug 2025 22:00:29 +0300 Subject: [PATCH 033/177] minor refactor --- res/scripts/stdlib.lua | 6 +++--- src/logic/BlocksController.cpp | 2 +- src/logic/scripting/scripting.cpp | 5 +++-- src/logic/scripting/scripting.hpp | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 6202a445..b0fb86ea 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -510,8 +510,8 @@ function __vc_on_world_open() end end -function __vc_on_world_tick() - time.schedules.world:tick(1.0 / 20.0) +function __vc_on_world_tick(tps) + time.schedules.world:tick(1.0 / tps) end function __vc_on_world_save() @@ -683,4 +683,4 @@ function dofile(path) end end return _dofile(path) -end \ No newline at end of file +end diff --git a/src/logic/BlocksController.cpp b/src/logic/BlocksController.cpp index cdc25b48..ee74bb95 100644 --- a/src/logic/BlocksController.cpp +++ b/src/logic/BlocksController.cpp @@ -127,7 +127,7 @@ void BlocksController::update(float delta, uint padding) { onBlocksTick(blocksTickClock.getPart(), blocksTickClock.getParts()); } if (worldTickClock.update(delta)) { - scripting::on_world_tick(); + scripting::on_world_tick(worldTickClock.getTickRate()); } } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 5da5b3ba..39168843 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -297,10 +297,11 @@ void scripting::on_world_load(LevelController* controller) { } } -void scripting::on_world_tick() { +void scripting::on_world_tick(int tps) { auto L = lua::get_main_state(); if (lua::getglobal(L, "__vc_on_world_tick")) { - lua::call_nothrow(L, 0, 0); + lua::pushinteger(L, tps); + lua::call_nothrow(L, 1, 0); } for (auto& pack : content_control->getAllContentPacks()) { lua::emit_event(L, pack.id + ":.worldtick"); diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 0f375746..bae5aac6 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -70,7 +70,7 @@ namespace scripting { ); void on_world_load(LevelController* controller); - void on_world_tick(); + void on_world_tick(int tps); void on_world_save(); void on_world_quit(); void cleanup(); From ec4b836b3c2a62a31042c5dda025b6a1af0d3409 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Aug 2025 23:32:35 +0300 Subject: [PATCH 034/177] feat: vertical movement and agent height --- src/voxels/Pathfinding.cpp | 143 ++++++++++++++++++++++++++++--------- src/voxels/Pathfinding.hpp | 14 +++- 2 files changed, 120 insertions(+), 37 deletions(-) diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index f235b554..59da59f2 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -22,7 +22,6 @@ struct Node { float fScore; }; - struct NodeLess { bool operator()(const Node& l, const Node& r) const { return l.fScore > r.fScore; @@ -33,9 +32,54 @@ static float distance(const glm::ivec3& a, const glm::ivec3& b) { return glm::distance(glm::vec3(a), glm::vec3(b)); } -Pathfinding::Pathfinding(const Level& level) : level(level) {} +Pathfinding::Pathfinding(const Level& level) + : level(level), chunks(*level.chunks) { +} + +static bool check_passability( + const Agent& agent, + const GlobalChunks& chunks, + const Node& node, + const glm::ivec2& offset, + bool diagonal +) { + if (!diagonal) { + return true; + } + auto a = node.pos + glm::ivec3(offset.x, 0, 0); + auto b = node.pos + glm::ivec3(0, 0, offset.y); + + for (int i = 0; i < agent.height; i++) { + if (blocks_agent::is_obstacle_at(chunks, a.x, a.y + i, a.z)) + return false; + if (blocks_agent::is_obstacle_at(chunks, b.x, b.y + i, b.z)) + return false; + } + return true; +} + +static void restore_route( + Route& route, + const Node& node, + const std::unordered_map& parents +) { + auto pos = node.pos; + while (true) { + const auto& found = parents.find(pos); + if (found == parents.end()) { + route.nodes.push_back({pos}); + break; + } + route.nodes.push_back({pos}); + pos = found->second.pos; + } +} + +Route Pathfinding::perform( + const Agent& agent, const glm::ivec3& start, const glm::ivec3& end +) { + using namespace blocks_agent; -Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { Route route {}; std::priority_queue, NodeLess> queue; @@ -50,24 +94,8 @@ Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { auto node = queue.top(); queue.pop(); - if (blocked.find(node.pos) != blocked.end()) { - continue; - } - if (node.pos.x == end.x && node.pos.z == end.z) { - auto prev = glm::ivec3(); - auto pos = node.pos; - while (pos != start) { - const auto& found = parents.find(pos); - if (found == parents.end()) { - route.nodes.push_back({pos}); - break; - } - route.nodes.push_back({pos}); - - prev = pos; - pos = found->second.pos; - } + restore_route(route, node, parents); route.nodes.push_back({start}); route.found = true; break; @@ -81,28 +109,30 @@ Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { for (int i = 0; i < sizeof(neighbors) / sizeof(glm::ivec2); i++) { auto offset = neighbors[i]; - auto point = node.pos + glm::ivec3(offset.x, 0, offset.y); - if (blocks_agent::is_obstacle_at( - chunks, point.x + 0.5f, point.y + 0.5f, point.z + 0.5f - )) { + auto pos = node.pos; + + int surface = getSurfaceAt(pos + glm::ivec3(offset.x, 0, offset.y), 1); + + if (surface == -1) { continue; } - if (i >= 4) { - auto a = node.pos + glm::ivec3(offset.x, 0, offset.y); - auto b = node.pos + glm::ivec3(offset.x, 0, offset.y); - if (blocks_agent::is_obstacle_at(chunks, a.x, a.y, a.z)) - continue; - if (blocks_agent::is_obstacle_at(chunks, b.x, b.y, b.z)) - continue; + pos.y = surface; + auto point = pos + glm::ivec3(offset.x, 0, offset.y); + + if (is_obstacle_at(chunks, pos.x, pos.y + agent.height / 2, pos.z)) { + continue; + } + if (!check_passability(agent, chunks, node, offset, i >= 4)) { + continue; } if (blocked.find(point) != blocked.end()) { continue; } + int score = glm::abs(node.pos.y - pos.y); float gScore = - node.gScore + glm::abs(offset.x) + glm::abs(offset.y); - const auto& foundParent = parents.find(point); - bool queued = foundParent != parents.end(); - if (!queued || gScore < foundParent->second.gScore) { + node.gScore + glm::abs(offset.x) + glm::abs(offset.y) + score; + const auto& found = parents.find(point); + if (found == parents.end() || gScore < found->second.gScore) { float hScore = distance(point, end); float fScore = gScore + hScore; Node nNode {point, node.pos, gScore, fScore}; @@ -113,3 +143,46 @@ Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { } return route; } + +static int check_point( + const ContentUnitIndices& defs, + const GlobalChunks& chunks, + int x, + int y, + int z +) { + auto vox = blocks_agent::get(chunks, x, y, z); + if (vox == nullptr) { + return 0; + } + const auto& def = defs.require(vox->id); + if (def.obstacle) { + return 0; + } + if (def.translucent) { + return -1; + } + return 1; +} + +int Pathfinding::getSurfaceAt(const glm::ivec3& pos, int maxDelta) { + using namespace blocks_agent; + + const auto& defs = level.content.getIndices()->blocks; + + int status; + int surface = pos.y; + if (check_point(defs, chunks, pos.x, surface, pos.z) <= 0) { + if (check_point(defs, chunks, pos.x, surface + 1, pos.z) <= 0) + return -1; + else + return surface + 1; + } else if ((status = check_point(defs, chunks, pos.x, surface - 1, pos.z)) <= 0) { + if (status == -1) + return -1; + return surface; + } else if (check_point(defs, chunks, pos.x, surface - 2, pos.z) == 0) { + return surface - 1; + } + return -1; +} diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index cefecdb2..f743fd1c 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -6,6 +6,7 @@ #include class Level; +class GlobalChunks; namespace voxels { struct RouteNode { @@ -17,6 +18,10 @@ namespace voxels { std::vector nodes; }; + struct Agent { + int height; + }; + struct Map { int width; int height; @@ -40,9 +45,14 @@ namespace voxels { class Pathfinding { public: Pathfinding(const Level& level); - - Route perform(const glm::ivec3& start, const glm::ivec3& end); + + Route perform( + const Agent& agent, const glm::ivec3& start, const glm::ivec3& end + ); private: const Level& level; + const GlobalChunks& chunks; + + int getSurfaceAt(const glm::ivec3& pos, int maxDelta); }; } From 958fc1d689fcbccd75c6817e7a0e6fa921b3088b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 2 Aug 2025 12:19:52 +0300 Subject: [PATCH 035/177] fix target reach check --- src/voxels/Pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 59da59f2..4282d5bb 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -94,7 +94,7 @@ Route Pathfinding::perform( auto node = queue.top(); queue.pop(); - if (node.pos.x == end.x && node.pos.z == end.z) { + if (node.pos.x == end.x && glm::abs((node.pos.y - end.y) / agent.height) == 0 && node.pos.z == end.z) { restore_route(route, node, parents); route.nodes.push_back({start}); route.found = true; From caab689731719a2a70f7ad944020bd7404562845 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 2 Aug 2025 17:18:40 +0300 Subject: [PATCH 036/177] add pathfinding library --- src/graphics/render/WorldRenderer.cpp | 31 +++++++++++ src/logic/scripting/lua/libs/api_lua.hpp | 1 + .../scripting/lua/libs/libpathfinding.cpp | 55 +++++++++++++++++++ src/logic/scripting/lua/lua_engine.cpp | 1 + src/voxels/Pathfinding.cpp | 24 +++++++- src/voxels/Pathfinding.hpp | 16 +++++- 6 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 src/logic/scripting/lua/libs/libpathfinding.cpp diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index fba83c64..d7290998 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -27,6 +27,7 @@ #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" +#include "voxels/Pathfinding.hpp" #include "window/Window.hpp" #include "world/Level.hpp" #include "world/LevelEvents.hpp" @@ -386,6 +387,36 @@ void WorldRenderer::renderFrame( skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); // In-world lines + for (const auto& [_, agent] : level.pathfinding->getAgents()) { + const auto& route = agent.route; + if (!route.found) + continue; + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines->pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + + lines->pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines->pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + } + linesShader.use(); linesShader.uniformMatrix("u_projview", camera.getProjView()); lines->draw(*lineBatch); diff --git a/src/logic/scripting/lua/libs/api_lua.hpp b/src/logic/scripting/lua/libs/api_lua.hpp index 6e65b790..c1d9f933 100644 --- a/src/logic/scripting/lua/libs/api_lua.hpp +++ b/src/logic/scripting/lua/libs/api_lua.hpp @@ -38,6 +38,7 @@ extern const luaL_Reg mat4lib[]; extern const luaL_Reg networklib[]; extern const luaL_Reg packlib[]; extern const luaL_Reg particleslib[]; // gfx.particles +extern const luaL_Reg pathfindinglib[]; extern const luaL_Reg playerlib[]; extern const luaL_Reg posteffectslib[]; // gfx.posteffects extern const luaL_Reg quatlib[]; diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp new file mode 100644 index 00000000..96ac0d13 --- /dev/null +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -0,0 +1,55 @@ +#include "api_lua.hpp" + +#include "voxels/Pathfinding.hpp" +#include "world/Level.hpp" + +using namespace scripting; + +static voxels::Agent* get_agent(lua::State* L) { + return level->pathfinding->getAgent(lua::tointeger(L, 1)); +} + +static int l_create_agent(lua::State* L) { + return lua::pushinteger(L, level->pathfinding->createAgent()); +} + +static int l_set_enabled(lua::State* L) { + if (auto agent = get_agent(L)) { + agent->enabled = lua::toboolean(L, 2); + } + return 0; +} + +static int l_is_enabled(lua::State* L) { + if (auto agent = get_agent(L)) { + return lua::pushboolean(L, agent->enabled); + } + return lua::pushboolean(L, false); +} + +static int l_make_route(lua::State* L) { + if (auto agent = get_agent(L)) { + auto start = lua::tovec3(L, 2); + auto target = lua::tovec3(L, 3); + auto route = level->pathfinding->perform(*agent, start, target); + agent->route = route; + if (!route.found) { + return 0; + } + lua::createtable(L, route.nodes.size(), 0); + for (int i = 0; i < route.nodes.size(); i++) { + lua::pushvec3(L, route.nodes[i].pos); + lua::rawseti(L, i + 1); + } + return 1; + } + return 0; +} + +const luaL_Reg pathfindinglib[] = { + {"create_agent", lua::wrap}, + {"set_enabled", lua::wrap}, + {"is_enabled", lua::wrap}, + {"make_route", lua::wrap}, + {NULL, NULL} +}; diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 891b89c7..f6ce4fe3 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -72,6 +72,7 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "input", inputlib); openlib(L, "inventory", inventorylib); openlib(L, "network", networklib); + openlib(L, "pathfinding", pathfindinglib); openlib(L, "player", playerlib); openlib(L, "time", timelib); openlib(L, "world", worldlib); diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 4282d5bb..54020cdd 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -75,6 +75,12 @@ static void restore_route( } } +int Pathfinding::createAgent() { + int id = nextAgent++; + agents[id] = Agent(); + return id; +} + Route Pathfinding::perform( const Agent& agent, const glm::ivec3& start, const glm::ivec3& end ) { @@ -89,12 +95,16 @@ Route Pathfinding::perform( std::unordered_map parents; const auto& chunks = *level.chunks; + int height = std::max(agent.height, 1); while (!queue.empty()) { + if (blocked.size() == agent.maxVisitedBlocks) { + break; + } auto node = queue.top(); queue.pop(); - if (node.pos.x == end.x && glm::abs((node.pos.y - end.y) / agent.height) == 0 && node.pos.z == end.z) { + if (node.pos.x == end.x && glm::abs((node.pos.y - end.y) / height) == 0 && node.pos.z == end.z) { restore_route(route, node, parents); route.nodes.push_back({start}); route.found = true; @@ -144,6 +154,18 @@ Route Pathfinding::perform( return route; } +Agent* Pathfinding::getAgent(int id) { + const auto& found = agents.find(id); + if (found != agents.end()) { + return &found->second; + } + return nullptr; +} + +const std::unordered_map& Pathfinding::getAgents() const { + return agents; +} + static int check_point( const ContentUnitIndices& defs, const GlobalChunks& chunks, diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index f743fd1c..04e039cb 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -4,6 +4,7 @@ #include #include #include +#include class Level; class GlobalChunks; @@ -19,7 +20,12 @@ namespace voxels { }; struct Agent { - int height; + bool enabled = false; + int height = 1; + int maxVisitedBlocks = 1e5; + glm::ivec3 start; + glm::ivec3 target; + Route route; }; struct Map { @@ -46,12 +52,20 @@ namespace voxels { public: Pathfinding(const Level& level); + int createAgent(); + Route perform( const Agent& agent, const glm::ivec3& start, const glm::ivec3& end ); + + Agent* getAgent(int id); + + const std::unordered_map& getAgents() const; private: const Level& level; const GlobalChunks& chunks; + std::unordered_map agents; + int nextAgent = 1; int getSurfaceAt(const glm::ivec3& pos, int maxDelta); }; From be3fb8346f35671df0fb0a012730291e03e53987 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 2 Aug 2025 23:03:40 +0300 Subject: [PATCH 037/177] add 'mayBeIncomplete' parameter --- src/graphics/render/WorldRenderer.cpp | 51 ++++++++++------- .../scripting/lua/libs/libpathfinding.cpp | 8 +++ src/voxels/Pathfinding.cpp | 55 ++++++++++++------- src/voxels/Pathfinding.hpp | 10 +++- 4 files changed, 82 insertions(+), 42 deletions(-) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index d7290998..149f5766 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -387,33 +387,42 @@ void WorldRenderer::renderFrame( skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); // In-world lines - for (const auto& [_, agent] : level.pathfinding->getAgents()) { - const auto& route = agent.route; - if (!route.found) - continue; - for (int i = 1; i < route.nodes.size(); i++) { - const auto& a = route.nodes.at(i - 1); - const auto& b = route.nodes.at(i); + if (debug) { + for (const auto& [_, agent] : level.pathfinding->getAgents()) { + const auto& route = agent.route; + if (!route.found) + continue; + for (const auto& blocked : route.visited) { + lines->pushLine( + glm::vec3(blocked) + glm::vec3(0.5f), + glm::vec3(blocked) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 0, 0, 1) + ); + } + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines->pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } - if (i == 1) { lines->pushLine( glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines->pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), glm::vec4(1, 1, 1, 1) ); } - - lines->pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec4(1, 0, 1, 1) - ); - - lines->pushLine( - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); } } diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp index 96ac0d13..e66b1f2a 100644 --- a/src/logic/scripting/lua/libs/libpathfinding.cpp +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -46,10 +46,18 @@ static int l_make_route(lua::State* L) { return 0; } +static int l_set_max_visited_blocks(lua::State* L) { + if (auto agent = get_agent(L)) { + agent->maxVisitedBlocks = lua::tointeger(L, 2); + } + return 0; +} + const luaL_Reg pathfindinglib[] = { {"create_agent", lua::wrap}, {"set_enabled", lua::wrap}, {"is_enabled", lua::wrap}, {"make_route", lua::wrap}, + {"set_max_visited", lua::wrap}, {NULL, NULL} }; diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 54020cdd..829bbde8 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -1,10 +1,6 @@ #include "Pathfinding.hpp" -#define GLM_ENABLE_EXPERIMENTAL -#include - #include -#include #include #include "world/Level.hpp" @@ -13,6 +9,8 @@ #include "voxels/blocks_agent.hpp" #include "content/Content.hpp" +inline constexpr float SQRT2 = 1.4142135623730951f; // sqrt(2) + using namespace voxels; struct Node { @@ -28,7 +26,7 @@ struct NodeLess { } }; -static float distance(const glm::ivec3& a, const glm::ivec3& b) { +static float heuristic(const glm::ivec3& a, const glm::ivec3& b) { return glm::distance(glm::vec3(a), glm::vec3(b)); } @@ -60,10 +58,10 @@ static bool check_passability( static void restore_route( Route& route, - const Node& node, + const glm::ivec3& lastPos, const std::unordered_map& parents ) { - auto pos = node.pos; + auto pos = lastPos; while (true) { const auto& found = parents.find(pos); if (found == parents.end()) { @@ -89,7 +87,7 @@ Route Pathfinding::perform( Route route {}; std::priority_queue, NodeLess> queue; - queue.push({start, {}, 0, distance(start, end)}); + queue.push({start, {}, 0, heuristic(start, end)}); std::unordered_set blocked; std::unordered_map parents; @@ -97,18 +95,31 @@ Route Pathfinding::perform( const auto& chunks = *level.chunks; int height = std::max(agent.height, 1); + glm::ivec3 nearest = start; + float minHScore = heuristic(start, end); + while (!queue.empty()) { if (blocked.size() == agent.maxVisitedBlocks) { + if (agent.mayBeIncomplete) { + restore_route(route, nearest, parents); + route.nodes.push_back({start}); + route.found = true; + route.visited = std::move(blocked); + return route; + } break; } auto node = queue.top(); queue.pop(); - if (node.pos.x == end.x && glm::abs((node.pos.y - end.y) / height) == 0 && node.pos.z == end.z) { - restore_route(route, node, parents); + if (node.pos.x == end.x && + glm::abs((node.pos.y - end.y) / height) == 0 && + node.pos.z == end.z) { + restore_route(route, node.pos, parents); route.nodes.push_back({start}); route.found = true; - break; + route.visited = std::move(blocked); + return route; } blocked.emplace(node.pos); @@ -128,6 +139,9 @@ Route Pathfinding::perform( } pos.y = surface; auto point = pos + glm::ivec3(offset.x, 0, offset.y); + if (blocked.find(point) != blocked.end()) { + continue; + } if (is_obstacle_at(chunks, pos.x, pos.y + agent.height / 2, pos.z)) { continue; @@ -135,18 +149,21 @@ Route Pathfinding::perform( if (!check_passability(agent, chunks, node, offset, i >= 4)) { continue; } - if (blocked.find(point) != blocked.end()) { - continue; - } - int score = glm::abs(node.pos.y - pos.y); + + int score = glm::abs(node.pos.y - pos.y) * 10; + float sum = glm::abs(offset.x) + glm::abs(offset.y); float gScore = - node.gScore + glm::abs(offset.x) + glm::abs(offset.y) + score; + node.gScore + glm::max(sum, SQRT2) * 0.5f + sum * 0.5f + score; const auto& found = parents.find(point); - if (found == parents.end() || gScore < found->second.gScore) { - float hScore = distance(point, end); + if (found == parents.end()) { + float hScore = heuristic(point, end); + if (hScore < minHScore) { + minHScore = hScore; + nearest = point; + } float fScore = gScore + hScore; Node nNode {point, node.pos, gScore, fScore}; - parents[point] = Node {node.pos, node.parent, gScore, fScore}; + parents[point] = node; queue.push(nNode); } } diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index 04e039cb..4b0b0008 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -1,10 +1,14 @@ #pragma once +#define GLM_ENABLE_EXPERIMENTAL +#include + #include #include #include #include #include +#include class Level; class GlobalChunks; @@ -17,12 +21,14 @@ namespace voxels { struct Route { bool found; std::vector nodes; + std::unordered_set visited; }; struct Agent { bool enabled = false; - int height = 1; - int maxVisitedBlocks = 1e5; + bool mayBeIncomplete = true; + int height = 2; + int maxVisitedBlocks = 1e3; glm::ivec3 start; glm::ivec3 target; Route route; From a78931205f7e6835c2e4d4760ac66979a9e3b3d2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 01:41:18 +0300 Subject: [PATCH 038/177] feat: async pathfinding --- src/graphics/render/WorldRenderer.cpp | 68 +++++------ src/io/settings_io.cpp | 3 + src/logic/LevelController.cpp | 4 + .../scripting/lua/libs/libpathfinding.cpp | 39 ++++++- src/settings.hpp | 6 + src/voxels/Pathfinding.cpp | 109 +++++++++--------- src/voxels/Pathfinding.hpp | 51 ++++---- 7 files changed, 168 insertions(+), 112 deletions(-) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 149f5766..8db4f4c7 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -274,6 +274,39 @@ void WorldRenderer::renderLines( } } +static void draw_route( + LinesRenderer& lines, const voxels::Agent& agent +) { + const auto& route = agent.route; + if (!route.found) + return; + + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines.pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } +} + void WorldRenderer::renderFrame( const DrawContext& pctx, Camera& camera, @@ -389,40 +422,7 @@ void WorldRenderer::renderFrame( // In-world lines if (debug) { for (const auto& [_, agent] : level.pathfinding->getAgents()) { - const auto& route = agent.route; - if (!route.found) - continue; - for (const auto& blocked : route.visited) { - lines->pushLine( - glm::vec3(blocked) + glm::vec3(0.5f), - glm::vec3(blocked) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 0, 0, 1) - ); - } - for (int i = 1; i < route.nodes.size(); i++) { - const auto& a = route.nodes.at(i - 1); - const auto& b = route.nodes.at(i); - - if (i == 1) { - lines->pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } - - lines->pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec4(1, 0, 1, 1) - ); - - lines->pushLine( - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } + draw_route(*lines, agent); } } diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index cdbf567e..b69be64c 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -83,6 +83,9 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.add("language", &settings.ui.language); builder.add("world-preview-size", &settings.ui.worldPreviewSize); + builder.section("pathfinding"); + builder.add("steps-per-async-agent", &settings.pathfinding.stepsPerAsyncAgent); + builder.section("debug"); builder.add("generator-test-mode", &settings.debug.generatorTestMode); builder.add("do-write-lights", &settings.debug.doWriteLights); diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index f1aca628..18225ea0 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -11,6 +11,7 @@ #include "objects/Player.hpp" #include "physics/Hitbox.hpp" #include "voxels/Chunks.hpp" +#include "voxels/Pathfinding.hpp" #include "scripting/scripting.hpp" #include "lighting/Lighting.hpp" #include "settings.hpp" @@ -69,6 +70,9 @@ LevelController::LevelController( } void LevelController::update(float delta, bool pause) { + level->pathfinding->performAllAsync( + settings.pathfinding.stepsPerAsyncAgent.get() + ); for (const auto& [_, player] : *level->players) { if (player->isSuspended()) { continue; diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp index e66b1f2a..4d2374a1 100644 --- a/src/logic/scripting/lua/libs/libpathfinding.cpp +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -31,8 +31,10 @@ static int l_make_route(lua::State* L) { if (auto agent = get_agent(L)) { auto start = lua::tovec3(L, 2); auto target = lua::tovec3(L, 3); - auto route = level->pathfinding->perform(*agent, start, target); - agent->route = route; + agent->state = {}; + agent->start = start; + agent->target = target; + auto route = level->pathfinding->perform(*agent); if (!route.found) { return 0; } @@ -46,6 +48,37 @@ static int l_make_route(lua::State* L) { return 0; } +static int l_make_route_async(lua::State* L) { + if (auto agent = get_agent(L)) { + auto start = lua::tovec3(L, 2); + auto target = lua::tovec3(L, 3); + agent->state = {}; + agent->start = start; + agent->target = target; + level->pathfinding->perform(*agent, 0); + } + return 0; +} + +static int l_pull_route(lua::State* L) { + if (auto agent = get_agent(L)) { + auto& route = agent->route; + if (!agent->state.finished) { + return 0; + } + if (!route.found) { + return lua::createtable(L, 0, 0); + } + lua::createtable(L, route.nodes.size(), 0); + for (int i = 0; i < route.nodes.size(); i++) { + lua::pushvec3(L, route.nodes[i].pos); + lua::rawseti(L, i + 1); + } + return 1; + } + return 0; +} + static int l_set_max_visited_blocks(lua::State* L) { if (auto agent = get_agent(L)) { agent->maxVisitedBlocks = lua::tointeger(L, 2); @@ -58,6 +91,8 @@ const luaL_Reg pathfindinglib[] = { {"set_enabled", lua::wrap}, {"is_enabled", lua::wrap}, {"make_route", lua::wrap}, + {"make_route_async", lua::wrap}, + {"pull_route", lua::wrap}, {"set_max_visited", lua::wrap}, {NULL, NULL} }; diff --git a/src/settings.hpp b/src/settings.hpp index d4c21688..d51079a2 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -85,6 +85,11 @@ struct GraphicsSettings { IntegerSetting denseRenderDistance {56, 0, 10'000}; }; +struct PathfindingSettings { + /// @brief Max visited blocks by an agent per async tick + IntegerSetting stepsPerAsyncAgent {256, 1, 2048}; +}; + struct DebugSettings { /// @brief Turns off chunks saving/loading FlagSetting generatorTestMode {false}; @@ -109,4 +114,5 @@ struct EngineSettings { DebugSettings debug; UiSettings ui; NetworkSettings network; + PathfindingSettings pathfinding; }; diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 829bbde8..b39fe99e 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -1,8 +1,5 @@ #include "Pathfinding.hpp" -#include -#include - #include "world/Level.hpp" #include "voxels/GlobalChunks.hpp" #include "voxels/Chunk.hpp" @@ -13,19 +10,6 @@ inline constexpr float SQRT2 = 1.4142135623730951f; // sqrt(2) using namespace voxels; -struct Node { - glm::ivec3 pos; - glm::ivec3 parent; - float gScore; - float fScore; -}; - -struct NodeLess { - bool operator()(const Node& l, const Node& r) const { - return l.fScore > r.fScore; - } -}; - static float heuristic(const glm::ivec3& a, const glm::ivec3& b) { return glm::distance(glm::vec3(a), glm::vec3(b)); } @@ -79,50 +63,70 @@ int Pathfinding::createAgent() { return id; } -Route Pathfinding::perform( - const Agent& agent, const glm::ivec3& start, const glm::ivec3& end -) { +void Pathfinding::performAllAsync(int stepsPerAgent) { + for (auto& [id, agent] : agents) { + if (agent.state.finished) { + continue; + } + perform(agent, stepsPerAgent); + } +} + +Route Pathfinding::perform(Agent& agent, int maxVisited) { using namespace blocks_agent; Route route {}; - std::priority_queue, NodeLess> queue; - queue.push({start, {}, 0, heuristic(start, end)}); - - std::unordered_set blocked; - std::unordered_map parents; + State state = std::move(agent.state); + if (state.queue.empty()) { + state.queue.push({agent.start, {}, 0, heuristic(agent.start, agent.target)}); + } const auto& chunks = *level.chunks; int height = std::max(agent.height, 1); - glm::ivec3 nearest = start; - float minHScore = heuristic(start, end); + if (state.nearest == glm::ivec3(0)) { + state.nearest = agent.start; + state.minHScore = heuristic(agent.start, agent.target); + } + int visited = -1; - while (!queue.empty()) { - if (blocked.size() == agent.maxVisitedBlocks) { + while (!state.queue.empty()) { + if (state.blocked.size() == agent.maxVisitedBlocks) { if (agent.mayBeIncomplete) { - restore_route(route, nearest, parents); - route.nodes.push_back({start}); + restore_route(route, state.nearest, state.parents); + route.nodes.push_back({agent.start}); route.found = true; - route.visited = std::move(blocked); + state.finished = true; + agent.state = std::move(state); + agent.route = route; return route; } break; } - auto node = queue.top(); - queue.pop(); + visited++; + if (visited == maxVisited) { + state.finished = false; + agent.state = std::move(state); + return {}; + } - if (node.pos.x == end.x && - glm::abs((node.pos.y - end.y) / height) == 0 && - node.pos.z == end.z) { - restore_route(route, node.pos, parents); - route.nodes.push_back({start}); + auto node = state.queue.top(); + state.queue.pop(); + + if (node.pos.x == agent.target.x && + glm::abs((node.pos.y - agent.target.y) / height) == 0 && + node.pos.z == agent.target.z) { + restore_route(route, node.pos, state.parents); + route.nodes.push_back({agent.start}); route.found = true; - route.visited = std::move(blocked); + state.finished = true; + agent.state = std::move(state); + agent.route = route; return route; } - blocked.emplace(node.pos); + state.blocked.emplace(node.pos); glm::ivec2 neighbors[8] { {0, 1}, {1, 0}, {0, -1}, {-1, 0}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, @@ -139,7 +143,7 @@ Route Pathfinding::perform( } pos.y = surface; auto point = pos + glm::ivec3(offset.x, 0, offset.y); - if (blocked.find(point) != blocked.end()) { + if (state.blocked.find(point) != state.blocked.end()) { continue; } @@ -153,22 +157,23 @@ Route Pathfinding::perform( int score = glm::abs(node.pos.y - pos.y) * 10; float sum = glm::abs(offset.x) + glm::abs(offset.y); float gScore = - node.gScore + glm::max(sum, SQRT2) * 0.5f + sum * 0.5f + score; - const auto& found = parents.find(point); - if (found == parents.end()) { - float hScore = heuristic(point, end); - if (hScore < minHScore) { - minHScore = hScore; - nearest = point; + node.gScore + sum + score; + const auto& found = state.parents.find(point); + if (found == state.parents.end()) { + float hScore = heuristic(point, agent.target); + if (hScore < state.minHScore) { + state.minHScore = hScore; + state.nearest = point; } - float fScore = gScore + hScore; + float fScore = gScore * 0.75f + hScore; Node nNode {point, node.pos, gScore, fScore}; - parents[point] = node; - queue.push(nNode); + state.parents[point] = node; + state.queue.push(nNode); } } } - return route; + agent.state = std::move(state); + return {}; } Agent* Pathfinding::getAgent(int id) { diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index 4b0b0008..c89841e5 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -21,7 +22,28 @@ namespace voxels { struct Route { bool found; std::vector nodes; - std::unordered_set visited; + }; + + struct Node { + glm::ivec3 pos; + glm::ivec3 parent; + float gScore; + float fScore; + }; + + struct NodeLess { + bool operator()(const Node& l, const Node& r) const { + return l.fScore > r.fScore; + } + }; + + struct State { + std::priority_queue, NodeLess> queue; + std::unordered_set blocked; + std::unordered_map parents; + glm::ivec3 nearest; + float minHScore; + bool finished = true; }; struct Agent { @@ -32,26 +54,7 @@ namespace voxels { glm::ivec3 start; glm::ivec3 target; Route route; - }; - - struct Map { - int width; - int height; - std::unique_ptr map; - - Map(int width, int height) - : width(width), - height(height), - map(std::make_unique(width * height)) { - } - - uint8_t& operator[](int i) { - return map[i]; - } - - const uint8_t& operator[](int i) const { - return map[i]; - } + State state {}; }; class Pathfinding { @@ -60,9 +63,9 @@ namespace voxels { int createAgent(); - Route perform( - const Agent& agent, const glm::ivec3& start, const glm::ivec3& end - ); + void performAllAsync(int stepsPerAgent); + + Route perform(Agent& agent, int maxVisited = -1); Agent* getAgent(int id); From 3aac6ecbcbc0b8865282cdce13c3f211e7a05774 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 02:03:20 +0300 Subject: [PATCH 039/177] refactor: extract DebugLinesRenderer --- ...desRenderer.cpp => DebugLinesRenderer.cpp} | 66 +++++++++++++++---- src/graphics/render/DebugLinesRenderer.hpp | 39 +++++++++++ src/graphics/render/GuidesRenderer.hpp | 28 -------- src/graphics/render/WorldRenderer.cpp | 64 +++--------------- src/graphics/render/WorldRenderer.hpp | 4 +- 5 files changed, 104 insertions(+), 97 deletions(-) rename src/graphics/render/{GuidesRenderer.cpp => DebugLinesRenderer.cpp} (66%) create mode 100644 src/graphics/render/DebugLinesRenderer.hpp delete mode 100644 src/graphics/render/GuidesRenderer.hpp diff --git a/src/graphics/render/GuidesRenderer.cpp b/src/graphics/render/DebugLinesRenderer.cpp similarity index 66% rename from src/graphics/render/GuidesRenderer.cpp rename to src/graphics/render/DebugLinesRenderer.cpp index 285793f4..ce6512e5 100644 --- a/src/graphics/render/GuidesRenderer.cpp +++ b/src/graphics/render/DebugLinesRenderer.cpp @@ -1,15 +1,49 @@ -#include "GuidesRenderer.hpp" - -#include +#include "DebugLinesRenderer.hpp" #include "graphics/core/Shader.hpp" +#include "window/Camera.hpp" #include "graphics/core/LineBatch.hpp" #include "graphics/core/DrawContext.hpp" +#include "graphics/render/LinesRenderer.hpp" +#include "world/Level.hpp" +#include "voxels/Chunk.hpp" +#include "voxels/Pathfinding.hpp" #include "maths/voxmaths.hpp" -#include "window/Camera.hpp" -#include "constants.hpp" -void GuidesRenderer::drawBorders( +static void draw_route( + LinesRenderer& lines, const voxels::Agent& agent +) { + const auto& route = agent.route; + if (!route.found) + return; + + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines.pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } +} + +void DebugLinesRenderer::drawBorders( LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez ) { int ww = ex - sx; @@ -37,7 +71,7 @@ void GuidesRenderer::drawBorders( batch.flush(); } -void GuidesRenderer::drawCoordSystem( +void DebugLinesRenderer::drawCoordSystem( LineBatch& batch, const DrawContext& pctx, float length ) { auto ctx = pctx.sub(); @@ -55,14 +89,20 @@ void GuidesRenderer::drawCoordSystem( batch.line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f); } -void GuidesRenderer::renderDebugLines( - const DrawContext& pctx, + +void DebugLinesRenderer::render( + DrawContext& pctx, const Camera& camera, - LineBatch& batch, + LinesRenderer& renderer, + LineBatch& linesBatch, Shader& linesShader, bool showChunkBorders ) { - DrawContext ctx = pctx.sub(&batch); + // In-world lines + for (const auto& [_, agent] : level.pathfinding->getAgents()) { + draw_route(renderer, agent); + } + DrawContext ctx = pctx.sub(&linesBatch); const auto& viewport = ctx.getViewport(); ctx.setDepthTest(true); @@ -78,7 +118,7 @@ void GuidesRenderer::renderDebugLines( int cz = floordiv(static_cast(coord.z), CHUNK_D); drawBorders( - batch, + linesBatch, cx * CHUNK_W, 0, cz * CHUNK_D, @@ -103,5 +143,5 @@ void GuidesRenderer::renderDebugLines( ) * model * glm::inverse(camera.rotation) ); - drawCoordSystem(batch, ctx, length); + drawCoordSystem(linesBatch, ctx, length); } diff --git a/src/graphics/render/DebugLinesRenderer.hpp b/src/graphics/render/DebugLinesRenderer.hpp new file mode 100644 index 00000000..ba4e83f9 --- /dev/null +++ b/src/graphics/render/DebugLinesRenderer.hpp @@ -0,0 +1,39 @@ +#pragma once + +class DrawContext; +class Camera; +class LineBatch; +class LinesRenderer; +class Shader; +class Level; + +class DebugLinesRenderer { +public: + DebugLinesRenderer(const Level& level) + : level(level) {}; + + /// @brief Render debug lines in the world + /// @param ctx Draw context + /// @param camera Camera used for rendering + /// @param renderer Lines renderer used for rendering lines + /// @param linesShader Shader used for rendering lines + /// @param showChunkBorders Whether to show chunk borders + void render( + DrawContext& ctx, + const Camera& camera, + LinesRenderer& renderer, + LineBatch& linesBatch, + Shader& linesShader, + bool showChunkBorders + ); +private: + const Level& level; + + void drawBorders( + LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez + ); + void drawCoordSystem( + LineBatch& batch, const DrawContext& pctx, float length + ); + +}; diff --git a/src/graphics/render/GuidesRenderer.hpp b/src/graphics/render/GuidesRenderer.hpp deleted file mode 100644 index 36daad82..00000000 --- a/src/graphics/render/GuidesRenderer.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -class LineBatch; -class DrawContext; -class Camera; -class Shader; - -class GuidesRenderer { -public: - void drawBorders( - LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez - ); - void drawCoordSystem( - LineBatch& batch, const DrawContext& pctx, float length - ); - - /// @brief Render all debug lines (chunks borders, coord system guides) - /// @param context graphics context - /// @param camera active camera - /// @param linesShader shader used - void renderDebugLines( - const DrawContext& context, - const Camera& camera, - LineBatch& batch, - Shader& linesShader, - bool showChunkBorders - ); -}; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 8db4f4c7..935b887c 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -52,8 +52,8 @@ #include "NamedSkeletons.hpp" #include "TextsRenderer.hpp" #include "ChunksRenderer.hpp" -#include "GuidesRenderer.hpp" #include "LinesRenderer.hpp" +#include "DebugLinesRenderer.hpp" #include "ModelBatch.hpp" #include "Skybox.hpp" #include "Emitter.hpp" @@ -80,7 +80,6 @@ WorldRenderer::WorldRenderer( modelBatch(std::make_unique( MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings() )), - guides(std::make_unique()), chunksRenderer(std::make_unique( &level, *player.chunks, @@ -120,6 +119,7 @@ WorldRenderer::WorldRenderer( ); lines = std::make_unique(); shadowMapping = std::make_unique(level); + debugLines = std::make_unique(level); } WorldRenderer::~WorldRenderer() = default; @@ -274,39 +274,6 @@ void WorldRenderer::renderLines( } } -static void draw_route( - LinesRenderer& lines, const voxels::Agent& agent -) { - const auto& route = agent.route; - if (!route.found) - return; - - for (int i = 1; i < route.nodes.size(); i++) { - const auto& a = route.nodes.at(i - 1); - const auto& b = route.nodes.at(i); - - if (i == 1) { - lines.pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } - - lines.pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec4(1, 0, 1, 1) - ); - - lines.pushLine( - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } -} - void WorldRenderer::renderFrame( const DrawContext& pctx, Camera& camera, @@ -316,6 +283,9 @@ void WorldRenderer::renderFrame( PostProcessing& postProcessing ) { // TODO: REFACTOR WHOLE RENDER ENGINE + + auto projView = camera.getProjView(); + float delta = uiDelta * !pause; timer += delta; weather.update(delta); @@ -373,9 +343,6 @@ void WorldRenderer::renderFrame( setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f); chunksRenderer->drawShadowsPass(shadowCamera, shader, camera); }); - - auto& linesShader = assets.require("lines"); - { DrawContext wctx = pctx.sub(); postProcessing.use(wctx, gbufferPipeline); @@ -387,14 +354,6 @@ void WorldRenderer::renderFrame( ctx.setDepthTest(true); ctx.setCullFace(true); renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible); - // Debug lines - if (hudVisible) { - if (debug) { - guides->renderDebugLines( - ctx, camera, *lineBatch, linesShader, showChunkBorders - ); - } - } } texts->render(pctx, camera, settings, hudVisible, true); } @@ -419,15 +378,12 @@ void WorldRenderer::renderFrame( // Background sky plane skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); - // In-world lines - if (debug) { - for (const auto& [_, agent] : level.pathfinding->getAgents()) { - draw_route(*lines, agent); - } - } - + auto& linesShader = assets.require("lines"); linesShader.use(); - linesShader.uniformMatrix("u_projview", camera.getProjView()); + debugLines->render( + ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders + ); + linesShader.uniformMatrix("u_projview", projView); lines->draw(*lineBatch); lineBatch->flush(); diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index 955568fa..4c8911db 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -22,7 +22,6 @@ class BlockWrapsRenderer; class PrecipitationRenderer; class HandsRenderer; class NamedSkeletons; -class GuidesRenderer; class LinesRenderer; class TextsRenderer; class Shader; @@ -36,6 +35,7 @@ class ModelBatch; class Assets; class Shadows; class GBuffer; +class DebugLinesRenderer; struct EngineSettings; struct CompileTimeShaderSettings { @@ -53,11 +53,11 @@ class WorldRenderer { std::unique_ptr lineBatch; std::unique_ptr batch3d; std::unique_ptr modelBatch; - std::unique_ptr guides; std::unique_ptr chunksRenderer; std::unique_ptr hands; std::unique_ptr skybox; std::unique_ptr shadowMapping; + std::unique_ptr debugLines; Weather weather {}; float timer = 0.0f; From 5a6537c89d65f459f73feb5cd7efe1319587c7b9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 02:48:52 +0300 Subject: [PATCH 040/177] add pathfinding.remove_agent --- src/logic/scripting/lua/libs/libpathfinding.cpp | 6 ++++++ src/voxels/Pathfinding.cpp | 9 +++++++++ src/voxels/Pathfinding.hpp | 2 ++ 3 files changed, 17 insertions(+) diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp index 4d2374a1..0370e3a2 100644 --- a/src/logic/scripting/lua/libs/libpathfinding.cpp +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -13,6 +13,11 @@ static int l_create_agent(lua::State* L) { return lua::pushinteger(L, level->pathfinding->createAgent()); } +static int l_remove_agent(lua::State* L) { + int id = lua::tointeger(L, 1); + return lua::pushboolean(L, level->pathfinding->removeAgent(id)); +} + static int l_set_enabled(lua::State* L) { if (auto agent = get_agent(L)) { agent->enabled = lua::toboolean(L, 2); @@ -88,6 +93,7 @@ static int l_set_max_visited_blocks(lua::State* L) { const luaL_Reg pathfindinglib[] = { {"create_agent", lua::wrap}, + {"remove_agent", lua::wrap}, {"set_enabled", lua::wrap}, {"is_enabled", lua::wrap}, {"make_route", lua::wrap}, diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index b39fe99e..51079f61 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -63,6 +63,15 @@ int Pathfinding::createAgent() { return id; } +bool Pathfinding::removeAgent(int id) { + auto found = agents.find(id); + if (found != agents.end()) { + agents.erase(found); + return true; + } + return false; +} + void Pathfinding::performAllAsync(int stepsPerAgent) { for (auto& [id, agent] : agents) { if (agent.state.finished) { diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index c89841e5..f7b3b0c4 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -63,6 +63,8 @@ namespace voxels { int createAgent(); + bool removeAgent(int id); + void performAllAsync(int stepsPerAgent); Route perform(Agent& agent, int maxVisited = -1); From 5310b6325ebdb4932d254c500963c3aea2bb5c00 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:07:32 +0300 Subject: [PATCH 041/177] fix hud.lua --- res/scripts/hud.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index abd1eaf4..cf2153a2 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -96,7 +96,7 @@ local function update_hand() local rotation = cam:get_rot() - local angle = player.get_rot() - 90 + local angle = player.get_rot(pid) - 90 local cos = math.cos(angle / (180 / math.pi)) local sin = math.sin(angle / (180 / math.pi)) From a110022ec2d678d77922748b7cc7c7df09b57243 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 19:06:22 +0300 Subject: [PATCH 042/177] add 'core:pathfinding' component --- res/scripts/components/pathfinding.lua | 39 ++++++++++++++++++++++++++ src/graphics/render/WorldRenderer.cpp | 8 ++++-- src/voxels/Pathfinding.cpp | 1 + 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 res/scripts/components/pathfinding.lua diff --git a/res/scripts/components/pathfinding.lua b/res/scripts/components/pathfinding.lua new file mode 100644 index 00000000..31d6d780 --- /dev/null +++ b/res/scripts/components/pathfinding.lua @@ -0,0 +1,39 @@ +local target +local route +local started + +local tsf = entity.transform + +agent = pathfinding.create_agent() +pathfinding.set_max_visited(agent, 100000) + +function set_target(new_target) + target = new_target +end + +function get_target() + return target +end + +function get_route() + return route +end + +function on_update() + if not started then + if target then + pathfinding.make_route_async(agent, tsf:get_pos(), target) + started = true + end + else + local new_route = pathfinding.pull_route(agent) + if new_route then + route = new_route + started = false + end + end +end + +function on_despawn() + pathfinding.remove_agent(agent) +end diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 935b887c..eef4c41e 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -380,9 +380,11 @@ void WorldRenderer::renderFrame( auto& linesShader = assets.require("lines"); linesShader.use(); - debugLines->render( - ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders - ); + if (debug && hudVisible) { + debugLines->render( + ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders + ); + } linesShader.uniformMatrix("u_projview", projView); lines->draw(*lineBatch); lineBatch->flush(); diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 51079f61..1a34a8b2 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -181,6 +181,7 @@ Route Pathfinding::perform(Agent& agent, int maxVisited) { } } } + state.finished = true; agent.state = std::move(state); return {}; } From 89c07cbf7518bf439caccc77060424e20c24616f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 19:09:43 +0300 Subject: [PATCH 043/177] update docs --- doc/en/scripting.md | 1 + doc/en/scripting/builtins/libpathfinding.md | 57 +++++++++++++++++++++ doc/ru/scripting.md | 1 + doc/ru/scripting/builtins/libpathfinding.md | 57 +++++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 doc/en/scripting/builtins/libpathfinding.md create mode 100644 doc/ru/scripting/builtins/libpathfinding.md diff --git a/doc/en/scripting.md b/doc/en/scripting.md index 05a89a26..9752e46e 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -31,6 +31,7 @@ Subsections: - [mat4](scripting/builtins/libmat4.md) - [network](scripting/builtins/libnetwork.md) - [pack](scripting/builtins/libpack.md) + - [pathfinding](scripting/builtins/libpathfinding.md) - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [rules](scripting/builtins/librules.md) diff --git a/doc/en/scripting/builtins/libpathfinding.md b/doc/en/scripting/builtins/libpathfinding.md new file mode 100644 index 00000000..4d07fa75 --- /dev/null +++ b/doc/en/scripting/builtins/libpathfinding.md @@ -0,0 +1,57 @@ +# *pathfinding* library + +The *pathfinding* library provides functions for working with the pathfinding system in the game world. It allows you to create and manage agents finding routes between points in the world. + +When used in entity logic, the `core:pathfinding` component should be used. + +## `core:pathfinding` component + +```lua +local pf = entity:get_component("core:pathfinding") + +--- ... +local x = ... +local y = ... +local z = ... + +--- Set the target for the agent +pf.set_target({x, y, z}) + +--- Get the current target of the agent +local target = pf.get_target() --> vec3 or nil +--- ... + +--- Get the current route of the agent +local route = pf.get_route() --> table or nil +--- ... +``` + +## Library functions + +```lua +--- Create a new agent. Returns the ID of the created agent +local agent = pathfinding.create_agent() --> int + +--- Delete an agent by ID. Returns true if the agent existed, otherwise false +pathfinding.remove_agent(agent: int) --> bool + +--- Set the agent state (enabled/disabled) +pathfinding.set_enabled(agent: int, enabled: bool) + +--- Check the agent state. Returns true if the agent is enabled, otherwise false +pathfinding.is_enabled(agent: int) --> bool + +--- Create a route based on the given points. Returns an array of route points +pathfinding.make_route(start: vec3, target: vec3) --> table + +--- Asynchronously create a route based on the given points. +--- This function allows to perform pathfinding in the background without blocking the main thread of execution +pathfinding.make_route_async(agent: int, start: vec3, target: vec3) + +--- Get the route that the agent has already found. Used to get the route after an asynchronous search. +--- If the search has not yet completed, returns nil. If the route is not found, returns an empty table. +pathfinding.pull_route(agent: int) --> table or nil + +--- Set the maximum number of visited blocks for the agent. Used to limit the amount of work of the pathfinding algorithm. +pathfinding.set_max_visited(agent: int, max_visited: int) +``` diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index eaae3302..913b5784 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -31,6 +31,7 @@ - [mat4](scripting/builtins/libmat4.md) - [network](scripting/builtins/libnetwork.md) - [pack](scripting/builtins/libpack.md) + - [pathfinding](scripting/builtins/libpathfinding.md) - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [rules](scripting/builtins/librules.md) diff --git a/doc/ru/scripting/builtins/libpathfinding.md b/doc/ru/scripting/builtins/libpathfinding.md new file mode 100644 index 00000000..d4f5ed7c --- /dev/null +++ b/doc/ru/scripting/builtins/libpathfinding.md @@ -0,0 +1,57 @@ +# Библиотека *pathfinding* + +Библиотека *pathfinding* предоставляет функции для работы с системой поиска пути в игровом мире. Она позволяет создавать и управлять агентами, которые могут находить маршруты между точками в мире. + +При использовании в логике сущностей следует использовать компонент `core:pathfinding`. + +## Компонент `core:pathfinding` + +```lua +local pf = entity:get_component("core:pathfinding") + +--- ... +local x = ... +local y = ... +local z = ... + +--- Установка цели для агента +pf.set_target({x, y, z}) + +--- Получение текущей цели агента +local target = pf.get_target() --> vec3 или nil +--- ... + +--- Получение текущего маршрута агента +local route = pf.get_route() --> table или nil +--- ... +``` + +## Функции библиотеки + +```lua +--- Создание нового агента. Возвращает идентификатор созданного агента +local agent = pathfinding.create_agent() --> int + +--- Удаление агента по идентификатору. Возвращает true, если агент существовал, иначе false +pathfinding.remove_agent(agent: int) --> bool + +--- Установка состояния агента (включен/выключен) +pathfinding.set_enabled(agent: int, enabled: bool) + +--- Проверка состояния агента. Возвращает true, если агент включен, иначе false +pathfinding.is_enabled(agent: int) --> bool + +--- Создание маршрута на основе заданных точек. Возвращает массив точек маршрута +pathfinding.make_route(start: vec3, target: vec3) --> table + +--- Асинхронное создание маршрута на основе заданных точек. +--- Функция позволяет выполнять поиск пути в фоновом режиме, не блокируя основной поток выполнения +pathfinding.make_route_async(agent: int, start: vec3, target: vec3) + +--- Получение маршрута, который агент уже нашел. Используется для получения маршрута после асинхронного поиска. +--- Если поиск ещё не завершён, возвращает nil. Если маршрут не найден, возвращает пустую таблицу. +pathfinding.pull_route(agent: int) --> table или nil + +--- Установка максимального количества посещенных блоков для агента. Используется для ограничения объема работы алгоритма поиска пути. +pathfinding.set_max_visited(agent: int, max_visited: int) +``` From f2aa77db8baa018d5e02d195ce8b3722bfaf0915 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 19:39:21 +0300 Subject: [PATCH 044/177] add vecn.distance function --- doc/en/scripting/builtins/libvecn.md | 11 +++++++++++ doc/ru/scripting/builtins/libvecn.md | 11 +++++++++++ src/logic/scripting/lua/libs/libvecn.cpp | 11 +++++++++++ 3 files changed, 33 insertions(+) diff --git a/doc/en/scripting/builtins/libvecn.md b/doc/en/scripting/builtins/libvecn.md index 22552eb7..36987dfb 100644 --- a/doc/en/scripting/builtins/libvecn.md +++ b/doc/en/scripting/builtins/libvecn.md @@ -100,6 +100,13 @@ vecn.length(a: vector) ``` +#### Distance - *vecn.distance(...)* + +```lua +-- returns the distance between two vectors +vecn.distance(a: vector, b: vector) +``` + #### Absolute value - *vecn.abs(...)* ```lua @@ -188,6 +195,10 @@ print("mul: " .. vec3.tostring(result_mul)) -- {10, 40, 80} local result_mul_scal = vec3.mul(v1_3d, scal) print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12} +-- calculating distance between vectors +local result_distance = vec3.distance(v1_3d, v2_3d) +print("distance: " .. result_distance) -- 43 + -- vector normalization local result_norm = vec3.normalize(v1_3d) print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} diff --git a/doc/ru/scripting/builtins/libvecn.md b/doc/ru/scripting/builtins/libvecn.md index 44cfa994..939e2da1 100644 --- a/doc/ru/scripting/builtins/libvecn.md +++ b/doc/ru/scripting/builtins/libvecn.md @@ -100,6 +100,13 @@ vecn.length(a: vector) ``` +#### Дистанция - *vecn.distance(...)* + +```lua +-- возвращает расстояние между двумя векторами +vecn.distance(a: vector, b: vector) +``` + #### Абсолютное значение - *vecn.abs(...)* ```lua @@ -192,6 +199,10 @@ print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12} local result_norm = vec3.normalize(v1_3d) print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} +-- дистанция между векторами +local result_distance = vec3.distance(v1_3d, v2_3d) +print("distance: " .. result_distance) -- 43 + -- длина вектора local result_len = vec3.length(v1_3d) print("len: " .. result_len) -- 3 diff --git a/src/logic/scripting/lua/libs/libvecn.cpp b/src/logic/scripting/lua/libs/libvecn.cpp index 724885fe..000a4367 100644 --- a/src/logic/scripting/lua/libs/libvecn.cpp +++ b/src/logic/scripting/lua/libs/libvecn.cpp @@ -74,6 +74,14 @@ static int l_scalar_op(lua::State* L) { return lua::pushnumber(L, func(vec)); } +template +static int l_distance(lua::State* L) { + lua::check_argc(L, 2); + auto a = lua::tovec(L, 1); + auto b = lua::tovec(L, 2); + return lua::pushnumber(L,glm::distance(a, b)); +} + template static int l_pow(lua::State* L) { uint argc = lua::check_argc(L, 2, 3); @@ -182,6 +190,7 @@ const luaL_Reg vec2lib[] = { {"sub", lua::wrap>}, {"mul", lua::wrap>}, {"div", lua::wrap>}, + {"distance", lua::wrap>}, {"normalize", lua::wrap>}, {"length", lua::wrap>}, {"tostring", lua::wrap>}, @@ -198,6 +207,7 @@ const luaL_Reg vec3lib[] = { {"sub", lua::wrap>}, {"mul", lua::wrap>}, {"div", lua::wrap>}, + {"distance", lua::wrap>}, {"normalize", lua::wrap>}, {"length", lua::wrap>}, {"tostring", lua::wrap>}, @@ -214,6 +224,7 @@ const luaL_Reg vec4lib[] = { {"sub", lua::wrap>}, {"mul", lua::wrap>}, {"div", lua::wrap>}, + {"distance", lua::wrap>}, {"normalize", lua::wrap>}, {"length", lua::wrap>}, {"tostring", lua::wrap>}, From 2fba3eba9ca30486648baac20e6ff289052fdc16 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:44:04 +0300 Subject: [PATCH 045/177] Update settings_controls.xml.lua --- res/layouts/pages/settings_controls.xml.lua | 55 +++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/res/layouts/pages/settings_controls.xml.lua b/res/layouts/pages/settings_controls.xml.lua index ad8491ad..df6fe20b 100644 --- a/res/layouts/pages/settings_controls.xml.lua +++ b/res/layouts/pages/settings_controls.xml.lua @@ -1,3 +1,12 @@ +local WARNING_COLORS = { + {252, 200, 149, 255}, + {246, 233, 44, 255}, + {250, 151, 75, 255}, + {250, 75, 139, 255}, + {208, 104, 107, 255} +} + +local GENERAL_WARNING_COLOR = {208, 138, 0, 255} function refresh_search() local search_text = document.search_textbox.text @@ -40,6 +49,48 @@ function change_sensitivity(val) refresh_sensitivity() end +function refresh_binding_labels() + local prev_bindings = {} + local conflicts_colors = {} + local available_colors = table.copy(WARNING_COLORS) + + local bindings = input.get_bindings() + table.sort(bindings, function(a, b) return a > b end) + + for _, bind_name in pairs(bindings) do + local key = input.get_binding_text(bind_name) + local prev = prev_bindings[key] + if prev then + local color = GENERAL_WARNING_COLOR + local conflict_color = conflicts_colors[key] + if conflict_color then + color = conflict_color + elseif #available_colors > 0 then + color = available_colors[1] + conflicts_colors[key] = color + table.remove(available_colors, 1) + end + + local tooltip = gui.str("settings.Conflict", "settings") + + local prev_bindmark = "bindmark_" .. prev + local cur_bindmark = "bindmark_" .. bind_name + document[prev_bindmark].visible = true + document[cur_bindmark].visible = true + + document[prev_bindmark].color = color + document[cur_bindmark].color = color + + document["bind_" .. prev].tooltip = tooltip + document["bind_" .. bind_name].tooltip = tooltip + else + document["bindmark_" .. bind_name].visible = false + document["bind_" .. bind_name].tooltip = '' + prev_bindings[key] = bind_name + end + end +end + function on_open() document.sensitivity_track.value = core.get_setting("camera.sensitivity") refresh_sensitivity() @@ -52,4 +103,8 @@ function on_open() id=name, name=gui.str(name) })) end + + document.bindings_panel:setInterval(100, function () + refresh_binding_labels() + end) end From 1f0b6f0d9d665f6222c1959f6550847a7da05517 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:50:27 +0300 Subject: [PATCH 046/177] Qol settings.controls update --- res/layouts/templates/binding.xml | 5 +++-- res/preload.json | 3 ++- res/texts/en_US.txt | 1 + res/texts/ru_RU.txt | 1 + res/textures/gui/half_block.png | Bin 0 -> 531 bytes 5 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 res/textures/gui/half_block.png diff --git a/res/layouts/templates/binding.xml b/res/layouts/templates/binding.xml index 643bd5aa..92843749 100644 --- a/res/layouts/templates/binding.xml +++ b/res/layouts/templates/binding.xml @@ -1,4 +1,5 @@ - + - + + diff --git a/res/preload.json b/res/preload.json index 887b0e26..0cd11b5b 100644 --- a/res/preload.json +++ b/res/preload.json @@ -47,7 +47,8 @@ "gui/info", "gui/world", "gui/hud", - "gui/entity" + "gui/entity", + "gui/half_block" ], "fonts": [ { diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 57a6cb6b..b3be4c92 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -22,6 +22,7 @@ graphics.dense-render.tooltip=Enables transparency in blocks like leaves # settings settings.Controls Search Mode=Search by attached button name +settings.Conflict=Possible conflicts found # Bindings chunks.reload=Reload Chunks diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index d2d7c63e..5d8d22ca 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -100,6 +100,7 @@ settings.Controls Search Mode=Поиск по привязанной кнопк settings.Limit Background FPS=Ограничить фоновую частоту кадров settings.Advanced render=Продвинутый рендер settings.Shadows quality=Качество теней +settings.Conflict=Найдены возможные конфликты # Управление chunks.reload=Перезагрузить Чанки diff --git a/res/textures/gui/half_block.png b/res/textures/gui/half_block.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6b699ed06e5d85ec26040170976688d5a5f1db GIT binary patch literal 531 zcmeAS@N?(olHy`uVBq!ia0vp^EI`b`!3HGn8ON~#DaPU;cPEB*=VV@jWYZme9T^xl z_H+M91W9rhctjR6Fz_7&Va6R3v)=+Wv}cAyltlRYSS9D@>LsS+C#C9D^BXQ!4ZB&DWj=Gm&h-@RX5Av48RJ>1mSz_-9TH6zobswg$M$}c3jDm&RSMcv+x zm&>NY3TQ%ZYDuC(MQ%=Bu~mhw64+oXAR8pCuViOal#*r@k>g7FXt#Bv$C=6)Qsxa7isrF3Kz@$;{7F02!E= zlwVq6t5jN=nPQcem}Z)kl47cxlxUoyYiO8~sGFE#Y^a-JY@A|lYG`4UW|*V|wZ0@X z4Pk#?F*F!}0iq9*(KpmH067`Nw(>8^Oa;0EWTl;<4OkvU%tjyN5G04$K}3Ll1+w5F z0}3X1=%(fYgR~qNb~;mLa0A0#)YHW=L?S%-&;S4b>)BKew47NupMgQ*0t?THlb7{C OG2`j#=d#Wzp$P!L;;2mk literal 0 HcmV?d00001 From 23837418f1d4d1fe77728bc37d44054cf85cc994 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:11:44 +0300 Subject: [PATCH 047/177] minor optimization --- res/layouts/pages/settings_controls.xml.lua | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/res/layouts/pages/settings_controls.xml.lua b/res/layouts/pages/settings_controls.xml.lua index df6fe20b..34631929 100644 --- a/res/layouts/pages/settings_controls.xml.lua +++ b/res/layouts/pages/settings_controls.xml.lua @@ -1,9 +1,9 @@ local WARNING_COLORS = { - {252, 200, 149, 255}, - {246, 233, 44, 255}, - {250, 151, 75, 255}, + {208, 104, 107, 255}, {250, 75, 139, 255}, - {208, 104, 107, 255} + {250, 151, 75, 255}, + {246, 233, 44, 255}, + {252, 200, 149, 255} } local GENERAL_WARNING_COLOR = {208, 138, 0, 255} @@ -49,7 +49,7 @@ function change_sensitivity(val) refresh_sensitivity() end -function refresh_binding_labels() +function refresh_binding_marks() local prev_bindings = {} local conflicts_colors = {} local available_colors = table.copy(WARNING_COLORS) @@ -57,18 +57,19 @@ function refresh_binding_labels() local bindings = input.get_bindings() table.sort(bindings, function(a, b) return a > b end) - for _, bind_name in pairs(bindings) do + for _, bind_name in ipairs(bindings) do local key = input.get_binding_text(bind_name) local prev = prev_bindings[key] if prev then local color = GENERAL_WARNING_COLOR local conflict_color = conflicts_colors[key] + local available_colors_len = #available_colors if conflict_color then color = conflict_color - elseif #available_colors > 0 then - color = available_colors[1] + elseif available_colors_len > 0 then + color = available_colors[available_colors_len] conflicts_colors[key] = color - table.remove(available_colors, 1) + table.remove(available_colors, available_colors_len) end local tooltip = gui.str("settings.Conflict", "settings") @@ -105,6 +106,6 @@ function on_open() end document.bindings_panel:setInterval(100, function () - refresh_binding_labels() + refresh_binding_marks() end) end From 7749da4a85549d690913d935645acf4ecaffcb75 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 5 Aug 2025 23:28:52 +0300 Subject: [PATCH 048/177] feat: project scripts --- src/engine/Engine.hpp | 4 ++++ src/engine/Mainloop.cpp | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index 8f8683da..a3427c14 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -174,4 +174,8 @@ public: devtools::Editor& getEditor() { return *editor; } + + const Project& getProject() { + return *project; + } }; diff --git a/src/engine/Mainloop.cpp b/src/engine/Mainloop.cpp index ecfd17bc..bdde4f53 100644 --- a/src/engine/Mainloop.cpp +++ b/src/engine/Mainloop.cpp @@ -2,8 +2,11 @@ #include "Engine.hpp" #include "debug/Logger.hpp" +#include "devtools/Project.hpp" #include "frontend/screens/MenuScreen.hpp" #include "frontend/screens/LevelScreen.hpp" +#include "interfaces/Process.hpp" +#include "logic/scripting/scripting.hpp" #include "window/Window.hpp" #include "world/Level.hpp" @@ -28,12 +31,24 @@ void Mainloop::run() { )); } }); + + io::path projectScript = "project:project_script.lua"; + std::unique_ptr process; + if (io::exists(projectScript)) { + logger.info() << "starting project script"; + process = scripting::start_coroutine(projectScript); + } else { + logger.warning() << "project script does not exists"; + } logger.info() << "starting menu screen"; engine.setScreen(std::make_shared(engine)); logger.info() << "main loop started"; while (!window.isShouldClose()){ + if (process) { + process->update(); + } time.update(window.time()); engine.updateFrontend(); if (!window.isIconified()) { From f6be6689aa2a24fe26091405c319aa5bebcdb279 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 6 Aug 2025 23:34:00 +0300 Subject: [PATCH 049/177] feat: passing args to component in entity definition --- src/content/loading/EntityLoader.cpp | 13 ++++++++++++- src/logic/scripting/scripting.cpp | 2 ++ src/objects/Entities.cpp | 4 ++-- src/objects/Entities.hpp | 11 +++++++++-- src/objects/EntityDef.hpp | 10 ++++++++-- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/content/loading/EntityLoader.cpp b/src/content/loading/EntityLoader.cpp index 7778282c..29a72cb7 100644 --- a/src/content/loading/EntityLoader.cpp +++ b/src/content/loading/EntityLoader.cpp @@ -30,7 +30,18 @@ template<> void ContentUnitLoader::loadUnit( if (auto found = root.at("components")) { for (const auto& elem : *found) { - def.components.emplace_back(elem.asString()); + std::string name; + dv::value params; + if (elem.isObject()) { + name = elem["name"].asString(); + if (elem.has("args")) { + params = elem["args"]; + } + } else { + name = elem.asString(); + } + def.components.push_back(ComponentInstance { + std::move(name), std::move(params)}); } } if (auto found = root.at("hitbox")) { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 39168843..56c5a17e 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -603,6 +603,8 @@ void scripting::on_entity_spawn( } else { lua::createtable(L, 0, 0); } + } else if (component->params != nullptr) { + lua::pushvalue(L, component->params); } else { lua::createtable(L, 0, 0); } diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 4cfe1b29..cdf979e0 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -173,9 +173,9 @@ entityid_t Entities::spawn( auto& scripting = registry.emplace(entity); registry.emplace(entity, skeleton->instance()); - for (auto& componentName : def.components) { + for (auto& instance : def.components) { auto component = std::make_unique( - componentName, EntityFuncsSet {}, nullptr + instance.component, EntityFuncsSet {}, nullptr, instance.params ); scripting.components.emplace_back(std::move(component)); } diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 6518b599..5bd8f955 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -80,11 +80,18 @@ struct UserComponent { std::string name; EntityFuncsSet funcsset; scriptenv env; + dv::value params; UserComponent( - const std::string& name, EntityFuncsSet funcsset, scriptenv env + const std::string& name, + EntityFuncsSet funcsset, + scriptenv env, + dv::value params ) - : name(name), funcsset(funcsset), env(env) { + : name(name), + funcsset(funcsset), + env(std::move(env)), + params(std::move(params)) { } }; diff --git a/src/objects/EntityDef.hpp b/src/objects/EntityDef.hpp index 53c69c95..4d10a2d0 100644 --- a/src/objects/EntityDef.hpp +++ b/src/objects/EntityDef.hpp @@ -5,6 +5,7 @@ #include #include "typedefs.hpp" +#include "data/dv.hpp" #include "maths/aabb.hpp" #include "physics/Hitbox.hpp" @@ -12,12 +13,17 @@ namespace rigging { class SkeletonConfig; } +struct ComponentInstance { + std::string component; + dv::value params; +}; + struct EntityDef { /// @brief Entity string id (with prefix included) std::string const name; - /// @brief Component IDs - std::vector components; + /// @brief Component instances + std::vector components; /// @brief Physic body type BodyType bodyType = BodyType::DYNAMIC; From 5f7a75bd7a327bab18498d1fadb05cdb4e1fd558 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 6 Aug 2025 23:34:31 +0300 Subject: [PATCH 050/177] update base:drop --- res/content/base/entities/drop.json | 9 ++++++++- res/content/base/scripts/components/drop.lua | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/res/content/base/entities/drop.json b/res/content/base/entities/drop.json index 4c5e064f..951ea5e6 100644 --- a/res/content/base/entities/drop.json +++ b/res/content/base/entities/drop.json @@ -1,6 +1,13 @@ { "components": [ - "base:drop" + { + "name": "base:drop", + "args": { + "item": "base:stone.item", + "count": 1 + } + } + ], "hitbox": [0.4, 0.25, 0.4], "sensors": [ diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index a979c72d..2b798a38 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -8,6 +8,9 @@ timer = 0.3 local def_index = entity:def_index() dropitem = ARGS +if dropitem.item then + dropitem.id = item.index(dropitem.item) +end if dropitem then timer = dropitem.pickup_delay or timer end From 36a522bee09a6784c394664f45240ebdd257cdf9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 6 Aug 2025 23:37:55 +0300 Subject: [PATCH 051/177] add 'entity.spawn' command --- res/scripts/stdcmd.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index 0308910f..1b204aa4 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -157,6 +157,16 @@ console.add_command( end ) + +console.add_command( + "entity.spawn nmae:str x:int~pos.x y:int~pos.y z:int~pos.z", + "Spawn entity with default parameters", + function(args, kwargs) + local eid = entities.spawn(args[1], {args[2], args[3], args[4]}) + return string.format("spawned %s at %s, %s, %s", unpack(args)) + end +) + console.add_command( "entity.despawn entity:sel=$entity.selected", "Despawn entity", From 0a02d3fbec6de2f27a265ef14d2753cbc196e0d5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 7 Aug 2025 22:14:52 +0300 Subject: [PATCH 052/177] add on_screen_change project script event & fix UIDocument::rebuildIndices & move menu background to project script --- res/project_script.lua | 26 ++++++++++++++++++ src/engine/Engine.cpp | 22 +++++++++++++++ src/engine/Engine.hpp | 9 +++++++ src/engine/Mainloop.cpp | 17 +++--------- src/frontend/UiDocument.cpp | 4 ++- src/frontend/screens/LevelScreen.cpp | 5 +++- src/frontend/screens/LevelScreen.hpp | 5 ++++ src/frontend/screens/MenuScreen.cpp | 36 +++++-------------------- src/frontend/screens/MenuScreen.hpp | 6 +++++ src/frontend/screens/Screen.hpp | 2 ++ src/graphics/ui/GUI.cpp | 18 ++++++------- src/graphics/ui/GUI.hpp | 3 +++ src/logic/scripting/lua/libs/libgui.cpp | 7 +++-- src/logic/scripting/lua/lua_engine.cpp | 6 +---- src/logic/scripting/scripting.cpp | 35 ++++++++++++++++++++++-- src/logic/scripting/scripting.hpp | 13 ++++++--- 16 files changed, 148 insertions(+), 66 deletions(-) create mode 100644 res/project_script.lua diff --git a/res/project_script.lua b/res/project_script.lua new file mode 100644 index 00000000..5f28c300 --- /dev/null +++ b/res/project_script.lua @@ -0,0 +1,26 @@ +local menubg + +function on_screen_changed(screen) + if screen ~= "menu" then + if menubg then + menubg:destruct() + menubg = nil + end + return + end + local controller = {} + function controller.resize_menu_bg() + local w, h = unpack(gui.get_viewport()) + if menubg then + menubg.region = {0, math.floor(h / 48), math.floor(w / 48), 0} + menubg.pos = {0, 0} + end + return w, h + end + _GUI_ROOT.root:add( + "", controller) + menubg = _GUI_ROOT.menubg + controller.resize_menu_bg() + menu.page = "main" +end diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 6596733f..5e5ba681 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -60,6 +60,17 @@ static std::unique_ptr load_icon() { return nullptr; } +static std::unique_ptr load_project_script() { + io::path scriptFile = "project:project_script.lua"; + if (io::exists(scriptFile)) { + logger.info() << "starting project script"; + return scripting::load_project_script(scriptFile); + } else { + logger.warning() << "project script does not exists"; + } + return nullptr; +} + Engine::Engine() = default; Engine::~Engine() = default; @@ -166,12 +177,15 @@ void Engine::initialize(CoreParameters coreParameters) { } }); scripting::initialize(this); + if (!isHeadless()) { gui->setPageLoader(scripting::create_page_loader()); } keepAlive(settings.ui.language.observe([this](auto lang) { langs::setup(lang, paths.resPaths.collectRoots()); }, true)); + + projectScript = load_project_script(); } void Engine::loadSettings() { @@ -228,6 +242,7 @@ void Engine::run() { } } +#include "graphics/ui/elements/Container.hpp" void Engine::postUpdate() { network->update(); postRunnables.run(); @@ -270,6 +285,7 @@ void Engine::saveSettings() { } void Engine::close() { + projectScript.reset(); saveSettings(); logger.info() << "shutting down"; if (screen) { @@ -349,6 +365,12 @@ void Engine::setScreen(std::shared_ptr screen) { audio::reset_channel(audio::get_channel_index("regular")); audio::reset_channel(audio::get_channel_index("ambient")); this->screen = std::move(screen); + if (this->screen) { + this->screen->onOpen(); + } + if (projectScript && this->screen) { + projectScript->onScreenChange(this->screen->getName()); + } } void Engine::onWorldOpen(std::unique_ptr level, int64_t localPlayer) { diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index a3427c14..d82fc4e6 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -38,6 +38,10 @@ namespace devtools { class Editor; } +namespace scripting { + class IProjectScript; +} + class initialize_error : public std::runtime_error { public: initialize_error(const std::string& message) : std::runtime_error(message) {} @@ -71,6 +75,7 @@ class Engine : public util::ObjectsKeeper { std::unique_ptr input; std::unique_ptr gui; std::unique_ptr editor; + std::unique_ptr projectScript; PostRunnables postRunnables; Time time; OnWorldOpen levelConsumer; @@ -178,4 +183,8 @@ public: const Project& getProject() { return *project; } + + scripting::IProjectScript* getProjectScript() { + return projectScript.get(); + } }; diff --git a/src/engine/Mainloop.cpp b/src/engine/Mainloop.cpp index bdde4f53..a14c5464 100644 --- a/src/engine/Mainloop.cpp +++ b/src/engine/Mainloop.cpp @@ -5,10 +5,10 @@ #include "devtools/Project.hpp" #include "frontend/screens/MenuScreen.hpp" #include "frontend/screens/LevelScreen.hpp" -#include "interfaces/Process.hpp" -#include "logic/scripting/scripting.hpp" #include "window/Window.hpp" #include "world/Level.hpp" +#include "graphics/ui/GUI.hpp" +#include "graphics/ui/elements/Container.hpp" static debug::Logger logger("mainloop"); @@ -31,26 +31,15 @@ void Mainloop::run() { )); } }); - - io::path projectScript = "project:project_script.lua"; - std::unique_ptr process; - if (io::exists(projectScript)) { - logger.info() << "starting project script"; - process = scripting::start_coroutine(projectScript); - } else { - logger.warning() << "project script does not exists"; - } logger.info() << "starting menu screen"; engine.setScreen(std::make_shared(engine)); logger.info() << "main loop started"; while (!window.isShouldClose()){ - if (process) { - process->update(); - } time.update(window.time()); engine.updateFrontend(); + if (!window.isIconified()) { engine.renderFrame(); } diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 7c6f8d13..3e9a3a27 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -14,11 +14,13 @@ UiDocument::UiDocument( const std::shared_ptr& root, scriptenv env ) : id(std::move(id)), script(script), root(root), env(std::move(env)) { - gui::UINode::getIndices(root, map); + rebuildIndices(); } void UiDocument::rebuildIndices() { + map.clear(); gui::UINode::getIndices(root, map); + map["root"] = root; } const UINodesMap& UiDocument::getMap() const { diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index ca80ed11..aa007293 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -97,7 +97,6 @@ LevelScreen::LevelScreen( animator->addAnimations(assets.getAnimations()); loadDecorations(); - initializeContent(); } LevelScreen::~LevelScreen() { @@ -112,6 +111,10 @@ LevelScreen::~LevelScreen() { engine.getPaths().setCurrentWorldFolder(""); } +void LevelScreen::onOpen() { + initializeContent(); +} + void LevelScreen::initializeContent() { auto& content = controller->getLevel()->content; for (auto& entry : content.getPacks()) { diff --git a/src/frontend/screens/LevelScreen.hpp b/src/frontend/screens/LevelScreen.hpp index b4f6c93a..616258e5 100644 --- a/src/frontend/screens/LevelScreen.hpp +++ b/src/frontend/screens/LevelScreen.hpp @@ -53,8 +53,13 @@ public: ); ~LevelScreen(); + void onOpen() override; void update(float delta) override; void draw(float delta) override; void onEngineShutdown() override; + + const char* getName() const override { + return "level"; + } }; diff --git a/src/frontend/screens/MenuScreen.cpp b/src/frontend/screens/MenuScreen.cpp index be114ed3..c1339494 100644 --- a/src/frontend/screens/MenuScreen.cpp +++ b/src/frontend/screens/MenuScreen.cpp @@ -13,12 +13,6 @@ #include "engine/Engine.hpp" MenuScreen::MenuScreen(Engine& engine) : Screen(engine) { - engine.getContentControl().resetContent(); - - auto menu = engine.getGUI().getMenu(); - menu->reset(); - menu->setPage("main"); - uicamera = std::make_unique(glm::vec3(), engine.getWindow().getSize().y); uicamera->perspective = false; @@ -29,33 +23,17 @@ MenuScreen::MenuScreen(Engine& engine) : Screen(engine) { MenuScreen::~MenuScreen() = default; +void MenuScreen::onOpen() { + engine.getContentControl().resetContent(); + + auto menu = engine.getGUI().getMenu(); + menu->reset(); +} + void MenuScreen::update(float delta) { } void MenuScreen::draw(float delta) { - auto assets = engine.getAssets(); - display::clear(); display::setBgColor(glm::vec3(0.2f)); - - const auto& size = engine.getWindow().getSize(); - uint width = size.x; - uint height = size.y; - - uicamera->setFov(height); - uicamera->setAspectRatio(width / static_cast(height)); - auto uishader = assets->get("ui"); - uishader->use(); - uishader->uniformMatrix("u_projview", uicamera->getProjView()); - - auto bg = assets->get("gui/menubg"); - batch->begin(); - batch->texture(bg); - batch->rect( - 0, 0, - width, height, 0, 0, 0, - UVRegion(0, 0, width / bg->getWidth(), height / bg->getHeight()), - false, false, glm::vec4(1.0f) - ); - batch->flush(); } diff --git a/src/frontend/screens/MenuScreen.hpp b/src/frontend/screens/MenuScreen.hpp index f9b5dd48..a8caf0fe 100644 --- a/src/frontend/screens/MenuScreen.hpp +++ b/src/frontend/screens/MenuScreen.hpp @@ -13,6 +13,12 @@ public: MenuScreen(Engine& engine); ~MenuScreen(); + void onOpen() override; + void update(float delta) override; void draw(float delta) override; + + const char* getName() const override { + return "menu"; + } }; diff --git a/src/frontend/screens/Screen.hpp b/src/frontend/screens/Screen.hpp index dde2f555..c24dc8b3 100644 --- a/src/frontend/screens/Screen.hpp +++ b/src/frontend/screens/Screen.hpp @@ -13,7 +13,9 @@ protected: public: Screen(Engine& engine); virtual ~Screen(); + virtual void onOpen() = 0; virtual void update(float delta) = 0; virtual void draw(float delta) = 0; virtual void onEngineShutdown() {}; + virtual const char* getName() const = 0; }; diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index ceb2f4a0..79e9fd63 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -56,6 +56,13 @@ GUI::GUI(Engine& engine) store("tooltip", tooltip); store("tooltip.label", UINode::find(tooltip, "tooltip.label")); container->add(tooltip); + + rootDocument = std::make_unique( + "core:root", + uidocscript {}, + std::dynamic_pointer_cast(container), + nullptr + ); } GUI::~GUI() = default; @@ -74,15 +81,8 @@ std::shared_ptr GUI::getMenu() { } void GUI::onAssetsLoad(Assets* assets) { - assets->store( - std::make_unique( - "core:root", - uidocscript {}, - std::dynamic_pointer_cast(container), - nullptr - ), - "core:root" - ); + rootDocument->rebuildIndices(); + assets->store(rootDocument, "core:root"); } void GUI::resetTooltip() { diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index 92e58db5..f2255fb5 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -22,6 +22,8 @@ namespace devtools { class Editor; } +class UiDocument; + /* Some info about padding and margin. Padding is element inner space, margin is outer @@ -70,6 +72,7 @@ namespace gui { std::shared_ptr pressed; std::shared_ptr focus; std::shared_ptr tooltip; + std::shared_ptr rootDocument; std::unordered_map> storage; std::unique_ptr uicamera; diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 66a8f648..2f37bcb2 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -79,9 +79,12 @@ static int l_textbox_paste(lua::State* L) { static int l_container_add(lua::State* L) { auto docnode = get_document_node(L); + if (docnode.document == nullptr) { + throw std::runtime_error("target document not found"); + } auto node = dynamic_cast(docnode.node.get()); if (node == nullptr) { - return 0; + throw std::runtime_error("target container not found"); } auto xmlsrc = lua::require_string(L, 2); try { @@ -99,7 +102,7 @@ static int l_container_add(lua::State* L) { UINode::getIndices(subnode, docnode.document->getMapWriteable()); node->add(std::move(subnode)); } catch (const std::exception& err) { - throw std::runtime_error(err.what()); + throw std::runtime_error("container:add(...): " + std::string(err.what())); } return 0; } diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 891b89c7..be37e7bb 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -58,12 +58,8 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "vec4", vec4lib); openlib(L, "yaml", yamllib); - if (stateType == StateType::SCRIPT) { - openlib(L, "app", applib); - } else if (stateType == StateType::BASE) { - openlib(L, "__vc_app", applib); - } if (stateType == StateType::BASE || stateType == StateType::SCRIPT) { + openlib(L, "__vc_app", applib); openlib(L, "assets", assetslib); openlib(L, "audio", audiolib); openlib(L, "console", consolelib); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 39168843..04db321e 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -116,11 +116,42 @@ public: } }; -std::unique_ptr scripting::start_coroutine( +class LuaProjectScript : public IProjectScript { +public: + LuaProjectScript(lua::State* L, scriptenv env) : L(L), env(std::move(env)) {} + + void onScreenChange(const std::string& name) override { + if (!lua::pushenv(L, *env)) { + return; + } + if (!lua::getfield(L, "on_screen_changed")) { + lua::pop(L); + return; + } + lua::pushlstring(L, name); + lua::call_nothrow(L, 1, 0); + lua::pop(L); + } +private: + lua::State* L; + scriptenv env; +}; + +std::unique_ptr scripting::load_project_script( const io::path& script ) { auto L = lua::get_main_state(); - if (lua::getglobal(L, "__vc_start_coroutine")) { + auto source = io::read_string(script); + auto env = create_environment(nullptr); + lua::loadbuffer(L, *env, source, script.name()); + lua::call(L, 0); + return std::make_unique(L, std::move(env)); +} + +std::unique_ptr scripting::start_coroutine(const io::path& script) { + auto L = lua::get_main_state(); + auto method = "__vc_start_coroutine"; + if (lua::getglobal(L, method)) { auto source = io::read_string(script); lua::loadbuffer(L, 0, source, script.name()); if (lua::call(L, 1)) { diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index bae5aac6..b6f8733d 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -65,9 +65,16 @@ namespace scripting { void process_post_runnables(); - std::unique_ptr start_coroutine( - const io::path& script - ); + class IProjectScript { + public: + virtual ~IProjectScript() {} + + virtual void onScreenChange(const std::string& name) = 0; + }; + + std::unique_ptr load_project_script(const io::path& script); + + std::unique_ptr start_coroutine(const io::path& script); void on_world_load(LevelController* controller); void on_world_tick(int tps); From 9fcff65ccacab06e36731b4c34fd857a86167fff Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 7 Aug 2025 22:37:40 +0300 Subject: [PATCH 053/177] cleanup --- res/project_script.lua | 24 ++++++++++++++++-------- src/devtools/Project.cpp | 3 +++ src/devtools/Project.hpp | 8 ++++++++ src/engine/Engine.cpp | 9 ++++----- src/engine/Engine.hpp | 5 ----- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/res/project_script.lua b/res/project_script.lua index 5f28c300..0da3b54d 100644 --- a/res/project_script.lua +++ b/res/project_script.lua @@ -1,13 +1,13 @@ local menubg -function on_screen_changed(screen) - if screen ~= "menu" then - if menubg then - menubg:destruct() - menubg = nil - end - return +local function clear_menu() + if menubg then + menubg:destruct() + menubg = nil end +end + +local function configure_menu() local controller = {} function controller.resize_menu_bg() local w, h = unpack(gui.get_viewport()) @@ -19,8 +19,16 @@ function on_screen_changed(screen) end _GUI_ROOT.root:add( "", controller) + "z-index='-1' interactive='true'/>", controller) menubg = _GUI_ROOT.menubg controller.resize_menu_bg() menu.page = "main" end + +function on_screen_changed(screen) + if screen ~= "menu" then + clear_menu() + else + configure_menu() + end +end diff --git a/src/devtools/Project.cpp b/src/devtools/Project.cpp index c1d382ee..881f6cc7 100644 --- a/src/devtools/Project.cpp +++ b/src/devtools/Project.cpp @@ -1,6 +1,9 @@ #include "Project.hpp" #include "data/dv_util.hpp" +#include "logic/scripting/scripting.hpp" + +Project::~Project() = default; dv::value Project::serialize() const { return dv::object({ diff --git a/src/devtools/Project.hpp b/src/devtools/Project.hpp index 857b58f1..3824ecfd 100644 --- a/src/devtools/Project.hpp +++ b/src/devtools/Project.hpp @@ -2,13 +2,21 @@ #include #include +#include #include "interfaces/Serializable.hpp" +namespace scripting { + class IProjectScript; +} + struct Project : Serializable { std::string name; std::string title; std::vector basePacks; + std::unique_ptr script; + + ~Project(); dv::value serialize() const override; void deserialize(const dv::value& src) override; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 5e5ba681..3f1ae9e5 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -185,7 +185,7 @@ void Engine::initialize(CoreParameters coreParameters) { langs::setup(lang, paths.resPaths.collectRoots()); }, true)); - projectScript = load_project_script(); + project->script = load_project_script(); } void Engine::loadSettings() { @@ -242,7 +242,6 @@ void Engine::run() { } } -#include "graphics/ui/elements/Container.hpp" void Engine::postUpdate() { network->update(); postRunnables.run(); @@ -285,7 +284,6 @@ void Engine::saveSettings() { } void Engine::close() { - projectScript.reset(); saveSettings(); logger.info() << "shutting down"; if (screen) { @@ -302,6 +300,7 @@ void Engine::close() { audio::close(); network.reset(); clearKeepedObjects(); + project.reset(); scripting::close(); logger.info() << "scripting finished"; if (!params.headless) { @@ -368,8 +367,8 @@ void Engine::setScreen(std::shared_ptr screen) { if (this->screen) { this->screen->onOpen(); } - if (projectScript && this->screen) { - projectScript->onScreenChange(this->screen->getName()); + if (project->script && this->screen) { + project->script->onScreenChange(this->screen->getName()); } } diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index d82fc4e6..35619a2a 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -75,7 +75,6 @@ class Engine : public util::ObjectsKeeper { std::unique_ptr input; std::unique_ptr gui; std::unique_ptr editor; - std::unique_ptr projectScript; PostRunnables postRunnables; Time time; OnWorldOpen levelConsumer; @@ -183,8 +182,4 @@ public: const Project& getProject() { return *project; } - - scripting::IProjectScript* getProjectScript() { - return projectScript.get(); - } }; From 5785b9fdda49dc730758e0988208047f4d12c1da Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 7 Aug 2025 23:05:50 +0300 Subject: [PATCH 054/177] revert lua_engine.cpp changes --- src/logic/scripting/lua/lua_engine.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index be37e7bb..891b89c7 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -58,8 +58,12 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "vec4", vec4lib); openlib(L, "yaml", yamllib); - if (stateType == StateType::BASE || stateType == StateType::SCRIPT) { + if (stateType == StateType::SCRIPT) { + openlib(L, "app", applib); + } else if (stateType == StateType::BASE) { openlib(L, "__vc_app", applib); + } + if (stateType == StateType::BASE || stateType == StateType::SCRIPT) { openlib(L, "assets", assetslib); openlib(L, "audio", audiolib); openlib(L, "console", consolelib); From 3760fb86f7453dd7673fdb58f713383218211fdf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 8 Aug 2025 00:29:12 +0300 Subject: [PATCH 055/177] move events library implementation to core:internal/events & disable access to core:internal modules outside of stdlib --- res/layouts/code_editor.xml.lua | 2 +- res/layouts/files_panel.xml.lua | 7 ++--- res/modules/internal/events.lua | 48 ++++++++++++++++++++++++++++ res/scripts/post_content.lua | 3 +- res/scripts/stdlib.lua | 55 +++------------------------------ res/scripts/stdmin.lua | 11 +++++++ 6 files changed, 67 insertions(+), 59 deletions(-) create mode 100644 res/modules/internal/events.lua diff --git a/res/layouts/code_editor.xml.lua b/res/layouts/code_editor.xml.lua index de460f2e..66a70ed8 100644 --- a/res/layouts/code_editor.xml.lua +++ b/res/layouts/code_editor.xml.lua @@ -256,7 +256,7 @@ function open_file_in_editor(filename, line, mutable) end function on_open(mode) - registry = require "core:internal/scripts_registry" + registry = __vc_scripts_registry document.codePanel:setInterval(200, refresh_file_title) diff --git a/res/layouts/files_panel.xml.lua b/res/layouts/files_panel.xml.lua index a4d6ca39..30b0ddc8 100644 --- a/res/layouts/files_panel.xml.lua +++ b/res/layouts/files_panel.xml.lua @@ -43,11 +43,8 @@ function build_files_list(filenames, highlighted_part) end end -function on_open(mode) - registry = require "core:internal/scripts_registry" - - local files_list = document.filesList - +function on_open() + registry = __vc_scripts_registry filenames = registry.filenames table.sort(filenames) build_files_list(filenames) diff --git a/res/modules/internal/events.lua b/res/modules/internal/events.lua new file mode 100644 index 00000000..4820ddd5 --- /dev/null +++ b/res/modules/internal/events.lua @@ -0,0 +1,48 @@ +local events = { + handlers = {} +} + +function events.on(event, func) + if events.handlers[event] == nil then + events.handlers[event] = {} + end + table.insert(events.handlers[event], func) +end + +function events.reset(event, func) + if func == nil then + events.handlers[event] = nil + else + events.handlers[event] = {func} + end +end + +function events.remove_by_prefix(prefix) + for name, handlers in pairs(events.handlers) do + local actualname = name + if type(name) == 'table' then + actualname = name[1] + end + if actualname:sub(1, #prefix+1) == prefix..':' then + events.handlers[actualname] = nil + end + end +end + +function events.emit(event, ...) + local result = nil + local handlers = events.handlers[event] + if handlers == nil then + return nil + end + for _, func in ipairs(handlers) do + local status, newres = xpcall(func, __vc__error, ...) + if not status then + debug.error("error in event ("..event..") handler: "..newres) + else + result = result or newres + end + end + return result +end +return events diff --git a/res/scripts/post_content.lua b/res/scripts/post_content.lua index 7b17876e..2ad3633b 100644 --- a/res/scripts/post_content.lua +++ b/res/scripts/post_content.lua @@ -62,5 +62,4 @@ end cache_names(block) cache_names(item) -local scripts_registry = require "core:internal/scripts_registry" -scripts_registry.build_registry() +__vc_scripts_registry.build_registry() diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index b0fb86ea..c5d48faf 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -169,61 +169,12 @@ function inventory.set_description(invid, slot, description) inventory.set_data(invid, slot, "description", description) end ------------------------------------------------- -------------------- Events --------------------- ------------------------------------------------- -events = { - handlers = {} -} - -function events.on(event, func) - if events.handlers[event] == nil then - events.handlers[event] = {} - end - table.insert(events.handlers[event], func) -end - -function events.reset(event, func) - if func == nil then - events.handlers[event] = nil - else - events.handlers[event] = {func} - end -end - -function events.remove_by_prefix(prefix) - for name, handlers in pairs(events.handlers) do - local actualname = name - if type(name) == 'table' then - actualname = name[1] - end - if actualname:sub(1, #prefix+1) == prefix..':' then - events.handlers[actualname] = nil - end - end -end +events = require "core:internal/events" function pack.unload(prefix) events.remove_by_prefix(prefix) end -function events.emit(event, ...) - local result = nil - local handlers = events.handlers[event] - if handlers == nil then - return nil - end - for _, func in ipairs(handlers) do - local status, newres = xpcall(func, __vc__error, ...) - if not status then - debug.error("error in event ("..event..") handler: "..newres) - else - result = result or newres - end - end - return result -end - gui_util = require "core:internal/gui_util" Document = gui_util.Document @@ -319,11 +270,12 @@ entities.get_all = function(uids) end local bytearray = require "core:internal/bytearray" - Bytearray = bytearray.FFIBytearray Bytearray_as_string = bytearray.FFIBytearray_as_string Bytearray_construct = function(...) return Bytearray(...) end +__vc_scripts_registry = require "core:internal/scripts_registry" + file.open = require "core:internal/stream_providers/file" file.open_named_pipe = require "core:internal/stream_providers/named_pipe" @@ -342,6 +294,7 @@ else end ffi = nil +__vc_lock_internal_modules() math.randomseed(time.uptime() * 1536227939) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 38fdbfdc..669d5874 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -550,6 +550,8 @@ function reload_module(name) end end +local internal_locked = false + -- Load script with caching -- -- path - script path `contentpack:filename`. @@ -559,6 +561,11 @@ end function __load_script(path, nocache) local packname, filename = parse_path(path) + if internal_locked and (packname == "res" or packname == "core") + and filename:starts_with("modules/internal") then + error("access to core:internal modules outside of [core]") + end + -- __cached_scripts used in condition because cached result may be nil if not nocache and __cached_scripts[path] ~= nil then return package.loaded[path] @@ -579,6 +586,10 @@ function __load_script(path, nocache) return result end +function __vc_lock_internal_modules() + internal_locked = true +end + function require(path) if not string.find(path, ':') then local prefix, _ = parse_path(_debug_getinfo(2).source) From e6812371ecdc7cbecbcf3136df0f04c00525e550 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 8 Aug 2025 19:58:52 +0300 Subject: [PATCH 056/177] update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9056cce5..c0b0e1c9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ Debug/voxel_engine /export /config /out +/projects /misc /world @@ -47,4 +48,4 @@ appimage-build/ # libs /libs/ -/vcpkg_installed/ \ No newline at end of file +/vcpkg_installed/ From bfbfe59f9be12a1c6fe88948ce8f32857e5cd1d0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 8 Aug 2025 21:36:53 +0300 Subject: [PATCH 057/177] add gui.root & add 'app' library to project scripts --- doc/en/scripting/builtins/libgui.md | 6 ++++++ doc/ru/scripting/builtins/libgui.md | 6 ++++++ res/project_script.lua | 2 +- res/scripts/stdlib.lua | 1 + src/logic/scripting/scripting.cpp | 6 ++++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/en/scripting/builtins/libgui.md b/doc/en/scripting/builtins/libgui.md index cf6839db..cbcca4ce 100644 --- a/doc/en/scripting/builtins/libgui.md +++ b/doc/en/scripting/builtins/libgui.md @@ -103,3 +103,9 @@ gui.load_document( ``` Loads a UI document with its script, returns the name of the document if successfully loaded. + +```lua +gui.root: Document +``` + +Root UI document diff --git a/doc/ru/scripting/builtins/libgui.md b/doc/ru/scripting/builtins/libgui.md index aea2f44a..a9bffe48 100644 --- a/doc/ru/scripting/builtins/libgui.md +++ b/doc/ru/scripting/builtins/libgui.md @@ -100,3 +100,9 @@ gui.load_document( ``` Загружает UI документ с его скриптом, возвращает имя документа, если успешно загружен. + +```lua +gui.root: Document +``` + +Корневой UI документ diff --git a/res/project_script.lua b/res/project_script.lua index 0da3b54d..9158f361 100644 --- a/res/project_script.lua +++ b/res/project_script.lua @@ -17,7 +17,7 @@ local function configure_menu() end return w, h end - _GUI_ROOT.root:add( + gui.root.root:add( "", controller) menubg = _GUI_ROOT.menubg diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index c5d48faf..68b9e4f6 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -189,6 +189,7 @@ end _GUI_ROOT = Document.new("core:root") _MENU = _GUI_ROOT.menu menu = _MENU +gui.root = _GUI_ROOT --- Console library extension --- console.cheats = {} diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 04db321e..00d18668 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -143,6 +143,12 @@ std::unique_ptr scripting::load_project_script( auto L = lua::get_main_state(); auto source = io::read_string(script); auto env = create_environment(nullptr); + lua::pushenv(L, *env); + if (lua::getglobal(L, "__vc_app")) { + lua::setfield(L, "app"); + } + lua::pop(L); + lua::loadbuffer(L, *env, source, script.name()); lua::call(L, 0); return std::make_unique(L, std::move(env)); From 3eae37702422b0d6fabdd4c413937efc68650eb7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 9 Aug 2025 21:19:01 +0300 Subject: [PATCH 058/177] refactor Entities --- src/frontend/debug_panel.cpp | 1 + src/graphics/render/Decorator.cpp | 1 + src/graphics/render/Emitter.cpp | 1 + src/logic/PlayerController.cpp | 15 +- src/logic/scripting/lua/libs/libentity.cpp | 2 + src/logic/scripting/lua/libs/libentity.hpp | 1 + src/logic/scripting/lua/libs/libplayer.cpp | 1 + src/logic/scripting/scripting.cpp | 1 + src/objects/Entities.cpp | 176 ++------------------- src/objects/Entities.hpp | 174 +------------------- src/objects/Entity.cpp | 119 ++++++++++++++ src/objects/Entity.hpp | 79 +++++++++ src/objects/Player.cpp | 20 ++- src/objects/Player.hpp | 3 + src/objects/Rigidbody.cpp | 74 +++++++++ src/objects/Rigidbody.hpp | 22 +++ src/objects/ScriptComponents.hpp | 49 ++++++ src/objects/Transform.cpp | 25 +++ src/objects/Transform.hpp | 43 +++++ src/objects/rigging.cpp | 19 ++- src/objects/rigging.hpp | 5 +- src/voxels/GlobalChunks.cpp | 1 + src/world/Level.cpp | 1 + 23 files changed, 485 insertions(+), 348 deletions(-) create mode 100644 src/objects/Entity.cpp create mode 100644 src/objects/Entity.hpp create mode 100644 src/objects/Rigidbody.cpp create mode 100644 src/objects/Rigidbody.hpp create mode 100644 src/objects/ScriptComponents.hpp create mode 100644 src/objects/Transform.cpp create mode 100644 src/objects/Transform.hpp diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 06ab5fc8..5e238f42 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -18,6 +18,7 @@ #include "objects/Players.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" +#include "objects/Entity.hpp" #include "physics/Hitbox.hpp" #include "util/stringutil.hpp" #include "voxels/Block.hpp" diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index dc928b4d..4742b68f 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -15,6 +15,7 @@ #include "objects/Player.hpp" #include "objects/Players.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "logic/LevelController.hpp" #include "util/stringutil.hpp" #include "engine/Engine.hpp" diff --git a/src/graphics/render/Emitter.cpp b/src/graphics/render/Emitter.cpp index cdc883ae..483a6339 100644 --- a/src/graphics/render/Emitter.cpp +++ b/src/graphics/render/Emitter.cpp @@ -7,6 +7,7 @@ #include "window/Camera.hpp" #include "graphics/core/Texture.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "world/Level.hpp" Emitter::Emitter( diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index df471c36..f5fdea1b 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -13,6 +13,7 @@ #include "items/ItemStack.hpp" #include "lighting/Lighting.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "objects/Player.hpp" #include "objects/Players.hpp" #include "physics/Hitbox.hpp" @@ -309,16 +310,7 @@ void PlayerController::updateKeyboard(const Input& inputEvents) { } void PlayerController::resetKeyboard() { - input.zoom = false; - input.moveForward = false; - input.moveBack = false; - input.moveLeft = false; - input.moveRight = false; - input.sprint = false; - input.shift = false; - input.cheat = false; - input.jump = false; - input.delta = {}; + input = {}; } void PlayerController::updatePlayer(float delta) { @@ -338,7 +330,8 @@ static int determine_rotation( if (norm.z > 0.0f) return BLOCK_DIR_NORTH; if (norm.z < 0.0f) return BLOCK_DIR_SOUTH; } else if (name == "pane" || name == "stairs") { - int verticalBit = (name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0; + int verticalBit = + (name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0; if (abs(camDir.x) > abs(camDir.z)) { if (camDir.x > 0.0f) return BLOCK_DIR_EAST | verticalBit; if (camDir.x < 0.0f) return BLOCK_DIR_WEST | verticalBit; diff --git a/src/logic/scripting/lua/libs/libentity.cpp b/src/logic/scripting/lua/libs/libentity.cpp index 8e00aa1c..45e4e14e 100644 --- a/src/logic/scripting/lua/libs/libentity.cpp +++ b/src/logic/scripting/lua/libs/libentity.cpp @@ -4,6 +4,8 @@ #include "engine/Engine.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" +#include "objects/Entity.hpp" +#include "objects/Rigidbody.hpp" #include "objects/Player.hpp" #include "objects/rigging.hpp" #include "physics/Hitbox.hpp" diff --git a/src/logic/scripting/lua/libs/libentity.hpp b/src/logic/scripting/lua/libs/libentity.hpp index b7708f02..5400b449 100644 --- a/src/logic/scripting/lua/libs/libentity.hpp +++ b/src/logic/scripting/lua/libs/libentity.hpp @@ -4,6 +4,7 @@ #include "frontend/hud.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "world/Level.hpp" #include "logic/LevelController.hpp" #include "api_lua.hpp" diff --git a/src/logic/scripting/lua/libs/libplayer.cpp b/src/logic/scripting/lua/libs/libplayer.cpp index d4e55b77..d850231d 100644 --- a/src/logic/scripting/lua/libs/libplayer.cpp +++ b/src/logic/scripting/lua/libs/libplayer.cpp @@ -5,6 +5,7 @@ #include "objects/Entities.hpp" #include "objects/Player.hpp" #include "objects/Players.hpp" +#include "objects/Entity.hpp" #include "physics/Hitbox.hpp" #include "window/Camera.hpp" #include "world/Level.hpp" diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 56c5a17e..87044201 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -21,6 +21,7 @@ #include "maths/Heightmap.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" +#include "objects/Entity.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" #include "util/timeutil.hpp" diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index cdf979e0..5088a70d 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -17,6 +17,7 @@ #include "maths/FrustumCulling.hpp" #include "maths/rays.hpp" #include "EntityDef.hpp" +#include "Entity.hpp" #include "rigging.hpp" #include "physics/Hitbox.hpp" #include "physics/PhysicsSolver.hpp" @@ -24,103 +25,16 @@ static debug::Logger logger("entities"); -static inline std::string COMP_TRANSFORM = "transform"; -static inline std::string COMP_RIGIDBODY = "rigidbody"; -static inline std::string COMP_SKELETON = "skeleton"; -static inline std::string SAVED_DATA_VARNAME = "SAVED_DATA"; - -void Transform::refresh() { - combined = glm::mat4(1.0f); - combined = glm::translate(combined, pos); - combined = combined * glm::mat4(rot); - combined = glm::scale(combined, size); - displayPos = pos; - displaySize = size; - dirty = false; -} - -void Entity::setInterpolatedPosition(const glm::vec3& position) { - getSkeleton().interpolation.refresh(position); -} - -glm::vec3 Entity::getInterpolatedPosition() const { - const auto& skeleton = getSkeleton(); - if (skeleton.interpolation.isEnabled()) { - return skeleton.interpolation.getCurrent(); - } - return getTransform().pos; -} - -void Entity::destroy() { - if (isValid()) { - entities.despawn(id); - } -} - -rigging::Skeleton& Entity::getSkeleton() const { - return registry.get(entity); -} - -void Entity::setRig(const rigging::SkeletonConfig* rigConfig) { - auto& skeleton = registry.get(entity); - skeleton.config = rigConfig; - skeleton.pose.matrices.resize( - rigConfig->getBones().size(), glm::mat4(1.0f) - ); - skeleton.calculated.matrices.resize( - rigConfig->getBones().size(), glm::mat4(1.0f) - ); -} - Entities::Entities(Level& level) : level(level), sensorsTickClock(20, 3), updateTickClock(20, 3) { } -template -static sensorcallback create_sensor_callback(Entities* entities) { - return [=](auto entityid, auto index, auto otherid) { - if (auto entity = entities->get(entityid)) { - if (entity->isValid()) { - callback(*entity, index, otherid); - } - } - }; -} - -static void initialize_body( - const EntityDef& def, Rigidbody& body, entityid_t id, Entities* entities -) { - body.sensors.resize(def.radialSensors.size() + def.boxSensors.size()); - for (auto& [i, box] : def.boxSensors) { - SensorParams params {}; - params.aabb = box; - body.sensors[i] = Sensor { - true, - SensorType::AABB, - i, - id, - params, - params, - {}, - {}, - create_sensor_callback(entities), - create_sensor_callback(entities)}; - } - for (auto& [i, radius] : def.radialSensors) { - SensorParams params {}; - params.radial = glm::vec4(radius); - body.sensors[i] = Sensor { - true, - SensorType::RADIUS, - i, - id, - params, - params, - {}, - {}, - create_sensor_callback(entities), - create_sensor_callback(entities)}; +std::optional Entities::get(entityid_t id) { + const auto& found = entities.find(id); + if (found != entities.end() && registry.valid(found->second)) { + return Entity(*this, id, registry, found->second); } + return std::nullopt; } entityid_t Entities::spawn( @@ -168,7 +82,7 @@ entityid_t Entities::spawn( Hitbox {def.bodyType, position, def.hitbox * 0.5f}, std::vector {} ); - initialize_body(def, body, id, this); + body.initialize(def, id, *this); auto& scripting = registry.emplace(entity); registry.emplace(entity, skeleton->instance()); @@ -232,8 +146,8 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { if (skeletonName != skeleton.config->getName()) { skeleton.config = level.content.getSkeleton(skeletonName); } - if (auto found = map.at(COMP_SKELETON)) { - auto& skeletonmap = *found; + if (auto foundSkeleton = map.at(COMP_SKELETON)) { + auto& skeletonmap = *foundSkeleton; if (auto found = skeletonmap.at("textures")) { auto& texturesmap = *found; for (auto& [slot, _] : texturesmap.asObject()) { @@ -284,7 +198,7 @@ std::optional Entities::rayCast( void Entities::loadEntities(dv::value root) { clean(); - auto& list = root["data"]; + const auto& list = root["data"]; for (auto& map : list) { try { loadEntity(map); @@ -298,74 +212,6 @@ void Entities::onSave(const Entity& entity) { scripting::on_entity_save(entity); } -dv::value Entities::serialize(const Entity& entity) { - auto root = dv::object(); - auto& eid = entity.getID(); - auto& def = eid.def; - root["def"] = def.name; - root["uid"] = eid.uid; - { - auto& transform = entity.getTransform(); - auto& tsfmap = root.object(COMP_TRANSFORM); - tsfmap["pos"] = dv::to_value(transform.pos); - if (transform.size != glm::vec3(1.0f)) { - tsfmap["size"] = dv::to_value(transform.size); - } - if (transform.rot != glm::mat3(1.0f)) { - tsfmap["rot"] = dv::to_value(transform.rot); - } - } - { - auto& rigidbody = entity.getRigidbody(); - auto& hitbox = rigidbody.hitbox; - auto& bodymap = root.object(COMP_RIGIDBODY); - if (!rigidbody.enabled) { - bodymap["enabled"] = false; - } - if (def.save.body.velocity) { - bodymap["vel"] = dv::to_value(rigidbody.hitbox.velocity); - } - if (def.save.body.settings) { - bodymap["damping"] = rigidbody.hitbox.linearDamping; - if (hitbox.type != def.bodyType) { - bodymap["type"] = BodyTypeMeta.getNameString(hitbox.type); - } - if (hitbox.crouching) { - bodymap["crouch"] = hitbox.crouching; - } - } - } - auto& skeleton = entity.getSkeleton(); - if (skeleton.config->getName() != def.skeletonName) { - root["skeleton"] = skeleton.config->getName(); - } - if (def.save.skeleton.pose || def.save.skeleton.textures) { - auto& skeletonmap = root.object(COMP_SKELETON); - if (def.save.skeleton.textures) { - auto& map = skeletonmap.object("textures"); - for (auto& [slot, texture] : skeleton.textures) { - map[slot] = texture; - } - } - if (def.save.skeleton.pose) { - auto& list = skeletonmap.list("pose"); - for (auto& mat : skeleton.pose.matrices) { - list.add(dv::to_value(mat)); - } - } - } - auto& scripts = entity.getScripting(); - if (!scripts.components.empty()) { - auto& compsMap = root.object("comps"); - for (auto& comp : scripts.components) { - auto data = - scripting::get_component_value(comp->env, SAVED_DATA_VARNAME); - compsMap[comp->name] = data; - } - } - return root; -} - dv::value Entities::serialize(const std::vector& entities) { auto list = dv::list(); for (auto& entity : entities) { @@ -375,7 +221,7 @@ dv::value Entities::serialize(const std::vector& entities) { } level.entities->onSave(entity); if (!eid.destroyFlag) { - list.add(level.entities->serialize(entity)); + list.add(entity.serialize()); } } return list; diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 5bd8f955..99da0db3 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -5,108 +5,21 @@ #include #include -#include "data/dv.hpp" #include "physics/Hitbox.hpp" +#include "Transform.hpp" +#include "Rigidbody.hpp" +#include "ScriptComponents.hpp" #include "typedefs.hpp" #include "util/Clock.hpp" -#define GLM_ENABLE_EXPERIMENTAL -#include -#include -#include -struct EntityFuncsSet { - bool init; - bool on_despawn; - bool on_grounded; - bool on_fall; - bool on_sensor_enter; - bool on_sensor_exit; - bool on_save; - bool on_aim_on; - bool on_aim_off; - bool on_attacked; - bool on_used; -}; +#include +#include struct EntityDef; -struct EntityId { - entityid_t uid; - const EntityDef& def; - bool destroyFlag = false; - int64_t player = -1; -}; - -struct Transform { - static inline constexpr float EPSILON = 0.0000001f; - glm::vec3 pos; - glm::vec3 size; - glm::mat3 rot; - glm::mat4 combined; - bool dirty = true; - - glm::vec3 displayPos; - glm::vec3 displaySize; - - void refresh(); - - inline void setRot(glm::mat3 m) { - rot = m; - dirty = true; - } - - inline void setSize(glm::vec3 v) { - if (glm::distance2(displaySize, v) >= EPSILON) { - dirty = true; - } - size = v; - } - - inline void setPos(glm::vec3 v) { - if (glm::distance2(displayPos, v) >= EPSILON) { - dirty = true; - } - pos = v; - } -}; - -struct Rigidbody { - bool enabled = true; - Hitbox hitbox; - std::vector sensors; -}; - -struct UserComponent { - std::string name; - EntityFuncsSet funcsset; - scriptenv env; - dv::value params; - - UserComponent( - const std::string& name, - EntityFuncsSet funcsset, - scriptenv env, - dv::value params - ) - : name(name), - funcsset(funcsset), - env(std::move(env)), - params(std::move(params)) { - } -}; - -struct ScriptComponents { - std::vector> components; - - ScriptComponents() = default; - - ScriptComponents(ScriptComponents&& other) - : components(std::move(other.components)) { - } -}; - class Level; class Assets; +class Entity; class LineBatch; class ModelBatch; class Frustum; @@ -118,72 +31,6 @@ namespace rigging { class SkeletonConfig; } -class Entity { - Entities& entities; - entityid_t id; - entt::registry& registry; - const entt::entity entity; -public: - Entity( - Entities& entities, - entityid_t id, - entt::registry& registry, - const entt::entity entity - ) - : entities(entities), id(id), registry(registry), entity(entity) { - } - - EntityId& getID() const { - return registry.get(entity); - } - - bool isValid() const { - return registry.valid(entity); - } - - const EntityDef& getDef() const { - return registry.get(entity).def; - } - - Transform& getTransform() const { - return registry.get(entity); - } - - Rigidbody& getRigidbody() const { - return registry.get(entity); - } - - ScriptComponents& getScripting() const { - return registry.get(entity); - } - - rigging::Skeleton& getSkeleton() const; - - void setRig(const rigging::SkeletonConfig* rigConfig); - - entityid_t getUID() const { - return registry.get(entity).uid; - } - - entt::entity getHandler() const { - return entity; - } - - int64_t getPlayer() const { - return registry.get(entity).player; - } - - void setPlayer(int64_t id) { - registry.get(entity).player = id; - } - - void setInterpolatedPosition(const glm::vec3& position); - - glm::vec3 getInterpolatedPosition() const; - - void destroy(); -}; - class Entities { entt::registry registry; Level& level; @@ -229,13 +76,7 @@ public: entityid_t uid = 0 ); - std::optional get(entityid_t id) { - const auto& found = entities.find(id); - if (found != entities.end() && registry.valid(found->second)) { - return Entity(*this, id, registry, found->second); - } - return std::nullopt; - } + std::optional get(entityid_t id); /// @brief Entities raycast. No blocks check included, use combined with /// Chunks.rayCast @@ -260,7 +101,6 @@ public: std::vector getAllInRadius(glm::vec3 center, float radius); void despawn(entityid_t id); void despawn(std::vector entities); - dv::value serialize(const Entity& entity); dv::value serialize(const std::vector& entities); void setNextID(entityid_t id) { diff --git a/src/objects/Entity.cpp b/src/objects/Entity.cpp new file mode 100644 index 00000000..24aa1736 --- /dev/null +++ b/src/objects/Entity.cpp @@ -0,0 +1,119 @@ +#include "Entity.hpp" + +#include "Transform.hpp" +#include "Rigidbody.hpp" +#include "ScriptComponents.hpp" +#include "Entities.hpp" +#include "EntityDef.hpp" +#include "rigging.hpp" +#include "logic/scripting/scripting.hpp" + +#include + +static inline std::string SAVED_DATA_VARNAME = "SAVED_DATA"; + +void Entity::setInterpolatedPosition(const glm::vec3& position) { + getSkeleton().interpolation.refresh(position); +} + +glm::vec3 Entity::getInterpolatedPosition() const { + const auto& skeleton = getSkeleton(); + if (skeleton.interpolation.isEnabled()) { + return skeleton.interpolation.getCurrent(); + } + return getTransform().pos; +} + +void Entity::destroy() { + if (isValid()) { + entities.despawn(id); + } +} + +rigging::Skeleton& Entity::getSkeleton() const { + return registry.get(entity); +} + +void Entity::setRig(const rigging::SkeletonConfig* rigConfig) { + auto& skeleton = registry.get(entity); + skeleton.config = rigConfig; + skeleton.pose.matrices.resize( + rigConfig->getBones().size(), glm::mat4(1.0f) + ); + skeleton.calculated.matrices.resize( + rigConfig->getBones().size(), glm::mat4(1.0f) + ); +} + +dv::value Entity::serialize() const { + const auto& eid = getID(); + const auto& def = eid.def; + const auto& transform = getTransform(); + const auto& rigidbody = getRigidbody(); + const auto& skeleton = getSkeleton(); + const auto& scripts = getScripting(); + + auto root = dv::object(); + root["def"] = def.name; + root["uid"] = eid.uid; + + root[COMP_TRANSFORM] = transform.serialize(); + root[COMP_RIGIDBODY] = + rigidbody.serialize(def.save.body.velocity, def.save.body.settings); + + if (skeleton.config->getName() != def.skeletonName) { + root["skeleton"] = skeleton.config->getName(); + } + if (def.save.skeleton.pose || def.save.skeleton.textures) { + root[COMP_SKELETON] = skeleton.serialize( + def.save.skeleton.pose, def.save.skeleton.textures + ); + } + if (!scripts.components.empty()) { + auto& compsMap = root.object("comps"); + for (auto& comp : scripts.components) { + auto data = + scripting::get_component_value(comp->env, SAVED_DATA_VARNAME); + compsMap[comp->name] = data; + } + } + return root; +} + +EntityId& Entity::getID() const { + return registry.get(entity); +} + +bool Entity::isValid() const { + return registry.valid(entity); +} + +Transform& Entity::getTransform() const { + return registry.get(entity); +} + + +ScriptComponents& Entity::getScripting() const { + return registry.get(entity); +} + +const EntityDef& Entity::getDef() const { + return registry.get(entity).def; +} + +Rigidbody& Entity::getRigidbody() const { + return registry.get(entity); +} + +entityid_t Entity::getUID() const { + return registry.get(entity).uid; +} + +int64_t Entity::getPlayer() const { + return registry.get(entity).player; +} + +void Entity::setPlayer(int64_t id) { + registry.get(entity).player = id; +} + diff --git a/src/objects/Entity.hpp b/src/objects/Entity.hpp new file mode 100644 index 00000000..b7e8c273 --- /dev/null +++ b/src/objects/Entity.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "typedefs.hpp" +#include "data/dv_fwd.hpp" + +#include +#include + +class Entities; +struct EntityDef; +struct Transform; +struct Rigidbody; +struct ScriptComponents; + +inline std::string COMP_TRANSFORM = "transform"; +inline std::string COMP_RIGIDBODY = "rigidbody"; +inline std::string COMP_SKELETON = "skeleton"; + +namespace rigging { + struct Skeleton; + class SkeletonConfig; +} + +struct EntityId { + entityid_t uid; + const EntityDef& def; + bool destroyFlag = false; + int64_t player = -1; +}; + +class Entity { + Entities& entities; + entityid_t id; + entt::registry& registry; + const entt::entity entity; +public: + Entity( + Entities& entities, + entityid_t id, + entt::registry& registry, + const entt::entity entity + ) + : entities(entities), id(id), registry(registry), entity(entity) { + } + + dv::value serialize() const; + + EntityId& getID() const; + + bool isValid() const; + + const EntityDef& getDef() const; + + Transform& getTransform() const; + + Rigidbody& getRigidbody() const; + + ScriptComponents& getScripting() const; + + rigging::Skeleton& getSkeleton() const; + + void setRig(const rigging::SkeletonConfig* rigConfig); + + entityid_t getUID() const; + + int64_t getPlayer() const; + + void setPlayer(int64_t id); + + void setInterpolatedPosition(const glm::vec3& position); + + glm::vec3 getInterpolatedPosition() const; + + entt::entity getHandler() const { + return entity; + } + + void destroy(); +}; diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index 21497244..8f2b0d76 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -9,6 +9,7 @@ #include "content/ContentReport.hpp" #include "items/Inventory.hpp" #include "Entities.hpp" +#include "Entity.hpp" #include "rigging.hpp" #include "physics/Hitbox.hpp" #include "physics/PhysicsSolver.hpp" @@ -137,7 +138,7 @@ void Player::updateInput(PlayerInput& input, float delta) { } if (glm::length(dir) > 0.0f) { dir = glm::normalize(dir); - hitbox->velocity += dir * speed * delta * 9.0f; + doMove(dir, speed, delta); } if (flight) { if (input.jump) { @@ -146,9 +147,22 @@ void Player::updateInput(PlayerInput& input, float delta) { if (input.shift) { hitbox->velocity.y -= speed * delta * 9; } + } else if (input.jump) { + doJump(); } - if (input.jump && hitbox->grounded) { - hitbox->velocity.y = JUMP_FORCE; +} + +void Player::doMove(const glm::vec3& dir, float speed, float delta) { + if (auto hitbox = getHitbox()) { + hitbox->velocity += dir * speed * delta * 9.0f; + } +} + +void Player::doJump() { + if (auto hitbox = getHitbox()) { + if (hitbox->grounded) { + hitbox->velocity.y = JUMP_FORCE; + } } } diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index c4d847b3..2da8ee1d 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -59,6 +59,9 @@ class Player : public Serializable { entityid_t eid = ENTITY_AUTO; entityid_t selectedEid = 0; + void doMove(const glm::vec3& dir, float speed, float delta); + void doJump(); + glm::vec3 rotation {}; public: util::VecInterpolation<3, float, true> rotationInterpolation {true}; diff --git a/src/objects/Rigidbody.cpp b/src/objects/Rigidbody.cpp new file mode 100644 index 00000000..62f88128 --- /dev/null +++ b/src/objects/Rigidbody.cpp @@ -0,0 +1,74 @@ +#define VC_ENABLE_REFLECTION + +#include "Rigidbody.hpp" + +#include "EntityDef.hpp" +#include "Entities.hpp" +#include "Entity.hpp" +#include "data/dv_util.hpp" +#include "logic/scripting/scripting.hpp" + +dv::value Rigidbody::serialize(bool saveVelocity, bool saveBodySettings) const { + auto bodymap = dv::object(); + if (!enabled) { + bodymap["enabled"] = false; + } + if (saveVelocity) { + bodymap["vel"] = dv::to_value(hitbox.velocity); + } + if (saveBodySettings) { + bodymap["damping"] = hitbox.linearDamping; + bodymap["type"] = BodyTypeMeta.getNameString(hitbox.type); + if (hitbox.crouching) { + bodymap["crouch"] = hitbox.crouching; + } + } + return bodymap; +} + +template +static sensorcallback create_sensor_callback(Entities& entities) { + return [&entities](auto entityid, auto index, auto otherid) { + if (auto entity = entities.get(entityid)) { + if (entity->isValid()) { + callback(*entity, index, otherid); + } + } + }; +} + +void Rigidbody::initialize( + const EntityDef& def, entityid_t id, Entities& entities +) { + sensors.resize(def.radialSensors.size() + def.boxSensors.size()); + for (auto& [i, box] : def.boxSensors) { + SensorParams params {}; + params.aabb = box; + sensors[i] = Sensor { + true, + SensorType::AABB, + i, + id, + params, + params, + {}, + {}, + create_sensor_callback(entities), + create_sensor_callback(entities)}; + } + for (auto& [i, radius] : def.radialSensors) { + SensorParams params {}; + params.radial = glm::vec4(radius); + sensors[i] = Sensor { + true, + SensorType::RADIUS, + i, + id, + params, + params, + {}, + {}, + create_sensor_callback(entities), + create_sensor_callback(entities)}; + } +} diff --git a/src/objects/Rigidbody.hpp b/src/objects/Rigidbody.hpp new file mode 100644 index 00000000..818329d2 --- /dev/null +++ b/src/objects/Rigidbody.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "data/dv_fwd.hpp" +#include "physics/Hitbox.hpp" + +#include +#include + +class Entities; +struct EntityDef; + +struct Rigidbody { + bool enabled = true; + Hitbox hitbox; + std::vector sensors; + + dv::value serialize(bool saveVelocity, bool saveBodySettings) const; + + void initialize( + const EntityDef& def, entityid_t id, Entities& entities + ); +}; diff --git a/src/objects/ScriptComponents.hpp b/src/objects/ScriptComponents.hpp new file mode 100644 index 00000000..2711d482 --- /dev/null +++ b/src/objects/ScriptComponents.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "typedefs.hpp" +#include "data/dv.hpp" + +#include + +struct EntityFuncsSet { + bool init; + bool on_despawn; + bool on_grounded; + bool on_fall; + bool on_sensor_enter; + bool on_sensor_exit; + bool on_save; + bool on_aim_on; + bool on_aim_off; + bool on_attacked; + bool on_used; +}; + +struct UserComponent { + std::string name; + EntityFuncsSet funcsset; + scriptenv env; + dv::value params; + + UserComponent( + const std::string& name, + EntityFuncsSet funcsset, + scriptenv env, + dv::value params + ) + : name(name), + funcsset(funcsset), + env(std::move(env)), + params(std::move(params)) { + } +}; + +struct ScriptComponents { + std::vector> components; + + ScriptComponents() = default; + + ScriptComponents(ScriptComponents&& other) + : components(std::move(other.components)) { + } +}; diff --git a/src/objects/Transform.cpp b/src/objects/Transform.cpp new file mode 100644 index 00000000..9c14353e --- /dev/null +++ b/src/objects/Transform.cpp @@ -0,0 +1,25 @@ +#include "Transform.hpp" + +#include "data/dv_util.hpp" + +void Transform::refresh() { + combined = glm::mat4(1.0f); + combined = glm::translate(combined, pos); + combined = combined * glm::mat4(rot); + combined = glm::scale(combined, size); + displayPos = pos; + displaySize = size; + dirty = false; +} + +dv::value Transform::serialize() const { + auto tsfmap = dv::object(); + tsfmap["pos"] = dv::to_value(pos); + if (size != glm::vec3(1.0f)) { + tsfmap["size"] = dv::to_value(size); + } + if (rot != glm::mat3(1.0f)) { + tsfmap["rot"] = dv::to_value(rot); + } + return tsfmap; +} diff --git a/src/objects/Transform.hpp b/src/objects/Transform.hpp new file mode 100644 index 00000000..0eb73236 --- /dev/null +++ b/src/objects/Transform.hpp @@ -0,0 +1,43 @@ +#pragma once + +#define GLM_ENABLE_EXPERIMENTAL + +#include +#include +#include +#include + +struct Transform { + static inline constexpr float EPSILON = 0.0000001f; + glm::vec3 pos; + glm::vec3 size; + glm::mat3 rot; + glm::mat4 combined; + bool dirty = true; + + glm::vec3 displayPos; + glm::vec3 displaySize; + + dv::value serialize() const; + + void refresh(); + + inline void setRot(glm::mat3 m) { + rot = m; + dirty = true; + } + + inline void setSize(glm::vec3 v) { + if (glm::distance2(displaySize, v) >= EPSILON) { + dirty = true; + } + size = v; + } + + inline void setPos(glm::vec3 v) { + if (glm::distance2(displayPos, v) >= EPSILON) { + dirty = true; + } + pos = v; + } +}; diff --git a/src/objects/rigging.cpp b/src/objects/rigging.cpp index 69ec5ce1..58138a48 100644 --- a/src/objects/rigging.cpp +++ b/src/objects/rigging.cpp @@ -23,7 +23,7 @@ Bone::Bone( std::string name, std::string model, std::vector> bones, - glm::vec3 offset + const glm::vec3& offset ) : index(index), name(std::move(name)), @@ -53,6 +53,23 @@ Skeleton::Skeleton(const SkeletonConfig* config) } } +dv::value Skeleton::serialize(bool saveTextures, bool savePose) const { + auto root = dv::object(); + if (saveTextures) { + auto& map = root.object("textures"); + for (auto& [slot, texture] : textures) { + map[slot] = texture; + } + } + if (savePose) { + auto& list = root.list("pose"); + for (auto& mat : pose.matrices) { + list.add(dv::to_value(mat)); + } + } + return root; +} + static void get_all_nodes(std::vector& nodes, Bone* node) { nodes[node->getIndex()] = node; for (auto& subnode : node->getSubnodes()) { diff --git a/src/objects/rigging.hpp b/src/objects/rigging.hpp index 2c326a4a..0c377c43 100644 --- a/src/objects/rigging.hpp +++ b/src/objects/rigging.hpp @@ -9,6 +9,7 @@ #include #include "typedefs.hpp" +#include "data/dv_fwd.hpp" #include "util/Interpolation.hpp" class Assets; @@ -50,7 +51,7 @@ namespace rigging { std::string name, std::string model, std::vector> bones, - glm::vec3 offset + const glm::vec3& offset ); void setModel(const std::string& name); @@ -89,6 +90,8 @@ namespace rigging { util::VecInterpolation<3, float> interpolation {false}; Skeleton(const SkeletonConfig* config); + + dv::value serialize(bool saveTextures, bool savePose) const; }; class SkeletonConfig { diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index fbca2eec..7451a563 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -10,6 +10,7 @@ #include "lighting/Lightmap.hpp" #include "maths/voxmaths.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "voxels/blocks_agent.hpp" #include "typedefs.hpp" #include "world/LevelEvents.hpp" diff --git a/src/world/Level.cpp b/src/world/Level.cpp index c33c7e0f..2a1583bd 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -5,6 +5,7 @@ #include "items/Inventories.hpp" #include "items/Inventory.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "objects/Player.hpp" #include "objects/Players.hpp" #include "physics/Hitbox.hpp" From 61da6b44a135bbc86d13e1e6a49ccbbb3e9191bc Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 9 Aug 2025 21:30:55 +0300 Subject: [PATCH 059/177] fix windows build --- src/objects/Entity.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/objects/Entity.hpp b/src/objects/Entity.hpp index b7e8c273..4a306c86 100644 --- a/src/objects/Entity.hpp +++ b/src/objects/Entity.hpp @@ -3,6 +3,7 @@ #include "typedefs.hpp" #include "data/dv_fwd.hpp" +#include #include #include From 5583734bc2773a1a08ad0a6a01f1be29f420e164 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 9 Aug 2025 22:43:06 +0300 Subject: [PATCH 060/177] refactor --- src/data/dv_util.hpp | 6 +- src/logic/scripting/scripting.cpp | 275 -------------------- src/logic/scripting/scripting_entities.cpp | 288 +++++++++++++++++++++ src/objects/Entities.cpp | 33 +-- src/objects/Rigidbody.cpp | 9 + src/objects/Rigidbody.hpp | 1 + src/objects/Transform.cpp | 6 + src/objects/Transform.hpp | 3 +- src/objects/rigging.cpp | 16 ++ src/objects/rigging.hpp | 1 + 10 files changed, 331 insertions(+), 307 deletions(-) create mode 100644 src/logic/scripting/scripting_entities.cpp diff --git a/src/data/dv_util.hpp b/src/data/dv_util.hpp index 3f09ffde..ffec61dd 100644 --- a/src/data/dv_util.hpp +++ b/src/data/dv_util.hpp @@ -46,12 +46,12 @@ namespace dv { if (!map.has(key)) { return; } - auto& list = map[key]; + const auto& srcList = map[key]; for (size_t i = 0; i < n; i++) { if constexpr (std::is_floating_point()) { - vec[i] = list[i].asNumber(); + vec[i] = srcList[i].asNumber(); } else { - vec[i] = list[i].asInteger(); + vec[i] = srcList[i].asInteger(); } } } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 87044201..6a2ca0a5 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -19,9 +19,6 @@ #include "lua/lua_engine.hpp" #include "lua/lua_custom_types.hpp" #include "maths/Heightmap.hpp" -#include "objects/Entities.hpp" -#include "objects/EntityDef.hpp" -#include "objects/Entity.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" #include "util/timeutil.hpp" @@ -34,8 +31,6 @@ using namespace scripting; static debug::Logger logger("scripting"); -static inline const std::string STDCOMP = "stdcomp"; - std::ostream* scripting::output_stream = &std::cout; std::ostream* scripting::error_stream = &std::cerr; Engine* scripting::engine = nullptr; @@ -198,36 +193,6 @@ std::unique_ptr scripting::start_coroutine( }); } -[[nodiscard]] static scriptenv create_component_environment( - const scriptenv& parent, int entityIdx, const std::string& name -) { - auto L = lua::get_main_state(); - int id = lua::create_environment(L, *parent); - - lua::pushvalue(L, entityIdx); - - lua::pushenv(L, id); - - lua::pushvalue(L, -1); - lua::setfield(L, "this"); - - lua::pushvalue(L, -2); - lua::setfield(L, "entity"); - - lua::pop(L); - if (lua::getfield(L, "components")) { - lua::pushenv(L, id); - lua::setfield(L, name); - lua::pop(L); - } - lua::pop(L); - - return std::shared_ptr(new int(id), [=](int* id) { //-V508 - lua::remove_environment(L, *id); - delete id; - }); -} - void scripting::process_post_runnables() { auto L = lua::get_main_state(); if (lua::getglobal(L, "__process_post_runnables")) { @@ -559,246 +524,6 @@ bool scripting::on_item_break_block( ); } -dv::value scripting::get_component_value( - const scriptenv& env, const std::string& name -) { - auto L = lua::get_main_state(); - lua::pushenv(L, *env); - if (lua::getfield(L, name)) { - return lua::tovalue(L, -1); - } - return nullptr; -} - -void scripting::on_entity_spawn( - const EntityDef&, - entityid_t eid, - const std::vector>& components, - const dv::value& args, - const dv::value& saved -) { - auto L = lua::get_main_state(); - lua::stackguard guard(L); - lua::requireglobal(L, STDCOMP); - if (lua::getfield(L, "new_Entity")) { - lua::pushinteger(L, eid); - lua::call(L, 1); - } - if (components.size() > 1) { - for (size_t i = 0; i < components.size() - 1; i++) { - lua::pushvalue(L, -1); - } - } - for (auto& component : components) { - auto compenv = create_component_environment( - get_root_environment(), -1, component->name - ); - lua::get_from(L, lua::CHUNKS_TABLE, component->name, true); - lua::pushenv(L, *compenv); - - if (args != nullptr) { - std::string compfieldname = component->name; - util::replaceAll(compfieldname, ":", "__"); - if (args.has(compfieldname)) { - lua::pushvalue(L, args[compfieldname]); - } else { - lua::createtable(L, 0, 0); - } - } else if (component->params != nullptr) { - lua::pushvalue(L, component->params); - } else { - lua::createtable(L, 0, 0); - } - lua::setfield(L, "ARGS"); - - if (saved == nullptr) { - lua::createtable(L, 0, 0); - } else { - if (saved.has(component->name)) { - lua::pushvalue(L, saved[component->name]); - } else { - lua::createtable(L, 0, 0); - } - } - lua::setfield(L, "SAVED_DATA"); - - 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); - - component->env = compenv; - } -} - -static void process_entity_callback( - const scriptenv& env, - const std::string& name, - std::function args -) { - auto L = lua::get_main_state(); - lua::pushenv(L, *env); - if (lua::hasfield(L, "__disabled")) { - lua::pop(L); - return; - } - if (lua::getfield(L, name)) { - if (args) { - lua::call_nothrow(L, args(L), 0); - } else { - lua::call_nothrow(L, 0, 0); - } - } - lua::pop(L); -} - -static void process_entity_callback( - const Entity& entity, - const std::string& name, - bool EntityFuncsSet::*flag, - std::function args -) { - const auto& script = entity.getScripting(); - for (auto& component : script.components) { - if (component->funcsset.*flag) { - process_entity_callback(component->env, name, args); - } - } -} - -void scripting::on_entity_despawn(const Entity& entity) { - process_entity_callback( - entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr - ); - auto L = lua::get_main_state(); - lua::get_from(L, "stdcomp", "remove_Entity", true); - lua::pushinteger(L, entity.getUID()); - lua::call(L, 1, 0); -} - -void scripting::on_entity_grounded(const Entity& entity, float force) { - process_entity_callback( - entity, - "on_grounded", - &EntityFuncsSet::on_grounded, - [force](auto L) { return lua::pushnumber(L, force); } - ); -} - -void scripting::on_entity_fall(const Entity& entity) { - process_entity_callback( - entity, "on_fall", &EntityFuncsSet::on_fall, nullptr - ); -} - -void scripting::on_entity_save(const Entity& entity) { - process_entity_callback( - entity, "on_save", &EntityFuncsSet::on_save, nullptr - ); -} - -void scripting::on_sensor_enter( - const Entity& entity, size_t index, entityid_t oid -) { - process_entity_callback( - entity, - "on_sensor_enter", - &EntityFuncsSet::on_sensor_enter, - [index, oid](auto L) { - lua::pushinteger(L, index); - lua::pushinteger(L, oid); - return 2; - } - ); -} - -void scripting::on_sensor_exit( - const Entity& entity, size_t index, entityid_t oid -) { - process_entity_callback( - entity, - "on_sensor_exit", - &EntityFuncsSet::on_sensor_exit, - [index, oid](auto L) { - lua::pushinteger(L, index); - lua::pushinteger(L, oid); - return 2; - } - ); -} - -void scripting::on_aim_on(const Entity& entity, Player* player) { - process_entity_callback( - entity, - "on_aim_on", - &EntityFuncsSet::on_aim_on, - [player](auto L) { return lua::pushinteger(L, player->getId()); } - ); -} - -void scripting::on_aim_off(const Entity& entity, Player* player) { - process_entity_callback( - entity, - "on_aim_off", - &EntityFuncsSet::on_aim_off, - [player](auto L) { return lua::pushinteger(L, player->getId()); } - ); -} - -void scripting::on_attacked( - const Entity& entity, Player* player, entityid_t attacker -) { - process_entity_callback( - entity, - "on_attacked", - &EntityFuncsSet::on_attacked, - [player, attacker](auto L) { - lua::pushinteger(L, attacker); - lua::pushinteger(L, player->getId()); - return 2; - } - ); -} - -void scripting::on_entity_used(const Entity& entity, Player* player) { - process_entity_callback( - entity, - "on_used", - &EntityFuncsSet::on_used, - [player](auto L) { return lua::pushinteger(L, player->getId()); } - ); -} - -void scripting::on_entities_update(int tps, int parts, int part) { - auto L = lua::get_main_state(); - lua::get_from(L, STDCOMP, "update", true); - lua::pushinteger(L, tps); - lua::pushinteger(L, parts); - lua::pushinteger(L, part); - lua::call_nothrow(L, 3, 0); - lua::pop(L); -} - -void scripting::on_entities_render(float delta) { - auto L = lua::get_main_state(); - lua::get_from(L, STDCOMP, "render", true); - lua::pushnumber(L, delta); - lua::call_nothrow(L, 1, 0); - lua::pop(L); -} - void scripting::on_ui_open( UiDocument* layout, std::vector args ) { diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp new file mode 100644 index 00000000..4deabd4b --- /dev/null +++ b/src/logic/scripting/scripting_entities.cpp @@ -0,0 +1,288 @@ +#include "scripting.hpp" + +#include "lua/lua_engine.hpp" +#include "objects/Entities.hpp" +#include "objects/EntityDef.hpp" +#include "objects/Entity.hpp" +#include "objects/Player.hpp" +#include "util/stringutil.hpp" + +using namespace scripting; + +static inline const std::string STDCOMP = "stdcomp"; + +[[nodiscard]] static scriptenv create_component_environment( + const scriptenv& parent, int entityIdx, const std::string& name +) { + auto L = lua::get_main_state(); + int id = lua::create_environment(L, *parent); + + lua::pushvalue(L, entityIdx); + + lua::pushenv(L, id); + + lua::pushvalue(L, -1); + lua::setfield(L, "this"); + + lua::pushvalue(L, -2); + lua::setfield(L, "entity"); + + lua::pop(L); + if (lua::getfield(L, "components")) { + lua::pushenv(L, id); + lua::setfield(L, name); + lua::pop(L); + } + lua::pop(L); + + return std::shared_ptr(new int(id), [=](int* id) { //-V508 + lua::remove_environment(L, *id); + delete id; + }); +} + +dv::value scripting::get_component_value( + const scriptenv& env, const std::string& name +) { + auto L = lua::get_main_state(); + lua::pushenv(L, *env); + if (lua::getfield(L, name)) { + return lua::tovalue(L, -1); + } + return nullptr; +} + +static void create_component( + lua::State* L, + int entityIdx, + UserComponent& component, + const dv::value& args, + const dv::value& saved +) { + lua::pushvalue(L, entityIdx); + auto compenv = create_component_environment( + get_root_environment(), -1, component.name + ); + lua::get_from(L, lua::CHUNKS_TABLE, component.name, true); + lua::pushenv(L, *compenv); + + if (args != nullptr) { + std::string compfieldname = component.name; + util::replaceAll(compfieldname, ":", "__"); + if (args.has(compfieldname)) { + lua::pushvalue(L, args[compfieldname]); + } else { + lua::createtable(L, 0, 0); + } + } else if (component.params != nullptr) { + lua::pushvalue(L, component.params); + } else { + lua::createtable(L, 0, 0); + } + lua::setfield(L, "ARGS"); + + if (saved == nullptr) { + lua::createtable(L, 0, 0); + } else { + if (saved.has(component.name)) { + lua::pushvalue(L, saved[component.name]); + } else { + lua::createtable(L, 0, 0); + } + } + lua::setfield(L, "SAVED_DATA"); + + 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); + + component.env = compenv; +} + +void scripting::on_entity_spawn( + const EntityDef&, + entityid_t eid, + const std::vector>& components, + const dv::value& args, + const dv::value& saved +) { + auto L = lua::get_main_state(); + lua::stackguard guard(L); + lua::requireglobal(L, STDCOMP); + if (lua::getfield(L, "new_Entity")) { + lua::pushinteger(L, eid); + lua::call(L, 1); + } + for (auto& component : components) { + create_component(L, -1, *component, args, saved); + } +} + +static void process_entity_callback( + const scriptenv& env, + const std::string& name, + std::function args +) { + auto L = lua::get_main_state(); + lua::pushenv(L, *env); + if (lua::hasfield(L, "__disabled")) { + lua::pop(L); + return; + } + if (lua::getfield(L, name)) { + if (args) { + lua::call_nothrow(L, args(L), 0); + } else { + lua::call_nothrow(L, 0, 0); + } + } + lua::pop(L); +} + +static void process_entity_callback( + const Entity& entity, + const std::string& name, + bool EntityFuncsSet::*flag, + std::function args +) { + const auto& script = entity.getScripting(); + for (auto& component : script.components) { + if (component->funcsset.*flag) { + process_entity_callback(component->env, name, args); + } + } +} + +void scripting::on_entity_despawn(const Entity& entity) { + process_entity_callback( + entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr + ); + auto L = lua::get_main_state(); + lua::get_from(L, "stdcomp", "remove_Entity", true); + lua::pushinteger(L, entity.getUID()); + lua::call(L, 1, 0); +} + +void scripting::on_entity_grounded(const Entity& entity, float force) { + process_entity_callback( + entity, + "on_grounded", + &EntityFuncsSet::on_grounded, + [force](auto L) { return lua::pushnumber(L, force); } + ); +} + +void scripting::on_entity_fall(const Entity& entity) { + process_entity_callback( + entity, "on_fall", &EntityFuncsSet::on_fall, nullptr + ); +} + +void scripting::on_entity_save(const Entity& entity) { + process_entity_callback( + entity, "on_save", &EntityFuncsSet::on_save, nullptr + ); +} + +void scripting::on_sensor_enter( + const Entity& entity, size_t index, entityid_t oid +) { + process_entity_callback( + entity, + "on_sensor_enter", + &EntityFuncsSet::on_sensor_enter, + [index, oid](auto L) { + lua::pushinteger(L, index); + lua::pushinteger(L, oid); + return 2; + } + ); +} + +void scripting::on_sensor_exit( + const Entity& entity, size_t index, entityid_t oid +) { + process_entity_callback( + entity, + "on_sensor_exit", + &EntityFuncsSet::on_sensor_exit, + [index, oid](auto L) { + lua::pushinteger(L, index); + lua::pushinteger(L, oid); + return 2; + } + ); +} + +void scripting::on_aim_on(const Entity& entity, Player* player) { + process_entity_callback( + entity, + "on_aim_on", + &EntityFuncsSet::on_aim_on, + [player](auto L) { return lua::pushinteger(L, player->getId()); } + ); +} + +void scripting::on_aim_off(const Entity& entity, Player* player) { + process_entity_callback( + entity, + "on_aim_off", + &EntityFuncsSet::on_aim_off, + [player](auto L) { return lua::pushinteger(L, player->getId()); } + ); +} + +void scripting::on_attacked( + const Entity& entity, Player* player, entityid_t attacker +) { + process_entity_callback( + entity, + "on_attacked", + &EntityFuncsSet::on_attacked, + [player, attacker](auto L) { + lua::pushinteger(L, attacker); + lua::pushinteger(L, player->getId()); + return 2; + } + ); +} + +void scripting::on_entity_used(const Entity& entity, Player* player) { + process_entity_callback( + entity, + "on_used", + &EntityFuncsSet::on_used, + [player](auto L) { return lua::pushinteger(L, player->getId()); } + ); +} + +void scripting::on_entities_update(int tps, int parts, int part) { + auto L = lua::get_main_state(); + lua::get_from(L, STDCOMP, "update", true); + lua::pushinteger(L, tps); + lua::pushinteger(L, parts); + lua::pushinteger(L, part); + lua::call_nothrow(L, 3, 0); + lua::pop(L); +} + +void scripting::on_entities_render(float delta) { + auto L = lua::get_main_state(); + lua::get_from(L, STDCOMP, "render", true); + lua::pushnumber(L, delta); + lua::call_nothrow(L, 1, 0); + lua::pop(L); +} diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 5088a70d..8f3d3cb1 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -19,7 +19,6 @@ #include "EntityDef.hpp" #include "Entity.hpp" #include "rigging.hpp" -#include "physics/Hitbox.hpp" #include "physics/PhysicsSolver.hpp" #include "world/Level.hpp" @@ -100,7 +99,8 @@ entityid_t Entities::spawn( } body.hitbox.position = tsf.pos; scripting::on_entity_spawn( - def, id, scripting.components, args, componentsMap); + def, id, scripting.components, args, componentsMap + ); return id; } @@ -127,19 +127,10 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { auto& skeleton = entity.getSkeleton(); if (map.has(COMP_RIGIDBODY)) { - auto& bodymap = map[COMP_RIGIDBODY]; - dv::get_vec(bodymap, "vel", body.hitbox.velocity); - std::string bodyTypeName; - map.at("type").get(bodyTypeName); - BodyTypeMeta.getItem(bodyTypeName, body.hitbox.type); - bodymap["crouch"].asBoolean(body.hitbox.crouching); - bodymap["damping"].asNumber(body.hitbox.linearDamping); + body.deserialize(map[COMP_RIGIDBODY]); } if (map.has(COMP_TRANSFORM)) { - auto& tsfmap = map[COMP_TRANSFORM]; - dv::get_vec(tsfmap, "pos", transform.pos); - dv::get_vec(tsfmap, "size", transform.size); - dv::get_mat(tsfmap, "rot", transform.rot); + transform.deserialize(map[COMP_TRANSFORM]); } std::string skeletonName = skeleton.config->getName(); map.at("skeleton").get(skeletonName); @@ -147,21 +138,7 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { skeleton.config = level.content.getSkeleton(skeletonName); } if (auto foundSkeleton = map.at(COMP_SKELETON)) { - auto& skeletonmap = *foundSkeleton; - if (auto found = skeletonmap.at("textures")) { - auto& texturesmap = *found; - for (auto& [slot, _] : texturesmap.asObject()) { - texturesmap.at(slot).get(skeleton.textures[slot]); - } - } - if (auto found = skeletonmap.at("pose")) { - auto& posearr = *found; - for (size_t i = 0; - i < std::min(skeleton.pose.matrices.size(), posearr.size()); - i++) { - dv::get_mat(posearr[i], skeleton.pose.matrices[i]); - } - } + skeleton.deserialize(*foundSkeleton); } } diff --git a/src/objects/Rigidbody.cpp b/src/objects/Rigidbody.cpp index 62f88128..9b1f78d2 100644 --- a/src/objects/Rigidbody.cpp +++ b/src/objects/Rigidbody.cpp @@ -26,6 +26,15 @@ dv::value Rigidbody::serialize(bool saveVelocity, bool saveBodySettings) const { return bodymap; } +void Rigidbody::deserialize(const dv::value& root) { + dv::get_vec(root, "vel", hitbox.velocity); + std::string bodyTypeName; + root.at("type").get(bodyTypeName); + BodyTypeMeta.getItem(bodyTypeName, hitbox.type); + root["crouch"].asBoolean(hitbox.crouching); + root["damping"].asNumber(hitbox.linearDamping); +} + template static sensorcallback create_sensor_callback(Entities& entities) { return [&entities](auto entityid, auto index, auto otherid) { diff --git a/src/objects/Rigidbody.hpp b/src/objects/Rigidbody.hpp index 818329d2..91790d8a 100644 --- a/src/objects/Rigidbody.hpp +++ b/src/objects/Rigidbody.hpp @@ -15,6 +15,7 @@ struct Rigidbody { std::vector sensors; dv::value serialize(bool saveVelocity, bool saveBodySettings) const; + void deserialize(const dv::value& root); void initialize( const EntityDef& def, entityid_t id, Entities& entities diff --git a/src/objects/Transform.cpp b/src/objects/Transform.cpp index 9c14353e..87ed4a73 100644 --- a/src/objects/Transform.cpp +++ b/src/objects/Transform.cpp @@ -23,3 +23,9 @@ dv::value Transform::serialize() const { } return tsfmap; } + +void Transform::deserialize(const dv::value& root) { + dv::get_vec(root, "pos", pos); + dv::get_vec(root, "size", size); + dv::get_mat(root, "rot", rot); +} diff --git a/src/objects/Transform.hpp b/src/objects/Transform.hpp index 0eb73236..183890dd 100644 --- a/src/objects/Transform.hpp +++ b/src/objects/Transform.hpp @@ -8,7 +8,7 @@ #include struct Transform { - static inline constexpr float EPSILON = 0.0000001f; + static inline constexpr float EPSILON = 1e-7f; glm::vec3 pos; glm::vec3 size; glm::mat3 rot; @@ -19,6 +19,7 @@ struct Transform { glm::vec3 displaySize; dv::value serialize() const; + void deserialize(const dv::value& root); void refresh(); diff --git a/src/objects/rigging.cpp b/src/objects/rigging.cpp index 58138a48..1da11f8c 100644 --- a/src/objects/rigging.cpp +++ b/src/objects/rigging.cpp @@ -70,6 +70,22 @@ dv::value Skeleton::serialize(bool saveTextures, bool savePose) const { return root; } +void Skeleton::deserialize(const dv::value& root) { + if (auto found = root.at("textures")) { + auto& texturesmap = *found; + for (auto& [slot, _] : texturesmap.asObject()) { + texturesmap.at(slot).get(textures[slot]); + } + } + if (auto found = root.at("pose")) { + auto& posearr = *found; + auto& matrices = pose.matrices; + for (size_t i = 0; i < std::min(matrices.size(), posearr.size()); i++) { + dv::get_mat(posearr[i], pose.matrices[i]); + } + } +} + static void get_all_nodes(std::vector& nodes, Bone* node) { nodes[node->getIndex()] = node; for (auto& subnode : node->getSubnodes()) { diff --git a/src/objects/rigging.hpp b/src/objects/rigging.hpp index 0c377c43..76ee764b 100644 --- a/src/objects/rigging.hpp +++ b/src/objects/rigging.hpp @@ -92,6 +92,7 @@ namespace rigging { Skeleton(const SkeletonConfig* config); dv::value serialize(bool saveTextures, bool savePose) const; + void deserialize(const dv::value& root); }; class SkeletonConfig { From fc573b4c6ee838cb4ca13278f5874597642c91c5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 9 Aug 2025 23:40:09 +0300 Subject: [PATCH 061/177] erase and forget --- src/graphics/render/WorldRenderer.cpp | 4 +++- src/objects/Entities.cpp | 19 ++++++++++--------- src/objects/Entities.hpp | 3 ++- src/objects/Player.cpp | 4 ---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index eef4c41e..099dd989 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -205,7 +205,9 @@ void WorldRenderer::renderOpaque( *modelBatch, culling ? frustumCulling.get() : nullptr, delta, - pause + pause, + player.currentCamera.get() == player.fpCamera.get() ? player.getEntity() + : 0 ); modelBatch->render(); particles->render(camera, delta * !pause); diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 8f3d3cb1..8dcdecd4 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -328,13 +328,10 @@ static void debug_render_skeleton( size_t pindex = bone->getIndex(); for (auto& sub : bone->getSubnodes()) { size_t sindex = sub->getIndex(); + const auto& matrices = skeleton.calculated.matrices; batch.line( - glm::vec3( - skeleton.calculated.matrices[pindex] * glm::vec4(0, 0, 0, 1) - ), - glm::vec3( - skeleton.calculated.matrices[sindex] * glm::vec4(0, 0, 0, 1) - ), + glm::vec3(matrices[pindex] * glm::vec4(0, 0, 0, 1)), + glm::vec3(matrices[sindex] * glm::vec4(0, 0, 0, 1)), glm::vec4(0, 0.5f, 0, 1) ); debug_render_skeleton(batch, sub.get(), skeleton); @@ -391,10 +388,14 @@ void Entities::render( ModelBatch& batch, const Frustum* frustum, float delta, - bool pause + bool pause, + entityid_t fpsEntity ) { - auto view = registry.view(); - for (auto [entity, transform, skeleton] : view.each()) { + auto view = registry.view(); + for (auto [entity, eid, transform, skeleton] : view.each()) { + if (eid.uid == fpsEntity) { + continue; + } if (transform.dirty) { transform.refresh(); } diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 99da0db3..03fcec8c 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -65,7 +65,8 @@ public: ModelBatch& batch, const Frustum* frustum, float delta, - bool pause + bool pause, + entityid_t fpsEntity ); entityid_t spawn( diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index 8f2b0d76..befdd0b3 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -186,10 +186,6 @@ void Player::postUpdate() { attemptToFindSpawnpoint(); } } - - // TODO: ERASE & FORGET - auto& skeleton = entity->getSkeleton(); - skeleton.visible = currentCamera != fpCamera; } void Player::teleport(glm::vec3 position) { From e26ce0459b0849b83930cea4c8a07896162be6d5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 10 Aug 2025 17:14:18 +0300 Subject: [PATCH 062/177] add block.model_name --- doc/en/scripting/builtins/libblock.md | 19 +++++++++++++++++-- doc/ru/scripting/builtins/libblock.md | 3 +++ src/logic/scripting/lua/libs/libblock.cpp | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md index 6d657899..8d3b07f8 100644 --- a/doc/en/scripting/builtins/libblock.md +++ b/doc/en/scripting/builtins/libblock.md @@ -119,13 +119,13 @@ block.seek_origin(x: int, y: int, z: int) -> int, int, int Part of a voxel data used for scripting. Size: 8 bit. -```python +```lua block.get_user_bits(x: int, y: int, z: int, offset: int, bits: int) -> int ``` Get specified bits as an unsigned integer. -```python +```lua block.set_user_bits(x: int, y: int, z: int, offset: int, bits: int, value: int) -> int ``` Set specified bits. @@ -151,6 +151,21 @@ The function returns a table with the results or nil if the ray does not hit any The result will use the destination table instead of creating a new one if the optional argument specified. +## Model + +Block model information. + +```lua +-- returns block model type (block/aabb/custom/...) +block.get_model(id: int) -> str + +-- returns block model name +block.model_name(id: int) -> str + +-- returns array of 6 textures assigned to sides of block +block.get_textures(id: int) -> string table +``` + ## Data fields ```lua diff --git a/doc/ru/scripting/builtins/libblock.md b/doc/ru/scripting/builtins/libblock.md index e5bdbb44..67547909 100644 --- a/doc/ru/scripting/builtins/libblock.md +++ b/doc/ru/scripting/builtins/libblock.md @@ -171,6 +171,9 @@ block.get_hitbox(id: int, rotation_index: int) -> {vec3, vec3} -- возвращает тип модели блока (block/aabb/custom/...) block.get_model(id: int) -> str +-- возвращает имя модели блока +block.model_name(id: int) -> str + -- возвращает массив из 6 текстур, назначенных на стороны блока block.get_textures(id: int) -> таблица строк ``` diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index bb527c9b..eb699bc5 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -364,6 +364,19 @@ static int l_get_textures(lua::State* L) { return 0; } + +static int l_model_name(lua::State* L) { + if (auto def = require_block(L)) { + // TODO: variant argument + const auto& modelName = def->defaults.model.name; + if (modelName.empty()) { + return lua::pushlstring(L, def->name + ".model"); + } + return lua::pushlstring(L, modelName); + } + return 0; +} + static int l_get_model(lua::State* L) { if (auto def = require_block(L)) { // TODO: variant argument @@ -713,6 +726,7 @@ const luaL_Reg blocklib[] = { {"get_size", lua::wrap}, {"is_segment", lua::wrap}, {"seek_origin", lua::wrap}, + {"model_name", lua::wrap}, {"get_textures", lua::wrap}, {"get_model", lua::wrap}, {"get_hitbox", lua::wrap}, From dfb83f6835de486e4c2b621cd9bcccefdf0e7436 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 10 Aug 2025 22:55:34 +0300 Subject: [PATCH 063/177] add core:mob component & move player movement to scripting --- res/content/base/entities/player.json | 6 ++ res/scripts/components/mob.lua | 116 ++++++++++++++++++++++++++ src/logic/PlayerController.cpp | 5 -- src/logic/PlayerController.hpp | 1 - src/objects/Entities.cpp | 4 +- src/objects/Player.cpp | 82 ------------------ src/objects/Player.hpp | 4 - src/physics/Hitbox.cpp | 3 +- src/physics/Hitbox.hpp | 3 +- src/physics/PhysicsSolver.cpp | 8 +- 10 files changed, 132 insertions(+), 100 deletions(-) create mode 100644 res/scripts/components/mob.lua diff --git a/res/content/base/entities/player.json b/res/content/base/entities/player.json index b20a7aed..d3d5712c 100644 --- a/res/content/base/entities/player.json +++ b/res/content/base/entities/player.json @@ -1,5 +1,11 @@ { "components": [ + { + "name": "core:mob", + "args": { + "jump_force": 8.0 + } + }, "base:player_animator" ], "hitbox": [0.6, 1.8, 0.6] diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua new file mode 100644 index 00000000..e3909da1 --- /dev/null +++ b/res/scripts/components/mob.lua @@ -0,0 +1,116 @@ +local body = entity.rigidbody + +local jump_force = SAVED_DATA.jump_force or ARGS.jump_force or 0.0 +local air_damping = SAVED_DATA.air_damping or ARGS.air_damping or 1.0 +local ground_damping = SAVED_DATA.ground_damping or ARGS.ground_damping or 1.0 +local movement_speed = SAVED_DATA.movement_speed or ARGS.movement_speed or 4.0 +local run_speed_mul = SAVED_DATA.run_speed_mul or ARGS.run_speed_mul or 1.5 +local crouch_speed_mul = SAVED_DATA.crouch_speed_mul or ARGS.crouch_speed_mul or 0.35 +local flight_speed_mul = SAVED_DATA.flight_speed_mul or ARGS.flight_speed_mul or 4.0 +local cheat_speed_mul = SAVED_DATA.cheat_speed_mul or ARGS.cheat_speed_mul or 5.0 + +function jump(multiplier) + if body:is_grounded() then + local vel = body:get_vel() + body:set_vel( + vec3.add(vel, {0, jump_force * (multiplier or 1.0), 0}, vel)) + end +end + +function elevate(speed, delta, vel) + vel = vel or body:get_vel() + body:set_vel( + vec3.add(vel, {0, speed * delta, 0}, vel)) +end + +function lower(speed, delta, vel) + vel = vel or body:get_vel() + body:set_vel( + vec3.add(vel, {0, -speed * delta, 0}, vel)) +end + +function move_horizontal(speed, dir, vel) + vel = vel or body:get_vel() + if vec3.length(dir) > 0.0 then + vec3.normalize(dir, dir) + + vel[1] = dir[1] * speed + vel[3] = dir[3] * speed + end + body:set_vel(vel) +end + +function on_update(tps) + local delta = (1.0 / tps) + local pid = entity:get_player() + if pid then + -- todo: replace with entity direction + local cam = cameras.get("core:first-person") + local front = cam:get_front() + local right = cam:get_right() + front[2] = 0.0 + vec3.normalize(front, front) + + local grounded = body:is_grounded() + + local isjump = input.is_active('movement.jump') + local issprint = input.is_active('movement.sprint') + local iscrouch = input.is_active('movement.crouch') + local isforward = input.is_active('movement.forward') + local ischeat = input.is_active('movement.cheat') + local isback = input.is_active('movement.back') + local isleft = input.is_active('movement.left') + local isright = input.is_active('movement.right') + local flight = player.is_flight(pid) + local noclip = player.is_noclip(pid) + + local vel = body:get_vel() + + local speed = movement_speed + + if flight then + speed = speed * flight_speed_mul + elseif issprint then + speed = speed * run_speed_mul + elseif iscrouch and grounded then + speed = speed * crouch_speed_mul + end + body:set_crouching(iscrouch) + + if ischeat then + speed = speed * cheat_speed_mul + end + + local dir = {0, 0, 0} + if isforward then + vec3.add(dir, front, dir) + end + if isback then + vec3.sub(dir, front, dir) + end + if isright then + vec3.add(dir, right, dir) + end + if isleft then + vec3.sub(dir, right, dir) + end + + if vec3.length(dir) > 0.0 then + move_horizontal(speed, dir, vel) + end + + if flight then + if isjump then + elevate(speed * 8.0, delta) + elseif iscrouch then + lower(speed * 8.0, delta) + end + elseif isjump then + jump() + end + body:set_vdamping(flight) + body:set_gravity_scale(flight and 0.0 or 1.0) + body:set_linear_damping((flight or not grounded) and air_damping or ground_damping) + body:set_body_type(noclip and "kinematic" or "dynamic") + end +end diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index f5fdea1b..8021db51 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -271,7 +271,6 @@ void PlayerController::update(float delta, const Input* inputEvents) { } else { resetKeyboard(); } - updatePlayer(delta); } void PlayerController::postUpdate( @@ -313,10 +312,6 @@ void PlayerController::resetKeyboard() { input = {}; } -void PlayerController::updatePlayer(float delta) { - player.updateInput(input, delta); -} - static int determine_rotation( const Block* def, const glm::ivec3& norm, const glm::vec3& camDir ) { diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp index cd49642c..7f895ee3 100644 --- a/src/logic/PlayerController.hpp +++ b/src/logic/PlayerController.hpp @@ -60,7 +60,6 @@ class PlayerController { void updateKeyboard(const Input& inputEvents); void resetKeyboard(); - void updatePlayer(float delta); void updateEntityInteraction(entityid_t eid, bool lclick, bool rclick); void updateInteraction(const Input& inputEvents, float delta); diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 8dcdecd4..2353dc1b 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -297,7 +297,9 @@ void Entities::updatePhysics(float delta) { int substeps = static_cast(delta * vel * 20); substeps = std::min(100, std::max(2, substeps)); physics->step(*level.chunks, hitbox, delta, substeps, eid.uid); - hitbox.linearDamping = hitbox.grounded * 24; + hitbox.friction = glm::abs(hitbox.gravityScale <= 1e-7f) + ? 8.0f + : (!grounded ? 2.0f : 10.0f); transform.setPos(hitbox.position); if (hitbox.grounded && !grounded) { scripting::on_entity_grounded( diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index befdd0b3..2c2fbe24 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -21,13 +21,6 @@ static debug::Logger logger("player"); -constexpr float CROUCH_SPEED_MUL = 0.35f; -constexpr float RUN_SPEED_MUL = 1.5f; -constexpr float PLAYER_GROUND_DAMPING = 10.0f; -constexpr float PLAYER_AIR_DAMPING = 8.0f; -constexpr float FLIGHT_SPEED_MUL = 4.0f; -constexpr float CHEAT_SPEED_MUL = 5.0f; -constexpr float JUMP_FORCE = 8.0f; constexpr int SPAWN_ATTEMPTS_PER_UPDATE = 64; Player::Player( @@ -82,17 +75,6 @@ void Player::updateEntity() { "will be respawned"; eid = ENTITY_AUTO; } - auto hitbox = getHitbox(); - if (hitbox == nullptr) { - return; - } - hitbox->linearDamping = PLAYER_GROUND_DAMPING; - hitbox->verticalDamping = flight; - hitbox->gravityScale = flight ? 0.0f : 1.0f; - if (flight || !hitbox->grounded) { - hitbox->linearDamping = PLAYER_AIR_DAMPING; - } - hitbox->type = noclip ? BodyType::KINEMATIC : BodyType::DYNAMIC; } Hitbox* Player::getHitbox() { @@ -102,70 +84,6 @@ Hitbox* Player::getHitbox() { return nullptr; } -void Player::updateInput(PlayerInput& input, float delta) { - auto hitbox = getHitbox(); - if (hitbox == nullptr) { - return; - } - bool crouch = input.shift && hitbox->grounded && !input.sprint; - float speed = this->speed; - if (flight) { - speed *= FLIGHT_SPEED_MUL; - } - if (input.cheat) { - speed *= CHEAT_SPEED_MUL; - } - - hitbox->crouching = crouch; - if (crouch) { - speed *= CROUCH_SPEED_MUL; - } else if (input.sprint) { - speed *= RUN_SPEED_MUL; - } - - glm::vec3 dir(0, 0, 0); - if (input.moveForward) { - dir += fpCamera->dir; - } - if (input.moveBack) { - dir -= fpCamera->dir; - } - if (input.moveRight) { - dir += fpCamera->right; - } - if (input.moveLeft) { - dir -= fpCamera->right; - } - if (glm::length(dir) > 0.0f) { - dir = glm::normalize(dir); - doMove(dir, speed, delta); - } - if (flight) { - if (input.jump) { - hitbox->velocity.y += speed * delta * 9; - } - if (input.shift) { - hitbox->velocity.y -= speed * delta * 9; - } - } else if (input.jump) { - doJump(); - } -} - -void Player::doMove(const glm::vec3& dir, float speed, float delta) { - if (auto hitbox = getHitbox()) { - hitbox->velocity += dir * speed * delta * 9.0f; - } -} - -void Player::doJump() { - if (auto hitbox = getHitbox()) { - if (hitbox->grounded) { - hitbox->velocity.y = JUMP_FORCE; - } - } -} - void Player::updateSelectedEntity() { selectedEid = selection.entity; } diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index 2da8ee1d..b9999c78 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -59,9 +59,6 @@ class Player : public Serializable { entityid_t eid = ENTITY_AUTO; entityid_t selectedEid = 0; - void doMove(const glm::vec3& dir, float speed, float delta); - void doJump(); - glm::vec3 rotation {}; public: util::VecInterpolation<3, float, true> rotationInterpolation {true}; @@ -85,7 +82,6 @@ public: void teleport(glm::vec3 position); void updateEntity(); - void updateInput(PlayerInput& input, float delta); void updateSelectedEntity(); void postUpdate(); diff --git a/src/physics/Hitbox.cpp b/src/physics/Hitbox.cpp index 83ad823c..adf07841 100644 --- a/src/physics/Hitbox.cpp +++ b/src/physics/Hitbox.cpp @@ -6,6 +6,5 @@ Hitbox::Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize) : type(type), position(position), halfsize(halfsize), - velocity(0.0f,0.0f,0.0f), - linearDamping(0.1f) + velocity(0.0f,0.0f,0.0f) {} diff --git a/src/physics/Hitbox.hpp b/src/physics/Hitbox.hpp index e825cefe..2285a632 100644 --- a/src/physics/Hitbox.hpp +++ b/src/physics/Hitbox.hpp @@ -52,7 +52,8 @@ struct Hitbox { glm::vec3 position; glm::vec3 halfsize; glm::vec3 velocity; - float linearDamping; + float linearDamping = 0.5; + float friction = 1.0f; bool verticalDamping = false; bool grounded = false; float gravityScale = 1.0f; diff --git a/src/physics/PhysicsSolver.cpp b/src/physics/PhysicsSolver.cpp index 62a6abd6..0d1cb4a0 100644 --- a/src/physics/PhysicsSolver.cpp +++ b/src/physics/PhysicsSolver.cpp @@ -25,7 +25,7 @@ void PhysicsSolver::step( entityid_t entity ) { float dt = delta / static_cast(substeps); - float linearDamping = hitbox.linearDamping; + float linearDamping = hitbox.linearDamping * hitbox.friction; float s = 2.0f/BLOCK_AABB_GRID; const glm::vec3& half = hitbox.halfsize; @@ -45,11 +45,11 @@ void PhysicsSolver::step( colisionCalc(chunks, hitbox, vel, pos, half, (prevGrounded && gravityScale > 0.0f) ? 0.5f : 0.0f); } - vel.x *= glm::max(0.0f, 1.0f - dt * linearDamping); + vel.x /= 1.0f + dt * linearDamping; + vel.z /= 1.0f + dt * linearDamping; if (hitbox.verticalDamping) { - vel.y *= glm::max(0.0f, 1.0f - dt * linearDamping); + vel.y /= 1.0f + dt * linearDamping; } - vel.z *= glm::max(0.0f, 1.0f - dt * linearDamping); pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f; if (hitbox.grounded && pos.y < py) { From 5a8f4de503a95ad6e5d5e9509429f9f331113e0a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 10 Aug 2025 23:19:10 +0300 Subject: [PATCH 064/177] fix input library in headless mode --- res/scripts/components/mob.lua | 4 +++- src/logic/scripting/lua/libs/libinput.cpp | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua index e3909da1..01da449a 100644 --- a/res/scripts/components/mob.lua +++ b/res/scripts/components/mob.lua @@ -110,7 +110,9 @@ function on_update(tps) end body:set_vdamping(flight) body:set_gravity_scale(flight and 0.0 or 1.0) - body:set_linear_damping((flight or not grounded) and air_damping or ground_damping) + body:set_linear_damping( + (flight or not grounded) and air_damping or ground_damping + ) body:set_body_type(noclip and "kinematic" or "dynamic") end end diff --git a/src/logic/scripting/lua/libs/libinput.cpp b/src/logic/scripting/lua/libs/libinput.cpp index c5f20cc4..3537cd36 100644 --- a/src/logic/scripting/lua/libs/libinput.cpp +++ b/src/logic/scripting/lua/libs/libinput.cpp @@ -31,6 +31,8 @@ static int l_mousecode(lua::State* L) { } static int l_add_callback(lua::State* L) { + if (engine->isHeadless()) + return 0; std::string bindname = lua::require_string(L, 1); size_t pos = bindname.find(':'); @@ -75,10 +77,14 @@ static int l_add_callback(lua::State* L) { } static int l_get_mouse_pos(lua::State* L) { + if (engine->isHeadless()) + return 0; return lua::pushvec2(L, engine->getInput().getCursor().pos); } static int l_get_bindings(lua::State* L) { + if (engine->isHeadless()) + return 0; const auto& bindings = engine->getInput().getBindings().getAll(); lua::createtable(L, bindings.size(), 0); @@ -92,18 +98,24 @@ static int l_get_bindings(lua::State* L) { } static int l_get_binding_text(lua::State* L) { + if (engine->isHeadless()) + return 0; auto bindname = lua::require_string(L, 1); const auto& bind = engine->getInput().getBindings().require(bindname); return lua::pushstring(L, bind.text()); } static int l_is_active(lua::State* L) { + if (engine->isHeadless()) + return 0; auto bindname = lua::require_string(L, 1); auto& bind = engine->getInput().getBindings().require(bindname); return lua::pushboolean(L, bind.active()); } static int l_is_pressed(lua::State* L) { + if (engine->isHeadless()) + return 0; std::string code = lua::require_string(L, 1); size_t sep = code.find(':'); if (sep == std::string::npos) { @@ -136,6 +148,8 @@ static void reset_pack_bindings(const io::path& packFolder) { } static int l_reset_bindings(lua::State*) { + if (engine->isHeadless()) + return 0; reset_pack_bindings("res:"); for (const auto& pack : content_control->getContentPacks()) { reset_pack_bindings(pack.folder); @@ -144,6 +158,8 @@ static int l_reset_bindings(lua::State*) { } static int l_set_enabled(lua::State* L) { + if (engine->isHeadless()) + return 0; std::string bindname = lua::require_string(L, 1); bool enabled = lua::toboolean(L, 2); engine->getInput().getBindings().require(bindname).enabled = enabled; @@ -161,4 +177,5 @@ const luaL_Reg inputlib[] = { {"is_pressed", lua::wrap}, {"reset_bindings", lua::wrap}, {"set_enabled", lua::wrap}, - {NULL, NULL}}; + {NULL, NULL} +}; From b60f0f0ea2df151a8b4a86d79a5c7fbd3845901b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 11 Aug 2025 01:03:28 +0300 Subject: [PATCH 065/177] update core:mob component --- res/scripts/components/mob.lua | 50 ++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua index 01da449a..8cc63dc5 100644 --- a/res/scripts/components/mob.lua +++ b/res/scripts/components/mob.lua @@ -1,19 +1,35 @@ local body = entity.rigidbody -local jump_force = SAVED_DATA.jump_force or ARGS.jump_force or 0.0 -local air_damping = SAVED_DATA.air_damping or ARGS.air_damping or 1.0 -local ground_damping = SAVED_DATA.ground_damping or ARGS.ground_damping or 1.0 -local movement_speed = SAVED_DATA.movement_speed or ARGS.movement_speed or 4.0 -local run_speed_mul = SAVED_DATA.run_speed_mul or ARGS.run_speed_mul or 1.5 -local crouch_speed_mul = SAVED_DATA.crouch_speed_mul or ARGS.crouch_speed_mul or 0.35 -local flight_speed_mul = SAVED_DATA.flight_speed_mul or ARGS.flight_speed_mul or 4.0 -local cheat_speed_mul = SAVED_DATA.cheat_speed_mul or ARGS.cheat_speed_mul or 5.0 +local props = {} + +local function def_prop(name, def_value) + props[name] = SAVED_DATA[name] or ARGS[name] or def_value + this["get_"..name] = function() return props[name] end + this["set_"..name] = function(value) + props[name] = value + if math.abs(value - def_value) < 1e-7 then + SAVED_DATA[name] = nil + else + SAVED_DATA[name] = value + end + end +end + +def_prop("jump_force", 0.0) +def_prop("air_damping", 1.0) +def_prop("ground_damping", 1.0) +def_prop("movement_speed", 4.0) +def_prop("run_speed_mul", 1.5) +def_prop("crouch_speed_mul", 0.35) +def_prop("flight_speed_mul", 4.0) +def_prop("cheat_speed_mul", 5.0) +def_prop("gravity_scale", 1.0) function jump(multiplier) if body:is_grounded() then local vel = body:get_vel() body:set_vel( - vec3.add(vel, {0, jump_force * (multiplier or 1.0), 0}, vel)) + vec3.add(vel, {0, props.jump_force * (multiplier or 1.0), 0}, vel)) end end @@ -43,7 +59,7 @@ end function on_update(tps) local delta = (1.0 / tps) local pid = entity:get_player() - if pid then + if pid and hud and not hud.is_inventory_open() and not menu.page ~= "" then -- todo: replace with entity direction local cam = cameras.get("core:first-person") local front = cam:get_front() @@ -66,19 +82,19 @@ function on_update(tps) local vel = body:get_vel() - local speed = movement_speed + local speed = props.movement_speed if flight then - speed = speed * flight_speed_mul + speed = speed * props.flight_speed_mul elseif issprint then - speed = speed * run_speed_mul + speed = speed * props.run_speed_mul elseif iscrouch and grounded then - speed = speed * crouch_speed_mul + speed = speed * props.crouch_speed_mul end body:set_crouching(iscrouch) if ischeat then - speed = speed * cheat_speed_mul + speed = speed * props.cheat_speed_mul end local dir = {0, 0, 0} @@ -109,9 +125,9 @@ function on_update(tps) jump() end body:set_vdamping(flight) - body:set_gravity_scale(flight and 0.0 or 1.0) + body:set_gravity_scale(flight and 0.0 or props.gravity_scale) body:set_linear_damping( - (flight or not grounded) and air_damping or ground_damping + (flight or not grounded) and props.air_damping or props.ground_damping ) body:set_body_type(noclip and "kinematic" or "dynamic") end From 6d3dac910671ce381e5bd985ebd6dd9776a61ec5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 13 Aug 2025 21:29:20 +0300 Subject: [PATCH 066/177] add 'on_physics_update' entity event --- res/modules/internal/stdcomp.lua | 17 +++++++++++++++++ res/scripts/components/mob.lua | 4 ++-- src/logic/scripting/scripting.hpp | 1 + src/logic/scripting/scripting_entities.cpp | 10 ++++++++++ src/objects/Entities.cpp | 12 +++++++++++- src/objects/Entities.hpp | 1 + 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua index a62989a9..767bfa35 100644 --- a/res/modules/internal/stdcomp.lua +++ b/res/modules/internal/stdcomp.lua @@ -125,6 +125,23 @@ return { ::continue:: end end, + physics_update = function(tps, parts, part) + for uid, entity in pairs(entities) do + if uid % parts ~= part then + goto continue + end + for _, component in pairs(entity.components) do + local callback = component.on_physics_update + if not component.__disabled and callback then + local result, err = pcall(callback, tps) + if err then + debug.error(err) + end + end + end + ::continue:: + end + end, render = function(delta) for _,entity in pairs(entities) do for _, component in pairs(entity.components) do diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua index 8cc63dc5..783506a1 100644 --- a/res/scripts/components/mob.lua +++ b/res/scripts/components/mob.lua @@ -18,7 +18,7 @@ end def_prop("jump_force", 0.0) def_prop("air_damping", 1.0) def_prop("ground_damping", 1.0) -def_prop("movement_speed", 4.0) +def_prop("movement_speed", 3.0) def_prop("run_speed_mul", 1.5) def_prop("crouch_speed_mul", 0.35) def_prop("flight_speed_mul", 4.0) @@ -56,7 +56,7 @@ function move_horizontal(speed, dir, vel) body:set_vel(vel) end -function on_update(tps) +function on_physics_update(tps) local delta = (1.0 / tps) local pid = entity:get_player() if pid and hud and not hud.is_inventory_open() and not menu.page ~= "" then diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index bae5aac6..ddc87e8b 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -130,6 +130,7 @@ namespace scripting { void on_entity_fall(const Entity& entity); void on_entity_save(const Entity& entity); void on_entities_update(int tps, int parts, int part); + void on_entities_physics_update(int tps, int parts, int part); void on_entities_render(float delta); void on_sensor_enter(const Entity& entity, size_t index, entityid_t oid); void on_sensor_exit(const Entity& entity, size_t index, entityid_t oid); diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 4deabd4b..2ab63ae3 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -279,6 +279,16 @@ void scripting::on_entities_update(int tps, int parts, int part) { lua::pop(L); } +void scripting::on_entities_physics_update(int tps, int parts, int part) { + auto L = lua::get_main_state(); + lua::get_from(L, STDCOMP, "physics_update", true); + lua::pushinteger(L, tps); + lua::pushinteger(L, parts); + lua::pushinteger(L, part); + lua::call_nothrow(L, 3, 0); + lua::pop(L); +} + void scripting::on_entities_render(float delta) { auto L = lua::get_main_state(); lua::get_from(L, STDCOMP, "render", true); diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 2353dc1b..4982c636 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -25,7 +25,10 @@ static debug::Logger logger("entities"); Entities::Entities(Level& level) - : level(level), sensorsTickClock(20, 3), updateTickClock(20, 3) { + : level(level), + sensorsTickClock(20, 3), + updateTickClock(20, 3), + physicsTickClock(60, 1) { } std::optional Entities::get(entityid_t id) { @@ -320,6 +323,13 @@ void Entities::update(float delta) { updateTickClock.getPart() ); } + if (physicsTickClock.update(delta)) { + scripting::on_entities_physics_update( + physicsTickClock.getTickRate(), + physicsTickClock.getParts(), + physicsTickClock.getPart() + ); + } } static void debug_render_skeleton( diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 03fcec8c..95e3be68 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -39,6 +39,7 @@ class Entities { entityid_t nextID = 1; util::Clock sensorsTickClock; util::Clock updateTickClock; + util::Clock physicsTickClock; void updateSensors( Rigidbody& body, const Transform& tsf, std::vector& sensors From bef8e4b9d6a503e3b56334080a336373cb85591c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 13 Aug 2025 23:42:06 +0300 Subject: [PATCH 067/177] feat: model autorefresh in editor --- res/layouts/code_editor.xml.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/layouts/code_editor.xml.lua b/res/layouts/code_editor.xml.lua index 66a70ed8..0f9a02e8 100644 --- a/res/layouts/code_editor.xml.lua +++ b/res/layouts/code_editor.xml.lua @@ -81,6 +81,11 @@ local function refresh_file_title() document.saveIcon.enabled = edited document.title.text = gui.str('File')..' - '..current_file.filename ..(edited and ' *' or '') + + local info = registry.get_info(current_file.filename) + if info and info.type == "model" then + pcall(run_current_file) + end end function on_control_combination(keycode) From 2cdcebf9d97d6c7aff051ae18fec1875a07fb23c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 13 Aug 2025 23:59:05 +0300 Subject: [PATCH 068/177] add 'assets' library docs --- doc/en/scripting.md | 1 + doc/en/scripting/builtins/libassets.md | 28 ++++++++++++++++++++++ doc/ru/scripting.md | 1 + doc/ru/scripting/builtins/libassets.md | 28 ++++++++++++++++++++++ res/layouts/code_editor.xml.lua | 1 - src/coders/vcm.cpp | 1 - src/logic/scripting/lua/libs/libassets.cpp | 3 +++ 7 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 doc/en/scripting/builtins/libassets.md create mode 100644 doc/ru/scripting/builtins/libassets.md diff --git a/doc/en/scripting.md b/doc/en/scripting.md index 05a89a26..131b9cfa 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -10,6 +10,7 @@ Subsections: - [Entities and components](scripting/ecs.md) - [Libraries](#) - [app](scripting/builtins/libapp.md) + - [assets](scripting/builtins/libassets.md) - [base64](scripting/builtins/libbase64.md) - [bjson, json, toml, yaml](scripting/filesystem.md) - [block](scripting/builtins/libblock.md) diff --git a/doc/en/scripting/builtins/libassets.md b/doc/en/scripting/builtins/libassets.md new file mode 100644 index 00000000..bd095435 --- /dev/null +++ b/doc/en/scripting/builtins/libassets.md @@ -0,0 +1,28 @@ +# *assets* library + +A library for working with audio/visual assets. + +## Functions + +```lua +-- Loads a texture +assets.load_texture( + -- Array of bytes of an image file + data: table | Bytearray, + -- Texture name after loading + name: str, + -- Image file format (only png is supported) + [optional] + format: str = "png" +) + +-- Parses and loads a 3D model +assets.parse_model( + -- Model file format (xml / vcm) + format: str, + -- Contents of the model file + content: str, + -- Model name after loading + name: str +) +``` diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index eaae3302..aa25e072 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -10,6 +10,7 @@ - [Сущности и компоненты](scripting/ecs.md) - [Библиотеки](#) - [app](scripting/builtins/libapp.md) + - [assets](scripting/builtins/libassets.md) - [base64](scripting/builtins/libbase64.md) - [bjson, json, toml, yaml](scripting/filesystem.md) - [block](scripting/builtins/libblock.md) diff --git a/doc/ru/scripting/builtins/libassets.md b/doc/ru/scripting/builtins/libassets.md new file mode 100644 index 00000000..2c510948 --- /dev/null +++ b/doc/ru/scripting/builtins/libassets.md @@ -0,0 +1,28 @@ +# Библиотека *assets* + +Библиотека для работы с аудио/визуальными загружаемыми ресурсами. + +## Функции + +```lua +-- Загружает текстуру +assets.load_texture( + -- Массив байт файла изображения + data: table | Bytearray, + -- Имя текстуры после загрузки + name: str, + -- Формат файла изображения (поддерживается только png) + [опционально] + format: str = "png" +) + +-- Парсит и загружает 3D модель +assets.parse_model( + -- Формат файла модели (xml / vcm) + format: str, + -- Содержимое файла модели + content: str, + -- Имя модели после загрузки + name: str +) +``` diff --git a/res/layouts/code_editor.xml.lua b/res/layouts/code_editor.xml.lua index 0f9a02e8..4e6c68d4 100644 --- a/res/layouts/code_editor.xml.lua +++ b/res/layouts/code_editor.xml.lua @@ -123,7 +123,6 @@ function run_current_file() local unit = info and info.unit if script_type == "model" then - print(current_file.filename) clear_output() local _, err = pcall(reload_model, current_file.filename, unit) if err then diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index a12524c0..9b564787 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -174,7 +174,6 @@ std::unique_ptr vcm::parse( "'model' tag expected as root, got '" + root.getTag() + "'" ); } - std::cout << xml::stringify(*doc) << std::endl; return load_model(root); } catch (const parsing_error& err) { throw std::runtime_error(err.errorLog()); diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index 8cfb8ca8..047b6330 100644 --- a/src/logic/scripting/lua/libs/libassets.cpp +++ b/src/logic/scripting/lua/libs/libassets.cpp @@ -23,6 +23,9 @@ static void load_texture( } static int l_load_texture(lua::State* L) { + if (lua::isstring(L, 3) && lua::require_lstring(L, 3) != "png") { + throw std::runtime_error("unsupportd image format"); + } if (lua::istable(L, 1)) { lua::pushvalue(L, 1); size_t size = lua::objlen(L, 1); From 47939c052724259d2b000fa3f620cbb06ebc8c61 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 14 Aug 2025 02:24:48 +0300 Subject: [PATCH 069/177] feat: textbox indentation manipulations using tab, shift+tab --- src/graphics/ui/elements/TextBox.cpp | 78 +++++++++++++++++++++++++++- src/graphics/ui/elements/TextBox.hpp | 2 + 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 0ed75882..34e2784a 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -810,6 +810,82 @@ void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) { } } +static int calc_indent(int linestart, std::wstring_view input) { + int indent = 0; + while (linestart + indent < input.length() && + input[linestart + indent] == L' ') + indent++; + return indent; +} + +void TextBox::onTab(bool shiftPressed) { + std::wstring indentStr = L" "; + + if (!shiftPressed && getSelectionLength() == 0) { + paste(indentStr); + return; + } + if (getSelectionLength() == 0) { + selectionStart = caret; + selectionEnd = caret; + selectionOrigin = caret; + } + + int lineA = getLineAt(selectionStart); + int lineB = getLineAt(selectionEnd); + int caretLine = getLineAt(caret); + + size_t lineAStart = getLinePos(lineA); + size_t lineBStart = getLinePos(lineB); + size_t caretLineStart = getLinePos(caretLine); + size_t caretIndent = calc_indent(caretLineStart, input); + size_t aIndent = calc_indent(lineAStart, input); + size_t bIndent = calc_indent(lineBStart, input); + + int lastSelectionStart = selectionStart; + int lastSelectionEnd = selectionEnd; + size_t lastCaret = caret; + + resetSelection(); + + for (int line = lineA; line <= lineB; line++) { + size_t linestart = getLinePos(line); + int indent = calc_indent(linestart, input); + + if (shiftPressed) { + if (indent >= indentStr.length()) { + setCaret(linestart); + select(linestart, linestart + indentStr.length()); + eraseSelected(); + } + } else { + setCaret(linestart); + paste(indentStr); + } + refreshLabel(); // todo: replace with textbox cache + } + + int linestart = getLinePos(caretLine); + int linestartA = getLinePos(lineA); + int linestartB = getLinePos(lineB); + int la = lastSelectionStart - lineAStart; + int lb = lastSelectionEnd - lineBStart; + if (shiftPressed) { + setCaret(lastCaret - caretLineStart + linestart - std::min(caretIndent, indentStr.length())); + selectionStart = la + linestartA - std::min(std::min(la, aIndent), indentStr.length()); + selectionEnd = lb + linestartB - std::min(std::min(lb, bIndent), indentStr.length()); + } else { + setCaret(lastCaret - caretLineStart + linestart + indentStr.length()); + selectionStart = la + linestartA + indentStr.length(); + selectionEnd = lb + linestartB + indentStr.length(); + } + if (selectionOrigin == lastSelectionStart) { + selectionOrigin = selectionStart; + } else { + selectionOrigin = selectionEnd; + } +} + void TextBox::refreshSyntax() { if (!syntax.empty()) { const auto& processor = gui.getEditor().getSyntaxProcessor(); @@ -868,7 +944,7 @@ void TextBox::performEditingKeyboardEvents(Keycode key) { } } } else if (key == Keycode::TAB) { - paste(L" "); + onTab(shiftPressed); } else if (key == Keycode::LEFT) { stepLeft(shiftPressed, breakSelection); } else if (key == Keycode::RIGHT) { diff --git a/src/graphics/ui/elements/TextBox.hpp b/src/graphics/ui/elements/TextBox.hpp index 321d71eb..9bbfac39 100644 --- a/src/graphics/ui/elements/TextBox.hpp +++ b/src/graphics/ui/elements/TextBox.hpp @@ -71,6 +71,8 @@ namespace gui { void stepDefaultDown(bool shiftPressed, bool breakSelection); void stepDefaultUp(bool shiftPressed, bool breakSelection); + void onTab(bool shiftPressed); + size_t normalizeIndex(int index); int calcIndexAt(int x, int y) const; From 140841f65cbdeb2974bc137e20f00311f7d4c299 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 15 Aug 2025 07:45:16 +0300 Subject: [PATCH 070/177] refresh root document indices on GUI::add --- src/graphics/ui/GUI.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 79e9fd63..3ab438b1 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -302,6 +302,7 @@ bool GUI::isFocusCaught() const { } void GUI::add(std::shared_ptr node) { + UINode::getIndices(node, rootDocument->getMapWriteable()); container->add(std::move(node)); } From 9ce70d70e1aa9933b550c6df60bc6a9908cb58f0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 15 Aug 2025 12:41:37 +0300 Subject: [PATCH 071/177] fix indentation manipulations history writing --- src/graphics/ui/elements/TextBox.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 34e2784a..1755b6b9 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -846,6 +846,8 @@ void TextBox::onTab(bool shiftPressed) { int lastSelectionEnd = selectionEnd; size_t lastCaret = caret; + auto combination = history->beginCombination(); + resetSelection(); for (int line = lineA; line <= lineB; line++) { @@ -884,6 +886,7 @@ void TextBox::onTab(bool shiftPressed) { } else { selectionOrigin = selectionEnd; } + historian->sync(); } void TextBox::refreshSyntax() { From 53ce219127b30cdabf3a3eabd002c76277d27d16 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 15 Aug 2025 23:48:04 +0300 Subject: [PATCH 072/177] add 'is_new' argument to on_world_open --- src/frontend/hud.cpp | 10 +++++----- src/logic/scripting/scripting.cpp | 7 ++++++- src/world/World.cpp | 2 ++ src/world/World.hpp | 2 ++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 60cee6c4..660b63fc 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -324,7 +324,7 @@ void Hud::updateWorldGenDebug() { void Hud::update(bool visible) { const auto& chunks = *player.chunks; - bool is_menu_open = menu.hasOpenPage(); + bool isMenuOpen = menu.hasOpenPage(); debugPanel->setVisible( debug && visible && !(inventoryOpen && inventoryView == nullptr) @@ -333,13 +333,13 @@ void Hud::update(bool visible) { if (!visible && inventoryOpen) { closeInventory(); } - if (pause && !is_menu_open) { + if (pause && !isMenuOpen) { setPause(false); } if (!gui.isFocusCaught()) { processInput(visible); } - if ((is_menu_open || inventoryOpen) == input.getCursor().locked) { + if ((isMenuOpen || inventoryOpen) == input.getCursor().locked) { input.toggleCursor(); } @@ -360,8 +360,8 @@ void Hud::update(bool visible) { contentAccessPanel->setSize(glm::vec2(caSize.x, windowSize.y)); contentAccess->setMinSize(glm::vec2(1, windowSize.y)); hotbarView->setVisible(visible && !(secondUI && !inventoryView)); - darkOverlay->setVisible(is_menu_open); - menu.setVisible(is_menu_open); + darkOverlay->setVisible(isMenuOpen); + menu.setVisible(isMenuOpen); if (visible) { for (auto& element : elements) { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 00d18668..bb33a8bc 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -27,6 +27,7 @@ #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" #include "world/Level.hpp" +#include "world/World.hpp" #include "interfaces/Process.hpp" using namespace scripting; @@ -330,7 +331,11 @@ void scripting::on_world_load(LevelController* controller) { } for (auto& pack : content_control->getAllContentPacks()) { - lua::emit_event(L, pack.id + ":.worldopen"); + lua::emit_event(L, pack.id + ":.worldopen", [](auto L) { + return lua::pushboolean( + L, !scripting::level->getWorld()->getInfo().isLoaded + ); + }); } } diff --git a/src/world/World.cpp b/src/world/World.cpp index 96264336..6508a4da 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -116,6 +116,8 @@ std::unique_ptr World::load( if (!info.has_value()) { throw world_load_error("could not to find world.json"); } + info->isLoaded = true; + logger.info() << "loading world " << info->name << " (" << worldFilesPtr->getFolder().string() << ")"; logger.info() << "world version: " << info->major << "." << info->minor diff --git a/src/world/World.hpp b/src/world/World.hpp index 295ddac3..725b9182 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -45,6 +45,8 @@ struct WorldInfo : public Serializable { int major = 0, minor = -1; + bool isLoaded = false; + dv::value serialize() const override; void deserialize(const dv::value& src) override; }; From 6af74fc21955550086d68187f2790fd3b46db73e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 16 Aug 2025 12:48:42 +0300 Subject: [PATCH 073/177] improve blocks.set performance --- src/graphics/render/ChunksRenderer.cpp | 3 +++ src/logic/scripting/lua/libs/libblock.cpp | 5 +---- src/voxels/Chunk.cpp | 1 + src/voxels/Chunk.hpp | 1 + src/voxels/blocks_agent.cpp | 18 ++++++++++-------- src/voxels/blocks_agent.hpp | 4 ++-- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index bb816c37..4100e7b2 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -171,6 +171,9 @@ const Mesh* ChunksRenderer::retrieveChunk( if (mesh == nullptr) { return nullptr; } + if (chunk->flags.dirtyHeights) { + chunk->updateHeights(); + } if (culling) { glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D); glm::vec3 max( diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index eb699bc5..52e2931c 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -101,12 +101,9 @@ static int l_set(lua::State* L) { if (static_cast(id) >= indices->blocks.count()) { return 0; } - int cx = floordiv(x); - int cz = floordiv(z); - if (!blocks_agent::get_chunk(*level->chunks, cx, cz)) { + if (!blocks_agent::set(*level->chunks, x, y, z, id, int2blockstate(state))) { return 0; } - blocks_agent::set(*level->chunks, x, y, z, id, int2blockstate(state)); auto chunksController = controller->getChunksController(); if (chunksController == nullptr) { diff --git a/src/voxels/Chunk.cpp b/src/voxels/Chunk.cpp index 2c1f4fd8..e6da850e 100644 --- a/src/voxels/Chunk.cpp +++ b/src/voxels/Chunk.cpp @@ -14,6 +14,7 @@ Chunk::Chunk(int xpos, int zpos) : x(xpos), z(zpos) { } void Chunk::updateHeights() { + flags.dirtyHeights = false; for (uint i = 0; i < CHUNK_VOL; i++) { if (voxels[i].id != 0) { bottom = i / (CHUNK_D * CHUNK_W); diff --git a/src/voxels/Chunk.hpp b/src/voxels/Chunk.hpp index 3ce6e641..a15907f0 100644 --- a/src/voxels/Chunk.hpp +++ b/src/voxels/Chunk.hpp @@ -37,6 +37,7 @@ public: bool loadedLights : 1; bool entities : 1; bool blocksData : 1; + bool dirtyHeights : 1; } flags {}; /// @brief Block inventories map where key is index of block in voxels array diff --git a/src/voxels/blocks_agent.cpp b/src/voxels/blocks_agent.cpp index a23fa9ef..d56b1838 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -7,7 +7,7 @@ using namespace blocks_agent; template -static inline void set_block( +static inline bool set_block( Storage& chunks, int32_t x, int32_t y, @@ -16,14 +16,14 @@ static inline void set_block( blockstate state ) { if (y < 0 || y >= CHUNK_H) { - return; + return false; } const auto& indices = chunks.getContentIndices(); int cx = floordiv(x); int cz = floordiv(z); Chunk* chunk = get_chunk(chunks, cx, cz); if (chunk == nullptr) { - return; + return false; } int lx = x - cx * CHUNK_W; int lz = z - cz * CHUNK_D; @@ -60,7 +60,8 @@ static inline void set_block( else if (y + 1 > chunk->top) chunk->top = y + 1; else if (id == 0) - chunk->updateHeights(); + chunk->flags.dirtyHeights = true; + if (lx == 0 && (chunk = get_chunk(chunks, cx - 1, cz))) { chunk->flags.modified = true; @@ -74,9 +75,10 @@ static inline void set_block( if (lz == CHUNK_D - 1 && (chunk = get_chunk(chunks, cx, cz + 1))) { chunk->flags.modified = true; } + return true; } -void blocks_agent::set( +bool blocks_agent::set( Chunks& chunks, int32_t x, int32_t y, @@ -84,10 +86,10 @@ void blocks_agent::set( uint32_t id, blockstate state ) { - set_block(chunks, x, y, z, id, state); + return set_block(chunks, x, y, z, id, state); } -void blocks_agent::set( +bool blocks_agent::set( GlobalChunks& chunks, int32_t x, int32_t y, @@ -95,7 +97,7 @@ void blocks_agent::set( uint32_t id, blockstate state ) { - set_block(chunks, x, y, z, id, state); + return set_block(chunks, x, y, z, id, state); } template diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index 34fde035..efecf4e3 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -119,7 +119,7 @@ inline bool is_replaceable_at(const Storage& chunks, int32_t x, int32_t y, int32 /// @param z block position Z /// @param id new block id /// @param state new block state -void set( +bool set( Chunks& chunks, int32_t x, int32_t y, @@ -135,7 +135,7 @@ void set( /// @param z block position Z /// @param id new block id /// @param state new block state -void set( +bool set( GlobalChunks& chunks, int32_t x, int32_t y, From 8d29c59125936b6f906440d6f74efefd577d7cae Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 16 Aug 2025 13:38:51 +0300 Subject: [PATCH 074/177] fix rigidbody:set_gravity_scale --- src/logic/scripting/lua/libs/lib__rigidbody.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logic/scripting/lua/libs/lib__rigidbody.cpp b/src/logic/scripting/lua/libs/lib__rigidbody.cpp index d0e66940..f5558ac4 100644 --- a/src/logic/scripting/lua/libs/lib__rigidbody.cpp +++ b/src/logic/scripting/lua/libs/lib__rigidbody.cpp @@ -54,7 +54,7 @@ static int l_get_gravity_scale(lua::State* L) { static int l_set_gravity_scale(lua::State* L) { if (auto entity = get_entity(L, 1)) { - entity->getRigidbody().hitbox.gravityScale = lua::tonumber(L, 2); + entity->getRigidbody().hitbox.gravityScale = lua::tovec3(L, 2).y; } return 0; } From c8d760e83edd82d82d94aff3a322b992ebee47e6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 18 Aug 2025 21:20:18 +0300 Subject: [PATCH 075/177] update project script methods, replace project script with project client script --- ...{project_script.lua => project_client.lua} | 12 +- src/devtools/Project.hpp | 4 +- src/engine/Engine.cpp | 136 ++++++++++-------- src/engine/Engine.hpp | 7 +- src/logic/scripting/scripting.cpp | 11 +- src/logic/scripting/scripting.hpp | 10 +- 6 files changed, 92 insertions(+), 88 deletions(-) rename res/{project_script.lua => project_client.lua} (76%) diff --git a/res/project_script.lua b/res/project_client.lua similarity index 76% rename from res/project_script.lua rename to res/project_client.lua index 9158f361..ede54110 100644 --- a/res/project_script.lua +++ b/res/project_client.lua @@ -1,13 +1,13 @@ local menubg -local function clear_menu() +function on_menu_clear() if menubg then menubg:destruct() menubg = nil end end -local function configure_menu() +function on_menu_setup() local controller = {} function controller.resize_menu_bg() local w, h = unpack(gui.get_viewport()) @@ -24,11 +24,3 @@ local function configure_menu() controller.resize_menu_bg() menu.page = "main" end - -function on_screen_changed(screen) - if screen ~= "menu" then - clear_menu() - else - configure_menu() - end -end diff --git a/src/devtools/Project.hpp b/src/devtools/Project.hpp index 3824ecfd..be9d88a3 100644 --- a/src/devtools/Project.hpp +++ b/src/devtools/Project.hpp @@ -7,14 +7,14 @@ #include "interfaces/Serializable.hpp" namespace scripting { - class IProjectScript; + class IClientProjectScript; } struct Project : Serializable { std::string name; std::string title; std::vector basePacks; - std::unique_ptr script; + std::unique_ptr clientScript; ~Project(); diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 3f1ae9e5..0288d609 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -60,11 +60,11 @@ static std::unique_ptr load_icon() { return nullptr; } -static std::unique_ptr load_project_script() { - io::path scriptFile = "project:project_script.lua"; +static std::unique_ptr load_client_project_script() { + io::path scriptFile = "project:project_client.lua"; if (io::exists(scriptFile)) { logger.info() << "starting project script"; - return scripting::load_project_script(scriptFile); + return scripting::load_client_project_script(scriptFile); } else { logger.warning() << "project script does not exists"; } @@ -83,6 +83,68 @@ Engine& Engine::getInstance() { return *instance; } +void Engine::onContentLoad() { + editor->loadTools(); + langs::setup(langs::get_current(), paths.resPaths.collectRoots()); + + if (isHeadless()) { + return; + } + for (auto& pack : content->getAllContentPacks()) { + auto configFolder = pack.folder / "config"; + auto bindsFile = configFolder / "bindings.toml"; + if (io::is_regular_file(bindsFile)) { + input->getBindings().read( + toml::parse( + bindsFile.string(), io::read_string(bindsFile) + ), + BindType::BIND + ); + } + } + loadAssets(); +} + +void Engine::initializeClient() { + std::string title = project->title; + if (title.empty()) { + title = "VoxelCore v" + + std::to_string(ENGINE_VERSION_MAJOR) + "." + + std::to_string(ENGINE_VERSION_MINOR); + } + if (ENGINE_DEBUG_BUILD) { + title += " [debug]"; + } + auto [window, input] = Window::initialize(&settings.display, title); + if (!window || !input){ + throw initialize_error("could not initialize window"); + } + window->setFramerate(settings.display.framerate.get()); + + time.set(window->time()); + if (auto icon = load_icon()) { + icon->flipY(); + window->setIcon(icon.get()); + } + this->window = std::move(window); + this->input = std::move(input); + + loadControls(); + + gui = std::make_unique(*this); + if (ENGINE_DEBUG_BUILD) { + menus::create_version_label(*gui); + } + keepAlive(settings.display.fullscreen.observe( + [this](bool value) { + if (value != this->window->isFullscreen()) { + this->window->toggleFullscreen(); + } + }, + true + )); +} + void Engine::initialize(CoreParameters coreParameters) { params = std::move(coreParameters); settingsHandler = std::make_unique(settings); @@ -111,70 +173,17 @@ void Engine::initialize(CoreParameters coreParameters) { controller = std::make_unique(*this); if (!params.headless) { - std::string title = project->title; - if (title.empty()) { - title = "VoxelCore v" + - std::to_string(ENGINE_VERSION_MAJOR) + "." + - std::to_string(ENGINE_VERSION_MINOR); - } - if (ENGINE_DEBUG_BUILD) { - title += " [debug]"; - } - auto [window, input] = Window::initialize(&settings.display, title); - if (!window || !input){ - throw initialize_error("could not initialize window"); - } - window->setFramerate(settings.display.framerate.get()); - - time.set(window->time()); - if (auto icon = load_icon()) { - icon->flipY(); - window->setIcon(icon.get()); - } - this->window = std::move(window); - this->input = std::move(input); - - loadControls(); - - gui = std::make_unique(*this); - if (ENGINE_DEBUG_BUILD) { - menus::create_version_label(*gui); - } - keepAlive(settings.display.fullscreen.observe( - [this](bool value) { - if (value != this->window->isFullscreen()) { - this->window->toggleFullscreen(); - } - }, - true - )); + initializeClient(); } audio::initialize(!params.headless, settings.audio); - bool langNotSet = settings.ui.language.get() == "auto"; - if (langNotSet) { + if (settings.ui.language.get() == "auto") { settings.ui.language.set( langs::locale_by_envlocale(platform::detect_locale()) ); } content = std::make_unique(*project, paths, *input, [this]() { - editor->loadTools(); - langs::setup(langs::get_current(), paths.resPaths.collectRoots()); - if (!isHeadless()) { - for (auto& pack : content->getAllContentPacks()) { - auto configFolder = pack.folder / "config"; - auto bindsFile = configFolder / "bindings.toml"; - if (io::is_regular_file(bindsFile)) { - input->getBindings().read( - toml::parse( - bindsFile.string(), io::read_string(bindsFile) - ), - BindType::BIND - ); - } - } - loadAssets(); - } + onContentLoad(); }); scripting::initialize(this); @@ -185,7 +194,7 @@ void Engine::initialize(CoreParameters coreParameters) { langs::setup(lang, paths.resPaths.collectRoots()); }, true)); - project->script = load_project_script(); + project->clientScript = load_client_project_script(); } void Engine::loadSettings() { @@ -360,6 +369,9 @@ void Engine::loadProject() { } void Engine::setScreen(std::shared_ptr screen) { + if (project->clientScript && this->screen) { + project->clientScript->onScreenChange(this->screen->getName(), false); + } // reset audio channels (stop all sources) audio::reset_channel(audio::get_channel_index("regular")); audio::reset_channel(audio::get_channel_index("ambient")); @@ -367,8 +379,8 @@ void Engine::setScreen(std::shared_ptr screen) { if (this->screen) { this->screen->onOpen(); } - if (project->script && this->screen) { - project->script->onScreenChange(this->screen->getName()); + if (project->clientScript && this->screen) { + project->clientScript->onScreenChange(this->screen->getName(), true); } } diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index 35619a2a..98bcd497 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -38,10 +38,6 @@ namespace devtools { class Editor; } -namespace scripting { - class IProjectScript; -} - class initialize_error : public std::runtime_error { public: initialize_error(const std::string& message) : std::runtime_error(message) {} @@ -86,6 +82,9 @@ class Engine : public util::ObjectsKeeper { void updateHotkeys(); void loadAssets(); void loadProject(); + + void initializeClient(); + void onContentLoad(); public: Engine(); ~Engine(); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index bb33a8bc..19ad225d 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -117,20 +117,19 @@ public: } }; -class LuaProjectScript : public IProjectScript { +class LuaProjectScript : public IClientProjectScript { public: LuaProjectScript(lua::State* L, scriptenv env) : L(L), env(std::move(env)) {} - void onScreenChange(const std::string& name) override { + void onScreenChange(const std::string& name, bool show) override { if (!lua::pushenv(L, *env)) { return; } - if (!lua::getfield(L, "on_screen_changed")) { + if (!lua::getfield(L, "on_" + name + (show ? "_setup" : "_clear"))) { lua::pop(L); return; } - lua::pushlstring(L, name); - lua::call_nothrow(L, 1, 0); + lua::call_nothrow(L, 0, 0); lua::pop(L); } private: @@ -138,7 +137,7 @@ private: scriptenv env; }; -std::unique_ptr scripting::load_project_script( +std::unique_ptr scripting::load_client_project_script( const io::path& script ) { auto L = lua::get_main_state(); diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index b6f8733d..78eb8e13 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -65,14 +65,16 @@ namespace scripting { void process_post_runnables(); - class IProjectScript { + class IClientProjectScript { public: - virtual ~IProjectScript() {} + virtual ~IClientProjectScript() {} - virtual void onScreenChange(const std::string& name) = 0; + virtual void onScreenChange(const std::string& name, bool show) = 0; }; - std::unique_ptr load_project_script(const io::path& script); + std::unique_ptr load_client_project_script( + const io::path& script + ); std::unique_ptr start_coroutine(const io::path& script); From b5f1698e7809633b60b6ca8b47cb17f219b673d9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 18 Aug 2025 22:27:38 +0300 Subject: [PATCH 076/177] fix ffi usage in named_pipe_unix.lua --- res/modules/internal/stream_providers/named_pipe_unix.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/modules/internal/stream_providers/named_pipe_unix.lua b/res/modules/internal/stream_providers/named_pipe_unix.lua index 0a9c8904..8de356fe 100644 --- a/res/modules/internal/stream_providers/named_pipe_unix.lua +++ b/res/modules/internal/stream_providers/named_pipe_unix.lua @@ -22,9 +22,9 @@ local O_NONBLOCK = 0x800 local F_GETFL = 3 local function getError() - local err = ffi.errno() + local err = FFI.errno() - return ffi.string(C.strerror(err)).." ("..err..")" + return FFI.string(C.strerror(err)).." ("..err..")" end local lib = {} @@ -101,4 +101,4 @@ return function(path, mode) end return io_stream.new(fd, mode:find('b') ~= nil, lib) -end \ No newline at end of file +end From d32407f1bbe6e2e75db333135f91c7934ec5f1e8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 18 Aug 2025 22:27:50 +0300 Subject: [PATCH 077/177] cleanup --- res/content/base/scripts/hud.lua | 3 +++ res/project_client.lua | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/res/content/base/scripts/hud.lua b/res/content/base/scripts/hud.lua index 00f6bdc6..9fc10f2a 100644 --- a/res/content/base/scripts/hud.lua +++ b/res/content/base/scripts/hud.lua @@ -21,6 +21,9 @@ function on_hud_open() local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0}) local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE) local drop = base_util.drop(ppos, itemid, 1, data, 1.5) + if not drop then + return + end local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL)) drop.rigidbody:set_vel(velocity) end) diff --git a/res/project_client.lua b/res/project_client.lua index ede54110..a1952c08 100644 --- a/res/project_client.lua +++ b/res/project_client.lua @@ -20,7 +20,7 @@ function on_menu_setup() gui.root.root:add( "", controller) - menubg = _GUI_ROOT.menubg + menubg = gui.root.menubg controller.resize_menu_bg() menu.page = "main" end From 31cd7912ec8ce0ef66587b79a04b8b7856a96889 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 18 Aug 2025 23:32:12 +0300 Subject: [PATCH 078/177] fix named pipe read (unix) & produce core:error event on error in schedule callback & fix editor traceback --- res/layouts/console.xml.lua | 2 +- res/modules/internal/stream_providers/named_pipe_unix.lua | 2 +- res/modules/schedule.lua | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua index 94cbda8b..f7e47d9b 100644 --- a/res/layouts/console.xml.lua +++ b/res/layouts/console.xml.lua @@ -4,7 +4,7 @@ history = session.get_entry("commands_history") history_pointer = #history events.on("core:open_traceback", function() - if modes then + if modes and modes.current ~= 'debug' then modes:set('debug') end end) diff --git a/res/modules/internal/stream_providers/named_pipe_unix.lua b/res/modules/internal/stream_providers/named_pipe_unix.lua index 8de356fe..1843daba 100644 --- a/res/modules/internal/stream_providers/named_pipe_unix.lua +++ b/res/modules/internal/stream_providers/named_pipe_unix.lua @@ -31,7 +31,7 @@ local lib = {} function lib.read(fd, len) local buffer = FFI.new("uint8_t[?]", len) - local result = C.read(fd, buffer, len) + local result = tonumber(C.read(fd, buffer, len)) local out = Bytearray() diff --git a/res/modules/schedule.lua b/res/modules/schedule.lua index 24d216f0..5d24a086 100644 --- a/res/modules/schedule.lua +++ b/res/modules/schedule.lua @@ -15,8 +15,9 @@ local Schedule = { local timer = self._timer + dt for id, interval in pairs(self._intervals) do if timer - interval.last_called >= interval.delay then - xpcall(interval.callback, function(s) - debug.error(s..'\n'..debug.traceback()) + local stack_size = debug.count_frames() + xpcall(interval.callback, function(msg) + __vc__error(msg, 1, 1, stack_size) end) interval.last_called = timer local repetions = interval.repetions From abc937769dcbc2d76a086dcc359837daa46f768e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 18 Aug 2025 23:56:46 +0300 Subject: [PATCH 079/177] add nil checks for inventory id, slot index to inventory slot related functions --- src/logic/scripting/lua/libs/libinventory.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/logic/scripting/lua/libs/libinventory.cpp b/src/logic/scripting/lua/libs/libinventory.cpp index 629283a7..5ed3f8cc 100644 --- a/src/logic/scripting/lua/libs/libinventory.cpp +++ b/src/logic/scripting/lua/libs/libinventory.cpp @@ -45,6 +45,12 @@ namespace { template int wrap_slot(lua::State* L) { + if (lua::isnoneornil(L, 1)) { + throw std::runtime_error("inventory id is nil"); + } + if (lua::isnoneornil(L, 2)) { + throw std::runtime_error("slot index is nil"); + } auto invid = lua::tointeger(L, 1); auto slotid = lua::tointeger(L, 2); auto& inv = get_inventory(invid); From 79bb61bbbe282603b35dd02e0f0aaa8d540036c8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 19 Aug 2025 01:33:20 +0300 Subject: [PATCH 080/177] add 'debug.enable-experimental' setting & add experimental vec3, vec2 optimization --- res/modules/internal/maths_inline.lua | 212 ++++++++++++++++++++++++++ res/scripts/stdlib.lua | 6 + src/io/settings_io.cpp | 1 + src/settings.hpp | 2 + 4 files changed, 221 insertions(+) create mode 100644 res/modules/internal/maths_inline.lua diff --git a/res/modules/internal/maths_inline.lua b/res/modules/internal/maths_inline.lua new file mode 100644 index 00000000..e3e37fcd --- /dev/null +++ b/res/modules/internal/maths_inline.lua @@ -0,0 +1,212 @@ +-- =================================================== -- +-- ====================== vec3 ======================= -- +-- =================================================== -- +function vec3.add(a, b, dst) + local btype = type(b) + if dst then + if btype == "table" then + dst[1] = a[1] + b[1] + dst[2] = a[2] + b[2] + dst[3] = a[3] + b[3] + else + dst[1] = a[1] + b + dst[2] = a[2] + b + dst[3] = a[3] + b + end + return dst + else + if btype == "table" then + return {a[1] + b[1], a[2] + b[2], a[3] + b[3]} + else + return {a[1] + b, a[2] + b, a[3] + b} + end + end +end + +function vec3.sub(a, b, dst) + local btype = type(b) + if dst then + if btype == "table" then + dst[1] = a[1] - b[1] + dst[2] = a[2] - b[2] + dst[3] = a[3] - b[3] + else + dst[1] = a[1] - b + dst[2] = a[2] - b + dst[3] = a[3] - b + end + return dst + else + if btype == "table" then + return {a[1] - b[1], a[2] - b[2], a[3] - b[3]} + else + return {a[1] - b, a[2] - b, a[3] - b} + end + end +end + +function vec3.mul(a, b, dst) + local btype = type(b) + if dst then + if btype == "table" then + dst[1] = a[1] * b[1] + dst[2] = a[2] * b[2] + dst[3] = a[3] * b[3] + else + dst[1] = a[1] * b + dst[2] = a[2] * b + dst[3] = a[3] * b + end + return dst + else + if btype == "table" then + return {a[1] * b[1], a[2] * b[2], a[3] * b[3]} + else + return {a[1] * b, a[2] * b, a[3] * b} + end + end +end + +function vec3.div(a, b, dst) + local btype = type(b) + if dst then + if btype == "table" then + dst[1] = a[1] / b[1] + dst[2] = a[2] / b[2] + dst[3] = a[3] / b[3] + else + dst[1] = a[1] / b + dst[2] = a[2] / b + dst[3] = a[3] / b + end + return dst + else + if btype == "table" then + return {a[1] / b[1], a[2] / b[2], a[3] / b[3]} + else + return {a[1] / b, a[2] / b, a[3] / b} + end + end +end + +function vec3.abs(a, dst) + local x = a[1] + local y = a[2] + local z = a[3] + if dst then + dst[1] = x < 0.0 and -x or x + dst[2] = y < 0.0 and -y or y + dst[3] = z < 0.0 and -z or z + else + return { + x < 0.0 and -x or x, + y < 0.0 and -y or y, + z < 0.0 and -z or z, + } + end +end + +function vec3.dot(a, b) + return a[1] * b[1] + a[2] * b[2] + a[3] * b[3] +end + +-- =================================================== -- +-- ====================== vec2 ======================= -- +-- =================================================== -- +function vec2.add(a, b, dst) + local btype = type(b) + if dst then + if btype == "table" then + dst[1] = a[1] + b[1] + dst[2] = a[2] + b[2] + else + dst[1] = a[1] + b + dst[2] = a[2] + b + end + return dst + else + if btype == "table" then + return {a[1] + b[1], a[2] + b[2]} + else + return {a[1] + b, a[2] + b} + end + end +end + +function vec2.sub(a, b, dst) + local btype = type(b) + if dst then + if btype == "table" then + dst[1] = a[1] - b[1] + dst[2] = a[2] - b[2] + else + dst[1] = a[1] - b + dst[2] = a[2] - b + end + return dst + else + if btype == "table" then + return {a[1] - b[1], a[2] - b[2]} + else + return {a[1] - b, a[2] - b} + end + end +end + +function vec2.mul(a, b, dst) + local btype = type(b) + if dst then + if btype == "table" then + dst[1] = a[1] * b[1] + dst[2] = a[2] * b[2] + else + dst[1] = a[1] * b + dst[2] = a[2] * b + end + return dst + else + if btype == "table" then + return {a[1] * b[1], a[2] * b[2]} + else + return {a[1] * b, a[2] * b} + end + end +end + +function vec2.div(a, b, dst) + local btype = type(b) + if dst then + if btype == "table" then + dst[1] = a[1] / b[1] + dst[2] = a[2] / b[2] + else + dst[1] = a[1] / b + dst[2] = a[2] / b + end + return dst + else + if btype == "table" then + return {a[1] / b[1], a[2] / b[2]} + else + return {a[1] / b, a[2] / b} + end + end +end + +function vec2.abs(a, dst) + local x = a[1] + local y = a[2] + if dst then + dst[1] = x < 0.0 and -x or x + dst[2] = y < 0.0 and -y or y + else + return { + x < 0.0 and -x or x, + y < 0.0 and -y or y, + } + end +end + +function vec2.dot(a, b) + return a[1] * b[1] + a[2] * b[2] +end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 68b9e4f6..9e4f9556 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -1,3 +1,5 @@ +local enable_experimental = core.get_setting("debug.enable-experimental") + ------------------------------------------------ ------ Extended kit of standard functions ------ ------------------------------------------------ @@ -169,6 +171,10 @@ function inventory.set_description(invid, slot, description) inventory.set_data(invid, slot, "description", description) end +if enable_experimental then + require "core:internal/maths_inline" +end + events = require "core:internal/events" function pack.unload(prefix) diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index cdbf567e..56c5f038 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -86,6 +86,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.section("debug"); builder.add("generator-test-mode", &settings.debug.generatorTestMode); builder.add("do-write-lights", &settings.debug.doWriteLights); + builder.add("enable-experimental", &settings.debug.enableExperimental); } dv::value SettingsHandler::getValue(const std::string& name) const { diff --git a/src/settings.hpp b/src/settings.hpp index d4c21688..10986f39 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -90,6 +90,8 @@ struct DebugSettings { FlagSetting generatorTestMode {false}; /// @brief Write lights cache FlagSetting doWriteLights {true}; + /// @brief Enable experimental optimizations and features + FlagSetting enableExperimental {false}; }; struct UiSettings { From 669cb48f544a41f67e73658f805530b5ca42aadb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 19 Aug 2025 21:20:44 +0300 Subject: [PATCH 081/177] fix set_gravity_scale use --- res/scripts/components/mob.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua index 783506a1..840aba42 100644 --- a/res/scripts/components/mob.lua +++ b/res/scripts/components/mob.lua @@ -125,7 +125,7 @@ function on_physics_update(tps) jump() end body:set_vdamping(flight) - body:set_gravity_scale(flight and 0.0 or props.gravity_scale) + body:set_gravity_scale({0, flight and 0.0 or props.gravity_scale, 0}) body:set_linear_damping( (flight or not grounded) and props.air_damping or props.ground_damping ) From 6cae75e02f78e24a951ba7e4b74dd25bb3cc3885 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 20 Aug 2025 23:49:18 +0300 Subject: [PATCH 082/177] fix extended blocks destruction particles spawn spread, offset --- res/content/base/scripts/world.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 4b78a3f1..7f468c29 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -1,6 +1,11 @@ function on_block_broken(id, x, y, z, playerid) if gfx then - gfx.particles.emit({x+0.5, y+0.5, z+0.5}, 64, { + local size = {block.get_size(id)} + gfx.particles.emit({ + x + size[1] * 0.5, + y + size[1] * 0.5, + z + size[1] * 0.5 + }, 64, { lifetime=1.0, spawn_interval=0.0001, explosion={4, 4, 4}, @@ -8,7 +13,7 @@ function on_block_broken(id, x, y, z, playerid) random_sub_uv=0.1, size={0.1, 0.1, 0.1}, spawn_shape="box", - spawn_spread={0.4, 0.4, 0.4} + spawn_spread=vec3.mul(size, 0.4) }) end From a9d40c312c1a5176f8878ac1804ee70e27ce8d4d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 20 Aug 2025 23:52:41 +0300 Subject: [PATCH 083/177] extract one texture from custom model if not assigned --- src/graphics/render/ModelsGenerator.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp index a5782f95..b4f42348 100644 --- a/src/graphics/render/ModelsGenerator.cpp +++ b/src/graphics/render/ModelsGenerator.cpp @@ -6,6 +6,7 @@ #include "voxels/Block.hpp" #include "content/Content.hpp" #include "debug/Logger.hpp" +#include "core_defs.hpp" static debug::Logger logger("models-generator"); @@ -67,11 +68,18 @@ void ModelsGenerator::prepareModel( } else { auto srcModel = assets.get(blockModel.name); if (srcModel) { + bool defaultAssigned = variant.textureFaces[0] != TEXTURE_NOTFOUND; auto model = std::make_unique(*srcModel); for (auto& mesh : model->meshes) { if (mesh.texture.length() && mesh.texture[0] == '$') { int index = std::stoll(mesh.texture.substr(1)); mesh.texture = "blocks:" + variant.textureFaces[index]; + } else if (!defaultAssigned && !mesh.texture.empty()) { + size_t sepPos = mesh.texture.find(':'); + if (sepPos == std::string::npos) + continue; + variant.textureFaces[0] = mesh.texture.substr(sepPos + 1); + defaultAssigned = true; } } blockModel.name = modelName; From 5994371145dd6b914700ea3db3721ff25ac856ee Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 01:33:06 +0300 Subject: [PATCH 084/177] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B9=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавил проверку версий установленных зависимостей паков. --- res/layouts/pages/content.xml.lua | 13 +++++++++++-- res/texts/en_US.txt | 1 + res/texts/ru_RU.txt | 1 + src/content/ContentPack.cpp | 10 +++++++++- src/content/ContentPack.hpp | 1 + src/content/PacksManager.cpp | 8 ++++++++ src/logic/scripting/lua/libs/libpack.cpp | 3 ++- 7 files changed, 33 insertions(+), 4 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 3f1903f3..50288d17 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -181,8 +181,9 @@ function check_dependencies(packinfo) return end for i,dep in ipairs(packinfo.dependencies) do - local depid = dep:sub(2,-1) - if dep:sub(1,1) == '!' then + local depid, depver = unpack(string.split(dep:sub(2,-1), "@")) + + if dep:sub(1,1) == '!' then if not table.has(packs_all, depid) then return string.format( "%s (%s)", gui.str("error.dependency-not-found"), depid @@ -192,6 +193,14 @@ function check_dependencies(packinfo) table.insert(required, depid) end end + + local dep_pack = pack.get_info(depid); + + if depver ~= "*" or depver ~= dep_pack.version then + return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); + end + + debug.print(packinfo); end return end diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 57a6cb6b..267566cd 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -7,6 +7,7 @@ world.convert-block-layouts=Blocks fields have changes! Convert world files? pack.remove-confirm=Do you want to erase all pack(s) content from the world forever? error.pack-not-found=Could not to find pack error.dependency-not-found=Dependency pack is not found +error.dependency-version-not-met=Dependency pack version is not met. world.delete-confirm=Do you want to delete world forever? world.generators.default=Default world.generators.flat=Flat diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index d2d7c63e..f7f11a36 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -32,6 +32,7 @@ devtools.output=Вывод error.pack-not-found=Не удалось найти пакет error.dependency-not-found=Используемая зависимость не найдена +error.dependency-version-not-met=Версия зависимости не соответствует необходимой pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)? # Подсказки diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index ac8c8d59..8c3ea541 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -132,7 +132,15 @@ ContentPack ContentPack::read(const io::path& folder) { level = DependencyLevel::weak; break; } - pack.dependencies.push_back({level, depName}); + + std::string depVersion = "*"; + size_t version_pos = depName.rfind("@"); + if (version_pos != std::string::npos){ + depVersion = depName.substr(version_pos + 1); + depName = depName.substr(0, version_pos); + } + + pack.dependencies.push_back({level, depName, depVersion}); } } diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index f4e44801..f8bc760c 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -35,6 +35,7 @@ enum class DependencyLevel { struct DependencyPack { DependencyLevel level; std::string id; + std::string verison; }; struct ContentPackStats { diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index 0e9925c8..76cc54d7 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -105,6 +105,14 @@ static bool resolve_dependencies( // added continue; } + if (dep.verison == "*" || dep.verison == found->second.version){ + // dependency pack version mets the required one + continue; + } else { + throw contentpack_error( + dep.id, io::path(), "does not meet required version '" + dep.verison +"' of '" + pack->id + "'" + ); + } if (!util::contains(allNames, dep.id) && dep.level != DependencyLevel::weak) { diff --git a/src/logic/scripting/lua/libs/libpack.cpp b/src/logic/scripting/lua/libs/libpack.cpp index adc45096..be38fcb2 100644 --- a/src/logic/scripting/lua/libs/libpack.cpp +++ b/src/logic/scripting/lua/libs/libpack.cpp @@ -114,7 +114,8 @@ static int l_pack_get_info( default: throw std::runtime_error(""); } - lua::pushfstring(L, "%s%s", prefix.c_str(), dpack.id.c_str()); + + lua::pushfstring(L, "%s%s@%s", prefix.c_str(), dpack.id.c_str(), dpack.verison.c_str()); lua::rawseti(L, i + 1); } lua::setfield(L, "dependencies"); From bbbd6c44014f71f6dd0306e9395bcdf3e2ac77b9 Mon Sep 17 00:00:00 2001 From: KotIsOff <71443736+kotisoff@users.noreply.github.com> Date: Sat, 23 Aug 2025 01:34:50 +0300 Subject: [PATCH 085/177] fixed comment typo --- src/content/PacksManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index 76cc54d7..c898d590 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -106,7 +106,7 @@ static bool resolve_dependencies( continue; } if (dep.verison == "*" || dep.verison == found->second.version){ - // dependency pack version mets the required one + // dependency pack version meets the required one continue; } else { throw contentpack_error( From 3131068ad8aa64202e64d0766ed9efde753ac19a Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 01:42:13 +0300 Subject: [PATCH 086/177] removed print --- res/layouts/pages/content.xml.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 50288d17..1c49f72d 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -200,7 +200,6 @@ function check_dependencies(packinfo) return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); end - debug.print(packinfo); end return end From 5d91d94433e7b6b0158ca6f10c9425a7ba564ae0 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 01:46:13 +0300 Subject: [PATCH 087/177] moved depver lua code in dep level condition --- res/layouts/pages/content.xml.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 1c49f72d..e57cea5f 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -189,17 +189,16 @@ function check_dependencies(packinfo) "%s (%s)", gui.str("error.dependency-not-found"), depid ) end + + local dep_pack = pack.get_info(depid); + if depver ~= "*" or depver ~= dep_pack.version then + return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); + end + if table.has(packs_installed, packinfo.id) then table.insert(required, depid) end end - - local dep_pack = pack.get_info(depid); - - if depver ~= "*" or depver ~= dep_pack.version then - return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); - end - end return end From 74238d08445e532891f62f503ed1ff580bd22084 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 02:00:07 +0300 Subject: [PATCH 088/177] fixed condition double yat --- res/layouts/pages/content.xml.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index e57cea5f..8ce1cebc 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -191,10 +191,10 @@ function check_dependencies(packinfo) end local dep_pack = pack.get_info(depid); - if depver ~= "*" or depver ~= dep_pack.version then + if depver ~= "*" and depver ~= dep_pack.version then return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); end - + if table.has(packs_installed, packinfo.id) then table.insert(required, depid) end From f91b734aeaee5ed490133db26c5f1fd211fec5ef Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 16:43:42 +0300 Subject: [PATCH 089/177] docs for dependency version --- doc/en/content-packs.md | 5 +++++ doc/ru/content-packs.md | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index c32d1b03..b7a78938 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -30,6 +30,11 @@ If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. +Dependency version is indicated by postfix after '@' symbol. +If postfix is not specified, '*' (any) version will be used. + +Example: 'randutil@1.0' - dependency 'randutil' which requires version 1.0. + Example: ```json { diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 77a78636..716e25ef 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -30,7 +30,12 @@ - '~' - слабая зависимость Отсутствие префикса интерпретируется как '!'. -Пример: '~randutil' - слабая зависимость 'randutil'. +Пример: '~randutil@1.0' - слабая зависимость 'randutil'. + +Версии зависимостей указываются с помощью постфикса через '@'. +Отсутствие версии зависимости интерпретируется как '*', т.е. любая версия. + +Пример: 'randutil@1.0' - зависимость 'randutil' версии 1.0. Пример: ```json From 45bc4037ecc7fa7d39d0c36ac7d642a83670a47d Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 16:46:31 +0300 Subject: [PATCH 090/177] fix: escaping for '*' symbol --- doc/en/content-packs.md | 2 +- doc/ru/content-packs.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index b7a78938..53e095b9 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -31,7 +31,7 @@ If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. Dependency version is indicated by postfix after '@' symbol. -If postfix is not specified, '*' (any) version will be used. +If postfix is not specified, '\*' (any) version will be used. Example: 'randutil@1.0' - dependency 'randutil' which requires version 1.0. diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 716e25ef..34ccaab7 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -33,7 +33,7 @@ Пример: '~randutil@1.0' - слабая зависимость 'randutil'. Версии зависимостей указываются с помощью постфикса через '@'. -Отсутствие версии зависимости интерпретируется как '*', т.е. любая версия. +Отсутствие версии зависимости интерпретируется как '\*', т.е. любая версия. Пример: 'randutil@1.0' - зависимость 'randutil' версии 1.0. From 8c7409eed15a35629e9ae764b217bba4936c97e4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 23 Aug 2025 19:46:07 +0300 Subject: [PATCH 091/177] add bytearray option for generation.load_fragment (#597) --- src/logic/scripting/lua/libs/libgeneration.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/logic/scripting/lua/libs/libgeneration.cpp b/src/logic/scripting/lua/libs/libgeneration.cpp index 291b847f..2dd5746b 100644 --- a/src/logic/scripting/lua/libs/libgeneration.cpp +++ b/src/logic/scripting/lua/libs/libgeneration.cpp @@ -36,11 +36,19 @@ static int l_create_fragment(lua::State* L) { } static int l_load_fragment(lua::State* L) { - io::path path = lua::require_string(L, 1); - if (!io::exists(path)) { - throw std::runtime_error("file "+path.string()+" does not exist"); + dv::value map; + if (!lua::isstring(L, 1)) { + io::path path = lua::require_string(L, 1); + if (!io::exists(path)) { + throw std::runtime_error("file "+path.string()+" does not exist"); + } + map = io::read_binary_json(path); + } else { + auto bytearray = lua::bytearray_as_string(L, 1); + map = json::from_binary( + reinterpret_cast(bytearray.data()), bytearray.size() + ); } - auto map = io::read_binary_json(path); auto fragment = std::make_shared(); fragment->deserialize(map); From 4a9aad04ff0ba6a6c382cd3a0dcdee7fecec55c4 Mon Sep 17 00:00:00 2001 From: Onran0 Date: Sun, 24 Aug 2025 02:39:05 +0900 Subject: [PATCH 092/177] udp support --- doc/ru/scripting/builtins/libnetwork.md | 61 ++++- res/scripts/classes.lua | 89 ++++++- src/logic/scripting/lua/libs/libnetwork.cpp | 151 ++++++++++- src/network/Network.cpp | 275 ++++++++++++++++++-- src/network/Network.hpp | 95 +++++-- 5 files changed, 607 insertions(+), 64 deletions(-) diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index 0d33acf1..0e9a02b1 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -2,7 +2,7 @@ Библиотека для работы с сетью. -## HTTP-запросы +## HTTP-Запросы ```lua -- Выполняет GET запрос к указанному URL. @@ -98,6 +98,65 @@ server:is_open() --> bool server:get_port() --> int ``` +## UDP-Датаграммы + +```lua +network.udp_connect( + address: str, + port: int, + -- Функция, вызызываемая при получении датаграммы с указанного при открытии сокета адреса и порта + datagramHandler: function(Bytearray), + -- Функция, вызываемая после открытия сокета + -- Необязательна, так как в UDP нет handshake + [опционально] openCallback: function(WriteableSocket), +) --> WriteableSocket +``` + +Открывает UDP-сокет с привязкой к удалённому адресу и порту + +Класс WriteableSocket имеет следующие методы: + +```lua +-- Отправляет датаграмму на адрес и порт, заданные при открытии сокета +socket:send(table|Bytearray|str) + +-- Закрывает сокет +socket:close() + +-- Проверяет открыт ли сокет +socket:is_open() --> bool + +-- Возвращает адрес и порт, на которые привязан сокет +socket:get_address() --> str, int +``` + +```lua +network.udp_open( + port: int, + -- Функция, вызываемая при получении датаграмы + -- В параметры передаётся адрес и порт отправителя, а также сами данные + datagramHandler: function(address: str, port: int, data: Bytearray) +) --> DatagramServerSocket +``` + +Открывает UDP-сервер на указанном порту + +Класс DatagramServerSocket имеет следующие методы: + +```lua +-- Отправляет датаграмму на переданный адрес и порт +server:send(address: str, port: int, data: table|Bytearray|str) + +-- Завершает принятие датаграмм +server:stop() + +-- Проверяет возможность принятия датаграмм +server:is_open() --> bool + +-- Возвращает порт, который слушает сервер +server:get_port() --> int +``` + ## Аналитика ```lua diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 41406892..30354050 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -46,18 +46,35 @@ local Socket = {__index={ get_address=function(self) return network.__get_address(self.id) end, }} +local WriteableSocket = {__index={ + send=function(self, ...) return network.__send(self.id, ...) end, + close=function(self) return network.__close(self.id) end, + is_open=function(self) return network.__is_alive(self.id) end, + get_address=function(self) return network.__get_address(self.id) end, +}} + local ServerSocket = {__index={ close=function(self) return network.__closeserver(self.id) end, is_open=function(self) return network.__is_serveropen(self.id) end, get_port=function(self) return network.__get_serverport(self.id) end, }} +local DatagramServerSocket = {__index={ + close=function(self) return network.__closeserver(self.id) end, + is_open=function(self) return network.__is_serveropen(self.id) end, + get_port=function(self) return network.__get_serverport(self.id) end, + send=function(self, ...) return network.__udp_server_send_to(self.id, ...) end +}} local _tcp_server_callbacks = {} local _tcp_client_callbacks = {} +local _udp_server_callbacks = {} +local _udp_client_datagram_callbacks = {} +local _udp_client_open_callbacks = {} + network.tcp_open = function (port, handler) - local socket = setmetatable({id=network.__open(port)}, ServerSocket) + local socket = setmetatable({id=network.__open_tcp(port)}, ServerSocket) _tcp_server_callbacks[socket.id] = function(id) handler(setmetatable({id=id}, Socket)) @@ -67,19 +84,61 @@ end network.tcp_connect = function(address, port, callback) local socket = setmetatable({id=0}, Socket) - socket.id = network.__connect(address, port) + socket.id = network.__connect_tcp(address, port) _tcp_client_callbacks[socket.id] = function() callback(socket) end return socket end +network.udp_open = function (port, datagramHandler) + if type(datagramHandler) ~= 'func' then + error "udp server cannot be opened without datagram handler" + end + + local socket = setmetatable({id=network.__open_udp(port)}, DatagramServerSocket) + + _udp_server_callbacks[socket.id] = datagramHandler + + return socket +end + +network.udp_connect = function (address, port, datagramHandler, openCallback) + if type(datagramHandler) ~= 'func' then + error "udp client socket cannot be opened without datagram handler" + end + + local socket = setmetatable({id=0}, WriteableSocket) + socket.id = network.__connect_udp(address, port) + + _udp_client_datagram_callbacks[socket.id] = datagramHandler + _udp_client_open_callbacks[socket.id] = openCallback + + return socket +end + +local function clean(iterable, checkFun, ...) + local tables = { ... } + + for id, _ in pairs(iterable) do + if not checkFun(id) then + for i = 1, #tables do + tables[i][id] = nil + end + end + end +end + network.__process_events = function() local CLIENT_CONNECTED = 1 local CONNECTED_TO_SERVER = 2 + local DATAGRAM = 3 + + local ON_SERVER = 1 + local ON_CLIENT = 2 local cleaned = false local events = network.__pull_events() for i, event in ipairs(events) do - local etype, sid, cid = unpack(event) + local etype, sid, cid, addr, port, side, data = unpack(event) if etype == CLIENT_CONNECTED then local callback = _tcp_server_callbacks[sid] @@ -87,24 +146,26 @@ network.__process_events = function() callback(cid) end elseif etype == CONNECTED_TO_SERVER then - local callback = _tcp_client_callbacks[cid] + local callback = _tcp_client_callbacks[cid] or _udp_client_open_callbacks[cid] if callback then callback() end + elseif etype == DATAGRAM then + if side == ON_CLIENT then + _udp_client_datagram_callbacks[cid](data) + elseif side == ON_SERVER then + _udp_server_callbacks[sid](addr, port, data) + end end -- remove dead servers if not cleaned then - for sid, _ in pairs(_tcp_server_callbacks) do - if not network.__is_serveropen(sid) then - _tcp_server_callbacks[sid] = nil - end - end - for cid, _ in pairs(_tcp_client_callbacks) do - if not network.__is_alive(cid) then - _tcp_client_callbacks[cid] = nil - end - end + clean(_tcp_server_callbacks, network.__is_serveropen, _tcp_server_callbacks) + clean(_tcp_client_callbacks, network.__is_alive, _tcp_client_callbacks) + + clean(_udp_server_callbacks, network.__is_serveropen, _udp_server_callbacks) + clean(_udp_client_datagram_callbacks, network.__is_alive, _udp_client_open_callbacks, _udp_client_datagram_callbacks) + cleaned = true end end diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index ed6799bd..dc2eff65 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -134,17 +134,58 @@ static int l_send(lua::State* L, network::Network& network) { return 0; } +static int l_udp_server_send_to(lua::State* L, network::Network& network) { + u64id_t id = lua::tointeger(L, 1); + + if (auto server = network.getServer(id)) { + if (server->getTransportType() != network::TransportType::UDP) + throw std::runtime_error("the server must work on UDP transport"); + + const std::string& addr = lua::tostring(L, 2); + const int& port = lua::tointeger(L, 3); + + auto udpServer = dynamic_cast(server); + + if (lua::istable(L, 4)) { + lua::pushvalue(L, 4); + size_t size = lua::objlen(L, 4); + util::Buffer buffer(size); + for (size_t i = 0; i < size; i++) { + lua::rawgeti(L, i + 1); + buffer[i] = lua::tointeger(L, -1); + lua::pop(L); + } + lua::pop(L); + udpServer->sendTo(addr, port, buffer.data(), size); + } else if (lua::isstring(L, 4)) { + auto string = lua::tolstring(L, 4); + udpServer->sendTo(addr, port, string.data(), string.length()); + } else { + auto string = lua::bytearray_as_string(L, 4); + udpServer->sendTo(addr, port, string.data(), string.length()); + lua::pop(L); + } + } + + return 0; +} + static int l_recv(lua::State* L, network::Network& network) { u64id_t id = lua::tointeger(L, 1); int length = lua::tointeger(L, 2); + auto connection = engine->getNetwork().getConnection(id); - if (connection == nullptr) { + + if (connection == nullptr || connection->getTransportType() != network::TransportType::TCP) { return 0; } - length = glm::min(length, connection->available()); + + auto tcpConnection = dynamic_cast(connection); + + length = glm::min(length, tcpConnection->available()); util::Buffer buffer(length); - int size = connection->recv(buffer.data(), length); + int size = tcpConnection->recv(buffer.data(), length); if (size == -1) { return 0; } @@ -162,15 +203,18 @@ static int l_recv(lua::State* L, network::Network& network) { static int l_available(lua::State* L, network::Network& network) { u64id_t id = lua::tointeger(L, 1); + if (auto connection = network.getConnection(id)) { - return lua::pushinteger(L, connection->available()); + return lua::pushinteger(L, dynamic_cast(connection)->available()); } + return 0; } enum NetworkEventType { CLIENT_CONNECTED = 1, - CONNECTED_TO_SERVER + CONNECTED_TO_SERVER, + DATAGRAM }; struct NetworkEvent { @@ -179,32 +223,96 @@ struct NetworkEvent { u64id_t client; }; +enum NetworkDatagramSide { + ON_SERVER = 1, + ON_CLIENT +}; + +struct NetworkDatagramEvent : NetworkEvent { + NetworkDatagramSide side; + std::string addr; + int port; + char* buffer; + size_t length; + + NetworkDatagramEvent(NetworkEventType datagram, + u64id_t sid, + u64id_t cid, + NetworkDatagramSide side, + const std::string& addr, + int port, + const char* data, + size_t length + ); +}; + static std::vector events_queue {}; -static int l_connect(lua::State* L, network::Network& network) { +static int l_connect_tcp(lua::State* L, network::Network& network) { std::string address = lua::require_string(L, 1); int port = lua::tointeger(L, 2); - u64id_t id = network.connect(address, port, [](u64id_t cid) { + u64id_t id = network.connectTcp(address, port, [](u64id_t cid) { events_queue.push_back({CONNECTED_TO_SERVER, 0, cid}); }); return lua::pushinteger(L, id); } -static int l_open(lua::State* L, network::Network& network) { +static int l_open_tcp(lua::State* L, network::Network& network) { int port = lua::tointeger(L, 1); - u64id_t id = network.openServer(port, [](u64id_t sid, u64id_t id) { + u64id_t id = network.openTcpServer(port, [](u64id_t sid, u64id_t id) { events_queue.push_back({CLIENT_CONNECTED, sid, id}); }); return lua::pushinteger(L, id); } +static int l_connect_udp(lua::State* L, network::Network& network) { + std::string address = lua::require_string(L, 1); + int port = lua::tointeger(L, 2); + u64id_t id = network.connectUdp(address, port, [](u64id_t cid) { + events_queue.push_back({CONNECTED_TO_SERVER, 0, cid}); + }, [address, port]( + u64id_t cid, + const char* buffer, + size_t length + ) { + events_queue.push_back( + NetworkDatagramEvent{ + DATAGRAM, 0, cid, ON_CLIENT, + address, port, buffer, length + } + ); + }); + return lua::pushinteger(L, id); +} + +static int l_open_udp(lua::State* L, network::Network& network) { + int port = lua::tointeger(L, 1); + u64id_t id = network.openUdpServer(port, []( + u64id_t sid, + const std::string& addr, + int port, + const char* buffer, + size_t length) { + events_queue.push_back( + NetworkDatagramEvent{ + DATAGRAM, sid, 0, ON_SERVER, + addr, port, buffer, length + } + ); + }); + return lua::pushinteger(L, id); +} + static int l_is_alive(lua::State* L, network::Network& network) { u64id_t id = lua::tointeger(L, 1); if (auto connection = network.getConnection(id)) { return lua::pushboolean( L, connection->getState() != network::ConnectionState::CLOSED || - connection->available() > 0 + ( + connection->getTransportType() == network::TransportType::TCP && + dynamic_cast(connection)->available() > 0 + ) ); } return lua::pushboolean(L, false); @@ -267,6 +375,22 @@ static int l_pull_events(lua::State* L, network::Network& network) { lua::pushinteger(L, events_queue[i].client); lua::rawseti(L, 3); + + if (typeid(events_queue[i]) == typeid(NetworkDatagramEvent)) { + const NetworkDatagramEvent& de = (NetworkDatagramEvent&) events_queue[i]; + + lua::pushstring(L, de.addr); + lua::rawseti(L, 4); + + lua::pushinteger(L, de.port); + lua::rawseti(L, 5); + + lua::pushinteger(L, de.side); + lua::rawseti(L, 6); + + lua::pushvalue(L, lua::create_bytearray(L, de.buffer, de.length)); + lua::rawseti(L, 7); + } lua::rawseti(L, i + 1); } @@ -298,9 +422,12 @@ const luaL_Reg networklib[] = { {"get_total_upload", wrap}, {"get_total_download", wrap}, {"__pull_events", wrap}, - {"__open", wrap}, + {"__open_tcp", wrap}, + {"__open_udp", wrap}, {"__closeserver", wrap}, - {"__connect", wrap}, + {"__udp_server_send_to", wrap}, + {"__connect_tcp", wrap}, + {"__connect_udp", wrap}, {"__close", wrap}, {"__send", wrap}, {"__recv", wrap}, diff --git a/src/network/Network.cpp b/src/network/Network.cpp index 8936adc6..92be35a2 100644 --- a/src/network/Network.cpp +++ b/src/network/Network.cpp @@ -291,7 +291,7 @@ static std::string to_string(const sockaddr_in& addr, bool port=true) { return ""; } -class SocketConnection : public Connection { +class SocketTcpConnection : public TcpConnection { SOCKET descriptor; sockaddr_in addr; size_t totalUpload = 0; @@ -317,10 +317,10 @@ class SocketConnection : public Connection { state = ConnectionState::CONNECTED; } public: - SocketConnection(SOCKET descriptor, sockaddr_in addr) + SocketTcpConnection(SOCKET descriptor, sockaddr_in addr) : descriptor(descriptor), addr(std::move(addr)), buffer(16'384) {} - ~SocketConnection() { + ~SocketTcpConnection() { if (state != ConnectionState::CLOSED) { shutdown(descriptor, 2); } @@ -442,7 +442,9 @@ public: return to_string(addr, false); } - static std::shared_ptr connect( + TransportType getTransportType() const override { return TransportType::TCP; } + + static std::shared_ptr connect( const std::string& address, int port, runnable callback ) { addrinfo hints {}; @@ -466,7 +468,7 @@ public: if (descriptor == -1) { throw std::runtime_error("Could not create socket"); } - auto socket = std::make_shared(descriptor, std::move(serverAddress)); + auto socket = std::make_shared(descriptor, std::move(serverAddress)); socket->connect(std::move(callback)); return socket; } @@ -476,7 +478,7 @@ public: } }; -class SocketTcpSServer : public TcpServer { +class SocketTcpServer : public TcpServer { u64id_t id; Network* network; SOCKET descriptor; @@ -486,10 +488,10 @@ class SocketTcpSServer : public TcpServer { std::unique_ptr thread = nullptr; int port; public: - SocketTcpSServer(u64id_t id, Network* network, SOCKET descriptor, int port) + SocketTcpServer(u64id_t id, Network* network, SOCKET descriptor, int port) : id(id), network(network), descriptor(descriptor), port(port) {} - ~SocketTcpSServer() { + ~SocketTcpServer() { closeSocket(); } @@ -510,7 +512,7 @@ public: break; } logger.info() << "client connected: " << to_string(address); - auto socket = std::make_shared( + auto socket = std::make_shared( clientDescriptor, address ); socket->startClient(); @@ -558,7 +560,7 @@ public: return port; } - static std::shared_ptr openServer( + static std::shared_ptr openServer( u64id_t id, Network* network, int port, ConnectCallback handler ) { SOCKET descriptor = socket( @@ -586,12 +588,230 @@ public: } logger.info() << "opened server at port " << port; auto server = - std::make_shared(id, network, descriptor, port); + std::make_shared(id, network, descriptor, port); server->startListen(std::move(handler)); return server; } }; +class SocketUdpConnection : public UdpConnection { + u64id_t id; + SOCKET descriptor; + sockaddr_in addr{}; + bool open = true; + std::unique_ptr thread; + ClientDatagramCallback callback; + + size_t totalUpload = 0; + size_t totalDownload = 0; + ConnectionState state = ConnectionState::INITIAL; + +public: + SocketUdpConnection(u64id_t id, SOCKET descriptor, sockaddr_in addr) + : id(id), descriptor(descriptor), addr(std::move(addr)) {} + + ~SocketUdpConnection() override { + close(); + } + + static std::shared_ptr connect( + u64id_t id, + const std::string& address, + int port, + ClientDatagramCallback handler, + runnable callback + ) { + SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0); + if (descriptor == -1) { + throw std::runtime_error("Could not create UDP socket"); + } + + sockaddr_in serverAddr{}; + serverAddr.sin_family = AF_INET; + if (inet_pton(AF_INET, address.c_str(), &serverAddr.sin_addr) <= 0) { + closesocket(descriptor); + throw std::runtime_error("Invalid UDP address: " + address); + } + serverAddr.sin_port = htons(port); + + // вызов connect() на UDP сокете → ограничиваем только одним адресом + if (::connect(descriptor, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { + auto err = handle_socket_error("UDP connect failed"); + closesocket(descriptor); + throw err; + } + + auto socket = std::make_shared(id, descriptor, serverAddr); + socket->connect(std::move(handler)); + + callback(); + + return socket; + } + + void connect(ClientDatagramCallback handler) override { + callback = std::move(handler); + state = ConnectionState::CONNECTED; + + thread = std::make_unique([this]() { + util::Buffer buffer(16'384); + while (open) { + int size = recv(descriptor, buffer.data(), buffer.size(), 0); + if (size <= 0) { + if (!open) break; + closesocket(descriptor); + state = ConnectionState::CLOSED; + break; + } + totalDownload += size; + if (callback) { + callback(id, buffer.data(), size); + } + } + }); + } + + int send(const char* buffer, size_t length) override { + int len = sendto(descriptor, buffer, length, 0, + (sockaddr*)&addr, sizeof(addr)); + if (len < 0) { + closesocket(descriptor); + state = ConnectionState::CLOSED; + } else totalUpload += len; + + return len; + } + + void close(bool discardAll=false) override { + if (!open) return; + open = false; + + if (state != ConnectionState::CLOSED) { + shutdown(descriptor, 2); + closesocket(descriptor); + } + + if (thread) { + thread->join(); + thread.reset(); + } + state = ConnectionState::CLOSED; + } + + size_t pullUpload() override { + size_t s = totalUpload; + totalUpload = 0; + return s; + } + + size_t pullDownload() override { + size_t s = totalDownload; + totalDownload = 0; + return s; + } + + [[nodiscard]] int getPort() const override { + return ntohs(addr.sin_port); + } + + [[nodiscard]] std::string getAddress() const override { + return to_string(addr, false); + } + + [[nodiscard]] ConnectionState getState() const override { + return state; + } + + [[nodiscard]] TransportType getTransportType() const noexcept override { + return TransportType::UDP; + } +}; + +class SocketUdpServer : public UdpServer { + u64id_t id; + Network* network; + SOCKET descriptor; + bool open = true; + std::unique_ptr thread = nullptr; + int port; + ServerDatagramCallback callback; + +public: + SocketUdpServer(u64id_t id, Network* network, SOCKET descriptor, int port, ServerDatagramCallback cb) + : id(id), network(network), descriptor(descriptor), port(port), callback(std::move(cb)) {} + + ~SocketUdpServer() override { + SocketUdpServer::close(); + } + + void startListen() { + thread = std::make_unique([this]() { + util::Buffer buffer(16384); + sockaddr_in clientAddr{}; + socklen_t addrlen = sizeof(clientAddr); + + while (open) { + int size = recvfrom(descriptor, buffer.data(), buffer.size(), 0, + reinterpret_cast(&clientAddr), &addrlen); + if (size <= 0) { + if (!open) break; + continue; + } + + std::string addrStr = to_string(clientAddr, false); + int port = ntohs(clientAddr.sin_port); + + callback(id, addrStr, port, buffer.data(), size); + } + }); + } + + void sendTo(const std::string& addr, int port, const char* buffer, size_t length) override { + sockaddr_in client{}; + client.sin_family = AF_INET; + inet_pton(AF_INET, addr.c_str(), &client.sin_addr); + client.sin_port = htons(port); + + sendto(descriptor, buffer, length, 0, + reinterpret_cast(&client), sizeof(client)); + } + + void close() override { + if (!open) return; + open = false; + shutdown(descriptor, 2); + closesocket(descriptor); + if (thread) { + thread->join(); + thread = nullptr; + } + } + + bool isOpen() override { return open; } + int getPort() const override { return port; } + + static std::shared_ptr openServer( + u64id_t id, Network* network, int port, const ServerDatagramCallback& handler + ) { + SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0); + if (descriptor == -1) throw std::runtime_error("Could not create UDP socket"); + + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(port); + + if (bind(descriptor, (sockaddr*)&address, sizeof(address)) < 0) { + closesocket(descriptor); + throw std::runtime_error("Could not bind UDP port " + std::to_string(port)); + } + + auto server = std::make_shared(id, network, descriptor, port, std::move(handler)); + server->startListen(); + return server; + } +}; + Network::Network(std::unique_ptr requests) : requests(std::move(requests)) { } @@ -627,7 +847,7 @@ Connection* Network::getConnection(u64id_t id) { return found->second.get(); } -TcpServer* Network::getServer(u64id_t id) const { +Server* Network::getServer(u64id_t id) const { const auto& found = servers.find(id); if (found == servers.end()) { return nullptr; @@ -635,20 +855,38 @@ TcpServer* Network::getServer(u64id_t id) const { return found->second.get(); } -u64id_t Network::connect(const std::string& address, int port, consumer callback) { +u64id_t Network::connectTcp(const std::string& address, int port, consumer callback) { std::lock_guard lock(connectionsMutex); u64id_t id = nextConnection++; - auto socket = SocketConnection::connect(address, port, [id, callback]() { + auto socket = SocketTcpConnection::connect(address, port, [id, callback]() { callback(id); }); connections[id] = std::move(socket); return id; } -u64id_t Network::openServer(int port, ConnectCallback handler) { +u64id_t Network::openTcpServer(int port, ConnectCallback handler) { u64id_t id = nextServer++; - auto server = SocketTcpSServer::openServer(id, this, port, handler); + auto server = SocketTcpServer::openServer(id, this, port, handler); + servers[id] = std::move(server); + return id; +} + +u64id_t Network::connectUdp(const std::string& address, int port, const consumer& callback, ClientDatagramCallback handler) { + std::lock_guard lock(connectionsMutex); + + u64id_t id = nextConnection++; + auto socket = SocketUdpConnection::connect(id, address, port, std::move(handler), [id, callback]() { + callback(id); + }); + connections[id] = std::move(socket); + return id; +} + +u64id_t Network::openUdpServer(int port, const ServerDatagramCallback& handler) { + u64id_t id = nextServer++; + auto server = SocketUdpServer::openServer(id, this, port, handler); servers[id] = std::move(server); return id; } @@ -679,7 +917,10 @@ void Network::update() { auto socket = socketiter->second.get(); totalDownload += socket->pullDownload(); totalUpload += socket->pullUpload(); - if (socket->available() == 0 && + if ( + ( socket->getTransportType() == TransportType::UDP || + dynamic_cast(socket)->available() == 0 + ) && socket->getState() == ConnectionState::CLOSED) { socketiter = connections.erase(socketiter); continue; diff --git a/src/network/Network.hpp b/src/network/Network.hpp index 934642fa..ddde70b5 100644 --- a/src/network/Network.hpp +++ b/src/network/Network.hpp @@ -13,6 +13,9 @@ namespace network { using OnResponse = std::function)>; using OnReject = std::function; using ConnectCallback = std::function; + using ServerDatagramCallback = std::function; + using ClientDatagramCallback = std::function; + class Requests { public: @@ -33,8 +36,8 @@ namespace network { long maxSize=0 ) = 0; - virtual size_t getTotalUpload() const = 0; - virtual size_t getTotalDownload() const = 0; + [[nodiscard]] virtual size_t getTotalUpload() const = 0; + [[nodiscard]] virtual size_t getTotalDownload() const = 0; virtual void update() = 0; }; @@ -43,32 +46,82 @@ namespace network { INITIAL, CONNECTING, CONNECTED, CLOSED }; + enum class TransportType { + TCP, UDP + }; + class Connection { public: - virtual ~Connection() {} + virtual ~Connection() = default; - virtual void connect(runnable callback) = 0; - virtual int recv(char* buffer, size_t length) = 0; - virtual int send(const char* buffer, size_t length) = 0; virtual void close(bool discardAll=false) = 0; - virtual int available() = 0; + + virtual int send(const char* buffer, size_t length) = 0; virtual size_t pullUpload() = 0; virtual size_t pullDownload() = 0; - virtual int getPort() const = 0; - virtual std::string getAddress() const = 0; + [[nodiscard]] virtual int getPort() const = 0; + [[nodiscard]] virtual std::string getAddress() const = 0; - virtual ConnectionState getState() const = 0; + [[nodiscard]] virtual ConnectionState getState() const = 0; + + [[nodiscard]] virtual TransportType getTransportType() const noexcept = 0; }; - class TcpServer { + class TcpConnection : public Connection { public: - virtual ~TcpServer() {} - virtual void startListen(ConnectCallback handler) = 0; + ~TcpConnection() override = default; + + virtual void connect(runnable callback) = 0; + virtual int recv(char* buffer, size_t length) = 0; + virtual int available() = 0; + + [[nodiscard]] TransportType getTransportType() const noexcept override { + return TransportType::TCP; + } + }; + + class UdpConnection : public Connection { + public: + ~UdpConnection() override = default; + + virtual void connect(ClientDatagramCallback handler) = 0; + + [[nodiscard]] TransportType getTransportType() const noexcept override { + return TransportType::UDP; + } + }; + + class Server { + public: + virtual ~Server() = default; virtual void close() = 0; virtual bool isOpen() = 0; - virtual int getPort() const = 0; + [[nodiscard]] virtual TransportType getTransportType() const noexcept = 0; + [[nodiscard]] virtual int getPort() const = 0; + }; + + class TcpServer : public Server { + public: + ~TcpServer() override {} + virtual void startListen(ConnectCallback handler) = 0; + + [[nodiscard]] TransportType getTransportType() const noexcept override { + return TransportType::TCP; + } + }; + + class UdpServer : public Server { + public: + ~UdpServer() override {} + virtual void startListen(ServerDatagramCallback handler) = 0; + + virtual void sendTo(const std::string& addr, int port, const char* buffer, size_t length) = 0; + + [[nodiscard]] TransportType getTransportType() const noexcept override { + return TransportType::UDP; + } }; class Network { @@ -78,7 +131,7 @@ namespace network { std::mutex connectionsMutex {}; u64id_t nextConnection = 1; - std::unordered_map> servers; + std::unordered_map> servers; u64id_t nextServer = 1; size_t totalDownload = 0; @@ -103,16 +156,18 @@ namespace network { ); [[nodiscard]] Connection* getConnection(u64id_t id); - [[nodiscard]] TcpServer* getServer(u64id_t id) const; + [[nodiscard]] Server* getServer(u64id_t id) const; - u64id_t connect(const std::string& address, int port, consumer callback); + u64id_t connectTcp(const std::string& address, int port, consumer callback); + u64id_t connectUdp(const std::string& address, int port, const consumer& callback, ClientDatagramCallback handler); - u64id_t openServer(int port, ConnectCallback handler); + u64id_t openTcpServer(int port, ConnectCallback handler); + u64id_t openUdpServer(int port, const ServerDatagramCallback& handler); u64id_t addConnection(const std::shared_ptr& connection); - size_t getTotalUpload() const; - size_t getTotalDownload() const; + [[nodiscard]] size_t getTotalUpload() const; + [[nodiscard]] size_t getTotalDownload() const; void update(); From 1bea391f1d7fb71ddc6095d064a013cd1cdae7d5 Mon Sep 17 00:00:00 2001 From: Onran0 Date: Sun, 24 Aug 2025 08:31:13 +0900 Subject: [PATCH 093/177] fixes --- doc/ru/scripting/builtins/libnetwork.md | 2 +- res/scripts/classes.lua | 8 ++- src/logic/scripting/lua/libs/libnetwork.cpp | 73 ++++++++++++++------- src/network/Network.cpp | 25 +++---- 4 files changed, 64 insertions(+), 44 deletions(-) diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index 0e9a02b1..30bc4135 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -135,7 +135,7 @@ network.udp_open( port: int, -- Функция, вызываемая при получении датаграмы -- В параметры передаётся адрес и порт отправителя, а также сами данные - datagramHandler: function(address: str, port: int, data: Bytearray) + datagramHandler: function(server: DatagramServerSocket, address: str, port: int, data: Bytearray) ) --> DatagramServerSocket ``` diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 30354050..aadbc308 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -90,19 +90,21 @@ network.tcp_connect = function(address, port, callback) end network.udp_open = function (port, datagramHandler) - if type(datagramHandler) ~= 'func' then + if type(datagramHandler) ~= 'function' then error "udp server cannot be opened without datagram handler" end local socket = setmetatable({id=network.__open_udp(port)}, DatagramServerSocket) - _udp_server_callbacks[socket.id] = datagramHandler + _udp_server_callbacks[socket.id] = function(...) + datagramHandler(socket, ...) + end return socket end network.udp_connect = function (address, port, datagramHandler, openCallback) - if type(datagramHandler) ~= 'func' then + if type(datagramHandler) ~= 'function' then error "udp client socket cannot be opened without datagram handler" end diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index dc2eff65..bfa67af6 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -1,8 +1,9 @@ -#include "api_lua.hpp" +#include +#include "api_lua.hpp" +#include "coders/json.hpp" #include "engine/Engine.hpp" #include "network/Network.hpp" -#include "coders/json.hpp" using namespace scripting; @@ -221,6 +222,18 @@ struct NetworkEvent { NetworkEventType type; u64id_t server; u64id_t client; + + NetworkEvent( + NetworkEventType type, + u64id_t server, + u64id_t client + ) { + this->type = type; + this->server = server; + this->client = client; + } + + virtual ~NetworkEvent() = default; }; enum NetworkDatagramSide { @@ -232,10 +245,11 @@ struct NetworkDatagramEvent : NetworkEvent { NetworkDatagramSide side; std::string addr; int port; - char* buffer; + const char* buffer; size_t length; - NetworkDatagramEvent(NetworkEventType datagram, + NetworkDatagramEvent( + NetworkEventType datagram, u64id_t sid, u64id_t cid, NetworkDatagramSide side, @@ -243,16 +257,24 @@ struct NetworkDatagramEvent : NetworkEvent { int port, const char* data, size_t length - ); + ) : NetworkEvent(DATAGRAM, sid, cid) { + this->side = side; + this->addr = addr; + this->port = port; + + buffer = data; + + this->length = length; + } }; -static std::vector events_queue {}; +static std::vector> events_queue {}; static int l_connect_tcp(lua::State* L, network::Network& network) { std::string address = lua::require_string(L, 1); int port = lua::tointeger(L, 2); u64id_t id = network.connectTcp(address, port, [](u64id_t cid) { - events_queue.push_back({CONNECTED_TO_SERVER, 0, cid}); + events_queue.push_back(std::make_unique(CONNECTED_TO_SERVER, 0, cid)); }); return lua::pushinteger(L, id); } @@ -260,7 +282,7 @@ static int l_connect_tcp(lua::State* L, network::Network& network) { static int l_open_tcp(lua::State* L, network::Network& network) { int port = lua::tointeger(L, 1); u64id_t id = network.openTcpServer(port, [](u64id_t sid, u64id_t id) { - events_queue.push_back({CLIENT_CONNECTED, sid, id}); + events_queue.push_back(std::make_unique(CLIENT_CONNECTED, sid, id)); }); return lua::pushinteger(L, id); } @@ -269,17 +291,17 @@ static int l_connect_udp(lua::State* L, network::Network& network) { std::string address = lua::require_string(L, 1); int port = lua::tointeger(L, 2); u64id_t id = network.connectUdp(address, port, [](u64id_t cid) { - events_queue.push_back({CONNECTED_TO_SERVER, 0, cid}); + events_queue.push_back(std::make_unique(CONNECTED_TO_SERVER, 0, cid)); }, [address, port]( u64id_t cid, const char* buffer, size_t length ) { events_queue.push_back( - NetworkDatagramEvent{ + std::make_unique( DATAGRAM, 0, cid, ON_CLIENT, address, port, buffer, length - } + ) ); }); return lua::pushinteger(L, id); @@ -294,10 +316,10 @@ static int l_open_udp(lua::State* L, network::Network& network) { const char* buffer, size_t length) { events_queue.push_back( - NetworkDatagramEvent{ + std::make_unique( DATAGRAM, sid, 0, ON_SERVER, addr, port, buffer, length - } + ) ); }); return lua::pushinteger(L, id); @@ -364,31 +386,32 @@ static int l_get_total_download(lua::State* L, network::Network& network) { static int l_pull_events(lua::State* L, network::Network& network) { lua::createtable(L, events_queue.size(), 0); - for (size_t i = 0; i < events_queue.size(); i++) { - lua::createtable(L, 3, 0); - lua::pushinteger(L, events_queue[i].type); + for (size_t i = 0; i < events_queue.size(); i++) { + const auto* datagramEvent = dynamic_cast(events_queue[i].get()); + + lua::createtable(L, datagramEvent ? 7 : 3, 0); + + lua::pushinteger(L, events_queue[i]->type); lua::rawseti(L, 1); - lua::pushinteger(L, events_queue[i].server); + lua::pushinteger(L, events_queue[i]->server); lua::rawseti(L, 2); - lua::pushinteger(L, events_queue[i].client); + lua::pushinteger(L, events_queue[i]->client); lua::rawseti(L, 3); - if (typeid(events_queue[i]) == typeid(NetworkDatagramEvent)) { - const NetworkDatagramEvent& de = (NetworkDatagramEvent&) events_queue[i]; - - lua::pushstring(L, de.addr); + if (datagramEvent) { + lua::pushstring(L, datagramEvent->addr); lua::rawseti(L, 4); - lua::pushinteger(L, de.port); + lua::pushinteger(L, datagramEvent->port); lua::rawseti(L, 5); - lua::pushinteger(L, de.side); + lua::pushinteger(L, datagramEvent->side); lua::rawseti(L, 6); - lua::pushvalue(L, lua::create_bytearray(L, de.buffer, de.length)); + lua::create_bytearray(L, datagramEvent->buffer, datagramEvent->length); lua::rawseti(L, 7); } diff --git a/src/network/Network.cpp b/src/network/Network.cpp index 92be35a2..234197e4 100644 --- a/src/network/Network.cpp +++ b/src/network/Network.cpp @@ -442,8 +442,6 @@ public: return to_string(addr, false); } - TransportType getTransportType() const override { return TransportType::TCP; } - static std::shared_ptr connect( const std::string& address, int port, runnable callback ) { @@ -611,7 +609,7 @@ public: : id(id), descriptor(descriptor), addr(std::move(addr)) {} ~SocketUdpConnection() override { - close(); + SocketUdpConnection::close(); } static std::shared_ptr connect( @@ -623,18 +621,17 @@ public: ) { SOCKET descriptor = socket(AF_INET, SOCK_DGRAM, 0); if (descriptor == -1) { - throw std::runtime_error("Could not create UDP socket"); + throw std::runtime_error("could not create UDP socket"); } sockaddr_in serverAddr{}; serverAddr.sin_family = AF_INET; if (inet_pton(AF_INET, address.c_str(), &serverAddr.sin_addr) <= 0) { closesocket(descriptor); - throw std::runtime_error("Invalid UDP address: " + address); + throw std::runtime_error("invalid UDP address: " + address); } serverAddr.sin_port = htons(port); - // вызов connect() на UDP сокете → ограничиваем только одним адресом if (::connect(descriptor, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { auto err = handle_socket_error("UDP connect failed"); closesocket(descriptor); @@ -721,10 +718,6 @@ public: [[nodiscard]] ConnectionState getState() const override { return state; } - - [[nodiscard]] TransportType getTransportType() const noexcept override { - return TransportType::UDP; - } }; class SocketUdpServer : public UdpServer { @@ -737,14 +730,16 @@ class SocketUdpServer : public UdpServer { ServerDatagramCallback callback; public: - SocketUdpServer(u64id_t id, Network* network, SOCKET descriptor, int port, ServerDatagramCallback cb) - : id(id), network(network), descriptor(descriptor), port(port), callback(std::move(cb)) {} + SocketUdpServer(u64id_t id, Network* network, SOCKET descriptor, int port) + : id(id), network(network), descriptor(descriptor), port(port) {} ~SocketUdpServer() override { SocketUdpServer::close(); } - void startListen() { + void startListen(ServerDatagramCallback handler) { + callback = std::move(handler); + thread = std::make_unique([this]() { util::Buffer buffer(16384); sockaddr_in clientAddr{}; @@ -806,8 +801,8 @@ public: throw std::runtime_error("Could not bind UDP port " + std::to_string(port)); } - auto server = std::make_shared(id, network, descriptor, port, std::move(handler)); - server->startListen(); + auto server = std::make_shared(id, network, descriptor, port); + server->startListen(std::move(handler)); return server; } }; From 8d79ee349996ef1387a4a06e16471c1195fb29c2 Mon Sep 17 00:00:00 2001 From: Onran0 Date: Sun, 24 Aug 2025 08:38:25 +0900 Subject: [PATCH 094/177] minor fixes --- doc/ru/scripting/builtins/libnetwork.md | 6 +++--- res/scripts/classes.lua | 4 ++-- src/logic/scripting/lua/libs/libnetwork.cpp | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index 30bc4135..c9cc890c 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -104,10 +104,10 @@ server:get_port() --> int network.udp_connect( address: str, port: int, - -- Функция, вызызываемая при получении датаграммы с указанного при открытии сокета адреса и порта + -- Функция, вызываемая при получении датаграммы с указанного при открытии сокета адреса и порта datagramHandler: function(Bytearray), -- Функция, вызываемая после открытия сокета - -- Необязательна, так как в UDP нет handshake + -- Опциональна, так как в UDP нет handshake [опционально] openCallback: function(WriteableSocket), ) --> WriteableSocket ``` @@ -135,7 +135,7 @@ network.udp_open( port: int, -- Функция, вызываемая при получении датаграмы -- В параметры передаётся адрес и порт отправителя, а также сами данные - datagramHandler: function(server: DatagramServerSocket, address: str, port: int, data: Bytearray) + datagramHandler: function(address: str, port: int, data: Bytearray, server: DatagramServerSocket) ) --> DatagramServerSocket ``` diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index aadbc308..e63d5a5a 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -96,8 +96,8 @@ network.udp_open = function (port, datagramHandler) local socket = setmetatable({id=network.__open_udp(port)}, DatagramServerSocket) - _udp_server_callbacks[socket.id] = function(...) - datagramHandler(socket, ...) + _udp_server_callbacks[socket.id] = function(address, port, data) + datagramHandler(address, port, data, socket) end return socket diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index bfa67af6..94390c93 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -1,5 +1,3 @@ -#include - #include "api_lua.hpp" #include "coders/json.hpp" #include "engine/Engine.hpp" From 5b18639d94d6ef304bddfd73cc8b6211bb4d09c8 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Mon, 25 Aug 2025 21:21:05 +0300 Subject: [PATCH 095/177] slightly corrected docs --- doc/en/content-packs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index 53e095b9..10c70afb 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -30,8 +30,8 @@ If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. -Dependency version is indicated by postfix after '@' symbol. -If postfix is not specified, '\*' (any) version will be used. +Dependency version is indicated after '@' symbol. +If version is not specified, '\*' (any) version will be used. Example: 'randutil@1.0' - dependency 'randutil' which requires version 1.0. From 7aa4c33721ab9c0238b8caf49f7305e9d1052ac2 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Tue, 26 Aug 2025 01:52:26 +0300 Subject: [PATCH 096/177] dependency version operators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit а также сделал и вынес Version в ContentPackVersion ну и подправил луа скрипты. требуются тесты --- res/layouts/pages/content.xml.lua | 84 +++++++++++++++++++++++- src/content/ContentPack.cpp | 41 ++++++++++-- src/content/ContentPack.hpp | 10 ++- src/content/ContentPackVersion.cpp | 66 +++++++++++++++++++ src/content/ContentPackVersion.hpp | 51 ++++++++++++++ src/content/PacksManager.cpp | 14 +++- src/logic/scripting/lua/libs/libpack.cpp | 2 +- 7 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 src/content/ContentPackVersion.cpp create mode 100644 src/content/ContentPackVersion.hpp diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 8ce1cebc..1fe5c160 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -176,6 +176,81 @@ function place_pack(panel, packinfo, callback, position_func) end end +local Version = {}; + +function Version.matches_pattern(version) + for _, letter in string.gmatch(version, "%s+") do + if type(letter) ~= "number" or letter ~= "." then + return false; + end + + local t = string.split(version, "."); + + return #t == 2 or #t == 3; + end +end + +function Version.__equal(ver1, ver2) + return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3]; +end + +function Version.__more(ver1, ver2) + if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end; + if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end; + return ver1[3] > ver2[3]; +end + +function Version.__less(ver1, ver2) + return Version.__more(ver2, ver1); +end + +function Version.__more_or_equal(ver1, ver2) + return not Version.__less(ver1, ver2); +end + +function Version.__less_or_equal(ver1, ver2) + return not Version.__more(ver1, ver2); +end + +function Version.compare(op, ver1, ver2) + ver1 = string.split(ver1, "."); + ver2 = string.split(ver2, "."); + + if op == "=" then return Version.__equal(ver1, ver2); + elseif op == ">" then return Version.__more(ver1, ver2); + elseif op == "<" then return Version.__less(ver1, ver2); + elseif op == ">=" then return Version.__more_or_equal(ver1, ver2); + elseif op == "<=" then return Version.__less_or_equal(ver1, ver2); + else return false; end +end + +function Version.parse(version) + local op = string.sub(version, 1, 2); + if op == ">=" or op == "=>" then + return ">=", string.sub(version, #op + 1); + elseif op == "<=" or op == "=<" then + return "<=", string.sub(version, #op + 1); + end + + op = string.sub(version, 1, 1); + if op == ">" or op == "<" then + return op, string.sub(version, #op + 1); + end + + return "=", version; +end + +local function compare_version(dependent_version, actual_version) + if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then + local op, dep_ver = Version.parse_version(dependent_version); + Version.compare(op, dep_ver, actual_version); + elseif dependent_version == "*" or dependent_version == actual_version then + return true; + else + return false; + end +end + function check_dependencies(packinfo) if packinfo.dependencies == nil then return @@ -190,9 +265,14 @@ function check_dependencies(packinfo) ) end + local dep_pack = pack.get_info(depid); - if depver ~= "*" and depver ~= dep_pack.version then - return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); + + if not compare_version(depver, dep_pack.version) then + local op, ver = Version.parse(depver); + + print(string.format("%s: %s !%s %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, op, ver, depid)); + return string.format("%s: %s != %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, ver, depid); end if table.has(packs_installed, packinfo.id) then diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 8c3ea541..49d7ee1f 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -133,14 +133,43 @@ ContentPack ContentPack::read(const io::path& folder) { break; } - std::string depVersion = "*"; - size_t version_pos = depName.rfind("@"); - if (version_pos != std::string::npos){ - depVersion = depName.substr(version_pos + 1); - depName = depName.substr(0, version_pos); + 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 == "<=" || 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 = "*"; + } + } } - pack.dependencies.push_back({level, depName, depVersion}); + pack.dependencies.push_back({level, depName, depVer, depVerOperator}); } } diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index f8bc760c..0069553a 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -20,11 +20,16 @@ public: io::path folder, const std::string& message ); - + std::string getPackId() const; io::path getFolder() const; }; +enum class DependencyVersionOperator { + equal, more, less, + more_or_equal, less_or_equal +}; + enum class DependencyLevel { required, // dependency must be installed optional, // dependency will be installed if found @@ -35,7 +40,8 @@ enum class DependencyLevel { struct DependencyPack { DependencyLevel level; std::string id; - std::string verison; + std::string version; + std::string op; }; struct ContentPackStats { diff --git a/src/content/ContentPackVersion.cpp b/src/content/ContentPackVersion.cpp new file mode 100644 index 00000000..b8a0564b --- /dev/null +++ b/src/content/ContentPackVersion.cpp @@ -0,0 +1,66 @@ +#include "ContentPackVersion.hpp" + +#include +#include +#include + +#include "coders/commons.hpp" + +Version::Version(const std::string& version) { + major = 0; + minor = 0; + patch = 0; + + std::vector parts; + + std::stringstream ss(version); + std::string part; + while (std::getline(ss, part, '.')) { + if (!part.empty()) { + parts.push_back(std::stoi(part)); + } + } + + if (parts.size() > 0) major = parts[0]; + if (parts.size() > 1) minor = parts[1]; + if (parts.size() > 2) patch = parts[2]; +} + +DependencyVersionOperator Version::string_to_operator(const std::string& op) { + if (op == "=") + return DependencyVersionOperator::equal; + else if (op == ">") + return DependencyVersionOperator::more; + else if (op == "<") + return DependencyVersionOperator::less; + else if (op == ">=" || op == "=>") + return DependencyVersionOperator::more_or_equal; + else if (op == "<=" || op == "=<") + return DependencyVersionOperator::less_or_equal; + else return DependencyVersionOperator::equal; +} + +bool isNumber(const std::string& s) { + return !s.empty() && std::all_of(s.begin(), s.end(), ::is_digit); +} + +bool Version::matches_pattern(const std::string& version) { + for (char c : version) { + if (!isdigit(c) && c != '.') { + return false; + } + } + + std::stringstream ss(version); + + std::vector parts; + std::string part; + while (std::getline(ss, part, '.')) { + if (part.empty()) return false; + if (!isNumber(part)) return false; + + parts.push_back(part); + } + + return parts.size() == 2 || parts.size() == 3; +} \ No newline at end of file diff --git a/src/content/ContentPackVersion.hpp b/src/content/ContentPackVersion.hpp new file mode 100644 index 00000000..ef298b9e --- /dev/null +++ b/src/content/ContentPackVersion.hpp @@ -0,0 +1,51 @@ +#include + +#include "content/ContentPack.hpp" + +class Version { + public: + int major; + int minor; + int patch; + + Version(const std::string& version); + + bool operator==(const Version& other) const { + return major == other.major && minor == other.minor && patch == other.patch; + } + + bool operator<(const Version& other) const { + if (major != other.major) return major < other.major; + if (minor != other.minor) return minor < other.minor; + return patch < other.patch; + } + + + bool operator>(const Version& other) const { + return other < *this; + } + + bool operator>=(const Version& other) const { + return !(*this < other); + } + + bool operator<=(const Version& other) const { + return !(*this > other); + } + + bool process_operator(const std::string& op, const Version& other) const { + auto dep_op = Version::string_to_operator(op); + + switch(dep_op) { + case DependencyVersionOperator::equal: return *this == other; + case DependencyVersionOperator::more: return *this > other; + case DependencyVersionOperator::less: return *this < other; + case DependencyVersionOperator::less_or_equal: return *this <= other; + case DependencyVersionOperator::more_or_equal: return *this >= other; + default: return false; + } + } + + static DependencyVersionOperator string_to_operator(const std::string& op); + static bool matches_pattern(const std::string& version); +}; \ No newline at end of file diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index c898d590..1e2ed49f 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -3,6 +3,7 @@ #include #include +#include "ContentPackVersion.hpp" #include "util/listutil.hpp" PacksManager::PacksManager() = default; @@ -105,12 +106,21 @@ static bool resolve_dependencies( // added continue; } - if (dep.verison == "*" || dep.verison == found->second.version){ + + auto dep_pack = found -> second; + + if (Version::matches_pattern(dep.version) && Version::matches_pattern(dep_pack.version) + && Version(dep_pack.version) + .process_operator(dep.op, Version(dep.version)) + ) { // dependency pack version meets the required one continue; + } else if (dep.version == "*" || dep.version == dep_pack.version){ + // fallback: dependency pack version also meets required one + continue; } else { throw contentpack_error( - dep.id, io::path(), "does not meet required version '" + dep.verison +"' of '" + pack->id + "'" + dep.id, io::path(), "does not meet required version '" + dep.op + dep.version +"' of '" + pack->id + "'" ); } diff --git a/src/logic/scripting/lua/libs/libpack.cpp b/src/logic/scripting/lua/libs/libpack.cpp index be38fcb2..fc5e2252 100644 --- a/src/logic/scripting/lua/libs/libpack.cpp +++ b/src/logic/scripting/lua/libs/libpack.cpp @@ -115,7 +115,7 @@ static int l_pack_get_info( throw std::runtime_error(""); } - lua::pushfstring(L, "%s%s@%s", prefix.c_str(), dpack.id.c_str(), dpack.verison.c_str()); + lua::pushfstring(L, "%s%s@%s%s", prefix.c_str(), dpack.id.c_str(), dpack.op.c_str(), dpack.version.c_str()); lua::rawseti(L, i + 1); } lua::setfield(L, "dependencies"); From 01b3229aac8a07be7eadf85f8997d00c9a422930 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Tue, 26 Aug 2025 01:59:00 +0300 Subject: [PATCH 097/177] fix: match wrong magic symbol --- res/layouts/pages/content.xml.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 1fe5c160..8ed37630 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -179,7 +179,7 @@ end local Version = {}; function Version.matches_pattern(version) - for _, letter in string.gmatch(version, "%s+") do + for _, letter in string.gmatch(version, "%.+") do if type(letter) ~= "number" or letter ~= "." then return false; end From a0c66d56d89aeabb2462b7e732b59f92e79f2464 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Tue, 26 Aug 2025 02:10:16 +0300 Subject: [PATCH 098/177] docs for operators --- doc/en/content-packs.md | 4 ++-- doc/ru/content-packs.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index 10c70afb..a41f95f9 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -30,10 +30,10 @@ If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. -Dependency version is indicated after '@' symbol. +Dependency version is indicated after '@' symbol and have operators to restrict acceptable versions. If version is not specified, '\*' (any) version will be used. -Example: 'randutil@1.0' - dependency 'randutil' which requires version 1.0. +Example: 'randutil@>=1.0' - dependency 'randutil' which requires version 1.0 or newer. Example: ```json diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 34ccaab7..4245555f 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -30,12 +30,12 @@ - '~' - слабая зависимость Отсутствие префикса интерпретируется как '!'. -Пример: '~randutil@1.0' - слабая зависимость 'randutil'. +Пример: '~randutil' - слабая зависимость 'randutil'. -Версии зависимостей указываются с помощью постфикса через '@'. +Версии зависимостей указываются после '@' и имеют операторы для ограничения допустимых версий. Отсутствие версии зависимости интерпретируется как '\*', т.е. любая версия. -Пример: 'randutil@1.0' - зависимость 'randutil' версии 1.0. +Пример: 'randutil@>=1.0' - зависимость 'randutil' версии 1.0 и старше. Пример: ```json From ac1623f86c61c6260ff806926c20ce6208ef0008 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 27 Aug 2025 19:31:34 +0300 Subject: [PATCH 099/177] add 'Show Paths' checkbox --- src/frontend/debug_panel.cpp | 14 ++++++++++++++ src/graphics/render/DebugLinesRenderer.cpp | 8 ++++++-- src/graphics/render/DebugLinesRenderer.hpp | 2 ++ src/graphics/render/WorldRenderer.hpp | 1 - 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 5e238f42..10c6b85c 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -12,6 +12,7 @@ #include "graphics/render/WorldRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp" +#include "graphics/render/DebugLinesRenderer.hpp" #include "logic/scripting/scripting.hpp" #include "network/Network.hpp" #include "objects/Player.hpp" @@ -45,6 +46,7 @@ static std::shared_ptr