diff --git a/doc/en/scripting.md b/doc/en/scripting.md index 2ed46bdb..d1fdc645 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -21,6 +21,7 @@ Subsections: - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [time](scripting/builtins/libtime.md) + - [utf8](scripting/builtins/libutf8.md) - [vec2, vec3, vec4](scripting/builtins/libvecn.md) - [world](scripting/builtins/libworld.md) - [Module core:bit_converter](scripting/modules/core_bit_converter.md) diff --git a/doc/en/scripting/builtins/libutf8.md b/doc/en/scripting/builtins/libutf8.md new file mode 100644 index 00000000..e7801f97 --- /dev/null +++ b/doc/en/scripting/builtins/libutf8.md @@ -0,0 +1,21 @@ +# *utf8* library + +The library provides functions for working with UTF-8. + +```lua +-- Converts a UTF-8 string to a Bytearray or an array of numbers if +-- the second argument is true +utf8.tobytes(text: str, [optional] usetable=false) -> Bytearray|table + +-- Converts a Bytearray or an array of numbers to a UTF-8 string +utf8.tostring(bytes: Bytearray|table) -> str + +-- Returns the length of a Unicode string +utf8.length(text: str) -> int + +-- Returns the code of the first character of the string +utf8.codepoint(chars: str) -> int + +-- Returns a substring from position startchar to endchar inclusive +utf8.sub(text: str, startchar: int, [optional] endchar: int) -> str +``` diff --git a/doc/en/world-generator.md b/doc/en/world-generator.md index 0de466bb..15d9ea54 100644 --- a/doc/en/world-generator.md +++ b/doc/en/world-generator.md @@ -72,15 +72,13 @@ Fragments used by the generator must present in the directory: ## Structures -A structure is a set of rules for inserting a fragment into the world by the generator. It currently has no properties, being created as empty objects in the `generators/generator_name.files/structures.json` file. Example: -```lua -{ - "tree0": {}, - "tree1": {}, - "tree2": {}, - "tower": {}, - "coal_ore0": {} -} +A structure is a set of rules for inserting a fragment into the world by the generator. It currently has no properties, being created as empty objects in the `generators/generator_name.files/structures.toml` file. Example: +```toml +tree0 = {} +tree1 = {} +tree2 = {} +tower = {} +coal_ore0 = {} ``` Currently, the name of the structure must match the name of the fragment used. @@ -136,7 +134,7 @@ structures = [ - block - plant block - structure-chance - probability of generating a small structure on a surface block. - structures - structures randomly placed on the surface. - - name - name of the structure declared in `structures.json`. + - name - name of the structure declared in `structures.toml`. - weight - weight directly affecting the chance of choosing a specific structure. ### Biome Parameters diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index 1f582d46..0900788e 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -21,6 +21,7 @@ - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [time](scripting/builtins/libtime.md) + - [utf8](scripting/builtins/libutf8.md) - [vec2, vec3, vec4](scripting/builtins/libvecn.md) - [world](scripting/builtins/libworld.md) - [Модуль core:bit_converter](scripting/modules/core_bit_converter.md) diff --git a/doc/ru/scripting/builtins/libpack.md b/doc/ru/scripting/builtins/libpack.md index 0beb0667..543ea7a3 100644 --- a/doc/ru/scripting/builtins/libpack.md +++ b/doc/ru/scripting/builtins/libpack.md @@ -31,7 +31,7 @@ file.write(pack.shared_file(PACK_ID, "example.txt"), text) ``` Для пака *containermod* запишет текст в файл `config:containermod/example.txt` -Используйте для хранения данныхm общих для всех миров. +Используйте для хранения данных общих для всех миров. ```python pack.get_folder(packid: str) -> str diff --git a/doc/ru/scripting/builtins/libutf8.md b/doc/ru/scripting/builtins/libutf8.md new file mode 100644 index 00000000..b29bb403 --- /dev/null +++ b/doc/ru/scripting/builtins/libutf8.md @@ -0,0 +1,21 @@ +# Библиотека *utf8* + +Библиотека предоставляет функции для работы с UTF-8. + +```lua +-- Конвертирует UTF-8 строку в Bytearray или массив чисел если +-- второй аргумент - true +utf8.tobytes(text: str, [опционально] usetable=false) -> Bytearray|table + +-- Конвертирует Bytearray или массив чисел в UTF-8 строку +utf8.tostring(bytes: Bytearray|table) -> str + +-- Возвращает длину юникод-строки +utf8.length(text: str) -> int + +-- Возвращает код первого символа строки +utf8.codepoint(chars: str) -> int + +-- Возвращает подстроку от позиции startchar до endchar включительно +utf8.sub(text: str, startchar: int, [опционально] endchar: int) -> str +``` diff --git a/doc/ru/world-generator.md b/doc/ru/world-generator.md index ec3112cb..98be0ff4 100644 --- a/doc/ru/world-generator.md +++ b/doc/ru/world-generator.md @@ -72,15 +72,13 @@ ## Структуры -Структура - набор правил по вставке фрагмента в мир генератором. На данный момент не имеет свойств, создаваясь в виде пустых объектов в файле `generators/имя_генератора.files/structures.json`. Пример: -```lua -{ - "tree0": {}, - "tree1": {}, - "tree2": {}, - "tower": {}, - "coal_ore0": {} -} +Структура - набор правил по вставке фрагмента в мир генератором. На данный момент не имеет свойств, создаваясь в виде пустых объектов в файле `generators/имя_генератора.files/structures.toml`. Пример: +```toml +tree0 = {} +tree1 = {} +tree2 = {} +tower = {} +coal_ore0 = {} ``` На данный момент, имя структуры должно совпадать с именем использованного фрагмента. @@ -136,7 +134,7 @@ structures = [ - block - блок растения - structure-chance - вероятность генерации малой структуры на блоке поверхности. - structures - структуры, случайно расставляемые на поверхности. - - name - имя структуры, объявленной в `structures.json`. + - name - имя структуры, объявленной в `structures.toml`. - weight - вес, напрямую влияющий на шанс выбора конкретной структуры. ### Параметры биомов diff --git a/src/logic/scripting/lua/libs/api_lua.hpp b/src/logic/scripting/lua/libs/api_lua.hpp index 2ac9c1e8..f9c8b3f8 100644 --- a/src/logic/scripting/lua/libs/api_lua.hpp +++ b/src/logic/scripting/lua/libs/api_lua.hpp @@ -35,6 +35,7 @@ extern const luaL_Reg playerlib[]; extern const luaL_Reg quatlib[]; // quat.cpp extern const luaL_Reg timelib[]; extern const luaL_Reg tomllib[]; +extern const luaL_Reg utf8lib[]; extern const luaL_Reg vec2lib[]; // vecn.cpp extern const luaL_Reg vec3lib[]; // vecn.cpp extern const luaL_Reg vec4lib[]; // vecn.cpp diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 6f0dd8b8..b0a960b2 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -167,6 +167,14 @@ static int l_get_setting_info(lua::State* L) { throw std::runtime_error("unsupported setting type"); } +#include "util/platform.hpp" + +static int l_open_folder(lua::State* L) { + auto path = engine->getPaths()->resolve(lua::require_string(L, 1)); + platform::open_folder(path); + return 0; +} + /// @brief Quit the game static int l_quit(lua::State*) { Window::setShouldClose(true); @@ -184,5 +192,6 @@ const luaL_Reg corelib[] = { {"set_setting", lua::wrap}, {"str_setting", lua::wrap}, {"get_setting_info", lua::wrap}, + {"open_folder", lua::wrap}, {"quit", lua::wrap}, {NULL, NULL}}; diff --git a/src/logic/scripting/lua/libs/libutf8.cpp b/src/logic/scripting/lua/libs/libutf8.cpp new file mode 100644 index 00000000..182f6295 --- /dev/null +++ b/src/logic/scripting/lua/libs/libutf8.cpp @@ -0,0 +1,73 @@ +#include "api_lua.hpp" + +#include + +#include "../lua_custom_types.hpp" +#include "util/stringutil.hpp" + +static int l_encode(lua::State* L) { + std::string_view string = lua::require_string(L, 1); + if (lua::toboolean(L, 2)) { + lua::createtable(L, string.length(), 0); + for (size_t i = 0; i < string.length(); i++) { + lua::pushinteger(L, string[i] & 0xFF); + lua::rawseti(L, i+1); + } + } else { + lua::newuserdata(L, string.length()); + auto bytearray = lua::touserdata(L, -1); + bytearray->data().reserve(string.length()); + std::memcpy(bytearray->data().data(), string.data(), string.length()); + } + return 1; +} + +static int l_decode(lua::State* L) { + if (lua::istable(L, 1)) { + size_t size = lua::objlen(L, 1); + util::Buffer buffer(size); + return lua::pushstring(L, std::string(buffer.data(), size)); + } else if (auto bytes = lua::touserdata(L, 1)) { + return lua::pushstring( + L, + std::string( + reinterpret_cast(bytes->data().data()), + bytes->data().size() + ) + ); + } + return 1; +} + +static int l_length(lua::State* L) { + auto string = lua::require_string(L, 1); + return lua::pushinteger(L, util::length_utf8(string)); +} + +static int l_codepoint(lua::State* L) { + std::string_view string = lua::require_string(L, 1); + if (string.empty()) { + return lua::pushinteger(L, 0); + } + uint size; + return lua::pushinteger(L, util::decode_utf8(size, string.data())); +} + +static int l_sub(lua::State* L) { + auto string = util::str2u32str_utf8(lua::require_string(L, 1)); + int start = std::max(0, static_cast(lua::tointeger(L, 2) - 1)); + int end = string.length(); + if (lua::gettop(L) >= 3) { + end = std::max(0, static_cast(lua::tointeger(L, 3) - 1)); + } + return lua::pushstring(L, util::u32str2str_utf8(string.substr(start, end))); +} + +const luaL_Reg utf8lib[] = { + {"tobytes", lua::wrap}, + {"tostring", lua::wrap}, + {"length", lua::wrap}, + {"codepoint", lua::wrap}, + {"sub", lua::wrap}, + {NULL, NULL} +}; diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index fd40e612..fe3e75b5 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -51,6 +51,7 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "quat", quatlib); openlib(L, "time", timelib); openlib(L, "toml", tomllib); + openlib(L, "utf8", utf8lib); openlib(L, "vec2", vec2lib); openlib(L, "vec3", vec3lib); openlib(L, "vec4", vec4lib); diff --git a/src/util/platform.cpp b/src/util/platform.cpp index b749718d..6bc3b343 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -7,12 +7,11 @@ #include #include "typedefs.hpp" +#include "stringutil.hpp" #ifdef _WIN32 #include -#include "stringutil.hpp" - void platform::configure_encoding() { // set utf-8 encoding to console output SetConsoleOutputCP(CP_UTF8); @@ -35,7 +34,6 @@ std::string platform::detect_locale() { .replace(2, 1, "_") .substr(0, 5); } - #else void platform::configure_encoding() { @@ -49,5 +47,18 @@ std::string platform::detect_locale() { return preferredLocaleName.substr(0, 5); } - #endif + +void platform::open_folder(const std::filesystem::path& folder) { + if (!std::filesystem::is_directory(folder)) { + return; + } +#ifdef __APPLE__ + auto cmd = "open "+util::quote(folder.u8string()); +#elif defined(_WIN32) + auto cmd = "start explorer "+util::quote(folder.u8string()); +#else + auto cmd = "xdg-open "+util::quote(folder.u8string()); +#endif + system(cmd.c_str()); +} diff --git a/src/util/platform.hpp b/src/util/platform.hpp index a620acad..b6e8bde9 100644 --- a/src/util/platform.hpp +++ b/src/util/platform.hpp @@ -1,9 +1,13 @@ #pragma once #include +#include namespace platform { void configure_encoding(); - // @return environment locale in ISO format ll_CC + /// @return environment locale in ISO format ll_CC std::string detect_locale(); + /// @brief Open folder using system file manager asynchronously + /// @param folder target folder + void open_folder(const std::filesystem::path& folder); } diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index c104bd5e..12936343 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -128,7 +128,7 @@ inline uint utf8_len(ubyte cp) { if ((cp & 0xF8) == 0xF0) { return 4; } - return 0; + throw std::runtime_error("utf8 decode error"); } uint32_t util::decode_utf8(uint& size, const char* chr) { @@ -156,6 +156,16 @@ size_t util::crop_utf8(std::string_view s, size_t maxSize) { return pos; } +size_t util::length_utf8(std::string_view s) { + size_t length = 0; + size_t pos = 0; + while (pos < s.length()) { + pos += utf8_len(s[pos]); + length++; + } + return length; +} + template std::string xstr2str_utf8(const std::basic_string& xs) { std::vector chars; diff --git a/src/util/stringutil.hpp b/src/util/stringutil.hpp index c7bc2fb5..f584a53c 100644 --- a/src/util/stringutil.hpp +++ b/src/util/stringutil.hpp @@ -44,6 +44,11 @@ namespace util { /// @param maxSize max encoded string length after crop /// @return cropped string size (less or equal to maxSize) size_t crop_utf8(std::string_view s, size_t maxSize); + + /// @brief Measure utf8-encoded string length + /// @param s source encoded string + /// @return unicode string length (number of codepoints) + size_t length_utf8(std::string_view s); bool is_integer(const std::string& text); bool is_integer(const std::wstring& text);