diff --git a/dev/tests/filesystem.lua b/dev/tests/filesystem.lua new file mode 100644 index 00000000..a9ff58ce --- /dev/null +++ b/dev/tests/filesystem.lua @@ -0,0 +1,50 @@ +debug.log("check initial state") +assert(file.exists("config:")) + +debug.log("write text file") +assert(file.write("config:text.txt", "example, пример")) +assert(file.exists("config:text.txt")) + +debug.log("read text file") +assert(file.read("config:text.txt") == "example, пример") + +debug.log("delete file") +file.remove("config:text.txt") +assert(not file.exists("config:text.txt")) + +debug.log("create directory") +file.mkdir("config:dir") +assert(file.isdir("config:dir")) + +debug.log("remove directory") +file.remove("config:dir") + +debug.log("create directories") +file.mkdirs("config:dir/subdir/other") +assert(file.isdir("config:dir/subdir/other")) + +debug.log("remove tree") +file.remove_tree("config:dir") +assert(not file.isdir("config:dir")) + +debug.log("write binary file") +local bytes = {0xDE, 0xAD, 0xC0, 0xDE} +file.write_bytes("config:binary", bytes) +assert(file.exists("config:binary")) + +debug.log("read binary file") +local rbytes = file.read_bytes("config:binary") +assert(#rbytes == #bytes) +for i, b in ipairs(bytes) do + assert(rbytes[i] == b) +end + +debug.log("delete file") +file.remove("config:binary") +assert(not file.exists("config:binary")) + +debug.log("checking entry points for writeability") +assert(file.is_writeable("config:")) +assert(file.is_writeable("export:")) +assert(not file.is_writeable("user:")) +assert(not file.is_writeable("res:")) diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index fb874e0c..6400c910 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -81,6 +81,13 @@ Turns off block model shading Determines the presence of the vertex AO effect. Turned-on by default. +### *culling* + +Face culling mode: +- **default** - normal face culling +- **optional** - face culling among blocks of the same rendering group can be disabled via the `graphics.dense-render` setting. +- **disabled** - face culling among blocks of the same rendering group disabled. + ## Physics ### *obstacle* diff --git a/doc/en/particles.md b/doc/en/particles.md index 8b7a37e7..47ab8ac3 100644 --- a/doc/en/particles.md +++ b/doc/en/particles.md @@ -16,6 +16,10 @@ Particles are a table, all fields of which are optional. | acceleration | Particles acceleration. | {0, -16, 0} | | explosion | Force of particles explosion on spawn. | {2, 2, 2} | | size | Size of particles. | {0.1, 0.1, 0.1} | +| size_spread | Maximum particle size spread over time. | 0.2 | +| angle_spread | Maximum initial rotation angle spread (0 to 1) | 0.0 | +| min_angular_vel | Minimum angular velocity (radians per sec). Non-negative. | 0.0 | +| max_angular_vel | Maximum angular velocity (radians per sec). Non-negative. | 0.0 | | spawn_shape | Shape of particle spawn area. (ball/sphere/box) | ball | | spawn_spread | Size of particle spawn area. | {0, 0, 0} | | random_sub_uv | Size of random texture subregion (1 - entire texture will be used). | 1.0 | diff --git a/doc/en/scripting/builtins/libfile.md b/doc/en/scripting/builtins/libfile.md index fe02bed1..1e683b65 100644 --- a/doc/en/scripting/builtins/libfile.md +++ b/doc/en/scripting/builtins/libfile.md @@ -25,6 +25,12 @@ file.read_bytes(path: str) -> array of integers Read file into bytes array. +```lua +file.is_writeable(path: str) -> bool +``` + +Checks if the specified path is writable. + ```python file.write(path: str, text: str) -> nil ``` diff --git a/doc/en/scripting/builtins/libinventory.md b/doc/en/scripting/builtins/libinventory.md index ccb7be20..a3f3632f 100644 --- a/doc/en/scripting/builtins/libinventory.md +++ b/doc/en/scripting/builtins/libinventory.md @@ -32,6 +32,21 @@ inventory.size(invid: int) -> int -- Returns remaining count if could not to add fully. inventory.add(invid: int, itemid: int, count: int) -> int +-- Returns the index of the first matching slot in the given range. +-- If no matching slot was found, returns nil +inventory.find_by_item( + -- inventory id + invid: int, + -- item id + itemid: int, + -- [optional] index of the slot range start (from 0) + range_begin: int, + -- [optional] index of the slot range end (from 0) + range_end: int, + -- [optional] minimum item count in the slot + min_count: int = 1 +) -> int + -- Returns block inventory ID or 0. inventory.get_block(x: int, y: int, z: int) -> int diff --git a/doc/en/scripting/builtins/libplayer.md b/doc/en/scripting/builtins/libplayer.md index b43bf860..52324d30 100644 --- a/doc/en/scripting/builtins/libplayer.md +++ b/doc/en/scripting/builtins/libplayer.md @@ -70,6 +70,13 @@ player.set_instant_destruction(playerid: int, bool) Getter and setter for instant destruction of blocks when the `player.destroy` binding is activated. +```lua +player.is_loading_chunks(playerid: int) -> bool +player.set_loading_chunks(playerid: int, bool) +``` + +Getter and setter of the property that determines whether the player is loading chunks. + ``` lua player.set_spawnpoint(playerid: int, x: number, y: number, z: number) player.get_spawnpoint(playerid: int) -> number, number, number @@ -84,6 +91,12 @@ player.get_name(playerid: int) -> str Player name setter and getter +```lua +player.set_selected_slot(playerid: int, slotid: int) +``` + +Sets the selected slot index + ```lua player.get_selected_block(playerid: int) -> x,y,z ``` diff --git a/doc/en/world-generator.md b/doc/en/world-generator.md index b0ab265c..40fd1b37 100644 --- a/doc/en/world-generator.md +++ b/doc/en/world-generator.md @@ -27,6 +27,7 @@ * [Small structures placement](#small-structures-placement) * [Wide structures placement](#wide-structures-placement) - [Structural air](#structural-air) +- [Generator 'Demo' (base:demo)](#generator-demo-basedemo) ## Basic concepts @@ -473,3 +474,24 @@ function place_structures_wide( `core:struct_air` - a block that should be used in chunks to mark empty space that should not be filled with blocks when generated in the world. + +# Generator 'Demo' (base:demo) + +## Adding new ore + +To add a new ore in your pack: +1. In the `generators` folder, create a `demo.files` folder (you don't need to create demo.toml). + +2. In the created folder, create a fragments folder and place the ore fragment file in it. +3. In `demo.files`, create a structures.toml file: +```toml +fragment_name = {} +``` +4. Also in `demo.files`, create an ores.json file: +```json +[ + {"struct": "fragment_name", "rarity": rarity} +] +``` +The higher the rarity value, the less ore generation chance. +You can rely on the rarity of coal ore: 4400. diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 1862e659..366cf0fb 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -82,7 +82,6 @@ При значении `true` блок не препятствует прохождению вертикального луча солнечного света. - ### Без освещения - *shadeless* Выключает освещение на модели блока. @@ -91,6 +90,13 @@ Определяет наличие эффекта вершинного AO. Включен по-умолчанию. +### Отсечение - *culling* + +Режим отсечения граней: +- **default** - обычное отсечение граней +- **optional** - отсечение граней среди блоков одной группы отрисовки можно отключить через настройку `graphics.dense-render` (Плотный рендер блоков). +- **disabled** - отсечение граней среди блоков одной группы отрисовки отключено. + ## Физика ### Препятствие - *obstacle* diff --git a/doc/ru/particles.md b/doc/ru/particles.md index 167337f8..90af27f1 100644 --- a/doc/ru/particles.md +++ b/doc/ru/particles.md @@ -18,6 +18,9 @@ | explosion | Сила разлёта частиц при спавне. | {2, 2, 2} | | size | Размер частиц. | {0.1, 0.1, 0.1} | | size_spread | Максимальное отклонение времени размера частиц. | 0.2 | +| angle_spread | Максимальное отклонение начального угла поворота (от 0 до 1) | 0.0 | +| min_angular_vel | Минимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 | +| max_angular_vel | Максимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 | | spawn_shape | Форма области спавна частиц. (ball/sphere/box) | ball | | spawn_spread | Размер области спавна частиц. | {0, 0, 0} | | random_sub_uv | Размер случайного подрегиона текстуры (1 - будет использована вся текстура). | 1.0 | diff --git a/doc/ru/scripting/builtins/libfile.md b/doc/ru/scripting/builtins/libfile.md index a9a9c273..d7ed7792 100644 --- a/doc/ru/scripting/builtins/libfile.md +++ b/doc/ru/scripting/builtins/libfile.md @@ -25,6 +25,12 @@ file.read_bytes(путь: str) -> array of integers Читает файл в массив байт. +```lua +file.is_writeable(путь: str) -> bool +``` + +Проверяет, доступно ли право записи по указанному пути. + ```python file.write(путь: str, текст: str) -> nil ``` diff --git a/doc/ru/scripting/builtins/libinventory.md b/doc/ru/scripting/builtins/libinventory.md index 7f3cf26e..a01bd917 100644 --- a/doc/ru/scripting/builtins/libinventory.md +++ b/doc/ru/scripting/builtins/libinventory.md @@ -38,6 +38,21 @@ inventory.add( count: int ) -> int +-- Возвращает индекс первого подходящего под критерии слота в заданном диапазоне. +-- Если подходящий слот не был найден, возвращает nil +inventory.find_by_item( + -- id инвентаря + invid: int, + -- id предмета + itemid: int, + -- [опционально] индекс начала диапазона слотов (c 0) + range_begin: int, + -- [опционально] индекс конца диапазона слотов (c 0) + range_end: int, + -- [опционально] минимальное количество предмета в слоте + min_count: int = 1 +) -> int + -- Функция возвращает id инвентаря блока. -- Если блок не может иметь инвентарь - возвращает 0. inventory.get_block(x: int, y: int, z: int) -> int diff --git a/doc/ru/scripting/builtins/libplayer.md b/doc/ru/scripting/builtins/libplayer.md index 284f725a..01a6d581 100644 --- a/doc/ru/scripting/builtins/libplayer.md +++ b/doc/ru/scripting/builtins/libplayer.md @@ -70,6 +70,13 @@ player.set_instant_destruction(playerid: int, bool) Геттер и сеттер мнгновенного разрушения блоков при активации привязки `player.destroy`. +```lua +player.is_loading_chunks(playerid: int) -> bool +player.set_loading_chunks(playerid: int, bool) +``` + +Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг. + ```lua player.set_spawnpoint(playerid: int, x: number, y: number, z: number) player.get_spawnpoint(playerid: int) -> number, number, number @@ -84,6 +91,12 @@ player.get_name(playerid: int) -> str Сеттер и геттер имени игрока +```lua +player.set_selected_slot(playerid: int, slotid: int) +``` + +Устанавливает индекс выбранного слота + ```lua player.get_selected_block(playerid: int) -> x,y,z ``` diff --git a/doc/ru/scripting/extensions.md b/doc/ru/scripting/extensions.md index d0ddd365..ca165b0c 100644 --- a/doc/ru/scripting/extensions.md +++ b/doc/ru/scripting/extensions.md @@ -46,6 +46,13 @@ table.remove_value(t: table, x: object) Удаляет элемент **x** из **t**. +```lua +table.shuffle(t: table) -> table +``` + +Перемешивает значения в таблице. + + ```lua table.tostring(t: table) -> string ``` @@ -146,6 +153,18 @@ math.rand(low, high) Возвращает случайное дробное число в диапазоне от **low** до **high**. +```lua +math.normalize(num: number, [опционально] conf: num) -> number +``` + +Возвращает нормализованное значение num относительно conf. + +```lua +math.round(num: number, [опционально] places: num) -> number +``` + +Возвращает округлённое значение num до указанного количества знаков после запятой places. + ## Дополнительные глобальные функции В этом же скрипте также определены и другие глобальные функции которые доступны для использования. Ниже их список diff --git a/doc/ru/world-generator.md b/doc/ru/world-generator.md index 5c1c6e6e..7f7be5c4 100644 --- a/doc/ru/world-generator.md +++ b/doc/ru/world-generator.md @@ -27,6 +27,7 @@ * [Расстановка малых структур](#расстановка-малых-структур) * [Расстановка 'широких' структур](#расстановка-широких-структур) - [Структурный воздух](#структурный-воздух) +- [Генератор 'Demo' (base:demo)](#генератор-demo-basedemo) ## Основные понятия @@ -478,3 +479,23 @@ function place_structures_wide( `core:struct_air` - блок, которые следует использовать в фрагментах для обозначения пустого пространства, которое не должно заполняться блоками при генерации в мире. + +# Генератор 'Demo' (base:demo) + +## Добавление новой руды + +Чтобы добавить новую руду из своего пака: +1. В папке `generators` создайте папку `demo.files` (demo.toml создавать не нужно). +2. В созданной папке создайте папку fragments и поместите в неё файл фрагмента руды. +3. В `demo.files` создайте файл structures.toml: +```toml +имя_фрагмента = {} +``` +4. Также в `demo.files` создайте файл ores.json: +```json +[ + {"struct": "имя_фрагмента", "rarity": редкость} +] +``` +Чем выше значение редкости, тем меньше вероятность генерации руды. +Опираться можно на редкость угольной руды: 4400. diff --git a/res/content/base/blocks/leaves.json b/res/content/base/blocks/leaves.json index cdf9f014..4ea4832f 100644 --- a/res/content/base/blocks/leaves.json +++ b/res/content/base/blocks/leaves.json @@ -1,5 +1,26 @@ { "texture": "leaves", "material": "base:grass", + "draw-group": 5, + "culling": "optional", + "particles": { + "lifetime": 4.0, + "spawn_interval": 1000.0, + "acceleration": [0, -0.1, 0], + "velocity": [0.2, -2.5, 0.3], + "explosion": [0, 0, 0], + "collision": false, + "size": [0.3, 0.3, 0.3], + "size_spread": 0.2, + "spawn_shape": "box", + "spawn_spread": [0.2, 0.2, 0.2], + "angle_spread": 1.0, + "min_angular_vel": 0.5, + "max_angular_vel": 2.0, + "lighting": true, + "frames": [ + "particles:leaf_0" + ] + }, "base:durability": 0.7 } diff --git a/res/content/base/textures/blocks/leaves.png b/res/content/base/textures/blocks/leaves.png index 3beaf7ab..46b38d22 100644 Binary files a/res/content/base/textures/blocks/leaves.png and b/res/content/base/textures/blocks/leaves.png differ diff --git a/res/content/base/textures/blocks/leaves_opaque.png b/res/content/base/textures/blocks/leaves_opaque.png new file mode 100644 index 00000000..3beaf7ab Binary files /dev/null and b/res/content/base/textures/blocks/leaves_opaque.png differ diff --git a/res/content/base/textures/particles/leaf_0.png b/res/content/base/textures/particles/leaf_0.png new file mode 100644 index 00000000..282fff80 Binary files /dev/null and b/res/content/base/textures/particles/leaf_0.png differ diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index 8a479a1c..c4503e52 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -41,4 +41,5 @@ function on_open() create_setting("graphics.fog-curve", "Fog Curve", 0.1) create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip") create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip") + create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip") end diff --git a/res/modules/data_buffer.lua b/res/modules/data_buffer.lua index e3d207ff..91dce29c 100644 --- a/res/modules/data_buffer.lua +++ b/res/modules/data_buffer.lua @@ -72,9 +72,14 @@ function data_buffer:put_byte(byte) end function data_buffer:put_bytes(bytes) - for i = 1, #bytes do - self:put_byte(bytes[i]) - end + if type(self.bytes) == 'table' then + for i = 1, #bytes do + self:put_byte(bytes[i]) + end + else + self.bytes:insert(self.pos, bytes) + self.pos = self.pos + #bytes + end end function data_buffer:put_single(single) @@ -308,4 +313,4 @@ end setmetatable(data_buffer, data_buffer) -return data_buffer \ No newline at end of file +return data_buffer diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua new file mode 100644 index 00000000..093d43c3 --- /dev/null +++ b/res/scripts/hud.lua @@ -0,0 +1,58 @@ +function on_hud_open() + input.add_callback("player.pick", function () + if hud.is_paused() or hud.is_inventory_open() then + return + end + local pid = hud.get_player() + local x, y, z = player.get_selected_block(pid) + if x == nil then + return + end + local id = block.get_picking_item(block.get(x, y, z)) + local inv, cur_slot = player.get_inventory(pid) + local slot = inventory.find_by_item(inv, id, 0, 9) + if slot then + player.set_selected_slot(pid, slot) + return + end + if not rules.get("allow-content-access") then + return + end + slot = inventory.find_by_item(inv, 0, 0, 9) + if slot then + cur_slot = slot + end + player.set_selected_slot(pid, cur_slot) + inventory.set(inv, cur_slot, id, 1) + end) + + input.add_callback("player.noclip", function () + if hud.is_paused() or hud.is_inventory_open() then + return + end + local pid = hud.get_player() + if player.is_noclip(pid) then + player.set_flight(pid, false) + player.set_noclip(pid, false) + else + player.set_flight(pid, true) + player.set_noclip(pid, true) + end + end) + + input.add_callback("player.flight", function () + if hud.is_paused() or hud.is_inventory_open() then + return + end + local pid = hud.get_player() + if player.is_noclip(pid) then + return + end + if player.is_flight(pid) then + player.set_flight(pid, false) + else + player.set_flight(pid, true) + player.set_vel(pid, 0, 1, 0) + end + end) +end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index aa56bc31..0a49ccd1 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -320,7 +320,6 @@ function __vc_on_hud_open() _rules.create("allow-content-access", hud._is_content_access(), function(value) hud._set_content_access(value) - input.set_enabled("player.pick", value) end) _rules.create("allow-flight", true, function(value) input.set_enabled("player.flight", value) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 67898360..5702d7ef 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -1,6 +1,6 @@ -- Check if given table is an array function is_array(x) - if #t > 0 then + if #x > 0 then return true end for k, v in pairs(x) do @@ -51,6 +51,19 @@ 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 table.copy(t) @@ -91,6 +104,15 @@ 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 + ---------------------------------------------- local pattern_escape_replacements = { diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index c19fe00a..e9b23897 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -16,6 +16,7 @@ devtools.traceback=Traceback (most recent call first) # Tooltips graphics.gamma.tooltip=Lighting brightness curve graphics.backlight.tooltip=Backlight to prevent total darkness +graphics.dense-render.tooltip=Enables transparency in blocks like leaves # settings settings.Controls Search Mode=Search by attached button name diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index ae591042..e78b5f72 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -28,6 +28,7 @@ pack.remove-confirm=Удалить весь поставляемый паком/ # Подсказки graphics.gamma.tooltip=Кривая яркости освещения graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту +graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья. # Меню menu.Apply=Применить @@ -67,6 +68,7 @@ world.delete-confirm=Удалить мир безвозвратно? # Настройки settings.Ambient=Фон settings.Backlight=Подсветка +settings.Dense blocks render=Плотный рендер блоков settings.Camera Shaking=Тряска Камеры settings.Camera Inertia=Инерция Камеры settings.Camera FOV Effects=Эффекты поля зрения diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index fee559e4..1dc676ea 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -239,10 +239,18 @@ void ContentLoader::loadBlock( } def.model = *model; } else if (!modelTypeName.empty()) { - logger.error() << "unknown model " << modelTypeName; + logger.error() << "unknown model: " << modelTypeName; def.model = BlockModel::none; } + std::string cullingModeName = to_string(def.culling); + root.at("culling").get(cullingModeName); + if (auto mode = CullingMode_from(cullingModeName)) { + def.culling = *mode; + } else { + logger.error() << "unknown culling mode: " << cullingModeName; + } + root.at("material").get(def.material); // rotation profile diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index a3b40211..e5d69447 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -95,12 +95,13 @@ struct ContentPackStats { } }; -struct world_funcs_set { - bool onblockplaced : 1; - bool onblockreplaced : 1; - bool onblockbroken : 1; - bool onblockinteract : 1; - bool onplayertick : 1; +struct WorldFuncsSet { + bool onblockplaced; + bool onblockreplaced; + bool onblockbreaking; + bool onblockbroken; + bool onblockinteract; + bool onplayertick; }; class ContentPackRuntime { @@ -108,7 +109,7 @@ class ContentPackRuntime { ContentPackStats stats {}; scriptenv env; public: - world_funcs_set worldfuncsset {}; + WorldFuncsSet worldfuncsset {}; ContentPackRuntime(ContentPack info, scriptenv env); ~ContentPackRuntime(); diff --git a/src/core_defs.hpp b/src/core_defs.hpp index 38160dac..8042ca36 100644 --- a/src/core_defs.hpp +++ b/src/core_defs.hpp @@ -21,12 +21,9 @@ inline const std::string BIND_MOVE_CROUCH = "movement.crouch"; inline const std::string BIND_MOVE_CHEAT = "movement.cheat"; inline const std::string BIND_CAM_ZOOM = "camera.zoom"; inline const std::string BIND_CAM_MODE = "camera.mode"; -inline const std::string BIND_PLAYER_NOCLIP = "player.noclip"; -inline const std::string BIND_PLAYER_FLIGHT = "player.flight"; inline const std::string BIND_PLAYER_ATTACK = "player.attack"; inline const std::string BIND_PLAYER_DESTROY = "player.destroy"; inline const std::string BIND_PLAYER_BUILD = "player.build"; -inline const std::string BIND_PLAYER_PICK = "player.pick"; inline const std::string BIND_PLAYER_FAST_INTERACTOIN = "player.fast_interaction"; inline const std::string BIND_HUD_INVENTORY = "hud.inventory"; diff --git a/src/engine.cpp b/src/engine.cpp index 2f8466d6..a3c63035 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -83,6 +83,9 @@ Engine::Engine(CoreParameters coreParameters) paths.setResourcesFolder(params.resFolder); paths.setUserFilesFolder(params.userFolder); paths.prepare(); + if (!params.scriptFile.empty()) { + paths.setScriptFolder(params.scriptFile.parent_path()); + } loadSettings(); auto resdir = paths.getResourcesFolder(); diff --git a/src/files/engine_paths.cpp b/src/files/engine_paths.cpp index 0ecea7b8..2c8cd02c 100644 --- a/src/files/engine_paths.cpp +++ b/src/files/engine_paths.cpp @@ -169,6 +169,10 @@ void EnginePaths::setResourcesFolder(std::filesystem::path folder) { this->resourcesFolder = std::move(folder); } +void EnginePaths::setScriptFolder(std::filesystem::path folder) { + this->scriptFolder = std::move(folder); +} + void EnginePaths::setCurrentWorldFolder(std::filesystem::path folder) { this->currentWorldFolder = std::move(folder); } @@ -211,7 +215,9 @@ std::filesystem::path EnginePaths::resolve( if (prefix == "export") { return userFilesFolder / EXPORT_FOLDER / fs::u8path(filename); } - + if (prefix == "script" && scriptFolder) { + return scriptFolder.value() / fs::u8path(filename); + } if (contentPacks) { for (auto& pack : *contentPacks) { if (pack.id == prefix) { diff --git a/src/files/engine_paths.hpp b/src/files/engine_paths.hpp index 61be1757..5ec34704 100644 --- a/src/files/engine_paths.hpp +++ b/src/files/engine_paths.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,8 @@ public: void setResourcesFolder(std::filesystem::path folder); std::filesystem::path getResourcesFolder() const; + void setScriptFolder(std::filesystem::path folder); + std::filesystem::path getWorldFolderByName(const std::string& name); std::filesystem::path getWorldsFolder() const; std::filesystem::path getConfigFolder() const; @@ -51,6 +54,7 @@ private: std::filesystem::path userFilesFolder {"."}; std::filesystem::path resourcesFolder {"res"}; std::filesystem::path currentWorldFolder; + std::optional scriptFolder; std::vector* contentPacks = nullptr; }; diff --git a/src/files/settings_io.cpp b/src/files/settings_io.cpp index d2a85f6a..f5f1341b 100644 --- a/src/files/settings_io.cpp +++ b/src/files/settings_io.cpp @@ -68,10 +68,12 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.section("graphics"); builder.add("fog-curve", &settings.graphics.fogCurve); builder.add("backlight", &settings.graphics.backlight); + builder.add("dense-render", &settings.graphics.denseRender); builder.add("gamma", &settings.graphics.gamma); builder.add("frustum-culling", &settings.graphics.frustumCulling); builder.add("skybox-resolution", &settings.graphics.skyboxResolution); builder.add("chunk-max-vertices", &settings.graphics.chunkMaxVertices); + builder.add("chunk-max-vertices-dense", &settings.graphics.chunkMaxVerticesDense); builder.add("chunk-max-renderers", &settings.graphics.chunkMaxRenderers); builder.section("ui"); diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index c6ba4e1c..844fd4e6 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -6,53 +6,71 @@ #include "assets/Assets.hpp" #include "content/Content.hpp" #include "content/ContentPack.hpp" -#include "core_defs.hpp" #include "graphics/core/Atlas.hpp" #include "maths/UVRegion.hpp" #include "voxels/Block.hpp" +#include "core_defs.hpp" +#include "settings.hpp" -ContentGfxCache::ContentGfxCache(const Content* content, const Assets& assets) - : content(content) { - auto indices = content->getIndices(); + +ContentGfxCache::ContentGfxCache( + const Content& content, + const Assets& assets, + const GraphicsSettings& settings +) + : content(content), assets(assets), settings(settings) { + refresh(); +} + +void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { + for (uint side = 0; side < 6; side++) { + std::string tex = def.textureFaces[side]; + if (def.culling == CullingMode::OPTIONAL && + !settings.denseRender.get() && atlas.has(tex + "_opaque")) { + tex = tex + "_opaque"; + } + if (atlas.has(tex)) { + sideregions[def.rt.id * 6 + side] = atlas.get(tex); + } else if (atlas.has(TEXTURE_NOTFOUND)) { + sideregions[def.rt.id * 6 + side] = atlas.get(TEXTURE_NOTFOUND); + } + } + if (def.model == BlockModel::custom) { + auto model = assets.require(def.modelName); + // temporary dirty fix tbh + if (def.modelName.find(':') == std::string::npos) { + for (auto& mesh : model.meshes) { + size_t pos = mesh.texture.find(':'); + if (pos == std::string::npos) { + continue; + } + if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) { + for (auto& vertex : mesh.vertices) { + vertex.uv = region->apply(vertex.uv); + } + } + } + } + models[def.rt.id] = std::move(model); + } +} + +void ContentGfxCache::refresh() { + auto indices = content.getIndices(); sideregions = std::make_unique(indices->blocks.count() * 6); const auto& atlas = assets.require("blocks"); const auto& blocks = indices->blocks.getIterable(); for (blockid_t i = 0; i < blocks.size(); i++) { auto def = blocks[i]; - for (uint side = 0; side < 6; side++) { - const std::string& tex = def->textureFaces[side]; - if (atlas.has(tex)) { - sideregions[i * 6 + side] = atlas.get(tex); - } else if (atlas.has(TEXTURE_NOTFOUND)) { - sideregions[i * 6 + side] = atlas.get(TEXTURE_NOTFOUND); - } - } - if (def->model == BlockModel::custom) { - auto model = assets.require(def->modelName); - // temporary dirty fix tbh - if (def->modelName.find(':') == std::string::npos) { - for (auto& mesh : model.meshes) { - size_t pos = mesh.texture.find(':'); - if (pos == std::string::npos) { - continue; - } - if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) { - for (auto& vertex : mesh.vertices) { - vertex.uv = region->apply(vertex.uv); - } - } - } - } - models[def->rt.id] = std::move(model); - } + refresh(*def, atlas); } } ContentGfxCache::~ContentGfxCache() = default; const Content* ContentGfxCache::getContent() const { - return content; + return &content; } const model::Model& ContentGfxCache::getModel(blockid_t id) const { diff --git a/src/frontend/ContentGfxCache.hpp b/src/frontend/ContentGfxCache.hpp index 96d46ea5..cb5e2ad7 100644 --- a/src/frontend/ContentGfxCache.hpp +++ b/src/frontend/ContentGfxCache.hpp @@ -10,19 +10,29 @@ class Content; class Assets; +class Atlas; +class Block; struct UVRegion; +struct GraphicsSettings; namespace model { struct Model; } class ContentGfxCache { - const Content* content; + const Content& content; + const Assets& assets; + const GraphicsSettings& settings; + // array of block sides uv regions (6 per block) std::unique_ptr sideregions; std::unordered_map models; public: - ContentGfxCache(const Content* content, const Assets& assets); + ContentGfxCache( + const Content& content, + const Assets& assets, + const GraphicsSettings& settings + ); ~ContentGfxCache(); inline const UVRegion& getRegion(blockid_t id, int side) const { @@ -32,4 +42,8 @@ public: const model::Model& getModel(blockid_t id) const; const Content* getContent() const; + + void refresh(const Block& block, const Atlas& atlas); + + void refresh(); }; diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 79784375..d3cbb9e8 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -14,12 +14,17 @@ #include "world/Level.hpp" LevelFrontend::LevelFrontend( - Player* currentPlayer, LevelController* controller, Assets& assets -) : level(*controller->getLevel()), - controller(controller), - assets(assets), - contentCache(std::make_unique(level.content, assets)) -{ + Player* currentPlayer, + LevelController* controller, + Assets& assets, + const EngineSettings& settings +) + : level(*controller->getLevel()), + controller(controller), + assets(assets), + contentCache(std::make_unique( + *level.content, assets, settings.graphics + )) { assets.store( BlocksPreview::build( *contentCache, assets, *level.content->getIndices() @@ -98,6 +103,10 @@ const Assets& LevelFrontend::getAssets() const { return assets; } +ContentGfxCache& LevelFrontend::getContentGfxCache() { + return *contentCache; +} + const ContentGfxCache& LevelFrontend::getContentGfxCache() const { return *contentCache; } diff --git a/src/frontend/LevelFrontend.hpp b/src/frontend/LevelFrontend.hpp index 163fc678..b53d267a 100644 --- a/src/frontend/LevelFrontend.hpp +++ b/src/frontend/LevelFrontend.hpp @@ -7,6 +7,7 @@ class Assets; class Player; class ContentGfxCache; class LevelController; +struct EngineSettings; class LevelFrontend { Level& level; @@ -14,12 +15,18 @@ class LevelFrontend { const Assets& assets; std::unique_ptr contentCache; public: - LevelFrontend(Player* currentPlayer, LevelController* controller, Assets& assets); + LevelFrontend( + Player* currentPlayer, + LevelController* controller, + Assets& assets, + const EngineSettings& settings + ); ~LevelFrontend(); Level& getLevel(); const Level& getLevel() const; const Assets& getAssets() const; const ContentGfxCache& getContentGfxCache() const; + ContentGfxCache& getContentGfxCache(); LevelController* getController() const; }; diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 33f8436b..f0535e14 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -154,7 +154,7 @@ static constexpr uint WORLDGEN_IMG_SIZE = 128U; Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player) : engine(engine), assets(*engine.getAssets()), - gui(engine.getGUI()), + gui(*engine.getGUI()), frontend(frontend), player(player), debugImgWorldGen(std::make_unique( @@ -183,11 +183,11 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player) engine, frontend.getLevel(), player, allowDebugCheats ); debugPanel->setZIndex(2); - gui->add(debugPanel); + gui.add(debugPanel); - gui->add(darkOverlay); - gui->add(hotbarView); - gui->add(contentAccessPanel); + gui.add(darkOverlay); + gui.add(hotbarView); + gui.add(contentAccessPanel); auto dplotter = std::make_shared(350, 250, 2000, 16); dplotter->setGravity(Gravity::bottom_right); @@ -208,10 +208,10 @@ Hud::~Hud() { for (auto& element : elements) { onRemove(element); } - gui->remove(hotbarView); - gui->remove(darkOverlay); - gui->remove(contentAccessPanel); - gui->remove(debugPanel); + gui.remove(hotbarView); + gui.remove(darkOverlay); + gui.remove(contentAccessPanel); + gui.remove(debugPanel); } /// @brief Remove all elements marked as removed @@ -322,9 +322,9 @@ void Hud::updateWorldGenDebugVisualization() { void Hud::update(bool visible) { const auto& level = frontend.getLevel(); const auto& chunks = *player.chunks; - auto menu = gui->getMenu(); + const auto& menu = gui.getMenu(); - debugPanel->setVisible(player.debug && visible); + debugPanel->setVisible(debug && visible); if (!visible && inventoryOpen) { closeInventory(); @@ -333,7 +333,7 @@ void Hud::update(bool visible) { setPause(false); } - if (!gui->isFocusCaught()) { + if (!gui.isFocusCaught()) { processInput(visible); } if ((pause || inventoryOpen) == Events::_cursor_locked) { @@ -359,7 +359,7 @@ void Hud::update(bool visible) { if (visible) { for (auto& element : elements) { - element.update(pause, inventoryOpen, player.debug); + element.update(pause, inventoryOpen, debug); if (element.isRemoved()) { onRemove(element); } @@ -367,8 +367,8 @@ void Hud::update(bool visible) { } cleanup(); - debugMinimap->setVisible(player.debug && showGeneratorMinimap); - if (player.debug && showGeneratorMinimap) { + debugMinimap->setVisible(debug && showGeneratorMinimap); + if (debug && showGeneratorMinimap) { updateWorldGenDebugVisualization(); } } @@ -460,7 +460,7 @@ void Hud::showExchangeSlot() { exchangeSlot->setColor(glm::vec4()); exchangeSlot->setInteractive(false); exchangeSlot->setZIndex(1); - gui->store(SlotView::EXCHANGE_SLOT_NAME, exchangeSlot); + gui.store(SlotView::EXCHANGE_SLOT_NAME, exchangeSlot); } @@ -494,7 +494,7 @@ void Hud::openPermanent(UiDocument* doc) { void Hud::dropExchangeSlot() { auto slotView = std::dynamic_pointer_cast( - gui->get(SlotView::EXCHANGE_SLOT_NAME) + gui.get(SlotView::EXCHANGE_SLOT_NAME) ); if (slotView == nullptr) { return; @@ -518,7 +518,7 @@ void Hud::dropExchangeSlot() { void Hud::closeInventory() { dropExchangeSlot(); - gui->remove(SlotView::EXCHANGE_SLOT_NAME); + gui.remove(SlotView::EXCHANGE_SLOT_NAME); exchangeSlot = nullptr; exchangeSlotInv = nullptr; inventoryOpen = false; @@ -536,7 +536,7 @@ void Hud::closeInventory() { } void Hud::add(const HudElement& element, const dv::value& argsArray) { - gui->add(element.getNode()); + gui.add(element.getNode()); auto document = element.getDocument(); if (document) { auto invview = std::dynamic_pointer_cast(element.getNode()); @@ -572,7 +572,7 @@ void Hud::onRemove(const HudElement& element) { invview->unbind(); } } - gui->remove(element.getNode()); + gui.remove(element.getNode()); } void Hud::remove(const std::shared_ptr& node) { @@ -585,6 +585,10 @@ void Hud::remove(const std::shared_ptr& node) { cleanup(); } +void Hud::setDebug(bool flag) { + debug = flag; +} + void Hud::draw(const DrawContext& ctx){ const Viewport& viewport = ctx.getViewport(); const uint width = viewport.getWidth(); @@ -602,7 +606,7 @@ void Hud::draw(const DrawContext& ctx){ uishader.uniformMatrix("u_projview", uicamera->getProjView()); // Crosshair - if (!pause && !inventoryOpen && !player.debug) { + if (!pause && !inventoryOpen && !debug) { DrawContext chctx = ctx.sub(batch); chctx.setBlendMode(BlendMode::inversion); auto texture = assets.get("gui/crosshair"); @@ -681,7 +685,7 @@ void Hud::setPause(bool pause) { closeInventory(); } - auto menu = gui->getMenu(); + const auto& menu = gui.getMenu(); if (pause) { menu->setPage("pause"); } else { @@ -713,10 +717,10 @@ void Hud::setContentAccess(bool flag) { void Hud::setDebugCheats(bool flag) { allowDebugCheats = flag; - gui->remove(debugPanel); + gui.remove(debugPanel); debugPanel = create_debug_panel( engine, frontend.getLevel(), player, allowDebugCheats ); debugPanel->setZIndex(2); - gui->add(debugPanel); + gui.add(debugPanel); } diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp index ff518d07..2a537157 100644 --- a/src/frontend/hud.hpp +++ b/src/frontend/hud.hpp @@ -73,7 +73,7 @@ class Hud : public util::ObjectsKeeper { Engine& engine; Assets& assets; std::unique_ptr uicamera; - gui::GUI* gui; + gui::GUI& gui; LevelFrontend& frontend; Player& player; @@ -113,6 +113,7 @@ class Hud : public util::ObjectsKeeper { bool showContentPanel = true; /// @brief Provide cheat controllers to the debug panel bool allowDebugCheats = true; + bool debug = false; /// @brief UI element will be dynamicly positioned near to inventory or in screen center std::shared_ptr secondUI; @@ -193,6 +194,8 @@ public: void onRemove(const HudElement& element); void remove(const std::shared_ptr& node); + void setDebug(bool flag); + Player* getPlayer() const; std::shared_ptr getBlockInventory(); diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index f21ed02f..5fc073b3 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -17,6 +17,8 @@ #include "graphics/render/WorldRenderer.hpp" #include "graphics/ui/GUI.hpp" #include "graphics/ui/elements/Menu.hpp" +#include "graphics/ui/GUI.hpp" +#include "frontend/ContentGfxCache.hpp" #include "logic/LevelController.hpp" #include "logic/PlayerController.hpp" #include "logic/scripting/scripting.hpp" @@ -55,7 +57,7 @@ LevelScreen::LevelScreen(Engine& engine, std::unique_ptr levelPtr) ); frontend = std::make_unique( - player, controller.get(), assets + player, controller.get(), assets, settings ); worldRenderer = std::make_unique( engine, *frontend, *player @@ -70,6 +72,11 @@ LevelScreen::LevelScreen(Engine& engine, std::unique_ptr levelPtr) player->chunks->saveAndClear(); worldRenderer->clear(); })); + keepAlive(settings.graphics.denseRender.observe([=](bool) { + player->chunks->saveAndClear(); + worldRenderer->clear(); + frontend->getContentGfxCache().refresh(); + })); keepAlive(settings.camera.fov.observe([=](double value) { player->fpCamera->setFov(glm::radians(value)); })); @@ -150,7 +157,9 @@ void LevelScreen::updateHotkeys() { hudVisible = !hudVisible; } if (Events::jpressed(keycode::F3)) { - player->debug = !player->debug; + debug = !debug; + hud->setDebug(debug); + worldRenderer->setDebug(debug); } } diff --git a/src/frontend/screens/LevelScreen.hpp b/src/frontend/screens/LevelScreen.hpp index c184f816..6a3b4eab 100644 --- a/src/frontend/screens/LevelScreen.hpp +++ b/src/frontend/screens/LevelScreen.hpp @@ -29,6 +29,7 @@ class LevelScreen : public Screen { void saveWorldPreview(); bool hudVisible = true; + bool debug = false; void updateHotkeys(); void initializeContent(); void initializePack(ContentPackRuntime* pack); diff --git a/src/graphics/core/GLTexture.cpp b/src/graphics/core/GLTexture.cpp index c5a918ed..3756d758 100644 --- a/src/graphics/core/GLTexture.cpp +++ b/src/graphics/core/GLTexture.cpp @@ -25,7 +25,7 @@ GLTexture::GLTexture(const ubyte* data, uint width, uint height, ImageFormat ima glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index cb590bcb..56f1c4e2 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -8,9 +8,6 @@ #include "voxels/Chunks.hpp" #include "lighting/Lightmap.hpp" #include "frontend/ContentGfxCache.hpp" -#include "settings.hpp" - -#include const glm::vec3 BlocksRenderer::SUN_VECTOR (0.411934f, 0.863868f, -0.279161f); @@ -342,41 +339,41 @@ void BlocksRenderer::blockCube( } if (ao) { - if (isOpen(coord + Z, group)) { + if (isOpen(coord + Z, block)) { faceAO(coord, X, Y, Z, texfaces[5], lights); } - if (isOpen(coord - Z, group)) { + if (isOpen(coord - Z, block)) { faceAO(coord, -X, Y, -Z, texfaces[4], lights); } - if (isOpen(coord + Y, group)) { + if (isOpen(coord + Y, block)) { faceAO(coord, X, -Z, Y, texfaces[3], lights); } - if (isOpen(coord - Y, group)) { + if (isOpen(coord - Y, block)) { faceAO(coord, X, Z, -Y, texfaces[2], lights); } - if (isOpen(coord + X, group)) { + if (isOpen(coord + X, block)) { faceAO(coord, -Z, Y, X, texfaces[1], lights); } - if (isOpen(coord - X, group)) { + if (isOpen(coord - X, block)) { faceAO(coord, Z, Y, -X, texfaces[0], lights); } } else { - if (isOpen(coord + Z, group)) { + if (isOpen(coord + Z, block)) { face(coord, X, Y, Z, texfaces[5], pickLight(coord + Z), lights); } - if (isOpen(coord - Z, group)) { + if (isOpen(coord - Z, block)) { face(coord, -X, Y, -Z, texfaces[4], pickLight(coord - Z), lights); } - if (isOpen(coord + Y, group)) { + if (isOpen(coord + Y, block)) { face(coord, X, -Z, Y, texfaces[3], pickLight(coord + Y), lights); } - if (isOpen(coord - Y, group)) { + if (isOpen(coord - Y, block)) { face(coord, X, Z, -Y, texfaces[2], pickLight(coord - Y), lights); } - if (isOpen(coord + X, group)) { + if (isOpen(coord + X, block)) { face(coord, -Z, Y, X, texfaces[1], pickLight(coord + X), lights); } - if (isOpen(coord - X, group)) { + if (isOpen(coord - X, block)) { face(coord, Z, Y, -X, texfaces[0], pickLight(coord - X), lights); } } diff --git a/src/graphics/render/BlocksRenderer.hpp b/src/graphics/render/BlocksRenderer.hpp index c0e0086e..c652b6a0 100644 --- a/src/graphics/render/BlocksRenderer.hpp +++ b/src/graphics/render/BlocksRenderer.hpp @@ -13,6 +13,7 @@ #include "graphics/core/MeshData.hpp" #include "maths/util.hpp" #include "commons.hpp" +#include "settings.hpp" class Content; class Mesh; @@ -22,7 +23,6 @@ class Chunks; class VoxelsVolume; class Chunks; class ContentGfxCache; -struct EngineSettings; struct UVRegion; class BlocksRenderer { @@ -118,7 +118,7 @@ class BlocksRenderer { bool isOpenForLight(int x, int y, int z) const; // Does block allow to see other blocks sides (is it transparent) - inline bool isOpen(const glm::ivec3& pos, ubyte group) const { + inline bool isOpen(const glm::ivec3& pos, const Block& def) const { auto id = voxelsBuffer->pickBlockId( chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z ); @@ -126,7 +126,13 @@ class BlocksRenderer { return false; } const auto& block = *blockDefsCache[id]; - if ((block.drawGroup != group && block.lightPassing) || !block.rt.solid) { + if (((block.drawGroup != def.drawGroup) && block.drawGroup) || !block.rt.solid) { + return true; + } + if ((def.culling == CullingMode::DISABLED || + (def.culling == CullingMode::OPTIONAL && + settings.graphics.denseRender.get())) && + id == def.rt.id) { return true; } return !id; diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index fe955bee..f1406ad1 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -24,15 +24,22 @@ class RendererWorker : public util::Worker, RendererResul BlocksRenderer renderer; public: RendererWorker( - const Level& level, + const Level& level, const Chunks& chunks, const ContentGfxCache& cache, const EngineSettings& settings - ) : level(level), - chunks(chunks), - renderer(settings.graphics.chunkMaxVertices.get(), - *level.content, cache, settings) - {} + ) + : level(level), + chunks(chunks), + renderer( + settings.graphics.denseRender.get() + ? settings.graphics.chunkMaxVerticesDense.get() + : settings.graphics.chunkMaxVertices.get(), + *level.content, + cache, + settings + ) { + } RendererResult operator()(const std::shared_ptr& chunk) override { renderer.build(chunk.get(), &chunks); diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index a3ef140b..c5d66ea8 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -104,9 +104,8 @@ void Decorator::update( void Decorator::update(float delta, const Camera& camera) { glm::ivec3 pos = camera.position; - pos -= glm::ivec3(UPDATE_AREA_DIAMETER / 2); for (int i = 0; i < ITERATIONS; i++) { - update(delta, pos, camera.position); + update(delta, pos - glm::ivec3(UPDATE_AREA_DIAMETER / 2), pos); } const auto& chunks = *player.chunks; const auto& indices = *level.content->getIndices(); diff --git a/src/graphics/render/Decorator.hpp b/src/graphics/render/Decorator.hpp index d19ed45f..e1b7eb17 100644 --- a/src/graphics/render/Decorator.hpp +++ b/src/graphics/render/Decorator.hpp @@ -14,7 +14,7 @@ class Chunks; class Camera; class Assets; class Player; -struct Block; +class Block; class Engine; class LevelController; class WorldRenderer; diff --git a/src/graphics/render/Emitter.cpp b/src/graphics/render/Emitter.cpp index 82848a63..e908ece8 100644 --- a/src/graphics/render/Emitter.cpp +++ b/src/graphics/render/Emitter.cpp @@ -19,12 +19,13 @@ Emitter::Emitter( ) : level(level), origin(std::move(origin)), - prototype({this, 0, glm::vec3(), preset.velocity, preset.lifetime, region}), + prototype({this, 0, {}, preset.velocity, preset.lifetime, region}), texture(texture), count(count), preset(std::move(preset)) { + random.setSeed(reinterpret_cast(this)); this->prototype.emitter = this; - timer = preset.spawnInterval; + timer = preset.spawnInterval * random.randFloat(); } const Texture* Emitter::getTexture() const { @@ -76,6 +77,10 @@ void Emitter::update( count = std::max(0, count - skipped); timer -= skipped * spawnInterval; } + if (count < 0) { + int skipped = timer / spawnInterval; + timer -= skipped * spawnInterval; + } return; } while (count && timer > spawnInterval) { @@ -83,6 +88,15 @@ void Emitter::update( Particle particle = prototype; particle.emitter = this; particle.random = random.rand32(); + if (glm::abs(preset.angleSpread) >= 0.005f) { + particle.angle = + random.randFloat() * preset.angleSpread * glm::pi() * 2; + } + particle.angularVelocity = + (preset.minAngularVelocity + + random.randFloat() * + (preset.maxAngularVelocity - preset.minAngularVelocity)) * + ((random.rand() % 2) * 2 - 1); glm::vec3 spawnOffset = generate_coord(preset.spawnShape); spawnOffset *= preset.spawnSpread; @@ -103,6 +117,7 @@ void Emitter::update( if (count > 0) { count--; } + refCount++; } } @@ -114,6 +129,10 @@ bool Emitter::isDead() const { return count == 0; } +bool Emitter::isReferred() const { + return refCount > 0; +} + const EmitterOrigin& Emitter::getOrigin() const { return origin; } diff --git a/src/graphics/render/Emitter.hpp b/src/graphics/render/Emitter.hpp index c1d6c168..ce1ec8f4 100644 --- a/src/graphics/render/Emitter.hpp +++ b/src/graphics/render/Emitter.hpp @@ -27,6 +27,10 @@ struct Particle { float lifetime; /// @brief UV region UVRegion region; + /// @brief Current rotation angle + float angle; + /// @brief Angular velocity + float angularVelocity; }; class Texture; @@ -39,7 +43,7 @@ class Emitter { EmitterOrigin origin; /// @brief Particle prototype Particle prototype; - /// @brief Particle + /// @brief Particle texture const Texture* texture; /// @brief Number of particles should be spawned before emitter deactivation. /// -1 is infinite. @@ -50,6 +54,9 @@ class Emitter { util::PseudoRandom random; public: + /// @brief Number of references (alive particles) + int refCount = 0; + /// @brief Particle settings ParticlesPreset preset; Emitter( @@ -82,6 +89,9 @@ public: /// @return true if the emitter has spawned all particles bool isDead() const; + /// @return true if there is at least one alive referring particle left + bool isReferred() const; + const EmitterOrigin& getOrigin() const; void setOrigin(const EmitterOrigin& origin); diff --git a/src/graphics/render/ModelsGenerator.hpp b/src/graphics/render/ModelsGenerator.hpp index 52bc56d0..c4665118 100644 --- a/src/graphics/render/ModelsGenerator.hpp +++ b/src/graphics/render/ModelsGenerator.hpp @@ -7,7 +7,7 @@ struct ItemDef; class Assets; class Content; -struct Block; +class Block; class ModelsGenerator { public: diff --git a/src/graphics/render/ParticlesRenderer.cpp b/src/graphics/render/ParticlesRenderer.cpp index 80057352..f61b67fd 100644 --- a/src/graphics/render/ParticlesRenderer.cpp +++ b/src/graphics/render/ParticlesRenderer.cpp @@ -36,12 +36,14 @@ static inline void update_particle( const auto& preset = particle.emitter->preset; auto& pos = particle.position; auto& vel = particle.velocity; + auto& angle = particle.angle; vel += delta * preset.acceleration; if (preset.collision && chunks.isObstacleAt(pos + vel * delta)) { vel *= 0.0f; } pos += vel * delta; + angle += particle.angularVelocity * delta; particle.lifetime -= delta; } @@ -65,7 +67,8 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) { auto iter = vec.begin(); while (iter != vec.end()) { auto& particle = *iter; - auto& preset = particle.emitter->preset; + auto& emitter = *particle.emitter; + auto& preset = emitter.preset; if (!preset.frames.empty()) { float time = preset.lifetime - particle.lifetime; @@ -86,19 +89,52 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) { } update_particle(particle, delta, chunks); + float scale = 1.0f + ((particle.random ^ 2628172) % 1000) * + 0.001f * preset.sizeSpread; + glm::vec4 light(1, 1, 1, 0); if (preset.lighting) { light = MainBatch::sampleLight( - particle.position, chunks, backlight + particle.position, + chunks, + backlight ); + auto size = glm::max(glm::vec3(0.5f), preset.size * scale); + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + light = glm::max( + light, + MainBatch::sampleLight( + particle.position - + size * glm::vec3(x, y, z), + chunks, + backlight + ) + ); + } + } + } light *= 0.9f + (particle.random % 100) * 0.001f; } - float scale = 1.0f + ((particle.random ^ 2628172) % 1000) * - 0.001f * preset.sizeSpread; + + + glm::vec3 localRight = right; + glm::vec3 localUp = preset.globalUpVector ? glm::vec3(0, 1, 0) : up; + float angle = particle.angle; + if (glm::abs(angle) >= 0.005f) { + glm::vec3 rotatedRight(glm::cos(angle), -glm::sin(angle), 0.0f); + glm::vec3 rotatedUp(glm::sin(angle), glm::cos(angle), 0.0f); + + localRight = right * rotatedRight.x + localUp * rotatedRight.y + + camera.front * rotatedRight.z; + localUp = right * rotatedUp.x + localUp * rotatedUp.y + + camera.front * rotatedUp.z; + } batch->quad( particle.position, - right, - preset.globalUpVector ? glm::vec3(0, 1, 0) : up, + localRight, + localUp, preset.size * scale, light, glm::vec3(1.0f), @@ -106,6 +142,7 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) { ); if (particle.lifetime <= 0.0f) { iter = vec.erase(iter); + emitter.refCount--; } else { iter++; } @@ -128,19 +165,15 @@ void ParticlesRenderer::render(const Camera& camera, float delta) { auto iter = emitters.begin(); while (iter != emitters.end()) { auto& emitter = *iter->second; + if (emitter.isDead() && !emitter.isReferred()) { + // destruct Emitter only when there is no particles spawned by it + iter = emitters.erase(iter); + continue; + } auto texture = emitter.getTexture(); const auto& found = particles.find(texture); std::vector* vec; - if (found == particles.end()) { - if (emitter.isDead()) { - // destruct Emitter only when there is no particles spawned by it - iter = emitters.erase(iter); - continue; - } - vec = &particles[texture]; - } else { - vec = &found->second; - } + vec = &particles[texture]; emitter.update(delta, camera.position, *vec); iter++; } diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index e8a75f0c..098cb8e8 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -211,7 +211,7 @@ void WorldRenderer::renderBlockSelection() { lineBatch->box( center, size + glm::vec3(0.01), glm::vec4(0.f, 0.f, 0.f, 0.5f) ); - if (player.debug) { + if (debug) { lineBatch->line( point, point + norm * 0.5f, glm::vec4(1.0f, 0.0f, 1.0f, 1.0f) ); @@ -228,7 +228,7 @@ void WorldRenderer::renderLines( if (player.selection.vox.id != BLOCK_VOID) { renderBlockSelection(); } - if (player.debug && showEntitiesDebug) { + if (debug && showEntitiesDebug) { auto ctx = pctx.sub(lineBatch.get()); bool culling = engine.getSettings().graphics.frustumCulling.get(); level.entities->renderDebug( @@ -337,7 +337,7 @@ void WorldRenderer::draw( renderLevel(ctx, camera, settings, delta, pause, hudVisible); // Debug lines if (hudVisible) { - if (player.debug) { + if (debug) { guides->renderDebugLines( ctx, camera, *lineBatch, linesShader, showChunkBorders ); @@ -410,3 +410,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { void WorldRenderer::clear() { chunks->clear(); } + +void WorldRenderer::setDebug(bool flag) { + debug = flag; +} diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index f8be0177..569858d8 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -45,6 +45,7 @@ class WorldRenderer { std::unique_ptr modelBatch; float timer = 0.0f; + bool debug = false; /// @brief Render block selection lines void renderBlockSelection(); @@ -100,4 +101,6 @@ public: ); void clear(); + + void setDebug(bool flag); }; diff --git a/src/items/Inventory.cpp b/src/items/Inventory.cpp index 00c5589e..9797a15e 100644 --- a/src/items/Inventory.cpp +++ b/src/items/Inventory.cpp @@ -23,10 +23,13 @@ size_t Inventory::findEmptySlot(size_t begin, size_t end) const { return npos; } -size_t Inventory::findSlotByItem(itemid_t id, size_t begin, size_t end) { +size_t Inventory::findSlotByItem( + itemid_t id, size_t begin, size_t end, size_t minCount +) { end = std::min(slots.size(), end); for (size_t i = begin; i < end; i++) { - if (slots[i].getItemId() == id) { + const auto& stack = slots[i]; + if (stack.getItemId() == id && stack.getCount() >= minCount) { return i; } } diff --git a/src/items/Inventory.hpp b/src/items/Inventory.hpp index 176ad7ee..49560523 100644 --- a/src/items/Inventory.hpp +++ b/src/items/Inventory.hpp @@ -22,7 +22,9 @@ public: ItemStack& getSlot(size_t index); size_t findEmptySlot(size_t begin = 0, size_t end = -1) const; - size_t findSlotByItem(itemid_t id, size_t begin = 0, size_t end = -1); + size_t findSlotByItem( + itemid_t id, size_t begin = 0, size_t end = -1, size_t minCount = 1 + ); inline size_t size() const { return slots.size(); diff --git a/src/items/ItemDef.hpp b/src/items/ItemDef.hpp index 7a2ef8d1..f6d61208 100644 --- a/src/items/ItemDef.hpp +++ b/src/items/ItemDef.hpp @@ -6,7 +6,7 @@ #include "data/dv.hpp" #include "typedefs.hpp" -struct item_funcs_set { +struct ItemFuncsSet { bool init : 1; bool on_use : 1; bool on_use_on_block : 1; @@ -43,7 +43,7 @@ struct ItemDef { struct { itemid_t id; blockid_t placingBlock; - item_funcs_set funcsset {}; + ItemFuncsSet funcsset {}; bool emissive = false; } rt {}; diff --git a/src/logic/ChunksController.cpp b/src/logic/ChunksController.cpp index f431efa7..a228de61 100644 --- a/src/logic/ChunksController.cpp +++ b/src/logic/ChunksController.cpp @@ -38,7 +38,10 @@ void ChunksController::update( int centerX = floordiv(position.x); int centerY = floordiv(position.z); - generator->update(centerX, centerY, loadDistance); + if (player.isLoadingChunks()) { + /// FIXME: one generator for multiple players + generator->update(centerX, centerY, loadDistance); + } int64_t mcstotal = 0; @@ -121,6 +124,12 @@ bool ChunksController::buildLights(const Player& player, const std::shared_ptrfetch(x, z)) { + player.chunks->putChunk(chunk); + } + return; + } auto chunk = level.chunks->create(x, z); player.chunks->putChunk(chunk); auto& chunkFlags = chunk->flags; diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index 9304d36e..6e7fcca6 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -47,9 +47,7 @@ LevelController::LevelController( ); chunks->update(16, 1, 0, *player); if (player->chunks->get( - std::floor(position.x), - std::floor(position.y), - std::floor(position.z) + std::floor(position.x), 0, std::floor(position.z) )) { confirmed++; } @@ -83,7 +81,7 @@ void LevelController::update(float delta, bool pause) { playerTickClock.getPart()) { const auto& position = player->getPosition(); - if (!player->chunks->get( + if (player->chunks->get( std::floor(position.x), std::floor(position.y), std::floor(position.z) diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index d9bc0065..44d1a64c 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -288,8 +288,6 @@ void PlayerController::updateKeyboard() { input.jump = Events::active(BIND_MOVE_JUMP); input.zoom = Events::active(BIND_CAM_ZOOM); input.cameraMode = Events::jactive(BIND_CAM_MODE); - input.noclip = Events::jactive(BIND_PLAYER_NOCLIP); - input.flight = Events::jactive(BIND_PLAYER_FLIGHT); } void PlayerController::resetKeyboard() { @@ -341,28 +339,6 @@ static int determine_rotation( return 0; } -static void pick_block( - ContentIndices* indices, - const Block& block, - Player& player, - int x, - int y, - int z -) { - itemid_t id = block.rt.pickingItem; - auto inventory = player.getInventory(); - size_t slotid = inventory->findSlotByItem(id, 0, 10); - if (slotid == Inventory::npos) { - slotid = player.getChosenSlot(); - } else { - player.setChosenSlot(slotid); - } - ItemStack& stack = inventory->getSlot(slotid); - if (stack.getItemId() != id) { - stack.set(ItemStack(id, 1)); - } -} - voxel* PlayerController::updateSelection(float maxDistance) { auto indices = level.content->getIndices(); auto& chunks = *player.chunks; @@ -544,6 +520,7 @@ void PlayerController::updateInteraction(float delta) { } auto& target = indices->blocks.require(vox->id); if (lclick) { + scripting::on_block_breaking(&player, target, iend); if (player.isInstantDestruction() && target.breakable) { blocksController.breakBlock( &player, target, iend.x, iend.y, iend.z @@ -567,10 +544,6 @@ void PlayerController::updateInteraction(float delta) { if (def && rclick) { processRightClick(*def, target); } - if (Events::jactive(BIND_PLAYER_PICK)) { - auto coord = selection.actualPosition; - pick_block(indices, target, player, coord.x, coord.y, coord.z); - } } Player* PlayerController::getPlayer() { diff --git a/src/logic/scripting/lua/libs/libfile.cpp b/src/logic/scripting/lua/libs/libfile.cpp index b5a7ae93..6b35b11e 100644 --- a/src/logic/scripting/lua/libs/libfile.cpp +++ b/src/logic/scripting/lua/libs/libfile.cpp @@ -57,12 +57,7 @@ static fs::path get_writeable_path(lua::State* L) { fs::path path = resolve_path(rawpath); auto entryPoint = rawpath.substr(0, rawpath.find(':')); if (writeable_entry_points.find(entryPoint) == writeable_entry_points.end()) { - if (lua::getglobal(L, "__vc_warning")) { - lua::pushstring(L, "writing to read-only entry point"); - lua::pushstring(L, entryPoint); - lua::pushinteger(L, 1); - lua::call_nothrow(L, 3); - } + throw std::runtime_error("access denied"); } return path; } @@ -149,27 +144,6 @@ static int l_read_bytes(lua::State* L) { ); } -static void read_bytes_from_table( - lua::State* L, int tableIndex, std::vector& bytes -) { - if (!lua::istable(L, tableIndex)) { - throw std::runtime_error("table expected"); - } else { - size_t size = lua::objlen(L, tableIndex); - for (size_t i = 0; i < size; i++) { - lua::rawgeti(L, i + 1, tableIndex); - const int byte = lua::tointeger(L, -1); - lua::pop(L); - if (byte < 0 || byte > 255) { - throw std::runtime_error( - "invalid byte '" + std::to_string(byte) + "'" - ); - } - bytes.push_back(byte); - } - } -} - static int l_write_bytes(lua::State* L) { fs::path path = get_writeable_path(L); @@ -181,7 +155,7 @@ static int l_write_bytes(lua::State* L) { } std::vector bytes; - read_bytes_from_table(L, 2, bytes); + lua::read_bytes_from_table(L, 2, bytes); return lua::pushboolean( L, files::write_bytes(path, bytes.data(), bytes.size()) ); @@ -223,7 +197,7 @@ static int l_list(lua::State* L) { static int l_gzip_compress(lua::State* L) { std::vector bytes; - read_bytes_from_table(L, 1, bytes); + lua::read_bytes_from_table(L, 1, bytes); auto compressed_bytes = gzip::compress(bytes.data(), bytes.size()); int newTable = lua::gettop(L); @@ -237,7 +211,7 @@ static int l_gzip_compress(lua::State* L) { static int l_gzip_decompress(lua::State* L) { std::vector bytes; - read_bytes_from_table(L, 1, bytes); + lua::read_bytes_from_table(L, 1, bytes); auto decompressed_bytes = gzip::decompress(bytes.data(), bytes.size()); int newTable = lua::gettop(L); @@ -264,6 +238,16 @@ static int l_read_combined_object(lua::State* L) { return lua::pushvalue(L, engine->getResPaths()->readCombinedObject(path)); } +static int l_is_writeable(lua::State* L) { + std::string rawpath = lua::require_string(L, 1); + fs::path path = resolve_path(rawpath); + auto entryPoint = rawpath.substr(0, rawpath.find(':')); + if (writeable_entry_points.find(entryPoint) == writeable_entry_points.end()) { + return lua::pushboolean(L, false); + } + return lua::pushboolean(L, true); +} + const luaL_Reg filelib[] = { {"exists", lua::wrap}, {"find", lua::wrap}, @@ -284,4 +268,5 @@ const luaL_Reg filelib[] = { {"gzip_decompress", lua::wrap}, {"read_combined_list", lua::wrap}, {"read_combined_object", lua::wrap}, + {"is_writeable", lua::wrap}, {NULL, NULL}}; diff --git a/src/logic/scripting/lua/libs/libinventory.cpp b/src/logic/scripting/lua/libs/libinventory.cpp index 8f1f5385..65f0dd7c 100644 --- a/src/logic/scripting/lua/libs/libinventory.cpp +++ b/src/logic/scripting/lua/libs/libinventory.cpp @@ -13,15 +13,15 @@ static void validate_itemid(itemid_t id) { } } -static std::shared_ptr get_inventory(int64_t id) { +static Inventory& get_inventory(int64_t id) { auto inv = level->inventories->get(id); if (inv == nullptr) { throw std::runtime_error("inventory not found: " + std::to_string(id)); } - return inv; + return *inv; } -static std::shared_ptr get_inventory(int64_t id, int arg) { +static Inventory& get_inventory(int64_t id, int arg) { auto inv = level->inventories->get(id); if (inv == nullptr) { throw std::runtime_error( @@ -29,62 +29,62 @@ static std::shared_ptr get_inventory(int64_t id, int arg) { std::to_string(arg) ); } - return inv; + return *inv; } -static void validate_slotid(int slotid, Inventory* inv) { - if (static_cast(slotid) >= inv->size()) { +static void validate_slotid(int slotid, const Inventory& inv) { + if (static_cast(slotid) >= inv.size()) { throw std::runtime_error( "slot index is out of range [0..inventory.size(invid)]" ); } } -static int l_inventory_get(lua::State* L) { +static int l_get(lua::State* L) { auto invid = lua::tointeger(L, 1); auto slotid = lua::tointeger(L, 2); auto inv = get_inventory(invid); - validate_slotid(slotid, inv.get()); - const ItemStack& item = inv->getSlot(slotid); + validate_slotid(slotid, inv); + const ItemStack& item = inv.getSlot(slotid); lua::pushinteger(L, item.getItemId()); lua::pushinteger(L, item.getCount()); return 2; } -static int l_inventory_set(lua::State* L) { +static int l_set(lua::State* L) { auto invid = lua::tointeger(L, 1); auto slotid = lua::tointeger(L, 2); auto itemid = lua::tointeger(L, 3); auto count = lua::tointeger(L, 4); validate_itemid(itemid); - auto inv = get_inventory(invid); + auto& inv = get_inventory(invid); - validate_slotid(slotid, inv.get()); - ItemStack& item = inv->getSlot(slotid); + validate_slotid(slotid, inv); + ItemStack& item = inv.getSlot(slotid); item.set(ItemStack(itemid, count)); return 0; } -static int l_inventory_size(lua::State* L) { +static int l_size(lua::State* L) { auto invid = lua::tointeger(L, 1); - auto inv = get_inventory(invid); - return lua::pushinteger(L, inv->size()); + auto& inv = get_inventory(invid); + return lua::pushinteger(L, inv.size()); } -static int l_inventory_add(lua::State* L) { +static int l_add(lua::State* L) { auto invid = lua::tointeger(L, 1); auto itemid = lua::tointeger(L, 2); auto count = lua::tointeger(L, 3); validate_itemid(itemid); - auto inv = get_inventory(invid); + auto& inv = get_inventory(invid); ItemStack item(itemid, count); - inv->move(item, indices); + inv.move(item, indices); return lua::pushinteger(L, item.getCount()); } -static int l_inventory_get_block(lua::State* L) { +static int l_get_block(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); @@ -92,7 +92,7 @@ static int l_inventory_get_block(lua::State* L) { return lua::pushinteger(L, id); } -static int l_inventory_bind_block(lua::State* L) { +static int l_bind_block(lua::State* L) { auto id = lua::tointeger(L, 1); auto x = lua::tointeger(L, 2); auto y = lua::tointeger(L, 3); @@ -101,7 +101,7 @@ static int l_inventory_bind_block(lua::State* L) { return 0; } -static int l_inventory_unbind_block(lua::State* L) { +static int l_unbind_block(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); auto z = lua::tointeger(L, 3); @@ -109,7 +109,7 @@ static int l_inventory_unbind_block(lua::State* L) { return 0; } -static int l_inventory_create(lua::State* L) { +static int l_create(lua::State* L) { auto invsize = lua::tointeger(L, 1); auto inv = level->inventories->create(invsize); if (inv == nullptr) { @@ -118,18 +118,13 @@ static int l_inventory_create(lua::State* L) { return lua::pushinteger(L, inv->getId()); } -static int l_inventory_remove(lua::State* L) { +static int l_remove(lua::State* L) { auto invid = lua::tointeger(L, 1); - auto inv = get_inventory(invid); - if (inv == nullptr) { - return 0; - } - level->inventories->remove(invid); return 0; } -static int l_inventory_clone(lua::State* L) { +static int l_clone(lua::State* L) { auto id = lua::tointeger(L, 1); auto clone = level->inventories->clone(id); if (clone == nullptr) { @@ -138,54 +133,69 @@ static int l_inventory_clone(lua::State* L) { return lua::pushinteger(L, clone->getId()); } -static int l_inventory_move(lua::State* L) { +static int l_move(lua::State* L) { auto invAid = lua::tointeger(L, 1); auto slotAid = lua::tointeger(L, 2); - auto invA = get_inventory(invAid, 1); - validate_slotid(slotAid, invA.get()); + auto& invA = get_inventory(invAid, 1); + validate_slotid(slotAid, invA); auto invBid = lua::tointeger(L, 3); auto slotBid = lua::isnil(L, 4) ? -1 : lua::tointeger(L, 4); - auto invB = get_inventory(invBid, 3); - auto& slot = invA->getSlot(slotAid); + auto& invB = get_inventory(invBid, 3); + auto& slot = invA.getSlot(slotAid); if (slotBid == -1) { - invB->move(slot, content->getIndices()); + invB.move(slot, content->getIndices()); } else { - invB->move(slot, content->getIndices(), slotBid, slotBid + 1); + invB.move(slot, content->getIndices(), slotBid, slotBid + 1); } return 0; } -static int l_inventory_move_range(lua::State* L) { +static int l_move_range(lua::State* L) { auto invAid = lua::tointeger(L, 1); auto slotAid = lua::tointeger(L, 2); - auto invA = get_inventory(invAid, 1); - validate_slotid(slotAid, invA.get()); + auto& invA = get_inventory(invAid, 1); + validate_slotid(slotAid, invA); auto invBid = lua::tointeger(L, 3); auto slotBegin = lua::isnoneornil(L, 4) ? -1 : lua::tointeger(L, 4); auto slotEnd = lua::isnoneornil(L, 5) ? -1 : lua::tointeger(L, 5) + 1; auto invB = get_inventory(invBid, 3); - auto& slot = invA->getSlot(slotAid); + auto& slot = invA.getSlot(slotAid); if (slotBegin == -1) { - invB->move(slot, content->getIndices()); + invB.move(slot, content->getIndices()); } else { - invB->move(slot, content->getIndices(), slotBegin, slotEnd); + invB.move(slot, content->getIndices(), slotBegin, slotEnd); } return 0; } +static int l_find_by_item(lua::State* L) { + auto invId = lua::tointeger(L, 1); + auto& inv = get_inventory(invId, 1); + integer_t blockid = lua::tointeger(L, 2); + integer_t begin = lua::isnumber(L, 3) ? lua::tointeger(L, 3) : 0; + integer_t end = lua::isnumber(L, 4) ? lua::tointeger(L, 4) : -1; + integer_t minCount = lua::isnumber(L, 5) ? lua::tointeger(L, 5) : blockid != 0; + size_t index = inv.findSlotByItem(blockid, begin, end, minCount); + if (index == Inventory::npos) { + return 0; + } + return lua::pushinteger(L, index); +} + const luaL_Reg inventorylib[] = { - {"get", lua::wrap}, - {"set", lua::wrap}, - {"size", lua::wrap}, - {"add", lua::wrap}, - {"move", lua::wrap}, - {"move_range", lua::wrap}, - {"get_block", lua::wrap}, - {"bind_block", lua::wrap}, - {"unbind_block", lua::wrap}, - {"create", lua::wrap}, - {"remove", lua::wrap}, - {"clone", lua::wrap}, + {"get", lua::wrap}, + {"set", lua::wrap}, + {"size", lua::wrap}, + {"add", lua::wrap}, + {"move", lua::wrap}, + {"move_range", lua::wrap}, + {"find_by_item", lua::wrap}, + {"get_block", lua::wrap}, + {"bind_block", lua::wrap}, + {"unbind_block", lua::wrap}, + {"create", lua::wrap}, + {"remove", lua::wrap}, + {"clone", lua::wrap}, {NULL, NULL}}; diff --git a/src/logic/scripting/lua/libs/libplayer.cpp b/src/logic/scripting/lua/libs/libplayer.cpp index 00c4585d..7c1c11ce 100644 --- a/src/logic/scripting/lua/libs/libplayer.cpp +++ b/src/logic/scripting/lua/libs/libplayer.cpp @@ -101,6 +101,13 @@ static int l_get_inv(lua::State* L) { return 2; } +static int l_set_selected_slot(lua::State* L) { + if (auto player = get_player(L, 1)) { + player->setChosenSlot(lua::tointeger(L, 2) % 10); + } + return 0; +} + static int l_is_flight(lua::State* L) { if (auto player = get_player(L, 1)) { return lua::pushboolean(L, player->isFlight()); @@ -157,6 +164,20 @@ static int l_set_instant_destruction(lua::State* L) { return 0; } +static int l_is_loading_chunks(lua::State* L) { + if (auto player = get_player(L, 1)) { + return lua::pushboolean(L, player->isLoadingChunks()); + } + return 0; +} + +static int l_set_loading_chunks(lua::State* L) { + if (auto player = get_player(L, 1)) { + player->setLoadingChunks(lua::toboolean(L, 2)); + } + return 0; +} + static int l_get_selected_block(lua::State* L) { if (auto player = get_player(L, 1)) { if (player->selection.vox.id == BLOCK_VOID) { @@ -275,6 +296,9 @@ const luaL_Reg playerlib[] = { {"set_infinite_items", lua::wrap}, {"is_instant_destruction", lua::wrap}, {"set_instant_destruction", lua::wrap}, + {"is_loading_chunks", lua::wrap}, + {"set_loading_chunks", lua::wrap}, + {"set_selected_slot", lua::wrap}, {"get_selected_block", lua::wrap}, {"get_selected_entity", lua::wrap}, {"set_spawnpoint", lua::wrap}, diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index 983abb39..f9da3b21 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "data/dv.hpp" @@ -698,4 +699,25 @@ namespace lua { } return def; } + + inline void read_bytes_from_table( + lua::State* L, int tableIndex, std::vector& bytes + ) { + if (!lua::istable(L, tableIndex)) { + throw std::runtime_error("table expected"); + } else { + size_t size = lua::objlen(L, tableIndex); + for (size_t i = 0; i < size; i++) { + lua::rawgeti(L, i + 1, tableIndex); + const int byte = lua::tointeger(L, -1); + lua::pop(L); + if (byte < 0 || byte > 255) { + throw std::runtime_error( + "invalid byte '" + std::to_string(byte) + "'" + ); + } + bytes.push_back(byte); + } + } + } } diff --git a/src/logic/scripting/lua/usertypes/lua_type_bytearray.cpp b/src/logic/scripting/lua/usertypes/lua_type_bytearray.cpp index 0493995b..fc7ecf6f 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_bytearray.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_bytearray.cpp @@ -2,6 +2,7 @@ #include +#include "util/listutil.hpp" #include "../lua_util.hpp" using namespace lua; @@ -18,8 +19,16 @@ LuaBytearray::~LuaBytearray() { static int l_append(lua::State* L) { if (auto buffer = touserdata(L, 1)) { - auto value = tointeger(L, 2); - buffer->data().push_back(static_cast(value)); + if (lua::isnumber(L, 2)) { + auto value = tointeger(L, 2); + buffer->data().push_back(static_cast(value)); + } else if (lua::istable(L, 2)) { + lua::read_bytes_from_table(L, 2, buffer->data()); + } else if (auto extension = lua::touserdata(L, 2)) { + util::concat(buffer->data(), extension->data()); + } else { + throw std::runtime_error("integer/table/Bytearray expected"); + } } return 0; } @@ -34,8 +43,19 @@ static int l_insert(lua::State* L) { if (static_cast(index) > data.size()) { return 0; } - auto value = tointeger(L, 3); - data.insert(data.begin() + index, static_cast(value)); + if (lua::isnumber(L, 3)) { + auto value = tointeger(L, 3); + data.insert(data.begin() + index, static_cast(value)); + } else if (lua::istable(L, 3)) { + std::vector temp; + lua::read_bytes_from_table(L, 3, temp); + data.insert(data.begin() + index, temp.begin(), temp.end()); + } else if (auto extension = lua::touserdata(L, 3)) { + const std::vector& src = extension->data(); + data.insert(data.begin() + index, src.begin(), src.end()); + } else { + throw std::runtime_error("integer/table/Bytearray expected"); + } return 0; } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 1732a88a..96e9f06e 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -336,16 +336,24 @@ void scripting::random_update_block(const Block& block, const glm::ivec3& pos) { }); } -void scripting::on_block_placed( - Player* player, const Block& block, const glm::ivec3& pos +/// TODO: replace template with index +template +static bool on_block_common( + const std::string& suffix, + bool blockfunc, + Player* player, + const Block& block, + const glm::ivec3& pos ) { - if (block.rt.funcsset.onplaced) { - std::string name = block.name + ".placed"; - lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) { - lua::pushivec_stack(L, pos); - lua::pushinteger(L, player ? player->getId() : -1); - return 4; - }); + bool result = false; + if (blockfunc) { + std::string name = block.name + "." + suffix; + result = + lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) { + lua::pushivec_stack(L, pos); + lua::pushinteger(L, player->getId()); + return 4; + }); } auto args = [&](lua::State* L) { lua::pushinteger(L, block.rt.id); @@ -354,93 +362,53 @@ void scripting::on_block_placed( return 5; }; for (auto& [packid, pack] : content->getPacks()) { - if (pack->worldfuncsset.onblockplaced) { + if (pack->worldfuncsset.*worldfunc) { lua::emit_event( - lua::get_main_state(), packid + ":.blockplaced", args + lua::get_main_state(), packid + ":.block" + suffix, args ); } } + return result; +} + +void scripting::on_block_placed( + Player* player, const Block& block, const glm::ivec3& pos +) { + on_block_common<&WorldFuncsSet::onblockplaced>( + "placed", block.rt.funcsset.onplaced, player, block, pos + ); } void scripting::on_block_replaced( Player* player, const Block& block, const glm::ivec3& pos ) { - if (block.rt.funcsset.onreplaced) { - std::string name = block.name + ".replaced"; - lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) { - lua::pushivec_stack(L, pos); - lua::pushinteger(L, player ? player->getId() : -1); - return 4; - }); - } - auto args = [&](lua::State* L) { - lua::pushinteger(L, block.rt.id); - lua::pushivec_stack(L, pos); - lua::pushinteger(L, player ? player->getId() : -1); - return 5; - }; - for (auto& [packid, pack] : content->getPacks()) { - if (pack->worldfuncsset.onblockreplaced) { - lua::emit_event( - lua::get_main_state(), packid + ":.blockreplaced", args - ); - } - } + on_block_common<&WorldFuncsSet::onblockreplaced>( + "replaced", block.rt.funcsset.onreplaced, player, block, pos + ); +} + +void scripting::on_block_breaking( + Player* player, const Block& block, const glm::ivec3& pos +) { + on_block_common<&WorldFuncsSet::onblockbreaking>( + "breaking", block.rt.funcsset.onbreaking, player, block, pos + ); } void scripting::on_block_broken( Player* player, const Block& block, const glm::ivec3& pos ) { - if (block.rt.funcsset.onbroken) { - std::string name = block.name + ".broken"; - lua::emit_event( - lua::get_main_state(), - name, - [pos, player](auto L) { - lua::pushivec_stack(L, pos); - lua::pushinteger(L, player ? player->getId() : -1); - return 4; - } - ); - } - auto args = [&](lua::State* L) { - lua::pushinteger(L, block.rt.id); - lua::pushivec_stack(L, pos); - lua::pushinteger(L, player ? player->getId() : -1); - return 5; - }; - for (auto& [packid, pack] : content->getPacks()) { - if (pack->worldfuncsset.onblockbroken) { - lua::emit_event( - lua::get_main_state(), packid + ":.blockbroken", args - ); - } - } + on_block_common<&WorldFuncsSet::onblockbroken>( + "broken", block.rt.funcsset.onbroken, player, block, pos + ); } bool scripting::on_block_interact( Player* player, const Block& block, const glm::ivec3& pos ) { - std::string name = block.name + ".interact"; - auto result = lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) { - lua::pushivec_stack(L, pos); - lua::pushinteger(L, player->getId()); - return 4; - }); - auto args = [&](lua::State* L) { - lua::pushinteger(L, block.rt.id); - lua::pushivec_stack(L, pos); - lua::pushinteger(L, player ? player->getId() : -1); - return 5; - }; - for (auto& [packid, pack] : content->getPacks()) { - if (pack->worldfuncsset.onblockinteract) { - lua::emit_event( - lua::get_main_state(), packid + ":.blockinteract", args - ); - } - } - return result; + return on_block_common<&WorldFuncsSet::onblockinteract>( + "interact", block.rt.funcsset.oninteract, player, block, pos + ); } void scripting::on_player_tick(Player* player, int tps) { @@ -599,7 +567,7 @@ static void process_entity_callback( static void process_entity_callback( const Entity& entity, const std::string& name, - bool entity_funcs_set::*flag, + bool EntityFuncsSet::*flag, std::function args ) { const auto& script = entity.getScripting(); @@ -612,7 +580,7 @@ static void process_entity_callback( void scripting::on_entity_despawn(const Entity& entity) { process_entity_callback( - entity, "on_despawn", &entity_funcs_set::on_despawn, nullptr + entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr ); auto L = lua::get_main_state(); lua::get_from(L, "stdcomp", "remove_Entity", true); @@ -624,20 +592,20 @@ void scripting::on_entity_grounded(const Entity& entity, float force) { process_entity_callback( entity, "on_grounded", - &entity_funcs_set::on_grounded, + &EntityFuncsSet::on_grounded, [force](auto L) { return lua::pushnumber(L, force); } ); } void scripting::on_entity_fall(const Entity& entity) { process_entity_callback( - entity, "on_fall", &entity_funcs_set::on_fall, nullptr + entity, "on_fall", &EntityFuncsSet::on_fall, nullptr ); } void scripting::on_entity_save(const Entity& entity) { process_entity_callback( - entity, "on_save", &entity_funcs_set::on_save, nullptr + entity, "on_save", &EntityFuncsSet::on_save, nullptr ); } @@ -647,7 +615,7 @@ void scripting::on_sensor_enter( process_entity_callback( entity, "on_sensor_enter", - &entity_funcs_set::on_sensor_enter, + &EntityFuncsSet::on_sensor_enter, [index, oid](auto L) { lua::pushinteger(L, index); lua::pushinteger(L, oid); @@ -662,7 +630,7 @@ void scripting::on_sensor_exit( process_entity_callback( entity, "on_sensor_exit", - &entity_funcs_set::on_sensor_exit, + &EntityFuncsSet::on_sensor_exit, [index, oid](auto L) { lua::pushinteger(L, index); lua::pushinteger(L, oid); @@ -675,7 +643,7 @@ void scripting::on_aim_on(const Entity& entity, Player* player) { process_entity_callback( entity, "on_aim_on", - &entity_funcs_set::on_aim_on, + &EntityFuncsSet::on_aim_on, [player](auto L) { return lua::pushinteger(L, player->getId()); } ); } @@ -684,7 +652,7 @@ void scripting::on_aim_off(const Entity& entity, Player* player) { process_entity_callback( entity, "on_aim_off", - &entity_funcs_set::on_aim_off, + &EntityFuncsSet::on_aim_off, [player](auto L) { return lua::pushinteger(L, player->getId()); } ); } @@ -695,7 +663,7 @@ void scripting::on_attacked( process_entity_callback( entity, "on_attacked", - &entity_funcs_set::on_attacked, + &EntityFuncsSet::on_attacked, [player, attacker](auto L) { lua::pushinteger(L, attacker); lua::pushinteger(L, player->getId()); @@ -708,7 +676,7 @@ void scripting::on_entity_used(const Entity& entity, Player* player) { process_entity_callback( entity, "on_used", - &entity_funcs_set::on_used, + &EntityFuncsSet::on_used, [player](auto L) { return lua::pushinteger(L, player->getId()); } ); } @@ -796,7 +764,7 @@ void scripting::load_content_script( const std::string& prefix, const fs::path& file, const std::string& fileName, - block_funcs_set& funcsset + BlockFuncsSet& funcsset ) { int env = *senv; lua::pop(lua::get_main_state(), load_script(env, "block", file, fileName)); @@ -804,6 +772,8 @@ void scripting::load_content_script( funcsset.update = register_event(env, "on_update", prefix + ".update"); funcsset.randupdate = register_event(env, "on_random_update", prefix + ".randupdate"); + funcsset.onbreaking = + register_event(env, "on_breaking", prefix + ".breaking"); funcsset.onbroken = register_event(env, "on_broken", prefix + ".broken"); funcsset.onplaced = register_event(env, "on_placed", prefix + ".placed"); funcsset.onreplaced = @@ -819,7 +789,7 @@ void scripting::load_content_script( const std::string& prefix, const fs::path& file, const std::string& fileName, - item_funcs_set& funcsset + ItemFuncsSet& funcsset ) { int env = *senv; lua::pop(lua::get_main_state(), load_script(env, "item", file, fileName)); @@ -846,7 +816,7 @@ void scripting::load_world_script( const std::string& prefix, const fs::path& file, const std::string& fileName, - world_funcs_set& funcsset + WorldFuncsSet& funcsset ) { int env = *senv; lua::pop(lua::get_main_state(), load_script(env, "world", file, fileName)); @@ -857,6 +827,8 @@ void scripting::load_world_script( register_event(env, "on_world_quit", prefix + ":.worldquit"); funcsset.onblockplaced = register_event(env, "on_block_placed", prefix + ":.blockplaced"); + funcsset.onblockbreaking = + register_event(env, "on_block_breaking", prefix + ":.blockbreaking"); funcsset.onblockbroken = register_event(env, "on_block_broken", prefix + ":.blockbroken"); funcsset.onblockreplaced = diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 4ccba2cb..fb552f66 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -21,9 +21,9 @@ class Player; struct ItemDef; class Inventory; class UiDocument; -struct block_funcs_set; -struct item_funcs_set; -struct world_funcs_set; +struct BlockFuncsSet; +struct ItemFuncsSet; +struct WorldFuncsSet; struct UserComponent; struct uidocscript; class BlocksController; @@ -77,6 +77,9 @@ namespace scripting { void on_block_replaced( Player* player, const Block& block, const glm::ivec3& pos ); + void on_block_breaking( + Player* player, const Block& block, const glm::ivec3& pos + ); void on_block_broken( Player* player, const Block& block, const glm::ivec3& pos ); @@ -141,7 +144,7 @@ namespace scripting { const std::string& prefix, const std::filesystem::path& file, const std::string& fileName, - block_funcs_set& funcsset + BlockFuncsSet& funcsset ); /// @brief Load script associated with an Item @@ -155,7 +158,7 @@ namespace scripting { const std::string& prefix, const std::filesystem::path& file, const std::string& fileName, - item_funcs_set& funcsset + ItemFuncsSet& funcsset ); /// @brief Load component script @@ -184,7 +187,7 @@ namespace scripting { const std::string& packid, const std::filesystem::path& file, const std::string& fileName, - world_funcs_set& funcsset + WorldFuncsSet& funcsset ); /// @brief Load script associated with an UiDocument diff --git a/src/maths/UVFace.hpp b/src/maths/UVFace.hpp new file mode 100644 index 00000000..538ae210 --- /dev/null +++ b/src/maths/UVFace.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "UVRegion.hpp" + +struct UVFace { + std::array points; + + UVFace(const UVRegion& region) { + points[0] = {region.u1, region.v1}; + points[1] = {region.u2, region.v1}; + points[2] = {region.u2, region.v2}; + points[3] = {region.u1, region.v2}; + } + + template + inline void rotate(int times) { + int times = n % 4; + if (times < 0) { + times += 4; + } + std::array temp = points; + points[0] = temp[times]; + points[1] = temp[(times + 1) % 4]; + points[2] = temp[(times + 2) % 4]; + points[3] = temp[(times + 3) % 4]; + } +}; diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 1a27243f..e047fdd4 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -162,7 +162,7 @@ entityid_t Entities::spawn( for (auto& componentName : def.components) { auto component = std::make_unique( - componentName, entity_funcs_set {}, nullptr + componentName, EntityFuncsSet {}, nullptr ); scripting.components.emplace_back(std::move(component)); } diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 0026d15e..0c93ae74 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -14,7 +14,7 @@ #include #include -struct entity_funcs_set { +struct EntityFuncsSet { bool init; bool on_despawn; bool on_grounded; @@ -77,11 +77,11 @@ struct Rigidbody { struct UserComponent { std::string name; - entity_funcs_set funcsset; + EntityFuncsSet funcsset; scriptenv env; UserComponent( - const std::string& name, entity_funcs_set funcsset, scriptenv env + const std::string& name, EntityFuncsSet funcsset, scriptenv env ) : name(name), funcsset(funcsset), env(env) { } diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index 0a2106ed..b20b32e9 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -135,18 +135,7 @@ void Player::updateInput(PlayerInput& input, float delta) { hitbox->velocity.y = JUMP_FORCE; } - if ((input.flight && !noclip) || (input.noclip && flight == noclip)) { - flight = !flight; - if (flight) { - hitbox->velocity.y += 1.0f; - } - } hitbox->type = noclip ? BodyType::KINEMATIC : BodyType::DYNAMIC; - if (input.noclip) { - noclip = !noclip; - } - input.noclip = false; - input.flight = false; } void Player::updateSelectedEntity() { @@ -262,6 +251,14 @@ void Player::setInstantDestruction(bool flag) { instantDestruction = flag; } +bool Player::isLoadingChunks() const { + return loadingChunks; +} + +void Player::setLoadingChunks(bool flag) { + loadingChunks = flag; +} + entityid_t Player::getEntity() const { return eid; } @@ -308,6 +305,7 @@ dv::value Player::serialize() const { root["noclip"] = noclip; root["infinite-items"] = infiniteItems; root["instant-destruction"] = instantDestruction; + root["loading-chunks"] = loadingChunks; root["chosen-slot"] = chosenSlot; root["entity"] = eid; root["inventory"] = inventory->serialize(); @@ -340,7 +338,8 @@ void Player::deserialize(const dv::value& src) { noclip = src["noclip"].asBoolean(); src.at("infinite-items").get(infiniteItems); src.at("instant-destruction").get(instantDestruction); - + src.at("loading-chunks").get(loadingChunks); + setChosenSlot(src["chosen-slot"].asInteger()); eid = src["entity"].asNumber(); diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index 1f57c6f7..b7ace8ff 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -27,8 +27,6 @@ struct PlayerInput { bool shift : 1; bool cheat : 1; bool jump : 1; - bool noclip : 1; - bool flight : 1; }; struct CursorSelection { @@ -53,13 +51,13 @@ class Player : public Serializable { bool noclip = false; bool infiniteItems = true; bool instantDestruction = true; + bool loadingChunks = true; entityid_t eid; entityid_t selectedEid = 0; public: - std::unique_ptr chunks; // not in use yet + std::unique_ptr chunks; std::shared_ptr fpCamera, spCamera, tpCamera; - std::shared_ptr currentCamera; - bool debug = false; + std::shared_ptr currentCamera;; glm::vec3 cam {}; CursorSelection selection {}; @@ -99,6 +97,9 @@ public: bool isInstantDestruction() const; void setInstantDestruction(bool flag); + bool isLoadingChunks() const; + void setLoadingChunks(bool flag); + entityid_t getEntity() const; void setEntity(entityid_t eid); diff --git a/src/presets/ParticlesPreset.cpp b/src/presets/ParticlesPreset.cpp index 181dbeb5..8a007076 100644 --- a/src/presets/ParticlesPreset.cpp +++ b/src/presets/ParticlesPreset.cpp @@ -43,6 +43,9 @@ dv::value ParticlesPreset::serialize() const { root["explosion"] = dv::to_value(explosion); root["size"] = dv::to_value(size); root["size_spread"] = sizeSpread; + root["angle_spread"] = angleSpread; + root["min_angular_vel"] = minAngularVelocity; + root["max_angular_vel"] = maxAngularVelocity; root["spawn_spread"] = dv::to_value(size); root["spawn_shape"] = to_string(spawnShape); root["random_sub_uv"] = randomSubUV; @@ -58,6 +61,9 @@ void ParticlesPreset::deserialize(const dv::value& src) { src.at("spawn_interval").get(spawnInterval); src.at("lifetime").get(lifetime); src.at("lifetime_spread").get(lifetimeSpread); + src.at("angle_spread").get(angleSpread); + src.at("min_angular_vel").get(minAngularVelocity); + src.at("max_angular_vel").get(maxAngularVelocity); src.at("random_sub_uv").get(randomSubUV); if (src.has("velocity")) { dv::get_vec(src["velocity"], velocity); diff --git a/src/presets/ParticlesPreset.hpp b/src/presets/ParticlesPreset.hpp index f8d5f2b4..2b955d13 100644 --- a/src/presets/ParticlesPreset.hpp +++ b/src/presets/ParticlesPreset.hpp @@ -27,7 +27,7 @@ struct ParticlesPreset : public Serializable { /// @brief Use global up vector instead of camera-dependent one bool globalUpVector = false; /// @brief Max distance of actually spawning particles. - float maxDistance = 16.0f; + float maxDistance = 32.0f; /// @brief Particles spawn interval float spawnInterval = 0.1f; /// @brief Particle life time @@ -44,6 +44,12 @@ struct ParticlesPreset : public Serializable { glm::vec3 size {0.1f}; /// @brief Particles size spread float sizeSpread = 0.2f; + /// @brief Random initial angle spread + float angleSpread = 0.0f; + /// @brief Minimum angular velocity + float minAngularVelocity = 0.0f; + /// @brief Maximum angular velocity + float maxAngularVelocity = 0.0f; /// @brief Spawn spread shape ParticleSpawnShape spawnShape = BALL; /// @brief Spawn spread diff --git a/src/settings.hpp b/src/settings.hpp index b0eee035..53ebf5f9 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -63,16 +63,24 @@ struct GraphicsSettings { NumberSetting gamma {1.0f, 0.4f, 1.0f}; /// @brief Enable blocks backlight to prevent complete darkness FlagSetting backlight {true}; + /// @brief Disable culling with 'optional' mode + FlagSetting denseRender {true}; /// @brief Enable chunks frustum culling FlagSetting frustumCulling {true}; + /// @brief Skybox texture face resolution IntegerSetting skyboxResolution {64 + 32, 64, 128}; + /// @brief Chunk renderer vertices buffer capacity IntegerSetting chunkMaxVertices {200'000, 0, 4'000'000}; + /// @brief Chunk renderer vertices buffer capacity in dense render mode + IntegerSetting chunkMaxVerticesDense {800'000, 0, 8'000'000}; + /// @brief Limit of chunk renderers count IntegerSetting chunkMaxRenderers {6, -4, 32}; }; struct DebugSettings { /// @brief Turns off chunks saving/loading FlagSetting generatorTestMode {false}; + /// @brief Write lights cache FlagSetting doWriteLights {true}; }; diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index 520f9f99..96bca245 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -49,6 +49,30 @@ std::optional BlockModel_from(std::string_view str) { return std::nullopt; } +std::string to_string(CullingMode mode) { + switch (mode) { + case CullingMode::DEFAULT: + return "default"; + case CullingMode::OPTIONAL: + return "optional"; + case CullingMode::DISABLED: + return "disabled"; + default: + return "unknown"; + } +} + +std::optional CullingMode_from(std::string_view str) { + if (str == "default") { + return CullingMode::DEFAULT; + } else if (str == "optional") { + return CullingMode::OPTIONAL; + } else if (str == "disabled") { + return CullingMode::DISABLED; + } + return std::nullopt; +} + CoordSystem::CoordSystem(glm::ivec3 axisX, glm::ivec3 axisY, glm::ivec3 axisZ) : axisX(axisX), axisY(axisY), axisZ(axisZ) { fix = glm::ivec3(0); diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 557bf04c..83dafa47 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -34,10 +34,11 @@ inline constexpr size_t MAX_USER_BLOCK_FIELDS_SIZE = 240; inline std::string DEFAULT_MATERIAL = "base:stone"; -struct block_funcs_set { +struct BlockFuncsSet { bool init : 1; bool update : 1; bool onplaced : 1; + bool onbreaking : 1; bool onbroken : 1; bool onreplaced : 1; bool oninteract : 1; @@ -97,6 +98,15 @@ enum class BlockModel { std::string to_string(BlockModel model); std::optional BlockModel_from(std::string_view str); +enum class CullingMode { + DEFAULT, + OPTIONAL, + DISABLED, +}; + +std::string to_string(CullingMode mode); +std::optional CullingMode_from(std::string_view str); + using BoxModel = AABB; /// @brief Common kit of block properties applied to groups of blocks @@ -142,6 +152,9 @@ public: std::string modelName = ""; + /// @brief Culling mode + CullingMode culling = CullingMode::DEFAULT; + /// @brief Does the block passing lights into itself bool lightPassing = false; @@ -229,7 +242,7 @@ public: std::vector hitboxes[BlockRotProfile::MAX_COUNT]; /// @brief set of block callbacks flags - block_funcs_set funcsset {}; + BlockFuncsSet funcsset {}; /// @brief picking item integer id itemid_t pickingItem = 0;