From 81d21d6e8b5c4f3437bf78f318088d75e81f3b19 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 11 Oct 2025 00:11:25 +0300 Subject: [PATCH 001/155] refactor lua usertypes --- src/logic/scripting/lua/libs/libbjson.cpp | 1 - .../scripting/lua/libs/libgeneration.cpp | 2 +- src/logic/scripting/lua/libs/libgui.cpp | 1 + src/logic/scripting/lua/libs/libutf8.cpp | 1 - src/logic/scripting/lua/lua_commons.hpp | 5 + src/logic/scripting/lua/lua_custom_types.hpp | 134 ------------------ src/logic/scripting/lua/lua_engine.cpp | 5 +- src/logic/scripting/lua/lua_util.hpp | 1 - .../lua/usertypes/lua_type_canvas.cpp | 5 +- .../lua/usertypes/lua_type_canvas.hpp | 46 ++++++ .../lua/usertypes/lua_type_heightmap.cpp | 13 +- .../lua/usertypes/lua_type_heightmap.hpp | 44 ++++++ .../lua/usertypes/lua_type_random.cpp | 2 +- .../lua/usertypes/lua_type_random.hpp | 23 +++ .../lua/usertypes/lua_type_voxelfragment.cpp | 3 +- .../lua/usertypes/lua_type_voxelfragment.hpp | 29 ++++ src/logic/scripting/scripting.cpp | 1 - .../scripting/scripting_world_generation.cpp | 2 +- 18 files changed, 166 insertions(+), 152 deletions(-) delete mode 100644 src/logic/scripting/lua/lua_custom_types.hpp create mode 100644 src/logic/scripting/lua/usertypes/lua_type_canvas.hpp create mode 100644 src/logic/scripting/lua/usertypes/lua_type_heightmap.hpp create mode 100644 src/logic/scripting/lua/usertypes/lua_type_random.hpp create mode 100644 src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp diff --git a/src/logic/scripting/lua/libs/libbjson.cpp b/src/logic/scripting/lua/libs/libbjson.cpp index 71498dbcd..8c28f04e1 100644 --- a/src/logic/scripting/lua/libs/libbjson.cpp +++ b/src/logic/scripting/lua/libs/libbjson.cpp @@ -1,7 +1,6 @@ #include "coders/binary_json.hpp" #include "api_lua.hpp" #include "util/Buffer.hpp" -#include "../lua_custom_types.hpp" static int l_tobytes(lua::State* L) { auto value = lua::tovalue(L, 1); diff --git a/src/logic/scripting/lua/libs/libgeneration.cpp b/src/logic/scripting/lua/libs/libgeneration.cpp index e215b4fba..15bdad5cc 100644 --- a/src/logic/scripting/lua/libs/libgeneration.cpp +++ b/src/logic/scripting/lua/libs/libgeneration.cpp @@ -9,7 +9,7 @@ #include "content/Content.hpp" #include "content/ContentControl.hpp" #include "engine/Engine.hpp" -#include "../lua_custom_types.hpp" +#include "../usertypes/lua_type_voxelfragment.hpp" using namespace scripting; diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 6395dc5ba..fc3732a1c 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -22,6 +22,7 @@ #include "items/Inventories.hpp" #include "util/stringutil.hpp" #include "world/Level.hpp" +#include "../usertypes/lua_type_canvas.hpp" using namespace gui; using namespace scripting; diff --git a/src/logic/scripting/lua/libs/libutf8.cpp b/src/logic/scripting/lua/libs/libutf8.cpp index ec746caaa..e6538c2ec 100644 --- a/src/logic/scripting/lua/libs/libutf8.cpp +++ b/src/logic/scripting/lua/libs/libutf8.cpp @@ -3,7 +3,6 @@ #include #include -#include "../lua_custom_types.hpp" #include "util/stringutil.hpp" static int l_tobytes(lua::State* L) { diff --git a/src/logic/scripting/lua/lua_commons.hpp b/src/logic/scripting/lua/lua_commons.hpp index f272dbf03..bb3163c86 100644 --- a/src/logic/scripting/lua/lua_commons.hpp +++ b/src/logic/scripting/lua/lua_commons.hpp @@ -48,4 +48,9 @@ namespace lua { void log_error(const std::string& text); + class Userdata { + public: + virtual ~Userdata() {}; + virtual const std::string& getTypeName() const = 0; + }; } diff --git a/src/logic/scripting/lua/lua_custom_types.hpp b/src/logic/scripting/lua/lua_custom_types.hpp deleted file mode 100644 index c17a78bca..000000000 --- a/src/logic/scripting/lua/lua_custom_types.hpp +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "lua_commons.hpp" - -struct fnl_state; -class Heightmap; -class VoxelFragment; -class Texture; -class ImageData; - -namespace lua { - class Userdata { - public: - virtual ~Userdata() {}; - virtual const std::string& getTypeName() const = 0; - }; - - class LuaHeightmap : public Userdata { - std::shared_ptr map; - std::unique_ptr noise; - public: - LuaHeightmap(const std::shared_ptr& map); - LuaHeightmap(uint width, uint height); - - virtual ~LuaHeightmap(); - - uint getWidth() const; - - uint getHeight() const; - - float* getValues(); - - const float* getValues() const; - - const std::string& getTypeName() const override { - return TYPENAME; - } - - const std::shared_ptr& getHeightmap() const { - return map; - } - - fnl_state* getNoise() { - return noise.get(); - } - - void setSeed(int64_t seed); - - static int createMetatable(lua::State*); - inline static std::string TYPENAME = "Heightmap"; - }; - static_assert(!std::is_abstract()); - - class LuaVoxelFragment : public Userdata { - std::array, 4> fragmentVariants; - public: - LuaVoxelFragment( - std::array, 4> fragmentVariants - ); - - virtual ~LuaVoxelFragment(); - - std::shared_ptr getFragment(size_t rotation) const { - return fragmentVariants.at(rotation & 0b11); - } - - const std::string& getTypeName() const override { - return TYPENAME; - } - - static int createMetatable(lua::State*); - inline static std::string TYPENAME = "VoxelFragment"; - }; - static_assert(!std::is_abstract()); - - class LuaCanvas : public Userdata { - public: - explicit LuaCanvas( - std::shared_ptr inTexture, - std::shared_ptr inData - ); - ~LuaCanvas() override = default; - - const std::string& getTypeName() const override { - return TYPENAME; - } - - [[nodiscard]] auto& texture() const { - return *mTexture; - } - - [[nodiscard]] auto& data() const { - return *mData; - } - - [[nodiscard]] bool hasTexture() const { - return mTexture != nullptr; - } - - auto shareTexture() const { - return mTexture; - } - - void createTexture(); - - static int createMetatable(lua::State*); - inline static std::string TYPENAME = "Canvas"; - private: - std::shared_ptr mTexture; // nullable - std::shared_ptr mData; - }; - static_assert(!std::is_abstract()); - - class LuaRandom : public Userdata { - public: - std::mt19937 rng; - - explicit LuaRandom(uint64_t seed) : rng(seed) {} - virtual ~LuaRandom() override = default; - - const std::string& getTypeName() const override { - return TYPENAME; - } - - static int createMetatable(lua::State*); - inline static std::string TYPENAME = "__vc_Random"; - }; - static_assert(!std::is_abstract()); -} diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 08d2eb85e..05adb354d 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -8,7 +8,10 @@ #include "debug/Logger.hpp" #include "util/stringutil.hpp" #include "libs/api_lua.hpp" -#include "lua_custom_types.hpp" +#include "usertypes/lua_type_heightmap.hpp" +#include "usertypes/lua_type_voxelfragment.hpp" +#include "usertypes/lua_type_canvas.hpp" +#include "usertypes/lua_type_random.hpp" #include "engine/Engine.hpp" static debug::Logger logger("lua-state"); diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index a8fb246e7..29f25f341 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -6,7 +6,6 @@ #include #include "data/dv.hpp" -#include "lua_custom_types.hpp" #include "lua_wrapper.hpp" #define GLM_ENABLE_EXPERIMENTAL #include diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index ec0869044..3343fe711 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -1,12 +1,13 @@ -#include +#include "lua_type_canvas.hpp" #include "graphics/core/ImageData.hpp" #include "graphics/core/Texture.hpp" -#include "logic/scripting/lua/lua_custom_types.hpp" #include "logic/scripting/lua/lua_util.hpp" #include "engine/Engine.hpp" #include "assets/Assets.hpp" +#include + using namespace lua; LuaCanvas::LuaCanvas( diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.hpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.hpp new file mode 100644 index 000000000..43fbbd108 --- /dev/null +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "../lua_commons.hpp" + +class Texture; +class ImageData; + +namespace lua { + class LuaCanvas : public Userdata { + public: + explicit LuaCanvas( + std::shared_ptr inTexture, + std::shared_ptr inData + ); + ~LuaCanvas() override = default; + + const std::string& getTypeName() const override { + return TYPENAME; + } + + [[nodiscard]] auto& texture() const { + return *mTexture; + } + + [[nodiscard]] auto& data() const { + return *mData; + } + + [[nodiscard]] bool hasTexture() const { + return mTexture != nullptr; + } + + auto shareTexture() const { + return mTexture; + } + + void createTexture(); + + static int createMetatable(lua::State*); + inline static std::string TYPENAME = "Canvas"; + private: + std::shared_ptr mTexture; // nullable + std::shared_ptr mData; + }; + static_assert(!std::is_abstract()); +} diff --git a/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp b/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp index 290e13456..4e2f05a91 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp @@ -1,9 +1,4 @@ -#include "../lua_custom_types.hpp" - -#include -#include -#include -#include +#include "lua_type_heightmap.hpp" #include "util/functional_util.hpp" #define FNL_IMPL @@ -14,6 +9,12 @@ #include "maths/Heightmap.hpp" #include "engine/Engine.hpp" #include "../lua_util.hpp" +#include "lua_type_heightmap.hpp" + +#include +#include +#include +#include using namespace lua; diff --git a/src/logic/scripting/lua/usertypes/lua_type_heightmap.hpp b/src/logic/scripting/lua/usertypes/lua_type_heightmap.hpp new file mode 100644 index 000000000..4109be675 --- /dev/null +++ b/src/logic/scripting/lua/usertypes/lua_type_heightmap.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "../lua_commons.hpp" + +struct fnl_state; +class Heightmap; + +namespace lua { + class LuaHeightmap : public Userdata { + std::shared_ptr map; + std::unique_ptr noise; + public: + LuaHeightmap(const std::shared_ptr& map); + LuaHeightmap(uint width, uint height); + + virtual ~LuaHeightmap(); + + uint getWidth() const; + + uint getHeight() const; + + float* getValues(); + + const float* getValues() const; + + const std::string& getTypeName() const override { + return TYPENAME; + } + + const std::shared_ptr& getHeightmap() const { + return map; + } + + fnl_state* getNoise() { + return noise.get(); + } + + void setSeed(int64_t seed); + + static int createMetatable(lua::State*); + inline static std::string TYPENAME = "Heightmap"; + }; + static_assert(!std::is_abstract()); +} diff --git a/src/logic/scripting/lua/usertypes/lua_type_random.cpp b/src/logic/scripting/lua/usertypes/lua_type_random.cpp index 99177056e..bdbe4a1a1 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_random.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_random.cpp @@ -1,5 +1,5 @@ -#include "../lua_custom_types.hpp" #include "../lua_util.hpp" +#include "lua_type_random.hpp" #include diff --git a/src/logic/scripting/lua/usertypes/lua_type_random.hpp b/src/logic/scripting/lua/usertypes/lua_type_random.hpp new file mode 100644 index 000000000..06ab24f8a --- /dev/null +++ b/src/logic/scripting/lua/usertypes/lua_type_random.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "../lua_commons.hpp" + +#include + +namespace lua { + class LuaRandom : public Userdata { + public: + std::mt19937 rng; + + explicit LuaRandom(uint64_t seed) : rng(seed) {} + virtual ~LuaRandom() override = default; + + const std::string& getTypeName() const override { + return TYPENAME; + } + + static int createMetatable(lua::State*); + inline static std::string TYPENAME = "__vc_Random"; + }; + static_assert(!std::is_abstract()); +} diff --git a/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.cpp b/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.cpp index 271a2a5a6..17cf37d1a 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.cpp @@ -1,7 +1,6 @@ -#include "../lua_custom_types.hpp" +#include "lua_type_voxelfragment.hpp" #include "../lua_util.hpp" - #include "world/generator/VoxelFragment.hpp" #include "util/stringutil.hpp" #include "world/Level.hpp" diff --git a/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp b/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp new file mode 100644 index 000000000..789076cce --- /dev/null +++ b/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "../lua_commons.hpp" + +class VoxelFragment; + +namespace lua { + class LuaVoxelFragment : public Userdata { + std::array, 4> fragmentVariants; + public: + LuaVoxelFragment( + std::array, 4> fragmentVariants + ); + + virtual ~LuaVoxelFragment(); + + std::shared_ptr getFragment(size_t rotation) const { + return fragmentVariants.at(rotation & 0b11); + } + + const std::string& getTypeName() const override { + return TYPENAME; + } + + static int createMetatable(lua::State*); + inline static std::string TYPENAME = "VoxelFragment"; + }; + static_assert(!std::is_abstract()); +} diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 997b5f82c..a4eccbe3b 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -17,7 +17,6 @@ #include "logic/BlocksController.hpp" #include "logic/LevelController.hpp" #include "lua/lua_engine.hpp" -#include "lua/lua_custom_types.hpp" #include "maths/Heightmap.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" diff --git a/src/logic/scripting/scripting_world_generation.cpp b/src/logic/scripting/scripting_world_generation.cpp index bf6c1dd10..3199b46c3 100644 --- a/src/logic/scripting/scripting_world_generation.cpp +++ b/src/logic/scripting/scripting_world_generation.cpp @@ -6,7 +6,7 @@ #include "scripting_commons.hpp" #include "typedefs.hpp" #include "lua/lua_engine.hpp" -#include "lua/lua_custom_types.hpp" +#include "lua/usertypes/lua_type_heightmap.hpp" #include "content/Content.hpp" #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" From 53b89cdb8e59162ad914d3cab30e5f771de0aaf2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 11 Oct 2025 00:34:16 +0300 Subject: [PATCH 002/155] fix windows build --- src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp b/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp index 789076cce..21c41c045 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp +++ b/src/logic/scripting/lua/usertypes/lua_type_voxelfragment.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "../lua_commons.hpp" class VoxelFragment; From 4b19c29a29621853a858137e51ea2814e784bf83 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 13 Oct 2025 23:34:56 +0300 Subject: [PATCH 003/155] add util::span --- src/util/span.hpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/util/span.hpp diff --git a/src/util/span.hpp b/src/util/span.hpp new file mode 100644 index 000000000..a9ab79b26 --- /dev/null +++ b/src/util/span.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace util { + template + class span { + public: + constexpr span(const T* ptr, size_t length) + : ptr(ptr), length(length) {} + + const T& operator[](size_t index) const { + return ptr[index]; + } + + const T& at(size_t index) const { + if (index >= length) { + throw std::out_of_range(); + } + return ptr[index]; + } + + auto begin() const { + return ptr; + } + + auto end() const { + return ptr + length; + } + + const T* data() const { + return ptr; + } + + size_t size() const { + return length; + } + private: + const T* ptr; + size_t length; + }; +} From 31137f83b6d563d857b8e640d7dd31ed02dbea03 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 14 Oct 2025 00:50:23 +0300 Subject: [PATCH 004/155] add MemoryPCMStream --- src/audio/MemoryPCMStream.cpp | 63 +++++++++++++++++++++++++++++++++++ src/audio/MemoryPCMStream.hpp | 42 +++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/audio/MemoryPCMStream.cpp create mode 100644 src/audio/MemoryPCMStream.hpp diff --git a/src/audio/MemoryPCMStream.cpp b/src/audio/MemoryPCMStream.cpp new file mode 100644 index 000000000..7fcf4b0ce --- /dev/null +++ b/src/audio/MemoryPCMStream.cpp @@ -0,0 +1,63 @@ +#include "MemoryPCMStream.hpp" + +#include + +using namespace audio; + +MemoryPCMStream::MemoryPCMStream( + uint sampleRate, uint channels, uint bitsPerSample +) + : sampleRate(sampleRate), channels(channels), bitsPerSample(bitsPerSample) { +} + +void MemoryPCMStream::feed(util::span bytes) { + buffer.insert(buffer.end(), bytes.begin(), bytes.end()); +} + +bool MemoryPCMStream::isOpen() const { + return open; +} + +void MemoryPCMStream::close() { + open = false; + buffer = {}; +} + +size_t MemoryPCMStream::read(char* dst, size_t bufferSize) { + if (!open) { + return PCMStream::ERROR; + } + if (buffer.empty()) { + return 0; + } + size_t count = std::min(bufferSize, buffer.size()); + std::memcpy(dst, buffer.data(), count); + buffer.erase(buffer.begin(), buffer.begin() + count); + return count; +} + +size_t MemoryPCMStream::getTotalSamples() const { + return 0; +} + +duration_t MemoryPCMStream::getTotalDuration() const { + return 0.0; +} + +uint MemoryPCMStream::getChannels() const { + return channels; +} + +uint MemoryPCMStream::getSampleRate() const { + return sampleRate; +} + +uint MemoryPCMStream::getBitsPerSample() const { + return bitsPerSample; +} + +bool MemoryPCMStream::isSeekable() const { + return false; +} + +void MemoryPCMStream::seek(size_t position) {} diff --git a/src/audio/MemoryPCMStream.hpp b/src/audio/MemoryPCMStream.hpp new file mode 100644 index 000000000..1f94d27e7 --- /dev/null +++ b/src/audio/MemoryPCMStream.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "audio.hpp" +#include "util/span.hpp" + +namespace audio { + class MemoryPCMStream : public PCMStream { + public: + MemoryPCMStream(uint sampleRate, uint channels, uint bitsPerSample); + + void feed(util::span bytes); + + bool isOpen() const override; + + void close() override; + + size_t read(char* buffer, size_t bufferSize) override; + + size_t getTotalSamples() const override; + + duration_t getTotalDuration() const override; + + uint getChannels() const override; + + uint getSampleRate() const override; + + uint getBitsPerSample() const override; + + bool isSeekable() const override; + + void seek(size_t position) override; + private: + uint sampleRate; + uint channels; + uint bitsPerSample; + bool open = true; + + std::vector buffer; + }; +} From 84f087049dfcc4cf25f25a87890d31a9934bb857 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 14 Oct 2025 00:51:04 +0300 Subject: [PATCH 005/155] add test PCMStream lua use --- res/scripts/hud.lua | 11 +++ src/audio/audio.hpp | 4 + src/logic/scripting/lua/libs/libaudio.cpp | 20 ++++ src/logic/scripting/lua/lua_engine.cpp | 2 + .../lua/usertypes/lua_type_pcmstream.cpp | 94 +++++++++++++++++++ .../lua/usertypes/lua_type_pcmstream.hpp | 26 +++++ 6 files changed, 157 insertions(+) create mode 100644 src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp create mode 100644 src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 90883d94d..cec5fc071 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -116,6 +116,17 @@ function on_hud_open() configure_SSAO() hud.default_hand_controller = update_hand + + local stream = PCMStream(44100, 1, 8) + stream:share("test-stream") + local bytes = Bytearray(44100 * 16) + for i=1,#bytes do + local x = math.sin(i * 0.08) * 127 + 128 + bytes[i] = x + end + stream:feed(bytes) + + audio.play_stream_2d("test-stream", 0.05, 1.0) end function on_hud_render() diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 5893c3345..a3553b38d 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -121,6 +121,10 @@ namespace audio { /// (always equals bufferSize if seekable and looped) virtual size_t readFully(char* buffer, size_t bufferSize, bool loop); + /// @brief Read available data to buffer + /// @param buffer destination buffer + /// @param bufferSize destination buffer size + /// @return count of received bytes or PCMStream::ERROR virtual size_t read(char* buffer, size_t bufferSize) = 0; /// @brief Close stream diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index db868c57f..4d4ad6a64 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -67,6 +67,26 @@ inline audio::speakerid_t play_stream( if (channel == -1) { return 0; } + if (!scripting::engine->isHeadless()) { + auto assets = scripting::engine->getAssets(); + + auto stream = assets->getShared(filename); + if (stream) { + return audio::play( + audio::open_stream(std::move(stream), true), + glm::vec3( + static_cast(x), + static_cast(y), + static_cast(z) + ), + relative, + volume, + pitch, + loop, + channel + ); + } + } io::path file; if (std::strchr(filename, ':')) { file = std::string(filename); diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 05adb354d..c4507bb13 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -12,6 +12,7 @@ #include "usertypes/lua_type_voxelfragment.hpp" #include "usertypes/lua_type_canvas.hpp" #include "usertypes/lua_type_random.hpp" +#include "usertypes/lua_type_pcmstream.hpp" #include "engine/Engine.hpp" static debug::Logger logger("lua-state"); @@ -130,6 +131,7 @@ void lua::init_state(State* L, StateType stateType) { newusertype(L); newusertype(L); newusertype(L); + newusertype(L); } void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { diff --git a/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp new file mode 100644 index 000000000..04c585cfa --- /dev/null +++ b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp @@ -0,0 +1,94 @@ +#include "../lua_util.hpp" +#include "lua_type_pcmstream.hpp" +#include "assets/Assets.hpp" +#include "audio/MemoryPCMStream.hpp" +#include "engine/Engine.hpp" + +using namespace lua; +using namespace audio; +using namespace scripting; + +LuaPCMStream::LuaPCMStream(std::shared_ptr&& stream) + : stream(std::move(stream)) { +} + +LuaPCMStream::~LuaPCMStream() = default; + +const std::shared_ptr& LuaPCMStream::getStream() const { + return stream; +} + +#include + +static int l_feed(lua::State* L) { + std::cout << "feed" << std::endl; + auto stream = touserdata(L, 1); + if (stream == nullptr) { + return 0; + } + auto bytes = bytearray_as_string(L, 2); + stream->getStream()->feed( + {reinterpret_cast(bytes.data()), bytes.size()} + ); + return 0; +} + +static int l_share(lua::State* L) { + auto stream = touserdata(L, 1); + if (stream == nullptr) { + return 0; + } + auto alias = require_lstring(L, 2); + if (engine->isHeadless()) { + return 0; + } + auto assets = engine->getAssets(); + assets->store(stream->getStream(), std::string(alias)); + return 0; +} + +static std::unordered_map methods { + {"feed", lua::wrap}, + {"share", lua::wrap}, +}; + +static int l_meta_meta_call(lua::State* L) { + auto sampleRate = touinteger(L, 2); + auto channels = touinteger(L, 3); + auto bitsPerSample = touinteger(L, 4); + auto stream = + std::make_shared(sampleRate, channels, bitsPerSample); + return newuserdata(L, std::move(stream)); +} + +static int l_meta_tostring(lua::State* L) { + return pushstring(L, "PCMStream"); +} + +static int l_meta_index(lua::State* L) { + auto stream = touserdata(L, 1); + if (stream == nullptr) { + return 0; + } + if (isstring(L, 2)) { + auto found = methods.find(tostring(L, 2)); + if (found != methods.end()) { + return pushcfunction(L, found->second); + } + } + return 0; +} + +int LuaPCMStream::createMetatable(lua::State* L) { + createtable(L, 0, 3); + pushcfunction(L, lua::wrap); + setfield(L, "__tostring"); + pushcfunction(L, lua::wrap); + setfield(L, "__index"); + + createtable(L, 0, 1); + pushcfunction(L, lua::wrap); + setfield(L, "__call"); + setmetatable(L); + return 1; +} diff --git a/src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp new file mode 100644 index 000000000..479796931 --- /dev/null +++ b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../lua_commons.hpp" + +namespace audio { + class MemoryPCMStream; +} + +namespace lua { + class LuaPCMStream : public Userdata { + public: + explicit LuaPCMStream(std::shared_ptr&& stream); + virtual ~LuaPCMStream() override; + + const std::shared_ptr& getStream() const; + + const std::string& getTypeName() const override { + return TYPENAME; + } + static int createMetatable(lua::State*); + inline static std::string TYPENAME = "PCMStream"; + private: + std::shared_ptr stream; + }; + static_assert(!std::is_abstract()); +} From 2b72f87c6488dee0a5cc899ee753148b07c1b2e4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 14 Oct 2025 00:53:51 +0300 Subject: [PATCH 006/155] fix --- src/util/span.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/span.hpp b/src/util/span.hpp index a9ab79b26..7c97f5ac0 100644 --- a/src/util/span.hpp +++ b/src/util/span.hpp @@ -15,7 +15,7 @@ namespace util { const T& at(size_t index) const { if (index >= length) { - throw std::out_of_range(); + throw std::out_of_range("index is out of range"); } return ptr[index]; } From 27416ab0cd8508324aa3d4fab8c5c5bd4b619275 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 14 Oct 2025 12:43:51 +0300 Subject: [PATCH 007/155] add audio::InputDevice --- src/audio/AL/ALAudio.cpp | 47 ++++++++++++++++++++++++++++++++++++++++ src/audio/AL/ALAudio.hpp | 25 +++++++++++++++++++++ src/audio/NoAudio.hpp | 7 ++++++ src/audio/audio.hpp | 17 +++++++++++++++ 4 files changed, 96 insertions(+) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 7f28882b6..92dd209a0 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -37,6 +37,38 @@ std::unique_ptr ALSound::newInstance(int priority, int channel) const { return speaker; } +ALInputDevice::ALInputDevice( + ALAudio* al, ALCdevice* device, uint channels, uint bitsPerSample +) + : al(al), device(device), channels(channels), bitsPerSample(bitsPerSample) { +} + +ALInputDevice::~ALInputDevice() { + alcCaptureCloseDevice(device); +} + +void ALInputDevice::startCapture() { + AL_CHECK(alcCaptureStart(device)); +} + +void ALInputDevice::stopCapture() { + AL_CHECK(alcCaptureStop(device)); +} + +uint ALInputDevice::getChannels() const { + return channels; +} + +size_t ALInputDevice::read(char* buffer, size_t bufferSize) { + ALCint samplesCount; + AL_CHECK(alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, 1, &samplesCount)); + size_t samplesRead = std::min( + samplesCount, bufferSize / channels / (bitsPerSample >> 3) + ); + AL_CHECK(alcCaptureSamples(device, buffer, samplesRead)); + return samplesRead; +} + ALStream::ALStream( ALAudio* al, std::shared_ptr source, bool keepSource ) @@ -411,6 +443,21 @@ std::unique_ptr ALAudio::openStream( return std::make_unique(this, stream, keepSource); } +std::unique_ptr ALAudio::openInputDevice( + uint sampleRate, uint channels, uint bitsPerSample +) { + uint bps = bitsPerSample >> 3; + AL_CHECK( + ALCdevice* device = alcCaptureOpenDevice( + nullptr, + sampleRate, + AL::to_al_format(channels, bps), + sampleRate * channels * bps + ) + ); + return std::make_unique(this, device, channels, bps); +} + std::unique_ptr ALAudio::create() { ALCdevice* device = alcOpenDevice(nullptr); if (device == nullptr) return nullptr; diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 7e0f0c29d..d04d45b66 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -82,6 +82,26 @@ namespace audio { static inline constexpr uint STREAM_BUFFERS = 3; }; + class ALInputDevice : public InputDevice { + public: + ALInputDevice( + ALAudio* al, ALCdevice* device, uint channels, uint bitsPerSample + ); + ~ALInputDevice() override; + + void startCapture() override; + void stopCapture() override; + + uint getChannels() const override; + + size_t read(char* buffer, size_t bufferSize) override; + private: + ALAudio* al; + ALCdevice* device; + uint channels; + uint bitsPerSample; + }; + /// @brief AL source adapter class ALSpeaker : public Speaker { ALAudio* al; @@ -157,10 +177,15 @@ namespace audio { std::unique_ptr createSound( std::shared_ptr pcm, bool keepPCM ) override; + std::unique_ptr openStream( std::shared_ptr stream, bool keepSource ) override; + std::unique_ptr openInputDevice( + uint sampleRate, uint channels, uint bitsPerSample + ) override; + void setListener( glm::vec3 position, glm::vec3 velocity, diff --git a/src/audio/NoAudio.hpp b/src/audio/NoAudio.hpp index edfc33199..bc2e6b49a 100644 --- a/src/audio/NoAudio.hpp +++ b/src/audio/NoAudio.hpp @@ -71,10 +71,17 @@ namespace audio { std::unique_ptr createSound( std::shared_ptr pcm, bool keepPCM ) override; + std::unique_ptr openStream( std::shared_ptr stream, bool keepSource ) override; + std::unique_ptr openInputDevice( + uint sampleRate, uint channels, uint bitsPerSample + ) override { + return nullptr; + } + void setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up ) override { diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index a3553b38d..56de29ad8 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -108,6 +108,20 @@ namespace audio { } }; + class InputDevice { + public: + virtual ~InputDevice() {}; + + virtual void startCapture() = 0; + virtual void stopCapture() = 0; + + /// @brief Get number of audio channels + /// @return 1 if mono, 2 if stereo + virtual uint getChannels() const = 0; + + virtual size_t read(char* buffer, size_t bufferSize) = 0; + }; + /// @brief audio::PCMStream is a data source for audio::Stream class PCMStream { public: @@ -345,6 +359,9 @@ namespace audio { virtual std::unique_ptr openStream( std::shared_ptr stream, bool keepSource ) = 0; + virtual std::unique_ptr openInputDevice( + uint sampleRate, uint channels, uint bitsPerSample + ) = 0; virtual void setListener( glm::vec3 position, glm::vec3 velocity, From 7dca9255dfc3d4ecc393a5a1b60b0c00ba035505 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 15 Oct 2025 22:13:16 +0300 Subject: [PATCH 008/155] add test audio input fetch function --- res/scripts/hud.lua | 13 ++-- src/audio/AL/ALAudio.cpp | 69 ++++++++++++++----- src/audio/audio.cpp | 10 +++ src/audio/audio.hpp | 13 ++++ src/logic/scripting/lua/libs/libaudio.cpp | 24 ++++++- .../lua/usertypes/lua_type_pcmstream.cpp | 3 - 6 files changed, 107 insertions(+), 25 deletions(-) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index cec5fc071..ba8284910 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -55,6 +55,8 @@ local function update_hand() skeleton.set_model("hand", bone, item.model_name(itemid)) end +local stream + function on_hud_open() input.add_callback("player.pick", function () if hud.is_paused() or hud.is_inventory_open() then @@ -117,16 +119,16 @@ function on_hud_open() hud.default_hand_controller = update_hand - local stream = PCMStream(44100, 1, 8) + stream = PCMStream(44100, 1, 16) stream:share("test-stream") - local bytes = Bytearray(44100 * 16) + local bytes = Bytearray(44100 / 8) for i=1,#bytes do - local x = math.sin(i * 0.08) * 127 + 128 + local x = math.sin(i * 0.08) * 1 + 0 bytes[i] = x end stream:feed(bytes) - audio.play_stream_2d("test-stream", 0.05, 1.0) + audio.play_stream_2d("test-stream", 2.0, 1.0, "ui") end function on_hud_render() @@ -135,4 +137,7 @@ function on_hud_render() else update_hand() end + + local bytes = audio.fetch_input() + stream:feed(bytes) end diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 92dd209a0..23dfeaa8d 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -5,11 +5,40 @@ #include "debug/Logger.hpp" #include "alutil.hpp" +#include "../MemoryPCMStream.hpp" static debug::Logger logger("al-audio"); using namespace audio; +const char* alc_error_to_string(ALCenum error) { + switch (error) { + case ALC_NO_ERROR: + return "no error"; + case ALC_INVALID_DEVICE: + return "invalid device handle"; + case ALC_INVALID_CONTEXT: + return "invalid context handle"; + case ALC_INVALID_ENUM: + return "invalid enum parameter passed to an ALC call"; + case ALC_INVALID_VALUE: + return "invalid value parameter passed to an ALC call"; + case ALC_OUT_OF_MEMORY: + return "out of memory"; + default: + return "unknown ALC error"; + } +} + +static void check_alc_errors(ALCdevice* device, const char* context) { + ALCenum error = alcGetError(device); + if (error == ALC_NO_ERROR) { + return; + } + logger.error() << context << ": " << alc_error_to_string(error) << "(" + << error << ")"; +} + ALSound::ALSound( ALAudio* al, uint buffer, const std::shared_ptr& pcm, bool keepPCM ) @@ -45,14 +74,17 @@ ALInputDevice::ALInputDevice( ALInputDevice::~ALInputDevice() { alcCaptureCloseDevice(device); + check_alc_errors(device, "alcCaptureCloseDevice"); } void ALInputDevice::startCapture() { - AL_CHECK(alcCaptureStart(device)); + alcCaptureStart(device); + check_alc_errors(device, "alcCaptureStart"); } void ALInputDevice::stopCapture() { - AL_CHECK(alcCaptureStop(device)); + alcCaptureStop(device); + check_alc_errors(device, "alcCaptureStop"); } uint ALInputDevice::getChannels() const { @@ -60,13 +92,15 @@ uint ALInputDevice::getChannels() const { } size_t ALInputDevice::read(char* buffer, size_t bufferSize) { - ALCint samplesCount; - AL_CHECK(alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, 1, &samplesCount)); + ALCint samplesCount = 0; + alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, sizeof(samplesCount), &samplesCount); + check_alc_errors(device, "alcGetIntegerv(ALC_CAPTURE_SAMPLES)"); size_t samplesRead = std::min( samplesCount, bufferSize / channels / (bitsPerSample >> 3) ); - AL_CHECK(alcCaptureSamples(device, buffer, samplesRead)); - return samplesRead; + alcCaptureSamples(device, buffer, samplesRead); + check_alc_errors(device, "alcCaptureSamples"); + return samplesRead * channels * (bitsPerSample >> 3); } ALStream::ALStream( @@ -195,7 +229,7 @@ void ALStream::update(double delta) { // alspeaker->stopped is assigned to false at ALSpeaker::play(...) if (p_speaker->isStopped() && !alspeaker->stopped) { //TODO: -V560 false-positive? - if (preloaded) { + if (preloaded || dynamic_cast(source.get())) { p_speaker->play(); } else { p_speaker->stop(); @@ -417,8 +451,10 @@ ALAudio::~ALAudio() { AL_CHECK(alDeleteBuffers(1, &buffer)); } - AL_CHECK(alcMakeContextCurrent(context)); + alcMakeContextCurrent(nullptr); + check_alc_errors(device, "alcMakeContextCurrent"); alcDestroyContext(context); + check_alc_errors(device, "alcDestroyContext"); if (!alcCloseDevice(device)) { logger.error() << "device not closed!"; } @@ -447,15 +483,16 @@ std::unique_ptr ALAudio::openInputDevice( uint sampleRate, uint channels, uint bitsPerSample ) { uint bps = bitsPerSample >> 3; - AL_CHECK( - ALCdevice* device = alcCaptureOpenDevice( - nullptr, - sampleRate, - AL::to_al_format(channels, bps), - sampleRate * channels * bps - ) + ALCdevice* device = alcCaptureOpenDevice( + nullptr, + sampleRate, + AL::to_al_format(channels, bitsPerSample), + sampleRate * channels * bps / 8 + ); + check_alc_errors(device, "alcCaptureOpenDevice"); + return std::make_unique( + this, device, channels, bitsPerSample ); - return std::make_unique(this, device, channels, bps); } std::unique_ptr ALAudio::create() { diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index f98c436e4..130e6fac0 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -151,6 +151,8 @@ public: } }; +static std::unique_ptr input_device = nullptr; + void audio::initialize(bool enabled, AudioSettings& settings) { enabled = enabled && settings.enabled.get(); if (enabled) { @@ -180,6 +182,13 @@ void audio::initialize(bool enabled, AudioSettings& settings) { audio::get_channel(channel.name)->setVolume(value * value); }, true)); } + + input_device = backend->openInputDevice(44100, 1, 16); + input_device->startCapture(); +} + +InputDevice* audio::get_input_device() { + return input_device.get(); } std::unique_ptr audio::load_PCM(const io::path& file, bool headerOnly) { @@ -458,6 +467,7 @@ void audio::reset_channel(int index) { } void audio::close() { + input_device->stopCapture(); speakers.clear(); delete backend; backend = nullptr; diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 56de29ad8..e1c19d258 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -24,6 +24,8 @@ namespace audio { /// @brief streams and important sounds constexpr inline int PRIORITY_HIGH = 10; + constexpr inline size_t MAX_INPUT_SAMPLES = 22050; + class Speaker; /// @brief Audio speaker states @@ -423,6 +425,15 @@ namespace audio { std::shared_ptr stream, bool keepSource ); + /// @brief Open audio input device + /// @param sampleRate sample rate + /// @param channels channels count (1 - mono, 2 - stereo) + /// @param bitsPerSample number of bits per sample (8 or 16) + /// @return new InputDevice instance or nullptr + std::unique_ptr open_input_device( + uint sampleRate, uint channels, uint bitsPerSample + ); + /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) @@ -536,6 +547,8 @@ namespace audio { /// @brief Stop all playing audio in channel, reset channel state void reset_channel(int channel); + InputDevice* get_input_device(); + /// @brief Finalize audio system void close(); }; diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index 4d4ad6a64..7f68bd6d2 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -379,16 +379,35 @@ static int l_audio_get_velocity(lua::State* L) { return 0; } -// @brief audio.count_speakers() -> integer +/// @brief audio.count_speakers() -> integer static int l_audio_count_speakers(lua::State* L) { return lua::pushinteger(L, audio::count_speakers()); } -// @brief audio.count_streams() -> integer +/// @brief audio.count_streams() -> integer static int l_audio_count_streams(lua::State* L) { return lua::pushinteger(L, audio::count_streams()); } +/// @brief audio.fetch_input(size) -> Bytearray +static int l_audio_fetch_input(lua::State* L) { + auto device = audio::get_input_device(); + if (device == nullptr) { + return 0; + } + size_t size = lua::touinteger(L, 1); + const size_t MAX_BUFFER_SIZE = audio::MAX_INPUT_SAMPLES * 4; + if (size == 0) { + size = MAX_BUFFER_SIZE; + } + size = std::min(size, MAX_BUFFER_SIZE); + ubyte buffer[MAX_BUFFER_SIZE]; + size = device->read(reinterpret_cast(buffer), size); + + std::vector bytes(buffer, buffer + size); + return lua::create_bytearray(L, std::move(bytes)); +} + const luaL_Reg audiolib[] = { {"play_sound", lua::wrap}, {"play_sound_2d", lua::wrap}, @@ -414,5 +433,6 @@ const luaL_Reg audiolib[] = { {"get_velocity", lua::wrap}, {"count_speakers", lua::wrap}, {"count_streams", lua::wrap}, + {"fetch_input", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp index 04c585cfa..3367813ea 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp @@ -18,10 +18,7 @@ const std::shared_ptr& LuaPCMStream::getStream() const { return stream; } -#include - static int l_feed(lua::State* L) { - std::cout << "feed" << std::endl; auto stream = touserdata(L, 1); if (stream == nullptr) { return 0; From d41167fd727404e0087eb1272741dc352167406f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 15 Oct 2025 22:40:00 +0300 Subject: [PATCH 009/155] fix --- src/audio/audio.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 130e6fac0..b45242bb5 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -184,7 +184,9 @@ void audio::initialize(bool enabled, AudioSettings& settings) { } input_device = backend->openInputDevice(44100, 1, 16); - input_device->startCapture(); + if (input_device) { + input_device->startCapture(); + } } InputDevice* audio::get_input_device() { @@ -467,7 +469,9 @@ void audio::reset_channel(int index) { } void audio::close() { - input_device->stopCapture(); + if (input_device) { + input_device->stopCapture(); + } speakers.clear(); delete backend; backend = nullptr; From ed9cf8800aea07c169719a3efddae7020e6be7e9 Mon Sep 17 00:00:00 2001 From: Eliot Byte Date: Thu, 23 Oct 2025 12:29:04 +0300 Subject: [PATCH 010/155] fix correct line generation algorithm and bounds calculation (#657) * fix correct line generation algorithm and bounds calculation - Fix closest_point_on_segment algorithm using proper dot product projection - Correct loop bounds calculation for radius=0 lines - Use precise integer arithmetic to avoid rounding errors - Resolves missing blocks in vertical line structures * fix ai hallucinations * polishing closest_point_on_segment * refactor: extract dot product calculation --- src/maths/util.hpp | 24 +++++++++++++++++------- src/world/generator/WorldGenerator.cpp | 6 +++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/maths/util.hpp b/src/maths/util.hpp index 00345942c..2aa3605c1 100644 --- a/src/maths/util.hpp +++ b/src/maths/util.hpp @@ -118,6 +118,11 @@ namespace util { return x * x + y * y + z * z; } + /// @return integer dot product of two vectors + inline int dot(const glm::ivec3& a, const glm::ivec3& b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + /// @brief Find nearest point on segment to given /// @param a segment point A /// @param b segment point B @@ -144,12 +149,17 @@ namespace util { inline glm::ivec3 closest_point_on_segment( const glm::ivec3& a, const glm::ivec3& b, const glm::ivec3& point ) { - auto vec = b - a; - float da = distance2(point, a); - float db = distance2(point, b); - float len = length2(vec); - float t = (((da - db) / len) * 0.5f + 0.5f); - t = std::min(1.0f, std::max(0.0f, t)); - return a + glm::ivec3(glm::vec3(vec) * t); + glm::ivec3 vec = b - a; + int len2 = length2(vec); + + if (len2 == 0) return a; + + glm::ivec3 ap = point - a; + int dot_product = dot(ap, vec); + + float t = static_cast(dot_product) / static_cast(len2); + t = glm::clamp(t, 0.0f, 1.0f); + + return a + glm::ivec3(glm::round(glm::vec3(vec) * t)); } } diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index e2bf4a863..ccd52db1f 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -547,13 +547,13 @@ void WorldGenerator::generateLine( auto b = line.b; int minX = std::max(0, std::min(a.x-radius-cgx, b.x-radius-cgx)); - int maxX = std::min(CHUNK_W, std::max(a.x+radius-cgx, b.x+radius-cgx)); + int maxX = std::min(CHUNK_W, std::max(a.x+radius-cgx, b.x+radius-cgx) + 1); int minZ = std::max(0, std::min(a.z-radius-cgz, b.z-radius-cgz)); - int maxZ = std::min(CHUNK_D, std::max(a.z+radius-cgz, b.z+radius-cgz)); + int maxZ = std::min(CHUNK_D, std::max(a.z+radius-cgz, b.z+radius-cgz) + 1); int minY = std::max(0, std::min(a.y-radius, b.y-radius)); - int maxY = std::min(CHUNK_H, std::max(a.y+radius, b.y+radius)); + int maxY = std::min(CHUNK_H, std::max(a.y+radius, b.y+radius) + 1); for (int y = minY; y < maxY; y++) { for (int z = minZ; z < maxZ; z++) { From b11bdf0bcc3b56ed2cfb3eaa5d8c1a6afcaa79c3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 23 Oct 2025 22:08:30 +0300 Subject: [PATCH 011/155] add audio.get_input_devices_names() --- src/audio/AL/ALAudio.cpp | 22 ++++++++++++++++++++++ src/audio/AL/ALAudio.hpp | 2 ++ src/audio/NoAudio.hpp | 4 ++++ src/audio/audio.cpp | 10 ++++++++++ src/audio/audio.hpp | 5 +++++ src/logic/scripting/lua/libs/libaudio.cpp | 12 ++++++++++++ 6 files changed, 55 insertions(+) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 23dfeaa8d..ce73823c5 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -479,6 +479,27 @@ std::unique_ptr ALAudio::openStream( return std::make_unique(this, stream, keepSource); } +std::vector ALAudio::getInputDeviceNames() { + std::vector devices; + + if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { + auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); + if (deviceList == nullptr) { + logger.warning() << "no input devices found"; + return devices; + } + while (*deviceList) { + std::string deviceName(deviceList); + devices.push_back(deviceName); + deviceList += deviceName.length() + 1; + } + } else { + logger.warning() << "enumeration extension is not available"; + } + + return devices; +} + std::unique_ptr ALAudio::openInputDevice( uint sampleRate, uint channels, uint bitsPerSample ) { @@ -490,6 +511,7 @@ std::unique_ptr ALAudio::openInputDevice( sampleRate * channels * bps / 8 ); check_alc_errors(device, "alcCaptureOpenDevice"); + return std::make_unique( this, device, channels, bitsPerSample ); diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index d04d45b66..8e4ad02ca 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -186,6 +186,8 @@ namespace audio { uint sampleRate, uint channels, uint bitsPerSample ) override; + std::vector getInputDeviceNames() override; + void setListener( glm::vec3 position, glm::vec3 velocity, diff --git a/src/audio/NoAudio.hpp b/src/audio/NoAudio.hpp index bc2e6b49a..c5dbae411 100644 --- a/src/audio/NoAudio.hpp +++ b/src/audio/NoAudio.hpp @@ -82,6 +82,10 @@ namespace audio { return nullptr; } + std::vector getInputDeviceNames() override { + return {}; + } + void setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up ) override { diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index b45242bb5..72b22df5b 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -253,6 +253,16 @@ std::unique_ptr audio::open_stream( return backend->openStream(std::move(stream), keepSource); } +std::unique_ptr audio::open_input_device( + uint sampleRate, uint channels, uint bitsPerSample +) { + return backend->openInputDevice(sampleRate, channels, bitsPerSample); +} + +std::vector audio::get_input_devices_names() { + return backend->getInputDeviceNames(); +} + void audio::set_listener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up ) { diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index e1c19d258..3364d89e6 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -370,6 +370,7 @@ namespace audio { glm::vec3 lookAt, glm::vec3 up ) = 0; + virtual std::vector getInputDeviceNames() = 0; virtual void update(double delta) = 0; /// @brief Check if backend is an abstraction that does not internally @@ -434,6 +435,10 @@ namespace audio { uint sampleRate, uint channels, uint bitsPerSample ); + /// @brief Retrieve names of available audio input devices + /// @return list of device names + std::vector get_input_devices_names(); + /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index 7f68bd6d2..951522d90 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -408,6 +408,17 @@ static int l_audio_fetch_input(lua::State* L) { return lua::create_bytearray(L, std::move(bytes)); } +static int l_audio_get_input_devices_names(lua::State* L) { + auto device_names = audio::get_input_devices_names(); + lua::createtable(L, device_names.size(), 0); + int index = 1; + for (const auto& name : device_names) { + lua::pushstring(L, name.c_str()); + lua::rawseti(L, index++); + } + return 1; +} + const luaL_Reg audiolib[] = { {"play_sound", lua::wrap}, {"play_sound_2d", lua::wrap}, @@ -434,5 +445,6 @@ const luaL_Reg audiolib[] = { {"count_speakers", lua::wrap}, {"count_streams", lua::wrap}, {"fetch_input", lua::wrap}, + {"get_input_devices_names", lua::wrap}, {nullptr, nullptr} }; From 6df27b89921e822311759b837529ea0a91f74cc0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 23 Oct 2025 22:54:05 +0300 Subject: [PATCH 012/155] add audio.get_output_devices_names, set_input_device, set_output_device --- src/audio/AL/ALAudio.cpp | 114 +++++++++++++++------- src/audio/AL/ALAudio.hpp | 10 +- src/audio/NoAudio.hpp | 8 +- src/audio/audio.cpp | 34 ++++++- src/audio/audio.hpp | 19 +++- src/logic/scripting/lua/libs/libaudio.cpp | 26 +++++ 6 files changed, 169 insertions(+), 42 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index ce73823c5..9e555133f 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -30,13 +30,14 @@ const char* alc_error_to_string(ALCenum error) { } } -static void check_alc_errors(ALCdevice* device, const char* context) { +static bool check_alc_errors(ALCdevice* device, const char* context) { ALCenum error = alcGetError(device); if (error == ALC_NO_ERROR) { - return; + return false; } logger.error() << context << ": " << alc_error_to_string(error) << "(" << error << ")"; + return true; } ALSound::ALSound( @@ -419,6 +420,8 @@ int ALSpeaker::getPriority() const { return priority; } +static bool alc_enumeration_ext = false; + ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) : device(device), context(context) { ALCint size; @@ -431,9 +434,15 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) maxSources = attrs[i + 1]; } } - auto devices = getAvailableDevices(); - logger.info() << "devices:"; - for (auto& name : devices) { + auto outputDevices = getOutputDeviceNames(); + logger.info() << "output devices:"; + for (auto& name : outputDevices) { + logger.info() << " " << name; + } + + auto inputDevices = getInputDeviceNames(); + logger.info() << "input devices:"; + for (auto& name : inputDevices) { logger.info() << " " << name; } } @@ -482,35 +491,59 @@ std::unique_ptr ALAudio::openStream( std::vector ALAudio::getInputDeviceNames() { std::vector devices; - if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { - auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); - if (deviceList == nullptr) { - logger.warning() << "no input devices found"; - return devices; - } - while (*deviceList) { - std::string deviceName(deviceList); - devices.push_back(deviceName); - deviceList += deviceName.length() + 1; - } - } else { + if (!alc_enumeration_ext) { logger.warning() << "enumeration extension is not available"; + return devices; + } + + auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); + if (deviceList == nullptr) { + logger.warning() << "no input devices found"; + return devices; + } + while (*deviceList) { + std::string deviceName(deviceList); + devices.push_back(deviceName); + deviceList += deviceName.length() + 1; + } + + return devices; +} + +std::vector ALAudio::getOutputDeviceNames() { + std::vector devices; + + if (!alc_enumeration_ext) { + logger.warning() << "enumeration extension is not available"; + return devices; + } + + auto deviceList = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + if (deviceList == nullptr) { + logger.warning() << "no input devices found"; + return devices; + } + while (*deviceList) { + std::string deviceName(deviceList); + devices.push_back(deviceName); + deviceList += deviceName.length() + 1; } return devices; } std::unique_ptr ALAudio::openInputDevice( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample ) { uint bps = bitsPerSample >> 3; ALCdevice* device = alcCaptureOpenDevice( - nullptr, + deviceName, sampleRate, AL::to_al_format(channels, bitsPerSample), sampleRate * channels * bps / 8 ); - check_alc_errors(device, "alcCaptureOpenDevice"); + if (check_alc_errors(device, "alcCaptureOpenDevice")) + return nullptr; return std::make_unique( this, device, channels, bitsPerSample @@ -518,6 +551,8 @@ std::unique_ptr ALAudio::openInputDevice( } std::unique_ptr ALAudio::create() { + alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"); + ALCdevice* device = alcOpenDevice(nullptr); if (device == nullptr) return nullptr; ALCcontext* context = alcCreateContext(device, nullptr); @@ -574,22 +609,35 @@ void ALAudio::freeBuffer(uint buffer) { freebuffers.push_back(buffer); } -std::vector ALAudio::getAvailableDevices() const { - std::vector devicesVec; - - const ALCchar* devices; - devices = alcGetString(device, ALC_DEVICE_SPECIFIER); - if (!AL_GET_ERROR()) { - return devicesVec; +void ALAudio::setOutputDevice(const std::string& deviceName) { + ALCdevice* newDevice = alcOpenDevice(deviceName.c_str()); + if (newDevice == nullptr) { + logger.error() << "failed to open output device: " << deviceName; + return; } - const char* ptr = devices; - do { - devicesVec.emplace_back(ptr); - ptr += devicesVec.back().size() + 1; - } while (ptr[0]); + ALCcontext* newContext = alcCreateContext(newDevice, nullptr); + if (!alcMakeContextCurrent(newContext)) { + logger.error() << "failed to make context current for device: " + << deviceName; + alcCloseDevice(newDevice); + return; + } - return devicesVec; + // Clean up old device and context + alcMakeContextCurrent(nullptr); + check_alc_errors(device, "alcMakeContextCurrent"); + alcDestroyContext(context); + check_alc_errors(device, "alcDestroyContext"); + if (!alcCloseDevice(device)) { + logger.error() << "old device not closed"; + } + + // Update to new device and context + device = newDevice; + context = newContext; + + logger.info() << "switched output device to: " << deviceName; } void ALAudio::setListener( diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 8e4ad02ca..d8fe2000a 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -172,8 +172,6 @@ namespace audio { void freeSource(uint source); void freeBuffer(uint buffer); - std::vector getAvailableDevices() const; - std::unique_ptr createSound( std::shared_ptr pcm, bool keepPCM ) override; @@ -183,11 +181,17 @@ namespace audio { ) override; std::unique_ptr openInputDevice( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, + uint sampleRate, + uint channels, + uint bitsPerSample ) override; + std::vector getOutputDeviceNames() override; std::vector getInputDeviceNames() override; + void setOutputDevice(const std::string& deviceName) override; + void setListener( glm::vec3 position, glm::vec3 velocity, diff --git a/src/audio/NoAudio.hpp b/src/audio/NoAudio.hpp index c5dbae411..a7b4232cf 100644 --- a/src/audio/NoAudio.hpp +++ b/src/audio/NoAudio.hpp @@ -77,7 +77,7 @@ namespace audio { ) override; std::unique_ptr openInputDevice( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample ) override { return nullptr; } @@ -85,6 +85,12 @@ namespace audio { std::vector getInputDeviceNames() override { return {}; } + std::vector getOutputDeviceNames() override { + return {}; + } + + void setOutputDevice(const std::string& deviceName) override { + } void setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 72b22df5b..06a36c06a 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -183,7 +183,7 @@ void audio::initialize(bool enabled, AudioSettings& settings) { }, true)); } - input_device = backend->openInputDevice(44100, 1, 16); + input_device = backend->openInputDevice(nullptr, 44100, 1, 16); if (input_device) { input_device->startCapture(); } @@ -254,15 +254,43 @@ std::unique_ptr audio::open_stream( } std::unique_ptr audio::open_input_device( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample ) { - return backend->openInputDevice(sampleRate, channels, bitsPerSample); + return backend->openInputDevice( + deviceName, sampleRate, channels, bitsPerSample + ); } std::vector audio::get_input_devices_names() { return backend->getInputDeviceNames(); } +std::vector audio::get_output_devices_names() { + return backend->getOutputDeviceNames(); +} + +void audio::set_input_device(const std::string& deviceName) { + auto newDevice = backend->openInputDevice( + deviceName.empty() ? nullptr : deviceName.c_str(), 44100, 1, 16 + ); + if (newDevice == nullptr) { + logger.error() << "could not open input device: " << deviceName; + return; + } + + if (input_device) { + input_device->stopCapture(); + } + input_device = std::move(newDevice); + if (input_device) { + input_device->startCapture(); + } +} + +void audio::set_output_device(const std::string& deviceName) { + backend->setOutputDevice(deviceName); +} + void audio::set_listener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up ) { diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 3364d89e6..47fab4f5d 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -362,7 +362,10 @@ namespace audio { std::shared_ptr stream, bool keepSource ) = 0; virtual std::unique_ptr openInputDevice( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, + uint sampleRate, + uint channels, + uint bitsPerSample ) = 0; virtual void setListener( glm::vec3 position, @@ -371,6 +374,8 @@ namespace audio { glm::vec3 up ) = 0; virtual std::vector getInputDeviceNames() = 0; + virtual std::vector getOutputDeviceNames() = 0; + virtual void setOutputDevice(const std::string& deviceName) = 0; virtual void update(double delta) = 0; /// @brief Check if backend is an abstraction that does not internally @@ -432,13 +437,23 @@ namespace audio { /// @param bitsPerSample number of bits per sample (8 or 16) /// @return new InputDevice instance or nullptr std::unique_ptr open_input_device( - uint sampleRate, uint channels, uint bitsPerSample + const char* deviceName, + uint sampleRate, + uint channels, + uint bitsPerSample ); /// @brief Retrieve names of available audio input devices /// @return list of device names std::vector get_input_devices_names(); + /// @brief Retrieve names of available audio output devices + /// @return list of device names + std::vector get_output_devices_names(); + + void set_input_device(const std::string& deviceName); + void set_output_device(const std::string& deviceName); + /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index 951522d90..d240ec2e2 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -419,6 +419,29 @@ static int l_audio_get_input_devices_names(lua::State* L) { return 1; } +static int l_audio_get_output_devices_names(lua::State* L) { + auto device_names = audio::get_output_devices_names(); + lua::createtable(L, device_names.size(), 0); + int index = 1; + for (const auto& name : device_names) { + lua::pushstring(L, name.c_str()); + lua::rawseti(L, index++); + } + return 1; +} + +static int l_audio_set_input_device(lua::State* L) { + auto device_name = lua::tostring(L, 1); + audio::set_input_device(device_name); + return 0; +} + +static int l_audio_set_output_device(lua::State* L) { + auto device_name = lua::tostring(L, 1); + audio::set_output_device(device_name); + return 0; +} + const luaL_Reg audiolib[] = { {"play_sound", lua::wrap}, {"play_sound_2d", lua::wrap}, @@ -446,5 +469,8 @@ const luaL_Reg audiolib[] = { {"count_streams", lua::wrap}, {"fetch_input", lua::wrap}, {"get_input_devices_names", lua::wrap}, + {"get_output_devices_names", lua::wrap}, + {"set_input_device", lua::wrap}, + {"set_output_device", lua::wrap}, {nullptr, nullptr} }; From 001c1b430bddd728ec5add68ec802902f075760a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 23 Oct 2025 23:45:55 +0300 Subject: [PATCH 013/155] minor refactor --- src/audio/AL/ALAudio.cpp | 4 ++-- src/audio/AL/ALAudio.hpp | 2 +- src/audio/NoAudio.hpp | 2 +- src/audio/audio.cpp | 8 +++----- src/audio/audio.hpp | 4 ++-- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 9e555133f..4215f3a1c 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -533,11 +533,11 @@ std::vector ALAudio::getOutputDeviceNames() { } std::unique_ptr ALAudio::openInputDevice( - const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample + const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample ) { uint bps = bitsPerSample >> 3; ALCdevice* device = alcCaptureOpenDevice( - deviceName, + deviceName.empty() ? nullptr : deviceName.c_str(), sampleRate, AL::to_al_format(channels, bitsPerSample), sampleRate * channels * bps / 8 diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index d8fe2000a..99ca7be11 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -181,7 +181,7 @@ namespace audio { ) override; std::unique_ptr openInputDevice( - const char* deviceName, + const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample diff --git a/src/audio/NoAudio.hpp b/src/audio/NoAudio.hpp index a7b4232cf..cc0c240d1 100644 --- a/src/audio/NoAudio.hpp +++ b/src/audio/NoAudio.hpp @@ -77,7 +77,7 @@ namespace audio { ) override; std::unique_ptr openInputDevice( - const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample + const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample ) override { return nullptr; } diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 06a36c06a..313deed81 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -183,7 +183,7 @@ void audio::initialize(bool enabled, AudioSettings& settings) { }, true)); } - input_device = backend->openInputDevice(nullptr, 44100, 1, 16); + input_device = backend->openInputDevice("", 44100, 1, 16); if (input_device) { input_device->startCapture(); } @@ -254,7 +254,7 @@ std::unique_ptr audio::open_stream( } std::unique_ptr audio::open_input_device( - const char* deviceName, uint sampleRate, uint channels, uint bitsPerSample + const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample ) { return backend->openInputDevice( deviceName, sampleRate, channels, bitsPerSample @@ -270,9 +270,7 @@ std::vector audio::get_output_devices_names() { } void audio::set_input_device(const std::string& deviceName) { - auto newDevice = backend->openInputDevice( - deviceName.empty() ? nullptr : deviceName.c_str(), 44100, 1, 16 - ); + auto newDevice = backend->openInputDevice(deviceName, 44100, 1, 16); if (newDevice == nullptr) { logger.error() << "could not open input device: " << deviceName; return; diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 47fab4f5d..8b3a1326c 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -362,7 +362,7 @@ namespace audio { std::shared_ptr stream, bool keepSource ) = 0; virtual std::unique_ptr openInputDevice( - const char* deviceName, + const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample @@ -437,7 +437,7 @@ namespace audio { /// @param bitsPerSample number of bits per sample (8 or 16) /// @return new InputDevice instance or nullptr std::unique_ptr open_input_device( - const char* deviceName, + const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample From 6c558eb3d5128e31dd89ac6ebd2a22549470af1a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 24 Oct 2025 01:19:07 +0300 Subject: [PATCH 014/155] add audio.get_input_info() --- src/audio/AL/ALAudio.cpp | 22 +++++++++++++++++++--- src/audio/AL/ALAudio.hpp | 9 ++++++++- src/audio/audio.hpp | 6 ++++++ src/logic/scripting/lua/libs/libaudio.cpp | 16 ++++++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 4215f3a1c..288774850 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -68,9 +68,17 @@ std::unique_ptr ALSound::newInstance(int priority, int channel) const { } ALInputDevice::ALInputDevice( - ALAudio* al, ALCdevice* device, uint channels, uint bitsPerSample + ALAudio* al, + ALCdevice* device, + uint channels, + uint bitsPerSample, + uint sampleRate ) - : al(al), device(device), channels(channels), bitsPerSample(bitsPerSample) { + : al(al), + device(device), + channels(channels), + bitsPerSample(bitsPerSample), + sampleRate(sampleRate) { } ALInputDevice::~ALInputDevice() { @@ -92,6 +100,14 @@ uint ALInputDevice::getChannels() const { return channels; } +uint ALInputDevice::getSampleRate() const { + return sampleRate; +} + +uint ALInputDevice::getBitsPerSample() const { + return bitsPerSample; +} + size_t ALInputDevice::read(char* buffer, size_t bufferSize) { ALCint samplesCount = 0; alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, sizeof(samplesCount), &samplesCount); @@ -546,7 +562,7 @@ std::unique_ptr ALAudio::openInputDevice( return nullptr; return std::make_unique( - this, device, channels, bitsPerSample + this, device, channels, bitsPerSample, sampleRate ); } diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 99ca7be11..3c108b6f9 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -85,7 +85,11 @@ namespace audio { class ALInputDevice : public InputDevice { public: ALInputDevice( - ALAudio* al, ALCdevice* device, uint channels, uint bitsPerSample + ALAudio* al, + ALCdevice* device, + uint channels, + uint bitsPerSample, + uint sampleRate ); ~ALInputDevice() override; @@ -93,6 +97,8 @@ namespace audio { void stopCapture() override; uint getChannels() const override; + uint getSampleRate() const override; + uint getBitsPerSample() const override; size_t read(char* buffer, size_t bufferSize) override; private: @@ -100,6 +106,7 @@ namespace audio { ALCdevice* device; uint channels; uint bitsPerSample; + uint sampleRate; }; /// @brief AL source adapter diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 8b3a1326c..dd6480276 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -120,6 +120,12 @@ namespace audio { /// @brief Get number of audio channels /// @return 1 if mono, 2 if stereo virtual uint getChannels() const = 0; + /// @brief Get audio sampling frequency + /// @return number of mono samples per second + virtual uint getSampleRate() const = 0; + /// @brief Get number of bits per mono sample + /// @return 8 or 16 + virtual uint getBitsPerSample() const = 0; virtual size_t read(char* buffer, size_t bufferSize) = 0; }; diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index d240ec2e2..4551af0bd 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -442,6 +442,21 @@ static int l_audio_set_output_device(lua::State* L) { return 0; } +static int l_audio_get_input_info(lua::State* L) { + auto device = audio::get_input_device(); + if (device == nullptr) { + return 0; + } + lua::createtable(L, 0, 3); + lua::pushinteger(L, device->getChannels()); + lua::setfield(L, "channels"); + lua::pushinteger(L, device->getSampleRate()); + lua::setfield(L, "sample_rate"); + lua::pushinteger(L, device->getBitsPerSample()); + lua::setfield(L, "bits_per_sample"); + return 1; +} + const luaL_Reg audiolib[] = { {"play_sound", lua::wrap}, {"play_sound_2d", lua::wrap}, @@ -472,5 +487,6 @@ const luaL_Reg audiolib[] = { {"get_output_devices_names", lua::wrap}, {"set_input_device", lua::wrap}, {"set_output_device", lua::wrap}, + {"get_input_info", lua::wrap}, {nullptr, nullptr} }; From ab7bf9c709108e29916cf36fa69932ec202c414b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 24 Oct 2025 23:10:16 +0300 Subject: [PATCH 015/155] cleanup --- src/audio/AL/ALAudio.cpp | 31 ----------------------- src/audio/AL/ALAudio.hpp | 2 -- src/audio/NoAudio.hpp | 3 --- src/audio/audio.cpp | 4 --- src/audio/audio.hpp | 2 -- src/logic/scripting/lua/libs/libaudio.cpp | 7 ----- 6 files changed, 49 deletions(-) diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 288774850..267b20dd6 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -625,37 +625,6 @@ void ALAudio::freeBuffer(uint buffer) { freebuffers.push_back(buffer); } -void ALAudio::setOutputDevice(const std::string& deviceName) { - ALCdevice* newDevice = alcOpenDevice(deviceName.c_str()); - if (newDevice == nullptr) { - logger.error() << "failed to open output device: " << deviceName; - return; - } - - ALCcontext* newContext = alcCreateContext(newDevice, nullptr); - if (!alcMakeContextCurrent(newContext)) { - logger.error() << "failed to make context current for device: " - << deviceName; - alcCloseDevice(newDevice); - return; - } - - // Clean up old device and context - alcMakeContextCurrent(nullptr); - check_alc_errors(device, "alcMakeContextCurrent"); - alcDestroyContext(context); - check_alc_errors(device, "alcDestroyContext"); - if (!alcCloseDevice(device)) { - logger.error() << "old device not closed"; - } - - // Update to new device and context - device = newDevice; - context = newContext; - - logger.info() << "switched output device to: " << deviceName; -} - void ALAudio::setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up ) { diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 3c108b6f9..6fef62f07 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -197,8 +197,6 @@ namespace audio { std::vector getOutputDeviceNames() override; std::vector getInputDeviceNames() override; - void setOutputDevice(const std::string& deviceName) override; - void setListener( glm::vec3 position, glm::vec3 velocity, diff --git a/src/audio/NoAudio.hpp b/src/audio/NoAudio.hpp index cc0c240d1..5012704b0 100644 --- a/src/audio/NoAudio.hpp +++ b/src/audio/NoAudio.hpp @@ -89,9 +89,6 @@ namespace audio { return {}; } - void setOutputDevice(const std::string& deviceName) override { - } - void setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up ) override { diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 313deed81..60f44b04d 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -285,10 +285,6 @@ void audio::set_input_device(const std::string& deviceName) { } } -void audio::set_output_device(const std::string& deviceName) { - backend->setOutputDevice(deviceName); -} - void audio::set_listener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up ) { diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index dd6480276..c394398ff 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -381,7 +381,6 @@ namespace audio { ) = 0; virtual std::vector getInputDeviceNames() = 0; virtual std::vector getOutputDeviceNames() = 0; - virtual void setOutputDevice(const std::string& deviceName) = 0; virtual void update(double delta) = 0; /// @brief Check if backend is an abstraction that does not internally @@ -458,7 +457,6 @@ namespace audio { std::vector get_output_devices_names(); void set_input_device(const std::string& deviceName); - void set_output_device(const std::string& deviceName); /// @brief Configure 3D listener /// @param position listener position diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index 4551af0bd..ed2e3db6c 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -436,12 +436,6 @@ static int l_audio_set_input_device(lua::State* L) { return 0; } -static int l_audio_set_output_device(lua::State* L) { - auto device_name = lua::tostring(L, 1); - audio::set_output_device(device_name); - return 0; -} - static int l_audio_get_input_info(lua::State* L) { auto device = audio::get_input_device(); if (device == nullptr) { @@ -486,7 +480,6 @@ const luaL_Reg audiolib[] = { {"get_input_devices_names", lua::wrap}, {"get_output_devices_names", lua::wrap}, {"set_input_device", lua::wrap}, - {"set_output_device", lua::wrap}, {"get_input_info", lua::wrap}, {nullptr, nullptr} }; From 2a1d2f9354ee2ab623aca9d0059c0a676395d6d0 Mon Sep 17 00:00:00 2001 From: eliotbyte Date: Tue, 28 Oct 2025 23:09:28 +0300 Subject: [PATCH 016/155] fix enable per-variant custom model caching --- src/frontend/ContentGfxCache.cpp | 6 +++--- src/frontend/ContentGfxCache.hpp | 8 ++++++-- src/graphics/render/BlocksPreview.cpp | 2 +- src/graphics/render/BlocksRenderer.cpp | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 47443aff5..20b7cdaa7 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -62,7 +62,7 @@ void ContentGfxCache::refreshVariant( } } } - models[def.rt.id] = std::move(model); + models[modelKey(def.rt.id, variantIndex)] = std::move(model); } } @@ -94,8 +94,8 @@ void ContentGfxCache::refresh() { ContentGfxCache::~ContentGfxCache() = default; -const model::Model& ContentGfxCache::getModel(blockid_t id) const { - const auto& found = models.find(id); +const model::Model& ContentGfxCache::getModel(blockid_t id, uint8_t variant) const { + const auto& found = models.find(modelKey(id, variant)); if (found == models.end()) { throw std::runtime_error("model not found"); } diff --git a/src/frontend/ContentGfxCache.hpp b/src/frontend/ContentGfxCache.hpp index 8eda04e57..2d2bacaa1 100644 --- a/src/frontend/ContentGfxCache.hpp +++ b/src/frontend/ContentGfxCache.hpp @@ -27,7 +27,11 @@ class ContentGfxCache { // array of block sides uv regions (6 per block) std::unique_ptr sideregions; - std::unordered_map models; + std::unordered_map models; + + static inline uint64_t modelKey(blockid_t id, uint8_t variant) { + return (uint64_t(id) << 8) | uint64_t(variant & 0xFF); + } void refreshVariant( const Block& def, @@ -53,7 +57,7 @@ public: return sideregions[getRegionIndex(id, variant, side, !dense)]; } - const model::Model& getModel(blockid_t id) const; + const model::Model& getModel(blockid_t id, uint8_t variant) const; void refresh(const Block& block, const Atlas& atlas); diff --git a/src/graphics/render/BlocksPreview.cpp b/src/graphics/render/BlocksPreview.cpp index 9a1bf97dd..4b8bfee97 100644 --- a/src/graphics/render/BlocksPreview.cpp +++ b/src/graphics/render/BlocksPreview.cpp @@ -67,7 +67,7 @@ std::unique_ptr BlocksPreview::draw( glm::vec3 poff = glm::vec3(0.0f, 0.0f, 1.0f); offset.y += (1.0f - hitbox).y * 0.5f; shader.uniformMatrix("u_apply", glm::translate(glm::mat4(1.0f), offset)); - const auto& model = cache.getModel(def.rt.id); + const auto& model = cache.getModel(def.rt.id, 0); for (const auto& mesh : model.meshes) { for (const auto& vertex : mesh.vertices) { diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 1f14d2c73..6df7967ea 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -308,7 +308,7 @@ void BlocksRenderer::blockCustomModel( Z = orient.axes[2]; } - const auto& model = cache.getModel(block.rt.id); + const auto& model = cache.getModel(block.rt.id, block.getVariantIndex(states.userbits)); for (const auto& mesh : model.meshes) { if (vertexCount + mesh.vertices.size() >= capacity) { overflow = true; From cd6b73f1eb5268043d4b6abfa9e11d8bc1403116 Mon Sep 17 00:00:00 2001 From: eliotbyte Date: Tue, 28 Oct 2025 23:09:48 +0300 Subject: [PATCH 017/155] docs: add custom model variant examples to block-propertie --- doc/en/block-properties.md | 26 ++++++++++++++++++++++++++ doc/ru/block-properties.md | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index 1244da99d..82811b630 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -98,6 +98,32 @@ Properties available for variance: Variants are managed via `block.set_variant(x, y, z, index)`. +### Custom model variants (geometry switching) + +You can use different custom models for different variants. Provide a separate `model-name` for each variant that needs different geometry. The renderer caches geometry per (block id, variant). + +The base model (specified in root) becomes variant 0. The variants array maps to indices 1+. + +Example (default + two custom variants): +```json +{ + "model": "custom", + "model-name": "stairs_middle", + "state-based": { + "bits": 4, + "variants": [ + { "model": "custom", "model-name": "stairs_left" }, + { "model": "custom", "model-name": "stairs_right" } + ] + } +} +``` + +In this example: +- Variant 0 = `stairs_middle` (from root) +- Variant 1 = `stairs_left` (from variants[0]) +- Variant 2 = `stairs_right` (from variants[1]) + ## Lighting ### *emission* diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 0003cb927..4569cc493 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -106,6 +106,32 @@ Управление состоянием производится через `block.set_variant(x, y, z, index)`. +### Кастомные модели по вариантам (переключение геометрии) + +Для custom-моделей можно переключать геометрию по варианту. Укажите отдельный `model-name` в нужных вариантах — рендерер кэширует геометрию по паре (id блока, вариант). + +Базовая модель (из корня) становится вариантом 0. Массив variants соответствует индексам 1+. + +Пример (база + два кастомных варианта): +```json +{ + "model": "custom", + "model-name": "stairs_middle", + "state-based": { + "bits": 4, + "variants": [ + { "model": "custom", "model-name": "stairs_left" }, + { "model": "custom", "model-name": "stairs_right" } + ] + } +} +``` + +В этом примере: +- Вариант 0 = `stairs_middle` (из корня) +- Вариант 1 = `stairs_left` (из variants[0]) +- Вариант 2 = `stairs_right` (из variants[1]) + ## Освещение ### Излучение - *emission*: From cf561e78a81810fcb70975c7b785938af37b4b64 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 30 Oct 2025 19:56:07 +0300 Subject: [PATCH 018/155] fix: stream stops and dies on underflow --- res/scripts/hud.lua | 9 +------- src/audio/AL/ALAudio.cpp | 46 ++++++++++++++++++++++++++-------------- src/audio/AL/ALAudio.hpp | 9 +++++++- src/audio/NoAudio.hpp | 7 ++++++ src/audio/audio.cpp | 11 ++++++++-- src/audio/audio.hpp | 5 +++++ 6 files changed, 60 insertions(+), 27 deletions(-) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index ba8284910..7730ba5fa 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -121,14 +121,7 @@ function on_hud_open() stream = PCMStream(44100, 1, 16) stream:share("test-stream") - local bytes = Bytearray(44100 / 8) - for i=1,#bytes do - local x = math.sin(i * 0.08) * 1 + 0 - bytes[i] = x - end - stream:feed(bytes) - - audio.play_stream_2d("test-stream", 2.0, 1.0, "ui") + streamid = audio.play_stream_2d("test-stream", 2.0, 1.0, "ui") end function on_hud_render() diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 267b20dd6..83d5c4f41 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -164,9 +164,10 @@ std::unique_ptr ALStream::createSpeaker(bool loop, int channel) { for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) { uint free_buffer = al->getFreeBuffer(); if (!preloadBuffer(free_buffer, loop)) { - break; + unusedBuffers.push(free_buffer); + } else { + AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer)); } - AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer)); } return std::make_unique(al, free_source, PRIORITY_HIGH, channel); } @@ -213,11 +214,11 @@ void ALStream::unqueueBuffers(uint alsource) { uint ALStream::enqueueBuffers(uint alsource) { uint preloaded = 0; if (!unusedBuffers.empty()) { - uint first_buffer = unusedBuffers.front(); - if (preloadBuffer(first_buffer, loop)) { + uint firstBuffer = unusedBuffers.front(); + if (preloadBuffer(firstBuffer, loop)) { preloaded++; unusedBuffers.pop(); - AL_CHECK(alSourceQueueBuffers(alsource, 1, &first_buffer)); + AL_CHECK(alSourceQueueBuffers(alsource, 1, &firstBuffer)); } } return preloaded; @@ -227,14 +228,14 @@ void ALStream::update(double delta) { if (this->speaker == 0) { return; } - auto p_speaker = audio::get_speaker(this->speaker); - if (p_speaker == nullptr) { + auto speaker = audio::get_speaker(this->speaker); + if (speaker == nullptr) { this->speaker = 0; return; } - ALSpeaker* alspeaker = dynamic_cast(p_speaker); + ALSpeaker* alspeaker = dynamic_cast(speaker); assert(alspeaker != nullptr); - if (alspeaker->stopped) { + if (alspeaker->manuallyStopped) { this->speaker = 0; return; } @@ -245,11 +246,11 @@ void ALStream::update(double delta) { uint preloaded = enqueueBuffers(alsource); // alspeaker->stopped is assigned to false at ALSpeaker::play(...) - if (p_speaker->isStopped() && !alspeaker->stopped) { //TODO: -V560 false-positive? - if (preloaded || dynamic_cast(source.get())) { - p_speaker->play(); - } else { - p_speaker->stop(); + if (speaker->isStopped() && !alspeaker->manuallyStopped) { //TODO: -V560 false-positive? + if (preloaded) { + speaker->play(); + } else if (isStopOnEnd()){ + speaker->stop(); } } } @@ -290,6 +291,14 @@ void ALStream::setTime(duration_t time) { } } +bool ALStream::isStopOnEnd() const { + return stopOnEnd; +} + +void ALStream::setStopOnEnd(bool flag) { + stopOnEnd = flag; +} + ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel) : al(al), priority(priority), channel(channel), source(source) { } @@ -356,7 +365,7 @@ void ALSpeaker::setLoop(bool loop) { void ALSpeaker::play() { paused = false; - stopped = false; + manuallyStopped = false; auto p_channel = get_channel(this->channel); AL_CHECK(alSourcef( source, @@ -372,7 +381,7 @@ void ALSpeaker::pause() { } void ALSpeaker::stop() { - stopped = true; + manuallyStopped = true; if (source) { AL_CHECK(alSourceStop(source)); @@ -436,6 +445,11 @@ int ALSpeaker::getPriority() const { return priority; } + +bool ALSpeaker::isManuallyStopped() const { + return manuallyStopped; +} + static bool alc_enumeration_ext = false; ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) diff --git a/src/audio/AL/ALAudio.hpp b/src/audio/AL/ALAudio.hpp index 6fef62f07..39e6f3371 100644 --- a/src/audio/AL/ALAudio.hpp +++ b/src/audio/AL/ALAudio.hpp @@ -58,6 +58,7 @@ namespace audio { bool keepSource; char buffer[BUFFER_SIZE]; bool loop = false; + bool stopOnEnd = false; bool preloadBuffer(uint buffer, bool loop); void unqueueBuffers(uint alsource); @@ -80,6 +81,10 @@ namespace audio { void setTime(duration_t time) override; static inline constexpr uint STREAM_BUFFERS = 3; + + bool isStopOnEnd() const override; + + void setStopOnEnd(bool stopOnEnd) override; }; class ALInputDevice : public InputDevice { @@ -117,7 +122,7 @@ namespace audio { float volume = 0.0f; public: ALStream* stream = nullptr; - bool stopped = true; + bool manuallyStopped = true; bool paused = false; uint source; duration_t duration = 0.0f; @@ -157,6 +162,8 @@ namespace audio { bool isRelative() const override; int getPriority() const override; + + bool isManuallyStopped() const override; }; class ALAudio : public Backend { diff --git a/src/audio/NoAudio.hpp b/src/audio/NoAudio.hpp index 5012704b0..0da1b4d63 100644 --- a/src/audio/NoAudio.hpp +++ b/src/audio/NoAudio.hpp @@ -61,6 +61,13 @@ namespace audio { void setTime(duration_t time) override { } + + bool isStopOnEnd() const override { + return false; + } + + void setStopOnEnd(bool stopOnEnd) override { + } }; class NoAudio : public Backend { diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 60f44b04d..0069ff12f 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -464,8 +464,15 @@ void audio::update(double delta) { speaker->update(channel); } if (speaker->isStopped()) { - streams.erase(it->first); - it = speakers.erase(it); + auto foundStream = streams.find(it->first); + if (foundStream == streams.end() || + (!speaker->isManuallyStopped() && + foundStream->second->isStopOnEnd())) { + streams.erase(it->first); + it = speakers.erase(it); + } else { + it++; + } } else { it++; } diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index c394398ff..551fd7ec3 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -221,6 +221,9 @@ namespace audio { /// @brief Set playhead to the selected time /// @param time selected time virtual void setTime(duration_t time) = 0; + + virtual bool isStopOnEnd() const = 0; + virtual void setStopOnEnd(bool stopOnEnd) = 0; }; /// @brief Sound is an audio asset that supposed to support many @@ -355,6 +358,8 @@ namespace audio { inline bool isStopped() const { return getState() == State::stopped; } + + virtual bool isManuallyStopped() const = 0; }; class Backend { From be2dc1abe54f896864fa2354ee3d17ae57a39f2e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 30 Oct 2025 22:48:53 +0300 Subject: [PATCH 019/155] add PCMStream:create_sound method --- res/scripts/hud.lua | 10 +++++++ src/audio/MemoryPCMStream.cpp | 4 +++ src/audio/MemoryPCMStream.hpp | 2 ++ .../lua/usertypes/lua_type_pcmstream.cpp | 26 +++++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 7730ba5fa..a5aad766b 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -122,6 +122,16 @@ function on_hud_open() stream = PCMStream(44100, 1, 16) stream:share("test-stream") streamid = audio.play_stream_2d("test-stream", 2.0, 1.0, "ui") + + + s = PCMStream(44100, 1, 8) + local buffer = Bytearray(44100) + for i=1, #buffer do + buffer[i] = math.random(1, 8) + end + s:feed(buffer) + s:create_sound("test-sound") + audio.play_sound_2d("test-sound", 2.0, 1.0, "ui") end function on_hud_render() diff --git a/src/audio/MemoryPCMStream.cpp b/src/audio/MemoryPCMStream.cpp index 7fcf4b0ce..e0f40d5ef 100644 --- a/src/audio/MemoryPCMStream.cpp +++ b/src/audio/MemoryPCMStream.cpp @@ -61,3 +61,7 @@ bool MemoryPCMStream::isSeekable() const { } void MemoryPCMStream::seek(size_t position) {} + +size_t MemoryPCMStream::available() const { + return buffer.size(); +} diff --git a/src/audio/MemoryPCMStream.hpp b/src/audio/MemoryPCMStream.hpp index 1f94d27e7..bef2a05bc 100644 --- a/src/audio/MemoryPCMStream.hpp +++ b/src/audio/MemoryPCMStream.hpp @@ -31,6 +31,8 @@ namespace audio { bool isSeekable() const override; void seek(size_t position) override; + + size_t available() const; private: uint sampleRate; uint channels; diff --git a/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp index 3367813ea..a4a13ea42 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.cpp @@ -44,9 +44,35 @@ static int l_share(lua::State* L) { return 0; } +static int l_create_sound(lua::State* L) { + auto stream = touserdata(L, 1); + if (stream == nullptr) { + return 0; + } + auto alias = require_lstring(L, 2); + auto memoryStream = stream->getStream(); + + std::vector buffer(memoryStream->available()); + memoryStream->readFully(buffer.data(), buffer.size(), true); + + auto pcm = std::make_shared( + std::move(buffer), + 0, + memoryStream->getChannels(), + static_cast(memoryStream->getBitsPerSample()), + memoryStream->getSampleRate(), + memoryStream->isSeekable() + ); + auto sound = audio::create_sound(std::move(pcm), true); + auto assets = engine->getAssets(); + assets->store(std::move(sound), std::string(alias)); + return 0; +} + static std::unordered_map methods { {"feed", lua::wrap}, {"share", lua::wrap}, + {"create_sound", lua::wrap}, }; static int l_meta_meta_call(lua::State* L) { From 0e2b203fb2ad4ec508598abdd76ea2cf874340e7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 31 Oct 2025 22:53:04 +0300 Subject: [PATCH 020/155] add debug.get_pack_by_frame --- res/scripts/stdmin.lua | 80 +++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index df5c7ce22..4e01e22ae 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,3 +1,40 @@ +function crc32(bytes, chksum) + local chksum = chksum or 0 + + local length = #bytes + if type(bytes) == "table" then + local buffer_len = _ffi.new('int[1]', length) + local buffer = _ffi.new( + string.format("char[%s]", length) + ) + for i=1, length do + buffer[i - 1] = bytes[i] + end + bytes = _ffi.string(buffer, buffer_len[0]) + end + return _crc32(bytes, chksum) +end + +-- Check if given table is an array +function is_array(x) + if #x > 0 then + return true + end + for k, v in pairs(x) do + return false + end + return true +end + +-- Get entry-point and filename from `entry-point:filename` path +function parse_path(path) + local index = string.find(path, ':') + if index == nil then + error("invalid path syntax (':' missing)") + end + return string.sub(path, 1, index-1), string.sub(path, index+1, -1) +end + local breakpoints = {} local dbg_steps_mode = false local dbg_step_into_func = false @@ -5,6 +42,7 @@ local hook_lock = false local current_func local current_func_stack_size +local __parse_path = parse_path local _debug_getinfo = debug.getinfo local _debug_getlocal = debug.getlocal local __pause = debug.pause @@ -74,6 +112,11 @@ local __sendvalue = debug.__sendvalue debug.__pull_events = nil debug.__sendvalue = nil +function debug.get_pack_by_frame(func) + local prefix, _ = __parse_path(_debug_getinfo(func, "S").source) + return prefix +end + function debug.pull_events() if not is_debugging then return @@ -194,43 +237,6 @@ function __vc_Canvas_set_data(self, data) self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer))) end -function crc32(bytes, chksum) - local chksum = chksum or 0 - - local length = #bytes - if type(bytes) == "table" then - local buffer_len = _ffi.new('int[1]', length) - local buffer = _ffi.new( - string.format("char[%s]", length) - ) - for i=1, length do - buffer[i - 1] = bytes[i] - end - bytes = _ffi.string(buffer, buffer_len[0]) - end - return _crc32(bytes, chksum) -end - --- Check if given table is an array -function is_array(x) - if #x > 0 then - return true - end - for k, v in pairs(x) do - return false - end - return true -end - --- Get entry-point and filename from `entry-point:filename` path -function parse_path(path) - local index = string.find(path, ':') - if index == nil then - error("invalid path syntax (':' missing)") - end - return string.sub(path, 1, index-1), string.sub(path, index+1, -1) -end - function pack.is_installed(packid) return file.isfile(packid..":package.json") end From 284f24433c15fe3e6fca24468b418f7885f8be0a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 31 Oct 2025 23:18:26 +0300 Subject: [PATCH 021/155] extract debugging code to internal/debugging module --- res/modules/internal/debugging.lua | 146 +++++++++++++++++++++++++ res/scripts/stdlib.lua | 6 +- res/scripts/stdmin.lua | 152 +-------------------------- src/logic/scripting/lua/lua_util.cpp | 10 +- 4 files changed, 157 insertions(+), 157 deletions(-) create mode 100644 res/modules/internal/debugging.lua diff --git a/res/modules/internal/debugging.lua b/res/modules/internal/debugging.lua new file mode 100644 index 000000000..cef402025 --- /dev/null +++ b/res/modules/internal/debugging.lua @@ -0,0 +1,146 @@ +local breakpoints = {} +local dbg_steps_mode = false +local dbg_step_into_func = false +local hook_lock = false +local current_func +local current_func_stack_size + +local __parse_path = parse_path +local _debug_getinfo = debug.getinfo +local _debug_getlocal = debug.getlocal +local __pause = debug.pause +local __error = error +local __sethook = debug.sethook + +-- 'return' hook not called for some functions +-- todo: speedup +local function calc_stack_size() + local s = debug.traceback("", 2) + local count = 0 + for i in s:gmatch("\n") do + count = count + 1 + end + return count +end + +local is_debugging = debug.is_debugging() +if is_debugging then + __sethook(function (e, line) + if e == "return" then + local info = _debug_getinfo(2) + if info.func == current_func then + current_func = nil + end + end + if dbg_steps_mode and not hook_lock then + hook_lock = true + + if not dbg_step_into_func then + local func = _debug_getinfo(2).func + if func ~= current_func then + return + end + if current_func_stack_size ~= calc_stack_size() then + return + end + end + current_func = func + __pause("step") + debug.pull_events() + end + hook_lock = false + local bps = breakpoints[line] + if not bps then + return + end + local source = _debug_getinfo(2).source + if not bps[source] then + return + end + current_func = _debug_getinfo(2).func + current_func_stack_size = calc_stack_size() + __pause("breakpoint") + debug.pull_events() + end, "lr") +end + +local DBG_EVENT_SET_BREAKPOINT = 1 +local DBG_EVENT_RM_BREAKPOINT = 2 +local DBG_EVENT_STEP = 3 +local DBG_EVENT_STEP_INTO_FUNCTION = 4 +local DBG_EVENT_RESUME = 5 +local DBG_EVENT_GET_VALUE = 6 +local __pull_events = debug.__pull_events +local __sendvalue = debug.__sendvalue +debug.__pull_events = nil +debug.__sendvalue = nil + +function debug.get_pack_by_frame(func) + local prefix, _ = __parse_path(_debug_getinfo(func, "S").source) + return prefix +end + +function debug.pull_events() + if not is_debugging then + return + end + if not debug.is_debugging() then + is_debugging = false + __sethook() + end + local events = __pull_events() + if not events then + return + end + for i, event in ipairs(events) do + if event[1] == DBG_EVENT_SET_BREAKPOINT then + debug.set_breakpoint(event[2], event[3]) + elseif event[1] == DBG_EVENT_RM_BREAKPOINT then + debug.remove_breakpoint(event[2], event[3]) + elseif event[1] == DBG_EVENT_STEP then + dbg_steps_mode = true + dbg_step_into_func = false + elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then + dbg_steps_mode = true + dbg_step_into_func = true + elseif event[1] == DBG_EVENT_RESUME then + dbg_steps_mode = false + dbg_step_into_func = false + elseif event[1] == DBG_EVENT_GET_VALUE then + local _, value = _debug_getlocal(event[2] + 3, event[3]) + for _, key in ipairs(event[4]) do + if value == nil then + value = "error: index nil value" + break + end + value = value[key] + end + __sendvalue(value, event[2], event[3], event[4]) + __pause() + end + end +end + +function debug.set_breakpoint(source, line) + local bps = breakpoints[line] + if not bps then + bps = {} + breakpoints[line] = bps + end + bps[source] = true +end + +function debug.remove_breakpoint(source, line) + local bps = breakpoints[line] + if not bps then + return + end + bps[source] = nil +end + +function error(message, level) + if is_debugging then + __pause("exception", message) + end + __error(message, level) +end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 3f732e07c..cb25150bc 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -177,10 +177,8 @@ 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 - +require "core:internal/maths_inline" +require "core:internal/debugging" asserts = require "core:internal/asserts" events = require "core:internal/events" diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 4e01e22ae..783e054e1 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,3 +1,6 @@ +local _ffi = ffi +local _debug_getinfo = debug.getinfo + function crc32(bytes, chksum) local chksum = chksum or 0 @@ -35,153 +38,6 @@ function parse_path(path) return string.sub(path, 1, index-1), string.sub(path, index+1, -1) end -local breakpoints = {} -local dbg_steps_mode = false -local dbg_step_into_func = false -local hook_lock = false -local current_func -local current_func_stack_size - -local __parse_path = parse_path -local _debug_getinfo = debug.getinfo -local _debug_getlocal = debug.getlocal -local __pause = debug.pause -local __error = error -local __sethook = debug.sethook - --- 'return' hook not called for some functions --- todo: speedup -local function calc_stack_size() - local s = debug.traceback("", 2) - local count = 0 - for i in s:gmatch("\n") do - count = count + 1 - end - return count -end - -local is_debugging = debug.is_debugging() -if is_debugging then - __sethook(function (e, line) - if e == "return" then - local info = _debug_getinfo(2) - if info.func == current_func then - current_func = nil - end - end - if dbg_steps_mode and not hook_lock then - hook_lock = true - - if not dbg_step_into_func then - local func = _debug_getinfo(2).func - if func ~= current_func then - return - end - if current_func_stack_size ~= calc_stack_size() then - return - end - end - current_func = func - __pause("step") - debug.pull_events() - end - hook_lock = false - local bps = breakpoints[line] - if not bps then - return - end - local source = _debug_getinfo(2).source - if not bps[source] then - return - end - current_func = _debug_getinfo(2).func - current_func_stack_size = calc_stack_size() - __pause("breakpoint") - debug.pull_events() - end, "lr") -end - -local DBG_EVENT_SET_BREAKPOINT = 1 -local DBG_EVENT_RM_BREAKPOINT = 2 -local DBG_EVENT_STEP = 3 -local DBG_EVENT_STEP_INTO_FUNCTION = 4 -local DBG_EVENT_RESUME = 5 -local DBG_EVENT_GET_VALUE = 6 -local __pull_events = debug.__pull_events -local __sendvalue = debug.__sendvalue -debug.__pull_events = nil -debug.__sendvalue = nil - -function debug.get_pack_by_frame(func) - local prefix, _ = __parse_path(_debug_getinfo(func, "S").source) - return prefix -end - -function debug.pull_events() - if not is_debugging then - return - end - if not debug.is_debugging() then - is_debugging = false - __sethook() - end - local events = __pull_events() - if not events then - return - end - for i, event in ipairs(events) do - if event[1] == DBG_EVENT_SET_BREAKPOINT then - debug.set_breakpoint(event[2], event[3]) - elseif event[1] == DBG_EVENT_RM_BREAKPOINT then - debug.remove_breakpoint(event[2], event[3]) - elseif event[1] == DBG_EVENT_STEP then - dbg_steps_mode = true - dbg_step_into_func = false - elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then - dbg_steps_mode = true - dbg_step_into_func = true - elseif event[1] == DBG_EVENT_RESUME then - dbg_steps_mode = false - dbg_step_into_func = false - elseif event[1] == DBG_EVENT_GET_VALUE then - local _, value = _debug_getlocal(event[2] + 3, event[3]) - for _, key in ipairs(event[4]) do - if value == nil then - value = "error: index nil value" - break - end - value = value[key] - end - __sendvalue(value, event[2], event[3], event[4]) - __pause() - end - end -end - -function debug.set_breakpoint(source, line) - local bps = breakpoints[line] - if not bps then - bps = {} - breakpoints[line] = bps - end - bps[source] = true -end - -function debug.remove_breakpoint(source, line) - local bps = breakpoints[line] - if not bps then - return - end - bps[source] = nil -end - -function error(message, level) - if is_debugging then - __pause("exception", message) - end - __error(message, level) -end - -- Lua has no parallelizm, also _set_data does not call any lua functions so -- may be reused one global ffi buffer per lua_State local canvas_ffi_buffer @@ -764,7 +620,7 @@ function __vc__error(msg, frame, n, lastn) if events then local frames = debug.get_traceback(1) events.emit( - "core:error", msg, + "core:error", msg, table.sub(frames, 1 + (n or 0), lastn and #frames-lastn) ) end diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index 401255c72..a09f50867 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -155,18 +155,18 @@ static int l_error_handler(lua_State* L) { } int lua::call(State* L, int argc, int nresults) { - int handler_pos = gettop(L) - argc; + int handlerPos = gettop(L) - argc; pushcfunction(L, l_error_handler); - insert(L, handler_pos); + insert(L, handlerPos); int top = gettop(L); - if (lua_pcall(L, argc, nresults, handler_pos)) { + if (lua_pcall(L, argc, nresults, handlerPos)) { std::string log = tostring(L, -1); pop(L); - remove(L, handler_pos); + remove(L, handlerPos); throw luaerror(log); } int added = gettop(L) - (top - argc - 1); - remove(L, handler_pos); + remove(L, handlerPos); return added; } From fe8e4ba7b41c8c974c653248054d59cb2e2f255e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 31 Oct 2025 23:30:29 +0300 Subject: [PATCH 022/155] add internal/audio_input module --- res/modules/internal/audio_input.lua | 27 +++++++++++++++++++++++++++ res/scripts/hud.lua | 10 ++++++++-- res/scripts/stdlib.lua | 1 + res/scripts/stdmin.lua | 2 +- res/texts/ru_RU.txt | 3 ++- 5 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 res/modules/internal/audio_input.lua diff --git a/res/modules/internal/audio_input.lua b/res/modules/internal/audio_input.lua new file mode 100644 index 000000000..3540e88b2 --- /dev/null +++ b/res/modules/internal/audio_input.lua @@ -0,0 +1,27 @@ +local audio_input_tokens_store = {} +audio.input = {} + +local _gui_confirm = gui.confirm +local _base64_encode_urlsafe = base64.encode_urlsafe +local _random_bytes = random.bytes +local _debug_pack_by_frame = debug.get_pack_by_frame +local _audio_fetch_input = audio.fetch_input + +function audio.fetch_input(token, size) + if audio_input_tokens_store[token] then + return _audio_fetch_input(size) + end + error("access denied") +end + +local GRAND_PERMISSION_MSG = "Grant '%{0}' pack audio recording permission?" + +function audio.input.request_open(callback) + local token = _base64_encode_urlsafe(_random_bytes(18)) + local caller = _debug_pack_by_frame(1) + _gui_confirm(gui.str(GRAND_PERMISSION_MSG):gsub("%%{0}", caller), function() + audio_input_tokens_store[token] = caller + callback(token) + menu:reset() + end) +end diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index a5aad766b..73355ddfc 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -134,6 +134,10 @@ function on_hud_open() audio.play_sound_2d("test-sound", 2.0, 1.0, "ui") end +audio.input.request_open(function(token) + input_access_token = token +end) + function on_hud_render() if hud.hand_controller then hud.hand_controller() @@ -141,6 +145,8 @@ function on_hud_render() update_hand() end - local bytes = audio.fetch_input() - stream:feed(bytes) + if input_access_token then + local bytes = audio.fetch_input(input_access_token) + stream:feed(bytes) + end end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index cb25150bc..d0077b6f8 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -179,6 +179,7 @@ end require "core:internal/maths_inline" require "core:internal/debugging" +require "core:internal/audio_input" asserts = require "core:internal/asserts" events = require "core:internal/events" diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 783e054e1..30343d7db 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -2,7 +2,7 @@ local _ffi = ffi local _debug_getinfo = debug.getinfo function crc32(bytes, chksum) - local chksum = chksum or 0 + chksum = chksum or 0 local length = #bytes if type(bytes) == "table" then diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 03ee8322c..cc6de4756 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -25,7 +25,8 @@ Grant %{0} pack modification permission?=Выдать разрешение на Error at line %{0}=Ошибка на строке %{0} Run=Запустить Filter=Фильтр -Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку: +Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку: +Grant '%{0}' pack audio recording permission?=Выдать паку '%{0}' разрешение на запись звука? editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить devtools.traceback=Стек вызовов (от последнего) From fcbbdd649bbad1214bf4bc44b6f87a6328711acf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 31 Oct 2025 23:54:56 +0300 Subject: [PATCH 023/155] split internal modules --- res/modules/internal/deprecated.lua | 48 ++ res/modules/internal/extensions/file.lua | 45 ++ res/modules/internal/extensions/inventory.lua | 71 +++ res/modules/internal/extensions/math.lua | 37 ++ res/modules/internal/extensions/pack.lua | 13 + res/modules/internal/extensions/string.lua | 126 +++++ res/modules/internal/extensions/table.lua | 179 +++++++ res/modules/internal/rules.lua | 70 +++ res/scripts/stdlib.lua | 206 +------- res/scripts/stdmin.lua | 456 +----------------- 10 files changed, 627 insertions(+), 624 deletions(-) create mode 100644 res/modules/internal/deprecated.lua create mode 100644 res/modules/internal/extensions/file.lua create mode 100644 res/modules/internal/extensions/inventory.lua create mode 100644 res/modules/internal/extensions/math.lua create mode 100644 res/modules/internal/extensions/pack.lua create mode 100644 res/modules/internal/extensions/string.lua create mode 100644 res/modules/internal/extensions/table.lua create mode 100644 res/modules/internal/rules.lua diff --git a/res/modules/internal/deprecated.lua b/res/modules/internal/deprecated.lua new file mode 100644 index 000000000..0be827955 --- /dev/null +++ b/res/modules/internal/deprecated.lua @@ -0,0 +1,48 @@ +-- --------- Deprecated functions ------ -- +local function wrap_deprecated(func, name, alternatives) + return function (...) + on_deprecated_call(name, alternatives) + return func(...) + end +end + +block_index = wrap_deprecated(block.index, "block_index", "block.index") +block_name = wrap_deprecated(block.name, "block_name", "block.name") +blocks_count = wrap_deprecated(block.defs_count, "blocks_count", "block.defs_count") +is_solid_at = wrap_deprecated(block.is_solid_at, "is_solid_at", "block.is_solid_at") +is_replaceable_at = wrap_deprecated(block.is_replaceable_at, "is_replaceable_at", "block.is_replaceable_at") +set_block = wrap_deprecated(block.set, "set_block", "block.set") +get_block = wrap_deprecated(block.get, "get_block", "block.get") +get_block_X = wrap_deprecated(block.get_X, "get_block_X", "block.get_X") +get_block_Y = wrap_deprecated(block.get_Y, "get_block_Y", "block.get_Y") +get_block_Z = wrap_deprecated(block.get_Z, "get_block_Z", "block.get_Z") +get_block_states = wrap_deprecated(block.get_states, "get_block_states", "block.get_states") +set_block_states = wrap_deprecated(block.set_states, "set_block_states", "block.set_states") +get_block_rotation = wrap_deprecated(block.get_rotation, "get_block_rotation", "block.get_rotation") +set_block_rotation = wrap_deprecated(block.set_rotation, "set_block_rotation", "block.set_rotation") +get_block_user_bits = wrap_deprecated(block.get_user_bits, "get_block_user_bits", "block.get_user_bits") +set_block_user_bits = wrap_deprecated(block.set_user_bits, "set_block_user_bits", "block.set_user_bits") + +function load_script(path, nocache) + on_deprecated_call("load_script", "require or loadstring") + return __load_script(path, nocache) +end + +_dofile = dofile +-- Replaces dofile('*/content/packid/*') with load_script('packid:*') +function dofile(path) + on_deprecated_call("dofile", "require or loadstring") + local index = string.find(path, "/content/") + if index then + local newpath = string.sub(path, index+9) + index = string.find(newpath, "/") + if index then + local label = string.sub(newpath, 1, index-1) + newpath = label..':'..string.sub(newpath, index+1) + if file.isfile(newpath) then + return __load_script(newpath, true) + end + end + end + return _dofile(path) +end diff --git a/res/modules/internal/extensions/file.lua b/res/modules/internal/extensions/file.lua new file mode 100644 index 000000000..0f1040128 --- /dev/null +++ b/res/modules/internal/extensions/file.lua @@ -0,0 +1,45 @@ +function file.name(path) + return path:match("([^:/\\]+)$") +end + +function file.stem(path) + local name = file.name(path) + return name:match("(.+)%.[^%.]+$") or name +end + +function file.ext(path) + return path:match("%.([^:/\\]+)$") +end + +function file.prefix(path) + return path:match("^([^:]+)") +end + +function file.parent(path) + local dir = path:match("(.*)/") + if not dir then + return file.prefix(path)..":" + end + return dir +end + +function file.path(path) + local pos = path:find(':') + return path:sub(pos + 1) +end + +function file.join(a, b) + if a[#a] == ':' then + return a .. b + end + return a .. "/" .. b +end + +function file.readlines(path) + local str = file.read(path) + local lines = {} + for s in str:gmatch("[^\r\n]+") do + table.insert(lines, s) + end + return lines +end diff --git a/res/modules/internal/extensions/inventory.lua b/res/modules/internal/extensions/inventory.lua new file mode 100644 index 000000000..799eecb00 --- /dev/null +++ b/res/modules/internal/extensions/inventory.lua @@ -0,0 +1,71 @@ +function inventory.get_uses(invid, slot) + local uses = inventory.get_data(invid, slot, "uses") + if uses == nil then + return item.uses(inventory.get(invid, slot)) + end + return uses +end + +function inventory.use(invid, slot) + local itemid, count = inventory.get(invid, slot) + if itemid == nil then + return + end + local item_uses = inventory.get_uses(invid, slot) + if item_uses == nil then + return + end + if item_uses == 1 then + inventory.set(invid, slot, itemid, count - 1) + elseif item_uses > 1 then + inventory.set_data(invid, slot, "uses", item_uses - 1) + end +end + +function inventory.decrement(invid, slot, count) + count = count or 1 + local itemid, itemcount = inventory.get(invid, slot) + if itemcount <= count then + inventory.set(invid, slot, 0) + else + inventory.set_count(invid, slot, itemcount - 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 + return + end + if caption == nil or type(caption) ~= "string" then + caption = "" + end + 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 + return + end + if description == nil or type(description) ~= "string" then + description = "" + end + inventory.set_data(invid, slot, "description", description) +end diff --git a/res/modules/internal/extensions/math.lua b/res/modules/internal/extensions/math.lua new file mode 100644 index 000000000..d906e94a2 --- /dev/null +++ b/res/modules/internal/extensions/math.lua @@ -0,0 +1,37 @@ +function math.clamp(_in, low, high) + return math.min(math.max(_in, low), high) +end + +function math.rand(low, high) + return low + (high - low) * math.random() +end + +function math.normalize(num, conf) + conf = conf or 1 + + return (num / conf) % 1 +end + +function math.round(num, places) + places = places or 0 + + local mult = 10 ^ places + return math.floor(num * mult + 0.5) / mult +end + +function math.sum(...) + local numbers = nil + local sum = 0 + + if type(...) == "table" then + numbers = ... + else + numbers = {...} + end + + for _, v in ipairs(numbers) do + sum = sum + v + end + + return sum +end diff --git a/res/modules/internal/extensions/pack.lua b/res/modules/internal/extensions/pack.lua new file mode 100644 index 000000000..c43c12038 --- /dev/null +++ b/res/modules/internal/extensions/pack.lua @@ -0,0 +1,13 @@ +function pack.is_installed(packid) + return file.isfile(packid..":package.json") +end + +function pack.data_file(packid, name) + file.mkdirs("world:data/"..packid) + return "world:data/"..packid.."/"..name +end + +function pack.shared_file(packid, name) + file.mkdirs("config:"..packid) + return "config:"..packid.."/"..name +end diff --git a/res/modules/internal/extensions/string.lua b/res/modules/internal/extensions/string.lua new file mode 100644 index 000000000..f76470df6 --- /dev/null +++ b/res/modules/internal/extensions/string.lua @@ -0,0 +1,126 @@ +local pattern_escape_replacements = { + ["("] = "%(", + [")"] = "%)", + ["."] = "%.", + ["%"] = "%%", + ["+"] = "%+", + ["-"] = "%-", + ["*"] = "%*", + ["?"] = "%?", + ["["] = "%[", + ["]"] = "%]", + ["^"] = "%^", + ["$"] = "%$", + ["\0"] = "%z" +} + +function string.pattern_safe(str) + return string.gsub(str, ".", pattern_escape_replacements) +end + +local string_sub = string.sub +local string_find = string.find +local string_len = string.len +function string.explode(separator, str, withpattern) + if (withpattern == nil) then withpattern = false end + + local ret = {} + local current_pos = 1 + + for i = 1, string_len(str) do + local start_pos, end_pos = string_find( + str, separator, current_pos, not withpattern) + if (not start_pos) then break end + ret[i] = string_sub(str, current_pos, start_pos - 1) + current_pos = end_pos + 1 + end + + ret[#ret + 1] = string_sub(str, current_pos) + + return ret +end + +function string.split(str, delimiter) + return string.explode(delimiter, str) +end + +function string.formatted_time(seconds, format) + if (not seconds) then seconds = 0 end + local hours = math.floor(seconds / 3600) + local minutes = math.floor((seconds / 60) % 60) + local millisecs = (seconds - math.floor(seconds)) * 1000 + seconds = math.floor(seconds % 60) + + if (format) then + return string.format(format, minutes, seconds, millisecs) + else + return { h = hours, m = minutes, s = seconds, ms = millisecs } + end +end + +function string.replace(str, tofind, toreplace) + local tbl = string.explode(tofind, str) + if (tbl[1]) then return table.concat(tbl, toreplace) end + return str +end + +function string.trim(s, char) + if char then char = string.pattern_safe(char) else char = "%s" end + return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s +end + +function string.trim_right(s, char) + if char then char = string.pattern_safe(char) else char = "%s" end + return string.match(s, "^(.-)" .. char .. "*$") or s +end + +function string.trim_left(s, char) + if char then char = string.pattern_safe(char) else char = "%s" end + return string.match(s, "^" .. char .. "*(.+)$") or s +end + +function string.pad(str, size, char) + char = char == nil and " " or char + + local padding = math.floor((size - #str) / 2) + local extra_padding = (size - #str) % 2 + + return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding) +end + +function string.left_pad(str, size, char) + char = char == nil and " " or char + + local left_padding = size - #str + return string.rep(char, left_padding) .. str +end + +function string.right_pad(str, size, char) + char = char == nil and " " or char + + local right_padding = size - #str + return str .. string.rep(char, right_padding) +end + +string.lower = utf8.lower +string.upper = utf8.upper +string.escape = utf8.escape + +local meta = getmetatable("") + +function meta:__index(key) + local val = string[key] + if (val ~= nil) then + return val + elseif (tonumber(key)) then + return string.sub(self, key, key) + end +end + +function string.starts_with(str, start) + return string.sub(str, 1, string.len(start)) == start +end + +function string.ends_with(str, endStr) + return endStr == "" or string.sub(str, -string.len(endStr)) == endStr +end diff --git a/res/modules/internal/extensions/table.lua b/res/modules/internal/extensions/table.lua new file mode 100644 index 000000000..33047df31 --- /dev/null +++ b/res/modules/internal/extensions/table.lua @@ -0,0 +1,179 @@ +function table.copy(t) + local copied = {} + + for k, v in pairs(t) do + copied[k] = v + end + + return copied +end + +function table.deep_copy(t) + local copied = {} + + for k, v in pairs(t) do + if type(v) == "table" then + copied[k] = table.deep_copy(v) + else + copied[k] = v + end + end + + return setmetatable(copied, getmetatable(t)) +end + +function table.count_pairs(t) + local count = 0 + + for k, v in pairs(t) do + count = count + 1 + end + + return count +end + +function table.random(t) + return t[math.random(1, #t)] +end + +function table.shuffle(t) + for i = #t, 2, -1 do + local j = math.random(i) + t[i], t[j] = t[j], t[i] + end + + return t +end + +function table.merge(t1, t2) + for i, v in pairs(t2) do + if type(i) == "number" then + t1[#t1 + 1] = v + elseif t1[i] == nil then + t1[i] = v + end + end + + return t1 +end + +function table.map(t, func) + for i, v in pairs(t) do + t[i] = func(i, v) + end + + return t +end + +function table.filter(t, func) + + for i = #t, 1, -1 do + if not func(i, t[i]) then + table.remove(t, i) + end + end + + local size = #t + + for i, v in pairs(t) do + local i_type = type(i) + if i_type == "number" then + if i < 1 or i > size then + if not func(i, v) then + t[i] = nil + end + end + else + if not func(i, v) then + t[i] = nil + end + end + end + + return t +end + +function table.set_default(t, key, default) + if t[key] == nil then + t[key] = default + return default + end + + return t[key] +end + +function table.flat(t) + local flat = {} + + for _, v in pairs(t) do + if type(v) == "table" then + table.merge(flat, v) + else + table.insert(flat, v) + end + end + + return flat +end + +function table.deep_flat(t) + local flat = {} + + for _, v in pairs(t) do + if type(v) == "table" then + table.merge(flat, table.deep_flat(v)) + else + table.insert(flat, v) + end + end + + return flat +end + +function table.sub(arr, start, stop) + local res = {} + start = start or 1 + stop = stop or #arr + + for i = start, stop do + table.insert(res, arr[i]) + end + + return res +end + +function table.has(t, x) + for i,v in ipairs(t) do + if v == x then + return true + end + end + return false +end + +function table.index(t, x) + for i,v in ipairs(t) do + if v == x then + return i + end + end + return -1 +end + +function table.remove_value(t, x) + local index = table.index(t, x) + if index ~= -1 then + table.remove(t, index) + end +end + +function table.tostring(t) + local s = '[' + for i,v in ipairs(t) do + s = s..tostring(v) + if i < #t then + s = s..', ' + end + end + return s..']' +end diff --git a/res/modules/internal/rules.lua b/res/modules/internal/rules.lua new file mode 100644 index 000000000..a1a5a8c32 --- /dev/null +++ b/res/modules/internal/rules.lua @@ -0,0 +1,70 @@ +local rules = {nexid = 1, rules = {}} + +function rules.get_rule(name) + local rule = rules.rules[name] + if rule == nil then + rule = {listeners={}} + rules.rules[name] = rule + end + return rule +end + +function rules.get(name) + local rule = rules.rules[name] + if rule == nil then + return nil + end + return rule.value +end + +function rules.set(name, value) + local rule = rules.get_rule(name) + rule.value = value + for _, handler in pairs(rule.listeners) do + handler(value) + end +end + +function rules.reset(name) + local rule = rules.get_rule(name) + rules.set(rule.default) +end + +function rules.listen(name, handler) + local rule = rules.get_rule(name) + local id = rules.nexid + rules.nextid = rules.nexid + 1 + rule.listeners[utf8.encode(id)] = handler + return id +end + +function rules.create(name, value, handler) + local rule = rules.get_rule(name) + rule.default = value + + local handlerid + if handler ~= nil then + handlerid = rules.listen(name, handler) + end + if rules.get(name) == nil then + rules.set(name, value) + elseif handler then + handler(rules.get(name)) + end + return handlerid +end + +function rules.unlisten(name, id) + local rule = rules.rules[name] + if rule == nil then + return + end + rule.listeners[utf8.encode(id)] = nil +end + +function rules.clear() + rules.rules = {} + rules.nextid = 1 +end + +return rules diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index d0077b6f8..71a8256ee 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -105,81 +105,10 @@ elseif __vc_app then complete_app_lib(__vc_app) end -function inventory.get_uses(invid, slot) - local uses = inventory.get_data(invid, slot, "uses") - if uses == nil then - return item.uses(inventory.get(invid, slot)) - end - return uses -end - -function inventory.use(invid, slot) - local itemid, count = inventory.get(invid, slot) - if itemid == nil then - return - end - local item_uses = inventory.get_uses(invid, slot) - if item_uses == nil then - return - end - if item_uses == 1 then - inventory.set(invid, slot, itemid, count - 1) - elseif item_uses > 1 then - inventory.set_data(invid, slot, "uses", item_uses - 1) - end -end - -function inventory.decrement(invid, slot, count) - count = count or 1 - local itemid, itemcount = inventory.get(invid, slot) - if itemcount <= count then - inventory.set(invid, slot, 0) - else - inventory.set_count(invid, slot, itemcount - 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 - return - end - if caption == nil or type(caption) ~= "string" then - caption = "" - end - 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 - return - end - if description == nil or type(description) ~= "string" then - description = "" - end - inventory.set_data(invid, slot, "description", description) -end - require "core:internal/maths_inline" require "core:internal/debugging" require "core:internal/audio_input" +require "core:internal/extensions/inventory" asserts = require "core:internal/asserts" events = require "core:internal/events" @@ -306,86 +235,20 @@ else os.pid = ffi.C.getpid() end -ffi = nil -__vc_lock_internal_modules() - math.randomseed(time.uptime() * 1536227939) -rules = {nexid = 1, rules = {}} +rules = require "core:internal/rules" local _rules = rules -function _rules.get_rule(name) - local rule = _rules.rules[name] - if rule == nil then - rule = {listeners={}} - _rules.rules[name] = rule - end - return rule -end - -function _rules.get(name) - local rule = _rules.rules[name] - if rule == nil then - return nil - end - return rule.value -end - -function _rules.set(name, value) - local rule = _rules.get_rule(name) - rule.value = value - for _, handler in pairs(rule.listeners) do - handler(value) - end -end - -function _rules.reset(name) - local rule = _rules.get_rule(name) - _rules.set(rule.default) -end - -function _rules.listen(name, handler) - local rule = _rules.get_rule(name) - local id = _rules.nexid - _rules.nextid = _rules.nexid + 1 - rule.listeners[utf8.encode(id)] = handler - return id -end - -function _rules.create(name, value, handler) - local rule = _rules.get_rule(name) - rule.default = value - - local handlerid - if handler ~= nil then - handlerid = _rules.listen(name, handler) - end - if _rules.get(name) == nil then - _rules.set(name, value) - elseif handler then - handler(_rules.get(name)) - end - return handlerid -end - -function _rules.unlisten(name, id) - local rule = _rules.rules[name] - if rule == nil then - return - end - rule.listeners[utf8.encode(id)] = nil -end - -function _rules.clear() - _rules.rules = {} - _rules.nextid = 1 -end - function __vc_on_hud_open() + local _hud_is_content_access = hud._is_content_access + local _hud_set_content_access = hud._set_content_access + local _hud_set_debug_cheats = hud._set_debug_cheats + _rules.create("allow-cheats", true) - _rules.create("allow-content-access", hud._is_content_access(), function(value) - hud._set_content_access(value) + _rules.create("allow-content-access", _hud_is_content_access(), function(value) + _hud_set_content_access(value) end) _rules.create("allow-flight", true, function(value) input.set_enabled("player.flight", value) @@ -406,7 +269,7 @@ function __vc_on_hud_open() input.set_enabled("player.fast_interaction", value) end) _rules.create("allow-debug-cheats", true, function(value) - hud._set_debug_cheats(value) + _hud_set_debug_cheats(value) end) input.add_callback("devtools.console", function() if menu.page ~= "" then @@ -605,6 +468,7 @@ local _getinfo = debug.getinfo for i, name in ipairs(removed_names) do debug[name] = nil end + debug.getinfo = function(lvl, fields) if type(lvl) == "number" then lvl = lvl + 1 @@ -614,51 +478,7 @@ debug.getinfo = function(lvl, fields) return debuginfo end --- --------- Deprecated functions ------ -- -local function wrap_deprecated(func, name, alternatives) - return function (...) - on_deprecated_call(name, alternatives) - return func(...) - end -end +require "core:internal/deprecated" -block_index = wrap_deprecated(block.index, "block_index", "block.index") -block_name = wrap_deprecated(block.name, "block_name", "block.name") -blocks_count = wrap_deprecated(block.defs_count, "blocks_count", "block.defs_count") -is_solid_at = wrap_deprecated(block.is_solid_at, "is_solid_at", "block.is_solid_at") -is_replaceable_at = wrap_deprecated(block.is_replaceable_at, "is_replaceable_at", "block.is_replaceable_at") -set_block = wrap_deprecated(block.set, "set_block", "block.set") -get_block = wrap_deprecated(block.get, "get_block", "block.get") -get_block_X = wrap_deprecated(block.get_X, "get_block_X", "block.get_X") -get_block_Y = wrap_deprecated(block.get_Y, "get_block_Y", "block.get_Y") -get_block_Z = wrap_deprecated(block.get_Z, "get_block_Z", "block.get_Z") -get_block_states = wrap_deprecated(block.get_states, "get_block_states", "block.get_states") -set_block_states = wrap_deprecated(block.set_states, "set_block_states", "block.set_states") -get_block_rotation = wrap_deprecated(block.get_rotation, "get_block_rotation", "block.get_rotation") -set_block_rotation = wrap_deprecated(block.set_rotation, "set_block_rotation", "block.set_rotation") -get_block_user_bits = wrap_deprecated(block.get_user_bits, "get_block_user_bits", "block.get_user_bits") -set_block_user_bits = wrap_deprecated(block.set_user_bits, "set_block_user_bits", "block.set_user_bits") - -function load_script(path, nocache) - on_deprecated_call("load_script", "require or loadstring") - return __load_script(path, nocache) -end - -_dofile = dofile --- Replaces dofile('*/content/packid/*') with load_script('packid:*') -function dofile(path) - on_deprecated_call("dofile", "require or loadstring") - local index = string.find(path, "/content/") - if index then - local newpath = string.sub(path, index+9) - index = string.find(newpath, "/") - if index then - local label = string.sub(newpath, 1, index-1) - newpath = label..':'..string.sub(newpath, index+1) - if file.isfile(newpath) then - return __load_script(newpath, true) - end - end - end - return _dofile(path) -end +ffi = nil +__vc_lock_internal_modules() diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 30343d7db..7e8678e58 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -42,6 +42,26 @@ end -- may be reused one global ffi buffer per lua_State local canvas_ffi_buffer local canvas_ffi_buffer_size = 0 +local _ffi = ffi +function __vc_Canvas_set_data(self, data) + if type(data) == "cdata" then + self:_set_data(tostring(_ffi.cast("uintptr_t", data))) + end + local width = self.width + local height = self.height + + local size = width * height * 4 + if size > canvas_ffi_buffer_size then + canvas_ffi_buffer = _ffi.new( + string.format("unsigned char[%s]", size) + ) + canvas_ffi_buffer_size = size + end + for i=0, size - 1 do + canvas_ffi_buffer[i] = data[i + 1] + end + self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer))) +end local ipairs_mt_supported = false for i, _ in ipairs(setmetatable({l={1}}, { @@ -72,42 +92,6 @@ function await(co) return res, err end -local _ffi = ffi -function __vc_Canvas_set_data(self, data) - if type(data) == "cdata" then - self:_set_data(tostring(_ffi.cast("uintptr_t", data))) - end - local width = self.width - local height = self.height - - local size = width * height * 4 - if size > canvas_ffi_buffer_size then - canvas_ffi_buffer = _ffi.new( - string.format("unsigned char[%s]", size) - ) - canvas_ffi_buffer_size = size - end - for i=0, size - 1 do - canvas_ffi_buffer[i] = data[i + 1] - end - self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer))) -end - -function pack.is_installed(packid) - return file.isfile(packid..":package.json") -end - -function pack.data_file(packid, name) - file.mkdirs("world:data/"..packid) - return "world:data/"..packid.."/"..name -end - -function pack.shared_file(packid, name) - file.mkdirs("config:"..packid) - return "config:"..packid.."/"..name -end - - function timeit(iters, func, ...) local tm = os.clock() for i=1,iters do @@ -118,364 +102,6 @@ end ---------------------------------------------- -function math.clamp(_in, low, high) - return math.min(math.max(_in, low), high) -end - -function math.rand(low, high) - return low + (high - low) * math.random() -end - -function math.normalize(num, conf) - conf = conf or 1 - - return (num / conf) % 1 -end - -function math.round(num, places) - places = places or 0 - - local mult = 10 ^ places - return math.floor(num * mult + 0.5) / mult -end - -function math.sum(...) - local numbers = nil - local sum = 0 - - if type(...) == "table" then - numbers = ... - else - numbers = {...} - end - - for _, v in ipairs(numbers) do - sum = sum + v - end - - return sum -end - ----------------------------------------------- - -function table.copy(t) - local copied = {} - - for k, v in pairs(t) do - copied[k] = v - end - - return copied -end - -function table.deep_copy(t) - local copied = {} - - for k, v in pairs(t) do - if type(v) == "table" then - copied[k] = table.deep_copy(v) - else - copied[k] = v - end - end - - return setmetatable(copied, getmetatable(t)) -end - -function table.count_pairs(t) - local count = 0 - - for k, v in pairs(t) do - count = count + 1 - end - - return count -end - -function table.random(t) - return t[math.random(1, #t)] -end - -function table.shuffle(t) - for i = #t, 2, -1 do - local j = math.random(i) - t[i], t[j] = t[j], t[i] - end - - return t -end - -function table.merge(t1, t2) - for i, v in pairs(t2) do - if type(i) == "number" then - t1[#t1 + 1] = v - elseif t1[i] == nil then - t1[i] = v - end - end - - return t1 -end - -function table.map(t, func) - for i, v in pairs(t) do - t[i] = func(i, v) - end - - return t -end - -function table.filter(t, func) - - for i = #t, 1, -1 do - if not func(i, t[i]) then - table.remove(t, i) - end - end - - local size = #t - - for i, v in pairs(t) do - local i_type = type(i) - if i_type == "number" then - if i < 1 or i > size then - if not func(i, v) then - t[i] = nil - end - end - else - if not func(i, v) then - t[i] = nil - end - end - end - - return t -end - -function table.set_default(t, key, default) - if t[key] == nil then - t[key] = default - return default - end - - return t[key] -end - -function table.flat(t) - local flat = {} - - for _, v in pairs(t) do - if type(v) == "table" then - table.merge(flat, v) - else - table.insert(flat, v) - end - end - - return flat -end - -function table.deep_flat(t) - local flat = {} - - for _, v in pairs(t) do - if type(v) == "table" then - table.merge(flat, table.deep_flat(v)) - else - table.insert(flat, v) - end - end - - return flat -end - -function table.sub(arr, start, stop) - local res = {} - start = start or 1 - stop = stop or #arr - - for i = start, stop do - table.insert(res, arr[i]) - end - - return res -end - ----------------------------------------------- - -local pattern_escape_replacements = { - ["("] = "%(", - [")"] = "%)", - ["."] = "%.", - ["%"] = "%%", - ["+"] = "%+", - ["-"] = "%-", - ["*"] = "%*", - ["?"] = "%?", - ["["] = "%[", - ["]"] = "%]", - ["^"] = "%^", - ["$"] = "%$", - ["\0"] = "%z" -} - -function string.pattern_safe(str) - return string.gsub(str, ".", pattern_escape_replacements) -end - -local string_sub = string.sub -local string_find = string.find -local string_len = string.len -function string.explode(separator, str, withpattern) - if (withpattern == nil) then withpattern = false end - - local ret = {} - local current_pos = 1 - - for i = 1, string_len(str) do - local start_pos, end_pos = string_find( - str, separator, current_pos, not withpattern) - if (not start_pos) then break end - ret[i] = string_sub(str, current_pos, start_pos - 1) - current_pos = end_pos + 1 - end - - ret[#ret + 1] = string_sub(str, current_pos) - - return ret -end - -function string.split(str, delimiter) - return string.explode(delimiter, str) -end - -function string.formatted_time(seconds, format) - if (not seconds) then seconds = 0 end - local hours = math.floor(seconds / 3600) - local minutes = math.floor((seconds / 60) % 60) - local millisecs = (seconds - math.floor(seconds)) * 1000 - seconds = math.floor(seconds % 60) - - if (format) then - return string.format(format, minutes, seconds, millisecs) - else - return { h = hours, m = minutes, s = seconds, ms = millisecs } - end -end - -function string.replace(str, tofind, toreplace) - local tbl = string.explode(tofind, str) - if (tbl[1]) then return table.concat(tbl, toreplace) end - return str -end - -function string.trim(s, char) - if char then char = string.pattern_safe(char) else char = "%s" end - return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s -end - -function string.trim_right(s, char) - if char then char = string.pattern_safe(char) else char = "%s" end - return string.match(s, "^(.-)" .. char .. "*$") or s -end - -function string.trim_left(s, char) - if char then char = string.pattern_safe(char) else char = "%s" end - return string.match(s, "^" .. char .. "*(.+)$") or s -end - -function string.pad(str, size, char) - char = char == nil and " " or char - - local padding = math.floor((size - #str) / 2) - local extra_padding = (size - #str) % 2 - - return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding) -end - -function string.left_pad(str, size, char) - char = char == nil and " " or char - - local left_padding = size - #str - return string.rep(char, left_padding) .. str -end - -function string.right_pad(str, size, char) - char = char == nil and " " or char - - local right_padding = size - #str - return str .. string.rep(char, right_padding) -end - -string.lower = utf8.lower -string.upper = utf8.upper -string.escape = utf8.escape - -local meta = getmetatable("") - -function meta:__index(key) - local val = string[key] - if (val ~= nil) then - return val - elseif (tonumber(key)) then - return string.sub(self, key, key) - end -end - -function string.starts_with(str, start) - return string.sub(str, 1, string.len(start)) == start -end - -function string.ends_with(str, endStr) - return endStr == "" or string.sub(str, -string.len(endStr)) == endStr -end - -function table.has(t, x) - for i,v in ipairs(t) do - if v == x then - return true - end - end - return false -end - -function table.index(t, x) - for i,v in ipairs(t) do - if v == x then - return i - end - end - return -1 -end - -function table.remove_value(t, x) - local index = table.index(t, x) - if index ~= -1 then - table.remove(t, index) - end -end - -function table.tostring(t) - local s = '[' - for i,v in ipairs(t) do - s = s..tostring(v) - if i < #t then - s = s..', ' - end - end - return s..']' -end - -function file.readlines(path) - local str = file.read(path) - local lines = {} - for s in str:gmatch("[^\r\n]+") do - table.insert(lines, s) - end - return lines -end - function debug.count_frames() local frames = 1 while true do @@ -634,43 +260,11 @@ function __vc_warning(msg, detail, n) end end -function file.name(path) - return path:match("([^:/\\]+)$") -end - -function file.stem(path) - local name = file.name(path) - return name:match("(.+)%.[^%.]+$") or name -end - -function file.ext(path) - return path:match("%.([^:/\\]+)$") -end - -function file.prefix(path) - return path:match("^([^:]+)") -end - -function file.parent(path) - local dir = path:match("(.*)/") - if not dir then - return file.prefix(path)..":" - end - return dir -end - -function file.path(path) - local pos = path:find(':') - return path:sub(pos + 1) -end - -function file.join(a, b) - if a[#a] == ':' then - return a .. b - end - return a .. "/" .. b -end - +require "core:internal/extensions/pack" +require "core:internal/extensions/math" +require "core:internal/extensions/file" +require "core:internal/extensions/table" +require "core:internal/extensions/string" bit.compile = require "core:bitwise/compiler" bit.execute = require "core:bitwise/executor" From 5a65cbe07128a149dba7d44bbd69154da871d6c8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 1 Nov 2025 22:04:39 +0300 Subject: [PATCH 024/155] move PCMStream to audio --- res/scripts/hud.lua | 4 ++-- src/logic/scripting/lua/lua_engine.cpp | 8 +++++++- src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index 73355ddfc..db6f9b79d 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -119,12 +119,12 @@ function on_hud_open() hud.default_hand_controller = update_hand - stream = PCMStream(44100, 1, 16) + stream = audio.PCMStream(44100, 1, 16) stream:share("test-stream") streamid = audio.play_stream_2d("test-stream", 2.0, 1.0, "ui") - s = PCMStream(44100, 1, 8) + s = audio.PCMStream(44100, 1, 8) local buffer = Bytearray(44100) for i=1, #buffer do buffer[i] = math.random(1, 8) diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index c4507bb13..80e2cff78 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -131,7 +131,6 @@ void lua::init_state(State* L, StateType stateType) { newusertype(L); newusertype(L); newusertype(L); - newusertype(L); } void lua::initialize(const EnginePaths& paths, const CoreParameters& params) { @@ -186,5 +185,12 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) { } pop(L); } + newusertype(L); + if (getglobal(L, "audio")) { + if (getglobal(L, "__vc_PCMStream")) { + setfield(L, "PCMStream"); + } + pop(L); + } return L; } diff --git a/src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp index 479796931..524c7169f 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp +++ b/src/logic/scripting/lua/usertypes/lua_type_pcmstream.hpp @@ -18,7 +18,7 @@ namespace lua { return TYPENAME; } static int createMetatable(lua::State*); - inline static std::string TYPENAME = "PCMStream"; + inline static std::string TYPENAME = "__vc_PCMStream"; private: std::shared_ptr stream; }; From 86a4060a689e133d645429f08fcc217f847c13a9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 1 Nov 2025 23:06:26 +0300 Subject: [PATCH 025/155] feat: multiple audio fetchers support --- res/modules/internal/audio_input.lua | 21 +++++++++++++++++---- res/modules/internal/bytearray.lua | 21 +++++++++++++++++++-- res/scripts/stdlib.lua | 9 ++++----- res/scripts/stdmin.lua | 6 ++++++ src/logic/scripting/lua/libs/libaudio.cpp | 2 +- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/res/modules/internal/audio_input.lua b/res/modules/internal/audio_input.lua index 3540e88b2..c0aa638ed 100644 --- a/res/modules/internal/audio_input.lua +++ b/res/modules/internal/audio_input.lua @@ -5,21 +5,34 @@ local _gui_confirm = gui.confirm local _base64_encode_urlsafe = base64.encode_urlsafe local _random_bytes = random.bytes local _debug_pack_by_frame = debug.get_pack_by_frame -local _audio_fetch_input = audio.fetch_input +local _audio_fetch_input = audio.__fetch_input +audio.__fetch_input = nil +local MAX_FETCH = 44100 * 4 + +local total_fetch = Bytearray() + +function audio.__reset_fetch_buffer() + total_fetch:clear() +end function audio.fetch_input(token, size) + size = size or MAX_FETCH if audio_input_tokens_store[token] then - return _audio_fetch_input(size) + if #total_fetch >= size then + return total_fetch:sub(1, size) + end + total_fetch:append(_audio_fetch_input(size - #total_fetch)) + return total_fetch:sub() end error("access denied") end -local GRAND_PERMISSION_MSG = "Grant '%{0}' pack audio recording permission?" +local GRANT_PERMISSION_MSG = "Grant '%{0}' pack audio recording permission?" function audio.input.request_open(callback) local token = _base64_encode_urlsafe(_random_bytes(18)) local caller = _debug_pack_by_frame(1) - _gui_confirm(gui.str(GRAND_PERMISSION_MSG):gsub("%%{0}", caller), function() + _gui_confirm(gui.str(GRANT_PERMISSION_MSG):gsub("%%{0}", caller), function() audio_input_tokens_store[token] = caller callback(token) menu:reset() diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 56795499e..7d5d250ee 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -14,6 +14,8 @@ FFI.cdef[[ local malloc = FFI.C.malloc local free = FFI.C.free +local FFIBytearray +local bytearray_type local function grow_buffer(self, elems) local new_capacity = math.ceil(self.capacity / 0.75 + elems) @@ -119,6 +121,20 @@ local function get_capacity(self) return self.capacity end +local function sub(self, offset, length) + offset = offset or 1 + length = length or (self.size - offset + 1) + if offset < 1 or offset > self.size then + return FFIBytearray(0) + end + if offset + length - 1 > self.size then + length = self.size - offset + 1 + end + local buffer = malloc(length) + FFI.copy(buffer, self.bytes + (offset - 1), length) + return bytearray_type(buffer, length, length) +end + local bytearray_methods = { append=append, insert=insert, @@ -127,6 +143,7 @@ local bytearray_methods = { clear=clear, reserve=reserve, get_capacity=get_capacity, + sub=sub, } local bytearray_mt = { @@ -168,9 +185,9 @@ local bytearray_mt = { } bytearray_mt.__pairs = bytearray_mt.__ipairs -local bytearray_type = FFI.metatype("bytearray_t", bytearray_mt) +bytearray_type = FFI.metatype("bytearray_t", bytearray_mt) -local FFIBytearray = { +FFIBytearray = { __call = function (self, n) local t = type(n) if t == "string" then diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 71a8256ee..19c17384c 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -211,11 +211,6 @@ entities.get_all = function(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 - __vc_scripts_registry = require "core:internal/scripts_registry" file.open = require "core:internal/stream_providers/file" @@ -424,6 +419,9 @@ end local __post_runnables = {} +local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer +audio.__reset_fetch_buffer = nil + function __process_post_runnables() if #__post_runnables then for _, func in ipairs(__post_runnables) do @@ -449,6 +447,7 @@ function __process_post_runnables() __vc_named_coroutines[name] = nil end + fn_audio_reset_fetch_buffer() debug.pull_events() network.__process_events() block.__process_register_events() diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 7e8678e58..49714b990 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -265,6 +265,12 @@ require "core:internal/extensions/math" require "core:internal/extensions/file" require "core:internal/extensions/table" require "core:internal/extensions/string" + +local bytearray = require "core:internal/bytearray" +Bytearray = bytearray.FFIBytearray +Bytearray_as_string = bytearray.FFIBytearray_as_string +Bytearray_construct = function(...) return Bytearray(...) end + bit.compile = require "core:bitwise/compiler" bit.execute = require "core:bitwise/executor" diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index ed2e3db6c..37c26549d 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -476,7 +476,7 @@ const luaL_Reg audiolib[] = { {"get_velocity", lua::wrap}, {"count_speakers", lua::wrap}, {"count_streams", lua::wrap}, - {"fetch_input", lua::wrap}, + {"__fetch_input", lua::wrap}, {"get_input_devices_names", lua::wrap}, {"get_output_devices_names", lua::wrap}, {"set_input_device", lua::wrap}, From aa54e900d9b10f9e2e32dacc340bd65551d4a672 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Nov 2025 01:17:56 +0300 Subject: [PATCH 026/155] add U16view, I16view, U32view, I32view classes --- res/modules/internal/audio_input.lua | 4 +- res/modules/internal/bytearray.lua | 61 ++++++++++++++++++++++++++-- res/scripts/hud.lua | 9 ++-- res/scripts/stdmin.lua | 4 ++ 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/res/modules/internal/audio_input.lua b/res/modules/internal/audio_input.lua index c0aa638ed..b87c70712 100644 --- a/res/modules/internal/audio_input.lua +++ b/res/modules/internal/audio_input.lua @@ -19,10 +19,10 @@ function audio.fetch_input(token, size) size = size or MAX_FETCH if audio_input_tokens_store[token] then if #total_fetch >= size then - return total_fetch:sub(1, size) + return total_fetch:slice(1, size) end total_fetch:append(_audio_fetch_input(size - #total_fetch)) - return total_fetch:sub() + return total_fetch:slice() end error("access denied") end diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 7d5d250ee..c292d6a81 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -121,7 +121,7 @@ local function get_capacity(self) return self.capacity end -local function sub(self, offset, length) +local function slice(self, offset, length) offset = offset or 1 length = length or (self.size - offset + 1) if offset < 1 or offset > self.size then @@ -131,6 +131,9 @@ local function sub(self, offset, length) length = self.size - offset + 1 end local buffer = malloc(length) + if not buffer then + error("malloc(" .. length .. ") returned NULL") + end FFI.copy(buffer, self.bytes + (offset - 1), length) return bytearray_type(buffer, length, length) end @@ -143,7 +146,7 @@ local bytearray_methods = { clear=clear, reserve=reserve, get_capacity=get_capacity, - sub=sub, + slice=slice, } local bytearray_mt = { @@ -227,7 +230,59 @@ local function FFIBytearray_as_string(bytes) end end +local function create_FFIview_class(name, typename, typesize) + local FFIU16view_mt = { + __index = function(self, key) + if key <= 0 or key > self.size then + return + end + return self.ptr[key - 1] + end, + __newindex = function(self, key, value) + if key == self.size + 1 then + return append(self, value) + elseif key <= 0 or key > self.size then + return + end + self.ptr[key - 1] = value + end, + __len = function(self) + return self.size + end, + __tostring = function(self) + return string.format(name .. "[%s]{...}", tonumber(self.size)) + end, + __ipairs = function(self) + local i = 0 + return function() + i = i + 1 + if i <= self.size then + return i, self.ptr[i - 1] + end + end + end + } + return function (bytes) + local ptr = FFI.cast(typename .. "*", bytes.bytes) + local x = setmetatable({ + bytes=bytes, + ptr=ptr, + size=math.floor(bytes.size / typesize), + }, FFIU16view_mt) + return x + end +end + +local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2) +local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2) +local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4) +local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4) + return { FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), - FFIBytearray_as_string = FFIBytearray_as_string + FFIBytearray_as_string = FFIBytearray_as_string, + FFIU16view = FFIU16view, + FFII16view = FFII16view, + FFIU32view = FFIU32view, + FFII32view = FFII32view, } diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index db6f9b79d..3e2fecb1b 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -124,10 +124,11 @@ function on_hud_open() streamid = audio.play_stream_2d("test-stream", 2.0, 1.0, "ui") - s = audio.PCMStream(44100, 1, 8) - local buffer = Bytearray(44100) - for i=1, #buffer do - buffer[i] = math.random(1, 8) + s = audio.PCMStream(44100, 1, 16) + local buffer = Bytearray(44100 * 4) + local view = U16view(buffer) + for i=1, view.size do + view[i] = 16000 + math.sin(i / 200.0 * (1 + i * 0.0001)) * 2000 end s:feed(buffer) s:create_sound("test-sound") diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 49714b990..63409c363 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -269,6 +269,10 @@ require "core:internal/extensions/string" local bytearray = require "core:internal/bytearray" Bytearray = bytearray.FFIBytearray Bytearray_as_string = bytearray.FFIBytearray_as_string +U16view = bytearray.FFIU16view +I16view = bytearray.FFII16view +U32view = bytearray.FFIU32view +I32view = bytearray.FFII32view Bytearray_construct = function(...) return Bytearray(...) end bit.compile = require "core:bitwise/compiler" From 90f7fce2b1051cd8dce5496b368c35f6fe745bbc Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 09:30:38 +0300 Subject: [PATCH 027/155] feat: pass pack environment to menu page script --- src/logic/scripting/lua/libs/libgui.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 6395dc5ba..0f8ac2da7 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -19,6 +19,8 @@ #include "graphics/ui/gui_util.hpp" #include "graphics/ui/markdown.hpp" #include "graphics/core/Font.hpp" +#include "content/Content.hpp" +#include "content/ContentPack.hpp" #include "items/Inventories.hpp" #include "util/stringutil.hpp" #include "world/Level.hpp" @@ -1026,13 +1028,17 @@ static int l_gui_load_document(lua::State* L) { io::path filename = lua::require_string(L, 1); auto alias = lua::require_string(L, 2); auto args = lua::tovalue(L, 3); - + auto prefix = filename.entryPoint(); + + auto env = scripting::get_root_environment(); + if (content) { + if (auto runtime = content->getPackRuntime(prefix)) { + env = runtime->getEnvironment(); + } + } + auto documentPtr = UiDocument::read( - engine->getGUI(), - scripting::get_root_environment(), - alias, - filename, - filename.string() + engine->getGUI(), std::move(env), alias, filename, filename.string() ); auto document = documentPtr.get(); engine->getAssets()->store(std::move(documentPtr), alias); From ca6096c397639f6f627e806eafa8ca0aa2f295d7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 11:53:48 +0300 Subject: [PATCH 028/155] minor refactor --- res/layouts/pages/scripts.xml.lua | 12 ++---------- res/scripts/stdlib.lua | 14 ++++++++++++++ src/engine/ServerMainloop.cpp | 3 +-- src/logic/scripting/scripting.cpp | 13 +++++++++++-- src/logic/scripting/scripting.hpp | 1 + 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/res/layouts/pages/scripts.xml.lua b/res/layouts/pages/scripts.xml.lua index dad0d1ff7..72c3246f4 100644 --- a/res/layouts/pages/scripts.xml.lua +++ b/res/layouts/pages/scripts.xml.lua @@ -1,18 +1,10 @@ function run_script(path) - debug.log("starting application script "..path) - - local code = file.read(path) - local chunk, err = loadstring(code, path) - if chunk == nil then - error(err) - end - setfenv(chunk, setmetatable({app=__vc_app}, {__index=_G})) - start_coroutine(chunk, path) + __vc_start_app_script(path) end function refresh() document.list:clear() - + local allpacks = table.merge(pack.get_available(), pack.get_installed()) local infos = pack.get_info(allpacks) for _, name in ipairs(allpacks) do diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 3f732e07c..447f7dd41 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -186,6 +186,20 @@ events = require "core:internal/events" function pack.unload(prefix) events.remove_by_prefix(prefix) + __vc__pack_envs[prefix] = nil +end + +function __vc_start_app_script(path) + debug.log("starting application script "..path) + + local code = file.read(path) + local chunk, err = loadstring(code, path) + if chunk == nil then + error(err) + end + local script_env = setmetatable({app = app or __vc_app}, {__index=_G}) + setfenv(chunk, script_env) + return start_coroutine(chunk, path) end gui_util = require "core:internal/gui_util" diff --git a/src/engine/ServerMainloop.cpp b/src/engine/ServerMainloop.cpp index 2f4465334..ab35ffbd4 100644 --- a/src/engine/ServerMainloop.cpp +++ b/src/engine/ServerMainloop.cpp @@ -32,8 +32,7 @@ void ServerMainloop::run() { setLevel(std::move(level)); }); - logger.info() << "starting test " << coreParams.scriptFile.string(); - auto process = scripting::start_coroutine( + auto process = scripting::start_app_script( "script:" + coreParams.scriptFile.filename().u8string() ); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 4d3672f15..c15f30aa1 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -151,9 +151,10 @@ std::unique_ptr scripting::load_client_project_script( return std::make_unique(L, std::move(env)); } -std::unique_ptr scripting::start_coroutine(const io::path& script) { +static std::unique_ptr start_lua_coroutine( + const io::path& script, const std::string& method +) { 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()); @@ -167,6 +168,14 @@ std::unique_ptr scripting::start_coroutine(const io::path& script) { return nullptr; } +std::unique_ptr scripting::start_coroutine(const io::path& script) { + return start_lua_coroutine(script, "__vc_start_coroutine"); +} + +std::unique_ptr scripting::start_app_script(const io::path& script) { + return start_lua_coroutine(script, "__vc_start_app_script"); +} + [[nodiscard]] scriptenv scripting::get_root_environment() { return std::make_shared(0); } diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 5a86b81d9..2f45ae602 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -77,6 +77,7 @@ namespace scripting { ); std::unique_ptr start_coroutine(const io::path& script); + std::unique_ptr start_app_script(const io::path& script); void on_world_load(LevelController* controller); void on_world_tick(int tps); From 9afe265094abf7fcc8535d6a58ec725d02df3847 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 11:59:58 +0300 Subject: [PATCH 029/155] update doc/*/scripting/builtins/libapp.md --- doc/en/scripting/builtins/libapp.md | 2 ++ doc/ru/scripting/builtins/libapp.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/doc/en/scripting/builtins/libapp.md b/doc/en/scripting/builtins/libapp.md index 69a472eef..45414ba11 100644 --- a/doc/en/scripting/builtins/libapp.md +++ b/doc/en/scripting/builtins/libapp.md @@ -7,6 +7,8 @@ The script/test name without the path and extension is available as `app.script` local filename = "script:"..app.script..".lua" ``` +Since the control script may not belong to any of the packs, it does not belongs to its own package and has its own global namespace in which all global functions and tables are available, as well as the `app` library. + ## Functions ```lua diff --git a/doc/ru/scripting/builtins/libapp.md b/doc/ru/scripting/builtins/libapp.md index 4e9493c4f..d2ddec725 100644 --- a/doc/ru/scripting/builtins/libapp.md +++ b/doc/ru/scripting/builtins/libapp.md @@ -7,6 +7,8 @@ local filename = "script:"..app.script..".lua" ``` +Так как управляющий сценарий может не принадлежать ни одному из паков, он не относиться к своему паку и имеет собственное пространство имён, в котором доступны все глобальные функции и таблицы, а также библиотека `app`. + ## Функции ```lua From d163f12e6796379fcf4536e8ee2abe5413561260 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 12:13:55 +0300 Subject: [PATCH 030/155] fix --- res/scripts/stdlib.lua | 4 ++-- src/logic/scripting/scripting.cpp | 17 +++-------------- src/logic/scripting/scripting.hpp | 1 - 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 447f7dd41..4443eed39 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -198,8 +198,8 @@ function __vc_start_app_script(path) error(err) end local script_env = setmetatable({app = app or __vc_app}, {__index=_G}) - setfenv(chunk, script_env) - return start_coroutine(chunk, path) + chunk = setfenv(chunk, script_env) + return __vc_start_coroutine(chunk, path) end gui_util = require "core:internal/gui_util" diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index c15f30aa1..586feeb8f 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -151,13 +151,10 @@ std::unique_ptr scripting::load_client_project_script( return std::make_unique(L, std::move(env)); } -static std::unique_ptr start_lua_coroutine( - const io::path& script, const std::string& method -) { +std::unique_ptr scripting::start_app_script(const io::path& script) { auto L = lua::get_main_state(); - if (lua::getglobal(L, method)) { - auto source = io::read_string(script); - lua::loadbuffer(L, 0, source, script.name()); + if (lua::getglobal(L, "__vc_start_app_script")) { + lua::pushstring(L, script.string()); if (lua::call(L, 1)) { int id = lua::tointeger(L, -1); lua::pop(L, 1); @@ -168,14 +165,6 @@ static std::unique_ptr start_lua_coroutine( return nullptr; } -std::unique_ptr scripting::start_coroutine(const io::path& script) { - return start_lua_coroutine(script, "__vc_start_coroutine"); -} - -std::unique_ptr scripting::start_app_script(const io::path& script) { - return start_lua_coroutine(script, "__vc_start_app_script"); -} - [[nodiscard]] scriptenv scripting::get_root_environment() { return std::make_shared(0); } diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 2f45ae602..d9c7e614b 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -76,7 +76,6 @@ namespace scripting { const io::path& script ); - std::unique_ptr start_coroutine(const io::path& script); std::unique_ptr start_app_script(const io::path& script); void on_world_load(LevelController* controller); From 934c5f194e89b8cca738929e67d0c2640ac2610f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 15:50:52 +0300 Subject: [PATCH 031/155] fix: on_hud_render is not documented --- doc/en/scripting/events.md | 6 ++++++ doc/ru/scripting/events.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 712dcb8c9..fce3abfbd 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -190,6 +190,12 @@ function on_hud_open(playerid: int) Called after world open. +```lua +function on_hud_render() +``` + +Called every frame. Used for client-side tasks such as animation and camera control. + ```lua function on_hud_close(playerid: int) ``` diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 4ca3fda52..73a6bc0ce 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -190,6 +190,12 @@ function on_hud_open(playerid: int) Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы. +```lua +function on_hud_render() +``` + +Вызывается каждый кадр. Используется для клиентских задач, таких как анимация, управление камерой. + ```lua function on_hud_close(playerid: int) ``` From 652f652e556b64401e17b10c749c9952ce90182c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 16:02:11 +0300 Subject: [PATCH 032/155] fix: player.get_camera, set_camera are not documented --- doc/en/scripting/builtins/libplayer.md | 12 ++++++++++++ doc/ru/scripting/builtins/libplayer.md | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/doc/en/scripting/builtins/libplayer.md b/doc/en/scripting/builtins/libplayer.md index fa799198a..480046746 100644 --- a/doc/en/scripting/builtins/libplayer.md +++ b/doc/en/scripting/builtins/libplayer.md @@ -112,6 +112,18 @@ player.get_name(playerid: int) -> str Player name setter and getter +```lua +player.get_camera(playerid: int) -> int +``` + +Returns the index of the player's current camera. + +```lua +player.set_camera(playerid: int, camera_index: int) +``` + +Switches the player's camera. See [cameras](libcameras.md). + ```lua player.set_selected_slot(playerid: int, slotid: int) ``` diff --git a/doc/ru/scripting/builtins/libplayer.md b/doc/ru/scripting/builtins/libplayer.md index 2228f42c3..873811937 100644 --- a/doc/ru/scripting/builtins/libplayer.md +++ b/doc/ru/scripting/builtins/libplayer.md @@ -112,6 +112,18 @@ player.get_name(playerid: int) -> str Сеттер и геттер имени игрока +```lua +player.get_camera(playerid: int) -> int +``` + +Возвращает индекс текущей камеры игрока + +```lua +player.set_camera(playerid: int, camera_index: int) +``` + +Переключает камеру игрока. См. [камеры](libcameras.md). + ```lua player.set_selected_slot(playerid: int, slotid: int) ``` From 751489b00363d354b64695acb5a90486e6bf9674 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 16:17:57 +0300 Subject: [PATCH 033/155] add input.get_mouse_delta() --- doc/en/scripting/builtins/libinput.md | 6 ++++++ doc/ru/scripting/builtins/libinput.md | 6 ++++++ src/logic/scripting/lua/libs/libinput.cpp | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/doc/en/scripting/builtins/libinput.md b/doc/en/scripting/builtins/libinput.md index 02f2a8d08..2c577389d 100644 --- a/doc/en/scripting/builtins/libinput.md +++ b/doc/en/scripting/builtins/libinput.md @@ -48,6 +48,12 @@ input.get_mouse_pos() --> {int, int} Returns cursor screen position. +```lua +input.get_mouse_delta() --> {int, int} +``` + +Returns cursor movement delta. + ```lua input.get_bindings() --> strings array ``` diff --git a/doc/ru/scripting/builtins/libinput.md b/doc/ru/scripting/builtins/libinput.md index afe22ee62..96bc3105d 100644 --- a/doc/ru/scripting/builtins/libinput.md +++ b/doc/ru/scripting/builtins/libinput.md @@ -48,6 +48,12 @@ input.get_mouse_pos() --> {int, int} Возвращает позицию курсора на экране. +```lua +input.get_mouse_delta() --> {int, int} +``` + +Возращает дельту позиции курсора. + ```lua input.get_bindings() --> массив строк ``` diff --git a/src/logic/scripting/lua/libs/libinput.cpp b/src/logic/scripting/lua/libs/libinput.cpp index 6d75fd4b0..eb605671b 100644 --- a/src/logic/scripting/lua/libs/libinput.cpp +++ b/src/logic/scripting/lua/libs/libinput.cpp @@ -82,6 +82,12 @@ static int l_get_mouse_pos(lua::State* L) { return lua::pushvec2(L, engine->getInput().getCursor().pos); } +static int l_get_mouse_delta(lua::State* L) { + if (engine->isHeadless()) + return 0; + return lua::pushvec2(L, engine->getInput().getCursor().delta); +} + static int l_get_bindings(lua::State* L) { if (engine->isHeadless()) return 0; @@ -171,6 +177,7 @@ const luaL_Reg inputlib[] = { {"mousecode", lua::wrap}, {"add_callback", lua::wrap}, {"get_mouse_pos", lua::wrap}, + {"get_mouse_delta", lua::wrap}, {"get_bindings", lua::wrap}, {"get_binding_text", lua::wrap}, {"is_active", lua::wrap}, From 9e817c39ce6fc5ee11f2005dabfa0dab1fb952fe Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 17:55:01 +0300 Subject: [PATCH 034/155] disable mouse camera control if non-standard camera used --- src/frontend/LevelFrontend.cpp | 3 +-- src/logic/PlayerController.cpp | 5 ++--- src/objects/Player.cpp | 6 ++++++ src/objects/Player.hpp | 6 ++++++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 5dfdf8227..9a8553da6 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -49,8 +49,7 @@ LevelFrontend::LevelFrontend( auto sound = rassets.get(material->stepsSound); glm::vec3 pos {}; auto soundsCamera = currentPlayer->currentCamera.get(); - if (soundsCamera == currentPlayer->spCamera.get() || - soundsCamera == currentPlayer->tpCamera.get()) { + if (currentPlayer->isCurrentCameraBuiltin()) { soundsCamera = currentPlayer->fpCamera.get(); } bool relative = player == currentPlayer && diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index 0ced94250..9de01404b 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -207,8 +207,7 @@ void CameraControl::update( tpCamera->front = camera->front; tpCamera->right = camera->right; } - if (player.currentCamera == spCamera || player.currentCamera == tpCamera || - player.currentCamera == camera) { + if (player.isCurrentCameraBuiltin()) { player.currentCamera->setFov(glm::radians(settings.fov.get())); } } @@ -280,7 +279,7 @@ void PlayerController::postUpdate( updateFootsteps(delta); } - if (!pause && input) { + if (!pause && input && player.isCurrentCameraBuiltin()) { camControl.updateMouse(this->input, windowHeight); } camControl.refreshRotation(); diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index f14f9d2dc..f27dc743e 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -84,6 +84,12 @@ Hitbox* Player::getHitbox() { return nullptr; } +bool Player::isCurrentCameraBuiltin() const { + return currentCamera.get() == fpCamera.get() || + currentCamera.get() == spCamera.get() || + currentCamera.get() == tpCamera.get(); +} + void Player::updateSelectedEntity() { selectedEid = selection.entity; } diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index 7d6523f2e..18c5a5996 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -131,6 +131,8 @@ public: return position; } + bool isCurrentCameraBuiltin() const; + Hitbox* getHitbox(); void setSpawnPoint(glm::vec3 point); @@ -147,4 +149,8 @@ public: inline u64id_t getId() const { return id; } + + Level& getLevel() const { + return level; + } }; From 88e61125d198d0c66482b3b8afa8295910d6f9f5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Nov 2025 19:47:35 +0300 Subject: [PATCH 035/155] add device select to audio settings --- res/layouts/pages/settings_audio.xml.lua | 34 +++++++++++++++++++++++ res/layouts/templates/problem.xml | 2 +- res/layouts/templates/script_file.xml | 2 +- res/modules/internal/audio_input.lua | 33 ++++++++++++++++++---- res/scripts/hud.lua | 8 ++++-- src/audio/AL/ALAudio.cpp | 11 ++++++++ src/audio/AL/ALAudio.hpp | 2 ++ src/audio/audio.hpp | 5 ++++ src/logic/scripting/lua/libs/libaudio.cpp | 4 ++- 9 files changed, 91 insertions(+), 10 deletions(-) diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index 9b29bd52e..1b14cf041 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -24,10 +24,44 @@ function update_setting(x, id, name, postfix) ) end +local initialized = false + function on_open() + if not initialized then + initialized = true + local token = audio.input.__get_core_token() + document.root:add("") + local prev_amplitude = 0.0 + document.tm:setInterval(16, function() + audio.input.fetch_input(token) + local amplitude = audio.input.get_max_amplitude() + if amplitude > 0.0 then + amplitude = math.sqrt(amplitude) + end + document.input_volume_inner.size = { + prev_amplitude * + document.input_volume_outer.size[1], + document.input_volume_outer.size[2] + } + prev_amplitude = amplitude * 0.25 + prev_amplitude * 0.75 + end) + end create_setting("audio.volume-master", "Master Volume", 0.01) create_setting("audio.volume-regular", "Regular Sounds", 0.01) create_setting("audio.volume-ui", "UI Sounds", 0.01) create_setting("audio.volume-ambient", "Ambient", 0.01) create_setting("audio.volume-music", "Music", 0.01) + document.root:add("") document.root:add("" - .."" + .."" .."") local selectbox = document.input_device_select local devices = {} diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index cc6de4756..9dec5dfd7 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -106,6 +106,7 @@ settings.Shadows quality=Качество теней settings.Conflict=Найдены возможные конфликты settings.Windowed=Оконный settings.Borderless=Безрамочный +settings.Microphone=Микрофон # Управление chunks.reload=Перезагрузить Чанки From d9f2d54bf0b891ee01678191b2a7a3aa2428c8a8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 5 Nov 2025 00:20:58 +0300 Subject: [PATCH 037/155] feat: canvas from texture atlas element --- src/graphics/core/Atlas.cpp | 8 +++ src/graphics/core/Atlas.hpp | 7 ++- src/graphics/core/ImageData.cpp | 11 ++++ src/graphics/core/ImageData.hpp | 2 + src/graphics/core/Texture.cpp | 6 +++ src/graphics/core/Texture.hpp | 1 + src/logic/scripting/lua/libs/libassets.cpp | 31 +++++++++++ src/logic/scripting/lua/lua_custom_types.hpp | 25 +++++---- .../lua/usertypes/lua_type_canvas.cpp | 54 ++++++++++++++----- src/maths/UVRegion.hpp | 9 +++- 10 files changed, 126 insertions(+), 28 deletions(-) diff --git a/src/graphics/core/Atlas.cpp b/src/graphics/core/Atlas.cpp index be205d1eb..d06a20b98 100644 --- a/src/graphics/core/Atlas.cpp +++ b/src/graphics/core/Atlas.cpp @@ -49,6 +49,14 @@ ImageData* Atlas::getImage() const { return image.get(); } +std::shared_ptr Atlas::shareTexture() const { + return texture; +} + +std::shared_ptr Atlas::shareImageData() const { + return image; +} + void AtlasBuilder::add(const std::string& name, std::unique_ptr image) { entries.push_back(atlasentry{name, std::shared_ptr(image.release())}); names.insert(name); diff --git a/src/graphics/core/Atlas.hpp b/src/graphics/core/Atlas.hpp index af3e47214..adea16a42 100644 --- a/src/graphics/core/Atlas.hpp +++ b/src/graphics/core/Atlas.hpp @@ -14,8 +14,8 @@ class ImageData; class Texture; class Atlas { - std::unique_ptr texture; - std::unique_ptr image; + std::shared_ptr texture; + std::shared_ptr image; std::unordered_map regions; public: /// @param image atlas raster @@ -36,6 +36,9 @@ public: Texture* getTexture() const; ImageData* getImage() const; + + std::shared_ptr shareTexture() const; + std::shared_ptr shareImageData() const; }; struct atlasentry { diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index 44a1936e4..3a5e3cb6c 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -95,6 +95,17 @@ void ImageData::blit(const ImageData& image, int x, int y) { throw std::runtime_error("mismatching format"); } +std::unique_ptr ImageData::cropped(int x, int y, int width, int height) const { + width = std::min(width, this->width - x); + height = std::min(height, this->height - y); + if (width <= 0 || height <= 0) { + throw std::runtime_error("invalid crop dimensions"); + } + auto subImage = std::make_unique(format, width, height); + subImage->blitMatchingFormat(*this, -x, -y); + return subImage; +} + static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) { const int left = 0; const int right = width; diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index d09a04112..863965680 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -33,6 +33,8 @@ public: void extrude(int x, int y, int w, int h); void fixAlphaColor(); + std::unique_ptr cropped(int x, int y, int width, int height) const; + ubyte* getData() const { return data.get(); } diff --git a/src/graphics/core/Texture.cpp b/src/graphics/core/Texture.cpp index 1dc18a651..46f153e8f 100644 --- a/src/graphics/core/Texture.cpp +++ b/src/graphics/core/Texture.cpp @@ -54,6 +54,12 @@ void Texture::reload(const ubyte* data) { glBindTexture(GL_TEXTURE_2D, 0); } +void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) { + glBindTexture(GL_TEXTURE_2D, id); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData()); + glBindTexture(GL_TEXTURE_2D, 0); +} + std::unique_ptr Texture::readData() { auto data = std::make_unique(width * height * 4); glBindTexture(GL_TEXTURE_2D, id); diff --git a/src/graphics/core/Texture.hpp b/src/graphics/core/Texture.hpp index 595686198..5e67890c7 100644 --- a/src/graphics/core/Texture.hpp +++ b/src/graphics/core/Texture.hpp @@ -19,6 +19,7 @@ public: virtual void bind() const; virtual void unbind() const; void reload(const ubyte* data); + void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h); void setNearestFilter(); diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index 9924069af..6018a12c6 100644 --- a/src/logic/scripting/lua/libs/libassets.cpp +++ b/src/logic/scripting/lua/libs/libassets.cpp @@ -7,7 +7,9 @@ #include "engine/Engine.hpp" #include "graphics/commons/Model.hpp" #include "graphics/core/Texture.hpp" +#include "graphics/core/Atlas.hpp" #include "util/Buffer.hpp" +#include "../lua_custom_types.hpp" using namespace scripting; @@ -64,8 +66,37 @@ static int l_parse_model(lua::State* L) { return 0; } +static int l_to_canvas(lua::State* L) { + auto alias = lua::require_lstring(L, 1); + size_t sep = alias.rfind(':'); + if (sep == std::string::npos) { + return 0; + } + auto atlasName = alias.substr(0, sep); + auto& assets = *engine->getAssets(); + if (auto atlas = assets.get(std::string(atlasName))) { + auto textureName = std::string(alias.substr(sep + 1)); + auto image = atlas->shareImageData(); + auto texture = atlas->shareTexture(); + if (auto region = atlas->getIf(textureName)) { + UVRegion uvRegion = *region; + int atlasWidth = static_cast(image->getWidth()); + int atlasHeight = static_cast(image->getHeight()); + int x = static_cast(uvRegion.u1 * atlasWidth); + int y = static_cast(uvRegion.v1 * atlasHeight); + int w = static_cast(uvRegion.getWidth() * atlasWidth); + int h = static_cast(uvRegion.getHeight() * atlasHeight); + return lua::newuserdata( + L, std::move(texture), image->cropped(x, y, w, h), uvRegion + ); + } + } + return 0; +} + const luaL_Reg assetslib[] = { {"load_texture", lua::wrap}, {"parse_model", lua::wrap}, + {"to_canvas", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/lua_custom_types.hpp b/src/logic/scripting/lua/lua_custom_types.hpp index c17a78bca..bf2c4f873 100644 --- a/src/logic/scripting/lua/lua_custom_types.hpp +++ b/src/logic/scripting/lua/lua_custom_types.hpp @@ -6,6 +6,7 @@ #include #include "lua_commons.hpp" +#include "maths/UVRegion.hpp" struct fnl_state; class Heightmap; @@ -81,8 +82,9 @@ namespace lua { class LuaCanvas : public Userdata { public: explicit LuaCanvas( - std::shared_ptr inTexture, - std::shared_ptr inData + std::shared_ptr texture, + std::shared_ptr data, + UVRegion region = UVRegion(0, 0, 1, 1) ); ~LuaCanvas() override = default; @@ -90,29 +92,32 @@ namespace lua { return TYPENAME; } - [[nodiscard]] auto& texture() const { - return *mTexture; + [[nodiscard]] auto& getTexture() const { + return *texture; } - [[nodiscard]] auto& data() const { - return *mData; + [[nodiscard]] auto& getData() const { + return *data; } [[nodiscard]] bool hasTexture() const { - return mTexture != nullptr; + return texture != nullptr; } auto shareTexture() const { - return mTexture; + return texture; } + void update(); + void createTexture(); static int createMetatable(lua::State*); inline static std::string TYPENAME = "Canvas"; private: - std::shared_ptr mTexture; // nullable - std::shared_ptr mData; + std::shared_ptr texture; // nullable + std::shared_ptr data; + UVRegion region; }; static_assert(!std::is_abstract()); diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index ec0869044..fcb27779a 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -10,14 +10,42 @@ using namespace lua; LuaCanvas::LuaCanvas( - std::shared_ptr inTexture, std::shared_ptr inData + std::shared_ptr texture, + std::shared_ptr data, + UVRegion region ) - : mTexture(std::move(inTexture)), mData(std::move(inData)) { + : texture(std::move(texture)), + data(std::move(data)), + region(std::move(region)) { +} + +void LuaCanvas::update() { + if (!hasTexture()) { + return; + } + if (region.isFull()) { + texture->reload(*data); + } else { + uint texWidth = texture->getWidth(); + uint texHeight = texture->getHeight(); + uint imgWidth = data->getWidth(); + uint imgHeight = data->getHeight(); + + uint x = static_cast(region.u1 * texWidth); + uint y = static_cast(region.v1 * texHeight); + uint w = static_cast((region.u2 - region.u1) * texWidth); + uint h = static_cast((region.v2 - region.v1) * texHeight); + + w = std::min(w, imgWidth); + h = std::min(h, imgHeight); + + texture->reloadPartial(*data, x, y, w, h); + } } void LuaCanvas::createTexture() { - mTexture = Texture::from(mData.get()); - mTexture->setMipMapping(false, true); + texture = Texture::from(data.get()); + texture->setMipMapping(false, true); } union RGBA { @@ -41,7 +69,7 @@ static RGBA* get_at(const ImageData& data, uint x, uint y) { static RGBA* get_at(State* L, uint x, uint y) { if (auto canvas = touserdata(L, 1)) { - return get_at(canvas->data(), x, y); + return get_at(canvas->getData(), x, y); } return nullptr; } @@ -97,7 +125,7 @@ static LuaCanvas& require_canvas(State* L, int idx) { static int l_clear(State* L) { auto& canvas = require_canvas(L, 1); - auto& image = canvas.data(); + auto& image = canvas.getData(); ubyte* data = image.getData(); RGBA rgba {}; if (gettop(L) == 1) { @@ -122,7 +150,7 @@ static int l_line(State* L) { RGBA rgba = get_rgba(L, 6); if (auto canvas = touserdata(L, 1)) { - auto& image = canvas->data(); + auto& image = canvas->getData(); image.drawLine( x1, y1, x2, y2, glm::ivec4 {rgba.r, rgba.g, rgba.b, rgba.a} ); @@ -135,13 +163,13 @@ static int l_blit(State* L) { auto& src = require_canvas(L, 2); int dst_x = tointeger(L, 3); int dst_y = tointeger(L, 4); - dst.data().blit(src.data(), dst_x, dst_y); + dst.getData().blit(src.getData(), dst_x, dst_y); return 0; } static int l_set_data(State* L) { auto& canvas = require_canvas(L, 1); - auto& image = canvas.data(); + auto& image = canvas.getData(); auto data = image.getData(); if (lua::isstring(L, 2)) { @@ -166,9 +194,7 @@ static int l_set_data(State* L) { static int l_update(State* L) { if (auto canvas = touserdata(L, 1)) { - if (canvas->hasTexture()) { - canvas->texture().reload(canvas->data()); - } + canvas->update(); } return 0; } @@ -202,7 +228,7 @@ static int l_meta_index(State* L) { if (texture == nullptr) { return 0; } - auto& data = texture->data(); + auto& data = texture->getData(); if (isnumber(L, 2)) { if (auto pixel = get_at(data, static_cast(tointeger(L, 2)))) { return pushinteger(L, pixel->rgba); @@ -231,7 +257,7 @@ static int l_meta_newindex(State* L) { if (texture == nullptr) { return 0; } - auto& data = texture->data(); + auto& data = texture->getData(); if (isnumber(L, 2) && isnumber(L, 3)) { if (auto pixel = get_at(data, static_cast(tointeger(L, 2)))) { pixel->rgba = static_cast(tointeger(L, 3)); diff --git a/src/maths/UVRegion.hpp b/src/maths/UVRegion.hpp index 354c939b1..f7e398d8e 100644 --- a/src/maths/UVRegion.hpp +++ b/src/maths/UVRegion.hpp @@ -1,8 +1,7 @@ #pragma once #include -#include -#include +#include struct UVRegion { float u1; @@ -69,4 +68,10 @@ struct UVRegion { copy.scale(scale); return copy; } + + bool isFull() const { + const auto e = 1e-7; + return glm::abs(u1 - 0.0) < e && glm::abs(v1 - 0.0) < e && + glm::abs(u2 - 1.0) < e && glm::abs(v2 - 1.0) < e; + } }; From 643ae11d5c31e759609365df9da34457f960fbc1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 5 Nov 2025 01:30:47 +0300 Subject: [PATCH 038/155] feat: rebuild mip-maps on texture reload --- src/graphics/core/Texture.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/graphics/core/Texture.cpp b/src/graphics/core/Texture.cpp index 46f153e8f..04285aa8b 100644 --- a/src/graphics/core/Texture.cpp +++ b/src/graphics/core/Texture.cpp @@ -51,12 +51,14 @@ void Texture::reload(const ubyte* data) { glBindTexture(GL_TEXTURE_2D, id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, static_cast(data)); + glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) { glBindTexture(GL_TEXTURE_2D, id); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData()); + glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } From 7b45b4083baa035694e1ed3912bed0326f2dd608 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 5 Nov 2025 01:47:42 +0300 Subject: [PATCH 039/155] feat: keep previous values in debug panel if cursor is not locked --- src/frontend/debug_panel.cpp | 39 +++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 10c6b85c0..c42b2930c 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -9,6 +9,7 @@ #include "graphics/ui/elements/TextBox.hpp" #include "graphics/ui/elements/TrackBar.hpp" #include "graphics/ui/elements/InputBindBox.hpp" +#include "graphics/ui/GUI.hpp" #include "graphics/render/WorldRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp" @@ -43,10 +44,15 @@ static std::shared_ptr